From ec08cb54456d2c5fd376e341adb7662b361c8b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Bri=C3=A8re?= Date: Fri, 28 Apr 2017 18:45:52 -0400 Subject: [PATCH 1/4] Leave categories order intact in "Add new torrent" dialog. The categories are already properly sorted in AddNewTorrentDialog; this second case-sensitive sort undid all that. This partially addresses #6708. --- src/gui/addnewtorrentdialog.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index 95b492dc7..354556f1b 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -133,7 +133,6 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP if (category != defaultCategory && category != m_torrentParams.category) ui->categoryComboBox->addItem(category); - ui->categoryComboBox->model()->sort(0); ui->contentTreeView->header()->setSortIndicator(0, Qt::AscendingOrder); loadState(); // Signal / slots From f53a403a7bcbb283d7f482c9086664104efd540d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Bri=C3=A8re?= Date: Fri, 28 Apr 2017 18:46:50 -0400 Subject: [PATCH 2/4] Set "category" column as case-insensitive in TransferListSortModel. (From what I can tell, it would appear that the sorting was already case-insensitive by default. This makes it explicit.) --- src/gui/transferlistsortmodel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/transferlistsortmodel.cpp b/src/gui/transferlistsortmodel.cpp index 423742979..4fedf7787 100644 --- a/src/gui/transferlistsortmodel.cpp +++ b/src/gui/transferlistsortmodel.cpp @@ -74,6 +74,7 @@ void TransferListSortModel::disableTrackerFilter() bool TransferListSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { switch (sortColumn()) { + case TorrentModel::TR_CATEGORY: case TorrentModel::TR_NAME: { QVariant vL = left.data(); QVariant vR = right.data(); From 722c928ab56927dff20142597a3e0e5d1f1f8cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Bri=C3=A8re?= Date: Wed, 3 May 2017 12:21:29 -0400 Subject: [PATCH 3/4] Turn isSpecialItem() into a CategoryFilterModel static method. --- src/gui/categoryfiltermodel.cpp | 7 +++++++ src/gui/categoryfiltermodel.h | 2 ++ src/gui/categoryfilterwidget.cpp | 11 ++--------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/gui/categoryfiltermodel.cpp b/src/gui/categoryfiltermodel.cpp index 715bdd7af..0968761ae 100644 --- a/src/gui/categoryfiltermodel.cpp +++ b/src/gui/categoryfiltermodel.cpp @@ -195,6 +195,13 @@ CategoryFilterModel::~CategoryFilterModel() delete m_rootItem; } +bool CategoryFilterModel::isSpecialItem(const QModelIndex &index) +{ + // the first two items at first level are special items: + // 'All' and 'Uncategorized' + return (!index.parent().isValid() && (index.row() <= 1)); +} + int CategoryFilterModel::columnCount(const QModelIndex &) const { return 1; diff --git a/src/gui/categoryfiltermodel.h b/src/gui/categoryfiltermodel.h index df92eab57..bf186faae 100644 --- a/src/gui/categoryfiltermodel.h +++ b/src/gui/categoryfiltermodel.h @@ -48,6 +48,8 @@ public: explicit CategoryFilterModel(QObject *parent = nullptr); ~CategoryFilterModel(); + static bool isSpecialItem(const QModelIndex &index); + int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; diff --git a/src/gui/categoryfilterwidget.cpp b/src/gui/categoryfilterwidget.cpp index 0da279b25..bf7952322 100644 --- a/src/gui/categoryfilterwidget.cpp +++ b/src/gui/categoryfilterwidget.cpp @@ -54,13 +54,6 @@ namespace return categoryFilter; } - - bool isSpecialItem(const QModelIndex &index) - { - // the first two items at first level are special items: - // 'All' and 'Uncategorized' - return (!index.parent().isValid() && (index.row() <= 1)); - } } CategoryFilterWidget::CategoryFilterWidget(QWidget *parent) @@ -115,7 +108,7 @@ void CategoryFilterWidget::showMenu(QPoint) connect(addAct, SIGNAL(triggered()), SLOT(addCategory())); auto selectedRows = selectionModel()->selectedRows(); - if (!selectedRows.empty() && !isSpecialItem(selectedRows.first())) { + if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first())) { if (BitTorrent::Session::instance()->isSubcategoriesEnabled()) { QAction *addSubAct = menu.addAction( GuiIconProvider::instance()->getIcon("list-add") @@ -238,7 +231,7 @@ void CategoryFilterWidget::addSubcategory() void CategoryFilterWidget::removeCategory() { auto selectedRows = selectionModel()->selectedRows(); - if (!selectedRows.empty() && !isSpecialItem(selectedRows.first())) { + if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first())) { BitTorrent::Session::instance()->removeCategory( static_cast(model())->categoryName(selectedRows.first())); updateGeometry(); From c37d90bf6d08a41009ddb30b09fd1f550fe2500f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Bri=C3=A8re?= Date: Fri, 28 Apr 2017 18:49:06 -0400 Subject: [PATCH 4/4] Properly sort categories case-insensitively in filter widget. Closes #6708. --- src/gui/CMakeLists.txt | 2 + src/gui/categoryfiltermodel.cpp | 12 +++--- src/gui/categoryfilterproxymodel.cpp | 57 ++++++++++++++++++++++++++++ src/gui/categoryfilterproxymodel.h | 48 +++++++++++++++++++++++ src/gui/categoryfilterwidget.cpp | 17 ++++++--- src/gui/gui.pri | 2 + 6 files changed, 125 insertions(+), 13 deletions(-) create mode 100644 src/gui/categoryfilterproxymodel.cpp create mode 100644 src/gui/categoryfilterproxymodel.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 7a444a8e5..1f2794b54 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -34,6 +34,7 @@ advancedsettings.h autoexpandabledialog.h banlistoptions.h categoryfiltermodel.h +categoryfilterproxymodel.h categoryfilterwidget.h cookiesdialog.h cookiesmodel.h @@ -76,6 +77,7 @@ advancedsettings.cpp autoexpandabledialog.cpp banlistoptions.cpp categoryfiltermodel.cpp +categoryfilterproxymodel.cpp categoryfilterwidget.cpp cookiesdialog.cpp cookiesmodel.cpp diff --git a/src/gui/categoryfiltermodel.cpp b/src/gui/categoryfiltermodel.cpp index 0968761ae..ca6048d83 100644 --- a/src/gui/categoryfiltermodel.cpp +++ b/src/gui/categoryfiltermodel.cpp @@ -139,8 +139,7 @@ public: item->m_parent = this; m_children[uid] = item; - auto pos = std::lower_bound(m_childUids.begin(), m_childUids.end(), uid); - m_childUids.insert(pos, uid); + m_childUids.append(uid); m_torrentsCount += item->torrentsCount(); } @@ -314,11 +313,10 @@ void CategoryFilterModel::categoryAdded(const QString &categoryName) parent = findItem(expanded[expanded.count() - 2]); } - auto item = new CategoryModelItem( - parent, m_isSubcategoriesEnabled ? shortName(categoryName) : categoryName); - - QModelIndex i = index(item); - beginInsertRows(i.parent(), i.row(), i.row()); + int row = parent->childCount(); + beginInsertRows(index(parent), row, row); + new CategoryModelItem( + parent, m_isSubcategoriesEnabled ? shortName(categoryName) : categoryName); endInsertRows(); } diff --git a/src/gui/categoryfilterproxymodel.cpp b/src/gui/categoryfilterproxymodel.cpp new file mode 100644 index 000000000..4862aa238 --- /dev/null +++ b/src/gui/categoryfilterproxymodel.cpp @@ -0,0 +1,57 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Frédéric Brière + * + * 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 "categoryfilterproxymodel.h" + +#include "base/utils/string.h" +#include "categoryfiltermodel.h" + +CategoryFilterProxyModel::CategoryFilterProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ +} + +QModelIndex CategoryFilterProxyModel::index(const QString &categoryName) const +{ + return mapFromSource(static_cast(sourceModel())->index(categoryName)); +} + +QString CategoryFilterProxyModel::categoryName(const QModelIndex &index) const +{ + return static_cast(sourceModel())->categoryName(mapToSource(index)); +} + +bool CategoryFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + // "All" and "Uncategorized" must be left in place + if (CategoryFilterModel::isSpecialItem(left) || CategoryFilterModel::isSpecialItem(right)) + return left.row() < right.row(); + else + return Utils::String::naturalCompareCaseInsensitive( + left.data().toString(), right.data().toString()); +} diff --git a/src/gui/categoryfilterproxymodel.h b/src/gui/categoryfilterproxymodel.h new file mode 100644 index 000000000..1b1b2484a --- /dev/null +++ b/src/gui/categoryfilterproxymodel.h @@ -0,0 +1,48 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2017 Frédéric Brière + * + * 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. + */ + +#ifndef CATEGORYFILTERPROXYMODEL_H +#define CATEGORYFILTERPROXYMODEL_H + +#include +#include + +class CategoryFilterProxyModel: public QSortFilterProxyModel +{ +public: + explicit CategoryFilterProxyModel(QObject *parent = nullptr); + + // CategoryFilterModel methods which we need to relay + QModelIndex index(const QString &categoryName) const; + QString categoryName(const QModelIndex &index) const; + +protected: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; +}; + +#endif // CATEGORYFILTERPROXYMODEL_H diff --git a/src/gui/categoryfilterwidget.cpp b/src/gui/categoryfilterwidget.cpp index bf7952322..3fe43ab4b 100644 --- a/src/gui/categoryfilterwidget.cpp +++ b/src/gui/categoryfilterwidget.cpp @@ -38,11 +38,12 @@ #include "base/utils/misc.h" #include "autoexpandabledialog.h" #include "categoryfiltermodel.h" +#include "categoryfilterproxymodel.h" #include "guiiconprovider.h" namespace { - QString getCategoryFilter(const CategoryFilterModel *const model, const QModelIndex &index) + QString getCategoryFilter(const CategoryFilterProxyModel *const model, const QModelIndex &index) { QString categoryFilter; // Defaults to All if (index.isValid()) { @@ -59,7 +60,10 @@ namespace CategoryFilterWidget::CategoryFilterWidget(QWidget *parent) : QTreeView(parent) { - setModel(new CategoryFilterModel(this)); + CategoryFilterProxyModel *proxyModel = new CategoryFilterProxyModel(this); + proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); + proxyModel->setSourceModel(new CategoryFilterModel(this)); + setModel(proxyModel); setFrameShape(QFrame::NoFrame); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -71,6 +75,7 @@ CategoryFilterWidget::CategoryFilterWidget(QWidget *parent) setAttribute(Qt::WA_MacShowFocusRect, false); #endif setContextMenuPolicy(Qt::CustomContextMenu); + sortByColumn(0, Qt::AscendingOrder); setCurrentIndex(model()->index(0, 0)); connect(this, SIGNAL(collapsed(QModelIndex)), SLOT(callUpdateGeometry())); @@ -88,14 +93,14 @@ QString CategoryFilterWidget::currentCategory() const if (!selectedRows.isEmpty()) current = selectedRows.first(); - return getCategoryFilter(static_cast(model()), current); + return getCategoryFilter(static_cast(model()), current); } void CategoryFilterWidget::onCurrentRowChanged(const QModelIndex ¤t, const QModelIndex &previous) { Q_UNUSED(previous); - emit categoryChanged(getCategoryFilter(static_cast(model()), current)); + emit categoryChanged(getCategoryFilter(static_cast(model()), current)); } void CategoryFilterWidget::showMenu(QPoint) @@ -233,7 +238,7 @@ void CategoryFilterWidget::removeCategory() auto selectedRows = selectionModel()->selectedRows(); if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first())) { BitTorrent::Session::instance()->removeCategory( - static_cast(model())->categoryName(selectedRows.first())); + static_cast(model())->categoryName(selectedRows.first())); updateGeometry(); } } @@ -242,7 +247,7 @@ void CategoryFilterWidget::removeUnusedCategories() { auto session = BitTorrent::Session::instance(); foreach (const QString &category, session->categories()) - if (model()->data(static_cast(model())->index(category), Qt::UserRole) == 0) + if (model()->data(static_cast(model())->index(category), Qt::UserRole) == 0) session->removeCategory(category); updateGeometry(); } diff --git a/src/gui/gui.pri b/src/gui/gui.pri index 5cb464ed7..8b28a54de 100644 --- a/src/gui/gui.pri +++ b/src/gui/gui.pri @@ -50,6 +50,7 @@ HEADERS += \ $$PWD/cookiesmodel.h \ $$PWD/cookiesdialog.h \ $$PWD/categoryfiltermodel.h \ + $$PWD/categoryfilterproxymodel.h \ $$PWD/categoryfilterwidget.h \ $$PWD/banlistoptions.h \ $$PWD/rss/rsswidget.h \ @@ -98,6 +99,7 @@ SOURCES += \ $$PWD/cookiesmodel.cpp \ $$PWD/cookiesdialog.cpp \ $$PWD/categoryfiltermodel.cpp \ + $$PWD/categoryfilterproxymodel.cpp \ $$PWD/categoryfilterwidget.cpp \ $$PWD/banlistoptions.cpp \ $$PWD/rss/rsswidget.cpp \