source: trunk/qt/details.cc @ 14386

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

Do not use printf-style formatting in C++ code

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