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 <stdio.h>
33 #include <stdlib.h>
34 #include <math.h>
35 #include <string.h>
36 #include <locale.h>
37
38 #include "gtkadjustment.h"
39 #include "gtkbindings.h"
40 #include "gtkboxgadgetprivate.h"
41 #include "gtkcssgadgetprivate.h"
42 #include "gtkcsscustomgadgetprivate.h"
43 #include "gtkentryprivate.h"
44 #include "gtkiconhelperprivate.h"
45 #include "gtkicontheme.h"
46 #include "gtkintl.h"
47 #include "gtkmarshalers.h"
48 #include "gtkorientable.h"
49 #include "gtkorientableprivate.h"
50 #include "gtkprivate.h"
51 #include "gtksettings.h"
52 #include "gtktypebuiltins.h"
53 #include "gtkwidgetpath.h"
54 #include "gtkwidgetprivate.h"
55 #include "gtkstylecontextprivate.h"
56 #include "gtkcssstylepropertyprivate.h"
57
58 #include "a11y/gtkspinbuttonaccessible.h"
59
60 #define MIN_SPIN_BUTTON_WIDTH 30
61 #define MAX_TIMER_CALLS 5
62 #define EPSILON 1e-10
63 #define MAX_DIGITS 20
64 #define TIMEOUT_INITIAL 500
65 #define TIMEOUT_REPEAT 50
66
67 /**
68 * SECTION:gtkspinbutton
69 * @Title: GtkSpinButton
70 * @Short_description: Retrieve an integer or floating-point number from
71 * the user
72 * @See_also: #GtkEntry
73 *
74 * A #GtkSpinButton is an ideal way to allow the user to set the value of
75 * some attribute. Rather than having to directly type a number into a
76 * #GtkEntry, GtkSpinButton allows the user to click on one of two arrows
77 * to increment or decrement the displayed value. A value can still be
78 * typed in, with the bonus that it can be checked to ensure it is in a
79 * given range.
80 *
81 * The main properties of a GtkSpinButton are through an adjustment.
82 * See the #GtkAdjustment section for more details about an adjustment's
83 * properties. Note that GtkSpinButton will by default make its entry
84 * large enough to accomodate the lower and upper bounds of the adjustment,
85 * which can lead to surprising results. Best practice is to set both
86 * the #GtkEntry:width-chars and #GtkEntry:max-width-chars poperties
87 * to the desired number of characters to display in the entry.
88 *
89 * # CSS nodes
90 *
91 * |[<!-- language="plain" -->
92 * spinbutton.horizontal
93 * ├── undershoot.left
94 * ├── undershoot.right
95 * ├── entry
96 * │ ╰── ...
97 * ├── button.down
98 * ╰── button.up
99 * ]|
100 *
101 * |[<!-- language="plain" -->
102 * spinbutton.vertical
103 * ├── undershoot.left
104 * ├── undershoot.right
105 * ├── button.up
106 * ├── entry
107 * │ ╰── ...
108 * ╰── button.down
109 * ]|
110 *
111 * GtkSpinButtons main CSS node has the name spinbutton. It creates subnodes
112 * for the entry and the two buttons, with these names. The button nodes have
113 * the style classes .up and .down. The GtkEntry subnodes (if present) are put
114 * below the entry node. The orientation of the spin button is reflected in
115 * the .vertical or .horizontal style class on the main node.
116 *
117 * ## Using a GtkSpinButton to get an integer
118 *
119 * |[<!-- language="C" -->
120 * // Provides a function to retrieve an integer value from a GtkSpinButton
121 * // and creates a spin button to model percentage values.
122 *
123 * gint
124 * grab_int_value (GtkSpinButton *button,
125 * gpointer user_data)
126 * {
127 * return gtk_spin_button_get_value_as_int (button);
128 * }
129 *
130 * void
131 * create_integer_spin_button (void)
132 * {
133 *
134 * GtkWidget *window, *button;
135 * GtkAdjustment *adjustment;
136 *
137 * adjustment = gtk_adjustment_new (50.0, 0.0, 100.0, 1.0, 5.0, 0.0);
138 *
139 * window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
140 * gtk_container_set_border_width (GTK_CONTAINER (window), 5);
141 *
142 * // creates the spinbutton, with no decimal places
143 * button = gtk_spin_button_new (adjustment, 1.0, 0);
144 * gtk_container_add (GTK_CONTAINER (window), button);
145 *
146 * gtk_widget_show_all (window);
147 * }
148 * ]|
149 *
150 * ## Using a GtkSpinButton to get a floating point value
151 *
152 * |[<!-- language="C" -->
153 * // Provides a function to retrieve a floating point value from a
154 * // GtkSpinButton, and creates a high precision spin button.
155 *
156 * gfloat
157 * grab_float_value (GtkSpinButton *button,
158 * gpointer user_data)
159 * {
160 * return gtk_spin_button_get_value (button);
161 * }
162 *
163 * void
164 * create_floating_spin_button (void)
165 * {
166 * GtkWidget *window, *button;
167 * GtkAdjustment *adjustment;
168 *
169 * adjustment = gtk_adjustment_new (2.500, 0.0, 5.0, 0.001, 0.1, 0.0);
170 *
171 * window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
172 * gtk_container_set_border_width (GTK_CONTAINER (window), 5);
173 *
174 * // creates the spinbutton, with three decimal places
175 * button = gtk_spin_button_new (adjustment, 0.001, 3);
176 * gtk_container_add (GTK_CONTAINER (window), button);
177 *
178 * gtk_widget_show_all (window);
179 * }
180 * ]|
181 */
182
183 enum {
184 UP_PANEL,
185 DOWN_PANEL
186 };
187
188 struct _GtkSpinButtonPrivate
189 {
190 GtkAdjustment *adjustment;
191
192 GdkWindow *down_panel;
193 GdkWindow *up_panel;
194
195 GtkCssGadget *gadget;
196 GtkCssGadget *down_button;
197 GtkCssGadget *up_button;
198
199 GdkWindow *click_child;
200 GdkWindow *in_child;
201
202 guint32 timer;
203
204 GtkSpinButtonUpdatePolicy update_policy;
205
206 gdouble climb_rate;
207 gdouble timer_step;
208
209 GtkOrientation orientation;
210
211 GtkGesture *swipe_gesture;
212
213 guint button : 2;
214 guint digits : 10;
215 guint need_timer : 1;
216 guint numeric : 1;
217 guint snap_to_ticks : 1;
218 guint timer_calls : 3;
219 guint wrap : 1;
220 };
221
222 enum {
223 PROP_0,
224 PROP_ADJUSTMENT,
225 PROP_CLIMB_RATE,
226 PROP_DIGITS,
227 PROP_SNAP_TO_TICKS,
228 PROP_NUMERIC,
229 PROP_WRAP,
230 PROP_UPDATE_POLICY,
231 PROP_VALUE,
232 PROP_ORIENTATION
233 };
234
235 /* Signals */
236 enum
237 {
238 INPUT,
239 OUTPUT,
240 VALUE_CHANGED,
241 CHANGE_VALUE,
242 WRAPPED,
243 LAST_SIGNAL
244 };
245
246 static void gtk_spin_button_editable_init (GtkEditableInterface *iface);
247 static void gtk_spin_button_finalize (GObject *object);
248 static void gtk_spin_button_set_property (GObject *object,
249 guint prop_id,
250 const GValue *value,
251 GParamSpec *pspec);
252 static void gtk_spin_button_get_property (GObject *object,
253 guint prop_id,
254 GValue *value,
255 GParamSpec *pspec);
256 static void gtk_spin_button_destroy (GtkWidget *widget);
257 static void gtk_spin_button_map (GtkWidget *widget);
258 static void gtk_spin_button_unmap (GtkWidget *widget);
259 static void gtk_spin_button_realize (GtkWidget *widget);
260 static void gtk_spin_button_unrealize (GtkWidget *widget);
261 static void gtk_spin_button_get_preferred_width (GtkWidget *widget,
262 gint *minimum,
263 gint *natural);
264 static void gtk_spin_button_get_preferred_height (GtkWidget *widget,
265 gint *minimum,
266 gint *natural);
267 static void gtk_spin_button_get_preferred_height_and_baseline_for_width (GtkWidget *widget,
268 gint width,
269 gint *minimum,
270 gint *natural,
271 gint *minimum_baseline,
272 gint *natural_baseline);
273 static void gtk_spin_button_size_allocate (GtkWidget *widget,
274 GtkAllocation *allocation);
275 static gint gtk_spin_button_draw (GtkWidget *widget,
276 cairo_t *cr);
277 static gint gtk_spin_button_button_press (GtkWidget *widget,
278 GdkEventButton *event);
279 static gint gtk_spin_button_button_release (GtkWidget *widget,
280 GdkEventButton *event);
281 static gint gtk_spin_button_motion_notify (GtkWidget *widget,
282 GdkEventMotion *event);
283 static gint gtk_spin_button_enter_notify (GtkWidget *widget,
284 GdkEventCrossing *event);
285 static gint gtk_spin_button_leave_notify (GtkWidget *widget,
286 GdkEventCrossing *event);
287 static gint gtk_spin_button_focus_out (GtkWidget *widget,
288 GdkEventFocus *event);
289 static void gtk_spin_button_grab_notify (GtkWidget *widget,
290 gboolean was_grabbed);
291 static void gtk_spin_button_state_flags_changed (GtkWidget *widget,
292 GtkStateFlags previous_state);
293 static gboolean gtk_spin_button_timer (GtkSpinButton *spin_button);
294 static gboolean gtk_spin_button_stop_spinning (GtkSpinButton *spin);
295 static void gtk_spin_button_value_changed (GtkAdjustment *adjustment,
296 GtkSpinButton *spin_button);
297 static gint gtk_spin_button_key_release (GtkWidget *widget,
298 GdkEventKey *event);
299 static gint gtk_spin_button_scroll (GtkWidget *widget,
300 GdkEventScroll *event);
301 static void gtk_spin_button_direction_changed (GtkWidget *widget,
302 GtkTextDirection previous_dir);
303 static void gtk_spin_button_activate (GtkEntry *entry);
304 static void gtk_spin_button_unset_adjustment (GtkSpinButton *spin_button);
305 static void gtk_spin_button_set_orientation (GtkSpinButton *spin_button,
306 GtkOrientation orientation);
307 static void gtk_spin_button_snap (GtkSpinButton *spin_button,
308 gdouble val);
309 static void gtk_spin_button_insert_text (GtkEditable *editable,
310 const gchar *new_text,
311 gint new_text_length,
312 gint *position);
313 static void gtk_spin_button_real_spin (GtkSpinButton *spin_button,
314 gdouble step);
315 static void gtk_spin_button_real_change_value (GtkSpinButton *spin,
316 GtkScrollType scroll);
317
318 static gint gtk_spin_button_default_input (GtkSpinButton *spin_button,
319 gdouble *new_val);
320 static void gtk_spin_button_default_output (GtkSpinButton *spin_button);
321 static void update_node_state (GtkSpinButton *spin_button);
322
323 static guint spinbutton_signals[LAST_SIGNAL] = {0};
324
G_DEFINE_TYPE_WITH_CODE(GtkSpinButton,gtk_spin_button,GTK_TYPE_ENTRY,G_ADD_PRIVATE (GtkSpinButton)G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,NULL)G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,gtk_spin_button_editable_init))325 G_DEFINE_TYPE_WITH_CODE (GtkSpinButton, gtk_spin_button, GTK_TYPE_ENTRY,
326 G_ADD_PRIVATE (GtkSpinButton)
327 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
328 G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
329 gtk_spin_button_editable_init))
330
331 #define add_spin_binding(binding_set, keyval, mask, scroll) \
332 gtk_binding_entry_add_signal (binding_set, keyval, mask, \
333 "change-value", 1, \
334 GTK_TYPE_SCROLL_TYPE, scroll)
335
336 static void
337 gtk_spin_button_class_init (GtkSpinButtonClass *class)
338 {
339 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
340 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
341 GtkEntryClass *entry_class = GTK_ENTRY_CLASS (class);
342 GtkBindingSet *binding_set;
343
344 gobject_class->finalize = gtk_spin_button_finalize;
345 gobject_class->set_property = gtk_spin_button_set_property;
346 gobject_class->get_property = gtk_spin_button_get_property;
347
348 widget_class->destroy = gtk_spin_button_destroy;
349 widget_class->map = gtk_spin_button_map;
350 widget_class->unmap = gtk_spin_button_unmap;
351 widget_class->realize = gtk_spin_button_realize;
352 widget_class->unrealize = gtk_spin_button_unrealize;
353 widget_class->get_preferred_width = gtk_spin_button_get_preferred_width;
354 widget_class->get_preferred_height = gtk_spin_button_get_preferred_height;
355 widget_class->get_preferred_height_and_baseline_for_width = gtk_spin_button_get_preferred_height_and_baseline_for_width;
356 widget_class->size_allocate = gtk_spin_button_size_allocate;
357 widget_class->draw = gtk_spin_button_draw;
358 widget_class->scroll_event = gtk_spin_button_scroll;
359 widget_class->button_press_event = gtk_spin_button_button_press;
360 widget_class->button_release_event = gtk_spin_button_button_release;
361 widget_class->motion_notify_event = gtk_spin_button_motion_notify;
362 widget_class->key_release_event = gtk_spin_button_key_release;
363 widget_class->enter_notify_event = gtk_spin_button_enter_notify;
364 widget_class->leave_notify_event = gtk_spin_button_leave_notify;
365 widget_class->focus_out_event = gtk_spin_button_focus_out;
366 widget_class->grab_notify = gtk_spin_button_grab_notify;
367 widget_class->state_flags_changed = gtk_spin_button_state_flags_changed;
368 widget_class->direction_changed = gtk_spin_button_direction_changed;
369
370 entry_class->activate = gtk_spin_button_activate;
371
372 class->input = NULL;
373 class->output = NULL;
374 class->change_value = gtk_spin_button_real_change_value;
375
376 g_object_class_install_property (gobject_class,
377 PROP_ADJUSTMENT,
378 g_param_spec_object ("adjustment",
379 P_("Adjustment"),
380 P_("The adjustment that holds the value of the spin button"),
381 GTK_TYPE_ADJUSTMENT,
382 GTK_PARAM_READWRITE));
383
384 g_object_class_install_property (gobject_class,
385 PROP_CLIMB_RATE,
386 g_param_spec_double ("climb-rate",
387 P_("Climb Rate"),
388 P_("The acceleration rate when you hold down a button or key"),
389 0.0, G_MAXDOUBLE, 0.0,
390 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
391
392 g_object_class_install_property (gobject_class,
393 PROP_DIGITS,
394 g_param_spec_uint ("digits",
395 P_("Digits"),
396 P_("The number of decimal places to display"),
397 0, MAX_DIGITS, 0,
398 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
399
400 g_object_class_install_property (gobject_class,
401 PROP_SNAP_TO_TICKS,
402 g_param_spec_boolean ("snap-to-ticks",
403 P_("Snap to Ticks"),
404 P_("Whether erroneous values are automatically changed to a spin button's nearest step increment"),
405 FALSE,
406 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
407
408 g_object_class_install_property (gobject_class,
409 PROP_NUMERIC,
410 g_param_spec_boolean ("numeric",
411 P_("Numeric"),
412 P_("Whether non-numeric characters should be ignored"),
413 FALSE,
414 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
415
416 g_object_class_install_property (gobject_class,
417 PROP_WRAP,
418 g_param_spec_boolean ("wrap",
419 P_("Wrap"),
420 P_("Whether a spin button should wrap upon reaching its limits"),
421 FALSE,
422 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
423
424 g_object_class_install_property (gobject_class,
425 PROP_UPDATE_POLICY,
426 g_param_spec_enum ("update-policy",
427 P_("Update Policy"),
428 P_("Whether the spin button should update always, or only when the value is legal"),
429 GTK_TYPE_SPIN_BUTTON_UPDATE_POLICY,
430 GTK_UPDATE_ALWAYS,
431 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
432
433 g_object_class_install_property (gobject_class,
434 PROP_VALUE,
435 g_param_spec_double ("value",
436 P_("Value"),
437 P_("Reads the current value, or sets a new value"),
438 -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
439 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
440
441 g_object_class_override_property (gobject_class,
442 PROP_ORIENTATION,
443 "orientation");
444
445 /**
446 * GtkSpinButton:shadow-type:
447 *
448 * Style of bevel around the spin button.
449 *
450 * Deprecated: 3.20: Use CSS to determine the style of the border;
451 * the value of this style property is ignored.
452 */
453 gtk_widget_class_install_style_property (widget_class,
454 g_param_spec_enum ("shadow-type",
455 P_("Shadow Type"),
456 P_("Style of bevel around the spin button"),
457 GTK_TYPE_SHADOW_TYPE,
458 GTK_SHADOW_IN,
459 GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
460
461 /**
462 * GtkSpinButton::input:
463 * @spin_button: the object on which the signal was emitted
464 * @new_value: (out) (type double): return location for the new value
465 *
466 * The ::input signal can be used to influence the conversion of
467 * the users input into a double value. The signal handler is
468 * expected to use gtk_entry_get_text() to retrieve the text of
469 * the entry and set @new_value to the new value.
470 *
471 * The default conversion uses g_strtod().
472 *
473 * Returns: %TRUE for a successful conversion, %FALSE if the input
474 * was not handled, and %GTK_INPUT_ERROR if the conversion failed.
475 */
476 spinbutton_signals[INPUT] =
477 g_signal_new (I_("input"),
478 G_TYPE_FROM_CLASS (gobject_class),
479 G_SIGNAL_RUN_LAST,
480 G_STRUCT_OFFSET (GtkSpinButtonClass, input),
481 NULL, NULL,
482 _gtk_marshal_INT__POINTER,
483 G_TYPE_INT, 1,
484 G_TYPE_POINTER);
485
486 /**
487 * GtkSpinButton::output:
488 * @spin_button: the object on which the signal was emitted
489 *
490 * The ::output signal can be used to change to formatting
491 * of the value that is displayed in the spin buttons entry.
492 * |[<!-- language="C" -->
493 * // show leading zeros
494 * static gboolean
495 * on_output (GtkSpinButton *spin,
496 * gpointer data)
497 * {
498 * GtkAdjustment *adjustment;
499 * gchar *text;
500 * int value;
501 *
502 * adjustment = gtk_spin_button_get_adjustment (spin);
503 * value = (int)gtk_adjustment_get_value (adjustment);
504 * text = g_strdup_printf ("%02d", value);
505 * gtk_entry_set_text (GTK_ENTRY (spin), text);
506 * g_free (text);
507 *
508 * return TRUE;
509 * }
510 * ]|
511 *
512 * Returns: %TRUE if the value has been displayed
513 */
514 spinbutton_signals[OUTPUT] =
515 g_signal_new (I_("output"),
516 G_TYPE_FROM_CLASS (gobject_class),
517 G_SIGNAL_RUN_LAST,
518 G_STRUCT_OFFSET (GtkSpinButtonClass, output),
519 _gtk_boolean_handled_accumulator, NULL,
520 _gtk_marshal_BOOLEAN__VOID,
521 G_TYPE_BOOLEAN, 0);
522
523 /**
524 * GtkSpinButton::value-changed:
525 * @spin_button: the object on which the signal was emitted
526 *
527 * The ::value-changed signal is emitted when the value represented by
528 * @spinbutton changes. Also see the #GtkSpinButton::output signal.
529 */
530 spinbutton_signals[VALUE_CHANGED] =
531 g_signal_new (I_("value-changed"),
532 G_TYPE_FROM_CLASS (gobject_class),
533 G_SIGNAL_RUN_LAST,
534 G_STRUCT_OFFSET (GtkSpinButtonClass, value_changed),
535 NULL, NULL,
536 NULL,
537 G_TYPE_NONE, 0);
538
539 /**
540 * GtkSpinButton::wrapped:
541 * @spin_button: the object on which the signal was emitted
542 *
543 * The ::wrapped signal is emitted right after the spinbutton wraps
544 * from its maximum to minimum value or vice-versa.
545 *
546 * Since: 2.10
547 */
548 spinbutton_signals[WRAPPED] =
549 g_signal_new (I_("wrapped"),
550 G_TYPE_FROM_CLASS (gobject_class),
551 G_SIGNAL_RUN_LAST,
552 G_STRUCT_OFFSET (GtkSpinButtonClass, wrapped),
553 NULL, NULL,
554 NULL,
555 G_TYPE_NONE, 0);
556
557 /* Action signals */
558 /**
559 * GtkSpinButton::change-value:
560 * @spin_button: the object on which the signal was emitted
561 * @scroll: a #GtkScrollType to specify the speed and amount of change
562 *
563 * The ::change-value signal is a [keybinding signal][GtkBindingSignal]
564 * which gets emitted when the user initiates a value change.
565 *
566 * Applications should not connect to it, but may emit it with
567 * g_signal_emit_by_name() if they need to control the cursor
568 * programmatically.
569 *
570 * The default bindings for this signal are Up/Down and PageUp and/PageDown.
571 */
572 spinbutton_signals[CHANGE_VALUE] =
573 g_signal_new (I_("change-value"),
574 G_TYPE_FROM_CLASS (gobject_class),
575 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
576 G_STRUCT_OFFSET (GtkSpinButtonClass, change_value),
577 NULL, NULL,
578 NULL,
579 G_TYPE_NONE, 1,
580 GTK_TYPE_SCROLL_TYPE);
581
582 binding_set = gtk_binding_set_by_class (class);
583
584 add_spin_binding (binding_set, GDK_KEY_Up, 0, GTK_SCROLL_STEP_UP);
585 add_spin_binding (binding_set, GDK_KEY_KP_Up, 0, GTK_SCROLL_STEP_UP);
586 add_spin_binding (binding_set, GDK_KEY_Down, 0, GTK_SCROLL_STEP_DOWN);
587 add_spin_binding (binding_set, GDK_KEY_KP_Down, 0, GTK_SCROLL_STEP_DOWN);
588 add_spin_binding (binding_set, GDK_KEY_Page_Up, 0, GTK_SCROLL_PAGE_UP);
589 add_spin_binding (binding_set, GDK_KEY_Page_Down, 0, GTK_SCROLL_PAGE_DOWN);
590 add_spin_binding (binding_set, GDK_KEY_End, GDK_CONTROL_MASK, GTK_SCROLL_END);
591 add_spin_binding (binding_set, GDK_KEY_Home, GDK_CONTROL_MASK, GTK_SCROLL_START);
592 add_spin_binding (binding_set, GDK_KEY_Page_Up, GDK_CONTROL_MASK, GTK_SCROLL_END);
593 add_spin_binding (binding_set, GDK_KEY_Page_Down, GDK_CONTROL_MASK, GTK_SCROLL_START);
594
595 gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SPIN_BUTTON_ACCESSIBLE);
596 gtk_widget_class_set_css_name (widget_class, "spinbutton");
597 }
598
599 static void
gtk_spin_button_editable_init(GtkEditableInterface * iface)600 gtk_spin_button_editable_init (GtkEditableInterface *iface)
601 {
602 iface->insert_text = gtk_spin_button_insert_text;
603 }
604
605 static void
gtk_spin_button_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)606 gtk_spin_button_set_property (GObject *object,
607 guint prop_id,
608 const GValue *value,
609 GParamSpec *pspec)
610 {
611 GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
612 GtkSpinButtonPrivate *priv = spin_button->priv;
613
614 switch (prop_id)
615 {
616 GtkAdjustment *adjustment;
617
618 case PROP_ADJUSTMENT:
619 adjustment = GTK_ADJUSTMENT (g_value_get_object (value));
620 gtk_spin_button_set_adjustment (spin_button, adjustment);
621 break;
622 case PROP_CLIMB_RATE:
623 gtk_spin_button_configure (spin_button,
624 priv->adjustment,
625 g_value_get_double (value),
626 priv->digits);
627 break;
628 case PROP_DIGITS:
629 gtk_spin_button_configure (spin_button,
630 priv->adjustment,
631 priv->climb_rate,
632 g_value_get_uint (value));
633 break;
634 case PROP_SNAP_TO_TICKS:
635 gtk_spin_button_set_snap_to_ticks (spin_button, g_value_get_boolean (value));
636 break;
637 case PROP_NUMERIC:
638 gtk_spin_button_set_numeric (spin_button, g_value_get_boolean (value));
639 break;
640 case PROP_WRAP:
641 gtk_spin_button_set_wrap (spin_button, g_value_get_boolean (value));
642 break;
643 case PROP_UPDATE_POLICY:
644 gtk_spin_button_set_update_policy (spin_button, g_value_get_enum (value));
645 break;
646 case PROP_VALUE:
647 gtk_spin_button_set_value (spin_button, g_value_get_double (value));
648 break;
649 case PROP_ORIENTATION:
650 gtk_spin_button_set_orientation (spin_button, g_value_get_enum (value));
651 break;
652 default:
653 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
654 break;
655 }
656 }
657
658 static void
gtk_spin_button_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)659 gtk_spin_button_get_property (GObject *object,
660 guint prop_id,
661 GValue *value,
662 GParamSpec *pspec)
663 {
664 GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
665 GtkSpinButtonPrivate *priv = spin_button->priv;
666
667 switch (prop_id)
668 {
669 case PROP_ADJUSTMENT:
670 g_value_set_object (value, priv->adjustment);
671 break;
672 case PROP_CLIMB_RATE:
673 g_value_set_double (value, priv->climb_rate);
674 break;
675 case PROP_DIGITS:
676 g_value_set_uint (value, priv->digits);
677 break;
678 case PROP_SNAP_TO_TICKS:
679 g_value_set_boolean (value, priv->snap_to_ticks);
680 break;
681 case PROP_NUMERIC:
682 g_value_set_boolean (value, priv->numeric);
683 break;
684 case PROP_WRAP:
685 g_value_set_boolean (value, priv->wrap);
686 break;
687 case PROP_UPDATE_POLICY:
688 g_value_set_enum (value, priv->update_policy);
689 break;
690 case PROP_VALUE:
691 g_value_set_double (value, gtk_adjustment_get_value (priv->adjustment));
692 break;
693 case PROP_ORIENTATION:
694 g_value_set_enum (value, priv->orientation);
695 break;
696 default:
697 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
698 break;
699 }
700 }
701
702 static void
swipe_gesture_begin(GtkGesture * gesture,GdkEventSequence * sequence,GtkSpinButton * spin_button)703 swipe_gesture_begin (GtkGesture *gesture,
704 GdkEventSequence *sequence,
705 GtkSpinButton *spin_button)
706 {
707 GdkEventSequence *current;
708 const GdkEvent *event;
709
710 current = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
711 event = gtk_gesture_get_last_event (gesture, current);
712
713 if (event->any.window == spin_button->priv->up_panel ||
714 event->any.window == spin_button->priv->down_panel)
715 gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
716
717 gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
718 gtk_widget_grab_focus (GTK_WIDGET (spin_button));
719 }
720
721 static void
swipe_gesture_update(GtkGesture * gesture,GdkEventSequence * sequence,GtkSpinButton * spin_button)722 swipe_gesture_update (GtkGesture *gesture,
723 GdkEventSequence *sequence,
724 GtkSpinButton *spin_button)
725 {
726 gdouble vel_y;
727
728 gtk_gesture_swipe_get_velocity (GTK_GESTURE_SWIPE (gesture), NULL, &vel_y);
729 gtk_spin_button_real_spin (spin_button, -vel_y / 20);
730 }
731
732 static void
update_node_ordering(GtkSpinButton * spin_button)733 update_node_ordering (GtkSpinButton *spin_button)
734 {
735 GtkSpinButtonPrivate *priv = spin_button->priv;
736 int down_button_pos, up_button_pos;
737
738 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
739 {
740 if (gtk_widget_get_direction (GTK_WIDGET (spin_button)) == GTK_TEXT_DIR_LTR)
741 {
742 down_button_pos = 1;
743 up_button_pos = -1;
744 }
745 else
746 {
747 down_button_pos = 1;
748 up_button_pos = 0;
749 }
750 }
751 else
752 {
753 up_button_pos = 0;
754 down_button_pos = -1;
755 }
756
757 gtk_box_gadget_set_orientation (GTK_BOX_GADGET (priv->gadget), priv->orientation);
758 gtk_box_gadget_remove_gadget (GTK_BOX_GADGET (priv->gadget), priv->up_button);
759 gtk_box_gadget_remove_gadget (GTK_BOX_GADGET (priv->gadget), priv->down_button);
760 gtk_box_gadget_insert_gadget (GTK_BOX_GADGET (priv->gadget),
761 up_button_pos, priv->up_button,
762 FALSE, GTK_ALIGN_FILL);
763 gtk_box_gadget_insert_gadget (GTK_BOX_GADGET (priv->gadget),
764 down_button_pos, priv->down_button,
765 FALSE, GTK_ALIGN_FILL);
766 }
767
768 static void
gtk_spin_button_init(GtkSpinButton * spin_button)769 gtk_spin_button_init (GtkSpinButton *spin_button)
770 {
771 GtkSpinButtonPrivate *priv;
772 GtkCssNode *widget_node, *entry_node;
773
774 spin_button->priv = gtk_spin_button_get_instance_private (spin_button);
775 priv = spin_button->priv;
776
777 priv->adjustment = NULL;
778 priv->down_panel = NULL;
779 priv->up_panel = NULL;
780 priv->timer = 0;
781 priv->climb_rate = 0.0;
782 priv->timer_step = 0.0;
783 priv->update_policy = GTK_UPDATE_ALWAYS;
784 priv->in_child = NULL;
785 priv->click_child = NULL;
786 priv->button = 0;
787 priv->need_timer = FALSE;
788 priv->timer_calls = 0;
789 priv->digits = 0;
790 priv->numeric = FALSE;
791 priv->wrap = FALSE;
792 priv->snap_to_ticks = FALSE;
793
794 priv->orientation = GTK_ORIENTATION_HORIZONTAL;
795
796 _gtk_orientable_set_style_classes (GTK_ORIENTABLE (spin_button));
797
798 widget_node = gtk_widget_get_css_node (GTK_WIDGET (spin_button));
799
800 priv->gadget = gtk_box_gadget_new_for_node (widget_node, GTK_WIDGET (spin_button));
801
802 entry_node = gtk_css_node_new ();
803 gtk_css_node_set_name (entry_node, I_("entry"));
804 gtk_css_node_set_parent (entry_node, widget_node);
805 gtk_css_node_set_state (entry_node, gtk_css_node_get_state (widget_node));
806 gtk_css_gadget_set_node (gtk_entry_get_gadget (GTK_ENTRY (spin_button)), entry_node);
807 g_object_unref (entry_node);
808 gtk_box_gadget_insert_gadget (GTK_BOX_GADGET (priv->gadget),
809 -1, gtk_entry_get_gadget (GTK_ENTRY (spin_button)),
810 TRUE, GTK_ALIGN_FILL);
811
812 priv->down_button = gtk_icon_helper_new_named ("button",
813 GTK_WIDGET (spin_button));
814 _gtk_icon_helper_set_use_fallback (GTK_ICON_HELPER (priv->down_button), TRUE);
815 _gtk_icon_helper_set_icon_name (GTK_ICON_HELPER (priv->down_button), "list-remove-symbolic", GTK_ICON_SIZE_MENU);
816 gtk_css_gadget_add_class (priv->down_button, "down");
817 gtk_css_node_set_parent (gtk_css_gadget_get_node (priv->down_button), widget_node);
818 gtk_css_node_set_state (gtk_css_gadget_get_node (priv->down_button), gtk_css_node_get_state (widget_node));
819 gtk_box_gadget_insert_gadget (GTK_BOX_GADGET (priv->gadget),
820 -1, priv->down_button,
821 FALSE, GTK_ALIGN_FILL);
822
823 priv->up_button = gtk_icon_helper_new_named ("button",
824 GTK_WIDGET (spin_button));
825 _gtk_icon_helper_set_use_fallback (GTK_ICON_HELPER (priv->up_button), TRUE);
826 _gtk_icon_helper_set_icon_name (GTK_ICON_HELPER (priv->up_button), "list-add-symbolic", GTK_ICON_SIZE_MENU);
827 gtk_css_gadget_add_class (priv->up_button, "up");
828 gtk_css_node_set_parent (gtk_css_gadget_get_node (priv->up_button), widget_node);
829 gtk_css_node_set_state (gtk_css_gadget_get_node (priv->up_button), gtk_css_node_get_state (widget_node));
830 gtk_box_gadget_insert_gadget (GTK_BOX_GADGET (priv->gadget),
831 -1, priv->up_button,
832 FALSE, GTK_ALIGN_FILL);
833
834 gtk_spin_button_set_adjustment (spin_button, NULL);
835
836 update_node_ordering (spin_button);
837 update_node_state (spin_button);
838
839 gtk_widget_add_events (GTK_WIDGET (spin_button), GDK_SCROLL_MASK);
840
841 priv->swipe_gesture = gtk_gesture_swipe_new (GTK_WIDGET (spin_button));
842 gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->swipe_gesture), TRUE);
843 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->swipe_gesture),
844 GTK_PHASE_CAPTURE);
845 g_signal_connect (priv->swipe_gesture, "begin",
846 G_CALLBACK (swipe_gesture_begin), spin_button);
847 g_signal_connect (priv->swipe_gesture, "update",
848 G_CALLBACK (swipe_gesture_update), spin_button);
849 }
850
851 static void
gtk_spin_button_finalize(GObject * object)852 gtk_spin_button_finalize (GObject *object)
853 {
854 GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object);
855 GtkSpinButtonPrivate *priv = spin_button->priv;
856
857 gtk_spin_button_unset_adjustment (spin_button);
858 g_clear_object (&priv->gadget);
859 g_clear_object (&priv->down_button);
860 g_clear_object (&priv->up_button);
861
862 g_object_unref (priv->swipe_gesture);
863
864 G_OBJECT_CLASS (gtk_spin_button_parent_class)->finalize (object);
865 }
866
867 static void
gtk_spin_button_destroy(GtkWidget * widget)868 gtk_spin_button_destroy (GtkWidget *widget)
869 {
870 gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));
871
872 GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->destroy (widget);
873 }
874
875 static void
gtk_spin_button_map(GtkWidget * widget)876 gtk_spin_button_map (GtkWidget *widget)
877 {
878 GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
879 GtkSpinButtonPrivate *priv = spin_button->priv;
880
881 if (gtk_widget_get_realized (widget) && !gtk_widget_get_mapped (widget))
882 {
883 GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->map (widget);
884 gdk_window_show (priv->down_panel);
885 gdk_window_show (priv->up_panel);
886 }
887 }
888
889 static void
gtk_spin_button_unmap(GtkWidget * widget)890 gtk_spin_button_unmap (GtkWidget *widget)
891 {
892 GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
893 GtkSpinButtonPrivate *priv = spin_button->priv;
894
895 if (gtk_widget_get_mapped (widget))
896 {
897 gtk_spin_button_stop_spinning (GTK_SPIN_BUTTON (widget));
898
899 gdk_window_hide (priv->down_panel);
900 gdk_window_hide (priv->up_panel);
901 GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unmap (widget);
902 }
903 }
904
905 static gboolean
gtk_spin_button_panel_at_limit(GtkSpinButton * spin_button,gint panel)906 gtk_spin_button_panel_at_limit (GtkSpinButton *spin_button,
907 gint panel)
908 {
909 GtkSpinButtonPrivate *priv = spin_button->priv;
910
911 if (priv->wrap)
912 return FALSE;
913
914 if (panel == UP_PANEL &&
915 (gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_value (priv->adjustment) <= EPSILON))
916 return TRUE;
917
918 if (panel == DOWN_PANEL &&
919 (gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment) <= EPSILON))
920 return TRUE;
921
922 return FALSE;
923 }
924
925 static GtkStateFlags
gtk_spin_button_panel_get_state(GtkSpinButton * spin_button,gint panel)926 gtk_spin_button_panel_get_state (GtkSpinButton *spin_button,
927 gint panel)
928 {
929 GtkStateFlags state;
930 GtkSpinButtonPrivate *priv = spin_button->priv;
931
932 state = gtk_widget_get_state_flags (GTK_WIDGET (spin_button));
933
934 state &= ~(GTK_STATE_FLAG_ACTIVE | GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_DROP_ACTIVE);
935
936 if ((state & GTK_STATE_FLAG_INSENSITIVE) ||
937 gtk_spin_button_panel_at_limit (spin_button, panel) ||
938 !gtk_editable_get_editable (GTK_EDITABLE (spin_button)))
939 {
940 state |= GTK_STATE_FLAG_INSENSITIVE;
941 }
942 else
943 {
944 GdkWindow *panel_win;
945
946 panel_win = panel == UP_PANEL ? priv->up_panel : priv->down_panel;
947
948 if (priv->click_child &&
949 priv->click_child == panel_win)
950 state |= GTK_STATE_FLAG_ACTIVE;
951 else if (priv->in_child &&
952 priv->in_child == panel_win &&
953 priv->click_child == NULL)
954 state |= GTK_STATE_FLAG_PRELIGHT;
955 }
956
957 return state;
958 }
959
960 static void
update_node_state(GtkSpinButton * spin_button)961 update_node_state (GtkSpinButton *spin_button)
962 {
963 GtkSpinButtonPrivate *priv = spin_button->priv;
964
965 gtk_css_gadget_set_state (priv->up_button, gtk_spin_button_panel_get_state (spin_button, UP_PANEL));
966 gtk_css_gadget_set_state (priv->down_button, gtk_spin_button_panel_get_state (spin_button, DOWN_PANEL));
967 }
968
969 static void
gtk_spin_button_realize(GtkWidget * widget)970 gtk_spin_button_realize (GtkWidget *widget)
971 {
972 GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
973 GtkSpinButtonPrivate *priv = spin_button->priv;
974 GtkAllocation down_allocation, up_allocation;
975 GdkWindowAttr attributes;
976 gint attributes_mask;
977 gboolean return_val;
978
979 gtk_widget_set_events (widget, gtk_widget_get_events (widget) |
980 GDK_KEY_RELEASE_MASK);
981 GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->realize (widget);
982
983 attributes.window_type = GDK_WINDOW_CHILD;
984 attributes.wclass = GDK_INPUT_ONLY;
985 attributes.visual = gtk_widget_get_visual (widget);
986 attributes.event_mask = gtk_widget_get_events (widget);
987 attributes.event_mask |= GDK_BUTTON_PRESS_MASK
988 | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK
989 | GDK_POINTER_MOTION_MASK;
990
991 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
992
993 gtk_css_gadget_get_border_allocation (priv->up_button, &up_allocation, NULL);
994 gtk_css_gadget_get_border_allocation (priv->down_button, &down_allocation, NULL);
995
996 /* create the left panel window */
997 attributes.x = down_allocation.x;
998 attributes.y = down_allocation.y;
999 attributes.width = down_allocation.width;
1000 attributes.height = down_allocation.height;
1001
1002 priv->down_panel = gdk_window_new (gtk_widget_get_window (widget),
1003 &attributes, attributes_mask);
1004 gtk_widget_register_window (widget, priv->down_panel);
1005
1006 /* create the right panel window */
1007 attributes.x = up_allocation.x;
1008 attributes.y = up_allocation.y;
1009 attributes.width = up_allocation.width;
1010 attributes.height = up_allocation.height;
1011
1012 priv->up_panel = gdk_window_new (gtk_widget_get_window (widget),
1013 &attributes, attributes_mask);
1014 gtk_widget_register_window (widget, priv->up_panel);
1015
1016 return_val = FALSE;
1017 g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
1018
1019 /* If output wasn't processed explicitly by the method connected to the
1020 * 'output' signal; and if we don't have any explicit 'text' set initially,
1021 * fallback to the default output. */
1022 if (!return_val &&
1023 (spin_button->priv->numeric || gtk_entry_get_text (GTK_ENTRY (spin_button)) == NULL))
1024 gtk_spin_button_default_output (spin_button);
1025
1026 gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1027 }
1028
1029 static void
gtk_spin_button_unrealize(GtkWidget * widget)1030 gtk_spin_button_unrealize (GtkWidget *widget)
1031 {
1032 GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1033 GtkSpinButtonPrivate *priv = spin->priv;
1034
1035 GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->unrealize (widget);
1036
1037 if (priv->down_panel)
1038 {
1039 gtk_widget_unregister_window (widget, priv->down_panel);
1040 gdk_window_destroy (priv->down_panel);
1041 priv->down_panel = NULL;
1042 }
1043
1044 if (priv->up_panel)
1045 {
1046 gtk_widget_unregister_window (widget, priv->up_panel);
1047 gdk_window_destroy (priv->up_panel);
1048 priv->up_panel = NULL;
1049 }
1050 }
1051
1052 /* Callback used when the spin button's adjustment changes.
1053 * We need to redraw the arrows when the adjustment’s range
1054 * changes, and reevaluate our size request.
1055 */
1056 static void
adjustment_changed_cb(GtkAdjustment * adjustment,gpointer data)1057 adjustment_changed_cb (GtkAdjustment *adjustment, gpointer data)
1058 {
1059 GtkSpinButton *spin_button = GTK_SPIN_BUTTON (data);
1060 GtkSpinButtonPrivate *priv = spin_button->priv;
1061
1062 priv->timer_step = gtk_adjustment_get_step_increment (priv->adjustment);
1063 update_node_state (spin_button);
1064 gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1065 }
1066
1067 static void
gtk_spin_button_unset_adjustment(GtkSpinButton * spin_button)1068 gtk_spin_button_unset_adjustment (GtkSpinButton *spin_button)
1069 {
1070 GtkSpinButtonPrivate *priv = spin_button->priv;
1071
1072 if (priv->adjustment)
1073 {
1074 g_signal_handlers_disconnect_by_func (priv->adjustment,
1075 gtk_spin_button_value_changed,
1076 spin_button);
1077 g_signal_handlers_disconnect_by_func (priv->adjustment,
1078 adjustment_changed_cb,
1079 spin_button);
1080 g_clear_object (&priv->adjustment);
1081 }
1082 }
1083
1084 static void
gtk_spin_button_set_orientation(GtkSpinButton * spin,GtkOrientation orientation)1085 gtk_spin_button_set_orientation (GtkSpinButton *spin,
1086 GtkOrientation orientation)
1087 {
1088 GtkEntry *entry = GTK_ENTRY (spin);
1089 GtkSpinButtonPrivate *priv = spin->priv;
1090
1091 if (priv->orientation == orientation)
1092 return;
1093
1094 priv->orientation = orientation;
1095 _gtk_orientable_set_style_classes (GTK_ORIENTABLE (spin));
1096
1097 /* change alignment if it's the default */
1098 if (priv->orientation == GTK_ORIENTATION_VERTICAL &&
1099 gtk_entry_get_alignment (entry) == 0.0)
1100 gtk_entry_set_alignment (entry, 0.5);
1101 else if (priv->orientation == GTK_ORIENTATION_HORIZONTAL &&
1102 gtk_entry_get_alignment (entry) == 0.5)
1103 gtk_entry_set_alignment (entry, 0.0);
1104
1105 update_node_ordering (spin);
1106
1107 g_object_notify (G_OBJECT (spin), "orientation");
1108 gtk_widget_queue_resize (GTK_WIDGET (spin));
1109 }
1110
1111 static gint
measure_string_width(PangoLayout * layout,const gchar * string)1112 measure_string_width (PangoLayout *layout,
1113 const gchar *string)
1114 {
1115 gint width;
1116
1117 pango_layout_set_text (layout, string, -1);
1118 pango_layout_get_pixel_size (layout, &width, NULL);
1119
1120 return width;
1121 }
1122
1123 static gchar *
weed_out_neg_zero(gchar * str,gint digits)1124 weed_out_neg_zero (gchar *str,
1125 gint digits)
1126 {
1127 if (str[0] == '-')
1128 {
1129 gchar neg_zero[8];
1130 g_snprintf (neg_zero, 8, "%0.*f", digits, -0.0);
1131 if (strcmp (neg_zero, str) == 0)
1132 memmove (str, str + 1, strlen (str));
1133 }
1134 return str;
1135 }
1136
1137 static gchar *
gtk_spin_button_format_for_value(GtkSpinButton * spin_button,gdouble value)1138 gtk_spin_button_format_for_value (GtkSpinButton *spin_button,
1139 gdouble value)
1140 {
1141 GtkSpinButtonPrivate *priv = spin_button->priv;
1142 gchar *buf = g_strdup_printf ("%0.*f", priv->digits, value);
1143
1144 return weed_out_neg_zero (buf, priv->digits);
1145 }
1146
1147 gint
gtk_spin_button_get_text_width(GtkSpinButton * spin_button)1148 gtk_spin_button_get_text_width (GtkSpinButton *spin_button)
1149 {
1150 GtkSpinButtonPrivate *priv = spin_button->priv;
1151 gint width, w;
1152 PangoLayout *layout;
1153 gchar *str;
1154 gdouble value;
1155
1156 layout = pango_layout_copy (gtk_entry_get_layout (GTK_ENTRY (spin_button)));
1157
1158 /* Get max of MIN_SPIN_BUTTON_WIDTH, size of upper, size of lower */
1159 width = MIN_SPIN_BUTTON_WIDTH;
1160
1161 value = CLAMP (gtk_adjustment_get_upper (priv->adjustment), -1e7, 1e7);
1162 str = gtk_spin_button_format_for_value (spin_button, value);
1163 w = measure_string_width (layout, str);
1164 width = MAX (width, w);
1165 g_free (str);
1166
1167 value = CLAMP (gtk_adjustment_get_lower (priv->adjustment), -1e7, 1e7);
1168 str = gtk_spin_button_format_for_value (spin_button, value);
1169 w = measure_string_width (layout, str);
1170 width = MAX (width, w);
1171 g_free (str);
1172
1173 g_object_unref (layout);
1174
1175 return width;
1176 }
1177
1178 static void
gtk_spin_button_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)1179 gtk_spin_button_get_preferred_width (GtkWidget *widget,
1180 gint *minimum,
1181 gint *natural)
1182 {
1183 gtk_css_gadget_get_preferred_size (GTK_SPIN_BUTTON (widget)->priv->gadget,
1184 GTK_ORIENTATION_HORIZONTAL,
1185 -1,
1186 minimum, natural,
1187 NULL, NULL);
1188 }
1189
1190 static void
gtk_spin_button_get_preferred_height_and_baseline_for_width(GtkWidget * widget,gint width,gint * minimum,gint * natural,gint * minimum_baseline,gint * natural_baseline)1191 gtk_spin_button_get_preferred_height_and_baseline_for_width (GtkWidget *widget,
1192 gint width,
1193 gint *minimum,
1194 gint *natural,
1195 gint *minimum_baseline,
1196 gint *natural_baseline)
1197 {
1198 gtk_css_gadget_get_preferred_size (GTK_SPIN_BUTTON (widget)->priv->gadget,
1199 GTK_ORIENTATION_VERTICAL,
1200 width,
1201 minimum, natural,
1202 minimum_baseline, natural_baseline);
1203 }
1204
1205 static void
gtk_spin_button_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)1206 gtk_spin_button_get_preferred_height (GtkWidget *widget,
1207 gint *minimum,
1208 gint *natural)
1209 {
1210 gtk_spin_button_get_preferred_height_and_baseline_for_width (widget, -1, minimum, natural, NULL, NULL);
1211 }
1212
1213 static void
gtk_spin_button_size_allocate(GtkWidget * widget,GtkAllocation * allocation)1214 gtk_spin_button_size_allocate (GtkWidget *widget,
1215 GtkAllocation *allocation)
1216 {
1217 GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1218 GtkSpinButtonPrivate *priv = spin->priv;
1219 GtkAllocation clip;
1220
1221 gtk_widget_set_allocation (widget, allocation);
1222
1223 gtk_css_gadget_allocate (priv->gadget,
1224 allocation,
1225 gtk_widget_get_allocated_baseline (widget),
1226 &clip);
1227
1228 gtk_widget_set_clip (widget, &clip);
1229
1230 if (gtk_widget_get_realized (widget))
1231 {
1232 GtkAllocation button_alloc;
1233
1234 gtk_css_gadget_get_border_allocation (priv->down_button, &button_alloc, NULL);
1235 gdk_window_move_resize (priv->down_panel,
1236 button_alloc.x, button_alloc.y,
1237 button_alloc.width, button_alloc.height);
1238
1239 gtk_css_gadget_get_border_allocation (priv->up_button, &button_alloc, NULL);
1240 gdk_window_move_resize (priv->up_panel,
1241 button_alloc.x, button_alloc.y,
1242 button_alloc.width, button_alloc.height);
1243 }
1244 }
1245
1246 static gint
gtk_spin_button_draw(GtkWidget * widget,cairo_t * cr)1247 gtk_spin_button_draw (GtkWidget *widget,
1248 cairo_t *cr)
1249 {
1250 gtk_css_gadget_draw (GTK_SPIN_BUTTON(widget)->priv->gadget, cr);
1251
1252 return GDK_EVENT_PROPAGATE;
1253 }
1254
1255 static gint
gtk_spin_button_enter_notify(GtkWidget * widget,GdkEventCrossing * event)1256 gtk_spin_button_enter_notify (GtkWidget *widget,
1257 GdkEventCrossing *event)
1258 {
1259 GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1260 GtkSpinButtonPrivate *priv = spin->priv;
1261
1262 if (event->window == priv->down_panel ||
1263 event->window == priv->up_panel)
1264 {
1265 priv->in_child = event->window;
1266 update_node_state (spin);
1267 gtk_widget_queue_draw (GTK_WIDGET (spin));
1268 }
1269
1270 return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->enter_notify_event (widget, event);
1271 }
1272
1273 static gint
gtk_spin_button_leave_notify(GtkWidget * widget,GdkEventCrossing * event)1274 gtk_spin_button_leave_notify (GtkWidget *widget,
1275 GdkEventCrossing *event)
1276 {
1277 GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1278 GtkSpinButtonPrivate *priv = spin->priv;
1279
1280 if (priv->in_child != NULL)
1281 {
1282 priv->in_child = NULL;
1283 update_node_state (spin);
1284 gtk_widget_queue_draw (GTK_WIDGET (spin));
1285 }
1286
1287 return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->leave_notify_event (widget, event);
1288 }
1289
1290 static gint
gtk_spin_button_focus_out(GtkWidget * widget,GdkEventFocus * event)1291 gtk_spin_button_focus_out (GtkWidget *widget,
1292 GdkEventFocus *event)
1293 {
1294 if (gtk_editable_get_editable (GTK_EDITABLE (widget)))
1295 gtk_spin_button_update (GTK_SPIN_BUTTON (widget));
1296
1297 return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->focus_out_event (widget, event);
1298 }
1299
1300 static void
gtk_spin_button_grab_notify(GtkWidget * widget,gboolean was_grabbed)1301 gtk_spin_button_grab_notify (GtkWidget *widget,
1302 gboolean was_grabbed)
1303 {
1304 GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1305
1306 if (!was_grabbed)
1307 {
1308 if (gtk_spin_button_stop_spinning (spin))
1309 gtk_widget_queue_draw (GTK_WIDGET (spin));
1310 }
1311 }
1312
1313 static void
gtk_spin_button_state_flags_changed(GtkWidget * widget,GtkStateFlags previous_state)1314 gtk_spin_button_state_flags_changed (GtkWidget *widget,
1315 GtkStateFlags previous_state)
1316 {
1317 GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1318
1319 if (!gtk_widget_is_sensitive (widget))
1320 {
1321 if (gtk_spin_button_stop_spinning (spin))
1322 gtk_widget_queue_draw (GTK_WIDGET (spin));
1323 }
1324
1325 gtk_css_gadget_set_state (gtk_entry_get_gadget (GTK_ENTRY (widget)), gtk_widget_get_state_flags (widget));
1326 update_node_state (spin);
1327
1328 GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->state_flags_changed (widget, previous_state);
1329 }
1330
1331 static gint
gtk_spin_button_scroll(GtkWidget * widget,GdkEventScroll * event)1332 gtk_spin_button_scroll (GtkWidget *widget,
1333 GdkEventScroll *event)
1334 {
1335 GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1336 GtkSpinButtonPrivate *priv = spin->priv;
1337
1338 if (event->direction == GDK_SCROLL_UP)
1339 {
1340 if (!gtk_widget_has_focus (widget))
1341 gtk_widget_grab_focus (widget);
1342 gtk_spin_button_real_spin (spin, gtk_adjustment_get_step_increment (priv->adjustment));
1343 }
1344 else if (event->direction == GDK_SCROLL_DOWN)
1345 {
1346 if (!gtk_widget_has_focus (widget))
1347 gtk_widget_grab_focus (widget);
1348 gtk_spin_button_real_spin (spin, -gtk_adjustment_get_step_increment (priv->adjustment));
1349 }
1350 else
1351 return FALSE;
1352
1353 return TRUE;
1354 }
1355
1356 static gboolean
gtk_spin_button_stop_spinning(GtkSpinButton * spin)1357 gtk_spin_button_stop_spinning (GtkSpinButton *spin)
1358 {
1359 GtkSpinButtonPrivate *priv = spin->priv;
1360 gboolean did_spin = FALSE;
1361
1362 if (priv->timer)
1363 {
1364 g_source_remove (priv->timer);
1365 priv->timer = 0;
1366 priv->need_timer = FALSE;
1367
1368 did_spin = TRUE;
1369 }
1370
1371 priv->button = 0;
1372 priv->timer_step = gtk_adjustment_get_step_increment (priv->adjustment);
1373 priv->timer_calls = 0;
1374
1375 priv->click_child = NULL;
1376
1377 return did_spin;
1378 }
1379
1380 static void
start_spinning(GtkSpinButton * spin,GdkWindow * click_child,gdouble step)1381 start_spinning (GtkSpinButton *spin,
1382 GdkWindow *click_child,
1383 gdouble step)
1384 {
1385 GtkSpinButtonPrivate *priv;
1386
1387 priv = spin->priv;
1388
1389 priv->click_child = click_child;
1390
1391 if (!priv->timer)
1392 {
1393 priv->timer_step = step;
1394 priv->need_timer = TRUE;
1395 priv->timer = gdk_threads_add_timeout (TIMEOUT_INITIAL,
1396 (GSourceFunc) gtk_spin_button_timer,
1397 (gpointer) spin);
1398 g_source_set_name_by_id (priv->timer, "[gtk+] gtk_spin_button_timer");
1399 }
1400 gtk_spin_button_real_spin (spin, click_child == priv->up_panel ? step : -step);
1401
1402 gtk_widget_queue_draw (GTK_WIDGET (spin));
1403 }
1404
1405 static gint
gtk_spin_button_button_press(GtkWidget * widget,GdkEventButton * event)1406 gtk_spin_button_button_press (GtkWidget *widget,
1407 GdkEventButton *event)
1408 {
1409 GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1410 GtkSpinButtonPrivate *priv = spin->priv;
1411
1412 if (!priv->button)
1413 {
1414 if ((event->window == priv->down_panel) ||
1415 (event->window == priv->up_panel))
1416 {
1417 if (!gtk_widget_has_focus (widget))
1418 gtk_widget_grab_focus (widget);
1419 priv->button = event->button;
1420
1421 if (gtk_editable_get_editable (GTK_EDITABLE (widget))) {
1422 gtk_spin_button_update (spin);
1423
1424 if (event->button == GDK_BUTTON_PRIMARY)
1425 start_spinning (spin, event->window, gtk_adjustment_get_step_increment (priv->adjustment));
1426 else if (event->button == GDK_BUTTON_MIDDLE)
1427 start_spinning (spin, event->window, gtk_adjustment_get_page_increment (priv->adjustment));
1428 else
1429 priv->click_child = event->window;
1430 } else
1431 gtk_widget_error_bell (widget);
1432
1433 return TRUE;
1434 }
1435 else
1436 {
1437 return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->button_press_event (widget, event);
1438 }
1439 }
1440 return FALSE;
1441 }
1442
1443 static gint
gtk_spin_button_button_release(GtkWidget * widget,GdkEventButton * event)1444 gtk_spin_button_button_release (GtkWidget *widget,
1445 GdkEventButton *event)
1446 {
1447 GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1448 GtkSpinButtonPrivate *priv = spin->priv;
1449
1450 if (event->button == priv->button)
1451 {
1452 GdkWindow *click_child = priv->click_child;
1453
1454 gtk_spin_button_stop_spinning (spin);
1455
1456 if (event->button == GDK_BUTTON_SECONDARY)
1457 {
1458 gdouble diff;
1459
1460 if (event->window == priv->down_panel &&
1461 click_child == event->window)
1462 {
1463 diff = gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment);
1464 if (diff > EPSILON)
1465 gtk_spin_button_real_spin (spin, -diff);
1466 }
1467 else if (event->window == priv->up_panel &&
1468 click_child == event->window)
1469 {
1470 diff = gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_value (priv->adjustment);
1471 if (diff > EPSILON)
1472 gtk_spin_button_real_spin (spin, diff);
1473 }
1474 }
1475
1476 update_node_state (spin);
1477 gtk_widget_queue_draw (GTK_WIDGET (spin));
1478
1479 return TRUE;
1480 }
1481 else
1482 {
1483 return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->button_release_event (widget, event);
1484 }
1485 }
1486
1487 static gint
gtk_spin_button_motion_notify(GtkWidget * widget,GdkEventMotion * event)1488 gtk_spin_button_motion_notify (GtkWidget *widget,
1489 GdkEventMotion *event)
1490 {
1491 GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1492 GtkSpinButtonPrivate *priv = spin->priv;
1493
1494 if (priv->button)
1495 return FALSE;
1496
1497 if (event->window == priv->down_panel ||
1498 event->window == priv->up_panel)
1499 {
1500 gdk_event_request_motions (event);
1501
1502 priv->in_child = event->window;
1503 gtk_widget_queue_draw (widget);
1504
1505 return FALSE;
1506 }
1507
1508 if (gtk_gesture_is_recognized (priv->swipe_gesture))
1509 return TRUE;
1510
1511 return GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->motion_notify_event (widget, event);
1512 }
1513
1514 static gint
gtk_spin_button_timer(GtkSpinButton * spin_button)1515 gtk_spin_button_timer (GtkSpinButton *spin_button)
1516 {
1517 GtkSpinButtonPrivate *priv = spin_button->priv;
1518 gboolean retval = FALSE;
1519
1520 if (priv->timer)
1521 {
1522 if (priv->click_child == priv->up_panel)
1523 gtk_spin_button_real_spin (spin_button, priv->timer_step);
1524 else
1525 gtk_spin_button_real_spin (spin_button, -priv->timer_step);
1526
1527 if (priv->need_timer)
1528 {
1529 priv->need_timer = FALSE;
1530 priv->timer = gdk_threads_add_timeout (TIMEOUT_REPEAT,
1531 (GSourceFunc) gtk_spin_button_timer,
1532 (gpointer) spin_button);
1533 g_source_set_name_by_id (priv->timer, "[gtk+] gtk_spin_button_timer");
1534 }
1535 else
1536 {
1537 if (priv->climb_rate > 0.0 && priv->timer_step
1538 < gtk_adjustment_get_page_increment (priv->adjustment))
1539 {
1540 if (priv->timer_calls < MAX_TIMER_CALLS)
1541 priv->timer_calls++;
1542 else
1543 {
1544 priv->timer_calls = 0;
1545 priv->timer_step += priv->climb_rate;
1546 }
1547 }
1548 retval = TRUE;
1549 }
1550 }
1551
1552 return retval;
1553 }
1554
1555 static void
gtk_spin_button_value_changed(GtkAdjustment * adjustment,GtkSpinButton * spin_button)1556 gtk_spin_button_value_changed (GtkAdjustment *adjustment,
1557 GtkSpinButton *spin_button)
1558 {
1559 gboolean return_val;
1560
1561 g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
1562
1563 return_val = FALSE;
1564 g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
1565 if (return_val == FALSE)
1566 gtk_spin_button_default_output (spin_button);
1567
1568 g_signal_emit (spin_button, spinbutton_signals[VALUE_CHANGED], 0);
1569
1570 update_node_state (spin_button);
1571
1572 gtk_widget_queue_draw (GTK_WIDGET (spin_button));
1573
1574 g_object_notify (G_OBJECT (spin_button), "value");
1575 }
1576
1577 static void
gtk_spin_button_real_change_value(GtkSpinButton * spin,GtkScrollType scroll)1578 gtk_spin_button_real_change_value (GtkSpinButton *spin,
1579 GtkScrollType scroll)
1580 {
1581 GtkSpinButtonPrivate *priv = spin->priv;
1582 gdouble old_value;
1583
1584 if (!gtk_editable_get_editable (GTK_EDITABLE (spin)))
1585 {
1586 gtk_widget_error_bell (GTK_WIDGET (spin));
1587 return;
1588 }
1589
1590 /* When the key binding is activated, there may be an outstanding
1591 * value, so we first have to commit what is currently written in
1592 * the spin buttons text entry. See #106574
1593 */
1594 gtk_spin_button_update (spin);
1595
1596 old_value = gtk_adjustment_get_value (priv->adjustment);
1597
1598 switch (scroll)
1599 {
1600 case GTK_SCROLL_STEP_BACKWARD:
1601 case GTK_SCROLL_STEP_DOWN:
1602 case GTK_SCROLL_STEP_LEFT:
1603 gtk_spin_button_real_spin (spin, -priv->timer_step);
1604
1605 if (priv->climb_rate > 0.0 && priv->timer_step
1606 < gtk_adjustment_get_page_increment (priv->adjustment))
1607 {
1608 if (priv->timer_calls < MAX_TIMER_CALLS)
1609 priv->timer_calls++;
1610 else
1611 {
1612 priv->timer_calls = 0;
1613 priv->timer_step += priv->climb_rate;
1614 }
1615 }
1616 break;
1617
1618 case GTK_SCROLL_STEP_FORWARD:
1619 case GTK_SCROLL_STEP_UP:
1620 case GTK_SCROLL_STEP_RIGHT:
1621 gtk_spin_button_real_spin (spin, priv->timer_step);
1622
1623 if (priv->climb_rate > 0.0 && priv->timer_step
1624 < gtk_adjustment_get_page_increment (priv->adjustment))
1625 {
1626 if (priv->timer_calls < MAX_TIMER_CALLS)
1627 priv->timer_calls++;
1628 else
1629 {
1630 priv->timer_calls = 0;
1631 priv->timer_step += priv->climb_rate;
1632 }
1633 }
1634 break;
1635
1636 case GTK_SCROLL_PAGE_BACKWARD:
1637 case GTK_SCROLL_PAGE_DOWN:
1638 case GTK_SCROLL_PAGE_LEFT:
1639 gtk_spin_button_real_spin (spin, -gtk_adjustment_get_page_increment (priv->adjustment));
1640 break;
1641
1642 case GTK_SCROLL_PAGE_FORWARD:
1643 case GTK_SCROLL_PAGE_UP:
1644 case GTK_SCROLL_PAGE_RIGHT:
1645 gtk_spin_button_real_spin (spin, gtk_adjustment_get_page_increment (priv->adjustment));
1646 break;
1647
1648 case GTK_SCROLL_START:
1649 {
1650 gdouble diff = gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment);
1651 if (diff > EPSILON)
1652 gtk_spin_button_real_spin (spin, -diff);
1653 break;
1654 }
1655
1656 case GTK_SCROLL_END:
1657 {
1658 gdouble diff = gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_value (priv->adjustment);
1659 if (diff > EPSILON)
1660 gtk_spin_button_real_spin (spin, diff);
1661 break;
1662 }
1663
1664 default:
1665 g_warning ("Invalid scroll type %d for GtkSpinButton::change-value", scroll);
1666 break;
1667 }
1668
1669 gtk_spin_button_update (spin);
1670
1671 if (gtk_adjustment_get_value (priv->adjustment) == old_value)
1672 gtk_widget_error_bell (GTK_WIDGET (spin));
1673 }
1674
1675 static gint
gtk_spin_button_key_release(GtkWidget * widget,GdkEventKey * event)1676 gtk_spin_button_key_release (GtkWidget *widget,
1677 GdkEventKey *event)
1678 {
1679 GtkSpinButton *spin = GTK_SPIN_BUTTON (widget);
1680 GtkSpinButtonPrivate *priv = spin->priv;
1681
1682 /* We only get a release at the end of a key repeat run, so reset the timer_step */
1683 priv->timer_step = gtk_adjustment_get_step_increment (priv->adjustment);
1684 priv->timer_calls = 0;
1685
1686 return TRUE;
1687 }
1688
1689 static void
gtk_spin_button_snap(GtkSpinButton * spin_button,gdouble val)1690 gtk_spin_button_snap (GtkSpinButton *spin_button,
1691 gdouble val)
1692 {
1693 GtkSpinButtonPrivate *priv = spin_button->priv;
1694 gdouble inc;
1695 gdouble tmp;
1696
1697 inc = gtk_adjustment_get_step_increment (priv->adjustment);
1698 if (inc == 0)
1699 return;
1700
1701 tmp = (val - gtk_adjustment_get_lower (priv->adjustment)) / inc;
1702 if (tmp - floor (tmp) < ceil (tmp) - tmp)
1703 val = gtk_adjustment_get_lower (priv->adjustment) + floor (tmp) * inc;
1704 else
1705 val = gtk_adjustment_get_lower (priv->adjustment) + ceil (tmp) * inc;
1706
1707 gtk_spin_button_set_value (spin_button, val);
1708 }
1709
1710 static void
gtk_spin_button_activate(GtkEntry * entry)1711 gtk_spin_button_activate (GtkEntry *entry)
1712 {
1713 if (gtk_editable_get_editable (GTK_EDITABLE (entry)))
1714 gtk_spin_button_update (GTK_SPIN_BUTTON (entry));
1715
1716 /* Chain up so that entry->activates_default is honored */
1717 GTK_ENTRY_CLASS (gtk_spin_button_parent_class)->activate (entry);
1718 }
1719
1720 static void
gtk_spin_button_insert_text(GtkEditable * editable,const gchar * new_text,gint new_text_length,gint * position)1721 gtk_spin_button_insert_text (GtkEditable *editable,
1722 const gchar *new_text,
1723 gint new_text_length,
1724 gint *position)
1725 {
1726 GtkEntry *entry = GTK_ENTRY (editable);
1727 GtkSpinButton *spin = GTK_SPIN_BUTTON (editable);
1728 GtkSpinButtonPrivate *priv = spin->priv;
1729 GtkEditableInterface *parent_editable_iface;
1730
1731 parent_editable_iface = g_type_interface_peek (gtk_spin_button_parent_class,
1732 GTK_TYPE_EDITABLE);
1733
1734 if (priv->numeric)
1735 {
1736 struct lconv *lc;
1737 gboolean sign;
1738 gint dotpos = -1;
1739 gint i;
1740 guint32 pos_sign;
1741 guint32 neg_sign;
1742 gint entry_length;
1743 const gchar *entry_text;
1744
1745 entry_length = gtk_entry_get_text_length (entry);
1746 entry_text = gtk_entry_get_text (entry);
1747
1748 lc = localeconv ();
1749
1750 if (*(lc->negative_sign))
1751 neg_sign = *(lc->negative_sign);
1752 else
1753 neg_sign = '-';
1754
1755 if (*(lc->positive_sign))
1756 pos_sign = *(lc->positive_sign);
1757 else
1758 pos_sign = '+';
1759
1760 #ifdef G_OS_WIN32
1761 /* Workaround for bug caused by some Windows application messing
1762 * up the positive sign of the current locale, more specifically
1763 * HKEY_CURRENT_USER\Control Panel\International\sPositiveSign.
1764 * See bug #330743 and for instance
1765 * http://www.msnewsgroups.net/group/microsoft.public.dotnet.languages.csharp/topic36024.aspx
1766 *
1767 * I don't know if the positive sign always gets bogusly set to
1768 * a digit when the above Registry value is corrupted as
1769 * described. (In my test case, it got set to "8", and in the
1770 * bug report above it presumably was set ot "0".) Probably it
1771 * might get set to almost anything? So how to distinguish a
1772 * bogus value from some correct one for some locale? That is
1773 * probably hard, but at least we should filter out the
1774 * digits...
1775 */
1776 if (pos_sign >= '0' && pos_sign <= '9')
1777 pos_sign = '+';
1778 #endif
1779
1780 for (sign=0, i=0; i<entry_length; i++)
1781 if ((entry_text[i] == neg_sign) ||
1782 (entry_text[i] == pos_sign))
1783 {
1784 sign = 1;
1785 break;
1786 }
1787
1788 if (sign && !(*position))
1789 return;
1790
1791 for (dotpos=-1, i=0; i<entry_length; i++)
1792 if (entry_text[i] == *(lc->decimal_point))
1793 {
1794 dotpos = i;
1795 break;
1796 }
1797
1798 if (dotpos > -1 && *position > dotpos &&
1799 (gint)priv->digits - entry_length
1800 + dotpos - new_text_length + 1 < 0)
1801 return;
1802
1803 for (i = 0; i < new_text_length; i++)
1804 {
1805 if (new_text[i] == neg_sign || new_text[i] == pos_sign)
1806 {
1807 if (sign || (*position) || i)
1808 return;
1809 sign = TRUE;
1810 }
1811 else if (new_text[i] == *(lc->decimal_point))
1812 {
1813 if (!priv->digits || dotpos > -1 ||
1814 (new_text_length - 1 - i + entry_length
1815 - *position > (gint)priv->digits))
1816 return;
1817 dotpos = *position + i;
1818 }
1819 else if (new_text[i] < 0x30 || new_text[i] > 0x39)
1820 return;
1821 }
1822 }
1823
1824 parent_editable_iface->insert_text (editable, new_text,
1825 new_text_length, position);
1826 }
1827
1828 static void
gtk_spin_button_real_spin(GtkSpinButton * spin_button,gdouble increment)1829 gtk_spin_button_real_spin (GtkSpinButton *spin_button,
1830 gdouble increment)
1831 {
1832 GtkSpinButtonPrivate *priv = spin_button->priv;
1833 GtkAdjustment *adjustment;
1834 gdouble new_value = 0.0;
1835 gboolean wrapped = FALSE;
1836
1837 adjustment = priv->adjustment;
1838
1839 new_value = gtk_adjustment_get_value (adjustment) + increment;
1840
1841 if (increment > 0)
1842 {
1843 if (priv->wrap)
1844 {
1845 if (fabs (gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_upper (adjustment)) < EPSILON)
1846 {
1847 new_value = gtk_adjustment_get_lower (adjustment);
1848 wrapped = TRUE;
1849 }
1850 else if (new_value > gtk_adjustment_get_upper (adjustment))
1851 new_value = gtk_adjustment_get_upper (adjustment);
1852 }
1853 else
1854 new_value = MIN (new_value, gtk_adjustment_get_upper (adjustment));
1855 }
1856 else if (increment < 0)
1857 {
1858 if (priv->wrap)
1859 {
1860 if (fabs (gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_lower (adjustment)) < EPSILON)
1861 {
1862 new_value = gtk_adjustment_get_upper (adjustment);
1863 wrapped = TRUE;
1864 }
1865 else if (new_value < gtk_adjustment_get_lower (adjustment))
1866 new_value = gtk_adjustment_get_lower (adjustment);
1867 }
1868 else
1869 new_value = MAX (new_value, gtk_adjustment_get_lower (adjustment));
1870 }
1871
1872 if (fabs (new_value - gtk_adjustment_get_value (adjustment)) > EPSILON)
1873 gtk_adjustment_set_value (adjustment, new_value);
1874
1875 if (wrapped)
1876 g_signal_emit (spin_button, spinbutton_signals[WRAPPED], 0);
1877
1878 gtk_widget_queue_draw (GTK_WIDGET (spin_button));
1879 }
1880
1881 static gint
gtk_spin_button_default_input(GtkSpinButton * spin_button,gdouble * new_val)1882 gtk_spin_button_default_input (GtkSpinButton *spin_button,
1883 gdouble *new_val)
1884 {
1885 gchar *err = NULL;
1886 const char *text;
1887
1888 text = gtk_entry_get_text (GTK_ENTRY (spin_button));
1889
1890 *new_val = g_strtod (text, &err);
1891 if (*err)
1892 {
1893 /* try to convert with local digits */
1894 gint64 val = 0;
1895 int sign = 1;
1896 const char *p;
1897
1898 for (p = text; *p; p = g_utf8_next_char (p))
1899 {
1900 gunichar ch = g_utf8_get_char (p);
1901
1902 if (p == text && ch == '-')
1903 {
1904 sign = -1;
1905 continue;
1906 }
1907
1908 if (!g_unichar_isdigit (ch))
1909 break;
1910
1911 val = val * 10 + g_unichar_digit_value (ch);
1912 }
1913
1914 if (*p)
1915 return GTK_INPUT_ERROR;
1916
1917 *new_val = sign * val;
1918 }
1919
1920 return FALSE;
1921 }
1922
1923 static void
gtk_spin_button_default_output(GtkSpinButton * spin_button)1924 gtk_spin_button_default_output (GtkSpinButton *spin_button)
1925 {
1926 GtkSpinButtonPrivate *priv = spin_button->priv;
1927 gchar *buf = gtk_spin_button_format_for_value (spin_button,
1928 gtk_adjustment_get_value (priv->adjustment));
1929
1930 if (strcmp (buf, gtk_entry_get_text (GTK_ENTRY (spin_button))))
1931 gtk_entry_set_text (GTK_ENTRY (spin_button), buf);
1932
1933 g_free (buf);
1934 }
1935
1936
1937 /***********************************************************
1938 ***********************************************************
1939 *** Public interface ***
1940 ***********************************************************
1941 ***********************************************************/
1942
1943
1944 /**
1945 * gtk_spin_button_configure:
1946 * @spin_button: a #GtkSpinButton
1947 * @adjustment: (nullable): a #GtkAdjustment to replace the spin button’s
1948 * existing adjustment, or %NULL to leave its current adjustment unchanged
1949 * @climb_rate: the new climb rate
1950 * @digits: the number of decimal places to display in the spin button
1951 *
1952 * Changes the properties of an existing spin button. The adjustment,
1953 * climb rate, and number of decimal places are updated accordingly.
1954 */
1955 void
gtk_spin_button_configure(GtkSpinButton * spin_button,GtkAdjustment * adjustment,gdouble climb_rate,guint digits)1956 gtk_spin_button_configure (GtkSpinButton *spin_button,
1957 GtkAdjustment *adjustment,
1958 gdouble climb_rate,
1959 guint digits)
1960 {
1961 GtkSpinButtonPrivate *priv;
1962
1963 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
1964
1965 priv = spin_button->priv;
1966
1967 if (!adjustment)
1968 adjustment = priv->adjustment;
1969
1970 g_object_freeze_notify (G_OBJECT (spin_button));
1971
1972 if (priv->adjustment != adjustment)
1973 {
1974 gtk_spin_button_unset_adjustment (spin_button);
1975
1976 priv->adjustment = adjustment;
1977 g_object_ref_sink (adjustment);
1978 g_signal_connect (adjustment, "value-changed",
1979 G_CALLBACK (gtk_spin_button_value_changed),
1980 spin_button);
1981 g_signal_connect (adjustment, "changed",
1982 G_CALLBACK (adjustment_changed_cb),
1983 spin_button);
1984 priv->timer_step = gtk_adjustment_get_step_increment (priv->adjustment);
1985
1986 g_object_notify (G_OBJECT (spin_button), "adjustment");
1987 gtk_widget_queue_resize (GTK_WIDGET (spin_button));
1988 }
1989
1990 if (priv->digits != digits)
1991 {
1992 priv->digits = digits;
1993 g_object_notify (G_OBJECT (spin_button), "digits");
1994 }
1995
1996 if (priv->climb_rate != climb_rate)
1997 {
1998 priv->climb_rate = climb_rate;
1999 g_object_notify (G_OBJECT (spin_button), "climb-rate");
2000 }
2001
2002 g_object_thaw_notify (G_OBJECT (spin_button));
2003
2004 gtk_spin_button_value_changed (adjustment, spin_button);
2005 }
2006
2007 /**
2008 * gtk_spin_button_new:
2009 * @adjustment: (allow-none): the #GtkAdjustment object that this spin
2010 * button should use, or %NULL
2011 * @climb_rate: specifies by how much the rate of change in the value will
2012 * accelerate if you continue to hold down an up/down button or arrow key
2013 * @digits: the number of decimal places to display
2014 *
2015 * Creates a new #GtkSpinButton.
2016 *
2017 * Returns: The new spin button as a #GtkWidget
2018 */
2019 GtkWidget *
gtk_spin_button_new(GtkAdjustment * adjustment,gdouble climb_rate,guint digits)2020 gtk_spin_button_new (GtkAdjustment *adjustment,
2021 gdouble climb_rate,
2022 guint digits)
2023 {
2024 GtkSpinButton *spin;
2025
2026 if (adjustment)
2027 g_return_val_if_fail (GTK_IS_ADJUSTMENT (adjustment), NULL);
2028
2029 spin = g_object_new (GTK_TYPE_SPIN_BUTTON, NULL);
2030
2031 gtk_spin_button_configure (spin, adjustment, climb_rate, digits);
2032
2033 return GTK_WIDGET (spin);
2034 }
2035
2036 /**
2037 * gtk_spin_button_new_with_range:
2038 * @min: Minimum allowable value
2039 * @max: Maximum allowable value
2040 * @step: Increment added or subtracted by spinning the widget
2041 *
2042 * This is a convenience constructor that allows creation of a numeric
2043 * #GtkSpinButton without manually creating an adjustment. The value is
2044 * initially set to the minimum value and a page increment of 10 * @step
2045 * is the default. The precision of the spin button is equivalent to the
2046 * precision of @step.
2047 *
2048 * Note that the way in which the precision is derived works best if @step
2049 * is a power of ten. If the resulting precision is not suitable for your
2050 * needs, use gtk_spin_button_set_digits() to correct it.
2051 *
2052 * Returns: The new spin button as a #GtkWidget
2053 */
2054 GtkWidget *
gtk_spin_button_new_with_range(gdouble min,gdouble max,gdouble step)2055 gtk_spin_button_new_with_range (gdouble min,
2056 gdouble max,
2057 gdouble step)
2058 {
2059 GtkAdjustment *adjustment;
2060 GtkSpinButton *spin;
2061 gint digits;
2062
2063 g_return_val_if_fail (min <= max, NULL);
2064 g_return_val_if_fail (step != 0.0, NULL);
2065
2066 spin = g_object_new (GTK_TYPE_SPIN_BUTTON, NULL);
2067
2068 adjustment = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
2069
2070 if (fabs (step) >= 1.0 || step == 0.0)
2071 digits = 0;
2072 else {
2073 digits = abs ((gint) floor (log10 (fabs (step))));
2074 if (digits > MAX_DIGITS)
2075 digits = MAX_DIGITS;
2076 }
2077
2078 gtk_spin_button_configure (spin, adjustment, step, digits);
2079
2080 gtk_spin_button_set_numeric (spin, TRUE);
2081
2082 return GTK_WIDGET (spin);
2083 }
2084
2085 /**
2086 * gtk_spin_button_set_adjustment:
2087 * @spin_button: a #GtkSpinButton
2088 * @adjustment: a #GtkAdjustment to replace the existing adjustment
2089 *
2090 * Replaces the #GtkAdjustment associated with @spin_button.
2091 */
2092 void
gtk_spin_button_set_adjustment(GtkSpinButton * spin_button,GtkAdjustment * adjustment)2093 gtk_spin_button_set_adjustment (GtkSpinButton *spin_button,
2094 GtkAdjustment *adjustment)
2095 {
2096 GtkSpinButtonPrivate *priv;
2097
2098 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2099
2100 priv = spin_button->priv;
2101
2102 if (!adjustment)
2103 adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
2104
2105 gtk_spin_button_configure (spin_button,
2106 adjustment,
2107 priv->climb_rate,
2108 priv->digits);
2109 }
2110
2111 /**
2112 * gtk_spin_button_get_adjustment:
2113 * @spin_button: a #GtkSpinButton
2114 *
2115 * Get the adjustment associated with a #GtkSpinButton
2116 *
2117 * Returns: (transfer none): the #GtkAdjustment of @spin_button
2118 **/
2119 GtkAdjustment *
gtk_spin_button_get_adjustment(GtkSpinButton * spin_button)2120 gtk_spin_button_get_adjustment (GtkSpinButton *spin_button)
2121 {
2122 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), NULL);
2123
2124 return spin_button->priv->adjustment;
2125 }
2126
2127 /**
2128 * gtk_spin_button_set_digits:
2129 * @spin_button: a #GtkSpinButton
2130 * @digits: the number of digits after the decimal point to be displayed for the spin button’s value
2131 *
2132 * Set the precision to be displayed by @spin_button. Up to 20 digit precision
2133 * is allowed.
2134 **/
2135 void
gtk_spin_button_set_digits(GtkSpinButton * spin_button,guint digits)2136 gtk_spin_button_set_digits (GtkSpinButton *spin_button,
2137 guint digits)
2138 {
2139 GtkSpinButtonPrivate *priv;
2140
2141 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2142
2143 priv = spin_button->priv;
2144
2145 if (priv->digits != digits)
2146 {
2147 priv->digits = digits;
2148 gtk_spin_button_value_changed (priv->adjustment, spin_button);
2149 g_object_notify (G_OBJECT (spin_button), "digits");
2150
2151 /* since lower/upper may have changed */
2152 gtk_widget_queue_resize (GTK_WIDGET (spin_button));
2153 }
2154 }
2155
2156 /**
2157 * gtk_spin_button_get_digits:
2158 * @spin_button: a #GtkSpinButton
2159 *
2160 * Fetches the precision of @spin_button. See gtk_spin_button_set_digits().
2161 *
2162 * Returns: the current precision
2163 **/
2164 guint
gtk_spin_button_get_digits(GtkSpinButton * spin_button)2165 gtk_spin_button_get_digits (GtkSpinButton *spin_button)
2166 {
2167 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0);
2168
2169 return spin_button->priv->digits;
2170 }
2171
2172 /**
2173 * gtk_spin_button_set_increments:
2174 * @spin_button: a #GtkSpinButton
2175 * @step: increment applied for a button 1 press.
2176 * @page: increment applied for a button 2 press.
2177 *
2178 * Sets the step and page increments for spin_button. This affects how
2179 * quickly the value changes when the spin button’s arrows are activated.
2180 **/
2181 void
gtk_spin_button_set_increments(GtkSpinButton * spin_button,gdouble step,gdouble page)2182 gtk_spin_button_set_increments (GtkSpinButton *spin_button,
2183 gdouble step,
2184 gdouble page)
2185 {
2186 GtkSpinButtonPrivate *priv;
2187
2188 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2189
2190 priv = spin_button->priv;
2191
2192 gtk_adjustment_configure (priv->adjustment,
2193 gtk_adjustment_get_value (priv->adjustment),
2194 gtk_adjustment_get_lower (priv->adjustment),
2195 gtk_adjustment_get_upper (priv->adjustment),
2196 step,
2197 page,
2198 gtk_adjustment_get_page_size (priv->adjustment));
2199 }
2200
2201 /**
2202 * gtk_spin_button_get_increments:
2203 * @spin_button: a #GtkSpinButton
2204 * @step: (out) (allow-none): location to store step increment, or %NULL
2205 * @page: (out) (allow-none): location to store page increment, or %NULL
2206 *
2207 * Gets the current step and page the increments used by @spin_button. See
2208 * gtk_spin_button_set_increments().
2209 **/
2210 void
gtk_spin_button_get_increments(GtkSpinButton * spin_button,gdouble * step,gdouble * page)2211 gtk_spin_button_get_increments (GtkSpinButton *spin_button,
2212 gdouble *step,
2213 gdouble *page)
2214 {
2215 GtkSpinButtonPrivate *priv;
2216
2217 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2218
2219 priv = spin_button->priv;
2220
2221 if (step)
2222 *step = gtk_adjustment_get_step_increment (priv->adjustment);
2223 if (page)
2224 *page = gtk_adjustment_get_page_increment (priv->adjustment);
2225 }
2226
2227 /**
2228 * gtk_spin_button_set_range:
2229 * @spin_button: a #GtkSpinButton
2230 * @min: minimum allowable value
2231 * @max: maximum allowable value
2232 *
2233 * Sets the minimum and maximum allowable values for @spin_button.
2234 *
2235 * If the current value is outside this range, it will be adjusted
2236 * to fit within the range, otherwise it will remain unchanged.
2237 */
2238 void
gtk_spin_button_set_range(GtkSpinButton * spin_button,gdouble min,gdouble max)2239 gtk_spin_button_set_range (GtkSpinButton *spin_button,
2240 gdouble min,
2241 gdouble max)
2242 {
2243 GtkAdjustment *adjustment;
2244
2245 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2246
2247 adjustment = spin_button->priv->adjustment;
2248
2249 gtk_adjustment_configure (adjustment,
2250 CLAMP (gtk_adjustment_get_value (adjustment), min, max),
2251 min,
2252 max,
2253 gtk_adjustment_get_step_increment (adjustment),
2254 gtk_adjustment_get_page_increment (adjustment),
2255 gtk_adjustment_get_page_size (adjustment));
2256 }
2257
2258 /**
2259 * gtk_spin_button_get_range:
2260 * @spin_button: a #GtkSpinButton
2261 * @min: (out) (allow-none): location to store minimum allowed value, or %NULL
2262 * @max: (out) (allow-none): location to store maximum allowed value, or %NULL
2263 *
2264 * Gets the range allowed for @spin_button.
2265 * See gtk_spin_button_set_range().
2266 */
2267 void
gtk_spin_button_get_range(GtkSpinButton * spin_button,gdouble * min,gdouble * max)2268 gtk_spin_button_get_range (GtkSpinButton *spin_button,
2269 gdouble *min,
2270 gdouble *max)
2271 {
2272 GtkSpinButtonPrivate *priv;
2273
2274 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2275
2276 priv = spin_button->priv;
2277
2278 if (min)
2279 *min = gtk_adjustment_get_lower (priv->adjustment);
2280 if (max)
2281 *max = gtk_adjustment_get_upper (priv->adjustment);
2282 }
2283
2284 /**
2285 * gtk_spin_button_get_value:
2286 * @spin_button: a #GtkSpinButton
2287 *
2288 * Get the value in the @spin_button.
2289 *
2290 * Returns: the value of @spin_button
2291 */
2292 gdouble
gtk_spin_button_get_value(GtkSpinButton * spin_button)2293 gtk_spin_button_get_value (GtkSpinButton *spin_button)
2294 {
2295 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0.0);
2296
2297 return gtk_adjustment_get_value (spin_button->priv->adjustment);
2298 }
2299
2300 /**
2301 * gtk_spin_button_get_value_as_int:
2302 * @spin_button: a #GtkSpinButton
2303 *
2304 * Get the value @spin_button represented as an integer.
2305 *
2306 * Returns: the value of @spin_button
2307 */
2308 gint
gtk_spin_button_get_value_as_int(GtkSpinButton * spin_button)2309 gtk_spin_button_get_value_as_int (GtkSpinButton *spin_button)
2310 {
2311 GtkSpinButtonPrivate *priv;
2312 gdouble val;
2313
2314 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), 0);
2315
2316 priv = spin_button->priv;
2317
2318 val = gtk_adjustment_get_value (priv->adjustment);
2319 if (val - floor (val) < ceil (val) - val)
2320 return floor (val);
2321 else
2322 return ceil (val);
2323 }
2324
2325 /**
2326 * gtk_spin_button_set_value:
2327 * @spin_button: a #GtkSpinButton
2328 * @value: the new value
2329 *
2330 * Sets the value of @spin_button.
2331 */
2332 void
gtk_spin_button_set_value(GtkSpinButton * spin_button,gdouble value)2333 gtk_spin_button_set_value (GtkSpinButton *spin_button,
2334 gdouble value)
2335 {
2336 GtkSpinButtonPrivate *priv;
2337
2338 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2339
2340 priv = spin_button->priv;
2341
2342 if (fabs (value - gtk_adjustment_get_value (priv->adjustment)) > EPSILON)
2343 gtk_adjustment_set_value (priv->adjustment, value);
2344 else
2345 {
2346 gint return_val = FALSE;
2347 g_signal_emit (spin_button, spinbutton_signals[OUTPUT], 0, &return_val);
2348 if (!return_val)
2349 gtk_spin_button_default_output (spin_button);
2350 }
2351 }
2352
2353 /**
2354 * gtk_spin_button_set_update_policy:
2355 * @spin_button: a #GtkSpinButton
2356 * @policy: a #GtkSpinButtonUpdatePolicy value
2357 *
2358 * Sets the update behavior of a spin button.
2359 * This determines whether the spin button is always updated
2360 * or only when a valid value is set.
2361 */
2362 void
gtk_spin_button_set_update_policy(GtkSpinButton * spin_button,GtkSpinButtonUpdatePolicy policy)2363 gtk_spin_button_set_update_policy (GtkSpinButton *spin_button,
2364 GtkSpinButtonUpdatePolicy policy)
2365 {
2366 GtkSpinButtonPrivate *priv;
2367
2368 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2369
2370 priv = spin_button->priv;
2371
2372 if (priv->update_policy != policy)
2373 {
2374 priv->update_policy = policy;
2375 g_object_notify (G_OBJECT (spin_button), "update-policy");
2376 }
2377 }
2378
2379 /**
2380 * gtk_spin_button_get_update_policy:
2381 * @spin_button: a #GtkSpinButton
2382 *
2383 * Gets the update behavior of a spin button.
2384 * See gtk_spin_button_set_update_policy().
2385 *
2386 * Returns: the current update policy
2387 */
2388 GtkSpinButtonUpdatePolicy
gtk_spin_button_get_update_policy(GtkSpinButton * spin_button)2389 gtk_spin_button_get_update_policy (GtkSpinButton *spin_button)
2390 {
2391 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), GTK_UPDATE_ALWAYS);
2392
2393 return spin_button->priv->update_policy;
2394 }
2395
2396 /**
2397 * gtk_spin_button_set_numeric:
2398 * @spin_button: a #GtkSpinButton
2399 * @numeric: flag indicating if only numeric entry is allowed
2400 *
2401 * Sets the flag that determines if non-numeric text can be typed
2402 * into the spin button.
2403 */
2404 void
gtk_spin_button_set_numeric(GtkSpinButton * spin_button,gboolean numeric)2405 gtk_spin_button_set_numeric (GtkSpinButton *spin_button,
2406 gboolean numeric)
2407 {
2408 GtkSpinButtonPrivate *priv;
2409
2410 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2411
2412 priv = spin_button->priv;
2413
2414 numeric = numeric != FALSE;
2415
2416 if (priv->numeric != numeric)
2417 {
2418 priv->numeric = numeric;
2419 g_object_notify (G_OBJECT (spin_button), "numeric");
2420 }
2421 }
2422
2423 /**
2424 * gtk_spin_button_get_numeric:
2425 * @spin_button: a #GtkSpinButton
2426 *
2427 * Returns whether non-numeric text can be typed into the spin button.
2428 * See gtk_spin_button_set_numeric().
2429 *
2430 * Returns: %TRUE if only numeric text can be entered
2431 */
2432 gboolean
gtk_spin_button_get_numeric(GtkSpinButton * spin_button)2433 gtk_spin_button_get_numeric (GtkSpinButton *spin_button)
2434 {
2435 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2436
2437 return spin_button->priv->numeric;
2438 }
2439
2440 /**
2441 * gtk_spin_button_set_wrap:
2442 * @spin_button: a #GtkSpinButton
2443 * @wrap: a flag indicating if wrapping behavior is performed
2444 *
2445 * Sets the flag that determines if a spin button value wraps
2446 * around to the opposite limit when the upper or lower limit
2447 * of the range is exceeded.
2448 */
2449 void
gtk_spin_button_set_wrap(GtkSpinButton * spin_button,gboolean wrap)2450 gtk_spin_button_set_wrap (GtkSpinButton *spin_button,
2451 gboolean wrap)
2452 {
2453 GtkSpinButtonPrivate *priv;
2454
2455 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2456
2457 priv = spin_button->priv;
2458
2459 wrap = wrap != FALSE;
2460
2461 if (priv->wrap != wrap)
2462 {
2463 priv->wrap = wrap;
2464 g_object_notify (G_OBJECT (spin_button), "wrap");
2465
2466 update_node_state (spin_button);
2467 }
2468 }
2469
2470 /**
2471 * gtk_spin_button_get_wrap:
2472 * @spin_button: a #GtkSpinButton
2473 *
2474 * Returns whether the spin button’s value wraps around to the
2475 * opposite limit when the upper or lower limit of the range is
2476 * exceeded. See gtk_spin_button_set_wrap().
2477 *
2478 * Returns: %TRUE if the spin button wraps around
2479 */
2480 gboolean
gtk_spin_button_get_wrap(GtkSpinButton * spin_button)2481 gtk_spin_button_get_wrap (GtkSpinButton *spin_button)
2482 {
2483 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2484
2485 return spin_button->priv->wrap;
2486 }
2487
2488 /**
2489 * gtk_spin_button_set_snap_to_ticks:
2490 * @spin_button: a #GtkSpinButton
2491 * @snap_to_ticks: a flag indicating if invalid values should be corrected
2492 *
2493 * Sets the policy as to whether values are corrected to the
2494 * nearest step increment when a spin button is activated after
2495 * providing an invalid value.
2496 */
2497 void
gtk_spin_button_set_snap_to_ticks(GtkSpinButton * spin_button,gboolean snap_to_ticks)2498 gtk_spin_button_set_snap_to_ticks (GtkSpinButton *spin_button,
2499 gboolean snap_to_ticks)
2500 {
2501 GtkSpinButtonPrivate *priv;
2502 guint new_val;
2503
2504 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2505
2506 priv = spin_button->priv;
2507
2508 new_val = (snap_to_ticks != 0);
2509
2510 if (new_val != priv->snap_to_ticks)
2511 {
2512 priv->snap_to_ticks = new_val;
2513 if (new_val && gtk_editable_get_editable (GTK_EDITABLE (spin_button)))
2514 gtk_spin_button_update (spin_button);
2515
2516 g_object_notify (G_OBJECT (spin_button), "snap-to-ticks");
2517 }
2518 }
2519
2520 /**
2521 * gtk_spin_button_get_snap_to_ticks:
2522 * @spin_button: a #GtkSpinButton
2523 *
2524 * Returns whether the values are corrected to the nearest step.
2525 * See gtk_spin_button_set_snap_to_ticks().
2526 *
2527 * Returns: %TRUE if values are snapped to the nearest step
2528 */
2529 gboolean
gtk_spin_button_get_snap_to_ticks(GtkSpinButton * spin_button)2530 gtk_spin_button_get_snap_to_ticks (GtkSpinButton *spin_button)
2531 {
2532 g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), FALSE);
2533
2534 return spin_button->priv->snap_to_ticks;
2535 }
2536
2537 /**
2538 * gtk_spin_button_spin:
2539 * @spin_button: a #GtkSpinButton
2540 * @direction: a #GtkSpinType indicating the direction to spin
2541 * @increment: step increment to apply in the specified direction
2542 *
2543 * Increment or decrement a spin button’s value in a specified
2544 * direction by a specified amount.
2545 */
2546 void
gtk_spin_button_spin(GtkSpinButton * spin_button,GtkSpinType direction,gdouble increment)2547 gtk_spin_button_spin (GtkSpinButton *spin_button,
2548 GtkSpinType direction,
2549 gdouble increment)
2550 {
2551 GtkSpinButtonPrivate *priv;
2552 GtkAdjustment *adjustment;
2553 gdouble diff;
2554
2555 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2556
2557 priv = spin_button->priv;
2558
2559 adjustment = priv->adjustment;
2560
2561 /* for compatibility with the 1.0.x version of this function */
2562 if (increment != 0 && increment != gtk_adjustment_get_step_increment (adjustment) &&
2563 (direction == GTK_SPIN_STEP_FORWARD ||
2564 direction == GTK_SPIN_STEP_BACKWARD))
2565 {
2566 if (direction == GTK_SPIN_STEP_BACKWARD && increment > 0)
2567 increment = -increment;
2568 direction = GTK_SPIN_USER_DEFINED;
2569 }
2570
2571 switch (direction)
2572 {
2573 case GTK_SPIN_STEP_FORWARD:
2574
2575 gtk_spin_button_real_spin (spin_button, gtk_adjustment_get_step_increment (adjustment));
2576 break;
2577
2578 case GTK_SPIN_STEP_BACKWARD:
2579
2580 gtk_spin_button_real_spin (spin_button, -gtk_adjustment_get_step_increment (adjustment));
2581 break;
2582
2583 case GTK_SPIN_PAGE_FORWARD:
2584
2585 gtk_spin_button_real_spin (spin_button, gtk_adjustment_get_page_increment (adjustment));
2586 break;
2587
2588 case GTK_SPIN_PAGE_BACKWARD:
2589
2590 gtk_spin_button_real_spin (spin_button, -gtk_adjustment_get_page_increment (adjustment));
2591 break;
2592
2593 case GTK_SPIN_HOME:
2594
2595 diff = gtk_adjustment_get_value (adjustment) - gtk_adjustment_get_lower (adjustment);
2596 if (diff > EPSILON)
2597 gtk_spin_button_real_spin (spin_button, -diff);
2598 break;
2599
2600 case GTK_SPIN_END:
2601
2602 diff = gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_value (adjustment);
2603 if (diff > EPSILON)
2604 gtk_spin_button_real_spin (spin_button, diff);
2605 break;
2606
2607 case GTK_SPIN_USER_DEFINED:
2608
2609 if (increment != 0)
2610 gtk_spin_button_real_spin (spin_button, increment);
2611 break;
2612
2613 default:
2614 break;
2615 }
2616 }
2617
2618 /**
2619 * gtk_spin_button_update:
2620 * @spin_button: a #GtkSpinButton
2621 *
2622 * Manually force an update of the spin button.
2623 */
2624 void
gtk_spin_button_update(GtkSpinButton * spin_button)2625 gtk_spin_button_update (GtkSpinButton *spin_button)
2626 {
2627 GtkSpinButtonPrivate *priv;
2628 gdouble val;
2629 gint error = 0;
2630 gint return_val;
2631
2632 g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button));
2633
2634 priv = spin_button->priv;
2635
2636 return_val = FALSE;
2637 g_signal_emit (spin_button, spinbutton_signals[INPUT], 0, &val, &return_val);
2638 if (return_val == FALSE)
2639 {
2640 return_val = gtk_spin_button_default_input (spin_button, &val);
2641 error = (return_val == GTK_INPUT_ERROR);
2642 }
2643 else if (return_val == GTK_INPUT_ERROR)
2644 error = 1;
2645
2646 gtk_widget_queue_draw (GTK_WIDGET (spin_button));
2647
2648 if (priv->update_policy == GTK_UPDATE_ALWAYS)
2649 {
2650 if (val < gtk_adjustment_get_lower (priv->adjustment))
2651 val = gtk_adjustment_get_lower (priv->adjustment);
2652 else if (val > gtk_adjustment_get_upper (priv->adjustment))
2653 val = gtk_adjustment_get_upper (priv->adjustment);
2654 }
2655 else if ((priv->update_policy == GTK_UPDATE_IF_VALID) &&
2656 (error ||
2657 val < gtk_adjustment_get_lower (priv->adjustment) ||
2658 val > gtk_adjustment_get_upper (priv->adjustment)))
2659 {
2660 gtk_spin_button_value_changed (priv->adjustment, spin_button);
2661 return;
2662 }
2663
2664 if (priv->snap_to_ticks)
2665 gtk_spin_button_snap (spin_button, val);
2666 else
2667 gtk_spin_button_set_value (spin_button, val);
2668 }
2669
2670 void
_gtk_spin_button_get_panels(GtkSpinButton * spin_button,GdkWindow ** down_panel,GdkWindow ** up_panel)2671 _gtk_spin_button_get_panels (GtkSpinButton *spin_button,
2672 GdkWindow **down_panel,
2673 GdkWindow **up_panel)
2674 {
2675 if (down_panel != NULL)
2676 *down_panel = spin_button->priv->down_panel;
2677
2678 if (up_panel != NULL)
2679 *up_panel = spin_button->priv->up_panel;
2680 }
2681
2682 static void
gtk_spin_button_direction_changed(GtkWidget * widget,GtkTextDirection previous_dir)2683 gtk_spin_button_direction_changed (GtkWidget *widget,
2684 GtkTextDirection previous_dir)
2685 {
2686 update_node_ordering (GTK_SPIN_BUTTON (widget));
2687
2688 GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->direction_changed (widget, previous_dir);
2689 }
2690