View Issue Details

IDProjectCategoryView StatusLast Update
0004702ScribusUser Interfacepublic2026-09-02 05:57
Reporterchristoph_s Assigned To 
PrioritynormalSeverityfeatureReproducibilityalways
Status newResolutionopen 
PlatformanyOSanyOS Versionany
Product Version1.3 
Summary0004702: Snap guides to tick marks in the rulers
DescriptionWould be a welcome feature, especially with large zoom factors.
Tags#please_test, snapping
Patch

Activities

cbradney

2006-12-18 22:02

administrator   ~0014062

Not in 1.3.4

qirat

2026-09-02 05:57

reporter   ~0054397

I stumbled upon this and it is a good quality of life refinement.
Here is a patch that made it possible.

- Adds soft snapping of dragged guides to visible ruler ticks.
- Activates only when tick spacing is ≥ 10 px; snap radius is 3 px.
- Uses ruler-derived visible tick positions, respecting ruler origin/mode and page context.
- Preserves Snap to Items priority.
- Keeps preview/HUD and final guide position in sync.
- No new preference and no change to generic canvas coordinate conversion.
guide-ruler-tick-snapping-v1.0.patch (5,167 bytes)   
Index: scribus/canvasgesture_rulermove.cpp
===================================================================
--- scribus/canvasgesture_rulermove.cpp	(revision 27810)
+++ scribus/canvasgesture_rulermove.cpp	(working copy)
@@ -26,7 +26,14 @@
 #include "scribusdoc.h"
 #include "scribusview.h"
 #include "ui/guidemanager.h"
+#include "ui/hruler.h"
+#include "ui/vruler.h"
 
+namespace
+{
+constexpr double RulerTickMinSpacingPx = 10.0;
+constexpr double RulerTickSnapRadiusPx = 3.0;
+}
 
 RulerGesture::RulerGesture(ScribusView *view, RulerGesture::Mode mode) :
 	CanvasGesture(view),
