summaryrefslogtreecommitdiffstats
path: root/src/snap-preferences.cpp
blob: f72220ef6d1c49c0b385a6c4587580690806fceb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Storing of snapping preferences.
 *
 * Authors:
 *   Diederik van Lierop <mail@diedenrezi.nl>
 *
 * Copyright (C) 2008 - 2011 Authors
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include "inkscape.h"
#include "snap-preferences.h"

Inkscape::SnapPreferences::SnapPreferences() :
    _snap_enabled_globally(true),
    _snap_postponed_globally(false),
    _strict_snapping(true)
{
    // Check for powers of two; see the comments in snap-enums.h
    g_assert((SNAPTARGET_BBOX_CATEGORY != 0) && !(SNAPTARGET_BBOX_CATEGORY & (SNAPTARGET_BBOX_CATEGORY - 1)));
    g_assert((SNAPTARGET_DISTRIBUTION_CATEGORY != 0) && !(SNAPTARGET_DISTRIBUTION_CATEGORY & (SNAPTARGET_DISTRIBUTION_CATEGORY - 1)));
    g_assert((SNAPTARGET_ALIGNMENT_CATEGORY != 0) && !(SNAPTARGET_ALIGNMENT_CATEGORY & (SNAPTARGET_ALIGNMENT_CATEGORY - 1)));
    g_assert((SNAPTARGET_NODE_CATEGORY != 0) && !(SNAPTARGET_NODE_CATEGORY & (SNAPTARGET_NODE_CATEGORY - 1)));
    g_assert((SNAPTARGET_DATUMS_CATEGORY != 0) && !(SNAPTARGET_DATUMS_CATEGORY & (SNAPTARGET_DATUMS_CATEGORY - 1)));
    g_assert((SNAPTARGET_OTHERS_CATEGORY != 0) && !(SNAPTARGET_OTHERS_CATEGORY & (SNAPTARGET_OTHERS_CATEGORY - 1)));

    for (int & _active_snap_target : _active_snap_targets) {
        _active_snap_target = -1;
    }
    clearTargetMask();

    for (bool& b : _simple_snapping) {
        b = false;
    }
}

bool Inkscape::SnapPreferences::get_simple_snap(Inkscape::SimpleSnap option) const {
    auto index = static_cast<size_t>(option);
    assert(index < size_t(Inkscape::SimpleSnap::_MaxEnumValue));
    return _simple_snapping[index];
}

void Inkscape::SnapPreferences::set_simple_snap(Inkscape::SimpleSnap option, bool enable) {
    auto index = static_cast<size_t>(option);
    assert(index < size_t(Inkscape::SimpleSnap::_MaxEnumValue));
    _simple_snapping[index] = enable;
}

bool Inkscape::SnapPreferences::isAnyDatumSnappable() const
{
    return isTargetSnappable(SNAPTARGET_GUIDE, SNAPTARGET_GRID, SNAPTARGET_PAGE_BORDER);
}

bool Inkscape::SnapPreferences::isAnyCategorySnappable() const
{
    return isTargetSnappable(SNAPTARGET_NODE_CATEGORY, SNAPTARGET_BBOX_CATEGORY, SNAPTARGET_OTHERS_CATEGORY) || isTargetSnappable(SNAPTARGET_GUIDE, SNAPTARGET_GRID, SNAPTARGET_PAGE_BORDER);
}

