1
mirror of https://github.com/qbittorrent/qBittorrent synced 2025-10-16 20:32:23 +02:00

Compare commits

...

39 Commits

Author SHA1 Message Date
sledgehammer999
94136262a8 Bump to 5.0.1 2024-10-28 18:12:20 +02:00
sledgehammer999
f52947e27e Update Changelog 2024-10-28 18:09:38 +02:00
sledgehammer999
315e88aee9 Sync translations from Transifex and run lupdate 2024-10-28 18:08:55 +02:00
Vladimir Golovnev
9104351c89 Backport changes to v5.0.x branch
PR #21679.
2024-10-24 12:55:50 +03:00
sledgehammer999
e58b0a65d2 Merge pull request #21663 from sledgehammer999/backport_dont_ignore_ssl_errors
Don't ignore SSL errors
2024-10-24 11:02:31 +03:00
Chocobo1
878d829904 Fix button state for SSL certificate check
A copy paste error was introduced in PR #20338.

PR #21659.
2024-10-23 08:56:55 +03:00
sledgehammer999
063f77bc6c Allow to use Qt's default QStyle
Relevant prior PR #21553

PR #21605.
2024-10-21 20:05:54 +03:00
sledgehammer999
2a4077414f Reorder code to match UI 2024-10-21 19:53:28 +03:00
sledgehammer999
2a44253802 Don't ignore SSL errors 2024-10-21 19:45:32 +03:00
Chocobo1
4712eba0dc Don't change combobox index after selection
Also keep the list sorted.

PR #21599.
2024-10-21 15:49:18 +03:00
Hanabishi
983b7814aa Add "Simple pread/pwrite" disk IO type
PR #21300.
2024-10-21 15:47:28 +03:00
Vladimir Golovnev
e082a21751 Improve color scheme change detection
* Fix pieces bars won't correctly detect color scheme change with Qt 6.8.
* Update RSS article content view on color scheme changed.

PR #21625.
Closes #21327.
2024-10-21 09:51:37 +03:00
dyseg
7dd1d1bac8 Free resources allocated by web session once it is destructed
PR #21618.
Closes #20873.
2024-10-21 09:48:24 +03:00
Chocobo1
49f57b1049 WebUI: fix 'rename files' dialog cannot be opened more than once
Added an IIFE around the whole script to suppress variable redeclaration errors.

Closes #21614.
Original PR #21620.
PR #21621.
2024-10-20 16:07:13 +08:00
Vladimir Golovnev
fbf68a0649 Correctly apply filename filter when !qB extension is enabled
PR #21628.
Closes #21624.
2024-10-19 13:39:12 +03:00
xavier2k6
39229dc06a Sync flag icons with upstream
* Release: 7.2.3
* Contains bug fixes & additional flags

PR #21220.
2024-10-14 11:54:52 +03:00
Vladimir Golovnev
bb314e1555 Correctly handle "torrent finished after move" event
PR #21596.
Closes #21576.
2024-10-14 11:52:56 +03:00
Vladimir Golovnev
a3a8b15828 Always notify user about duplicate torrent
PR #21480.
Closes #21475.
2024-10-14 11:52:47 +03:00
Vladimir Golovnev
b579afe1aa Allow to choose Qt style
PR #21553.
2024-10-11 16:09:59 +03:00
stalkerok
93096dba56 Disable the ability to create torrents with a piece size of 256MiB
Disabling will reduce the number of users experiencing this issue.
https://github.com/qbittorrent/qBittorrent/issues/21011

PR #21295.
2024-10-10 15:40:05 +03:00
Vladimir Golovnev
6379c33964 Disable "Move to trash" option by default
PR #21528.
2024-10-10 14:16:37 +03:00
Chocobo1
84372de675 Import correct libraries
Fixes "plugin not supported" errors with python 3.8.

PR #21539.
2024-10-10 16:29:51 +08:00
skomerko
403b7c7c35 WebUI: Use proper text color to highlight items in all filter lists
Previously, text color of selected filter items was not applied correctly in all situations, making them difficult to read.
This improves existing styles so that text is always correctly distinguished from the background.

