Make the ServiceListDelegate use less handcoded values and more general variables...
[amarok.git] / src / mediabrowser.cpp
blob7f71229ce65db409104cd5d72455f4c783be1eaa
1 // (c) 2004 Christian Muehlhaeuser <chris@chris.de>
2 // (c) 2005-2006 Martin Aumueller <aumuell@reserv.at>
3 // (c) 2005 Seb Ruiz <ruiz@kde.org>
4 // (c) 2006 T.R.Shashwath <trshash84@gmail.com>
5 // See COPYING file for licensing information
8 #define DEBUG_PREFIX "MediaBrowser"
10 #include "mediabrowser.h"
12 #include "config-amarok.h"
14 #include "amarok.h"
15 #include "amarokconfig.h"
16 #include "app.h"
17 #include "browserToolBar.h"
18 #include "collectiondb.h"
19 #include "debug.h"
20 #include "editfilterdialog.h"
21 #include "deviceconfiguredialog.h"
22 #include "expression.h"
23 #include "hintlineedit.h"
24 #include "MediaItem.h"
25 #include "medium.h"
26 #include "mediumpluginmanager.h"
27 #include "metabundle.h"
28 #include "mountpointmanager.h"
29 #include "playlist/PlaylistModel.h"
30 #include "pluginmanager.h"
31 #include "podcastbundle.h"
32 #include "scriptmanager.h"
33 #include "scrobbler.h"
34 #include "searchwidget.h"
35 #include "statusbar.h"
36 #include "transferdialog.h"
37 #include "TheInstances.h"
39 #include <q3header.h>
40 #include <Q3PopupMenu>
41 #include <Q3PtrList>
42 #include <q3simplerichtext.h>
43 #include <QByteArray>
44 #include <QCheckBox>
45 #include <QDateTime>
46 #include <QDir>
47 #include <QDomDocument>
48 #include <QDomElement>
49 #include <QDomNode>
50 #include <QDropEvent>
51 #include <QFileInfo>
52 #include <QGroupBox>
53 #include <QImage>
54 #include <QKeyEvent>
55 #include <QLabel>
56 #include <QList>
57 #include <QListIterator>
58 #include <QObject>
59 #include <QPainter>
60 #include <QPaintEvent>
61 #include <QPixmap>
62 #include <QProgressBar>
63 #include <QRadioButton>
64 #include <QTimer>
65 #include <QToolButton>
66 #include <QToolTip> //QToolTip::add()
68 #include <k3multipledrag.h>
69 #include <k3process.h>
70 #include <k3tempfile.h>
71 #include <k3urldrag.h> //dragObject()
72 #include <KActionCollection>
73 #include <KApplication> //kapp
74 #include <KComboBox>
75 #include <KDirLister>
76 #include <KFileDialog>
77 #include <KGlobal>
78 #include <KIconLoader>
79 #include <KInputDialog>
80 #include <KIO/Job>
81 #include <KLocale>
82 #include <KMenu>
83 #include <KMessageBox>
84 #include <KPushButton>
85 #include <KRun>
86 #include <KStandardDirs> //locate file
87 #include <KTabBar>
88 #include <solid/device.h>
89 #include <solid/deviceinterface.h>
90 #include <solid/devicenotifier.h>
91 #include <solid/portablemediaplayer.h>
94 MediaBrowser *MediaBrowser::s_instance = 0;
96 bool MediaBrowser::isAvailable() //static
98 if( !MediaBrowser::instance() )
99 return false;
101 return true;
103 //to re-enable hiding, uncomment this and get rid of the return true above:
104 //return MediaBrowser::instance()->m_haveDevices;
107 class SpaceLabel : public QLabel {
108 public:
109 SpaceLabel(QWidget *parent)
110 : QLabel(parent)
112 m_total = m_used = m_scheduled = 0;
115 void paintEvent(QPaintEvent *e)
117 QPainter p(this);
118 p.fillRect(e->rect(), palette().brush(QColorGroup::Background));
120 if(m_total > 0)
122 int used = int(float(m_used)/float(m_total)*width());
123 int scheduled = int(float(m_used + m_scheduled)/float(m_total)*width());
125 if(m_used > 0)
127 QColor blueish(70,120,255);
128 if(e->rect().left() < used)
130 int right = used;
131 if(e->rect().right() < right)
132 right = e->rect().right();
133 p.fillRect(e->rect().left(), e->rect().top(),
134 used, e->rect().bottom()+1, QBrush(blueish, Qt::SolidPattern));
138 if(m_scheduled > 0)
140 QColor sched(70, 230, 120);
141 if(m_used + m_scheduled > m_total - m_total/200)
143 sched.setRgb( 255, 120, 120 );
145 int left = e->rect().left();
146 if(used > left)
147 left = used;
148 int right = e->rect().right();
149 if(scheduled < right)
150 right = scheduled;
151 p.fillRect(left, e->rect().top(), right, e->rect().bottom()+1, QBrush(sched, Qt::SolidPattern));
154 if(m_used + m_scheduled < m_total)
156 QColor grey(180, 180, 180);
157 int left = e->rect().left();
158 if(scheduled > left)
159 left = scheduled;
160 int right = e->rect().right();
161 p.fillRect(left, e->rect().top(), right, e->rect().bottom()+1, palette().brush(QColorGroup::Background));
164 QLabel::paintEvent(e);
167 KIO::filesize_t m_total;
168 KIO::filesize_t m_used;
169 KIO::filesize_t m_scheduled;
172 class DummyMediaDevice : public MediaDevice
174 public:
175 DummyMediaDevice() : MediaDevice()
177 m_name = i18n( "No Device Available" );
178 m_type = "dummy-mediadevice";
179 m_medium = Medium( "DummyDevice", "DummyDevice" );
181 void init( MediaBrowser *browser ) { MediaDevice::init( browser ); }
182 virtual ~DummyMediaDevice() {}
183 virtual bool isConnected() { return false; }
184 virtual MediaItem* trackExists(const MetaBundle&) { return 0; }
185 virtual bool lockDevice(bool) { return true; }
186 virtual void unlockDevice() {}
187 virtual bool openDevice( bool silent )
189 if( !silent )
191 //QString msg = i18n( "Sorry, you do not have a supported portable music player." );
192 //Amarok::StatusBar::instance()->longMessage( msg, KDE::StatusBar::Sorry );
194 return false;
196 virtual bool closeDevice() { return false; }
197 virtual void synchronizeDevice() {}
198 virtual MediaItem* copyTrackToDevice(const MetaBundle&) { return 0; }
199 virtual int deleteItemFromDevice(MediaItem*, int) { return -1; }
203 MediaBrowser::MediaBrowser( const char * /*name*/ )
204 : QWidget( 0)
205 , m_timer( new QTimer( this ) )
206 , m_currentDevice( 0 )
207 , m_waitForTranscode( false )
208 , m_quitting( false )
209 , m_connectAction( 0 )
210 , m_disconnectAction( 0 )
211 , m_customAction( 0 )
212 , m_configAction( 0 )
213 , m_transferAction( 0 )
215 s_instance = this;
217 QVBoxLayout *layout = new QVBoxLayout;
219 layout->setSpacing( 4 );
221 m_timer->setSingleShot( true );
223 m_toolbar = new Browser::ToolBar( this );
224 m_toolbar->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
225 m_toolbar->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
227 //TODO: how to fix getButton
228 m_connectAction = new KAction(KIcon("connect_creating"), i18n("Connect"), this);
229 connect(m_connectAction, SIGNAL(triggered()), this, SLOT(connectClicked()));
230 m_toolbar->addAction(m_connectAction);
231 // m_toolbar->insertButton( "connect_creating", CONNECT, true, i18n("Connect") );
232 // m_toolbar->getButton(CONNECT)->setToolTip( i18n( "Connect media device" ) );
234 m_disconnectAction = new KAction(KIcon("media-eject"), i18n("Disconnect"), this);
235 connect(m_disconnectAction, SIGNAL(triggered()), this, SLOT(disconnectClicked()));
236 m_toolbar->addAction(m_disconnectAction);
237 // m_toolbar->insertButton( "media-eject", DISCONNECT, true, i18n("Disconnect") );
238 // m_toolbar->getButton(DISCONNECT)->setToolTip( i18n( "Disconnect media device" ) );
240 m_transferAction = new KAction(KIcon("rebuild"), i18n("Transfer"), this);
241 connect(m_transferAction, SIGNAL(triggered()), this, SLOT(transferClicked()));
242 m_toolbar->addAction(m_transferAction);
243 // m_toolbar->insertButton( "rebuild", TRANSFER, true, i18n("Transfer") );
244 // m_toolbar->getButton(TRANSFER)->setToolTip( i18n( "Transfer tracks to media device" ) );
246 m_toolbar->addSeparator();
248 // m_toolbar->setIconText( KToolBar::IconTextRight, true );
249 m_customAction = new KAction(KIcon( "add_playlist" ), i18n("custom"), this);
250 connect(m_customAction, SIGNAL(triggered()), this, SLOT(custom()));
251 m_toolbar->addAction(m_customAction);
252 // m_toolbar->insertButton( Amarok::icon( "add_playlist" ), CUSTOM, SIGNAL( clicked() ), this, SLOT( customClicked() ), true, "custom" );
253 // m_toolbar->getButton(TRANSFER)->setToolTip( i18n( "Transfer tracks to media device" ) );
255 m_toolbar->setToolButtonStyle( Qt::ToolButtonIconOnly );
257 m_configAction = new KAction(KIcon("configure"), i18n("Configure"), this);
258 connect(m_configAction, SIGNAL(triggered()), this, SLOT(config()));
259 m_toolbar->addAction(m_configAction);
260 // m_toolbar->insertButton( Amarok::icon( "configure" ), CONFIGURE, true, i18n("Configure") );
261 // m_toolbar->getButton(CONFIGURE)->setToolTip( i18n( "Configure device" ) );
264 m_deviceCombo = new KComboBox();
265 layout->addWidget( m_deviceCombo );
267 // searching/filtering
268 QToolBar* searchToolBar = new Browser::ToolBar( this );
269 searchToolBar->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
270 m_searchWidget = new SearchWidget( searchToolBar, this );
271 searchToolBar->addWidget( m_searchWidget );
272 connect( m_timer, SIGNAL( timeout() ), SLOT( slotSetFilter() ) );
273 // connect( m_searchEdit, SIGNAL( textChanged( const QString& ) ), SLOT( slotSetFilterTimeout() ) );
274 // connect( m_searchEdit, SIGNAL( returnPressed() ), SLOT( slotSetFilter() ) );
276 // connect to device manager
277 connect( Solid::DeviceNotifier::instance(), SIGNAL( deviceAdded(const QString&) ),
278 SLOT( deviceAdded(const QString&) ) );
279 connect( Solid::DeviceNotifier::instance(), SIGNAL( deviceRemoved(const QString&) ),
280 SLOT( deviceRemoved(const QString&) ) );
283 // we always have a dummy device
284 m_pluginName[ i18n( "Disable" ) ] = "dummy-mediadevice";
285 m_pluginAmarokName["dummy-mediadevice"] = i18n( "Disable" );
286 m_pluginName[ i18n( "Do not handle" ) ] = "ignore";
287 m_pluginAmarokName["ignore"] = i18n( "Do not handle" );
288 // query available device plugins
289 m_plugins = PluginManager::query( "[X-KDE-Amarok-plugintype] == 'mediadevice'" );
290 for( KService::List::ConstIterator it = m_plugins.begin(); it != m_plugins.end(); ++it ) {
291 // Save name properties in QMap for lookup
292 m_pluginName[(*it)->name()] = (*it)->property( "X-KDE-Amarok-name" ).toString();
293 m_pluginAmarokName[(*it)->property( "X-KDE-Amarok-name" ).toString()] = (*it)->name();
296 m_views = new QWidget( this );
297 m_queue = new MediaQueue( this );
298 m_progressBox = new KHBox();
299 layout->addWidget( m_progressBox );
300 m_progress = new QProgressBar( m_progressBox );
301 m_cancelButton = new KPushButton( KIcon( Amarok::icon( "cancel" ) ), i18n("Cancel"), m_progressBox );
304 m_stats = new SpaceLabel(this);
306 m_progressBox->hide();
308 MediaDevice *dev = new DummyMediaDevice();
309 dev->init( this );
310 dev->setUid( "DummyDevice" );
311 addDevice( dev );
312 activateDevice( 0, false );
313 queue()->load( Amarok::saveLocation() + "transferlist.xml" );
314 queue()->computeSize();
316 setFocusProxy( m_queue );
318 updateStats();
320 QList<Solid::Device> pmpList = Solid::Device::listFromType( Solid::DeviceInterface::PortableMediaPlayer );
321 foreach( Solid::Device device, pmpList )
322 deviceAdded( device.udi() );
324 //TODO: Take generic storage devices into account too -- or do we rely on the
325 //Solid backend to tell us if it's a PMP with "storage" type?
327 connect( m_deviceCombo, SIGNAL( activated( int ) ), SLOT( activateDevice( int ) ) );
329 connect( m_cancelButton, SIGNAL( clicked() ), SLOT( cancelClicked() ) );
330 connect( pApp, SIGNAL( prepareToQuit() ), SLOT( prepareToQuit() ) );
331 connect( CollectionDB::instance(), SIGNAL( tagsChanged( const MetaBundle& ) ),
332 SLOT( tagsChanged( const MetaBundle& ) ) );
334 //TODO: If we will be supporting manually adding devices, probably need the following section
335 /*m_haveDevices = false;
336 QMap<QString,QString> savedDevices = Amarok::config( "MediaBrowser" ).entryMap();
337 for( QMap<QString,QString>::Iterator it = savedDevices.begin();
338 it != savedDevices.end();
339 ++it )
341 if( it.data() != "deleted" && it.data() != "ignore" )
343 m_haveDevices = true;
344 break;
348 emit availabilityChanged( !pmpList.isEmpty() );
350 this->setLayout( layout );
353 bool
354 MediaBrowser::blockQuit() const
356 for( QList<MediaDevice *>::const_iterator it = m_devices.begin();
357 it != m_devices.end();
358 ++it )
360 if( *it && (*it)->isConnected() )
361 return true;
364 return false;
367 void
368 MediaBrowser::tagsChanged( const MetaBundle &bundle )
370 m_itemMapMutex.lock();
371 debug() << "tags changed for " << bundle.url().url();
372 ItemMap::iterator it = m_itemMap.find( bundle.url().url() );
373 if( it != m_itemMap.end() )
375 MediaItem *item = *it;
376 m_itemMapMutex.unlock();
377 if( item->device() )
379 item->device()->tagsChanged( item, bundle );
381 else
383 // it's an item on the transfer queue
384 item->setBundle( new MetaBundle( bundle ) );
386 QString text = item->bundle()->prettyTitle();
387 if( text.isEmpty() || (!item->bundle()->isValidMedia() && !item->bundle()->podcastBundle()) )
388 text = item->bundle()->url().prettyUrl();
389 if( !item->m_playlistName.isNull() )
391 text += " (" + item->m_playlistName + ')';
393 item->setText( 0, text);
396 else
398 m_itemMapMutex.unlock();
402 bool
403 MediaBrowser::getBundle( const KUrl &url, MetaBundle *bundle ) const
405 QMutexLocker locker( &m_itemMapMutex );
406 ItemMap::const_iterator it = m_itemMap.find( url.url() );
407 if( it == m_itemMap.end() )
408 return false;
410 if( bundle )
411 *bundle = *(*it)->bundle();
413 return true;
416 KUrl
417 MediaBrowser::getProxyUrl( const KUrl& daapUrl ) const
419 DEBUG_BLOCK
420 KUrl url;
421 MediaDevice* dc = findChildren<MediaDevice *>( "DaapClient" ).first();
422 if( dc )
423 url = dc->getProxyUrl( daapUrl );
424 return url;
427 MediaDevice *
428 MediaBrowser::deviceFromId( const QString &id ) const
430 for( QList<MediaDevice *>::const_iterator it = m_devices.constBegin();
431 it != m_devices.end();
432 it++ )
434 if( (*it)->uniqueId() == id )
435 return (*it);
438 return NULL;
441 void
442 MediaBrowser::activateDevice( const MediaDevice *dev )
444 int index = 0;
445 for( QList<MediaDevice *>::iterator it = m_devices.begin();
446 it != m_devices.end();
447 it++ )
449 if( *it == dev )
451 activateDevice( index );
452 break;
454 index++;
458 void
459 MediaBrowser::activateDevice( int index, bool skipDummy )
461 if( m_currentDevice && m_currentDevice->customAction() )
463 m_toolbar->removeAction( m_currentDevice->customAction() );
464 m_toolbar->hide();
465 m_toolbar->show();
468 foreach( MediaDevice *md, m_devices )
470 if( md && md->view() )
471 md->view()->hide();
474 if( index < 0 )
476 m_currentDevice = m_devices.last();
477 return;
480 if( skipDummy )
481 index++;
483 if( index >= m_devices.count() )
485 if( !m_devices.isEmpty() )
486 m_currentDevice = m_devices.last();
487 else
488 m_currentDevice = 0;
489 updateButtons();
490 queue()->computeSize();
491 updateStats();
492 return;
495 m_currentDevice = m_devices[index];
496 if( m_currentDevice && m_currentDevice->view() )
498 m_currentDevice->view()->show();
499 if( m_currentDevice->customAction() )
501 m_toolbar->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
502 m_toolbar->addAction( m_currentDevice->customAction() );
503 m_toolbar->hide();
504 m_toolbar->show();
507 m_deviceCombo->setCurrentIndex( index-1 );
509 updateButtons();
510 queue()->computeSize();
511 updateStats();
514 void
515 MediaBrowser::addDevice( MediaDevice *device )
517 m_devices.append( device );
519 device->loadConfig();
521 if( device->autoConnect() )
523 device->connectDevice( true );
524 updateButtons();
527 updateDevices();
530 void
531 MediaBrowser::removeDevice( MediaDevice *device )
533 DEBUG_BLOCK
535 debug() << "remove device: type=" << device->deviceType();
537 for( QList<MediaDevice *>::iterator it = m_devices.begin();
538 it != m_devices.end();
539 it++ )
541 if( *it == device )
543 bool current = ( (*it)->uid() == m_currentDevice->uid() );
544 m_devices.erase( it );
545 if( current )
546 activateDevice( 0, false );
547 break;
551 if( device->isConnected() )
553 if( device->disconnectDevice( false /* don't run post-disconnect command */ ) )
554 unloadDevicePlugin( device );
555 else
557 debug() << "Cannot remove device because disconnect failed";
558 Amarok::StatusBar::instance()->longMessage(
559 i18n( "Cannot remove device because disconnect failed" ),
560 KDE::StatusBar::Warning );
563 else
564 unloadDevicePlugin( device );
566 updateDevices();
569 void
570 MediaBrowser::updateDevices()
572 m_deviceCombo->clear();
573 uint i = 0;
574 for( QList<MediaDevice *>::iterator it = m_devices.begin();
575 it != m_devices.end();
576 it++ )
578 if( m_devices.count() > 1 && dynamic_cast<DummyMediaDevice *>(*it) )
579 continue;
580 QString name = (*it)->name();
581 if( !(*it)->deviceNode().isEmpty() )
583 name = i18n( "%1 at %2", name, (*it)->deviceNode() );
585 if( (*it)->hasMountPoint() && !(*it)->mountPoint().isEmpty() )
587 name += i18n( " (mounted at %1)", (*it)->mountPoint() );
589 m_deviceCombo->addItem( name, i );
590 if( !m_currentDevice || (*it)->uid() == m_currentDevice->uid() )
592 m_deviceCombo->setCurrentItem( name );
594 i++;
596 m_deviceCombo->setEnabled( m_devices.count() > 1 );
597 m_haveDevices = m_devices.count() > 1;
598 emit availabilityChanged( m_haveDevices );
601 QStringList
602 MediaBrowser::deviceNames() const
604 QStringList list;
606 for( QList<MediaDevice *>::const_iterator it = m_devices.constBegin();
607 it != m_devices.constEnd();
608 it++ )
610 QString name = (*it)->name();
611 list << name;
614 return list;
617 bool
618 MediaBrowser::deviceSwitch( const QString &name )
620 int index = 0;
621 for( QList<MediaDevice *>::iterator it = m_devices.begin();
622 it != m_devices.end();
623 it++ )
625 if( (*it)->name() == name )
627 activateDevice( index, false );
628 return true;
630 index++;
633 return false;
636 void
637 MediaBrowser::transcodingFinished( const QString &src, const QString &dst )
639 KUrl srcJob = KUrl( m_transcodeSrc );
640 KUrl srcResult = KUrl( src );
642 if( srcJob.path() == srcResult.path() )
644 m_transcodedUrl = KUrl( dst );
645 m_waitForTranscode = false;
647 else
649 debug() << "transcoding for " << src << " finished, "
650 << "but we are waiting for " << m_transcodeSrc << " -- aborting";
651 m_waitForTranscode = false;
655 KUrl
656 MediaBrowser::transcode( const KUrl &src, const QString &filetype )
658 const ScriptManager* const sm = ScriptManager::instance();
660 if( sm->transcodeScriptRunning().isEmpty() )
662 debug() << "cannot transcode with no transcoder registered";
663 return KUrl();
666 m_waitForTranscode = true;
667 m_transcodeSrc = src.url();
668 m_transcodedUrl = KUrl();
669 ScriptManager::instance()->notifyTranscode( src.url(), filetype );
671 while( m_waitForTranscode && !sm->transcodeScriptRunning().isEmpty() )
673 usleep( 10000 );
674 kapp->processEvents( QEventLoop::AllEvents );
677 return m_transcodedUrl;
681 void
682 MediaBrowser::slotSetFilterTimeout() //SLOT
684 m_timer->start( 280 ); //stops the timer for us first
687 void
688 MediaBrowser::slotSetFilter() //SLOT
690 m_timer->stop();
692 if( m_currentDevice )
693 m_currentDevice->view()->setFilter( m_searchWidget->lineEdit()->text() );
696 void
697 MediaBrowser::slotSetFilter( const QString &text )
699 m_searchWidget->lineEdit()->setText( text );
700 slotSetFilter();
703 void
704 MediaBrowser::slotEditFilter()
706 EditFilterDialog *fd = new EditFilterDialog( this, true, m_searchWidget->lineEdit()->text() );
707 connect( fd, SIGNAL(filterChanged(const QString &)), SLOT(slotSetFilter(const QString &)) );
708 if( fd->exec() )
709 m_searchWidget->lineEdit()->setText( fd->filter() );
710 delete fd;
713 void
714 MediaBrowser::prepareToQuit()
716 m_waitForTranscode = false;
717 m_quitting = true;
718 for( QList<MediaDevice *>::iterator it = m_devices.begin();
719 it != m_devices.end();
720 ++it )
722 if( (*it)->isConnected() )
723 (*it)->disconnectDevice( false /* don't unmount */ );
727 MediaBrowser::~MediaBrowser()
729 debug() << "having to remove " << m_devices.count() << " devices";
730 while( !m_devices.isEmpty() )
732 removeDevice( m_devices.last() );
735 queue()->save( Amarok::saveLocation() + "transferlist.xml" );
737 delete m_deviceCombo;
738 delete m_queue;
741 MediaView::MediaView( QWidget* parent, MediaDevice *device )
742 : K3ListView( parent )
743 , m_parent( parent )
744 , m_device( device )
746 hide();
747 setSelectionMode( Q3ListView::Extended );
748 setItemsMovable( false );
749 setShowSortIndicator( true );
750 setFullWidth( true );
751 setRootIsDecorated( true );
752 setDragEnabled( true );
753 setDropVisualizer( true ); //the visualizer (a line marker) is drawn when dragging over tracks
754 setDropHighlighter( true ); //and the highligther (a focus rect) is drawn when dragging over playlists
755 setDropVisualizerWidth( 3 );
756 setAcceptDrops( true );
758 header()->hide();
759 addColumn( i18n( "Remote Media" ) );
761 KActionCollection* ac = new KActionCollection( this );
762 KStandardAction::selectAll( this, SLOT( selectAll() ), ac );
764 connect( this, SIGNAL( contextMenuRequested( Q3ListViewItem*, const QPoint&, int ) ),
765 this, SLOT( rmbPressed( Q3ListViewItem*, const QPoint&, int ) ) );
767 connect( this, SIGNAL( itemRenamed( Q3ListViewItem* ) ),
768 this, SLOT( renameItem( Q3ListViewItem* ) ) );
770 connect( this, SIGNAL( expanded( Q3ListViewItem* ) ),
771 this, SLOT( slotExpand( Q3ListViewItem* ) ) );
773 connect( this, SIGNAL( returnPressed( Q3ListViewItem* ) ),
774 this, SLOT( invokeItem( Q3ListViewItem* ) ) );
776 connect( this, SIGNAL( doubleClicked( Q3ListViewItem*, const QPoint&, int ) ),
777 this, SLOT( invokeItem( Q3ListViewItem*, const QPoint &, int ) ) );
780 void
781 MediaView::keyPressEvent( QKeyEvent *e )
783 if( e->key() == Qt::Key_Delete )
784 m_device->deleteFromDevice();
785 else
786 K3ListView::keyPressEvent( e );
789 void
790 MediaView::invokeItem( Q3ListViewItem* i, const QPoint& point, int column ) //SLOT
792 if( column == -1 )
793 return;
795 QPoint p = mapFromGlobal( point );
796 if ( p.x() > header()->sectionPos( header()->mapToIndex( 0 ) ) + treeStepSize() * ( i->depth() + ( rootIsDecorated() ? 1 : 0) ) + itemMargin()
797 || p.x() < header()->sectionPos( header()->mapToIndex( 0 ) ) )
798 invokeItem( i );
802 void
803 MediaView::invokeItem( Q3ListViewItem *i )
805 MediaItem *item = static_cast<MediaItem *>( i );
806 if( !item )
807 return;
809 KUrl::List urls = nodeBuildDragList( item );
810 The::playlistModel()->insertMedia( urls, Playlist::AppendAndPlay );
813 void
814 MediaView::renameItem( Q3ListViewItem *item )
816 m_device->renameItem( item );
819 void
820 MediaView::slotExpand( Q3ListViewItem *item )
822 m_device->expandItem( item );
826 MediaView::~MediaView()
831 Q3DragObject *
832 MediaView::dragObject()
834 KUrl::List urls = nodeBuildDragList( 0 );
835 K3MultipleDrag *md = new K3MultipleDrag( viewport() );
836 md->addDragObject( K3ListView::dragObject() );
837 K3URLDrag* ud = new K3URLDrag( urls, viewport() );
838 md->addDragObject( ud );
839 md->setPixmap( CollectionDB::createDragPixmap( urls ),
840 QPoint( CollectionDB::DRAGPIXMAP_OFFSET_X, CollectionDB::DRAGPIXMAP_OFFSET_Y ) );
841 return md;
845 KUrl::List
846 MediaView::nodeBuildDragList( MediaItem* item, int flags )
848 KUrl::List items;
849 MediaItem* fi;
851 if ( !item )
853 fi = static_cast<MediaItem*>(firstChild());
855 else
856 fi = item;
858 while ( fi )
860 if( fi->isVisible() )
862 if ( fi->isSelected() || !(flags & OnlySelected ) )
864 if( fi->isLeafItem() || fi->type() == MediaItem::DIRECTORY )
865 items += fi->url();
866 else
868 if(fi->childCount() )
869 items += nodeBuildDragList( static_cast<MediaItem*>(fi->firstChild()), None );
872 else
874 if ( fi->childCount() )
875 items += nodeBuildDragList( static_cast<MediaItem*>(fi->firstChild()), OnlySelected );
878 fi = static_cast<MediaItem*>(fi->nextSibling());
880 return items;
884 MediaView::getSelectedLeaves( MediaItem *parent, Q3PtrList<MediaItem> *list, int flags )
886 int numFiles = 0;
887 if( !list )
888 list = new Q3PtrList<MediaItem>;
890 MediaItem *it;
891 if( !parent )
892 it = static_cast<MediaItem *>(firstChild());
893 else
894 it = static_cast<MediaItem *>(parent->firstChild());
896 for( ; it; it = static_cast<MediaItem*>(it->nextSibling()))
898 if( it->isVisible() )
900 if( it->childCount() && !( it->type() == MediaItem::DIRECTORY && it->isSelected() ) )
902 int f = flags;
903 if( it->isSelected() )
904 f &= ~OnlySelected;
905 numFiles += getSelectedLeaves(it, list, f );
907 if( it->isSelected() || !(flags & OnlySelected) )
909 if( it->type() == MediaItem::TRACK ||
910 it->type() == MediaItem::DIRECTORY ||
911 it->type() == MediaItem::PODCASTITEM ||
912 it->type() == MediaItem::PLAYLISTITEM||
913 it->type() == MediaItem::INVISIBLE ||
914 it->type() == MediaItem::ORPHANED )
916 if( flags & OnlyPlayed )
918 if( it->played() > 0 )
919 numFiles++;
921 else
922 numFiles++;
924 if( ( it->isLeafItem() && (!(flags & OnlyPlayed) || it->played()>0) )
925 || it->type() == MediaItem::DIRECTORY )
926 list->append( it );
930 return numFiles;
934 bool
935 MediaView::acceptDrag( QDropEvent *e ) const
937 if( e->source() == MediaBrowser::queue()->viewport() )
938 return false;
940 return e->source() == viewport()
941 || e->mimeData()->hasFormat( "amarok-sql" )
942 || KUrl::List::canDecode( e->mimeData() );
945 void
946 MediaView::contentsDropEvent( QDropEvent *e )
948 cleanDropVisualizer();
949 cleanItemHighlighter();
951 if(e->source() == viewport())
953 const QPoint p = contentsToViewport( e->pos() );
954 MediaItem *item = static_cast<MediaItem *>(itemAt( p ));
956 if( !item && MediaBrowser::instance()->currentDevice()->m_type != "generic-mediadevice" )
957 return;
959 Q3PtrList<MediaItem> items;
961 if( !item || item->type() == MediaItem::DIRECTORY ||
962 item->type() == MediaItem::TRACK )
964 Q3PtrList<MediaItem> items;
965 getSelectedLeaves( 0, &items );
966 m_device->addToDirectory( item, items );
968 else if( item->type() == MediaItem::PLAYLIST )
970 MediaItem *list = item;
971 MediaItem *after = 0;
972 for(MediaItem *it = static_cast<MediaItem *>(item->firstChild());
974 it = static_cast<MediaItem *>(it->nextSibling()))
975 after = it;
977 getSelectedLeaves( 0, &items );
978 m_device->addToPlaylist( list, after, items );
980 else if( item->type() == MediaItem::PLAYLISTITEM )
982 MediaItem *list = static_cast<MediaItem *>(item->parent());
983 MediaItem *after = 0;
984 for(MediaItem *it = static_cast<MediaItem*>(item->parent()->firstChild());
986 it = static_cast<MediaItem *>(it->nextSibling()))
988 if(it == item)
989 break;
990 after = it;
993 getSelectedLeaves( 0, &items );
994 m_device->addToPlaylist( list, after, items );
996 else if( item->type() == MediaItem::PLAYLISTSROOT )
998 Q3PtrList<MediaItem> items;
999 getSelectedLeaves( 0, &items );
1000 QString base( i18n("New Playlist") );
1001 QString name = base;
1002 int i=1;
1003 while( item->findItem(name) )
1005 QString num;
1006 num.setNum(i);
1007 name = base + ' ' + num;
1008 i++;
1010 MediaItem *pl = m_device->newPlaylist(name, item, items);
1011 ensureItemVisible(pl);
1012 rename(pl, 0);
1015 else
1017 if( e->mimeData()->hasFormat( "amarok-sql" ) )
1019 QString data( e->mimeData()->data( "amarok-sql" ) );
1020 QString playlist = data.section( "\n", 0, 0 );
1021 QString query = data.section( "\n", 1 );
1022 QStringList values = CollectionDB::instance()->query( query );
1023 KUrl::List list = CollectionDB::instance()->URLsFromSqlDrag( values );
1024 MediaBrowser::queue()->addUrls( list, playlist );
1026 else if ( KUrl::List::canDecode( e->mimeData() ) )
1028 KUrl::List list = KUrl::List::fromMimeData( e->mimeData() );
1029 MediaBrowser::queue()->addUrls( list );
1034 void
1035 MediaView::viewportPaintEvent( QPaintEvent *e )
1037 K3ListView::viewportPaintEvent( e );
1039 // Superimpose bubble help:
1041 if ( !MediaBrowser::instance()->currentDevice() || !MediaBrowser::instance()->currentDevice()->isConnected() )
1043 QPainter p( viewport() );
1045 Q3SimpleRichText t( i18n(
1046 "<div align=center>"
1047 "<h3>Media Device Browser</h3>"
1048 "Configure your media device and then "
1049 "click the Connect button to access your media device. "
1050 "Drag and drop files to enqueue them for transfer."
1051 "</div>" ), QApplication::font() );
1053 t.setWidth( width() - 50 );
1055 const uint w = t.width() + 20;
1056 const uint h = t.height() + 20;
1058 p.setBrush( palette().background() );
1059 p.drawRoundRect( 15, 15, w, h, (8*200)/w, (8*200)/h );
1060 t.draw( &p, 20, 20, QRect(), palette() );
1062 MediaBrowser::instance()->updateButtons();
1065 void
1066 MediaView::rmbPressed( Q3ListViewItem *item, const QPoint &p, int i )
1068 if( m_device->isConnected() )
1069 m_device->rmbPressed( item, p, i );
1072 MediaItem *
1073 MediaView::newDirectory( MediaItem *parent )
1075 bool ok;
1076 const QString name = KInputDialog::getText(i18n("Add Directory"), i18n("Directory Name:"), QString(), &ok, this);
1078 if( ok && !name.isEmpty() )
1080 m_device->newDirectory( name, parent );
1083 return 0;
1086 void
1087 MediaBrowser::deviceAdded( const QString &udi )
1089 MediaDevice *md = loadDevicePlugin( udi );
1090 if( md )
1092 addDevice( md );
1093 if( m_currentDevice == *(m_devices.begin()) || m_currentDevice == *(m_devices.end()) )
1094 activateDevice( m_devices.count()-1, false );
1098 void
1099 MediaBrowser::deviceRemoved( const QString &udi )
1101 for( QList<MediaDevice *>::iterator it = m_devices.begin();
1102 it != m_devices.end();
1103 it++ )
1105 if( (*it)->m_uid == udi )
1107 if( (*it)->isConnected() )
1109 if( (*it)->disconnectDevice() )
1110 removeDevice( *it );
1111 Amarok::StatusBar::instance()->longMessage(
1112 i18n( "The device %1 was removed before it was disconnected. "
1113 "In order to avoid possible data loss, press the \"Disconnect\" "
1114 "button before disconnecting the device.", (*it)->name() ),
1115 KDE::StatusBar::Warning );
1117 else
1118 removeDevice( *it );
1119 break;
1124 MediaDevice *
1125 MediaBrowser::loadDevicePlugin( const QString &udi )
1127 DEBUG_BLOCK
1129 Solid::Device solidDevice( udi );
1131 Solid::PortableMediaPlayer* pmp = solidDevice.as<Solid::PortableMediaPlayer>();
1133 //TODO: Generic storage?
1134 if( !pmp )
1136 debug() << "Failed to convert Solid device to PortableMediaPlayer";
1137 return 0;
1139 if( pmp->supportedProtocols().size() == 0 )
1141 debug() << "Portable Media Player " << udi << " does not support any protocols";
1142 return 0;
1145 QString protocol = pmp->supportedProtocols()[0];
1146 if( protocol == "storage" )
1147 protocol = "generic";
1148 if( protocol == "pde" )
1149 protocol == "njb";
1151 protocol += "-mediadevice";
1152 QString query = "[X-KDE-Amarok-plugintype] == 'mediadevice' and [X-KDE-Amarok-name] == '%1'";
1153 Amarok::Plugin *plugin = PluginManager::createFromQuery( query.arg( protocol ) );
1155 if( plugin )
1157 debug() << "Returning plugin!";
1158 MediaDevice *device = static_cast<MediaDevice *>( plugin );
1159 device->init( this );
1160 device->m_uid = solidDevice.udi();
1161 device->m_name = solidDevice.product();
1162 device->m_type = protocol;
1163 return device;
1166 debug() << "no plugin for " << protocol;
1167 return 0;
1170 void
1171 MediaBrowser::unloadDevicePlugin( MediaDevice *device )
1173 DEBUG_BLOCK
1175 if( !device )
1176 return;
1178 disconnect( device ); // disconnect all signals
1180 if( dynamic_cast<DummyMediaDevice *>(device) )
1182 delete device;
1184 else
1186 PluginManager::unload( device );
1190 bool
1191 MediaBrowser::config()
1193 if( m_deviceCombo->currentText() == "No Device Selected" )
1195 Amarok::StatusBar::instance()->longMessage( i18n( "No device selected to configure." ),
1196 KDE::StatusBar::Sorry );
1197 return true;
1200 DeviceConfigureDialog* dcd = new DeviceConfigureDialog( m_currentDevice->m_medium );
1201 dcd->exec();
1202 bool successful = dcd->successful();
1203 delete dcd;
1204 return successful;
1207 void
1208 MediaBrowser::updateButtons()
1210 if( !connectAction() || !disconnectAction() || !transferAction() )
1211 return;
1213 if( m_currentDevice )
1215 transferAction()->setVisible( m_currentDevice->m_transfer );
1216 customAction()->setVisible( m_currentDevice->m_customButton );
1217 configAction()->setVisible( m_currentDevice->m_configure );
1219 connectAction()->setEnabled( !m_currentDevice->isConnected() );
1220 disconnectAction()->setEnabled( m_currentDevice->isConnected() );
1221 transferAction()->setEnabled( m_currentDevice->isConnected() && m_queue->childCount() > 0 );
1222 if( customAction() )
1223 customAction()->setEnabled( true );
1225 else
1227 connectAction()->setEnabled( false );
1228 disconnectAction()->setEnabled( false );
1229 transferAction()->setEnabled( false );
1230 if( customAction() )
1231 customAction()->setEnabled( false );
1235 void
1236 MediaBrowser::updateStats()
1238 if( !m_stats )
1239 return;
1241 KIO::filesize_t queued = m_queue->totalSize();
1243 QString text = i18np( "1 track in queue", "%1 tracks in queue", m_queue->childCount() );
1244 if(m_queue->childCount() > 0)
1246 text += i18n(" (%1)", KIO::convertSize( queued ) );
1249 KIO::filesize_t total, avail;
1250 if( m_currentDevice && m_currentDevice->getCapacity(&total, &avail) )
1252 text += i18n( " - %1 of %2 available", KIO::convertSize( avail ), KIO::convertSize( total ) );
1254 m_stats->m_used = total-avail;
1255 m_stats->m_total = total;
1256 m_stats->m_scheduled = queued;
1258 else
1260 m_stats->m_used = 0;
1261 m_stats->m_total = 0;
1262 m_stats->m_scheduled = queued;
1265 m_stats->setText(text);
1266 m_stats->setToolTip( text );
1270 bool
1271 MediaView::setFilter( const QString &filter, MediaItem *parent )
1273 bool advanced = ExpressionParser::isAdvancedExpression( filter );
1274 QList<int> defaultColumns;
1275 defaultColumns << MetaBundle::Album;
1276 defaultColumns << MetaBundle::Title;
1277 defaultColumns << MetaBundle::Artist;
1279 bool root = false;
1280 MediaItem *it;
1281 if( !parent )
1283 root = true;
1284 it = static_cast<MediaItem *>(firstChild());
1286 else
1288 it = static_cast<MediaItem *>(parent->firstChild());
1291 bool childrenVisible = false;
1292 for( ; it; it = static_cast<MediaItem *>(it->nextSibling()))
1294 bool visible = true;
1295 if(it->isLeafItem())
1297 if( advanced )
1299 ParsedExpression parsed = ExpressionParser::parse( filter );
1300 visible = it->bundle() && it->bundle()->matchesParsedExpression( parsed, defaultColumns );
1302 else
1304 visible = it->bundle() && it->bundle()->matchesSimpleExpression( filter, defaultColumns );
1307 else
1309 visible = setFilter(filter, it);
1310 if(it->type()==MediaItem::PLAYLISTSROOT || it->type()==MediaItem::PLAYLIST)
1312 visible = true;
1314 else if(it->type()==MediaItem::DIRECTORY)
1316 bool match = true;
1317 QStringList list = filter.split( " ", QString::SkipEmptyParts );
1318 for( QStringList::iterator i = list.begin();
1319 i != list.end();
1320 ++i )
1322 if( !(*it).text(0).contains( *i ) )
1324 match = false;
1325 break;
1328 if( match )
1329 visible = true;
1332 if( filter.isEmpty() )
1333 visible = true;
1334 it->setVisible( visible );
1335 if(visible)
1336 childrenVisible = true;
1339 if( root && m_device )
1340 m_device->updateRootItems();
1342 return childrenVisible;
1345 MediaDevice::MediaDevice()
1346 : Amarok::Plugin()
1347 , m_name( QString() )
1348 , m_hasMountPoint( true )
1349 , m_autoDeletePodcasts( false )
1350 , m_syncStats( false )
1351 , m_transcode( false )
1352 , m_transcodeAlways( false )
1353 , m_transcodeRemove( false )
1354 , sysProc ( 0 )
1355 , m_parent( 0 )
1356 , m_view( 0 )
1357 , m_uid( QString() )
1358 , m_wait( false )
1359 , m_requireMount( false )
1360 , m_canceled( false )
1361 , m_transferring( false )
1362 , m_deleting( false )
1363 , m_deferredDisconnect( false )
1364 , m_scheduledDisconnect( false )
1365 , m_transfer( true )
1366 , m_configure( true )
1367 , m_customButton( false )
1368 , m_playlistItem( 0 )
1369 , m_podcastItem( 0 )
1370 , m_invisibleItem( 0 )
1371 , m_staleItem( 0 )
1372 , m_orphanedItem( 0 )
1374 sysProc = new K3ShellProcess(); Q_CHECK_PTR(sysProc);
1377 void MediaDevice::init( MediaBrowser* parent )
1379 m_parent = parent;
1380 //if( !m_view )
1381 // m_view = new MediaView( m_parent->m_views, this );
1382 //m_view->hide();
1385 MediaDevice::~MediaDevice()
1387 delete m_view;
1388 delete sysProc;
1391 bool
1392 MediaDevice::isSpecialItem( MediaItem *item )
1394 return (item == m_playlistItem) ||
1395 (item == m_podcastItem) ||
1396 (item == m_invisibleItem) ||
1397 (item == m_staleItem) ||
1398 (item == m_orphanedItem);
1401 void
1402 MediaDevice::loadConfig()
1404 m_transcode = configBool( "Transcode" );
1405 m_transcodeAlways = configBool( "TranscodeAlways" );
1406 m_transcodeRemove = configBool( "TranscodeRemove" );
1407 m_preconnectcmd = configString( "PreConnectCommand" );
1408 if( m_preconnectcmd.isEmpty() )
1409 m_preconnectcmd = configString( "MountCommand" );
1410 m_postdisconnectcmd = configString( "PostDisconnectCommand" );
1411 if( m_postdisconnectcmd.isEmpty() )
1412 m_postdisconnectcmd = configString( "UmountCommand" );
1413 if( m_requireMount && m_postdisconnectcmd.isEmpty() )
1414 m_postdisconnectcmd = "kdeeject -q %d";
1417 QString
1418 MediaDevice::configString( const QString &name, const QString &defValue )
1420 QString configName = "MediaDevice";
1421 if( !uniqueId().isEmpty() )
1422 configName += '_' + uniqueId();
1423 KConfigGroup config = Amarok::config( configName );
1424 return config.readEntry( name, defValue );
1427 void
1428 MediaDevice::setConfigString( const QString &name, const QString &value )
1430 QString configName = "MediaDevice";
1431 if( !uniqueId().isEmpty() )
1432 configName += '_' + uniqueId();
1433 KConfigGroup config = Amarok::config( configName );
1434 config.writeEntry( name, value );
1437 bool
1438 MediaDevice::configBool( const QString &name, bool defValue )
1440 QString configName = "MediaDevice";
1441 if( !uniqueId().isEmpty() )
1442 configName += '_' + uniqueId();
1443 KConfigGroup config = Amarok::config( configName );
1444 return config.readEntry( name, defValue );
1447 void
1448 MediaDevice::setConfigBool( const QString &name, bool value )
1450 QString configName = "MediaDevice";
1451 if( !uniqueId().isEmpty() )
1452 configName += '_' + uniqueId();
1453 KConfigGroup config = Amarok::config( configName );
1454 config.writeEntry( name, value );
1457 MediaView *
1458 MediaDevice::view()
1460 return m_view;
1463 void
1464 MediaDevice::hideProgress()
1466 m_parent->m_progressBox->hide();
1469 void
1470 MediaDevice::updateRootItems()
1472 if(m_podcastItem)
1473 m_podcastItem->setVisible(m_podcastItem->childCount() > 0);
1474 if(m_invisibleItem)
1475 m_invisibleItem->setVisible(m_invisibleItem->childCount() > 0);
1476 if(m_staleItem)
1477 m_staleItem->setVisible(m_staleItem->childCount() > 0);
1478 if(m_orphanedItem)
1479 m_orphanedItem->setVisible(m_orphanedItem->childCount() > 0);
1482 void
1483 MediaQueue::syncPlaylist( const QString &name, const QString &query, bool loading )
1485 MediaItem* item = new MediaItem( this, lastItem() );
1486 item->setType( MediaItem::PLAYLIST );
1487 item->setExpandable( false );
1488 item->setData( query );
1489 item->m_playlistName = name;
1490 item->setText( 0, name );
1491 item->m_flags |= MediaItem::SmartPlaylist;
1492 m_parent->m_progress->setRange( 0, m_parent->m_progress->maximum() + 1 );
1493 itemCountChanged();
1494 if( !loading )
1495 URLsAdded();
1498 void
1499 MediaQueue::syncPlaylist( const QString &name, const KUrl &url, bool loading )
1501 MediaItem* item = new MediaItem( this, lastItem() );
1502 item->setType( MediaItem::PLAYLIST );
1503 item->setExpandable( false );
1504 item->setData( url.url() );
1505 item->m_playlistName = name;
1506 item->setText( 0, name );
1507 m_parent->m_progress->setRange( 0, m_parent->m_progress->maximum() + 1 );
1508 itemCountChanged();
1509 if( !loading )
1510 URLsAdded();
1513 BundleList
1514 MediaDevice::bundlesToSync( const QString &name, const KUrl &url )
1516 //PORT 2.0
1517 // BundleList bundles;
1518 // if( !PlaylistFile::isPlaylistFile( url ) )
1519 // {
1520 // Amarok::StatusBar::instance()->longMessage( i18n( "Not a playlist file: %1", url.path() ),
1521 // KDE::StatusBar::Sorry );
1522 // return bundles;
1523 // }
1525 // PlaylistFile playlist( url.path() );
1526 // if( playlist.isError() )
1527 // {
1528 // Amarok::StatusBar::instance()->longMessage( i18n( "Failed to load playlist: %1", url.path() ),
1529 // KDE::StatusBar::Sorry );
1530 // return bundles;
1531 // }
1533 // for( BundleList::iterator it = playlist.bundles().begin();
1534 // it != playlist.bundles().end();
1535 // ++it )
1536 // {
1537 // bundles += MetaBundle( (*it).url() );
1538 // }
1539 // preparePlaylistForSync( name, bundles );
1540 // return bundles;
1543 BundleList
1544 MediaDevice::bundlesToSync( const QString &name, const QString &query )
1546 const QStringList values = CollectionDB::instance()->query( query );
1548 BundleList bundles;
1549 for( QStringList::const_iterator it = values.begin(); it != values.end(); ++it )
1550 bundles += CollectionDB::instance()->bundleFromQuery( &it );
1551 preparePlaylistForSync( name, bundles );
1552 return bundles;
1555 void
1556 MediaDevice::preparePlaylistForSync( const QString &name, const BundleList &bundles )
1558 if( ! m_playlistItem ) // might be syncing a new playlist from the playlist browser
1559 return;
1560 MediaItem *pl = m_playlistItem->findItem( name );
1561 if( pl )
1563 MediaItem *next = 0;
1564 for( MediaItem *it = static_cast<MediaItem *>(pl->firstChild());
1566 it = next )
1568 next = static_cast<MediaItem *>(it->nextSibling());
1569 const MetaBundle *bundle = (*it).bundle();
1570 if( !bundle )
1571 continue;
1572 if( isOnOtherPlaylist( name, *bundle ) )
1573 continue;
1574 if( isInBundleList( bundles, *bundle ) )
1575 continue;
1576 deleteItemFromDevice( it );
1578 deleteItemFromDevice( pl, None );
1580 purgeEmptyItems();
1583 bool
1584 MediaDevice::bundleMatch( const MetaBundle &b1, const MetaBundle &b2 )
1586 if( b1.track() != b2.track() )
1587 return false;
1588 if( b1.title() != b2.title() )
1589 return false;
1590 if( b1.album() != b2.album() )
1591 return false;
1592 if( b1.artist() != b2.artist() )
1593 return false;
1594 #if 0
1595 if( b1.discNumber() != b2.discNumber() )
1596 return false;
1597 if( b1.composer() != b2.composer() )
1598 return false;
1599 #endif
1601 return true;
1604 bool
1605 MediaDevice::isInBundleList( const BundleList &bundles, const MetaBundle &b )
1607 for( BundleList::const_iterator it = bundles.begin();
1608 it != bundles.end();
1609 ++it )
1611 if( bundleMatch( b, *it ) )
1612 return true;
1615 return false;
1618 bool
1619 MediaDevice::isOnOtherPlaylist( const QString &playlistToAvoid, const MetaBundle &bundle )
1621 for( MediaItem *it = static_cast<MediaItem *>(m_playlistItem->firstChild());
1623 it = static_cast<MediaItem *>(it->nextSibling()) )
1625 if( it->text( 0 ) == playlistToAvoid )
1626 continue;
1627 if( isOnPlaylist( *it, bundle ) )
1628 return true;
1631 return false;
1634 bool
1635 MediaDevice::isOnPlaylist( const MediaItem &playlist, const MetaBundle &bundle )
1637 for( MediaItem *it = static_cast<MediaItem *>(playlist.firstChild());
1639 it = static_cast<MediaItem *>(it->nextSibling()) )
1641 const MetaBundle *b = (*it).bundle();
1642 if( !b )
1643 continue;
1644 if( bundleMatch( *b, bundle ) )
1645 return true;
1648 return false;
1651 void
1652 MediaQueue::addUrl( const KUrl& url2, MetaBundle *bundle, const QString &playlistName )
1654 KUrl url = Amarok::mostLocalURL( url2 );
1656 //Port 2.0
1657 // if( PlaylistFile::isPlaylistFile( url ) )
1658 // {
1659 // QString name = url.path().section( "/", -1 ).section( ".", 0, -2 ).replace( "_", " " );
1660 // PlaylistFile playlist( url.path() );
1662 // if( playlist.isError() )
1663 // {
1664 // Amarok::StatusBar::instance()->longMessage( i18n( "Failed to load playlist: %1", url.path() ),
1665 // KDE::StatusBar::Sorry );
1666 // return;
1667 // }
1669 // for( BundleList::iterator it = playlist.bundles().begin();
1670 // it != playlist.bundles().end();
1671 // ++it )
1672 // {
1673 // addUrl( (*it).url(), 0, name );
1674 // }
1675 // return;
1676 // }
1677 if( url.protocol() == "file" && QFileInfo( url.path() ).isDir() )
1679 //TODO: PORT
1680 // KUrl::List urls = Amarok::recursiveUrlExpand( url );
1681 // oldForeachType( KUrl::List, urls )
1682 // addUrl( *it );
1683 // return;
1686 if( playlistName.isNull() )
1688 for( MediaItem *it = static_cast<MediaItem *>(firstChild());
1690 it = static_cast<MediaItem *>(it->nextSibling()) )
1692 if( it->url() == url )
1694 Amarok::StatusBar::instance()->shortMessage(
1695 i18n( "Track already queued for transfer: %1", url.url() ) );
1696 return;
1701 if(!bundle)
1702 bundle = new MetaBundle( url );
1704 MediaItem* item = new MediaItem( this, lastItem() );
1705 item->setExpandable( false );
1706 item->setDropEnabled( true );
1707 item->setBundle( bundle );
1708 if(bundle->podcastBundle() )
1710 item->setType( MediaItem::PODCASTITEM );
1712 item->m_playlistName = playlistName;
1714 QString text = item->bundle()->prettyTitle();
1715 if( text.isEmpty() || (!item->bundle()->isValidMedia() && !item->bundle()->podcastBundle()) )
1716 text = item->bundle()->url().prettyUrl();
1717 if( !item->m_playlistName.isNull() )
1719 text += " (" + item->m_playlistName + ')';
1721 item->setText( 0, text);
1723 m_parent->updateButtons();
1724 m_parent->m_progress->setRange( 0, m_parent->m_progress->maximum() + 1 );
1725 addItemToSize( item );
1726 itemCountChanged();
1729 void
1730 MediaQueue::addUrl( const KUrl &url, MediaItem *item )
1732 DEBUG_BLOCK
1733 MediaItem *newitem = new MediaItem( this, lastItem() );
1734 newitem->setExpandable( false );
1735 newitem->setDropEnabled( true );
1736 MetaBundle *bundle = new MetaBundle( *item->bundle() );
1737 KUrl filepath(url);
1738 filepath.addPath( bundle->filename() );
1739 bundle->setUrl( filepath );
1740 newitem->m_device = item->m_device;
1741 if(bundle->podcastBundle() )
1743 item->setType( MediaItem::PODCASTITEM );
1745 QString text = item->bundle()->prettyTitle();
1746 if( text.isEmpty() || (!item->bundle()->isValidMedia() && !item->bundle()->podcastBundle()) )
1747 text = item->bundle()->url().prettyUrl();
1748 if( !item->m_playlistName.isEmpty() )
1750 text += " (" + item->m_playlistName + ')';
1752 newitem->setText( 0, text);
1753 newitem->setBundle( bundle );
1754 m_parent->updateButtons();
1755 m_parent->m_progress->setRange( 0, m_parent->m_progress->maximum() + 1 );
1756 addItemToSize( item );
1757 itemCountChanged();
1761 void
1762 MediaQueue::addUrls( const KUrl::List urls, const QString &playlistName )
1764 KUrl::List::ConstIterator it = urls.begin();
1765 for ( ; it != urls.end(); ++it )
1766 addUrl( *it, 0, playlistName );
1768 URLsAdded();
1771 void
1772 MediaQueue::URLsAdded()
1774 m_parent->updateStats();
1775 m_parent->updateButtons();
1776 if( m_parent->currentDevice()
1777 && m_parent->currentDevice()->isConnected()
1778 && m_parent->currentDevice()->asynchronousTransfer()
1779 && !m_parent->currentDevice()->isTransferring() )
1780 m_parent->currentDevice()->transferFiles();
1782 save( Amarok::saveLocation() + "transferlist.xml" );
1785 void
1786 MediaDevice::copyTrackFromDevice( MediaItem *item )
1788 debug() << "copyTrackFromDevice: not copying " << item->url() << ": not implemented";
1791 Q3DragObject *
1792 MediaQueue::dragObject()
1794 KUrl::List urls;
1795 for( Q3ListViewItem *it = firstChild(); it; it = it->nextSibling() )
1797 if( it->isVisible() && it->isSelected() && static_cast<MediaItem *>(it) )
1798 urls += static_cast<MediaItem *>(it)->url();
1801 K3MultipleDrag *md = new K3MultipleDrag( viewport() );
1802 Q3DragObject *d = K3ListView::dragObject();
1803 K3URLDrag* urldrag = new K3URLDrag( urls, viewport() );
1804 md->addDragObject( d );
1805 md->addDragObject( urldrag );
1806 md->setPixmap( CollectionDB::createDragPixmap( urls ),
1807 QPoint( CollectionDB::DRAGPIXMAP_OFFSET_X, CollectionDB::DRAGPIXMAP_OFFSET_Y ) );
1808 return md;
1811 QString
1812 MediaDevice::replaceVariables( const QString &cmd )
1814 QString result = cmd;
1815 result.replace( "%d", deviceNode() );
1816 result.replace( "%m", mountPoint() );
1817 return result;
1820 int MediaDevice::runPreConnectCommand()
1822 if( m_preconnectcmd.isEmpty() )
1823 return 0;
1825 QString cmd = replaceVariables( m_preconnectcmd );
1827 debug() << "running pre-connect command: [" << cmd << "]";
1828 int e=sysCall(cmd);
1829 debug() << "pre-connect: e=" << e;
1830 return e;
1833 int MediaDevice::runPostDisconnectCommand()
1835 if( m_postdisconnectcmd.isEmpty() )
1836 return 0;
1838 QString cmd = replaceVariables( m_postdisconnectcmd );
1839 debug() << "running post-disconnect command: [" << cmd << "]";
1840 int e=sysCall(cmd);
1841 debug() << "post-disconnect: e=" << e;
1843 return e;
1846 int MediaDevice::sysCall( const QString &command )
1848 if ( sysProc->isRunning() ) return -1;
1850 sysProc->clearArguments();
1851 (*sysProc) << command;
1852 if (!sysProc->start( K3Process::Block, K3Process::AllOutput ))
1853 kFatal() << i18n("could not execute %1", command.toLocal8Bit().data());
1855 return (sysProc->exitStatus());
1858 void
1859 MediaDevice::abortTransfer()
1861 setCanceled( true );
1862 cancelTransfer();
1865 bool
1866 MediaDevice::kioCopyTrack( const KUrl &src, const KUrl &dst )
1868 m_wait = true;
1870 KIO::FileCopyJob *job = KIO::file_copy( src, dst,
1871 -1 /* permissions */,
1872 false /* overwrite */,
1873 false /* resume */,
1874 false /* show progress */ );
1875 connect( job, SIGNAL( result( KIO::Job * ) ),
1876 this, SLOT( fileTransferred( KIO::Job * ) ) );
1878 bool tryToRemove = false;
1879 while ( m_wait )
1881 if( isCanceled() )
1883 job->kill( KJob::EmitResult );
1884 tryToRemove = true;
1885 m_wait = false;
1887 else
1889 usleep(10000);
1890 kapp->processEvents( QEventLoop::ExcludeUserInputEvents );
1894 if( !tryToRemove )
1896 if(m_copyFailed)
1898 tryToRemove = true;
1899 Amarok::StatusBar::instance()->longMessage(
1900 i18n( "Media Device: Copying %1 to %2 failed" )
1901 .arg( src.prettyUrl(), dst.prettyUrl() ),
1902 KDE::StatusBar::Error );
1904 else
1906 MetaBundle bundle2(dst);
1907 if(!bundle2.isValidMedia() && bundle2.filesize()==MetaBundle::Undetermined)
1909 tryToRemove = true;
1910 // probably s.th. went wrong
1911 Amarok::StatusBar::instance()->longMessage(
1912 i18n( "Media Device: Reading tags from %1 failed", dst.prettyUrl() ),
1913 KDE::StatusBar::Error );
1918 if( tryToRemove )
1920 QFile::remove( dst.path() );
1921 return false;
1924 return true;
1927 void
1928 MediaDevice::fileTransferred( KIO::Job *job ) //SLOT
1930 if(job->error())
1932 m_copyFailed = true;
1933 debug() << "file transfer failed: " << job->errorText();
1935 else
1937 m_copyFailed = false;
1940 m_wait = false;
1943 void
1944 MediaBrowser::cancelClicked()
1946 DEBUG_BLOCK
1948 m_waitForTranscode = false;
1949 if( m_currentDevice )
1950 m_currentDevice->abortTransfer();
1953 void
1954 MediaBrowser::transferClicked()
1956 transferAction()->setEnabled( false );
1957 if( m_currentDevice
1958 && m_currentDevice->isConnected()
1959 && !m_currentDevice->isTransferring() )
1961 if( !m_currentDevice->hasTransferDialog() )
1962 m_currentDevice->transferFiles();
1963 else
1965 m_currentDevice->runTransferDialog();
1966 //may not work with non-TransferDialog-class object, but maybe some run time introspection could solve it?
1967 if( m_currentDevice->getTransferDialog() &&
1968 ( reinterpret_cast<TransferDialog *>(m_currentDevice->getTransferDialog()))->isAccepted() )
1969 m_currentDevice->transferFiles();
1970 else
1971 updateButtons();
1974 m_currentDevice->m_transferDir = m_currentDevice->m_medium.mountPoint();
1977 void
1978 MediaBrowser::connectClicked()
1980 bool haveToConfig = false;
1981 // it was just clicked, so isOn() == true.
1982 if( m_currentDevice && !m_currentDevice->isConnected() )
1984 haveToConfig = !m_currentDevice->connectDevice();
1987 haveToConfig |= !m_currentDevice;
1988 haveToConfig |= ( m_currentDevice && !m_currentDevice->isConnected() );
1990 if ( !m_currentDevice->needsManualConfig() )
1991 haveToConfig = false;
1993 if( haveToConfig && m_devices.at( 0 ) == m_currentDevice )
1995 if( config() && m_currentDevice && !m_currentDevice->isConnected() )
1996 m_currentDevice->connectDevice();
1999 updateDevices();
2000 updateButtons();
2001 updateStats();
2005 void
2006 MediaBrowser::disconnectClicked()
2008 if( m_currentDevice && m_currentDevice->isTransferring() )
2010 int action = KMessageBox::questionYesNoCancel( MediaBrowser::instance(),
2011 i18n( "Transfer in progress. Finish or stop after current track?" ),
2012 i18n( "Stop Transfer?" ),
2013 KGuiItem(i18n("&Finish"), "goto-page"),
2014 KGuiItem(i18n("&Stop"), "media-eject") );
2015 if( action == KMessageBox::Cancel )
2017 return;
2019 else if( action == KMessageBox::Yes )
2021 m_currentDevice->scheduleDisconnect();
2022 return;
2026 transferAction()->setEnabled( false );
2027 disconnectAction()->setEnabled( false );
2029 if( m_currentDevice )
2031 m_currentDevice->disconnectDevice( true );
2034 updateDevices();
2035 updateButtons();
2036 updateStats();
2039 void
2040 MediaBrowser::customClicked()
2042 if( m_currentDevice )
2043 m_currentDevice->customClicked();
2046 bool
2047 MediaDevice::connectDevice( bool silent )
2049 if( !lockDevice( true ) )
2050 return false;
2052 runPreConnectCommand();
2053 openDevice( silent );
2055 if( isConnected()
2056 && MediaBrowser::instance()->currentDevice() != this
2057 && MediaBrowser::instance()->currentDevice()
2058 && !MediaBrowser::instance()->currentDevice()->isConnected() )
2060 MediaBrowser::instance()->activateDevice( this );
2062 m_parent->updateStats();
2063 m_parent->updateButtons();
2065 if( !isConnected() )
2067 unlockDevice();
2068 return false;
2071 if( m_syncStats )
2073 syncStatsFromDevice( 0 );
2074 Scrobbler::instance()->m_submitter->syncComplete();
2077 // delete podcasts already played
2078 if( m_autoDeletePodcasts && m_podcastItem )
2080 Q3PtrList<MediaItem> list;
2081 //NOTE we assume that currentItem is the main target
2082 int numFiles = m_view->getSelectedLeaves( m_podcastItem, &list, MediaView::OnlyPlayed );
2084 if(numFiles > 0)
2086 m_parent->m_stats->setText( i18np( "1 track to be deleted", "%1 tracks to be deleted", numFiles ) );
2088 setProgress( 0, numFiles );
2090 int numDeleted = deleteItemFromDevice( m_podcastItem, true );
2091 purgeEmptyItems();
2092 if( numDeleted < 0 )
2094 Amarok::StatusBar::instance()->longMessage(
2095 i18n( "Failed to purge podcasts already played" ),
2096 KDE::StatusBar::Sorry );
2098 else if( numDeleted > 0 )
2100 Amarok::StatusBar::instance()->shortMessage(
2101 i18np( "Purged 1 podcasts already played",
2102 "Purged %1 podcasts already played",
2103 numDeleted ) );
2106 synchronizeDevice();
2108 QTimer::singleShot( 1500, m_parent->m_progressBox, SLOT(hide()) );
2109 m_parent->queue()->computeSize();
2110 m_parent->updateStats();
2113 unlockDevice();
2115 updateRootItems();
2117 if( m_deferredDisconnect )
2119 m_deferredDisconnect = false;
2120 disconnectDevice( m_runDisconnectHook );
2123 Amarok::StatusBar::instance()->shortMessage( i18n( "Device successfully connected" ) );
2125 m_parent->updateDevices();
2127 return true;
2130 bool
2131 MediaDevice::disconnectDevice( bool postDisconnectHook )
2133 DEBUG_BLOCK
2135 abortTransfer();
2137 debug() << "disconnecting: hook=" << postDisconnectHook;
2139 if( !lockDevice( true ) )
2141 m_runDisconnectHook = postDisconnectHook;
2142 m_deferredDisconnect = true;
2143 debug() << "disconnecting: locked";
2144 return false;
2146 debug() << "disconnecting: ok";
2148 if( m_syncStats )
2150 syncStatsToDevice();
2153 closeDevice();
2154 unlockDevice();
2156 m_parent->updateStats();
2158 bool result = true;
2159 if( postDisconnectHook && runPostDisconnectCommand() != 0 )
2161 Amarok::StatusBar::instance()->longMessage(
2162 i18n( "Post-disconnect command failed, before removing device, please make sure that it is safe to do so." ),
2163 KDE::StatusBar::Information );
2164 result = false;
2166 else
2167 Amarok::StatusBar::instance()->shortMessage( i18n( "Device successfully disconnected" ) );
2169 m_parent->updateDevices();
2171 return result;
2174 void
2175 MediaDevice::syncStatsFromDevice( MediaItem *root )
2177 MediaItem *it = static_cast<MediaItem *>( m_view->firstChild() );
2178 if( root )
2180 it = static_cast<MediaItem *>( root->firstChild() );
2183 kapp->processEvents( QEventLoop::ExcludeUserInputEvents );
2185 for( ; it; it = static_cast<MediaItem *>( it->nextSibling() ) )
2187 switch( it->type() )
2189 case MediaItem::TRACK:
2190 if( !it->parent() || static_cast<MediaItem *>( it->parent() )->type() != MediaItem::PLAYLIST )
2192 const MetaBundle *bundle = it->bundle();
2193 for( int i=0; i<it->recentlyPlayed(); i++ )
2195 // submit to last.fm
2196 if( bundle->length() > 30
2197 && !bundle->artist().isEmpty() && bundle->artist() != i18n( "Unknown" )
2198 && !bundle->title().isEmpty() && bundle->title() != i18n( "Unknown" ) )
2200 // don't submit tracks shorter than 30 sec or w/o artist/title
2201 debug() << "scrobbling " << bundle->artist() << " - " << bundle->title();
2202 SubmitItem *sit = new SubmitItem( bundle->artist(), bundle->album(), bundle->title(), bundle->length(), false /* fake time */ );
2203 Scrobbler::instance()->m_submitter->submitItem( sit );
2206 // increase Amarok playcount
2207 QString url = CollectionDB::instance()->getURL( *bundle );
2208 if( !url.isEmpty() )
2210 QDateTime t = it->playTime();
2211 CollectionDB::instance()->addSongPercentage( url, 100, "mediadevice", t.isValid() ? &t : 0 );
2212 debug() << "played " << url;
2216 if( it->ratingChanged() )
2218 // copy rating from media device to Amarok
2219 QString url = CollectionDB::instance()->getURL( *bundle );
2220 debug() << "rating changed " << url << ": " << it->rating()/10;
2221 if( !url.isEmpty() )
2223 CollectionDB::instance()->setSongRating( url, it->rating()/10 );
2224 it->setRating( it->rating() ); // prevent setting it again next time
2228 break;
2229 case MediaItem::PODCASTITEM:
2230 if( !it->parent() || static_cast<MediaItem *>( it->parent() )->type() != MediaItem::PLAYLIST )
2232 const MetaBundle *bundle = it->bundle();
2233 if( it->played() || it->recentlyPlayed() )
2235 if( PodcastEpisodeBundle *peb = bundle->podcastBundle() )
2237 debug() << "marking podcast episode as played: " << peb->url();
2238 //PORT 2.0
2239 // if( PlaylistBrowser::instance() )
2240 // {
2241 // PodcastEpisode *p = PlaylistBrowser::instance()->findPodcastEpisode( peb->url(), peb->parent() );
2242 // if ( p )
2243 // p->setListened();
2244 // else
2245 // debug() << "did not find podcast episode: " << peb->url() << " from " << peb->parent();
2246 // }
2250 break;
2252 default:
2253 syncStatsFromDevice( it );
2254 break;
2259 void
2260 MediaItem::syncStatsFromPath( const QString &url )
2262 if( url.isEmpty() )
2263 return;
2265 // copy Amarok rating, play count and last played time to device
2266 int rating = CollectionDB::instance()->getSongRating( url )*10;
2267 if( rating )
2268 setRating( rating );
2269 int playcount = CollectionDB::instance()->getPlayCount( url );
2270 if( playcount > played() )
2271 setPlayCount( playcount );
2272 QDateTime lastplay = CollectionDB::instance()->getLastPlay( url );
2273 if( lastplay > playTime() )
2274 setLastPlayed( lastplay.toTime_t() );
2277 void
2278 MediaDevice::syncStatsToDevice( MediaItem *root )
2280 MediaItem *it = static_cast<MediaItem *>( m_view->firstChild() );
2281 if( root )
2283 it = static_cast<MediaItem *>( root->firstChild() );
2286 kapp->processEvents( QEventLoop::ExcludeUserInputEvents );
2288 for( ; it; it = static_cast<MediaItem *>( it->nextSibling() ) )
2290 switch( it->type() )
2292 case MediaItem::TRACK:
2293 if( !it->parent() || static_cast<MediaItem *>( it->parent() )->type() != MediaItem::PLAYLIST )
2295 const MetaBundle *bundle = it->bundle();
2296 QString url = CollectionDB::instance()->getURL( *bundle );
2297 it->syncStatsFromPath( url );
2299 break;
2301 case MediaItem::PODCASTITEM:
2302 if( !it->parent() || static_cast<MediaItem *>( it->parent() )->type() != MediaItem::PLAYLIST )
2304 const MetaBundle *bundle = it->bundle();
2305 if( PodcastEpisodeBundle *peb = bundle->podcastBundle() )
2307 // //PORT 2.0
2308 // if( PlaylistBrowser::instance() )
2309 // {
2310 // PodcastEpisode *p = PlaylistBrowser::instance()->findPodcastEpisode( peb->url(), peb->parent() );
2311 // if( p )
2312 // it->setListened( !p->isNew() );
2313 // }
2316 break;
2318 default:
2319 syncStatsToDevice( it );
2320 break;
2325 void
2326 MediaDevice::transferFiles()
2328 if( !lockDevice( true ) )
2330 return;
2333 setCanceled( false );
2335 m_transferring = true;
2336 m_parent->transferAction()->setEnabled( false );
2338 setProgress( 0, m_parent->m_queue->childCount() );
2340 // ok, let's copy the stuff to the device
2342 KUrl::List existing, unplayable;
2343 unsigned transcodeFail = 0;
2344 // iterate through items
2345 MediaItem *next = static_cast<MediaItem *>(m_parent->m_queue->firstChild());
2346 while( next )
2348 MediaItem *transferredItem = next;
2349 transferredItem->setFailed( false );
2350 transferredItem->m_flags |= MediaItem::Transferring;
2351 next = static_cast<MediaItem *>( transferredItem->nextSibling() );
2353 if( transferredItem->device() )
2355 transferredItem->device()->copyTrackFromDevice( transferredItem );
2356 m_parent->m_queue->subtractItemFromSize( transferredItem, true );
2357 delete transferredItem;
2358 setProgress( progress() + 1 );
2359 m_parent->m_queue->itemCountChanged();
2360 kapp->processEvents( QEventLoop::ExcludeUserInputEvents );
2361 continue;
2364 BundleList bundles;
2365 if( transferredItem->type() == MediaItem::PLAYLIST )
2367 if( transferredItem->flags() & MediaItem::SmartPlaylist )
2368 bundles = bundlesToSync( transferredItem->text( 0 ), transferredItem->data() );
2369 else
2370 bundles = bundlesToSync( transferredItem->text( 0 ), KUrl( transferredItem->data() ) );
2372 else if( transferredItem->bundle() )
2373 bundles += *transferredItem->bundle();
2374 else
2376 // this should not happen
2377 debug() << "invalid item in transfer queue";
2378 m_parent->m_queue->subtractItemFromSize( transferredItem, true );
2379 delete transferredItem;
2380 m_parent->m_queue->itemCountChanged();
2381 continue;
2384 if( bundles.count() > 1 )
2385 setProgress( progress(), MediaBrowser::instance()->m_progress->maximum() + bundles.count() - 1 );
2387 QString playlist = transferredItem->m_playlistName;
2388 for( BundleList::const_iterator it = bundles.begin();
2389 it != bundles.end();
2390 ++it )
2392 if( isCanceled() )
2393 break;
2395 const MetaBundle *bundle = &(*it);
2397 bool transcoding = false;
2398 MediaItem *item = trackExists( *bundle );
2399 if( item && playlist.isEmpty() )
2401 Amarok::StatusBar::instance()->shortMessage( i18n( "Track already on media device: %1" ).
2402 arg( (*it).url().prettyUrl() ),
2403 KDE::StatusBar::Sorry );
2404 existing += (*it).url();
2405 setProgress( progress() + 1 );
2406 continue;
2408 else if( !item ) // the item does not yet exist on the media device
2410 if( m_transcode && ( !isPlayable( *bundle ) || m_transcodeAlways ) )
2412 QString preferred = supportedFiletypes().isEmpty() ? "mp3" : supportedFiletypes().first();
2413 debug() << "transcoding " << bundle->url() << " to " << preferred;
2414 KUrl transcoded = MediaBrowser::instance()->transcode( bundle->url(), preferred );
2415 if( isCanceled() )
2416 break;
2417 if( transcoded.isEmpty() )
2419 debug() << "transcoding failed";
2420 transcodeFail++;
2422 else
2424 transcoding = true;
2425 MetaBundle *transcodedBundle = new MetaBundle( transcoded );
2426 transcodedBundle->setArtist( bundle->artist() );
2427 transcodedBundle->setTitle( bundle->title() );
2428 transcodedBundle->setComposer( bundle->composer() );
2429 transcodedBundle->setAlbum( bundle->album() );
2430 transcodedBundle->setGenre( bundle->genre() );
2431 transcodedBundle->setComment( bundle->comment() );
2432 transcodedBundle->setYear( bundle->year() );
2433 transcodedBundle->setDiscNumber( bundle->discNumber() );
2434 transcodedBundle->setTrack( bundle->track() );
2435 if( bundle->podcastBundle() )
2437 transcodedBundle->setPodcastBundle( *bundle->podcastBundle() );
2438 transcodedBundle->copyFrom( *bundle->podcastBundle() );
2440 bundle = transcodedBundle;
2444 if( !isPlayable( *bundle ) )
2446 Amarok::StatusBar::instance()->shortMessage( i18n( "Track not playable on media device: %1", bundle->url().path() ),
2447 KDE::StatusBar::Sorry );
2448 unplayable += (*it).url();
2449 transferredItem->setFailed();
2450 if( transcoding )
2452 delete bundle;
2453 bundle = 0;
2455 setProgress( progress() + 1 );
2456 continue;
2458 item = copyTrackToDevice( *bundle );
2461 if( !item ) // copyTrackToDevice() failed
2463 if( !isCanceled() )
2465 Amarok::StatusBar::instance()->longMessage(
2466 i18n( "Failed to copy track to media device: %1", bundle->url().path() ),
2467 KDE::StatusBar::Sorry );
2468 transferredItem->setFailed();
2472 if( transcoding )
2474 if( m_transcodeRemove )
2475 QFile( bundle->url().path() ).remove();
2477 delete bundle;
2478 bundle = 0;
2481 if( isCanceled() )
2482 break;
2484 if( !item )
2486 setProgress( progress() + 1 );
2487 continue;
2490 item->syncStatsFromPath( (*it).url().path() );
2492 if( m_playlistItem && !playlist.isEmpty() )
2494 MediaItem *pl = m_playlistItem->findItem( playlist );
2495 if( !pl )
2497 Q3PtrList<MediaItem> items;
2498 pl = newPlaylist( playlist, m_playlistItem, items );
2500 if( pl )
2502 Q3PtrList<MediaItem> items;
2503 items.append( item );
2504 addToPlaylist( pl, pl->lastChild(), items );
2508 setProgress( progress() + 1 );
2511 transferredItem->m_flags &= ~MediaItem::Transferring;
2513 if( isCanceled() )
2515 m_parent->updateStats();
2516 break;
2519 if( !(transferredItem->flags() & MediaItem::Failed) )
2521 m_parent->m_queue->subtractItemFromSize( transferredItem, true );
2522 delete transferredItem;
2523 m_parent->m_queue->itemCountChanged();
2525 m_parent->updateStats();
2527 kapp->processEvents( QEventLoop::ExcludeUserInputEvents );
2529 synchronizeDevice();
2530 unlockDevice();
2531 fileTransferFinished();
2533 QString msg;
2534 if( unplayable.count() > 0 )
2536 msg = i18np( "One track not playable on media device",
2537 "%1 tracks not playable on media device", unplayable.count() );
2539 if( existing.count() > 0 )
2541 if( msg.isEmpty() )
2542 msg = i18np( "One track already on media device",
2543 "%1 tracks already on media device", existing.count() );
2544 else
2545 msg += i18np( ", one track already on media device",
2546 ", %1 tracks already on media device", existing.count() );
2548 if( transcodeFail > 0 )
2550 if( msg.isEmpty() )
2551 msg = i18np( "One track was not transcoded",
2552 "%1 tracks were not transcoded", transcodeFail );
2553 else
2554 msg += i18np( ", one track was not transcoded",
2555 ", %1 tracks were not transcoded", transcodeFail );
2557 const ScriptManager* const sm = ScriptManager::instance();
2558 if( !sm->transcodeScriptRunning().isEmpty() )
2559 msg += i18n( " (no transcode script running)" );
2562 if( unplayable.count() + existing.count() > 0 )
2564 QString longMsg = i18n( "The following tracks were not transferred: ");
2565 for( KUrl::List::Iterator it = existing.begin();
2566 it != existing.end();
2567 it++ )
2569 longMsg += "<br>" + (*it).prettyUrl();
2571 for( KUrl::List::Iterator it = unplayable.begin();
2572 it != unplayable.end();
2573 it++ )
2575 longMsg += "<br>" + (*it).prettyUrl();
2577 Amarok::StatusBar::instance()->shortLongMessage( msg, longMsg, KDE::StatusBar::Sorry );
2579 else if( !msg.isEmpty() )
2581 Amarok::StatusBar::instance()->shortMessage( msg, KDE::StatusBar::Sorry );
2584 m_parent->updateButtons();
2585 m_parent->queue()->save( Amarok::saveLocation() + "transferlist.xml" );
2586 m_transferring = false;
2588 if( m_deferredDisconnect )
2590 m_deferredDisconnect = false;
2591 disconnectDevice( m_runDisconnectHook );
2593 else if( m_scheduledDisconnect )
2595 disconnectDevice( true );
2597 m_scheduledDisconnect = false;
2601 MediaDevice::progress() const
2603 return m_parent->m_progress->value();
2606 void
2607 MediaDevice::setProgress( const int progress, const int total )
2609 if( total != -1 )
2610 m_parent->m_progress->setRange( 0, total );
2611 m_parent->m_progress->setValue( progress );
2612 m_parent->m_progressBox->show();
2615 void
2616 MediaDevice::fileTransferFinished() //SLOT
2618 m_parent->updateStats();
2619 m_parent->m_progressBox->hide();
2620 m_parent->transferAction()->setEnabled( isConnected() && m_parent->queue()->childCount() > 0 );
2621 m_wait = false;
2626 MediaDevice::deleteFromDevice(MediaItem *item, int flags )
2628 MediaItem* fi = item;
2629 int count = 0;
2631 if ( !(flags & Recursing) )
2633 if( !lockDevice( true ) )
2634 return 0;
2636 setCanceled( false );
2638 m_deleting = true;
2640 Q3PtrList<MediaItem> list;
2641 //NOTE we assume that currentItem is the main target
2642 int numFiles = m_view->getSelectedLeaves(item, &list, MediaView::OnlySelected | ((flags & OnlyPlayed) ? MediaView::OnlyPlayed : MediaView::None) );
2644 m_parent->m_stats->setText( i18np( "1 track to be deleted", "%1 tracks to be deleted", numFiles ) );
2645 if( numFiles > 0 && (flags & DeleteTrack) )
2647 int button = KMessageBox::warningContinueCancel( m_parent,
2648 i18np( "<p>You have selected 1 track to be <b>irreversibly</b> deleted.",
2649 "<p>You have selected %1 tracks to be <b>irreversibly</b> deleted.",
2650 numFiles
2652 QString(),
2653 KGuiItem(i18n("&Delete"),"edit-delete") );
2655 if ( button != KMessageBox::Continue )
2657 m_parent->queue()->computeSize();
2658 m_parent->updateStats();
2659 m_deleting = false;
2660 unlockDevice();
2661 return 0;
2664 if(!isTransferring())
2666 setProgress( 0, numFiles );
2670 // don't return if numFiles==0: playlist items might be to delete
2672 if( !fi )
2673 fi = static_cast<MediaItem*>(m_view->firstChild());
2676 while( fi )
2678 MediaItem *next = static_cast<MediaItem*>(fi->nextSibling());
2680 if( isCanceled() )
2682 break;
2685 if( !fi->isVisible() )
2687 fi = next;
2688 continue;
2691 if( fi->isSelected() )
2693 int ret = deleteItemFromDevice(fi, flags);
2694 if( ret >= 0 && count >= 0 )
2695 count += ret;
2696 else
2697 count = -1;
2699 else
2701 if( fi->childCount() )
2703 int ret = deleteFromDevice( static_cast<MediaItem*>(fi->firstChild()), flags | Recursing );
2704 if( ret >= 0 && count >= 0 )
2705 count += ret;
2706 else
2707 count = -1;
2710 m_parent->updateStats();
2712 fi = next;
2715 if(!(flags & Recursing))
2717 purgeEmptyItems();
2718 synchronizeDevice();
2719 m_deleting = false;
2720 unlockDevice();
2722 if(!isTransferring())
2724 QTimer::singleShot( 1500, m_parent->m_progressBox, SLOT(hide()) );
2727 if( m_deferredDisconnect )
2729 m_deferredDisconnect = false;
2730 disconnectDevice( m_runDisconnectHook );
2733 m_parent->queue()->computeSize();
2734 m_parent->updateStats();
2736 return count;
2739 void
2740 MediaDevice::purgeEmptyItems( MediaItem *root )
2742 MediaItem *it = 0;
2743 if( root )
2745 it = static_cast<MediaItem *>(root->firstChild());
2747 else
2749 it = static_cast<MediaItem *>(m_view->firstChild());
2752 MediaItem *next = 0;
2753 for( ; it; it=next )
2755 next = static_cast<MediaItem *>(it->nextSibling());
2756 purgeEmptyItems( it );
2757 if( it->childCount() == 0 &&
2758 (it->type() == MediaItem::ARTIST ||
2759 it->type() == MediaItem::ALBUM ||
2760 it->type() == MediaItem::PODCASTCHANNEL) )
2761 delete it;
2765 void
2766 MediaQueue::save( const QString &path )
2768 QFile file( path );
2770 if( !file.open( QIODevice::WriteOnly ) ) return;
2772 QDomDocument newdoc;
2773 QDomElement transferlist = newdoc.createElement( "playlist" );
2774 transferlist.setAttribute( "product", "Amarok" );
2775 transferlist.setAttribute( "version", APP_VERSION );
2776 newdoc.appendChild( transferlist );
2778 for( const MediaItem *item = static_cast<MediaItem *>( firstChild() );
2779 item;
2780 item = static_cast<MediaItem *>( item->nextSibling() ) )
2782 QDomElement i = newdoc.createElement("item");
2783 i.setAttribute("url", item->url().url());
2785 if( item->bundle() )
2787 QDomElement attr = newdoc.createElement( "Title" );
2788 QDomText t = newdoc.createTextNode( item->bundle()->title() );
2789 attr.appendChild( t );
2790 i.appendChild( attr );
2792 attr = newdoc.createElement( "Artist" );
2793 t = newdoc.createTextNode( item->bundle()->artist() );
2794 attr.appendChild( t );
2795 i.appendChild( attr );
2797 attr = newdoc.createElement( "Album" );
2798 t = newdoc.createTextNode( item->bundle()->album() );
2799 attr.appendChild( t );
2800 i.appendChild( attr );
2802 attr = newdoc.createElement( "Year" );
2803 t = newdoc.createTextNode( QString::number( item->bundle()->year() ) );
2804 attr.appendChild( t );
2805 i.appendChild( attr );
2807 attr = newdoc.createElement( "Comment" );
2808 t = newdoc.createTextNode( item->bundle()->comment() );
2809 attr.appendChild( t );
2810 i.appendChild( attr );
2812 attr = newdoc.createElement( "Genre" );
2813 t = newdoc.createTextNode( item->bundle()->genre() );
2814 attr.appendChild( t );
2815 i.appendChild( attr );
2817 attr = newdoc.createElement( "Track" );
2818 t = newdoc.createTextNode( QString::number( item->bundle()->track() ) );
2819 attr.appendChild( t );
2820 i.appendChild( attr );
2823 if(item->type() == MediaItem::PODCASTITEM)
2825 i.setAttribute( "podcast", "1" );
2828 if(item->type() == MediaItem::PODCASTITEM
2829 && item->bundle()->podcastBundle())
2831 PodcastEpisodeBundle *peb = item->bundle()->podcastBundle();
2832 QDomElement attr = newdoc.createElement( "PodcastDescription" );
2833 QDomText t = newdoc.createTextNode( peb->description() );
2834 attr.appendChild( t );
2835 i.appendChild( attr );
2837 attr = newdoc.createElement( "PodcastAuthor" );
2838 t = newdoc.createTextNode( peb->author() );
2839 attr.appendChild( t );
2840 i.appendChild( attr );
2842 attr = newdoc.createElement( "PodcastRSS" );
2843 t = newdoc.createTextNode( peb->parent().url() );
2844 attr.appendChild( t );
2845 i.appendChild( attr );
2847 attr = newdoc.createElement( "PodcastURL" );
2848 t = newdoc.createTextNode( peb->url().url() );
2849 attr.appendChild( t );
2850 i.appendChild( attr );
2853 if( !item->m_playlistName.isEmpty() )
2855 i.setAttribute( "playlist", item->m_playlistName );
2858 if(item->type() == MediaItem::PLAYLIST)
2860 i.setAttribute( "playlistdata", item->data() );
2861 if( item->flags() & MediaItem::SmartPlaylist )
2862 i.setAttribute( "smartplaylist", "1" );
2865 transferlist.appendChild( i );
2868 QTextStream stream( &file );
2869 stream.setCodec( QTextCodec::codecForName( "UTF-8" ) );
2870 stream.setAutoDetectUnicode( true );
2871 stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2872 stream << newdoc.toString();
2876 void
2877 MediaQueue::load( const QString& filename )
2879 QFile file( filename );
2880 if( !file.open( QIODevice::ReadOnly ) ) {
2881 return;
2884 clearItems();
2886 QTextStream stream( &file );
2887 stream.setCodec( QTextCodec::codecForName( "UTF-8" ) );
2888 stream.setAutoDetectUnicode( true );
2890 QDomDocument d;
2891 QString er;
2892 int l, c;
2893 if( !d.setContent( stream.readAll(), &er, &l, &c ) ) { // return error values
2894 Amarok::StatusBar::instance()->longMessageThreadSafe( i18n(
2895 //TODO add a link to the path to the playlist
2896 "The XML in the transferlist was invalid. Please report this as a bug to the Amarok "
2897 "developers. Thank you." ), KDE::StatusBar::Error );
2898 error() << "[TRANSFERLISTLOADER]: Error loading xml file: " << filename << "(" << er << ")"
2899 << " at line " << l << ", column " << c;
2900 return;
2903 QList<QDomNode> nodes;
2904 const QString ITEM( "item" ); //so we don't construct this QString all the time
2905 for( QDomNode n = d.namedItem( "playlist" ).firstChild(); !n.isNull(); n = n.nextSibling() )
2907 if( n.nodeName() != ITEM ) continue;
2909 QDomElement elem = n.toElement();
2910 if( !elem.isNull() )
2911 nodes += n;
2913 if( !elem.hasAttribute( "url" ) )
2915 continue;
2917 KUrl url(elem.attribute("url"));
2919 bool podcast = elem.hasAttribute( "podcast" );
2920 PodcastEpisodeBundle peb;
2921 if( url.isLocalFile() )
2922 peb.setLocalURL( url );
2923 MetaBundle *bundle = new MetaBundle( url );
2924 for(QDomNode node = elem.firstChild();
2925 !node.isNull();
2926 node = node.nextSibling())
2928 if(node.firstChild().isNull())
2929 continue;
2931 if(node.nodeName() == "Title" )
2932 bundle->setTitle(node.firstChild().toText().nodeValue());
2933 else if(node.nodeName() == "Artist" )
2934 bundle->setArtist(node.firstChild().toText().nodeValue());
2935 else if(node.nodeName() == "Album" )
2936 bundle->setAlbum(node.firstChild().toText().nodeValue());
2937 else if(node.nodeName() == "Year" )
2938 bundle->setYear(node.firstChild().toText().nodeValue().toUInt());
2939 else if(node.nodeName() == "Genre" )
2940 bundle->setGenre(node.firstChild().toText().nodeValue());
2941 else if(node.nodeName() == "Comment" )
2942 bundle->setComment(node.firstChild().toText().nodeValue());
2943 else if(node.nodeName() == "PodcastDescription" )
2944 peb.setDescription( node.firstChild().toText().nodeValue() );
2945 else if(node.nodeName() == "PodcastAuthor" )
2946 peb.setAuthor( node.firstChild().toText().nodeValue() );
2947 else if(node.nodeName() == "PodcastRSS" )
2948 peb.setParent( KUrl( node.firstChild().toText().nodeValue() ) );
2949 else if(node.nodeName() == "PodcastURL" )
2950 peb.setUrl( KUrl( node.firstChild().toText().nodeValue() ) );
2953 if( podcast )
2955 bundle->setPodcastBundle( peb );
2958 QString playlist = elem.attribute( "playlist" );
2959 QString playlistdata = elem.attribute( "playlistdata" );
2960 if( !playlistdata.isEmpty() )
2962 QString smart = elem.attribute( "smartplaylist" );
2963 if( smart.isEmpty() )
2964 syncPlaylist( playlist, KUrl( playlistdata ), true );
2965 else
2966 syncPlaylist( playlist, playlistdata, true );
2968 else
2969 addUrl( url, bundle, playlist );
2972 URLsAdded();
2975 bool
2976 MediaDevice::isPlayable( const MetaBundle &bundle )
2978 if( supportedFiletypes().isEmpty() )
2979 return true;
2981 QString type = bundle.url().path().section( ".", -1 ).toLower();
2982 return supportedFiletypes().contains( type );
2985 bool
2986 MediaDevice::isPreferredFormat( const MetaBundle &bundle )
2988 if( supportedFiletypes().isEmpty() )
2989 return true;
2991 QString type = bundle.url().path().section( ".", -1 ).toLower();
2992 return ( type == supportedFiletypes().first() );
2996 MediaQueue::MediaQueue(MediaBrowser *parent)
2997 : K3ListView( parent ), m_parent( parent )
2999 setFixedHeight( 200 );
3000 setSelectionMode( Q3ListView::Extended );
3001 setItemsMovable( true );
3002 setDragEnabled( true );
3003 setShowSortIndicator( false );
3004 setSorting( -1 );
3005 setFullWidth( true );
3006 setRootIsDecorated( false );
3007 setDropVisualizer( true ); //the visualizer (a line marker) is drawn when dragging over tracks
3008 setDropHighlighter( true ); //and the highligther (a focus rect) is drawn when dragging over playlists
3009 setDropVisualizerWidth( 3 );
3010 setAcceptDrops( true );
3011 addColumn( i18n( "Transfer Queue" ) );
3013 itemCountChanged();
3015 KActionCollection* ac = new KActionCollection( this );
3016 KStandardAction::selectAll( this, SLOT( selectAll() ), ac );
3018 connect( this, SIGNAL( contextMenuRequested( Q3ListViewItem*, const QPoint&, int ) ),
3019 SLOT( slotShowContextMenu( Q3ListViewItem*, const QPoint&, int ) ) );
3020 connect( this, SIGNAL( dropped(QDropEvent*, Q3ListViewItem*, Q3ListViewItem*) ),
3021 SLOT( slotDropped(QDropEvent*, Q3ListViewItem*, Q3ListViewItem*) ) );
3024 bool
3025 MediaQueue::acceptDrag( QDropEvent *e ) const
3028 return e->source() == viewport()
3029 || e->mimeData()->hasFormat( "amarok-sql" )
3030 || KUrl::List::canDecode( e->mimeData() );
3033 void
3034 MediaQueue::slotDropped( QDropEvent* e, Q3ListViewItem* parent, Q3ListViewItem* after)
3036 if( e->source() != viewport() )
3038 if( e->mimeData()->hasFormat( "amarok-sql" ) )
3040 QString data( e->mimeData()->data( "amarok-sql" ) );
3041 QString playlist = data.section( "\n", 0, 0 );
3042 QString query = data.section( "\n", 1 );
3043 QStringList values = CollectionDB::instance()->query( query );
3044 KUrl::List list = CollectionDB::instance()->URLsFromSqlDrag( values );
3045 addUrls( list, playlist );
3047 else if ( KUrl::List::canDecode( e->mimeData() ) )
3049 KUrl::List list = KUrl::List::fromMimeData( e->mimeData() );
3050 if (!list.isEmpty() )
3051 addUrls( list );
3054 else if( Q3ListViewItem *i = currentItem() )
3056 moveItem( i, parent, after );
3060 void
3061 MediaQueue::dropProxyEvent( QDropEvent *e )
3063 slotDropped( e, 0, 0 );
3066 MediaItem*
3067 MediaQueue::findPath( QString path )
3069 for( Q3ListViewItem *item = firstChild();
3070 item;
3071 item = item->nextSibling())
3073 if(static_cast<MediaItem *>(item)->url().path() == path)
3074 return static_cast<MediaItem *>(item);
3077 return 0;
3080 void
3081 MediaQueue::computeSize() const
3083 m_totalSize = 0;
3084 for( Q3ListViewItem *it = firstChild();
3086 it = it->nextSibling())
3088 MediaItem *item = static_cast<MediaItem *>(it);
3090 if( item && item->bundle() &&
3091 ( !m_parent->currentDevice()
3092 || !m_parent->currentDevice()->isConnected()
3093 || !m_parent->currentDevice()->trackExists(*item->bundle()) ) )
3094 m_totalSize += ((item->size()+1023)/1024)*1024;
3098 KIO::filesize_t
3099 MediaQueue::totalSize() const
3101 return m_totalSize;
3104 void
3105 MediaQueue::addItemToSize( const MediaItem *item ) const
3107 if( item && item->bundle() &&
3108 ( !m_parent->currentDevice()
3109 || !m_parent->currentDevice()->isConnected()
3110 || !m_parent->currentDevice()->trackExists(*item->bundle()) ) )
3111 m_totalSize += ((item->size()+1023)/1024)*1024;
3114 void
3115 MediaQueue::subtractItemFromSize( const MediaItem *item, bool unconditionally ) const
3117 if( item && item->bundle() &&
3118 ( !m_parent->currentDevice()
3119 || !m_parent->currentDevice()->isConnected()
3120 || (unconditionally || !m_parent->currentDevice()->trackExists(*item->bundle())) ) )
3121 m_totalSize -= ((item->size()+1023)/1024)*1024;
3124 void
3125 MediaQueue::removeSelected()
3127 QList<Q3ListViewItem*> selected = selectedItems();
3129 QListIterator<Q3ListViewItem*> iter( selected );
3130 while( iter.hasNext() )
3132 Q3ListViewItem *item = iter.next();
3133 if( !(static_cast<MediaItem *>(item)->flags() & MediaItem::Transferring) )
3135 subtractItemFromSize( static_cast<MediaItem *>(item) );
3136 delete item;
3137 if( m_parent->currentDevice() && m_parent->currentDevice()->isTransferring() )
3139 MediaBrowser::instance()->m_progress->setRange( 0, MediaBrowser::instance()->m_progress->maximum() - 1 );
3144 MediaBrowser::instance()->updateStats();
3145 MediaBrowser::instance()->updateButtons();
3146 itemCountChanged();
3149 void
3150 MediaQueue::keyPressEvent( QKeyEvent *e )
3152 if( e->key() == Qt::Key_Delete )
3153 removeSelected();
3154 else
3155 K3ListView::keyPressEvent( e );
3158 void
3159 MediaQueue::itemCountChanged()
3161 if( childCount() == 0 )
3162 hide();
3163 else if( !isShown() )
3164 show();
3167 void
3168 MediaQueue::slotShowContextMenu( Q3ListViewItem* item, const QPoint& point, int )
3170 if( !childCount() )
3171 return;
3173 Q3PopupMenu menu( this );
3175 enum Actions { REMOVE_SELECTED, CLEAR_ALL, START_TRANSFER };
3177 if( item )
3178 menu.insertItem( KIcon( Amarok::icon( "remove_from_playlist" ) ), i18n( "&Remove From Queue" ), REMOVE_SELECTED );
3180 menu.insertItem( KIcon( Amarok::icon( "playlist_clear" ) ), i18n( "&Clear Queue" ), CLEAR_ALL );
3181 menu.insertItem( KIcon( Amarok::icon( "playlist_refresh" ) ), i18n( "&Start Transfer" ), START_TRANSFER );
3182 menu.setItemEnabled( START_TRANSFER,
3183 MediaBrowser::instance()->currentDevice() &&
3184 MediaBrowser::instance()->currentDevice()->isConnected() &&
3185 MediaBrowser::instance()->currentDevice()->m_transfer );
3187 switch( menu.exec( point ) )
3189 case REMOVE_SELECTED:
3190 removeSelected();
3191 break;
3192 case CLEAR_ALL:
3193 clearItems();
3194 break;
3195 case START_TRANSFER:
3196 MediaBrowser::instance()->transferClicked();
3197 break;
3201 void
3202 MediaQueue::clearItems()
3204 clear();
3205 itemCountChanged();
3206 if(m_parent)
3208 computeSize();
3209 m_parent->updateStats();
3210 m_parent->updateButtons();
3215 #include "mediabrowser.moc"