From ce0e7e68676535e7fa3eec4fdfe5cc1a736fd07a Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sat, 26 Sep 2026 07:37:38 +1200 Subject: [PATCH] Add jumping to a folio cell such as B13 from Ctrl+G (#1034) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Typing a cell reference into the "Atteindre un élément" popup now offers "Case B13"; Enter zooms the view onto that cell with one cell of margin. The cell is read the way the border labels it: row letter(s) then column number, honouring the "columns start at 0" setting and multi-letter rows (AA, AB...). Cells outside the folio are not offered. BorderTitleBlock::cellRect() is the reverse of convertPosition(); a round trip over every cell of a 30x23 folio, under both column-numbering settings, returned the same cell for all 1380. When an element is labelled exactly like the cell (K1), the element stays first so Enter keeps its old meaning; the cell is listed after it. DiagramView::zoomToRect() re-centres from a queued call: zooming in makes the scroll bars appear, and the viewport resize that follows is anchored under the mouse (setResizeAnchor(AnchorUnderMouse)), which otherwise scrolls the view away from the cell straight after the zoom. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG --- sources/bordertitleblock.cpp | 41 +++++++++++++++++++++++++ sources/bordertitleblock.h | 1 + sources/diagramview.cpp | 17 +++++++++++ sources/diagramview.h | 1 + sources/ui/jumptoelementdialog.cpp | 48 +++++++++++++++++++++++++++++- sources/ui/jumptoelementdialog.h | 5 +++- 6 files changed, 111 insertions(+), 2 deletions(-) diff --git a/sources/bordertitleblock.cpp b/sources/bordertitleblock.cpp index 4a2a2dfdc..c6913ba46 100644 --- a/sources/bordertitleblock.cpp +++ b/sources/bordertitleblock.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #define MIN_COLUMN_COUNT 3 @@ -935,6 +936,46 @@ void BorderTitleBlock::updateDiagramContextForTitleBlock( m_titleblock_template_renderer -> setContext(context); } +/** + @brief BorderTitleBlock::cellRect + Convert a cell written the way the border labels it (ex : B13, the row + letter(s) then the column number) to its rect in scene coordinate. + This is the reverse of convertPosition(). + @param cell : the cell to convert, case and surrounding spaces ignored + @return the rect of the cell, or a null QRectF if \a cell is not a + cell reference or lies outside of the border. +*/ +QRectF BorderTitleBlock::cellRect(const QString &cell) const +{ + static const QRegularExpression cell_re( + QStringLiteral("^\\s*([A-Za-z]+)\\s*(\\d{1,4})\\s*$")); + const QRegularExpressionMatch match = cell_re.match(cell); + if (!match.hasMatch()) + return QRectF(); + + //Row letters count like A..Z, AA, AB... (see incrementLetters()) + int row = 0; + for (const QChar c : match.captured(1).toUpper()) { + row = row * 26 + (c.unicode() - 'A' + 1); + if (row > rows_count_) + return QRectF(); + } + + int column = match.captured(2).toInt(); + QSettings settings; + if (settings.value("border-columns_0", true).toBool()) + ++column; + + if (row < 1 || column < 1 || column > columns_count_) + return QRectF(); + + const QPointF top_left = insideBorderRect().topLeft(); + return QRectF(top_left.x() + (column - 1) * columns_width_, + top_left.y() + (row - 1) * rows_height_, + columns_width_, + rows_height_); +} + /** @brief BorderTitleBlock::incrementLetters increments string with Letters A to Z diff --git a/sources/bordertitleblock.h b/sources/bordertitleblock.h index 28da7360c..4065bf488 100644 --- a/sources/bordertitleblock.h +++ b/sources/bordertitleblock.h @@ -159,6 +159,7 @@ class BorderTitleBlock : public QObject void setDiagramHeight(const qreal &); DiagramPosition convertPosition(const QPointF &); + QRectF cellRect(const QString &cell) const; // methods to set title block basic data void setFolio(const QString &folio); diff --git a/sources/diagramview.cpp b/sources/diagramview.cpp index 843b694be..10216015c 100644 --- a/sources/diagramview.cpp +++ b/sources/diagramview.cpp @@ -385,6 +385,23 @@ void DiagramView::zoomReset() adjustGridToZoom(); } +/** + @brief DiagramView::zoomToRect + Adjust zoom to fit \a rect, in scene coordinate, in the view. + @param rect +*/ +void DiagramView::zoomToRect(const QRectF &rect) +{ + fitInView(rect, Qt::KeepAspectRatio); + //Zooming in makes the scroll bars appear, which resizes the viewport + //from a queued call; that resize is anchored under the mouse and + //would scroll away from rect, so center again once it has run. + QMetaObject::invokeMethod(this, [this, rect]() { + centerOn(rect.center()); + }, Qt::QueuedConnection); + adjustGridToZoom(); +} + /** Copie les elements selectionnes du schema dans le presse-papier puis les supprime Copies the selected elements from the diagram to the clipboard and then deletes them diff --git a/sources/diagramview.h b/sources/diagramview.h index eb73432cb..a03b49ebd 100644 --- a/sources/diagramview.h +++ b/sources/diagramview.h @@ -140,6 +140,7 @@ class DiagramView : public PaletteGraphicsView void zoomFit(); void zoomContent(); void zoomReset(); + void zoomToRect(const QRectF &rect); void cut(); void copy(); void paste(const QPointF & = QPointF(), QClipboard::Mode = QClipboard::Clipboard); diff --git a/sources/ui/jumptoelementdialog.cpp b/sources/ui/jumptoelementdialog.cpp index 9203cd909..118a4c8e0 100644 --- a/sources/ui/jumptoelementdialog.cpp +++ b/sources/ui/jumptoelementdialog.cpp @@ -18,6 +18,7 @@ #include "jumptoelementdialog.h" #include "../diagram.h" +#include "../diagramview.h" #include "../qetgraphicsitem/element.h" #include @@ -38,7 +39,7 @@ JumpToElementDialog::JumpToElementDialog(Diagram *diagram, QWidget *parent) : 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->setPlaceholderText(tr("Nom, label ou information de l'élément, ou case (ex. B13)…")); m_filter_edit->installEventFilter(this); m_result_list = new QListWidget(this); @@ -86,6 +87,7 @@ void JumpToElementDialog::buildCandidates() Candidate candidate; candidate.element = element; + candidate.label = label; candidate.display_text = label.isEmpty() ? name : (label + QStringLiteral(" — ") + name); QStringList search_parts; @@ -111,6 +113,7 @@ void JumpToElementDialog::updateFilteredList(const QString &filter_text) m_result_list->clear(); const QString needle = filter_text.trimmed().toLower(); + bool exact_label_match = false; for (int i = 0; i < m_candidates.size(); ++i) { const Candidate &candidate = m_candidates.at(i); if (!candidate.element) { @@ -121,6 +124,23 @@ void JumpToElementDialog::updateFilteredList(const QString &filter_text) } auto *list_item = new QListWidgetItem(candidate.display_text, m_result_list); list_item->setData(Qt::UserRole, i); + if (candidate.label.compare(needle, Qt::CaseInsensitive) == 0) { + exact_label_match = true; + } + } + + //A cell of the border (ex : B13) comes first, unless an element + //is labelled exactly like it: Enter keeps jumping to that element. + if (m_diagram) { + const QRectF cell_rect = m_diagram->border_and_titleblock.cellRect(needle); + if (!cell_rect.isNull()) { + auto *cell_item = new QListWidgetItem( + tr("Case %1").arg(QString(needle).remove(QLatin1Char(' ')).toUpper())); + cell_item->setData(Qt::UserRole, -1); + cell_item->setData(Qt::UserRole + 1, cell_rect); + m_result_list->insertItem(exact_label_match ? m_result_list->count() : 0, + cell_item); + } } if (m_result_list->count() > 0) { @@ -142,6 +162,11 @@ void JumpToElementDialog::activateCurrentItem() } const int index = current->data(Qt::UserRole).toInt(); + if (index == -1) { + zoomToCell(current->data(Qt::UserRole + 1).toRectF()); + accept(); + return; + } if (index < 0 || index >= m_candidates.size()) { reject(); return; @@ -159,6 +184,27 @@ void JumpToElementDialog::activateCurrentItem() accept(); } +/** + @brief JumpToElementDialog::zoomToCell + Zoom the view of m_diagram on \a cell_rect with one cell of context + around it. + @param cell_rect : the cell, in scene coordinate +*/ +void JumpToElementDialog::zoomToCell(const QRectF &cell_rect) +{ + if (!m_diagram) { + return; + } + for (QGraphicsView *view : m_diagram->views()) { + if (auto *diagram_view = qobject_cast(view)) { + diagram_view->zoomToRect(cell_rect.adjusted( + -cell_rect.width(), -cell_rect.height(), + cell_rect.width(), cell_rect.height())); + return; + } + } +} + /** @brief JumpToElementDialog::eventFilter Redirect Up/Down/Enter/Escape typed in the filter field to the result diff --git a/sources/ui/jumptoelementdialog.h b/sources/ui/jumptoelementdialog.h index 603b3932b..4a34d1f2b 100644 --- a/sources/ui/jumptoelementdialog.h +++ b/sources/ui/jumptoelementdialog.h @@ -31,7 +31,8 @@ class QListWidget; 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 + into view. Typing a cell of the border instead (ex : B13) offers to + zoom on that cell. Up/Down move through the filtered list, Escape cancels without changing the current selection. */ class JumpToElementDialog : public QDialog @@ -51,9 +52,11 @@ class JumpToElementDialog : public QDialog private: void buildCandidates(); + void zoomToCell(const QRectF &cell_rect); struct Candidate { QPointer element; + QString label; QString display_text; QString search_text; };