Backport changes to v5.1.x branch

PR #22268.
This commit is contained in:
Vladimir Golovnev 2025-03-16 10:39:31 +03:00 committed by GitHub
commit 76a3aba7e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 251 additions and 68 deletions

View file

@ -138,16 +138,15 @@ jobs:
- name: Install AppImage - name: Install AppImage
run: | run: |
sudo apt install libfuse2
curl \ curl \
-L \ -L \
-Z \ -Z \
-O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-static-x86_64.AppImage \ -O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage \
-O https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-static-x86_64.AppImage \ -O https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage \
-O https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage -O https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage
chmod +x \ chmod +x \
linuxdeploy-static-x86_64.AppImage \ linuxdeploy-x86_64.AppImage \
linuxdeploy-plugin-qt-static-x86_64.AppImage \ linuxdeploy-plugin-qt-x86_64.AppImage \
linuxdeploy-plugin-appimage-x86_64.AppImage linuxdeploy-plugin-appimage-x86_64.AppImage
- name: Prepare files for AppImage - name: Prepare files for AppImage
@ -160,12 +159,12 @@ jobs:
- name: Package AppImage - name: Package AppImage
run: | run: |
./linuxdeploy-static-x86_64.AppImage --appdir qbittorrent --plugin qt ./linuxdeploy-x86_64.AppImage --appdir qbittorrent --plugin qt
rm qbittorrent/apprun-hooks/* rm qbittorrent/apprun-hooks/*
cp .github/workflows/helper/appimage/export_vars.sh qbittorrent/apprun-hooks/export_vars.sh cp .github/workflows/helper/appimage/export_vars.sh qbittorrent/apprun-hooks/export_vars.sh
NO_APPSTREAM=1 \ NO_APPSTREAM=1 \
OUTPUT=upload/qbittorrent-CI_Ubuntu_x86_64.AppImage \ OUTPUT=upload/qbittorrent-CI_Ubuntu_x86_64.AppImage \
./linuxdeploy-static-x86_64.AppImage --appdir qbittorrent --output appimage ./linuxdeploy-x86_64.AppImage --appdir qbittorrent --output appimage
- name: Upload build artifacts - name: Upload build artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

View file

@ -7,21 +7,21 @@ LangString inst_desktop ${LANG_SWEDISH} "Skapa skrivbordsgenväg"
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut" ;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
LangString inst_startmenu ${LANG_SWEDISH} "Skapa startmenygenväg" LangString inst_startmenu ${LANG_SWEDISH} "Skapa startmenygenväg"
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up" ;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
LangString inst_startup ${LANG_SWEDISH} "Starta qBittorrent vid Windows start" LangString inst_startup ${LANG_SWEDISH} "Starta qBittorrent vid Windows-uppstart"
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent" ;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
LangString inst_torrent ${LANG_SWEDISH} "Öppna .torrent-filer med qBittorrent" LangString inst_torrent ${LANG_SWEDISH} "Öppna .torrent-filer med qBittorrent"
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent" ;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
LangString inst_magnet ${LANG_SWEDISH} "Öppna magnetlänkar med qBittorrent" LangString inst_magnet ${LANG_SWEDISH} "Öppna magnetlänkar med qBittorrent"
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule" ;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
LangString inst_firewall ${LANG_SWEDISH} "Lägg till Windows-brandväggregel" LangString inst_firewall ${LANG_SWEDISH} "Lägg till Windows-brandväggsregel"
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)" ;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
LangString inst_pathlimit ${LANG_SWEDISH} "Inaktivera gränsen för Windows-sökvägslängd (260 tecken MAX_PATH-begränsning, kräver Windows 10 1607 eller senare)" LangString inst_pathlimit ${LANG_SWEDISH} "Inaktivera gränsen för Windows-sökvägslängd (260 tecken MAX_PATH-begränsning, kräver Windows 10 1607 eller senare)"
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule" ;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
LangString inst_firewallinfo ${LANG_SWEDISH} "Lägger till Windows-brandväggregel" LangString inst_firewallinfo ${LANG_SWEDISH} "Lägger till Windows-brandväggsregel"
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing." ;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
LangString inst_warning ${LANG_SWEDISH} "qBittorrent körs. Vänligen stäng programmet innan du installerar." LangString inst_warning ${LANG_SWEDISH} "qBittorrent körs. Stäng programmet innan du installerar."
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact." ;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
LangString inst_uninstall_question ${LANG_SWEDISH} "Nuvarande version avinstalleras. Användarinställningar och torrenter kommer att förbli intakta." LangString inst_uninstall_question ${LANG_SWEDISH} "Aktuell version avinstalleras. Användarinställningar och torrenter kommer att förbli intakta."
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version." ;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
LangString inst_unist ${LANG_SWEDISH} "Avinstallerar tidigare version." LangString inst_unist ${LANG_SWEDISH} "Avinstallerar tidigare version."
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent." ;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
@ -53,7 +53,7 @@ LangString remove_firewallinfo ${LANG_SWEDISH} "Tar bort Windows-brandväggsrege
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data" ;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
LangString remove_cache ${LANG_SWEDISH} "Ta bort torrenter och cachade data" LangString remove_cache ${LANG_SWEDISH} "Ta bort torrenter och cachade data"
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling." ;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
LangString uninst_warning ${LANG_SWEDISH} "qBittorrent körs. Vänligen stäng programmet innan du avinstallerar." LangString uninst_warning ${LANG_SWEDISH} "qBittorrent körs. Stäng programmet innan du avinstallerar."
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:" ;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
LangString uninst_tor_warn ${LANG_SWEDISH} "Tar inte bort .torrent-association. Den är associerad med:" LangString uninst_tor_warn ${LANG_SWEDISH} "Tar inte bort .torrent-association. Den är associerad med:"
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:" ;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"

View file

@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru> * Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez * Copyright (C) 2006 Christophe Dumez
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -124,6 +124,28 @@ namespace
const int PIXMAP_CACHE_SIZE = 64 * 1024 * 1024; // 64MiB const int PIXMAP_CACHE_SIZE = 64 * 1024 * 1024; // 64MiB
#endif #endif
const QString PARAM_ADDSTOPPED = u"@addStopped"_s;
const QString PARAM_CATEGORY = u"@category"_s;
const QString PARAM_FIRSTLASTPIECEPRIORITY = u"@firstLastPiecePriority"_s;
const QString PARAM_SAVEPATH = u"@savePath"_s;
const QString PARAM_SEQUENTIAL = u"@sequential"_s;
const QString PARAM_SKIPCHECKING = u"@skipChecking"_s;
const QString PARAM_SKIPDIALOG = u"@skipDialog"_s;
QString bindParamValue(const QStringView paramName, const QStringView paramValue)
{
return paramName + u'=' + paramValue;
}
std::pair<QStringView, QStringView> parseParam(const QStringView param)
{
const qsizetype sepIndex = param.indexOf(u'=');
if (sepIndex >= 0)
return {param.first(sepIndex), param.sliced(sepIndex + 1)};
return {param, {}};
}
QString serializeParams(const QBtCommandLineParameters &params) QString serializeParams(const QBtCommandLineParameters &params)
{ {
QStringList result; QStringList result;
@ -138,85 +160,86 @@ namespace
const BitTorrent::AddTorrentParams &addTorrentParams = params.addTorrentParams; const BitTorrent::AddTorrentParams &addTorrentParams = params.addTorrentParams;
if (!addTorrentParams.savePath.isEmpty()) if (!addTorrentParams.savePath.isEmpty())
result.append(u"@savePath=" + addTorrentParams.savePath.data()); result.append(bindParamValue(PARAM_SAVEPATH, addTorrentParams.savePath.data()));
if (addTorrentParams.addStopped.has_value()) if (addTorrentParams.addStopped.has_value())
result.append(*addTorrentParams.addStopped ? u"@addStopped=1"_s : u"@addStopped=0"_s); result.append(bindParamValue(PARAM_ADDSTOPPED, (*addTorrentParams.addStopped ? u"1" : u"0")));
if (addTorrentParams.skipChecking) if (addTorrentParams.skipChecking)
result.append(u"@skipChecking"_s); result.append(PARAM_SKIPCHECKING);
if (!addTorrentParams.category.isEmpty()) if (!addTorrentParams.category.isEmpty())
result.append(u"@category=" + addTorrentParams.category); result.append(bindParamValue(PARAM_CATEGORY, addTorrentParams.category));
if (addTorrentParams.sequential) if (addTorrentParams.sequential)
result.append(u"@sequential"_s); result.append(PARAM_SEQUENTIAL);
if (addTorrentParams.firstLastPiecePriority) if (addTorrentParams.firstLastPiecePriority)
result.append(u"@firstLastPiecePriority"_s); result.append(PARAM_FIRSTLASTPIECEPRIORITY);
if (params.skipDialog.has_value()) if (params.skipDialog.has_value())
result.append(*params.skipDialog ? u"@skipDialog=1"_s : u"@skipDialog=0"_s); result.append(bindParamValue(PARAM_SKIPDIALOG, (*params.skipDialog ? u"1" : u"0")));
result += params.torrentSources; result += params.torrentSources;
return result.join(PARAMS_SEPARATOR); return result.join(PARAMS_SEPARATOR);
} }
QBtCommandLineParameters parseParams(const QString &str) QBtCommandLineParameters parseParams(const QStringView str)
{ {
QBtCommandLineParameters parsedParams; QBtCommandLineParameters parsedParams;
BitTorrent::AddTorrentParams &addTorrentParams = parsedParams.addTorrentParams; BitTorrent::AddTorrentParams &addTorrentParams = parsedParams.addTorrentParams;
for (QString param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts))) for (QStringView param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
{ {
param = param.trimmed(); param = param.trimmed();
const auto [paramName, paramValue] = parseParam(param);
// Process strings indicating options specified by the user. // Process strings indicating options specified by the user.
if (param.startsWith(u"@savePath=")) if (paramName == PARAM_SAVEPATH)
{ {
addTorrentParams.savePath = Path(param.mid(10)); addTorrentParams.savePath = Path(paramValue.toString());
continue; continue;
} }
if (param.startsWith(u"@addStopped=")) if (paramName == PARAM_ADDSTOPPED)
{ {
addTorrentParams.addStopped = (QStringView(param).mid(11).toInt() != 0); addTorrentParams.addStopped = (paramValue.toInt() != 0);
continue; continue;
} }
if (param == u"@skipChecking") if (paramName == PARAM_SKIPCHECKING)
{ {
addTorrentParams.skipChecking = true; addTorrentParams.skipChecking = true;
continue; continue;
} }
if (param.startsWith(u"@category=")) if (paramName == PARAM_CATEGORY)
{ {
addTorrentParams.category = param.mid(10); addTorrentParams.category = paramValue.toString();
continue; continue;
} }
if (param == u"@sequential") if (paramName == PARAM_SEQUENTIAL)
{ {
addTorrentParams.sequential = true; addTorrentParams.sequential = true;
continue; continue;
} }
if (param == u"@firstLastPiecePriority") if (paramName == PARAM_FIRSTLASTPIECEPRIORITY)
{ {
addTorrentParams.firstLastPiecePriority = true; addTorrentParams.firstLastPiecePriority = true;
continue; continue;
} }
if (param.startsWith(u"@skipDialog=")) if (paramName == PARAM_SKIPDIALOG)
{ {
parsedParams.skipDialog = (QStringView(param).mid(12).toInt() != 0); parsedParams.skipDialog = (paramValue.toInt() != 0);
continue; continue;
} }
parsedParams.torrentSources.append(param); parsedParams.torrentSources.append(param.toString());
} }
return parsedParams; return parsedParams;

View file

@ -30,8 +30,10 @@
#pragma once #pragma once
#include <QByteArray> #include <QByteArray>
#include <QHash>
#include <QHostAddress> #include <QHostAddress>
#include <QList> #include <QList>
#include <QMap>
#include <QString> #include <QString>
#include "base/global.h" #include "base/global.h"

View file

@ -287,6 +287,8 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
macosdockbadge/badger.mm macosdockbadge/badger.mm
macosdockbadge/badgeview.h macosdockbadge/badgeview.h
macosdockbadge/badgeview.mm macosdockbadge/badgeview.mm
macosshiftclickhandler.h
macosshiftclickhandler.cpp
macutilities.h macutilities.h
macutilities.mm macutilities.mm
) )

View file

@ -0,0 +1,73 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Luke Memet (lukemmtt)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include "macosshiftclickhandler.h"
#include <QMouseEvent>
#include <QTreeView>
MacOSShiftClickHandler::MacOSShiftClickHandler(QTreeView *treeView)
: QObject(treeView)
, m_treeView {treeView}
{
treeView->installEventFilter(this);
}
bool MacOSShiftClickHandler::eventFilter(QObject *watched, QEvent *event)
{
if ((watched == m_treeView) && (event->type() == QEvent::MouseButtonPress))
{
const auto *mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->button() != Qt::LeftButton)
return false;
const QModelIndex clickedIndex = m_treeView->indexAt(mouseEvent->position().toPoint());
if (!clickedIndex.isValid())
return false;
const Qt::KeyboardModifiers modifiers = mouseEvent->modifiers();
const bool shiftPressed = modifiers.testFlag(Qt::ShiftModifier);
if (shiftPressed && m_lastClickedIndex.isValid())
{
const QItemSelection selection(m_lastClickedIndex, clickedIndex);
const bool commandPressed = modifiers.testFlag(Qt::ControlModifier);
if (commandPressed)
m_treeView->selectionModel()->select(selection, (QItemSelectionModel::Select | QItemSelectionModel::Rows));
else
m_treeView->selectionModel()->select(selection, (QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows));
m_treeView->selectionModel()->setCurrentIndex(clickedIndex, QItemSelectionModel::NoUpdate);
return true;
}
if (!modifiers.testFlags(Qt::AltModifier | Qt::MetaModifier))
m_lastClickedIndex = clickedIndex;
}
return QObject::eventFilter(watched, event);
}

View file

@ -0,0 +1,50 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2025 Luke Memet (lukemmtt)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#pragma once
#include <QObject>
#include <QPersistentModelIndex>
class QTreeView;
// Workaround for QTBUG-115838: Shift-click range selection not working properly on macOS
class MacOSShiftClickHandler final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(MacOSShiftClickHandler)
public:
explicit MacOSShiftClickHandler(QTreeView *treeView);
private:
bool eventFilter(QObject *watched, QEvent *event) override;
QTreeView *m_treeView = nullptr;
QPersistentModelIndex m_lastClickedIndex;
};

View file

@ -74,6 +74,7 @@
#include "utils.h" #include "utils.h"
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
#include "macosshiftclickhandler.h"
#include "macutilities.h" #include "macutilities.h"
#endif #endif
@ -158,6 +159,7 @@ TransferListWidget::TransferListWidget(IGUIApplication *app, QWidget *parent)
setDropIndicatorShown(true); setDropIndicatorShown(true);
#if defined(Q_OS_MACOS) #if defined(Q_OS_MACOS)
setAttribute(Qt::WA_MacShowFocusRect, false); setAttribute(Qt::WA_MacShowFocusRect, false);
new MacOSShiftClickHandler(this);
#endif #endif
header()->setFirstSectionMovable(true); header()->setFirstSectionMovable(true);
header()->setStretchLastSection(false); header()->setStretchLastSection(false);

View file

@ -80,7 +80,33 @@ inline QHash<QString, UIThemeColor> defaultUIThemeColors()
{u"TransferList.StoppedUploading"_s, {Color::Primer::Light::doneFg, Color::Primer::Dark::doneFg}}, {u"TransferList.StoppedUploading"_s, {Color::Primer::Light::doneFg, Color::Primer::Dark::doneFg}},
{u"TransferList.Moving"_s, {Color::Primer::Light::successFg, Color::Primer::Dark::successFg}}, {u"TransferList.Moving"_s, {Color::Primer::Light::successFg, Color::Primer::Dark::successFg}},
{u"TransferList.MissingFiles"_s, {Color::Primer::Light::dangerFg, Color::Primer::Dark::dangerFg}}, {u"TransferList.MissingFiles"_s, {Color::Primer::Light::dangerFg, Color::Primer::Dark::dangerFg}},
{u"TransferList.Error"_s, {Color::Primer::Light::dangerFg, Color::Primer::Dark::dangerFg}} {u"TransferList.Error"_s, {Color::Primer::Light::dangerFg, Color::Primer::Dark::dangerFg}},
{u"Palette.Window"_s, {{}, {}}},
{u"Palette.WindowText"_s, {{}, {}}},
{u"Palette.Base"_s, {{}, {}}},
{u"Palette.AlternateBase"_s, {{}, {}}},
{u"Palette.Text"_s, {{}, {}}},
{u"Palette.ToolTipBase"_s, {{}, {}}},
{u"Palette.ToolTipText"_s, {{}, {}}},
{u"Palette.BrightText"_s, {{}, {}}},
{u"Palette.Highlight"_s, {{}, {}}},
{u"Palette.HighlightedText"_s, {{}, {}}},
{u"Palette.Button"_s, {{}, {}}},
{u"Palette.ButtonText"_s, {{}, {}}},
{u"Palette.Link"_s, {{}, {}}},
{u"Palette.LinkVisited"_s, {{}, {}}},
{u"Palette.Light"_s, {{}, {}}},
{u"Palette.Midlight"_s, {{}, {}}},
{u"Palette.Mid"_s, {{}, {}}},
{u"Palette.Dark"_s, {{}, {}}},
{u"Palette.Shadow"_s, {{}, {}}},
{u"Palette.WindowTextDisabled"_s, {{}, {}}},
{u"Palette.TextDisabled"_s, {{}, {}}},
{u"Palette.ToolTipTextDisabled"_s, {{}, {}}},
{u"Palette.BrightTextDisabled"_s, {{}, {}}},
{u"Palette.HighlightedTextDisabled"_s, {{}, {}}},
{u"Palette.ButtonTextDisabled"_s, {{}, {}}}
}; };
} }

View file

@ -49,7 +49,7 @@ void RSSController::addFolderAction()
{ {
requireParams({u"path"_s}); requireParams({u"path"_s});
const QString path = params()[u"path"_s].trimmed(); const QString path = params()[u"path"_s];
const nonstd::expected<void, QString> result = RSS::Session::instance()->addFolder(path); const nonstd::expected<void, QString> result = RSS::Session::instance()->addFolder(path);
if (!result) if (!result)
throw APIError(APIErrorType::Conflict, result.error()); throw APIError(APIErrorType::Conflict, result.error());
@ -59,8 +59,8 @@ void RSSController::addFeedAction()
{ {
requireParams({u"url"_s, u"path"_s}); requireParams({u"url"_s, u"path"_s});
const QString url = params()[u"url"_s].trimmed(); const QString url = params()[u"url"_s];
const QString path = params()[u"path"_s].trimmed(); const QString path = params()[u"path"_s];
const nonstd::expected<void, QString> result = RSS::Session::instance()->addFeed(url, (path.isEmpty() ? url : path)); const nonstd::expected<void, QString> result = RSS::Session::instance()->addFeed(url, (path.isEmpty() ? url : path));
if (!result) if (!result)
throw APIError(APIErrorType::Conflict, result.error()); throw APIError(APIErrorType::Conflict, result.error());
@ -70,8 +70,8 @@ void RSSController::setFeedURLAction()
{ {
requireParams({u"path"_s, u"url"_s}); requireParams({u"path"_s, u"url"_s});
const QString path = params()[u"path"_s].trimmed(); const QString path = params()[u"path"_s];
const QString url = params()[u"url"_s].trimmed(); const QString url = params()[u"url"_s];
const nonstd::expected<void, QString> result = RSS::Session::instance()->setFeedURL(path, url); const nonstd::expected<void, QString> result = RSS::Session::instance()->setFeedURL(path, url);
if (!result) if (!result)
throw APIError(APIErrorType::Conflict, result.error()); throw APIError(APIErrorType::Conflict, result.error());
@ -81,7 +81,7 @@ void RSSController::removeItemAction()
{ {
requireParams({u"path"_s}); requireParams({u"path"_s});
const QString path = params()[u"path"_s].trimmed(); const QString path = params()[u"path"_s];
const nonstd::expected<void, QString> result = RSS::Session::instance()->removeItem(path); const nonstd::expected<void, QString> result = RSS::Session::instance()->removeItem(path);
if (!result) if (!result)
throw APIError(APIErrorType::Conflict, result.error()); throw APIError(APIErrorType::Conflict, result.error());
@ -91,8 +91,8 @@ void RSSController::moveItemAction()
{ {
requireParams({u"itemPath"_s, u"destPath"_s}); requireParams({u"itemPath"_s, u"destPath"_s});
const QString itemPath = params()[u"itemPath"_s].trimmed(); const QString itemPath = params()[u"itemPath"_s];
const QString destPath = params()[u"destPath"_s].trimmed(); const QString destPath = params()[u"destPath"_s];
const nonstd::expected<void, QString> result = RSS::Session::instance()->moveItem(itemPath, destPath); const nonstd::expected<void, QString> result = RSS::Session::instance()->moveItem(itemPath, destPath);
if (!result) if (!result)
throw APIError(APIErrorType::Conflict, result.error()); throw APIError(APIErrorType::Conflict, result.error());
@ -146,8 +146,8 @@ void RSSController::setRuleAction()
{ {
requireParams({u"ruleName"_s, u"ruleDef"_s}); requireParams({u"ruleName"_s, u"ruleDef"_s});
const QString ruleName {params()[u"ruleName"_s].trimmed()}; const QString ruleName {params()[u"ruleName"_s]};
const QByteArray ruleDef {params()[u"ruleDef"_s].trimmed().toUtf8()}; const QByteArray ruleDef {params()[u"ruleDef"_s].toUtf8()};
const auto jsonObj = QJsonDocument::fromJson(ruleDef).object(); const auto jsonObj = QJsonDocument::fromJson(ruleDef).object();
RSS::AutoDownloader::instance()->setRule(RSS::AutoDownloadRule::fromJsonObject(jsonObj, ruleName)); RSS::AutoDownloader::instance()->setRule(RSS::AutoDownloadRule::fromJsonObject(jsonObj, ruleName));
@ -157,8 +157,8 @@ void RSSController::renameRuleAction()
{ {
requireParams({u"ruleName"_s, u"newRuleName"_s}); requireParams({u"ruleName"_s, u"newRuleName"_s});
const QString ruleName {params()[u"ruleName"_s].trimmed()}; const QString ruleName {params()[u"ruleName"_s]};
const QString newRuleName {params()[u"newRuleName"_s].trimmed()}; const QString newRuleName {params()[u"newRuleName"_s]};
RSS::AutoDownloader::instance()->renameRule(ruleName, newRuleName); RSS::AutoDownloader::instance()->renameRule(ruleName, newRuleName);
} }
@ -167,7 +167,7 @@ void RSSController::removeRuleAction()
{ {
requireParams({u"ruleName"_s}); requireParams({u"ruleName"_s});
const QString ruleName {params()[u"ruleName"_s].trimmed()}; const QString ruleName {params()[u"ruleName"_s]};
RSS::AutoDownloader::instance()->removeRule(ruleName); RSS::AutoDownloader::instance()->removeRule(ruleName);
} }

View file

@ -100,6 +100,7 @@ ol {
.dynamicTableDiv, .dynamicTableDiv,
.mochaContentWrapper, .mochaContentWrapper,
.panel, .panel,
.scrollableMenu,
#rssDetailsView { #rssDetailsView {
scrollbar-width: thin; scrollbar-width: thin;
} }

View file

@ -21,7 +21,7 @@
} = window.MUI.Windows.instances["multiRenamePage"]; } = window.MUI.Windows.instances["multiRenamePage"];
const bulkRenameFilesContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({ const bulkRenameFilesContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
targets: "#bulkRenameFilesTableDiv tr", targets: "#bulkRenameFilesTableDiv tbody tr",
menu: "multiRenameFilesMenu", menu: "multiRenameFilesMenu",
actions: { actions: {
ToggleSelection: (element, ref) => { ToggleSelection: (element, ref) => {

View file

@ -294,6 +294,7 @@ window.qBittorrent.DynamicTable ??= (() => {
this.updateTableHeaders(); this.updateTableHeaders();
this.tableBody.replaceChildren(); this.tableBody.replaceChildren();
this.updateTable(true); this.updateTable(true);
this.reselectRows(this.selectedRowsIds());
} }
if (this.currentHeaderAction === "drag") { if (this.currentHeaderAction === "drag") {
resetElementBorderStyle(el); resetElementBorderStyle(el);
@ -750,10 +751,7 @@ window.qBittorrent.DynamicTable ??= (() => {
reselectRows: function(rowIds) { reselectRows: function(rowIds) {
this.deselectAll(); this.deselectAll();
this.selectedRows = rowIds.slice(); this.selectedRows = rowIds.slice();
for (const tr of this.getTrs()) { this.setRowClass();
if (rowIds.includes(tr.rowId))
tr.classList.add("selected");
}
}, },
setRowClass: function() { setRowClass: function() {
@ -1752,7 +1750,7 @@ window.qBittorrent.DynamicTable ??= (() => {
td.append(span); td.append(span);
} }
span.style.backgroundImage = `url('images/flags/${country_code ?? "xx"}.svg')`; span.style.backgroundImage = `url('images/flags/${country_code || "xx"}.svg')`;
span.textContent = country; span.textContent = country;
td.title = country; td.title = country;
}; };
@ -2058,7 +2056,11 @@ window.qBittorrent.DynamicTable ??= (() => {
break; break;
} }
td.className = statusClass; for (const c of [...td.classList]) {
if (c.startsWith("tracker"))
td.classList.remove(c);
}
td.classList.add(statusClass);
td.textContent = status; td.textContent = status;
td.title = status; td.title = status;
}; };

View file

@ -575,7 +575,7 @@ window.qBittorrent.PropFiles ??= (() => {
}; };
const torrentFilesContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({ const torrentFilesContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
targets: "#torrentFilesTableDiv tr", targets: "#torrentFilesTableDiv tbody tr",
menu: "torrentFilesMenu", menu: "torrentFilesMenu",
actions: { actions: {
Rename: (element, ref) => { Rename: (element, ref) => {

View file

@ -196,7 +196,7 @@ window.qBittorrent.PropTrackers ??= (() => {
if (current_hash.length === 0) if (current_hash.length === 0)
return; return;
const trackerUrl = encodeURIComponent(element.childNodes[1].textContent); const trackerUrl = encodeURIComponent(torrentTrackersTable.selectedRowsIds()[0]);
new MochaUI.Window({ new MochaUI.Window({
id: "trackersPage", id: "trackersPage",
icon: "images/qbittorrent-tray.svg", icon: "images/qbittorrent-tray.svg",

View file

@ -97,8 +97,8 @@ window.qBittorrent.Search ??= (() => {
} }
}, },
offsets: { offsets: {
x: -15, x: 2,
y: -53 y: -60
}, },
onShow: function() { onShow: function() {
setActiveTab(this.options.element); setActiveTab(this.options.element);
@ -109,7 +109,7 @@ window.qBittorrent.Search ??= (() => {
// load "Search in" preference from local storage // load "Search in" preference from local storage
$("searchInTorrentName").value = (LocalPreferences.get("search_in_filter") === "names") ? "names" : "everywhere"; $("searchInTorrentName").value = (LocalPreferences.get("search_in_filter") === "names") ? "names" : "everywhere";
const searchResultsTableContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({ const searchResultsTableContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
targets: "#searchResultsTableDiv tr", targets: "#searchResultsTableDiv tbody tr",
menu: "searchResultsTableMenu", menu: "searchResultsTableMenu",
actions: { actions: {
Download: downloadSearchTorrent, Download: downloadSearchTorrent,
@ -184,7 +184,10 @@ window.qBittorrent.Search ??= (() => {
closeTabElem.src = "images/application-exit.svg"; closeTabElem.src = "images/application-exit.svg";
closeTabElem.width = "10"; closeTabElem.width = "10";
closeTabElem.height = "10"; closeTabElem.height = "10";
closeTabElem.addEventListener("click", function(e) { qBittorrent.Search.closeSearchTab(this); }); closeTabElem.addEventListener("click", function(e) {
e.stopPropagation();
closeSearchTab(this);
});
tabElem.prepend(closeTabElem); tabElem.prepend(closeTabElem);
tabElem.appendChild(getStatusIconElement("QBT_TR(Searching...)QBT_TR[CONTEXT=SearchJobWidget]", "images/queued.svg")); tabElem.appendChild(getStatusIconElement("QBT_TR(Searching...)QBT_TR[CONTEXT=SearchJobWidget]", "images/queued.svg"));

View file

@ -98,7 +98,7 @@
} }
}, },
offsets: { offsets: {
x: -15, x: 0,
y: 2 y: 2
}, },
onShow: function() { onShow: function() {

View file

@ -206,7 +206,7 @@
}); });
const logTableContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({ const logTableContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({
targets: ":is(#logMessageView, #logPeerView) tr", targets: ":is(#logMessageTableDiv, #logPeerTableDiv) tbody tr",
menu: "logTableMenu", menu: "logTableMenu",
actions: { actions: {
Clear: () => { Clear: () => {

View file

@ -218,7 +218,7 @@
$("rssFetchingDisabled").classList.remove("invisible"); $("rssFetchingDisabled").classList.remove("invisible");
const rssFeedContextMenu = new window.qBittorrent.ContextMenu.RssFeedContextMenu({ const rssFeedContextMenu = new window.qBittorrent.ContextMenu.RssFeedContextMenu({
targets: "#rssFeedTableDiv tr", targets: "#rssFeedTableDiv tbody tr",
menu: "rssFeedMenu", menu: "rssFeedMenu",
actions: { actions: {
update: (el) => { update: (el) => {
@ -288,7 +288,7 @@
rssFeedTable.setup("rssFeedTableDiv", "rssFeedFixedHeaderDiv", rssFeedContextMenu); rssFeedTable.setup("rssFeedTableDiv", "rssFeedFixedHeaderDiv", rssFeedContextMenu);
const rssArticleContextMenu = new window.qBittorrent.ContextMenu.RssArticleContextMenu({ const rssArticleContextMenu = new window.qBittorrent.ContextMenu.RssArticleContextMenu({
targets: "#rssArticleTableDiv tr", targets: "#rssArticleTableDiv tbody tr",
menu: "rssArticleMenu", menu: "rssArticleMenu",
actions: { actions: {
Download: (el) => { Download: (el) => {

View file

@ -94,7 +94,7 @@
const setup = () => { const setup = () => {
searchPluginsTable = new window.qBittorrent.DynamicTable.SearchPluginsTable(); searchPluginsTable = new window.qBittorrent.DynamicTable.SearchPluginsTable();
searchPluginsTableContextMenu = new window.qBittorrent.ContextMenu.SearchPluginsTableContextMenu({ searchPluginsTableContextMenu = new window.qBittorrent.ContextMenu.SearchPluginsTableContextMenu({
targets: "#searchPluginsTableDiv tr", targets: "#searchPluginsTableDiv tbody tr",
menu: "searchPluginsTableMenu", menu: "searchPluginsTableMenu",
actions: { actions: {
Enabled: enablePlugin, Enabled: enablePlugin,

View file

@ -29,7 +29,7 @@
// create a context menu // create a context menu
const contextMenu = new window.qBittorrent.ContextMenu.TorrentsTableContextMenu({ const contextMenu = new window.qBittorrent.ContextMenu.TorrentsTableContextMenu({
targets: "#torrentsTableDiv tr", targets: "#torrentsTableDiv tbody tr",
menu: "torrentsTableMenu", menu: "torrentsTableMenu",
actions: { actions: {
start: (element, ref) => { start: (element, ref) => {