From 911f0d403920e0cc5da10fee80330ce2092b669f Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Thu, 22 Dec 2022 08:19:33 +0300 Subject: [PATCH 01/14] Correctly count the number of torrents in subcategories PR #18261. Closes #18137. --- src/gui/categoryfiltermodel.cpp | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/gui/categoryfiltermodel.cpp b/src/gui/categoryfiltermodel.cpp index c45351952..f30ceaaae 100644 --- a/src/gui/categoryfiltermodel.cpp +++ b/src/gui/categoryfiltermodel.cpp @@ -56,7 +56,7 @@ public: clear(); if (m_parent) { - m_parent->m_torrentsCount -= m_torrentsCount; + m_parent->decreaseTorrentsCount(m_torrentsCount); const QString uid = m_parent->m_children.key(this); m_parent->m_children.remove(uid); m_parent->m_childUids.removeOne(uid); @@ -86,18 +86,18 @@ public: return m_torrentsCount; } - void increaseTorrentsCount() + void increaseTorrentsCount(const int delta = 1) { - ++m_torrentsCount; + m_torrentsCount += delta; if (m_parent) - m_parent->increaseTorrentsCount(); + m_parent->increaseTorrentsCount(delta); } - void decreaseTorrentsCount() + void decreaseTorrentsCount(const int delta = 1) { - --m_torrentsCount; + m_torrentsCount -= delta; if (m_parent) - m_parent->decreaseTorrentsCount(); + m_parent->decreaseTorrentsCount(delta); } int pos() const @@ -139,7 +139,7 @@ public: item->m_parent = this; m_children[uid] = item; m_childUids.append(uid); - m_torrentsCount += item->torrentsCount(); + increaseTorrentsCount(item->torrentsCount()); } void clear() @@ -408,9 +408,9 @@ void CategoryFilterModel::populate() m_rootItem->addChild(UID_UNCATEGORIZED, new CategoryModelItem(nullptr, tr("Uncategorized"), torrentsCount)); using BitTorrent::Torrent; - for (const QString &categoryName : asConst(session->categories())) + if (m_isSubcategoriesEnabled) { - if (m_isSubcategoriesEnabled) + for (const QString &categoryName : asConst(session->categories())) { CategoryModelItem *parent = m_rootItem; for (const QString &subcat : asConst(session->expandCategory(categoryName))) @@ -419,16 +419,19 @@ void CategoryFilterModel::populate() if (!parent->hasChild(subcatName)) { const int torrentsCount = std::count_if(torrents.cbegin(), torrents.cend() - , [subcat](Torrent *torrent) { return torrent->category() == subcat; }); + , [subcat](Torrent *torrent) { return torrent->category() == subcat; }); new CategoryModelItem(parent, subcatName, torrentsCount); } parent = parent->child(subcatName); } } - else + } + else + { + for (const QString &categoryName : asConst(session->categories())) { const int torrentsCount = std::count_if(torrents.begin(), torrents.end() - , [categoryName](Torrent *torrent) { return torrent->belongsToCategory(categoryName); }); + , [categoryName](Torrent *torrent) { return torrent->belongsToCategory(categoryName); }); new CategoryModelItem(m_rootItem, categoryName, torrentsCount); } } From c57896df8fe8b4cb40970addbd2445491872f14b Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Thu, 22 Dec 2022 08:21:29 +0300 Subject: [PATCH 02/14] Use "additional trackers" when metadata retrieving This can help when the DHT nodes are few. PR #18251. Closes #18244. --- src/base/bittorrent/sessionimpl.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index 89ce7a9bd..96def934e 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -2811,6 +2811,19 @@ bool SessionImpl::downloadMetadata(const MagnetUri &magnetUri) lt::add_torrent_params p = magnetUri.addTorrentParams(); + if (isAddTrackersEnabled()) + { + // Use "additional trackers" when metadata retrieving (this can help when the DHT nodes are few) + p.trackers.reserve(p.trackers.size() + static_cast(m_additionalTrackerList.size())); + p.tracker_tiers.reserve(p.trackers.size() + static_cast(m_additionalTrackerList.size())); + p.tracker_tiers.resize(p.trackers.size(), 0); + for (const TrackerEntry &trackerEntry : asConst(m_additionalTrackerList)) + { + p.trackers.push_back(trackerEntry.url.toStdString()); + p.tracker_tiers.push_back(trackerEntry.tier); + } + } + // Flags // Preallocation mode if (isPreallocationEnabled()) From 967c3bb55d8450e44bc10c5f494030da3e271cd0 Mon Sep 17 00:00:00 2001 From: "Nowshed H. Imran" Date: Thu, 22 Dec 2022 17:14:29 +0600 Subject: [PATCH 03/14] Fix icon colors inconsistencies PR #18226. Fixes #18163. Fixes #18222. --- src/icons/browser-cookies.svg | 2 +- src/icons/filter-stalled.svg | 2 +- src/icons/view-refresh.svg | 2 +- src/webui/www/private/images/filter-stalled.svg | 2 +- src/webui/www/private/images/view-refresh.svg | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/icons/browser-cookies.svg b/src/icons/browser-cookies.svg index c5fdc49d6..0873b0db0 100644 --- a/src/icons/browser-cookies.svg +++ b/src/icons/browser-cookies.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/icons/filter-stalled.svg b/src/icons/filter-stalled.svg index b376051c5..28896ec8a 100644 --- a/src/icons/filter-stalled.svg +++ b/src/icons/filter-stalled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/icons/view-refresh.svg b/src/icons/view-refresh.svg index 3b5b6a848..bea8382f0 100644 --- a/src/icons/view-refresh.svg +++ b/src/icons/view-refresh.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/webui/www/private/images/filter-stalled.svg b/src/webui/www/private/images/filter-stalled.svg index b376051c5..28896ec8a 100644 --- a/src/webui/www/private/images/filter-stalled.svg +++ b/src/webui/www/private/images/filter-stalled.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/webui/www/private/images/view-refresh.svg b/src/webui/www/private/images/view-refresh.svg index 3b5b6a848..bea8382f0 100644 --- a/src/webui/www/private/images/view-refresh.svg +++ b/src/webui/www/private/images/view-refresh.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From a4289a517ff62807104671a2e1193e6b5cbe3fad Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Sun, 25 Dec 2022 16:14:59 +0300 Subject: [PATCH 04/14] Apply correct tab order to Category options dialog Also pre-select (sub)category name for editing when dialog is opened for creating new (sub)category. PR #18270. Closes #18265. --- src/gui/torrentcategorydialog.cpp | 3 +++ src/gui/torrentcategorydialog.ui | 28 +++++++++++++++++----------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/gui/torrentcategorydialog.cpp b/src/gui/torrentcategorydialog.cpp index 34cdce354..ccbc3bd62 100644 --- a/src/gui/torrentcategorydialog.cpp +++ b/src/gui/torrentcategorydialog.cpp @@ -133,6 +133,9 @@ QString TorrentCategoryDialog::categoryName() const void TorrentCategoryDialog::setCategoryName(const QString &categoryName) { m_ui->textCategoryName->setText(categoryName); + + const int subcategoryNameStart = categoryName.lastIndexOf(u"/") + 1; + m_ui->textCategoryName->setSelection(subcategoryNameStart, (categoryName.size() - subcategoryNameStart)); } BitTorrent::CategoryOptions TorrentCategoryDialog::categoryOptions() const diff --git a/src/gui/torrentcategorydialog.ui b/src/gui/torrentcategorydialog.ui index 7eaaa7909..2a9c785ef 100644 --- a/src/gui/torrentcategorydialog.ui +++ b/src/gui/torrentcategorydialog.ui @@ -29,13 +29,10 @@ - - - - - 0 - 0 - + + + + Name: @@ -49,10 +46,13 @@ - - - - Name: + + + + + 0 + 0 + @@ -173,6 +173,12 @@ 1 + + textCategoryName + comboSavePath + comboUseDownloadPath + comboDownloadPath + From de15907ea78e74d5b4d344a3ed6ac3dd82d36671 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Sun, 25 Dec 2022 16:25:56 +0300 Subject: [PATCH 05/14] Re-allow to use icons from system theme PR #18195. --- src/base/preferences.cpp | 12 +++ src/base/preferences.h | 4 + src/gui/categoryfiltermodel.cpp | 2 +- src/gui/categoryfilterwidget.cpp | 10 +-- src/gui/executionlogwidget.cpp | 4 +- src/gui/mainwindow.cpp | 19 ++--- src/gui/optionsdialog.cpp | 25 ++++-- src/gui/optionsdialog.ui | 83 ++++++++++--------- src/gui/properties/propertieswidget.cpp | 2 +- src/gui/properties/proptabbar.cpp | 4 +- src/gui/properties/trackerlistwidget.cpp | 6 +- src/gui/properties/trackersadditiondialog.cpp | 2 +- src/gui/rss/articlelistwidget.cpp | 6 +- src/gui/rss/automatedrssdownloader.cpp | 12 +-- src/gui/rss/feedlistwidget.cpp | 2 +- src/gui/rss/rsswidget.cpp | 8 +- src/gui/search/searchjobwidget.cpp | 8 +- src/gui/search/searchwidget.cpp | 2 +- src/gui/statusbar.cpp | 4 +- src/gui/tagfiltermodel.cpp | 2 +- src/gui/tagfilterwidget.cpp | 8 +- src/gui/torrentcontentmodel.cpp | 2 +- src/gui/transferlistfilterswidget.cpp | 36 ++++---- src/gui/transferlistmodel.cpp | 8 +- src/gui/transferlistwidget.cpp | 24 +++--- src/gui/uithememanager.cpp | 48 ++++++++--- src/gui/uithememanager.h | 5 +- 27 files changed, 204 insertions(+), 144 deletions(-) diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index 4cc99227c..101eefd77 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -996,6 +996,18 @@ void Preferences::resolvePeerHostNames(const bool resolve) setValue(u"Preferences/Connection/ResolvePeerHostNames"_qs, resolve); } +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) +bool Preferences::useSystemIcons() const +{ + return value(u"Preferences/Advanced/useSystemIconTheme"_qs, false); +} + +void Preferences::useSystemIcons(const bool enabled) +{ + setValue(u"Preferences/Advanced/useSystemIconTheme"_qs, enabled); +} +#endif + bool Preferences::isRecursiveDownloadEnabled() const { return !value(u"Preferences/Advanced/DisableRecursiveDownload"_qs, false); diff --git a/src/base/preferences.h b/src/base/preferences.h index d22db8df5..0dde9afef 100644 --- a/src/base/preferences.h +++ b/src/base/preferences.h @@ -281,6 +281,10 @@ public: void resolvePeerCountries(bool resolve); bool resolvePeerHostNames() const; void resolvePeerHostNames(bool resolve); +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) + bool useSystemIcons() const; + void useSystemIcons(bool enabled); +#endif bool isRecursiveDownloadEnabled() const; void setRecursiveDownloadEnabled(bool enable); #ifdef Q_OS_WIN diff --git a/src/gui/categoryfiltermodel.cpp b/src/gui/categoryfiltermodel.cpp index f30ceaaae..7b3844770 100644 --- a/src/gui/categoryfiltermodel.cpp +++ b/src/gui/categoryfiltermodel.cpp @@ -211,7 +211,7 @@ QVariant CategoryFilterModel::data(const QModelIndex &index, int role) const if ((index.column() == 0) && (role == Qt::DecorationRole)) { - return UIThemeManager::instance()->getIcon(u"view-categories"_qs); + return UIThemeManager::instance()->getIcon(u"view-categories"_qs, u"inode-directory"_qs); } if ((index.column() == 0) && (role == Qt::DisplayRole)) diff --git a/src/gui/categoryfilterwidget.cpp b/src/gui/categoryfilterwidget.cpp index 61a77f049..5c22bfbd4 100644 --- a/src/gui/categoryfilterwidget.cpp +++ b/src/gui/categoryfilterwidget.cpp @@ -121,18 +121,18 @@ void CategoryFilterWidget::showMenu() , this, &CategoryFilterWidget::addSubcategory); } - menu->addAction(UIThemeManager::instance()->getIcon(u"edit-rename"_qs), tr("Edit category...") + menu->addAction(UIThemeManager::instance()->getIcon(u"edit-rename"_qs, u"document-edit"_qs), tr("Edit category...") , this, &CategoryFilterWidget::editCategory); - menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs), tr("Remove category") + menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Remove category") , this, &CategoryFilterWidget::removeCategory); } - menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs), tr("Remove unused categories") + menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Remove unused categories") , this, &CategoryFilterWidget::removeUnusedCategories); menu->addSeparator(); - menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs), tr("Resume torrents") + menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs), tr("Resume torrents") , this, &CategoryFilterWidget::actionResumeTorrentsTriggered); - menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs), tr("Pause torrents") + menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs), tr("Pause torrents") , this, &CategoryFilterWidget::actionPauseTorrentsTriggered); menu->addAction(UIThemeManager::instance()->getIcon(u"list-remove"_qs), tr("Remove torrents") , this, &CategoryFilterWidget::actionDeleteTorrentsTriggered); diff --git a/src/gui/executionlogwidget.cpp b/src/gui/executionlogwidget.cpp index 5d12f3f50..95f1cb064 100644 --- a/src/gui/executionlogwidget.cpp +++ b/src/gui/executionlogwidget.cpp @@ -69,8 +69,8 @@ ExecutionLogWidget::ExecutionLogWidget(const Log::MsgTypes types, QWidget *paren m_ui->tabBan->layout()->addWidget(peerView); #ifndef Q_OS_MACOS - m_ui->tabConsole->setTabIcon(0, UIThemeManager::instance()->getIcon(u"help-contents"_qs)); - m_ui->tabConsole->setTabIcon(1, UIThemeManager::instance()->getIcon(u"ip-blocked"_qs)); + m_ui->tabConsole->setTabIcon(0, UIThemeManager::instance()->getIcon(u"help-contents"_qs, u"view-calendar-journal"_qs)); + m_ui->tabConsole->setTabIcon(1, UIThemeManager::instance()->getIcon(u"ip-blocked"_qs, u"view-filter"_qs)); #endif } diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 6f73e4ce9..636cf78c7 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -134,8 +134,7 @@ MainWindow::MainWindow(IGUIApplication *app, const State initialState) m_displaySpeedInTitle = pref->speedInTitleBar(); // Setting icons #ifndef Q_OS_MACOS - const QIcon appLogo(UIThemeManager::instance()->getIcon(u"qbittorrent"_qs, u"qbittorrent-tray"_qs)); - setWindowIcon(appLogo); + setWindowIcon(UIThemeManager::instance()->getIcon(u"qbittorrent"_qs)); #endif // Q_OS_MACOS #if (defined(Q_OS_UNIX)) @@ -147,7 +146,7 @@ MainWindow::MainWindow(IGUIApplication *app, const State initialState) m_ui->actionOpen->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_qs)); m_ui->actionDownloadFromURL->setIcon(UIThemeManager::instance()->getIcon(u"insert-link"_qs)); m_ui->actionSetGlobalSpeedLimits->setIcon(UIThemeManager::instance()->getIcon(u"speedometer"_qs)); - m_ui->actionCreateTorrent->setIcon(UIThemeManager::instance()->getIcon(u"torrent-creator"_qs)); + m_ui->actionCreateTorrent->setIcon(UIThemeManager::instance()->getIcon(u"torrent-creator"_qs, u"document-edit"_qs)); m_ui->actionAbout->setIcon(UIThemeManager::instance()->getIcon(u"help-about"_qs)); m_ui->actionStatistics->setIcon(UIThemeManager::instance()->getIcon(u"view-statistics"_qs)); m_ui->actionTopQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-top"_qs)); @@ -159,13 +158,13 @@ MainWindow::MainWindow(IGUIApplication *app, const State initialState) m_ui->actionDonateMoney->setIcon(UIThemeManager::instance()->getIcon(u"wallet-open"_qs)); m_ui->actionExit->setIcon(UIThemeManager::instance()->getIcon(u"application-exit"_qs)); m_ui->actionLock->setIcon(UIThemeManager::instance()->getIcon(u"object-locked"_qs)); - m_ui->actionOptions->setIcon(UIThemeManager::instance()->getIcon(u"configure"_qs)); - m_ui->actionPause->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs)); - m_ui->actionPauseAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs)); - m_ui->actionStart->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_qs)); - m_ui->actionStartAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_qs)); - m_ui->menuAutoShutdownOnDownloadsCompletion->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_qs)); - m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon(u"browser-cookies"_qs)); + m_ui->actionOptions->setIcon(UIThemeManager::instance()->getIcon(u"configure"_qs, u"preferences-system"_qs)); + m_ui->actionPause->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs)); + m_ui->actionPauseAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs)); + m_ui->actionStart->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs)); + m_ui->actionStartAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs)); + m_ui->menuAutoShutdownOnDownloadsCompletion->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_qs, u"application-exit"_qs)); + m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon(u"browser-cookies"_qs, u"preferences-web-browser-cookies"_qs)); m_ui->menuLog->setIcon(UIThemeManager::instance()->getIcon(u"help-contents"_qs)); m_ui->actionCheckForUpdates->setIcon(UIThemeManager::instance()->getIcon(u"view-refresh"_qs)); diff --git a/src/gui/optionsdialog.cpp b/src/gui/optionsdialog.cpp index d4068e26a..c13146136 100644 --- a/src/gui/optionsdialog.cpp +++ b/src/gui/optionsdialog.cpp @@ -203,17 +203,17 @@ OptionsDialog::OptionsDialog(IGUIApplication *app, QWidget *parent) // Main icons m_ui->tabSelection->item(TAB_UI)->setIcon(UIThemeManager::instance()->getIcon(u"preferences-desktop"_qs)); - m_ui->tabSelection->item(TAB_BITTORRENT)->setIcon(UIThemeManager::instance()->getIcon(u"preferences-bittorrent"_qs)); - m_ui->tabSelection->item(TAB_CONNECTION)->setIcon(UIThemeManager::instance()->getIcon(u"network-connect"_qs)); - m_ui->tabSelection->item(TAB_DOWNLOADS)->setIcon(UIThemeManager::instance()->getIcon(u"download"_qs)); - m_ui->tabSelection->item(TAB_SPEED)->setIcon(UIThemeManager::instance()->getIcon(u"speedometer"_qs)); - m_ui->tabSelection->item(TAB_RSS)->setIcon(UIThemeManager::instance()->getIcon(u"application-rss"_qs)); + m_ui->tabSelection->item(TAB_BITTORRENT)->setIcon(UIThemeManager::instance()->getIcon(u"preferences-bittorrent"_qs, u"preferences-system-network"_qs)); + m_ui->tabSelection->item(TAB_CONNECTION)->setIcon(UIThemeManager::instance()->getIcon(u"network-connect"_qs, u"network-wired"_qs)); + m_ui->tabSelection->item(TAB_DOWNLOADS)->setIcon(UIThemeManager::instance()->getIcon(u"download"_qs, u"folder-download"_qs)); + m_ui->tabSelection->item(TAB_SPEED)->setIcon(UIThemeManager::instance()->getIcon(u"speedometer"_qs, u"chronometer"_qs)); + m_ui->tabSelection->item(TAB_RSS)->setIcon(UIThemeManager::instance()->getIcon(u"application-rss"_qs, u"application-rss+xml"_qs)); #ifdef DISABLE_WEBUI m_ui->tabSelection->item(TAB_WEBUI)->setHidden(true); #else - m_ui->tabSelection->item(TAB_WEBUI)->setIcon(UIThemeManager::instance()->getIcon(u"preferences-webui"_qs)); + m_ui->tabSelection->item(TAB_WEBUI)->setIcon(UIThemeManager::instance()->getIcon(u"preferences-webui"_qs, u"network-server"_qs)); #endif - m_ui->tabSelection->item(TAB_ADVANCED)->setIcon(UIThemeManager::instance()->getIcon(u"preferences-advanced"_qs)); + m_ui->tabSelection->item(TAB_ADVANCED)->setIcon(UIThemeManager::instance()->getIcon(u"preferences-advanced"_qs, u"preferences-other"_qs)); // set uniform size for all icons int maxHeight = -1; @@ -288,6 +288,11 @@ void OptionsDialog::loadBehaviorTabOptions() m_ui->customThemeFilePath->setMode(FileSystemPathEdit::Mode::FileOpen); m_ui->customThemeFilePath->setDialogCaption(tr("Select qBittorrent UI Theme file")); m_ui->customThemeFilePath->setFileNameFilter(tr("qBittorrent UI Theme file (*.qbtheme config.json)")); +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) + m_ui->checkUseSystemIcon->setChecked(pref->useSystemIcons()); +#else + m_ui->checkUseSystemIcon->setVisible(false); +#endif m_ui->confirmDeletion->setChecked(pref->confirmTorrentDeletion()); m_ui->checkAltRowColors->setChecked(pref->useAlternatingRowColors()); @@ -382,6 +387,9 @@ void OptionsDialog::loadBehaviorTabOptions() connect(m_ui->comboI18n, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton); +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) + connect(m_ui->checkUseSystemIcon, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); +#endif connect(m_ui->checkUseCustomTheme, &QGroupBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->customThemeFilePath, &FileSystemPathEdit::selectedPathChanged, this, &ThisType::enableApplyButton); @@ -451,6 +459,9 @@ void OptionsDialog::saveBehaviorTabOptions() const } pref->setLocale(locale); +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) + pref->useSystemIcons(m_ui->checkUseSystemIcon->isChecked()); +#endif pref->setUseCustomUITheme(m_ui->checkUseCustomTheme->isChecked()); pref->setCustomUIThemePath(m_ui->customThemeFilePath->selectedPath()); diff --git a/src/gui/optionsdialog.ui b/src/gui/optionsdialog.ui index ea4c73d7f..e407d79e9 100644 --- a/src/gui/optionsdialog.ui +++ b/src/gui/optionsdialog.ui @@ -133,41 +133,18 @@ Interface - - - - Use custom UI Theme + + + + + true + - - true + + Changing Interface settings requires application restart - - - - - UI Theme file: - - - - - - - - - - - Qt::Horizontal - - - - 200 - 20 - - - - @@ -191,15 +168,45 @@ - - - - - true - + + + + Qt::Horizontal + + + 200 + 20 + + + + + + + + Use custom UI Theme + + + true + + + + + + UI Theme file: + + + + + + + + + + + - Changing Interface settings requires application restart + Use icons from system theme diff --git a/src/gui/properties/propertieswidget.cpp b/src/gui/properties/propertieswidget.cpp index 8c63d048f..b8875a6e7 100644 --- a/src/gui/properties/propertieswidget.cpp +++ b/src/gui/properties/propertieswidget.cpp @@ -750,7 +750,7 @@ void PropertiesWidget::displayWebSeedListMenu() if (!rows.isEmpty()) { - menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs), tr("Remove Web seed") + menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Remove Web seed") , this, &PropertiesWidget::deleteSelectedUrlSeeds); menu->addSeparator(); menu->addAction(UIThemeManager::instance()->getIcon(u"edit-copy"_qs), tr("Copy Web seed URL") diff --git a/src/gui/properties/proptabbar.cpp b/src/gui/properties/proptabbar.cpp index 81941f9fa..d2094a658 100644 --- a/src/gui/properties/proptabbar.cpp +++ b/src/gui/properties/proptabbar.cpp @@ -45,7 +45,7 @@ PropTabBar::PropTabBar(QWidget *parent) // General tab QPushButton *mainInfosButton = new QPushButton( #ifndef Q_OS_MACOS - UIThemeManager::instance()->getIcon(u"help-about"_qs), + UIThemeManager::instance()->getIcon(u"help-about"_qs, u"document-properties"_qs), #endif tr("General"), parent); mainInfosButton->setShortcut(Qt::ALT + Qt::Key_G); @@ -54,7 +54,7 @@ PropTabBar::PropTabBar(QWidget *parent) // Trackers tab QPushButton *trackersButton = new QPushButton( #ifndef Q_OS_MACOS - UIThemeManager::instance()->getIcon(u"trackers"_qs), + UIThemeManager::instance()->getIcon(u"trackers"_qs, u"network-server"_qs), #endif tr("Trackers"), parent); trackersButton->setShortcut(Qt::ALT + Qt::Key_C); diff --git a/src/gui/properties/trackerlistwidget.cpp b/src/gui/properties/trackerlistwidget.cpp index 791700485..e38afdcaa 100644 --- a/src/gui/properties/trackerlistwidget.cpp +++ b/src/gui/properties/trackerlistwidget.cpp @@ -572,7 +572,7 @@ void TrackerListWidget::showTrackerListMenu() { menu->addAction(UIThemeManager::instance()->getIcon(u"edit-rename"_qs),tr("Edit tracker URL...") , this, &TrackerListWidget::editSelectedTracker); - menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs), tr("Remove tracker") + menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Remove tracker") , this, &TrackerListWidget::deleteSelectedTrackers); menu->addAction(UIThemeManager::instance()->getIcon(u"edit-copy"_qs), tr("Copy tracker URL") , this, &TrackerListWidget::copyTrackerUrl); @@ -580,10 +580,10 @@ void TrackerListWidget::showTrackerListMenu() if (!torrent->isPaused()) { - menu->addAction(UIThemeManager::instance()->getIcon(u"reannounce"_qs), tr("Force reannounce to selected trackers") + menu->addAction(UIThemeManager::instance()->getIcon(u"reannounce"_qs, u"view-refresh"_qs), tr("Force reannounce to selected trackers") , this, &TrackerListWidget::reannounceSelected); menu->addSeparator(); - menu->addAction(UIThemeManager::instance()->getIcon(u"reannounce"_qs), tr("Force reannounce to all trackers") + menu->addAction(UIThemeManager::instance()->getIcon(u"reannounce"_qs, u"view-refresh"_qs), tr("Force reannounce to all trackers") , this, [this]() { BitTorrent::Torrent *h = m_properties->getCurrentTorrent(); diff --git a/src/gui/properties/trackersadditiondialog.cpp b/src/gui/properties/trackersadditiondialog.cpp index 4ea931ddd..5b154c994 100644 --- a/src/gui/properties/trackersadditiondialog.cpp +++ b/src/gui/properties/trackersadditiondialog.cpp @@ -52,7 +52,7 @@ TrackersAdditionDialog::TrackersAdditionDialog(QWidget *parent, BitTorrent::Torr { m_ui->setupUi(this); - m_ui->downloadButton->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs)); + m_ui->downloadButton->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs, u"download"_qs)); m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Add")); connect(m_ui->downloadButton, &QAbstractButton::clicked, this, &TrackersAdditionDialog::onDownloadButtonClicked); diff --git a/src/gui/rss/articlelistwidget.cpp b/src/gui/rss/articlelistwidget.cpp index 8d8d48daf..3f3f3b0aa 100644 --- a/src/gui/rss/articlelistwidget.cpp +++ b/src/gui/rss/articlelistwidget.cpp @@ -105,7 +105,7 @@ void ArticleListWidget::handleArticleRead(RSS::Article *rssArticle) const QColor defaultColor {palette().color(QPalette::Inactive, QPalette::WindowText)}; const QBrush foregroundBrush {UIThemeManager::instance()->getColor(u"RSS.ReadArticle"_qs, defaultColor)}; item->setData(Qt::ForegroundRole, foregroundBrush); - item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"loading"_qs)); + item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"loading"_qs, u"sphere"_qs)); checkInvariant(); } @@ -133,14 +133,14 @@ QListWidgetItem *ArticleListWidget::createItem(RSS::Article *article) const const QColor defaultColor {palette().color(QPalette::Inactive, QPalette::WindowText)}; const QBrush foregroundBrush {UIThemeManager::instance()->getColor(u"RSS.ReadArticle"_qs, defaultColor)}; item->setData(Qt::ForegroundRole, foregroundBrush); - item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"loading"_qs)); + item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"loading"_qs, u"sphere"_qs)); } else { const QColor defaultColor {palette().color(QPalette::Active, QPalette::Link)}; const QBrush foregroundBrush {UIThemeManager::instance()->getColor(u"RSS.UnreadArticle"_qs, defaultColor)}; item->setData(Qt::ForegroundRole, foregroundBrush); - item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"loading"_qs)); + item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"loading"_qs, u"sphere"_qs)); } return item; diff --git a/src/gui/rss/automatedrssdownloader.cpp b/src/gui/rss/automatedrssdownloader.cpp index ff5f31f4e..a8f9204a2 100644 --- a/src/gui/rss/automatedrssdownloader.cpp +++ b/src/gui/rss/automatedrssdownloader.cpp @@ -75,7 +75,7 @@ AutomatedRssDownloader::AutomatedRssDownloader(QWidget *parent) { m_ui->setupUi(this); // Icons - m_ui->removeRuleBtn->setIcon(UIThemeManager::instance()->getIcon(u"edit-clear"_qs)); + m_ui->removeRuleBtn->setIcon(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs)); m_ui->addRuleBtn->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_qs)); m_ui->addCategoryBtn->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_qs)); @@ -521,7 +521,7 @@ void AutomatedRssDownloader::displayRulesListMenu() { if (selection.count() == 1) { - menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs), tr("Delete rule") + menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Delete rule") , this, &AutomatedRssDownloader::on_removeRuleBtn_clicked); menu->addSeparator(); menu->addAction(UIThemeManager::instance()->getIcon(u"edit-rename"_qs), tr("Rename rule...") @@ -529,7 +529,7 @@ void AutomatedRssDownloader::displayRulesListMenu() } else { - menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs), tr("Delete selected rules") + menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Delete selected rules") , this, &AutomatedRssDownloader::on_removeRuleBtn_clicked); } @@ -759,7 +759,7 @@ void AutomatedRssDownloader::updateMustLineValidity() else { m_ui->lineContains->setStyleSheet(u"QLineEdit { color: #ff0000; }"_qs); - m_ui->labelMustStat->setPixmap(UIThemeManager::instance()->getIcon(u"dialog-warning"_qs).pixmap(16, 16)); + m_ui->labelMustStat->setPixmap(UIThemeManager::instance()->getIcon(u"dialog-warning"_qs, u"task-attention"_qs).pixmap(16, 16)); m_ui->labelMustStat->setToolTip(error); } } @@ -806,7 +806,7 @@ void AutomatedRssDownloader::updateMustNotLineValidity() else { m_ui->lineNotContains->setStyleSheet(u"QLineEdit { color: #ff0000; }"_qs); - m_ui->labelMustNotStat->setPixmap(UIThemeManager::instance()->getIcon(u"dialog-warning"_qs).pixmap(16, 16)); + m_ui->labelMustNotStat->setPixmap(UIThemeManager::instance()->getIcon(u"dialog-warning"_qs, u"task-attention"_qs).pixmap(16, 16)); m_ui->labelMustNotStat->setToolTip(error); } } @@ -824,7 +824,7 @@ void AutomatedRssDownloader::updateEpisodeFilterValidity() else { m_ui->lineEFilter->setStyleSheet(u"QLineEdit { color: #ff0000; }"_qs); - m_ui->labelEpFilterStat->setPixmap(UIThemeManager::instance()->getIcon(u"dialog-warning"_qs).pixmap(16, 16)); + m_ui->labelEpFilterStat->setPixmap(UIThemeManager::instance()->getIcon(u"dialog-warning"_qs, u"task-attention"_qs).pixmap(16, 16)); } } diff --git a/src/gui/rss/feedlistwidget.cpp b/src/gui/rss/feedlistwidget.cpp index 06676be1d..8657dcca8 100644 --- a/src/gui/rss/feedlistwidget.cpp +++ b/src/gui/rss/feedlistwidget.cpp @@ -81,7 +81,7 @@ namespace if (feed->isLoading()) return UIThemeManager::instance()->getIcon(u"loading"_qs); if (feed->hasError()) - return UIThemeManager::instance()->getIcon(u"task-reject"_qs); + return UIThemeManager::instance()->getIcon(u"task-reject"_qs, u"unavailable"_qs); return loadIcon(feed->iconPath(), u"application-rss"_qs); } diff --git a/src/gui/rss/rsswidget.cpp b/src/gui/rss/rsswidget.cpp index 52d76a341..e0bfec041 100644 --- a/src/gui/rss/rsswidget.cpp +++ b/src/gui/rss/rsswidget.cpp @@ -64,8 +64,8 @@ RSSWidget::RSSWidget(QWidget *parent) // Icons m_ui->actionCopyFeedURL->setIcon(UIThemeManager::instance()->getIcon(u"edit-copy"_qs)); m_ui->actionDelete->setIcon(UIThemeManager::instance()->getIcon(u"edit-clear"_qs)); - m_ui->actionDownloadTorrent->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs)); - m_ui->actionMarkItemsRead->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_qs)); + m_ui->actionDownloadTorrent->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs, u"download"_qs)); + m_ui->actionMarkItemsRead->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_qs, u"mail-mark-read"_qs)); m_ui->actionNewFolder->setIcon(UIThemeManager::instance()->getIcon(u"folder-new"_qs)); m_ui->actionNewSubscription->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_qs)); m_ui->actionOpenNewsURL->setIcon(UIThemeManager::instance()->getIcon(u"application-url"_qs)); @@ -74,9 +74,9 @@ RSSWidget::RSSWidget(QWidget *parent) m_ui->actionUpdateAllFeeds->setIcon(UIThemeManager::instance()->getIcon(u"view-refresh"_qs)); #ifndef Q_OS_MACOS m_ui->newFeedButton->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_qs)); - m_ui->markReadButton->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_qs)); + m_ui->markReadButton->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_qs, u"mail-mark-read"_qs)); m_ui->updateAllButton->setIcon(UIThemeManager::instance()->getIcon(u"view-refresh"_qs)); - m_ui->rssDownloaderBtn->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs)); + m_ui->rssDownloaderBtn->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs, u"download"_qs)); #endif m_articleListWidget = new ArticleListWidget(m_ui->splitterMain); diff --git a/src/gui/search/searchjobwidget.cpp b/src/gui/search/searchjobwidget.cpp index 9b907c717..3b7cb37d1 100644 --- a/src/gui/search/searchjobwidget.cpp +++ b/src/gui/search/searchjobwidget.cpp @@ -392,7 +392,7 @@ void SearchJobWidget::contextMenuEvent(QContextMenuEvent *event) menu->addAction(UIThemeManager::instance()->getIcon(u"download"_qs) , tr("Open download window"), this, [this]() { downloadTorrents(AddTorrentOption::ShowDialog); }); - menu->addAction(UIThemeManager::instance()->getIcon(u"downloading"_qs) + menu->addAction(UIThemeManager::instance()->getIcon(u"downloading"_qs, u"download"_qs) , tr("Download"), this, [this]() { downloadTorrents(AddTorrentOption::SkipDialog); }); menu->addSeparator(); menu->addAction(UIThemeManager::instance()->getIcon(u"application-url"_qs), tr("Open description page") @@ -401,11 +401,11 @@ void SearchJobWidget::contextMenuEvent(QContextMenuEvent *event) QMenu *copySubMenu = menu->addMenu( UIThemeManager::instance()->getIcon(u"edit-copy"_qs), tr("Copy")); - copySubMenu->addAction(UIThemeManager::instance()->getIcon(u"name"_qs), tr("Name") + copySubMenu->addAction(UIThemeManager::instance()->getIcon(u"name"_qs, u"edit-copy"_qs), tr("Name") , this, &SearchJobWidget::copyTorrentNames); - copySubMenu->addAction(UIThemeManager::instance()->getIcon(u"insert-link"_qs), tr("Download link") + copySubMenu->addAction(UIThemeManager::instance()->getIcon(u"insert-link"_qs, u"edit-copy"_qs), tr("Download link") , this, &SearchJobWidget::copyTorrentDownloadLinks); - copySubMenu->addAction(UIThemeManager::instance()->getIcon(u"application-url"_qs), tr("Description page URL") + copySubMenu->addAction(UIThemeManager::instance()->getIcon(u"application-url"_qs, u"edit-copy"_qs), tr("Description page URL") , this, &SearchJobWidget::copyTorrentURLs); menu->popup(event->globalPos()); diff --git a/src/gui/search/searchwidget.cpp b/src/gui/search/searchwidget.cpp index 03d4a39ff..2896f773e 100644 --- a/src/gui/search/searchwidget.cpp +++ b/src/gui/search/searchwidget.cpp @@ -112,7 +112,7 @@ SearchWidget::SearchWidget(IGUIApplication *app, MainWindow *mainWindow) #ifndef Q_OS_MACOS // Icons m_ui->searchButton->setIcon(UIThemeManager::instance()->getIcon(u"edit-find"_qs)); - m_ui->pluginsButton->setIcon(UIThemeManager::instance()->getIcon(u"plugins"_qs)); + m_ui->pluginsButton->setIcon(UIThemeManager::instance()->getIcon(u"plugins"_qs, u"preferences-system-network"_qs)); #else // On macOS the icons overlap the text otherwise QSize iconSize = m_ui->tabWidget->iconSize(); diff --git a/src/gui/statusbar.cpp b/src/gui/statusbar.cpp index 0f4bda38e..b4877b692 100644 --- a/src/gui/statusbar.cpp +++ b/src/gui/statusbar.cpp @@ -69,7 +69,7 @@ StatusBar::StatusBar(QWidget *parent) connect(m_connecStatusLblIcon, &QAbstractButton::clicked, this, &StatusBar::connectionButtonClicked); m_dlSpeedLbl = new QPushButton(this); - m_dlSpeedLbl->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs)); + m_dlSpeedLbl->setIcon(UIThemeManager::instance()->getIcon(u"downloading"_qs, u"downloading_small"_qs)); connect(m_dlSpeedLbl, &QAbstractButton::clicked, this, &StatusBar::capSpeed); m_dlSpeedLbl->setFlat(true); m_dlSpeedLbl->setFocusPolicy(Qt::NoFocus); @@ -78,7 +78,7 @@ StatusBar::StatusBar(QWidget *parent) m_dlSpeedLbl->setMinimumWidth(200); m_upSpeedLbl = new QPushButton(this); - m_upSpeedLbl->setIcon(UIThemeManager::instance()->getIcon(u"upload"_qs)); + m_upSpeedLbl->setIcon(UIThemeManager::instance()->getIcon(u"upload"_qs, u"seeding"_qs)); connect(m_upSpeedLbl, &QAbstractButton::clicked, this, &StatusBar::capSpeed); m_upSpeedLbl->setFlat(true); m_upSpeedLbl->setFocusPolicy(Qt::NoFocus); diff --git a/src/gui/tagfiltermodel.cpp b/src/gui/tagfiltermodel.cpp index 702f9c9e7..c12029efc 100644 --- a/src/gui/tagfiltermodel.cpp +++ b/src/gui/tagfiltermodel.cpp @@ -123,7 +123,7 @@ QVariant TagFilterModel::data(const QModelIndex &index, int role) const switch (role) { case Qt::DecorationRole: - return UIThemeManager::instance()->getIcon(u"tags"_qs); + return UIThemeManager::instance()->getIcon(u"tags"_qs, u"inode-directory"_qs); case Qt::DisplayRole: return u"%1 (%2)"_qs.arg(tagDisplayName(item.tag())).arg(item.torrentsCount()); case Qt::UserRole: diff --git a/src/gui/tagfilterwidget.cpp b/src/gui/tagfilterwidget.cpp index 7d971c516..125769fc3 100644 --- a/src/gui/tagfilterwidget.cpp +++ b/src/gui/tagfilterwidget.cpp @@ -113,16 +113,16 @@ void TagFilterWidget::showMenu() const auto selectedRows = selectionModel()->selectedRows(); if (!selectedRows.empty() && !TagFilterModel::isSpecialItem(selectedRows.first())) { - menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs), tr("Remove tag") + menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Remove tag") , this, &TagFilterWidget::removeTag); } - menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs), tr("Remove unused tags") + menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_qs, u"list-remove"_qs), tr("Remove unused tags") , this, &TagFilterWidget::removeUnusedTags); menu->addSeparator(); - menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs), tr("Resume torrents") + menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs), tr("Resume torrents") , this, &TagFilterWidget::actionResumeTorrentsTriggered); - menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs), tr("Pause torrents") + menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs), tr("Pause torrents") , this, &TagFilterWidget::actionPauseTorrentsTriggered); menu->addAction(UIThemeManager::instance()->getIcon(u"list-remove"_qs), tr("Remove torrents") , this, &TagFilterWidget::actionDeleteTorrentsTriggered); diff --git a/src/gui/torrentcontentmodel.cpp b/src/gui/torrentcontentmodel.cpp index 21bbff55d..0ae77fd57 100644 --- a/src/gui/torrentcontentmodel.cpp +++ b/src/gui/torrentcontentmodel.cpp @@ -70,7 +70,7 @@ namespace { public: UnifiedFileIconProvider() - : m_textPlainIcon {UIThemeManager::instance()->getIcon(u"help-about"_qs)} + : m_textPlainIcon {UIThemeManager::instance()->getIcon(u"help-about"_qs, u"text-plain"_qs)} { } diff --git a/src/gui/transferlistfilterswidget.cpp b/src/gui/transferlistfilterswidget.cpp index 4a7c0281d..0a62098ac 100644 --- a/src/gui/transferlistfilterswidget.cpp +++ b/src/gui/transferlistfilterswidget.cpp @@ -173,31 +173,31 @@ StatusFilterWidget::StatusFilterWidget(QWidget *parent, TransferListWidget *tran // Add status filters auto *all = new QListWidgetItem(this); all->setData(Qt::DisplayRole, tr("All (0)", "this is for the status filter")); - all->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-all"_qs)); + all->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-all"_qs, u"filterall"_qs)); auto *downloading = new QListWidgetItem(this); downloading->setData(Qt::DisplayRole, tr("Downloading (0)")); downloading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"downloading"_qs)); auto *seeding = new QListWidgetItem(this); seeding->setData(Qt::DisplayRole, tr("Seeding (0)")); - seeding->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"upload"_qs)); + seeding->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"upload"_qs, u"uploading"_qs)); auto *completed = new QListWidgetItem(this); completed->setData(Qt::DisplayRole, tr("Completed (0)")); - completed->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"checked-completed"_qs)); + completed->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"checked-completed"_qs, u"completed"_qs)); auto *resumed = new QListWidgetItem(this); resumed->setData(Qt::DisplayRole, tr("Resumed (0)")); - resumed->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"torrent-start"_qs)); + resumed->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs)); auto *paused = new QListWidgetItem(this); paused->setData(Qt::DisplayRole, tr("Paused (0)")); - paused->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"torrent-stop"_qs)); + paused->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs)); auto *active = new QListWidgetItem(this); active->setData(Qt::DisplayRole, tr("Active (0)")); - active->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-active"_qs)); + active->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-active"_qs, u"filteractive"_qs)); auto *inactive = new QListWidgetItem(this); inactive->setData(Qt::DisplayRole, tr("Inactive (0)")); - inactive->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-inactive"_qs)); + inactive->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-inactive"_qs, u"filterinactive"_qs)); auto *stalled = new QListWidgetItem(this); stalled->setData(Qt::DisplayRole, tr("Stalled (0)")); - stalled->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-stalled"_qs)); + stalled->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"filter-stalled"_qs, u"filterstalled"_qs)); auto *stalledUploading = new QListWidgetItem(this); stalledUploading->setData(Qt::DisplayRole, tr("Stalled Uploading (0)")); stalledUploading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"stalledUP"_qs)); @@ -206,7 +206,7 @@ StatusFilterWidget::StatusFilterWidget(QWidget *parent, TransferListWidget *tran stalledDownloading->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"stalledDL"_qs)); auto *checking = new QListWidgetItem(this); checking->setData(Qt::DisplayRole, tr("Checking (0)")); - checking->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"force-recheck"_qs)); + checking->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"force-recheck"_qs, u"checking"_qs)); auto *moving = new QListWidgetItem(this); moving->setData(Qt::DisplayRole, tr("Moving (0)")); moving->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"set-location"_qs)); @@ -308,9 +308,9 @@ void StatusFilterWidget::showMenu() QMenu *menu = new QMenu(this); menu->setAttribute(Qt::WA_DeleteOnClose); - menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs), tr("Resume torrents") + menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs), tr("Resume torrents") , transferList, &TransferListWidget::startVisibleTorrents); - menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs), tr("Pause torrents") + menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs), tr("Pause torrents") , transferList, &TransferListWidget::pauseVisibleTorrents); menu->addAction(UIThemeManager::instance()->getIcon(u"list-remove"_qs), tr("Remove torrents") , transferList, &TransferListWidget::deleteVisibleTorrents); @@ -371,16 +371,16 @@ TrackerFiltersList::TrackerFiltersList(QWidget *parent, TransferListWidget *tran { auto *allTrackers = new QListWidgetItem(this); allTrackers->setData(Qt::DisplayRole, tr("All (0)", "this is for the tracker filter")); - allTrackers->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_qs)); + allTrackers->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_qs, u"network-server"_qs)); auto *noTracker = new QListWidgetItem(this); noTracker->setData(Qt::DisplayRole, tr("Trackerless (0)")); - noTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackerless"_qs)); + noTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackerless"_qs, u"network-server"_qs)); auto *errorTracker = new QListWidgetItem(this); errorTracker->setData(Qt::DisplayRole, tr("Error (0)")); - errorTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_qs)); + errorTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_qs, u"dialog-error"_qs)); auto *warningTracker = new QListWidgetItem(this); warningTracker->setData(Qt::DisplayRole, tr("Warning (0)")); - warningTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-warning"_qs)); + warningTracker->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-warning"_qs, u"dialog-warning"_qs)); m_trackers[NULL_HOST] = {{}, noTracker}; @@ -474,7 +474,7 @@ void TrackerFiltersList::addItems(const QString &trackerURL, const QVectorsetData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_qs)); + trackerItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_qs, u"network-server"_qs)); const TrackerData trackerData {{}, trackerItem}; trackersIt = m_trackers.insert(host, trackerData); @@ -712,9 +712,9 @@ void TrackerFiltersList::showMenu() QMenu *menu = new QMenu(this); menu->setAttribute(Qt::WA_DeleteOnClose); - menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs), tr("Resume torrents") + menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs), tr("Resume torrents") , transferList, &TransferListWidget::startVisibleTorrents); - menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs), tr("Pause torrents") + menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs), tr("Pause torrents") , transferList, &TransferListWidget::pauseVisibleTorrents); menu->addAction(UIThemeManager::instance()->getIcon(u"list-remove"_qs), tr("Remove torrents") , transferList, &TransferListWidget::deleteVisibleTorrents); diff --git a/src/gui/transferlistmodel.cpp b/src/gui/transferlistmodel.cpp index 720e93691..4547935d1 100644 --- a/src/gui/transferlistmodel.cpp +++ b/src/gui/transferlistmodel.cpp @@ -158,15 +158,15 @@ TransferListModel::TransferListModel(QObject *parent) {BitTorrent::TorrentState::Error, tr("Errored", "Torrent status, the torrent has an error")} } , m_stateThemeColors {torrentStateColorsFromUITheme()} - , m_checkingIcon {UIThemeManager::instance()->getIcon(u"force-recheck"_qs)} - , m_completedIcon {UIThemeManager::instance()->getIcon(u"checked-completed"_qs)} + , m_checkingIcon {UIThemeManager::instance()->getIcon(u"force-recheck"_qs, u"checking"_qs)} + , m_completedIcon {UIThemeManager::instance()->getIcon(u"checked-completed"_qs, u"completed"_qs)} , m_downloadingIcon {UIThemeManager::instance()->getIcon(u"downloading"_qs)} , m_errorIcon {UIThemeManager::instance()->getIcon(u"error"_qs)} - , m_pausedIcon {UIThemeManager::instance()->getIcon(u"torrent-stop"_qs)} + , m_pausedIcon {UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs)} , m_queuedIcon {UIThemeManager::instance()->getIcon(u"queued"_qs)} , m_stalledDLIcon {UIThemeManager::instance()->getIcon(u"stalledDL"_qs)} , m_stalledUPIcon {UIThemeManager::instance()->getIcon(u"stalledUP"_qs)} - , m_uploadingIcon {UIThemeManager::instance()->getIcon(u"upload"_qs)} + , m_uploadingIcon {UIThemeManager::instance()->getIcon(u"upload"_qs, u"uploading"_qs)} { configure(); connect(Preferences::instance(), &Preferences::changed, this, &TransferListModel::configure); diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index 196334702..9bca61d45 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -930,11 +930,11 @@ void TransferListWidget::displayListMenu() // Create actions - auto *actionStart = new QAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs), tr("&Resume", "Resume/start the torrent"), listMenu); + auto *actionStart = new QAction(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs), tr("&Resume", "Resume/start the torrent"), listMenu); connect(actionStart, &QAction::triggered, this, &TransferListWidget::startSelectedTorrents); - auto *actionPause = new QAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs), tr("&Pause", "Pause the torrent"), listMenu); + auto *actionPause = new QAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs), tr("&Pause", "Pause the torrent"), listMenu); connect(actionPause, &QAction::triggered, this, &TransferListWidget::pauseSelectedTorrents); - auto *actionForceStart = new QAction(UIThemeManager::instance()->getIcon(u"torrent-start-forced"_qs), tr("Force Resu&me", "Force Resume/start the torrent"), listMenu); + auto *actionForceStart = new QAction(UIThemeManager::instance()->getIcon(u"torrent-start-forced"_qs, u"media-playback-start"_qs), tr("Force Resu&me", "Force Resume/start the torrent"), listMenu); connect(actionForceStart, &QAction::triggered, this, &TransferListWidget::forceStartSelectedTorrents); auto *actionDelete = new QAction(UIThemeManager::instance()->getIcon(u"list-remove"_qs), tr("&Remove", "Remove the torrent"), listMenu); connect(actionDelete, &QAction::triggered, this, &TransferListWidget::softDeleteSelectedTorrents); @@ -952,21 +952,21 @@ void TransferListWidget::displayListMenu() connect(actionTopQueuePos, &QAction::triggered, this, &TransferListWidget::topQueuePosSelectedTorrents); auto *actionBottomQueuePos = new QAction(UIThemeManager::instance()->getIcon(u"go-bottom"_qs), tr("Move to &bottom", "i.e. Move to bottom of the queue"), listMenu); connect(actionBottomQueuePos, &QAction::triggered, this, &TransferListWidget::bottomQueuePosSelectedTorrents); - auto *actionSetTorrentPath = new QAction(UIThemeManager::instance()->getIcon(u"set-location"_qs), tr("Set loc&ation..."), listMenu); + auto *actionSetTorrentPath = new QAction(UIThemeManager::instance()->getIcon(u"set-location"_qs, u"inode-directory"_qs), tr("Set loc&ation..."), listMenu); connect(actionSetTorrentPath, &QAction::triggered, this, &TransferListWidget::setSelectedTorrentsLocation); - auto *actionForceRecheck = new QAction(UIThemeManager::instance()->getIcon(u"force-recheck"_qs), tr("Force rec&heck"), listMenu); + auto *actionForceRecheck = new QAction(UIThemeManager::instance()->getIcon(u"force-recheck"_qs, u"document-edit-verify"_qs), tr("Force rec&heck"), listMenu); connect(actionForceRecheck, &QAction::triggered, this, &TransferListWidget::recheckSelectedTorrents); - auto *actionForceReannounce = new QAction(UIThemeManager::instance()->getIcon(u"reannounce"_qs), tr("Force r&eannounce"), listMenu); + auto *actionForceReannounce = new QAction(UIThemeManager::instance()->getIcon(u"reannounce"_qs, u"document-edit-verify"_qs), tr("Force r&eannounce"), listMenu); connect(actionForceReannounce, &QAction::triggered, this, &TransferListWidget::reannounceSelectedTorrents); - auto *actionCopyMagnetLink = new QAction(UIThemeManager::instance()->getIcon(u"torrent-magnet"_qs), tr("&Magnet link"), listMenu); + auto *actionCopyMagnetLink = new QAction(UIThemeManager::instance()->getIcon(u"torrent-magnet"_qs, u"kt-magnet"_qs), tr("&Magnet link"), listMenu); connect(actionCopyMagnetLink, &QAction::triggered, this, &TransferListWidget::copySelectedMagnetURIs); - auto *actionCopyID = new QAction(UIThemeManager::instance()->getIcon(u"help-about"_qs), tr("Torrent &ID"), listMenu); + auto *actionCopyID = new QAction(UIThemeManager::instance()->getIcon(u"help-about"_qs, u"edit-copy"_qs), tr("Torrent &ID"), listMenu); connect(actionCopyID, &QAction::triggered, this, &TransferListWidget::copySelectedIDs); - auto *actionCopyName = new QAction(UIThemeManager::instance()->getIcon(u"name"_qs), tr("&Name"), listMenu); + auto *actionCopyName = new QAction(UIThemeManager::instance()->getIcon(u"name"_qs, u"edit-copy"_qs), tr("&Name"), listMenu); connect(actionCopyName, &QAction::triggered, this, &TransferListWidget::copySelectedNames); - auto *actionCopyHash1 = new QAction(UIThemeManager::instance()->getIcon(u"hash"_qs), tr("Info &hash v1"), listMenu); + auto *actionCopyHash1 = new QAction(UIThemeManager::instance()->getIcon(u"hash"_qs, u"edit-copy"_qs), tr("Info &hash v1"), listMenu); connect(actionCopyHash1, &QAction::triggered, this, [this]() { copySelectedInfohashes(CopyInfohashPolicy::Version1); }); - auto *actionCopyHash2 = new QAction(UIThemeManager::instance()->getIcon(u"hash"_qs), tr("Info h&ash v2"), listMenu); + auto *actionCopyHash2 = new QAction(UIThemeManager::instance()->getIcon(u"hash"_qs, u"edit-copy"_qs), tr("Info h&ash v2"), listMenu); connect(actionCopyHash2, &QAction::triggered, this, [this]() { copySelectedInfohashes(CopyInfohashPolicy::Version2); }); auto *actionSuperSeedingMode = new TriStateAction(tr("Super seeding mode"), listMenu); connect(actionSuperSeedingMode, &QAction::triggered, this, &TransferListWidget::setSelectedTorrentsSuperSeeding); @@ -1152,7 +1152,7 @@ void TransferListWidget::displayListMenu() QStringList tags(BitTorrent::Session::instance()->tags().values()); std::sort(tags.begin(), tags.end(), Utils::Compare::NaturalLessThan()); - QMenu *tagsMenu = listMenu->addMenu(UIThemeManager::instance()->getIcon(u"tags"_qs), tr("Ta&gs")); + QMenu *tagsMenu = listMenu->addMenu(UIThemeManager::instance()->getIcon(u"tags"_qs, u"view-categories"_qs), tr("Ta&gs")); tagsMenu->addAction(UIThemeManager::instance()->getIcon(u"list-add"_qs), tr("&Add...", "Add / assign multiple tags...") , this, &TransferListWidget::askAddTagsForSelection); diff --git a/src/gui/uithememanager.cpp b/src/gui/uithememanager.cpp index 1253646b4..25412eb00 100644 --- a/src/gui/uithememanager.cpp +++ b/src/gui/uithememanager.cpp @@ -167,7 +167,10 @@ void UIThemeManager::initInstance() } UIThemeManager::UIThemeManager() - : m_useCustomTheme(Preferences::instance()->useCustomUITheme()) + : m_useCustomTheme {Preferences::instance()->useCustomUITheme()} +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) + , m_useSystemIcons {Preferences::instance()->useSystemIcons()} +#endif { if (m_useCustomTheme) { @@ -196,14 +199,25 @@ void UIThemeManager::applyStyleSheet() const qApp->setStyleSheet(QString::fromUtf8(m_themeSource->readStyleSheet())); } -QIcon UIThemeManager::getIcon(const QString &iconId, const QString &fallback) const +QIcon UIThemeManager::getIcon(const QString &iconId, [[maybe_unused]] const QString &fallback) const { // Cache to avoid rescaling svg icons const auto iter = m_iconCache.find(iconId); if (iter != m_iconCache.end()) return *iter; - const QIcon icon {getIconPathFromResources(iconId, fallback).data()}; +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) + // Don't cache system icons because users might change them at run time + if (m_useSystemIcons) + { + auto icon = QIcon::fromTheme(iconId); + if (icon.name() != iconId) + icon = QIcon::fromTheme(fallback, QIcon(getIconPathFromResources(iconId).data())); + return icon; + } +#endif + + const QIcon icon {getIconPathFromResources(iconId).data()}; m_iconCache[iconId] = icon; return icon; } @@ -259,23 +273,33 @@ QIcon UIThemeManager::getSystrayIcon() const Path UIThemeManager::getIconPath(const QString &iconId) const { - return getIconPathFromResources(iconId, {}); +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) + if (m_useSystemIcons) + { + Path path = Utils::Fs::tempPath() / Path(iconId + u".png"); + if (!path.exists()) + { + const QIcon icon = QIcon::fromTheme(iconId); + if (!icon.isNull()) + icon.pixmap(32).save(path.toString()); + else + path = getIconPathFromResources(iconId); + } + + return path; + } +#endif + + return getIconPathFromResources(iconId); } -Path UIThemeManager::getIconPathFromResources(const QString &iconId, const QString &fallback) const +Path UIThemeManager::getIconPathFromResources(const QString &iconId) const { if (m_useCustomTheme && m_themeSource) { const Path customIcon = m_themeSource->iconPath(iconId); if (!customIcon.isEmpty()) return customIcon; - - if (!fallback.isEmpty()) - { - const Path fallbackIcon = m_themeSource->iconPath(fallback); - if (!fallbackIcon.isEmpty()) - return fallbackIcon; - } } return findIcon(iconId, DEFAULT_ICONS_DIR); diff --git a/src/gui/uithememanager.h b/src/gui/uithememanager.h index 3b3c18cde..fe5bc4738 100644 --- a/src/gui/uithememanager.h +++ b/src/gui/uithememanager.h @@ -70,13 +70,16 @@ public: private: UIThemeManager(); // singleton class - Path getIconPathFromResources(const QString &iconId, const QString &fallback = {}) const; + Path getIconPathFromResources(const QString &iconId) const; void loadColorsFromJSONConfig(); void applyPalette() const; void applyStyleSheet() const; static UIThemeManager *m_instance; const bool m_useCustomTheme; +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) + const bool m_useSystemIcons; +#endif std::unique_ptr m_themeSource; QHash m_colors; mutable QHash m_iconCache; From c78ac614f5bc8a400804619f1e0a7d304d5f40e8 Mon Sep 17 00:00:00 2001 From: brvphoenix <30111323+brvphoenix@users.noreply.github.com> Date: Sun, 25 Dec 2022 21:44:37 +0800 Subject: [PATCH 06/14] Unify the way to generate the language list in WebUI and GUI PR #17994. Closes #18090. --- src/base/utils/misc.cpp | 90 ++++++++++++++++++ src/base/utils/misc.h | 2 + src/gui/optionsdialog.cpp | 98 +------------------- src/webui/webapplication.cpp | 25 ++++- src/webui/www/private/views/preferences.html | 72 +++----------- 5 files changed, 133 insertions(+), 154 deletions(-) diff --git a/src/base/utils/misc.cpp b/src/base/utils/misc.cpp index 01d81e178..e9803f36c 100644 --- a/src/base/utils/misc.cpp +++ b/src/base/utils/misc.cpp @@ -53,6 +53,8 @@ #include #include +#include +#include #include #include #include @@ -403,6 +405,94 @@ QString Utils::Misc::getUserIDString() return uid; } +QString Utils::Misc::languageToLocalizedString(const QString &localeStr) +{ + if (localeStr.startsWith(u"eo", Qt::CaseInsensitive)) + { + // QLocale doesn't work with that locale. Esperanto isn't a "real" language. + return C_LOCALE_ESPERANTO; + } + + if (localeStr.startsWith(u"ltg", Qt::CaseInsensitive)) + { + // QLocale doesn't work with that locale. + return C_LOCALE_LATGALIAN; + } + + const QLocale locale {localeStr}; + switch (locale.language()) + { + case QLocale::Arabic: return C_LOCALE_ARABIC; + case QLocale::Armenian: return C_LOCALE_ARMENIAN; + case QLocale::Azerbaijani: return C_LOCALE_AZERBAIJANI; + case QLocale::Basque: return C_LOCALE_BASQUE; + case QLocale::Bulgarian: return C_LOCALE_BULGARIAN; + case QLocale::Byelorussian: return C_LOCALE_BYELORUSSIAN; + case QLocale::Catalan: return C_LOCALE_CATALAN; + case QLocale::Chinese: + switch (locale.country()) + { + case QLocale::China: return C_LOCALE_CHINESE_SIMPLIFIED; + case QLocale::HongKong: return C_LOCALE_CHINESE_TRADITIONAL_HK; + default: return C_LOCALE_CHINESE_TRADITIONAL_TW; + } + case QLocale::Croatian: return C_LOCALE_CROATIAN; + case QLocale::Czech: return C_LOCALE_CZECH; + case QLocale::Danish: return C_LOCALE_DANISH; + case QLocale::Dutch: return C_LOCALE_DUTCH; + case QLocale::English: + switch (locale.country()) + { + case QLocale::Australia: return C_LOCALE_ENGLISH_AUSTRALIA; + case QLocale::UnitedKingdom: return C_LOCALE_ENGLISH_UNITEDKINGDOM; + default: return C_LOCALE_ENGLISH; + } + case QLocale::Estonian: return C_LOCALE_ESTONIAN; + case QLocale::Finnish: return C_LOCALE_FINNISH; + case QLocale::French: return C_LOCALE_FRENCH; + case QLocale::Galician: return C_LOCALE_GALICIAN; + case QLocale::Georgian: return C_LOCALE_GEORGIAN; + case QLocale::German: return C_LOCALE_GERMAN; + case QLocale::Greek: return C_LOCALE_GREEK; + case QLocale::Hebrew: return C_LOCALE_HEBREW; + case QLocale::Hindi: return C_LOCALE_HINDI; + case QLocale::Hungarian: return C_LOCALE_HUNGARIAN; + case QLocale::Icelandic: return C_LOCALE_ICELANDIC; + case QLocale::Indonesian: return C_LOCALE_INDONESIAN; + case QLocale::Italian: return C_LOCALE_ITALIAN; + case QLocale::Japanese: return C_LOCALE_JAPANESE; + case QLocale::Korean: return C_LOCALE_KOREAN; + case QLocale::Latvian: return C_LOCALE_LATVIAN; + case QLocale::Lithuanian: return C_LOCALE_LITHUANIAN; + case QLocale::Malay: return C_LOCALE_MALAY; + case QLocale::Mongolian: return C_LOCALE_MONGOLIAN; + case QLocale::NorwegianBokmal: return C_LOCALE_NORWEGIAN; + case QLocale::Occitan: return C_LOCALE_OCCITAN; + case QLocale::Persian: return C_LOCALE_PERSIAN; + case QLocale::Polish: return C_LOCALE_POLISH; + case QLocale::Portuguese: + if (locale.country() == QLocale::Brazil) + return C_LOCALE_PORTUGUESE_BRAZIL; + return C_LOCALE_PORTUGUESE; + case QLocale::Romanian: return C_LOCALE_ROMANIAN; + case QLocale::Russian: return C_LOCALE_RUSSIAN; + case QLocale::Serbian: return C_LOCALE_SERBIAN; + case QLocale::Slovak: return C_LOCALE_SLOVAK; + case QLocale::Slovenian: return C_LOCALE_SLOVENIAN; + case QLocale::Spanish: return C_LOCALE_SPANISH; + case QLocale::Swedish: return C_LOCALE_SWEDISH; + case QLocale::Thai: return C_LOCALE_THAI; + case QLocale::Turkish: return C_LOCALE_TURKISH; + case QLocale::Ukrainian: return C_LOCALE_UKRAINIAN; + case QLocale::Uzbek: return C_LOCALE_UZBEK; + case QLocale::Vietnamese: return C_LOCALE_VIETNAMESE; + default: + const QString lang = QLocale::languageToString(locale.language()); + qWarning() << "Unrecognized language name: " << lang; + return lang; + } +} + QString Utils::Misc::parseHtmlLinks(const QString &rawText) { QString result = rawText; diff --git a/src/base/utils/misc.h b/src/base/utils/misc.h index 95da08afd..fc1eac40d 100644 --- a/src/base/utils/misc.h +++ b/src/base/utils/misc.h @@ -85,6 +85,8 @@ namespace Utils::Misc QString userFriendlyDuration(qlonglong seconds, qlonglong maxCap = -1); QString getUserIDString(); + QString languageToLocalizedString(const QString &localeStr); + #ifdef Q_OS_WIN Path windowsSystemPath(); diff --git a/src/gui/optionsdialog.cpp b/src/gui/optionsdialog.cpp index c13146136..2ae1359e1 100644 --- a/src/gui/optionsdialog.cpp +++ b/src/gui/optionsdialog.cpp @@ -52,8 +52,8 @@ #include "base/rss/rss_session.h" #include "base/torrentfileguard.h" #include "base/torrentfileswatcher.h" -#include "base/unicodestrings.h" #include "base/utils/fs.h" +#include "base/utils/misc.h" #include "base/utils/net.h" #include "base/utils/password.h" #include "base/utils/random.h" @@ -89,81 +89,6 @@ namespace return ret; } - QString languageToLocalizedString(const QLocale &locale) - { - switch (locale.language()) - { - case QLocale::Arabic: return C_LOCALE_ARABIC; - case QLocale::Armenian: return C_LOCALE_ARMENIAN; - case QLocale::Azerbaijani: return C_LOCALE_AZERBAIJANI; - case QLocale::Basque: return C_LOCALE_BASQUE; - case QLocale::Bulgarian: return C_LOCALE_BULGARIAN; - case QLocale::Byelorussian: return C_LOCALE_BYELORUSSIAN; - case QLocale::Catalan: return C_LOCALE_CATALAN; - case QLocale::Chinese: - switch (locale.country()) - { - case QLocale::China: return C_LOCALE_CHINESE_SIMPLIFIED; - case QLocale::HongKong: return C_LOCALE_CHINESE_TRADITIONAL_HK; - default: return C_LOCALE_CHINESE_TRADITIONAL_TW; - } - case QLocale::Croatian: return C_LOCALE_CROATIAN; - case QLocale::Czech: return C_LOCALE_CZECH; - case QLocale::Danish: return C_LOCALE_DANISH; - case QLocale::Dutch: return C_LOCALE_DUTCH; - case QLocale::English: - switch (locale.country()) - { - case QLocale::Australia: return C_LOCALE_ENGLISH_AUSTRALIA; - case QLocale::UnitedKingdom: return C_LOCALE_ENGLISH_UNITEDKINGDOM; - default: return C_LOCALE_ENGLISH; - } - case QLocale::Estonian: return C_LOCALE_ESTONIAN; - case QLocale::Finnish: return C_LOCALE_FINNISH; - case QLocale::French: return C_LOCALE_FRENCH; - case QLocale::Galician: return C_LOCALE_GALICIAN; - case QLocale::Georgian: return C_LOCALE_GEORGIAN; - case QLocale::German: return C_LOCALE_GERMAN; - case QLocale::Greek: return C_LOCALE_GREEK; - case QLocale::Hebrew: return C_LOCALE_HEBREW; - case QLocale::Hindi: return C_LOCALE_HINDI; - case QLocale::Hungarian: return C_LOCALE_HUNGARIAN; - case QLocale::Icelandic: return C_LOCALE_ICELANDIC; - case QLocale::Indonesian: return C_LOCALE_INDONESIAN; - case QLocale::Italian: return C_LOCALE_ITALIAN; - case QLocale::Japanese: return C_LOCALE_JAPANESE; - case QLocale::Korean: return C_LOCALE_KOREAN; - case QLocale::Latvian: return C_LOCALE_LATVIAN; - case QLocale::Lithuanian: return C_LOCALE_LITHUANIAN; - case QLocale::Malay: return C_LOCALE_MALAY; - case QLocale::Mongolian: return C_LOCALE_MONGOLIAN; - case QLocale::NorwegianBokmal: return C_LOCALE_NORWEGIAN; - case QLocale::Occitan: return C_LOCALE_OCCITAN; - case QLocale::Persian: return C_LOCALE_PERSIAN; - case QLocale::Polish: return C_LOCALE_POLISH; - case QLocale::Portuguese: - if (locale.country() == QLocale::Brazil) - return C_LOCALE_PORTUGUESE_BRAZIL; - return C_LOCALE_PORTUGUESE; - case QLocale::Romanian: return C_LOCALE_ROMANIAN; - case QLocale::Russian: return C_LOCALE_RUSSIAN; - case QLocale::Serbian: return C_LOCALE_SERBIAN; - case QLocale::Slovak: return C_LOCALE_SLOVAK; - case QLocale::Slovenian: return C_LOCALE_SLOVENIAN; - case QLocale::Spanish: return C_LOCALE_SPANISH; - case QLocale::Swedish: return C_LOCALE_SWEDISH; - case QLocale::Thai: return C_LOCALE_THAI; - case QLocale::Turkish: return C_LOCALE_TURKISH; - case QLocale::Ukrainian: return C_LOCALE_UKRAINIAN; - case QLocale::Uzbek: return C_LOCALE_UZBEK; - case QLocale::Vietnamese: return C_LOCALE_VIETNAMESE; - default: - const QString lang = QLocale::languageToString(locale.language()); - qWarning() << "Unrecognized language name: " << lang; - return lang; - } - } - class WheelEventEater final : public QObject { public: @@ -1326,25 +1251,8 @@ void OptionsDialog::initializeLanguageCombo() const QStringList langFiles = langDir.entryList(QStringList(u"qbittorrent_*.qm"_qs), QDir::Files); for (const QString &langFile : langFiles) { - QString localeStr = langFile.mid(12); // remove "qbittorrent_" - localeStr.chop(3); // Remove ".qm" - QString languageName; - if (localeStr.startsWith(u"eo", Qt::CaseInsensitive)) - { - // QLocale doesn't work with that locale. Esperanto isn't a "real" language. - languageName = C_LOCALE_ESPERANTO; - } - else if (localeStr.startsWith(u"ltg", Qt::CaseInsensitive)) - { - // QLocale doesn't work with that locale. - languageName = C_LOCALE_LATGALIAN; - } - else - { - QLocale locale(localeStr); - languageName = languageToLocalizedString(locale); - } - m_ui->comboI18n->addItem(/*QIcon(":/icons/flags/"+country+".svg"), */ languageName, localeStr); + const QString localeStr = langFile.section(u"_"_qs, 1, -1).section(u"."_qs, 0, 0); // remove "qbittorrent_" and ".qm" + m_ui->comboI18n->addItem(/*QIcon(":/icons/flags/"+country+".svg"), */ Utils::Misc::languageToLocalizedString(localeStr), localeStr); qDebug() << "Supported locale:" << localeStr; } } diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index d35a48c87..f16e6e812 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -112,6 +113,22 @@ namespace return u"no-store"_qs; } + + QString createLanguagesOptionsHtml() + { + // List language files + const QDir langDir {u":/www/translations"_qs}; + const QStringList langFiles = langDir.entryList(QStringList(u"webui_*.qm"_qs), QDir::Files); + QStringList languages; + for (const QString &langFile : langFiles) + { + const QString localeStr = langFile.section(u"_"_qs, 1, -1).section(u"."_qs, 0, 0); // remove "webui_" and ".qm" + languages << u""_qs.arg(localeStr, Utils::Misc::languageToLocalizedString(localeStr)); + qDebug() << "Supported locale:" << localeStr; + } + + return languages.join(u'\n'); + } } WebApplication::WebApplication(IApplication *app, QObject *parent) @@ -472,13 +489,17 @@ void WebApplication::sendFile(const Path &path) const QMimeType mimeType = QMimeDatabase().mimeTypeForFileNameAndData(path.data(), data); const bool isTranslatable = !m_isAltUIUsed && mimeType.inherits(u"text/plain"_qs); - // Translate the file if (isTranslatable) { auto dataStr = QString::fromUtf8(data); + // Translate the file translateDocument(dataStr); - data = dataStr.toUtf8(); + // Add the language options + if (path == (m_rootFolder / Path(PRIVATE_FOLDER) / Path(u"views/preferences.html"_qs))) + dataStr.replace(u"${LANGUAGE_OPTIONS}"_qs, createLanguagesOptionsHtml()); + + data = dataStr.toUtf8(); m_translatedFiles[path] = {data, mimeType.name(), lastModified}; // caching translated file } diff --git a/src/webui/www/private/views/preferences.html b/src/webui/www/private/views/preferences.html index 78f1b42c5..22bddb687 100644 --- a/src/webui/www/private/views/preferences.html +++ b/src/webui/www/private/views/preferences.html @@ -666,62 +666,7 @@ QBT_TR(Language)QBT_TR[CONTEXT=OptionsDialog] @@ -1408,6 +1353,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD updateWebUICustomHTTPHeadersSettings: updateWebUICustomHTTPHeadersSettings, updateWebUIReverseProxySettings: updateWebUIReverseProxySettings, updateDynDnsSettings: updateDynDnsSettings, + updateWebuiLocaleSelect: updateWebuiLocaleSelect, registerDynDns: registerDynDns, applyPreferences: applyPreferences }; @@ -1737,6 +1683,18 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD }).send(); }; + const updateWebuiLocaleSelect = (selected) => { + let languages = []; + for (let i = 0; i < $('locale_select').options.length; i++) + languages.push($('locale_select').options[i].value); + + if (!languages.includes(selected)) { + const lang = selected.slice(0, selected.indexOf('_')); + selected = languages.includes(lang) ? lang : 'en'; + } + $('locale_select').setProperty('value', selected); + }; + const loadPreferences = function() { const url = 'api/v2/app/preferences'; new Request.JSON({ @@ -2013,7 +1971,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD // Web UI tab // Language - $('locale_select').setProperty('value', ((pref.locale === "en_US") ? "en" : pref.locale)); + updateWebuiLocaleSelect(pref.locale); $('performanceWarning').setProperty('checked', pref.performance_warning); // HTTP Server From fb22b58ce6dc81f328e33e74b3a8ff9cc1912599 Mon Sep 17 00:00:00 2001 From: Jason Carr <79025+jcarr@users.noreply.github.com> Date: Wed, 28 Dec 2022 05:20:02 -0500 Subject: [PATCH 07/14] WebUI: change order of accepted types of file input PR #18286. --- src/webui/www/private/upload.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webui/www/private/upload.html b/src/webui/www/private/upload.html index 32de2b00d..1a0974356 100644 --- a/src/webui/www/private/upload.html +++ b/src/webui/www/private/upload.html @@ -14,7 +14,7 @@
- +
From e0d0efcc203946d545fc17e4a74c61b313e557fb Mon Sep 17 00:00:00 2001 From: brvphoenix <30111323+brvphoenix@users.noreply.github.com> Date: Fri, 13 Jan 2023 15:52:53 +0800 Subject: [PATCH 08/14] WebUI: Add missing icons PR #18380. --- src/webui/www/private/images/folder-documents.svg | 1 + src/webui/www/private/images/task-reject.svg | 1 + src/webui/www/webui.qrc | 2 ++ 3 files changed, 4 insertions(+) create mode 100644 src/webui/www/private/images/folder-documents.svg create mode 100644 src/webui/www/private/images/task-reject.svg diff --git a/src/webui/www/private/images/folder-documents.svg b/src/webui/www/private/images/folder-documents.svg new file mode 100644 index 000000000..0b8d29c86 --- /dev/null +++ b/src/webui/www/private/images/folder-documents.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/webui/www/private/images/task-reject.svg b/src/webui/www/private/images/task-reject.svg new file mode 100644 index 000000000..bcd6364c0 --- /dev/null +++ b/src/webui/www/private/images/task-reject.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/webui/www/webui.qrc b/src/webui/www/webui.qrc index 4a6a5038d..597453128 100644 --- a/src/webui/www/webui.qrc +++ b/src/webui/www/webui.qrc @@ -309,6 +309,7 @@ private/images/flags/za.svgprivate/images/flags/zm.svgprivate/images/flags/zw.svg + private/images/folder-documents.svgprivate/images/folder-new.svgprivate/images/force-recheck.svgprivate/images/go-bottom.svg @@ -348,6 +349,7 @@ private/images/tabs.gifprivate/images/tags.svgprivate/images/task-complete.svg + private/images/task-reject.svgprivate/images/toolbox-divider.gifprivate/images/torrent-magnet.svgprivate/images/torrent-start-forced.svg From e3562be0d6c57acdb78fa15716274a5d6d30ef90 Mon Sep 17 00:00:00 2001 From: thalieht Date: Fri, 13 Jan 2023 09:58:46 +0200 Subject: [PATCH 09/14] WebUI: Add "Resume data storage type" option PR #18357. --- src/webui/api/appcontroller.cpp | 5 +++++ src/webui/webapplication.h | 2 +- src/webui/www/private/views/preferences.html | 13 +++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/webui/api/appcontroller.cpp b/src/webui/api/appcontroller.cpp index 0b82613c7..e0ddc7d5b 100644 --- a/src/webui/api/appcontroller.cpp +++ b/src/webui/api/appcontroller.cpp @@ -299,6 +299,8 @@ void AppController::preferencesAction() // Advanced settings // qBitorrent preferences + // Resume data storage type + data[u"resume_data_storage_type"_qs] = Utils::String::fromEnum(session->resumeDataStorageType()); // Physical memory (RAM) usage limit data[u"memory_working_set_limit"_qs] = app()->memoryWorkingSetLimit(); // Current network interface @@ -776,6 +778,9 @@ void AppController::setPreferencesAction() // Advanced settings // qBittorrent preferences + // Resume data storage type + if (hasKey(u"resume_data_storage_type"_qs)) + session->setResumeDataStorageType(Utils::String::toEnum(it.value().toString(), BitTorrent::ResumeDataStorageType::Legacy)); // Physical memory (RAM) usage limit if (hasKey(u"memory_working_set_limit"_qs)) app()->setMemoryWorkingSetLimit(it.value().toInt()); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 54a3f3c4e..bc55919b0 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -52,7 +52,7 @@ #include "base/utils/version.h" #include "api/isessionmanager.h" -inline const Utils::Version<3, 2> API_VERSION {2, 8, 18}; +inline const Utils::Version<3, 2> API_VERSION {2, 8, 19}; class APIController; class AuthController; diff --git a/src/webui/www/private/views/preferences.html b/src/webui/www/private/views/preferences.html index 22bddb687..3c3634431 100644 --- a/src/webui/www/private/views/preferences.html +++ b/src/webui/www/private/views/preferences.html @@ -879,6 +879,17 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
QBT_TR(qBittorrent Section)QBT_TR[CONTEXT=OptionsDialog] (QBT_TR(Open documentation)QBT_TR[CONTEXT=HttpServer])
+ + + +
+ + + +
@@ -2026,6 +2037,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD // Advanced settings // qBittorrent section + $('resumeDataStorageType').setProperty('value', pref.resume_data_storage_type); $('memoryWorkingSetLimit').setProperty('value', pref.memory_working_set_limit); updateNetworkInterfaces(pref.current_network_interface); updateInterfaceAddresses(pref.current_network_interface, pref.current_interface_address); @@ -2453,6 +2465,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD // Update advanced settings // qBittorrent section + settings.set('resume_data_storage_type', $('resumeDataStorageType').getProperty('value')); settings.set('memory_working_set_limit', $('memoryWorkingSetLimit').getProperty('value')); settings.set('current_network_interface', $('networkInterface').getProperty('value')); settings.set('current_interface_address', $('optionalIPAddressToBind').getProperty('value')); From 0b7c8497f9d6046bd5e1db90c4a681d5eadd6bef Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Mon, 16 Jan 2023 06:54:02 +0300 Subject: [PATCH 10/14] Add all torrents passed via the command line PR #18296. Closes #18289. --- src/app/application.cpp | 13 +++++++++---- src/app/application.h | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/app/application.cpp b/src/app/application.cpp index 4e4e1fc7a..5b3124a45 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -658,8 +658,7 @@ Application::AddTorrentParams Application::parseParams(const QStringList ¶ms continue; } - parsedParams.torrentSource = param; - break; + parsedParams.torrentSources.append(param); } return parsedParams; @@ -675,10 +674,16 @@ void Application::processParams(const AddTorrentParams ¶ms) // should be overridden. const bool showDialogForThisTorrent = !params.skipTorrentDialog.value_or(!AddNewTorrentDialog::isEnabled()); if (showDialogForThisTorrent) - AddNewTorrentDialog::show(params.torrentSource, params.torrentParams, m_window); + { + for (const QString &torrentSource : params.torrentSources) + AddNewTorrentDialog::show(torrentSource, params.torrentParams, m_window); + } else #endif - BitTorrent::Session::instance()->addTorrent(params.torrentSource, params.torrentParams); + { + for (const QString &torrentSource : params.torrentSources) + BitTorrent::Session::instance()->addTorrent(torrentSource, params.torrentParams); + } } int Application::exec(const QStringList ¶ms) diff --git a/src/app/application.h b/src/app/application.h index d8801face..21f1dc132 100644 --- a/src/app/application.h +++ b/src/app/application.h @@ -148,7 +148,7 @@ private slots: private: struct AddTorrentParams { - QString torrentSource; + QStringList torrentSources; BitTorrent::AddTorrentParams torrentParams; std::optional skipTorrentDialog; }; From daaa88fa0daf353f6d8603bf3fe4ed0f0b97ba09 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Mon, 16 Jan 2023 14:31:49 +0300 Subject: [PATCH 11/14] Use QThreadPool to invoke free disk space checking jobs Prevent the creation of an excessive number of threads. PR #18347. Closes #18202. --- src/webui/api/freediskspacechecker.h | 4 +-- src/webui/api/synccontroller.cpp | 42 +++++++++++++--------------- src/webui/api/synccontroller.h | 7 ++--- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/src/webui/api/freediskspacechecker.h b/src/webui/api/freediskspacechecker.h index 092032bfc..a472f35cc 100644 --- a/src/webui/api/freediskspacechecker.h +++ b/src/webui/api/freediskspacechecker.h @@ -30,13 +30,13 @@ #include -class FreeDiskSpaceChecker : public QObject +class FreeDiskSpaceChecker final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(FreeDiskSpaceChecker) public: - FreeDiskSpaceChecker() = default; + using QObject::QObject; public slots: void check(); diff --git a/src/webui/api/synccontroller.cpp b/src/webui/api/synccontroller.cpp index f3b6c160b..1473d4cd6 100644 --- a/src/webui/api/synccontroller.cpp +++ b/src/webui/api/synccontroller.cpp @@ -32,7 +32,7 @@ #include #include -#include +#include #include "base/bittorrent/cachestatus.h" #include "base/bittorrent/infohash.h" @@ -49,7 +49,6 @@ #include "base/utils/string.h" #include "apierror.h" #include "freediskspacechecker.h" -#include "isessionmanager.h" #include "serialize/serialize_torrent.h" namespace @@ -177,7 +176,7 @@ namespace switch (static_cast(value.type())) { case QMetaType::QVariantMap: - { + { QVariantMap map; processMap(prevData[key].toMap(), value.toMap(), map); if (!map.isEmpty()) @@ -185,7 +184,7 @@ namespace } break; case QMetaType::QVariantHash: - { + { QVariantMap map; processHash(prevData[key].toHash(), value.toHash(), map, removedItems); if (!map.isEmpty()) @@ -195,7 +194,7 @@ namespace } break; case QMetaType::QVariantList: - { + { QVariantList list; processList(prevData[key].toList(), value.toList(), list, removedItems); if (!list.isEmpty()) @@ -372,15 +371,7 @@ namespace SyncController::SyncController(IApplication *app, QObject *parent) : APIController(app, parent) - , m_freeDiskSpaceChecker {new FreeDiskSpaceChecker} - , m_freeDiskSpaceThread {new QThread} { - m_freeDiskSpaceChecker->moveToThread(m_freeDiskSpaceThread.get()); - - connect(m_freeDiskSpaceThread.get(), &QThread::finished, m_freeDiskSpaceChecker, &QObject::deleteLater); - connect(m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::checked, this, &SyncController::freeDiskSpaceSizeUpdated); - - m_freeDiskSpaceThread->start(); invokeChecker(); m_freeDiskSpaceElapsedTimer.start(); } @@ -595,20 +586,27 @@ void SyncController::torrentPeersAction() qint64 SyncController::getFreeDiskSpace() { if (m_freeDiskSpaceElapsedTimer.hasExpired(FREEDISKSPACE_CHECK_TIMEOUT)) - { invokeChecker(); - m_freeDiskSpaceElapsedTimer.restart(); - } return m_freeDiskSpace; } -void SyncController::freeDiskSpaceSizeUpdated(qint64 freeSpaceSize) +void SyncController::invokeChecker() { - m_freeDiskSpace = freeSpaceSize; -} + if (m_isFreeDiskSpaceCheckerRunning) + return; -void SyncController::invokeChecker() const -{ - QMetaObject::invokeMethod(m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::check, Qt::QueuedConnection); + auto *freeDiskSpaceChecker = new FreeDiskSpaceChecker; + connect(freeDiskSpaceChecker, &FreeDiskSpaceChecker::checked, this, [this](const qint64 freeSpaceSize) + { + m_freeDiskSpace = freeSpaceSize; + m_isFreeDiskSpaceCheckerRunning = false; + m_freeDiskSpaceElapsedTimer.restart(); + }); + connect(freeDiskSpaceChecker, &FreeDiskSpaceChecker::checked, freeDiskSpaceChecker, &QObject::deleteLater); + m_isFreeDiskSpaceCheckerRunning = true; + QThreadPool::globalInstance()->start([freeDiskSpaceChecker] + { + freeDiskSpaceChecker->check(); + }); } diff --git a/src/webui/api/synccontroller.h b/src/webui/api/synccontroller.h index aae77c36f..9d9a4014f 100644 --- a/src/webui/api/synccontroller.h +++ b/src/webui/api/synccontroller.h @@ -31,7 +31,6 @@ #include #include -#include "base/utils/thread.h" #include "apicontroller.h" class QThread; @@ -51,16 +50,14 @@ public: private slots: void maindataAction(); void torrentPeersAction(); - void freeDiskSpaceSizeUpdated(qint64 freeSpaceSize); private: qint64 getFreeDiskSpace(); - void invokeChecker() const; + void invokeChecker(); qint64 m_freeDiskSpace = 0; - FreeDiskSpaceChecker *m_freeDiskSpaceChecker = nullptr; - Utils::Thread::UniquePtr m_freeDiskSpaceThread; QElapsedTimer m_freeDiskSpaceElapsedTimer; + bool m_isFreeDiskSpaceCheckerRunning = false; QVariantMap m_lastMaindataResponse; QVariantMap m_lastAcceptedMaindataResponse; From 7144454a1fa28ef0927108ea9df081e518ea017e Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Mon, 16 Jan 2023 14:43:36 +0300 Subject: [PATCH 12/14] Remove confusing helpers from Session interface Such helpers do not make practical sense, since they can be trivially implemented on top of the base interface, but at the same time they can lead to undesirable consequences when some calling code requires slightly different behavior than another. PR #18367. Fixes #18338. --- src/base/bittorrent/session.h | 3 --- src/base/bittorrent/sessionimpl.cpp | 30 +++++------------------------ src/base/bittorrent/sessionimpl.h | 3 --- src/gui/mainwindow.cpp | 20 ++++++++++++++++--- 4 files changed, 22 insertions(+), 34 deletions(-) diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index 981917dd4..79b3f47b4 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -403,9 +403,6 @@ namespace BitTorrent virtual Torrent *findTorrent(const InfoHash &infoHash) const = 0; virtual QVector torrents() const = 0; virtual qsizetype torrentsCount() const = 0; - virtual bool hasActiveTorrents() const = 0; - virtual bool hasUnfinishedTorrents() const = 0; - virtual bool hasRunningSeed() const = 0; virtual const SessionStatus &status() const = 0; virtual const CacheStatus &cacheStatus() const = 0; virtual bool isListening() const = 0; diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index 96def934e..75feae1cb 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -2190,30 +2190,6 @@ Torrent *SessionImpl::findTorrent(const InfoHash &infoHash) const return m_torrents.value(altID); } -bool SessionImpl::hasActiveTorrents() const -{ - return std::any_of(m_torrents.begin(), m_torrents.end(), [](TorrentImpl *torrent) - { - return TorrentFilter::ActiveTorrent.match(torrent); - }); -} - -bool SessionImpl::hasUnfinishedTorrents() const -{ - return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent) - { - return (!torrent->isSeed() && !torrent->isPaused() && !torrent->isErrored() && torrent->hasMetadata()); - }); -} - -bool SessionImpl::hasRunningSeed() const -{ - return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent) - { - return (torrent->isSeed() && !torrent->isPaused()); - }); -} - void SessionImpl::banIP(const QString &ip) { QStringList bannedIPs = m_bannedIPs; @@ -4651,7 +4627,11 @@ void SessionImpl::handleTorrentFinished(TorrentImpl *const torrent) } } - if (!hasUnfinishedTorrents()) + const bool hasUnfinishedTorrents = std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent) + { + return !(torrent->isSeed() || torrent->isPaused() || torrent->isErrored()); + }); + if (!hasUnfinishedTorrents) emit allTorrentsFinished(); } diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index d9bbc1a7c..3d0c3b08d 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -376,9 +376,6 @@ namespace BitTorrent Torrent *findTorrent(const InfoHash &infoHash) const override; QVector torrents() const override; qsizetype torrentsCount() const override; - bool hasActiveTorrents() const override; - bool hasUnfinishedTorrents() const override; - bool hasRunningSeed() const override; const SessionStatus &status() const override; const CacheStatus &cacheStatus() const override; bool isListening() const override; diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 636cf78c7..2150b6f1b 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -1138,7 +1138,12 @@ void MainWindow::closeEvent(QCloseEvent *e) } #endif // Q_OS_MACOS - if (pref->confirmOnExit() && BitTorrent::Session::instance()->hasActiveTorrents()) + const QVector allTorrents = BitTorrent::Session::instance()->torrents(); + const bool hasActiveTorrents = std::any_of(allTorrents.cbegin(), allTorrents.cend(), [](BitTorrent::Torrent *torrent) + { + return torrent->isActive(); + }); + if (pref->confirmOnExit() && hasActiveTorrents) { if (e->spontaneous() || m_forceExit) { @@ -1902,8 +1907,17 @@ void MainWindow::on_actionAutoShutdown_toggled(bool enabled) void MainWindow::updatePowerManagementState() { - const bool inhibitSuspend = (Preferences::instance()->preventFromSuspendWhenDownloading() && BitTorrent::Session::instance()->hasUnfinishedTorrents()) - || (Preferences::instance()->preventFromSuspendWhenSeeding() && BitTorrent::Session::instance()->hasRunningSeed()); + const QVector allTorrents = BitTorrent::Session::instance()->torrents(); + const bool hasUnfinishedTorrents = std::any_of(allTorrents.cbegin(), allTorrents.cend(), [](const BitTorrent::Torrent *torrent) + { + return (!torrent->isSeed() && !torrent->isPaused() && !torrent->isErrored() && torrent->hasMetadata()); + }); + const bool hasRunningSeed = std::any_of(allTorrents.cbegin(), allTorrents.cend(), [](const BitTorrent::Torrent *torrent) + { + return (torrent->isSeed() && !torrent->isPaused()); + }); + const bool inhibitSuspend = (Preferences::instance()->preventFromSuspendWhenDownloading() && hasUnfinishedTorrents) + || (Preferences::instance()->preventFromSuspendWhenSeeding() && hasRunningSeed); m_pwr->setActivityState(inhibitSuspend); } From c3abe4c2a67c7689c454add9581b167b20eab705 Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Mon, 16 Jan 2023 14:45:12 +0300 Subject: [PATCH 13/14] Fix startup performance on Qt5 Use more appropriate container (QList) for resume data queue buffer. QVector in Qt5 has poor performance of the first element taking operation, which is used to process the resume data queue. In Qt6, QVector is just an alias for QList, so there was no problem there. PR #18387. Fixes #18341. --- src/base/bittorrent/resumedatastorage.cpp | 5 +++-- src/base/bittorrent/resumedatastorage.h | 6 +++--- src/base/bittorrent/sessionimpl.cpp | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/base/bittorrent/resumedatastorage.cpp b/src/base/bittorrent/resumedatastorage.cpp index 0b640a95e..6e67a5b58 100644 --- a/src/base/bittorrent/resumedatastorage.cpp +++ b/src/base/bittorrent/resumedatastorage.cpp @@ -33,6 +33,7 @@ #include #include #include +#include const int TORRENTIDLIST_TYPEID = qRegisterMetaType>(); @@ -59,11 +60,11 @@ void BitTorrent::ResumeDataStorage::loadAll() const loadingThread->start(); } -QVector BitTorrent::ResumeDataStorage::fetchLoadedResumeData() const +QList BitTorrent::ResumeDataStorage::fetchLoadedResumeData() const { const QMutexLocker locker {&m_loadedResumeDataMutex}; - const QVector loadedResumeData = m_loadedResumeData; + const QList loadedResumeData = m_loadedResumeData; m_loadedResumeData.clear(); return loadedResumeData; diff --git a/src/base/bittorrent/resumedatastorage.h b/src/base/bittorrent/resumedatastorage.h index 3faf63494..b583da60a 100644 --- a/src/base/bittorrent/resumedatastorage.h +++ b/src/base/bittorrent/resumedatastorage.h @@ -29,9 +29,9 @@ #pragma once #include +#include #include #include -#include #include "base/3rdparty/expected.hpp" #include "base/path.h" @@ -65,7 +65,7 @@ namespace BitTorrent virtual void storeQueue(const QVector &queue) const = 0; void loadAll() const; - QVector fetchLoadedResumeData() const; + QList fetchLoadedResumeData() const; signals: void loadStarted(const QVector &torrents); @@ -78,7 +78,7 @@ namespace BitTorrent virtual void doLoadAll() const = 0; const Path m_path; - mutable QVector m_loadedResumeData; + mutable QList m_loadedResumeData; mutable QMutex m_loadedResumeDataMutex; }; } diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index 75feae1cb..25a8adfa5 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -322,7 +322,7 @@ struct BitTorrent::SessionImpl::ResumeSessionContext final : public QObject ResumeDataStorage *startupStorage = nullptr; ResumeDataStorageType currentStorageType = ResumeDataStorageType::Legacy; - QVector loadedResumeData; + QList loadedResumeData; int processingResumeDataCount = 0; int64_t totalResumeDataCount = 0; int64_t finishedResumeDataCount = 0; From 904bcc14d5220769b2b13bd9941567cab839873e Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Sun, 22 Jan 2023 16:48:58 +0300 Subject: [PATCH 14/14] Reload system tray icon to replace menu PR #18250. Closes #18074. --- src/app/application.cpp | 3 --- src/gui/desktopintegration.cpp | 32 +++++++++++++++++++++++++++++++- src/gui/desktopintegration.h | 1 + src/gui/mainwindow.cpp | 3 +-- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/app/application.cpp b/src/app/application.cpp index 5b3124a45..988543098 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -793,12 +793,9 @@ try }); disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog); - // we must not delete menu while it is used by DesktopIntegration - auto *oldMenu = m_desktopIntegration->menu(); const MainWindow::State windowState = (!m_startupProgressDialog || (m_startupProgressDialog->windowState() & Qt::WindowMinimized)) ? MainWindow::Minimized : MainWindow::Normal; m_window = new MainWindow(this, windowState); - delete oldMenu; delete m_startupProgressDialog; #ifdef Q_OS_WIN auto *pref = Preferences::instance(); diff --git a/src/gui/desktopintegration.cpp b/src/gui/desktopintegration.cpp index ee0950924..ae28c494f 100644 --- a/src/gui/desktopintegration.cpp +++ b/src/gui/desktopintegration.cpp @@ -99,6 +99,12 @@ DesktopIntegration::DesktopIntegration(QObject *parent) connect(Preferences::instance(), &Preferences::changed, this, &DesktopIntegration::onPreferencesChanged); } +DesktopIntegration::~DesktopIntegration() +{ + if (m_menu) + delete m_menu; +} + bool DesktopIntegration::isActive() const { #ifdef Q_OS_MACOS @@ -135,12 +141,36 @@ void DesktopIntegration::setMenu(QMenu *menu) if (menu == m_menu) return; +#if defined Q_OS_MACOS + if (m_menu) + delete m_menu; + m_menu = menu; -#ifdef Q_OS_MACOS if (m_menu) m_menu->setAsDockMenu(); +#elif defined Q_OS_UNIX + const bool systemTrayEnabled = m_systrayIcon; + if (m_menu) + { + if (m_systrayIcon) + { + delete m_systrayIcon; + m_systrayIcon = nullptr; + } + delete m_menu; + } + + m_menu = menu; + + if (systemTrayEnabled && !m_systrayIcon) + createTrayIcon(); #else + if (m_menu) + delete m_menu; + + m_menu = menu; + if (m_systrayIcon) m_systrayIcon->setContextMenu(m_menu); #endif diff --git a/src/gui/desktopintegration.h b/src/gui/desktopintegration.h index 2c2b7f9a3..726165c96 100644 --- a/src/gui/desktopintegration.h +++ b/src/gui/desktopintegration.h @@ -49,6 +49,7 @@ class DesktopIntegration final : public QObject public: explicit DesktopIntegration(QObject *parent = nullptr); + ~DesktopIntegration() override; bool isActive() const; diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 2150b6f1b..ad781f3a9 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -463,7 +463,6 @@ MainWindow::MainWindow(IGUIApplication *app, const State initialState) MainWindow::~MainWindow() { - app()->desktopIntegration()->setMenu(nullptr); delete m_ui; } @@ -1573,7 +1572,7 @@ void MainWindow::downloadFromURLList(const QStringList &urlList) QMenu *MainWindow::createDesktopIntegrationMenu() { - auto *menu = new QMenu(this); + auto *menu = new QMenu; #ifndef Q_OS_MACOS connect(menu, &QMenu::aboutToShow, this, [this]()