Merge branch 'master' into qt6_cmake_joshua

This commit is contained in:
joshua
2026-05-05 20:09:51 +02:00
118 changed files with 64023 additions and 19853 deletions

View File

@@ -119,8 +119,8 @@ int ElementCollectionItem::rowForInsertItem(const QString &name)
return -1;
QList <ElementCollectionItem *> 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

View File

@@ -259,10 +259,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<QETProject *> projects)
bool company_collection,
bool custom_collection,
QList<QETProject *> projects)
{
clear();
m_items_list_to_setUp.clear();
if (common_collection)
@@ -275,17 +277,17 @@ 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<void>();
connect(watcher, &QFutureWatcher<void>::progressValueChanged,
this, &ElementsCollectionModel::loadingProgressValueChanged);
this, &ElementsCollectionModel::loadingProgressValueChanged);
connect(watcher, &QFutureWatcher<void>::progressRangeChanged,
this, &ElementsCollectionModel::loadingProgressRangeChanged);
this, &ElementsCollectionModel::loadingProgressRangeChanged);
connect(watcher, &QFutureWatcher<void>::finished,
this, &ElementsCollectionModel::loadingFinished);
connect(
@@ -298,6 +300,41 @@ void ElementsCollectionModel::loadCollections(bool common_collection,
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
@@ -356,11 +393,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);
@@ -375,14 +412,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 <ElementCollectionItem *> child_list;
for (int i=0 ; i<rowCount() ; i++)
@@ -392,15 +430,18 @@ void ElementsCollectionModel::addLocation(const ElementsLocation& location)
if (eci->type() == FileElementCollectionItem::Type) {
FileElementCollectionItem *feci =
static_cast<FileElementCollectionItem *>(eci);
static_cast<FileElementCollectionItem *>(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;
}
}
}
}
}
@@ -562,14 +603,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 <ElementCollectionItem *> child_list;
@@ -577,30 +618,34 @@ QModelIndex ElementsCollectionModel::indexFromLocation(
child_list.append(static_cast<ElementCollectionItem *>(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<FileElementCollectionItem *>(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<FileElementCollectionItem *>(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<XmlProjectElementCollectionItem *>(eci)) {
match_eci = xpeci->itemAtPath(location.collectionPath(false));
}
}
else if (eci->type() == XmlProjectElementCollectionItem::Type) {
if (XmlProjectElementCollectionItem *xpeci = static_cast<XmlProjectElementCollectionItem *>(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();
}
/**

View File

@@ -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);

View File

@@ -93,10 +93,7 @@ void ElementsCollectionWidget::addProject(QETProject *project)
{
if (m_model)
{
m_progress_bar->show();
m_tree_view->setDisabled(true);
QList <QETProject *> 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<QTreeView *>(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<FileElementCollectionItem*>(eci);
static_cast<FileElementCollectionItem*>(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<XmlProjectElementCollectionItem *>(eci);
static_cast<XmlProjectElementCollectionItem *>(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<QAbstractItemModel*>(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<QAbstractItemModel*>(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<FileElementCollectionItem*>(eci);
FileElementCollectionItem *feci = static_cast<FileElementCollectionItem*>(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)
@@ -836,15 +906,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<ElementCollectionItem*>(
m_model->itemFromIndex(index));
if (m_macros_model && index.model() == m_macros_model) {
return static_cast<ElementCollectionItem *>(m_macros_model->itemFromIndex(index));
}
if (m_model && index.model() == m_model) {
return static_cast<ElementCollectionItem *>(m_model->itemFromIndex(index));
}
return nullptr;
}

View File

@@ -25,6 +25,8 @@
#include <QTimer>
#include <QElapsedTimer>
#include <QScopedPointer>
#include <QTabWidget>
#include <QTreeView>
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;

View File

@@ -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(?<project_id>[0-9])\\+(?<collection_path>embed://*.*)$");
if (!re.isValid())
{
qWarning() <<QObject::tr("this is an error in the code")
<< re.errorString()
<< re.patternErrorOffset();
return;
}
QRegularExpression re ("^project(?<project_id>[0-9])\\+(?<collection_path>embed://*.*)$");
if (!re.isValid()) return;
QRegularExpressionMatch match = re.match(tmp_path);
if (!match.hasMatch())
{
qDebug()<<"no Match => return"
<<tmp_path;
return;
}
if (!match.hasMatch()) return;
bool conv_ok;
uint project_id = match.captured("project_id").toUInt(&conv_ok);
if (!conv_ok)
{
qWarning()<<"toUint failed"
<<match.captured("project_id")
<<re
<<tmp_path;
return;
}
if (!conv_ok) return;
QETProject *project = QETApp::project(project_id);
if (project)
{
@@ -287,10 +267,7 @@ void ElementsLocation::setPath(const QString &path)
m_project = project;
}
}
// The path is in file system,
// the given path is relative to common or custom collection
else if (path.startsWith("common://") || path.startsWith("company://") || path.startsWith("custom://"))
else if (path.startsWith("common://") || path.startsWith("company://") || path.startsWith("custom://") || path.startsWith("macros://"))
{
QString p;
if (path.startsWith("common://"))
@@ -303,6 +280,11 @@ void ElementsLocation::setPath(const QString &path)
tmp_path.remove("company://");
p = QETApp::companyElementsDirN() % "/" % tmp_path;
}
else if (path.startsWith("macros://"))
{
tmp_path.remove("macros://");
p = macrosPath % "/" % tmp_path;
}
else
{
tmp_path.remove("custom://");
@@ -312,11 +294,10 @@ void ElementsLocation::setPath(const QString &path)
m_file_system_path = p;
m_collection_path = path;
}
//In this case, the path is supposed to be relative to the file system.
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()))
@@ -331,6 +312,13 @@ void ElementsLocation::setPath(const QString &path)
path_.prepend("company://");
m_collection_path = path_;
}
else if (path_.startsWith(macrosPath))
{
QString matchPath = macrosPath + "/";
path_.remove(matchPath);
path_.prepend("macros://");
m_collection_path = path_;
}
else if (path_.startsWith(QETApp::customElementsDirN()))
{
path_.remove(QETApp::customElementsDirN()+="/");
@@ -353,6 +341,13 @@ void ElementsLocation::setPath(const QString &path)
path_.prepend("company://");
m_collection_path = path_;
}
else if (path_.startsWith(macrosPath))
{
QString matchPath = macrosPath + "/";
path_.remove(matchPath);
path_.prepend("macros://");
m_collection_path = path_;
}
else if (path_.startsWith(QETApp::customElementsDirN()))
{
path_.remove(QETApp::customElementsDirN()+="/");
@@ -371,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);
}
@@ -477,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");
}
/**
@@ -942,3 +938,14 @@ QDebug operator<< (QDebug debug, const ElementsLocation &location)
return debug;
}
/**
* @brief ElementsLocation::isMacrosCollection
* @return True if this location represent an item from the macros collection
*/
bool ElementsLocation::isMacrosCollection() const
{
QString macrosPath = QETApp::userMacrosDir();
if (macrosPath.endsWith("/")) macrosPath.remove(macrosPath.length() - 1, 1);
return fileSystemPath().startsWith(macrosPath);
}

View File

@@ -78,6 +78,7 @@ class ElementsLocation
bool isCommonCollection() const;
bool isCompanyCollection() const;
bool isCustomCollection() const;
bool isMacrosCollection() const;
bool isProject() const;
bool exist() const;
bool isWritable() const;

View File

@@ -22,7 +22,12 @@
#include "../qeticons.h"
#include "elementcollectionitem.h"
#include "elementslocation.h"
#include "../qetproject.h"
#include "../diagram.h"
#include "xmlelementcollection.h"
#include "../NameList/nameslist.h"
#include <QPainter>
#include <QScopedPointer>
#include <QDrag>
#include <QStandardItemModel>
@@ -91,9 +96,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<QETProject> 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(&macro_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",

View File

@@ -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,11 @@ QString FileElementCollectionItem::localName(const ElementsLocation &location)
localName();
}
else if (isElement()) {
setText(location.name());
QString display_name = location.name();
if (display_name.endsWith(".qetmak")) {
display_name.remove(".qetmak");
}
setText(display_name);
}
return text();
@@ -194,24 +207,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://"; //
else
return "custom://";
}
else if (parent() && parent()->type()
== FileElementCollectionItem::Type) {
== FileElementCollectionItem::Type) {
ElementCollectionItem *eci =
static_cast<ElementCollectionItem*>(parent());
if (eci->isCollectionRoot())
return eci->collectionPath() + m_path;
static_cast<ElementCollectionItem*>(parent());
if (eci->isCollectionRoot())
return eci->collectionPath() + m_path;
else
return eci->collectionPath() % "/" % m_path;
}
else
return QString();
}
else
return QString();
}
/**
@@ -220,10 +238,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;
}
@@ -273,55 +295,63 @@ void FileElementCollectionItem::addChildAtPath(const QString &collection_name)
}
/**
@brief FileElementCollectionItem::setUpData
SetUp the data of this item
*/
* @brief FileElementCollectionItem::setUpData
* SetUp the data of this item
*/
void FileElementCollectionItem::setUpData()
{
if (isDir())
{
localName();
setFlags(Qt::ItemIsSelectable
| Qt::ItemIsDragEnabled
| Qt::ItemIsDropEnabled
| Qt::ItemIsEnabled);
| Qt::ItemIsDragEnabled
| Qt::ItemIsDropEnabled
| Qt::ItemIsEnabled);
}
else
{
setFlags(Qt::ItemIsSelectable
| Qt::ItemIsDragEnabled
| Qt::ItemIsEnabled);
//Set the local name and all informations of the element
//in the data Qt::UserRole+1, these data will be use for search.
ElementsLocation loc(collectionPath());
DiagramContext context = loc.elementInformations();
QStringList search_list;
for (QString& key : context.keys())
{ search_list.append(context.value(key).toString()); }
search_list.append(localName(loc));
setData(search_list.join(" "));
| Qt::ItemIsDragEnabled
| Qt::ItemIsEnabled);
if (m_path.endsWith(".qetmak")) {
setData(localName());
} else {
// Parse standard element information for search
ElementsLocation loc(collectionPath());
DiagramContext context = loc.elementInformations();
QStringList search_list;
for (QString& key : context.keys())
{ search_list.append(context.value(key).toString()); }
search_list.append(localName(loc));
setData(search_list.join(" "));
}
}
setToolTip(collectionPath());
}
/**
@brief FileElementCollectionItem::setUpIcon
SetUp the icon of this item.
Because icon use several memory,
we use this method for setup icon instead setUpData.
*/
* @brief FileElementCollectionItem::setUpIcon
* SetUp the icon of this item.
* Because icon use several memory,
* we use this method for setup icon instead setUpData.
*/
void FileElementCollectionItem::setUpIcon()
{
if (!icon().isNull())
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 if (m_path == macrosPath)
setIcon(QIcon(":/ico/16x16/go-home.png"));
else
setIcon(QIcon(":/ico/16x16/go-home.png"));
}
@@ -330,8 +360,12 @@ void FileElementCollectionItem::setUpIcon()
if (isDir()) {
setIcon(QET::Icons::Folder);
} else {
ElementsLocation loc(collectionPath());
setIcon(loc.icon());
if (m_path.endsWith(".qetmak")) {
setIcon(QIcon());
} else {
ElementsLocation loc(collectionPath());
setIcon(loc.icon());
}
}
}
}
@@ -347,13 +381,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 +416,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 +427,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);
}

View File

@@ -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;

View File

