File indexing completed on 2024-05-19 04:48:41
0001 /**************************************************************************************** 0002 * Copyright (c) 2009 Bart Cerneels <bart.cerneels@kde.org> * 0003 * * 0004 * This program is free software; you can redistribute it and/or modify it under * 0005 * the terms of the GNU General Public License as published by the Free Software * 0006 * Foundation; either version 2 of the License, or (at your option) any later * 0007 * version. * 0008 * * 0009 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0010 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0011 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0012 * * 0013 * You should have received a copy of the GNU General Public License along with * 0014 * this program. If not, see <http://www.gnu.org/licenses/>. * 0015 ****************************************************************************************/ 0016 0017 #include "PlaylistsInFoldersProxy.h" 0018 0019 #include "AmarokMimeData.h" 0020 #include "MainWindow.h" 0021 #include "core/support/Debug.h" 0022 #include "core/playlists/Playlist.h" 0023 #include "SvgHandler.h" 0024 #include "UserPlaylistModel.h" 0025 #include "playlist/PlaylistModelStack.h" 0026 #include "widgets/PrettyTreeRoles.h" 0027 0028 #include <QIcon> 0029 #include <QInputDialog> 0030 #include <QLabel> 0031 #include <QMessageBox> 0032 0033 PlaylistsInFoldersProxy::PlaylistsInFoldersProxy( QAbstractItemModel *model ) 0034 : QtGroupingProxy( model, QModelIndex(), PlaylistBrowserNS::UserModel::LabelColumn ) 0035 { 0036 m_renameFolderAction = new QAction( QIcon::fromTheme( QStringLiteral("media-track-edit-amarok") ), 0037 i18n( "&Rename Folder..." ), this ); 0038 m_renameFolderAction->setProperty( "popupdropper_svg_id", "edit_group" ); 0039 connect( m_renameFolderAction, &QAction::triggered, this, &PlaylistsInFoldersProxy::slotRenameFolder ); 0040 0041 m_deleteFolderAction = new QAction( QIcon::fromTheme( QStringLiteral("media-track-remove-amarok") ), 0042 i18n( "&Delete Folder" ), this ); 0043 m_deleteFolderAction->setProperty( "popupdropper_svg_id", "delete_group" ); 0044 m_deleteFolderAction->setObjectName( QStringLiteral("deleteAction") ); 0045 connect( m_deleteFolderAction, &QAction::triggered, this, &PlaylistsInFoldersProxy::slotDeleteFolder ); 0046 0047 if( auto m = static_cast<PlaylistBrowserNS::PlaylistBrowserModel*>(sourceModel()) ) 0048 connect( m, &PlaylistBrowserNS::PlaylistBrowserModel::renameIndex, 0049 this, &PlaylistsInFoldersProxy::slotRenameIndex ); 0050 } 0051 0052 PlaylistsInFoldersProxy::~PlaylistsInFoldersProxy() 0053 { 0054 } 0055 0056 QVariant 0057 PlaylistsInFoldersProxy::data( const QModelIndex &idx, int role ) const 0058 { 0059 0060 //Turn the QVariantList of the source into a comma separated string, but only for the real items 0061 if( !isGroup( idx ) && idx.column() == PlaylistBrowserNS::PlaylistBrowserModel::ProviderColumn 0062 && role == Qt::DisplayRole ) 0063 { 0064 QVariant indexData = QtGroupingProxy::data( idx, role ); 0065 if( indexData.type() != QVariant::List ) 0066 return indexData; 0067 0068 QString providerString = indexData.toStringList().join( QStringLiteral(", ") ); 0069 return QVariant( providerString ); 0070 } 0071 0072 if( idx.column() == 0 && isGroup( idx ) && 0073 role == PrettyTreeRoles::DecoratorRole ) 0074 { 0075 //whether we use the list from m_deleteFolderAction or m_renameFolderAction does not matter 0076 //they are the same anyway 0077 QPersistentModelIndexList actionList = 0078 m_deleteFolderAction->data().value<QPersistentModelIndexList>(); 0079 0080 //make a persistent modelindex since the location of the groups can change while executing 0081 //actions. 0082 actionList << QPersistentModelIndex( idx ); 0083 QVariant value = QVariant::fromValue( actionList ); 0084 m_deleteFolderAction->setData( value ); 0085 m_renameFolderAction->setData( value ); 0086 0087 QList<QAction *> actions; 0088 actions << m_renameFolderAction << m_deleteFolderAction; 0089 return QVariant::fromValue( actions ); 0090 } 0091 0092 if( idx.column() == 0 && isGroup( idx ) && 0093 role == PrettyTreeRoles::DecoratorRoleCount ) 0094 return 2; // 2 actions, see above 0095 0096 return QtGroupingProxy::data( idx, role ); 0097 } 0098 0099 bool 0100 PlaylistsInFoldersProxy::removeRows( int row, int count, const QModelIndex &parent ) 0101 { 0102 DEBUG_BLOCK 0103 bool result; 0104 debug() << "in parent " << parent << "remove " << count << " starting at row " << row; 0105 if( !parent.isValid() ) 0106 { 0107 QModelIndex folderIdx = index( row, 0, QModelIndex() ); 0108 if( isGroup( folderIdx ) ) 0109 { 0110 deleteFolder( folderIdx ); 0111 return true; 0112 } 0113 0114 //is a playlist not in a folder 0115 QModelIndex childIdx = mapToSource( index( row, 0, m_rootIndex ) ); 0116 result = sourceModel()->removeRows( childIdx.row(), count, m_rootIndex ); 0117 if( result ) 0118 { 0119 beginRemoveRows( parent, row, row + count - 1 ); 0120 endRemoveRows(); 0121 } 0122 return result; 0123 } 0124 0125 if( isGroup( parent ) ) 0126 { 0127 result = true; 0128 for( int i = row; i < row + count; i++ ) 0129 { 0130 QModelIndex childIdx = mapToSource( index( i, 0, parent ) ); 0131 //set success to false if removeRows returns false 0132 result = sourceModel()->removeRow( childIdx.row(), QModelIndex() ) ? result : false; 0133 } 0134 return result; 0135 } 0136 0137 //removing a track from a playlist 0138 beginRemoveRows( parent, row, row + count - 1 ); 0139 QModelIndex originalIdx = mapToSource( parent ); 0140 result = sourceModel()->removeRows( row, count, originalIdx ); 0141 endRemoveRows(); 0142 0143 return result; 0144 } 0145 0146 QStringList 0147 PlaylistsInFoldersProxy::mimeTypes() const 0148 { 0149 QStringList mimeTypes = sourceModel()->mimeTypes(); 0150 mimeTypes << AmarokMimeData::PLAYLISTBROWSERGROUP_MIME; 0151 return mimeTypes; 0152 } 0153 0154 QMimeData * 0155 PlaylistsInFoldersProxy::mimeData( const QModelIndexList &indexes ) const 0156 { 0157 DEBUG_BLOCK 0158 AmarokMimeData* mime = new AmarokMimeData(); 0159 QModelIndexList sourceIndexes; 0160 foreach( const QModelIndex &idx, indexes ) 0161 { 0162 debug() << idx; 0163 if( isGroup( idx ) ) 0164 { 0165 debug() << "is a group, add mimeData of all children"; 0166 } 0167 else 0168 { 0169 debug() << "is original item, add mimeData from source model"; 0170 sourceIndexes << mapToSource( idx ); 0171 } 0172 } 0173 0174 if( !sourceIndexes.isEmpty() ) 0175 return sourceModel()->mimeData( sourceIndexes ); 0176 0177 return mime; 0178 } 0179 0180 bool 0181 PlaylistsInFoldersProxy::dropMimeData( const QMimeData *data, Qt::DropAction action, 0182 int row, int column, const QModelIndex &parent ) 0183 { 0184 DEBUG_BLOCK 0185 Q_UNUSED( row ); 0186 Q_UNUSED( column ); 0187 debug() << "dropped on " << QStringLiteral("row: %1, column: %2, parent:").arg( row ).arg( column ); 0188 debug() << parent; 0189 if( action == Qt::IgnoreAction ) 0190 { 0191 debug() << "ignored"; 0192 return true; 0193 } 0194 0195 if( data->hasFormat( AmarokMimeData::PLAYLIST_MIME ) || 0196 data->hasFormat( AmarokMimeData::PLAYLISTBROWSERGROUP_MIME ) ) 0197 { 0198 debug() << "has amarok mime data"; 0199 const AmarokMimeData *amarokMime = dynamic_cast<const AmarokMimeData *>(data); 0200 if( amarokMime == nullptr ) 0201 { 0202 error() << "could not cast to amarokMimeData"; 0203 return false; 0204 } 0205 0206 if( !parent.isValid() ) 0207 { 0208 debug() << "dropped on the root"; 0209 Playlists::PlaylistList playlists = amarokMime->playlists(); 0210 foreach( Playlists::PlaylistPtr playlist, playlists ) 0211 playlist->setGroups( QStringList() ); 0212 buildTree(); 0213 return true; 0214 } 0215 0216 if( isGroup( parent ) ) 0217 { 0218 debug() << "dropped on a group"; 0219 if( data->hasFormat( AmarokMimeData::PLAYLIST_MIME ) ) 0220 { 0221 debug() << "playlist dropped on group"; 0222 if( parent.row() < 0 || parent.row() >= rowCount( QModelIndex() ) ) 0223 { 0224 debug() << "ERROR: something went seriously wrong in " << __FILE__ << __LINE__; 0225 return false; 0226 } 0227 //apply the new groupname to the source index 0228 QString groupName = parent.data( Qt::DisplayRole ).toString(); 0229 //TODO: apply the new groupname to the source index 0230 Playlists::PlaylistList playlists = amarokMime->playlists(); 0231 foreach( Playlists::PlaylistPtr playlist, playlists ) 0232 playlist->setGroups( QStringList( groupName ) ); 0233 buildTree(); 0234 return true; 0235 } 0236 else if( data->hasFormat( AmarokMimeData::PLAYLISTBROWSERGROUP_MIME ) ) 0237 { 0238 debug() << "playlistgroup dropped on group"; 0239 //TODO: multilevel group support 0240 debug() << "ignore drop until we have multilevel group support"; 0241 return false; 0242 } 0243 } 0244 } 0245 else 0246 { 0247 QModelIndex sourceIndex = mapToSource( parent ); 0248 return sourceModel()->dropMimeData( data, action, row, column, 0249 sourceIndex ); 0250 } 0251 0252 return false; 0253 } 0254 0255 Qt::DropActions 0256 PlaylistsInFoldersProxy::supportedDropActions() const 0257 { 0258 //always add MoveAction because playlists can be put into a different group 0259 return sourceModel()->supportedDropActions() | Qt::MoveAction; 0260 } 0261 0262 Qt::DropActions 0263 PlaylistsInFoldersProxy::supportedDragActions() const 0264 { 0265 //always add MoveAction because playlists can be put into a different group 0266 return sourceModel()->supportedDragActions() | Qt::MoveAction; 0267 } 0268 0269 void 0270 PlaylistsInFoldersProxy::setSourceModel( QAbstractItemModel *model ) 0271 { 0272 if( sourceModel() ) 0273 sourceModel()->disconnect(); 0274 0275 QtGroupingProxy::setSourceModel( model ); 0276 0277 connect( sourceModel(), SIGNAL(renameIndex(QModelIndex)), 0278 SLOT(slotRenameIndex(QModelIndex)) ); 0279 } 0280 0281 void 0282 PlaylistsInFoldersProxy::slotRenameIndex( const QModelIndex &sourceIdx ) 0283 { 0284 QModelIndex idx = mapFromSource( sourceIdx ); 0285 if( idx.isValid() ) 0286 Q_EMIT renameIndex( idx ); 0287 } 0288 0289 void 0290 PlaylistsInFoldersProxy::slotDeleteFolder() 0291 { 0292 QAction *action = qobject_cast<QAction *>( QObject::sender() ); 0293 if( action == nullptr ) 0294 return; 0295 0296 QPersistentModelIndexList indexes = action->data().value<QPersistentModelIndexList>(); 0297 0298 foreach( const QModelIndex &groupIdx, indexes ) 0299 deleteFolder( groupIdx ); 0300 } 0301 0302 void 0303 PlaylistsInFoldersProxy::slotRenameFolder() 0304 { 0305 QAction *action = qobject_cast<QAction *>( QObject::sender() ); 0306 if( action == nullptr ) 0307 return; 0308 0309 QPersistentModelIndexList indexes = action->data().value<QPersistentModelIndexList>(); 0310 0311 if( indexes.isEmpty() ) 0312 return; 0313 0314 //get the name for this new group 0315 //inline rename is handled by the view using setData() 0316 QModelIndex folder = indexes.first(); 0317 QString folderName = folder.data( Qt::DisplayRole ).toString(); 0318 bool ok; 0319 const QString newName = QInputDialog::getText( nullptr, 0320 i18n("New name"), 0321 i18nc("Enter a new name for a folder that already exists", 0322 "Enter new folder name:"), 0323 QLineEdit::Normal, 0324 folderName, 0325 &ok ); 0326 if( !ok || newName == folderName ) 0327 return; 0328 0329 setData( folder, newName ); 0330 } 0331 0332 void 0333 PlaylistsInFoldersProxy::deleteFolder( const QModelIndex &groupIdx ) 0334 { 0335 int childCount = rowCount( groupIdx ); 0336 if( childCount > 0 ) 0337 { 0338 auto button = QMessageBox::question( The::mainWindow(), 0339 i18n( "Confirm Delete" ), 0340 i18n( "Are you sure you want to delete this folder and its contents?" ) ); 0341 //TODO:include a text area with all the names of the playlists 0342 0343 if( button != QMessageBox::Yes ) 0344 return; 0345 0346 removeRows( 0, childCount, groupIdx ); 0347 } 0348 removeGroup( groupIdx ); 0349 //force a rebuild because groupHash might be incorrect 0350 //TODO: make QtGroupingProxy adjust groupHash keys 0351 buildTree(); 0352 } 0353 0354 QModelIndex 0355 PlaylistsInFoldersProxy::createNewFolder( const QString &groupName ) 0356 { 0357 RowData data; 0358 ItemData roleData; 0359 roleData.insert( Qt::DisplayRole, groupName ); 0360 roleData.insert( Qt::DecorationRole, QVariant( QIcon::fromTheme( QStringLiteral("folder") ) ) ); 0361 roleData.insert( Qt::EditRole, groupName ); 0362 data.insert( 0, roleData ); 0363 return addEmptyGroup( data ); 0364 } 0365 0366 Qt::ItemFlags PlaylistsInFoldersProxy::flags(const QModelIndex &idx) const 0367 { 0368 if( isGroup(idx) && idx.column() == 0) 0369 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | 0370 Qt::ItemIsDropEnabled; 0371 0372 return QtGroupingProxy::flags(idx); 0373 } 0374