Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
measure-tool.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Our nice measuring tool
4 *
5 * Authors:
6 * Felipe Correa da Silva Sanches <juca@members.fsf.org>
7 * Jon A. Cruz <jon@joncruz.org>
8 * Jabiertxo Arraiza <jabier.arraiza@marker.es>
9 *
10 * Copyright (C) 2011 Authors
11 *
12 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
13 */
14
15#include "measure-tool.h"
16
17#include <iomanip>
18
19#include <gtkmm.h>
20#include <glibmm/i18n.h>
21
22#include <2geom/line.h>
24
25#include "colors/utils.h"
26#include "desktop-style.h"
27#include "desktop.h"
28#include "document-undo.h"
29#include "layer-manager.h"
30#include "page-manager.h"
31#include "path-chemistry.h"
32#include "selection.h"
33#include "text-editing.h"
34
35#include "display/curve.h"
40
41#include "helper/geom.h"
42
43#include "object/sp-defs.h"
44#include "object/sp-ellipse.h"
45#include "object/sp-flowtext.h"
46#include "object/sp-namedview.h"
47#include "object/sp-root.h"
48#include "object/sp-shape.h"
49#include "object/sp-text.h"
50
51#include "svg/svg.h"
52
53#include "ui/clipboard.h"
55#include "ui/icon-names.h"
56#include "ui/knot/knot.h"
58#include "ui/widget/canvas.h" // Canvas area
60
61#include "util/units.h"
63
65
66namespace Inkscape::UI::Tools {
67namespace {
68
72struct LabelPlacement
73{
74 Glib::ustring label;
75 double lengthVal;
76 double offset;
79};
80
81bool SortLabelPlacement(LabelPlacement const &first, LabelPlacement const &second)
82{
83 if (first.end.y() == second.end.y()) {
84 return first.end.x() < second.end.x();
85 } else {
86 return first.end.y() < second.end.y();
87 }
88}
89
90//precision is for give the number of decimal positions
91//of the label to calculate label width
92void repositionOverlappingLabels(std::vector<LabelPlacement> &placements, SPDesktop *desktop, Geom::Point const &normal, double fontsize, int precision)
93{
94 std::sort(placements.begin(), placements.end(), SortLabelPlacement);
95
96 double border = 3;
97 Geom::Rect box;
98 {
99 Geom::Point tmp(fontsize * (6 + precision) + (border * 2), fontsize + (border * 2));
100 tmp = desktop->w2d(tmp);
101 box = Geom::Rect(-tmp[Geom::X] / 2, -tmp[Geom::Y] / 2, tmp[Geom::X] / 2, tmp[Geom::Y] / 2);
102 }
103
104 // Using index since vector may be re-ordered as we go.
105 // Starting at one, since the first item can't overlap itself
106 for (size_t i = 1; i < placements.size(); i++) {
107 LabelPlacement &place = placements[i];
108
109 bool changed = false;
110 do {
111 Geom::Rect current(box + place.end);
112
113 changed = false;
114 bool overlaps = false;
115 for (size_t j = i; (j > 0) && !overlaps; --j) {
116 LabelPlacement &otherPlace = placements[j - 1];
117 Geom::Rect target(box + otherPlace.end);
118 if (current.intersects(target)) {
119 overlaps = true;
120 }
121 }
122 if (overlaps) {
123 place.offset += (fontsize + border);
124 place.end = place.start - desktop->w2d(normal * place.offset);
125 changed = true;
126 }
127 } while (changed);
128
129 std::sort(placements.begin(), placements.begin() + i + 1, SortLabelPlacement);
130 }
131}
132
143Geom::Point calcAngleDisplayAnchor(SPDesktop *desktop, double angle, double baseAngle,
144 Geom::Point const &startPoint, Geom::Point const &endPoint,
145 double fontsize)
146{
147 // Time for the trick work of figuring out where things should go, and how.
148 double lengthVal = (endPoint - startPoint).length();
149 double effective = baseAngle + angle / 2;
150 auto where = Geom::Point(lengthVal, 0) * Geom::Rotate(effective) * Geom::Translate(startPoint);
151
152 // When the angle is tight, the label would end up under the cursor and/or lines. Bump it
153 double scaledFontsize = std::abs(fontsize * desktop->w2d(Geom::Point(0, 1)).y());
154 if (std::abs((where - endPoint).length()) < scaledFontsize) {
155 where.y() += scaledFontsize * 2;
156 }
157
158 // We now have the ideal position, but need to see if it will fit/work.
159
160 Geom::Rect screen_world = desktop->getCanvas()->get_area_world();
161 if (screen_world.interiorContains(desktop->d2w(startPoint)) ||
162 screen_world.interiorContains(desktop->d2w(endPoint))) {
163 screen_world.expandBy(fontsize * -3, fontsize / -2);
164 where = desktop->w2d(screen_world.clamp(desktop->d2w(where)));
165 } // else likely initialized the measurement tool, keep display near the measurement.
166
167 return where;
168}
169
170
183Geom::Point calcDeltaLabelTextPos(std::vector<LabelPlacement> placements, SPDesktop *desktop, Geom::Point basePoint,
184 double fontsize, Glib::ustring unit_name, int maxStrLength, Geom::Point normal, bool is_dX = true)
185{
186 double border = 3;
187 Geom::Rect box;
188 {
189 Geom::Point tmp((fontsize * maxStrLength * 0.66) + (border * 2), fontsize + (border * 2));
190 tmp = desktop->w2d(tmp);
191 box = Geom::Rect(-tmp[Geom::X] / 2, -tmp[Geom::Y] / 2, tmp[Geom::X] / 2, tmp[Geom::Y] / 2);
192 }
193 Geom::Point textPos = basePoint;
194 double step;
195 if (is_dX) {
196 step = normal[Geom::Y] * fontsize * 2; // the label box is bigger than the font...
197 textPos[Geom::Y] += step * 1.5; // bringing it slightly higher at the initial position
198 } else {
199 step = normal[Geom::X] * fontsize * 2;
200 textPos[Geom::X] += step;
201 }
202
203 bool changed = false;
204 do {
205 changed = false;
206 for (auto item : placements) { // placements are not ordered so checking all of them
207 Geom::Rect itemBox(box + item.end);
208 Geom::Rect boxDelta(box + textPos);
209 if (boxDelta.intersects(itemBox)) {
210 if (is_dX) {
211 textPos[Geom::Y] += step; // the normals to dX and dY are always horizontal/vertical
212 } else {
213 textPos[Geom::X] += step;
214 }
215 changed = true;
216 }
217 }
218 } while (changed);
219 return textPos;
220}
221
222} // namespace
223
236 double angle, bool to_phantom,
237 Inkscape::XML::Node *measure_repr)
238{
239 // Given that we have a point on the arc's edge and the angle of the arc, we need to get the two endpoints.
240
241 double textLen = std::abs((anchor - center).length());
242 double sideLen = std::abs((end - center).length());
243 if (sideLen > 0.0) {
244 double factor = std::min(1.0, textLen / sideLen);
245
246 // arc start
247 Geom::Point p1 = end * Geom::Translate(-center)
248 * Geom::Scale(factor)
249 * Geom::Translate(center);
250
251 // arc end
252 Geom::Point p4 = p1 * Geom::Translate(-center)
253 * Geom::Rotate(-angle)
254 * Geom::Translate(center);
255
256 // from Riskus
257 double xc = center[Geom::X];
258 double yc = center[Geom::Y];
259 double ax = p1[Geom::X] - xc;
260 double ay = p1[Geom::Y] - yc;
261 double bx = p4[Geom::X] - xc;
262 double by = p4[Geom::Y] - yc;
263 double q1 = (ax * ax) + (ay * ay);
264 double q2 = q1 + (ax * bx) + (ay * by);
265
266 double k2;
267
268 /*
269 * The denominator of the expression for k2 can become 0, so this should be handled.
270 * The function for k2 tends to a limit for very small values of (ax * by) - (ay * bx), so theoretically
271 * it should be correct for values close to 0, however due to floating point inaccuracies this
272 * is not the case, and instabilities still exist. Therefore do a range check on the denominator.
273 * (This also solves some instances where again due to floating point inaccuracies, the square root term
274 * becomes slightly negative in case of very small values for ax * by - ay * bx).
275 * The values of this range have been generated by trying to make this term as small as possible,
276 * by zooming in as much as possible in the GUI, using the measurement tool and
277 * trying to get as close to 180 or 0 degrees as possible.
278 * Smallest value I was able to get was around 1e-5, and then I added some zeroes for good measure.
279 */
280 if (!((ax * by - ay * bx < 0.00000000001) && (ax * by - ay * bx > -0.00000000001))) {
281 k2 = (4.0 / 3.0) * (std::sqrt(2 * q1 * q2) - q2) / ((ax * by) - (ay * bx));
282 } else {
283 // If the denominator is 0, there are 2 cases:
284 // Either the angle is (almost) +-180 degrees, in which case the limit of k2 tends to -+4.0/3.0.
285 if (angle > 3.14 || angle < -3.14) { // The angle is in radians
286 // Now there are also 2 cases, where inkscape thinks it is 180 degrees, or -180 degrees.
287 // Adjust the value of k2 accordingly
288 if (angle > 0) {
289 k2 = -4.0 / 3.0;
290 } else {
291 k2 = 4.0 / 3.0;
292 }
293 } else {
294 // if the angle is (almost) 0, k2 is equal to 0
295 k2 = 0.0;
296 }
297 }
298
299 Geom::Point p2(xc + ax - (k2 * ay),
300 yc + ay + (k2 * ax));
301 Geom::Point p3(xc + bx + (k2 * by),
302 yc + by - (k2 * bx));
303
304 auto *curve = new Inkscape::CanvasItemCurve(_desktop->getCanvasTemp(), p1, p2, p3, p4);
305 curve->set_name("CanvasItemCurve:MeasureToolCurve");
307 curve->lower_to_bottom();
308 curve->set_visible(true);
309 if (to_phantom){
310 curve->set_stroke(0x8888887f);
311 measure_phantom_items.emplace_back(curve);
312 } else {
313 measure_tmp_items.emplace_back(curve);
314 }
315
316 if (measure_repr) {
317 Geom::PathVector pathv;
318 Geom::Path path;
319 path.start(_desktop->doc2dt(p1));
321 pathv.push_back(path);
322 auto layer = _desktop->layerManager().currentLayer();
323 pathv *= layer->i2doc_affine().inverse();
324 if (!pathv.empty()) {
325 setMeasureItem(pathv, true, false, 0xff00007f, measure_repr);
326 }
327 }
328 }
329}
330
331static std::optional<Geom::Point> explicit_base_tmp;
332
334 : ToolBase(desktop, "/tools/measure", "measure.svg")
335{
337 end_p = readMeasurePoint(false);
338
339 // create the knots
340 this->knot_start = new SPKnot(desktop, _("Measure start, <b>Shift+Click</b> for position dialog"),
341 Inkscape::CANVAS_ITEM_CTRL_TYPE_POINT, "CanvasItemCtrl:MeasureTool");
342 this->knot_start->updateCtrl();
343 this->knot_start->moveto(start_p);
344 this->knot_start->show();
345
346 this->knot_end = new SPKnot(desktop, _("Measure end, <b>Shift+Click</b> for position dialog"),
347 Inkscape::CANVAS_ITEM_CTRL_TYPE_POINT, "CanvasItemCtrl:MeasureTool");
348 this->knot_end->updateCtrl();
349 this->knot_end->moveto(end_p);
350 this->knot_end->show();
351
353
354 this->_knot_start_moved_connection = this->knot_start->moved_signal.connect(sigc::mem_fun(*this, &MeasureTool::knotStartMovedHandler));
355 this->_knot_start_click_connection = this->knot_start->click_signal.connect(sigc::mem_fun(*this, &MeasureTool::knotClickHandler));
357 this->_knot_end_moved_connection = this->knot_end->moved_signal.connect(sigc::mem_fun(*this, &MeasureTool::knotEndMovedHandler));
358 this->_knot_end_click_connection = this->knot_end->click_signal.connect(sigc::mem_fun(*this, &MeasureTool::knotClickHandler));
360}
361
363{
364 enableGrDrag(false);
366
367 // unref should call destroy
370}
371
372static char const *endpoint_to_pref(bool is_start)
373{
374 return is_start ? "/tools/measure/measure-start" : "/tools/measure/measure-end";
375}
376
381
382void MeasureTool::writeMeasurePoint(Geom::Point point, bool is_start) const
383{
384 Preferences::get()->setPoint(endpoint_to_pref(is_start), point);
385}
386
387//This function is used to reverse the Measure, I do it in two steps because when
388//we move the knot the start_ or the end_p are overwritten so I need the original values.
390{
391 auto const start = start_p;
392 auto const end = end_p;
394 knot_start->show();
396 knot_end->show();
397 start_p = end;
398 end_p = start;
400}
401
402void MeasureTool::knotClickHandler(SPKnot *knot, guint state)
403{
404 if (state & GDK_SHIFT_MASK) {
405 auto prefs = Preferences::get();
406 auto const unit_name = prefs->getString("/tools/measure/unit", "px");
409 }
410}
411
412void MeasureTool::knotStartMovedHandler(SPKnot */*knot*/, Geom::Point const &ppointer, guint state)
413{
414 Geom::Point point = this->knot_start->position();
415 if (state & GDK_CONTROL_MASK) {
416 spdc_endpoint_snap_rotation(this, point, end_p, state);
417 } else if (!(state & GDK_SHIFT_MASK)) {
418 SnapManager &snap_manager = _desktop->getNamedView()->snap_manager;
419 snap_manager.setup(_desktop);
421 scp.addOrigin(this->knot_end->position());
422 Inkscape::SnappedPoint sp = snap_manager.freeSnap(scp);
423 point = sp.getPoint();
424 snap_manager.unSetup();
425 }
426 if(start_p != point) {
427 start_p = point;
428 this->knot_start->moveto(start_p);
429 }
431}
432
433void MeasureTool::knotEndMovedHandler(SPKnot */*knot*/, Geom::Point const &ppointer, guint state)
434{
435 Geom::Point point = this->knot_end->position();
436 if (state & GDK_CONTROL_MASK) {
437 spdc_endpoint_snap_rotation(this, point, start_p, state);
438 } else if (!(state & GDK_SHIFT_MASK)) {
439 SnapManager &snap_manager = _desktop->getNamedView()->snap_manager;
440 snap_manager.setup(_desktop);
442 scp.addOrigin(this->knot_start->position());
443 Inkscape::SnappedPoint sp = snap_manager.freeSnap(scp);
444 point = sp.getPoint();
445 snap_manager.unSetup();
446 }
447 if(end_p != point) {
448 end_p = point;
449 this->knot_end->moveto(end_p);
450 }
452}
453
454void MeasureTool::knotUngrabbedHandler(SPKnot */*knot*/, unsigned state)
455{
459}
460
462 SPCurve curve, std::vector<double> &intersections)
463{
464 curve.transform(item->i2doc_affine());
465 // Find all intersections of the control-line with this shape
466 Geom::CrossingSet cs = Geom::crossings(lineseg, curve.get_pathvector());
468
469 // Reconstruct and store the points of intersection
471 bool show_hidden = prefs->getBool("/tools/measure/show_hidden", true);
472 for (const auto & m : cs[0]) {
473 if (!show_hidden) {
474 double eps = 0.0001;
475 if ((m.ta > eps &&
476 item == desktop->getItemAtPoint(desktop->d2w(desktop->dt2doc(lineseg[0].pointAt(m.ta - eps))), true, nullptr)) ||
477 (m.ta + eps < 1 &&
478 item == desktop->getItemAtPoint(desktop->d2w(desktop->dt2doc(lineseg[0].pointAt(m.ta + eps))), true, nullptr))) {
479 intersections.push_back(m.ta);
480 }
481 } else {
482 intersections.push_back(m.ta);
483 }
484 }
485}
486
488{
489 bool ret = false;
490
491 inspect_event(event,
492 [&] (ButtonPressEvent const &event) {
493 if (event.num_press != 1 || event.button != 1) {
494 return;
495 }
496 knot_start->hide();
497 knot_end->hide();
498 explicit_base = {};
500 last_end = {};
501
502 saveDragOrigin(event.pos);
503 start_p = _desktop->w2d(event.pos);
504
505 auto &snap_manager = _desktop->getNamedView()->snap_manager;
506 snap_manager.setup(_desktop);
507 snap_manager.freeSnapReturnByRef(start_p, SNAPSOURCE_OTHER_HANDLE);
508 snap_manager.unSetup();
509
515 ret = true;
516 },
517 [&] (KeyPressEvent const &event) {
518 if (event.keyval == GDK_KEY_Control_L || event.keyval == GDK_KEY_Control_R) {
521 showInfoBox(last_pos, true);
522 }
523 if ((event.modifiers & GDK_ALT_MASK ) && ((event.keyval == GDK_KEY_c) || (event.keyval == GDK_KEY_C))) {
525 ret = true;
526 }
527 },
528 [&] (KeyReleaseEvent const &event) {
529 if (event.keyval == GDK_KEY_Control_L || event.keyval == GDK_KEY_Control_R) {
530 showInfoBox(last_pos, false);
531 }
532 },
533 [&] (MotionEvent const &event) {
534 if (!(event.modifiers & GDK_BUTTON1_MASK)) {
535 if (!(event.modifiers & GDK_SHIFT_MASK)) {
536 auto const motion_dt = _desktop->w2d(event.pos);
537
538 auto &snap_manager = _desktop->getNamedView()->snap_manager;
539 snap_manager.setup(_desktop);
540
541 auto scp = SnapCandidatePoint(motion_dt, SNAPSOURCE_OTHER_HANDLE);
542 scp.addOrigin(start_p);
543
544 snap_manager.preSnap(scp);
545 snap_manager.unSetup();
546 }
547 last_pos = event.pos;
548 showInfoBox(last_pos, event.modifiers & GDK_CONTROL_MASK);
549 } else {
550 if (!checkDragMoved(event.pos)) {
551 return;
552 }
553
554 auto prefs = Preferences::get();
555 tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
556
557 measure_item.clear();
558
559 if (!last_end || Geom::LInfty(event.pos - *last_end) > tolerance / 4.0) {
560 auto const motion_dt = _desktop->w2d(event.pos);
561 end_p = motion_dt;
562
563 if (event.modifiers & GDK_CONTROL_MASK) {
565 } else if (!(event.modifiers & GDK_SHIFT_MASK)) {
566 auto &snap_manager = _desktop->getNamedView()->snap_manager;
567 snap_manager.setup(_desktop);
569 scp.addOrigin(start_p);
570 auto const sp = snap_manager.freeSnap(scp);
571 end_p = sp.getPoint();
572 snap_manager.unSetup();
573 }
575 last_end = event.pos;
576 }
577 gobble_motion_events(GDK_BUTTON1_MASK);
578
579 ret = true;
580 }
581 },
582 [&] (ButtonReleaseEvent const &event) {
583 if (event.button != 1) {
584 return;
585 }
587 knot_start->show();
588 if (last_end) {
590 if (event.modifiers & GDK_CONTROL_MASK) {
592 } else if (!(event.modifiers & GDK_SHIFT_MASK)) {
593 auto &snap_manager = _desktop->getNamedView()->snap_manager;
594 snap_manager.setup(_desktop);
596 scp.addOrigin(start_p);
597 auto const sp = snap_manager.freeSnap(scp);
598 end_p = sp.getPoint();
599 snap_manager.unSetup();
600 }
601 }
603 knot_end->show();
605
607 },
608 [&] (CanvasEvent const &event) {}
609 );
610
611 return ret || ToolBase::root_handler(event);
612}
613
615{
617 SPObject *arrowStart = doc->getObjectById("Arrow2Sstart");
618 SPObject *arrowEnd = doc->getObjectById("Arrow2Send");
619 if (!arrowStart) {
620 setMarker(true);
621 }
622 if (!arrowEnd) {
623 setMarker(false);
624 }
625}
626
627void MeasureTool::setMarker(bool isStart)
628{
630 SPDefs *defs = doc->getDefs();
631 Inkscape::XML::Node *rmarker;
632 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
633 rmarker = xml_doc->createElement("svg:marker");
634 rmarker->setAttribute("id", isStart ? "Arrow2Sstart" : "Arrow2Send");
635 rmarker->setAttribute("inkscape:isstock", "true");
636 rmarker->setAttribute("inkscape:stockid", isStart ? "Arrow2Sstart" : "Arrow2Send");
637 rmarker->setAttribute("orient", "auto");
638 rmarker->setAttribute("refX", "0.0");
639 rmarker->setAttribute("refY", "0.0");
640 rmarker->setAttribute("style", "overflow:visible;");
641 auto marker = cast<SPItem>(defs->appendChildRepr(rmarker));
642 Inkscape::GC::release(rmarker);
643 marker->updateRepr();
644 Inkscape::XML::Node *rpath;
645 rpath = xml_doc->createElement("svg:path");
646 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");
647 rpath->setAttribute("id", isStart ? "Arrow2SstartPath" : "Arrow2SendPath");
649 sp_repr_css_set_property (css, "stroke", "none");
650 sp_repr_css_set_property (css, "fill", "#000000");
651 sp_repr_css_set_property (css, "fill-opacity", "1");
652 Glib::ustring css_str;
654 rpath->setAttribute("style", css_str);
656 rpath->setAttribute("transform", isStart ? "scale(0.3) translate(-2.3,0)" : "scale(0.3) rotate(180) translate(-2.3,0)");
657 auto path = cast<SPItem>(marker->appendChildRepr(rpath));
659 path->updateRepr();
660}
661
663{
664 if (!_desktop || !start_p.isFinite() || !end_p.isFinite() || start_p == end_p) {
665 return;
666 }
670 Geom::Ray ray(start,end);
671 SPNamedView *namedview = _desktop->getNamedView();
672 if(!namedview) {
673 return;
674 }
675 setGuide(start,ray.angle(), _("Measure"));
676 if(explicit_base) {
677 auto layer = _desktop->layerManager().currentLayer();
680 if(ray.angle() != 0) {
681 setGuide(start,ray.angle(), _("Base"));
682 }
683 }
684 setGuide(start,0,"");
685 setGuide(start,Geom::rad_from_deg(90),_("Start"));
686 setGuide(end,0,_("End"));
687 setGuide(end,Geom::rad_from_deg(90),"");
688 showCanvasItems(true);
689 doc->ensureUpToDate();
690 DocumentUndo::done(_desktop->getDocument(), _("Add guides from measure tool"), INKSCAPE_ICON("tool-measure"));
691}
692
694{
695 if (!_desktop || !start_p.isFinite() || !end_p.isFinite() || start_p == end_p) {
696 return;
697 }
699
700 measure_phantom_items.clear();
701 measure_tmp_items.clear();
702
703 showCanvasItems(false, false, true);
704 doc->ensureUpToDate();
705 DocumentUndo::done(_desktop->getDocument(), _("Keep last measure on the canvas, for reference"), INKSCAPE_ICON("tool-measure"));
706}
707
709{
710 if (!_desktop || !start_p.isFinite() || !end_p.isFinite() || start_p == end_p) {
711 return;
712 }
715 guint32 line_color_primary = 0x0000ff7f;
717 Inkscape::XML::Node *rgroup = xml_doc->createElement("svg:g");
718 showCanvasItems(false, true, false, rgroup);
719 setLine(start_p,end_p, false, line_color_primary, rgroup);
720 auto measure_item = cast<SPItem>(_desktop->layerManager().currentLayer()->appendChildRepr(rgroup));
721 Inkscape::GC::release(rgroup);
722 measure_item->updateRepr();
723 doc->ensureUpToDate();
724 DocumentUndo::done(_desktop->getDocument(), _("Convert measure to items"), INKSCAPE_ICON("tool-measure"));
725 reset();
726}
727
729{
730 if (!_desktop || !start_p.isFinite() || !end_p.isFinite() || start_p == end_p) {
731 return;
732 }
734 setMarkers();
736 Geom::Point start = start_p + Geom::Point::polar(ray.angle(), 5);
738 dimension_offset = prefs->getDouble("/tools/measure/offset", 5.0);
739 start = start + Geom::Point::polar(ray.angle() + Geom::rad_from_deg(90), -dimension_offset);
740 Geom::Point end = end_p + Geom::Point::polar(ray.angle(), -5);
741 end = end+ Geom::Point::polar(ray.angle() + Geom::rad_from_deg(90), -dimension_offset);
742 guint32 color = 0x000000ff;
743 setLine(start, end, true, color);
744 Glib::ustring unit_name = prefs->getString("/tools/measure/unit");
745 if (!unit_name.compare("")) {
746 unit_name = DEFAULT_UNIT_NAME;
747 }
748 double fontsize = prefs->getDouble("/tools/measure/fontsize", 10.0);
749
751 double totallengthval = (end_p - start_p).length();
752 totallengthval = Inkscape::Util::Quantity::convert(totallengthval, "px", unit_name);
753 double scale = prefs->getDouble("/tools/measure/scale", 100.0) / 100.0;
754
755 int precision = prefs->getInt("/tools/measure/precision", 2);
756 Glib::ustring total = Inkscape::ustring::format_classic(std::fixed, std::setprecision(precision), totallengthval * scale);
757 total += unit_name;
758
759 double textangle = Geom::rad_from_deg(180) - ray.angle();
760 if (_desktop->is_yaxisdown()) {
761 textangle = ray.angle() - Geom::rad_from_deg(180);
762 }
763
764 setLabelText(total, middle, fontsize, textangle, color);
765
766 doc->ensureUpToDate();
767 DocumentUndo::done(_desktop->getDocument(), _("Add global measure line"), INKSCAPE_ICON("tool-measure"));
768}
769
770void MeasureTool::setGuide(Geom::Point origin, double angle, const char *label)
771{
773 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
774 SPRoot const *root = doc->getRoot();
776 if(root) {
777 affine *= root->c2p.inverse();
778 }
779 SPNamedView *namedview = _desktop->getNamedView();
780 if(!namedview) {
781 return;
782 }
783
784 // <sodipodi:guide> stores inverted y-axis coordinates
785 if (_desktop->is_yaxisdown()) {
786 origin[Geom::Y] = doc->getHeight().value("px") - origin[Geom::Y];
787 angle *= -1.0;
788 }
789
790 origin *= affine;
791 //measure angle
792 Inkscape::XML::Node *guide;
793 guide = xml_doc->createElement("sodipodi:guide");
794 std::stringstream position;
795 position.imbue(std::locale::classic());
796 position << origin[Geom::X] << "," << origin[Geom::Y];
797 guide->setAttribute("position", position.str() );
798 guide->setAttribute("inkscape:color", "rgb(167,0,255)");
799 guide->setAttribute("inkscape:label", label);
800 Geom::Point unit_vector = Geom::rot90(origin.polar(angle));
801 std::stringstream angle_str;
802 angle_str.imbue(std::locale::classic());
803 angle_str << unit_vector[Geom::X] << "," << unit_vector[Geom::Y];
804 guide->setAttribute("orientation", angle_str.str());
805 namedview->appendChild(guide);
807}
808
809void MeasureTool::setLine(Geom::Point start_point,Geom::Point end_point, bool markers, guint32 color, Inkscape::XML::Node *measure_repr)
810{
811 if (!_desktop || !start_p.isFinite() || !end_p.isFinite()) {
812 return;
813 }
814 Geom::PathVector pathv;
815 Geom::Path path;
816 path.start(_desktop->doc2dt(start_point));
817 path.appendNew<Geom::LineSegment>(_desktop->doc2dt(end_point));
818 pathv.push_back(path);
820 if(!pathv.empty()) {
821 setMeasureItem(pathv, false, markers, color, measure_repr);
822 }
823}
824
826{
827 if (!_desktop || !origin.isFinite()) {
828 return;
829 }
830 char const * svgd;
831 svgd = "m 0.707,0.707 6.586,6.586 m 0,-6.586 -6.586,6.586";
834 pathv *= Geom::Translate(Geom::Point(-3.5,-3.5));
835 pathv *= scale;
836 pathv *= Geom::Translate(Geom::Point() - (scale.vector() * 0.5));
839 if (!pathv.empty()) {
840 guint32 line_color_secondary = 0xff0000ff;
841 setMeasureItem(pathv, false, false, line_color_secondary, measure_repr);
842 }
843}
844
845void MeasureTool::setLabelText(Glib::ustring const &value, Geom::Point pos, double fontsize, Geom::Coord angle,
846 guint32 background, Inkscape::XML::Node *measure_repr)
847{
849 /* Create <text> */
850 pos = _desktop->doc2dt(pos);
851 Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text");
852 rtext->setAttribute("xml:space", "preserve");
853
854
855 /* Set style */
856 sp_desktop_apply_style_tool(_desktop, rtext, "/tools/text", true);
857 if(measure_repr) {
858 rtext->setAttributeSvgDouble("x", 2);
859 rtext->setAttributeSvgDouble("y", 2);
860 } else {
861 rtext->setAttributeSvgDouble("x", 0);
862 rtext->setAttributeSvgDouble("y", 0);
863 }
864
865 /* Create <tspan> */
866 Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan");
867 rtspan->setAttribute("sodipodi:role", "line");
869 std::stringstream font_size;
870 font_size.imbue(std::locale::classic());
871 if(measure_repr) {
872 font_size << fontsize;
873 } else {
874 font_size << fontsize << "pt";
875 }
876 sp_repr_css_set_property (css, "font-size", font_size.str().c_str());
877 sp_repr_css_set_property (css, "font-style", "normal");
878 sp_repr_css_set_property (css, "font-weight", "normal");
879 sp_repr_css_set_property (css, "line-height", "125%");
880 sp_repr_css_set_property (css, "letter-spacing", "0");
881 sp_repr_css_set_property (css, "word-spacing", "0");
882 sp_repr_css_set_property (css, "text-align", "center");
883 sp_repr_css_set_property (css, "text-anchor", "middle");
884 if(measure_repr) {
885 sp_repr_css_set_property (css, "fill", "#FFFFFF");
886 } else {
887 sp_repr_css_set_property (css, "fill", "#000000");
888 }
889 sp_repr_css_set_property (css, "fill-opacity", "1");
890 sp_repr_css_set_property (css, "stroke", "none");
891 Glib::ustring css_str;
893 rtspan->setAttribute("style", css_str);
895 rtext->addChild(rtspan, nullptr);
896 Inkscape::GC::release(rtspan);
897 /* Create TEXT */
898 Inkscape::XML::Node *rstring = xml_doc->createTextNode(value.c_str());
899 rtspan->addChild(rstring, nullptr);
900 Inkscape::GC::release(rstring);
901 auto layer = _desktop->layerManager().currentLayer();
902 auto text_item = cast<SPText>(layer->appendChildRepr(rtext));
904 text_item->rebuildLayout();
905 text_item->updateRepr();
906 Geom::OptRect bbox = text_item->geometricBounds();
907 if (!measure_repr && bbox) {
908 Geom::Point center = bbox->midpoint();
909 text_item->transform *= Geom::Translate(center).inverse();
910 pos += Geom::Point::polar(angle+ Geom::rad_from_deg(90), -bbox->height());
911 }
912 if (measure_repr) {
913 /* Create <group> */
914 Inkscape::XML::Node *rgroup = xml_doc->createElement("svg:g");
915 /* Create <rect> */
916 Inkscape::XML::Node *rrect = xml_doc->createElement("svg:rect");
919 sp_repr_css_set_property_double(css, "fill-opacity", 0.5);
920 sp_repr_css_set_property (css, "stroke-width", "0");
921 Glib::ustring css_str;
923 rrect->setAttribute("style", css_str);
925 rgroup->setAttributeSvgDouble("x", 0);
926 rgroup->setAttributeSvgDouble("y", 0);
927 rrect->setAttributeSvgDouble("x", -bbox->width()/2.0);
928 rrect->setAttributeSvgDouble("y", -bbox->height());
929 rrect->setAttributeSvgDouble("width", bbox->width() + 6);
930 rrect->setAttributeSvgDouble("height", bbox->height() + 6);
931 Inkscape::XML::Node *rtextitem = text_item->getRepr();
932 text_item->deleteObject();
933 rgroup->addChild(rtextitem, nullptr);
934 Inkscape::GC::release(rtextitem);
935 rgroup->addChild(rrect, nullptr);
937 auto text_item_box = cast<SPItem>(layer->appendChildRepr(rgroup));
939 if(bbox) {
940 text_item_box->transform *= Geom::Translate(bbox->midpoint() - Geom::Point(1.0,1.0)).inverse();
941 }
942 text_item_box->transform *= scale;
943 text_item_box->transform *= Geom::Translate(Geom::Point() - (scale.vector() * 0.5));
944 text_item_box->transform *= Geom::Translate(pos);
945 text_item_box->transform *= layer->i2doc_affine().inverse();
946 text_item_box->updateRepr();
947 text_item_box->doWriteTransform(text_item_box->transform, nullptr, true);
948 Inkscape::XML::Node *rlabel = text_item_box->getRepr();
949 text_item_box->deleteObject();
950 measure_repr->addChild(rlabel, nullptr);
951 Inkscape::GC::release(rlabel);
952 } else {
953 text_item->transform *= Geom::Rotate(angle);
954 text_item->transform *= Geom::Translate(pos);
955 text_item->transform *= layer->i2doc_affine().inverse();
956 text_item->doWriteTransform(text_item->transform, nullptr, true);
957 }
958}
959
961{
962 knot_start->hide();
963 knot_end->hide();
964
965 measure_tmp_items.clear();
966}
967
968void MeasureTool::setMeasureCanvasText(bool is_angle, double precision, double amount, double fontsize,
969 Glib::ustring unit_name, Geom::Point position, guint32 background,
970 bool to_left, bool to_item,
971 bool to_phantom, Inkscape::XML::Node *measure_repr, Glib::ustring label)
972{
973 Glib::ustring measure = Inkscape::ustring::format_classic(std::setprecision(precision), std::fixed, amount);
974 measure += " ";
975 measure += (is_angle ? "°" : unit_name);
976 if (!label.empty()) { measure = label + ": " + measure; }
977 auto canvas_tooltip = new Inkscape::CanvasItemText(_desktop->getCanvasTemp(), position, measure);
978 canvas_tooltip->set_fontsize(fontsize);
979 canvas_tooltip->set_fill(0xffffffff);
980 canvas_tooltip->set_background(background);
981 if (to_left) {
982 canvas_tooltip->set_anchor(Geom::Point(0, 0.5));
983 } else {
984 canvas_tooltip->set_anchor(Geom::Point(0.5, 0.5));
985 }
986
987 if (to_phantom){
988 canvas_tooltip->set_background(0x4444447f);
989 measure_phantom_items.emplace_back(canvas_tooltip);
990 } else {
991 measure_tmp_items.emplace_back(canvas_tooltip);
992 }
993
994 if (to_item) {
995 setLabelText(measure, position, fontsize, 0, background, measure_repr);
996 }
997
998 canvas_tooltip->set_visible(true);
999}
1000
1001void MeasureTool::setMeasureCanvasItem(Geom::Point position, bool to_item, bool to_phantom, XML::Node *measure_repr)
1002{
1003 uint32_t color = 0xff0000ff;
1004 if (to_phantom) {
1005 color = 0x888888ff;
1006 }
1007
1009 canvas_item->lower_to_bottom();
1010 canvas_item->set_pickable(false);
1011 canvas_item->set_visible(true);
1012
1013 (to_phantom ? measure_phantom_items : measure_tmp_items).emplace_back(std::move(canvas_item));
1014
1015 if (to_item) {
1016 setPoint(position, measure_repr);
1017 }
1018}
1019
1021 CanvasItemColor ctrl_line_type,
1022 XML::Node *measure_repr)
1023{
1024 uint32_t color = ctrl_line_type == CANVAS_ITEM_PRIMARY ? 0x0000ff7f : 0xff00007f;
1025 if (to_phantom) {
1026 color = ctrl_line_type == CANVAS_ITEM_PRIMARY ? 0x4444447f : 0x8888887f;
1027 }
1028
1029 auto control_line = make_canvasitem<CanvasItemCurve>(_desktop->getCanvasTemp(), start, end);
1030 control_line->set_stroke(color);
1031 control_line->lower_to_bottom();
1032 control_line->set_visible(true);
1033
1034 (to_phantom ? measure_phantom_items : measure_tmp_items).emplace_back(std::move(control_line));
1035
1036 if (to_item) {
1037 setLine(start, end, false, color, measure_repr);
1038 }
1039}
1040
1041// This is the text that follows the cursor around.
1042void MeasureTool::showItemInfoText(Geom::Point pos, Glib::ustring const &measure_str, double fontsize)
1043{
1044 auto canvas_tooltip = make_canvasitem<CanvasItemText>(_desktop->getCanvasTemp(), pos, measure_str);
1045 canvas_tooltip->set_fontsize(fontsize);
1046 canvas_tooltip->set_fill(0xffffffff);
1047 canvas_tooltip->set_background(0x00000099);
1048 canvas_tooltip->set_anchor(Geom::Point());
1049 canvas_tooltip->set_fixed_line(true);
1050 canvas_tooltip->set_visible(true);
1051 measure_item.emplace_back(std::move(canvas_tooltip));
1052}
1053
1054void MeasureTool::showInfoBox(Geom::Point cursor, bool into_groups)
1055{
1057
1058 measure_item.clear();
1059
1060 auto newover = _desktop->getItemAtPoint(cursor, into_groups);
1061 if (!newover) {
1062 // Clear over when the cursor isn't over anything.
1063 over = nullptr;
1064 clipBMeas.unsetShapeMeasures(); // shape measurements are not set and will not be copied to the clipboard
1065 return;
1066 }
1067 auto unit = _desktop->getNamedView()->getDisplayUnit();
1068
1069 // Load preferences for measuring the new object.
1070 auto prefs = Preferences::get();
1071 int precision = prefs->getInt("/tools/measure/precision", 2);
1072 bool selected = prefs->getBool("/tools/measure/only_selected", false);
1073 auto box_type = prefs->getBool("/tools/bounding_box", false) ? SPItem::GEOMETRIC_BBOX : SPItem::VISUAL_BBOX;
1074 double fontsize = prefs->getDouble("/tools/measure/fontsize", 10.0);
1075 double scale = prefs->getDouble("/tools/measure/scale", 100.0) / 100.0;
1076 Glib::ustring unit_name = prefs->getString("/tools/measure/unit", unit->abbr);
1077
1078 auto const zoom = Geom::Scale(Quantity::convert(_desktop->current_zoom(), "px", unit->abbr)).inverse();
1079
1080 if (newover != over) {
1081 // Get information for the item, and cache it to save time.
1082 over = newover;
1083 auto affine = over->i2dt_affine() * Geom::Scale(scale);
1084 // Correct for the current page's position.
1087 }
1088 if (auto bbox = over->bounds(box_type, affine)) {
1089 item_width = Quantity::convert(bbox->width(), "px", unit_name);
1090 item_height = Quantity::convert(bbox->height(), "px", unit_name);
1091 item_x = Quantity::convert(bbox->left(), "px", unit_name);
1092 item_y = Quantity::convert(bbox->top(), "px", unit_name);
1093
1094 if (auto shape = cast<SPShape>(over)) {
1095 auto pw = paths_to_pw(shape->curve()->get_pathvector());
1096 item_length = Quantity::convert(Geom::length(pw * affine), "px", unit_name);
1097 }
1098 }
1099 }
1100
1101 gchar *measure_str = nullptr;
1102 std::stringstream precision_str;
1103 precision_str.imbue(std::locale::classic());
1104 double origin = Quantity::convert(14, "px", unit->abbr);
1105 double yaxis_shift = Quantity::convert(fontsize, "px", unit->abbr);
1106 Geom::Point rel_position = Geom::Point(origin, origin + yaxis_shift);
1107 /* Keeps infobox just above the cursor */
1108 Geom::Point pos = _desktop->w2d(cursor);
1109 double gap = Quantity::convert(7 + fontsize, "px", unit->abbr);
1110 double yaxisdir = _desktop->yaxisdir();
1111
1112 if (selected) {
1113 showItemInfoText(pos - (yaxisdir * Geom::Point(0, rel_position[Geom::Y]) * zoom), _desktop->getSelection()->includes(over) ? _("Selected") : _("Not selected"), fontsize);
1114 rel_position = Geom::Point(rel_position[Geom::X], rel_position[Geom::Y] + gap);
1115 }
1116
1117 if (is<SPShape>(over)) {
1118
1119 precision_str << _("Length") << ": %." << precision << "f %s";
1120 measure_str = g_strdup_printf(precision_str.str().c_str(), item_length, unit_name.c_str());
1121 precision_str.str("");
1122 showItemInfoText(pos - (yaxisdir * Geom::Point(0, rel_position[Geom::Y]) * zoom), measure_str, fontsize);
1123 rel_position = Geom::Point(rel_position[Geom::X], rel_position[Geom::Y] + gap);
1124
1125 } else if (is<SPGroup>(over)) {
1126
1127 measure_str = _("Press 'CTRL' to measure into group");
1128 showItemInfoText(pos - (yaxisdir * Geom::Point(0, rel_position[Geom::Y]) * zoom), measure_str, fontsize);
1129 rel_position = Geom::Point(rel_position[Geom::X], rel_position[Geom::Y] + gap);
1130
1131 }
1132
1133 precision_str << "Y: %." << precision << "f %s";
1134 measure_str = g_strdup_printf(precision_str.str().c_str(), item_y, unit_name.c_str());
1135 precision_str.str("");
1136 showItemInfoText(pos - (yaxisdir * Geom::Point(0, rel_position[Geom::Y]) * zoom), measure_str, fontsize);
1137 rel_position = Geom::Point(rel_position[Geom::X], rel_position[Geom::Y] + gap);
1138
1139 precision_str << "X: %." << precision << "f %s";
1140 measure_str = g_strdup_printf(precision_str.str().c_str(), item_x, unit_name.c_str());
1141 precision_str.str("");
1142 showItemInfoText(pos - (yaxisdir * Geom::Point(0, rel_position[Geom::Y]) * zoom), measure_str, fontsize);
1143 rel_position = Geom::Point(rel_position[Geom::X], rel_position[Geom::Y] + gap);
1144
1145 precision_str << _("Height") << ": %." << precision << "f %s";
1146 measure_str = g_strdup_printf(precision_str.str().c_str(), item_height, unit_name.c_str());
1147 precision_str.str("");
1148 showItemInfoText(pos - (yaxisdir * Geom::Point(0, rel_position[Geom::Y]) * zoom), measure_str, fontsize);
1149 rel_position = Geom::Point(rel_position[Geom::X], rel_position[Geom::Y] + gap);
1150
1151 precision_str << _("Width") << ": %." << precision << "f %s";
1152 measure_str = g_strdup_printf(precision_str.str().c_str(), item_width, unit_name.c_str());
1153 precision_str.str("");
1154 showItemInfoText(pos - (yaxisdir * Geom::Point(0, rel_position[Geom::Y]) * zoom), measure_str, fontsize);
1155 g_free(measure_str);
1156
1157 clipBMeas.lengths[MT::LengthIDs::SHAPE_LENGTH] = item_length; // will be copied to the clipboard
1167
1168}
1169
1170void MeasureTool::showCanvasItems(bool to_guides, bool to_item, bool to_phantom, Inkscape::XML::Node *measure_repr)
1171{
1172 if (!_desktop || !start_p.isFinite() || !end_p.isFinite() || start_p == end_p) {
1173 return;
1174 }
1176 writeMeasurePoint(end_p, false);
1177
1178 //clear previous canvas items, we'll draw new ones
1179 measure_tmp_items.clear();
1180
1181 //TODO:Calculate the measure area for current length and origin
1182 // and use canvas->redraw_all(). In the calculation need a gap for outside text
1183 // maybe this remove the trash lines on measure use
1184 auto prefs = Preferences::get();
1185 bool show_in_between = prefs->getBool("/tools/measure/show_in_between", true);
1186 bool all_layers = prefs->getBool("/tools/measure/all_layers", true);
1187 dimension_offset = 70;
1188 Geom::PathVector lineseg;
1189 Geom::Path p;
1190 Geom::Point start_p_doc = start_p * _desktop->dt2doc();
1191 Geom::Point end_p_doc = end_p * _desktop->dt2doc();
1192 p.start(start_p_doc);
1193 p.appendNew<Geom::LineSegment>(end_p_doc);
1194 lineseg.push_back(p);
1195
1196 double angle = atan2(end_p - start_p);
1197 double baseAngle = 0;
1198
1199 if (explicit_base) {
1200 baseAngle = atan2(*explicit_base - start_p);
1201 angle -= baseAngle;
1202
1203 // make sure that the angle is between -pi and pi.
1204 if (angle > M_PI) {
1205 angle -= 2 * M_PI;
1206 }
1207 if (angle < -M_PI) {
1208 angle += 2 * M_PI;
1209 }
1210 }
1211
1212 std::vector<SPItem*> items;
1214 Geom::Rect rect(start_p_doc, end_p_doc);
1215 items = doc->getItemsPartiallyInBox(_desktop->dkey, rect, false, true, false, true);
1216 SPGroup *current_layer = _desktop->layerManager().currentLayer();
1217
1218 std::vector<double> intersection_times;
1219 bool only_selected = prefs->getBool("/tools/measure/only_selected", false);
1220 for (auto i : items) {
1221 SPItem *item = i;
1222 if (!_desktop->getSelection()->includes(i) && only_selected) {
1223 continue;
1224 }
1225 if (all_layers || _desktop->layerManager().layerForObject(item) == current_layer) {
1226 if (auto e = cast<SPGenericEllipse>(item)) { // this fixes a bug with the calculation of the intersection on
1227 e->set_shape(); // ellipses and circles. If the calculate_intersections(...) is fixed
1228 // then this if() can be removed
1229 Geom::PathVector new_pv = pathv_to_linear_and_cubic_beziers(e->curve()->get_pathvector());
1230 calculate_intersections(_desktop, item, lineseg, SPCurve(new_pv), intersection_times);
1231 } else if (auto shape = cast<SPShape>(item)) {
1232 calculate_intersections(_desktop, item, lineseg, *shape->curve(), intersection_times);
1233 } else {
1234 if (is<SPText>(item) || is<SPFlowtext>(item)) {
1236 do {
1237 Inkscape::Text::Layout::iterator iter_next = iter;
1238 iter_next.nextGlyph(); // iter_next is one glyph ahead from iter
1239 if (iter == iter_next) {
1240 break;
1241 }
1242
1243 // get path from iter to iter_next:
1244 auto curve = te_get_layout(item)->convertToCurves(iter, iter_next);
1245 iter = iter_next; // shift to next glyph
1246 if (curve.is_empty()) { // whitespace glyph?
1247 continue;
1248 }
1249
1250 calculate_intersections(_desktop, item, lineseg, std::move(curve), intersection_times);
1251 if (iter == te_get_layout(item)->end()) {
1252 break;
1253 }
1254 } while (true);
1255 }
1256 }
1257 }
1258 }
1259 Glib::ustring unit_name = prefs->getString("/tools/measure/unit");
1260 if (!unit_name.compare("")) {
1261 unit_name = DEFAULT_UNIT_NAME;
1262 }
1263 double scale = prefs->getDouble("/tools/measure/scale", 100.0) / 100.0;
1264 double fontsize = prefs->getDouble("/tools/measure/fontsize", 10.0);
1265 // Normal will be used for lines and text
1267 Geom::Point normal = _desktop->w2d(windowNormal);
1268
1269 std::vector<Geom::Point> intersections;
1270 std::sort(intersection_times.begin(), intersection_times.end());
1271 for (double & intersection_time : intersection_times) {
1272 intersections.push_back(lineseg[0].pointAt(intersection_time));
1273 }
1274
1275 if(!show_in_between && intersection_times.size() > 1) {
1276 Geom::Point start = lineseg[0].pointAt(intersection_times[0]);
1277 Geom::Point end = lineseg[0].pointAt(intersection_times[intersection_times.size()-1]);
1278 intersections.clear();
1279 intersections.push_back(start);
1280 intersections.push_back(end);
1281 }
1282 if (!prefs->getBool("/tools/measure/ignore_1st_and_last", true)) {
1283 intersections.insert(intersections.begin(),lineseg[0].pointAt(0));
1284 intersections.push_back(lineseg[0].pointAt(1));
1285 }
1286 int precision = prefs->getInt("/tools/measure/precision", 2);
1287 Glib::ustring MTSpath = prefs->getString("/tools/measure/MTSpath","");// path to the settings of the dialog
1288 bool showDeltas = false;
1289 bool show_deltas_label = false;
1290 bool show_segments_label = false;
1291 double seg_min_len = 0.1;
1292 bool showAngle = true;
1293 if (!MTSpath.empty()){
1294 Glib::ustring pathStr = MTSpath;
1295 pathStr.append("/segments_min_length");
1296 seg_min_len = prefs->getDouble(pathStr.c_str(), 0.1);
1297 pathStr = MTSpath;
1298 pathStr.append("/show_segments_label");
1299 show_segments_label = prefs->getBool(pathStr.c_str(), false);
1300 pathStr = MTSpath;
1301 pathStr.append("/show_deltas_label");
1302 show_deltas_label = prefs->getBool(pathStr.c_str(), false);
1303 pathStr = MTSpath;
1304 pathStr.append("/show_deltas");
1305 showDeltas = prefs->getBool(pathStr.c_str(), false);
1306 pathStr = MTSpath;
1307 pathStr.append("/show_angle");
1308 showAngle = prefs->getBool(pathStr.c_str(), true);
1309 }
1310 int segIndex = 1;
1311 clipBMeas.segLengths.clear();
1312 std::vector<LabelPlacement> placements;
1313 for (size_t idx = 1; idx < intersections.size(); ++idx) {
1314 LabelPlacement placement;
1315 placement.lengthVal = (intersections[idx] - intersections[idx - 1]).length();
1316 placement.lengthVal = Inkscape::Util::Quantity::convert(placement.lengthVal, "px", unit_name);
1317 placement.offset = dimension_offset / 2;
1318 placement.start = _desktop->doc2dt((intersections[idx - 1] + intersections[idx]) / 2);
1319 placement.end = placement.start - (normal * placement.offset);
1320 if (placement.lengthVal > seg_min_len) { // trying to avoid 0length segments
1321 placement.label = clipBMeas.symbols[MT::LengthIDs::SEGMENT] + std::to_string(segIndex);
1322 clipBMeas.segLengths[placement.label] = placement.lengthVal * scale; // will be copied to the clipboard
1324 placements.push_back(placement);
1325 segIndex++;
1326 }
1327 }
1328
1329 // Adjust positions
1330 repositionOverlappingLabels(placements, _desktop, windowNormal, fontsize, precision);
1331
1332 Geom::Point deltasBasePoint; // will use these to show lines later
1333 Geom::Point dXmidpos, dYmidpos, dXTextPos, dYTextPos; //
1334 bool dX_is0, dY_is0; //
1335 if (showDeltas) {
1336 Geom::Point dPoint = end_p - start_p;
1337 double dX = dPoint[Geom::X];
1338 double dY = dPoint[Geom::Y];
1339 dX_is0 = equalWithinRange(dX, 0, precision);
1340 dY_is0 = equalWithinRange(dY, 0, precision);
1341 if (!dX_is0 && !dY_is0) { // not showing deltas if either of them is 0 ...
1342 std::vector<Geom::Point> basePointinfo = calcDeltaBasePoint(dX, dY);
1343 deltasBasePoint = basePointinfo[0];
1344 dXmidpos = basePointinfo[3];
1345 dYmidpos = basePointinfo[4];
1346 std::vector<LabelPlacement> allPlacements = placements; // placements only has the segments
1347 if (placements.size() > 1) { // between length
1348 LabelPlacement placement;
1349 placement.lengthVal = ((intersections[0] + normal * dimension_offset) -
1350 (intersections[intersections.size() - 1] + normal * dimension_offset)).length();
1351 placement.lengthVal = Inkscape::Util::Quantity::convert(placement.lengthVal, "px", unit_name);
1352 placement.offset = dimension_offset / 2;
1353 placement.start = _desktop->doc2dt(((intersections[0] + normal * dimension_offset) +
1354 (intersections[intersections.size() - 1] + normal * dimension_offset)) / 2);
1355 placement.end = placement.start; // this label is not displaced
1356 allPlacements.push_back(placement);
1357 }
1358 const int intdXdY = static_cast<int>(std::ceil(dX * dY / 2)); // averaging the number of chars from dX and dY
1359 int maxStrLength = (show_segments_label ? 3 : 0) + std::to_string(intdXdY).length() + precision + unit_name.length();
1360 dXTextPos = calcDeltaLabelTextPos(allPlacements, _desktop, dXmidpos, fontsize, unit_name, maxStrLength, basePointinfo[1], true);
1361 dYTextPos = calcDeltaLabelTextPos(allPlacements, _desktop, dYmidpos, fontsize, unit_name, maxStrLength, basePointinfo[2], false);
1362 dX = Inkscape::Util::Quantity::convert(dX, "px", unit_name);
1363 dY = Inkscape::Util::Quantity::convert(dY, "px", unit_name);
1364 double dYscaled = dY * scale;
1365 int dYstrLen = std::to_string(dYscaled).length();
1366 if (show_deltas_label) { dYstrLen += 3; }
1367 setMeasureCanvasText(false, precision, dX * scale, fontsize, unit_name, dXTextPos, 0x3333337f,
1368 false, to_item, to_phantom, measure_repr, (show_deltas_label ? clipBMeas.symbols[MT::LengthIDs::DX] : ""));
1369 setMeasureCanvasText(false, precision, dYscaled, fontsize, unit_name, dYTextPos - Geom::Point((dYstrLen * fontsize / 2),0),
1370 0x3333337f, false, to_item, to_phantom, measure_repr, (show_deltas_label ? clipBMeas.symbols[MT::LengthIDs::DY] : ""));
1371 clipBMeas.lengths[MT::LengthIDs::DX] = dX * scale; // will be copied to the clipboard
1375 }
1376 } else { // measures are unset and will not be copied to the clipboard
1379 }
1380
1381 for (auto & place : placements) {
1382 setMeasureCanvasText(false, precision, place.lengthVal * scale, fontsize, unit_name, place.end, 0x0000007f,
1383 false, to_item, to_phantom, measure_repr, (show_segments_label ? place.label : ""));
1384 }
1385 Geom::Point angleDisplayPt = calcAngleDisplayAnchor(_desktop, angle, baseAngle, start_p, end_p, fontsize);
1386 if (showAngle) { // angleDisplayPt needs to be outside to be used below for the lines
1387 setMeasureCanvasText(true, precision, Geom::deg_from_rad(angle), fontsize, unit_name, angleDisplayPt, 0x337f337f,
1388 false, to_item, to_phantom, measure_repr);
1389 clipBMeas.lengths[MT::LengthIDs::ANGLE] = Geom::deg_from_rad(angle); // will be copied to the clipboard
1391 } else { // measure is unset and will not be copied to the clipboard
1393 }
1394
1395 {
1396 double totallengthval = (end_p - start_p).length();
1397 totallengthval = Inkscape::Util::Quantity::convert(totallengthval, "px", unit_name);
1399 setMeasureCanvasText(false, precision, totallengthval * scale, fontsize, unit_name, origin, 0x3333337f,
1400 true, to_item, to_phantom, measure_repr);
1401 clipBMeas.lengths[MT::LengthIDs::LENGTH] = totallengthval * scale; // will be copied to the clipboard
1403 }
1404
1405 if (placements.size() > 1) {
1406 double totallengthval = (intersections[intersections.size()-1] - intersections[0]).length();
1407 totallengthval = Inkscape::Util::Quantity::convert(totallengthval, "px", unit_name);
1408 Geom::Point origin = _desktop->doc2dt((intersections[0] + intersections[intersections.size()-1])/2) + normal * dimension_offset;
1409 setMeasureCanvasText(false, precision, totallengthval * scale, fontsize, unit_name, origin, 0x33337f7f,
1410 false, to_item, to_phantom, measure_repr);
1411 clipBMeas.lengths[MT::LengthIDs::LENGTH_BETWEEN] = totallengthval * scale; // will be copied to the clipboard
1413 } else { // measure is unset and will not be copied to the clipboard
1415 }
1416
1417 // Initial point
1418 setMeasureCanvasItem(start_p, false, to_phantom, measure_repr);
1419
1420 // Now that text has been added, we can add lines and controls so that they go underneath
1421 for (size_t idx = 0; idx < intersections.size(); ++idx) {
1422 setMeasureCanvasItem(_desktop->doc2dt(intersections[idx]), to_item, to_phantom, measure_repr);
1423 if(to_guides) {
1424 gchar *cross_number;
1425 if (!prefs->getBool("/tools/measure/ignore_1st_and_last", true)) {
1426 cross_number= g_strdup_printf(_("Crossing %lu"), static_cast<unsigned long>(idx));
1427 } else {
1428 cross_number= g_strdup_printf(_("Crossing %lu"), static_cast<unsigned long>(idx + 1));
1429 }
1430 if (!prefs->getBool("/tools/measure/ignore_1st_and_last", true) && idx == 0) {
1431 setGuide(_desktop->doc2dt(intersections[idx]), angle + Geom::rad_from_deg(90), "");
1432 } else {
1433 setGuide(_desktop->doc2dt(intersections[idx]), angle + Geom::rad_from_deg(90), cross_number);
1434 }
1435 g_free(cross_number);
1436 }
1437 }
1438 // Since adding goes to the bottom, do all lines last.
1439
1440 // draw main control line
1441 {
1442 setMeasureCanvasControlLine(start_p, end_p, false, to_phantom, Inkscape::CANVAS_ITEM_PRIMARY, measure_repr);
1443 if (showAngle) {
1444 double length = std::abs((end_p - start_p).length());
1445 Geom::Point anchorEnd = start_p;
1446 anchorEnd[Geom::X] += length;
1447 if (explicit_base) {
1448 anchorEnd *= (Geom::Affine(Geom::Translate(-start_p))
1449 * Geom::Affine(Geom::Rotate(baseAngle))
1451 }
1452 setMeasureCanvasControlLine(start_p, anchorEnd, to_item, to_phantom, Inkscape::CANVAS_ITEM_SECONDARY, measure_repr);
1453 createAngleDisplayCurve(start_p, end_p, angleDisplayPt, angle, to_phantom, measure_repr);
1454 }
1455 }
1456
1457 if ((showDeltas) && (!dX_is0) && (!dY_is0)) { // adding delta lines
1458 setMeasureCanvasControlLine(start_p, deltasBasePoint, to_item, to_phantom, Inkscape::CANVAS_ITEM_SECONDARY, measure_repr);
1459 setMeasureCanvasControlLine(end_p, deltasBasePoint, to_item, to_phantom, Inkscape::CANVAS_ITEM_SECONDARY, measure_repr);
1460 setMeasureCanvasControlLine(dXmidpos, dXTextPos, to_item, to_phantom, Inkscape::CANVAS_ITEM_SECONDARY, measure_repr);
1461 setMeasureCanvasControlLine(dYmidpos, dYTextPos - Geom::Point((5 * fontsize),0), to_item, to_phantom, Inkscape::CANVAS_ITEM_SECONDARY, measure_repr);
1462 }
1463
1464 if (placements.size() > 1) {
1465 setMeasureCanvasControlLine(_desktop->doc2dt(intersections[0]) + normal * dimension_offset, _desktop->doc2dt(intersections[intersections.size() - 1]) + normal * dimension_offset, to_item, to_phantom, Inkscape::CANVAS_ITEM_PRIMARY , measure_repr);
1466
1467 setMeasureCanvasControlLine(_desktop->doc2dt(intersections[0]), _desktop->doc2dt(intersections[0]) + normal * dimension_offset, to_item, to_phantom, Inkscape::CANVAS_ITEM_PRIMARY , measure_repr);
1468
1469 setMeasureCanvasControlLine(_desktop->doc2dt(intersections[intersections.size() - 1]), _desktop->doc2dt(intersections[intersections.size() - 1]) + normal * dimension_offset, to_item, to_phantom, Inkscape::CANVAS_ITEM_PRIMARY , measure_repr);
1470 }
1471
1472 // call-out lines
1473 for (auto & place : placements) {
1474 setMeasureCanvasControlLine(place.start, place.end, to_item, to_phantom, Inkscape::CANVAS_ITEM_SECONDARY, measure_repr);
1475 }
1476/* this is not needed, it does the same thing as the for (auto & place : placements) above ... but now the shortest segments will
1477 not be shown, so this will show extra lines.
1478 This whole comment block should be deleted. But I didn't want to delete it without giving an explanation.
1479 for (size_t idx = 1; idx < intersections.size(); ++idx) {
1480 Geom::Point measure_text_pos = (intersections[idx - 1] + intersections[idx]) / 2;
1481 setMeasureCanvasControlLine(_desktop->doc2dt(measure_text_pos), _desktop->doc2dt(measure_text_pos) - (normal * dimension_offset / 2), to_item, to_phantom, Inkscape::CANVAS_ITEM_SECONDARY, measure_repr);
1482 }
1483 */
1484}
1485
1494void MeasureTool::setMeasureItem(Geom::PathVector pathv, bool is_curve, bool markers, guint32 color, Inkscape::XML::Node *measure_repr)
1495{
1496 if(!_desktop) {
1497 return;
1498 }
1500 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
1501 Inkscape::XML::Node *repr;
1502 repr = xml_doc->createElement("svg:path");
1503 auto str = sp_svg_write_path(pathv);
1505 auto layer = _desktop->layerManager().currentLayer();
1506 Geom::Coord strokewidth = layer->i2doc_affine().inverse().expansionX();
1507 std::stringstream stroke_width;
1508 stroke_width.imbue(std::locale::classic());
1509 if(measure_repr) {
1510 stroke_width << strokewidth / _desktop->current_zoom();
1511 } else {
1512 stroke_width << strokewidth;
1513 }
1514 sp_repr_css_set_property (css, "stroke-width", stroke_width.str().c_str());
1515 sp_repr_css_set_property (css, "fill", "none");
1516 sp_repr_css_set_property_string(css, "stroke", color ? Inkscape::Colors::rgba_to_hex(color) : "#ff0000");
1517 char const * stroke_linecap = is_curve ? "butt" : "square";
1518 sp_repr_css_set_property (css, "stroke-linecap", stroke_linecap);
1519 sp_repr_css_set_property (css, "stroke-linejoin", "miter");
1520 sp_repr_css_set_property (css, "stroke-miterlimit", "4");
1521 sp_repr_css_set_property (css, "stroke-dasharray", "none");
1522 if(measure_repr) {
1523 sp_repr_css_set_property (css, "stroke-opacity", "0.5");
1524 } else {
1525 sp_repr_css_set_property (css, "stroke-opacity", "1");
1526 }
1527 if(markers) {
1528 sp_repr_css_set_property (css, "marker-start", "url(#Arrow2Sstart)");
1529 sp_repr_css_set_property (css, "marker-end", "url(#Arrow2Send)");
1530 }
1531 Glib::ustring css_str;
1533 repr->setAttribute("style", css_str);
1535 repr->setAttribute("d", str);
1536 if(measure_repr) {
1537 measure_repr->addChild(repr, nullptr);
1539 } else {
1540 auto item = cast<SPItem>(layer->appendChildRepr(repr));
1542 item->updateRepr();
1545 }
1546}
1547
1565 int precision = prefs->getInt("/tools/measure/precision", 2);
1566 Glib::ustring unit_name = prefs->getString("/tools/measure/unit");
1567 Glib::ustring MTSpath = prefs->getString("/tools/measure/MTSpath",""); // path to the settings
1568 Glib::ustring pathStr = MTSpath;
1569 pathStr.append("/show_angle");
1570 bool showAngleOpt = prefs->getBool(pathStr.c_str(), true);
1571 pathStr = MTSpath;
1572 pathStr.append("/show_deltas");
1573 bool deltasOpt = prefs->getBool(pathStr.c_str(), true);
1574 pathStr = MTSpath;
1575 pathStr.append("/labels");
1576 bool labelsOpt = prefs->getBool(pathStr.c_str(), true);
1577 pathStr = MTSpath;
1578 pathStr.append("/units");
1579 bool unitsOpt = prefs->getBool(pathStr.c_str(), true);
1580 pathStr = MTSpath;
1581 pathStr.append("/tabs");
1582 bool tabsOpt = prefs->getBool(pathStr.c_str(), true);
1583 pathStr = MTSpath;
1584 pathStr.append("/length");
1585 bool lengthOpt = prefs->getBool(pathStr.c_str(), true);
1586 pathStr = MTSpath;
1587 pathStr.append("/between");
1588 bool betweenOpt = prefs->getBool(pathStr.c_str(), true);
1589 pathStr = MTSpath;
1590 pathStr.append("/angle");
1591 bool angleOpt = prefs->getBool(pathStr.c_str(), true);
1592 pathStr = MTSpath;
1593 pathStr.append("/dX");
1594 bool dXOpt = prefs->getBool(pathStr.c_str(), true);
1595 pathStr = MTSpath;
1596 pathStr.append("/dY");
1597 bool dYOpt = prefs->getBool(pathStr.c_str(), true);
1598 pathStr = MTSpath;
1599 pathStr.append("/segments");
1600 bool segmentsOpt = prefs->getBool(pathStr.c_str(), true);
1601 pathStr = MTSpath;
1602 pathStr.append("/shape_width");
1603 bool shape_widthOpt = prefs->getBool(pathStr.c_str(), true);
1604 pathStr = MTSpath;
1605 pathStr.append("/shape_height");
1606 bool shape_heightOpt = prefs->getBool(pathStr.c_str(), true);
1607 pathStr = MTSpath;
1608 pathStr.append("/shape_X");
1609 bool shape_XOpt = prefs->getBool(pathStr.c_str(), true);
1610 pathStr = MTSpath;
1611 pathStr.append("/shape_Y");
1612 bool shape_YOpt = prefs->getBool(pathStr.c_str(), true);
1613 pathStr = MTSpath;
1614 pathStr.append("/shape_length");
1615 bool shape_lengthOpt = prefs->getBool(pathStr.c_str(), true);
1616
1617 Glib::ustring stringToCopy = "";
1618 if ((lengthOpt) && (clipBMeas.measureIsSet[MT::LengthIDs::LENGTH])) {
1619 stringToCopy += clipBMeas.composeMeaStr(MT::LengthIDs::LENGTH, precision, unit_name, labelsOpt, unitsOpt, tabsOpt) + "\n";
1620 }
1621 if ((betweenOpt) && (clipBMeas.measureIsSet[MT::LengthIDs::LENGTH_BETWEEN])) { // not copying it if it is the same as the length
1623 stringToCopy += clipBMeas.composeMeaStr(MT::LengthIDs::LENGTH_BETWEEN, precision, unit_name, labelsOpt, unitsOpt, tabsOpt) + "\n";
1624 } else {
1625 if (!lengthOpt) { // if the length is not being copied, then will copy this
1626 stringToCopy += clipBMeas.composeMeaStr(MT::LengthIDs::LENGTH_BETWEEN, precision, unit_name, labelsOpt, unitsOpt, tabsOpt) + "\n";
1627 }
1628 }
1629 }
1630 if ((deltasOpt) && (dXOpt) && (clipBMeas.measureIsSet[MT::LengthIDs::DX])) {
1631 stringToCopy += clipBMeas.composeMeaStr(MT::LengthIDs::DX, precision, unit_name, labelsOpt, unitsOpt, tabsOpt) + "\n";
1632 }
1633 if ((deltasOpt) && (dYOpt) && (clipBMeas.measureIsSet[MT::LengthIDs::DY])) {
1634 stringToCopy += clipBMeas.composeMeaStr(MT::LengthIDs::DY, precision, unit_name, labelsOpt, unitsOpt, tabsOpt) + "\n";
1635 }
1636 if ((showAngleOpt) && (angleOpt) && (clipBMeas.measureIsSet[MT::LengthIDs::ANGLE])) {
1637 stringToCopy += clipBMeas.composeMeaStr(MT::LengthIDs::ANGLE, precision, "°", labelsOpt, unitsOpt, tabsOpt) + "\n";
1638 }
1639 if ((clipBMeas.segLengths.size() > 0) && (segmentsOpt) && (clipBMeas.measureIsSet[MT::LengthIDs::SEGMENT])) {
1640 Glib::ustring firstSeg = clipBMeas.symbols[MT::LengthIDs::SEGMENT] + "1";
1642 // do nothing the segment is the same as the total length - no point in showing it
1643 } else {
1644 stringToCopy += _("\nIntersection segments lengths:\n");
1645 Glib::ustring sep = tabsOpt ? "\t" : " ";
1646 for (const auto& [key,value] : clipBMeas.segLengths) {
1647 if (labelsOpt) { stringToCopy += key +":" + sep; }
1648 stringToCopy += Glib::ustring::format(std::setprecision(precision), std::fixed, value);
1649 if (unitsOpt) { stringToCopy += sep + unit_name; }
1650 stringToCopy += "\n";
1651 }
1652 }
1653 }
1654
1655 bool showTitle = true;
1656 const char* title = _("\nInfo about the shape under the pointer:\n");
1657 if ((shape_widthOpt) && (clipBMeas.measureIsSet[MT::LengthIDs::SHAPE_WIDTH])) {
1658 if (showTitle) {
1659 stringToCopy += title;
1660 showTitle = false;
1661 }
1662 stringToCopy += clipBMeas.composeMeaStr(MT::LengthIDs::SHAPE_WIDTH, precision, unit_name, labelsOpt, unitsOpt, tabsOpt) + "\n";
1663 }
1664 if ((shape_heightOpt) && (clipBMeas.measureIsSet[MT::LengthIDs::SHAPE_HEIGHT])) {
1665 if (showTitle) {
1666 stringToCopy += title;
1667 showTitle = false;
1668 }
1669 stringToCopy += clipBMeas.composeMeaStr(MT::LengthIDs::SHAPE_HEIGHT, precision, unit_name, labelsOpt, unitsOpt, tabsOpt) + "\n";
1670 }
1671 if ((shape_XOpt) && (clipBMeas.measureIsSet[MT::LengthIDs::SHAPE_X])) {
1672 if (showTitle) {
1673 stringToCopy += title;
1674 showTitle = false;
1675 }
1676 stringToCopy += clipBMeas.composeMeaStr(MT::LengthIDs::SHAPE_X, precision, unit_name, labelsOpt, unitsOpt, tabsOpt) + "\n";
1677 }
1678 if ((shape_YOpt) && (clipBMeas.measureIsSet[MT::LengthIDs::SHAPE_Y])) {
1679 if (showTitle) {
1680 stringToCopy += title;
1681 showTitle = false;
1682 }
1683 stringToCopy += clipBMeas.composeMeaStr(MT::LengthIDs::SHAPE_Y, precision, unit_name, labelsOpt, unitsOpt, tabsOpt) + "\n";
1684 }
1685 if ((shape_lengthOpt) && (clipBMeas.measureIsSet[MT::LengthIDs::SHAPE_LENGTH])) {
1686 if (showTitle) {
1687 stringToCopy += title;
1688 showTitle = false;
1689 }
1690 stringToCopy += clipBMeas.composeMeaStr(MT::LengthIDs::SHAPE_LENGTH, precision, unit_name, labelsOpt, unitsOpt, tabsOpt) + "\n";
1691 }
1692
1693
1695 if (cm->copyString(stringToCopy)) {
1696 _desktop->messageStack()->flash(Inkscape::MessageType::INFORMATION_MESSAGE,_("The measurements have been copied to the clipboard"));
1697 }
1698
1699}
1700
1711std::vector<Geom::Point> MeasureTool::calcDeltaBasePoint(double dX, double dY)
1712{
1713 Geom::Point deltasBasePoint;
1714 Geom::Point dXnormal;
1715 Geom::Point dYnormal;
1716 Geom::Point dXbase;
1717 Geom::Point dYbase;
1718 double midX = (std::abs(dX) / 2);
1719 double midY = (std::abs(dY) / 2);
1720 if ((dX > 0) && (dY > 0)) { // positioning the measures on the outside to avoid the clutter
1721 deltasBasePoint = Geom::Point(start_p[Geom::X], end_p[Geom::Y]);
1722 dXnormal = Geom::Point(0, 1);
1723 dYnormal = Geom::Point(-1, 0);
1724 dXbase = Geom::Point(start_p[Geom::X] + midX / 2, end_p[Geom::Y]); // putting closer to the base point to avoid other labels
1725 dYbase = Geom::Point(start_p[Geom::X], start_p[Geom::Y] + midY);
1726 }
1727 if ((dX > 0) && (dY < 0)) {
1728 deltasBasePoint = Geom::Point(start_p[Geom::X], end_p[Geom::Y]);
1729 dXnormal = Geom::Point(0, -1);
1730 dYnormal = Geom::Point(-1, 0);
1731 dXbase = Geom::Point(start_p[Geom::X] + midX / 2, end_p[Geom::Y]);
1732 dYbase = Geom::Point(start_p[Geom::X], end_p[Geom::Y] + midY);
1733 }
1734 if ((dX < 0) && (dY > 0)) {
1735 deltasBasePoint = Geom::Point(end_p[Geom::X],start_p[Geom::Y]);
1736 dXnormal = Geom::Point(0, -1);
1737 dYnormal = Geom::Point(-1, 0);
1738 dXbase = Geom::Point(end_p[Geom::X] + midX / 2, start_p[Geom::Y]);
1739 dYbase = Geom::Point(end_p[Geom::X], start_p[Geom::Y] + midY);
1740 }
1741 if ((dX < 0) && (dY < 0)) {
1742 deltasBasePoint = Geom::Point(end_p[Geom::X],start_p[Geom::Y]);
1743 dXnormal = Geom::Point(0, 1);
1744 dYnormal = Geom::Point(-1, 0);
1745 dXbase = Geom::Point(end_p[Geom::X] + midX / 2, start_p[Geom::Y]);
1746 dYbase = Geom::Point(end_p[Geom::X], end_p[Geom::Y] + midY);
1747 }
1748 std::vector<Geom::Point> result;
1749 result.push_back(deltasBasePoint);
1750 result.push_back(dXnormal);
1751 result.push_back(dYnormal);
1752 result.push_back(dXbase);
1753 result.push_back(dYbase);
1754 return result;
1755}
1756
1767bool MeasureTool::equalWithinRange(double value, double reference_value, double epsilon, bool positiveAllowed, bool negativeAllowed) {
1768 if (positiveAllowed) {
1769 if ((value <= reference_value + epsilon) && (value >= reference_value)) { return true; }
1770 }
1771 if (negativeAllowed) {
1772 if ((value >= reference_value - epsilon) && (value <= reference_value)) { return true; }
1773 }
1774 return false;
1775}
1776
1779
1788Glib::ustring MT::ClipboardMeaClass::composeMeaStr(int id, int precision, Glib::ustring unit, bool withLabel, bool withUnit, bool tabSeparated) {
1789 Glib::ustring value = Glib::ustring::format(std::setprecision(precision), std::fixed, lengths[id]);
1790 Glib::ustring sep = tabSeparated ? "\t" : " ";
1791 Glib::ustring result = withLabel ? labels[id] + ":" + sep : "";
1792 result += value;
1793 if (withUnit) {
1794 result += sep + unit;
1795 }
1796 return result;
1797}
1798
1800 measureIsSet[LengthIDs::SHAPE_LENGTH] = false;
1801 measureIsSet[LengthIDs::SHAPE_WIDTH] = false;
1802 measureIsSet[LengthIDs::SHAPE_HEIGHT] = false;
1803 measureIsSet[LengthIDs::SHAPE_X] = false;
1804 measureIsSet[LengthIDs::SHAPE_Y] = false;
1805}
1806
1807} // namespace Inkscape::UI::Tools
1808
1809/*
1810 Local Variables:
1811 mode:c++
1812 c-file-style:"stroustrup"
1813 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1814 indent-tabs-mode:nil
1815 fill-column:99
1816 End:
1817*/
1818// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
double scale
Definition aa.cpp:228
Point origin
Definition aa.cpp:227
Inkscape canvas widget.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Coord expansionX() const
Calculates the amount of x-scaling imparted by the Affine.
Definition affine.cpp:64
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Bezier curve with compile-time specified order.
CPoint clamp(CPoint const &p) const
Clamp point to the rectangle.
void expandBy(C amount)
Expand the rectangle in both directions by the specified amount.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Sequence of subpaths.
Definition pathvector.h:122
void push_back(Path const &path)
Append a path at the end.
Definition pathvector.h:172
Point pointAt(Coord t) const
bool empty() const
Check whether the vector contains any paths.
Definition pathvector.h:145
Sequence of contiguous curves, aka spline.
Definition path.h:353
void appendNew(Args &&... args)
Append a new curve to the path.
Definition path.h:804
void start(Point const &p)
Definition path.cpp:426
Two-dimensional point that doubles as a vector.
Definition point.h:66
bool isFinite() const
Check whether both coordinates are finite.
Definition point.h:218
Straight ray from a specific point to infinity.
Definition ray.h:53
void setPoints(Point const &a, Point const &b)
Definition ray.h:75
Coord angle() const
Definition ray.h:73
Axis aligned, non-empty rectangle.
Definition rect.h:92
bool interiorContains(Point const &p) const
Check whether the interior includes the given point.
Definition rect.h:127
Rotation around the origin.
Definition transforms.h:187
Scaling from the origin.
Definition transforms.h:150
Scale inverse() const
Definition transforms.h:172
Translation by a vector.
Definition transforms.h:115
Translate inverse() const
Get the inverse translation.
Definition transforms.h:133
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
SPObject * layerForObject(SPObject *object)
Return layer that contains object.
SPGroup * currentLayer() const
Returns current top layer.
MessageId flash(MessageType type, char const *message)
Temporarily pushes a message onto the stack.
void clear()
Unselects all selected objects.
Geom::Affine getSelectedPageAffine() const
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
double getDouble(Glib::ustring const &pref_path, double def=0.0, Glib::ustring const &unit="")
Retrieve a floating point value.
Glib::ustring getString(Glib::ustring const &pref_path, Glib::ustring const &def="")
Retrieve an UTF-8 string.
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
Geom::Point getPoint(Glib::ustring const &pref_path, Geom::Point def=Geom::Point())
Retrieve a point.
void setPoint(Glib::ustring const &pref_path, Geom::Point value)
Set a point value.
void add(XML::Node *repr)
Add an XML node's SPObject to the set of selected objects.
Definition selection.h:107
bool includes(XML::Node *repr, bool anyAncestor=false)
Returns true if the given item is selected.
Definition selection.h:140
Class to store data for points which are snap candidates, either as a source or as a target.
void addOrigin(Geom::Point pt)
Class describing the result of an attempt to snap.
Geom::Point getPoint() const
Holds a position within the glyph output of Layout.
Definition Layout-TNG.h:973
SPCurve convertToCurves(iterator const &from_glyph, iterator const &to_glyph) const
Convert the specified range of characters into their bezier outlines.
iterator begin() const
Returns an iterator pointing at the first glyph of the flowed output.
System-wide clipboard manager.
Definition clipboard.h:44
static ClipboardManager * get()
virtual bool copyString(Glib::ustring str)=0
static void showDialog(SPDesktop *desktop, SPKnot *knot, Glib::ustring const &unit_name)
Glib::ustring composeMeaStr(int id, int precision, Glib::ustring unit, bool withLabel=true, bool withUnit=true, bool tabSeparated=false)
Composes the string for a easurement to be copied to the clipboards.
std::map< int, Glib::ustring > symbols
std::map< Glib::ustring, double > segLengths
Geom::Point readMeasurePoint(bool is_start) const
void setMeasureItem(Geom::PathVector pathv, bool is_curve, bool markers, guint32 color, Inkscape::XML::Node *measure_repr)
Create a measure item in current document.
sigc::scoped_connection _knot_end_ungrabbed_connection
void setMeasureCanvasText(bool is_angle, double precision, double amount, double fontsize, Glib::ustring unit_name, Geom::Point position, guint32 background, bool to_left, bool to_item, bool to_phantom, Inkscape::XML::Node *measure_repr, Glib::ustring label="")
void setLabelText(Glib::ustring const &value, Geom::Point pos, double fontsize, Geom::Coord angle, guint32 background, Inkscape::XML::Node *measure_repr=nullptr)
std::optional< Geom::Point > last_end
void setGuide(Geom::Point origin, double angle, const char *label)
sigc::scoped_connection _knot_start_moved_connection
std::vector< CanvasItemPtr< CanvasItem > > measure_phantom_items
void showCanvasItems(bool to_guides=false, bool to_item=false, bool to_phantom=false, Inkscape::XML::Node *measure_repr=nullptr)
void copyToClipboard()
Copies some measurements to the clipboard.
void setLine(Geom::Point start_point, Geom::Point end_point, bool markers, guint32 color, Inkscape::XML::Node *measure_repr=nullptr)
void setMeasureCanvasControlLine(Geom::Point start, Geom::Point end, bool to_item, bool to_phantom, Inkscape::CanvasItemColor color, Inkscape::XML::Node *measure_repr)
sigc::scoped_connection _knot_end_moved_connection
sigc::scoped_connection _knot_end_click_connection
std::vector< CanvasItemPtr< CanvasItem > > measure_tmp_items
sigc::scoped_connection _knot_start_ungrabbed_connection
bool equalWithinRange(double value, double reference_value, double epsilon, bool positiveAllowed=true, bool negativeAllowed=true)
Checks if a value is very close to a reference value and can be considered equal to it.
bool root_handler(CanvasEvent const &event) override
void knotUngrabbedHandler(SPKnot *, unsigned int)
void knotEndMovedHandler(SPKnot *, Geom::Point const &ppointer, guint state)
void knotClickHandler(SPKnot *knot, guint state)
void showItemInfoText(Geom::Point pos, Glib::ustring const &measure_str, double fontsize)
MT::ClipboardMeaClass clipBMeas
sigc::scoped_connection _knot_start_click_connection
void showInfoBox(Geom::Point cursor, bool into_groups)
std::optional< Geom::Point > explicit_base
void setMeasureCanvasItem(Geom::Point position, bool to_item, bool to_phantom, Inkscape::XML::Node *measure_repr)
std::vector< Geom::Point > calcDeltaBasePoint(double dX, double dY)
Calculates the base point from which to draw the dX and dY lines.
void knotStartMovedHandler(SPKnot *, Geom::Point const &ppointer, guint state)
void createAngleDisplayCurve(Geom::Point const &center, Geom::Point const &end, Geom::Point const &anchor, double angle, bool to_phantom, Inkscape::XML::Node *measure_repr=nullptr)
Given an angle, the arc center and edge point, draw an arc segment centered around that edge point.
std::vector< CanvasItemPtr< CanvasItem > > measure_item
void writeMeasurePoint(Geom::Point point, bool is_start) const
void setPoint(Geom::Point origin, Inkscape::XML::Node *measure_repr)
Base class for Event processors.
Definition tool-base.h:107
void ungrabCanvasEvents()
Ungrab events from the Canvas Catchall.
void grabCanvasEvents(EventMask mask=EventType::KEY_PRESS|EventType::BUTTON_RELEASE|EventType::MOTION|EventType::BUTTON_PRESS)
Grab events from the Canvas Catchall.
virtual bool root_handler(CanvasEvent const &event)
void saveDragOrigin(Geom::Point const &pos)
bool checkDragMoved(Geom::Point const &pos)
Analyse the current position and return true once it has moved farther than tolerance from the drag o...
void enableGrDrag(bool enable=true)
Geom::IntRect get_area_world() const
Return the area shown in the canvas in world coordinates.
Definition canvas.cpp:1560
double value(Unit const *u) const
Return the quantity's value in the specified unit.
Definition units.cpp:502
static double convert(double from_dist, Unit const *from, Unit const *to)
Convert distances.
Definition units.cpp:525
Interface for refcounted XML nodes.
Definition node.h:80
virtual void addChild(Node *child, Node *after)=0
Insert another node as a child of this node.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
bool setAttributeSvgDouble(Util::const_char_ptr key, double val)
For attributes where an exponent is allowed.
Definition node.cpp:111
Wrapper around a Geom::PathVector object.
Definition curve.h:26
To do: update description of desktop.
Definition desktop.h:149
Inkscape::UI::Widget::Canvas * getCanvas() const
Definition desktop.h:190
double current_zoom() const
Definition desktop.h:335
SPDocument * getDocument() const
Definition desktop.h:189
unsigned dkey
Definition desktop.h:229
Inkscape::MessageStack * messageStack() const
Definition desktop.h:160
Inkscape::CanvasItemGroup * getCanvasTemp() const
Definition desktop.h:202
Geom::Affine const & dt2doc() const
Definition desktop.cpp:1343
Geom::Affine const & d2w() const
Transformation from desktop to window coordinates.
Definition desktop.h:419
SPItem * getItemAtPoint(Geom::Point const &p, bool into_groups, SPItem *upto=nullptr) const
Definition desktop.cpp:352
SPNamedView * getNamedView() const
Definition desktop.h:191
Inkscape::Selection * getSelection() const
Definition desktop.h:188
double yaxisdir() const
Definition desktop.h:426
SPDocument * doc() const
Definition desktop.h:159
bool is_yaxisdown() const
Definition desktop.h:427
Inkscape::LayerManager & layerManager()
Definition desktop.h:287
Geom::Affine const & doc2dt() const
Definition desktop.cpp:1337
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Definition desktop.h:416
Typed SVG document implementation.
Definition document.h:103
bool get_origin_follows_page()
SPRoot * getRoot()
Returns our SPRoot.
Definition document.h:202
SPObject * getObjectById(std::string const &id) const
std::vector< SPItem * > getItemsPartiallyInBox(unsigned int dkey, Geom::Rect const &box, bool take_hidden=false, bool take_insensitive=false, bool take_groups=true, bool enter_groups=false, bool enter_layers=true) const
Get items whose bounding box overlaps with given area.
Inkscape::PageManager & getPageManager()
Definition document.h:164
SPDefs * getDefs()
Return the main defs object for the document.
Definition document.cpp:245
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:213
int ensureUpToDate(unsigned int object_modified_tag=0)
Repeatedly works on getting the document updated, since sometimes it takes more than one pass to get ...
Inkscape::Util::Quantity getHeight() const
Definition document.cpp:896
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1821
Geom::OptRect bounds(BBoxType type, Geom::Affine const &transform=Geom::identity()) const
Definition sp-item.cpp:995
@ VISUAL_BBOX
Definition sp-item.h:118
@ GEOMETRIC_BBOX
Definition sp-item.h:116
Geom::Affine i2doc_affine() const
Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
Definition sp-item.cpp:1816
Desktop-bound visual control object.
Definition knot.h:51
sigc::signal< void(SPKnot *, unsigned int)> ungrabbed_signal
Definition knot.h:97
sigc::signal< void(SPKnot *, Geom::Point const &, unsigned int)> moved_signal
Definition knot.h:98
sigc::signal< void(SPKnot *, unsigned int)> click_signal
Definition knot.h:93
static void unref(SPKnot *knot)
Definition knot.cpp:47
void hide()
Hide knot on its canvas.
Definition knot.cpp:327
void show()
Show knot on its canvas.
Definition knot.cpp:322
void updateCtrl()
Update knot's control state.
Definition knot.cpp:389
Geom::Point position() const
Returns position of knot.
Definition knot.h:160
void moveto(Geom::Point const &p)
Move knot to new position, without emitting a MOVED signal.
Definition knot.cpp:353
SnapManager snap_manager
Inkscape::Util::Unit const * getDisplayUnit() const
Returns namedview's default unit.
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
void appendChild(Inkscape::XML::Node *child)
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
SPObject * appendChildRepr(Inkscape::XML::Node *repr)
Append repr as child of this object.
<svg> element
Definition sp-root.h:33
Class to coordinate snapping operations.
Definition snap.h:80
void setup(SPDesktop const *desktop, bool snapindicator=true, SPObject const *item_to_ignore=nullptr, std::vector< Inkscape::SnapCandidatePoint > *unselected_nodes=nullptr)
Convenience shortcut when there is only one item to ignore.
Definition snap.cpp:663
void unSetup()
Definition snap.h:147
Inkscape::SnappedPoint freeSnap(Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &bbox_to_snap=Geom::OptRect(), bool to_path_only=false) const
Try to snap a point to grids, guides or objects.
Definition snap.cpp:143
System-wide clipboard management - class declaration.
RootCluster root
std::shared_ptr< Css const > css
Css & result
void sp_desktop_apply_style_tool(SPDesktop *desktop, Inkscape::XML::Node *repr, Glib::ustring const &tool_path, bool with_text)
Apply the desktop's current style or the tool style to repr.
Editable view implementation.
static char const *const current
Definition dir-util.cpp:71
TODO: insert short description here.
static bool overlaps(Geom::Rect const &area, Geom::Rect const &box)
unsigned int guint32
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Geom::PathVector pathv_to_linear_and_cubic_beziers(Geom::PathVector const &pathv)
Definition geom.cpp:588
Specific geometry functions for Inkscape, not provided my lib2geom.
Macro for icon names used in Inkscape.
SPItem * item
Declarations for SPKnot: Desktop-bound visual control object.
Infinite straight line.
double offset
Geom::Point start
double lengthVal
Glib::ustring label
Geom::Point end
void normal(std::vector< Point > &N, std::vector< Point > const &B)
double angle(std::vector< Point > const &A)
Coord length(LineSegment const &seg)
Affine identity()
Create an identity matrix.
Definition affine.h:210
Crossings crossings(Curve const &a, Curve const &b)
std::vector< Crossings > CrossingSet
Definition crossing.h:128
void delete_duplicates(Crossings &crs)
Definition crossing.cpp:188
Point unit_vector(Point const &a)
D2< T > rot90(D2< T > const &a)
Definition d2.h:397
Coord LInfty(Point const &p)
Point middle_point(LineSegment const &_segment)
std::string rgba_to_hex(uint32_t value, bool alpha)
Output the RGBA value as a #RRGGBB hex color, if alpha is true then the output will be #RRGGBBAA inst...
Definition utils.cpp:70
static R & release(R &r)
Decrements the reference count of a anchored object.
static void calculate_intersections(SPDesktop *desktop, SPItem *item, Geom::PathVector const &lineseg, SPCurve curve, std::vector< double > &intersections)
static char const * endpoint_to_pref(bool is_start)
static const auto fontsize
void spdc_endpoint_snap_rotation(ToolBase *tool, Geom::Point &p, Geom::Point const &o, unsigned state)
Snaps node or handle to PI/rotationsnapsperpi degree increments.
void gobble_motion_events(unsigned mask)
Definition tool-base.h:244
static std::optional< Geom::Point > explicit_base_tmp
Glib::ustring format_classic(T const &... args)
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
@ SNAPSOURCE_OTHER_HANDLE
Definition snap-enums.h:56
@ CANVAS_ITEM_CTRL_TYPE_POINT
@ CANVAS_ITEM_CTRL_TYPE_MARKER
@ INFORMATION_MESSAGE
Definition message.h:30
static cairo_user_data_key_t key
Path intersection.
SPCSSAttr * sp_repr_css_attr_new()
Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
Definition repr-css.cpp:67
void sp_repr_css_write_string(SPCSSAttr *css, Glib::ustring &str)
Write a style attribute string from a list of properties stored in an SPCSAttr object.
Definition repr-css.cpp:243
void sp_repr_css_set_property_double(SPCSSAttr *css, gchar const *name, double value)
Set a style property to a new float value (e.g.
Definition repr-css.cpp:224
void sp_repr_css_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
Definition repr-css.cpp:76
void sp_repr_css_set_property_string(SPCSSAttr *css, char const *name, std::string const &value)
Set a style property to a standard string.
Definition repr-css.cpp:235
void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
Set a style property to a new value (e.g.
Definition repr-css.cpp:191
GList * items
TODO: insert short description here.
SPRoot: SVG <svg> implementation.
A mouse button (left/right/middle) is pressed.
A mouse button (left/right/middle) is released.
Abstract base class for events.
unsigned modifiers
The modifiers mask immediately before the event.
A key has been pressed.
A key has been released.
Movement of the mouse pointer.
Interface for XML documents.
Definition document.h:43
virtual Node * createTextNode(char const *content)=0
virtual Node * createElement(char const *name)=0
Definition curve.h:24
Geom::PathVector sp_svg_read_pathv(char const *str)
Definition svg-path.cpp:37
static void sp_svg_write_path(Inkscape::SVG::PathString &str, Geom::Path const &p, bool normalize=false)
Definition svg-path.cpp:109
SPDesktop * desktop
double border
Inkscape::Text::Layout const * te_get_layout(SPItem const *item)