diff --git a/cmake/qet_compilation_vars.cmake b/cmake/qet_compilation_vars.cmake index affe12ab9..0e5c240a2 100644 --- a/cmake/qet_compilation_vars.cmake +++ b/cmake/qet_compilation_vars.cmake @@ -697,6 +697,8 @@ set(QET_SRC_FILES ${QET_DIR}/sources/ui/importelementdialog.h ${QET_DIR}/sources/ui/importelementtextpatterndialog.cpp ${QET_DIR}/sources/ui/importelementtextpatterndialog.h + ${QET_DIR}/sources/ui/jumptoelementdialog.cpp + ${QET_DIR}/sources/ui/jumptoelementdialog.h ${QET_DIR}/sources/ui/inditextpropertieswidget.cpp ${QET_DIR}/sources/ui/inditextpropertieswidget.h ${QET_DIR}/sources/ui/linksingleelementwidget.cpp diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp index 88c239d6e..1a4e995c3 100644 --- a/sources/qetdiagrameditor.cpp +++ b/sources/qetdiagrameditor.cpp @@ -39,6 +39,7 @@ #include "qetmessagebox.h" #include "recentfiles.h" #include "ui/bomexportdialog.h" +#include "ui/jumptoelementdialog.h" #include "ui/diagrampropertieseditordockwidget.h" #include "ui/backupdialog.h" #include "ui/dialogwaiting.h" @@ -747,6 +748,19 @@ void QETDiagramEditor::setUpActions() this->m_search_and_replace_widget.setHidden(!m_search_and_replace_widget.isHidden()); } }); + + m_jump_to_element = new QAction(tr("Atteindre un élément"), this); + m_jump_to_element->setShortcut(Qt::CTRL | Qt::Key_G); + m_jump_to_element->setStatusTip(tr("Recherche et sélectionne rapidement un élément du folio", "status bar tip")); + connect(m_jump_to_element, &QAction::triggered, [this]() + { + DiagramView *diagram_view = this->currentDiagramView(); + if (!diagram_view || !diagram_view->diagram()) { + return; + } + JumpToElementDialog dialog(diagram_view->diagram(), this); + dialog.exec(); + }); } /** @@ -861,6 +875,7 @@ void QETDiagramEditor::setUpMenu() menu_edition -> addActions(m_depth_action_group->actions()); menu_edition -> addSeparator(); menu_edition -> addAction(m_find); + menu_edition -> addAction(m_jump_to_element); // menu Projet menu_project -> addAction(m_project_edit_properties); diff --git a/sources/qetdiagrameditor.h b/sources/qetdiagrameditor.h index 98a60a967..0114a186e 100644 --- a/sources/qetdiagrameditor.h +++ b/sources/qetdiagrameditor.h @@ -222,7 +222,8 @@ class QETDiagramEditor : public QETMainWindow *m_close_file, ///< Close current project file *m_save_file, ///< Save current project *m_save_file_as, ///< Save current project as a specific file - *m_find = nullptr; + *m_find = nullptr, + *m_jump_to_element = nullptr; ///< Open the "jump to element" quick-open popup QList m_zoom_action_toolBar; ///Only zoom action must displayed in the toolbar diff --git a/sources/ui/jumptoelementdialog.cpp b/sources/ui/jumptoelementdialog.cpp new file mode 100644 index 000000000..9203cd909 --- /dev/null +++ b/sources/ui/jumptoelementdialog.cpp @@ -0,0 +1,198 @@ +/* + 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 . +*/ +#include "jumptoelementdialog.h" + +#include "../diagram.h" +#include "../qetgraphicsitem/element.h" + +#include +#include +#include +#include +#include + +/** + @brief JumpToElementDialog::JumpToElementDialog + @param diagram : the diagram whose elements can be jumped to + @param parent +*/ +JumpToElementDialog::JumpToElementDialog(Diagram *diagram, QWidget *parent) : + QDialog(parent), + m_diagram(diagram) +{ + setWindowTitle(tr("Atteindre un élément", "window title")); + + m_filter_edit = new QLineEdit(this); + m_filter_edit->setPlaceholderText(tr("Nom, label ou information de l'élément…")); + m_filter_edit->installEventFilter(this); + + m_result_list = new QListWidget(this); + m_result_list->setFocusPolicy(Qt::NoFocus); + + auto *layout = new QVBoxLayout(this); + layout->addWidget(m_filter_edit); + layout->addWidget(m_result_list); + setLayout(layout); + resize(420, 320); + + connect(m_filter_edit, &QLineEdit::textChanged, this, &JumpToElementDialog::updateFilteredList); + connect(m_result_list, &QListWidget::itemActivated, this, &JumpToElementDialog::activateCurrentItem); + + buildCandidates(); + updateFilteredList(QString()); + m_filter_edit->setFocus(); +} + +JumpToElementDialog::~JumpToElementDialog() +{ +} + +/** + @brief JumpToElementDialog::buildCandidates + Collect every element on m_diagram into m_candidates, along with the + searchable text used to filter it (its type name, its label, and its + other element informations). +*/ +void JumpToElementDialog::buildCandidates() +{ + m_candidates.clear(); + if (!m_diagram) { + return; + } + + for (auto item : m_diagram->items()) { + Element *element = qgraphicsitem_cast(item); + if (!element) { + continue; + } + + const QString label = element->elementInformations().value(QStringLiteral("label")).toString(); + const QString name = element->name(); + + Candidate candidate; + candidate.element = element; + candidate.display_text = label.isEmpty() ? name : (label + QStringLiteral(" — ") + name); + + QStringList search_parts; + search_parts << label << name; + const DiagramContext infos = element->elementInformations(); + for (const QString &key : infos.keys()) { + search_parts << infos.value(key).toString(); + } + candidate.search_text = search_parts.join(QLatin1Char(' ')).toLower(); + + m_candidates << candidate; + } +} + +/** + @brief JumpToElementDialog::updateFilteredList + Refill m_result_list with every candidate whose search text contains + @a filter_text (case-insensitive), and select the first match. + @param filter_text +*/ +void JumpToElementDialog::updateFilteredList(const QString &filter_text) +{ + m_result_list->clear(); + + const QString needle = filter_text.trimmed().toLower(); + for (int i = 0; i < m_candidates.size(); ++i) { + const Candidate &candidate = m_candidates.at(i); + if (!candidate.element) { + continue; + } + if (!needle.isEmpty() && !candidate.search_text.contains(needle)) { + continue; + } + auto *list_item = new QListWidgetItem(candidate.display_text, m_result_list); + list_item->setData(Qt::UserRole, i); + } + + if (m_result_list->count() > 0) { + m_result_list->setCurrentRow(0); + } +} + +/** + @brief JumpToElementDialog::activateCurrentItem + Select the element corresponding to the currently highlighted result + on the diagram, scroll it into view, and close this dialog. +*/ +void JumpToElementDialog::activateCurrentItem() +{ + QListWidgetItem *current = m_result_list->currentItem(); + if (!current || !m_diagram) { + reject(); + return; + } + + const int index = current->data(Qt::UserRole).toInt(); + if (index < 0 || index >= m_candidates.size()) { + reject(); + return; + } + + Element *element = m_candidates.at(index).element; + if (!element) { + reject(); + return; + } + + m_diagram->clearSelection(); + element->setSelected(true); + element->ensureVisible(); + accept(); +} + +/** + @brief JumpToElementDialog::eventFilter + Redirect Up/Down/Enter/Escape typed in the filter field to the result + list, so the user never has to leave the text field to navigate or + confirm a choice. +*/ +bool JumpToElementDialog::eventFilter(QObject *watched, QEvent *event) +{ + if (watched == m_filter_edit && event->type() == QEvent::KeyPress) { + auto *key_event = static_cast(event); + switch (key_event->key()) { + case Qt::Key_Down: + case Qt::Key_Up: { + const int row_count = m_result_list->count(); + if (row_count == 0) { + return true; + } + int row = m_result_list->currentRow(); + row = key_event->key() == Qt::Key_Down + ? (row + 1) % row_count + : (row - 1 + row_count) % row_count; + m_result_list->setCurrentRow(row); + return true; + } + case Qt::Key_Return: + case Qt::Key_Enter: + activateCurrentItem(); + return true; + case Qt::Key_Escape: + reject(); + return true; + default: + break; + } + } + return QDialog::eventFilter(watched, event); +} diff --git a/sources/ui/jumptoelementdialog.h b/sources/ui/jumptoelementdialog.h new file mode 100644 index 000000000..603b3932b --- /dev/null +++ b/sources/ui/jumptoelementdialog.h @@ -0,0 +1,67 @@ +/* + 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 . +*/ +#ifndef JUMPTOELEMENTDIALOG_H +#define JUMPTOELEMENTDIALOG_H + +#include +#include + +class Diagram; +class Element; +class QLineEdit; +class QListWidget; + +/** + @brief The JumpToElementDialog class + A lightweight, transient "quick open" popup: type part of an element's + label or other information to live-filter the elements on a diagram, + then Enter to select the chosen element on the diagram and scroll it + into view. Up/Down move through the filtered list, Escape cancels + without changing the current selection. +*/ +class JumpToElementDialog : public QDialog +{ + Q_OBJECT + + public: + explicit JumpToElementDialog(Diagram *diagram, QWidget *parent = nullptr); + ~JumpToElementDialog() override; + + protected: + bool eventFilter(QObject *watched, QEvent *event) override; + + private slots: + void updateFilteredList(const QString &filter_text); + void activateCurrentItem(); + + private: + void buildCandidates(); + + struct Candidate { + QPointer element; + QString display_text; + QString search_text; + }; + + QPointer m_diagram; + QList m_candidates; + QLineEdit *m_filter_edit; + QListWidget *m_result_list; +}; + +#endif // JUMPTOELEMENTDIALOG_H