File indexing completed on 2024-05-19 04:56:07

0001 /**
0002  * \file playlistmodel.cpp
0003  * Model containing files in playlist.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 05 Aug 2018
0008  *
0009  * Copyright (C) 2018-2024  Urs Fleisch
0010  *
0011  * This file is part of Kid3.
0012  *
0013  * Kid3 is free software; you can redistribute it and/or modify
0014  * it under the terms of the GNU General Public License as published by
0015  * the Free Software Foundation; either version 2 of the License, or
0016  * (at your option) any later version.
0017  *
0018  * Kid3 is distributed in the hope that it will be useful,
0019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0021  * GNU General Public License for more details.
0022  *
0023  * You should have received a copy of the GNU General Public License
0024  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0025  */
0026 
0027 #include "playlistmodel.h"
0028 #include <QFileInfo>
0029 #include "filesystemmodel.h"
0030 #include "fileproxymodel.h"
0031 #include "playlistcreator.h"
0032 #include "fileconfig.h"
0033 
0034 PlaylistModel::PlaylistModel(FileProxyModel* fsModel, QObject* parent)
0035   : QAbstractProxyModel(parent),
0036     m_fsModel(fsModel), m_modified(false)
0037 {
0038   setObjectName(QLatin1String("PlaylistModel"));
0039   setSourceModel(m_fsModel);
0040   connect(m_fsModel, &FileProxyModel::modelAboutToBeReset,
0041           this, &PlaylistModel::onSourceModelAboutToBeReset);
0042 }
0043 
0044 Qt::ItemFlags PlaylistModel::flags(const QModelIndex& index) const
0045 {
0046   if (!index.isValid())
0047     return QAbstractProxyModel::flags(index) | Qt::ItemIsDropEnabled;
0048 
0049   return QAbstractProxyModel::flags(index) | Qt::ItemIsDropEnabled |
0050       Qt::ItemIsDragEnabled;
0051 }
0052 
0053 bool PlaylistModel::setData(const QModelIndex& index,
0054                             const QVariant& value, int role)
0055 {
0056   if (role == FileSystemModel::FilePathRole &&
0057       index.isValid() &&
0058       index.row() >= 0 && index.row() < m_items.size() &&
0059       index.column() == 0) {
0060     if (QModelIndex idx = m_fsModel->index(value.toString()); idx.isValid()) {
0061       if (QPersistentModelIndex& itemIdx = m_items[index.row()];
0062           itemIdx != idx) {
0063         itemIdx = idx;
0064         emit dataChanged(index, index);
0065         setModified(true);
0066         return true;
0067       }
0068     }
0069   }
0070   return false;
0071 }
0072 
0073 int PlaylistModel::rowCount(const QModelIndex& parent) const
0074 {
0075   return parent.isValid() ? 0 : m_items.size();
0076 }
0077 
0078 int PlaylistModel::columnCount(const QModelIndex& parent) const
0079 {
0080   return parent.isValid() ? 0 : 1;
0081 }
0082 
0083 bool PlaylistModel::insertRows(int row, int count,
0084                                const QModelIndex& parent)
0085 {
0086   if (count <= 0 || row < 0 || row > rowCount(parent))
0087     return false;
0088   beginInsertRows(parent, row, row + count - 1);
0089   for (int i = 0; i < count; ++i) {
0090     m_items.insert(row, QPersistentModelIndex());
0091   }
0092   endInsertRows();
0093   setModified(true);
0094   return true;
0095 }
0096 
0097 bool PlaylistModel::removeRows(int row, int count,
0098                                const QModelIndex& parent)
0099 {
0100   if (count <= 0 || row < 0 || row + count > rowCount(parent))
0101     return false;
0102   beginRemoveRows(parent, row, row + count - 1);
0103   for (int i = 0; i < count; ++i) {
0104     m_items.removeAt(row);
0105   }
0106   endRemoveRows();
0107   setModified(true);
0108   return true;
0109 }
0110 
0111 QModelIndex PlaylistModel::index(int row, int column,
0112                                  const QModelIndex& parent) const
0113 {
0114   QModelIndex result = !parent.isValid() && row >= 0 && row < m_items.size() &&
0115       column == 0
0116       ? createIndex(row, column)
0117       : QModelIndex();
0118   return result;
0119 }
0120 
0121 QModelIndex PlaylistModel::parent(const QModelIndex& child) const
0122 {
0123   Q_UNUSED(child)
0124   return QModelIndex();
0125 }
0126 
0127 QModelIndex PlaylistModel::mapToSource(const QModelIndex& proxyIndex) const
0128 {
0129   QModelIndex result;
0130   if (!proxyIndex.parent().isValid() && proxyIndex.row() >= 0 &&
0131       proxyIndex.row() < m_items.size() && proxyIndex.column() == 0) {
0132     result = m_items.at(proxyIndex.row());
0133   }
0134   return result;
0135 }
0136 
0137 QModelIndex PlaylistModel::mapFromSource(const QModelIndex& sourceIndex) const
0138 {
0139   QModelIndex result;
0140   for (int i = 0; i < m_items.size(); ++i) {
0141     if (m_items.at(i) == sourceIndex) {
0142       result = index(i, sourceIndex.column());
0143       break;
0144     }
0145   }
0146   return result;
0147 }
0148 
0149 Qt::DropActions PlaylistModel::supportedDropActions() const
0150 {
0151   return Qt::MoveAction | Qt::CopyAction;
0152 }
0153 
0154 void PlaylistModel::setPlaylistFile(const QString& path)
0155 {
0156   if (m_playlistFilePath == path)
0157     return;
0158 
0159   m_filesNotFound.clear();
0160   if (path.isEmpty()) {
0161     m_playlistFilePath.clear();
0162     m_playlistFileName.clear();
0163     beginResetModel();
0164     m_items.clear();
0165     endResetModel();
0166     setModified(false);
0167     return;
0168   }
0169 
0170   m_playlistConfig = PlaylistConfig::instance();
0171   PlaylistCreator creator(QString(), m_playlistConfig);
0172   QStringList filePaths;
0173   PlaylistConfig::PlaylistFormat format;
0174   bool useFullPath;
0175   bool writeInfo;
0176 
0177   QFileInfo fileInfo(path);
0178   m_playlistFileName = fileInfo.fileName();
0179   m_playlistFilePath = fileInfo.absoluteDir().filePath(m_playlistFileName);
0180   if (creator.read(path, filePaths, format, useFullPath, writeInfo)) {
0181     beginResetModel();
0182     m_items.clear();
0183     const auto constFilePaths = filePaths;
0184     for (const QString& filePath :  constFilePaths) {
0185       if (QModelIndex index = m_fsModel->index(filePath); index.isValid()) {
0186         m_items.append(index);
0187       } else {
0188         m_filesNotFound.append(filePath);
0189       }
0190     }
0191     endResetModel();
0192 
0193     m_playlistConfig.setFormat(format);
0194     m_playlistConfig.setUseFullPath(useFullPath);
0195     m_playlistConfig.setWriteInfo(writeInfo);
0196   } else {
0197     // File does not exist yet, prepare model to be populated with
0198     // setPathsInPlaylist().
0199     beginResetModel();
0200     m_items.clear();
0201     endResetModel();
0202 
0203     m_playlistConfig.setFormat(PlaylistConfig::formatFromFileExtension(path));
0204   }
0205   setModified(false);
0206 }
0207 
0208 void PlaylistModel::setModified(bool modified)
0209 {
0210   if (m_modified != modified) {
0211     m_modified = modified;
0212     emit modifiedChanged(m_modified);
0213   }
0214 }
0215 
0216 bool PlaylistModel::save()
0217 {
0218   if (PlaylistCreator creator(QString(), m_playlistConfig);
0219       creator.write(m_playlistFilePath, m_items)) {
0220     setModified(false);
0221     return true;
0222   }
0223   return false;
0224 }
0225 
0226 QStringList PlaylistModel::pathsInPlaylist() const
0227 {
0228   QStringList paths;
0229   const auto idxs = m_items;
0230   for (const QPersistentModelIndex& idx : idxs) {
0231     if (const auto model =
0232         qobject_cast<const FileProxyModel*>(idx.model())) {
0233       paths.append(model->filePath(idx));
0234     }
0235   }
0236   return paths;
0237 }
0238 
0239 bool PlaylistModel::setPathsInPlaylist(const QStringList& paths)
0240 {
0241   bool ok = true;
0242   beginResetModel();
0243   m_items.clear();
0244   for (const QString& filePath : paths) {
0245     if (QModelIndex index = m_fsModel->index(filePath); index.isValid()) {
0246       m_items.append(index);
0247     } else {
0248       ok = false;
0249     }
0250   }
0251   endResetModel();
0252   setModified(true);
0253   return ok;
0254 }
0255 
0256 void PlaylistModel::onSourceModelAboutToBeReset()
0257 {
0258   m_pathsSavedDuringReset = pathsInPlaylist();
0259   // Restoring the model when modelReset() is signaled would cause an invalid
0260   // read of a deallocated file system node. Wait until the model is reloaded.
0261   connect(m_fsModel, &FileProxyModel::sortingFinished,
0262           this, &PlaylistModel::onSourceModelReloaded);
0263 }
0264 
0265 void PlaylistModel::onSourceModelReloaded()
0266 {
0267   disconnect(m_fsModel, &FileProxyModel::sortingFinished,
0268              this, &PlaylistModel::onSourceModelReloaded);
0269   if (!m_pathsSavedDuringReset.isEmpty()) {
0270     const bool oldModified = isModified();
0271     setPathsInPlaylist(m_pathsSavedDuringReset);
0272     m_pathsSavedDuringReset.clear();
0273     setModified(oldModified);
0274   }
0275 }