/*************************************************************************** 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 #include #include #include #include #include #include #include #include #include // 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(invisibleRootItem()); } XQItem& XQModel::xqItemFromIndex(const QModelIndex& index) const { if( index.isValid() ) { QStandardItem* xqItem = QStandardItemModel::itemFromIndex(index); if( xqItem ) return *static_cast(xqItem); } return XQItem::fallBackDummyItem(); } const XQItem& XQModel::xqConstItemFromIndex(const QModelIndex& index) const { QStandardItem* xqItem = QStandardItemModel::itemFromIndex(index); if( xqItem ) return *static_cast(xqItem); return XQItem::fallBackDummyItem(); } XQItem& XQModel::xqItemFromRow(int row) const { return *static_cast( QStandardItemModel::item(row) ); } const XQItem& XQModel::xqConstItemFromRow(int row) const { return *static_cast( 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(); 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 XQModel::roleNames() const { QHash 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; }