/*************************************************************************** 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 #include //! 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 = "xtree concept
"; text += "2024-2025 c.holzheuer

"; text += "sourceworx.org/xtree"; 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(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 ); }