Merge pull request #18271 from glassez/v4.5

Backport changes to v4.5.x branch
This commit is contained in:
Vladimir Golovnev 2023-01-25 09:06:23 +03:00 committed by GitHub
commit c924904308
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 511 additions and 409 deletions

View file

@ -658,8 +658,7 @@ Application::AddTorrentParams Application::parseParams(const QStringList &params
continue;
}
parsedParams.torrentSource = param;
break;
parsedParams.torrentSources.append(param);
}
return parsedParams;
@ -675,10 +674,16 @@ void Application::processParams(const AddTorrentParams &params)
// 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 &params)
@ -788,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();

View file

@ -148,7 +148,7 @@ private slots:
private:
struct AddTorrentParams
{
QString torrentSource;
QStringList torrentSources;
BitTorrent::AddTorrentParams torrentParams;
std::optional<bool> skipTorrentDialog;
};

View file

@ -33,6 +33,7 @@
#include <QMetaObject>
#include <QMutexLocker>
#include <QThread>
#include <QVector>
const int TORRENTIDLIST_TYPEID = qRegisterMetaType<QVector<BitTorrent::TorrentID>>();
@ -59,11 +60,11 @@ void BitTorrent::ResumeDataStorage::loadAll() const
loadingThread->start();
}
QVector<BitTorrent::LoadedResumeData> BitTorrent::ResumeDataStorage::fetchLoadedResumeData() const
QList<BitTorrent::LoadedResumeData> BitTorrent::ResumeDataStorage::fetchLoadedResumeData() const
{
const QMutexLocker locker {&m_loadedResumeDataMutex};
const QVector<BitTorrent::LoadedResumeData> loadedResumeData = m_loadedResumeData;
const QList<BitTorrent::LoadedResumeData> loadedResumeData = m_loadedResumeData;
m_loadedResumeData.clear();
return loadedResumeData;

View file

@ -29,9 +29,9 @@
#pragma once
#include <QtContainerFwd>
#include <QList>
#include <QMutex>
#include <QObject>
#include <QVector>
#include "base/3rdparty/expected.hpp"
#include "base/path.h"
@ -65,7 +65,7 @@ namespace BitTorrent
virtual void storeQueue(const QVector<TorrentID> &queue) const = 0;
void loadAll() const;
QVector<LoadedResumeData> fetchLoadedResumeData() const;
QList<LoadedResumeData> fetchLoadedResumeData() const;
signals:
void loadStarted(const QVector<BitTorrent::TorrentID> &torrents);
@ -78,7 +78,7 @@ namespace BitTorrent
virtual void doLoadAll() const = 0;
const Path m_path;
mutable QVector<LoadedResumeData> m_loadedResumeData;
mutable QList<LoadedResumeData> m_loadedResumeData;
mutable QMutex m_loadedResumeDataMutex;
};
}

View file

@ -403,9 +403,6 @@ namespace BitTorrent
virtual Torrent *findTorrent(const InfoHash &infoHash) const = 0;
virtual QVector<Torrent *> 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;

View file

@ -322,7 +322,7 @@ struct BitTorrent::SessionImpl::ResumeSessionContext final : public QObject
ResumeDataStorage *startupStorage = nullptr;
ResumeDataStorageType currentStorageType = ResumeDataStorageType::Legacy;
QVector<LoadedResumeData> loadedResumeData;
QList<LoadedResumeData> loadedResumeData;
int processingResumeDataCount = 0;
int64_t totalResumeDataCount = 0;
int64_t finishedResumeDataCount = 0;
@ -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;
@ -2811,6 +2787,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<std::size_t>(m_additionalTrackerList.size()));
p.tracker_tiers.reserve(p.trackers.size() + static_cast<std::size_t>(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())
@ -4638,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();
}

View file

@ -376,9 +376,6 @@ namespace BitTorrent
Torrent *findTorrent(const InfoHash &infoHash) const override;
QVector<Torrent *> 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;

View file

@ -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);

