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"
65 #define _(String) dgettext("modest-libs", String)
67 typedef struct _ModestNumberEditorPrivate ModestNumberEditorPrivate;
69 #define MODEST_NUMBER_EDITOR_GET_PRIVATE(obj) \
70 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), MODEST_TYPE_NUMBER_EDITOR, \
71 ModestNumberEditorPrivate));
73 struct _ModestNumberEditorPrivate
75 gint start; /* Minimum */
76 gint end; /* Maximum */
81 guint select_all_idle_id; /* Selection repaint hack
82 see modest_number_editor_select_all */
87 modest_number_editor_class_init (ModestNumberEditorClass *editor_class);
90 modest_number_editor_init (ModestNumberEditor *editor);
93 modest_number_editor_entry_focusout (GtkWidget *widget,
98 modest_number_editor_entry_changed (GtkWidget *widget,
102 modest_number_editor_finalize (GObject *self);
105 modest_number_editor_range_error (ModestNumberEditor *editor,
106 ModestNumberEditorErrorType type);
109 modest_number_editor_select_all (ModestNumberEditor *editor);
112 modest_number_editor_validate_value (ModestNumberEditor *editor,
113 gboolean allow_intermediate);
116 modest_number_editor_set_property (GObject * object,
118 const GValue * value,
122 modest_number_editor_get_property (GObject *object,
139 static GtkContainerClass* parent_class;
141 static guint ModestNumberEditor_signal[LAST_SIGNAL] = {0};
144 * modest_number_editor_get_type:
146 * Returns GType for ModestNumberEditor.
148 * Returns: ModestNumberEditor type
151 modest_number_editor_get_type (void)
153 static GType editor_type = 0;
157 static const GTypeInfo editor_info =
159 sizeof (ModestNumberEditorClass),
160 NULL, /* base_init */
161 NULL, /* base_finalize */
162 (GClassInitFunc) modest_number_editor_class_init,
163 NULL, /* class_finalize */
164 NULL, /* class_data */
165 sizeof (ModestNumberEditor),
167 (GInstanceInitFunc) modest_number_editor_init,
169 editor_type = g_type_register_static (HILDON_TYPE_ENTRY,
170 "ModestNumberEditor",
177 modest_number_editor_class_init (ModestNumberEditorClass *editor_class)
179 GObjectClass *gobject_class = G_OBJECT_CLASS (editor_class);
181 g_type_class_add_private (editor_class,
182 sizeof (ModestNumberEditorPrivate));
184 parent_class = g_type_class_peek_parent (editor_class);
186 editor_class->range_error = modest_number_editor_range_error;
188 gobject_class->finalize = modest_number_editor_finalize;
189 gobject_class->set_property = modest_number_editor_set_property;
190 gobject_class->get_property = modest_number_editor_get_property;
193 * ModestNumberEditor:value:
195 * The current value of the number editor.
197 g_object_class_install_property (gobject_class, PROP_VALUE,
198 g_param_spec_int ("value",
200 "The current value of number editor",
203 0, G_PARAM_READWRITE));
205 ModestNumberEditor_signal[RANGE_ERROR] =
206 g_signal_new ("range_error", MODEST_TYPE_NUMBER_EDITOR,
207 G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET
208 (ModestNumberEditorClass, range_error),
209 g_signal_accumulator_true_handled, NULL,
210 modest_marshal_BOOLEAN__ENUM,
211 G_TYPE_BOOLEAN, 1, MODEST_TYPE_NUMBER_EDITOR_ERROR_TYPE);
213 ModestNumberEditor_signal[VALID_CHANGED] =
214 g_signal_new ("valid_changed", MODEST_TYPE_NUMBER_EDITOR,
215 G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET
216 (ModestNumberEditorClass, valid_changed),
218 g_cclosure_marshal_VOID__BOOLEAN,
219 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
223 modest_number_editor_finalize (GObject *self)
225 ModestNumberEditorPrivate *priv;
227 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (self);
230 if (priv->select_all_idle_id)
231 g_source_remove (priv->select_all_idle_id);
233 /* Call parent class finalize, if have one */
234 if (G_OBJECT_CLASS (parent_class)->finalize)
235 G_OBJECT_CLASS (parent_class)->finalize(self);
239 modest_number_editor_init (ModestNumberEditor *editor)
241 ModestNumberEditorPrivate *priv;
243 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
246 priv->select_all_idle_id = 0;
248 /* Connect child widget signals */
249 g_signal_connect (GTK_OBJECT (editor), "changed",
250 G_CALLBACK (modest_number_editor_entry_changed),
253 g_signal_connect (GTK_OBJECT (editor), "focus-out-event",
254 G_CALLBACK (modest_number_editor_entry_focusout),
257 hildon_gtk_entry_set_input_mode (GTK_ENTRY (editor),
258 HILDON_GTK_INPUT_MODE_NUMERIC);
260 modest_number_editor_set_range (editor, G_MININT, G_MAXINT);
262 priv->is_valid = TRUE;
265 /* Format given number to editor field, no checks performed, all signals
266 are sent normally. */
268 modest_number_editor_real_set_value (ModestNumberEditor *editor,
271 /* FIXME: That looks REALLY bad */
274 /* Update text in entry to new value */
275 g_snprintf (buffer, sizeof (buffer), "%d", value);
276 gtk_entry_set_text (GTK_ENTRY (editor), buffer);
280 add_select_all_idle (ModestNumberEditor *editor)
282 ModestNumberEditorPrivate *priv;
284 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
286 if (! priv->select_all_idle_id)
288 priv->select_all_idle_id =
289 g_idle_add((GSourceFunc) modest_number_editor_select_all, editor);
294 modest_number_editor_validate_value (ModestNumberEditor *editor,
295 gboolean allow_intermediate)
297 ModestNumberEditorPrivate *priv;
298 gint error_code, fixup_value;
303 gboolean is_valid = TRUE;
305 g_assert (MODEST_IS_NUMBER_EDITOR(editor));
307 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
310 text = gtk_entry_get_text (GTK_ENTRY (editor));
312 fixup_value = priv->default_val;
314 if (text && text[0]) {
315 /* Try to convert entry text to number */
316 value = strtol (text, &tail, 10);
318 /* Check if conversion succeeded */
320 /* Check if value is in allowed range. This is tricky in those
321 cases when user is editing a value.
322 For example: Range = [100, 500] and user have just inputted "4".
323 This should not lead into error message. Otherwise value is
324 resetted back to "100" and next "4" press will reset it back
327 if (allow_intermediate) {
328 /* We now have the following error cases:
329 * If inputted value as above maximum and
330 maximum is either positive or then maximum
331 negative and value is positive.
332 * If inputted value is below minimum and minimum
333 is negative or minumum positive and value
335 In all other cases situation can be fixed just by
336 adding new numbers to the string.
338 if (value > priv->end && (priv->end >= 0 || (priv->end < 0 && value >= 0))) {
339 error_code = MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED;
340 fixup_value = priv->end;
342 } else if (value < priv->start && (priv->start < 0 || (priv->start >= 0 && value <= 0))) {
343 error_code = MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED;
344 fixup_value = priv->start;
348 if (value > priv->end) {
349 error_code = MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED;
350 fixup_value = priv->end;
352 } else if (value < priv->start) {
353 error_code = MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED;
354 fixup_value = priv->start;
358 /* The only valid case when conversion can fail is when we
359 have plain '-', intermediate forms are allowed AND
360 minimum bound is negative */
363 if (! allow_intermediate || strcmp (text, "-") != 0 || priv->start >= 0)
364 error_code = MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE;
368 if (! allow_intermediate)
369 error_code = MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE;
372 if (error_code != -1) {
373 /* If entry is empty and intermediate forms are nor allowed,
375 /* Change to default value */
376 modest_number_editor_set_value (editor, fixup_value);
377 g_signal_emit (editor, ModestNumberEditor_signal[RANGE_ERROR], 0, error_code, &r);
378 add_select_all_idle (editor);
379 is_valid = modest_number_editor_is_valid (editor);
382 if (priv->is_valid != is_valid) {
383 priv->is_valid = is_valid;
384 g_signal_emit (editor, ModestNumberEditor_signal[VALID_CHANGED], 0, is_valid);
389 modest_number_editor_entry_changed (GtkWidget *widget,
392 g_assert (MODEST_IS_NUMBER_EDITOR (data));
393 modest_number_editor_validate_value (MODEST_NUMBER_EDITOR (data), TRUE);
394 g_object_notify (G_OBJECT (data), "value");
398 modest_number_editor_entry_focusout (GtkWidget *widget,
399 GdkEventFocus *event,
404 g_assert (MODEST_IS_NUMBER_EDITOR(data));
406 window = gtk_widget_get_toplevel (widget);
407 if (window && gtk_window_has_toplevel_focus (GTK_WINDOW (window)))
408 modest_number_editor_validate_value (MODEST_NUMBER_EDITOR(data), FALSE);
414 modest_number_editor_range_error (ModestNumberEditor *editor,
415 ModestNumberEditorErrorType type)
419 gchar *err_msg = NULL;
420 ModestNumberEditorPrivate *priv;
422 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
428 /* Construct error message */
431 case MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED:
432 err_msg = g_strdup_printf (_HL("ckct_ib_maximum_value"), max, max);
435 case MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED:
436 err_msg = g_strdup_printf (_HL("ckct_ib_minimum_value"), min, min);
439 case MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE:
441 g_strdup_printf (_HL("ckct_ib_set_a_value_within_range"), min, max);
445 /* Infoprint error */
448 hildon_banner_show_information (GTK_WIDGET (GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET(editor),
449 GTK_TYPE_WINDOW))), NULL, err_msg);
457 * modest_number_editor_new:
458 * @min: minimum accepted value
459 * @max: maximum accepted value
461 * Creates new number editor
463 * Returns: a new #ModestNumberEditor widget
466 modest_number_editor_new (gint min,
469 ModestNumberEditor *editor = g_object_new (MODEST_TYPE_NUMBER_EDITOR, NULL);
471 /* Set user inputted range to editor */
472 modest_number_editor_set_range (editor, min, max);
474 return GTK_WIDGET (editor);
478 * modest_number_editor_set_range:
479 * @editor: a #ModestNumberEditor widget
480 * @min: minimum accepted value
481 * @max: maximum accepted value
483 * Sets accepted number range for editor
486 modest_number_editor_set_range (ModestNumberEditor *editor,
490 ModestNumberEditorPrivate *priv;
491 gchar buffer_min[32], buffer_max[32];
494 g_return_if_fail (MODEST_IS_NUMBER_EDITOR (editor));
496 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
499 /* Set preferences */
500 priv->start = MIN (min, max);
501 priv->end = MAX (min, max);
503 /* Find maximum allowed length of value */
504 g_snprintf (buffer_min, sizeof (buffer_min), "%d", min);
505 g_snprintf (buffer_max, sizeof (buffer_max), "%d", max);
506 a = strlen (buffer_min);
507 b = strlen (buffer_max);
509 /* Set maximum size of entry */
510 gtk_entry_set_width_chars (GTK_ENTRY (editor), MAX (a, b));
511 modest_number_editor_set_value (editor, priv->start);
515 * modest_number_editor_is_valid:
516 * @editor: pointer to #ModestNumberEditor
518 * Returns: if @editor contents are valid
521 modest_number_editor_is_valid (ModestNumberEditor *editor)
523 ModestNumberEditorPrivate *priv;
525 g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), FALSE);
527 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
530 return priv->is_valid;
535 * modest_number_editor_get_value:
536 * @editor: pointer to #ModestNumberEditor
538 * Returns: current NumberEditor value
541 modest_number_editor_get_value (ModestNumberEditor *editor)
543 ModestNumberEditorPrivate *priv;
545 g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), 0);
547 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
550 return atoi (gtk_entry_get_text (GTK_ENTRY (editor)));
554 * modest_number_editor_set_value:
555 * @editor: pointer to #ModestNumberEditor
556 * @value: numeric value for number editor
558 * Sets numeric value for number editor
561 modest_number_editor_set_value (ModestNumberEditor *editor,
564 ModestNumberEditorPrivate *priv;
566 g_return_if_fail (MODEST_IS_NUMBER_EDITOR (editor));
568 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
571 g_return_if_fail (value <= priv->end);
572 g_return_if_fail (value >= priv->start);
574 priv->default_val = value;
575 modest_number_editor_real_set_value (editor, value);
576 g_object_notify (G_OBJECT(editor), "value");
579 /* When calling gtk_entry_set_text, the entry widget does things that can
580 * cause the whole widget to redraw. This redrawing is delayed and if any
581 * selections are made right after calling the gtk_entry_set_text the
582 * setting of the selection might seem to have no effect.
584 * If the selection is delayed with a lower priority than the redrawing,
585 * the selection should stick. Calling this function with g_idle_add should
589 modest_number_editor_select_all (ModestNumberEditor *editor)
591 ModestNumberEditorPrivate *priv;
593 g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), FALSE);
595 priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
598 GDK_THREADS_ENTER ();
599 gtk_editable_select_region (GTK_EDITABLE (editor), 0, -1);
600 priv->select_all_idle_id = 0;
601 GDK_THREADS_LEAVE ();
606 modest_number_editor_set_property (GObject *object,
611 ModestNumberEditor *editor;
613 editor = MODEST_NUMBER_EDITOR (object);
618 modest_number_editor_set_value (editor, g_value_get_int (value));
622 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
628 modest_number_editor_get_property (GObject *object,
633 ModestNumberEditor *editor;
635 editor = MODEST_NUMBER_EDITOR (object);
640 g_value_set_int(value, modest_number_editor_get_value (editor));
644 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
650 /* enumerations from "modest-number-editor.h" */
652 modest_number_editor_error_type_get_type (void)
654 static GType etype = 0;
656 static const GEnumValue values[] = {
657 { MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED, "MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED", "maximum-value-exceed" },
658 { MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED, "MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED", "minimum-value-exceed" },
659 { MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE, "MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE", "erroneous-value" },
662 etype = g_enum_register_static ("ModestNumberEditorErrorType", values);