Inkscape
Vector Graphics Editor
spinbutton.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Author:
4 * Johan B. C. Engelen
5 *
6 * Copyright (C) 2011 Author
7 *
8 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
9 */
10
11#include "spinbutton.h"
12
13#include <cmath>
14#include <gtkmm/enums.h>
15#include <gtkmm/object.h>
16#include <gtkmm/popovermenu.h>
17#include <gtkmm/checkbutton.h>
18#include <memory>
19#include <sigc++/functors/mem_fun.h>
20
21#include "scroll-utils.h"
22#include "ui/controller.h"
23#include "ui/tools/tool-base.h"
26#include "unit-menu.h"
27#include "unit-tracker.h"
30
31namespace Inkscape::UI::Widget {
32
33MathSpinButton::MathSpinButton(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &refGlade)
34 : Gtk::SpinButton(cobject)
35{
36 signal_input().connect(sigc::mem_fun(*this, &MathSpinButton::on_input), true);
37}
38
39int MathSpinButton::on_input(double &newvalue)
40{
41 try {
42 auto eval = Inkscape::Util::ExpressionEvaluator(get_text().c_str(), nullptr);
43 auto result = eval.evaluate();
44 newvalue = result.value;
45 } catch (Inkscape::Util::EvaluatorException const &e) {
46 g_message ("%s", e.what());
47 return false;
48 }
49 return true;
50}
51
53{
54 Controller::add_key<&SpinButton::on_key_pressed>(*this, *this);
55
56 property_has_focus().signal_changed().connect(sigc::mem_fun(*this, &SpinButton::on_has_focus_changed));
57 UI::on_popup_menu(*this, sigc::mem_fun(*this, &SpinButton::on_popup_menu));
58
59 signal_input().connect(sigc::mem_fun(*this, &SpinButton::on_input), true);
60
61 signal_destroy().connect([this] { _unparentChildren(); });
62}
63
64int SpinButton::on_input(double &newvalue)
65{
66 if (_dont_evaluate) return false;
67
68 try {
71 Unit const *unit = nullptr;
72 if (_unit_menu) {
73 unit = _unit_menu->getUnit();
74 } else {
76 }
78 result = eval.evaluate();
79 // check if output dimension corresponds to input unit
80 if (result.dimension != (unit->isAbsolute() ? 1 : 0) ) {
81 throw Inkscape::Util::EvaluatorException("Input dimensions do not match with parameter dimensions.","");
82 }
83 } else {
85 result = eval.evaluate();
86 }
87 newvalue = result.value;
88 } catch (Inkscape::Util::EvaluatorException const &e) {
89 g_message ("%s", e.what());
90 return false;
91 }
92
93 return true;
94}
95
97{
98 if (has_focus()) {
99 _on_focus_in_value = get_value();
100 }
101}
102
103bool SpinButton::on_key_pressed(GtkEventControllerKey const * const controller,
104 unsigned const keyval, unsigned const keycode,
105 GdkModifierType const state)
106{
107 bool inc = false;
108 double val = 0;
109
110 if (_increment > 0) {
111 constexpr auto modifiers = GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_ALT_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK;
112 // no modifiers pressed?
113 if ((state & modifiers) == 0) {
114 inc = true;
115 val = get_value();
116 }
117 }
118
119 switch (Inkscape::UI::Tools::get_latin_keyval(controller, keyval, keycode, state)) {
120 case GDK_KEY_Escape: // defocus
121 undo();
122 defocus();
123 break;
124
125 case GDK_KEY_Return: // defocus
126 case GDK_KEY_KP_Enter:
127 defocus();
128 break;
129
130 case GDK_KEY_Tab:
131 case GDK_KEY_ISO_Left_Tab:
132 // set the flag meaning "do not leave toolbar when changing value"
133 _stay = true;
134 break;
135
136 case GDK_KEY_z:
137 case GDK_KEY_Z:
138 if (Controller::has_flag(state, GDK_CONTROL_MASK)) {
139 _stay = true;
140 undo();
141 return true; // I consumed the event
142 }
143 break;
144
145 case GDK_KEY_Up:
146 case GDK_KEY_KP_Up:
147 if (inc) {
148 set_value(val + _increment);
149 return true;
150 }
151 break;
152
153 case GDK_KEY_Down:
154 case GDK_KEY_KP_Down:
155 if (inc) {
156 set_value(val - _increment);
157 return true;
158 }
159 break;
160
161 default:
162 break;
163 }
164
165 return false;
166}
167
169{
170 auto adj = get_adjustment();
171 adj->set_value(value);
172}
173
175{
176 if (!_custom_popup) {
177 return false;
178 }
180 _popover_menu->popup_at_center(*this);
181 return true;
182}
183
185{
186 auto adj = get_adjustment();
187 auto adj_value = adj->get_value();
188 auto lower = adj->get_lower();
189 auto upper = adj->get_upper();
190 auto page = adj->get_page_increment();
191
192 auto values = NumericMenuData{};
193
194 for (auto const &custom_data : _custom_menu_data) {
195 if (custom_data.first >= lower && custom_data.first <= upper) {
196 values.emplace(custom_data);
197 }
198 }
199
200 values.emplace(adj_value, "");
201 values.emplace(std::fmin(adj_value + page, upper), "");
202 values.emplace(std::fmax(adj_value - page, lower), "");
203
204 if (!_popover_menu) {
205 _popover_menu = std::make_unique<UI::Widget::PopoverMenu>(Gtk::PositionType::BOTTOM);
206 _popover_menu->set_parent(*this);
207 } else {
208 _popover_menu->remove_all();
209 }
210 Gtk::CheckButton *group = nullptr;
211
212 for (auto const &value : values) {
213 bool const enable = adj_value == value.first;
214 auto const item_label = !value.second.empty() ? Glib::ustring::compose("%1: %2", value.first, value.second)
216 auto const radio_button = Gtk::make_managed<Gtk::CheckButton>(item_label);
217 if (!group) {
218 group = radio_button;
219 } else {
220 radio_button->set_group(*group);
221 }
222 radio_button->set_active(enable);
223
224 auto const item = Gtk::make_managed<UI::Widget::PopoverMenuItem>();
225 item->set_child(*radio_button);
226 item->signal_activate().connect(sigc::bind(sigc::mem_fun(*this, &SpinButton::on_numeric_menu_item_activate), value.first));
227 _popover_menu->append(*item);
228 }
229}
230
232{
233 set_value(_on_focus_in_value);
234}
235
237{
238 if (_popover_menu) {
239 _popover_menu->unparent();
240 }
241}
242
244{
246}
247
249{
250 // defocus spinbutton by moving focus to the canvas, unless "stay" is on
251 if (_stay) {
252 _stay = false;
253 } else {
254 Gtk::Widget *widget = _defocus_widget ? _defocus_widget : get_scrollable_ancestor(this);
255 if (widget) {
256 widget->grab_focus();
257 }
258 }
259}
260
262{
263 _custom_popup = true;
264 _custom_menu_data = std::move(custom_menu_data);
265}
266
267void SpinButton::set_increment(double delta) {
268 _increment = delta;
269}
270
271} // namespace Inkscape::UI::Widget
272
273/*
274 Local Variables:
275 mode:c++
276 c-file-style:"stroustrup"
277 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
278 indent-tabs-mode:nil
279 fill-column:99
280 End:
281*/
282// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
uint64_t page
Definition: canvas.cpp:172
int on_input(double &newvalue)
Definition: spinbutton.cpp:39
MathSpinButton(BaseObjectType *cobject, const Glib::RefPtr< Gtk::Builder > &refGlade)
Definition: spinbutton.cpp:33
SpinButton widget, that allows entry of simple math expressions (also units, when linked with UnitMen...
Definition: spinbutton.h:49
NumericMenuData _custom_menu_data
Definition: spinbutton.h:88
void on_has_focus_changed()
When focus is obtained, save the value to enable undo later.
Definition: spinbutton.cpp:96
void undo()
Undo the editing, by resetting the value upon when the spinbutton got focus.
Definition: spinbutton.cpp:231
bool on_key_pressed(GtkEventControllerKey const *controller, unsigned keyval, unsigned keycode, GdkModifierType state)
Handle specific keypress events, like Ctrl+Z.
Definition: spinbutton.cpp:103
void set_increment(double delta)
Definition: spinbutton.cpp:267
UnitTracker * _unit_tracker
Linked unit tracker for unit conversion in entered expressions.
Definition: spinbutton.h:81
bool _dont_evaluate
Don't attempt to evaluate expressions.
Definition: spinbutton.h:87
bool _stay
Whether to ignore defocusing.
Definition: spinbutton.h:86
std::map< double, Glib::ustring > NumericMenuData
Definition: spinbutton.h:51
int on_input(double &newvalue)
This callback function should try to convert the entered text to a number and write it to newvalue.
Definition: spinbutton.cpp:64
UnitMenu * _unit_menu
Linked unit menu for unit conversion in entered expressions.
Definition: spinbutton.h:80
std::unique_ptr< UI::Widget::PopoverMenu > _popover_menu
Definition: spinbutton.h:91
void on_numeric_menu_item_activate(double value)
Definition: spinbutton.cpp:168
Gtk::Widget * _defocus_widget
Widget that should grab focus when the spinbutton defocuses.
Definition: spinbutton.h:83
bool on_popup_menu(PopupMenuOptionalClick)
Definition: spinbutton.cpp:174
void set_custom_numeric_menu_data(NumericMenuData &&custom_menu_data)
Definition: spinbutton.cpp:261
Unit const * getUnit() const
Returns the Unit object corresponding to the current selection in the dropdown widget.
Definition: unit-menu.cpp:58
Inkscape::Util::Unit const * getActiveUnit() const
Special exception class for the expression evaluator.
const char * what() const noexcept override
EvaluatorQuantity evaluate()
Evaluates the given arithmetic expression, along with an optional dimension analysis,...
Utilities to more easily use Gtk::EventController & subclasses like Gesture.
Css & result
TODO: insert short description here.
SPItem * item
Definition: imagemagick.cpp:43
SymmetricMatrix< N > adj(const ConstBaseSymmetricMatrix< N > &S)
D2< T > compose(D2< T > const &a, T const &b)
Definition: d2.h:405
iter inc(iter const &x, unsigned n)
Definition: path.cpp:410
Definition: desktop.h:51
bool has_flag(Gdk::ModifierType const state, Gdk::ModifierType const flags)
Helper to query if ModifierType state contains one or more of given flag(s).
Definition: controller.h:47
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.
Definition: tool-base.cpp:1364
Custom widgets.
Definition: desktop.h:127
Gtk::Widget * get_scrollable_ancestor(Gtk::Widget *widget)
Get the first ancestor which is scrollable.
void on_popup_menu(Gtk::Widget &widget, PopupMenuSlot slot)
Connect slot to a widgetʼs key and button events that traditionally trigger a popup menu,...
Definition: popup-menu.cpp:57
std::optional< PopupMenuClick > PopupMenuOptionalClick
Optional: not present if popup wasnʼt triggered by click.
Definition: popup-menu.h:41
Glib::ustring format_classic(T const &... args)
A replacement for GTK3ʼs Gtk::MenuItem, as removed in GTK4.
A replacement for GTK3ʼs Gtk::Menu, as removed in GTK4.