1/* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3 *
4 * GtkSpinButton widget for GTK
5 * Copyright (C) 1998 Lars Hamann and Stefan Jeske
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21/*
22 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
23 * file for a list of people on the GTK+ Team. See the ChangeLog
24 * files for a list of changes. These files are distributed with
25 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
26 */
27
28#include "config.h"
29
30#include "gtkspinbutton.h"
31
32#include "gtkspinbuttonprivate.h"
33
34#include "gtkaccessibleprivate.h"
35#include "gtkadjustment.h"
36#include "gtkbox.h"
37#include "gtkbutton.h"
38#include "gtkbuttonprivate.h"
39#include "gtkeditable.h"
40#include "gtkcelleditable.h"
41#include "gtkimage.h"
42#include "gtktext.h"
43#include "gtkeventcontrollerkey.h"
44#include "gtkeventcontrollerfocus.h"
45#include "gtkeventcontrollermotion.h"
46#include "gtkeventcontrollerscroll.h"
47#include "gtkgestureclick.h"
48#include "gtkgestureswipe.h"
49#include "gtkicontheme.h"
50#include "gtkintl.h"
51#include "gtkmarshalers.h"
52#include "gtkorientable.h"
53#include "gtkprivate.h"
54#include "gtksettings.h"
55#include "gtktypebuiltins.h"
56#include "gtkwidgetprivate.h"
57#include "gtkboxlayout.h"
58#include "gtktextprivate.h"
59
60#include <stdio.h>
61#include <stdlib.h>
62#include <math.h>
63#include <string.h>
64#include <locale.h>
65
66#define MAX_TIMER_CALLS 5
67#define EPSILON 1e-10
68#define MAX_DIGITS 20
69#define TIMEOUT_INITIAL 500
70#define TIMEOUT_REPEAT 50
71
72/**
73 * GtkSpinButton:
74 *
75 * A `GtkSpinButton` is an ideal way to allow the user to set the
76 * value of some attribute.
77 *
78 * ![An example GtkSpinButton](spinbutton.png)
79 *
80 * Rather than having to directly type a number into a `GtkEntry`,
81 * `GtkSpinButton` allows the user to click on one of two arrows
82 * to increment or decrement the displayed value. A value can still be
83 * typed in, with the bonus that it can be checked to ensure it is in a
84 * given range.
85 *
86 * The main properties of a `GtkSpinButton` are through an adjustment.
87 * See the [class@Gtk.Adjustment] documentation for more details about
88 * an adjustment's properties.
89 *
90 * Note that `GtkSpinButton` will by default make its entry large enough
91 * to accommodate the lower and upper bounds of the adjustment. If this
92 * is not desired, the automatic sizing can be turned off by explicitly
93 * setting [property@Gtk.Editable:width-chars] to a value != -1.
94 *
95 * ## Using a GtkSpinButton to get an integer
96 *
97 * ```c
98 * // Provides a function to retrieve an integer value from a GtkSpinButton
99 * // and creates a spin button to model percentage values.
100 *
101 * int
102 * grab_int_value (GtkSpinButton *button,
103 * gpointer user_data)
104 * {
105 * return gtk_spin_button_get_value_as_int (button);
106 * }
107 *
108 * void
109 * create_integer_spin_button (void)
110 * {
111 *
112 * GtkWidget *window, *button;
113 * GtkAdjustment *adjustment;
114 *
115 * adjustment = gtk_adjustment_new (50.0, 0.0, 100.0, 1.0, 5.0, 0.0);
116 *
117 * window = gtk_window_new ();
118 *
119 * // creates the spinbutton, with no decimal places
120 * button = gtk_spin_button_new (adjustment, 1.0, 0);
121 * gtk_window_set_child (GTK_WINDOW (window), button);
122 *
123 * gtk_widget_show (window);
124 * }
125 * ```
126 *
127 * ## Using a GtkSpinButton to get a floating point value
128 *
129 * ```c
130 * // Provides a function to retrieve a floating point value from a
131 * // GtkSpinButton, and creates a high precision spin button.
132 *
133 * float
134 * grab_float_value (GtkSpinButton *button,
135 * gpointer user_data)
136 * {
137 * return gtk_spin_button_get_value (button);
138 * }
139 *
140 * void
141 * create_floating_spin_button (void)
142 * {
143 * GtkWidget *window, *button;
144 * GtkAdjustment *adjustment;
145 *
146 * adjustment = gtk_adjustment_new (2.500, 0.0, 5.0, 0.001, 0.1, 0.0);
147 *
148 * window = gtk_window_new ();
149 *
150 * // creates the spinbutton, with three decimal places
151 * button = gtk_spin_button_new (adjustment, 0.001, 3);
152 * gtk_window_set_child (GTK_WINDOW (window), button);
153 *
154 * gtk_widget_show (window);
155 * }
156 * ```
157 *
158 * # CSS nodes
159 *
160 * ```
161 * spinbutton.horizontal
162 * ├── text
163 * │ ├── undershoot.left
164 * │ ╰── undershoot.right
165 * ├── button.down
166 * ╰── button.up
167 * ```
168 *
169 * ```
170 * spinbutton.vertical
171 * ├── button.up
172 * ├── text
173 * │ ├── undershoot.left
174 * │ ╰── undershoot.right
175 * ╰── button.down
176 * ```
177 *
178 * `GtkSpinButton`s main CSS node has the name spinbutton. It creates subnodes
179 * for the entry and the two buttons, with these names. The button nodes have
180 * the style classes .up and .down. The `GtkText` subnodes (if present) are put
181 * below the text node. The orientation of the spin button is reflected in
182 * the .vertical or .horizontal style class on the main node.
183 *
184 * # Accessiblity
185 *
186 * `GtkSpinButton` uses the %GTK_ACCESSIBLE_ROLE_SPIN_BUTTON role.
187 */
188
189typedef struct _GtkSpinButton GtkSpinButton;
190typedef struct _GtkSpinButtonClass GtkSpinButtonClass;
191
192struct _GtkSpinButton
193{
194 GtkWidget parent_instance;
195
196 GtkAdjustment *adjustment;
197
198 GtkWidget *entry;
199
200 GtkWidget *up_button;
201 GtkWidget *down_button;
202
203 GtkWidget *click_child;
204
205 guint32 timer;
206
207 GtkSpinButtonUpdatePolicy update_policy;
208
209 double climb_rate;
210 double timer_step;
211 double swipe_remainder;
212
213 int width_chars;
214
215 GtkOrientation orientation;
216
217 guint digits : 10;
218 guint need_timer : 1;
219 guint numeric : 1;
220 guint snap_to_ticks : 1;
221 guint timer_calls : 3;
222 guint wrap : 1;
223 guint editing_canceled : 1;
224};
225
226struct _GtkSpinButtonClass
227{
228 GtkWidgetClass parent_class;
229
230 int (*input) (GtkSpinButton *spin_button,
231 double *new_value);
232 int (*output) (GtkSpinButton *spin_button);
233 void (*value_changed) (GtkSpinButton *spin_button);
234
235 /* Action signals for keybindings, do not connect to these */
236 void (*change_value) (GtkSpinButton *spin_button,
237 GtkScrollType scroll);
238
239 void (*wrapped) (GtkSpinButton *spin_button);
240};
241
242enum {
243 PROP_0,
244 PROP_ADJUSTMENT,
245 PROP_CLIMB_RATE,
246 PROP_DIGITS,
247 PROP_SNAP_TO_TICKS,
248 PROP_NUMERIC,
249 PROP_WRAP,
250 PROP_UPDATE_POLICY,
251 PROP_VALUE,
252 NUM_SPINBUTTON_PROPS,
253 PROP_ORIENTATION = NUM_SPINBUTTON_PROPS,
254 PROP_EDITING_CANCELED
255};
256
257/* Signals */
258enum
259{
260 INPUT,
261 OUTPUT,
262 VALUE_CHANGED,
263 CHANGE_VALUE,
264 WRAPPED,
265 LAST_SIGNAL
266};
267
268static void gtk_spin_button_editable_init (GtkEditableInterface *iface);
269static void gtk_spin_button_cell_editable_init (GtkCellEditableIface *iface);
270static void gtk_spin_button_finalize (GObject *object);
271static void gtk_spin_button_dispose (GObject *object);
272static void gtk_spin_button_set_property (GObject *object,
273 guint prop_id,
274 const GValue *value,
275 GParamSpec *pspec);
276static void gtk_spin_button_get_property (GObject *object,
277 guint prop_id,
278 GValue *value,
279 GParamSpec *pspec);
280static void gtk_spin_button_realize (GtkWidget *widget);
281static void gtk_spin_button_state_flags_changed (GtkWidget *widget,
282 GtkStateFlags previous_state);
283static gboolean gtk_spin_button_timer (GtkSpinButton *spin_button);
284static gboolean gtk_spin_button_stop_spinning (GtkSpinButton *spin);
285static void gtk_spin_button_value_changed (GtkAdjustment *adjustment,
286 GtkSpinButton *spin_button);
287
288static void gtk_spin_button_activate (GtkText *entry,
289 gpointer user_data);
290static void gtk_spin_button_unset_adjustment (GtkSpinButton *spin_button);
291static void gtk_spin_button_set_orientation (GtkSpinButton *spin_button,
292 GtkOrientation orientation);
293static void gtk_spin_button_snap (GtkSpinButton *spin_button,
294 double val);
295static void gtk_spin_button_insert_text (GtkEditable *editable,
296 const char *new_text,
297 int new_text_length,
298 int *position);
299static void gtk_spin_button_real_spin (GtkSpinButton *spin_button,
300 double step);
301static void gtk_spin_button_real_change_value (GtkSpinButton *spin,
302 GtkScrollType scroll);
303
304static int gtk_spin_button_default_input (GtkSpinButton *spin_button,
305 double *new_val);
306static void gtk_spin_button_default_output (GtkSpinButton *spin_button);
307
308static void gtk_spin_button_update_width_chars (GtkSpinButton *spin_button);
309
310static void gtk_spin_button_accessible_init (GtkAccessibleInterface *iface);
311
312static guint spinbutton_signals[LAST_SIGNAL] = {0};
313static GParamSpec *spinbutton_props[NUM_SPINBUTTON_PROPS] = {NULL, };
314
315G_DEFINE_TYPE_WITH_CODE (GtkSpinButton, gtk_spin_button, GTK_TYPE_WIDGET,
316 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
317 G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE,
318 gtk_spin_button_accessible_init)
319 G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
320 gtk_spin_button_editable_init)
321 G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_EDITABLE,
322 gtk_spin_button_cell_editable_init))
323
324#define add_spin_binding(widget_class, keyval, mask, scroll) \
325 gtk_widget_class_add_binding_signal (widget_class, keyval, mask, \
326 "change-value", \
327 "(i)", scroll)
328
329
330static gboolean
331gtk_spin_button_grab_focus (GtkWidget *widget)
332{
333 GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
334
335 return gtk_widget_grab_focus (widget: spin_button->entry);
336}
337
338static gboolean
339gtk_spin_button_mnemonic_activate (GtkWidget *widget,
340 gboolean group_cycling)
341{
342 GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
343
344 return gtk_widget_grab_focus (widget: spin_button->entry);
345}
346
347static void
348gtk_spin_button_class_init (GtkSpinButtonClass *class)
349{
350 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
351 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
352
353 gobject_class->finalize = gtk_spin_button_finalize;
354 gobject_class->dispose = gtk_spin_button_dispose;
355 gobject_class->set_property = gtk_spin_button_set_property;
356 gobject_class->get_property = gtk_spin_button_get_property;
357
358 widget_class->realize = gtk_spin_button_realize;
359 widget_class->state_flags_changed = gtk_spin_button_state_flags_changed;
360 widget_class->mnemonic_activate = gtk_spin_button_mnemonic_activate;
361 widget_class->grab_focus = gtk_spin_button_grab_focus;
362 widget_class->focus = gtk_widget_focus_child;
363
364 class->input = NULL;
365 class->output = NULL;
366 class->change_value = gtk_spin_button_real_change_value;
367
368 /**
369 * GtkSpinButton:adjustment: (attributes org.gtk.Property.get=gtk_spin_button_get_adjustment org.gtk.Property.set=gtk_spin_button_set_adjustment)
370 *
371 * The adjustment that holds the value of the spin button.
372 */
373 spinbutton_props[PROP_ADJUSTMENT] =
374 g_param_spec_object (name: "adjustment",
375 P_("Adjustment"),
376 P_("The adjustment that holds the value of the spin button"),
377 GTK_TYPE_ADJUSTMENT,
378 GTK_PARAM_READWRITE);
379
380 /**
381 * GtkSpinButton:climb-rate: (attributes org.gtk.Property.get=gtk_spin_button_get_climb_rate org.gtk.Property.set=gtk_spin_button_set_climb_rate)
382 *
383 * The acceleration rate when you hold down a button or key.
384 */
385 spinbutton_props[PROP_CLIMB_RATE] =
386 g_param_spec_double (name: "climb-rate",
387 P_("Climb Rate"),
388 P_("The acceleration rate when you hold down a button or key"),
389 minimum: 0.0, G_MAXDOUBLE, default_value: 0.0,
390 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
391
392 /**
393 * GtkSpinButton:digits: (attributes org.gtk.Property.get=gtk_spin_button_get_digits org.gtk.Property.set=gtk_spin_button_set_digits)
394 *
395 * The number of decimal places to display.
396 */
397 spinbutton_props[PROP_DIGITS] =
398 g_param_spec_uint (name: "digits",
399 P_("Digits"),
400 P_("The number of decimal places to display"),
401 minimum: 0, MAX_DIGITS, default_value: 0,
402 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
403
404 /**
405 * GtkSpinButton:snap-to-ticks: (attributes org.gtk.Property.get=gtk_spin_button_get_snap_to_ticks org.gtk.Property.set=gtk_spin_button_set_snap_to_ticks)
406 *
407 * Whether erroneous values are automatically changed to the spin buttons
408 * nearest step increment.
409 */
410 spinbutton_props[PROP_SNAP_TO_TICKS] =
411 g_param_spec_boolean (name: "snap-to-ticks",
412 P_("Snap to Ticks"),
413 P_("Whether erroneous values are automatically changed to a spin button’s nearest step increment"),
414 FALSE,
415 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
416
417 /**
418 * GtkSpinButton:numeric: (attributes org.gtk.Property.get=gtk_spin_button_get_numeric org.gtk.Property.set=gtk_spin_button_set_numeric)
419 *
420 * Whether non-numeric characters should be ignored.
421 */
422 spinbutton_props[PROP_NUMERIC] =
423 g_param_spec_boolean (name: "numeric",
424 P_("Numeric"),
425 P_("Whether non-numeric characters should be ignored"),
426 FALSE,
427 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
428
429 /**
430 * GtkSpinButton:wrap: (attributes org.gtk.Property.get=gtk_spin_button_get_wrap org.gtk.Property.set=gtk_spin_button_set_wrap)
431 *
432 * Whether a spin button should wrap upon reaching its limits.
433 */
434 spinbutton_props[PROP_WRAP] =
435 g_param_spec_boolean (name: "wrap",
436 P_("Wrap"),
437 P_("Whether a spin button should wrap upon reaching its limits"),
438 FALSE,
439 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
440
441 /**
442 * GtkSpinButton:update-policy: (attributes org.gtk.Property.get=gtk_spin_button_get_update_policy org.gtk.Property.set=gtk_spin_button_set_update_policy)
443 *
444 * Whether the spin button should update always, or only when the value
445 * is acceptable.
446 */
447 spinbutton_props[PROP_UPDATE_POLICY] =
448 g_param_spec_enum (name: "update-policy",
449 P_("Update Policy"),
450 P_("Whether the spin button should update always, or only when the value is legal"),
451 enum_type: GTK_TYPE_SPIN_BUTTON_UPDATE_POLICY,
452 default_value: GTK_UPDATE_ALWAYS,
453 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
454
455 /**
456 * GtkSpinButton:value: (attributes org.gtk.Property.get=gtk_spin_button_get_value org.gtk.Property.set=gtk_spin_button_set_value)
457 *
458 * The current value.
459 */
460 spinbutton_props[PROP_VALUE] =
461 g_param_spec_double (name: "value",
462 P_("Value"),
463 P_("Reads the current value, or sets a new value"),
464 minimum: -G_MAXDOUBLE, G_MAXDOUBLE, default_value: 0.0,
465 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
466
467 g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_SPINBUTTON_PROPS, pspecs: spinbutton_props);
468 g_object_class_override_property (oclass: gobject_class, property_id: PROP_ORIENTATION, name: "orientation");
469 g_object_class_override_property (oclass: gobject_class, property_id: PROP_EDITING_CANCELED, name: "editing-canceled");
470 gtk_editable_install_properties (object_class: gobject_class, first_prop: PROP_EDITING_CANCELED + 1);
471
472 /**
473 * GtkSpinButton::input:
474 * @spin_button: the object on which the signal was emitted
475 * @new_value: (out) (type double): return location for the new value
476 *
477 * Emitted to convert the users input into a double value.
478 *
479 * The signal handler is expected to use [method@Gtk.Editable.get_text]
480 * to retrieve the text of the spinbutton and set @new_value to the
481 * new value.
482 *
483 * The default conversion uses g_strtod().
484 *
485 * Returns: %TRUE for a successful conversion, %FALSE if the input
486 * was not handled, and %GTK_INPUT_ERROR if the conversion failed.
487 */
488 spinbutton_signals[INPUT] =
489 g_signal_new (I_("input"),
490 G_TYPE_FROM_CLASS (gobject_class),
491 signal_flags: G_SIGNAL_RUN_LAST,
492 G_STRUCT_OFFSET (GtkSpinButtonClass, input),
493 NULL, NULL,
494 c_marshaller: _gtk_marshal_INT__POINTER,
495 G_TYPE_INT, n_params: 1,
496 G_TYPE_POINTER);
497
498 /**
499 * GtkSpinButton::output:
500 * @spin_button: the object on which the signal was emitted
501 *
502 * Emitted to tweak the formatting of the value for display.
503 *
504 * ```c
505 * // show leading zeros
506 * static gboolean
507 * on_output (GtkSpinButton *spin,
508 * gpointer data)
509 * {
510 * GtkAdjustment *adjustment;
511 * char *text;
512 * int value;
513 *
514 * adjustment = gtk_spin_button_get_adjustment (spin);
515 * value = (int)gtk_adjustment_get_value (adjustment);
516 * text = g_strdup_printf ("%02d", value);
517 * gtk_spin_button_set_text (spin, text):
518 * g_free (text);
519 *
520 * return TRUE;
521 * }
522 * ```
523 *
524 * Returns: %TRUE if the value has been displayed
525 */
526 spinbutton_signals[OUTPUT] =
527 g_signal_new (I_("output"),
528 G_TYPE_FROM_CLASS (gobject_class),
529 signal_flags: G_SIGNAL_RUN_LAST,
530 G_STRUCT_OFFSET (GtkSpinButtonClass, output),
531 accumulator: _gtk_boolean_handled_accumulator, NULL,
532 c_marshaller: _gtk_marshal_BOOLEAN__VOID,
533 G_TYPE_BOOLEAN, n_params: 0);
534
535 /**
536 * GtkSpinButton::value-changed:
537 * @spin_button: the object on which the signal was emitted
538 *
539 * Emitted when the value is changed.
540 *
541 * Also see the [signal@Gtk.SpinButton::output] signal.
542 */
543 spinbutton_signals[VALUE_CHANGED] =
544 g_signal_new (I_("value-changed"),
545 G_TYPE_FROM_CLASS (gobject_class),
546 signal_flags: G_SIGNAL_RUN_LAST,
547 G_STRUCT_OFFSET (GtkSpinButtonClass, value_changed),
548 NULL, NULL,
549 NULL,
550 G_TYPE_NONE, n_params: 0);
551
552 /**
553 * GtkSpinButton::wrapped:
554 * @spin_button: the object on which the signal was emitted
555 *
556 * Emitted right after the spinbutton wraps from its maximum
557 * to its minimum value or vice-versa.
558 */
559 spinbutton_signals[WRAPPED] =
560 g_signal_new (I_("wrapped"),
561 G_TYPE_FROM_CLASS (gobject_class),
562 signal_flags: G_SIGNAL_RUN_LAST,
563 G_STRUCT_OFFSET (GtkSpinButtonClass, wrapped),
564 NULL, NULL,
565 NULL,
566 G_TYPE_NONE, n_params: 0);
567
568 /* Action signals */
569 /**
570 * GtkSpinButton::change-value:
571 * @spin_button: the object on which the signal was emitted
572 * @scroll: a `GtkScrollType` to specify the speed and amount of change
573 *
574 * Emitted when the user initiates a value change.
575 *
576 * This is a [keybinding signal](class.SignalAction.html).
577 *
578 * Applications should not connect to it, but may emit it with
579 * g_signal_emit_by_name() if they need to control the cursor
580 * programmatically.
581 *
582 * The default bindings for this signal are Up/Down and PageUp/PageDown.
583 */
584 spinbutton_signals[CHANGE_VALUE] =
585 g_signal_new (I_("change-value"),
586 G_TYPE_FROM_CLASS (gobject_class),
587 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
588 G_STRUCT_OFFSET (GtkSpinButtonClass, change_value),
589 NULL, NULL,
590 NULL,
591 G_TYPE_NONE, n_params: 1,
592 GTK_TYPE_SCROLL_TYPE);
593
594 add_spin_binding (widget_class, GDK_KEY_Up, 0, GTK_SCROLL_STEP_UP);
595 add_spin_binding (widget_class, GDK_KEY_KP_Up, 0, GTK_SCROLL_STEP_UP);
596 add_spin_binding (widget_class, GDK_KEY_Down, 0, GTK_SCROLL_STEP_DOWN);
597 add_spin_binding (widget_class, GDK_KEY_KP_Down, 0, GTK_SCROLL_STEP_DOWN);
598 add_spin_binding (widget_class, GDK_KEY_Page_Up, 0, GTK_SCROLL_PAGE_UP);
599 add_spin_binding (widget_class, GDK_KEY_Page_Down, 0, GTK_SCROLL_PAGE_DOWN);
600 add_spin_binding (widget_class, GDK_KEY_End, GDK_CONTROL_MASK, GTK_SCROLL_END);
601 add_spin_binding (widget_class, GDK_KEY_Home, GDK_CONTROL_MASK, GTK_SCROLL_START);
602 add_spin_binding (widget_class, GDK_KEY_Page_Up, GDK_CONTROL_MASK, GTK_SCROLL_END);
603 add_spin_binding (widget_class, GDK_KEY_Page_Down, GDK_CONTROL_MASK, GTK_SCROLL_START);
604
605 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
606 gtk_widget_class_set_css_name (widget_class, I_("spinbutton"));
607 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_SPIN_BUTTON);
608}
609
610static GtkEditable *
611gtk_spin_button_get_delegate (GtkEditable *editable)
612{
613 GtkSpinButton *spin_button = GTK_SPIN_BUTTON (editable);
614
615 return GTK_EDITABLE (spin_button->entry);
616}
617
618static void
619gtk_spin_button_editable_init (GtkEditableInterface *iface)
620{
621 iface->get_delegate = gtk_spin_button_get_delegate;
622 iface->insert_text = gtk_spin_button_insert_text;
623}
624
625static gboolean
626gtk_spin_button_accessible_get_platform_state (GtkAccessible *self,
627 GtkAccessiblePlatformState state)
628{
629 GtkSpinButton *spin_button = GTK_SPIN_BUTTON (self);
630
631 switch (state)
632 {
633 case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE:
634 return gtk_widget_get_focusable (widget: spin_button->entry);
635 case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED:
636 return gtk_widget_has_focus (widget: spin_button->entry);
637 case GTK_ACCESSIBLE_PLATFORM_STATE_ACTIVE:
638 return FALSE;
639 default:
640 g_assert_not_reached ();
641 }
642}
643
644static void
645gtk_spin_button_accessible_init (GtkAccessibleInterface *iface)
646{
647 GtkAccessibleInterface *parent_iface = g_type_interface_peek_parent (g_iface: iface);
648 iface->get_at_context = parent_iface->get_at_context;
649 iface->get_platform_state = gtk_spin_button_accessible_get_platform_state;
650}
651
652static void
653gtk_cell_editable_spin_button_activated (GtkText *text, GtkSpinButton *spin)
654{
655 g_object_ref (spin);
656 gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (spin));
657 gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (spin));
658 g_object_unref (object: spin);
659}
660
661static gboolean
662gtk_cell_editable_spin_button_key_pressed (GtkEventControllerKey *key,
663 guint keyval,
664 guint keycode,
665 GdkModifierType modifiers,
666 GtkSpinButton *spin)
667{
668 if (keyval == GDK_KEY_Escape)
669 {
670 spin->editing_canceled = TRUE;
671
672 g_object_ref (spin);
673 gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (spin));
674 gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (spin));
675 g_object_unref (object: spin);
676
677 return GDK_EVENT_STOP;
678 }
679
680 /* override focus */
681 if (keyval == GDK_KEY_Up || keyval == GDK_KEY_Down)
682 {
683 g_object_ref (spin);
684 gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (spin));
685 gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (spin));
686 g_object_unref (object: spin);
687
688 return GDK_EVENT_STOP;
689 }
690
691 return GDK_EVENT_PROPAGATE;
692}
693
694static void
695gtk_spin_button_start_editing (GtkCellEditable *cell_editable,
696 GdkEvent *event)
697{
698 GtkSpinButton *spin = GTK_SPIN_BUTTON (cell_editable);
699
700 g_signal_connect (spin->entry, "activate",
701 G_CALLBACK (gtk_cell_editable_spin_button_activated), cell_editable);
702 g_signal_connect (gtk_text_get_key_controller (GTK_TEXT (spin->entry)), "key-pressed",
703 G_CALLBACK (gtk_cell_editable_spin_button_key_pressed), cell_editable);
704}
705
706static void
707gtk_spin_button_cell_editable_init (GtkCellEditableIface *iface)
708{
709 iface->start_editing = gtk_spin_button_start_editing;
710}
711
712static void
713gtk_spin_button_set_property (GObject *object,
714 guint prop_id,
715 const GValue *value,
716 GParamSpec *pspec)
717{
718 GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
719
720 if (prop_id == PROP_EDITING_CANCELED + 1 + GTK_EDITABLE_PROP_WIDTH_CHARS)
721 {
722 spin_button->width_chars = g_value_get_int (value);
723 gtk_spin_button_update_width_chars (spin_button);
724 return;
725 }
726
727 if (gtk_editable_delegate_set_property (object, prop_id, value, pspec))
728 return;
729
730 switch (prop_id)
731 {
732 GtkAdjustment *adjustment;
733
734 case PROP_ADJUSTMENT:
735 adjustment = GTK_ADJUSTMENT (g_value_get_object (value));
736 gtk_spin_button_set_adjustment (spin_button, adjustment);
737 break;
738 case PROP_CLIMB_RATE:
739 gtk_spin_button_configure (spin_button,
740 adjustment: spin_button->adjustment,
741 climb_rate: g_value_get_double (value),
742 digits: spin_button->digits);
743 break;
744 case PROP_DIGITS:
745 gtk_spin_button_configure (spin_button,
746 adjustment: spin_button->adjustment,
747 climb_rate: spin_button->climb_rate,
748 digits: g_value_get_uint (value));
749 break;
750 case PROP_SNAP_TO_TICKS:
751 gtk_spin_button_set_snap_to_ticks (spin_button, snap_to_ticks: g_value_get_boolean (value));
752 break;
753 case PROP_NUMERIC:
754 gtk_spin_button_set_numeric (spin_button, numeric: g_value_get_boolean (value));
755 break;
756 case PROP_WRAP:
757 gtk_spin_button_set_wrap (spin_button, wrap: g_value_get_boolean (value));
758 break;
759 case PROP_UPDATE_POLICY:
760 gtk_spin_button_set_update_policy (spin_button, policy: g_value_get_enum (value));
761 break;
762 case PROP_VALUE:
763 gtk_spin_button_set_value (spin_button, value: g_value_get_double (value));
764 break;
765 case PROP_ORIENTATION:
766 gtk_spin_button_set_orientation (spin_button, orientation: g_value_get_enum (value));
767 break;
768 case PROP_EDITING_CANCELED:
769 if (spin_button->editing_canceled != g_value_get_boolean (value))
770 {
771 spin_button->editing_canceled = g_value_get_boolean (value);
772 g_object_notify (object, property_name: "editing-canceled");
773 }
774 break;
775 default:
776 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
777 break;
778 }
779}
780
781static void
782gtk_spin_button_get_property (GObject *object,
783 guint prop_id,
784 GValue *value,
785 GParamSpec *pspec)
786{
787 GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
788
789 if (prop_id == PROP_EDITING_CANCELED + 1 + GTK_EDITABLE_PROP_WIDTH_CHARS)
790 {
791 g_value_set_int (value, v_int: spin_button->width_chars);
792 return;
793 }
794 if (gtk_editable_delegate_get_property (object, prop_id, value, pspec))
795 return;
796
797 switch (prop_id)
798 {
799 case PROP_ADJUSTMENT:
800 g_value_set_object (value, v_object: spin_button->adjustment);
801 break;
802 case PROP_CLIMB_RATE:
803 g_value_set_double (value, v_double: spin_button->climb_rate);
804 break;
805 case PROP_DIGITS:
806 g_value_set_uint (value, v_uint: spin_button->digits);
807 break;
808 case PROP_SNAP_TO_TICKS:
809 g_value_set_boolean (value, v_boolean: spin_button->snap_to_ticks);
810 break;
811 case PROP_NUMERIC:
812 g_value_set_boolean (value, v_boolean: spin_button->numeric);
813 break;
814 case PROP_WRAP:
815 g_value_set_boolean (value, v_boolean: spin_button->wrap);
816 break;
817 case PROP_UPDATE_POLICY:
818 g_value_set_enum (value, v_enum: spin_button->update_policy);
819 break;
820 case PROP_VALUE:
821 g_value_set_double (value, v_double: gtk_adjustment_get_value (adjustment: spin_button->adjustment));
822 break;
823 case PROP_ORIENTATION:
824 g_value_set_enum (value, v_enum: gtk_orientable_get_orientation (GTK_ORIENTABLE (gtk_widget_get_layout_manager (GTK_WIDGET (spin_button)))));
825 break;
826 case PROP_EDITING_CANCELED:
827 g_value_set_boolean (value, v_boolean: spin_button->editing_canceled);
828 break;
829 default:
830 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
831 break;
832 }
833}
834
835static void
836swipe_gesture_begin (GtkGesture *gesture,
837 GdkEventSequence *sequence,
838 GtkSpinButton *spin_button)
839{
840 gtk_gesture_set_state (gesture, state: GTK_EVENT_SEQUENCE_CLAIMED);
841 gtk_widget_grab_focus (GTK_WIDGET (spin_button));
842 spin_button->swipe_remainder = 0;
843}
844
845static void
846swipe_gesture_update (GtkGesture *gesture,
847 GdkEventSequence *sequence,
848 GtkSpinButton *spin_button)
849{
850 double vel_y, step;
851
852 gtk_gesture_swipe_get_velocity (GTK_GESTURE_SWIPE (gesture), NULL, velocity_y: &vel_y);
853 step = (-vel_y / 20) + spin_button->swipe_remainder;
854 spin_button->swipe_remainder = fmod (x: step, y: gtk_adjustment_get_step_increment (adjustment: spin_button->adjustment));
855 gtk_spin_button_real_spin (spin_button, step: step - spin_button->swipe_remainder);
856}
857
858static gboolean
859scroll_controller_scroll (GtkEventControllerScroll *Scroll,
860 double dx,
861 double dy,
862 GtkWidget *widget)
863{
864 GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
865
866 if (!gtk_widget_has_focus (widget))
867 gtk_widget_grab_focus (widget);
868 gtk_spin_button_real_spin (spin_button: spin, step: -dy * gtk_adjustment_get_step_increment (adjustment: spin->adjustment));
869
870 return GDK_EVENT_STOP;
871}
872
873static gboolean
874gtk_spin_button_stop_spinning (GtkSpinButton *spin)
875{
876 gboolean did_spin = FALSE;
877
878 if (spin->timer)
879 {
880 g_source_remove (tag: spin->timer);
881 spin->timer = 0;
882 spin->need_timer = FALSE;
883
884 did_spin = TRUE;
885 }
886
887 spin->timer_step = gtk_adjustment_get_step_increment (adjustment: spin->adjustment);
888 spin->timer_calls = 0;
889
890 spin->click_child = NULL;
891
892 return did_spin;
893}
894
895static void
896start_spinning (GtkSpinButton *spin,
897 GtkWidget *click_child,
898 double step)
899{
900 spin->click_child = click_child;
901
902 if (!spin->timer)
903 {
904 spin->timer_step = step;
905 spin->need_timer = TRUE;
906 spin->timer = g_timeout_add (TIMEOUT_INITIAL,
907 function: (GSourceFunc) gtk_spin_button_timer,
908 data: (gpointer) spin);
909 gdk_source_set_static_name_by_id (tag: spin->timer, name: "[gtk] gtk_spin_button_timer");
910 }
911 gtk_spin_button_real_spin (spin_button: spin, step: click_child == spin->up_button ? step : -step);
912}
913
914static void
915button_pressed_cb (GtkGestureClick *gesture,
916 int n_pressses,
917 double x,
918 double y,
919 gpointer user_data)
920{
921 GtkSpinButton *spin_button = user_data;
922 GtkWidget *pressed_button = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
923
924 gtk_widget_grab_focus (GTK_WIDGET (spin_button));
925
926 if (gtk_editable_get_editable (GTK_EDITABLE (spin_button->entry)))
927 {
928 int button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
929 gtk_spin_button_update (spin_button);
930
931 if (button == GDK_BUTTON_PRIMARY)
932 start_spinning (spin: spin_button, click_child: pressed_button, step: gtk_adjustment_get_step_increment (adjustment: spin_button->adjustment));
933 else if (button == GDK_BUTTON_MIDDLE)
934 start_spinning (spin: spin_button, click_child: pressed_button, step: gtk_adjustment_get_page_increment (adjustment: spin_button->adjustment));
935
936 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED);
937 }
938 else
939 {
940 gtk_widget_error_bell (GTK_WIDGET (spin_button));
941 }
942}
943
944static void
945button_released_cb (GtkGestureClick *gesture,
946 int n_pressses,
947 double x,
948 double y,
949 gpointer user_data)
950{
951 GtkSpinButton *spin_button = user_data;
952 int button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
953
954 gtk_spin_button_stop_spinning (spin: spin_button);
955
956 if (button == GDK_BUTTON_SECONDARY)
957 {
958 GtkWidget *button_widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
959 double diff;
960 if (button_widget == spin_button->down_button)
961 {
962 diff = gtk_adjustment_get_value (adjustment: spin_button->adjustment) - gtk_adjustment_get_lower (adjustment: spin_button->adjustment);
963 if (diff > EPSILON)
964 gtk_spin_button_real_spin (spin_button, step: -diff);
965 }
966 else if (button_widget == spin_button->up_button)
967 {
968 diff = gtk_adjustment_get_upper (adjustment: spin_button->adjustment) - gtk_adjustment_get_value (adjustment: spin_button->adjustment);
969 if (diff > EPSILON)
970 gtk_spin_button_real_spin (spin_button, step: diff);
971 }
972 }
973}
974
975static void
976button_cancel_cb (GtkGesture *gesture,
977 GdkEventSequence *sequence,
978 GtkSpinButton *spin_button)
979{
980 gtk_spin_button_stop_spinning (spin: spin_button);
981}
982
983static void
984key_controller_key_released (GtkEventControllerKey *key,
985 guint keyval,
986 guint keycode,
987 GdkModifierType modifiers,
988 GtkSpinButton *spin_button)
989{
990 spin_button->timer_step = gtk_adjustment_get_step_increment (adjustment: spin_button->adjustment);
991 spin_button->timer_calls = 0;
992}
993
994static void
995key_controller_focus_out (GtkEventController *controller,
996 GtkSpinButton *spin_button)
997{
998 if (gtk_editable_get_editable (GTK_EDITABLE (spin_button->entry)))
999 gtk_spin_button_update (spin_button);
1000}
1001
1002static void
1003gtk_spin_button_init (GtkSpinButton *spin_button)
1004{
1005 GtkEventController *controller;
1006 GtkGesture *gesture;
1007
1008 spin_button->adjustment = NULL;
1009 spin_button->timer = 0;
1010 spin_button->climb_rate = 0.0;
1011 spin_button->timer_step = 0.0;
1012 spin_button->update_policy = GTK_UPDATE_ALWAYS;
1013 spin_button->click_child = NULL;
1014 spin_button->need_timer = FALSE;
1015 spin_button->timer_calls = 0;
1016 spin_button->digits = 0;
1017 spin_button->numeric = FALSE;
1018 spin_button->wrap = FALSE;
1019 spin_button->snap_to_ticks = FALSE;
1020 spin_button->width_chars = -1;
1021
1022 gtk_widget_update_orientation (GTK_WIDGET (spin_button), orientation: GTK_ORIENTATION_HORIZONTAL);
1023
1024 spin_button->entry = gtk_text_new ();
1025 gtk_editable_init_delegate (GTK_EDITABLE (spin_button));
1026 gtk_editable_set_width_chars (GTK_EDITABLE (spin_button->entry), n_chars: 0);
1027 gtk_editable_set_max_width_chars (GTK_EDITABLE (spin_button->entry), n_chars: 0);
1028 gtk_widget_set_hexpand (widget: spin_button->entry, TRUE);
1029 gtk_widget_set_vexpand (widget: spin_button->entry, TRUE);
1030 g_signal_connect (spin_button->entry, "activate", G_CALLBACK (gtk_spin_button_activate), spin_button);
1031 gtk_widget_set_parent (widget: spin_button->entry, GTK_WIDGET (spin_button));
1032
1033 spin_button->down_button = g_object_new (GTK_TYPE_BUTTON,
1034 first_property_name: "accessible-role", GTK_ACCESSIBLE_ROLE_NONE,
1035 "icon-name", "value-decrease-symbolic",
1036 NULL);
1037 gtk_widget_add_css_class (widget: spin_button->down_button, css_class: "down");
1038 gtk_widget_set_can_focus (widget: spin_button->down_button, FALSE);
1039 gtk_widget_set_parent (widget: spin_button->down_button, GTK_WIDGET (spin_button));
1040
1041 gesture = gtk_gesture_click_new ();
1042 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), button: 0);
1043 gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE);
1044 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
1045 phase: GTK_PHASE_CAPTURE);
1046 g_signal_connect (gesture, "pressed", G_CALLBACK (button_pressed_cb), spin_button);
1047 g_signal_connect (gesture, "released", G_CALLBACK (button_released_cb), spin_button);
1048 g_signal_connect (gesture, "cancel", G_CALLBACK (button_cancel_cb), spin_button);
1049 gtk_widget_add_controller (GTK_WIDGET (spin_button->down_button), GTK_EVENT_CONTROLLER (gesture));
1050 gtk_gesture_group (group_gesture: gtk_button_get_gesture (GTK_BUTTON (spin_button->down_button)), gesture);
1051
1052 spin_button->up_button = g_object_new (GTK_TYPE_BUTTON,
1053 first_property_name: "accessible-role", GTK_ACCESSIBLE_ROLE_NONE,
1054 "icon-name", "value-increase-symbolic",
1055 NULL);
1056 gtk_widget_add_css_class (widget: spin_button->up_button, css_class: "up");
1057 gtk_widget_set_can_focus (widget: spin_button->up_button, FALSE);
1058 gtk_widget_set_parent (widget: spin_button->up_button, GTK_WIDGET (spin_button));
1059
1060 gesture = gtk_gesture_click_new ();
1061 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), button: 0);
1062 gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE);
1063 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
1064 phase: GTK_PHASE_CAPTURE);
1065 g_signal_connect (gesture, "pressed", G_CALLBACK (button_pressed_cb), spin_button);
1066 g_signal_connect (gesture, "released", G_CALLBACK (button_released_cb), spin_button);
1067 g_signal_connect (gesture, "cancel", G_CALLBACK (button_cancel_cb), spin_button);
1068 gtk_widget_add_controller (GTK_WIDGET (spin_button->up_button), GTK_EVENT_CONTROLLER (gesture));
1069 gtk_gesture_group (group_gesture: gtk_button_get_gesture (GTK_BUTTON (spin_button->up_button)),
1070 gesture);
1071
1072 gtk_spin_button_set_adjustment (spin_button, NULL);
1073
1074 gesture = gtk_gesture_swipe_new ();
1075 gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), TRUE);
1076 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
1077 phase: GTK_PHASE_CAPTURE);
1078 g_signal_connect (gesture, "begin",
1079 G_CALLBACK (swipe_gesture_begin), spin_button);
1080 g_signal_connect (gesture, "update",
1081 G_CALLBACK (swipe_gesture_update), spin_button);
1082 gtk_widget_add_controller (GTK_WIDGET (spin_button->entry),
1083 GTK_EVENT_CONTROLLER (gesture));
1084
1085 controller = gtk_event_controller_scroll_new (flags: GTK_EVENT_CONTROLLER_SCROLL_VERTICAL |
1086 GTK_EVENT_CONTROLLER_SCROLL_DISCRETE);
1087 g_signal_connect (controller, "scroll",
1088 G_CALLBACK (scroll_controller_scroll), spin_button);
1089 gtk_widget_add_controller (GTK_WIDGET (spin_button), controller);
1090
1091 controller = gtk_event_controller_key_new ();
1092 g_signal_connect (controller, "key-released",
1093 G_CALLBACK (key_controller_key_released), spin_button);
1094 gtk_widget_add_controller (GTK_WIDGET (spin_button), controller);
1095 controller = gtk_event_controller_focus_new ();
1096 g_signal_connect (controller, "leave",
1097 G_CALLBACK (key_controller_focus_out), spin_button);
1098 gtk_widget_add_controller (GTK_WIDGET (spin_button), controller);
1099}
1100
1101static void
1102gtk_spin_button_finalize (GObject *object)
1103{
1104 GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
1105
1106 gtk_spin_button_unset_adjustment (spin_button);
1107
1108 gtk_editable_finish_delegate (GTK_EDITABLE (spin_button));
1109
1110 gtk_widget_unparent (widget: spin_button->entry);
1111 gtk_widget_unparent (widget: spin_button->up_button);
1112 gtk_widget_unparent (widget: spin_button->down_button);
1113
1114 G_OBJECT_CLASS (gtk_spin_button_parent_class)->finalize (object);
1115}
1116
1117static void
1118gtk_spin_button_dispose (GObject *object)
1119{
1120 gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (object));
1121
1122 G_OBJECT_CLASS (gtk_spin_button_parent_class)->dispose (object);
1123}
1124
1125static void
1126gtk_spin_button_realize (GtkWidget *widget)
1127{
1128 GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
1129 gboolean return_val;
1130 const char *text;
1131
1132 GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->realize (widget);
1133
1134 return_val = FALSE;
1135 g_signal_emit (instance: spin_button, signal_id: spinbutton_signals[OUTPUT], detail: 0, &return_val);
1136
1137 /* If output wasn't processed explicitly by the method connected to the
1138 * 'output' signal; and if we don't have any explicit 'text' set initially,
1139 * fallback to the default output.
1140 */
1141 text = gtk_editable_get_text (GTK_EDITABLE (spin_button->entry));
1142 if (!return_val && (spin_button->numeric || text == NULL || *text == '\0'))
1143 gtk_spin_button_default_output (spin_button);
1144}
1145
1146/* This is called when :value, :wrap, or the bounds of the adjustment change,
1147 * as the combination of those determines if our up|down_button are sensitive
1148 */
1149static void
1150update_buttons_sensitivity (GtkSpinButton *spin_button)
1151{
1152 const double lower = gtk_adjustment_get_lower (adjustment: spin_button->adjustment);
1153 const double upper = gtk_adjustment_get_upper (adjustment: spin_button->adjustment);
1154 const double value = gtk_adjustment_get_value (adjustment: spin_button->adjustment);
1155
1156 gtk_widget_set_sensitive (widget: spin_button->up_button,
1157 sensitive: spin_button->wrap || upper - value > EPSILON);
1158 gtk_widget_set_sensitive (widget: spin_button->down_button,
1159 sensitive: spin_button->wrap || value - lower > EPSILON);
1160}
1161
1162/* Callback used when the spin button's adjustment changes.
1163 * We need to reevaluate our size request & up|down_button sensitivity.
1164 */
1165static void
1166adjustment_changed_cb (GtkAdjustment *adjustment, gpointer data)
1167{
1168 GtkSpinButton *spin_button = GTK_SPIN_BUTTON (data);
1169
1170 spin_button->timer_step = gtk_adjustment_get_step_increment (adjustment);
1171
1172 update_buttons_sensitivity (spin_button);
1173
1174 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: spin_button),
1175 first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, gtk_adjustment_get_upper (adjustment),
1176 GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, gtk_adjustment_get_lower (adjustment),
1177 GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (adjustment),
1178 -1);
1179
1180 gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1181}
1182
1183static void
1184gtk_spin_button_unset_adjustment (GtkSpinButton *spin_button)
1185{
1186 if (spin_button->adjustment)
1187 {
1188 g_signal_handlers_disconnect_by_func (spin_button->adjustment,
1189 gtk_spin_button_value_changed,
1190 spin_button);
1191 g_signal_handlers_disconnect_by_func (spin_button->adjustment,
1192 adjustment_changed_cb,
1193 spin_button);
1194 g_clear_object (&spin_button->adjustment);
1195 }
1196}
1197
1198static void
1199gtk_spin_button_set_orientation (GtkSpinButton *spin,
1200 GtkOrientation orientation)
1201{
1202 GtkBoxLayout *layout_manager;
1203 GtkEditable *editable = GTK_EDITABLE (spin->entry);
1204
1205 if (gtk_orientable_get_orientation (GTK_ORIENTABLE (spin)) == orientation)
1206 return;
1207
1208 layout_manager = GTK_BOX_LAYOUT (ptr: gtk_widget_get_layout_manager (GTK_WIDGET (spin)));
1209 gtk_orientable_set_orientation (GTK_ORIENTABLE (layout_manager), orientation);
1210
1211 gtk_widget_update_orientation (GTK_WIDGET (spin), orientation);
1212
1213 /* change alignment if it's the default */
1214 if (orientation == GTK_ORIENTATION_VERTICAL &&
1215 gtk_editable_get_alignment (editable) == 0.0)
1216 gtk_editable_set_alignment (editable, xalign: 0.5);
1217 else if (orientation == GTK_ORIENTATION_HORIZONTAL &&
1218 gtk_editable_get_alignment (editable) == 0.5)
1219 gtk_editable_set_alignment (editable, xalign: 0.0);
1220
1221 if (orientation == GTK_ORIENTATION_HORIZONTAL)
1222 {
1223 /* Current orientation of the box is vertical! */
1224 gtk_widget_insert_after (widget: spin->up_button, GTK_WIDGET (spin), previous_sibling: spin->down_button);
1225 }
1226 else
1227 {
1228 /* Current orientation of the box is horizontal! */
1229 gtk_widget_insert_before (widget: spin->up_button, GTK_WIDGET (spin), next_sibling: spin->entry);
1230 }
1231
1232 g_object_notify (G_OBJECT (spin), property_name: "orientation");
1233}
1234
1235static char *
1236weed_out_neg_zero (char *str,
1237 int digits)
1238{
1239 if (str[0] == '-')
1240 {
1241 char neg_zero[8];
1242 g_snprintf (string: neg_zero, n: 8, format: "%0.*f", digits, -0.0);
1243 if (strcmp (s1: neg_zero, s2: str) == 0)
1244 memmove (dest: str, src: str + 1, n: strlen (s: str));
1245 }
1246 return str;
1247}
1248
1249static char *
1250gtk_spin_button_format_for_value (GtkSpinButton *spin_button,
1251 double value)
1252{
1253 char *buf = g_strdup_printf (format: "%0.*f", spin_button->digits, value);
1254
1255 return weed_out_neg_zero (str: buf, digits: spin_button->digits);
1256}
1257
1258static void
1259gtk_spin_button_update_width_chars (GtkSpinButton *spin_button)
1260{
1261 char *str;
1262 double value;
1263 int width_chars, c;
1264
1265 if (spin_button->width_chars == -1)
1266 {
1267 width_chars = 0;
1268
1269 value = gtk_adjustment_get_lower (adjustment: spin_button->adjustment);
1270 str = gtk_spin_button_format_for_value (spin_button, value);
1271 c = g_utf8_strlen (p: str, max: -1);
1272 g_free (mem: str);
1273
1274 width_chars = MAX (width_chars, c);
1275
1276 value = gtk_adjustment_get_upper (adjustment: spin_button->adjustment);
1277 str = gtk_spin_button_format_for_value (spin_button, value);
1278 c = g_utf8_strlen (p: str, max: -1);
1279 g_free (mem: str);
1280
1281 width_chars = MAX (width_chars, c);
1282
1283 width_chars = MIN (width_chars, 10);
1284 }
1285 else
1286 width_chars = spin_button->width_chars;
1287
1288 gtk_editable_set_width_chars (GTK_EDITABLE (spin_button->entry), n_chars: width_chars);
1289}
1290
1291static void
1292gtk_spin_button_state_flags_changed (GtkWidget *widget,
1293 GtkStateFlags previous_state)
1294{
1295 GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1296
1297 if (!gtk_widget_is_sensitive (widget))
1298 gtk_spin_button_stop_spinning (spin);
1299
1300 GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->state_flags_changed (widget, previous_state);
1301}
1302
1303static int
1304gtk_spin_button_timer (GtkSpinButton *spin_button)
1305{
1306 gboolean retval = FALSE;
1307
1308 if (spin_button->timer)
1309 {
1310 if (spin_button->click_child == spin_button->up_button)
1311 gtk_spin_button_real_spin (spin_button, step: spin_button->timer_step);
1312 else
1313 gtk_spin_button_real_spin (spin_button, step: -spin_button->timer_step);
1314
1315 if (spin_button->need_timer)
1316 {
1317 spin_button->need_timer = FALSE;
1318 spin_button->timer = g_timeout_add (TIMEOUT_REPEAT,
1319 function: (GSourceFunc) gtk_spin_button_timer,
1320 data: spin_button);
1321 gdk_source_set_static_name_by_id (tag: spin_button->timer, name: "[gtk] gtk_spin_button_timer");
1322 }
1323 else
1324 {
1325 if (spin_button->climb_rate > 0.0 && spin_button->timer_step
1326 < gtk_adjustment_get_page_increment (adjustment: spin_button->adjustment))
1327 {
1328 if (spin_button->timer_calls < MAX_TIMER_CALLS)
1329 spin_button->timer_calls++;
1330 else
1331 {
1332 spin_button->timer_calls = 0;
1333 spin_button->timer_step += spin_button->climb_rate;
1334 }
1335 }
1336 retval = TRUE;
1337 }
1338 }
1339
1340 return retval;
1341}
1342
1343static void
1344gtk_spin_button_value_changed (GtkAdjustment *adjustment,
1345 GtkSpinButton *spin_button)
1346{
1347 gboolean return_val;
1348
1349 g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
1350
1351 return_val = FALSE;
1352 g_signal_emit (instance: spin_button, signal_id: spinbutton_signals[OUTPUT], detail: 0, &return_val);
1353 if (return_val == FALSE)
1354 gtk_spin_button_default_output (spin_button);
1355
1356 g_signal_emit (instance: spin_button, signal_id: spinbutton_signals[VALUE_CHANGED], detail: 0);
1357
1358 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: spin_button),
1359 first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (adjustment),
1360 -1);
1361
1362 update_buttons_sensitivity (spin_button);
1363
1364 g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_VALUE]);
1365}
1366
1367static void
1368gtk_spin_button_real_change_value (GtkSpinButton *spin,
1369 GtkScrollType scroll)
1370{
1371 double old_value;
1372
1373 if (!gtk_editable_get_editable (GTK_EDITABLE (spin->entry)))
1374 {
1375 gtk_widget_error_bell (GTK_WIDGET (spin));
1376 return;
1377 }
1378
1379 /* When the key binding is activated, there may be an outstanding
1380 * value, so we first have to commit what is currently written in
1381 * the spin buttons text entry. See #106574
1382 */
1383 gtk_spin_button_update (spin_button: spin);
1384
1385 old_value = gtk_adjustment_get_value (adjustment: spin->adjustment);
1386
1387 switch (scroll)
1388 {
1389 case GTK_SCROLL_STEP_BACKWARD:
1390 case GTK_SCROLL_STEP_DOWN:
1391 case GTK_SCROLL_STEP_LEFT:
1392 gtk_spin_button_real_spin (spin_button: spin, step: -spin->timer_step);
1393
1394 if (spin->climb_rate > 0.0 &&
1395 spin->timer_step < gtk_adjustment_get_page_increment (adjustment: spin->adjustment))
1396 {
1397 if (spin->timer_calls < MAX_TIMER_CALLS)
1398 spin->timer_calls++;
1399 else
1400 {
1401 spin->timer_calls = 0;
1402 spin->timer_step += spin->climb_rate;
1403 }
1404 }
1405 break;
1406
1407 case GTK_SCROLL_STEP_FORWARD:
1408 case GTK_SCROLL_STEP_UP:
1409 case GTK_SCROLL_STEP_RIGHT:
1410 gtk_spin_button_real_spin (spin_button: spin, step: spin->timer_step);
1411
1412 if (spin->climb_rate > 0.0 &&
1413 spin->timer_step < gtk_adjustment_get_page_increment (adjustment: spin->adjustment))
1414 {
1415 if (spin->timer_calls < MAX_TIMER_CALLS)
1416 spin->timer_calls++;
1417 else
1418 {
1419 spin->timer_calls = 0;
1420 spin->timer_step += spin->climb_rate;
1421 }
1422 }
1423 break;
1424
1425 case GTK_SCROLL_PAGE_BACKWARD:
1426 case GTK_SCROLL_PAGE_DOWN:
1427 case GTK_SCROLL_PAGE_LEFT:
1428 gtk_spin_button_real_spin (spin_button: spin, step: -gtk_adjustment_get_page_increment (adjustment: spin->adjustment));
1429 break;
1430
1431 case GTK_SCROLL_PAGE_FORWARD:
1432 case GTK_SCROLL_PAGE_UP:
1433 case GTK_SCROLL_PAGE_RIGHT:
1434 gtk_spin_button_real_spin (spin_button: spin, step: gtk_adjustment_get_page_increment (adjustment: spin->adjustment));
1435 break;
1436
1437 case GTK_SCROLL_START:
1438 {
1439 double diff = gtk_adjustment_get_value (adjustment: spin->adjustment) - gtk_adjustment_get_lower (adjustment: spin->adjustment);
1440 if (diff > EPSILON)
1441 gtk_spin_button_real_spin (spin_button: spin, step: -diff);
1442 break;
1443 }
1444
1445 case GTK_SCROLL_END:
1446 {
1447 double diff = gtk_adjustment_get_upper (adjustment: spin->adjustment) - gtk_adjustment_get_value (adjustment: spin->adjustment);
1448 if (diff > EPSILON)
1449 gtk_spin_button_real_spin (spin_button: spin, step: diff);
1450 break;
1451 }
1452
1453 case GTK_SCROLL_NONE:
1454 case GTK_SCROLL_JUMP:
1455 default:
1456 g_warning ("Invalid scroll type %d for GtkSpinButton::change-value", scroll);
1457 break;
1458 }
1459
1460 gtk_spin_button_update (spin_button: spin);
1461
1462 if (gtk_adjustment_get_value (adjustment: spin->adjustment) == old_value)
1463 gtk_widget_error_bell (GTK_WIDGET (spin));
1464}
1465
1466static void
1467gtk_spin_button_snap (GtkSpinButton *spin_button,
1468 double val)
1469{
1470 double inc;
1471 double tmp;
1472
1473 inc = gtk_adjustment_get_step_increment (adjustment: spin_button->adjustment);
1474 if (inc == 0)
1475 return;
1476
1477 tmp = (val - gtk_adjustment_get_lower (adjustment: spin_button->adjustment)) / inc;
1478 if (tmp - floor (x: tmp) < ceil (x: tmp) - tmp)
1479 val = gtk_adjustment_get_lower (adjustment: spin_button->adjustment) + floor (x: tmp) * inc;
1480 else
1481 val = gtk_adjustment_get_lower (adjustment: spin_button->adjustment) + ceil (x: tmp) * inc;
1482
1483 gtk_spin_button_set_value (spin_button, value: val);
1484}
1485
1486static void
1487gtk_spin_button_activate (GtkText *entry,
1488 gpointer user_data)
1489{
1490 GtkSpinButton *spin_button = user_data;
1491
1492 if (gtk_editable_get_editable (GTK_EDITABLE (spin_button->entry)))
1493 gtk_spin_button_update (spin_button);
1494}
1495
1496static void
1497gtk_spin_button_insert_text (GtkEditable *editable,
1498 const char *new_text,
1499 int new_text_length,
1500 int *position)
1501{
1502 GtkSpinButton *spin = GTK_SPIN_BUTTON (editable);
1503
1504 if (spin->numeric)
1505 {
1506 struct lconv *lc;
1507 gboolean sign;
1508 int dotpos = -1;
1509 int i;
1510 guint32 pos_sign;
1511 guint32 neg_sign;
1512 int entry_length;
1513 const char *entry_text;
1514
1515 entry_text = gtk_editable_get_text (GTK_EDITABLE (spin->entry));
1516 entry_length = g_utf8_strlen (p: entry_text, max: -1);
1517
1518 lc = localeconv ();
1519
1520 if (*(lc->negative_sign))
1521 neg_sign = *(lc->negative_sign);
1522 else
1523 neg_sign = '-';
1524
1525 if (*(lc->positive_sign))
1526 pos_sign = *(lc->positive_sign);
1527 else
1528 pos_sign = '+';
1529
1530#ifdef G_OS_WIN32
1531 /* Workaround for bug caused by some Windows application messing
1532 * up the positive sign of the current locale, more specifically
1533 * HKEY_CURRENT_USER\Control Panel\International\sPositiveSign.
1534 * See bug #330743 and for instance
1535 * http://www.msnewsgroups.net/group/microsoft.public.dotnet.languages.csharp/topic36024.aspx
1536 *
1537 * I don't know if the positive sign always gets bogusly set to
1538 * a digit when the above Registry value is corrupted as
1539 * described. (In my test case, it got set to "8", and in the
1540 * bug report above it presumably was set to "0".) Probably it
1541 * might get set to almost anything? So how to distinguish a
1542 * bogus value from some correct one for some locale? That is
1543 * probably hard, but at least we should filter out the
1544 * digits...
1545 */
1546 if (pos_sign >= '0' && pos_sign <= '9')
1547 pos_sign = '+';
1548#endif
1549
1550 for (sign = 0, i = 0; i<entry_length; i++)
1551 if ((entry_text[i] == neg_sign) ||
1552 (entry_text[i] == pos_sign))
1553 {
1554 sign = 1;
1555 break;
1556 }
1557
1558 if (sign && !(*position))
1559 return;
1560
1561 for (dotpos = -1, i = 0; i<entry_length; i++)
1562 if (entry_text[i] == *(lc->decimal_point))
1563 {
1564 dotpos = i;
1565 break;
1566 }
1567
1568 if (dotpos > -1 && *position > dotpos &&
1569 (int)spin->digits - entry_length
1570 + dotpos - new_text_length + 1 < 0)
1571 return;
1572
1573 for (i = 0; i < new_text_length; i++)
1574 {
1575 if (new_text[i] == neg_sign || new_text[i] == pos_sign)
1576 {
1577 if (sign || (*position) || i)
1578 return;
1579 sign = TRUE;
1580 }
1581 else if (new_text[i] == *(lc->decimal_point))
1582 {
1583 if (!spin->digits || dotpos > -1 ||
1584 (new_text_length - 1 - i + entry_length - *position > (int)spin->digits))
1585 return;
1586 dotpos = *position + i;
1587 }
1588 else if (new_text[i] < 0x30 || new_text[i] > 0x39)
1589 return;
1590 }
1591 }
1592
1593 gtk_editable_insert_text (GTK_EDITABLE (spin->entry),
1594 text: new_text, length: new_text_length, position);
1595}
1596
1597static void
1598gtk_spin_button_real_spin (GtkSpinButton *spin_button,
1599 double increment)
1600{
1601 GtkAdjustment *adjustment;
1602 double new_value = 0.0;
1603 gboolean wrapped = FALSE;
1604
1605 adjustment = spin_button->adjustment;
1606
1607 new_value = gtk_adjustment_get_value (adjustment) + increment;
1608
1609 if (increment > 0)
1610 {
1611 if (spin_button->wrap)
1612 {
1613 if (fabs (x: gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_upper (adjustment)) < EPSILON)
1614 {
1615 new_value = gtk_adjustment_get_lower (adjustment);
1616 wrapped = TRUE;
1617 }
1618 else if (new_value > gtk_adjustment_get_upper (adjustment))
1619 new_value = gtk_adjustment_get_upper (adjustment);
1620 }
1621 else
1622 new_value = MIN (new_value, gtk_adjustment_get_upper (adjustment));
1623 }
1624 else if (increment < 0)
1625 {
1626 if (spin_button->wrap)
1627 {
1628 if (fabs (x: gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_lower (adjustment)) < EPSILON)
1629 {
1630 new_value = gtk_adjustment_get_upper (adjustment);
1631 wrapped = TRUE;
1632 }
1633 else if (new_value < gtk_adjustment_get_lower (adjustment))
1634 new_value = gtk_adjustment_get_lower (adjustment);
1635 }
1636 else
1637 new_value = MAX (new_value, gtk_adjustment_get_lower (adjustment));
1638 }
1639
1640 if (fabs (x: new_value - gtk_adjustment_get_value (adjustment)) > EPSILON)
1641 gtk_adjustment_set_value (adjustment, value: new_value);
1642
1643 if (wrapped)
1644 g_signal_emit (instance: spin_button, signal_id: spinbutton_signals[WRAPPED], detail: 0);
1645}
1646
1647static int
1648gtk_spin_button_default_input (GtkSpinButton *spin_button,
1649 double *new_val)
1650{
1651 char *err = NULL;
1652 const char *text = gtk_editable_get_text (GTK_EDITABLE (spin_button->entry));
1653
1654 *new_val = g_strtod (nptr: text, endptr: &err);
1655 if (*err)
1656 {
1657 /* try to convert with local digits */
1658 gint64 val = 0;
1659 int sign = 1;
1660 const char *p;
1661
1662 for (p = text; *p; p = g_utf8_next_char (p))
1663 {
1664 gunichar ch = g_utf8_get_char (p);
1665
1666 if (p == text && ch == '-')
1667 {
1668 sign = -1;
1669 continue;
1670 }
1671
1672 if (!g_unichar_isdigit (c: ch))
1673 break;
1674
1675 val = val * 10 + g_unichar_digit_value (c: ch);
1676 }
1677
1678 if (*p)
1679 return GTK_INPUT_ERROR;
1680
1681 *new_val = sign * val;
1682 }
1683
1684 return FALSE;
1685}
1686
1687static void
1688gtk_spin_button_default_output (GtkSpinButton *spin_button)
1689{
1690 char *buf = gtk_spin_button_format_for_value (spin_button,
1691 value: gtk_adjustment_get_value (adjustment: spin_button->adjustment));
1692
1693 if (strcmp (s1: buf, s2: gtk_editable_get_text (GTK_EDITABLE (spin_button->entry))))
1694 gtk_editable_set_text (GTK_EDITABLE (spin_button->entry), text: buf);
1695
1696 g_free (mem: buf);
1697}
1698
1699
1700/***********************************************************
1701 ***********************************************************
1702 *** Public interface ***
1703 ***********************************************************
1704 ***********************************************************/
1705
1706/**
1707 * gtk_spin_button_configure:
1708 * @spin_button: a `GtkSpinButton`
1709 * @adjustment: (nullable): a `GtkAdjustment` to replace the spin button’s
1710 * existing adjustment, or %NULL to leave its current adjustment unchanged
1711 * @climb_rate: the new climb rate
1712 * @digits: the number of decimal places to display in the spin button
1713 *
1714 * Changes the properties of an existing spin button.
1715 *
1716 * The adjustment, climb rate, and number of decimal places
1717 * are updated accordingly.
1718 */
1719void
1720gtk_spin_button_configure (GtkSpinButton *spin_button,
1721 GtkAdjustment *adjustment,
1722 double climb_rate,
1723 guint digits)
1724{
1725 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
1726
1727 if (!adjustment)
1728 adjustment = spin_button->adjustment;
1729
1730 g_object_freeze_notify (G_OBJECT (spin_button));
1731
1732 if (spin_button->adjustment != adjustment)
1733 {
1734 gtk_spin_button_unset_adjustment (spin_button);
1735
1736 spin_button->adjustment = adjustment;
1737 g_object_ref_sink (adjustment);
1738 g_signal_connect (adjustment, "value-changed",
1739 G_CALLBACK (gtk_spin_button_value_changed),
1740 spin_button);
1741 g_signal_connect (adjustment, "changed",
1742 G_CALLBACK (adjustment_changed_cb),
1743 spin_button);
1744 spin_button->timer_step = gtk_adjustment_get_step_increment (adjustment: spin_button->adjustment);
1745
1746 g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_ADJUSTMENT]);
1747 gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1748 }
1749
1750 if (spin_button->digits != digits)
1751 {
1752 spin_button->digits = digits;
1753 g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_DIGITS]);
1754 }
1755
1756 if (spin_button->climb_rate != climb_rate)
1757 {
1758 spin_button->climb_rate = climb_rate;
1759 g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_CLIMB_RATE]);
1760 }
1761
1762 gtk_spin_button_update_width_chars (spin_button);
1763
1764 g_object_thaw_notify (G_OBJECT (spin_button));
1765
1766 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: spin_button),
1767 first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, gtk_adjustment_get_upper (adjustment),
1768 GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, gtk_adjustment_get_lower (adjustment),
1769 GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (adjustment),
1770 -1);
1771
1772 gtk_spin_button_value_changed (adjustment, spin_button);
1773}
1774
1775/**
1776 * gtk_spin_button_new:
1777 * @adjustment: (nullable): the `GtkAdjustment` that this spin button should use
1778 * @climb_rate: specifies by how much the rate of change in the value will
1779 * accelerate if you continue to hold down an up/down button or arrow key
1780 * @digits: the number of decimal places to display
1781 *
1782 * Creates a new `GtkSpinButton`.
1783 *
1784 * Returns: The new `GtkSpinButton`
1785 */
1786GtkWidget *
1787gtk_spin_button_new (GtkAdjustment *adjustment,
1788 double climb_rate,
1789 guint digits)
1790{
1791 GtkSpinButton *spin;
1792
1793 if (adjustment)
1794 g_return_val_if_fail (GTK_IS_ADJUSTMENT (adjustment), NULL);
1795
1796 spin = g_object_new (GTK_TYPE_SPIN_BUTTON, NULL);
1797
1798 gtk_spin_button_configure (spin_button: spin, adjustment, climb_rate, digits);
1799
1800 return GTK_WIDGET (spin);
1801}
1802
1803/**
1804 * gtk_spin_button_new_with_range:
1805 * @min: Minimum allowable value
1806 * @max: Maximum allowable value
1807 * @step: Increment added or subtracted by spinning the widget
1808 *
1809 * Creates a new `GtkSpinButton` with the given properties.
1810 *
1811 * This is a convenience constructor that allows creation
1812 * of a numeric `GtkSpinButton` without manually creating
1813 * an adjustment. The value is initially set to the minimum
1814 * value and a page increment of 10 * @step is the default.
1815 * The precision of the spin button is equivalent to the
1816 * precision of @step.
1817 *
1818 * Note that the way in which the precision is derived works
1819 * best if @step is a power of ten. If the resulting precision
1820 * is not suitable for your needs, use
1821 * [method@Gtk.SpinButton.set_digits] to correct it.
1822 *
1823 * Returns: The new `GtkSpinButton`
1824 */
1825GtkWidget *
1826gtk_spin_button_new_with_range (double min,
1827 double max,
1828 double step)
1829{
1830 GtkAdjustment *adjustment;
1831 GtkSpinButton *spin;
1832 int digits;
1833
1834 g_return_val_if_fail (min <= max, NULL);
1835 g_return_val_if_fail (step != 0.0, NULL);
1836
1837 spin = g_object_new (GTK_TYPE_SPIN_BUTTON, NULL);
1838
1839 adjustment = gtk_adjustment_new (value: min, lower: min, upper: max, step_increment: step, page_increment: 10 * step, page_size: 0);
1840
1841 if (fabs (x: step) >= 1.0 || step == 0.0)
1842 digits = 0;
1843 else {
1844 digits = abs (x: (int) floor (x: log10 (x: fabs (x: step))));
1845 if (digits > MAX_DIGITS)
1846 digits = MAX_DIGITS;
1847 }
1848
1849 gtk_spin_button_configure (spin_button: spin, adjustment, climb_rate: step, digits);
1850
1851 gtk_spin_button_set_numeric (spin_button: spin, TRUE);
1852
1853 return GTK_WIDGET (spin);
1854}
1855
1856/**
1857 * gtk_spin_button_set_adjustment: (attributes org.gtk.Method.set_property=adjustment)
1858 * @spin_button: a `GtkSpinButton`
1859 * @adjustment: a `GtkAdjustment` to replace the existing adjustment
1860 *
1861 * Replaces the `GtkAdjustment` associated with @spin_button.
1862 */
1863void
1864gtk_spin_button_set_adjustment (GtkSpinButton *spin_button,
1865 GtkAdjustment *adjustment)
1866{
1867 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
1868
1869 if (!adjustment)
1870 adjustment = gtk_adjustment_new (value: 0.0, lower: 0.0, upper: 0.0, step_increment: 0.0, page_increment: 0.0, page_size: 0.0);
1871
1872 gtk_spin_button_configure (spin_button,
1873 adjustment,
1874 climb_rate: spin_button->climb_rate,
1875 digits: spin_button->digits);
1876}
1877
1878/**
1879 * gtk_spin_button_get_adjustment: (attributes org.gtk.Method.get_property=adjustment)
1880 * @spin_button: a `GtkSpinButton`
1881 *
1882 * Get the adjustment associated with a `GtkSpinButton`.
1883 *
1884 * Returns: (transfer none): the `GtkAdjustment` of @spin_button
1885 */
1886GtkAdjustment *
1887gtk_spin_button_get_adjustment (GtkSpinButton *spin_button)
1888{
1889 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), NULL);
1890
1891 return spin_button->adjustment;
1892}
1893
1894/**
1895 * gtk_spin_button_set_digits: (attributes org.gtk.Method.set_property=digits)
1896 * @spin_button: a `GtkSpinButton`
1897 * @digits: the number of digits after the decimal point to be
1898 * displayed for the spin button’s value
1899 *
1900 * Set the precision to be displayed by @spin_button.
1901 *
1902 * Up to 20 digit precision is allowed.
1903 */
1904void
1905gtk_spin_button_set_digits (GtkSpinButton *spin_button,
1906 guint digits)
1907{
1908 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
1909
1910 if (spin_button->digits != digits)
1911 {
1912 spin_button->digits = digits;
1913 gtk_spin_button_value_changed (adjustment: spin_button->adjustment, spin_button);
1914 g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_DIGITS]);
1915
1916 /* since lower/upper may have changed */
1917 gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1918 }
1919}
1920
1921/**
1922 * gtk_spin_button_get_digits: (attributes org.gtk.Method.get_property=digits)
1923 * @spin_button: a `GtkSpinButton`
1924 *
1925 * Fetches the precision of @spin_button.
1926 *
1927 * Returns: the current precision
1928 **/
1929guint
1930gtk_spin_button_get_digits (GtkSpinButton *spin_button)
1931{
1932 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0);
1933
1934 return spin_button->digits;
1935}
1936
1937/**
1938 * gtk_spin_button_set_increments:
1939 * @spin_button: a `GtkSpinButton`
1940 * @step: increment applied for a button 1 press.
1941 * @page: increment applied for a button 2 press.
1942 *
1943 * Sets the step and page increments for spin_button.
1944 *
1945 * This affects how quickly the value changes when
1946 * the spin button’s arrows are activated.
1947 */
1948void
1949gtk_spin_button_set_increments (GtkSpinButton *spin_button,
1950 double step,
1951 double page)
1952{
1953 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
1954
1955 gtk_adjustment_configure (adjustment: spin_button->adjustment,
1956 value: gtk_adjustment_get_value (adjustment: spin_button->adjustment),
1957 lower: gtk_adjustment_get_lower (adjustment: spin_button->adjustment),
1958 upper: gtk_adjustment_get_upper (adjustment: spin_button->adjustment),
1959 step_increment: step,
1960 page_increment: page,
1961 page_size: gtk_adjustment_get_page_size (adjustment: spin_button->adjustment));
1962}
1963
1964/**
1965 * gtk_spin_button_get_increments:
1966 * @spin_button: a `GtkSpinButton`
1967 * @step: (out) (optional): location to store step increment
1968 * @page: (out) (optional): location to store page increment
1969 *
1970 * Gets the current step and page the increments
1971 * used by @spin_button.
1972 *
1973 * See [method@Gtk.SpinButton.set_increments].
1974 */
1975void
1976gtk_spin_button_get_increments (GtkSpinButton *spin_button,
1977 double *step,
1978 double *page)
1979{
1980 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
1981
1982 if (step)
1983 *step = gtk_adjustment_get_step_increment (adjustment: spin_button->adjustment);
1984 if (page)
1985 *page = gtk_adjustment_get_page_increment (adjustment: spin_button->adjustment);
1986}
1987
1988/**
1989 * gtk_spin_button_set_range:
1990 * @spin_button: a `GtkSpinButton`
1991 * @min: minimum allowable value
1992 * @max: maximum allowable value
1993 *
1994 * Sets the minimum and maximum allowable values for @spin_button.
1995 *
1996 * If the current value is outside this range, it will be adjusted
1997 * to fit within the range, otherwise it will remain unchanged.
1998 */
1999void
2000gtk_spin_button_set_range (GtkSpinButton *spin_button,
2001 double min,
2002 double max)
2003{
2004 GtkAdjustment *adjustment;
2005
2006 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2007
2008 adjustment = spin_button->adjustment;
2009
2010 gtk_adjustment_configure (adjustment,
2011 CLAMP (gtk_adjustment_get_value (adjustment), min, max),
2012 lower: min,
2013 upper: max,
2014 step_increment: gtk_adjustment_get_step_increment (adjustment),
2015 page_increment: gtk_adjustment_get_page_increment (adjustment),
2016 page_size: gtk_adjustment_get_page_size (adjustment));
2017}
2018
2019/**
2020 * gtk_spin_button_get_range:
2021 * @spin_button: a `GtkSpinButton`
2022 * @min: (out) (optional): location to store minimum allowed value
2023 * @max: (out) (optional): location to store maximum allowed value
2024 *
2025 * Gets the range allowed for @spin_button.
2026 *
2027 * See [method@Gtk.SpinButton.set_range].
2028 */
2029void
2030gtk_spin_button_get_range (GtkSpinButton *spin_button,
2031 double *min,
2032 double *max)
2033{
2034 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2035
2036 if (min)
2037 *min = gtk_adjustment_get_lower (adjustment: spin_button->adjustment);
2038 if (max)
2039 *max = gtk_adjustment_get_upper (adjustment: spin_button->adjustment);
2040}
2041
2042/**
2043 * gtk_spin_button_get_value: (attributes org.gtk.Method.get_property=value)
2044 * @spin_button: a `GtkSpinButton`
2045 *
2046 * Get the value in the @spin_button.
2047 *
2048 * Returns: the value of @spin_button
2049 */
2050double
2051gtk_spin_button_get_value (GtkSpinButton *spin_button)
2052{
2053 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0.0);
2054
2055 return gtk_adjustment_get_value (adjustment: spin_button->adjustment);
2056}
2057
2058/**
2059 * gtk_spin_button_get_value_as_int:
2060 * @spin_button: a `GtkSpinButton`
2061 *
2062 * Get the value @spin_button represented as an integer.
2063 *
2064 * Returns: the value of @spin_button
2065 */
2066int
2067gtk_spin_button_get_value_as_int (GtkSpinButton *spin_button)
2068{
2069 double val;
2070
2071 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0);
2072
2073 val = gtk_adjustment_get_value (adjustment: spin_button->adjustment);
2074 if (val - floor (x: val) < ceil (x: val) - val)
2075 return floor (x: val);
2076 else
2077 return ceil (x: val);
2078}
2079
2080/**
2081 * gtk_spin_button_set_value: (attributes org.gtk.Method.set_property=value)
2082 * @spin_button: a `GtkSpinButton`
2083 * @value: the new value
2084 *
2085 * Sets the value of @spin_button.
2086 */
2087void
2088gtk_spin_button_set_value (GtkSpinButton *spin_button,
2089 double value)
2090{
2091 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2092
2093 if (fabs (x: value - gtk_adjustment_get_value (adjustment: spin_button->adjustment)) > EPSILON)
2094 gtk_adjustment_set_value (adjustment: spin_button->adjustment, value);
2095 else
2096 {
2097 int return_val = FALSE;
2098 g_signal_emit (instance: spin_button, signal_id: spinbutton_signals[OUTPUT], detail: 0, &return_val);
2099 if (!return_val)
2100 gtk_spin_button_default_output (spin_button);
2101 }
2102}
2103
2104/**
2105 * gtk_spin_button_set_update_policy: (attributes org.gtk.Method.set_property=update-policy)
2106 * @spin_button: a `GtkSpinButton`
2107 * @policy: a `GtkSpinButtonUpdatePolicy` value
2108 *
2109 * Sets the update behavior of a spin button.
2110 *
2111 * This determines whether the spin button is always
2112 * updated or only when a valid value is set.
2113 */
2114void
2115gtk_spin_button_set_update_policy (GtkSpinButton *spin_button,
2116 GtkSpinButtonUpdatePolicy policy)
2117{
2118 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2119
2120 if (spin_button->update_policy != policy)
2121 {
2122 spin_button->update_policy = policy;
2123 g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_UPDATE_POLICY]);
2124 }
2125}
2126
2127/**
2128 * gtk_spin_button_get_update_policy: (attributes org.gtk.Method.get_property=update-policy)
2129 * @spin_button: a `GtkSpinButton`
2130 *
2131 * Gets the update behavior of a spin button.
2132 *
2133 * See [method@Gtk.SpinButton.set_update_policy].
2134 *
2135 * Returns: the current update policy
2136 */
2137GtkSpinButtonUpdatePolicy
2138gtk_spin_button_get_update_policy (GtkSpinButton *spin_button)
2139{
2140 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), GTK_UPDATE_ALWAYS);
2141
2142 return spin_button->update_policy;
2143}
2144
2145/**
2146 * gtk_spin_button_set_numeric: (attributes org.gtk.Method.set_property=numeric)
2147 * @spin_button: a `GtkSpinButton`
2148 * @numeric: flag indicating if only numeric entry is allowed
2149 *
2150 * Sets the flag that determines if non-numeric text can be typed
2151 * into the spin button.
2152 */
2153void
2154gtk_spin_button_set_numeric (GtkSpinButton *spin_button,
2155 gboolean numeric)
2156{
2157 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2158
2159 numeric = numeric != FALSE;
2160
2161 if (spin_button->numeric != numeric)
2162 {
2163 spin_button->numeric = numeric;
2164 g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_NUMERIC]);
2165 }
2166}
2167
2168/**
2169 * gtk_spin_button_get_numeric: (attributes org.gtk.Method.get_property=numeric)
2170 * @spin_button: a `GtkSpinButton`
2171 *
2172 * Returns whether non-numeric text can be typed into the spin button.
2173 *
2174 * Returns: %TRUE if only numeric text can be entered
2175 */
2176gboolean
2177gtk_spin_button_get_numeric (GtkSpinButton *spin_button)
2178{
2179 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2180
2181 return spin_button->numeric;
2182}
2183
2184/**
2185 * gtk_spin_button_set_wrap: (attributes org.gtk.Method.set_property=wrap)
2186 * @spin_button: a `GtkSpinButton`
2187 * @wrap: a flag indicating if wrapping behavior is performed
2188 *
2189 * Sets the flag that determines if a spin button value wraps
2190 * around to the opposite limit when the upper or lower limit
2191 * of the range is exceeded.
2192 */
2193void
2194gtk_spin_button_set_wrap (GtkSpinButton *spin_button,
2195 gboolean wrap)
2196{
2197 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2198
2199 wrap = wrap != FALSE;
2200
2201 if (spin_button->wrap != wrap)
2202 {
2203 spin_button->wrap = wrap;
2204 g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_WRAP]);
2205
2206 update_buttons_sensitivity (spin_button);
2207 }
2208}
2209
2210/**
2211 * gtk_spin_button_get_wrap: (attributes org.gtk.Method.get_property=wrap)
2212 * @spin_button: a `GtkSpinButton`
2213 *
2214 * Returns whether the spin button’s value wraps around to the
2215 * opposite limit when the upper or lower limit of the range is
2216 * exceeded.
2217 *
2218 * Returns: %TRUE if the spin button wraps around
2219 */
2220gboolean
2221gtk_spin_button_get_wrap (GtkSpinButton *spin_button)
2222{
2223 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2224
2225 return spin_button->wrap;
2226}
2227
2228/**
2229 * gtk_spin_button_set_snap_to_ticks: (attributes org.gtk.Method.set_property=snap-to-ticks)
2230 * @spin_button: a `GtkSpinButton`
2231 * @snap_to_ticks: a flag indicating if invalid values should be corrected
2232 *
2233 * Sets the policy as to whether values are corrected to the
2234 * nearest step increment when a spin button is activated after
2235 * providing an invalid value.
2236 */
2237void
2238gtk_spin_button_set_snap_to_ticks (GtkSpinButton *spin_button,
2239 gboolean snap_to_ticks)
2240{
2241 guint new_val;
2242
2243 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2244
2245 new_val = (snap_to_ticks != 0);
2246
2247 if (new_val != spin_button->snap_to_ticks)
2248 {
2249 spin_button->snap_to_ticks = new_val;
2250 if (new_val && gtk_editable_get_editable (GTK_EDITABLE (spin_button->entry)))
2251 gtk_spin_button_update (spin_button);
2252
2253 g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_SNAP_TO_TICKS]);
2254 }
2255}
2256
2257/**
2258 * gtk_spin_button_get_snap_to_ticks: (attributes org.gtk.Method.get_property=snap-to-ticks)
2259 * @spin_button: a `GtkSpinButton`
2260 *
2261 * Returns whether the values are corrected to the nearest step.
2262 *
2263 * Returns: %TRUE if values are snapped to the nearest step
2264 */
2265gboolean
2266gtk_spin_button_get_snap_to_ticks (GtkSpinButton *spin_button)
2267{
2268 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2269
2270 return spin_button->snap_to_ticks;
2271}
2272
2273/**
2274 * gtk_spin_button_set_climb_rate: (attributes org.gtk.Method.set_property=climb-rate)
2275 * @spin_button: a `GtkSpinButton`
2276 * @climb_rate: the rate of acceleration, must be >= 0
2277 *
2278 * Sets the acceleration rate for repeated changes when you
2279 * hold down a button or key.
2280 */
2281void
2282gtk_spin_button_set_climb_rate (GtkSpinButton *spin_button,
2283 double climb_rate)
2284{
2285 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2286 g_return_if_fail (0.0 <= climb_rate);
2287
2288 if (spin_button->climb_rate == climb_rate)
2289 return;
2290
2291 spin_button->climb_rate = climb_rate;
2292
2293 g_object_notify_by_pspec (G_OBJECT (spin_button), pspec: spinbutton_props[PROP_CLIMB_RATE]);
2294}
2295
2296/**
2297 * gtk_spin_button_get_climb_rate: (attributes org.gtk.Method.get_property=climb-rate)
2298 * @spin_button: a `GtkSpinButton`
2299 *
2300 * Returns the acceleration rate for repeated changes.
2301 *
2302 * Returns: the acceleration rate
2303 */
2304double
2305gtk_spin_button_get_climb_rate (GtkSpinButton *spin_button)
2306{
2307 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0.0);
2308
2309 return spin_button->climb_rate;
2310}
2311
2312/**
2313 * gtk_spin_button_spin:
2314 * @spin_button: a `GtkSpinButton`
2315 * @direction: a `GtkSpinType` indicating the direction to spin
2316 * @increment: step increment to apply in the specified direction
2317 *
2318 * Increment or decrement a spin button’s value in a specified
2319 * direction by a specified amount.
2320 */
2321void
2322gtk_spin_button_spin (GtkSpinButton *spin_button,
2323 GtkSpinType direction,
2324 double increment)
2325{
2326 GtkAdjustment *adjustment;
2327 double diff;
2328
2329 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2330
2331 adjustment = spin_button->adjustment;
2332
2333 /* for compatibility with the 1.0.x version of this function */
2334 if (increment != 0 && increment != gtk_adjustment_get_step_increment (adjustment) &&
2335 (direction == GTK_SPIN_STEP_FORWARD ||
2336 direction == GTK_SPIN_STEP_BACKWARD))
2337 {
2338 if (direction == GTK_SPIN_STEP_BACKWARD && increment > 0)
2339 increment = -increment;
2340 direction = GTK_SPIN_USER_DEFINED;
2341 }
2342
2343 switch (direction)
2344 {
2345 case GTK_SPIN_STEP_FORWARD:
2346
2347 gtk_spin_button_real_spin (spin_button, increment: gtk_adjustment_get_step_increment (adjustment));
2348 break;
2349
2350 case GTK_SPIN_STEP_BACKWARD:
2351
2352 gtk_spin_button_real_spin (spin_button, increment: -gtk_adjustment_get_step_increment (adjustment));
2353 break;
2354
2355 case GTK_SPIN_PAGE_FORWARD:
2356
2357 gtk_spin_button_real_spin (spin_button, increment: gtk_adjustment_get_page_increment (adjustment));
2358 break;
2359
2360 case GTK_SPIN_PAGE_BACKWARD:
2361
2362 gtk_spin_button_real_spin (spin_button, increment: -gtk_adjustment_get_page_increment (adjustment));
2363 break;
2364
2365 case GTK_SPIN_HOME:
2366
2367 diff = gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_lower (adjustment);
2368 if (diff > EPSILON)
2369 gtk_spin_button_real_spin (spin_button, increment: -diff);
2370 break;
2371
2372 case GTK_SPIN_END:
2373
2374 diff = gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_value (adjustment);
2375 if (diff > EPSILON)
2376 gtk_spin_button_real_spin (spin_button, increment: diff);
2377 break;
2378
2379 case GTK_SPIN_USER_DEFINED:
2380
2381 if (increment != 0)
2382 gtk_spin_button_real_spin (spin_button, increment);
2383 break;
2384
2385 default:
2386 break;
2387 }
2388}
2389
2390/**
2391 * gtk_spin_button_update:
2392 * @spin_button: a `GtkSpinButton`
2393 *
2394 * Manually force an update of the spin button.
2395 */
2396void
2397gtk_spin_button_update (GtkSpinButton *spin_button)
2398{
2399 double val;
2400 int error = 0;
2401 int return_val;
2402
2403 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2404
2405 return_val = FALSE;
2406 g_signal_emit (instance: spin_button, signal_id: spinbutton_signals[INPUT], detail: 0, &val, &return_val);
2407 if (return_val == FALSE)
2408 {
2409 return_val = gtk_spin_button_default_input (spin_button, new_val: &val);
2410 error = (return_val == GTK_INPUT_ERROR);
2411 }
2412 else if (return_val == GTK_INPUT_ERROR)
2413 error = 1;
2414
2415 if (spin_button->update_policy == GTK_UPDATE_ALWAYS)
2416 {
2417 if (val < gtk_adjustment_get_lower (adjustment: spin_button->adjustment))
2418 val = gtk_adjustment_get_lower (adjustment: spin_button->adjustment);
2419 else if (val > gtk_adjustment_get_upper (adjustment: spin_button->adjustment))
2420 val = gtk_adjustment_get_upper (adjustment: spin_button->adjustment);
2421 }
2422 else if ((spin_button->update_policy == GTK_UPDATE_IF_VALID) &&
2423 (error ||
2424 val < gtk_adjustment_get_lower (adjustment: spin_button->adjustment) ||
2425 val > gtk_adjustment_get_upper (adjustment: spin_button->adjustment)))
2426 {
2427 gtk_spin_button_value_changed (adjustment: spin_button->adjustment, spin_button);
2428 return;
2429 }
2430
2431 if (spin_button->snap_to_ticks)
2432 gtk_spin_button_snap (spin_button, val);
2433 else
2434 gtk_spin_button_set_value (spin_button, value: val);
2435}
2436
2437GtkText *
2438gtk_spin_button_get_text_widget (GtkSpinButton *spin_button)
2439{
2440 return GTK_TEXT (spin_button->entry);
2441}
2442
2443

source code of gtk/gtk/gtkspinbutton.c