0 | 0 |
// SPDX-License-Identifier: GPL-2.0-or-later
|
1 | 1 |
/*
|
2 | |
* Our nice measuring tool
|
|
2 |
* An experimental Livecoding Tool
|
3 | 3 |
*
|
4 | 4 |
* 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
|
10 | 7 |
*
|
11 | 8 |
* Released under GNU GPL v2+, read the file 'COPYING' for more information.
|
12 | 9 |
*/
|
|
16 | 13 |
|
17 | 14 |
#include <boost/none_t.hpp>
|
18 | 15 |
|
19 | |
#include <2geom/line.h>
|
20 | |
#include <2geom/path-intersection.h>
|
21 | |
|
22 | 16 |
#include "desktop-style.h"
|
23 | 17 |
#include "desktop.h"
|
24 | |
#include "document-undo.h"
|
25 | 18 |
#include "inkscape.h"
|
26 | |
#include "path-chemistry.h"
|
27 | |
#include "rubberband.h"
|
28 | |
#include "text-editing.h"
|
29 | 19 |
#include "verbs.h"
|
30 | 20 |
|
31 | |
#include "display/curve.h"
|
32 | 21 |
#include "display/sodipodi-ctrl.h"
|
33 | 22 |
#include "display/sp-canvas-util.h"
|
34 | 23 |
#include "display/sp-canvas.h"
|
35 | |
#include "display/sp-ctrlcurve.h"
|
36 | |
#include "display/sp-ctrlline.h"
|
37 | 24 |
|
38 | 25 |
#include "object/sp-defs.h"
|
39 | 26 |
#include "object/sp-flowtext.h"
|
|
42 | 29 |
#include "object/sp-shape.h"
|
43 | 30 |
#include "object/sp-text.h"
|
44 | 31 |
|
45 | |
#include "ui/pixmaps/cursor-measure.xpm"
|
46 | |
|
47 | 32 |
#include "svg/stringstream.h"
|
48 | 33 |
#include "svg/svg-color.h"
|
49 | 34 |
#include "svg/svg.h"
|
50 | 35 |
|
51 | |
#include "ui/dialog/knot-properties.h"
|
52 | |
#include "ui/tools/freehand-base.h"
|
53 | 36 |
#include "ui/tools/livecode-tool.h"
|
54 | 37 |
|
55 | 38 |
#include "util/units.h"
|
56 | 39 |
|
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
|
64 | 40 |
|
65 | 41 |
|
66 | 42 |
namespace Inkscape {
|
|
72 | 48 |
return LivecodeTool::prefsPath;
|
73 | 49 |
}
|
74 | 50 |
|
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 ¢er, 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";
|
330 | 52 |
|
331 | 53 |
LivecodeTool::LivecodeTool()
|
332 | |
: ToolBase(cursor_measure_xpm)
|
333 | |
, grabbed(nullptr)
|
|
54 |
: ToolBase(nullptr, false)
|
|
55 |
, api(SP_ACTIVE_DESKTOP)
|
334 | 56 |
{
|
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 | |
|
373 | 57 |
}
|
374 | 58 |
|
375 | 59 |
LivecodeTool::~LivecodeTool()
|
376 | 60 |
{
|
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();
|
397 | 61 |
}
|
398 | 62 |
|
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));
|
403 | 69 |
}
|
404 | 70 |
|
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?
|
488 | 71 |
void LivecodeTool::finish()
|
489 | 72 |
{
|
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);
|
496 | 75 |
|
497 | 76 |
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 | |
}
|
524 | 77 |
}
|
525 | 78 |
|
526 | 79 |
bool LivecodeTool::root_handler(GdkEvent* event)
|
527 | 80 |
{
|
528 | 81 |
gint ret = FALSE;
|
529 | 82 |
|
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);
|
665 | 84 |
if (!ret) {
|
666 | |
ret = ToolBase::root_handler(event);
|
|
85 |
api.push_event(event);
|
667 | 86 |
}
|
668 | 87 |
|
669 | 88 |
return ret;
|
670 | 89 |
}
|
671 | 90 |
|
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)
|
673 | 96 |
{
|
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;
|
719 | 105 |
}
|
720 | 106 |
|
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 | |
}
|
1451 | 107 |
|
1452 | 108 |
}
|
1453 | 109 |
}
|