Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
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/adjustment.h>
15#include <gtkmm/enums.h>
16#include <gtkmm/eventcontrollerfocus.h>
17#include <gtkmm/eventcontrollerkey.h>
18#include <gtkmm/object.h>
19#include <gtkmm/popovermenu.h>
20#include <gtkmm/checkbutton.h>
21#include <memory>
22#include <sigc++/functors/mem_fun.h>
23
24#include "scroll-utils.h"
25#include "ui/controller.h"
26#include "ui/defocus-target.h"
27#include "ui/tools/tool-base.h"
28#include "ui/util.h"
31#include "unit-menu.h"
32#include "unit-tracker.h"
35
36namespace Inkscape::UI::Widget {
37
38MathSpinButton::MathSpinButton(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &refGlade)
39 : Gtk::SpinButton(cobject)
40{
41 signal_input().connect(sigc::mem_fun(*this, &MathSpinButton::on_input), true);
42}
43
44int MathSpinButton::on_input(double &newvalue)
45{
46 try {
48 } catch (Inkscape::Util::EvaluatorException const &e) {
49 g_message ("%s", e.what());
50 return false;
51 }
52 return true;
53}
54
56{
57 auto const key = Gtk::EventControllerKey::create();
58 key->signal_key_pressed().connect([this, &key = *key](auto &&...args) { return on_key_pressed(key, args...); }, true);
59 key->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
60 add_controller(key);
61
62 auto focus = Gtk::EventControllerFocus::create();
63 focus->signal_enter().connect([this] {
64 // When focus is obtained, save the value to enable undo later.
65 _on_focus_in_value = get_value();
66 });
67 add_controller(focus);
68
69 UI::on_popup_menu(*this, sigc::mem_fun(*this, &SpinButton::on_popup_menu));
70
71 signal_input().connect(sigc::mem_fun(*this, &SpinButton::on_input), true);
72
73 signal_destroy().connect([this] { _unparentChildren(); });
74}
75
76int SpinButton::on_input(double &newvalue)
77{
78 if (_dont_evaluate) return false;
79
80 try {
83 Unit const *unit = nullptr;
84 if (_unit_menu) {
85 unit = _unit_menu->getUnit();
86 } else {
88 }
90 // check if output dimension corresponds to input unit
91 if (result.dimension != (unit->isAbsolute() ? 1 : 0) ) {
92 throw Inkscape::Util::EvaluatorException("Input dimensions do not match with parameter dimensions.","");
93 }
94 } else {
96 }
97 newvalue = result.value;
98 } catch (Inkscape::Util::EvaluatorException const &e) {
99 g_message ("%s", e.what());
100 return false;
101 }
102
103 return true;
104}
105
106bool SpinButton::on_key_pressed(Gtk::EventControllerKey const &controller,
107 unsigned keyval, unsigned keycode, Gdk::ModifierType state)
108{
109 bool inc = false;
110 double val = 0;
111
112 if (_increment > 0) {
113 constexpr auto modifiers = Gdk::ModifierType::SHIFT_MASK |
114 Gdk::ModifierType::CONTROL_MASK |
115 Gdk::ModifierType::ALT_MASK |
116 Gdk::ModifierType::SUPER_MASK |
117 Gdk::ModifierType::HYPER_MASK |
118 Gdk::ModifierType::META_MASK;
119 // no modifiers pressed?
120 if (!Controller::has_flag(state, modifiers)) {
121 inc = true;
122 val = get_value();
123 }
124 }
125
126 switch (Inkscape::UI::Tools::get_latin_keyval(controller, keyval, keycode, state)) {
127 case GDK_KEY_Escape: // defocus
128 undo();
129 defocus();
130 return true;
131
132 case GDK_KEY_Return: // defocus
133 case GDK_KEY_KP_Enter:
134 defocus();
135 break;
136
137 case GDK_KEY_z:
138 case GDK_KEY_Z:
139 if (Controller::has_flag(state, Gdk::ModifierType::CONTROL_MASK)) {
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 get_adjustment()->set_value(value);
171}
172
174{
175 if (!_custom_popup) {
176 return false;
177 }
179 _popover_menu->popup_at_center(*this);
180 return true;
181}
182
184{
185 auto adj = get_adjustment();
186 auto adj_value = adj->get_value();
187 auto lower = adj->get_lower();
188 auto upper = adj->get_upper();
189 auto page = adj->get_page_increment();
190
191 auto values = NumericMenuData{};
192
193 for (auto const &custom_data : _custom_menu_data) {
194 if (custom_data.first >= lower && custom_data.first <= upper) {
195 values.emplace(custom_data);
196 }
197 }
198
199 values.emplace(adj_value, "");
200 values.emplace(std::fmin(adj_value + page, upper), "");
201 values.emplace(std::fmax(adj_value - page, lower), "");
202
203 if (!_popover_menu) {
204 _popover_menu = std::make_unique<UI::Widget::PopoverMenu>(Gtk::PositionType::BOTTOM);
205 _popover_menu->set_parent(*this);
206 } else {
207 _popover_menu->remove_all();
208 }
209 Gtk::CheckButton *group = nullptr;
210
211 for (auto const &value : values) {
212 bool const enable = adj_value == value.first;
213 auto const item_label = !value.second.empty() ? Glib::ustring::compose("%1: %2", value.first, value.second)
215 auto const radio_button = Gtk::make_managed<Gtk::CheckButton>(item_label);
216 if (!group) {
217 group = radio_button;
218 } else {
219 radio_button->set_group(*group);
220 }
221 radio_button->set_active(enable);
222
223 auto const item = Gtk::make_managed<UI::Widget::PopoverMenuItem>();
224 item->set_child(*radio_button);
225 item->signal_activate().connect(sigc::bind(sigc::mem_fun(*this, &SpinButton::on_numeric_menu_item_activate), value.first));
226 _popover_menu->append(*item);
227 }
228}
229
231{
232 set_value(_on_focus_in_value);
233}
234
236{
237 if (_popover_menu) {
238 _popover_menu->unparent();
239 }
240}
241
246
248{
249 // clear selection, which would otherwise persist
250 select_region(0, 0);
251
252 // defocus spinbutton by moving focus to the canvas
253 if (_defocus_target) {
255 } else if (auto widget = get_scrollable_ancestor(this)) {
256 widget->grab_focus();
257 }
258}
259
261{
262 _custom_popup = true;
263 _custom_menu_data = std::move(custom_menu_data);
264}
265
269
270} // namespace Inkscape::UI::Widget
271
272/*
273 Local Variables:
274 mode:c++
275 c-file-style:"stroustrup"
276 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
277 indent-tabs-mode:nil
278 fill-column:99
279 End:
280*/
281// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
uint64_t page
Definition canvas.cpp:171
virtual void onDefocus()=0
MathSpinButton(BaseObjectType *cobject, const Glib::RefPtr< Gtk::Builder > &refGlade)
SpinButton widget, that allows entry of simple math expressions (also units, when linked with UnitMen...
Definition spinbutton.h:52
NumericMenuData _custom_menu_data
Definition spinbutton.h:90
void undo()
Undo the editing, by resetting the value upon when the spinbutton got focus.
UnitTracker * _unit_tracker
Linked unit tracker for unit conversion in entered expressions.
Definition spinbutton.h:84
bool _dont_evaluate
Don't attempt to evaluate expressions.
Definition spinbutton.h:89
bool on_key_pressed(Gtk::EventControllerKey const &controller, unsigned keyval, unsigned keycode, Gdk::ModifierType state)
Handle specific keypress events, like Ctrl+Z.
std::map< double, Glib::ustring > NumericMenuData
Definition spinbutton.h:54
int on_input(double &newvalue)
This callback function should try to convert the entered text to a number and write it to newvalue.
UnitMenu * _unit_menu
Linked unit menu for unit conversion in entered expressions.
Definition spinbutton.h:83
std::unique_ptr< UI::Widget::PopoverMenu > _popover_menu
Definition spinbutton.h:93
void on_numeric_menu_item_activate(double value)
Inkscape::UI::DefocusTarget * _defocus_target
Widget that should be informed when the spinbutton defocuses.
Definition spinbutton.h:86
bool on_popup_menu(PopupMenuOptionalClick)
void set_custom_numeric_menu_data(NumericMenuData &&custom_menu_data)
Unit const * getUnit() const
Returns the Unit object corresponding to the current selection in the dropdown widget.
Definition unit-menu.cpp:64
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 desktop.h:50
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:25
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.
Custom widgets.
Definition desktop.h:126
Gtk::Widget * get_scrollable_ancestor(Gtk::Widget *widget)
Get the first ancestor which is scrollable.
static char const * get_text(Gtk::Editable const &editable)
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,...
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)
static cairo_user_data_key_t key
A replacement for GTK3ʼs Gtk::MenuItem, as removed in GTK4.
A replacement for GTK3ʼs Gtk::Menu, as removed in GTK4.
int delta