source: trunk/qt/details.cc @ 14150

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

support qt5 in transmission-qt

  • Property svn:keywords set to Date Rev Author Id
File size: 39.6 KB
Line 
1/*
2 * This file Copyright (C) Mnemosyne LLC
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2
6 * as published by the Free Software Foundation.
7 *
8 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
9 *
10 * $Id: details.cc 14150 2013-07-27 21:58:14Z jordan $
11 */
12
13#include <cassert>
14#include <ctime>
15
16#include <QCheckBox>
17#include <QComboBox>
18#include <QDateTime>
19#include <QDialogButtonBox>
20#include <QDoubleSpinBox>
21#include <QEvent>
22#include <QFont>
23#include <QFontMetrics>
24#include <QHBoxLayout>
25#include <QHBoxLayout>
26#include <QHeaderView>
27#include <QInputDialog>
28#include <QItemSelectionModel>
29#include <QLabel>
30#include <QList>
31#include <QMap>
32#include <QMessageBox>
33#include <QPushButton>
34#include <QRadioButton>
35#include <QResizeEvent>
36#include <QSpinBox>
37#include <QStringList>
38#include <QStyle>
39#include <QTabWidget>
40#include <QTextBrowser>
41#include <QTreeView>
42#include <QTreeWidget>
43#include <QTreeWidgetItem>
44#include <QVBoxLayout>
45
46#include <libtransmission/transmission.h>
47#include <libtransmission/utils.h> // tr_getRatio ()
48
49#include "details.h"
50#include "file-tree.h"
51#include "formatter.h"
52#include "hig.h"
53#include "prefs.h"
54#include "session.h"
55#include "squeezelabel.h"
56#include "torrent.h"
57#include "torrent-model.h"
58#include "tracker-delegate.h"
59#include "tracker-model.h"
60#include "tracker-model-filter.h"
61
62class Prefs;
63class Session;
64
65/****
66*****
67****/
68
69namespace
70{
71  const int REFRESH_INTERVAL_MSEC = 4000;
72
73  const char * PREF_KEY ("pref-key");
74
75  enum // peer columns
76  {
77    COL_LOCK,
78    COL_UP,
79    COL_DOWN,
80    COL_PERCENT,
81    COL_STATUS,
82    COL_ADDRESS,
83    COL_CLIENT,
84    N_COLUMNS
85  };
86}
87
88/***
89****
90***/
91
92class PeerItem: public QTreeWidgetItem
93{
94    Peer peer;
95    QString collatedAddress;
96    QString status;
97
98  public:
99
100    virtual ~PeerItem () {}
101
102    PeerItem (const Peer& p)
103    {
104      peer = p;
105      int q[4];
106      if (sscanf (p.address.toUtf8 ().constData (), "%d.%d.%d.%d", q+0, q+1, q+2, q+3) == 4)
107        collatedAddress.sprintf ("%03d.%03d.%03d.%03d", q[0], q[1], q[2], q[3]);
108      else
109        collatedAddress = p.address;
110    }
111
112  public:
113
114    void refresh (const Peer& p) { peer = p; }
115
116    void setStatus (const QString& s) { status = s; }
117
118    virtual bool operator< (const QTreeWidgetItem & other) const
119    {
120      const PeerItem * i = dynamic_cast<const PeerItem*> (&other);
121      QTreeWidget * tw (treeWidget ());
122      const int column = tw ? tw->sortColumn () : 0;
123      switch (column)
124        {
125          case COL_UP: return peer.rateToPeer < i->peer.rateToPeer;
126          case COL_DOWN: return peer.rateToClient < i->peer.rateToClient;
127          case COL_PERCENT: return peer.progress < i->peer.progress;
128          case COL_STATUS: return status < i->status;
129          case COL_CLIENT: return peer.clientName < i->peer.clientName;
130          case COL_LOCK: return peer.isEncrypted && !i->peer.isEncrypted;
131          default: return collatedAddress < i->collatedAddress;
132        }
133    }
134};
135
136/***
137****
138***/
139
140QIcon
141Details :: getStockIcon (const QString& freedesktop_name, int fallback)
142{
143  QIcon icon = QIcon::fromTheme (freedesktop_name);
144
145  if (icon.isNull ())
146    icon = style ()->standardIcon (QStyle::StandardPixmap (fallback), 0, this);
147
148  return icon;
149}
150
151Details :: Details (Session       & session,
152                    Prefs         & prefs,
153                    TorrentModel  & model,
154                    QWidget       * parent):
155  QDialog (parent, Qt::Dialog),
156  mySession (session),
157  myPrefs (prefs),
158  myModel (model),
159  myChangedTorrents (false),
160  myHavePendingRefresh (false)
161{
162  QVBoxLayout * layout = new QVBoxLayout (this);
163
164  setWindowTitle (tr ("Torrent Properties"));
165
166  QTabWidget * t = new QTabWidget (this);
167  QWidget * w;
168  t->addTab (w = createInfoTab (),      tr ("Information"));
169  myWidgets << w;
170  t->addTab (w = createPeersTab (),     tr ("Peers"));
171  myWidgets << w;
172  t->addTab (w = createTrackerTab (),   tr ("Tracker"));
173  myWidgets << w;
174  t->addTab (w = createFilesTab (),     tr ("Files"));
175  myWidgets << w;
176  t->addTab (w = createOptionsTab (),   tr ("Options"));
177  myWidgets << w;
178  layout->addWidget (t);
179
180  QDialogButtonBox * buttons = new QDialogButtonBox (QDialogButtonBox::Close, Qt::Horizontal, this);
181  connect (buttons, SIGNAL (rejected ()), this, SLOT (close ()));
182  layout->addWidget (buttons);
183  QWidget::setAttribute (Qt::WA_DeleteOnClose, true);
184
185  QList<int> initKeys;
186  initKeys << Prefs :: SHOW_TRACKER_SCRAPES
187           << Prefs :: SHOW_BACKUP_TRACKERS;
188  foreach (int key, initKeys)
189    refreshPref (key);
190
191  connect (&myTimer, SIGNAL (timeout ()), this, SLOT (onTimer ()));
192  connect (&myPrefs, SIGNAL (changed (int)), this, SLOT (refreshPref (int)));
193
194  onTimer ();
195  myTimer.setSingleShot (false);
196  myTimer.start (REFRESH_INTERVAL_MSEC);
197}
198
199Details :: ~Details ()
200{
201  myTrackerDelegate->deleteLater ();
202  myTrackerFilter->deleteLater ();
203  myTrackerModel->deleteLater ();
204}
205
206void
207Details :: setIds (const QSet<int>& ids)
208{
209  if (ids == myIds)
210    return;
211
212  myChangedTorrents = true;
213
214  // stop listening to the old torrents
215  foreach (int id, myIds)
216    {
217      const Torrent * tor = myModel.getTorrentFromId (id);
218      if (tor)
219        disconnect (tor, SIGNAL (torrentChanged (int)), this, SLOT (onTorrentChanged ()));
220    }
221
222  myFileTreeView->clear ();
223  myIds = ids;
224  myTrackerModel->refresh (myModel, myIds);
225
226  // listen to the new torrents
227  foreach (int id, myIds)
228    {
229      const Torrent * tor = myModel.getTorrentFromId (id);
230      if (tor)
231        connect (tor, SIGNAL (torrentChanged (int)), this, SLOT (onTorrentChanged ()));
232    }
233
234  foreach (QWidget * w, myWidgets)
235    w->setEnabled (false);
236
237  onTimer ();
238}
239
240void
241Details :: refreshPref (int key)
242{
243  QString str;
244
245  switch (key)
246    {
247      case Prefs :: SHOW_TRACKER_SCRAPES:
248        {
249          QItemSelectionModel * selectionModel (myTrackerView->selectionModel ());
250          const QItemSelection selection (selectionModel->selection ());
251          const QModelIndex currentIndex (selectionModel->currentIndex ());
252          myTrackerDelegate->setShowMore (myPrefs.getBool (key));
253          selectionModel->clear ();
254          myTrackerView->reset ();
255          selectionModel->select (selection, QItemSelectionModel::Select);
256          selectionModel->setCurrentIndex (currentIndex, QItemSelectionModel::NoUpdate);
257          break;
258        }
259
260      case Prefs :: SHOW_BACKUP_TRACKERS:
261        myTrackerFilter->setShowBackupTrackers (myPrefs.getBool (key));
262        break;
263
264      default:
265        break;
266    }
267}
268
269
270/***
271****
272***/
273
274QString
275Details :: timeToStringRounded (int seconds)
276{
277  if (seconds > 60)
278    seconds -= (seconds % 60);
279
280  return Formatter::timeToString (seconds);
281}
282
283void
284Details :: onTimer ()
285{
286  getNewData ();
287}
288
289void
290Details :: getNewData ()
291{
292  if (!myIds.empty ())
293    {
294      QSet<int> infos;
295      foreach (int id, myIds)
296        {
297          const Torrent * tor = myModel.getTorrentFromId (id);
298          if (tor->isMagnet ())
299            infos.insert (tor->id ());
300        }
301
302      if (!infos.isEmpty ())
303        mySession.initTorrents (infos);
304      mySession.refreshExtraStats (myIds);
305    }
306}
307
308void
309Details :: onTorrentChanged ()
310{
311  if (!myHavePendingRefresh)
312    {
313      myHavePendingRefresh = true;
314      QTimer::singleShot (100, this, SLOT (refresh ()));
315    }
316}
317
318namespace
319{
320  void setIfIdle (QComboBox * box, int i)
321  {
322    if (!box->hasFocus ())
323      {
324        box->blockSignals (true);
325        box->setCurrentIndex (i);
326        box->blockSignals (false);
327      }
328  }
329
330  void setIfIdle (QDoubleSpinBox * spin, double value)
331  {
332    if (!spin->hasFocus ())
333      {
334        spin->blockSignals (true);
335        spin->setValue (value);
336        spin->blockSignals (false);
337      }
338  }
339
340  void setIfIdle (QSpinBox * spin, int value)
341  {
342    if (!spin->hasFocus ())
343      {
344        spin->blockSignals (true);
345        spin->setValue (value);
346        spin->blockSignals (false);
347      }
348  }
349}
350
351void
352Details :: refresh ()
353{
354  const int n = myIds.size ();
355  const bool single = n == 1;
356  const QString blank;
357  const QFontMetrics fm (fontMetrics ());
358  QList<const Torrent*> torrents;
359  QString string;
360  const QString none = tr ("None");
361  const QString mixed = tr ("Mixed");
362  const QString unknown = tr ("Unknown");
363
364  // build a list of torrents
365  foreach (int id, myIds)
366    {
367      const Torrent * tor = myModel.getTorrentFromId (id);
368      if (tor)
369        torrents << tor;
370    }
371
372  ///
373  ///  activity tab
374  ///
375
376  // myStateLabel
377  if (torrents.empty ())
378    {
379      string = none;
380    }
381  else
382    {
383      bool isMixed = false;
384      bool allPaused = true;
385      bool allFinished = true;
386      const tr_torrent_activity baseline = torrents[0]->getActivity ();
387      foreach (const Torrent * t, torrents)
388        {
389          const tr_torrent_activity activity = t->getActivity ();
390          if (activity != baseline)
391            isMixed = true;
392          if (activity != TR_STATUS_STOPPED)
393            allPaused = allFinished = false;
394          if (!t->isFinished ())
395            allFinished = false;
396        }
397
398      if (isMixed)
399        string = mixed;
400      else if (allFinished)
401        string = tr ("Finished");
402      else if (allPaused)
403        string = tr ("Paused");
404      else
405        string = torrents[0]->activityString ();
406    }
407  myStateLabel->setText (string);
408  const QString stateString = string;
409
410  // myHaveLabel
411  double sizeWhenDone = 0;
412  double leftUntilDone = 0;
413  double available = 0;
414  int64_t haveTotal = 0;
415  int64_t haveVerified = 0;
416  int64_t haveUnverified = 0;
417  int64_t verifiedPieces = 0;
418  if (torrents.empty ())
419    {
420      string = none;
421    }
422  else
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 = (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 ("Downloaded:"), myDownloadedLabel = new SqueezeLabel);
947  hig->addRow (tr ("Uploaded:"), myUploadedLabel = 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 (const QItemSelection&, const 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 (const QSet<int>&, int)),
1388           this,           SLOT ( onFilePriorityChanged (const QSet<int>&, int)));
1389
1390  connect (myFileTreeView, SIGNAL (     wantedChanged (const QSet<int>&, bool)),
1391           this,           SLOT ( onFileWantedChanged (const QSet<int>&, bool)));
1392
1393  connect (myFileTreeView, SIGNAL (pathEdited (const QString&, const QString&)),
1394           this,           SLOT (onPathEdited (const QString&, const QString&)));
1395
1396  return myFileTreeView;
1397}
1398
1399void
1400Details :: onFilePriorityChanged (const QSet<int>& indices, int priority)
1401{
1402  tr_quark key;
1403
1404  switch (priority)
1405    {
1406      case TR_PRI_LOW:
1407        key = TR_KEY_priority_low;
1408        break;
1409
1410      case TR_PRI_HIGH:
1411        key = TR_KEY_priority_high;
1412        break;
1413
1414      default:
1415        key = TR_KEY_priority_normal;
1416        break;
1417    }
1418
1419  mySession.torrentSet (myIds, key, indices.toList ());
1420  getNewData ();
1421}
1422
1423void
1424Details :: onFileWantedChanged (const QSet<int>& indices, bool wanted)
1425{
1426  const tr_quark key = wanted ? TR_KEY_files_wanted : TR_KEY_files_unwanted;
1427  mySession.torrentSet (myIds, key, indices.toList ());
1428  getNewData ();
1429}
1430
1431void
1432Details :: onPathEdited (const QString& oldpath, const QString& newname)
1433{
1434  mySession.torrentRenamePath (myIds, oldpath, newname);
1435}
Note: See TracBrowser for help on using the repository browser.