Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
mesh-tool.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Mesh drawing and editing tool
4 *
5 * Authors:
6 * bulia byak <buliabyak@users.sf.net>
7 * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
8 * Abhishek Sharma
9 * Tavmjong Bah <tavmjong@free.fr>
10 *
11 * Copyright (C) 2012 Tavmjong Bah
12 * Copyright (C) 2007 Johan Engelen
13 * Copyright (C) 2005 Authors
14 *
15 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16 */
17
18//#define DEBUG_MESH
19
20#include "mesh-tool.h"
21
22// Libraries
23#include <gdk/gdkkeysyms.h>
24#include <glibmm/i18n.h>
25
26// General
27#include "desktop.h"
28#include "document-undo.h"
29#include "document.h"
30#include "gradient-drag.h"
31#include "gradient-chemistry.h"
32#include "message-context.h"
33#include "message-stack.h"
34#include "rubberband.h"
35#include "selection.h"
36#include "snap.h"
37
39#include "display/curve.h"
40
41#include "object/sp-defs.h"
43#include "object/sp-namedview.h"
44#include "object/sp-text.h"
45#include "style.h"
46
47#include "ui/icon-names.h"
49
51
52namespace Inkscape {
53namespace UI {
54namespace Tools {
55
56// TODO: The gradient tool class looks like a 1:1 copy.
57
59 : ToolBase(desktop, "/tools/mesh", "mesh.svg")
60// TODO: Why are these connections stored as pointers?
61 , selcon(nullptr)
62 , cursor_addnode(false)
63 , show_handles(true)
64 , edit_fill(true)
65 , edit_stroke(true)
66{
67 // TODO: This value is overwritten in the root handler
68 this->tolerance = 6;
69
71 if (prefs->getBool("/tools/mesh/selcue", true)) {
72 this->enableSelectionCue();
73 }
74
75 this->enableGrDrag();
77
78 this->selcon = new sigc::connection(selection->connectChanged(
79 sigc::mem_fun(*this, &MeshTool::selection_changed)
80 ));
81
82 sp_event_context_read(this, "show_handles");
83 sp_event_context_read(this, "edit_fill");
84 sp_event_context_read(this, "edit_stroke");
85
86 this->selection_changed(selection);
87}
88
90 this->enableGrDrag(false);
91
92 this->selcon->disconnect();
93 delete this->selcon;
94}
95
96// This must match GrPointType enum sp-gradient.h
97// We should move this to a shared header (can't simply move to gradient.h since that would require
98// including <glibmm/i18n.h> which messes up "N_" in extensions... argh!).
99const gchar *ms_handle_descr [] = {
100 N_("Linear gradient <b>start</b>"), //POINT_LG_BEGIN
101 N_("Linear gradient <b>end</b>"),
102 N_("Linear gradient <b>mid stop</b>"),
103 N_("Radial gradient <b>center</b>"),
104 N_("Radial gradient <b>radius</b>"),
105 N_("Radial gradient <b>radius</b>"),
106 N_("Radial gradient <b>focus</b>"), // POINT_RG_FOCUS
107 N_("Radial gradient <b>mid stop</b>"),
108 N_("Radial gradient <b>mid stop</b>"),
109 N_("Mesh gradient <b>corner</b>"),
110 N_("Mesh gradient <b>handle</b>"),
111 N_("Mesh gradient <b>tensor</b>")
112};
113
116
117 if (selection == nullptr) {
118 return;
119 }
120
121 guint n_obj = (guint) boost::distance(selection->items());
122
123 if (!_grdrag->isNonEmpty() || selection->isEmpty()) {
124 return;
125 }
126
127 guint n_tot = _grdrag->numDraggers();
128 guint n_sel = _grdrag->numSelected();
129
130 //The use of ngettext in the following code is intentional even if the English singular form would never be used
131 if (n_sel == 1) {
133 gchar * message = g_strconcat(
134 //TRANSLATORS: %s will be substituted with the point name (see previous messages); This is part of a compound message
135 _("%s selected"),
136 //TRANSLATORS: Mind the space in front. This is part of a compound message
137 ngettext(" out of %d mesh handle"," out of %d mesh handles",n_tot),
138 ngettext(" on %d selected object"," on %d selected objects",n_obj),nullptr);
139 this->message_context->setF(Inkscape::NORMAL_MESSAGE, message,
141 } else {
142 gchar * message =
143 g_strconcat(
144 //TRANSLATORS: This is a part of a compound message (out of two more indicating: grandint handle count & object count)
145 ngettext("One handle merging %d stop (drag with <b>Shift</b> to separate) selected",
146 "One handle merging %d stops (drag with <b>Shift</b> to separate) selected",
148 ngettext(" out of %d mesh handle"," out of %d mesh handles",n_tot),
149 ngettext(" on %d selected object"," on %d selected objects",n_obj),nullptr);
151 }
152 } else if (n_sel > 1) {
153 //TRANSLATORS: The plural refers to number of selected mesh handles. This is part of a compound message (part two indicates selected object count)
154 gchar * message =
155 g_strconcat(ngettext("<b>%d</b> mesh handle selected out of %d","<b>%d</b> mesh handles selected out of %d",n_sel),
156 //TRANSLATORS: Mind the space in front. (Refers to gradient handles selected). This is part of a compound message
157 ngettext(" on %d selected object"," on %d selected objects",n_obj),nullptr);
158 this->message_context->setF(Inkscape::NORMAL_MESSAGE, message, n_sel, n_tot, n_obj);
159 } else if (n_sel == 0) {
161 //TRANSLATORS: The plural refers to number of selected objects
162 ngettext("<b>No</b> mesh handles selected out of %d on %d selected object",
163 "<b>No</b> mesh handles selected out of %d on %d selected objects",n_obj), n_tot, n_obj);
164 }
165
166 // FIXME
167 // We need to update mesh gradient handles.
168 // Get gradient this drag belongs too..
169}
170
172 Glib::ustring entry_name = value.getEntryName();
173 if (entry_name == "show_handles") {
174 this->show_handles = value.getBool(true);
175 } else if (entry_name == "edit_fill") {
176 this->edit_fill = value.getBool(true);
177 } else if (entry_name == "edit_stroke") {
178 this->edit_stroke = value.getBool(true);
179 } else {
180 ToolBase::set(value);
181 }
182}
183
185{
186 g_assert(_grdrag);
189}
190
192{
193 g_assert(_grdrag);
196}
197
202std::vector<GrDrag::ItemCurve*> MeshTool::over_curve(Geom::Point event_p, bool first)
203{
204 // Translate mouse point into proper coord system: needed later.
205 mousepoint_doc = _desktop->w2d(event_p);
206 std::vector<GrDrag::ItemCurve*> selected;
207
208 for (auto &it : _grdrag->item_curves) {
209 if (it.curve->contains(event_p, tolerance)) {
210 selected.emplace_back(&it);
211 if (first) {
212 break;
213 }
214 }
215 }
216 return selected;
217}
218
223{
224#ifdef DEBUG_MESH
225 std::cout << "split_near_point: entrance: " << mouse_p << std::endl;
226#endif
227
228 // item is the selected item. mouse_p the location in doc coordinates of where to add the stop
230 DocumentUndo::done(_desktop->getDocument(), _("Split mesh row/column"), INKSCAPE_ICON("mesh-gradient"));
232}
233
238{
239
240#ifdef DEBUG_MESH
241 std::cout << "sp_mesh_corner_operation: entrance: " << operation << std::endl;
242#endif
243
244 SPDocument *doc = nullptr;
245
246 std::map<SPMeshGradient*, std::vector<guint> > points;
247 std::map<SPMeshGradient*, SPItem*> items;
248 std::map<SPMeshGradient*, Inkscape::PaintTarget> fill_or_stroke;
249
250 // Get list of selected draggers for each mesh.
251 // For all selected draggers (a dragger may include draggerables from different meshes).
252 for (auto dragger : _grdrag->selected) {
253 // For all draggables of dragger (a draggable corresponds to a unique mesh).
254 for (auto d : dragger->draggables) {
255 // Only mesh corners
256 if( d->point_type != POINT_MG_CORNER ) continue;
257
258 // Find the gradient
259 auto gradient = cast<SPMeshGradient>( getGradient (d->item, d->fill_or_stroke) );
260
261 // Collect points together for same gradient
262 points[gradient].push_back( d->point_i );
263 items[gradient] = d->item;
264 fill_or_stroke[gradient] = d->fill_or_stroke ? Inkscape::FOR_FILL: Inkscape::FOR_STROKE;
265 }
266 }
267
268 // Loop over meshes.
269 for( std::map<SPMeshGradient*, std::vector<guint> >::const_iterator iter = points.begin(); iter != points.end(); ++iter) {
270 SPMeshGradient *mg = iter->first;
271 if( iter->second.size() > 0 ) {
272 guint noperation = 0;
273 switch (operation) {
274
276 // std::cout << "SIDE_TOGGLE" << std::endl;
277 noperation += mg->array.side_toggle( iter->second );
278 break;
279
281 // std::cout << "SIDE_ARC" << std::endl;
282 noperation += mg->array.side_arc( iter->second );
283 break;
284
286 // std::cout << "TENSOR_TOGGLE" << std::endl;
287 noperation += mg->array.tensor_toggle( iter->second );
288 break;
289
291 // std::cout << "COLOR_SMOOTH" << std::endl;
292 noperation += mg->array.color_smooth( iter->second );
293 break;
294
296 // std::cout << "COLOR_PICK" << std::endl;
297 noperation += mg->array.color_pick( iter->second, items[iter->first] );
298 break;
299
300 case MG_CORNER_INSERT:
301 // std::cout << "INSERT" << std::endl;
302 noperation += mg->array.insert( iter->second );
303 break;
304
305 default:
306 std::cerr << "sp_mesh_corner_operation: unknown operation" << std::endl;
307 }
308
309 if( noperation > 0 ) {
310 mg->array.write( mg );
311 mg->requestModified(SP_OBJECT_MODIFIED_FLAG);
312 doc = mg->document;
313
314 switch (operation) {
315
317 DocumentUndo::done(doc, _("Toggled mesh path type."), INKSCAPE_ICON("mesh-gradient"));
318 _grdrag->local_change = true; // Don't create new draggers.
319 break;
320
322 DocumentUndo::done(doc, _("Approximated arc for mesh side."), INKSCAPE_ICON("mesh-gradient"));
323 _grdrag->local_change = true; // Don't create new draggers.
324 break;
325
327 DocumentUndo::done(doc, _("Toggled mesh tensors."), INKSCAPE_ICON("mesh-gradient"));
328 _grdrag->local_change = true; // Don't create new draggers.
329 break;
330
332 DocumentUndo::done(doc, _("Smoothed mesh corner color."), INKSCAPE_ICON("mesh-gradient"));
333 _grdrag->local_change = true; // Don't create new draggers.
334 break;
335
337 DocumentUndo::done(doc, _("Picked mesh corner color."), INKSCAPE_ICON("mesh-gradient"));
338 _grdrag->local_change = true; // Don't create new draggers.
339 break;
340
341 case MG_CORNER_INSERT:
342 DocumentUndo::done(doc, _("Inserted new row or column."), INKSCAPE_ICON("mesh-gradient"));
343 break;
344
345 default:
346 std::cerr << "sp_mesh_corner_operation: unknown operation" << std::endl;
347 }
348 }
349 }
350 }
351}
352
353
358{
359
360#ifdef DEBUG_MESH
361 std::cout << "fit_mesh_in_bbox: entrance: Entrance" << std::endl;
362#endif
363
365 if (selection == nullptr) {
366 return;
367 }
368
369 bool changed = false;
370 auto itemlist = selection->items();
371 for (auto i=itemlist.begin(); i!=itemlist.end(); ++i) {
372
373 SPItem *item = *i;
374 SPStyle *style = item->style;
375
376 if (style) {
377
378 if (style->fill.isPaintserver()) {
380 if ( is<SPMeshGradient>(server) ) {
381
382 Geom::OptRect item_bbox = item->geometricBounds();
383 auto gradient = cast<SPMeshGradient>(server);
384 if (gradient->array.fill_box( item_bbox )) {
385 changed = true;
386 }
387 }
388 }
389
390 if (style->stroke.isPaintserver()) {
392 if ( is<SPMeshGradient>(server) ) {
393
394 Geom::OptRect item_bbox = item->visualBounds();
395 auto gradient = cast<SPMeshGradient>(server);
396 if (gradient->array.fill_box( item_bbox )) {
397 changed = true;
398 }
399 }
400 }
401
402 }
403 }
404 if (changed) {
405 DocumentUndo::done(_desktop->getDocument(), _("Fit mesh inside bounding box"), INKSCAPE_ICON("mesh-gradient"));
406 }
407}
408
409// Helper function to determine if an item has a mesh or not.
410static bool has_mesh(SPItem *item)
411{
412 if (!item) {
413 return false;
414 }
415
416 auto fill_or_stroke_pref =
417 static_cast<Inkscape::PaintTarget>(Inkscape::Preferences::get()->getInt("/tools/mesh/newfillorstroke"));
418
419 if (auto style = item->style) {
420 auto server =
421 (fill_or_stroke_pref == Inkscape::FOR_FILL) ? style->getFillPaintServer() : style->getStrokePaintServer();
422 if (is<SPMeshGradient>(server)) {
423 return true;
424 }
425 }
426
427 return false;
428}
429
435{
436 auto selection = _desktop->getSelection();
437 auto prefs = Inkscape::Preferences::get();
438
439 tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
440
441 bool contains_mesh = false;
442 if (!selection->isEmpty()) {
443 contains_mesh = has_mesh(selection->items().front());
444 }
445
446 bool ret = false;
447
448 g_assert(_grdrag);
449
450 inspect_event(event,
451 [&] (ButtonPressEvent const &event) {
452 if (event.num_press == 2 && event.button == 1) {
453
454#ifdef DEBUG_MESH
455 std::cout << "root_handler: GDK_2BUTTON_PRESS" << std::endl;
456#endif
457
458 // Double click:
459 // If over a mesh line, divide mesh row/column
460 // If not over a line and no mesh, create new mesh for top selected object.
461
462 // Are we over a mesh line? (Should replace by CanvasItem event.)
463 auto over_curve = this->over_curve(event.pos);
464
465 if (!over_curve.empty() && contains_mesh) {
466 // We take the first item in selection, because with doubleclick, the first click
467 // always resets selection to the single object under cursor
468 split_near_point(selection->items().front(), mousepoint_doc);
469 } else if (!contains_mesh) {
470 // Create a new gradient with default coordinates.
471
472 // Check if object already has mesh... if it does,
473 // don't create new mesh with click-drag.
474 new_default();
475 }
476
477 ret = true;
478 }
479
480 if (event.num_press == 1 && event.button == 1) {
481
482#ifdef DEBUG_MESH
483 std::cout << "root_handler: GDK_BUTTON_PRESS" << std::endl;
484#endif
485
486 // Button down
487 // If mesh already exists, do rubber band selection.
488 // Else set origin for drag which will create a new gradient.
489
490 // Are we over a mesh curve?
491 auto over_curve = this->over_curve(event.pos, false);
492
493 if (!over_curve.empty() && contains_mesh) {
494 for (auto it : over_curve) {
495 Inkscape::PaintTarget fill_or_stroke = it->is_fill ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE;
496 GrDragger *dragger0 = _grdrag->getDraggerFor(it->item, POINT_MG_CORNER, it->corner0, fill_or_stroke);
497 GrDragger *dragger1 = _grdrag->getDraggerFor(it->item, POINT_MG_CORNER, it->corner1, fill_or_stroke);
498 bool add = (event.modifiers & GDK_SHIFT_MASK);
499 bool toggle = (event.modifiers & GDK_CONTROL_MASK);
500 if ( !add && !toggle ) {
502 }
503 _grdrag->setSelected( dragger0, true, !toggle );
504 _grdrag->setSelected( dragger1, true, !toggle );
505 }
506 ret = true;
507
508 } else {
509 Geom::Point button_w(event.pos);
510
511 // Save drag origin
512 saveDragOrigin(button_w);
513
514 dragging = true;
515
516 Geom::Point button_dt = _desktop->w2d(button_w);
517 // Check if object already has mesh... if it does,
518 // don't create new mesh with click-drag.
519 if (contains_mesh && !(event.modifiers & GDK_CONTROL_MASK)) {
521 }
522
523 // remember clicked item, disregarding groups, honoring Alt; do nothing with Crtl to
524 // enable Ctrl+doubleclick of exactly the selected item(s)
525 if (!(event.modifiers & GDK_CONTROL_MASK)) {
526 item_to_select = sp_event_context_find_item (_desktop, button_w, event.modifiers & GDK_ALT_MASK, TRUE);
527 }
528
529 if (!selection->isEmpty()) {
531 m.setup(_desktop);
533 m.unSetup();
534 }
535
536 origin = button_dt;
537
538 ret = true;
539 }
540 }
541 },
542 [&] (MotionEvent const &event) {
543 // Mouse move
544 if (dragging && (event.modifiers & GDK_BUTTON1_MASK)) {
545 if (!checkDragMoved(event.pos)) {
546 return;
547 }
548
549#ifdef DEBUG_MESH
550 std::cout << "root_handler: GDK_MOTION_NOTIFY: Dragging" << std::endl;
551#endif
552
553 Geom::Point const motion_dt = _desktop->w2d(event.pos);
554
555 if (Inkscape::Rubberband::get(_desktop)->isStarted()) {
556 Inkscape::Rubberband::get(_desktop)->move(motion_dt);
557 this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("<b>Draw around</b> handles to select them"));
558 } else {
559 // Do nothing. For a linear/radial gradient we follow the drag, updating the
560 // gradient as the end node is dragged. For a mesh gradient, the gradient is always
561 // created to fill the object when the drag ends.
562 }
563
564 gobble_motion_events(GDK_BUTTON1_MASK);
565
566 ret = true;
567 } else {
568 // Not dragging
569
570 // Do snapping
571 if (!_grdrag->mouseOver() && !selection->isEmpty()) {
572 auto &m = _desktop->getNamedView()->snap_manager;
573 m.setup(_desktop);
574
575 auto const motion_dt = _desktop->w2d(event.pos);
576 m.preSnap(SnapCandidatePoint(motion_dt, SNAPSOURCE_OTHER_HANDLE));
577 m.unSetup();
578 }
579
580 // Highlight corner node corresponding to side or tensor node
581 if (_grdrag->mouseOver()) {
582 // MESH FIXME: Light up corresponding corner node corresponding to node we are over.
583 // See "pathflash" in ui/tools/node-tool.cpp for ideas.
584 // Use _desktop->add_temporary_canvasitem( SPCanvasItem, milliseconds );
585 }
586
587 // Change cursor shape if over line
588 auto over_curve = this->over_curve(event.pos);
589
590 if (cursor_addnode && over_curve.empty()) {
591 set_cursor("mesh.svg");
592 cursor_addnode = false;
593 } else if (!cursor_addnode && !over_curve.empty() && contains_mesh) {
594 set_cursor("mesh-add.svg");
595 cursor_addnode = true;
596 }
597 }
598 },
599 [&] (ButtonReleaseEvent const &event) {
600 xyp = {};
601 if (event.button == 1) {
602
603#ifdef DEBUG_MESH
604 std::cout << "root_handler: GDK_BUTTON_RELEASE" << std::endl;
605#endif
606
607 // Check if over line
608 auto over_curve = this->over_curve(event.pos);
609
610 if ( (event.modifiers & GDK_CONTROL_MASK) && (event.modifiers & GDK_ALT_MASK ) ) {
611 if (!over_curve.empty() && has_mesh(over_curve[0]->item)) {
612 split_near_point(over_curve[0]->item, mousepoint_doc);
613 ret = true;
614 }
615 } else {
616 dragging = false;
617
618 // Unless clicked with Ctrl (to enable Ctrl+doubleclick).
619 if (event.modifiers & GDK_CONTROL_MASK && !(event.modifiers & GDK_SHIFT_MASK)) {
620 Inkscape::Rubberband::get(_desktop)->stop();
621 ret = true;
622 } else {
623
624 if (!within_tolerance) {
625
626 // Check if object already has mesh... if it does,
627 // don't create new mesh with click-drag.
628 if (!contains_mesh) {
629 new_default();
630 } else {
631
632 // we've been dragging, either create a new gradient
633 // or rubberband-select if we have rubberband
635
636 if (r->isStarted() && !this->within_tolerance) {
637 // this was a rubberband drag
638 if (r->getMode() == Rubberband::Mode::RECT) {
639 Geom::OptRect const b = r->getRectangle();
640 if (!(event.modifiers & GDK_SHIFT_MASK)) {
641 _grdrag->deselectAll();
642 }
643 _grdrag->selectRect(*b);
644 }
645 }
646 }
647
648 } else if (this->item_to_select) {
649 if (!over_curve.empty()) {
650 // Clicked on an existing mesh line, don't change selection. This stops
651 // possible change in selection during a double click with overlapping objects.
652 } else {
653 // No dragging, select clicked item if any.
654 if (event.modifiers & GDK_SHIFT_MASK) {
655 selection->toggle(item_to_select);
656 } else {
657 _grdrag->deselectAll();
658 selection->set(item_to_select);
659 }
660 }
661 } else {
662 if (!over_curve.empty()) {
663 // Clicked on an existing mesh line, don't change selection. This stops
664 // possible change in selection during a double click with overlapping objects.
665 } else {
666 // Click in an empty space; do the same as Esc.
667 if (!_grdrag->selected.empty()) {
668 _grdrag->deselectAll();
669 } else {
670 selection->clear();
671 }
672 }
673 }
674
675 item_to_select = nullptr;
676 ret = true;
677 }
678 }
679 Inkscape::Rubberband::get(_desktop)->stop();
680 }
681 },
682 [&] (KeyPressEvent const &event) {
683
684#ifdef DEBUG_MESH
685 std::cout << "root_handler: GDK_KEY_PRESS" << std::endl;
686#endif
687
688 // FIXME: tip
689 switch (get_latin_keyval (event)) {
690 case GDK_KEY_Alt_L:
691 case GDK_KEY_Alt_R:
692 case GDK_KEY_Control_L:
693 case GDK_KEY_Control_R:
694 case GDK_KEY_Shift_L:
695 case GDK_KEY_Shift_R:
696 case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine)
697 case GDK_KEY_Meta_R:
698 // sp_event_show_modifier_tip (this->defaultMessageContext(), event,
699 // _("FIXME<b>Ctrl</b>: snap mesh angle"),
700 // _("FIXME<b>Shift</b>: draw mesh around the starting point"),
701 // NULL);
702 break;
703
704 case GDK_KEY_A:
705 case GDK_KEY_a:
706 if (mod_ctrl_only(event) && _grdrag->isNonEmpty()) {
707 _grdrag->selectAll();
708 ret = true;
709 }
710 break;
711
712 case GDK_KEY_Escape:
713 if (!_grdrag->selected.empty()) {
714 _grdrag->deselectAll();
715 } else {
716 selection->clear();
717 }
718
719 ret = true;
720 //TODO: make dragging escapable by Esc
721 break;
722
723 // Mesh Operations --------------------------------------------
724
725 case GDK_KEY_Insert:
726 case GDK_KEY_KP_Insert:
727 // with any modifiers:
728 corner_operation(MG_CORNER_INSERT);
729 ret = true;
730 break;
731
732 case GDK_KEY_i:
733 case GDK_KEY_I:
734 if (mod_shift_only(event)) {
735 // Shift+I - insert corners (alternate keybinding for keyboards
736 // that don't have the Insert key)
737 this->corner_operation(MG_CORNER_INSERT);
738 ret = true;
739 }
740 break;
741
742 case GDK_KEY_Delete:
743 case GDK_KEY_KP_Delete:
744 case GDK_KEY_BackSpace:
745 if (!_grdrag->selected.empty()) {
746 ret = true;
747 }
748 break;
749
750 case GDK_KEY_b: // Toggle mesh side between lineto and curveto.
751 case GDK_KEY_B:
752 if (mod_alt(event) && _grdrag->isNonEmpty() && _grdrag->hasSelection()) {
753 corner_operation(MG_CORNER_SIDE_TOGGLE);
754 ret = true;
755 }
756 break;
757
758 case GDK_KEY_c: // Convert mesh side from generic Bezier to Bezier approximating arc,
759 case GDK_KEY_C: // preserving handle direction.
760 if (mod_alt(event) && _grdrag->isNonEmpty() && _grdrag->hasSelection()) {
761 corner_operation(MG_CORNER_SIDE_ARC);
762 ret = true;
763 }
764 break;
765
766 case GDK_KEY_g: // Toggle mesh tensor points on/off
767 case GDK_KEY_G:
768 if (mod_alt(event) && _grdrag->isNonEmpty() && _grdrag->hasSelection()) {
769 corner_operation(MG_CORNER_TENSOR_TOGGLE);
770 ret = true;
771 }
772 break;
773
774 case GDK_KEY_j: // Smooth corner color
775 case GDK_KEY_J:
776 if (mod_alt(event) && _grdrag->isNonEmpty() && _grdrag->hasSelection()) {
777 corner_operation(MG_CORNER_COLOR_SMOOTH);
778 ret = true;
779 }
780 break;
781
782 case GDK_KEY_k: // Pick corner color
783 case GDK_KEY_K:
784 if (mod_alt(event) && _grdrag->isNonEmpty() && _grdrag->hasSelection()) {
785 corner_operation(MG_CORNER_COLOR_PICK);
786 ret = true;
787 }
788 break;
789
790 default:
791 ret = _grdrag->key_press_handler(event);
792 break;
793 }
794 },
795 [&] (KeyReleaseEvent const &event) {
796 switch (get_latin_keyval(event)) {
797 case GDK_KEY_Alt_L:
798 case GDK_KEY_Alt_R:
799 case GDK_KEY_Control_L:
800 case GDK_KEY_Control_R:
801 case GDK_KEY_Shift_L:
802 case GDK_KEY_Shift_R:
803 case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt
804 case GDK_KEY_Meta_R:
805 defaultMessageContext()->clear();
806 break;
807 default:
808 break;
809 }
810 },
811 [&] (CanvasEvent const &event) {}
812 );
813
814 return ret || ToolBase::root_handler(event);
815}
816
817// Creates a new mesh gradient.
818void MeshTool::new_default()
819{
820 Inkscape::Selection *selection = _desktop->getSelection();
821 SPDocument *document = _desktop->getDocument();
822
823 if (!selection->isEmpty()) {
824
826 Inkscape::PaintTarget fill_or_stroke_pref =
827 static_cast<Inkscape::PaintTarget>(prefs->getInt("/tools/mesh/newfillorstroke"));
828 Inkscape::Preferences::Entry _show_handles_value = prefs->getEntry("/tools/mesh/show_handles");
829
830 // Ensure that the preference show_handles is enable by default
831 if (!_show_handles_value.isSet()) {
832 prefs->setBool("/tools/mesh/show_handles", true);
833 }
834 // Ensure mesh is immediately editable.
835 // Editing both fill and stroke at same time doesn't work well so avoid.
836 if (fill_or_stroke_pref == Inkscape::FOR_FILL) {
837 prefs->setBool("/tools/mesh/edit_fill", true );
838 prefs->setBool("/tools/mesh/edit_stroke", false);
839 } else {
840 prefs->setBool("/tools/mesh/edit_fill", false);
841 prefs->setBool("/tools/mesh/edit_stroke", true );
842 }
843
844// HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs
846 sp_repr_css_set_property(css, "fill-opacity", "1.0");
847
848 Inkscape::XML::Document *xml_doc = document->getReprDoc();
849 SPDefs *defs = document->getDefs();
850
851 auto items= selection->items();
852 for(auto i=items.begin();i!=items.end();++i){
853
854 //FIXME: see above
855 sp_repr_css_change_recursive((*i)->getRepr(), css, "style");
856
857 // Create mesh element
858 Inkscape::XML::Node *repr = xml_doc->createElement("svg:meshgradient");
859
860 // privates are garbage-collectable
861 repr->setAttribute("inkscape:collect", "always");
862
863 // Attach to document
864 defs->getRepr()->appendChild(repr);
866
867 // Get corresponding object
868 SPMeshGradient *mg = static_cast<SPMeshGradient *>(document->getObjectByRepr(repr));
869 mg->array.create(mg, *i, (fill_or_stroke_pref == Inkscape::FOR_FILL) ?
870 (*i)->geometricBounds() : (*i)->visualBounds());
871
872 bool isText = is<SPText>(*i);
874 ((fill_or_stroke_pref == Inkscape::FOR_FILL) ? "fill":"stroke"),
875 mg, isText);
876
877 (*i)->requestModified(SP_OBJECT_MODIFIED_FLAG|SP_OBJECT_STYLE_MODIFIED_FLAG);
878 }
879
880 if (css) {
882 css = nullptr;
883 }
884
885 DocumentUndo::done(_desktop->getDocument(), _("Create mesh"), INKSCAPE_ICON("mesh-gradient"));
886
887 // status text; we do not track coords because this branch is run once, not all the time
888 // during drag
889 int n_objects = (int) boost::distance(selection->items());
890 message_context->setF(Inkscape::NORMAL_MESSAGE,
891 ngettext("<b>Gradient</b> for %d object; with <b>Ctrl</b> to snap angle",
892 "<b>Gradient</b> for %d objects; with <b>Ctrl</b> to snap angle", n_objects),
893 n_objects);
894 } else {
895 _desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>objects</b> on which to create gradient."));
896 }
897}
898
899}
900}
901}
902
903/*
904 Local Variables:
905 mode:c++
906 c-file-style:"stroustrup"
907 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
908 indent-tabs-mode:nil
909 fill-column:99
910 End:
911*/
912// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Two-dimensional point that doubles as a vector.
Definition point.h:66
void updateDraggers()
Regenerates the draggers list from the current selection; is called when selection is changed or modi...
guint singleSelectedDraggerSingleDraggableType()
GrDragger * select_prev()
Select the knot previous from the last selected one and deselect all other selected.
bool isNonEmpty()
void deselectAll()
Deselect all stops/draggers (public; emits signal).
bool local_change
guint singleSelectedDraggerNumDraggables()
GrDragger * select_next()
Select the knot next to the last selected one and deselect all other selected.
SPStop * addStopNearPoint(SPItem *item, Geom::Point mouse_p, double tolerance)
guint numSelected()
std::set< GrDragger * > selected
std::vector< ItemCurve > item_curves
void setSelected(GrDragger *dragger, bool add_to_selection=false, bool override=true)
Select a dragger.
guint numDraggers()
GrDragger * getDraggerFor(GrDraggable *d)
Select the dragger which has the given draggable.
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
SPItemRange items()
Returns a range of selected SPItems.
Definition object-set.h:255
bool isEmpty()
Returns true if no items are selected.
Data type representing a typeless value of a preference.
bool isSet() const
Check whether the received entry is set.
Glib::ustring getEntryName() const
Get the last component of the preference's path.
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.
Entry const getEntry(Glib::ustring const &pref_path)
Retrieve a preference entry without specifying its type.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
Rubberbanding selector.
Definition rubberband.h:36
void start(SPDesktop *desktop, Geom::Point const &p, bool tolerance=false)
static Rubberband * get(SPDesktop *desktop)
void move(Geom::Point const &p)
Geom::OptRect getRectangle() const
Rubberband::Mode getMode() const
Definition rubberband.h:56
bool isStarted() const
Definition rubberband.h:53
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
sigc::connection connectChanged(sigc::slot< void(Selection *)> slot)
Connects a slot to be notified of selection changes.
Definition selection.h:179
void corner_operation(MeshCornerOperation operation)
Wrapper for various mesh operations that require a list of selected corner nodes.
MeshTool(SPDesktop *desktop)
Definition mesh-tool.cpp:58
void set(Preferences::Entry const &val) override
Called by our pref_observer if a preference has been changed.
std::vector< GrDrag::ItemCurve * > over_curve(Geom::Point event_p, bool first=true)
Returns vector of control curves mouse is over.
void split_near_point(SPItem *item, Geom::Point mouse_p)
Split row/column near the mouse point.
sigc::connection * selcon
Definition mesh-tool.h:45
bool root_handler(CanvasEvent const &event) override
Handles all keyboard and mouse input for meshs.
void selection_changed(Inkscape::Selection *sel)
void fit_mesh_in_bbox()
Scale mesh to just fit into bbox of selected items.
Base class for Event processors.
Definition tool-base.h:107
bool dragging
are we dragging?
Definition tool-base.h:146
std::unique_ptr< MessageContext > message_context
Definition tool-base.h:193
SPItem * item_to_select
the item where mouse_press occurred, to be selected if this is a click not drag
Definition tool-base.h:152
virtual bool root_handler(CanvasEvent const &event)
void saveDragOrigin(Geom::Point const &pos)
void enableGrDrag(bool enable=true)
void enableSelectionCue(bool enable=true)
Enables/disables the ToolBase's SelCue.
virtual void set(Preferences::Entry const &val)
Called by our pref_observer if a preference has been changed.
Interface for refcounted XML nodes.
Definition node.h:80
virtual void appendChild(Node *child)=0
Append a node as the last 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
To do: update description of desktop.
Definition desktop.h:149
double current_zoom() const
Definition desktop.h:335
SPDocument * getDocument() const
Definition desktop.h:189
SPNamedView * getNamedView() const
Definition desktop.h:191
Inkscape::Selection * getSelection() const
Definition desktop.h:188
bool scroll_to_point(Geom::Point const &s_dt, double autoscrollspeed=0)
Scroll screen so as to keep point 'p' visible in window.
Definition desktop.cpp:894
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Definition desktop.h:416
Typed SVG document implementation.
Definition document.h:101
SPDefs * getDefs()
Return the main defs object for the document.
Definition document.cpp:246
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:211
SPObject * getObjectByRepr(Inkscape::XML::Node *repr) const
SPMeshNodeArray array
Mesh Gradients.
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::OptRect geometricBounds(Geom::Affine const &transform=Geom::identity()) const
Get item's geometric bounding box in this item's coordinate system.
Definition sp-item.cpp:927
Geom::OptRect visualBounds(Geom::Affine const &transform=Geom::identity(), bool wfilter=true, bool wclip=true, bool wmask=true) const
Get item's visual bounding box in this item's coordinate system.
Definition sp-item.cpp:932
Mesh gradient.
unsigned insert(std::vector< unsigned > const &)
Splits selected rows and/or columns in half (according to the path 't' parameter).
unsigned color_smooth(std::vector< unsigned > const &)
Attempts to smooth color transitions across corners.
unsigned side_arc(std::vector< unsigned > const &)
Converts generic Beziers to Beziers approximating elliptical arcs, preserving handle direction.
unsigned side_toggle(std::vector< unsigned > const &)
Toggle sides between lineto and curve to if both corners selected.
void create(SPMeshGradient *mg, SPItem *item, Geom::OptRect bbox)
Create a default mesh.
unsigned tensor_toggle(std::vector< unsigned > const &)
Toggle sides between lineto and curve to if both corners selected.
void write(SPMeshGradient *mg)
Write repr using our array.
unsigned color_pick(std::vector< unsigned > const &, SPItem *)
Pick color from background for selected corners.
SnapManager snap_manager
void requestModified(unsigned int flags)
Requests that a modification notification signal be emitted later (e.g.
SPDocument * document
Definition sp-object.h:188
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
An SVG style object.
Definition style.h:45
SPPaintServer * getFillPaintServer()
Definition style.h:339
T< SPAttr::FILL, SPIPaint > fill
fill
Definition style.h:240
T< SPAttr::STROKE, SPIPaint > stroke
stroke
Definition style.h:247
SPPaintServer * getStrokePaintServer()
Definition style.h:343
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 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
void unSetup()
Definition snap.h:147
std::shared_ptr< Css const > css
Editable view implementation.
TODO: insert short description here.
SPGradient * getGradient(SPItem *item, Inkscape::PaintTarget fill_or_stroke)
Fetches either the fill or the stroke gradient from the given item.
Macro for icon names used in Inkscape.
SPItem * item
Interface for locally managing a current status message.
Raw stack of active status messages.
static R & release(R &r)
Decrements the reference count of a anchored object.
SPItem * sp_event_context_find_item(SPDesktop *desktop, Geom::Point const &p, bool select_under, bool into_groups)
Returns item at point p in desktop.
void gobble_motion_events(unsigned mask)
Definition tool-base.h:244
const gchar * ms_handle_descr[]
Definition mesh-tool.cpp:99
static bool has_mesh(SPItem *item)
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.
void sp_event_context_read(ToolBase *tool, char const *key)
Calls virtual set() function of ToolBase.
Helper class to stream background task notifications as a series of messages.
bool mod_alt(unsigned modifiers)
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
@ SNAPSOURCE_OTHER_HANDLE
Definition snap-enums.h:56
@ SNAPSOURCE_NODE_HANDLE
Definition snap-enums.h:43
bool mod_ctrl_only(unsigned modifiers)
bool mod_shift_only(unsigned modifiers)
@ NORMAL_MESSAGE
Definition message.h:26
@ WARNING_MESSAGE
Definition message.h:28
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_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
Definition repr-css.cpp:76
void sp_repr_css_change_recursive(Node *repr, SPCSSAttr *css, gchar const *attr)
Definition repr-css.cpp:371
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
@ POINT_MG_CORNER
Definition sp-gradient.h:56
MeshCornerOperation
@ MG_CORNER_COLOR_PICK
@ MG_CORNER_SIDE_TOGGLE
@ MG_CORNER_INSERT
@ MG_CORNER_COLOR_SMOOTH
@ MG_CORNER_SIDE_ARC
@ MG_CORNER_TENSOR_TOGGLE
TODO: insert short description here.
This class holds together a visible on-canvas knot and a list of draggables that need to be moved whe...
Geom::Point point
A mouse button (left/right/middle) is pressed.
Abstract base class for events.
unsigned modifiers
The modifiers mask immediately before the event.
Movement of the mouse pointer.
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
void sp_style_set_property_url(SPObject *item, gchar const *property, SPObject *linked, bool recursive)
Definition style.cpp:1380
SPStyle - a style object for SPItem objects.
SPDesktop * desktop