1 /*
2     SPDX-FileCopyrightText: 2007 Joris Guisson <joris.guisson@gmail.com>
3     SPDX-FileCopyrightText: 2007 Ivan Vasic <ivasic@gmail.com>
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "queuemanagerwidget.h"
8 
9 #include <QAction>
10 #include <QBoxLayout>
11 #include <QHeaderView>
12 #include <QLineEdit>
13 #include <QToolBar>
14 #include <QTreeView>
15 
16 #include <KConfigGroup>
17 #include <KLocalizedString>
18 #include <KStandardGuiItem>
19 
20 #include "queuemanagermodel.h"
21 #include <torrent/queuemanager.h>
22 #include <util/log.h>
23 
24 using namespace bt;
25 
26 namespace kt
27 {
QueueManagerWidget(QueueManager * qman,QWidget * parent)28 QueueManagerWidget::QueueManagerWidget(QueueManager *qman, QWidget *parent)
29     : QWidget(parent)
30     , qman(qman)
31 {
32     QHBoxLayout *layout = new QHBoxLayout(this);
33     layout->setMargin(0);
34     layout->setSpacing(0);
35     QVBoxLayout *vbox = new QVBoxLayout();
36     vbox->setMargin(0);
37     vbox->setSpacing(0);
38     view = new QTreeView(this);
39     view->setUniformRowHeights(true);
40     toolbar = new QToolBar(this);
41     toolbar->setOrientation(Qt::Vertical);
42     toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
43     layout->addWidget(toolbar);
44 
45     search = new QLineEdit(this);
46     search->setPlaceholderText(i18n("Search"));
47     search->setClearButtonEnabled(true);
48     search->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
49     connect(search, &QLineEdit::textChanged, this, &QueueManagerWidget::searchTextChanged);
50     search->hide();
51     vbox->addWidget(search);
52     vbox->addWidget(view);
53     layout->addLayout(vbox);
54 
55     show_search = toolbar->addAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("Show Search"));
56     show_search->setToolTip(i18n("Show or hide the search bar"));
57     show_search->setCheckable(true);
58     connect(show_search, &QAction::toggled, this, &QueueManagerWidget::showSearch);
59 
60     move_top = toolbar->addAction(QIcon::fromTheme(QStringLiteral("go-top")), i18n("Move Top"), this, &QueueManagerWidget::moveTopClicked);
61     move_top->setToolTip(i18n("Move a torrent to the top of the queue"));
62 
63     move_up = toolbar->addAction(QIcon::fromTheme(QStringLiteral("go-up")), i18n("Move Up"), this, &QueueManagerWidget::moveUpClicked);
64     move_up->setToolTip(i18n("Move a torrent up in the queue"));
65 
66     move_down = toolbar->addAction(QIcon::fromTheme(QStringLiteral("go-down")), i18n("Move Down"), this, &QueueManagerWidget::moveDownClicked);
67     move_down->setToolTip(i18n("Move a torrent down in the queue"));
68 
69     move_bottom = toolbar->addAction(QIcon::fromTheme(QStringLiteral("go-bottom")), i18n("Move Bottom"), this, &QueueManagerWidget::moveBottomClicked);
70     move_bottom->setToolTip(i18n("Move a torrent to the bottom of the queue"));
71 
72     show_downloads = toolbar->addAction(QIcon::fromTheme(QStringLiteral("arrow-down")), i18n("Show Downloads"));
73     show_downloads->setToolTip(i18n("Show all downloads"));
74     show_downloads->setCheckable(true);
75     connect(show_downloads, &QAction::toggled, this, &QueueManagerWidget::showDownloads);
76 
77     show_uploads = toolbar->addAction(QIcon::fromTheme(QStringLiteral("arrow-up")), i18n("Show Uploads"));
78     show_uploads->setToolTip(i18n("Show all uploads"));
79     show_uploads->setCheckable(true);
80     connect(show_uploads, &QAction::toggled, this, &QueueManagerWidget::showUploads);
81 
82     show_not_queued = toolbar->addAction(QIcon::fromTheme(QStringLiteral("kt-queue-manager")), i18n("Show Not Queued"));
83     show_not_queued->setToolTip(i18n("Show all not queued torrents"));
84     show_not_queued->setCheckable(true);
85     connect(show_not_queued, &QAction::toggled, this, &QueueManagerWidget::showNotQueued);
86 
87     model = new QueueManagerModel(qman, this);
88     view->setModel(model);
89     view->setRootIsDecorated(false);
90     view->setAlternatingRowColors(true);
91     view->setSelectionBehavior(QAbstractItemView::SelectRows);
92     view->setSortingEnabled(false);
93     view->setDragDropMode(QAbstractItemView::InternalMove);
94     view->setDragEnabled(true);
95     view->setAcceptDrops(true);
96     view->setDropIndicatorShown(true);
97     view->setAutoScroll(true);
98     view->setSelectionMode(QAbstractItemView::ContiguousSelection);
99 
100     connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QueueManagerWidget::selectionChanged);
101 
102     updateButtons();
103 }
104 
~QueueManagerWidget()105 QueueManagerWidget::~QueueManagerWidget()
106 {
107 }
108 
onTorrentAdded(bt::TorrentInterface * tc)109 void QueueManagerWidget::onTorrentAdded(bt::TorrentInterface *tc)
110 {
111     model->onTorrentAdded(tc);
112 }
113 
onTorrentRemoved(bt::TorrentInterface * tc)114 void QueueManagerWidget::onTorrentRemoved(bt::TorrentInterface *tc)
115 {
116     model->onTorrentRemoved(tc);
117 }
118 
moveUpClicked()119 void QueueManagerWidget::moveUpClicked()
120 {
121     const QModelIndexList sel = view->selectionModel()->selectedRows();
122     QList<int> rows;
123     for (const QModelIndex &idx : sel)
124         rows.append(idx.row());
125 
126     if (rows.isEmpty() || rows.front() == 0)
127         return;
128 
129     model->moveUp(rows.front(), rows.count());
130 
131     QItemSelection nsel;
132     int cols = model->columnCount(QModelIndex());
133     QModelIndex top_left = model->index(rows.front() - 1, 0);
134     QModelIndex bottom_right = model->index(rows.back() - 1, cols - 1);
135     nsel.select(top_left, bottom_right);
136     view->selectionModel()->select(nsel, QItemSelectionModel::Select);
137     if (!indexVisible(top_left))
138         view->scrollTo(top_left, QAbstractItemView::PositionAtCenter);
139 
140     updateButtons();
141 }
142 
moveDownClicked()143 void QueueManagerWidget::moveDownClicked()
144 {
145     const QModelIndexList sel = view->selectionModel()->selectedRows();
146     QList<int> rows;
147     for (const QModelIndex &idx : sel)
148         rows.append(idx.row());
149 
150     int rowcount = model->rowCount(QModelIndex());
151     if (rows.isEmpty() || rows.back() == rowcount - 1)
152         return;
153 
154     model->moveDown(rows.front(), rows.count());
155 
156     QItemSelection nsel;
157     int cols = model->columnCount(QModelIndex());
158     QModelIndex top_left = model->index(rows.front() + 1, 0);
159     QModelIndex bottom_right = model->index(rows.back() + 1, cols - 1);
160     nsel.select(top_left, bottom_right);
161     view->selectionModel()->select(nsel, QItemSelectionModel::Select);
162     if (!indexVisible(top_left))
163         view->scrollTo(top_left, QAbstractItemView::PositionAtCenter);
164 
165     updateButtons();
166 }
167 
moveTopClicked()168 void QueueManagerWidget::moveTopClicked()
169 {
170     const QModelIndexList sel = view->selectionModel()->selectedRows();
171     QList<int> rows;
172     for (const QModelIndex &idx : sel)
173         rows.append(idx.row());
174 
175     if (rows.isEmpty() || rows.front() == 0)
176         return;
177 
178     model->moveTop(rows.front(), rows.count());
179 
180     QItemSelection nsel;
181     int cols = model->columnCount(QModelIndex());
182     nsel.select(model->index(0, 0), model->index(rows.count() - 1, cols - 1));
183     view->selectionModel()->select(nsel, QItemSelectionModel::Select);
184     view->scrollToTop();
185 
186     updateButtons();
187 }
188 
moveBottomClicked()189 void QueueManagerWidget::moveBottomClicked()
190 {
191     const QModelIndexList sel = view->selectionModel()->selectedRows();
192     QList<int> rows;
193     for (const QModelIndex &idx : sel)
194         rows.append(idx.row());
195 
196     int rowcount = model->rowCount(QModelIndex());
197     if (rows.isEmpty() || rows.back() == rowcount - 1)
198         return;
199 
200     model->moveBottom(rows.front(), rows.count());
201 
202     QItemSelection nsel;
203     int cols = model->columnCount(QModelIndex());
204     nsel.select(model->index(rowcount - rows.count(), 0), model->index(rowcount - 1, cols - 1));
205     view->selectionModel()->select(nsel, QItemSelectionModel::Select);
206     view->scrollToBottom();
207 
208     updateButtons();
209 }
210 
saveState(KSharedConfigPtr cfg)211 void QueueManagerWidget::saveState(KSharedConfigPtr cfg)
212 {
213     KConfigGroup g = cfg->group("QueueManagerWidget");
214     QByteArray s = view->header()->saveState();
215     g.writeEntry("view_state", s.toBase64());
216     g.writeEntry("search_text", search->text());
217     g.writeEntry("search_bar_visible", show_search->isChecked());
218     g.writeEntry("show_uploads", show_uploads->isChecked());
219     g.writeEntry("show_downloads", show_downloads->isChecked());
220     g.writeEntry("show_not_queued", show_not_queued->isChecked());
221 }
222 
loadState(KSharedConfigPtr cfg)223 void QueueManagerWidget::loadState(KSharedConfigPtr cfg)
224 {
225     KConfigGroup g = cfg->group("QueueManagerWidget");
226     QByteArray s = QByteArray::fromBase64(g.readEntry("view_state", QByteArray()));
227     if (!s.isEmpty())
228         view->header()->restoreState(s);
229 
230     QString st = g.readEntry("search_text", QString());
231     if (!st.isEmpty())
232         search->setText(st);
233 
234     show_search->setChecked(g.readEntry("search_bar_visible", false));
235     show_downloads->setChecked(g.readEntry("show_downloads", true));
236     show_uploads->setChecked(g.readEntry("show_uploads", true));
237     show_not_queued->setChecked(g.readEntry("show_not_queued", true));
238 }
239 
update()240 void QueueManagerWidget::update()
241 {
242     model->update();
243 }
244 
searchTextChanged(const QString & t)245 void QueueManagerWidget::searchTextChanged(const QString &t)
246 {
247     QModelIndex idx = model->find(t);
248     if (idx.isValid()) {
249         view->scrollTo(idx, QAbstractItemView::PositionAtCenter);
250     }
251 }
252 
showSearch(bool on)253 void QueueManagerWidget::showSearch(bool on)
254 {
255     search->setVisible(on);
256 }
257 
showDownloads(bool on)258 void QueueManagerWidget::showDownloads(bool on)
259 {
260     model->setShowDownloads(on);
261 }
262 
showUploads(bool on)263 void QueueManagerWidget::showUploads(bool on)
264 {
265     model->setShowUploads(on);
266 }
267 
showNotQueued(bool on)268 void QueueManagerWidget::showNotQueued(bool on)
269 {
270     model->setShowNotQueued(on);
271 }
272 
indexVisible(const QModelIndex & idx)273 bool QueueManagerWidget::indexVisible(const QModelIndex &idx)
274 {
275     QRect r = view->visualRect(idx);
276     return view->viewport()->rect().contains(r);
277 }
278 
selectionChanged(const QItemSelection & selected,const QItemSelection & deselected)279 void QueueManagerWidget::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
280 {
281     Q_UNUSED(selected);
282     Q_UNUSED(deselected);
283     updateButtons();
284 }
285 
updateButtons()286 void QueueManagerWidget::updateButtons()
287 {
288     QModelIndexList idx = view->selectionModel()->selectedRows();
289     if (idx.count() == 0) {
290         move_top->setEnabled(false);
291         move_up->setEnabled(false);
292         move_down->setEnabled(false);
293         move_bottom->setEnabled(false);
294     } else {
295         move_top->setEnabled(idx.front().row() != 0);
296         move_up->setEnabled(idx.front().row() != 0);
297 
298         int rows = model->rowCount(QModelIndex());
299         move_down->setEnabled(idx.back().row() != rows - 1);
300         move_bottom->setEnabled(idx.back().row() != rows - 1);
301     }
302 }
303 
304 }
305