View file

@ -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

View file

@ -53,6 +53,8 @@
#include <zlib.h>
#include <QCoreApplication>
#include <QDebug>
#include <QLocale>
#include <QMimeDatabase>
#include <QRegularExpression>
#include <QSet>
@ -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;

View file

@ -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();

View file

@ -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()
@ -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))
@ -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);
}
}

View file

@ -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);

View file

@ -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

View file

@ -49,6 +49,7 @@ class DesktopIntegration final : public QObject
public:
explicit DesktopIntegration(QObject *parent = nullptr);
~DesktopIntegration() override;
bool isActive() const;

View file

@ -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
}

View file

@ -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));
@ -464,7 +463,6 @@ MainWindow::MainWindow(IGUIApplication *app, const State initialState)
MainWindow::~MainWindow()
{
app()->desktopIntegration()->setMenu(nullptr);
delete m_ui;
}
@ -1139,7 +1137,12 @@ void MainWindow::closeEvent(QCloseEvent *e)
}
#endif // Q_OS_MACOS
if (pref->confirmOnExit() && BitTorrent::Session::instance()->hasActiveTorrents())
const QVector<BitTorrent::Torrent *> 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)
{
@ -1569,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]()
@ -1903,8 +1906,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<BitTorrent::Torrent *> 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);
}

View file

@ -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:
@ -203,17 +128,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 +213,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 +312,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 +384,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());
@ -1315,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;
}
}

View file

@ -133,41 +133,18 @@
<string>Interface</string>
</property>
<layout class="QGridLayout" name="gridLayout_81">
<item row="3" column="0" colspan="3">
<widget class="QGroupBox" name="checkUseCustomTheme">
<property name="title">
<string>Use custom UI Theme</string>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_15">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="checkable">
<bool>true</bool>
<property name="text">
<string>Changing Interface settings requires application restart</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_18">
<item>
<widget class="QLabel" name="label_16">
<property name="text">
<string>UI Theme file:</string>
</property>
</widget>
</item>
<item>
<widget class="FileSystemPathLineEdit" name="customThemeFilePath" native="true"/>
</item>
</layout>
</widget>
</item>
<item row="1" column="2">
<spacer name="horizontalSpacer_111">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>200</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
@ -191,15 +168,45 @@
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_15">
<property name="font">
<font>
<italic>true</italic>
</font>
<item row="1" column="2">
<spacer name="horizontalSpacer_111">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>200</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0" colspan="3">
<widget class="QGroupBox" name="checkUseCustomTheme">
<property name="title">
<string>Use custom UI Theme</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_18">
<item>
<widget class="QLabel" name="label_16">
<property name="text">
<string>UI Theme file:</string>
</property>
</widget>
</item>
<item>
<widget class="FileSystemPathLineEdit" name="customThemeFilePath" native="true"/>
</item>
</layout>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="checkUseSystemIcon">
<property name="text">
<string>Changing Interface settings requires application restart</string>
<string>Use icons from system theme</string>
</property>
</widget>
</item>

View file

@ -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")

View file

@ -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);

View file

@ -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();

View file

@ -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);

View file

@ -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;

View file

@ -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));
}
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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());

View file

@ -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();

View file

@ -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);

View file

@ -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:

View file

@ -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);

View file

@ -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

View file

@ -29,13 +29,10 @@
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<widget class="FileSystemPathComboEdit" name="comboSavePath" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<item row="0" column="0">
<widget class="QLabel" name="labelCategoryName">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
@ -49,10 +46,13 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelCategoryName">
<property name="text">
<string>Name:</string>
<item row="1" column="1">
<widget class="FileSystemPathComboEdit" name="comboSavePath" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
@ -173,6 +173,12 @@
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>textCategoryName</tabstop>
<tabstop>comboSavePath</tabstop>
<tabstop>comboUseDownloadPath</tabstop>
<tabstop>comboDownloadPath</tabstop>
</tabstops>
<resources/>
<connections>
<connection>

View file

