* fix for two bugs in modest-easysetup-country-combo-box.c:
[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-maemo-utils.h"
37 #include "modest-easysetup-country-combo-box.h"
38 #include <gtk/gtkliststore.h>
39 #include <gtk/gtkcelllayout.h>
40 #include <gtk/gtkcellrenderertext.h>
41
42 #include <stdlib.h>
43 #include <string.h> /* For memcpy() */
44
45 #include <libintl.h> /* For dgettext(). */
46
47 /* Include config.h so that _() works: */
48 #ifdef HAVE_CONFIG_H
49 #include <config.h>
50 #endif
51
52 #define MAX_LINE_LEN 128 /* max length of a line in MCC file */
53
54 G_DEFINE_TYPE (EasysetupCountryComboBox, easysetup_country_combo_box, GTK_TYPE_COMBO_BOX);
55
56 #define COUNTRY_COMBO_BOX_GET_PRIVATE(o) \
57         (G_TYPE_INSTANCE_GET_PRIVATE ((o), EASYSETUP_TYPE_COUNTRY_COMBO_BOX, EasysetupCountryComboBoxPrivate))
58
59 typedef struct _EasysetupCountryComboBoxPrivate EasysetupCountryComboBoxPrivate;
60
61 struct _EasysetupCountryComboBoxPrivate
62 {
63         GtkTreeModel *model;
64 };
65
66 static void
67 easysetup_country_combo_box_get_property (GObject *object, guint property_id,
68                                                                                                                         GValue *value, GParamSpec *pspec)
69 {
70         switch (property_id) {
71         default:
72                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
73         }
74 }
75
76 static void
77 easysetup_country_combo_box_set_property (GObject *object, guint property_id,
78                                                                                                                         const GValue *value, GParamSpec *pspec)
79 {
80         switch (property_id) {
81         default:
82                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
83         }
84 }
85
86 static void
87 easysetup_country_combo_box_dispose (GObject *object)
88 {
89         if (G_OBJECT_CLASS (easysetup_country_combo_box_parent_class)->dispose)
90                 G_OBJECT_CLASS (easysetup_country_combo_box_parent_class)->dispose (object);
91 }
92
93 enum MODEL_COLS {
94         MODEL_COL_NAME = 0, /* string */
95         MODEL_COL_MCC  = 1 /* the 'effective mcc' for this country */
96 };
97
98         
99 static void
100 easysetup_country_combo_box_finalize (GObject *object)
101 {
102         EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (object);
103
104         g_object_unref (G_OBJECT (priv->model));
105         G_OBJECT_CLASS (easysetup_country_combo_box_parent_class)->finalize (object);
106 }
107
108 static void
109 easysetup_country_combo_box_class_init (EasysetupCountryComboBoxClass *klass)
110 {
111         GObjectClass *object_class = G_OBJECT_CLASS (klass);
112
113         g_type_class_add_private (klass, sizeof (EasysetupCountryComboBoxPrivate));
114
115         object_class->get_property = easysetup_country_combo_box_get_property;
116         object_class->set_property = easysetup_country_combo_box_set_property;
117         object_class->dispose = easysetup_country_combo_box_dispose;
118         object_class->finalize = easysetup_country_combo_box_finalize;
119 }
120
121
122
123
124 /* cluster mcc's, based on the list
125  * http://en.wikipedia.org/wiki/Mobile_country_code
126  */
127 static int
128 effective_mcc (gint mcc)
129 {
130         switch (mcc) {
131         case 405: return 404; /* india */
132         case 441: return 440; /* japan */       
133         case 235: return 234; /* united kingdom */
134         case 311:
135         case 312:
136         case 313:
137         case 314:
138         case 315:
139         case 316: return 310; /* united states */
140         default:  return mcc;
141         }
142 }
143
144
145 /* each line is of the form:
146    xxx    logical_id
147    
148   NOTE: this function is NOT re-entrant, the out-param country
149   are static strings that should NOT be freed. and will change when
150   calling this function again
151
152   also note, this function will return the "effective mcc", which
153   is the normalized mcc for a country - ie. even if the there
154   are multiple entries for the United States with various mccs,
155   this function will always return 310, even if the real mcc parsed
156   would be 314. see the 'effective_mcc' function above.
157 */
158 static int
159 parse_mcc_mapping_line (const char* line,  char** country)
160 {
161         int i, j;
162         char mcc[4];  /* the mcc code, always 3 bytes*/
163         static char my_country[128];
164
165         if (!line) {
166                 *country = NULL;
167                 return 0;
168         }
169         
170         for (i = 3, j = 0; i < 128; ++i) {
171                 char kar = line[i];
172                 if (kar == '\0')
173                         break;
174                 else if (kar < '_')
175                         continue;
176                 else 
177                         my_country [j++] = kar;
178         }
179         my_country[j] = '\0';
180
181         mcc[0] = line[0];
182         mcc[1] = line[1];
183         mcc[2] = line[2];
184         mcc[3] = '\0';
185         
186         *country = my_country;
187
188         return effective_mcc ((int) strtol ((const char*)mcc, NULL, 10));
189 }
190
191 /** Note that the mcc_mapping file is installed 
192  * by the operator-wizard-settings package.
193  */
194 static void
195 load_from_file (EasysetupCountryComboBox *self)
196 {
197         EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (self);
198         GtkListStore *liststore = GTK_LIST_STORE (priv->model);
199         
200         char line[MAX_LINE_LEN];
201         guint previous_mcc = 0;
202         
203         FILE *file = modest_maemo_open_mcc_mapping_file ();
204         if (!file) {
205                 g_warning("Could not open mcc_mapping file");
206                 return;
207         }
208
209         while (fgets (line, MAX_LINE_LEN, file) != NULL) { 
210
211                 int mcc;
212                 char *country = NULL;
213                 mcc = parse_mcc_mapping_line (line, &country);
214                 if (!country || mcc == 0) {
215                         g_warning ("%s: error parsing line: '%s'", __FUNCTION__, line);
216                         continue;
217                 }
218
219                 if (mcc == previous_mcc) {
220                         /* g_warning ("already seen: %s", line); */
221                         continue;
222                 }
223                 previous_mcc = mcc;
224                 
225                 /* Get the translation for the country name:
226                  * Note that the osso_countries_1.0 translation domain files are installed 
227                  * by the operator-wizard-settings package. */
228                 /* For post-Bora, there is a separate (meta)package osso-countries-l10n-mr0 */
229                                 
230                 /* Note: Even when the untranslated names are different, there may still be 
231                  * duplicate translated names. They would be translation bugs.
232                  */
233                 const gchar *name_translated = dgettext ("osso-countries", country);
234                 
235                 /* Add the row to the model: */
236                 GtkTreeIter iter;
237                 gtk_list_store_append (liststore, &iter);
238                 gtk_list_store_set(liststore, &iter, MODEL_COL_MCC, mcc, MODEL_COL_NAME, name_translated, -1);
239         }       
240         fclose (file);
241         
242         /* Sort the items: */
243         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (liststore), 
244                                               MODEL_COL_NAME, GTK_SORT_ASCENDING);
245 }
246
247 static void
248 easysetup_country_combo_box_init (EasysetupCountryComboBox *self)
249 {
250         EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (self);
251         priv->model = NULL;
252 }
253
254 void
255 easysetup_country_combo_box_load_data(EasysetupCountryComboBox *self)
256 {
257         EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (self);
258
259         /* Create a tree model for the combo box,
260          * with a string for the name, and an int for the MCC ID.
261          * This must match our MODEL_COLS enum constants.
262          */
263         priv->model = GTK_TREE_MODEL (gtk_list_store_new (2,  G_TYPE_STRING, G_TYPE_INT));
264         
265         /* Setup the combo box: */
266         GtkComboBox *combobox = GTK_COMBO_BOX (self);
267         
268         /* Country column:
269          * The ID model column in not shown in the view. */
270         GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
271         g_object_set (G_OBJECT (renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
272         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT (combobox), renderer, TRUE);
273         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combobox), renderer, 
274         "text", MODEL_COL_NAME, NULL);
275
276         
277         g_assert (GTK_IS_LIST_STORE(priv->model));
278
279         
280         /* Fill the model with rows: */
281         load_from_file (self);
282         /* Set this _after_ loading from file, it makes loading faster */
283         gtk_combo_box_set_model (combobox, priv->model);
284 }
285
286 EasysetupCountryComboBox*
287 easysetup_country_combo_box_new (void)
288 {
289         return g_object_new (EASYSETUP_TYPE_COUNTRY_COMBO_BOX, NULL);
290 }
291
292 /**
293  * Returns the MCC number of the selected country, or 0 if no country was selected. 
294  */
295 gint
296 easysetup_country_combo_box_get_active_country_mcc (EasysetupCountryComboBox *self)
297 {
298         GtkTreeIter active;
299         const gboolean found = gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self), &active);
300         if (found) {
301                 EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (self);
302                 gint mcc = 0;
303                 gtk_tree_model_get (priv->model, &active, MODEL_COL_MCC, &mcc, -1); 
304                 return mcc;     
305         }
306         return 0; /* Failed. */
307 }
308
309
310 /**
311  * Selects the MCC number of the selected country.
312  * Specify 0 to select no country. 
313  */
314 gboolean
315 easysetup_country_combo_box_set_active_country_mcc (EasysetupCountryComboBox *self, guint mcc)
316 {
317         EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (self);
318         GtkTreeIter iter;
319
320         if (!gtk_tree_model_get_iter_first (priv->model, &iter)) 
321                 return FALSE;
322         do {
323                 gint current_mcc = 0;
324                 gtk_tree_model_get (priv->model, &iter, MODEL_COL_MCC, &current_mcc, -1);
325                 if (current_mcc == mcc) {
326                         gtk_combo_box_set_active_iter (GTK_COMBO_BOX (self), &iter);
327                         return TRUE;
328                 }
329         } while (gtk_tree_model_iter_next (priv->model, &iter));
330         
331         return FALSE; /* not found */
332 }
333