57d1adae32ce5a5e96a6bbe17192b013b7d4d7e7
[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
248     /* Connect child widget signals */
249     g_signal_connect (GTK_OBJECT (editor), "changed",
250             G_CALLBACK (modest_number_editor_entry_changed),
251             editor);
252
253     g_signal_connect (GTK_OBJECT (editor), "focus-out-event",
254             G_CALLBACK (modest_number_editor_entry_focusout),
255             editor);
256
257     hildon_gtk_entry_set_input_mode (GTK_ENTRY (editor), 
258                                      HILDON_GTK_INPUT_MODE_NUMERIC);
259
260     modest_number_editor_set_range (editor, G_MININT, G_MAXINT);
261
262     priv->is_valid = TRUE;
263 }
264
265 /* Format given number to editor field, no checks performed, all signals
266    are sent normally. */
267 static void
268 modest_number_editor_real_set_value             (ModestNumberEditor *editor, 
269                                                  gint value)
270 {
271     /* FIXME: That looks REALLY bad */
272     gchar buffer[32];
273
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);
277 }
278
279 static void
280 add_select_all_idle                             (ModestNumberEditor *editor)
281 {
282     ModestNumberEditorPrivate *priv;
283
284     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
285
286     if (! priv->select_all_idle_id)
287     {
288         priv->select_all_idle_id =
289             g_idle_add((GSourceFunc) modest_number_editor_select_all, editor);
290     }    
291 }
292
293 static void
294 modest_number_editor_validate_value             (ModestNumberEditor *editor, 
295                                                  gboolean allow_intermediate)
296 {
297         ModestNumberEditorPrivate *priv;
298         gint error_code, fixup_value;
299         const gchar *text;
300         long value;
301         gchar *tail;
302         gboolean r;
303         gboolean is_valid = TRUE;
304
305         g_assert (MODEST_IS_NUMBER_EDITOR(editor));
306         
307         priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
308         g_assert (priv);
309
310         text = gtk_entry_get_text (GTK_ENTRY (editor));
311         error_code = -1;
312         fixup_value = priv->default_val;
313
314         if (text && text[0]) {
315                 /* Try to convert entry text to number */
316                 value = strtol (text, &tail, 10);
317
318                 /* Check if conversion succeeded */
319                 if (tail[0] == 0) {
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
325                            and so on. */
326
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
334                                  negative or zero.
335                                  In all other cases situation can be fixed just by
336                                  adding new numbers to the string.
337                                 */
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;
341                                         is_valid = FALSE;
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;
345                                         is_valid = FALSE;
346                                 }
347                         } else {
348                                 if (value > priv->end) {
349                                         error_code = MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED;
350                                         fixup_value = priv->end;
351                                         is_valid = FALSE;
352                                 } else if (value < priv->start) {
353                                         error_code = MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED;
354                                         fixup_value = priv->start;
355                                         is_valid = FALSE;
356                                 }
357                         }
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 */
361                 } else {
362                         is_valid = FALSE;
363                         if (! allow_intermediate || strcmp (text, "-") != 0 || priv->start >= 0)
364                                 error_code = MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE;
365                 }
366         } else {
367                 is_valid = FALSE;
368                 if (! allow_intermediate)
369                         error_code = MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE;
370         }
371
372         if (error_code != -1) {
373                 /* If entry is empty and intermediate forms are nor allowed, 
374                    emit error signal */
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);
380         }
381
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);
385         }
386 }
387
388 static void 
389 modest_number_editor_entry_changed              (GtkWidget *widget, 
390                                                  gpointer data)
391 {
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");
395 }
396
397 static gboolean
398 modest_number_editor_entry_focusout             (GtkWidget *widget, 
399                                                  GdkEventFocus *event,
400                                                  gpointer data)
401 {
402         GtkWidget *window;
403         
404         g_assert (MODEST_IS_NUMBER_EDITOR(data));
405
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);
409
410         return FALSE;
411 }
412
413 static gboolean
414 modest_number_editor_range_error                (ModestNumberEditor *editor,
415                                                  ModestNumberEditorErrorType type)
416 {
417
418     gint min, max;
419     gchar *err_msg = NULL;
420     ModestNumberEditorPrivate *priv;
421
422     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
423     g_assert (priv);
424
425     min = priv->start;
426     max = priv->end;
427
428     /* Construct error message */
429     switch (type)
430     {
431         case MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED:
432             err_msg = g_strdup_printf (_HL("ckct_ib_maximum_value"), max, max);
433             break;
434
435         case MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED:
436             err_msg = g_strdup_printf (_HL("ckct_ib_minimum_value"), min, min);
437             break;
438
439         case MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE:
440             err_msg =
441                 g_strdup_printf (_HL("ckct_ib_set_a_value_within_range"), min, max);
442             break;
443     }
444
445     /* Infoprint error */
446     if (err_msg)
447     {
448         hildon_banner_show_information (GTK_WIDGET (GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET(editor),
449                                         GTK_TYPE_WINDOW))), NULL, err_msg);
450         g_free(err_msg);
451     }
452
453     return TRUE;
454 }
455
456 /**
457  * modest_number_editor_new:
458  * @min: minimum accepted value
459  * @max: maximum accepted value
460  * 
461  * Creates new number editor
462  *
463  * Returns: a new #ModestNumberEditor widget
464  */
465 GtkWidget*
466 modest_number_editor_new                        (gint min, 
467                                                  gint max)
468 {
469     ModestNumberEditor *editor = g_object_new (MODEST_TYPE_NUMBER_EDITOR, NULL);
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