1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Storing of snapping preferences.
4 *
5 * Authors:
6 * Diederik van Lierop <mail@diedenrezi.nl>
7 *
8 * Copyright (C) 2008 - 2011 Authors
9 *
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13 #include "inkscape.h"
14
SnapPreferences()15 Inkscape::SnapPreferences::SnapPreferences() :
16 _snap_enabled_globally(true),
17 _snap_postponed_globally(false),
18 _strict_snapping(true),
19 _snap_perp(false),
20 _snap_tang(false)
21 {
22 // Check for powers of two; see the comments in snap-enums.h
23 g_assert((SNAPTARGET_BBOX_CATEGORY != 0) && !(SNAPTARGET_BBOX_CATEGORY & (SNAPTARGET_BBOX_CATEGORY - 1)));
24 g_assert((SNAPTARGET_NODE_CATEGORY != 0) && !(SNAPTARGET_NODE_CATEGORY & (SNAPTARGET_NODE_CATEGORY - 1)));
25 g_assert((SNAPTARGET_DATUMS_CATEGORY != 0) && !(SNAPTARGET_DATUMS_CATEGORY & (SNAPTARGET_DATUMS_CATEGORY - 1)));
26 g_assert((SNAPTARGET_OTHERS_CATEGORY != 0) && !(SNAPTARGET_OTHERS_CATEGORY & (SNAPTARGET_OTHERS_CATEGORY - 1)));
27
28 for (int & _active_snap_target : _active_snap_targets) {
29 _active_snap_target = -1;
30 }
31 }
32
isAnyDatumSnappable() const33 bool Inkscape::SnapPreferences::isAnyDatumSnappable() const
34 {
35 return isTargetSnappable(SNAPTARGET_GUIDE, SNAPTARGET_GRID, SNAPTARGET_PAGE_BORDER);
36 }
37
isAnyCategorySnappable() const38 bool Inkscape::SnapPreferences::isAnyCategorySnappable() const
39 {
40 return isTargetSnappable(SNAPTARGET_NODE_CATEGORY, SNAPTARGET_BBOX_CATEGORY, SNAPTARGET_OTHERS_CATEGORY) || isTargetSnappable(SNAPTARGET_GUIDE, SNAPTARGET_GRID, SNAPTARGET_PAGE_BORDER);
41 }
42
_mapTargetToArrayIndex(Inkscape::SnapTargetType & target,bool & always_on,bool & group_on) const43 void Inkscape::SnapPreferences::_mapTargetToArrayIndex(Inkscape::SnapTargetType &target, bool &always_on, bool &group_on) const
44 {
45 if (target == SNAPTARGET_BBOX_CATEGORY ||
46 target == SNAPTARGET_NODE_CATEGORY ||
47 target == SNAPTARGET_OTHERS_CATEGORY ||
48 target == SNAPTARGET_DATUMS_CATEGORY) {
49 // These main targets should be handled separately, because otherwise we might call isTargetSnappable()
50 // for them (to check whether the corresponding group is on) which would lead to an infinite recursive loop
51 always_on = (target == SNAPTARGET_DATUMS_CATEGORY);
52 group_on = true;
53 return;
54 }
55
56 if (target & SNAPTARGET_BBOX_CATEGORY) {
57 group_on = isTargetSnappable(SNAPTARGET_BBOX_CATEGORY); // Only if the group with bbox sources/targets has been enabled, then we might snap to any of the bbox targets
58 return;
59 }
60
61 if (target & SNAPTARGET_NODE_CATEGORY) {
62 group_on = isTargetSnappable(SNAPTARGET_NODE_CATEGORY); // Only if the group with path/node sources/targets has been enabled, then we might snap to any of the nodes/paths
63 switch (target) {
64 case SNAPTARGET_RECT_CORNER:
65 target = SNAPTARGET_NODE_CUSP;
66 break;
67 case SNAPTARGET_ELLIPSE_QUADRANT_POINT:
68 target = SNAPTARGET_NODE_SMOOTH;
69 break;
70 case SNAPTARGET_PATH_GUIDE_INTERSECTION:
71 target = SNAPTARGET_PATH_INTERSECTION;
72 break;
73 case SNAPTARGET_PATH_PERPENDICULAR:
74 case SNAPTARGET_PATH_TANGENTIAL:
75 target = SNAPTARGET_PATH;
76 break;
77 default:
78 break;
79 }
80
81 return;
82 }
83
84 if (target & SNAPTARGET_DATUMS_CATEGORY) {
85 group_on = true; // These snap targets cannot be disabled as part of a disabled group;
86 switch (target) {
87 // Some snap targets don't have their own toggle. These targets are called "secondary targets". We will re-map
88 // them to their cousin which does have a toggle, and which is called a "primary target"
89 case SNAPTARGET_GRID_INTERSECTION:
90 case SNAPTARGET_GRID_PERPENDICULAR:
91 target = SNAPTARGET_GRID;
92 break;
93 case SNAPTARGET_GUIDE_INTERSECTION:
94 case SNAPTARGET_GUIDE_ORIGIN:
95 case SNAPTARGET_GUIDE_PERPENDICULAR:
96 target = SNAPTARGET_GUIDE;
97 break;
98 case SNAPTARGET_PAGE_CORNER:
99 target = SNAPTARGET_PAGE_BORDER;
100 break;
101
102 // Some snap targets cannot be toggled at all, and are therefore always enabled
103 case SNAPTARGET_GRID_GUIDE_INTERSECTION:
104 always_on = true; // Doesn't have it's own button
105 break;
106
107 // These are only listed for completeness
108 case SNAPTARGET_GRID:
109 case SNAPTARGET_GUIDE:
110 case SNAPTARGET_PAGE_BORDER:
111 case SNAPTARGET_DATUMS_CATEGORY:
112 break;
113 default:
114 g_warning("Snap-preferences warning: Undefined snap target (#%i)", target);
115 break;
116 }
117 return;
118 }
119
120 if (target & SNAPTARGET_OTHERS_CATEGORY) {
121 // Only if the group with "other" snap sources/targets has been enabled, then we might snap to any of those targets
122 // ... but this doesn't hold for the page border, grids, and guides
123 group_on = isTargetSnappable(SNAPTARGET_OTHERS_CATEGORY);
124 switch (target) {
125 // Some snap targets don't have their own toggle. These targets are called "secondary targets". We will re-map
126 // them to their cousin which does have a toggle, and which is called a "primary target"
127 case SNAPTARGET_TEXT_ANCHOR:
128 target = SNAPTARGET_TEXT_BASELINE;
129 break;
130
131 case SNAPTARGET_IMG_CORNER: // Doesn't have its own button, on if the group is on
132 target = SNAPTARGET_OTHERS_CATEGORY;
133 break;
134 // Some snap targets cannot be toggled at all, and are therefore always enabled
135 case SNAPTARGET_CONSTRAINED_ANGLE:
136 case SNAPTARGET_CONSTRAINT:
137 always_on = true; // Doesn't have it's own button
138 break;
139
140 // These are only listed for completeness
141 case SNAPTARGET_OBJECT_MIDPOINT:
142 case SNAPTARGET_ROTATION_CENTER:
143 case SNAPTARGET_TEXT_BASELINE:
144 case SNAPTARGET_OTHERS_CATEGORY:
145 break;
146 default:
147 g_warning("Snap-preferences warning: Undefined snap target (#%i)", target);
148 break;
149 }
150
151 return;
152 }
153
154 if (target == SNAPTARGET_UNDEFINED ) {
155 g_warning("Snap-preferences warning: Undefined snaptarget (#%i)", target);
156 } else {
157 g_warning("Snap-preferences warning: Snaptarget not handled (#%i)", target);
158 }
159 }
160
setTargetSnappable(Inkscape::SnapTargetType const target,bool enabled)161 void Inkscape::SnapPreferences::setTargetSnappable(Inkscape::SnapTargetType const target, bool enabled)
162 {
163 bool always_on = false;
164 bool group_on = false; // Only needed as a dummy
165 Inkscape::SnapTargetType index = target;
166
167 _mapTargetToArrayIndex(index, always_on, group_on);
168
169 if (always_on) { // If true, then this snap target is always active and cannot be toggled
170 // Catch coding errors
171 g_warning("Snap-preferences warning: Trying to enable/disable a snap target (#%i) that's always on by definition", index);
172 } else {
173 if (index == target) { // I.e. if it has not been re-mapped, then we have a primary target at hand
174 _active_snap_targets[index] = enabled;
175 } else { // If it has been re-mapped though, then this target does not have its own toggle button and should therefore not be set
176 g_warning("Snap-preferences warning: Trying to enable/disable a secondary snap target (#%i); only primary targets can be set", index);
177 }
178 }
179 }
180
isTargetSnappable(Inkscape::SnapTargetType const target) const181 bool Inkscape::SnapPreferences::isTargetSnappable(Inkscape::SnapTargetType const target) const
182 {
183 bool always_on = false;
184 bool group_on = false;
185 Inkscape::SnapTargetType index = target;
186
187 _mapTargetToArrayIndex(index, always_on, group_on);
188
189 if (group_on) { // If true, then this snap target is in a snap group that has been enabled (e.g. bbox group, nodes/paths group, or "others" group
190 if (always_on) { // If true, then this snap target is always active and cannot be toggled
191 return true;
192 } else {
193 if (_active_snap_targets[index] == -1) {
194 // Catch coding errors
195 g_warning("Snap-preferences warning: Using an uninitialized snap target setting (#%i)", index);
196 // This happens if setTargetSnappable() has not been called for this parameter, e.g. from within sp_namedview_set,
197 // or if this target index doesn't exist at all
198 }
199 return _active_snap_targets[index];
200 }
201 } else {
202 return false;
203 }
204 }
205
isTargetSnappable(Inkscape::SnapTargetType const target1,Inkscape::SnapTargetType const target2) const206 bool Inkscape::SnapPreferences::isTargetSnappable(Inkscape::SnapTargetType const target1, Inkscape::SnapTargetType const target2) const {
207 return isTargetSnappable(target1) || isTargetSnappable(target2);
208 }
209
isTargetSnappable(Inkscape::SnapTargetType const target1,Inkscape::SnapTargetType const target2,Inkscape::SnapTargetType const target3) const210 bool Inkscape::SnapPreferences::isTargetSnappable(Inkscape::SnapTargetType const target1, Inkscape::SnapTargetType const target2, Inkscape::SnapTargetType const target3) const {
211 return isTargetSnappable(target1) || isTargetSnappable(target2) || isTargetSnappable(target3);
212 }
213
isTargetSnappable(Inkscape::SnapTargetType const target1,Inkscape::SnapTargetType const target2,Inkscape::SnapTargetType const target3,Inkscape::SnapTargetType const target4) const214 bool Inkscape::SnapPreferences::isTargetSnappable(Inkscape::SnapTargetType const target1, Inkscape::SnapTargetType const target2, Inkscape::SnapTargetType const target3, Inkscape::SnapTargetType const target4) const {
215 return isTargetSnappable(target1) || isTargetSnappable(target2) || isTargetSnappable(target3) || isTargetSnappable(target4);
216 }
217
isTargetSnappable(Inkscape::SnapTargetType const target1,Inkscape::SnapTargetType const target2,Inkscape::SnapTargetType const target3,Inkscape::SnapTargetType const target4,Inkscape::SnapTargetType const target5) const218 bool Inkscape::SnapPreferences::isTargetSnappable(Inkscape::SnapTargetType const target1, Inkscape::SnapTargetType const target2, Inkscape::SnapTargetType const target3, Inkscape::SnapTargetType const target4, Inkscape::SnapTargetType const target5) const {
219 return isTargetSnappable(target1) || isTargetSnappable(target2) || isTargetSnappable(target3) || isTargetSnappable(target4) || isTargetSnappable(target5);
220 }
221
isSnapButtonEnabled(Inkscape::SnapTargetType const target) const222 bool Inkscape::SnapPreferences::isSnapButtonEnabled(Inkscape::SnapTargetType const target) const
223 {
224 bool always_on = false; // Only needed as a dummy
225 bool group_on = false; // Only needed as a dummy
226 Inkscape::SnapTargetType index = target;
227
228 _mapTargetToArrayIndex(index, always_on, group_on);
229
230 if (_active_snap_targets[index] == -1) {
231 // Catch coding errors
232 g_warning("Snap-preferences warning: Using an uninitialized snap target setting (#%i)", index);
233 // This happens if setTargetSnappable() has not been called for this parameter, e.g. from within sp_namedview_set,
234 // or if this target index doesn't exist at all
235 } else {
236 if (index == target) { // I.e. if it has not been re-mapped, then we have a primary target at hand, which does have its own toggle button
237 return _active_snap_targets[index];
238 } else { // If it has been re-mapped though, then this target does not have its own toggle button and therefore the button status cannot be read
239 g_warning("Snap-preferences warning: Trying to determine the button status of a secondary snap target (#%i); However, only primary targets have a button", index);
240 }
241 }
242
243 return false;
244 }
245
source2target(Inkscape::SnapSourceType source) const246 Inkscape::SnapTargetType Inkscape::SnapPreferences::source2target(Inkscape::SnapSourceType source) const
247 {
248 switch (source)
249 {
250 case SNAPSOURCE_UNDEFINED:
251 return SNAPTARGET_UNDEFINED;
252 case SNAPSOURCE_BBOX_CATEGORY:
253 return SNAPTARGET_BBOX_CATEGORY;
254 case SNAPSOURCE_BBOX_CORNER:
255 return SNAPTARGET_BBOX_CORNER;
256 case SNAPSOURCE_BBOX_MIDPOINT:
257 return SNAPTARGET_BBOX_MIDPOINT;
258 case SNAPSOURCE_BBOX_EDGE_MIDPOINT:
259 return SNAPTARGET_BBOX_EDGE_MIDPOINT;
260 case SNAPSOURCE_NODE_CATEGORY:
261 return SNAPTARGET_NODE_CATEGORY;
262 case SNAPSOURCE_NODE_SMOOTH:
263 return SNAPTARGET_NODE_SMOOTH;
264 case SNAPSOURCE_NODE_CUSP:
265 return SNAPTARGET_NODE_CUSP;
266 case SNAPSOURCE_LINE_MIDPOINT:
267 return SNAPTARGET_LINE_MIDPOINT;
268 case SNAPSOURCE_PATH_INTERSECTION:
269 return SNAPTARGET_PATH_INTERSECTION;
270 case SNAPSOURCE_RECT_CORNER:
271 return SNAPTARGET_RECT_CORNER;
272 case SNAPSOURCE_ELLIPSE_QUADRANT_POINT:
273 return SNAPTARGET_ELLIPSE_QUADRANT_POINT;
274 case SNAPSOURCE_DATUMS_CATEGORY:
275 return SNAPTARGET_DATUMS_CATEGORY;
276 case SNAPSOURCE_GUIDE:
277 return SNAPTARGET_GUIDE;
278 case SNAPSOURCE_GUIDE_ORIGIN:
279 return SNAPTARGET_GUIDE_ORIGIN;
280 case SNAPSOURCE_OTHERS_CATEGORY:
281 return SNAPTARGET_OTHERS_CATEGORY;
282 case SNAPSOURCE_ROTATION_CENTER:
283 return SNAPTARGET_ROTATION_CENTER;
284 case SNAPSOURCE_OBJECT_MIDPOINT:
285 return SNAPTARGET_OBJECT_MIDPOINT;
286 case SNAPSOURCE_IMG_CORNER:
287 return SNAPTARGET_IMG_CORNER;
288 case SNAPSOURCE_TEXT_ANCHOR:
289 return SNAPTARGET_TEXT_ANCHOR;
290
291 case SNAPSOURCE_NODE_HANDLE:
292 case SNAPSOURCE_OTHER_HANDLE:
293 case SNAPSOURCE_CONVEX_HULL_CORNER:
294 // For these snapsources there doesn't exist an equivalent snap target
295 return SNAPTARGET_NODE_CATEGORY;
296 case SNAPSOURCE_GRID_PITCH:
297 return SNAPTARGET_GRID;
298 default:
299 g_warning("Mapping of snap source to snap target undefined");
300 return SNAPTARGET_UNDEFINED;
301 }
302 }
303
isSourceSnappable(Inkscape::SnapSourceType const source) const304 bool Inkscape::SnapPreferences::isSourceSnappable(Inkscape::SnapSourceType const source) const
305 {
306 return isTargetSnappable(source2target(source));
307 }
308
309
310 /*
311 Local Variables:
312 mode:c++
313 c-file-style:"stroustrup"
314 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
315 indent-tabs-mode:nil
316 fill-column:99
317 End:
318 */
319 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
320