Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
seltrans.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
5/* Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * bulia byak <buliabyak@users.sf.net>
8 * Carl Hetherington <inkscape@carlh.net>
9 * Diederik van Lierop <mail@diedenrezi.nl>
10 * Abhishek Sharma
11 *
12 * Copyright (C) 1999-2002 Lauris Kaplinski
13 * Copyright (C) 1999-2014 Authors
14 *
15 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16 */
17
18#include <cstring>
19#include <string>
20
21#include <gdk/gdkkeysyms.h>
22#include <glibmm/i18n.h>
23#include <giomm/application.h>
24
25#include <2geom/transforms.h>
26
27#include "seltrans.h"
28
29#include "desktop-style.h"
30#include "desktop.h"
31#include "document-undo.h"
32#include "document.h"
33#include "message-stack.h"
34#include "mod360.h"
35#include "pure-transform.h"
36#include "selection.h"
37#include "seltrans-handles.h"
38
45#include "live_effects/effect.h"
46
48#include "object/sp-namedview.h"
49#include "object/sp-root.h"
50
51#include "ui/icon-names.h"
52#include "ui/modifiers.h"
53#include "ui/knot/knot.h"
56
58
59static void sp_sel_trans_handle_grab(SPKnot *knot, guint state, SPSelTransHandle const* data);
60static void sp_sel_trans_handle_ungrab(SPKnot *knot, guint state, SPSelTransHandle const* data);
61static void sp_sel_trans_handle_click(SPKnot *knot, guint state, SPSelTransHandle const* data);
62static void sp_sel_trans_handle_new_event(SPKnot *knot, Geom::Point const &position, guint32 state, SPSelTransHandle const* data);
63static gboolean sp_sel_trans_handle_request(SPKnot *knot, Geom::Point *p, guint state, SPSelTransHandle const *data);
64
65// TODO Remove
66#define SP_SELECT_CONTEXT(obj) (dynamic_cast<Inkscape::UI::Tools::SelectTool*>((Inkscape::UI::Tools::ToolBase*)obj))
67
69{
70 bool ret = false;
71
72 inspect_event(event,
73 [&] (Inkscape::KeyPressEvent const &event) {
74 if (Inkscape::UI::Tools::get_latin_keyval(event) == GDK_KEY_space) {
75 // Stamping mode: both mode (show content and outline) operation with knot.
76 if (!knot->is_grabbed()) {
77 return;
78 }
79 auto desktop = knot->desktop;
80 auto seltrans = SP_SELECT_CONTEXT(desktop->getTool())->_seltrans;
81 // This stamp can't produce clones without requiring extra support of "undoing"
82 // the cascaded transform from this knot's changes.
83 seltrans->stamp();
84 ret = true;
85 }
86 },
87 [&] (Inkscape::CanvasEvent const &event) {}
88 );
89
90 return ret;
91}
92
94 Observer("/tools/bounding_box"),
95 _sel_trans(sel_trans)
96{
97}
98
100{
101 _sel_trans._boundingBoxPrefsChanged(static_cast<int>(val.getBool()));
102}
103
109 _bbox(),
111 _message_context(*desktop->messageStack()),
113{
115 int prefs_bbox = prefs->getBool("/tools/bounding_box");
116 _snap_bbox_type = !prefs_bbox ?
118
119 g_return_if_fail(desktop != nullptr);
120
123
124 _center_is_set = false; // reread _center from items, or set to bbox midpoint
125
126 _makeHandles();
128
130
131 _norm = make_canvasitem<CanvasItemCtrl>(desktop->getCanvasControls(), Inkscape::CANVAS_ITEM_CTRL_TYPE_CENTER);
132 _norm->set_visible(false);
133
134 _grip = make_canvasitem<CanvasItemCtrl>(desktop->getCanvasControls(), Inkscape::CANVAS_ITEM_CTRL_TYPE_MARKER);
135 _grip->set_visible(false);
136
137 for (auto &i : _l) {
138 i = make_canvasitem<CanvasItemCurve>(desktop->getCanvasControls());
139 i->set_visible(false);
140 }
141
143 sigc::mem_fun(*this, &Inkscape::SelTrans::_selChanged)
144 );
145
147 sigc::mem_fun(*this, &Inkscape::SelTrans::_selModified)
148 );
149
151
153}
154
156{
157 _sel_changed_connection.disconnect();
158 _sel_modified_connection.disconnect();
159
160 for (auto &knot : knots) {
161 SPKnot::unref(knot);
162 knot = nullptr;
163 }
164
165 _norm.reset();
166 _grip.reset();
167 for (auto &i : _l) {
168 i.reset();
169 }
170
171 _clear_stamp();
172
173 for (auto &_item : _items) {
174 sp_object_unref(_item, nullptr);
175 }
176
177 _items.clear();
178 _objects_const.clear();
179 _items_affines.clear();
180 _items_centers.clear();
181}
182
183void
185 _stamped = false;
186 for (auto old_obj :_stamp_cache) {
187 auto oldLPEObj = cast<SPLPEItem>(old_obj);
188 if (oldLPEObj) {
189 sp_lpe_item_enable_path_effects(oldLPEObj, true);
190 }
191 }
192 if(!_stamp_cache.empty()){
193 _stamp_cache.clear();
194 }
195}
196
198{
199 _state = STATE_SCALE;
200}
201
203{
205 bool show_align = prefs->getBool("/dialogs/align/oncanvas", false);
206
207 if (_state == STATE_SCALE) {
208 _state = STATE_ROTATE;
209 } else if (_state == STATE_ROTATE && show_align) {
210 _state = STATE_ALIGN;
211 } else {
212 _state = STATE_SCALE;
213 }
214
215 _center_is_set = true; // no need to reread center
216
217 _updateHandles();
218}
219
221{
222 _center = p;
223 _center_is_set = true;
224
225 // Write the new center position into all selected items
226 auto items= _desktop->getSelection()->items();
227 for (auto it : items) {
228 it->setCenter(p);
229 // only set the value; updating repr and document_done will be done once, on ungrab
230 }
231
232 _updateHandles();
233}
234
235void Inkscape::SelTrans::grab(Geom::Point const &p, gdouble x, gdouble y, bool show_handles, bool translating)
236{
237 // While dragging a handle, we will either scale, skew, or rotate and the "translating" parameter will be false
238 // When dragging the selected item itself however, we will translate the selection and that parameter will be true
239 Inkscape::Selection *selection = _desktop->getSelection();
241
242 g_return_if_fail(!_grabbed);
243
244 _grabbed = true;
245 _show_handles = show_handles;
246 _updateVolatileState();
247 _current_relative_affine.setIdentity();
248
249 _changed = false;
250
251 if (_empty) {
252 return;
253 }
254
255 auto items= _desktop->getSelection()->items();
256 for (auto item : items) {
257 SPItem *it = static_cast<SPItem*>(sp_object_ref(item, nullptr));
258 _items.push_back(it);
259 _objects_const.push_back(it);
260 _items_affines.push_back(it->i2dt_affine());
261 _items_centers.push_back(it->getCenter()); // for content-dragging, we need to remember original centers
262 }
263
264 if (y != -1 && _desktop->is_yaxisdown()) {
265 y = 1 - y;
266 }
267
268 _handle_x = x;
269 _handle_y = y;
270
271 // The selector tool should snap the bbox, special snappoints, and path nodes
272 // (The special points are the handles, center, rotation axis, font baseline, ends of spiral, etc.)
273
274 // First, determine the bounding box
275 _bbox = selection->bounds(_snap_bbox_type);
276 _stroked_bbox = selection->strokedBounds(); // Used for correctly scaling the strokewidth
277 _geometric_bbox = selection->geometricBounds();
278
279 _point = p;
280 if (_geometric_bbox) {
281 _point_geom = _geometric_bbox->min() + _geometric_bbox->dimensions() * Geom::Scale(x, y);
282 } else {
283 _point_geom = p;
284 }
285
286 // Next, get all points to consider for snapping
287 SnapManager const &m = _desktop->getNamedView()->snap_manager;
288 _snap_points.clear();
289 if (m.someSnapperMightSnap(false)) { // Only search for snap sources when really needed, to avoid unnecessary delays
290 _snap_points = selection->getSnapPoints(&m.snapprefs); // This might take some time!
291 }
292 if (_snap_points.size() > 200 && !(prefs->getBool("/options/snapclosestonly/value", false))) {
293 /* Snapping a huge number of nodes will take way too long, so limit the number of snappable nodes
294 A typical user would rarely ever try to snap such a large number of nodes anyway, because
295 (s)he would hardly be able to discern which node would be snapping */
296 std::cerr << "Warning: limit of 200 snap sources reached, some will be ignored" << std::endl;
297 _snap_points.resize(200);
298 // Unfortunately, by now we will have lost the font-baseline snappoints :-(
299 }
300
301 // Find bbox hulling all special points, which excludes stroke width. Here we need to include the
302 // path nodes, for example because a rectangle which has been converted to a path doesn't have
303 // any other special points
304 Geom::OptRect snap_points_bbox = selection->bounds(SPItem::GEOMETRIC_BBOX);
305
306 _bbox_points.clear();
307 // Collect the bounding box's corners and midpoints for each selected item
312 // Preferably we'd use the bbox of each selected item, but for example 50 items will produce at least 200 bbox points,
313 // which might make Inkscape crawl(see the comment a few lines above). In that case we will use the bbox of the selection as a whole
314 bool c1 = (_items.size() > 0) && (_items.size() < 50);
315 bool c2 = prefs->getBool("/options/snapclosestonly/value", false);
316 if (translating && (c1 || c2)) {
317 // Get the bounding box points for each item in the selection
318 for (auto & _item : _items) {
319 Geom::OptRect b = _item->desktopBounds(_snap_bbox_type);
320 getBBoxPoints(b, &_bbox_points, false, c, emp, mp);
321 }
322 } else {
323 // Only get the bounding box points of the selection as a whole
324 getBBoxPoints(selection->bounds(_snap_bbox_type), &_bbox_points, false, c, emp, mp);
325 }
326 }
327
328 if (_bbox) {
329 // There are two separate "opposites" (i.e. opposite w.r.t. the handle being dragged):
330 // - one for snapping the boundingbox, which can be either visual or geometric
331 // - one for snapping the special points
332 // The "opposite" in case of a geometric boundingbox always coincides with the "opposite" for the special points
333 // These distinct "opposites" are needed in the snapmanager to avoid bugs such as LP167905 (in which
334 // a box is caught between two guides)
335 _opposite_for_bboxpoints = _bbox->min() + _bbox->dimensions() * Geom::Scale(1-x, 1-y);
336 if (snap_points_bbox) {
337 _opposite_for_specpoints = (*snap_points_bbox).min() + (*snap_points_bbox).dimensions() * Geom::Scale(1-x, 1-y);
338 } else {
339 _opposite_for_specpoints = _opposite_for_bboxpoints;
340 }
341 _opposite = _opposite_for_bboxpoints;
342 }
343
344 // When snapping the node closest to the mouse pointer is absolutely preferred over the closest snap
345 // (i.e. when weight == 1), then we will not even try to snap to other points and disregard those other points
346
347 if (prefs->getBool("/options/snapclosestonly/value", false)) {
348 _keepClosestPointOnly(p);
349 }
350
351 if ((x != -1) && (y != -1)) {
352 _norm->set_visible(true);
353 _grip->set_visible(true);
354 }
355
356 if (_show == SHOW_OUTLINE) {
357 for (auto & i : _l)
358 i->set_visible(true);
359 }
360
361 _updateHandles();
362 g_return_if_fail(_stamp_cache.empty());
363}
364
365void Inkscape::SelTrans::transform(Geom::Affine const &rel_affine, Geom::Point const &norm)
366{
367 g_return_if_fail(_grabbed);
368 g_return_if_fail(!_empty);
369
370 Geom::Affine const affine( Geom::Translate(-norm) * rel_affine * Geom::Translate(norm) );
371
372 if (_show == SHOW_CONTENT) {
373 auto selection = _desktop->getSelection();
374 // update the content
375 for (unsigned i = 0; i < _items.size(); i++) {
376 SPItem &item = *_items[i];
377 if( is<SPRoot>(&item) ) {
378 _desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Cannot transform an embedded SVG."));
379 break;
380 }
381
382 SiblingState sibling_state = selection->getSiblingState(&item);
383
390 if (sibling_state == SiblingState::SIBLING_TEXT_SHAPE_INSIDE || sibling_state == SiblingState::SIBLING_TEXT_PATH) {
391 continue;
392 }
393
394 Geom::Affine const &prev_transform = _items_affines[i];
395 item.set_i2d_affine(prev_transform * affine);
396 auto lpeitem = cast<SPLPEItem>(item.parent);
397 if (lpeitem && lpeitem->hasPathEffectRecursive()) {
398 sp_lpe_item_update_patheffect(lpeitem, true, false);
399 }
400 // The new affine will only have been applied if the transformation is different from the previous one, see SPItem::set_item_transform
401 }
402 } else {
403 if (_bbox) {
404 Geom::Point p[4];
405 /* update the outline */
406 for (unsigned i = 0 ; i < 4 ; i++) {
407 p[i] = _bbox->corner(i) * affine;
408 }
409 for (unsigned i = 0 ; i < 4 ; i++) {
410 _l[i]->set_coords(p[i], p[(i+1)%4]);
411 }
412 }
413 }
414
415 _current_relative_affine = affine;
416 _changed = true;
417 _updateHandles();
418}
419
421{
422 g_return_if_fail(_grabbed);
423 _grabbed = false;
424 _show_handles = true;
425
426 _desktop->getSnapIndicator()->remove_snapsource();
427
428 Inkscape::Selection *selection = _desktop->getSelection();
429 _updateVolatileState();
430
431 for (auto & _item : _items) {
432 sp_object_unref(_item, nullptr);
433 }
434
435 _norm->set_visible(false);
436 _grip->set_visible(false);
437
438 if (_show == SHOW_OUTLINE) {
439 for (auto & i : _l)
440 i->set_visible(false);
441 }
442 if (_stamped) {
443 _clear_stamp();
444 }
445
446 _message_context.clear();
447
448 if (!_empty && _changed) {
449 if (!_current_relative_affine.isIdentity()) { // we can have a identity affine
450 // when trying to stretch a perfectly vertical line in horizontal direction, which will not be allowed by the handles;
451
452 selection->applyAffine(_current_relative_affine, (_show == SHOW_OUTLINE) ? true : false);
453 if (_center) {
454 *_center *= _current_relative_affine;
455 _center_is_set = true;
456 }
457
458 // If dragging showed content live, sp_selection_apply_affine cannot change the centers
459 // appropriately - it does not know the original positions of the centers (all objects already have
460 // the new bboxes). So we need to reset the centers from our saved array.
461 if (_show != SHOW_OUTLINE && !_current_relative_affine.isTranslation()) {
462 for (unsigned i = 0; i < _items_centers.size(); i++) {
463 SPItem *currentItem = _items[i];
464 if (currentItem->isCenterSet()) { // only if it's already set
465 currentItem->setCenter (_items_centers[i] * _current_relative_affine);
466 currentItem->updateRepr();
467 }
468 }
469 }
470 for (unsigned i = 0; i < _items_centers.size(); i++) {
471 auto currentItem = cast<SPLPEItem>(_items[i]);
472 if (currentItem) {
473 sp_lpe_item_update_patheffect(currentItem, true, true);
474 }
475 }
476 }
477 _items.clear();
478 _objects_const.clear();
479 _items_affines.clear();
480 _items_centers.clear();
481
482 if (!_current_relative_affine.isIdentity()) { // we can have a identity affine
483 // when trying to stretch a perfectly vertical line in horizontal direction, which will not be allowed
484 // by the handles; this would be identified as a (zero) translation by isTranslation()
485 if (_current_relative_affine.isTranslation()) {
486 DocumentUndo::done(_desktop->getDocument(), _("Move"), INKSCAPE_ICON("tool-pointer"));
487 } else if (_current_relative_affine.withoutTranslation().isScale()) {
488 DocumentUndo::done(_desktop->getDocument(), _("Scale"), INKSCAPE_ICON("tool-pointer"));
489 } else if (_current_relative_affine.withoutTranslation().isRotation()) {
490 DocumentUndo::done(_desktop->getDocument(), _("Rotate"), INKSCAPE_ICON("tool-pointer"));
491 } else {
492 DocumentUndo::done(_desktop->getDocument(), _("Skew"), INKSCAPE_ICON("tool-pointer"));
493 }
494 } else {
495 _updateHandles();
496 }
497
498 } else {
499 if (_stamped) {
500 _clear_stamp();
501 }
502 if (_center_is_set) {
503 // we were dragging center; update reprs and commit undoable action
504 auto items= _desktop->getSelection()->items();
505 for (auto item : items) {
506 SPItem *it = item;
507 it->updateRepr();
508 }
509 DocumentUndo::done(_desktop->getDocument(), _("Set center"), INKSCAPE_ICON("tool-pointer"));
510 }
511
512 _items.clear();
513 _objects_const.clear();
514 _items_affines.clear();
515 _items_centers.clear();
516 _updateHandles();
517 }
518
519 _desktop->getSnapIndicator()->remove_snaptarget();
520}
521
522/* fixme: This is really bad, as we compare positions for each stamp (Lauris) */
523/* fixme: IMHO the best way to keep sort cache would be to implement timestamping at last */
524
526{
527 Inkscape::Selection *selection = _desktop->getSelection();
528
529 bool fixup = !_grabbed;
530 if ( fixup && !_stamp_cache.empty() ) {
531 // TODO - give a proper fix. Simple temporary work-around for the grab() issue
532 _stamp_cache.clear();
533 }
534
535 /* stamping mode */
536 if (!_empty) {
537 _stamped = true;
538 std::vector<SPItem*> l;
539 if (!_stamp_cache.empty()) {
540 l = _stamp_cache;
541 } else {
542 /* Build cache */
543 l.insert(l.end(), selection->items().begin(), selection->items().end());
544 sort(l.begin(), l.end(), sp_object_compare_position_bool);
545 _stamp_cache = l;
546 // we disable LPE while stamping and reenable on ungrab with _stamped bool
547 for (auto old_obj : l) {
548 auto oldLPEObj = cast<SPLPEItem>(old_obj);
549 if (oldLPEObj) {
550 sp_lpe_item_enable_path_effects(oldLPEObj, false);
551 }
552 }
553 }
554 std::vector<SPObject *> copies;
555 // special case on clones when dragging a clone without its original
556 // we check if its satellite is selected. if it has a clone original
557 // to allow perform the write statement on line:616
558 bool lpewritetransforms = true;
559 for (auto old_obj : l) {
560 auto oldLPEObj = cast<SPLPEItem>(old_obj);
561 if (oldLPEObj) {
562 auto effect = oldLPEObj->getFirstPathEffectOfType(Inkscape::LivePathEffect::CLONE_ORIGINAL);
563 if (effect) {
564 std::vector<SPObject *> satellites = effect->effect_get_satellites();
565 for (auto obj : satellites) {
566 if (!selection->includes(obj)) {
567 lpewritetransforms = false;
568 }
569 }
570 }
571 effect = oldLPEObj->getFirstPathEffectOfType(Inkscape::LivePathEffect::BEND_PATH);
572 if (effect) {
573 if (!oldLPEObj->optimizeTransforms() && selection->includes(oldLPEObj)) {
574 lpewritetransforms = false;
575 std::vector<SPObject *> satellites = effect->effect_get_satellites();
576 for (auto obj : satellites) {
577 if (selection->includes(obj)) {
578 lpewritetransforms = true;
579 }
580 }
581 }
582 }
583 }
584 }
585
586 for(auto &original_item : l) {
587 Inkscape::XML::Node *original_repr = original_item->getRepr();
588
589 // remember parent
590 Inkscape::XML::Node *parent = original_repr->parent();
591
592 Inkscape::XML::Node *copy_repr = nullptr;
593
594 if (clone) {
595 copy_repr = parent->document()->createElement("svg:use");
596 copy_repr->setAttribute("x", "0");
597 copy_repr->setAttribute("y", "0");
598 copy_repr->setAttribute("xlink:href", std::string("#") + original_item->getId());
599 copy_repr->setAttribute("inkscape:transform-center-x", original_repr->attribute("inkscape:transform-center-x"));
600 copy_repr->setAttribute("inkscape:transform-center-y", original_repr->attribute("inkscape:transform-center-y"));
601 } else {
602 copy_repr = original_repr->duplicate(parent->document());
603 }
604
605 // add the new repr to the parent
606 parent->addChild(copy_repr, original_repr->prev());
607
608 SPItem *copy_item = (SPItem *) _desktop->getDocument()->getObjectByRepr(copy_repr);
609 Geom::Affine new_affine = Geom::identity();
610 if (_show == SHOW_OUTLINE || clone) {
611 Geom::Affine const i2d(original_item->i2dt_affine());
612 Geom::Affine const i2dnew( i2d * _current_relative_affine );
613 copy_item->set_i2d_affine(i2dnew);
614 new_affine = copy_item->transform;
615 if (clone) {
616 new_affine = original_item->transform.inverse() * new_affine;
617 }
618 } else {
619 new_affine = original_item->transform;
620 }
621 if (original_item && copy_item && !clone) {
622 original_item->setTmpSuccessor(copy_item);
623 }
624 auto newLPEObj = cast<SPLPEItem>(copy_item);
625 if (newLPEObj) {
626 // disable LPE bool on dowrite to prevent move of selection original satellite
627 // when unselected (lpe performs a transform function that moves satellite and
628 // on unselect, goes to the wrong place)
629 if (newLPEObj->hasPathEffectOfType(Inkscape::LivePathEffect::BOOL_OP)) {
630 sp_lpe_item_enable_path_effects(newLPEObj,false);
631 }
632 }
633 if (!newLPEObj ||
634 !lpewritetransforms ||
635 (!newLPEObj->hasPathEffectOfType(Inkscape::LivePathEffect::CLONE_ORIGINAL) &&
636 !newLPEObj->hasPathEffectOfType(Inkscape::LivePathEffect::BEND_PATH)))
637 {
638 copy_item->doWriteTransform(new_affine);
639 if ( copy_item->isCenterSet() && _center ) {
640 copy_item->setCenter(*_center * _current_relative_affine);
641 }
642 }
643 Inkscape::GC::release(copy_repr);
644 copies.push_back(copy_item);
645 }
646 for (auto new_obj : copies) {
647 auto newLPEObj = cast<SPLPEItem>(new_obj);
648 if (newLPEObj && !clone) {
649 sp_lpe_item_enable_path_effects(newLPEObj,true);
650 newLPEObj->forkPathEffectsIfNecessary(1, true, true);
651 sp_lpe_item_update_patheffect(newLPEObj, false, true, true);
652 }
653 }
654 for(auto original_item : l) {
655 // unrefering tmp _successor (not needed anymore) used on fork to keep new satellite
656 // items forked along the LPEs
657 if (original_item && !clone) {
658 original_item->fixTmpSuccessors();
659 original_item->unsetTmpSuccessor();
660 }
661 }
662 DocumentUndo::done(_desktop->getDocument(), _("Stamp"), INKSCAPE_ICON("tool-pointer"));
663 }
664
665 if ( fixup && _stamped ) {
666 // TODO - give a proper fix. Simple temporary work-around for the grab() issue
667 _clear_stamp();
668 }
669}
670
672{
673 for (auto & knot : knots)
674 knot->hide();
675
676 if ( !_show_handles || _empty ) {
677 _desktop->getSelection()->setAnchor(0.0, 0.0, false);
678 return;
679 }
680
681 if (!_center_is_set) {
682 _center = _desktop->getSelection()->center();
683 _center_is_set = true;
684 }
685
686 if ( _state == STATE_SCALE ) {
687 _showHandles(HANDLE_STRETCH);
688 _showHandles(HANDLE_SCALE);
689 } else if(_state == STATE_ALIGN) {
690 _showHandles(HANDLE_SIDE_ALIGN);
691 _showHandles(HANDLE_CORNER_ALIGN);
692 _showHandles(HANDLE_CENTER_ALIGN);
693 } else {
694 _showHandles(HANDLE_SKEW);
695 _showHandles(HANDLE_ROTATE);
696 _showHandles(HANDLE_CENTER);
697 }
698
699 // Set anchor point, 0.0 is always set if nothing is selected (top/left).
700 bool set = false;
701 for (int i = 0; i < NUMHANDS; i++) {
702 if (knots[i]->is_selected()) {
703 double anchor_x, anchor_y = 0.0;
704 if (hands[i].type == HANDLE_CENTER) {
705 anchor_x = (_center->x() - _bbox->min()[Geom::X]) / _bbox->dimensions()[Geom::X];
706 anchor_y = (_center->y() - _bbox->min()[Geom::Y]) / _bbox->dimensions()[Geom::Y];
707 } else {
708 anchor_x = hands[i].x;
709 anchor_y = (hands[i].y - 0.5) * (-_desktop->yaxisdir()) + 0.5;
710 }
711 set = true;
712 _desktop->getSelection()->setAnchor(anchor_x, anchor_y);
713 }
714 }
715 if (!set)
716 _desktop->getSelection()->setAnchor(0.0, 0.0, false);
717}
718
720{
721 Inkscape::Selection *selection = _desktop->getSelection();
722 _empty = selection->isEmpty();
723
724 if (_empty) {
725 return;
726 }
727
728 //Update the bboxes
729 _bbox = selection->bounds(_snap_bbox_type);
730 _stroked_bbox = selection->strokedBounds();
731
732 if (!_bbox) {
733 _empty = true;
734 return;
735 }
736
737 std::vector<SPItem *> vec(selection->items().begin(), selection->items().end());
738 _strokewidth = stroke_average_width(vec);
739}
740
742{
743 // shouldn't have nullary bbox, but knots
744 g_assert(_bbox);
745
746 auto const y_dir = _desktop->yaxisdir();
747
748 for (int i = 0; i < NUMHANDS; i++) {
749 if (hands[i].type != type)
750 continue;
751
752 // Position knots to scale the selection bbox
753 Geom::Point const bpos(hands[i].x, (hands[i].y - 0.5) * (-y_dir) + 0.5);
754 Geom::Point p(_bbox->min() + (_bbox->dimensions() * Geom::Scale(bpos)));
755 knots[i]->moveto(p);
756 knots[i]->show();
757
758 // This controls the center handle's position, because the default can
759 // be moved and needs to be remembered.
760 if( type == HANDLE_CENTER && _center )
761 knots[i]->moveto(*_center);
762 }
763}
764
766{
767 for (int i = 0; i < NUMHANDS; i++) {
770
771 auto confine_mod = Modifier::get(Type::TRANS_CONFINE)->get_label();
772 auto center_mod = Modifier::get(Type::TRANS_OFF_CENTER)->get_label();
773 auto increment_mod = Modifier::get(Type::TRANS_INCREMENT)->get_label();
774
775 switch (hands[i].type) {
776 case HANDLE_STRETCH:
777 case HANDLE_SCALE:
778 {
779 auto tip = Glib::ustring::compose(_("<b>Scale</b> selection; with <b>%1</b> to scale uniformly; with <b>%2</b> to scale around rotation center"), confine_mod, center_mod);
780 knots[i] = new SPKnot(_desktop, tip.c_str(), CANVAS_ITEM_CTRL_TYPE_ADJ_HANDLE, "SelTrans");
781 break;
782 }
783 case HANDLE_SKEW:
784 {
785 auto tip = Glib::ustring::compose(_("<b>Skew</b> selection; with <b>%1</b> to snap angle; with <b>%2</b> to skew around the opposite side"), increment_mod, center_mod);
786 knots[i] = new SPKnot(_desktop, tip.c_str(), CANVAS_ITEM_CTRL_TYPE_ADJ_SKEW, "SelTrans");
787 break;
788 }
789 case HANDLE_ROTATE:
790 {
791 auto tip = Glib::ustring::compose(_("<b>Rotate</b> selection; with <b>%1</b> to snap angle; with <b>%2</b> to rotate around the opposite corner"), increment_mod, center_mod);
792 knots[i] = new SPKnot(_desktop, tip.c_str(), CANVAS_ITEM_CTRL_TYPE_ADJ_ROTATE, "SelTrans");
793 break;
794 }
795 case HANDLE_CENTER:
796 {
797 auto tip = Glib::ustring::compose(_("<b>Center</b> of transformation: drag to reposition; scaling, rotation and skew with %1 also uses this center"), center_mod);
798 knots[i] = new SPKnot(_desktop, tip.c_str(), CANVAS_ITEM_CTRL_TYPE_ADJ_CENTER, "SelTrans");
799 break;
800 }
802 knots[i] = new SPKnot(_desktop,
803 _("<b>Align</b> objects to the side clicked; <b>Shift</b> click to invert side; <b>Ctrl</b> to group whole selection."),
805 break;
807 knots[i] = new SPKnot(_desktop,
808 _("<b>Align</b> objects to the corner clicked; <b>Shift</b> click to invert side; <b>Ctrl</b> to group whole selection."),
810 break;
812 knots[i] = new SPKnot(_desktop,
813 _("<b>Align</b> objects to center; <b>Shift</b> click to center vertically instead of horizontally."),
815 break;
816 default:
817 knots[i] = new SPKnot(_desktop, "", CANVAS_ITEM_CTRL_TYPE_ADJ_HANDLE, "SelTrans");
818 }
819
820 knots[i]->setAnchor(hands[i].anchor);
821 knots[i]->updateCtrl();
822
823 knots[i]->request_signal.connect(sigc::bind(sigc::ptr_fun(sp_sel_trans_handle_request), &hands[i]));
824 knots[i]->moved_signal.connect(sigc::bind(sigc::ptr_fun(sp_sel_trans_handle_new_event), &hands[i]));
825 knots[i]->grabbed_signal.connect(sigc::bind(sigc::ptr_fun(sp_sel_trans_handle_grab), &hands[i]));
826 knots[i]->ungrabbed_signal.connect(sigc::bind(sigc::ptr_fun(sp_sel_trans_handle_ungrab), &hands[i]));
827 knots[i]->click_signal.connect(sigc::bind(sigc::ptr_fun(sp_sel_trans_handle_click), &hands[i]));
828 knots[i]->event_signal.connect(sigc::bind(sigc::ptr_fun(sp_sel_trans_handle_event), &hands[i]));
829 }
830}
831
832static void sp_sel_trans_handle_grab(SPKnot *knot, guint state, SPSelTransHandle const* data)
833{
834 SP_SELECT_CONTEXT(knot->desktop->getTool())->_seltrans->handleGrab(
835 knot, state, *(SPSelTransHandle const *) data
836 );
837}
838
839static void sp_sel_trans_handle_ungrab(SPKnot *knot, guint /*state*/, SPSelTransHandle const* /*data*/)
840{
841 SP_SELECT_CONTEXT(knot->desktop->getTool())->_seltrans->ungrab();
842}
843
844static void sp_sel_trans_handle_new_event(SPKnot *knot, Geom::Point const& position, guint state, SPSelTransHandle const *data)
845{
846 Geom::Point pos = position;
847
848 SP_SELECT_CONTEXT(knot->desktop->getTool())->_seltrans->handleNewEvent(
849 knot, &pos, state, *(SPSelTransHandle const *) data
850 );
851}
852
853static gboolean sp_sel_trans_handle_request(SPKnot *knot, Geom::Point *position, guint state, SPSelTransHandle const *data)
854{
855 return SP_SELECT_CONTEXT(knot->desktop->getTool())->_seltrans->handleRequest(
856 knot, position, state, *(SPSelTransHandle const *) data
857 );
858}
859
860static void sp_sel_trans_handle_click(SPKnot *knot, guint state, SPSelTransHandle const* data)
861{
862 SP_SELECT_CONTEXT(knot->desktop->getTool())->_seltrans->handleClick(
863 knot, state, *(SPSelTransHandle const *) data
864 );
865}
866
867void Inkscape::SelTrans::handleClick(SPKnot *knot, guint state, SPSelTransHandle const &handle)
868{
869 switch (handle.type) {
870 case HANDLE_CENTER:
871 if (state & GDK_SHIFT_MASK) {
872 // Unset the center position for all selected items
873 auto items = _desktop->getSelection()->items();
874 for (auto it : items) {
875 it->unsetCenter();
876 it->updateRepr();
877 _center_is_set = false; // center has changed
878 _updateHandles();
879 }
880 DocumentUndo::done(_desktop->getDocument(), _("Reset center"), INKSCAPE_ICON("tool-pointer"));
881 }
882 // no break, continue.
883 case HANDLE_STRETCH:
884 case HANDLE_SCALE:
885 {
886 bool was_selected = knot->is_selected();
887 for (auto & child_knot : knots) {
888 child_knot->selectKnot(false);
889 }
890 if (!was_selected) {
891 knot->selectKnot(true);
892 }
893 _updateHandles();
894 }
895 break;
899 align(state, handle);
900 default:
901 break;
902 }
903}
904
905void Inkscape::SelTrans::handleGrab(SPKnot *knot, guint /*state*/, SPSelTransHandle const &handle)
906{
907 grab(knot->position(), handle.x, handle.y, FALSE, FALSE);
908
909 // Forcing handles visibility must be done after grab() to be effective
910 switch (handle.type) {
911 case HANDLE_CENTER:
913 _norm->set_visible(false);
914 _grip->set_visible(true);
915 break;
916 default:
918 _norm->set_visible(true);
919 _grip->set_visible(true);
920 break;
921 }
922}
923
924
925void Inkscape::SelTrans::handleNewEvent(SPKnot *knot, Geom::Point *position, guint state, SPSelTransHandle const &handle)
926{
927 if (!knot->is_grabbed()) {
928 return;
929 }
930
931 // in case items have been unhooked from the document, don't
932 // try to continue processing events for them.
933 for (auto & _item : _items) {
934 if ( !_item->document ) {
935 return;
936 }
937 }
938 switch (handle.type) {
939 case HANDLE_SCALE:
940 scale(*position, state);
941 break;
942 case HANDLE_STRETCH:
943 stretch(handle, *position, state);
944 break;
945 case HANDLE_SKEW:
946 skew(handle, *position, state);
947 break;
948 case HANDLE_ROTATE:
949 rotate(*position, state);
950 break;
951 case HANDLE_CENTER:
952 setCenter(*position);
953 break;
957 break;
958 }
959}
960
961
962gboolean Inkscape::SelTrans::handleRequest(SPKnot *knot, Geom::Point *position, guint state, SPSelTransHandle const &handle)
963{
964 if (!knot->is_grabbed()) {
965 return TRUE;
966 }
967
968 // When holding shift while rotating or skewing, the transformation will be
969 // relative to the point opposite of the handle; otherwise it will be relative
970 // to the center as set for the selection
972 if ((!off_center == !(_state == STATE_ROTATE)) && (handle.type != HANDLE_CENTER)) {
973 _origin = _opposite;
974 _origin_for_bboxpoints = _opposite_for_bboxpoints;
975 _origin_for_specpoints = _opposite_for_specpoints;
976 } else if (_center) {
977 _origin = *_center;
978 _origin_for_bboxpoints = *_center;
979 _origin_for_specpoints = *_center;
980 } else {
981 // FIXME
982 return TRUE;
983 }
984 if (request(handle, *position, state)) {
985 knot->setPosition(*position, state);
986 _grip->set_position(*position);
987 if (handle.type == HANDLE_CENTER) {
988 _norm->set_position(*position);
989 } else {
990 _norm->set_position(_origin);
991 }
992 }
993
994 return TRUE;
995}
996
997
999{
1000 if (!_grabbed) {
1002 // reread in case it changed on the fly:
1003 int prefs_bbox = prefs->getBool("/tools/bounding_box");
1004 _snap_bbox_type = !prefs_bbox ?
1006
1007 _updateVolatileState();
1008 _current_relative_affine.setIdentity();
1009 _center_is_set = false; // center(s) may have changed
1010 auto items= selection->items();
1011 for (auto item : items) {
1012 SPItem *it = static_cast<SPItem*>(sp_object_ref(item, nullptr));
1013 auto lpeitem = cast<SPLPEItem>(it);
1014 // only update if never do a LPE cycle (document load, revert...) and selection is not a layer
1015 if (lpeitem && !lpeitem->lpe_initialized && (!is<SPGroup>(lpeitem) || !lpeitem->getAttribute("inkscape:groupmode"))) {
1016 sp_lpe_item_update_patheffect(lpeitem, true, true);
1017 }
1019 }
1020 _updateHandles();
1021 }
1022}
1023
1024void Inkscape::SelTrans::_selModified(Inkscape::Selection */*selection*/, guint /*flags*/)
1025{
1026 if (!_grabbed) {
1027 _updateVolatileState();
1028 _current_relative_affine.setIdentity();
1029
1030 // reset internal flag
1031 _changed = false;
1032
1033 _center_is_set = false; // center(s) may have changed
1034
1035 _updateHandles();
1036 }
1037}
1038
1040{
1041 _snap_bbox_type = !prefs_bbox ?
1043
1044 _updateVolatileState();
1045 _updateHandles();
1046}
1047
1048/*
1049 * handlers for handle move-request
1050 */
1051
1053static double sign(double const x)
1054{
1055 return ( x < 0
1056 ? -1
1057 : 1 );
1058}
1059
1061{
1062
1063 // Calculate the scale factors, which can be either visual or geometric
1064 // depending on which type of bbox is currently being used (see preferences -> selector tool)
1065 Geom::Scale default_scale = calcScaleFactors(_point, pt, _origin);
1066
1067 // Find the scale factors for the geometric bbox
1068 Geom::Point pt_geom = _getGeomHandlePos(pt);
1069 Geom::Scale geom_scale = calcScaleFactors(_point_geom, pt_geom, _origin_for_specpoints);
1070
1071 _absolute_affine = Geom::identity(); //Initialize the scaler
1072
1074 if (increments) { // scale by an integer multiplier/divider
1075 // We're scaling either the visual or the geometric bbox here (see the comment above)
1076 for ( unsigned int i = 0 ; i < 2 ; i++ ) {
1077 if (fabs(default_scale[i]) > 1) {
1078 default_scale[i] = round(default_scale[i]);
1079 } else if (default_scale[i] != 0) {
1080 default_scale[i] = 1/round(1/(MIN(default_scale[i], 10)));
1081 }
1082 }
1083 // Update the knot position
1084 pt = _calcAbsAffineDefault(default_scale);
1085 // When scaling by an integer, snapping is not needed
1086 } else {
1087 // In all other cases we should try to snap now
1088 Inkscape::PureScale *bb, *sn;
1089
1091 if (confine || Inkscape::Preferences::get()->getBool("/tools/select/lock_aspect_ratio", false)) {
1092 // Scale is locked to a 1:1 aspect ratio, so that s[X] must be made to equal s[Y].
1093 //
1094 // The aspect-ratio must be locked before snapping
1095 if (fabs(default_scale[Geom::X]) > fabs(default_scale[Geom::Y])) {
1096 default_scale[Geom::X] = fabs(default_scale[Geom::Y]) * sign(default_scale[Geom::X]);
1097 geom_scale[Geom::X] = fabs(geom_scale[Geom::Y]) * sign(geom_scale[Geom::X]);
1098 } else {
1099 default_scale[Geom::Y] = fabs(default_scale[Geom::X]) * sign(default_scale[Geom::Y]);
1100 geom_scale[Geom::Y] = fabs(geom_scale[Geom::X]) * sign(geom_scale[Geom::Y]);
1101 }
1102
1103 // Snap along a suitable constraint vector from the origin.
1104
1105 bb = new Inkscape::PureScaleConstrained(default_scale, _origin_for_bboxpoints);
1106 sn = new Inkscape::PureScaleConstrained(geom_scale, _origin_for_specpoints);
1107 } else {
1108 /* Scale aspect ratio is unlocked */
1109 bb = new Inkscape::PureScale(default_scale, _origin_for_bboxpoints, false);
1110 sn = new Inkscape::PureScale(geom_scale, _origin_for_specpoints, false);
1111 }
1112 SnapManager &m = _desktop->getNamedView()->snap_manager;
1113 m.setup(_desktop, false, _objects_const);
1114 m.snapTransformed(_bbox_points, _point, (*bb));
1115 m.snapTransformed(_snap_points, _point, (*sn));
1116 m.unSetup();
1117
1118 // These lines below are duplicated in stretchRequest
1119 //TODO: Eliminate this code duplication
1122 // We snapped the bbox (which is either visual or geometric)
1123 _desktop->getSnapIndicator()->set_new_snaptarget(bb->best_snapped_point);
1124 default_scale = bb->getScaleSnapped();
1125 // Calculate the new transformation and update the handle position
1126 pt = _calcAbsAffineDefault(default_scale);
1127 } else if (sn->best_snapped_point.getSnapped()) {
1128 _desktop->getSnapIndicator()->set_new_snaptarget(sn->best_snapped_point);
1129 // We snapped the special points (e.g. nodes), which are not at the visual bbox
1130 // The handle location however (pt) might however be at the visual bbox, so we
1131 // will have to calculate pt taking the stroke width into account
1132 geom_scale = sn->getScaleSnapped();
1133 pt = _calcAbsAffineGeom(geom_scale);
1134 }
1135 } else {
1136 // We didn't snap at all! Don't update the handle position, just calculate the new transformation
1137 _calcAbsAffineDefault(default_scale);
1138 _desktop->getSnapIndicator()->remove_snaptarget();
1139 }
1140
1141 delete bb;
1142 delete sn;
1143 }
1144
1145 /* Status text */
1147 _message_context.setF(Inkscape::IMMEDIATE_MESSAGE,
1148 _("<b>Scale</b>: %0.2f%% x %0.2f%%; with <b>%s</b> to lock ratio"),
1149 100 * _absolute_affine[0], 100 * _absolute_affine[3], confine_mod.c_str());
1150
1151 return TRUE;
1152}
1153
1154gboolean Inkscape::SelTrans::stretchRequest(SPSelTransHandle const &handle, Geom::Point &pt, guint state)
1155{
1156 Geom::Dim2 axis, perp;
1157 switch (handle.anchor) {
1158 case SP_ANCHOR_S:
1159 case SP_ANCHOR_N:
1160 axis = Geom::Y;
1161 perp = Geom::X;
1162 break;
1163 case SP_ANCHOR_W:
1164 case SP_ANCHOR_E:
1165 axis = Geom::X;
1166 perp = Geom::Y;
1167 break;
1168 default:
1169 g_assert_not_reached();
1170 return TRUE;
1171 };
1172
1173 // Calculate the scale factors, which can be either visual or geometric
1174 // depending on which type of bbox is currently being used (see preferences -> selector tool)
1175 Geom::Scale default_scale = calcScaleFactors(_point, pt, _origin);
1176 default_scale[perp] = 1;
1177
1178 // Find the scale factors for the geometric bbox
1179 Geom::Point pt_geom = _getGeomHandlePos(pt);
1180 Geom::Scale geom_scale = calcScaleFactors(_point_geom, pt_geom, _origin_for_specpoints);
1181 geom_scale[perp] = 1;
1182
1183 _absolute_affine = Geom::identity(); //Initialize the scaler
1184
1186 if (increments) { // stretch by an integer multiplier/divider
1187 if (fabs(default_scale[axis]) > 1) {
1188 default_scale[axis] = round(default_scale[axis]);
1189 } else if (default_scale[axis] != 0) {
1190 default_scale[axis] = 1/round(1/(MIN(default_scale[axis], 10)));
1191 }
1192 // Calculate the new transformation and update the handle position
1193 pt = _calcAbsAffineDefault(default_scale);
1194 // When stretching by an integer, snapping is not needed
1195 } else {
1196 // In all other cases we should try to snap now
1197
1198 SnapManager &m = _desktop->getNamedView()->snap_manager;
1199 m.setup(_desktop, false, _objects_const);
1200
1202 Inkscape::PureStretchConstrained bb = Inkscape::PureStretchConstrained(Geom::Coord(default_scale[axis]), _origin_for_bboxpoints, Geom::Dim2(axis), confine);
1203 Inkscape::PureStretchConstrained sn = Inkscape::PureStretchConstrained(Geom::Coord(geom_scale[axis]), _origin_for_specpoints, Geom::Dim2(axis), confine);
1204
1205 m.snapTransformed(_bbox_points, _point, bb);
1206 m.snapTransformed(_snap_points, _point, sn);
1207 m.unSetup();
1208
1209 if (bb.best_snapped_point.getSnapped()) {
1210 // We snapped the bbox (which is either visual or geometric)
1211 default_scale[axis] = bb.getMagnitude();
1212 }
1213
1214 if (sn.best_snapped_point.getSnapped()) {
1215 geom_scale[axis] = sn.getMagnitude();
1216 }
1217
1218 if (confine) {
1219 // on scale_confine, apply symmetrical scaling instead of stretching
1220 // Preserve aspect ratio, but never flip in the dimension not being edited (by using fabs())
1221 default_scale[perp] = fabs(default_scale[axis]);
1222 geom_scale[perp] = fabs(geom_scale[axis]);
1223 }
1224
1225 // These lines below are duplicated in scaleRequest
1228 // We snapped the bbox (which is either visual or geometric)
1229 _desktop->getSnapIndicator()->set_new_snaptarget(bb.best_snapped_point);
1230 default_scale = bb.getStretchSnapped();
1231 // Calculate the new transformation and update the handle position
1232 pt = _calcAbsAffineDefault(default_scale);
1233 } else if (sn.best_snapped_point.getSnapped()) {
1234 _desktop->getSnapIndicator()->set_new_snaptarget(sn.best_snapped_point);
1235 // We snapped the special points (e.g. nodes), which are not at the visual bbox
1236 // The handle location however (pt) might however be at the visual bbox, so we
1237 // will have to calculate pt taking the stroke width into account
1238 geom_scale = sn.getStretchSnapped();
1239 pt = _calcAbsAffineGeom(geom_scale);
1240 }
1241 } else {
1242 // We didn't snap at all! Don't update the handle position, just calculate the new transformation
1243 _calcAbsAffineDefault(default_scale);
1244 _desktop->getSnapIndicator()->remove_snaptarget();
1245 }
1246 }
1247
1248 // status text
1250 _message_context.setF(Inkscape::IMMEDIATE_MESSAGE,
1251 _("<b>Scale</b>: %0.2f%% x %0.2f%%; with <b>%s</b> to lock ratio"),
1252 100 * _absolute_affine[0], 100 * _absolute_affine[3], confine_mod.c_str());
1253
1254 return TRUE;
1255}
1256
1257gboolean Inkscape::SelTrans::request(SPSelTransHandle const &handle, Geom::Point &pt, guint state)
1258{
1259 // These _should_ be in the handstype somewhere instead
1260 switch (handle.type) {
1261 case HANDLE_SCALE:
1262 return scaleRequest(pt, state);
1263 case HANDLE_STRETCH:
1264 return stretchRequest(handle, pt, state);
1265 case HANDLE_SKEW:
1266 return skewRequest(handle, pt, state);
1267 case HANDLE_ROTATE:
1268 return rotateRequest(pt, state);
1269 case HANDLE_CENTER:
1270 return centerRequest(pt, state);
1271 case HANDLE_SIDE_ALIGN:
1274 break; // Do nothing, no dragging
1275 }
1276 return FALSE;
1277}
1278
1279gboolean Inkscape::SelTrans::skewRequest(SPSelTransHandle const &handle, Geom::Point &pt, guint state)
1280{
1281 /* When skewing (or rotating):
1282 * 1) the stroke width will not change. This makes life much easier because we don't have to
1283 * account for that (like for scaling or stretching). As a consequence, all points will
1284 * have the same origin for the transformation and for the snapping.
1285 * 2) When holding shift, the transformation will be relative to the point opposite of
1286 * the handle; otherwise it will be relative to the center as set for the selection
1287 */
1288
1289 Geom::Dim2 dim_a;
1290 Geom::Dim2 dim_b;
1291
1292 switch (handle.anchor) {
1293 case SP_ANCHOR_S:
1294 case SP_ANCHOR_N:
1295 dim_a = Geom::Y;
1296 dim_b = Geom::X;
1297 break;
1298 case SP_ANCHOR_W:
1299 case SP_ANCHOR_E:
1300 dim_a = Geom::X;
1301 dim_b = Geom::Y;
1302 break;
1303 default:
1304 g_assert_not_reached();
1305 std::terminate();
1306 break;
1307 }
1308
1309 // _point and _origin are noisy, ranging from 1 to 1e-9 or even smaller; this is due to the
1310 // limited SVG output precision, which can be arbitrarily set in the preferences
1311 Geom::Point const initial_delta = _point - _origin;
1312
1313 // The handle and the origin shouldn't be too close to each other; let's check for that!
1314 // Due to the limited resolution though (see above), we'd better use a relative error here
1315 if (_bbox) {
1316 Geom::Coord d = (*_bbox).dimensions()[dim_a];
1317 if (fabs(initial_delta[dim_a]/d) < 1e-4) {
1318 return false;
1319 }
1320 }
1321
1322 // Calculate the scale factors, which can be either visual or geometric
1323 // depending on which type of bbox is currently being used (see preferences -> selector tool)
1324 Geom::Scale scale = calcScaleFactors(_point, pt, _origin, false);
1325 Geom::Scale skew = calcScaleFactors(_point, pt, _origin, true);
1326 scale[dim_b] = 1;
1327 skew[dim_b] = 1;
1328
1329 if (fabs(scale[dim_a]) < 1) {
1330 // Prevent shrinking of the selected object, while allowing mirroring
1331 scale[dim_a] = sign(scale[dim_a]);
1332 } else {
1333 // Allow expanding of the selected object by integer multiples
1334 scale[dim_a] = floor(scale[dim_a] + 0.5);
1335 }
1336
1337 double radians = atan(skew[dim_a] / scale[dim_a]);
1338
1340 if (increments) {
1342 // Snap to defined angle increments
1343 int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1344 if (snaps) {
1345 double sections = floor(radians * snaps / M_PI + .5);
1346 if (fabs(sections) >= snaps / 2) {
1347 sections = sign(sections) * (snaps / 2 - 1);
1348 }
1349 radians = (M_PI / snaps) * sections;
1350 }
1351 skew[dim_a] = tan(radians) * scale[dim_a];
1352 } else {
1353 // Snap to objects, grids, guides
1354
1355 SnapManager &m = _desktop->getNamedView()->snap_manager;
1356 m.setup(_desktop, false, _objects_const);
1357
1358 // When skewing, we cannot snap the corners of the bounding box, see the comment in PureSkewConstrained for details
1359 Inkscape::PureSkewConstrained sn = Inkscape::PureSkewConstrained(skew[dim_a], scale[dim_a], _origin, Geom::Dim2(dim_b));
1360 m.snapTransformed(_snap_points, _point, sn);
1361
1362 if (sn.best_snapped_point.getSnapped()) {
1363 // We snapped something, so change the skew to reflect it
1364 skew[dim_a] = sn.getSkewSnapped();
1365 _desktop->getSnapIndicator()->set_new_snaptarget(sn.best_snapped_point);
1366 } else {
1367 _desktop->getSnapIndicator()->remove_snaptarget();
1368 }
1369
1370 m.unSetup();
1371 }
1372
1373 // Update the handle position
1374 pt[dim_b] = initial_delta[dim_a] * skew[dim_a] + _point[dim_b];
1375 pt[dim_a] = initial_delta[dim_a] * scale[dim_a] + _origin[dim_a];
1376
1377 // Calculate the relative affine
1378 _relative_affine = Geom::identity();
1379 _relative_affine[2*dim_a + dim_a] = (pt[dim_a] - _origin[dim_a]) / initial_delta[dim_a];
1380 _relative_affine[2*dim_a + (dim_b)] = (pt[dim_b] - _point[dim_b]) / initial_delta[dim_a];
1381 _relative_affine[2*(dim_b) + (dim_a)] = 0;
1382 _relative_affine[2*(dim_b) + (dim_b)] = 1;
1383
1384 for (int i = 0; i < 2; i++) {
1385 if (fabs(_relative_affine[3*i]) < 1e-15) {
1386 _relative_affine[3*i] = 1e-15;
1387 }
1388 }
1389
1390 // Update the status text
1392 double degrees = mod360symm(Geom::deg_from_rad(radians));
1393 _message_context.setF(Inkscape::IMMEDIATE_MESSAGE,
1394 // TRANSLATORS: don't modify the first ";"
1395 // (it will NOT be displayed as ";" - only the second one will be)
1396 _("<b>Skew</b>: %0.2f&#176;; with <b>%s</b> to snap angle"),
1397 degrees, increment_mod.c_str());
1398
1399 return TRUE;
1400}
1401
1403{
1404 /* When rotating (or skewing):
1405 * 1) the stroke width will not change. This makes life much easier because we don't have to
1406 * account for that (like for scaling or stretching). As a consequence, all points will
1407 * have the same origin for the transformation and for the snapping.
1408 * 2) When holding shift, the transformation will be relative to the point opposite of
1409 * the handle; otherwise it will be relative to the center as set for the selection
1410 */
1411
1413 int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1414
1415 // rotate affine in rotate
1416 Geom::Point const d1 = _point - _origin;
1417 Geom::Point const d2 = pt - _origin;
1418
1419 Geom::Coord const h1 = Geom::L2(d1); // initial radius
1420 if (h1 < 1e-15) return FALSE;
1421 Geom::Point q1 = d1 / h1; // normalized initial vector to handle
1422 Geom::Coord const h2 = Geom::L2(d2); // new radius
1423 if (fabs(h2) < 1e-15) return FALSE;
1424 Geom::Point q2 = d2 / h2; // normalized new vector to handle
1425
1426 Geom::Rotate r1(q1);
1427 Geom::Rotate r2(q2);
1428
1429 double radians = atan2(Geom::dot(Geom::rot90(d1), d2), Geom::dot(d1, d2));;
1432 // Either key will now snap the rotation to specific points
1433 if (increments || confine) {
1434 // Snap to defined angle increments
1435 double cos_t = Geom::dot(q1, q2);
1436 double sin_t = Geom::dot(Geom::rot90(q1), q2);
1437 radians = atan2(sin_t, cos_t);
1438 if (snaps) {
1439 radians = ( M_PI / snaps ) * floor( radians * snaps / M_PI + .5 );
1440 }
1441 r1 = Geom::Rotate(0); //q1 = Geom::Point(1, 0);
1442 r2 = Geom::Rotate(radians); //q2 = Geom::Point(cos(radians), sin(radians));
1443 } else {
1444 SnapManager &m = _desktop->getNamedView()->snap_manager;
1445 m.setup(_desktop, false, _objects_const);
1446 // When rotating, we cannot snap the corners of the bounding box, see the comment in "constrainedSnapRotate" for details
1448 m.snapTransformed(_snap_points, _point, sn);
1449 m.unSetup();
1450
1451 if (sn.best_snapped_point.getSnapped()) {
1452 _desktop->getSnapIndicator()->set_new_snaptarget(sn.best_snapped_point);
1453 // We snapped something, so change the rotation to reflect it
1454 radians = sn.getAngleSnapped();
1455 r1 = Geom::Rotate(0);
1456 r2 = Geom::Rotate(radians);
1457 } else {
1458 _desktop->getSnapIndicator()->remove_snaptarget();
1459 }
1460
1461 }
1462
1463
1464 // Calculate the relative affine
1465 _relative_affine = r2 * r1.inverse();
1466
1467 // Update the handle position
1468 pt = _point * Geom::Translate(-_origin) * _relative_affine * Geom::Translate(_origin);
1469
1470 // Update the status text
1472 double degrees = mod360symm(Geom::deg_from_rad(radians));
1473 _message_context.setF(Inkscape::IMMEDIATE_MESSAGE,
1474 // TRANSLATORS: don't modify the first ";"
1475 // (it will NOT be displayed as ";" - only the second one will be)
1476 _("<b>Rotate</b>: %0.2f&#176;; with <b>%s</b> to snap angle"), degrees, increment_mod.c_str());
1477
1478 return TRUE;
1479}
1480
1481// Move the item's transformation center
1483{
1484 // When dragging the transformation center while multiple items have been selected, then those
1485 // items will share a single center. While dragging that single center, it should never snap to the
1486 // centers of any of the selected objects. Therefore we will have to pass the list of selected items
1487 // to the snapper, to avoid self-snapping of the rotation center
1488 std::vector<SPItem *> items(_selection->items().begin(), _selection->items().end());
1489 SnapManager &m = _desktop->getNamedView()->snap_manager;
1490 m.setup(_desktop);
1492
1495 if (confine) {
1496 std::vector<Inkscape::Snapper::SnapConstraint> constraints;
1497 constraints.emplace_back(_point, Geom::Point(1, 0));
1498 constraints.emplace_back(_point, Geom::Point(0, 1));
1500 pt = sp.getPoint();
1501 }
1502 else if (!no_snap) {
1504 }
1505
1506 m.unSetup();
1507
1508 // status text
1511 Glib::ustring xs(x_q.string(_desktop->getNamedView()->display_units));
1512 Glib::ustring ys(y_q.string(_desktop->getNamedView()->display_units));
1513 _message_context.setF(Inkscape::NORMAL_MESSAGE, _("Move <b>center</b> to %s, %s"),
1514 xs.c_str(), ys.c_str());
1515 return TRUE;
1516}
1517
1518void Inkscape::SelTrans::align(guint state, SPSelTransHandle const &handle)
1519{
1520 Glib::ustring argument;
1521 int index = handle.control + ALIGN_OFFSET + ((state & GDK_SHIFT_MASK) ? ALIGN_SHIFT_OFFSET : 0);
1522 if (index < 0 || index >= AlignArguments.size()) {
1523 std::cerr << "Inkscape::Seltrans::align: index out of bounds! " << index << std::endl;
1524 index = 0;
1525 }
1526
1527 auto variant = Glib::Variant<Glib::ustring>::create(AlignArguments[index]);
1528 auto app = Gio::Application::get_default();
1529 app->activate_action("object-align", variant);
1530}
1531
1532/*
1533 * handlers for handle movement
1534 *
1535 */
1536
1537
1538
1539void Inkscape::SelTrans::stretch(SPSelTransHandle const &/*handle*/, Geom::Point &/*pt*/, guint /*state*/)
1540{
1541 transform(_absolute_affine, Geom::Point(0, 0)); // we have already accounted for origin, so pass 0,0
1542}
1543
1544void Inkscape::SelTrans::scale(Geom::Point &/*pt*/, guint /*state*/)
1545{
1546 transform(_absolute_affine, Geom::Point(0, 0)); // we have already accounted for origin, so pass 0,0
1547}
1548
1549void Inkscape::SelTrans::skew(SPSelTransHandle const &/*handle*/, Geom::Point &/*pt*/, guint /*state*/)
1550{
1551 transform(_relative_affine, _origin);
1552}
1553
1554void Inkscape::SelTrans::rotate(Geom::Point &/*pt*/, guint /*state*/)
1555{
1556 transform(_relative_affine, _origin);
1557}
1558
1559void Inkscape::SelTrans::moveTo(Geom::Point const &xy, guint state)
1560{
1561 SnapManager &m = _desktop->getNamedView()->snap_manager;
1562
1563 /* The amount that we've moved by during this drag */
1564 Geom::Point dxy = xy - _point;
1565
1569
1570 if (confine) {
1571 if (fabs(dxy[Geom::X]) > fabs(dxy[Geom::Y])) {
1572 dxy[Geom::Y] = 0;
1573 } else {
1574 dxy[Geom::X] = 0;
1575 }
1576 }
1577
1578 if (increments) {// Alt pressed means: move only by integer multiples of the grid spacing
1579 m.setup(_desktop, true, _objects_const);
1580 dxy = m.multipleOfGridPitch(dxy, _point);
1581 m.unSetup();
1582 } else if (!no_snap) {
1583 /* We're snapping to things, possibly with a constraint to horizontal or
1584 ** vertical movement. Obtain a list of possible translations and then
1585 ** pick the smallest.
1586 */
1587
1588 m.setup(_desktop, false, _objects_const);
1589
1590 /* This will be our list of possible translations */
1591 std::list<Inkscape::SnappedPoint> s;
1592
1593 Inkscape::PureTranslate *bb, *sn;
1594
1595 if (confine) { // constrained movement with snapping
1596
1597 /* Snap to things, and also constrain to horizontal or vertical movement */
1598
1599 Geom::Dim2 dim = fabs(dxy[Geom::X]) > fabs(dxy[Geom::Y]) ? Geom::X : Geom::Y;
1600 // When doing a constrained translation, all points will move in the same direction, i.e.
1601 // either horizontally or vertically. Therefore we only have to specify the direction of
1602 // the constraint-line once. The constraint lines are parallel, but might not be colinear.
1603 // Therefore we will have to set the point through which the constraint-line runs
1604 // individually for each point to be snapped; this will be handled however by snapTransformed()
1605 bb = new Inkscape::PureTranslateConstrained(dxy[dim], dim);
1606 sn = new Inkscape::PureTranslateConstrained(dxy[dim], dim);
1607 } else {
1608 /* Snap to things with no constraint */
1609 bb = new Inkscape::PureTranslate(dxy);
1610 sn = new Inkscape::PureTranslate(dxy);
1611 }
1612 // Let's leave this timer code here for a while. I'll probably need it in the near future (Diederik van Lierop)
1613 /* GTimeVal starttime;
1614 GTimeVal endtime;
1615 g_get_current_time(&starttime); */
1616
1617 m.snapTransformed(_bbox_points, _point, (*bb));
1618 m.snapTransformed(_snap_points, _point, (*sn));
1619 m.unSetup();
1620
1621 /*g_get_current_time(&endtime);
1622 double elapsed = ((((double)endtime.tv_sec - starttime.tv_sec) * G_USEC_PER_SEC + (endtime.tv_usec - starttime.tv_usec))) / 1000.0;
1623 std::cout << "Time spent snapping: " << elapsed << std::endl; */
1624
1625 /* Pick one */
1626 Inkscape::SnappedPoint best_snapped_point;
1627
1628 bool sn_is_best = sn->best_snapped_point.getSnapped();
1629 bool bb_is_best = bb->best_snapped_point.getSnapped();
1630
1631 if (bb_is_best && sn_is_best) {
1632 sn_is_best = bb->best_snapped_point.isOtherSnapBetter(sn->best_snapped_point, true);
1633 bb_is_best = !sn_is_best;
1634 }
1635
1636 if (sn_is_best) {
1637 best_snapped_point = sn->best_snapped_point;
1638 dxy = sn->getTranslationSnapped();
1639 } else if (bb_is_best) {
1640 best_snapped_point = bb->best_snapped_point;
1641 dxy = bb->getTranslationSnapped();
1642 }
1643
1644 if (best_snapped_point.getSnapped()) {
1645 _desktop->getSnapIndicator()->set_new_snaptarget(best_snapped_point);
1646 } else {
1647 // We didn't snap, so remove any previous snap indicator
1648 _desktop->getSnapIndicator()->remove_snaptarget();
1649 if (confine) {
1650 // If we didn't snap, then we should still constrain horizontally or vertically
1651 // (When we did snap, then this constraint has already been enforced by
1652 // calling constrainedSnapTranslate() above)
1653 if (fabs(dxy[Geom::X]) > fabs(dxy[Geom::Y])) {
1654 dxy[Geom::Y] = 0;
1655 } else {
1656 dxy[Geom::X] = 0;
1657 }
1658 }
1659 }
1660 delete bb;
1661 delete sn;
1662 }
1663
1664 Geom::Affine const move((Geom::Translate(dxy)));
1665 Geom::Point const norm(0, 0);
1666 transform(move, norm);
1667
1668 // status text
1673 Glib::ustring xs(x_q.string(_desktop->getNamedView()->display_units));
1674 Glib::ustring ys(y_q.string(_desktop->getNamedView()->display_units));
1675 _message_context.setF(Inkscape::NORMAL_MESSAGE,
1676 _("<b>Move</b> by %s, %s; with <b>%s</b> to restrict to horizontal/vertical; with <b>%s</b> to disable snapping"),
1677 xs.c_str(), ys.c_str(), confine_mod.c_str(), no_snap_mod.c_str());
1678}
1679
1680// Given a location of a handle at the visual bounding box, find the corresponding location at the
1681// geometrical bounding box
1683{
1684 if ( _snap_bbox_type == SPItem::GEOMETRIC_BBOX) {
1685 // When the selector tool is using geometric bboxes, then the handle is already
1686 // located at one of the geometric bbox corners
1687 return visual_handle_pos;
1688 }
1689
1690 if (!_geometric_bbox) {
1691 //_getGeomHandlePos() can only be used after _geometric_bbox has been defined!
1692 return visual_handle_pos;
1693 }
1694
1695 // Using the Geom::Rect constructor below ensures that "min() < max()", which is important
1696 // because this will also hold for _bbox, and which is required for get_scale_transform_for_stroke()
1697 Geom::Rect new_bbox = Geom::Rect(_origin_for_bboxpoints, visual_handle_pos); // new visual bounding box
1698 // Please note that the new_bbox might in fact be just a single line, for example when stretching (in
1699 // which case the handle and origin will be aligned vertically or horizontally)
1700 Geom::Point normalized_handle_pos = (visual_handle_pos - new_bbox.min()) * Geom::Scale(new_bbox.dimensions()).inverse();
1701
1702 // Calculate the absolute affine while taking into account the scaling of the stroke width
1704 bool transform_stroke = prefs->getBool("/options/transform/stroke", true);
1705 bool preserve = prefs->getBool("/options/preservetransform/value", false);
1706 Geom::Affine abs_affine = get_scale_transform_for_uniform_stroke (*_bbox, _strokewidth, _strokewidth, transform_stroke, preserve,
1707 new_bbox.min()[Geom::X], new_bbox.min()[Geom::Y], new_bbox.max()[Geom::X], new_bbox.max()[Geom::Y]);
1708
1709 // Calculate the scaled geometrical bbox
1710 Geom::Rect new_geom_bbox = Geom::Rect(_geometric_bbox->min() * abs_affine, _geometric_bbox->max() * abs_affine);
1711 // Find the location of the handle on this new geometrical bbox
1712 return normalized_handle_pos * Geom::Scale(new_geom_bbox.dimensions()) + new_geom_bbox.min(); //new position of the geometric handle
1713}
1714
1715Geom::Scale Inkscape::calcScaleFactors(Geom::Point const &initial_point, Geom::Point const &new_point, Geom::Point const &origin, bool const skew)
1716{
1717 // Work out the new scale factors for the bbox
1718
1719 Geom::Point const initial_delta = initial_point - origin;
1720 Geom::Point const new_delta = new_point - origin;
1721 Geom::Point const offset = new_point - initial_point;
1722 Geom::Scale scale(1, 1);
1723
1724 for ( unsigned int i = 0 ; i < 2 ; i++ ) {
1725 if ( fabs(initial_delta[i]) > 1e-6 ) {
1726 if (skew) {
1727 scale[i] = offset[1-i] / initial_delta[i];
1728 } else {
1729 scale[i] = new_delta[i] / initial_delta[i];
1730 }
1731 }
1732 }
1733
1734 return scale;
1735}
1736
1737// Only for scaling/stretching
1739{
1740 Geom::Affine abs_affine = Geom::Translate(-_origin) * Geom::Affine(default_scale) * Geom::Translate(_origin);
1741 Geom::Point new_bbox_min = _stroked_bbox->min() * abs_affine;
1742 Geom::Point new_bbox_max = _stroked_bbox->max() * abs_affine;
1743
1744 bool transform_stroke = false;
1745 bool preserve = false;
1746 gdouble stroke_x = 0;
1747 gdouble stroke_y = 0;
1748
1749 if ( _snap_bbox_type != SPItem::GEOMETRIC_BBOX) {
1751 transform_stroke = prefs->getBool("/options/transform/stroke", true);
1752 preserve = prefs->getBool("/options/preservetransform/value", false);
1753 stroke_x = _stroked_bbox->width() - _geometric_bbox->width();
1754 stroke_y = _stroked_bbox->height() - _geometric_bbox->height();
1755 }
1756
1757 _absolute_affine = get_scale_transform_for_uniform_stroke (*_stroked_bbox, stroke_x, stroke_y, transform_stroke, preserve,
1758 new_bbox_min[Geom::X], new_bbox_min[Geom::Y], new_bbox_max[Geom::X], new_bbox_max[Geom::Y]);
1759
1760 // return the new handle position
1761 return ( _point - _origin ) * default_scale + _origin;
1762}
1763
1764// Only for scaling/stretching
1766{
1767 _relative_affine = Geom::Affine(geom_scale);
1768 _absolute_affine = Geom::Translate(-_origin_for_specpoints) * _relative_affine * Geom::Translate(_origin_for_specpoints);
1769
1771 bool const transform_stroke = prefs->getBool("/options/transform/stroke", true);
1772 if (_geometric_bbox) {
1773 Geom::Rect visual_bbox = get_visual_bbox(_geometric_bbox, _absolute_affine, _strokewidth, transform_stroke);
1774 // return the new handle position
1775 return visual_bbox.min() + visual_bbox.dimensions() * Geom::Scale(_handle_x, _handle_y);
1776 }
1777
1778 // Fall back scenario, in case we don't have a geometric bounding box at hand;
1779 // (Due to some bugs related to bounding boxes having at least one zero dimension; For more details
1780 // see https://bugs.launchpad.net/inkscape/+bug/318726)
1781 g_warning("No geometric bounding box has been calculated; this is a bug that needs fixing!");
1782 return _calcAbsAffineDefault(geom_scale); // this is bogus, but we must return _something_
1783}
1784
1786{
1787 SnapManager const &m = _desktop->getNamedView()->snap_manager;
1788
1789 // If we're not going to snap nodes, then we might just as well get rid of their snappoints right away
1791 _snap_points.clear();
1792 }
1793
1794 // If we're not going to snap bounding boxes, then we might just as well get rid of their snappoints right away
1796 _bbox_points.clear();
1797 }
1798
1799 _all_snap_sources_sorted = _snap_points;
1800 _all_snap_sources_sorted.insert(_all_snap_sources_sorted.end(), _bbox_points.begin(), _bbox_points.end());
1801
1802 // Calculate and store the distance to the reference point for each snap candidate point
1803 for(auto & i : _all_snap_sources_sorted) {
1804 i.setDistance(Geom::L2(i.getPoint() - p));
1805 }
1806
1807 // Sort them ascending, using the distance calculated above as the single criteria
1808 std::sort(_all_snap_sources_sorted.begin(), _all_snap_sources_sorted.end());
1809
1810 // Now get the closest snap source
1811 _snap_points.clear();
1812 _bbox_points.clear();
1813 if (!_all_snap_sources_sorted.empty()) {
1814 _all_snap_sources_iter = _all_snap_sources_sorted.begin();
1815 if (_all_snap_sources_sorted.front().getSourceType() & SNAPSOURCE_BBOX_CATEGORY) {
1816 _bbox_points.push_back(_all_snap_sources_sorted.front());
1817 } else {
1818 _snap_points.push_back(_all_snap_sources_sorted.front());
1819 }
1820 }
1821
1822}
1823// TODO: This code is duplicated in transform-handle-set.cpp; fix this!
1825{
1827 if (prefs->getBool("/options/snapclosestonly/value", false)) {
1828 if (!_all_snap_sources_sorted.empty()) {
1829 if (reverse) { // Shift-tab will find a closer point
1830 if (_all_snap_sources_iter == _all_snap_sources_sorted.begin()) {
1831 _all_snap_sources_iter = _all_snap_sources_sorted.end();
1832 }
1833 --_all_snap_sources_iter;
1834 } else { // Tab will find a point further away
1835 ++_all_snap_sources_iter;
1836 if (_all_snap_sources_iter == _all_snap_sources_sorted.end()) {
1837 _all_snap_sources_iter = _all_snap_sources_sorted.begin();
1838 }
1839 }
1840
1841 _snap_points.clear();
1842 _bbox_points.clear();
1843
1844 if ((*_all_snap_sources_iter).getSourceType() & SNAPSOURCE_BBOX_CATEGORY) {
1845 _bbox_points.push_back(*_all_snap_sources_iter);
1846 } else {
1847 _snap_points.push_back(*_all_snap_sources_iter);
1848 }
1849
1850 // Show the updated snap source now; otherwise it won't be shown until the selection is being moved again
1851 SnapManager &m = _desktop->getNamedView()->snap_manager;
1852 m.setup(_desktop);
1853 m.displaySnapsource(*_all_snap_sources_iter);
1854 m.unSetup();
1855 }
1856 }
1857}
1858
1859/*
1860 Local Variables:
1861 mode:c++
1862 c-file-style:"stroustrup"
1863 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1864 indent-tabs-mode:nil
1865 fill-column:99
1866 End:
1867*/
1868// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double scale
Definition aa.cpp:228
Point origin
Definition aa.cpp:227
Enums for CanvasItems.
3x3 matrix representing an affine transformation.
Definition affine.h:70
void setIdentity()
Sets this matrix to be the Identity Affine.
Definition affine.cpp:96
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
CPoint dimensions() const
Get rectangle's width and height as a point.
CPoint max() const
Get the corner of the rectangle with largest coordinate values.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
Rotation around the origin.
Definition transforms.h:187
Rotate inverse() const
Definition transforms.h:209
Scaling from the origin.
Definition transforms.h:150
Translation by a vector.
Definition transforms.h:115
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
A class to represent ways functionality is driven by shift modifiers.
Definition modifiers.h:101
bool active(int button_state) const
Test if this modifier is currently active.
std::string get_label() const
Definition modifiers.h:171
static Modifier * get(Type index)
A function to turn an enum index into a modifier object.
Definition modifiers.h:213
SPItemRange items()
Returns a range of selected SPItems.
Definition object-set.h:255
Geom::OptRect bounds(SPItem::BBoxType type) const
Returns the bounding rectangle of the selection.
void applyAffine(Geom::Affine const &affine, bool set_i2d=true, bool compensate=true, bool adjust_transf_center=true)
Apply matrix to the selection.
Geom::OptRect strokedBounds() const
void clear()
Unselects all selected objects.
Geom::OptRect geometricBounds() const
bool isEmpty()
Returns true if no items are selected.
Data type representing a typeless value of a preference.
bool getBool(bool def=false) const
Interpret the preference as a Boolean value.
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
void addObserver(Observer &)
Register a preference observer.
Geom::Scale getScaleSnapped()
SnappedPoint best_snapped_point
Geom::Point getTranslationSnapped()
void notify(Preferences::Entry const &val) override
Notification about a preference change.
Definition seltrans.cpp:99
void moveTo(Geom::Point const &xy, unsigned int state)
SPDesktop * _desktop
Definition seltrans.h:133
void align(guint state, SPSelTransHandle const &handle)
std::vector< Inkscape::SnapCandidatePoint > _all_snap_sources_sorted
Definition seltrans.h:142
void handleGrab(SPKnot *knot, unsigned int state, SPSelTransHandle const &handle)
Definition seltrans.cpp:905
SelTrans(SPDesktop *desktop)
Definition seltrans.cpp:104
void stretch(SPSelTransHandle const &handle, Geom::Point &pt, unsigned int state)
Geom::OptRect _stroked_bbox
Definition seltrans.h:158
void setCenter(Geom::Point const &p)
Definition seltrans.cpp:220
Geom::Point _getGeomHandlePos(Geom::Point const &visual_handle_pos)
BoundingBoxPrefsObserver _bounding_box_prefs_observer
Definition seltrans.h:195
Geom::Affine _current_relative_affine
Definition seltrans.h:162
bool _center_is_set
we've already set _center, no need to reread it from items
Definition seltrans.h:181
void rotate(Geom::Point &pt, unsigned int state)
Geom::Point _calcAbsAffineDefault(Geom::Scale const default_scale)
int rotateRequest(Geom::Point &pt, unsigned int state)
Inkscape::Selection * _selection
Definition seltrans.h:146
int handleRequest(SPKnot *knot, Geom::Point *position, unsigned int state, SPSelTransHandle const &handle)
Definition seltrans.cpp:962
CanvasItemPtr< CanvasItemCtrl > _grip
Definition seltrans.h:185
int skewRequest(SPSelTransHandle const &handle, Geom::Point &pt, unsigned int state)
sigc::connection _sel_modified_connection
Definition seltrans.h:194
void _boundingBoxPrefsChanged(int prefs_bbox)
void getNextClosestPoint(bool reverse)
Geom::OptRect _bbox
Definition seltrans.h:157
sigc::connection _sel_changed_connection
Definition seltrans.h:193
void _showHandles(SPSelTransType type)
Definition seltrans.cpp:741
SPItem::BBoxType _snap_bbox_type
Definition seltrans.h:155
CanvasItemPtr< CanvasItemCtrl > _norm
Definition seltrans.h:184
void skew(SPSelTransHandle const &handle, Geom::Point &pt, unsigned int state)
void _selChanged(Inkscape::Selection *selection)
Definition seltrans.cpp:998
std::vector< Inkscape::SnapCandidatePoint >::iterator _all_snap_sources_iter
Definition seltrans.h:143
void handleNewEvent(SPKnot *knot, Geom::Point *position, unsigned int state, SPSelTransHandle const &handle)
Definition seltrans.cpp:925
void _updateVolatileState()
Definition seltrans.cpp:719
void _keepClosestPointOnly(Geom::Point const &p)
void handleClick(SPKnot *knot, unsigned int state, SPSelTransHandle const &handle)
Definition seltrans.cpp:867
Inkscape::SelCue _selcue
Definition seltrans.h:144
void grab(Geom::Point const &p, double x, double y, bool show_handles, bool translating)
Definition seltrans.cpp:235
void scale(Geom::Point &pt, unsigned int state)
Geom::Point _calcAbsAffineGeom(Geom::Scale const geom_scale)
void stamp(bool clone=false)
Definition seltrans.cpp:525
int centerRequest(Geom::Point &pt, unsigned int state)
int stretchRequest(SPSelTransHandle const &handle, Geom::Point &pt, unsigned int state)
Inkscape::MessageContext _message_context
Definition seltrans.h:192
std::array< CanvasItemPtr< CanvasItemCurve >, 4 > _l
Definition seltrans.h:186
int request(SPSelTransHandle const &handle, Geom::Point &pt, unsigned int state)
void _selModified(Inkscape::Selection *selection, unsigned int flags)
void transform(Geom::Affine const &rel_affine, Geom::Point const &norm)
Definition seltrans.cpp:365
int scaleRequest(Geom::Point &pt, unsigned int state)
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
std::vector< Inkscape::SnapCandidatePoint > getSnapPoints(SnapPreferences const *snapprefs) const
Compute the list of points in the selection that are to be considered for snapping from.
bool includes(XML::Node *repr, bool anyAncestor=false)
Returns true if the given item is selected.
Definition selection.h:140
sigc::connection connectChanged(sigc::slot< void(Selection *)> slot)
Connects a slot to be notified of selection changes.
Definition selection.h:179
sigc::connection connectModified(sigc::slot< void(Selection *, unsigned)> slot)
Connects a slot to be notified of selected object modifications.
Definition selection.h:232
Class to store data for points which are snap candidates, either as a source or as a target.
bool isTargetSnappable(Inkscape::SnapTargetType const target) const
Class describing the result of an attempt to snap.
Geom::Point getPoint() const
bool isOtherSnapBetter(SnappedPoint const &other_one, bool weighted) const
Glib::ustring string(Unit const *u) const
Return a printable string of the value in the specified unit.
Definition units.cpp:515
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * parent()=0
Get the parent of this node.
virtual Node * prev()=0
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
virtual Node * duplicate(Document *doc) const =0
Create a duplicate of this node.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
virtual Document * document()=0
Get the node's associated document.
To do: update description of desktop.
Definition desktop.h:149
Inkscape::CanvasItemGroup * getCanvasControls() const
Definition desktop.h:196
Inkscape::Selection * getSelection() const
Definition desktop.h:188
Inkscape::UI::Tools::ToolBase * getTool() const
Definition desktop.h:187
Base class for visual SVG elements.
Definition sp-item.h:109
void set_i2d_affine(Geom::Affine const &transform)
Definition sp-item.cpp:1827
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1821
Geom::Point getCenter(bool ensure_uptodate=true) const
Definition sp-item.cpp:377
Geom::Affine transform
Definition sp-item.h:138
@ VISUAL_BBOX
Definition sp-item.h:118
@ GEOMETRIC_BBOX
Definition sp-item.h:116
bool isCenterSet() const
Definition sp-item.cpp:372
void doWriteTransform(Geom::Affine const &transform, Geom::Affine const *adv=nullptr, bool compensate=true)
Set a new transform on an object.
Definition sp-item.cpp:1658
void setCenter(Geom::Point const &object_centre)
Sets the transform_center_x and transform_center_y properties to retain the rotation center.
Definition sp-item.cpp:331
Desktop-bound visual control object.
Definition knot.h:51
void setPosition(Geom::Point const &p, unsigned int state)
Move knot to new position and emits "moved" signal.
Definition knot.cpp:342
bool is_grabbed() const
Definition knot.h:171
static void unref(SPKnot *knot)
Definition knot.cpp:47
bool is_selected() const
Definition knot.h:168
Geom::Point position() const
Returns position of knot.
Definition knot.h:160
SPDesktop * desktop
Desktop we are on.
Definition knot.h:59
void selectKnot(bool select)
Select knot.
Definition knot.cpp:103
SnapManager snap_manager
SPObject * parent
Definition sp-object.h:189
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
Class to coordinate snapping operations.
Definition snap.h:80
void displaySnapsource(Inkscape::SnapCandidatePoint const &p) const
Mark the location of the snap source (not the snap target!) on the canvas by drawing a symbol.
Definition snap.cpp:781
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
Geom::Point multipleOfGridPitch(Geom::Point const &t, Geom::Point const &origin)
Snap to the closest multiple of a grid pitch.
Definition snap.cpp:178
void freeSnapReturnByRef(Geom::Point &p, Inkscape::SnapSourceType const source_type, Geom::OptRect const &bbox_to_snap=Geom::OptRect()) const
Try to snap a point to grids, guides or objects.
Definition snap.cpp:135
SPNamedView const * getNamedView() const
Definition snap.h:371
void setRotationCenterSource(const std::vector< SPItem * > &items)
Definition snap.h:156
void snapTransformed(std::vector< Inkscape::SnapCandidatePoint > const &points, Geom::Point const &pointer, Inkscape::PureTransform &transform)
Method for snapping sets of points while they are being transformed.
Definition snap.cpp:465
Inkscape::SnappedPoint multipleConstrainedSnaps(Inkscape::SnapCandidatePoint const &p, std::vector< Inkscape::Snapper::SnapConstraint > const &constraints, bool dont_snap=false, Geom::OptRect const &bbox_to_snap=Geom::OptRect()) const
Definition snap.cpp:306
void unSetup()
Definition snap.h:147
Inkscape::SnapPreferences & snapprefs
Definition snap.h:342
bool someSnapperMightSnap(bool immediately=true) const
Return true if any snapping might occur, whether its to grids, guides or objects.
Definition snap.cpp:100
double c[8][4]
gdouble stroke_average_width(const std::vector< SPItem * > &objects)
Determine average stroke width, simple method.
Editable view implementation.
static char const *const parent
Definition dir-util.cpp:70
TODO: insert short description here.
unsigned int guint32
@ SP_ANCHOR_W
Definition enums.h:33
@ SP_ANCHOR_E
Definition enums.h:29
@ SP_ANCHOR_N
Definition enums.h:35
@ SP_ANCHOR_S
Definition enums.h:31
Dim2
2D axis enumeration (X or Y).
Definition coord.h:48
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
auto floor(Geom::Rect const &rect)
Definition geom.h:130
Macro for icon names used in Inkscape.
SPItem * item
Declarations for SPKnot: Desktop-bound visual control object.
double offset
Raw stack of active status messages.
double mod360symm(double const x)
Returns x wrapped around to between -180 and less than 180, or 0 if x isn't finite.
Definition mod360.cpp:36
TODO: insert short description here.
Affine identity()
Create an identity matrix.
Definition affine.h:210
SBasis L2(D2< SBasis > const &a, unsigned k)
Definition d2-sbasis.cpp:42
T dot(D2< T > const &a, D2< T > const &b)
Definition d2.h:355
D2< T > rot90(D2< T > const &a)
Definition d2.h:397
static R & release(R &r)
Decrements the reference count of a anchored object.
Type
This anonymous enum is used to provide a list of the Shifts.
Definition modifiers.h:56
unsigned get_latin_keyval(GtkEventControllerKey const *const controller, unsigned const keyval, unsigned const keycode, GdkModifierType const state, unsigned *consumed_modifiers)
Return the keyval corresponding to the event controller key in Latin group.
@ SNAPSOURCE_ROTATION_CENTER
Definition snap-enums.h:52
@ SNAPSOURCE_BBOX_CATEGORY
Definition snap-enums.h:23
Geom::Scale calcScaleFactors(Geom::Point const &initial_point, Geom::Point const &new_point, Geom::Point const &origin, bool const skew=false)
@ SNAPTARGET_BBOX_EDGE_MIDPOINT
Definition snap-enums.h:78
@ SNAPTARGET_OTHERS_CATEGORY
Definition snap-enums.h:114
@ SNAPTARGET_BBOX_MIDPOINT
Definition snap-enums.h:79
@ SNAPTARGET_ALIGNMENT_CATEGORY
Definition snap-enums.h:125
@ SNAPTARGET_DISTRIBUTION_CATEGORY
Definition snap-enums.h:139
@ SNAPTARGET_NODE_CATEGORY
Definition snap-enums.h:81
@ SNAPTARGET_BBOX_CORNER
Definition snap-enums.h:76
@ SNAPTARGET_BBOX_CATEGORY
Definition snap-enums.h:73
@ CANVAS_ITEM_CTRL_TYPE_CENTER
@ CANVAS_ITEM_CTRL_TYPE_ADJ_ROTATE
@ CANVAS_ITEM_CTRL_TYPE_ADJ_HANDLE
@ CANVAS_ITEM_CTRL_TYPE_MARKER
@ CANVAS_ITEM_CTRL_TYPE_ADJ_SALIGN
@ CANVAS_ITEM_CTRL_TYPE_ADJ_CALIGN
@ CANVAS_ITEM_CTRL_TYPE_ADJ_SKEW
@ CANVAS_ITEM_CTRL_TYPE_ADJ_MALIGN
@ CANVAS_ITEM_CTRL_TYPE_ADJ_CENTER
@ NORMAL_MESSAGE
Definition message.h:26
@ IMMEDIATE_MESSAGE
Definition message.h:27
@ WARNING_MESSAGE
Definition message.h:28
void getBBoxPoints(Geom::OptRect const bbox, std::vector< SnapCandidatePoint > *points, bool const isTarget, bool const corners, bool const edges, bool const midpoint)
Default version of the getBBoxPoints with default corner source types.
SiblingState
SiblingState enums are used to associate the current state while grabbing objects.
Definition object-set.h:45
@ SIBLING_TEXT_SHAPE_INSIDE
GList * items
SPSelTransHandle const hands[]
const int ALIGN_OFFSET
int const NUMHANDS
const int ALIGN_SHIFT_OFFSET
SPSelTransType
@ HANDLE_ROTATE
@ HANDLE_SCALE
@ HANDLE_CORNER_ALIGN
@ HANDLE_STRETCH
@ HANDLE_SIDE_ALIGN
@ HANDLE_CENTER
@ HANDLE_CENTER_ALIGN
@ HANDLE_SKEW
const std::vector< Glib::ustring > AlignArguments
static bool sp_sel_trans_handle_event(SPKnot *knot, Inkscape::CanvasEvent const &event, SPSelTransHandle const *)
Definition seltrans.cpp:68
static gboolean sp_sel_trans_handle_request(SPKnot *knot, Geom::Point *p, guint state, SPSelTransHandle const *data)
Definition seltrans.cpp:853
static void sp_sel_trans_handle_new_event(SPKnot *knot, Geom::Point const &position, guint32 state, SPSelTransHandle const *data)
static void sp_sel_trans_handle_ungrab(SPKnot *knot, guint state, SPSelTransHandle const *data)
Definition seltrans.cpp:839
static void sp_sel_trans_handle_grab(SPKnot *knot, guint state, SPSelTransHandle const *data)
Definition seltrans.cpp:832
static double sign(double const x)
Returns -1 or 1 according to the sign of x.
static void sp_sel_trans_handle_click(SPKnot *knot, guint state, SPSelTransHandle const *data)
Definition seltrans.cpp:860
Provides a class that shows a temporary indicator on the canvas of where the snap was,...
Geom::Affine get_scale_transform_for_uniform_stroke(Geom::Rect const &bbox_visual, gdouble stroke_x, gdouble stroke_y, bool transform_stroke, bool preserve, gdouble x0, gdouble y0, gdouble x1, gdouble y1)
Calculate the affine transformation required to transform one visual bounding box into another,...
Geom::Rect get_visual_bbox(Geom::OptRect const &initial_geom_bbox, Geom::Affine const &abs_affine, gdouble const initial_strokewidth, bool const transform_stroke)
TODO: insert short description here.
void sp_lpe_item_update_patheffect(SPLPEItem *lpeitem, bool wholetree, bool write, bool with_satellites)
Calls any registered handlers for the update_patheffect action.
void sp_lpe_item_enable_path_effects(SPLPEItem *lpeitem, bool enable)
bool sp_object_compare_position_bool(SPObject const *first, SPObject const *second)
SPObject * sp_object_unref(SPObject *object, SPObject *owner)
Decrease reference count of object, with possible debugging and finalization.
SPObject * sp_object_ref(SPObject *object, SPObject *owner)
Increase reference count of object, with possible debugging.
SPRoot: SVG <svg> implementation.
static const Point data[]
Abstract base class for events.
A key has been pressed.
virtual Node * createElement(char const *name)=0
unsigned int control
SPAnchorType anchor
SPSelTransType type
int index
SPDesktop * desktop
Affine transformation classes.