void Inkscape::SnapPreferences::_mapTargetToArrayIndex(Inkscape::SnapTargetType &target, bool &always_on, bool &group_on) const
{
    if (target == SNAPTARGET_BBOX_CATEGORY ||
            target == SNAPTARGET_NODE_CATEGORY ||
            target == SNAPTARGET_OTHERS_CATEGORY ||
            target == SNAPTARGET_DATUMS_CATEGORY ||
            target == SNAPTARGET_ALIGNMENT_CATEGORY ||
            target == SNAPTARGET_DISTRIBUTION_CATEGORY) {
        // These main targets should be handled separately, because otherwise we might call isTargetSnappable()
        // for them (to check whether the corresponding group is on) which would lead to an infinite recursive loop
        always_on = (target == SNAPTARGET_DATUMS_CATEGORY);
        group_on = true;
        return;
    }

    if (target & SNAPTARGET_BBOX_CATEGORY) {
        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
        return;
    }

    if (target & SNAPTARGET_NODE_CATEGORY) {
        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
        switch (target) {
            case SNAPTARGET_RECT_CORNER:
                target = SNAPTARGET_NODE_CUSP;
                break;
            case SNAPTARGET_ELLIPSE_QUADRANT_POINT:
                target = SNAPTARGET_NODE_SMOOTH;
                break;
            case SNAPTARGET_PATH_GUIDE_INTERSECTION:
                target = SNAPTARGET_PATH_INTERSECTION;
                break;
            // case SNAPTARGET_PATH_PERPENDICULAR:
            // case SNAPTARGET_PATH_TANGENTIAL:
                // target = SNAPTARGET_PATH;
                break;
            default:
                break;
        }

        return;
    }

    if (target & SNAPTARGET_DATUMS_CATEGORY) {
        group_on = true; // These snap targets cannot be disabled as part of a disabled group;
        switch (target) {
            // Some snap targets don't have their own toggle. These targets are called "secondary targets". We will re-map
            // them to their cousin which does have a toggle, and which is called a "primary target"
            case SNAPTARGET_GRID_INTERSECTION:
            case SNAPTARGET_GRID_PERPENDICULAR:
                target = SNAPTARGET_GRID;
                break;
            case SNAPTARGET_GUIDE_INTERSECTION:
            case SNAPTARGET_GUIDE_ORIGIN:
            case SNAPTARGET_GUIDE_PERPENDICULAR:
                target = SNAPTARGET_GUIDE;
                break;
            case SNAPTARGET_PAGE_CORNER:
            case SNAPTARGET_PAGE_CENTER:
                target = SNAPTARGET_PAGE_BORDER;
                break;

            // Some snap targets cannot be toggled at all, and are therefore always enabled
            case SNAPTARGET_GRID_GUIDE_INTERSECTION:
                always_on = true; // Doesn't have it's own button
                break;

            // These are only listed for completeness
            case SNAPTARGET_GRID:
            case SNAPTARGET_GUIDE:
            case SNAPTARGET_PAGE_BORDER:
            case SNAPTARGET_DATUMS_CATEGORY:
                break;
            default:
                g_warning("Snap-preferences warning: Undefined snap target (#%i)", target);
                break;
        }
        return;
    }

    if (target & SNAPTARGET_ALIGNMENT_CATEGORY) {
        group_on = isTargetSnappable(SNAPTARGET_ALIGNMENT_CATEGORY);
        return;
    }

    if (target & SNAPTARGET_DISTRIBUTION_CATEGORY) {
        group_on = isTargetSnappable(SNAPTARGET_DISTRIBUTION_CATEGORY);
        return;
    }

    if (target & SNAPTARGET_OTHERS_CATEGORY) {
        // Only if the group with "other" snap sources/targets has been enabled, then we might snap to any of those targets
        // ... but this doesn't hold for the page border, grids, and guides
        group_on = isTargetSnappable(SNAPTARGET_OTHERS_CATEGORY);
        switch (target) {
            // Some snap targets don't have their own toggle. These targets are called "secondary targets". We will re-map
            // them to their cousin which does have a toggle, and which is called a "primary target"
            case SNAPTARGET_TEXT_ANCHOR:
                target = SNAPTARGET_TEXT_BASELINE;
                break;

            case SNAPTARGET_IMG_CORNER: // Doesn't have its own button, on if the group is on
                target = SNAPTARGET_OTHERS_CATEGORY;
                break;
            // Some snap targets cannot be toggled at all, and are therefore always enabled
            case SNAPTARGET_CONSTRAINED_ANGLE:
            case SNAPTARGET_CONSTRAINT:
                always_on = true; // Doesn't have it's own button
                break;

            // These are only listed for completeness
            case SNAPTARGET_OBJECT_MIDPOINT:
            case SNAPTARGET_ROTATION_CENTER:
            case SNAPTARGET_TEXT_BASELINE:
            case SNAPTARGET_OTHERS_CATEGORY:
                break;
            default:
                g_warning("Snap-preferences warning: Undefined snap target (#%i)", target);
                break;
        }

        return;
    }

    if (target == SNAPTARGET_UNDEFINED ) {
        g_warning("Snap-preferences warning: Undefined snaptarget (#%i)", target);
    } else {
        g_warning("Snap-preferences warning: Snaptarget not handled (#%i)", target);
    }
}

void Inkscape::SnapPreferences::setTargetSnappable(Inkscape::SnapTargetType const target, bool enabled)
{
    bool always_on = false;
    bool group_on = false; // Only needed as a dummy
    Inkscape::SnapTargetType index = target;

    _mapTargetToArrayIndex(index, always_on, group_on);

    if (always_on) { // If true, then this snap target is always active and cannot be toggled
        // Catch coding errors
        g_warning("Snap-preferences warning: Trying to enable/disable a snap target (#%i) that's always on by definition", index);
    } else {
        if (index == target) { // I.e. if it has not been re-mapped, then we have a primary target at hand
            _active_snap_targets[index] = enabled;
        } else { // If it has been re-mapped though, then this target does not have its own toggle button and should therefore not be set
            g_warning("Snap-preferences warning: Trying to enable/disable a secondary snap target (#%i); only primary targets can be set", index);
        }
    }
}

