X-Git-Url: http://git.maemo.org/git/?p=modest;a=blobdiff_plain;f=src%2Fmaemo%2Feasysetup%2Fmodest-easysetup-country-combo-box.c;h=7005569984720126211c8bcba0f7c136018ec7ee;hp=dcfd1d6d1710cf52637385ed74b13f32ac8372e1;hb=ef00f85692d7ba7b6e3f89421dda704bfc9e609f;hpb=f348bec88d15da6adc2622ac7c3b5ac58d8f85d6 diff --git a/src/maemo/easysetup/modest-easysetup-country-combo-box.c b/src/maemo/easysetup/modest-easysetup-country-combo-box.c index dcfd1d6..7005569 100644 --- a/src/maemo/easysetup/modest-easysetup-country-combo-box.c +++ b/src/maemo/easysetup/modest-easysetup-country-combo-box.c @@ -27,9 +27,13 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#ifndef _GNU_SOURCE #define _GNU_SOURCE /* So we can use the getline() function, which is a convenient GNU extension. */ +#endif + #include +#include "../modest-maemo-utils.h" #include "modest-easysetup-country-combo-box.h" #include #include @@ -45,6 +49,8 @@ #include #endif +#define MAX_LINE_LEN 128 /* max length of a line in MCC file */ + G_DEFINE_TYPE (EasysetupCountryComboBox, easysetup_country_combo_box, GTK_TYPE_COMBO_BOX); #define COUNTRY_COMBO_BOX_GET_PRIVATE(o) \ @@ -84,13 +90,18 @@ easysetup_country_combo_box_dispose (GObject *object) G_OBJECT_CLASS (easysetup_country_combo_box_parent_class)->dispose (object); } +enum MODEL_COLS { + MODEL_COL_NAME = 0, /* string */ + MODEL_COL_MCC = 1 /* the 'effective mcc' for this country */ +}; + + static void easysetup_country_combo_box_finalize (GObject *object) { EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (object); g_object_unref (G_OBJECT (priv->model)); - G_OBJECT_CLASS (easysetup_country_combo_box_parent_class)->finalize (object); } @@ -107,161 +118,168 @@ easysetup_country_combo_box_class_init (EasysetupCountryComboBoxClass *klass) object_class->finalize = easysetup_country_combo_box_finalize; } -enum MODEL_COLS { - MODEL_COL_NAME = 0, - MODEL_COL_ID = 1 -}; -/** id and country must be freed. + + +/* cluster mcc's, based on the list + * http://en.wikipedia.org/wiki/Mobile_country_code */ -static void parse_mcc_mapping_line (const char* line, char** id, char** country) +static int +effective_mcc (gint mcc) { - /* Initialize output parameters: */ - *id = NULL; - *country = NULL; - - g_assert(line); - - const gboolean is_valid_utf8 = g_utf8_validate (line, -1, NULL); - if(!is_valid_utf8) { - g_warning("UTF8 validation failed."); - return; + switch (mcc) { + case 405: return 404; /* india */ + case 441: return 440; /* japan */ + case 235: return 234; /* united kingdom */ + case 311: + case 312: + case 313: + case 314: + case 315: + case 316: return 310; /* united states */ + default: return mcc; + } +} + + +/* each line is of the form: + xxx logical_id + + NOTE: this function is NOT re-entrant, the out-param country + are static strings that should NOT be freed. and will change when + calling this function again + + also note, this function will return the "effective mcc", which + is the normalized mcc for a country - ie. even if the there + are multiple entries for the United States with various mccs, + this function will always return 310, even if the real mcc parsed + would be 314. see the 'effective_mcc' function above. +*/ +static int +parse_mcc_mapping_line (const char* line, char** country) +{ + int i, j; + char mcc[3]; /* the mcc code, always 3 bytes*/ + static char my_country[128]; + + if (!line) { + *country = NULL; + return 0; } - /* Look at each character, to find the whitespace between the ID and name: */ - char* result_id = NULL; - char* result_country = NULL; - - const char* p = line; - const char* p_start_of_country = NULL; - while (p && *p) - { - p = g_utf8_next_char(p); - gunichar ch = g_utf8_get_char(p); - if (g_unichar_isspace(ch)) { /* Note: This checks for any whitespace, not just space. */ - if(!result_id) { - /* The text before this must be the ID: */ - const int length = p - line; - result_id = g_malloc (length + 1); /* 1 for null-termination. */ - memcpy(result_id, line, length); - result_id[length] = 0; /* Null-termination. */ - } - else if(p_start_of_country) - { - /* This whitespace is probably the newline after the country. */ - - /* The text after the whitespace, after the ID, must be the country: */ - int length = p - p_start_of_country; - result_country = g_malloc(length + 1); - memcpy(result_country, p_start_of_country, length); - result_country[length] = 0; /* Null-termination. */ - break; - } - } - else if(result_id && !p_start_of_country) { - p_start_of_country = p; - } + for (i = 3, j = 0; i < 128; ++i) { + char kar = line[i]; + if (kar == '\0') + break; + else if (kar < '_') + continue; + else + my_country [j++] = kar; } + my_country[j] = '\0'; + + mcc[0] = line[0]; + mcc[1] = line[1]; + mcc[2] = line[2]; - *id = result_id; - *country = result_country; + *country = my_country; + + return effective_mcc ((int) strtol ((const char*)mcc, NULL, 10)); } /** Note that the mcc_mapping file is installed * by the operator-wizard-settings package. */ -static void load_from_file (EasysetupCountryComboBox *self) +static void +load_from_file (EasysetupCountryComboBox *self) { EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (self); + GtkListStore *liststore = GTK_LIST_STORE (priv->model); - /* Load the file one line at a time: */ -#ifndef MODEST_HILDON_VERSION_0 - const gchar* filepath = PROVIDER_DATA_DIR "/mcc_mapping"; -#else - /* this is the official version, in the 'operator-wizard-settings' package */ - const gchar* filepath = "/usr/share/operator-wizard/mcc_mapping"; -#endif /*MODEST_HILDON_VERSION_0*/ - FILE *file = fopen(filepath, "r"); - if (!file) - { - const gchar* filepath_hack = HACK_TOP_SRCDIR "src/maemo/easysetup/mcc_mapping"; - g_warning ("Could not locate the official mcc_mapping countries list file from %s, " - "so attempting to load it instead from %s", filepath, filepath_hack); - file = fopen(filepath_hack, "r"); - } + char line[MAX_LINE_LEN]; + guint previous_mcc = 0; + FILE *file = modest_maemo_open_mcc_mapping_file (); if (!file) { g_warning("Could not open mcc_mapping file"); return; } - GtkListStore *liststore = GTK_LIST_STORE (priv->model); - - /* We use the getline() GNU extension, - * because it reads per line, which simplifies our code, - * and it doesn't require us to hard-code a buffer length. - */ - int len = 0; - char *line = NULL; - while (getline (&line, &len, file) > 0) { /* getline will realloc line if necessary. */ - /* printf ("DBEUG: len=%d, line: %s\n", len, line); */ - - char *id_str = NULL; + while (fgets (line, MAX_LINE_LEN, file) > 0) { + + int mcc; char *country = NULL; - parse_mcc_mapping_line (line, &id_str, &country); - //printf("DEBUG: parsed: id=%s, country=%s\n", id_str, country); - - if(id_str && country) { - guint id = (guint)g_ascii_strtod(id_str, NULL); /* Note that this parses locale-independent text. */ - - /* Get the translation for the country name: - * Note that the osso_countries_1.0 translation domain files are installed - * by the operator-wizard-settings package. */ - /* For post-Bora, there is a separate (meta)package osso-countries-l10n-mr0 */ - const gchar *name_translated = dgettext ("osso-countries", country); - if(!name_translated) - name_translated = country; - - /* Add the row to the model: */ - GtkTreeIter iter; - gtk_list_store_append (liststore, &iter); - gtk_list_store_set(liststore, &iter, MODEL_COL_ID, id, MODEL_COL_NAME, name_translated, -1); + mcc = parse_mcc_mapping_line (line, &country); + if (!country || mcc == 0) { + g_warning ("%s: error parsing line: '%s'", __FUNCTION__, line); + continue; } - - g_free (id_str); - g_free (country); - } - if (line) - free (line); + if (mcc == previous_mcc) { + /* g_warning ("already seen: %s", line); */ + continue; + } + previous_mcc = mcc; + + /* Get the translation for the country name: + * Note that the osso_countries_1.0 translation domain files are installed + * by the operator-wizard-settings package. */ + /* For post-Bora, there is a separate (meta)package osso-countries-l10n-mr0 */ + + /* Note: Even when the untranslated names are different, there may still be + * duplicate translated names. They would be translation bugs. + */ + const gchar *name_translated = dgettext ("osso-countries", country); + /* Add the row to the model: */ + GtkTreeIter iter; + gtk_list_store_append (liststore, &iter); + gtk_list_store_set(liststore, &iter, MODEL_COL_MCC, mcc, MODEL_COL_NAME, name_translated, -1); + } fclose (file); + + /* Sort the items: */ + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (liststore), + MODEL_COL_NAME, GTK_SORT_ASCENDING); } static void easysetup_country_combo_box_init (EasysetupCountryComboBox *self) { EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (self); + priv->model = NULL; +} + +void +easysetup_country_combo_box_load_data(EasysetupCountryComboBox *self) +{ + EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (self); /* Create a tree model for the combo box, * with a string for the name, and an int for the MCC ID. * This must match our MODEL_COLS enum constants. */ - priv->model = GTK_TREE_MODEL (gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_UINT)); - + priv->model = GTK_TREE_MODEL (gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT)); + /* Setup the combo box: */ GtkComboBox *combobox = GTK_COMBO_BOX (self); - gtk_combo_box_set_model (combobox, priv->model); - + /* Country column: * The ID model column in not shown in the view. */ GtkCellRenderer *renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT (combobox), renderer, TRUE); gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combobox), renderer, "text", MODEL_COL_NAME, NULL); + + + g_assert (GTK_IS_LIST_STORE(priv->model)); + /* Fill the model with rows: */ load_from_file (self); + /* Set this _after_ loading from file, it makes loading faster */ + gtk_combo_box_set_model (combobox, priv->model); } EasysetupCountryComboBox* @@ -273,74 +291,42 @@ easysetup_country_combo_box_new (void) /** * Returns the MCC number of the selected country, or 0 if no country was selected. */ -guint -easysetup_country_combo_box_get_active_country_id (EasysetupCountryComboBox *self) +gint +easysetup_country_combo_box_get_active_country_mcc (EasysetupCountryComboBox *self) { GtkTreeIter active; const gboolean found = gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self), &active); if (found) { EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (self); - - guint id = 0; - gtk_tree_model_get (priv->model, &active, MODEL_COL_ID, &id, -1); - return id; + gint mcc = 0; + gtk_tree_model_get (priv->model, &active, MODEL_COL_MCC, &mcc, -1); + return mcc; } - return 0; /* Failed. */ } -/* This allows us to pass more than one piece of data to the signal handler, - * and get a result: */ -typedef struct -{ - EasysetupCountryComboBox* self; - guint mcc_id; - gboolean found; -} ForEachData; - -static gboolean -on_model_foreach_select_id(GtkTreeModel *model, - GtkTreePath *path, GtkTreeIter *iter, gpointer user_data) -{ - ForEachData *state = (ForEachData*)(user_data); - - /* Select the item if it has the matching ID: */ - guint id = 0; - gtk_tree_model_get (model, iter, MODEL_COL_ID, &id, -1); - if(id == state->mcc_id) { - gtk_combo_box_set_active_iter (GTK_COMBO_BOX (state->self), iter); - - state->found = TRUE; - return TRUE; /* Stop walking the tree. */ - } - - return FALSE; /* Keep walking the tree. */ -} - /** * Selects the MCC number of the selected country. * Specify 0 to select no country. */ gboolean -easysetup_country_combo_box_set_active_country_id (EasysetupCountryComboBox *self, guint mcc_id) +easysetup_country_combo_box_set_active_country_mcc (EasysetupCountryComboBox *self, guint mcc) { EasysetupCountryComboBoxPrivate *priv = COUNTRY_COMBO_BOX_GET_PRIVATE (self); + GtkTreeIter iter; + + if (!gtk_tree_model_get_iter_first (priv->model, &iter)) + return FALSE; + do { + gint current_mcc = 0; + gtk_tree_model_get (priv->model, &iter, MODEL_COL_MCC, ¤t_mcc, -1); + if (current_mcc == mcc) { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (self), &iter); + return TRUE; + } + } while (gtk_tree_model_iter_next (priv->model, &iter)); - /* Create a state instance so we can send two items of data to the signal handler: */ - ForEachData *state = g_new0 (ForEachData, 1); - state->self = self; - state->mcc_id = mcc_id; - state->found = FALSE; - - /* Look at each item, and select the one with the correct ID: */ - gtk_tree_model_foreach (priv->model, &on_model_foreach_select_id, state); - - const gboolean result = state->found; - - /* Free the state instance: */ - g_free(state); - - return result; + return FALSE; /* not found */ }