This fixes issue from second post in https://github.com/qbittorrent/qBittorrent/issues/21426

PR #21507.
2024-10-07 22:13:50 +08:00
skomerko
b2fab43865 WebUI: Don't load Tabs & dynamicTable stylesheets in Properties panel
This removes duplicate stylesheet imports that caused the transfer list to be completely collapsed in Chrome-based browsers.

Closes #21426.
PR #21506.
2024-10-07 22:03:54 +08:00
Vladimir Golovnev
387821267f Don't try to apply Mark-of-the-Web to nonexistent files
Trying to apply it to a nonexistent file is unacceptable, as it may unexpectedly create such a file.

PR #21488.
Closes #21440.
2024-10-05 12:28:09 +03:00
Vladimir Golovnev
dd7ef8e934 WebUI: Fix incorrect row ID
Incorrect row ID prevented the "Torrent content removing mode" option from being displayed on some platforms.

PR #21481.
2024-10-04 14:11:45 +03:00
sledgehammer999
cce295faeb Bump to 5.0.0 2024-09-29 20:53:45 +03:00
sledgehammer999
db5479ea01 Update Changelog 2024-09-29 20:49:43 +03:00
sledgehammer999
e1216c4c9a Sync translations from Transifex and run lupdate 2024-09-29 20:36:58 +03:00
sledgehammer999
f4a0868426 Make Program Updater choose the same build for download
We're probably stuck offering the duo of RC_1_2 and RC_2_0 for some
time in the future. So hardcode the choices and make the Program Updater
choose the variant the user currently uses.
2024-09-29 20:28:10 +03:00
sledgehammer999
59a5fcf7d0 Sync translations from Transifex and run lupdate 2024-09-13 11:10:38 +03:00
Vladimir Golovnev
f9a2b02a8d Backport changes to v5.0.x branch
PR #21241.
2024-09-12 08:42:52 +03:00
skomerko
04f6a565f3 WebUI: Provide 'Merge trackers to existing torrent' option
PR #21302.
2024-09-11 19:18:17 +03:00
Vladimir Golovnev
3e96048ee4 Apply "merge trackers" logic regardless of way the torrent is added
PR #21299.
2024-09-07 09:13:19 +04:00
Prince Gupta
d4ccf3001c Fix highlighted piece color
PR #20971.
2024-09-02 16:17:57 +03:00
skomerko
64506f16bd WebUI: Provide 'Use Category paths in Manual Mode' option
PR #21223.
2024-08-26 13:10:05 +03:00
sledgehammer999
24a7a835af Create new resources for this branch for Transifex 2024-08-26 01:11:05 +03:00
sledgehammer999
93b9bf9552 Sync translations from Transifex and run lupdate 2024-08-26 01:05:21 +03:00
Vladimir Golovnev
f4125601de Refresh search results colors once color scheme is changed
* Refresh search results colors once color scheme is changed
* Improve color of visited search result items

PR #21189.
Closes #21187.
2024-08-21 15:12:07 +03:00
550 changed files with 118374 additions and 109422 deletions

View File

@@ -95,7 +95,7 @@ jobs:
- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
version: "6.7.0"
version: "6.7.3"
archives: qtbase qtsvg qttools
cache: true

View File

@@ -1,7 +1,7 @@
[main]
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
source_file = src/lang/qbittorrent_en.ts
source_lang = en
@@ -9,7 +9,7 @@ type = QT
minimum_perc = 23
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
source_file = src/webui/www/translations/webui_en.ts
source_lang = en

View File

