Number editor must be finger size
[modest] / src / hildon2 / modest-number-editor.c
1 /*
2  * Copyright (C) 2008 Nokia Corporation, all rights reserved.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
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.
17  *
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.
29  */
30
31 /**
32  * SECTION:modest-number-editor
33  * @short_description: A widget used to enter a number within a pre-defined range.
34  *
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.
38  *
39  * <example>
40  * <title>ModestNumberEditor example</title>
41  * <programlisting>
42  * number_editor = modest_number_editor_new (-250, 500);
43  * modest_number_editor_set_range (number_editor, 0, 100);
44  * </programlisting>
45  * </example>
46  */
47
48 #undef                                          MODEST_DISABLE_DEPRECATED
49
50 #ifdef                                          HAVE_CONFIG_H
51 #include                                        <config.h>
52 #endif
53
54 #include                                        <string.h>
55 #include                                        <stdio.h>
56 #include                                        <stdlib.h>
57 #include                                        <libintl.h>
58 #include                                        <gdk/gdkkeysyms.h>
59
60 #include                                        "modest-number-editor.h"
61 #include                                        "modest-marshal.h"
62 #include                                        <hildon/hildon-banner.h>
63 #include                                        "modest-text-utils.h"
64
65 #define                                         _(String) dgettext("modest-libs", String)
66
67 typedef struct                                  _ModestNumberEditorPrivate ModestNumberEditorPrivate;
68
69 #define                                         MODEST_NUMBER_EDITOR_GET_PRIVATE(obj) \
70                                                 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), MODEST_TYPE_NUMBER_EDITOR, \
71                                                 ModestNumberEditorPrivate));
72
73 struct                                          _ModestNumberEditorPrivate
74 {
75         gint start; /* Minimum */
76         gint end;   /* Maximum */
77         gint default_val;
78         gboolean is_valid;
79
80         /* Timer IDs */
81         guint select_all_idle_id; /* Selection repaint hack
82                                      see modest_number_editor_select_all */
83 };
84
85
86 static void
87 modest_number_editor_class_init                 (ModestNumberEditorClass *editor_class);
88
89 static void
90 modest_number_editor_init                       (ModestNumberEditor *editor);
91
92 static gboolean
93 modest_number_editor_entry_focusout             (GtkWidget *widget, 
94                                                  GdkEventFocus *event,
95                                                  gpointer data);
96
97 static void
98 modest_number_editor_entry_changed              (GtkWidget *widget, 
99                                                  gpointer data);
100
101 static void
102 modest_number_editor_finalize                   (GObject *self);
103
104 static gboolean
105 modest_number_editor_range_error                (ModestNumberEditor *editor,
106                                                  ModestNumberEditorErrorType type);
107
108 static gboolean
109 modest_number_editor_select_all                 (ModestNumberEditor *editor);
110
111 static void
112 modest_number_editor_validate_value             (ModestNumberEditor *editor, 
113                                                  gboolean allow_intermediate);
114     
115 static void 
116 modest_number_editor_set_property               (GObject * object,
117                                                  guint prop_id,
118                                                  const GValue * value,
119                                                  GParamSpec * pspec);
120
121 static void
122 modest_number_editor_get_property               (GObject *object,
123                                                  guint prop_id,
124                                                  GValue *value, 
125                                                  GParamSpec * pspec);
126
127 enum
128 {
129     RANGE_ERROR,
130     VALID_CHANGED,
131     LAST_SIGNAL
132 };
133
134 enum {
135     PROP_0,
136     PROP_VALUE
137 };
138
139 static GtkContainerClass*                       parent_class;
140
141 static guint                                    ModestNumberEditor_signal[LAST_SIGNAL] = {0};
142
143 /**
144  * modest_number_editor_get_type:
145  *
146  * Returns GType for ModestNumberEditor.
147  *
148  * Returns: ModestNumberEditor type
149  */
150 GType G_GNUC_CONST
151 modest_number_editor_get_type                   (void)
152 {
153     static GType editor_type = 0;
154
155     if (!editor_type)
156     {
157         static const GTypeInfo editor_info =
158         {
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),
166             0,  /* n_preallocs */
167             (GInstanceInitFunc) modest_number_editor_init,
168         };
169         editor_type = g_type_register_static (HILDON_TYPE_ENTRY,
170                 "ModestNumberEditor",
171                 &editor_info, 0);
172     }
173     return editor_type;
174 }
175
176 static void
177 modest_number_editor_class_init                 (ModestNumberEditorClass *editor_class)
178 {
179     GObjectClass *gobject_class = G_OBJECT_CLASS (editor_class);
180
181     g_type_class_add_private (editor_class,
182             sizeof (ModestNumberEditorPrivate));
183
184     parent_class = g_type_class_peek_parent (editor_class);
185
186     editor_class->range_error = modest_number_editor_range_error;
187
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;
191
192     /**
193      * ModestNumberEditor:value:
194      *
195      * The current value of the number editor.
196      */
197     g_object_class_install_property (gobject_class, PROP_VALUE,
198             g_param_spec_int ("value",
199                 "Value",
200                 "The current value of number editor",
201                 G_MININT,
202                 G_MAXINT,
203                 0, G_PARAM_READWRITE));
204
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);
212
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),
217                 NULL, NULL,
218                 g_cclosure_marshal_VOID__BOOLEAN,
219                 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
220 }
221
222 static void
223 modest_number_editor_finalize                   (GObject *self)
224 {
225     ModestNumberEditorPrivate *priv;
226
227     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (self);
228     g_assert (priv);
229
230     if (priv->select_all_idle_id)
231         g_source_remove (priv->select_all_idle_id);
232
233     /* Call parent class finalize, if have one */
234     if (G_OBJECT_CLASS (parent_class)->finalize)
235         G_OBJECT_CLASS (parent_class)->finalize(self);
236 }
237
238 static void
239 modest_number_editor_init                       (ModestNumberEditor *editor)
240 {
241     ModestNumberEditorPrivate *priv;
242
243     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
244     g_assert (priv);
245
246     priv->select_all_idle_id = 0;
247     priv->is_valid = TRUE;
248 }
249
250 /* Format given number to editor field, no checks performed, all signals
251    are sent normally. */
252 static void
253 modest_number_editor_real_set_value             (ModestNumberEditor *editor, 
254                                                  gint value)
255 {
256     /* FIXME: That looks REALLY bad */
257     gchar buffer[32];
258
259     /* Update text in entry to new value */
260     g_snprintf (buffer, sizeof (buffer), "%d", value);
261     gtk_entry_set_text (GTK_ENTRY (editor), buffer);
262 }
263
264 static void
265 add_select_all_idle                             (ModestNumberEditor *editor)
266 {
267     ModestNumberEditorPrivate *priv;
268
269     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
270
271     if (! priv->select_all_idle_id)
272     {
273         priv->select_all_idle_id =
274             g_idle_add((GSourceFunc) modest_number_editor_select_all, editor);
275     }    
276 }
277
278 static void
279 modest_number_editor_validate_value             (ModestNumberEditor *editor, 
280                                                  gboolean allow_intermediate)
281 {
282         ModestNumberEditorPrivate *priv;
283         gint error_code, fixup_value;
284         const gchar *text;
285         long value;
286         gchar *tail;
287         gboolean r;
288         gboolean is_valid = TRUE;
289
290         g_assert (MODEST_IS_NUMBER_EDITOR(editor));
291         
292         priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
293         g_assert (priv);
294
295         text = gtk_entry_get_text (GTK_ENTRY (editor));
296         error_code = -1;
297         fixup_value = priv->default_val;
298
299         if (text && text[0]) {
300                 /* Try to convert entry text to number */
301                 value = strtol (text, &tail, 10);
302
303                 /* Check if conversion succeeded */
304                 if (tail[0] == 0) {
305                         /* Check if value is in allowed range. This is tricky in those
306                            cases when user is editing a value. 
307                            For example: Range = [100, 500] and user have just inputted "4".
308                            This should not lead into error message. Otherwise value is
309                            resetted back to "100" and next "4" press will reset it back
310                            and so on. */
311
312                         if (allow_intermediate) {
313                                 /* We now have the following error cases:
314                                  * If inputted value as above maximum and
315                                  maximum is either positive or then maximum
316                                  negative and value is positive.
317                                  * If inputted value is below minimum and minimum
318                                  is negative or minumum positive and value
319                                  negative or zero.
320                                  In all other cases situation can be fixed just by
321                                  adding new numbers to the string.
322                                 */
323                                 if (value > priv->end && (priv->end >= 0 || (priv->end < 0 && value >= 0))) {
324                                         error_code = MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED;
325                                         fixup_value = priv->end;
326                                         is_valid = FALSE;
327                                 } else if (value < priv->start && (priv->start < 0 || (priv->start >= 0 && value <= 0))) {
328                                         error_code = MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED;
329                                         fixup_value = priv->start;
330                                         is_valid = FALSE;
331                                 }
332                         } else {
333                                 if (value > priv->end) {
334                                         error_code = MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED;
335                                         fixup_value = priv->end;
336                                         is_valid = FALSE;
337                                 } else if (value < priv->start) {
338                                         error_code = MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED;
339                                         fixup_value = priv->start;
340                                         is_valid = FALSE;
341                                 }
342                         }
343                         /* The only valid case when conversion can fail is when we
344                            have plain '-', intermediate forms are allowed AND
345                            minimum bound is negative */
346                 } else {
347                         is_valid = FALSE;
348                         if (! allow_intermediate || strcmp (text, "-") != 0 || priv->start >= 0)
349                                 error_code = MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE;
350                 }
351         } else {
352                 is_valid = FALSE;
353                 if (! allow_intermediate)
354                         error_code = MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE;
355         }
356
357         if (error_code != -1) {
358                 /* If entry is empty and intermediate forms are nor allowed, 
359                    emit error signal */
360                 /* Change to default value */
361                 modest_number_editor_set_value (editor, fixup_value);
362                 g_signal_emit (editor, ModestNumberEditor_signal[RANGE_ERROR], 0, error_code, &r);
363                 add_select_all_idle (editor);
364                 is_valid = modest_number_editor_is_valid (editor);
365         }
366
367         if (priv->is_valid != is_valid) {
368                 priv->is_valid = is_valid;
369                 g_signal_emit (editor, ModestNumberEditor_signal[VALID_CHANGED], 0, is_valid);
370         }
371 }
372
373 static void 
374 modest_number_editor_entry_changed              (GtkWidget *widget, 
375                                                  gpointer data)
376 {
377     g_assert (MODEST_IS_NUMBER_EDITOR (data));
378     modest_number_editor_validate_value (MODEST_NUMBER_EDITOR (data), TRUE);
379     g_object_notify (G_OBJECT (data), "value");
380 }
381
382 static gboolean
383 modest_number_editor_entry_focusout             (GtkWidget *widget, 
384                                                  GdkEventFocus *event,
385                                                  gpointer data)
386 {
387         GtkWidget *window;
388         
389         g_assert (MODEST_IS_NUMBER_EDITOR(data));
390
391         window = gtk_widget_get_toplevel (widget);
392         if (window && gtk_window_has_toplevel_focus (GTK_WINDOW (window)))
393                 modest_number_editor_validate_value (MODEST_NUMBER_EDITOR(data), FALSE);
394
395         return FALSE;
396 }
397
398 static gboolean
399 modest_number_editor_range_error                (ModestNumberEditor *editor,
400                                                  ModestNumberEditorErrorType type)
401 {
402
403     gint min, max;
404     gchar *err_msg = NULL;
405     ModestNumberEditorPrivate *priv;
406
407     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
408     g_assert (priv);
409
410     min = priv->start;
411     max = priv->end;
412
413     /* Construct error message */
414     switch (type)
415     {
416         case MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED:
417             err_msg = g_strdup_printf (_HL("ckct_ib_maximum_value"), max, max);
418             break;
419
420         case MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED:
421             err_msg = g_strdup_printf (_HL("ckct_ib_minimum_value"), min, min);
422             break;
423
424         case MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE:
425             err_msg =
426                 g_strdup_printf (_HL("ckct_ib_set_a_value_within_range"), min, max);
427             break;
428     }
429
430     /* Infoprint error */
431     if (err_msg)
432     {
433         hildon_banner_show_information (GTK_WIDGET (GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET(editor),
434                                         GTK_TYPE_WINDOW))), NULL, err_msg);
435         g_free(err_msg);
436     }
437
438     return TRUE;
439 }
440
441 /**
442  * modest_number_editor_new:
443  * @min: minimum accepted value
444  * @max: maximum accepted value
445  * 
446  * Creates new number editor
447  *
448  * Returns: a new #ModestNumberEditor widget
449  */
450 GtkWidget*
451 modest_number_editor_new                        (gint min, 
452                                                  gint max)
453 {
454     ModestNumberEditor *editor = g_object_new (MODEST_TYPE_NUMBER_EDITOR, NULL);
455
456     /* Connect child widget signals */
457     g_signal_connect (GTK_OBJECT (editor), "changed",
458             G_CALLBACK (modest_number_editor_entry_changed),
459             editor);
460
461     g_signal_connect (GTK_OBJECT (editor), "focus-out-event",
462             G_CALLBACK (modest_number_editor_entry_focusout),
463             editor);
464
465     /* Numeric input mode */
466     hildon_gtk_entry_set_input_mode (GTK_ENTRY (editor), 
467                                      HILDON_GTK_INPUT_MODE_NUMERIC);
468     hildon_gtk_widget_set_theme_size ((GtkWidget *) editor, 
469                                       HILDON_SIZE_FINGER_HEIGHT);
470
471     /* Set user inputted range to editor */
472     modest_number_editor_set_range (editor, min, max);
473
474     return GTK_WIDGET (editor);
475 }
476
477 /**
478  * modest_number_editor_set_range:
479  * @editor: a #ModestNumberEditor widget
480  * @min: minimum accepted value
481  * @max: maximum accepted value
482  *
483  * Sets accepted number range for editor
484  */
485 void
486 modest_number_editor_set_range                  (ModestNumberEditor *editor, 
487                                                  gint min, 
488                                                  gint max)
489 {
490     ModestNumberEditorPrivate *priv;
491     gchar buffer_min[32], buffer_max[32];
492     gint a, b;
493
494     g_return_if_fail (MODEST_IS_NUMBER_EDITOR (editor));
495
496     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
497     g_assert (priv);
498
499     /* Set preferences */
500     priv->start = MIN (min, max);
501     priv->end = MAX (min, max);
502
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);
508
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);
512 }
513
514 /**
515  * modest_number_editor_is_valid:
516  * @editor: pointer to #ModestNumberEditor
517  *
518  * Returns: if @editor contents are valid
519  */
520 gboolean
521 modest_number_editor_is_valid                  (ModestNumberEditor *editor)
522 {
523     ModestNumberEditorPrivate *priv;
524
525     g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), FALSE);
526
527     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
528     g_assert (priv);
529
530     return priv->is_valid;
531
532 }
533
534 /**
535  * modest_number_editor_get_value:
536  * @editor: pointer to #ModestNumberEditor
537  *
538  * Returns: current NumberEditor value
539  */
540 gint
541 modest_number_editor_get_value                  (ModestNumberEditor *editor)
542 {
543     ModestNumberEditorPrivate *priv;
544
545     g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), 0);
546
547     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
548     g_assert (priv);
549
550     return atoi (gtk_entry_get_text (GTK_ENTRY (editor)));
551 }
552
553 /**
554  * modest_number_editor_set_value:
555  * @editor: pointer to #ModestNumberEditor
556  * @value: numeric value for number editor
557  *
558  * Sets numeric value for number editor
559  */
560 void
561 modest_number_editor_set_value                  (ModestNumberEditor *editor, 
562                                                  gint value)
563 {
564     ModestNumberEditorPrivate *priv;
565
566     g_return_if_fail (MODEST_IS_NUMBER_EDITOR (editor));
567
568     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
569     g_assert (priv);
570
571     g_return_if_fail (value <= priv->end);
572     g_return_if_fail (value >= priv->start);
573
574     priv->default_val = value;
575     modest_number_editor_real_set_value (editor, value);
576     g_object_notify (G_OBJECT(editor), "value");
577 }
578
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.
583  *
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
586  * do it.
587  */
588 static gboolean
589 modest_number_editor_select_all                 (ModestNumberEditor *editor)
590 {   
591     ModestNumberEditorPrivate *priv;
592
593     g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), FALSE);
594
595     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
596     g_assert (priv);
597
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 ();
602     return FALSE;
603
604
605 static void
606 modest_number_editor_set_property               (GObject *object,
607                                                  guint prop_id,
608                                                  const GValue *value, 
609                                                  GParamSpec *pspec)
610 {
611     ModestNumberEditor *editor;
612
613     editor = MODEST_NUMBER_EDITOR (object);
614
615     switch (prop_id) {
616
617         case PROP_VALUE:
618             modest_number_editor_set_value (editor, g_value_get_int (value));
619             break;
620
621         default:
622             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
623             break;
624     }
625 }
626
627 static void
628 modest_number_editor_get_property               (GObject *object,
629                                                  guint prop_id, 
630                                                  GValue *value, 
631                                                  GParamSpec *pspec)
632 {
633     ModestNumberEditor *editor;
634
635     editor = MODEST_NUMBER_EDITOR (object);
636
637     switch (prop_id) {
638
639         case PROP_VALUE:
640             g_value_set_int(value, modest_number_editor_get_value (editor));
641             break;
642
643         default:
644             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
645             break;
646     }
647 }
648
649
650 /* enumerations from "modest-number-editor.h" */
651 GType
652 modest_number_editor_error_type_get_type (void)
653 {
654   static GType etype = 0;
655   if (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" },
660       { 0, NULL, NULL }
661     };
662     etype = g_enum_register_static ("ModestNumberEditorErrorType", values);
663   }
664   return etype;
665 }
666