485 lines
13 KiB
C++
485 lines
13 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 <QDebug>
|
|
#include <QFileDialog>
|
|
#include <QMessageBox>
|
|
#include <QPushButton>
|
|
#include <QQmlContext>
|
|
|
|
#include <xqmainwindow.h>
|
|
#include <xqcommand.h>
|
|
#include <xqexception.h>
|
|
#include <xqitemfactory.h>
|
|
#include <xqnodewriter.h>
|
|
#include <xqquickwidget.h>
|
|
|
|
|
|
//! konstruktor.
|
|
|
|
XQMainWindow::XQMainWindow( QWidget* parent )
|
|
: QMainWindow(parent)
|
|
{
|
|
setupUi(this);
|
|
setWindowTitle( QString("XTree %1").arg(c_Version));
|
|
initMainWindow();
|
|
}
|
|
|
|
|
|
// setzt das working directory: dieses muss das 'xml' datenverzeichnis enthalten.
|
|
|
|
void XQMainWindow::setupWorkingDir()
|
|
{
|
|
QDir dir = QDir::current();
|
|
|
|
while (dir.exists())
|
|
{
|
|
QString xmlPath = dir.absoluteFilePath("xml");
|
|
if (QDir(xmlPath).exists())
|
|
{
|
|
qDebug() << " --- CD TO: " << dir.absolutePath();
|
|
QDir::setCurrent( dir.absolutePath() );
|
|
}
|
|
if (!dir.cdUp())
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
//! actions & document struktur einrichten.
|
|
|
|
void XQMainWindow::initMainWindow()
|
|
{
|
|
|
|
qDebug() << " --- initMainWindow(): here we go!";
|
|
// das working dir setzen: 'xml' muss als unterverzeichnis vorhanden sein.
|
|
setupWorkingDir();
|
|
|
|
// als allererstes laden wir die Modelschreibungen
|
|
XQItemFactory::instance().initItemFactory( c_ModelSheetFileName );
|
|
|
|
_undoView->setStack( &_undoStack );
|
|
|
|
_actionUndo->setData( XQCommand::cmdUndo);
|
|
_actionRedo->setData( XQCommand::cmdRedo);
|
|
_actionCut->setData( XQCommand::cmdCut);
|
|
_actionCopy->setData( XQCommand::cmdCopy);
|
|
_actionPaste->setData( XQCommand::cmdPaste);
|
|
_actionNew->setData( XQCommand::cmdNew);
|
|
_actionDelete->setData( XQCommand::cmdDelete);
|
|
|
|
connect( _actionUndo, &QAction::triggered, this, &XQMainWindow::onUndo );
|
|
connect( _actionRedo, &QAction::triggered, this, &XQMainWindow::onRedo );
|
|
|
|
connect( _actionOpen, &QAction::triggered, this, &XQMainWindow::onOpenDocument );
|
|
connect( _actionSave, &QAction::triggered, this, &XQMainWindow::onSaveDocument );
|
|
connect( _actionSaveAs, &QAction::triggered, this, &XQMainWindow::onSaveDocumentAs );
|
|
connect( _actionExit, &QAction::triggered, this, &XQMainWindow::onExit );
|
|
connect( _actionAbout, &QAction::triggered, this, &XQMainWindow::onAbout );
|
|
|
|
connect( _tabWidget, SIGNAL(tabBarClicked(int)), this, SLOT(onChildViewTabClicked(int)) );
|
|
|
|
//connect(&_mainModel, &QStandardItemModel::itemChanged, this, &XQMainWindow::onTreeItemChanged );
|
|
//connect( _mainTreeView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(onDoubleClicked(QModelIndex)) );
|
|
//connect( _mainTreeView, SIGNAL(clicked(QModelIndex)), this, SLOT(onTreeItemClicked(QModelIndex)) );
|
|
|
|
connect(&_mainModel, &XQViewModel::xqItemChanged, this, &XQMainWindow::onTreeViewItemChanged );
|
|
|
|
connect( _mainTreeView, &QTreeView::clicked, this, [&,this](const QModelIndex& index)
|
|
{
|
|
onTreeViewItemClicked( XQItem::xqItemFromIndex(index) );
|
|
});
|
|
|
|
try
|
|
{
|
|
|
|
// hand over undostack
|
|
_mainModel.setUndoStack(&_undoStack);
|
|
// hand over left side navigation tree
|
|
_mainModel.setTreeTable(_mainTreeView);
|
|
// #1. init the left side main tree view
|
|
_mainModel.initModel( c_MainModelName );
|
|
|
|
// 1: Wiebelbach
|
|
// 2: Gerbrunn
|
|
// 3: TBB
|
|
|
|
// #2. load demo data
|
|
loadDocument( c_DocumentFileName1 );
|
|
loadDocument( c_DocumentFileName2, true );
|
|
//loadDocument( c_DocumentFileName2 );
|
|
//loadDocument( c_DocumentFileName3 );
|
|
|
|
|
|
qDebug() << " --- all here: " << XQNode::s_Count;
|
|
|
|
}
|
|
catch( XQException& exception )
|
|
{
|
|
qDebug() << exception.what();
|
|
QMessageBox::critical( this, "Failure", QString("Failure: %1").arg(exception.what()) );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//! slot für zentrales undo
|
|
|
|
void XQMainWindow::onUndo()
|
|
{
|
|
qDebug() << " --- undo Pressed";
|
|
if(_undoStack.canUndo())
|
|
_undoStack.undo();
|
|
|
|
}
|
|
|
|
|
|
//! slot für zentrales redo
|
|
|
|
void XQMainWindow::onRedo()
|
|
{
|
|
qDebug() << " --- redo Pressed";
|
|
if(_undoStack.canRedo())
|
|
_undoStack.redo();
|
|
}
|
|
|
|
|
|
//! erzeugt ein document
|
|
|
|
void XQMainWindow::onCreateDocument()
|
|
{
|
|
qDebug() << " ---- create document Pressed!";
|
|
}
|
|
|
|
|
|
//! öffnet ein XML document
|
|
|
|
void XQMainWindow::onOpenDocument()
|
|
{
|
|
QString fileName = QFileDialog::getOpenFileName(this, tr("Open Project"), c_DocumentDirectory, tr("project data(*.xtr)") );
|
|
QFile file(fileName);
|
|
|
|
if (!file.open(QFile::ReadOnly | QFile::Text))
|
|
{
|
|
QMessageBox::warning(this, "Warning", "Cannot load file: " + file.errorString());
|
|
return;
|
|
}
|
|
// close dummy file ...
|
|
file.close();
|
|
|
|
loadDocument( fileName );
|
|
}
|
|
|
|
|
|
//! speichert ein XML document
|
|
|
|
void XQMainWindow::onSaveDocument()
|
|
{
|
|
qDebug() << " ---- save Pressed!";
|
|
saveDocument( c_ModelDummyFileName );
|
|
}
|
|
|
|
|
|
//! fragt nach einem datei-namen und speichert das akutelle XML
|
|
//! document unter diesem
|
|
|
|
void XQMainWindow::onSaveDocumentAs()
|
|
{
|
|
QString fileName = QFileDialog::getSaveFileName(this, "Save as", c_DocumentDirectory, tr("project data(*.xtr)") );
|
|
QFile file(fileName);
|
|
// open dummy file
|
|
if (!file.open(QFile::WriteOnly | QFile::Text))
|
|
{
|
|
QMessageBox::warning(this, "Warning", "Cannot save file: " + file.errorString());
|
|
return;
|
|
}
|
|
// close dummy file ...
|
|
file.close();
|
|
// and create a xml stream
|
|
saveDocument( fileName );
|
|
}
|
|
|
|
|
|
//! wird aufgerufen, wenn ein XML geschlossen werden soll.
|
|
|
|
void XQMainWindow::onCloseDocument()
|
|
{
|
|
qDebug() << " ---- close Pressed!";
|
|
}
|
|
|
|
|
|
//! beendet diese application
|
|
|
|
void XQMainWindow::onExit()
|
|
{
|
|
qApp->exit();
|
|
}
|
|
|
|
|
|
//! zeigt den about-dialog
|
|
|
|
void XQMainWindow::onAbout()
|
|
{
|
|
|
|
QMessageBox msgBox(QMessageBox::NoIcon, "About", "", QMessageBox::Ok);
|
|
|
|
QString text = "<b>xtree concept</b><br>";
|
|
text += "2024-2025 c.holzheuer<br><br>";
|
|
text += "<a href=\"https://sourceworx.org/xtree\">sourceworx.org/xtree</a>";
|
|
|
|
msgBox.setTextFormat(Qt::RichText); // This allows you to click the link
|
|
msgBox.setText( text );
|
|
|
|
msgBox.exec();
|
|
}
|
|
|
|
|
|
|
|
//! wenn ein item im navigations-baum geklickt wird, soll die document
|
|
//! view rechts angepasst werden.
|
|
|
|
void XQMainWindow::onTreeViewItemClicked( const XQItem& item )
|
|
{
|
|
//qDebug() << " --- Tree item CLICK:" << item.text() << " : " << item.itemType().text();
|
|
if( item.itemType().text() == "TreeChildType" )
|
|
{
|
|
setChildTabByName( item.text() );
|
|
}
|
|
}
|
|
|
|
void XQMainWindow::onTreeViewItemChanged(const XQItem& item )
|
|
{
|
|
qDebug() << " --- TREE VIEW itemChanged: text" << item.text() << " parent: " << item.parent()->text() << " type: " << item.itemType().text() << " : " << (void*)&_mainModel << " : " << (void*) sender();
|
|
// hier müssen wir erst das projekt aktivieren
|
|
XQItem* xqItem = static_cast<XQItem*>(item.parent());
|
|
onTreeViewItemClicked( *xqItem );
|
|
//if( item.itemType().text() == "TreeSectionType" )
|
|
{
|
|
int idx = _tabWidget->currentIndex();
|
|
if(_documentStore.contains(idx) )
|
|
{
|
|
qDebug() << " --- Has Document and might toggle: " << item.text();
|
|
XQViewModel& childModel = *_documentStore[idx].viewModel;
|
|
childModel.onToggleSection(item.text());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//! beim click auf ein tab im linken fenster wird der navigationsbaum angepasst.
|
|
|
|
void XQMainWindow::onChildViewTabClicked( int idx )
|
|
{
|
|
if(_documentStore.contains(idx) )
|
|
{
|
|
QModelIndex treeIndex =_documentStore[idx].treeItem->index();
|
|
_mainModel.expandNewItem( treeIndex );
|
|
}
|
|
}
|
|
|
|
|
|
//! SLOT, der aufgerufen wird, sobald eine section erzeugt worden ist.
|
|
|
|
void XQMainWindow::onSectionCreated( const XQModelSection& section )
|
|
{
|
|
if( _currentProjectItem )
|
|
{
|
|
_mainModel.addSectionItem( section, _currentProjectItem );
|
|
}
|
|
}
|
|
|
|
|
|
//! SLOT, der aufgerufen wird, wenn eine section getoggelt wurde.
|
|
|
|
void XQMainWindow::onSectionToggled( const XQModelSection& section )
|
|
{
|
|
for (int row = 0; row < _currentProjectItem->rowCount(); ++row)
|
|
{
|
|
QStandardItem* child = _currentProjectItem->child(row);
|
|
if (child->text() == section.contentType() )
|
|
{
|
|
bool checked = (child->checkState() == Qt::Checked);
|
|
child->setCheckState( checked ? Qt::Unchecked :Qt::Checked );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//! aktiviert das tab, das zum dokument mit dem schlüssel 'key' gehört.
|
|
|
|
void XQMainWindow::setChildTabByName( const QString& key )
|
|
{
|
|
for( int i=0; i<_documentStore.size(); ++i )
|
|
{
|
|
if( key == _documentStore[i].friendlyName)
|
|
{
|
|
_tabWidget->setCurrentIndex(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
XQNodePtr XQMainWindow::createDataTree( const QString& fileName )
|
|
{
|
|
// gibts die Datei?
|
|
if( !QFile::exists( fileName) )
|
|
throw XQException( "no such file", fileName );
|
|
|
|
XQNodeFactory treeLoader;
|
|
// xml daten laden
|
|
XQNodePtr rawTree = treeLoader.load_tree( qPrintable(fileName) );
|
|
// versteckten root node ignorieren
|
|
return rawTree->first_child();
|
|
}
|
|
|
|
|
|
XQChildModel* XQMainWindow::createChildModel( const XQNodePtr& contentRoot )
|
|
{
|
|
// Ein neues Child-Model erzeugen
|
|
XQChildModel* childModel = new XQChildModel(this);
|
|
|
|
connect( childModel, SIGNAL(sectionCreated(XQModelSection)), this, SLOT(onSectionCreated(XQModelSection)) );
|
|
connect( childModel, SIGNAL(sectionToggled(XQModelSection)), this, SLOT(onSectionToggled(XQModelSection)) );
|
|
|
|
// Den globalen undo-stack ...
|
|
childModel->setUndoStack(&_undoStack);
|
|
|
|
// die Modelstruktur anlegen
|
|
childModel->initModel( c_ChildModelName );
|
|
|
|
// model inhalte laden
|
|
childModel->addModelData( contentRoot->first_child() );
|
|
childModel->setObjectName( contentRoot->friendly_name() );
|
|
|
|
return childModel;
|
|
|
|
}
|
|
|
|
//! liest eine XML datei namens 'fileName'
|
|
|
|
void XQMainWindow::loadDocument( const QString& fileName, bool useQML )
|
|
{
|
|
|
|
// Datenbaum laden
|
|
XQNodePtr contentRoot = createDataTree( fileName );
|
|
|
|
// Falls schon vorhanden ...
|
|
const QString& pID = contentRoot->attribute(c_ProjectID);
|
|
int idx = _documentStore.indexOf( pID );
|
|
if( idx > -1 )
|
|
{
|
|
const XQDocument& document = _documentStore.at(idx);
|
|
QMessageBox::warning( this, "Load Document", QString("File: %1 already loaded.").arg( fileName ) );
|
|
_mainTreeView->setCurrentIndex( document.treeItem->index() );
|
|
_tabWidget->setCurrentIndex( idx );
|
|
// ... wird nichts wieter unternommen
|
|
return;
|
|
}
|
|
|
|
// 'friendly Name' ist ein Link auf ein anderes Attribute
|
|
// das als Namen verwendet wird.
|
|
const QString& fName = contentRoot->friendly_name();
|
|
QString pTabTitle = QString("Project %1: %2").arg( pID, fName );
|
|
|
|
// neuen eintrag im übsichts-baum erzeugen
|
|
_currentProjectItem = _mainModel.addProjectItem( contentRoot );
|
|
// Kindmodel für den Datenbaum erzeugen.
|
|
XQChildModel* childModel = createChildModel(contentRoot);
|
|
|
|
_documentStore.addDocument( fileName, fName, _currentProjectItem, childModel );
|
|
|
|
|
|
QWidget* childView{};
|
|
if(useQML)
|
|
{
|
|
XQQuickWidget* quickView = new XQQuickWidget(_tabWidget);
|
|
//quickChild->setResizeMode(QQuickWidget::SizeViewToRootObject);
|
|
|
|
quickView->rootContext()->setContextProperty("xtrChildModel", childModel);
|
|
quickView->setSource(QUrl("qrc:/xqtreeview.qml"));
|
|
childView = quickView;
|
|
}
|
|
else
|
|
{
|
|
// Eine neue TreeView erzeugn und im TabWidget parken.
|
|
XQTreeTable* treeTable = new XQTreeTable(_tabWidget);
|
|
// und die TreeView übergeben
|
|
childModel->setTreeTable(treeTable);
|
|
childView = treeTable;
|
|
}
|
|
|
|
_tabWidget->addTab( childView, pTabTitle );
|
|
_tabWidget->setCurrentWidget( childView );
|
|
setWindowTitle( pTabTitle );
|
|
|
|
}
|
|
|
|
|
|
|
|
void XQMainWindow::loadDocumentQML( const QString& fileName )
|
|
{
|
|
// gibts die Datei?
|
|
if( !QFile::exists( fileName) )
|
|
throw XQException( "no such file", fileName );
|
|
|
|
XQNodeFactory treeLoader;
|
|
// xml daten laden
|
|
XQNodePtr rawTree = treeLoader.load_tree( qPrintable(fileName) );
|
|
// versteckten root node ignorieren
|
|
XQNodePtr contentRoot = rawTree->first_child();
|
|
|
|
|
|
// 'friendly Name' ist ein Link auf ein anderes Attribute
|
|
// das als Namen verwendet wird.
|
|
const QString& fName = contentRoot->friendly_name();
|
|
|
|
// Ein neues Child-Model erzeugen
|
|
XQChildModel* childModel = new XQChildModel(this);
|
|
// die Modelstruktur anlegen
|
|
childModel->initModel( c_ChildModelName );
|
|
// model inhalte laden
|
|
childModel->addModelData( contentRoot->first_child() );
|
|
|
|
XQQuickWidget* quickChild = new XQQuickWidget(_tabWidget);
|
|
//quickChild->setResizeMode(QQuickWidget::SizeViewToRootObject);
|
|
|
|
quickChild->rootContext()->setContextProperty("xtrChildModel", childModel);
|
|
quickChild->setSource(QUrl("qrc:/xqtreeview.qml"));
|
|
_tabWidget->addTab( quickChild, "QML:"+fName );
|
|
_tabWidget->setCurrentWidget( quickChild );
|
|
quickChild->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
|
|
|
}
|
|
|
|
|
|
|
|
//! speichert ein XML unter dem 'filename'
|
|
|
|
void XQMainWindow::saveDocument( const QString& fileName )
|
|
{
|
|
XQNodeWriter nodeWriter;
|
|
int curIdx = _tabWidget->currentIndex();
|
|
//XQNodePtr rootNode = _documentStore[curIdx].treeItem->contentNode();
|
|
XQNodePtr rootNode = _documentStore[curIdx].viewModel->contentRootNode();
|
|
Q_ASSERT(rootNode);
|
|
nodeWriter.dumpTree( rootNode, fileName );
|
|
}
|
|
|
|
|
|
|
|
|
|
|