From 2e55c1f3077920ae6258de9c034073d984ef6462 Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Tue, 4 Nov 2014 12:24:37 +0300 Subject: [PATCH 1/7] Optimize torrentRow a bit The problem is that torrentRow() does linear search over the list of all available torrents. So it doesn't scale well for large number of torrents. Removing the copying of QString from linear search inner loop, speed up it considerably. The proper solution should be using hash table instead of linear search. This require more radical changes in TorrentModel and may be done in a separate commit. --- src/qtlibtorrent/torrentmodel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qtlibtorrent/torrentmodel.h b/src/qtlibtorrent/torrentmodel.h index e62fce743..a39de99e5 100644 --- a/src/qtlibtorrent/torrentmodel.h +++ b/src/qtlibtorrent/torrentmodel.h @@ -57,7 +57,7 @@ public: inline int columnCount() const { return NB_COLUMNS; } QVariant data(int column, int role = Qt::DisplayRole) const; bool setData(int column, const QVariant &value, int role = Qt::DisplayRole); - inline QString hash() const { return m_hash; } + inline QString const& hash() const { return m_hash; } State state() const; signals: From e5c024967d2ccc9ea00aa7350671c2ad8e14e93e Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Wed, 5 Nov 2014 03:05:46 +0300 Subject: [PATCH 2/7] Emit TorrentModel::dataChanged() signal only for specific rows, not for the entire table In commit b50d733 TorrentModel moved from a periodic refresh, to using postStatusUpdate(). In this transition I forgot to remove emition of dataChanged() signal for the entire table. According to my measurements this commit reduce CPU usage of qbittorrent by a factor of 3: Before: Total wall clock: 97.07s CPU time: 21.77s - Time spent in TransferListDelegate::paint(): 14.60s - Time spent in TorrentModel::forceModelRefresh(): 1.44s - Time spent in TorrentModel::stateUpdated(): 0.02s After: Total wall clock: 96.13s CPU time: 6.68s - Time spent in TransferListDelegate::paint(): 2.63s - Time spent in TorrentModel::forceModelRefresh(): <0.01s - Time spent in TorrentModel::stateUpdated(): 1.73s As it is seen the time spent in painting is reduced by a factor of 6 (14.60->2.63) at the cost of slightly increased time of notifications that model is changed (1.44->1.73). The next commits attempt to address this issue. --- src/qtlibtorrent/torrentmodel.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/qtlibtorrent/torrentmodel.cpp b/src/qtlibtorrent/torrentmodel.cpp index eb819ee03..d28a8b2ef 100644 --- a/src/qtlibtorrent/torrentmodel.cpp +++ b/src/qtlibtorrent/torrentmodel.cpp @@ -508,7 +508,6 @@ void TorrentModel::setRefreshInterval(int refreshInterval) void TorrentModel::forceModelRefresh() { - emit dataChanged(index(0, 0), index(rowCount()-1, columnCount()-1)); QBtSession::instance()->postTorrentUpdate(); } @@ -591,8 +590,10 @@ void TorrentModel::stateUpdated(const std::vector &s libtorrent::torrent_status const& status = *i; const int row = torrentRow(misc::toQString(status.info_hash)); - if (row >= 0) + if (row >= 0) { m_torrents[row]->refreshStatus(status); + notifyTorrentChanged(row); + } } } From 0976918ca2223d3967fa9b28c3e6b39e2f35f92f Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Sun, 9 Nov 2014 01:27:06 +0300 Subject: [PATCH 3/7] Call updateTorrentNumbers() once per every model refresh, not once for every row changed Torrent numbers were recalculated on every dataChanged() signal. The previous commit greatly increases the number of dataChanged() signals. HEAD^^: Total wall clock: 97.069s updateTorrentNumbers() time: 0.033s HEAD^: Total wall clock: 96.132s updateTorrentNumbers() time: 0.179s HEAD: Total wall clock: 95.535s updateTorrentNumbers() time: 0.047s After this commit the time of updateTorrentNumbers() is (almost) back to the level that it was in HEAD^^. --- src/qtlibtorrent/torrentmodel.cpp | 2 ++ src/qtlibtorrent/torrentmodel.h | 1 + src/transferlistfilterswidget.cpp | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/qtlibtorrent/torrentmodel.cpp b/src/qtlibtorrent/torrentmodel.cpp index d28a8b2ef..9234a1138 100644 --- a/src/qtlibtorrent/torrentmodel.cpp +++ b/src/qtlibtorrent/torrentmodel.cpp @@ -595,6 +595,8 @@ void TorrentModel::stateUpdated(const std::vector &s notifyTorrentChanged(row); } } + + emit modelRefreshed(); } bool TorrentModel::inhibitSystem() diff --git a/src/qtlibtorrent/torrentmodel.h b/src/qtlibtorrent/torrentmodel.h index a39de99e5..8bebce656 100644 --- a/src/qtlibtorrent/torrentmodel.h +++ b/src/qtlibtorrent/torrentmodel.h @@ -101,6 +101,7 @@ signals: void torrentAdded(TorrentModelItem *torrentItem); void torrentAboutToBeRemoved(TorrentModelItem *torrentItem); void torrentChangedLabel(TorrentModelItem *torrentItem, QString previous, QString current); + void modelRefreshed(); private slots: void addTorrent(const QTorrentHandle& h); diff --git a/src/transferlistfilterswidget.cpp b/src/transferlistfilterswidget.cpp index 21618c429..f8734d612 100644 --- a/src/transferlistfilterswidget.cpp +++ b/src/transferlistfilterswidget.cpp @@ -203,7 +203,7 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi // SIGNAL/SLOT connect(statusFilters, SIGNAL(currentRowChanged(int)), transferList, SLOT(applyStatusFilter(int))); - connect(transferList->getSourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(updateTorrentNumbers())); + connect(transferList->getSourceModel(), SIGNAL(modelRefreshed()), SLOT(updateTorrentNumbers())); connect(transferList->getSourceModel(), SIGNAL(torrentAdded(TorrentModelItem*)), SLOT(handleNewTorrent(TorrentModelItem*))); connect(labelFilters, SIGNAL(currentRowChanged(int)), this, SLOT(applyLabelFilter(int))); connect(labelFilters, SIGNAL(torrentDropped(int)), this, SLOT(torrentDropped(int))); From cf2cb298268371f787b797a0536b2a0ce415a78f Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Sun, 9 Nov 2014 01:43:24 +0300 Subject: [PATCH 4/7] Split transferlistsortmodel into .h and .cpp --- src/src.pro | 1 + src/transferlistsortmodel.cpp | 171 ++++++++++++++++++++++++++++++++++ src/transferlistsortmodel.h | 137 +-------------------------- 3 files changed, 174 insertions(+), 135 deletions(-) create mode 100644 src/transferlistsortmodel.cpp diff --git a/src/src.pro b/src/src.pro index b00956a2e..8c0e2144d 100644 --- a/src/src.pro +++ b/src/src.pro @@ -160,6 +160,7 @@ nox { SOURCES += mainwindow.cpp \ ico.cpp \ transferlistwidget.cpp \ + transferlistsortmodel.cpp \ transferlistdelegate.cpp \ transferlistfilterswidget.cpp \ torrentcontentmodel.cpp \ diff --git a/src/transferlistsortmodel.cpp b/src/transferlistsortmodel.cpp new file mode 100644 index 000000000..9c3c1f19d --- /dev/null +++ b/src/transferlistsortmodel.cpp @@ -0,0 +1,171 @@ +/* + * Bittorrent Client using Qt4 and libtorrent. + * Copyright (C) 2013 Nick Tiskov + * + * 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. + * + * Contact : daymansmail@gmail.com + */ + +#include "transferlistsortmodel.h" + +#include "torrentmodel.h" +#include "misc.h" + +TransferListSortModel::TransferListSortModel(QObject *parent) + : QSortFilterProxyModel(parent) +{} + +bool TransferListSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { + const int column = sortColumn(); + + if (column == TorrentModelItem::TR_NAME) { + QVariant vL = left.data(); + QVariant vR = right.data(); + if (!(vL.isValid() && vR.isValid())) + return QSortFilterProxyModel::lessThan(left, right); + Q_ASSERT(vL.isValid()); + Q_ASSERT(vR.isValid()); + + bool res = false; + if (misc::naturalSort(vL.toString(), vR.toString(), res)) + return res; + + return QSortFilterProxyModel::lessThan(left, right); + } + else if (column == TorrentModelItem::TR_ADD_DATE || column == TorrentModelItem::TR_SEED_DATE || column == TorrentModelItem::TR_SEEN_COMPLETE_DATE) { + QDateTime vL = left.data().toDateTime(); + QDateTime vR = right.data().toDateTime(); + + //not valid dates should be sorted at the bottom. + if (!vL.isValid()) return false; + if (!vR.isValid()) return true; + + return vL < vR; + } + else if (column == TorrentModelItem::TR_PRIORITY) { + const int vL = left.data().toInt(); + const int vR = right.data().toInt(); + + // Seeding torrents should be sorted by their completed date instead. + if (vL == -1 && vR == -1) { + QAbstractItemModel *model = sourceModel(); + const QDateTime dateL = model->data(model->index(left.row(), TorrentModelItem::TR_SEED_DATE)).toDateTime(); + const QDateTime dateR = model->data(model->index(right.row(), TorrentModelItem::TR_SEED_DATE)).toDateTime(); + + //not valid dates should be sorted at the bottom. + if (!dateL.isValid()) return false; + if (!dateR.isValid()) return true; + + return dateL < dateR; + } + + // Seeding torrents should be at the bottom + if (vL == -1) return false; + if (vR == -1) return true; + return vL < vR; + } + else if (column == TorrentModelItem::TR_PEERS || column == TorrentModelItem::TR_SEEDS) { + int left_active = left.data().toInt(); + int left_total = left.data(Qt::UserRole).toInt(); + int right_active = right.data().toInt(); + int right_total = right.data(Qt::UserRole).toInt(); + + // Active peers/seeds take precedence over total peers/seeds. + if (left_active == right_active) + return (left_total < right_total); + else return (left_active < right_active); + } + else if (column == TorrentModelItem::TR_ETA) { + const QAbstractItemModel *model = sourceModel(); + const int prioL = model->data(model->index(left.row(), TorrentModelItem::TR_PRIORITY)).toInt(); + const int prioR = model->data(model->index(right.row(), TorrentModelItem::TR_PRIORITY)).toInt(); + const qlonglong etaL = left.data().toLongLong(); + const qlonglong etaR = right.data().toLongLong(); + const bool ascend = (sortOrder() == Qt::AscendingOrder); + const bool invalidL = (etaL < 0 || etaL >= MAX_ETA); + const bool invalidR = (etaR < 0 || etaR >= MAX_ETA); + const bool seedingL = (prioL < 0); + const bool seedingR = (prioR < 0); + bool activeL; + bool activeR; + + switch (model->data(model->index(left.row(), TorrentModelItem::TR_STATUS)).toInt()) { + case TorrentModelItem::STATE_DOWNLOADING: + case TorrentModelItem::STATE_DOWNLOADING_META: + case TorrentModelItem::STATE_STALLED_DL: + case TorrentModelItem::STATE_SEEDING: + case TorrentModelItem::STATE_STALLED_UP: + activeL = true; + break; + default: + activeL = false; + } + + switch (model->data(model->index(right.row(), TorrentModelItem::TR_STATUS)).toInt()) { + case TorrentModelItem::STATE_DOWNLOADING: + case TorrentModelItem::STATE_DOWNLOADING_META: + case TorrentModelItem::STATE_STALLED_DL: + case TorrentModelItem::STATE_SEEDING: + case TorrentModelItem::STATE_STALLED_UP: + activeR = true; + break; + default: + activeR = false; + } + + // Sorting rules prioritized. + // 1. Active torrents at the top + // 2. Seeding torrents at the bottom + // 3. Torrents with invalid ETAs at the bottom + + if (activeL != activeR) return activeL; + if (seedingL != seedingR) { + if (seedingL) return !ascend; + else return ascend; + } + + if (invalidL && invalidR) { + + if (seedingL) { //Both seeding + QDateTime dateL = model->data(model->index(left.row(), TorrentModelItem::TR_SEED_DATE)).toDateTime(); + QDateTime dateR = model->data(model->index(right.row(), TorrentModelItem::TR_SEED_DATE)).toDateTime(); + + //not valid dates should be sorted at the bottom. + if (!dateL.isValid()) return false; + if (!dateR.isValid()) return true; + + return dateL < dateR; + } + else + return prioL < prioR; + } + else if ((invalidL == false) && (invalidR == false)) + return QSortFilterProxyModel::lessThan(left, right); + else + return !invalidL; + } + + return QSortFilterProxyModel::lessThan(left, right); +} diff --git a/src/transferlistsortmodel.h b/src/transferlistsortmodel.h index 7414172c4..a25b3fc79 100644 --- a/src/transferlistsortmodel.h +++ b/src/transferlistsortmodel.h @@ -32,148 +32,15 @@ #define TRANSFERLISTSORTMODEL_H #include -#include "torrentmodel.h" -#include "misc.h" class TransferListSortModel : public QSortFilterProxyModel { Q_OBJECT public: - TransferListSortModel(QObject *parent = 0) : QSortFilterProxyModel(parent) {} + TransferListSortModel(QObject *parent = 0); protected: - virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const { - const int column = sortColumn(); - - if (column == TorrentModelItem::TR_NAME) { - QVariant vL = left.data(); - QVariant vR = right.data(); - if (!(vL.isValid() && vR.isValid())) - return QSortFilterProxyModel::lessThan(left, right); - Q_ASSERT(vL.isValid()); - Q_ASSERT(vR.isValid()); - - bool res = false; - if (misc::naturalSort(vL.toString(), vR.toString(), res)) - return res; - - return QSortFilterProxyModel::lessThan(left, right); - } - else if (column == TorrentModelItem::TR_ADD_DATE || column == TorrentModelItem::TR_SEED_DATE || column == TorrentModelItem::TR_SEEN_COMPLETE_DATE) { - QDateTime vL = left.data().toDateTime(); - QDateTime vR = right.data().toDateTime(); - - //not valid dates should be sorted at the bottom. - if (!vL.isValid()) return false; - if (!vR.isValid()) return true; - - return vL < vR; - } - else if (column == TorrentModelItem::TR_PRIORITY) { - const int vL = left.data().toInt(); - const int vR = right.data().toInt(); - - // Seeding torrents should be sorted by their completed date instead. - if (vL == -1 && vR == -1) { - QAbstractItemModel *model = sourceModel(); - const QDateTime dateL = model->data(model->index(left.row(), TorrentModelItem::TR_SEED_DATE)).toDateTime(); - const QDateTime dateR = model->data(model->index(right.row(), TorrentModelItem::TR_SEED_DATE)).toDateTime(); - - //not valid dates should be sorted at the bottom. - if (!dateL.isValid()) return false; - if (!dateR.isValid()) return true; - - return dateL < dateR; - } - - // Seeding torrents should be at the bottom - if (vL == -1) return false; - if (vR == -1) return true; - return vL < vR; - } - else if (column == TorrentModelItem::TR_PEERS || column == TorrentModelItem::TR_SEEDS) { - int left_active = left.data().toInt(); - int left_total = left.data(Qt::UserRole).toInt(); - int right_active = right.data().toInt(); - int right_total = right.data(Qt::UserRole).toInt(); - - // Active peers/seeds take precedence over total peers/seeds. - if (left_active == right_active) - return (left_total < right_total); - else return (left_active < right_active); - } - else if (column == TorrentModelItem::TR_ETA) { - const QAbstractItemModel *model = sourceModel(); - const int prioL = model->data(model->index(left.row(), TorrentModelItem::TR_PRIORITY)).toInt(); - const int prioR = model->data(model->index(right.row(), TorrentModelItem::TR_PRIORITY)).toInt(); - const qlonglong etaL = left.data().toLongLong(); - const qlonglong etaR = right.data().toLongLong(); - const bool ascend = (sortOrder() == Qt::AscendingOrder); - const bool invalidL = (etaL < 0 || etaL >= MAX_ETA); - const bool invalidR = (etaR < 0 || etaR >= MAX_ETA); - const bool seedingL = (prioL < 0); - const bool seedingR = (prioR < 0); - bool activeL; - bool activeR; - - switch (model->data(model->index(left.row(), TorrentModelItem::TR_STATUS)).toInt()) { - case TorrentModelItem::STATE_DOWNLOADING: - case TorrentModelItem::STATE_DOWNLOADING_META: - case TorrentModelItem::STATE_STALLED_DL: - case TorrentModelItem::STATE_SEEDING: - case TorrentModelItem::STATE_STALLED_UP: - activeL = true; - break; - default: - activeL = false; - } - - switch (model->data(model->index(right.row(), TorrentModelItem::TR_STATUS)).toInt()) { - case TorrentModelItem::STATE_DOWNLOADING: - case TorrentModelItem::STATE_DOWNLOADING_META: - case TorrentModelItem::STATE_STALLED_DL: - case TorrentModelItem::STATE_SEEDING: - case TorrentModelItem::STATE_STALLED_UP: - activeR = true; - break; - default: - activeR = false; - } - - // Sorting rules prioritized. - // 1. Active torrents at the top - // 2. Seeding torrents at the bottom - // 3. Torrents with invalid ETAs at the bottom - - if (activeL != activeR) return activeL; - if (seedingL != seedingR) { - if (seedingL) return !ascend; - else return ascend; - } - - if (invalidL && invalidR) { - - if (seedingL) { //Both seeding - QDateTime dateL = model->data(model->index(left.row(), TorrentModelItem::TR_SEED_DATE)).toDateTime(); - QDateTime dateR = model->data(model->index(right.row(), TorrentModelItem::TR_SEED_DATE)).toDateTime(); - - //not valid dates should be sorted at the bottom. - if (!dateL.isValid()) return false; - if (!dateR.isValid()) return true; - - return dateL < dateR; - } - else - return prioL < prioR; - } - else if ((invalidL == false) && (invalidR == false)) - return QSortFilterProxyModel::lessThan(left, right); - else - return !invalidL; - } - - return QSortFilterProxyModel::lessThan(left, right); - } + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; }; #endif // TRANSFERLISTSORTMODEL_H From 8bafc5e21654e1b1b7f92bd44c93a2bd7ad2a708 Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Sun, 9 Nov 2014 01:58:01 +0300 Subject: [PATCH 5/7] Merge StatusSortFilterProxyModel into TransferListSortModel I thought merging all 3 sort-filter proxy models into one should speedup dataChanged() signal. As turned out this is not the case. The time difference is within inaccuracy range, so this doesn't affect performance. But I still think it is good to merge them into one proxy model as it simplifies code. --- src/src.pro | 2 - src/statussortfilterproxymodel.cpp | 90 ------------------------------ src/statussortfilterproxymodel.h | 52 ----------------- src/transferlistsortmodel.cpp | 56 +++++++++++++++++++ src/transferlistsortmodel.h | 13 ++++- src/transferlistwidget.cpp | 16 ++---- src/transferlistwidget.h | 2 - 7 files changed, 71 insertions(+), 160 deletions(-) delete mode 100644 src/statussortfilterproxymodel.cpp delete mode 100644 src/statussortfilterproxymodel.h diff --git a/src/src.pro b/src/src.pro index 8c0e2144d..34eb13e7b 100644 --- a/src/src.pro +++ b/src/src.pro @@ -154,7 +154,6 @@ nox { autoexpandabledialog.h \ statsdialog.h \ messageboxraised.h \ - statussortfilterproxymodel.h \ torrentfilterenum.h SOURCES += mainwindow.cpp \ @@ -179,7 +178,6 @@ nox { autoexpandabledialog.cpp \ statsdialog.cpp \ messageboxraised.cpp \ - statussortfilterproxymodel.cpp \ statusbar.cpp \ trackerlogin.cpp diff --git a/src/statussortfilterproxymodel.cpp b/src/statussortfilterproxymodel.cpp deleted file mode 100644 index 5eb4d8ec7..000000000 --- a/src/statussortfilterproxymodel.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2014 sledgehammer999 - * - * 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. - * - * Contact : hammered999@gmail.com - */ - -#include "statussortfilterproxymodel.h" -#include "torrentmodel.h" - -StatusSortFilterProxyModel::StatusSortFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent), filter0(TorrentFilter::ALL) -{ -} - -void StatusSortFilterProxyModel::setFilterStatus(const TorrentFilter::TorrentFilter &filter) { - if (filter != filter0) { - filter0 = filter; - invalidateFilter(); - } -} - -TorrentFilter::TorrentFilter StatusSortFilterProxyModel::getFilterStatus() const { - return filter0; -} - -bool StatusSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { - if (filter0 == TorrentFilter::ALL) - return true; - QAbstractItemModel *model = sourceModel(); - if (!model) return false; - QModelIndex index = model->index(sourceRow, TorrentModelItem::TR_STATUS, sourceParent); - TorrentModelItem::State state = (TorrentModelItem::State)index.data().toInt(); - - switch (filter0) { - case TorrentFilter::DOWNLOADING: - return (state == TorrentModelItem::STATE_DOWNLOADING || state == TorrentModelItem::STATE_STALLED_DL - || state == TorrentModelItem::STATE_PAUSED_DL || state == TorrentModelItem::STATE_CHECKING_DL - || state == TorrentModelItem::STATE_QUEUED_DL || state == TorrentModelItem::STATE_DOWNLOADING_META); - - case TorrentFilter::COMPLETED: - return (state == TorrentModelItem::STATE_SEEDING || state == TorrentModelItem::STATE_STALLED_UP - || state == TorrentModelItem::STATE_PAUSED_UP || state == TorrentModelItem::STATE_CHECKING_UP - || state == TorrentModelItem::STATE_QUEUED_UP); - - case TorrentFilter::PAUSED: - return (state == TorrentModelItem::STATE_PAUSED_UP || state == TorrentModelItem::STATE_PAUSED_DL); - - case TorrentFilter::ACTIVE: - if (state == TorrentModelItem::STATE_STALLED_DL) { - const qulonglong up_speed = model->index(sourceRow, TorrentModelItem::TR_UPSPEED, sourceParent).data().toULongLong(); - return (up_speed > 0); - } - - return (state == TorrentModelItem::STATE_DOWNLOADING || state == TorrentModelItem::STATE_SEEDING); - - case TorrentFilter::INACTIVE: - if (state == TorrentModelItem::STATE_STALLED_DL) { - const qulonglong up_speed = model->index(sourceRow, TorrentModelItem::TR_UPSPEED, sourceParent).data().toULongLong(); - return !(up_speed > 0); - } - - return (state != TorrentModelItem::STATE_DOWNLOADING && state != TorrentModelItem::STATE_SEEDING); - - default: - return false; - } -} diff --git a/src/statussortfilterproxymodel.h b/src/statussortfilterproxymodel.h deleted file mode 100644 index b5a5b1cec..000000000 --- a/src/statussortfilterproxymodel.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Bittorrent Client using Qt4 and libtorrent. - * Copyright (C) 2014 sledgehammer999 - * - * 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. - * - * Contact : hammered999@gmail.com - */ - -#ifndef STATUSSORTFILTERPROXYMODEL_H -#define STATUSSORTFILTERPROXYMODEL_H - -#include -#include "torrentfilterenum.h" - -class StatusSortFilterProxyModel : public QSortFilterProxyModel { - Q_OBJECT - -public: - StatusSortFilterProxyModel(QObject *parent = 0); - void setFilterStatus(const TorrentFilter::TorrentFilter &filter); - TorrentFilter::TorrentFilter getFilterStatus() const; - -protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; - -private: - TorrentFilter::TorrentFilter filter0; -}; - -#endif // STATUSSORTFILTERPROXYMODEL_H diff --git a/src/transferlistsortmodel.cpp b/src/transferlistsortmodel.cpp index 9c3c1f19d..d74b3c818 100644 --- a/src/transferlistsortmodel.cpp +++ b/src/transferlistsortmodel.cpp @@ -35,8 +35,16 @@ TransferListSortModel::TransferListSortModel(QObject *parent) : QSortFilterProxyModel(parent) + , filter0(TorrentFilter::ALL) {} +void TransferListSortModel::setStatusFilter(const TorrentFilter::TorrentFilter &filter) { + if (filter != filter0) { + filter0 = filter; + invalidateFilter(); + } +} + bool TransferListSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { const int column = sortColumn(); @@ -169,3 +177,51 @@ bool TransferListSortModel::lessThan(const QModelIndex &left, const QModelIndex return QSortFilterProxyModel::lessThan(left, right); } + +bool TransferListSortModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { + return matchStatusFilter(sourceRow, sourceParent) + && QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); +} + +bool TransferListSortModel::matchStatusFilter(int sourceRow, const QModelIndex &sourceParent) const { + if (filter0 == TorrentFilter::ALL) + return true; + QAbstractItemModel *model = sourceModel(); + if (!model) return false; + QModelIndex index = model->index(sourceRow, TorrentModelItem::TR_STATUS, sourceParent); + TorrentModelItem::State state = (TorrentModelItem::State)index.data().toInt(); + + switch (filter0) { + case TorrentFilter::DOWNLOADING: + return (state == TorrentModelItem::STATE_DOWNLOADING || state == TorrentModelItem::STATE_STALLED_DL + || state == TorrentModelItem::STATE_PAUSED_DL || state == TorrentModelItem::STATE_CHECKING_DL + || state == TorrentModelItem::STATE_QUEUED_DL || state == TorrentModelItem::STATE_DOWNLOADING_META); + + case TorrentFilter::COMPLETED: + return (state == TorrentModelItem::STATE_SEEDING || state == TorrentModelItem::STATE_STALLED_UP + || state == TorrentModelItem::STATE_PAUSED_UP || state == TorrentModelItem::STATE_CHECKING_UP + || state == TorrentModelItem::STATE_QUEUED_UP); + + case TorrentFilter::PAUSED: + return (state == TorrentModelItem::STATE_PAUSED_UP || state == TorrentModelItem::STATE_PAUSED_DL); + + case TorrentFilter::ACTIVE: + if (state == TorrentModelItem::STATE_STALLED_DL) { + const qulonglong up_speed = model->index(sourceRow, TorrentModelItem::TR_UPSPEED, sourceParent).data().toULongLong(); + return (up_speed > 0); + } + + return (state == TorrentModelItem::STATE_DOWNLOADING || state == TorrentModelItem::STATE_SEEDING); + + case TorrentFilter::INACTIVE: + if (state == TorrentModelItem::STATE_STALLED_DL) { + const qulonglong up_speed = model->index(sourceRow, TorrentModelItem::TR_UPSPEED, sourceParent).data().toULongLong(); + return !(up_speed > 0); + } + + return (state != TorrentModelItem::STATE_DOWNLOADING && state != TorrentModelItem::STATE_SEEDING); + + default: + return false; + } +} diff --git a/src/transferlistsortmodel.h b/src/transferlistsortmodel.h index a25b3fc79..a0e261efb 100644 --- a/src/transferlistsortmodel.h +++ b/src/transferlistsortmodel.h @@ -32,6 +32,7 @@ #define TRANSFERLISTSORTMODEL_H #include +#include "torrentfilterenum.h" class TransferListSortModel : public QSortFilterProxyModel { Q_OBJECT @@ -39,8 +40,16 @@ class TransferListSortModel : public QSortFilterProxyModel { public: TransferListSortModel(QObject *parent = 0); -protected: - virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + void setStatusFilter(const TorrentFilter::TorrentFilter &filter); + +private: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + + bool matchStatusFilter(int sourceRow, const QModelIndex &sourceParent) const; + +private: + TorrentFilter::TorrentFilter filter0; }; #endif // TRANSFERLISTSORTMODEL_H diff --git a/src/transferlistwidget.cpp b/src/transferlistwidget.cpp index f8e435a09..abd0bea0c 100644 --- a/src/transferlistwidget.cpp +++ b/src/transferlistwidget.cpp @@ -63,7 +63,6 @@ #include "iconprovider.h" #include "fs_utils.h" #include "autoexpandabledialog.h" -#include "statussortfilterproxymodel.h" #include "transferlistsortmodel.h" using namespace libtorrent; @@ -89,13 +88,9 @@ TransferListWidget::TransferListWidget(QWidget *parent, MainWindow *main_window, labelFilterModel->setFilterKeyColumn(TorrentModelItem::TR_LABEL); labelFilterModel->setFilterRole(Qt::DisplayRole); - statusFilterModel = new StatusSortFilterProxyModel(); - statusFilterModel->setDynamicSortFilter(true); - statusFilterModel->setSourceModel(labelFilterModel); - nameFilterModel = new TransferListSortModel(); nameFilterModel->setDynamicSortFilter(true); - nameFilterModel->setSourceModel(statusFilterModel); + nameFilterModel->setSourceModel(labelFilterModel); nameFilterModel->setFilterKeyColumn(TorrentModelItem::TR_NAME); nameFilterModel->setFilterRole(Qt::DisplayRole); nameFilterModel->setSortCaseSensitivity(Qt::CaseInsensitive); @@ -171,7 +166,6 @@ TransferListWidget::~TransferListWidget() { saveSettings(); // Clean up delete labelFilterModel; - delete statusFilterModel; delete nameFilterModel; delete listModel; delete listDelegate; @@ -204,16 +198,14 @@ inline QString TransferListWidget::getHashFromRow(int row) const { inline QModelIndex TransferListWidget::mapToSource(const QModelIndex &index) const { Q_ASSERT(index.isValid()); if (index.model() == nameFilterModel) - return labelFilterModel->mapToSource(statusFilterModel->mapToSource(nameFilterModel->mapToSource(index))); - if (index.model() == statusFilterModel) - return labelFilterModel->mapToSource(statusFilterModel->mapToSource(index)); + return labelFilterModel->mapToSource(nameFilterModel->mapToSource(index)); return labelFilterModel->mapToSource(index); } inline QModelIndex TransferListWidget::mapFromSource(const QModelIndex &index) const { Q_ASSERT(index.isValid()); Q_ASSERT(index.model() == labelFilterModel); - return nameFilterModel->mapFromSource(statusFilterModel->mapFromSource(labelFilterModel->mapFromSource(index))); + return nameFilterModel->mapFromSource(labelFilterModel->mapFromSource(index)); } void TransferListWidget::torrentDoubleClicked(const QModelIndex& index) { @@ -919,7 +911,7 @@ void TransferListWidget::applyNameFilter(const QString& name) { } void TransferListWidget::applyStatusFilter(int f) { - statusFilterModel->setFilterStatus((TorrentFilter::TorrentFilter)f); + nameFilterModel->setStatusFilter((TorrentFilter::TorrentFilter)f); // Select first item if nothing is selected if (selectionModel()->selectedRows(0).empty() && nameFilterModel->rowCount() > 0) { qDebug("Nothing is selected, selecting first row: %s", qPrintable(nameFilterModel->index(0, TorrentModelItem::TR_NAME).data().toString())); diff --git a/src/transferlistwidget.h b/src/transferlistwidget.h index a07e6123a..8ddc27900 100644 --- a/src/transferlistwidget.h +++ b/src/transferlistwidget.h @@ -40,7 +40,6 @@ class MainWindow; class TransferListDelegate; class TransferListSortModel; class TorrentModel; -class StatusSortFilterProxyModel; QT_BEGIN_NAMESPACE class QShortcut; @@ -114,7 +113,6 @@ private: TransferListDelegate *listDelegate; TorrentModel *listModel; TransferListSortModel *nameFilterModel; - StatusSortFilterProxyModel *statusFilterModel; QSortFilterProxyModel *labelFilterModel; QBtSession* BTSession; MainWindow *main_window; From f235c0ae6ca3bca45ad3e28e37e5ae178ae09c11 Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Sun, 9 Nov 2014 02:49:21 +0300 Subject: [PATCH 6/7] Merge label filter into TransferListSortModel This also fixes a bug that when label filter contains special symbols from regex, the label filter may match torrents with multiple different labels. --- src/transferlistsortmodel.cpp | 28 ++++++++++++++++++++++++++++ src/transferlistsortmodel.h | 6 ++++++ src/transferlistwidget.cpp | 24 ++++++++---------------- src/transferlistwidget.h | 1 - 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/transferlistsortmodel.cpp b/src/transferlistsortmodel.cpp index d74b3c818..56e5cf79c 100644 --- a/src/transferlistsortmodel.cpp +++ b/src/transferlistsortmodel.cpp @@ -45,6 +45,22 @@ void TransferListSortModel::setStatusFilter(const TorrentFilter::TorrentFilter & } } +void TransferListSortModel::setLabelFilter(QString const& label) { + if (!labelFilterEnabled || labelFilter != label) { + labelFilterEnabled = true; + labelFilter = label; + invalidateFilter(); + } +} + +void TransferListSortModel::disableLabelFilter() { + if (labelFilterEnabled) { + labelFilterEnabled = false; + labelFilter = QString(); + invalidateFilter(); + } +} + bool TransferListSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { const int column = sortColumn(); @@ -180,6 +196,7 @@ bool TransferListSortModel::lessThan(const QModelIndex &left, const QModelIndex bool TransferListSortModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { return matchStatusFilter(sourceRow, sourceParent) + && matchLabelFilter(sourceRow, sourceParent) && QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); } @@ -225,3 +242,14 @@ bool TransferListSortModel::matchStatusFilter(int sourceRow, const QModelIndex & return false; } } + +bool TransferListSortModel::matchLabelFilter(int sourceRow, const QModelIndex &sourceParent) const { + if (!labelFilterEnabled) + return true; + + QAbstractItemModel *model = sourceModel(); + if (!model) + return false; + + return model->index(sourceRow, TorrentModelItem::TR_LABEL, sourceParent).data().toString() == labelFilter; +} diff --git a/src/transferlistsortmodel.h b/src/transferlistsortmodel.h index a0e261efb..e8d35d6f9 100644 --- a/src/transferlistsortmodel.h +++ b/src/transferlistsortmodel.h @@ -41,15 +41,21 @@ public: TransferListSortModel(QObject *parent = 0); void setStatusFilter(const TorrentFilter::TorrentFilter &filter); + void setLabelFilter(QString const& label); + void disableLabelFilter(); private: bool lessThan(const QModelIndex &left, const QModelIndex &right) const; bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; bool matchStatusFilter(int sourceRow, const QModelIndex &sourceParent) const; + bool matchLabelFilter(int sourceRow, const QModelIndex &sourceParent) const; private: TorrentFilter::TorrentFilter filter0; + + bool labelFilterEnabled; + QString labelFilter; }; #endif // TRANSFERLISTSORTMODEL_H diff --git a/src/transferlistwidget.cpp b/src/transferlistwidget.cpp index abd0bea0c..269da5670 100644 --- a/src/transferlistwidget.cpp +++ b/src/transferlistwidget.cpp @@ -81,16 +81,9 @@ TransferListWidget::TransferListWidget(QWidget *parent, MainWindow *main_window, // Create transfer list model listModel = new TorrentModel(this); - // Set Sort/Filter proxy - labelFilterModel = new QSortFilterProxyModel(); - labelFilterModel->setDynamicSortFilter(true); - labelFilterModel->setSourceModel(listModel); - labelFilterModel->setFilterKeyColumn(TorrentModelItem::TR_LABEL); - labelFilterModel->setFilterRole(Qt::DisplayRole); - nameFilterModel = new TransferListSortModel(); nameFilterModel->setDynamicSortFilter(true); - nameFilterModel->setSourceModel(labelFilterModel); + nameFilterModel->setSourceModel(listModel); nameFilterModel->setFilterKeyColumn(TorrentModelItem::TR_NAME); nameFilterModel->setFilterRole(Qt::DisplayRole); nameFilterModel->setSortCaseSensitivity(Qt::CaseInsensitive); @@ -165,7 +158,6 @@ TransferListWidget::~TransferListWidget() { // Save settings saveSettings(); // Clean up - delete labelFilterModel; delete nameFilterModel; delete listModel; delete listDelegate; @@ -198,14 +190,14 @@ inline QString TransferListWidget::getHashFromRow(int row) const { inline QModelIndex TransferListWidget::mapToSource(const QModelIndex &index) const { Q_ASSERT(index.isValid()); if (index.model() == nameFilterModel) - return labelFilterModel->mapToSource(nameFilterModel->mapToSource(index)); - return labelFilterModel->mapToSource(index); + return nameFilterModel->mapToSource(index); + return index; } inline QModelIndex TransferListWidget::mapFromSource(const QModelIndex &index) const { Q_ASSERT(index.isValid()); - Q_ASSERT(index.model() == labelFilterModel); - return nameFilterModel->mapFromSource(labelFilterModel->mapFromSource(index)); + Q_ASSERT(index.model() == nameFilterModel); + return nameFilterModel->mapFromSource(index); } void TransferListWidget::torrentDoubleClicked(const QModelIndex& index) { @@ -895,15 +887,15 @@ void TransferListWidget::currentChanged(const QModelIndex& current, const QModel void TransferListWidget::applyLabelFilter(QString label) { if (label == "all") { - labelFilterModel->setFilterRegExp(QRegExp()); + nameFilterModel->disableLabelFilter(); return; } if (label == "none") { - labelFilterModel->setFilterRegExp(QRegExp("^$")); + nameFilterModel->setLabelFilter(QString()); return; } qDebug("Applying Label filter: %s", qPrintable(label)); - labelFilterModel->setFilterRegExp(QRegExp("^" + QRegExp::escape(label) + "$", Qt::CaseSensitive)); + nameFilterModel->setLabelFilter(label); } void TransferListWidget::applyNameFilter(const QString& name) { diff --git a/src/transferlistwidget.h b/src/transferlistwidget.h index 8ddc27900..b593e4f31 100644 --- a/src/transferlistwidget.h +++ b/src/transferlistwidget.h @@ -113,7 +113,6 @@ private: TransferListDelegate *listDelegate; TorrentModel *listModel; TransferListSortModel *nameFilterModel; - QSortFilterProxyModel *labelFilterModel; QBtSession* BTSession; MainWindow *main_window; QShortcut *editHotkey; From 976982ba09935dd0d482ae900c58218e059741e1 Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Sun, 9 Nov 2014 03:36:53 +0300 Subject: [PATCH 7/7] Now user-created labels "all" and "none" works as regular labels --- src/transferlistfilterswidget.cpp | 4 ++-- src/transferlistwidget.cpp | 12 ++++-------- src/transferlistwidget.h | 1 + 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/transferlistfilterswidget.cpp b/src/transferlistfilterswidget.cpp index f8734d612..f52f13d5a 100644 --- a/src/transferlistfilterswidget.cpp +++ b/src/transferlistfilterswidget.cpp @@ -359,10 +359,10 @@ void TransferListFiltersWidget::removeSelectedLabel() { void TransferListFiltersWidget::applyLabelFilter(int row) { switch(row) { case 0: - transferList->applyLabelFilter("all"); + transferList->applyLabelFilterAll(); break; case 1: - transferList->applyLabelFilter("none"); + transferList->applyLabelFilter(QString()); break; default: transferList->applyLabelFilter(labelFilters->labelFromRow(row)); diff --git a/src/transferlistwidget.cpp b/src/transferlistwidget.cpp index 269da5670..00a99ac61 100644 --- a/src/transferlistwidget.cpp +++ b/src/transferlistwidget.cpp @@ -885,15 +885,11 @@ void TransferListWidget::currentChanged(const QModelIndex& current, const QModel emit currentTorrentChanged(h); } +void TransferListWidget::applyLabelFilterAll() { + nameFilterModel->disableLabelFilter(); +} + void TransferListWidget::applyLabelFilter(QString label) { - if (label == "all") { - nameFilterModel->disableLabelFilter(); - return; - } - if (label == "none") { - nameFilterModel->setLabelFilter(QString()); - return; - } qDebug("Applying Label filter: %s", qPrintable(label)); nameFilterModel->setLabelFilter(label); } diff --git a/src/transferlistwidget.h b/src/transferlistwidget.h index b593e4f31..47f1773e0 100644 --- a/src/transferlistwidget.h +++ b/src/transferlistwidget.h @@ -80,6 +80,7 @@ public slots: void displayDLHoSMenu(const QPoint&); void applyNameFilter(const QString& name); void applyStatusFilter(int f); + void applyLabelFilterAll(); void applyLabelFilter(QString label); void previewFile(QString filePath); void removeLabelFromRows(QString label);