X-Git-Url: https://vcs.maemo.org/git/?a=blobdiff_plain;f=belltower.c;h=42e65d7922915c9c31903091d858ff4b8c4f573b;hb=HEAD;hp=54373bdf43550bd1b07df9653160ef944cdd70c3;hpb=1b9332f15e723c26747c9b85e0a87a0bc2efc414;p=belltower diff --git a/belltower.c b/belltower.c index 54373bd..42e65d7 100644 --- a/belltower.c +++ b/belltower.c @@ -8,17 +8,89 @@ #include #include +#include #include #include #include #include +#include #include #define MAX_FIELDS 50 +#define MAX_RECENT 5 +#define CONFIG_GENERAL_GROUP "General" +#define CONFIG_DISTANCES_KEY "Distances" +#define CONFIG_TOWERSORT_KEY "Towersort" +#define CONFIG_BOOKMARK_GROUP "Bookmarks" +#define CONFIG_RECENT_GROUP "Recent" +#define CONFIG_SEEN_CREDITS_KEY "seen_credits" +#define CONFIG_DIRECTORY "/home/user/.config/belltower" +#define CONFIG_FILENAME CONFIG_DIRECTORY "/belltower.ini" + +/** + * Somewhat arbitrary minimum number of belltowers in + * one country for the country to be considered to have + * "many" belltowers. + */ +#define MANY_BELLTOWERS 10 + +char *distances_settings[] = { + CONFIG_DISTANCES_KEY, + "Distances are measured in...", + "Miles", + "Kilometres", + NULL +}; + +char *towersort_settings[] = { + CONFIG_TOWERSORT_KEY, + "Towers are sorted by...", + "Name of town", + "Distance from you", + "Days until practice night", + "Number of bells", + "Weight of tenor", + NULL +}; + +char **settings[] = { + distances_settings, + towersort_settings +}; + +gint settings_value[G_N_ELEMENTS (settings)] = { 0, }; + +typedef enum { + SETTINGS_DISTANCES, + SETTINGS_TOWERSORT +} Settings; -#define EM_DASH "\xE2\x80\x94" +typedef enum { + DISTANCES_MILES, + DISTANCES_KILOMETRES +} DistancesSetting; + +typedef enum { + TOWERSORT_TOWN, + TOWERSORT_DISTANCE, + TOWERSORT_PRACTICE, + TOWERSORT_BELLS, + TOWERSORT_WEIGHT +} TowersortSetting; GtkWidget *window; +LocationGPSDevice *device; +GKeyFile *static_content; +GKeyFile *config; + +typedef enum { + /** stop scanning the database */ + FILTER_STOP, + /** ignore this one */ + FILTER_IGNORE, + /** add this one to the list */ + FILTER_ACCEPT +} FilterResult; /* FIXME: @@ -74,6 +146,9 @@ typedef struct { int n_fields; } tower; +static void show_towers_from_list (GSList *list, gchar *list_name); +static void free_tower_list (GSList *list); + static void show_message (char *message) { @@ -85,6 +160,164 @@ show_message (char *message) gtk_widget_destroy (GTK_WIDGET (note)); } +/** + * Loads the content of the static and dynamic data files. + * Possibly puts up a warning if we can't load the static file. + */ +static void +load_config (void) +{ + gint i; + + static_content = g_key_file_new (); + + if (!g_key_file_load_from_file (static_content, + "/usr/share/belltower/static.ini", + G_KEY_FILE_NONE, + NULL)) + { + show_message ("Could not load static content. Attempting to continue."); + } + + config = g_key_file_new (); + /* it doesn't matter if this fails */ + g_key_file_load_from_file (config, + CONFIG_FILENAME, + G_KEY_FILE_KEEP_COMMENTS, + NULL); + + for (i=0; ifields[FieldLat], &endptr); + if (*endptr) return -1; + tower_long = strtod(details->fields[FieldLong], &endptr); + if (*endptr) return -1; + + km_distance = location_distance_between (device->fix->latitude, + device->fix->longitude, + tower_lat, + tower_long); + if (settings_value[SETTINGS_DISTANCES]==DISTANCES_KILOMETRES) + return (int) km_distance; + else + return (int) (km_distance / km_to_miles); +} + +static gchar* +distance_to_tower_str (tower *details) +{ + int distance = distance_to_tower (details); + + if (distance==-1) + { + return g_strdup ("unknown"); + } + else if (settings_value[SETTINGS_DISTANCES]==DISTANCES_KILOMETRES) + { + return g_strdup_printf("%dkm", (int) distance); + } + else + { + return g_strdup_printf("%dmi", (int) distance); + } +} + +/** + * Returns the number of days from today until + * the tower's practice night. If the tower's + * practice night is unknown, returns 9: this + * means that such towers always sort to the end. + */ +static gint +days_until_practice_night (tower *details) +{ + /* let's not use the date parsing routines, because + * we might get confused by locales, and the day names + * used in Dove are constant + */ + time_t now = time (NULL); + struct tm *calendar = localtime (&now); + const char* dove_days = "SunMonTueWedThuFriSat"; + char *found; + gint practice_night; + + if (strcmp (details->fields[FieldPracticeNight], "")==0) + { + /* we don't know */ + return 9; + } + + found = strstr (dove_days, details->fields[FieldPracticeNight]); + + if (!found) + { + return 9; + } + + practice_night = (found-dove_days)/3; + + return ((practice_night+7)-calendar->tm_wday)%7; +} + static void call_dbus (DBusBusType type, char *name, @@ -131,7 +364,7 @@ show_browser (gchar *url) url); } -typedef gboolean (*ParseDoveCallback)(tower *details, gpointer data); +typedef FilterResult (*ParseDoveCallback)(tower *details, gpointer data); typedef void (*ButtonCallback)(void); GtkWidget *tower_window, *buttons, *tower_table; @@ -184,17 +417,54 @@ add_button (char *label, } +char *tower_displayed = NULL; +char *tower_website = NULL; +char *tower_map = NULL; +char *tower_directions = NULL; +char *peals_list = NULL; + +#define BUTTON_BOOKMARKED_YES "Remove from bookmarks" +#define BUTTON_BOOKMARKED_NO "Bookmark" + static void bookmark_toggled (GtkButton *button, gpointer dummy) { - show_message ("Bookmarks are not yet implemented."); -} + if (g_key_file_get_boolean (config, + CONFIG_BOOKMARK_GROUP, + tower_displayed, + NULL)) + { -char *tower_website = NULL; -char *tower_map = NULL; -char *tower_directions = NULL; -char *peals_list = NULL; + /* it's bookmarked; remove the bookmark */ + + if (!g_key_file_remove_key (config, + CONFIG_BOOKMARK_GROUP, + tower_displayed, + NULL)) + { + show_message ("Could not remove bookmark."); + return; + } + + save_config (); + gtk_button_set_label (button, + BUTTON_BOOKMARKED_NO); + } + else + { + /* it's not bookmarked; add a bookmark */ + + g_key_file_set_boolean (config, + CONFIG_BOOKMARK_GROUP, + tower_displayed, + TRUE); + + save_config (); + gtk_button_set_label (button, + BUTTON_BOOKMARKED_YES); + } +} static void show_tower_website (void) @@ -209,46 +479,226 @@ show_tower_map (void) } static void +show_tower_directions (void) +{ + if (tower_directions) + { + show_browser (tower_directions); + } + else + { + show_message ("I don't know where you are!"); + } +} + +static void show_peals_list (void) { show_browser (peals_list); } -static gboolean -get_areas_cb (tower *details, - gpointer data) +static FilterResult +get_countries_cb (tower *details, + gpointer data) { GHashTable *hash = (GHashTable *)data; + gpointer value; if (details->serial==0) return TRUE; /* header row */ if (!g_hash_table_lookup_extended (hash, + details->fields[FieldCountry], + NULL, &value)) + { + g_hash_table_insert (hash, + g_strdup(details->fields[FieldCountry]), + GINT_TO_POINTER (0)); + } + else + { + g_hash_table_replace (hash, + g_strdup(details->fields[FieldCountry]), + GINT_TO_POINTER (GPOINTER_TO_INT (value)+1)); + } + + return FILTER_IGNORE; +} + +typedef struct { + GHashTable *hash; + gchar *country_name; +} country_cb_data; + +typedef struct { + char *country; + char *county; +} country_and_county; + +static FilterResult +get_counties_cb (tower *details, + gpointer data) +{ + country_cb_data *d = (country_cb_data *)data; + + if (details->serial==0) + return FILTER_IGNORE; /* header row */ + + if (strcmp(details->fields[FieldCountry], d->country_name)!=0) + return FILTER_IGNORE; /* wrong country */ + + if (!g_hash_table_lookup_extended (d->hash, details->fields[FieldCounty], NULL, NULL)) { - char *display_format; + g_hash_table_insert (d->hash, + g_strdup(details->fields[FieldCounty]), + g_strdup (details->fields[FieldCounty])); + } + + return FILTER_IGNORE; +} + +static FilterResult +get_nearby_towers_cb (tower *details, + gpointer distance) +{ + if (details->serial==0) + return FILTER_IGNORE; /* header row */ + + if (distance_to_tower (details) <= GPOINTER_TO_INT (distance)) + { + return FILTER_ACCEPT; + } + else + { + return FILTER_IGNORE; + } +} - if (strcmp (details->fields[FieldCounty], "")==0) +static FilterResult +get_towers_by_county_cb (tower *details, + gpointer data) +{ + country_and_county *cac = (country_and_county *) data; + + if ((!cac->county || strcmp (cac->county, details->fields[FieldCounty])==0) && + (!cac->country || strcmp (cac->country, details->fields[FieldCountry])==0)) + { + return FILTER_ACCEPT; + } + else + { + return FILTER_IGNORE; + } +} + +static FilterResult +get_towers_by_search_cb (tower *details, + gpointer data) +{ + char *s = (char *) data; + + if (strcasestr(details->fields[FieldCountry], s) || + strcasestr(details->fields[FieldCounty], s) || + strcasestr(details->fields[FieldDedication], s) || + strcasestr(details->fields[FieldPlace], s)) + { + return FILTER_ACCEPT; + } + else + { + return FILTER_IGNORE; + } +} + +/** + * A filter which accepts towers based on whether they + * appear in a particular group in the config file. + * + * \param details the candidate tower + * \param data pointer to a char* which names the group + */ +static FilterResult +get_group_of_towers_cb (tower *details, + gpointer data) +{ + if (g_key_file_has_key (config, + (char*) data, + details->fields[FieldPrimaryKey], + NULL)) + { + return FILTER_ACCEPT; + } + else + { + return FILTER_IGNORE; + } +} + +/** + * Removes the oldest entry from the [Recent] group in the config + * file until there are only five entries left. Does not save + * the file; you have to do that. + */ +static void +remove_old_recent_entries (void) +{ + gint count; + + do + { + gchar **towers; + gint oldest_date = 0; + gchar *oldest_tower = NULL; + gint i; + + /* It is a bit inefficient to do this every + * time we go around the loop. However, it + * makes the code far simpler, and we almost + * never go around more than once. + */ + towers = g_key_file_get_keys (config, + CONFIG_RECENT_GROUP, + &count, + NULL); + + if (count <= MAX_RECENT) + /* everything's fine */ + return; + + for (i=0; ifields[FieldCountry]); + gint date = g_key_file_get_integer (config, + CONFIG_RECENT_GROUP, + towers[i], + NULL); + + if (date==0) + continue; + + if (oldest_date==0 || + date < oldest_date) + { + oldest_tower = towers[i]; + oldest_date = date; + } } - else + + if (oldest_tower) { - display_format = g_strdup_printf ("%s " EM_DASH " %s", - details->fields[FieldCountry], - details->fields[FieldCounty]); + g_key_file_remove_key (config, + CONFIG_RECENT_GROUP, + oldest_tower, + NULL); + count --; } - - g_hash_table_insert (hash, - g_strdup(details->fields[FieldCounty]), - display_format); + g_strfreev (towers); } - - return TRUE; + while (count > MAX_RECENT); } -static gboolean +static FilterResult single_tower_cb (tower *details, gpointer data) { @@ -257,40 +707,19 @@ single_tower_cb (tower *details, gchar *str; gint tenor_weight; gchar *primary_key = (gchar*) data; + gchar *distance; if (strcmp(details->fields[FieldPrimaryKey], primary_key)!=0) { /* not this one; keep going */ - return TRUE; + return FILTER_IGNORE; } tower_window = hildon_stackable_window_new (); - if (g_str_has_prefix (details->fields[FieldDedication], - "S ")) - { - /* FIXME: This needs to be cleverer, because we can have - * e.g. "S Peter and S Paul". - * May have to use regexps. - * Reallocation in general even when unchanged is okay, - * because it's the common case (most towers are S Something) - */ - - /* FIXME: Since we're passing this in as markup, - * we need to escape the strings. - */ - - str = g_strdup_printf("St %s, %s", - details->fields[FieldDedication]+2, - details->fields[FieldPlace]); - - } - else - { - str = g_strdup_printf("%s, %s", - details->fields[FieldDedication], - details->fields[FieldPlace]); - } + str = g_strdup_printf("%s, %s", + details->fields[FieldDedication], + details->fields[FieldPlace]); hildon_window_set_markup (HILDON_WINDOW (tower_window), str); @@ -301,6 +730,9 @@ single_tower_cb (tower *details, buttons = gtk_vbox_new (TRUE, 0); menu = HILDON_APP_MENU (hildon_app_menu_new ()); + distance = distance_to_tower_str(details); + + add_table_field ("Distance", distance); add_table_field ("Postcode", details->fields[FieldPostcode]); add_table_field ("County", details->fields[FieldCounty]); add_table_field ("Country", details->fields[FieldCountry]); @@ -308,6 +740,8 @@ single_tower_cb (tower *details, add_table_field ("Practice night", details->fields[FieldPracticeNight]); add_table_field ("Bells", details->fields[FieldBells]); + g_free (distance); + tenor_weight = atoi (details->fields[FieldWt]); str = g_strdup_printf("%dcwt %dqr %dlb in %s", tenor_weight/112, @@ -318,15 +752,23 @@ single_tower_cb (tower *details, add_table_field ("Tenor", str); g_free (str); - add_button ("Tower website", show_tower_website); + if (strcmp(details->fields[FieldWebPage], "")!=0) + { + add_button ("Tower website", show_tower_website); + } add_button ("Peals", show_peals_list); add_button ("Map", show_tower_map); - add_button ("Directions", NULL); + add_button ("Directions", show_tower_directions); /* don't use a toggle button: it looks stupid */ button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT, HILDON_BUTTON_ARRANGEMENT_VERTICAL, - "Bookmark", NULL); + g_key_file_get_boolean (config, + CONFIG_BOOKMARK_GROUP, + details->fields[FieldPrimaryKey], + NULL)? + BUTTON_BOOKMARKED_YES: BUTTON_BOOKMARKED_NO, + NULL); g_signal_connect (button, "clicked", G_CALLBACK (bookmark_toggled), NULL); gtk_box_pack_start (GTK_BOX (buttons), button, FALSE, FALSE, 0); @@ -347,20 +789,138 @@ single_tower_cb (tower *details, tower_map = g_strdup_printf ("http://maps.google.com/maps?q=%s,%s", details->fields[FieldLat], details->fields[FieldLong]); + g_free (tower_directions); + if (device->fix->fields & LOCATION_GPS_DEVICE_LATLONG_SET) + { + tower_directions = g_strdup_printf ("http://maps.google.com/maps?q=%f,%f+to+%s,%s", + device->fix->latitude, + device->fix->longitude, + details->fields[FieldLat], + details->fields[FieldLong]); + } + g_free (tower_displayed); + tower_displayed = g_strdup (details->fields[FieldPrimaryKey]); + + g_key_file_set_integer (config, + CONFIG_RECENT_GROUP, + tower_displayed, + time (NULL)); + remove_old_recent_entries (); + save_config (); + gtk_widget_show_all (GTK_WIDGET (tower_window)); - return FALSE; + return FILTER_STOP; +} + +/** + * A tower that was accepted by a filter. + */ +typedef struct { + char *sortkey; + char *primarykey; + char *displayname; +} FoundTower; + +static FoundTower * +found_tower_new (tower *basis) +{ + FoundTower* result = g_new (FoundTower, 1); + + switch (settings_value[SETTINGS_TOWERSORT]) + { + case TOWERSORT_DISTANCE: + result->sortkey = g_strdup_printf ("%5d %s", + distance_to_tower (basis), + basis->fields[FieldPlace]); + break; + case TOWERSORT_PRACTICE: + result->sortkey = g_strdup_printf ("%d %s", + days_until_practice_night (basis), + basis->fields[FieldPlace]); + break; + case TOWERSORT_BELLS: + result->sortkey = g_strdup_printf ("%10s", basis->fields[FieldBells]); + break; + case TOWERSORT_WEIGHT: + result->sortkey = g_strdup_printf ("%10s", basis->fields[FieldWt]); + break; + case TOWERSORT_TOWN: + default: + result->sortkey = g_strdup (basis->fields[FieldPlace]); + } + + result->primarykey = g_strdup (basis->fields[FieldPrimaryKey]); + + if (device->fix->fields & LOCATION_GPS_DEVICE_LATLONG_SET) + { + gchar *distance = distance_to_tower_str (basis); + result->displayname = g_strdup_printf ("%s, %s (%s, %s) (%s)", + basis->fields[FieldDedication], + basis->fields[FieldPlace], + basis->fields[FieldBells], + basis->fields[FieldPracticeNight], + distance); + g_free (distance); + } + else + { + result->displayname = g_strdup_printf ("%s, %s (%s, %s)", + basis->fields[FieldDedication], + basis->fields[FieldPlace], + basis->fields[FieldBells], + basis->fields[FieldPracticeNight]); + } + + return result; } static void +found_tower_free (FoundTower *tower) +{ + g_free (tower->sortkey); + g_free (tower->primarykey); + g_free (tower->displayname); + g_free (tower); +} + +/** + * Comparison function for FoundTower objects. + * + * \param a a FoundTower + * \param b another FoundTower + */ +gint found_tower_compare (gconstpointer a, + gconstpointer b) +{ + FoundTower *fta = (FoundTower *)a; + FoundTower *ftb = (FoundTower *)b; + + return strcmp (fta->sortkey, ftb->sortkey); +} + +/** + * Calls a given function once for each tower in the world. + * (The first call, however, is a header row.) + * + * \param callback The function to call. + * \param data Arbitrary data to pass to the callback. + * \param dialogue_title If non-NULL, a list will be displayed + * with the results. This is the title + * used for that dialogue. (The dialogue + * will automatically free filter_results.) + */ +static void parse_dove (ParseDoveCallback callback, - gpointer data) + gpointer data, + gchar *dialogue_title) { FILE *dove = fopen("/usr/share/belltower/dove.txt", "r"); char tower_rec[4096]; tower result; char *i; gboolean seen_newline; + GSList *filter_results = NULL; if (!dove) { @@ -400,48 +960,139 @@ parse_dove (ParseDoveCallback callback, result.fields[FieldCountry] = "England"; } - if (!callback (&result, data)) + switch (callback (&result, data)) { + case FILTER_IGNORE: + /* nothing */ + break; + + case FILTER_STOP: fclose (dove); return; + + case FILTER_ACCEPT: + filter_results = g_slist_insert_sorted (filter_results, + found_tower_new (&result), + found_tower_compare); } result.serial++; } fclose (dove); + + if (dialogue_title) + { + show_towers_from_list (filter_results, + dialogue_title); + } + else + { + free_tower_list (filter_results); + } } static void -nearby_towers (void) +show_tower (char *primary_key) { - char buffer[4096]; - LocationGPSDevice *device; - device = g_object_new (LOCATION_TYPE_GPS_DEVICE, NULL); + parse_dove (single_tower_cb, primary_key, NULL); +} - sprintf(buffer, "%f %f %x", - device->fix->latitude, - device->fix->longitude, - device->fix->fields); - show_message (buffer); +static void +free_tower_list (GSList *list) +{ + GSList *cursor = list; - if (device->fix->fields & LOCATION_GPS_DEVICE_LATLONG_SET) + while (cursor) { - show_message ("I know where you are!"); + found_tower_free ((FoundTower*) cursor->data); + cursor = cursor->next; } - else + + g_slist_free (list); +} + +/** + * Displays a list of towers for the user to choose from. + * When one is chosen, we go to the display page for that tower. + * If there are none, this will tell the user there were none. + * If there is only one, we go straight to its display page. + * + * \param list a GSList of FoundTower objects. + * \param list_name the title for the dialogue. + */ +static void +show_towers_from_list (GSList *list, + gchar *list_name) +{ + GtkWidget *dialog; + GtkWidget *selector; + gint result = -1; + GSList *cursor; + + if (!list) { - show_message ("I don't know where you are!"); + hildon_banner_show_information(window, + NULL, + "No towers found."); + return; } - g_object_unref (device); + if (!list->next) + { + /* only one; don't bother showing the list */ + FoundTower* found = (FoundTower*) list->data; + + hildon_banner_show_information(window, + NULL, + "One tower found."); + show_tower (found->primarykey); + + free_tower_list (list); + return; + } + + dialog = hildon_picker_dialog_new (GTK_WINDOW (window)); + selector = hildon_touch_selector_new_text (); + gtk_window_set_title (GTK_WINDOW (dialog), list_name); + + for (cursor=list; cursor; cursor=cursor->next) + { + FoundTower* found = (FoundTower*) cursor->data; + hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector), + found->displayname); + } + + hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (dialog), + HILDON_TOUCH_SELECTOR (selector)); + + gtk_widget_show_all (GTK_WIDGET (dialog)); + + if (gtk_dialog_run (GTK_DIALOG (dialog))==GTK_RESPONSE_OK) + { + GList *rows = hildon_touch_selector_get_selected_rows (HILDON_TOUCH_SELECTOR (selector), + 0); + GtkTreePath *path = (GtkTreePath*) rows->data; + gint *indices = gtk_tree_path_get_indices (path); + + result = *indices; + } + + gtk_widget_destroy (GTK_WIDGET (dialog)); + + if (result!=-1) + { + FoundTower *found = (FoundTower *) g_slist_nth_data (list, result); + show_tower (found->primarykey); + } + + free_tower_list (list); } -static void -show_tower (char *primary_key) +static gint strcmp_f (gconstpointer a, + gconstpointer b) { - parse_dove (single_tower_cb, - primary_key); + return strcmp ((char*)a, (char*)b); } static void @@ -449,88 +1100,413 @@ put_areas_into_list (gpointer key, gpointer value, gpointer data) { - GtkTreeIter iter; - GtkListStore *list_store = (GtkListStore*) data; - gtk_list_store_append (list_store, &iter); - gtk_list_store_set (list_store, &iter, - 0, value, - -1); + GSList **list = (GSList **)data; + *list = g_slist_insert_sorted (*list, + value, + strcmp_f); } static void -towers_by_area (void) +nearby_towers (void) { - GtkWidget *dialog = gtk_dialog_new (); - GtkWidget *vbox = GTK_DIALOG(dialog)->vbox; - GtkCellRenderer *renderer = gtk_cell_renderer_text_new (); - GtkWidget *treeview = hildon_gtk_tree_view_new (HILDON_UI_MODE_NORMAL); - GtkWidget *pan = hildon_pannable_area_new (); - GtkListStore *list_store = gtk_list_store_new(1, G_TYPE_STRING); + if (!(device->fix->fields & LOCATION_GPS_DEVICE_LATLONG_SET)) + { + show_message ("I don't know where you are!"); + return; + } + parse_dove (get_nearby_towers_cb, + settings_value[SETTINGS_DISTANCES]==DISTANCES_KILOMETRES? + GINT_TO_POINTER (80) : + GINT_TO_POINTER (50), + settings_value[SETTINGS_DISTANCES]==DISTANCES_KILOMETRES? + "Towers within eighty kilometres of you": + "Towers within fifty miles of you"); +} + +static void +towers_by_subarea (gchar *area) +{ + GtkWidget *dialog = hildon_picker_dialog_new (GTK_WINDOW (window)); + GtkWidget *selector = hildon_touch_selector_new_text (); GHashTable *hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); - parse_dove (get_areas_cb, - hash); + GSList *list=NULL, *cursor; + gchar *title = g_strdup_printf ("Areas of %s", area); + country_cb_data d = { hash, area }; + country_and_county cac = { area, NULL }; + + gtk_window_set_title (GTK_WINDOW (dialog), title); + g_free (title); + + parse_dove (get_counties_cb, &d, NULL); g_hash_table_foreach (hash, put_areas_into_list, - list_store); + &list); + + for (cursor=list; cursor; cursor=cursor->next) + { + hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector), + cursor->data); + } - /* g_signal_connect (G_OBJECT (dialog), "delete_event", G_CALLBACK (g_hash_table_destroy), hash)*/ + hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (dialog), + HILDON_TOUCH_SELECTOR (selector)); + + gtk_widget_show_all (GTK_WIDGET (dialog)); + + if (gtk_dialog_run (GTK_DIALOG (dialog))==GTK_RESPONSE_OK) + { + gchar *title; + cac.county = g_strdup (hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (selector))); + title = g_strdup_printf ("Towers in %s", + cac.county); + + parse_dove (get_towers_by_county_cb, + &cac, + title); + g_free (cac.county); + g_free (title); + } + g_hash_table_unref (hash); + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +/** + * Maps a hash table from country names to counts of belltowers to a + * newly-created hash table mapping country names to display + * names, containing only those countries which have many + * (or few) belltowers. + * + * \param source the source table + * \param want_many true if you want countries with many belltowers; + * false if you want countries with few. + */ +static GHashTable* +get_countries_with_many (GHashTable *source, + gboolean want_many) +{ + GHashTable *result = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); + GList *countries = g_hash_table_get_keys (source); + GList *cursor = countries; + + while (cursor) + { + gboolean has_many = + GPOINTER_TO_INT (g_hash_table_lookup (source, + cursor->data)) >= MANY_BELLTOWERS; - gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_store), - 0, - GTK_SORT_ASCENDING); + if (has_many == want_many) + { + g_hash_table_insert (result, + g_strdup (cursor->data), + g_strdup (cursor->data)); + } - g_object_set (GTK_OBJECT(renderer), "yalign", 0.5, NULL); - g_object_set (GTK_OBJECT(renderer), "xpad", 24, NULL); + cursor = cursor->next; + } - gtk_window_set_title (GTK_WINDOW (dialog), "Towers by area"); + g_list_free (countries); + return result; +} - gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), - 0, "", - renderer, - "text", 0, - NULL); +#define COUNTRIES_WITH_MANY "Countries with many belltowers" +#define COUNTRIES_WITH_FEW "Countries with few belltowers" - gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), - GTK_TREE_MODEL (list_store)); +/** + * Displays a list of areas of the world with many (or few) + * belltowers. If you ask for the areas with many, it include + * a link to the areas with few. + * + * \param countries_with_many True to list countries with many; + * false to list countries with few. + */ +static void +towers_by_area_with_many (gboolean countries_with_many) +{ + GtkWidget *dialog = hildon_picker_dialog_new (GTK_WINDOW (window)); + GtkWidget *selector = hildon_touch_selector_new_text (); + GHashTable *countries_to_counts = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); + GHashTable *country_names; + GSList *list = NULL, *cursor; + gchar *result = NULL; + + gtk_window_set_title (GTK_WINDOW (dialog), + countries_with_many? + COUNTRIES_WITH_MANY : COUNTRIES_WITH_FEW); + + parse_dove (get_countries_cb, countries_to_counts, NULL); + + country_names = get_countries_with_many (countries_to_counts, + countries_with_many); + + g_hash_table_foreach (country_names, + put_areas_into_list, + &list); - g_object_unref (list_store); + for (cursor=list; cursor; cursor=cursor->next) + { + hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector), + cursor->data); + } - hildon_pannable_area_add_with_viewport (HILDON_PANNABLE_AREA (pan), - treeview); + if (countries_with_many) + { + hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector), + COUNTRIES_WITH_FEW); + } - hildon_pannable_area_set_size_request_policy (HILDON_PANNABLE_AREA (pan), - HILDON_SIZE_REQUEST_CHILDREN); - gtk_container_add(GTK_CONTAINER (vbox), pan); + hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (dialog), + HILDON_TOUCH_SELECTOR (selector)); gtk_widget_show_all (GTK_WIDGET (dialog)); - gtk_dialog_run (GTK_DIALOG (dialog)); + if (gtk_dialog_run (GTK_DIALOG (dialog))==GTK_RESPONSE_OK) + { + result = g_strdup (hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (selector))); + } + + g_hash_table_unref (countries_to_counts); + g_hash_table_unref (country_names); gtk_widget_destroy (GTK_WIDGET (dialog)); - /* g_hash_table_unref (hash); */ + if (result) + { + if (countries_with_many) + { + /* these countries have many towers, so + * show the sub-areas + */ + if (strcmp (result, COUNTRIES_WITH_FEW)==0) + towers_by_area_with_many (FALSE); + else + towers_by_subarea (result); + } + else + { + country_and_county cac = { result, NULL }; + gchar *title = g_strdup_printf ("Belltowers in %s", + result); + + parse_dove (get_towers_by_county_cb, + &cac, + title); + + g_free (title); + } + + g_free (result); + } +} + +/** + * Shows all the towers in areas with many towers. + */ +static void +towers_by_area (void) +{ + towers_by_area_with_many (TRUE); } static void show_bookmarks (void) { - /* nothing */ + parse_dove (get_group_of_towers_cb, + CONFIG_BOOKMARK_GROUP, + "Bookmarks"); } static void tower_search (void) { - /* nothing */ + GtkWidget *terms = gtk_dialog_new_with_buttons ("What are you looking for?", + GTK_WINDOW (window), + GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT, + "Search", + GTK_RESPONSE_OK, + NULL); + GtkWidget *entry = gtk_entry_new (); + + gtk_box_pack_end (GTK_BOX (GTK_DIALOG (terms)->vbox), + entry, TRUE, TRUE, 0); + + gtk_widget_show_all (GTK_WIDGET (terms)); + + if (gtk_dialog_run (GTK_DIALOG (terms))==GTK_RESPONSE_OK) + { + parse_dove (get_towers_by_search_cb, + (char*) gtk_entry_get_text (GTK_ENTRY (entry)), + "Search results"); + } + + gtk_widget_destroy (GTK_WIDGET (terms)); } static void recent_towers (void) { - show_tower ("NORTON HE"); + parse_dove (get_group_of_towers_cb, + CONFIG_RECENT_GROUP, + "Towers you have recently viewed"); +} + +/** + * Displays a web page. + * (Perhaps this should be merged with show_browser().) + * + * \param url The URL. + */ +static void +show_web_page (GtkButton *dummy, + gpointer url) +{ + show_browser (url); +} + +/** + * Shows the credits. + * + * \param source If non-null, we were called from a button press, + * so always show the credits. If null, we were called + * automatically on startup, so show the credits if + * they haven't already been seen. + */ +static void +show_credits (GtkButton *source, + gpointer dummy) +{ + gboolean from_button = (source!=NULL); + GtkWidget *dialog, *label, *button; + + if (!from_button && + g_key_file_get_boolean (config, + CONFIG_GENERAL_GROUP, + CONFIG_SEEN_CREDITS_KEY, + NULL)) + { + return; + } + + + dialog = gtk_dialog_new_with_buttons ("Credits", + GTK_WINDOW (window), + GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT, + NULL + ); + + button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT, + HILDON_BUTTON_ARRANGEMENT_VERTICAL, + "Welcome to Belltower. The program is \xc2\xa9 2009 Thomas Thurman.", + "View the program's home page."); + g_signal_connect (button, "clicked", G_CALLBACK (show_web_page), + "http://belltower.garage.maemo.org"); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), + button, + TRUE, TRUE, 0); + + button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT, + HILDON_BUTTON_ARRANGEMENT_VERTICAL, + "This program is provided under the GNU GPL, with no warranty.", + "View the GNU General Public Licence."); + g_signal_connect (button, "clicked", G_CALLBACK (show_web_page), + "http://www.gnu.org/copyleft/gpl.html"); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), + button, + TRUE, TRUE, 0); + + button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT, + HILDON_BUTTON_ARRANGEMENT_VERTICAL, + "The data comes from Dove's Guide for Church Bell Ringers.", + "View Dove's Guide."); + g_signal_connect (button, "clicked", G_CALLBACK (show_web_page), + "http://dove.cccbr.org.uk"); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), + button, + TRUE, TRUE, 0); + + button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT, + HILDON_BUTTON_ARRANGEMENT_VERTICAL, + "The belfry image is \xc2\xa9 Amanda Slater, cc-by-sa.", + "View the original photograph."); + g_signal_connect (button, "clicked", G_CALLBACK (show_web_page), + "http://www.flickr.com/photos/pikerslanefarm/3398769335/"); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), + button, + TRUE, TRUE, 0); + + gtk_widget_show_all (GTK_WIDGET (dialog)); + + g_key_file_set_boolean (config, + CONFIG_GENERAL_GROUP, + CONFIG_SEEN_CREDITS_KEY, + TRUE); + save_config (); +} + +static void +settings_dialogue (void) +{ + GtkWidget *dialog, *button; + GtkWidget *selector[G_N_ELEMENTS (settings)]; + gint i; + + dialog = gtk_dialog_new_with_buttons ("Settings", + GTK_WINDOW (window), + GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT, + NULL + ); + + for (i=0; ivbox), + button, + TRUE, TRUE, 0); + } + + gtk_widget_show_all (GTK_WIDGET (GTK_DIALOG(dialog)->vbox)); + gtk_dialog_run (GTK_DIALOG (dialog)); + + for (i=0; idata; + gint *indices = gtk_tree_path_get_indices (path); + + g_key_file_set_string (config, + CONFIG_GENERAL_GROUP, + settings[i][0], + hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (selector[i]))); + + settings_value[i] = *indices; + } + save_config (); + + gtk_widget_destroy (GTK_WIDGET (dialog)); } int @@ -542,6 +1518,8 @@ main(int argc, char **argv) gtk_init (&argc, &argv); g_set_application_name ("Belltower"); + device = g_object_new (LOCATION_TYPE_GPS_DEVICE, NULL); + window = hildon_stackable_window_new (); gtk_window_set_title (GTK_WINDOW (window), "Belltower"); g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL); @@ -556,10 +1534,15 @@ main(int argc, char **argv) add_button ("Bookmarks", show_bookmarks); add_button ("By area", towers_by_area); add_button ("Search", tower_search); + /* This won't be a button on the main screen + * for always, but we'll put it there for now + * to work around a hildon bug. + */ + add_button ("Settings", settings_dialogue); /* extra buttons for the app menu */ button = gtk_button_new_with_label ("Credits"); - hildon_app_menu_append (menu, GTK_BUTTON (button)); + g_signal_connect (button, "clicked", G_CALLBACK (show_credits), NULL); hildon_app_menu_append (menu, GTK_BUTTON (button)); gtk_widget_show_all (GTK_WIDGET (menu)); @@ -574,6 +1557,9 @@ main(int argc, char **argv) gtk_container_add (GTK_CONTAINER (window), hbox); gtk_widget_show_all (GTK_WIDGET (window)); + load_config (); + show_credits (NULL, NULL); + gtk_main (); return EXIT_SUCCESS;