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"