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