* optimize the parsing of the provider data, particularly the
[modest] / src / maemo / easysetup / modest-easysetup-country-combo-box.c
1 /* Copyright (c) 2006, 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-easysetup-country-combo-box.h"
37 #include <gtk/gtkliststore.h>
38 #include <gtk/gtkcelllayout.h>
39 #include <gtk/gtkcellrenderertext.h>
40
41 #include <stdlib.h>
42 #include <string.h> /* For memcpy() */
43
44 #include <libintl.h> /* For dgettext(). */
45
46 /* Include config.h so that _() works: */
47 #ifdef HAVE_CONFIG_H
48 #include <config.h>
49 #endif
50
51 #define MAX_LINE_LEN 128 /* max length of a line in MCC file */
52
53 G_DEFINE_TYPE (EasysetupCountryComboBox, easysetup_country_combo_box, GTK_TYPE_COMBO_BOX);
54
55 #define COUNTRY_COMBO_BOX_GET_PRIVATE(o) \
56         (G_TYPE_INSTANCE_GET_PRIVATE ((o), EASYSETUP_TYPE_COUNTRY_COMBO_BOX, EasysetupCountryComboBoxPrivate))
57
58 typedef struct _EasysetupCountryComboBoxPrivate EasysetupCountryComboBoxPrivate;
59
60 struct _EasysetupCountryComboBoxPrivate
61 {
62         GtkTreeModel *model;
63 };
64
65 static void
66 easysetup_country_combo_box_get_property (GObject *object, guint property_id,
67                                                                                                                         GValue *value, GParamSpec *pspec)
68 {
69         switch (property_id) {
70         default:
71                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
72         }
73 }
74
75 static void
76 easysetup_country_combo_box_set_property (GObject *object, guint property_id,
77                                                                                                                         const GValue *value, GParamSpec *pspec)
78 {
79         switch (property_id) {
80         default:
81                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
82         }
83 }
84
85 static void
86 easysetup_country_combo_box_dispose (GObject *object)
87 {
88         if (G_OBJECT_CLASS (easysetup_country_combo_box_parent_class)->dispose)
89                 G_OBJECT_CLASS (easysetup_country_combo_box_parent_class)->dispose (object);
90 }
91
92 enum MODEL_COLS {
93         MODEL_COL_NAME = 0, /* string */
94         MODEL_COL_MCC  = 1 /* the 'effective mcc' for this country */
95 };
96
97         
98 static void
99 easysetup_country_combo_box_finalize (GObject *object)
100 {
101         EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (object);
102
103         g_object_unref (G_OBJECT (priv->model));
104         G_OBJECT_CLASS (easysetup_country_combo_box_parent_class)->finalize (object);
105 }
106
107 static void
108 easysetup_country_combo_box_class_init (EasysetupCountryComboBoxClass *klass)
109 {
110         GObjectClass *object_class = G_OBJECT_CLASS (klass);
111
112         g_type_class_add_private (klass, sizeof (EasysetupCountryComboBoxPrivate));
113
114         object_class->get_property = easysetup_country_combo_box_get_property;
115         object_class->set_property = easysetup_country_combo_box_set_property;
116         object_class->dispose = easysetup_country_combo_box_dispose;
117         object_class->finalize = easysetup_country_combo_box_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         int i, j;
161         char mcc[4];  /* the mcc code, always 3 bytes*/
162         static char my_country[128];
163         
164         /* Initialize output parameters: */
165         *country = NULL;
166
167         g_return_val_if_fail (line && strlen(line) > 20, 0);
168         
169         for (i = 3, j = 0; i < 128; ++i) {
170                 char kar = line[i];
171                 if (kar < '_' ) { /* optimization */
172                         if (kar == '\n' || kar == '\r')
173                                 break;
174                         else if (kar == ' ' || kar == '\t')
175                                 continue;
176                         else  /* error */
177                                 return 0;
178                 } else {
179                         printf ("%c", kar);
180                         my_country [j++] = kar;
181                 }
182         }
183         my_country[j] = '\0';
184
185         mcc[0] = line[0];
186         mcc[1] = line[1];
187         mcc[2] = line[2];
188         mcc[3] = '\0';
189         
190         *country = my_country;
191         return effective_mcc (atoi(mcc));
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 (EasysetupCountryComboBox *self)
199 {
200         EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (self);
201         GtkListStore *liststore = GTK_LIST_STORE (priv->model);
202         
203         char line[MAX_LINE_LEN];
204         guint previous_mcc = 0;
205                 
206         /* Load the file one line at a time: */
207 #ifdef MODEST_HILDON_VERSION_0
208         const gchar* filepath = PROVIDER_DATA_DIR "/mcc_mapping";
209 #else
210         /* This is the official version, in the 'operator-wizard-settings' package */
211         const gchar* filepath = "/usr/share/operator-wizard/mcc_mapping";
212 #endif /*MODEST_HILDON_VERSION_0*/
213         /* printf ("DEBUG: %s: filepath=%s\n", __FUNCTION__, filepath); */
214         FILE *file = fopen(filepath, "r");
215         if (!file) {
216                 const gchar* filepath_hack = HACK_TOP_SRCDIR "src/maemo/easysetup/mcc_mapping";
217                 g_warning ("Could not locate the official mcc_mapping countries list file from %s, "
218                         "so attempting to load it instead from %s", filepath, filepath_hack);
219                 file = fopen(filepath_hack, "r");
220         }
221         if (!file) {
222                 g_warning("Could not open mcc_mapping file");
223                 return;
224         }
225
226         while (fgets (line, MAX_LINE_LEN, file) > 0) { 
227
228                 int mcc;
229                 char *country = NULL;
230                 mcc = parse_mcc_mapping_line (line, &country);
231                 if (!country || mcc == 0) {
232                         g_warning ("%s: error parsing line: '%s'", __FUNCTION__, line);
233                         continue;
234                 }
235
236                 if (mcc == previous_mcc) {
237                         /* g_warning ("already seen: %s", line); */
238                         continue;
239                 }
240                 previous_mcc = mcc;
241                 
242                 /* Get the translation for the country name:
243                  * Note that the osso_countries_1.0 translation domain files are installed 
244                  * by the operator-wizard-settings package. */
245                 /* For post-Bora, there is a separate (meta)package osso-countries-l10n-mr0 */
246                                 
247                 /* Note: Even when the untranslated names are different, there may still be 
248                  * duplicate translated names. They would be translation bugs.
249                  */
250                 const gchar *name_translated = dgettext ("osso-countries", country);
251                 
252                 /* Add the row to the model: */
253                 GtkTreeIter iter;
254                 gtk_list_store_append (liststore, &iter);
255                 gtk_list_store_set(liststore, &iter, MODEL_COL_MCC, mcc, MODEL_COL_NAME, name_translated, -1);
256         }       
257         fclose (file);
258         
259         /* Sort the items: */
260         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (liststore), 
261                                               MODEL_COL_NAME, GTK_SORT_ASCENDING);
262 }
263
264 static void
265 easysetup_country_combo_box_init (EasysetupCountryComboBox *self)
266 {
267         EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (self);
268
269         /* Create a tree model for the combo box,
270          * with a string for the name, and an int for the MCC ID.
271          * This must match our MODEL_COLS enum constants.
272          */
273         priv->model = GTK_TREE_MODEL (gtk_list_store_new (2,  G_TYPE_STRING, G_TYPE_INT));
274         
275         /* Setup the combo box: */
276         GtkComboBox *combobox = GTK_COMBO_BOX (self);
277         gtk_combo_box_set_model (combobox, priv->model);
278         
279         /* Country column:
280          * The ID model column in not shown in the view. */
281         GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
282         g_object_set (G_OBJECT (renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
283         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT (combobox), renderer, TRUE);
284         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combobox), renderer, 
285         "text", MODEL_COL_NAME, NULL);
286
287         
288         g_assert (GTK_IS_LIST_STORE(priv->model));
289
290         
291         /* Fill the model with rows: */
292         load_from_file (self);
293 }
294
295 EasysetupCountryComboBox*
296 easysetup_country_combo_box_new (void)
297 {
298         return g_object_new (EASYSETUP_TYPE_COUNTRY_COMBO_BOX, NULL);
299 }
300
301 /**
302  * Returns the MCC number of the selected country, or 0 if no country was selected. 
303  */
304 gint
305 easysetup_country_combo_box_get_active_country_mcc (EasysetupCountryComboBox *self)
306 {
307         GtkTreeIter active;
308         const gboolean found = gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self), &active);
309         if (found) {
310                 EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (self);
311                 gint mcc = 0;
312                 gtk_tree_model_get (priv->model, &active, MODEL_COL_MCC, &mcc, -1); 
313                 return mcc;     
314         }
315         return 0; /* Failed. */
316 }
317
318
319 /**
320  * Selects the MCC number of the selected country.
321  * Specify 0 to select no country. 
322  */
323 gboolean
324 easysetup_country_combo_box_set_active_country_mcc (EasysetupCountryComboBox *self, guint mcc)
325 {
326         EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (self);
327         GtkTreeIter iter;
328
329         if (!gtk_tree_model_get_iter_first (priv->model, &iter)) 
330                 return FALSE;
331         do {
332                 gint current_mcc = 0;
333                 gtk_tree_model_get (priv->model, &iter, MODEL_COL_MCC, &current_mcc, -1);
334                 if (current_mcc == mcc) {
335                         gtk_combo_box_set_active_iter (GTK_COMBO_BOX (self), &iter);
336                         return TRUE;
337                 }
338         } while (gtk_tree_model_iter_next (priv->model, &iter));
339         
340         return FALSE; /* not found */
341 }
342