git.s-ol.nu inkscape / e892be0
add api and input modules for the livecoding tool s-ol 2 years ago
8 changed file(s) with 619 addition(s) and 1425 deletion(s). Raw diff Collapse all Expand all
277277 add_subdirectory(widgets)
278278 add_subdirectory(xml)
279279 add_subdirectory(2geom)
280 add_subdirectory(livecode)
280281
281282 # Directories containing lists files that describe building internal libraries
282283 add_subdirectory(3rdparty)
0 # SPDX-License-Identifier: GPL-2.0-or-later
1
2 set(livecode_SRC
3 api.cpp
4 input.cpp
5
6 # HEADERS
7 api.h
8 input.h
9 )
10
11 add_inkscape_source("${livecode_SRC}")
0 // SPDX-License-Identifier: GPL-2.0-or-later
1 /*
2 * API for the livecoding script language
3 *
4 * Authors:
5 * Sol Bekic <s+inkscape@s-ol.nu>
6 * Copyright (C) 2019 Authors
7 *
8 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
9 */
10
11 #include "svg/stringstream.h"
12 #include "svg/svg-color.h"
13 #include "svg/svg.h"
14 #include "document.h"
15 #include "display/sp-canvas-item.h"
16
17 #include "livecode/api.h"
18
19 namespace Inkscape {
20 namespace Livecode {
21
22 API::API(SPDesktop *desktop)
23 : desktop(desktop)
24 , doc_root(nullptr)
25 , ui_root(nullptr)
26 , _mouse(*this)
27 {
28 }
29
30 API::~API() {
31 if (doc_root) {
32 doc_root->deleteObject(true, true);
33 doc_root = nullptr;
34 }
35
36 if (ui_root) {
37 ui_root->deleteObject(true, true);
38 ui_root = nullptr;
39 }
40 }
41
42 Inkscape::XML::Node *API::make_rect(Geom::Rect const &rect, SPCSSAttr *css) {
43 SPDocument *doc = desktop->getDocument();
44 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
45
46 Inkscape::XML::Node *repr = xml_doc->createElement("svg:rect");
47
48 sp_repr_set_svg_double(repr, "x", rect.left());
49 sp_repr_set_svg_double(repr, "y", rect.top());
50 sp_repr_set_svg_double(repr, "width", rect.width());
51 sp_repr_set_svg_double(repr, "height", rect.height());
52
53 if (css) {
54 Glib::ustring css_str;
55 sp_repr_css_write_string(css, css_str);
56 sp_repr_css_attr_unref(css);
57 repr->setAttribute("style", css_str.c_str());
58 }
59
60 return repr;
61 }
62
63 Inkscape::XML::Node *API::make_line(Geom::Point const &p1, Geom::Point const &p2, SPCSSAttr *css) {
64 SPDocument *doc = desktop->getDocument();
65 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
66
67 Inkscape::XML::Node *repr = xml_doc->createElement("svg:line");
68
69 sp_repr_set_svg_double(repr, "x1", p1.x());
70 sp_repr_set_svg_double(repr, "y1", p1.y());
71 sp_repr_set_svg_double(repr, "x2", p2.x());
72 sp_repr_set_svg_double(repr, "y2", p2.y());
73
74 if (css) {
75 Glib::ustring css_str;
76 sp_repr_css_write_string(css, css_str);
77 sp_repr_css_attr_unref(css);
78 repr->setAttribute("style", css_str.c_str());
79 }
80
81 return repr;
82 }
83
84 Inkscape::XML::Node *API::make_path(Glib::ustring d, Geom::Point const &pos, SPCSSAttr *css) {
85 SPDocument *doc = desktop->getDocument();
86 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
87
88 Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
89
90 sp_repr_set_svg_double(repr, "x", pos.x());
91 sp_repr_set_svg_double(repr, "y", pos.y());
92 repr->setAttribute("d", d.c_str());
93
94 if (css) {
95 Glib::ustring css_str;
96 sp_repr_css_write_string(css, css_str);
97 sp_repr_css_attr_unref(css);
98 repr->setAttribute("style", css_str.c_str());
99 }
100
101 return repr;
102 }
103
104 Inkscape::XML::Node *API::make_arrow(Geom::Point const &from, Geom::Point const &to, SPCSSAttr *css) {
105 SPDocument *doc = desktop->getDocument();
106 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
107
108 Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
109
110 Geom::Point back = from - to;
111 back.normalize();
112 Geom::Point const cross_a = back.cw() + back * 2.0;
113 Geom::Point const cross_b = back.cw() + back * 2.0;
114
115 gchar* d = g_strdup_printf("M %f,%f %f,%f l %f,%f M %f,%f l %f,%f",
116 from.x(), from.y(),
117 to.x(), to.y(),
118 cross_a.x(), cross_a.y(),
119 to.x(), to.y(),
120 cross_b.x(), cross_b.y());
121 repr->setAttribute("d", d);
122 g_free(d);
123
124 Glib::ustring css_str;
125 if (css) {
126 sp_repr_css_write_string(css, css_str);
127 sp_repr_css_attr_unref(css);
128 } else {
129 css_str = "stroke: #000000;";
130 }
131 repr->setAttribute("style", css_str.c_str());
132
133 return repr;
134 }
135
136 namespace {
137 SPCSSAttr *handle_style() {
138 SPCSSAttr *css = sp_repr_css_attr_new();
139 sp_repr_css_set_property(css, "fill", "#ffffff");
140 sp_repr_css_set_property(css, "stroke-width", "0.2");
141 sp_repr_css_set_property(css, "stroke", "#000000");
142
143 return css;
144 }
145 }
146
147 bool API::input_point(Glib::ustring const &id, Geom::Point *point) {
148 auto const half_handle_size = Geom::Point(2, 2);
149 auto ui_point = *point * desktop->w2d().inverse();
150 Geom::Rect const rect(ui_point - half_handle_size, ui_point + half_handle_size);
151
152 SPCSSAttr *css = handle_style();
153 guint32 color = 0xffaaaaff;
154
155 if (rect.interiorContains(_mouse.ui_pos())) {
156 hot = id;
157 sp_repr_css_set_property(css, "fill", "#ff0000");
158 }
159
160 if (hot == id && _mouse.pressed()) {
161 active = id;
162 }
163
164 bool change = false;
165
166 if (active == id) {
167 sp_repr_css_set_property(css, "fill", "#0000ff");
168
169 auto const delta = _mouse.delta();
170 change = !delta.isZero();
171 *point += delta;
172 if (_mouse.released()) {
173 active = "";
174 }
175 }
176
177 Inkscape::XML::Node *repr = make_rect(rect, css);
178 draw_ui(id, repr);
179 return change;
180 }
181
182 bool API::input_line(Glib::ustring const &id, Geom::Point *p1, Geom::Point *p2) {
183 bool change = false;
184 if (input_point(id + "_p1", p1)) {
185 change = true;
186 }
187 if (input_point(id + "_p2", p2)) {
188 change = true;
189 }
190 draw_doc("", make_line(*p1, *p2));
191 return change;
192 }
193
194 bool API::input_arrow(Glib::ustring const &id, Geom::Point *from, Geom::Point *to) {
195 bool change = false;
196 if (input_point(id + "_from", from)) {
197 change = true;
198 }
199 if (input_point(id + "_to", to)) {
200 change = true;
201 }
202 draw_doc("", make_line(*from, *to));
203 return change;
204 }
205
206 bool API::input_rect(Glib::ustring const &id, Geom::Rect *rect) {
207 Geom::Point min = rect->min();
208 Geom::Point max = rect->max();
209
210 bool change = false;
211 if (input_point(id + "_min", &min)) {
212 Geom::Point const delta = min - rect->min();
213 *rect += delta;
214 change = true;
215 }
216 if (input_point(id + "_max", &max)) {
217 rect->setMax(max);
218 change = true;
219 }
220
221 draw_doc("", make_rect(*rect));
222 return change;
223 }
224
225 Mouse const &API::mouse() {
226 return _mouse;
227 }
228
229 void API::push_event(GdkEvent *event) {
230 _mouse.push_event(event);
231 }
232
233 void API::setup_frame() {
234 hot = "";
235
236 if (doc_root) {
237 doc_root->deleteObject(true, true);
238 doc_root = nullptr;
239 }
240
241 if (ui_root) {
242 ui_root->deleteObject(true, true);
243 ui_root = nullptr;
244 }
245
246 Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
247 Inkscape::XML::Node *rdoc = xml_doc->createElement("svg:g");
248 Inkscape::XML::Node *rui = xml_doc->createElement("svg:g");
249
250 doc_root = SP_ITEM(desktop->currentLayer()->appendChildRepr(rdoc));
251 doc_root->updateRepr();
252 Inkscape::GC::release(rdoc);
253
254 ui_root = SP_ITEM(desktop->currentLayer()->appendChildRepr(rui));
255 ui_root->doWriteTransform(ui2doc(), nullptr, true);
256 Inkscape::GC::release(rui);
257 }
258
259 void API::finish_frame() {
260 doc_root->updateRepr();
261 ui_root->updateRepr();
262 _mouse.finish_frame();
263 }
264
265 void API::draw_doc(Glib::ustring const &id, Inkscape::XML::Node *repr) {
266 repr->setAttribute("inkscape:livecode-id", id);
267 doc_root->appendChildRepr(repr);
268 Inkscape::GC::release(repr);
269 }
270 void API::draw_ui(Glib::ustring const &id, Inkscape::XML::Node *repr) {
271 repr->setAttribute("inkscape:livecode-id", id);
272 ui_root->appendChildRepr(repr);
273 Inkscape::GC::release(repr);
274 }
275
276 }
277 }
278
279 /*
280 Local Variables:
281 mode:c++
282 c-file-style:"stroustrup"
283 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
284 indent-tabs-mode:nil
285 fill-column:99
286 End:
287 */
288 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
0 // SPDX-License-Identifier: GPL-2.0-or-later
1 #ifndef INK_LIVECODE_API_H
2 #define INK_LIVECODE_API_H
3
4 /*
5 * API for the livecoding script language
6 *
7 * Authors:
8 * Sol Bekic <s+inkscape@s-ol.nu>
9 * Copyright (C) 2019 Authors
10 *
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
14 #include <cstddef>
15 #include <2geom/point.h>
16 #include <2geom/rect.h>
17 #include <gdk/gdk.h>
18
19 #include "xml/repr.h"
20 #include "desktop.h"
21 #include "livecode/input.h"
22
23
24 class SPDocument;
25 class SPItem;
26
27 namespace Inkscape {
28 namespace Livecode {
29
30 class Mouse;
31
32 class API {
33 public:
34 API(SPDesktop *desktop);
35 ~API();
36
37 Inkscape::XML::Node *make_rect(Geom::Rect const &rect, SPCSSAttr *css = nullptr);
38 Inkscape::XML::Node *make_line(Geom::Point const &p1, Geom::Point const &p2, SPCSSAttr *css = nullptr);
39 Inkscape::XML::Node *make_path(Glib::ustring d, Geom::Point const &pos = Geom::Point(), SPCSSAttr *css = nullptr);
40 Inkscape::XML::Node *make_arrow(Geom::Point const &from, Geom::Point const &to, SPCSSAttr *css = nullptr);
41
42 bool input_point(Glib::ustring const &id, Geom::Point *point);
43 bool input_line(Glib::ustring const &id, Geom::Point *p1, Geom::Point *p2);
44 bool input_arrow(Glib::ustring const &id, Geom::Point *from, Geom::Point *to);
45 bool input_rect(Glib::ustring const &id, Geom::Rect *rect);
46
47 void draw_doc(Glib::ustring const &id, Inkscape::XML::Node *item);
48 void draw_ui(Glib::ustring const &id, Inkscape::XML::Node *item);
49
50 void push_event(GdkEvent *event);
51 void setup_frame();
52 void finish_frame();
53
54 inline Geom::Affine ui2dt() const {
55 return doc_root->i2doc_affine();
56 }
57 inline Geom::Affine dt2ui() const {
58 return ui2dt().inverse();
59 }
60
61 inline Geom::Affine ui2doc() const {
62 return desktop->w2d();
63 }
64 inline Geom::Affine doc2ui() const {
65 return ui2doc().inverse();
66 }
67 inline Geom::Affine ui2doc_vec() const {
68 return desktop->w2d().withoutTranslation();
69 }
70 inline Geom::Affine doc2ui_vec() const {
71 return ui2doc_vec().inverse();
72 }
73
74 Mouse const &mouse();
75
76 private:
77 SPDesktop *desktop;
78 SPItem *doc_root, *ui_root;
79
80 Mouse _mouse;
81
82 Glib::ustring hot, active;
83 std::vector<SPItem *> drawn_items;
84 };
85
86 }
87 }
88
89 #endif // INK_LIVECODE_API_H
90
91 /*
92 Local Variables:
93 mode:c++
94 c-file-style:"stroustrup"
95 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
96 indent-tabs-mode:nil
97 fill-column:99
98 End:
99 */
100 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
0 // SPDX-License-Identifier: GPL-2.0-or-later
1 /*
2 * Input state keeper for the livecoding tool
3 *
4 * Authors:
5 * Sol Bekic <s+inkscape@s-ol.nu>
6 * Copyright (C) 2019 Authors
7 *
8 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
9 */
10
11 #include "livecode/input.h"
12 #include "livecode/api.h"
13 #include "desktop.h"
14
15 namespace Inkscape {
16 namespace Livecode {
17
18
19 Mouse::Mouse(API &api)
20 : api(api)
21 {
22 }
23
24 void Mouse::finish_frame()
25 {
26 prev_mask = last_mask;
27 prev_pos = last_pos;
28 }
29
30 void Mouse::push_event(GdkEvent *event)
31 {
32 switch (event->type) {
33 case GDK_BUTTON_PRESS: {
34 last_mask |= 1 << event->button.button - 1;
35 break;
36 }
37 case GDK_BUTTON_RELEASE: {
38 last_mask &= ~(1 << event->button.button - 1);
39 break;
40 }
41 case GDK_MOTION_NOTIFY: {
42 last_pos = Geom::Point(event->motion.x, event->motion.y) * api.dt2ui();
43 break;
44 }
45 default:
46 break;
47 }
48 }
49
50 InputState Mouse::event(guint button) const {
51 guint const flag = 1 << button - 1;
52 bool last = last_mask & flag;
53 bool prev = prev_mask & flag;
54
55 if (last && prev) return INPUTSTATE_HELD;
56 else if (last) return INPUTSTATE_PRESSED;
57 else if (prev) return INPUTSTATE_RELEASED;
58 return INPUTSTATE_NONE;
59 }
60
61 bool Mouse::pressed(guint button) const {
62 guint const flag = 1 << button - 1;
63 return (last_mask & flag) && !(prev_mask & flag);
64 }
65
66 bool Mouse::held(guint button) const {
67 return last_mask & (1 << button -1);
68 }
69
70 bool Mouse::released(guint button) const {
71 guint const flag = 1 << button - 1;
72 return (prev_mask & flag) && !(last_mask & flag);
73 }
74
75 Geom::Point Mouse::pos() const {
76 return ui_pos() * api.ui2doc();
77 }
78 Geom::Point Mouse::delta() const {
79 return ui_delta() * api.ui2doc_vec();
80 }
81
82 Geom::Point Mouse::ui_pos() const {
83 return last_pos;
84 }
85 Geom::Point Mouse::ui_delta() const {
86 return last_pos - prev_pos;
87 }
88
89
90 }
91 }
92
93 /*
94 Local Variables:
95 mode:c++
96 c-file-style:"stroustrup"
97 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
98 indent-tabs-mode:nil
99 fill-column:99
100 End:
101 */
102 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
0 // SPDX-License-Identifier: GPL-2.0-or-later
1 #ifndef INK_LIVECODE_INPUT_H
2 #define INK_LIVECODE_INPUT_H
3
4 /*
5 * Input state keeper for the livecoding tool
6 *
7 * Authors:
8 * Sol Bekic <s+inkscape@s-ol.nu>
9 * Copyright (C) 2019 Authors
10 *
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
14 #include <gdk/gdk.h>
15 #include <2geom/point.h>
16 #include <2geom/geom.h>
17
18
19 namespace Inkscape {
20 namespace Livecode {
21
22 class API;
23
24 enum InputState {
25 INPUTSTATE_NONE,
26 INPUTSTATE_HOVER,
27 INPUTSTATE_PRESSED,
28 INPUTSTATE_HELD,
29 INPUTSTATE_RELEASED,
30 };
31
32 class Mouse {
33 public:
34 Geom::Point pos() const;
35 Geom::Point delta() const;
36
37 Geom::Point ui_pos() const;
38 Geom::Point ui_delta() const;
39
40 InputState event(guint button = 1) const;
41 bool pressed(guint button = 1) const;
42 bool held(guint button = 1) const;
43 bool released(guint button = 1) const;
44
45 private:
46 API& api;
47
48 guint last_mask, prev_mask;
49 Geom::Point last_pos, prev_pos;
50
51 Mouse(API &api);
52
53 void push_event(GdkEvent *event);
54 void finish_frame();
55
56 friend class API;
57 };
58
59 }
60 }
61
62 #endif // INK_LIVECODE_INPUT_H
63
64 /*
65 Local Variables:
66 mode:c++
67 c-file-style:"stroustrup"
68 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
69 indent-tabs-mode:nil
70 fill-column:99
71 End:
72 */
73 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
00 // SPDX-License-Identifier: GPL-2.0-or-later
11 /*
2 * Our nice measuring tool
2 * An experimental Livecoding Tool
33 *
44 * Authors:
5 * Felipe Correa da Silva Sanches <juca@members.fsf.org>
6 * Jon A. Cruz <jon@joncruz.org>
7 * Jabiertxo Arraiza <jabier.arraiza@marker.es>
8 *
9 * Copyright (C) 2011 Authors
5 * Sol Bekic <s+inkscape@s-ol.nu>
6 * Copyright (C) 2019 Authors
107 *
118 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
129 */
1613
1714 #include <boost/none_t.hpp>
1815
19 #include <2geom/line.h>
20 #include <2geom/path-intersection.h>
21
2216 #include "desktop-style.h"
2317 #include "desktop.h"
24 #include "document-undo.h"
2518 #include "inkscape.h"
26 #include "path-chemistry.h"
27 #include "rubberband.h"
28 #include "text-editing.h"
2919 #include "verbs.h"
3020
31 #include "display/curve.h"
3221 #include "display/sodipodi-ctrl.h"
3322 #include "display/sp-canvas-util.h"
3423 #include "display/sp-canvas.h"
35 #include "display/sp-ctrlcurve.h"
36 #include "display/sp-ctrlline.h"
3724
3825 #include "object/sp-defs.h"
3926 #include "object/sp-flowtext.h"
4229 #include "object/sp-shape.h"
4330 #include "object/sp-text.h"
4431
45 #include "ui/pixmaps/cursor-measure.xpm"
46
4732 #include "svg/stringstream.h"
4833 #include "svg/svg-color.h"
4934 #include "svg/svg.h"
5035
51 #include "ui/dialog/knot-properties.h"
52 #include "ui/tools/freehand-base.h"
5336 #include "ui/tools/livecode-tool.h"
5437
5538 #include "util/units.h"
5639
57 using Inkscape::ControlManager;
58 using Inkscape::CTLINE_SECONDARY;
59 using Inkscape::Util::unit_table;
60 using Inkscape::DocumentUndo;
61
62 #define MT_KNOT_COLOR_NORMAL 0xffffff00
63 #define MT_KNOT_COLOR_MOUSEOVER 0xff000000
6440
6541
6642 namespace Inkscape {
7248 return LivecodeTool::prefsPath;
7349 }
7450
75 const std::string LivecodeTool::prefsPath = "/tools/livecode";
76
77 namespace {
78
79 /**
80 * Simple class to use for removing label overlap.
81 */
82 class LabelPlacement {
83 public:
84
85 double lengthVal;
86 double offset;
87 Geom::Point start;
88 Geom::Point end;
89 };
90
91 bool SortLabelPlacement(LabelPlacement const &first, LabelPlacement const &second)
92 {
93 if (first.end[Geom::Y] == second.end[Geom::Y]) {
94 return first.end[Geom::X] < second.end[Geom::X];
95 } else {
96 return first.end[Geom::Y] < second.end[Geom::Y];
97 }
98 }
99
100 //precision is for give the number of decimal positions
101 //of the label to calculate label width
102 void repositionOverlappingLabels(std::vector<LabelPlacement> &placements, SPDesktop *desktop, Geom::Point const &normal, double fontsize, int precision)
103 {
104 std::sort(placements.begin(), placements.end(), SortLabelPlacement);
105
106 double border = 3;
107 Geom::Rect box;
108 {
109 Geom::Point tmp(fontsize * (6 + precision) + (border * 2), fontsize + (border * 2));
110 tmp = desktop->w2d(tmp);
111 box = Geom::Rect(-tmp[Geom::X] / 2, -tmp[Geom::Y] / 2, tmp[Geom::X] / 2, tmp[Geom::Y] / 2);
112 }
113
114 // Using index since vector may be re-ordered as we go.
115 // Starting at one, since the first item can't overlap itself
116 for (size_t i = 1; i < placements.size(); i++) {
117 LabelPlacement &place = placements[i];
118
119 bool changed = false;
120 do {
121 Geom::Rect current(box + place.end);
122
123 changed = false;
124 bool overlaps = false;
125 for (size_t j = i; (j > 0) && !overlaps; --j) {
126 LabelPlacement &otherPlace = placements[j - 1];
127 Geom::Rect target(box + otherPlace.end);
128 if (current.intersects(target)) {
129 overlaps = true;
130 }
131 }
132 if (overlaps) {
133 place.offset += (fontsize + border);
134 place.end = place.start - desktop->w2d(normal * place.offset);
135 changed = true;
136 }
137 } while (changed);
138
139 std::sort(placements.begin(), placements.begin() + i + 1, SortLabelPlacement);
140 }
141 }
142
143 /**
144 * Calculates where to place the anchor for the display text and arc.
145 *
146 * @param desktop the desktop that is being used.
147 * @param angle the angle to be displaying.
148 * @param baseAngle the angle of the initial baseline.
149 * @param startPoint the point that is the vertex of the selected angle.
150 * @param endPoint the point that is the end the user is manipulating for measurement.
151 * @param fontsize the size to display the text label at.
152 */
153 Geom::Point calcAngleDisplayAnchor(SPDesktop *desktop, double angle, double baseAngle,
154 Geom::Point const &startPoint, Geom::Point const &endPoint,
155 double fontsize)
156 {
157 // Time for the trick work of figuring out where things should go, and how.
158 double lengthVal = (endPoint - startPoint).length();
159 double effective = baseAngle + (angle / 2);
160 Geom::Point where(lengthVal, 0);
161 where *= Geom::Affine(Geom::Rotate(effective)) * Geom::Affine(Geom::Translate(startPoint));
162
163 // When the angle is tight, the label would end up under the cursor and/or lines. Bump it
164 double scaledFontsize = std::abs(fontsize * desktop->w2d(Geom::Point(0, 1.0))[Geom::Y]);
165 if (std::abs((where - endPoint).length()) < scaledFontsize) {
166 where[Geom::Y] += scaledFontsize * 2;
167 }
168
169 // We now have the ideal position, but need to see if it will fit/work.
170
171 Geom::Rect visibleArea = desktop->get_display_area();
172 // Bring it in to "title safe" for the anchor point
173 Geom::Point textBox = desktop->w2d(Geom::Point(fontsize * 3, fontsize / 2));
174 textBox[Geom::Y] = std::abs(textBox[Geom::Y]);
175
176 visibleArea = Geom::Rect(visibleArea.min()[Geom::X] + textBox[Geom::X],
177 visibleArea.min()[Geom::Y] + textBox[Geom::Y],
178 visibleArea.max()[Geom::X] - textBox[Geom::X],
179 visibleArea.max()[Geom::Y] - textBox[Geom::Y]);
180
181 where[Geom::X] = std::min(where[Geom::X], visibleArea.max()[Geom::X]);
182 where[Geom::X] = std::max(where[Geom::X], visibleArea.min()[Geom::X]);
183 where[Geom::Y] = std::min(where[Geom::Y], visibleArea.max()[Geom::Y]);
184 where[Geom::Y] = std::max(where[Geom::Y], visibleArea.min()[Geom::Y]);
185
186 return where;
187 }
188
189 /**
190 * Create a measure item in current document.
191 *
192 * @param pathv the path to create.
193 * @param markers if the path results get markers.
194 * @param color of the stroke.
195 * @param measure_repr container element.
196 */
197 void setMeasureItem(Geom::PathVector pathv, bool is_curve, bool markers, guint32 color, Inkscape::XML::Node *measure_repr)
198 {
199 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
200 if(!desktop) {
201 return;
202 }
203 SPDocument *doc = desktop->getDocument();
204 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
205 Inkscape::XML::Node *repr;
206 repr = xml_doc->createElement("svg:path");
207 gchar *str = sp_svg_write_path(pathv);
208 SPCSSAttr *css = sp_repr_css_attr_new();
209 Geom::Coord strokewidth = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse().expansionX();
210 std::stringstream stroke_width;
211 stroke_width.imbue(std::locale::classic());
212 if(measure_repr) {
213 stroke_width << strokewidth / desktop->current_zoom();
214 } else {
215 stroke_width << strokewidth;
216 }
217 sp_repr_css_set_property (css, "stroke-width", stroke_width.str().c_str());
218 sp_repr_css_set_property (css, "fill", "none");
219 if(color) {
220 gchar color_line[64];
221 sp_svg_write_color (color_line, sizeof(color_line), color);
222 sp_repr_css_set_property (css, "stroke", color_line);
223 } else {
224 sp_repr_css_set_property (css, "stroke", "#ff0000");
225 }
226 char const * stroke_linecap = is_curve ? "butt" : "square";
227 sp_repr_css_set_property (css, "stroke-linecap", stroke_linecap);
228 sp_repr_css_set_property (css, "stroke-linejoin", "miter");
229 sp_repr_css_set_property (css, "stroke-miterlimit", "4");
230 sp_repr_css_set_property (css, "stroke-dasharray", "none");
231 if(measure_repr) {
232 sp_repr_css_set_property (css, "stroke-opacity", "0.5");
233 } else {
234 sp_repr_css_set_property (css, "stroke-opacity", "1");
235 }
236 if(markers) {
237 sp_repr_css_set_property (css, "marker-start", "url(#Arrow2Sstart)");
238 sp_repr_css_set_property (css, "marker-end", "url(#Arrow2Send)");
239 }
240 Glib::ustring css_str;
241 sp_repr_css_write_string(css,css_str);
242 repr->setAttribute("style", css_str.c_str());
243 sp_repr_css_attr_unref (css);
244 g_assert( str != nullptr );
245 repr->setAttribute("d", str);
246 g_free(str);
247 if(measure_repr) {
248 measure_repr->addChild(repr, nullptr);
249 Inkscape::GC::release(repr);
250 } else {
251 SPItem *item = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr));
252 Inkscape::GC::release(repr);
253 item->updateRepr();
254 desktop->getSelection()->clear();
255 desktop->getSelection()->add(item);
256 }
257 }
258
259 /**
260 * Given an angle, the arc center and edge point, draw an arc segment centered around that edge point.
261 *
262 * @param desktop the desktop that is being used.
263 * @param center the center point for the arc.
264 * @param end the point that ends at the edge of the arc segment.
265 * @param anchor the anchor point for displaying the text label.
266 * @param angle the angle of the arc segment to draw.
267 * @param measure_rpr the container of the curve if converted to items.
268 */
269 void createAngleDisplayCurve(SPDesktop *desktop, Geom::Point const &center, Geom::Point const &end, Geom::Point const &anchor, double angle, bool to_phantom, std::vector<SPCanvasItem *> &measure_phantom_items , std::vector<SPCanvasItem *> &measure_tmp_items , Inkscape::XML::Node *measure_repr = nullptr)
270 {
271 // Given that we have a point on the arc's edge and the angle of the arc, we need to get the two endpoints.
272
273 double textLen = std::abs((anchor - center).length());
274 double sideLen = std::abs((end - center).length());
275 if (sideLen > 0.0) {
276 double factor = std::min(1.0, textLen / sideLen);
277
278 // arc start
279 Geom::Point p1 = end * (Geom::Affine(Geom::Translate(-center))
280 * Geom::Affine(Geom::Scale(factor))
281 * Geom::Affine(Geom::Translate(center)));
282
283 // arc end
284 Geom::Point p4 = p1 * (Geom::Affine(Geom::Translate(-center))
285 * Geom::Affine(Geom::Rotate(-angle))
286 * Geom::Affine(Geom::Translate(center)));
287
288 // from Riskus
289 double xc = center[Geom::X];
290 double yc = center[Geom::Y];
291 double ax = p1[Geom::X] - xc;
292 double ay = p1[Geom::Y] - yc;
293 double bx = p4[Geom::X] - xc;
294 double by = p4[Geom::Y] - yc;
295 double q1 = (ax * ax) + (ay * ay);
296 double q2 = q1 + (ax * bx) + (ay * by);
297
298 double k2 = (4.0 / 3.0) * (std::sqrt(2 * q1 * q2) - q2) / ((ax * by) - (ay * bx));
299
300 Geom::Point p2(xc + ax - (k2 * ay),
301 yc + ay + (k2 * ax));
302 Geom::Point p3(xc + bx + (k2 * by),
303 yc + by - (k2 * bx));
304 SPCtrlCurve *curve = ControlManager::getManager().createControlCurve(desktop->getTempGroup(), p1, p2, p3, p4, CTLINE_SECONDARY);
305 if(to_phantom){
306 curve->rgba = 0x8888887f;
307 measure_phantom_items.push_back(SP_CANVAS_ITEM(curve));
308 } else {
309 measure_tmp_items.push_back(SP_CANVAS_ITEM(curve));
310 }
311 sp_canvas_item_move_to_z(SP_CANVAS_ITEM(curve), 0);
312 sp_canvas_item_show(SP_CANVAS_ITEM(curve));
313 if(measure_repr) {
314 Geom::PathVector pathv;
315 Geom::Path path;
316 path.start(desktop->doc2dt(p1));
317 path.appendNew<Geom::CubicBezier>(desktop->doc2dt(p2),desktop->doc2dt(p3),desktop->doc2dt(p4));
318 pathv.push_back(path);
319 pathv *= SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
320 if(!pathv.empty()) {
321 setMeasureItem(pathv, true, false, 0xff00007f, measure_repr);
322 }
323 }
324 }
325 }
326
327 boost::optional<Geom::Point> explicit_base_tmp = boost::none;
328
329 } // namespace
51 const std::string LivecodeTool::prefsPath = "/tools/ersaser";
33052
33153 LivecodeTool::LivecodeTool()
332 : ToolBase(cursor_measure_xpm)
333 , grabbed(nullptr)
54 : ToolBase(nullptr, false)
55 , api(SP_ACTIVE_DESKTOP)
33456 {
335 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
336 start_p = readMeasurePoint(true);
337 end_p = readMeasurePoint(false);
338 dimension_offset = 35;
339 last_pos = Geom::Point(0,0);
340 // create the knots
341 this->knot_start = new SPKnot(desktop, _("Measure start, <b>Shift+Click</b> for position dialog"));
342 this->knot_start->setMode(SP_KNOT_MODE_XOR);
343 this->knot_start->setFill(MT_KNOT_COLOR_NORMAL, MT_KNOT_COLOR_MOUSEOVER, MT_KNOT_COLOR_MOUSEOVER, MT_KNOT_COLOR_MOUSEOVER);
344 this->knot_start->setStroke(0x0000007f, 0x0000007f, 0x0000007f, 0x0000007f);
345 this->knot_start->setShape(SP_KNOT_SHAPE_CIRCLE);
346 this->knot_start->updateCtrl();
347 this->knot_end = new SPKnot(desktop, _("Measure end, <b>Shift+Click</b> for position dialog"));
348 this->knot_end->setMode(SP_KNOT_MODE_XOR);
349 this->knot_end->setFill(MT_KNOT_COLOR_NORMAL, MT_KNOT_COLOR_MOUSEOVER, MT_KNOT_COLOR_MOUSEOVER, MT_KNOT_COLOR_MOUSEOVER);
350 this->knot_end->setStroke(0x0000007f, 0x0000007f, 0x0000007f, 0x0000007f);
351 this->knot_end->setShape(SP_KNOT_SHAPE_CIRCLE);
352 this->knot_end->updateCtrl();
353 Geom::Rect display_area = desktop->get_display_area();
354 if(display_area.interiorContains(start_p) && display_area.interiorContains(end_p) && end_p != Geom::Point()) {
355 this->knot_start->moveto(start_p);
356 this->knot_start->show();
357 this->knot_end->moveto(end_p);
358 this->knot_end->show();
359 showCanvasItems();
360 } else {
361 start_p = Geom::Point(0,0);
362 end_p = Geom::Point(0,0);
363 writeMeasurePoint(start_p, true);
364 writeMeasurePoint(end_p, false);
365 }
366 this->_knot_start_moved_connection = this->knot_start->moved_signal.connect(sigc::mem_fun(*this, &LivecodeTool::knotStartMovedHandler));
367 this->_knot_start_click_connection = this->knot_start->click_signal.connect(sigc::mem_fun(*this, &LivecodeTool::knotClickHandler));
368 this->_knot_start_ungrabbed_connection = this->knot_start->ungrabbed_signal.connect(sigc::mem_fun(*this, &LivecodeTool::knotUngrabbedHandler));
369 this->_knot_end_moved_connection = this->knot_end->moved_signal.connect(sigc::mem_fun(*this, &LivecodeTool::knotEndMovedHandler));
370 this->_knot_end_click_connection = this->knot_end->click_signal.connect(sigc::mem_fun(*this, &LivecodeTool::knotClickHandler));
371 this->_knot_end_ungrabbed_connection = this->knot_end->ungrabbed_signal.connect(sigc::mem_fun(*this, &LivecodeTool::knotUngrabbedHandler));
372
37357 }
37458
37559 LivecodeTool::~LivecodeTool()
37660 {
377 this->_knot_start_moved_connection.disconnect();
378 this->_knot_start_ungrabbed_connection.disconnect();
379 this->_knot_end_moved_connection.disconnect();
380 this->_knot_end_ungrabbed_connection.disconnect();
381
382 /* unref should call destroy */
383 knot_unref(this->knot_start);
384 knot_unref(this->knot_end);
385 for (auto & measure_tmp_item : measure_tmp_items) {
386 sp_canvas_item_destroy(measure_tmp_item);
387 }
388 measure_tmp_items.clear();
389 for (auto & idx : measure_item) {
390 sp_canvas_item_destroy(idx);
391 }
392 measure_item.clear();
393 for (auto & measure_phantom_item : measure_phantom_items) {
394 sp_canvas_item_destroy(measure_phantom_item);
395 }
396 measure_phantom_items.clear();
39761 }
39862
399 Geom::Point LivecodeTool::readMeasurePoint(bool is_start) {
400 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
401 Glib::ustring measure_point = is_start ? "/tools/measure/measure-start" : "/tools/measure/measure-end";
402 return prefs->getPoint(measure_point, Geom::Point(Geom::infinity(),Geom::infinity()));
63 void LivecodeTool::setup()
64 {
65 ToolBase::setup();
66
67 Gtk::Widget* w = Glib::wrap(GTK_WIDGET(desktop->getCanvas()));
68 tick_callback = w->add_tick_callback(sigc::mem_fun(this, &LivecodeTool::handle_tick));
40369 }
40470
405 void LivecodeTool::writeMeasurePoint(Geom::Point point, bool is_start) {
406 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
407 Glib::ustring measure_point = is_start ? "/tools/measure/measure-start" : "/tools/measure/measure-end";
408 prefs->setPoint(measure_point, point);
409 }
410
411 //This function is used to reverse the Measure, I do it in two steps because when
412 //we move the knot the start_ or the end_p are overwritten so I need the original values.
413 void LivecodeTool::reverseKnots()
414 {
415 Geom::Point start = start_p;
416 Geom::Point end = end_p;
417 this->knot_start->moveto(end);
418 this->knot_start->show();
419 this->knot_end->moveto(start);
420 this->knot_end->show();
421 start_p = end;
422 end_p = start;
423 this->showCanvasItems();
424 }
425
426 void LivecodeTool::knotClickHandler(SPKnot *knot, guint state)
427 {
428 if (state & GDK_SHIFT_MASK) {
429 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
430 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
431 Glib::ustring const unit_name = prefs->getString("/tools/measure/unit");
432 explicit_base = explicit_base_tmp;
433 Inkscape::UI::Dialogs::KnotPropertiesDialog::showDialog(desktop, knot, unit_name);
434 }
435 }
436
437 void LivecodeTool::knotStartMovedHandler(SPKnot */*knot*/, Geom::Point const &ppointer, guint state)
438 {
439 Geom::Point point = this->knot_start->position();
440 if (state & GDK_CONTROL_MASK) {
441 spdc_endpoint_snap_rotation(this, point, end_p, state);
442 } else if (!(state & GDK_SHIFT_MASK)) {
443 SnapManager &snap_manager = desktop->namedview->snap_manager;
444 snap_manager.setup(desktop);
445 Inkscape::SnapCandidatePoint scp(point, Inkscape::SNAPSOURCE_OTHER_HANDLE);
446 scp.addOrigin(this->knot_end->position());
447 Inkscape::SnappedPoint sp = snap_manager.freeSnap(scp);
448 point = sp.getPoint();
449 snap_manager.unSetup();
450 }
451 if(start_p != point) {
452 start_p = point;
453 this->knot_start->moveto(start_p);
454 }
455 showCanvasItems();
456 }
457
458 void LivecodeTool::knotEndMovedHandler(SPKnot */*knot*/, Geom::Point const &ppointer, guint state)
459 {
460 Geom::Point point = this->knot_end->position();
461 if (state & GDK_CONTROL_MASK) {
462 spdc_endpoint_snap_rotation(this, point, start_p, state);
463 } else if (!(state & GDK_SHIFT_MASK)) {
464 SnapManager &snap_manager = desktop->namedview->snap_manager;
465 snap_manager.setup(desktop);
466 Inkscape::SnapCandidatePoint scp(point, Inkscape::SNAPSOURCE_OTHER_HANDLE);
467 scp.addOrigin(this->knot_start->position());
468 Inkscape::SnappedPoint sp = snap_manager.freeSnap(scp);
469 point = sp.getPoint();
470 snap_manager.unSetup();
471 }
472 if(end_p != point) {
473 end_p = point;
474 this->knot_end->moveto(end_p);
475 }
476 showCanvasItems();
477 }
478
479 void LivecodeTool::knotUngrabbedHandler(SPKnot */*knot*/, unsigned int state)
480 {
481 this->knot_start->moveto(start_p);
482 this->knot_end->moveto(end_p);
483 showCanvasItems();
484 }
485
486
487 //todo: we need this function?
48871 void LivecodeTool::finish()
48972 {
490 this->enableGrDrag(false);
491
492 if (this->grabbed) {
493 sp_canvas_item_ungrab(this->grabbed);
494 this->grabbed = nullptr;
495 }
73 Gtk::Widget* w = Glib::wrap(GTK_WIDGET(desktop->getCanvas()));
74 w->remove_tick_callback(tick_callback);
49675
49776 ToolBase::finish();
498 }
499
500 static void calculate_intersections(SPDesktop * /*desktop*/, SPItem* item, Geom::PathVector const &lineseg, SPCurve *curve, std::vector<double> &intersections)
501 {
502 curve->transform(item->i2doc_affine());
503 // Find all intersections of the control-line with this shape
504 Geom::CrossingSet cs = Geom::crossings(lineseg, curve->get_pathvector());
505 Geom::delete_duplicates(cs[0]);
506
507 // Reconstruct and store the points of intersection
508 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
509 bool show_hidden = prefs->getBool("/tools/measure/show_hidden", true);
510 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
511 for (Geom::Crossings::const_iterator m = cs[0].begin(); m != cs[0].end(); ++m) {
512 if (!show_hidden) {
513 double eps = 0.0001;
514 if (((*m).ta > eps &&
515 item == desktop->getItemAtPoint(desktop->d2w(desktop->dt2doc(lineseg[0].pointAt((*m).ta - eps))), true, nullptr)) ||
516 ((*m).ta + eps < 1 &&
517 item == desktop->getItemAtPoint(desktop->d2w(desktop->dt2doc(lineseg[0].pointAt((*m).ta + eps))), true, nullptr))) {
518 intersections.push_back((*m).ta);
519 }
520 } else {
521 intersections.push_back((*m).ta);
522 }
523 }
52477 }
52578
52679 bool LivecodeTool::root_handler(GdkEvent* event)
52780 {
52881 gint ret = FALSE;
52982
530 switch (event->type) {
531 case GDK_BUTTON_PRESS: {
532 this->knot_start->hide();
533 this->knot_end->hide();
534 Geom::Point const button_w(event->button.x, event->button.y);
535 explicit_base = boost::none;
536 explicit_base_tmp = boost::none;
537 last_end = boost::none;
538
539 if (event->button.button == 1 && !this->space_panning) {
540 // save drag origin
541 start_p = desktop->w2d(Geom::Point(event->button.x, event->button.y));
542 within_tolerance = true;
543
544 ret = TRUE;
545 }
546
547 SnapManager &snap_manager = desktop->namedview->snap_manager;
548 snap_manager.setup(desktop);
549 snap_manager.freeSnapReturnByRef(start_p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
550 snap_manager.unSetup();
551
552 sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
553 GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK,
554 nullptr, event->button.time);
555 this->grabbed = SP_CANVAS_ITEM(desktop->acetate);
556 break;
557 }
558 case GDK_KEY_PRESS: {
559 if ((event->key.keyval == GDK_KEY_Control_L) || (event->key.keyval == GDK_KEY_Control_R)) {
560 explicit_base_tmp = explicit_base;
561 explicit_base = end_p;
562 showInfoBox(last_pos, true);
563 }
564 break;
565 }
566 case GDK_KEY_RELEASE: {
567 if ((event->key.keyval == GDK_KEY_Control_L) || (event->key.keyval == GDK_KEY_Control_R)) {
568 showInfoBox(last_pos, false);
569 }
570 break;
571 }
572 case GDK_MOTION_NOTIFY: {
573 if (!(event->motion.state & GDK_BUTTON1_MASK)) {
574 if(!(event->motion.state & GDK_SHIFT_MASK)) {
575 Geom::Point const motion_w(event->motion.x, event->motion.y);
576 Geom::Point const motion_dt(desktop->w2d(motion_w));
577
578 SnapManager &snap_manager = desktop->namedview->snap_manager;
579 snap_manager.setup(desktop);
580
581 Inkscape::SnapCandidatePoint scp(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE);
582 scp.addOrigin(start_p);
583
584 snap_manager.preSnap(scp);
585 snap_manager.unSetup();
586 }
587 last_pos = Geom::Point(event->motion.x, event->motion.y);
588 if (event->motion.state & GDK_CONTROL_MASK) {
589 showInfoBox(last_pos, true);
590 } else {
591 showInfoBox(last_pos, false);
592 }
593 } else {
594 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
595 //Inkscape::Util::Unit const * unit = desktop->getNamedView()->getDisplayUnit();
596 for (auto & idx : measure_item) {
597 sp_canvas_item_destroy(idx);
598 }
599 measure_item.clear();
600 ret = TRUE;
601 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
602 tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
603 Geom::Point const motion_w(event->motion.x, event->motion.y);
604 if ( within_tolerance) {
605 if ( Geom::LInfty( motion_w - start_p ) < tolerance) {
606 return FALSE; // Do not drag if we're within tolerance from origin.
607 }
608 }
609 // Once the user has moved farther than tolerance from the original location
610 // (indicating they intend to move the object, not click), then always process the
611 // motion notify coordinates as given (no snapping back to origin)
612 within_tolerance = false;
613 if(event->motion.time == 0 || !last_end || Geom::LInfty( motion_w - *last_end ) > (tolerance/4.0)) {
614 Geom::Point const motion_dt(desktop->w2d(motion_w));
615 end_p = motion_dt;
616
617 if (event->motion.state & GDK_CONTROL_MASK) {
618 spdc_endpoint_snap_rotation(this, end_p, start_p, event->motion.state);
619 } else if (!(event->motion.state & GDK_SHIFT_MASK)) {
620 SnapManager &snap_manager = desktop->namedview->snap_manager;
621 snap_manager.setup(desktop);
622 Inkscape::SnapCandidatePoint scp(end_p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
623 scp.addOrigin(start_p);
624 Inkscape::SnappedPoint sp = snap_manager.freeSnap(scp);
625 end_p = sp.getPoint();
626 snap_manager.unSetup();
627 }
628 showCanvasItems();
629 last_end = motion_w ;
630 }
631 gobble_motion_events(GDK_BUTTON1_MASK);
632 }
633 break;
634 }
635 case GDK_BUTTON_RELEASE: {
636 this->knot_start->moveto(start_p);
637 this->knot_start->show();
638 if(last_end) {
639 end_p = desktop->w2d(*last_end);
640 if (event->button.state & GDK_CONTROL_MASK) {
641 spdc_endpoint_snap_rotation(this, end_p, start_p, event->motion.state);
642 } else if (!(event->button.state & GDK_SHIFT_MASK)) {
643 SnapManager &snap_manager = desktop->namedview->snap_manager;
644 snap_manager.setup(desktop);
645 Inkscape::SnapCandidatePoint scp(end_p, Inkscape::SNAPSOURCE_OTHER_HANDLE);
646 scp.addOrigin(start_p);
647 Inkscape::SnappedPoint sp = snap_manager.freeSnap(scp);
648 end_p = sp.getPoint();
649 snap_manager.unSetup();
650 }
651 }
652 this->knot_end->moveto(end_p);
653 this->knot_end->show();
654 showCanvasItems();
655
656 if (this->grabbed) {
657 sp_canvas_item_ungrab(this->grabbed);
658 this->grabbed = nullptr;
659 }
660 break;
661 }
662 default:
663 break;
664 }
83 ret = ToolBase::root_handler(event);
66584 if (!ret) {
666 ret = ToolBase::root_handler(event);
85 api.push_event(event);
66786 }
66887
66988 return ret;
67089 }
67190
672 void LivecodeTool::setMarkers()
91 static Geom::Point p(100, 100);
92 static Geom::Point a(50, 100), b(120, 300);
93 static Geom::Rect rect(Geom::Point(20, 200), Geom::Point(220, 20));
94
95 bool LivecodeTool::handle_tick(Glib::RefPtr<Gdk::FrameClock> const &frame_clock)
67396 {
674 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
675 SPDocument *doc = desktop->getDocument();
676 SPObject *arrowStart = doc->getObjectById("Arrow2Sstart");
677 SPObject *arrowEnd = doc->getObjectById("Arrow2Send");
678 if (!arrowStart) {
679 setMarker(true);
680 }
681 if(!arrowEnd) {
682 setMarker(false);
683 }
684 }
685 void LivecodeTool::setMarker(bool isStart)
686 {
687 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
688 SPDocument *doc = desktop->getDocument();
689 SPDefs *defs = doc->getDefs();
690 Inkscape::XML::Node *rmarker;
691 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
692 rmarker = xml_doc->createElement("svg:marker");
693 rmarker->setAttribute("id", isStart ? "Arrow2Sstart" : "Arrow2Send");
694 rmarker->setAttribute("inkscape:isstock", "true");
695 rmarker->setAttribute("inkscape:stockid", isStart ? "Arrow2Sstart" : "Arrow2Send");
696 rmarker->setAttribute("orient", "auto");
697 rmarker->setAttribute("refX", "0.0");
698 rmarker->setAttribute("refY", "0.0");
699 rmarker->setAttribute("style", "overflow:visible;");
700 SPItem *marker = SP_ITEM(defs->appendChildRepr(rmarker));
701 Inkscape::GC::release(rmarker);
702 marker->updateRepr();
703 Inkscape::XML::Node *rpath;
704 rpath = xml_doc->createElement("svg:path");
705 rpath->setAttribute("d", "M 8.72,4.03 L -2.21,0.02 L 8.72,-4.00 C 6.97,-1.63 6.98,1.62 8.72,4.03 z");
706 rpath->setAttribute("id", isStart ? "Arrow2SstartPath" : "Arrow2SendPath");
707 SPCSSAttr *css = sp_repr_css_attr_new();
708 sp_repr_css_set_property (css, "stroke", "none");
709 sp_repr_css_set_property (css, "fill", "#000000");
710 sp_repr_css_set_property (css, "fill-opacity", "1");
711 Glib::ustring css_str;
712 sp_repr_css_write_string(css,css_str);
713 rpath->setAttribute("style", css_str.c_str());
714 sp_repr_css_attr_unref (css);
715 rpath->setAttribute("transform", isStart ? "scale(0.3) translate(-2.3,0)" : "scale(0.3) rotate(180) translate(-2.3,0)");
716 SPItem *path = SP_ITEM(marker->appendChildRepr(rpath));
717 Inkscape::GC::release(rpath);
718 path->updateRepr();
97 api.setup_frame();
98
99 api.input_point("p", &p);
100 api.input_arrow("a->b", &a, &b);
101 api.input_rect("rect", &rect);
102
103 api.finish_frame();
104 return true;
719105 }
720106
721 void LivecodeTool::toGuides()
722 {
723 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
724 if(!desktop || !start_p.isFinite() || !end_p.isFinite() || start_p == end_p) {
725 return;
726 }
727 SPDocument *doc = desktop->getDocument();
728 Geom::Point start = desktop->doc2dt(start_p) * desktop->doc2dt();
729 Geom::Point end = desktop->doc2dt(end_p) * desktop->doc2dt();
730 Geom::Ray ray(start,end);
731 SPNamedView *namedview = desktop->namedview;
732 if(!namedview) {
733 return;
734 }
735 setGuide(start,ray.angle(), _("Measure"));
736 if(explicit_base) {
737 explicit_base = *explicit_base * SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
738 ray.setPoints(start, *explicit_base);
739 if(ray.angle() != 0) {
740 setGuide(start,ray.angle(), _("Base"));
741 }
742 }
743 setGuide(start,0,"");
744 setGuide(start,Geom::rad_from_deg(90),_("Start"));
745 setGuide(end,0,_("End"));
746 setGuide(end,Geom::rad_from_deg(90),"");
747 showCanvasItems(true);
748 doc->ensureUpToDate();
749 DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_MEASURE,_("Add guides from measure tool"));
750 }
751
752 void LivecodeTool::toPhantom()
753 {
754 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
755 if(!desktop || !start_p.isFinite() || !end_p.isFinite() || start_p == end_p) {
756 return;
757 }
758 SPDocument *doc = desktop->getDocument();
759 for (auto & measure_phantom_item : measure_phantom_items) {
760 sp_canvas_item_destroy(measure_phantom_item);
761 }
762 measure_phantom_items.clear();
763 for (auto & measure_tmp_item : measure_tmp_items) {
764 sp_canvas_item_destroy(measure_tmp_item);
765 }
766 measure_tmp_items.clear();
767 showCanvasItems(false, false, true);
768 doc->ensureUpToDate();
769 DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_MEASURE,_("Keep last measure on the canvas, for reference"));
770 }
771
772 void LivecodeTool::toItem()
773 {
774 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
775 if(!desktop || !start_p.isFinite() || !end_p.isFinite() || start_p == end_p) {
776 return;
777 }
778 SPDocument *doc = desktop->getDocument();
779 Geom::Ray ray(start_p,end_p);
780 guint32 line_color_primary = 0x0000ff7f;
781 Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
782 Inkscape::XML::Node *rgroup = xml_doc->createElement("svg:g");
783 showCanvasItems(false, true, false, rgroup);
784 setLine(start_p,end_p, false, line_color_primary, rgroup);
785 SPItem *measure_item = SP_ITEM(desktop->currentLayer()->appendChildRepr(rgroup));
786 Inkscape::GC::release(rgroup);
787 measure_item->updateRepr();
788 doc->ensureUpToDate();
789 DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_MEASURE,_("Convert measure to items"));
790 reset();
791 }
792
793 void LivecodeTool::toMarkDimension()
794 {
795 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
796 if(!desktop || !start_p.isFinite() || !end_p.isFinite() || start_p == end_p) {
797 return;
798 }
799 SPDocument *doc = desktop->getDocument();
800 setMarkers();
801 Geom::Ray ray(start_p,end_p);
802 Geom::Point start = start_p + Geom::Point::polar(ray.angle(), 5);
803 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
804 dimension_offset = prefs->getDouble("/tools/measure/offset", 5.0);
805 start = start + Geom::Point::polar(ray.angle() + Geom::rad_from_deg(90), -dimension_offset);
806 Geom::Point end = end_p + Geom::Point::polar(ray.angle(), -5);
807 end = end+ Geom::Point::polar(ray.angle() + Geom::rad_from_deg(90), -dimension_offset);
808 guint32 color = 0x000000ff;
809 setLine(start, end, true, color);
810 Glib::ustring unit_name = prefs->getString("/tools/measure/unit");
811 if (!unit_name.compare("")) {
812 unit_name = DEFAULT_UNIT_NAME;
813 }
814 double fontsize = prefs->getDouble("/tools/measure/fontsize", 10.0);
815 int precision = prefs->getInt("/tools/measure/precision", 2);
816 std::stringstream precision_str;
817 precision_str.imbue(std::locale::classic());
818 precision_str << "%." << precision << "f %s";
819 Geom::Point middle = Geom::middle_point(start, end);
820 double totallengthval = (end_p - start_p).length();
821 totallengthval = Inkscape::Util::Quantity::convert(totallengthval, "px", unit_name);
822 double scale = prefs->getDouble("/tools/measure/scale", 100.0) / 100.0;
823 gchar *totallength_str = g_strdup_printf(precision_str.str().c_str(), totallengthval * scale, unit_name.c_str());
824 double textangle = Geom::rad_from_deg(180) - ray.angle();
825 if (desktop->is_yaxisdown()) {
826 textangle = ray.angle() - Geom::rad_from_deg(180);
827 }
828 setLabelText(totallength_str, middle, fontsize, textangle, color);
829 g_free(totallength_str);
830 doc->ensureUpToDate();
831 DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_MEASURE,_("Add global measure line"));
832 }
833
834 void LivecodeTool::setGuide(Geom::Point origin,double angle, const char *label)
835 {
836 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
837 SPDocument *doc = desktop->getDocument();
838 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
839 SPRoot const *root = doc->getRoot();
840 Geom::Affine affine(Geom::identity());
841 if(root) {
842 affine *= root->c2p.inverse();
843 }
844 SPNamedView *namedview = desktop->namedview;
845 if(!namedview) {
846 return;
847 }
848
849 // <sodipodi:guide> stores inverted y-axis coordinates
850 if (desktop->is_yaxisdown()) {
851 origin[Geom::Y] = doc->getHeight().value("px") - origin[Geom::Y];
852 angle *= -1.0;
853 }
854
855 origin *= affine;
856 //measure angle
857 Inkscape::XML::Node *guide;
858 guide = xml_doc->createElement("sodipodi:guide");
859 std::stringstream position;
860 position.imbue(std::locale::classic());
861 position << origin[Geom::X] << "," << origin[Geom::Y];
862 guide->setAttribute("position", position.str().c_str() );
863 guide->setAttribute("inkscape:color", "rgb(167,0,255)");
864 guide->setAttribute("inkscape:label", label);
865 Geom::Point unit_vector = Geom::rot90(origin.polar(angle));
866 std::stringstream angle_str;
867 angle_str.imbue(std::locale::classic());
868 angle_str << unit_vector[Geom::X] << "," << unit_vector[Geom::Y];
869 guide->setAttribute("orientation", angle_str.str().c_str());
870 namedview->appendChild(guide);
871 Inkscape::GC::release(guide);
872 }
873
874 void LivecodeTool::setLine(Geom::Point start_point,Geom::Point end_point, bool markers, guint32 color, Inkscape::XML::Node *measure_repr)
875 {
876 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
877 if(!desktop || !start_p.isFinite() || !end_p.isFinite()) {
878 return;
879 }
880 Geom::PathVector pathv;
881 Geom::Path path;
882 path.start(desktop->doc2dt(start_point));
883 path.appendNew<Geom::LineSegment>(desktop->doc2dt(end_point));
884 pathv.push_back(path);
885 pathv *= SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
886 if(!pathv.empty()) {
887 setMeasureItem(pathv, false, markers, color, measure_repr);
888 }
889 }
890
891 void LivecodeTool::setPoint(Geom::Point origin, Inkscape::XML::Node *measure_repr)
892 {
893 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
894 if(!desktop || !origin.isFinite()) {
895 return;
896 }
897 char const * svgd;
898 svgd = "m 0.707,0.707 6.586,6.586 m 0,-6.586 -6.586,6.586";
899 Geom::PathVector pathv = sp_svg_read_pathv(svgd);
900 Geom::Scale scale = Geom::Scale(desktop->current_zoom()).inverse();
901 pathv *= Geom::Translate(Geom::Point(-3.5,-3.5));
902 pathv *= scale;
903 pathv *= Geom::Translate(Geom::Point() - (scale.vector() * 0.5));
904 pathv *= Geom::Translate(desktop->doc2dt(origin));
905 pathv *= SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
906 if (!pathv.empty()) {
907 guint32 line_color_secondary = 0xff0000ff;
908 setMeasureItem(pathv, false, false, line_color_secondary, measure_repr);
909 }
910 }
911
912 void LivecodeTool::setLabelText(const char *value, Geom::Point pos, double fontsize, Geom::Coord angle, guint32 background, Inkscape::XML::Node *measure_repr, CanvasTextAnchorPositionEnum text_anchor)
913 {
914 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
915 Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
916 /* Create <text> */
917 pos = desktop->doc2dt(pos);
918 Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
919 rtext->setAttribute("xml:space", "preserve");
920
921
922 /* Set style */
923 sp_desktop_apply_style_tool(desktop, rtext, "/tools/text", true);
924 if(measure_repr) {
925 sp_repr_set_svg_double(rtext, "x", 2);
926 sp_repr_set_svg_double(rtext, "y", 2);
927 } else {
928 sp_repr_set_svg_double(rtext, "x", 0);
929 sp_repr_set_svg_double(rtext, "y", 0);
930 }
931
932 /* Create <tspan> */
933 Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
934 rtspan->setAttribute("sodipodi:role", "line");
935 SPCSSAttr *css = sp_repr_css_attr_new();
936 std::stringstream font_size;
937 font_size.imbue(std::locale::classic());
938 if(measure_repr) {
939 font_size << fontsize;
940 } else {
941 font_size << fontsize << "pt";
942 }
943 sp_repr_css_set_property (css, "font-size", font_size.str().c_str());
944 sp_repr_css_set_property (css, "font-style", "normal");
945 sp_repr_css_set_property (css, "font-weight", "normal");
946 sp_repr_css_set_property (css, "line-height", "125%");
947 sp_repr_css_set_property (css, "letter-spacing", "0");
948 sp_repr_css_set_property (css, "word-spacing", "0");
949 sp_repr_css_set_property (css, "text-align", "center");
950 sp_repr_css_set_property (css, "text-anchor", "middle");
951 if(measure_repr) {
952 sp_repr_css_set_property (css, "fill", "#FFFFFF");
953 } else {
954 sp_repr_css_set_property (css, "fill", "#000000");
955 }
956 sp_repr_css_set_property (css, "fill-opacity", "1");
957 sp_repr_css_set_property (css, "stroke", "none");
958 Glib::ustring css_str;
959 sp_repr_css_write_string(css,css_str);
960 rtspan->setAttribute("style", css_str.c_str());
961 sp_repr_css_attr_unref (css);
962 rtext->addChild(rtspan, nullptr);
963 Inkscape::GC::release(rtspan);
964 /* Create TEXT */
965 Inkscape::XML::Node *rstring = xml_doc->createTextNode(value);
966 rtspan->addChild(rstring, nullptr);
967 Inkscape::GC::release(rstring);
968 SPItem *text_item = SP_ITEM(desktop->currentLayer()->appendChildRepr(rtext));
969 Inkscape::GC::release(rtext);
970 text_item->updateRepr();
971 Geom::OptRect bbox = text_item->geometricBounds();
972 if (!measure_repr && bbox) {
973 Geom::Point center = bbox->midpoint();
974 text_item->transform *= Geom::Translate(center).inverse();
975 pos += Geom::Point::polar(angle+ Geom::rad_from_deg(90), -bbox->height());
976 }
977 if(measure_repr) {
978 /* Create <group> */
979 Inkscape::XML::Node *rgroup = xml_doc->createElement("svg:g");
980 /* Create <rect> */
981 Inkscape::XML::Node *rrect = xml_doc->createElement("svg:rect");
982 SPCSSAttr *css = sp_repr_css_attr_new ();
983 gchar color_line[64];
984 sp_svg_write_color (color_line, sizeof(color_line), background);
985 sp_repr_css_set_property (css, "fill", color_line);
986 sp_repr_css_set_property (css, "fill-opacity", "0.5");
987 sp_repr_css_set_property (css, "stroke-width", "0");
988 Glib::ustring css_str;
989 sp_repr_css_write_string(css,css_str);
990 rrect->setAttribute("style", css_str.c_str());
991 sp_repr_css_attr_unref (css);
992 sp_repr_set_svg_double(rgroup, "x", 0);
993 sp_repr_set_svg_double(rgroup, "y", 0);
994 sp_repr_set_svg_double(rrect, "x", -bbox->width()/2.0);
995 sp_repr_set_svg_double(rrect, "y", -bbox->height());
996 sp_repr_set_svg_double(rrect, "width", bbox->width() + 6);
997 sp_repr_set_svg_double(rrect, "height", bbox->height() + 6);
998 Inkscape::XML::Node *rtextitem = text_item->getRepr();
999 text_item->deleteObject();
1000 rgroup->addChild(rtextitem, nullptr);
1001 Inkscape::GC::release(rtextitem);
1002 rgroup->addChild(rrect, nullptr);
1003 Inkscape::GC::release(rrect);
1004 SPItem *text_item_box = SP_ITEM(desktop->currentLayer()->appendChildRepr(rgroup));
1005 Geom::Scale scale = Geom::Scale(desktop->current_zoom()).inverse();
1006 if(bbox && text_anchor == TEXT_ANCHOR_CENTER) {
1007 text_item_box->transform *= Geom::Translate(bbox->midpoint() - Geom::Point(1.0,1.0)).inverse();
1008 }
1009 text_item_box->transform *= scale;
1010 text_item_box->transform *= Geom::Translate(Geom::Point() - (scale.vector() * 0.5));
1011 text_item_box->transform *= Geom::Translate(pos);
1012 text_item_box->transform *= SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
1013 text_item_box->updateRepr();
1014 text_item_box->doWriteTransform(text_item_box->transform, nullptr, true);
1015 Inkscape::XML::Node *rlabel = text_item_box->getRepr();
1016 text_item_box->deleteObject();
1017 measure_repr->addChild(rlabel, nullptr);
1018 Inkscape::GC::release(rlabel);
1019 } else {
1020 text_item->transform *= Geom::Rotate(angle);
1021 text_item->transform *= Geom::Translate(pos);
1022 text_item->transform *= SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
1023 text_item->doWriteTransform(text_item->transform, nullptr, true);
1024 }
1025 }
1026
1027 void LivecodeTool::reset()
1028 {
1029 this->knot_start->hide();
1030 this->knot_end->hide();
1031 for (auto & measure_tmp_item : measure_tmp_items) {
1032 sp_canvas_item_destroy(measure_tmp_item);
1033 }
1034 measure_tmp_items.clear();
1035 }
1036
1037 void LivecodeTool::setMeasureCanvasText(bool is_angle, double precision, double amount, double fontsize, Glib::ustring unit_name, Geom::Point position, guint32 background, CanvasTextAnchorPositionEnum text_anchor, bool to_item, bool to_phantom, Inkscape::XML::Node *measure_repr)
1038 {
1039 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1040 std::stringstream precision_str;
1041 precision_str.imbue(std::locale::classic());
1042 if(is_angle){
1043 precision_str << "%." << precision << "f °";
1044 } else {
1045 precision_str << "%." << precision << "f %s";
1046 }
1047 gchar *measure_str = g_strdup_printf(precision_str.str().c_str(), amount, unit_name.c_str());
1048 SPCanvasText *canvas_tooltip = sp_canvastext_new(desktop->getTempGroup(),
1049 desktop,
1050 position,
1051 measure_str);
1052 sp_canvastext_set_fontsize(canvas_tooltip, fontsize);
1053 canvas_tooltip->rgba = 0xffffffff;
1054 canvas_tooltip->rgba_background = background;
1055 canvas_tooltip->outline = false;
1056 canvas_tooltip->background = true;
1057 canvas_tooltip->anchor_position = text_anchor;
1058 if(to_phantom){
1059 canvas_tooltip->rgba_background = 0x4444447f;
1060 measure_phantom_items.push_back(SP_CANVAS_ITEM(canvas_tooltip));
1061 sp_canvas_item_show(SP_CANVAS_ITEM(canvas_tooltip));
1062 } else {
1063 measure_tmp_items.push_back(SP_CANVAS_ITEM(canvas_tooltip));
1064 sp_canvas_item_show(SP_CANVAS_ITEM(canvas_tooltip));
1065 }
1066
1067 if(to_item) {
1068 setLabelText(measure_str, position, fontsize, 0, background, measure_repr);
1069 }
1070 g_free(measure_str);
1071 }
1072
1073 void LivecodeTool::setMeasureCanvasItem(Geom::Point position, bool to_item, bool to_phantom, Inkscape::XML::Node *measure_repr){
1074 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1075 guint32 color = 0xff0000ff;
1076 if(to_phantom){
1077 color = 0x888888ff;
1078 }
1079 SPCanvasItem * canvasitem = sp_canvas_item_new(desktop->getTempGroup(),
1080 SP_TYPE_CTRL,
1081 "anchor", SP_ANCHOR_CENTER,
1082 "size", 9,
1083 "stroked", TRUE,
1084 "stroke_color", color,
1085 "mode", SP_KNOT_MODE_XOR,
1086 "shape", SP_KNOT_SHAPE_CROSS,
1087 NULL );
1088
1089 SP_CTRL(canvasitem)->moveto(position);
1090 if(to_phantom){
1091 measure_phantom_items.push_back(canvasitem);
1092 } else {
1093 measure_tmp_items.push_back(canvasitem);
1094 }
1095 sp_canvas_item_show(canvasitem);
1096 sp_canvas_item_move_to_z(canvasitem, 0);
1097
1098 if(to_item) {
1099 setPoint(position, measure_repr);
1100 }
1101 }
1102
1103 void LivecodeTool::setMeasureCanvasControlLine(Geom::Point start, Geom::Point end, bool to_item, bool to_phantom, Inkscape::CtrlLineType ctrl_line_type, Inkscape::XML::Node *measure_repr){
1104 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1105 gint32 color = ctrl_line_type == CTLINE_PRIMARY ? 0x0000ff7f : 0xff00007f;
1106 if(to_phantom){
1107 color = ctrl_line_type == CTLINE_PRIMARY ? 0x4444447f : 0x8888887f;
1108 }
1109 SPCtrlLine *control_line = ControlManager::getManager().createControlLine(desktop->getTempGroup(),
1110 start,
1111 end,
1112 ctrl_line_type);
1113 control_line->rgba = color;
1114 if(to_phantom){
1115 measure_phantom_items.push_back(SP_CANVAS_ITEM(control_line));
1116 } else {
1117 measure_tmp_items.push_back(SP_CANVAS_ITEM(control_line));
1118 }
1119 sp_canvas_item_move_to_z(SP_CANVAS_ITEM(control_line), 0);
1120 sp_canvas_item_show(SP_CANVAS_ITEM(control_line));
1121 if(to_item) {
1122 setLine(start,
1123 end,
1124 false,
1125 color,
1126 measure_repr);
1127 }
1128 }
1129
1130 void LivecodeTool::showItemInfoText(Geom::Point pos, gchar *measure_str, double fontsize)
1131 {
1132 SPCanvasText *canvas_tooltip = sp_canvastext_new(desktop->getTempGroup(),
1133 desktop,
1134 pos,
1135 measure_str);
1136 sp_canvastext_set_fontsize(canvas_tooltip, fontsize);
1137 canvas_tooltip->rgba = 0xffffffff;
1138 canvas_tooltip->outline = false;
1139 canvas_tooltip->background = true;
1140 canvas_tooltip->anchor_position = TEXT_ANCHOR_LEFT;
1141 canvas_tooltip->rgba_background = 0x00000099;
1142 measure_item.push_back(SP_CANVAS_ITEM(canvas_tooltip));
1143 sp_canvas_item_show(SP_CANVAS_ITEM(canvas_tooltip));
1144 }
1145
1146 void LivecodeTool::showInfoBox(Geom::Point cursor, bool into_groups)
1147 {
1148 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1149 Inkscape::Util::Unit const * unit = desktop->getNamedView()->getDisplayUnit();
1150 for (auto & idx : measure_item) {
1151 sp_canvas_item_destroy(idx);
1152 }
1153 measure_item.clear();
1154
1155 SPItem *newover = desktop->getItemAtPoint(cursor, into_groups);
1156 if (newover) {
1157 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1158 double fontsize = prefs->getDouble("/tools/measure/fontsize", 10.0);
1159 double scale = prefs->getDouble("/tools/measure/scale", 100.0) / 100.0;
1160 int precision = prefs->getInt("/tools/measure/precision", 2);
1161 Glib::ustring unit_name = prefs->getString("/tools/measure/unit");
1162 bool only_selected = prefs->getBool("/tools/measure/only_selected", false);
1163 if (!unit_name.compare("")) {
1164 unit_name = DEFAULT_UNIT_NAME;
1165 }
1166 Geom::Scale zoom = Geom::Scale(Inkscape::Util::Quantity::convert(desktop->current_zoom(), "px", unit->abbr)).inverse();
1167 if(newover != over){
1168 over = newover;
1169 Preferences *prefs = Preferences::get();
1170 int prefs_bbox = prefs->getBool("/tools/bounding_box", false);
1171 SPItem::BBoxType bbox_type = !prefs_bbox ? SPItem::VISUAL_BBOX : SPItem::GEOMETRIC_BBOX;
1172 Geom::OptRect bbox = over->bounds(bbox_type);
1173 if (bbox) {
1174
1175 item_width = Inkscape::Util::Quantity::convert((*bbox).width() * scale, unit->abbr, unit_name);
1176 item_height = Inkscape::Util::Quantity::convert((*bbox).height() * scale, unit->abbr, unit_name);
1177 item_x = Inkscape::Util::Quantity::convert((*bbox).left(), unit->abbr, unit_name);
1178 Geom::Point y_point(0,Inkscape::Util::Quantity::convert((*bbox).bottom() * scale, unit->abbr, "px"));
1179 y_point *= desktop->doc2dt();
1180 item_y = Inkscape::Util::Quantity::convert(y_point[Geom::Y] * scale, "px", unit_name);
1181 if (SP_IS_SHAPE(over)) {
1182 Geom::PathVector shape = SP_SHAPE(over)->getCurve()->get_pathvector();
1183 item_length = Geom::length(paths_to_pw(shape));
1184 item_length = Inkscape::Util::Quantity::convert(item_length * scale, unit->abbr, unit_name);
1185 }
1186 }
1187 }
1188 gchar *measure_str = nullptr;
1189 std::stringstream precision_str;
1190 precision_str.imbue(std::locale::classic());
1191 double origin = Inkscape::Util::Quantity::convert(14, "px", unit->abbr);
1192 Geom::Point rel_position = Geom::Point(origin, origin);
1193 Geom::Point pos = desktop->w2d(cursor);
1194 double gap = Inkscape::Util::Quantity::convert(7 + fontsize, "px", unit->abbr);
1195 if (only_selected) {
1196 if (desktop->getSelection()->includes(over)) {
1197 showItemInfoText(pos + (rel_position * zoom),_("Selected"),fontsize);
1198 } else {
1199 showItemInfoText(pos + (rel_position * zoom),_("Not selected"),fontsize);
1200 }
1201 rel_position = Geom::Point(rel_position[Geom::X], rel_position[Geom::Y] + gap);
1202 }
1203 if (SP_IS_SHAPE(over)) {
1204 precision_str << _("Length") << ": %." << precision << "f %s";
1205 measure_str = g_strdup_printf(precision_str.str().c_str(), item_length, unit_name.c_str());
1206 precision_str.str("");
1207 showItemInfoText(pos + (rel_position * zoom),measure_str,fontsize);
1208 rel_position = Geom::Point(rel_position[Geom::X], rel_position[Geom::Y] + gap);
1209 } else if (SP_IS_GROUP(over)) {
1210 measure_str = _("Press 'CTRL' to measure into group");
1211 showItemInfoText(pos + (rel_position * zoom),measure_str,fontsize);
1212 rel_position = Geom::Point(rel_position[Geom::X], rel_position[Geom::Y] + gap);
1213 }
1214 precision_str << "Y: %." << precision << "f %s";
1215 measure_str = g_strdup_printf(precision_str.str().c_str(), item_y, unit_name.c_str());
1216 precision_str.str("");
1217 showItemInfoText(pos + (rel_position * zoom),measure_str,fontsize);
1218 rel_position = Geom::Point(rel_position[Geom::X], rel_position[Geom::Y] + gap);
1219
1220 precision_str << "X: %." << precision << "f %s";
1221 measure_str = g_strdup_printf(precision_str.str().c_str(), item_x, unit_name.c_str());
1222 precision_str.str("");
1223 showItemInfoText(pos + (rel_position * zoom),measure_str,fontsize);
1224 rel_position = Geom::Point(rel_position[Geom::X], rel_position[Geom::Y] + gap);
1225
1226 precision_str << _("Height") << ": %." << precision << "f %s";
1227 measure_str = g_strdup_printf(precision_str.str().c_str(), item_height, unit_name.c_str());
1228 precision_str.str("");
1229 showItemInfoText(pos + (rel_position * zoom),measure_str,fontsize);
1230 rel_position = Geom::Point(rel_position[Geom::X], rel_position[Geom::Y] + gap);
1231
1232 precision_str << _("Width") << ": %." << precision << "f %s";
1233 measure_str = g_strdup_printf(precision_str.str().c_str(), item_width, unit_name.c_str());
1234 precision_str.str("");
1235 showItemInfoText(pos + (rel_position * zoom),measure_str,fontsize);
1236 g_free(measure_str);
1237 }
1238 }
1239
1240 void LivecodeTool::showCanvasItems(bool to_guides, bool to_item, bool to_phantom, Inkscape::XML::Node *measure_repr)
1241 {
1242 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1243 if(!desktop || !start_p.isFinite() || !end_p.isFinite() || start_p == end_p) {
1244 return;
1245 }
1246 writeMeasurePoint(start_p, true);
1247 writeMeasurePoint(end_p, false);
1248 //clear previous canvas items, we'll draw new ones
1249 for (auto & measure_tmp_item : measure_tmp_items) {
1250 sp_canvas_item_destroy(measure_tmp_item);
1251 }
1252 measure_tmp_items.clear();
1253 //TODO:Calculate the measure area for current length and origin
1254 // and use canvas->requestRedraw. In the calculation need a gap for outside text
1255 // maybe this remove the trash lines on measure use
1256 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1257 bool show_in_between = prefs->getBool("/tools/measure/show_in_between", true);
1258 bool all_layers = prefs->getBool("/tools/measure/all_layers", true);
1259 dimension_offset = 70;
1260 Geom::PathVector lineseg;
1261 Geom::Path p;
1262 Geom::Point start_p_doc = start_p * desktop->dt2doc();
1263 Geom::Point end_p_doc = end_p * desktop->dt2doc();
1264 p.start(start_p_doc);
1265 p.appendNew<Geom::LineSegment>(end_p_doc);
1266 lineseg.push_back(p);
1267
1268 double angle = atan2(end_p - start_p);
1269 double baseAngle = 0;
1270
1271 if (explicit_base) {
1272 baseAngle = atan2(explicit_base.get() - start_p);
1273 angle -= baseAngle;
1274 }
1275
1276 std::vector<SPItem*> items;
1277 SPDocument *doc = desktop->getDocument();
1278 Geom::Rect rect(start_p_doc, end_p_doc);
1279 items = doc->getItemsPartiallyInBox(desktop->dkey, rect, false, true, false, true);
1280 Inkscape::LayerModel *layer_model = nullptr;
1281 SPObject *current_layer = nullptr;
1282 if(desktop){
1283 layer_model = desktop->layers;
1284 current_layer = desktop->currentLayer();
1285 }
1286 std::vector<double> intersection_times;
1287 bool only_selected = prefs->getBool("/tools/measure/only_selected", false);
1288 for (std::vector<SPItem*>::const_iterator i=items.begin(); i!=items.end(); ++i) {
1289 SPItem *item = *i;
1290 if (!desktop->getSelection()->includes(*i) && only_selected) {
1291 continue;
1292 }
1293 if(all_layers || (layer_model && layer_model->layerForObject(item) == current_layer)){
1294 if (SP_IS_SHAPE(item)) {
1295 calculate_intersections(desktop, item, lineseg, SP_SHAPE(item)->getCurve(), intersection_times);
1296 } else {
1297 if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
1298 Inkscape::Text::Layout::iterator iter = te_get_layout(item)->begin();
1299 do {
1300 Inkscape::Text::Layout::iterator iter_next = iter;
1301 iter_next.nextGlyph(); // iter_next is one glyph ahead from iter
1302 if (iter == iter_next) {
1303 break;
1304 }
1305
1306 // get path from iter to iter_next:
1307 SPCurve *curve = te_get_layout(item)->convertToCurves(iter, iter_next);
1308 iter = iter_next; // shift to next glyph
1309 if (!curve) {
1310 continue; // error converting this glyph
1311 }
1312 if (curve->is_empty()) { // whitespace glyph?
1313 curve->unref();
1314 continue;
1315 }
1316
1317 calculate_intersections(desktop, item, lineseg, curve, intersection_times);
1318 if (iter == te_get_layout(item)->end()) {
1319 break;
1320 }
1321 } while (true);
1322 }
1323 }
1324 }
1325 }
1326 Glib::ustring unit_name = prefs->getString("/tools/measure/unit");
1327 if (!unit_name.compare("")) {
1328 unit_name = DEFAULT_UNIT_NAME;
1329 }
1330 double scale = prefs->getDouble("/tools/measure/scale", 100.0) / 100.0;
1331 double fontsize = prefs->getDouble("/tools/measure/fontsize", 10.0);
1332 // Normal will be used for lines and text
1333 Geom::Point windowNormal = Geom::unit_vector(Geom::rot90(desktop->d2w(end_p - start_p)));
1334 Geom::Point normal = desktop->w2d(windowNormal);
1335
1336 std::vector<Geom::Point> intersections;
1337 std::sort(intersection_times.begin(), intersection_times.end());
1338 for (double & intersection_time : intersection_times) {
1339 intersections.push_back(lineseg[0].pointAt(intersection_time));
1340 }
1341
1342 if(!show_in_between && intersection_times.size() > 1) {
1343 Geom::Point start = lineseg[0].pointAt(intersection_times[0]);
1344 Geom::Point end = lineseg[0].pointAt(intersection_times[intersection_times.size()-1]);
1345 intersections.clear();
1346 intersections.push_back(start);
1347 intersections.push_back(end);
1348 }
1349 if (!prefs->getBool("/tools/measure/ignore_1st_and_last", true)) {
1350 intersections.insert(intersections.begin(),lineseg[0].pointAt(0));
1351 intersections.push_back(lineseg[0].pointAt(1));
1352 }
1353 std::vector<LabelPlacement> placements;
1354 for (size_t idx = 1; idx < intersections.size(); ++idx) {
1355 LabelPlacement placement;
1356 placement.lengthVal = (intersections[idx] - intersections[idx - 1]).length();
1357 placement.lengthVal = Inkscape::Util::Quantity::convert(placement.lengthVal, "px", unit_name);
1358 placement.offset = dimension_offset / 2;
1359 placement.start = desktop->doc2dt( (intersections[idx - 1] + intersections[idx]) / 2 );
1360 placement.end = placement.start - (normal * placement.offset);
1361
1362 placements.push_back(placement);
1363 }
1364 int precision = prefs->getInt("/tools/measure/precision", 2);
1365 // Adjust positions
1366 repositionOverlappingLabels(placements, desktop, windowNormal, fontsize, precision);
1367 for (auto & place : placements) {
1368 setMeasureCanvasText(false, precision, place.lengthVal * scale, fontsize, unit_name, place.end, 0x0000007f, TEXT_ANCHOR_CENTER, to_item, to_phantom, measure_repr);
1369 }
1370 Geom::Point angleDisplayPt = calcAngleDisplayAnchor(desktop, angle, baseAngle,
1371 start_p, end_p,
1372 fontsize);
1373 {
1374 setMeasureCanvasText(true, precision, Geom::deg_from_rad(angle), fontsize, unit_name, angleDisplayPt, 0x337f337f, TEXT_ANCHOR_CENTER, to_item, to_phantom, measure_repr);
1375 }
1376
1377 {
1378 double totallengthval = (end_p - start_p).length();
1379 totallengthval = Inkscape::Util::Quantity::convert(totallengthval, "px", unit_name);
1380 Geom::Point origin = end_p + desktop->w2d(Geom::Point(3*fontsize, -fontsize));
1381 setMeasureCanvasText(false, precision, totallengthval * scale, fontsize, unit_name, origin, 0x3333337f, TEXT_ANCHOR_LEFT, to_item, to_phantom, measure_repr);
1382 }
1383
1384 if (intersections.size() > 2) {
1385 double totallengthval = (intersections[intersections.size()-1] - intersections[0]).length();
1386 totallengthval = Inkscape::Util::Quantity::convert(totallengthval, "px", unit_name);
1387 Geom::Point origin = desktop->doc2dt((intersections[0] + intersections[intersections.size()-1])/2) + normal * dimension_offset;
1388 setMeasureCanvasText(false, precision, totallengthval * scale, fontsize, unit_name, origin, 0x33337f7f, TEXT_ANCHOR_CENTER, to_item, to_phantom, measure_repr);
1389 }
1390
1391 // Initial point
1392 {
1393 setMeasureCanvasItem(start_p, false, to_phantom, measure_repr);
1394 }
1395
1396 // Now that text has been added, we can add lines and controls so that they go underneath
1397 for (size_t idx = 0; idx < intersections.size(); ++idx) {
1398 setMeasureCanvasItem(desktop->doc2dt(intersections[idx]), to_item, to_phantom, measure_repr);
1399 if(to_guides) {
1400 gchar *cross_number;
1401 if (!prefs->getBool("/tools/measure/ignore_1st_and_last", true)) {
1402 cross_number= g_strdup_printf(_("Crossing %lu"), static_cast<unsigned long>(idx));
1403 } else {
1404 cross_number= g_strdup_printf(_("Crossing %lu"), static_cast<unsigned long>(idx + 1));
1405 }
1406 if (!prefs->getBool("/tools/measure/ignore_1st_and_last", true) && idx == 0) {
1407 setGuide(desktop->doc2dt(intersections[idx]), angle + Geom::rad_from_deg(90), "");
1408 } else {
1409 setGuide(desktop->doc2dt(intersections[idx]), angle + Geom::rad_from_deg(90), cross_number);
1410 }
1411 g_free(cross_number);
1412 }
1413 }
1414 // Since adding goes to the bottom, do all lines last.
1415
1416 // draw main control line
1417 {
1418 setMeasureCanvasControlLine(start_p, end_p, false, to_phantom, CTLINE_PRIMARY, measure_repr);
1419 double length = std::abs((end_p - start_p).length());
1420 Geom::Point anchorEnd = start_p;
1421 anchorEnd[Geom::X] += length;
1422 if (explicit_base) {
1423 anchorEnd *= (Geom::Affine(Geom::Translate(-start_p))
1424 * Geom::Affine(Geom::Rotate(baseAngle))
1425 * Geom::Affine(Geom::Translate(start_p)));
1426 }
1427 setMeasureCanvasControlLine(start_p, anchorEnd, to_item, to_phantom, CTLINE_SECONDARY, measure_repr);
1428 createAngleDisplayCurve(desktop, start_p, end_p, angleDisplayPt, angle, to_phantom, measure_phantom_items, measure_tmp_items, measure_repr);
1429 }
1430
1431 if (intersections.size() > 2) {
1432 setMeasureCanvasControlLine(desktop->doc2dt(intersections[0]) + normal * dimension_offset, desktop->doc2dt(intersections[intersections.size() - 1]) + normal * dimension_offset, to_item, to_phantom, CTLINE_PRIMARY , measure_repr);
1433
1434 setMeasureCanvasControlLine(desktop->doc2dt(intersections[0]), desktop->doc2dt(intersections[0]) + normal * dimension_offset, to_item, to_phantom, CTLINE_PRIMARY , measure_repr);
1435
1436 setMeasureCanvasControlLine(desktop->doc2dt(intersections[intersections.size() - 1]), desktop->doc2dt(intersections[intersections.size() - 1]) + normal * dimension_offset, to_item, to_phantom, CTLINE_PRIMARY , measure_repr);
1437 }
1438
1439 // call-out lines
1440 for (auto & place : placements) {
1441 setMeasureCanvasControlLine(place.start, place.end, to_item, to_phantom, CTLINE_SECONDARY, measure_repr);
1442 }
1443
1444 {
1445 for (size_t idx = 1; idx < intersections.size(); ++idx) {
1446 Geom::Point measure_text_pos = (intersections[idx - 1] + intersections[idx]) / 2;
1447 setMeasureCanvasControlLine(desktop->doc2dt(measure_text_pos), desktop->doc2dt(measure_text_pos) - (normal * dimension_offset / 2), to_item, to_phantom, CTLINE_SECONDARY, measure_repr);
1448 }
1449 }
1450 }
1451107
1452108 }
1453109 }
22 #define SEEN_SP_LIVECODE_CONTEXT_H
33
44 /*
5 * Our fine measuring tool
5 * An experimental Livecoding Tool
66 *
77 * Authors:
8 * Felipe Correa da Silva Sanches <juca@members.fsf.org>
9 * Jabiertxo Arraiza <jabier.arraiza@marker.es>
10 * Copyright (C) 2011 Authors
8 * Sol Bekic <s+inkscape@s-ol.nu>
9 * Copyright (C) 2019 Authors
1110 *
1211 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
1312 */
1918 #include "display/canvas-text.h"
2019 #include "display/canvas-temporary-item.h"
2120 #include "ui/control-manager.h"
21 #include "livecode/api.h"
2222 #include <boost/optional.hpp>
2323
2424 #define SP_LIVECODE_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::LivecodeTool*>((Inkscape::UI::Tools::ToolBase*)obj))
3737
3838 static const std::string prefsPath;
3939
40 void setup() override;
4041 void finish() override;
4142 bool root_handler(GdkEvent* event) override;
42 virtual void showCanvasItems(bool to_guides = false, bool to_item = false, bool to_phantom = false, Inkscape::XML::Node *measure_repr = nullptr);
43 virtual void reverseKnots();
44 virtual void toGuides();
45 virtual void toPhantom();
46 virtual void toMarkDimension();
47 virtual void toItem();
48 virtual void reset();
49 virtual void setMarkers();
50 virtual void setMarker(bool isStart);
5143 const std::string& getPrefsPath() override;
52 Geom::Point readMeasurePoint(bool is_start);
53 void showInfoBox(Geom::Point cursor, bool into_groups);
54 void showItemInfoText(Geom::Point pos, gchar *measure_str, double fontsize);
55 void writeMeasurePoint(Geom::Point point, bool is_start);
56 void setGuide(Geom::Point origin, double angle, const char *label);
57 void setPoint(Geom::Point origin, Inkscape::XML::Node *measure_repr);
58 void setLine(Geom::Point start_point,Geom::Point end_point, bool markers, guint32 color, Inkscape::XML::Node *measure_repr = nullptr);
59 void setMeasureCanvasText(bool is_angle, double precision, double amount, double fontsize, Glib::ustring unit_name, Geom::Point position, guint32 background, CanvasTextAnchorPositionEnum text_anchor, bool to_item, bool to_phantom, Inkscape::XML::Node *measure_repr);
60 void setMeasureCanvasItem(Geom::Point position, bool to_item, bool to_phantom, Inkscape::XML::Node *measure_repr);
61 void setMeasureCanvasControlLine(Geom::Point start, Geom::Point end, bool to_item, bool to_phantom, Inkscape::CtrlLineType ctrl_line_type, Inkscape::XML::Node *measure_repr);
62 void setLabelText(const char *value, Geom::Point pos, double fontsize, Geom::Coord angle, guint32 background , Inkscape::XML::Node *measure_repr = nullptr, CanvasTextAnchorPositionEnum text_anchor = TEXT_ANCHOR_CENTER );
63 void knotStartMovedHandler(SPKnot */*knot*/, Geom::Point const &ppointer, guint state);
64 void knotEndMovedHandler(SPKnot */*knot*/, Geom::Point const &ppointer, guint state);
65 void knotClickHandler(SPKnot *knot, guint state);
66 void knotUngrabbedHandler(SPKnot */*knot*/, unsigned int /*state*/);
44
6745 private:
68 SPCanvasItem* grabbed;
69 boost::optional<Geom::Point> explicit_base;
70 boost::optional<Geom::Point> last_end;
71 SPKnot *knot_start;
72 SPKnot *knot_end;
73 gint dimension_offset;
74 Geom::Point start_p;
75 Geom::Point end_p;
76 Geom::Point last_pos;
77 std::vector<SPCanvasItem *> measure_tmp_items;
78 std::vector<SPCanvasItem *> measure_phantom_items;
79 std::vector<SPCanvasItem *> measure_item;
80 double item_width;
81 double item_height;
82 double item_x;
83 double item_y;
84 double item_length;
85 SPItem *over;
86 sigc::connection _knot_start_moved_connection;
87 sigc::connection _knot_start_ungrabbed_connection;
88 sigc::connection _knot_start_click_connection;
89 sigc::connection _knot_end_moved_connection;
90 sigc::connection _knot_end_click_connection;
91 sigc::connection _knot_end_ungrabbed_connection;
46 bool handle_tick(Glib::RefPtr<Gdk::FrameClock> const &frame_clock);
47
48 guint tick_callback;
49 Livecode::API api;
9250 };
9351
9452 }