@ -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)}
{
}

View file

@ -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 QVector<BitTo
else
{
trackerItem = new QListWidgetItem();
trackerItem->setData(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);

View file

@ -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);

View file

@ -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<Qt::CaseInsensitive>());
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);

View file

@ -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);

View file

@ -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<UIThemeSource> m_themeSource;
QHash<QString, QColor> m_colors;
mutable QHash<QString, QIcon> m_iconCache;

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -1 +1 @@
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="5.2" transform="matrix(.96198992 0 0 .96361031 .608162 .582235)"><path d="m4.0469562 10.349429 5.7224138-6.2783812 5.722414 6.2783812m-5.722414 17.579469v-22.6021738" stroke="#6495ed"/><path d="m27.953043 21.650571-5.722413 6.278382-5.722414-6.278382m5.722414-17.5794689v22.6021739" stroke="#3cb371"/></g></svg>
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="5.00656"><path d="m10.005859 2.0019531a2.5032799 2.5032799 0 0 0 -2.5039059 2.5039063v16.5156246l-1.1484375-1.261718a2.5032799 2.5032799 0 0 0 -3.5371093-.166016 2.5032799 2.5032799 0 0 0 -.1660157 3.535156l5.5039063 6.050781a2.5035303 2.5035303 0 0 0 3.7031251 0l5.505859-6.050781a2.5032799 2.5032799 0 0 0 -.167969-3.535156 2.5032799 2.5032799 0 0 0 -3.535156.166016l-1.15039 1.263671v-16.5175776a2.5032799 2.5032799 0 0 0 -2.503907-2.5039063z" fill="#3cb371"/><path d="m21.994141 2.0019531a2.5035303 2.5035303 0 0 0 -1.851563.8183594l-5.505859 6.0507813a2.5032799 2.5032799 0 0 0 .167969 3.5351562 2.5032799 2.5032799 0 0 0 3.535156-.166016l1.15039-1.263671v16.517578a2.5032799 2.5032799 0 0 0 2.503907 2.503906 2.5032799 2.5032799 0 0 0 2.503906-2.503906v-16.515625l1.148437 1.261718a2.5032799 2.5032799 0 0 0 3.53711.166016 2.5032799 2.5032799 0 0 0 .166015-3.5351562l-5.503906-6.0507813a2.5035303 2.5035303 0 0 0 -1.851562-.8183594z" fill="#1e90ff"/></g></svg>

Before

Width:  |  Height:  |  Size: 470 B

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1 +1 @@
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><g stroke="#1e90ff"><path d="m26.502476 9.1481903-1.356007-1.5373535a12.290749 11.977843 0 0 0 -8.916781-3.7185561c-6.8057232 0-12.3273474 5.3810502-12.3273474 12.0135073 0 6.632457 5.5216242 12.013508 12.3273474 12.013508a12.333125 12.019138 0 0 0 11.625587-8.009005" fill="none" stroke-linecap="round" stroke-miterlimit="10" stroke-width="3.99096"/><path d="m29.861915 3.2711167v7.6925913a1.1518373 1.1130536 0 0 1 -1.15184 1.113053h-7.960634c-1.026576 0-1.540583-1.198619-.814926-1.899843l7.961356-7.6932866c.725658-.7026152 1.966044-.2045237 1.966044.7874853z" fill="#4285f4" stroke-width=".070767"/></g></svg>
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(1.0016046 0 0 .9993628 .090124 .104351)"><path d="m26.502476 9.1481903-1.356007-1.5373535a12.290749 11.977843 0 0 0 -8.916781-3.7185561c-6.8057232 0-12.3273474 5.3810502-12.3273474 12.0135073 0 6.632457 5.5216242 12.013508 12.3273474 12.013508a12.333125 12.019138 0 0 0 11.625587-8.009005" fill="none" stroke="#1e90ff" stroke-linecap="round" stroke-miterlimit="10" stroke-width="3.99096"/><path d="m29.861915 3.2711167v7.6925913a1.1518373 1.1130536 0 0 1 -1.15184 1.113053h-7.960634c-1.026576 0-1.540583-1.198619-.814926-1.899843l7.961356-7.6932866c.725658-.7026152 1.966044-.2045237 1.966044.7874853z" fill="#1e90ff"/></g></svg>

