Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
booleans-tool.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Authors:
4 * Martin Owens
5 *
6 * Copyright (C) 2022 Authors
7 *
8 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
9 */
10
11#include <glibmm/i18n.h>
12
13#include "actions/actions-tools.h" // set_active_tool()
18#include "display/drawing.h"
19
20#include "desktop.h"
21#include "document.h"
22#include "document-undo.h"
23#include "event-log.h"
24#include "selection.h"
25#include "ui/icon-names.h"
26#include "ui/modifiers.h"
28#include "style.h"
29
32
33namespace Inkscape {
34namespace UI {
35namespace Tools {
36
38 : ToolBase(desktop, "/tools/booleans", "select.svg")
39{
40 to_commit = false;
42 if (auto selection = desktop->getSelection()) {
44 boolean_builder = std::make_unique<BooleanBuilder>(selection);
46
47 // Any changes to the selection cancel the shape building process
48 _sel_modified = selection->connectModified([this](Selection *sel, int) { shape_cancel(); });
49 _sel_changed = selection->connectChanged([this](Selection *sel) { shape_cancel(); });
50 }
52
53 auto prefs = Inkscape::Preferences::get();
54 set_opacity(prefs->getDouble("/tools/booleans/opacity", 0.5));
56}
57
64
70{
71 if (auto selection = _desktop->getSelection()) {
72 for (auto item : selection->items()) {
73 // We don't hide any image or group that contains an image
74 // FUTURE: There is a corner case where regular shapes are inside a group
75 // alongside an image, they should be hidden, but that's much more convoluted.
76 if (hide && boolean_builder && boolean_builder->contains_image(item))
77 continue;
78 if (auto ditem = item->get_arenaitem(_desktop->dkey)) {
79 ditem->setOpacity(hide ? 0.0 : SP_SCALE24_TO_FLOAT(item->style->opacity.value));
80 }
81 }
82 }
83}
84
89{
90 if (auto drawing = _desktop->getCanvasDrawing()->get_drawing()) {
91 drawing->setOpacity(opacity);
92 }
93}
94
95void InteractiveBooleansTool::switching_away(std::string const &new_tool)
96{
97 // We unhide the selected items before comitting to prevent undo from entering
98 // into a state where the drawing item for a group is invisible.
100
101 if (boolean_builder && (new_tool == "/tools/select" || new_tool == "/tool/nodes")) {
102 // Only forcefully commit if we have the user's explicit instruction to do so.
103 if (boolean_builder->has_changes() || to_commit) {
104 bool replace = Inkscape::Preferences::get()->getBool("/tools/booleans/replace", true);
105 _desktop->getSelection()->setList(boolean_builder->shape_commit(true, replace));
106 DocumentUndo::done(_desktop->doc(), "Built Shapes", INKSCAPE_ICON("draw-booleans"));
107 }
108 }
109}
110
112{
113 if (!boolean_builder || !boolean_builder->has_items()) {
114 if (_desktop->getSelection()->isEmpty()) {
115 _desktop->showNotice(_("You must select some objects to use the Shape Builder tool."), 5000);
116 } else {
117 _desktop->showNotice(_("The Shape Builder requires regular shapes to be selected."), 5000);
118 }
119 return false;
120 }
121 return true;
122}
123
125{
126 Glib::ustring path = val.getEntryName();
127 if (path == "/tools/booleans/mode") {
129 boolean_builder->task_cancel();
130 }
131}
132
134{
135 to_commit = true;
136 // disconnect so we don't get canceled by accident.
137 _sel_modified.disconnect();
138 _sel_changed.disconnect();
139 set_active_tool(_desktop, "Select");
140}
141
147
149{
150 if (!boolean_builder) {
151 return false;
152 }
153
154 bool ret = false;
155
156 inspect_event(event,
157 [&] (ButtonPressEvent const &event) {
158 ret = event_button_press_handler(event);
159 },
160 [&] (ButtonReleaseEvent const &event) {
161 ret = event_button_release_handler(event);
162 },
163 [&] (KeyPressEvent const &event) {
164 ret = event_key_press_handler(event);
165 },
166 [&] (MotionEvent const &event) {
167 ret = event_motion_handler(event);
168 },
169 [&] (CanvasEvent const &event) {}
170 );
171
172 if (ret) {
173 return true;
174 }
175
176 bool add = should_add(event.modifiersAfter());
177 set_cursor(add ? "cursor-union.svg" : "cursor-delete.svg");
179
181 boolean_builder->highlight(*last_cursor_position, add);
182 }
183
184 return ToolBase::root_handler(event);
185}
186
191bool InteractiveBooleansTool::should_add(unsigned state) const
192{
193 auto prefs = Preferences::get();
194 bool pref = prefs->getInt("/tools/booleans/mode", 0) != 0;
195 auto modifier = Modifier::get(Modifiers::Type::BOOL_SHIFT);
196 return pref == modifier->active(state);
197}
198
200{
201 auto prefs = Preferences::get();
202 bool pref = prefs->getInt("/tools/booleans/mode", 0) == 0;
203 auto modifier = Modifier::get(Modifiers::Type::BOOL_SHIFT);
205 (pref ? "<b>Drag</b> over fragments to unite them. <b>Click</b> to create a segment. Hold <b>%s</b> to Subtract."
206 : "<b>Drag</b> over fragments to delete them. <b>Click</b> to delete a segment. Hold <b>%s</b> to Unite."),
207 modifier->get_label().c_str());
208}
209
211{
212 if (event.num_press != 1) {
213 return false;
214 }
215
216 if (event.button == 1) {
217 boolean_builder->task_select(event.pos, should_add(event.modifiers));
218 return true;
219 } else if (event.button == 3) {
220 // right click; do not eat it so that right-click menu can appear, but cancel dragging
221 boolean_builder->task_cancel();
222 }
223
224 return false;
225}
226
228{
229 last_cursor_position = event.pos;
230 bool add = should_add(event.modifiers);
231
232 if (event.modifiers & GDK_BUTTON1_MASK) {
233 if (boolean_builder->has_task()) {
234 return boolean_builder->task_add(event.pos);
235 } else {
236 return boolean_builder->task_select(event.pos, add);
237 }
238 }
239
240 return false;
241}
242
244{
245 if (event.button == 1) {
246 boolean_builder->task_commit();
247 }
248 return true;
249}
250
252{
253 if (_acc_undo.isTriggeredBy(event)) {
254 boolean_builder->undo();
255 return true;
256 } else if (_acc_redo.isTriggeredBy(event)) {
257 boolean_builder->redo();
258 return true;
259 }
260
261 switch (get_latin_keyval(event)) {
262 case GDK_KEY_Escape:
263 if (boolean_builder->has_task()) {
264 boolean_builder->task_cancel();
265 } else {
266 shape_cancel();
267 }
268 return true;
269 case GDK_KEY_Return:
270 case GDK_KEY_KP_Enter:
271 if (boolean_builder->has_task()) {
272 boolean_builder->task_commit();
273 } else {
274 shape_commit();
275 }
276 return true;
277 default:
278 break;
279 }
280
281 return false;
282}
283
284} // namespace Tools
285} // namespace UI
286} // namespace Inkscape
void set_active_tool(InkscapeWindow *win, Glib::ustring const &tool)
A tool for building shapes.
Inkscape::Drawing * get_drawing()
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:103
boost::enable_if< boost::is_base_of< SPObject, T >, void >::type setList(const std::vector< T * > &objs)
Selects exactly the specified objects.
Definition object-set.h:299
bool isEmpty()
Returns true if no items are selected.
Data type representing a typeless value of a preference.
Glib::ustring getEntryName() const
Get the last component of the preference's path.
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
bool event_motion_handler(MotionEvent const &event)
void set_opacity(double opacity=1.0)
Set the variable transparency of the rest of the canvas.
bool event_button_press_handler(ButtonPressEvent const &event)
bool should_add(unsigned state) const
Returns true if the shape builder should add items, false if shape builder should delete items.
bool event_key_press_handler(KeyPressEvent const &event)
void set(Preferences::Entry const &val) override
Called by our pref_observer if a preference has been changed.
void hide_selected_objects(bool hide=true)
Hide all selected items, because they are going to be re-drawn as a fractured pattern and we don't wa...
bool root_handler(CanvasEvent const &event) override
std::optional< Geom::Point > last_cursor_position
void switching_away(std::string const &new_tool) override
bool event_button_release_handler(ButtonReleaseEvent const &event)
std::unique_ptr< BooleanBuilder > boolean_builder
Base class for Event processors.
Definition tool-base.h:107
void set_cursor(std::string filename)
Sets the current cursor to the given filename.
std::unique_ptr< MessageContext > message_context
Definition tool-base.h:193
Util::ActionAccel _acc_undo
Definition tool-base.h:223
virtual bool root_handler(CanvasEvent const &event)
Util::ActionAccel _acc_redo
Definition tool-base.h:224
bool isTriggeredBy(KeyEvent const &key) const
Checks whether a given key event triggers this action.
To do: update description of desktop.
Definition desktop.h:149
unsigned dkey
Definition desktop.h:229
Inkscape::CanvasItemDrawing * getCanvasDrawing() const
Definition desktop.h:204
Inkscape::Selection * getSelection() const
Definition desktop.h:188
SPDocument * doc() const
Definition desktop.h:159
void clearWaitingCursor()
Definition desktop.cpp:1166
void showNotice(Glib::ustring const &msg, int timeout=0)
Definition desktop.cpp:1268
void setWaitingCursor()
Definition desktop.cpp:1155
Inkscape::EventLog * get_event_log()
Definition document.h:160
Inkscape::DrawingItem * get_arenaitem(unsigned key) const
Return the arenaitem corresponding to the given item in the display with the given key.
Definition sp-item.cpp:1864
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
T< SPAttr::OPACITY, SPIScale24 > opacity
opacity
Definition style.h:216
Editable view implementation.
TODO: insert short description here.
SVG drawing for display.
Macro for icon names used in Inkscape.
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.
Helper class to stream background task notifications as a series of messages.
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
@ IMMEDIATE_MESSAGE
Definition message.h:27
unsigned button
The button that was pressed/released. (Matches GDK_BUTTON_*.)
Geom::Point pos
Location of the cursor, in world coordinates.
A mouse button (left/right/middle) is pressed.
int num_press
Counter for repeated clicks (e.g. double clicks). Starts at 1 and increments by 1 each time.
A mouse button (left/right/middle) is released.
Abstract base class for events.
unsigned modifiers
The modifiers mask immediately before the event.
unsigned modifiersAfter() const
Get the modifiers mask immediately after the event. (Convenience function.)
A key has been pressed.
Movement of the mouse pointer.
Geom::Point pos
Location of the cursor.
SPStyle - a style object for SPItem objects.
SPDesktop * desktop