@@ -58,6 +58,10 @@ XmlElementCollection::XmlElementCollection(QETProject *project) :
QChar(0x30A4), QChar(0x30F3), QChar(0x30D0), QChar(0x30FC),
QChar(0x30C8), QChar(0x3055), QChar(0x308C), QChar(0x305F),
QChar(0x8981), QChar(0x7D20)};
const QChar korean_data[8] = {
QChar(0xC804), QChar(0xAE30), QChar(0x0020),
QChar(0xC2EC), QChar(0xBCFC), QChar(0x0020),
QChar(0xC694), QChar(0xC18C)};
const QChar russian_data[24] = {
QChar(0x0418), QChar(0x043C), QChar(0x043F), QChar(0x043E),
QChar(0x0440), QChar(0x0442), QChar(0x0438), QChar(0x0440),
@@ -88,6 +92,8 @@ XmlElementCollection::XmlElementCollection(QETProject *project) :
names.addName("it", "Elementi importati");
names.addName("ja", QString(japanese_data, 10));
//names.addName("ja", "インバートされた要素");
names.addName("ko", QString(korean_data, 8));
names.addName("ko_KR", QString(korean_data, 8));
names.addName("nl", "Elementen geïmporteerd");
names.addName("nl_BE", "Elementen geïmporteerd");
names.addName("pl", "Elementy importowane");

View File

@@ -0,0 +1,257 @@
/*
* 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 <QFile>
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QStatusBar>
#include <QPainter>
DiagramEventAddMacro::DiagramEventAddMacro(const ElementsLocation &location, Diagram *diagram, QPointF pos) :
DiagramEventInterface(diagram),
m_location(location),
m_preview_item(nullptr)
{
if (loadMacro()) {
init();
QScopedPointer<QETProject> 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;
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));
}
}

View File

@@ -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 <QDomDocument>
#include <QGraphicsPixmapItem>
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<QStatusBar> m_status_bar;
};
#endif // DIAGRAMEVENTADDMACRO_H

View File

@@ -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 <QDropEvent>
/**
@@ -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,12 +210,13 @@ void DiagramView::handleElementDrop(QDropEvent *event)
return;
}
if (location.path().endsWith(".qetmak")) {
diagram()->setEventInterface(new DiagramEventAddMacro(location, diagram(), event->position()));
} else {
diagram()->setEventInterface(new DiagramEventAddElement(location, diagram(), event->position()));
}
diagram()->setEventInterface(
new DiagramEventAddElement(
location, diagram(), event->position()));
//Set focus to the view to get event
//Set focus to the view to get event
this->setFocus();
}
@@ -1157,6 +1165,7 @@ QList<QAction *> 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();
@@ -1220,6 +1229,100 @@ 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<QGraphicsItem *> 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<QString> 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 << 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
*/

View File

@@ -18,7 +18,7 @@
#ifndef DIAGRAMVIEW_H
#define DIAGRAMVIEW_H
#include "ElementsCollection/elementslocation.h"
#include "../ElementsCollection/elementslocation.h"
#include "titleblock/templatelocation.h"
#include <QClipboard>
@@ -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

View File

@@ -1,4 +1,4 @@
/*
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
@@ -96,8 +96,18 @@ void ElementPropertiesEditorWidget::upDateInterface()
}
else if (m_data.m_type == ElementData::Master) {
ui->m_master_type_cb->setCurrentIndex(
ui->m_master_type_cb->findData (
m_data.m_master_type));
ui->m_master_type_cb->findData (
m_data.m_master_type));
// NEU: Checkbox und Zahlenbox für max_slaves einstellen
if (m_data.m_max_slaves == -1) {
ui->max_slaves_checkbox->setChecked(false);
ui->max_slaves_spinbox->setEnabled(false);
} else {
ui->max_slaves_checkbox->setChecked(true);
ui->max_slaves_spinbox->setEnabled(true);
ui->max_slaves_spinbox->setValue(m_data.m_max_slaves);
}
} else if (m_data.m_type == ElementData::Terminal) {
ui->m_terminal_type_cb->setCurrentIndex(
ui->m_terminal_type_cb->findData(
@@ -151,10 +161,13 @@ void ElementPropertiesEditorWidget::setUpInterface()
ui->m_terminal_func_cb->addItem(tr("Phase"), ElementData::TFPhase);
ui->m_terminal_func_cb->addItem(tr("Neutre"), ElementData::TFNeutral);
//Disable the edition of the first column of the information tree
//by this little workaround
//Disable the edition of the first column of the information tree
//by this little workaround
ui->m_tree->setItemDelegate(new EditorDelegate(this));
ui->m_tree->header()->resizeSection(0, 150);
// NEU: Checkbox mit der Zahlenbox verbinden (Aktivieren/Deaktivieren)
connect(ui->max_slaves_checkbox, SIGNAL(toggled(bool)), ui->max_slaves_spinbox, SLOT(setEnabled(bool)));
populateTree();
}
@@ -226,6 +239,13 @@ void ElementPropertiesEditorWidget::on_m_buttonBox_accepted()
}
else if (m_data.m_type == ElementData::Master) {
m_data.m_master_type = ui->m_master_type_cb->currentData().value<ElementData::MasterType>();
//If the checkbox is checked, save the number; otherwise, -1 (infinity)
if (ui->max_slaves_checkbox->isChecked()) {
m_data.m_max_slaves = ui->max_slaves_spinbox->value();
} else {
m_data.m_max_slaves = -1;
}
}
else if (m_data.m_type == ElementData::Terminal)
{

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>527</width>
<height>442</height>
<height>492</height>
</rect>
</property>
<property name="windowTitle">
@@ -104,6 +104,23 @@
<item row="0" column="1">
<widget class="QComboBox" name="m_master_type_cb"/>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="max_slaves_checkbox">
<property name="text">
<string>Définir le nombre maximal d'esclaves</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="max_slaves_spinbox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@@ -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

View File

@@ -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);

View File

@@ -67,7 +67,10 @@ ElementsPanel::ElementsPanel(QWidget *parent) :
connect(this, &ElementsPanel::itemDoubleClicked, this, &ElementsPanel::slot_doubleClick);
connect(this, &GenericPanel::firstActivated, [this]() {QTimer::singleShot(250, this, SLOT(reload()));});
connect(this, &ElementsPanel::panelContentChanged, this, &ElementsPanel::panelContentChange);
// manage signal itemClicked
connect(this, &ElementsPanel::itemClicked, this, &ElementsPanel::slot_clicked);
//Emit a signal instead au manage is own context menu
setContextMenuPolicy(Qt::CustomContextMenu);
}
@@ -132,31 +135,69 @@ void ElementsPanel::panelContentChange()
Le QTreeWidgetItem insere le plus haut
*/
QTreeWidgetItem *ElementsPanel::addProject(QETProject *project,
QTreeWidgetItem *parent_item,
PanelOptions options)
QTreeWidgetItem *parent_item,
PanelOptions options)
{
Q_UNUSED(parent_item)
Q_UNUSED(options)
// 1. Save the current chart before clearing the selection
Diagram *current_diagram = nullptr;
if (QTreeWidgetItem *current_qtwi = currentItem()) {
if (current_qtwi->type() == QET::Diagram) {
current_diagram = valueForItem<Diagram *>(current_qtwi);
}
}
bool first_add = (first_reload_ || !projects_to_display_.contains(project));
clearSelection();
// create the QTreeWidgetItem representing the project
QTreeWidgetItem *qtwi_project = GenericPanel::addProject(project, nullptr, GenericPanel::All);
// the project will be inserted right before the common tb templates collection
invisibleRootItem() -> insertChild(
indexOfTopLevelItem(common_tbt_collection_item_),
qtwi_project
qtwi_project
);
if (first_add) qtwi_project -> setExpanded(true);
if (first_add){
qtwi_project -> setExpanded(true);
// on adding an project select first diagram
setCurrentItem(qtwi_project -> child(0));
qtwi_project -> child(0)->setSelected(true);
}
else {
// 2. Check whether we can restore the previous selection
bool restored = false;
if (current_diagram) {
// Browse the children of the project node to find our diagram
for (int i = 0; i < qtwi_project->childCount(); ++i) {
QTreeWidgetItem *child = qtwi_project->child(i);
if (child->type() == QET::Diagram && valueForItem<Diagram *>(child) == current_diagram) {
setCurrentItem(child);
child->setSelected(true);
restored = true;
break;
}
}
}
// 3. Fallback: Only if NOTHING could be restored (e.g., actually adding a new page)
if (!restored && qtwi_project->childCount() >= 2) {
// on adding an diagram to project select the last diagram
setCurrentItem(qtwi_project->child(qtwi_project->childCount()-2));
qtwi_project->child(qtwi_project->childCount()-2)->setSelected(true);
}
}
if (TitleBlockTemplatesCollection *tbt_collection = project -> embeddedTitleBlockTemplatesCollection()) {
if (QTreeWidgetItem *tbt_collection_qtwi = itemForTemplatesCollection(tbt_collection)) {
if (first_add) tbt_collection_qtwi -> setExpanded(true);
}
}
qtwi_project -> setStatusTip(0, tr("Double-cliquez pour réduire ou développer ce projet", "Status tip"));
return(qtwi_project);
}
@@ -258,21 +299,28 @@ void ElementsPanel::reload()
}
/**
Gere le double-clic sur un element.
Si un double-clic sur un projet est effectue, le signal requestForProject
est emis.
Si un double-clic sur un schema est effectue, le signal requestForDiagram
est emis.
@brief ElementsPanel::slot_clicked
handle click on qtwi
@param qtwi item that was clickerd on
*/
void ElementsPanel::slot_clicked(QTreeWidgetItem *clickedItem, int) {
requestForItem(clickedItem);
}
/**
@brief ElementsPanel::slot_doubleClick
handle double click on qtwi
@param qtwi
*/
void ElementsPanel::slot_doubleClick(QTreeWidgetItem *qtwi, int) {
int qtwi_type = qtwi -> type();
if (qtwi_type == QET::Project) {
QETProject *project = valueForItem<QETProject *>(qtwi);
emit(requestForProject(project));
// open project properties
emit(requestForProjectPropertiesEdition());
} else if (qtwi_type == QET::Diagram) {
Diagram *diagram = valueForItem<Diagram *>(qtwi);
diagram->showMe();
// open diagram properties
emit(requestForDiagramPropertiesEdition());
} else if (qtwi_type == QET::TitleBlockTemplate) {
TitleBlockTemplateLocation tbt = valueForItem<TitleBlockTemplateLocation>(qtwi);
emit(requestForTitleBlockTemplate(tbt));
@@ -444,3 +492,64 @@ void ElementsPanel::ensureHierarchyIsVisible(const QList<QTreeWidgetItem *> &ite
if (parent_qtwi -> isHidden()) parent_qtwi -> setHidden(false);
}
}
/**
* @brief ElementsPanel::syncTabBars
* set the project- or diagram Tab corresponding to
* the selection in the treeView
*/
void ElementsPanel::requestForItem(QTreeWidgetItem *clickedItem)
{
// activate diagram
if(clickedItem->type() == QET::Diagram){
Diagram *diagram = valueForItem<Diagram *>(clickedItem);
// if we click on diagramItem in annother project we need the other project
emit(requestForProject(projectForItem(clickedItem->parent())));
// required for keyPressEvent
// after emit the focus is on the diagram editor, we put it back to elementsPanel
this->setFocus();
// activate diagram
diagram->showMe();
}
// activate project
else if(clickedItem->type() == QET::Project) {
QETProject *project = projectForItem(clickedItem);
emit(requestForProject(project));
this->setFocus();
}
}
/**
* @brief ElementsPanel::keyPressEvent
* @param event
*/
void ElementsPanel::keyPressEvent(QKeyEvent *event)
{
switch (event->key())
{
case Qt::Key_Up:{
// check if there is another item abbove
if(!itemAbove(currentItem()))
break;
setCurrentItem(itemAbove(currentItem()));
if (currentItem()->type()==QET::Diagram || currentItem()->type()==QET::Project){
requestForItem(currentItem());
}
break;
}
case Qt::Key_Down:{
// check if there is another item below
if(!itemBelow(currentItem()))
break;
setCurrentItem(itemBelow(currentItem()));
if (currentItem()->type()==QET::Diagram || currentItem()->type()==QET::Project){
requestForItem(currentItem());
}
break;
}
default:
QTreeView::keyPressEvent(event);
}
}

View File

@@ -53,8 +53,13 @@ class ElementsPanel : public GenericPanel {
signals:
void requestForProject(QETProject *);
void requestForTitleBlockTemplate(const TitleBlockTemplateLocation &);
// Signal to open the project properties
void requestForProjectPropertiesEdition();
// Signal to open the diagram properties
void requestForDiagramPropertiesEdition();
public slots:
void slot_clicked(QTreeWidgetItem *, int);
void slot_doubleClick(QTreeWidgetItem *, int);
void reload();
void filter(const QString &, QET::Filtering = QET::RegularFilter);
@@ -63,7 +68,9 @@ class ElementsPanel : public GenericPanel {
void buildFilterList();
void applyCurrentFilter(const QList<QTreeWidgetItem *> &);
void ensureHierarchyIsVisible(const QList<QTreeWidgetItem *> &);
void requestForItem(QTreeWidgetItem *);
void keyPressEvent(QKeyEvent *event)override;
protected:
void startDrag(Qt::DropActions) override;
void startTitleBlockTemplateDrag(const TitleBlockTemplateLocation &);

View File

@@ -24,6 +24,7 @@
#include "qeticons.h"
#include "qetproject.h"
#include "titleblock/templatedeleter.h"
#include <QFileInfo>
/*
When the ENABLE_PANEL_WIDGET_DND_CHECKS flag is set, the panel
@@ -120,6 +121,12 @@ ElementsPanelWidget::ElementsPanelWidget(QWidget *parent) : QWidget(parent) {
SLOT(openTitleBlockTemplate(const TitleBlockTemplateLocation &))
);
// manage double click on TreeWidgetItem
connect(elements_panel, SIGNAL(requestForProjectPropertiesEdition()), this, SLOT(editProjectProperties()) );
connect(elements_panel, SIGNAL(requestForDiagramPropertiesEdition()), this, SLOT(editDiagramProperties()) );
// manage project activation
connect(elements_panel, SIGNAL(requestForProject(QETProject*)), this, SIGNAL(requestForProject(QETProject*)));
// disposition verticale
QVBoxLayout *vlayout = new QVBoxLayout(this);
vlayout -> setContentsMargins(0,0,0,0);
@@ -146,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));
}
}
@@ -236,6 +248,7 @@ void ElementsPanelWidget::deleteDiagram()
{
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
emit(requestForDiagramDeletion(selected_diagram));
elements_panel->reload();
}
}
@@ -473,28 +486,33 @@ void ElementsPanelWidget::keyPressEvent (QKeyEvent *e) {
break;
case Qt::Key_F3:
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
emit(requestForDiagramMoveUp(selected_diagram));
}
break;
case Qt::Key_F4:
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
emit(requestForDiagramMoveDown(selected_diagram));
}
break;
case Qt::Key_F5:
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
emit(requestForDiagramMoveUpTop(selected_diagram));
}
break;
case Qt::Key_F6:
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
emit(requestForDiagramMoveDownx10(selected_diagram));
}
break;
case Qt::Key_F7:
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
emit(requestForDiagramMoveDownx100(selected_diagram));
}
@@ -502,12 +520,14 @@ void ElementsPanelWidget::keyPressEvent (QKeyEvent *e) {
break;
case Qt::Key_F8:
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
emit(requestForDiagramMoveUpx10(selected_diagram));
}
break;
case Qt::Key_F9:
if (Diagram *selected_diagram = elements_panel -> selectedDiagram()) {
elements_panel->setSelectedItem(elements_panel->getItemForDiagram(selected_diagram));
emit(requestForDiagramMoveUpx100(selected_diagram));
}
break;

View File

@@ -418,7 +418,8 @@ void ExportDialog::generateSvg(
// "joue" la QPicture sur un QSvgGenerator
QSvgGenerator svg_engine;
svg_engine.setSize(QSize((width*9/16), (height*9/16)));
svg_engine.setSize(QSize(width, height));
svg_engine.setViewBox(QRect(0, 0, width*0.75, height*0.75));
svg_engine.setOutputDevice(&io_device);
QPainter svg_painter(&svg_engine);
picture.play(&svg_painter);

View File

@@ -766,7 +766,6 @@ void GenericPanel::projectDiagramsOrderChanged(QETProject *project,
if (!moved_qtwi_diagram) return;
// remove the QTWI then insert it back at the adequate location
bool was_selected = moved_qtwi_diagram -> isSelected();
qtwi_project -> removeChild (moved_qtwi_diagram);
qtwi_project -> insertChild (to, moved_qtwi_diagram);
@@ -781,8 +780,14 @@ void GenericPanel::projectDiagramsOrderChanged(QETProject *project,
updateDiagramItem(qtwi_diagram, diagram);
}
if (was_selected)
// select the moved diagram
if(m_selected_item){
setCurrentItem(moved_qtwi_diagram);
}
else{
setCurrentItem(qtwi_project -> child(from));
}
m_selected_item = nullptr;
emit(panelContentChanged());
}
@@ -1041,3 +1046,12 @@ void GenericPanel::emitFirstActivated()
{
emit(firstActivated());
}
/**
@brief GenericPanel::setSelectedItem
@param selectedItem
*/
void GenericPanel::setSelectedItem(QTreeWidgetItem *selectedItem)
{
m_selected_item = selectedItem;
}

View File

@@ -95,8 +95,8 @@ class GenericPanel : public QTreeWidget {
virtual QTreeWidgetItem *addDiagram(Diagram *,
QTreeWidgetItem * = nullptr,
PanelOptions = AddAllChild);
protected:
virtual QTreeWidgetItem *getItemForDiagram(Diagram *, bool * = nullptr);
protected:
virtual QTreeWidgetItem *updateDiagramItem(QTreeWidgetItem *,
Diagram *,
PanelOptions = AddAllChild,
@@ -171,6 +171,9 @@ class GenericPanel : public QTreeWidget {
const QString &);
// various other methods
public:
void setSelectedItem(QTreeWidgetItem *selectedItem);
protected:
virtual QString defaultText(QET::ItemType);
virtual QIcon defaultIcon(QET::ItemType);
@@ -222,5 +225,7 @@ class GenericPanel : public QTreeWidget {
representing a title block template
*/
QHash<TitleBlockTemplateLocation, QTreeWidgetItem *> tb_templates_;
QTreeWidgetItem *m_selected_item = nullptr;
};
#endif

View File

@@ -721,6 +721,14 @@ void ProjectView::initActions()
m_end_view = new QAction(QET::Icons::ArrowRightDouble, tr("Aller à la fin du projet"),this);
connect(m_end_view, &QAction::triggered, [this](){this->m_tab->setCurrentWidget(lastDiagram());});
// button to scroll one page left
m_next_view_left = new QAction(QET::Icons::ArrowLeft, tr("go one page left"),this);
connect(m_next_view_left, &QAction::triggered, [this](){this->m_tab->setCurrentWidget(previousDiagram());});
// button to scroll one page right
m_next_view_right = new QAction(QET::Icons::ArrowRight, tr("go one page right"),this);
connect(m_next_view_right, &QAction::triggered, [this](){this->m_tab->setCurrentWidget(nextDiagram());});
}
/**
@@ -748,34 +756,65 @@ void ProjectView::initWidgets()
m_tab = new QTabWidget(this);
#endif
m_tab -> setMovable(true);
// setting UsesScrollButton ensures that when the tab bar is full, the tabs are scrolled.
m_tab -> setUsesScrollButtons(true);
// disable the internal scroll buttons of the TabWidget, we will use our own buttons.
m_tab->setStyleSheet("QTabBar QToolButton {border-image: ;border-width: 0px}");
m_tab->setStyleSheet("QTabBar::scroller {width: 0px;}");
// add layouts
QHBoxLayout *TopRightCorner_Layout = new QHBoxLayout();
TopRightCorner_Layout->setContentsMargins(0,0,0,0);
// some place left to the 'next_right_view_button' button
TopRightCorner_Layout->insertSpacing(1,10);
QToolButton *add_new_diagram_button = new QToolButton;
add_new_diagram_button -> setDefaultAction(m_add_new_diagram);
add_new_diagram_button -> setAutoRaise(true);
TopRightCorner_Layout->addWidget(add_new_diagram_button);
QHBoxLayout *TopLeftCorner_Layout = new QHBoxLayout();
TopLeftCorner_Layout->setContentsMargins(0,0,0,0);
// add buttons
QToolButton *m_next_right_view_button =new QToolButton;
m_next_right_view_button->setDefaultAction(m_next_view_right);
m_next_right_view_button->setAutoRaise(true);
TopRightCorner_Layout->addWidget(m_next_right_view_button);
connect(m_tab, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
connect(m_tab, SIGNAL(tabBarDoubleClicked(int)), this, SLOT(tabDoubleClicked(int)));
connect(m_tab->tabBar(), SIGNAL(tabMoved(int, int)), this, SLOT(tabMoved(int, int)), Qt::QueuedConnection);
//arrows button to return on first view
QToolButton *m_first_view_button =new QToolButton;
m_first_view_button->setDefaultAction(m_first_view);
m_first_view_button->setAutoRaise(true);
m_tab->setCornerWidget(m_first_view_button, Qt::TopLeftCorner);
//arrows button to go on last view
QToolButton *m_end_view_button =new QToolButton;
m_end_view_button->setDefaultAction(m_end_view);
m_end_view_button->setAutoRaise(true);
TopRightCorner_Layout->addWidget(m_end_view_button);
QWidget *tabwidget=new QWidget(this);
tabwidget->setLayout(TopRightCorner_Layout);
m_tab -> setCornerWidget(tabwidget, Qt::TopRightCorner);
QToolButton *add_new_diagram_button = new QToolButton;
add_new_diagram_button -> setDefaultAction(m_add_new_diagram);
add_new_diagram_button -> setAutoRaise(true);
TopRightCorner_Layout->addWidget(add_new_diagram_button);
// some place right to the 'add_new_diagram_button' button
TopRightCorner_Layout->addSpacing(5);
QToolButton *m_first_view_button =new QToolButton;
m_first_view_button->setDefaultAction(m_first_view);
m_first_view_button->setAutoRaise(true);
TopLeftCorner_Layout->addWidget(m_first_view_button);
QToolButton *m_next_left_view_button =new QToolButton;
m_next_left_view_button->setDefaultAction(m_next_view_left);
m_next_left_view_button->setAutoRaise(true);
TopLeftCorner_Layout->addWidget(m_next_left_view_button);
// some place right to the 'first_view_button' button
TopLeftCorner_Layout->addSpacing(10);
// add widgets to tabbar
QWidget *tabwidgetRight=new QWidget(this);
tabwidgetRight->setLayout(TopRightCorner_Layout);
m_tab -> setCornerWidget(tabwidgetRight, Qt::TopRightCorner);
QWidget *tabwidgetLeft=new QWidget(this);
tabwidgetLeft->setLayout(TopLeftCorner_Layout);
m_tab -> setCornerWidget(tabwidgetLeft, Qt::TopLeftCorner);
// manage signals
connect(m_tab, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
connect(m_tab, SIGNAL(tabBarDoubleClicked(int)), this, SLOT(tabDoubleClicked(int)));
connect(m_tab->tabBar(), SIGNAL(tabMoved(int,int)), this, SLOT(tabMoved(int,int)), Qt::QueuedConnection);
fallback_widget_ -> setVisible(false);
m_tab -> setVisible(false);

View File

@@ -168,8 +168,10 @@ class ProjectView : public QWidget
// attributes
private:
QAction *m_add_new_diagram,
*m_first_view,
*m_end_view;
*m_first_view,
*m_end_view,
*m_next_view_left,
*m_next_view_right;
QETProject *m_project;
QVBoxLayout *layout_;
QWidget *fallback_widget_;

View File

@@ -76,6 +76,16 @@ QDomElement ElementData::kindInfoToXml(QDomDocument &document)
xml_type.appendChild(type_txt);
returned_elmt.appendChild(xml_type);
// Save max_slaves only if a specific limit is set (not default -1)
if (m_max_slaves != -1) {
auto xml_max_slaves = document.createElement(QStringLiteral("kindInformation"));
xml_max_slaves.setAttribute(QStringLiteral("name"), QStringLiteral("max_slaves"));
auto max_slaves_txt = document.createTextNode(QString::number(m_max_slaves));
xml_max_slaves.appendChild(max_slaves_txt);
returned_elmt.appendChild(xml_max_slaves);
}
}
else if (m_type == ElementData::Slave)
{
@@ -558,9 +568,12 @@ void ElementData::kindInfoFromXml(const QDomElement &xml_element)
}
auto name = dom_elmt.attribute(QStringLiteral("name"));
if (m_type == ElementData::Master &&
name == QLatin1String("type")) {
m_master_type = masterTypeFromString(dom_elmt.text());
if (m_type == ElementData::Master) {
if (name == QLatin1String("type")) {
m_master_type = masterTypeFromString(dom_elmt.text());
} else if (name == QLatin1String("max_slaves")) {
m_max_slaves = dom_elmt.text().toInt();
}
}
else if (m_type == ElementData::Slave ) {
if (name == QLatin1String("type")) {

View File

@@ -134,6 +134,7 @@ class ElementData : public PropertiesInterface
ElementData::Type m_type = ElementData::Simple;
ElementData::MasterType m_master_type = ElementData::Coil;
int m_max_slaves{-1};
ElementData::SlaveType m_slave_type = ElementData::SSimple;
ElementData::SlaveState m_slave_state = ElementData::NO;
@@ -141,7 +142,7 @@ class ElementData : public PropertiesInterface
ElementData::TerminalType m_terminal_type = ElementData::TTGeneric;
ElementData::TerminalFunction m_terminal_function = ElementData::TFGeneric;
int m_contact_count = 1;
int m_contact_count{1};
DiagramContext m_informations;
NamesList m_names_list;
QString m_drawing_information;

View File

@@ -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;
@@ -717,6 +719,8 @@ void QETApp::resetCollectionsPath()
m_user_company_tbt_dir.clear();
m_user_custom_tbt_dir.clear();
m_user_macros_dir.clear();
}
/**
@@ -815,6 +819,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
@@ -931,6 +967,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://")) {
@@ -969,6 +1007,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
@@ -980,6 +1019,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(
@@ -2205,6 +2248,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

View File

@@ -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<uint, QETProject *> 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);

View File

@@ -45,6 +45,8 @@
#include "TerminalStrip/ui/terminalstripeditorwindow.h"
#include "ui/diagrameditorhandlersizewidget.h"
#include "TerminalStrip/ui/addterminalstripitemdialog.h"
#include "wiringlistexport.h"
#include "ui/terminalnumberingdialog.h"
#ifdef BUILD_WITHOUT_KF5
#else
@@ -465,13 +467,27 @@ void QETDiagramEditor::setUpActions()
wne.toCsv();
}
});
#ifdef QET_EXPORT_PROJECT_DB
m_export_project_db = new QAction(QET::Icons::DocumentSpreadsheet, tr("Exporter la base de donnée interne du projet"), this);
connect(m_export_project_db, &QAction::triggered, [this]() {
projectDataBase::exportDb(this->currentProject()->dataBase(), this);
// Export wiring list to CSV
m_project_export_wiring_list = new QAction(QET::Icons::DocumentSpreadsheet, tr("Exporter le plan de câblage"), this);
connect(m_project_export_wiring_list, &QAction::triggered, [this]() {
QETProject *project = this->currentProject();
if (project)
{
WiringListExport wle(project, this);
wle.toCsv();
}
});
#endif
// Terminal Numbering
m_terminal_numbering = new QAction(QET::Icons::TerminalStrip, tr("Numérotation automatique des bornes"), this);
connect(m_terminal_numbering, &QAction::triggered, this, &QETDiagramEditor::slot_terminalNumbering);
#ifdef QET_EXPORT_PROJECT_DB
m_export_project_db = new QAction(QET::Icons::DocumentSpreadsheet, tr("Exporter la base de donnée interne du projet"), this);
connect(m_export_project_db, &QAction::triggered, [this]() {
projectDataBase::exportDb(this->currentProject()->dataBase(), this);
});
#endif
//MDI view style
m_tabbed_view_mode = new QAction(tr("en utilisant des onglets"), this);
@@ -835,6 +851,8 @@ void QETDiagramEditor::setUpMenu()
menu_project -> addAction(m_project_export_conductor_num);
menu_project -> addAction(m_terminal_strip_dialog);
menu_project -> addAction(m_project_terminalBloc);
menu_project -> addAction(m_project_export_wiring_list);
menu_project -> addAction(m_terminal_numbering);
#ifdef QET_EXPORT_PROJECT_DB
menu_project -> addSeparator();
menu_project -> addAction(m_export_project_db);
@@ -1567,6 +1585,8 @@ void QETDiagramEditor::slot_updateActions()
m_csv_export -> setEnabled(editable_project);
m_project_export_conductor_num-> setEnabled(opened_project);
m_terminal_strip_dialog -> setEnabled(editable_project);
m_project_export_wiring_list -> setEnabled(opened_project);
m_terminal_numbering -> setEnabled(editable_project);
#ifdef QET_EXPORT_PROJECT_DB
m_export_project_db -> setEnabled(editable_project);
#endif
@@ -1828,6 +1848,31 @@ void QETDiagramEditor::addProjectView(ProjectView *project_view)
connect(project_view, SIGNAL(errorEncountered(QString)),
this, SLOT(showError(const QString &)));
//Highlight the current page
connect(project_view, &ProjectView::diagramActivated, this, [this](DiagramView *dv) {
if (dv && dv->diagram() && pa) {
// 1. Find the item in the tree that corresponds to this diagram
QTreeWidgetItem *item = pa->elementsPanel().getItemForDiagram(dv->diagram());
// 2. If you find it, select it
if (item) {
pa->elementsPanel().setCurrentItem(item);
}
}
});
//Highlight the current page in projectView on project activation
connect(this, &QETDiagramEditor::syncElementsPanel, this, [this]() {
if (pa && currentDiagramView()) {
// In the tree, find the element that corresponds to the diagram of the selected project.
QTreeWidgetItem *item = pa->elementsPanel().getItemForDiagram(currentDiagramView()->diagram());
if (item) {
// select the diagram
pa->elementsPanel().setCurrentItem(item);
}
}
});
//We maximise the new window if the current window is inexistent or maximized
QWidget *current_window = m_workspace.activeSubWindow();
bool maximise = ((!current_window)
@@ -2336,6 +2381,7 @@ void QETDiagramEditor::subWindowActivated(QMdiSubWindow *subWindows)
slot_updateActions();
slot_updateWindowsMenu();
emit syncElementsPanel();
}
/**
@@ -2471,7 +2517,27 @@ void QETDiagramEditor::generateTerminalBlock()
#endif
if ( !success ) {
QMessageBox::warning(nullptr,
QObject::tr("Error launching qet_tb_generator plugin"),
message);
QObject::tr("Error launching qet_tb_generator plugin"),
message);
}
}
/**
* @brief QETDiagramEditor::slot_terminalNumbering
* Opens the dialog for automatic terminal numbering and applies the generated undo command.
*/
void QETDiagramEditor::slot_terminalNumbering() {
TerminalNumberingDialog dialog(this);
if (dialog.exec() == QDialog::Accepted) {
QETProject *project = currentProject();
if (!project) return;
// Fetch the generated undo command from the dialog logic
QUndoCommand *macro = dialog.getUndoCommand(project);
// If changes were made, push them to the global undo stack
if (macro) {
undo_group.activeStack()->push(macro);
}
}
}

View File

@@ -42,6 +42,7 @@ class RecentFiles;
class DiagramPropertiesEditorDockWidget;
class ElementsCollectionWidget;
class AutoNumberingDockWidget;
class TerminalNumberingDialog;
#ifdef BUILD_WITHOUT_KF5
#else
@@ -98,6 +99,9 @@ class QETDiagramEditor : public QETMainWindow
ProjectView *findProject(const QString &) const;
QMdiSubWindow *subWindowForWidget(QWidget *) const;
signals:
void syncElementsPanel();
public slots:
void save();
void saveAs();
@@ -129,6 +133,7 @@ class QETDiagramEditor : public QETMainWindow
void projectWasClosed(ProjectView *);
void editProjectProperties(ProjectView *);
void editProjectProperties(QETProject *);
void slot_terminalNumbering();
void editDiagramProperties(DiagramView *);
void editDiagramProperties(Diagram *);
void addDiagramToProject(QETProject *);
@@ -197,6 +202,8 @@ class QETDiagramEditor : public QETMainWindow
*m_terminal_strip_dialog = nullptr, ///<Launch terminal strip dialog
*m_project_terminalBloc, ///< generate terminal block
*m_project_export_conductor_num,///<Export the wire num to csv
*m_project_export_wiring_list, ///< Action to export the wiring list
*m_terminal_numbering, ///< Action to launch terminal numbering
*m_export_project_db, ///Export to file the internal database of the current project
*m_tile_window, ///< Show MDI subwindows as tile
*m_cascade_window, ///< Show MDI subwindows as cascade

View File

@@ -1351,6 +1351,7 @@ void DynamicElementTextItem::updateXref()
{
m_slave_Xref_item = new QGraphicsTextItem(xref_label, this);
m_slave_Xref_item->setFont(QETApp::diagramTextsFont(5));
m_slave_Xref_item->setDefaultTextColor(Qt::black);
m_slave_Xref_item->installSceneEventFilter(this);
m_update_slave_Xref_connection << connect(m_master_element.data(), &Element::xChanged, this, &DynamicElementTextItem::updateXref);

View File

@@ -183,3 +183,27 @@ void MasterElement::aboutDeleteXref()
return;
}
}
/**
* @brief MasterElement::isFull
* @return true if the master has reached its maximum number of slaves
*/
bool MasterElement::isFull() const
{
// Set default value to -1 (unlimited slaves)
int max_slaves = -1;
QVariant max_slaves_variant = kindInformations().value("max_slaves");
// Overwrite default if a valid limit is defined in the element's XML
if (max_slaves_variant.isValid() && !max_slaves_variant.toString().isEmpty()) {
max_slaves = max_slaves_variant.toInt();
}
// If no limit is set (-1), the master is never full
if (max_slaves == -1) {
return false;
}
// Return true if current connected elements reached or exceeded the limit
return connected_elements.size() >= max_slaves;
}

View File

@@ -44,6 +44,8 @@ class MasterElement : public Element
void unlinkElement (Element *elmt) override;
void initLink (QETProject *project) override;
QRectF XrefBoundingRect() const;
bool isFull() const; // Check Slave-Limit
protected:
QVariant itemChange(

View File

@@ -198,6 +198,7 @@ namespace QET {
QIcon hu;
QIcon it;
QIcon jp;
QIcon ko;
QIcon pl;
QIcon pt;
QIcon ro;
@@ -672,6 +673,7 @@ void QET::Icons::initIcons()
hu .addFile(":/ico/24x16/hu.png");
it .addFile(":/ico/24x16/it.png");
jp .addFile(":/ico/24x16/jp.png");
ko .addFile(":/ico/24x16/kr.png");
pl .addFile(":/ico/24x16/pl.png");
pt .addFile(":/ico/24x16/pt.png");
ro .addFile(":/ico/24x16/ro.png");

View File

@@ -206,6 +206,7 @@ namespace QET {
extern QIcon hu;
extern QIcon it;
extern QIcon jp;
extern QIcon ko;
extern QIcon pl;
extern QIcon pt;
extern QIcon ro;

View File

@@ -131,6 +131,7 @@ void AboutQETDialog::setTranslators()
addAuthor(ui->m_translators_label, "Yaroslav", "", tr("Traduction en ukrainien"));
addAuthor(ui->m_translators_label, "JoelAs", "", tr("Traduction en norvégien"));
addAuthor(ui->m_translators_label, "Yuki", "yuki.atoh@gmail.com", tr("Traduction en japonais"));
addAuthor(ui->m_translators_label, "Jung Kwang-Ho", "jkh2rokmc@daum.net", tr("Traduction en coréen"));
addAuthor(ui->m_translators_label, "Nathalie", "nathalie.roussier@giz.de", tr("Traduction en mongol"));
addAuthor(ui->m_translators_label, "Uroš Platiše", "uros.platise@energycon.eu", tr("Traduction en slovène"));
}

View File

@@ -173,6 +173,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();
}
@@ -315,6 +324,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();
}
}
/**
@@ -358,6 +382,7 @@ void GeneralConfigurationPage::fillLang()
ui->m_lang_cb->addItem(QET::Icons::hr, tr("Croate"), "hr");
ui->m_lang_cb->addItem(QET::Icons::it, tr("Italien"), "it");
ui->m_lang_cb->addItem(QET::Icons::jp, tr("Japonais"), "ja");
ui->m_lang_cb->addItem(QET::Icons::ko, tr("Coréen"), "ko");
ui->m_lang_cb->addItem(QET::Icons::pl, tr("Polonais"), "pl");
ui->m_lang_cb->addItem(QET::Icons::pt, tr("Portugais"), "pt");
ui->m_lang_cb->addItem(QET::Icons::ro, tr("Roumains"), "ro");
@@ -505,6 +530,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()
{

View File

@@ -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);

View File

@@ -17,7 +17,7 @@
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
<number>2</number>
</property>
<widget class="QWidget" name="tab_3">
<attribute name="title">
@@ -349,6 +349,27 @@
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Répertoire des Macros utilisateur</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QComboBox" name="m_user_macros_path_cb">
<item>
<property name="text">
<string>Par defaut</string>
</property>
</item>
<item>
<property name="text">
<string>Parcourir...</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>

View File

@@ -160,6 +160,7 @@ void ElementInfoWidget::enableLiveEdit()
{
for (ElementInfoPartWidget *eipw : m_eipw_list)
connect(eipw, &ElementInfoPartWidget::textChanged, this, &ElementInfoWidget::apply);
connect(ui->m_auto_num_locked_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
}
/**
@@ -170,6 +171,7 @@ void ElementInfoWidget::disableLiveEdit()
{
for (ElementInfoPartWidget *eipw : m_eipw_list)
disconnect(eipw, &ElementInfoPartWidget::textChanged, this, &ElementInfoWidget::apply);
disconnect(ui->m_auto_num_locked_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply);
}
/**
@@ -193,6 +195,12 @@ void ElementInfoWidget::buildInterface()
}
ui->scroll_vlayout->addStretch();
// Show checkbox only if the element is a terminal
if (m_element.data()->elementData().m_type == ElementData::Terminal) {
ui->m_auto_num_locked_cb->setVisible(true);
} else {
ui->m_auto_num_locked_cb->setVisible(false);
}
}
/**
@@ -231,6 +239,11 @@ void ElementInfoWidget::updateUi()
for (ElementInfoPartWidget *eipw : m_eipw_list) {
eipw -> setText (element_info[eipw->key()].toString());
}
// Load the lock status for auto numbering
if (m_element->elementData().m_type == ElementData::Terminal) {
QString lock_value = element_info.value(QStringLiteral("auto_num_locked")).toString();
ui->m_auto_num_locked_cb->setChecked(lock_value == QLatin1String("true"));
}
if (m_live_edit) {
enableLiveEdit();
@@ -259,6 +272,10 @@ DiagramContext ElementInfoWidget::currentInfo() const
}
}
// Save the auto numbering lock status
if (m_element->elementData().m_type == ElementData::Terminal) {
info_.addValue(QStringLiteral("auto_num_locked"), ui->m_auto_num_locked_cb->isChecked() ? QStringLiteral("true") : QStringLiteral("false"));
}
return info_;
}

View File

@@ -29,6 +29,19 @@
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="m_auto_num_locked_cb">
<property name="text">
<string>Exclure de la numérotation auto</string>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="styleSheet">
<string notr="true">margin: 5px; font-weight: bold;</string>
</property>
</widget>
</item>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="sizePolicy">

View File

@@ -372,6 +372,10 @@ QWidget *ElementPropertiesWidget::generalWidget()
description_string += QString(tr("Rotation : %1°\n")).arg(m_element.data()->rotation());
description_string += QString(tr("Dimensions : %1*%2\n")).arg(m_element -> size().width()).arg(m_element -> size().height());
description_string += QString(tr("Bornes : %1\n")).arg(m_element -> terminals().count());
if (m_element->linkType() == Element::Master){
description_string += QString(tr("Nombre maximum de contacts esclaves définis : %1\n")).arg(m_element -> elementData().m_max_slaves);
description_string += QString(tr("Nombre de contacts esclaves utilisés : %1\n")).arg(m_element ->linkedElements().count());
}
description_string += QString(tr("Emplacement : %1\n")).arg(m_element.data()->location().toString());
// widget himself

View File

@@ -16,7 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "linksingleelementwidget.h"
#include "../qetgraphicsitem/masterelement.h"
#include "../qetgraphicsitem/conductor.h"
#include "../diagram.h"
#include "../diagramposition.h"
@@ -386,7 +386,22 @@ QVector <QPointer<Element>> LinkSingleElementWidget::availableElements()
//If element is linked, remove is parent from the list
if(!m_element->isFree()) elmt_vector.removeAll(m_element->linkedElements().first());
// Filter out all master elements from the list
for (int i = elmt_vector.size() - 1; i >= 0; --i) {
Element *elmt = elmt_vector.at(i);
// If the item in the list is a master
if (elmt->linkType() == Element::Master) {
// We convert the generic element pointer into a MasterElement pointer
MasterElement *master = static_cast<MasterElement*>(elmt);
// If the master is full, we'll remove it from the list!
if (master->isFull()) {
elmt_vector.removeAt(i);
}
}
}
return elmt_vector;
}

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>389</width>
<height>442</height>
<height>460</height>
</rect>
</property>
<property name="windowTitle">
@@ -64,6 +64,23 @@
</property>
</widget>
</item>
<item row="4" column="0" colspan="3">
<widget class="QLabel" name="m_hidden_masters_label">
<property name="text">
<string>Remarque : les éléments maîtres ayant atteint leur nombre maximal d'esclaves sont masqués.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>

View File

@@ -1,20 +1,20 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
* Copyright 2006-2026 The QElectroTech Team
* This file is part of QElectroTech.
*
* QElectroTech is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* QElectroTech is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "masterpropertieswidget.h"
#include "../diagram.h"
@@ -25,64 +25,65 @@
#include "ui_masterpropertieswidget.h"
#include <QListWidgetItem>
#include <QMessageBox>
/**
@brief MasterPropertiesWidget::MasterPropertiesWidget
Default constructor
@param elmt
@param parent
*/
* @brief MasterPropertiesWidget::MasterPropertiesWidget
* Default constructor
* @param elmt
* @param parent
*/
MasterPropertiesWidget::MasterPropertiesWidget(Element *elmt, QWidget *parent) :
AbstractElementPropertiesEditorWidget(parent),
ui(new Ui::MasterPropertiesWidget),
m_project(nullptr)
AbstractElementPropertiesEditorWidget(parent),
ui(new Ui::MasterPropertiesWidget),
m_project(nullptr)
{
ui->setupUi(this);
ui->m_free_tree_widget->setContextMenuPolicy(Qt::CustomContextMenu);
ui->m_link_tree_widget->setContextMenuPolicy(Qt::CustomContextMenu);
QStringList list;
QSettings settings;
if (settings.value("genericpanel/folio", false).toBool()) {
list << tr("Vignette")
<< tr("Label de folio")
<< tr("Titre de folio")
<< tr("Position");
<< tr("Label de folio")
<< tr("Titre de folio")
<< tr("Position");
}
else {
list << tr("Vignette")
<< tr("N° de folio")
<< tr("Titre de folio")
<< tr("Position");
<< tr("N° de folio")
<< tr("Titre de folio")
<< tr("Position");
}
ui->m_free_tree_widget->setHeaderLabels(list);
ui->m_link_tree_widget->setHeaderLabels(list);
m_context_menu = new QMenu(this);
m_link_action = new QAction(tr("Lier l'élément"), this);
m_unlink_action = new QAction(tr("Délier l'élément"), this);
m_show_qtwi = new QAction(tr("Montrer l'élément"), this);
m_show_element = new QAction(tr("Montrer l'élément maître"), this);
m_save_header_state = new QAction(tr("Enregistrer la disposition"), this);
connect(ui->m_free_tree_widget, &QTreeWidget::itemDoubleClicked,
this, &MasterPropertiesWidget::showElementFromTWI);
this, &MasterPropertiesWidget::showElementFromTWI);
connect(ui->m_link_tree_widget, &QTreeWidget::itemDoubleClicked,
this, &MasterPropertiesWidget::showElementFromTWI);
this, &MasterPropertiesWidget::showElementFromTWI);
connect(ui->m_free_tree_widget, &QTreeWidget::customContextMenuRequested,
[this](QPoint point) {this->customContextMenu(point, 1);});
[this](QPoint point) {this->customContextMenu(point, 1);});
connect(ui->m_link_tree_widget, &QTreeWidget::customContextMenuRequested,
[this](QPoint point) {this->customContextMenu(point, 2);});
[this](QPoint point) {this->customContextMenu(point, 2);});
connect(m_link_action, &QAction::triggered,
this, &MasterPropertiesWidget::on_link_button_clicked);
this, &MasterPropertiesWidget::on_link_button_clicked);
connect(m_unlink_action, &QAction::triggered,
this, &MasterPropertiesWidget::on_unlink_button_clicked);
this, &MasterPropertiesWidget::on_unlink_button_clicked);
connect(m_show_qtwi, &QAction::triggered,
[this]() {this->showElementFromTWI(this->m_qtwi_at_context_menu,0);});
[this]() {this->showElementFromTWI(this->m_qtwi_at_context_menu,0);});
connect(m_show_element, &QAction::triggered, [this]()
{
this->m_element->diagram()->showMe();
@@ -90,46 +91,46 @@ MasterPropertiesWidget::MasterPropertiesWidget(Element *elmt, QWidget *parent) :
if(this->m_showed_element)
m_showed_element->setHighlighted(false);
});
QHeaderView *qhv = ui->m_free_tree_widget->header();
qhv->setContextMenuPolicy(Qt::CustomContextMenu);
connect(qhv, &QHeaderView::customContextMenuRequested,
this, &MasterPropertiesWidget::headerCustomContextMenuRequested);
this, &MasterPropertiesWidget::headerCustomContextMenuRequested);
connect(m_save_header_state, &QAction::triggered, [qhv]()
{
QByteArray qba = qhv->saveState();
QSettings settings;
settings.setValue("link-element-widget/master-state", qba);
});
setElement(elmt);
}
/**
@brief MasterPropertiesWidget::~MasterPropertiesWidget
Destructor
*/
* @brief MasterPropertiesWidget::~MasterPropertiesWidget
* Destructor
*/
MasterPropertiesWidget::~MasterPropertiesWidget()
{
if (m_showed_element)
m_showed_element->setHighlighted(false);
if(m_element)
m_element->setHighlighted(false);
delete ui;
}
/**
@brief MasterPropertiesWidget::setElement
Set the element to be edited
@param element
*/
* @brief MasterPropertiesWidget::setElement
* Set the element to be edited
* @param element
*/
void MasterPropertiesWidget::setElement(Element *element)
{
if (m_element == element)
return;
if (m_showed_element)
{
m_showed_element->setHighlighted(false);
@@ -137,40 +138,40 @@ void MasterPropertiesWidget::setElement(Element *element)
}
if (m_element)
m_element->setHighlighted(false);
if (m_project)
disconnect(m_project, SIGNAL(diagramRemoved(QETProject*,Diagram*)),
this, SLOT(diagramWasdeletedFromProject()));
this, SLOT(diagramWasdeletedFromProject()));
if(Q_LIKELY(element->diagram() && element->diagram()->project()))
{
m_project = element->diagram()->project();
connect(m_project, SIGNAL(diagramRemoved(QETProject*,Diagram*)),
this, SLOT(diagramWasdeletedFromProject()));
}
else
m_project = nullptr;
if(Q_LIKELY(element->diagram() && element->diagram()->project()))
{
m_project = element->diagram()->project();
connect(m_project, SIGNAL(diagramRemoved(QETProject*,Diagram*)),
this, SLOT(diagramWasdeletedFromProject()));
}
else
m_project = nullptr;
//Keep up to date this widget when the linked elements of m_element change
if (m_element)
disconnect(m_element.data(), &Element::linkedElementChanged,
this, &MasterPropertiesWidget::updateUi);
m_element = element;
this, &MasterPropertiesWidget::updateUi);
m_element = element;
connect(m_element.data(), &Element::linkedElementChanged,
this, &MasterPropertiesWidget::updateUi);
this, &MasterPropertiesWidget::updateUi);
updateUi();
}
/**
@brief MasterPropertiesWidget::apply
If link between edited element and other change,
apply the change with a QUndoCommand (got with method associatedUndo)
pushed to the stack of element project.
Return true if link change, else false
@note is void no Return ???
*/
* @brief MasterPropertiesWidget::apply
* If link between edited element and other change,
* apply the change with a QUndoCommand (got with method associatedUndo)
* pushed to the stack of element project.
* Return true if link change, else false
* @note is void no Return ???
*/
void MasterPropertiesWidget::apply()
{
if (QUndoCommand *undo = associatedUndo())
@@ -178,25 +179,25 @@ void MasterPropertiesWidget::apply()
}
/**
@brief MasterPropertiesWidget::reset
Reset current widget, clear eveything and rebuild widget.
*/
* @brief MasterPropertiesWidget::reset
* Reset current widget, clear eveything and rebuild widget.
*/
void MasterPropertiesWidget::reset()
{
foreach (QTreeWidgetItem *qtwi, m_qtwi_hash.keys())
delete qtwi;
m_qtwi_hash.clear();
updateUi();
}
/**
@brief MasterPropertiesWidget::associatedUndo
If link between the edited element and other change,
return a QUndoCommand with this change.
If no change return nullptr.
@return
*/
* @brief MasterPropertiesWidget::associatedUndo
* If link between the edited element and other change,
* return a QUndoCommand with this change.
* If no change return nullptr.
* @return
*/
QUndoCommand* MasterPropertiesWidget::associatedUndo() const
{
QList <Element *> to_link;
@@ -205,7 +206,7 @@ QUndoCommand* MasterPropertiesWidget::associatedUndo() const
for (int i=0; i<ui->m_link_tree_widget->topLevelItemCount(); i++)
to_link << m_qtwi_hash[ui->m_link_tree_widget->topLevelItem(i)];
//The two list contain the same element, there is no change
//The two list contain the same element, there is no change
if (to_link.size() == linked_.size())
{
bool equal = true;
@@ -229,11 +230,11 @@ QUndoCommand* MasterPropertiesWidget::associatedUndo() const
}
/**
@brief MasterPropertiesWidget::setLiveEdit
@param live_edit = true : live edit is enable
else false : live edit is disable.
@return always true because live edit is handled by this editor widget
*/
* @brief MasterPropertiesWidget::setLiveEdit
* @param live_edit = true : live edit is enable
* else false : live edit is disable.
* @return always true because live edit is handled by this editor widget
*/
bool MasterPropertiesWidget::setLiveEdit(bool live_edit)
{
m_live_edit = live_edit;
@@ -241,9 +242,9 @@ bool MasterPropertiesWidget::setLiveEdit(bool live_edit)
}
/**
@brief MasterPropertiesWidget::updateUi
Build the interface of the widget
*/
* @brief MasterPropertiesWidget::updateUi
* Build the interface of the widget
*/
void MasterPropertiesWidget::updateUi()
{
ui->m_free_tree_widget->clear();
@@ -256,75 +257,75 @@ void MasterPropertiesWidget::updateUi()
ElementProvider elmt_prov(m_project);
QSettings settings;
//Build the list of free available element
//Build the list of free available element
QList <QTreeWidgetItem *> items_list;
for(const auto &elmt : elmt_prov.freeElement(ElementData::Slave))
{
QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_free_tree_widget);
qtwi->setIcon(0, elmt->pixmap());
if(settings.value("genericpanel/folio", false).toBool())
{
autonum::sequentialNumbers seq;
QString F =autonum::AssignVariables::formulaToLabel(
elmt->diagram()->border_and_titleblock.folio(),
seq,
elmt->diagram(),
elmt);
elmt->diagram()->border_and_titleblock.folio(),
seq,
elmt->diagram(),
elmt);
qtwi->setText(1, F);
}
else
{
qtwi->setText(1, QString::number(
elmt->diagram()->folioIndex()
+ 1));
elmt->diagram()->folioIndex()
+ 1));
}
qtwi->setText(2, elmt->diagram()->title());
qtwi->setText(4, elmt->diagram()->convertPosition(
elmt->scenePos()).toString());
elmt->scenePos()).toString());
items_list.append(qtwi);
m_qtwi_hash.insert(qtwi, elmt);
}
ui->m_free_tree_widget->addTopLevelItems(items_list);
items_list.clear();
//Build the list of already linked element
//Build the list of already linked element
const QList<Element *> link_list = m_element->linkedElements();
for(Element *elmt : link_list)
{
QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_link_tree_widget);
qtwi->setIcon(0, elmt->pixmap());
if(settings.value("genericpanel/folio", false).toBool())
{
autonum::sequentialNumbers seq;
QString F =autonum::AssignVariables::formulaToLabel(
elmt->diagram()->border_and_titleblock.folio(),
seq,
elmt->diagram(),
elmt);
elmt->diagram()->border_and_titleblock.folio(),
seq,
elmt->diagram(),
elmt);
qtwi->setText(1, F);
}
else
{
qtwi->setText(1, QString::number(
elmt->diagram()->folioIndex()
+ 1));
elmt->diagram()->folioIndex()
+ 1));
}
qtwi->setText(2, elmt->diagram()->title());
qtwi->setText(3, elmt->diagram()->convertPosition(
elmt->scenePos()).toString());
elmt->scenePos()).toString());
items_list.append(qtwi);
m_qtwi_hash.insert(qtwi, elmt);
}
if(items_list.count())
ui->m_link_tree_widget->addTopLevelItems(items_list);
QVariant v = settings.value("link-element-widget/master-state");
if(!v.isNull())
{
@@ -334,9 +335,9 @@ void MasterPropertiesWidget::updateUi()
}
/**
@brief MasterPropertiesWidget::headerCustomContextMenuRequested
@param pos
*/
* @brief MasterPropertiesWidget::headerCustomContextMenuRequested
* @param pos
*/
void MasterPropertiesWidget::headerCustomContextMenuRequested(const QPoint &pos)
{
m_context_menu->clear();
@@ -345,36 +346,57 @@ void MasterPropertiesWidget::headerCustomContextMenuRequested(const QPoint &pos)
}
/**
@brief MasterPropertiesWidget::on_link_button_clicked
move current item in the free_list to linked_list
*/
* @brief MasterPropertiesWidget::on_link_button_clicked
* Moves the current item from the free_list to the linked_list,
* provided the master's slave limit has not been reached.
*/
void MasterPropertiesWidget::on_link_button_clicked()
{
//take the current item from free_list and push it to linked_list
// Get the maximum number of allowed slaves from the element's information
QVariant max_slaves_variant = m_element->kindInformations().value("max_slaves");
if (max_slaves_variant.isValid() && !max_slaves_variant.toString().isEmpty()) {
int max_slaves = max_slaves_variant.toInt();
int current_slaves = ui->m_link_tree_widget->topLevelItemCount();
// If a limit is set and reached
if (max_slaves != -1 && current_slaves >= max_slaves) {
// Show a message box with the actual window as parent to ensure it's on top
QMessageBox::warning(this->window(),
tr("Nombre maximal d'esclaves atteint."),
tr("Cet élément maître ne peut plus accepter aucun nouveau contact esclave, la limite fixée a été atteinte (Limite: %1).").arg(max_slaves));
return;
}
}
// Move current item from free_list to linked_list
QTreeWidgetItem *qtwi = ui->m_free_tree_widget->currentItem();
if (qtwi)
{
ui->m_free_tree_widget->takeTopLevelItem(
ui->m_free_tree_widget->indexOfTopLevelItem(qtwi));
ui->m_free_tree_widget->indexOfTopLevelItem(qtwi));
ui->m_link_tree_widget->insertTopLevelItem(0, qtwi);
if(m_live_edit)
apply();
}
}
/**
@brief MasterPropertiesWidget::on_unlink_button_clicked
move current item in linked_list to free_list
*/
* @brief MasterPropertiesWidget::on_unlink_button_clicked
* move current item in linked_list to free_list
*/
void MasterPropertiesWidget::on_unlink_button_clicked()
{
//take the current item from linked_list and push it to free_list
//take the current item from linked_list and push it to free_list
QTreeWidgetItem *qtwi = ui->m_link_tree_widget->currentItem();
if(qtwi)
{
ui->m_link_tree_widget->takeTopLevelItem(
ui->m_link_tree_widget->indexOfTopLevelItem(qtwi));
ui->m_link_tree_widget->indexOfTopLevelItem(qtwi));
ui->m_free_tree_widget->insertTopLevelItem(0, qtwi);
if(m_live_edit)
@@ -383,18 +405,18 @@ void MasterPropertiesWidget::on_unlink_button_clicked()
}
/**
@brief MasterPropertiesWidget::showElementFromTWI
Show the element corresponding to the given QTreeWidgetItem
@param qtwi
@param column
*/
* @brief MasterPropertiesWidget::showElementFromTWI
* Show the element corresponding to the given QTreeWidgetItem
* @param qtwi
* @param column
*/
void MasterPropertiesWidget::showElementFromTWI(QTreeWidgetItem *qtwi, int column)
{
Q_UNUSED(column);
if (m_showed_element)
{
disconnect(m_showed_element, SIGNAL(destroyed()),
this, SLOT(showedElementWasDeleted()));
this, SLOT(showedElementWasDeleted()));
m_showed_element -> setHighlighted(false);
}
if (m_element)
@@ -404,23 +426,23 @@ void MasterPropertiesWidget::showElementFromTWI(QTreeWidgetItem *qtwi, int colum
m_showed_element->diagram()->showMe();
m_showed_element->setHighlighted(true);
connect(m_showed_element, SIGNAL(destroyed()),
this, SLOT(showedElementWasDeleted()));
this, SLOT(showedElementWasDeleted()));
}
/**
@brief MasterPropertiesWidget::showedElementWasDeleted
Set to nullptr the current showed element when he was deleted
*/
* @brief MasterPropertiesWidget::showedElementWasDeleted
* Set to nullptr the current showed element when he was deleted
*/
void MasterPropertiesWidget::showedElementWasDeleted()
{
m_showed_element = nullptr;
}
/**
@brief MasterPropertiesWidget::diagramWasdeletedFromProject
This slot is called when a diagram is removed from the parent project
of edited element to update the content of this widget
*/
* @brief MasterPropertiesWidget::diagramWasdeletedFromProject
* This slot is called when a diagram is removed from the parent project
* of edited element to update the content of this widget
*/
void MasterPropertiesWidget::diagramWasdeletedFromProject()
{
// We use a timer because if the removed diagram
@@ -431,11 +453,11 @@ void MasterPropertiesWidget::diagramWasdeletedFromProject()
}
/**
@brief MasterPropertiesWidget::customContextMenu
Display a context menu
@param pos
@param i : the tree widget where the context menu was requested.
*/
* @brief MasterPropertiesWidget::customContextMenu
* Display a context menu
* @param pos
* @param i : the tree widget where the context menu was requested.
*/
void MasterPropertiesWidget::customContextMenu(const QPoint &pos, int i)
{
// add the size of the header to display the topleft of the QMenu
@@ -444,14 +466,14 @@ void MasterPropertiesWidget::customContextMenu(const QPoint &pos, int i)
// section related to QAbstractScrollArea
QPoint point = pos;
point.ry()+=ui->m_free_tree_widget->header()->height();
m_context_menu->clear();
if (i == 1)
{
point = ui->m_free_tree_widget->mapToGlobal(point);
//Context at for free tree widget
//Context at for free tree widget
if (ui->m_free_tree_widget->currentItem())
{
m_qtwi_at_context_menu = ui->m_free_tree_widget->currentItem();
@@ -462,8 +484,8 @@ void MasterPropertiesWidget::customContextMenu(const QPoint &pos, int i)
else
{
point = ui->m_link_tree_widget->mapToGlobal(point);
//context at for link tre widget
//context at for link tre widget
if (ui->m_link_tree_widget->currentItem())
{
m_qtwi_at_context_menu = ui->m_link_tree_widget->currentItem();
@@ -471,7 +493,7 @@ void MasterPropertiesWidget::customContextMenu(const QPoint &pos, int i)
m_context_menu->addAction(m_show_qtwi);
}
}
m_context_menu->addAction(m_show_element);
m_context_menu->popup(point);
}

View File

@@ -0,0 +1,178 @@
#include "terminalnumberingdialog.h"
#include "ui_terminalnumberingdialog.h"
#include "../qetproject.h"
#include "../diagram.h"
#include "../qetgraphicsitem/element.h"
#include "../undocommand/changeelementinformationcommand.h"
#include <QUndoCommand>
#include <algorithm>
/**
* @brief TerminalNumberingDialog::TerminalNumberingDialog
* Constructor
* @param parent
*/
TerminalNumberingDialog::TerminalNumberingDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::TerminalNumberingDialog)
{
ui->setupUi(this);
}
/**
* @brief TerminalNumberingDialog::~TerminalNumberingDialog
* Destructor
*/
TerminalNumberingDialog::~TerminalNumberingDialog()
{
delete ui;
}
/**
* @brief TerminalNumberingDialog::isXAxisPriority
* @return true if X axis has priority, false if Y axis has priority
*/
bool TerminalNumberingDialog::isXAxisPriority() const
{
return ui->rb_priority_x->isChecked();
}
/**
* @brief TerminalNumberingDialog::isAlphanumeric
* @return true if alphanumeric sorting is enabled, false if numeric only
*/
bool TerminalNumberingDialog::isAlphanumeric() const
{
return ui->rb_type_alpha->isChecked();
}
/**
* @brief TerminalNumberingDialog::getUndoCommand
* Scans the given project for terminals, sorts them according to user preferences
* (X/Y axis, alphanumeric rules), and generates an undo command containing all label changes.
* * @param project Pointer to the current QETProject
* @return QUndoCommand* containing the modifications, or nullptr if no changes are needed.
*/
QUndoCommand* TerminalNumberingDialog::getUndoCommand(QETProject *project) const {
if (!project) return nullptr;
bool axisX = isXAxisPriority();
bool alpha = isAlphanumeric();
// 1. Helper structure to store and sort terminal data
struct TermInfo {
Element *elmt;
QString prefix;
QString suffix;
int folioIndex;
qreal x;
qreal y;
};
QList<TermInfo> termList;
// 2. Collect all terminals from all folios in the project
foreach (Diagram *diagram, project->diagrams()) {
int fIndex = diagram->folioIndex();
foreach (QGraphicsItem *qgi, diagram->items()) {
if (Element *elmt = qgraphicsitem_cast<Element *>(qgi)) {
// Check if the element is actually a terminal
if (elmt->elementData().m_type == ElementData::Terminal) {
DiagramContext info = elmt->elementInformations();
// Ignore locked terminals (if the user checked a 'lock' property)
if (info.value(QStringLiteral("auto_num_locked")).toString() == QLatin1String("true")) {
continue;
}
QString label = elmt->actualLabel();
if (label.isEmpty()) continue;
// Split prefix (e.g., "-X1") and suffix (e.g., "1" or "A")
QString prefix = label;
QString suffix = "";
int colonIndex = label.lastIndexOf(':');
if (colonIndex != -1) {
prefix = label.left(colonIndex);
suffix = label.mid(colonIndex + 1);
}
// If user chose purely numeric, skip terminals with alphabetical suffixes
if (!alpha && !suffix.isEmpty()) {
bool isNum;
suffix.toInt(&isNum);
if (!isNum) continue;
}
TermInfo ti;
ti.elmt = elmt;
ti.prefix = prefix;
ti.suffix = suffix;
ti.folioIndex = fIndex;
ti.x = elmt->pos().x();
ti.y = elmt->pos().y();
termList.append(ti);
}
}
}
}
// 3. Sort terminals based on user selection (X or Y axis priority)
std::sort(termList.begin(), termList.end(), [axisX](const TermInfo &a, const TermInfo &b) {
// First sort by BMK Prefix alphabetically (case insensitive)
int prefixCmp = a.prefix.compare(b.prefix, Qt::CaseInsensitive);
if (prefixCmp != 0) return prefixCmp < 0;
// Then sort by folio (page) index
if (a.folioIndex != b.folioIndex) return a.folioIndex < b.folioIndex;
// Finally sort by coordinates (with a 1.0px tolerance to handle slight misalignments)
if (axisX) {
if (qAbs(a.x - b.x) > 1.0) return a.x < b.x;
return a.y < b.y;
} else {
if (qAbs(a.y - b.y) > 1.0) return a.y < b.y;
return a.x < b.x;
}
});
// 4. Generate new numbering and create the undo command macro
QUndoCommand *macro = new QUndoCommand(QObject::tr("Automatic terminal numbering"));
QMap<QString, int> counters;
foreach (const TermInfo &ti, termList) {
// Increment the counter for this terminal block (e.g., "-X3")
counters[ti.prefix]++;
int newNum = counters[ti.prefix];
// Determine if the original suffix was a pure number
QString newLabel;
bool isNum;
ti.suffix.toInt(&isNum);
if (isNum || ti.suffix.isEmpty()) {
// If it was a number (e.g., "1") or empty, update it with the new counter
newLabel = ti.prefix + ":" + QString::number(newNum);
} else {
// If it was alphabetical (e.g., "N", "PE"), keep the original text but consume the count!
newLabel = ti.prefix + ":" + ti.suffix;
}
DiagramContext oldInfo = ti.elmt->elementInformations();
DiagramContext newInfo = oldInfo;
newInfo.addValue(QStringLiteral("label"), newLabel);
// Create an undo command only if the label actually changes
if (oldInfo != newInfo) {
new ChangeElementInformationCommand(ti.elmt, oldInfo, newInfo, macro);
}
}
// 5. Return the macro if it contains changes, otherwise delete and return null
if (macro->childCount() > 0) {
return macro;
} else {
delete macro;
return nullptr;
}
}

View File

@@ -0,0 +1,35 @@
#ifndef TERMINALNUMBERINGDIALOG_H
#define TERMINALNUMBERINGDIALOG_H
#include <QDialog>
class QETProject;
class QUndoCommand;
namespace Ui {
class TerminalNumberingDialog;
}
/**
* @brief The TerminalNumberingDialog class
* Dialog to configure the automatic numbering of terminals.
*/
class TerminalNumberingDialog : public QDialog
{
Q_OBJECT
public:
explicit TerminalNumberingDialog(QWidget *parent = nullptr);
~TerminalNumberingDialog();
// Getters for the user's choices
bool isXAxisPriority() const;
bool isAlphanumeric() const;
QUndoCommand* getUndoCommand(QETProject *project) const;
private:
Ui::TerminalNumberingDialog *ui;
};
#endif // TERMINALNUMBERINGDIALOG_H

View File

@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TerminalNumberingDialog</class>
<widget class="QDialog" name="TerminalNumberingDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Numérotation automatique des bornes</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_description">
<property name="text">
<string>Cette fonction numérote les bornes du projet selon leur position. Les bornes vides ou verrouillées sont ignorées.Le marquage des bornes doit être configuré au préalable comme suit : '-X:AB'. La partie avant les deux-points (le bornier) peut être nommée au choix. 'AB' peut être composé de chiffres ou de lettres."</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_axis">
<property name="title">
<string>Priorité des axes</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="rb_priority_x">
<property name="text">
<string>Priorité à l'axe X (horizontal)</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rb_priority_y">
<property name="text">
<string>Priorité à l'axe Y (vertical)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_type">
<property name="title">
<string>Type de numérotation</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QRadioButton" name="rb_type_num">
<property name="text">
<string>Numérique uniquement (1, 2, 3...)</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rb_type_alpha">
<property name="text">
<string>Alphanumérique (A, B, C... 1, 2...)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>TerminalNumberingDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>TerminalNumberingDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -0,0 +1,290 @@
#include "wiringlistexport.h"
#include "qetproject.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QTextStream>
#include <QDomDocument>
#include <QFile>
#include <QRegularExpression>
#include <QQueue>
#include <QSet>
WiringListExport::WiringListExport(QETProject *project, QWidget *parent) :
QObject(parent),
m_project(project),
m_parent(parent)
{
}
QString WiringListExport::normalizeUuid(const QString &u) const
{
QString res = u;
res.remove('{').remove('}');
return res.trimmed().toLower();
}
QString WiringListExport::findDiagramFolio(const QDomElement &diagramElem) const
{
if (diagramElem.isNull()) return "";
if (diagramElem.hasAttribute("folio")) return diagramElem.attribute("folio");
if (diagramElem.hasAttribute("title")) return diagramElem.attribute("title");
return "";
}
QDomElement WiringListExport::climbToDiagram(QDomNode node) const
{
while (!node.isNull()) {
if (node.isElement() && node.toElement().tagName().toLower() == "diagram") {
return node.toElement();
}
node = node.parentNode();
}
return QDomElement();
}
QMap<QString, ElementInfo> WiringListExport::collectElementsInfo(const QDomElement &root) const
{
QMap<QString, ElementInfo> infoMap;
QDomNodeList elements = root.elementsByTagName("element");
for (int i = 0; i < elements.size(); ++i) {
QDomElement el = elements.at(i).toElement();
QString uuid = normalizeUuid(el.attribute("uuid", el.attribute("id", "")));
if (uuid.isEmpty()) continue;
ElementInfo info;
info.folio = findDiagramFolio(climbToDiagram(el));
QDomElement linksNode = el.firstChildElement("links_uuids");
if (!linksNode.isNull()) {
QDomNodeList linkUuids = linksNode.elementsByTagName("link_uuid");
for (int j = 0; j < linkUuids.size(); ++j) {
QString luuid = normalizeUuid(linkUuids.at(j).toElement().attribute("uuid"));
if (!luuid.isEmpty()) info.links.append(luuid);
}
}
QDomElement elInfoNode = el.firstChildElement("elementInformations");
if (!elInfoNode.isNull()) {
QDomNodeList eics = elInfoNode.elementsByTagName("elementInformation");
for (int j = 0; j < eics.size(); ++j) {
QDomElement eic = eics.at(j).toElement();
QString nameAttr = eic.attribute("name").toLower();
if (nameAttr == "label") info.label = eic.text().trimmed();
if (nameAttr == "name") info.name = eic.text().trimmed();
}
}
QString typeVal = el.attribute("type").toLower();
if (typeVal.contains("naechste") || typeVal.contains("vorherige") ||
typeVal.contains("next") || typeVal.contains("previous")) {
info.isPlaceholder = true;
}
infoMap.insert(uuid, info);
}
return infoMap;
}
QList<ConductorData> WiringListExport::collectConductors(const QDomElement &root) const
{
QList<ConductorData> conductors;
QDomNodeList conductorNodes = root.elementsByTagName("conductor");
for (int i = 0; i < conductorNodes.size(); ++i) {
QDomElement cond = conductorNodes.at(i).toElement();
if (cond.attribute("num") == "Brücke") continue;
ConductorData data;
data.index = i;
data.el1_uuid = normalizeUuid(cond.attribute("element1", cond.attribute("element1id", "")));
data.el2_uuid = normalizeUuid(cond.attribute("element2", cond.attribute("element2id", "")));
data.element1_label = cond.attribute("element1_label");
data.element2_label = cond.attribute("element2_label");
data.terminalname1 = cond.attribute("terminalname1");
data.terminalname2 = cond.attribute("terminalname2");
data.tension_protocol = cond.attribute("tension_protocol");
data.conductor_color = cond.attribute("conductor_color");
data.conductor_section = cond.attribute("conductor_section");
data.function = cond.attribute("function");
QDomElement diag = climbToDiagram(cond);
data.folio = findDiagramFolio(diag);
if (data.folio.isEmpty()) data.folio = cond.attribute("folio", cond.attribute("page", ""));
conductors.append(data);
}
return conductors;
}
void WiringListExport::resolveEndpoints(QList<ConductorData> &conductors, const QMap<QString, ElementInfo> &elementsInfo) const
{
QRegularExpression numericLabelRe("^\\d+(\\.\\d+)?$");
QMap<QString, QList<ConductorData>> el_to_cons;
for (const ConductorData &c : conductors) {
if (!c.el1_uuid.isEmpty()) el_to_cons[c.el1_uuid].append(c);
if (!c.el2_uuid.isEmpty()) el_to_cons[c.el2_uuid].append(c);
}
for (int i = 0; i < conductors.size(); ++i) {
ConductorData &c = conductors[i];
auto resolveSide = [&](const QString &startUuid, QString &outLabel, QString &outTerminal) {
if (startUuid.isEmpty() || !elementsInfo.contains(startUuid)) return;
const ElementInfo &startInfo = elementsInfo[startUuid];
if (!startInfo.links.isEmpty() || startInfo.isPlaceholder) {
QQueue<QString> q;
QSet<QString> visited;
q.enqueue(startUuid);
visited.insert(startUuid);
int depth = 0;
while (!q.isEmpty() && depth < 3) {
int levelSize = q.size();
for (int k = 0; k < levelSize; ++k) {
QString curr = q.dequeue();
if (elementsInfo.contains(curr)) {
const ElementInfo &currInfo = elementsInfo[curr];
if (!currInfo.isPlaceholder && !currInfo.label.isEmpty() && !numericLabelRe.match(currInfo.label).hasMatch()) {
outLabel = currInfo.label;
return;
}
for (const QString &lnk : currInfo.links) {
if (!visited.contains(lnk)) {
visited.insert(lnk);
q.enqueue(lnk);
}
}
}
for (const ConductorData &cond : el_to_cons.value(curr)) {
if (cond.index == c.index) continue;
QString other;
QString terminalHint;
if (cond.el1_uuid == curr) {
other = cond.el2_uuid;
terminalHint = cond.terminalname2;
} else {
other = cond.el1_uuid;
terminalHint = cond.terminalname1;
}
if (!other.isEmpty() && !visited.contains(other)) {
if (elementsInfo.contains(other)) {
const ElementInfo &oInfo = elementsInfo[other];
if (!oInfo.isPlaceholder && !oInfo.label.isEmpty() && !numericLabelRe.match(oInfo.label).hasMatch()) {
outLabel = oInfo.label;
if (outTerminal.isEmpty()) outTerminal = terminalHint;
return;
}
}
visited.insert(other);
q.enqueue(other);
}
}
}
depth++;
}
} else {
if (outLabel.isEmpty()) {
outLabel = startInfo.label.isEmpty() ? startInfo.name : startInfo.label;
}
}
};
bool p1 = elementsInfo.value(c.el1_uuid).isPlaceholder;
bool p2 = elementsInfo.value(c.el2_uuid).isPlaceholder;
if (c.element1_label.isEmpty() || p1) {
if (p1) c.element1_label = "";
resolveSide(c.el1_uuid, c.element1_label, c.terminalname1);
}
if (c.element2_label.isEmpty() || p2) {
if (p2) c.element2_label = "";
resolveSide(c.el2_uuid, c.element2_label, c.terminalname2);
}
}
}
void WiringListExport::toCsv()
{
if (!m_project) return;
QDomDocument doc = m_project->toXml();
if (doc.isNull()) {
QMessageBox::warning(m_parent, tr("Erreur"), tr("Impossible de lire la structure en mémoire du projet."));
return;
}
QFileDialog dialog(m_parent);
dialog.setAcceptMode(QFileDialog::AcceptSave);
dialog.setWindowTitle(tr("Exporter le plan de câblage"));
dialog.setDefaultSuffix("csv");
dialog.setNameFilter(tr("Fichiers CSV (*.csv)"));
if (dialog.exec() != QDialog::Accepted) return;
QString fileName = dialog.selectedFiles().first();
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::warning(m_parent, tr("Erreur"), tr("Impossible d'ouvrir le fichier pour l'écriture."));
return;
}
QMap<QString, ElementInfo> elementsInfo = collectElementsInfo(doc.documentElement());
QList<ConductorData> conductors = collectConductors(doc.documentElement());
resolveEndpoints(conductors, elementsInfo);
QList<ConductorData> uniqueConductors;
QSet<QString> seenConnections;
for (const ConductorData &c : conductors) {
if (c.element1_label.isEmpty() && c.element2_label.isEmpty()) continue;
QString sideA = c.element1_label + ":" + c.terminalname1;
QString sideB = c.element2_label + ":" + c.terminalname2;
QString key = (sideA < sideB) ? (sideA + "||" + sideB) : (sideB + "||" + sideA);
if (!seenConnections.contains(key)) {
seenConnections.insert(key);
uniqueConductors.append(c);
}
}
QTextStream out(&file);
out << tr("Page", "Wiring list CSV header") << ";"
<< tr("Composant 1", "Wiring list CSV header") << ";"
<< tr("Borne 1", "Wiring list CSV header") << ";"
<< tr("Composant 2", "Wiring list CSV header") << ";"
<< tr("Borne 2", "Wiring list CSV header") << ";"
<< tr("Tension / Protocole", "Wiring list CSV header") << ";"
<< tr("Couleur du fil", "Wiring list CSV header") << ";"
<< tr("Section du fil", "Wiring list CSV header") << ";"
<< tr("Fonction", "Wiring list CSV header") << "\n";
for (const ConductorData &c : uniqueConductors) {
out << c.folio << ";"
<< c.element1_label << ";"
<< c.terminalname1 << ";"
<< c.element2_label << ";"
<< c.terminalname2 << ";"
<< c.tension_protocol << ";"
<< c.conductor_color << ";"
<< c.conductor_section << ";"
<< c.function << "\n";
}
file.close();
QMessageBox::information(m_parent, tr("Export réussi"), tr("Le plan de câblage a été exporté avec succès !"));
}

