Fixes FwNULL 5/16
[modest] / src / hildon2 / modest-country-picker.c
1 /* Copyright (c) 2008, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #ifndef _GNU_SOURCE
31 #define _GNU_SOURCE /* So we can use the getline() function, which is a convenient GNU extension. */
32 #endif
33
34 #include <stdio.h>
35
36 #include "modest-maemo-utils.h"
37 #include "modest-country-picker.h"
38 #include <hildon/hildon-touch-selector-entry.h>
39 #include <gtk/gtkliststore.h>
40 #include <gtk/gtkcelllayout.h>
41 #include <gtk/gtkcellrenderertext.h>
42
43 #include <stdlib.h>
44 #include <string.h> /* For memcpy() */
45 #include <langinfo.h>
46 #include <locale.h>
47 #include <libintl.h> /* For dgettext(). */
48
49 /* Include config.h so that _() works: */
50 #ifdef HAVE_CONFIG_H
51 #include <config.h>
52 #endif
53
54 #define MAX_LINE_LEN 128 /* max length of a line in MCC file */
55
56 G_DEFINE_TYPE (ModestCountryPicker, modest_country_picker, HILDON_TYPE_PICKER_BUTTON);
57
58 typedef struct
59 {
60         gint locale_mcc;
61 /*      GtkTreeModel *model; */
62 } ModestCountryPickerPrivate;
63
64 #define MODEST_COUNTRY_PICKER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
65                                                                            MODEST_TYPE_COUNTRY_PICKER, \
66                                                                            ModestCountryPickerPrivate))
67
68 static void
69 modest_country_picker_get_property (GObject *object, guint property_id,
70                                     GValue *value, GParamSpec *pspec)
71 {
72         switch (property_id) {
73         default:
74                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
75         }
76 }
77
78 static void
79 modest_country_picker_set_property (GObject *object, guint property_id,
80                                     const GValue *value, GParamSpec *pspec)
81 {
82         switch (property_id) {
83         default:
84                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
85         }
86 }
87
88 static void
89 modest_country_picker_dispose (GObject *object)
90 {
91         if (G_OBJECT_CLASS (modest_country_picker_parent_class)->dispose)
92                 G_OBJECT_CLASS (modest_country_picker_parent_class)->dispose (object);
93 }
94
95 enum MODEL_COLS {
96         MODEL_COL_NAME = 0, /* string */
97         MODEL_COL_MCC  = 1 /* the 'effective mcc' for this country */
98 };
99
100         
101 static void
102 modest_country_picker_finalize (GObject *object)
103 {
104         G_OBJECT_CLASS (modest_country_picker_parent_class)->finalize (object);
105 }
106
107 static void
108 modest_country_picker_class_init (ModestCountryPickerClass *klass)
109 {
110         GObjectClass *object_class = G_OBJECT_CLASS (klass);
111
112         g_type_class_add_private (klass, sizeof (ModestCountryPickerPrivate));
113
114         object_class->get_property = modest_country_picker_get_property;
115         object_class->set_property = modest_country_picker_set_property;
116         object_class->dispose = modest_country_picker_dispose;
117         object_class->finalize = modest_country_picker_finalize;
118 }
119
120
121
122
123 /* cluster mcc's, based on the list
124  * http://en.wikipedia.org/wiki/Mobile_country_code
125  */
126 static int
127 effective_mcc (gint mcc)
128 {
129         switch (mcc) {
130         case 405: return 404; /* india */
131         case 441: return 440; /* japan */       
132         case 235: return 234; /* united kingdom */
133         case 311:
134         case 312:
135         case 313:
136         case 314:
137         case 315:
138         case 316: return 310; /* united states */
139         default:  return mcc;
140         }
141 }
142
143
144 /* each line is of the form:
145    xxx    logical_id
146
147   NOTE: this function is NOT re-entrant, the out-param country
148   are static strings that should NOT be freed. and will change when
149   calling this function again
150
151   also note, this function will return the "effective mcc", which
152   is the normalized mcc for a country - ie. even if the there
153   are multiple entries for the United States with various mccs,
154   this function will always return 310, even if the real mcc parsed
155   would be 314. see the 'effective_mcc' function above.
156 */
157 static int
158 parse_mcc_mapping_line (const char* line,  char** country)
159 {
160         char mcc[4];  /* the mcc code, always 3 bytes*/
161         gchar *iter, *tab, *final;
162
163         if (!line) {
164                 *country = NULL;
165                 return 0;
166         }
167
168         /* Go to the first tab (Country separator) */
169         tab = g_utf8_strrchr (line, -1, '\t');
170         if (!tab)
171                 return 0;
172
173         *country = g_utf8_find_next_char (tab, NULL);
174
175         /* Replace by end of string. We need to use strlen, because
176            g_utf8_strrchr expects bytes and not UTF8 characters  */
177         final = g_utf8_strrchr (tab, strlen (tab) + 1, '\n');
178         if (G_LIKELY (final))
179                 *final = '\0';
180         else
181                 tab[strlen(tab) - 1] = '\0';
182
183         /* Get MCC code */
184         mcc[0] = g_utf8_get_char (line);
185         iter = g_utf8_find_next_char (line, NULL);
186         mcc[1] = g_utf8_get_char (iter);
187         iter = g_utf8_find_next_char (iter, NULL);
188         mcc[2] = g_utf8_get_char (iter);
189         mcc[3] = '\0';
190
191         return effective_mcc ((int) strtol ((const char*)mcc, NULL, 10));
192 }
193
194 /** Note that the mcc_mapping file is installed 
195  * by the operator-wizard-settings package.
196  */
197 static void
198 load_from_file (ModestCountryPicker *self, GtkListStore *liststore)
199 {
200         ModestCountryPickerPrivate *priv = MODEST_COUNTRY_PICKER_GET_PRIVATE (self);
201         gboolean translated;
202         char line[MAX_LINE_LEN];
203         guint previous_mcc = 0;
204         gchar *territory;
205
206         FILE *file = modest_maemo_open_mcc_mapping_file (&translated);
207         if (!file) {
208                 g_warning("Could not open mcc_mapping file");
209                 return;
210         }
211
212         /* Get the territory specified for the current locale */
213         territory = nl_langinfo (_NL_ADDRESS_COUNTRY_NAME);
214
215         while (fgets (line, MAX_LINE_LEN, file) != NULL) {
216
217                 int mcc;
218                 char *country = NULL;
219                 const gchar *name_translated;
220
221                 mcc = parse_mcc_mapping_line (line, &country);
222                 if (!country || mcc == 0) {
223                         g_warning ("%s: error parsing line: '%s'", __FUNCTION__, line);
224                         continue;
225                 }
226
227                 if (mcc == previous_mcc) {
228                         /* g_warning ("already seen: %s", line); */
229                         continue;
230                 }
231                 previous_mcc = mcc;
232
233                 if (!priv->locale_mcc) {
234                         if (translated) {
235                                 if (!g_utf8_collate (country, territory))
236                                         priv->locale_mcc = mcc;
237                         } else {
238                                 gchar *translation = dgettext ("osso-countries", country);
239                                 if (!g_utf8_collate (translation, territory))
240                                         priv->locale_mcc = mcc;
241                         }
242                 }
243                 name_translated = dgettext ("osso-countries", country);
244
245                 /* Add the row to the model: */
246                 GtkTreeIter iter;
247                 gtk_list_store_append (liststore, &iter);
248                 gtk_list_store_set(liststore, &iter, MODEL_COL_MCC, mcc, MODEL_COL_NAME, name_translated, -1);
249         }
250         fclose (file);
251
252         /* Fallback to Finland */
253         if (!priv->locale_mcc)
254                 priv->locale_mcc = 244;
255
256         /* Sort the items: */
257         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (liststore), 
258                                               MODEL_COL_NAME, GTK_SORT_ASCENDING);
259 }
260
261 static void
262 modest_country_picker_init (ModestCountryPicker *self)
263 {
264         ModestCountryPickerPrivate *priv = MODEST_COUNTRY_PICKER_GET_PRIVATE (self);
265         priv->locale_mcc = 0;
266 }
267
268 static gchar *
269 country_picker_print_func (HildonTouchSelector *selector, gpointer userdata)
270 {
271         GtkTreeModel *model;
272         GtkTreeIter iter;
273         gchar *text = NULL;
274
275         /* Always pick the selected country from the tree view and
276            never from the entry */
277         model = hildon_touch_selector_get_model (selector, 0);
278         if (hildon_touch_selector_get_selected (selector, 0, &iter)) {
279                 gint column;
280                 GtkWidget *entry;
281                 const gchar *entry_text;
282
283                 column = hildon_touch_selector_entry_get_text_column (HILDON_TOUCH_SELECTOR_ENTRY (selector));
284                 gtk_tree_model_get (model, &iter, column, &text, -1);
285
286                 entry = GTK_WIDGET (hildon_touch_selector_entry_get_entry (HILDON_TOUCH_SELECTOR_ENTRY (selector)));
287                 entry_text = hildon_entry_get_text (HILDON_ENTRY (entry));
288                 if (entry_text != NULL && text != NULL && strcmp (entry_text, text)) {
289                         hildon_entry_set_text (HILDON_ENTRY (entry), text);
290                 }
291         }
292         return text;
293 }
294
295 void
296 modest_country_picker_load_data(ModestCountryPicker *self)
297 {
298         GtkCellRenderer *renderer;
299         GtkWidget *selector;
300         GtkListStore *model;
301         HildonTouchSelectorColumn *column;
302
303         /* Create a tree model for the combo box,
304          * with a string for the name, and an int for the MCC ID.
305          * This must match our MODEL_COLS enum constants.
306          */
307         model = gtk_list_store_new (2,  G_TYPE_STRING, G_TYPE_INT);
308
309         /* Country column:
310          * The ID model column in not shown in the view. */
311         renderer = gtk_cell_renderer_text_new ();
312         g_object_set (G_OBJECT (renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
313
314         selector = hildon_touch_selector_entry_new ();
315         hildon_touch_selector_set_print_func (HILDON_TOUCH_SELECTOR (selector), (HildonTouchSelectorPrintFunc) country_picker_print_func);
316         column = hildon_touch_selector_append_column (HILDON_TOUCH_SELECTOR (selector), GTK_TREE_MODEL (model),
317                                                       renderer, "text", MODEL_COL_NAME, NULL);
318         g_object_set (G_OBJECT (column), "text-column", MODEL_COL_NAME, NULL);
319
320         /* Fill the model with rows: */
321         load_from_file (self, model);
322
323         /* Set this _after_ loading from file, it makes loading faster */
324         hildon_touch_selector_set_model (HILDON_TOUCH_SELECTOR (selector),
325                                          0, GTK_TREE_MODEL (model));
326         hildon_touch_selector_entry_set_text_column (HILDON_TOUCH_SELECTOR_ENTRY (selector),
327                                                      MODEL_COL_NAME);
328         hildon_touch_selector_entry_set_input_mode (HILDON_TOUCH_SELECTOR_ENTRY (selector),
329                                                     HILDON_GTK_INPUT_MODE_ALPHA |
330                                                     HILDON_GTK_INPUT_MODE_AUTOCAP);
331
332         hildon_picker_button_set_selector (HILDON_PICKER_BUTTON (self), HILDON_TOUCH_SELECTOR (selector));
333
334         g_object_unref (model);
335 }
336
337 ModestCountryPicker*
338 modest_country_picker_new (HildonSizeType size,
339                            HildonButtonArrangement arrangement)
340 {
341         return g_object_new (MODEST_TYPE_COUNTRY_PICKER, 
342                              "arrangement", arrangement,
343                              "size", size,
344                              NULL);
345 }
346
347 /**
348  * Returns the MCC number of the selected country, or 0 if no country was selected. 
349  */
350 gint
351 modest_country_picker_get_active_country_mcc (ModestCountryPicker *self)
352 {
353         GtkTreeIter active;
354         gboolean found;
355
356         found = hildon_touch_selector_get_selected (hildon_picker_button_get_selector
357                                                     (HILDON_PICKER_BUTTON (self)), 0, &active);
358         if (found) {
359                 gint mcc = 0;
360                 gtk_tree_model_get (hildon_touch_selector_get_model (hildon_picker_button_get_selector
361                                                                      (HILDON_PICKER_BUTTON (self)), 
362                                                                      0), 
363                                     &active, MODEL_COL_MCC, &mcc, -1);
364                 return mcc;     
365         }
366         return 0; /* Failed. */
367 }
368
369
370 /**
371  * Selects the MCC number of the selected country.
372  * Specify 0 to select no country. 
373  */
374 gboolean
375 modest_country_picker_set_active_country_locale (ModestCountryPicker *self)
376 {
377         ModestCountryPickerPrivate *priv = MODEST_COUNTRY_PICKER_GET_PRIVATE (self);
378         GtkWidget *selector;
379         GtkTreeIter iter;
380         gint current_mcc;
381         GtkTreeModel *model;
382
383         selector = GTK_WIDGET (hildon_picker_button_get_selector (HILDON_PICKER_BUTTON (self)));
384         model = hildon_touch_selector_get_model (HILDON_TOUCH_SELECTOR (selector), 0);
385         if (!gtk_tree_model_get_iter_first (model, &iter))
386                 return FALSE;
387         do {
388                 gtk_tree_model_get (model, &iter, MODEL_COL_MCC, &current_mcc, -1);
389                 if (priv->locale_mcc == current_mcc) {
390                         hildon_touch_selector_select_iter (HILDON_TOUCH_SELECTOR (selector), 0, 
391                                                            &iter, TRUE);
392                         hildon_button_set_value (HILDON_BUTTON (self), 
393                                                  hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (selector)));
394                         return TRUE;
395                 }
396         } while (gtk_tree_model_iter_next (model, &iter));
397         
398         return FALSE; /* not found */
399 }
400