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 | |
53 | typedef struct _GtkCellRendererSpinClass GtkCellRendererSpinClass; |
54 | typedef struct _GtkCellRendererSpinPrivate GtkCellRendererSpinPrivate; |
55 | |
56 | struct _GtkCellRendererSpin |
57 | { |
58 | GtkCellRendererText parent; |
59 | }; |
60 | |
61 | struct _GtkCellRendererSpinClass |
62 | { |
63 | GtkCellRendererTextClass parent; |
64 | }; |
65 | |
66 | struct _GtkCellRendererSpinPrivate |
67 | { |
68 | GtkWidget *spin; |
69 | GtkAdjustment *adjustment; |
70 | double climb_rate; |
71 | guint digits; |
72 | }; |
73 | |
74 | static void gtk_cell_renderer_spin_finalize (GObject *object); |
75 | |
76 | static void gtk_cell_renderer_spin_get_property (GObject *object, |
77 | guint prop_id, |
78 | GValue *value, |
79 | GParamSpec *spec); |
80 | static void gtk_cell_renderer_spin_set_property (GObject *object, |
81 | guint prop_id, |
82 | const GValue *value, |
83 | GParamSpec *spec); |
84 | |
85 | static gboolean gtk_cell_renderer_spin_key_pressed (GtkEventControllerKey *controller, |
86 | guint keyval, |
87 | guint keycode, |
88 | GdkModifierType state, |
89 | GtkWidget *widget); |
90 | |
91 | static 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); |
98 | enum { |
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 | |
107 | G_DEFINE_TYPE_WITH_PRIVATE (GtkCellRendererSpin, gtk_cell_renderer_spin, GTK_TYPE_CELL_RENDERER_TEXT) |
108 | |
109 | |
110 | static void |
111 | gtk_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 | |
163 | static void |
164 | gtk_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 | |
173 | static void |
174 | gtk_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 | |
184 | static void |
185 | gtk_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 | |
209 | static void |
210 | gtk_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 | |
249 | static void |
250 | gtk_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 | |
277 | static gboolean |
278 | gtk_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 | |
301 | static void |
302 | gtk_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 | |
323 | static GtkCellEditable * |
324 | gtk_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 | */ |
379 | GtkCellRenderer * |
380 | gtk_cell_renderer_spin_new (void) |
381 | { |
382 | return g_object_new (GTK_TYPE_CELL_RENDERER_SPIN, NULL); |
383 | } |
384 | |