@@ -199,6 +206,7 @@
 			if ((page >= 0) && (viewport.contains(m->globalPosition())))
 			{
 				ScPage* currentPage = m_doc->Pages->at(page);
+				bool snappedToItem = false;
 				if (m_doc->SnapItems)
 				{
 					double xout = 0;
@@ -207,9 +215,23 @@
 					if (yout != y)
 					{
 						y = yout;
+						snappedToItem = true;
 						newMousePoint = m_canvas->canvasToLocal(QPointF(x, y));
 					}
 				}
+				if (!snappedToItem)
+				{
+					const double tickSpacing = m_view->vertRuler->visibleTickSpacing();
+					if (tickSpacing * m_canvas->scale() >= RulerTickMinSpacingPx)
+					{
+						const double tick = m_view->vertRuler->nearestVisibleTick(y, currentPage);
+						if (qAbs(tick - y) * m_canvas->scale() <= RulerTickSnapRadiusPx)
+						{
+							y = tick;
+							newMousePoint = m_canvas->canvasToLocal(QPointF(x, y));
+						}
+					}
+				}
 				if (!m_haveGuide)
 				{
 					QApplication::changeOverrideCursor(QCursor(Qt::SplitVCursor));
@@ -267,6 +289,7 @@
 			if ((page >= 0) && viewport.contains(m->globalPosition()))
 			{
 				ScPage* currentPage = m_doc->Pages->at(page);
+				bool snappedToItem = false;
 				if (m_doc->SnapItems)
 				{
 					double xout = 0;
@@ -275,9 +298,23 @@
 					if (xout != x)
 					{
 						x = xout;
+						snappedToItem = true;
 						newMousePoint = m_canvas->canvasToLocal(QPointF(x, y));
 					}
 				}
+				if (!snappedToItem)
+				{
+					const double tickSpacing = m_view->horizRuler->visibleTickSpacing();
+					if (tickSpacing * m_canvas->scale() >= RulerTickMinSpacingPx)
+					{
+						const double tick = m_view->horizRuler->nearestVisibleTick(x);
+						if (qAbs(tick - x) * m_canvas->scale() <= RulerTickSnapRadiusPx)
+						{
+							x = tick;
+							newMousePoint = m_canvas->canvasToLocal(QPointF(x, y));
+						}
+					}
+				}
 				if (!m_haveGuide)
 				{
 					QApplication::changeOverrideCursor(QCursor(Qt::SplitHCursor));
Index: scribus/ui/hruler.h
===================================================================
--- scribus/ui/hruler.h	(revision 27810)
+++ scribus/ui/hruler.h	(working copy)
@@ -65,6 +65,8 @@
 	~Hruler() {}
 
 	double ruleSpacing() const;
+	double visibleTickSpacing() const;
+	double nearestVisibleTick(double canvasPos) const;
 	void   setItem(PageItem * item);
 	void   setRtl(bool rtl);
 	void   textMode(bool state) { m_textEditMode = state; }
Index: scribus/ui/hruler.cpp
===================================================================
--- scribus/ui/hruler.cpp	(revision 27810)
+++ scribus/ui/hruler.cpp	(working copy)
@@ -937,6 +937,23 @@
 	return m_iter;
 }
 
+double Hruler::visibleTickSpacing() const
+{
+	if (m_doc->unitIndex() == SC_C || m_doc->unitIndex() == SC_P)
+		return qMin(m_iter, m_iter2);
+	return qMin(m_iter, m_iter2 / 2.0);
+}
+
+double Hruler::nearestVisibleTick(double canvasPos) const
+{
+	const double spacing = visibleTickSpacing();
+	double origin = m_doc->rulerXoffset;
+	if (m_doc->guidesPrefs().rulerMode && m_doc->currentPage())
+		origin += m_doc->currentPage()->xOffset();
+
+	return origin + qRound((canvasPos - origin) / spacing) * spacing;
+}
+
 void Hruler::draw(int where)
 {
 	// erase old marker
Index: scribus/ui/vruler.h
===================================================================
--- scribus/ui/vruler.h	(revision 27810)
+++ scribus/ui/vruler.h	(working copy)
@@ -34,6 +34,7 @@
 class PageItem;
 class PrefsManager;
 class RulerGesture;
+class ScPage;
 class ScribusDoc;
 class ScribusView;
 
@@ -50,6 +51,8 @@
 	~Vruler() {}
 	
 	double ruleSpacing() const;
+	double visibleTickSpacing() const;
+	double nearestVisibleTick(double canvasPos, const ScPage* page) const;
 	void shift(double pos) { m_offset = pos; }
 	void shiftRel(double dist) { m_offset += dist; }
 
Index: scribus/ui/vruler.cpp
===================================================================
--- scribus/ui/vruler.cpp	(revision 27810)
+++ scribus/ui/vruler.cpp	(working copy)
@@ -295,6 +295,23 @@
 	return m_iter;
 }
 
+double Vruler::visibleTickSpacing() const
+{
+	return qMin(m_iter, m_iter2 / 2.0);
+}
+
+double Vruler::nearestVisibleTick(double canvasPos, const ScPage* page) const
+{
+	const double spacing = visibleTickSpacing();
+	double origin = m_doc->rulerYoffset;
+	if (perPageMode() && page)
+		origin = page->yOffset();
+	else if (m_doc->guidesPrefs().rulerMode && m_doc->currentPage())
+		origin += m_doc->currentPage()->yOffset();
+
+	return origin + qRound((canvasPos - origin) / spacing) * spacing;
+}
+
 void Vruler::draw(int where)
 {
 	// erase old marker

Issue History

Date Modified Username Field Change
2006-12-04 10:43 christoph_s New Issue
2006-12-18 22:02 cbradney Note Added: 0014062
2016-05-25 03:49 Kunda Tag Attached: snapping
2026-09-02 05:57 qirat Note Added: 0054397
2026-09-02 05:57 qirat File Added: guide-ruler-tick-snapping-v1.0.patch
2026-09-02 05:57 qirat Tag Attached: #please_test