1 /****************************************************************************************
2  * Copyright (c) 2007 Alexandre Pereira de Oliveira <aleprj@gmail.com>                  *
3  * Copyright (c) 2007-2009 Maximilian Kossick <maximilian.kossick@googlemail.com>       *
4  * Copyright (c) 2007 Nikolaj Hald Nielsen <nhn@kde.org>                                *
5  *                                                                                      *
6  * This program is free software; you can redistribute it and/or modify it under        *
7  * the terms of the GNU General Public License as published by the Free Software        *
8  * Foundation; either version 2 of the License, or (at your option) any later           *
9  * version.                                                                             *
10  *                                                                                      *
11  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
12  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
13  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
14  *                                                                                      *
15  * You should have received a copy of the GNU General Public License along with         *
16  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
17  ****************************************************************************************/
18 
19 #define DEBUG_PREFIX "CollectionTreeItemModel"
20 
21 #include "CollectionTreeItemModel.h"
22 
23 #include <amarokconfig.h>
24 #include "AmarokMimeData.h"
25 #include "CollectionTreeItem.h"
26 #include "core/support/Debug.h"
27 #include "core/support/Amarok.h"
28 #include "core/collections/Collection.h"
29 #include "core/collections/CollectionLocation.h"
30 #include "core/collections/QueryMaker.h"
31 #include "core/meta/Meta.h"
32 #include "core-impl/collections/support/CollectionManager.h"
33 #include "core-impl/collections/support/FileCollectionLocation.h"
34 
35 #include <KLocalizedString>
36 
37 #include <QTimer>
38 #include <QMap>
39 
CollectionTreeItemModel(const QList<CategoryId::CatMenuId> & levelType)40 CollectionTreeItemModel::CollectionTreeItemModel( const QList<CategoryId::CatMenuId> &levelType )
41     : CollectionTreeItemModelBase()
42 {
43     m_rootItem = new CollectionTreeItem( this );
44     CollectionManager *collMgr = CollectionManager::instance();
45     connect( collMgr, &CollectionManager::collectionAdded, this, &CollectionTreeItemModel::collectionAdded, Qt::QueuedConnection );
46     connect( collMgr, &CollectionManager::collectionRemoved, this, &CollectionTreeItemModel::collectionRemoved );
47 
48     QList<Collections::Collection *> collections = CollectionManager::instance()->viewableCollections();
49     foreach( Collections::Collection *coll, collections )
50     {
51         connect( coll, &Collections::Collection::updated, this, &CollectionTreeItemModel::slotFilterWithoutAutoExpand );
52         m_collections.insert( coll->collectionId(), CollectionRoot( coll, new CollectionTreeItem( coll, m_rootItem, this ) ) );
53     }
54 
55     setLevels( levelType );
56 }
57 
58 Qt::ItemFlags
flags(const QModelIndex & idx) const59 CollectionTreeItemModel::flags( const QModelIndex &idx ) const
60 {
61     if( !idx.isValid() )
62         return 0;
63 
64     Qt::ItemFlags flags = CollectionTreeItemModelBase::flags( idx );
65     if( idx.parent().isValid() )
66         return flags; // has parent -> not a collection -> no drops
67 
68     // we depend on someone (probably CollectionTreeView) to call
69     // CollectionTreeItemModelBase::setDragSourceCollections() every time a drag is
70     // initiated or enters collection browser widget
71     CollectionTreeItem *item = static_cast<CollectionTreeItem*>( idx.internalPointer() );
72     Q_ASSERT(item->type() == CollectionTreeItem::Collection);
73     if( m_dragSourceCollections.contains( item->parentCollection() ) )
74         return flags; // attempt to drag tracks from the same collection, don't allow this (bug 291068)
75 
76     if( !item->parentCollection()->isWritable() )
77         return flags; // not writeable, disallow drops
78 
79     // all paranoid checks passed, tracks can be dropped to this item
80     return flags | Qt::ItemIsDropEnabled;
81 }
82 
83 QVariant
data(const QModelIndex & index,int role) const84 CollectionTreeItemModel::data(const QModelIndex &index, int role) const
85 {
86     if (!index.isValid())
87         return QVariant();
88 
89     CollectionTreeItem *item = static_cast<CollectionTreeItem*>(index.internalPointer());
90     // subtract one here because there is a collection level for this model
91     return dataForItem( item, role, item->level() - 1 );
92 }
93 
94 bool
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent)95 CollectionTreeItemModel::dropMimeData( const QMimeData *data, Qt::DropAction action,
96                                        int row, int column, const QModelIndex &parent )
97 {
98     Q_UNUSED(row)
99     Q_UNUSED(column)
100     //no drops on empty areas
101     if( !parent.isValid() )
102         return false;
103 
104     CollectionTreeItem *item = static_cast<CollectionTreeItem*>( parent.internalPointer() );
105     Q_ASSERT(item->type() == CollectionTreeItem::Collection);
106 
107     Collections::Collection *targetCollection = item->parentCollection();
108     Q_ASSERT(targetCollection);
109 
110     //TODO: accept external drops.
111     const AmarokMimeData *mimeData = qobject_cast<const AmarokMimeData *>( data );
112     Q_ASSERT(mimeData);
113 
114     //TODO: optimize for copy from same provider.
115     Meta::TrackList tracks = mimeData->tracks();
116     QMap<Collections::Collection *, Meta::TrackPtr> collectionTrackMap;
117 
118     foreach( Meta::TrackPtr track, tracks )
119     {
120         Collections::Collection *sourceCollection = track->collection();
121         collectionTrackMap.insertMulti( sourceCollection, track );
122     }
123 
124     foreach( Collections::Collection *sourceCollection, collectionTrackMap.uniqueKeys() )
125     {
126         if( sourceCollection == targetCollection )
127             continue; // should be already caught by ...Model::flags(), but hey
128 
129         Collections::CollectionLocation *sourceLocation;
130         if( sourceCollection )
131         {
132             sourceLocation = sourceCollection->location();
133             Q_ASSERT(sourceLocation);
134         }
135         else
136         {
137             sourceLocation = new Collections::FileCollectionLocation();
138         }
139 
140         // we need to create target collection location per each source collection location
141         // -- prepareSomething() takes ownership of the pointer.
142         Collections::CollectionLocation *targetLocation = targetCollection->location();
143         Q_ASSERT(targetLocation);
144 
145         if( action == Qt::CopyAction )
146         {
147             sourceLocation->prepareCopy( collectionTrackMap.values( sourceCollection ),
148                                          targetLocation );
149         }
150         else if( action == Qt::MoveAction )
151         {
152             sourceLocation->prepareMove( collectionTrackMap.values( sourceCollection ),
153                                          targetLocation );
154         }
155     }
156     return true;
157 }
158 
159 
160 bool
canFetchMore(const QModelIndex & parent) const161 CollectionTreeItemModel::canFetchMore( const QModelIndex &parent ) const
162 {
163     if ( !parent.isValid() )
164         return false;       //children of the root item are the collections, and they are always known
165 
166     CollectionTreeItem *item = static_cast<CollectionTreeItem*>( parent.internalPointer() );
167     return item->level() <= m_levelType.count() && item->requiresUpdate();
168 }
169 
170 void
fetchMore(const QModelIndex & parent)171 CollectionTreeItemModel::fetchMore( const QModelIndex &parent )
172 {
173     if ( !parent.isValid() )
174         return;
175 
176     CollectionTreeItem *item = static_cast<CollectionTreeItem*>( parent.internalPointer() );
177     ensureChildrenLoaded( item );
178 }
179 
180 Qt::DropActions
supportedDropActions() const181 CollectionTreeItemModel::supportedDropActions() const
182 {
183     // this also causes supportedDragActions() to contain move action
184     return CollectionTreeItemModelBase::supportedDropActions() | Qt::MoveAction;
185 }
186 
187 void
collectionAdded(Collections::Collection * newCollection)188 CollectionTreeItemModel::collectionAdded( Collections::Collection *newCollection )
189 {
190     if( !newCollection )
191         return;
192 
193     connect( newCollection, &Collections::Collection::updated, this, &CollectionTreeItemModel::slotFilterWithoutAutoExpand ) ;
194 
195     QString collectionId = newCollection->collectionId();
196     if( m_collections.contains( collectionId ) )
197         return;
198 
199     //inserts new collection at the end.
200     beginInsertRows( QModelIndex(), m_rootItem->childCount(), m_rootItem->childCount() );
201     m_collections.insert( collectionId, CollectionRoot( newCollection, new CollectionTreeItem( newCollection, m_rootItem, this ) ) );
202     endInsertRows();
203 
204     if( m_collections.count() == 1 )
205         QTimer::singleShot( 0, this, &CollectionTreeItemModel::requestCollectionsExpansion );
206 }
207 
208 void
collectionRemoved(const QString & collectionId)209 CollectionTreeItemModel::collectionRemoved( const QString &collectionId )
210 {
211     int count = m_rootItem->childCount();
212     for( int i = 0; i < count; i++ )
213     {
214         CollectionTreeItem *item = m_rootItem->child( i );
215         if( item && !item->isDataItem() && item->parentCollection()->collectionId() == collectionId )
216         {
217             beginRemoveRows( QModelIndex(), i, i );
218             m_rootItem->removeChild( i );
219             m_collections.remove( collectionId );
220             m_expandedCollections.remove( item->parentCollection() );
221             endRemoveRows();
222         }
223     }
224 }
225 
226 void
filterChildren()227 CollectionTreeItemModel::filterChildren()
228 {
229     int count = m_rootItem->childCount();
230     for ( int i = 0; i < count; i++ )
231     {
232         markSubTreeAsDirty( m_rootItem->child( i ) );
233         ensureChildrenLoaded( m_rootItem->child( i ) );
234     }
235 }
236 
237 void
requestCollectionsExpansion()238 CollectionTreeItemModel::requestCollectionsExpansion()
239 {
240     for( int i = 0, count = m_rootItem->childCount(); i < count; i++ )
241     {
242         Q_EMIT expandIndex( itemIndex( m_rootItem->child( i ) ) );
243     }
244 }
245 
246