Make the ServiceListDelegate use less handcoded values and more general variables...
[amarok.git] / src / tracktooltip.cpp
blob6ec68fe88c82568e3b97c9c9cd31460acf7213d9
1 /*
2 This program is free software; you can redistribute it and/or modify
3 it under the terms of the GNU General Public License as published by
4 the Free Software Foundation; either version 2 of the License, or
5 (at your option) any later version.
6 */
8 /*
9 tracktooltip.cpp
10 begin: Tue 10 Feb 2004
11 copyright: (C) 2004 by Christian Muehlhaeuser
12 email: chris@chris.de
14 copyright: (C) 2005 by Gábor Lehel
15 email: illissius@gmail.com
18 #include "tracktooltip.h"
20 #include "amarokconfig.h"
21 #include "amarok.h"
22 #include "amarok_export.h"
23 #include "app.h"
24 #include "collectiondb.h"
25 #include "debug.h"
26 #include "metabundle.h"
27 #include "moodbar.h"
28 #include "podcastbundle.h"
30 #include <KCalendarSystem>
31 #include <KStandardDirs>
33 #include <QApplication>
34 #include <QPixmap>
37 namespace Amarok
39 QString AMAROK_EXPORT verboseTimeSince( const QDateTime &datetime )
41 const QDateTime now = QDateTime::currentDateTime();
42 const int datediff = datetime.daysTo( now );
44 if( datediff >= 6*7 /*six weeks*/ ) { // return absolute month/year
45 const KCalendarSystem *cal = KGlobal::locale()->calendar();
46 const QDate date = datetime.date();
47 return i18nc( "monthname year", "%1 %2", cal->monthName(date),
48 cal->yearString(date, KCalendarSystem::LongFormat) );
51 //TODO "last week" = maybe within 7 days, but prolly before last sunday
53 if( datediff >= 7 ) // return difference in weeks
54 return i18np( "One week ago", "%1 weeks ago", (datediff+3)/7 );
56 if( datediff == -1 )
57 return i18n( "Tomorrow" );
59 const int timediff = datetime.secsTo( now );
61 if( timediff >= 24*60*60 /*24 hours*/ ) // return difference in days
62 return datediff == 1 ?
63 i18n( "Yesterday" ) :
64 i18np( "One day ago", "%1 days ago", (timediff+12*60*60)/(24*60*60) );
66 if( timediff >= 90*60 /*90 minutes*/ ) // return difference in hours
67 return i18np( "One hour ago", "%1 hours ago", (timediff+30*60)/(60*60) );
69 //TODO are we too specific here? Be more fuzzy? ie, use units of 5 minutes, or "Recently"
71 if( timediff >= 0 ) // return difference in minutes
72 return timediff/60 ?
73 i18np( "One minute ago", "%1 minutes ago", (timediff+30)/60 ) :
74 i18n( "Within the last minute" );
76 return i18n( "The future" );
79 QString verboseTimeSince( uint time_t )
81 if( !time_t )
82 return i18n( "Never" );
84 QDateTime dt;
85 dt.setTime_t( time_t );
86 return verboseTimeSince( dt );
90 TrackToolTip *TrackToolTip::instance()
92 static TrackToolTip tip;
93 return &tip;
96 TrackToolTip::TrackToolTip(): m_haspos( false )
98 connect( CollectionDB::instance(), SIGNAL( coverChanged( const QString &, const QString & ) ),
99 this, SLOT( slotCoverChanged( const QString &, const QString & ) ) );
100 connect( CollectionDB::instance(), SIGNAL( imageFetched( const QString & ) ),
101 this, SLOT( slotImageChanged( const QString & ) ) );
102 // PORT 2.0 connect( Playlist::instance(), SIGNAL( columnsChanged() ), this, SLOT( slotUpdate() ) );
103 connect( CollectionDB::instance(), SIGNAL( scoreChanged( const QString&, float ) ),
104 this, SLOT( slotUpdate( const QString& ) ) );
105 connect( CollectionDB::instance(), SIGNAL( ratingChanged( const QString&, int ) ),
106 this, SLOT( slotUpdate( const QString& ) ) );
107 // Only connect this once -- m_tags exists for the lifetime of this instance
108 connect( &m_tags.moodbar(), SIGNAL( jobEvent( int ) ),
109 SLOT( slotMoodbarEvent() ) );
110 // This is so the moodbar can be re-rendered when AlterMood is changed
111 connect( App::instance(), SIGNAL( moodbarPrefs( bool, bool, int, bool ) ),
112 SLOT( slotMoodbarEvent() ) );
113 clear();
116 void TrackToolTip::addToWidget( QWidget *widget )
118 if( widget && !m_widgets.containsRef( widget ) )
120 m_widgets.append( widget );
121 Amarok::ToolTip::add( this, widget );
125 void TrackToolTip::removeFromWidget( QWidget *widget )
127 if( widget && m_widgets.containsRef( widget ) )
129 Amarok::ToolTip::remove( widget );
130 m_widgets.removeRef( widget );
135 #define MOODBAR_WIDTH 150
137 void TrackToolTip::setTrack( const MetaBundle &tags, bool force )
139 if( force || m_tags != tags || m_tags.url() != tags.url() )
141 m_haspos = false;
142 m_tooltip.clear();
144 QStringList left, right;
145 const QString tableRow = "<tr><td width=70 align=right>%1:</td><td align=left>%2</td></tr>";
147 QString filename = "", title = ""; //special case these, put the first one encountered on top
149 // Playlist *playlist = Playlist::instance();
150 // const int n = playlist->numVisibleColumns();
151 // for( int i = 0; i < n; ++i )
152 // {
153 // const int column = playlist->mapToLogicalColumn( i );
155 // if( column == PlaylistItem::Score )
156 // {
157 // const float score = CollectionDB::instance()->getSongPercentage( tags.url().path() );
158 // if( score > 0.f )
159 // {
160 // right << QString::number( score, 'f', 2 ); // 2 digits after decimal point
161 // left << playlist->columnText( column );
162 // }
163 // }
164 // else if( column == PlaylistItem::Rating )
165 // {
166 // const int rating = CollectionDB::instance()->getSongRating( tags.url().path() );
167 // if( rating > 0 )
168 // {
169 // QString s;
170 // for( int i = 0; i < rating / 2; ++i )
171 // s += QString( "<img src=\"%1\" height=\"%2\" width=\"%3\">" )
172 // .arg( KStandardDirs::locate( "data", "amarok/images/star.png" ) )
173 // .arg( QFontMetrics( QToolTip::font() ).height() )
174 // .arg( QFontMetrics( QToolTip::font() ).height() );
175 // if( rating % 2 )
176 // s += QString( "<img src=\"%1\" height=\"%2\" width=\"%3\">" )
177 // .arg( KStandardDirs::locate( "data", "amarok/images/smallstar.png" ) )
178 // .arg( QFontMetrics( QToolTip::font() ).height() )
179 // .arg( QFontMetrics( QToolTip::font() ).height() );
180 // right << s;
181 // left << playlist->columnText( column );
182 // }
183 // }
184 // else if( column == PlaylistItem::Mood )
185 // {
186 // if( !AmarokConfig::showMoodbar() )
187 // continue;
189 // m_tags.moodbar().load();
191 // switch( tags.moodbar_const().state() )
192 // {
193 // case Moodbar::JobQueued:
194 // case Moodbar::JobRunning:
195 // right << tags.prettyText( column );
196 // left << playlist->columnText( column );
197 // break;
199 // case Moodbar::Loaded:
200 // {
201 // // Ok so this is a hack, but it works quite well.
202 // // Save an image in the user's home directory just so
203 // // it can be referenced in an <img> tag. Store which
204 // // moodbar is saved in m_moodbarURL so we don't have
205 // // to re-save it every second.
206 // left << playlist->columnText( column );
207 // QString filename = KStandardDirs::locateLocal( "data",
208 // "amarok/mood_tooltip.png" );
209 // int height = QFontMetrics( QToolTip::font() ).height() - 2;
211 // if( m_moodbarURL != tags.url().url() )
212 // {
213 // QPixmap moodbar
214 // = const_cast<MetaBundle&>( tags ).moodbar().draw(
215 // MOODBAR_WIDTH, height );
216 // moodbar.save( filename, "PNG", 100 );
217 // m_moodbarURL = tags.url().url();
218 // }
220 // right << QString( "<img src=\"%1\" height=\"%2\" width=\"%3\">" )
221 // .arg( filename ).arg( height ).arg( MOODBAR_WIDTH );
222 // }
223 // break;
225 // default:
226 // // no tag
227 // break;
228 // }
229 // }
230 // else if( column == PlaylistItem::PlayCount )
231 // {
232 // const int count = CollectionDB::instance()->getPlayCount( tags.url().path() );
233 // if( count > 0 )
234 // {
235 // right << QString::number( count );
236 // left << playlist->columnText( column );
237 // }
238 // }
239 // else if( column == PlaylistItem::LastPlayed )
240 // {
241 // const uint lastPlayed = CollectionDB::instance()->getLastPlay( tags.url().path() ).toTime_t();
242 // right << Amarok::verboseTimeSince( lastPlayed );
243 // left << playlist->columnText( column );
244 // }
245 // else if( column == PlaylistItem::Filename && title.isEmpty() )
246 // filename = tags.prettyText( column );
247 // else if( column == PlaylistItem::Title && filename.isEmpty() )
248 // title = tags.prettyText( column );
249 // else if( column != PlaylistItem::Length )
250 // {
251 // const QString tag = tags.prettyText( column );
252 // if( !tag.isEmpty() )
253 // {
254 // right << tag;
255 // left << playlist->columnText( column );
256 // }
257 // }
258 // }
260 if( !filename.isEmpty() )
262 right.prepend( filename );
263 //PORT 2.0
264 // left.prepend( playlist->columnText( PlaylistItem::Filename ) );
266 else if( !title.isEmpty() )
268 right.prepend( title );
269 //PORT 2.0
270 // left.prepend( playlist->columnText( PlaylistItem::Title ) );
273 if( tags.length() > 0 ) //special case this too, always on the bottom
275 m_haspos = true;
276 right << "%9 / " + tags.prettyLength();
277 //PORT 2.0
278 // left << playlist->columnText( PlaylistItem::Length );
281 //NOTE it seems to be necessary to <center> each element indivdually
282 m_tooltip += "<center><b>Amarok</b></center><table cellpadding='2' cellspacing='2' align='center'><tr>";
284 m_tooltip += "%1"; //the cover gets substituted in, in tooltip()
285 m_cover = CollectionDB::instance()->podcastImage( tags, true );
286 if( m_cover.isEmpty() || m_cover.contains( "nocover" ) != -1 )
288 m_cover = CollectionDB::instance()->albumImage( tags, true, 150 );
289 if ( m_cover == CollectionDB::instance()->notAvailCover() )
290 m_cover.clear();
293 m_tooltip += "<td><table cellpadding='0' cellspacing='0'>";
295 if (tags.title().isEmpty() || tags.artist().isEmpty())
296 // no title or no artist, so we add prettyTitle
297 m_tooltip += QString ("<tr><td align=center colspan='2'>%1</td></tr>")
298 .arg(tags.veryNiceTitle());
299 for( int x = 0; x < left.count(); ++x )
300 if ( !right[x].isEmpty() )
301 m_tooltip += tableRow.arg( left[x] ).arg( right[x] );
303 m_tooltip += "</table></td>";
304 m_tooltip += "</tr></table></center>";
306 m_tags = tags;
307 updateWidgets();
311 void TrackToolTip::setPos( int pos )
313 if( m_pos != pos )
315 m_pos = pos;
316 updateWidgets();
320 void TrackToolTip::clear()
322 m_pos = 0;
323 m_cover.clear();
324 m_tooltip = i18n( "Amarok - rediscover your music" );
325 m_tags = MetaBundle();
326 m_tags.setUrl( KUrl() );
328 updateWidgets();
331 QPair<QString, QRect> TrackToolTip::toolTipText( QWidget*, const QPoint& ) const
333 return QPair<QString, QRect>( tooltip(), QRect() );
336 void TrackToolTip::slotCoverChanged( const QString &artist, const QString &album )
338 if( artist == m_tags.artist() && album == m_tags.album() )
340 m_cover = CollectionDB::instance()->albumImage( m_tags, true, 150 );
341 if( m_cover == CollectionDB::instance()->notAvailCover() )
342 m_cover.clear();
344 updateWidgets();
348 void TrackToolTip::slotImageChanged( const QString &remoteURL )
350 PodcastEpisodeBundle peb;
351 if( CollectionDB::instance()->getPodcastEpisodeBundle( m_tags.url().url(), &peb ) )
353 PodcastChannelBundle pcb;
354 if( CollectionDB::instance()->getPodcastChannelBundle( peb.parent().url(), &pcb ) )
356 if( pcb.imageURL().url() == remoteURL )
358 m_cover = CollectionDB::instance()->podcastImage( remoteURL );
359 if( m_cover == CollectionDB::instance()->notAvailCover() )
360 m_cover.clear();
362 updateWidgets();
369 void TrackToolTip::slotUpdate( const QString &url )
371 if( url.isNull() || url == m_tags.url().path() )
372 setTrack( m_tags, true );
375 void
376 TrackToolTip::slotMoodbarEvent( void )
378 // Clear this so the moodbar gets redrawn
379 m_moodbarURL.clear();
380 // Reset the moodbar in case AlterMood has changed
381 m_tags.moodbar().reset();
383 setTrack( m_tags, true );
387 QString TrackToolTip::tooltip() const
389 QString tip = m_tooltip;;
390 if( !m_tags.isEmpty() )
392 if( !m_cover.isEmpty() )
393 tip = tip.arg( QString( "<td><table cellpadding='0' cellspacing='0'><tr><td>"
394 "<img src='%1'>"
395 "</td></tr></table></td>" ).arg( m_cover ) );
396 else
397 tip = tip.arg("");
398 if( m_haspos )
399 tip = tip.arg( MetaBundle::prettyLength( m_pos / 1000, true ) );
401 return tip;
404 void TrackToolTip::updateWidgets()
406 Amarok::ToolTip::updateTip();
409 #include "tracktooltip.moc"