add mp3 and ogg torrent url info to JamendoAlbum
[amarok.git] / src / mediabrowser.cpp
blob6f5a7ff6d69f8144efa15557faba2da4e9adaabe
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 "MediaDevicePluginManager.h"
28 #include "metabundle.h"
29 #include "mountpointmanager.h"
30 #include "playlist/PlaylistModel.h"
31 #include "pluginmanager.h"
32 #include "podcastbundle.h"
33 #include "scriptmanager.h"
34 #include "scrobbler.h"
35 #include "searchwidget.h"
36 #include "statusbar.h"
37 #include "transferdialog.h"
38 #include "TheInstances.h"
40 #include <q3header.h>
41 #include <Q3PopupMenu>
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 DummyMediaDevice : public MediaDevice
109 public:
110 DummyMediaDevice() : MediaDevice()
112 m_name = i18n( "No Device Available" );
113 m_type = "dummy-mediadevice";
114 m_uid = "manual|DummyDevice|none|none";
116 void init( MediaBrowser *browser ) { MediaDevice::init( browser ); }
117 virtual ~DummyMediaDevice() {}
118 virtual bool isConnected() { return false; }
119 virtual MediaItem* trackExists(const MetaBundle&) { return 0; }
120 virtual bool lockDevice(bool) { return true; }
121 virtual void unlockDevice() {}
122 virtual bool openDevice( bool silent )
124 if( !silent )
126 //QString msg = i18n( "Sorry, you do not have a supported portable music player." );
127 //Amarok::StatusBar::instance()->longMessage( msg, KDE::StatusBar::Sorry );
129 return false;
131 virtual bool closeDevice() { return false; }
132 virtual void synchronizeDevice() {}
133 virtual MediaItem* copyTrackToDevice(const MetaBundle&) { return 0; }
134 virtual int deleteItemFromDevice(MediaItem*, int) { return -1; }
138 MediaBrowser::MediaBrowser( const char * /*name*/ )
139 : KVBox( 0)
140 , m_timer( new QTimer( this ) )
141 , m_currentDevice( 0 )
142 , m_waitForTranscode( false )
143 , m_quitting( false )
144 , m_connectAction( 0 )
145 , m_disconnectAction( 0 )
146 , m_customAction( 0 )
147 , m_configAction( 0 )
148 , m_transferAction( 0 )
150 s_instance = this;
152 m_timer->setSingleShot( true );
154 m_toolbar = new Browser::ToolBar( this );
155 m_toolbar->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
156 m_toolbar->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
158 //TODO: how to fix getButton
159 // perhaps the action can be referenced instead?
160 m_connectAction = new KAction(KIcon("connect_creating"), i18n("Connect"), this);
161 connect(m_connectAction, SIGNAL(triggered()), this, SLOT(connectClicked()));
162 m_toolbar->addAction(m_connectAction);
163 // m_toolbar->insertButton( "connect_creating", CONNECT, true, i18n("Connect") );
164 // m_toolbar->getButton(CONNECT)->setToolTip( i18n( "Connect media device" ) );
166 m_disconnectAction = new KAction(KIcon("media-eject"), i18n("Disconnect"), this);
167 connect(m_disconnectAction, SIGNAL(triggered()), this, SLOT(disconnectClicked()));
168 m_toolbar->addAction(m_disconnectAction);
169 // m_toolbar->insertButton( "media-eject", DISCONNECT, true, i18n("Disconnect") );
170 // m_toolbar->getButton(DISCONNECT)->setToolTip( i18n( "Disconnect media device" ) );
172 m_transferAction = new KAction(KIcon("rebuild"), i18n("Transfer"), this);
173 connect(m_transferAction, SIGNAL(triggered()), this, SLOT(transferClicked()));
174 m_toolbar->addAction(m_transferAction);
175 // m_toolbar->insertButton( "rebuild", TRANSFER, true, i18n("Transfer") );
176 // m_toolbar->getButton(TRANSFER)->setToolTip( i18n( "Transfer tracks to media device" ) );
178 m_toolbar->addSeparator();
180 // m_toolbar->setIconText( KToolBar::IconTextRight, true );
181 m_customAction = new KAction(KIcon( "add_playlist" ), i18n("custom"), this);
182 connect(m_customAction, SIGNAL(triggered()), this, SLOT(customClicked()));
183 m_customAction->setText( i18n("Special Device Functions") );
184 m_customAction->setToolTip( i18n("Device-specific special functions or information") );
185 m_toolbar->addAction(m_customAction);
186 // m_toolbar->insertButton( Amarok::icon( "add_playlist" ), CUSTOM, SIGNAL( clicked() ), this, SLOT( customClicked() ), true, "custom" );
187 // m_toolbar->getButton(TRANSFER)->setToolTip( i18n( "Transfer tracks to media device" ) );
189 m_toolbar->setToolButtonStyle( Qt::ToolButtonIconOnly );
191 m_configAction = new KAction(KIcon("configure"), i18n("Configure"), this);
192 connect(m_configAction, SIGNAL(triggered()), this, SLOT(config()));
193 m_toolbar->addAction(m_configAction);
194 // m_toolbar->insertButton( Amarok::icon( "configure" ), CONFIGURE, true, i18n("Configure") );
195 // m_toolbar->getButton(CONFIGURE)->setToolTip( i18n( "Configure device" ) );
198 m_deviceCombo = new KComboBox( this );
200 // searching/filtering
201 QToolBar* searchToolBar = new Browser::ToolBar( this );
202 searchToolBar->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
203 m_searchWidget = new SearchWidget( searchToolBar, this );
204 searchToolBar->addWidget( m_searchWidget );
205 connect( m_timer, SIGNAL( timeout() ), SLOT( slotSetFilter() ) );
207 // connect to device cache
208 connect( MediaDeviceCache::instance(), SIGNAL( deviceAdded(const QString&) ),
209 SLOT( deviceAdded(const QString&) ) );
210 connect( MediaDeviceCache::instance(), SIGNAL( deviceRemoved(const QString&) ),
211 SLOT( deviceRemoved(const QString&) ) );
214 // we always have a dummy device
215 m_pluginName[ i18n( "Disable" ) ] = "dummy-mediadevice";
216 m_pluginAmarokName["dummy-mediadevice"] = i18n( "Disable" );
217 m_pluginName[ i18n( "Do not handle" ) ] = "ignore";
218 m_pluginAmarokName["ignore"] = i18n( "Do not handle" );
219 // query available device plugins
220 m_plugins = PluginManager::query( "[X-KDE-Amarok-plugintype] == 'mediadevice'" );
221 for( KService::List::ConstIterator it = m_plugins.begin(); it != m_plugins.end(); ++it ) {
222 // Save name properties in QMap for lookup
223 m_pluginName[(*it)->name()] = (*it)->property( "X-KDE-Amarok-name" ).toString();
224 m_pluginAmarokName[(*it)->property( "X-KDE-Amarok-name" ).toString()] = (*it)->name();
227 m_views = new KVBox( this );
228 m_queue = new MediaQueue( this );
229 m_progressBox = new KHBox( this );
230 m_progress = new QProgressBar( m_progressBox );
231 m_cancelButton = new KPushButton( KIcon( Amarok::icon( "cancel" ) ), i18n("Cancel"), m_progressBox );
234 m_stats = new SpaceLabel(this);
236 m_progressBox->hide();
238 MediaDevice *dev = new DummyMediaDevice();
239 dev->init( this );
240 addDevice( dev );
241 activateDevice( 0, false );
242 updateDevices();
243 queue()->load( Amarok::saveLocation() + "transferlist.xml" );
244 queue()->computeSize();
246 setFocusProxy( m_queue );
248 updateStats();
250 MediaDeviceCache::instance()->refreshCache();
251 foreach( QString uid, MediaDeviceCache::instance()->getAll() )
252 deviceAdded( uid );
254 //TODO: Take generic storage devices into account too -- or do we rely on the
255 //Solid backend to tell us if it's a PMP with "storage" type?
257 connect( m_deviceCombo, SIGNAL( activated( int ) ), SLOT( activateDevice( int ) ) );
259 connect( m_cancelButton, SIGNAL( clicked() ), SLOT( cancelClicked() ) );
260 connect( pApp, SIGNAL( prepareToQuit() ), SLOT( prepareToQuit() ) );
261 connect( CollectionDB::instance(), SIGNAL( tagsChanged( const MetaBundle& ) ),
262 SLOT( tagsChanged( const MetaBundle& ) ) );
264 //TODO: If we will be supporting manually adding devices, probably need the following section
265 /*m_haveDevices = false;
266 QMap<QString,QString> savedDevices = Amarok::config( "MediaBrowser" ).entryMap();
267 for( QMap<QString,QString>::Iterator it = savedDevices.begin();
268 it != savedDevices.end();
269 ++it )
271 if( it.data() != "deleted" && it.data() != "ignore" )
273 m_haveDevices = true;
274 break;
280 bool
281 MediaBrowser::blockQuit() const
283 for( QList<MediaDevice *>::const_iterator it = m_devices.begin();
284 it != m_devices.end();
285 ++it )
287 if( *it && (*it)->isConnected() )
288 return true;
291 return false;
294 void
295 MediaBrowser::tagsChanged( const MetaBundle &bundle )
297 m_itemMapMutex.lock();
298 debug() << "tags changed for " << bundle.url().url();
299 ItemMap::iterator it = m_itemMap.find( bundle.url().url() );
300 if( it != m_itemMap.end() )
302 MediaItem *item = *it;
303 m_itemMapMutex.unlock();
304 if( item->device() )
306 item->device()->tagsChanged( item, bundle );
308 else
310 // it's an item on the transfer queue
311 item->setBundle( new MetaBundle( bundle ) );
313 QString text = item->bundle()->prettyTitle();
314 if( text.isEmpty() || (!item->bundle()->isValidMedia() && !item->bundle()->podcastBundle()) )
315 text = item->bundle()->url().prettyUrl();
316 if( !item->m_playlistName.isNull() )
318 text += " (" + item->m_playlistName + ')';
320 item->setText( 0, text);
323 else
325 m_itemMapMutex.unlock();
329 bool
330 MediaBrowser::getBundle( const KUrl &url, MetaBundle *bundle ) const
332 QMutexLocker locker( &m_itemMapMutex );
333 ItemMap::const_iterator it = m_itemMap.find( url.url() );
334 if( it == m_itemMap.end() )
335 return false;
337 if( bundle )
338 *bundle = *(*it)->bundle();
340 return true;
343 KUrl
344 MediaBrowser::getProxyUrl( const KUrl& daapUrl ) const
346 DEBUG_BLOCK
347 KUrl url;
348 MediaDevice* dc = findChildren<MediaDevice *>( "DaapClient" ).first();
349 if( dc )
350 url = dc->getProxyUrl( daapUrl );
351 return url;
354 MediaDevice *
355 MediaBrowser::deviceFromId( const QString &id ) const
357 for( QList<MediaDevice *>::const_iterator it = m_devices.constBegin();
358 it != m_devices.constEnd();
359 it++ )
361 if( (*it)->uid() == id )
362 return (*it);
365 return NULL;
368 void
369 MediaBrowser::activateDevice( const MediaDevice *dev )
371 int index = 0;
372 for( QList<MediaDevice *>::iterator it = m_devices.begin();
373 it != m_devices.end();
374 it++ )
376 if( *it == dev )
378 activateDevice( index );
379 break;
381 index++;
385 void
386 MediaBrowser::activateDevice( int index, bool skipDummy )
388 if( m_currentDevice && m_currentDevice->customAction() )
390 m_toolbar->removeAction( m_currentDevice->customAction() );
391 m_toolbar->hide();
392 m_toolbar->show();
395 foreach( MediaDevice *md, m_devices )
397 if( md && md->view() )
398 md->view()->hide();
401 if( index < 0 )
403 m_currentDevice = m_devices.last();
404 return;
407 if( skipDummy )
408 index++;
410 if( index >= m_devices.count() )
412 if( !m_devices.isEmpty() )
413 m_currentDevice = m_devices.last();
414 else
415 m_currentDevice = 0;
416 updateButtons();
417 queue()->computeSize();
418 updateStats();
419 return;
422 m_currentDevice = m_devices[index];
423 if( m_currentDevice && m_currentDevice->view() )
425 m_currentDevice->view()->show();
426 if( m_currentDevice->customAction() )
428 m_toolbar->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
429 m_toolbar->addAction( m_currentDevice->customAction() );
430 m_toolbar->hide();
431 m_toolbar->show();
434 m_deviceCombo->setCurrentIndex( index-1 );
436 updateButtons();
437 queue()->computeSize();
438 updateStats();
441 void
442 MediaBrowser::addDevice( MediaDevice *device )
444 m_devices.append( device );
446 device->loadConfig();
448 if( device->autoConnect() )
450 device->connectDevice( true );
451 updateButtons();
454 updateDevices();
457 void
458 MediaBrowser::removeDevice( MediaDevice *device )
460 DEBUG_BLOCK
462 debug() << "remove device: type=" << device->deviceType();
464 for( QList<MediaDevice *>::iterator it = m_devices.begin();
465 it != m_devices.end();
466 it++ )
468 if( *it == device )
470 bool current = ( (*it)->uid() == m_currentDevice->uid() );
471 m_devices.erase( it );
472 if( current )
473 activateDevice( 0, false );
474 break;
478 if( device->isConnected() )
480 if( device->disconnectDevice( false /* don't run post-disconnect command */ ) )
481 unloadDevicePlugin( device );
482 else
484 debug() << "Cannot remove device because disconnect failed";
485 Amarok::StatusBar::instance()->longMessage(
486 i18n( "Cannot remove device because disconnect failed" ),
487 KDE::StatusBar::Warning );
490 else
491 unloadDevicePlugin( device );
493 updateDevices();
496 void
497 MediaBrowser::updateDevices()
499 m_deviceCombo->clear();
500 uint i = 0;
501 for( QList<MediaDevice *>::iterator it = m_devices.begin();
502 it != m_devices.end();
503 it++ )
505 if( m_devices.count() > 1 && dynamic_cast<DummyMediaDevice *>(*it) )
506 continue;
507 QString name = (*it)->name();
508 if( !(*it)->deviceNode().isEmpty() )
510 name = i18n( "%1 at %2", name, (*it)->deviceNode() );
512 if( (*it)->hasMountPoint() && !(*it)->mountPoint().isEmpty() )
514 name += i18n( " (mounted at %1)", (*it)->mountPoint() );
516 m_deviceCombo->addItem( name, i );
517 if( !m_currentDevice || (*it)->uid() == m_currentDevice->uid() )
519 m_deviceCombo->setCurrentItem( name );
521 i++;
523 m_deviceCombo->setEnabled( m_devices.count() > 1 );
524 m_haveDevices = m_devices.count() > 1;
527 QStringList
528 MediaBrowser::deviceNames() const
530 QStringList list;
532 for( QList<MediaDevice *>::const_iterator it = m_devices.constBegin();
533 it != m_devices.constEnd();
534 it++ )
536 QString name = (*it)->name();
537 list << name;
540 return list;
543 bool
544 MediaBrowser::deviceSwitch( const QString &name )
546 int index = 0;
547 for( QList<MediaDevice *>::iterator it = m_devices.begin();
548 it != m_devices.end();
549 it++ )
551 if( (*it)->name() == name )
553 activateDevice( index, false );
554 return true;
556 index++;
559 return false;
562 void
563 MediaBrowser::transcodingFinished( const QString &src, const QString &dst )
565 KUrl srcJob = KUrl( m_transcodeSrc );
566 KUrl srcResult = KUrl( src );
568 if( srcJob.path() == srcResult.path() )
570 m_transcodedUrl = KUrl( dst );
571 m_waitForTranscode = false;
573 else
575 debug() << "transcoding for " << src << " finished, "
576 << "but we are waiting for " << m_transcodeSrc << " -- aborting";
577 m_waitForTranscode = false;
581 KUrl
582 MediaBrowser::transcode( const KUrl &src, const QString &filetype )
584 const ScriptManager* const sm = ScriptManager::instance();
586 if( sm->transcodeScriptRunning().isEmpty() )
588 debug() << "cannot transcode with no transcoder registered";
589 return KUrl();
592 m_waitForTranscode = true;
593 m_transcodeSrc = src.url();
594 m_transcodedUrl = KUrl();
595 ScriptManager::instance()->notifyTranscode( src.url(), filetype );
597 while( m_waitForTranscode && !sm->transcodeScriptRunning().isEmpty() )
599 usleep( 10000 );
600 kapp->processEvents( QEventLoop::AllEvents );
603 return m_transcodedUrl;
607 void
608 MediaBrowser::slotSetFilterTimeout() //SLOT
610 m_timer->start( 280 ); //stops the timer for us first
613 void
614 MediaBrowser::slotSetFilter() //SLOT
616 m_timer->stop();
618 if( m_currentDevice )
619 m_currentDevice->view()->setFilter( m_searchWidget->lineEdit()->text() );
622 void
623 MediaBrowser::slotSetFilter( const QString &text )
625 m_searchWidget->lineEdit()->setText( text );
626 slotSetFilter();
629 void
630 MediaBrowser::slotEditFilter()
632 EditFilterDialog *fd = new EditFilterDialog( this, true, m_searchWidget->lineEdit()->text() );
633 connect( fd, SIGNAL(filterChanged(const QString &)), SLOT(slotSetFilter(const QString &)) );
634 if( fd->exec() )
635 m_searchWidget->lineEdit()->setText( fd->filter() );
636 delete fd;
639 void
640 MediaBrowser::prepareToQuit()
642 m_waitForTranscode = false;
643 m_quitting = true;
644 for( QList<MediaDevice *>::iterator it = m_devices.begin();
645 it != m_devices.end();
646 ++it )
648 if( (*it)->isConnected() )
649 (*it)->disconnectDevice( false /* don't unmount */ );
653 MediaBrowser::~MediaBrowser()
655 debug() << "having to remove " << m_devices.count() << " devices";
656 while( !m_devices.isEmpty() )
658 removeDevice( m_devices.last() );
661 queue()->save( Amarok::saveLocation() + "transferlist.xml" );
663 delete m_deviceCombo;
664 delete m_queue;
667 MediaView::MediaView( QWidget* parent, MediaDevice *device )
668 : K3ListView( parent )
669 , m_parent( parent )
670 , m_device( device )
672 hide();
673 setSelectionMode( Q3ListView::Extended );
674 setItemsMovable( false );
675 setShowSortIndicator( true );
676 setFullWidth( true );
677 setRootIsDecorated( true );
678 setDragEnabled( true );
679 setDropVisualizer( true ); //the visualizer (a line marker) is drawn when dragging over tracks
680 setDropHighlighter( true ); //and the highligther (a focus rect) is drawn when dragging over playlists
681 setDropVisualizerWidth( 3 );
682 setAcceptDrops( true );
684 header()->hide();
685 addColumn( i18n( "Remote Media" ) );
687 KActionCollection* ac = new KActionCollection( this );
688 KStandardAction::selectAll( this, SLOT( selectAll() ), ac );
690 connect( this, SIGNAL( contextMenuRequested( Q3ListViewItem*, const QPoint&, int ) ),
691 this, SLOT( rmbPressed( Q3ListViewItem*, const QPoint&, int ) ) );
693 connect( this, SIGNAL( itemRenamed( Q3ListViewItem* ) ),
694 this, SLOT( renameItem( Q3ListViewItem* ) ) );
696 connect( this, SIGNAL( expanded( Q3ListViewItem* ) ),
697 this, SLOT( slotExpand( Q3ListViewItem* ) ) );
699 connect( this, SIGNAL( returnPressed( Q3ListViewItem* ) ),
700 this, SLOT( invokeItem( Q3ListViewItem* ) ) );
702 connect( this, SIGNAL( doubleClicked( Q3ListViewItem*, const QPoint&, int ) ),
703 this, SLOT( invokeItem( Q3ListViewItem*, const QPoint &, int ) ) );
706 void
707 MediaView::keyPressEvent( QKeyEvent *e )
709 if( e->key() == Qt::Key_Delete )
710 m_device->deleteFromDevice();
711 else
712 K3ListView::keyPressEvent( e );
715 void
716 MediaView::invokeItem( Q3ListViewItem* i, const QPoint& point, int column ) //SLOT
718 if( column == -1 )
719 return;
721 QPoint p = mapFromGlobal( point );
722 if ( p.x() > header()->sectionPos( header()->mapToIndex( 0 ) ) + treeStepSize() * ( i->depth() + ( rootIsDecorated() ? 1 : 0) ) + itemMargin()
723 || p.x() < header()->sectionPos( header()->mapToIndex( 0 ) ) )
724 invokeItem( i );
728 void
729 MediaView::invokeItem( Q3ListViewItem *i )
731 MediaItem *item = static_cast<MediaItem *>( i );
732 if( !item )
733 return;
735 KUrl::List urls = nodeBuildDragList( item );
736 The::playlistModel()->insertMedia( urls, Playlist::AppendAndPlay );
739 void
740 MediaView::renameItem( Q3ListViewItem *item )
742 m_device->renameItem( item );
745 void
746 MediaView::slotExpand( Q3ListViewItem *item )
748 m_device->expandItem( item );
752 MediaView::~MediaView()
757 Q3DragObject *
758 MediaView::dragObject()
760 KUrl::List urls = nodeBuildDragList( 0 );
761 K3MultipleDrag *md = new K3MultipleDrag( viewport() );
762 md->addDragObject( K3ListView::dragObject() );
763 K3URLDrag* ud = new K3URLDrag( urls, viewport() );
764 md->addDragObject( ud );
765 md->setPixmap( CollectionDB::createDragPixmap( urls ),
766 QPoint( CollectionDB::DRAGPIXMAP_OFFSET_X, CollectionDB::DRAGPIXMAP_OFFSET_Y ) );
767 return md;
771 KUrl::List
772 MediaView::nodeBuildDragList( MediaItem* item, int flags )
774 KUrl::List items;
775 MediaItem* fi;
777 if ( !item )
779 fi = static_cast<MediaItem*>(firstChild());
781 else
782 fi = item;
784 while ( fi )
786 if( fi->isVisible() )
788 if ( fi->isSelected() || !(flags & OnlySelected ) )
790 if( fi->isLeafItem() || fi->type() == MediaItem::DIRECTORY )
791 items += fi->url();
792 else
794 if(fi->childCount() )
795 items += nodeBuildDragList( static_cast<MediaItem*>(fi->firstChild()), None );
798 else
800 if ( fi->childCount() )
801 items += nodeBuildDragList( static_cast<MediaItem*>(fi->firstChild()), OnlySelected );
804 fi = static_cast<MediaItem*>(fi->nextSibling());
806 return items;
810 MediaView::getSelectedLeaves( MediaItem *parent, QList<MediaItem*> *list, int flags )
812 int numFiles = 0;
813 if( !list )
814 list = new QList<MediaItem*>;
816 MediaItem *it;
817 if( !parent )
818 it = static_cast<MediaItem *>(firstChild());
819 else
820 it = static_cast<MediaItem *>(parent->firstChild());
822 for( ; it; it = static_cast<MediaItem*>(it->nextSibling()))
824 if( it->isVisible() )
826 if( it->childCount() && !( it->type() == MediaItem::DIRECTORY && it->isSelected() ) )
828 int f = flags;
829 if( it->isSelected() )
830 f &= ~OnlySelected;
831 numFiles += getSelectedLeaves(it, list, f );
833 if( it->isSelected() || !(flags & OnlySelected) )
835 if( it->type() == MediaItem::TRACK ||
836 it->type() == MediaItem::DIRECTORY ||
837 it->type() == MediaItem::PODCASTITEM ||
838 it->type() == MediaItem::PLAYLISTITEM||
839 it->type() == MediaItem::INVISIBLE ||
840 it->type() == MediaItem::ORPHANED )
842 if( flags & OnlyPlayed )
844 if( it->played() > 0 )
845 numFiles++;
847 else
848 numFiles++;
850 if( ( it->isLeafItem() && (!(flags & OnlyPlayed) || it->played()>0) )
851 || it->type() == MediaItem::DIRECTORY )
852 list->append( it );
856 return numFiles;
860 bool
861 MediaView::acceptDrag( QDropEvent *e ) const
863 if( e->source() == MediaBrowser::queue()->viewport() )
864 return false;
866 return e->source() == viewport()
867 || e->mimeData()->hasFormat( "amarok-sql" )
868 || KUrl::List::canDecode( e->mimeData() );
871 void
872 MediaView::contentsDropEvent( QDropEvent *e )
874 cleanDropVisualizer();
875 cleanItemHighlighter();
877 if(e->source() == viewport())
879 const QPoint p = contentsToViewport( e->pos() );
880 MediaItem *item = static_cast<MediaItem *>(itemAt( p ));
882 if( !item && MediaBrowser::instance()->currentDevice()->m_type != "generic-mediadevice" )
883 return;
885 QList<MediaItem*> items;
887 if( !item || item->type() == MediaItem::DIRECTORY ||
888 item->type() == MediaItem::TRACK )
890 QList<MediaItem*> items;
891 getSelectedLeaves( 0, &items );
892 m_device->addToDirectory( item, items );
894 else if( item->type() == MediaItem::PLAYLIST )
896 MediaItem *list = item;
897 MediaItem *after = 0;
898 for(MediaItem *it = static_cast<MediaItem *>(item->firstChild());
900 it = static_cast<MediaItem *>(it->nextSibling()))
901 after = it;
903 getSelectedLeaves( 0, &items );
904 m_device->addToPlaylist( list, after, items );
906 else if( item->type() == MediaItem::PLAYLISTITEM )
908 MediaItem *list = static_cast<MediaItem *>(item->parent());
909 MediaItem *after = 0;
910 for(MediaItem *it = static_cast<MediaItem*>(item->parent()->firstChild());
912 it = static_cast<MediaItem *>(it->nextSibling()))
914 if(it == item)
915 break;
916 after = it;
919 getSelectedLeaves( 0, &items );
920 m_device->addToPlaylist( list, after, items );
922 else if( item->type() == MediaItem::PLAYLISTSROOT )
924 QList<MediaItem*> items;
925 getSelectedLeaves( 0, &items );
926 QString base( i18n("New Playlist") );
927 QString name = base;
928 int i=1;
929 while( item->findItem(name) )
931 QString num;
932 num.setNum(i);
933 name = base + ' ' + num;
934 i++;
936 MediaItem *pl = m_device->newPlaylist(name, item, items);
937 ensureItemVisible(pl);
938 rename(pl, 0);
941 else
943 if( e->mimeData()->hasFormat( "amarok-sql" ) )
945 QString data( e->mimeData()->data( "amarok-sql" ) );
946 QString playlist = data.section( "\n", 0, 0 );
947 QString query = data.section( "\n", 1 );
948 QStringList values = CollectionDB::instance()->query( query );
949 KUrl::List list = CollectionDB::instance()->URLsFromSqlDrag( values );
950 MediaBrowser::queue()->addUrls( list, playlist );
952 else if ( KUrl::List::canDecode( e->mimeData() ) )
954 KUrl::List list = KUrl::List::fromMimeData( e->mimeData() );
955 MediaBrowser::queue()->addUrls( list );
960 void
961 MediaView::viewportPaintEvent( QPaintEvent *e )
963 K3ListView::viewportPaintEvent( e );
965 // Superimpose bubble help:
967 if ( !MediaBrowser::instance()->currentDevice() || !MediaBrowser::instance()->currentDevice()->isConnected() )
969 QPainter p( viewport() );
971 Q3SimpleRichText t( i18n(
972 "<div align=center>"
973 "<h3>Media Device Browser</h3>"
974 "Configure your media device and then "
975 "click the Connect button to access your media device. "
976 "Drag and drop files to enqueue them for transfer."
977 "</div>" ), QApplication::font() );
979 t.setWidth( width() - 50 );
981 const uint w = t.width() + 20;
982 const uint h = t.height() + 20;
984 p.setBrush( palette().background() );
985 p.drawRoundRect( 15, 15, w, h, (8*200)/w, (8*200)/h );
986 t.draw( &p, 20, 20, QRect(), palette() );
988 MediaBrowser::instance()->updateButtons();
991 void
992 MediaView::rmbPressed( Q3ListViewItem *item, const QPoint &p, int i )
994 if( m_device->isConnected() )
995 m_device->rmbPressed( item, p, i );
998 MediaItem *
999 MediaView::newDirectory( MediaItem *parent )
1001 bool ok;
1002 const QString name = KInputDialog::getText(i18n("Add Directory"), i18n("Directory Name:"), QString(), &ok, this);
1004 if( ok && !name.isEmpty() )
1006 m_device->newDirectory( name, parent );
1009 return 0;
1012 void
1013 MediaBrowser::deviceAdded( const QString &uid )
1015 DEBUG_BLOCK
1016 debug() << "deviceAdded called with a uid of: " << uid;
1017 MediaDevice *md = loadDevicePlugin( uid );
1018 if( md )
1020 addDevice( md );
1021 if( m_currentDevice == *(m_devices.begin()) || m_currentDevice == *(m_devices.end()) )
1022 activateDevice( m_devices.count()-1, false );
1026 void
1027 MediaBrowser::deviceRemoved( const QString &uid )
1029 for( QList<MediaDevice *>::iterator it = m_devices.begin();
1030 it != m_devices.end();
1031 it++ )
1033 if( (*it)->m_uid == uid )
1035 if( (*it)->isConnected() )
1037 if( (*it)->disconnectDevice() )
1038 removeDevice( *it );
1039 Amarok::StatusBar::instance()->longMessage(
1040 i18n( "The device %1 was removed before it was disconnected. "
1041 "In order to avoid possible data loss, press the \"Disconnect\" "
1042 "button before disconnecting the device.", (*it)->name() ),
1043 KDE::StatusBar::Warning );
1045 else
1046 removeDevice( *it );
1047 break;
1052 MediaDevice *
1053 MediaBrowser::loadDevicePlugin( const QString &uid )
1055 DEBUG_BLOCK
1057 QString name;
1058 QString mountPoint;
1059 QString protocol;
1061 if( MediaDeviceCache::instance()->deviceType( uid ) == MediaDeviceCache::SolidType )
1063 debug() << "uid " << uid << " detected as a Solid device";
1064 Solid::Device solidDevice( uid );
1066 Solid::PortableMediaPlayer* pmp = solidDevice.as<Solid::PortableMediaPlayer>();
1068 //TODO: Generic storage? need to set mount point if so...
1069 if( !pmp )
1071 debug() << "Failed to convert Solid device to PortableMediaPlayer";
1072 return 0;
1074 if( pmp->supportedProtocols().size() == 0 )
1076 debug() << "Portable Media Player " << uid << " does not support any protocols";
1077 return 0;
1080 foreach( QString supported, pmp->supportedProtocols() )
1081 debug() << "Device supports protocol " << supported;
1083 protocol = pmp->supportedProtocols()[0];
1084 if( protocol == "storage" )
1085 protocol = "generic";
1086 if( protocol == "pde" )
1087 protocol == "njb";
1089 name = solidDevice.vendor() + " - " + solidDevice.product();
1091 else if( MediaDeviceCache::instance()->deviceType( uid ) == MediaDeviceCache::ManualType )
1093 QString handler = Amarok::config( "PortableDevices" ).readEntry( uid, QString() );
1094 if( handler.isEmpty() || !handler.startsWith( "manual" ) )
1096 //this shouldn't happen, as manually adding the device should create the necessary entry
1097 debug() << "Something's wrong, trying to manually load a non-existant or non-manual manual device";
1098 return 0;
1100 QStringList sl = handler.split( "|" );
1101 if( sl[0] != "manual" )
1102 return 0;
1103 protocol = sl[1];
1104 name = sl[2];
1105 mountPoint = sl[3];
1108 protocol += "-mediadevice";
1109 QString query = "[X-KDE-Amarok-plugintype] == 'mediadevice' and [X-KDE-Amarok-name] == '%1'";
1110 Amarok::Plugin *plugin = PluginManager::createFromQuery( query.arg( protocol ) );
1112 if( plugin )
1114 debug() << "Returning plugin!";
1115 MediaDevice *device = static_cast<MediaDevice *>( plugin );
1116 device->init( this );
1117 device->m_uid = uid;
1118 device->m_name = name;
1119 device->m_type = protocol;
1120 device->m_mountPoint = mountPoint;
1121 return device;
1124 debug() << "no plugin for " << protocol;
1125 return 0;
1128 void
1129 MediaBrowser::pluginSelected( const QString &uid, const QString &plugin )
1131 DEBUG_BLOCK
1132 if( !plugin.isEmpty() )
1134 debug() << "Device uid is " << uid << " and plugin selected is: " << plugin;
1135 Amarok::config( "PortableDevices" ).writeEntry( uid, plugin );
1137 bool success = true;
1138 foreach( MediaDevice* device, m_devices )
1140 if( device->uid() == uid )
1142 debug() << "removing matching device";
1143 if( device->isConnected() )
1145 if( device->disconnectDevice( false ) )
1146 removeDevice( device );
1147 else
1148 success = false;
1150 else
1151 removeDevice( device );
1152 break;
1156 if( success )
1158 deviceAdded( uid );
1160 else
1162 debug() << "Cannot change plugin while operation is in progress" << endl;
1163 Amarok::StatusBar::instance()->longMessage(
1164 i18n( "Cannot change plugin while operation is in progress" ),
1165 KDE::StatusBar::Warning );
1168 else
1169 debug() << "Device uid is " << uid << " and you opted not to use a plugin";
1172 void
1173 MediaBrowser::showPluginManager()
1175 MediaDevicePluginManagerDialog* mpm = new MediaDevicePluginManagerDialog();
1176 mpm->exec();
1177 delete mpm;
1181 MediaBrowser::configSelectPlugin( int index )
1183 Q_UNUSED( index );
1185 if( m_currentDevice == m_devices.begin() )
1187 AmarokConfig::setDeviceType( m_pluginName[m_configPluginCombo->currentText()] );
1189 else if( currentDevice() )
1191 KConfig *config = Amarok::config( "MediaBrowser" );
1192 config->writeEntry( currentDevice()->uniqueId(), m_pluginName[m_configPluginCombo->currentText()] );
1195 if( !currentDevice() )
1196 activateDevice( 0, false );
1198 if( !currentDevice() )
1199 return;
1201 if( m_pluginName[m_configPluginCombo->currentText()] != currentDevice()->deviceType() )
1203 MediaDevice *dev = currentDevice();
1204 dev->removeConfigElements( m_configBox );
1205 if( dev->isConnected() )
1207 dev->disconnectDevice( false );
1209 unloadDevicePlugin( dev );
1210 *m_currentDevice = loadDevicePlugin( AmarokConfig::deviceType() );
1211 if( !*m_currentDevice )
1213 *m_currentDevice = new DummyMediaDevice();
1214 if( AmarokConfig::deviceType() != "dummy-mediadevice" )
1216 QString msg = i18n( "The requested media device could not be loaded" );
1217 Amarok::StatusBar::instance()->shortMessage( msg );
1220 dev = currentDevice();
1221 dev->init( this );
1222 dev->loadConfig();
1224 m_configBox->hide();
1225 dev->addConfigElements( m_configBox );
1226 m_configBox->show();
1228 dev->view()->show();
1230 if( dev->autoConnect() )
1232 dev->connectDevice( true );
1233 updateButtons();
1236 updateDevices();
1240 void
1241 MediaBrowser::unloadDevicePlugin( MediaDevice *device )
1243 DEBUG_BLOCK
1245 if( !device )
1246 return;
1248 disconnect( device ); // disconnect all signals
1250 if( dynamic_cast<DummyMediaDevice *>(device) )
1252 delete device;
1254 else
1256 PluginManager::unload( device );
1260 bool
1261 MediaBrowser::config()
1263 if( m_deviceCombo->currentText() == "No Device Selected" )
1265 //Amarok::StatusBar::instance()->longMessage( i18n( "No device selected to configure." ),
1266 // KDE::StatusBar::Sorry );
1267 showPluginManager();
1268 return true;
1271 DeviceConfigureDialog* dcd = new DeviceConfigureDialog( m_currentDevice );
1272 dcd->exec();
1273 bool successful = dcd->successful();
1274 delete dcd;
1275 return successful;
1278 void
1279 MediaBrowser::updateButtons()
1281 if( !connectAction() || !disconnectAction() || !transferAction() )
1282 return;
1284 if( m_currentDevice )
1286 transferAction()->setVisible( m_currentDevice->m_transfer );
1287 customAction()->setVisible( m_currentDevice->m_customButton );
1288 configAction()->setVisible( m_currentDevice->m_configure );
1290 connectAction()->setEnabled( !m_currentDevice->isConnected() );
1291 disconnectAction()->setEnabled( m_currentDevice->isConnected() );
1292 transferAction()->setEnabled( m_currentDevice->isConnected() && m_queue->childCount() > 0 );
1293 if( customAction() )
1294 customAction()->setEnabled( true );
1296 else
1298 connectAction()->setEnabled( false );
1299 disconnectAction()->setEnabled( false );
1300 transferAction()->setEnabled( false );
1301 if( customAction() )
1302 customAction()->setEnabled( false );
1306 void
1307 MediaBrowser::updateStats()
1309 if( !m_stats )
1310 return;
1312 KIO::filesize_t queued = m_queue->totalSize();
1314 QString text = i18np( " 1 track in queue", " %1 tracks in queue", m_queue->childCount() );
1315 if(m_queue->childCount() > 0)
1317 text += i18n(" (%1)", KIO::convertSize( queued ) );
1320 KIO::filesize_t total, avail;
1321 if( m_currentDevice && m_currentDevice->getCapacity(&total, &avail) )
1323 text += i18n( " - %1 of %2 available", KIO::convertSize( avail ), KIO::convertSize( total ) );
1325 m_stats->m_used = total-avail;
1326 m_stats->m_total = total;
1327 m_stats->m_scheduled = queued;
1329 else
1331 m_stats->m_used = 0;
1332 m_stats->m_total = 0;
1333 m_stats->m_scheduled = queued;
1336 m_stats->setText(text);
1337 m_stats->setToolTip( text );
1341 bool
1342 MediaView::setFilter( const QString &filter, MediaItem *parent )
1344 bool advanced = ExpressionParser::isAdvancedExpression( filter );
1345 QList<int> defaultColumns;
1346 defaultColumns << MetaBundle::Album;
1347 defaultColumns << MetaBundle::Title;
1348 defaultColumns << MetaBundle::Artist;
1350 bool root = false;
1351 MediaItem *it;
1352 if( !parent )
1354 root = true;
1355 it = static_cast<MediaItem *>(firstChild());
1357 else
1359 it = static_cast<MediaItem *>(parent->firstChild());
1362 bool childrenVisible = false;
1363 for( ; it; it = static_cast<MediaItem *>(it->nextSibling()))
1365 bool visible = true;
1366 if(it->isLeafItem())
1368 if( advanced )
1370 ParsedExpression parsed = ExpressionParser::parse( filter );
1371 visible = it->bundle() && it->bundle()->matchesParsedExpression( parsed, defaultColumns );
1373 else
1375 visible = it->bundle() && it->bundle()->matchesSimpleExpression( filter, defaultColumns );
1378 else
1380 visible = setFilter(filter, it);
1381 if(it->type()==MediaItem::PLAYLISTSROOT || it->type()==MediaItem::PLAYLIST)
1383 visible = true;
1385 else if(it->type()==MediaItem::DIRECTORY)
1387 bool match = true;
1388 QStringList list = filter.split( " ", QString::SkipEmptyParts );
1389 for( QStringList::iterator i = list.begin();
1390 i != list.end();
1391 ++i )
1393 if( !(*it).text(0).contains( *i ) )
1395 match = false;
1396 break;
1399 if( match )
1400 visible = true;
1403 if( filter.isEmpty() )
1404 visible = true;
1405 it->setVisible( visible );
1406 if(visible)
1407 childrenVisible = true;
1410 if( root && m_device )
1411 m_device->updateRootItems();
1413 return childrenVisible;
1416 void
1417 MediaQueue::syncPlaylist( const QString &name, const QString &query, bool loading )
1419 MediaItem* item = new MediaItem( this, lastItem() );
1420 item->setType( MediaItem::PLAYLIST );
1421 item->setExpandable( false );
1422 item->setData( query );
1423 item->m_playlistName = name;
1424 item->setText( 0, name );
1425 item->m_flags |= MediaItem::SmartPlaylist;
1426 m_parent->m_progress->setRange( 0, m_parent->m_progress->maximum() + 1 );
1427 itemCountChanged();
1428 if( !loading )
1429 URLsAdded();
1432 void
1433 MediaQueue::syncPlaylist( const QString &name, const KUrl &url, bool loading )
1435 MediaItem* item = new MediaItem( this, lastItem() );
1436 item->setType( MediaItem::PLAYLIST );
1437 item->setExpandable( false );
1438 item->setData( url.url() );
1439 item->m_playlistName = name;
1440 item->setText( 0, name );
1441 m_parent->m_progress->setRange( 0, m_parent->m_progress->maximum() + 1 );
1442 itemCountChanged();
1443 if( !loading )
1444 URLsAdded();
1447 void
1448 MediaQueue::addUrl( const KUrl& url2, MetaBundle *bundle, const QString &playlistName )
1450 KUrl url = Amarok::mostLocalURL( url2 );
1452 //Port 2.0
1453 // if( PlaylistFile::isPlaylistFile( url ) )
1454 // {
1455 // QString name = url.path().section( "/", -1 ).section( ".", 0, -2 ).replace( "_", " " );
1456 // PlaylistFile playlist( url.path() );
1458 // if( playlist.isError() )
1459 // {
1460 // Amarok::StatusBar::instance()->longMessage( i18n( "Failed to load playlist: %1", url.path() ),
1461 // KDE::StatusBar::Sorry );
1462 // return;
1463 // }
1465 // for( BundleList::iterator it = playlist.bundles().begin();
1466 // it != playlist.bundles().end();
1467 // ++it )
1468 // {
1469 // addUrl( (*it).url(), 0, name );
1470 // }
1471 // return;
1472 // }
1473 if( url.protocol() == "file" && QFileInfo( url.path() ).isDir() )
1475 //TODO: PORT
1476 // KUrl::List urls = Amarok::recursiveUrlExpand( url );
1477 // oldForeachType( KUrl::List, urls )
1478 // addUrl( *it );
1479 // return;
1482 if( playlistName.isNull() )
1484 for( MediaItem *it = static_cast<MediaItem *>(firstChild());
1486 it = static_cast<MediaItem *>(it->nextSibling()) )
1488 if( it->url() == url )
1490 Amarok::StatusBar::instance()->shortMessage(
1491 i18n( "Track already queued for transfer: %1", url.url() ) );
1492 return;
1497 if(!bundle)
1498 bundle = new MetaBundle( url );
1500 MediaItem* item = new MediaItem( this, lastItem() );
1501 item->setExpandable( false );
1502 item->setDropEnabled( true );
1503 item->setBundle( bundle );
1504 if(bundle->podcastBundle() )
1506 item->setType( MediaItem::PODCASTITEM );
1508 item->m_playlistName = playlistName;
1510 QString text = item->bundle()->prettyTitle();
1511 if( text.isEmpty() || (!item->bundle()->isValidMedia() && !item->bundle()->podcastBundle()) )
1512 text = item->bundle()->url().prettyUrl();
1513 if( !item->m_playlistName.isNull() )
1515 text += " (" + item->m_playlistName + ')';
1517 item->setText( 0, text);
1519 m_parent->updateButtons();
1520 m_parent->m_progress->setRange( 0, m_parent->m_progress->maximum() + 1 );
1521 addItemToSize( item );
1522 itemCountChanged();
1525 void
1526 MediaQueue::addUrl( const KUrl &url, MediaItem *item )
1528 DEBUG_BLOCK
1529 MediaItem *newitem = new MediaItem( this, lastItem() );
1530 newitem->setExpandable( false );
1531 newitem->setDropEnabled( true );
1532 MetaBundle *bundle = new MetaBundle( *item->bundle() );
1533 KUrl filepath(url);
1534 filepath.addPath( bundle->filename() );
1535 bundle->setUrl( filepath );
1536 newitem->m_device = item->m_device;
1537 if(bundle->podcastBundle() )
1539 item->setType( MediaItem::PODCASTITEM );
1541 QString text = item->bundle()->prettyTitle();
1542 if( text.isEmpty() || (!item->bundle()->isValidMedia() && !item->bundle()->podcastBundle()) )
1543 text = item->bundle()->url().prettyUrl();
1544 if( !item->m_playlistName.isEmpty() )
1546 text += " (" + item->m_playlistName + ')';
1548 newitem->setText( 0, text);
1549 newitem->setBundle( bundle );
1550 m_parent->updateButtons();
1551 m_parent->m_progress->setRange( 0, m_parent->m_progress->maximum() + 1 );
1552 addItemToSize( item );
1553 itemCountChanged();
1557 void
1558 MediaQueue::addUrls( const KUrl::List urls, const QString &playlistName )
1560 KUrl::List::ConstIterator it = urls.begin();
1561 for ( ; it != urls.end(); ++it )
1562 addUrl( *it, 0, playlistName );
1564 URLsAdded();
1567 void
1568 MediaQueue::URLsAdded()
1570 m_parent->updateStats();
1571 m_parent->updateButtons();
1572 if( m_parent->currentDevice()
1573 && m_parent->currentDevice()->isConnected()
1574 && m_parent->currentDevice()->asynchronousTransfer()
1575 && !m_parent->currentDevice()->isTransferring() )
1576 m_parent->currentDevice()->transferFiles();
1578 save( Amarok::saveLocation() + "transferlist.xml" );
1581 Q3DragObject *
1582 MediaQueue::dragObject()
1584 KUrl::List urls;
1585 for( Q3ListViewItem *it = firstChild(); it; it = it->nextSibling() )
1587 if( it->isVisible() && it->isSelected() && static_cast<MediaItem *>(it) )
1588 urls += static_cast<MediaItem *>(it)->url();
1591 K3MultipleDrag *md = new K3MultipleDrag( viewport() );
1592 Q3DragObject *d = K3ListView::dragObject();
1593 K3URLDrag* urldrag = new K3URLDrag( urls, viewport() );
1594 md->addDragObject( d );
1595 md->addDragObject( urldrag );
1596 md->setPixmap( CollectionDB::createDragPixmap( urls ),
1597 QPoint( CollectionDB::DRAGPIXMAP_OFFSET_X, CollectionDB::DRAGPIXMAP_OFFSET_Y ) );
1598 return md;
1601 void
1602 MediaBrowser::cancelClicked()
1604 DEBUG_BLOCK
1606 m_waitForTranscode = false;
1607 if( m_currentDevice )
1608 m_currentDevice->abortTransfer();
1611 void
1612 MediaBrowser::transferClicked()
1614 transferAction()->setEnabled( false );
1615 if( m_currentDevice
1616 && m_currentDevice->isConnected()
1617 && !m_currentDevice->isTransferring() )
1619 if( !m_currentDevice->hasTransferDialog() )
1620 m_currentDevice->transferFiles();
1621 else
1623 m_currentDevice->runTransferDialog();
1624 //may not work with non-TransferDialog-class object, but maybe some run time introspection could solve it?
1625 if( m_currentDevice->getTransferDialog() &&
1626 ( reinterpret_cast<TransferDialog *>(m_currentDevice->getTransferDialog()))->isAccepted() )
1627 m_currentDevice->transferFiles();
1628 else
1629 updateButtons();
1632 m_currentDevice->m_transferDir = m_currentDevice->mountPoint();
1635 void
1636 MediaBrowser::connectClicked()
1638 bool haveToConfig = false;
1639 // it was just clicked, so isOn() == true.
1640 if( m_currentDevice && !m_currentDevice->isConnected() )
1642 haveToConfig = !m_currentDevice->connectDevice();
1645 haveToConfig |= !m_currentDevice;
1646 haveToConfig |= ( m_currentDevice && !m_currentDevice->isConnected() );
1648 if ( !m_currentDevice->needsManualConfig() )
1649 haveToConfig = false;
1651 if( haveToConfig && m_devices.at( 0 ) == m_currentDevice )
1653 if( config() && m_currentDevice && !m_currentDevice->isConnected() )
1654 m_currentDevice->connectDevice();
1657 updateDevices();
1658 updateButtons();
1659 updateStats();
1662 void
1663 MediaBrowser::disconnectClicked()
1665 if( m_currentDevice && m_currentDevice->isTransferring() )
1667 int action = KMessageBox::questionYesNoCancel( MediaBrowser::instance(),
1668 i18n( "Transfer in progress. Finish or stop after current track?" ),
1669 i18n( "Stop Transfer?" ),
1670 KGuiItem(i18n("&Finish"), "goto-page"),
1671 KGuiItem(i18n("&Stop"), "media-eject") );
1672 if( action == KMessageBox::Cancel )
1674 return;
1676 else if( action == KMessageBox::Yes )
1678 m_currentDevice->scheduleDisconnect();
1679 return;
1683 transferAction()->setEnabled( false );
1684 disconnectAction()->setEnabled( false );
1686 if( m_currentDevice )
1688 m_currentDevice->disconnectDevice( true );
1691 updateDevices();
1692 updateButtons();
1693 updateStats();
1696 void
1697 MediaBrowser::customClicked()
1699 if( m_currentDevice )
1700 m_currentDevice->customClicked();
1703 void
1704 MediaQueue::save( const QString &path )
1706 QFile file( path );
1708 if( !file.open( QIODevice::WriteOnly ) ) return;
1710 QDomDocument newdoc;
1711 QDomElement transferlist = newdoc.createElement( "playlist" );
1712 transferlist.setAttribute( "product", "Amarok" );
1713 transferlist.setAttribute( "version", APP_VERSION );
1714 newdoc.appendChild( transferlist );
1716 for( const MediaItem *item = static_cast<MediaItem *>( firstChild() );
1717 item;
1718 item = static_cast<MediaItem *>( item->nextSibling() ) )
1720 QDomElement i = newdoc.createElement("item");
1721 i.setAttribute("url", item->url().url());
1723 if( item->bundle() )
1725 QDomElement attr = newdoc.createElement( "Title" );
1726 QDomText t = newdoc.createTextNode( item->bundle()->title() );
1727 attr.appendChild( t );
1728 i.appendChild( attr );
1730 attr = newdoc.createElement( "Artist" );
1731 t = newdoc.createTextNode( item->bundle()->artist() );
1732 attr.appendChild( t );
1733 i.appendChild( attr );
1735 attr = newdoc.createElement( "Album" );
1736 t = newdoc.createTextNode( item->bundle()->album() );
1737 attr.appendChild( t );
1738 i.appendChild( attr );
1740 attr = newdoc.createElement( "Year" );
1741 t = newdoc.createTextNode( QString::number( item->bundle()->year() ) );
1742 attr.appendChild( t );
1743 i.appendChild( attr );
1745 attr = newdoc.createElement( "Comment" );
1746 t = newdoc.createTextNode( item->bundle()->comment() );
1747 attr.appendChild( t );
1748 i.appendChild( attr );
1750 attr = newdoc.createElement( "Genre" );
1751 t = newdoc.createTextNode( item->bundle()->genre() );
1752 attr.appendChild( t );
1753 i.appendChild( attr );
1755 attr = newdoc.createElement( "Track" );
1756 t = newdoc.createTextNode( QString::number( item->bundle()->track() ) );
1757 attr.appendChild( t );
1758 i.appendChild( attr );
1761 if(item->type() == MediaItem::PODCASTITEM)
1763 i.setAttribute( "podcast", "1" );
1766 if(item->type() == MediaItem::PODCASTITEM
1767 && item->bundle()->podcastBundle())
1769 PodcastEpisodeBundle *peb = item->bundle()->podcastBundle();
1770 QDomElement attr = newdoc.createElement( "PodcastDescription" );
1771 QDomText t = newdoc.createTextNode( peb->description() );
1772 attr.appendChild( t );
1773 i.appendChild( attr );
1775 attr = newdoc.createElement( "PodcastAuthor" );
1776 t = newdoc.createTextNode( peb->author() );
1777 attr.appendChild( t );
1778 i.appendChild( attr );
1780 attr = newdoc.createElement( "PodcastRSS" );
1781 t = newdoc.createTextNode( peb->parent().url() );
1782 attr.appendChild( t );
1783 i.appendChild( attr );
1785 attr = newdoc.createElement( "PodcastURL" );
1786 t = newdoc.createTextNode( peb->url().url() );
1787 attr.appendChild( t );
1788 i.appendChild( attr );
1791 if( !item->m_playlistName.isEmpty() )
1793 i.setAttribute( "playlist", item->m_playlistName );
1796 if(item->type() == MediaItem::PLAYLIST)
1798 i.setAttribute( "playlistdata", item->data() );
1799 if( item->flags() & MediaItem::SmartPlaylist )
1800 i.setAttribute( "smartplaylist", "1" );
1803 transferlist.appendChild( i );
1806 QTextStream stream( &file );
1807 stream.setCodec( QTextCodec::codecForName( "UTF-8" ) );
1808 stream.setAutoDetectUnicode( true );
1809 stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1810 stream << newdoc.toString();
1814 void
1815 MediaQueue::load( const QString& filename )
1817 QFile file( filename );
1818 if( !file.open( QIODevice::ReadOnly ) ) {
1819 return;
1822 clearItems();
1824 QTextStream stream( &file );
1825 stream.setCodec( QTextCodec::codecForName( "UTF-8" ) );
1826 stream.setAutoDetectUnicode( true );
1828 QDomDocument d;
1829 QString er;
1830 int l, c;
1831 if( !d.setContent( stream.readAll(), &er, &l, &c ) ) { // return error values
1832 Amarok::StatusBar::instance()->longMessageThreadSafe( i18n(
1833 //TODO add a link to the path to the playlist
1834 "The XML in the transferlist was invalid. Please report this as a bug to the Amarok "
1835 "developers. Thank you." ), KDE::StatusBar::Error );
1836 error() << "[TRANSFERLISTLOADER]: Error loading xml file: " << filename << "(" << er << ")"
1837 << " at line " << l << ", column " << c;
1838 return;
1841 QList<QDomNode> nodes;
1842 const QString ITEM( "item" ); //so we don't construct this QString all the time
1843 for( QDomNode n = d.namedItem( "playlist" ).firstChild(); !n.isNull(); n = n.nextSibling() )
1845 if( n.nodeName() != ITEM ) continue;
1847 QDomElement elem = n.toElement();
1848 if( !elem.isNull() )
1849 nodes += n;
1851 if( !elem.hasAttribute( "url" ) )
1853 continue;
1855 KUrl url(elem.attribute("url"));
1857 bool podcast = elem.hasAttribute( "podcast" );
1858 PodcastEpisodeBundle peb;
1859 if( url.isLocalFile() )
1860 peb.setLocalURL( url );
1861 MetaBundle *bundle = new MetaBundle( url );
1862 for(QDomNode node = elem.firstChild();
1863 !node.isNull();
1864 node = node.nextSibling())
1866 if(node.firstChild().isNull())
1867 continue;
1869 if(node.nodeName() == "Title" )
1870 bundle->setTitle(node.firstChild().toText().nodeValue());
1871 else if(node.nodeName() == "Artist" )
1872 bundle->setArtist(node.firstChild().toText().nodeValue());
1873 else if(node.nodeName() == "Album" )
1874 bundle->setAlbum(node.firstChild().toText().nodeValue());
1875 else if(node.nodeName() == "Year" )
1876 bundle->setYear(node.firstChild().toText().nodeValue().toUInt());
1877 else if(node.nodeName() == "Genre" )
1878 bundle->setGenre(node.firstChild().toText().nodeValue());
1879 else if(node.nodeName() == "Comment" )
1880 bundle->setComment(node.firstChild().toText().nodeValue());
1881 else if(node.nodeName() == "PodcastDescription" )
1882 peb.setDescription( node.firstChild().toText().nodeValue() );
1883 else if(node.nodeName() == "PodcastAuthor" )
1884 peb.setAuthor( node.firstChild().toText().nodeValue() );
1885 else if(node.nodeName() == "PodcastRSS" )
1886 peb.setParent( KUrl( node.firstChild().toText().nodeValue() ) );
1887 else if(node.nodeName() == "PodcastURL" )
1888 peb.setUrl( KUrl( node.firstChild().toText().nodeValue() ) );
1891 if( podcast )
1893 bundle->setPodcastBundle( peb );
1896 QString playlist = elem.attribute( "playlist" );
1897 QString playlistdata = elem.attribute( "playlistdata" );
1898 if( !playlistdata.isEmpty() )
1900 QString smart = elem.attribute( "smartplaylist" );
1901 if( smart.isEmpty() )
1902 syncPlaylist( playlist, KUrl( playlistdata ), true );
1903 else
1904 syncPlaylist( playlist, playlistdata, true );
1906 else
1907 addUrl( url, bundle, playlist );
1910 URLsAdded();
1913 MediaQueue::MediaQueue(MediaBrowser *parent)
1914 : K3ListView( parent ), m_parent( parent )
1916 setFixedHeight( 200 );
1917 setSelectionMode( Q3ListView::Extended );
1918 setItemsMovable( true );
1919 setDragEnabled( true );
1920 setShowSortIndicator( false );
1921 setSorting( -1 );
1922 setFullWidth( true );
1923 setRootIsDecorated( false );
1924 setDropVisualizer( true ); //the visualizer (a line marker) is drawn when dragging over tracks
1925 setDropHighlighter( true ); //and the highligther (a focus rect) is drawn when dragging over playlists
1926 setDropVisualizerWidth( 3 );
1927 setAcceptDrops( true );
1928 addColumn( i18n( "Transfer Queue" ) );
1930 itemCountChanged();
1932 KActionCollection* ac = new KActionCollection( this );
1933 KStandardAction::selectAll( this, SLOT( selectAll() ), ac );
1935 connect( this, SIGNAL( contextMenuRequested( Q3ListViewItem*, const QPoint&, int ) ),
1936 SLOT( slotShowContextMenu( Q3ListViewItem*, const QPoint&, int ) ) );
1937 connect( this, SIGNAL( dropped(QDropEvent*, Q3ListViewItem*, Q3ListViewItem*) ),
1938 SLOT( slotDropped(QDropEvent*, Q3ListViewItem*, Q3ListViewItem*) ) );
1941 bool
1942 MediaQueue::acceptDrag( QDropEvent *e ) const
1945 return e->source() == viewport()
1946 || e->mimeData()->hasFormat( "amarok-sql" )
1947 || KUrl::List::canDecode( e->mimeData() );
1950 void
1951 MediaQueue::slotDropped( QDropEvent* e, Q3ListViewItem* parent, Q3ListViewItem* after)
1953 if( e->source() != viewport() )
1955 if( e->mimeData()->hasFormat( "amarok-sql" ) )
1957 QString data( e->mimeData()->data( "amarok-sql" ) );
1958 QString playlist = data.section( "\n", 0, 0 );
1959 QString query = data.section( "\n", 1 );
1960 QStringList values = CollectionDB::instance()->query( query );
1961 KUrl::List list = CollectionDB::instance()->URLsFromSqlDrag( values );
1962 addUrls( list, playlist );
1964 else if ( KUrl::List::canDecode( e->mimeData() ) )
1966 KUrl::List list = KUrl::List::fromMimeData( e->mimeData() );
1967 if (!list.isEmpty() )
1968 addUrls( list );
1971 else if( Q3ListViewItem *i = currentItem() )
1973 moveItem( i, parent, after );
1977 void
1978 MediaQueue::dropProxyEvent( QDropEvent *e )
1980 slotDropped( e, 0, 0 );
1983 MediaItem*
1984 MediaQueue::findPath( QString path )
1986 for( Q3ListViewItem *item = firstChild();
1987 item;
1988 item = item->nextSibling())
1990 if(static_cast<MediaItem *>(item)->url().path() == path)
1991 return static_cast<MediaItem *>(item);
1994 return 0;
1997 void
1998 MediaQueue::computeSize() const
2000 m_totalSize = 0;
2001 for( Q3ListViewItem *it = firstChild();
2003 it = it->nextSibling())
2005 MediaItem *item = static_cast<MediaItem *>(it);
2007 if( item && item->bundle() &&
2008 ( !m_parent->currentDevice()
2009 || !m_parent->currentDevice()->isConnected()
2010 || !m_parent->currentDevice()->trackExists(*item->bundle()) ) )
2011 m_totalSize += ((item->size()+1023)/1024)*1024;
2015 KIO::filesize_t
2016 MediaQueue::totalSize() const
2018 return m_totalSize;
2021 void
2022 MediaQueue::addItemToSize( const MediaItem *item ) const
2024 if( item && item->bundle() &&
2025 ( !m_parent->currentDevice()
2026 || !m_parent->currentDevice()->isConnected()
2027 || !m_parent->currentDevice()->trackExists(*item->bundle()) ) )
2028 m_totalSize += ((item->size()+1023)/1024)*1024;
2031 void
2032 MediaQueue::subtractItemFromSize( const MediaItem *item, bool unconditionally ) const
2034 if( item && item->bundle() &&
2035 ( !m_parent->currentDevice()
2036 || !m_parent->currentDevice()->isConnected()
2037 || (unconditionally || !m_parent->currentDevice()->trackExists(*item->bundle())) ) )
2038 m_totalSize -= ((item->size()+1023)/1024)*1024;
2041 void
2042 MediaQueue::removeSelected()
2044 QList<Q3ListViewItem*> selected = selectedItems();
2046 QListIterator<Q3ListViewItem*> iter( selected );
2047 while( iter.hasNext() )
2049 Q3ListViewItem *item = iter.next();
2050 if( !(static_cast<MediaItem *>(item)->flags() & MediaItem::Transferring) )
2052 subtractItemFromSize( static_cast<MediaItem *>(item) );
2053 delete item;
2054 if( m_parent->currentDevice() && m_parent->currentDevice()->isTransferring() )
2056 MediaBrowser::instance()->m_progress->setRange( 0, MediaBrowser::instance()->m_progress->maximum() - 1 );
2061 MediaBrowser::instance()->updateStats();
2062 MediaBrowser::instance()->updateButtons();
2063 itemCountChanged();
2066 void
2067 MediaQueue::keyPressEvent( QKeyEvent *e )
2069 if( e->key() == Qt::Key_Delete )
2070 removeSelected();
2071 else
2072 K3ListView::keyPressEvent( e );
2075 void
2076 MediaQueue::itemCountChanged()
2078 if( childCount() == 0 )
2079 hide();
2080 else if( !isShown() )
2081 show();
2084 void
2085 MediaQueue::slotShowContextMenu( Q3ListViewItem* item, const QPoint& point, int )
2087 if( !childCount() )
2088 return;
2090 Q3PopupMenu menu( this );
2092 enum Actions { REMOVE_SELECTED, CLEAR_ALL, START_TRANSFER };
2094 if( item )
2095 menu.insertItem( KIcon( Amarok::icon( "remove_from_playlist" ) ), i18n( "&Remove From Queue" ), REMOVE_SELECTED );
2097 menu.insertItem( KIcon( Amarok::icon( "playlist_clear" ) ), i18n( "&Clear Queue" ), CLEAR_ALL );
2098 menu.insertItem( KIcon( Amarok::icon( "playlist_refresh" ) ), i18n( "&Start Transfer" ), START_TRANSFER );
2099 menu.setItemEnabled( START_TRANSFER,
2100 MediaBrowser::instance()->currentDevice() &&
2101 MediaBrowser::instance()->currentDevice()->isConnected() &&
2102 MediaBrowser::instance()->currentDevice()->m_transfer );
2104 switch( menu.exec( point ) )
2106 case REMOVE_SELECTED:
2107 removeSelected();
2108 break;
2109 case CLEAR_ALL:
2110 clearItems();
2111 break;
2112 case START_TRANSFER:
2113 MediaBrowser::instance()->transferClicked();
2114 break;
2118 void
2119 MediaQueue::clearItems()
2121 clear();
2122 itemCountChanged();
2123 if(m_parent)
2125 computeSize();
2126 m_parent->updateStats();
2127 m_parent->updateButtons();
2132 #include "mediabrowser.moc"