|
|
|
/***************************************************************************
|
|
|
|
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 <config.h>
|
|
|
|
#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 <tqcursor.h>
|
|
|
|
#include <tqheader.h>
|
|
|
|
#include <tqimage.h>
|
|
|
|
#include <tqmutex.h>
|
|
|
|
#include <tqpainter.h>
|
|
|
|
#include <tqpen.h>
|
|
|
|
#include <tqpixmap.h>
|
|
|
|
#include <tqrect.h>
|
|
|
|
|
|
|
|
#include <kdeversion.h>
|
|
|
|
#include <kfilemetainfo.h>
|
|
|
|
#include <kglobal.h>
|
|
|
|
#include <kiconeffect.h>
|
|
|
|
#include <kstandarddirs.h>
|
|
|
|
#include <kstringhandler.h>
|
|
|
|
|
|
|
|
#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<int> &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<int> &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<PlaylistItem*>(this), TQListViewItemIterator::Visible ); *it; ++it )
|
|
|
|
#define pit static_cast<PlaylistItem*>( *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<PlaylistItem*>(this), TQListViewItemIterator::Visible ); *it; --it )
|
|
|
|
#define pit static_cast<PlaylistItem*>( *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<PlaylistItem*>(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:
|
|
|
|
// <LMMMMMMMMMMMMMMMR>
|
|
|
|
|
|
|
|
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 <reggie@kde.org>
|
|
|
|
Copyright (C) 2000,2003 Charles Samuels <charles@kde.org>
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|