source: trunk/qt/filterbar.cc @ 11137

Last change on this file since 11137 was 11137, checked in by charles, 11 years ago

(trunk qt) now that 4.6 has a standard call QIcon::fromTheme(), we can drop the third-party qticonloader code

  • Property svn:keywords set to Date Rev Author Id
File size: 18.6 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 11137 2010-08-06 20:21:35Z charles $
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            case QVariant::Pixmap: pixmap = qvariant_cast<QPixmap>(variant); break;
177            case QVariant::Icon:   pixmap = qvariant_cast<QIcon>(variant).pixmap(iconSize()); break;
178            default: break;
179        }
180        if( !pixmap.isNull() ) {
181            s->drawItemPixmap( &painter, rect, Qt::AlignLeft|Qt::AlignVCenter, pixmap );
182            rect.setLeft( rect.left() + pixmap.width() + hmargin );
183        }
184
185        // draw the count
186        const int count = modelIndex.data( TorrentCountRole ).toInt();
187        if( count >= 0 ) {
188            const QString text = QString::number( count);
189            const QPen pen = painter.pen( );
190            painter.setPen( opt.palette.color( QPalette::Disabled, QPalette::Text ) );
191            QRect r = s->itemTextRect( painter.fontMetrics(), rect, Qt::AlignRight|Qt::AlignVCenter, false, text );
192            painter.drawText( r, 0, text );
193            rect.setRight( r.left() - hmargin );
194            painter.setPen( pen );
195        }
196
197        // draw the text
198        QString text = modelIndex.data( Qt::DisplayRole ).toString();
199        text = painter.fontMetrics().elidedText ( text, Qt::ElideRight, rect.width() );
200        s->drawItemText( &painter, rect, Qt::AlignLeft|Qt::AlignVCenter, opt.palette, true, text );
201    }
202}
203
204/****
205*****
206*****  ACTIVITY
207*****
208****/
209
210QComboBox*
211FilterBar :: createActivityCombo( )
212{
213    QComboBox * c = new FilterBarComboBox( this );
214    FilterBarComboBoxDelegate * delegate = new FilterBarComboBoxDelegate( 0, c );
215    c->setItemDelegate( delegate );
216
217    QPixmap blankPixmap( c->iconSize( ) );
218    blankPixmap.fill( Qt::transparent );
219    QIcon blankIcon( blankPixmap );
220
221    QStandardItemModel * model = new QStandardItemModel;
222
223    QStandardItem * row = new QStandardItem( tr( "All" ) );
224    row->setData( FilterMode::SHOW_ALL, ActivityRole );
225    model->appendRow( row );
226
227    model->appendRow( new QStandardItem ); // separator
228    delegate->setSeparator( model, model->index( 1, 0 ) );
229
230    row = new QStandardItem( QIcon::fromTheme( "system-run", blankIcon ), tr( "Active" ) );
231    row->setData( FilterMode::SHOW_ACTIVE, ActivityRole );
232    model->appendRow( row );
233
234    row = new QStandardItem( QIcon::fromTheme( "go-down", blankIcon ), tr( "Downloading" ) );
235    row->setData( FilterMode::SHOW_DOWNLOADING, ActivityRole );
236    model->appendRow( row );
237
238    row = new QStandardItem( QIcon::fromTheme( "go-up", blankIcon ), tr( "Seeding" ) );
239    row->setData( FilterMode::SHOW_SEEDING, ActivityRole );
240    model->appendRow( row );
241
242    row = new QStandardItem( QIcon::fromTheme( "media-playback-pause", blankIcon ), tr( "Paused" ) );
243    row->setData( FilterMode::SHOW_PAUSED, ActivityRole );
244    model->appendRow( row );
245
246    row = new QStandardItem( blankIcon, tr( "Finished" ) );
247    row->setData( FilterMode::SHOW_FINISHED, ActivityRole );
248    model->appendRow( row );
249
250    row = new QStandardItem( blankIcon, tr( "Queued" ) );
251    row->setData( FilterMode::SHOW_QUEUED, ActivityRole );
252    model->appendRow( row );
253
254    row = new QStandardItem( QIcon::fromTheme( "view-refresh", blankIcon ), tr( "Verifying" ) );
255    row->setData( FilterMode::SHOW_VERIFYING, ActivityRole );
256    model->appendRow( row );
257
258    row = new QStandardItem( QIcon::fromTheme( "dialog-error", blankIcon ), tr( "Error" ) );
259    row->setData( FilterMode::SHOW_ERROR, ActivityRole );
260    model->appendRow( row );
261
262    c->setModel( model );
263    return c;
264}
265
266/****
267*****
268***** 
269*****
270****/
271
272namespace
273{
274    QString readableHostName( const QString host )
275    {
276        // get the readable name...
277        QString name = host;
278        const int pos = name.lastIndexOf( '.' );
279        if( pos >= 0 )
280            name.truncate( pos );
281        if( !name.isEmpty( ) )
282            name[0] = name[0].toUpper( );
283        return name;
284    }
285}
286
287void
288FilterBar :: refreshTrackers( )
289{
290    Favicons& favicons = dynamic_cast<MyApp*>(QApplication::instance())->favicons;
291    const int firstTrackerRow = 2; // skip over the "All" and separator...
292
293    // pull info from the tracker model...
294    QSet<QString> oldHosts;
295    for( int row=firstTrackerRow; ; ++row ) {
296        QModelIndex index = myTrackerModel->index( row, 0 );
297        if( !index.isValid( ) )
298            break;
299        oldHosts << index.data(TrackerRole).toString();
300    }
301
302    // pull the new stats from the torrent model...
303    QSet<QString> newHosts;
304    QMap<QString,int> torrentsPerHost;
305    for( int row=0; ; ++row )
306    {
307        QModelIndex index = myTorrents.index( row, 0 );
308        if( !index.isValid( ) )
309            break;
310        const Torrent * tor = index.data( TorrentModel::TorrentRole ).value<const Torrent*>();
311        const QStringList trackers = tor->trackers( );
312        QSet<QString> torrentHosts;
313        foreach( QString tracker, trackers )
314            torrentHosts.insert( Favicons::getHost( QUrl( tracker ) ) );
315        foreach( QString host, torrentHosts ) {
316            newHosts.insert( host );
317            ++torrentsPerHost[host];
318        }
319    }
320
321    // update the "All" row
322    myTrackerModel->setData( myTrackerModel->index(0,0), myTorrents.rowCount(), TorrentCountRole );
323
324    // rows to update
325    foreach( QString host, oldHosts & newHosts )
326    {
327        const QString name = readableHostName( host );
328        QStandardItem * row = myTrackerModel->findItems(name).front();
329        row->setData( torrentsPerHost[host], TorrentCountRole );
330        row->setData( favicons.findFromHost(host), Qt::DecorationRole );
331    }
332
333    // rows to remove
334    foreach( QString host, oldHosts - newHosts ) {
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        // find the sorted position to add this row
348        int i = firstTrackerRow;
349        for( int n=myTrackerModel->rowCount(); i<n; ++i )
350            if( myTrackerModel->index(i,0).data(Qt::DisplayRole).toString() > name )
351                break;
352
353        // add the row
354        QStandardItem * row = new QStandardItem( favicons.findFromHost( host ), readableHostName( host ) );
355        row->setData( torrentsPerHost[host], TorrentCountRole );
356        row->setData( favicons.findFromHost(host), Qt::DecorationRole );
357        row->setData( host, TrackerRole );
358        myTrackerModel->insertRow( i, row );
359        anyAdded = true;
360    }
361
362    if( anyAdded ) // the one added might match our filter...
363        refreshPref( Prefs::FILTER_TRACKERS );
364}
365
366
367QComboBox*
368FilterBar :: createTrackerCombo( QStandardItemModel * model )
369{
370    QComboBox * c = new FilterBarComboBox( this );
371    FilterBarComboBoxDelegate * delegate = new FilterBarComboBoxDelegate( 0, c );
372    c->setItemDelegate( delegate );
373
374    QStandardItem * row = new QStandardItem( tr( "All" ) );
375    row->setData( "", TrackerRole );
376    row->setData( myTorrents.rowCount(), TorrentCountRole );
377    model->appendRow( row );
378
379    model->appendRow( new QStandardItem ); // separator
380    delegate->setSeparator( model, model->index( 1, 0 ) );
381
382    c->setModel( model );
383    return c;
384}
385
386/****
387*****
388***** 
389*****
390****/
391
392FilterBar :: FilterBar( Prefs& prefs, TorrentModel& torrents, TorrentFilter& filter, QWidget * parent ):
393    QWidget( parent ),
394    myPrefs( prefs ),
395    myTorrents( torrents ),
396    myFilter( filter ),
397    myRecountTimer( new QTimer( this ) ),
398    myIsBootstrapping( true )
399{
400    QHBoxLayout * h = new QHBoxLayout( this );
401    const int hmargin = qMax( int(HIG::PAD), style()->pixelMetric( QStyle::PM_LayoutHorizontalSpacing ) );
402
403    h->setSpacing( 0 );
404    h->setContentsMargins( 2, 2, 2, 2 );
405    h->addWidget( new QLabel( tr( "Show:" ), this ) );
406    h->addSpacing( hmargin );
407
408    myActivityCombo = createActivityCombo( );
409    h->addWidget( myActivityCombo, 1 );
410    h->addSpacing( hmargin );
411
412    myTrackerModel = new QStandardItemModel;
413    myTrackerCombo = createTrackerCombo( myTrackerModel );
414    h->addWidget( myTrackerCombo, 1 );
415    h->addSpacing( hmargin*2 );
416   
417    myLineEdit = new QLineEdit( this );
418    h->addWidget( myLineEdit );
419    connect( myLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onTextChanged(QString)));
420
421    QPushButton * p = new QPushButton;
422    QIcon icon = QIcon::fromTheme( "edit-clear", style()->standardIcon( QStyle::SP_DialogCloseButton ) );
423    int iconSize = style()->pixelMetric( QStyle::PM_SmallIconSize );
424    p->setIconSize( QSize( iconSize, iconSize ) );
425    p->setIcon( icon );
426    p->setFlat( true );
427    h->addWidget( p );
428    connect( p, SIGNAL(clicked(bool)), myLineEdit, SLOT(clear()));
429
430    // listen for changes from the other players
431    connect( &myPrefs, SIGNAL(changed(int)), this, SLOT(refreshPref(int)));
432    connect( myActivityCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onActivityIndexChanged(int)));
433    connect( myTrackerCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onTrackerIndexChanged(int)));
434    connect( &myTorrents, SIGNAL(modelReset()), this, SLOT(onTorrentModelReset()));
435    connect( &myTorrents, SIGNAL(rowsInserted(const QModelIndex&,int,int)), this, SLOT(onTorrentModelRowsInserted(const QModelIndex&,int,int)));
436    connect( &myTorrents, SIGNAL(rowsRemoved(const QModelIndex&,int,int)), this, SLOT(onTorrentModelRowsRemoved(const QModelIndex&,int,int)));
437    connect( &myTorrents, SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&)), this, SLOT(onTorrentModelDataChanged(const QModelIndex&,const QModelIndex&)));
438    connect( myRecountTimer, SIGNAL(timeout()), this, SLOT(recount()) );
439
440    recountSoon( );
441    refreshTrackers( );
442    myIsBootstrapping = false;
443
444    // initialize our state
445    QList<int> initKeys;
446    initKeys << Prefs :: FILTER_MODE
447             << Prefs :: FILTER_TRACKERS;
448    foreach( int key, initKeys )
449        refreshPref( key );
450}
451
452FilterBar :: ~FilterBar( )
453{
454    delete myRecountTimer;
455}
456
457/***
458****
459***/
460
461void
462FilterBar :: refreshPref( int key )
463{
464    switch( key )
465    {
466        case Prefs :: FILTER_MODE: {
467            const FilterMode m = myPrefs.get<FilterMode>( key );
468            QAbstractItemModel * model = myActivityCombo->model( );
469            QModelIndexList indices = model->match( model->index(0,0), ActivityRole, m.mode(), -1 );
470            myActivityCombo->setCurrentIndex( indices.isEmpty() ? 0 : indices.first().row( ) );
471            break;
472        }
473
474        case Prefs :: FILTER_TRACKERS: {
475            const QString tracker = myPrefs.getString( key );
476            QModelIndexList indices = myTrackerModel->match( myTrackerModel->index(0,0), TrackerRole, tracker, 1, Qt::MatchFixedString );
477            if( !indices.isEmpty( )  )
478                myTrackerCombo->setCurrentIndex( indices.first().row() );
479            else { // hm, we don't seem to have this tracker anymore...
480                const bool isBootstrapping = myTrackerModel->rowCount( ) <= 2;
481                if( !isBootstrapping )
482                    myPrefs.set( key, "" );
483            }
484            break;
485        }
486
487        case Prefs :: FILTER_TEXT:
488            myLineEdit->setText( myPrefs.getString( key ) );
489            break;
490    }
491}
492
493void
494FilterBar :: onTextChanged( const QString& str )
495{
496    if( !myIsBootstrapping )
497        myPrefs.set( Prefs::FILTER_TEXT, str.trimmed( ) );
498}
499
500void
501FilterBar :: onTrackerIndexChanged( int i )
502{
503    if( !myIsBootstrapping )
504    {
505        QString str;
506        const bool isTracker = !myTrackerCombo->itemData(i,TrackerRole).toString().isEmpty();
507        if( isTracker )
508            str = myTrackerCombo->itemData(i,TrackerRole).toString();
509        else // show all
510            str = "";
511        myPrefs.set( Prefs::FILTER_TRACKERS, str );
512    }
513}
514
515void
516FilterBar :: onActivityIndexChanged( int i )
517{
518    if( !myIsBootstrapping )
519    {
520        const FilterMode mode = myActivityCombo->itemData( i, ActivityRole ).toInt( );
521        myPrefs.set( Prefs::FILTER_MODE, mode );
522    }
523}
524
525/***
526****
527***/
528
529void FilterBar :: onTorrentModelReset( ) { recountSoon( ); }
530void FilterBar :: onTorrentModelRowsInserted( const QModelIndex&, int, int ) { recountSoon( ); }
531void FilterBar :: onTorrentModelRowsRemoved( const QModelIndex&, int, int ) { recountSoon( ); }
532void FilterBar :: onTorrentModelDataChanged( const QModelIndex&, const QModelIndex& ) { recountSoon( ); }
533
534void
535FilterBar :: recountSoon( )
536{
537    if( !myRecountTimer->isActive( ) )
538    {
539        myRecountTimer->setSingleShot( true );
540        myRecountTimer->start( 500 );
541    }
542}
543void
544FilterBar :: recount ( )
545{
546    // recount the activity combobox...
547    for( int i=0, n=FilterMode::NUM_MODES; i<n; ++i )
548    {
549        const FilterMode m( i );
550        QAbstractItemModel * model = myActivityCombo->model( );
551        QModelIndexList indices = model->match( model->index(0,0), ActivityRole, m.mode(), -1 );
552        if( !indices.isEmpty( ) ) {
553            const int count = myFilter.count( m );
554            model->setData( indices.first(), count, TorrentCountRole );
555        }
556    }
557
558    refreshTrackers( );
559}
Note: See TracBrowser for help on using the repository browser.