From 6289f957cb1ab35b7292cf74772c613ac1a43d13 Mon Sep 17 00:00:00 2001 From: "Stiliyan Tonev (Bark)" Date: Tue, 20 May 2025 14:04:43 +0300 Subject: [PATCH 1/4] feat: Add Per-Status group sort persistence. Save data to local storage in the format `selected_filter_sort_${status group}` and `selected_filter_sort_reverse_${status group}`. When opening/switching to a given status-group apply its sorting to the table. --- src/webui/www/private/scripts/client.js | 20 +++++++++++++++++++ src/webui/www/private/scripts/dynamicTable.js | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index 4c17cb5b1..a2e5dd34b 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -350,10 +350,30 @@ window.addEventListener("DOMContentLoaded", (event) => { } }; + const getFilterSortColumn = (filterName) => { + return LocalPreferences.get(`selected_filter_sort_${filterName}`); + }; + + const saveFilterSort = (filterName, sort, isReverse) => { + LocalPreferences.set(`selected_filter_sort_${filterName}`, sort); + LocalPreferences.set(`selected_filter_sort_reverse_${filterName}`, isReverse); + }; + setStatusFilter = (name) => { const currentHash = torrentsTable.getCurrentTorrentID(); + // Save current sorting for this filter. + if (torrentsTable.getSortedColumn()) + saveFilterSort(selectedStatus, torrentsTable.getSortedColumn(), torrentsTable.reverseSort ?? "0"); LocalPreferences.set("selected_filter", name); + // If there is a saved sorting column, load it. + const sortColumn = getFilterSortColumn(name); + if (sortColumn) { + torrentsTable.setSortedColumn( + sortColumn, + LocalPreferences.get(`selected_filter_sort_reverse_${name}`, "0") + ); + } selectedStatus = name; highlightSelectedStatus(); updateMainData(); diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index aacdb5f5d..873626cfb 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -687,7 +687,7 @@ window.qBittorrent.DynamicTable ??= (() => { const oldColumn = this.sortedColumn; this.sortedColumn = column; this.reverseSort = reverse ?? "0"; - this.setSortedColumnIcon(column, oldColumn, false); + this.setSortedColumnIcon(column, oldColumn, reverse === "1"); } else { // Toggle sort order From 2551b2e062575f7c3bf23da387520fef1af0af9c Mon Sep 17 00:00:00 2001 From: "Stiliyan Tonev (Bark)" Date: Sat, 24 May 2025 16:29:55 +0300 Subject: [PATCH 2/4] refact: Improve JS as advised in PR. --- src/webui/www/private/scripts/client.js | 19 ++++++------------- src/webui/www/private/scripts/dynamicTable.js | 2 +- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index a2e5dd34b..b03ffbb55 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -350,25 +350,18 @@ window.addEventListener("DOMContentLoaded", (event) => { } }; - const getFilterSortColumn = (filterName) => { - return LocalPreferences.get(`selected_filter_sort_${filterName}`); - }; - - const saveFilterSort = (filterName, sort, isReverse) => { - LocalPreferences.set(`selected_filter_sort_${filterName}`, sort); - LocalPreferences.set(`selected_filter_sort_reverse_${filterName}`, isReverse); - }; - setStatusFilter = (name) => { const currentHash = torrentsTable.getCurrentTorrentID(); // Save current sorting for this filter. - if (torrentsTable.getSortedColumn()) - saveFilterSort(selectedStatus, torrentsTable.getSortedColumn(), torrentsTable.reverseSort ?? "0"); + if (torrentsTable.getSortedColumn()) { + LocalPreferences.set(`selected_filter_sort_${selectedStatus}`, torrentsTable.getSortedColumn()); + LocalPreferences.set(`selected_filter_sort_reverse_${selectedStatus}`, (torrentsTable.reverseSort ?? "0")); + } LocalPreferences.set("selected_filter", name); // If there is a saved sorting column, load it. - const sortColumn = getFilterSortColumn(name); - if (sortColumn) { + const sortColumn = LocalPreferences.get(`selected_filter_sort_${name}`); + if (sortColumn !== null) { torrentsTable.setSortedColumn( sortColumn, LocalPreferences.get(`selected_filter_sort_reverse_${name}`, "0") diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index 873626cfb..7baeecba4 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -687,7 +687,7 @@ window.qBittorrent.DynamicTable ??= (() => { const oldColumn = this.sortedColumn; this.sortedColumn = column; this.reverseSort = reverse ?? "0"; - this.setSortedColumnIcon(column, oldColumn, reverse === "1"); + this.setSortedColumnIcon(column, oldColumn, (reverse === "1")); } else { // Toggle sort order From 0765add1145cc1d9044af738c804749748cbf9f9 Mon Sep 17 00:00:00 2001 From: "Stiliyan Tonev (Bark)" Date: Tue, 27 May 2025 09:31:45 +0300 Subject: [PATCH 3/4] feat: Add functionality to Desktop/App too. * Add preference flag that is turned **OFF** by default * Exposes the `Type` in `TorrentFilter` so that we can know on which status we are. * Maintains a `QHash` object with the `Status` as `key` and the pair of `column` + `sort order` as `value`. * This sorting is maintained for the session being, we do not save between shutdowns of QBT --- src/base/preferences.cpp | 13 +++++++++++++ src/base/preferences.h | 3 +++ src/base/torrentfilter.h | 1 + src/gui/optionsdialog.cpp | 3 +++ src/gui/optionsdialog.ui | 7 +++++++ src/gui/transferlistsortmodel.cpp | 5 +++++ src/gui/transferlistsortmodel.h | 1 + src/gui/transferlistwidget.cpp | 19 +++++++++++++++++++ src/gui/transferlistwidget.h | 2 ++ src/webui/api/appcontroller.cpp | 3 +++ src/webui/www/private/scripts/client.js | 7 +++++-- src/webui/www/private/views/preferences.html | 7 +++++++ 12 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index c8cd49d1d..24cdba49c 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -451,6 +451,19 @@ void Preferences::setPreventFromSuspendWhenSeeding(const bool b) setValue(u"Preferences/General/PreventFromSuspendWhenSeeding"_s, b); } +bool Preferences::usePerStatusSortOrder() const +{ + return value(u"Preferences/General/UsePerStatusSortOrder"_s, false); +} + +void Preferences::setUsePerStatusSortOrder(const bool use) +{ + if (use == usePerStatusSortOrder()) + return; + + setValue(u"Preferences/General/UsePerStatusSortOrder"_s, use); +} + #ifdef Q_OS_WIN bool Preferences::WinStartup() const { diff --git a/src/base/preferences.h b/src/base/preferences.h index 0da55ab99..d76a2627c 100644 --- a/src/base/preferences.h +++ b/src/base/preferences.h @@ -166,6 +166,9 @@ public: int getActionOnDblClOnTorrentFn() const; void setActionOnDblClOnTorrentFn(int act); + bool usePerStatusSortOrder() const; + void setUsePerStatusSortOrder(bool use); + // Connection options QTime getSchedulerStartTime() const; void setSchedulerStartTime(const QTime &time); diff --git a/src/base/torrentfilter.h b/src/base/torrentfilter.h index 39fd3e06f..280db3302 100644 --- a/src/base/torrentfilter.h +++ b/src/base/torrentfilter.h @@ -100,6 +100,7 @@ public: bool setType(Type type); + Type getType() const { return m_type; } bool setTypeByName(const QString &filter); bool setTorrentIDSet(const std::optional &idSet); bool setCategory(const std::optional &category); diff --git a/src/gui/optionsdialog.cpp b/src/gui/optionsdialog.cpp index ad2eb348b..59429e825 100644 --- a/src/gui/optionsdialog.cpp +++ b/src/gui/optionsdialog.cpp @@ -361,6 +361,7 @@ void OptionsDialog::loadBehaviorTabOptions() m_ui->checkBoxFreeDiskSpaceStatusBar->setChecked(pref->isStatusbarFreeDiskSpaceDisplayed()); m_ui->checkBoxExternalIPStatusBar->setChecked(pref->isStatusbarExternalIPDisplayed()); m_ui->checkBoxPerformanceWarning->setChecked(session->isPerformanceWarningEnabled()); + m_ui->checkBoxPerFilterSorting->setChecked(pref->usePerStatusSortOrder()); connect(m_ui->comboLanguage, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton); @@ -453,6 +454,7 @@ void OptionsDialog::loadBehaviorTabOptions() connect(m_ui->checkBoxFreeDiskSpaceStatusBar, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkBoxExternalIPStatusBar, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkBoxPerformanceWarning, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); + connect(m_ui->checkBoxPerFilterSorting, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); } void OptionsDialog::saveBehaviorTabOptions() const @@ -548,6 +550,7 @@ void OptionsDialog::saveBehaviorTabOptions() const pref->setStatusbarFreeDiskSpaceDisplayed(m_ui->checkBoxFreeDiskSpaceStatusBar->isChecked()); pref->setStatusbarExternalIPDisplayed(m_ui->checkBoxExternalIPStatusBar->isChecked()); + pref->setUsePerStatusSortOrder(m_ui->checkBoxPerFilterSorting->isChecked()); session->setPerformanceWarningEnabled(m_ui->checkBoxPerformanceWarning->isChecked()); } diff --git a/src/gui/optionsdialog.ui b/src/gui/optionsdialog.ui index a0afed167..81dd10aca 100644 --- a/src/gui/optionsdialog.ui +++ b/src/gui/optionsdialog.ui @@ -871,6 +871,13 @@ + + + + Use Per-Status Sorting + + + diff --git a/src/gui/transferlistsortmodel.cpp b/src/gui/transferlistsortmodel.cpp index 782aead74..5a2eb04d9 100644 --- a/src/gui/transferlistsortmodel.cpp +++ b/src/gui/transferlistsortmodel.cpp @@ -123,6 +123,11 @@ void TransferListSortModel::sort(const int column, const Qt::SortOrder order) QSortFilterProxyModel::sort(column, order); } +TorrentFilter::Type TransferListSortModel::getType() const +{ + return m_filter.getType(); +} + void TransferListSortModel::setStatusFilter(const TorrentFilter::Type filter) { if (m_filter.setType(filter)) diff --git a/src/gui/transferlistsortmodel.h b/src/gui/transferlistsortmodel.h index 3b3a59694..b791a04a9 100644 --- a/src/gui/transferlistsortmodel.h +++ b/src/gui/transferlistsortmodel.h @@ -56,6 +56,7 @@ public: void disableTagFilter(); void setTrackerFilter(const QSet &torrentIDs); void disableTrackerFilter(); + TorrentFilter::Type getType() const; private: int compare(const QModelIndex &left, const QModelIndex &right) const; diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index b2ed64ee7..e2a4f6cbd 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -1339,7 +1339,26 @@ void TransferListWidget::applyFilter(const QString &name, const TransferListMode void TransferListWidget::applyStatusFilter(const int filterIndex) { const auto filterType = static_cast(filterIndex); + //Save sort order for old filter + const bool usePerFilterSort = Preferences::instance()->usePerStatusSortOrder(); + const TorrentFilter::Type filter = m_sortFilterModel->getType(); + const int column = m_sortFilterModel->sortColumn(); + if (column != -1 && usePerFilterSort) + { + m_statusSortPairs.insert_or_assign(filter, qMakePair(column, m_sortFilterModel->sortOrder())); + } + m_sortFilterModel->setStatusFilter(((filterType >= TorrentFilter::All) && (filterType < TorrentFilter::_Count)) ? filterType : TorrentFilter::All); + + // Load sort for new status filter + if (m_statusSortPairs.contains(filterType) && usePerFilterSort) + { + const QPair sortPair = m_statusSortPairs.value(filterType); + m_sortFilterModel->sort(sortPair.first, sortPair.second); + // Rows are sorted correctly, but header state does not reflect actual sort setting + header()->setSortIndicator(sortPair.first, sortPair.second); + } + // Select first item if nothing is selected if (selectionModel()->selectedRows(0).empty() && (m_sortFilterModel->rowCount() > 0)) { diff --git a/src/gui/transferlistwidget.h b/src/gui/transferlistwidget.h index 097dd809a..f62080f2b 100644 --- a/src/gui/transferlistwidget.h +++ b/src/gui/transferlistwidget.h @@ -35,6 +35,7 @@ #include #include "base/bittorrent/infohash.h" +#include "base/torrentfilter.h" #include "guiapplicationcomponent.h" #include "transferlistmodel.h" @@ -137,6 +138,7 @@ private: void applyToSelectedTorrents(const std::function &fn); QList getVisibleTorrents() const; int visibleColumnsCount() const; + QHash> m_statusSortPairs; TransferListModel *m_listModel = nullptr; TransferListSortModel *m_sortFilterModel = nullptr; diff --git a/src/webui/api/appcontroller.cpp b/src/webui/api/appcontroller.cpp index e2e465acd..378b3ced3 100644 --- a/src/webui/api/appcontroller.cpp +++ b/src/webui/api/appcontroller.cpp @@ -144,6 +144,7 @@ void AppController::preferencesAction() data[u"locale"_s] = pref->getLocale(); data[u"performance_warning"_s] = session->isPerformanceWarningEnabled(); data[u"status_bar_external_ip"_s] = pref->isStatusbarExternalIPDisplayed(); + data[u"per_status_sorting"_s] = pref->usePerStatusSortOrder(); // Transfer List data[u"confirm_torrent_deletion"_s] = pref->confirmTorrentDeletion(); // Log file @@ -537,6 +538,8 @@ void AppController::setPreferencesAction() pref->setStatusbarExternalIPDisplayed(it.value().toBool()); if (hasKey(u"performance_warning"_s)) session->setPerformanceWarningEnabled(it.value().toBool()); + if (hasKey(u"per_status_sorting"_s)) + pref->setUsePerStatusSortOrder(it.value().toBool()); // Transfer List if (hasKey(u"confirm_torrent_deletion"_s)) pref->setConfirmTorrentDeletion(it.value().toBool()); diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index b03ffbb55..bbe1f72c1 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -352,21 +352,24 @@ window.addEventListener("DOMContentLoaded", (event) => { setStatusFilter = (name) => { const currentHash = torrentsTable.getCurrentTorrentID(); + const usePerFilterSorting = window.qBittorrent.Cache.preferences.get().per_status_sorting; // Save current sorting for this filter. - if (torrentsTable.getSortedColumn()) { + if ((torrentsTable.getSortedColumn()) && usePerFilterSorting) { LocalPreferences.set(`selected_filter_sort_${selectedStatus}`, torrentsTable.getSortedColumn()); LocalPreferences.set(`selected_filter_sort_reverse_${selectedStatus}`, (torrentsTable.reverseSort ?? "0")); } LocalPreferences.set("selected_filter", name); + // If there is a saved sorting column, load it. const sortColumn = LocalPreferences.get(`selected_filter_sort_${name}`); - if (sortColumn !== null) { + if ((sortColumn !== null) && usePerFilterSorting) { torrentsTable.setSortedColumn( sortColumn, LocalPreferences.get(`selected_filter_sort_reverse_${name}`, "0") ); } + selectedStatus = name; highlightSelectedStatus(); updateMainData(); diff --git a/src/webui/www/private/views/preferences.html b/src/webui/www/private/views/preferences.html index b2e81856f..69e3f7c09 100644 --- a/src/webui/www/private/views/preferences.html +++ b/src/webui/www/private/views/preferences.html @@ -109,6 +109,11 @@ +
+ + +
+
QBT_TR(Custom WebUI settings)QBT_TR[CONTEXT=OptionsDialog]
@@ -2236,6 +2241,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD updateColoSchemeSelect(); document.getElementById("statusBarExternalIP").checked = pref.status_bar_external_ip; document.getElementById("performanceWarning").checked = pref.performance_warning; + document.getElementById("perStatusSorting").checked = pref.per_status_sorting; document.getElementById("displayFullURLTrackerColumn").checked = (LocalPreferences.get("full_url_tracker_column", "false") === "true"); document.getElementById("useVirtualList").checked = (LocalPreferences.get("use_virtual_list", "false") === "true"); document.getElementById("hideZeroFiltersCheckbox").checked = (LocalPreferences.get("hide_zero_status_filters", "false") === "true"); @@ -2670,6 +2676,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD LocalPreferences.set("color_scheme", "dark"); settings["status_bar_external_ip"] = document.getElementById("statusBarExternalIP").checked; settings["performance_warning"] = document.getElementById("performanceWarning").checked; + settings["per_status_sorting"] = document.getElementById("perStatusSorting").checked; LocalPreferences.set("full_url_tracker_column", document.getElementById("displayFullURLTrackerColumn").checked.toString()); LocalPreferences.set("use_virtual_list", document.getElementById("useVirtualList").checked.toString()); LocalPreferences.set("hide_zero_status_filters", document.getElementById("hideZeroFiltersCheckbox").checked.toString()); From 193da439d5d6f46e0664503bddfdbbd2cb4ebfbe Mon Sep 17 00:00:00 2001 From: "Stiliyan Tonev (Bark)" Date: Wed, 18 Jun 2025 13:57:50 +0300 Subject: [PATCH 4/4] fix: Use syntaxis that works for every QT version --- src/gui/transferlistwidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index e2a4f6cbd..7811bf0d6 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -1345,7 +1345,7 @@ void TransferListWidget::applyStatusFilter(const int filterIndex) const int column = m_sortFilterModel->sortColumn(); if (column != -1 && usePerFilterSort) { - m_statusSortPairs.insert_or_assign(filter, qMakePair(column, m_sortFilterModel->sortOrder())); + m_statusSortPairs[filter] = qMakePair(column, m_sortFilterModel->sortOrder()); } m_sortFilterModel->setStatusFilter(((filterType >= TorrentFilter::All) && (filterType < TorrentFilter::_Count)) ? filterType : TorrentFilter::All);