/**
 * Set a target mask, which will turn off all other targets except the masked ones.
 *
 * target - The Snap Target to change the mask for.
 * enabled - The mask setting to set.
 *    -1 means use user settings (turn off mask)
 *     0 means mask with disabled,
 *     1 means mask with enabled,
 */
void Inkscape::SnapPreferences::setTargetMask(Inkscape::SnapTargetType const target, int enabled)
{
    bool always_on = false;
    bool group_on = false; // Only needed as a dummy
    Inkscape::SnapTargetType index = target;

    _mapTargetToArrayIndex(index, always_on, group_on);

    _active_mask_targets[index] = enabled;
}

/**
 * Clear the target mask, this should be done in a four step process.
 *
 * snap_manager->snaprepfs->clearTargetMask(0); // Default all options to disabled
 * snap_manager->snaprepfs->setTargetMask(SOME_TARGET, 1);
 * snap_manager->freeSnap(...);
 * snap_manager->snaprepfs->clearTargetMask(); // Turns off masking
 */
void Inkscape::SnapPreferences::clearTargetMask(int enabled)
{
    for (int & _active_mask_targets : _active_mask_targets) {
        _active_mask_targets = enabled;
    }
}

bool Inkscape::SnapPreferences::isTargetSnappable(Inkscape::SnapTargetType const target) const
{
    bool always_on = false;
    bool group_on = false;
    Inkscape::SnapTargetType index = target;

    _mapTargetToArrayIndex(index, always_on, group_on);

    // Check masking first, it over-rides even group_on
    if (_active_mask_targets[index] != -1) {
        return _active_mask_targets[index];
    }

    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
        if (always_on) { // If true, then this snap target is always active and cannot be toggled
            return true;
        } else {
            if (_active_snap_targets[index] == -1) {
                // Catch coding errors
                g_warning("Snap-preferences warning: Using an uninitialized snap target setting (#%i)", index);
                // This happens if setTargetSnappable() has not been called for this parameter, e.g. from within sp_namedview_set,
                // or if this target index doesn't exist at all
            }
            return _active_snap_targets[index];
        }
    } else {
        return false;
    }
}

bool Inkscape::SnapPreferences::isTargetSnappable(Inkscape::SnapTargetType const target1, Inkscape::SnapTargetType const target2) const {
    return isTargetSnappable(target1) || isTargetSnappable(target2);
}

bool Inkscape::SnapPreferences::isTargetSnappable(Inkscape::SnapTargetType const target1, Inkscape::SnapTargetType const target2, Inkscape::SnapTargetType const target3) const {
    return isTargetSnappable(target1) || isTargetSnappable(target2) || isTargetSnappable(target3);
}

bool Inkscape::SnapPreferences::isTargetSnappable(Inkscape::SnapTargetType const target1, Inkscape::SnapTargetType const target2, Inkscape::SnapTargetType const target3, Inkscape::SnapTargetType const target4) const {
    return isTargetSnappable(target1) || isTargetSnappable(target2) || isTargetSnappable(target3) || isTargetSnappable(target4);
}

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 {
    return isTargetSnappable(target1) || isTargetSnappable(target2) || isTargetSnappable(target3) || isTargetSnappable(target4) || isTargetSnappable(target5);
}

bool Inkscape::SnapPreferences::isSnapButtonEnabled(Inkscape::SnapTargetType const target) const
{
    bool always_on = false; // Only needed as a dummy
    bool group_on = false; // Only needed as a dummy
    Inkscape::SnapTargetType index = target;

    _mapTargetToArrayIndex(index, always_on, group_on);

    if (_active_snap_targets[index] == -1) {
        // Catch coding errors
        g_warning("Snap-preferences warning: Using an uninitialized snap target setting (#%i)", index);
        // This happens if setTargetSnappable() has not been called for this parameter, e.g. from within sp_namedview_set,
        // or if this target index doesn't exist at all
    } else {
        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
            return _active_snap_targets[index];
        } 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
            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);
        }
    }

    return false;
}

