From 86da56b866debff7ee0c659c1c4021a33520f4c1 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Fri, 2 Oct 2026 19:13:01 +1300 Subject: [PATCH 1/2] Fix bugtracker #112: no snap in the symbol editor below 100 % zoom Below 100 % ElementView::drawBackground() hid the grid and also set the snap step to 1 unit, so anything dragged while zoomed out landed off the grid with nothing on screen to show it. A large symbol opens fitted to the window below 100 %, which is why the report also says "when the selection is big". Keep hiding the grid below 100 % (its dots would merge into a grey wash), but snap every 10 units, as at 100 %. The zoom-to-grid rule moves into a header-only ElementViewGrid so it can be tested without an editor window, like textgrid.h. Checked in the editor: a one-rectangle symbol zoomed out to about 0.6x, its edge dragged by (15, 10) px and saved: master moves it by (28, 18), this branch by (30, 20). tst_elementviewgrid fails with the old step 1 and passes now; ctest 45/45. Co-Authored-By: Claude Opus 5.5 (1M context) --- sources/editor/elementview.cpp | 28 ++++--------- sources/editor/elementviewgrid.h | 60 ++++++++++++++++++++++++++++ tests/qttest/CMakeLists.txt | 7 ++++ tests/qttest/tst_elementviewgrid.cpp | 45 +++++++++++++++++++++ 4 files changed, 119 insertions(+), 21 deletions(-) create mode 100644 sources/editor/elementviewgrid.h create mode 100644 tests/qttest/tst_elementviewgrid.cpp diff --git a/sources/editor/elementview.cpp b/sources/editor/elementview.cpp index 6eed40457..641581820 100644 --- a/sources/editor/elementview.cpp +++ b/sources/editor/elementview.cpp @@ -17,6 +17,8 @@ */ #include "elementview.h" +#include "elementviewgrid.h" + #include "../qetapp.h" #include "UndoCommand/pastepartscommand.h" #include "ui/qetelementeditor.h" @@ -535,27 +537,11 @@ void ElementView::drawBackground(QPainter *p, const QRectF &r) { // choisit la granularite de la grille en fonction du zoom en cours // selects the grid granularity according to the current zoom level - int drawn_x_grid = 1;//scene_ -> xGrid(); - int drawn_y_grid = 1;//scene_ -> yGrid(); - bool draw_grid = true; - bool draw_cross = false; - - if (zoom_factor < 1.0) { //< no grid - draw_grid = false; - } else if (zoom_factor < 4.0) { //< grid 10*10 - drawn_x_grid *= 10; - drawn_y_grid *= 10; - }else if (zoom_factor < 8.0) { //< grid 5*5 - drawn_x_grid *= 5; - drawn_y_grid *= 5; - draw_cross = true; - } else if (zoom_factor < 10.0) { //< grid 2*2 - drawn_x_grid *= 2; - drawn_y_grid *= 2; - draw_cross = true; - } else { //< grid 1*1 - draw_cross = true; - } + const ElementViewGrid grid = ElementViewGrid::forZoom(zoom_factor); + const int drawn_x_grid = grid.step; + const int drawn_y_grid = grid.step; + const bool draw_grid = grid.draw_grid; + const bool draw_cross = grid.draw_cross; m_scene->setGrid(drawn_x_grid, drawn_y_grid); diff --git a/sources/editor/elementviewgrid.h b/sources/editor/elementviewgrid.h new file mode 100644 index 000000000..7a6b2a7e9 --- /dev/null +++ b/sources/editor/elementviewgrid.h @@ -0,0 +1,60 @@ +/* + 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 ELEMENTVIEWGRID_H +#define ELEMENTVIEWGRID_H + +#include + +/** + The symbol editor's grid at a given zoom: the step parts snap to, and + how the grid is drawn. The step gets finer as the view zooms in, so a + small detail can be placed precisely. + + Below 100 % the grid is not drawn (its dots would merge into a grey + wash), but parts still snap every 10 units, as at 100 %. Snapping to 1 + unit there left everything dragged while zoomed out off the grid, with + nothing on screen to show it (bugtracker #112). +*/ +struct ElementViewGrid +{ + int step = 10; + bool draw_grid = true; + bool draw_cross = false; + + static ElementViewGrid forZoom(qreal zoom_factor) + { + ElementViewGrid grid; + if (zoom_factor < 1.0) { + grid.draw_grid = false; + } else if (zoom_factor < 4.0) { + grid.step = 10; + } else if (zoom_factor < 8.0) { + grid.step = 5; + grid.draw_cross = true; + } else if (zoom_factor < 10.0) { + grid.step = 2; + grid.draw_cross = true; + } else { + grid.step = 1; + grid.draw_cross = true; + } + return grid; + } +}; + +#endif // ELEMENTVIEWGRID_H diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt index 3b1085990..9fc364762 100644 --- a/tests/qttest/CMakeLists.txt +++ b/tests/qttest/CMakeLists.txt @@ -104,6 +104,13 @@ add_test(NAME tst_textgrid COMMAND tst_textgrid) target_include_directories(tst_textgrid PRIVATE ${QET_DIR}/sources) target_link_libraries(tst_textgrid PRIVATE Qt::Test) +# elementviewgrid.h is header-only: the symbol editor's snap step at each +# zoom (bugtracker #112), tested without an editor window. +add_executable(tst_elementviewgrid tst_elementviewgrid.cpp) +add_test(NAME tst_elementviewgrid COMMAND tst_elementviewgrid) +target_include_directories(tst_elementviewgrid PRIVATE ${QET_DIR}/sources) +target_link_libraries(tst_elementviewgrid PRIVATE Qt::Test) + # alignment.h is header-only too (it builds on textgrid.h): the geometry # behind the align commands, tested without a scene. add_executable(tst_alignment tst_alignment.cpp) diff --git a/tests/qttest/tst_elementviewgrid.cpp b/tests/qttest/tst_elementviewgrid.cpp new file mode 100644 index 000000000..5082666f6 --- /dev/null +++ b/tests/qttest/tst_elementviewgrid.cpp @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include + +#include "editor/elementviewgrid.h" + +// The symbol editor's snap step at each zoom (bugtracker #112): below +// 100 % the grid is hidden but parts must still snap every 10 units. +class tst_elementviewgrid : public QObject +{ + Q_OBJECT + +private slots: + void stepAtZoom_data() + { + QTest::addColumn("zoom"); + QTest::addColumn("step"); + QTest::addColumn("drawGrid"); + QTest::addColumn("drawCross"); + + QTest::newRow("10 % (fit to a big symbol)") << 0.10 << 10 << false << false; + QTest::newRow("62.5 %, the #112 case") << 0.625 << 10 << false << false; + QTest::newRow("100 %") << 1.0 << 10 << true << false; + QTest::newRow("200 %") << 2.0 << 10 << true << false; + QTest::newRow("400 %") << 4.0 << 5 << true << true; + QTest::newRow("800 %") << 8.0 << 2 << true << true; + QTest::newRow("1000 %") << 10.0 << 1 << true << true; + } + + void stepAtZoom() + { + QFETCH(qreal, zoom); + QFETCH(int, step); + QFETCH(bool, drawGrid); + QFETCH(bool, drawCross); + + const ElementViewGrid grid = ElementViewGrid::forZoom(zoom); + QCOMPARE(grid.step, step); + QCOMPARE(grid.draw_grid, drawGrid); + QCOMPARE(grid.draw_cross, drawCross); + } +}; + +QTEST_APPLESS_MAIN(tst_elementviewgrid) + +#include "tst_elementviewgrid.moc" From a16bac8f84a3902f36980c4510641a5f66bed537 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Fri, 2 Oct 2026 21:09:24 +1300 Subject: [PATCH 2/2] Snap a moved multi-part selection to the editor grid too (bugtracker #112) A selection of several parts is moved by ElementPrimitiveDecorator, which snapped to a fixed 1-unit step at every zoom. That is the "selection is big" half of #112: below 100 % one part landed on the 10 grid but a multi-part selection did not, and at 100-400 % it ignored the grid too. It now snaps to the editor's current grid, as a single part does; Ctrl still gives the fine 1-unit step. Resizing and arrow-key moves are unchanged. Also from review: add rows just below each zoom threshold to tst_elementviewgrid (each catches a moved threshold the old rows missed), and use one grid_step in drawBackground() instead of identical x/y copies. Co-Authored-By: Claude Opus 5.5 --- sources/editor/elementprimitivedecorator.cpp | 8 +++++++- sources/editor/elementview.cpp | 13 ++++++------- tests/qttest/tst_elementviewgrid.cpp | 8 ++++++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/sources/editor/elementprimitivedecorator.cpp b/sources/editor/elementprimitivedecorator.cpp index 3b678a7d4..8c01c3693 100644 --- a/sources/editor/elementprimitivedecorator.cpp +++ b/sources/editor/elementprimitivedecorator.cpp @@ -206,7 +206,13 @@ void ElementPrimitiveDecorator::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { // When moving the selection, consider the position of the first selected item QPointF current_position = scene_pos - mouse_offset_; - QPointF rounded_current_position = snapConstPointToGrid(current_position); + // Snap to the editor's grid, as a single part does (bugtracker + // #112); Ctrl keeps the fine 1-unit step. + ElementScene *element_scene = qobject_cast(scene()); + QPointF rounded_current_position = + (element_scene && !(event->modifiers() & Qt::ControlModifier)) + ? element_scene->snapToGrid(current_position) + : snapConstPointToGrid(current_position); movement = rounded_current_position - decorated_items_.at(0) -> toItem() -> scenePos(); QRectF bounding_rect = modified_bounding_rect_; diff --git a/sources/editor/elementview.cpp b/sources/editor/elementview.cpp index 641581820..588ac2399 100644 --- a/sources/editor/elementview.cpp +++ b/sources/editor/elementview.cpp @@ -538,12 +538,11 @@ void ElementView::drawBackground(QPainter *p, const QRectF &r) { // choisit la granularite de la grille en fonction du zoom en cours // selects the grid granularity according to the current zoom level const ElementViewGrid grid = ElementViewGrid::forZoom(zoom_factor); - const int drawn_x_grid = grid.step; - const int drawn_y_grid = grid.step; + const int grid_step = grid.step; const bool draw_grid = grid.draw_grid; const bool draw_cross = grid.draw_cross; - m_scene->setGrid(drawn_x_grid, drawn_y_grid); + m_scene->setGrid(grid_step, grid_step); if (draw_grid) { // draw the dots of the grid @@ -573,12 +572,12 @@ void ElementView::drawBackground(QPainter *p, const QRectF &r) { qreal limit_y = r.y() + r.height(); int g_x = (int)ceil(r.x()); - while (g_x % drawn_x_grid) ++ g_x; + while (g_x % grid_step) ++ g_x; int g_y = (int)ceil(r.y()); - while (g_y % drawn_y_grid) ++ g_y; + while (g_y % grid_step) ++ g_y; - for (int gx = g_x ; gx < limit_x ; gx += drawn_x_grid) { - for (int gy = g_y ; gy < limit_y ; gy += drawn_y_grid) { + for (int gx = g_x ; gx < limit_x ; gx += grid_step) { + for (int gy = g_y ; gy < limit_y ; gy += grid_step) { if (draw_cross) { if (!(gx % 10) && !(gy % 10)) { p -> drawLine(QLineF(gx - (pen.width()/4.0), gy, gx + (pen.width()/4.0), gy)); diff --git a/tests/qttest/tst_elementviewgrid.cpp b/tests/qttest/tst_elementviewgrid.cpp index 5082666f6..bf3c48e3b 100644 --- a/tests/qttest/tst_elementviewgrid.cpp +++ b/tests/qttest/tst_elementviewgrid.cpp @@ -24,6 +24,14 @@ private slots: QTest::newRow("400 %") << 4.0 << 5 << true << true; QTest::newRow("800 %") << 8.0 << 2 << true << true; QTest::newRow("1000 %") << 10.0 << 1 << true << true; + + // Just below each threshold: the comparisons are strict (<), so a + // change to <= would move these rows into the next band. + QTest::newRow("99.9 %") << 0.999 << 10 << false << false; + QTest::newRow("399.9 %") << 3.999 << 10 << true << false; + QTest::newRow("401 %") << 4.01 << 5 << true << true; + QTest::newRow("799.9 %") << 7.999 << 5 << true << true; + QTest::newRow("999.9 %") << 9.999 << 2 << true << true; } void stepAtZoom()