Before

Width:  |  Height:  |  Size: 697 B

After

Width:  |  Height:  |  Size: 733 B

View file

@ -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());

View file

@ -30,13 +30,13 @@
#include <QObject>
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();

View file

@ -32,7 +32,7 @@
#include <QJsonObject>
#include <QMetaObject>
#include <QThread>
#include <QThreadPool>
#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<QMetaType::Type>(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();
});
}

View file

@ -31,7 +31,6 @@
#include <QElapsedTimer>
#include <QVariantMap>
#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;

View file

@ -32,6 +32,7 @@
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonDocument>
@ -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"<option value=\"%1\">%2</option>"_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
}

View file

@ -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;

View file

@ -1 +1 @@
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="5.2" transform="matrix(.96198992 0 0 .96361031 .608162 .582235)"><path d="m4.0469562 10.349429 5.7224138-6.2783812 5.722414 6.2783812m-5.722414 17.579469v-22.6021738" stroke="#6495ed"/><path d="m27.953043 21.650571-5.722413 6.278382-5.722414-6.278382m5.722414-17.5794689v22.6021739" stroke="#3cb371"/></g></svg>
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="5.00656"><path d="m10.005859 2.0019531a2.5032799 2.5032799 0 0 0 -2.5039059 2.5039063v16.5156246l-1.1484375-1.261718a2.5032799 2.5032799 0 0 0 -3.5371093-.166016 2.5032799 2.5032799 0 0 0 -.1660157 3.535156l5.5039063 6.050781a2.5035303 2.5035303 0 0 0 3.7031251 0l5.505859-6.050781a2.5032799 2.5032799 0 0 0 -.167969-3.535156 2.5032799 2.5032799 0 0 0 -3.535156.166016l-1.15039 1.263671v-16.5175776a2.5032799 2.5032799 0 0 0 -2.503907-2.5039063z" fill="#3cb371"/><path d="m21.994141 2.0019531a2.5035303 2.5035303 0 0 0 -1.851563.8183594l-5.505859 6.0507813a2.5032799 2.5032799 0 0 0 .167969 3.5351562 2.5032799 2.5032799 0 0 0 3.535156-.166016l1.15039-1.263671v16.517578a2.5032799 2.5032799 0 0 0 2.503907 2.503906 2.5032799 2.5032799 0 0 0 2.503906-2.503906v-16.515625l1.148437 1.261718a2.5032799 2.5032799 0 0 0 3.53711.166016 2.5032799 2.5032799 0 0 0 .166015-3.5351562l-5.503906-6.0507813a2.5035303 2.5035303 0 0 0 -1.851562-.8183594z" fill="#1e90ff"/></g></svg>

Before

