/*************************************************************************** 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) void showItemList( const XQItemList& list) { for(const auto& entry : list ) qDebug() << " --- itemList: " << ((XQItem*)entry)->content(); qDebug(); } 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(); } XQItem& XQModel::xqFirstItem(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 ""; } 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.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( 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 ) { try { 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(); } } catch( XQException& exception ) { qDebug() << exception.what(); QMessageBox::critical( nullptr, "Failure", QString("Failure: %1").arg(exception.what()) ); } } /** * @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; 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(); } } catch( XQException& exception ) { qDebug() << exception.what(); QMessageBox::critical( nullptr, "Failure", QString("Failure: %1").arg(exception.what()) ); } } // 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 ) { // 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; XQItem& firstItem = xqFirstItem( (*it).itemPos ); qDebug() << " --- Cut: " << firstItem.text() << " " << firstItem.row() << " id#" << entry.contentNode->_id; // jetzt löschen, dabei wird die parent-verbindung entfernt entry.contentNode->unlink_self(); removeRow(entry.itemPos ); } } // clone to clipboard, remove items void XQModel::cmdCutUndo( XQCommand& command ) { command.dumpList("UNDO Cut"); _sections.dump(); int xx = command.first().itemPos; const XQModelSection& section = _sections.sectionFromRow( xx ); for (auto& entry : command ) { XQNodePtr savedNode = entry.contentNode; // __fix! should not bee _contentRoot! savedNode->add_me_at( entry.nodePos, _contentRoot ); XQItemList list = _itemFactory.makeContentRow( savedNode, section.sheetRootNode ); XQItem& firstItem = *((XQItem*)list[0]); qDebug() << " --- Cut Undo: " << firstItem.text() << " " << firstItem.row() << " id#" << entry.contentNode->_id; insertRow( entry.itemPos, list ); } } void XQModel::cmdPaste( XQCommand& command ) { // selection holen ... QItemSelectionModel* selectionModel = treeView()->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 ) { XQNodePtr savedNode = entry.contentNode; XQItemList list = _itemFactory.makeContentRow( savedNode, section.sheetRootNode ); // wir klonen den knoten aus dem clipbord savedNode->clone(section.contentRootNode )->add_me_at( nodePos ); insertRow( insRow, list ); const QModelIndex& selIdx = list[0]->index(); _treeView->selectionModel()->select(selIdx, QItemSelectionModel::Select | QItemSelectionModel::Rows); // zur nächsten zeile insRow++; nodePos++; } // unsere änderungen merken fürs 'undo' command.saveNodes( selectionModel->selectedRows() ); } void XQModel::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 void XQModel::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 ); } } 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; }