Files
xtree.old.ng/model/xqmodel.cpp

526 lines
14 KiB
C++
Raw Normal View History

2025-08-05 22:39:41 +02:00
/***************************************************************************
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)
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<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();
}
XQItem& XQModel::xqFirstItem(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 "";
}
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.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
2025-08-06 16:44:08 +02:00
//! markierte knoten entfernen, 'command' enthält die liste
2025-08-05 22:39:41 +02:00
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
2025-08-06 16:44:08 +02:00
//const XQNodeBackup& entry = *it;
//XQItem& firstItem = xqFirstItem( (*it).itemPos );
//qDebug() << " --- Cut: " << firstItem.text() << " " << firstItem.row() << " id#" << entry.contentNode->_id;
2025-08-05 22:39:41 +02:00
// jetzt löschen, dabei wird die parent-verbindung entfernt
2025-08-06 16:44:08 +02:00
const XQNodeBackup& entry = *it;
2025-08-05 22:39:41 +02:00
entry.contentNode->unlink_self();
removeRow(entry.itemPos );
}
}
2025-08-06 16:44:08 +02:00
//! entfernte knoten aus wieder einfügen , 'command' enthält die liste
2025-08-05 22:39:41 +02:00
void XQModel::cmdCutUndo( XQCommand& command )
{
2025-08-06 16:44:08 +02:00
// die anfangsposition
int itmPos = command.first().itemPos;
// die 'zuständige' section rausfinden
const XQModelSection& section = _sections.sectionFromRow( itmPos );
// über alle einträge ...
2025-08-05 22:39:41 +02:00
for (auto& entry : command )
{
2025-08-06 16:44:08 +02:00
const XQNodePtr& savedNode = entry.contentNode;
// __fix! should not be _contentRoot!
savedNode->add_me_at( entry.nodePos, _contentRoot );
2025-08-05 22:39:41 +02:00
XQItemList list = _itemFactory.makeContentRow( savedNode, section.sheetRootNode );
2025-08-06 16:44:08 +02:00
2025-08-05 22:39:41 +02:00
XQItem& firstItem = *((XQItem*)list[0]);
2025-08-06 16:44:08 +02:00
qDebug() << " --- Cut Undo: " << firstItem.text() << " " << firstItem.row() << " id#" << entry.contentNode->_id << " count: " << entry.contentNode.use_count();
2025-08-05 22:39:41 +02:00
insertRow( entry.itemPos, list );
}
}
2025-08-06 16:44:08 +02:00
//! clipboard inhalte einfügen
2025-08-05 22:39:41 +02:00
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 )
{
2025-08-06 16:44:08 +02:00
//
// siehe! und es war schrott!
//
// das ist ein clon
2025-08-05 22:39:41 +02:00
XQNodePtr savedNode = entry.contentNode;
2025-08-06 16:44:08 +02:00
// der wir hier rein gelinkt
2025-08-05 22:39:41 +02:00
XQItemList list = _itemFactory.makeContentRow( savedNode, section.sheetRootNode );
2025-08-06 16:44:08 +02:00
2025-08-05 22:39:41 +02:00
// wir klonen den knoten aus dem clipbord
savedNode->clone(section.contentRootNode )->add_me_at( nodePos );
insertRow( insRow, list );
2025-08-06 16:44:08 +02:00
2025-08-05 22:39:41 +02:00
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<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;
}