Don't show the Transcoding dialog if FFmpeg is not installed or if it doesn't support...
[amarok:teo.git] / src / browsers / CollectionTreeView.cpp
1 /****************************************************************************************
2  * Copyright (c) 2007 Alexandre Pereira de Oliveira <aleprj@gmail.com>                  *
3  * Copyright (c) 2007 Ian Monroe <ian@monroe.nu>                                        *
4  *                                                                                      *
5  * This program is free software; you can redistribute it and/or modify it under        *
6  * the terms of the GNU General Public License as published by the Free Software        *
7  * Foundation; either version 2 of the License, or (at your option) version 3 or        *
8  * any later version accepted by the membership of KDE e.V. (or its successor approved  *
9  * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of  *
10  * version 3 of the license.                                                            *
11  *                                                                                      *
12  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
13  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
14  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
15  *                                                                                      *
16  * You should have received a copy of the GNU General Public License along with         *
17  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
18  ****************************************************************************************/
19
20 #define DEBUG_PREFIX "CollectionTreeView"
21 #include "core/support/Debug.h"
22
23 #include "CollectionTreeView.h"
24
25 #include "core/support/Amarok.h"
26 #include "AmarokMimeData.h"
27 #include "core/collections/CollectionLocation.h"
28 #include "core-impl/collections/support/CollectionManager.h"
29 #include "browsers/CollectionTreeItem.h"
30 #include "browsers/CollectionTreeItemModel.h"
31 #include "context/ContextView.h"
32 #include "GlobalCollectionActions.h"
33 #include "core/meta/Meta.h"
34 #include "core/collections/MetaQueryMaker.h"
35 #include "core/capabilities/BookmarkThisCapability.h"
36 #include "core/capabilities/CollectionCapability.h"
37 #include "core/capabilities/CustomActionsCapability.h"
38 #include "PaletteHandler.h"
39 #include "playlist/PlaylistModelStack.h"
40 #include "PopupDropperFactory.h"
41 #include "context/popupdropper/libpud/PopupDropper.h"
42 #include "context/popupdropper/libpud/PopupDropperItem.h"
43 #include "core/collections/QueryMaker.h"
44 #include "SvgHandler.h"
45 #include "TagDialog.h"
46 #include "transcoding/TranscodingAssistantDialog.h"
47 #include "src/core/transcoding/TranscodingController.h"
48
49 #include <QContextMenuEvent>
50 #include <QHash>
51 #include <QMouseEvent>
52 #include <QSet>
53
54 #include <KGlobalSettings>
55 #include <KIcon>
56 #include <KComboBox>
57 #include <KMenu>
58 #include <KMessageBox> // NOTE: for delete dialog, will move to CollectionCapability later
59
60 CollectionTreeView::CollectionTreeView( QWidget *parent)
61     : Amarok::PrettyTreeView( parent )
62     , m_filterModel( 0 )
63     , m_treeModel( 0 )
64     , m_pd( 0 )
65     , m_appendAction( 0 )
66     , m_loadAction( 0 )
67     , m_editAction( 0 )
68     , m_organizeAction( 0 )
69     , m_dragMutex()
70     , m_ongoingDrag( false )
71     , m_expandToggledWhenPressed( false )
72 {
73     setSortingEnabled( true );
74     setFocusPolicy( Qt::StrongFocus );
75     sortByColumn( 0, Qt::AscendingOrder );
76     setSelectionMode( QAbstractItemView::ExtendedSelection );
77     setSelectionBehavior( QAbstractItemView::SelectRows );
78 #ifdef Q_WS_MAC
79     setVerticalScrollMode( QAbstractItemView::ScrollPerItem ); // for some bizarre reason w/ some styles on mac
80     setHorizontalScrollMode( QAbstractItemView::ScrollPerItem ); // per-pixel scrolling is slower than per-item
81 #else
82     setVerticalScrollMode( QAbstractItemView::ScrollPerPixel ); // Scrolling per item is really not smooth and looks terrible
83     setHorizontalScrollMode( QAbstractItemView::ScrollPerPixel ); // Scrolling per item is really not smooth and looks terrible
84 #endif
85
86     setDragDropMode( QAbstractItemView::DragOnly ); // implement drop when time allows
87
88     if( KGlobalSettings::graphicEffectsLevel() != KGlobalSettings::NoEffects )
89         setAnimated( true );
90
91     connect( this, SIGNAL( collapsed( const QModelIndex & ) ), SLOT( slotCollapsed( const QModelIndex & ) ) );
92     connect( this, SIGNAL( expanded( const QModelIndex & ) ), SLOT( slotExpanded( const QModelIndex & ) ) );
93 }
94
95 void CollectionTreeView::setModel( QAbstractItemModel * model )
96 {
97     m_treeModel = qobject_cast<CollectionTreeItemModelBase*>( model );
98     if( !m_treeModel )
99         return;
100
101     m_filterTimer.setSingleShot( true );
102     connect( &m_filterTimer, SIGNAL( timeout() ), m_treeModel, SLOT( slotFilter() ) );
103     connect( m_treeModel, SIGNAL( allQueriesFinished() ), SLOT( slotCheckAutoExpand() ));
104
105     m_filterModel = new CollectionSortFilterProxyModel( this );
106     m_filterModel->setSortRole( CustomRoles::SortRole );
107     m_filterModel->setFilterRole( CustomRoles::FilterRole );
108     m_filterModel->setSortCaseSensitivity( Qt::CaseInsensitive );
109     m_filterModel->setFilterCaseSensitivity( Qt::CaseInsensitive );
110     m_filterModel->setSourceModel( model );
111     m_filterModel->setDynamicSortFilter( true );
112
113     QTreeView::setModel( m_filterModel );
114
115     QTimer::singleShot( 0, this, SLOT( slotCheckAutoExpand() ) );
116 }
117
118 CollectionTreeView::~CollectionTreeView()
119 {
120     DEBUG_BLOCK
121
122     delete m_treeModel;
123     delete m_filterModel;
124 }
125
126 void
127 CollectionTreeView::setLevels( const QList<int> &levels )
128 {
129     if( m_treeModel )
130         m_treeModel->setLevels( levels );
131 }
132
133 QList< int > CollectionTreeView::levels() const
134 {
135     if( m_treeModel )
136         return m_treeModel->levels();
137     return QList<int>();
138 }
139
140 void
141 CollectionTreeView::setLevel( int level, int type )
142 {
143     if( !m_treeModel )
144         return;
145     QList<int> levels = m_treeModel->levels();
146     if ( type == CategoryId::None )
147     {
148         while( levels.count() >= level )
149             levels.removeLast();
150     }
151     else
152     {
153         levels.removeAll( type );
154         levels[level] = type;
155     }
156     setLevels( levels );
157 }
158
159 QSortFilterProxyModel*
160 CollectionTreeView::filterModel() const
161 {
162     return m_filterModel;
163 }
164
165 void
166 CollectionTreeView::contextMenuEvent( QContextMenuEvent* event )
167 {
168     if( !m_treeModel )
169         return;
170
171     QModelIndex index = indexAt( event->pos() );
172     if( !index.isValid() )
173     {
174         Amarok::PrettyTreeView::contextMenuEvent( event );
175         return;
176     }
177
178     QAction separator( this );
179     separator.setSeparator( true );
180
181     QModelIndexList indices = selectedIndexes();
182
183     // if previously selected indices do not contain the index of the item
184     // currently under the mouse when context menu is invoked.
185     if( !indices.contains( index ) )
186     {
187         indices.clear();
188         indices << index;
189         setCurrentIndex( index );
190     }
191
192     if( m_filterModel )
193     {
194         QModelIndexList tmp;
195         foreach( const QModelIndex &idx, indices )
196         {
197             tmp.append( m_filterModel->mapToSource( idx ) );
198         }
199         indices = tmp;
200     }
201
202     // Abort if nothing is selected
203     if( indices.isEmpty() )
204         return;
205
206     m_currentItems.clear();
207     foreach( const QModelIndex &index, indices )
208     {
209         if( index.isValid() && index.internalPointer() )
210             m_currentItems.insert( static_cast<CollectionTreeItem*>( index.internalPointer() ) );
211     }
212
213     QActionList actions = createBasicActions( indices );
214     actions += &separator;
215     actions += createExtendedActions( indices );
216
217     KMenu menu( this );
218
219     // Destroy the menu when the model is reset (collection update), so that we don't operate on invalid data.
220     // see BUG 190056
221     connect( m_treeModel, SIGNAL( modelReset() ), &menu, SLOT( deleteLater() ) );
222
223     foreach( QAction *action, actions )
224         menu.addAction( action );
225
226     QActionList customActions = createCustomActions( indices );
227     KMenu menuCustom( i18n( "Album" )  );
228     if( customActions.count() > 1 )
229     {
230         menuCustom.addActions( customActions );
231         menuCustom.setIcon( KIcon( "filename-album-amarok" ) );
232         menu.addMenu( &menuCustom );
233         menu.addSeparator();
234     }
235     else if( customActions.count() == 1 )
236     {
237         menu.addActions( customActions );
238     }
239
240     QActionList collectionActions = createCollectionActions( indices );
241     KMenu menuCollection( i18n( "Collection" ) );
242     if( collectionActions.count() > 1 )
243     {
244         menuCollection.setIcon( KIcon( "collection-amarok" ) );
245         menuCollection.addActions( collectionActions );
246         menu.addMenu( &menuCollection );
247         menu.addSeparator();
248     }
249     else if( collectionActions.count() == 1 )
250     {
251         menu.addActions( collectionActions );
252     }
253
254     m_currentCopyDestination =   getCopyActions(   indices );
255     m_currentMoveDestination =   getMoveActions(   indices );
256     m_currentRemoveDestination = getRemoveActions( indices );
257
258     KMenu copyMenu( i18n( "Copy to Collection" ) );
259     if( !m_currentCopyDestination.empty() )
260     {
261         copyMenu.setIcon( KIcon( "edit-copy" ) );
262         copyMenu.addActions( m_currentCopyDestination.keys() );
263         menu.addMenu( &copyMenu );
264     }
265
266     KMenu moveMenu( i18n( "Move to Collection" ) );
267     if( !m_currentMoveDestination.empty() )
268     {
269         // moveMenu.setIcon(); // TODO: no move icon
270         moveMenu.addActions( m_currentMoveDestination.keys() );
271         menu.addMenu( &moveMenu );
272     }
273
274     if( !m_currentRemoveDestination.empty() )
275     {
276         menu.addActions( m_currentRemoveDestination.keys() );
277     }
278
279     menu.exec( event->globalPos() );
280 }
281
282 void CollectionTreeView::mouseDoubleClickEvent( QMouseEvent *event )
283 {
284     if( event->button() == Qt::MidButton )
285     {
286         event->accept();
287         return;
288     }
289
290     QModelIndex index = indexAt( event->pos() );
291     if( !index.isValid() )
292     {
293         event->accept();
294         return;
295     }
296
297     if( event->button() != Amarok::contextMouseButton() && event->modifiers() == Qt::NoModifier )
298     {
299         CollectionTreeItem *item = getItemFromIndex( index );
300         playChildTracks( item, Playlist::AppendAndPlay );
301     }
302     event->accept();
303 }
304
305 void CollectionTreeView::mousePressEvent( QMouseEvent *event )
306 {
307     const QModelIndex index = indexAt( event->pos() );
308     if( !index.isValid() )
309     {
310         event->accept();
311         return;
312     }
313
314     bool prevExpandState = isExpanded( index );
315
316     // This will toggle the expansion of the current item when clicking
317     // on the fold marker but not on the item itself. Required here to
318     // enable dragging.
319     Amarok::PrettyTreeView::mousePressEvent( event );
320
321     m_expandToggledWhenPressed = ( prevExpandState != isExpanded(index) );
322 }
323
324 void CollectionTreeView::mouseReleaseEvent( QMouseEvent *event )
325 {
326     if( m_pd )
327     {
328         connect( m_pd, SIGNAL( fadeHideFinished() ), m_pd, SLOT( deleteLater() ) );
329         m_pd->hide();
330         m_pd = 0;
331     }
332
333     QModelIndex index = indexAt( event->pos() );
334     if( !index.isValid() )
335     {
336         event->accept();
337         return;
338     }
339
340     if( event->button() == Qt::MidButton )
341     {
342         CollectionTreeItem *item = getItemFromIndex( index );
343         playChildTracks( item, Playlist::AppendAndPlay );
344         event->accept();
345         return;
346     }
347
348     if( !m_expandToggledWhenPressed &&
349         event->button() != Amarok::contextMouseButton() &&
350         event->modifiers() == Qt::NoModifier &&
351         KGlobalSettings::singleClick() &&
352         model()->hasChildren( index ) )
353     {
354         m_expandToggledWhenPressed = !m_expandToggledWhenPressed;
355         setExpanded( index, !isExpanded( index ) );
356         event->accept();
357         return;
358     }
359     Amarok::PrettyTreeView::mouseReleaseEvent( event );
360 }
361
362 void CollectionTreeView::mouseMoveEvent( QMouseEvent *event )
363 {
364     // pass event to parent widget
365     if( event->buttons() || event->modifiers() )
366     {
367         Amarok::PrettyTreeView::mouseMoveEvent( event );
368         return;
369     }
370     event->accept();
371 }
372
373 CollectionTreeItem* CollectionTreeView::getItemFromIndex( QModelIndex &index )
374 {
375     QModelIndex filteredIndex;
376     if( m_filterModel )
377         filteredIndex = m_filterModel->mapToSource( index );
378     else
379         filteredIndex = index;
380
381     if( !filteredIndex.isValid() )
382     {
383         return 0;
384     }
385
386     return static_cast<CollectionTreeItem*>( filteredIndex.internalPointer() );
387 }
388
389 void CollectionTreeView::keyPressEvent( QKeyEvent *event )
390 {
391     QModelIndexList indices = selectedIndexes();
392     if( indices.isEmpty() )
393     {
394         QTreeView::keyPressEvent( event );
395         return;
396     }
397
398     if( m_filterModel )
399     {
400         QModelIndexList tmp;
401         foreach( const QModelIndex &idx, indices )
402             tmp.append( m_filterModel->mapToSource( idx ) );
403         indices = tmp;
404     }
405
406     m_currentItems.clear();
407     foreach( const QModelIndex &index, indices )
408     {
409         if( index.isValid() && index.internalPointer() )
410             m_currentItems.insert( static_cast<CollectionTreeItem*>( index.internalPointer() ) );
411     }
412
413     QModelIndex current = currentIndex();
414     switch( event->key() )
415     {
416         case Qt::Key_Enter:
417         case Qt::Key_Return:
418             slotAppendChildTracks();
419             return;
420         case Qt::Key_Up:
421             if( current.parent() == QModelIndex() && current.row() == 0 )
422             {
423                 emit leavingTree();
424                 return;
425             }
426             break;
427         case Qt::Key_Down:
428             break;
429         // L and R should magically work when we get a patched version of qt
430         case Qt::Key_Right:
431         case Qt::Key_Direction_R:
432             expand( current );
433             return;
434         case Qt::Key_Left:
435         case Qt::Key_Direction_L:
436             collapse( current );
437             return;
438         default:
439             break;
440     }
441     QTreeView::keyPressEvent( event );
442 }
443
444 void
445 CollectionTreeView::startDrag(Qt::DropActions supportedActions)
446 {
447     DEBUG_BLOCK
448
449     //setSelectionMode( QAbstractItemView::NoSelection );
450
451     // When a parent item is dragged, startDrag() is called a bunch of times. Here we prevent that:
452     m_dragMutex.lock();
453     if( m_ongoingDrag )
454     {
455         m_dragMutex.unlock();
456         return;
457     }
458     m_ongoingDrag = true;
459     m_dragMutex.unlock();
460
461     if( !m_pd )
462         m_pd = The::popupDropperFactory()->createPopupDropper( Context::ContextView::self() );
463
464     if( m_pd && m_pd->isHidden() )
465     {
466         QModelIndexList indices = selectedIndexes();
467         if( m_filterModel )
468         {
469             QModelIndexList tmp;
470             foreach( const QModelIndex &idx, indices )
471             {
472                 tmp.append( m_filterModel->mapToSource( idx ) );
473             }
474             indices = tmp;
475         }
476
477         QActionList actions = createBasicActions( indices );
478
479         QFont font;
480         font.setPointSize( 16 );
481         font.setBold( true );
482
483         foreach( QAction * action, actions )
484             m_pd->addItem( The::popupDropperFactory()->createItem( action ) );
485
486         m_currentCopyDestination = getCopyActions( indices );
487         m_currentMoveDestination = getMoveActions( indices );
488
489         m_currentItems.clear();
490         foreach( const QModelIndex &index, indices )
491         {
492             if( index.isValid() && index.internalPointer() )
493                 m_currentItems.insert( static_cast<CollectionTreeItem*>( index.internalPointer() ) );
494         }
495
496         PopupDropperItem* subItem;
497
498         actions = createExtendedActions( indices );
499
500         PopupDropper * morePud = 0;
501         if( actions.count() > 1 )
502         {
503             morePud = The::popupDropperFactory()->createPopupDropper( 0, true );
504
505             foreach( QAction * action, actions )
506                 morePud->addItem( The::popupDropperFactory()->createItem( action ) );
507         }
508         else
509             m_pd->addItem( The::popupDropperFactory()->createItem( actions[0] ) );
510
511         //TODO: Keep bugging i18n team about problems with 3 dots
512         if ( actions.count() > 1 )
513         {
514             subItem = m_pd->addSubmenu( &morePud, i18n( "More..." )  );
515             The::popupDropperFactory()->adjustItem( subItem );
516         }
517
518         m_pd->show();
519     }
520
521     QTreeView::startDrag( supportedActions );
522     debug() << "After the drag!";
523
524     if( m_pd )
525     {
526         debug() << "clearing PUD";
527         connect( m_pd, SIGNAL( fadeHideFinished() ), m_pd, SLOT( clear() ) );
528         m_pd->hide();
529     }
530
531     m_dragMutex.lock();
532     m_ongoingDrag = false;
533     m_dragMutex.unlock();
534 }
535
536 void CollectionTreeView::selectionChanged(const QItemSelection & selected, const QItemSelection & deselected)
537 {
538     QModelIndexList indexes = selected.indexes();
539
540     QModelIndexList changedIndexes = indexes;
541     changedIndexes << deselected.indexes();
542     foreach( const QModelIndex &index, changedIndexes )
543         update( index );
544
545     if ( indexes.count() < 1 )
546         return;
547
548     QModelIndex index;
549     if ( m_filterModel )
550         index = m_filterModel->mapToSource( indexes[0] );
551     else
552         index = indexes[0];
553
554     CollectionTreeItem * item = static_cast<CollectionTreeItem *>( index.internalPointer() );
555     emit( itemSelected ( item ) );
556 }
557
558 void
559 CollectionTreeView::slotSetFilterTimeout()
560 {
561     KComboBox *comboBox = qobject_cast<KComboBox*>( sender() );
562     if( comboBox )
563     {
564         if( m_treeModel )
565             m_treeModel->setCurrentFilter( comboBox->currentText() );
566         m_filterTimer.stop();
567         m_filterTimer.start( 500 );
568     }
569 }
570
571 void
572 CollectionTreeView::slotCollapsed( const QModelIndex &index )
573 {
574     if( !m_treeModel )
575         return;
576     if( m_filterModel )
577         m_treeModel->slotCollapsed( m_filterModel->mapToSource( index ) );
578     else
579         m_treeModel->slotCollapsed( index );
580 }
581
582 void
583 CollectionTreeView::slotExpanded( const QModelIndex &index )
584 {
585     if( !m_treeModel )
586         return;
587     if( m_filterModel )
588         m_treeModel->slotExpanded( m_filterModel->mapToSource( index ));
589     else
590         m_treeModel->slotExpanded( index );
591 }
592
593 void
594 CollectionTreeView::slotCheckAutoExpand()
595 {
596     if( m_filterModel )
597     {
598         // Cases where root is not collections but
599         // for example magnatunes's plethora of genres, don't expand by default
600         if( m_filterModel->rowCount() > 6 )
601             return;
602
603         QModelIndexList indicesToCheck;
604         for( int i = 0; i < m_filterModel->rowCount(); i++ ) //need something to start for'ing with
605             indicesToCheck += m_filterModel->index( i, 0 ); //lowest level is fine for that
606
607         QModelIndex current;
608         for( int j = 0; j < indicesToCheck.size(); j++)
609         {
610             current = indicesToCheck.at( j );
611             if( m_filterModel->rowCount( current ) < 4 )
612             { //don't expand if many results
613                 expand( current );
614                 for( int i = 0; i < m_filterModel->rowCount( current ); i++ )
615                     indicesToCheck += m_filterModel->index( i, 0, current );
616             }
617         }
618     }
619 }
620
621 void
622 CollectionTreeView::playChildTracks( CollectionTreeItem *item, Playlist::AddOptions insertMode)
623 {
624     QSet<CollectionTreeItem*> items;
625     items.insert( item );
626
627     playChildTracks( items, insertMode );
628 }
629
630 void
631 CollectionTreeView::playChildTracks( const QSet<CollectionTreeItem*> &items, Playlist::AddOptions insertMode )
632 {
633     if( !m_treeModel )
634         return;
635     //Ensure that if a parent and child are both selected we ignore the child
636     QSet<CollectionTreeItem*> parents( cleanItemSet( items ) );
637
638     //Store the type of playlist insert to be done and cause a slot to be invoked when the tracklist has been generated.
639     AmarokMimeData *mime = dynamic_cast<AmarokMimeData*>( m_treeModel->mimeData( QList<CollectionTreeItem*>::fromSet( parents ) ) );
640     m_playChildTracksMode.insert( mime, insertMode );
641     connect( mime, SIGNAL( trackListSignal( Meta::TrackList ) ), this, SLOT( playChildTracksSlot( Meta::TrackList) ) );
642     mime->getTrackListSignal();
643 }
644
645 void
646 CollectionTreeView::playChildTracksSlot( Meta::TrackList list ) //slot
647 {
648     AmarokMimeData *mime = dynamic_cast<AmarokMimeData*>( sender() );
649
650     Playlist::AddOptions insertMode = m_playChildTracksMode.take( mime );
651
652     qStableSort( list.begin(), list.end(), Meta::Track::lessThan );
653     The::playlistController()->insertOptioned( list, insertMode );
654
655     mime->deleteLater();
656 }
657
658 void
659 CollectionTreeView::organizeTracks( const QSet<CollectionTreeItem*> &items ) const
660 {
661     DEBUG_BLOCK
662     if( !items.count() )
663         return;
664
665     //Create query based upon items, ensuring that if a parent and child are both selected we ignore the child
666     Collections::QueryMaker *qm = createMetaQueryFromItems( items, true );
667     if( !qm )
668         return;
669
670     CollectionTreeItem *item = items.toList().first();
671     while( item->isDataItem() )
672         item = item->parent();
673
674     Collections::Collection *coll = item->parentCollection();
675     Collections::CollectionLocation *location = coll->location();
676     if( !location->isOrganizable() )
677     {
678         debug() << "Collection not organizable";
679         //how did we get here??
680         delete location;
681         delete qm;
682         return;
683     }
684     location->prepareMove( qm, coll->location() );
685 }
686
687 void
688 CollectionTreeView::copyTracks( const QSet<CollectionTreeItem*> &items, Collections::Collection *destination,
689                                 bool removeSources, Transcoding::Configuration configuration ) const
690 {
691     DEBUG_BLOCK
692     if( !destination->isWritable() )
693     {
694         warning() << "collection " << destination->prettyName() << " is not writable! Aborting";
695         return;
696     }
697     //copied from organizeTracks. create a method for this somewhere
698     if( !items.count() )
699     {
700         warning() << "No items to copy! Aborting";
701         return;
702     }
703
704     //Create query based upon items, ensuring that if a parent and child are both selected we ignore the child
705     Collections::QueryMaker *qm = createMetaQueryFromItems( items, true );
706     if( !qm )
707     {
708         warning() << "could not get qm!";
709         return;
710     }
711
712     CollectionTreeItem *item = items.toList().first();
713     while( item->isDataItem() )
714     {
715         item = item->parent();
716     }
717     Collections::Collection *coll = item->parentCollection();
718     Collections::CollectionLocation *source = coll->location();
719     Collections::CollectionLocation *dest = destination->location();
720     if( removeSources )
721     {
722         if( !source->isWritable() ) //error
723         {
724             warning() << "We can not write to ze source!!! OMGooses!";
725             delete dest;
726             delete source;
727             delete qm;
728             return;
729         }
730
731         debug() << "starting source->prepareMove";
732         source->prepareMove( qm, dest );
733     }
734     else
735     {
736         debug() << "starting source->prepareCopy";
737         source->prepareCopy( qm, dest, configuration );
738     }
739 }
740
741 void
742 CollectionTreeView::removeTracks( const QSet<CollectionTreeItem*> &items ) const
743 {
744     DEBUG_BLOCK
745
746     //copied from organizeTracks. create a method for this somewhere
747     if( !items.count() )
748         return;
749
750     //Create query based upon items, ensuring that if a parent and child are both selected we ignore the child
751     Collections::QueryMaker *qm = createMetaQueryFromItems( items, true );
752     if( !qm )
753         return;
754
755     CollectionTreeItem *item = items.toList().first();
756     while( item->isDataItem() )
757         item = item->parent();
758
759     Collections::Collection *coll = item->parentCollection();
760
761     if( !coll->isWritable() )
762         return;
763
764     Collections::CollectionLocation *source = coll->location();
765
766     if( !source->isWritable() ) //error
767     {
768         warning() << "We can not write to ze source!!! OMGooses!";
769         delete source;
770         delete qm;
771         return;
772     }
773     source->prepareRemove( qm );
774 }
775
776 void
777 CollectionTreeView::editTracks( const QSet<CollectionTreeItem*> &items ) const
778 {
779     //Create query based upon items, ensuring that if a parent and child are both selected we ignore the child
780     Collections::QueryMaker *qm = createMetaQueryFromItems( items, true );
781     if( !qm )
782         return;
783
784     (void)new TagDialog( qm ); //the dialog will show itself automatically as soon as it is ready
785 }
786
787 void CollectionTreeView::slotFilterNow()
788 {
789     if( m_treeModel )
790         m_treeModel->slotFilter();
791     setFocus( Qt::OtherFocusReason );
792 }
793
794 QActionList CollectionTreeView::createBasicActions( const QModelIndexList & indices )
795 {
796     QActionList actions;
797
798     if( !indices.isEmpty() )
799     {
800         if( m_appendAction == 0 )
801         {
802             m_appendAction = new QAction( KIcon( "media-track-add-amarok" ), i18n( "&Add to Playlist" ), this );
803             m_appendAction->setProperty( "popupdropper_svg_id", "append" );
804             connect( m_appendAction, SIGNAL( triggered() ), this, SLOT( slotAppendChildTracks() ) );
805         }
806
807         actions.append( m_appendAction );
808
809         if( m_loadAction == 0 )
810         {
811             m_loadAction = new QAction( i18nc( "Replace the currently loaded tracks with these", "&Replace Playlist" ), this );
812             m_loadAction->setProperty( "popupdropper_svg_id", "load" );
813             connect( m_loadAction, SIGNAL( triggered() ), this, SLOT( slotPlayChildTracks() ) );
814         }
815
816         actions.append( m_loadAction );
817     }
818
819     return actions;
820 }
821
822 QActionList CollectionTreeView::createExtendedActions( const QModelIndexList & indices )
823 {
824     QActionList actions;
825
826     if( !indices.isEmpty() )
827     {
828         if ( m_editAction == 0 )
829         {
830             m_editAction = new QAction( KIcon( "media-track-edit-amarok" ), i18n( "&Edit Track Details" ), this );
831             setProperty( "popupdropper_svg_id", "edit" );
832             connect( m_editAction, SIGNAL( triggered() ), this, SLOT( slotEditTracks() ) );
833         }
834         actions.append( m_editAction );
835
836         {   //keep the scope of item minimal
837             CollectionTreeItem *item = static_cast<CollectionTreeItem*>( indices.first().internalPointer() );
838             while( item->isDataItem() )
839             {
840                 item = item->parent();
841             }
842
843             Collections::Collection *collection = item->parentCollection();
844             const Collections::CollectionLocation* location = collection->location();
845
846             if( location->isOrganizable() )
847             {
848                 bool onlyOneCollection = true;
849                 foreach( const QModelIndex &index, indices )
850                 {
851                     Q_UNUSED( index )
852                     CollectionTreeItem *item = static_cast<CollectionTreeItem*>( indices.first().internalPointer() );
853                     while( item->isDataItem() )
854                         item = item->parent();
855
856                     onlyOneCollection = item->parentCollection() == collection;
857                     if( !onlyOneCollection )
858                         break;
859                 }
860
861                 if( onlyOneCollection )
862                 {
863                     if ( m_organizeAction == 0 )
864                     {
865                         m_organizeAction = new QAction( KIcon("folder-open" ), i18nc( "Organize Files", "Organize Files" ), this );
866                         m_organizeAction->setProperty( "popupdropper_svg_id", "organize" );
867                         connect( m_organizeAction, SIGNAL( triggered() ), this, SLOT( slotOrganize() ) );
868                     }
869                     actions.append( m_organizeAction );
870                 }
871             }
872             delete location;
873         }
874
875
876         //hmmm... figure out what kind of item we are dealing with....
877
878         if ( indices.size() == 1 )
879         {
880             debug() << "checking for global actions";
881             CollectionTreeItem *item = static_cast<CollectionTreeItem*>( indices.first().internalPointer() );
882
883             QActionList gActions = The::globalCollectionActions()->actionsFor( item->data() );
884             foreach( QAction *action, gActions )
885             {
886                 if( action ) // Can become 0-pointer, see http://bugs.kde.org/show_bug.cgi?id=183250
887                 {
888                     actions.append( action );
889                     debug() << "Got global action: " << action->text();
890                 }
891             }
892         }
893     }
894     else
895         debug() << "invalid index or null internalPointer";
896
897     return actions;
898 }
899
900 QActionList
901 CollectionTreeView::createCustomActions( const QModelIndexList &indices )
902 {
903     QActionList actions;
904     if( indices.count() == 1 )
905     {
906         if( indices.first().isValid() && indices.first().internalPointer() )
907         {
908             Meta::DataPtr data = static_cast<CollectionTreeItem*>( indices.first().internalPointer() )->data();
909             if( data )
910             {
911                 Capabilities::CustomActionsCapability *cac = data->create<Capabilities::CustomActionsCapability>();
912                 if( cac )
913                 {
914                     QActionList cActions = cac->customActions();
915
916                     foreach( QAction *action, cActions )
917                     {
918                         if( action )
919                         {
920                             actions.append( action );
921                             debug() << "Got custom action: " << action->text();
922                         }
923                     }
924                     delete cac;
925                 }
926                 //check if this item can be bookmarked...
927                 Capabilities::BookmarkThisCapability *btc = data->create<Capabilities::BookmarkThisCapability>();
928                 if( btc )
929                 {
930                     if( btc->isBookmarkable() ) {
931
932                         QAction *bookmarAction = btc->bookmarkAction();
933                         if ( bookmarAction )
934                             actions.append( bookmarAction );
935                     }
936                     delete btc;
937                 }
938             }
939         }
940     }
941     return actions;
942 }
943
944 QActionList
945 CollectionTreeView::createCollectionActions( const QModelIndexList & indices )
946 {
947     QActionList actions;
948     // Extract collection whose constituent was selected
949
950     CollectionTreeItem *item = static_cast<CollectionTreeItem*>( indices.first().internalPointer() );
951
952     // Don't return any collection actions for non collection items
953     if( item->isDataItem() )
954         return actions;
955
956     Collections::Collection *collection = item->parentCollection();
957
958     // Generate CollectionCapability, test for existence
959
960     Capabilities::CollectionCapability *cc = collection->create<Capabilities::CollectionCapability>();
961
962     if( cc )
963     {
964         actions = cc->collectionActions();
965         delete cc;
966     }
967
968     return actions;
969 }
970
971
972 QHash<QAction*, Collections::Collection*> CollectionTreeView::getCopyActions(const QModelIndexList & indices )
973 {
974     QHash<QAction*, Collections::Collection*> currentCopyDestination;
975
976     if( onlyOneCollection( indices ) )
977     {
978         Collections::Collection *collection = getCollection( indices.first() );
979         QList<Collections::Collection*> writableCollections;
980         QHash<Collections::Collection*, CollectionManager::CollectionStatus> hash = CollectionManager::instance()->collections();
981         QHash<Collections::Collection*, CollectionManager::CollectionStatus>::const_iterator it = hash.constBegin();
982         while( it != hash.constEnd() )
983         {
984             Collections::Collection *coll = it.key();
985             if( coll && coll->isWritable() && coll != collection )
986                 writableCollections.append( coll );
987             ++it;
988         }
989         if( !writableCollections.isEmpty() )
990         {
991             foreach( Collections::Collection *coll, writableCollections )
992             {
993                 QAction *action = new QAction( coll->icon(), coll->prettyName(), 0 );
994                 action->setProperty( "popupdropper_svg_id", "collection" );
995                 connect( action, SIGNAL( triggered() ), this, SLOT( slotCopyTracks() ) );
996
997                 currentCopyDestination.insert( action, coll );
998             }
999         }
1000     }
1001     return currentCopyDestination;
1002 }
1003
1004 QHash<QAction*, Collections::Collection*> CollectionTreeView::getMoveActions( const QModelIndexList & indices )
1005 {
1006     QHash<QAction*, Collections::Collection*> currentMoveDestination;
1007
1008     if( onlyOneCollection( indices) )
1009     {
1010         Collections::Collection *collection = getCollection( indices.first() );
1011         QList<Collections::Collection*> writableCollections;
1012         QHash<Collections::Collection*, CollectionManager::CollectionStatus> hash = CollectionManager::instance()->collections();
1013         QHash<Collections::Collection*, CollectionManager::CollectionStatus>::const_iterator it = hash.constBegin();
1014         while( it != hash.constEnd() )
1015         {
1016             Collections::Collection *coll = it.key();
1017             if( coll && coll->isWritable() && coll != collection )
1018                 writableCollections.append( coll );
1019             ++it;
1020         }
1021         if( !writableCollections.isEmpty() )
1022         {
1023             if( collection->isWritable() )
1024             {
1025                 foreach( Collections::Collection *coll, writableCollections )
1026                 {
1027                     QAction *action = new QAction( coll->icon(), coll->prettyName(), 0 );
1028                     action->setProperty( "popupdropper_svg_id", "collection" );
1029                     connect( action, SIGNAL( triggered() ), this, SLOT( slotMoveTracks() ) );
1030                     currentMoveDestination.insert( action, coll );
1031                 }
1032             }
1033         }
1034     }
1035     return currentMoveDestination;
1036 }
1037
1038 QHash<QAction*, Collections::Collection*> CollectionTreeView::getRemoveActions( const QModelIndexList & indices )
1039 {
1040     QHash<QAction*, Collections::Collection*> currentRemoveDestination;
1041
1042     if( onlyOneCollection( indices) )
1043     {
1044         Collections::Collection *collection = getCollection( indices.first() );
1045         if( collection && collection->isWritable() )
1046         {
1047             //writableCollections.append( collection );
1048             QAction *action = new QAction( KIcon( "remove-amarok" ), i18n( "Delete Tracks" ), 0 );
1049             action->setProperty( "popupdropper_svg_id", "delete" );
1050
1051             connect( action, SIGNAL( triggered() ), this, SLOT( slotRemoveTracks() ) );
1052
1053             currentRemoveDestination.insert( action, collection );
1054         }
1055
1056
1057     }
1058     return currentRemoveDestination;
1059 }
1060
1061 bool CollectionTreeView::onlyOneCollection( const QModelIndexList & indices )
1062 {
1063     DEBUG_BLOCK
1064
1065     if( !indices.isEmpty() )
1066     {
1067         Collections::Collection *collection = getCollection( indices.first() );
1068         foreach( const QModelIndex &index, indices )
1069         {
1070             Collections::Collection *currentCollection = getCollection( index );
1071             if( collection != currentCollection )
1072                 return false;
1073         }
1074     }
1075
1076     return true;
1077 }
1078
1079 Collections::Collection * CollectionTreeView::getCollection( const QModelIndex & index )
1080 {
1081     Collections::Collection *collection = 0;
1082     if( index.isValid() )
1083     {
1084         CollectionTreeItem *item = static_cast<CollectionTreeItem*>( index.internalPointer() );
1085         while( item->isDataItem() )
1086         {
1087             item = item->parent();
1088         }
1089         collection = item->parentCollection();
1090     }
1091
1092     return collection;
1093 }
1094
1095 void CollectionTreeView::slotPlayChildTracks()
1096 {
1097     playChildTracks( m_currentItems, Playlist::LoadAndPlay );
1098 }
1099
1100 void CollectionTreeView::slotAppendChildTracks()
1101 {
1102     playChildTracks( m_currentItems, Playlist::AppendAndPlay );
1103 }
1104
1105 void CollectionTreeView::slotQueueChildTracks()
1106 {
1107     playChildTracks( m_currentItems, Playlist::Queue );
1108 }
1109
1110 void CollectionTreeView::slotEditTracks()
1111 {
1112     editTracks( m_currentItems );
1113 }
1114
1115 void CollectionTreeView::slotCopyTracks()
1116 {
1117     if( sender() )
1118     {
1119         if( QAction * action = dynamic_cast<QAction *>( sender() ) )
1120         {
1121             Transcoding::Configuration configuration = Transcoding::Configuration();
1122
1123             if( !The::transcodingController()->availableFormats().isEmpty() )
1124             {
1125                 Transcoding::AssistantDialog dialog( this );
1126                 if( dialog.exec() )
1127                     configuration = dialog.configuration();
1128             }
1129             else
1130                 debug() << "FFmpeg is not installed or does not support any of the required formats.";
1131
1132             copyTracks( m_currentItems, m_currentCopyDestination[ action ], false, configuration );
1133         }
1134     }
1135 }
1136
1137 void CollectionTreeView::slotMoveTracks()
1138 {
1139     if( sender() )
1140     {
1141         if ( QAction * action = dynamic_cast<QAction *>( sender() ) )
1142             copyTracks( m_currentItems, m_currentCopyDestination[ action ], true );
1143     }
1144 }
1145
1146 void CollectionTreeView::slotRemoveTracks()
1147 {
1148     if( sender() )
1149     {
1150         if ( QAction * action = dynamic_cast<QAction *>( sender() ) )
1151         {
1152             Q_UNUSED( action );
1153             removeTracks( m_currentItems );
1154         }
1155     }
1156 }
1157
1158 void CollectionTreeView::slotOrganize()
1159 {
1160     if( sender() )
1161     {
1162         if( QAction * action = dynamic_cast<QAction *>( sender() ) )
1163         {
1164             Q_UNUSED( action )
1165             organizeTracks( m_currentItems );
1166         }
1167     }
1168 }
1169
1170 QSet<CollectionTreeItem*>
1171 CollectionTreeView::cleanItemSet( const QSet<CollectionTreeItem*> &items )
1172 {
1173     QSet<CollectionTreeItem*> parents;
1174     foreach( CollectionTreeItem *item, items )
1175     {
1176         CollectionTreeItem *tmpItem = item;
1177         while( tmpItem )
1178         {
1179             if( items.contains( tmpItem->parent() ) )
1180                 tmpItem = tmpItem->parent();
1181             else
1182             {
1183                 parents.insert( tmpItem );
1184                 break;
1185             }
1186         }
1187     }
1188     return parents;
1189 }
1190
1191 Collections::QueryMaker*
1192 CollectionTreeView::createMetaQueryFromItems( const QSet<CollectionTreeItem*> &items, bool cleanItems ) const
1193 {
1194     if( !m_treeModel )
1195         return 0;
1196
1197     QSet<CollectionTreeItem*> parents = cleanItems ? cleanItemSet( items ) : items;
1198
1199     QList<Collections::QueryMaker*> queryMakers;
1200     foreach( CollectionTreeItem *item, parents )
1201     {
1202         Collections::QueryMaker *qm = item->queryMaker();
1203         CollectionTreeItem *tmp = item;
1204         while( tmp->isDataItem() )
1205         {
1206             if ( tmp->isVariousArtistItem() )
1207                 qm->setAlbumQueryMode( Collections::QueryMaker::OnlyCompilations );
1208             else
1209                 qm->addMatch( tmp->data() );
1210             tmp = tmp->parent();
1211         }
1212         m_treeModel->addFilters( qm );
1213         queryMakers.append( qm );
1214     }
1215     return new Collections::MetaQueryMaker( queryMakers );
1216 }
1217
1218
1219
1220 #include "CollectionTreeView.moc"
1221