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