Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
select-toolbar.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Select toolbar
4 *
5 * Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * bulia byak <buliabyak@users.sf.net>
8 * Jon A. Cruz <jon@joncruz.org>
9 * Abhishek Sharma
10 * Vaibhav Malik <vaibhavmalik2018@gmail.com>
11 *
12 * Copyright (C) 2003-2005 authors
13 *
14 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
15 */
16
17#include "select-toolbar.h"
18
19#include <glibmm/i18n.h>
20#include <glibmm/main.h>
21#include <gtkmm/adjustment.h>
22#include <gtkmm/box.h>
23#include <gtkmm/enums.h>
24#include <gtkmm/image.h>
25#include <gtkmm/togglebutton.h>
26#include <2geom/rect.h>
27
28#include "desktop.h"
29#include "document-undo.h"
30#include "document.h"
31#include "message-stack.h"
33#include "object/sp-namedview.h"
34#include "page-manager.h"
35#include "selection.h"
36#include "ui/builder-utils.h"
37#include "ui/icon-names.h"
38#include "ui/util.h"
43
48
49namespace Inkscape::UI::Toolbar {
50
54
55SelectToolbar::SelectToolbar(Glib::RefPtr<Gtk::Builder> const &builder)
56 : Toolbar{get_widget<Gtk::Box>(builder, "select-toolbar")}
57 , _tracker{std::make_unique<UnitTracker>(Util::UNIT_TYPE_LINEAR)}
58 , _action_prefix{"selector:toolbar:"}
59 , _select_touch_btn{get_widget<Gtk::ToggleButton>(builder, "_select_touch_btn")}
60 , _transform_stroke_btn{get_widget<Gtk::ToggleButton>(builder, "_transform_stroke_btn")}
61 , _transform_corners_btn{get_widget<Gtk::ToggleButton>(builder, "_transform_corners_btn")}
62 , _transform_gradient_btn{get_widget<Gtk::ToggleButton>(builder, "_transform_gradient_btn")}
63 , _transform_pattern_btn{get_widget<Gtk::ToggleButton>(builder, "_transform_pattern_btn")}
64 , _x_item{get_derived_widget<UI::Widget::SpinButton>(builder, "_x_item")}
65 , _y_item{get_derived_widget<UI::Widget::SpinButton>(builder, "_y_item")}
66 , _w_item{get_derived_widget<UI::Widget::SpinButton>(builder, "_w_item")}
67 , _h_item{get_derived_widget<UI::Widget::SpinButton>(builder, "_h_item")}
68 , _lock_btn{get_widget<Gtk::ToggleButton>(builder, "_lock_btn")}
69{
70 auto prefs = Preferences::get();
71
76
77 auto unit_menu = _tracker->create_tool_item(_("Units"), (""));
78 get_widget<Gtk::Box>(builder, "unit_menu_box").append(*unit_menu);
79
80 _select_touch_btn.set_active(prefs->getBool("/tools/select/touch_box", false));
81 _select_touch_btn.signal_toggled().connect(sigc::mem_fun(*this, &SelectToolbar::toggle_touch));
82
83 _tracker->addUnit(Util::UnitTable::get().getUnit("%"));
84
85 // Use StyleContext to check if the child is a context item (an item that is disabled if there is no selection).
86 auto children = UI::get_children(_toolbar);
87 for (auto const child : children) {
88 if (child->has_css_class("context_item")) {
89 _context_items.push_back(child);
90 }
91 }
92
93 _transform_stroke_btn.set_active(prefs->getBool("/options/transform/stroke", true));
94 _transform_stroke_btn.signal_toggled().connect(sigc::mem_fun(*this, &SelectToolbar::toggle_stroke));
95
96 _transform_corners_btn.set_active(prefs->getBool("/options/transform/rectcorners", true));
97 _transform_corners_btn.signal_toggled().connect(sigc::mem_fun(*this, &SelectToolbar::toggle_corners));
98
99 _transform_gradient_btn.set_active(prefs->getBool("/options/transform/gradient", true));
100 _transform_gradient_btn.signal_toggled().connect(sigc::mem_fun(*this, &SelectToolbar::toggle_gradient));
101
102 _transform_pattern_btn.set_active(prefs->getBool("/options/transform/pattern", true));
103 _transform_pattern_btn.signal_toggled().connect(sigc::mem_fun(*this, &SelectToolbar::toggle_pattern));
104
105 _lock_btn.signal_toggled().connect(sigc::mem_fun(*this, &SelectToolbar::toggle_lock));
106 _lock_btn.set_active(prefs->getBool("/tools/select/lock_aspect_ratio", false));
107 toggle_lock();
108
109 _box_observer = prefs->createObserver("/tools/bounding_box", [this](const Preferences::Entry& entry) {
110 if (_desktop) {
112 }
113 });
114
116}
117
119
121{
122 if (_desktop) {
123 _selection_changed_conn.disconnect();
124 _selection_modified_conn.disconnect();
125 }
126
128
129 if (_desktop) {
130 auto sel = _desktop->getSelection();
131
132 // Force update when selection changes.
134 _selection_modified_conn = sel->connectModified(sigc::mem_fun(*this, &SelectToolbar::_selectionModified));
135
136 // Update now.
138 _sensitize();
139 }
140}
141
143{
144 _tracker->setActiveUnit(unit);
145}
146
148{
149 auto const path = "/tools/select/" + name;
150 auto const val = Preferences::get()->getDouble(path, 0.0);
151 auto const adj = btn.get_adjustment();
152 adj->set_value(val);
153 adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &SelectToolbar::any_value_changed), adj));
154 _tracker->addAdjustment(adj->gobj());
155
156 btn.addUnitTracker(_tracker.get());
157 btn.setDefocusTarget(this);
158
159 // select toolbar spin buttons increment by 1.0 with key up/down, and 0.1 with spinner buttons
160 btn.set_increment(1.0);
161}
162
164{
165 auto const selection = _desktop->getSelection();
166 bool const sensitive = selection && !selection->isEmpty();
167 for (auto item : _context_items) {
168 item->set_sensitive(sensitive);
169 }
170}
171
172void SelectToolbar::any_value_changed(Glib::RefPtr<Gtk::Adjustment> const &adj)
173{
174 // quit if run by the XML listener or a unit change
175 if (_blocker.pending() || _tracker->isUpdating() || !_desktop) {
176 return;
177 }
178
179 // in turn, prevent XML listener from responding
180 auto guard = _blocker.block();
181
182 auto prefs = Preferences::get();
183 auto selection = _desktop->getSelection();
184 auto document = _desktop->getDocument();
185 auto &pm = document->getPageManager();
186 auto page = pm.getSelectedPageRect();
187 auto page_correction = document->get_origin_follows_page();
188
189 document->ensureUpToDate();
190
191 Geom::OptRect bbox_vis = selection->visualBounds();
192 Geom::OptRect bbox_geom = selection->geometricBounds();
193 Geom::OptRect bbox_user = selection->preferredBounds();
194
195 if (!bbox_user) {
196 return;
197 }
198
199 auto const unit = _tracker->getActiveUnit();
200
201 double old_w = bbox_user->width();
202 double old_h = bbox_user->height();
203 double new_w, new_h, new_x, new_y = 0;
204
205 auto _adj_x = _x_item.get_adjustment();
206 auto _adj_y = _y_item.get_adjustment();
207 auto _adj_w = _w_item.get_adjustment();
208 auto _adj_h = _h_item.get_adjustment();
209
210 if (unit->type == Util::UNIT_TYPE_LINEAR) {
211 new_w = Quantity::convert(_adj_w->get_value(), unit, "px");
212 new_h = Quantity::convert(_adj_h->get_value(), unit, "px");
213 new_x = Quantity::convert(_adj_x->get_value(), unit, "px");
214 new_y = Quantity::convert(_adj_y->get_value(), unit, "px");
215
216 } else {
217 double old_x = bbox_user->min()[Geom::X] + (old_w * selection->anchor.x());
218 double old_y = bbox_user->min()[Geom::Y] + (old_h * selection->anchor.y());
219
220 // Adjust against selected page, so later correction isn't broken.
221 if (page_correction) {
222 old_x -= page.left();
223 old_y -= page.top();
224 }
225
226 new_x = old_x * (_adj_x->get_value() / 100 / unit->factor);
227 new_y = old_y * (_adj_y->get_value() / 100 / unit->factor);
228 new_w = old_w * (_adj_w->get_value() / 100 / unit->factor);
229 new_h = old_h * (_adj_h->get_value() / 100 / unit->factor);
230 }
231
232 // Adjust depending on the selected anchor.
233 double x0 = (new_x - (old_w * selection->anchor.x())) - ((new_w - old_w) * selection->anchor.x());
234 double y0 = (new_y - (old_h * selection->anchor.y())) - ((new_h - old_h) * selection->anchor.y());
235
236 // Adjust according to the selected page, if needed
237 if (page_correction) {
238 x0 += page.left();
239 y0 += page.top();
240 }
241
242 double x1 = x0 + new_w;
243 double xrel = new_w / old_w;
244 double y1 = y0 + new_h;
245 double yrel = new_h / old_h;
246
247 // Keep proportions if lock is on
248 if (_lock_btn.get_active()) {
249 if (adj == _adj_h) {
250 x1 = x0 + yrel * bbox_user->dimensions()[Geom::X];
251 } else if (adj == _adj_w) {
252 y1 = y0 + xrel * bbox_user->dimensions()[Geom::Y];
253 }
254 }
255
256 // scales and moves, in px
257 double mh = fabs(x0 - bbox_user->min()[Geom::X]);
258 double sh = fabs(x1 - bbox_user->max()[Geom::X]);
259 double mv = fabs(y0 - bbox_user->min()[Geom::Y]);
260 double sv = fabs(y1 - bbox_user->max()[Geom::Y]);
261
262 // unless the unit is %, convert the scales and moves to the unit
263 if (unit->type == Util::UNIT_TYPE_LINEAR) {
264 mh = Quantity::convert(mh, "px", unit);
265 sh = Quantity::convert(sh, "px", unit);
266 mv = Quantity::convert(mv, "px", unit);
267 sv = Quantity::convert(sv, "px", unit);
268 }
269
270 auto const actionkey = get_action_key(mh, sh, mv, sv);
271
272 if (actionkey) {
273
274 bool transform_stroke = prefs->getBool("/options/transform/stroke", true);
275 bool preserve = prefs->getBool("/options/preservetransform/value", false);
276
277 Geom::Affine scaler;
278 if (prefs->getInt("/tools/bounding_box") == 0) { // SPItem::VISUAL_BBOX
279 scaler = get_scale_transform_for_variable_stroke(*bbox_vis, *bbox_geom, transform_stroke, preserve, x0, y0, x1, y1);
280 } else {
281 // 1) We could have use the newer get_scale_transform_for_variable_stroke() here, but to avoid regressions
282 // we'll just use the old get_scale_transform_for_uniform_stroke() for now.
283 // 2) get_scale_transform_for_uniform_stroke() is intended for visual bounding boxes, not geometrical ones!
284 // we'll trick it into using a geometric bounding box though, by setting the stroke width to zero
285 scaler = get_scale_transform_for_uniform_stroke(*bbox_geom, 0, 0, false, false, x0, y0, x1, y1);
286 }
287
288 selection->applyAffine(scaler);
289 DocumentUndo::maybeDone(document, actionkey, _("Transform by toolbar"), INKSCAPE_ICON("tool-pointer"));
290 }
291}
292
294{
295 if (_blocker.pending()) {
296 return;
297 }
298
299 auto guard = _blocker.block();
300
301 if (sel && !sel->isEmpty()) {
302 if (auto const bbox = sel->preferredBounds()) {
303 auto const unit = _tracker->getActiveUnit();
304
305 auto width = bbox->width();
306 auto height = bbox->height();
307 auto x = bbox->left() + width * sel->anchor.x();
308 auto y = bbox->top() + height * sel->anchor.y();
309
311 auto &pm = _desktop->getDocument()->getPageManager();
312 auto page = pm.getSelectedPageRect();
313 x -= page.left();
314 y -= page.top();
315 }
316
317 auto _adj_x = _x_item.get_adjustment();
318 auto _adj_y = _y_item.get_adjustment();
319 auto _adj_w = _w_item.get_adjustment();
320 auto _adj_h = _h_item.get_adjustment();
321
322 if (unit->type == Util::UNIT_TYPE_DIMENSIONLESS) {
323 double const val = unit->factor * 100;
324 _adj_x->set_value(val);
325 _adj_y->set_value(val);
326 _adj_w->set_value(val);
327 _adj_h->set_value(val);
328 _tracker->setFullVal(_adj_x->gobj(), x);
329 _tracker->setFullVal(_adj_y->gobj(), y);
330 _tracker->setFullVal(_adj_w->gobj(), width);
331 _tracker->setFullVal(_adj_h->gobj(), height);
332 } else {
333 _adj_x->set_value(Quantity::convert(x, "px", unit));
334 _adj_y->set_value(Quantity::convert(y, "px", unit));
335 _adj_w->set_value(Quantity::convert(width, "px", unit));
336 _adj_h->set_value(Quantity::convert(height, "px", unit));
337 }
338 }
339 }
340}
341
343{
344 assert(_desktop->getSelection() == selection);
345 layout_widget_update(selection);
346 _sensitize();
347}
348
349void SelectToolbar::_selectionModified(Selection *selection, unsigned flags)
350{
351 assert(_desktop->getSelection() == selection);
352 if (flags & (SP_OBJECT_MODIFIED_FLAG |
353 SP_OBJECT_PARENT_MODIFIED_FLAG |
354 SP_OBJECT_CHILD_MODIFIED_FLAG ))
355 {
356 layout_widget_update(selection);
357 }
358}
359
360char const *SelectToolbar::get_action_key(double mh, double sh, double mv, double sv)
361{
362 // do the action only if one of the scales/moves is greater than half the last significant
363 // digit in the spinbox (currently spinboxes have 3 fractional digits, so that makes 0.0005). If
364 // the value was changed by the user, the difference will be at least that much; otherwise it's
365 // just rounding difference between the spinbox value and actual value, so no action is
366 // performed
367 double const threshold = 5e-4;
368 char const *const action = mh > threshold ? "move:horizontal:" :
369 sh > threshold ? "scale:horizontal:" :
370 mv > threshold ? "move:vertical:" :
371 sv > threshold ? "scale:vertical:" : nullptr;
372 if (!action) {
373 return nullptr;
374 }
375 _action_key = _action_prefix + action;
376 return _action_key.c_str();
377}
378
380{
381 Preferences::get()->setBool("/tools/select/lock_aspect_ratio", _lock_btn.get_active());
382
383 _lock_btn.set_image_from_icon_name(_lock_btn.get_active() ? "object-locked" : "object-unlocked");
384}
385
387{
388 Preferences::get()->setBool("/tools/select/touch_box", _select_touch_btn.get_active());
389}
390
392{
393 bool active = _transform_stroke_btn.get_active();
394 Preferences::get()->setBool("/options/transform/stroke", active);
395 if (active) {
396 _desktop->messageStack()->flash(INFORMATION_MESSAGE, _("Now <b>stroke width</b> is <b>scaled</b> when objects are scaled."));
397 } else {
398 _desktop->messageStack()->flash(INFORMATION_MESSAGE, _("Now <b>stroke width</b> is <b>not scaled</b> when objects are scaled."));
399 }
400}
401
403{
404 bool active = _transform_corners_btn.get_active();
405 Preferences::get()->setBool("/options/transform/rectcorners", active);
406 if (active) {
407 _desktop->messageStack()->flash(INFORMATION_MESSAGE, _("Now <b>rounded rectangle corners</b> are <b>scaled</b> when rectangles are scaled."));
408 } else {
409 _desktop->messageStack()->flash(INFORMATION_MESSAGE, _("Now <b>rounded rectangle corners</b> are <b>not scaled</b> when rectangles are scaled."));
410 }
411}
412
414{
415 bool active = _transform_gradient_btn.get_active();
416 Preferences::get()->setBool("/options/transform/gradient", active);
417 if (active) {
418 _desktop->messageStack()->flash(INFORMATION_MESSAGE, _("Now <b>gradients</b> are <b>transformed</b> along with their objects when those are transformed (moved, scaled, rotated, or skewed)."));
419 } else {
420 _desktop->messageStack()->flash(INFORMATION_MESSAGE, _("Now <b>gradients</b> remain <b>fixed</b> when objects are transformed (moved, scaled, rotated, or skewed)."));
421 }
422}
423
425{
426 bool active = _transform_pattern_btn.get_active();
427 Preferences::get()->setInt("/options/transform/pattern", active);
428 if (active) {
429 _desktop->messageStack()->flash(INFORMATION_MESSAGE, _("Now <b>patterns</b> are <b>transformed</b> along with their objects when those are transformed (moved, scaled, rotated, or skewed)."));
430 } else {
431 _desktop->messageStack()->flash(INFORMATION_MESSAGE, _("Now <b>patterns</b> remain <b>fixed</b> when objects are transformed (moved, scaled, rotated, or skewed)."));
432 }
433}
434
435} // namespace Inkscape::UI::Toolbar
436
437/*
438 Local Variables:
439 mode:c++
440 c-file-style:"stroustrup"
441 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
442 indent-tabs-mode:nil
443 fill-column:99
444 End:
445*/
446// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
Gtk builder utilities.
uint64_t page
Definition canvas.cpp:171
3x3 matrix representing an affine transformation.
Definition affine.h:70
C left() const
Return leftmost coordinate of the rectangle (+X is to the right).
Axis-aligned rectangle that can be empty.
Definition rect.h:203
constexpr Coord y() const noexcept
Definition point.h:106
constexpr Coord x() const noexcept
Definition point.h:104
static void maybeDone(SPDocument *document, const gchar *keyconst, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
MessageId flash(MessageType type, char const *message)
Temporarily pushes a message onto the stack.
Geom::OptRect preferredBounds() const
Returns either the visual or geometric bounding rectangle of the selection, based on the preferences ...
bool isEmpty()
Returns true if no items are selected.
Geom::Rect getSelectedPageRect() const
Returns the selected page rect, OR the viewbox rect.
Data type representing a typeless value of a preference.
double getDouble(Glib::ustring const &pref_path, double def=0.0, Glib::ustring const &unit="")
Retrieve a floating point value.
static Preferences * get()
Access the singleton Preferences object.
void setInt(Glib::ustring const &pref_path, int value)
Set an integer value.
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
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
Geom::Point anchor
Definition selection.h:201
Gtk::ToggleButton & _transform_stroke_btn
void any_value_changed(Glib::RefPtr< Gtk::Adjustment > const &adj)
Gtk::ToggleButton & _transform_gradient_btn
UI::Widget::SpinButton & _w_item
UI::Widget::SpinButton & _h_item
Gtk::ToggleButton & _transform_corners_btn
char const * get_action_key(double mh, double sh, double mv, double sv)
std::unique_ptr< UI::Widget::UnitTracker > _tracker
void _selectionModified(Selection *selection, unsigned flags)
UI::Widget::SpinButton & _y_item
void setup_derived_spin_button(UI::Widget::SpinButton &btn, Glib::ustring const &name)
std::vector< Gtk::Widget * > _context_items
Gtk::ToggleButton & _transform_pattern_btn
void setActiveUnit(Util::Unit const *unit) override
UI::Widget::SpinButton & _x_item
void setDesktop(SPDesktop *desktop) override
void _selectionChanged(Selection *selection)
Base class for all tool toolbars.
Definition toolbar.h:72
virtual void setDesktop(SPDesktop *desktop)
Definition toolbar.h:76
SpinButton widget, that allows entry of simple math expressions (also units, when linked with UnitMen...
Definition spinbutton.h:52
void addUnitTracker(UnitTracker *ut)
Definition spinbutton.h:68
void setDefocusTarget(decltype(_defocus_target) target)
Definition spinbutton.h:127
static UnitTable & get()
Definition units.cpp:441
scoped_block block()
To do: update description of desktop.
Definition desktop.h:149
SPDocument * getDocument() const
Definition desktop.h:189
Inkscape::MessageStack * messageStack() const
Definition desktop.h:160
Inkscape::Selection * getSelection() const
Definition desktop.h:188
bool get_origin_follows_page()
Inkscape::PageManager & getPageManager()
Definition document.h:162
A combobox that can be displayed in a toolbar.
Editable view implementation.
TODO: insert short description here.
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Macro for icon names used in Inkscape.
SPItem * item
Raw stack of active status messages.
Definition desktop.h:50
std::vector< Gtk::Widget * > get_children(Gtk::Widget &widget)
Get a vector of the widgetʼs children, from get_first_child() through each get_next_sibling().
Definition util.cpp:156
W & get_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id)
W & get_derived_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id, Args &&... args)
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
Miscellaneous supporting code.
Definition document.h:93
@ UNIT_TYPE_DIMENSIONLESS
Definition units.h:31
@ UNIT_TYPE_LINEAR
Definition units.h:32
@ INFORMATION_MESSAGE
Definition message.h:30
STL namespace.
Ocnode * child[8]
Definition quantize.cpp:33
Axis-aligned rectangle.
Select toolbar.
Geom::Affine get_scale_transform_for_variable_stroke(Geom::Rect const &bbox_visual, Geom::Rect const &bbox_geom, 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::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,...
TODO: insert short description here.
SPDesktop * desktop
double height
double width
Glib::ustring name
Definition toolbars.cpp:55
Glib::RefPtr< Gtk::Builder > builder
TODO: insert short description here.