/* Copyright 2006-2025 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 . */ #include "linksingleelementwidget.h" #include "../qetgraphicsitem/conductor.h" #include "../diagram.h" #include "../diagramposition.h" #include "../qetgraphicsitem/element.h" #include "../elementprovider.h" #include "../undocommand/linkelementcommand.h" #include "../qetinformation.h" #include "../ui_linksingleelementwidget.h" #include /** @brief LinkSingleElementWidget::LinkSingleElementWidget Default constructor @param elmt the edited element @param parent the parent widget */ LinkSingleElementWidget::LinkSingleElementWidget(Element *elmt, QWidget *parent) : AbstractElementPropertiesEditorWidget(parent), ui(new Ui::LinkSingleElementWidget) { ui->setupUi(this); ui->m_tree_widget->setContextMenuPolicy(Qt::CustomContextMenu); m_context_menu = new QMenu(this); m_link_action = new QAction(tr("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 esclave"), this); m_save_header_state = new QAction(tr("Enregistrer la disposition"), this); connect(m_show_qtwi, &QAction::triggered, this, [=]() { this->on_m_tree_widget_itemDoubleClicked(this->m_qtwi_at_context_menu, 0); }); connect(m_link_action, &QAction::triggered, this, &LinkSingleElementWidget::linkTriggered); connect(m_show_element, &QAction::triggered, this, [=]() { this->m_element->diagram()->showMe(); this->m_element->setHighlighted(true); if(this->m_showed_element) m_showed_element->setHighlighted(false); }); QHeaderView *qhv = ui->m_tree_widget->header(); qhv->setContextMenuPolicy(Qt::CustomContextMenu); connect(qhv, &QHeaderView::customContextMenuRequested, this, &LinkSingleElementWidget::headerCustomContextMenuRequested); connect(m_save_header_state, &QAction::triggered, [this, qhv]() { QByteArray qba = qhv->saveState(); QSettings settings; const auto elmt_type{this->m_element->elementData().m_type}; if (elmt_type & ElementData::AllReport) settings.setValue(QStringLiteral("link-element-widget/report-state"), qba); else if (elmt_type == ElementData::Slave) settings.setValue(QStringLiteral("link-element-widget/slave-state"), qba); }); setElement(elmt); } /** @brief LinkSingleElementWidget::~LinkSingleElementWidget Default destructor */ LinkSingleElementWidget::~LinkSingleElementWidget() { if(m_showed_element) m_showed_element->setHighlighted(false); if(m_element) { m_element->setHighlighted(false); if (!m_element->isFree()) m_element->linkedElements().first()->setHighlighted(false); } delete ui; } /** @brief LinkSingleElementWidget::setElement Set element to be the edited element. @param element */ void LinkSingleElementWidget::setElement(Element *element) { if (m_element == element) return; //Remove connection of previous edited element if (m_element) { disconnect(m_element->diagram()->project(), &QETProject::diagramRemoved, this, &LinkSingleElementWidget::diagramWasRemovedFromProject); disconnect(m_element.data(), &Element::linkedElementChanged, this, &LinkSingleElementWidget::updateUi); m_element->setHighlighted(false); } if(m_showed_element) m_showed_element->setHighlighted(false); m_unlink = false; m_showed_element = nullptr; m_element_to_link = nullptr; m_pending_qtwi = nullptr; //Setup the new element, connection and ui m_element = element; const auto elmt_type{m_element->elementData().m_type}; if (elmt_type == ElementData::Slave) m_filter = ElementData::Master; else if (elmt_type & ElementData::AllReport) m_filter = elmt_type == ElementData::NextReport ? ElementData::PreviousReport : ElementData::NextReport; else m_filter = ElementData::Simple; connect(m_element->diagram()->project(), &QETProject::diagramRemoved, this, &LinkSingleElementWidget::diagramWasRemovedFromProject); connect(m_element.data(), &Element::linkedElementChanged, this, &LinkSingleElementWidget::updateUi, Qt::QueuedConnection); updateUi(); } /** @brief LinkSingleElementWidget::apply Apply the new property of the edited element by pushing the associated undo command to parent project undo stack */ void LinkSingleElementWidget::apply() { QUndoCommand *undo = associatedUndo(); if (undo) m_element->diagram()->undoStack().push(undo); m_unlink = false; m_element_to_link = nullptr; m_pending_qtwi = nullptr; } /** @brief LinkSingleElementWidget::associatedUndo @return the undo command associated to the current edition if there isn't change, return nulptr */ QUndoCommand *LinkSingleElementWidget::associatedUndo() const { LinkElementCommand *undo = new LinkElementCommand(m_element); if (m_element_to_link || m_unlink) { if (m_element_to_link) undo->setLink(m_element_to_link); else if (m_unlink) undo->unlinkAll(); return undo; } return nullptr; } /** @brief LinkSingleElementWidget::title @return the title used for this editor */ QString LinkSingleElementWidget::title() const { if (m_element->elementData().m_type & ElementData::AllReport) return tr("Report de folio"); else return tr("Référence croisée (esclave)"); } /** @brief LinkSingleElementWidget::updateUi Update the content of this widget */ void LinkSingleElementWidget::updateUi() { m_unlink = false; //Update the behavior of link/unlink button if (m_element->isFree()) hideButtons(); else showButtons(); buildTree(); } /** @brief LinkSingleElementWidget::buildTree Build the content of the QTreeWidget */ void LinkSingleElementWidget::buildTree() { clearTreeWidget(); setUpHeaderLabels(); QSettings settings; const auto elmt_vector{availableElements()}; if (m_element->elementData().m_type == ElementData::Slave) { for(const auto &elmt : elmt_vector) { QStringList search_list; QStringList str_list; str_list << elmt->actualLabel(); if(!str_list.last().isEmpty()) { search_list << str_list.last(); } str_list << elmt->elementInformations()[QETInformation::ELMT_COMMENT].toString(); if (!str_list.last().isEmpty()) search_list << str_list.last(); if (const auto diag = elmt->diagram()) { if (settings.value(QStringLiteral("genericpanel/folio"), false).toBool()) { autonum::sequentialNumbers seq; QString F =autonum::AssignVariables::formulaToLabel( diag->border_and_titleblock.folio(), seq, diag, elmt); str_list << F; } else { str_list << QString::number(diag->folioIndex() + 1); } str_list << diag->convertPosition(elmt->scenePos()).toString(); str_list << diag->title(); } else { qDebug() << "In method void LinkSingleElementWidget::updateUi(), provided element must be in a diagram"; } QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_tree_widget, str_list); m_qtwi_elmt_hash.insert(qtwi, elmt); m_qtwi_strl_hash.insert(qtwi, search_list); } QVariant v = settings.value(QStringLiteral("link-element-widget/slave-state")); if(!v.isNull()) ui->m_tree_widget->header()->restoreState(v.toByteArray()); } else if (m_element->elementData().m_type & ElementData::AllReport) { for(const auto &elmt : elmt_vector) { QStringList search_list; QStringList str_list; if (!elmt->conductors().isEmpty()) { ConductorProperties cp = elmt->conductors().first()->properties(); str_list << cp.text; if (!str_list.last().isEmpty()) search_list << str_list.last(); str_list << cp.m_function; if (!str_list.last().isEmpty()) search_list << str_list.last(); str_list << cp.m_tension_protocol; if (!str_list.last().isEmpty()) search_list << str_list.last(); str_list << cp.m_wire_color; if (!str_list.last().isEmpty()) search_list << str_list.last(); str_list << cp.m_wire_section; if (!str_list.last().isEmpty()) search_list << str_list.last(); } else str_list << "" << "" << "" << "" << ""; if (Diagram *diag = elmt->diagram()) { if (settings.value(QStringLiteral("genericpanel/folio"), false).toBool()) { autonum::sequentialNumbers seq; QString F =autonum::AssignVariables::formulaToLabel(diag->border_and_titleblock.folio(), seq, diag, elmt); str_list << F; } else { str_list << QString::number(diag->folioIndex() + 1); } str_list << diag->convertPosition(elmt->scenePos()).toString(); str_list << diag->title(); } else { qDebug() << "In method void LinkSingleElementWidget::updateUi(), provided element must be in a diagram"; } QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_tree_widget, str_list); m_qtwi_elmt_hash.insert(qtwi, elmt); m_qtwi_strl_hash.insert(qtwi, search_list); } QSettings settings; QVariant v = settings.value(QStringLiteral("link-element-widget/report-state")); if(!v.isNull()) ui->m_tree_widget->header()->restoreState(v.toByteArray()); } setUpCompleter(); } /** @brief LinkSingleElementWidget::setLiveEdit @param live_edit @return */ bool LinkSingleElementWidget::setLiveEdit(bool live_edit) { m_live_edit = live_edit; return true; } /** @brief LinkSingleElementWidget::availableElements @return A QList with all available elements to be linked with the edited element. This method takes care of the "find in diagram" combo box */ QVector > LinkSingleElementWidget::availableElements() { QVector > elmt_vector; //if element isn't free and unlink isn't pressed, return an empty list if (!m_element->isFree() && !m_unlink) return elmt_vector; if (!m_element->diagram() || !m_element->diagram()->project()) return elmt_vector; ElementProvider ep(m_element->diagram()->project()); if (m_filter & ElementData::AllReport) elmt_vector = ep.freeElement(m_filter); else elmt_vector = ep.find(m_filter); //If element is linked, remove is parent from the list if(!m_element->isFree()) elmt_vector.removeAll(m_element->linkedElements().first()); return elmt_vector; } /** @brief LinkSingleElementWidget::setUpCompleter Setup the completer of search_field */ void LinkSingleElementWidget::setUpCompleter() { ui->m_search_field->clear(); if(ui->m_search_field->completer()) delete ui->m_search_field->completer(); QStringList search; for(const auto &str_list : m_qtwi_strl_hash.values()) search.append(str_list); QCompleter *c = new QCompleter(search, ui->m_search_field); c->setCaseSensitivity(Qt::CaseInsensitive); ui->m_search_field->setCompleter(c); } /** @brief LinkSingleElementWidget::clearTreeWidget Clear the tree widget. Delete all QTreeWidget (in the tree widget and in the hash). Clear the hash. */ void LinkSingleElementWidget::clearTreeWidget() { while(ui->m_tree_widget->topLevelItemCount()) { QTreeWidgetItem *qtwi = ui->m_tree_widget->takeTopLevelItem(0); if (!m_qtwi_elmt_hash.contains(qtwi)) delete qtwi; } foreach(QTreeWidgetItem *qtwi, m_qtwi_elmt_hash.keys()) delete qtwi; m_qtwi_elmt_hash.clear(); m_qtwi_strl_hash.clear(); } void LinkSingleElementWidget::setUpHeaderLabels() { QStringList list; const QSettings settings; const auto elmt_type{m_element->elementData().m_type}; if (elmt_type == ElementData::Slave) { if (settings.value(QStringLiteral("genericpanel/folio"), false).toBool()) { list << tr("Label") << tr("Commentaire") << tr("Label de folio") << tr("Position") << tr("Titre de folio"); } else { list << tr("Label") << tr("Commentaire") << tr("N° de folio") << tr("Position") << tr("Titre de folio"); } } if (elmt_type & ElementData::AllReport) { if (settings.value(QStringLiteral("genericpanel/folio"), false).toBool()) { list << tr("N° de fil") << tr("Fonction") << tr("Tension / Protocole") << tr("Couleur du conducteur") << tr("Section du conducteur") << tr("Label de folio") << tr("Position") << tr("Titre de folio"); } else { list << tr("N° de fil") << tr("Fonction") << tr("Tension / Protocole") << tr("Couleur du conducteur") << tr("Section du conducteur") << tr("N° de folio") << tr("Position") << tr("Titre de folio"); } } ui->m_tree_widget->setHeaderLabels(list); } /** @brief LinkSingleElementWidget::diagramWasRemovedFromProject This slot is called when a diagram is removed from the parent project of edited element to update the content of this widget */ void LinkSingleElementWidget::diagramWasRemovedFromProject() { // We use a timer because if the removed diagram // contains the master element linked to the edited element // we must wait for this elements to be unlinked, // or else the list of available master isn't up to date QTimer::singleShot(10, this, SLOT(updateUi())); } void LinkSingleElementWidget::showedElementWasDeleted() { m_showed_element = nullptr; } /** @brief LinkSingleElementWidget::linkTriggered Action linkis triggered */ void LinkSingleElementWidget::linkTriggered() { if(!m_qtwi_at_context_menu) return; m_element_to_link = m_qtwi_elmt_hash.value(m_qtwi_at_context_menu); if(m_live_edit) { apply(); updateUi(); } else { //In no live edit mode, we set the background of the qtwi green, // to inform the user // which element will be linked when he press the apply button if (m_pending_qtwi) { for(int i=0 ; i<6 ; i++) { m_pending_qtwi->setBackground(i, QBrush( Qt::white, Qt::NoBrush)); } } for (int i=0 ; i<6 ; i++) { m_qtwi_at_context_menu->setBackground(i, QBrush(Qt::green, Qt::SolidPattern)); } m_pending_qtwi = m_qtwi_at_context_menu; } } /** @brief LinkSingleElementWidget::hideButtons Hide the button displayed when element is already linked */ void LinkSingleElementWidget::hideButtons() { ui->m_label->hide(); ui->m_unlink_pb->hide(); ui->m_show_linked_pb->hide(); ui->m_show_this_pb->hide(); ui->m_search_field->show(); } /** @brief LinkSingleElementWidget::showButtons Show the button displayed when element is already linked */ void LinkSingleElementWidget::showButtons() { ui->m_label->show(); ui->m_unlink_pb->show(); ui->m_show_linked_pb->show(); ui->m_show_this_pb->show(); ui->m_search_field->hide(); } void LinkSingleElementWidget::headerCustomContextMenuRequested( const QPoint &pos) { m_context_menu->clear(); m_context_menu->addAction(m_save_header_state); m_context_menu->popup(ui->m_tree_widget->header()->mapToGlobal(pos)); } void LinkSingleElementWidget::on_m_unlink_pb_clicked() { m_unlink = true; if(m_live_edit) { apply(); updateUi(); } else buildTree(); } /** @brief LinkSingleElementWidget::on_m_tree_widget_itemDoubleClicked Highlight the element represented by item @param item @param column */ void LinkSingleElementWidget::on_m_tree_widget_itemDoubleClicked( QTreeWidgetItem *item, int column) { Q_UNUSED(column); if (m_showed_element) { disconnect(m_showed_element, SIGNAL(destroyed()), this, SLOT(showedElementWasDeleted())); m_showed_element->setHighlighted(false); } Element *elmt = m_qtwi_elmt_hash.value(item); elmt->diagram()->showMe(); elmt->setHighlighted(true); m_showed_element = elmt; connect(m_showed_element, SIGNAL(destroyed()), this, SLOT(showedElementWasDeleted())); } void LinkSingleElementWidget::on_m_tree_widget_customContextMenuRequested( const QPoint &pos) { //add the size of the header to display the topleft of the QMenu //at the position of the mouse. //See doc about QWidget::customContextMenuRequested //section related to QAbstractScrollArea QPoint point = pos; point.ry()+=ui->m_tree_widget->header()->height(); point = ui->m_tree_widget->mapToGlobal(point); m_context_menu->clear(); if (ui->m_tree_widget->currentItem()) { m_qtwi_at_context_menu = ui->m_tree_widget->currentItem(); m_context_menu->addAction(m_link_action); m_context_menu->addAction(m_show_qtwi); } m_context_menu->addAction(m_show_element); m_context_menu->popup(point); } void LinkSingleElementWidget::on_m_show_linked_pb_clicked() { if (!m_element->isFree()) { Element *elmt = m_element->linkedElements().first(); elmt->diagram()->showMe(); elmt->setHighlighted(true); } } void LinkSingleElementWidget::on_m_show_this_pb_clicked() { m_show_element->trigger(); } /** @brief LinkSingleElementWidget::on_m_search_field_textEdited Search all items which match with arg1 and shows it, other items is hidden. If arg1 is empty, show all items. @param arg1 */ void LinkSingleElementWidget::on_m_search_field_textEdited(const QString &arg1) { //Show all items if arg1 is empty, if not hide all items foreach(QTreeWidgetItem *qtwi, m_qtwi_elmt_hash.keys()) qtwi->setHidden(!arg1.isEmpty()); QList qtwi_list; foreach(QTreeWidgetItem *qtwi, m_qtwi_strl_hash.keys()) { foreach(QString str, m_qtwi_strl_hash.value(qtwi)) { if(str.contains(arg1, Qt::CaseInsensitive)) { qtwi_list << qtwi; continue; } } } //Show items which match with arg1 foreach(QTreeWidgetItem *qtwi, qtwi_list) qtwi->setHidden(false); }