source: trunk/qt/details.cc @ 14196

Last change on this file since 14196 was 14196, checked in by jordan, 10 years ago

(trunk, qt) #4813: allow launching files in Qt interface

  • Property svn:keywords set to Date Rev Author Id
File size: 40.2 KB
Line 
1/*
2 * This file Copyright (C) Mnemosyne LLC
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2
6 * as published by the Free Software Foundation.
7 *
8 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
9 *
10 * $Id: details.cc 14196 2013-09-08 19:03:25Z jordan $
11 */
12
13#include <cassert>
14#include <climits> /* INT_MAX */
15#include <ctime>
16
17#include <QCheckBox>
18#include <QComboBox>
19#include <QDateTime>
20#include <QDesktopServices>
21#include <QDialogButtonBox>
22#include <QDoubleSpinBox>
23#include <QEvent>
24#include <QFont>
25#include <QFontMetrics>
26#include <QHBoxLayout>
27#include <QHBoxLayout>
28#include <QHeaderView>
29#include <QInputDialog>
30#include <QItemSelectionModel>
31#include <QLabel>
32#include <QList>
33#include <QMap>
34#include <QMessageBox>
35#include <QPushButton>
36#include <QRadioButton>
37#include <QResizeEvent>
38#include <QSpinBox>
39#include <QStringList>
40#include <QStyle>
41#include <QTabWidget>
42#include <QTextBrowser>
43#include <QTreeView>
44#include <QTreeWidget>
45#include <QTreeWidgetItem>
46#include <QVBoxLayout>
47
48#include <libtransmission/transmission.h>
49#include <libtransmission/utils.h> // tr_getRatio ()
50
51#include "details.h"
52#include "file-tree.h"
53#include "formatter.h"
54#include "hig.h"
55#include "prefs.h"
56#include "session.h"
57#include "squeezelabel.h"
58#include "torrent.h"
59#include "torrent-model.h"
60#include "tracker-delegate.h"
61#include "tracker-model.h"
62#include "tracker-model-filter.h"
63
64class Prefs;
65class Session;
66
67/****
68*****
69****/
70
71namespace
72{
73  const int REFRESH_INTERVAL_MSEC = 4000;
74
75  const char * PREF_KEY ("pref-key");
76
77  enum // peer columns
78  {
79    COL_LOCK,
80    COL_UP,
81    COL_DOWN,
82    COL_PERCENT,
83    COL_STATUS,
84    COL_ADDRESS,
85    COL_CLIENT,
86    N_COLUMNS
87  };
88}
89
90/***
91****
92***/
93
94class PeerItem: public QTreeWidgetItem
95{
96    Peer peer;
97    QString collatedAddress;
98    QString status;
99
100  public:
101
102    virtual ~PeerItem () {}
103
104    PeerItem (const Peer& p)
105    {
106      peer = p;
107      int q[4];
108      if (sscanf (p.address.toUtf8 ().constData (), "%d.%d.%d.%d", q+0, q+1, q+2, q+3) == 4)
109        collatedAddress.sprintf ("%03d.%03d.%03d.%03d", q[0], q[1], q[2], q[3]);
110      else
111        collatedAddress = p.address;
112    }
113
114  public:
115
116    void refresh (const Peer& p) { peer = p; }
117
118    void setStatus (const QString& s) { status = s; }
119
120    virtual bool operator< (const QTreeWidgetItem & other) const
121    {
122      const PeerItem * i = dynamic_cast<const PeerItem*> (&other);
123      QTreeWidget * tw (treeWidget ());
124      const int column = tw ? tw->sortColumn () : 0;
125      switch (column)
126        {
127          case COL_UP: return peer.rateToPeer < i->peer.rateToPeer;
128          case COL_DOWN: return peer.rateToClient < i->peer.rateToClient;
129          case COL_PERCENT: return peer.progress < i->peer.progress;
130          case COL_STATUS: return status < i->status;
131          case COL_CLIENT: return peer.clientName < i->peer.clientName;
132          case COL_LOCK: return peer.isEncrypted && !i->peer.isEncrypted;
133          default: return collatedAddress < i->collatedAddress;
134        }
135    }
136};
137
138/***
139****
140***/
141
142QIcon
143Details :: getStockIcon (const QString& freedesktop_name, int fallback)
144{
145  QIcon icon = QIcon::fromTheme (freedesktop_name);
146
147  if (icon.isNull ())
148    icon = style ()->standardIcon (QStyle::StandardPixmap (fallback), 0, this);
149
150  return icon;
151}
152
153Details :: Details (Session       & session,
154                    Prefs         & prefs,
155                    TorrentModel  & model,
156                    QWidget       * parent):
157  QDialog (parent, Qt::Dialog),
158  mySession (session),
159  myPrefs (prefs),
160  myModel (model),
161  myChangedTorrents (false),
162  myHavePendingRefresh (false)
163{
164  QVBoxLayout * layout = new QVBoxLayout (this);
165
166  setWindowTitle (tr ("Torrent Properties"));
167
168  QTabWidget * t = new QTabWidget (this);
169  QWidget * w;
170  t->addTab (w = createInfoTab (),      tr ("Information"));
171  myWidgets << w;
172  t->addTab (w = createPeersTab (),     tr ("Peers"));
173  myWidgets << w;
174  t->addTab (w = createTrackerTab (),   tr ("Tracker"));
175  myWidgets << w;
176  t->addTab (w = createFilesTab (),     tr ("Files"));
177  myWidgets << w;
178  t->addTab (w = createOptionsTab (),   tr ("Options"));
179  myWidgets << w;
180  layout->addWidget (t);
181
182  QDialogButtonBox * buttons = new QDialogButtonBox (QDialogButtonBox::Close, Qt::Horizontal, this);
183  connect (buttons, SIGNAL (rejected ()), this, SLOT (close ()));
184  layout->addWidget (buttons);
185  QWidget::setAttribute (Qt::WA_DeleteOnClose, true);
186
187  QList<int> initKeys;
188  initKeys << Prefs :: SHOW_TRACKER_SCRAPES
189           << Prefs :: SHOW_BACKUP_TRACKERS;
190  foreach (int key, initKeys)
191    refreshPref (key);
192
193  connect (&myTimer, SIGNAL (timeout ()), this, SLOT (onTimer ()));
194  connect (&myPrefs, SIGNAL (changed (int)), this, SLOT (refreshPref (int)));
195
196  onTimer ();
197  myTimer.setSingleShot (false);
198  myTimer.start (REFRESH_INTERVAL_MSEC);
199}
200
201Details :: ~Details ()
202{
203  myTrackerDelegate->deleteLater ();
204  myTrackerFilter->deleteLater ();
205  myTrackerModel->deleteLater ();
206}
207
208void
209Details :: setIds (const QSet<int>& ids)
210{
211  if (ids == myIds)
212    return;
213
214  myChangedTorrents = true;
215
216  // stop listening to the old torrents
217  foreach (int id, myIds)
218    {
219      const Torrent * tor = myModel.getTorrentFromId (id);
220      if (tor)
221        disconnect (tor, SIGNAL (torrentChanged (int)), this, SLOT (onTorrentChanged ()));
222    }
223
224  myFileTreeView->clear ();
225  myIds = ids;
226  myTrackerModel->refresh (myModel, myIds);
227
228  // listen to the new torrents
229  foreach (int id, myIds)
230    {
231      const Torrent * tor = myModel.getTorrentFromId (id);
232      if (tor)
233        connect (tor, SIGNAL (torrentChanged (int)), this, SLOT (onTorrentChanged ()));
234    }
235
236  foreach (QWidget * w, myWidgets)
237    w->setEnabled (false);
238
239  onTimer ();
240}
241
242void
243Details :: refreshPref (int key)
244{
245  QString str;
246
247  switch (key)
248    {
249      case Prefs :: SHOW_TRACKER_SCRAPES:
250        {
251          QItemSelectionModel * selectionModel (myTrackerView->selectionModel ());
252          const QItemSelection selection (selectionModel->selection ());
253          const QModelIndex currentIndex (selectionModel->currentIndex ());
254          myTrackerDelegate->setShowMore (myPrefs.getBool (key));
255          selectionModel->clear ();
256          myTrackerView->reset ();
257          selectionModel->select (selection, QItemSelectionModel::Select);
258          selectionModel->setCurrentIndex (currentIndex, QItemSelectionModel::NoUpdate);
259          break;
260        }
261
262      case Prefs :: SHOW_BACKUP_TRACKERS:
263        myTrackerFilter->setShowBackupTrackers (myPrefs.getBool (key));
264        break;
265
266      default:
267        break;
268    }
269}
270
271
272/***
273****
274***/
275
276QString
277Details :: timeToStringRounded (int seconds)
278{
279  if (seconds > 60)
280    seconds -= (seconds % 60);
281
282  return Formatter::timeToString (seconds);
283}
284
285void
286Details :: onTimer ()
287{
288  getNewData ();
289}
290
291void
292Details :: getNewData ()
293{
294  if (!myIds.empty ())
295    {
296      QSet<int> infos;
297      foreach (int id, myIds)
298        {
299          const Torrent * tor = myModel.getTorrentFromId (id);
300          if (tor->isMagnet ())
301            infos.insert (tor->id ());
302        }
303
304      if (!infos.isEmpty ())
305        mySession.initTorrents (infos);
306      mySession.refreshExtraStats (myIds);
307    }
308}
309
310void
311Details :: onTorrentChanged ()
312{
313  if (!myHavePendingRefresh)
314    {
315      myHavePendingRefresh = true;
316      QTimer::singleShot (100, this, SLOT (refresh ()));
317    }
318}
319
320namespace
321{
322  void setIfIdle (QComboBox * box, int i)
323  {
324    if (!box->hasFocus ())
325      {
326        box->blockSignals (true);
327        box->setCurrentIndex (i);
328        box->blockSignals (false);
329      }
330  }
331
332  void setIfIdle (QDoubleSpinBox * spin, double value)
333  {
334    if (!spin->hasFocus ())
335      {
336        spin->blockSignals (true);
337        spin->setValue (value);
338        spin->blockSignals (false);
339      }
340  }
341
342  void setIfIdle (QSpinBox * spin, int value)
343  {
344    if (!spin->hasFocus ())
345      {
346        spin->blockSignals (true);
347        spin->setValue (value);
348        spin->blockSignals (false);
349      }
350  }
351}
352
353void
354Details :: refresh ()
355{
356  const int n = myIds.size ();
357  const bool single = n == 1;
358  const QString blank;
359  const QFontMetrics fm (fontMetrics ());
360  QList<const Torrent*> torrents;
361  QString string;
362  const QString none = tr ("None");
363  const QString mixed = tr ("Mixed");
364  const QString unknown = tr ("Unknown");
365
366  // build a list of torrents
367  foreach (int id, myIds)
368    {
369      const Torrent * tor = myModel.getTorrentFromId (id);
370      if (tor)
371        torrents << tor;
372    }
373
374  ///
375  ///  activity tab
376  ///
377
378  // myStateLabel
379  if (torrents.empty ())
380    {
381      string = none;
382    }
383  else
384    {
385      bool isMixed = false;
386      bool allPaused = true;
387      bool allFinished = true;
388      const tr_torrent_activity baseline = torrents[0]->getActivity ();
389      foreach (const Torrent * t, torrents)
390        {
391          const tr_torrent_activity activity = t->getActivity ();
392          if (activity != baseline)
393            isMixed = true;
394          if (activity != TR_STATUS_STOPPED)
395            allPaused = allFinished = false;
396          if (!t->isFinished ())
397            allFinished = false;
398        }
399
400      if (isMixed)
401        string = mixed;
402      else if (allFinished)
403        string = tr ("Finished");
404      else if (allPaused)
405        string = tr ("Paused");
406      else
407        string = torrents[0]->activityString ();
408    }
409  myStateLabel->setText (string);
410  const QString stateString = string;
411
412  // myHaveLabel
413  double sizeWhenDone = 0;
414  double leftUntilDone = 0;
415  double available = 0;
416  int64_t haveTotal = 0;
417  int64_t haveVerified = 0;
418  int64_t haveUnverified = 0;
419  int64_t verifiedPieces = 0;
420  if (torrents.empty ())
421    {
422      string = none;
423    }
424  else
425    {
426      foreach (const Torrent * t, torrents)
427        {
428          if (t->hasMetadata ())
429            {
430              haveTotal += t->haveTotal ();
431              haveUnverified += t->haveUnverified ();
432              const uint64_t v = t->haveVerified ();
433              haveVerified += v;
434              if (t->pieceSize ())
435                verifiedPieces += v / t->pieceSize ();
436              sizeWhenDone += t->sizeWhenDone ();
437              leftUntilDone += t->leftUntilDone ();
438              available += t->sizeWhenDone () - t->leftUntilDone () + t->desiredAvailable ();
439            }
440        }
441
442      const double d = 100.0 * (sizeWhenDone ? (sizeWhenDone - leftUntilDone) / sizeWhenDone : 1);
443      QString pct = Formatter::percentToString (d);
444
445      if (!haveUnverified && !leftUntilDone)
446        {
447          string = tr ("%1 (100%)")
448                     .arg (Formatter::sizeToString (haveVerified));
449        }
450      else if (!haveUnverified)
451        {
452          string = tr ("%1 of %2 (%3%)")
453                     .arg (Formatter::sizeToString (haveVerified))
454                     .arg (Formatter::sizeToString (sizeWhenDone))
455                     .arg (pct);
456        }
457      else
458        {
459          string = tr ("%1 of %2 (%3%), %4 Unverified")
460                     .arg (Formatter::sizeToString (haveVerified + haveUnverified))
461                     .arg (Formatter::sizeToString (sizeWhenDone))
462                     .arg (pct)
463                     .arg (Formatter::sizeToString (haveUnverified));
464        }
465    }
466  myHaveLabel->setText (string);
467
468  // myAvailabilityLabel
469  if (torrents.empty ())
470    string = none;
471  else if (sizeWhenDone == 0)
472    string = none;
473  else
474    string = QString ("%1%").arg (Formatter::percentToString ( (100.0 * available) / sizeWhenDone));
475  myAvailabilityLabel->setText (string);
476
477  // myDownloadedLabel
478  if (torrents.empty ())
479    {
480      string = none;
481    }
482  else
483    {
484      uint64_t d = 0;
485      uint64_t f = 0;
486      foreach (const Torrent * t, torrents)
487        {
488          d += t->downloadedEver ();
489          f += t->failedEver ();
490        }
491      const QString dstr = Formatter::sizeToString (d);
492      const QString fstr = Formatter::sizeToString (f);
493      if (f)
494        string = tr ("%1 (%2 corrupt)").arg (dstr).arg (fstr);
495      else
496        string = dstr;
497    }
498  myDownloadedLabel->setText (string);
499
500  //  myUploadedLabel
501  if (torrents.empty ())
502    {
503      string = none;
504    }
505  else
506    {
507      uint64_t u = 0;
508      uint64_t d = 0;
509      foreach (const Torrent * t, torrents)
510        {
511          u += t->uploadedEver ();
512          d += t->downloadedEver ();
513        }
514      string = tr ("%1 (Ratio: %2)")
515                 .arg (Formatter::sizeToString (u))
516                 .arg (Formatter::ratioToString (tr_getRatio (u, d)));
517    }
518  myUploadedLabel->setText (string);
519
520  const QDateTime qdt_now = QDateTime::currentDateTime ();
521
522  // myRunTimeLabel
523  if (torrents.empty ())
524    {
525      string = none;
526    }
527  else
528    {
529      bool allPaused = true;
530      QDateTime baseline = torrents[0]->lastStarted ();
531      foreach (const Torrent * t, torrents)
532        {
533          if (baseline != t->lastStarted ())
534            baseline = QDateTime ();
535          if (!t->isPaused ())
536            allPaused = false;
537        }
538
539      if (allPaused)
540        string = stateString; // paused || finished
541      else if (baseline.isNull ())
542        string = mixed;
543      else
544        string = Formatter::timeToString (baseline.secsTo (qdt_now));
545    }
546  myRunTimeLabel->setText (string);
547
548
549  // myETALabel
550  string.clear ();
551  if (torrents.empty ())
552    {
553      string = none;
554    }
555  else
556    {
557      int baseline = torrents[0]->getETA ();
558      foreach (const Torrent * t, torrents)
559        {
560          if (baseline != t->getETA ())
561            {
562              string = mixed;
563              break;
564            }
565        }
566
567      if (string.isEmpty ())
568        {
569          if (baseline < 0)
570            string = tr ("Unknown");
571          else
572            string = Formatter::timeToString (baseline);
573       }
574    }
575  myETALabel->setText (string);
576
577
578  // myLastActivityLabel
579  if (torrents.empty ())
580    {
581      string = none;
582    }
583  else
584    {
585      QDateTime latest = torrents[0]->lastActivity ();
586      foreach (const Torrent * t, torrents)
587        {
588          const QDateTime dt = t->lastActivity ();
589          if (latest < dt)
590            latest = dt;
591        }
592
593      const int seconds = latest.isValid () ? latest.secsTo (qdt_now) : -1;
594      if (seconds < 0)
595        string = none;
596      else if (seconds < 5)
597        string = tr ("Active now");
598      else
599        string = tr ("%1 ago").arg (Formatter::timeToString (seconds));
600    }
601  myLastActivityLabel->setText (string);
602
603
604  if (torrents.empty ())
605    {
606      string = none;
607    }
608  else
609    {
610      string = torrents[0]->getError ();
611      foreach (const Torrent * t, torrents)
612        {
613          if (string != t->getError ())
614            {
615              string = mixed;
616              break;
617            }
618        }
619    }
620  if (string.isEmpty ())
621    string = none;
622  myErrorLabel->setText (string);
623
624
625  ///
626  /// information tab
627  ///
628
629  // mySizeLabel
630  if (torrents.empty ())
631    {
632      string = none;
633    }
634  else
635    {
636      int pieces = 0;
637      uint64_t size = 0;
638      uint32_t pieceSize = torrents[0]->pieceSize ();
639      foreach (const Torrent * t, torrents)
640        {
641          pieces += t->pieceCount ();
642          size += t->totalSize ();
643          if (pieceSize != t->pieceSize ())
644            pieceSize = 0;
645        }
646
647      if (!size)
648        string = none;
649      else if (pieceSize > 0)
650        string = tr ("%1 (%Ln pieces @ %2)", "", pieces)
651                   .arg (Formatter::sizeToString (size))
652                   .arg (Formatter::memToString (pieceSize));
653      else
654        string = tr ("%1 (%Ln pieces)", "", pieces)
655                   .arg (Formatter::sizeToString (size));
656    }
657  mySizeLabel->setText (string);
658
659  // myHashLabel
660  string = none;
661  if (!torrents.empty ())
662    {
663      string = torrents[0]->hashString ();
664      foreach (const Torrent * t, torrents)
665        {
666          if (string != t->hashString ())
667            {
668              string = mixed;
669              break;
670            }
671        }
672    }
673  myHashLabel->setText (string);
674
675  // myPrivacyLabel
676  string = none;
677  if (!torrents.empty ())
678    {
679      bool b = torrents[0]->isPrivate ();
680      string = b ? tr ("Private to this tracker -- DHT and PEX disabled")
681                 : tr ("Public torrent");
682      foreach (const Torrent * t, torrents)
683        {
684          if (b != t->isPrivate ())
685            {
686              string = mixed;
687              break;
688            }
689        }
690    }
691  myPrivacyLabel->setText (string);
692
693  // myCommentBrowser
694  string = none;
695  if (!torrents.empty ())
696    {
697      string = torrents[0]->comment ();
698      foreach (const Torrent * t, torrents)
699        {
700          if (string != t->comment ())
701            {
702              string = mixed;
703              break;
704            }
705        }
706    }
707  if (myCommentBrowser->toPlainText() != string)
708    {
709      myCommentBrowser->setText (string);
710      myCommentBrowser->setMaximumHeight (QWIDGETSIZE_MAX);
711    }
712
713  // myOriginLabel
714  string = none;
715  if (!torrents.empty ())
716    {
717      bool mixed_creator=false, mixed_date=false;
718      const QString creator = torrents[0]->creator ();
719      const QString date = torrents[0]->dateCreated ().toString ();
720      foreach (const Torrent * t, torrents)
721        {
722          mixed_creator |= (creator != t->creator ());
723          mixed_date |= (date != t->dateCreated ().toString ());
724        }
725
726      if (mixed_creator && mixed_date)
727        string = mixed;
728      else if (mixed_date && !creator.isEmpty ())
729        string = tr ("Created by %1").arg (creator);
730      else if (mixed_creator && !date.isEmpty ())
731        string = tr ("Created on %1").arg (date);
732      else if (creator.isEmpty () && date.isEmpty ())
733        string = tr ("N/A");
734      else
735        string = tr ("Created by %1 on %2").arg (creator).arg (date);
736    }
737  myOriginLabel->setText (string);
738
739  // myLocationLabel
740  string = none;
741  if (!torrents.empty ())
742    {
743      string = torrents[0]->getPath ();
744      foreach (const Torrent * t, torrents)
745        {
746          if (string != t->getPath ())
747            {
748              string = mixed;
749              break;
750            }
751        }
752    }
753  myLocationLabel->setText (string);
754
755
756  ///
757  ///  Options Tab
758  ///
759
760  if (myChangedTorrents && !torrents.empty ())
761    {
762      int i;
763      bool uniform;
764      bool baselineFlag;
765      int baselineInt;
766      const Torrent * tor;
767      const Torrent * baseline = *torrents.begin ();
768
769      // mySessionLimitCheck
770      uniform = true;
771      baselineFlag = baseline->honorsSessionLimits ();
772      foreach (tor, torrents) if (baselineFlag != tor->honorsSessionLimits ()) { uniform = false; break; }
773      mySessionLimitCheck->setChecked (uniform && baselineFlag);
774
775      // mySingleDownCheck
776      uniform = true;
777      baselineFlag = baseline->downloadIsLimited ();
778      foreach (tor, torrents) if (baselineFlag != tor->downloadIsLimited ()) { uniform = false; break; }
779      mySingleDownCheck->setChecked (uniform && baselineFlag);
780
781      // mySingleUpCheck
782      uniform = true;
783      baselineFlag = baseline->uploadIsLimited ();
784      foreach (tor, torrents) if (baselineFlag != tor->uploadIsLimited ()) { uniform = false; break; }
785      mySingleUpCheck->setChecked (uniform && baselineFlag);
786
787      // myBandwidthPriorityCombo
788      uniform = true;
789      baselineInt = baseline->getBandwidthPriority ();
790      foreach (tor, torrents) if (baselineInt != tor->getBandwidthPriority ()) { uniform = false; break; }
791      if (uniform)
792        i = myBandwidthPriorityCombo->findData (baselineInt);
793      else
794        i = -1;
795      setIfIdle (myBandwidthPriorityCombo, i);
796
797      setIfIdle (mySingleDownSpin, int (tor->downloadLimit ().KBps ()));
798      setIfIdle (mySingleUpSpin, int (tor->uploadLimit ().KBps ()));
799      setIfIdle (myPeerLimitSpin, tor->peerLimit ());
800    }
801
802  if (!torrents.empty ())
803    {
804      const Torrent * tor;
805
806      // ratio
807      bool uniform = true;
808      int baselineInt = torrents[0]->seedRatioMode ();
809      foreach (tor, torrents) if (baselineInt != tor->seedRatioMode ()) { uniform = false; break; }
810
811      setIfIdle (myRatioCombo, uniform ? myRatioCombo->findData (baselineInt) : -1);
812      myRatioSpin->setVisible (uniform && (baselineInt == TR_RATIOLIMIT_SINGLE));
813
814      setIfIdle (myRatioSpin, tor->seedRatioLimit ());
815
816      // idle
817      uniform = true;
818      baselineInt = torrents[0]->seedIdleMode ();
819      foreach (tor, torrents) if (baselineInt != tor->seedIdleMode ()) { uniform = false; break; }
820
821      setIfIdle (myIdleCombo, uniform ? myIdleCombo->findData (baselineInt) : -1);
822      myIdleSpin->setVisible (uniform && (baselineInt == TR_RATIOLIMIT_SINGLE));
823
824      setIfIdle (myIdleSpin, tor->seedIdleLimit ());
825    }
826
827  ///
828  ///  Tracker tab
829  ///
830
831  myTrackerModel->refresh (myModel, myIds);
832
833  ///
834  ///  Peers tab
835  ///
836
837  QMap<QString,QTreeWidgetItem*> peers2;
838  QList<QTreeWidgetItem*> newItems;
839  foreach (const Torrent * t, torrents)
840    {
841      const QString idStr (QString::number (t->id ()));
842      PeerList peers = t->peers ();
843
844      foreach (const Peer& peer, peers)
845        {
846          const QString key = idStr + ":" + peer.address;
847          PeerItem * item = (PeerItem*) myPeers.value (key, 0);
848
849          if (item == 0) // new peer has connected
850            {
851              static const QIcon myEncryptionIcon (":/icons/encrypted.png");
852              static const QIcon myEmptyIcon;
853              item = new PeerItem (peer);
854              item->setTextAlignment (COL_UP, Qt::AlignRight|Qt::AlignVCenter);
855              item->setTextAlignment (COL_DOWN, Qt::AlignRight|Qt::AlignVCenter);
856              item->setTextAlignment (COL_PERCENT, Qt::AlignRight|Qt::AlignVCenter);
857              item->setIcon (COL_LOCK, peer.isEncrypted ? myEncryptionIcon : myEmptyIcon);
858              item->setToolTip (COL_LOCK, peer.isEncrypted ? tr ("Encrypted connection") : "");
859              item->setText (COL_ADDRESS, peer.address);
860              item->setText (COL_CLIENT, peer.clientName);
861              newItems << item;
862            }
863
864          const QString code = peer.flagStr;
865          item->setStatus (code);
866          item->refresh (peer);
867
868          QString codeTip;
869          foreach (QChar ch, code)
870            {
871              QString txt;
872              switch (ch.unicode ())
873                {
874                  case 'O': txt = tr ("Optimistic unchoke"); break;
875                  case 'D': txt = tr ("Downloading from this peer"); break;
876                  case 'd': txt = tr ("We would download from this peer if they would let us"); break;
877                  case 'U': txt = tr ("Uploading to peer"); break;
878                  case 'u': txt = tr ("We would upload to this peer if they asked"); break;
879                  case 'K': txt = tr ("Peer has unchoked us, but we're not interested"); break;
880                  case '?': txt = tr ("We unchoked this peer, but they're not interested"); break;
881                  case 'E': txt = tr ("Encrypted connection"); break;
882                  case 'H': txt = tr ("Peer was discovered through DHT"); break;
883                  case 'X': txt = tr ("Peer was discovered through Peer Exchange (PEX)"); break;
884                  case 'I': txt = tr ("Peer is an incoming connection"); break;
885                  case 'T': txt = tr ("Peer is connected over uTP"); break;
886                }
887
888              if (!txt.isEmpty ())
889                codeTip += QString ("%1: %2\n").arg (ch).arg (txt);
890            }
891
892          if (!codeTip.isEmpty ())
893            codeTip.resize (codeTip.size ()-1); // eat the trailing linefeed
894
895          item->setText (COL_UP, peer.rateToPeer.isZero () ? "" : Formatter::speedToString (peer.rateToPeer));
896          item->setText (COL_DOWN, peer.rateToClient.isZero () ? "" : Formatter::speedToString (peer.rateToClient));
897          item->setText (COL_PERCENT, peer.progress > 0 ? QString ("%1%").arg ( (int) (peer.progress * 100.0)) : "");
898          item->setText (COL_STATUS, code);
899          item->setToolTip (COL_STATUS, codeTip);
900
901          peers2.insert (key, item);
902        }
903    }
904
905  myPeerTree->addTopLevelItems (newItems);
906  foreach (QString key, myPeers.keys ())
907    {
908      if (!peers2.contains (key)) // old peer has disconnected
909        {
910          QTreeWidgetItem * item = myPeers.value (key, 0);
911          myPeerTree->takeTopLevelItem (myPeerTree->indexOfTopLevelItem (item));
912          delete item;
913        }
914    }
915  myPeers = peers2;
916
917  if (!single)
918    myFileTreeView->clear ();
919  if (single)
920    myFileTreeView->update (torrents[0]->files (), myChangedTorrents);
921
922  myChangedTorrents = false;
923  myHavePendingRefresh = false;
924  foreach (QWidget * w, myWidgets)
925    w->setEnabled (true);
926}
927
928void
929Details :: enableWhenChecked (QCheckBox * box, QWidget * w)
930{
931  connect (box, SIGNAL (toggled (bool)), w, SLOT (setEnabled (bool)));
932  w->setEnabled (box->isChecked ());
933}
934
935
936/***
937****
938***/
939
940QWidget *
941Details :: createInfoTab ()
942{
943  HIG * hig = new HIG (this);
944
945  hig->addSectionTitle (tr ("Activity"));
946  hig->addRow (tr ("Have:"), myHaveLabel = new SqueezeLabel);
947  hig->addRow (tr ("Availability:"), myAvailabilityLabel = new SqueezeLabel);
948  hig->addRow (tr ("Downloaded:"), myDownloadedLabel = new SqueezeLabel);
949  hig->addRow (tr ("Uploaded:"), myUploadedLabel = new SqueezeLabel);
950  hig->addRow (tr ("State:"), myStateLabel = new SqueezeLabel);
951  hig->addRow (tr ("Running time:"), myRunTimeLabel = new SqueezeLabel);
952  hig->addRow (tr ("Remaining time:"), myETALabel = new SqueezeLabel);
953  hig->addRow (tr ("Last activity:"), myLastActivityLabel = new SqueezeLabel);
954  hig->addRow (tr ("Error:"), myErrorLabel = new SqueezeLabel);
955  hig->addSectionDivider ();
956
957  hig->addSectionDivider ();
958  hig->addSectionTitle (tr ("Details"));
959  hig->addRow (tr ("Size:"), mySizeLabel = new SqueezeLabel);
960  hig->addRow (tr ("Location:"), myLocationLabel = new SqueezeLabel);
961  hig->addRow (tr ("Hash:"), myHashLabel = new SqueezeLabel);
962  hig->addRow (tr ("Privacy:"), myPrivacyLabel = new SqueezeLabel);
963  hig->addRow (tr ("Origin:"), myOriginLabel = new SqueezeLabel);
964  myOriginLabel->setMinimumWidth (325); // stop long origin strings from resizing the widgit
965  hig->addRow (tr ("Comment:"), myCommentBrowser = new QTextBrowser);
966  const int h = QFontMetrics (myCommentBrowser->font ()).lineSpacing () * 4;
967  myCommentBrowser->setFixedHeight (h);
968
969  hig->finish ();
970
971  return hig;
972}
973
974/***
975****
976***/
977
978void
979Details :: onShowTrackerScrapesToggled (bool val)
980{
981  myPrefs.set (Prefs::SHOW_TRACKER_SCRAPES, val);
982}
983
984void
985Details :: onShowBackupTrackersToggled (bool val)
986{
987  myPrefs.set (Prefs::SHOW_BACKUP_TRACKERS, val);
988}
989
990void
991Details :: onHonorsSessionLimitsToggled (bool val)
992{
993  mySession.torrentSet (myIds, TR_KEY_honorsSessionLimits, val);
994  getNewData ();
995}
996void
997Details :: onDownloadLimitedToggled (bool val)
998{
999  mySession.torrentSet (myIds, TR_KEY_downloadLimited, val);
1000  getNewData ();
1001}
1002void
1003Details :: onSpinBoxEditingFinished ()
1004{
1005  const QObject * spin = sender ();
1006  const tr_quark key = spin->property (PREF_KEY).toInt ();
1007  const QDoubleSpinBox * d = qobject_cast<const QDoubleSpinBox*> (spin);
1008  if (d)
1009    mySession.torrentSet (myIds, key, d->value ());
1010  else
1011    mySession.torrentSet (myIds, key, qobject_cast<const QSpinBox*> (spin)->value ());
1012  getNewData ();
1013}
1014
1015void
1016Details :: onUploadLimitedToggled (bool val)
1017{
1018  mySession.torrentSet (myIds, TR_KEY_uploadLimited, val);
1019  getNewData ();
1020}
1021
1022void
1023Details :: onIdleModeChanged (int index)
1024{
1025  const int val = myIdleCombo->itemData (index).toInt ();
1026  mySession.torrentSet (myIds, TR_KEY_seedIdleMode, val);
1027  getNewData ();
1028}
1029
1030void
1031Details :: onRatioModeChanged (int index)
1032{
1033  const int val = myRatioCombo->itemData (index).toInt ();
1034  mySession.torrentSet (myIds, TR_KEY_seedRatioMode, val);
1035}
1036
1037void
1038Details :: onBandwidthPriorityChanged (int index)
1039{
1040  if (index != -1)
1041    {
1042      const int priority = myBandwidthPriorityCombo->itemData (index).toInt ();
1043      mySession.torrentSet (myIds, TR_KEY_bandwidthPriority, priority);
1044      getNewData ();
1045    }
1046}
1047
1048void
1049Details :: onTrackerSelectionChanged ()
1050{
1051  const int selectionCount = myTrackerView->selectionModel ()->selectedRows ().size ();
1052  myEditTrackerButton->setEnabled (selectionCount == 1);
1053  myRemoveTrackerButton->setEnabled (selectionCount > 0);
1054}
1055
1056void
1057Details :: onAddTrackerClicked ()
1058{
1059  bool ok = false;
1060  const QString url = QInputDialog::getText (this,
1061                                             tr ("Add URL "),
1062                                             tr ("Add tracker announce URL:"),
1063                                             QLineEdit::Normal, QString (), &ok);
1064  if (!ok)
1065    {
1066      // user pressed "cancel" -- noop
1067    }
1068  else if (!QUrl (url).isValid ())
1069    {
1070      QMessageBox::warning (this, tr ("Error"), tr ("Invalid URL \"%1\"").arg (url));
1071    }
1072  else
1073    {
1074      QSet<int> ids;
1075
1076      foreach (int id, myIds)
1077        if (myTrackerModel->find (id,url) == -1)
1078          ids.insert (id);
1079
1080      if (ids.empty ()) // all the torrents already have this tracker
1081        {
1082          QMessageBox::warning (this, tr ("Error"), tr ("Tracker already exists."));
1083        }
1084        else
1085        {
1086          QStringList urls;
1087          urls << url;
1088          mySession.torrentSet (ids, TR_KEY_trackerAdd, urls);
1089          getNewData ();
1090        }
1091    }
1092}
1093
1094void
1095Details :: onEditTrackerClicked ()
1096{
1097  QItemSelectionModel * selectionModel = myTrackerView->selectionModel ();
1098  QModelIndexList selectedRows = selectionModel->selectedRows ();
1099  assert (selectedRows.size () == 1);
1100  QModelIndex i = selectionModel->currentIndex ();
1101  const TrackerInfo trackerInfo = myTrackerView->model ()->data (i, TrackerModel::TrackerRole).value<TrackerInfo> ();
1102
1103  bool ok = false;
1104  const QString newval = QInputDialog::getText (this,
1105                                                tr ("Edit URL "),
1106                                                tr ("Edit tracker announce URL:"),
1107                                                QLineEdit::Normal,
1108                                                trackerInfo.st.announce, &ok);
1109
1110  if (!ok)
1111    {
1112      // user pressed "cancel" -- noop
1113    }
1114  else if (!QUrl (newval).isValid ())
1115    {
1116      QMessageBox::warning (this, tr ("Error"), tr ("Invalid URL \"%1\"").arg (newval));
1117    }
1118    else
1119    {
1120      QSet<int> ids;
1121      ids << trackerInfo.torrentId;
1122
1123      const QPair<int,QString> idUrl = qMakePair (trackerInfo.st.id, newval);
1124
1125      mySession.torrentSet (ids, TR_KEY_trackerReplace, idUrl);
1126      getNewData ();
1127    }
1128}
1129
1130void
1131Details :: onRemoveTrackerClicked ()
1132{
1133  // make a map of torrentIds to announce URLs to remove
1134  QItemSelectionModel * selectionModel = myTrackerView->selectionModel ();
1135  QModelIndexList selectedRows = selectionModel->selectedRows ();
1136  QMap<int,int> torrentId_to_trackerIds;
1137  foreach (QModelIndex i, selectedRows)
1138    {
1139      const TrackerInfo inf = myTrackerView->model ()->data (i, TrackerModel::TrackerRole).value<TrackerInfo> ();
1140      torrentId_to_trackerIds.insertMulti (inf.torrentId, inf.st.id);
1141    }
1142
1143  // batch all of a tracker's torrents into one command
1144  foreach (int id, torrentId_to_trackerIds.uniqueKeys ())
1145    {
1146      QSet<int> ids;
1147      ids << id;
1148      mySession.torrentSet (ids, TR_KEY_trackerRemove, torrentId_to_trackerIds.values (id));
1149    }
1150
1151  selectionModel->clearSelection ();
1152  getNewData ();
1153}
1154
1155QWidget *
1156Details :: createOptionsTab ()
1157{
1158  QSpinBox * s;
1159  QCheckBox * c;
1160  QComboBox * m;
1161  QHBoxLayout * h;
1162  QDoubleSpinBox * ds;
1163  const QString speed_K_str = Formatter::unitStr (Formatter::SPEED, Formatter::KB);
1164
1165  HIG * hig = new HIG (this);
1166  hig->addSectionTitle (tr ("Speed"));
1167
1168  c = new QCheckBox (tr ("Honor global &limits"));
1169  mySessionLimitCheck = c;
1170  hig->addWideControl (c);
1171  connect (c, SIGNAL (clicked (bool)), this, SLOT (onHonorsSessionLimitsToggled (bool)));
1172
1173  c = new QCheckBox (tr ("Limit &download speed (%1):").arg (speed_K_str));
1174  mySingleDownCheck = c;
1175  s = new QSpinBox ();
1176  s->setProperty (PREF_KEY, TR_KEY_downloadLimit);
1177  s->setSingleStep (5);
1178  s->setRange (0, INT_MAX);
1179  mySingleDownSpin = s;
1180  hig->addRow (c, s);
1181  enableWhenChecked (c, s);
1182  connect (c, SIGNAL (clicked (bool)), this, SLOT (onDownloadLimitedToggled (bool)));
1183  connect (s, SIGNAL (editingFinished ()), this, SLOT (onSpinBoxEditingFinished ()));
1184
1185  c = new QCheckBox (tr ("Limit &upload speed (%1):").arg (speed_K_str));
1186  mySingleUpCheck = c;
1187  s = new QSpinBox ();
1188  s->setSingleStep (5);
1189  s->setRange (0, INT_MAX);
1190  s->setProperty (PREF_KEY, TR_KEY_uploadLimit);
1191  mySingleUpSpin = s;
1192  hig->addRow (c, s);
1193  enableWhenChecked (c, s);
1194  connect (c, SIGNAL (clicked (bool)), this, SLOT (onUploadLimitedToggled (bool)));
1195  connect (s, SIGNAL (editingFinished ()), this, SLOT (onSpinBoxEditingFinished ()));
1196
1197  m = new QComboBox;
1198  m->addItem (tr ("High"),   TR_PRI_HIGH);
1199  m->addItem (tr ("Normal"), TR_PRI_NORMAL);
1200  m->addItem (tr ("Low"),    TR_PRI_LOW);
1201  connect (m, SIGNAL (currentIndexChanged (int)), this, SLOT (onBandwidthPriorityChanged (int)));
1202  hig->addRow (tr ("Torrent &priority:"), m);
1203  myBandwidthPriorityCombo = m;
1204
1205  hig->addSectionDivider ();
1206  hig->addSectionTitle (tr ("Seeding Limits"));
1207
1208  h = new QHBoxLayout ();
1209  h->setSpacing (HIG :: PAD);
1210  m = new QComboBox;
1211  m->addItem (tr ("Use Global Settings"),      TR_RATIOLIMIT_GLOBAL);
1212  m->addItem (tr ("Seed regardless of ratio"), TR_RATIOLIMIT_UNLIMITED);
1213  m->addItem (tr ("Stop seeding at ratio:"),   TR_RATIOLIMIT_SINGLE);
1214  connect (m, SIGNAL (currentIndexChanged (int)), this, SLOT (onRatioModeChanged (int)));
1215  h->addWidget (myRatioCombo = m);
1216  ds = new QDoubleSpinBox ();
1217  ds->setRange (0.5, INT_MAX);
1218  ds->setProperty (PREF_KEY, TR_KEY_seedRatioLimit);
1219  connect (ds, SIGNAL (editingFinished ()), this, SLOT (onSpinBoxEditingFinished ()));
1220  h->addWidget (myRatioSpin = ds);
1221  hig->addRow (tr ("&Ratio:"), h, m);
1222
1223  h = new QHBoxLayout ();
1224  h->setSpacing (HIG :: PAD);
1225  m = new QComboBox;
1226  m->addItem (tr ("Use Global Settings"),                 TR_IDLELIMIT_GLOBAL);
1227  m->addItem (tr ("Seed regardless of activity"),         TR_IDLELIMIT_UNLIMITED);
1228  m->addItem (tr ("Stop seeding if idle for N minutes:"), TR_IDLELIMIT_SINGLE);
1229  connect (m, SIGNAL (currentIndexChanged (int)), this, SLOT (onIdleModeChanged (int)));
1230  h->addWidget (myIdleCombo = m);
1231  s = new QSpinBox ();
1232  s->setSingleStep (5);
1233  s->setRange (1, 9999);
1234  s->setProperty (PREF_KEY, TR_KEY_seedIdleLimit);
1235  connect (s, SIGNAL (editingFinished ()), this, SLOT (onSpinBoxEditingFinished ()));
1236  h->addWidget (myIdleSpin = s);
1237  hig->addRow (tr ("&Idle:"), h, m);
1238
1239
1240  hig->addSectionDivider ();
1241  hig->addSectionTitle (tr ("Peer Connections"));
1242
1243  s = new QSpinBox ();
1244  s->setSingleStep (5);
1245  s->setRange (1, 300);
1246  s->setProperty (PREF_KEY, TR_KEY_peer_limit);
1247  connect (s, SIGNAL (editingFinished ()), this, SLOT (onSpinBoxEditingFinished ()));
1248  myPeerLimitSpin = s;
1249  hig->addRow (tr ("&Maximum peers:"), s);
1250
1251  hig->finish ();
1252
1253  return hig;
1254}
1255
1256/***
1257****
1258***/
1259
1260QWidget *
1261Details :: createTrackerTab ()
1262{
1263  QCheckBox * c;
1264  QPushButton * p;
1265  QWidget * top = new QWidget;
1266  QVBoxLayout * v = new QVBoxLayout (top);
1267  QHBoxLayout * h = new QHBoxLayout ();
1268  QVBoxLayout * v2 = new QVBoxLayout ();
1269
1270  v->setSpacing (HIG::PAD_BIG);
1271  v->setContentsMargins (HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG);
1272
1273  h->setSpacing (HIG::PAD);
1274  h->setContentsMargins (HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL);
1275
1276  v2->setSpacing (HIG::PAD);
1277
1278  myTrackerModel = new TrackerModel;
1279  myTrackerFilter = new TrackerModelFilter;
1280  myTrackerFilter->setSourceModel (myTrackerModel);
1281  myTrackerView = new QTreeView;
1282  myTrackerView->setModel (myTrackerFilter);
1283  myTrackerView->setHeaderHidden (true);
1284  myTrackerView->setSelectionMode (QTreeWidget::ExtendedSelection);
1285  myTrackerView->setRootIsDecorated (false);
1286  myTrackerView->setIndentation (2);
1287  myTrackerView->setItemsExpandable (false);
1288  myTrackerView->setAlternatingRowColors (true);
1289  myTrackerView->setItemDelegate (myTrackerDelegate = new TrackerDelegate ());
1290  connect (myTrackerView->selectionModel (), SIGNAL (selectionChanged (const QItemSelection&, const QItemSelection&)), this, SLOT (onTrackerSelectionChanged ()));
1291  h->addWidget (myTrackerView, 1);
1292
1293  p = new QPushButton ();
1294  p->setIcon (getStockIcon ("list-add", QStyle::SP_DialogOpenButton));
1295  p->setToolTip (tr ("Add Tracker"));
1296  myAddTrackerButton = p;
1297  v2->addWidget (p, 1);
1298  connect (p, SIGNAL (clicked (bool)), this, SLOT (onAddTrackerClicked ()));
1299
1300  p = new QPushButton ();
1301  p->setIcon (getStockIcon ("document-properties", QStyle::SP_DesktopIcon));
1302  p->setToolTip (tr ("Edit Tracker"));
1303  myAddTrackerButton = p;
1304  p->setEnabled (false);
1305  myEditTrackerButton = p;
1306  v2->addWidget (p, 1);
1307  connect (p, SIGNAL (clicked (bool)), this, SLOT (onEditTrackerClicked ()));
1308
1309  p = new QPushButton ();
1310  p->setIcon (getStockIcon ("list-remove", QStyle::SP_TrashIcon));
1311  p->setToolTip (tr ("Remove Trackers"));
1312  p->setEnabled (false);
1313  myRemoveTrackerButton = p;
1314  v2->addWidget (p, 1);
1315  connect (p, SIGNAL (clicked (bool)), this, SLOT (onRemoveTrackerClicked ()));
1316
1317  v2->addStretch (1);
1318
1319  h->addLayout (v2, 1);
1320  h->setStretch (1, 0);
1321
1322  v->addLayout (h, 1);
1323
1324  c = new QCheckBox (tr ("Show &more details"));
1325  c->setChecked (myPrefs.getBool (Prefs::SHOW_TRACKER_SCRAPES));
1326  myShowTrackerScrapesCheck = c;
1327  v->addWidget (c, 1);
1328  connect (c, SIGNAL (clicked (bool)), this, SLOT (onShowTrackerScrapesToggled (bool)));
1329
1330  c = new QCheckBox (tr ("Show &backup trackers"));
1331  c->setChecked (myPrefs.getBool (Prefs::SHOW_BACKUP_TRACKERS));
1332  myShowBackupTrackersCheck = c;
1333  v->addWidget (c, 1);
1334  connect (c, SIGNAL (clicked (bool)), this, SLOT (onShowBackupTrackersToggled (bool)));
1335
1336  return top;
1337}
1338
1339/***
1340****
1341***/
1342
1343QWidget *
1344Details :: createPeersTab ()
1345{
1346  QWidget * top = new QWidget;
1347  QVBoxLayout * v = new QVBoxLayout (top);
1348  v->setSpacing (HIG :: PAD_BIG);
1349  v->setContentsMargins (HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG);
1350
1351  QStringList headers;
1352  headers << QString () << tr ("Up") << tr ("Down") << tr ("%") << tr ("Status") << tr ("Address") << tr ("Client");
1353  myPeerTree = new QTreeWidget;
1354  myPeerTree->setUniformRowHeights (true);
1355  myPeerTree->setHeaderLabels (headers);
1356  myPeerTree->setColumnWidth (0, 20);
1357  myPeerTree->setSortingEnabled (true);
1358  myPeerTree->sortByColumn (COL_ADDRESS, Qt::AscendingOrder);
1359  myPeerTree->setRootIsDecorated (false);
1360  myPeerTree->setTextElideMode (Qt::ElideRight);
1361  v->addWidget (myPeerTree, 1);
1362
1363  const QFontMetrics m (font ());
1364  QSize size = m.size (0, "1024 MiB/s");
1365  myPeerTree->setColumnWidth (COL_UP, size.width ());
1366  myPeerTree->setColumnWidth (COL_DOWN, size.width ());
1367  size = m.size (0, " 100% ");
1368  myPeerTree->setColumnWidth (COL_PERCENT, size.width ());
1369  size = m.size (0, "ODUK?EXI");
1370  myPeerTree->setColumnWidth (COL_STATUS, size.width ());
1371  size = m.size (0, "888.888.888.888");
1372  myPeerTree->setColumnWidth (COL_ADDRESS, size.width ());
1373  size = m.size (0, "Some BitTorrent Client");
1374  myPeerTree->setColumnWidth (COL_CLIENT, size.width ());
1375  myPeerTree->setAlternatingRowColors (true);
1376
1377  return top;
1378}
1379
1380/***
1381****
1382***/
1383
1384QWidget *
1385Details :: createFilesTab ()
1386{
1387  myFileTreeView = new FileTreeView ();
1388
1389  connect (myFileTreeView, SIGNAL (     priorityChanged (const QSet<int>&, int)),
1390           this,           SLOT ( onFilePriorityChanged (const QSet<int>&, int)));
1391
1392  connect (myFileTreeView, SIGNAL (     wantedChanged (const QSet<int>&, bool)),
1393           this,           SLOT ( onFileWantedChanged (const QSet<int>&, bool)));
1394
1395  connect (myFileTreeView, SIGNAL (pathEdited (const QString&, const QString&)),
1396           this,           SLOT (onPathEdited (const QString&, const QString&)));
1397
1398  connect (myFileTreeView, SIGNAL (openRequested (const QString&)),
1399           this,           SLOT (onOpenRequested (const QString&)));
1400
1401  return myFileTreeView;
1402}
1403
1404void
1405Details :: onFilePriorityChanged (const QSet<int>& indices, int priority)
1406{
1407  tr_quark key;
1408
1409  switch (priority)
1410    {
1411      case TR_PRI_LOW:
1412        key = TR_KEY_priority_low;
1413        break;
1414
1415      case TR_PRI_HIGH:
1416        key = TR_KEY_priority_high;
1417        break;
1418
1419      default:
1420        key = TR_KEY_priority_normal;
1421        break;
1422    }
1423
1424  mySession.torrentSet (myIds, key, indices.toList ());
1425  getNewData ();
1426}
1427
1428void
1429Details :: onFileWantedChanged (const QSet<int>& indices, bool wanted)
1430{
1431  const tr_quark key = wanted ? TR_KEY_files_wanted : TR_KEY_files_unwanted;
1432  mySession.torrentSet (myIds, key, indices.toList ());
1433  getNewData ();
1434}
1435
1436void
1437Details :: onPathEdited (const QString& oldpath, const QString& newname)
1438{
1439  mySession.torrentRenamePath (myIds, oldpath, newname);
1440}
1441
1442void
1443Details :: onOpenRequested (const QString& path)
1444{
1445  if (!mySession.isLocal ())
1446    return;
1447
1448  foreach (const int id, myIds)
1449    {
1450      const Torrent * const tor = myModel.getTorrentFromId (id);
1451      if (tor == NULL)
1452        continue;
1453
1454      const QString localFilePath = tor->getPath () + "/" + path;
1455      if (!QFile::exists (localFilePath))
1456        continue;
1457
1458      if (QDesktopServices::openUrl (QUrl::fromLocalFile (localFilePath)))
1459        break;
1460    }
1461}
Note: See TracBrowser for help on using the repository browser.