slight improvement to the context view layout imo.
[amarok.git] / src / mediabrowser.cpp
blobd2072ec5411700d630d0e7eaafa4c4595d27ad7c
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 "collection/CollectionManager.h"
20 #include "debug.h"
21 #include "editfilterdialog.h"
22 #include "deviceconfiguredialog.h"
23 #include "expression.h"
24 #include "hintlineedit.h"
25 #include "MediaItem.h"
26 #include "MediaDevice.h"
27 #include "MediaDeviceCache.h"
28 #include "MediaDevicePluginManager.h"
29 #include "meta/file/File.h"
30 #include "mountpointmanager.h"
31 #include "playlist/PlaylistModel.h"
32 #include "pluginmanager.h"
33 #include "Process.h"
34 #include "scriptmanager.h"
35 #include "scrobbler.h"
36 #include "searchwidget.h"
37 #include "ContextStatusBar.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 <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>
92 #include <solid/storageaccess.h>
94 #include <unistd.h>
96 MediaBrowser *MediaBrowser::s_instance = 0;
98 bool MediaBrowser::isAvailable() //static
100 if( !MediaBrowser::instance() )
101 return false;
103 return true;
105 //to re-enable hiding, uncomment this and get rid of the return true above:
106 //return MediaBrowser::instance()->m_haveDevices;
109 class DummyMediaDevice : public MediaDevice
111 public:
112 DummyMediaDevice() : MediaDevice()
114 m_name = i18n( "No Device Available" );
115 m_type = "dummy-mediadevice";
116 m_udi = "manual|DummyDevice|none|none";
118 void init( MediaBrowser *browser ) { MediaDevice::init( browser ); }
119 virtual ~DummyMediaDevice() {}
120 virtual bool isConnected() { return false; }
121 virtual MediaItem* trackExists(const Meta::TrackPtr) { return 0; }
122 virtual bool lockDevice(bool) { return true; }
123 virtual void unlockDevice() {}
124 virtual bool openDevice( bool silent )
126 if( !silent )
128 //QString msg = i18n( "Sorry, you do not have a supported portable music player." );
129 //Amarok::ContextStatusBar::instance()->longMessage( msg, KDE::StatusBar::Sorry );
131 return false;
133 virtual bool closeDevice() { return false; }
134 virtual void synchronizeDevice() {}
135 virtual MediaItem* copyTrackToDevice(const Meta::TrackPtr track) { return 0; }
136 virtual int deleteItemFromDevice(MediaItem*, int) { return -1; }
140 MediaBrowser::MediaBrowser( const char * /*name*/ )
141 : KVBox( 0)
142 , m_timer( new QTimer( this ) )
143 , m_currentDevice( 0 )
144 , m_waitForTranscode( false )
145 , m_quitting( false )
146 , m_connectAction( 0 )
147 , m_disconnectAction( 0 )
148 , m_customAction( 0 )
149 , m_configAction( 0 )
150 , m_transferAction( 0 )
152 s_instance = this;
154 m_timer->setSingleShot( true );
156 m_toolbar = new Browser::ToolBar( this );
157 m_toolbar->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
158 m_toolbar->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
160 //TODO: how to fix getButton
161 // perhaps the action can be referenced instead?
162 m_connectAction = new KAction(KIcon("connect_creating"), i18n("Connect"), this);
163 connect(m_connectAction, SIGNAL(triggered()), this, SLOT(connectClicked()));
164 m_toolbar->addAction(m_connectAction);
165 // m_toolbar->insertButton( "connect_creating", CONNECT, true, i18n("Connect") );
166 // m_toolbar->getButton(CONNECT)->setToolTip( i18n( "Connect media device" ) );
168 m_disconnectAction = new KAction(KIcon("media-eject"), i18n("Disconnect"), this);
169 connect(m_disconnectAction, SIGNAL(triggered()), this, SLOT(disconnectClicked()));
170 m_toolbar->addAction(m_disconnectAction);
171 // m_toolbar->insertButton( "media-eject", DISCONNECT, true, i18n("Disconnect") );
172 // m_toolbar->getButton(DISCONNECT)->setToolTip( i18n( "Disconnect media device" ) );
174 m_transferAction = new KAction(KIcon("rebuild"), i18n("Transfer"), this);
175 connect(m_transferAction, SIGNAL(triggered()), this, SLOT(transferClicked()));
176 m_toolbar->addAction(m_transferAction);
177 // m_toolbar->insertButton( "rebuild", TRANSFER, true, i18n("Transfer") );
178 // m_toolbar->getButton(TRANSFER)->setToolTip( i18n( "Transfer tracks to media device" ) );
180 m_toolbar->addSeparator();
182 // m_toolbar->setIconText( KToolBar::IconTextRight, true );
183 m_customAction = new KAction(KIcon( "add_playlist" ), i18n("custom"), this);
184 connect(m_customAction, SIGNAL(triggered()), this, SLOT(customClicked()));
185 m_customAction->setText( i18n("Special Device Functions") );
186 m_customAction->setToolTip( i18n("Device-specific special functions or information") );
187 m_toolbar->addAction(m_customAction);
188 // m_toolbar->insertButton( Amarok::icon( "add_playlist" ), CUSTOM, SIGNAL( clicked() ), this, SLOT( customClicked() ), true, "custom" );
189 // m_toolbar->getButton(TRANSFER)->setToolTip( i18n( "Transfer tracks to media device" ) );
191 m_toolbar->setToolButtonStyle( Qt::ToolButtonIconOnly );
193 m_configAction = new KAction(KIcon("configure"), i18n("Configure"), this);
194 connect(m_configAction, SIGNAL(triggered()), this, SLOT(config()));
195 m_toolbar->addAction(m_configAction);
196 // m_toolbar->insertButton( Amarok::icon( "configure" ), CONFIGURE, true, i18n("Configure") );
197 // m_toolbar->getButton(CONFIGURE)->setToolTip( i18n( "Configure device" ) );
200 m_deviceCombo = new KComboBox( this );
202 // searching/filtering
203 QToolBar* searchToolBar = new Browser::ToolBar( this );
204 searchToolBar->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
205 m_searchWidget = new SearchWidget( searchToolBar, this );
206 searchToolBar->addWidget( m_searchWidget );
207 connect( m_timer, SIGNAL( timeout() ), SLOT( slotSetFilter() ) );
209 // connect to device cache
210 connect( MediaDeviceCache::instance(), SIGNAL( deviceAdded(const QString&) ),
211 SLOT( deviceAdded(const QString&) ) );
212 connect( MediaDeviceCache::instance(), SIGNAL( deviceRemoved(const QString&) ),
213 SLOT( deviceRemoved(const QString&) ) );
216 // we always have a dummy device
217 m_pluginName[ i18n( "Disable" ) ] = "dummy-mediadevice";
218 m_pluginAmarokName["dummy-mediadevice"] = i18n( "Disable" );
219 m_pluginName[ i18n( "Do not handle" ) ] = "ignore";
220 m_pluginAmarokName["ignore"] = i18n( "Do not handle" );
221 // query available device plugins
222 m_plugins = PluginManager::query( "[X-KDE-Amarok-plugintype] == 'mediadevice'" );
223 for( KService::List::ConstIterator it = m_plugins.begin(); it != m_plugins.end(); ++it ) {
224 // Save name properties in QMap for lookup
225 m_pluginName[(*it)->name()] = (*it)->property( "X-KDE-Amarok-name" ).toString();
226 m_pluginAmarokName[(*it)->property( "X-KDE-Amarok-name" ).toString()] = (*it)->name();
229 m_views = new KVBox( this );
230 m_queue = new MediaQueue( this );
231 m_progressBox = new KHBox( this );
232 m_progress = new QProgressBar( m_progressBox );
233 m_cancelButton = new KPushButton( KIcon( Amarok::icon( "cancel" ) ), i18n("Cancel"), m_progressBox );
236 m_stats = new SpaceLabel(this);
238 m_progressBox->hide();
240 MediaDevice *dev = new DummyMediaDevice();
241 dev->init( this );
242 addDevice( dev );
243 activateDevice( 0, false );
244 updateDevices();
245 queue()->load( Amarok::saveLocation() + "transferlist.xml" );
246 queue()->computeSize();
248 setFocusProxy( m_queue );
250 updateStats();
252 MediaDeviceCache::instance()->refreshCache();
253 foreach( QString udi, MediaDeviceCache::instance()->getAll() )
254 deviceAdded( udi );
256 connect( m_deviceCombo, SIGNAL( activated( int ) ), SLOT( activateDevice( int ) ) );
258 connect( m_cancelButton, SIGNAL( clicked() ), SLOT( cancelClicked() ) );
259 connect( pApp, SIGNAL( prepareToQuit() ), SLOT( prepareToQuit() ) );
260 //TODO port to meta & new collection interface
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 Meta::TrackPtr newTrack )
297 if (newTrack.isNull())
298 return;
300 m_itemMapMutex.lock();
301 debug() << "tags changed for " << newTrack->url();
302 ItemMap::iterator it = m_itemMap.find( newTrack->url() );
303 if( it != m_itemMap.end() )
305 MediaItem *item = *it;
306 m_itemMapMutex.unlock();
307 if( item->device() )
309 item->device()->tagsChanged( item, newTrack );
311 else
313 // it's an item on the transfer queue
314 item->setMeta( Meta::DataPtr::staticCast(newTrack) );
316 QString text = item->meta()->prettyName();
317 if( !item->m_playlistName.isNull() )
319 text += " (" + item->m_playlistName + ')';
321 item->setText( 0, text);
324 else
326 m_itemMapMutex.unlock();
330 Meta::TrackPtr
331 MediaBrowser::getMeta( const KUrl &url ) const
333 QMutexLocker locker( &m_itemMapMutex );
335 ItemMap::const_iterator it = m_itemMap.find( url.url() );
336 if( it == m_itemMap.end() )
337 return Meta::TrackPtr();
339 return Meta::TrackPtr::dynamicCast( (*it)->meta() );
342 KUrl
343 MediaBrowser::getProxyUrl( const KUrl& daapUrl ) const
345 DEBUG_BLOCK
346 KUrl url;
347 MediaDevice* dc = findChildren<MediaDevice *>( "DaapClient" ).first();
348 if( dc )
349 url = dc->getProxyUrl( daapUrl );
350 return url;
353 MediaDevice *
354 MediaBrowser::deviceFromId( const QString &id ) const
356 for( QList<MediaDevice *>::const_iterator it = m_devices.constBegin();
357 it != m_devices.constEnd();
358 it++ )
360 if( (*it)->udi() == id )
361 return (*it);
364 return NULL;
367 void
368 MediaBrowser::activateDevice( const MediaDevice *dev )
370 int index = 0;
371 for( QList<MediaDevice *>::iterator it = m_devices.begin();
372 it != m_devices.end();
373 it++ )
375 if( *it == dev )
377 activateDevice( index );
378 break;
380 index++;
384 void
385 MediaBrowser::activateDevice( int index, bool skipDummy )
387 if( m_currentDevice && m_currentDevice->customAction() )
389 m_toolbar->removeAction( m_currentDevice->customAction() );
390 m_toolbar->hide();
391 m_toolbar->show();
394 foreach( MediaDevice *md, m_devices )
396 if( md && md->view() )
397 md->view()->hide();
400 if( index < 0 )
402 m_currentDevice = m_devices.last();
403 return;
406 if( skipDummy )
407 index++;
409 if( index >= m_devices.count() )
411 if( !m_devices.isEmpty() )
412 m_currentDevice = m_devices.last();
413 else
414 m_currentDevice = 0;
415 updateButtons();
416 queue()->computeSize();
417 updateStats();
418 return;
421 m_currentDevice = m_devices[index];
422 if( m_currentDevice && m_currentDevice->view() )
424 m_currentDevice->view()->show();
425 if( m_currentDevice->customAction() )
427 m_toolbar->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
428 m_toolbar->addAction( m_currentDevice->customAction() );
429 m_toolbar->hide();
430 m_toolbar->show();
433 m_deviceCombo->setCurrentIndex( index-1 );
435 updateButtons();
436 queue()->computeSize();
437 updateStats();
440 void
441 MediaBrowser::addDevice( MediaDevice *device )
443 m_devices.append( device );
445 device->loadConfig();
447 if( device->autoConnect() )
449 device->connectDevice( true );
450 updateButtons();
453 updateDevices();
456 void
457 MediaBrowser::removeDevice( MediaDevice *device )
459 DEBUG_BLOCK
461 debug() << "remove device: type=" << device->type();
463 for( QList<MediaDevice *>::iterator it = m_devices.begin();
464 it != m_devices.end();
465 it++ )
467 if( *it == device )
469 bool current = ( (*it)->udi() == m_currentDevice->udi() );
470 m_devices.erase( it );
471 if( current )
472 activateDevice( 0, false );
473 break;
477 if( device->isConnected() )
479 if( device->disconnectDevice( false /* don't run post-disconnect command */ ) )
480 unloadDevicePlugin( device );
481 else
483 debug() << "Cannot remove device because disconnect failed";
484 Amarok::ContextStatusBar::instance()->longMessage(
485 i18n( "Cannot remove device because disconnect failed" ),
486 KDE::StatusBar::Warning );
489 else
490 unloadDevicePlugin( device );
492 updateDevices();
495 void
496 MediaBrowser::updateDevices()
498 DEBUG_BLOCK
499 m_deviceCombo->clear();
500 uint i = 0;
501 foreach( MediaDevice* device, m_devices )
503 if( m_devices.count() > 1 && dynamic_cast<DummyMediaDevice *>(device) )
504 continue;
505 QString name = device->name();
506 if( !device->deviceNode().isEmpty() )
508 name = i18n( "%1 at %2", name, device->deviceNode() );
510 if( device->hasMountPoint() && !device->mountPoint().isEmpty() )
512 name += i18n( " (mounted at %1)", device->mountPoint() );
514 m_deviceCombo->addItem( name, i );
515 if( !m_currentDevice || device->udi() == m_currentDevice->udi() )
517 m_deviceCombo->setCurrentItem( name );
520 m_deviceCombo->setEnabled( m_devices.count() > 1 );
521 m_haveDevices = m_devices.count() > 1;
524 QStringList
525 MediaBrowser::deviceNames() const
527 QStringList list;
529 for( QList<MediaDevice *>::const_iterator it = m_devices.constBegin();
530 it != m_devices.constEnd();
531 it++ )
533 QString name = (*it)->name();
534 list << name;
537 return list;
540 bool
541 MediaBrowser::deviceSwitch( const QString &name )
543 int index = 0;
544 for( QList<MediaDevice *>::iterator it = m_devices.begin();
545 it != m_devices.end();
546 it++ )
548 if( (*it)->name() == name )
550 activateDevice( index, false );
551 return true;
553 index++;
556 return false;
559 void
560 MediaBrowser::transcodingFinished( const QString &src, const QString &dst )
562 KUrl srcJob = KUrl( m_transcodeSrc );
563 KUrl srcResult = KUrl( src );
565 if( srcJob.path() == srcResult.path() )
567 m_transcodedUrl = KUrl( dst );
568 m_waitForTranscode = false;
570 else
572 debug() << "transcoding for " << src << " finished, "
573 << "but we are waiting for " << m_transcodeSrc << " -- aborting";
574 m_waitForTranscode = false;
578 KUrl
579 MediaBrowser::transcode( const KUrl &src, const QString &filetype )
581 const ScriptManager* const sm = ScriptManager::instance();
583 if( sm->transcodeScriptRunning().isEmpty() )
585 debug() << "cannot transcode with no transcoder registered";
586 return KUrl();
589 m_waitForTranscode = true;
590 m_transcodeSrc = src.url();
591 m_transcodedUrl = KUrl();
592 ScriptManager::instance()->notifyTranscode( src.url(), filetype );
594 while( m_waitForTranscode && !sm->transcodeScriptRunning().isEmpty() )
596 usleep( 10000 );
597 kapp->processEvents( QEventLoop::AllEvents );
600 return m_transcodedUrl;
604 void
605 MediaBrowser::slotSetFilterTimeout() //SLOT
607 m_timer->start( 280 ); //stops the timer for us first
610 void
611 MediaBrowser::slotSetFilter() //SLOT
613 m_timer->stop();
615 if( m_currentDevice )
616 m_currentDevice->view()->setFilter( m_searchWidget->lineEdit()->text() );
619 void
620 MediaBrowser::slotSetFilter( const QString &text )
622 m_searchWidget->lineEdit()->setText( text );
623 slotSetFilter();
626 void
627 MediaBrowser::slotEditFilter()
629 EditFilterDialog *fd = new EditFilterDialog( this, true, m_searchWidget->lineEdit()->text() );
630 connect( fd, SIGNAL(filterChanged(const QString &)), SLOT(slotSetFilter(const QString &)) );
631 if( fd->exec() )
632 m_searchWidget->lineEdit()->setText( fd->filter() );
633 delete fd;
636 void
637 MediaBrowser::prepareToQuit()
639 m_waitForTranscode = false;
640 m_quitting = true;
641 for( QList<MediaDevice *>::iterator it = m_devices.begin();
642 it != m_devices.end();
643 ++it )
645 if( (*it)->isConnected() )
646 (*it)->disconnectDevice( false /* don't unmount */ );
650 MediaBrowser::~MediaBrowser()
652 debug() << "having to remove " << m_devices.count() << " devices";
653 while( !m_devices.isEmpty() )
655 removeDevice( m_devices.last() );
658 queue()->save( Amarok::saveLocation() + "transferlist.xml" );
660 delete m_deviceCombo;
661 delete m_queue;
664 MediaView::MediaView( QWidget* parent, MediaDevice *device )
665 : K3ListView( parent )
666 , m_parent( parent )
667 , m_device( device )
669 hide();
670 setSelectionMode( Q3ListView::Extended );
671 setItemsMovable( false );
672 setShowSortIndicator( true );
673 setFullWidth( true );
674 setRootIsDecorated( true );
675 setDragEnabled( true );
676 setDropVisualizer( true ); //the visualizer (a line marker) is drawn when dragging over tracks
677 setDropHighlighter( true ); //and the highligther (a focus rect) is drawn when dragging over playlists
678 setDropVisualizerWidth( 3 );
679 setAcceptDrops( true );
681 header()->hide();
682 addColumn( i18n( "Remote Media" ) );
684 KActionCollection* ac = new KActionCollection( this );
685 KStandardAction::selectAll( this, SLOT( selectAll() ), ac );
687 connect( this, SIGNAL( contextMenuRequested( Q3ListViewItem*, const QPoint&, int ) ),
688 this, SLOT( rmbPressed( Q3ListViewItem*, const QPoint&, int ) ) );
690 connect( this, SIGNAL( itemRenamed( Q3ListViewItem* ) ),
691 this, SLOT( renameItem( Q3ListViewItem* ) ) );
693 connect( this, SIGNAL( expanded( Q3ListViewItem* ) ),
694 this, SLOT( slotExpand( Q3ListViewItem* ) ) );
696 connect( this, SIGNAL( returnPressed( Q3ListViewItem* ) ),
697 this, SLOT( invokeItem( Q3ListViewItem* ) ) );
699 connect( this, SIGNAL( doubleClicked( Q3ListViewItem*, const QPoint&, int ) ),
700 this, SLOT( invokeItem( Q3ListViewItem*, const QPoint &, int ) ) );
703 void
704 MediaView::keyPressEvent( QKeyEvent *e )
706 if( e->key() == Qt::Key_Delete )
707 m_device->deleteFromDevice();
708 else
709 K3ListView::keyPressEvent( e );
712 void
713 MediaView::invokeItem( Q3ListViewItem* i, const QPoint& point, int column ) //SLOT
715 if( column == -1 )
716 return;
718 QPoint p = mapFromGlobal( point );
719 if ( p.x() > header()->sectionPos( header()->mapToIndex( 0 ) ) + treeStepSize() * ( i->depth() + ( rootIsDecorated() ? 1 : 0) ) + itemMargin()
720 || p.x() < header()->sectionPos( header()->mapToIndex( 0 ) ) )
721 invokeItem( i );
725 void
726 MediaView::invokeItem( Q3ListViewItem *i )
728 MediaItem *item = static_cast<MediaItem *>( i );
729 if( !item )
730 return;
732 Meta::TrackList tracks = CollectionManager::instance()->tracksForUrls(
733 nodeBuildDragList( item ) );
734 The::playlistModel()->insertOptioned( tracks, Playlist::AppendAndPlay );
737 void
738 MediaView::renameItem( Q3ListViewItem *item )
740 m_device->renameItem( item );
743 void
744 MediaView::slotExpand( Q3ListViewItem *item )
746 m_device->expandItem( item );
750 MediaView::~MediaView()
755 Q3DragObject *
756 MediaView::dragObject()
758 KUrl::List urls = nodeBuildDragList( 0 );
759 K3MultipleDrag *md = new K3MultipleDrag( viewport() );
760 md->addDragObject( K3ListView::dragObject() );
761 K3URLDrag* ud = new K3URLDrag( urls, viewport() );
762 md->addDragObject( ud );
763 md->setPixmap( CollectionDB::createDragPixmap( urls ),
764 QPoint( CollectionDB::DRAGPIXMAP_OFFSET_X, CollectionDB::DRAGPIXMAP_OFFSET_Y ) );
765 return md;
769 KUrl::List
770 MediaView::nodeBuildDragList( MediaItem* item, int flags )
772 KUrl::List items;
773 MediaItem* fi;
775 if ( !item )
777 fi = static_cast<MediaItem*>(firstChild());
779 else
780 fi = item;
782 while ( fi )
784 if( fi->isVisible() )
786 if ( fi->isSelected() || !(flags & OnlySelected ) )
788 if( fi->isLeafItem() || fi->type() == MediaItem::DIRECTORY )
789 items += fi->url();
790 else
792 if(fi->childCount() )
793 items += nodeBuildDragList( static_cast<MediaItem*>(fi->firstChild()), None );
796 else
798 if ( fi->childCount() )
799 items += nodeBuildDragList( static_cast<MediaItem*>(fi->firstChild()), OnlySelected );
802 fi = static_cast<MediaItem*>(fi->nextSibling());
804 return items;
808 MediaView::getSelectedLeaves( MediaItem *parent, QList<MediaItem*> *list, int flags )
810 int numFiles = 0;
811 if( !list )
812 list = new QList<MediaItem*>;
814 MediaItem *it;
815 if( !parent )
816 it = static_cast<MediaItem *>(firstChild());
817 else
818 it = static_cast<MediaItem *>(parent->firstChild());
820 for( ; it; it = static_cast<MediaItem*>(it->nextSibling()))
822 if( it->isVisible() )
824 if( it->childCount() && !( it->type() == MediaItem::DIRECTORY && it->isSelected() ) )
826 int f = flags;
827 if( it->isSelected() )
828 f &= ~OnlySelected;
829 numFiles += getSelectedLeaves(it, list, f );
831 if( it->isSelected() || !(flags & OnlySelected) )
833 if( it->type() == MediaItem::TRACK ||
834 it->type() == MediaItem::DIRECTORY ||
835 it->type() == MediaItem::PODCASTITEM ||
836 it->type() == MediaItem::PLAYLISTITEM||
837 it->type() == MediaItem::INVISIBLE ||
838 it->type() == MediaItem::ORPHANED )
840 if( flags & OnlyPlayed )
842 if( it->played() > 0 )
843 numFiles++;
845 else
846 numFiles++;
848 if( ( it->isLeafItem() && (!(flags & OnlyPlayed) || it->played()>0) )
849 || it->type() == MediaItem::DIRECTORY )
850 list->append( it );
854 return numFiles;
858 bool
859 MediaView::acceptDrag( QDropEvent *e ) const
861 if( e->source() == MediaBrowser::queue()->viewport() )
862 return false;
864 return e->source() == viewport()
865 || e->mimeData()->hasFormat( "amarok-sql" )
866 || KUrl::List::canDecode( e->mimeData() );
869 void
870 MediaView::contentsDropEvent( QDropEvent *e )
872 cleanDropVisualizer();
873 cleanItemHighlighter();
875 if(e->source() == viewport())
877 const QPoint p = contentsToViewport( e->pos() );
878 MediaItem *item = static_cast<MediaItem *>(itemAt( p ));
880 if( !item && MediaBrowser::instance()->currentDevice()->m_type != "generic-mediadevice" )
881 return;
883 QList<MediaItem*> items;
885 if( !item || item->type() == MediaItem::DIRECTORY ||
886 item->type() == MediaItem::TRACK )
888 QList<MediaItem*> items;
889 getSelectedLeaves( 0, &items );
890 m_device->addToDirectory( item, items );
892 else if( item->type() == MediaItem::PLAYLIST )
894 MediaItem *list = item;
895 MediaItem *after = 0;
896 for(MediaItem *it = static_cast<MediaItem *>(item->firstChild());
898 it = static_cast<MediaItem *>(it->nextSibling()))
899 after = it;
901 getSelectedLeaves( 0, &items );
902 m_device->addToPlaylist( list, after, items );
904 else if( item->type() == MediaItem::PLAYLISTITEM )
906 MediaItem *list = static_cast<MediaItem *>(item->parent());
907 MediaItem *after = 0;
908 for(MediaItem *it = static_cast<MediaItem*>(item->parent()->firstChild());
910 it = static_cast<MediaItem *>(it->nextSibling()))
912 if(it == item)
913 break;
914 after = it;
917 getSelectedLeaves( 0, &items );
918 m_device->addToPlaylist( list, after, items );
920 else if( item->type() == MediaItem::PLAYLISTSROOT )
922 QList<MediaItem*> items;
923 getSelectedLeaves( 0, &items );
924 QString base( i18n("New Playlist") );
925 QString name = base;
926 int i=1;
927 while( item->findItem(name) )
929 QString num;
930 num.setNum(i);
931 name = base + ' ' + num;
932 i++;
934 MediaItem *pl = m_device->newPlaylist(name, item, items);
935 ensureItemVisible(pl);
936 rename(pl, 0);
939 else
941 if( e->mimeData()->hasFormat( "amarok-sql" ) )
943 QString data( e->mimeData()->data( "amarok-sql" ) );
944 QString playlist = data.section( "\n", 0, 0 );
945 QString query = data.section( "\n", 1 );
946 QStringList values = CollectionDB::instance()->query( query );
947 KUrl::List list = CollectionDB::instance()->URLsFromSqlDrag( values );
948 MediaBrowser::queue()->addUrls( list, playlist );
950 else if ( KUrl::List::canDecode( e->mimeData() ) )
952 KUrl::List list = KUrl::List::fromMimeData( e->mimeData() );
953 MediaBrowser::queue()->addUrls( list );
958 void
959 MediaView::viewportPaintEvent( QPaintEvent *e )
961 K3ListView::viewportPaintEvent( e );
963 // Superimpose bubble help:
965 if ( !MediaBrowser::instance()->currentDevice() || !MediaBrowser::instance()->currentDevice()->isConnected() )
967 QPainter p( viewport() );
969 Q3SimpleRichText t( i18n(
970 "<div align=center>"
971 "<h3>Media Device Browser</h3>"
972 "Configure your media device and then "
973 "click the Connect button to access your media device. "
974 "Drag and drop files to enqueue them for transfer."
975 "</div>" ), QApplication::font() );
977 t.setWidth( width() - 50 );
979 const uint w = t.width() + 20;
980 const uint h = t.height() + 20;
982 p.setBrush( palette().background() );
983 p.drawRoundRect( 15, 15, w, h, (8*200)/w, (8*200)/h );
984 t.draw( &p, 20, 20, QRect(), palette() );
986 MediaBrowser::instance()->updateButtons();
989 void
990 MediaView::rmbPressed( Q3ListViewItem *item, const QPoint &p, int i )
992 if( m_device->isConnected() )
993 m_device->rmbPressed( item, p, i );
996 MediaItem *
997 MediaView::newDirectory( MediaItem *parent )
999 bool ok;
1000 const QString name = KInputDialog::getText(i18n("Add Directory"), i18n("Directory Name:"), QString(), &ok, this);
1002 if( ok && !name.isEmpty() )
1004 m_device->newDirectory( name, parent );
1007 return 0;
1010 void
1011 MediaBrowser::deviceAdded( const QString &udi )
1013 DEBUG_BLOCK
1014 debug() << "deviceAdded called with a udi of: " << udi;
1015 if( MediaDeviceCache::instance()->deviceType( udi ) == MediaDeviceCache::SolidVolumeType &&
1016 !MediaDeviceCache::instance()->isGenericEnabled( udi ) )
1018 debug() << "device is a generic volume but not enabled for use";
1019 return;
1021 MediaDevice *md = loadDevicePlugin( udi );
1022 if( md )
1024 addDevice( md );
1025 if( m_currentDevice == *(m_devices.begin()) || m_currentDevice == *(m_devices.end()) )
1026 activateDevice( m_devices.count()-1, false );
1030 void
1031 MediaBrowser::deviceRemoved( const QString &udi )
1033 for( QList<MediaDevice *>::iterator it = m_devices.begin();
1034 it != m_devices.end();
1035 it++ )
1037 if( (*it)->m_udi == udi )
1039 if( (*it)->isConnected() )
1041 if( (*it)->disconnectDevice() )
1042 removeDevice( *it );
1043 Amarok::ContextStatusBar::instance()->longMessage(
1044 i18n( "The device %1 was removed before it was disconnected. "
1045 "In order to avoid possible data loss, press the \"Disconnect\" "
1046 "button before disconnecting the device.", (*it)->name() ),
1047 KDE::StatusBar::Warning );
1049 else
1050 removeDevice( *it );
1051 break;
1056 MediaDevice *
1057 MediaBrowser::loadDevicePlugin( const QString &udi )
1059 DEBUG_BLOCK
1061 QString name;
1062 QString mountPoint;
1063 QString protocol;
1065 if( MediaDeviceCache::instance()->deviceType( udi ) == MediaDeviceCache::SolidPMPType )
1067 debug() << "udi " << udi << " detected as a Solid PMP device";
1068 Solid::Device solidDevice( udi );
1070 Solid::PortableMediaPlayer* pmp = solidDevice.as<Solid::PortableMediaPlayer>();
1072 if( !pmp )
1074 debug() << "Failed to convert Solid device to PortableMediaPlayer";
1075 return 0;
1077 if( pmp->supportedProtocols().size() == 0 )
1079 debug() << "Portable Media Player " << udi << " does not support any protocols";
1080 return 0;
1083 foreach( const QString &supported, pmp->supportedProtocols() )
1084 debug() << "Device supports protocol " << supported;
1086 protocol = pmp->supportedProtocols()[0];
1087 if( protocol == "storage" )
1088 protocol = "generic";
1089 if( protocol == "pde" )
1090 protocol == "njb";
1092 protocol += "-mediadevice";
1093 name = solidDevice.vendor() + " - " + solidDevice.product();
1095 else if( MediaDeviceCache::instance()->deviceType( udi ) == MediaDeviceCache::SolidVolumeType )
1097 debug() << "udi " << " detected as Solid volume device";
1098 Solid::Device solidDevice( udi );
1100 Solid::StorageAccess* ssa = solidDevice.as<Solid::StorageAccess>();
1101 if( !ssa )
1103 debug() << "Failed to convert Solid device to StorageAccess";
1104 return 0;
1107 protocol = "generic-mediadevice";
1108 name = solidDevice.parent().vendor() + " - " + solidDevice.parent().product();
1109 mountPoint = ssa->filePath();
1111 else if( MediaDeviceCache::instance()->deviceType( udi ) == MediaDeviceCache::ManualType )
1113 debug() << "udi " << udi << " detected as manual device";
1114 KConfigGroup config = Amarok::config( "PortableDevices" );
1115 protocol = config.readEntry( udi, QString() );
1117 if( protocol.isEmpty() )
1119 debug() << "Found no plugin in amarokrc for " << udi;
1120 return 0;
1123 QStringList sl = udi.split( "|" );
1124 name = sl[1];
1125 mountPoint = sl[2];
1128 QString query = "[X-KDE-Amarok-plugintype] == 'mediadevice' and [X-KDE-Amarok-name] == '%1'";
1129 debug() << "query is " << query;
1130 Amarok::Plugin *plugin = PluginManager::createFromQuery( query.arg( protocol ) );
1132 if( plugin )
1134 debug() << "Returning plugin!";
1135 MediaDevice *device = static_cast<MediaDevice *>( plugin );
1136 device->init( this );
1137 device->m_udi = udi;
1138 device->m_name = name;
1139 device->m_type = protocol;
1140 device->m_mountPoint = mountPoint;
1141 return device;
1144 debug() << "no plugin for " << protocol;
1145 return 0;
1148 void
1149 MediaBrowser::pluginSelected( const QString &udi, const QString &plugin )
1151 DEBUG_BLOCK
1152 if( !plugin.isEmpty() )
1154 debug() << "Device udi is " << udi << " and plugin selected is: " << plugin;
1155 if( MediaDeviceCache::instance()->deviceType( udi ) == MediaDeviceCache::ManualType )
1156 Amarok::config( "PortableDevices" ).writeEntry( udi, plugin );
1158 bool success = true;
1159 foreach( MediaDevice* device, m_devices )
1161 debug() << "plugin = " << plugin << ", device->type() = " << device->type();
1162 if( device->udi() == udi )
1164 if( device->type() == plugin )
1165 return;
1166 debug() << "removing matching device";
1167 if( device->isConnected() )
1169 if( device->disconnectDevice( false ) )
1170 removeDevice( device );
1171 else
1172 success = false;
1174 else
1175 removeDevice( device );
1176 break;
1180 if( success )
1182 deviceAdded( udi );
1184 else
1186 debug() << "Cannot change plugin while operation is in progress" << endl;
1187 Amarok::ContextStatusBar::instance()->longMessage(
1188 i18n( "Cannot change plugin while operation is in progress" ),
1189 KDE::StatusBar::Warning );
1192 else
1193 debug() << "Device udi is " << udi << " and you opted not to use a plugin";
1196 void
1197 MediaBrowser::showPluginManager()
1199 MediaDevicePluginManagerDialog* mpm = new MediaDevicePluginManagerDialog();
1200 mpm->exec();
1201 delete mpm;
1204 void
1205 MediaBrowser::unloadDevicePlugin( MediaDevice *device )
1207 DEBUG_BLOCK
1209 if( !device )
1210 return;
1212 disconnect( device ); // disconnect all signals
1214 if( dynamic_cast<DummyMediaDevice *>(device) )
1216 delete device;
1218 else
1220 PluginManager::unload( device );
1224 bool
1225 MediaBrowser::config()
1227 if( m_deviceCombo->currentText() == "No Device Available" )
1229 //Amarok::ContextStatusBar::instance()->longMessage( i18n( "No device selected to configure." ),
1230 // KDE::StatusBar::Sorry );
1231 showPluginManager();
1232 return true;
1235 DeviceConfigureDialog* dcd = new DeviceConfigureDialog( m_currentDevice );
1236 dcd->exec();
1237 bool successful = dcd->successful();
1238 delete dcd;
1239 return successful;
1242 void
1243 MediaBrowser::updateButtons()
1245 if( !connectAction() || !disconnectAction() || !transferAction() )
1246 return;
1248 if( m_currentDevice )
1250 transferAction()->setVisible( m_currentDevice->m_transfer );
1251 customAction()->setVisible( m_currentDevice->m_customButton );
1252 configAction()->setVisible( m_currentDevice->m_configure );
1254 connectAction()->setEnabled( !m_currentDevice->isConnected() );
1255 disconnectAction()->setEnabled( m_currentDevice->isConnected() );
1256 transferAction()->setEnabled( m_currentDevice->isConnected() && m_queue->childCount() > 0 );
1257 if( customAction() )
1258 customAction()->setEnabled( true );
1260 else
1262 connectAction()->setEnabled( false );
1263 disconnectAction()->setEnabled( false );
1264 transferAction()->setEnabled( false );
1265 if( customAction() )
1266 customAction()->setEnabled( false );
1270 void
1271 MediaBrowser::updateStats()
1273 if( !m_stats )
1274 return;
1276 KIO::filesize_t queued = m_queue->totalSize();
1278 QString text = i18np( " 1 track in queue", " %1 tracks in queue", m_queue->childCount() );
1279 if(m_queue->childCount() > 0)
1281 text += i18n(" (%1)", KIO::convertSize( queued ) );
1284 KIO::filesize_t total, avail;
1285 if( m_currentDevice && m_currentDevice->getCapacity(&total, &avail) )
1287 text += i18n( " - %1 of %2 available", KIO::convertSize( avail ), KIO::convertSize( total ) );
1289 m_stats->m_used = total-avail;
1290 m_stats->m_total = total;
1291 m_stats->m_scheduled = queued;
1293 else
1295 m_stats->m_used = 0;
1296 m_stats->m_total = 0;
1297 m_stats->m_scheduled = queued;
1300 m_stats->setText(text);
1301 m_stats->setToolTip( text );
1305 bool
1306 MediaView::setFilter( const QString &filter, MediaItem *parent )
1308 //TODO port to meta, for now never filter.
1309 return true;
1311 bool advanced = ExpressionParser::isAdvancedExpression( filter );
1312 QList<int> defaultColumns;
1313 defaultColumns << MetaBundle::Album;
1314 defaultColumns << MetaBundle::Title;
1315 defaultColumns << MetaBundle::Artist;
1317 bool root = false;
1318 MediaItem *it;
1319 if( !parent )
1321 root = true;
1322 it = static_cast<MediaItem *>(firstChild());
1324 else
1326 it = static_cast<MediaItem *>(parent->firstChild());
1329 bool childrenVisible = false;
1330 for( ; it; it = static_cast<MediaItem *>(it->nextSibling()))
1332 bool visible = true;
1333 if(it->isLeafItem())
1335 if( advanced )
1337 ParsedExpression parsed = ExpressionParser::parse( filter );
1338 visible = it->bundle() && it->bundle()->matchesParsedExpression( parsed, defaultColumns );
1340 else
1342 visible = it->bundle() && it->bundle()->matchesSimpleExpression( filter, defaultColumns );
1345 else
1347 visible = setFilter(filter, it);
1348 if(it->type()==MediaItem::PLAYLISTSROOT || it->type()==MediaItem::PLAYLIST)
1350 visible = true;
1352 else if(it->type()==MediaItem::DIRECTORY)
1354 bool match = true;
1355 QStringList list = filter.split( " ", QString::SkipEmptyParts );
1356 for( QStringList::iterator i = list.begin();
1357 i != list.end();
1358 ++i )
1360 if( !(*it).text(0).contains( *i ) )
1362 match = false;
1363 break;
1366 if( match )
1367 visible = true;
1370 if( filter.isEmpty() )
1371 visible = true;
1372 it->setVisible( visible );
1373 if(visible)
1374 childrenVisible = true;
1377 if( root && m_device )
1378 m_device->updateRootItems();
1380 return childrenVisible;
1384 void
1385 MediaQueue::syncPlaylist( const QString &name, const QString &query, bool loading )
1387 MediaItem* item = new MediaItem( this, lastItem() );
1388 item->setType( MediaItem::PLAYLIST );
1389 item->setExpandable( false );
1390 item->setData( query );
1391 item->m_playlistName = name;
1392 item->setText( 0, name );
1393 item->m_flags |= MediaItem::SmartPlaylist;
1394 m_parent->m_progress->setRange( 0, m_parent->m_progress->maximum() + 1 );
1395 itemCountChanged();
1396 if( !loading )
1397 URLsAdded();
1400 void
1401 MediaQueue::syncPlaylist( const QString &name, const KUrl &url, bool loading )
1403 MediaItem* item = new MediaItem( this, lastItem() );
1404 item->setType( MediaItem::PLAYLIST );
1405 item->setExpandable( false );
1406 item->setData( url.url() );
1407 item->m_playlistName = name;
1408 item->setText( 0, name );
1409 m_parent->m_progress->setRange( 0, m_parent->m_progress->maximum() + 1 );
1410 itemCountChanged();
1411 if( !loading )
1412 URLsAdded();
1415 void
1416 MediaQueue::addUrl( const KUrl& url2, Meta::TrackPtr meta, const QString &playlistName )
1418 KUrl url = Amarok::mostLocalURL( url2 );
1420 //Port 2.0
1421 // if( PlaylistFile::isPlaylistFile( url ) )
1422 // {
1423 // QString name = url.path().section( "/", -1 ).section( ".", 0, -2 ).replace( "_", " " );
1424 // PlaylistFile playlist( url.path() );
1426 // if( playlist.isError() )
1427 // {
1428 // Amarok::ContextStatusBar::instance()->longMessage( i18n( "Failed to load playlist: %1", url.path() ),
1429 // KDE::StatusBar::Sorry );
1430 // return;
1431 // }
1433 // for( BundleList::iterator it = playlist.bundles().begin();
1434 // it != playlist.bundles().end();
1435 // ++it )
1436 // {
1437 // addUrl( (*it).url(), 0, name );
1438 // }
1439 // return;
1440 // }
1441 if( url.protocol() == "file" && QFileInfo( url.path() ).isDir() )
1443 //TODO: PORT
1444 // KUrl::List urls = Amarok::recursiveUrlExpand( url );
1445 // oldForeachType( KUrl::List, urls )
1446 // addUrl( *it );
1447 // return;
1450 if( playlistName.isNull() )
1452 for( MediaItem *it = static_cast<MediaItem *>(firstChild());
1454 it = static_cast<MediaItem *>(it->nextSibling()) )
1456 if( it->url() == url )
1458 Amarok::ContextStatusBar::instance()->shortMessage(
1459 i18n( "Track already queued for transfer: %1", url.url() ) );
1460 return;
1465 if(!meta)
1466 meta = Meta::TrackPtr( new MetaFile::Track( url ) );
1468 MediaItem* item = new MediaItem( this, lastItem() );
1469 item->setExpandable( false );
1470 item->setDropEnabled( true );
1471 item->setMeta( Meta::DataPtr::staticCast(meta) );
1472 //TODO: podcast meta
1473 //if(bundle->podcastBundle() )
1475 // item->setType( MediaItem::PODCASTITEM );
1477 item->m_playlistName = playlistName;
1479 QString text = item->meta()->prettyName();
1480 if( !item->m_playlistName.isNull() )
1482 text += " (" + item->m_playlistName + ')';
1484 item->setText( 0, text);
1486 m_parent->updateButtons();
1487 m_parent->m_progress->setRange( 0, m_parent->m_progress->maximum() + 1 );
1488 addItemToSize( item );
1489 itemCountChanged();
1492 void
1493 MediaQueue::addUrl( const KUrl &url, MediaItem *item )
1495 DEBUG_BLOCK
1496 MediaItem *newitem = new MediaItem( this, lastItem() );
1497 newitem->setExpandable( false );
1498 newitem->setDropEnabled( true );
1499 Meta::TrackPtr meta = Meta::TrackPtr::dynamicCast(item->meta());
1500 //TODO port to meta
1501 //KUrl filepath(url);
1502 //filepath.addPath( meta->filename() );
1503 //bundle->setUrl( filepath );
1504 newitem->m_device = item->m_device;
1505 //if(bundle->podcastBundle() )
1507 // item->setType( MediaItem::PODCASTITEM );
1509 QString text = item->meta()->prettyName();
1510 if( !item->m_playlistName.isEmpty() )
1512 text += " (" + item->m_playlistName + ')';
1514 newitem->setText( 0, text);
1515 newitem->setMeta( Meta::DataPtr::staticCast(meta) );
1516 m_parent->updateButtons();
1517 m_parent->m_progress->setRange( 0, m_parent->m_progress->maximum() + 1 );
1518 addItemToSize( item );
1519 itemCountChanged();
1523 void
1524 MediaQueue::addUrls( const KUrl::List urls, const QString &playlistName )
1526 KUrl::List::ConstIterator it = urls.begin();
1527 for ( ; it != urls.end(); ++it )
1528 addUrl( *it, Meta::TrackPtr(), playlistName );
1530 URLsAdded();
1533 void
1534 MediaQueue::URLsAdded()
1536 m_parent->updateStats();
1537 m_parent->updateButtons();
1538 if( m_parent->currentDevice()
1539 && m_parent->currentDevice()->isConnected()
1540 && m_parent->currentDevice()->asynchronousTransfer()
1541 && !m_parent->currentDevice()->isTransferring() )
1542 m_parent->currentDevice()->transferFiles();
1544 save( Amarok::saveLocation() + "transferlist.xml" );
1547 Q3DragObject *
1548 MediaQueue::dragObject()
1550 KUrl::List urls;
1551 for( Q3ListViewItem *it = firstChild(); it; it = it->nextSibling() )
1553 if( it->isVisible() && it->isSelected() && static_cast<MediaItem *>(it) )
1554 urls += static_cast<MediaItem *>(it)->url();
1557 K3MultipleDrag *md = new K3MultipleDrag( viewport() );
1558 Q3DragObject *d = K3ListView::dragObject();
1559 K3URLDrag* urldrag = new K3URLDrag( urls, viewport() );
1560 md->addDragObject( d );
1561 md->addDragObject( urldrag );
1562 md->setPixmap( CollectionDB::createDragPixmap( urls ),
1563 QPoint( CollectionDB::DRAGPIXMAP_OFFSET_X, CollectionDB::DRAGPIXMAP_OFFSET_Y ) );
1564 return md;
1567 void
1568 MediaBrowser::cancelClicked()
1570 DEBUG_BLOCK
1572 m_waitForTranscode = false;
1573 if( m_currentDevice )
1574 m_currentDevice->abortTransfer();
1577 void
1578 MediaBrowser::transferClicked()
1580 transferAction()->setEnabled( false );
1581 if( m_currentDevice
1582 && m_currentDevice->isConnected()
1583 && !m_currentDevice->isTransferring() )
1585 if( !m_currentDevice->hasTransferDialog() )
1586 m_currentDevice->transferFiles();
1587 else
1589 m_currentDevice->runTransferDialog();
1590 //may not work with non-TransferDialog-class object, but maybe some run time introspection could solve it?
1591 if( m_currentDevice->getTransferDialog() &&
1592 ( reinterpret_cast<TransferDialog *>(m_currentDevice->getTransferDialog()))->isAccepted() )
1593 m_currentDevice->transferFiles();
1594 else
1595 updateButtons();
1598 m_currentDevice->m_transferDir = m_currentDevice->mountPoint();
1601 void
1602 MediaBrowser::connectClicked()
1604 bool haveToConfig = false;
1605 // it was just clicked, so isOn() == true.
1606 if( m_currentDevice && !m_currentDevice->isConnected() )
1608 haveToConfig = !m_currentDevice->connectDevice();
1611 haveToConfig |= !m_currentDevice;
1612 haveToConfig |= ( m_currentDevice && !m_currentDevice->isConnected() );
1614 if ( !m_currentDevice->needsManualConfig() )
1615 haveToConfig = false;
1617 if( haveToConfig && m_devices.at( 0 ) == m_currentDevice )
1619 if( config() && m_currentDevice && !m_currentDevice->isConnected() )
1620 m_currentDevice->connectDevice();
1623 updateDevices();
1624 updateButtons();
1625 updateStats();
1628 void
1629 MediaBrowser::disconnectClicked()
1631 if( m_currentDevice && m_currentDevice->isTransferring() )
1633 int action = KMessageBox::questionYesNoCancel( MediaBrowser::instance(),
1634 i18n( "Transfer in progress. Finish or stop after current track?" ),
1635 i18n( "Stop Transfer?" ),
1636 KGuiItem(i18n("&Finish"), "goto-page"),
1637 KGuiItem(i18n("&Stop"), "media-eject") );
1638 if( action == KMessageBox::Cancel )
1640 return;
1642 else if( action == KMessageBox::Yes )
1644 m_currentDevice->scheduleDisconnect();
1645 return;
1649 transferAction()->setEnabled( false );
1650 disconnectAction()->setEnabled( false );
1652 if( m_currentDevice )
1654 m_currentDevice->disconnectDevice( true );
1657 updateDevices();
1658 updateButtons();
1659 updateStats();
1662 void
1663 MediaBrowser::customClicked()
1665 if( m_currentDevice )
1666 m_currentDevice->customClicked();
1669 void
1670 MediaQueue::save( const QString &path )
1672 QFile file( path );
1674 if( !file.open( QIODevice::WriteOnly ) ) return;
1676 QDomDocument newdoc;
1677 QDomElement transferlist = newdoc.createElement( "playlist" );
1678 transferlist.setAttribute( "product", "Amarok" );
1679 transferlist.setAttribute( "version", APP_VERSION );
1680 newdoc.appendChild( transferlist );
1682 for( const MediaItem *item = static_cast<MediaItem *>( firstChild() );
1683 item;
1684 item = static_cast<MediaItem *>( item->nextSibling() ) )
1686 QDomElement i = newdoc.createElement("item");
1687 i.setAttribute("url", item->url().url());
1689 if( item->meta() )
1691 QDomElement attr = newdoc.createElement( "Title" );
1692 QDomText t = newdoc.createTextNode( item->meta()->name() );
1693 attr.appendChild( t );
1694 i.appendChild( attr );
1695 /* TODO port to meta
1696 attr = newdoc.createElement( "Artist" );
1697 t = newdoc.createTextNode( item->bundle()->artist() );
1698 attr.appendChild( t );
1699 i.appendChild( attr );
1701 attr = newdoc.createElement( "Album" );
1702 t = newdoc.createTextNode( item->bundle()->album() );
1703 attr.appendChild( t );
1704 i.appendChild( attr );
1706 attr = newdoc.createElement( "Year" );
1707 t = newdoc.createTextNode( QString::number( item->bundle()->year() ) );
1708 attr.appendChild( t );
1709 i.appendChild( attr );
1711 attr = newdoc.createElement( "Comment" );
1712 t = newdoc.createTextNode( item->bundle()->comment() );
1713 attr.appendChild( t );
1714 i.appendChild( attr );
1716 attr = newdoc.createElement( "Genre" );
1717 t = newdoc.createTextNode( item->bundle()->genre() );
1718 attr.appendChild( t );
1719 i.appendChild( attr );
1721 attr = newdoc.createElement( "Track" );
1722 t = newdoc.createTextNode( QString::number( item->bundle()->track() ) );
1723 attr.appendChild( t );
1724 i.appendChild( attr );
1728 if(item->type() == MediaItem::PODCASTITEM)
1730 i.setAttribute( "podcast", "1" );
1733 /*TODO if(item->type() == MediaItem::PODCASTITEM
1734 && item->bundle()->podcastBundle())
1736 PodcastEpisodeBundle *peb = item->bundle()->podcastBundle();
1737 QDomElement attr = newdoc.createElement( "PodcastDescription" );
1738 QDomText t = newdoc.createTextNode( peb->description() );
1739 attr.appendChild( t );
1740 i.appendChild( attr );
1742 attr = newdoc.createElement( "PodcastAuthor" );
1743 t = newdoc.createTextNode( peb->author() );
1744 attr.appendChild( t );
1745 i.appendChild( attr );
1747 attr = newdoc.createElement( "PodcastRSS" );
1748 t = newdoc.createTextNode( peb->parent().url() );
1749 attr.appendChild( t );
1750 i.appendChild( attr );
1752 attr = newdoc.createElement( "PodcastURL" );
1753 t = newdoc.createTextNode( peb->url().url() );
1754 attr.appendChild( t );
1755 i.appendChild( attr );
1758 if( !item->m_playlistName.isEmpty() )
1760 i.setAttribute( "playlist", item->m_playlistName );
1763 if(item->type() == MediaItem::PLAYLIST)
1765 i.setAttribute( "playlistdata", item->data() );
1766 if( item->flags() & MediaItem::SmartPlaylist )
1767 i.setAttribute( "smartplaylist", "1" );
1770 transferlist.appendChild( i );
1773 QTextStream stream( &file );
1774 stream.setCodec( QTextCodec::codecForName( "UTF-8" ) );
1775 stream.setAutoDetectUnicode( true );
1776 stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1777 stream << newdoc.toString();
1781 void
1782 MediaQueue::load( const QString& filename )
1784 QFile file( filename );
1785 if( !file.open( QIODevice::ReadOnly ) ) {
1786 return;
1789 clearItems();
1791 QTextStream stream( &file );
1792 stream.setCodec( QTextCodec::codecForName( "UTF-8" ) );
1793 stream.setAutoDetectUnicode( true );
1795 QDomDocument d;
1796 QString er;
1797 int l, c;
1798 if( !d.setContent( stream.readAll(), &er, &l, &c ) ) { // return error values
1799 Amarok::ContextStatusBar::instance()->longMessageThreadSafe( i18n(
1800 //TODO add a link to the path to the playlist
1801 "The XML in the transferlist was invalid. Please report this as a bug to the Amarok "
1802 "developers. Thank you." ), KDE::StatusBar::Error );
1803 error() << "[TRANSFERLISTLOADER]: Error loading xml file: " << filename << "(" << er << ")"
1804 << " at line " << l << ", column " << c;
1805 return;
1808 QList<QDomNode> nodes;
1809 const QString ITEM( "item" ); //so we don't construct this QString all the time
1810 for( QDomNode n = d.namedItem( "playlist" ).firstChild(); !n.isNull(); n = n.nextSibling() )
1812 if( n.nodeName() != ITEM ) continue;
1814 QDomElement elem = n.toElement();
1815 if( !elem.isNull() )
1816 nodes += n;
1818 if( !elem.hasAttribute( "url" ) )
1820 continue;
1822 KUrl url(elem.attribute("url"));
1824 //TODO port to meta
1825 //bool podcast = elem.hasAttribute( "podcast" );
1826 //PodcastEpisodeBundle peb;
1827 //if( url.isLocalFile() )
1828 // peb.setLocalURL( url );
1829 Meta::TrackPtr track = Meta::TrackPtr( new MetaFile::Track( url ) );
1831 for(QDomNode node = elem.firstChild();
1832 !node.isNull();
1833 node = node.nextSibling())
1835 if(node.firstChild().isNull())
1836 continue;
1838 //TODO port to meta
1839 //if(node.nodeName() == "Title" )
1840 // bundle->setTitle(node.firstChild().toText().nodeValue());
1841 //else if(node.nodeName() == "Artist" )
1842 // bundle->setArtist(node.firstChild().toText().nodeValue());
1843 //else if(node.nodeName() == "Album" )
1844 // bundle->setAlbum(node.firstChild().toText().nodeValue());
1845 //else if(node.nodeName() == "Year" )
1846 // bundle->setYear(node.firstChild().toText().nodeValue().toUInt());
1847 //else if(node.nodeName() == "Genre" )
1848 // bundle->setGenre(node.firstChild().toText().nodeValue());
1849 //else if(node.nodeName() == "Comment" )
1850 // bundle->setComment(node.firstChild().toText().nodeValue());
1851 //else if(node.nodeName() == "PodcastDescription" )
1852 // peb.setDescription( node.firstChild().toText().nodeValue() );
1853 //else if(node.nodeName() == "PodcastAuthor" )
1854 // peb.setAuthor( node.firstChild().toText().nodeValue() );
1855 //else if(node.nodeName() == "PodcastRSS" )
1856 // peb.setParent( KUrl( node.firstChild().toText().nodeValue() ) );
1857 //else if(node.nodeName() == "PodcastURL" )
1858 // peb.setUrl( KUrl( node.firstChild().toText().nodeValue() ) );
1861 //TODO port to meta
1862 //if( podcast )
1864 // bundle->setPodcastBundle( peb );
1867 QString playlist = elem.attribute( "playlist" );
1868 QString playlistdata = elem.attribute( "playlistdata" );
1869 if( !playlistdata.isEmpty() )
1871 QString smart = elem.attribute( "smartplaylist" );
1872 if( smart.isEmpty() )
1873 syncPlaylist( playlist, KUrl( playlistdata ), true );
1874 else
1875 syncPlaylist( playlist, playlistdata, true );
1877 else
1878 addUrl( url, track, playlist );
1881 URLsAdded();
1884 MediaQueue::MediaQueue(MediaBrowser *parent)
1885 : K3ListView( parent ), m_parent( parent )
1887 setFixedHeight( 200 );
1888 setSelectionMode( Q3ListView::Extended );
1889 setItemsMovable( true );
1890 setDragEnabled( true );
1891 setShowSortIndicator( false );
1892 setSorting( -1 );
1893 setFullWidth( true );
1894 setRootIsDecorated( false );
1895 setDropVisualizer( true ); //the visualizer (a line marker) is drawn when dragging over tracks
1896 setDropHighlighter( true ); //and the highligther (a focus rect) is drawn when dragging over playlists
1897 setDropVisualizerWidth( 3 );
1898 setAcceptDrops( true );
1899 addColumn( i18n( "Transfer Queue" ) );
1901 itemCountChanged();
1903 KActionCollection* ac = new KActionCollection( this );
1904 KStandardAction::selectAll( this, SLOT( selectAll() ), ac );
1906 connect( this, SIGNAL( contextMenuRequested( Q3ListViewItem*, const QPoint&, int ) ),
1907 SLOT( slotShowContextMenu( Q3ListViewItem*, const QPoint&, int ) ) );
1908 connect( this, SIGNAL( dropped(QDropEvent*, Q3ListViewItem*, Q3ListViewItem*) ),
1909 SLOT( slotDropped(QDropEvent*, Q3ListViewItem*, Q3ListViewItem*) ) );
1912 bool
1913 MediaQueue::acceptDrag( QDropEvent *e ) const
1916 return e->source() == viewport()
1917 || e->mimeData()->hasFormat( "amarok-sql" )
1918 || KUrl::List::canDecode( e->mimeData() );
1921 void
1922 MediaQueue::slotDropped( QDropEvent* e, Q3ListViewItem* parent, Q3ListViewItem* after)
1924 if( e->source() != viewport() )
1926 if( e->mimeData()->hasFormat( "amarok-sql" ) )
1928 QString data( e->mimeData()->data( "amarok-sql" ) );
1929 QString playlist = data.section( "\n", 0, 0 );
1930 QString query = data.section( "\n", 1 );
1931 QStringList values = CollectionDB::instance()->query( query );
1932 KUrl::List list = CollectionDB::instance()->URLsFromSqlDrag( values );
1933 addUrls( list, playlist );
1935 else if ( KUrl::List::canDecode( e->mimeData() ) )
1937 KUrl::List list = KUrl::List::fromMimeData( e->mimeData() );
1938 if (!list.isEmpty() )
1939 addUrls( list );
1942 else if( Q3ListViewItem *i = currentItem() )
1944 moveItem( i, parent, after );
1948 void
1949 MediaQueue::dropProxyEvent( QDropEvent *e )
1951 slotDropped( e, 0, 0 );
1954 MediaItem*
1955 MediaQueue::findPath( QString path )
1957 for( Q3ListViewItem *item = firstChild();
1958 item;
1959 item = item->nextSibling())
1961 if(static_cast<MediaItem *>(item)->url().path() == path)
1962 return static_cast<MediaItem *>(item);
1965 return 0;
1968 void
1969 MediaQueue::computeSize() const
1971 m_totalSize = 0;
1972 for( Q3ListViewItem *it = firstChild();
1974 it = it->nextSibling())
1976 MediaItem *item = static_cast<MediaItem *>(it);
1978 if( item && item->meta() &&
1979 ( !m_parent->currentDevice()
1980 || !m_parent->currentDevice()->isConnected()
1981 || !m_parent->currentDevice()->trackExists(Meta::TrackPtr::dynamicCast(item->meta())) ) )
1982 m_totalSize += ((item->size()+1023)/1024)*1024;
1986 KIO::filesize_t
1987 MediaQueue::totalSize() const
1989 return m_totalSize;
1992 void
1993 MediaQueue::addItemToSize( const MediaItem *item ) const
1995 if( item && item->meta() &&
1996 ( !m_parent->currentDevice()
1997 || !m_parent->currentDevice()->isConnected()
1998 || !m_parent->currentDevice()->trackExists(Meta::TrackPtr::dynamicCast(item->meta())) ) )
1999 m_totalSize += ((item->size()+1023)/1024)*1024;
2002 void
2003 MediaQueue::subtractItemFromSize( const MediaItem *item, bool unconditionally ) const
2005 if( item && item->meta() &&
2006 ( !m_parent->currentDevice()
2007 || !m_parent->currentDevice()->isConnected()
2008 || (unconditionally || !m_parent->currentDevice()->trackExists(Meta::TrackPtr::dynamicCast(item->meta()))) ) )
2009 m_totalSize -= ((item->size()+1023)/1024)*1024;
2012 void
2013 MediaQueue::removeSelected()
2015 QList<Q3ListViewItem*> selected = selectedItems();
2017 QListIterator<Q3ListViewItem*> iter( selected );
2018 while( iter.hasNext() )
2020 Q3ListViewItem *item = iter.next();
2021 if( !(static_cast<MediaItem *>(item)->flags() & MediaItem::Transferring) )
2023 subtractItemFromSize( static_cast<MediaItem *>(item) );
2024 delete item;
2025 if( m_parent->currentDevice() && m_parent->currentDevice()->isTransferring() )
2027 MediaBrowser::instance()->m_progress->setRange( 0, MediaBrowser::instance()->m_progress->maximum() - 1 );
2032 MediaBrowser::instance()->updateStats();
2033 MediaBrowser::instance()->updateButtons();
2034 itemCountChanged();
2037 void
2038 MediaQueue::keyPressEvent( QKeyEvent *e )
2040 if( e->key() == Qt::Key_Delete )
2041 removeSelected();
2042 else
2043 K3ListView::keyPressEvent( e );
2046 void
2047 MediaQueue::itemCountChanged()
2049 if( childCount() == 0 )
2050 hide();
2051 else if( !isShown() )
2052 show();
2055 void
2056 MediaQueue::slotShowContextMenu( Q3ListViewItem* item, const QPoint& point, int )
2058 if( !childCount() )
2059 return;
2061 Q3PopupMenu menu( this );
2063 enum Actions { REMOVE_SELECTED, CLEAR_ALL, START_TRANSFER };
2065 if( item )
2066 menu.insertItem( KIcon( Amarok::icon( "remove_from_playlist" ) ), i18n( "&Remove From Queue" ), REMOVE_SELECTED );
2068 menu.insertItem( KIcon( Amarok::icon( "playlist_clear" ) ), i18n( "&Clear Queue" ), CLEAR_ALL );
2069 menu.insertItem( KIcon( Amarok::icon( "playlist_refresh" ) ), i18n( "&Start Transfer" ), START_TRANSFER );
2070 menu.setItemEnabled( START_TRANSFER,
2071 MediaBrowser::instance()->currentDevice() &&
2072 MediaBrowser::instance()->currentDevice()->isConnected() &&
2073 MediaBrowser::instance()->currentDevice()->m_transfer );
2075 switch( menu.exec( point ) )
2077 case REMOVE_SELECTED:
2078 removeSelected();
2079 break;
2080 case CLEAR_ALL:
2081 clearItems();
2082 break;
2083 case START_TRANSFER:
2084 MediaBrowser::instance()->transferClicked();
2085 break;
2089 void
2090 MediaQueue::clearItems()
2092 clear();
2093 itemCountChanged();
2094 if(m_parent)
2096 computeSize();
2097 m_parent->updateStats();
2098 m_parent->updateButtons();
2103 #include "mediabrowser.moc"