@@ -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: Improve tracker entries handling (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: 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: 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: Do not exit the app when WebUI has failed to start (Hanabishi)
- 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: Restore search tabs 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: Add columns `Incomplete Save Path`, `Info Hash v1`, `Info Hash v2` (thalieht)
- 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: Send binary data with filename and mime type specified (glassez)
- WEBAPI: Expose API for the torrent creator (glassez, Radu Carpa)

2
dist/mac/Info.plist vendored
View File

@@ -55,7 +55,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>5.0.0</string>
<string>5.0.1</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>

File diff suppressed because it is too large Load Diff

View File

@@ -62,6 +62,6 @@
<url type="contribute">https://github.com/qbittorrent/qBittorrent/blob/master/CONTRIBUTING.md</url>
<content_rating type="oars-1.1"/>
<releases>
<release version="5.0.0~rc1" date="2024-08-18"/>
<release version="5.0.1" date="2024-10-28"/>
</releases>
</component>

View File

@@ -14,7 +14,7 @@
; 4.5.1.3 -> good
; 4.5.1.3.2 -> 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
; If set, its value will be used like this:

View File

@@ -1,6 +1,6 @@
/*
* 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>
*
* This program is free software; you can redistribute it and/or
@@ -58,10 +58,6 @@
#include <QSplashScreen>
#include <QTimer>
#ifdef Q_OS_WIN
#include <QOperatingSystemVersion>
#endif
#ifdef QBT_STATIC_QT
#include <QtPlugin>
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
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
std::unique_ptr<Application> app;
try

View File

@@ -157,10 +157,36 @@ void AddTorrentManager::handleAddTorrentFailed(const QString &source, const QStr
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")
.arg(source, torrent->name(), message));
.arg(source, existingTorrent->name(), message));
emit addTorrentFailed(source, message);
}
@@ -184,32 +210,7 @@ bool AddTorrentManager::processTorrent(const QString &source, const BitTorrent::
if (BitTorrent::Torrent *torrent = btSession()->findTorrent(infoHash))
{
// a duplicate torrent is being added
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"));
handleDuplicateTorrent(source, torrentDescr, torrent);
return false;
}

View File

@@ -72,7 +72,7 @@ protected:
bool addTorrentToSession(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
, const BitTorrent::AddTorrentParams &addTorrentParams);
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 releaseTorrentFileGuard(const QString &source);

View File

@@ -92,7 +92,8 @@ namespace BitTorrent
{
Default = 0,
MMap = 1,
Posix = 2
Posix = 2,
SimplePreadPwrite = 3
};
Q_ENUM_NS(DiskIOType)

View File

@@ -526,7 +526,7 @@ SessionImpl::SessionImpl(QObject *parent)
, m_I2POutboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/OutboundQuantity"_s), 3}
, m_I2PInboundLength {BITTORRENT_SESSION_KEY(u"I2P/InboundLength"_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_seedingLimitTimer {new QTimer(this)}
, m_resumeDataTimer {new QTimer(this)}
@@ -1638,6 +1638,13 @@ void SessionImpl::initializeNativeSession()
#ifdef QBT_USES_LIBTORRENT2
// preserve the same behavior as in earlier libtorrent versions
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
lt::session_params sessionParams {std::move(pack), {}};
@@ -1648,6 +1655,7 @@ void SessionImpl::initializeNativeSession()
sessionParams.disk_io_constructor = customPosixDiskIOConstructor;
break;
case DiskIOType::MMap:
case DiskIOType::SimplePreadPwrite:
sessionParams.disk_io_constructor = customMMapDiskIOConstructor;
break;
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)))
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;
}
// It looks illogical that we don't just use an existing handle,
// 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;
}
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)
{
const Path actualDownloadPath = useAutoTMM
@@ -2784,24 +2836,12 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
isFindingIncompleteFiles = true;
}
const auto nativeIndexes = torrentInfo.nativeIndexes();
if (!isFindingIncompleteFiles)
{
for (int index = 0; index < filePaths.size(); ++index)
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
// Use qBittorrent default priority rather than libtorrent's (4)
p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
@@ -5175,6 +5215,9 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
if (torrent)
{
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)
{
@@ -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
{
QJsonObject jsonObj;
@@ -6077,28 +6146,7 @@ void SessionImpl::handleStateUpdateAlert(const lt::state_update_alert *alert)
if (!updatedTorrents.isEmpty())
emit torrentsUpdated(updatedTorrents);
if (!m_pendingFinishedTorrents.isEmpty())
{
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();
}
processPendingFinishedTorrents();
if (m_needSaveTorrentsQueue)
saveTorrentsQueue();

View File

@@ -593,6 +593,7 @@ namespace BitTorrent
void moveTorrentStorage(const MoveStorageJob &job) const;
void handleMoveTorrentStorageJobFinished(const Path &newPath);
void processPendingFinishedTorrents();
void loadCategories();
void storeCategories() const;

View File

@@ -1819,7 +1819,7 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
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)
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(m_filePriorities[i]);

View File

@@ -148,10 +148,20 @@ Net::DownloadManager::DownloadManager(QObject *parent)
QStringList errorList;
for (const QSslError &error : errors)
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
reply->ignoreSslErrors();
QString errorMsg;
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

View File

@@ -43,7 +43,6 @@ class QSslSocket;
#else
class QTcpSocket;
#endif
class QTextCodec;
namespace Net
{

View File

@@ -429,6 +429,19 @@ void Preferences::setWinStartup(const bool b)
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
// Downloads
@@ -1330,6 +1343,19 @@ void Preferences::setMarkOfTheWebEnabled(const bool 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
{
return value(u"Preferences/Search/pythonExecutablePath"_s, Path());

View File

@@ -130,6 +130,8 @@ public:
#ifdef Q_OS_WIN
bool WinStartup() const;
void setWinStartup(bool b);
QString getStyle() const;
void setStyle(const QString &styleName);
#endif
// Downloads
@@ -293,6 +295,8 @@ public:
void setTrackerPortForwardingEnabled(bool enabled);
bool isMarkOfTheWebEnabled() const;
void setMarkOfTheWebEnabled(bool enabled);
bool isIgnoreSSLErrors() const;
void setIgnoreSSLErrors(bool enabled);
Path getPythonExecutablePath() const;
void setPythonExecutablePath(const Path &path);
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)

View File

@@ -271,6 +271,11 @@ Path Utils::OS::windowsSystemPath()
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
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:"));
#ifdef Q_OS_MACOS

View File

@@ -30,9 +30,9 @@
#define QBT_VERSION_MAJOR 5
#define QBT_VERSION_MINOR 0
#define QBT_VERSION_BUGFIX 0
#define QBT_VERSION_BUGFIX 1
#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) QBT__STRINGIFY(x)

View File

@@ -105,6 +105,7 @@ namespace
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
ENABLE_MARK_OF_THE_WEB,
#endif // Q_OS_MACOS || Q_OS_WIN
IGNORE_SSL_ERRORS,
PYTHON_EXECUTABLE_PATH,
START_SESSION_PAUSED,
SESSION_SHUTDOWN_TIMEOUT,
@@ -332,6 +333,8 @@ void AdvancedSettings::saveAdvancedSettings() const
// Mark-of-the-Web
pref->setMarkOfTheWebEnabled(m_checkBoxMarkOfTheWeb.isChecked());
#endif // Q_OS_MACOS || Q_OS_WIN
// Ignore SSL errors
pref->setIgnoreSSLErrors(m_checkBoxIgnoreSSLErrors.isChecked());
// Python executable path
pref->setPythonExecutablePath(Path(m_pythonExecutablePath.text().trimmed()));
// Start session paused
@@ -585,6 +588,7 @@ void AdvancedSettings::loadAdvancedSettings()
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("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())));
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);
@@ -853,6 +857,10 @@ void AdvancedSettings::loadAdvancedSettings()
m_checkBoxMarkOfTheWeb.setChecked(pref->isMarkOfTheWebEnabled());
addRow(ENABLE_MARK_OF_THE_WEB, motwLabel, &m_checkBoxMarkOfTheWeb);
#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
m_pythonExecutablePath.setPlaceholderText(tr("(Auto detect if empty)"));
m_pythonExecutablePath.setText(pref->getPythonExecutablePath().toString());

View File

@@ -77,9 +77,10 @@ private:
m_spinBoxSavePathHistoryLength, m_spinBoxPeerTurnover, m_spinBoxPeerTurnoverCutoff, m_spinBoxPeerTurnoverInterval, m_spinBoxRequestQueueSize;
QCheckBox m_checkBoxOsCache, m_checkBoxRecheckCompleted, m_checkBoxResolveCountries, m_checkBoxResolveHosts,
m_checkBoxProgramNotifications, m_checkBoxTorrentAddedNotifications, m_checkBoxReannounceWhenAddressChanged, m_checkBoxTrackerFavicon, m_checkBoxTrackerStatus,
m_checkBoxTrackerPortForwarding, m_checkBoxConfirmTorrentRecheck, m_checkBoxConfirmRemoveAllTags, m_checkBoxAnnounceAllTrackers, m_checkBoxAnnounceAllTiers,
m_checkBoxMultiConnectionsPerIp, m_checkBoxValidateHTTPSTrackerCertificate, m_checkBoxSSRFMitigation, m_checkBoxBlockPeersOnPrivilegedPorts, m_checkBoxPieceExtentAffinity,
m_checkBoxSuggestMode, m_checkBoxSpeedWidgetEnabled, m_checkBoxIDNSupport, m_checkBoxConfirmRemoveTrackerFromAllTorrents, m_checkBoxStartSessionPaused;
m_checkBoxTrackerPortForwarding, m_checkBoxIgnoreSSLErrors, m_checkBoxConfirmTorrentRecheck, m_checkBoxConfirmRemoveAllTags, m_checkBoxAnnounceAllTrackers,
m_checkBoxAnnounceAllTiers, m_checkBoxMultiConnectionsPerIp, m_checkBoxValidateHTTPSTrackerCertificate, m_checkBoxSSRFMitigation, m_checkBoxBlockPeersOnPrivilegedPorts,
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,
m_comboBoxSeedChokingAlgorithm, m_comboBoxResumeDataStorage, m_comboBoxTorrentContentRemoveOption;
QLineEdit m_lineEditAppInstanceName, m_pythonExecutablePath, m_lineEditAnnounceIP, m_lineEditDHTBootstrapNodes;

View File

@@ -175,7 +175,8 @@ void GUIAddTorrentManager::onMetadataDownloaded(const BitTorrent::TorrentInfo &m
}
}
bool GUIAddTorrentManager::processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams &params)
bool GUIAddTorrentManager::processTorrent(const QString &source
, const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams &params)
{
const bool hasMetadata = torrentDescr.info().has_value();
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
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
torrent->setMetadata(*torrentDescr.info());
}
if (hasMetadata)
{
// Trying to set metadata to existing torrent in case if it has none
torrent->setMetadata(*torrentDescr.info());
}
if (torrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate()))
{
handleDuplicateTorrent(source, torrent, tr("Trackers cannot be merged because it is a private torrent"));
const bool isPrivate = torrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate());
const QString dialogCaption = tr("Torrent is already present");
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
{
bool mergeTrackers = btSession()->isMergeTrackersEnabled();
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());
}
handleDuplicateTorrent(source, torrentDescr, torrent);
}
return false;

View File

@@ -30,6 +30,7 @@
#include "optionsdialog.h"
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <limits>
@@ -44,6 +45,10 @@
#include <QSystemTrayIcon>
#include <QTranslator>
#ifdef Q_OS_WIN
#include <QStyleFactory>
#endif
#include "base/bittorrent/session.h"
#include "base/bittorrent/sharelimitaction.h"
#include "base/exceptions.h"
@@ -56,6 +61,7 @@
#include "base/rss/rss_session.h"
#include "base/torrentfileguard.h"
#include "base/torrentfileswatcher.h"
#include "base/utils/compare.h"
#include "base/utils/io.h"
#include "base/utils/misc.h"
#include "base/utils/net.h"
@@ -236,6 +242,8 @@ void OptionsDialog::loadBehaviorTabOptions()
initializeLanguageCombo();
setLocale(pref->getLocale());
initializeStyleCombo();
m_ui->checkUseCustomTheme->setChecked(Preferences::instance()->useCustomUITheme());
m_ui->customThemeFilePath->setSelectedPath(Preferences::instance()->customUIThemePath());
m_ui->customThemeFilePath->setMode(FileSystemPathEdit::Mode::FileOpen);
@@ -345,7 +353,11 @@ void OptionsDialog::loadBehaviorTabOptions()
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))
connect(m_ui->checkUseSystemIcon, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
@@ -443,6 +455,13 @@ void OptionsDialog::saveBehaviorTabOptions() const
}
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))
pref->useSystemIcons(m_ui->checkUseSystemIcon->isChecked());
#endif
@@ -1387,7 +1406,7 @@ void OptionsDialog::initializeLanguageCombo()
for (const QString &langFile : langFiles)
{
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();
}
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
bool OptionsDialog::WinStartup() const
{
@@ -1721,7 +1767,7 @@ QString OptionsDialog::getProxyPassword() const
// Locale Settings
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)
@@ -1746,7 +1792,7 @@ void OptionsDialog::setLocale(const QString &localeStr)
name = locale.name();
}
// 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)
{
//Attempt to find a language match without a country
@@ -1754,16 +1800,16 @@ void OptionsDialog::setLocale(const QString &localeStr)
if (pos > -1)
{
QString lang = name.left(pos);
index = m_ui->comboI18n->findData(lang, Qt::UserRole);
index = m_ui->comboLanguage->findData(lang, Qt::UserRole);
}
}
if (index < 0)
{
// 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);
}
m_ui->comboI18n->setCurrentIndex(index);
m_ui->comboLanguage->setCurrentIndex(index);
}
Path OptionsDialog::getTorrentExportDir() const
@@ -1869,7 +1915,7 @@ Path OptionsDialog::getFilter() const
void OptionsDialog::webUIHttpsCertChanged(const Path &path)
{
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->lblSslCertStatus->setPixmap(UIThemeManager::instance()->getScaledPixmap(

View File

@@ -1,6 +1,6 @@
/*
* 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>
*
* This program is free software; you can redistribute it and/or
@@ -143,6 +143,7 @@ private:
// General options
void initializeLanguageCombo();
void initializeStyleCombo();
QString getLocale() const;
bool isSplashScreenDisabled() const;
#ifdef Q_OS_WIN

View File

@@ -132,9 +132,9 @@
<property name="title">
<string>Interface</string>
</property>
<layout class="QGridLayout" name="gridLayout_81">
<layout class="QGridLayout" name="UISettingsBoxLayout">
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="label_15">
<widget class="QLabel" name="labelRestartRequired">
<property name="font">
<font>
<italic>true</italic>
@@ -146,17 +146,17 @@
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_9">
<widget class="QLabel" name="labelLanguage">
<property name="text">
<string>Language:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboI18n"/>
<widget class="QComboBox" name="comboLanguage"/>
</item>
<item row="1" column="2">
<spacer name="horizontalSpacer_111">
<spacer name="spacerLanguage">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@@ -168,7 +168,26 @@
</property>
</spacer>
</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">
<property name="title">
<string>Use custom UI Theme</string>
@@ -190,14 +209,14 @@
</layout>
</widget>
</item>
<item row="3" column="0" colspan="2">
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="checkUseSystemIcon">
<property name="text">
<string>Use icons from system theme</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<item row="5" column="0" colspan="2">
<widget class="QPushButton" name="buttonCustomizeUITheme">
<property name="text">
<string>Customize UI Theme...</string>
@@ -3881,7 +3900,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
</customwidgets>
<tabstops>
<tabstop>tabOption</tabstop>
<tabstop>comboI18n</tabstop>
<tabstop>comboLanguage</tabstop>
<tabstop>checkUseCustomTheme</tabstop>
<tabstop>customThemeFilePath</tabstop>
<tabstop>checkAddStopped</tabstop>

View File

@@ -29,6 +29,9 @@
#include "programupdater.h"
#include <libtorrent/version.hpp>
#include <QtCore/qconfig.h>
#include <QtSystemDetection>
#include <QDebug>
#include <QDesktopServices>
@@ -61,6 +64,20 @@ namespace
}
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
@@ -97,12 +114,7 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
: QString {};
};
#ifdef Q_OS_MACOS
const QString OS_TYPE = u"Mac OS X"_s;
#elif defined(Q_OS_WIN)
const QString OS_TYPE = u"Windows x64"_s;
#endif
const QString variant = buildVariant();
bool inItem = false;
QString version;
QString updateLink;
@@ -128,7 +140,7 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
{
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));
if (!version.isEmpty())

View File

@@ -42,7 +42,6 @@
#include "base/indexrange.h"
#include "base/path.h"
#include "base/utils/misc.h"
#include "gui/uithememanager.h"
namespace
{
@@ -119,13 +118,7 @@ PiecesBar::PiecesBar(QWidget *parent)
: QWidget(parent)
{
setMouseTracking(true);
updateColorsImpl();
connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, [this]
{
updateColors();
redraw();
});
}
void PiecesBar::setTorrent(const BitTorrent::Torrent *torrent)
@@ -143,12 +136,19 @@ void PiecesBar::clear()
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));
return true;
}
if (eventType == QEvent::PaletteChange)
{
updateColors();
redraw();
}
return base::event(e);
}
@@ -195,10 +195,8 @@ void PiecesBar::paintEvent(QPaintEvent *)
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)};
painter.fillRect(targetHighlightRect, highlightColor);
painter.fillRect(targetHighlightRect, highlightedPieceColor());
}
QPainterPath border;
@@ -231,6 +229,13 @@ QColor PiecesBar::pieceColor() const
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
{
return palette().color(QPalette::Active, QPalette::ToolTipText);

View File

@@ -68,7 +68,9 @@ protected:
QColor backgroundColor() const;
QColor borderColor() const;
QColor pieceColor() const;
QColor highlightedPieceColor() const;
QColor colorBoxBorderColor() const;
const QVector<QRgb> &pieceColors() const;
// mix two colors by light model, ratio <0, 1>

View File

@@ -126,6 +126,8 @@ RSSWidget::RSSWidget(IGUIApplication *app, QWidget *parent)
, this, &RSSWidget::handleSessionProcessingStateChanged);
connect(RSS::Session::instance()->rootFolder(), &RSS::Folder::unreadCountChanged
, this, &RSSWidget::handleUnreadCountChanged);
m_ui->textBrowser->installEventFilter(this);
}
RSSWidget::~RSSWidget()
@@ -494,60 +496,11 @@ void RSSWidget::handleCurrentArticleItemChanged(QListWidgetItem *currentItem, QL
article->markAsRead();
}
if (!currentItem) return;
if (!currentItem)
return;
auto *article = m_articleListWidget->getRSSArticle(currentItem);
Q_ASSERT(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);
renderArticle(article);
}
void RSSWidget::saveSlidersPosition()
@@ -590,3 +543,73 @@ void RSSWidget::handleUnreadCountChanged()
{
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);
}

View File

@@ -40,6 +40,11 @@ class QTreeWidgetItem;
class ArticleListWidget;
class FeedListWidget;
namespace RSS
{
class Article;
}
namespace Ui
{
class RSSWidget;
@@ -85,6 +90,9 @@ private slots:
void handleUnreadCountChanged();
private:
bool eventFilter(QObject *obj, QEvent *event) override;
void renderArticle(const RSS::Article *article) const;
Ui::RSSWidget *m_ui = nullptr;
ArticleListWidget *m_articleListWidget = nullptr;
FeedListWidget *m_feedListWidget = nullptr;

View File

@@ -1,6 +1,6 @@
/*
* 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>
*
* This program is free software; you can redistribute it and/or
@@ -50,6 +50,19 @@
#include "searchsortmodel.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)
: GUIApplicationComponent(app, parent)
, m_ui {new Ui::SearchJobWidget}
@@ -158,6 +171,8 @@ SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *
connect(this, &QObject::destroyed, searchHandler, &QObject::deleteLater);
setStatusTip(statusText(m_status));
connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, &SearchJobWidget::onUIThemeChanged);
}
SearchJobWidget::~SearchJobWidget()
@@ -179,9 +194,31 @@ QHeaderView *SearchJobWidget::header() const
// Set the color of a row in data model
void SearchJobWidget::setRowColor(int row, const QColor &color)
{
m_proxyModel->setDynamicSortFilter(false);
for (int i = 0; i < m_proxyModel->columnCount(); ++i)
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);
}
@@ -284,7 +321,8 @@ void SearchJobWidget::downloadTorrent(const QModelIndex &rowIndex, const AddTorr
, this, [this, option](const QString &source) { addTorrentToSession(source, option); });
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)

Some files were not shown because too many files have changed in this diff Show More