source: trunk/qt/details.cc @ 14185

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

revert quint64/quint32/qint64/qint32 use to inttypes to match libtransmission's API

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