diff --git a/cmake/qet_compilation_vars.cmake b/cmake/qet_compilation_vars.cmake index 859033cc6..0ac2f64f0 100644 --- a/cmake/qet_compilation_vars.cmake +++ b/cmake/qet_compilation_vars.cmake @@ -254,6 +254,8 @@ set(QET_SRC_FILES ${QET_DIR}/sources/diagramevent/diagrameventaddtext.h ${QET_DIR}/sources/diagramevent/diagrameventinterface.cpp ${QET_DIR}/sources/diagramevent/diagrameventinterface.h + ${QET_DIR}/sources/diagramevent/diagrameventaddmacro.cpp + ${QET_DIR}/sources/diagramevent/diagrameventaddmacro.h ${QET_DIR}/sources/dvevent/dveventinterface.cpp ${QET_DIR}/sources/dvevent/dveventinterface.h diff --git a/sources/ElementsCollection/elementcollectionitem.cpp b/sources/ElementsCollection/elementcollectionitem.cpp index fcc2a96e1..3e63c8c8c 100644 --- a/sources/ElementsCollection/elementcollectionitem.cpp +++ b/sources/ElementsCollection/elementcollectionitem.cpp @@ -119,8 +119,8 @@ int ElementCollectionItem::rowForInsertItem(const QString &name) return -1; QList child; - //The item to insert is an element we search from element child - if (name.endsWith(".elmt")) + //The item to insert is an element/template we search from element child + if (name.endsWith(".elmt") || name.endsWith(".qetmak")) { child = elementsDirectChild(); //There isn't element, we insert at last position diff --git a/sources/ElementsCollection/elementscollectionmodel.cpp b/sources/ElementsCollection/elementscollectionmodel.cpp index 2663c008f..a55eb523e 100644 --- a/sources/ElementsCollection/elementscollectionmodel.cpp +++ b/sources/ElementsCollection/elementscollectionmodel.cpp @@ -264,10 +264,12 @@ bool ElementsCollectionModel::dropMimeData(const QMimeData *data, @param projects : list of projects to load */ void ElementsCollectionModel::loadCollections(bool common_collection, - bool company_collection, - bool custom_collection, - QList projects) + bool company_collection, + bool custom_collection, + QList projects) { + clear(); + m_items_list_to_setUp.clear(); if (common_collection) @@ -280,36 +282,64 @@ void ElementsCollectionModel::loadCollections(bool common_collection, if (common_collection || company_collection || custom_collection) m_items_list_to_setUp.append(items()); - for (QETProject *project : projects) { addProject(project, false); m_items_list_to_setUp.append(projectItems(project)); } + auto *watcher = new QFutureWatcher(); connect(watcher, &QFutureWatcher::progressValueChanged, - this, &ElementsCollectionModel::loadingProgressValueChanged); + this, &ElementsCollectionModel::loadingProgressValueChanged); connect(watcher, &QFutureWatcher::progressRangeChanged, - this, &ElementsCollectionModel::loadingProgressRangeChanged); + this, &ElementsCollectionModel::loadingProgressRangeChanged); connect(watcher, &QFutureWatcher::finished, - this, &ElementsCollectionModel::loadingFinished); - connect( - watcher, - &QFutureWatcher::finished, - watcher, - &QFutureWatcher::deleteLater); -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove + this, &ElementsCollectionModel::loadingFinished); + connect(watcher, &QFutureWatcher::finished, watcher, &QFutureWatcher::deleteLater); + + #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) m_future = QtConcurrent::map(m_items_list_to_setUp, setUpData); -#else -# if TODO_LIST -# pragma message("@TODO remove code for QT 6 or later") -# endif - qDebug() << "Help code for QT 6 or later" - << "QtConcurrent::run its backwards now...function, object, args"; -#endif + #else + qDebug() << "Help code for QT 6 or later"; + #endif watcher->setFuture(m_future); } +/** + * @brief ElementsCollectionModel::loadMacrosCollection + * Load the macros collection synchronously to avoid thread-collisions. + */ +void ElementsCollectionModel::loadMacrosCollection() +{ + m_items_list_to_setUp.clear(); + addMacrosCollection(true); +} + +/** + * @brief ElementsCollectionModel::addMacrosCollection + * Add the user macros collection to this model + * @param set_data + */ +void ElementsCollectionModel::addMacrosCollection(bool set_data) +{ + QString macrosPath = QETApp::userMacrosDir(); + qDebug() << "=== MAKRO PFAD CHECK ===" << macrosPath; + if (macrosPath.endsWith("/")) { + macrosPath.remove(macrosPath.length() - 1, 1); + } + + FileElementCollectionItem *feci = new FileElementCollectionItem(); + if (feci->setRootPath(macrosPath, + set_data, + m_hide_element)) { + invisibleRootItem()->appendRow(feci); + if (set_data) + feci->setUpData(); + } + else + delete feci; +} + /** @brief ElementsCollectionModel::addCommonCollection Add the common elements collection to this model @@ -368,11 +398,11 @@ void ElementsCollectionModel::addCustomCollection(bool set_data) } /** - @brief ElementsCollectionModel::addLocation - Add the element or directory to this model. - If the location is already managed by this model, do nothing. - @param location -*/ + * @brief ElementsCollectionModel::addLocation + * Add the element or directory to this model. + * If the location is already managed by this model, do nothing. + * @param location + */ void ElementsCollectionModel::addLocation(const ElementsLocation& location) { QModelIndex index = indexFromLocation(location); @@ -387,14 +417,15 @@ void ElementsCollectionModel::addLocation(const ElementsLocation& location) if (project) { XmlProjectElementCollectionItem *xpeci = - m_project_hash.value(project); + m_project_hash.value(project); last_item = xpeci->lastItemForPath( - location.collectionPath(false), - collection_name); + location.collectionPath(false), + collection_name); } } - else if (location.isCustomCollection()) { + // ANPASSUNG: Makros und Custom Collection werden hier behandelt! + else if (location.isCustomCollection() || location.isMacrosCollection()) { QList child_list; for (int i=0 ; itype() == FileElementCollectionItem::Type) { FileElementCollectionItem *feci = - static_cast(eci); + static_cast(eci); + + // Wir prüfen explizit, ob es Custom ODER Macros ist, und weisen es richtig zu. + if ((location.isCustomCollection() && feci->isCustomCollection()) || + (location.isMacrosCollection() && feci->isMacrosCollection())) { - if (feci->isCustomCollection()) { last_item = feci->lastItemForPath( - location.collectionPath(false), - collection_name); + location.collectionPath(false), + collection_name); if(last_item) break; - } + } } } } @@ -574,14 +608,14 @@ void ElementsCollectionModel::hideElement() } /** - @brief ElementsCollectionModel::indexFromLocation - Return the index who represent location. - Index can be non valid - @param location - @return -*/ + * @brief ElementsCollectionModel::indexFromLocation + * Return the index who represent location. + * Index can be non valid + * @param location + * @return + */ QModelIndex ElementsCollectionModel::indexFromLocation( - const ElementsLocation &location) + const ElementsLocation &location) { QList child_list; @@ -589,30 +623,34 @@ QModelIndex ElementsCollectionModel::indexFromLocation( child_list.append(static_cast(item(i))); } - foreach(ElementCollectionItem *eci, child_list) { + foreach(ElementCollectionItem *eci, child_list) { - ElementCollectionItem *match_eci = nullptr; + ElementCollectionItem *match_eci = nullptr; - if (eci->type() == FileElementCollectionItem::Type) { - if (FileElementCollectionItem *feci = static_cast(eci)) { - if ( (location.isCommonCollection() && feci->isCommonCollection()) || - (location.isCompanyCollection() && feci->isCompanyCollection()) || - (location.isCustomCollection() && !feci->isCommonCollection()) ) { - match_eci = feci->itemAtPath(location.collectionPath(false)); + if (eci->type() == FileElementCollectionItem::Type) { + if (FileElementCollectionItem *feci = static_cast(eci)) { + + // ANPASSUNG: Makro-Prüfung hinzugefügt, damit das Modell den Pfad im Baum findet! + if ( (location.isCommonCollection() && feci->isCommonCollection()) || + (location.isCompanyCollection() && feci->isCompanyCollection()) || + (location.isMacrosCollection() && feci->isMacrosCollection()) || + (location.isCustomCollection() && feci->isCustomCollection()) ) { + + match_eci = feci->itemAtPath(location.collectionPath(false)); } - } } - else if (eci->type() == XmlProjectElementCollectionItem::Type) { - if (XmlProjectElementCollectionItem *xpeci = static_cast(eci)) { - match_eci = xpeci->itemAtPath(location.collectionPath(false)); - } + } + else if (eci->type() == XmlProjectElementCollectionItem::Type) { + if (XmlProjectElementCollectionItem *xpeci = static_cast(eci)) { + match_eci = xpeci->itemAtPath(location.collectionPath(false)); } - - if (match_eci) - return indexFromItem(match_eci); } - return QModelIndex(); + if (match_eci) + return indexFromItem(match_eci); + } + + return QModelIndex(); } /** diff --git a/sources/ElementsCollection/elementscollectionmodel.h b/sources/ElementsCollection/elementscollectionmodel.h index d6652f9df..1bf12e66e 100644 --- a/sources/ElementsCollection/elementscollectionmodel.h +++ b/sources/ElementsCollection/elementscollectionmodel.h @@ -47,6 +47,8 @@ class ElementsCollectionModel : public QStandardItemModel void addCommonCollection(bool set_data = true); void addCompanyCollection(bool set_data = true); void addCustomCollection(bool set_data = true); + void addMacrosCollection(bool set_data = true); + void loadMacrosCollection(); void addLocation(const ElementsLocation& location); void addProject(QETProject *project, bool set_data = true); diff --git a/sources/ElementsCollection/elementscollectionwidget.cpp b/sources/ElementsCollection/elementscollectionwidget.cpp index 56d793463..e88bd8731 100644 --- a/sources/ElementsCollection/elementscollectionwidget.cpp +++ b/sources/ElementsCollection/elementscollectionwidget.cpp @@ -93,10 +93,7 @@ void ElementsCollectionWidget::addProject(QETProject *project) { if (m_model) { - m_progress_bar->show(); - m_tree_view->setDisabled(true); - QList prj; prj.append(project); - m_model->loadCollections(false, false, false, prj); + m_model->addProject(project, true); } else { m_waiting_project.append(project); @@ -176,16 +173,14 @@ void ElementsCollectionWidget::setUpAction() */ void ElementsCollectionWidget::setUpWidget() { - //Setup the main layout m_main_vlayout = new QVBoxLayout(this); - this->setLayout(m_main_vlayout); + m_main_vlayout->setContentsMargins(0, 0, 0, 0); + m_main_vlayout->setSpacing(2); m_search_field = new QLineEdit(this); - m_search_field->setPlaceholderText(tr("Rechercher")); + m_search_field->setPlaceholderText(tr("Rechercher...")); m_search_field->setClearButtonEnabled(true); - m_main_vlayout->addWidget(m_search_field); - //Setup the tree view m_tree_view = new ElementsTreeView(this); m_tree_view->setHeaderHidden(true); m_tree_view->setIconSize(QSize(50, 50)); @@ -195,12 +190,29 @@ void ElementsCollectionWidget::setUpWidget() m_tree_view->setAnimated(true); m_tree_view->setMouseTracking(true); m_tree_view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - m_main_vlayout->addWidget(m_tree_view); - //Setup the progress bar + //Setup the macros tree view + m_macros_tree_view = new ElementsTreeView(this); + m_macros_tree_view->setHeaderHidden(true); + m_macros_tree_view->setIconSize(QSize(50, 50)); + m_macros_tree_view->setDragDropMode(QAbstractItemView::DragDrop); + m_macros_tree_view->setContextMenuPolicy(Qt::CustomContextMenu); + m_macros_tree_view->setAutoExpandDelay(500); + m_macros_tree_view->setAnimated(true); + m_macros_tree_view->setMouseTracking(true); + m_macros_tree_view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + + m_tab_widget = new QTabWidget(this); + m_tab_widget->setDocumentMode(true); + m_tab_widget->setTabPosition(QTabWidget::North); + m_tab_widget->addTab(m_tree_view, tr("Collections")); + m_tab_widget->addTab(m_macros_tree_view, tr("Modèles")); + + m_main_vlayout->addWidget(m_search_field); + m_main_vlayout->addWidget(m_tab_widget); + m_progress_bar = new QProgressBar(this); m_progress_bar->setFormat(QObject::tr("chargement %p% (%v sur %m)")); - m_main_vlayout->addWidget(m_progress_bar); m_progress_bar->hide(); @@ -243,11 +255,15 @@ void ElementsCollectionWidget::setUpConnection() this, &ElementsCollectionWidget::dirProperties); connect(m_tree_view, &QTreeView::doubleClicked, - [this](const QModelIndex &index) - { - this->m_index_at_context_menu = index ; - this->editElement(); - }); + [this](const QModelIndex &index) + { + this->m_index_at_context_menu = index ; + ElementCollectionItem *eci = elementCollectionItemForIndex(index); + if (eci && eci->collectionPath().endsWith(".qetmak")) { + return; // Do nothing on double click for macros + } + this->editElement(); + }); connect(m_tree_view, &QTreeView::entered, [this] (const QModelIndex &index) { @@ -256,37 +272,64 @@ void ElementsCollectionWidget::setUpConnection() if (qde && eci) qde->statusBar()->showMessage(eci->localName()); }); + + connect(m_macros_tree_view, &QTreeView::customContextMenuRequested, + this, &ElementsCollectionWidget::customContextMenu); + + connect(m_macros_tree_view, &QTreeView::doubleClicked, + [this](const QModelIndex &index) + { + this->m_index_at_context_menu = index ; + ElementCollectionItem *eci = elementCollectionItemForIndex(index); + if (eci && eci->collectionPath().endsWith(".qetmak")) { + return; // Do nothing on double click for macros + } + this->editElement(); + }); + + connect(m_macros_tree_view, &QTreeView::entered, + [this] (const QModelIndex &index) { + QETDiagramEditor *qde = QETApp::diagramEditorAncestorOf(this); + ElementCollectionItem *eci = elementCollectionItemForIndex(index); + if (qde && eci) + qde->statusBar()->showMessage(eci->localName()); + }); } /** - @brief ElementsCollectionWidget::customContextMenu - Display the context menu of this widget at point - @param point -*/ + * @brief ElementsCollectionWidget::customContextMenu + * Display the context menu of this widget at point + * @param point + */ void ElementsCollectionWidget::customContextMenu(const QPoint &point) { - m_index_at_context_menu = m_tree_view->indexAt(point); + QTreeView *clicked_tree = qobject_cast(sender()); + if (!clicked_tree) clicked_tree = m_tree_view; // Fallback + + m_index_at_context_menu = clicked_tree->indexAt(point); if (!m_index_at_context_menu.isValid()) return; m_context_menu->clear(); ElementCollectionItem *eci = elementCollectionItemForIndex( - m_index_at_context_menu); + m_index_at_context_menu); bool add_open_dir = false; - if (eci->isElement()) + if (eci->isElement() && !eci->collectionPath().endsWith(".qetmak")) m_context_menu->addAction(m_edit_element); if (eci->type() == FileElementCollectionItem::Type) { add_open_dir = true; FileElementCollectionItem *feci = - static_cast(eci); + static_cast(eci); if (!feci->isCommonCollection()) { if (feci->isDir()) { - m_context_menu->addAction(m_new_element); + if (!feci->isMacrosCollection()) { + m_context_menu->addAction(m_new_element); + } m_context_menu->addAction(m_new_directory); if (!feci->isCollectionRoot()) { @@ -301,7 +344,7 @@ void ElementsCollectionWidget::customContextMenu(const QPoint &point) if (eci->type() == XmlProjectElementCollectionItem::Type) { XmlProjectElementCollectionItem *xpeci = - static_cast(eci); + static_cast(eci); if (xpeci->isCollectionRoot()) add_open_dir = true; } @@ -320,7 +363,7 @@ void ElementsCollectionWidget::customContextMenu(const QPoint &point) m_context_menu->addAction(m_open_dir); m_context_menu->addAction(m_reload); - m_context_menu->popup(mapToGlobal(m_tree_view->mapToParent(point))); + m_context_menu->popup(mapToGlobal(clicked_tree->mapToParent(point))); } /** @@ -360,6 +403,9 @@ void ElementsCollectionWidget::editElement() if ( !(eci && eci->isElement()) ) return; + // Prevent the element editor from opening for macros + if (eci->collectionPath().endsWith(".qetmak")) return; + ElementsLocation location(eci->collectionPath()); QETApp *app = QETApp::instance(); @@ -384,11 +430,15 @@ void ElementsCollectionWidget::deleteElement() if (!eci) return; ElementsLocation loc(eci->collectionPath()); - if (! (loc.isElement() - && loc.exist() - && loc.isFileSystem() - && (loc.collectionPath().startsWith("company://") - || loc.collectionPath().startsWith("custom://"))) ) return; + + bool isDeletableFile = loc.isElement() || eci->collectionPath().endsWith(".qetmak"); + + if (! (isDeletableFile + && loc.exist() + && loc.isFileSystem() + && (loc.collectionPath().startsWith("company://") + || loc.collectionPath().startsWith("custom://") + || loc.collectionPath().startsWith("macros://"))) ) return; if (QET::QetMessageBox::question( this, @@ -400,9 +450,10 @@ void ElementsCollectionWidget::deleteElement() QFile file(loc.fileSystemPath()); if (file.remove()) { - m_model->removeRows(m_index_at_context_menu.row(), - 1, - m_index_at_context_menu.parent()); + QAbstractItemModel *clicked_model = const_cast(m_index_at_context_menu.model()); + if (clicked_model) { + clicked_model->removeRows(m_index_at_context_menu.row(), 1, m_index_at_context_menu.parent()); + } } else { @@ -429,10 +480,11 @@ void ElementsCollectionWidget::deleteDirectory() ElementsLocation loc (eci->collectionPath()); if (! (loc.isDirectory() - && loc.exist() - && loc.isFileSystem() - && (loc.collectionPath().startsWith("company://") - || loc.collectionPath().startsWith("custom://"))) ) return; + && loc.exist() + && loc.isFileSystem() + && (loc.collectionPath().startsWith("company://") + || loc.collectionPath().startsWith("custom://") + || loc.collectionPath().startsWith("macros://"))) ) return; if (QET::QetMessageBox::question( this, @@ -445,9 +497,10 @@ void ElementsCollectionWidget::deleteDirectory() QDir dir (loc.fileSystemPath()); if (dir.removeRecursively()) { - m_model->removeRows(m_index_at_context_menu.row(), - 1, - m_index_at_context_menu.parent()); + QAbstractItemModel *clicked_model = const_cast(m_index_at_context_menu.model()); + if (clicked_model) { + clicked_model->removeRows(m_index_at_context_menu.row(), 1, m_index_at_context_menu.parent()); + } } else { @@ -489,19 +542,29 @@ void ElementsCollectionWidget::editDirectory() */ void ElementsCollectionWidget::newDirectory() { - ElementCollectionItem *eci = elementCollectionItemForIndex( - m_index_at_context_menu); + ElementCollectionItem *eci = elementCollectionItemForIndex(m_index_at_context_menu); - if (eci->type() != FileElementCollectionItem::Type) return; + if (!eci || eci->type() != FileElementCollectionItem::Type) return; - FileElementCollectionItem *feci = - static_cast(eci); + FileElementCollectionItem *feci = static_cast(eci); if(feci->isCommonCollection()) return; ElementsLocation location(feci->collectionPath()); ElementsCategoryEditor new_dir_editor(location, false, this); - if (new_dir_editor.exec() == QDialog::Accepted) - m_model->addLocation(new_dir_editor.createdLocation()); + + if (new_dir_editor.exec() == QDialog::Accepted) { + ElementsLocation new_loc = new_dir_editor.createdLocation(); + + if (new_loc.isMacrosCollection()) { + if (m_macros_model) { + m_macros_model->addLocation(new_loc); + } + } else { + if (m_model) { + m_model->addLocation(new_loc); + } + } + } } /** @@ -662,12 +725,19 @@ void ElementsCollectionWidget::reload() &ElementsCollectionWidget::loadingFinished); m_new_model->loadCollections(true, true, true, project_list); + + if (m_macros_model) { + m_macros_model->deleteLater(); + } + m_macros_model = new ElementsCollectionModel(m_macros_tree_view); + m_macros_tree_view->setModel(m_macros_model); + m_macros_model->loadMacrosCollection(); } /** - @brief ElementsCollectionWidget::loadingFinished - Process when collection finished to be loaded -*/ + * @brief ElementsCollectionWidget::loadingFinished + * Process when collection finished to be loaded + */ void ElementsCollectionWidget::loadingFinished() { if (m_new_model) @@ -842,15 +912,21 @@ void ElementsCollectionWidget::showAndExpandItem(const QModelIndex &index, } /** - @brief ElementsCollectionWidget::elementCollectionItemForIndex - @param index - @return The internal pointer of index casted to ElementCollectionItem; -*/ -ElementCollectionItem *ElementsCollectionWidget::elementCollectionItemForIndex( - const QModelIndex &index) { - if (!index.isValid()) - return nullptr; + * @brief ElementsCollectionWidget::elementCollectionItemForIndex + * @param index + * @return The internal pointer of index casted to ElementCollectionItem; + */ +ElementCollectionItem *ElementsCollectionWidget::elementCollectionItemForIndex(const QModelIndex &index) +{ + if (!index.isValid()) return nullptr; - return static_cast( - m_model->itemFromIndex(index)); + if (m_macros_model && index.model() == m_macros_model) { + return static_cast(m_macros_model->itemFromIndex(index)); + } + + if (m_model && index.model() == m_model) { + return static_cast(m_model->itemFromIndex(index)); + } + + return nullptr; } diff --git a/sources/ElementsCollection/elementscollectionwidget.h b/sources/ElementsCollection/elementscollectionwidget.h index 4a5fe4071..a8294ec78 100644 --- a/sources/ElementsCollection/elementscollectionwidget.h +++ b/sources/ElementsCollection/elementscollectionwidget.h @@ -25,6 +25,8 @@ #include #include #include +#include +#include class ElementsCollectionModel; class QVBoxLayout; @@ -90,9 +92,12 @@ class ElementsCollectionWidget : public QWidget private: ElementsCollectionModel *m_model = nullptr; ElementsCollectionModel *m_new_model = nullptr; + ElementsCollectionModel *m_macros_model = nullptr; QLineEdit *m_search_field; QTimer m_search_timer; ElementsTreeView *m_tree_view; + ElementsTreeView *m_macros_tree_view = nullptr; + QTabWidget *m_tab_widget = nullptr; QVBoxLayout *m_main_vlayout; QMenu *m_context_menu; QModelIndex m_index_at_context_menu; diff --git a/sources/ElementsCollection/elementslocation.cpp b/sources/ElementsCollection/elementslocation.cpp index f236d9edb..c14f244ff 100644 --- a/sources/ElementsCollection/elementslocation.cpp +++ b/sources/ElementsCollection/elementslocation.cpp @@ -181,7 +181,7 @@ QString ElementsLocation::collectionPath(bool protocol) const else { QString path = m_collection_path; - return path.remove(QRegularExpression("common://|company://|custom://|embed://")); + return path.remove(QRegularExpression("common://|company://|custom://|macros://|embed://")); } } @@ -232,54 +232,34 @@ QString ElementsLocation::path() const (start by common://, company://, custom:// or embed://) or not. @param path */ + void ElementsLocation::setPath(const QString &path) { QString tmp_path = path; -#ifdef Q_OS_WIN32 - //On windows, we convert backslash to slash + #ifdef Q_OS_WIN32 tmp_path = QDir::fromNativeSeparators(path); + #endif -#endif + QString macrosPath = QETApp::userMacrosDir(); + if (macrosPath.endsWith("/")) macrosPath.remove(macrosPath.length() - 1, 1); - //There is a project, the path is for an embedded coolection. if (m_project) { m_collection_path = path; - //Add the protocol to the collection path if (!path.startsWith("embed://")) m_collection_path.prepend("embed://"); - } - - //The path start with project, we get the project and the path from the string else if (tmp_path.startsWith("project")) { - QRegularExpression re - ("^project(?[0-9])\\+(?embed://*.*)$"); - if (!re.isValid()) - { - qWarning() <[0-9])\\+(?embed://*.*)$"); + if (!re.isValid()) return; QRegularExpressionMatch match = re.match(tmp_path); - if (!match.hasMatch()) - { - qDebug()<<"no Match => return" - < +#include #include #include @@ -95,9 +100,107 @@ void ElementsTreeView::startElementDrag(const ElementsLocation &location) if (location.isDirectory()) { mime_data->setData("application/x-qet-category-uri", - location_str.toLatin1()); + location_str.toLatin1()); drag->setPixmap(QET::Icons::Folder.pixmap(22, 22)); } + else if (location.fileName().endsWith(".qetmak")) + { + mime_data->setData("application/x-qet-element-uri", location_str.toLatin1()); + + QPixmap macro_pixmap; + + // --- MINI-RENDERER FÜR DAS MAKRO-VORSCHAUBILD --- + QFile file(location.fileSystemPath()); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QDomDocument macro_doc; + if (macro_doc.setContent(&file)) { + QDomElement root = macro_doc.documentElement(); + if (root.tagName() == "qet_macro") { + + // 1. Unsichtbares Dummy-Projekt erstellen + QScopedPointer dummy_project(new QETProject()); + + // 2. Bauteile in das Dummy-Projekt laden (wie beim echten Drop) + QDomElement collection_node = root.firstChildElement("collection"); + if (!collection_node.isNull()) { + QDomNodeList elements = collection_node.elementsByTagName("element"); + for (int i = 0; i < elements.count(); ++i) { + QDomElement elmt_node = elements.at(i).toElement(); + QString path = elmt_node.attribute("path"); + QDomElement definition = elmt_node.firstChildElement("definition"); + if (!path.isEmpty() && !definition.isNull()) { + int last_slash = path.lastIndexOf('/'); + QString dir_path = (last_slash != -1) ? path.left(last_slash) : ""; + QString file_name = (last_slash != -1) ? path.mid(last_slash + 1) : path; + + if (!dir_path.isEmpty()) { + #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + QStringList parts = dir_path.split('/', QString::SkipEmptyParts); + #else + QStringList parts = dir_path.split('/', Qt::SkipEmptyParts); + #endif + QString current_path = ""; + for (const QString &part : parts) { + QString parent_path = current_path; + if (!current_path.isEmpty()) current_path += "/"; + current_path += part; + if (current_path == "import") continue; + NamesList empty_names; + dummy_project->embeddedElementCollection()->createDir(parent_path, part, empty_names); + } + } + dummy_project->embeddedElementCollection()->addElementDefinition(dir_path, file_name, definition); + } + } + } + + Diagram *dummy_diagram = dummy_project->addNewDiagram(); + + // 4. Makro auf dem unsichtbaren Blatt zeichnen + QDomElement diagram_content_node = root.firstChildElement("diagram_content"); + QDomElement diagram_node = diagram_content_node.firstChildElement("diagram"); + if (!diagram_node.isNull()) { + QDomNodeList instances = diagram_node.elementsByTagName("element"); + for (int i = 0; i < instances.count(); ++i) { + QDomElement inst = instances.at(i).toElement(); + QString type = inst.attribute("type"); + if (type.startsWith("macro://")) { + inst.setAttribute("type", type.replace("macro://", "embed://")); + } + } + + dummy_diagram->fromXml(diagram_node, QPointF(0, 0), false, nullptr); + dummy_diagram->clearSelection(); + + // 5. "Screenshot" (Pixmap) von den gezeichneten Elementen machen + QRectF scene_rect = dummy_diagram->itemsBoundingRect(); + if (!scene_rect.isEmpty()) { + scene_rect.adjust(-5, -5, 5, 5); // Kleiner Rand + macro_pixmap = QPixmap(scene_rect.size().toSize()); + macro_pixmap.fill(Qt::transparent); // Transparenter Hintergrund + + QPainter painter(¯o_pixmap); + painter.setRenderHint(QPainter::Antialiasing); + dummy_diagram->render(&painter, macro_pixmap.rect(), scene_rect); + } + } + } + } + } + + if (macro_pixmap.isNull()) { + macro_pixmap = QET::Icons::Project.pixmap(32, 32); + } + + // Bild verkleinern, falls das Makro gigantisch groß ist + if (macro_pixmap.width() > MAX_DND_PIXMAP_WIDTH || macro_pixmap.height() > MAX_DND_PIXMAP_HEIGHT) { + macro_pixmap = macro_pixmap.scaled(MAX_DND_PIXMAP_WIDTH, MAX_DND_PIXMAP_HEIGHT, Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + + drag->setPixmap(macro_pixmap); + // Bild zentriert an die Maus hängen + drag->setHotSpot(QPoint(macro_pixmap.width() / 2, macro_pixmap.height() / 2)); + } else if (location.isElement()) { mime_data->setData("application/x-qet-element-uri", diff --git a/sources/ElementsCollection/fileelementcollectionitem.cpp b/sources/ElementsCollection/fileelementcollectionitem.cpp index 3349d196f..3550dbec7 100644 --- a/sources/ElementsCollection/fileelementcollectionitem.cpp +++ b/sources/ElementsCollection/fileelementcollectionitem.cpp @@ -93,7 +93,7 @@ QString FileElementCollectionItem::dirPath() const */ bool FileElementCollectionItem::isDir() const { - if (m_path.endsWith(".elmt")) + if (m_path.endsWith(".elmt") || m_path.endsWith(".qetmak")) return false; else return true; @@ -110,9 +110,9 @@ bool FileElementCollectionItem::isElement() const } /** - @brief FileElementCollectionItem::localName - @return the located name of this item -*/ + * @brief FileElementCollectionItem::localName + * @return the located name of this item + */ QString FileElementCollectionItem::localName() { if (!text().isNull()) @@ -120,12 +120,17 @@ QString FileElementCollectionItem::localName() else if (isDir()) { if (isCollectionRoot()) { + QString macrosPath = QETApp::userMacrosDir(); + if (macrosPath.endsWith("/")) macrosPath.remove(macrosPath.length() - 1, 1); + if (m_path == QETApp::commonElementsDirN()) setText(QObject::tr("Collection QET")); else if (m_path == QETApp::companyElementsDirN()) setText(QObject::tr("Collection Company")); else if (m_path == QETApp::customElementsDirN()) setText(QObject::tr("Collection utilisateur")); + else if (m_path == macrosPath) + setText(QObject::tr("Makros")); else setText(QObject::tr("Collection inconnue")); } @@ -136,7 +141,7 @@ QString FileElementCollectionItem::localName() if(docu.load_file(str.toStdString().c_str())) { if (QString(docu.document_element().name()) - == "qet-directory") + == "qet-directory") { NamesList nl; nl.fromXml(docu.document_element()); @@ -147,7 +152,11 @@ QString FileElementCollectionItem::localName() } else if (isElement()) { ElementsLocation loc(collectionPath()); - setText(loc.name()); + QString display_name = loc.name(); + if (display_name.endsWith(".qetmak")) { + display_name.remove(".qetmak"); + } + setText(display_name); } return text(); @@ -169,7 +178,12 @@ QString FileElementCollectionItem::localName(const ElementsLocation &location) localName(); } else if (isElement()) { - setText(location.name()); + QString display_name = location.name(); + // Schneide die Endung .qetmak für die Anzeige ab + if (display_name.endsWith(".qetmak")) { + display_name.remove(".qetmak"); + } + setText(display_name); } return text(); @@ -194,24 +208,29 @@ QString FileElementCollectionItem::name() const QString FileElementCollectionItem::collectionPath() const { if (isCollectionRoot()) { + QString macrosPath = QETApp::userMacrosDir(); + if (macrosPath.endsWith("/")) macrosPath.remove(macrosPath.length() - 1, 1); + if (m_path == QETApp::commonElementsDirN()) return "common://"; else if (m_path == QETApp::companyElementsDirN()) return "company://"; - else - return "custom://"; + else if (m_path == macrosPath) + return "macros://"; // <-- NEU: Protokoll für Makros zuweisen + else + return "custom://"; } else if (parent() && parent()->type() - == FileElementCollectionItem::Type) { + == FileElementCollectionItem::Type) { ElementCollectionItem *eci = - static_cast(parent()); - if (eci->isCollectionRoot()) - return eci->collectionPath() + m_path; + static_cast(parent()); + if (eci->isCollectionRoot()) + return eci->collectionPath() + m_path; else return eci->collectionPath() % "/" % m_path; - } - else - return QString(); + } + else + return QString(); } /** @@ -220,10 +239,14 @@ QString FileElementCollectionItem::collectionPath() const */ bool FileElementCollectionItem::isCollectionRoot() const { + QString macrosPath = QETApp::userMacrosDir(); + if (macrosPath.endsWith("/")) macrosPath.remove(macrosPath.length() - 1, 1); + if (m_path == QETApp::commonElementsDirN() - || m_path == QETApp::companyElementsDirN() - || m_path == QETApp::customElementsDirN()) - return true; + || m_path == QETApp::companyElementsDirN() + || m_path == QETApp::customElementsDirN() + || m_path == macrosPath) + return true; else return false; } @@ -318,12 +341,17 @@ void FileElementCollectionItem::setUpIcon() return; if (isCollectionRoot()) { + QString macrosPath = QETApp::userMacrosDir(); + if (macrosPath.endsWith("/")) macrosPath.remove(macrosPath.length() - 1, 1); + if (m_path == QETApp::commonElementsDirN()) setIcon(QIcon(":/ico/16x16/qet.png")); else if (m_path == QETApp::companyElementsDirN()) setIcon(QIcon(":/ico/16x16/go-company.png")); - else - setIcon(QIcon(":/ico/16x16/go-home.png")); + else if (m_path == macrosPath) + setIcon(QIcon(":/ico/16x16/go-home.png")); // <-- NEU: Icon für Makros (z.B. go-home) + else + setIcon(QIcon(":/ico/16x16/go-home.png")); } else { @@ -347,13 +375,13 @@ void FileElementCollectionItem::setUpIcon() @param hide_element */ void FileElementCollectionItem::setPathName(const QString& path_name, - bool set_data, - bool hide_element) + bool set_data, + bool hide_element) { m_path = path_name; - //This isn't an element, we create the childs - if (!path_name.endsWith(".elmt")) + //This isn't an element or template, we create the childs + if (!path_name.endsWith(".elmt") && !path_name.endsWith(".qetmak")) populate(set_data, hide_element); } @@ -382,9 +410,9 @@ void FileElementCollectionItem::populate(bool set_data, bool hide_element) return; //Get all elmt file in this directory - dir.setNameFilters(QStringList() << "*.elmt"); + dir.setNameFilters(QStringList() << "*.elmt" << "*.qetmak"); for (auto& str : - dir.entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name)) + dir.entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name)) { FileElementCollectionItem *feci = new FileElementCollectionItem(); appendRow(feci); @@ -393,3 +421,15 @@ void FileElementCollectionItem::populate(bool set_data, bool hide_element) feci->setUpData(); } } + +/** + * @brief FileElementCollectionItem::isMacrosCollection + * @return True if this item represent the macros collection + */ +bool FileElementCollectionItem::isMacrosCollection() const +{ + QString macrosPath = QETApp::userMacrosDir(); + if (macrosPath.endsWith("/")) macrosPath.remove(macrosPath.length() - 1, 1); + + return fileSystemPath().startsWith(macrosPath); +} diff --git a/sources/ElementsCollection/fileelementcollectionitem.h b/sources/ElementsCollection/fileelementcollectionitem.h index ae023fb7e..d2c735d72 100644 --- a/sources/ElementsCollection/fileelementcollectionitem.h +++ b/sources/ElementsCollection/fileelementcollectionitem.h @@ -50,6 +50,7 @@ class FileElementCollectionItem : public ElementCollectionItem bool isCommonCollection() const; bool isCompanyCollection() const; bool isCustomCollection() const; + bool isMacrosCollection() const; void addChildAtPath(const QString &collection_name) override; void setUpData() override; diff --git a/sources/diagramevent/diagrameventaddmacro.cpp b/sources/diagramevent/diagrameventaddmacro.cpp new file mode 100644 index 000000000..6ca126cff --- /dev/null +++ b/sources/diagramevent/diagrameventaddmacro.cpp @@ -0,0 +1,260 @@ +/* + * Copyright 2006-2026 The QElectroTech Team + * This file is part of QElectroTech. + */ +#include "diagrameventaddmacro.h" + +#include "../diagram.h" +#include "../qetapp.h" +#include "../qetdiagrameditor.h" +#include "../qetproject.h" +#include "../ElementsCollection/xmlelementcollection.h" +#include "../NameList/nameslist.h" +#include "../diagramcommands.h" +#include "../diagramcontent.h" +#include +#include +#include +#include +#include + +DiagramEventAddMacro::DiagramEventAddMacro(const ElementsLocation &location, Diagram *diagram, QPointF pos) : +DiagramEventInterface(diagram), +m_location(location), +m_preview_item(nullptr) +{ + if (loadMacro()) { + init(); + + QScopedPointer dummy_project(new QETProject()); + QDomElement root = m_macro_doc.documentElement(); + + QDomElement collection_node = root.firstChildElement("collection"); + if (!collection_node.isNull()) { + QDomNodeList elements = collection_node.elementsByTagName("element"); + for (int i = 0; i < elements.count(); ++i) { + QDomElement elmt_node = elements.at(i).toElement(); + QString path = elmt_node.attribute("path"); + QDomElement definition = elmt_node.firstChildElement("definition"); + + if (!path.isEmpty() && !definition.isNull()) { + int last_slash = path.lastIndexOf('/'); + QString dir_path = (last_slash != -1) ? path.left(last_slash) : ""; + QString file_name = (last_slash != -1) ? path.mid(last_slash + 1) : path; + + if (!dir_path.isEmpty()) { + #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + QStringList parts = dir_path.split('/', QString::SkipEmptyParts); + #else + QStringList parts = dir_path.split('/', Qt::SkipEmptyParts); + #endif + QString current_path = ""; + for (const QString &part : parts) { + QString parent_path = current_path; + if (!current_path.isEmpty()) current_path += "/"; + current_path += part; + if (current_path == "import") continue; + NamesList empty_names; + dummy_project->embeddedElementCollection()->createDir(parent_path, part, empty_names); + } + } + dummy_project->embeddedElementCollection()->addElementDefinition(dir_path, file_name, definition); + } + } + } + + Diagram *dummy_diagram = dummy_project->addNewDiagram(); + QDomElement diagram_node = root.firstChildElement("diagram_content").firstChildElement("diagram"); + + if (!diagram_node.isNull()) { + dummy_diagram->fromXml(diagram_node, QPointF(0, 0), false, nullptr); + + QRectF scene_rect = dummy_diagram->itemsBoundingRect(); + if (!scene_rect.isEmpty()) { + QPixmap pixmap(scene_rect.toAlignedRect().size()); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + painter.setRenderHint(QPainter::Antialiasing); + dummy_diagram->render(&painter, QRectF(QPointF(0,0), scene_rect.size()), scene_rect); + + m_preview_item = new QGraphicsPixmapItem(pixmap); + m_preview_item->setOffset(scene_rect.topLeft()); + } + } + + if (m_preview_item) { + m_preview_item->setPos(Diagram::snapToGrid(pos)); + m_preview_item->setOpacity(0.6); + m_diagram->addItem(m_preview_item); + m_running = true; + } + + if (!diagram->views().isEmpty()) { + const auto qde = QETApp::diagramEditorAncestorOf(diagram->views().at(0)); + if (qde) { + m_status_bar = qde->statusBar(); + } + } else { + m_status_bar.clear(); + } + } +} + +DiagramEventAddMacro::~DiagramEventAddMacro() +{ + if (m_preview_item) { + m_diagram->removeItem(m_preview_item); + delete m_preview_item; + } + + if (m_status_bar) { + m_status_bar->clearMessage(); + } + + for (auto view : m_diagram->views()) + view->setContextMenuPolicy(Qt::DefaultContextMenu); +} + +void DiagramEventAddMacro::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if (m_preview_item) { + const auto pos_{Diagram::snapToGrid(event->scenePos())}; + m_preview_item->setPos(pos_); + + if (m_status_bar) { + m_status_bar->showMessage(QString("x %1 : y %2 (Makro-Anker)").arg(QString::number(pos_.x()), QString::number(pos_.y()))); + } + } + event->setAccepted(true); +} + +void DiagramEventAddMacro::mousePressEvent(QGraphicsSceneMouseEvent *event) { + event->setAccepted(true); +} + +void DiagramEventAddMacro::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (m_preview_item) { + if (event->button() == Qt::RightButton) { + m_diagram->removeItem(m_preview_item); + delete m_preview_item; + m_preview_item = nullptr; + m_running = false; + emit finish(); + } + else if (event->button() == Qt::LeftButton) { + addMacro(Diagram::snapToGrid(event->scenePos())); + } + } + event->setAccepted(true); +} + +void DiagramEventAddMacro::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + if (m_preview_item && (event->button() == Qt::LeftButton)) { + m_diagram->removeItem(m_preview_item); + delete m_preview_item; + m_preview_item = nullptr; + m_running = false; + emit finish(); + } + event->setAccepted(true); +} + +void DiagramEventAddMacro::keyPressEvent(QKeyEvent *event) +{ + DiagramEventInterface::keyPressEvent(event); +} + +void DiagramEventAddMacro::init() +{ + foreach(QGraphicsView *view, m_diagram->views()) + view->setContextMenuPolicy(Qt::NoContextMenu); +} + +bool DiagramEventAddMacro::loadMacro() +{ + QFile file(m_location.fileSystemPath()); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qDebug() << "Error: Macro file could not be read:" << m_location.fileSystemPath(); + return false; + } + + if (!m_macro_doc.setContent(&file)) { + qDebug() << "Error: Invalid XML in macro."; + return false; + } + + QDomElement root = m_macro_doc.documentElement(); + if (root.tagName() != "qet_macro") return false; + + QDomElement collection_node = root.firstChildElement("collection"); + if (!collection_node.isNull()) { + QDomNodeList elements = collection_node.elementsByTagName("element"); + for (int i = 0; i < elements.count(); ++i) { + QDomElement elmt_node = elements.at(i).toElement(); + QString path = elmt_node.attribute("path"); + QDomElement definition = elmt_node.firstChildElement("definition"); + + if (!path.isEmpty() && !definition.isNull()) { + int last_slash = path.lastIndexOf('/'); + QString dir_path = (last_slash != -1) ? path.left(last_slash) : ""; + QString file_name = (last_slash != -1) ? path.mid(last_slash + 1) : path; + + if (!dir_path.isEmpty()) { + #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + QStringList parts = dir_path.split('/', QString::SkipEmptyParts); + #else + QStringList parts = dir_path.split('/', Qt::SkipEmptyParts); + #endif + QString current_path = ""; + for (const QString &part : parts) { + QString parent_path = current_path; + if (!current_path.isEmpty()) current_path += "/"; + current_path += part; + if (current_path == "import") continue; + NamesList empty_names; + m_diagram->project()->embeddedElementCollection()->createDir(parent_path, part, empty_names); + } + } + m_diagram->project()->embeddedElementCollection()->addElementDefinition(dir_path, file_name, definition); + } + } + } + + QDomElement diagram_node = root.firstChildElement("diagram_content").firstChildElement("diagram"); + if (!diagram_node.isNull()) { + QDomNodeList instances = diagram_node.elementsByTagName("element"); + for (int i = 0; i < instances.count(); ++i) { + QDomElement inst = instances.at(i).toElement(); + QString type = inst.attribute("type"); + if (type.startsWith("macro://")) { + inst.setAttribute("type", type.replace("macro://", "embed://")); + } + } + } + + return true; +} + +void DiagramEventAddMacro::addMacro(QPointF final_pos) +{ + QDomElement root = m_macro_doc.documentElement(); + QDomElement diagram_node = root.firstChildElement("diagram_content").firstChildElement("diagram"); + + if (!diagram_node.isNull()) { + QDomElement cloned_node = diagram_node.cloneNode(true).toElement(); + + QPointF target_pos = final_pos; + if (m_preview_item) { + target_pos += m_preview_item->offset(); + } + + DiagramContent pasted_content; + + m_diagram->fromXml(cloned_node, target_pos, false, &pasted_content); + m_diagram->refreshContents(); + + m_diagram->undoStack().push(new PasteDiagramCommand(m_diagram, pasted_content)); + } +} diff --git a/sources/diagramevent/diagrameventaddmacro.h b/sources/diagramevent/diagrameventaddmacro.h new file mode 100644 index 000000000..3f726bca5 --- /dev/null +++ b/sources/diagramevent/diagrameventaddmacro.h @@ -0,0 +1,45 @@ +/* + C opyright 2006-2026 The QEle*ctroTech Team + This file is part of QElectroTech. + */ +#ifndef DIAGRAMEVENTADDMACRO_H +#define DIAGRAMEVENTADDMACRO_H + +#include "../ElementsCollection/elementslocation.h" +#include "diagrameventinterface.h" + +#include +#include + +class QStatusBar; + +/** + * @brief The DiagramEventAddMacro class + */ +class DiagramEventAddMacro : public DiagramEventInterface +{ + Q_OBJECT + +public: + DiagramEventAddMacro(const ElementsLocation &location, Diagram *diagram, QPointF pos = QPointF(0,0)); + ~DiagramEventAddMacro() override; + + void mouseMoveEvent (QGraphicsSceneMouseEvent *event) override; + void mousePressEvent (QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent (QGraphicsSceneMouseEvent *event) override; + void mouseDoubleClickEvent (QGraphicsSceneMouseEvent *event) override; + void keyPressEvent (QKeyEvent *event) override; + void init() override; + +private: + bool loadMacro(); + void addMacro(QPointF final_pos); + +private: + ElementsLocation m_location; + QDomDocument m_macro_doc; + QGraphicsPixmapItem *m_preview_item; + QPointer m_status_bar; +}; + +#endif // DIAGRAMEVENTADDMACRO_H diff --git a/sources/diagramview.cpp b/sources/diagramview.cpp index da74601c1..271ac6fb3 100644 --- a/sources/diagramview.cpp +++ b/sources/diagramview.cpp @@ -20,6 +20,7 @@ #include "QPropertyUndoCommand/qpropertyundocommand.h" #include "diagramcommands.h" #include "diagramevent/diagrameventaddelement.h" +#include "diagramevent/diagrameventaddmacro.h" #include "dvevent/dveventinterface.h" #include "projectview.h" #include "qetdiagrameditor.h" @@ -34,7 +35,9 @@ #include "utils/conductorcreator.h" #include "undocommand/addgraphicsobjectcommand.h" #include "diagram.h" - +#include "ElementsCollection/xmlelementcollection.h" +#include "NameList/nameslist.h" +#include "elementdialog.h" #include /** @@ -84,6 +87,10 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) : d.exec(); }); + // Setup the action to create a template + m_create_template = new QAction(tr("Créer un template", "context menu action"), this); + connect(m_create_template, SIGNAL(triggered()), this, SLOT(createTemplateFromSelection())); + //setup three separators, to be use in context menu for(int i=0 ; i<3 ; ++i) { @@ -188,13 +195,13 @@ void DiagramView::dropEvent(QDropEvent *e) { } /** - @brief DiagramView::handleElementDrop - Handle the drop of an element. - @param event the QDropEvent describing the current drag'n drop -*/ + * @brief DiagramView::handleElementDrop + * Handle the drop of an element. + * @param event the QDropEvent describing the current drag'n drop + */ void DiagramView::handleElementDrop(QDropEvent *event) { - //Build an element from the text of the mime data + //Build an element from the text of the mime data ElementsLocation location(event->mimeData()->text()); if ( !(location.isElement() && location.exist()) ) @@ -203,20 +210,20 @@ void DiagramView::handleElementDrop(QDropEvent *event) return; } -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove - diagram()->setEventInterface( - new DiagramEventAddElement( - location, diagram(), mapToScene(event->pos()))); -#else -#if TODO_LIST -#pragma message("@TODO remove code for QT 6 or later") -#endif - diagram()->setEventInterface( - new DiagramEventAddElement( - location, diagram(), event->position())); -#endif + QPointF drop_pos; + #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove + drop_pos = mapToScene(event->pos()); + #else + drop_pos = event->position(); + #endif - //Set focus to the view to get event + if (location.path().endsWith(".qetmak")) { + diagram()->setEventInterface(new DiagramEventAddMacro(location, diagram(), drop_pos)); + } else { + diagram()->setEventInterface(new DiagramEventAddElement(location, diagram(), drop_pos)); + } + + //Set focus to the view to get event this->setFocus(); } @@ -1202,6 +1209,7 @@ QList DiagramView::contextMenuActions() const list << qde->m_copy; list << m_multi_paste; list << m_separators.at(0); + list << m_create_template; // Add the create template action list << qde->m_conductor_reset; list << m_separators.at(1); list << qde->m_selection_actions_group.actions(); @@ -1265,6 +1273,101 @@ void DiagramView::contextMenuEvent(QContextMenuEvent *e) } } +/** + * @brief DiagramView::createTemplateFromSelection + * Triggered from the context menu to create a new template (macro) from the current selection. + */ +void DiagramView::createTemplateFromSelection() +{ + QList selected_elements = m_diagram->selectedItems(); + + if (selected_elements.isEmpty()) { + return; + } + + qDebug() << "Ready to create a template from" << selected_elements.size() << "elements!"; + + // Open the dialog to let the user select where to save the .qetmak file + ElementsLocation template_location = ElementDialog::getSaveTemplateLocation(this); + + // Check if the user clicked 'Cancel' or closed the window + if (template_location.isNull()) { + qDebug() << "User canceled template creation."; + return; + } + + qDebug() << "Will save template to:" << template_location.path(); + + QDomDocument content_xml = m_diagram->toXml(false, true); + + QDomDocument macro_doc; + QDomElement root = macro_doc.createElement("qet_macro"); + macro_doc.appendChild(root); + + QDomElement collection_node = macro_doc.createElement("collection"); + root.appendChild(collection_node); + + QSet processed_types; + + QDomNodeList element_nodes = content_xml.elementsByTagName("element"); + for (int i = 0; i < element_nodes.count(); ++i) { + QDomElement elmt_node = element_nodes.at(i).toElement(); + QString old_type = elmt_node.attribute("type"); + + if (old_type.isEmpty()) continue; + + ElementsLocation loc(old_type, m_diagram->project()); + + QString clean_path = loc.collectionPath(false); + + QString new_type = "macro://" + clean_path; + elmt_node.setAttribute("type", new_type); + + if (!processed_types.contains(clean_path)) { + processed_types.insert(clean_path); + + QDomElement definition_node = loc.xml(); + + if (!definition_node.isNull()) { + QDomElement collection_elmt = macro_doc.createElement("element"); + collection_elmt.setAttribute("path", clean_path); + + QDomNode imported_def = macro_doc.importNode(definition_node, true); + collection_elmt.appendChild(imported_def); + + collection_node.appendChild(collection_elmt); + } else { + qDebug() << "Warnung: Konnte XML-Definition für" << old_type << "nicht laden."; + } + } + } + + QDomElement content_container = macro_doc.createElement("diagram_content"); + root.appendChild(content_container); + + QDomNode imported_node = macro_doc.importNode(content_xml.documentElement(), true); + content_container.appendChild(imported_node); + + QString full_path = template_location.fileSystemPath(); + + QDir().mkpath(QFileInfo(full_path).absolutePath()); + + QFile file(full_path); + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream out(&file); + out.setCodec("UTF-8"); + out << macro_doc.toString(4); + file.close(); + qDebug() << "Template successfully saved to:" << full_path; + + QMessageBox::information(this, tr("Modèle enregistré"), + tr("Le modèle a été enregistré avec succès sous :\n%1").arg(full_path)); + } else { + qDebug() << "Error: Could not open file for writing:" << full_path; + QMessageBox::critical(this, tr("Erreur"), tr("Le fichier n'a pas pu être écrit.")); + } +} + /** @return l'editeur de schemas parent ou 0 */ diff --git a/sources/diagramview.h b/sources/diagramview.h index 880b18cd9..5f87227f3 100644 --- a/sources/diagramview.h +++ b/sources/diagramview.h @@ -18,7 +18,7 @@ #ifndef DIAGRAMVIEW_H #define DIAGRAMVIEW_H -#include "ElementsCollection/elementslocation.h" +#include "../ElementsCollection/elementslocation.h" #include "titleblock/templatelocation.h" #include @@ -53,6 +53,7 @@ class DiagramView : public QGraphicsView DVEventInterface *m_event_interface = nullptr; QAction *m_paste_here = nullptr; QAction *m_multi_paste = nullptr; + QAction *m_create_template = nullptr; QPoint m_paste_here_pos; QPointF m_drag_last_pos; bool m_fresh_focus_in, @@ -133,5 +134,6 @@ class DiagramView : public QGraphicsView private slots: void adjustGridToZoom(); void applyReadOnly(); + void createTemplateFromSelection(); }; #endif diff --git a/sources/elementdialog.cpp b/sources/elementdialog.cpp index 662704f62..155a64bac 100644 --- a/sources/elementdialog.cpp +++ b/sources/elementdialog.cpp @@ -74,6 +74,10 @@ void ElementDialog::setUpWidget() title_ = tr("Enregistrer une catégorie", "dialog title"); label_ = tr("Choisissez une catégorie.", "dialog content"); break; + case SaveTemplate: + title_ = tr("Enregistrer un template", "dialog title"); + label_ = tr("Choisissez l'emplacement dans lequel vous souhaitez enregistrer votre template.", "dialog content"); + break; default: title_ = tr("Titre"); label_ = tr("Label"); @@ -92,10 +96,14 @@ void ElementDialog::setUpWidget() foreach(QETProject *prj, QETApp::registeredProjects()) prjs.append(prj); - if (m_mode == OpenElement) + if (m_mode == OpenElement) { m_model->loadCollections(true, true, true, prjs); - else + } else if (m_mode == SaveTemplate) { + // Load only the templates/macros collection for the template save dialog + m_model->loadMacrosCollection(); + } else { m_model->loadCollections(false, true, true, prjs); + } m_tree_view->setModel(m_model); m_tree_view->setHeaderHidden(true); @@ -103,14 +111,21 @@ void ElementDialog::setUpWidget() m_buttons_box = new QDialogButtonBox(this); - if (m_mode == SaveCategory || m_mode == SaveElement) + if (m_mode == SaveCategory || m_mode == SaveElement || m_mode == SaveTemplate) { m_buttons_box->setStandardButtons(QDialogButtonBox::Save | QDialogButtonBox::Cancel); m_buttons_box->button(QDialogButtonBox::Save)->setDisabled(true); m_text_field = new QFileNameEdit(); m_text_field->setDisabled(true); - m_text_field->setPlaceholderText(m_mode == SaveCategory? tr("Nom du nouveau dossier") : tr("Nom du nouvel élément")); + + if (m_mode == SaveCategory) { + m_text_field->setPlaceholderText(tr("Nom du nouveau dossier")); + } else if (m_mode == SaveTemplate) { + m_text_field->setPlaceholderText(tr("Nom du nouveau template")); + } else { + m_text_field->setPlaceholderText(tr("Nom du nouvel élément")); + } layout->addWidget(m_text_field); } @@ -156,14 +171,17 @@ void ElementDialog::checkCurrentLocation() if (m_mode == OpenElement) { m_buttons_box->button(QDialogButtonBox::Open)->setEnabled(m_location.isElement() && m_location.exist()); } - else if (m_mode == SaveElement) + else if (m_mode == SaveElement || m_mode == SaveTemplate) { m_buttons_box->button(QDialogButtonBox::Save)->setDisabled(true); - //Location doesn't exist + //Location doesn't exist if (!m_location.exist()) { return; } - if (m_location.isElement()) + // Accept .elmt for elements, and .qetmak for templates + bool is_valid_file = m_location.isElement() || (m_mode == SaveTemplate && m_location.path().endsWith(".qetmak")); + + if (is_valid_file) { m_text_field->setDisabled(true); m_buttons_box->button(QDialogButtonBox::Save)->setEnabled(true); @@ -174,10 +192,10 @@ void ElementDialog::checkCurrentLocation() if (m_text_field->text().isEmpty()) { return; } - //Only enable save button if the location at path : - //m_location.collectionPath + m_text_filed.text doesn't exist. + //Only enable save button if the location at path doesn't exist. QString new_path = m_text_field->text(); - if (!new_path.endsWith(".elmt")) new_path += ".elmt"; + QString extension = (m_mode == SaveTemplate) ? ".qetmak" : ".elmt"; + if (!new_path.endsWith(extension)) new_path += extension; ElementsLocation loc = m_location; loc.addToPath(new_path); @@ -209,15 +227,19 @@ void ElementDialog::checkAccept() return; } } - else if (m_mode == SaveElement) + else if (m_mode == SaveElement || m_mode == SaveTemplate) { - if (loc.isElement()) + bool is_valid_file = loc.isElement() || (m_mode == SaveTemplate && loc.path().endsWith(".qetmak")); + + if (is_valid_file) { if (loc.exist()) { + QString msgTitle = (m_mode == SaveTemplate) ? tr("Écraser le template ?", "message box title") : tr("Écraser l'élément ?", "message box title"); + QString msgContent = (m_mode == SaveTemplate) ? tr("Le template existe déjà. Voulez-vous l'écraser ?", "message box content") : tr("L'élément existe déjà. Voulez-vous l'écraser ?", "message box content"); QMessageBox::StandardButton answer = QET::QetMessageBox::question(this, - tr("Écraser l'élément ?", "message box title"), - tr("L'élément existe déjà. Voulez-vous l'écraser ?", "message box content"), + msgTitle, + msgContent, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (answer == QMessageBox::Yes) {accept();} @@ -248,19 +270,21 @@ ElementsLocation ElementDialog::location() const else {return ElementsLocation(); } } - else if (m_mode == SaveElement) + else if (m_mode == SaveElement || m_mode == SaveTemplate) { - //Current selected location is element, we return this location - if (m_location.isElement()) { return m_location; } + //Current selected location is element or template, we return this location + bool is_valid_file = m_location.isElement() || (m_mode == SaveTemplate && m_location.path().endsWith(".qetmak")); + if (is_valid_file) { return m_location; } - //Current selected location is directory, we return a location at path : - //m_location->collectionPath + m_text_field->text + //Current selected location is directory, we return a location at path : + //m_location->collectionPath + m_text_field->text else if (m_location.isDirectory()) { QString new_path = m_text_field->text(); if (new_path.isEmpty()) { return ElementsLocation(); } - if (!new_path.endsWith(".elmt")) { new_path += ".elmt"; } + QString extension = (m_mode == SaveTemplate) ? ".qetmak" : ".elmt"; + if (!new_path.endsWith(extension)) { new_path += extension; } ElementsLocation loc = m_location; loc.addToPath(new_path); @@ -291,6 +315,16 @@ ElementsLocation ElementDialog::getSaveElementLocation(QWidget *parentWidget) { return(ElementDialog::execConfiguredDialog(ElementDialog::SaveElement, parentWidget)); } +/** + * @brief ElementDialog::getSaveTemplateLocation + * Display a dialog that allow to user to select a location for saving a template + * @param parentWidget + * @return The location where the template must be save + */ +ElementsLocation ElementDialog::getSaveTemplateLocation(QWidget *parentWidget) { + return(ElementDialog::execConfiguredDialog(ElementDialog::SaveTemplate, parentWidget)); +} + /** @brief ElementDialog::execConfiguredDialog launch a dialog with the chosen mode diff --git a/sources/elementdialog.h b/sources/elementdialog.h index 9c000981a..9d9cbc95a 100644 --- a/sources/elementdialog.h +++ b/sources/elementdialog.h @@ -42,7 +42,8 @@ class ElementDialog : public QDialog OpenElement = 0, ///< The dialog should open an element SaveElement = 1, ///< The dialog should select an element for saving OpenCategory = 2, ///< The dialog should open a category - SaveCategory = 3 ///< The dialog should select a category for saving + SaveCategory = 3, ///< The dialog should select a category for saving + SaveTemplate = 4 ///< The dialog should select a template for saving }; // constructors, destructor @@ -74,6 +75,7 @@ class ElementDialog : public QDialog public: static ElementsLocation getOpenElementLocation(QWidget *parent = nullptr); static ElementsLocation getSaveElementLocation(QWidget *parent = nullptr); + static ElementsLocation getSaveTemplateLocation(QWidget *parent = nullptr); private: static ElementsLocation execConfiguredDialog(int, QWidget *parent = nullptr); diff --git a/sources/elementspanelwidget.cpp b/sources/elementspanelwidget.cpp index 0d17a19d0..610d9c5cc 100644 --- a/sources/elementspanelwidget.cpp +++ b/sources/elementspanelwidget.cpp @@ -24,6 +24,7 @@ #include "qeticons.h" #include "qetproject.h" #include "titleblock/templatedeleter.h" +#include /* When the ENABLE_PANEL_WIDGET_DND_CHECKS flag is set, the panel @@ -152,6 +153,11 @@ void ElementsPanelWidget::openDirectoryForSelectedItem() if (QTreeWidgetItem *qtwi = elements_panel -> currentItem()) { QString dir_path = elements_panel -> dirPathForItem(qtwi); if (!dir_path.isEmpty()) { + QFileInfo fileInfo(dir_path); + // Wenn der Pfad auf eine Datei (z.B. Makro) zeigt, isoliere den Ordnerpfad + if (fileInfo.isFile()) { + dir_path = fileInfo.absolutePath(); + } QDesktopServices::openUrl(QUrl::fromLocalFile(dir_path)); } } diff --git a/sources/qetapp.cpp b/sources/qetapp.cpp index f4e14ad57..aaa462fc5 100644 --- a/sources/qetapp.cpp +++ b/sources/qetapp.cpp @@ -91,6 +91,8 @@ QString QETApp::m_user_company_tbt_dir = QString(); QString QETApp::m_user_custom_tbt_dir = QString(); +QString QETApp::m_user_macros_dir = QString(); + QETApp *QETApp::m_qetapp = nullptr; bool lang_is_set = false; @@ -724,6 +726,8 @@ void QETApp::resetCollectionsPath() m_user_company_tbt_dir.clear(); m_user_custom_tbt_dir.clear(); + + m_user_macros_dir.clear(); } /** @@ -822,6 +826,38 @@ QString QETApp::customTitleBlockTemplatesDir() return(dataDir() + "/titleblocks/"); } +/** + * @brief QETApp::userMacrosDir + * @return the path of the directory containing the user macros collection. + */ +QString QETApp::userMacrosDir() +{ + if (m_user_macros_dir.isEmpty()) + { + QSettings settings; + QString path = settings.value( + "elements-collections/macros-path", + "default").toString(); + if (path != "default" && !path.isEmpty()) + { + QDir dir(path); + if (dir.exists()) + { + m_user_macros_dir = path; + return m_user_macros_dir; + } + } + else { + m_user_macros_dir = "default"; + } + } + else if (m_user_macros_dir != "default") { + return m_user_macros_dir; + } + + return(dataDir() + "/macros/"); +} + /** @brief QETApp::configDir Return the QET configuration folder, i.e. the path to the folder in @@ -938,6 +974,8 @@ QString QETApp::realPath(const QString &sym_path) { directory = commonElementsDir(); } else if (sym_path.startsWith("company://")) { directory = companyElementsDir(); + } else if (sym_path.startsWith("macros://")) { + directory = userMacrosDir(); } else if (sym_path.startsWith("company://")) { directory = companyElementsDir(); } else if (sym_path.startsWith("custom://")) { @@ -976,6 +1014,7 @@ QString QETApp::symbolicPath(const QString &real_path) { QString commond = commonElementsDir(); QString companyd = companyElementsDir(); QString customd = customElementsDir(); + QString macrosd = userMacrosDir(); QString chemin; // analyzes the file path passed in parameter // analyse le chemin de fichier passe en parametre @@ -987,6 +1026,10 @@ QString QETApp::symbolicPath(const QString &real_path) { chemin = "company://" + real_path.right( real_path.length() - companyd.length()); + } else if (real_path.startsWith(macrosd)) { + chemin = "macros://" + + real_path.right( + real_path.length() - macrosd.length()); } else if (real_path.startsWith(customd)) { chemin = "custom://" + real_path.right( @@ -2212,6 +2255,10 @@ void QETApp::initConfiguration() if (!custom_tbt_dir.exists()) custom_tbt_dir.mkpath(QETApp::customTitleBlockTemplatesDir()); + QDir macros_dir(QETApp::userMacrosDir()); + if (!macros_dir.exists()) + macros_dir.mkpath(QETApp::userMacrosDir()); + /* recent files * note: * icons must be initialized before these instructions diff --git a/sources/qetapp.h b/sources/qetapp.h index 59d10d3cd..2f4ecc363 100644 --- a/sources/qetapp.h +++ b/sources/qetapp.h @@ -91,6 +91,7 @@ class QETApp : public QObject static QString commonTitleBlockTemplatesDir(); static QString companyTitleBlockTemplatesDir(); static QString customTitleBlockTemplatesDir(); + static QString userMacrosDir(); static bool registerProject(QETProject *); static bool unregisterProject(QETProject *); static QMap registeredProjects(); @@ -242,7 +243,7 @@ class QETApp : public QObject static QString m_user_company_tbt_dir; static QString m_user_custom_tbt_dir; - + static QString m_user_macros_dir; public slots: void systray(QSystemTrayIcon::ActivationReason); diff --git a/sources/ui/configpage/generalconfigurationpage.cpp b/sources/ui/configpage/generalconfigurationpage.cpp index 00d62f57a..9fe0c9b8f 100644 --- a/sources/ui/configpage/generalconfigurationpage.cpp +++ b/sources/ui/configpage/generalconfigurationpage.cpp @@ -177,6 +177,15 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) : ui->m_custom_tbt_path_cb->blockSignals(false); } + path = settings.value("elements-collections/macros-path", "default").toString(); + if (path != "default") + { + ui->m_user_macros_path_cb->blockSignals(true); + ui->m_user_macros_path_cb->setCurrentIndex(1); + ui->m_user_macros_path_cb->setItemData(1, path, Qt::DisplayRole); + ui->m_user_macros_path_cb->blockSignals(false); + } + fillLang(); } @@ -321,6 +330,21 @@ void GeneralConfigurationPage::applyConf() if (path != settings.value("elements-collections/custom-tbt-path").toString()) { QETApp::resetCollectionsPath(); } + + path = settings.value("elements-collections/macros-path").toString(); + if (ui->m_user_macros_path_cb->currentIndex() == 1) + { + QString path = ui->m_user_macros_path_cb->currentText(); + QDir dir(path); + settings.setValue("elements-collections/macros-path", + dir.exists() ? path : "default"); + } + else { + settings.setValue("elements-collections/macros-path", "default"); + } + if (path != settings.value("elements-collections/macros-path").toString()) { + QETApp::resetCollectionsPath(); + } } /** @@ -512,6 +536,19 @@ void GeneralConfigurationPage::on_m_custom_tbt_path_cb_currentIndexChanged(int i } } +void GeneralConfigurationPage::on_m_user_macros_path_cb_currentIndexChanged(int index) +{ + if (index == 1) + { + QString path = QFileDialog::getExistingDirectory(this, tr("Chemin des macros utilisateur"), QETApp::documentDir()); + if (!path.isEmpty()) { + ui->m_user_macros_path_cb->setItemData(1, path, Qt::DisplayRole); + } + else { + ui->m_user_macros_path_cb->setCurrentIndex(0); + } + } +} void GeneralConfigurationPage::on_m_indi_text_font_pb_clicked() { diff --git a/sources/ui/configpage/generalconfigurationpage.h b/sources/ui/configpage/generalconfigurationpage.h index ce85d8f82..ab55fdb79 100644 --- a/sources/ui/configpage/generalconfigurationpage.h +++ b/sources/ui/configpage/generalconfigurationpage.h @@ -46,6 +46,7 @@ class GeneralConfigurationPage : public ConfigPage void on_m_custom_elmt_path_cb_currentIndexChanged(int index); void on_m_company_tbt_path_cb_currentIndexChanged(int index); void on_m_custom_tbt_path_cb_currentIndexChanged(int index); + void on_m_user_macros_path_cb_currentIndexChanged(int index); void on_m_indi_text_font_pb_clicked(); void on_MaxPartsElementEditorList_sb_valueChanged(int value); void on_DiagramEditor_Grid_PointSize_min_sb_valueChanged(int value); diff --git a/sources/ui/configpage/generalconfigurationpage.ui b/sources/ui/configpage/generalconfigurationpage.ui index 38130c8b9..1fd4959f3 100644 --- a/sources/ui/configpage/generalconfigurationpage.ui +++ b/sources/ui/configpage/generalconfigurationpage.ui @@ -17,7 +17,7 @@ - 0 + 2 @@ -349,6 +349,27 @@ + + + + Répertoire des Macros utilisateur + + + + + + + + Par defaut + + + + + Parcourir... + + + +