/*************************************************************************** playlistitem.cpp - description ------------------- begin : Die Dez 3 2002 copyright : (C) 2002 by Mark Kretschmann email : markey@web.de copyright : (C) 2005 by Alexandre Oliveira email : aleprj@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #define DEBUG_PREFIX "PlaylistItem" #include #include "amarok.h" #include "amarokconfig.h" #include "collectiondb.h" #include "debug.h" #include "enginecontroller.h" #include "playlist.h" #include "sliderwidget.h" #include "starmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "playlistitem.h" double PlaylistItem::glowIntensity; TQColor PlaylistItem::glowText = TQt::white; TQColor PlaylistItem::glowBase = TQt::white; bool PlaylistItem::s_pixmapChanged = false; PlaylistItem::PlaylistItem( TQListView *listview, TQListViewItem *item ) : KListViewItem( listview, item ) , m_album( 0 ) { KListViewItem::setVisible( false ); } PlaylistItem::PlaylistItem( const MetaBundle &bundle, TQListViewItem *lvi, bool enabled ) : MetaBundle( bundle ), KListViewItem( lvi->listView(), lvi->itemAbove() ) , m_album( 0 ) , m_deleteAfterEdit( false ) , m_isBeingRenamed( false ) , m_isNew( true ) { setDragEnabled( true ); Playlist::instance()->m_urlIndex.add( this ); if( !uniqueId().isEmpty() ) Playlist::instance()->addToUniqueMap( uniqueId(), this ); refAlbum(); incrementCounts(); incrementLengths(); filter( listView()->m_filter ); listView()->countChanged(); setAllCriteriaEnabled( enabled ); } PlaylistItem::~PlaylistItem() { if( isEmpty() ) //constructed with the generic constructor, for PlaylistLoader's marker item return; decrementCounts(); decrementLengths(); derefAlbum(); listView()->countChanged(); if( listView()->m_hoveredRating == this ) listView()->m_hoveredRating = 0; Playlist::instance()->removeFromUniqueMap( uniqueId(), this ); Playlist::instance()->m_urlIndex.remove(this); } ///////////////////////////////////////////////////////////////////////////////////// // PUBLIC METHODS ///////////////////////////////////////////////////////////////////////////////////// void PlaylistItem::setText( int column, const TQString &text ) { if( column == Rating ) setExactText( column, TQString::number( int( text.toFloat() * 2 ) ) ); else setExactText( column, text ); } TQString PlaylistItem::text( int column ) const { if( column == Title && listView()->header()->sectionSize( Filename ) ) //don't show the filename twice return exactText( column ); else switch ( column ) { case Artist: case Composer: case Album: case Genre: case Comment: return exactText( column ); //HACK case Rating: return isEditing( column ) ? exactText( column ) : prettyText( column ); default: { if( column != Title && isEditing( column ) ) return editingText(); else return prettyText( column ); } } } void PlaylistItem::aboutToChange( const TQValueList &columns ) { bool totals = false, ref = false, length = false, url = false; for( int i = 0, n = columns.count(); i < n; ++i ) switch( columns[i] ) { case Length: length = true; break; case Artist: case Album: ref = true; //note, no breaks case Track: case Rating: case Score: case LastPlayed: totals = true; break; case Filename: case Directory: url = true; break; } if ( length ) decrementLengths(); if( totals ) decrementTotals(); if( ref ) derefAlbum(); if ( url ) Playlist::instance()->m_urlIndex.remove(this); } void PlaylistItem::reactToChanges( const TQValueList &columns ) { MetaBundle::reactToChanges(columns); bool totals = false, ref = false, length = false, url = false; for( int i = 0, n = columns.count(); i < n; ++i ) { if( columns[i] == Mood ) moodbar().reset(); if ( !length && columns[i] == Length ) { length = true; incrementLengths(); listView()->countChanged(); } switch( columns[i] ) { case Artist: case Album: ref = true; //note, no breaks case Track: case Rating: case Score: case LastPlayed: totals = true; break; case Filename: case Directory: url = true; } updateColumn( columns[i] ); } if ( url ) Playlist::instance()->m_urlIndex.add(this); if( ref ) refAlbum(); if( totals ) incrementTotals(); } void PlaylistItem::filter( const TQString &expression ) { setVisible( matchesExpression( expression, listView()->visibleColumns() ) ); } bool PlaylistItem::isCurrent() const { return this == listView()->currentTrack(); } bool PlaylistItem::isQueued() const { return queuePosition() != -1; } int PlaylistItem::queuePosition() const { return listView()->m_nextTracks.findRef( this ); } void PlaylistItem::setEnabled() { m_enabled = m_filestatusEnabled && m_dynamicEnabled; setDropEnabled( m_enabled ); // this forbids items to be dropped into a history queue. update(); } void PlaylistItem::setDynamicEnabled( bool enabled ) { m_dynamicEnabled = enabled; setEnabled(); } void PlaylistItem::setFilestatusEnabled( bool enabled ) { m_filestatusEnabled = enabled; checkExists(); setEnabled(); } void PlaylistItem::setAllCriteriaEnabled( bool enabled ) { m_filestatusEnabled = enabled; m_dynamicEnabled = enabled; checkExists(); setEnabled(); } void PlaylistItem::setSelected( bool selected ) { if( isEmpty() ) return; if( isVisible() ) { const bool prevSelected = isSelected(); KListViewItem::setSelected( selected ); if( prevSelected && !isSelected() ) { listView()->m_selCount--; listView()->m_selLength -= length(); listView()->countChanged(); } else if( !prevSelected && isSelected() ) { listView()->m_selCount++; listView()->m_selLength += length(); listView()->countChanged(); } } } void PlaylistItem::setVisible( bool visible ) { if( isEmpty() ) return; if( !visible && isSelected() ) { listView()->m_selCount--; listView()->m_selLength -= length(); KListViewItem::setSelected( false ); listView()->countChanged(); } const bool prevVisible = isVisible(); KListViewItem::setVisible( visible ); if( prevVisible && !isVisible() ) { listView()->m_visCount--; listView()->m_visLength -= length(); listView()->countChanged(); decrementTotals(); } else if( !prevVisible && isVisible() ) { listView()->m_visCount++; listView()->m_visLength += length(); listView()->countChanged(); incrementTotals(); } } void PlaylistItem::setEditing( int column ) { switch( column ) { case Title: case Artist: case Composer: case Album: case Genre: case Comment: setExactText( column, editingText() ); break; case Year: m_year = -1; break; case DiscNumber: m_discNumber = -1; break; case Track: m_track = -1; break; case Bpm: m_bpm = -1; break; case Length: m_length = -1; break; case Bitrate: m_bitrate = -1; break; case SampleRate: m_sampleRate = -1; break; case Score: m_score = -1; break; case Rating: m_rating = -1; break; case PlayCount: m_playCount = -1; break; case LastPlayed: m_lastPlay = 1; break; default: warning() << "Tried to set the text of an immutable or nonexistent column!" << endl; } update(); } bool PlaylistItem::isEditing( int column ) const { switch( column ) { case Title: case Artist: case Composer: case Album: case Genre: case Comment: //FIXME fix this hack! return exactText( column ) == editingText(); case Year: return m_year == -1; case DiscNumber: return m_discNumber == -1; case Track: return m_track == -1; case Bpm: return m_bpm == -1; case Length: return m_length == -1; case Bitrate: return m_bitrate == -1; case SampleRate: return m_sampleRate == -1; case Score: return m_score == -1; case Rating: return m_rating == -1; case PlayCount: return m_playCount == -1; case LastPlayed: return m_lastPlay == 1; default: return false; } } bool PlaylistItem::anyEditing() const { for( int i = 0; i < NUM_COLUMNS; i++ ) { if( isEditing( i ) ) return true; } return false; } int PlaylistItem::ratingAtPoint( int x ) //static { Playlist* const pl = Playlist::instance(); x -= pl->header()->sectionPos( Rating ); return kClamp( ( x - 1 ) / ( StarManager::instance()->getGreyStar()->width() + pl->itemMargin() ) + 1, 1, 5 ) * 2; } int PlaylistItem::ratingColumnWidth() //static { return StarManager::instance()->getGreyStar()->width() * 5 + Playlist::instance()->itemMargin() * 6; } void PlaylistItem::update() const { listView()->repaintItem( this ); } void PlaylistItem::updateColumn( int column ) const { const TQRect r = listView()->itemRect( this ); if( !r.isValid() ) return; listView()->viewport()->update( listView()->header()->sectionPos( column ) - listView()->contentsX() + 1, r.y() + 1, listView()->header()->sectionSize( column ) - 2, height() - 2 ); } bool PlaylistItem::operator== ( const PlaylistItem & item ) const { return item.url() == this->url(); } bool PlaylistItem::operator< ( const PlaylistItem & item ) const { return item.url() < this->url(); } PlaylistItem* PlaylistItem::nextInAlbum() const { if( !m_album ) return 0; const int index = m_album->tracks.findRef( this ); if( index == int(m_album->tracks.count() - 1) ) return 0; if( index != -1 ) return m_album->tracks.at( index + 1 ); if( track() ) for( int i = 0, n = m_album->tracks.count(); i < n; ++i ) if( m_album->tracks.at( i )->discNumber() > discNumber() || ( m_album->tracks.at( i )->discNumber() == discNumber() && m_album->tracks.at( i )->track() > track() ) ) return m_album->tracks.at( i ); else for( TQListViewItemIterator it( const_cast(this), TQListViewItemIterator::Visible ); *it; ++it ) #define pit static_cast( *it ) if( pit != this && pit->m_album == m_album && !pit->track() ) return pit; #undef pit return 0; } PlaylistItem* PlaylistItem::prevInAlbum() const { if( !m_album ) return 0; const int index = m_album->tracks.findRef( this ); if( index == 0 ) return 0; if( index != -1 ) return m_album->tracks.at( index - 1 ); if( track() ) for( int i = m_album->tracks.count() - 1; i >= 0; --i ) if( m_album->tracks.at( i )->track() && ( m_album->tracks.at( i )->discNumber() < discNumber() || ( m_album->tracks.at( i )->discNumber() == discNumber() && m_album->tracks.at( i )->track() < track() ) ) ) return m_album->tracks.at( i ); else for( TQListViewItemIterator it( const_cast(this), TQListViewItemIterator::Visible ); *it; --it ) #define pit static_cast( *it ) if( pit != this && pit->m_album == m_album && !pit->track() ) return pit; #undef pit return 0; } ///////////////////////////////////////////////////////////////////////////////////// // PRIVATE METHODS ///////////////////////////////////////////////////////////////////////////////////// int PlaylistItem::compare( TQListViewItem *i, int col, bool ascending ) const { #define i static_cast(i) if( Playlist::instance()->dynamicMode() && (isEnabled() != i->isEnabled()) ) return isEnabled() ? 1 : -1; //damn C++ and its lack of operator<=> #define cmp(a,b) ( (a < b ) ? -1 : ( a > b ) ? 1 : 0 ) switch( col ) { case Track: return cmp( track(), i->track() ); case Score: return cmp( score(), i->score() ); case Rating: return cmp( rating(), i->rating() ); case Length: return cmp( length(), i->length() ); case PlayCount: return cmp( playCount(), i->playCount() ); case LastPlayed: return cmp( lastPlay(), i->lastPlay() ); case Bitrate: return cmp( bitrate(), i->bitrate() ); case Bpm: return cmp( bpm(), i->bpm() ); case Filesize: return cmp( filesize(), i->filesize() ); case Mood: return cmp( moodbar_const().hueSort(), i->moodbar_const().hueSort() ); case Year: if( year() == i->year() ) return compare( i, Artist, ascending ); return cmp( year(), i->year() ); case DiscNumber: if( discNumber() == i->discNumber() ) return compare( i, Track, true ) * (ascending ? 1 : -1); return cmp( discNumber(), i->discNumber() ); } #undef cmp #undef i TQString a = text( col ).lower(); TQString b = i->text( col ).lower(); switch( col ) { case Type: a = a.rightJustify( b.length(), '0' ); b = b.rightJustify( a.length(), '0' ); break; case Artist: if( a == b ) //if same artist, try to sort by album return compare( i, Album, ascending ); else { if( a.startsWith( "the ", false ) ) a = a.mid( 4 ); if( b.startsWith( "the ", false ) ) b = b.mid( 4 ); } break; case Album: if( a == b ) //if same album, try to sort by track //TODO only sort in ascending order? return compare( i, DiscNumber, true ) * (ascending ? 1 : -1); break; } return TQString::localeAwareCompare( a, b ); } void PlaylistItem::paintCell( TQPainter *painter, const TQColorGroup &cg, int column, int width, int align ) { //TODO add spacing on either side of items //p->translate( 2, 0 ); width -= 3; // Don't try to draw if width or height is 0, as this crashes TQt if( !painter || !listView() || width <= 0 || height() == 0 ) return; static const TQImage currentTrackLeft = locate( "data", "amarok/images/currenttrack_bar_left.png" ); static const TQImage currentTrackMid = locate( "data", "amarok/images/currenttrack_bar_mid.png" ); static const TQImage currentTrackRight = locate( "data", "amarok/images/currenttrack_bar_right.png" ); if( column == Mood && !moodbar().dataExists() ) moodbar().load(); // Only has an effect the first time // The moodbar column can have text in it, like "Calculating". // moodbarType is 0 if column != Mood, 1 if we're displaying // a moodbar, and 2 if we're displaying text const int moodbarType = column != Mood ? 0 : moodbar().state() == Moodbar::Loaded ? 1 : 2; const TQString colText = text( column ); const bool isCurrent = this == listView()->currentTrack(); TQPixmap buf( width, height() ); TQPainter p( &buf, true ); if( isCurrent ) { static paintCacheItem paintCache[NUM_COLUMNS]; // Convert intensity to string, so we can use it as a key const TQString colorKey = TQString::number( glowIntensity ); const bool cacheValid = paintCache[column].width == width && paintCache[column].height == height() && paintCache[column].text == colText && paintCache[column].font == painter->font() && paintCache[column].color == glowBase && paintCache[column].selected == isSelected() && !s_pixmapChanged; // If any parameter changed, we must regenerate all pixmaps if ( !cacheValid ) { for( int i = 0; i < NUM_COLUMNS; ++i) paintCache[i].map.clear(); s_pixmapChanged = false; } // Determine if we need to repaint the pixmap, or paint from cache if ( paintCache[column].map.find( colorKey ) == paintCache[column].map.end() ) { // Update painting cache paintCache[column].width = width; paintCache[column].height = height(); paintCache[column].text = colText; paintCache[column].font = painter->font(); paintCache[column].color = glowBase; paintCache[column].selected = isSelected(); TQColor bg; if( isSelected() ) bg = listView()->colorGroup().highlight(); else bg = isAlternate() ? listView()->alternateBackground() : listView()->viewport()->backgroundColor(); buf.fill( bg ); // Draw column divider line p.setPen( listView()->viewport()->colorGroup().mid() ); p.drawLine( width - 1, 0, width - 1, height() - 1 ); // Here we draw the background bar graphics for the current track: // // Illustration of design, L = Left, M = Middle, R = Right: // int leftOffset = 0; int rightOffset = 0; int margin = listView()->itemMargin(); const float colorize = 0.8; const double intensity = 1.0 - glowIntensity * 0.021; // Left part if( column == listView()->m_firstColumn ) { TQImage tmpImage = currentTrackLeft.smoothScale( 1, height(), TQ_ScaleMax ); KIconEffect::colorize( tmpImage, glowBase, colorize ); imageTransparency( tmpImage, intensity ); p.drawImage( 0, 0, tmpImage, 0, 0, tmpImage.width() - 1 ); //HACK leftOffset = tmpImage.width() - 1; //HACK Subtracting 1, to work around the black line bug margin += 6; } // Right part else if( column == Playlist::instance()->mapToLogicalColumn( Playlist::instance()->numVisibleColumns() - 1 ) ) { TQImage tmpImage = currentTrackRight.smoothScale( 1, height(), TQ_ScaleMax ); KIconEffect::colorize( tmpImage, glowBase, colorize ); imageTransparency( tmpImage, intensity ); p.drawImage( width - tmpImage.width(), 0, tmpImage ); rightOffset = tmpImage.width(); margin += 6; } // Middle part // Here we scale the one pixel wide middel image to stretch to the full column width. TQImage tmpImage = currentTrackMid.copy(); KIconEffect::colorize( tmpImage, glowBase, colorize ); imageTransparency( tmpImage, intensity ); tmpImage = tmpImage.smoothScale( width - leftOffset - rightOffset, height() ); p.drawImage( leftOffset, 0, tmpImage ); // Draw the pixmap, if present int leftMargin = margin; if ( pixmap( column ) ) { p.drawPixmap( leftMargin, height() / 2 - pixmap( column )->height() / 2, *pixmap( column ) ); leftMargin += pixmap( column )->width() + 2; } if( align != TQt::AlignCenter ) align |= TQt::AlignVCenter; if( column != Rating && moodbarType != 1 ) { // Draw the text static TQFont font; static int minbearing = 1337 + 666; if( minbearing == 2003 || font != painter->font() ) { font = painter->font(); minbearing = painter->fontMetrics().minLeftBearing() + painter->fontMetrics().minRightBearing(); } const bool italic = font.italic(); int state = EngineController::engine()->state(); if( state == Engine::Playing || state == Engine::Paused ) font.setItalic( !italic ); p.setFont( font ); p.setPen( cg.highlightedText() ); // paint.setPen( glowText ); const int _width = width - leftMargin - margin + minbearing - 1; // -1 seems to be necessary const TQString _text = KStringHandler::rPixelSqueeze( colText, painter->fontMetrics(), _width ); p.drawText( leftMargin, 0, _width, height(), align, _text ); font.setItalic( italic ); p.setFont( font ); } paintCache[column].map[colorKey] = buf; } else p.drawPixmap( 0, 0, paintCache[column].map[colorKey] ); if( column == Rating ) drawRating( &p ); if( moodbarType == 1 ) drawMood( &p, width, height() ); } else { const TQColorGroup _cg = ( !exists() || !isEnabled() ) ? listView()->palette().disabled() : listView()->palette().active(); TQColor bg = isSelected() ? _cg.highlight() : isAlternate() ? listView()->alternateBackground() : listView()->viewport()->backgroundColor(); #if KDE_IS_VERSION( 3, 3, 91 ) if( listView()->shadeSortColumn() && !isSelected() && listView()->columnSorted() == column ) { /* from klistview.cpp Copyright (C) 2000 Reginald Stadlbauer Copyright (C) 2000,2003 Charles Samuels Copyright (C) 2000 Peter Putzer */ if ( bg == TQt::black ) bg = TQColor(55, 55, 55); // dark gray else { int h,s,v; bg.hsv(&h, &s, &v); if ( v > 175 ) bg = bg.dark(104); else bg = bg.light(120); } } #endif const TQColor textc = isSelected() ? _cg.highlightedText() : _cg.text(); buf.fill( bg ); // Draw column divider line if( !isSelected() ) { p.setPen( listView()->viewport()->colorGroup().mid() ); p.drawLine( width - 1, 0, width - 1, height() - 1 ); } // Draw the pixmap, if present int margin = listView()->itemMargin(), leftMargin = margin; if ( pixmap( column ) ) { p.drawPixmap( leftMargin, height() / 2 - pixmap( column )->height() / 2, *pixmap( column ) ); leftMargin += pixmap( column )->width(); } if( align != TQt::AlignCenter ) align |= TQt::AlignVCenter; if( column == Rating ) drawRating( &p ); else if( moodbarType == 1 ) drawMood( &p, width, height() ); else { // Draw the text static TQFont font; static int minbearing = 1337 + 666; //can be 0 or negative, 2003 is less likely if( minbearing == 2003 || font != painter->font() ) { font = painter->font(); //getting your bearings can be expensive, so we cache them minbearing = painter->fontMetrics().minLeftBearing() + painter->fontMetrics().minRightBearing(); } p.setFont( font ); p.setPen( ( m_isNew && isEnabled() && !isSelected() ) ? AmarokConfig::newPlaylistItemsColor() : textc ); const int _width = width - leftMargin - margin + minbearing - 1; // -1 seems to be necessary const TQString _text = KStringHandler::rPixelSqueeze( colText, painter->fontMetrics(), _width ); p.drawText( leftMargin, 0, _width, height(), align, _text ); } } /// Track action symbols const int queue = listView()->m_nextTracks.findRef( this ) + 1; const bool stop = ( this == listView()->m_stopAfterTrack ); const bool repeat = Amarok::repeatTrack() && isCurrent; const uint num = ( queue ? 1 : 0 ) + ( stop ? 1 : 0 ) + ( repeat ? 1 : 0 ); static const TQPixmap pixstop = Amarok::getPNG( "currenttrack_stop_small" ), pixrepeat = Amarok::getPNG( "currenttrack_repeat_small" ); //figure out if we are in the actual physical first column if( column == listView()->m_firstColumn && num ) { //margin, height const uint m = 2, h = height() - m; const TQString str = TQString::number( queue ); const uint qw = painter->fontMetrics().width( str ), sw = pixstop.width(), rw = pixrepeat.width(), qh = painter->fontMetrics().height(), sh = pixstop.height(), rh = pixrepeat.height(); //maxwidth const uint mw = kMax( qw, kMax( rw, sw ) ); //width of first & second column of pixmaps const uint w1 = ( num == 3 ) ? kMax( qw, rw ) : ( num == 2 && isCurrent ) ? kMax( repeat ? rw : 0, kMax( stop ? sw : 0, queue ? qw : 0 ) ) : ( num == 2 ) ? qw : queue ? qw : repeat ? rw : stop ? sw : 0, w2 = ( num == 3 ) ? sw : ( num == 2 && !isCurrent ) ? sw : 0; //phew //ellipse width, total width const uint ew = 16, tw = w1 + w2 + m * ( w2 ? 2 : 1 ); p.setBrush( cg.highlight() ); p.setPen( cg.highlight().dark() ); //TODO blend with background color p.drawEllipse( width - tw - ew/2, m / 2, ew, h ); p.drawRect( width - tw, m / 2, tw, h ); p.setPen( cg.highlight() ); p.drawLine( width - tw, m/2 + 1, width - tw, h - m/2 ); int x = width - m - mw, y = height() / 2, tmp = 0; const bool multi = ( isCurrent && num >= 2 ); if( queue ) { //draw the shadowed inner text //NOTE we can't set an arbituary font size or family, these settings are already optional //and user defaults should also take presidence if no playlist font has been selected //const TQFont smallFont( "Arial", (playNext > 9) ? 9 : 12 ); //p->setFont( smallFont ); //TODO the shadow is hard to do well when using a dark font color //TODO it also looks cluttered for small font sizes //p->setPen( cg.highlightedText().dark() ); //p->drawText( width - w + 2, 3, w, h-1, TQt::AlignCenter, str ); if( !multi ) tmp = -(qh / 2); y += tmp; p.setPen( cg.highlightedText() ); p.drawText( x, y, -x + width, multi ? h/2 : qh, TQt::AlignCenter, str ); y -= tmp; if( isCurrent ) y -= height() / 2; else x -= m + w2; } if( repeat ) { if( multi ) tmp = (h/2 - rh)/2 + ( num == 2 && stop ? 0 : 1 ); else tmp = -(rh / 2); y += tmp; p.drawPixmap( x, y, pixrepeat ); y -= tmp; if( num == 3 ) { x -= m + w2 + 2; y = height() / 2; } else y -= height() / 2; } if( stop ) { if( multi && num != 3 ) tmp = m + (h/2 - sh)/2; else tmp = -(sh / 2); y += tmp; p.drawPixmap( x, y, pixstop ); y -= tmp; } } if( this != listView()->currentTrack() && !isSelected() ) { p.setPen( TQPen( cg.mid(), 0, TQt::SolidLine ) ); p.drawLine( width - 1, 0, width - 1, height() - 1 ); } p.end(); painter->drawPixmap( 0, 0, buf ); } void PlaylistItem::drawRating( TQPainter *p ) { int gray = 0; if( this == listView()->m_hoveredRating || ( isSelected() && listView()->m_selCount > 1 && listView()->m_hoveredRating && listView()->m_hoveredRating->isSelected() ) ) { const int pos = listView()->viewportToContents( listView()->viewport()->mapFromGlobal( TQCursor::pos() ) ).x(); gray = ratingAtPoint( pos ); } drawRating( p, ( rating() + 1 ) / 2, gray / 2, rating() % 2 ); } void PlaylistItem::drawRating( TQPainter *p, int stars, int greystars, bool half ) { int i = 1, x = 1; const int y = height() / 2 - StarManager::instance()->getGreyStar()->height() / 2; if( half ) i++; //We use multiple pre-colored stars instead of coloring here to keep things speedy for(; i <= stars; ++i ) { bitBlt( p->device(), x, y, StarManager::instance()->getStar( stars ) ); x += StarManager::instance()->getGreyStar()->width() + listView()->itemMargin(); } if( half ) { bitBlt( p->device(), x, y, StarManager::instance()->getHalfStar( stars ) ); x += StarManager::instance()->getGreyStar()->width() + listView()->itemMargin(); } for(; i <= greystars; ++i ) { bitBlt( p->device(), x, y, StarManager::instance()->getGreyStar() ); x += StarManager::instance()->getGreyStar()->width() + listView()->itemMargin(); } } #define MOODBAR_SPACING 2 // The distance from the moodbar pixmap to each side void PlaylistItem::drawMood( TQPainter *p, int width, int height ) { // In theory, if AmarokConfig::showMoodbar() == false, then the // moodbar column should be hidden and we shouldn't be here. if( !AmarokConfig::showMoodbar() ) return; // Due to the logic of the calling code, this should always return true if( moodbar().dataExists() ) { TQPixmap mood = moodbar().draw( width - MOODBAR_SPACING*2, height - MOODBAR_SPACING*2 ); p->drawPixmap( MOODBAR_SPACING, MOODBAR_SPACING, mood ); } else moodbar().load(); // This only has any effect the first time it's run // We don't have to listen for the jobEvent() signal since we // inherit MetaBundle, and the moodbar lets the MetaBundle know // about new data directly via moodbarJobEvent() below. } // This is run when a job starts or finishes void PlaylistItem::moodbarJobEvent( int newState ) { (void) newState; // want to redraw nomatter what the new state is if( AmarokConfig::showMoodbar() ) repaint(); // Don't automatically resort because it's annoying } void PlaylistItem::setup() { KListViewItem::setup(); // We make the current track item a bit taller than ordinary items if( this == listView()->currentTrack() ) setHeight( int( float( listView()->fontMetrics().height() ) * 1.53 ) ); } void PlaylistItem::paintFocus( TQPainter* p, const TQColorGroup& cg, const TQRect& r ) { if( this != listView()->currentTrack() ) KListViewItem::paintFocus( p, cg, r ); } const TQString &PlaylistItem::editingText() { static const TQString text = i18n( "Writing tag..." ); return text; } /** * Changes the transparency (alpha component) of an image. * @param image Image to be manipulated. Must be true color (8 bit per channel). * @param factor > 1.0 == more transparency, < 1.0 == less transparency. */ void PlaylistItem::imageTransparency( TQImage& image, float factor ) //static { uint *data = reinterpret_cast( image.bits() ); const int pixels = image.width() * image.height(); uint table[256]; register int c; // Precalculate lookup table for( int i = 0; i < 256; ++i ) { c = int( double( i ) * factor ); if( c > 255 ) c = 255; table[i] = c; } // Process all pixels. Highly optimized. for( int i = 0; i < pixels; ++i ) { c = data[i]; // Memory access is slow, so do it only once data[i] = tqRgba( tqRed( c ), tqGreen( c ), tqBlue( c ), table[tqAlpha( c )] ); } } AtomicString PlaylistItem::artist_album() const { static const AtomicString various_artist = TQString( "Various Artists (INTERNAL) [ASDF!]" ); if( compilation() == CompilationYes ) return various_artist; else return artist(); } void PlaylistItem::refAlbum() { if( Amarok::entireAlbums() ) { if( listView()->m_albums[artist_album()].find( album() ) == listView()->m_albums[artist_album()].end() ) listView()->m_albums[artist_album()][album()] = new PlaylistAlbum; m_album = listView()->m_albums[artist_album()][album()]; m_album->refcount++; } } void PlaylistItem::derefAlbum() { if( Amarok::entireAlbums() && m_album ) { m_album->refcount--; if( !m_album->refcount ) { if (!listView()->m_prevAlbums.removeRef( m_album )) warning() << "Unable to remove album reference from " << "listView.m_prevAlbums" << endl; listView()->m_albums[artist_album()].remove( album() ); if( listView()->m_albums[artist_album()].isEmpty() ) listView()->m_albums.remove( artist_album() ); delete m_album; } } } void PlaylistItem::incrementTotals() { if( Amarok::entireAlbums() && m_album ) { const uint prevCount = m_album->tracks.count(); // Multiple tracks with same track number are possible; // e.g. with "various_artist" albums or albums with multiple disks. // We are trying to keep m_album->tracks sorted first by discNumber() and then by track(). // It seems that the following assumption is made for the following: // ``Either all the tracks in an album have their track()'s set (to nonzero) or none of them have.'' if( !track() || !m_album->tracks.count() || ( m_album->tracks.getLast()->track() && ( ( m_album->tracks.getLast()->discNumber() < discNumber() ) || ( m_album->tracks.getLast()->discNumber() == discNumber() && m_album->tracks.getLast()->track() < track() ) ) ) ) m_album->tracks.append( this ); else for( int i = 0, n = m_album->tracks.count(); i < n; ++i ) if(!m_album->tracks.at(i)->track() || m_album->tracks.at(i)->discNumber() > discNumber() || ( m_album->tracks.at(i)->discNumber() == discNumber() && m_album->tracks.at(i)->track() > track() ) ) { m_album->tracks.insert( i, this ); break; } const TQ_INT64 prevTotal = m_album->total; TQ_INT64 total = m_album->total * prevCount; total += totalIncrementAmount(); m_album->total = TQ_INT64( double( total + 0.5 ) / m_album->tracks.count() ); if( listView()->m_prevAlbums.findRef( m_album ) == -1 ) listView()->m_total = listView()->m_total - prevTotal + m_album->total; } else if( listView()->m_prevTracks.findRef( this ) == -1 ) listView()->m_total += totalIncrementAmount(); } void PlaylistItem::decrementTotals() { if( Amarok::entireAlbums() && m_album ) { const TQ_INT64 prevTotal = m_album->total; TQ_INT64 total = m_album->total * m_album->tracks.count(); if (!m_album->tracks.removeRef( this )) warning() << "Unable to remove myself from m_album" << endl; total -= totalIncrementAmount(); m_album->total = TQ_INT64( double( total + 0.5 ) / m_album->tracks.count() ); if( listView()->m_prevAlbums.findRef( m_album ) == -1 ) listView()->m_total = listView()->m_total - prevTotal + m_album->total; } else if( listView()->m_prevTracks.findRef( this ) == -1 ) listView()->m_total -= totalIncrementAmount(); } int PlaylistItem::totalIncrementAmount() const { switch( AmarokConfig::favorTracks() ) { case AmarokConfig::EnumFavorTracks::Off: return 0; case AmarokConfig::EnumFavorTracks::HigherScores: return score() > 0.f ? static_cast( score() ) : 50; case AmarokConfig::EnumFavorTracks::HigherRatings: return rating() ? rating() : 5; // 2.5 case AmarokConfig::EnumFavorTracks::LessRecentlyPlayed: { if( lastPlay() ) return listView()->m_startupTime_t - lastPlay(); else if( listView()->m_oldestTime_t ) return ( listView()->m_startupTime_t - listView()->m_oldestTime_t ) * 2; else return listView()->m_startupTime_t - 1058652000; //july 20, 2003, when Amarok was first released. } default: return 0; } } void PlaylistItem::incrementCounts() { listView()->m_totalCount++; if( isSelected() ) { listView()->m_selCount++; } if( isVisible() ) { listView()->m_visCount++; incrementTotals(); } } void PlaylistItem::decrementCounts() { listView()->m_totalCount--; if( isSelected() ) { listView()->m_selCount--; } if( isVisible() ) { listView()->m_visCount--; decrementTotals(); } } void PlaylistItem::incrementLengths() { listView()->m_totalLength += length(); if( isSelected() ) { listView()->m_selLength += length(); } if( isVisible() ) { listView()->m_visLength += length(); } } void PlaylistItem::decrementLengths() { listView()->m_totalLength -= length(); if( isSelected() ) { listView()->m_selLength -= length(); } if( isVisible() ) { listView()->m_visLength -= length(); } }