Enable customizing the save statistics time interval
[qBittorrent.git] / src / gui / transferlistmodel.cpp
blob8263a3b6d90c0a1139c5546ae5402c378d563fd1
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
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.
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.
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.
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.
30 #include "transferlistmodel.h"
32 #include <QApplication>
33 #include <QDateTime>
34 #include <QDebug>
36 #include "base/bittorrent/infohash.h"
37 #include "base/bittorrent/session.h"
38 #include "base/bittorrent/torrent.h"
39 #include "base/global.h"
40 #include "base/preferences.h"
41 #include "base/types.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"
48 namespace
50 QHash<BitTorrent::TorrentState, QColor> torrentStateColorsFromUITheme()
52 struct TorrentStateColorDescriptor
54 const BitTorrent::TorrentState state;
55 const QString id;
58 const TorrentStateColorDescriptor colorDescriptors[] =
60 {BitTorrent::TorrentState::Downloading, u"TransferList.Downloading"_s},
61 {BitTorrent::TorrentState::StalledDownloading, u"TransferList.StalledDownloading"_s},
62 {BitTorrent::TorrentState::DownloadingMetadata, u"TransferList.DownloadingMetadata"_s},
63 {BitTorrent::TorrentState::ForcedDownloadingMetadata, u"TransferList.ForcedDownloadingMetadata"_s},
64 {BitTorrent::TorrentState::ForcedDownloading, u"TransferList.ForcedDownloading"_s},
65 {BitTorrent::TorrentState::Uploading, u"TransferList.Uploading"_s},
66 {BitTorrent::TorrentState::StalledUploading, u"TransferList.StalledUploading"_s},
67 {BitTorrent::TorrentState::ForcedUploading, u"TransferList.ForcedUploading"_s},
68 {BitTorrent::TorrentState::QueuedDownloading, u"TransferList.QueuedDownloading"_s},
69 {BitTorrent::TorrentState::QueuedUploading, u"TransferList.QueuedUploading"_s},
70 {BitTorrent::TorrentState::CheckingDownloading, u"TransferList.CheckingDownloading"_s},
71 {BitTorrent::TorrentState::CheckingUploading, u"TransferList.CheckingUploading"_s},
72 {BitTorrent::TorrentState::CheckingResumeData, u"TransferList.CheckingResumeData"_s},
73 {BitTorrent::TorrentState::StoppedDownloading, u"TransferList.StoppedDownloading"_s},
74 {BitTorrent::TorrentState::StoppedUploading, u"TransferList.StoppedUploading"_s},
75 {BitTorrent::TorrentState::Moving, u"TransferList.Moving"_s},
76 {BitTorrent::TorrentState::MissingFiles, u"TransferList.MissingFiles"_s},
77 {BitTorrent::TorrentState::Error, u"TransferList.Error"_s}
80 QHash<BitTorrent::TorrentState, QColor> colors;
81 for (const TorrentStateColorDescriptor &colorDescriptor : colorDescriptors)
83 const QColor themeColor = UIThemeManager::instance()->getColor(colorDescriptor.id);
84 colors.insert(colorDescriptor.state, themeColor);
86 return colors;
90 // TransferListModel
92 TransferListModel::TransferListModel(QObject *parent)
93 : QAbstractListModel {parent}
94 , m_statusStrings
96 {BitTorrent::TorrentState::Downloading, tr("Downloading")},
97 {BitTorrent::TorrentState::StalledDownloading, tr("Stalled", "Torrent is waiting for download to begin")},
98 {BitTorrent::TorrentState::DownloadingMetadata, tr("Downloading metadata", "Used when loading a magnet link")},
99 {BitTorrent::TorrentState::ForcedDownloadingMetadata, tr("[F] Downloading metadata", "Used when forced to load a magnet link. You probably shouldn't translate the F.")},
100 {BitTorrent::TorrentState::ForcedDownloading, tr("[F] Downloading", "Used when the torrent is forced started. You probably shouldn't translate the F.")},
101 {BitTorrent::TorrentState::Uploading, tr("Seeding", "Torrent is complete and in upload-only mode")},
102 {BitTorrent::TorrentState::StalledUploading, tr("Seeding", "Torrent is complete and in upload-only mode")},
103 {BitTorrent::TorrentState::ForcedUploading, tr("[F] Seeding", "Used when the torrent is forced started. You probably shouldn't translate the F.")},
104 {BitTorrent::TorrentState::QueuedDownloading, tr("Queued", "Torrent is queued")},
105 {BitTorrent::TorrentState::QueuedUploading, tr("Queued", "Torrent is queued")},
106 {BitTorrent::TorrentState::CheckingDownloading, tr("Checking", "Torrent local data is being checked")},
107 {BitTorrent::TorrentState::CheckingUploading, tr("Checking", "Torrent local data is being checked")},
108 {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.")},
109 {BitTorrent::TorrentState::StoppedDownloading, tr("Stopped")},
110 {BitTorrent::TorrentState::StoppedUploading, tr("Completed")},
111 {BitTorrent::TorrentState::Moving, tr("Moving", "Torrent local data are being moved/relocated")},
112 {BitTorrent::TorrentState::MissingFiles, tr("Missing Files")},
113 {BitTorrent::TorrentState::Error, tr("Errored", "Torrent status, the torrent has an error")}
116 configure();
117 connect(Preferences::instance(), &Preferences::changed, this, &TransferListModel::configure);
119 loadUIThemeResources();
120 connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, [this]
122 loadUIThemeResources();
123 emit dataChanged(index(0, 0), index((rowCount() - 1), (columnCount() - 1)), {Qt::DecorationRole, Qt::ForegroundRole});
126 // Load the torrents
127 using namespace BitTorrent;
128 addTorrents(Session::instance()->torrents());
130 // Listen for torrent changes
131 connect(Session::instance(), &Session::torrentsLoaded, this, &TransferListModel::addTorrents);
132 connect(Session::instance(), &Session::torrentAboutToBeRemoved, this, &TransferListModel::handleTorrentAboutToBeRemoved);
133 connect(Session::instance(), &Session::torrentsUpdated, this, &TransferListModel::handleTorrentsUpdated);
135 connect(Session::instance(), &Session::torrentFinished, this, &TransferListModel::handleTorrentStatusUpdated);
136 connect(Session::instance(), &Session::torrentMetadataReceived, this, &TransferListModel::handleTorrentStatusUpdated);
137 connect(Session::instance(), &Session::torrentStarted, this, &TransferListModel::handleTorrentStatusUpdated);
138 connect(Session::instance(), &Session::torrentStopped, this, &TransferListModel::handleTorrentStatusUpdated);
139 connect(Session::instance(), &Session::torrentFinishedChecking, this, &TransferListModel::handleTorrentStatusUpdated);
142 int TransferListModel::rowCount(const QModelIndex &) const
144 return m_torrentList.size();
147 int TransferListModel::columnCount(const QModelIndex &) const
149 return NB_COLUMNS;
152 QVariant TransferListModel::headerData(const int section, const Qt::Orientation orientation, const int role) const
154 if (orientation == Qt::Horizontal)
156 if (role == Qt::DisplayRole)
158 switch (section)
160 case TR_QUEUE_POSITION: return QChar(u'#');
161 case TR_NAME: return tr("Name", "i.e: torrent name");
162 case TR_SIZE: return tr("Size", "i.e: torrent size");
163 case TR_PROGRESS: return tr("Progress", "% Done");
164 case TR_STATUS: return tr("Status", "Torrent status (e.g. downloading, seeding, stopped)");
165 case TR_SEEDS: return tr("Seeds", "i.e. full sources (often untranslated)");
166 case TR_PEERS: return tr("Peers", "i.e. partial sources (often untranslated)");
167 case TR_DLSPEED: return tr("Down Speed", "i.e: Download speed");
168 case TR_UPSPEED: return tr("Up Speed", "i.e: Upload speed");
169 case TR_RATIO: return tr("Ratio", "Share ratio");
170 case TR_POPULARITY: return tr("Popularity");
171 case TR_ETA: return tr("ETA", "i.e: Estimated Time of Arrival / Time left");
172 case TR_CATEGORY: return tr("Category");
173 case TR_TAGS: return tr("Tags");
174 case TR_ADD_DATE: return tr("Added On", "Torrent was added to transfer list on 01/01/2010 08:00");
175 case TR_SEED_DATE: return tr("Completed On", "Torrent was completed on 01/01/2010 08:00");
176 case TR_TRACKER: return tr("Tracker");
177 case TR_DLLIMIT: return tr("Down Limit", "i.e: Download limit");
178 case TR_UPLIMIT: return tr("Up Limit", "i.e: Upload limit");
179 case TR_AMOUNT_DOWNLOADED: return tr("Downloaded", "Amount of data downloaded (e.g. in MB)");
180 case TR_AMOUNT_UPLOADED: return tr("Uploaded", "Amount of data uploaded (e.g. in MB)");
181 case TR_AMOUNT_DOWNLOADED_SESSION: return tr("Session Download", "Amount of data downloaded since program open (e.g. in MB)");
182 case TR_AMOUNT_UPLOADED_SESSION: return tr("Session Upload", "Amount of data uploaded since program open (e.g. in MB)");
183 case TR_AMOUNT_LEFT: return tr("Remaining", "Amount of data left to download (e.g. in MB)");
184 case TR_TIME_ELAPSED: return tr("Time Active", "Time (duration) the torrent is active (not stopped)");
185 case TR_SAVE_PATH: return tr("Save Path", "Torrent save path");
186 case TR_DOWNLOAD_PATH: return tr("Incomplete Save Path", "Torrent incomplete save path");
187 case TR_COMPLETED: return tr("Completed", "Amount of data completed (e.g. in MB)");
188 case TR_RATIO_LIMIT: return tr("Ratio Limit", "Upload share ratio limit");
189 case TR_SEEN_COMPLETE_DATE: return tr("Last Seen Complete", "Indicates the time when the torrent was last seen complete/whole");
190 case TR_LAST_ACTIVITY: return tr("Last Activity", "Time passed since a chunk was downloaded/uploaded");
191 case TR_TOTAL_SIZE: return tr("Total Size", "i.e. Size including unwanted data");
192 case TR_AVAILABILITY: return tr("Availability", "The number of distributed copies of the torrent");
193 case TR_INFOHASH_V1: return tr("Info Hash v1", "i.e: torrent info hash v1");
194 case TR_INFOHASH_V2: return tr("Info Hash v2", "i.e: torrent info hash v2");
195 case TR_REANNOUNCE: return tr("Reannounce In", "Indicates the time until next trackers reannounce");
196 case TR_PRIVATE: return tr("Private", "Flags private torrents");
197 default: return {};
200 else if (role == Qt::ToolTipRole)
202 switch (section)
204 case TR_POPULARITY: return tr("Ratio / Time Active (in months), indicates how popular the torrent is");
205 default: return {};
208 else if (role == Qt::TextAlignmentRole)
210 switch (section)
212 case TR_AMOUNT_DOWNLOADED:
213 case TR_AMOUNT_UPLOADED:
214 case TR_AMOUNT_DOWNLOADED_SESSION:
215 case TR_AMOUNT_UPLOADED_SESSION:
216 case TR_AMOUNT_LEFT:
217 case TR_COMPLETED:
218 case TR_SIZE:
219 case TR_TOTAL_SIZE:
220 case TR_ETA:
221 case TR_SEEDS:
222 case TR_PEERS:
223 case TR_UPSPEED:
224 case TR_DLSPEED:
225 case TR_UPLIMIT:
226 case TR_DLLIMIT:
227 case TR_RATIO_LIMIT:
228 case TR_RATIO:
229 case TR_POPULARITY:
230 case TR_QUEUE_POSITION:
231 case TR_LAST_ACTIVITY:
232 case TR_AVAILABILITY:
233 case TR_REANNOUNCE:
234 return QVariant(Qt::AlignRight | Qt::AlignVCenter);
235 default:
236 return QAbstractListModel::headerData(section, orientation, role);
241 return QAbstractListModel::headerData(section, orientation, role);
244 QString TransferListModel::displayValue(const BitTorrent::Torrent *torrent, const int column) const
246 bool hideValues = false;
247 if (m_hideZeroValuesMode == HideZeroValuesMode::Always)
248 hideValues = true;
249 else if (m_hideZeroValuesMode == HideZeroValuesMode::Stopped)
250 hideValues = (torrent->state() == BitTorrent::TorrentState::StoppedDownloading);
252 const auto availabilityString = [hideValues](const qreal value) -> QString
254 if (hideValues && (value == 0))
255 return {};
256 return (value >= 0)
257 ? Utils::String::fromDouble(value, 3)
258 : tr("N/A");
261 const auto unitString = [hideValues](const qint64 value, const bool isSpeedUnit = false) -> QString
263 return (hideValues && (value == 0))
264 ? QString {} : Utils::Misc::friendlyUnit(value, isSpeedUnit);
267 const auto limitString = [hideValues](const qint64 value) -> QString
269 if (hideValues && (value <= 0))
270 return {};
272 return (value > 0)
273 ? Utils::Misc::friendlyUnit(value, true)
274 : C_INFINITY;
277 const auto amountString = [hideValues](const qint64 value, const qint64 total) -> QString
279 if (hideValues && (value == 0) && (total == 0))
280 return {};
281 return u"%1 (%2)"_s.arg(QString::number(value), QString::number(total));
284 const auto etaString = [hideValues](const qlonglong value) -> QString
286 if (hideValues && (value >= MAX_ETA))
287 return {};
288 return Utils::Misc::userFriendlyDuration(value, MAX_ETA);
291 const auto ratioString = [hideValues](const qreal value) -> QString
293 if (hideValues && (value <= 0))
294 return {};
296 return ((static_cast<int>(value) == -1) || (value > BitTorrent::Torrent::MAX_RATIO))
297 ? C_INFINITY : Utils::String::fromDouble(value, 2);
300 const auto queuePositionString = [](const qint64 value) -> QString
302 return (value >= 0) ? QString::number(value + 1) : u"*"_s;
305 const auto lastActivityString = [hideValues](qint64 value) -> QString
307 if (hideValues && ((value < 0) || (value >= MAX_ETA)))
308 return {};
310 // Show '< 1m ago' when elapsed time is 0
311 if (value == 0)
312 value = 1;
314 return (value >= 0)
315 ? tr("%1 ago", "e.g.: 1h 20m ago").arg(Utils::Misc::userFriendlyDuration(value))
316 : Utils::Misc::userFriendlyDuration(value);
319 const auto timeElapsedString = [hideValues](const qint64 elapsedTime, const qint64 seedingTime) -> QString
321 if (seedingTime <= 0)
323 if (hideValues && (elapsedTime == 0))
324 return {};
325 return Utils::Misc::userFriendlyDuration(elapsedTime);
328 return tr("%1 (seeded for %2)", "e.g. 4m39s (seeded for 3m10s)")
329 .arg(Utils::Misc::userFriendlyDuration(elapsedTime)
330 , Utils::Misc::userFriendlyDuration(seedingTime));
333 const auto progressString = [](const qreal progress) -> QString
335 return (progress >= 1)
336 ? u"100%"_s
337 : (Utils::String::fromDouble((progress * 100), 1) + u'%');
340 const auto statusString = [this](const BitTorrent::TorrentState state, const QString &errorMessage) -> QString
342 return (state == BitTorrent::TorrentState::Error)
343 ? m_statusStrings[state] + u": " + errorMessage
344 : m_statusStrings[state];
347 const auto hashString = [hideValues](const auto &hash) -> QString
349 if (hideValues && !hash.isValid())
350 return {};
351 return hash.isValid() ? hash.toString() : tr("N/A");
354 const auto reannounceString = [hideValues](const qint64 time) -> QString
356 if (hideValues && (time == 0))
357 return {};
358 return Utils::Misc::userFriendlyDuration(time);
361 const auto privateString = [hideValues](const bool isPrivate, const bool hasMetadata) -> QString
363 if (hideValues && !isPrivate)
364 return {};
365 if (hasMetadata)
366 return isPrivate ? tr("Yes") : tr("No");
367 return tr("N/A");
370 switch (column)
372 case TR_NAME:
373 return torrent->name();
374 case TR_QUEUE_POSITION:
375 return queuePositionString(torrent->queuePosition());
376 case TR_SIZE:
377 return unitString(torrent->wantedSize());
378 case TR_PROGRESS:
379 return progressString(torrent->progress());
380 case TR_STATUS:
381 return statusString(torrent->state(), torrent->error());
382 case TR_SEEDS:
383 return amountString(torrent->seedsCount(), torrent->totalSeedsCount());
384 case TR_PEERS:
385 return amountString(torrent->leechsCount(), torrent->totalLeechersCount());
386 case TR_DLSPEED:
387 return unitString(torrent->downloadPayloadRate(), true);
388 case TR_UPSPEED:
389 return unitString(torrent->uploadPayloadRate(), true);
390 case TR_ETA:
391 return etaString(torrent->eta());
392 case TR_RATIO:
393 return ratioString(torrent->realRatio());
394 case TR_RATIO_LIMIT:
395 return ratioString(torrent->maxRatio());
396 case TR_POPULARITY:
397 return ratioString(torrent->popularity());
398 case TR_CATEGORY:
399 return torrent->category();
400 case TR_TAGS:
401 return Utils::String::joinIntoString(torrent->tags(), u", "_s);
402 case TR_ADD_DATE:
403 return QLocale().toString(torrent->addedTime().toLocalTime(), QLocale::ShortFormat);
404 case TR_SEED_DATE:
405 return QLocale().toString(torrent->completedTime().toLocalTime(), QLocale::ShortFormat);
406 case TR_TRACKER:
407 return torrent->currentTracker();
408 case TR_DLLIMIT:
409 return limitString(torrent->downloadLimit());
410 case TR_UPLIMIT:
411 return limitString(torrent->uploadLimit());
412 case TR_AMOUNT_DOWNLOADED:
413 return unitString(torrent->totalDownload());
414 case TR_AMOUNT_UPLOADED:
415 return unitString(torrent->totalUpload());
416 case TR_AMOUNT_DOWNLOADED_SESSION:
417 return unitString(torrent->totalPayloadDownload());
418 case TR_AMOUNT_UPLOADED_SESSION:
419 return unitString(torrent->totalPayloadUpload());
420 case TR_AMOUNT_LEFT:
421 return unitString(torrent->remainingSize());
422 case TR_TIME_ELAPSED:
423 return timeElapsedString(torrent->activeTime(), torrent->finishedTime());
424 case TR_SAVE_PATH:
425 return torrent->savePath().toString();
426 case TR_DOWNLOAD_PATH:
427 return torrent->downloadPath().toString();
428 case TR_COMPLETED:
429 return unitString(torrent->completedSize());
430 case TR_SEEN_COMPLETE_DATE:
431 return QLocale().toString(torrent->lastSeenComplete().toLocalTime(), QLocale::ShortFormat);
432 case TR_LAST_ACTIVITY:
433 return lastActivityString(torrent->timeSinceActivity());
434 case TR_AVAILABILITY:
435 return availabilityString(torrent->distributedCopies());
436 case TR_TOTAL_SIZE:
437 return unitString(torrent->totalSize());
438 case TR_INFOHASH_V1:
439 return hashString(torrent->infoHash().v1());
440 case TR_INFOHASH_V2:
441 return hashString(torrent->infoHash().v2());
442 case TR_REANNOUNCE:
443 return reannounceString(torrent->nextAnnounce());
444 case TR_PRIVATE:
445 return privateString(torrent->isPrivate(), torrent->hasMetadata());
448 return {};
451 QVariant TransferListModel::internalValue(const BitTorrent::Torrent *torrent, const int column, const bool alt) const
453 switch (column)
455 case TR_NAME:
456 return torrent->name();
457 case TR_QUEUE_POSITION:
458 return torrent->queuePosition();
459 case TR_SIZE:
460 return torrent->wantedSize();
461 case TR_PROGRESS:
462 return torrent->progress() * 100;
463 case TR_STATUS:
464 return QVariant::fromValue(torrent->state());
465 case TR_SEEDS:
466 return !alt ? torrent->seedsCount() : torrent->totalSeedsCount();
467 case TR_PEERS:
468 return !alt ? torrent->leechsCount() : torrent->totalLeechersCount();
469 case TR_DLSPEED:
470 return torrent->downloadPayloadRate();
471 case TR_UPSPEED:
472 return torrent->uploadPayloadRate();
473 case TR_ETA:
474 return torrent->eta();
475 case TR_RATIO:
476 return torrent->realRatio();
477 case TR_POPULARITY:
478 return torrent->popularity();
479 case TR_CATEGORY:
480 return torrent->category();
481 case TR_TAGS:
482 return QVariant::fromValue(torrent->tags());
483 case TR_ADD_DATE:
484 return torrent->addedTime();
485 case TR_SEED_DATE:
486 return torrent->completedTime();
487 case TR_TRACKER:
488 return torrent->currentTracker();
489 case TR_DLLIMIT:
490 return torrent->downloadLimit();
491 case TR_UPLIMIT:
492 return torrent->uploadLimit();
493 case TR_AMOUNT_DOWNLOADED:
494 return torrent->totalDownload();
495 case TR_AMOUNT_UPLOADED:
496 return torrent->totalUpload();
497 case TR_AMOUNT_DOWNLOADED_SESSION:
498 return torrent->totalPayloadDownload();
499 case TR_AMOUNT_UPLOADED_SESSION:
500 return torrent->totalPayloadUpload();
501 case TR_AMOUNT_LEFT:
502 return torrent->remainingSize();
503 case TR_TIME_ELAPSED:
504 return !alt ? torrent->activeTime() : torrent->finishedTime();
505 case TR_DOWNLOAD_PATH:
506 return torrent->downloadPath().data();
507 case TR_SAVE_PATH:
508 return torrent->savePath().data();
509 case TR_COMPLETED:
510 return torrent->completedSize();
511 case TR_RATIO_LIMIT:
512 return torrent->maxRatio();
513 case TR_SEEN_COMPLETE_DATE:
514 return torrent->lastSeenComplete();
515 case TR_LAST_ACTIVITY:
516 return torrent->timeSinceActivity();
517 case TR_AVAILABILITY:
518 return torrent->distributedCopies();
519 case TR_TOTAL_SIZE:
520 return torrent->totalSize();
521 case TR_INFOHASH_V1:
522 return QVariant::fromValue(torrent->infoHash().v1());
523 case TR_INFOHASH_V2:
524 return QVariant::fromValue(torrent->infoHash().v2());
525 case TR_REANNOUNCE:
526 return torrent->nextAnnounce();
527 case TR_PRIVATE:
528 return (torrent->hasMetadata() ? torrent->isPrivate() : QVariant());
531 return {};
534 QVariant TransferListModel::data(const QModelIndex &index, const int role) const
536 if (!index.isValid()) return {};
538 const BitTorrent::Torrent *torrent = m_torrentList.value(index.row());
539 if (!torrent) return {};
541 switch (role)
543 case Qt::ForegroundRole:
544 return m_stateThemeColors.value(torrent->state());
545 case Qt::DisplayRole:
546 return displayValue(torrent, index.column());
547 case UnderlyingDataRole:
548 return internalValue(torrent, index.column(), false);
549 case AdditionalUnderlyingDataRole:
550 return internalValue(torrent, index.column(), true);
551 case Qt::DecorationRole:
552 if (index.column() == TR_NAME)
553 return getIconByState(torrent->state());
554 break;
555 case Qt::ToolTipRole:
556 switch (index.column())
558 case TR_NAME:
559 case TR_STATUS:
560 case TR_CATEGORY:
561 case TR_TAGS:
562 case TR_TRACKER:
563 case TR_SAVE_PATH:
564 case TR_DOWNLOAD_PATH:
565 case TR_INFOHASH_V1:
566 case TR_INFOHASH_V2:
567 return displayValue(torrent, index.column());
569 break;
570 case Qt::TextAlignmentRole:
571 switch (index.column())
573 case TR_AMOUNT_DOWNLOADED:
574 case TR_AMOUNT_UPLOADED:
575 case TR_AMOUNT_DOWNLOADED_SESSION:
576 case TR_AMOUNT_UPLOADED_SESSION:
577 case TR_AMOUNT_LEFT:
578 case TR_COMPLETED:
579 case TR_SIZE:
580 case TR_TOTAL_SIZE:
581 case TR_ETA:
582 case TR_SEEDS:
583 case TR_PEERS:
584 case TR_UPSPEED:
585 case TR_DLSPEED:
586 case TR_UPLIMIT:
587 case TR_DLLIMIT:
588 case TR_RATIO_LIMIT:
589 case TR_RATIO:
590 case TR_POPULARITY:
591 case TR_QUEUE_POSITION:
592 case TR_LAST_ACTIVITY:
593 case TR_AVAILABILITY:
594 case TR_REANNOUNCE:
595 return QVariant(Qt::AlignRight | Qt::AlignVCenter);
597 break;
598 default:
599 break;
602 return {};
605 bool TransferListModel::setData(const QModelIndex &index, const QVariant &value, int role)
607 if (!index.isValid() || (role != Qt::DisplayRole)) return false;
609 BitTorrent::Torrent *const torrent = m_torrentList.value(index.row());
610 if (!torrent) return false;
612 // Category and Name columns can be edited
613 switch (index.column())
615 case TR_NAME:
616 torrent->setName(value.toString());
617 break;
618 case TR_CATEGORY:
619 torrent->setCategory(value.toString());
620 break;
621 default:
622 return false;
625 return true;
628 void TransferListModel::addTorrents(const QList<BitTorrent::Torrent *> &torrents)
630 qsizetype row = m_torrentList.size();
631 const qsizetype total = row + torrents.size();
633 beginInsertRows({}, row, total);
635 m_torrentList.reserve(total);
636 for (BitTorrent::Torrent *torrent : torrents)
638 Q_ASSERT(!m_torrentMap.contains(torrent));
640 m_torrentList.append(torrent);
641 m_torrentMap[torrent] = row++;
644 endInsertRows();
647 Qt::ItemFlags TransferListModel::flags(const QModelIndex &index) const
649 if (!index.isValid()) return Qt::NoItemFlags;
651 // Explicitly mark as editable
652 return QAbstractListModel::flags(index) | Qt::ItemIsEditable;
655 BitTorrent::Torrent *TransferListModel::torrentHandle(const QModelIndex &index) const
657 if (!index.isValid()) return nullptr;
659 return m_torrentList.value(index.row());
662 void TransferListModel::handleTorrentAboutToBeRemoved(BitTorrent::Torrent *const torrent)
664 const int row = m_torrentMap.value(torrent, -1);
665 Q_ASSERT(row >= 0);
667 beginRemoveRows({}, row, row);
668 m_torrentList.removeAt(row);
669 m_torrentMap.remove(torrent);
670 for (int &value : m_torrentMap)
672 if (value > row)
673 --value;
675 endRemoveRows();
678 void TransferListModel::handleTorrentStatusUpdated(BitTorrent::Torrent *const torrent)
680 const int row = m_torrentMap.value(torrent, -1);
681 Q_ASSERT(row >= 0);
683 emit dataChanged(index(row, 0), index(row, columnCount() - 1));
686 void TransferListModel::handleTorrentsUpdated(const QList<BitTorrent::Torrent *> &torrents)
688 const int columns = (columnCount() - 1);
690 if (torrents.size() <= (m_torrentList.size() * 0.5))
692 for (BitTorrent::Torrent *const torrent : torrents)
694 const int row = m_torrentMap.value(torrent, -1);
695 Q_ASSERT(row >= 0);
697 emit dataChanged(index(row, 0), index(row, columns));
700 else
702 // save the overhead when more than half of the torrent list needs update
703 emit dataChanged(index(0, 0), index((rowCount() - 1), columns));
707 void TransferListModel::configure()
709 const Preferences *pref = Preferences::instance();
711 HideZeroValuesMode hideZeroValuesMode = HideZeroValuesMode::Never;
712 if (pref->getHideZeroValues())
714 if (pref->getHideZeroComboValues() == 1)
715 hideZeroValuesMode = HideZeroValuesMode::Stopped;
716 else
717 hideZeroValuesMode = HideZeroValuesMode::Always;
720 if (m_hideZeroValuesMode != hideZeroValuesMode)
722 m_hideZeroValuesMode = hideZeroValuesMode;
723 emit dataChanged(index(0, 0), index((rowCount() - 1), (columnCount() - 1)));
727 void TransferListModel::loadUIThemeResources()
729 m_stateThemeColors = torrentStateColorsFromUITheme();
731 const auto *themeManager = UIThemeManager::instance();
732 m_checkingIcon = themeManager->getIcon(u"force-recheck"_s, u"checking"_s);
733 m_completedIcon = themeManager->getIcon(u"checked-completed"_s, u"completed"_s);
734 m_downloadingIcon = themeManager->getIcon(u"downloading"_s);
735 m_errorIcon = themeManager->getIcon(u"error"_s);
736 m_movingIcon = themeManager->getIcon(u"set-location"_s);
737 m_stoppedIcon = themeManager->getIcon(u"stopped"_s, u"media-playback-pause"_s);
738 m_queuedIcon = themeManager->getIcon(u"queued"_s);
739 m_stalledDLIcon = themeManager->getIcon(u"stalledDL"_s);
740 m_stalledUPIcon = themeManager->getIcon(u"stalledUP"_s);
741 m_uploadingIcon = themeManager->getIcon(u"upload"_s, u"uploading"_s);
744 QIcon TransferListModel::getIconByState(const BitTorrent::TorrentState state) const
746 switch (state)
748 case BitTorrent::TorrentState::Downloading:
749 case BitTorrent::TorrentState::ForcedDownloading:
750 case BitTorrent::TorrentState::DownloadingMetadata:
751 case BitTorrent::TorrentState::ForcedDownloadingMetadata:
752 return m_downloadingIcon;
753 case BitTorrent::TorrentState::StalledDownloading:
754 return m_stalledDLIcon;
755 case BitTorrent::TorrentState::StalledUploading:
756 return m_stalledUPIcon;
757 case BitTorrent::TorrentState::Uploading:
758 case BitTorrent::TorrentState::ForcedUploading:
759 return m_uploadingIcon;
760 case BitTorrent::TorrentState::StoppedDownloading:
761 return m_stoppedIcon;
762 case BitTorrent::TorrentState::StoppedUploading:
763 return m_completedIcon;
764 case BitTorrent::TorrentState::QueuedDownloading:
765 case BitTorrent::TorrentState::QueuedUploading:
766 return m_queuedIcon;
767 case BitTorrent::TorrentState::CheckingDownloading:
768 case BitTorrent::TorrentState::CheckingUploading:
769 case BitTorrent::TorrentState::CheckingResumeData:
770 return m_checkingIcon;
771 case BitTorrent::TorrentState::Moving:
772 return m_movingIcon;
773 case BitTorrent::TorrentState::Unknown:
774 case BitTorrent::TorrentState::MissingFiles:
775 case BitTorrent::TorrentState::Error:
776 return m_errorIcon;
777 default:
778 Q_ASSERT(false);
779 return m_errorIcon;