|
0 |
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
1 |
/*
|
|
2 |
* Our nice measuring tool
|
|
3 |
*
|
|
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
|
|
10 |
*
|
|
11 |
* Released under GNU GPL v2+, read the file 'COPYING' for more information.
|
|
12 |
*/
|
|
13 |
|
|
14 |
#include <gtkmm.h>
|
|
15 |
#include <glibmm/i18n.h>
|
|
16 |
|
|
17 |
#include <boost/none_t.hpp>
|
|
18 |
|
|
19 |
#include <2geom/line.h>
|
|
20 |
#include <2geom/path-intersection.h>
|
|
21 |
|
|
22 |
#include "desktop-style.h"
|
|
23 |
#include "desktop.h"
|
|
24 |
#include "document-undo.h"
|
|
25 |
#include "inkscape.h"
|
|
26 |
#include "path-chemistry.h"
|
|
27 |
#include "rubberband.h"
|
|
28 |
#include "text-editing.h"
|
|
29 |
#include "verbs.h"
|
|
30 |
|
|
31 |
#include "display/curve.h"
|
|
32 |
#include "display/sodipodi-ctrl.h"
|
|
33 |
#include "display/sp-canvas-util.h"
|
|
34 |
#include "display/sp-canvas.h"
|
|
35 |
#include "display/sp-ctrlcurve.h"
|
|
36 |
#include "display/sp-ctrlline.h"
|
|
37 |
|
|
38 |
#include "object/sp-defs.h"
|
|
39 |
#include "object/sp-flowtext.h"
|
|
40 |
#include "object/sp-namedview.h"
|
|
41 |
#include "object/sp-root.h"
|
|
42 |
#include "object/sp-shape.h"
|
|
43 |
#include "object/sp-text.h"
|
|
44 |
|
|
45 |
#include "ui/pixmaps/cursor-measure.xpm"
|
|
46 |
|
|
47 |
#include "svg/stringstream.h"
|
|
48 |
#include "svg/svg-color.h"
|
|
49 |
#include "svg/svg.h"
|
|
50 |
|
|
51 |
#include "ui/dialog/knot-properties.h"
|
|
52 |
#include "ui/tools/freehand-base.h"
|
|
53 |
#include "ui/tools/livecode-tool.h"
|
|
54 |
|
|
55 |
#include "util/units.h"
|
|
56 |
|
|
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 |
|
|
65 |
|
|
66 |
namespace Inkscape {
|
|
67 |
namespace UI {
|
|
68 |
namespace Tools {
|
|
69 |
|
|
70 |
const std::string& LivecodeTool::getPrefsPath()
|
|
71 |
{
|
|
72 |
return LivecodeTool::prefsPath;
|
|
73 |
}
|
|
74 |
|
|
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
|
|
330 |
|
|
331 |
LivecodeTool::LivecodeTool()
|
|
332 |
: ToolBase(cursor_measure_xpm)
|
|
333 |
, grabbed(nullptr)
|
|
334 |
{
|
|
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 |
}
|
|
374 |
|
|
375 |
LivecodeTool::~LivecodeTool()
|
|
376 |
{
|
|
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 |
}
|
|
398 |
|
|
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()));
|
|
403 |
}
|
|
404 |
|
|
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 |
void LivecodeTool::finish()
|
|
489 |
{
|
|
490 |
this->enableGrDrag(false);
|
|
491 |
|
|
492 |
if (this->grabbed) {
|
|
493 |
sp_canvas_item_ungrab(this->grabbed);
|
|
494 |
this->grabbed = nullptr;
|
|
495 |
}
|
|
496 |
|
|
497 |
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 |
}
|
|
525 |
|
|
526 |
bool LivecodeTool::root_handler(GdkEvent* event)
|
|
527 |
{
|
|
528 |
gint ret = FALSE;
|
|
529 |
|
|
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 |
}
|
|
665 |
if (!ret) {
|
|
666 |
ret = ToolBase::root_handler(event);
|
|
667 |
}
|
|
668 |
|
|
669 |
return ret;
|
|
670 |
}
|
|
671 |
|
|
672 |
void LivecodeTool::setMarkers()
|
|
673 |
{
|
|
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();
|
|
719 |
}
|
|
720 |
|
|
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 |
|
|
1452 |
}
|
|
1453 |
}
|
|
1454 |
}
|
|
1455 |
|
|
1456 |
/*
|
|
1457 |
Local Variables:
|
|
1458 |
mode:c++
|
|
1459 |
c-file-style:"stroustrup"
|
|
1460 |
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
|
|
1461 |
indent-tabs-mode:nil
|
|
1462 |
fill-column:99
|
|
1463 |
End:
|
|
1464 |
*/
|
|
1465 |
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
|