some more work on collabsible albums. I think I will need to optimize the playlist...
[amarok.git] / src / mediabrowser.cpp
blobaa403762ae0dbfa0bdd05d8c1ee51bd61ca26e02
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 "MediaDevice.h"
26 #include "MediaDeviceCache.h"
27 #include "medium.h"
28 //#include "mediumpluginmanager.h"
29 #include "metabundle.h"
30 #include "mountpointmanager.h"
31 #include "playlist/PlaylistModel.h"
32 #include "pluginmanager.h"
33 #include "podcastbundle.h"
34 #include "scriptmanager.h"
35 #include "scrobbler.h"
36 #include "searchwidget.h"
37 #include "statusbar.h"
38 #include "transferdialog.h"
39 #include "TheInstances.h"
41 #include <q3header.h>
42 #include <Q3PopupMenu>
43 #include <q3simplerichtext.h>
44 #include <QByteArray>
45 #include <QCheckBox>
46 #include <QDateTime>
47 #include <QDir>
48 #include <QDomDocument>
49 #include <QDomElement>
50 #include <QDomNode>
51 #include <QDropEvent>
52 #include <QFileInfo>
53 #include <QGroupBox>
54 #include <QImage>
55 #include <QKeyEvent>
56 #include <QLabel>
57 #include <QList>
58 #include <QListIterator>
59 #include <QObject>
60 #include <QPainter>
61 #include <QPaintEvent>
62 #include <QPixmap>
63 #include <QProgressBar>
64 #include <QRadioButton>
65 #include <QTimer>
66 #include <QToolButton>
67 #include <QToolTip> //QToolTip::add()
69 #include <k3multipledrag.h>
70 #include <k3process.h>
71 #include <k3tempfile.h>
72 #include <k3urldrag.h> //dragObject()
73 #include <KActionCollection>
74 #include <KApplication> //kapp
75 #include <KComboBox>
76 #include <KDirLister>
77 #include <KFileDialog>
78 #include <KGlobal>
79 #include <KIconLoader>
80 #include <KInputDialog>
81 #include <KIO/Job>
82 #include <KLocale>
83 #include <KMenu>
84 #include <KMessageBox>
85 #include <KPushButton>
86 #include <KRun>
87 #include <KStandardDirs> //locate file
88 #include <KTabBar>
89 #include <solid/device.h>
90 #include <solid/deviceinterface.h>
91 #include <solid/devicenotifier.h>
92 #include <solid/portablemediaplayer.h>
95 MediaBrowser *MediaBrowser::s_instance = 0;
97 bool MediaBrowser::isAvailable() //static
99 if( !MediaBrowser::instance() )
100 return false;
102 return true;
104 //to re-enable hiding, uncomment this and get rid of the return true above:
105 //return MediaBrowser::instance()->m_haveDevices;
108 class DummyMediaDevice : public MediaDevice
110 public:
111 DummyMediaDevice() : MediaDevice()
113 m_name = i18n( "No Device Available" );
114 m_type = "dummy-mediadevice";
115 m_uid = "manual|DummyDevice|none|none";
117 void init( MediaBrowser *browser ) { MediaDevice::init( browser ); }
118 virtual ~DummyMediaDevice() {}
119 virtual bool isConnected() { return false; }
120 virtual MediaItem* trackExists(const MetaBundle&) { return 0; }
121 virtual bool lockDevice(bool) { return true; }
122 virtual void unlockDevice() {}
123 virtual bool openDevice( bool silent )
125 if( !silent )
127 //QString msg = i18n( "Sorry, you do not have a supported portable music player." );
128 //Amarok::StatusBar::instance()->longMessage( msg, KDE::StatusBar::Sorry );
130 return false;
132 virtual bool closeDevice() { return false; }
133 virtual void synchronizeDevice() {}
134 virtual MediaItem* copyTrackToDevice(const MetaBundle&) { return 0; }
135 virtual int deleteItemFromDevice(MediaItem*, int) { return -1; }
139 MediaBrowser::MediaBrowser( const char * /*name*/ )
140 : KVBox( 0)
141 , m_timer( new QTimer( this ) )
142 , m_currentDevice( 0 )
143 , m_waitForTranscode( false )
144 , m_quitting( false )
145 , m_connectAction( 0 )
146 , m_disconnectAction( 0 )
147 , m_customAction( 0 )
148 , m_configAction( 0 )
149 , m_transferAction( 0 )
151 s_instance = this;
153 m_timer->setSingleShot( true );
155 m_toolbar = new Browser::ToolBar( this );
156 m_toolbar->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
157 m_toolbar->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
159 //TODO: how to fix getButton
160 // perhaps the action can be referenced instead?
161 m_connectAction = new KAction(KIcon("connect_creating"), i18n("Connect"), this);
162 connect(m_connectAction, SIGNAL(triggered()), this, SLOT(connectClicked()));
163 m_toolbar->addAction(m_connectAction);
164 // m_toolbar->insertButton( "connect_creating", CONNECT, true, i18n("Connect") );
165 // m_toolbar->getButton(CONNECT)->setToolTip( i18n( "Connect media device" ) );
167 m_disconnectAction = new KAction(KIcon("media-eject"), i18n("Disconnect"), this);
168 connect(m_disconnectAction, SIGNAL(triggered()), this, SLOT(disconnectClicked()));
169 m_toolbar->addAction(m_disconnectAction);
170 // m_toolbar->insertButton( "media-eject", DISCONNECT, true, i18n("Disconnect") );
171 // m_toolbar->getButton(DISCONNECT)->setToolTip( i18n( "Disconnect media device" ) );
173 m_transferAction = new KAction(KIcon("rebuild"), i18n("Transfer"), this);
174 connect(m_transferAction, SIGNAL(triggered()), this, SLOT(transferClicked()));
175 m_toolbar->addAction(m_transferAction);
176 // m_toolbar->insertButton( "rebuild", TRANSFER, true, i18n("Transfer") );
177 // m_toolbar->getButton(TRANSFER)->setToolTip( i18n( "Transfer tracks to media device" ) );
179 m_toolbar->addSeparator();
181 // m_toolbar->setIconText( KToolBar::IconTextRight, true );
182 m_customAction = new KAction(KIcon( "add_playlist" ), i18n("custom"), this);
183 connect(m_customAction, SIGNAL(triggered()), this, SLOT(customClicked()));
184 m_customAction->setText( i18n("Special Device Functions") );
185 m_customAction->setToolTip( i18n("Device-specific special functions or information") );
186 m_toolbar->addAction(m_customAction);
187 // m_toolbar->insertButton( Amarok::icon( "add_playlist" ), CUSTOM, SIGNAL( clicked() ), this, SLOT( customClicked() ), true, "custom" );
188 // m_toolbar->getButton(TRANSFER)->setToolTip( i18n( "Transfer tracks to media device" ) );
190 m_toolbar->setToolButtonStyle( Qt::ToolButtonIconOnly );
192 m_configAction = new KAction(KIcon("configure"), i18n("Configure"), this);
193 connect(m_configAction, SIGNAL(triggered()), this, SLOT(config()));
194 m_toolbar->addAction(m_configAction);
195 // m_toolbar->insertButton( Amarok::icon( "configure" ), CONFIGURE, true, i18n("Configure") );
196 // m_toolbar->getButton(CONFIGURE)->setToolTip( i18n( "Configure device" ) );
199 m_deviceCombo = new KComboBox( this );
201 // searching/filtering
202 QToolBar* searchToolBar = new Browser::ToolBar( this );
203 searchToolBar->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
204 m_searchWidget = new SearchWidget( searchToolBar, this );
205 searchToolBar->addWidget( m_searchWidget );
206 connect( m_timer, SIGNAL( timeout() ), SLOT( slotSetFilter() ) );
208 // connect to device cache
209 connect( MediaDeviceCache::instance(), SIGNAL( deviceAdded(const QString&) ),
210 SLOT( deviceAdded(const QString&) ) );
211 connect( MediaDeviceCache::instance(), SIGNAL( deviceRemoved(const QString&) ),
212 SLOT( deviceRemoved(const QString&) ) );
215 // we always have a dummy device
216 m_pluginName[ i18n( "Disable" ) ] = "dummy-mediadevice";
217 m_pluginAmarokName["dummy-mediadevice"] = i18n( "Disable" );
218 m_pluginName[ i18n( "Do not handle" ) ] = "ignore";
219 m_pluginAmarokName["ignore"] = i18n( "Do not handle" );
220 // query available device plugins
221 m_plugins = PluginManager::query( "[X-KDE-Amarok-plugintype] == 'mediadevice'" );
222 for( KService::List::ConstIterator it = m_plugins.begin(); it != m_plugins.end(); ++it ) {
223 // Save name properties in QMap for lookup
224 m_pluginName[(*it)->name()] = (*it)->property( "X-KDE-Amarok-name" ).toString();
225 m_pluginAmarokName[(*it)->property( "X-KDE-Amarok-name" ).toString()] = (*it)->name();
228 m_views = new KVBox( this );
229 m_queue = new MediaQueue( this );
230 m_progressBox = new KHBox( this );
231 m_progress = new QProgressBar( m_progressBox );
232 m_cancelButton = new KPushButton( KIcon( Amarok::icon( "cancel" ) ), i18n("Cancel"), m_progressBox );
235 m_stats = new SpaceLabel(this);
237 m_progressBox->hide();
239 MediaDevice *dev = new DummyMediaDevice();
240 dev->init( this );
241 addDevice( dev );
242 activateDevice( 0, false );
243 updateDevices();
244 queue()->load( Amarok::saveLocation() + "transferlist.xml" );
245 queue()->computeSize();
247 setFocusProxy( m_queue );
249 updateStats();
251 MediaDeviceCache::instance()->refreshCache();
252 foreach( QString udi, MediaDeviceCache::instance()->getAll() )
253 deviceAdded( udi );
255 //TODO: Take generic storage devices into account too -- or do we rely on the
256 //Solid backend to tell us if it's a PMP with "storage" type?
258 connect( m_deviceCombo, SIGNAL( activated( int ) ), SLOT( activateDevice( int ) ) );
260 connect( m_cancelButton, SIGNAL( clicked() ), SLOT( cancelClicked() ) );
261 connect( pApp, SIGNAL( prepareToQuit() ), SLOT( prepareToQuit() ) );
262 connect( CollectionDB::instance(), SIGNAL( tagsChanged( const MetaBundle& ) ),
263 SLOT( tagsChanged( const MetaBundle& ) ) );
265 //TODO: If we will be supporting manually adding devices, probably need the following section
266 /*m_haveDevices = false;
267 QMap<QString,QString> savedDevices = Amarok::config( "MediaBrowser" ).entryMap();
268 for( QMap<QString,QString>::Iterator it = savedDevices.begin();
269 it != savedDevices.end();
270 ++it )
272 if( it.data() != "deleted" && it.data() != "ignore" )
274 m_haveDevices = true;
275 break;
281 bool
282 MediaBrowser::blockQuit() const
284 for( QList<MediaDevice *>::const_iterator it = m_devices.begin();
285 it != m_devices.end();
286 ++it )
288 if( *it && (*it)->isConnected() )
289 return true;
292 return false;
295 void
296 MediaBrowser::tagsChanged( const MetaBundle &bundle )
298 m_itemMapMutex.lock();
299 debug() << "tags changed for " << bundle.url().url();
300 ItemMap::iterator it = m_itemMap.find( bundle.url().url() );
301 if( it != m_itemMap.end() )
303 MediaItem *item = *it;
304 m_itemMapMutex.unlock();
305 if( item->device() )
307 item->device()->tagsChanged( item, bundle );
309 else
311 // it's an item on the transfer queue
312 item->setBundle( new MetaBundle( bundle ) );
314 QString text = item->bundle()->prettyTitle();
315 if( text.isEmpty() || (!item->bundle()->isValidMedia() && !item->bundle()->podcastBundle()) )
316 text = item->bundle()->url().prettyUrl();
317 if( !item->m_playlistName.isNull() )
319 text += " (" + item->m_playlistName + ')';
321 item->setText( 0, text);
324 else
326 m_itemMapMutex.unlock();
330 bool
331 MediaBrowser::getBundle( const KUrl &url, MetaBundle *bundle ) const
333 QMutexLocker locker( &m_itemMapMutex );
334 ItemMap::const_iterator it = m_itemMap.find( url.url() );
335 if( it == m_itemMap.end() )
336 return false;
338 if( bundle )
339 *bundle = *(*it)->bundle();
341 return true;
344 KUrl
345 MediaBrowser::getProxyUrl( const KUrl& daapUrl ) const
347 DEBUG_BLOCK
348 KUrl url;
349 MediaDevice* dc = findChildren<MediaDevice *>( "DaapClient" ).first();
350 if( dc )
351 url = dc->getProxyUrl( daapUrl );
352 return url;
355 MediaDevice *
356 MediaBrowser::deviceFromId( const QString &id ) const
358 for( QList<MediaDevice *>::const_iterator it = m_devices.constBegin();
359 it != m_devices.end();
360 it++ )
362 if( (*it)->uid() == id )
363 return (*it);
366 return NULL;
369 void
370 MediaBrowser::activateDevice( const MediaDevice *dev )
372 int index = 0;
373 for( QList<MediaDevice *>::iterator it = m_devices.begin();
374 it != m_devices.end();
375 it++ )
377 if( *it == dev )
379 activateDevice( index );
380 break;
382 index++;
386 void
387 MediaBrowser::activateDevice( int index, bool skipDummy )
389 if( m_currentDevice && m_currentDevice->customAction() )
391 m_toolbar->removeAction( m_currentDevice->customAction() );
392 m_toolbar->hide();
393 m_toolbar->show();
396 foreach( MediaDevice *md, m_devices )
398 if( md && md->view() )
399 md->view()->hide();
402 if( index < 0 )
404 m_currentDevice = m_devices.last();
405 return;
408 if( skipDummy )
409 index++;
411 if( index >= m_devices.count() )
413 if( !m_devices.isEmpty() )
414 m_currentDevice = m_devices.last();
415 else
416 m_currentDevice = 0;
417 updateButtons();
418 queue()->computeSize();
419 updateStats();
420 return;
423 m_currentDevice = m_devices[index];
424 if( m_currentDevice && m_currentDevice->view() )
426 m_currentDevice->view()->show();
427 if( m_currentDevice->customAction() )
429 m_toolbar->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
430 m_toolbar->addAction( m_currentDevice->customAction() );
431 m_toolbar->hide();
432 m_toolbar->show();
435 m_deviceCombo->setCurrentIndex( index-1 );
437 updateButtons();
438 queue()->computeSize();
439 updateStats();
442 void
443 MediaBrowser::addDevice( MediaDevice *device )
445 m_devices.append( device );
447 device->loadConfig();
449 if( device->autoConnect() )
451 device->connectDevice( true );
452 updateButtons();
455 updateDevices();
458 void
459 MediaBrowser::removeDevice( MediaDevice *device )
461 DEBUG_BLOCK
463 debug() << "remove device: type=" << device->deviceType();
465 for( QList<MediaDevice *>::iterator it = m_devices.begin();
466 it != m_devices.end();
467 it++ )
469 if( *it == device )
471 bool current = ( (*it)->uid() == m_currentDevice->uid() );
472 m_devices.erase( it );
473 if( current )
474 activateDevice( 0, false );
475 break;
479 if( device->isConnected() )
481 if( device->disconnectDevice( false /* don't run post-disconnect command */ ) )
482 unloadDevicePlugin( device );
483 else
485 debug() << "Cannot remove device because disconnect failed";
486 Amarok::StatusBar::instance()->longMessage(
487 i18n( "Cannot remove device because disconnect failed" ),
488 KDE::StatusBar::Warning );
491 else
492 unloadDevicePlugin( device );
494 updateDevices();
497 void
498 MediaBrowser::updateDevices()
500 m_deviceCombo->clear();
501 uint i = 0;
502 for( QList<MediaDevice *>::iterator it = m_devices.begin();
503 it != m_devices.end();
504 it++ )
506 if( m_devices.count() > 1 && dynamic_cast<DummyMediaDevice *>(*it) )
507 continue;
508 QString name = (*it)->name();
509 if( !(*it)->deviceNode().isEmpty() )
511 name = i18n( "%1 at %2", name, (*it)->deviceNode() );
513 if( (*it)->hasMountPoint() && !(*it)->mountPoint().isEmpty() )
515 name += i18n( " (mounted at %1)", (*it)->mountPoint() );
517 m_deviceCombo->addItem( name, i );
518 if( !m_currentDevice || (*it)->uid() == m_currentDevice->uid() )
520 m_deviceCombo->setCurrentItem( name );
522 i++;
524 m_deviceCombo->setEnabled( m_devices.count() > 1 );
525 m_haveDevices = m_devices.count() > 1;
528 QStringList
529 MediaBrowser::deviceNames() const
531 QStringList list;
533 for( QList<MediaDevice *>::const_iterator it = m_devices.constBegin();
534 it != m_devices.constEnd();
535 it++ )
537 QString name = (*it)->name();
538 list << name;
541 return list;
544 bool
545 MediaBrowser::deviceSwitch( const QString &name )
547 int index = 0;
548 for( QList<MediaDevice *>::iterator it = m_devices.begin();
549 it != m_devices.end();
550 it++ )
552 if( (*it)->name() == name )
554 activateDevice( index, false );
555 return true;
557 index++;
560 return false;
563 void
564 MediaBrowser::transcodingFinished( const QString &src, const QString &dst )
566 KUrl srcJob = KUrl( m_transcodeSrc );
567 KUrl srcResult = KUrl( src );
569 if( srcJob.path() == srcResult.path() )
571 m_transcodedUrl = KUrl( dst );
572 m_waitForTranscode = false;
574 else
576 debug() << "transcoding for " << src << " finished, "
577 << "but we are waiting for " << m_transcodeSrc << " -- aborting";
578 m_waitForTranscode = false;
582 KUrl
583 MediaBrowser::transcode( const KUrl &src, const QString &filetype )
585 const ScriptManager* const sm = ScriptManager::instance();
587 if( sm->transcodeScriptRunning().isEmpty() )
589 debug() << "cannot transcode with no transcoder registered";
590 return KUrl();
593 m_waitForTranscode = true;
594 m_transcodeSrc = src.url();
595 m_transcodedUrl = KUrl();
596 ScriptManager::instance()->notifyTranscode( src.url(), filetype );
598 while( m_waitForTranscode && !sm->transcodeScriptRunning().isEmpty() )
600 usleep( 10000 );
601 kapp->processEvents( QEventLoop::AllEvents );
604 return m_transcodedUrl;
608 void
609 MediaBrowser::slotSetFilterTimeout() //SLOT
611 m_timer->start( 280 ); //stops the timer for us first
614 void
615 MediaBrowser::slotSetFilter() //SLOT
617 m_timer->stop();
619 if( m_currentDevice )
620 m_currentDevice->view()->setFilter( m_searchWidget->lineEdit()->text() );
623 void
624 MediaBrowser::slotSetFilter( const QString &text )
626 m_searchWidget->lineEdit()->setText( text );
627 slotSetFilter();
630 void
631 MediaBrowser::slotEditFilter()
633 EditFilterDialog *fd = new EditFilterDialog( this, true, m_searchWidget->lineEdit()->text() );
634 connect( fd, SIGNAL(filterChanged(const QString &)), SLOT(slotSetFilter(const QString &)) );
635 if( fd->exec() )
636 m_searchWidget->lineEdit()->setText( fd->filter() );
637 delete fd;
640 void
641 MediaBrowser::prepareToQuit()
643 m_waitForTranscode = false;
644 m_quitting = true;
645 for( QList<MediaDevice *>::iterator it = m_devices.begin();
646 it != m_devices.end();
647 ++it )
649 if( (*it)->isConnected() )
650 (*it)->disconnectDevice( false /* don't unmount */ );
654 MediaBrowser::~MediaBrowser()
656 debug() << "having to remove " << m_devices.count() << " devices";
657 while( !m_devices.isEmpty() )
659 removeDevice( m_devices.last() );
662 queue()->save( Amarok::saveLocation() + "transferlist.xml" );
664 delete m_deviceCombo;
665 delete m_queue;
668 MediaView::MediaView( QWidget* parent, MediaDevice *device )
669 : K3ListView( parent )
670 , m_parent( parent )
671 , m_device( device )
673 hide();
674 setSelectionMode( Q3ListView::Extended );
675 setItemsMovable( false );
676 setShowSortIndicator( true );
677 setFullWidth( true );
678 setRootIsDecorated( true );
679 setDragEnabled( true );
680 setDropVisualizer( true ); //the visualizer (a line marker) is drawn when dragging over tracks
681 setDropHighlighter( true ); //and the highligther (a focus rect) is drawn when dragging over playlists
682 setDropVisualizerWidth( 3 );
683 setAcceptDrops( true );
685 header()->hide();
686 addColumn( i18n( "Remote Media" ) );
688 KActionCollection* ac = new KActionCollection( this );
689 KStandardAction::selectAll( this, SLOT( selectAll() ), ac );
691 connect( this, SIGNAL( contextMenuRequested( Q3ListViewItem*, const QPoint&, int ) ),
692 this, SLOT( rmbPressed( Q3ListViewItem*, const QPoint&, int ) ) );
694 connect( this, SIGNAL( itemRenamed( Q3ListViewItem* ) ),
695 this, SLOT( renameItem( Q3ListViewItem* ) ) );
697 connect( this, SIGNAL( expanded( Q3ListViewItem* ) ),
698 this, SLOT( slotExpand( Q3ListViewItem* ) ) );
700 connect( this, SIGNAL( returnPressed( Q3ListViewItem* ) ),
701 this, SLOT( invokeItem( Q3ListViewItem* ) ) );
703 connect( this, SIGNAL( doubleClicked( Q3ListViewItem*, const QPoint&, int ) ),
704 this, SLOT( invokeItem( Q3ListViewItem*, const QPoint &, int ) ) );
707 void
708 MediaView::keyPressEvent( QKeyEvent *e )
710 if( e->key() == Qt::Key_Delete )
711 m_device->deleteFromDevice();
712 else
713 K3ListView::keyPressEvent( e );
716 void
717 MediaView::invokeItem( Q3ListViewItem* i, const QPoint& point, int column ) //SLOT
719 if( column == -1 )
720 return;
722 QPoint p = mapFromGlobal( point );
723 if ( p.x() > header()->sectionPos( header()->mapToIndex( 0 ) ) + treeStepSize() * ( i->depth() + ( rootIsDecorated() ? 1 : 0) ) + itemMargin()
724 || p.x() < header()->sectionPos( header()->mapToIndex( 0 ) ) )
725 invokeItem( i );
729 void
730 MediaView::invokeItem( Q3ListViewItem *i )
732 MediaItem *item = static_cast<MediaItem *>( i );
733 if( !item )
734 return;
736 KUrl::List urls = nodeBuildDragList( item );
737 The::playlistModel()->insertMedia( urls, Playlist::AppendAndPlay );
740 void
741 MediaView::renameItem( Q3ListViewItem *item )
743 m_device->renameItem( item );
746 void
747 MediaView::slotExpand( Q3ListViewItem *item )
749 m_device->expandItem( item );
753 MediaView::~MediaView()
758 Q3DragObject *
759 MediaView::dragObject()
761 KUrl::List urls = nodeBuildDragList( 0 );
762 K3MultipleDrag *md = new K3MultipleDrag( viewport() );
763 md->addDragObject( K3ListView::dragObject() );
764 K3URLDrag* ud = new K3URLDrag( urls, viewport() );
765 md->addDragObject( ud );
766 md->setPixmap( CollectionDB::createDragPixmap( urls ),
767 QPoint( CollectionDB::DRAGPIXMAP_OFFSET_X, CollectionDB::DRAGPIXMAP_OFFSET_Y ) );
768 return md;
772 KUrl::List
773 MediaView::nodeBuildDragList( MediaItem* item, int flags )
775 KUrl::List items;
776 MediaItem* fi;
778 if ( !item )
780 fi = static_cast<MediaItem*>(firstChild());
782 else
783 fi = item;
785 while ( fi )
787 if( fi->isVisible() )
789 if ( fi->isSelected() || !(flags & OnlySelected ) )
791 if( fi->isLeafItem() || fi->type() == MediaItem::DIRECTORY )
792 items += fi->url();
793 else
795 if(fi->childCount() )
796 items += nodeBuildDragList( static_cast<MediaItem*>(fi->firstChild()), None );
799 else
801 if ( fi->childCount() )
802 items += nodeBuildDragList( static_cast<MediaItem*>(fi->firstChild()), OnlySelected );
805 fi = static_cast<MediaItem*>(fi->nextSibling());
807 return items;
811 MediaView::getSelectedLeaves( MediaItem *parent, QList<MediaItem*> *list, int flags )
813 int numFiles = 0;
814 if( !list )
815 list = new QList<MediaItem*>;
817 MediaItem *it;
818 if( !parent )
819 it = static_cast<MediaItem *>(firstChild());
820 else
821 it = static_cast<MediaItem *>(parent->firstChild());
823 for( ; it; it = static_cast<MediaItem*>(it->nextSibling()))
825 if( it->isVisible() )
827 if( it->childCount() && !( it->type() == MediaItem::DIRECTORY && it->isSelected() ) )
829 int f = flags;
830 if( it->isSelected() )
831 f &= ~OnlySelected;
832 numFiles += getSelectedLeaves(it, list, f );
834 if( it->isSelected() || !(flags & OnlySelected) )
836 if( it->type() == MediaItem::TRACK ||
837 it->type() == MediaItem::DIRECTORY ||
838 it->type() == MediaItem::PODCASTITEM ||
839 it->type() == MediaItem::PLAYLISTITEM||
840 it->type() == MediaItem::INVISIBLE ||
841 it->type() == MediaItem::ORPHANED )
843 if( flags & OnlyPlayed )
845 if( it->played() > 0 )
846 numFiles++;
848 else
849 numFiles++;
851 if( ( it->isLeafItem() && (!(flags & OnlyPlayed) || it->played()>0) )
852 || it->type() == MediaItem::DIRECTORY )
853 list->append( it );
857 return numFiles;
861 bool
862 MediaView::acceptDrag( QDropEvent *e ) const
864 if( e->source() == MediaBrowser::queue()->viewport() )
865 return false;
867 return e->source() == viewport()
868 || e->mimeData()->hasFormat( "amarok-sql" )
869 || KUrl::List::canDecode( e->mimeData() );
872 void
873 MediaView::contentsDropEvent( QDropEvent *e )
875 cleanDropVisualizer();
876 cleanItemHighlighter();
878 if(e->source() == viewport())
880 const QPoint p = contentsToViewport( e->pos() );
881 MediaItem *item = static_cast<MediaItem *>(itemAt( p ));
883 if( !item && MediaBrowser::instance()->currentDevice()->m_type != "generic-mediadevice" )
884 return;
886 QList<MediaItem*> items;
888 if( !item || item->type() == MediaItem::DIRECTORY ||
889 item->type() == MediaItem::TRACK )
891 QList<MediaItem*> items;
892 getSelectedLeaves( 0, &items );
893 m_device->addToDirectory( item, items );
895 else if( item->type() == MediaItem::PLAYLIST )
897 MediaItem *list = item;
898 MediaItem *after = 0;
899 for(MediaItem *it = static_cast<MediaItem *>(item->firstChild());
901 it = static_cast<MediaItem *>(it->nextSibling()))
902 after = it;
904 getSelectedLeaves( 0, &items );
905 m_device->addToPlaylist( list, after, items );
907 else if( item->type() == MediaItem::PLAYLISTITEM )
909 MediaItem *list = static_cast<MediaItem *>(item->parent());
910 MediaItem *after = 0;
911 for(MediaItem *it = static_cast<MediaItem*>(item->parent()->firstChild());
913 it = static_cast<MediaItem *>(it->nextSibling()))
915 if(it == item)
916 break;
917 after = it;
920 getSelectedLeaves( 0, &items );
921 m_device->addToPlaylist( list, after, items );
923 else if( item->type() == MediaItem::PLAYLISTSROOT )
925 QList<MediaItem*> items;
926 getSelectedLeaves( 0, &items );
927 QString base( i18n("New Playlist") );
928 QString name = base;
929 int i=1;
930 while( item->findItem(name) )
932 QString num;
933 num.setNum(i);
934 name = base + ' ' + num;
935 i++;
937 MediaItem *pl = m_device->newPlaylist(name, item, items);
938 ensureItemVisible(pl);
939 rename(pl, 0);
942 else
944 if( e->mimeData()->hasFormat( "amarok-sql" ) )
946 QString data( e->mimeData()->data( "amarok-sql" ) );
947 QString playlist = data.section( "\n", 0, 0 );
948 QString query = data.section( "\n", 1 );
949 QStringList values = CollectionDB::instance()->query( query );
950 KUrl::List list = CollectionDB::instance()->URLsFromSqlDrag( values );
951 MediaBrowser::queue()->addUrls( list, playlist );
953 else if ( KUrl::List::canDecode( e->mimeData() ) )
955 KUrl::List list = KUrl::List::fromMimeData( e->mimeData() );
956 MediaBrowser::queue()->addUrls( list );
961 void
962 MediaView::viewportPaintEvent( QPaintEvent *e )
964 K3ListView::viewportPaintEvent( e );
966 // Superimpose bubble help:
968 if ( !MediaBrowser::instance()->currentDevice() || !MediaBrowser::instance()->currentDevice()->isConnected() )
970 QPainter p( viewport() );
972 Q3SimpleRichText t( i18n(
973 "<div align=center>"
974 "<h3>Media Device Browser</h3>"
975 "Configure your media device and then "
976 "click the Connect button to access your media device. "
977 "Drag and drop files to enqueue them for transfer."
978 "</div>" ), QApplication::font() );
980 t.setWidth( width() - 50 );
982 const uint w = t.width() + 20;
983 const uint h = t.height() + 20;
985 p.setBrush( palette().background() );
986 p.drawRoundRect( 15, 15, w, h, (8*200)/w, (8*200)/h );
987 t.draw( &p, 20, 20, QRect(), palette() );
989 MediaBrowser::instance()->updateButtons();
992 void
993 MediaView::rmbPressed( Q3ListViewItem *item, const QPoint &p, int i )
995 if( m_device->isConnected() )
996 m_device->rmbPressed( item, p, i );
999 MediaItem *
1000 MediaView::newDirectory( MediaItem *parent )
1002 bool ok;
1003 const QString name = KInputDialog::getText(i18n("Add Directory"), i18n("Directory Name:"), QString(), &ok, this);
1005 if( ok && !name.isEmpty() )
1007 m_device->newDirectory( name, parent );
1010 return 0;
1013 void
1014 MediaBrowser::deviceAdded( const QString &udi )
1016 DEBUG_BLOCK
1017 debug() << "deviceAdded called with a udi of: " << udi;
1018 MediaDevice *md = loadDevicePlugin( udi );
1019 if( md )
1021 addDevice( md );
1022 if( m_currentDevice == *(m_devices.begin()) || m_currentDevice == *(m_devices.end()) )
1023 activateDevice( m_devices.count()-1, false );
1027 void
1028 MediaBrowser::deviceRemoved( const QString &udi )
1030 for( QList<MediaDevice *>::iterator it = m_devices.begin();
1031 it != m_devices.end();
1032 it++ )
1034 if( (*it)->m_uid == udi )
1036 if( (*it)->isConnected() )
1038 if( (*it)->disconnectDevice() )
1039 removeDevice( *it );
1040 Amarok::StatusBar::instance()->longMessage(
1041 i18n( "The device %1 was removed before it was disconnected. "
1042 "In order to avoid possible data loss, press the \"Disconnect\" "
1043 "button before disconnecting the device.", (*it)->name() ),
1044 KDE::StatusBar::Warning );
1046 else
1047 removeDevice( *it );
1048 break;
1053 MediaDevice *
1054 MediaBrowser::loadDevicePlugin( const QString &udi )
1056 DEBUG_BLOCK
1058 QString name;
1059 QString mountPoint;
1060 QString protocol;
1062 if( MediaDeviceCache::instance()->deviceType( udi ) == MediaDeviceCache::SolidType )
1064 debug() << "udi " << udi << " detected as a Solid device";
1065 Solid::Device solidDevice( udi );
1067 Solid::PortableMediaPlayer* pmp = solidDevice.as<Solid::PortableMediaPlayer>();
1069 //TODO: Generic storage? need to set mount point if so...
1070 if( !pmp )
1072 debug() << "Failed to convert Solid device to PortableMediaPlayer";
1073 return 0;
1075 if( pmp->supportedProtocols().size() == 0 )
1077 debug() << "Portable Media Player " << udi << " does not support any protocols";
1078 return 0;
1081 foreach( QString supported, pmp->supportedProtocols() )
1082 debug() << "Device supports protocol " << supported;
1084 protocol = pmp->supportedProtocols()[0];
1085 if( protocol == "storage" )
1086 protocol = "generic";
1087 if( protocol == "pde" )
1088 protocol == "njb";
1090 name = solidDevice.vendor() + " - " + solidDevice.product();
1092 else if( MediaDeviceCache::instance()->deviceType( udi ) == MediaDeviceCache::ManualType )
1094 QString handler = Amarok::config( "PortableDevices" ).readEntry( udi, QString() );
1095 if( handler.isEmpty() || !handler.startsWith( "manual" ) )
1097 //this shouldn't happen, as manually adding the device should create the necessary entry
1098 debug() << "Something's wrong, trying to manually load a non-existant or non-manual manual device";
1099 return 0;
1101 QStringList sl = handler.split( "|" );
1102 if( sl[0] != "manual" )
1103 return 0;
1104 name = sl[2];
1105 protocol = sl[1];
1106 mountPoint = sl[3];
1109 protocol += "-mediadevice";
1110 QString query = "[X-KDE-Amarok-plugintype] == 'mediadevice' and [X-KDE-Amarok-name] == '%1'";
1111 Amarok::Plugin *plugin = PluginManager::createFromQuery( query.arg( protocol ) );
1113 if( plugin )
1115 debug() << "Returning plugin!";
1116 MediaDevice *device = static_cast<MediaDevice *>( plugin );
1117 device->init( this );
1118 device->m_uid = udi;
1119 device->m_name = name;
1120 device->m_type = protocol;
1121 device->m_mountPoint = mountPoint;
1122 return device;
1125 debug() << "no plugin for " << protocol;
1126 return 0;
1129 void
1130 MediaBrowser::unloadDevicePlugin( MediaDevice *device )
1132 DEBUG_BLOCK
1134 if( !device )
1135 return;
1137 disconnect( device ); // disconnect all signals
1139 if( dynamic_cast<DummyMediaDevice *>(device) )
1141 delete device;
1143 else
1145 PluginManager::unload( device );
1149 bool
1150 MediaBrowser::config()
1152 if( m_deviceCombo->currentText() == "No Device Selected" )
1154 Amarok::StatusBar::instance()->longMessage( i18n( "No device selected to configure." ),
1155 KDE::StatusBar::Sorry );
1156 return true;
1159 DeviceConfigureDialog* dcd = new DeviceConfigureDialog( m_currentDevice );
1160 dcd->exec();
1161 bool successful = dcd->successful();
1162 delete dcd;
1163 return successful;
1166 void
1167 MediaBrowser::updateButtons()
1169 if( !connectAction() || !disconnectAction() || !transferAction() )
1170 return;
1172 if( m_currentDevice )
1174 transferAction()->setVisible( m_currentDevice->m_transfer );
1175 customAction()->setVisible( m_currentDevice->m_customButton );
1176 configAction()->setVisible( m_currentDevice->m_configure );
1178 connectAction()->setEnabled( !m_currentDevice->isConnected() );
1179 disconnectAction()->setEnabled( m_currentDevice->isConnected() );
1180 transferAction()->setEnabled( m_currentDevice->isConnected() && m_queue->childCount() > 0 );
1181 if( customAction() )
1182 customAction()->setEnabled( true );
1184 else
1186 connectAction()->setEnabled( false );
1187 disconnectAction()->setEnabled( false );
1188 transferAction()->setEnabled( false );
1189 if( customAction() )
1190 customAction()->setEnabled( false );
1194 void
1195 MediaBrowser::updateStats()
1197 if( !m_stats )
1198 return;
1200 KIO::filesize_t queued = m_queue->totalSize();
1202 QString text = i18np( " 1 track in queue", " %1 tracks in queue", m_queue->childCount() );
1203 if(m_queue->childCount() > 0)
1205 text += i18n(" (%1)", KIO::convertSize( queued ) );
1208 KIO::filesize_t total, avail;
1209 if( m_currentDevice && m_currentDevice->getCapacity(&total, &avail) )
1211 text += i18n( " - %1 of %2 available", KIO::convertSize( avail ), KIO::convertSize( total ) );
1213 m_stats->m_used = total-avail;
1214 m_stats->m_total = total;
1215 m_stats->m_scheduled = queued;
1217 else
1219 m_stats->m_used = 0;
1220 m_stats->m_total = 0;
1221 m_stats->m_scheduled = queued;
1224 m_stats->setText(text);
1225 m_stats->setToolTip( text );
1229 bool
1230 MediaView::setFilter( const QString &filter, MediaItem *parent )
1232 bool advanced = ExpressionParser::isAdvancedExpression( filter );
1233 QList<int> defaultColumns;
1234 defaultColumns << MetaBundle::Album;
1235 defaultColumns << MetaBundle::Title;
1236 defaultColumns << MetaBundle::Artist;
1238 bool root = false;
1239 MediaItem *it;
1240 if( !parent )
1242 root = true;
1243 it = static_cast<MediaItem *>(firstChild());
1245 else
1247 it = static_cast<MediaItem *>(parent->firstChild());
1250 bool childrenVisible = false;
1251 for( ; it; it = static_cast<MediaItem *>(it->nextSibling()))
1253 bool visible = true;
1254 if(it->isLeafItem())
1256 if( advanced )
1258 ParsedExpression parsed = ExpressionParser::parse( filter );
1259 visible = it->bundle() && it->bundle()->matchesParsedExpression( parsed, defaultColumns );
1261 else
1263 visible = it->bundle() && it->bundle()->matchesSimpleExpression( filter, defaultColumns );
1266 else
1268 visible = setFilter(filter, it);
1269 if(it->type()==MediaItem::PLAYLISTSROOT || it->type()==MediaItem::PLAYLIST)
1271 visible = true;
1273 else if(it->type()==MediaItem::DIRECTORY)
1275 bool match = true;
1276 QStringList list = filter.split( " ", QString::SkipEmptyParts );
1277 for( QStringList::iterator i = list.begin();
1278 i != list.end();
1279 ++i )
1281 if( !(*it).text(0).contains( *i ) )
1283 match = false;
1284 break;
1287 if( match )
1288 visible = true;
1291 if( filter.isEmpty() )
1292 visible = true;
1293 it->setVisible( visible );
1294 if(visible)
1295 childrenVisible = true;
1298 if( root && m_device )
1299 m_device->updateRootItems();
1301 return childrenVisible;
1304 void
1305 MediaQueue::syncPlaylist( const QString &name, const QString &query, bool loading )
1307 MediaItem* item = new MediaItem( this, lastItem() );
1308 item->setType( MediaItem::PLAYLIST );
1309 item->setExpandable( false );
1310 item->setData( query );
1311 item->m_playlistName = name;
1312 item->setText( 0, name );
1313 item->m_flags |= MediaItem::SmartPlaylist;
1314 m_parent->m_progress->setRange( 0, m_parent->m_progress->maximum() + 1 );
1315 itemCountChanged();
1316 if( !loading )
1317 URLsAdded();
1320 void
1321 MediaQueue::syncPlaylist( const QString &name, const KUrl &url, bool loading )
1323 MediaItem* item = new MediaItem( this, lastItem() );
1324 item->setType( MediaItem::PLAYLIST );
1325 item->setExpandable( false );
1326 item->setData( url.url() );
1327 item->m_playlistName = name;
1328 item->setText( 0, name );
1329 m_parent->m_progress->setRange( 0, m_parent->m_progress->maximum() + 1 );
1330 itemCountChanged();
1331 if( !loading )
1332 URLsAdded();
1335 void
1336 MediaQueue::addUrl( const KUrl& url2, MetaBundle *bundle, const QString &playlistName )
1338 KUrl url = Amarok::mostLocalURL( url2 );
1340 //Port 2.0
1341 // if( PlaylistFile::isPlaylistFile( url ) )
1342 // {
1343 // QString name = url.path().section( "/", -1 ).section( ".", 0, -2 ).replace( "_", " " );
1344 // PlaylistFile playlist( url.path() );
1346 // if( playlist.isError() )
1347 // {
1348 // Amarok::StatusBar::instance()->longMessage( i18n( "Failed to load playlist: %1", url.path() ),
1349 // KDE::StatusBar::Sorry );
1350 // return;
1351 // }
1353 // for( BundleList::iterator it = playlist.bundles().begin();
1354 // it != playlist.bundles().end();
1355 // ++it )
1356 // {
1357 // addUrl( (*it).url(), 0, name );
1358 // }
1359 // return;
1360 // }
1361 if( url.protocol() == "file" && QFileInfo( url.path() ).isDir() )
1363 //TODO: PORT
1364 // KUrl::List urls = Amarok::recursiveUrlExpand( url );
1365 // oldForeachType( KUrl::List, urls )
1366 // addUrl( *it );
1367 // return;
1370 if( playlistName.isNull() )
1372 for( MediaItem *it = static_cast<MediaItem *>(firstChild());
1374 it = static_cast<MediaItem *>(it->nextSibling()) )
1376 if( it->url() == url )
1378 Amarok::StatusBar::instance()->shortMessage(
1379 i18n( "Track already queued for transfer: %1", url.url() ) );
1380 return;
1385 if(!bundle)
1386 bundle = new MetaBundle( url );
1388 MediaItem* item = new MediaItem( this, lastItem() );
1389 item->setExpandable( false );
1390 item->setDropEnabled( true );
1391 item->setBundle( bundle );
1392 if(bundle->podcastBundle() )
1394 item->setType( MediaItem::PODCASTITEM );
1396 item->m_playlistName = playlistName;
1398 QString text = item->bundle()->prettyTitle();
1399 if( text.isEmpty() || (!item->bundle()->isValidMedia() && !item->bundle()->podcastBundle()) )
1400 text = item->bundle()->url().prettyUrl();
1401 if( !item->m_playlistName.isNull() )
1403 text += " (" + item->m_playlistName + ')';
1405 item->setText( 0, text);
1407 m_parent->updateButtons();
1408 m_parent->m_progress->setRange( 0, m_parent->m_progress->maximum() + 1 );
1409 addItemToSize( item );
1410 itemCountChanged();
1413 void
1414 MediaQueue::addUrl( const KUrl &url, MediaItem *item )
1416 DEBUG_BLOCK
1417 MediaItem *newitem = new MediaItem( this, lastItem() );
1418 newitem->setExpandable( false );
1419 newitem->setDropEnabled( true );
1420 MetaBundle *bundle = new MetaBundle( *item->bundle() );
1421 KUrl filepath(url);
1422 filepath.addPath( bundle->filename() );
1423 bundle->setUrl( filepath );
1424 newitem->m_device = item->m_device;
1425 if(bundle->podcastBundle() )
1427 item->setType( MediaItem::PODCASTITEM );
1429 QString text = item->bundle()->prettyTitle();
1430 if( text.isEmpty() || (!item->bundle()->isValidMedia() && !item->bundle()->podcastBundle()) )
1431 text = item->bundle()->url().prettyUrl();
1432 if( !item->m_playlistName.isEmpty() )
1434 text += " (" + item->m_playlistName + ')';
1436 newitem->setText( 0, text);
1437 newitem->setBundle( bundle );
1438 m_parent->updateButtons();
1439 m_parent->m_progress->setRange( 0, m_parent->m_progress->maximum() + 1 );
1440 addItemToSize( item );
1441 itemCountChanged();
1445 void
1446 MediaQueue::addUrls( const KUrl::List urls, const QString &playlistName )
1448 KUrl::List::ConstIterator it = urls.begin();
1449 for ( ; it != urls.end(); ++it )
1450 addUrl( *it, 0, playlistName );
1452 URLsAdded();
1455 void
1456 MediaQueue::URLsAdded()
1458 m_parent->updateStats();
1459 m_parent->updateButtons();
1460 if( m_parent->currentDevice()
1461 && m_parent->currentDevice()->isConnected()
1462 && m_parent->currentDevice()->asynchronousTransfer()
1463 && !m_parent->currentDevice()->isTransferring() )
1464 m_parent->currentDevice()->transferFiles();
1466 save( Amarok::saveLocation() + "transferlist.xml" );
1469 Q3DragObject *
1470 MediaQueue::dragObject()
1472 KUrl::List urls;
1473 for( Q3ListViewItem *it = firstChild(); it; it = it->nextSibling() )
1475 if( it->isVisible() && it->isSelected() && static_cast<MediaItem *>(it) )
1476 urls += static_cast<MediaItem *>(it)->url();
1479 K3MultipleDrag *md = new K3MultipleDrag( viewport() );
1480 Q3DragObject *d = K3ListView::dragObject();
1481 K3URLDrag* urldrag = new K3URLDrag( urls, viewport() );
1482 md->addDragObject( d );
1483 md->addDragObject( urldrag );
1484 md->setPixmap( CollectionDB::createDragPixmap( urls ),
1485 QPoint( CollectionDB::DRAGPIXMAP_OFFSET_X, CollectionDB::DRAGPIXMAP_OFFSET_Y ) );
1486 return md;
1489 void
1490 MediaBrowser::cancelClicked()
1492 DEBUG_BLOCK
1494 m_waitForTranscode = false;
1495 if( m_currentDevice )
1496 m_currentDevice->abortTransfer();
1499 void
1500 MediaBrowser::transferClicked()
1502 transferAction()->setEnabled( false );
1503 if( m_currentDevice
1504 && m_currentDevice->isConnected()
1505 && !m_currentDevice->isTransferring() )
1507 if( !m_currentDevice->hasTransferDialog() )
1508 m_currentDevice->transferFiles();
1509 else
1511 m_currentDevice->runTransferDialog();
1512 //may not work with non-TransferDialog-class object, but maybe some run time introspection could solve it?
1513 if( m_currentDevice->getTransferDialog() &&
1514 ( reinterpret_cast<TransferDialog *>(m_currentDevice->getTransferDialog()))->isAccepted() )
1515 m_currentDevice->transferFiles();
1516 else
1517 updateButtons();
1520 m_currentDevice->m_transferDir = m_currentDevice->mountPoint();
1523 void
1524 MediaBrowser::connectClicked()
1526 bool haveToConfig = false;
1527 // it was just clicked, so isOn() == true.
1528 if( m_currentDevice && !m_currentDevice->isConnected() )
1530 haveToConfig = !m_currentDevice->connectDevice();
1533 haveToConfig |= !m_currentDevice;
1534 haveToConfig |= ( m_currentDevice && !m_currentDevice->isConnected() );
1536 if ( !m_currentDevice->needsManualConfig() )
1537 haveToConfig = false;
1539 if( haveToConfig && m_devices.at( 0 ) == m_currentDevice )
1541 if( config() && m_currentDevice && !m_currentDevice->isConnected() )
1542 m_currentDevice->connectDevice();
1545 updateDevices();
1546 updateButtons();
1547 updateStats();
1551 void
1552 MediaBrowser::disconnectClicked()
1554 if( m_currentDevice && m_currentDevice->isTransferring() )
1556 int action = KMessageBox::questionYesNoCancel( MediaBrowser::instance(),
1557 i18n( "Transfer in progress. Finish or stop after current track?" ),
1558 i18n( "Stop Transfer?" ),
1559 KGuiItem(i18n("&Finish"), "goto-page"),
1560 KGuiItem(i18n("&Stop"), "media-eject") );
1561 if( action == KMessageBox::Cancel )
1563 return;
1565 else if( action == KMessageBox::Yes )
1567 m_currentDevice->scheduleDisconnect();
1568 return;
1572 transferAction()->setEnabled( false );
1573 disconnectAction()->setEnabled( false );
1575 if( m_currentDevice )
1577 m_currentDevice->disconnectDevice( true );
1580 updateDevices();
1581 updateButtons();
1582 updateStats();
1585 void
1586 MediaBrowser::customClicked()
1588 if( m_currentDevice )
1589 m_currentDevice->customClicked();
1592 void
1593 MediaItem::syncStatsFromPath( const QString &url )
1595 if( url.isEmpty() )
1596 return;
1598 // copy Amarok rating, play count and last played time to device
1599 int rating = CollectionDB::instance()->getSongRating( url )*10;
1600 if( rating )
1601 setRating( rating );
1602 int playcount = CollectionDB::instance()->getPlayCount( url );
1603 if( playcount > played() )
1604 setPlayCount( playcount );
1605 QDateTime lastplay = CollectionDB::instance()->getLastPlay( url );
1606 if( lastplay > playTime() )
1607 setLastPlayed( lastplay.toTime_t() );
1610 void
1611 MediaQueue::save( const QString &path )
1613 QFile file( path );
1615 if( !file.open( QIODevice::WriteOnly ) ) return;
1617 QDomDocument newdoc;
1618 QDomElement transferlist = newdoc.createElement( "playlist" );
1619 transferlist.setAttribute( "product", "Amarok" );
1620 transferlist.setAttribute( "version", APP_VERSION );
1621 newdoc.appendChild( transferlist );
1623 for( const MediaItem *item = static_cast<MediaItem *>( firstChild() );
1624 item;
1625 item = static_cast<MediaItem *>( item->nextSibling() ) )
1627 QDomElement i = newdoc.createElement("item");
1628 i.setAttribute("url", item->url().url());
1630 if( item->bundle() )
1632 QDomElement attr = newdoc.createElement( "Title" );
1633 QDomText t = newdoc.createTextNode( item->bundle()->title() );
1634 attr.appendChild( t );
1635 i.appendChild( attr );
1637 attr = newdoc.createElement( "Artist" );
1638 t = newdoc.createTextNode( item->bundle()->artist() );
1639 attr.appendChild( t );
1640 i.appendChild( attr );
1642 attr = newdoc.createElement( "Album" );
1643 t = newdoc.createTextNode( item->bundle()->album() );
1644 attr.appendChild( t );
1645 i.appendChild( attr );
1647 attr = newdoc.createElement( "Year" );
1648 t = newdoc.createTextNode( QString::number( item->bundle()->year() ) );
1649 attr.appendChild( t );
1650 i.appendChild( attr );
1652 attr = newdoc.createElement( "Comment" );
1653 t = newdoc.createTextNode( item->bundle()->comment() );
1654 attr.appendChild( t );
1655 i.appendChild( attr );
1657 attr = newdoc.createElement( "Genre" );
1658 t = newdoc.createTextNode( item->bundle()->genre() );
1659 attr.appendChild( t );
1660 i.appendChild( attr );
1662 attr = newdoc.createElement( "Track" );
1663 t = newdoc.createTextNode( QString::number( item->bundle()->track() ) );
1664 attr.appendChild( t );
1665 i.appendChild( attr );
1668 if(item->type() == MediaItem::PODCASTITEM)
1670 i.setAttribute( "podcast", "1" );
1673 if(item->type() == MediaItem::PODCASTITEM
1674 && item->bundle()->podcastBundle())
1676 PodcastEpisodeBundle *peb = item->bundle()->podcastBundle();
1677 QDomElement attr = newdoc.createElement( "PodcastDescription" );
1678 QDomText t = newdoc.createTextNode( peb->description() );
1679 attr.appendChild( t );
1680 i.appendChild( attr );
1682 attr = newdoc.createElement( "PodcastAuthor" );
1683 t = newdoc.createTextNode( peb->author() );
1684 attr.appendChild( t );
1685 i.appendChild( attr );
1687 attr = newdoc.createElement( "PodcastRSS" );
1688 t = newdoc.createTextNode( peb->parent().url() );
1689 attr.appendChild( t );
1690 i.appendChild( attr );
1692 attr = newdoc.createElement( "PodcastURL" );
1693 t = newdoc.createTextNode( peb->url().url() );
1694 attr.appendChild( t );
1695 i.appendChild( attr );
1698 if( !item->m_playlistName.isEmpty() )
1700 i.setAttribute( "playlist", item->m_playlistName );
1703 if(item->type() == MediaItem::PLAYLIST)
1705 i.setAttribute( "playlistdata", item->data() );
1706 if( item->flags() & MediaItem::SmartPlaylist )
1707 i.setAttribute( "smartplaylist", "1" );
1710 transferlist.appendChild( i );
1713 QTextStream stream( &file );
1714 stream.setCodec( QTextCodec::codecForName( "UTF-8" ) );
1715 stream.setAutoDetectUnicode( true );
1716 stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1717 stream << newdoc.toString();
1721 void
1722 MediaQueue::load( const QString& filename )
1724 QFile file( filename );
1725 if( !file.open( QIODevice::ReadOnly ) ) {
1726 return;
1729 clearItems();
1731 QTextStream stream( &file );
1732 stream.setCodec( QTextCodec::codecForName( "UTF-8" ) );
1733 stream.setAutoDetectUnicode( true );
1735 QDomDocument d;
1736 QString er;
1737 int l, c;
1738 if( !d.setContent( stream.readAll(), &er, &l, &c ) ) { // return error values
1739 Amarok::StatusBar::instance()->longMessageThreadSafe( i18n(
1740 //TODO add a link to the path to the playlist
1741 "The XML in the transferlist was invalid. Please report this as a bug to the Amarok "
1742 "developers. Thank you." ), KDE::StatusBar::Error );
1743 error() << "[TRANSFERLISTLOADER]: Error loading xml file: " << filename << "(" << er << ")"
1744 << " at line " << l << ", column " << c;
1745 return;
1748 QList<QDomNode> nodes;
1749 const QString ITEM( "item" ); //so we don't construct this QString all the time
1750 for( QDomNode n = d.namedItem( "playlist" ).firstChild(); !n.isNull(); n = n.nextSibling() )
1752 if( n.nodeName() != ITEM ) continue;
1754 QDomElement elem = n.toElement();
1755 if( !elem.isNull() )
1756 nodes += n;
1758 if( !elem.hasAttribute( "url" ) )
1760 continue;
1762 KUrl url(elem.attribute("url"));
1764 bool podcast = elem.hasAttribute( "podcast" );
1765 PodcastEpisodeBundle peb;
1766 if( url.isLocalFile() )
1767 peb.setLocalURL( url );
1768 MetaBundle *bundle = new MetaBundle( url );
1769 for(QDomNode node = elem.firstChild();
1770 !node.isNull();
1771 node = node.nextSibling())
1773 if(node.firstChild().isNull())
1774 continue;
1776 if(node.nodeName() == "Title" )
1777 bundle->setTitle(node.firstChild().toText().nodeValue());
1778 else if(node.nodeName() == "Artist" )
1779 bundle->setArtist(node.firstChild().toText().nodeValue());
1780 else if(node.nodeName() == "Album" )
1781 bundle->setAlbum(node.firstChild().toText().nodeValue());
1782 else if(node.nodeName() == "Year" )
1783 bundle->setYear(node.firstChild().toText().nodeValue().toUInt());
1784 else if(node.nodeName() == "Genre" )
1785 bundle->setGenre(node.firstChild().toText().nodeValue());
1786 else if(node.nodeName() == "Comment" )
1787 bundle->setComment(node.firstChild().toText().nodeValue());
1788 else if(node.nodeName() == "PodcastDescription" )
1789 peb.setDescription( node.firstChild().toText().nodeValue() );
1790 else if(node.nodeName() == "PodcastAuthor" )
1791 peb.setAuthor( node.firstChild().toText().nodeValue() );
1792 else if(node.nodeName() == "PodcastRSS" )
1793 peb.setParent( KUrl( node.firstChild().toText().nodeValue() ) );
1794 else if(node.nodeName() == "PodcastURL" )
1795 peb.setUrl( KUrl( node.firstChild().toText().nodeValue() ) );
1798 if( podcast )
1800 bundle->setPodcastBundle( peb );
1803 QString playlist = elem.attribute( "playlist" );
1804 QString playlistdata = elem.attribute( "playlistdata" );
1805 if( !playlistdata.isEmpty() )
1807 QString smart = elem.attribute( "smartplaylist" );
1808 if( smart.isEmpty() )
1809 syncPlaylist( playlist, KUrl( playlistdata ), true );
1810 else
1811 syncPlaylist( playlist, playlistdata, true );
1813 else
1814 addUrl( url, bundle, playlist );
1817 URLsAdded();
1820 MediaQueue::MediaQueue(MediaBrowser *parent)
1821 : K3ListView( parent ), m_parent( parent )
1823 setFixedHeight( 200 );
1824 setSelectionMode( Q3ListView::Extended );
1825 setItemsMovable( true );
1826 setDragEnabled( true );
1827 setShowSortIndicator( false );
1828 setSorting( -1 );
1829 setFullWidth( true );
1830 setRootIsDecorated( false );
1831 setDropVisualizer( true ); //the visualizer (a line marker) is drawn when dragging over tracks
1832 setDropHighlighter( true ); //and the highligther (a focus rect) is drawn when dragging over playlists
1833 setDropVisualizerWidth( 3 );
1834 setAcceptDrops( true );
1835 addColumn( i18n( "Transfer Queue" ) );
1837 itemCountChanged();
1839 KActionCollection* ac = new KActionCollection( this );
1840 KStandardAction::selectAll( this, SLOT( selectAll() ), ac );
1842 connect( this, SIGNAL( contextMenuRequested( Q3ListViewItem*, const QPoint&, int ) ),
1843 SLOT( slotShowContextMenu( Q3ListViewItem*, const QPoint&, int ) ) );
1844 connect( this, SIGNAL( dropped(QDropEvent*, Q3ListViewItem*, Q3ListViewItem*) ),
1845 SLOT( slotDropped(QDropEvent*, Q3ListViewItem*, Q3ListViewItem*) ) );
1848 bool
1849 MediaQueue::acceptDrag( QDropEvent *e ) const
1852 return e->source() == viewport()
1853 || e->mimeData()->hasFormat( "amarok-sql" )
1854 || KUrl::List::canDecode( e->mimeData() );
1857 void
1858 MediaQueue::slotDropped( QDropEvent* e, Q3ListViewItem* parent, Q3ListViewItem* after)
1860 if( e->source() != viewport() )
1862 if( e->mimeData()->hasFormat( "amarok-sql" ) )
1864 QString data( e->mimeData()->data( "amarok-sql" ) );
1865 QString playlist = data.section( "\n", 0, 0 );
1866 QString query = data.section( "\n", 1 );
1867 QStringList values = CollectionDB::instance()->query( query );
1868 KUrl::List list = CollectionDB::instance()->URLsFromSqlDrag( values );
1869 addUrls( list, playlist );
1871 else if ( KUrl::List::canDecode( e->mimeData() ) )
1873 KUrl::List list = KUrl::List::fromMimeData( e->mimeData() );
1874 if (!list.isEmpty() )
1875 addUrls( list );
1878 else if( Q3ListViewItem *i = currentItem() )
1880 moveItem( i, parent, after );
1884 void
1885 MediaQueue::dropProxyEvent( QDropEvent *e )
1887 slotDropped( e, 0, 0 );
1890 MediaItem*
1891 MediaQueue::findPath( QString path )
1893 for( Q3ListViewItem *item = firstChild();
1894 item;
1895 item = item->nextSibling())
1897 if(static_cast<MediaItem *>(item)->url().path() == path)
1898 return static_cast<MediaItem *>(item);
1901 return 0;
1904 void
1905 MediaQueue::computeSize() const
1907 m_totalSize = 0;
1908 for( Q3ListViewItem *it = firstChild();
1910 it = it->nextSibling())
1912 MediaItem *item = static_cast<MediaItem *>(it);
1914 if( item && item->bundle() &&
1915 ( !m_parent->currentDevice()
1916 || !m_parent->currentDevice()->isConnected()
1917 || !m_parent->currentDevice()->trackExists(*item->bundle()) ) )
1918 m_totalSize += ((item->size()+1023)/1024)*1024;
1922 KIO::filesize_t
1923 MediaQueue::totalSize() const
1925 return m_totalSize;
1928 void
1929 MediaQueue::addItemToSize( const MediaItem *item ) const
1931 if( item && item->bundle() &&
1932 ( !m_parent->currentDevice()
1933 || !m_parent->currentDevice()->isConnected()
1934 || !m_parent->currentDevice()->trackExists(*item->bundle()) ) )
1935 m_totalSize += ((item->size()+1023)/1024)*1024;
1938 void
1939 MediaQueue::subtractItemFromSize( const MediaItem *item, bool unconditionally ) const
1941 if( item && item->bundle() &&
1942 ( !m_parent->currentDevice()
1943 || !m_parent->currentDevice()->isConnected()
1944 || (unconditionally || !m_parent->currentDevice()->trackExists(*item->bundle())) ) )
1945 m_totalSize -= ((item->size()+1023)/1024)*1024;
1948 void
1949 MediaQueue::removeSelected()
1951 QList<Q3ListViewItem*> selected = selectedItems();
1953 QListIterator<Q3ListViewItem*> iter( selected );
1954 while( iter.hasNext() )
1956 Q3ListViewItem *item = iter.next();
1957 if( !(static_cast<MediaItem *>(item)->flags() & MediaItem::Transferring) )
1959 subtractItemFromSize( static_cast<MediaItem *>(item) );
1960 delete item;
1961 if( m_parent->currentDevice() && m_parent->currentDevice()->isTransferring() )
1963 MediaBrowser::instance()->m_progress->setRange( 0, MediaBrowser::instance()->m_progress->maximum() - 1 );
1968 MediaBrowser::instance()->updateStats();
1969 MediaBrowser::instance()->updateButtons();
1970 itemCountChanged();
1973 void
1974 MediaQueue::keyPressEvent( QKeyEvent *e )
1976 if( e->key() == Qt::Key_Delete )
1977 removeSelected();
1978 else
1979 K3ListView::keyPressEvent( e );
1982 void
1983 MediaQueue::itemCountChanged()
1985 if( childCount() == 0 )
1986 hide();
1987 else if( !isShown() )
1988 show();
1991 void
1992 MediaQueue::slotShowContextMenu( Q3ListViewItem* item, const QPoint& point, int )
1994 if( !childCount() )
1995 return;
1997 Q3PopupMenu menu( this );
1999 enum Actions { REMOVE_SELECTED, CLEAR_ALL, START_TRANSFER };
2001 if( item )
2002 menu.insertItem( KIcon( Amarok::icon( "remove_from_playlist" ) ), i18n( "&Remove From Queue" ), REMOVE_SELECTED );
2004 menu.insertItem( KIcon( Amarok::icon( "playlist_clear" ) ), i18n( "&Clear Queue" ), CLEAR_ALL );
2005 menu.insertItem( KIcon( Amarok::icon( "playlist_refresh" ) ), i18n( "&Start Transfer" ), START_TRANSFER );
2006 menu.setItemEnabled( START_TRANSFER,
2007 MediaBrowser::instance()->currentDevice() &&
2008 MediaBrowser::instance()->currentDevice()->isConnected() &&
2009 MediaBrowser::instance()->currentDevice()->m_transfer );
2011 switch( menu.exec( point ) )
2013 case REMOVE_SELECTED:
2014 removeSelected();
2015 break;
2016 case CLEAR_ALL:
2017 clearItems();
2018 break;
2019 case START_TRANSFER:
2020 MediaBrowser::instance()->transferClicked();
2021 break;
2025 void
2026 MediaQueue::clearItems()
2028 clear();
2029 itemCountChanged();
2030 if(m_parent)
2032 computeSize();
2033 m_parent->updateStats();
2034 m_parent->updateButtons();
2039 #include "mediabrowser.moc"