summaryrefslogtreecommitdiffstats
path: root/src/ui/tools/marker-tool.cpp
blob: e2e2631d82935c412aed3184195bdd2d94e1353b (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
// SPDX-License-Identifier: GPL-2.0-or-later
/** @file
 * Marker edit mode - onCanvas marker editing of marker orientation, position, scale
 *//*
 * Authors:
 * see git history
 * Rachana Podaralla <rpodaralla3@gatech.edu>
 *
 * Copyright (C) 2018 Authors
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include "display/curve.h"

#include "desktop.h"
#include "document.h"
#include "style.h"
#include "message-context.h"
#include "selection.h"

#include "object/sp-path.h"
#include "object/sp-shape.h"
#include "object/sp-marker.h"

#include "ui/shape-editor.h"
#include "ui/tool/event-utils.h"
#include "ui/tool/multi-path-manipulator.h"
#include "ui/tool/path-manipulator.h"
#include "ui/tools/marker-tool.h"


namespace Inkscape {
namespace UI {
namespace Tools {

MarkerTool::MarkerTool(SPDesktop *desktop)
    : ToolBase(desktop, "/tools/marker", "select.svg")
{
    Inkscape::Selection *selection = desktop->getSelection();

    this->sel_changed_connection.disconnect();
    this->sel_changed_connection = selection->connectChanged(
    	sigc::mem_fun(this, &MarkerTool::selection_changed)
    );
    this->selection_changed(selection);

    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
    if (prefs->getBool("/tools/marker/selcue")) this->enableSelectionCue();
    if (prefs->getBool("/tools/marker/gradientdrag")) this->enableGrDrag();
}

MarkerTool::~MarkerTool()
{
    ungrabCanvasEvents();

    this->message_context->clear();
    this->_shape_editors.clear();

    this->enableGrDrag(false);
    this->sel_changed_connection.disconnect();
}

/*
- cycles through all the selected items to see if any have a marker in the right location (based on enterMarkerMode)
- if a matching item is found, loads the corresponding marker on the shape into the shape-editor and exits the loop
- forces user to only edit one marker at a time
*/
void MarkerTool::selection_changed(Inkscape::Selection *selection) {
    using namespace Inkscape::UI;

    g_assert(_desktop != nullptr);

    SPDocument *doc = _desktop->getDocument();
    g_assert(doc != nullptr);

    auto selected_items = selection->items();
    this->_shape_editors.clear();

    for(auto i = selected_items.begin(); i != selected_items.end(); ++i){
        SPItem *item = *i;

        if(item) {
            SPShape* shape = dynamic_cast<SPShape*>(item);

            if(shape && shape->hasMarkers() && (editMarkerMode != -1)) {
                SPObject *obj = shape->_marker[editMarkerMode];

                if(obj) {

                    SPMarker *sp_marker = dynamic_cast<SPMarker *>(obj);
                    g_assert(sp_marker != nullptr);

                    sp_validate_marker(sp_marker, doc);

                    ShapeRecord sr;
                    switch(editMarkerMode) {
                        case SP_MARKER_LOC_START:
                            sr  = get_marker_transform(shape, item, sp_marker, SP_MARKER_LOC_START);
                            break;

                        case SP_MARKER_LOC_MID:
                            sr  = get_marker_transform(shape, item, sp_marker, SP_MARKER_LOC_MID);
                            break;

                        case SP_MARKER_LOC_END:
                            sr  = get_marker_transform(shape, item, sp_marker, SP_MARKER_LOC_END);
                            break;

                        default:
                            break;
                    }

                    auto si = std::make_unique<ShapeEditor>(_desktop, sr.edit_transform, sr.edit_rotation, editMarkerMode);
                    si->set_item(dynamic_cast<SPItem *>(sr.object));

                    this->_shape_editors.insert({item, std::move(si)});
                    break;                     
                }
            }
        }
    }
}

// handles selection of new items
bool MarkerTool::root_handler(GdkEvent* event) {
    g_assert(_desktop != nullptr);

    Inkscape::Selection *selection = _desktop->getSelection();
    gint ret = false;
    
    switch (event->type) {
        case GDK_BUTTON_PRESS:
            if (event->button.button == 1) {

                Geom::Point const button_w(event->button.x, event->button.y);  
                this->item_to_select = sp_event_context_find_item (_desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE);

                grabCanvasEvents();
                ret = true;
            }
            break;
        case GDK_BUTTON_RELEASE:
            if (event->button.button == 1) {

                if (this->item_to_select) {
                    // unselect all items, except for newly selected item
                    selection->set(this->item_to_select);
                } else {
                    // clicked into empty space, deselect any selected items
                    selection->clear();
                }

                this->item_to_select = nullptr;
                ungrabCanvasEvents();
                ret = true;
            }
            break;
        default:
            break;
    }

    return (!ret? ToolBase::root_handler(event): ret);
}

/* 
- this function uses similar logic that exists in sp_shape_update_marker_view
- however, the tangent angle needs to be saved here and parent_item->i2dt_affine() needs to also be accounted for in the right places
- calculate where the shape-editor knotholders need to go based on the reference shape
*/
ShapeRecord MarkerTool::get_marker_transform(SPShape* shape, SPItem *parent_item, SPMarker *sp_marker, SPMarkerLoc marker_type)
{

    // scale marker transform with parent stroke width
    SPStyle *style = shape->style;
    SPDocument *doc = _desktop->getDocument();
    Geom::Scale scale = doc->getDocumentScale();

    if(sp_marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
        scale *= Geom::Scale(style->stroke_width.computed);
    }

    Geom::PathVector const &pathv = shape->curve()->get_pathvector();
    Geom::Affine ret = Geom::identity(); //edit_transform
    double angle = 0.0; // edit_rotation - tangent angle used for auto orientation
    Geom::Point p;
    
    if(marker_type == SP_MARKER_LOC_START) {

        Geom::Curve const &c = pathv.begin()->front();
        p = c.pointAt(0);
        ret = Geom::Translate(p * parent_item->i2doc_affine());

        if (!c.isDegenerate()) {
            Geom::Point tang = c.unitTangentAt(0);
            angle = Geom::atan2(tang);
            ret = Geom::Rotate(angle) * ret;
        }

    } else if(marker_type == SP_MARKER_LOC_MID) {
        /* 
        - a shape can have multiple mid markers - only one is needed
        - once a valid mid marker is found, save edit_transfom and edit_rotation and break out of loop
        */
        for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) {

            // mid marker start position
            if (path_it != pathv.begin() && ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)))
            {
                Geom::Curve const &c = path_it->front();
                p = c.pointAt(0);
                ret = Geom::Translate(p * parent_item->i2doc_affine());

                if (!c.isDegenerate()) {
                    Geom::Point tang = c.unitTangentAt(0);
                    angle = Geom::atan2(tang);
                    ret = Geom::Rotate(angle) * ret;
                    break;
                }
            }

            // mid marker mid positions
            if ( path_it->size_default() > 1) {
                Geom::Path::const_iterator curve_it1 = path_it->begin();
                Geom::Path::const_iterator curve_it2 = ++(path_it->begin());
                while (curve_it2 != path_it->end_default())
                {
                    Geom::Curve const & c1 = *curve_it1;
                    Geom::Curve const & c2 = *curve_it2;

                    p = c1.pointAt(1);
                    Geom::Curve * c1_reverse = c1.reverse();
                    Geom::Point tang1 = - c1_reverse->unitTangentAt(0);
                    delete c1_reverse;
                    Geom::Point tang2 = c2.unitTangentAt(0);

                    double const angle1 = Geom::atan2(tang1);
                    double const angle2 = Geom::atan2(tang2);

                    angle = .5 * (angle1 + angle2);

                    if ( fabs( angle2 - angle1 ) > M_PI ) {
                        angle += M_PI;
                    }

                    ret = Geom::Rotate(angle) * Geom::Translate(p * parent_item->i2doc_affine());

                    ++curve_it1;
                    ++curve_it2;
                    break;
                }
            }

            // mid marker end position
            if ( path_it != (pathv.end()-1) && !path_it->empty()) {
                Geom::Curve const &c = path_it->back_default();
                p = c.pointAt(1);
                ret = Geom::Translate(p * parent_item->i2doc_affine());

                if ( !c.isDegenerate() ) {
                    Geom::Curve * c_reverse = c.reverse();
                    Geom::Point tang = - c_reverse->unitTangentAt(0);
                    delete c_reverse;
                    angle = Geom::atan2(tang);
                    ret = Geom::Rotate(angle) * ret;
                    break;
                } 
            }
        }

    } else if (marker_type == SP_MARKER_LOC_END) {

        Geom::Path const &path_last = pathv.back();
        unsigned int index = path_last.size_default();
        if (index > 0) index--;

        Geom::Curve const &c = path_last[index];
        p = c.pointAt(1);
        ret = Geom::Translate(p * parent_item->i2doc_affine());

        if ( !c.isDegenerate() ) {
            Geom::Curve * c_reverse = c.reverse();
            Geom::Point tang = - c_reverse->unitTangentAt(0);
            delete c_reverse;
            angle = Geom::atan2(tang);
            ret = Geom::Rotate(angle) * ret;
        } 
    }

    /* scale by stroke width */
    ret = scale * ret;
    /* account for parent transform */
    ret = parent_item->transform.withoutTranslation() * ret;

    ShapeRecord sr;
    sr.object = sp_marker;
    sr.edit_transform = ret;
    sr.edit_rotation = angle * 180.0/M_PI;
    sr.role = SHAPE_ROLE_NORMAL;
    return sr;
}

}}}