diff --git a/.github/workflows/ci_ubuntu.yaml b/.github/workflows/ci_ubuntu.yaml index c07e7f2d5..5643d7cce 100644 --- a/.github/workflows/ci_ubuntu.yaml +++ b/.github/workflows/ci_ubuntu.yaml @@ -138,16 +138,15 @@ jobs: - name: Install AppImage run: | - sudo apt install libfuse2 curl \ -L \ -Z \ - -O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-static-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/releases/download/continuous/linuxdeploy-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 chmod +x \ - linuxdeploy-static-x86_64.AppImage \ - linuxdeploy-plugin-qt-static-x86_64.AppImage \ + linuxdeploy-x86_64.AppImage \ + linuxdeploy-plugin-qt-x86_64.AppImage \ linuxdeploy-plugin-appimage-x86_64.AppImage - name: Prepare files for AppImage @@ -160,12 +159,12 @@ jobs: - name: Package AppImage run: | - ./linuxdeploy-static-x86_64.AppImage --appdir qbittorrent --plugin qt + ./linuxdeploy-x86_64.AppImage --appdir qbittorrent --plugin qt rm qbittorrent/apprun-hooks/* cp .github/workflows/helper/appimage/export_vars.sh qbittorrent/apprun-hooks/export_vars.sh NO_APPSTREAM=1 \ 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 uses: actions/upload-artifact@v4 diff --git a/dist/windows/installer-translations/swedish.nsh b/dist/windows/installer-translations/swedish.nsh index 77db60683..51912d112 100644 --- a/dist/windows/installer-translations/swedish.nsh +++ b/dist/windows/installer-translations/swedish.nsh @@ -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_SWEDISH} "Skapa startmenygenväg" ;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_SWEDISH} "Öppna .torrent-filer med qBittorrent" ;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent" LangString inst_magnet ${LANG_SWEDISH} "Öppna magnetlänkar med qBittorrent" ;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_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_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_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_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_SWEDISH} "Avinstallerar tidigare version." ;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_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_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_SWEDISH} "Tar inte bort .torrent-association. Den är associerad med:" ;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:" diff --git a/src/app/application.cpp b/src/app/application.cpp index a415950d0..e82df1168 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -1,6 +1,6 @@ /* * Bittorrent Client using Qt and libtorrent. - * Copyright (C) 2015-2024 Vladimir Golovnev + * Copyright (C) 2015-2025 Vladimir Golovnev * Copyright (C) 2006 Christophe Dumez * * 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 #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 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 ¶ms) { QStringList result; @@ -138,85 +160,86 @@ namespace const BitTorrent::AddTorrentParams &addTorrentParams = params.addTorrentParams; if (!addTorrentParams.savePath.isEmpty()) - result.append(u"@savePath=" + addTorrentParams.savePath.data()); + result.append(bindParamValue(PARAM_SAVEPATH, addTorrentParams.savePath.data())); 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) - result.append(u"@skipChecking"_s); + result.append(PARAM_SKIPCHECKING); if (!addTorrentParams.category.isEmpty()) - result.append(u"@category=" + addTorrentParams.category); + result.append(bindParamValue(PARAM_CATEGORY, addTorrentParams.category)); if (addTorrentParams.sequential) - result.append(u"@sequential"_s); + result.append(PARAM_SEQUENTIAL); if (addTorrentParams.firstLastPiecePriority) - result.append(u"@firstLastPiecePriority"_s); + result.append(PARAM_FIRSTLASTPIECEPRIORITY); 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; return result.join(PARAMS_SEPARATOR); } - QBtCommandLineParameters parseParams(const QString &str) + QBtCommandLineParameters parseParams(const QStringView str) { QBtCommandLineParameters parsedParams; 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(); + const auto [paramName, paramValue] = parseParam(param); // 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; } - if (param.startsWith(u"@addStopped=")) + if (paramName == PARAM_ADDSTOPPED) { - addTorrentParams.addStopped = (QStringView(param).mid(11).toInt() != 0); + addTorrentParams.addStopped = (paramValue.toInt() != 0); continue; } - if (param == u"@skipChecking") + if (paramName == PARAM_SKIPCHECKING) { addTorrentParams.skipChecking = true; continue; } - if (param.startsWith(u"@category=")) + if (paramName == PARAM_CATEGORY) { - addTorrentParams.category = param.mid(10); + addTorrentParams.category = paramValue.toString(); continue; } - if (param == u"@sequential") + if (paramName == PARAM_SEQUENTIAL) { addTorrentParams.sequential = true; continue; } - if (param == u"@firstLastPiecePriority") + if (paramName == PARAM_FIRSTLASTPIECEPRIORITY) { addTorrentParams.firstLastPiecePriority = true; continue; } - if (param.startsWith(u"@skipDialog=")) + if (paramName == PARAM_SKIPDIALOG) { - parsedParams.skipDialog = (QStringView(param).mid(12).toInt() != 0); + parsedParams.skipDialog = (paramValue.toInt() != 0); continue; } - parsedParams.torrentSources.append(param); + parsedParams.torrentSources.append(param.toString()); } return parsedParams; diff --git a/src/base/http/types.h b/src/base/http/types.h index dcc839744..ebe342a6f 100644 --- a/src/base/http/types.h +++ b/src/base/http/types.h @@ -30,8 +30,10 @@ #pragma once #include +#include #include #include +#include #include #include "base/global.h" diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 69c9ae1a2..e25d02945 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -287,6 +287,8 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") macosdockbadge/badger.mm macosdockbadge/badgeview.h macosdockbadge/badgeview.mm + macosshiftclickhandler.h + macosshiftclickhandler.cpp macutilities.h macutilities.mm ) diff --git a/src/gui/macosshiftclickhandler.cpp b/src/gui/macosshiftclickhandler.cpp new file mode 100644 index 000000000..81ba81926 --- /dev/null +++ b/src/gui/macosshiftclickhandler.cpp @@ -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 +#include + +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(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); +} diff --git a/src/gui/macosshiftclickhandler.h b/src/gui/macosshiftclickhandler.h new file mode 100644 index 000000000..43d6fda16 --- /dev/null +++ b/src/gui/macosshiftclickhandler.h @@ -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 +#include + +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; +}; diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index 9435b05be..cd6b44653 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -74,6 +74,7 @@ #include "utils.h" #ifdef Q_OS_MACOS +#include "macosshiftclickhandler.h" #include "macutilities.h" #endif @@ -158,6 +159,7 @@ TransferListWidget::TransferListWidget(IGUIApplication *app, QWidget *parent) setDropIndicatorShown(true); #if defined(Q_OS_MACOS) setAttribute(Qt::WA_MacShowFocusRect, false); + new MacOSShiftClickHandler(this); #endif header()->setFirstSectionMovable(true); header()->setStretchLastSection(false); diff --git a/src/gui/uithemecommon.h b/src/gui/uithemecommon.h index 31ae5a456..9f1545fe6 100644 --- a/src/gui/uithemecommon.h +++ b/src/gui/uithemecommon.h @@ -80,7 +80,33 @@ inline QHash defaultUIThemeColors() {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.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, {{}, {}}} }; } diff --git a/src/webui/api/rsscontroller.cpp b/src/webui/api/rsscontroller.cpp index d103d3198..0dd5d1dc3 100644 --- a/src/webui/api/rsscontroller.cpp +++ b/src/webui/api/rsscontroller.cpp @@ -49,7 +49,7 @@ void RSSController::addFolderAction() { requireParams({u"path"_s}); - const QString path = params()[u"path"_s].trimmed(); + const QString path = params()[u"path"_s]; const nonstd::expected result = RSS::Session::instance()->addFolder(path); if (!result) throw APIError(APIErrorType::Conflict, result.error()); @@ -59,8 +59,8 @@ void RSSController::addFeedAction() { requireParams({u"url"_s, u"path"_s}); - const QString url = params()[u"url"_s].trimmed(); - const QString path = params()[u"path"_s].trimmed(); + const QString url = params()[u"url"_s]; + const QString path = params()[u"path"_s]; const nonstd::expected result = RSS::Session::instance()->addFeed(url, (path.isEmpty() ? url : path)); if (!result) throw APIError(APIErrorType::Conflict, result.error()); @@ -70,8 +70,8 @@ void RSSController::setFeedURLAction() { requireParams({u"path"_s, u"url"_s}); - const QString path = params()[u"path"_s].trimmed(); - const QString url = params()[u"url"_s].trimmed(); + const QString path = params()[u"path"_s]; + const QString url = params()[u"url"_s]; const nonstd::expected result = RSS::Session::instance()->setFeedURL(path, url); if (!result) throw APIError(APIErrorType::Conflict, result.error()); @@ -81,7 +81,7 @@ void RSSController::removeItemAction() { requireParams({u"path"_s}); - const QString path = params()[u"path"_s].trimmed(); + const QString path = params()[u"path"_s]; const nonstd::expected result = RSS::Session::instance()->removeItem(path); if (!result) throw APIError(APIErrorType::Conflict, result.error()); @@ -91,8 +91,8 @@ void RSSController::moveItemAction() { requireParams({u"itemPath"_s, u"destPath"_s}); - const QString itemPath = params()[u"itemPath"_s].trimmed(); - const QString destPath = params()[u"destPath"_s].trimmed(); + const QString itemPath = params()[u"itemPath"_s]; + const QString destPath = params()[u"destPath"_s]; const nonstd::expected result = RSS::Session::instance()->moveItem(itemPath, destPath); if (!result) throw APIError(APIErrorType::Conflict, result.error()); @@ -146,8 +146,8 @@ void RSSController::setRuleAction() { requireParams({u"ruleName"_s, u"ruleDef"_s}); - const QString ruleName {params()[u"ruleName"_s].trimmed()}; - const QByteArray ruleDef {params()[u"ruleDef"_s].trimmed().toUtf8()}; + const QString ruleName {params()[u"ruleName"_s]}; + const QByteArray ruleDef {params()[u"ruleDef"_s].toUtf8()}; const auto jsonObj = QJsonDocument::fromJson(ruleDef).object(); RSS::AutoDownloader::instance()->setRule(RSS::AutoDownloadRule::fromJsonObject(jsonObj, ruleName)); @@ -157,8 +157,8 @@ void RSSController::renameRuleAction() { requireParams({u"ruleName"_s, u"newRuleName"_s}); - const QString ruleName {params()[u"ruleName"_s].trimmed()}; - const QString newRuleName {params()[u"newRuleName"_s].trimmed()}; + const QString ruleName {params()[u"ruleName"_s]}; + const QString newRuleName {params()[u"newRuleName"_s]}; RSS::AutoDownloader::instance()->renameRule(ruleName, newRuleName); } @@ -167,7 +167,7 @@ void RSSController::removeRuleAction() { requireParams({u"ruleName"_s}); - const QString ruleName {params()[u"ruleName"_s].trimmed()}; + const QString ruleName {params()[u"ruleName"_s]}; RSS::AutoDownloader::instance()->removeRule(ruleName); } diff --git a/src/webui/www/private/css/style.css b/src/webui/www/private/css/style.css index 665300d91..f28e0a5cf 100644 --- a/src/webui/www/private/css/style.css +++ b/src/webui/www/private/css/style.css @@ -100,6 +100,7 @@ ol { .dynamicTableDiv, .mochaContentWrapper, .panel, + .scrollableMenu, #rssDetailsView { scrollbar-width: thin; } diff --git a/src/webui/www/private/rename_files.html b/src/webui/www/private/rename_files.html index 27310e0de..f8eefa009 100644 --- a/src/webui/www/private/rename_files.html +++ b/src/webui/www/private/rename_files.html @@ -21,7 +21,7 @@ } = window.MUI.Windows.instances["multiRenamePage"]; const bulkRenameFilesContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({ - targets: "#bulkRenameFilesTableDiv tr", + targets: "#bulkRenameFilesTableDiv tbody tr", menu: "multiRenameFilesMenu", actions: { ToggleSelection: (element, ref) => { diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index ee9bf554f..21cbce2bc 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -294,6 +294,7 @@ window.qBittorrent.DynamicTable ??= (() => { this.updateTableHeaders(); this.tableBody.replaceChildren(); this.updateTable(true); + this.reselectRows(this.selectedRowsIds()); } if (this.currentHeaderAction === "drag") { resetElementBorderStyle(el); @@ -750,10 +751,7 @@ window.qBittorrent.DynamicTable ??= (() => { reselectRows: function(rowIds) { this.deselectAll(); this.selectedRows = rowIds.slice(); - for (const tr of this.getTrs()) { - if (rowIds.includes(tr.rowId)) - tr.classList.add("selected"); - } + this.setRowClass(); }, setRowClass: function() { @@ -1752,7 +1750,7 @@ window.qBittorrent.DynamicTable ??= (() => { 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; td.title = country; }; @@ -2058,7 +2056,11 @@ window.qBittorrent.DynamicTable ??= (() => { 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.title = status; }; diff --git a/src/webui/www/private/scripts/prop-files.js b/src/webui/www/private/scripts/prop-files.js index 69875fa92..39c9db083 100644 --- a/src/webui/www/private/scripts/prop-files.js +++ b/src/webui/www/private/scripts/prop-files.js @@ -575,7 +575,7 @@ window.qBittorrent.PropFiles ??= (() => { }; const torrentFilesContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({ - targets: "#torrentFilesTableDiv tr", + targets: "#torrentFilesTableDiv tbody tr", menu: "torrentFilesMenu", actions: { Rename: (element, ref) => { diff --git a/src/webui/www/private/scripts/prop-trackers.js b/src/webui/www/private/scripts/prop-trackers.js index 796b090c1..3f151d5c8 100644 --- a/src/webui/www/private/scripts/prop-trackers.js +++ b/src/webui/www/private/scripts/prop-trackers.js @@ -196,7 +196,7 @@ window.qBittorrent.PropTrackers ??= (() => { if (current_hash.length === 0) return; - const trackerUrl = encodeURIComponent(element.childNodes[1].textContent); + const trackerUrl = encodeURIComponent(torrentTrackersTable.selectedRowsIds()[0]); new MochaUI.Window({ id: "trackersPage", icon: "images/qbittorrent-tray.svg", diff --git a/src/webui/www/private/scripts/search.js b/src/webui/www/private/scripts/search.js index cd97bde5d..3dc0b454b 100644 --- a/src/webui/www/private/scripts/search.js +++ b/src/webui/www/private/scripts/search.js @@ -97,8 +97,8 @@ window.qBittorrent.Search ??= (() => { } }, offsets: { - x: -15, - y: -53 + x: 2, + y: -60 }, onShow: function() { setActiveTab(this.options.element); @@ -109,7 +109,7 @@ window.qBittorrent.Search ??= (() => { // load "Search in" preference from local storage $("searchInTorrentName").value = (LocalPreferences.get("search_in_filter") === "names") ? "names" : "everywhere"; const searchResultsTableContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({ - targets: "#searchResultsTableDiv tr", + targets: "#searchResultsTableDiv tbody tr", menu: "searchResultsTableMenu", actions: { Download: downloadSearchTorrent, @@ -184,7 +184,10 @@ window.qBittorrent.Search ??= (() => { closeTabElem.src = "images/application-exit.svg"; closeTabElem.width = "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.appendChild(getStatusIconElement("QBT_TR(Searching...)QBT_TR[CONTEXT=SearchJobWidget]", "images/queued.svg")); diff --git a/src/webui/www/private/views/filters.html b/src/webui/www/private/views/filters.html index 60eeb5c88..ef7bc88dc 100644 --- a/src/webui/www/private/views/filters.html +++ b/src/webui/www/private/views/filters.html @@ -98,7 +98,7 @@ } }, offsets: { - x: -15, + x: 0, y: 2 }, onShow: function() { diff --git a/src/webui/www/private/views/log.html b/src/webui/www/private/views/log.html index a988e5ba5..0790bc0bb 100644 --- a/src/webui/www/private/views/log.html +++ b/src/webui/www/private/views/log.html @@ -206,7 +206,7 @@ }); const logTableContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({ - targets: ":is(#logMessageView, #logPeerView) tr", + targets: ":is(#logMessageTableDiv, #logPeerTableDiv) tbody tr", menu: "logTableMenu", actions: { Clear: () => { diff --git a/src/webui/www/private/views/rss.html b/src/webui/www/private/views/rss.html index e1aa96bf0..31d0b2561 100644 --- a/src/webui/www/private/views/rss.html +++ b/src/webui/www/private/views/rss.html @@ -218,7 +218,7 @@ $("rssFetchingDisabled").classList.remove("invisible"); const rssFeedContextMenu = new window.qBittorrent.ContextMenu.RssFeedContextMenu({ - targets: "#rssFeedTableDiv tr", + targets: "#rssFeedTableDiv tbody tr", menu: "rssFeedMenu", actions: { update: (el) => { @@ -288,7 +288,7 @@ rssFeedTable.setup("rssFeedTableDiv", "rssFeedFixedHeaderDiv", rssFeedContextMenu); const rssArticleContextMenu = new window.qBittorrent.ContextMenu.RssArticleContextMenu({ - targets: "#rssArticleTableDiv tr", + targets: "#rssArticleTableDiv tbody tr", menu: "rssArticleMenu", actions: { Download: (el) => { diff --git a/src/webui/www/private/views/searchplugins.html b/src/webui/www/private/views/searchplugins.html index e6a6b5bea..768b605fc 100644 --- a/src/webui/www/private/views/searchplugins.html +++ b/src/webui/www/private/views/searchplugins.html @@ -94,7 +94,7 @@ const setup = () => { searchPluginsTable = new window.qBittorrent.DynamicTable.SearchPluginsTable(); searchPluginsTableContextMenu = new window.qBittorrent.ContextMenu.SearchPluginsTableContextMenu({ - targets: "#searchPluginsTableDiv tr", + targets: "#searchPluginsTableDiv tbody tr", menu: "searchPluginsTableMenu", actions: { Enabled: enablePlugin, diff --git a/src/webui/www/private/views/transferlist.html b/src/webui/www/private/views/transferlist.html index 40dfdac7a..0692a06db 100644 --- a/src/webui/www/private/views/transferlist.html +++ b/src/webui/www/private/views/transferlist.html @@ -29,7 +29,7 @@ // create a context menu const contextMenu = new window.qBittorrent.ContextMenu.TorrentsTableContextMenu({ - targets: "#torrentsTableDiv tr", + targets: "#torrentsTableDiv tbody tr", menu: "torrentsTableMenu", actions: { start: (element, ref) => {