source: trunk/qt/filterbar.cc @ 14380

Last change on this file since 14380 was 14380, checked in by mikedld, 8 years ago

Strip needless const& specifiers on SIGNAL and SLOT arguments (Qt client)

  • Property svn:keywords set to Date Rev Author Id
File size: 19.3 KB
Line 
1/*
2 * This file Copyright (C) 2012-2014 Mnemosyne LLC
3 *
4 * It may be used under the GNU Public License v2 or v3 licenses,
5 * or any future license endorsed by Mnemosyne LLC.
6 *
7 * $Id: filterbar.cc 14380 2014-12-13 09:04:10Z mikedld $
8 */
9
10#include <QAbstractItemView>
11#include <QPushButton>
12#include <QLabel>
13#include <QHBoxLayout>
14#include <QLineEdit>
15#include <QStylePainter>
16#include <QString>
17#include <QtGui>
18
19#include "app.h"
20#include "favicon.h"
21#include "filters.h"
22#include "filterbar.h"
23#include "hig.h"
24#include "prefs.h"
25#include "torrent-filter.h"
26#include "torrent-model.h"
27#include "utils.h"
28
29/****
30*****
31*****  DELEGATE
32*****
33****/
34
35enum
36{
37  TorrentCountRole = Qt::UserRole + 1,
38  TorrentCountStringRole,
39  ActivityRole,
40  TrackerRole
41};
42
43namespace
44{
45  int getHSpacing (QWidget * w)
46  {
47    return qMax (int (HIG::PAD_SMALL), w->style ()->pixelMetric (QStyle::PM_LayoutHorizontalSpacing, 0, w));
48  }
49}
50
51FilterBarComboBoxDelegate::FilterBarComboBoxDelegate (QObject * parent, QComboBox * combo):
52  QItemDelegate (parent),
53  myCombo (combo)
54{
55}
56
57bool
58FilterBarComboBoxDelegate::isSeparator (const QModelIndex& index)
59{
60  return index.data (Qt::AccessibleDescriptionRole).toString () == QLatin1String ("separator");
61}
62void
63FilterBarComboBoxDelegate::setSeparator (QAbstractItemModel * model, const QModelIndex& index)
64{
65  model->setData (index, QString::fromLatin1 ("separator"), Qt::AccessibleDescriptionRole);
66
67  if (QStandardItemModel *m = qobject_cast<QStandardItemModel*> (model))
68    if (QStandardItem *item = m->itemFromIndex (index))
69      item->setFlags (item->flags () & ~ (Qt::ItemIsSelectable|Qt::ItemIsEnabled));
70}
71
72void
73FilterBarComboBoxDelegate::paint (QPainter                    * painter,
74                                  const QStyleOptionViewItem  & option,
75                                  const QModelIndex           & index) const
76{
77  if (isSeparator (index))
78    {
79      QRect rect = option.rect;
80      if (const QStyleOptionViewItemV3 *v3 = qstyleoption_cast<const QStyleOptionViewItemV3*> (&option))
81        if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView*> (v3->widget))
82          rect.setWidth (view->viewport ()->width ());
83      QStyleOption opt;
84      opt.rect = rect;
85      myCombo->style ()->drawPrimitive (QStyle::PE_IndicatorToolBarSeparator, &opt, painter, myCombo);
86    }
87  else
88    {
89      QStyleOptionViewItem disabledOption = option;
90      disabledOption.state &= ~ (QStyle::State_Enabled | QStyle::State_Selected);
91      QRect boundingBox = option.rect;
92
93      const int hmargin = getHSpacing (myCombo);
94      boundingBox.setLeft (boundingBox.left () + hmargin);
95      boundingBox.setRight (boundingBox.right () - hmargin);
96
97      QRect decorationRect = rect (option, index, Qt::DecorationRole);
98      decorationRect.moveLeft (decorationRect.left ());
99      decorationRect.setSize (myCombo->iconSize ());
100      decorationRect = QStyle::alignedRect (Qt::LeftToRight,
101                                            Qt::AlignLeft|Qt::AlignVCenter,
102                                            decorationRect.size (), boundingBox);
103      boundingBox.setLeft (decorationRect.right () + hmargin);
104
105      QRect countRect  = rect (option, index, TorrentCountStringRole);
106      countRect = QStyle::alignedRect (Qt::LeftToRight,
107                                       Qt::AlignRight|Qt::AlignVCenter,
108                                       countRect.size (), boundingBox);
109      boundingBox.setRight (countRect.left () - hmargin);
110      const QRect displayRect = boundingBox;
111
112      drawBackground (painter, option, index);
113      QStyleOptionViewItem option2 = option;
114      option2.decorationSize = myCombo->iconSize ();
115      drawDecoration (painter, option, decorationRect, decoration (option2,index.data (Qt::DecorationRole)));
116      drawDisplay (painter, option, displayRect, index.data (Qt::DisplayRole).toString ());
117      drawDisplay (painter, disabledOption, countRect, index.data (TorrentCountStringRole).toString ());
118      drawFocus (painter, option, displayRect|countRect);
119    }
120}
121
122QSize
123FilterBarComboBoxDelegate::sizeHint (const QStyleOptionViewItem & option,
124                                     const QModelIndex          & index) const
125{
126  if (isSeparator (index))
127    {
128      const int pm = myCombo->style ()->pixelMetric (QStyle::PM_DefaultFrameWidth, 0, myCombo);
129      return QSize (pm, pm + 10);
130    }
131  else
132    {
133      QStyle * s = myCombo->style ();
134      const int hmargin = getHSpacing (myCombo);
135
136      QSize size = QItemDelegate::sizeHint (option, index);
137      size.setHeight (qMax (size.height (), myCombo->iconSize ().height () + 6));
138      size.rwidth () += s->pixelMetric (QStyle::PM_FocusFrameHMargin, 0, myCombo);
139      size.rwidth () += rect (option,index,TorrentCountStringRole).width ();
140      size.rwidth () += hmargin * 4;
141      return size;
142    }
143}
144
145/**
146***
147**/
148
149FilterBarComboBox::FilterBarComboBox (QWidget * parent):
150  QComboBox (parent)
151{
152}
153
154int
155FilterBarComboBox::currentCount () const
156{
157  int count = 0;
158
159  const QModelIndex modelIndex = model ()->index (currentIndex (), 0, rootModelIndex ());
160  if (modelIndex.isValid ())
161    count = modelIndex.data (TorrentCountRole).toInt ();
162
163  return count;
164}
165
166void
167FilterBarComboBox::paintEvent (QPaintEvent * e)
168{
169  Q_UNUSED (e);
170
171  QStylePainter painter (this);
172  painter.setPen (palette ().color (QPalette::Text));
173
174  // draw the combobox frame, focusrect and selected etc.
175  QStyleOptionComboBox opt;
176  initStyleOption (&opt);
177  painter.drawComplexControl (QStyle::CC_ComboBox, opt);
178
179  // draw the icon and text
180  const QModelIndex modelIndex = model ()->index (currentIndex (), 0, rootModelIndex ());
181  if (modelIndex.isValid ())
182    {
183      QStyle * s = style ();
184      QRect rect = s->subControlRect (QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this);
185      const int hmargin = getHSpacing (this);
186      rect.setRight (rect.right () - hmargin);
187
188      // draw the icon
189      QPixmap pixmap;
190      QVariant variant = modelIndex.data (Qt::DecorationRole);
191      switch (variant.type ())
192        {
193          case QVariant::Pixmap: pixmap = qvariant_cast<QPixmap> (variant); break;
194          case QVariant::Icon:   pixmap = qvariant_cast<QIcon> (variant).pixmap (iconSize ()); break;
195          default: break;
196        }
197      if (!pixmap.isNull ())
198        {
199          s->drawItemPixmap (&painter, rect, Qt::AlignLeft|Qt::AlignVCenter, pixmap);
200          rect.setLeft (rect.left () + pixmap.width () + hmargin);
201        }
202
203      // draw the count
204      QString text = modelIndex.data (TorrentCountStringRole).toString ();
205      if (!text.isEmpty ())
206        {
207          const QPen pen = painter.pen ();
208          painter.setPen (opt.palette.color (QPalette::Disabled, QPalette::Text));
209          QRect r = s->itemTextRect (painter.fontMetrics (), rect, Qt::AlignRight|Qt::AlignVCenter, false, text);
210          painter.drawText (r, 0, text);
211          rect.setRight (r.left () - hmargin);
212          painter.setPen (pen);
213        }
214
215      // draw the text
216      text = modelIndex.data (Qt::DisplayRole).toString ();
217      text = painter.fontMetrics ().elidedText (text, Qt::ElideRight, rect.width ());
218      s->drawItemText (&painter, rect, Qt::AlignLeft|Qt::AlignVCenter, opt.palette, true, text);
219    }
220}
221
222/****
223*****
224*****  ACTIVITY
225*****
226****/
227
228FilterBarComboBox *
229FilterBar::createActivityCombo ()
230{
231  FilterBarComboBox * c = new FilterBarComboBox (this);
232  FilterBarComboBoxDelegate * delegate = new FilterBarComboBoxDelegate (this, c);
233  c->setItemDelegate (delegate);
234
235  QPixmap blankPixmap (c->iconSize ());
236  blankPixmap.fill (Qt::transparent);
237  QIcon blankIcon (blankPixmap);
238
239  QStandardItemModel * model = new QStandardItemModel (this);
240
241  QStandardItem * row = new QStandardItem (tr ("All"));
242  row->setData (FilterMode::SHOW_ALL, ActivityRole);
243  model->appendRow (row);
244
245  model->appendRow (new QStandardItem); // separator
246  delegate->setSeparator (model, model->index (1, 0));
247
248  row = new QStandardItem (QIcon::fromTheme ("system-run", blankIcon), tr ("Active"));
249  row->setData (FilterMode::SHOW_ACTIVE, ActivityRole);
250  model->appendRow (row);
251
252  row = new QStandardItem (QIcon::fromTheme ("go-down", blankIcon), tr ("Downloading"));
253  row->setData (FilterMode::SHOW_DOWNLOADING, ActivityRole);
254  model->appendRow (row);
255
256  row = new QStandardItem (QIcon::fromTheme ("go-up", blankIcon), tr ("Seeding"));
257  row->setData (FilterMode::SHOW_SEEDING, ActivityRole);
258  model->appendRow (row);
259
260  row = new QStandardItem (QIcon::fromTheme ("media-playback-pause", blankIcon), tr ("Paused"));
261  row->setData (FilterMode::SHOW_PAUSED, ActivityRole);
262  model->appendRow (row);
263
264  row = new QStandardItem (blankIcon, tr ("Finished"));
265  row->setData (FilterMode::SHOW_FINISHED, ActivityRole);
266  model->appendRow (row);
267
268  row = new QStandardItem (QIcon::fromTheme ("view-refresh", blankIcon), tr ("Verifying"));
269  row->setData (FilterMode::SHOW_VERIFYING, ActivityRole);
270  model->appendRow (row);
271
272  row = new QStandardItem (QIcon::fromTheme ("dialog-error", blankIcon), tr ("Error"));
273  row->setData (FilterMode::SHOW_ERROR, ActivityRole);
274  model->appendRow (row);
275
276  c->setModel (model);
277  return c;
278}
279
280/****
281*****
282*****
283*****
284****/
285
286namespace
287{
288  QString readableHostName (const QString& host)
289  {
290    // get the readable name...
291    QString name = host;
292    const int pos = name.lastIndexOf ('.');
293    if (pos >= 0)
294      name.truncate (pos);
295    if (!name.isEmpty ())
296      name[0] = name[0].toUpper ();
297    return name;
298  }
299}
300
301void
302FilterBar::refreshTrackers ()
303{
304  Favicons& favicons = dynamic_cast<MyApp*> (QApplication::instance ())->favicons;
305  const int firstTrackerRow = 2; // skip over the "All" and separator...
306
307  // pull info from the tracker model...
308  QSet<QString> oldHosts;
309  for (int row=firstTrackerRow; ; ++row)
310    {
311      QModelIndex index = myTrackerModel->index (row, 0);
312      if (!index.isValid ())
313        break;
314      oldHosts << index.data (TrackerRole).toString ();
315    }
316
317  // pull the new stats from the torrent model...
318  QSet<QString> newHosts;
319  QMap<QString,int> torrentsPerHost;
320  for (int row=0; ; ++row)
321    {
322      QModelIndex index = myTorrents.index (row, 0);
323      if (!index.isValid ())
324        break;
325      const Torrent * tor = index.data (TorrentModel::TorrentRole).value<const Torrent*> ();
326      QSet<QString> torrentNames;
327      foreach (QString host, tor->hosts ())
328        {
329          newHosts.insert (host);
330          torrentNames.insert (readableHostName (host));
331        }
332      foreach (QString name, torrentNames)
333        ++torrentsPerHost[name];
334    }
335
336  // update the "All" row
337  myTrackerModel->setData (myTrackerModel->index (0,0), myTorrents.rowCount (), TorrentCountRole);
338  myTrackerModel->setData (myTrackerModel->index (0,0), getCountString (myTorrents.rowCount ()), TorrentCountStringRole);
339
340  // rows to update
341  foreach (QString host, oldHosts & newHosts)
342    {
343      const QString name = readableHostName (host);
344      QStandardItem * row = myTrackerModel->findItems (name).front ();
345      const int count = torrentsPerHost[name];
346      row->setData (count, TorrentCountRole);
347      row->setData (getCountString (count), TorrentCountStringRole);
348      row->setData (favicons.findFromHost (host), Qt::DecorationRole);
349    }
350
351  // rows to remove
352  foreach (QString host, oldHosts - newHosts)
353    {
354      const QString name = readableHostName (host);
355      QStandardItem * item = myTrackerModel->findItems (name).front ();
356      if (!item->data (TrackerRole).toString ().isEmpty ()) // don't remove "All"
357        myTrackerModel->removeRows (item->row (), 1);
358    }
359
360  // rows to add
361  bool anyAdded = false;
362  foreach (QString host, newHosts - oldHosts)
363    {
364      const QString name = readableHostName (host);
365
366      if (!myTrackerModel->findItems (name).isEmpty ())
367        continue;
368
369      // find the sorted position to add this row
370      int i = firstTrackerRow;
371      for (int n=myTrackerModel->rowCount (); i<n; ++i)
372        {
373          const QString rowName = myTrackerModel->index (i,0).data (Qt::DisplayRole).toString ();
374          if (rowName >= name)
375            break;
376        }
377
378      // add the row
379      QStandardItem * row = new QStandardItem (favicons.findFromHost (host), name);
380      const int count = torrentsPerHost[host];
381      row->setData (count, TorrentCountRole);
382      row->setData (getCountString (count), TorrentCountStringRole);
383      row->setData (favicons.findFromHost (host), Qt::DecorationRole);
384      row->setData (host, TrackerRole);
385      myTrackerModel->insertRow (i, row);
386      anyAdded = true;
387    }
388
389  if (anyAdded) // the one added might match our filter...
390    refreshPref (Prefs::FILTER_TRACKERS);
391}
392
393
394FilterBarComboBox *
395FilterBar::createTrackerCombo (QStandardItemModel * model)
396{
397  FilterBarComboBox * c = new FilterBarComboBox (this);
398  FilterBarComboBoxDelegate * delegate = new FilterBarComboBoxDelegate (this, c);
399  c->setItemDelegate (delegate);
400
401  QStandardItem * row = new QStandardItem (tr ("All"));
402  row->setData ("", TrackerRole);
403  const int count = myTorrents.rowCount ();
404  row->setData (count, TorrentCountRole);
405  row->setData (getCountString (count), TorrentCountStringRole);
406  model->appendRow (row);
407
408  model->appendRow (new QStandardItem); // separator
409  delegate->setSeparator (model, model->index (1, 0));
410
411  c->setModel (model);
412  return c;
413}
414
415/****
416*****
417*****
418*****
419****/
420
421FilterBar::FilterBar (Prefs& prefs, TorrentModel& torrents, TorrentFilter& filter, QWidget * parent):
422  QWidget (parent),
423  myPrefs (prefs),
424  myTorrents (torrents),
425  myFilter (filter),
426  myRecountTimer (new QTimer (this)),
427  myIsBootstrapping (true)
428{
429  QHBoxLayout * h = new QHBoxLayout (this);
430  const int hmargin = qMax (int (HIG::PAD), style ()->pixelMetric (QStyle::PM_LayoutHorizontalSpacing));
431
432  myCountLabel = new QLabel (this);
433  h->setSpacing (0);
434  h->setContentsMargins (2, 2, 2, 2);
435  h->addWidget (myCountLabel);
436  h->addSpacing (hmargin);
437
438  myActivityCombo = createActivityCombo ();
439  h->addWidget (myActivityCombo, 1);
440  h->addSpacing (hmargin);
441
442  myTrackerModel = new QStandardItemModel (this);
443  myTrackerCombo = createTrackerCombo (myTrackerModel);
444  h->addWidget (myTrackerCombo, 1);
445  h->addSpacing (hmargin*2);
446
447  myLineEdit = new QLineEdit (this);
448  h->addWidget (myLineEdit);
449  connect (myLineEdit, SIGNAL (textChanged (QString)), this, SLOT (onTextChanged (QString)));
450
451  QPushButton * p = new QPushButton (this);
452  QIcon icon = QIcon::fromTheme ("edit-clear", style ()->standardIcon (QStyle::SP_DialogCloseButton));
453  int iconSize = style ()->pixelMetric (QStyle::PM_SmallIconSize);
454  p->setIconSize (QSize (iconSize, iconSize));
455  p->setIcon (icon);
456  p->setFlat (true);
457  h->addWidget (p);
458  connect (p, SIGNAL (clicked (bool)), myLineEdit, SLOT (clear ()));
459
460  // listen for changes from the other players
461  connect (&myPrefs, SIGNAL (changed (int)), this, SLOT (refreshPref (int)));
462  connect (myActivityCombo, SIGNAL (currentIndexChanged (int)), this, SLOT (onActivityIndexChanged (int)));
463  connect (myTrackerCombo, SIGNAL (currentIndexChanged (int)), this, SLOT (onTrackerIndexChanged (int)));
464  connect (&myFilter, SIGNAL (rowsInserted (QModelIndex, int, int)), this, SLOT (refreshCountLabel ()));
465  connect (&myFilter, SIGNAL (rowsRemoved (QModelIndex, int, int)), this, SLOT (refreshCountLabel ()));
466  connect (&myTorrents, SIGNAL (modelReset ()), this, SLOT (onTorrentModelReset ()));
467  connect (&myTorrents, SIGNAL (rowsInserted (QModelIndex, int, int)), this, SLOT (onTorrentModelRowsInserted (QModelIndex, int, int)));
468  connect (&myTorrents, SIGNAL (rowsRemoved (QModelIndex, int, int)), this, SLOT (onTorrentModelRowsRemoved (QModelIndex, int, int)));
469  connect (&myTorrents, SIGNAL (dataChanged (QModelIndex, QModelIndex)), this, SLOT (onTorrentModelDataChanged (QModelIndex, QModelIndex)));
470  connect (myRecountTimer, SIGNAL (timeout ()), this, SLOT (recount ()));
471
472  recountSoon ();
473  refreshTrackers ();
474  refreshCountLabel ();
475  myIsBootstrapping = false;
476
477  // initialize our state
478  QList<int> initKeys;
479  initKeys << Prefs::FILTER_MODE
480           << Prefs::FILTER_TRACKERS;
481  foreach (int key, initKeys)
482      refreshPref (key);
483}
484
485FilterBar::~FilterBar ()
486{
487  delete myRecountTimer;
488}
489
490/***
491****
492***/
493
494void
495FilterBar::refreshPref (int key)
496{
497  switch (key)
498    {
499      case Prefs::FILTER_MODE:
500        {
501          const FilterMode m = myPrefs.get<FilterMode> (key);
502          QAbstractItemModel * model = myActivityCombo->model ();
503          QModelIndexList indices = model->match (model->index (0,0), ActivityRole, m.mode ());
504          myActivityCombo->setCurrentIndex (indices.isEmpty () ? 0 : indices.first ().row ());
505          break;
506        }
507
508      case Prefs::FILTER_TRACKERS:
509        {
510          const QString tracker = myPrefs.getString (key);
511          const QString name = readableHostName (tracker);
512          QList<QStandardItem*> rows = myTrackerModel->findItems (name);
513          if (!rows.isEmpty ())
514            {
515              myTrackerCombo->setCurrentIndex (rows.front ()->row ());
516            }
517          else // hm, we don't seem to have this tracker anymore...
518            {
519              const bool isBootstrapping = myTrackerModel->rowCount () <= 2;
520              if (!isBootstrapping)
521                myPrefs.set (key, "");
522            }
523          break;
524        }
525    }
526}
527
528void
529FilterBar::onTextChanged (const QString& str)
530{
531  if (!myIsBootstrapping)
532    myPrefs.set (Prefs::FILTER_TEXT, str.trimmed ());
533}
534
535void
536FilterBar::onTrackerIndexChanged (int i)
537{
538  if (!myIsBootstrapping)
539    {
540      QString str;
541      const bool isTracker = !myTrackerCombo->itemData (i,TrackerRole).toString ().isEmpty ();
542      if (!isTracker) // show all
543        {
544          str = "";
545        }
546      else
547        {
548          str = myTrackerCombo->itemData (i,TrackerRole).toString ();
549          const int pos = str.lastIndexOf ('.');
550          if (pos >= 0)
551            str.truncate (pos+1);
552        }
553      myPrefs.set (Prefs::FILTER_TRACKERS, str);
554    }
555}
556
557void
558FilterBar::onActivityIndexChanged (int i)
559{
560  if (!myIsBootstrapping)
561    {
562      const FilterMode mode = myActivityCombo->itemData (i, ActivityRole).toInt ();
563      myPrefs.set (Prefs::FILTER_MODE, mode);
564    }
565}
566
567/***
568****
569***/
570
571void FilterBar::onTorrentModelReset () { recountSoon (); }
572void FilterBar::onTorrentModelRowsInserted (const QModelIndex&, int, int) { recountSoon (); }
573void FilterBar::onTorrentModelRowsRemoved (const QModelIndex&, int, int) { recountSoon (); }
574void FilterBar::onTorrentModelDataChanged (const QModelIndex&, const QModelIndex&) { recountSoon (); }
575
576void
577FilterBar::recountSoon ()
578{
579  if (!myRecountTimer->isActive ())
580    {
581      myRecountTimer->setSingleShot (true);
582      myRecountTimer->start (800);
583    }
584}
585void
586FilterBar::recount ()
587{
588  QAbstractItemModel * model = myActivityCombo->model ();
589
590  int torrentsPerMode[FilterMode::NUM_MODES] = {};
591  myFilter.countTorrentsPerMode (torrentsPerMode);
592
593  for (int row=0, n=model->rowCount (); row<n; ++row)
594    {
595      QModelIndex index = model->index (row, 0);
596      const int mode = index.data (ActivityRole).toInt ();
597      const int count = torrentsPerMode [mode];
598      model->setData (index, count, TorrentCountRole);
599      model->setData (index, getCountString (count), TorrentCountStringRole);
600    }
601
602  refreshTrackers ();
603  refreshCountLabel ();
604}
605
606QString
607FilterBar::getCountString (int n) const
608{
609  return QString ("%L1").arg (n);
610}
611
612void
613FilterBar::refreshCountLabel ()
614{
615  const int visibleCount = myFilter.rowCount ();
616  const int trackerCount = myTrackerCombo->currentCount ();
617  const int activityCount = myActivityCombo->currentCount ();
618
619  if ((visibleCount == activityCount) || (visibleCount == trackerCount))
620    myCountLabel->setText (tr("Show:"));
621  else
622    myCountLabel->setText (tr("Show %Ln of:", 0, visibleCount));
623}
Note: See TracBrowser for help on using the repository browser.