View File

@@ -0,0 +1,71 @@
#ifndef WIRINGLISTEXPORT_H
#define WIRINGLISTEXPORT_H
#include <QObject>
#include <QString>
#include <QMap>
#include <QList>
#include <QStringList>
class QETProject;
class QWidget;
class QDomElement;
class QDomNode;
// Internal data structures for parsing the XML graph
struct ElementInfo {
QString folio;
QStringList links;
QString label;
QString name;
bool isPlaceholder = false;
};
struct ConductorData {
int index = 0;
QString el1_uuid;
QString el2_uuid;
QString element1_label;
QString element2_label;
QString terminalname1;
QString terminalname2;
QString tension_protocol;
QString conductor_color;
QString conductor_section;
QString function;
QString folio;
// Resolved endpoints
QString chosen_a_uuid;
QString chosen_a_label;
QString chosen_b_uuid;
QString chosen_b_label;
};
/**
* @brief The WiringListExport class
* Handles the export of the wiring list (Verdrahtungsplan) to a CSV file.
* Automatically resolves links and placeholders to find physical endpoints.
*/
class WiringListExport : public QObject
{
Q_OBJECT
public:
explicit WiringListExport(QETProject *project, QWidget *parent = nullptr);
void toCsv();
private:
QETProject *m_project;
QWidget *m_parent;
QString normalizeUuid(const QString &u) const;
QString findDiagramFolio(const QDomElement &diagramElem) const;
QDomElement climbToDiagram(QDomNode node) const;
QMap<QString, ElementInfo> collectElementsInfo(const QDomElement &root) const;
QList<ConductorData> collectConductors(const QDomElement &root) const;
void resolveEndpoints(QList<ConductorData> &conductors, const QMap<QString, ElementInfo> &elementsInfo) const;
};
#endif // WIRINGLISTEXPORT_H