1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3  * @file
4  * Rect aux toolbar
5  */
6 /* Authors:
7  *   MenTaLguY <mental@rydia.net>
8  *   Lauris Kaplinski <lauris@kaplinski.com>
9  *   bulia byak <buliabyak@users.sf.net>
10  *   Frank Felfe <innerspace@iname.com>
11  *   John Cliff <simarilius@yahoo.com>
12  *   David Turner <novalis@gnu.org>
13  *   Josh Andler <scislac@scislac.com>
14  *   Jon A. Cruz <jon@joncruz.org>
15  *   Maximilian Albert <maximilian.albert@gmail.com>
16  *   Tavmjong Bah <tavmjong@free.fr>
17  *   Abhishek Sharma
18  *   Kris De Gussem <Kris.DeGussem@gmail.com>
19  *
20  * Copyright (C) 2004 David Turner
21  * Copyright (C) 2003 MenTaLguY
22  * Copyright (C) 1999-2011 authors
23  * Copyright (C) 2001-2002 Ximian, Inc.
24  *
25  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
26  */
27 
28 #include "rect-toolbar.h"
29 
30 #include <glibmm/i18n.h>
31 
32 #include <gtkmm/separatortoolitem.h>
33 #include <gtkmm/toolbutton.h>
34 
35 #include "desktop.h"
36 #include "document-undo.h"
37 #include "selection.h"
38 #include "verbs.h"
39 
40 #include "object/sp-namedview.h"
41 #include "object/sp-rect.h"
42 
43 #include "ui/icon-names.h"
44 #include "ui/tools/rect-tool.h"
45 #include "ui/uxmanager.h"
46 #include "ui/widget/canvas.h"
47 #include "ui/widget/combo-tool-item.h"
48 #include "ui/widget/label-tool-item.h"
49 #include "ui/widget/spinbutton.h"
50 #include "ui/widget/spin-button-tool-item.h"
51 #include "ui/widget/unit-tracker.h"
52 
53 #include "widgets/widget-sizes.h"
54 
55 #include "xml/node-event-vector.h"
56 
57 using Inkscape::UI::Widget::UnitTracker;
58 using Inkscape::UI::UXManager;
59 using Inkscape::DocumentUndo;
60 using Inkscape::Util::Unit;
61 using Inkscape::Util::Quantity;
62 using Inkscape::Util::unit_table;
63 
64 static Inkscape::XML::NodeEventVector rect_tb_repr_events = {
65     nullptr, /* child_added */
66     nullptr, /* child_removed */
67     Inkscape::UI::Toolbar::RectToolbar::event_attr_changed,
68     nullptr, /* content_changed */
69     nullptr  /* order_changed */
70 };
71 
72 namespace Inkscape {
73 namespace UI {
74 namespace Toolbar {
75 
RectToolbar(SPDesktop * desktop)76 RectToolbar::RectToolbar(SPDesktop *desktop)
77     : Toolbar(desktop),
78       _tracker(new UnitTracker(Inkscape::Util::UNIT_TYPE_LINEAR)),
79       _freeze(false),
80       _single(true),
81       _repr(nullptr),
82       _mode_item(Gtk::manage(new UI::Widget::LabelToolItem(_("<b>New:</b>"))))
83 {
84     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
85 
86     // rx/ry units menu: create
87     //tracker->addUnit( SP_UNIT_PERCENT, 0 );
88     // fixme: add % meaning per cent of the width/height
89     _tracker->setActiveUnit(desktop->getNamedView()->display_units);
90     _mode_item->set_use_markup(true);
91 
92     /* W */
93     {
94         auto width_val = prefs->getDouble("/tools/shapes/rect/width", 0);
95         _width_adj = Gtk::Adjustment::create(width_val, 0, 1e6, SPIN_STEP, SPIN_PAGE_STEP);
96         _width_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("rect-width", _("W:"), _width_adj));
97         _width_item->get_spin_button()->addUnitTracker(_tracker);
98         _width_item->set_focus_widget(_desktop->canvas);
99         _width_item->set_all_tooltip_text(_("Width of rectangle"));
100 
101         _width_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &RectToolbar::value_changed),
102                                                               _width_adj,
103                                                               "width",
104                                                               &SPRect::setVisibleWidth));
105         _tracker->addAdjustment(_width_adj->gobj());
106         _width_item->set_sensitive(false);
107 
108         std::vector<double> values = {1, 2, 3, 5, 10, 20, 50, 100, 200, 500};
109         _width_item->set_custom_numeric_menu_data(values);
110     }
111 
112     /* H */
113     {
114         auto height_val = prefs->getDouble("/tools/shapes/rect/height", 0);
115 
116         _height_adj = Gtk::Adjustment::create(height_val, 0, 1e6, SPIN_STEP, SPIN_PAGE_STEP);
117         _height_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &RectToolbar::value_changed),
118                                                                _height_adj,
119                                                                "height",
120                                                                &SPRect::setVisibleHeight));
121         _tracker->addAdjustment(_height_adj->gobj());
122 
123         std::vector<double> values = { 1,  2,  3,  5, 10, 20, 50, 100, 200, 500};
124         _height_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("rect-height", _("H:"), _height_adj));
125         _height_item->get_spin_button()->addUnitTracker(_tracker);
126         _height_item->set_custom_numeric_menu_data(values);
127         _height_item->set_all_tooltip_text(_("Height of rectangle"));
128         _height_item->set_focus_widget(_desktop->canvas);
129         _height_item->set_sensitive(false);
130     }
131 
132     /* rx */
133     {
134         std::vector<Glib::ustring> labels = {_("not rounded"), "", "", "", "", "", "", "",  ""};
135         std::vector<double>        values = {             0.5,  1,  2,  3,  5, 10, 20, 50, 100};
136         auto rx_val = prefs->getDouble("/tools/shapes/rect/rx", 0);
137         _rx_adj = Gtk::Adjustment::create(rx_val, 0, 1e6, SPIN_STEP, SPIN_PAGE_STEP);
138         _rx_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &RectToolbar::value_changed),
139                                                            _rx_adj,
140                                                            "rx",
141                                                            &SPRect::setVisibleRx));
142         _tracker->addAdjustment(_rx_adj->gobj());
143         _rx_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("rect-rx", _("Rx:"), _rx_adj));
144         _rx_item->get_spin_button()->addUnitTracker(_tracker);
145         _rx_item->set_all_tooltip_text(_("Horizontal radius of rounded corners"));
146         _rx_item->set_focus_widget(_desktop->canvas);
147         _rx_item->set_custom_numeric_menu_data(values, labels);
148     }
149 
150     /* ry */
151     {
152         std::vector<Glib::ustring> labels = {_("not rounded"), "", "", "", "", "", "", "",  ""};
153         std::vector<double>        values = {             0.5,  1,  2,  3,  5, 10, 20, 50, 100};
154         auto ry_val = prefs->getDouble("/tools/shapes/rect/ry", 0);
155         _ry_adj = Gtk::Adjustment::create(ry_val, 0, 1e6, SPIN_STEP, SPIN_PAGE_STEP);
156         _ry_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &RectToolbar::value_changed),
157                                                            _ry_adj,
158                                                            "ry",
159                                                            &SPRect::setVisibleRy));
160         _tracker->addAdjustment(_ry_adj->gobj());
161         _ry_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("rect-ry", _("Ry:"), _ry_adj));
162         _ry_item->get_spin_button()->addUnitTracker(_tracker);
163         _ry_item->set_all_tooltip_text(_("Vertical radius of rounded corners"));
164         _ry_item->set_focus_widget(_desktop->canvas);
165         _ry_item->set_custom_numeric_menu_data(values, labels);
166     }
167 
168     // add the units menu
169     auto unit_menu_ti = _tracker->create_tool_item(_("Units"), (""));
170 
171     /* Reset */
172     {
173         _not_rounded = Gtk::manage(new Gtk::ToolButton(_("Not rounded")));
174         _not_rounded->set_tooltip_text(_("Make corners sharp"));
175         _not_rounded->set_icon_name(INKSCAPE_ICON("rectangle-make-corners-sharp"));
176         _not_rounded->signal_clicked().connect(sigc::mem_fun(*this, &RectToolbar::defaults));
177         _not_rounded->set_sensitive(true);
178     }
179 
180     add(*_mode_item);
181     add(*_width_item);
182     add(*_height_item);
183     add(*_rx_item);
184     add(*_ry_item);
185     add(*unit_menu_ti);
186     add(* Gtk::manage(new Gtk::SeparatorToolItem()));
187     add(*_not_rounded);
188     show_all();
189 
190     sensitivize();
191 
192     _desktop->connectEventContextChanged(sigc::mem_fun(*this, &RectToolbar::watch_ec));
193 }
194 
~RectToolbar()195 RectToolbar::~RectToolbar()
196 {
197     if (_repr) { // remove old listener
198         _repr->removeListenerByData(this);
199         Inkscape::GC::release(_repr);
200         _repr = nullptr;
201     }
202 }
203 
204 GtkWidget *
create(SPDesktop * desktop)205 RectToolbar::create(SPDesktop *desktop)
206 {
207     auto toolbar = new RectToolbar(desktop);
208     return GTK_WIDGET(toolbar->gobj());
209 }
210 
211 void
value_changed(Glib::RefPtr<Gtk::Adjustment> & adj,gchar const * value_name,void (SPRect::* setter)(gdouble))212 RectToolbar::value_changed(Glib::RefPtr<Gtk::Adjustment>&  adj,
213                            gchar const                    *value_name,
214                            void (SPRect::*setter)(gdouble))
215 {
216     Unit const *unit = _tracker->getActiveUnit();
217     g_return_if_fail(unit != nullptr);
218 
219     if (DocumentUndo::getUndoSensitive(_desktop->getDocument())) {
220         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
221         prefs->setDouble(Glib::ustring("/tools/shapes/rect/") + value_name,
222             Quantity::convert(adj->get_value(), unit, "px"));
223     }
224 
225     // quit if run by the attr_changed listener
226     if (_freeze || _tracker->isUpdating()) {
227         return;
228     }
229 
230     // in turn, prevent listener from responding
231     _freeze = true;
232 
233     bool modmade = false;
234     Inkscape::Selection *selection = _desktop->getSelection();
235     auto itemlist= selection->items();
236     for(auto i=itemlist.begin();i!=itemlist.end();++i){
237         if (SP_IS_RECT(*i)) {
238             if (adj->get_value() != 0) {
239                 (SP_RECT(*i)->*setter)(Quantity::convert(adj->get_value(), unit, "px"));
240             } else {
241                 (*i)->removeAttribute(value_name);
242             }
243             modmade = true;
244         }
245     }
246 
247     sensitivize();
248 
249     if (modmade) {
250         DocumentUndo::done(_desktop->getDocument(), SP_VERB_CONTEXT_RECT,
251                            _("Change rectangle"));
252     }
253 
254     _freeze = false;
255 }
256 
257 void
sensitivize()258 RectToolbar::sensitivize()
259 {
260     if (_rx_adj->get_value() == 0 && _ry_adj->get_value() == 0 && _single) { // only for a single selected rect (for now)
261         _not_rounded->set_sensitive(false);
262     } else {
263         _not_rounded->set_sensitive(true);
264     }
265 }
266 
267 void
defaults()268 RectToolbar::defaults()
269 {
270     _rx_adj->set_value(0.0);
271     _ry_adj->set_value(0.0);
272 
273     sensitivize();
274 }
275 
276 void
watch_ec(SPDesktop * desktop,Inkscape::UI::Tools::ToolBase * ec)277 RectToolbar::watch_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec)
278 {
279     static sigc::connection changed;
280 
281     // use of dynamic_cast<> seems wrong here -- we just need to check the current tool
282 
283     if (dynamic_cast<Inkscape::UI::Tools::RectTool *>(ec)) {
284         Inkscape::Selection *sel = desktop->getSelection();
285 
286         changed = sel->connectChanged(sigc::mem_fun(*this, &RectToolbar::selection_changed));
287 
288         // Synthesize an emission to trigger the update
289         selection_changed(sel);
290     } else {
291         if (changed) {
292             changed.disconnect();
293 
294             if (_repr) { // remove old listener
295                 _repr->removeListenerByData(this);
296                 Inkscape::GC::release(_repr);
297                 _repr = nullptr;
298             }
299         }
300     }
301 }
302 
303 /**
304  *  \param selection should not be NULL.
305  */
306 void
selection_changed(Inkscape::Selection * selection)307 RectToolbar::selection_changed(Inkscape::Selection *selection)
308 {
309     int n_selected = 0;
310     Inkscape::XML::Node *repr = nullptr;
311     SPItem *item = nullptr;
312 
313     if (_repr) { // remove old listener
314         _item = nullptr;
315         _repr->removeListenerByData(this);
316         Inkscape::GC::release(_repr);
317         _repr = nullptr;
318     }
319 
320     auto itemlist= selection->items();
321     for(auto i=itemlist.begin();i!=itemlist.end();++i){
322         if (SP_IS_RECT(*i)) {
323             n_selected++;
324             item = *i;
325             repr = item->getRepr();
326         }
327     }
328 
329     _single = false;
330 
331     if (n_selected == 0) {
332         _mode_item->set_markup(_("<b>New:</b>"));
333         _width_item->set_sensitive(false);
334         _height_item->set_sensitive(false);
335     } else if (n_selected == 1) {
336         _mode_item->set_markup(_("<b>Change:</b>"));
337         _single = true;
338         _width_item->set_sensitive(true);
339         _height_item->set_sensitive(true);
340 
341         if (repr) {
342             _repr = repr;
343             _item = item;
344             Inkscape::GC::anchor(_repr);
345             _repr->addListener(&rect_tb_repr_events, this);
346             _repr->synthesizeEvents(&rect_tb_repr_events, this);
347         }
348     } else {
349         // FIXME: implement averaging of all parameters for multiple selected
350         //gtk_label_set_markup(GTK_LABEL(l), _("<b>Average:</b>"));
351         _mode_item->set_markup(_("<b>Change:</b>"));
352         sensitivize();
353     }
354 }
355 
event_attr_changed(Inkscape::XML::Node *,gchar const *,gchar const *,gchar const *,bool,gpointer data)356 void RectToolbar::event_attr_changed(Inkscape::XML::Node * /*repr*/, gchar const * /*name*/,
357                                      gchar const * /*old_value*/, gchar const * /*new_value*/,
358                                      bool /*is_interactive*/, gpointer data)
359 {
360     auto toolbar = reinterpret_cast<RectToolbar*>(data);
361 
362     // quit if run by the _changed callbacks
363     if (toolbar->_freeze) {
364         return;
365     }
366 
367     // in turn, prevent callbacks from responding
368     toolbar->_freeze = true;
369 
370     Unit const *unit = toolbar->_tracker->getActiveUnit();
371     g_return_if_fail(unit != nullptr);
372 
373     if (toolbar->_item && SP_IS_RECT(toolbar->_item)) {
374         {
375             gdouble rx = SP_RECT(toolbar->_item)->getVisibleRx();
376             toolbar->_rx_adj->set_value(Quantity::convert(rx, "px", unit));
377         }
378 
379         {
380             gdouble ry = SP_RECT(toolbar->_item)->getVisibleRy();
381             toolbar->_ry_adj->set_value(Quantity::convert(ry, "px", unit));
382         }
383 
384         {
385             gdouble width = SP_RECT(toolbar->_item)->getVisibleWidth();
386             toolbar->_width_adj->set_value(Quantity::convert(width, "px", unit));
387         }
388 
389         {
390             gdouble height = SP_RECT(toolbar->_item)->getVisibleHeight();
391             toolbar->_height_adj->set_value(Quantity::convert(height, "px", unit));
392         }
393     }
394 
395     toolbar->sensitivize();
396     toolbar->_freeze = false;
397 }
398 
399 }
400 }
401 }
402 
403 
404 /*
405   Local Variables:
406   mode:c++
407   c-file-style:"stroustrup"
408   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
409   indent-tabs-mode:nil
410   fill-column:99
411   End:
412 */
413 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
414