source: trunk/qt/filterbar.cc @ 13869

Last change on this file since 13869 was 13869, checked in by jordan, 10 years ago

(qt) copyediting: modify more files to the new indentation/whitespace formatting

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