source: trunk/qt/details.cc @ 14238

Last change on this file since 14238 was 14238, checked in by jordan, 9 years ago

(trunk, gtk/qt/web) #5581: 'Inconsistent ordering of Upload and Download speed limits' -- fixed.

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