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 }