1 /***************************************************************************
2 * copyright : (C) 2005-2007 Seb Ruiz <ruiz@kde.org> *
3 **************************************************************************/
5 /***************************************************************************
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
12 ***************************************************************************/
14 #include "Statistics.h"
16 #include "amarok.h" //oldForeach macro
17 #include "amarokconfig.h"
18 #include "browserToolBar.h" //search toolbar
21 #include "playlist/PlaylistModel.h"
22 #include "querybuilder.h"
23 #include "tagdialog.h" //showContextMenu()
24 #include "TheInstances.h"
26 #include <k3multipledrag.h>
27 #include <k3urldrag.h> //startDrag()
28 #include <KApplication>
29 #include <KIconLoader>
32 #include <KWindowSystem>
36 #include <Q3PopupMenu>
37 #include <q3simplerichtext.h>
41 #include <QToolButton>
48 * Transform to be usable within HTML/HTML attributes
50 QString
escapeHTMLAttr( const QString
&s
)
52 return QString(s
).replace( "%", "%25" ).replace( "'", "%27" ).replace( "\"", "%22" ).
53 replace( "#", "%23" ).replace( "?", "%3F" );
55 QString
unescapeHTMLAttr( const QString
&s
)
57 return QString(s
).replace( "%3F", "?" ).replace( "%23", "#" ).replace( "%22", "\"" ).
58 replace( "%27", "'" ).replace( "%25", "%" );
62 * Function that must be used when separating contextBrowser escaped urls
63 * detail can contain track/discnumber
65 void albumArtistTrackFromUrl( QString url
, QString
&artist
, QString
&album
, QString
&detail
)
67 if ( !url
.contains("@@@") ) return;
68 //KHTML removes the trailing space!
69 if ( url
.endsWith( " @@@" ) )
72 const QStringList list
= url
.split( " @@@ ", QString::KeepEmptyParts
);
74 int size
= list
.count();
79 artist
= size
> 0 ? unescapeHTMLAttr( list
[0] ) : "";
80 album
= size
> 1 ? unescapeHTMLAttr( list
[1] ) : "";
81 detail
= size
> 2 ? unescapeHTMLAttr( list
[2] ) : "";
86 //////////////////////////////////////////////////////////////////////////////////////////
88 //////////////////////////////////////////////////////////////////////////////////////////
90 Statistics
*Statistics::s_instance
= 0;
92 Statistics::Statistics( QWidget
*parent
, const char *name
)
94 , m_timer( new QTimer( this ) )
98 setDefaultButton( Close
);
99 showButtonSeparator( true );
100 setObjectName( name
);
104 // Gives the window a small title bar, and skips a taskbar entry
106 KWindowSystem::setType( winId(), NET::Utility
);
107 KWindowSystem::setState( winId(), NET::SkipTaskbar
);
110 kapp
->setTopWidget( this );
111 setCaption( KDialog::makeStandardCaption( i18n("Collection Statistics") ) );
112 setInitialSize( QSize( 400, 550 ) );
114 KVBox
*box
= new KVBox( this );
115 setMainWidget( box
);
117 { //<Search LineEdit>
118 KToolBar
*bar
= new Browser::ToolBar( box
);
119 bar
->setIconDimensions( 22 ); //looks more sensible
120 bar
->setMovable( false ); //removes the ugly frame
121 bar
->setSizePolicy( QSizePolicy::Minimum
, QSizePolicy::Minimum
);
123 QToolButton
*clearButton
= new QToolButton( bar
);
124 clearButton
->setIcon( KIcon("edit-clear-locationbar") );
125 clearButton
->setToolTip( i18n( "Clear search field" ) );
127 m_lineEdit
= new KLineEdit( bar
);
128 m_lineEdit
->setClickMessage( i18n( "Enter search terms here" ) );
130 m_lineEdit
->setSizePolicy(QSizePolicy::Expanding
,QSizePolicy::Minimum
);
131 m_lineEdit
->setFrame( QFrame::Sunken
);
132 m_lineEdit
->installEventFilter( this ); //we intercept keyEvents
134 connect( clearButton
,SIGNAL( clicked() ) , m_lineEdit
, SLOT( clear() ) );
135 connect( m_timer
, SIGNAL( timeout() ) , SLOT( slotSetFilter() ) );
136 connect( m_lineEdit
, SIGNAL( textChanged( const QString
& ) ), SLOT( slotSetFilterTimeout() ) );
137 connect( m_lineEdit
, SIGNAL( returnPressed() ) , SLOT( slotSetFilter() ) );
138 } //</Search LineEdit>
140 m_listView
= new StatisticsList( box
);
143 Statistics::~Statistics()
149 Statistics::slotSetFilterTimeout() //SLOT
151 m_timer
->setSingleShot( true ); //stops the timer for us first
152 m_timer
->start( 280 );
156 Statistics::slotSetFilter() //SLOT
159 m_listView
->setFilter( m_lineEdit
->text() );
160 if( m_listView
->childCount() > 1 )
161 m_listView
->renderView();
163 m_listView
->refreshView();
167 //////////////////////////////////////////////////////////////////////////////////////////
168 /// CLASS StatisticsList
169 //////////////////////////////////////////////////////////////////////////////////////////
171 StatisticsList::StatisticsList( QWidget
*parent
, const char *name
)
172 : K3ListView( parent
)
174 , m_expanded( false )
178 addColumn( i18n("Name") );
179 setResizeMode( Q3ListView::LastColumn
);
180 setSelectionMode( Q3ListView::Extended
);
182 setObjectName( name
);
184 setAcceptDrops( false );
185 setDragEnabled( true );
187 connect( this, SIGNAL( onItem( Q3ListViewItem
*) ), SLOT( startHover( Q3ListViewItem
* ) ) );
188 connect( this, SIGNAL( onViewport() ), SLOT( clearHover() ) );
189 connect( this, SIGNAL( clicked( Q3ListViewItem
*) ), SLOT( itemClicked( Q3ListViewItem
* ) ) );
190 connect( this, SIGNAL( contextMenuRequested( Q3ListViewItem
*, const QPoint
&, int ) ),
191 this, SLOT( showContextMenu( Q3ListViewItem
*, const QPoint
&, int ) ) );
193 if( CollectionDB::instance()->isEmpty() )
200 StatisticsList::startDrag()
202 // there is only one item ever selected in this tool. maybe this needs to change
207 K3MultipleDrag
*drag
= new K3MultipleDrag( this );
209 Q3ListViewItemIterator
it( this, Q3ListViewItemIterator::Selected
);
211 StatisticsDetailedItem
*item
= dynamic_cast<StatisticsDetailedItem
*>(*it
);
216 if( item
->itemType() == StatisticsDetailedItem::TRACK
)
218 list
+= KUrl( item
->url() );
219 drag
->addDragObject( new K3URLDrag( list
, viewport() ) );
220 drag
->setPixmap( CollectionDB::createDragPixmap(list
),
221 QPoint( CollectionDB::DRAGPIXMAP_OFFSET_X
,
222 CollectionDB::DRAGPIXMAP_OFFSET_Y
) );
226 Q3TextDrag
*textdrag
= new Q3TextDrag( '\n' + item
->getSQL(), 0 );
227 textdrag
->setSubtype( "amarok-sql" );
228 drag
->addDragObject( textdrag
);
229 drag
->setPixmap( CollectionDB::createDragPixmapFromSQL( item
->getSQL() ),
230 QPoint( CollectionDB::DRAGPIXMAP_OFFSET_X
,
231 CollectionDB::DRAGPIXMAP_OFFSET_Y
) );
239 StatisticsList::refreshView()
245 error() << "Statistics: uh oh, no first child!";
248 while( firstChild()->firstChild() )
249 delete firstChild()->firstChild();
251 expandInformation( static_cast<StatisticsItem
*>(firstChild()), true /*refresh*/ );
258 StatisticsList::renderView()
262 //ensure cleanliness - this function is not just called from the ctor, but also when returning to the initial display
263 while( firstChild() )
271 qb
.addReturnFunctionValue( QueryBuilder::funcCount
, QueryBuilder::tabSong
, QueryBuilder::valURL
);
272 qb
.setOptions( QueryBuilder::optRemoveDuplicates
);
275 m_trackItem
= new StatisticsItem( i18n("Favorite Tracks"), this, 0 );
276 m_trackItem
->setSubtext( i18np("%1 track", "%1 tracks", a
[0].toInt()) );
279 qb
.addReturnFunctionValue( QueryBuilder::funcSum
, QueryBuilder::tabStats
, QueryBuilder::valPlayCounter
);
282 m_mostplayedItem
= new StatisticsItem( i18n("Most Played Tracks"), this, m_trackItem
);
283 m_mostplayedItem
->setSubtext( i18np("%1 play", "%1 plays", a
[0].toInt()) );
286 //qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabArtist, QueryBuilder::valID );
287 //qb.setOptions( QueryBuilder::optRemoveDuplicates );
289 qb
.setOptions( QueryBuilder::optRemoveDuplicates
);
290 qb
.addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valArtistID
);
291 //I can't get the correct value w/o using a subquery, and querybuilder doesn't support those
292 a
= QStringList( QString::number( qb
.run().count() ) );
294 m_artistItem
= new StatisticsItem( i18n("Favorite Artists"), this, m_mostplayedItem
);
295 m_artistItem
->setSubtext( i18np("%1 artist", "%1 artists", a
[0].toInt()) );
298 //qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabAlbum, QueryBuilder::valID );
299 //qb.setOptions( QueryBuilder::optRemoveDuplicates );
301 qb
.setOptions( QueryBuilder::optRemoveDuplicates
);
302 qb
.addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valAlbumID
);
303 //I can't get the correct value w/o using a subquery, and querybuilder doesn't support those
304 a
= QStringList( QString::number( qb
.run().count() ) );
306 m_albumItem
= new StatisticsItem( i18n("Favorite Albums"), this, m_artistItem
);
307 m_albumItem
->setSubtext( i18np("%1 album", "%1 albums", a
[0].toInt()) );
310 //qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabGenre, QueryBuilder::valID );
311 //qb.setOptions( QueryBuilder::optRemoveDuplicates );
313 qb
.setOptions( QueryBuilder::optRemoveDuplicates
);
314 qb
.addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valGenreID
);
315 //I can't get the correct value w/o using a subquery, and querybuilder doesn't support those
316 a
= QStringList(QString::number( qb
.run().count() ));
318 m_genreItem
= new StatisticsItem( i18n("Favorite Genres"), this, m_albumItem
);
319 m_genreItem
->setSubtext( i18np("%1 genre", "%1 genres", a
[0].toInt()) );
322 qb
.addReturnFunctionValue( QueryBuilder::funcMin
, QueryBuilder::tabStats
, QueryBuilder::valCreateDate
);
323 qb
.setOptions( QueryBuilder::optRemoveDuplicates
);
325 QDateTime firstPlay
= QDateTime::currentDateTime();
327 firstPlay
.setTime_t( a
[0].toUInt() );
329 m_newestItem
= new StatisticsItem( i18n("Newest Items"), this, m_genreItem
);
330 m_newestItem
->setSubtext( i18n("First played %1", Amarok::verboseTimeSince( firstPlay
) ) );
332 m_trackItem
->setIcon( Amarok::icon("track") );
333 m_mostplayedItem
->setIcon( Amarok::icon("mostplayed") );
334 m_artistItem
->setIcon( Amarok::icon("artist") );
335 m_albumItem
->setIcon( Amarok::icon("album") );
336 m_genreItem
->setIcon( Amarok::icon("favourite_genres") );
337 m_newestItem
->setIcon( Amarok::icon("clock") );
341 StatisticsList::itemClicked( Q3ListViewItem
*item
) //SLOT
346 if( item
->depth() != 0 ) //not very flexible, *shrug*
349 #define item static_cast<StatisticsItem*>(item)
351 if( item
->isExpanded() )
357 expandInformation( item
);
358 item
->setOpen( true );
364 StatisticsList::expandInformation( StatisticsItem
*item
, bool refresh
)
367 KLocale
*locale
= new KLocale( "locale" );
371 StatisticsDetailedItem
*m_last
= 0;
374 if( item
== m_trackItem
)
381 delete m_mostplayedItem
;
384 qb
.addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valTitle
);
385 qb
.addReturnValue( QueryBuilder::tabArtist
, QueryBuilder::valName
);
386 qb
.addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valURL
);
387 qb
.addReturnValue( QueryBuilder::tabStats
, QueryBuilder::valScore
);
388 qb
.addReturnValue( QueryBuilder::tabStats
, QueryBuilder::valRating
);
389 qb
.addNumericFilter( QueryBuilder::tabStats
, QueryBuilder::valForFavoriteSorting(), "0", QueryBuilder::modeGreater
);
390 qb
.setGoogleFilter( QueryBuilder::tabSong
| QueryBuilder::tabArtist
, m_filter
);
392 qb
.setLimit( 0, 50 );
393 QStringList fave
= qb
.run();
395 for( int i
=0; i
< fave
.count(); i
+= qb
.countReturnValues() )
397 QString name
= i18n("%1. %2 - %3", QString::number(c
),
398 fave
[i
].isEmpty() ? i18n( "Unknown" ) : fave
[i
],
399 fave
[i
+1].isEmpty() ? i18n( "Unknown" ) : fave
[i
+1]);
400 QString score
= locale
->formatNumber( fave
[i
+3].toDouble(), 0 );
401 QString rating
= locale
->formatNumber( fave
[i
+4].toDouble() / 2.0, 1 );
402 m_last
= new StatisticsDetailedItem( name
, subText( score
, rating
), item
, m_last
);
403 m_last
->setItemType( StatisticsDetailedItem::TRACK
);
404 m_last
->setUrl( fave
[i
+2] );
409 else if( item
== m_mostplayedItem
)
419 qb
.addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valTitle
);
420 qb
.addReturnValue( QueryBuilder::tabArtist
, QueryBuilder::valName
);
421 qb
.addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valURL
);
422 qb
.addReturnValue( QueryBuilder::tabStats
, QueryBuilder::valPlayCounter
);
423 qb
.addNumericFilter( QueryBuilder::tabStats
, QueryBuilder::valPlayCounter
, "0", QueryBuilder::modeGreater
);
424 qb
.setGoogleFilter( QueryBuilder::tabSong
| QueryBuilder::tabArtist
, m_filter
);
425 qb
.sortBy( QueryBuilder::tabStats
, QueryBuilder::valPlayCounter
, true );
426 qb
.setLimit( 0, 50 );
427 QStringList fave
= qb
.run();
429 for( int i
=0; i
< fave
.count(); i
+= qb
.countReturnValues() )
431 QString name
= i18n("%1. %2 - %3", QString::number(c
),
432 fave
[i
].isEmpty() ? i18n( "Unknown" ) : fave
[i
],
433 fave
[i
+1].isEmpty() ? i18n( "Unknown" ) : fave
[i
+1]);
434 double plays
= fave
[i
+3].toDouble();
435 QString subtext
= i18n("%1: %2", i18n( "Playcount" ), plays
);
436 m_last
= new StatisticsDetailedItem( name
, subtext
, item
, m_last
);
437 m_last
->setItemType( StatisticsDetailedItem::TRACK
);
438 m_last
->setUrl( fave
[i
+2] );
443 else if( item
== m_artistItem
)
449 delete m_mostplayedItem
;
453 qb
.addReturnValue( QueryBuilder::tabArtist
, QueryBuilder::valName
);
454 qb
.addReturnFunctionValue( QueryBuilder::funcAvg
, QueryBuilder::tabStats
, QueryBuilder::valScore
);
455 qb
.addReturnFunctionValue( QueryBuilder::funcAvg
, QueryBuilder::tabStats
, QueryBuilder::valRating
);
456 qb
.sortByFavoriteAvg();
457 // only artists with more than 3 tracks
458 qb
.having( QueryBuilder::tabArtist
, QueryBuilder::valID
, QueryBuilder::funcCount
, QueryBuilder::modeGreater
, "3" );
459 qb
.setGoogleFilter( QueryBuilder::tabArtist
, m_filter
);
460 qb
.groupBy( QueryBuilder::tabArtist
, QueryBuilder::valName
);
461 qb
.setLimit( 0, 50 );
462 QStringList fave
= qb
.run();
464 for( int i
=0; i
< fave
.count(); i
+= qb
.countReturnValues() )
466 QString name
= i18n("%1. %2", QString::number(c
), fave
[i
].isEmpty() ? i18n( "Unknown" ) : fave
[i
] );
467 QString score
= locale
->formatNumber( fave
[i
+1].toDouble(), 2 );
468 QString rating
= locale
->formatNumber( fave
[i
+2].toDouble() / 2.0, 2 );
469 m_last
= new StatisticsDetailedItem( name
, subText( score
, rating
), item
, m_last
);
470 m_last
->setItemType( StatisticsDetailedItem::ARTIST
);
471 QString url
= QString("%1").arg( fave
[i
] );
472 m_last
->setUrl( url
);
477 else if( item
== m_albumItem
)
483 delete m_mostplayedItem
;
487 qb
.addReturnValue( QueryBuilder::tabAlbum
, QueryBuilder::valName
);
488 qb
.addReturnValue( QueryBuilder::tabArtist
, QueryBuilder::valName
);
489 qb
.addReturnValue( QueryBuilder::tabArtist
, QueryBuilder::valID
);
490 qb
.addReturnValue( QueryBuilder::tabAlbum
, QueryBuilder::valID
);
491 qb
.addReturnFunctionValue( QueryBuilder::funcAvg
, QueryBuilder::tabStats
, QueryBuilder::valScore
);
492 qb
.addReturnFunctionValue( QueryBuilder::funcAvg
, QueryBuilder::tabStats
, QueryBuilder::valRating
);
493 qb
.addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valIsCompilation
);
494 // only albums with more than 3 tracks
495 qb
.having( QueryBuilder::tabAlbum
, QueryBuilder::valID
, QueryBuilder::funcCount
, QueryBuilder::modeGreater
, "3" );
496 qb
.setGoogleFilter( QueryBuilder::tabAlbum
| QueryBuilder::tabArtist
, m_filter
);
497 qb
.sortByFavoriteAvg();
498 qb
.excludeMatch( QueryBuilder::tabAlbum
, i18n( "Unknown" ) );
499 qb
.groupBy( QueryBuilder::tabAlbum
, QueryBuilder::valID
);
500 qb
.groupBy( QueryBuilder::tabAlbum
, QueryBuilder::valName
);
501 qb
.groupBy( QueryBuilder::tabArtist
, QueryBuilder::valID
);
502 qb
.groupBy( QueryBuilder::tabArtist
, QueryBuilder::valName
);
503 qb
.groupBy( QueryBuilder::tabSong
, QueryBuilder::valIsCompilation
);
505 qb
.setLimit( 0, 50 );
506 QStringList fave
= qb
.run();
508 const QString trueValue
= CollectionDB::instance()->boolT();
509 for( int i
=0; i
< fave
.count(); i
+= qb
.countReturnValues() )
511 const bool isSampler
= (fave
[i
+6] == trueValue
);
512 QString name
= i18n("%1. %2 - %3", QString::number(c
),
513 fave
[i
].isEmpty() ? i18n( "Unknown" ) : fave
[i
],
514 isSampler
? i18n( "Various Artists" ) :
515 ( fave
[i
+1].isEmpty() ? i18n( "Unknown" ) : fave
[i
+1] ) );
516 QString score
= locale
->formatNumber( fave
[i
+4].toDouble(), 2 );
517 QString rating
= locale
->formatNumber( fave
[i
+5].toDouble() / 2.0, 2 );
519 m_last
= new StatisticsDetailedItem( name
, subText( score
, rating
), item
, m_last
);
520 m_last
->setItemType( StatisticsDetailedItem::ALBUM
);
521 QString url
= QString("%1 @@@ %2").arg( isSampler
? "0" : fave
[i
+2], fave
[i
+3] );
522 m_last
->setUrl( url
);
527 else if( item
== m_genreItem
)
533 delete m_mostplayedItem
;
537 qb
.addReturnValue( QueryBuilder::tabGenre
, QueryBuilder::valName
);
538 qb
.addReturnFunctionValue( QueryBuilder::funcAvg
, QueryBuilder::tabStats
, QueryBuilder::valScore
);
539 qb
.addReturnFunctionValue( QueryBuilder::funcAvg
, QueryBuilder::tabStats
, QueryBuilder::valRating
);
540 // only genres with more than 3 tracks
541 qb
.having( QueryBuilder::tabGenre
, QueryBuilder::valID
, QueryBuilder::funcCount
, QueryBuilder::modeGreater
, "3" );
542 // only genres which have been played/rated
543 qb
.setGoogleFilter( QueryBuilder::tabGenre
, m_filter
);
544 qb
.sortByFavoriteAvg();
545 qb
.groupBy( QueryBuilder::tabGenre
, QueryBuilder::valName
);
546 qb
.setLimit( 0, 50 );
547 QStringList fave
= qb
.run();
549 for( int i
=0; i
< fave
.count(); i
+= qb
.countReturnValues() )
551 QString name
= i18n("%1. %2", QString::number(c
),
552 fave
[i
].isEmpty() ? i18n( "Unknown" ) : fave
[i
] );
553 QString score
= locale
->formatNumber( fave
[i
+1].toDouble(), 2 );
554 QString rating
= locale
->formatNumber( fave
[i
+2].toDouble() / 2.0, 2 );
556 m_last
= new StatisticsDetailedItem( name
, subText( score
, rating
), item
, m_last
);
557 m_last
->setItemType( StatisticsDetailedItem::GENRE
);
558 QString url
= QString("%1").arg( fave
[i
] );
559 m_last
->setUrl( url
);
564 else if( item
== m_newestItem
)
570 delete m_mostplayedItem
;
574 qb
.addReturnValue( QueryBuilder::tabAlbum
, QueryBuilder::valName
);
575 qb
.addReturnValue( QueryBuilder::tabArtist
, QueryBuilder::valName
);
576 qb
.addReturnValue( QueryBuilder::tabArtist
, QueryBuilder::valID
);
577 qb
.addReturnValue( QueryBuilder::tabAlbum
, QueryBuilder::valID
);
578 qb
.addReturnFunctionValue( QueryBuilder::funcMax
, QueryBuilder::tabSong
, QueryBuilder::valCreateDate
);
579 qb
.sortByFunction( QueryBuilder::funcMax
, QueryBuilder::tabSong
, QueryBuilder::valCreateDate
, true );
580 qb
.excludeMatch( QueryBuilder::tabAlbum
, i18n( "Unknown" ) );
581 qb
.setGoogleFilter( QueryBuilder::tabAlbum
| QueryBuilder::tabArtist
, m_filter
);
582 qb
.groupBy( QueryBuilder::tabAlbum
, QueryBuilder::valName
);
583 qb
.groupBy( QueryBuilder::tabAlbum
, QueryBuilder::valID
);
584 qb
.groupBy( QueryBuilder::tabArtist
, QueryBuilder::valName
);
585 qb
.groupBy( QueryBuilder::tabArtist
, QueryBuilder::valID
);
586 qb
.setOptions( QueryBuilder::optNoCompilations
); // samplers __need__ to be handled differently
587 qb
.setLimit( 0, 50 );
588 QStringList newest
= qb
.run();
590 for( int i
=0; i
< newest
.count(); i
+= qb
.countReturnValues() )
592 QString name
= i18n("%1. %2 - %3", QString::number(c
),
593 newest
[i
].isEmpty() ? i18n( "Unknown" ) : newest
[i
],
594 newest
[i
+1].isEmpty() ? i18n( "Unknown" ) : newest
[i
+1] );
595 QDateTime added
= QDateTime();
596 added
.setTime_t( newest
[i
+4].toUInt() );
597 QString subtext
= i18n("Added: %1", Amarok::verboseTimeSince( added
) );
598 m_last
= new StatisticsDetailedItem( name
, subtext
, item
, m_last
);
599 m_last
->setItemType( StatisticsDetailedItem::HISTORY
);
600 QString url
= QString("%1 @@@ %2").arg( newest
[i
+2] ).arg( newest
[i
+3] );
601 m_last
->setUrl( url
);
606 item
->setExpanded( true );
607 repaintItem( item
); // Better than ::repaint(), flickers less
611 QString
StatisticsList::subText( const QString
&score
, const QString
&rating
) //static
613 if( AmarokConfig::useScores() && AmarokConfig::useRatings() )
614 return i18n( "Score: %1 Rating: %2", score
, rating
);
615 else if( AmarokConfig::useScores() )
616 return i18n( "Score: %1", score
);
617 else if( AmarokConfig::useRatings() )
618 return i18n( "Rating: %1", rating
);
624 StatisticsList::startHover( Q3ListViewItem
*item
) //SLOT
626 if( m_currentItem
&& item
!= m_currentItem
)
627 static_cast<StatisticsItem
*>(m_currentItem
)->leaveHover();
629 if( item
->depth() != 0 )
635 static_cast<StatisticsItem
*>(item
)->enterHover();
636 m_currentItem
= item
;
640 StatisticsList::clearHover() //SLOT
643 static_cast<StatisticsItem
*>(m_currentItem
)->leaveHover();
649 StatisticsList::viewportPaintEvent( QPaintEvent
*e
)
651 if( e
) K3ListView::viewportPaintEvent( e
);
653 if( CollectionDB::instance()->isEmpty() && e
)
655 QPainter
p( viewport() );
656 QString
minimumText(i18n(
658 "<h3>Statistics</h3>"
659 "You need a collection to use statistics! "
660 "Create a collection and then start playing "
661 "tracks to accumulate data on your play habits!"
663 Q3SimpleRichText
t( minimumText
, QApplication::font() );
665 if ( t
.width()+30 >= viewport()->width() || t
.height()+30 >= viewport()->height() )
669 const uint w
= t
.width();
670 const uint h
= t
.height();
671 const uint x
= (viewport()->width() - w
- 30) / 2 ;
672 const uint y
= (viewport()->height() - h
- 30) / 2 ;
674 p
.setBrush( palette().background() );
675 p
.drawRoundRect( x
, y
, w
+30, h
+30, (8*200)/w
, (8*200)/h
);
676 t
.draw( &p
, x
+15, y
+15, QRect(), palette() );
681 StatisticsList::showContextMenu( Q3ListViewItem
*item
, const QPoint
&p
, int ) //SLOT
683 if( !item
|| item
->rtti() == StatisticsItem::RTTI
) return;
685 #define item static_cast<StatisticsDetailedItem*>(item)
687 bool hasSQL
= !( item
->itemType() == StatisticsDetailedItem::TRACK
); //track is url
689 Q3PopupMenu
menu( this );
690 enum Actions
{ APPEND
, QUEUE
, INFO
};
692 menu
.insertItem( KIcon( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND
);
693 menu
.insertItem( KIcon( Amarok::icon( "queue_track" ) ), i18n( "&Queue Track" ), QUEUE
);
697 menu
.insertItem( KIcon( Amarok::icon( "info" ) ), i18nc( "[only-singular]", "Edit Track &Information..." ), INFO
);
701 // switch( menu.exec( p ) )
705 // Playlist::instance()->insertMediaSql( item->getSQL() ):
706 // The::playlistModel()->insertMedia( KUrl( item->url() ) );
711 // Playlist::instance()->insertMediaSql( item->getSQL(), Playlist::Queue ):
712 // The::playlistModel()->insertMedia( KUrl( item->url() ), Playlist::Queue );
718 // TagDialog* dialog = new TagDialog( item->getURLs(), Statistics::instance() );
723 // TagDialog* dialog = new TagDialog( KUrl( item->url() ), Statistics::instance() );
730 //////////////////////////////////////////////////////////////////////////////////////////
731 /// CLASS StatisticsItem
732 //////////////////////////////////////////////////////////////////////////////////////////
734 StatisticsItem::StatisticsItem( QString text
, StatisticsList
*parent
, K3ListViewItem
*after
, const char *name
)
735 : K3ListViewItem( static_cast<K3ListView
*>(parent
), after
, name
)
736 , m_animTimer( new QTimer( this ) )
738 , m_isActive( false )
739 , m_isExpanded( false )
741 setDragEnabled( false );
742 setDropEnabled( false );
743 setSelectable( false );
747 connect( m_animTimer
, SIGNAL( timeout() ), this, SLOT( slotAnimTimer() ) );
751 StatisticsItem::setIcon( const QString
&icon
)
753 QString path
= KIconLoader::global()->iconPath( icon
, -KIconLoader::SizeHuge
);
754 path
.replace( "32x32", "48x48" ); //HACK fucking KIconLoader only returns 32x32 max. Why?
756 // debug() << "ICONPATH: " << path;
758 setPixmap( 0, path
);
762 StatisticsItem::enterHover()
767 m_animTimer
->start( ANIM_INTERVAL
);
771 StatisticsItem::leaveHover()
773 // This can happen if you enter and leave the tab quickly
774 if( m_animCount
== 0 )
779 m_animTimer
->start( ANIM_INTERVAL
);
783 StatisticsItem::slotAnimTimer()
788 listView()->repaintItem( this ); // Better than ::repaint(), flickers less
790 if( m_animCount
>= ANIM_MAX
)
796 listView()->repaintItem( this );
797 if( m_animCount
<= 0 )
806 StatisticsItem::paintCell( QPainter
*p
, const QColorGroup
&cg
, int column
, int width
, int )
808 QColor fillColor
, textColor
;
810 if( m_isActive
) //glowing animation
812 fillColor
= blendColors( cg
.background(), cg
.highlight(), static_cast<int>( m_animCount
* 3.5 ) );
813 textColor
= blendColors( cg
.text(), cg
.highlightedText(), static_cast<int>( m_animCount
* 4.5 ) );
815 else //alternate colours
817 fillColor
= isSelected() ? cg
.highlight() : backgroundColor(0);
818 textColor
= isSelected() ? cg
.highlightedText() : cg
.text();
821 p
->fillRect( 0, 0, width
, height(), fillColor
);
823 K3ListView
*lv
= static_cast<K3ListView
*>( listView() );
825 QFont
font( p
->font() );
826 font
.setBold( true );
827 QFontMetrics
fm( p
->fontMetrics() );
829 int textHeight
= height();
832 p
->setPen( textColor
);
834 if( pixmap( column
) )
836 int y
= (textHeight
- pixmap(column
)->height())/2;
837 p
->drawPixmap( 0, y
, *pixmap(column
) );
838 text_x
+= pixmap(column
)->width() + 4;
842 QFontMetrics
fmName( font
);
844 QString name
= text(column
);
845 // we need to take into account that the item is indented, hence the itemMargin.
846 const int _width
= width
- text_x
- lv
->itemMargin()*2;
847 name
= fmName
.elidedText( name
, Qt::ElideRight
, _width
);
849 p
->drawText( text_x
, 0, width
, textHeight
, Qt::AlignVCenter
, name
);
851 if( !m_subText
.isEmpty() )
853 font
.setBold( false );
856 p
->drawText( text_x
, fmName
.height() + 1, width
, textHeight
, Qt::AlignVCenter
, m_subText
);
861 QPen
pen( cg
.highlight(), 1 );
863 int y
= textHeight
- 1;
864 p
->drawLine( 0, y
, width
, y
);
869 StatisticsItem::blendColors( const QColor
& color1
, const QColor
& color2
, int percent
)
871 const float factor1
= ( 100 - ( float ) percent
) / 100;
872 const float factor2
= ( float ) percent
/ 100;
874 const int r
= static_cast<int>( color1
.red() * factor1
+ color2
.red() * factor2
);
875 const int g
= static_cast<int>( color1
.green() * factor1
+ color2
.green() * factor2
);
876 const int b
= static_cast<int>( color1
.blue() * factor1
+ color2
.blue() * factor2
);
879 result
.setRgb( r
, g
, b
);
884 //////////////////////////////////////////////////////////////////////////////////////////
885 /// CLASS StatisticsDetailedItem
886 //////////////////////////////////////////////////////////////////////////////////////////
888 StatisticsDetailedItem::StatisticsDetailedItem( const QString
&text
, const QString
&subtext
, StatisticsItem
*parent
,
889 StatisticsDetailedItem
*after
, const char *name
)
890 : K3ListViewItem( parent
, after
, name
)
892 , m_subText( subtext
)
894 setDragEnabled( true );
895 setDropEnabled( false );
896 setSelectable( true );
902 StatisticsDetailedItem::paintCell( QPainter
*p
, const QColorGroup
&cg
, int column
, int width
, int )
904 bool showDetails
= !m_subText
.isEmpty();
906 // use alternate background
907 p
->fillRect( 0, 0, width
, height(), isSelected() ? cg
.highlight() : backgroundColor() );
909 K3ListView
*lv
= static_cast<K3ListView
*>( listView() );
911 QFont
font( p
->font() );
912 QFontMetrics
fm( p
->fontMetrics() );
918 textHeight
= fm
.lineSpacing() + lv
->itemMargin() + 1;
920 textHeight
= height();
922 p
->setPen( isSelected() ? cg
.highlightedText() : cg
.text() );
924 if( pixmap( column
) )
926 int y
= (textHeight
- pixmap(column
)->height())/2;
927 if( showDetails
) y
++;
928 p
->drawPixmap( text_x
, y
, *pixmap(column
) );
929 text_x
+= pixmap(column
)->width() + 4;
933 QFontMetrics
fmName( font
);
935 QString name
= text(column
);
936 const int _width
= width
- text_x
- lv
->itemMargin()*2;
937 name
= fmName
.elidedText( name
, Qt::ElideRight
, _width
);
939 p
->drawText( text_x
, 0, width
, textHeight
, Qt::AlignVCenter
, name
);
943 const QColorGroup _cg
= listView()->palette().disabled();
944 text_x
= lv
->treeStepSize() + 3;
945 font
.setItalic( true );
946 p
->setPen( isSelected() ? _cg
.highlightedText() : _cg
.text().dark() );
947 p
->drawText( text_x
, textHeight
, width
, fm
.lineSpacing(), Qt::AlignVCenter
, m_subText
);
952 StatisticsDetailedItem::setup()
954 QFontMetrics
fm( listView()->font() );
955 int margin
= listView()->itemMargin()*2;
956 int h
= fm
.lineSpacing();
959 if( !m_subText
.isEmpty() )
960 setHeight( h
+ fm
.lineSpacing() + margin
);
962 setHeight( h
+ margin
);
966 StatisticsDetailedItem::getSQL()
969 QString query
= QString();
970 QString artist
, album
, track
; // track is unused here
971 Amarok::albumArtistTrackFromUrl( url(), artist
, album
, track
);
973 if( itemType() == StatisticsDetailedItem::ALBUM
|| itemType() == StatisticsDetailedItem::HISTORY
)
977 qb
.addMatch( QueryBuilder::tabSong
, QueryBuilder::valArtistID
, artist
);
978 qb
.addMatch( QueryBuilder::tabSong
, QueryBuilder::valAlbumID
, album
);
979 qb
.sortBy( QueryBuilder::tabSong
, QueryBuilder::valTrack
);
982 else if( itemType() == StatisticsDetailedItem::ARTIST
)
984 const uint artist_id
= CollectionDB::instance()->artistID( url() );
987 qb
.addMatch( QueryBuilder::tabSong
, QueryBuilder::valArtistID
, QString::number( artist_id
) );
988 qb
.sortBy( QueryBuilder::tabSong
, QueryBuilder::valTrack
);
991 else if( itemType() == StatisticsDetailedItem::GENRE
)
993 const uint genre_id
= CollectionDB::instance()->genreID( url() );
996 qb
.addMatch( QueryBuilder::tabSong
, QueryBuilder::valGenreID
, QString::number( genre_id
) );
997 qb
.sortBy( QueryBuilder::tabSong
, QueryBuilder::valTrack
);
999 debug() << "DetailedStatisticsItem: query is: " << qb
.query();
1005 StatisticsDetailedItem::getURLs()
1007 if( itemType() == StatisticsDetailedItem::TRACK
)
1008 return KUrl::List( KUrl(url()) );
1011 QString query
= QString();
1012 QString artist
, album
, track
; // track is unused here
1013 Amarok::albumArtistTrackFromUrl( m_url
, artist
, album
, track
);
1015 qb
.addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valURL
);
1017 if( itemType() == StatisticsDetailedItem::ALBUM
|| itemType() == StatisticsDetailedItem::HISTORY
)
1019 if ( artist
!= "0" )
1020 qb
.addMatch( QueryBuilder::tabSong
, QueryBuilder::valArtistID
, artist
);
1021 qb
.addMatch( QueryBuilder::tabSong
, QueryBuilder::valAlbumID
, album
);
1022 qb
.sortBy( QueryBuilder::tabSong
, QueryBuilder::valTrack
);
1025 else if( itemType() == StatisticsDetailedItem::ARTIST
)
1027 const uint artist_id
= CollectionDB::instance()->artistID( url() );
1028 qb
.addMatch( QueryBuilder::tabSong
, QueryBuilder::valArtistID
, QString::number( artist_id
) );
1029 qb
.sortBy( QueryBuilder::tabSong
, QueryBuilder::valTrack
);
1032 else if( itemType() == StatisticsDetailedItem::GENRE
)
1034 const uint genre_id
= CollectionDB::instance()->genreID( url() );
1035 qb
.addMatch( QueryBuilder::tabSong
, QueryBuilder::valGenreID
, QString::number( genre_id
) );
1036 qb
.sortBy( QueryBuilder::tabSong
, QueryBuilder::valTrack
);
1039 QStringList values
= qb
.run();
1041 oldForeach( values
)
1042 urls
+= KUrl( *it
);
1046 #include "Statistics.moc"