You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
amarok/amarok/src/queuemanager.cpp

537 lines
16 KiB

/***************************************************************************
* copyright : (C) 2005 Seb Ruiz <me@sebruiz.net> *
**************************************************************************/
/***************************************************************************
* *
* 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 "QueueManager"
#include "debug.h"
#include "amarok.h"
#include "amarokconfig.h" //check if dynamic mode
#include "playlist.h"
#include "queuemanager.h"
#include <tdeapplication.h>
#include <kguiitem.h>
#include <tdelocale.h>
#include <kpushbutton.h>
#include <kurldrag.h>
#include <twin.h>
#include <tqpainter.h>
#include <tqptrlist.h>
#include <tqsimplerichtext.h>
#include <tqtooltip.h>
#include <tqvbox.h>
//////////////////////////////////////////////////////////////////////////////////////////
/// CLASS QueueItem
//////////////////////////////////////////////////////////////////////////////////////////
void
QueueItem::paintCell( TQPainter *p, const TQColorGroup &cg, int column, int width, int align )
{
TDEListViewItem::paintCell( p, cg, column, width, align );
TQString str = TQString::number( ( static_cast<TDEListView *>( listView() ) )->itemIndex( this ) + 1 );
//draw the symbol's outline
uint fw = p->fontMetrics().width( str ) + 2;
const uint w = 16; //keep this even
const uint h = height() - 2;
p->setBrush( cg.highlight() );
p->setPen( cg.highlight().dark() ); //TODO blend with background color
p->drawEllipse( width - fw - w/2, 1, w, h );
p->drawRect( width - fw, 1, fw, h );
p->setPen( cg.highlight() );
p->drawLine( width - fw, 2, width - fw, h - 1 );
fw += 2; //add some more padding
p->setPen( cg.highlightedText() );
p->drawText( width - fw, 2, fw, h-1, TQt::AlignCenter, str );
}
//////////////////////////////////////////////////////////////////////////////////////////
/// CLASS QueueList
//////////////////////////////////////////////////////////////////////////////////////////
QueueList::QueueList( TQWidget *parent, const char *name )
: TDEListView( parent, name )
{
addColumn( i18n("Name") );
setResizeMode( TQListView::LastColumn );
setSelectionMode( TQListView::Extended );
setSorting( -1 );
setAcceptDrops( true );
setDragEnabled( true );
setDropVisualizer( true ); //the visualizer (a line marker) is drawn when dragging over tracks
setDropVisualizerWidth( 3 );
}
void
QueueList::viewportPaintEvent( TQPaintEvent *e )
{
if( e ) TDEListView::viewportPaintEvent( e );
if( !childCount() && e )
{
TQPainter p( viewport() );
TQString minimumText(i18n(
"<div align=center>"
"<h3>The Queue Manager</h3>"
"To create a queue, "
"<b>drag</b> tracks from the playlist, and "
"<b>drop</b> them here.<br><br>"
"Drag and drop tracks within the manager to resort queue orders."
"</div>" ) );
TQSimpleRichText t( minimumText, TQApplication::font() );
if ( t.width()+30 >= viewport()->width() || t.height()+30 >= viewport()->height() )
//too big, giving up
return;
const uint w = t.width();
const uint h = t.height();
const uint x = (viewport()->width() - w - 30) / 2 ;
const uint y = (viewport()->height() - h - 30) / 2 ;
p.setBrush( colorGroup().background() );
p.drawRoundRect( x, y, w+30, h+30, (8*200)/w, (8*200)/h );
t.draw( &p, x+15, y+15, TQRect(), colorGroup() );
}
}
void
QueueList::keyPressEvent( TQKeyEvent *e )
{
switch( e->key() ) {
case Key_Delete: //remove
removeSelected();
break;
case CTRL+Key_Up:
moveSelectedUp();
break;
case CTRL+Key_Down:
moveSelectedDown();
break;
}
}
bool
QueueList::hasSelection()
{
TQListViewItemIterator it( this, TQListViewItemIterator::Selected );
if( !it.current() )
return false;
return true;
}
TQPtrList<TQListViewItem>
QueueList::selectedItems()
{
TQPtrList<TQListViewItem> selected;
TQListViewItemIterator it( this, TQListViewItemIterator::Selected );
for( ; it.current(); ++it )
selected.append( it.current() );
return selected;
}
void
QueueList::moveSelectedUp() // SLOT
{
TQPtrList<TQListViewItem> selected = selectedItems();
bool item_moved = false;
// Whilst it would be substantially faster to do this: ((*it)->itemAbove())->move( *it ),
// this would only work for sequentially ordered items
for( TQListViewItem *item = selected.first(); item; item = selected.next() )
{
if( item == itemAtIndex(0) )
continue;
TQListViewItem *after;
item == itemAtIndex(1) ?
after = 0:
after = ( item->itemAbove() )->itemAbove();
moveItem( item, 0, after );
item_moved = true;
}
ensureItemVisible( selected.first() );
if( item_moved )
emit changed();
}
void
QueueList::moveSelectedDown() // SLOT
{
TQPtrList<TQListViewItem> list = selectedItems();
bool item_moved = false;
for( TQListViewItem *item = list.last(); item; item = list.prev() )
{
TQListViewItem *after = item->nextSibling();
if( !after )
continue;
moveItem( item, 0, after );
item_moved = true;
}
ensureItemVisible( list.last() );
if( item_moved )
emit changed();
}
void
QueueList::removeSelected() //SLOT
{
setSelected( currentItem(), true );
bool item_removed = false;
TQPtrList<TQListViewItem> selected = selectedItems();
for( TQListViewItem *item = selected.first(); item; item = selected.next() )
{
delete item;
item_removed = true;
}
if( isEmpty() )
QueueManager::instance()->updateButtons();
if( item_removed )
emit changed();
}
void
QueueList::clear() // SLOT
{
TDEListView::clear();
emit changed();
}
void
QueueList::contentsDragEnterEvent( TQDragEnterEvent *e )
{
debug() << "contentsDrageEnterEvent()" << endl;
e->accept( e->source() == reinterpret_cast<TDEListView*>( Playlist::instance() )->viewport() );
}
void
QueueList::contentsDragMoveEvent( TQDragMoveEvent *e )
{
debug() << "contentsDrageMoveEvent()" << endl;
TDEListView::contentsDragMoveEvent( e );
// Must be overloaded for dnd to work
e->accept( ( e->source() == reinterpret_cast<TDEListView*>( Playlist::instance() )->viewport() ) ||
e->source() == viewport() );
}
void
QueueList::contentsDropEvent( TQDropEvent *e )
{
debug() << "contentsDragDropEvent()" << endl;
if( e->source() == viewport() )
{
TDEListView::contentsDropEvent( e );
emit changed();
}
else
{
TQListViewItem *parent = 0;
TQListViewItem *after;
findDrop( e->pos(), parent, after );
QueueManager::instance()->addItems( after );
}
}
//////////////////////////////////////////////////////////////////////////////////////////
/// CLASS QueueManager
//////////////////////////////////////////////////////////////////////////////////////////
QueueManager *QueueManager::s_instance = 0;
QueueManager::QueueManager( TQWidget *parent, const char *name )
: KDialogBase( KDialogBase::Swallow, 0, parent, name, false, 0, Ok|Apply|Cancel )
{
s_instance = this;
// Gives the window a small title bar, and skips a taskbar entry
KWin::setType( winId(), NET::Utility );
KWin::setState( winId(), NET::SkipTaskbar );
kapp->setTopWidget( this );
setCaption( kapp->makeStdCaption( i18n("Queue Manager") ) );
setInitialSize( TQSize( 400, 260 ) );
TQVBox *mainBox = new TQVBox( this );
setMainWidget( mainBox );
TQHBox *box = new TQHBox( mainWidget() );
box->setSpacing( 5 );
m_listview = new QueueList( box );
TQVBox *buttonBox = new TQVBox( box );
m_up = new KPushButton( KGuiItem( TQString(), "go-up" ), buttonBox );
m_down = new KPushButton( KGuiItem( TQString(), "go-down" ), buttonBox );
m_remove = new KPushButton( KGuiItem( TQString(), Amarok::icon( "dequeue_track" ) ), buttonBox );
m_add = new KPushButton( KGuiItem( TQString(), Amarok::icon( "queue_track" ) ), buttonBox );
m_clear = new KPushButton( KGuiItem( TQString(), Amarok::icon( "playlist_clear" ) ), buttonBox );
TQToolTip::add( m_up, i18n( "Move up" ) );
TQToolTip::add( m_down, i18n( "Move down" ) );
TQToolTip::add( m_remove, i18n( "Remove" ) );
TQToolTip::add( m_add, i18n( "Enqueue track" ) );
TQToolTip::add( m_clear, i18n( "Clear queue" ) );
m_up->setEnabled( false );
m_down->setEnabled( false );
m_remove->setEnabled( false );
m_add->setEnabled( false );
m_clear->setEnabled( false );
connect( m_up, TQT_SIGNAL( clicked() ), m_listview, TQT_SLOT( moveSelectedUp() ) );
connect( m_down, TQT_SIGNAL( clicked() ), m_listview, TQT_SLOT( moveSelectedDown() ) );
connect( m_remove, TQT_SIGNAL( clicked() ), this, TQT_SLOT( removeSelected() ) );
connect( m_add, TQT_SIGNAL( clicked() ), this, TQT_SLOT( addItems() ) );
connect( m_clear, TQT_SIGNAL( clicked() ), m_listview, TQT_SLOT( clear() ) );
Playlist *pl = Playlist::instance();
connect( pl, TQT_SIGNAL( selectionChanged() ), TQT_SLOT( updateButtons() ) );
connect( m_listview, TQT_SIGNAL( selectionChanged() ), TQT_SLOT( updateButtons() ) );
connect( pl, TQT_SIGNAL( queueChanged(const PLItemList &, const PLItemList &) ),
TQT_SLOT( changeQueuedItems(const PLItemList &, const PLItemList &) ) );
connect( this, TQT_SIGNAL( applyClicked()), TQT_SLOT( applyNow() ) );
connect( m_listview, TQT_SIGNAL( changed() ), this, TQT_SLOT ( changed() ) );
s_instance->enableButtonApply(false);
insertItems();
}
QueueManager::~QueueManager()
{
s_instance = 0;
}
void
QueueManager::applyNow()
{
Playlist *pl = Playlist::instance();
pl->changeFromQueueManager( newQueue() );
s_instance->enableButtonApply(false);
}
void
QueueManager::addItems( TQListViewItem *after )
{
/*
HACK!!!!! We can know which items where dragged since they should still be selected
I do this, because:
- Dragging items from the playlist provides urls
- Providing urls, requires iterating through the entire list in order to find which
item was selected. Possibly a very expensive task - worst case: O(n)
- After a drag, those items are still selected in the playlist, so we can find out
which PlaylistItems were dragged by selectedItems();
*/
if( !after )
after = m_listview->lastChild();
TQPtrList<TQListViewItem> list = Playlist::instance()->selectedItems();
bool item_added = false;
for( TQListViewItem *item = list.first(); item; item = list.next() )
{
#define item static_cast<PlaylistItem*>(item)
TQValueList<PlaylistItem*> current = m_map.values();
if( current.find( item ) == current.end() ) //avoid duplication
{
TQString title = i18n("%1 - %2").arg( item->artist(), item->title() );
after = new QueueItem( m_listview, after, title );
m_map[ after ] = item;
item_added = true;
}
#undef item
}
if( item_added )
emit m_listview->changed();
}
void
QueueManager::changeQueuedItems( const PLItemList &in, const PLItemList &out ) //SLOT
{
TQPtrListIterator<PlaylistItem> it(in);
for( it.toFirst(); it; ++it ) addQueuedItem( *it );
it = TQPtrListIterator<PlaylistItem>(out);
for( it.toFirst(); it; ++it ) removeQueuedItem( *it );
}
void
QueueManager::addQueuedItem( PlaylistItem *item )
{
Playlist *pl = Playlist::instance();
if( !pl ) return; //should never happen
const int index = pl->m_nextTracks.findRef( item );
TQListViewItem *after;
if( !index ) after = 0;
else
{
int find = m_listview->childCount();
if( index - 1 <= find )
find = index - 1;
after = m_listview->itemAtIndex( find );
}
TQValueList<PlaylistItem*> current = m_map.values();
TQValueListIterator<PlaylistItem*> newItem = current.find( item );
TQString title = i18n("%1 - %2").arg( item->artist(), item->title() );
if( newItem == current.end() ) //avoid duplication
{
after = new QueueItem( m_listview, after, title );
m_map[ after ] = item;
}
}
void
QueueManager::removeQueuedItem( PlaylistItem *item )
{
Playlist *pl = Playlist::instance();
if( !pl ) return; //should never happen
TQValueList<PlaylistItem*> current = m_map.values();
TQValueListIterator<PlaylistItem*> newItem = current.find( item );
TQString title = i18n("%1 - %2").arg( item->artist(), item->title() );
TQListViewItem *removableItem = m_listview->findItem( title, 0 );
if( removableItem )
{
//Remove the key from the map, so we can re-queue the item
TQMapIterator<TQListViewItem*, PlaylistItem*> end( m_map.end() );
for( TQMapIterator<TQListViewItem*, PlaylistItem*> it = m_map.begin(); it != end; ++it )
{
if( it.data() == item )
{
m_map.remove( it );
//Remove the item from the queuelist
m_listview->takeItem( removableItem );
delete removableItem;
return;
}
}
}
}
/// Playlist uses this to determine the altered queue and reflect the changes.
TQPtrList<PlaylistItem>
QueueManager::newQueue()
{
TQPtrList<PlaylistItem> queue;
for( TQListViewItem *key = m_listview->firstChild(); key; key = key->nextSibling() )
{
queue.append( m_map[ key ] );
}
return queue;
}
void
QueueManager::insertItems()
{
TQPtrList<PlaylistItem> list = Playlist::instance()->m_nextTracks;
TQListViewItem *last = 0;
for( PlaylistItem *item = list.first(); item; item = list.next() )
{
TQString title = i18n("%1 - %2").arg( item->artist(), item->title() );
last = new QueueItem( m_listview, last, title );
m_map[ last ] = item;
}
updateButtons();
}
void
QueueManager::changed() // SLOT
{
s_instance->enableButtonApply(true);
}
void
QueueManager::removeSelected() //SLOT
{
TQPtrList<TQListViewItem> selected = m_listview->selectedItems();
bool item_removed = false;
for( TQListViewItem *item = selected.first(); item; item = selected.next() )
{
//Remove the key from the map, so we can re-queue the item
TQMapIterator<TQListViewItem*, PlaylistItem*> it = m_map.find( item );
m_map.remove( it );
//Remove the item from the queuelist
m_listview->takeItem( item );
delete item;
item_removed = true;
}
if( item_removed )
emit m_listview->changed();
}
void
QueueManager::updateButtons() //SLOT
{
const bool enablePL = !Playlist::instance()->selectedItems().isEmpty();
const bool emptyLV = m_listview->isEmpty();
const bool enableQL = m_listview->hasSelection() && !emptyLV;
m_up->setEnabled( enableQL );
m_down->setEnabled( enableQL );
m_remove->setEnabled( enableQL );
m_add->setEnabled( enablePL );
m_clear->setEnabled( !emptyLV );
}
#include "queuemanager.moc"