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