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