added nodes.
This commit is contained in:
83
nodes/services/ntxclipboard.cpp
Normal file
83
nodes/services/ntxclipboard.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "ntxclipboard.h"
|
||||
|
||||
namespace ntx
|
||||
{
|
||||
void NtxClipboard::copyNodes(const NtxNodeList& nodes)
|
||||
{
|
||||
m_nodeEntryList.clear();
|
||||
|
||||
for (const auto& node : nodes)
|
||||
{
|
||||
if (!node) continue;
|
||||
|
||||
NtxNodeEntry entry;
|
||||
entry.clone = createOrphanClone(node);
|
||||
entry.originalParent = node->getParent();
|
||||
entry.originalPosition = node->ownPos().value_or(0);
|
||||
|
||||
m_nodeEntryList.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
void NtxClipboard::cutNodes(const NtxNodeList& nodes)
|
||||
{
|
||||
// Identisch zu copyNodes - Der Unterschied liegt in NtxHeadNode
|
||||
copyNodes(nodes);
|
||||
}
|
||||
|
||||
NtxNodeList NtxClipboard::pasteNodes() const
|
||||
{
|
||||
NtxNodeList result;
|
||||
|
||||
for (const auto& entry : m_nodeEntryList)
|
||||
{
|
||||
if (!entry.clone) continue;
|
||||
|
||||
// Neuen Klon vom Clipboard-Clone erstellen (mehrfaches Paste möglich)
|
||||
auto freshClone = entry.clone->clone();
|
||||
if (freshClone)
|
||||
{
|
||||
result.push_back(freshClone);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool NtxClipboard::isEmpty() const
|
||||
{
|
||||
return m_nodeEntryList.empty();
|
||||
}
|
||||
|
||||
size_t NtxClipboard::getCount() const
|
||||
{
|
||||
return m_nodeEntryList.size();
|
||||
}
|
||||
|
||||
void NtxClipboard::clear()
|
||||
{
|
||||
m_nodeEntryList.clear();
|
||||
}
|
||||
|
||||
const NtxNodeEntryList& NtxClipboard::getEntries() const
|
||||
{
|
||||
return m_nodeEntryList;
|
||||
}
|
||||
|
||||
NtxNodePtr NtxClipboard::createOrphanClone(NtxNodePtr node) const
|
||||
{
|
||||
if (!node) return nullptr;
|
||||
|
||||
// Deep-Clone erstellen
|
||||
auto clone = node->clone();
|
||||
|
||||
// Parent-Verbindung trennen (Elternlos machen)
|
||||
if (clone)
|
||||
{
|
||||
clone->setParent(nullptr);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
} // namespace ntx
|
||||
47
nodes/services/ntxclipboard.h
Normal file
47
nodes/services/ntxclipboard.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef NTX_CLIPBOARD_H
|
||||
#define NTX_CLIPBOARD_H
|
||||
|
||||
#include <ntxicommand.h>
|
||||
#include <vector>
|
||||
|
||||
namespace ntx
|
||||
{
|
||||
/**
|
||||
* @brief Einfaches Clipboard für Copy/Cut/Paste-Operationen.
|
||||
*
|
||||
* Speichert elternlose Deep-Clones mit Original-Position für Undo.
|
||||
* Kein Singleton - wird als Member von NtxHeadNode verwendet.
|
||||
*/
|
||||
class NtxClipboard
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
NtxClipboard() = default;
|
||||
~NtxClipboard() = default;
|
||||
|
||||
// Copy/Cut Operations
|
||||
void copyNodes(const NtxNodeList& nodes);
|
||||
void cutNodes(const NtxNodeList& nodes);
|
||||
|
||||
// Paste Operations
|
||||
NtxNodeList pasteNodes() const;
|
||||
|
||||
// State
|
||||
bool isEmpty() const;
|
||||
size_t getCount() const;
|
||||
void clear();
|
||||
|
||||
// Access
|
||||
const NtxNodeEntryList& getEntries() const;
|
||||
|
||||
private:
|
||||
|
||||
NtxNodeEntryList m_nodeEntryList;
|
||||
|
||||
NtxNodePtr createOrphanClone(NtxNodePtr node) const;
|
||||
};
|
||||
|
||||
} // namespace ntx
|
||||
|
||||
#endif // NTX_CLIPBOARD_H
|
||||
141
nodes/services/ntxcmdlist.cpp
Normal file
141
nodes/services/ntxcmdlist.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#include <NtxCmdList.h>
|
||||
|
||||
namespace ntx
|
||||
{
|
||||
|
||||
NtxCmdList::NtxCmdList()
|
||||
: m_executedCount(0)
|
||||
{
|
||||
setDescription("Multi Command");
|
||||
}
|
||||
|
||||
NtxCmdList::NtxCmdList(const NtxString& description)
|
||||
: m_executedCount(0)
|
||||
{
|
||||
setDescription(description);
|
||||
}
|
||||
|
||||
bool NtxCmdList::execute()
|
||||
{
|
||||
m_executedCount = 0;
|
||||
|
||||
for (size_t i = 0; i < m_commands.size(); ++i)
|
||||
{
|
||||
if (!m_commands[i]->canExecute())
|
||||
{
|
||||
rollback(i);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_commands[i]->execute())
|
||||
{
|
||||
rollback(i);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_executedCount++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NtxCmdList::undo()
|
||||
{
|
||||
if (m_executedCount == 0)
|
||||
return false;
|
||||
|
||||
for (size_t i = m_executedCount; i > 0; --i)
|
||||
{
|
||||
size_t index = i - 1;
|
||||
|
||||
if (!m_commands[index]->canUndo())
|
||||
continue;
|
||||
|
||||
m_commands[index]->undo();
|
||||
}
|
||||
|
||||
m_executedCount = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NtxCmdList::canExecute() const
|
||||
{
|
||||
if (m_commands.empty())
|
||||
return false;
|
||||
|
||||
for (const auto& cmd : m_commands)
|
||||
{
|
||||
if (!cmd->canExecute())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NtxCmdList::canUndo() const
|
||||
{
|
||||
if (m_executedCount == 0)
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < m_executedCount; ++i)
|
||||
{
|
||||
if (m_commands[i]->canUndo())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
NtxString NtxCmdList::getDescription() const
|
||||
{
|
||||
NtxString desc = NtxICommand::getDescription();
|
||||
|
||||
if (!desc.empty())
|
||||
return desc;
|
||||
|
||||
if (m_commands.empty())
|
||||
return "Empty Multi Command";
|
||||
|
||||
return "Multi Command (" + std::to_string(m_commands.size()) + " commands)";
|
||||
}
|
||||
|
||||
void NtxCmdList::addCommand(NtxCommandUPtr command)
|
||||
{
|
||||
if (command)
|
||||
{
|
||||
m_commands.push_back(std::move(command));
|
||||
}
|
||||
}
|
||||
|
||||
void NtxCmdList::clear()
|
||||
{
|
||||
m_commands.clear();
|
||||
m_executedCount = 0;
|
||||
}
|
||||
|
||||
size_t NtxCmdList::count() const
|
||||
{
|
||||
return m_commands.size();
|
||||
}
|
||||
|
||||
bool NtxCmdList::isEmpty() const
|
||||
{
|
||||
return m_commands.empty();
|
||||
}
|
||||
|
||||
void NtxCmdList::rollback(size_t upToIndex)
|
||||
{
|
||||
for (size_t i = upToIndex; i > 0; --i)
|
||||
{
|
||||
size_t index = i - 1;
|
||||
|
||||
if (m_commands[index]->canUndo())
|
||||
{
|
||||
m_commands[index]->undo();
|
||||
}
|
||||
}
|
||||
|
||||
m_executedCount = 0;
|
||||
}
|
||||
|
||||
} // namespace ntx
|
||||
42
nodes/services/ntxcmdlist.h
Normal file
42
nodes/services/ntxcmdlist.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef NTX_CMDLIST_H
|
||||
#define NTX_CMDLIST_H
|
||||
|
||||
#include <ntxicommand.h>
|
||||
#include <vector>
|
||||
|
||||
namespace ntx
|
||||
{
|
||||
|
||||
class NtxCmdList : public NtxICommand
|
||||
{
|
||||
public:
|
||||
|
||||
NtxCmdList();
|
||||
explicit NtxCmdList(const NtxString& description);
|
||||
~NtxCmdList() override = default;
|
||||
|
||||
bool execute() override;
|
||||
bool undo() override;
|
||||
|
||||
bool canExecute() const override;
|
||||
bool canUndo() const override;
|
||||
|
||||
NtxString getDescription() const override;
|
||||
|
||||
void addCommand(NtxCommandUPtr command);
|
||||
void clear();
|
||||
|
||||
size_t count() const;
|
||||
bool isEmpty() const;
|
||||
|
||||
private:
|
||||
|
||||
std::vector<NtxCommandUPtr> m_commands;
|
||||
size_t m_executedCount;
|
||||
|
||||
void rollback(size_t upToIndex);
|
||||
};
|
||||
|
||||
} // namespace ntx
|
||||
|
||||
#endif // NTX_CMDLIST_H
|
||||
55
nodes/services/ntxheadnode.cpp
Normal file
55
nodes/services/ntxheadnode.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include <ntxheadnode.h>
|
||||
|
||||
namespace ntx
|
||||
{
|
||||
|
||||
bool NtxHeadNode::copyNode(NtxNodePtr node)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NtxHeadNode::cutNode(NtxNodePtr node)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
NtxNodePtr NtxHeadNode::pasteNode(NtxNodePtr parent, int index)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool NtxHeadNode::addNode(NtxNodePtr parent, NtxNodePtr node, int index)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NtxHeadNode::deleteNode(NtxNodePtr node)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NtxHeadNode::hasClipboard() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void NtxHeadNode::clearClipboard()
|
||||
{
|
||||
}
|
||||
|
||||
NtxNodePtr NtxHeadNode::peekClipboard() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool NtxHeadNode::validateNode(NtxNodePtr node) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NtxHeadNode::canAddToParent(NtxNodePtr parent, NtxNodePtr child) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace ntx
|
||||
46
nodes/services/ntxheadnode.h
Normal file
46
nodes/services/ntxheadnode.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#ifndef NTX_HEADNODE_H
|
||||
#define NTX_HEADNODE_H
|
||||
|
||||
#include <ntxinode.h>
|
||||
#include <ntxclipboard.h>
|
||||
#include <memory>
|
||||
|
||||
namespace ntx
|
||||
{
|
||||
class NtxINode;
|
||||
using NtxNodePtr = std::shared_ptr<NtxINode>;
|
||||
|
||||
class NtxHeadNode
|
||||
{
|
||||
public:
|
||||
|
||||
NtxHeadNode() = default;
|
||||
~NtxHeadNode() = default;
|
||||
|
||||
NtxHeadNode(const NtxHeadNode&) = delete;
|
||||
NtxHeadNode& operator=(const NtxHeadNode&) = delete;
|
||||
NtxHeadNode(NtxHeadNode&&) = default;
|
||||
NtxHeadNode& operator=(NtxHeadNode&&) = default;
|
||||
|
||||
bool copyNode(NtxNodePtr node);
|
||||
bool cutNode(NtxNodePtr node);
|
||||
NtxNodePtr pasteNode(NtxNodePtr parent, int index = -1);
|
||||
bool addNode(NtxNodePtr parent, NtxNodePtr node, int index = -1);
|
||||
bool deleteNode(NtxNodePtr node);
|
||||
|
||||
bool hasClipboard() const;
|
||||
void clearClipboard();
|
||||
NtxNodePtr peekClipboard() const;
|
||||
|
||||
private:
|
||||
|
||||
NtxClipboard m_clipboard; // ← Eigene Klasse als Member!
|
||||
bool m_isCutOperation{false};
|
||||
|
||||
bool validateNode(NtxNodePtr node) const;
|
||||
bool canAddToParent(NtxNodePtr parent, NtxNodePtr child) const;
|
||||
};
|
||||
|
||||
} // namespace ntx
|
||||
|
||||
#endif // NTX_HEADNODE_H
|
||||
21
nodes/services/ntxicommand.cpp
Normal file
21
nodes/services/ntxicommand.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include <ntxicommand.h>
|
||||
|
||||
namespace ntx
|
||||
{
|
||||
|
||||
bool NtxICommand::canUndo() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
NtxString NtxICommand::getDescription() const
|
||||
{
|
||||
return m_description;
|
||||
}
|
||||
|
||||
void NtxICommand::setDescription(const NtxString& description)
|
||||
{
|
||||
m_description = description;
|
||||
}
|
||||
|
||||
} // namespace ntx
|
||||
52
nodes/services/ntxicommand.h
Normal file
52
nodes/services/ntxicommand.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#ifndef NTX_ICOMMAND_H
|
||||
#define NTX_ICOMMAND_H
|
||||
|
||||
#include <ntxinode.h>
|
||||
#include <memory>
|
||||
|
||||
namespace ntx
|
||||
{
|
||||
|
||||
// Einfache Struct für Clipboard-Eintrag
|
||||
struct NtxNodeEntry
|
||||
{
|
||||
NtxNodePtr clone; // Elternloser Deep-Clone
|
||||
NtxNodeWeakPtr originalParent; // Schwache Referenz für Undo
|
||||
size_t originalPosition; // Position im Original-Parent
|
||||
};
|
||||
|
||||
using NtxNodeEntryList = std::vector<NtxNodeEntry>;
|
||||
|
||||
class NtxICommand
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~NtxICommand() = default;
|
||||
|
||||
virtual bool execute() = 0;
|
||||
virtual bool undo() = 0;
|
||||
|
||||
virtual bool canExecute() const = 0;
|
||||
virtual bool canUndo() const;
|
||||
|
||||
virtual NtxString getDescription() const;
|
||||
|
||||
protected:
|
||||
|
||||
NtxICommand() = default;
|
||||
NtxICommand(const NtxICommand&) = default;
|
||||
NtxICommand& operator=(const NtxICommand&) = default;
|
||||
|
||||
void setDescription(const NtxString& description);
|
||||
|
||||
private:
|
||||
|
||||
NtxString m_description;
|
||||
};
|
||||
|
||||
using NtxCommandPtr = std::shared_ptr<NtxICommand>;
|
||||
using NtxCommandUPtr = std::unique_ptr<NtxICommand>;
|
||||
|
||||
} // namespace ntx
|
||||
|
||||
#endif // NTX_ICOMMAND_H
|
||||
161
nodes/services/ntxnodecommands.cpp
Normal file
161
nodes/services/ntxnodecommands.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
#include "ntxnodecommands.h"
|
||||
|
||||
namespace ntx
|
||||
{
|
||||
|
||||
// =========================================================================
|
||||
// NtxInsertNodeCmd
|
||||
// =========================================================================
|
||||
|
||||
NtxInsertNodeCmd::NtxInsertNodeCmd(NtxNodePtr parent, NtxNodePtr node, size_t index)
|
||||
: m_parent(std::move(parent))
|
||||
, m_node(std::move(node))
|
||||
, m_index(index)
|
||||
{
|
||||
setDescription("Insert node");
|
||||
}
|
||||
|
||||
bool NtxInsertNodeCmd::execute()
|
||||
{
|
||||
if (!canExecute())
|
||||
return false;
|
||||
|
||||
if (m_index > m_parent->getChildCount())
|
||||
m_index = m_parent->getChildCount();
|
||||
|
||||
m_parent->insertChild(m_index, m_node);
|
||||
m_executed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NtxInsertNodeCmd::undo()
|
||||
{
|
||||
if (!m_executed)
|
||||
return false;
|
||||
|
||||
auto idx = m_parent->getChildIndex(m_node);
|
||||
if (idx == static_cast<size_t>(-1))
|
||||
return false;
|
||||
|
||||
m_parent->removeChild(idx);
|
||||
m_executed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NtxInsertNodeCmd::canExecute() const
|
||||
{
|
||||
return m_parent && m_node && !m_executed;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// NtxRemoveNodeCmd
|
||||
// =========================================================================
|
||||
|
||||
NtxRemoveNodeCmd::NtxRemoveNodeCmd(NtxNodePtr node)
|
||||
: m_node(std::move(node))
|
||||
{
|
||||
setDescription("Remove node");
|
||||
}
|
||||
|
||||
bool NtxRemoveNodeCmd::execute()
|
||||
{
|
||||
if (!canExecute())
|
||||
return false;
|
||||
|
||||
m_parent = m_node->getParent();
|
||||
if (!m_parent)
|
||||
return false;
|
||||
|
||||
auto pos = m_node->ownPos();
|
||||
if (!pos)
|
||||
return false;
|
||||
|
||||
m_index = *pos;
|
||||
m_parent->removeChild(m_index);
|
||||
m_executed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NtxRemoveNodeCmd::undo()
|
||||
{
|
||||
if (!m_executed || !m_parent)
|
||||
return false;
|
||||
|
||||
m_parent->insertChild(m_index, m_node);
|
||||
m_executed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NtxRemoveNodeCmd::canExecute() const
|
||||
{
|
||||
return m_node && m_node->getParent() && !m_executed;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// NtxMacroCommand
|
||||
// =========================================================================
|
||||
|
||||
NtxMacroCommand::NtxMacroCommand(const NtxString& description)
|
||||
{
|
||||
setDescription(description);
|
||||
}
|
||||
|
||||
NtxCommandUPtr NtxMacroCommand::create(
|
||||
const NtxString& description,
|
||||
std::vector<NtxCommandUPtr> commands)
|
||||
{
|
||||
auto macro = std::make_unique<NtxMacroCommand>(description);
|
||||
for (auto& cmd : commands)
|
||||
macro->add(std::move(cmd));
|
||||
return macro;
|
||||
}
|
||||
|
||||
void NtxMacroCommand::add(NtxCommandUPtr cmd)
|
||||
{
|
||||
if (cmd)
|
||||
m_commands.push_back(std::move(cmd));
|
||||
}
|
||||
|
||||
bool NtxMacroCommand::execute()
|
||||
{
|
||||
m_executedCount = 0;
|
||||
|
||||
for (auto& cmd : m_commands)
|
||||
{
|
||||
if (!cmd->execute())
|
||||
{
|
||||
// Rollback: Bereits ausgeführte rückgängig machen
|
||||
for (size_t i = m_executedCount; i > 0; --i)
|
||||
m_commands[i - 1]->undo();
|
||||
m_executedCount = 0;
|
||||
return false;
|
||||
}
|
||||
++m_executedCount;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NtxMacroCommand::undo()
|
||||
{
|
||||
for (size_t i = m_executedCount; i > 0; --i)
|
||||
{
|
||||
if (!m_commands[i - 1]->undo())
|
||||
return false;
|
||||
}
|
||||
m_executedCount = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NtxMacroCommand::canExecute() const
|
||||
{
|
||||
if (m_commands.empty())
|
||||
return false;
|
||||
for (const auto& cmd : m_commands)
|
||||
{
|
||||
if (!cmd->canExecute())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ntx
|
||||
102
nodes/services/ntxnodecommands.h
Normal file
102
nodes/services/ntxnodecommands.h
Normal file
@@ -0,0 +1,102 @@
|
||||
#ifndef NTX_NODECOMMANDS_H
|
||||
#define NTX_NODECOMMANDS_H
|
||||
|
||||
#include <ntxicommand.h>
|
||||
|
||||
namespace ntx
|
||||
{
|
||||
|
||||
// =========================================================================
|
||||
// Atomar: Knoten einfügen
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @brief Fügt einen Knoten an einer Position ein.
|
||||
* Undo entfernt ihn wieder.
|
||||
*/
|
||||
class NtxInsertNodeCmd : public NtxICommand
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
NtxInsertNodeCmd(NtxNodePtr parent, NtxNodePtr node, size_t index);
|
||||
|
||||
bool execute() override;
|
||||
bool undo() override;
|
||||
bool canExecute() const override;
|
||||
|
||||
private:
|
||||
|
||||
NtxNodePtr m_parent;
|
||||
NtxNodePtr m_node;
|
||||
size_t m_index;
|
||||
bool m_executed{false};
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// Atomar: Knoten entfernen
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @brief Entfernt einen Knoten. Merkt sich Parent + Position für Undo.
|
||||
*/
|
||||
class NtxRemoveNodeCmd : public NtxICommand
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
explicit NtxRemoveNodeCmd(NtxNodePtr node);
|
||||
|
||||
bool execute() override;
|
||||
bool undo() override;
|
||||
bool canExecute() const override;
|
||||
|
||||
private:
|
||||
|
||||
NtxNodePtr m_node;
|
||||
NtxNodePtr m_parent;
|
||||
size_t m_index{0};
|
||||
bool m_executed{false};
|
||||
};
|
||||
|
||||
// =========================================================================
|
||||
// Makro-Command (Composite Command)
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* @brief Führt mehrere Commands als atomare Einheit aus.
|
||||
*
|
||||
* execute(): Alle Commands vorwärts ausführen.
|
||||
* undo(): Alle Commands rückwärts rückgängig machen.
|
||||
* Bei Fehler: Rollback der bereits ausgeführten.
|
||||
*/
|
||||
class NtxMacroCommand : public NtxICommand
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
NtxMacroCommand() = default;
|
||||
explicit NtxMacroCommand(const NtxString& description);
|
||||
|
||||
/// Factory: Erzeugt ein Makro aus einer Liste von Commands.
|
||||
static NtxCommandUPtr create(
|
||||
const NtxString& description,
|
||||
std::vector<NtxCommandUPtr> commands);
|
||||
|
||||
void add(NtxCommandUPtr cmd);
|
||||
|
||||
bool execute() override;
|
||||
bool undo() override;
|
||||
bool canExecute() const override;
|
||||
|
||||
size_t commandCount() const { return m_commands.size(); }
|
||||
|
||||
private:
|
||||
|
||||
std::vector<NtxCommandUPtr> m_commands;
|
||||
size_t m_executedCount{0};
|
||||
};
|
||||
|
||||
} // namespace ntx
|
||||
|
||||
#endif // NTX_NODECOMMANDS_H
|
||||
209
nodes/services/ntxnodetree.cpp
Normal file
209
nodes/services/ntxnodetree.cpp
Normal file
@@ -0,0 +1,209 @@
|
||||
#include <QDebug>
|
||||
|
||||
#include <ntxnodetree.h>
|
||||
#include <ntxnodefactory.h>
|
||||
|
||||
|
||||
namespace ntx
|
||||
{
|
||||
|
||||
NtxNodeTree::NtxNodeTree(NtxClassTypeId id)
|
||||
{
|
||||
//m_ctypeId = id;
|
||||
}
|
||||
|
||||
NtxNodePtr NtxNodeTree::clone() const
|
||||
{
|
||||
/*
|
||||
auto cloned = NtxNodeFactory::makeNode(getClassTypeLabel());
|
||||
qDebug() << "Cloning NtxNodeTree with CTypeLabel: NOT DONE: " << getClassTypeLabel().c_str();
|
||||
|
||||
forEachProperty([&](const NtxString& key, const NtxVariant& value) {
|
||||
cloned->setProperty(key, value);
|
||||
});
|
||||
for (size_t i = 0; i < getChildCount(); ++i)
|
||||
cloned->addChild(getChild(i)->clone());
|
||||
|
||||
|
||||
|
||||
|
||||
return cloned;
|
||||
|
||||
*/
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Command-Ausführung (delegiert an NtxUndoStack)
|
||||
// =========================================================================
|
||||
|
||||
bool NtxNodeTree::execute(NtxCommandUPtr cmd)
|
||||
{
|
||||
if (!cmd || !cmd->canExecute())
|
||||
return false;
|
||||
m_undoStack.push(std::move(cmd));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NtxNodeTree::undo()
|
||||
{
|
||||
if (!canUndo())
|
||||
return false;
|
||||
m_undoStack.undo();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NtxNodeTree::redo()
|
||||
{
|
||||
if (!canRedo())
|
||||
return false;
|
||||
m_undoStack.redo();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NtxNodeTree::canUndo() const { return m_undoStack.canUndo(); }
|
||||
bool NtxNodeTree::canRedo() const { return m_undoStack.canRedo(); }
|
||||
NtxString NtxNodeTree::undoText() const { return m_undoStack.undoText(); }
|
||||
NtxString NtxNodeTree::redoText() const { return m_undoStack.redoText(); }
|
||||
|
||||
void NtxNodeTree::setClean() { m_undoStack.setClean(); }
|
||||
bool NtxNodeTree::isDirty() const { return !m_undoStack.isClean(); }
|
||||
|
||||
// =========================================================================
|
||||
// Einzel-Operationen
|
||||
// =========================================================================
|
||||
|
||||
bool NtxNodeTree::addNode(NtxNodePtr parent, NtxNodePtr node, size_t index)
|
||||
{
|
||||
return execute(std::make_unique<NtxInsertNodeCmd>(
|
||||
std::move(parent), std::move(node), index));
|
||||
}
|
||||
|
||||
bool NtxNodeTree::deleteNode(NtxNodePtr node)
|
||||
{
|
||||
return execute(std::make_unique<NtxRemoveNodeCmd>(std::move(node)));
|
||||
}
|
||||
|
||||
bool NtxNodeTree::moveNode(NtxNodePtr node, NtxNodePtr newParent, size_t newIndex)
|
||||
{
|
||||
// Move = Remove + Insert als Makro
|
||||
auto macro = std::make_unique<NtxMacroCommand>("Move node");
|
||||
macro->add(std::make_unique<NtxRemoveNodeCmd>(node));
|
||||
macro->add(std::make_unique<NtxInsertNodeCmd>(
|
||||
std::move(newParent), std::move(node), newIndex));
|
||||
return execute(std::move(macro));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Listen-Operationen (Makro-Commands)
|
||||
// =========================================================================
|
||||
|
||||
bool NtxNodeTree::addNodes(NtxNodePtr parent, const NtxNodeList& nodes, size_t index)
|
||||
{
|
||||
std::vector<NtxCommandUPtr> cmds;
|
||||
cmds.reserve(nodes.size());
|
||||
|
||||
for (size_t i = 0; i < nodes.size(); ++i)
|
||||
{
|
||||
cmds.push_back(std::make_unique<NtxInsertNodeCmd>(
|
||||
parent, nodes[i], index + i));
|
||||
}
|
||||
|
||||
return execute(NtxMacroCommand::create(
|
||||
"Insert " + std::to_string(nodes.size()) + " nodes",
|
||||
std::move(cmds)));
|
||||
}
|
||||
|
||||
bool NtxNodeTree::deleteNodes(const NtxNodeList& nodes)
|
||||
{
|
||||
std::vector<NtxCommandUPtr> cmds;
|
||||
cmds.reserve(nodes.size());
|
||||
|
||||
// Rückwärts löschen → Indizes bleiben stabil
|
||||
for (auto it = nodes.rbegin(); it != nodes.rend(); ++it)
|
||||
cmds.push_back(std::make_unique<NtxRemoveNodeCmd>(*it));
|
||||
|
||||
return execute(NtxMacroCommand::create(
|
||||
"Delete " + std::to_string(nodes.size()) + " nodes",
|
||||
std::move(cmds)));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Clipboard
|
||||
// =========================================================================
|
||||
|
||||
void NtxNodeTree::copyNodes(const NtxNodeList& nodes)
|
||||
{
|
||||
m_clipboard.copyNodes(nodes);
|
||||
m_isCutOperation = false;
|
||||
}
|
||||
|
||||
void NtxNodeTree::cutNodes(const NtxNodeList& nodes)
|
||||
{
|
||||
m_clipboard.copyNodes(nodes);
|
||||
m_isCutOperation = true;
|
||||
}
|
||||
|
||||
NtxNodeList NtxNodeTree::pasteNodes(NtxNodePtr parent, size_t index)
|
||||
{
|
||||
NtxNodeList pasted = m_clipboard.pasteNodes();
|
||||
if (pasted.empty())
|
||||
return {};
|
||||
|
||||
if (m_isCutOperation)
|
||||
{
|
||||
// Cut+Paste = Remove originals + Insert clones (als Makro)
|
||||
auto macro = std::make_unique<NtxMacroCommand>(
|
||||
"Paste " + std::to_string(pasted.size()) + " nodes (cut)");
|
||||
|
||||
// 1. Originale entfernen (rückwärts für stabile Indizes)
|
||||
const auto& entries = m_clipboard.getEntries();
|
||||
for (auto it = entries.rbegin(); it != entries.rend(); ++it)
|
||||
{
|
||||
if (auto origParent = it->originalParent.lock())
|
||||
{
|
||||
if (it->originalPosition < origParent->getChildCount())
|
||||
{
|
||||
auto origNode = origParent->getChild(it->originalPosition);
|
||||
if (origNode)
|
||||
macro->add(std::make_unique<NtxRemoveNodeCmd>(origNode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Clones einfügen
|
||||
for (size_t i = 0; i < pasted.size(); ++i)
|
||||
{
|
||||
macro->add(std::make_unique<NtxInsertNodeCmd>(
|
||||
parent, pasted[i], index + i));
|
||||
}
|
||||
|
||||
if (!execute(std::move(macro)))
|
||||
return {};
|
||||
|
||||
m_isCutOperation = false;
|
||||
m_clipboard.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copy+Paste = nur Insert
|
||||
if (!addNodes(parent, pasted, index))
|
||||
return {};
|
||||
}
|
||||
|
||||
return pasted;
|
||||
}
|
||||
|
||||
bool NtxNodeTree::hasClipboard() const
|
||||
{
|
||||
return !m_clipboard.isEmpty();
|
||||
}
|
||||
|
||||
void NtxNodeTree::clearClipboard()
|
||||
{
|
||||
m_clipboard.clear();
|
||||
m_isCutOperation = false;
|
||||
}
|
||||
|
||||
} // namespace ntx
|
||||
99
nodes/services/ntxnodetree.h
Normal file
99
nodes/services/ntxnodetree.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#ifndef NTX_NODETREE_H
|
||||
#define NTX_NODETREE_H
|
||||
|
||||
#include <ntxnode.h>
|
||||
#include <ntxclipboard.h>
|
||||
#include <ntxundostack.h>
|
||||
#include <ntxnodecommands.h>
|
||||
|
||||
namespace ntx
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Service-Schicht für Knoten-Operationen mit Undo/Redo.
|
||||
*
|
||||
* Ist selbst ein NtxINode (Composite-Pattern): Kann als Wurzel
|
||||
* eines Teilbaums dienen und in andere NtxNodeTrees eingehängt werden.
|
||||
*
|
||||
* Bietet:
|
||||
* - Command-basierte Baum-Manipulation (add/delete/move)
|
||||
* - Undo/Redo via NtxUndoStack
|
||||
* - Copy/Cut/Paste via NtxClipboard
|
||||
* - Listen-Operationen via NtxMacroCommand
|
||||
*/
|
||||
class NtxNodeTree //: public NtxNode
|
||||
{
|
||||
friend class NtxNodeFactory;
|
||||
|
||||
public:
|
||||
|
||||
//~NtxNodeTree() override = default;
|
||||
|
||||
NtxNodeTree(const NtxNodeTree&) = delete;
|
||||
NtxNodeTree& operator=(const NtxNodeTree&) = delete;
|
||||
|
||||
NtxNodePtr clone() const;// override;
|
||||
|
||||
// =========================================================================
|
||||
// Command-Ausführung
|
||||
// =========================================================================
|
||||
|
||||
/// Führt ein beliebiges Command aus (mit Undo-Support).
|
||||
bool execute(NtxCommandUPtr cmd);
|
||||
|
||||
bool undo();
|
||||
bool redo();
|
||||
bool canUndo() const;
|
||||
bool canRedo() const;
|
||||
NtxString undoText() const;
|
||||
NtxString redoText() const;
|
||||
|
||||
void setClean();
|
||||
bool isDirty() const;
|
||||
|
||||
// =========================================================================
|
||||
// Einzel-Operationen (erzeugen intern Commands)
|
||||
// =========================================================================
|
||||
|
||||
bool addNode(NtxNodePtr parent, NtxNodePtr node, size_t index);
|
||||
bool deleteNode(NtxNodePtr node);
|
||||
bool moveNode(NtxNodePtr node, NtxNodePtr newParent, size_t newIndex);
|
||||
|
||||
// =========================================================================
|
||||
// Listen-Operationen (erzeugen NtxMacroCommand)
|
||||
// =========================================================================
|
||||
|
||||
bool addNodes(NtxNodePtr parent, const NtxNodeList& nodes, size_t index);
|
||||
bool deleteNodes(const NtxNodeList& nodes);
|
||||
|
||||
// =========================================================================
|
||||
// Clipboard
|
||||
// =========================================================================
|
||||
|
||||
void copyNodes(const NtxNodeList& nodes);
|
||||
void cutNodes(const NtxNodeList& nodes);
|
||||
NtxNodeList pasteNodes(NtxNodePtr parent, size_t index);
|
||||
|
||||
bool hasClipboard() const;
|
||||
void clearClipboard();
|
||||
|
||||
// =========================================================================
|
||||
// Zugriff auf Interna
|
||||
// =========================================================================
|
||||
|
||||
NtxUndoStack& undoStack() { return m_undoStack; }
|
||||
const NtxUndoStack& undoStack() const { return m_undoStack; }
|
||||
|
||||
private:
|
||||
|
||||
NtxNodeTree() = default;
|
||||
explicit NtxNodeTree(NtxClassTypeId id);
|
||||
|
||||
NtxUndoStack m_undoStack;
|
||||
NtxClipboard m_clipboard;
|
||||
bool m_isCutOperation{false};
|
||||
};
|
||||
|
||||
} // namespace ntx
|
||||
|
||||
#endif // NTX_NODETREE_H
|
||||
209
nodes/services/ntxnodexml.cpp
Normal file
209
nodes/services/ntxnodexml.cpp
Normal file
@@ -0,0 +1,209 @@
|
||||
|
||||
|
||||
#include "ntxnodexml.h"
|
||||
#include <ntxnodefactory.h>
|
||||
#include <pugixml.hpp>
|
||||
#include <iostream>
|
||||
#include <sstream> // Add this include at the top of the file
|
||||
|
||||
|
||||
namespace ntx
|
||||
{
|
||||
// Rekursive Hilfsfunktion
|
||||
static NtxNodePtr parseElementRecursive(const pugi::xml_node& xmlNode)
|
||||
{
|
||||
if (!xmlNode)
|
||||
return nullptr;
|
||||
|
||||
// 1. Tag-Name als ID-String verwenden
|
||||
NtxString tagName = xmlNode.name();
|
||||
|
||||
// Ignoriere spezielle Knotentypen (Kommentare, PCDATA/Text, etc.) wenn sie leer sind
|
||||
// In diesem einfachen Beispiel behandeln wir nur Element-Knoten als echte NtxNodes.
|
||||
if (xmlNode.type() != pugi::node_element)
|
||||
return nullptr;
|
||||
|
||||
// 2. Erzeuge Knoten via Factory (nutzt implizit CTypeFactory für Mapping)
|
||||
NtxNodePtr newNode = NtxNodeFactory::makeNode(tagName);
|
||||
if (!newNode)
|
||||
return nullptr;
|
||||
|
||||
// 3. Attribute übertragen
|
||||
for (pugi::xml_attribute attr : xmlNode.attributes())
|
||||
{
|
||||
NtxString key = attr.name();
|
||||
|
||||
// Einfache Heuristik für Typen:
|
||||
// PugiXML liefert strings. Wir könnten versuchen zu konvertieren oder alles als String lassen.
|
||||
// Hier: Alles als String speichern, oder versuchen int/double zu parsen.
|
||||
// Fürs erste: Speichern als String, Payload erlaubt mixed types.
|
||||
newNode->set(key, NtxString(attr.value()));
|
||||
|
||||
/* Optional: Intelligentes Parsen
|
||||
if (attr.as_int() != 0 || std::string(attr.value()) == "0")
|
||||
newNode->set(key, attr.as_int());
|
||||
else
|
||||
newNode->set(key, NtxString(attr.value()));
|
||||
*/
|
||||
}
|
||||
|
||||
// 4. Kinder rekursiv verarbeiten
|
||||
for (pugi::xml_node child : xmlNode.children())
|
||||
{
|
||||
NtxNodePtr childNode = parseElementRecursive(child);
|
||||
if (childNode)
|
||||
{
|
||||
newNode->addChild(childNode);
|
||||
}
|
||||
}
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
NtxNodePtr NtxNodeXml::loadFromFile(const std::string& filename)
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(filename.c_str());
|
||||
|
||||
if (!result)
|
||||
{
|
||||
std::cerr << "XML Load Error: " << result.description() << " in " << filename << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Wir nehmen den ersten Root-Knoten (das Wurzelelement)
|
||||
// doc.document_element() gibt das erste Kind vom Typ Element zurück (ignoriert PI, DOCTYPE etc.)
|
||||
return parseElementRecursive(doc.document_element());
|
||||
}
|
||||
|
||||
NtxNodePtr NtxNodeXml::loadFromString(const std::string& xmlContent)
|
||||
{
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_string(xmlContent.c_str());
|
||||
|
||||
if (!result)
|
||||
{
|
||||
std::cerr << "XML Parse Error: " << result.description() << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return parseElementRecursive(doc.document_element());
|
||||
}
|
||||
|
||||
// Rekursive Hilfsfunktion zum Serialisieren
|
||||
static void serializeNodeRecursive(const NtxNodeCPtr& node, pugi::xml_node& xmlNode)
|
||||
{
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
// 1. Tag-Name aus CType-Label holen
|
||||
NtxString tagName = node->getClassTypeLabel();
|
||||
pugi::xml_node childElement = xmlNode.append_child(tagName.c_str());
|
||||
|
||||
// 2. Alle Attribute aus dem Payload schreiben
|
||||
node->forEachProperty([&childElement](const NtxString& key, const NtxVariant& value)
|
||||
{
|
||||
// Konvertiere NtxVariant zu String und setze als XML-Attribut
|
||||
std::visit([&childElement, &key](auto&& arg)
|
||||
{
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
|
||||
if constexpr (std::is_same_v<T, std::monostate>)
|
||||
{
|
||||
// Überspringe leere Werte
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, NtxString>)
|
||||
{
|
||||
childElement.append_attribute(key.c_str()).set_value(arg.c_str());
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, int>)
|
||||
{
|
||||
childElement.append_attribute(key.c_str()).set_value(arg);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, double>)
|
||||
{
|
||||
childElement.append_attribute(key.c_str()).set_value(arg);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, bool>)
|
||||
{
|
||||
childElement.append_attribute(key.c_str()).set_value(arg);
|
||||
}
|
||||
}, value);
|
||||
});
|
||||
|
||||
// 3. Kinder rekursiv verarbeiten
|
||||
for (size_t i = 0; i < node->getChildCount(); ++i)
|
||||
{
|
||||
NtxNodeCPtr child = node->getChild(i);
|
||||
if (child)
|
||||
{
|
||||
serializeNodeRecursive(child, childElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NtxNodeXml::saveToFile(const NtxNodePtr& node, const std::string& filename)
|
||||
{
|
||||
return saveToFile(std::const_pointer_cast<const NtxINode>(node), filename);
|
||||
}
|
||||
|
||||
bool NtxNodeXml::saveToFile(const NtxNodeCPtr& node, const std::string& filename)
|
||||
{
|
||||
if (!node)
|
||||
{
|
||||
std::cerr << "Cannot save null node to file: " << filename << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
pugi::xml_document doc;
|
||||
|
||||
// XML-Deklaration hinzufügen
|
||||
pugi::xml_node declaration = doc.prepend_child(pugi::node_declaration);
|
||||
declaration.append_attribute("version") = "1.0";
|
||||
declaration.append_attribute("encoding") = "UTF-8";
|
||||
|
||||
// Root-Node serialisieren
|
||||
serializeNodeRecursive(node, doc);
|
||||
|
||||
// In Datei speichern mit Formatierung
|
||||
bool success = doc.save_file(filename.c_str(), " ", pugi::format_default, pugi::encoding_utf8);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
std::cerr << "Failed to save XML to file: " << filename << std::endl;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
std::string NtxNodeXml::saveToString(const NtxNodePtr& node)
|
||||
{
|
||||
return saveToString(std::const_pointer_cast<const NtxINode>(node));
|
||||
}
|
||||
|
||||
std::string NtxNodeXml::saveToString(const NtxNodeCPtr& node)
|
||||
{
|
||||
if (!node)
|
||||
{
|
||||
std::cerr << "Cannot serialize null node to string" << std::endl;
|
||||
return "";
|
||||
}
|
||||
|
||||
pugi::xml_document doc;
|
||||
|
||||
// XML-Deklaration hinzufügen
|
||||
pugi::xml_node declaration = doc.prepend_child(pugi::node_declaration);
|
||||
declaration.append_attribute("version") = "1.0";
|
||||
declaration.append_attribute("encoding") = "UTF-8";
|
||||
|
||||
// Root-Node serialisieren
|
||||
serializeNodeRecursive(node, doc);
|
||||
|
||||
// In String konvertieren
|
||||
std::ostringstream oss;
|
||||
doc.save(oss, " ", pugi::format_default, pugi::encoding_utf8);
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
} // namespace ntx
|
||||
27
nodes/services/ntxnodexml.h
Normal file
27
nodes/services/ntxnodexml.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef NTX_NODEXML_H
|
||||
#define NTX_NODEXML_H
|
||||
|
||||
#include <ntxinode.h>
|
||||
#include <string>
|
||||
|
||||
namespace ntx
|
||||
{
|
||||
class NtxNodeXml
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
static NtxNodePtr loadFromFile(const std::string& filename);
|
||||
static NtxNodePtr loadFromString(const std::string& xmlContent);
|
||||
|
||||
// Methoden zum Speichern
|
||||
static bool saveToFile(const NtxNodePtr& node, const std::string& filename);
|
||||
static bool saveToFile(const NtxNodeCPtr& node, const std::string& filename);
|
||||
|
||||
static std::string saveToString(const NtxNodePtr& node);
|
||||
static std::string saveToString(const NtxNodeCPtr& node);
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif // NTX_NODEXML_H
|
||||
156
nodes/services/ntxundostack.cpp
Normal file
156
nodes/services/ntxundostack.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
#include <ntxundostack.h>
|
||||
|
||||
namespace ntx
|
||||
{
|
||||
|
||||
NtxUndoStack::NtxUndoStack()
|
||||
: m_index(0)
|
||||
, m_cleanIndex(0)
|
||||
, m_undoLimit(0)
|
||||
{
|
||||
}
|
||||
|
||||
void NtxUndoStack::push(NtxCommandUPtr command)
|
||||
{
|
||||
if (!command)
|
||||
return;
|
||||
|
||||
if (!command->canExecute())
|
||||
return;
|
||||
|
||||
if (!command->execute())
|
||||
return;
|
||||
|
||||
if (m_index < static_cast<int>(m_commands.size()))
|
||||
{
|
||||
m_commands.erase(m_commands.begin() + m_index, m_commands.end());
|
||||
}
|
||||
|
||||
m_commands.push_back(std::move(command));
|
||||
m_index++;
|
||||
|
||||
if (m_cleanIndex > m_index)
|
||||
m_cleanIndex = -1;
|
||||
|
||||
checkUndoLimit();
|
||||
}
|
||||
|
||||
bool NtxUndoStack::canUndo() const
|
||||
{
|
||||
return m_index > 0;
|
||||
}
|
||||
|
||||
bool NtxUndoStack::canRedo() const
|
||||
{
|
||||
return m_index < static_cast<int>(m_commands.size());
|
||||
}
|
||||
|
||||
void NtxUndoStack::undo()
|
||||
{
|
||||
if (!canUndo())
|
||||
return;
|
||||
|
||||
m_index--;
|
||||
|
||||
if (m_commands[m_index]->canUndo())
|
||||
{
|
||||
m_commands[m_index]->undo();
|
||||
}
|
||||
}
|
||||
|
||||
void NtxUndoStack::redo()
|
||||
{
|
||||
if (!canRedo())
|
||||
return;
|
||||
|
||||
if (m_commands[m_index]->canExecute())
|
||||
{
|
||||
m_commands[m_index]->execute();
|
||||
}
|
||||
|
||||
m_index++;
|
||||
}
|
||||
|
||||
int NtxUndoStack::index() const
|
||||
{
|
||||
return m_index;
|
||||
}
|
||||
|
||||
int NtxUndoStack::count() const
|
||||
{
|
||||
return static_cast<int>(m_commands.size());
|
||||
}
|
||||
|
||||
NtxString NtxUndoStack::undoText() const
|
||||
{
|
||||
if (!canUndo())
|
||||
return NtxString();
|
||||
|
||||
return m_commands[m_index - 1]->getDescription();
|
||||
}
|
||||
|
||||
NtxString NtxUndoStack::redoText() const
|
||||
{
|
||||
if (!canRedo())
|
||||
return NtxString();
|
||||
|
||||
return m_commands[m_index]->getDescription();
|
||||
}
|
||||
|
||||
void NtxUndoStack::clear()
|
||||
{
|
||||
m_commands.clear();
|
||||
m_index = 0;
|
||||
m_cleanIndex = 0;
|
||||
}
|
||||
|
||||
void NtxUndoStack::setClean()
|
||||
{
|
||||
m_cleanIndex = m_index;
|
||||
}
|
||||
|
||||
bool NtxUndoStack::isClean() const
|
||||
{
|
||||
return m_cleanIndex == m_index;
|
||||
}
|
||||
|
||||
void NtxUndoStack::setUndoLimit(int limit)
|
||||
{
|
||||
m_undoLimit = limit;
|
||||
checkUndoLimit();
|
||||
}
|
||||
|
||||
int NtxUndoStack::undoLimit() const
|
||||
{
|
||||
return m_undoLimit;
|
||||
}
|
||||
|
||||
const NtxICommand* NtxUndoStack::command(int index) const
|
||||
{
|
||||
if (index < 0 || index >= static_cast<int>(m_commands.size()))
|
||||
return nullptr;
|
||||
|
||||
return m_commands[index].get();
|
||||
}
|
||||
|
||||
void NtxUndoStack::checkUndoLimit()
|
||||
{
|
||||
if (m_undoLimit <= 0)
|
||||
return;
|
||||
|
||||
int deleteCount = m_index - m_undoLimit;
|
||||
if (deleteCount <= 0)
|
||||
return;
|
||||
|
||||
m_commands.erase(m_commands.begin(), m_commands.begin() + deleteCount);
|
||||
m_index -= deleteCount;
|
||||
|
||||
if (m_cleanIndex != -1)
|
||||
{
|
||||
m_cleanIndex -= deleteCount;
|
||||
if (m_cleanIndex < 0)
|
||||
m_cleanIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ntx
|
||||
56
nodes/services/ntxundostack.h
Normal file
56
nodes/services/ntxundostack.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef NTX_UNDO_STACK_H
|
||||
#define NTX_UNDO_STACK_H
|
||||
|
||||
#include <ntxicommand.h>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace ntx
|
||||
{
|
||||
|
||||
class NtxUndoStack
|
||||
{
|
||||
public:
|
||||
|
||||
NtxUndoStack();
|
||||
~NtxUndoStack() = default;
|
||||
|
||||
NtxUndoStack(const NtxUndoStack&) = delete;
|
||||
NtxUndoStack& operator=(const NtxUndoStack&) = delete;
|
||||
|
||||
void push(NtxCommandUPtr command);
|
||||
|
||||
bool canUndo() const;
|
||||
bool canRedo() const;
|
||||
|
||||
void undo();
|
||||
void redo();
|
||||
|
||||
int index() const;
|
||||
int count() const;
|
||||
|
||||
NtxString undoText() const;
|
||||
NtxString redoText() const;
|
||||
|
||||
void clear();
|
||||
void setClean();
|
||||
bool isClean() const;
|
||||
|
||||
void setUndoLimit(int limit);
|
||||
int undoLimit() const;
|
||||
|
||||
const NtxICommand* command(int index) const;
|
||||
|
||||
private:
|
||||
|
||||
std::vector<NtxCommandUPtr> m_commands;
|
||||
int m_index;
|
||||
int m_cleanIndex;
|
||||
int m_undoLimit;
|
||||
|
||||
void checkUndoLimit();
|
||||
};
|
||||
|
||||
} // namespace ntx
|
||||
|
||||
#endif // NTX_UNDO_STACK_H
|
||||
Reference in New Issue
Block a user