source: trunk/qt/filterbar.cc @ 11086

Last change on this file since 11086 was 11086, checked in by Longinus00, 12 years ago

(trunk) #3471:Add 'finished' to filterbar

File size: 18.7 KB
Line 
1/*
2 * This file Copyright (C) 2010 Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id$
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 "prefs.h"
21#include "qticonloader.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( 4, 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( QtIconLoader::icon( "system-run" ), tr( "Active" ) );
231    row->setData( FilterMode::SHOW_ACTIVE, ActivityRole );
232    model->appendRow( row );
233
234    row = new QStandardItem( QtIconLoader::icon( "go-down" ), tr( "Downloading" ) );
235    row->setData( FilterMode::SHOW_DOWNLOADING, ActivityRole );
236    model->appendRow( row );
237
238    row = new QStandardItem( QtIconLoader::icon( "go-up" ), tr( "Seeding" ) );
239    row->setData( FilterMode::SHOW_SEEDING, ActivityRole );
240    model->appendRow( row );
241
242    row = new QStandardItem( QtIconLoader::icon( "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( QtIconLoader::icon( "view-refresh", blankIcon ), tr( "Verifying" ) );
255    row->setData( FilterMode::SHOW_VERIFYING, ActivityRole );
256    model->appendRow( row );
257
258    row = new QStandardItem( QtIconLoader::icon( "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    int hmargin = 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 = QtIconLoader::icon( "edit-clear" );
423    if( icon.isNull( ) )
424        icon = style()->standardIcon( QStyle::SP_DialogCloseButton );
425    int iconSize = style()->pixelMetric( QStyle::PM_SmallIconSize );
426    p->setIconSize( QSize( iconSize, iconSize ) );
427    p->setIcon( icon );
428    p->setFlat( true );
429    h->addWidget( p );
430    connect( p, SIGNAL(clicked(bool)), myLineEdit, SLOT(clear()));
431
432    // listen for changes from the other players
433    connect( &myPrefs, SIGNAL(changed(int)), this, SLOT(refreshPref(int)));
434    connect( myActivityCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onActivityIndexChanged(int)));
435    connect( myTrackerCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onTrackerIndexChanged(int)));
436    connect( &myTorrents, SIGNAL(modelReset()), this, SLOT(onTorrentModelReset()));
437    connect( &myTorrents, SIGNAL(rowsInserted(const QModelIndex&,int,int)), this, SLOT(onTorrentModelRowsInserted(const QModelIndex&,int,int)));
438    connect( &myTorrents, SIGNAL(rowsRemoved(const QModelIndex&,int,int)), this, SLOT(onTorrentModelRowsRemoved(const QModelIndex&,int,int)));
439    connect( &myTorrents, SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&)), this, SLOT(onTorrentModelDataChanged(const QModelIndex&,const QModelIndex&)));
440    connect( myRecountTimer, SIGNAL(timeout()), this, SLOT(recount()) );
441
442    recountSoon( );
443    refreshTrackers( );
444    myIsBootstrapping = false;
445
446    // initialize our state
447    QList<int> initKeys;
448    initKeys << Prefs :: FILTER_MODE
449             << Prefs :: FILTER_TRACKERS;
450    foreach( int key, initKeys )
451        refreshPref( key );
452}
453
454FilterBar :: ~FilterBar( )
455{
456    delete myRecountTimer;
457}
458
459/***
460****
461***/
462
463void
464FilterBar :: refreshPref( int key )
465{
466    switch( key )
467    {
468        case Prefs :: FILTER_MODE: {
469            const FilterMode m = myPrefs.get<FilterMode>( key );
470            QAbstractItemModel * model = myActivityCombo->model( );
471            QModelIndexList indices = model->match( model->index(0,0), ActivityRole, m.mode(), -1 );
472            myActivityCombo->setCurrentIndex( indices.isEmpty() ? 0 : indices.first().row( ) );
473            break;
474        }
475
476        case Prefs :: FILTER_TRACKERS: {
477            const QString tracker = myPrefs.getString( key );
478            QModelIndexList indices = myTrackerModel->match( myTrackerModel->index(0,0), TrackerRole, tracker, 1, Qt::MatchFixedString );
479            if( !indices.isEmpty( )  )
480                myTrackerCombo->setCurrentIndex( indices.first().row() );
481            else { // hm, we don't seem to have this tracker anymore...
482                const bool isBootstrapping = myTrackerModel->rowCount( ) <= 2;
483                if( !isBootstrapping )
484                    myPrefs.set( key, "" );
485            }
486            break;
487        }
488
489        case Prefs :: FILTER_TEXT:
490            myLineEdit->setText( myPrefs.getString( key ) );
491            break;
492    }
493}
494
495void
496FilterBar :: onTextChanged( const QString& str )
497{
498    if( !myIsBootstrapping )
499        myPrefs.set( Prefs::FILTER_TEXT, str.trimmed( ) );
500}
501
502void
503FilterBar :: onTrackerIndexChanged( int i )
504{
505    if( !myIsBootstrapping )
506    {
507        QString str;
508        const bool isTracker = !myTrackerCombo->itemData(i,TrackerRole).toString().isEmpty();
509        if( isTracker )
510            str = myTrackerCombo->itemData(i,TrackerRole).toString();
511        else // show all
512            str = "";
513        myPrefs.set( Prefs::FILTER_TRACKERS, str );
514    }
515}
516
517void
518FilterBar :: onActivityIndexChanged( int i )
519{
520    if( !myIsBootstrapping )
521    {
522        const FilterMode mode = myActivityCombo->itemData( i, ActivityRole ).toInt( );
523        myPrefs.set( Prefs::FILTER_MODE, mode );
524    }
525}
526
527/***
528****
529***/
530
531void FilterBar :: onTorrentModelReset( ) { recountSoon( ); }
532void FilterBar :: onTorrentModelRowsInserted( const QModelIndex&, int, int ) { recountSoon( ); }
533void FilterBar :: onTorrentModelRowsRemoved( const QModelIndex&, int, int ) { recountSoon( ); }
534void FilterBar :: onTorrentModelDataChanged( const QModelIndex&, const QModelIndex& ) { recountSoon( ); }
535
536void
537FilterBar :: recountSoon( )
538{
539    if( !myRecountTimer->isActive( ) )
540    {
541        myRecountTimer->setSingleShot( true );
542        myRecountTimer->start( 500 );
543    }
544}
545void
546FilterBar :: recount ( )
547{
548    // recount the activity combobox...
549    for( int i=0, n=FilterMode::NUM_MODES; i<n; ++i )
550    {
551        const FilterMode m( i );
552        QAbstractItemModel * model = myActivityCombo->model( );
553        QModelIndexList indices = model->match( model->index(0,0), ActivityRole, m.mode(), -1 );
554        if( !indices.isEmpty( ) ) {
555            const int count = myFilter.count( m );
556            model->setData( indices.first(), count, TorrentCountRole );
557        }
558    }
559
560    refreshTrackers( );
561}
Note: See TracBrowser for help on using the repository browser.