1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 *
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
28 */
29
30 #include "transferlistmodel.h"
31
32 #include <QApplication>
33 #include <QDateTime>
34 #include <QDebug>
35 #include <QIcon>
36 #include <QPalette>
37
38 #include "base/bittorrent/session.h"
39 #include "base/bittorrent/torrent.h"
40 #include "base/global.h"
41 #include "base/preferences.h"
42 #include "base/unicodestrings.h"
43 #include "base/utils/fs.h"
44 #include "base/utils/misc.h"
45 #include "base/utils/string.h"
46 #include "uithememanager.h"
47
48 static QIcon getIconByState(BitTorrent::TorrentState state);
49 static QColor getDefaultColorByState(BitTorrent::TorrentState state);
50
51 static QIcon getPausedIcon();
52 static QIcon getQueuedIcon();
53 static QIcon getDownloadingIcon();
54 static QIcon getStalledDownloadingIcon();
55 static QIcon getUploadingIcon();
56 static QIcon getStalledUploadingIcon();
57 static QIcon getCompletedIcon();
58 static QIcon getCheckingIcon();
59 static QIcon getErrorIcon();
60
61 static bool isDarkTheme();
62
63 namespace
64 {
torrentStateColorsFromUITheme()65 QHash<BitTorrent::TorrentState, QColor> torrentStateColorsFromUITheme()
66 {
67 struct TorrentStateColorDescriptor
68 {
69 const BitTorrent::TorrentState state;
70 const QString id;
71 };
72
73 const TorrentStateColorDescriptor colorDescriptors[] =
74 {
75 {BitTorrent::TorrentState::Downloading, QLatin1String("TransferList.Downloading")},
76 {BitTorrent::TorrentState::StalledDownloading, QLatin1String("TransferList.StalledDownloading")},
77 {BitTorrent::TorrentState::DownloadingMetadata, QLatin1String("TransferList.DownloadingMetadata")},
78 {BitTorrent::TorrentState::ForcedDownloading, QLatin1String("TransferList.ForcedDownloading")},
79 {BitTorrent::TorrentState::Uploading, QLatin1String("TransferList.Uploading")},
80 {BitTorrent::TorrentState::StalledUploading, QLatin1String("TransferList.StalledUploading")},
81 {BitTorrent::TorrentState::ForcedUploading, QLatin1String("TransferList.ForcedUploading")},
82 {BitTorrent::TorrentState::QueuedDownloading, QLatin1String("TransferList.QueuedDownloading")},
83 {BitTorrent::TorrentState::QueuedUploading, QLatin1String("TransferList.QueuedUploading")},
84 {BitTorrent::TorrentState::CheckingDownloading, QLatin1String("TransferList.CheckingDownloading")},
85 {BitTorrent::TorrentState::CheckingUploading, QLatin1String("TransferList.CheckingUploading")},
86 {BitTorrent::TorrentState::CheckingResumeData, QLatin1String("TransferList.CheckingResumeData")},
87 {BitTorrent::TorrentState::PausedDownloading, QLatin1String("TransferList.PausedDownloading")},
88 {BitTorrent::TorrentState::PausedUploading, QLatin1String("TransferList.PausedUploading")},
89 {BitTorrent::TorrentState::Moving, QLatin1String("TransferList.Moving")},
90 {BitTorrent::TorrentState::MissingFiles, QLatin1String("TransferList.MissingFiles")},
91 {BitTorrent::TorrentState::Error, QLatin1String("TransferList.Error")}
92 };
93
94 QHash<BitTorrent::TorrentState, QColor> colors;
95 for (const TorrentStateColorDescriptor &colorDescriptor : colorDescriptors)
96 {
97 const QColor themeColor = UIThemeManager::instance()->getColor(colorDescriptor.id, QColor());
98 if (themeColor.isValid())
99 colors.insert(colorDescriptor.state, themeColor);
100 }
101 return colors;
102 }
103 }
104
105 // TransferListModel
106
TransferListModel(QObject * parent)107 TransferListModel::TransferListModel(QObject *parent)
108 : QAbstractListModel {parent}
109 , m_statusStrings
110 {
111 {BitTorrent::TorrentState::Downloading, tr("Downloading")},
112 {BitTorrent::TorrentState::StalledDownloading, tr("Stalled", "Torrent is waiting for download to begin")},
113 {BitTorrent::TorrentState::DownloadingMetadata, tr("Downloading metadata", "Used when loading a magnet link")},
114 {BitTorrent::TorrentState::ForcedDownloading, tr("[F] Downloading", "Used when the torrent is forced started. You probably shouldn't translate the F.")},
115 {BitTorrent::TorrentState::Uploading, tr("Seeding", "Torrent is complete and in upload-only mode")},
116 {BitTorrent::TorrentState::StalledUploading, tr("Seeding", "Torrent is complete and in upload-only mode")},
117 {BitTorrent::TorrentState::ForcedUploading, tr("[F] Seeding", "Used when the torrent is forced started. You probably shouldn't translate the F.")},
118 {BitTorrent::TorrentState::QueuedDownloading, tr("Queued", "Torrent is queued")},
119 {BitTorrent::TorrentState::QueuedUploading, tr("Queued", "Torrent is queued")},
120 {BitTorrent::TorrentState::CheckingDownloading, tr("Checking", "Torrent local data is being checked")},
121 {BitTorrent::TorrentState::CheckingUploading, tr("Checking", "Torrent local data is being checked")},
122 {BitTorrent::TorrentState::CheckingResumeData, tr("Checking resume data", "Used when loading the torrents from disk after qbt is launched. It checks the correctness of the .fastresume file. Normally it is completed in a fraction of a second, unless loading many many torrents.")},
123 {BitTorrent::TorrentState::PausedDownloading, tr("Paused")},
124 {BitTorrent::TorrentState::PausedUploading, tr("Completed")},
125 {BitTorrent::TorrentState::Moving, tr("Moving", "Torrent local data are being moved/relocated")},
126 {BitTorrent::TorrentState::MissingFiles, tr("Missing Files")},
127 {BitTorrent::TorrentState::Error, tr("Errored", "Torrent status, the torrent has an error")}
128 }
129 , m_stateThemeColors {torrentStateColorsFromUITheme()}
130 {
131 configure();
132 connect(Preferences::instance(), &Preferences::changed, this, &TransferListModel::configure);
133
134 // Load the torrents
135 using namespace BitTorrent;
136 for (Torrent *const torrent : asConst(Session::instance()->torrents()))
137 addTorrent(torrent);
138
139 // Listen for torrent changes
140 connect(Session::instance(), &Session::torrentLoaded, this, &TransferListModel::addTorrent);
141 connect(Session::instance(), &Session::torrentAboutToBeRemoved, this, &TransferListModel::handleTorrentAboutToBeRemoved);
142 connect(Session::instance(), &Session::torrentsUpdated, this, &TransferListModel::handleTorrentsUpdated);
143
144 connect(Session::instance(), &Session::torrentFinished, this, &TransferListModel::handleTorrentStatusUpdated);
145 connect(Session::instance(), &Session::torrentMetadataReceived, this, &TransferListModel::handleTorrentStatusUpdated);
146 connect(Session::instance(), &Session::torrentResumed, this, &TransferListModel::handleTorrentStatusUpdated);
147 connect(Session::instance(), &Session::torrentPaused, this, &TransferListModel::handleTorrentStatusUpdated);
148 connect(Session::instance(), &Session::torrentFinishedChecking, this, &TransferListModel::handleTorrentStatusUpdated);
149 }
150
rowCount(const QModelIndex &) const151 int TransferListModel::rowCount(const QModelIndex &) const
152 {
153 return m_torrentList.size();
154 }
155
columnCount(const QModelIndex &) const156 int TransferListModel::columnCount(const QModelIndex &) const
157 {
158 return NB_COLUMNS;
159 }
160
headerData(int section,Qt::Orientation orientation,int role) const161 QVariant TransferListModel::headerData(int section, Qt::Orientation orientation, int role) const
162 {
163 if (orientation == Qt::Horizontal)
164 {
165 if (role == Qt::DisplayRole)
166 {
167 switch (section)
168 {
169 case TR_QUEUE_POSITION: return QChar('#');
170 case TR_NAME: return tr("Name", "i.e: torrent name");
171 case TR_SIZE: return tr("Size", "i.e: torrent size");
172 case TR_PROGRESS: return tr("Progress", "% Done");
173 case TR_STATUS: return tr("Status", "Torrent status (e.g. downloading, seeding, paused)");
174 case TR_SEEDS: return tr("Seeds", "i.e. full sources (often untranslated)");
175 case TR_PEERS: return tr("Peers", "i.e. partial sources (often untranslated)");
176 case TR_DLSPEED: return tr("Down Speed", "i.e: Download speed");
177 case TR_UPSPEED: return tr("Up Speed", "i.e: Upload speed");
178 case TR_RATIO: return tr("Ratio", "Share ratio");
179 case TR_ETA: return tr("ETA", "i.e: Estimated Time of Arrival / Time left");
180 case TR_CATEGORY: return tr("Category");
181 case TR_TAGS: return tr("Tags");
182 case TR_ADD_DATE: return tr("Added On", "Torrent was added to transfer list on 01/01/2010 08:00");
183 case TR_SEED_DATE: return tr("Completed On", "Torrent was completed on 01/01/2010 08:00");
184 case TR_TRACKER: return tr("Tracker");
185 case TR_DLLIMIT: return tr("Down Limit", "i.e: Download limit");
186 case TR_UPLIMIT: return tr("Up Limit", "i.e: Upload limit");
187 case TR_AMOUNT_DOWNLOADED: return tr("Downloaded", "Amount of data downloaded (e.g. in MB)");
188 case TR_AMOUNT_UPLOADED: return tr("Uploaded", "Amount of data uploaded (e.g. in MB)");
189 case TR_AMOUNT_DOWNLOADED_SESSION: return tr("Session Download", "Amount of data downloaded since program open (e.g. in MB)");
190 case TR_AMOUNT_UPLOADED_SESSION: return tr("Session Upload", "Amount of data uploaded since program open (e.g. in MB)");
191 case TR_AMOUNT_LEFT: return tr("Remaining", "Amount of data left to download (e.g. in MB)");
192 case TR_TIME_ELAPSED: return tr("Time Active", "Time (duration) the torrent is active (not paused)");
193 case TR_SAVE_PATH: return tr("Save path", "Torrent save path");
194 case TR_COMPLETED: return tr("Completed", "Amount of data completed (e.g. in MB)");
195 case TR_RATIO_LIMIT: return tr("Ratio Limit", "Upload share ratio limit");
196 case TR_SEEN_COMPLETE_DATE: return tr("Last Seen Complete", "Indicates the time when the torrent was last seen complete/whole");
197 case TR_LAST_ACTIVITY: return tr("Last Activity", "Time passed since a chunk was downloaded/uploaded");
198 case TR_TOTAL_SIZE: return tr("Total Size", "i.e. Size including unwanted data");
199 case TR_AVAILABILITY: return tr("Availability", "The number of distributed copies of the torrent");
200 default: return {};
201 }
202 }
203 else if (role == Qt::TextAlignmentRole)
204 {
205 switch (section)
206 {
207 case TR_AMOUNT_DOWNLOADED:
208 case TR_AMOUNT_UPLOADED:
209 case TR_AMOUNT_DOWNLOADED_SESSION:
210 case TR_AMOUNT_UPLOADED_SESSION:
211 case TR_AMOUNT_LEFT:
212 case TR_COMPLETED:
213 case TR_SIZE:
214 case TR_TOTAL_SIZE:
215 case TR_ETA:
216 case TR_SEEDS:
217 case TR_PEERS:
218 case TR_UPSPEED:
219 case TR_DLSPEED:
220 case TR_UPLIMIT:
221 case TR_DLLIMIT:
222 case TR_RATIO_LIMIT:
223 case TR_RATIO:
224 case TR_QUEUE_POSITION:
225 case TR_LAST_ACTIVITY:
226 case TR_AVAILABILITY:
227 return QVariant(Qt::AlignRight | Qt::AlignVCenter);
228 default:
229 return QAbstractListModel::headerData(section, orientation, role);
230 }
231 }
232 }
233
234 return {};
235 }
236
displayValue(const BitTorrent::Torrent * torrent,const int column) const237 QString TransferListModel::displayValue(const BitTorrent::Torrent *torrent, const int column) const
238 {
239 bool hideValues = false;
240 if (m_hideZeroValuesMode == HideZeroValuesMode::Always)
241 hideValues = true;
242 else if (m_hideZeroValuesMode == HideZeroValuesMode::Paused)
243 hideValues = (torrent->state() == BitTorrent::TorrentState::PausedDownloading);
244
245 const auto availabilityString = [hideValues](const qreal value) -> QString
246 {
247 if (hideValues && (value == 0))
248 return {};
249 return (value >= 0)
250 ? Utils::String::fromDouble(value, 3)
251 : tr("N/A");
252 };
253
254 const auto unitString = [hideValues](const qint64 value, const bool isSpeedUnit = false) -> QString
255 {
256 return (hideValues && (value == 0))
257 ? QString {} : Utils::Misc::friendlyUnit(value, isSpeedUnit);
258 };
259
260 const auto limitString = [hideValues](const qint64 value) -> QString
261 {
262 if (hideValues && (value <= 0))
263 return {};
264
265 return (value > 0)
266 ? Utils::Misc::friendlyUnit(value, true)
267 : QString::fromUtf8(C_INFINITY);
268 };
269
270 const auto amountString = [hideValues](const qint64 value, const qint64 total) -> QString
271 {
272 if (hideValues && (value == 0) && (total == 0))
273 return {};
274 return QString::fromLatin1("%1 (%2)").arg(QString::number(value), QString::number(total));
275 };
276
277 const auto etaString = [hideValues](const qlonglong value) -> QString
278 {
279 if (hideValues && (value >= MAX_ETA))
280 return {};
281 return Utils::Misc::userFriendlyDuration(value, MAX_ETA);
282 };
283
284 const auto ratioString = [hideValues](const qreal value) -> QString
285 {
286 if (hideValues && (value <= 0))
287 return {};
288
289 return ((static_cast<int>(value) == -1) || (value > BitTorrent::Torrent::MAX_RATIO))
290 ? QString::fromUtf8(C_INFINITY) : Utils::String::fromDouble(value, 2);
291 };
292
293 const auto queuePositionString = [](const qint64 value) -> QString
294 {
295 return (value >= 0) ? QString::number(value + 1) : QLatin1String("*");
296 };
297
298 const auto lastActivityString = [hideValues](qint64 value) -> QString
299 {
300 if (hideValues && ((value < 0) || (value >= MAX_ETA)))
301 return {};
302
303 // Show '< 1m ago' when elapsed time is 0
304 if (value == 0)
305 value = 1;
306
307 return (value >= 0)
308 ? tr("%1 ago", "e.g.: 1h 20m ago").arg(Utils::Misc::userFriendlyDuration(value))
309 : Utils::Misc::userFriendlyDuration(value);
310 };
311
312 const auto timeElapsedString = [hideValues](const qint64 elapsedTime, const qint64 seedingTime) -> QString
313 {
314 if (seedingTime <= 0)
315 {
316 if (hideValues && (elapsedTime == 0))
317 return {};
318 return Utils::Misc::userFriendlyDuration(elapsedTime);
319 }
320
321 return tr("%1 (seeded for %2)", "e.g. 4m39s (seeded for 3m10s)")
322 .arg(Utils::Misc::userFriendlyDuration(elapsedTime)
323 , Utils::Misc::userFriendlyDuration(seedingTime));
324 };
325
326 const auto tagsString = [](const QSet<QString> &tags) -> QString
327 {
328 QStringList tagsList = tags.values();
329 tagsList.sort();
330 return tagsList.join(", ");
331 };
332
333 const auto progressString = [](const qreal progress) -> QString
334 {
335 return (progress >= 1)
336 ? QString::fromLatin1("100%")
337 : Utils::String::fromDouble((progress * 100), 1) + QLatin1Char('%');
338 };
339
340 const auto statusString = [this](const BitTorrent::TorrentState state, const QString &errorMessage) -> QString
341 {
342 return (state == BitTorrent::TorrentState::Error)
343 ? m_statusStrings[state] + ": " + errorMessage
344 : m_statusStrings[state];
345 };
346
347 switch (column)
348 {
349 case TR_NAME:
350 return torrent->name();
351 case TR_QUEUE_POSITION:
352 return queuePositionString(torrent->queuePosition());
353 case TR_SIZE:
354 return unitString(torrent->wantedSize());
355 case TR_PROGRESS:
356 return progressString(torrent->progress());
357 case TR_STATUS:
358 return statusString(torrent->state(), torrent->error());
359 case TR_SEEDS:
360 return amountString(torrent->seedsCount(), torrent->totalSeedsCount());
361 case TR_PEERS:
362 return amountString(torrent->leechsCount(), torrent->totalLeechersCount());
363 case TR_DLSPEED:
364 return unitString(torrent->downloadPayloadRate(), true);
365 case TR_UPSPEED:
366 return unitString(torrent->uploadPayloadRate(), true);
367 case TR_ETA:
368 return etaString(torrent->eta());
369 case TR_RATIO:
370 return ratioString(torrent->realRatio());
371 case TR_RATIO_LIMIT:
372 return ratioString(torrent->maxRatio());
373 case TR_CATEGORY:
374 return torrent->category();
375 case TR_TAGS:
376 return tagsString(torrent->tags());
377 case TR_ADD_DATE:
378 return QLocale().toString(torrent->addedTime().toLocalTime(), QLocale::ShortFormat);
379 case TR_SEED_DATE:
380 return QLocale().toString(torrent->completedTime().toLocalTime(), QLocale::ShortFormat);
381 case TR_TRACKER:
382 return torrent->currentTracker();
383 case TR_DLLIMIT:
384 return limitString(torrent->downloadLimit());
385 case TR_UPLIMIT:
386 return limitString(torrent->uploadLimit());
387 case TR_AMOUNT_DOWNLOADED:
388 return unitString(torrent->totalDownload());
389 case TR_AMOUNT_UPLOADED:
390 return unitString(torrent->totalUpload());
391 case TR_AMOUNT_DOWNLOADED_SESSION:
392 return unitString(torrent->totalPayloadDownload());
393 case TR_AMOUNT_UPLOADED_SESSION:
394 return unitString(torrent->totalPayloadUpload());
395 case TR_AMOUNT_LEFT:
396 return unitString(torrent->remainingSize());
397 case TR_TIME_ELAPSED:
398 return timeElapsedString(torrent->activeTime(), torrent->seedingTime());
399 case TR_SAVE_PATH:
400 return Utils::Fs::toNativePath(torrent->savePath());
401 case TR_COMPLETED:
402 return unitString(torrent->completedSize());
403 case TR_SEEN_COMPLETE_DATE:
404 return QLocale().toString(torrent->lastSeenComplete().toLocalTime(), QLocale::ShortFormat);
405 case TR_LAST_ACTIVITY:
406 return lastActivityString((torrent->isPaused() || torrent->isChecking()) ? -1 : torrent->timeSinceActivity());
407 case TR_AVAILABILITY:
408 return availabilityString(torrent->distributedCopies());
409 case TR_TOTAL_SIZE:
410 return unitString(torrent->totalSize());
411 }
412
413 return {};
414 }
415
internalValue(const BitTorrent::Torrent * torrent,const int column,const bool alt) const416 QVariant TransferListModel::internalValue(const BitTorrent::Torrent *torrent, const int column, const bool alt) const
417 {
418 switch (column)
419 {
420 case TR_NAME:
421 return torrent->name();
422 case TR_QUEUE_POSITION:
423 return torrent->queuePosition();
424 case TR_SIZE:
425 return torrent->wantedSize();
426 case TR_PROGRESS:
427 return torrent->progress() * 100;
428 case TR_STATUS:
429 return QVariant::fromValue(torrent->state());
430 case TR_SEEDS:
431 return !alt ? torrent->seedsCount() : torrent->totalSeedsCount();
432 case TR_PEERS:
433 return !alt ? torrent->leechsCount() : torrent->totalLeechersCount();
434 case TR_DLSPEED:
435 return torrent->downloadPayloadRate();
436 case TR_UPSPEED:
437 return torrent->uploadPayloadRate();
438 case TR_ETA:
439 return torrent->eta();
440 case TR_RATIO:
441 return torrent->realRatio();
442 case TR_CATEGORY:
443 return torrent->category();
444 case TR_TAGS:
445 return QStringList {torrent->tags().values()};
446 case TR_ADD_DATE:
447 return torrent->addedTime();
448 case TR_SEED_DATE:
449 return torrent->completedTime();
450 case TR_TRACKER:
451 return torrent->currentTracker();
452 case TR_DLLIMIT:
453 return torrent->downloadLimit();
454 case TR_UPLIMIT:
455 return torrent->uploadLimit();
456 case TR_AMOUNT_DOWNLOADED:
457 return torrent->totalDownload();
458 case TR_AMOUNT_UPLOADED:
459 return torrent->totalUpload();
460 case TR_AMOUNT_DOWNLOADED_SESSION:
461 return torrent->totalPayloadDownload();
462 case TR_AMOUNT_UPLOADED_SESSION:
463 return torrent->totalPayloadUpload();
464 case TR_AMOUNT_LEFT:
465 return torrent->remainingSize();
466 case TR_TIME_ELAPSED:
467 return !alt ? torrent->activeTime() : torrent->seedingTime();
468 case TR_SAVE_PATH:
469 return Utils::Fs::toNativePath(torrent->savePath());
470 case TR_COMPLETED:
471 return torrent->completedSize();
472 case TR_RATIO_LIMIT:
473 return torrent->maxRatio();
474 case TR_SEEN_COMPLETE_DATE:
475 return torrent->lastSeenComplete();
476 case TR_LAST_ACTIVITY:
477 return (torrent->isPaused() || torrent->isChecking()) ? -1 : torrent->timeSinceActivity();
478 case TR_AVAILABILITY:
479 return torrent->distributedCopies();
480 case TR_TOTAL_SIZE:
481 return torrent->totalSize();
482 }
483
484 return {};
485 }
486
data(const QModelIndex & index,const int role) const487 QVariant TransferListModel::data(const QModelIndex &index, const int role) const
488 {
489 if (!index.isValid()) return {};
490
491 const BitTorrent::Torrent *torrent = m_torrentList.value(index.row());
492 if (!torrent) return {};
493
494 switch (role)
495 {
496 case Qt::ForegroundRole:
497 return m_stateThemeColors.value(torrent->state(), getDefaultColorByState(torrent->state()));
498 case Qt::DisplayRole:
499 return displayValue(torrent, index.column());
500 case UnderlyingDataRole:
501 return internalValue(torrent, index.column(), false);
502 case AdditionalUnderlyingDataRole:
503 return internalValue(torrent, index.column(), true);
504 case Qt::DecorationRole:
505 if (index.column() == TR_NAME)
506 return getIconByState(torrent->state());
507 break;
508 case Qt::ToolTipRole:
509 switch (index.column())
510 {
511 case TR_NAME:
512 case TR_STATUS:
513 case TR_CATEGORY:
514 case TR_TAGS:
515 case TR_TRACKER:
516 case TR_SAVE_PATH:
517 return displayValue(torrent, index.column());
518 }
519 break;
520 case Qt::TextAlignmentRole:
521 switch (index.column())
522 {
523 case TR_AMOUNT_DOWNLOADED:
524 case TR_AMOUNT_UPLOADED:
525 case TR_AMOUNT_DOWNLOADED_SESSION:
526 case TR_AMOUNT_UPLOADED_SESSION:
527 case TR_AMOUNT_LEFT:
528 case TR_COMPLETED:
529 case TR_SIZE:
530 case TR_TOTAL_SIZE:
531 case TR_ETA:
532 case TR_SEEDS:
533 case TR_PEERS:
534 case TR_UPSPEED:
535 case TR_DLSPEED:
536 case TR_UPLIMIT:
537 case TR_DLLIMIT:
538 case TR_RATIO_LIMIT:
539 case TR_RATIO:
540 case TR_QUEUE_POSITION:
541 case TR_LAST_ACTIVITY:
542 case TR_AVAILABILITY:
543 return QVariant {Qt::AlignRight | Qt::AlignVCenter};
544 }
545 }
546
547 return {};
548 }
549
setData(const QModelIndex & index,const QVariant & value,int role)550 bool TransferListModel::setData(const QModelIndex &index, const QVariant &value, int role)
551 {
552 if (!index.isValid() || (role != Qt::DisplayRole)) return false;
553
554 BitTorrent::Torrent *const torrent = m_torrentList.value(index.row());
555 if (!torrent) return false;
556
557 // Category and Name columns can be edited
558 switch (index.column())
559 {
560 case TR_NAME:
561 torrent->setName(value.toString());
562 break;
563 case TR_CATEGORY:
564 torrent->setCategory(value.toString());
565 break;
566 default:
567 return false;
568 }
569
570 return true;
571 }
572
addTorrent(BitTorrent::Torrent * const torrent)573 void TransferListModel::addTorrent(BitTorrent::Torrent *const torrent)
574 {
575 Q_ASSERT(!m_torrentMap.contains(torrent));
576
577 const int row = m_torrentList.size();
578
579 beginInsertRows({}, row, row);
580 m_torrentList << torrent;
581 m_torrentMap[torrent] = row;
582 endInsertRows();
583 }
584
flags(const QModelIndex & index) const585 Qt::ItemFlags TransferListModel::flags(const QModelIndex &index) const
586 {
587 if (!index.isValid()) return Qt::NoItemFlags;
588
589 // Explicitly mark as editable
590 return QAbstractListModel::flags(index) | Qt::ItemIsEditable;
591 }
592
torrentHandle(const QModelIndex & index) const593 BitTorrent::Torrent *TransferListModel::torrentHandle(const QModelIndex &index) const
594 {
595 if (!index.isValid()) return nullptr;
596
597 return m_torrentList.value(index.row());
598 }
599
handleTorrentAboutToBeRemoved(BitTorrent::Torrent * const torrent)600 void TransferListModel::handleTorrentAboutToBeRemoved(BitTorrent::Torrent *const torrent)
601 {
602 const int row = m_torrentMap.value(torrent, -1);
603 Q_ASSERT(row >= 0);
604
605 beginRemoveRows({}, row, row);
606 m_torrentList.removeAt(row);
607 m_torrentMap.remove(torrent);
608 for (int &value : m_torrentMap)
609 {
610 if (value > row)
611 --value;
612 }
613 endRemoveRows();
614 }
615
handleTorrentStatusUpdated(BitTorrent::Torrent * const torrent)616 void TransferListModel::handleTorrentStatusUpdated(BitTorrent::Torrent *const torrent)
617 {
618 const int row = m_torrentMap.value(torrent, -1);
619 Q_ASSERT(row >= 0);
620
621 emit dataChanged(index(row, 0), index(row, columnCount() - 1));
622 }
623
handleTorrentsUpdated(const QVector<BitTorrent::Torrent * > & torrents)624 void TransferListModel::handleTorrentsUpdated(const QVector<BitTorrent::Torrent *> &torrents)
625 {
626 const int columns = (columnCount() - 1);
627
628 if (torrents.size() <= (m_torrentList.size() * 0.5))
629 {
630 for (BitTorrent::Torrent *const torrent : torrents)
631 {
632 const int row = m_torrentMap.value(torrent, -1);
633 Q_ASSERT(row >= 0);
634
635 emit dataChanged(index(row, 0), index(row, columns));
636 }
637 }
638 else
639 {
640 // save the overhead when more than half of the torrent list needs update
641 emit dataChanged(index(0, 0), index((rowCount() - 1), columns));
642 }
643 }
644
configure()645 void TransferListModel::configure()
646 {
647 const Preferences *pref = Preferences::instance();
648
649 HideZeroValuesMode hideZeroValuesMode = HideZeroValuesMode::Never;
650 if (pref->getHideZeroValues())
651 {
652 if (pref->getHideZeroComboValues() == 1)
653 hideZeroValuesMode = HideZeroValuesMode::Paused;
654 else
655 hideZeroValuesMode = HideZeroValuesMode::Always;
656 }
657
658 if (m_hideZeroValuesMode != hideZeroValuesMode)
659 {
660 m_hideZeroValuesMode = hideZeroValuesMode;
661 emit dataChanged(index(0, 0), index((rowCount() - 1), (columnCount() - 1)));
662 }
663 }
664
665 // Static functions
666
getIconByState(const BitTorrent::TorrentState state)667 QIcon getIconByState(const BitTorrent::TorrentState state)
668 {
669 switch (state)
670 {
671 case BitTorrent::TorrentState::Downloading:
672 case BitTorrent::TorrentState::ForcedDownloading:
673 case BitTorrent::TorrentState::DownloadingMetadata:
674 return getDownloadingIcon();
675 case BitTorrent::TorrentState::StalledDownloading:
676 return getStalledDownloadingIcon();
677 case BitTorrent::TorrentState::StalledUploading:
678 return getStalledUploadingIcon();
679 case BitTorrent::TorrentState::Uploading:
680 case BitTorrent::TorrentState::ForcedUploading:
681 return getUploadingIcon();
682 case BitTorrent::TorrentState::PausedDownloading:
683 return getPausedIcon();
684 case BitTorrent::TorrentState::PausedUploading:
685 return getCompletedIcon();
686 case BitTorrent::TorrentState::QueuedDownloading:
687 case BitTorrent::TorrentState::QueuedUploading:
688 return getQueuedIcon();
689 case BitTorrent::TorrentState::CheckingDownloading:
690 case BitTorrent::TorrentState::CheckingUploading:
691 case BitTorrent::TorrentState::CheckingResumeData:
692 case BitTorrent::TorrentState::Moving:
693 return getCheckingIcon();
694 case BitTorrent::TorrentState::Unknown:
695 case BitTorrent::TorrentState::MissingFiles:
696 case BitTorrent::TorrentState::Error:
697 return getErrorIcon();
698 default:
699 Q_ASSERT(false);
700 return getErrorIcon();
701 }
702 }
703
getDefaultColorByState(const BitTorrent::TorrentState state)704 QColor getDefaultColorByState(const BitTorrent::TorrentState state)
705 {
706 // Color names taken from http://cloford.com/resources/colours/500col.htm
707 bool dark = isDarkTheme();
708
709 switch (state)
710 {
711 case BitTorrent::TorrentState::Downloading:
712 case BitTorrent::TorrentState::ForcedDownloading:
713 case BitTorrent::TorrentState::DownloadingMetadata:
714 if (!dark)
715 return {34, 139, 34}; // Forest Green
716 else
717 return {50, 205, 50}; // Lime Green
718 case BitTorrent::TorrentState::StalledDownloading:
719 case BitTorrent::TorrentState::StalledUploading:
720 if (!dark)
721 return {0, 0, 0}; // Black
722 else
723 return {204, 204, 204}; // Gray 80
724 case BitTorrent::TorrentState::Uploading:
725 case BitTorrent::TorrentState::ForcedUploading:
726 if (!dark)
727 return {65, 105, 225}; // Royal Blue
728 else
729 return {99, 184, 255}; // Steel Blue 1
730 case BitTorrent::TorrentState::PausedDownloading:
731 return {250, 128, 114}; // Salmon
732 case BitTorrent::TorrentState::PausedUploading:
733 if (!dark)
734 return {0, 0, 139}; // Dark Blue
735 else
736 return {79, 148, 205}; // Steel Blue 3
737 case BitTorrent::TorrentState::Error:
738 case BitTorrent::TorrentState::MissingFiles:
739 return {255, 0, 0}; // red
740 case BitTorrent::TorrentState::QueuedDownloading:
741 case BitTorrent::TorrentState::QueuedUploading:
742 case BitTorrent::TorrentState::CheckingDownloading:
743 case BitTorrent::TorrentState::CheckingUploading:
744 case BitTorrent::TorrentState::CheckingResumeData:
745 case BitTorrent::TorrentState::Moving:
746 if (!dark)
747 return {0, 128, 128}; // Teal
748 else
749 return {0, 205, 205}; // Cyan 3
750 case BitTorrent::TorrentState::Unknown:
751 return {255, 0, 0}; // red
752 default:
753 Q_ASSERT(false);
754 return {255, 0, 0}; // red
755 }
756 }
757
getPausedIcon()758 QIcon getPausedIcon()
759 {
760 static QIcon cached = UIThemeManager::instance()->getIcon(QLatin1String("paused"));
761 return cached;
762 }
763
getQueuedIcon()764 QIcon getQueuedIcon()
765 {
766 static QIcon cached = UIThemeManager::instance()->getIcon(QLatin1String("queued"));
767 return cached;
768 }
769
getDownloadingIcon()770 QIcon getDownloadingIcon()
771 {
772 static QIcon cached = UIThemeManager::instance()->getIcon(QLatin1String("downloading"));
773 return cached;
774 }
775
getStalledDownloadingIcon()776 QIcon getStalledDownloadingIcon()
777 {
778 static QIcon cached = UIThemeManager::instance()->getIcon(QLatin1String("stalledDL"));
779 return cached;
780 }
781
getUploadingIcon()782 QIcon getUploadingIcon()
783 {
784 static QIcon cached = UIThemeManager::instance()->getIcon(QLatin1String("uploading"));
785 return cached;
786 }
787
getStalledUploadingIcon()788 QIcon getStalledUploadingIcon()
789 {
790 static QIcon cached = UIThemeManager::instance()->getIcon(QLatin1String("stalledUP"));
791 return cached;
792 }
793
getCompletedIcon()794 QIcon getCompletedIcon()
795 {
796 static QIcon cached = UIThemeManager::instance()->getIcon(QLatin1String("completed"));
797 return cached;
798 }
799
getCheckingIcon()800 QIcon getCheckingIcon()
801 {
802 static QIcon cached = UIThemeManager::instance()->getIcon(QLatin1String("checking"));
803 return cached;
804 }
805
getErrorIcon()806 QIcon getErrorIcon()
807 {
808 static QIcon cached = UIThemeManager::instance()->getIcon(QLatin1String("error"));
809 return cached;
810 }
811
isDarkTheme()812 bool isDarkTheme()
813 {
814 const QPalette pal = QApplication::palette();
815 // QPalette::Base is used for the background of the Treeview
816 const QColor &color = pal.color(QPalette::Active, QPalette::Base);
817 return (color.lightness() < 127);
818 }
819