2 * Copyright (C) 2008 Nokia Corporation, all rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * * Neither the name of the Nokia Corporation nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
21 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
22 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 * SECTION:modest-number-editor
33 * @short_description: A widget used to enter a number within a pre-defined range.
35 * ModestNumberEditor is used to enter a number from a specific range.
36 * There are two buttons to scroll the value in number field.
37 * Manual input is also possible.
40 * <title>ModestNumberEditor example</title>
42 * number_editor = modest_number_editor_new (-250, 500);
43 * modest_number_editor_set_range (number_editor, 0, 100);
48 #undef MODEST_DISABLE_DEPRECATED
58 #include <gdk/gdkkeysyms.h>
60 #include "modest-number-editor.h"
61 #include "modest-marshal.h"
62 #include <hildon/hildon-banner.h>
63 #include "modest-text-utils.h"
64 #include "modest-platform.h"
66 typedef struct _ModestNumberEditorPrivate ModestNumberEditorPrivate;
68 #define MODEST_NUMBER_EDITOR_GET_PRIVATE(obj) \
69 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), MODEST_TYPE_NUMBER_EDITOR, \
70 ModestNumberEditorPrivate));
72 struct _ModestNumberEditorPrivate
74 gint start; /* Minimum */
75 gint end; /* Maximum */
80 guint select_all_idle_id; /* Selection repaint hack
81 see modest_number_editor_select_all */
86 modest_number_editor_class_init (ModestNumberEditorClass *editor_class);
89 modest_number_editor_init (ModestNumberEditor *editor);
92 modest_number_editor_entry_focusout (GtkWidget *widget,
97 modest_number_editor_entry_changed (GtkWidget *widget,
101 modest_number_editor_finalize (GObject *self);
104 modest_number_editor_range_error (ModestNumberEditor *editor,
105 ModestNumberEditorErrorType type);
108 modest_number_editor_select_all (ModestNumberEditor *editor);
111 modest_number_editor_validate_value (ModestNumberEditor *editor,
112 gboolean allow_intermediate);
115 modest_number_editor_set_property (GObject * object,
117 const GValue * value,
121 modest_number_editor_get_property (GObject *object,
138 static GtkContainerClass* parent_class;
140 static guint ModestNumberEditor_signal[LAST_SIGNAL] = {0};
143 * modest_number_editor_get_type:
145 * Returns GType for ModestNumberEditor.
147 * Returns: ModestNumberEditor type
150 modest_number_editor_get_type (void)
152 static GType editor_type = 0;
156 static const GTypeInfo editor_info =
158 sizeof (ModestNumberEditorClass),
159 NULL, /* base_init */
160 NULL, /* base_finalize */
161 (GClassInitFunc) modest_number_editor_class_init,
162 NULL, /* class_finalize */
163 NULL, /* class_data */
164 sizeof (ModestNumberEditor),
166 (GInstanceInitFunc) modest_number_editor_init,
168 editor_type = g_type_register_static (HILDON_TYPE_ENTRY,
169 "ModestNumberEditor",
176 modest_number_editor_class_init (ModestNumberEditorClass *editor_class)
178 GObjectClass *gobject_class = G_OBJECT_CLASS (editor_class);
180 g_type_class_add_private (editor_class,
181 sizeof (ModestNumberEditorPrivate));
183 parent_class = g_type_class_peek_parent (editor_class);
185 editor_class->range_error = modest_number_editor_range_error;
187 gobject_class->finalize = modest_number_editor_finalize;
188 gobject_class->set_property = modest_number_editor_set_property;
189 gobject_class->get_property = modest_number_editor_get_property;
192 * ModestNumberEditor:value:
194 * The current value of the number editor.
196 g_object_class_install_property (gobject_class, PROP_VALUE,
197 g_param_spec_int ("value",
199 "The current value of number editor",
202 0, G_PARAM_READWRITE));
204 ModestNumberEditor_signal[RANGE_ERROR] =
205 g_signal_new ("range_error", MODEST_TYPE_NUMBER_EDITOR,
206 G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET
207 (ModestNumberEditorClass, range_error),
208 g_signal_accumulator_true_handled, NULL,
209 modest_marshal_BOOLEAN__ENUM,
210 G_TYPE_BOOLEAN, 1, MODEST_TYPE_NUMBER_EDITOR_ERROR_TYPE);
212 ModestNumberEditor_signal[VALID_CHANGED] =
213 g_signal_new ("valid_changed", MODEST_TYPE_NUMBER_EDITOR,
214 G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET
215 (ModestNumberEditorClass, valid_changed),
217 g_cclosure_marshal_VOID__BOOLEAN,
218 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
222 modest_number_editor_finalize (GObject *self)
224 ModestNumberEditorPrivate *priv;
226 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (self);
229 if (priv->select_all_idle_id)
230 g_source_remove (priv->select_all_idle_id);
232 /* Call parent class finalize, if have one */
233 if (G_OBJECT_CLASS (parent_class)->finalize)
234 G_OBJECT_CLASS (parent_class)->finalize(self);
238 modest_number_editor_init (ModestNumberEditor *editor)
240 ModestNumberEditorPrivate *priv;
242 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
245 priv->select_all_idle_id = 0;
246 priv->is_valid = TRUE;
249 /* Format given number to editor field, no checks performed, all signals
250 are sent normally. */
252 modest_number_editor_real_set_value (ModestNumberEditor *editor,
255 /* FIXME: That looks REALLY bad */
258 /* Update text in entry to new value */
259 g_snprintf (buffer, sizeof (buffer), "%d", value);
260 gtk_entry_set_text (GTK_ENTRY (editor), buffer);
264 add_select_all_idle (ModestNumberEditor *editor)
266 ModestNumberEditorPrivate *priv;
268 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
270 if (! priv->select_all_idle_id)
272 priv->select_all_idle_id =
273 g_idle_add((GSourceFunc) modest_number_editor_select_all, editor);
278 modest_number_editor_validate_value (ModestNumberEditor *editor,
279 gboolean allow_intermediate)
281 ModestNumberEditorPrivate *priv;
282 gint error_code, fixup_value;
287 gboolean is_valid = TRUE;
289 g_assert (MODEST_IS_NUMBER_EDITOR(editor));
291 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
294 text = gtk_entry_get_text (GTK_ENTRY (editor));
296 fixup_value = priv->default_val;
298 if (text && text[0]) {
299 /* Try to convert entry text to number */
300 value = strtol (text, &tail, 10);
302 /* Check if conversion succeeded */
304 /* Check if value is in allowed range. This is tricky in those
305 cases when user is editing a value.
306 For example: Range = [100, 500] and user have just inputted "4".
307 This should not lead into error message. Otherwise value is
308 resetted back to "100" and next "4" press will reset it back
311 if (allow_intermediate) {
312 /* We now have the following error cases:
313 * If inputted value as above maximum and
314 maximum is either positive or then maximum
315 negative and value is positive.
316 * If inputted value is below minimum and minimum
317 is negative or minumum positive and value
319 In all other cases situation can be fixed just by
320 adding new numbers to the string.
322 if (value > priv->end && (priv->end >= 0 || (priv->end < 0 && value >= 0))) {
323 error_code = MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED;
324 fixup_value = priv->end;
326 } else if (value < priv->start && (priv->start < 0 || (priv->start >= 0 && value <= 0))) {
327 error_code = MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED;
328 fixup_value = priv->start;
332 if (value > priv->end) {
333 error_code = MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED;
334 fixup_value = priv->end;
336 } else if (value < priv->start) {
337 error_code = MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED;
338 fixup_value = priv->start;
342 /* The only valid case when conversion can fail is when we
343 have plain '-', intermediate forms are allowed AND
344 minimum bound is negative */
347 if (! allow_intermediate || strcmp (text, "-") != 0 || priv->start >= 0)
348 error_code = MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE;
352 if (! allow_intermediate)
353 error_code = MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE;
356 if (error_code != -1) {
357 /* If entry is empty and intermediate forms are nor allowed,
359 /* Change to default value */
360 modest_number_editor_set_value (editor, fixup_value);
361 g_signal_emit (editor, ModestNumberEditor_signal[RANGE_ERROR], 0, error_code, &r);
362 add_select_all_idle (editor);
363 is_valid = modest_number_editor_is_valid (editor);
366 if (priv->is_valid != is_valid) {
367 priv->is_valid = is_valid;
368 g_signal_emit (editor, ModestNumberEditor_signal[VALID_CHANGED], 0, is_valid);
373 modest_number_editor_entry_changed (GtkWidget *widget,
376 g_assert (MODEST_IS_NUMBER_EDITOR (data));
377 modest_number_editor_validate_value (MODEST_NUMBER_EDITOR (data), TRUE);
378 g_object_notify (G_OBJECT (data), "value");
382 modest_number_editor_entry_focusout (GtkWidget *widget,
383 GdkEventFocus *event,
388 g_assert (MODEST_IS_NUMBER_EDITOR(data));
390 window = gtk_widget_get_toplevel (widget);
391 if (window && gtk_window_has_toplevel_focus (GTK_WINDOW (window)))
392 modest_number_editor_validate_value (MODEST_NUMBER_EDITOR(data), FALSE);
398 modest_number_editor_range_error (ModestNumberEditor *editor,
399 ModestNumberEditorErrorType type)
403 gchar *err_msg = NULL;
404 ModestNumberEditorPrivate *priv;
406 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
412 /* Construct error message */
415 case MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED:
416 err_msg = g_strdup_printf (_HL("ckct_ib_maximum_value"), max, max);
419 case MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED:
420 err_msg = g_strdup_printf (_HL("ckct_ib_minimum_value"), min, min);
423 case MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE:
425 g_strdup_printf (_HL("ckct_ib_set_a_value_within_range"), min, max);
429 /* Infoprint error */
431 modest_platform_system_banner (GTK_WIDGET (GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET(editor),
432 GTK_TYPE_WINDOW))), NULL, err_msg);
440 * modest_number_editor_new:
441 * @min: minimum accepted value
442 * @max: maximum accepted value
444 * Creates new number editor
446 * Returns: a new #ModestNumberEditor widget
449 modest_number_editor_new (gint min,
452 ModestNumberEditor *editor = g_object_new (MODEST_TYPE_NUMBER_EDITOR, NULL);
454 /* Connect child widget signals */
455 g_signal_connect (GTK_OBJECT (editor), "changed",
456 G_CALLBACK (modest_number_editor_entry_changed),
459 g_signal_connect (GTK_OBJECT (editor), "focus-out-event",
460 G_CALLBACK (modest_number_editor_entry_focusout),
463 /* Numeric input mode */
464 hildon_gtk_entry_set_input_mode (GTK_ENTRY (editor),
465 HILDON_GTK_INPUT_MODE_NUMERIC);
466 hildon_gtk_widget_set_theme_size ((GtkWidget *) editor,
467 HILDON_SIZE_FINGER_HEIGHT);
469 /* Set user inputted range to editor */
470 modest_number_editor_set_range (editor, min, max);
472 return GTK_WIDGET (editor);
476 * modest_number_editor_set_range:
477 * @editor: a #ModestNumberEditor widget
478 * @min: minimum accepted value
479 * @max: maximum accepted value
481 * Sets accepted number range for editor
484 modest_number_editor_set_range (ModestNumberEditor *editor,
488 ModestNumberEditorPrivate *priv;
489 gchar buffer_min[32], buffer_max[32];
492 g_return_if_fail (MODEST_IS_NUMBER_EDITOR (editor));
494 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
497 /* Set preferences */
498 priv->start = MIN (min, max);
499 priv->end = MAX (min, max);
501 /* Find maximum allowed length of value */
502 g_snprintf (buffer_min, sizeof (buffer_min), "%d", min);
503 g_snprintf (buffer_max, sizeof (buffer_max), "%d", max);
504 a = strlen (buffer_min);
505 b = strlen (buffer_max);
507 /* Set maximum size of entry */
508 gtk_entry_set_width_chars (GTK_ENTRY (editor), MAX (a, b));
509 modest_number_editor_set_value (editor, priv->start);
513 * modest_number_editor_is_valid:
514 * @editor: pointer to #ModestNumberEditor
516 * Returns: if @editor contents are valid
519 modest_number_editor_is_valid (ModestNumberEditor *editor)
521 ModestNumberEditorPrivate *priv;
523 g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), FALSE);
525 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
528 return priv->is_valid;
533 * modest_number_editor_get_value:
534 * @editor: pointer to #ModestNumberEditor
536 * Returns: current NumberEditor value
539 modest_number_editor_get_value (ModestNumberEditor *editor)
541 ModestNumberEditorPrivate *priv;
543 g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), 0);
545 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
548 return atoi (gtk_entry_get_text (GTK_ENTRY (editor)));
552 * modest_number_editor_set_value:
553 * @editor: pointer to #ModestNumberEditor
554 * @value: numeric value for number editor
556 * Sets numeric value for number editor
559 modest_number_editor_set_value (ModestNumberEditor *editor,
562 ModestNumberEditorPrivate *priv;
564 g_return_if_fail (MODEST_IS_NUMBER_EDITOR (editor));
566 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
569 g_return_if_fail (value <= priv->end);
570 g_return_if_fail (value >= priv->start);
572 priv->default_val = value;
573 modest_number_editor_real_set_value (editor, value);
574 g_object_notify (G_OBJECT(editor), "value");
577 /* When calling gtk_entry_set_text, the entry widget does things that can
578 * cause the whole widget to redraw. This redrawing is delayed and if any
579 * selections are made right after calling the gtk_entry_set_text the
580 * setting of the selection might seem to have no effect.
582 * If the selection is delayed with a lower priority than the redrawing,
583 * the selection should stick. Calling this function with g_idle_add should
587 modest_number_editor_select_all (ModestNumberEditor *editor)
589 ModestNumberEditorPrivate *priv;
591 g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), FALSE);
593 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
596 GDK_THREADS_ENTER ();
597 gtk_editable_select_region (GTK_EDITABLE (editor), 0, -1);
598 priv->select_all_idle_id = 0;
599 GDK_THREADS_LEAVE ();
604 modest_number_editor_set_property (GObject *object,
609 ModestNumberEditor *editor;
611 editor = MODEST_NUMBER_EDITOR (object);
616 modest_number_editor_set_value (editor, g_value_get_int (value));
620 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
626 modest_number_editor_get_property (GObject *object,
631 ModestNumberEditor *editor;
633 editor = MODEST_NUMBER_EDITOR (object);
638 g_value_set_int(value, modest_number_editor_get_value (editor));
642 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
648 /* enumerations from "modest-number-editor.h" */
650 modest_number_editor_error_type_get_type (void)
652 static GType etype = 0;
654 static const GEnumValue values[] = {
655 { MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED, "MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED", "maximum-value-exceed" },
656 { MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED, "MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED", "minimum-value-exceed" },
657 { MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE, "MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE", "erroneous-value" },
660 etype = g_enum_register_static ("ModestNumberEditorErrorType", values);