Width:  |  Height:  |  Size: 470 B

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1 @@
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><g fill="#1e90ff"><path d="m20.949219 13.083984-9.898438 9.898438c.555802.551364 1.200161 1.005584 1.90625 1.34375.165012.080142.333109.153763.503907.220703.045118.018.091118.033781.136718.050781.760655.238926 1.535072.444831 2.357422.431641.0149 0 .030022.003906.044922.003906.08443 0 .16632-.010672.25-.013672.136229-.00253.272354-.009051.408203-.019531.13051-.0122.258379-.033534.386719-.052734.840074-.172598 1.62762-.465682 2.33789-.865235.710271-.399553 1.343205-.903889 1.873047-1.498047.529843-.594157.957068-1.278743 1.25586-2.035156.298791-.756412.46921-1.586339.486328-2.472656v-.042969c0-.1096-.008672-.216009-.013672-.324219-.06609-.712616-.139954-1.410863-.423828-2.095703-.00642-.01956-.012931-.039093-.019531-.058593-.001-.003-.002937-.00386-.003907-.00586-.349361-.924496-.890527-1.764536-1.58789-2.464844z"/><path d="m2.7773438 2c-.4308891 0-.7773438.446-.7773438 1v3.921875 2.078125 20.078125c0 .5114.3190539.921875.7167969.921875h26.5664061c.397743 0 .716797-.410474.716797-.921875v-22.15625c0-.5113999-.319054-.921875-.716797-.921875h-12.726562c-.886457.034-1.316068-.264328-1.707032-.7363281l-1.849609-2.4199219c-.261411-.4404-.457783-.84375-.888672-.84375zm13.2226562 8.033203c1.0138 0 1.982426.19057 2.873047.535156.890621.344587 1.703238.843189 2.408203 1.464844l.375.34375c.11897.11897.23239.24879.34375.375.621655.704965 1.122211 1.519535 1.466797 2.410156.344586.890622.533203 1.857294.533203 2.871094 0 1.10457-.224084 2.158128-.628906 3.115235-.404822.957106-.990986 1.819111-1.714844 2.542968-.723858.723858-1.585862 1.308069-2.542969 1.712891s-2.008711.628906-3.113281.628906c-1.0138 0-1.982426-.188617-2.873047-.533203s-1.703238-.845142-2.408203-1.466797c-.219938-.213654-.510719-.451508-.71875-.71875-.6216549-.704965-1.1222107-1.517582-1.4667969-2.408203s-.5332031-1.859247-.5332031-2.873047c0-1.10457.2240844-2.156174.6289062-3.113281.4048219-.957107.9909863-1.819111 1.7148438-2.542969.723857-.723857 1.585862-1.310022 2.542969-1.714844s2.008711-.628906 3.113281-.628906z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1 @@
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><path d="m28.122773 8.9725973c-1.251859-2.1448373-2.95009-3.843056-5.094918-5.0949173-2.145273-1.2518613-4.487221-1.87768-7.027407-1.87768-2.539963 0-4.882582.6258187-7.0274094 1.87768-2.1450513 1.2516373-3.8432821 2.949856-5.0951397 5.0949173-1.2520819 2.1450427-1.8778989 4.4876717-1.8778989 7.0274027 0 2.539955.6260408 4.882136 1.8776749 7.027403 1.251858 2.144613 2.9500888 3.843056 5.0951399 5.094917 2.1450512 1.251861 4.4874462 1.87768 7.0274092 1.87768 2.539962 0 4.882579-.625819 7.027407-1.87768 2.144828-1.251637 3.843059-2.95008 5.094918-5.094917 1.251635-2.145043 1.877451-4.487672 1.877451-7.027403 0-2.539955-.625816-4.882584-1.877227-7.0274027zm-5.523614 10.3267547c.230907.230673.346582.50456.346582.820475 0 .32801-.115687.607693-.346582.838376l-1.640504 1.640725c-.230906.230907-.510364.34636-.838599.34636-.315929 0-.589794-.115547-.820253-.34636l-3.299579-3.299576-3.29958 3.299576c-.230907.230907-.504323.34636-.820252.34636-.328235 0-.607694-.115547-.8386-.34636l-1.6407277-1.640725c-.2309067-.230674-.3463584-.510366-.3463584-.838376 0-.315915.1154534-.589792.3463584-.820475l3.2995797-3.299352-3.2995797-3.299576c-.2309067-.230673-.3463584-.504336-.3463584-.820251 0-.328234.1154534-.607693.3463584-.8386l1.6407277-1.640501c.230907-.2309067.510365-.34636.8386-.34636.315929 0 .589346.1155467.820252.34636l3.29958 3.299352 3.299579-3.299352c.230459-.2309067.504324-.34636.820253-.34636.328235 0 .607693.1155467.838599.34636l1.640504 1.640501c.230907.230907.346582.510366.346582.8386 0 .315915-.115687.589568-.346582.820251l-3.299356 3.299576z" fill="#f00" stroke-width="2.333333"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -1 +1 @@
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><g stroke="#1e90ff"><path d="m26.502476 9.1481903-1.356007-1.5373535a12.290749 11.977843 0 0 0 -8.916781-3.7185561c-6.8057232 0-12.3273474 5.3810502-12.3273474 12.0135073 0 6.632457 5.5216242 12.013508 12.3273474 12.013508a12.333125 12.019138 0 0 0 11.625587-8.009005" fill="none" stroke-linecap="round" stroke-miterlimit="10" stroke-width="3.99096"/><path d="m29.861915 3.2711167v7.6925913a1.1518373 1.1130536 0 0 1 -1.15184 1.113053h-7.960634c-1.026576 0-1.540583-1.198619-.814926-1.899843l7.961356-7.6932866c.725658-.7026152 1.966044-.2045237 1.966044.7874853z" fill="#4285f4" stroke-width=".070767"/></g></svg>
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(1.0016046 0 0 .9993628 .090124 .104351)"><path d="m26.502476 9.1481903-1.356007-1.5373535a12.290749 11.977843 0 0 0 -8.916781-3.7185561c-6.8057232 0-12.3273474 5.3810502-12.3273474 12.0135073 0 6.632457 5.5216242 12.013508 12.3273474 12.013508a12.333125 12.019138 0 0 0 11.625587-8.009005" fill="none" stroke="#1e90ff" stroke-linecap="round" stroke-miterlimit="10" stroke-width="3.99096"/><path d="m29.861915 3.2711167v7.6925913a1.1518373 1.1130536 0 0 1 -1.15184 1.113053h-7.960634c-1.026576 0-1.540583-1.198619-.814926-1.899843l7.961356-7.6932866c.725658-.7026152 1.966044-.2045237 1.966044.7874853z" fill="#1e90ff"/></g></svg>

