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