source: trunk/qt/details.cc @ 14380

Last change on this file since 14380 was 14380, checked in by mikedld, 8 years ago

Strip needless const& specifiers on SIGNAL and SLOT arguments (Qt client)

  • Property svn:keywords set to Date Rev Author Id
File size: 40.0 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 14380 2014-12-13 09:04:10Z mikedld $
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      peer(p)
103    {
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 available = 0;
412  if (torrents.empty ())
413    {
414      string = none;
415    }
416  else
417    {
418      double leftUntilDone = 0;
419      int64_t haveTotal = 0;
420      int64_t haveVerified = 0;
421      int64_t haveUnverified = 0;
422      int64_t verifiedPieces = 0;
423
424      foreach (const Torrent * t, torrents)
425        {
426          if (t->hasMetadata ())
427            {
428              haveTotal += t->haveTotal ();
429              haveUnverified += t->haveUnverified ();
430              const uint64_t v = t->haveVerified ();
431              haveVerified += v;
432              if (t->pieceSize ())
433                verifiedPieces += v / t->pieceSize ();
434              sizeWhenDone += t->sizeWhenDone ();
435              leftUntilDone += t->leftUntilDone ();
436              available += t->sizeWhenDone () - t->leftUntilDone () + t->desiredAvailable ();
437            }
438        }
439
440      const double d = 100.0 * (sizeWhenDone ? (sizeWhenDone - leftUntilDone) / sizeWhenDone : 1);
441      QString pct = Formatter::percentToString (d);
442
443      if (!haveUnverified && !leftUntilDone)
444        {
445          string = tr ("%1 (100%)")
446                     .arg (Formatter::sizeToString (haveVerified));
447        }
448      else if (!haveUnverified)
449        {
450          string = tr ("%1 of %2 (%3%)")
451                     .arg (Formatter::sizeToString (haveVerified))
452                     .arg (Formatter::sizeToString (sizeWhenDone))
453                     .arg (pct);
454        }
455      else
456        {
457          string = tr ("%1 of %2 (%3%), %4 Unverified")
458                     .arg (Formatter::sizeToString (haveVerified + haveUnverified))
459                     .arg (Formatter::sizeToString (sizeWhenDone))
460                     .arg (pct)
461                     .arg (Formatter::sizeToString (haveUnverified));
462        }
463    }
464  myHaveLabel->setText (string);
465
466  // myAvailabilityLabel
467  if (torrents.empty ())
468    string = none;
469  else if (sizeWhenDone == 0)
470    string = none;
471  else
472    string = QString ("%1%").arg (Formatter::percentToString ( (100.0 * available) / sizeWhenDone));
473  myAvailabilityLabel->setText (string);
474
475  // myDownloadedLabel
476  if (torrents.empty ())
477    {
478      string = none;
479    }
480  else
481    {
482      uint64_t d = 0;
483      uint64_t f = 0;
484      foreach (const Torrent * t, torrents)
485        {
486          d += t->downloadedEver ();
487          f += t->failedEver ();
488        }
489      const QString dstr = Formatter::sizeToString (d);
490      const QString fstr = Formatter::sizeToString (f);
491      if (f)
492        string = tr ("%1 (%2 corrupt)").arg (dstr).arg (fstr);
493      else
494        string = dstr;
495    }
496  myDownloadedLabel->setText (string);
497
498  //  myUploadedLabel
499  if (torrents.empty ())
500    {
501      string = none;
502    }
503  else
504    {
505      uint64_t u = 0;
506      uint64_t d = 0;
507      foreach (const Torrent * t, torrents)
508        {
509          u += t->uploadedEver ();
510          d += t->downloadedEver ();
511        }
512      string = tr ("%1 (Ratio: %2)")
513                 .arg (Formatter::sizeToString (u))
514                 .arg (Formatter::ratioToString (tr_getRatio (u, d)));
515    }
516  myUploadedLabel->setText (string);
517
518  const QDateTime qdt_now = QDateTime::currentDateTime ();
519
520  // myRunTimeLabel
521  if (torrents.empty ())
522    {
523      string = none;
524    }
525  else
526    {
527      bool allPaused = true;
528      QDateTime baseline = torrents[0]->lastStarted ();
529      foreach (const Torrent * t, torrents)
530        {
531          if (baseline != t->lastStarted ())
532            baseline = QDateTime ();
533          if (!t->isPaused ())
534            allPaused = false;
535        }
536
537      if (allPaused)
538        string = stateString; // paused || finished
539      else if (baseline.isNull ())
540        string = mixed;
541      else
542        string = Formatter::timeToString (baseline.secsTo (qdt_now));
543    }
544  myRunTimeLabel->setText (string);
545
546
547  // myETALabel
548  string.clear ();
549  if (torrents.empty ())
550    {
551      string = none;
552    }
553  else
554    {
555      int baseline = torrents[0]->getETA ();
556      foreach (const Torrent * t, torrents)
557        {
558          if (baseline != t->getETA ())
559            {
560              string = mixed;
561              break;
562            }
563        }
564
565      if (string.isEmpty ())
566        {
567          if (baseline < 0)
568            string = tr ("Unknown");
569          else
570            string = Formatter::timeToString (baseline);
571       }
572    }
573  myETALabel->setText (string);
574
575
576  // myLastActivityLabel
577  if (torrents.empty ())
578    {
579      string = none;
580    }
581  else
582    {
583      QDateTime latest = torrents[0]->lastActivity ();
584      foreach (const Torrent * t, torrents)
585        {
586          const QDateTime dt = t->lastActivity ();
587          if (latest < dt)
588            latest = dt;
589        }
590
591      const int seconds = latest.isValid () ? latest.secsTo (qdt_now) : -1;
592      if (seconds < 0)
593        string = none;
594      else if (seconds < 5)
595        string = tr ("Active now");
596      else
597        string = tr ("%1 ago").arg (Formatter::timeToString (seconds));
598    }
599  myLastActivityLabel->setText (string);
600
601
602  if (torrents.empty ())
603    {
604      string = none;
605    }
606  else
607    {
608      string = torrents[0]->getError ();
609      foreach (const Torrent * t, torrents)
610        {
611          if (string != t->getError ())
612            {
613              string = mixed;
614              break;
615            }
616        }
617    }
618  if (string.isEmpty ())
619    string = none;
620  myErrorLabel->setText (string);
621
622
623  ///
624  /// information tab
625  ///
626
627  // mySizeLabel
628  if (torrents.empty ())
629    {
630      string = none;
631    }
632  else
633    {
634      int pieces = 0;
635      uint64_t size = 0;
636      uint32_t pieceSize = torrents[0]->pieceSize ();
637      foreach (const Torrent * t, torrents)
638        {
639          pieces += t->pieceCount ();
640          size += t->totalSize ();
641          if (pieceSize != t->pieceSize ())
642            pieceSize = 0;
643        }
644
645      if (!size)
646        string = none;
647      else if (pieceSize > 0)
648        string = tr ("%1 (%Ln pieces @ %2)", "", pieces)
649                   .arg (Formatter::sizeToString (size))
650                   .arg (Formatter::memToString (pieceSize));
651      else
652        string = tr ("%1 (%Ln pieces)", "", pieces)
653                   .arg (Formatter::sizeToString (size));
654    }
655  mySizeLabel->setText (string);
656
657  // myHashLabel
658  string = none;
659  if (!torrents.empty ())
660    {
661      string = torrents[0]->hashString ();
662      foreach (const Torrent * t, torrents)
663        {
664          if (string != t->hashString ())
665            {
666              string = mixed;
667              break;
668            }
669        }
670    }
671  myHashLabel->setText (string);
672
673  // myPrivacyLabel
674  string = none;
675  if (!torrents.empty ())
676    {
677      bool b = torrents[0]->isPrivate ();
678      string = b ? tr ("Private to this tracker -- DHT and PEX disabled")
679                 : tr ("Public torrent");
680      foreach (const Torrent * t, torrents)
681        {
682          if (b != t->isPrivate ())
683            {
684              string = mixed;
685              break;
686            }
687        }
688    }
689  myPrivacyLabel->setText (string);
690
691  // myCommentBrowser
692  string = none;
693  if (!torrents.empty ())
694    {
695      string = torrents[0]->comment ();
696      foreach (const Torrent * t, torrents)
697        {
698          if (string != t->comment ())
699            {
700              string = mixed;
701              break;
702            }
703        }
704    }
705  if (myCommentBrowser->toPlainText() != string)
706    {
707      myCommentBrowser->setText (string);
708      myCommentBrowser->setMaximumHeight (QWIDGETSIZE_MAX);
709    }
710
711  // myOriginLabel
712  string = none;
713  if (!torrents.empty ())
714    {
715      bool mixed_creator=false, mixed_date=false;
716      const QString creator = torrents[0]->creator ();
717      const QString date = torrents[0]->dateCreated ().toString ();
718      foreach (const Torrent * t, torrents)
719        {
720          mixed_creator |= (creator != t->creator ());
721          mixed_date |= (date != t->dateCreated ().toString ());
722        }
723
724      if (mixed_creator && mixed_date)
725        string = mixed;
726      else if (mixed_date && !creator.isEmpty ())
727        string = tr ("Created by %1").arg (creator);
728      else if (mixed_creator && !date.isEmpty ())
729        string = tr ("Created on %1").arg (date);
730      else if (creator.isEmpty () && date.isEmpty ())
731        string = tr ("N/A");
732      else
733        string = tr ("Created by %1 on %2").arg (creator).arg (date);
734    }
735  myOriginLabel->setText (string);
736
737  // myLocationLabel
738  string = none;
739  if (!torrents.empty ())
740    {
741      string = torrents[0]->getPath ();
742      foreach (const Torrent * t, torrents)
743        {
744          if (string != t->getPath ())
745            {
746              string = mixed;
747              break;
748            }
749        }
750    }
751  myLocationLabel->setText (string);
752
753
754  ///
755  ///  Options Tab
756  ///
757
758  if (myChangedTorrents && !torrents.empty ())
759    {
760      int i;
761      bool uniform;
762      bool baselineFlag;
763      int baselineInt;
764      const Torrent * tor;
765      const Torrent * baseline = *torrents.begin ();
766
767      // mySessionLimitCheck
768      uniform = true;
769      baselineFlag = baseline->honorsSessionLimits ();
770      foreach (tor, torrents) if (baselineFlag != tor->honorsSessionLimits ()) { uniform = false; break; }
771      mySessionLimitCheck->setChecked (uniform && baselineFlag);
772
773      // mySingleDownCheck
774      uniform = true;
775      baselineFlag = baseline->downloadIsLimited ();
776      foreach (tor, torrents) if (baselineFlag != tor->downloadIsLimited ()) { uniform = false; break; }
777      mySingleDownCheck->setChecked (uniform && baselineFlag);
778
779      // mySingleUpCheck
780      uniform = true;
781      baselineFlag = baseline->uploadIsLimited ();
782      foreach (tor, torrents) if (baselineFlag != tor->uploadIsLimited ()) { uniform = false; break; }
783      mySingleUpCheck->setChecked (uniform && baselineFlag);
784
785      // myBandwidthPriorityCombo
786      uniform = true;
787      baselineInt = baseline->getBandwidthPriority ();
788      foreach (tor, torrents) if (baselineInt != tor->getBandwidthPriority ()) { uniform = false; break; }
789      if (uniform)
790        i = myBandwidthPriorityCombo->findData (baselineInt);
791      else
792        i = -1;
793      setIfIdle (myBandwidthPriorityCombo, i);
794
795      setIfIdle (mySingleDownSpin, int (tor->downloadLimit ().KBps ()));
796      setIfIdle (mySingleUpSpin, int (tor->uploadLimit ().KBps ()));
797      setIfIdle (myPeerLimitSpin, tor->peerLimit ());
798    }
799
800  if (!torrents.empty ())
801    {
802      const Torrent * tor;
803
804      // ratio
805      bool uniform = true;
806      int baselineInt = torrents[0]->seedRatioMode ();
807      foreach (tor, torrents) if (baselineInt != tor->seedRatioMode ()) { uniform = false; break; }
808
809      setIfIdle (myRatioCombo, uniform ? myRatioCombo->findData (baselineInt) : -1);
810      myRatioSpin->setVisible (uniform && (baselineInt == TR_RATIOLIMIT_SINGLE));
811
812      setIfIdle (myRatioSpin, tor->seedRatioLimit ());
813
814      // idle
815      uniform = true;
816      baselineInt = torrents[0]->seedIdleMode ();
817      foreach (tor, torrents) if (baselineInt != tor->seedIdleMode ()) { uniform = false; break; }
818
819      setIfIdle (myIdleCombo, uniform ? myIdleCombo->findData (baselineInt) : -1);
820      myIdleSpin->setVisible (uniform && (baselineInt == TR_RATIOLIMIT_SINGLE));
821
822      setIfIdle (myIdleSpin, tor->seedIdleLimit ());
823    }
824
825  ///
826  ///  Tracker tab
827  ///
828
829  myTrackerModel->refresh (myModel, myIds);
830
831  ///
832  ///  Peers tab
833  ///
834
835  QMap<QString,QTreeWidgetItem*> peers2;
836  QList<QTreeWidgetItem*> newItems;
837  foreach (const Torrent * t, torrents)
838    {
839      const QString idStr (QString::number (t->id ()));
840      PeerList peers = t->peers ();
841
842      foreach (const Peer& peer, peers)
843        {
844          const QString key = idStr + ":" + peer.address;
845          PeerItem * item = static_cast<PeerItem*> (myPeers.value (key, 0));
846
847          if (item == 0) // new peer has connected
848            {
849              static const QIcon myEncryptionIcon (":/icons/encrypted.png");
850              static const QIcon myEmptyIcon;
851              item = new PeerItem (peer);
852              item->setTextAlignment (COL_UP, Qt::AlignRight|Qt::AlignVCenter);
853              item->setTextAlignment (COL_DOWN, Qt::AlignRight|Qt::AlignVCenter);
854              item->setTextAlignment (COL_PERCENT, Qt::AlignRight|Qt::AlignVCenter);
855              item->setIcon (COL_LOCK, peer.isEncrypted ? myEncryptionIcon : myEmptyIcon);
856              item->setToolTip (COL_LOCK, peer.isEncrypted ? tr ("Encrypted connection") : "");
857              item->setText (COL_ADDRESS, peer.address);
858              item->setText (COL_CLIENT, peer.clientName);
859              newItems << item;
860            }
861
862          const QString code = peer.flagStr;
863          item->setStatus (code);
864          item->refresh (peer);
865
866          QString codeTip;
867          foreach (QChar ch, code)
868            {
869              QString txt;
870              switch (ch.unicode ())
871                {
872                  case 'O': txt = tr ("Optimistic unchoke"); break;
873                  case 'D': txt = tr ("Downloading from this peer"); break;
874                  case 'd': txt = tr ("We would download from this peer if they would let us"); break;
875                  case 'U': txt = tr ("Uploading to peer"); break;
876                  case 'u': txt = tr ("We would upload to this peer if they asked"); break;
877                  case 'K': txt = tr ("Peer has unchoked us, but we're not interested"); break;
878                  case '?': txt = tr ("We unchoked this peer, but they're not interested"); break;
879                  case 'E': txt = tr ("Encrypted connection"); break;
880                  case 'H': txt = tr ("Peer was discovered through DHT"); break;
881                  case 'X': txt = tr ("Peer was discovered through Peer Exchange (PEX)"); break;
882                  case 'I': txt = tr ("Peer is an incoming connection"); break;
883                  case 'T': txt = tr ("Peer is connected over uTP"); break;
884                }
885
886              if (!txt.isEmpty ())
887                codeTip += QString ("%1: %2\n").arg (ch).arg (txt);
888            }
889
890          if (!codeTip.isEmpty ())
891            codeTip.resize (codeTip.size ()-1); // eat the trailing linefeed
892
893          item->setText (COL_UP, peer.rateToPeer.isZero () ? "" : Formatter::speedToString (peer.rateToPeer));
894          item->setText (COL_DOWN, peer.rateToClient.isZero () ? "" : Formatter::speedToString (peer.rateToClient));
895          item->setText (COL_PERCENT, peer.progress > 0 ? QString ("%1%").arg ( (int) (peer.progress * 100.0)) : "");
896          item->setText (COL_STATUS, code);
897          item->setToolTip (COL_STATUS, codeTip);
898
899          peers2.insert (key, item);
900        }
901    }
902
903  myPeerTree->addTopLevelItems (newItems);
904  foreach (QString key, myPeers.keys ())
905    {
906      if (!peers2.contains (key)) // old peer has disconnected
907        {
908          QTreeWidgetItem * item = myPeers.value (key, 0);
909          myPeerTree->takeTopLevelItem (myPeerTree->indexOfTopLevelItem (item));
910          delete item;
911        }
912    }
913  myPeers = peers2;
914
915  if (!single)
916    myFileTreeView->clear ();
917  if (single)
918    myFileTreeView->update (torrents[0]->files (), myChangedTorrents);
919
920  myChangedTorrents = false;
921  myHavePendingRefresh = false;
922  foreach (QWidget * w, myWidgets)
923    w->setEnabled (true);
924}
925
926void
927Details::enableWhenChecked (QCheckBox * box, QWidget * w)
928{
929  connect (box, SIGNAL (toggled (bool)), w, SLOT (setEnabled (bool)));
930  w->setEnabled (box->isChecked ());
931}
932
933
934/***
935****
936***/
937
938QWidget *
939Details::createInfoTab ()
940{
941  HIG * hig = new HIG (this);
942
943  hig->addSectionTitle (tr ("Activity"));
944  hig->addRow (tr ("Have:"), myHaveLabel = new SqueezeLabel);
945  hig->addRow (tr ("Availability:"), myAvailabilityLabel = new SqueezeLabel);
946  hig->addRow (tr ("Uploaded:"), myUploadedLabel = new SqueezeLabel);
947  hig->addRow (tr ("Downloaded:"), myDownloadedLabel = new SqueezeLabel);
948  hig->addRow (tr ("State:"), myStateLabel = new SqueezeLabel);
949  hig->addRow (tr ("Running time:"), myRunTimeLabel = new SqueezeLabel);
950  hig->addRow (tr ("Remaining time:"), myETALabel = new SqueezeLabel);
951  hig->addRow (tr ("Last activity:"), myLastActivityLabel = new SqueezeLabel);
952  hig->addRow (tr ("Error:"), myErrorLabel = new SqueezeLabel);
953  hig->addSectionDivider ();
954
955  hig->addSectionDivider ();
956  hig->addSectionTitle (tr ("Details"));
957  hig->addRow (tr ("Size:"), mySizeLabel = new SqueezeLabel);
958  hig->addRow (tr ("Location:"), myLocationLabel = new SqueezeLabel);
959  hig->addRow (tr ("Hash:"), myHashLabel = new SqueezeLabel);
960  hig->addRow (tr ("Privacy:"), myPrivacyLabel = new SqueezeLabel);
961  hig->addRow (tr ("Origin:"), myOriginLabel = new SqueezeLabel);
962  myOriginLabel->setMinimumWidth (325); // stop long origin strings from resizing the widgit
963  hig->addRow (tr ("Comment:"), myCommentBrowser = new QTextBrowser);
964  const int h = QFontMetrics (myCommentBrowser->font ()).lineSpacing () * 4;
965  myCommentBrowser->setFixedHeight (h);
966
967  hig->finish ();
968
969  return hig;
970}
971
972/***
973****
974***/
975
976void
977Details::onShowTrackerScrapesToggled (bool val)
978{
979  myPrefs.set (Prefs::SHOW_TRACKER_SCRAPES, val);
980}
981
982void
983Details::onShowBackupTrackersToggled (bool val)
984{
985  myPrefs.set (Prefs::SHOW_BACKUP_TRACKERS, val);
986}
987
988void
989Details::onHonorsSessionLimitsToggled (bool val)
990{
991  mySession.torrentSet (myIds, TR_KEY_honorsSessionLimits, val);
992  getNewData ();
993}
994void
995Details::onDownloadLimitedToggled (bool val)
996{
997  mySession.torrentSet (myIds, TR_KEY_downloadLimited, val);
998  getNewData ();
999}
1000void
1001Details::onSpinBoxEditingFinished ()
1002{
1003  const QObject * spin = sender ();
1004  const tr_quark key = spin->property (PREF_KEY).toInt ();
1005  const QDoubleSpinBox * d = qobject_cast<const QDoubleSpinBox*> (spin);
1006  if (d)
1007    mySession.torrentSet (myIds, key, d->value ());
1008  else
1009    mySession.torrentSet (myIds, key, qobject_cast<const QSpinBox*> (spin)->value ());
1010  getNewData ();
1011}
1012
1013void
1014Details::onUploadLimitedToggled (bool val)
1015{
1016  mySession.torrentSet (myIds, TR_KEY_uploadLimited, val);
1017  getNewData ();
1018}
1019
1020void
1021Details::onIdleModeChanged (int index)
1022{
1023  const int val = myIdleCombo->itemData (index).toInt ();
1024  mySession.torrentSet (myIds, TR_KEY_seedIdleMode, val);
1025  getNewData ();
1026}
1027
1028void
1029Details::onRatioModeChanged (int index)
1030{
1031  const int val = myRatioCombo->itemData (index).toInt ();
1032  mySession.torrentSet (myIds, TR_KEY_seedRatioMode, val);
1033}
1034
1035void
1036Details::onBandwidthPriorityChanged (int index)
1037{
1038  if (index != -1)
1039    {
1040      const int priority = myBandwidthPriorityCombo->itemData (index).toInt ();
1041      mySession.torrentSet (myIds, TR_KEY_bandwidthPriority, priority);
1042      getNewData ();
1043    }
1044}
1045
1046void
1047Details::onTrackerSelectionChanged ()
1048{
1049  const int selectionCount = myTrackerView->selectionModel ()->selectedRows ().size ();
1050  myEditTrackerButton->setEnabled (selectionCount == 1);
1051  myRemoveTrackerButton->setEnabled (selectionCount > 0);
1052}
1053
1054void
1055Details::onAddTrackerClicked ()
1056{
1057  bool ok = false;
1058  const QString url = QInputDialog::getText (this,
1059                                             tr ("Add URL "),
1060                                             tr ("Add tracker announce URL:"),
1061                                             QLineEdit::Normal, QString (), &ok);
1062  if (!ok)
1063    {
1064      // user pressed "cancel" -- noop
1065    }
1066  else if (!QUrl (url).isValid ())
1067    {
1068      QMessageBox::warning (this, tr ("Error"), tr ("Invalid URL \"%1\"").arg (url));
1069    }
1070  else
1071    {
1072      QSet<int> ids;
1073
1074      foreach (int id, myIds)
1075        if (myTrackerModel->find (id,url) == -1)
1076          ids.insert (id);
1077
1078      if (ids.empty ()) // all the torrents already have this tracker
1079        {
1080          QMessageBox::warning (this, tr ("Error"), tr ("Tracker already exists."));
1081        }
1082        else
1083        {
1084          QStringList urls;
1085          urls << url;
1086          mySession.torrentSet (ids, TR_KEY_trackerAdd, urls);
1087          getNewData ();
1088        }
1089    }
1090}
1091
1092void
1093Details::onEditTrackerClicked ()
1094{
1095  QItemSelectionModel * selectionModel = myTrackerView->selectionModel ();
1096  QModelIndexList selectedRows = selectionModel->selectedRows ();
1097  assert (selectedRows.size () == 1);
1098  QModelIndex i = selectionModel->currentIndex ();
1099  const TrackerInfo trackerInfo = myTrackerView->model ()->data (i, TrackerModel::TrackerRole).value<TrackerInfo> ();
1100
1101  bool ok = false;
1102  const QString newval = QInputDialog::getText (this,
1103                                                tr ("Edit URL "),
1104                                                tr ("Edit tracker announce URL:"),
1105                                                QLineEdit::Normal,
1106                                                trackerInfo.st.announce, &ok);
1107
1108  if (!ok)
1109    {
1110      // user pressed "cancel" -- noop
1111    }
1112  else if (!QUrl (newval).isValid ())
1113    {
1114      QMessageBox::warning (this, tr ("Error"), tr ("Invalid URL \"%1\"").arg (newval));
1115    }
1116    else
1117    {
1118      QSet<int> ids;
1119      ids << trackerInfo.torrentId;
1120
1121      const QPair<int,QString> idUrl = qMakePair (trackerInfo.st.id, newval);
1122
1123      mySession.torrentSet (ids, TR_KEY_trackerReplace, idUrl);
1124      getNewData ();
1125    }
1126}
1127
1128void
1129Details::onRemoveTrackerClicked ()
1130{
1131  // make a map of torrentIds to announce URLs to remove
1132  QItemSelectionModel * selectionModel = myTrackerView->selectionModel ();
1133  QModelIndexList selectedRows = selectionModel->selectedRows ();
1134  QMap<int,int> torrentId_to_trackerIds;
1135  foreach (QModelIndex i, selectedRows)
1136    {
1137      const TrackerInfo inf = myTrackerView->model ()->data (i, TrackerModel::TrackerRole).value<TrackerInfo> ();
1138      torrentId_to_trackerIds.insertMulti (inf.torrentId, inf.st.id);
1139    }
1140
1141  // batch all of a tracker's torrents into one command
1142  foreach (int id, torrentId_to_trackerIds.uniqueKeys ())
1143    {
1144      QSet<int> ids;
1145      ids << id;
1146      mySession.torrentSet (ids, TR_KEY_trackerRemove, torrentId_to_trackerIds.values (id));
1147    }
1148
1149  selectionModel->clearSelection ();
1150  getNewData ();
1151}
1152
1153QWidget *
1154Details::createOptionsTab ()
1155{
1156  QSpinBox * s;
1157  QCheckBox * c;
1158  QComboBox * m;
1159  QHBoxLayout * h;
1160  QDoubleSpinBox * ds;
1161  const QString speed_K_str = Formatter::unitStr (Formatter::SPEED, Formatter::KB);
1162
1163  HIG * hig = new HIG (this);
1164  hig->addSectionTitle (tr ("Speed"));
1165
1166  c = new QCheckBox (tr ("Honor global &limits"));
1167  mySessionLimitCheck = c;
1168  hig->addWideControl (c);
1169  connect (c, SIGNAL (clicked (bool)), this, SLOT (onHonorsSessionLimitsToggled (bool)));
1170
1171  c = new QCheckBox (tr ("Limit &download speed (%1):").arg (speed_K_str));
1172  mySingleDownCheck = c;
1173  s = new QSpinBox ();
1174  s->setProperty (PREF_KEY, TR_KEY_downloadLimit);
1175  s->setSingleStep (5);
1176  s->setRange (0, INT_MAX);
1177  mySingleDownSpin = s;
1178  hig->addRow (c, s);
1179  enableWhenChecked (c, s);
1180  connect (c, SIGNAL (clicked (bool)), this, SLOT (onDownloadLimitedToggled (bool)));
1181  connect (s, SIGNAL (editingFinished ()), this, SLOT (onSpinBoxEditingFinished ()));
1182
1183  c = new QCheckBox (tr ("Limit &upload speed (%1):").arg (speed_K_str));
1184  mySingleUpCheck = c;
1185  s = new QSpinBox ();
1186  s->setSingleStep (5);
1187  s->setRange (0, INT_MAX);
1188  s->setProperty (PREF_KEY, TR_KEY_uploadLimit);
1189  mySingleUpSpin = s;
1190  hig->addRow (c, s);
1191  enableWhenChecked (c, s);
1192  connect (c, SIGNAL (clicked (bool)), this, SLOT (onUploadLimitedToggled (bool)));
1193  connect (s, SIGNAL (editingFinished ()), this, SLOT (onSpinBoxEditingFinished ()));
1194
1195  m = new QComboBox;
1196  m->addItem (tr ("High"),   TR_PRI_HIGH);
1197  m->addItem (tr ("Normal"), TR_PRI_NORMAL);
1198  m->addItem (tr ("Low"),    TR_PRI_LOW);
1199  connect (m, SIGNAL (currentIndexChanged (int)), this, SLOT (onBandwidthPriorityChanged (int)));
1200  hig->addRow (tr ("Torrent &priority:"), m);
1201  myBandwidthPriorityCombo = m;
1202
1203  hig->addSectionDivider ();
1204  hig->addSectionTitle (tr ("Seeding Limits"));
1205
1206  h = new QHBoxLayout ();
1207  h->setSpacing (HIG::PAD);
1208  m = new QComboBox;
1209  m->addItem (tr ("Use Global Settings"),      TR_RATIOLIMIT_GLOBAL);
1210  m->addItem (tr ("Seed regardless of ratio"), TR_RATIOLIMIT_UNLIMITED);
1211  m->addItem (tr ("Stop seeding at ratio:"),   TR_RATIOLIMIT_SINGLE);
1212  connect (m, SIGNAL (currentIndexChanged (int)), this, SLOT (onRatioModeChanged (int)));
1213  h->addWidget (myRatioCombo = m);
1214  ds = new QDoubleSpinBox ();
1215  ds->setRange (0.5, INT_MAX);
1216  ds->setProperty (PREF_KEY, TR_KEY_seedRatioLimit);
1217  connect (ds, SIGNAL (editingFinished ()), this, SLOT (onSpinBoxEditingFinished ()));
1218  h->addWidget (myRatioSpin = ds);
1219  hig->addRow (tr ("&Ratio:"), h, m);
1220
1221  h = new QHBoxLayout ();
1222  h->setSpacing (HIG::PAD);
1223  m = new QComboBox;
1224  m->addItem (tr ("Use Global Settings"),                 TR_IDLELIMIT_GLOBAL);
1225  m->addItem (tr ("Seed regardless of activity"),         TR_IDLELIMIT_UNLIMITED);
1226  m->addItem (tr ("Stop seeding if idle for N minutes:"), TR_IDLELIMIT_SINGLE);
1227  connect (m, SIGNAL (currentIndexChanged (int)), this, SLOT (onIdleModeChanged (int)));
1228  h->addWidget (myIdleCombo = m);
1229  s = new QSpinBox ();
1230  s->setSingleStep (5);
1231  s->setRange (1, 9999);
1232  s->setProperty (PREF_KEY, TR_KEY_seedIdleLimit);
1233  connect (s, SIGNAL (editingFinished ()), this, SLOT (onSpinBoxEditingFinished ()));
1234  h->addWidget (myIdleSpin = s);
1235  hig->addRow (tr ("&Idle:"), h, m);
1236
1237
1238  hig->addSectionDivider ();
1239  hig->addSectionTitle (tr ("Peer Connections"));
1240
1241  s = new QSpinBox ();
1242  s->setSingleStep (5);
1243  s->setRange (1, 300);
1244  s->setProperty (PREF_KEY, TR_KEY_peer_limit);
1245  connect (s, SIGNAL (editingFinished ()), this, SLOT (onSpinBoxEditingFinished ()));
1246  myPeerLimitSpin = s;
1247  hig->addRow (tr ("&Maximum peers:"), s);
1248
1249  hig->finish ();
1250
1251  return hig;
1252}
1253
1254/***
1255****
1256***/
1257
1258QWidget *
1259Details::createTrackerTab ()
1260{
1261  QCheckBox * c;
1262  QPushButton * p;
1263  QWidget * top = new QWidget;
1264  QVBoxLayout * v = new QVBoxLayout (top);
1265  QHBoxLayout * h = new QHBoxLayout ();
1266  QVBoxLayout * v2 = new QVBoxLayout ();
1267
1268  v->setSpacing (HIG::PAD_BIG);
1269  v->setContentsMargins (HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG);
1270
1271  h->setSpacing (HIG::PAD);
1272  h->setContentsMargins (HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL);
1273
1274  v2->setSpacing (HIG::PAD);
1275
1276  myTrackerModel = new TrackerModel;
1277  myTrackerFilter = new TrackerModelFilter;
1278  myTrackerFilter->setSourceModel (myTrackerModel);
1279  myTrackerView = new QTreeView;
1280  myTrackerView->setModel (myTrackerFilter);
1281  myTrackerView->setHeaderHidden (true);
1282  myTrackerView->setSelectionMode (QTreeWidget::ExtendedSelection);
1283  myTrackerView->setRootIsDecorated (false);
1284  myTrackerView->setIndentation (2);
1285  myTrackerView->setItemsExpandable (false);
1286  myTrackerView->setAlternatingRowColors (true);
1287  myTrackerView->setItemDelegate (myTrackerDelegate = new TrackerDelegate ());
1288  connect (myTrackerView->selectionModel (), SIGNAL (selectionChanged (QItemSelection, QItemSelection)), this, SLOT (onTrackerSelectionChanged ()));
1289  h->addWidget (myTrackerView, 1);
1290
1291  p = new QPushButton ();
1292  p->setIcon (getStockIcon ("list-add", QStyle::SP_DialogOpenButton));
1293  p->setToolTip (tr ("Add Tracker"));
1294  myAddTrackerButton = p;
1295  v2->addWidget (p, 1);
1296  connect (p, SIGNAL (clicked (bool)), this, SLOT (onAddTrackerClicked ()));
1297
1298  p = new QPushButton ();
1299  p->setIcon (getStockIcon ("document-properties", QStyle::SP_DesktopIcon));
1300  p->setToolTip (tr ("Edit Tracker"));
1301  myAddTrackerButton = p;
1302  p->setEnabled (false);
1303  myEditTrackerButton = p;
1304  v2->addWidget (p, 1);
1305  connect (p, SIGNAL (clicked (bool)), this, SLOT (onEditTrackerClicked ()));
1306
1307  p = new QPushButton ();
1308  p->setIcon (getStockIcon ("list-remove", QStyle::SP_TrashIcon));
1309  p->setToolTip (tr ("Remove Trackers"));
1310  p->setEnabled (false);
1311  myRemoveTrackerButton = p;
1312  v2->addWidget (p, 1);
1313  connect (p, SIGNAL (clicked (bool)), this, SLOT (onRemoveTrackerClicked ()));
1314
1315  v2->addStretch (1);
1316
1317  h->addLayout (v2, 1);
1318  h->setStretch (1, 0);
1319
1320  v->addLayout (h, 1);
1321
1322  c = new QCheckBox (tr ("Show &more details"));
1323  c->setChecked (myPrefs.getBool (Prefs::SHOW_TRACKER_SCRAPES));
1324  myShowTrackerScrapesCheck = c;
1325  v->addWidget (c, 1);
1326  connect (c, SIGNAL (clicked (bool)), this, SLOT (onShowTrackerScrapesToggled (bool)));
1327
1328  c = new QCheckBox (tr ("Show &backup trackers"));
1329  c->setChecked (myPrefs.getBool (Prefs::SHOW_BACKUP_TRACKERS));
1330  myShowBackupTrackersCheck = c;
1331  v->addWidget (c, 1);
1332  connect (c, SIGNAL (clicked (bool)), this, SLOT (onShowBackupTrackersToggled (bool)));
1333
1334  return top;
1335}
1336
1337/***
1338****
1339***/
1340
1341QWidget *
1342Details::createPeersTab ()
1343{
1344  QWidget * top = new QWidget;
1345  QVBoxLayout * v = new QVBoxLayout (top);
1346  v->setSpacing (HIG::PAD_BIG);
1347  v->setContentsMargins (HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG, HIG::PAD_BIG);
1348
1349  QStringList headers;
1350  headers << QString () << tr ("Up") << tr ("Down") << tr ("%") << tr ("Status") << tr ("Address") << tr ("Client");
1351  myPeerTree = new QTreeWidget;
1352  myPeerTree->setUniformRowHeights (true);
1353  myPeerTree->setHeaderLabels (headers);
1354  myPeerTree->setColumnWidth (0, 20);
1355  myPeerTree->setSortingEnabled (true);
1356  myPeerTree->sortByColumn (COL_ADDRESS, Qt::AscendingOrder);
1357  myPeerTree->setRootIsDecorated (false);
1358  myPeerTree->setTextElideMode (Qt::ElideRight);
1359  v->addWidget (myPeerTree, 1);
1360
1361  const QFontMetrics m (font ());
1362  QSize size = m.size (0, "1024 MiB/s");
1363  myPeerTree->setColumnWidth (COL_UP, size.width ());
1364  myPeerTree->setColumnWidth (COL_DOWN, size.width ());
1365  size = m.size (0, " 100% ");
1366  myPeerTree->setColumnWidth (COL_PERCENT, size.width ());
1367  size = m.size (0, "ODUK?EXI");
1368  myPeerTree->setColumnWidth (COL_STATUS, size.width ());
1369  size = m.size (0, "888.888.888.888");
1370  myPeerTree->setColumnWidth (COL_ADDRESS, size.width ());
1371  size = m.size (0, "Some BitTorrent Client");
1372  myPeerTree->setColumnWidth (COL_CLIENT, size.width ());
1373  myPeerTree->setAlternatingRowColors (true);
1374
1375  return top;
1376}
1377
1378/***
1379****
1380***/
1381
1382QWidget *
1383Details::createFilesTab ()
1384{
1385  myFileTreeView = new FileTreeView ();
1386
1387  connect (myFileTreeView, SIGNAL (     priorityChanged (QSet<int>, int)),
1388           this,           SLOT ( onFilePriorityChanged (QSet<int>, int)));
1389
1390  connect (myFileTreeView, SIGNAL (     wantedChanged (QSet<int>, bool)),
1391           this,           SLOT ( onFileWantedChanged (QSet<int>, bool)));
1392
1393  connect (myFileTreeView, SIGNAL (pathEdited (QString, QString)),
1394           this,           SLOT (onPathEdited (QString, QString)));
1395
1396  connect (myFileTreeView, SIGNAL (openRequested (QString)),
1397           this,           SLOT (onOpenRequested (QString)));
1398
1399  return myFileTreeView;
1400}
1401
1402void
1403Details::onFilePriorityChanged (const QSet<int>& indices, int priority)
1404{
1405  tr_quark key;
1406
1407  switch (priority)
1408    {
1409      case TR_PRI_LOW:
1410        key = TR_KEY_priority_low;
1411        break;
1412
1413      case TR_PRI_HIGH:
1414        key = TR_KEY_priority_high;
1415        break;
1416
1417      default:
1418        key = TR_KEY_priority_normal;
1419        break;
1420    }
1421
1422  mySession.torrentSet (myIds, key, indices.toList ());
1423  getNewData ();
1424}
1425
1426void
1427Details::onFileWantedChanged (const QSet<int>& indices, bool wanted)
1428{
1429  const tr_quark key = wanted ? TR_KEY_files_wanted : TR_KEY_files_unwanted;
1430  mySession.torrentSet (myIds, key, indices.toList ());
1431  getNewData ();
1432}
1433
1434void
1435Details::onPathEdited (const QString& oldpath, const QString& newname)
1436{
1437  mySession.torrentRenamePath (myIds, oldpath, newname);
1438}
1439
1440void
1441Details::onOpenRequested (const QString& path)
1442{
1443  if (!mySession.isLocal ())
1444    return;
1445
1446  foreach (const int id, myIds)
1447    {
1448      const Torrent * const tor = myModel.getTorrentFromId (id);
1449      if (tor == NULL)
1450        continue;
1451
1452      const QString localFilePath = tor->getPath () + "/" + path;
1453      if (!QFile::exists (localFilePath))
1454        continue;
1455
1456      if (QDesktopServices::openUrl (QUrl::fromLocalFile (localFilePath)))
1457        break;
1458    }
1459}
Note: See TracBrowser for help on using the repository browser.