Before

Width:  |  Height:  |  Size: 697 B

After

Width:  |  Height:  |  Size: 733 B

View file

@ -14,7 +14,7 @@
<iframe id="upload_frame" name="upload_frame" class="invisible" src="about:blank"></iframe>
<form action="api/v2/torrents/add" enctype="multipart/form-data" method="post" id="uploadForm" style="text-align: center;" target="upload_frame" autocorrect="off" autocapitalize="none">
<div style="margin-top: 25px; display: inline-block; border: 1px solid lightgrey; border-radius: 4px;">
<input type="file" id="fileselect" accept="application/x-bittorrent, .torrent" name="fileselect[]" multiple />
<input type="file" id="fileselect" accept=".torrent, application/x-bittorrent" name="fileselect[]" multiple />
</div>
<fieldset class="settings" style="border: 0; text-align: left; margin-top: 12px;">
<table style="margin: auto;">

View file

@ -666,62 +666,7 @@
<legend>QBT_TR(Language)QBT_TR[CONTEXT=OptionsDialog]</legend>
<label for="locale_select">QBT_TR(User Interface Language:)QBT_TR[CONTEXT=OptionsDialog]</label>
<select id="locale_select">
<option value="ar_AE">عربي</option>
<option value="az@latin">Azərbaycan dili</option>
<option value="be_BY">Беларуская</option>
<option value="bg_BG">Български</option>
<option value="ca_ES">Català</option>
<option value="cs_CZ">Čeština</option>
<option value="da_DK">Dansk</option>
<option value="de_DE">Deutsch</option>
<option value="el_GR">Ελληνικά</option>
<option value="en">English</option>
<option value="en_AU">English (Australia)</option>
<option value="en_GB">English (United Kingdom)</option>
<option value="eo_EO">Esperanto</option>
<option value="es_ES">Español</option>
<option value="et">Eesti, eesti keel</option>
<option value="eu_ES">Euskara</option>
<option value="fa">فارسی</option>
<option value="fi_FI">Suomi</option>
<option value="fr_FR">Français</option>
<option value="gl_ES">Galego</option>
<option value="he_IL">עברית</option>
<option value="hi_IN">हिन्दी, हिंदी</option>
<option value="hr_HR">Hrvatski</option>
<option value="hu_HU">Magyar</option>
<option value="hy_AM">Հայերեն</option>
<option value="id">Bahasa Indonesia</option>
<option value="is">Íslenska</option>
<option value="it_IT">Italiano</option>
<option value="ja_JP">日本語</option>
<option value="ka_GE">ქართული</option>
<option value="ko_KR">한국어</option>
<option value="ltg">Latgalīšu volūda</option>
<option value="lt_LT">Lietuvių</option>
<option value="lv_LV">Latviešu valoda</option>
<option value="mn_MN">Монгол хэл</option>
<option value="ms_MY">بهاس ملايو</option>
<option value="nb_NO">Norsk</option>
<option value="nl_NL">Nederlands</option>
<option value="oc">lenga d'òc</option>
<option value="pl_PL">Polski</option>
<option value="pt_BR">Português brasileiro</option>
<option value="pt_PT">Português</option>
<option value="ro_RO">Română</option>
<option value="ru_RU">Русский</option>
<option value="sk_SK">Slovenčina</option>
<option value="sl_SI">Slovenščina</option>
<option value="sr_CS">Српски</option>
<option value="sv_SE">Svenska</option>
<option value="th">ไทย</option>
<option value="tr_TR">Türkçe</option>
<option value="uk_UA">Українська</option>
<option value="uz@Latn">أۇزبېك</option>
<option value="vi_VN">Tiếng Việt</option>
<option value="zh">简体中文</option>
<option value="zh_HK">香港正體字</option>
<option value="zh_TW">正體中文</option>
${LANGUAGE_OPTIONS}
</select>
</fieldset>
@ -934,6 +879,17 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
<fieldset class="settings">
<legend>QBT_TR(qBittorrent Section)QBT_TR[CONTEXT=OptionsDialog]&nbsp;(<a href="https://github.com/qbittorrent/qBittorrent/wiki/Explanation-of-Options-in-qBittorrent#Advanced" target="_blank">QBT_TR(Open documentation)QBT_TR[CONTEXT=HttpServer]</a>)</legend>
<table>
<tr>
<td>
<label for="resumeDataStorageType">QBT_TR(Resume data storage type (requires restart):)QBT_TR[CONTEXT=OptionsDialog]</label>
</td>
<td>
<select id="resumeDataStorageType" style="width: 15em;">
<option value="Legacy">QBT_TR(Fastresume files)QBT_TR[CONTEXT=OptionsDialog]</option>
<option value="SQLite">QBT_TR(SQLite database (experimental))QBT_TR[CONTEXT=OptionsDialog]</option>
</select>
</td>
</tr>
<tr>
<td>
<label for="memoryWorkingSetLimit">QBT_TR(Physical memory (RAM) usage limit (applied if libtorrent >= 2.0):)QBT_TR[CONTEXT=OptionsDialog]&nbsp;<a href="https://wikipedia.org/wiki/Working_set" target="_blank">(?)</a></label>
@ -1408,6 +1364,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 +1694,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 +1982,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
@ -2068,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);
@ -2495,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'));

View file

@ -309,6 +309,7 @@
<file>private/images/flags/za.svg</file>
<file>private/images/flags/zm.svg</file>
<file>private/images/flags/zw.svg</file>
<file>private/images/folder-documents.svg</file>
<file>private/images/folder-new.svg</file>
<file>private/images/force-recheck.svg</file>
<file>private/images/go-bottom.svg</file>
@ -348,6 +349,7 @@
<file>private/images/tabs.gif</file>
<file>private/images/tags.svg</file>
<file>private/images/task-complete.svg</file>
<file>private/images/task-reject.svg</file>
<file>private/images/toolbox-divider.gif</file>
<file>private/images/torrent-magnet.svg</file>
<file>private/images/torrent-start-forced.svg</file>