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