Inkscape::SnapTargetType Inkscape::SnapPreferences::source2target(Inkscape::SnapSourceType source) const
{
    switch (source)
    {
        case SNAPSOURCE_UNDEFINED:
            return SNAPTARGET_UNDEFINED;
        case SNAPSOURCE_BBOX_CATEGORY:
            return SNAPTARGET_BBOX_CATEGORY;
        case SNAPSOURCE_BBOX_CORNER:
            return SNAPTARGET_BBOX_CORNER;
        case SNAPSOURCE_BBOX_MIDPOINT:
            return SNAPTARGET_BBOX_MIDPOINT;
        case SNAPSOURCE_BBOX_EDGE_MIDPOINT:
            return SNAPTARGET_BBOX_EDGE_MIDPOINT;
        case SNAPSOURCE_NODE_CATEGORY:
            return SNAPTARGET_NODE_CATEGORY;
        case SNAPSOURCE_NODE_SMOOTH:
            return SNAPTARGET_NODE_SMOOTH;
        case SNAPSOURCE_NODE_CUSP:
            return SNAPTARGET_NODE_CUSP;
        case SNAPSOURCE_LINE_MIDPOINT:
            return SNAPTARGET_LINE_MIDPOINT;
        case SNAPSOURCE_PATH_INTERSECTION:
            return SNAPTARGET_PATH_INTERSECTION;
        case SNAPSOURCE_RECT_CORNER:
            return SNAPTARGET_RECT_CORNER;
        case SNAPSOURCE_ELLIPSE_QUADRANT_POINT:
            return SNAPTARGET_ELLIPSE_QUADRANT_POINT;
        case SNAPSOURCE_DATUMS_CATEGORY:
            return SNAPTARGET_DATUMS_CATEGORY;
        case SNAPSOURCE_GUIDE:
            return SNAPTARGET_GUIDE;
        case SNAPSOURCE_GUIDE_ORIGIN:
            return SNAPTARGET_GUIDE_ORIGIN;
        case SNAPSOURCE_OTHERS_CATEGORY:
            return SNAPTARGET_OTHERS_CATEGORY;
        case SNAPSOURCE_ROTATION_CENTER:
            return SNAPTARGET_ROTATION_CENTER;
        case SNAPSOURCE_OBJECT_MIDPOINT:
            return SNAPTARGET_OBJECT_MIDPOINT;
        case SNAPSOURCE_IMG_CORNER:
            return SNAPTARGET_IMG_CORNER;
        case SNAPSOURCE_TEXT_ANCHOR:
            return SNAPTARGET_TEXT_ANCHOR;

        case SNAPSOURCE_NODE_HANDLE:
        case SNAPSOURCE_OTHER_HANDLE:
        case SNAPSOURCE_CONVEX_HULL_CORNER:
            // For these snapsources there doesn't exist an equivalent snap target
            return SNAPTARGET_NODE_CATEGORY;
        case SNAPSOURCE_GRID_PITCH:
            return SNAPTARGET_GRID;

        case SNAPSOURCE_PAGE_CORNER:
            return SNAPTARGET_PAGE_CORNER;
        case SNAPSOURCE_PAGE_CENTER:
            return SNAPTARGET_PAGE_CENTER;

        case SNAPSOURCE_ALIGNMENT_CATEGORY:
            return SNAPTARGET_ALIGNMENT_CATEGORY;
        case SNAPSOURCE_ALIGNMENT_BBOX_CORNER:
            return SNAPTARGET_ALIGNMENT_BBOX_CORNER;
        case SNAPSOURCE_ALIGNMENT_BBOX_MIDPOINT:
            return SNAPTARGET_ALIGNMENT_BBOX_EDGE_MIDPOINT;
        case SNAPSOURCE_ALIGNMENT_BBOX_EDGE_MIDPOINT:
            return SNAPTARGET_ALIGNMENT_BBOX_EDGE_MIDPOINT;
        case SNAPSOURCE_ALIGNMENT_PAGE_CENTER:
            return SNAPTARGET_ALIGNMENT_PAGE_CENTER;
        case SNAPSOURCE_ALIGNMENT_PAGE_CORNER:
            return SNAPTARGET_ALIGNMENT_PAGE_CORNER;
        case Inkscape::SNAPSOURCE_ALIGNMENT_HANDLE:
            return SNAPTARGET_ALIGNMENT_HANDLE;

        default:
            g_warning("Mapping of snap source to snap target undefined (#%i)", source);
            return SNAPTARGET_UNDEFINED;
    }
}

bool Inkscape::SnapPreferences::isSourceSnappable(Inkscape::SnapSourceType const source) const
{
    return isTargetSnappable(source2target(source));
}


/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :