added nodes.

This commit is contained in:
Christoph Holzheuer
2026-04-02 15:15:28 +02:00
parent b05180f575
commit 0b54793b08
43 changed files with 4371 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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