550 lines
14 KiB
C++
550 lines
14 KiB
C++
/***************************************************************************
|
|
|
|
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 <xqmodel.h>
|
|
#include <xqselectionmodel.h>
|
|
#include <xqtreeview.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)
|
|
|
|
|
|
|
|
XQModel::~XQModel()
|
|
{
|
|
}
|
|
|
|
|
|
XQModel::XQModel( QObject* parent )
|
|
: QStandardItemModel{ parent }, _itemFactory{ XQItemFactory::instance() }
|
|
{
|
|
invisibleRootItem()->setData( "[rootItem]", Qt::DisplayRole );
|
|
setItemPrototype( new XQItem );
|
|
}
|
|
|
|
|
|
const XQItem& XQModel::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());
|
|
|
|
}
|
|
|
|
|
|
XQItem& XQModel::xqItemFromIndex(const QModelIndex& index) const
|
|
{
|
|
if( index.isValid() )
|
|
{
|
|
QStandardItem* xqItem = QStandardItemModel::itemFromIndex(index);
|
|
if( xqItem )
|
|
return *static_cast<XQItem*>(xqItem);
|
|
}
|
|
return XQItem::fallBackDummyItem();
|
|
}
|
|
|
|
|
|
const XQItem& XQModel::xqConstItemFromIndex(const QModelIndex& index) const
|
|
{
|
|
QStandardItem* xqItem = QStandardItemModel::itemFromIndex(index);
|
|
if( xqItem )
|
|
return *static_cast<XQItem*>(xqItem);
|
|
|
|
return XQItem::fallBackDummyItem();
|
|
}
|
|
|
|
|
|
XQItem& XQModel::xqItemFromRow(int row) const
|
|
{
|
|
return *static_cast<XQItem*>( QStandardItemModel::item(row) );
|
|
}
|
|
|
|
const XQItem& XQModel::xqConstItemFromRow(int row) const
|
|
{
|
|
return *static_cast<XQItem*>( QStandardItemModel::item(row) );
|
|
}
|
|
|
|
|
|
QString XQModel::fetchNodeAttribute(int row, const QString& key )
|
|
{
|
|
/*
|
|
__fix
|
|
XQItem* item = fromRow(row);
|
|
if( item && item->hasNode() )
|
|
return item->contentNode()->attribute( key );
|
|
|
|
*/
|
|
return "";
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief XQModel::fetchNodeTagName get the tag_name of the contentNode
|
|
* in a row
|
|
* @param row the row
|
|
* @return
|
|
*/
|
|
|
|
QString XQModel::fetchNodeTagName( int row )
|
|
{
|
|
// __fix
|
|
/*
|
|
XQItem* item = fromRow(row);
|
|
if( item && item->hasNode() )
|
|
return item->contentNode()->tag_name();
|
|
*/
|
|
return "";
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief XQModel::onActionTriggered: called, when a context menu action is triggered.
|
|
* @param action
|
|
*/
|
|
|
|
void XQModel::onActionTriggered(QAction* action)
|
|
{
|
|
qDebug() << " --- onActionTriggered: count:" << XQNode::s_Count;
|
|
|
|
// all selected indices
|
|
QModelIndexList selectionList = treeView()->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.createContentNodeList( 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( treeView()->currentIndex() );
|
|
|
|
// execute command
|
|
_undoStack->push( command );
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief XQModel::onCommandRedo called to execute a command ('do').
|
|
* @param command the current command
|
|
*/
|
|
|
|
void XQModel::onCommandRedo( XQCommand& command )
|
|
{
|
|
qDebug() << " --- onCommandRedo: count:" << XQNode::s_Count;
|
|
|
|
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();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief XQModel::onCommandUndo: called to 'undo' a command.
|
|
* @param command the command to be undone.
|
|
*/
|
|
|
|
void XQModel::onCommandUndo( XQCommand& command )
|
|
{
|
|
qDebug() << " --- onCommandUndo: count: " << XQNode::s_Count;
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
// undo-/redo-able stuff
|
|
|
|
/**
|
|
* @brief XQModel::cmdCutRows Cut the rows whose positions have been store in the command.
|
|
* @param command
|
|
*/
|
|
|
|
void XQModel::cmdCut( XQCommand& command )
|
|
{
|
|
|
|
command.savedNodesList().dumpSavedNodesList(" command CUT");
|
|
|
|
for (auto it = command.savedNodesList().rbegin(); it != command.savedNodesList().rend(); ++it)
|
|
{
|
|
const XQSavedNode& entry = *it;
|
|
XQItem& myItem = xqItemFromRow( entry.itemPos );
|
|
qDebug() << " --- AJAJA: " << myItem.text() << myItem.row();
|
|
|
|
|
|
|
|
}
|
|
|
|
XQItemList list;
|
|
for (auto it = command.savedNodesList().rbegin(); it != command.savedNodesList().rend(); ++it)
|
|
{
|
|
const XQSavedNode& entry = *it;
|
|
list = takeRow(entry.itemPos );
|
|
}
|
|
|
|
|
|
|
|
// here, we have a cloned selection in the clipboard. The
|
|
// clipboard does not store item position by nature, so we
|
|
// cannot use it for 'undo'.
|
|
// To preverse Positions, we take the nodes in reverse order.
|
|
|
|
// wir ändern hier den inhalt von command:
|
|
//
|
|
// vorher: command entält die arbeits liste mit positionen, die ausgeschitten werden sollen
|
|
// nachher: ...
|
|
//
|
|
|
|
/*
|
|
for (auto it = command.savedNodesList().rbegin(); it != command.savedNodesList().rend(); ++it)
|
|
{
|
|
XQSavedNode& entry = *it;
|
|
XQItem* myItem = fromRow( entry.itemPos );
|
|
XQNodePtr contentNode = myItem->contentNode();
|
|
|
|
qDebug() << " -- cmd cut row: " << entry.itemPos << " : " << contentNode->friendly_name() << " : " << contentNode->_id << " parent: " << contentNode->parent()->_id;
|
|
// FIX: could we put this into on call?
|
|
// delete visual item
|
|
removeRow( entry.itemPos );
|
|
// delete data node
|
|
//contentNode->delete_self();
|
|
contentNode->unlink_self();
|
|
}
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
// clone to clipboard, remove items
|
|
void XQModel::cmdCutUndo( XQCommand& command )
|
|
{
|
|
|
|
XQSavedNodesList& posList = command.savedNodesList();
|
|
//dumpSavedNodesList( posList );
|
|
// we need the concerned section to get the sheet node
|
|
const XQModelSection& section = _sections.sectionFromRow( posList.first().itemPos );
|
|
for (auto& entry : posList )
|
|
{
|
|
// we create another clone here since our command will be deleted.
|
|
XQNodePtr newNode = entry.contentNode;//->clone();
|
|
qDebug() << " -- cmd undo cut row: " << entry.itemPos << " : " << newNode->friendly_name() << " : " << newNode->_id << " parent: " << newNode->parent()->_id;
|
|
// parent of newNode does not change here
|
|
XQItemList list = _itemFactory.createDataRow( newNode, section.sheetRootNode );
|
|
// insert clonde node copy
|
|
newNode->add_me_at_pos( entry.nodePos );
|
|
insertRow( entry.itemPos, list );
|
|
|
|
}
|
|
|
|
command.savedNodesList().dumpSavedNodesList();
|
|
|
|
}
|
|
|
|
/**
|
|
* @brief XQModel::cmdPasteRows Create new XQItemLists by cloning the dataNodes from the clipboard.
|
|
* @param origin The start index
|
|
*/
|
|
|
|
void XQModel::cmdPaste( XQCommand& command )
|
|
{
|
|
|
|
command.clearSavedNodes();
|
|
|
|
QModelIndex origin = command.originIndex();
|
|
// fetch the first item target position
|
|
int itemPos = origin.row();
|
|
Q_ASSERT( itemPos != -1 );
|
|
|
|
XQSavedNodesList& posList = command.savedNodesList();
|
|
|
|
// we also need the id of the preceding nodes id as insert position
|
|
XQNodePtr predecessor = xqItemFromIndex(origin).contentNode();
|
|
int nodePos = predecessor->own_pos();
|
|
|
|
// we need the concerned section to get the sheet node
|
|
const XQModelSection& section = _sections.sectionFromRow( itemPos );
|
|
|
|
|
|
qDebug() << " --- paste nodes at node ID: " << predecessor->_id << " node pos: " << nodePos;
|
|
|
|
for( auto contentNode : _clipBoard.dataNodeList() )
|
|
{
|
|
XQNodePtr newNode = contentNode;//->clone();
|
|
// parent of newNode does not change here
|
|
XQItemList list = _itemFactory.createDataRow( newNode, section.sheetRootNode );
|
|
|
|
// we have to store the current data for undo, so we need
|
|
// another clone, since the node will be deleted when the command is destroyed.
|
|
//posList.push_back( {itemPos, nodePos, contentNode->clone() } );
|
|
posList.push_back( {itemPos, nodePos, contentNode } );
|
|
|
|
|
|
qDebug() << " -- cmd paste clipboard row: " << newNode->friendly_name() << " : " << newNode->_id << " parent: " << newNode->parent()->_id;
|
|
newNode->add_me_at_pos( nodePos++ );
|
|
insertRow( itemPos++, list );
|
|
|
|
}
|
|
|
|
command.savedNodesList().dumpSavedNodesList();
|
|
|
|
}
|
|
|
|
|
|
void XQModel::cmdPasteUndo( XQCommand& command )
|
|
{
|
|
|
|
}
|
|
|
|
|
|
// don't clone into clipboard, remove items
|
|
void XQModel::cmdDelete( XQCommand& command )
|
|
{
|
|
cmdCutUndo( command );
|
|
}
|
|
|
|
void XQModel::cmdDeleteUndo( XQCommand& command )
|
|
{
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief XQModel::cmdNewRow create one new item row
|
|
* @param command the command
|
|
*/
|
|
|
|
void XQModel::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.sectionxqItemFromIndex( origin );
|
|
|
|
// create new item row
|
|
XQItemList list = _itemFactory.createGenericRow( newNode, section.sheetRootNode );
|
|
|
|
// add it to the treeview ...
|
|
insertRow( origin.row(), list );
|
|
|
|
// ... and make it ...
|
|
treeView()->setCurrentIndex( list[0]->index() );
|
|
// ... editable
|
|
treeView()->edit( list[0]->index() );
|
|
*/
|
|
}
|
|
|
|
void XQModel::cmdNewUndo( XQCommand& command )
|
|
{
|
|
}
|
|
|
|
|
|
void XQModel::cmdToggleSection( const QModelIndex& index )
|
|
{
|
|
Q_ASSERT(index.isValid());
|
|
|
|
int fstRow = _sections.firstRow( index );
|
|
int lstRow = _sections.lastRow( index );
|
|
|
|
bool hidden =_treeView->isRowHidden( fstRow, _treeView->rootIndex() );
|
|
for (int row = fstRow; row < lstRow; ++row )
|
|
_treeView->setRowHidden( row, _treeView->rootIndex(), !hidden );
|
|
|
|
}
|
|
|
|
|
|
XQTreeView* XQModel::treeView()
|
|
{
|
|
return _treeView;
|
|
}
|
|
|
|
void XQModel::setTreeView(XQTreeView* mainView )
|
|
{
|
|
// store view for direct access: the maintree
|
|
_treeView = mainView;
|
|
// connect myself as model to the mainview
|
|
_treeView->setModel(this);
|
|
XQItemDelegate* delegate = new XQItemDelegate( *this );
|
|
_treeView->setItemDelegate( delegate );
|
|
|
|
_contextMenu = new XQContextMenu( mainView );
|
|
|
|
connect( _treeView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onShowContextMenu(QPoint)));
|
|
//connect( _treeView, 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();
|
|
}
|
|
|
|
/**
|
|
* @brief XQModel::setupViewProperties set the tree views' properties: context menu policy,
|
|
* edit triggers and so on.
|
|
*/
|
|
|
|
void XQModel::setupViewProperties()
|
|
{
|
|
_treeView->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
_treeView->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
|
|
_treeView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
//_treeView->setSelectionMode(QAbstractItemView::ContiguousSelection);
|
|
_treeView->setSelectionModel( new XQSelectionModel(this) );
|
|
}
|
|
|
|
|
|
void XQModel::addSection( const XQItemList& list, const XQNodePtr& sheetNode )
|
|
{
|
|
appendRow(list);
|
|
_sections.addSectionEntry( list[0]->index(), sheetNode );
|
|
}
|
|
|
|
|
|
QUndoStack* XQModel::undoStack()
|
|
{
|
|
return _undoStack;
|
|
}
|
|
|
|
|
|
void XQModel::setUndoStack( QUndoStack* undoStack )
|
|
{
|
|
_undoStack = undoStack;
|
|
}
|
|
|
|
|
|
void XQModel::onShowContextMenu(const QPoint& point)
|
|
{
|
|
initContextMenu();
|
|
_contextMenu->popup(_treeView->mapToGlobal(point));
|
|
}
|
|
|
|
|
|
|
|
QHash<int, QByteArray> XQModel::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;
|
|
|
|
}
|
|
|