1/* GtkCellRendererSpin
2 * Copyright (C) 2004 Lorenzo Gil Sanchez
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Lorenzo Gil Sanchez <lgs@sicem.biz>
18 * Carlos Garnacho Parro <carlosg@gnome.org>
19 */
20
21#include "config.h"
22
23#include "gtkcellrendererspin.h"
24
25#include "gtkadjustment.h"
26#include "gtkintl.h"
27#include "gtkprivate.h"
28#include "gtkspinbutton.h"
29#include "gtkentry.h"
30#include "gtkeventcontrollerkey.h"
31
32
33/**
34 * GtkCellRendererSpin:
35 *
36 * Renders a spin button in a cell
37 *
38 * `GtkCellRendererSpin` renders text in a cell like `GtkCellRendererText` from
39 * which it is derived. But while `GtkCellRendererText` offers a simple entry to
40 * edit the text, `GtkCellRendererSpin` offers a `GtkSpinButton` widget. Of course,
41 * that means that the text has to be parseable as a floating point number.
42 *
43 * The range of the spinbutton is taken from the adjustment property of the
44 * cell renderer, which can be set explicitly or mapped to a column in the
45 * tree model, like all properties of cell renders. `GtkCellRendererSpin`
46 * also has properties for the `GtkCellRendererSpin:climb-rate` and the number
47 * of `GtkCellRendererSpin:digits` to display. Other `GtkSpinButton` properties
48 * can be set in a handler for the `GtkCellRenderer::editing-started` signal.
49 *
50 * The `GtkCellRendererSpin` cell renderer was added in GTK 2.10.
51 */
52
53typedef struct _GtkCellRendererSpinClass GtkCellRendererSpinClass;
54typedef struct _GtkCellRendererSpinPrivate GtkCellRendererSpinPrivate;
55
56struct _GtkCellRendererSpin
57{
58 GtkCellRendererText parent;
59};
60
61struct _GtkCellRendererSpinClass
62{
63 GtkCellRendererTextClass parent;
64};
65
66struct _GtkCellRendererSpinPrivate
67{
68 GtkWidget *spin;
69 GtkAdjustment *adjustment;
70 double climb_rate;
71 guint digits;
72};
73
74static void gtk_cell_renderer_spin_finalize (GObject *object);
75
76static void gtk_cell_renderer_spin_get_property (GObject *object,
77 guint prop_id,
78 GValue *value,
79 GParamSpec *spec);
80static void gtk_cell_renderer_spin_set_property (GObject *object,
81 guint prop_id,
82 const GValue *value,
83 GParamSpec *spec);
84
85static gboolean gtk_cell_renderer_spin_key_pressed (GtkEventControllerKey *controller,
86 guint keyval,
87 guint keycode,
88 GdkModifierType state,
89 GtkWidget *widget);
90
91static GtkCellEditable * gtk_cell_renderer_spin_start_editing (GtkCellRenderer *cell,
92 GdkEvent *event,
93 GtkWidget *widget,
94 const char *path,
95 const GdkRectangle *background_area,
96 const GdkRectangle *cell_area,
97 GtkCellRendererState flags);
98enum {
99 PROP_0,
100 PROP_ADJUSTMENT,
101 PROP_CLIMB_RATE,
102 PROP_DIGITS
103};
104
105#define GTK_CELL_RENDERER_SPIN_PATH "gtk-cell-renderer-spin-path"
106
107G_DEFINE_TYPE_WITH_PRIVATE (GtkCellRendererSpin, gtk_cell_renderer_spin, GTK_TYPE_CELL_RENDERER_TEXT)
108
109
110static void
111gtk_cell_renderer_spin_class_init (GtkCellRendererSpinClass *klass)
112{
113 GObjectClass *object_class = G_OBJECT_CLASS (klass);
114 GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass);
115
116 object_class->finalize = gtk_cell_renderer_spin_finalize;
117 object_class->get_property = gtk_cell_renderer_spin_get_property;
118 object_class->set_property = gtk_cell_renderer_spin_set_property;
119
120 cell_class->start_editing = gtk_cell_renderer_spin_start_editing;
121
122 /**
123 * GtkCellRendererSpin:adjustment:
124 *
125 * The adjustment that holds the value of the spinbutton.
126 * This must be non-%NULL for the cell renderer to be editable.
127 */
128 g_object_class_install_property (oclass: object_class,
129 property_id: PROP_ADJUSTMENT,
130 pspec: g_param_spec_object (name: "adjustment",
131 P_("Adjustment"),
132 P_("The adjustment that holds the value of the spin button"),
133 GTK_TYPE_ADJUSTMENT,
134 GTK_PARAM_READWRITE));
135
136
137 /**
138 * GtkCellRendererSpin:climb-rate:
139 *
140 * The acceleration rate when you hold down a button.
141 */
142 g_object_class_install_property (oclass: object_class,
143 property_id: PROP_CLIMB_RATE,
144 pspec: g_param_spec_double (name: "climb-rate",
145 P_("Climb rate"),
146 P_("The acceleration rate when you hold down a button"),
147 minimum: 0.0, G_MAXDOUBLE, default_value: 0.0,
148 GTK_PARAM_READWRITE));
149 /**
150 * GtkCellRendererSpin:digits:
151 *
152 * The number of decimal places to display.
153 */
154 g_object_class_install_property (oclass: object_class,
155 property_id: PROP_DIGITS,
156 pspec: g_param_spec_uint (name: "digits",
157 P_("Digits"),
158 P_("The number of decimal places to display"),
159 minimum: 0, maximum: 20, default_value: 0,
160 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
161}
162
163static void
164gtk_cell_renderer_spin_init (GtkCellRendererSpin *self)
165{
166 GtkCellRendererSpinPrivate *priv = gtk_cell_renderer_spin_get_instance_private (self);
167
168 priv->adjustment = NULL;
169 priv->climb_rate = 0.0;
170 priv->digits = 0;
171}
172
173static void
174gtk_cell_renderer_spin_finalize (GObject *object)
175{
176 GtkCellRendererSpinPrivate *priv = gtk_cell_renderer_spin_get_instance_private (GTK_CELL_RENDERER_SPIN (object));
177
178 g_clear_object (&priv->adjustment);
179 g_clear_object (&priv->spin);
180
181 G_OBJECT_CLASS (gtk_cell_renderer_spin_parent_class)->finalize (object);
182}
183
184static void
185gtk_cell_renderer_spin_get_property (GObject *object,
186 guint prop_id,
187 GValue *value,
188 GParamSpec *pspec)
189{
190 GtkCellRendererSpinPrivate *priv = gtk_cell_renderer_spin_get_instance_private (GTK_CELL_RENDERER_SPIN (object));
191
192 switch (prop_id)
193 {
194 case PROP_ADJUSTMENT:
195 g_value_set_object (value, v_object: priv->adjustment);
196 break;
197 case PROP_CLIMB_RATE:
198 g_value_set_double (value, v_double: priv->climb_rate);
199 break;
200 case PROP_DIGITS:
201 g_value_set_uint (value, v_uint: priv->digits);
202 break;
203 default:
204 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
205 break;
206 }
207}
208
209static void
210gtk_cell_renderer_spin_set_property (GObject *object,
211 guint prop_id,
212 const GValue *value,
213 GParamSpec *pspec)
214{
215 GtkCellRendererSpinPrivate *priv = gtk_cell_renderer_spin_get_instance_private (GTK_CELL_RENDERER_SPIN (object));
216 GObject *obj;
217
218 switch (prop_id)
219 {
220 case PROP_ADJUSTMENT:
221 obj = g_value_get_object (value);
222
223 if (priv->adjustment)
224 {
225 g_object_unref (object: priv->adjustment);
226 priv->adjustment = NULL;
227 }
228
229 if (obj)
230 priv->adjustment = GTK_ADJUSTMENT (g_object_ref_sink (obj));
231
232 break;
233 case PROP_CLIMB_RATE:
234 priv->climb_rate = g_value_get_double (value);
235 break;
236 case PROP_DIGITS:
237 if (priv->digits != g_value_get_uint (value))
238 {
239 priv->digits = g_value_get_uint (value);
240 g_object_notify_by_pspec (object, pspec);
241 }
242 break;
243 default:
244 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
245 break;
246 }
247}
248
249static void
250gtk_cell_renderer_spin_focus_changed (GtkWidget *widget,
251 GParamSpec *pspec,
252 gpointer data)
253{
254 const char *path;
255 const char *new_text;
256 gboolean canceled;
257
258 if (gtk_widget_has_focus (widget))
259 return;
260
261 g_object_get (object: widget, first_property_name: "editing-canceled", &canceled, NULL);
262
263 g_signal_handlers_disconnect_by_func (widget,
264 gtk_cell_renderer_spin_focus_changed,
265 data);
266
267 gtk_cell_renderer_stop_editing (GTK_CELL_RENDERER (data), canceled);
268
269 if (canceled)
270 return;
271
272 path = g_object_get_data (G_OBJECT (widget), GTK_CELL_RENDERER_SPIN_PATH);
273 new_text = gtk_editable_get_text (GTK_EDITABLE (widget));
274 g_signal_emit_by_name (instance: data, detailed_signal: "edited", path, new_text);
275}
276
277static gboolean
278gtk_cell_renderer_spin_key_pressed (GtkEventControllerKey *controller,
279 guint keyval,
280 guint keycode,
281 GdkModifierType state,
282 GtkWidget *widget)
283{
284 if (state == 0)
285 {
286 if (keyval == GDK_KEY_Up)
287 {
288 gtk_spin_button_spin (GTK_SPIN_BUTTON (widget), direction: GTK_SPIN_STEP_FORWARD, increment: 1);
289 return TRUE;
290 }
291 else if (keyval == GDK_KEY_Down)
292 {
293 gtk_spin_button_spin (GTK_SPIN_BUTTON (widget), direction: GTK_SPIN_STEP_BACKWARD, increment: 1);
294 return TRUE;
295 }
296 }
297
298 return FALSE;
299}
300
301static void
302gtk_cell_renderer_spin_editing_done (GtkSpinButton *spin,
303 GtkCellRendererSpin *cell)
304{
305 GtkCellRendererSpinPrivate *priv = gtk_cell_renderer_spin_get_instance_private (GTK_CELL_RENDERER_SPIN (cell));
306 gboolean canceled;
307 const char *path;
308 const char *new_text;
309
310 g_clear_object (&priv->spin);
311
312 g_object_get (object: spin, first_property_name: "editing-canceled", &canceled, NULL);
313 gtk_cell_renderer_stop_editing (GTK_CELL_RENDERER (cell), canceled);
314
315 if (canceled)
316 return;
317
318 path = g_object_get_data (G_OBJECT (spin), GTK_CELL_RENDERER_SPIN_PATH);
319 new_text = gtk_editable_get_text (GTK_EDITABLE (spin));
320 g_signal_emit_by_name (instance: cell, detailed_signal: "edited", path, new_text);
321}
322
323static GtkCellEditable *
324gtk_cell_renderer_spin_start_editing (GtkCellRenderer *cell,
325 GdkEvent *event,
326 GtkWidget *widget,
327 const char *path,
328 const GdkRectangle *background_area,
329 const GdkRectangle *cell_area,
330 GtkCellRendererState flags)
331{
332 GtkCellRendererSpinPrivate *priv = gtk_cell_renderer_spin_get_instance_private (GTK_CELL_RENDERER_SPIN (cell));
333 GtkCellRendererText *cell_text = GTK_CELL_RENDERER_TEXT (cell);
334 GtkEventController *key_controller;
335 gboolean editable;
336 char *text;
337
338 g_object_get (object: cell_text, first_property_name: "editable", &editable, NULL);
339 if (!editable)
340 return NULL;
341
342 if (!priv->adjustment)
343 return NULL;
344
345 priv->spin = gtk_spin_button_new (adjustment: priv->adjustment, climb_rate: priv->climb_rate, digits: priv->digits);
346 g_object_ref_sink (priv->spin);
347
348 g_object_get (object: cell_text, first_property_name: "text", &text, NULL);
349 if (text)
350 {
351 gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->spin), value: g_strtod (nptr: text, NULL));
352 g_free (mem: text);
353 }
354
355 key_controller = gtk_event_controller_key_new ();
356 g_signal_connect (key_controller, "key-pressed",
357 G_CALLBACK (gtk_cell_renderer_spin_key_pressed), priv->spin);
358 gtk_widget_add_controller (widget: priv->spin, controller: key_controller);
359
360 g_object_set_data_full (G_OBJECT (priv->spin), GTK_CELL_RENDERER_SPIN_PATH,
361 data: g_strdup (str: path), destroy: g_free);
362
363 g_signal_connect (priv->spin, "editing-done",
364 G_CALLBACK (gtk_cell_renderer_spin_editing_done), cell);
365
366 g_signal_connect (priv->spin, "notify::has-focus",
367 G_CALLBACK (gtk_cell_renderer_spin_focus_changed), cell);
368
369 return GTK_CELL_EDITABLE (priv->spin);
370}
371
372/**
373 * gtk_cell_renderer_spin_new:
374 *
375 * Creates a new `GtkCellRendererSpin`.
376 *
377 * Returns: a new `GtkCellRendererSpin`
378 */
379GtkCellRenderer *
380gtk_cell_renderer_spin_new (void)
381{
382 return g_object_new (GTK_TYPE_CELL_RENDERER_SPIN, NULL);
383}
384

source code of gtk/gtk/gtkcellrendererspin.c