You've already forked qBittorrent
mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-10-26 06:12:17 +01:00
Compare commits
39 Commits
release-5.
...
release-5.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94136262a8 | ||
|
|
f52947e27e | ||
|
|
315e88aee9 | ||
|
|
9104351c89 | ||
|
|
e58b0a65d2 | ||
|
|
878d829904 | ||
|
|
063f77bc6c | ||
|
|
2a4077414f | ||
|
|
2a44253802 | ||
|
|
4712eba0dc | ||
|
|
983b7814aa | ||
|
|
e082a21751 | ||
|
|
7dd1d1bac8 | ||
|
|
49f57b1049 | ||
|
|
fbf68a0649 | ||
|
|
39229dc06a | ||
|
|
bb314e1555 | ||
|
|
a3a8b15828 | ||
|
|
b579afe1aa | ||
|
|
93096dba56 | ||
|
|
6379c33964 | ||
|
|
84372de675 | ||
|
|
403b7c7c35 | ||
|
|
b2fab43865 | ||
|
|
387821267f | ||
|
|
dd7ef8e934 | ||
|
|
cce295faeb | ||
|
|
db5479ea01 | ||
|
|
e1216c4c9a | ||
|
|
f4a0868426 | ||
|
|
59a5fcf7d0 | ||
|
|
f9a2b02a8d | ||
|
|
04f6a565f3 | ||
|
|
3e96048ee4 | ||
|
|
d4ccf3001c | ||
|
|
64506f16bd | ||
|
|
24a7a835af | ||
|
|
93b9bf9552 | ||
|
|
f4125601de |
2
.github/workflows/ci_windows.yaml
vendored
2
.github/workflows/ci_windows.yaml
vendored
@@ -95,7 +95,7 @@ jobs:
|
|||||||
- name: Install Qt
|
- name: Install Qt
|
||||||
uses: jurplel/install-qt-action@v4
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: "6.7.0"
|
version: "6.7.3"
|
||||||
archives: qtbase qtsvg qttools
|
archives: qtbase qtsvg qttools
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[main]
|
[main]
|
||||||
host = https://www.transifex.com
|
host = https://www.transifex.com
|
||||||
|
|
||||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_master]
|
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_v50x]
|
||||||
file_filter = src/lang/qbittorrent_<lang>.ts
|
file_filter = src/lang/qbittorrent_<lang>.ts
|
||||||
source_file = src/lang/qbittorrent_en.ts
|
source_file = src/lang/qbittorrent_en.ts
|
||||||
source_lang = en
|
source_lang = en
|
||||||
@@ -9,7 +9,7 @@ type = QT
|
|||||||
minimum_perc = 23
|
minimum_perc = 23
|
||||||
lang_map = pt: pt_PT, zh: zh_CN
|
lang_map = pt: pt_PT, zh: zh_CN
|
||||||
|
|
||||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_webui]
|
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_webui_v50x]
|
||||||
file_filter = src/webui/www/translations/webui_<lang>.ts
|
file_filter = src/webui/www/translations/webui_<lang>.ts
|
||||||
source_file = src/webui/www/translations/webui_en.ts
|
source_file = src/webui/www/translations/webui_en.ts
|
||||||
source_lang = en
|
source_lang = en
|
||||||
|
|||||||
28
Changelog
28
Changelog
@@ -1,4 +1,24 @@
|
|||||||
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
Mon Oct 28th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.1
|
||||||
|
- FEATURE: Add "Simple pread/pwrite" disk IO type (Hanabishi)
|
||||||
|
- BUGFIX: Don't ignore SSL errors (sledgehammer999)
|
||||||
|
- BUGFIX: Don't try to apply Mark-of-the-Web to nonexistent files (glassez)
|
||||||
|
- BUGFIX: Disable "Move to trash" option by default (glassez)
|
||||||
|
- BUGFIX: Disable the ability to create torrents with a piece size of 256MiB (stalkerok)
|
||||||
|
- BUGFIX: Allow to choose Qt style (glassez)
|
||||||
|
- BUGFIX: Always notify user about duplicate torrent (glassez)
|
||||||
|
- BUGFIX: Correctly handle "torrent finished after move" event (glassez)
|
||||||
|
- BUGFIX: Correctly apply filename filter when `!qB` extension is enabled (glassez)
|
||||||
|
- BUGFIX: Improve color scheme change detection (glassez)
|
||||||
|
- BUGFIX: Fix button state for SSL certificate check (Chocobo1)
|
||||||
|
- WEBUI: Fix CSS that results in hidden torrent list in some browsers (skomerko)
|
||||||
|
- WEBUI: Use proper text color to highlight items in all filter lists (skomerko)
|
||||||
|
- WEBUI: Fix 'rename files' dialog cannot be opened more than once (Chocobo1)
|
||||||
|
- WEBUI: Fix UI of Advanced Settings to show all settings (glassez)
|
||||||
|
- WEBUI: Free resources allocated by web session once it is destructed (dyseg)
|
||||||
|
- SEARCH: Import correct libraries (Chocobo1)
|
||||||
|
- OTHER: Sync flag icons with upstream (xavier2k6)
|
||||||
|
|
||||||
|
Sun Sep 29th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
||||||
- FEATURE: Support creating .torrent with larger piece size (Chocobo1)
|
- FEATURE: Support creating .torrent with larger piece size (Chocobo1)
|
||||||
- FEATURE: Improve tracker entries handling (glassez)
|
- FEATURE: Improve tracker entries handling (glassez)
|
||||||
- FEATURE: Add separate filter item for tracker errors (glassez)
|
- FEATURE: Add separate filter item for tracker errors (glassez)
|
||||||
@@ -34,6 +54,8 @@ Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
|||||||
- BUGFIX: Sanitize peer client names (Hanabishi)
|
- BUGFIX: Sanitize peer client names (Hanabishi)
|
||||||
- BUGFIX: Apply share limits immediately when torrent downloading is finished (glassez)
|
- BUGFIX: Apply share limits immediately when torrent downloading is finished (glassez)
|
||||||
- BUGFIX: Show download progress for folders with zero byte size as 100 instead of 0 (vikas_c)
|
- BUGFIX: Show download progress for folders with zero byte size as 100 instead of 0 (vikas_c)
|
||||||
|
- BUGFIX: Fix highlighted piece color (Prince Gupta)
|
||||||
|
- BUGFIX: Apply "merge trackers" logic regardless of way the torrent is added (glassez)
|
||||||
- WEBUI: Improve WebUI responsiveness (Chocobo1)
|
- WEBUI: Improve WebUI responsiveness (Chocobo1)
|
||||||
- WEBUI: Do not exit the app when WebUI has failed to start (Hanabishi)
|
- WEBUI: Do not exit the app when WebUI has failed to start (Hanabishi)
|
||||||
- WEBUI: Add `Moving` filter to side panel (xavier2k6)
|
- WEBUI: Add `Moving` filter to side panel (xavier2k6)
|
||||||
@@ -49,10 +71,12 @@ Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
|||||||
- WEBUI: Improve table scrolling and selection on mobile (Thomas Piccirello)
|
- WEBUI: Improve table scrolling and selection on mobile (Thomas Piccirello)
|
||||||
- WEBUI: Restore search tabs on load (Thomas Piccirello)
|
- WEBUI: Restore search tabs on load (Thomas Piccirello)
|
||||||
- WEBUI: Restore previously used tab on load (Thomas Piccirello)
|
- WEBUI: Restore previously used tab on load (Thomas Piccirello)
|
||||||
- WEBUI: Increase default height of 'Share ratio limit' dialog (thalieht)
|
- WEBUI: Increase default height of `Share ratio limit` dialog (thalieht)
|
||||||
- WEBUI: Use enabled search plugins by default (Thomas Piccirello)
|
- WEBUI: Use enabled search plugins by default (Thomas Piccirello)
|
||||||
- WEBUI: Add columns `Incomplete Save Path`, `Info Hash v1`, `Info Hash v2` (thalieht)
|
- WEBUI: Add columns `Incomplete Save Path`, `Info Hash v1`, `Info Hash v2` (thalieht)
|
||||||
- WEBUI: Always create generic filter items (skomerko)
|
- WEBUI: Always create generic filter items (skomerko)
|
||||||
|
- WEBUI: Provide `Use Category paths in Manual Mode` option (skomerko)
|
||||||
|
- WEBUI: Provide `Merge trackers to existing torrent` option (skomerko)
|
||||||
- WEBAPI: Fix wrong timestamp values (Chocobo1)
|
- WEBAPI: Fix wrong timestamp values (Chocobo1)
|
||||||
- WEBAPI: Send binary data with filename and mime type specified (glassez)
|
- WEBAPI: Send binary data with filename and mime type specified (glassez)
|
||||||
- WEBAPI: Expose API for the torrent creator (glassez, Radu Carpa)
|
- WEBAPI: Expose API for the torrent creator (glassez, Radu Carpa)
|
||||||
|
|||||||
2
dist/mac/Info.plist
vendored
2
dist/mac/Info.plist
vendored
@@ -55,7 +55,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>5.0.0</string>
|
<string>5.0.1</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>${EXECUTABLE_NAME}</string>
|
<string>${EXECUTABLE_NAME}</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
|
|||||||
146
dist/unix/org.qbittorrent.qBittorrent.desktop
vendored
146
dist/unix/org.qbittorrent.qBittorrent.desktop
vendored
File diff suppressed because it is too large
Load Diff
@@ -62,6 +62,6 @@
|
|||||||
<url type="contribute">https://github.com/qbittorrent/qBittorrent/blob/master/CONTRIBUTING.md</url>
|
<url type="contribute">https://github.com/qbittorrent/qBittorrent/blob/master/CONTRIBUTING.md</url>
|
||||||
<content_rating type="oars-1.1"/>
|
<content_rating type="oars-1.1"/>
|
||||||
<releases>
|
<releases>
|
||||||
<release version="5.0.0~rc1" date="2024-08-18"/>
|
<release version="5.0.1" date="2024-10-28"/>
|
||||||
</releases>
|
</releases>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
2
dist/windows/config.nsh
vendored
2
dist/windows/config.nsh
vendored
@@ -14,7 +14,7 @@
|
|||||||
; 4.5.1.3 -> good
|
; 4.5.1.3 -> good
|
||||||
; 4.5.1.3.2 -> bad
|
; 4.5.1.3.2 -> bad
|
||||||
; 4.5.0beta -> bad
|
; 4.5.0beta -> bad
|
||||||
!define /ifndef QBT_VERSION "5.0.0"
|
!define /ifndef QBT_VERSION "5.0.1"
|
||||||
|
|
||||||
; Option that controls the installer's window name
|
; Option that controls the installer's window name
|
||||||
; If set, its value will be used like this:
|
; If set, its value will be used like this:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2014-2023 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2014-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
@@ -58,10 +58,6 @@
|
|||||||
#include <QSplashScreen>
|
#include <QSplashScreen>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
#include <QOperatingSystemVersion>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef QBT_STATIC_QT
|
#ifdef QBT_STATIC_QT
|
||||||
#include <QtPlugin>
|
#include <QtPlugin>
|
||||||
Q_IMPORT_PLUGIN(QICOPlugin)
|
Q_IMPORT_PLUGIN(QICOPlugin)
|
||||||
@@ -189,11 +185,6 @@ int main(int argc, char *argv[])
|
|||||||
// We must save it here because QApplication constructor may change it
|
// We must save it here because QApplication constructor may change it
|
||||||
const bool isOneArg = (argc == 2);
|
const bool isOneArg = (argc == 2);
|
||||||
|
|
||||||
#if !defined(DISABLE_GUI) && defined(Q_OS_WIN)
|
|
||||||
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10)
|
|
||||||
QApplication::setStyle(u"Fusion"_s);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// `app` must be declared out of try block to allow display message box in case of exception
|
// `app` must be declared out of try block to allow display message box in case of exception
|
||||||
std::unique_ptr<Application> app;
|
std::unique_ptr<Application> app;
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -157,10 +157,36 @@ void AddTorrentManager::handleAddTorrentFailed(const QString &source, const QStr
|
|||||||
emit addTorrentFailed(source, reason);
|
emit addTorrentFailed(source, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddTorrentManager::handleDuplicateTorrent(const QString &source, BitTorrent::Torrent *torrent, const QString &message)
|
void AddTorrentManager::handleDuplicateTorrent(const QString &source
|
||||||
|
, const BitTorrent::TorrentDescriptor &torrentDescr, BitTorrent::Torrent *existingTorrent)
|
||||||
{
|
{
|
||||||
|
const bool hasMetadata = torrentDescr.info().has_value();
|
||||||
|
if (hasMetadata)
|
||||||
|
{
|
||||||
|
// Trying to set metadata to existing torrent in case if it has none
|
||||||
|
existingTorrent->setMetadata(*torrentDescr.info());
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool isPrivate = existingTorrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate());
|
||||||
|
QString message;
|
||||||
|
if (!btSession()->isMergeTrackersEnabled())
|
||||||
|
{
|
||||||
|
message = tr("Merging of trackers is disabled");
|
||||||
|
}
|
||||||
|
else if (isPrivate)
|
||||||
|
{
|
||||||
|
message = tr("Trackers cannot be merged because it is a private torrent");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// merge trackers and web seeds
|
||||||
|
existingTorrent->addTrackers(torrentDescr.trackers());
|
||||||
|
existingTorrent->addUrlSeeds(torrentDescr.urlSeeds());
|
||||||
|
message = tr("Trackers are merged from new source");
|
||||||
|
}
|
||||||
|
|
||||||
LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: %2. Result: %3")
|
LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: %2. Result: %3")
|
||||||
.arg(source, torrent->name(), message));
|
.arg(source, existingTorrent->name(), message));
|
||||||
emit addTorrentFailed(source, message);
|
emit addTorrentFailed(source, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,32 +210,7 @@ bool AddTorrentManager::processTorrent(const QString &source, const BitTorrent::
|
|||||||
if (BitTorrent::Torrent *torrent = btSession()->findTorrent(infoHash))
|
if (BitTorrent::Torrent *torrent = btSession()->findTorrent(infoHash))
|
||||||
{
|
{
|
||||||
// a duplicate torrent is being added
|
// a duplicate torrent is being added
|
||||||
|
handleDuplicateTorrent(source, torrentDescr, torrent);
|
||||||
const bool hasMetadata = torrentDescr.info().has_value();
|
|
||||||
if (hasMetadata)
|
|
||||||
{
|
|
||||||
// Trying to set metadata to existing torrent in case if it has none
|
|
||||||
torrent->setMetadata(*torrentDescr.info());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!btSession()->isMergeTrackersEnabled())
|
|
||||||
{
|
|
||||||
handleDuplicateTorrent(source, torrent, tr("Merging of trackers is disabled"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool isPrivate = torrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate());
|
|
||||||
if (isPrivate)
|
|
||||||
{
|
|
||||||
handleDuplicateTorrent(source, torrent, tr("Trackers cannot be merged because it is a private torrent"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// merge trackers and web seeds
|
|
||||||
torrent->addTrackers(torrentDescr.trackers());
|
|
||||||
torrent->addUrlSeeds(torrentDescr.urlSeeds());
|
|
||||||
|
|
||||||
handleDuplicateTorrent(source, torrent, tr("Trackers are merged from new source"));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ protected:
|
|||||||
bool addTorrentToSession(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
|
bool addTorrentToSession(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
|
||||||
, const BitTorrent::AddTorrentParams &addTorrentParams);
|
, const BitTorrent::AddTorrentParams &addTorrentParams);
|
||||||
void handleAddTorrentFailed(const QString &source, const QString &reason);
|
void handleAddTorrentFailed(const QString &source, const QString &reason);
|
||||||
void handleDuplicateTorrent(const QString &source, BitTorrent::Torrent *torrent, const QString &message);
|
void handleDuplicateTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr, BitTorrent::Torrent *existingTorrent);
|
||||||
void setTorrentFileGuard(const QString &source, std::shared_ptr<TorrentFileGuard> torrentFileGuard);
|
void setTorrentFileGuard(const QString &source, std::shared_ptr<TorrentFileGuard> torrentFileGuard);
|
||||||
void releaseTorrentFileGuard(const QString &source);
|
void releaseTorrentFileGuard(const QString &source);
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,8 @@ namespace BitTorrent
|
|||||||
{
|
{
|
||||||
Default = 0,
|
Default = 0,
|
||||||
MMap = 1,
|
MMap = 1,
|
||||||
Posix = 2
|
Posix = 2,
|
||||||
|
SimplePreadPwrite = 3
|
||||||
};
|
};
|
||||||
Q_ENUM_NS(DiskIOType)
|
Q_ENUM_NS(DiskIOType)
|
||||||
|
|
||||||
|
|||||||
@@ -526,7 +526,7 @@ SessionImpl::SessionImpl(QObject *parent)
|
|||||||
, m_I2POutboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/OutboundQuantity"_s), 3}
|
, m_I2POutboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/OutboundQuantity"_s), 3}
|
||||||
, m_I2PInboundLength {BITTORRENT_SESSION_KEY(u"I2P/InboundLength"_s), 3}
|
, m_I2PInboundLength {BITTORRENT_SESSION_KEY(u"I2P/InboundLength"_s), 3}
|
||||||
, m_I2POutboundLength {BITTORRENT_SESSION_KEY(u"I2P/OutboundLength"_s), 3}
|
, m_I2POutboundLength {BITTORRENT_SESSION_KEY(u"I2P/OutboundLength"_s), 3}
|
||||||
, m_torrentContentRemoveOption {BITTORRENT_SESSION_KEY(u"TorrentContentRemoveOption"_s), TorrentContentRemoveOption::MoveToTrash}
|
, m_torrentContentRemoveOption {BITTORRENT_SESSION_KEY(u"TorrentContentRemoveOption"_s), TorrentContentRemoveOption::Delete}
|
||||||
, m_startPaused {BITTORRENT_SESSION_KEY(u"StartPaused"_s)}
|
, m_startPaused {BITTORRENT_SESSION_KEY(u"StartPaused"_s)}
|
||||||
, m_seedingLimitTimer {new QTimer(this)}
|
, m_seedingLimitTimer {new QTimer(this)}
|
||||||
, m_resumeDataTimer {new QTimer(this)}
|
, m_resumeDataTimer {new QTimer(this)}
|
||||||
@@ -1638,6 +1638,13 @@ void SessionImpl::initializeNativeSession()
|
|||||||
#ifdef QBT_USES_LIBTORRENT2
|
#ifdef QBT_USES_LIBTORRENT2
|
||||||
// preserve the same behavior as in earlier libtorrent versions
|
// preserve the same behavior as in earlier libtorrent versions
|
||||||
pack.set_bool(lt::settings_pack::enable_set_file_valid_data, true);
|
pack.set_bool(lt::settings_pack::enable_set_file_valid_data, true);
|
||||||
|
|
||||||
|
// This is a special case. We use MMap disk IO but tweak it to always fallback to pread/pwrite.
|
||||||
|
if (diskIOType() == DiskIOType::SimplePreadPwrite)
|
||||||
|
{
|
||||||
|
pack.set_int(lt::settings_pack::mmap_file_size_cutoff, std::numeric_limits<int>::max());
|
||||||
|
pack.set_int(lt::settings_pack::disk_write_mode, lt::settings_pack::mmap_write_mode_t::always_pwrite);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
lt::session_params sessionParams {std::move(pack), {}};
|
lt::session_params sessionParams {std::move(pack), {}};
|
||||||
@@ -1648,6 +1655,7 @@ void SessionImpl::initializeNativeSession()
|
|||||||
sessionParams.disk_io_constructor = customPosixDiskIOConstructor;
|
sessionParams.disk_io_constructor = customPosixDiskIOConstructor;
|
||||||
break;
|
break;
|
||||||
case DiskIOType::MMap:
|
case DiskIOType::MMap:
|
||||||
|
case DiskIOType::SimplePreadPwrite:
|
||||||
sessionParams.disk_io_constructor = customMMapDiskIOConstructor;
|
sessionParams.disk_io_constructor = customMMapDiskIOConstructor;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -2711,8 +2719,39 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
|||||||
if (m_loadingTorrents.contains(id) || (infoHash.isHybrid() && m_loadingTorrents.contains(altID)))
|
if (m_loadingTorrents.contains(id) || (infoHash.isHybrid() && m_loadingTorrents.contains(altID)))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (findTorrent(infoHash))
|
if (Torrent *torrent = findTorrent(infoHash))
|
||||||
|
{
|
||||||
|
// a duplicate torrent is being added
|
||||||
|
|
||||||
|
if (hasMetadata)
|
||||||
|
{
|
||||||
|
// Trying to set metadata to existing torrent in case if it has none
|
||||||
|
torrent->setMetadata(*source.info());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isMergeTrackersEnabled())
|
||||||
|
{
|
||||||
|
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
|
||||||
|
.arg(torrent->name(), tr("Merging of trackers is disabled")));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool isPrivate = torrent->isPrivate() || (hasMetadata && source.info()->isPrivate());
|
||||||
|
if (isPrivate)
|
||||||
|
{
|
||||||
|
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
|
||||||
|
.arg(torrent->name(), tr("Trackers cannot be merged because it is a private torrent")));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge trackers and web seeds
|
||||||
|
torrent->addTrackers(source.trackers());
|
||||||
|
torrent->addUrlSeeds(source.urlSeeds());
|
||||||
|
|
||||||
|
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
|
||||||
|
.arg(torrent->name(), tr("Trackers are merged from new source")));
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// It looks illogical that we don't just use an existing handle,
|
// It looks illogical that we don't just use an existing handle,
|
||||||
// but as previous experience has shown, it actually creates unnecessary
|
// but as previous experience has shown, it actually creates unnecessary
|
||||||
@@ -2776,6 +2815,19 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
|||||||
loadTorrentParams.name = contentName;
|
loadTorrentParams.name = contentName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto nativeIndexes = torrentInfo.nativeIndexes();
|
||||||
|
|
||||||
|
Q_ASSERT(p.file_priorities.empty());
|
||||||
|
Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
|
||||||
|
QList<DownloadPriority> filePriorities = addTorrentParams.filePriorities;
|
||||||
|
|
||||||
|
// Filename filter should be applied before `findIncompleteFiles()` is called.
|
||||||
|
if (filePriorities.isEmpty() && isExcludedFileNamesEnabled())
|
||||||
|
{
|
||||||
|
// Check file name blacklist when priorities are not explicitly set
|
||||||
|
applyFilenameFilter(filePaths, filePriorities);
|
||||||
|
}
|
||||||
|
|
||||||
if (!loadTorrentParams.hasFinishedStatus)
|
if (!loadTorrentParams.hasFinishedStatus)
|
||||||
{
|
{
|
||||||
const Path actualDownloadPath = useAutoTMM
|
const Path actualDownloadPath = useAutoTMM
|
||||||
@@ -2784,24 +2836,12 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
|||||||
isFindingIncompleteFiles = true;
|
isFindingIncompleteFiles = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto nativeIndexes = torrentInfo.nativeIndexes();
|
|
||||||
if (!isFindingIncompleteFiles)
|
if (!isFindingIncompleteFiles)
|
||||||
{
|
{
|
||||||
for (int index = 0; index < filePaths.size(); ++index)
|
for (int index = 0; index < filePaths.size(); ++index)
|
||||||
p.renamed_files[nativeIndexes[index]] = filePaths.at(index).toString().toStdString();
|
p.renamed_files[nativeIndexes[index]] = filePaths.at(index).toString().toStdString();
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_ASSERT(p.file_priorities.empty());
|
|
||||||
Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
|
|
||||||
|
|
||||||
QList<DownloadPriority> filePriorities = addTorrentParams.filePriorities;
|
|
||||||
|
|
||||||
if (filePriorities.isEmpty() && isExcludedFileNamesEnabled())
|
|
||||||
{
|
|
||||||
// Check file name blacklist when priorities are not explicitly set
|
|
||||||
applyFilenameFilter(filePaths, filePriorities);
|
|
||||||
}
|
|
||||||
|
|
||||||
const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files
|
const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files
|
||||||
// Use qBittorrent default priority rather than libtorrent's (4)
|
// Use qBittorrent default priority rather than libtorrent's (4)
|
||||||
p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
|
p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
|
||||||
@@ -5175,6 +5215,9 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
|
|||||||
if (torrent)
|
if (torrent)
|
||||||
{
|
{
|
||||||
torrent->handleMoveStorageJobFinished(newPath, finishedJob.context, torrentHasOutstandingJob);
|
torrent->handleMoveStorageJobFinished(newPath, finishedJob.context, torrentHasOutstandingJob);
|
||||||
|
// The torrent may become "finished" at the end of the move if it was moved
|
||||||
|
// from the "incomplete" location after downloading finished.
|
||||||
|
processPendingFinishedTorrents();
|
||||||
}
|
}
|
||||||
else if (!torrentHasOutstandingJob)
|
else if (!torrentHasOutstandingJob)
|
||||||
{
|
{
|
||||||
@@ -5186,6 +5229,32 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SessionImpl::processPendingFinishedTorrents()
|
||||||
|
{
|
||||||
|
if (m_pendingFinishedTorrents.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (TorrentImpl *torrent : asConst(m_pendingFinishedTorrents))
|
||||||
|
{
|
||||||
|
LogMsg(tr("Torrent download finished. Torrent: \"%1\"").arg(torrent->name()));
|
||||||
|
emit torrentFinished(torrent);
|
||||||
|
|
||||||
|
if (const Path exportPath = finishedTorrentExportDirectory(); !exportPath.isEmpty())
|
||||||
|
exportTorrentFile(torrent, exportPath);
|
||||||
|
|
||||||
|
processTorrentShareLimits(torrent);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pendingFinishedTorrents.clear();
|
||||||
|
|
||||||
|
const bool hasUnfinishedTorrents = std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
|
||||||
|
{
|
||||||
|
return !(torrent->isFinished() || torrent->isStopped() || torrent->isErrored());
|
||||||
|
});
|
||||||
|
if (!hasUnfinishedTorrents)
|
||||||
|
emit allTorrentsFinished();
|
||||||
|
}
|
||||||
|
|
||||||
void SessionImpl::storeCategories() const
|
void SessionImpl::storeCategories() const
|
||||||
{
|
{
|
||||||
QJsonObject jsonObj;
|
QJsonObject jsonObj;
|
||||||
@@ -6077,28 +6146,7 @@ void SessionImpl::handleStateUpdateAlert(const lt::state_update_alert *alert)
|
|||||||
if (!updatedTorrents.isEmpty())
|
if (!updatedTorrents.isEmpty())
|
||||||
emit torrentsUpdated(updatedTorrents);
|
emit torrentsUpdated(updatedTorrents);
|
||||||
|
|
||||||
if (!m_pendingFinishedTorrents.isEmpty())
|
processPendingFinishedTorrents();
|
||||||
{
|
|
||||||
for (TorrentImpl *torrent : m_pendingFinishedTorrents)
|
|
||||||
{
|
|
||||||
LogMsg(tr("Torrent download finished. Torrent: \"%1\"").arg(torrent->name()));
|
|
||||||
emit torrentFinished(torrent);
|
|
||||||
|
|
||||||
if (const Path exportPath = finishedTorrentExportDirectory(); !exportPath.isEmpty())
|
|
||||||
exportTorrentFile(torrent, exportPath);
|
|
||||||
|
|
||||||
processTorrentShareLimits(torrent);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_pendingFinishedTorrents.clear();
|
|
||||||
|
|
||||||
const bool hasUnfinishedTorrents = std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
|
|
||||||
{
|
|
||||||
return !(torrent->isFinished() || torrent->isStopped() || torrent->isErrored());
|
|
||||||
});
|
|
||||||
if (!hasUnfinishedTorrents)
|
|
||||||
emit allTorrentsFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_needSaveTorrentsQueue)
|
if (m_needSaveTorrentsQueue)
|
||||||
saveTorrentsQueue();
|
saveTorrentsQueue();
|
||||||
|
|||||||
@@ -593,6 +593,7 @@ namespace BitTorrent
|
|||||||
|
|
||||||
void moveTorrentStorage(const MoveStorageJob &job) const;
|
void moveTorrentStorage(const MoveStorageJob &job) const;
|
||||||
void handleMoveTorrentStorageJobFinished(const Path &newPath);
|
void handleMoveTorrentStorageJobFinished(const Path &newPath);
|
||||||
|
void processPendingFinishedTorrents();
|
||||||
|
|
||||||
void loadCategories();
|
void loadCategories();
|
||||||
void storeCategories() const;
|
void storeCategories() const;
|
||||||
|
|||||||
@@ -1819,7 +1819,7 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
|
|||||||
m_filePriorities.append(LT::fromNative(p.file_priorities[LT::toUnderlyingType(nativeIndex)]));
|
m_filePriorities.append(LT::fromNative(p.file_priorities[LT::toUnderlyingType(nativeIndex)]));
|
||||||
}
|
}
|
||||||
|
|
||||||
m_session->applyFilenameFilter(fileNames, m_filePriorities);
|
m_session->applyFilenameFilter(m_filePaths, m_filePriorities);
|
||||||
for (int i = 0; i < m_filePriorities.size(); ++i)
|
for (int i = 0; i < m_filePriorities.size(); ++i)
|
||||||
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(m_filePriorities[i]);
|
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(m_filePriorities[i]);
|
||||||
|
|
||||||
|
|||||||
@@ -148,10 +148,20 @@ Net::DownloadManager::DownloadManager(QObject *parent)
|
|||||||
QStringList errorList;
|
QStringList errorList;
|
||||||
for (const QSslError &error : errors)
|
for (const QSslError &error : errors)
|
||||||
errorList += error.errorString();
|
errorList += error.errorString();
|
||||||
LogMsg(tr("Ignoring SSL error, URL: \"%1\", errors: \"%2\"").arg(reply->url().toString(), errorList.join(u". ")), Log::WARNING);
|
|
||||||
|
|
||||||
// Ignore all SSL errors
|
QString errorMsg;
|
||||||
reply->ignoreSslErrors();
|
if (!Preferences::instance()->isIgnoreSSLErrors())
|
||||||
|
{
|
||||||
|
errorMsg = tr("SSL error, URL: \"%1\", errors: \"%2\"");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errorMsg = tr("Ignoring SSL error, URL: \"%1\", errors: \"%2\"");
|
||||||
|
// Ignore all SSL errors
|
||||||
|
reply->ignoreSslErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
LogMsg(errorMsg.arg(reply->url().toString(), errorList.join(u". ")), Log::WARNING);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(ProxyConfigurationManager::instance(), &ProxyConfigurationManager::proxyConfigurationChanged
|
connect(ProxyConfigurationManager::instance(), &ProxyConfigurationManager::proxyConfigurationChanged
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ class QSslSocket;
|
|||||||
#else
|
#else
|
||||||
class QTcpSocket;
|
class QTcpSocket;
|
||||||
#endif
|
#endif
|
||||||
class QTextCodec;
|
|
||||||
|
|
||||||
namespace Net
|
namespace Net
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -429,6 +429,19 @@ void Preferences::setWinStartup(const bool b)
|
|||||||
settings.remove(profileID);
|
settings.remove(profileID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Preferences::getStyle() const
|
||||||
|
{
|
||||||
|
return value<QString>(u"Appearance/Style"_s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Preferences::setStyle(const QString &styleName)
|
||||||
|
{
|
||||||
|
if (styleName == getStyle())
|
||||||
|
return;
|
||||||
|
|
||||||
|
setValue(u"Appearance/Style"_s, styleName);
|
||||||
|
}
|
||||||
#endif // Q_OS_WIN
|
#endif // Q_OS_WIN
|
||||||
|
|
||||||
// Downloads
|
// Downloads
|
||||||
@@ -1330,6 +1343,19 @@ void Preferences::setMarkOfTheWebEnabled(const bool enabled)
|
|||||||
setValue(u"Preferences/Advanced/markOfTheWeb"_s, enabled);
|
setValue(u"Preferences/Advanced/markOfTheWeb"_s, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Preferences::isIgnoreSSLErrors() const
|
||||||
|
{
|
||||||
|
return value(u"Preferences/Advanced/IgnoreSSLErrors"_s, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Preferences::setIgnoreSSLErrors(const bool enabled)
|
||||||
|
{
|
||||||
|
if (enabled == isIgnoreSSLErrors())
|
||||||
|
return;
|
||||||
|
|
||||||
|
setValue(u"Preferences/Advanced/IgnoreSSLErrors"_s, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
Path Preferences::getPythonExecutablePath() const
|
Path Preferences::getPythonExecutablePath() const
|
||||||
{
|
{
|
||||||
return value(u"Preferences/Search/pythonExecutablePath"_s, Path());
|
return value(u"Preferences/Search/pythonExecutablePath"_s, Path());
|
||||||
|
|||||||
@@ -130,6 +130,8 @@ public:
|
|||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
bool WinStartup() const;
|
bool WinStartup() const;
|
||||||
void setWinStartup(bool b);
|
void setWinStartup(bool b);
|
||||||
|
QString getStyle() const;
|
||||||
|
void setStyle(const QString &styleName);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Downloads
|
// Downloads
|
||||||
@@ -293,6 +295,8 @@ public:
|
|||||||
void setTrackerPortForwardingEnabled(bool enabled);
|
void setTrackerPortForwardingEnabled(bool enabled);
|
||||||
bool isMarkOfTheWebEnabled() const;
|
bool isMarkOfTheWebEnabled() const;
|
||||||
void setMarkOfTheWebEnabled(bool enabled);
|
void setMarkOfTheWebEnabled(bool enabled);
|
||||||
|
bool isIgnoreSSLErrors() const;
|
||||||
|
void setIgnoreSSLErrors(bool enabled);
|
||||||
Path getPythonExecutablePath() const;
|
Path getPythonExecutablePath() const;
|
||||||
void setPythonExecutablePath(const Path &path);
|
void setPythonExecutablePath(const Path &path);
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||||
|
|||||||
@@ -271,6 +271,11 @@ Path Utils::OS::windowsSystemPath()
|
|||||||
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
||||||
bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url)
|
bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url)
|
||||||
{
|
{
|
||||||
|
// Trying to apply this to a non-existent file is unacceptable,
|
||||||
|
// as it may unexpectedly create such a file.
|
||||||
|
if (!file.exists())
|
||||||
|
return false;
|
||||||
|
|
||||||
Q_ASSERT(url.isEmpty() || url.startsWith(u"http:") || url.startsWith(u"https:"));
|
Q_ASSERT(url.isEmpty() || url.startsWith(u"http:") || url.startsWith(u"https:"));
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
|
|||||||
@@ -30,9 +30,9 @@
|
|||||||
|
|
||||||
#define QBT_VERSION_MAJOR 5
|
#define QBT_VERSION_MAJOR 5
|
||||||
#define QBT_VERSION_MINOR 0
|
#define QBT_VERSION_MINOR 0
|
||||||
#define QBT_VERSION_BUGFIX 0
|
#define QBT_VERSION_BUGFIX 1
|
||||||
#define QBT_VERSION_BUILD 0
|
#define QBT_VERSION_BUILD 0
|
||||||
#define QBT_VERSION_STATUS "rc1" // Should be empty for stable releases!
|
#define QBT_VERSION_STATUS "" // Should be empty for stable releases!
|
||||||
|
|
||||||
#define QBT__STRINGIFY(x) #x
|
#define QBT__STRINGIFY(x) #x
|
||||||
#define QBT_STRINGIFY(x) QBT__STRINGIFY(x)
|
#define QBT_STRINGIFY(x) QBT__STRINGIFY(x)
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ namespace
|
|||||||
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
||||||
ENABLE_MARK_OF_THE_WEB,
|
ENABLE_MARK_OF_THE_WEB,
|
||||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
#endif // Q_OS_MACOS || Q_OS_WIN
|
||||||
|
IGNORE_SSL_ERRORS,
|
||||||
PYTHON_EXECUTABLE_PATH,
|
PYTHON_EXECUTABLE_PATH,
|
||||||
START_SESSION_PAUSED,
|
START_SESSION_PAUSED,
|
||||||
SESSION_SHUTDOWN_TIMEOUT,
|
SESSION_SHUTDOWN_TIMEOUT,
|
||||||
@@ -332,6 +333,8 @@ void AdvancedSettings::saveAdvancedSettings() const
|
|||||||
// Mark-of-the-Web
|
// Mark-of-the-Web
|
||||||
pref->setMarkOfTheWebEnabled(m_checkBoxMarkOfTheWeb.isChecked());
|
pref->setMarkOfTheWebEnabled(m_checkBoxMarkOfTheWeb.isChecked());
|
||||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
#endif // Q_OS_MACOS || Q_OS_WIN
|
||||||
|
// Ignore SSL errors
|
||||||
|
pref->setIgnoreSSLErrors(m_checkBoxIgnoreSSLErrors.isChecked());
|
||||||
// Python executable path
|
// Python executable path
|
||||||
pref->setPythonExecutablePath(Path(m_pythonExecutablePath.text().trimmed()));
|
pref->setPythonExecutablePath(Path(m_pythonExecutablePath.text().trimmed()));
|
||||||
// Start session paused
|
// Start session paused
|
||||||
@@ -585,6 +588,7 @@ void AdvancedSettings::loadAdvancedSettings()
|
|||||||
m_comboBoxDiskIOType.addItem(tr("Default"), QVariant::fromValue(BitTorrent::DiskIOType::Default));
|
m_comboBoxDiskIOType.addItem(tr("Default"), QVariant::fromValue(BitTorrent::DiskIOType::Default));
|
||||||
m_comboBoxDiskIOType.addItem(tr("Memory mapped files"), QVariant::fromValue(BitTorrent::DiskIOType::MMap));
|
m_comboBoxDiskIOType.addItem(tr("Memory mapped files"), QVariant::fromValue(BitTorrent::DiskIOType::MMap));
|
||||||
m_comboBoxDiskIOType.addItem(tr("POSIX-compliant"), QVariant::fromValue(BitTorrent::DiskIOType::Posix));
|
m_comboBoxDiskIOType.addItem(tr("POSIX-compliant"), QVariant::fromValue(BitTorrent::DiskIOType::Posix));
|
||||||
|
m_comboBoxDiskIOType.addItem(tr("Simple pread/pwrite"), QVariant::fromValue(BitTorrent::DiskIOType::SimplePreadPwrite));
|
||||||
m_comboBoxDiskIOType.setCurrentIndex(m_comboBoxDiskIOType.findData(QVariant::fromValue(session->diskIOType())));
|
m_comboBoxDiskIOType.setCurrentIndex(m_comboBoxDiskIOType.findData(QVariant::fromValue(session->diskIOType())));
|
||||||
addRow(DISK_IO_TYPE, tr("Disk IO type (requires restart)") + u' ' + makeLink(u"https://www.libtorrent.org/single-page-ref.html#default-disk-io-constructor", u"(?)")
|
addRow(DISK_IO_TYPE, tr("Disk IO type (requires restart)") + u' ' + makeLink(u"https://www.libtorrent.org/single-page-ref.html#default-disk-io-constructor", u"(?)")
|
||||||
, &m_comboBoxDiskIOType);
|
, &m_comboBoxDiskIOType);
|
||||||
@@ -853,6 +857,10 @@ void AdvancedSettings::loadAdvancedSettings()
|
|||||||
m_checkBoxMarkOfTheWeb.setChecked(pref->isMarkOfTheWebEnabled());
|
m_checkBoxMarkOfTheWeb.setChecked(pref->isMarkOfTheWebEnabled());
|
||||||
addRow(ENABLE_MARK_OF_THE_WEB, motwLabel, &m_checkBoxMarkOfTheWeb);
|
addRow(ENABLE_MARK_OF_THE_WEB, motwLabel, &m_checkBoxMarkOfTheWeb);
|
||||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
#endif // Q_OS_MACOS || Q_OS_WIN
|
||||||
|
// Ignore SSL errors
|
||||||
|
m_checkBoxIgnoreSSLErrors.setChecked(pref->isIgnoreSSLErrors());
|
||||||
|
m_checkBoxIgnoreSSLErrors.setToolTip(tr("Affects certificate validation and non-torrent protocol activities (e.g. RSS feeds, program updates, torrent files, geoip db, etc)"));
|
||||||
|
addRow(IGNORE_SSL_ERRORS, tr("Ignore SSL errors"), &m_checkBoxIgnoreSSLErrors);
|
||||||
// Python executable path
|
// Python executable path
|
||||||
m_pythonExecutablePath.setPlaceholderText(tr("(Auto detect if empty)"));
|
m_pythonExecutablePath.setPlaceholderText(tr("(Auto detect if empty)"));
|
||||||
m_pythonExecutablePath.setText(pref->getPythonExecutablePath().toString());
|
m_pythonExecutablePath.setText(pref->getPythonExecutablePath().toString());
|
||||||
|
|||||||
@@ -77,9 +77,10 @@ private:
|
|||||||
m_spinBoxSavePathHistoryLength, m_spinBoxPeerTurnover, m_spinBoxPeerTurnoverCutoff, m_spinBoxPeerTurnoverInterval, m_spinBoxRequestQueueSize;
|
m_spinBoxSavePathHistoryLength, m_spinBoxPeerTurnover, m_spinBoxPeerTurnoverCutoff, m_spinBoxPeerTurnoverInterval, m_spinBoxRequestQueueSize;
|
||||||
QCheckBox m_checkBoxOsCache, m_checkBoxRecheckCompleted, m_checkBoxResolveCountries, m_checkBoxResolveHosts,
|
QCheckBox m_checkBoxOsCache, m_checkBoxRecheckCompleted, m_checkBoxResolveCountries, m_checkBoxResolveHosts,
|
||||||
m_checkBoxProgramNotifications, m_checkBoxTorrentAddedNotifications, m_checkBoxReannounceWhenAddressChanged, m_checkBoxTrackerFavicon, m_checkBoxTrackerStatus,
|
m_checkBoxProgramNotifications, m_checkBoxTorrentAddedNotifications, m_checkBoxReannounceWhenAddressChanged, m_checkBoxTrackerFavicon, m_checkBoxTrackerStatus,
|
||||||
m_checkBoxTrackerPortForwarding, m_checkBoxConfirmTorrentRecheck, m_checkBoxConfirmRemoveAllTags, m_checkBoxAnnounceAllTrackers, m_checkBoxAnnounceAllTiers,
|
m_checkBoxTrackerPortForwarding, m_checkBoxIgnoreSSLErrors, m_checkBoxConfirmTorrentRecheck, m_checkBoxConfirmRemoveAllTags, m_checkBoxAnnounceAllTrackers,
|
||||||
m_checkBoxMultiConnectionsPerIp, m_checkBoxValidateHTTPSTrackerCertificate, m_checkBoxSSRFMitigation, m_checkBoxBlockPeersOnPrivilegedPorts, m_checkBoxPieceExtentAffinity,
|
m_checkBoxAnnounceAllTiers, m_checkBoxMultiConnectionsPerIp, m_checkBoxValidateHTTPSTrackerCertificate, m_checkBoxSSRFMitigation, m_checkBoxBlockPeersOnPrivilegedPorts,
|
||||||
m_checkBoxSuggestMode, m_checkBoxSpeedWidgetEnabled, m_checkBoxIDNSupport, m_checkBoxConfirmRemoveTrackerFromAllTorrents, m_checkBoxStartSessionPaused;
|
m_checkBoxPieceExtentAffinity, m_checkBoxSuggestMode, m_checkBoxSpeedWidgetEnabled, m_checkBoxIDNSupport, m_checkBoxConfirmRemoveTrackerFromAllTorrents,
|
||||||
|
m_checkBoxStartSessionPaused;
|
||||||
QComboBox m_comboBoxInterface, m_comboBoxInterfaceAddress, m_comboBoxDiskIOReadMode, m_comboBoxDiskIOWriteMode, m_comboBoxUtpMixedMode, m_comboBoxChokingAlgorithm,
|
QComboBox m_comboBoxInterface, m_comboBoxInterfaceAddress, m_comboBoxDiskIOReadMode, m_comboBoxDiskIOWriteMode, m_comboBoxUtpMixedMode, m_comboBoxChokingAlgorithm,
|
||||||
m_comboBoxSeedChokingAlgorithm, m_comboBoxResumeDataStorage, m_comboBoxTorrentContentRemoveOption;
|
m_comboBoxSeedChokingAlgorithm, m_comboBoxResumeDataStorage, m_comboBoxTorrentContentRemoveOption;
|
||||||
QLineEdit m_lineEditAppInstanceName, m_pythonExecutablePath, m_lineEditAnnounceIP, m_lineEditDHTBootstrapNodes;
|
QLineEdit m_lineEditAppInstanceName, m_pythonExecutablePath, m_lineEditAnnounceIP, m_lineEditDHTBootstrapNodes;
|
||||||
|
|||||||
@@ -175,7 +175,8 @@ void GUIAddTorrentManager::onMetadataDownloaded(const BitTorrent::TorrentInfo &m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GUIAddTorrentManager::processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams ¶ms)
|
bool GUIAddTorrentManager::processTorrent(const QString &source
|
||||||
|
, const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams ¶ms)
|
||||||
{
|
{
|
||||||
const bool hasMetadata = torrentDescr.info().has_value();
|
const bool hasMetadata = torrentDescr.info().has_value();
|
||||||
const BitTorrent::InfoHash infoHash = torrentDescr.infoHash();
|
const BitTorrent::InfoHash infoHash = torrentDescr.infoHash();
|
||||||
@@ -183,32 +184,39 @@ bool GUIAddTorrentManager::processTorrent(const QString &source, const BitTorren
|
|||||||
// Prevent showing the dialog if download is already present
|
// Prevent showing the dialog if download is already present
|
||||||
if (BitTorrent::Torrent *torrent = btSession()->findTorrent(infoHash))
|
if (BitTorrent::Torrent *torrent = btSession()->findTorrent(infoHash))
|
||||||
{
|
{
|
||||||
if (hasMetadata)
|
if (Preferences::instance()->confirmMergeTrackers())
|
||||||
{
|
{
|
||||||
// Trying to set metadata to existing torrent in case if it has none
|
if (hasMetadata)
|
||||||
torrent->setMetadata(*torrentDescr.info());
|
{
|
||||||
}
|
// Trying to set metadata to existing torrent in case if it has none
|
||||||
|
torrent->setMetadata(*torrentDescr.info());
|
||||||
|
}
|
||||||
|
|
||||||
if (torrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate()))
|
const bool isPrivate = torrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate());
|
||||||
{
|
const QString dialogCaption = tr("Torrent is already present");
|
||||||
handleDuplicateTorrent(source, torrent, tr("Trackers cannot be merged because it is a private torrent"));
|
if (isPrivate)
|
||||||
|
{
|
||||||
|
// We cannot merge trackers for private torrent but we still notify user
|
||||||
|
// about duplicate torrent if confirmation dialog is enabled.
|
||||||
|
RaisedMessageBox::warning(app()->mainWindow(), dialogCaption
|
||||||
|
, tr("Trackers cannot be merged because it is a private torrent."));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const bool mergeTrackers = btSession()->isMergeTrackersEnabled();
|
||||||
|
const QMessageBox::StandardButton btn = RaisedMessageBox::question(app()->mainWindow(), dialogCaption
|
||||||
|
, tr("Torrent '%1' is already in the transfer list. Do you want to merge trackers from new source?").arg(torrent->name())
|
||||||
|
, (QMessageBox::Yes | QMessageBox::No), (mergeTrackers ? QMessageBox::Yes : QMessageBox::No));
|
||||||
|
if (btn == QMessageBox::Yes)
|
||||||
|
{
|
||||||
|
torrent->addTrackers(torrentDescr.trackers());
|
||||||
|
torrent->addUrlSeeds(torrentDescr.urlSeeds());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bool mergeTrackers = btSession()->isMergeTrackersEnabled();
|
handleDuplicateTorrent(source, torrentDescr, torrent);
|
||||||
if (Preferences::instance()->confirmMergeTrackers())
|
|
||||||
{
|
|
||||||
const QMessageBox::StandardButton btn = RaisedMessageBox::question(app()->mainWindow(), tr("Torrent is already present")
|
|
||||||
, tr("Torrent '%1' is already in the transfer list. Do you want to merge trackers from new source?").arg(torrent->name())
|
|
||||||
, (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
|
|
||||||
mergeTrackers = (btn == QMessageBox::Yes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mergeTrackers)
|
|
||||||
{
|
|
||||||
torrent->addTrackers(torrentDescr.trackers());
|
|
||||||
torrent->addUrlSeeds(torrentDescr.urlSeeds());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
#include "optionsdialog.h"
|
#include "optionsdialog.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
@@ -44,6 +45,10 @@
|
|||||||
#include <QSystemTrayIcon>
|
#include <QSystemTrayIcon>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include <QStyleFactory>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "base/bittorrent/session.h"
|
#include "base/bittorrent/session.h"
|
||||||
#include "base/bittorrent/sharelimitaction.h"
|
#include "base/bittorrent/sharelimitaction.h"
|
||||||
#include "base/exceptions.h"
|
#include "base/exceptions.h"
|
||||||
@@ -56,6 +61,7 @@
|
|||||||
#include "base/rss/rss_session.h"
|
#include "base/rss/rss_session.h"
|
||||||
#include "base/torrentfileguard.h"
|
#include "base/torrentfileguard.h"
|
||||||
#include "base/torrentfileswatcher.h"
|
#include "base/torrentfileswatcher.h"
|
||||||
|
#include "base/utils/compare.h"
|
||||||
#include "base/utils/io.h"
|
#include "base/utils/io.h"
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "base/utils/net.h"
|
#include "base/utils/net.h"
|
||||||
@@ -236,6 +242,8 @@ void OptionsDialog::loadBehaviorTabOptions()
|
|||||||
initializeLanguageCombo();
|
initializeLanguageCombo();
|
||||||
setLocale(pref->getLocale());
|
setLocale(pref->getLocale());
|
||||||
|
|
||||||
|
initializeStyleCombo();
|
||||||
|
|
||||||
m_ui->checkUseCustomTheme->setChecked(Preferences::instance()->useCustomUITheme());
|
m_ui->checkUseCustomTheme->setChecked(Preferences::instance()->useCustomUITheme());
|
||||||
m_ui->customThemeFilePath->setSelectedPath(Preferences::instance()->customUIThemePath());
|
m_ui->customThemeFilePath->setSelectedPath(Preferences::instance()->customUIThemePath());
|
||||||
m_ui->customThemeFilePath->setMode(FileSystemPathEdit::Mode::FileOpen);
|
m_ui->customThemeFilePath->setMode(FileSystemPathEdit::Mode::FileOpen);
|
||||||
@@ -345,7 +353,11 @@ void OptionsDialog::loadBehaviorTabOptions()
|
|||||||
|
|
||||||
m_ui->checkBoxPerformanceWarning->setChecked(session->isPerformanceWarningEnabled());
|
m_ui->checkBoxPerformanceWarning->setChecked(session->isPerformanceWarningEnabled());
|
||||||
|
|
||||||
connect(m_ui->comboI18n, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
connect(m_ui->comboLanguage, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
connect(m_ui->comboStyle, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
|
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
|
||||||
connect(m_ui->checkUseSystemIcon, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
connect(m_ui->checkUseSystemIcon, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||||
@@ -443,6 +455,13 @@ void OptionsDialog::saveBehaviorTabOptions() const
|
|||||||
}
|
}
|
||||||
pref->setLocale(locale);
|
pref->setLocale(locale);
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
if (const QVariant systemStyle = m_ui->comboStyle->currentData(); systemStyle.isValid())
|
||||||
|
pref->setStyle(systemStyle.toString());
|
||||||
|
else
|
||||||
|
pref->setStyle(m_ui->comboStyle->currentText());
|
||||||
|
#endif
|
||||||
|
|
||||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
|
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
|
||||||
pref->useSystemIcons(m_ui->checkUseSystemIcon->isChecked());
|
pref->useSystemIcons(m_ui->checkUseSystemIcon->isChecked());
|
||||||
#endif
|
#endif
|
||||||
@@ -1387,7 +1406,7 @@ void OptionsDialog::initializeLanguageCombo()
|
|||||||
for (const QString &langFile : langFiles)
|
for (const QString &langFile : langFiles)
|
||||||
{
|
{
|
||||||
const QString langCode = QStringView(langFile).sliced(12).chopped(3).toString(); // remove "qbittorrent_" and ".qm"
|
const QString langCode = QStringView(langFile).sliced(12).chopped(3).toString(); // remove "qbittorrent_" and ".qm"
|
||||||
m_ui->comboI18n->addItem(Utils::Misc::languageToLocalizedString(langCode), langCode);
|
m_ui->comboLanguage->addItem(Utils::Misc::languageToLocalizedString(langCode), langCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1672,6 +1691,33 @@ bool OptionsDialog::isSplashScreenDisabled() const
|
|||||||
return !m_ui->checkShowSplash->isChecked();
|
return !m_ui->checkShowSplash->isChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OptionsDialog::initializeStyleCombo()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
m_ui->labelStyleHint->setText(tr("%1 is recommended for best compatibility with Windows dark mode"
|
||||||
|
, "Fusion is recommended for best compatibility with Windows dark mode").arg(u"Fusion"_s));
|
||||||
|
m_ui->comboStyle->addItem(tr("System", "System default Qt style"), u"system"_s);
|
||||||
|
m_ui->comboStyle->setItemData(0, tr("Let Qt decide the style for this system"), Qt::ToolTipRole);
|
||||||
|
m_ui->comboStyle->insertSeparator(1);
|
||||||
|
|
||||||
|
QStringList styleNames = QStyleFactory::keys();
|
||||||
|
std::sort(styleNames.begin(), styleNames.end(), Utils::Compare::NaturalLessThan<Qt::CaseInsensitive>());
|
||||||
|
m_ui->comboStyle->addItems(styleNames);
|
||||||
|
const QString prefStyleName = Preferences::instance()->getStyle();
|
||||||
|
const QString selectedStyleName = prefStyleName.isEmpty() ? QApplication::style()->name() : prefStyleName;
|
||||||
|
|
||||||
|
if (selectedStyleName.compare(u"system"_s, Qt::CaseInsensitive) != 0)
|
||||||
|
m_ui->comboStyle->setCurrentText(selectedStyleName);
|
||||||
|
#else
|
||||||
|
m_ui->labelStyle->hide();
|
||||||
|
m_ui->comboStyle->hide();
|
||||||
|
m_ui->labelStyleHint->hide();
|
||||||
|
m_ui->UISettingsBoxLayout->removeWidget(m_ui->labelStyle);
|
||||||
|
m_ui->UISettingsBoxLayout->removeWidget(m_ui->comboStyle);
|
||||||
|
m_ui->UISettingsBoxLayout->removeWidget(m_ui->labelStyleHint);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
bool OptionsDialog::WinStartup() const
|
bool OptionsDialog::WinStartup() const
|
||||||
{
|
{
|
||||||
@@ -1721,7 +1767,7 @@ QString OptionsDialog::getProxyPassword() const
|
|||||||
// Locale Settings
|
// Locale Settings
|
||||||
QString OptionsDialog::getLocale() const
|
QString OptionsDialog::getLocale() const
|
||||||
{
|
{
|
||||||
return m_ui->comboI18n->itemData(m_ui->comboI18n->currentIndex(), Qt::UserRole).toString();
|
return m_ui->comboLanguage->itemData(m_ui->comboLanguage->currentIndex(), Qt::UserRole).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OptionsDialog::setLocale(const QString &localeStr)
|
void OptionsDialog::setLocale(const QString &localeStr)
|
||||||
@@ -1746,7 +1792,7 @@ void OptionsDialog::setLocale(const QString &localeStr)
|
|||||||
name = locale.name();
|
name = locale.name();
|
||||||
}
|
}
|
||||||
// Attempt to find exact match
|
// Attempt to find exact match
|
||||||
int index = m_ui->comboI18n->findData(name, Qt::UserRole);
|
int index = m_ui->comboLanguage->findData(name, Qt::UserRole);
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
{
|
{
|
||||||
//Attempt to find a language match without a country
|
//Attempt to find a language match without a country
|
||||||
@@ -1754,16 +1800,16 @@ void OptionsDialog::setLocale(const QString &localeStr)
|
|||||||
if (pos > -1)
|
if (pos > -1)
|
||||||
{
|
{
|
||||||
QString lang = name.left(pos);
|
QString lang = name.left(pos);
|
||||||
index = m_ui->comboI18n->findData(lang, Qt::UserRole);
|
index = m_ui->comboLanguage->findData(lang, Qt::UserRole);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
{
|
{
|
||||||
// Unrecognized, use US English
|
// Unrecognized, use US English
|
||||||
index = m_ui->comboI18n->findData(u"en"_s, Qt::UserRole);
|
index = m_ui->comboLanguage->findData(u"en"_s, Qt::UserRole);
|
||||||
Q_ASSERT(index >= 0);
|
Q_ASSERT(index >= 0);
|
||||||
}
|
}
|
||||||
m_ui->comboI18n->setCurrentIndex(index);
|
m_ui->comboLanguage->setCurrentIndex(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
Path OptionsDialog::getTorrentExportDir() const
|
Path OptionsDialog::getTorrentExportDir() const
|
||||||
@@ -1869,7 +1915,7 @@ Path OptionsDialog::getFilter() const
|
|||||||
void OptionsDialog::webUIHttpsCertChanged(const Path &path)
|
void OptionsDialog::webUIHttpsCertChanged(const Path &path)
|
||||||
{
|
{
|
||||||
const auto readResult = Utils::IO::readFile(path, Utils::Net::MAX_SSL_FILE_SIZE);
|
const auto readResult = Utils::IO::readFile(path, Utils::Net::MAX_SSL_FILE_SIZE);
|
||||||
const bool isCertValid = !Utils::SSLKey::load(readResult.value_or(QByteArray())).isNull();
|
const bool isCertValid = Utils::Net::isSSLCertificatesValid(readResult.value_or(QByteArray()));
|
||||||
|
|
||||||
m_ui->textWebUIHttpsCert->setSelectedPath(path);
|
m_ui->textWebUIHttpsCert->setSelectedPath(path);
|
||||||
m_ui->lblSslCertStatus->setPixmap(UIThemeManager::instance()->getScaledPixmap(
|
m_ui->lblSslCertStatus->setPixmap(UIThemeManager::instance()->getScaledPixmap(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2023-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
@@ -143,6 +143,7 @@ private:
|
|||||||
|
|
||||||
// General options
|
// General options
|
||||||
void initializeLanguageCombo();
|
void initializeLanguageCombo();
|
||||||
|
void initializeStyleCombo();
|
||||||
QString getLocale() const;
|
QString getLocale() const;
|
||||||
bool isSplashScreenDisabled() const;
|
bool isSplashScreenDisabled() const;
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
|
|||||||
@@ -132,9 +132,9 @@
|
|||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Interface</string>
|
<string>Interface</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout_81">
|
<layout class="QGridLayout" name="UISettingsBoxLayout">
|
||||||
<item row="0" column="0" colspan="3">
|
<item row="0" column="0" colspan="3">
|
||||||
<widget class="QLabel" name="label_15">
|
<widget class="QLabel" name="labelRestartRequired">
|
||||||
<property name="font">
|
<property name="font">
|
||||||
<font>
|
<font>
|
||||||
<italic>true</italic>
|
<italic>true</italic>
|
||||||
@@ -146,17 +146,17 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QLabel" name="label_9">
|
<widget class="QLabel" name="labelLanguage">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Language:</string>
|
<string>Language:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="QComboBox" name="comboI18n"/>
|
<widget class="QComboBox" name="comboLanguage"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="2">
|
<item row="1" column="2">
|
||||||
<spacer name="horizontalSpacer_111">
|
<spacer name="spacerLanguage">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
@@ -168,7 +168,26 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0" colspan="3">
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="labelStyle">
|
||||||
|
<property name="text">
|
||||||
|
<string>Style:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QComboBox" name="comboStyle"/>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="2">
|
||||||
|
<widget class="QLabel" name="labelStyleHint">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<italic>true</italic>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0" colspan="3">
|
||||||
<widget class="QGroupBox" name="checkUseCustomTheme">
|
<widget class="QGroupBox" name="checkUseCustomTheme">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Use custom UI Theme</string>
|
<string>Use custom UI Theme</string>
|
||||||
@@ -190,14 +209,14 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0" colspan="2">
|
<item row="4" column="0" colspan="2">
|
||||||
<widget class="QCheckBox" name="checkUseSystemIcon">
|
<widget class="QCheckBox" name="checkUseSystemIcon">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Use icons from system theme</string>
|
<string>Use icons from system theme</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0" colspan="2">
|
<item row="5" column="0" colspan="2">
|
||||||
<widget class="QPushButton" name="buttonCustomizeUITheme">
|
<widget class="QPushButton" name="buttonCustomizeUITheme">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Customize UI Theme...</string>
|
<string>Customize UI Theme...</string>
|
||||||
@@ -3881,7 +3900,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
|
|||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>tabOption</tabstop>
|
<tabstop>tabOption</tabstop>
|
||||||
<tabstop>comboI18n</tabstop>
|
<tabstop>comboLanguage</tabstop>
|
||||||
<tabstop>checkUseCustomTheme</tabstop>
|
<tabstop>checkUseCustomTheme</tabstop>
|
||||||
<tabstop>customThemeFilePath</tabstop>
|
<tabstop>customThemeFilePath</tabstop>
|
||||||
<tabstop>checkAddStopped</tabstop>
|
<tabstop>checkAddStopped</tabstop>
|
||||||
|
|||||||
@@ -29,6 +29,9 @@
|
|||||||
|
|
||||||
#include "programupdater.h"
|
#include "programupdater.h"
|
||||||
|
|
||||||
|
#include <libtorrent/version.hpp>
|
||||||
|
|
||||||
|
#include <QtCore/qconfig.h>
|
||||||
#include <QtSystemDetection>
|
#include <QtSystemDetection>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
@@ -61,6 +64,20 @@ namespace
|
|||||||
}
|
}
|
||||||
return (newVersion > currentVersion);
|
return (newVersion > currentVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString buildVariant()
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_MACOS)
|
||||||
|
const auto BASE_OS = u"Mac OS X"_s;
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
|
const auto BASE_OS = u"Windows x64"_s;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if constexpr ((QT_VERSION_MAJOR == 6) && (LIBTORRENT_VERSION_MAJOR == 1))
|
||||||
|
return BASE_OS;
|
||||||
|
|
||||||
|
return u"%1 (qt%2 lt%3%4)"_s.arg(BASE_OS, QString::number(QT_VERSION_MAJOR), QString::number(LIBTORRENT_VERSION_MAJOR), QString::number(LIBTORRENT_VERSION_MINOR));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProgramUpdater::checkForUpdates() const
|
void ProgramUpdater::checkForUpdates() const
|
||||||
@@ -97,12 +114,7 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
|||||||
: QString {};
|
: QString {};
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
const QString variant = buildVariant();
|
||||||
const QString OS_TYPE = u"Mac OS X"_s;
|
|
||||||
#elif defined(Q_OS_WIN)
|
|
||||||
const QString OS_TYPE = u"Windows x64"_s;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool inItem = false;
|
bool inItem = false;
|
||||||
QString version;
|
QString version;
|
||||||
QString updateLink;
|
QString updateLink;
|
||||||
@@ -128,7 +140,7 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
|||||||
{
|
{
|
||||||
if (inItem && (xml.name() == u"item"))
|
if (inItem && (xml.name() == u"item"))
|
||||||
{
|
{
|
||||||
if (type.compare(OS_TYPE, Qt::CaseInsensitive) == 0)
|
if (type.compare(variant, Qt::CaseInsensitive) == 0)
|
||||||
{
|
{
|
||||||
qDebug("The last update available is %s", qUtf8Printable(version));
|
qDebug("The last update available is %s", qUtf8Printable(version));
|
||||||
if (!version.isEmpty())
|
if (!version.isEmpty())
|
||||||
|
|||||||
@@ -42,7 +42,6 @@
|
|||||||
#include "base/indexrange.h"
|
#include "base/indexrange.h"
|
||||||
#include "base/path.h"
|
#include "base/path.h"
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "gui/uithememanager.h"
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -119,13 +118,7 @@ PiecesBar::PiecesBar(QWidget *parent)
|
|||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
{
|
{
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
|
|
||||||
updateColorsImpl();
|
updateColorsImpl();
|
||||||
connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, [this]
|
|
||||||
{
|
|
||||||
updateColors();
|
|
||||||
redraw();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PiecesBar::setTorrent(const BitTorrent::Torrent *torrent)
|
void PiecesBar::setTorrent(const BitTorrent::Torrent *torrent)
|
||||||
@@ -143,12 +136,19 @@ void PiecesBar::clear()
|
|||||||
|
|
||||||
bool PiecesBar::event(QEvent *e)
|
bool PiecesBar::event(QEvent *e)
|
||||||
{
|
{
|
||||||
if (e->type() == QEvent::ToolTip)
|
const QEvent::Type eventType = e->type();
|
||||||
|
if (eventType == QEvent::ToolTip)
|
||||||
{
|
{
|
||||||
showToolTip(static_cast<QHelpEvent *>(e));
|
showToolTip(static_cast<QHelpEvent *>(e));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventType == QEvent::PaletteChange)
|
||||||
|
{
|
||||||
|
updateColors();
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
|
||||||
return base::event(e);
|
return base::event(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,10 +195,8 @@ void PiecesBar::paintEvent(QPaintEvent *)
|
|||||||
|
|
||||||
if (!m_highlightedRegion.isNull())
|
if (!m_highlightedRegion.isNull())
|
||||||
{
|
{
|
||||||
QColor highlightColor {this->palette().color(QPalette::Active, QPalette::Highlight)};
|
|
||||||
highlightColor.setAlphaF(0.35f);
|
|
||||||
QRect targetHighlightRect {m_highlightedRegion.adjusted(borderWidth, borderWidth, borderWidth, height() - 2 * borderWidth)};
|
QRect targetHighlightRect {m_highlightedRegion.adjusted(borderWidth, borderWidth, borderWidth, height() - 2 * borderWidth)};
|
||||||
painter.fillRect(targetHighlightRect, highlightColor);
|
painter.fillRect(targetHighlightRect, highlightedPieceColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
QPainterPath border;
|
QPainterPath border;
|
||||||
@@ -231,6 +229,13 @@ QColor PiecesBar::pieceColor() const
|
|||||||
return palette().color(QPalette::Active, QPalette::Highlight);
|
return palette().color(QPalette::Active, QPalette::Highlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QColor PiecesBar::highlightedPieceColor() const
|
||||||
|
{
|
||||||
|
QColor col = palette().color(QPalette::Highlight).darker();
|
||||||
|
col.setAlphaF(0.35);
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
|
||||||
QColor PiecesBar::colorBoxBorderColor() const
|
QColor PiecesBar::colorBoxBorderColor() const
|
||||||
{
|
{
|
||||||
return palette().color(QPalette::Active, QPalette::ToolTipText);
|
return palette().color(QPalette::Active, QPalette::ToolTipText);
|
||||||
|
|||||||
@@ -68,7 +68,9 @@ protected:
|
|||||||
QColor backgroundColor() const;
|
QColor backgroundColor() const;
|
||||||
QColor borderColor() const;
|
QColor borderColor() const;
|
||||||
QColor pieceColor() const;
|
QColor pieceColor() const;
|
||||||
|
QColor highlightedPieceColor() const;
|
||||||
QColor colorBoxBorderColor() const;
|
QColor colorBoxBorderColor() const;
|
||||||
|
|
||||||
const QVector<QRgb> &pieceColors() const;
|
const QVector<QRgb> &pieceColors() const;
|
||||||
|
|
||||||
// mix two colors by light model, ratio <0, 1>
|
// mix two colors by light model, ratio <0, 1>
|
||||||
|
|||||||
@@ -126,6 +126,8 @@ RSSWidget::RSSWidget(IGUIApplication *app, QWidget *parent)
|
|||||||
, this, &RSSWidget::handleSessionProcessingStateChanged);
|
, this, &RSSWidget::handleSessionProcessingStateChanged);
|
||||||
connect(RSS::Session::instance()->rootFolder(), &RSS::Folder::unreadCountChanged
|
connect(RSS::Session::instance()->rootFolder(), &RSS::Folder::unreadCountChanged
|
||||||
, this, &RSSWidget::handleUnreadCountChanged);
|
, this, &RSSWidget::handleUnreadCountChanged);
|
||||||
|
|
||||||
|
m_ui->textBrowser->installEventFilter(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
RSSWidget::~RSSWidget()
|
RSSWidget::~RSSWidget()
|
||||||
@@ -494,60 +496,11 @@ void RSSWidget::handleCurrentArticleItemChanged(QListWidgetItem *currentItem, QL
|
|||||||
article->markAsRead();
|
article->markAsRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentItem) return;
|
if (!currentItem)
|
||||||
|
return;
|
||||||
|
|
||||||
auto *article = m_articleListWidget->getRSSArticle(currentItem);
|
auto *article = m_articleListWidget->getRSSArticle(currentItem);
|
||||||
Q_ASSERT(article);
|
renderArticle(article);
|
||||||
|
|
||||||
const QString highlightedBaseColor = m_ui->textBrowser->palette().color(QPalette::Highlight).name();
|
|
||||||
const QString highlightedBaseTextColor = m_ui->textBrowser->palette().color(QPalette::HighlightedText).name();
|
|
||||||
const QString alternateBaseColor = m_ui->textBrowser->palette().color(QPalette::AlternateBase).name();
|
|
||||||
|
|
||||||
QString html =
|
|
||||||
u"<div style='border: 2px solid red; margin-left: 5px; margin-right: 5px; margin-bottom: 5px;'>" +
|
|
||||||
u"<div style='background-color: \"%1\"; font-weight: bold; color: \"%2\";'>%3</div>"_s.arg(highlightedBaseColor, highlightedBaseTextColor, article->title());
|
|
||||||
if (article->date().isValid())
|
|
||||||
html += u"<div style='background-color: \"%1\";'><b>%2</b>%3</div>"_s.arg(alternateBaseColor, tr("Date: "), QLocale::system().toString(article->date().toLocalTime()));
|
|
||||||
if (m_feedListWidget->currentItem() == m_feedListWidget->stickyUnreadItem())
|
|
||||||
html += u"<div style='background-color: \"%1\";'><b>%2</b>%3</div>"_s.arg(alternateBaseColor, tr("Feed: "), article->feed()->title());
|
|
||||||
if (!article->author().isEmpty())
|
|
||||||
html += u"<div style='background-color: \"%1\";'><b>%2</b>%3</div>"_s.arg(alternateBaseColor, tr("Author: "), article->author());
|
|
||||||
html += u"</div>"
|
|
||||||
u"<div style='margin-left: 5px; margin-right: 5px;'>";
|
|
||||||
if (Qt::mightBeRichText(article->description()))
|
|
||||||
{
|
|
||||||
html += article->description();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
QString description = article->description();
|
|
||||||
QRegularExpression rx;
|
|
||||||
// If description is plain text, replace BBCode tags with HTML and wrap everything in <pre></pre> so it looks nice
|
|
||||||
rx.setPatternOptions(QRegularExpression::InvertedGreedinessOption
|
|
||||||
| QRegularExpression::CaseInsensitiveOption);
|
|
||||||
|
|
||||||
rx.setPattern(u"\\[img\\](.+)\\[/img\\]"_s);
|
|
||||||
description = description.replace(rx, u"<img src=\"\\1\">"_s);
|
|
||||||
|
|
||||||
rx.setPattern(u"\\[url=(\")?(.+)\\1\\]"_s);
|
|
||||||
description = description.replace(rx, u"<a href=\"\\2\">"_s);
|
|
||||||
description = description.replace(u"[/url]"_s, u"</a>"_s, Qt::CaseInsensitive);
|
|
||||||
|
|
||||||
rx.setPattern(u"\\[(/)?([bius])\\]"_s);
|
|
||||||
description = description.replace(rx, u"<\\1\\2>"_s);
|
|
||||||
|
|
||||||
rx.setPattern(u"\\[color=(\")?(.+)\\1\\]"_s);
|
|
||||||
description = description.replace(rx, u"<span style=\"color:\\2\">"_s);
|
|
||||||
description = description.replace(u"[/color]"_s, u"</span>"_s, Qt::CaseInsensitive);
|
|
||||||
|
|
||||||
rx.setPattern(u"\\[size=(\")?(.+)\\d\\1\\]"_s);
|
|
||||||
description = description.replace(rx, u"<span style=\"font-size:\\2px\">"_s);
|
|
||||||
description = description.replace(u"[/size]"_s, u"</span>"_s, Qt::CaseInsensitive);
|
|
||||||
|
|
||||||
html += u"<pre>" + description + u"</pre>";
|
|
||||||
}
|
|
||||||
html += u"</div>";
|
|
||||||
m_ui->textBrowser->setHtml(html);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RSSWidget::saveSlidersPosition()
|
void RSSWidget::saveSlidersPosition()
|
||||||
@@ -590,3 +543,73 @@ void RSSWidget::handleUnreadCountChanged()
|
|||||||
{
|
{
|
||||||
emit unreadCountUpdated(RSS::Session::instance()->rootFolder()->unreadCount());
|
emit unreadCountUpdated(RSS::Session::instance()->rootFolder()->unreadCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RSSWidget::eventFilter(QObject *obj, QEvent *event)
|
||||||
|
{
|
||||||
|
if ((obj == m_ui->textBrowser) && (event->type() == QEvent::PaletteChange))
|
||||||
|
{
|
||||||
|
QListWidgetItem *currentItem = m_articleListWidget->currentItem();
|
||||||
|
if (currentItem)
|
||||||
|
{
|
||||||
|
const RSS::Article *article = m_articleListWidget->getRSSArticle(currentItem);
|
||||||
|
renderArticle(article);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RSSWidget::renderArticle(const RSS::Article *article) const
|
||||||
|
{
|
||||||
|
Q_ASSERT(article);
|
||||||
|
|
||||||
|
const QString highlightedBaseColor = m_ui->textBrowser->palette().color(QPalette::Active, QPalette::Highlight).name();
|
||||||
|
const QString highlightedBaseTextColor = m_ui->textBrowser->palette().color(QPalette::Active, QPalette::HighlightedText).name();
|
||||||
|
const QString alternateBaseColor = m_ui->textBrowser->palette().color(QPalette::Active, QPalette::AlternateBase).name();
|
||||||
|
|
||||||
|
QString html =
|
||||||
|
u"<div style='border: 2px solid red; margin-left: 5px; margin-right: 5px; margin-bottom: 5px;'>" +
|
||||||
|
u"<div style='background-color: \"%1\"; font-weight: bold; color: \"%2\";'>%3</div>"_s.arg(highlightedBaseColor, highlightedBaseTextColor, article->title());
|
||||||
|
if (article->date().isValid())
|
||||||
|
html += u"<div style='background-color: \"%1\";'><b>%2</b>%3</div>"_s.arg(alternateBaseColor, tr("Date: "), QLocale::system().toString(article->date().toLocalTime()));
|
||||||
|
if (m_feedListWidget->currentItem() == m_feedListWidget->stickyUnreadItem())
|
||||||
|
html += u"<div style='background-color: \"%1\";'><b>%2</b>%3</div>"_s.arg(alternateBaseColor, tr("Feed: "), article->feed()->title());
|
||||||
|
if (!article->author().isEmpty())
|
||||||
|
html += u"<div style='background-color: \"%1\";'><b>%2</b>%3</div>"_s.arg(alternateBaseColor, tr("Author: "), article->author());
|
||||||
|
html += u"</div>"
|
||||||
|
u"<div style='margin-left: 5px; margin-right: 5px;'>";
|
||||||
|
if (Qt::mightBeRichText(article->description()))
|
||||||
|
{
|
||||||
|
html += article->description();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QString description = article->description();
|
||||||
|
QRegularExpression rx;
|
||||||
|
// If description is plain text, replace BBCode tags with HTML and wrap everything in <pre></pre> so it looks nice
|
||||||
|
rx.setPatternOptions(QRegularExpression::InvertedGreedinessOption
|
||||||
|
| QRegularExpression::CaseInsensitiveOption);
|
||||||
|
|
||||||
|
rx.setPattern(u"\\[img\\](.+)\\[/img\\]"_s);
|
||||||
|
description = description.replace(rx, u"<img src=\"\\1\">"_s);
|
||||||
|
|
||||||
|
rx.setPattern(u"\\[url=(\")?(.+)\\1\\]"_s);
|
||||||
|
description = description.replace(rx, u"<a href=\"\\2\">"_s);
|
||||||
|
description = description.replace(u"[/url]"_s, u"</a>"_s, Qt::CaseInsensitive);
|
||||||
|
|
||||||
|
rx.setPattern(u"\\[(/)?([bius])\\]"_s);
|
||||||
|
description = description.replace(rx, u"<\\1\\2>"_s);
|
||||||
|
|
||||||
|
rx.setPattern(u"\\[color=(\")?(.+)\\1\\]"_s);
|
||||||
|
description = description.replace(rx, u"<span style=\"color:\\2\">"_s);
|
||||||
|
description = description.replace(u"[/color]"_s, u"</span>"_s, Qt::CaseInsensitive);
|
||||||
|
|
||||||
|
rx.setPattern(u"\\[size=(\")?(.+)\\d\\1\\]"_s);
|
||||||
|
description = description.replace(rx, u"<span style=\"font-size:\\2px\">"_s);
|
||||||
|
description = description.replace(u"[/size]"_s, u"</span>"_s, Qt::CaseInsensitive);
|
||||||
|
|
||||||
|
html += u"<pre>" + description + u"</pre>";
|
||||||
|
}
|
||||||
|
html += u"</div>";
|
||||||
|
m_ui->textBrowser->setHtml(html);
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ class QTreeWidgetItem;
|
|||||||
class ArticleListWidget;
|
class ArticleListWidget;
|
||||||
class FeedListWidget;
|
class FeedListWidget;
|
||||||
|
|
||||||
|
namespace RSS
|
||||||
|
{
|
||||||
|
class Article;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Ui
|
namespace Ui
|
||||||
{
|
{
|
||||||
class RSSWidget;
|
class RSSWidget;
|
||||||
@@ -85,6 +90,9 @@ private slots:
|
|||||||
void handleUnreadCountChanged();
|
void handleUnreadCountChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||||
|
void renderArticle(const RSS::Article *article) const;
|
||||||
|
|
||||||
Ui::RSSWidget *m_ui = nullptr;
|
Ui::RSSWidget *m_ui = nullptr;
|
||||||
ArticleListWidget *m_articleListWidget = nullptr;
|
ArticleListWidget *m_articleListWidget = nullptr;
|
||||||
FeedListWidget *m_feedListWidget = nullptr;
|
FeedListWidget *m_feedListWidget = nullptr;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2018-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
@@ -50,6 +50,19 @@
|
|||||||
#include "searchsortmodel.h"
|
#include "searchsortmodel.h"
|
||||||
#include "ui_searchjobwidget.h"
|
#include "ui_searchjobwidget.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
enum DataRole
|
||||||
|
{
|
||||||
|
LinkVisitedRole = Qt::UserRole + 100
|
||||||
|
};
|
||||||
|
|
||||||
|
QColor visitedRowColor()
|
||||||
|
{
|
||||||
|
return QApplication::palette().color(QPalette::Disabled, QPalette::WindowText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent)
|
SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent)
|
||||||
: GUIApplicationComponent(app, parent)
|
: GUIApplicationComponent(app, parent)
|
||||||
, m_ui {new Ui::SearchJobWidget}
|
, m_ui {new Ui::SearchJobWidget}
|
||||||
@@ -158,6 +171,8 @@ SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *
|
|||||||
connect(this, &QObject::destroyed, searchHandler, &QObject::deleteLater);
|
connect(this, &QObject::destroyed, searchHandler, &QObject::deleteLater);
|
||||||
|
|
||||||
setStatusTip(statusText(m_status));
|
setStatusTip(statusText(m_status));
|
||||||
|
|
||||||
|
connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, &SearchJobWidget::onUIThemeChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchJobWidget::~SearchJobWidget()
|
SearchJobWidget::~SearchJobWidget()
|
||||||
@@ -179,9 +194,31 @@ QHeaderView *SearchJobWidget::header() const
|
|||||||
// Set the color of a row in data model
|
// Set the color of a row in data model
|
||||||
void SearchJobWidget::setRowColor(int row, const QColor &color)
|
void SearchJobWidget::setRowColor(int row, const QColor &color)
|
||||||
{
|
{
|
||||||
m_proxyModel->setDynamicSortFilter(false);
|
|
||||||
for (int i = 0; i < m_proxyModel->columnCount(); ++i)
|
for (int i = 0; i < m_proxyModel->columnCount(); ++i)
|
||||||
m_proxyModel->setData(m_proxyModel->index(row, i), color, Qt::ForegroundRole);
|
m_proxyModel->setData(m_proxyModel->index(row, i), color, Qt::ForegroundRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SearchJobWidget::setRowVisited(const int row)
|
||||||
|
{
|
||||||
|
m_proxyModel->setDynamicSortFilter(false);
|
||||||
|
|
||||||
|
m_proxyModel->setData(m_proxyModel->index(row, 0), true, LinkVisitedRole);
|
||||||
|
setRowColor(row, visitedRowColor());
|
||||||
|
|
||||||
|
m_proxyModel->setDynamicSortFilter(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SearchJobWidget::onUIThemeChanged()
|
||||||
|
{
|
||||||
|
m_proxyModel->setDynamicSortFilter(false);
|
||||||
|
|
||||||
|
for (int row = 0; row < m_proxyModel->rowCount(); ++row)
|
||||||
|
{
|
||||||
|
const QVariant userData = m_proxyModel->data(m_proxyModel->index(row, 0), LinkVisitedRole);
|
||||||
|
const bool isVisited = userData.toBool();
|
||||||
|
if (isVisited)
|
||||||
|
setRowColor(row, visitedRowColor());
|
||||||
|
}
|
||||||
|
|
||||||
m_proxyModel->setDynamicSortFilter(true);
|
m_proxyModel->setDynamicSortFilter(true);
|
||||||
}
|
}
|
||||||
@@ -284,7 +321,8 @@ void SearchJobWidget::downloadTorrent(const QModelIndex &rowIndex, const AddTorr
|
|||||||
, this, [this, option](const QString &source) { addTorrentToSession(source, option); });
|
, this, [this, option](const QString &source) { addTorrentToSession(source, option); });
|
||||||
connect(downloadHandler, &SearchDownloadHandler::downloadFinished, downloadHandler, &SearchDownloadHandler::deleteLater);
|
connect(downloadHandler, &SearchDownloadHandler::downloadFinished, downloadHandler, &SearchDownloadHandler::deleteLater);
|
||||||
}
|
}
|
||||||
setRowColor(rowIndex.row(), QApplication::palette().color(QPalette::LinkVisited));
|
|
||||||
|
setRowVisited(rowIndex.row());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchJobWidget::addTorrentToSession(const QString &source, const AddTorrentOption option)
|
void SearchJobWidget::addTorrentToSession(const QString &source, const AddTorrentOption option)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user