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/elementscollectionwidget.cpp b/sources/ElementsCollection/elementscollectionwidget.cpp index a4c61a78b..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); @@ -194,7 +191,7 @@ void ElementsCollectionWidget::setUpWidget() m_tree_view->setMouseTracking(true); m_tree_view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - //Setup the macros tree view (DEV) + //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)); @@ -209,7 +206,7 @@ void ElementsCollectionWidget::setUpWidget() 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 (DEV)")); + 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); @@ -258,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) { @@ -276,11 +277,15 @@ void ElementsCollectionWidget::setUpConnection() this, &ElementsCollectionWidget::customContextMenu); connect(m_macros_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_macros_tree_view, &QTreeView::entered, [this] (const QModelIndex &index) { @@ -310,7 +315,7 @@ void ElementsCollectionWidget::customContextMenu(const QPoint &point) 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) @@ -398,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(); @@ -422,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, @@ -468,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, diff --git a/sources/ElementsCollection/elementslocation.cpp b/sources/ElementsCollection/elementslocation.cpp index 2f2f810fe..c14f244ff 100644 --- a/sources/ElementsCollection/elementslocation.cpp +++ b/sources/ElementsCollection/elementslocation.cpp @@ -297,7 +297,7 @@ void ElementsLocation::setPath(const QString &path) else { QString path_ = path; - if(path_.endsWith(".elmt")) + if(path_.endsWith(".elmt") || path_.endsWith(".qetmak")) { m_file_system_path = path_; if (path_.startsWith(QETApp::commonElementsDirN())) @@ -366,10 +366,11 @@ void ElementsLocation::setPath(const QString &path) */ bool ElementsLocation::addToPath(const QString &string) { - if (m_collection_path.endsWith(".elmt", Qt::CaseInsensitive)) + if (m_collection_path.endsWith(".elmt", Qt::CaseInsensitive) || + m_collection_path.endsWith(".qetmak", Qt::CaseInsensitive)) { qDebug() << "ElementsLocation::addToPath :" - " Can't add string to the path of an element"; + " Can't add string to the path of an element or template"; return(false); } @@ -472,7 +473,7 @@ QString ElementsLocation::toString() const */ bool ElementsLocation::isElement() const { - return m_collection_path.endsWith(".elmt"); + return m_collection_path.endsWith(".elmt") || m_collection_path.endsWith(".qetmak"); } /** diff --git a/sources/ElementsCollection/elementstreeview.cpp b/sources/ElementsCollection/elementstreeview.cpp index 13b4a793a..bfd69b97b 100644 --- a/sources/ElementsCollection/elementstreeview.cpp +++ b/sources/ElementsCollection/elementstreeview.cpp @@ -22,7 +22,12 @@ #include "../qeticons.h" #include "elementcollectionitem.h" #include "elementslocation.h" - +#include "../qetproject.h" +#include "../diagram.h" +#include "xmlelementcollection.h" +#include "../nameslist.h" +#include +#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 f1b26672c..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; @@ -120,7 +120,6 @@ QString FileElementCollectionItem::localName() else if (isDir()) { if (isCollectionRoot()) { - // --- NEU: Makro-Pfad laden --- QString macrosPath = QETApp::userMacrosDir(); if (macrosPath.endsWith("/")) macrosPath.remove(macrosPath.length() - 1, 1); @@ -130,7 +129,7 @@ QString FileElementCollectionItem::localName() setText(QObject::tr("Collection Company")); else if (m_path == QETApp::customElementsDirN()) setText(QObject::tr("Collection utilisateur")); - else if (m_path == macrosPath) // <-- NEU: Name des Ordners zuweisen + else if (m_path == macrosPath) setText(QObject::tr("Makros")); else setText(QObject::tr("Collection inconnue")); @@ -153,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(); @@ -175,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(); @@ -237,7 +245,7 @@ bool FileElementCollectionItem::isCollectionRoot() const if (m_path == QETApp::commonElementsDirN() || m_path == QETApp::companyElementsDirN() || m_path == QETApp::customElementsDirN() - || m_path == macrosPath) // <-- NEU: Makros sind jetzt ein echtes Root-Verzeichnis + || m_path == macrosPath) return true; else return false; @@ -367,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); } @@ -402,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); diff --git a/sources/diagramevent/diagrameventaddmacro.cpp b/sources/diagramevent/diagrameventaddmacro.cpp new file mode 100644 index 000000000..7d3971bd4 --- /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 "../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..c3a935f60 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 "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 89e6b521b..f9424c299 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/ui/configpage/generalconfigurationpage.cpp b/sources/ui/configpage/generalconfigurationpage.cpp index e050c5e45..9fe0c9b8f 100644 --- a/sources/ui/configpage/generalconfigurationpage.cpp +++ b/sources/ui/configpage/generalconfigurationpage.cpp @@ -540,7 +540,7 @@ void GeneralConfigurationPage::on_m_user_macros_path_cb_currentIndexChanged(int { if (index == 1) { - QString path = QFileDialog::getExistingDirectory(this, tr("Chemin des macros utilisateur (DEV)"), QETApp::documentDir()); + 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); } diff --git a/sources/ui/configpage/generalconfigurationpage.ui b/sources/ui/configpage/generalconfigurationpage.ui index 2e645d9c0..1fd4959f3 100644 --- a/sources/ui/configpage/generalconfigurationpage.ui +++ b/sources/ui/configpage/generalconfigurationpage.ui @@ -17,7 +17,7 @@ - 0 + 2 @@ -352,7 +352,7 @@ - Macros utilisateur (DEV) + Répertoire des Macros utilisateur