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