first commit, again.
This commit is contained in:
148
src/model/xqcommand.cpp
Normal file
148
src/model/xqcommand.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
/***************************************************************************
|
||||
|
||||
source::worx xtree
|
||||
Copyright © 2024-2025 c.holzheuer
|
||||
christoph.holzheuer@gmail.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include <xqcommand.h>
|
||||
#include <xqviewmodel.h>
|
||||
#include <xqtreetable.h>
|
||||
|
||||
|
||||
//! hilfsfunktion: zeigt alle position und die zugehörigen knoten an.
|
||||
|
||||
void XQNodeStore::dumpList( const QString& title ) const
|
||||
{
|
||||
if( !title.isEmpty() )
|
||||
qDebug() << " --- " << title;
|
||||
for( const auto& entry : *this )
|
||||
qDebug() << " -- dumpList: itemPos: " << entry.itemPos << " nodePos: " << entry.nodePos << " id: " << ( entry.contentNode ? entry.contentNode->_id : 0 ) << " used: " << ( entry.contentNode ? entry.contentNode.use_count() : 0 );
|
||||
}
|
||||
|
||||
|
||||
//! kostruktor. übergibt command-type und die aufrufende modelView.
|
||||
|
||||
XQCommand::XQCommand(CmdType cmdType, XQViewModel* modelView )
|
||||
: _cmdType{ cmdType }, _model(modelView)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! destruktor
|
||||
|
||||
XQCommand::~XQCommand()
|
||||
{
|
||||
qDebug() << " --- command destructor: " << toString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//! gibt den enum-type dieses commands zurück.
|
||||
|
||||
XQCommand::CmdType XQCommand::commandType() const
|
||||
{
|
||||
return _cmdType;
|
||||
}
|
||||
|
||||
|
||||
//! setzt den enum-type dieses commands.
|
||||
|
||||
void XQCommand::setCommandType( XQCommand::CmdType cmdType )
|
||||
{
|
||||
_cmdType = cmdType;
|
||||
}
|
||||
|
||||
|
||||
//! ruft 'onCommandRedo' 'meines' models auf.
|
||||
|
||||
void XQCommand::redo()
|
||||
{
|
||||
_model->onCommandRedo( *this );
|
||||
}
|
||||
|
||||
|
||||
//! ruft 'onCommandUndo' 'meines' models auf.
|
||||
|
||||
void XQCommand::undo()
|
||||
{
|
||||
_model->onCommandUndo( *this );
|
||||
}
|
||||
|
||||
|
||||
//! gibt den urpsrungs-index dieses commands zurück.
|
||||
|
||||
const QModelIndex& XQCommand::originIndex() const
|
||||
{
|
||||
return _originIndex;
|
||||
}
|
||||
|
||||
|
||||
//! merkt sich den ersten QModelIndex der von diesem command
|
||||
//! betroffenen items. zusätzlich wird der command-text erzeugt.
|
||||
|
||||
void XQCommand::setOriginIndex( const QModelIndex& origin )
|
||||
{
|
||||
QString cmdText("%1: %2 (%3)");
|
||||
QString name = origin.data().toString();
|
||||
QString items("%1 item%2");
|
||||
int mySize = size();
|
||||
items = items.arg(mySize).arg(mySize > 1 ? "s" : "");
|
||||
cmdText = cmdText.arg( toString(), name, items );
|
||||
_originIndex = origin;
|
||||
setText(cmdText);
|
||||
}
|
||||
|
||||
|
||||
//! erzeugt aus den 'selected indices' eine liste mit der jewiligen knotenposition,
|
||||
//! der item-zeile und dem content-knoten.
|
||||
|
||||
void XQCommand::saveNodes( const QModelIndexList& list )
|
||||
{
|
||||
clear();
|
||||
// über jede zeil
|
||||
for( auto entry : list )
|
||||
{
|
||||
// knoten holen
|
||||
const XQNodePtr& contentNode = XQItem::xqItemFromIndex( entry ).contentNode();
|
||||
// hier speichern wir den original knoten, nicht einen clone, wie im clipboard.
|
||||
push_back( {entry.row(), contentNode->own_pos(), contentNode } );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//! erzeugt einen string aus dem command-type, fürs debuggen.
|
||||
|
||||
QString XQCommand::toString()
|
||||
{
|
||||
|
||||
static QMap<CmdType,QString> s_CmdTypeMap
|
||||
{
|
||||
{ cmdTextEdit, "cmdTextEdit" },
|
||||
{ cmdInvalid, "cmdInvalid" },
|
||||
{ cmdCut, "cmdCut" },
|
||||
{ cmdPaste, "cmdPaste" },
|
||||
{ cmdPasteSelf, "cmdPasteSelf" },
|
||||
{ cmdNew, "cmdNew" },
|
||||
{ cmdUndo, "cmdUndo" },
|
||||
{ cmdRedo, "cmdRedo" },
|
||||
{ cmdCopy, "cmdCopy" },
|
||||
{ cmdMove, "cmdMove" },
|
||||
{ cmdDelete, "cmdDelete" },
|
||||
{ cmdToggleSection, "cmdToggleSection" },
|
||||
{ cmdExtern, "cmdExtern" }
|
||||
};
|
||||
|
||||
if( !s_CmdTypeMap.contains( _cmdType ))
|
||||
return QString(" cmdType missmatch");
|
||||
return s_CmdTypeMap[_cmdType];
|
||||
|
||||
}
|
107
src/model/xqcommand.h
Normal file
107
src/model/xqcommand.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/***************************************************************************
|
||||
|
||||
source::worx xtree
|
||||
Copyright © 2024-2025 c.holzheuer
|
||||
christoph.holzheuer@gmail.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#ifndef XQCOMMAND_H
|
||||
#define XQCOMMAND_H
|
||||
|
||||
#include <QUndoCommand>
|
||||
#include <xqitem.h>
|
||||
|
||||
class XQViewModel;
|
||||
|
||||
struct XQNodeBackup
|
||||
{
|
||||
int itemPos{-1};
|
||||
int nodePos{-1};
|
||||
XQNodePtr contentNode;
|
||||
};
|
||||
|
||||
class XQNodeStore : public QVector<XQNodeBackup>
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
void dumpList( const QString& title="" ) const;
|
||||
virtual void saveNodes( const QModelIndexList& list ) = 0;
|
||||
|
||||
};
|
||||
|
||||
// Das command enthält immer auch die betroffenen items
|
||||
// ist also auch eine SavedNodeList
|
||||
class XQCommand : public QUndoCommand, public XQNodeStore
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
enum CmdType
|
||||
{
|
||||
cmdInvalid,
|
||||
|
||||
cmdUndo,
|
||||
cmdRedo,
|
||||
|
||||
cmdTextEdit,
|
||||
|
||||
cmdCut,
|
||||
cmdPaste,
|
||||
cmdPasteSelf,
|
||||
cmdCopy,
|
||||
cmdMove,
|
||||
cmdNew,
|
||||
cmdDelete,
|
||||
|
||||
cmdToggleSection,
|
||||
|
||||
cmdExtern //??
|
||||
};
|
||||
|
||||
XQCommand(CmdType cmdType, XQViewModel* modelView );
|
||||
virtual ~XQCommand();
|
||||
|
||||
CmdType commandType() const;
|
||||
void setCommandType( CmdType cmdType );
|
||||
|
||||
const QModelIndex& originIndex() const;
|
||||
void setOriginIndex( const QModelIndex& origin );
|
||||
|
||||
void saveNodes( const QModelIndexList& list ) override;
|
||||
|
||||
void redo() override;
|
||||
void undo() override;
|
||||
|
||||
QString toString();
|
||||
|
||||
protected:
|
||||
|
||||
CmdType _cmdType{cmdInvalid};
|
||||
XQViewModel* _model{}; // needed for redo() / undo()
|
||||
QModelIndex _originIndex;
|
||||
|
||||
/*
|
||||
|
||||
Du hast den item editor vergessen, Du Honk!
|
||||
|
||||
NTCompositeModel* m_pModel;
|
||||
QModelIndex m_index;
|
||||
QVariant m_value;
|
||||
QVariant m_oldValue;
|
||||
bool m_updateIndex;
|
||||
*/
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(XQCommand::CmdType);
|
||||
|
||||
#endif // XQCOMMAND_H
|
||||
|
206
src/model/xqmodelsectionlist.cpp
Normal file
206
src/model/xqmodelsectionlist.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
/***************************************************************************
|
||||
|
||||
source::worx xtree
|
||||
Copyright © 2024-2025 c.holzheuer
|
||||
christoph.holzheuer@gmail.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include <xqmodelsectionlist.h>
|
||||
|
||||
|
||||
//! kontstruktor. übergibt den start-index und einen model-knoten mit der beschreibung
|
||||
//! der datenknoten.
|
||||
|
||||
XQModelSection::XQModelSection(const QModelIndex& modelIndex, XQNodePtr sheetNode)
|
||||
: _modelIndex{ modelIndex }, _sectionRootNode{ sheetNode }
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! elementvergleich.
|
||||
|
||||
bool XQModelSection::operator==(const XQModelSection& other) const
|
||||
{
|
||||
return _modelIndex == other._modelIndex && _sectionRootNode == other._sectionRootNode;
|
||||
}
|
||||
|
||||
|
||||
//! true wenn der start-index valide und ein model-knoten vorhanden.
|
||||
|
||||
bool XQModelSection::isValid() const
|
||||
{
|
||||
return _modelIndex.isValid() && _sectionRootNode;
|
||||
}
|
||||
|
||||
const QModelIndex& XQModelSection::modelIndex() const
|
||||
{
|
||||
return _modelIndex;
|
||||
}
|
||||
|
||||
XQNodePtr XQModelSection::sectionRootNode() const
|
||||
{
|
||||
return _sectionRootNode;
|
||||
}
|
||||
|
||||
//! Gibt den sheet-node zurück, das ist die model-beschreibung,
|
||||
//! siehe modelsheet.xml:
|
||||
//! <section>
|
||||
//! <header>
|
||||
//! <data> <- dort
|
||||
|
||||
//! __fix! das versteht doch kein mensch!
|
||||
|
||||
XQNodePtr XQModelSection::sheetRootNode() const
|
||||
{
|
||||
return _sectionRootNode->find_child_by_tag_name( c_ModelSheet );
|
||||
}
|
||||
|
||||
|
||||
//! Gibt den content root node zurück, das ist der
|
||||
//! zeiger auf die realen inhalte.
|
||||
|
||||
XQNodePtr XQModelSection::contentRootNode() const
|
||||
{
|
||||
return _contentRootNode;
|
||||
}
|
||||
|
||||
void XQModelSection::setContentRootNode( const XQNodePtr contentRootNode )
|
||||
{
|
||||
_contentRootNode = contentRootNode;
|
||||
}
|
||||
|
||||
|
||||
//! gibt die zeile des start-index zurück.
|
||||
|
||||
int XQModelSection::XQModelSection::row() const
|
||||
{
|
||||
return _modelIndex.row();
|
||||
}
|
||||
|
||||
|
||||
//! gibt den 'content type' zurück.
|
||||
|
||||
const QString& XQModelSection::contentType() const
|
||||
{
|
||||
return _sectionRootNode->attribute( c_ContentType );
|
||||
}
|
||||
|
||||
|
||||
//! gibt das dieser section entsprechende header-item zurück.
|
||||
|
||||
XQItem& XQModelSection::XQModelSection::headerItem() const
|
||||
{
|
||||
return XQItem::xqItemFromIndex( _modelIndex );
|
||||
}
|
||||
|
||||
|
||||
//! testet, ob die unter 'sectionKey' eine gültige section vorhanden ist.
|
||||
|
||||
bool XQModelSectionList::hasValidSection(const QString& sectionKey) const
|
||||
{
|
||||
if (!contains(sectionKey) )
|
||||
return false;
|
||||
return at(sectionKey).isValid();
|
||||
}
|
||||
|
||||
|
||||
//! gibt für einen model index die 'zuständige' section zurück.
|
||||
|
||||
const XQModelSection& XQModelSectionList::sectionFromIndex( const QModelIndex& index ) const
|
||||
{
|
||||
return sectionFromRow( index.row() );
|
||||
}
|
||||
|
||||
|
||||
//! gibt für eine zeile die 'zuständige' section zurück: der bestand an section wird
|
||||
//! nach der passenden section durchsucht.
|
||||
|
||||
const XQModelSection& XQModelSectionList::sectionFromRow(int itemRow ) const
|
||||
{
|
||||
|
||||
int i = size() - 1;
|
||||
for (; i >= 0; --i)
|
||||
{
|
||||
if ( at(i).modelIndex().row() < itemRow )
|
||||
return at(i);
|
||||
}
|
||||
|
||||
static XQModelSection s_DummySection;
|
||||
|
||||
return s_DummySection;
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! ermittelt die erste zeile einer section.
|
||||
|
||||
int XQModelSectionList::firstRow(const QModelIndex& idx) const
|
||||
{
|
||||
return sectionFromRow(idx.row() ).row();
|
||||
}
|
||||
|
||||
|
||||
//! ermittelt die zeile unterhalb des gegebenen modelindex,
|
||||
//! zum einfügen neuer items ebendort.
|
||||
|
||||
int XQModelSectionList::lastRow(const QModelIndex& idx) const
|
||||
{
|
||||
return lastRow(sectionFromRow(idx.row()));
|
||||
}
|
||||
|
||||
|
||||
//! ermittelt die zeile unterhalb der gegebenen section,
|
||||
//! zum einfügen neuer items ebendort.
|
||||
|
||||
int XQModelSectionList::lastRow(const XQModelSection& section ) const
|
||||
{
|
||||
//qDebug() << " -- last row in section: " << section.modelIndex.data().toString() << " --> " << section.modelIndex.row();
|
||||
// row() der section unterhalb dieser
|
||||
// __fix? index mit speichern?
|
||||
int index = indexOf(section);
|
||||
if (index > -1)
|
||||
{
|
||||
// last section? return last row of model
|
||||
if (index == size() - 1)
|
||||
return section.modelIndex().model()->rowCount();// - 1;
|
||||
// return row above the row of the next section -> last row of given section
|
||||
return at(index+1).row();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
//! gibt alle sections aus, zum ankucken.
|
||||
|
||||
void XQModelSectionList::dump() const
|
||||
{
|
||||
qDebug() << " --- sections dump(): " <<size() << " entries.";
|
||||
for( int i = 0; i<size(); ++i )
|
||||
{
|
||||
QModelIndex idx = at(i).modelIndex();
|
||||
qDebug() << " --- sections:" << i << "row: " << idx.row() << " keyOf(i): " << keyOf(i) << " indexData: "<< idx.data().toString() << " itemData: " << XQItem::xqItemFromIndex(idx).data(Qt::DisplayRole).toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
85
src/model/xqmodelsectionlist.h
Normal file
85
src/model/xqmodelsectionlist.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/***************************************************************************
|
||||
|
||||
source::worx xtree
|
||||
Copyright © 2024-2025 c.holzheuer
|
||||
christoph.holzheuer@gmail.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#ifndef XQMODELSECTIONLIST_H
|
||||
#define XQMODELSECTIONLIST_H
|
||||
|
||||
#include <QPersistentModelIndex>
|
||||
|
||||
#include <xqmaptor.h>
|
||||
#include <xqitem.h>
|
||||
|
||||
/**
|
||||
* @brief Struct containing data for a header section
|
||||
*/
|
||||
|
||||
class XQModelSection
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
|
||||
XQModelSection() = default;
|
||||
XQModelSection(const QModelIndex& modelIndex, XQNodePtr sheetNode );
|
||||
|
||||
bool operator==(const XQModelSection& other) const;
|
||||
bool isValid() const;
|
||||
int row() const;
|
||||
|
||||
const QModelIndex& modelIndex() const;
|
||||
XQNodePtr sectionRootNode() const;
|
||||
XQNodePtr sheetRootNode() const;
|
||||
XQNodePtr contentRootNode() const;
|
||||
void setContentRootNode( const XQNodePtr dataRootNode );
|
||||
|
||||
const QString& contentType() const;
|
||||
XQItem& headerItem() const;
|
||||
|
||||
protected:
|
||||
|
||||
QPersistentModelIndex _modelIndex;
|
||||
|
||||
XQNodePtr _sectionRootNode{};
|
||||
XQNodePtr _contentRootNode{};
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(XQModelSection)
|
||||
|
||||
/**
|
||||
* @brief Maptor containing all header sections.
|
||||
*/
|
||||
|
||||
class XQModelSectionList : public XQMaptor<XQModelSection>
|
||||
{
|
||||
public:
|
||||
|
||||
XQModelSectionList() = default;
|
||||
virtual ~XQModelSectionList() = default;
|
||||
|
||||
void createSectionEntry(const XQItemList& list, const XQNodePtr& sheetNode );
|
||||
bool hasValidSection(const QString& sectionKey) const;
|
||||
|
||||
const XQModelSection& sectionFromRow( int row ) const;
|
||||
const XQModelSection& sectionFromIndex( const QModelIndex& index ) const;
|
||||
|
||||
int firstRow(const QModelIndex& idx) const;
|
||||
int lastRow(const QModelIndex& idx) const;
|
||||
int lastRow(const XQModelSection& section) const;
|
||||
|
||||
void dump()const override;
|
||||
|
||||
};
|
||||
|
||||
#endif // XQMODELSECTIONLIST_H
|
92
src/model/xqnode.cpp
Normal file
92
src/model/xqnode.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/***************************************************************************
|
||||
|
||||
source::worx xtree
|
||||
Copyright © 2024-2025 c.holzheuer
|
||||
christoph.holzheuer@gmail.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include <xqnode.h>
|
||||
#include <xqitem.h>
|
||||
|
||||
|
||||
|
||||
|
||||
//! hilfsfunktion: gibt diesen teilbaum rekursiv aus
|
||||
|
||||
void inspect( const XQNodePtr& node, int indent )
|
||||
{
|
||||
qDebug() << std::string(indent * 2, ' ').c_str() << node.use_count() << ": " << node->to_string();
|
||||
if (node->has_children())
|
||||
{
|
||||
for (const auto& child : node->children())
|
||||
{
|
||||
inspect( child, indent + 1 );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! operator<< für QString und std::ostream
|
||||
|
||||
// Overload the operator<< for MyClass and std::ostream
|
||||
std::ostream& operator<<(std::ostream& os, const QString& obj)
|
||||
{
|
||||
// Simply call the getter and insert the string into the stream
|
||||
os << obj.toStdString();
|
||||
return os; // Return the stream for chaining
|
||||
}
|
||||
|
||||
|
||||
//! 'QString' implementation von split
|
||||
|
||||
template<>
|
||||
bool znode::zpayload<QString>::xstr_split_by(const QString& entry, const QString& sep, QString& key, QString& value )
|
||||
{
|
||||
int index = entry.indexOf(sep);
|
||||
if(index < 0)
|
||||
return false;
|
||||
key = entry.left(index);
|
||||
value = entry.mid( index+sep.length() );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//! 'QString' implementation von substr
|
||||
|
||||
template<>
|
||||
QString znode::zpayload<QString>::xstr_sub_str( const QString& entry, int pos ) const
|
||||
{
|
||||
return entry.mid(pos);
|
||||
}
|
||||
|
||||
|
||||
//! 'QString' implementation vom test auf 'empty'
|
||||
|
||||
template<>
|
||||
bool znode::zpayload<QString>::xstr_is_empty(const QString& entry ) const
|
||||
{
|
||||
return entry.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
//! 'QString' varianten der keystrings.
|
||||
|
||||
template<>
|
||||
const QString znode::zpayload<QString>::cType = "Type";
|
||||
|
||||
template<>
|
||||
const QString znode::zpayload<QString>::cName = "Name";
|
||||
|
||||
template<>
|
||||
const QString znode::zpayload<QString>::cValue = "Value";
|
||||
|
||||
template<>
|
||||
const QString znode::zpayload<QString>::cFriendlyName = "FriendlyName";
|
47
src/model/xqnode.h
Normal file
47
src/model/xqnode.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/***************************************************************************
|
||||
|
||||
source::worx xtree
|
||||
Copyright © 2024-2025 c.holzheuer
|
||||
christoph.holzheuer@gmail.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#ifndef XQNODE_H
|
||||
#define XQNODE_H
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QModelIndex>
|
||||
|
||||
#include <znode.h>
|
||||
#include <xqappdata.h>
|
||||
#include <znode_factory.h>
|
||||
|
||||
//! überlädt den operator<< für QString und std::ostream
|
||||
std::ostream& operator<<(std::ostream& os, const QString& obj);
|
||||
|
||||
//! raw node
|
||||
using XQNode = znode::zbasic_node<QString>;
|
||||
//! default shared node
|
||||
using XQNodePtr = std::shared_ptr<znode::zbasic_node<QString>>;
|
||||
|
||||
//! die node factory
|
||||
using XQNodeFactory = znode::znode_factory<QString>;
|
||||
|
||||
|
||||
|
||||
//void inspect( XQNodePtr node, int offSet=0 );
|
||||
void inspect( const XQNodePtr& node, int indent=0 );
|
||||
|
||||
Q_DECLARE_METATYPE(XQNodePtr);
|
||||
|
||||
|
||||
#endif // XQNODE_H
|
65
src/model/xqnodewriter.cpp
Normal file
65
src/model/xqnodewriter.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/***************************************************************************
|
||||
|
||||
source::worx xtree
|
||||
Copyright © 2024-2025 c.holzheuer
|
||||
christoph.holzheuer@gmail.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include <xqnodewriter.h>
|
||||
|
||||
#include <QFile>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#include <xqnode.h>
|
||||
|
||||
|
||||
|
||||
|
||||
//! schreibt einen (teil)baum in ein file
|
||||
|
||||
void XQNodeWriter::dumpTree( XQNodePtr rootNode, const QString& fileName ) const
|
||||
{
|
||||
QFile treeFile( fileName );
|
||||
if (!treeFile.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
throw XQException("can't open", fileName);
|
||||
|
||||
QXmlStreamWriter writer(&treeFile);
|
||||
writer.setAutoFormatting(true); // Makes the output more readable
|
||||
writer.writeStartDocument();
|
||||
|
||||
dumpNode( writer, rootNode );
|
||||
|
||||
writer.writeEndDocument();
|
||||
treeFile.close();
|
||||
}
|
||||
|
||||
|
||||
//! schreibt einen knoten in einen stream
|
||||
|
||||
void XQNodeWriter::dumpNode( QXmlStreamWriter& writer, XQNodePtr node ) const
|
||||
{
|
||||
//qDebug() << " --- dumpNode: id:" << node._id;
|
||||
|
||||
writer.writeStartElement(node->tag_name() );
|
||||
|
||||
if( !node->attributes().empty() )
|
||||
{
|
||||
for( const auto& attrEntry : node->attributes() )
|
||||
writer.writeAttribute( attrEntry.first , attrEntry.second );
|
||||
}
|
||||
|
||||
if( node->has_children() )
|
||||
{
|
||||
for (auto& child : node->children())
|
||||
dumpNode( writer, child );
|
||||
}
|
||||
|
||||
writer.writeEndElement();
|
||||
}
|
42
src/model/xqnodewriter.h
Normal file
42
src/model/xqnodewriter.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/***************************************************************************
|
||||
|
||||
source::worx xtree
|
||||
Copyright © 2024-2025 c.holzheuer
|
||||
christoph.holzheuer@gmail.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#ifndef XQNODEWRITER_H
|
||||
#define XQNODEWRITER_H
|
||||
|
||||
#include <Qt>
|
||||
|
||||
#include "qxmlstream.h"
|
||||
#include <xqnode.h>
|
||||
|
||||
|
||||
class QString;
|
||||
|
||||
class XQNodeWriter
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
XQNodeWriter() = default;
|
||||
virtual ~XQNodeWriter() = default;
|
||||
|
||||
void dumpTree( XQNodePtr rootNode, const QString& fileName ) const;
|
||||
|
||||
protected:
|
||||
|
||||
void dumpNode( QXmlStreamWriter& writer, XQNodePtr node ) const;
|
||||
|
||||
};
|
||||
|
||||
#endif // XQNODEWRITER_H
|
70
src/model/xqselectionmodel.cpp
Normal file
70
src/model/xqselectionmodel.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
/***************************************************************************
|
||||
|
||||
source::worx xtree
|
||||
Copyright © 2024-2025 c.holzheuer
|
||||
christoph.holzheuer@gmail.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include <xqselectionmodel.h>
|
||||
#include <xqitem.h>
|
||||
|
||||
|
||||
|
||||
//! konstruiert ein selectionmodel.
|
||||
|
||||
XQSelectionModel::XQSelectionModel(QAbstractItemModel* model)
|
||||
: QItemSelectionModel(model)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! konstruiert ein selectionmodel.
|
||||
|
||||
XQSelectionModel::XQSelectionModel(QAbstractItemModel* model, QObject* parent)
|
||||
: QItemSelectionModel(model, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! firz
|
||||
|
||||
void XQSelectionModel::select(const QItemSelection& selection, QItemSelectionModel::SelectionFlags command)
|
||||
{
|
||||
// step #0: fetch selected indices.
|
||||
const QModelIndexList list = selection.indexes();
|
||||
if (list.isEmpty() || selectedRows().isEmpty() )
|
||||
return QItemSelectionModel::select(selection, command);
|
||||
|
||||
// fetch first index
|
||||
QModelIndex firstValid = list.first();
|
||||
if (hasSelection() )
|
||||
firstValid = selectedRows().first();
|
||||
|
||||
//XQItem& firstItem = XQItem::xqItemFromIndex(firstValid);
|
||||
//if( firstItem.isValid() )
|
||||
{
|
||||
|
||||
XQNodePtr firstNode = XQItem::xqItemFromIndex(firstValid).contentNode();
|
||||
QItemSelection newSelection;
|
||||
// __fixme! das crasht!
|
||||
|
||||
for (const QModelIndex& idx : list)
|
||||
{
|
||||
XQNodePtr nextNode = XQItem::xqItemFromIndex(idx).contentNode();
|
||||
if (!nextNode || idx.data().toString().isEmpty() || nextNode->tag_name() != firstNode->tag_name() )
|
||||
break;
|
||||
newSelection.select(idx, idx);
|
||||
}
|
||||
return QItemSelectionModel::select(newSelection, command);
|
||||
}
|
||||
QItemSelectionModel::select(selection, command);
|
||||
}
|
41
src/model/xqselectionmodel.h
Normal file
41
src/model/xqselectionmodel.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/***************************************************************************
|
||||
|
||||
source::worx xtree
|
||||
Copyright © 2024-2025 c.holzheuer
|
||||
christoph.holzheuer@gmail.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#ifndef XQSELECTIONMODEL_H
|
||||
#define XQSELECTIONMODEL_H
|
||||
|
||||
#include <QItemSelectionModel>
|
||||
#include <QObject>
|
||||
|
||||
/**
|
||||
* @brief Extends QItemSelectionModel so that the selection is limited to a single section entry.
|
||||
*/
|
||||
|
||||
class XQSelectionModel : public QItemSelectionModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
XQSelectionModel(QAbstractItemModel* model = nullptr);
|
||||
XQSelectionModel(QAbstractItemModel* model, QObject* parent);
|
||||
virtual ~XQSelectionModel() = default;
|
||||
|
||||
public slots:
|
||||
|
||||
void select(const QItemSelection& selection, QItemSelectionModel::SelectionFlags command) override;
|
||||
|
||||
};
|
||||
|
||||
#endif // XQSELECTIONMODEL_H
|
55
src/model/xqsimpleclipboard.cpp
Normal file
55
src/model/xqsimpleclipboard.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
/***************************************************************************
|
||||
|
||||
source::worx xtree
|
||||
Copyright © 2024-2025 c.holzheuer
|
||||
christoph.holzheuer@gmail.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include <xqsimpleclipboard.h>
|
||||
#include <xqviewmodel.h>
|
||||
|
||||
|
||||
//! true, wenn paste an er stelle 'curIdx' möglich ist.
|
||||
|
||||
bool XQSimpleClipBoard::canPaste( const QModelIndex& curIdx ) const
|
||||
{
|
||||
bool pasteOk = false;
|
||||
if( !isEmpty() )
|
||||
{
|
||||
XQItem& item = XQItem::xqItemFromIndex(curIdx);
|
||||
// __fixme! header items haben keinen ZNode!
|
||||
qDebug() << " --- can paste: " << item.contentNode()->tag_name() << " nodelist: " << front().contentNode->tag_name();
|
||||
// paste is only allowed for the same component.type, which
|
||||
// is coded in the tag_type
|
||||
pasteOk = item.contentNode()->tag_name() == front().contentNode->tag_name();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << " -- ClipBoard: nodelist empty!";
|
||||
}
|
||||
return pasteOk;
|
||||
}
|
||||
|
||||
|
||||
//! erzeugt eine positions-list aus der liste selectierter indicies.
|
||||
//! Der mit seiner position zusammen gespeicherter knoten muss hier
|
||||
//! gekloned werden.
|
||||
|
||||
void XQSimpleClipBoard::saveNodes( const QModelIndexList& list )
|
||||
{
|
||||
clear();
|
||||
for( auto entry : list )
|
||||
{
|
||||
XQNodePtr contentNode = XQItem::xqItemFromIndex( entry ).contentNode();
|
||||
// im clipboard brauchen wir eine eltern-lose kopie des knotens
|
||||
push_back( {entry.row(), contentNode->own_pos(), contentNode->clone() } );
|
||||
}
|
||||
}
|
||||
|
36
src/model/xqsimpleclipboard.h
Normal file
36
src/model/xqsimpleclipboard.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/***************************************************************************
|
||||
|
||||
source::worx xtree
|
||||
Copyright © 2024-2025 c.holzheuer
|
||||
christoph.holzheuer@gmail.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#ifndef XQSIMPLECLIPBOARD_H
|
||||
#define XQSIMPLECLIPBOARD_H
|
||||
|
||||
#include <xqitem.h>
|
||||
#include <xqcommand.h>
|
||||
|
||||
|
||||
class XQSimpleClipBoard : public XQNodeStore
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
XQSimpleClipBoard() = default;
|
||||
virtual ~XQSimpleClipBoard() = default;
|
||||
|
||||
bool canPaste( const QModelIndex& curIdx ) const;
|
||||
|
||||
void saveNodes( const QModelIndexList& list ) override;
|
||||
|
||||
};
|
||||
|
||||
#endif // XQSIMPLECLIPBOARD_H
|
601
src/model/xqviewmodel.cpp
Normal file
601
src/model/xqviewmodel.cpp
Normal file
@@ -0,0 +1,601 @@
|
||||
/***************************************************************************
|
||||
|
||||
source::worx xtree
|
||||
Copyright © 2024-2025 c.holzheuer
|
||||
christoph.holzheuer@gmail.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QUndoStack>
|
||||
|
||||
#include <xqexception.h>
|
||||
#include <xqviewmodel.h>
|
||||
#include <xqselectionmodel.h>
|
||||
#include <xqtreetable.h>
|
||||
#include <xqcommand.h>
|
||||
#include <xqitemdelegate.h>
|
||||
#include <xqitemfactory.h>
|
||||
#include <znode_factory.h>
|
||||
|
||||
|
||||
// create global dummy item as
|
||||
// fallback return value (klappt nicht)
|
||||
//Q_GLOBAL_STATIC(XQItem,s_dummyItem)
|
||||
|
||||
|
||||
//! hilfsfunkion, zeigt den string-content() für alle elemente der liste
|
||||
|
||||
void showItemList( const XQItemList& list)
|
||||
{
|
||||
for(const auto& entry : list )
|
||||
qDebug() << " --- itemList: " << ((XQItem*)entry)->content();
|
||||
qDebug();
|
||||
}
|
||||
|
||||
|
||||
//! Konstruktur mit parent.
|
||||
|
||||
XQViewModel::XQViewModel( QObject* parent )
|
||||
: QStandardItemModel{ parent }, _itemFactory{ XQItemFactory::instance() }
|
||||
{
|
||||
invisibleRootItem()->setData( "[rootItem]", Qt::DisplayRole );
|
||||
setItemPrototype( new XQItem );
|
||||
}
|
||||
|
||||
|
||||
//! gibt einen static-cast<QXItem*> auf 'invisibleRootItem()' zurück
|
||||
|
||||
const XQItem& XQViewModel::xqRootItem()
|
||||
{
|
||||
// das ist ein hack, denn 'invisibleRootItem()' ist und bleibt ein
|
||||
// QStandardItem. Trick: keine eigenen members in XQItem, alles
|
||||
// dynamisch über den ItemData Mechanismus wie in QStandardItem
|
||||
|
||||
return *static_cast<XQItem*>(invisibleRootItem());
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! hifsfunktion, die das item zu einen index zurückgibt
|
||||
|
||||
XQItem& XQViewModel::xqItemFromIndex(const QModelIndex& index) const
|
||||
{
|
||||
if( index.isValid() )
|
||||
{
|
||||
QStandardItem* xqItem = QStandardItemModel::itemFromIndex(index);
|
||||
if( xqItem )
|
||||
return *static_cast<XQItem*>(xqItem);
|
||||
}
|
||||
return XQItem::fallBackDummyItem();
|
||||
}
|
||||
|
||||
//! hilfsfunktiom, die das erste xqitem einer zeile zurückgibt.
|
||||
|
||||
XQItem& XQViewModel::xqFirstItem(int row) const
|
||||
{
|
||||
return *static_cast<XQItem*>( QStandardItemModel::item(row) );
|
||||
}
|
||||
|
||||
|
||||
//! initialisiert dieses model über den namen. Es wird hier
|
||||
//! nur die strukur erzeugt, keine inhalte.
|
||||
|
||||
void XQViewModel::initModel(const QString& modelName)
|
||||
{
|
||||
/*
|
||||
model
|
||||
section
|
||||
header
|
||||
data
|
||||
section
|
||||
...
|
||||
|
||||
*/
|
||||
// model rootnode finden -> <DocumentTreeModel>
|
||||
XQNodePtr modelSheet = _itemFactory.findModelSheet( modelName ); // throws
|
||||
|
||||
// #1: über alle sections
|
||||
for( auto& sectionNode : modelSheet->children() )
|
||||
{
|
||||
// #2: (optionalen?) header erzeugen
|
||||
const XQNodePtr header = sectionNode->find_child_by_tag_name( c_Header );
|
||||
if( header )
|
||||
{
|
||||
XQItemList list = _itemFactory.makeHeaderRow( header );
|
||||
addSection(list, sectionNode );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//! Hilfsfunktion: fügt die item-liste unserem model hinzu und erzeugt eine 'section'.
|
||||
//! die section kann erst gültig sein, wenn die items im model gelandet sind,
|
||||
//! deswegen ist das hier zusammengefasst.
|
||||
|
||||
//! Wrzeugt dann eine section aus einer frisch erzeugten itemlist. Der erste modelindex
|
||||
//! der liste und der root knoten der model-beschreibung werden gespeichert.
|
||||
|
||||
void XQViewModel::addSection(const XQItemList& list, const XQNodePtr& sectionNode )
|
||||
{
|
||||
// 1. die liste darf nicht leer sein
|
||||
Q_ASSERT(!list.isEmpty());
|
||||
// 2. sectionNode muss da sein
|
||||
Q_ASSERT(sectionNode);
|
||||
// 3. 'ContenType' muss vorhanden sein
|
||||
if( !sectionNode->has_attribute( c_ContentType) )
|
||||
throw XQException( "section list: Section node needs attribute 'ContentType'!");
|
||||
|
||||
// 5. das erzeugt dann auch valide indices
|
||||
appendRow(list);
|
||||
|
||||
// 6. die beschreibung der daten liegt im unterknoten 'Data'
|
||||
|
||||
|
||||
// 6. jetzt können wir auch die sction erzeugen
|
||||
XQModelSection section(list[0]->index(), sectionNode );
|
||||
_sections.addAtKey(sectionNode->attribute( c_ContentType), section);
|
||||
|
||||
// ... und es der welt mitteilen.
|
||||
emit sectionCreated( section );
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! SLOT, der aufgerufen wird, wenn eine edit-action getriggert wurde.
|
||||
|
||||
void XQViewModel::onActionTriggered(QAction* action)
|
||||
{
|
||||
qDebug() << " --- onActionTriggered: count:" << XQNode::s_Count;
|
||||
|
||||
// all selected indices
|
||||
QModelIndexList selectionList = treeTable()->selectionModel()->selectedRows();
|
||||
// extract command type
|
||||
XQCommand::CmdType cmdType = action->data().value<XQCommand::CmdType>();
|
||||
|
||||
switch( cmdType )
|
||||
{
|
||||
// just handle undo ...
|
||||
case XQCommand::cmdUndo :
|
||||
return _undoStack->undo();
|
||||
|
||||
// ... or do/redo
|
||||
case XQCommand::cmdRedo :
|
||||
return _undoStack->redo();
|
||||
|
||||
// for copy & cut, we create a clone of the dataNodes in the clipboard
|
||||
case XQCommand::cmdCopy :
|
||||
case XQCommand::cmdCut :
|
||||
// don't 'copy' empty selections
|
||||
if( !selectionList.isEmpty() )
|
||||
_clipBoard.saveNodes( selectionList );
|
||||
// for copy, we are done, since copy cannot be undone
|
||||
if( cmdType == XQCommand::cmdCopy )
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// we create a command
|
||||
XQCommand* command = new XQCommand( cmdType, this );
|
||||
// store the row positions of the selected indices
|
||||
command->saveNodes( selectionList );
|
||||
command->setOriginIndex( treeTable()->currentIndex() );
|
||||
|
||||
// execute command
|
||||
_undoStack->push( command );
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
switch (command.commandType())
|
||||
{
|
||||
case XQCommand::cmdToggleSection:
|
||||
return cmdToggleSection( command.originIndex() );
|
||||
|
||||
case XQCommand::cmdCut:
|
||||
return cmdCut( command );
|
||||
|
||||
case XQCommand::cmdPaste:
|
||||
return cmdPaste( command );
|
||||
|
||||
case XQCommand::cmdNew:
|
||||
return cmdNew( command );
|
||||
|
||||
case XQCommand::cmdDelete:
|
||||
return cmdDelete( command );
|
||||
|
||||
case XQCommand::cmdMove:
|
||||
break;
|
||||
|
||||
default:
|
||||
qDebug() << " --- onCommandRedo: default: not handled: " << command.toString();
|
||||
}
|
||||
*/
|
||||
|
||||
//! führt die 'redo' action des gegebenen commnds aus.
|
||||
|
||||
void XQViewModel::onCommandRedo( XQCommand& command )
|
||||
{
|
||||
static MemCallMap redoCalls
|
||||
{
|
||||
{ XQCommand::cmdToggleSection, &XQViewModel::cmdToggleSection },
|
||||
{ XQCommand::cmdCut, &XQViewModel::cmdCut },
|
||||
{ XQCommand::cmdPaste, &XQViewModel::cmdPaste },
|
||||
{ XQCommand::cmdNew, &XQViewModel::cmdNew },
|
||||
{ XQCommand::cmdDelete, &XQViewModel::cmdDelete }
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
MemCall memCall = redoCalls[command.commandType()];
|
||||
if( memCall )
|
||||
(this->*memCall)( command );
|
||||
else
|
||||
qDebug() << " --- onCommandRedo: default: not handled: " << command.toString();
|
||||
}
|
||||
catch( XQException& exception )
|
||||
{
|
||||
qDebug() << exception.what();
|
||||
QMessageBox::critical( nullptr, "Failure", QString("Failure: %1").arg(exception.what()) );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
try
|
||||
{
|
||||
switch (command.commandType())
|
||||
{
|
||||
case XQCommand::cmdToggleSection:
|
||||
return cmdToggleSection( command.originIndex() );
|
||||
break;
|
||||
|
||||
// undo Cut -> perform undoCut
|
||||
case XQCommand::cmdCut:
|
||||
return cmdCutUndo( command );
|
||||
|
||||
// undo Paste -> perform Cut
|
||||
case XQCommand::cmdPaste:
|
||||
return cmdPasteUndo( command );
|
||||
|
||||
// undo Move -> perform move back
|
||||
case XQCommand::cmdMove:
|
||||
// not yet implemented
|
||||
break;
|
||||
|
||||
// undo New -> perform Delete
|
||||
case XQCommand::cmdNew:
|
||||
cmdNewUndo( command );
|
||||
break;
|
||||
|
||||
// undo Delete -> perform New
|
||||
case XQCommand::cmdDelete:
|
||||
qDebug() << " --- onCommandUndo: delete: " << command.toString();
|
||||
return cmdDeleteUndo( command );
|
||||
|
||||
default:
|
||||
qDebug() << " --- onCommandUndo: default: not handled: " << command.toString();
|
||||
}
|
||||
*/
|
||||
//! führt die 'undo' action des gegebenen commnds aus.
|
||||
|
||||
void XQViewModel::onCommandUndo( XQCommand& command )
|
||||
{
|
||||
qDebug() << " --- onCommandUndo: count: " << XQNode::s_Count;
|
||||
|
||||
static MemCallMap undoCalls
|
||||
{
|
||||
{ XQCommand::cmdToggleSection, &XQViewModel::cmdToggleSection },
|
||||
{ XQCommand::cmdCut, &XQViewModel::cmdCutUndo },
|
||||
{ XQCommand::cmdPaste, &XQViewModel::cmdPasteUndo },
|
||||
{ XQCommand::cmdNew, &XQViewModel::cmdNewUndo },
|
||||
{ XQCommand::cmdDelete, &XQViewModel::cmdDeleteUndo },
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
MemCall memCall = undoCalls[command.commandType()];
|
||||
if( memCall )
|
||||
(this->*memCall)( command );
|
||||
else
|
||||
qDebug() << " --- onCommandUndo: default: not handled: " << command.toString();
|
||||
}
|
||||
catch( XQException& exception )
|
||||
{
|
||||
qDebug() << exception.what();
|
||||
QMessageBox::critical( nullptr, "Failure", QString("Failure: %1").arg(exception.what()) );
|
||||
}
|
||||
}
|
||||
|
||||
// undo-/redo-able stuff
|
||||
|
||||
//! markierte knoten entfernen, 'command' enthält die liste
|
||||
|
||||
void XQViewModel::cmdCut( XQCommand& command )
|
||||
{
|
||||
// wir gehen rückwärts über alle gemerkten knoten ...
|
||||
for (auto it = command.rbegin(); it != command.rend(); ++it)
|
||||
{
|
||||
// ... holen das erste item, das auch den content node enthält
|
||||
//const XQNodeBackup& entry = *it;
|
||||
// jetzt löschen, dabei wird die parent-verbindung entfernt
|
||||
const XQNodeBackup& entry = *it;
|
||||
|
||||
XQItem& firstItem = xqFirstItem( (*it).itemPos );
|
||||
qDebug() << " --- Cut: " << firstItem.text() << " " << firstItem.row() << " id#" << entry.contentNode->_id;
|
||||
|
||||
entry.contentNode->unlink_self();
|
||||
removeRow(entry.itemPos );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//! entfernte knoten wieder einfügen , 'command' enthält die liste
|
||||
|
||||
void XQViewModel::cmdCutUndo( XQCommand& command )
|
||||
{
|
||||
// die anfangsposition
|
||||
int itmPos = command.first().itemPos;
|
||||
// die 'zuständige' section rausfinden
|
||||
const XQModelSection& section = _sections.sectionFromRow( itmPos );
|
||||
// über alle einträge ...
|
||||
for (auto& entry : command )
|
||||
{
|
||||
const XQNodePtr& savedNode = entry.contentNode;
|
||||
// __fix! should not be _contentRoot!
|
||||
savedNode->add_me_at( entry.nodePos, _contentRoot );
|
||||
XQItemList list = _itemFactory.makeContentRow( section.sheetRootNode(), savedNode );
|
||||
|
||||
XQItem& firstItem = *((XQItem*)list[0]);
|
||||
qDebug() << " --- Cut Undo: " << firstItem.text() << " " << firstItem.row() << " id#" << entry.contentNode->_id << " count: " << entry.contentNode.use_count();
|
||||
|
||||
insertRow( entry.itemPos, list );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//! clipboard inhalte einfügen
|
||||
|
||||
void XQViewModel::cmdPaste( XQCommand& command )
|
||||
{
|
||||
// selection holen ...
|
||||
QItemSelectionModel* selectionModel = treeTable()->selectionModel();
|
||||
// ... und löschen
|
||||
selectionModel->clearSelection();
|
||||
|
||||
// aktuelles item finden
|
||||
const XQItem& item = xqItemFromIndex( command.originIndex() );
|
||||
|
||||
// die neue item position ist unter dem akutellen item
|
||||
int insRow = item.row()+1;
|
||||
int nodePos = item.contentNode()->own_pos()+1;
|
||||
|
||||
// die zugehörige section finden
|
||||
const XQModelSection& section = _sections.sectionFromRow( insRow-1 );
|
||||
// wir pasten das clipboard
|
||||
for (auto& entry : _clipBoard )
|
||||
{
|
||||
// noch ein clone vom clone erzeugen ...
|
||||
XQNodePtr newNode = entry.contentNode->clone(section.contentRootNode() );
|
||||
newNode->clone(section.contentRootNode() )->add_me_at( nodePos );
|
||||
// ... und damit eine frische item-row erzeugen
|
||||
XQItemList list = _itemFactory.makeContentRow( section.sheetRootNode(), newNode );
|
||||
insertRow( insRow, list );
|
||||
// die neue item-row selektieren
|
||||
const QModelIndex& selIdx = list[0]->index();
|
||||
_treeTable->selectionModel()->select(selIdx, QItemSelectionModel::Select | QItemSelectionModel::Rows);
|
||||
// zur nächsten zeile
|
||||
insRow++;
|
||||
nodePos++;
|
||||
}
|
||||
|
||||
// unsere änderungen merken fürs 'undo'
|
||||
command.saveNodes( selectionModel->selectedRows() );
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! einfügen aus dem clipboard wieder rückgängig machen
|
||||
|
||||
void XQViewModel::cmdPasteUndo( XQCommand& command )
|
||||
{
|
||||
command.dumpList("Paste UNDO");
|
||||
// wir gehen rückwärts über alle markieren knoten ...
|
||||
for (auto it = command.rbegin(); it != command.rend(); ++it)
|
||||
{
|
||||
// ... holen das erste item, das auch den content node enthält
|
||||
const XQNodeBackup& entry = *it;
|
||||
XQItem& firstItem = xqFirstItem( (*it).itemPos );
|
||||
qDebug() << " --- Cut: " << firstItem.text() << " " << firstItem.row();
|
||||
// jetzt löschen
|
||||
entry.contentNode->unlink_self();
|
||||
removeRow(entry.itemPos );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// don't clone into clipboard, remove items
|
||||
|
||||
//! entfernen der selection ohne copy in clipboard.
|
||||
|
||||
void XQViewModel::cmdDelete( XQCommand& command )
|
||||
{
|
||||
// wir gehen rückwärts über alle markieren knoten ...
|
||||
for (auto it = command.rbegin(); it != command.rend(); ++it)
|
||||
{
|
||||
// ... holen das erste item, das auch den content node enthält
|
||||
const XQNodeBackup& entry = *it;
|
||||
XQItem& firstItem = xqFirstItem( (*it).itemPos );
|
||||
qDebug() << " --- Cut: " << firstItem.text() << " " << firstItem.row();
|
||||
// jetzt löschen
|
||||
entry.contentNode->unlink_self();
|
||||
removeRow(entry.itemPos );
|
||||
}
|
||||
}
|
||||
|
||||
//! macht 'delete' wirder rückgängig.
|
||||
|
||||
void XQViewModel::cmdDeleteUndo( XQCommand& command )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! legt eine neue, leere zeile an.
|
||||
|
||||
void XQViewModel::cmdNew( XQCommand& command )
|
||||
{
|
||||
|
||||
// __fix
|
||||
/*
|
||||
const QModelIndex& origin = command.originIndex();
|
||||
if( !origin.isValid() )
|
||||
throw XQException("cmdNewRow failed: index not valid ");
|
||||
|
||||
XQItem* target = xqItemFromIndex( origin );
|
||||
// current data node
|
||||
XQNodePtr node = target->contentNode();
|
||||
|
||||
// we create a new data node
|
||||
//XQNodePtr newNode = new XQNodePtr( node->tag_name(), node->parent() );
|
||||
XQNodePtr newNode = XQNode::make_node( node->tag_name(), node->tag_value(), node->parent() );
|
||||
// store node in node->parent()
|
||||
//node->add_before_me( newNode );
|
||||
// store node also in 'command' to enable undo
|
||||
const XQModelSection& section = _sections.sectionFromIndex( origin );
|
||||
|
||||
// create new item row
|
||||
XQItemList list = _itemFactory.createGenericRow( newNode, section.sheetRootNode );
|
||||
|
||||
// add it to the treeview ...
|
||||
insertRow( origin.row(), list );
|
||||
|
||||
// ... and make it ...
|
||||
treeTable()->setCurrentIndex( list[0]->index() );
|
||||
// ... editable
|
||||
treeTable()->edit( list[0]->index() );
|
||||
*/
|
||||
}
|
||||
|
||||
//! entfernt die neu angelegte zeile.
|
||||
|
||||
void XQViewModel::cmdNewUndo( XQCommand& command )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//! schaltet eine section sichtbar oder unsichtbar.
|
||||
|
||||
void XQViewModel::cmdToggleSection( XQCommand& command )
|
||||
{
|
||||
const QModelIndex& index = command.originIndex();
|
||||
Q_ASSERT(index.isValid());
|
||||
|
||||
int fstRow = _sections.firstRow( index );
|
||||
int lstRow = _sections.lastRow( index );
|
||||
|
||||
bool hidden =_treeTable->isRowHidden( fstRow, _treeTable->rootIndex() );
|
||||
for (int row = fstRow; row < lstRow; ++row )
|
||||
_treeTable->setRowHidden( row, _treeTable->rootIndex(), !hidden );
|
||||
|
||||
emit sectionToggled( _sections.sectionFromIndex(index) );
|
||||
}
|
||||
|
||||
|
||||
//! git die treetable zurück
|
||||
|
||||
XQTreeTable* XQViewModel::treeTable()
|
||||
{
|
||||
return _treeTable;
|
||||
}
|
||||
|
||||
//! setzt die treetable als member.
|
||||
|
||||
void XQViewModel::setTreeTable(XQTreeTable* mainView )
|
||||
{
|
||||
// store view for direct access: the maintree
|
||||
_treeTable = mainView;
|
||||
// connect myself as model to the mainview
|
||||
_treeTable->setModel(this);
|
||||
XQItemDelegate* delegate = new XQItemDelegate( *this );
|
||||
_treeTable->setItemDelegate( delegate );
|
||||
|
||||
_contextMenu = new XQContextMenu( mainView );
|
||||
|
||||
connect( _treeTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onShowContextMenu(QPoint)));
|
||||
//connect( _treeTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(onDoubleClicked(QModelIndex)) );
|
||||
connect(_contextMenu, SIGNAL(triggered(QAction*)), this, SLOT(onActionTriggered(QAction*)));
|
||||
|
||||
// __fixme, die view soll über das modelsheet konfiguriert werden!
|
||||
setupViewProperties();
|
||||
}
|
||||
|
||||
|
||||
//! setzt die eigenschaften der TreeTable.
|
||||
|
||||
void XQViewModel::setupViewProperties()
|
||||
{
|
||||
_treeTable->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
_treeTable->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
|
||||
_treeTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
_treeTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
//_treeTable->setSelectionMode(QAbstractItemView::ContiguousSelection);
|
||||
_treeTable->setSelectionModel( new XQSelectionModel(this) );
|
||||
}
|
||||
|
||||
|
||||
//! gibt den undo-stack zurück.
|
||||
|
||||
QUndoStack* XQViewModel::undoStack()
|
||||
{
|
||||
return _undoStack;
|
||||
}
|
||||
|
||||
|
||||
//! setzt den undo-stack.
|
||||
|
||||
void XQViewModel::setUndoStack( QUndoStack* undoStack )
|
||||
{
|
||||
_undoStack = undoStack;
|
||||
}
|
||||
|
||||
|
||||
//! SLOT, der die erstellung & anzeige es context-menues triggert.
|
||||
|
||||
void XQViewModel::onShowContextMenu(const QPoint& point)
|
||||
{
|
||||
initContextMenu();
|
||||
_contextMenu->popup(_treeTable->mapToGlobal(point));
|
||||
}
|
||||
|
||||
|
||||
//! gibt die namen der neuen data-roles zurück.
|
||||
//! __fix: die alten roles fehlen hier!
|
||||
|
||||
QHash<int, QByteArray> XQViewModel::roleNames() const
|
||||
{
|
||||
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[XQItem::ContentRole] = "content";
|
||||
roles[XQItem::ItemTypeRole] = "itemType";
|
||||
roles[XQItem::RenderStyleRole] = "renderStyle";
|
||||
roles[XQItem::EditorTypeRole] = "editorType";
|
||||
roles[XQItem::UnitTypeRole] = "unitType";
|
||||
roles[XQItem::FixedChoicesRole] = "fixedChoices";
|
||||
roles[XQItem::ContentNodeRole] = "contentNode";
|
||||
roles[XQItem::SheetNodeRole] = "sheetNode";
|
||||
roles[XQItem::TypeKeyRole] = "typeKey";
|
||||
|
||||
return roles;
|
||||
|
||||
}
|
||||
|
599
src/model/xqviewmodel.cpp~RF540b760.TMP
Normal file
599
src/model/xqviewmodel.cpp~RF540b760.TMP
Normal file
@@ -0,0 +1,599 @@
|
||||
/***************************************************************************
|
||||
|
||||
source::worx xtree
|
||||
Copyright © 2024-2025 c.holzheuer
|
||||
christoph.holzheuer@gmail.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QUndoStack>
|
||||
|
||||
#include <xqexception.h>
|
||||
#include <xqviewmodel.h>
|
||||
#include <xqselectionmodel.h>
|
||||
#include <xqtreetable.h>
|
||||
#include <xqcommand.h>
|
||||
#include <xqitemdelegate.h>
|
||||
#include <xqitemfactory.h>
|
||||
#include <znode_factory.h>
|
||||
|
||||
|
||||
// create global dummy item as
|
||||
// fallback return value (klappt nicht)
|
||||
//Q_GLOBAL_STATIC(XQItem,s_dummyItem)
|
||||
|
||||
|
||||
//! hilfsfunkion, zeigt den string-content() für alle elemente der liste
|
||||
|
||||
void showItemList( const XQItemList& list)
|
||||
{
|
||||
for(const auto& entry : list )
|
||||
qDebug() << " --- itemList: " << ((XQItem*)entry)->content();
|
||||
qDebug();
|
||||
}
|
||||
|
||||
|
||||
//! Konstruktur mit parent.
|
||||
|
||||
XQViewModel::XQViewModel( QObject* parent )
|
||||
: QStandardItemModel{ parent }, _itemFactory{ XQItemFactory::instance() }
|
||||
{
|
||||
invisibleRootItem()->setData( "[rootItem]", Qt::DisplayRole );
|
||||
setItemPrototype( new XQItem );
|
||||
}
|
||||
|
||||
|
||||
//! gibt einen static-cast<QXItem*> auf 'invisibleRootItem()' zurück
|
||||
|
||||
const XQItem& XQViewModel::xqRootItem()
|
||||
{
|
||||
// das ist ein hack, denn 'invisibleRootItem()' ist und bleibt ein
|
||||
// QStandardItem. Trick: keine eigenen members in XQItem, alles
|
||||
// dynamisch über den ItemData Mechanismus wie in QStandardItem
|
||||
|
||||
return *static_cast<XQItem*>(invisibleRootItem());
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! hifsfunktion, die das item zu einen index zurückgibt
|
||||
|
||||
XQItem& XQViewModel::xqItemFromIndex(const QModelIndex& index) const
|
||||
{
|
||||
if( index.isValid() )
|
||||
{
|
||||
QStandardItem* xqItem = QStandardItemModel::itemFromIndex(index);
|
||||
if( xqItem )
|
||||
return *static_cast<XQItem*>(xqItem);
|
||||
}
|
||||
return XQItem::fallBackDummyItem();
|
||||
}
|
||||
|
||||
//! hilfsfunktiom, die das erste xqitem einer zeile zurückgibt.
|
||||
|
||||
XQItem& XQViewModel::xqFirstItem(int row) const
|
||||
{
|
||||
return *static_cast<XQItem*>( QStandardItemModel::item(row) );
|
||||
}
|
||||
|
||||
|
||||
//! initialisiert dieses model über den namen. Es wird hier
|
||||
//! nur die strukur erzeugt, keine inhalte.
|
||||
|
||||
void XQViewModel::initModel(const QString& modelName)
|
||||
{
|
||||
/*
|
||||
model
|
||||
section
|
||||
header
|
||||
data
|
||||
section
|
||||
...
|
||||
|
||||
*/
|
||||
// model rootnode finden -> <DocumentTreeModel>
|
||||
XQNodePtr modelSheet = _itemFactory.findModelSheet( modelName ); // throws
|
||||
|
||||
// #1: über alle sections
|
||||
for( auto& section : modelSheet->children() )
|
||||
{
|
||||
// #2: (optionalen?) header erzeugen
|
||||
const XQNodePtr header = section->find_child_by_tag_name( "Header");
|
||||
if( header )
|
||||
{
|
||||
XQItemList list = _itemFactory.makeContentRow( header, nullptr );
|
||||
addSection(list, section );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//! hilfsfunktion: fügt die liste unserem model hinzu und erzeugt eine 'section'.
|
||||
//! die section kann erst gültig sein, wenn die items im model gelandet sind,
|
||||
//! deswegen ist das hier zusammengefasst.
|
||||
|
||||
//! erzeugt dann eine section aus einer frisch erzeugten itemlist. der erste modelindex
|
||||
//! der liste und der unterknoten 'Data' werden gespeichert.
|
||||
|
||||
void XQViewModel::addSection(const XQItemList& list, const XQNodePtr& sheetNode )
|
||||
{
|
||||
// 1. die liste darf nicht leer sein
|
||||
Q_ASSERT(!list.isEmpty());
|
||||
// 2. sheetNode muss da sein
|
||||
Q_ASSERT(sheetNode);
|
||||
// 3. 'ContenType' muss vorhanden sein
|
||||
if( !sheetNode->has_attribute( c_ContentType) )
|
||||
throw XQException( "section list: Section node needs attribute 'ContentType'!");
|
||||
// 4. Data child muss auch da sein
|
||||
XQNodePtr dataNode = sheetNode->find_child_by_tag_name( c_Data );
|
||||
if( !dataNode )
|
||||
throw XQException( "section list: 'Data' child is missing!");
|
||||
|
||||
// 5. das erzeugt dann auch valide indices
|
||||
appendRow(list);
|
||||
|
||||
XQModelSection section(list[0]->index(), dataNode );
|
||||
_sections.addAtKey(sheetNode->attribute( c_ContentType), section);
|
||||
|
||||
emit sectionCreated( §ion );
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! SLOT, der aufgerufen wird, wenn eine edit-action getriggert wurde.
|
||||
|
||||
void XQViewModel::onActionTriggered(QAction* action)
|
||||
{
|
||||
qDebug() << " --- onActionTriggered: count:" << XQNode::s_Count;
|
||||
|
||||
// all selected indices
|
||||
QModelIndexList selectionList = treeTable()->selectionModel()->selectedRows();
|
||||
// extract command type
|
||||
XQCommand::CmdType cmdType = action->data().value<XQCommand::CmdType>();
|
||||
|
||||
switch( cmdType )
|
||||
{
|
||||
// just handle undo ...
|
||||
case XQCommand::cmdUndo :
|
||||
return _undoStack->undo();
|
||||
|
||||
// ... or do/redo
|
||||
case XQCommand::cmdRedo :
|
||||
return _undoStack->redo();
|
||||
|
||||
// for copy & cut, we create a clone of the dataNodes in the clipboard
|
||||
case XQCommand::cmdCopy :
|
||||
case XQCommand::cmdCut :
|
||||
// don't 'copy' empty selections
|
||||
if( !selectionList.isEmpty() )
|
||||
_clipBoard.saveNodes( selectionList );
|
||||
// for copy, we are done, since copy cannot be undone
|
||||
if( cmdType == XQCommand::cmdCopy )
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// we create a command
|
||||
XQCommand* command = new XQCommand( cmdType, this );
|
||||
// store the row positions of the selected indices
|
||||
command->saveNodes( selectionList );
|
||||
command->setOriginIndex( treeTable()->currentIndex() );
|
||||
|
||||
// execute command
|
||||
_undoStack->push( command );
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
switch (command.commandType())
|
||||
{
|
||||
case XQCommand::cmdToggleSection:
|
||||
return cmdToggleSection( command.originIndex() );
|
||||
|
||||
case XQCommand::cmdCut:
|
||||
return cmdCut( command );
|
||||
|
||||
case XQCommand::cmdPaste:
|
||||
return cmdPaste( command );
|
||||
|
||||
case XQCommand::cmdNew:
|
||||
return cmdNew( command );
|
||||
|
||||
case XQCommand::cmdDelete:
|
||||
return cmdDelete( command );
|
||||
|
||||
case XQCommand::cmdMove:
|
||||
break;
|
||||
|
||||
default:
|
||||
qDebug() << " --- onCommandRedo: default: not handled: " << command.toString();
|
||||
}
|
||||
*/
|
||||
|
||||
//! führt die 'redo' action des gegebenen commnds aus.
|
||||
|
||||
void XQViewModel::onCommandRedo( XQCommand& command )
|
||||
{
|
||||
static MemCallMap redoCalls
|
||||
{
|
||||
{ XQCommand::cmdToggleSection, &XQViewModel::cmdToggleSection },
|
||||
{ XQCommand::cmdCut, &XQViewModel::cmdCut },
|
||||
{ XQCommand::cmdPaste, &XQViewModel::cmdPaste },
|
||||
{ XQCommand::cmdNew, &XQViewModel::cmdNew },
|
||||
{ XQCommand::cmdDelete, &XQViewModel::cmdDelete }
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
MemCall memCall = redoCalls[command.commandType()];
|
||||
if( memCall )
|
||||
(this->*memCall)( command );
|
||||
else
|
||||
qDebug() << " --- onCommandRedo: default: not handled: " << command.toString();
|
||||
}
|
||||
catch( XQException& exception )
|
||||
{
|
||||
qDebug() << exception.what();
|
||||
QMessageBox::critical( nullptr, "Failure", QString("Failure: %1").arg(exception.what()) );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
try
|
||||
{
|
||||
switch (command.commandType())
|
||||
{
|
||||
case XQCommand::cmdToggleSection:
|
||||
return cmdToggleSection( command.originIndex() );
|
||||
break;
|
||||
|
||||
// undo Cut -> perform undoCut
|
||||
case XQCommand::cmdCut:
|
||||
return cmdCutUndo( command );
|
||||
|
||||
// undo Paste -> perform Cut
|
||||
case XQCommand::cmdPaste:
|
||||
return cmdPasteUndo( command );
|
||||
|
||||
// undo Move -> perform move back
|
||||
case XQCommand::cmdMove:
|
||||
// not yet implemented
|
||||
break;
|
||||
|
||||
// undo New -> perform Delete
|
||||
case XQCommand::cmdNew:
|
||||
cmdNewUndo( command );
|
||||
break;
|
||||
|
||||
// undo Delete -> perform New
|
||||
case XQCommand::cmdDelete:
|
||||
qDebug() << " --- onCommandUndo: delete: " << command.toString();
|
||||
return cmdDeleteUndo( command );
|
||||
|
||||
default:
|
||||
qDebug() << " --- onCommandUndo: default: not handled: " << command.toString();
|
||||
}
|
||||
*/
|
||||
//! führt die 'undo' action des gegebenen commnds aus.
|
||||
|
||||
void XQViewModel::onCommandUndo( XQCommand& command )
|
||||
{
|
||||
qDebug() << " --- onCommandUndo: count: " << XQNode::s_Count;
|
||||
|
||||
static MemCallMap undoCalls
|
||||
{
|
||||
{ XQCommand::cmdToggleSection, &XQViewModel::cmdToggleSection },
|
||||
{ XQCommand::cmdCut, &XQViewModel::cmdCutUndo },
|
||||
{ XQCommand::cmdPaste, &XQViewModel::cmdPasteUndo },
|
||||
{ XQCommand::cmdNew, &XQViewModel::cmdNewUndo },
|
||||
{ XQCommand::cmdDelete, &XQViewModel::cmdDeleteUndo },
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
MemCall memCall = undoCalls[command.commandType()];
|
||||
if( memCall )
|
||||
(this->*memCall)( command );
|
||||
else
|
||||
qDebug() << " --- onCommandUndo: default: not handled: " << command.toString();
|
||||
}
|
||||
catch( XQException& exception )
|
||||
{
|
||||
qDebug() << exception.what();
|
||||
QMessageBox::critical( nullptr, "Failure", QString("Failure: %1").arg(exception.what()) );
|
||||
}
|
||||
}
|
||||
|
||||
// undo-/redo-able stuff
|
||||
|
||||
//! markierte knoten entfernen, 'command' enthält die liste
|
||||
|
||||
void XQViewModel::cmdCut( XQCommand& command )
|
||||
{
|
||||
// wir gehen rückwärts über alle gemerkten knoten ...
|
||||
for (auto it = command.rbegin(); it != command.rend(); ++it)
|
||||
{
|
||||
// ... holen das erste item, das auch den content node enthält
|
||||
//const XQNodeBackup& entry = *it;
|
||||
// jetzt löschen, dabei wird die parent-verbindung entfernt
|
||||
const XQNodeBackup& entry = *it;
|
||||
|
||||
XQItem& firstItem = xqFirstItem( (*it).itemPos );
|
||||
qDebug() << " --- Cut: " << firstItem.text() << " " << firstItem.row() << " id#" << entry.contentNode->_id;
|
||||
|
||||
entry.contentNode->unlink_self();
|
||||
removeRow(entry.itemPos );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//! entfernte knoten wieder einfügen , 'command' enthält die liste
|
||||
|
||||
void XQViewModel::cmdCutUndo( XQCommand& command )
|
||||
{
|
||||
// die anfangsposition
|
||||
int itmPos = command.first().itemPos;
|
||||
// die 'zuständige' section rausfinden
|
||||
const XQModelSection& section = _sections.sectionFromRow( itmPos );
|
||||
// über alle einträge ...
|
||||
for (auto& entry : command )
|
||||
{
|
||||
const XQNodePtr& savedNode = entry.contentNode;
|
||||
// __fix! should not be _contentRoot!
|
||||
savedNode->add_me_at( entry.nodePos, _contentRoot );
|
||||
XQItemList list = _itemFactory.makeContentRow( section.sheetRootNode, savedNode );
|
||||
|
||||
XQItem& firstItem = *((XQItem*)list[0]);
|
||||
qDebug() << " --- Cut Undo: " << firstItem.text() << " " << firstItem.row() << " id#" << entry.contentNode->_id << " count: " << entry.contentNode.use_count();
|
||||
|
||||
insertRow( entry.itemPos, list );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//! clipboard inhalte einfügen
|
||||
|
||||
void XQViewModel::cmdPaste( XQCommand& command )
|
||||
{
|
||||
// selection holen ...
|
||||
QItemSelectionModel* selectionModel = treeTable()->selectionModel();
|
||||
// ... und löschen
|
||||
selectionModel->clearSelection();
|
||||
|
||||
// aktuelles item finden
|
||||
const XQItem& item = xqItemFromIndex( command.originIndex() );
|
||||
|
||||
// die neue item position ist unter dem akutellen item
|
||||
int insRow = item.row()+1;
|
||||
int nodePos = item.contentNode()->own_pos()+1;
|
||||
|
||||
// die zugehörige section finden
|
||||
const XQModelSection& section = _sections.sectionFromRow( insRow-1 );
|
||||
// wir pasten das clipboard
|
||||
for (auto& entry : _clipBoard )
|
||||
{
|
||||
// noch ein clone vom clone erzeugen ...
|
||||
XQNodePtr newNode = entry.contentNode->clone(section.contentRootNode );
|
||||
newNode->clone(section.contentRootNode )->add_me_at( nodePos );
|
||||
// ... und damit eine frische item-row erzeugen
|
||||
XQItemList list = _itemFactory.makeContentRow( section.sheetRootNode, newNode );
|
||||
insertRow( insRow, list );
|
||||
// die neue item-row selektieren
|
||||
const QModelIndex& selIdx = list[0]->index();
|
||||
_treeTable->selectionModel()->select(selIdx, QItemSelectionModel::Select | QItemSelectionModel::Rows);
|
||||
// zur nächsten zeile
|
||||
insRow++;
|
||||
nodePos++;
|
||||
}
|
||||
|
||||
// unsere änderungen merken fürs 'undo'
|
||||
command.saveNodes( selectionModel->selectedRows() );
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! einfügen aus dem clipboard wieder rückgängig machen
|
||||
|
||||
void XQViewModel::cmdPasteUndo( XQCommand& command )
|
||||
{
|
||||
command.dumpList("Paste UNDO");
|
||||
// wir gehen rückwärts über alle markieren knoten ...
|
||||
for (auto it = command.rbegin(); it != command.rend(); ++it)
|
||||
{
|
||||
// ... holen das erste item, das auch den content node enthält
|
||||
const XQNodeBackup& entry = *it;
|
||||
XQItem& firstItem = xqFirstItem( (*it).itemPos );
|
||||
qDebug() << " --- Cut: " << firstItem.text() << " " << firstItem.row();
|
||||
// jetzt löschen
|
||||
entry.contentNode->unlink_self();
|
||||
removeRow(entry.itemPos );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// don't clone into clipboard, remove items
|
||||
|
||||
//! entfernen der selection ohne copy in clipboard.
|
||||
|
||||
void XQViewModel::cmdDelete( XQCommand& command )
|
||||
{
|
||||
// wir gehen rückwärts über alle markieren knoten ...
|
||||
for (auto it = command.rbegin(); it != command.rend(); ++it)
|
||||
{
|
||||
// ... holen das erste item, das auch den content node enthält
|
||||
const XQNodeBackup& entry = *it;
|
||||
XQItem& firstItem = xqFirstItem( (*it).itemPos );
|
||||
qDebug() << " --- Cut: " << firstItem.text() << " " << firstItem.row();
|
||||
// jetzt löschen
|
||||
entry.contentNode->unlink_self();
|
||||
removeRow(entry.itemPos );
|
||||
}
|
||||
}
|
||||
|
||||
//! macht 'delete' wirder rückgängig.
|
||||
|
||||
void XQViewModel::cmdDeleteUndo( XQCommand& command )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! legt eine neue, leere zeile an.
|
||||
|
||||
void XQViewModel::cmdNew( XQCommand& command )
|
||||
{
|
||||
|
||||
// __fix
|
||||
/*
|
||||
const QModelIndex& origin = command.originIndex();
|
||||
if( !origin.isValid() )
|
||||
throw XQException("cmdNewRow failed: index not valid ");
|
||||
|
||||
XQItem* target = xqItemFromIndex( origin );
|
||||
// current data node
|
||||
XQNodePtr node = target->contentNode();
|
||||
|
||||
// we create a new data node
|
||||
//XQNodePtr newNode = new XQNodePtr( node->tag_name(), node->parent() );
|
||||
XQNodePtr newNode = XQNode::make_node( node->tag_name(), node->tag_value(), node->parent() );
|
||||
// store node in node->parent()
|
||||
//node->add_before_me( newNode );
|
||||
// store node also in 'command' to enable undo
|
||||
const XQModelSection& section = _sections.sectionFromIndex( origin );
|
||||
|
||||
// create new item row
|
||||
XQItemList list = _itemFactory.createGenericRow( newNode, section.sheetRootNode );
|
||||
|
||||
// add it to the treeview ...
|
||||
insertRow( origin.row(), list );
|
||||
|
||||
// ... and make it ...
|
||||
treeTable()->setCurrentIndex( list[0]->index() );
|
||||
// ... editable
|
||||
treeTable()->edit( list[0]->index() );
|
||||
*/
|
||||
}
|
||||
|
||||
//! entfernt die neu angelegte zeile.
|
||||
|
||||
void XQViewModel::cmdNewUndo( XQCommand& command )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//! schaltet eine section sichtbar oder unsichtbar.
|
||||
|
||||
void XQViewModel::cmdToggleSection( XQCommand& command )
|
||||
{
|
||||
const QModelIndex& index = command.originIndex();
|
||||
Q_ASSERT(index.isValid());
|
||||
|
||||
int fstRow = _sections.firstRow( index );
|
||||
int lstRow = _sections.lastRow( index );
|
||||
|
||||
bool hidden =_treeTable->isRowHidden( fstRow, _treeTable->rootIndex() );
|
||||
for (int row = fstRow; row < lstRow; ++row )
|
||||
_treeTable->setRowHidden( row, _treeTable->rootIndex(), !hidden );
|
||||
|
||||
}
|
||||
|
||||
|
||||
//! git die treetable zurück
|
||||
|
||||
XQTreeTable* XQViewModel::treeTable()
|
||||
{
|
||||
return _treeTable;
|
||||
}
|
||||
|
||||
//! setzt die treetable als member.
|
||||
|
||||
void XQViewModel::setTreeTable(XQTreeTable* mainView )
|
||||
{
|
||||
// store view for direct access: the maintree
|
||||
_treeTable = mainView;
|
||||
// connect myself as model to the mainview
|
||||
_treeTable->setModel(this);
|
||||
XQItemDelegate* delegate = new XQItemDelegate( *this );
|
||||
_treeTable->setItemDelegate( delegate );
|
||||
|
||||
_contextMenu = new XQContextMenu( mainView );
|
||||
|
||||
connect( _treeTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onShowContextMenu(QPoint)));
|
||||
//connect( _treeTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(onDoubleClicked(QModelIndex)) );
|
||||
connect(_contextMenu, SIGNAL(triggered(QAction*)), this, SLOT(onActionTriggered(QAction*)));
|
||||
|
||||
// __fixme, die view soll über das modelsheet konfiguriert werden!
|
||||
setupViewProperties();
|
||||
}
|
||||
|
||||
|
||||
//! setzt die eigenschaften der TreeTable.
|
||||
|
||||
void XQViewModel::setupViewProperties()
|
||||
{
|
||||
_treeTable->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
_treeTable->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
|
||||
_treeTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
_treeTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
//_treeTable->setSelectionMode(QAbstractItemView::ContiguousSelection);
|
||||
_treeTable->setSelectionModel( new XQSelectionModel(this) );
|
||||
}
|
||||
|
||||
|
||||
//! gibt den undo-stack zurück.
|
||||
|
||||
QUndoStack* XQViewModel::undoStack()
|
||||
{
|
||||
return _undoStack;
|
||||
}
|
||||
|
||||
|
||||
//! setzt den undo-stack.
|
||||
|
||||
void XQViewModel::setUndoStack( QUndoStack* undoStack )
|
||||
{
|
||||
_undoStack = undoStack;
|
||||
}
|
||||
|
||||
|
||||
//! SLOT, der die erstellung & anzeige es context-menues triggert.
|
||||
|
||||
void XQViewModel::onShowContextMenu(const QPoint& point)
|
||||
{
|
||||
initContextMenu();
|
||||
_contextMenu->popup(_treeTable->mapToGlobal(point));
|
||||
}
|
||||
|
||||
|
||||
//! gibt die namen der neuen data-roles zurück.
|
||||
//! __fix: die alten roles fehlen hier!
|
||||
|
||||
QHash<int, QByteArray> XQViewModel::roleNames() const
|
||||
{
|
||||
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[XQItem::ContentRole] = "content";
|
||||
roles[XQItem::ItemTypeRole] = "itemType";
|
||||
roles[XQItem::RenderStyleRole] = "renderStyle";
|
||||
roles[XQItem::EditorTypeRole] = "editorType";
|
||||
roles[XQItem::UnitTypeRole] = "unitType";
|
||||
roles[XQItem::FixedChoicesRole] = "fixedChoices";
|
||||
roles[XQItem::ContentNodeRole] = "contentNode";
|
||||
roles[XQItem::SheetNodeRole] = "sheetNode";
|
||||
roles[XQItem::TypeKeyRole] = "typeKey";
|
||||
|
||||
return roles;
|
||||
|
||||
}
|
||||
|
136
src/model/xqviewmodel.h
Normal file
136
src/model/xqviewmodel.h
Normal file
@@ -0,0 +1,136 @@
|
||||
/***************************************************************************
|
||||
|
||||
source::worx xtree
|
||||
Copyright © 2024-2025 c.holzheuer
|
||||
christoph.holzheuer@gmail.com
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
|
||||
#ifndef XQVIEWMODEL_H
|
||||
#define XQVIEWMODEL_H
|
||||
|
||||
#include <QUndoStack>
|
||||
#include <QMenu>
|
||||
#include <QStandardItemModel>
|
||||
#include <QAbstractItemView>
|
||||
|
||||
#include <xqsimpleclipboard.h>
|
||||
#include <xqmodelsectionlist.h>
|
||||
#include <xqitemfactory.h>
|
||||
#include <xqcontextmenu.h>
|
||||
|
||||
|
||||
class XQTreeTable;
|
||||
class XQItem;
|
||||
class XQCommand;
|
||||
|
||||
|
||||
//! ein erweitertes QStandardItemModel welches 'seine' view bereits enthält.
|
||||
|
||||
class XQViewModel : public QStandardItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
XQViewModel(QObject* parent = nullptr);
|
||||
virtual ~XQViewModel() = default;
|
||||
|
||||
XQTreeTable* treeTable();
|
||||
virtual void setTreeTable( XQTreeTable* mainView );
|
||||
|
||||
QUndoStack* undoStack();
|
||||
void setUndoStack( QUndoStack* undoStack );
|
||||
|
||||
virtual void initModel( const QString& modelName);
|
||||
|
||||
//little helpers
|
||||
const XQItem& xqRootItem();
|
||||
|
||||
XQItem& xqItemFromIndex(const QModelIndex& index) const;
|
||||
XQItem& xqFirstItem(int row) const;
|
||||
|
||||
// undo-/redo-able stuff
|
||||
|
||||
virtual void cmdToggleSection( XQCommand& command );
|
||||
virtual void cmdCut( XQCommand& command );
|
||||
virtual void cmdCutUndo( XQCommand& command );
|
||||
virtual void cmdPaste( XQCommand& command );
|
||||
virtual void cmdPasteUndo( XQCommand& command );
|
||||
virtual void cmdDelete( XQCommand& command );
|
||||
virtual void cmdDeleteUndo( XQCommand& command );
|
||||
virtual void cmdNew( XQCommand& command );
|
||||
virtual void cmdNewUndo( XQCommand& command );
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/*!
|
||||
|
||||
Derzeit wird die default-implementierung von data/setData genutzt. hier wäre dann die
|
||||
Stelle um setData & data an externe 'handler' umzubiegen, siehe giovannies 'model-injection'
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
|
||||
{
|
||||
return QStandardItemModel::data( index, role );
|
||||
}
|
||||
|
||||
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override
|
||||
{
|
||||
qDebug() << " --- setData: " << value.toString();
|
||||
return QStandardItemModel::setData( index, value, role );
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
public slots:
|
||||
|
||||
virtual void onShowContextMenu(const QPoint& point);
|
||||
virtual void onActionTriggered(QAction* action);
|
||||
|
||||
// handle XQCommands ( == UndoCommand )
|
||||
virtual void onCommandRedo( XQCommand& command );
|
||||
virtual void onCommandUndo( XQCommand& command );
|
||||
|
||||
signals:
|
||||
|
||||
void itemCreated( XQItem* newItem );
|
||||
void sectionCreated( const XQModelSection& section );
|
||||
void sectionToggled( const XQModelSection& section );
|
||||
|
||||
protected:
|
||||
|
||||
void addSection(const XQItemList& list, const XQNodePtr& sheetNode );
|
||||
virtual void initContextMenu() = 0;
|
||||
|
||||
// __fixme: should be created from xml
|
||||
virtual void setupViewProperties();
|
||||
|
||||
protected:
|
||||
|
||||
using MemCall = void (XQViewModel::*)(XQCommand&);
|
||||
using MemCallMap = QMap<XQCommand::CmdType,MemCall>;
|
||||
|
||||
// das eine reference auf ein globales singleton
|
||||
XQItemFactory& _itemFactory;
|
||||
XQSimpleClipBoard _clipBoard;
|
||||
XQModelSectionList _sections;
|
||||
|
||||
XQTreeTable* _treeTable{};
|
||||
//QAbstractItemView* _treeTable{};
|
||||
QUndoStack* _undoStack{};
|
||||
XQContextMenu* _contextMenu{};
|
||||
|
||||
//! Die Modelbeschreibung
|
||||
XQNodePtr _modelSheet{};
|
||||
//! Der eigentliche Inhalt
|
||||
XQNodePtr _contentRoot{};
|
||||
|
||||
};
|
||||
|
||||
#endif // XQVIEWMODEL_H
|
Reference in New Issue
Block a user