From 0e02190eb2a439b43ad6cd5f609c6f8614ce5407 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sat, 26 Sep 2026 09:15:35 +1200 Subject: [PATCH] Add a finer snap grid for dragged texts Texts snapped to the folio grid, so the first drag of an off-grid label pulled it sideways by up to half a grid step (discussion #1020). Texts now snap to a fraction of the folio grid, chosen from a "Textes 1:N" button in the View toolbar and a menu under Affichage: Off, 1:1, 1:2, 1:2.5, 1:5, 1:10. Because the step divides the folio grid, texts on different elements still line up. Ctrl still places a text freely. 1:1 is the default and matches the previous behaviour. The setting is stored in QSettings; nothing changes in saved projects. All five text movers switch together: element texts, text groups, texts moved with a selection, conductor texts and independent texts. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG --- sources/diagram.cpp | 30 +++++++++++++++ sources/diagram.h | 1 + sources/elementtextsmover.cpp | 2 +- sources/qetdiagrameditor.cpp | 38 +++++++++++++++++++ sources/qetdiagrameditor.h | 4 ++ sources/qetgraphicsitem/conductortextitem.cpp | 2 +- sources/qetgraphicsitem/diagramtextitem.cpp | 2 +- .../dynamicelementtextitem.cpp | 2 +- .../qetgraphicsitem/elementtextitemgroup.cpp | 2 +- 9 files changed, 78 insertions(+), 5 deletions(-) diff --git a/sources/diagram.cpp b/sources/diagram.cpp index 01a6e63d2..022b0a9d2 100644 --- a/sources/diagram.cpp +++ b/sources/diagram.cpp @@ -2684,6 +2684,36 @@ QPointF Diagram::snapToGrid(const QPointF &p) return (QPointF(p_x, p_y)); } +/** + @brief Diagram::snapToTextGrid + Return the nearest point of p on the text grid. + The text grid is the folio grid divided by the + "diagrameditor/text_grid_divisor" setting (1 = same as the folio grid, + 0 = no grid). Because it divides the folio grid, texts snapped to it + still line up with every element and with each other. + Ctrl held, or a divisor of 0, rounds to the nearest pixel instead. + @param p point to find the nearest snapped point + @return +*/ +QPointF Diagram::snapToTextGrid(const QPointF &p) +{ + QSettings settings; + const qreal divisor = settings.value( + QStringLiteral("diagrameditor/text_grid_divisor"), + 1).toReal(); + + if (divisor <= 0 + || QApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) + return QPointF(qRound(p.x()), qRound(p.y())); + + const qreal x_step = settings.value(QStringLiteral("diagrameditor/Xgrid"), + Diagram::xGrid).toInt() / divisor; + const qreal y_step = settings.value(QStringLiteral("diagrameditor/Ygrid"), + Diagram::yGrid).toInt() / divisor; + return QPointF(qRound(p.x() / x_step) * x_step, + qRound(p.y() / y_step) * y_step); +} + /** diff --git a/sources/diagram.h b/sources/diagram.h index 198323bbc..244299a04 100644 --- a/sources/diagram.h +++ b/sources/diagram.h @@ -233,6 +233,7 @@ class Diagram : public QGraphicsScene BorderOptions borderOptions(); DiagramPosition convertPosition(const QPointF &); static QPointF snapToGrid(const QPointF &p); + static QPointF snapToTextGrid(const QPointF &p); bool drawTerminals() const; void setDrawTerminals(bool); diff --git a/sources/elementtextsmover.cpp b/sources/elementtextsmover.cpp index ddf2f235c..f0b61139a 100644 --- a/sources/elementtextsmover.cpp +++ b/sources/elementtextsmover.cpp @@ -101,7 +101,7 @@ void ElementTextsMover::continueMovement(QGraphicsSceneMouseEvent *event) button_down_parent_pos = qgi->mapToParent(qgi->mapFromScene(event->buttonDownScenePos(Qt::LeftButton))); QPointF new_pos = m_items_hash.value(qgi) + current_parent_pos - button_down_parent_pos; - event->modifiers() == Qt::ControlModifier ? qgi->setPos(new_pos) : qgi->setPos(Diagram::snapToGrid(new_pos)); + event->modifiers() == Qt::ControlModifier ? qgi->setPos(new_pos) : qgi->setPos(Diagram::snapToTextGrid(new_pos)); } } diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp index da26262b2..1c2d1c410 100644 --- a/sources/qetdiagrameditor.cpp +++ b/sources/qetdiagrameditor.cpp @@ -20,6 +20,7 @@ #include "scripting/qetscripting.h" #endif #include +#include #include "ElementsCollection/elementscollectionwidget.h" #include "QWidgetAnimation/qwidgetanimation.h" #include "autoNum/ui/autonumberingdockwidget.h" @@ -481,6 +482,41 @@ void QETDiagramEditor::setUpActions() } }); + //Snap step for dragged texts, as a fraction of the folio grid + m_text_grid_menu = new QMenu(tr("Grille des textes"), this); + m_text_grid_menu->setIcon(QET::Icons::Grid); + m_text_grid_menu->setToolTipsVisible(true); + m_text_grid_button = new QToolButton(this); + m_text_grid_button->setMenu(m_text_grid_menu); + m_text_grid_button->setPopupMode(QToolButton::InstantPopup); + m_text_grid_button->setToolButtonStyle(Qt::ToolButtonTextOnly); + m_text_grid_button->setToolTip(tr("Grille d'accrochage des textes déplacés à la souris.\n" + "Maintenir Ctrl pendant le déplacement pour placer librement.")); + auto text_grid_group = new QActionGroup(this); + const qreal current_divisor = settings.value("diagrameditor/text_grid_divisor", 1).toReal(); + const QList> text_grids { + {tr("Désactivée"), 0}, + {QStringLiteral("1:1"), 1}, + {QStringLiteral("1:2"), 2}, + {QStringLiteral("1:2.5"), 2.5}, + {QStringLiteral("1:5"), 5}, + {QStringLiteral("1:10"), 10}}; + for (const auto &grid : text_grids) + { + QAction *action = m_text_grid_menu->addAction(grid.first); + action->setCheckable(true); + action->setData(grid.second); + text_grid_group->addAction(action); + if (qFuzzyCompare(grid.second + 1, current_divisor + 1)) { + action->setChecked(true); + m_text_grid_button->setText(tr("Textes %1").arg(grid.first)); + } + } + connect(text_grid_group, &QActionGroup::triggered, this, [this](QAction *action) { + QSettings().setValue("diagrameditor/text_grid_divisor", action->data()); + m_text_grid_button->setText(tr("Textes %1").arg(action->text())); + }); + // Draw or not the custom guides m_draw_guides = new QAction ( QIcon::fromTheme("guides"), tr("Afficher les guides"), this); m_draw_guides->setStatusTip(tr("Affiche ou masque les guides")); @@ -956,6 +992,7 @@ void QETDiagramEditor::setUpToolBar() view_tool_bar -> addWidget(new DiagramEditorHandlerSizeWidget(this)); view_tool_bar -> addSeparator(); view_tool_bar -> addAction(m_draw_grid); + view_tool_bar -> addWidget(m_text_grid_button); view_tool_bar -> addAction(m_draw_guides); view_tool_bar -> addWidget(m_background_color_button); view_tool_bar -> addSeparator(); @@ -1109,6 +1146,7 @@ void QETDiagramEditor::setUpMenu() menu_affichage -> addAction(m_mode_visualise); menu_affichage -> addSeparator(); menu_affichage -> addAction(m_draw_grid); + menu_affichage -> addMenu(m_text_grid_menu); menu_affichage -> addAction(m_draw_guides); menu_affichage -> addMenu(m_background_color_button->menu()); menu_affichage -> addSeparator(); diff --git a/sources/qetdiagrameditor.h b/sources/qetdiagrameditor.h index 71ad56244..bf6de32b5 100644 --- a/sources/qetdiagrameditor.h +++ b/sources/qetdiagrameditor.h @@ -28,6 +28,8 @@ #include #include +class QToolButton; + class QMdiSubWindow; class QETProject; class QETResult; @@ -244,6 +246,8 @@ class QETDiagramEditor : public QETMainWindow ConductorColorToolButton *m_conductor_color_button = nullptr; ///< Diagram background color picker, in the "Affichage" toolbar DiagramBgColorToolButton *m_background_color_button = nullptr; + QMenu *m_text_grid_menu = nullptr; ///< Snap step used when dragging texts + QToolButton *m_text_grid_button = nullptr; QList m_zoom_action_toolBar; ///Only zoom action must displayed in the toolbar diff --git a/sources/qetgraphicsitem/conductortextitem.cpp b/sources/qetgraphicsitem/conductortextitem.cpp index e5e2201ac..624b5ea77 100644 --- a/sources/qetgraphicsitem/conductortextitem.cpp +++ b/sources/qetgraphicsitem/conductortextitem.cpp @@ -185,7 +185,7 @@ void ConductorTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (parent_conductor_) { if (parent_conductor_->nearShape().contains(intended_pos)) { - event->modifiers() == Qt::ControlModifier ? setPos(intended_pos) : setPos(Diagram::snapToGrid(intended_pos)); + event->modifiers() == Qt::ControlModifier ? setPos(intended_pos) : setPos(Diagram::snapToTextGrid(intended_pos)); parent_conductor_ -> setHighlighted(Conductor::Normal); } else { parent_conductor_ -> setHighlighted(Conductor::Alert); diff --git a/sources/qetgraphicsitem/diagramtextitem.cpp b/sources/qetgraphicsitem/diagramtextitem.cpp index 4587ab647..771a3d42d 100644 --- a/sources/qetgraphicsitem/diagramtextitem.cpp +++ b/sources/qetgraphicsitem/diagramtextitem.cpp @@ -370,7 +370,7 @@ void DiagramTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { //Set the actual pos QPointF new_pos = event->scenePos() + m_mouse_to_origin_movement; - event->modifiers() == Qt::ControlModifier ? setPos(new_pos) : setPos(Diagram::snapToGrid(new_pos)); + event->modifiers() == Qt::ControlModifier ? setPos(new_pos) : setPos(Diagram::snapToTextGrid(new_pos)); //Update the actual movement for other selected item diff --git a/sources/qetgraphicsitem/dynamicelementtextitem.cpp b/sources/qetgraphicsitem/dynamicelementtextitem.cpp index 1316d992b..fda123da9 100644 --- a/sources/qetgraphicsitem/dynamicelementtextitem.cpp +++ b/sources/qetgraphicsitem/dynamicelementtextitem.cpp @@ -639,7 +639,7 @@ void DynamicElementTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) //DiagramTextItem::mouseMoveEvent() for independent texts. //Without it this was the only text move in the editor that //ignored the grid. - event->modifiers() == Qt::ControlModifier ? setPos(new_pos) : setPos(Diagram::snapToGrid(new_pos)); + event->modifiers() == Qt::ControlModifier ? setPos(new_pos) : setPos(Diagram::snapToTextGrid(new_pos)); if(diagram()) diagram()->elementTextsMover().continueMovement(event); diff --git a/sources/qetgraphicsitem/elementtextitemgroup.cpp b/sources/qetgraphicsitem/elementtextitemgroup.cpp index 253c10146..9e4016fcf 100644 --- a/sources/qetgraphicsitem/elementtextitemgroup.cpp +++ b/sources/qetgraphicsitem/elementtextitemgroup.cpp @@ -688,7 +688,7 @@ void ElementTextItemGroup::mouseMoveEvent(QGraphicsSceneMouseEvent *event) button_down_parent_pos = mapToParent(mapFromScene(event->buttonDownScenePos(Qt::LeftButton))); QPointF new_pos = m_initial_position + current_parent_pos - button_down_parent_pos; - event->modifiers() == Qt::ControlModifier ? setPos(new_pos) : setPos(Diagram::snapToGrid(new_pos)); + event->modifiers() == Qt::ControlModifier ? setPos(new_pos) : setPos(Diagram::snapToTextGrid(new_pos)); if(diagram()) diagram()->elementTextsMover().continueMovement(event);