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>
16 #include <QHBoxLayout>
18 #include <QStylePainter>
25 #include "filterbar.h"
28 #include "torrent-filter.h"
29 #include "torrent-model.h"
40 TorrentCountRole
= Qt::UserRole
+ 1,
41 TorrentCountStringRole
,
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
),
61 FilterBarComboBoxDelegate :: isSeparator (const QModelIndex
&index
)
63 return index
.data (Qt::AccessibleDescriptionRole
).toString () == QLatin1String ("separator");
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
));
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 ());
88 myCombo
->style ()->drawPrimitive (QStyle::PE_IndicatorToolBarSeparator
, &opt
, painter
, myCombo
);
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
);
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);
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;
152 FilterBarComboBox :: FilterBarComboBox (QWidget
* parent
):
158 FilterBarComboBox :: currentCount () const
162 const QModelIndex modelIndex
= model ()->index (currentIndex (), 0, rootModelIndex ());
163 if (modelIndex
.isValid ())
164 count
= modelIndex
.data (TorrentCountRole
).toInt ();
170 FilterBarComboBox :: paintEvent (QPaintEvent
* 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
);
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;
200 if (!pixmap
.isNull ())
202 s
->drawItemPixmap (&painter
, rect
, Qt::AlignLeft
|Qt::AlignVCenter
, pixmap
);
203 rect
.setLeft (rect
.left () + pixmap
.width () + hmargin
);
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
);
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
);
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
);
291 QString
readableHostName (const QString host
)
293 // get the readable name...
295 const int pos
= name
.lastIndexOf ('.');
298 if (!name
.isEmpty ())
299 name
[0] = name
[0].toUpper ();
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 ())
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 ())
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
);
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
);
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);
364 bool anyAdded
= false;
365 foreach (QString host
, newHosts
- oldHosts
)
367 const QString name
= readableHostName (host
);
369 if (!myTrackerModel
->findItems (name
).isEmpty ())
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 ();
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
);
392 if (anyAdded
) // the one added might match our filter...
393 refreshPref (Prefs::FILTER_TRACKERS
);
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));
424 FilterBar :: FilterBar (Prefs
& prefs
, TorrentModel
& torrents
, TorrentFilter
& filter
, QWidget
* parent
):
427 myTorrents (torrents
),
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);
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
));
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 ()));
477 refreshCountLabel ();
478 myIsBootstrapping
= false;
480 // initialize our state
482 initKeys
<< Prefs :: FILTER_MODE
483 << Prefs :: FILTER_TRACKERS
;
484 foreach (int key
, initKeys
)
488 FilterBar :: ~FilterBar ()
490 delete myRecountTimer
;
498 FilterBar :: refreshPref (int 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 ());
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
, "");
532 FilterBar :: onTextChanged (const QString
& str
)
534 if (!myIsBootstrapping
)
535 myPrefs
.set (Prefs::FILTER_TEXT
, str
.trimmed ());
539 FilterBar :: onTrackerIndexChanged (int i
)
541 if (!myIsBootstrapping
)
544 const bool isTracker
= !myTrackerCombo
->itemData (i
,TrackerRole
).toString ().isEmpty ();
545 if (!isTracker
) // show all
551 str
= myTrackerCombo
->itemData (i
,TrackerRole
).toString ();
552 const int pos
= str
.lastIndexOf ('.');
554 str
.truncate (pos
+1);
556 myPrefs
.set (Prefs::FILTER_TRACKERS
, str
);
561 FilterBar :: onActivityIndexChanged (int i
)
563 if (!myIsBootstrapping
)
565 const FilterMode mode
= myActivityCombo
->itemData (i
, ActivityRole
).toInt ();
566 myPrefs
.set (Prefs::FILTER_MODE
, mode
);
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 (); }
580 FilterBar :: recountSoon ()
582 if (!myRecountTimer
->isActive ())
584 myRecountTimer
->setSingleShot (true);
585 myRecountTimer
->start (800);
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
);
606 refreshCountLabel ();
610 FilterBar :: getCountString (int n
) const
612 return QString ("%L1").arg (n
);
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:"));
625 myCountLabel
->setText (tr("Show %Ln of:", 0, visibleCount
));