qBittorrent
tagfilterwidget.cpp
Go to the documentation of this file.
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2017 Tony Gregerson <tony.gregerson@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  * In addition, as a special exception, the copyright holders give permission to
20  * link this program with the OpenSSL project's "OpenSSL" library (or with
21  * modified versions of it that use the same license as the "OpenSSL" library),
22  * and distribute the linked executables. You must obey the GNU General Public
23  * License in all respects for all of the code used other than "OpenSSL". If you
24  * modify file(s), you may extend this exception to your version of the file(s),
25  * but you are not obligated to do so. If you do not wish to do so, delete this
26  * exception statement from your version.
27  */
28 
29 #include "tagfilterwidget.h"
30 
31 #include <QMenu>
32 #include <QMessageBox>
33 
35 #include "base/global.h"
36 #include "autoexpandabledialog.h"
37 #include "tagfiltermodel.h"
38 #include "tagfilterproxymodel.h"
39 #include "uithememanager.h"
40 #include "utils.h"
41 
42 namespace
43 {
44  QString getTagFilter(const TagFilterProxyModel *const model, const QModelIndex &index)
45  {
46  QString tagFilter; // Defaults to All
47  if (index.isValid())
48  {
49  if (index.row() == 1)
50  tagFilter = ""; // Untagged
51  else if (index.row() > 1)
52  tagFilter = model->tag(index);
53  }
54  return tagFilter;
55  }
56 }
57 
59  : QTreeView(parent)
60 {
61  auto *proxyModel = new TagFilterProxyModel(this);
62  proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
63  proxyModel->setSourceModel(new TagFilterModel(this));
64  setModel(proxyModel);
65  setFrameShape(QFrame::NoFrame);
66  setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
67  setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
68  setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
69  setUniformRowHeights(true);
70  setHeaderHidden(true);
71  setIconSize(Utils::Gui::smallIconSize());
72 #if defined(Q_OS_MACOS)
73  setAttribute(Qt::WA_MacShowFocusRect, false);
74 #endif
75  setIndentation(0);
76  setContextMenuPolicy(Qt::CustomContextMenu);
77  sortByColumn(0, Qt::AscendingOrder);
78  setCurrentIndex(model()->index(0, 0));
79 
80  connect(this, &TagFilterWidget::collapsed, this, &TagFilterWidget::callUpdateGeometry);
81  connect(this, &TagFilterWidget::expanded, this, &TagFilterWidget::callUpdateGeometry);
82  connect(this, &TagFilterWidget::customContextMenuRequested, this, &TagFilterWidget::showMenu);
83  connect(selectionModel(), &QItemSelectionModel::currentRowChanged, this
85  connect(model(), &QAbstractItemModel::modelReset, this, &TagFilterWidget::callUpdateGeometry);
86 }
87 
89 {
90  QModelIndex current;
91  const auto selectedRows = selectionModel()->selectedRows();
92  if (!selectedRows.isEmpty())
93  current = selectedRows.first();
94 
95  return getTagFilter(static_cast<TagFilterProxyModel *>(model()), current);
96 }
97 
98 void TagFilterWidget::onCurrentRowChanged(const QModelIndex &current, const QModelIndex &previous)
99 {
100  Q_UNUSED(previous);
101 
102  emit tagChanged(getTagFilter(static_cast<TagFilterProxyModel *>(model()), current));
103 }
104 
106 {
107  QMenu *menu = new QMenu(this);
108  menu->setAttribute(Qt::WA_DeleteOnClose);
109 
110  menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add tag...")
111  , this, &TagFilterWidget::addTag);
112 
113  const auto selectedRows = selectionModel()->selectedRows();
114  if (!selectedRows.empty() && !TagFilterModel::isSpecialItem(selectedRows.first()))
115  {
116  menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove tag")
117  , this, &TagFilterWidget::removeTag);
118  }
119 
120  menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove unused tags")
122  menu->addSeparator();
123  menu->addAction(UIThemeManager::instance()->getIcon("media-playback-start"), tr("Resume torrents")
125  menu->addAction(UIThemeManager::instance()->getIcon("media-playback-pause"), tr("Pause torrents")
127  menu->addAction(UIThemeManager::instance()->getIcon("edit-delete"), tr("Delete torrents")
129 
130  menu->popup(QCursor::pos());
131 }
132 
134 {
135  updateGeometry();
136 }
137 
139 {
140  return
141  {
142  // Width should be exactly the width of the content
143  sizeHintForColumn(0),
144  // Height should be exactly the height of the content
145  static_cast<int>(sizeHintForRow(0) * (model()->rowCount() + 0.5)),
146  };
147 }
148 
150 {
151  QSize size = sizeHint();
152  size.setWidth(6);
153  return size;
154 }
155 
156 void TagFilterWidget::rowsInserted(const QModelIndex &parent, int start, int end)
157 {
158  QTreeView::rowsInserted(parent, start, end);
159  updateGeometry();
160 }
161 
163 {
164  bool ok = false;
165  QString tag = "";
166  bool invalid = true;
167  while (invalid)
168  {
169  invalid = false;
171  this, tr("New Tag"), tr("Tag:"), QLineEdit::Normal, tag, &ok).trimmed();
172  if (ok && !tag.isEmpty())
173  {
175  {
176  QMessageBox::warning(
177  this, tr("Invalid tag name")
178  , tr("Tag name '%1' is invalid").arg(tag));
179  invalid = true;
180  }
181  }
182  }
183 
184  return ok ? tag : QString();
185 }
186 
188 {
189  const QString tag = askTagName();
190  if (tag.isEmpty()) return;
191 
192  if (BitTorrent::Session::instance()->tags().contains(tag))
193  QMessageBox::warning(this, tr("Tag exists"), tr("Tag name already exists."));
194  else
196 }
197 
199 {
200  const auto selectedRows = selectionModel()->selectedRows();
201  if (!selectedRows.empty() && !TagFilterModel::isSpecialItem(selectedRows.first()))
202  {
204  static_cast<TagFilterProxyModel *>(model())->tag(selectedRows.first()));
205  updateGeometry();
206  }
207 }
208 
210 {
211  auto session = BitTorrent::Session::instance();
212  for (const QString &tag : asConst(session->tags()))
213  if (model()->data(static_cast<TagFilterProxyModel *>(model())->index(tag), Qt::UserRole) == 0)
214  session->removeTag(tag);
215  updateGeometry();
216 }
static QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode=QLineEdit::Normal, const QString &text={}, bool *ok=nullptr, bool excludeExtension=false, Qt::InputMethodHints inputMethodHints=Qt::ImhNone)
bool removeTag(const QString &tag)
Definition: session.cpp:850
static Session * instance()
Definition: session.cpp:997
bool addTag(const QString &tag)
Definition: session.cpp:839
static bool isValidTag(const QString &tag)
Definition: session.cpp:829
static bool isSpecialItem(const QModelIndex &index)
QString tag(const QModelIndex &index) const
TagFilterWidget(QWidget *parent=nullptr)
void actionPauseTorrentsTriggered()
void actionResumeTorrentsTriggered()
void tagChanged(const QString &tag)
void onCurrentRowChanged(const QModelIndex &current, const QModelIndex &previous)
void actionDeleteTorrentsTriggered()
void showMenu(QPoint)
QSize minimumSizeHint() const override
QSize sizeHint() const override
void rowsInserted(const QModelIndex &parent, int start, int end) override
QString currentTag() const
static UIThemeManager * instance()
constexpr std::add_const_t< T > & asConst(T &t) noexcept
Definition: global.h:42
QSize smallIconSize(const QWidget *widget=nullptr)
Definition: utils.cpp:97
QString getTagFilter(const TagFilterProxyModel *const model, const QModelIndex &index)