Transmission: update to 2.82
[tomato.git] / release / src / router / transmission / qt / filterbar.cc
blobdac8302d319f5250bf697ac189011dd31fd632e4
1 /*
2 * This file Copyright (C) Mnemosyne LLC
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2
6 * as published by the Free Software Foundation.
8 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
10 * $Id: filterbar.cc 14150 2013-07-27 21:58:14Z jordan $
13 #include <QAbstractItemView>
14 #include <QPushButton>
15 #include <QLabel>
16 #include <QHBoxLayout>
17 #include <QLineEdit>
18 #include <QStylePainter>
19 #include <QString>
20 #include <QtGui>
22 #include "app.h"
23 #include "favicon.h"
24 #include "filters.h"
25 #include "filterbar.h"
26 #include "hig.h"
27 #include "prefs.h"
28 #include "torrent-filter.h"
29 #include "torrent-model.h"
30 #include "utils.h"
32 /****
33 *****
34 ***** DELEGATE
35 *****
36 ****/
38 enum
40 TorrentCountRole = Qt::UserRole + 1,
41 TorrentCountStringRole,
42 ActivityRole,
43 TrackerRole
46 namespace
48 int getHSpacing (QWidget * w)
50 return qMax (int (HIG::PAD_SMALL), w->style ()->pixelMetric (QStyle::PM_LayoutHorizontalSpacing, 0, w));
54 FilterBarComboBoxDelegate :: FilterBarComboBoxDelegate (QObject * parent, QComboBox * combo):
55 QItemDelegate (parent),
56 myCombo (combo)
60 bool
61 FilterBarComboBoxDelegate :: isSeparator (const QModelIndex &index)
63 return index.data (Qt::AccessibleDescriptionRole).toString () == QLatin1String ("separator");
65 void
66 FilterBarComboBoxDelegate :: setSeparator (QAbstractItemModel * model, const QModelIndex& index)
68 model->setData (index, QString::fromLatin1 ("separator"), Qt::AccessibleDescriptionRole);
70 if (QStandardItemModel *m = qobject_cast<QStandardItemModel*> (model))
71 if (QStandardItem *item = m->itemFromIndex (index))
72 item->setFlags (item->flags () & ~ (Qt::ItemIsSelectable|Qt::ItemIsEnabled));
75 void
76 FilterBarComboBoxDelegate :: paint (QPainter * painter,
77 const QStyleOptionViewItem & option,
78 const QModelIndex & index) const
80 if (isSeparator (index))
82 QRect rect = option.rect;
83 if (const QStyleOptionViewItemV3 *v3 = qstyleoption_cast<const QStyleOptionViewItemV3*> (&option))
84 if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView*> (v3->widget))
85 rect.setWidth (view->viewport ()->width ());
86 QStyleOption opt;
87 opt.rect = rect;
88 myCombo->style ()->drawPrimitive (QStyle::PE_IndicatorToolBarSeparator, &opt, painter, myCombo);
90 else
92 QStyleOptionViewItem disabledOption = option;
93 disabledOption.state &= ~ (QStyle::State_Enabled | QStyle::State_Selected);
94 QRect boundingBox = option.rect;
96 const int hmargin = getHSpacing (myCombo);
97 boundingBox.setLeft (boundingBox.left () + hmargin);
98 boundingBox.setRight (boundingBox.right () - hmargin);
100 QRect decorationRect = rect (option, index, Qt::DecorationRole);
101 decorationRect.moveLeft (decorationRect.left ());
102 decorationRect.setSize (myCombo->iconSize ());
103 decorationRect = QStyle::alignedRect (Qt::LeftToRight,
104 Qt::AlignLeft|Qt::AlignVCenter,
105 decorationRect.size (), boundingBox);
106 boundingBox.setLeft (decorationRect.right () + hmargin);
108 QRect countRect = rect (option, index, TorrentCountStringRole);
109 countRect = QStyle::alignedRect (Qt::LeftToRight,
110 Qt::AlignRight|Qt::AlignVCenter,
111 countRect.size (), boundingBox);
112 boundingBox.setRight (countRect.left () - hmargin);
113 const QRect displayRect = boundingBox;
115 drawBackground (painter, option, index);
116 QStyleOptionViewItem option2 = option;
117 option2.decorationSize = myCombo->iconSize ();
118 drawDecoration (painter, option, decorationRect, decoration (option2,index.data (Qt::DecorationRole)));
119 drawDisplay (painter, option, displayRect, index.data (Qt::DisplayRole).toString ());
120 drawDisplay (painter, disabledOption, countRect, index.data (TorrentCountStringRole).toString ());
121 drawFocus (painter, option, displayRect|countRect);
125 QSize
126 FilterBarComboBoxDelegate :: sizeHint (const QStyleOptionViewItem & option,
127 const QModelIndex & index) const
129 if (isSeparator (index))
131 const int pm = myCombo->style ()->pixelMetric (QStyle::PM_DefaultFrameWidth, 0, myCombo);
132 return QSize (pm, pm + 10);
134 else
136 QStyle * s = myCombo->style ();
137 const int hmargin = getHSpacing (myCombo);
139 QSize size = QItemDelegate::sizeHint (option, index);
140 size.setHeight (qMax (size.height (), myCombo->iconSize ().height () + 6));
141 size.rwidth () += s->pixelMetric (QStyle::PM_FocusFrameHMargin, 0, myCombo);
142 size.rwidth () += rect (option,index,TorrentCountStringRole).width ();
143 size.rwidth () += hmargin * 4;
144 return size;
152 FilterBarComboBox :: FilterBarComboBox (QWidget * parent):
153 QComboBox (parent)
158 FilterBarComboBox :: currentCount () const
160 int count = 0;
162 const QModelIndex modelIndex = model ()->index (currentIndex (), 0, rootModelIndex ());
163 if (modelIndex.isValid ())
164 count = modelIndex.data (TorrentCountRole).toInt ();
166 return count;
169 void
170 FilterBarComboBox :: paintEvent (QPaintEvent * e)
172 Q_UNUSED (e);
174 QStylePainter painter (this);
175 painter.setPen (palette ().color (QPalette::Text));
177 // draw the combobox frame, focusrect and selected etc.
178 QStyleOptionComboBox opt;
179 initStyleOption (&opt);
180 painter.drawComplexControl (QStyle::CC_ComboBox, opt);
182 // draw the icon and text
183 const QModelIndex modelIndex = model ()->index (currentIndex (), 0, rootModelIndex ());
184 if (modelIndex.isValid ())
186 QStyle * s = style ();
187 QRect rect = s->subControlRect (QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this);
188 const int hmargin = getHSpacing (this);
189 rect.setRight (rect.right () - hmargin);
191 // draw the icon
192 QPixmap pixmap;
193 QVariant variant = modelIndex.data (Qt::DecorationRole);
194 switch (variant.type ())
196 case QVariant::Pixmap: pixmap = qvariant_cast<QPixmap> (variant); break;
197 case QVariant::Icon: pixmap = qvariant_cast<QIcon> (variant).pixmap (iconSize ()); break;
198 default: break;
200 if (!pixmap.isNull ())
202 s->drawItemPixmap (&painter, rect, Qt::AlignLeft|Qt::AlignVCenter, pixmap);
203 rect.setLeft (rect.left () + pixmap.width () + hmargin);
206 // draw the count
207 QString text = modelIndex.data (TorrentCountStringRole).toString ();
208 if (!text.isEmpty ())
210 const QPen pen = painter.pen ();
211 painter.setPen (opt.palette.color (QPalette::Disabled, QPalette::Text));
212 QRect r = s->itemTextRect (painter.fontMetrics (), rect, Qt::AlignRight|Qt::AlignVCenter, false, text);
213 painter.drawText (r, 0, text);
214 rect.setRight (r.left () - hmargin);
215 painter.setPen (pen);
218 // draw the text
219 text = modelIndex.data (Qt::DisplayRole).toString ();
220 text = painter.fontMetrics ().elidedText (text, Qt::ElideRight, rect.width ());
221 s->drawItemText (&painter, rect, Qt::AlignLeft|Qt::AlignVCenter, opt.palette, true, text);
225 /****
226 *****
227 ***** ACTIVITY
228 *****
229 ****/
231 FilterBarComboBox *
232 FilterBar :: createActivityCombo ()
234 FilterBarComboBox * c = new FilterBarComboBox (this);
235 FilterBarComboBoxDelegate * delegate = new FilterBarComboBoxDelegate (this, c);
236 c->setItemDelegate (delegate);
238 QPixmap blankPixmap (c->iconSize ());
239 blankPixmap.fill (Qt::transparent);
240 QIcon blankIcon (blankPixmap);
242 QStandardItemModel * model = new QStandardItemModel (this);
244 QStandardItem * row = new QStandardItem (tr ("All"));
245 row->setData (FilterMode::SHOW_ALL, ActivityRole);
246 model->appendRow (row);
248 model->appendRow (new QStandardItem); // separator
249 delegate->setSeparator (model, model->index (1, 0));
251 row = new QStandardItem (QIcon::fromTheme ("system-run", blankIcon), tr ("Active"));
252 row->setData (FilterMode::SHOW_ACTIVE, ActivityRole);
253 model->appendRow (row);
255 row = new QStandardItem (QIcon::fromTheme ("go-down", blankIcon), tr ("Downloading"));
256 row->setData (FilterMode::SHOW_DOWNLOADING, ActivityRole);
257 model->appendRow (row);
259 row = new QStandardItem (QIcon::fromTheme ("go-up", blankIcon), tr ("Seeding"));
260 row->setData (FilterMode::SHOW_SEEDING, ActivityRole);
261 model->appendRow (row);
263 row = new QStandardItem (QIcon::fromTheme ("media-playback-pause", blankIcon), tr ("Paused"));
264 row->setData (FilterMode::SHOW_PAUSED, ActivityRole);
265 model->appendRow (row);
267 row = new QStandardItem (blankIcon, tr ("Finished"));
268 row->setData (FilterMode::SHOW_FINISHED, ActivityRole);
269 model->appendRow (row);
271 row = new QStandardItem (QIcon::fromTheme ("view-refresh", blankIcon), tr ("Verifying"));
272 row->setData (FilterMode::SHOW_VERIFYING, ActivityRole);
273 model->appendRow (row);
275 row = new QStandardItem (QIcon::fromTheme ("dialog-error", blankIcon), tr ("Error"));
276 row->setData (FilterMode::SHOW_ERROR, ActivityRole);
277 model->appendRow (row);
279 c->setModel (model);
280 return c;
283 /****
284 *****
285 *****
286 *****
287 ****/
289 namespace
291 QString readableHostName (const QString host)
293 // get the readable name...
294 QString name = host;
295 const int pos = name.lastIndexOf ('.');
296 if (pos >= 0)
297 name.truncate (pos);
298 if (!name.isEmpty ())
299 name[0] = name[0].toUpper ();
300 return name;
304 void
305 FilterBar :: refreshTrackers ()
307 Favicons& favicons = dynamic_cast<MyApp*> (QApplication::instance ())->favicons;
308 const int firstTrackerRow = 2; // skip over the "All" and separator...
310 // pull info from the tracker model...
311 QSet<QString> oldHosts;
312 for (int row=firstTrackerRow; ; ++row)
314 QModelIndex index = myTrackerModel->index (row, 0);
315 if (!index.isValid ())
316 break;
317 oldHosts << index.data (TrackerRole).toString ();
320 // pull the new stats from the torrent model...
321 QSet<QString> newHosts;
322 QMap<QString,int> torrentsPerHost;
323 for (int row=0; ; ++row)
325 QModelIndex index = myTorrents.index (row, 0);
326 if (!index.isValid ())
327 break;
328 const Torrent * tor = index.data (TorrentModel::TorrentRole).value<const Torrent*> ();
329 QSet<QString> torrentNames;
330 foreach (QString host, tor->hosts ())
332 newHosts.insert (host);
333 torrentNames.insert (readableHostName (host));
335 foreach (QString name, torrentNames)
336 ++torrentsPerHost[name];
339 // update the "All" row
340 myTrackerModel->setData (myTrackerModel->index (0,0), myTorrents.rowCount (), TorrentCountRole);
341 myTrackerModel->setData (myTrackerModel->index (0,0), getCountString (myTorrents.rowCount ()), TorrentCountStringRole);
343 // rows to update
344 foreach (QString host, oldHosts & newHosts)
346 const QString name = readableHostName (host);
347 QStandardItem * row = myTrackerModel->findItems (name).front ();
348 const int count = torrentsPerHost[name];
349 row->setData (count, TorrentCountRole);
350 row->setData (getCountString (count), TorrentCountStringRole);
351 row->setData (favicons.findFromHost (host), Qt::DecorationRole);
354 // rows to remove
355 foreach (QString host, oldHosts - newHosts)
357 const QString name = readableHostName (host);
358 QStandardItem * item = myTrackerModel->findItems (name).front ();
359 if (!item->data (TrackerRole).toString ().isEmpty ()) // don't remove "All"
360 myTrackerModel->removeRows (item->row (), 1);
363 // rows to add
364 bool anyAdded = false;
365 foreach (QString host, newHosts - oldHosts)
367 const QString name = readableHostName (host);
369 if (!myTrackerModel->findItems (name).isEmpty ())
370 continue;
372 // find the sorted position to add this row
373 int i = firstTrackerRow;
374 for (int n=myTrackerModel->rowCount (); i<n; ++i)
376 const QString rowName = myTrackerModel->index (i,0).data (Qt::DisplayRole).toString ();
377 if (rowName >= name)
378 break;
381 // add the row
382 QStandardItem * row = new QStandardItem (favicons.findFromHost (host), name);
383 const int count = torrentsPerHost[host];
384 row->setData (count, TorrentCountRole);
385 row->setData (getCountString (count), TorrentCountStringRole);
386 row->setData (favicons.findFromHost (host), Qt::DecorationRole);
387 row->setData (host, TrackerRole);
388 myTrackerModel->insertRow (i, row);
389 anyAdded = true;
392 if (anyAdded) // the one added might match our filter...
393 refreshPref (Prefs::FILTER_TRACKERS);
397 FilterBarComboBox *
398 FilterBar :: createTrackerCombo (QStandardItemModel * model)
400 FilterBarComboBox * c = new FilterBarComboBox (this);
401 FilterBarComboBoxDelegate * delegate = new FilterBarComboBoxDelegate (this, c);
402 c->setItemDelegate (delegate);
404 QStandardItem * row = new QStandardItem (tr ("All"));
405 row->setData ("", TrackerRole);
406 const int count = myTorrents.rowCount ();
407 row->setData (count, TorrentCountRole);
408 row->setData (getCountString (count), TorrentCountStringRole);
409 model->appendRow (row);
411 model->appendRow (new QStandardItem); // separator
412 delegate->setSeparator (model, model->index (1, 0));
414 c->setModel (model);
415 return c;
418 /****
419 *****
420 *****
421 *****
422 ****/
424 FilterBar :: FilterBar (Prefs& prefs, TorrentModel& torrents, TorrentFilter& filter, QWidget * parent):
425 QWidget (parent),
426 myPrefs (prefs),
427 myTorrents (torrents),
428 myFilter (filter),
429 myRecountTimer (new QTimer (this)),
430 myIsBootstrapping (true)
432 QHBoxLayout * h = new QHBoxLayout (this);
433 const int hmargin = qMax (int (HIG::PAD), style ()->pixelMetric (QStyle::PM_LayoutHorizontalSpacing));
435 myCountLabel = new QLabel (this);
436 h->setSpacing (0);
437 h->setContentsMargins (2, 2, 2, 2);
438 h->addWidget (myCountLabel);
439 h->addSpacing (hmargin);
441 myActivityCombo = createActivityCombo ();
442 h->addWidget (myActivityCombo, 1);
443 h->addSpacing (hmargin);
445 myTrackerModel = new QStandardItemModel (this);
446 myTrackerCombo = createTrackerCombo (myTrackerModel);
447 h->addWidget (myTrackerCombo, 1);
448 h->addSpacing (hmargin*2);
450 myLineEdit = new QLineEdit (this);
451 h->addWidget (myLineEdit);
452 connect (myLineEdit, SIGNAL (textChanged (QString)), this, SLOT (onTextChanged (QString)));
454 QPushButton * p = new QPushButton (this);
455 QIcon icon = QIcon::fromTheme ("edit-clear", style ()->standardIcon (QStyle::SP_DialogCloseButton));
456 int iconSize = style ()->pixelMetric (QStyle::PM_SmallIconSize);
457 p->setIconSize (QSize (iconSize, iconSize));
458 p->setIcon (icon);
459 p->setFlat (true);
460 h->addWidget (p);
461 connect (p, SIGNAL (clicked (bool)), myLineEdit, SLOT (clear ()));
463 // listen for changes from the other players
464 connect (&myPrefs, SIGNAL (changed (int)), this, SLOT (refreshPref (int)));
465 connect (myActivityCombo, SIGNAL (currentIndexChanged (int)), this, SLOT (onActivityIndexChanged (int)));
466 connect (myTrackerCombo, SIGNAL (currentIndexChanged (int)), this, SLOT (onTrackerIndexChanged (int)));
467 connect (&myFilter, SIGNAL (rowsInserted (const QModelIndex&,int,int)), this, SLOT (refreshCountLabel ()));
468 connect (&myFilter, SIGNAL (rowsRemoved (const QModelIndex&,int,int)), this, SLOT (refreshCountLabel ()));
469 connect (&myTorrents, SIGNAL (modelReset ()), this, SLOT (onTorrentModelReset ()));
470 connect (&myTorrents, SIGNAL (rowsInserted (const QModelIndex&,int,int)), this, SLOT (onTorrentModelRowsInserted (const QModelIndex&,int,int)));
471 connect (&myTorrents, SIGNAL (rowsRemoved (const QModelIndex&,int,int)), this, SLOT (onTorrentModelRowsRemoved (const QModelIndex&,int,int)));
472 connect (&myTorrents, SIGNAL (dataChanged (const QModelIndex&,const QModelIndex&)), this, SLOT (onTorrentModelDataChanged (const QModelIndex&,const QModelIndex&)));
473 connect (myRecountTimer, SIGNAL (timeout ()), this, SLOT (recount ()));
475 recountSoon ();
476 refreshTrackers ();
477 refreshCountLabel ();
478 myIsBootstrapping = false;
480 // initialize our state
481 QList<int> initKeys;
482 initKeys << Prefs :: FILTER_MODE
483 << Prefs :: FILTER_TRACKERS;
484 foreach (int key, initKeys)
485 refreshPref (key);
488 FilterBar :: ~FilterBar ()
490 delete myRecountTimer;
493 /***
494 ****
495 ***/
497 void
498 FilterBar :: refreshPref (int key)
500 switch (key)
502 case Prefs :: FILTER_MODE:
504 const FilterMode m = myPrefs.get<FilterMode> (key);
505 QAbstractItemModel * model = myActivityCombo->model ();
506 QModelIndexList indices = model->match (model->index (0,0), ActivityRole, m.mode ());
507 myActivityCombo->setCurrentIndex (indices.isEmpty () ? 0 : indices.first ().row ());
508 break;
511 case Prefs :: FILTER_TRACKERS:
513 const QString tracker = myPrefs.getString (key);
514 const QString name = readableHostName (tracker);
515 QList<QStandardItem*> rows = myTrackerModel->findItems (name);
516 if (!rows.isEmpty ())
518 myTrackerCombo->setCurrentIndex (rows.front ()->row ());
520 else // hm, we don't seem to have this tracker anymore...
522 const bool isBootstrapping = myTrackerModel->rowCount () <= 2;
523 if (!isBootstrapping)
524 myPrefs.set (key, "");
526 break;
531 void
532 FilterBar :: onTextChanged (const QString& str)
534 if (!myIsBootstrapping)
535 myPrefs.set (Prefs::FILTER_TEXT, str.trimmed ());
538 void
539 FilterBar :: onTrackerIndexChanged (int i)
541 if (!myIsBootstrapping)
543 QString str;
544 const bool isTracker = !myTrackerCombo->itemData (i,TrackerRole).toString ().isEmpty ();
545 if (!isTracker) // show all
547 str = "";
549 else
551 str = myTrackerCombo->itemData (i,TrackerRole).toString ();
552 const int pos = str.lastIndexOf ('.');
553 if (pos >= 0)
554 str.truncate (pos+1);
556 myPrefs.set (Prefs::FILTER_TRACKERS, str);
560 void
561 FilterBar :: onActivityIndexChanged (int i)
563 if (!myIsBootstrapping)
565 const FilterMode mode = myActivityCombo->itemData (i, ActivityRole).toInt ();
566 myPrefs.set (Prefs::FILTER_MODE, mode);
570 /***
571 ****
572 ***/
574 void FilterBar :: onTorrentModelReset () { recountSoon (); }
575 void FilterBar :: onTorrentModelRowsInserted (const QModelIndex&, int, int) { recountSoon (); }
576 void FilterBar :: onTorrentModelRowsRemoved (const QModelIndex&, int, int) { recountSoon (); }
577 void FilterBar :: onTorrentModelDataChanged (const QModelIndex&, const QModelIndex&) { recountSoon (); }
579 void
580 FilterBar :: recountSoon ()
582 if (!myRecountTimer->isActive ())
584 myRecountTimer->setSingleShot (true);
585 myRecountTimer->start (800);
588 void
589 FilterBar :: recount ()
591 QAbstractItemModel * model = myActivityCombo->model ();
593 int torrentsPerMode[FilterMode::NUM_MODES] = { };
594 myFilter.countTorrentsPerMode (torrentsPerMode);
596 for (int row=0, n=model->rowCount (); row<n; ++row)
598 QModelIndex index = model->index (row, 0);
599 const int mode = index.data (ActivityRole).toInt ();
600 const int count = torrentsPerMode [mode];
601 model->setData (index, count, TorrentCountRole);
602 model->setData (index, getCountString (count), TorrentCountStringRole);
605 refreshTrackers ();
606 refreshCountLabel ();
609 QString
610 FilterBar :: getCountString (int n) const
612 return QString ("%L1").arg (n);
615 void
616 FilterBar :: refreshCountLabel ()
618 const int visibleCount = myFilter.rowCount ();
619 const int trackerCount = myTrackerCombo->currentCount ();
620 const int activityCount = myActivityCombo->currentCount ();
622 if ((visibleCount == activityCount) || (visibleCount == trackerCount))
623 myCountLabel->setText (tr("Show:"));
624 else
625 myCountLabel->setText (tr("Show %Ln of:", 0, visibleCount));