#ifndef AMUSE_PROJECT_MODEL_HPP #define AMUSE_PROJECT_MODEL_HPP #include #include #include #include #include #include #include "Common.hpp" #include "NewSoundMacroDialog.hpp" #include "amuse/AudioGroup.hpp" #include "amuse/AudioGroupData.hpp" #include "amuse/AudioGroupProject.hpp" #include "amuse/AudioGroupPool.hpp" #include "amuse/AudioGroupSampleDirectory.hpp" class ProjectModel; class EditorUndoCommand; enum AmuseItemEditFlags { AmuseItemNone = 0, AmuseItemCut = 1, AmuseItemCopy = 2, AmuseItemPaste = 4, AmuseItemDelete = 8, AmuseItemNoCut = (AmuseItemCopy | AmuseItemPaste | AmuseItemDelete), AmuseItemAll = (AmuseItemCut | AmuseItemCopy | AmuseItemPaste | AmuseItemDelete) }; class OutlineFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT QRegExp m_usageKey; public: explicit OutlineFilterProxyModel(ProjectModel* source); public slots: void setFilterRegExp(const QString &pattern); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; }; class NullItemProxyModel : public QIdentityProxyModel { Q_OBJECT public: explicit NullItemProxyModel(ProjectModel* source); QModelIndex mapFromSource(const QModelIndex& sourceIndex) const; QModelIndex mapToSource(const QModelIndex& proxyIndex) const; int rowCount(const QModelIndex& parent) const; QModelIndex index(int row, int column, const QModelIndex& parent) const; QVariant data(const QModelIndex& proxyIndex, int role) const; }; class PageObjectProxyModel : public QIdentityProxyModel { Q_OBJECT public: explicit PageObjectProxyModel(ProjectModel* source); QModelIndex mapFromSource(const QModelIndex& sourceIndex) const; QModelIndex mapToSource(const QModelIndex& proxyIndex) const; QModelIndex parent(const QModelIndex& child) const; int rowCount(const QModelIndex& parent) const; QModelIndex index(int row, int column, const QModelIndex& parent) const; QVariant data(const QModelIndex& proxyIndex, int role) const; Qt::ItemFlags flags(const QModelIndex& proxyIndex) const; }; class ProjectModel : public QAbstractItemModel { Q_OBJECT public: enum class ImportMode { Original, WAVs, Both }; struct NameUndoRegistry { std::unordered_map m_songIDs; std::unordered_map m_sfxIDs; void registerSongName(amuse::SongId id) const; void unregisterSongName(amuse::SongId id); void registerSFXName(amuse::SongId id) const; void unregisterSFXName(amuse::SongId id); void clear() { m_songIDs.clear(); m_sfxIDs.clear(); } }; private: QDir m_dir; OutlineFilterProxyModel m_outlineProxy; NullItemProxyModel m_nullProxy; PageObjectProxyModel m_pageObjectProxy; amuse::ProjectDatabase m_projectDatabase; std::unordered_map> m_groups; struct Song { QString m_path; int m_refCount = 0; }; std::unordered_map m_midiFiles; public: class INode : public amuse::IObj { friend class ProjectModel; virtual void _sortChildren(); public: enum class Type { Null, Root, Group, // Top-level group SongGroup, SoundGroup, Collection, // Classified object collection, one of the following: SoundMacro, ADSR, Curve, Keymap, Layer, Sample }; protected: QString m_name; INode* m_parent = nullptr; int m_row = -1; std::vector> m_children; amuse::IObjToken m_nullChild; public: virtual ~INode() = default; INode(const QString& name); INode(INode* parent) : m_parent(parent), m_row(0) { /* ONLY USED BY NULL NODE! */ } int childCount() const { return int(m_children.size()); } INode* child(int row) const { if (row == m_children.size()) return nullChild(); return m_children[row].get(); } INode* nullChild() const { return m_nullChild.get(); } INode* parent() const { return m_parent; } int row() const { return m_row; } void reindexRows(int row) { for (auto it = m_children.begin() + row; it != m_children.end(); ++it) (*it)->m_row = row++; m_nullChild->m_row = row; } void insertChild(amuse::ObjToken n) { assert(n->m_parent == nullptr && "Inserting already-parented node"); n->m_parent = this; int row = hypotheticalIndex(n->name()); m_children.insert(m_children.begin() + row, n.get()); reindexRows(row); } amuse::ObjToken removeChild(INode* n) { amuse::ObjToken ret = n; int row = ret->row(); assert(ret.get() == m_children.at(row).get() && "Removing non-child from node"); m_children.erase(m_children.begin() + row); reindexRows(row); ret->m_parent = nullptr; ret->m_row = -1; return ret; } void reserve(size_t sz) { m_children.reserve(sz); } template T& makeChild(_Args&&... args) { auto tok = amuse::MakeObj(std::forward<_Args>(args)...); insertChild(tok.get()); return static_cast(*tok); } template T& _appendChild(_Args&&... args) { auto tok = amuse::MakeObj(std::forward<_Args>(args)...); tok->m_parent = this; tok->m_row = m_children.size(); m_children.push_back(tok.get()); m_nullChild->m_row = m_children.size(); return static_cast(*tok); } INode* findChild(const QString& name) const { int idx = hypotheticalIndex(name); if (idx >= m_children.size()) return nullptr; INode* ret = m_children[idx].get(); if (ret->name() == name) return ret; return nullptr; } bool depthTraverse(const std::function& func) { for (auto& n : m_children) if (!n->depthTraverse(func)) break; return func(this); } bool oneLevelTraverse(const std::function& func) { for (auto& n : m_children) if (!func(n.get())) return false; return true; } const QString& name() const { return m_name; } virtual int hypotheticalIndex(const QString& name) const; virtual amuse::NameDB* getNameDb() const { return nullptr; } virtual Type type() const = 0; virtual QString text() const = 0; virtual QIcon icon() const = 0; virtual Qt::ItemFlags flags() const { return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; } virtual AmuseItemEditFlags editFlags() const { return AmuseItemNone; } virtual void registerNames(const NameUndoRegistry& registry) const {} virtual void unregisterNames(NameUndoRegistry& registry) const {} }; struct NullNode final : INode { NullNode(INode* parent) : INode(parent) {} Type type() const { return Type::Null; } QString text() const { return {}; } QIcon icon() const { return {}; } }; struct RootNode final : INode { RootNode() : INode(QStringLiteral("")) {} Type type() const { return Type::Root; } QString text() const { return {}; } QIcon icon() const { return {}; } Qt::ItemFlags flags() const { return Qt::ItemIsEnabled; } }; struct CollectionNode; struct BasePoolObjectNode; struct GroupNode final : INode { std::unordered_map>::iterator m_it; GroupNode(const QString& name) : INode(name) {} GroupNode(std::unordered_map>::iterator it) : INode(it->first), m_it(it) {} int hypotheticalIndex(const QString& name) const; void _sortChildren(); static QIcon Icon; Type type() const { return Type::Group; } QString text() const { return m_name; } QIcon icon() const { return Icon; } AmuseItemEditFlags editFlags() const { return AmuseItemNoCut; } CollectionNode* getCollectionOfType(Type tp) const; amuse::AudioGroupDatabase* getAudioGroup() const { return m_it->second.get(); } BasePoolObjectNode* pageObjectNodeOfId(amuse::ObjectId id) const; }; struct SongGroupNode final : INode { amuse::GroupId m_id; amuse::ObjToken m_index; SongGroupNode(const QString& name, amuse::ObjToken index) : INode(name), m_index(index) {} SongGroupNode(amuse::GroupId id, amuse::ObjToken index) : INode(amuse::GroupId::CurNameDB->resolveNameFromId(id).data()), m_id(id), m_index(index) {} static QIcon Icon; Type type() const { return Type::SongGroup; } QString text() const { return m_name; } QIcon icon() const { return Icon; } AmuseItemEditFlags editFlags() const { return AmuseItemAll; } amuse::NameDB* getNameDb() const { return amuse::GroupId::CurNameDB; } void registerNames(const NameUndoRegistry& registry) const { amuse::GroupId::CurNameDB->registerPair(text().toUtf8().data(), m_id); for (auto& p : m_index->m_midiSetups) registry.registerSongName(p.first); } void unregisterNames(NameUndoRegistry& registry) const { amuse::GroupId::CurNameDB->remove(m_id); for (auto& p : m_index->m_midiSetups) registry.unregisterSongName(p.first); } }; struct SoundGroupNode final : INode { amuse::GroupId m_id; amuse::ObjToken m_index; SoundGroupNode(const QString& name, amuse::ObjToken index) : INode(name), m_index(index) {} SoundGroupNode(amuse::GroupId id, amuse::ObjToken index) : INode(amuse::GroupId::CurNameDB->resolveNameFromId(id).data()), m_id(id), m_index(index) {} static QIcon Icon; Type type() const { return Type::SoundGroup; } QString text() const { return m_name; } QIcon icon() const { return Icon; } AmuseItemEditFlags editFlags() const { return AmuseItemAll; } amuse::NameDB* getNameDb() const { return amuse::GroupId::CurNameDB; } void registerNames(const NameUndoRegistry& registry) const { amuse::GroupId::CurNameDB->registerPair(text().toUtf8().data(), m_id); for (auto& p : m_index->m_sfxEntries) registry.registerSFXName(p.first); } void unregisterNames(NameUndoRegistry& registry) const { amuse::GroupId::CurNameDB->remove(m_id); for (auto& p : m_index->m_sfxEntries) registry.unregisterSFXName(p.first); } }; struct CollectionNode final : INode { QIcon m_icon; Type m_collectionType; CollectionNode(const QString& name, const QIcon& icon, Type collectionType) : INode(name), m_icon(icon), m_collectionType(collectionType) {} Type type() const { return Type::Collection; } QString text() const { return m_name; } QIcon icon() const { return m_icon; } Qt::ItemFlags flags() const { return Qt::ItemIsEnabled; } Type collectionType() const { return m_collectionType; } int indexOfId(amuse::ObjectId id) const; amuse::ObjectId idOfIndex(int idx) const; BasePoolObjectNode* nodeOfIndex(int idx) const; BasePoolObjectNode* nodeOfId(amuse::ObjectId id) const; }; struct BasePoolObjectNode : INode { amuse::ObjectId m_id; BasePoolObjectNode(const QString& name) : INode(name) {} BasePoolObjectNode(amuse::ObjectId id, const QString& name) : INode(name), m_id(id) {} amuse::ObjectId id() const { return m_id; } QString text() const { return m_name; } QIcon icon() const { return {}; } }; template struct PoolObjectNode final : BasePoolObjectNode { amuse::ObjToken m_obj; PoolObjectNode(const QString& name, amuse::ObjToken obj) : BasePoolObjectNode(name), m_obj(obj) {} PoolObjectNode(ID id, amuse::ObjToken obj) : BasePoolObjectNode(id, ID::CurNameDB->resolveNameFromId(id).data()), m_obj(obj) {} Type type() const { return TP; } AmuseItemEditFlags editFlags() const { return TP == INode::Type::Sample ? AmuseItemNoCut : AmuseItemAll; } void registerNames(const NameUndoRegistry& registry) const { ID::CurNameDB->registerPair(text().toUtf8().data(), m_id); } void unregisterNames(NameUndoRegistry& registry) const { ID::CurNameDB->remove(m_id); } amuse::NameDB* getNameDb() const { return ID::CurNameDB; } }; using SoundMacroNode = PoolObjectNode; using ADSRNode = PoolObjectNode, INode::Type::ADSR>; using CurveNode = PoolObjectNode, INode::Type::Curve>; using KeymapNode = PoolObjectNode, INode::Type::Keymap>; using LayersNode = PoolObjectNode, INode::Type::Layer>; using SampleNode = PoolObjectNode; amuse::ObjToken m_root; bool m_needsReset = false; void _buildGroupNodeCollections(GroupNode& gn); void _buildGroupNode(GroupNode& gn, amuse::AudioGroup& group); void _resetModelData(); void _resetSongRefCount(); QString MakeDedupedSubprojectName(const QString& origName); static QString MakeDedupedName(const QString& origName, amuse::NameDB* db); public: explicit ProjectModel(const QString& path, QObject* parent = Q_NULLPTR); bool clearProjectData(); bool openGroupData(const QString& groupName, UIMessenger& messenger); void openSongsData(); void importSongsData(const QString& path); bool reloadSampleData(const QString& groupName, UIMessenger& messenger); bool importGroupData(const QString& groupName, const amuse::AudioGroupData& data, ImportMode mode, UIMessenger& messenger); void saveSongsIndex(); bool saveToFile(UIMessenger& messenger); QStringList getGroupList() const; bool exportGroup(const QString& path, const QString& groupName, UIMessenger& messenger) const; bool importHeader(const QString& path, const QString& groupName, UIMessenger& messenger) const; bool exportHeader(const QString& path, const QString& groupName, bool& yesToAll, UIMessenger& messenger) const; void updateNodeNames(); bool ensureModelData(); QModelIndex proxyCreateIndex(int arow, int acolumn, void *adata) const; QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const; QModelIndex index(INode* node) const; QModelIndex parent(const QModelIndex& child) const; int rowCount(const QModelIndex& parent = QModelIndex()) const; int columnCount(const QModelIndex& parent = QModelIndex()) const; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); Qt::ItemFlags flags(const QModelIndex& index) const; INode* node(const QModelIndex& index) const; GroupNode* getGroupNode(INode* node) const; AmuseItemEditFlags editFlags(const QModelIndex& index) const; RootNode* rootNode() const { return m_root.get(); } void _postAddNode(INode* n, const NameUndoRegistry& registry); void _preDelNode(INode* n, NameUndoRegistry& registry); void _addNode(GroupNode* node, std::unique_ptr&& data, const NameUndoRegistry& registry); std::unique_ptr _delNode(GroupNode* node, NameUndoRegistry& registry); GroupNode* newSubproject(const QString& name); template void _addGroupNode(NT* node, GroupNode* parent, const NameUndoRegistry& registry, T& container); template void _delGroupNode(NT* node, GroupNode* parent, NameUndoRegistry& registry, T& container); void _addNode(SoundGroupNode* node, GroupNode* parent, const NameUndoRegistry& registry); void _delNode(SoundGroupNode* node, GroupNode* parent, NameUndoRegistry& registry); SoundGroupNode* newSoundGroup(GroupNode* group, const QString& name); void _addNode(SongGroupNode* node, GroupNode* parent, const NameUndoRegistry& registry); void _delNode(SongGroupNode* node, GroupNode* parent, NameUndoRegistry& registry); SongGroupNode* newSongGroup(GroupNode* group, const QString& name); template void _addPoolNode(NT* node, GroupNode* parent, const NameUndoRegistry& registry, T& container); template void _delPoolNode(NT* node, GroupNode* parent, NameUndoRegistry& registry, T& container); void _addNode(SoundMacroNode* node, GroupNode* parent, const NameUndoRegistry& registry); void _delNode(SoundMacroNode* node, GroupNode* parent, NameUndoRegistry& registry); SoundMacroNode* newSoundMacro(GroupNode* group, const QString& name, const SoundMacroTemplateEntry* templ = nullptr); void _addNode(ADSRNode* node, GroupNode* parent, const NameUndoRegistry& registry); void _delNode(ADSRNode* node, GroupNode* parent, NameUndoRegistry& registry); ADSRNode* newADSR(GroupNode* group, const QString& name); void _addNode(CurveNode* node, GroupNode* parent, const NameUndoRegistry& registry); void _delNode(CurveNode* node, GroupNode* parent, NameUndoRegistry& registry); CurveNode* newCurve(GroupNode* group, const QString& name); void _addNode(KeymapNode* node, GroupNode* parent, const NameUndoRegistry& registry); void _delNode(KeymapNode* node, GroupNode* parent, NameUndoRegistry& registry); KeymapNode* newKeymap(GroupNode* group, const QString& name); void _addNode(LayersNode* node, GroupNode* parent, const NameUndoRegistry& registry); void _delNode(LayersNode* node, GroupNode* parent, NameUndoRegistry& registry); LayersNode* newLayers(GroupNode* group, const QString& name); void _renameNode(INode* node, const QString& name); template EditorUndoCommand* readMimeYAML(athena::io::YAMLDocReader& r, const QString& name, GroupNode* gn); template void loadMimeData(const QMimeData* data, const QString& mimeType, GroupNode* gn); QStringList mimeTypes() const; QMimeData* mimeData(const QModelIndexList& indexes) const; bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex &parent); void cut(const QModelIndex& index); void copy(const QModelIndex& index); void paste(const QModelIndex& index); QModelIndex duplicate(const QModelIndex& index); void del(const QModelIndex& index); const QDir& dir() const { return m_dir; } QString path() const { return m_dir.path(); } OutlineFilterProxyModel* getOutlineProxy() { return &m_outlineProxy; } NullItemProxyModel* getNullProxy() { return &m_nullProxy; } PageObjectProxyModel* getPageObjectProxy() { return &m_pageObjectProxy; } GroupNode* getGroupOfSfx(amuse::SFXId id) const; QString getMIDIPathOfSong(amuse::SongId id) const; void setMIDIPathOfSong(amuse::SongId id, const QString& path); void _allocateSongId(amuse::SongId id, std::string_view name); std::pair allocateSongId(); void deallocateSongId(amuse::SongId oldId); amuse::SongId exchangeSongId(amuse::SongId oldId, std::string_view newName); void setIdDatabases(INode* context) const; }; #endif //AMUSE_PROJECT_MODEL_HPP