File indexing completed on 2024-05-05 05:40:29
0001 /*************************************************************************** 0002 * Copyright (C) 2021 by Renaud Guezennec * 0003 * http://www.rolisteam.org/contact * 0004 * * 0005 * This software is free software; you can redistribute it and/or modify * 0006 * it under the terms of the GNU General Public License as published by * 0007 * the Free Software Foundation; either version 2 of the License, or * 0008 * (at your option) any later version. * 0009 * * 0010 * This program is distributed in the hope that it will be useful, * 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0013 * GNU General Public License for more details. * 0014 * * 0015 * You should have received a copy of the GNU General Public License * 0016 * along with this program; if not, write to the * 0017 * Free Software Foundation, Inc., * 0018 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * 0019 ***************************************************************************/ 0020 #include "model/mediamodel.h" 0021 0022 #include "data/campaign.h" 0023 #include "data/media.h" 0024 #include "undoCmd/renamecampaignmedia.h" 0025 0026 #include <QDebug> 0027 #include <QDir> 0028 #include <QFile> 0029 #include <QFileInfo> 0030 #include <QIcon> 0031 #include <QString> 0032 #include <QStringList> 0033 0034 namespace campaign 0035 { 0036 namespace 0037 { 0038 QIcon IconFromMedia(Media* media) 0039 { 0040 if(!media) 0041 return {}; 0042 0043 static auto hash= QHash<Core::MediaType, QString>({ 0044 {Core::MediaType::Unknown, "unknown"}, 0045 {Core::MediaType::TokenFile, "contact"}, 0046 {Core::MediaType::ImageFile, "image-x-generic"}, 0047 {Core::MediaType::MapFile, "vmap"}, 0048 {Core::MediaType::TextFile, "text-x-generic"}, 0049 {Core::MediaType::MindmapFile, "mindmap"}, 0050 {Core::MediaType::CharacterSheetFile, "treeview"}, 0051 {Core::MediaType::WebpageFile, "text-html"}, 0052 {Core::MediaType::PdfFile, "pdfLogo"}, 0053 {Core::MediaType::AudioFile, "audio-x-generic"}, 0054 {Core::MediaType::PlayListFile, "playlist"}, 0055 }); 0056 0057 return QIcon::fromTheme(hash.value(media->type())); 0058 } 0059 0060 QString getParentPath(const QString& path) 0061 { 0062 auto canon= QFileInfo(path).dir().canonicalPath(); 0063 auto abso= QFileInfo(path).dir().absolutePath(); 0064 return canon.isEmpty() ? abso : canon; 0065 } 0066 0067 QStringList buildParentList(const QString& path, const QString& root) 0068 { 0069 QStringList res; 0070 if(!path.startsWith(root) || path == root) 0071 return res; 0072 0073 auto current= path; 0074 auto pastCurrent= current; 0075 do 0076 { 0077 pastCurrent= current; 0078 res.prepend(current); 0079 current= getParentPath(current); 0080 } while(current != root && pastCurrent != current); 0081 0082 return res; 0083 } 0084 0085 int findIndexOf(MediaNode* parent, MediaNode* child) 0086 { 0087 if(!parent || !child) 0088 return -1; 0089 0090 auto const& children= parent->children(); 0091 auto it= std::find_if(std::begin(children), std::end(children), 0092 [child](const std::unique_ptr<MediaNode>& tmp) { return tmp.get() == child; }); 0093 0094 if(it == std::end(children)) 0095 return -1; 0096 0097 return std::distance(std::begin(children), it); 0098 } 0099 0100 MediaNode* findNode(const QString& path, MediaNode* root) 0101 { 0102 auto rootPath= root->path(); 0103 if(path == rootPath) 0104 return root; 0105 0106 QStringList parents= buildParentList(path, rootPath); 0107 0108 auto tmp= root; 0109 for(const auto& currentPath : parents) 0110 { 0111 auto const& children= tmp->children(); 0112 auto it= std::find_if(std::begin(children), std::end(children), 0113 [currentPath](const std::unique_ptr<MediaNode>& child) 0114 { return child->path() == currentPath; }); 0115 0116 if(it == std::end(children)) 0117 return nullptr; 0118 0119 tmp= (*it).get(); 0120 } 0121 0122 if(tmp == root) 0123 tmp= nullptr; 0124 0125 return tmp; 0126 } 0127 0128 MediaNode* findNodeFromId(const QString& id, MediaNode* root) 0129 { 0130 MediaNode* node= nullptr; 0131 if(root->uuid() == id) 0132 { 0133 node= root; 0134 } 0135 else 0136 { 0137 auto const& children= root->children(); 0138 for(quint32 i= 0; i < children.size() && !node; ++i) 0139 { 0140 auto const& child= children.at(i); 0141 node= findNodeFromId(id, child.get()); 0142 } 0143 } 0144 return node; 0145 } 0146 } // namespace 0147 0148 MediaNode::MediaNode(MediaNode::NodeType type, const QString& path, const QString& uuid) 0149 : m_type(type), m_path(path), m_uuid(uuid) 0150 { 0151 m_info= QFileInfo(m_path); 0152 } 0153 0154 MediaNode::NodeType MediaNode::nodeType() const 0155 { 0156 return m_type; 0157 } 0158 0159 void MediaNode::setPath(const QString& path) 0160 { 0161 if(path == m_path) 0162 return; 0163 m_path= path; 0164 m_info= QFileInfo(m_path); 0165 emit pathChanged(); 0166 } 0167 0168 void MediaNode::addChild(std::unique_ptr<MediaNode> node) 0169 { 0170 m_children.push_back(std::move(node)); 0171 } 0172 0173 QString MediaNode::uuid() const 0174 { 0175 return m_uuid; 0176 } 0177 0178 int MediaNode::childrenCount() const 0179 { 0180 return m_children.size(); 0181 } 0182 0183 QString MediaNode::parentPath() const 0184 { 0185 QDir info= m_info.dir(); 0186 return info.absolutePath(); 0187 } 0188 0189 void MediaNode::removeChild(int i) 0190 { 0191 Q_ASSERT(!m_children.empty()); 0192 Q_ASSERT(i < m_children.size()); 0193 m_children.erase(m_children.begin() + i); 0194 } 0195 0196 QDateTime MediaNode::modifiedTime() const 0197 { 0198 return m_info.metadataChangeTime(); 0199 } 0200 0201 QString MediaNode::path() const 0202 { 0203 return m_path; 0204 } 0205 0206 QString MediaNode::name() const 0207 { 0208 return m_info.baseName(); 0209 } 0210 0211 QVariant MediaNode::size() const 0212 { 0213 return m_type == Directory ? QVariant() : QVariant::fromValue(QLocale().formattedDataSize(m_info.size())); 0214 } 0215 0216 MediaNode* MediaNode::childAt(int i) const 0217 { 0218 if(i > static_cast<int>(m_children.size()) || i < 0 || m_children.empty()) 0219 return nullptr; 0220 0221 return m_children[i].get(); 0222 } 0223 0224 const std::vector<std::unique_ptr<MediaNode>>& MediaNode::children() const 0225 { 0226 return m_children; 0227 } 0228 0229 MediaModel::MediaModel(Campaign* campaign, QObject* parent) 0230 : QAbstractItemModel(parent) 0231 , m_campaign(campaign) 0232 , m_root( 0233 new MediaNode(campaign::MediaNode::Directory, m_campaign->directory(campaign::Campaign::Place::MEDIA_ROOT))) 0234 { 0235 m_headers= QStringList{tr("Name"), tr("Size"), tr("Type"), tr("Date Added"), tr("Date Modified")}; 0236 0237 connect(m_campaign, &campaign::Campaign::mediaAdded, this, &MediaModel::addMediaNode); 0238 connect(m_campaign, &campaign::Campaign::mediaRemoved, this, &MediaModel::removeMediaNode); 0239 connect(m_campaign, &campaign::Campaign::rootDirectoryChanged, this, 0240 [this]() 0241 { 0242 beginResetModel(); 0243 m_root.reset(new MediaNode(campaign::MediaNode::Directory, 0244 m_campaign->directory(campaign::Campaign::Place::MEDIA_ROOT))); 0245 endResetModel(); 0246 }); 0247 } 0248 0249 QVariant MediaModel::headerData(int section, Qt::Orientation orientation, int role) const 0250 { 0251 if(orientation == Qt::Vertical) 0252 return {}; 0253 0254 if(Qt::DisplayRole == role) 0255 { 0256 return m_headers[section]; 0257 } 0258 0259 return {}; 0260 } 0261 0262 QModelIndex MediaModel::index(int row, int column, const QModelIndex& parent) const 0263 { 0264 if(row < 0) 0265 return QModelIndex(); 0266 0267 MediaNode* parentNode= nullptr; 0268 0269 if(!parent.isValid()) 0270 parentNode= m_root.get(); 0271 else 0272 parentNode= static_cast<MediaNode*>(parent.internalPointer()); 0273 0274 auto childNode= parentNode->childAt(row); 0275 0276 if(childNode) 0277 return createIndex(row, column, childNode); 0278 else 0279 return QModelIndex(); 0280 } 0281 0282 QModelIndex MediaModel::parent(const QModelIndex& index) const 0283 { 0284 if(!index.isValid()) 0285 return QModelIndex(); 0286 0287 auto indexNode= static_cast<MediaNode*>(index.internalPointer()); 0288 auto parentPath= indexNode->parentPath(); 0289 0290 // QFileInfo dir(parentPath); 0291 0292 if(parentPath.isEmpty() || parentPath == m_campaign->directory(campaign::Campaign::Place::MEDIA_ROOT) 0293 || parentPath.size() < m_campaign->rootDirectory().size()) 0294 { 0295 return {}; 0296 } 0297 0298 auto node= findNode(parentPath, m_root.get()); 0299 int i= findIndexOf(node, indexNode); 0300 0301 if(node == nullptr) 0302 return {}; 0303 0304 return createIndex(i, 0, node); 0305 } 0306 0307 int MediaModel::rowCount(const QModelIndex& parent) const 0308 { 0309 MediaNode* mediaNode= nullptr; 0310 if(!parent.isValid()) 0311 mediaNode= m_root.get(); 0312 else 0313 mediaNode= static_cast<MediaNode*>(parent.internalPointer()); 0314 0315 return mediaNode->childrenCount(); 0316 } 0317 0318 int MediaModel::columnCount(const QModelIndex& parent) const 0319 { 0320 /*if(!parent.isValid()) 0321 return 0;*/ 0322 Q_UNUSED(parent) 0323 0324 return m_headers.size(); 0325 } 0326 0327 QVariant MediaModel::data(const QModelIndex& index, int role) const 0328 { 0329 if(!index.isValid()) 0330 return QVariant(); 0331 0332 auto mediaNode= static_cast<MediaNode*>(index.internalPointer()); 0333 0334 int realrole= Role_Unknown; 0335 if(role == Qt::DisplayRole || role == Qt::EditRole) 0336 { 0337 realrole= index.column() + Role_Name; 0338 } 0339 else if(role == Qt::DecorationRole && index.column() == 0) 0340 { 0341 realrole= Role_Icon; 0342 } 0343 else 0344 { 0345 realrole= role; 0346 } 0347 0348 auto media= m_campaign->mediaFromUuid(mediaNode->uuid()); 0349 0350 if(!media) 0351 return {}; 0352 0353 QVariant res; 0354 switch(realrole) 0355 { 0356 case Role_Name: 0357 res= mediaNode->name(); 0358 break; 0359 case Role_Size: 0360 res= mediaNode->size(); 0361 break; 0362 case Role_Type: 0363 res= (mediaNode->nodeType() == MediaNode::Directory) ? QVariant(tr("Directory")) : 0364 QVariant::fromValue(media->type()); 0365 break; 0366 case Role_AddedDate: 0367 res= (mediaNode->nodeType() == MediaNode::Directory) ? QVariant() : media->addedTime(); 0368 break; 0369 case Role_ModifiedDate: 0370 res= mediaNode->modifiedTime(); 0371 break; 0372 case Role_Icon: 0373 res= (mediaNode->nodeType() == MediaNode::Directory) ? QIcon::fromTheme("folder") : IconFromMedia(media); 0374 break; 0375 case Role_Path: 0376 res= mediaNode->path(); 0377 break; 0378 case Role_Uuid: 0379 res= mediaNode->uuid(); 0380 break; 0381 case Role_IsDir: 0382 res= (mediaNode->nodeType() == MediaNode::Directory); 0383 break; 0384 case Role_Unknown: 0385 break; 0386 } 0387 0388 return res; 0389 } 0390 0391 void MediaModel::addMediaNode(Media* media) 0392 { 0393 if(!media) 0394 return; 0395 auto id= media->id(); 0396 auto path= media->path(); 0397 auto node= findNode(path, m_root.get()); 0398 0399 if(node) 0400 return; 0401 MediaNode* parentNode= m_root.get(); 0402 QModelIndex parentIdx; 0403 QStringList parents= buildParentList(getParentPath(path), m_root->path()); 0404 for(const auto& parent : parents) 0405 { 0406 auto node= findNode(parent, m_root.get()); 0407 int i= findIndexOf(parentNode, node); 0408 if(!node) 0409 { 0410 i= parentNode->childrenCount(); 0411 beginInsertRows(parentIdx, i, i); 0412 std::unique_ptr<MediaNode> media(new MediaNode(MediaNode::Directory, parent)); 0413 node= media.get(); 0414 parentNode->addChild(std::move(media)); 0415 endInsertRows(); 0416 } 0417 parentIdx= index(i, 0, parentIdx); 0418 parentNode= node; 0419 } 0420 beginInsertRows(parentIdx, parentNode->childrenCount(), parentNode->childrenCount()); 0421 std::unique_ptr<MediaNode> mediaFile(new MediaNode(MediaNode::File, path, id)); 0422 parentNode->addChild(std::move(mediaFile)); 0423 endInsertRows(); 0424 } 0425 0426 void MediaModel::removeMediaNode(const QString& id) 0427 { 0428 auto node= findNodeFromId(id, m_root.get()); 0429 if(!node) 0430 return; 0431 0432 auto parentNode= findNode(node->parentPath(), m_root.get()); 0433 int i= findIndexOf(parentNode, node); 0434 0435 if(i < 0) 0436 { 0437 qDebug() << "Error: parent node not found!!"; 0438 return; 0439 } 0440 0441 auto idx= createIndex(i, 0, node); 0442 auto parentIdx= idx.parent(); 0443 0444 beginRemoveRows(parentIdx, idx.row(), idx.row()); 0445 parentNode->removeChild(idx.row()); 0446 endRemoveRows(); 0447 } 0448 0449 bool MediaModel::setData(const QModelIndex& index, const QVariant& value, int role) 0450 { 0451 bool res= false; 0452 if(data(index, role) != value) 0453 { 0454 if(index.column() != 0) 0455 return res; 0456 0457 auto mediaNode= static_cast<MediaNode*>(index.internalPointer()); 0458 if(!mediaNode) 0459 return res; 0460 0461 auto name= mediaNode->name(); 0462 auto newPath= mediaNode->path().replace(name, value.toString()); 0463 emit performCommand(new RenameCampaignMedia(mediaNode, newPath, mediaNode->path(), this, m_campaign)); 0464 0465 emit dataChanged(index, index, QVector<int>() << role); 0466 res= true; 0467 } 0468 return res; 0469 } 0470 0471 Qt::ItemFlags MediaModel::flags(const QModelIndex& index) const 0472 { 0473 if(!index.isValid()) 0474 return Qt::NoItemFlags; 0475 0476 auto editable= Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; 0477 auto nonEditable= Qt::ItemIsEnabled | Qt::ItemIsSelectable; 0478 0479 return index.column() == 0 ? editable : nonEditable; 0480 } 0481 0482 void MediaModel::dataChangedFor(MediaNode* node) 0483 { 0484 if(!node) 0485 return; 0486 0487 auto id= node->uuid(); 0488 auto path= node->path(); 0489 0490 MediaNode* parentNode= m_root.get(); 0491 QModelIndex parentIdx; 0492 QStringList parents= buildParentList(getParentPath(path), m_root->path()); 0493 for(const auto& parent : parents) 0494 { 0495 auto node= findNode(parent, m_root.get()); 0496 int i= findIndexOf(parentNode, node); 0497 0498 parentIdx= index(i, 0, parentIdx); 0499 parentNode= node; 0500 } 0501 auto r= findIndexOf(parentNode, node); 0502 auto idx= index(r, 0, parentIdx); 0503 emit dataChanged(idx, idx, QVector<int>()); 0504 } 0505 0506 void MediaModel::setCampaign(Campaign* campaign) 0507 { 0508 if(m_campaign == campaign) 0509 return; 0510 m_campaign= campaign; 0511 emit campaignChanged(); 0512 } 0513 0514 void MediaModel::initDataFromCampaign() 0515 { 0516 beginResetModel(); 0517 m_root.reset( 0518 new MediaNode(campaign::MediaNode::Directory, m_campaign->directory(campaign::Campaign::Place::MEDIA_ROOT))); 0519 auto const& list= m_campaign->medias(); 0520 std::for_each(std::begin(list), std::end(list), 0521 [this](const std::unique_ptr<campaign::Media>& media) { addMediaNode(media.get()); }); 0522 endResetModel(); 0523 } 0524 0525 } // namespace campaign