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