Searching by bookmarks
[belltower] / belltower.c
1 /*
2  * belltower
3  * an app to find belltowers under Maemo 5
4  *
5  * Copyright (c) 2009 Thomas Thurman <tthurman@gnome.org>
6  * Released under the GPL
7  */
8
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <glib.h>
13 #include <hildon/hildon.h>
14 #include <gtk/gtk.h>
15 #include <location/location-gps-device.h>
16 #include <location/location-distance-utils.h>
17 #include <dbus/dbus-glib.h>
18
19 #define MAX_FIELDS 50
20 #define CONFIG_GENERAL_GROUP "General"
21 #define CONFIG_BOOKMARK_GROUP "Bookmarks"
22 #define CONFIG_SEEN_CREDITS_KEY "seen_credits"
23 #define CONFIG_DIRECTORY "/home/user/.config/belltower"
24 #define CONFIG_FILENAME CONFIG_DIRECTORY "/belltower.ini"
25
26 GtkWidget *window;
27 LocationGPSDevice *device;
28 GKeyFile *static_content;
29 GKeyFile *config;
30
31 typedef enum {
32   /** stop scanning the database */
33   FILTER_STOP,
34   /** ignore this one */
35   FILTER_IGNORE,
36   /** add this one to the list */
37   FILTER_ACCEPT
38 } FilterResult;
39
40 /*
41   FIXME:
42   We should really do this by looking at the header row of the table.
43   They might decide to put in new columns some day.
44 */
45 typedef enum {
46   FieldPrimaryKey,
47   FieldNationalGrid,
48   FieldAccRef,
49   FieldSNLat,
50   FieldSNLong,
51   FieldPostcode,
52   FieldTowerBase,
53   FieldCounty,
54   FieldCountry,
55   FieldDiocese,
56   FieldPlace,
57   FieldPlace2,
58   FieldPlaceCL,
59   FieldDedication,
60   FieldBells,
61   FieldWt,
62   FieldApp,
63   FieldNote,
64   FieldHz,
65   FieldDetails,
66   FieldGF,
67   FieldToilet,
68   FieldUR,
69   FieldPDNo,
70   FieldPracticeNight,
71   FieldPSt,
72   FieldPrXF,
73   FieldOvhaulYr,
74   FieldContractor,
75   FieldExtraInfo,
76   FieldWebPage,
77   FieldUpdated,
78   FieldAffiliations,
79   FieldAltName,
80   FieldLat,
81   FieldLong,
82   FieldSimulator
83 } field;
84
85 typedef struct {
86   int serial;
87
88   /* the raw data */
89
90   char* fields[MAX_FIELDS];
91   int n_fields;
92 } tower;
93
94 static void
95 show_message (char *message)
96 {
97   HildonNote* note = HILDON_NOTE
98     (hildon_note_new_information (GTK_WINDOW (window),
99                                   message?message:
100                                   "Some message was supposed to be here."));
101   gtk_dialog_run (GTK_DIALOG (note));
102   gtk_widget_destroy (GTK_WIDGET (note));
103 }
104
105 /**
106  * Loads the content of the static and dynamic data files.
107  * Possibly puts up a warning if we can't load the static file.
108  */
109 static void
110 load_config (void)
111 {
112   static_content = g_key_file_new ();
113
114   if (!g_key_file_load_from_file (static_content,
115                                   "/usr/share/belltower/static.ini",
116                                   G_KEY_FILE_NONE,
117                                   NULL))
118     {
119       show_message ("Could not load static content.  Attempting to continue.");
120     }
121
122   config = g_key_file_new ();
123   /* it doesn't matter if this fails */
124   g_key_file_load_from_file (config,
125                              CONFIG_FILENAME,
126                              G_KEY_FILE_KEEP_COMMENTS,
127                              NULL);
128 }
129
130 /**
131  * Saves the dynamic data file to disk.
132  * Puts up a message if there was any error.
133  */
134 static void
135 save_config (void)
136 {
137   gchar *data;
138
139   g_mkdir_with_parents (CONFIG_DIRECTORY, 0700);
140
141   data = g_key_file_to_data (config, NULL, NULL);
142
143   if (!g_file_set_contents (CONFIG_FILENAME,
144                             data,
145                             -1,
146                             NULL))
147     {
148       show_message ("Could not write config file.");
149     }
150
151   g_free (data);
152 }
153
154 static gint
155 distance_to_tower (tower *details)
156 {
157   char *endptr;
158   double tower_lat;
159   double tower_long;
160   double km_distance;
161   const double km_to_miles = 1.609344;
162
163   tower_lat = strtod(details->fields[FieldLat], &endptr);
164   if (*endptr) return -1;
165   tower_long = strtod(details->fields[FieldLong], &endptr);
166   if (*endptr) return -1;
167
168   km_distance = location_distance_between (device->fix->latitude,
169                                            device->fix->longitude,
170                                            tower_lat,
171                                            tower_long);
172
173   return (int) (km_distance / km_to_miles);
174 }
175
176 static gchar*
177 distance_to_tower_str (tower *details)
178 {
179   int miles = distance_to_tower (details);
180
181   if (miles==-1)
182     {
183       return g_strdup ("unknown");
184     }
185   else
186     {
187       return g_strdup_printf("%dmi", (int) miles);
188     }
189 }
190
191 static void
192 call_dbus (DBusBusType type,
193            char *name,
194            char *path,
195            char *interface,
196            char *method,
197            char *parameter)
198 {
199   DBusGConnection *connection;
200   GError *error = NULL;
201
202   DBusGProxy *proxy;
203
204   connection = dbus_g_bus_get (type,
205                                &error);
206   if (connection == NULL)
207     {
208       show_message (error->message);
209       g_error_free (error);
210       return;
211     }
212
213   proxy = dbus_g_proxy_new_for_name (connection, name, path, interface);
214
215   error = NULL;
216   if (!dbus_g_proxy_call (proxy, method, &error,
217                           G_TYPE_STRING, parameter,
218                           G_TYPE_INVALID,
219                           G_TYPE_INVALID))
220     {
221       show_message (error->message);
222       g_error_free (error);
223     }
224 }
225
226 static void
227 show_browser (gchar *url)
228 {
229   call_dbus (DBUS_BUS_SESSION,
230              "com.nokia.osso_browser",
231              "/com/nokia/osso_browser/request",
232              "com.nokia.osso_browser",
233              "load_url",
234              url);
235 }
236
237 typedef FilterResult (*ParseDoveCallback)(tower *details, gpointer data);
238 typedef void (*ButtonCallback)(void);
239
240 GtkWidget *tower_window, *buttons, *tower_table;
241 HildonAppMenu *menu;
242
243 static void
244 add_table_field (char *name,
245                  char *value)
246 {
247   int row;
248   GtkLabel *label;
249   gchar *str;
250
251   g_object_get(tower_table, "n-rows", &row);
252
253   row++;
254
255   gtk_table_resize (GTK_TABLE (tower_table), row, 2);
256
257   label = GTK_LABEL (gtk_label_new (NULL));
258   str = g_strdup_printf("<b>%s</b>", name);
259   gtk_label_set_markup (label, str);
260   g_free (str);
261   gtk_label_set_justify (label, GTK_JUSTIFY_RIGHT);
262   gtk_table_attach_defaults (GTK_TABLE (tower_table),
263                              GTK_WIDGET (label),
264                              0, 1, row, row+1);
265
266   label = GTK_LABEL (gtk_label_new (value));
267   gtk_label_set_justify (label, GTK_JUSTIFY_LEFT);
268   gtk_table_attach_defaults (GTK_TABLE (tower_table),
269                              GTK_WIDGET (label),
270                              1, 2, row, row+1);
271 }
272
273 static void
274 add_button (char *label,
275             ButtonCallback callback)
276 {
277   GtkWidget *button;
278
279   button = gtk_button_new_with_label (label);
280   g_signal_connect (button, "clicked", G_CALLBACK (callback), NULL);
281   hildon_app_menu_append (menu, GTK_BUTTON (button));
282   button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
283                                         HILDON_BUTTON_ARRANGEMENT_VERTICAL,
284                                         label, NULL);
285   g_signal_connect (button, "clicked", G_CALLBACK (callback), NULL);
286   gtk_box_pack_end (GTK_BOX (buttons), button, FALSE, FALSE, 0);
287 }
288
289
290 char *tower_displayed = NULL;
291 char *tower_website = NULL;
292 char *tower_map = NULL;
293 char *tower_directions = NULL;
294 char *peals_list = NULL;
295
296 #define BUTTON_BOOKMARKED_YES "Remove from bookmarks"
297 #define BUTTON_BOOKMARKED_NO "Bookmark"
298
299 static void
300 bookmark_toggled (GtkButton *button,
301                   gpointer dummy)
302 {
303   if (g_key_file_get_boolean (config,
304                               CONFIG_BOOKMARK_GROUP,
305                               tower_displayed,
306                               NULL))
307     {
308
309       /* it's bookmarked; remove the bookmark */
310
311       if (!g_key_file_remove_key (config,
312                                   CONFIG_BOOKMARK_GROUP,
313                                   tower_displayed,
314                                   NULL))
315         {
316           show_message ("Could not remove bookmark.");
317           return;
318         }
319
320       save_config ();
321       gtk_button_set_label (button,
322                             BUTTON_BOOKMARKED_NO);
323     }
324   else
325     {
326       /* it's not bookmarked; add a bookmark */
327
328       g_key_file_set_boolean (config,
329                               CONFIG_BOOKMARK_GROUP,
330                               tower_displayed,
331                               TRUE);
332
333       save_config ();
334       gtk_button_set_label (button,
335                             BUTTON_BOOKMARKED_YES);
336     }
337 }
338
339 static void
340 show_tower_website (void)
341 {
342   show_browser (tower_website);
343 }
344
345 static void
346 show_tower_map (void)
347 {
348   show_browser (tower_map);
349 }
350
351 static void
352 show_peals_list (void)
353 {
354   show_browser (peals_list);
355 }
356
357 static FilterResult
358 get_countries_cb (tower *details,
359                   gpointer data)
360 {
361   GHashTable *hash = (GHashTable *)data;
362
363   if (details->serial==0)
364     return TRUE; /* header row */
365
366   if (!g_hash_table_lookup_extended (hash,
367                                     details->fields[FieldCountry],
368                                      NULL, NULL))
369     {
370       g_hash_table_insert (hash,
371                            g_strdup(details->fields[FieldCountry]),
372                            g_strdup (details->fields[FieldCountry]));
373     }
374
375   return FILTER_IGNORE;
376 }
377
378 typedef struct {
379   GHashTable *hash;
380   gchar *country_name;
381 } country_cb_data;
382
383 typedef struct {
384   char *country;
385   char *county;
386 } country_and_county;
387
388 static FilterResult
389 get_counties_cb (tower *details,
390                  gpointer data)
391 {
392   country_cb_data *d = (country_cb_data *)data;
393
394   if (details->serial==0)
395     return FILTER_IGNORE; /* header row */
396
397   if (strcmp(details->fields[FieldCountry], d->country_name)!=0)
398     return FILTER_IGNORE; /* wrong country */
399
400   if (!g_hash_table_lookup_extended (d->hash,
401                                     details->fields[FieldCounty],
402                                      NULL, NULL))
403     {
404       g_hash_table_insert (d->hash,
405                            g_strdup(details->fields[FieldCounty]),
406                            g_strdup (details->fields[FieldCounty]));
407     }
408
409   return FILTER_IGNORE;
410 }
411
412 static FilterResult
413 get_nearby_towers_cb (tower *details,
414                       gpointer data)
415 {
416   if (details->serial==0)
417     return FILTER_IGNORE; /* header row */
418
419   if (distance_to_tower (details) < 50)
420     {
421       return FILTER_ACCEPT;
422     }
423   else
424     {
425       return FILTER_IGNORE;
426     }
427 }
428
429 static FilterResult
430 get_towers_by_county_cb (tower *details,
431                          gpointer data)
432 {
433   country_and_county *cac = (country_and_county *) data;
434
435   if ((!cac->county || strcmp (cac->county, details->fields[FieldCounty])==0) &&
436       (!cac->country || strcmp (cac->country, details->fields[FieldCountry])==0))
437     {
438       return FILTER_ACCEPT;
439     }
440   else
441     {
442       return FILTER_IGNORE;
443     }
444 }
445
446 static FilterResult
447 get_towers_by_search_cb (tower *details,
448                          gpointer data)
449 {
450   char *s = (char *) data;
451
452   if (strcasestr(details->fields[FieldCountry], s) ||
453       strcasestr(details->fields[FieldCounty], s) ||
454       strcasestr(details->fields[FieldDedication], s) ||
455       strcasestr(details->fields[FieldPlace], s))
456     {
457       return FILTER_ACCEPT;
458     }
459   else
460     {
461       return FILTER_IGNORE;
462     }
463 }
464
465 static FilterResult
466 get_bookmarked_towers_cb (tower *details,
467                           gpointer data)
468 {
469   if (g_key_file_get_boolean (config,
470                               CONFIG_BOOKMARK_GROUP,
471                               details->fields[FieldPrimaryKey],
472                               NULL))
473     {
474       return FILTER_ACCEPT;
475     }
476   else
477     {
478       return FILTER_IGNORE;
479     }
480 }
481
482 static FilterResult
483 single_tower_cb (tower *details,
484                  gpointer data)
485 {
486
487   GtkWidget *hbox, *button;
488   gchar *str;
489   gint tenor_weight;
490   gchar *primary_key = (gchar*) data;
491   gchar *miles;
492
493   if (strcmp(details->fields[FieldPrimaryKey], primary_key)!=0)
494     {
495       /* not this one; keep going */
496       return FILTER_IGNORE;
497     }
498
499   tower_window = hildon_stackable_window_new ();
500
501   if (g_str_has_prefix (details->fields[FieldDedication],
502                         "S "))
503     {
504       /* FIXME: This needs to be cleverer, because we can have
505        * e.g. "S Peter and S Paul".
506        * May have to use regexps.
507        * Reallocation in general even when unchanged is okay,
508        * because it's the common case (most towers are S Something)
509        */
510       
511       /* FIXME: Since we're passing this in as markup,
512        * we need to escape the strings.
513        */
514
515       str = g_strdup_printf("S<sup>t</sup> %s, %s",
516                               details->fields[FieldDedication]+2,
517                               details->fields[FieldPlace]);
518
519     }
520   else
521     {
522       str = g_strdup_printf("%s, %s",
523                               details->fields[FieldDedication],
524                               details->fields[FieldPlace]);
525     }
526
527   hildon_window_set_markup (HILDON_WINDOW (tower_window),
528                             str);
529   g_free (str);
530
531   hbox = gtk_hbox_new (FALSE, 0);
532   tower_table = gtk_table_new (0, 2, FALSE);
533   buttons = gtk_vbox_new (TRUE, 0);
534   menu = HILDON_APP_MENU (hildon_app_menu_new ());
535
536   miles = distance_to_tower_str(details);
537
538   add_table_field ("Distance", miles);
539   add_table_field ("Postcode", details->fields[FieldPostcode]);
540   add_table_field ("County", details->fields[FieldCounty]);
541   add_table_field ("Country", details->fields[FieldCountry]);
542   add_table_field ("Diocese", details->fields[FieldDiocese]);
543   add_table_field ("Practice night", details->fields[FieldPracticeNight]);
544   add_table_field ("Bells", details->fields[FieldBells]);
545
546   g_free (miles);
547
548   tenor_weight = atoi (details->fields[FieldWt]);
549   str = g_strdup_printf("%dcwt %dqr %dlb in %s",
550                         tenor_weight/112,
551                         (tenor_weight % 112)/28,
552                         tenor_weight % 28,
553                         details->fields[FieldNote]
554                         );
555   add_table_field ("Tenor", str);
556   g_free (str);
557
558   if (strcmp(details->fields[FieldWebPage], "")!=0)
559     {
560       add_button ("Tower website", show_tower_website);
561     }
562   add_button ("Peals", show_peals_list);
563   add_button ("Map", show_tower_map);
564   add_button ("Directions", NULL);
565
566   /* don't use a toggle button: it looks stupid */
567   button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
568                                         HILDON_BUTTON_ARRANGEMENT_VERTICAL,
569                                         g_key_file_get_boolean (config,
570                                                                 CONFIG_BOOKMARK_GROUP,
571                                                                 details->fields[FieldPrimaryKey],
572                                                                 NULL)?
573                                         BUTTON_BOOKMARKED_YES: BUTTON_BOOKMARKED_NO,
574                                         NULL);
575   g_signal_connect (button, "clicked", G_CALLBACK (bookmark_toggled), NULL);
576   gtk_box_pack_start (GTK_BOX (buttons), button, FALSE, FALSE, 0);
577
578   gtk_widget_show_all (GTK_WIDGET (menu));
579   hildon_window_set_app_menu (HILDON_WINDOW (tower_window), menu);
580
581   gtk_box_pack_end (GTK_BOX (hbox), buttons, TRUE, TRUE, 0);
582   gtk_box_pack_end (GTK_BOX (hbox), tower_table, TRUE, TRUE, 0);
583
584   gtk_container_add (GTK_CONTAINER (tower_window), hbox);
585
586   g_free (tower_website);
587   tower_website = g_strdup_printf ("http://%s", details->fields[FieldWebPage]);
588   g_free (peals_list);
589   peals_list = g_strdup_printf ("http://www.pealbase.ismysite.co.uk/felstead/tbid.php?tid=%s",
590        details->fields[FieldTowerBase]);
591   g_free (tower_map);
592   tower_map = g_strdup_printf ("http://maps.google.com/maps?q=%s,%s",
593         details->fields[FieldLat],
594         details->fields[FieldLong]);
595   g_free (tower_displayed);
596   tower_displayed = g_strdup (details->fields[FieldPrimaryKey]);
597
598   gtk_widget_show_all (GTK_WIDGET (tower_window));
599
600   return FILTER_STOP;
601 }
602
603 /**
604  * A tower that was accepted by a filter.
605  */
606 typedef struct {
607   char *sortkey;
608   char *primarykey;
609   char *displayname;
610 } FoundTower;
611
612 static FoundTower *
613 found_tower_new (tower *basis)
614 {
615   FoundTower* result = g_new (FoundTower, 1);
616
617   result->sortkey = g_strdup (basis->fields[FieldPrimaryKey]);
618   result->primarykey = g_strdup (basis->fields[FieldPrimaryKey]);
619
620   if (device->fix->fields & LOCATION_GPS_DEVICE_LATLONG_SET)
621     {
622       gchar *distance = distance_to_tower_str (basis);
623       result->displayname = g_strdup_printf ("%s, %s (%s, %s) (%s)",
624                                              basis->fields[FieldDedication],
625                                              basis->fields[FieldPlace],
626                                              basis->fields[FieldBells],
627                                              basis->fields[FieldPracticeNight],
628                                              distance);
629       g_free (distance);
630     }
631   else
632     {
633       result->displayname = g_strdup_printf ("%s, %s (%s, %s)",
634                                              basis->fields[FieldDedication],
635                                              basis->fields[FieldPlace],
636                                              basis->fields[FieldBells],
637                                              basis->fields[FieldPracticeNight]);
638     }
639
640   return result;
641 }
642
643 static void
644 found_tower_free (FoundTower *tower)
645 {
646   g_free (tower->sortkey);
647   g_free (tower->primarykey);
648   g_free (tower->displayname);
649   g_free (tower);
650 }
651
652 static void
653 parse_dove (ParseDoveCallback callback,
654             GSList **filter_results,
655             gpointer data)
656 {
657   FILE *dove = fopen("/usr/share/belltower/dove.txt", "r");
658   char tower_rec[4096];
659   tower result;
660   char *i;
661   gboolean seen_newline;
662
663   if (!dove)
664     {
665       show_message ("Cannot open Dove database!");
666       exit (255);
667     }
668
669   result.serial = 0;
670
671   while (fgets (tower_rec, sizeof (tower_rec), dove))
672     {
673       seen_newline = FALSE;
674       result.fields[0] = tower_rec;
675       result.n_fields = 0;
676       for (i=tower_rec; *i; i++) {
677         if (*i=='\n')
678           {
679             seen_newline = TRUE;
680           }
681         if (*i=='\\' || *i=='\n')
682           {
683             *i = 0;
684             result.n_fields++;
685             result.fields[result.n_fields] = i+1;
686           }
687       }
688
689       if (!seen_newline)
690         {
691           /* keep it simple, stupid */
692           show_message ("Line too long, cannot continue.");
693           exit (255);
694         }
695
696       if (strcmp (result.fields[FieldCountry], "")==0)
697         {
698           result.fields[FieldCountry] = "England";
699         }
700
701       switch (callback (&result, data))
702         {
703         case FILTER_IGNORE:
704           /* nothing */
705           break;
706
707         case FILTER_STOP:
708           fclose (dove);
709           return;
710
711         case FILTER_ACCEPT:
712           if (filter_results)
713             {
714               *filter_results = g_slist_append (*filter_results,
715                                                 found_tower_new (&result));
716             }
717         }
718
719       result.serial++;
720     }
721
722   fclose (dove);
723 }
724
725 static void
726 show_tower (char *primary_key)
727 {
728   parse_dove (single_tower_cb, NULL, primary_key);
729 }
730
731 static void
732 show_towers_from_list (GSList *list)
733 {
734   GtkWidget *dialog;
735   GtkWidget *selector;
736   gint result = -1;
737   GSList *cursor;
738   gchar foo[2048];
739
740   if (!list)
741     {
742       hildon_banner_show_information(window,
743                                      NULL,
744                                      "No towers found.");
745       return;
746     }
747
748   if (!list->next)
749     {
750       /* only one; don't bother showing the list */
751       FoundTower* found = (FoundTower*) list->data;
752
753       hildon_banner_show_information(window,
754                                      NULL,
755                                      "One tower found.");
756       show_tower (found->primarykey);
757
758       /* FIXME: and free the list */
759       return;
760     }
761
762   dialog = hildon_picker_dialog_new (GTK_WINDOW (window));
763   selector = hildon_touch_selector_new_text ();
764
765   for (cursor=list; cursor; cursor=cursor->next)
766     {
767       FoundTower* found = (FoundTower*) cursor->data;
768       hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector),
769                                          found->displayname);
770     }
771
772   hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (dialog),
773                                      HILDON_TOUCH_SELECTOR (selector));
774
775   gtk_widget_show_all (GTK_WIDGET (dialog));
776
777   if (gtk_dialog_run (GTK_DIALOG (dialog))==GTK_RESPONSE_OK)
778     {
779       GList *rows = hildon_touch_selector_get_selected_rows (HILDON_TOUCH_SELECTOR (selector),
780                                                              0);
781       GtkTreePath *path = (GtkTreePath*) rows->data;
782       gint *indices = gtk_tree_path_get_indices (path);
783
784       result = *indices;
785     }
786
787   gtk_widget_destroy (GTK_WIDGET (dialog));
788
789   if (result!=-1)
790     {
791       FoundTower *found = (FoundTower *) g_slist_nth_data (list, result);
792       show_tower (found->primarykey);
793     }
794
795   /* FIXME: and free the list */
796 }
797
798 static gint strcmp_f (gconstpointer a,
799                       gconstpointer b)
800 {
801   return strcmp ((char*)a, (char*)b);
802 }
803
804 static void
805 put_areas_into_list (gpointer key,
806                      gpointer value,
807                      gpointer data)
808 {
809   GSList **list = (GSList **)data;
810   *list = g_slist_insert_sorted (*list,
811                                  value,
812                                  strcmp_f);
813 }
814
815 static void
816 nearby_towers (void)
817 {
818   GSList *matches = NULL;
819
820   if (!(device->fix->fields & LOCATION_GPS_DEVICE_LATLONG_SET))
821     {
822       show_message ("I don't know where you are!");
823       return;
824     }
825
826   parse_dove (get_nearby_towers_cb,
827               &matches,
828               NULL);
829
830   show_towers_from_list (matches);
831 }
832
833 static void
834 towers_by_subarea (gchar *area)
835 {
836   GtkWidget *dialog = hildon_picker_dialog_new (GTK_WINDOW (window));
837   GtkWidget *selector = hildon_touch_selector_new_text ();
838   GHashTable *hash = g_hash_table_new_full (g_str_hash,
839                                             g_str_equal,
840                                             g_free,
841                                             g_free);
842   GSList *list=NULL, *cursor;
843   gchar *title = g_strdup_printf ("Areas of %s", area);
844   country_cb_data d = { hash, area };
845   country_and_county cac = { area, NULL };
846
847   gtk_window_set_title (GTK_WINDOW (dialog), title);
848   g_free (title);
849
850   parse_dove (get_counties_cb, NULL, &d);
851
852   g_hash_table_foreach (hash,
853                         put_areas_into_list,
854                         &list);
855
856   for (cursor=list; cursor; cursor=cursor->next)
857     {
858       hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector),
859                                          cursor->data);
860     }
861
862   hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (dialog),
863                                      HILDON_TOUCH_SELECTOR (selector));
864
865   gtk_widget_show_all (GTK_WIDGET (dialog));
866
867   if (gtk_dialog_run (GTK_DIALOG (dialog))==GTK_RESPONSE_OK)
868     {
869       GSList *matches = NULL;
870       cac.county = strdup (hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (selector)));
871
872       parse_dove (get_towers_by_county_cb,
873                   &matches,
874                   &cac);
875       g_free (cac.county);
876
877       show_towers_from_list (matches);
878     }
879   g_hash_table_unref (hash);
880   gtk_widget_destroy (GTK_WIDGET (dialog));
881 }
882
883 static void
884 towers_by_area (void)
885 {
886   GtkWidget *dialog = hildon_picker_dialog_new (GTK_WINDOW (window));
887   GtkWidget *selector = hildon_touch_selector_new_text ();
888   GHashTable *hash = g_hash_table_new_full (g_str_hash,
889                                             g_str_equal,
890                                             g_free,
891                                             g_free);
892   GSList *list = NULL, *cursor;
893   gchar *result = NULL;
894
895   gtk_window_set_title (GTK_WINDOW (dialog), "Areas of the world");
896
897   parse_dove (get_countries_cb, NULL, hash);
898
899   g_hash_table_foreach (hash,
900                         put_areas_into_list,
901                         &list);
902
903   for (cursor=list; cursor; cursor=cursor->next)
904     {
905       hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector),
906                                          cursor->data);
907     }
908
909   hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (dialog),
910                                      HILDON_TOUCH_SELECTOR (selector));
911
912   gtk_widget_show_all (GTK_WIDGET (dialog));
913
914   if (gtk_dialog_run (GTK_DIALOG (dialog))==GTK_RESPONSE_OK)
915     {
916       result = g_strdup (hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (selector)));
917     }
918   g_hash_table_unref (hash);
919   gtk_widget_destroy (GTK_WIDGET (dialog));
920
921   if (result)
922     {
923       towers_by_subarea (result);
924       g_free (result);
925     }
926 }
927
928 static void
929 show_bookmarks (void)
930 {
931   GSList *matches = NULL;
932
933   parse_dove (get_bookmarked_towers_cb,
934               &matches,
935               NULL);
936
937   show_towers_from_list (matches);
938 }
939
940 static void
941 tower_search (void)
942 {
943   GtkWidget *terms = gtk_dialog_new_with_buttons ("What are you looking for?",
944                                                   GTK_WINDOW (window),
945                                                   GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
946                                                   "Search",
947                                                   GTK_RESPONSE_OK,
948                                                   NULL);
949   GtkWidget *entry = gtk_entry_new ();
950   GSList *matches = NULL;
951
952   gtk_box_pack_end (GTK_BOX (GTK_DIALOG (terms)->vbox),
953                     entry, TRUE, TRUE, 0);
954
955   gtk_widget_show_all (GTK_WIDGET (terms));
956
957   if (gtk_dialog_run (GTK_DIALOG (terms))==GTK_RESPONSE_OK)
958     {
959       GSList *matches = NULL;
960
961       parse_dove (get_towers_by_search_cb,
962                   &matches,
963                   (char*) gtk_entry_get_text (GTK_ENTRY (entry)));
964
965       show_towers_from_list (matches);
966     }
967
968   gtk_widget_destroy (GTK_WIDGET (terms));
969 }
970
971 static void
972 recent_towers (void)
973 {
974   show_message ("This is not yet implemented.");
975 }
976
977 /**
978  * Displays a web page.
979  * (Perhaps this should be merged with show_browser().)
980  *
981  * \param url  The URL.
982  */
983 static void
984 show_web_page (GtkButton *dummy,
985                gpointer url)
986 {
987   show_browser (url);
988 }
989
990 /**
991  * Shows the credits.
992  *
993  * \param source If non-null, we were called from a button press,
994  *               so always show the credits.  If null, we were called
995  *               automatically on startup, so show the credits if
996  *               they haven't already been seen.
997  */
998 static void
999 show_credits (GtkButton *source,
1000               gpointer dummy)
1001 {
1002   gboolean from_button = (source!=NULL);
1003   GtkWidget *dialog, *label, *button;
1004
1005   if (!from_button &&
1006       g_key_file_get_boolean (config,
1007                               CONFIG_GENERAL_GROUP,
1008                               CONFIG_SEEN_CREDITS_KEY,
1009                               NULL))
1010     {
1011       return;
1012     }
1013                               
1014
1015   dialog = gtk_dialog_new_with_buttons ("Credits",
1016                                         GTK_WINDOW (window),
1017                                         GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
1018                                         NULL
1019                                         );
1020
1021   button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
1022                                         HILDON_BUTTON_ARRANGEMENT_VERTICAL,
1023                                         "View the GNU General Public Licence",
1024                                         "This program is provided under the GPL, with no warranty.");
1025   g_signal_connect (button, "clicked", G_CALLBACK (show_web_page),
1026                     "www.gnu.org/copyleft/gpl.html");
1027   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1028                     button,
1029                     TRUE, TRUE, 0);
1030
1031   button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
1032                                         HILDON_BUTTON_ARRANGEMENT_VERTICAL,
1033                                         "View Dove's Guide for Church Bell Ringers",
1034                                         "The source of this program's data.");
1035   g_signal_connect (button, "clicked", G_CALLBACK (show_web_page),
1036                     "http://dove.cccbr.org.uk");
1037   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1038                     button,
1039                     TRUE, TRUE, 0);
1040
1041   button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
1042                                         HILDON_BUTTON_ARRANGEMENT_VERTICAL,
1043                                         "View belfry photograph",
1044                                         "Image \xc2\xa9 Amanda Slater, cc-by-sa.");
1045   g_signal_connect (button, "clicked", G_CALLBACK (show_web_page),
1046                     "http://www.flickr.com/photos/pikerslanefarm/3398769335/");
1047   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1048                     button,
1049                     TRUE, TRUE, 0);
1050   
1051   gtk_widget_show_all (GTK_WIDGET (dialog));
1052
1053   g_key_file_set_boolean (config,
1054                           CONFIG_GENERAL_GROUP,
1055                           CONFIG_SEEN_CREDITS_KEY,
1056                           TRUE);
1057   save_config ();
1058 }
1059
1060 int
1061 main(int argc, char **argv)
1062 {
1063   GtkWidget *bell, *button, *hbox;
1064   GdkPixbuf *bell_picture;
1065
1066   gtk_init (&argc, &argv);
1067   g_set_application_name ("Belltower");
1068
1069   device = g_object_new (LOCATION_TYPE_GPS_DEVICE, NULL);
1070
1071   window = hildon_stackable_window_new ();
1072   gtk_window_set_title (GTK_WINDOW (window), "Belltower");
1073   g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL);
1074
1075   bell_picture = gdk_pixbuf_new_from_file ("/usr/share/belltower/bells1.jpg", NULL);
1076
1077   buttons = gtk_vbox_new (TRUE, 0);
1078   menu = HILDON_APP_MENU (hildon_app_menu_new ());
1079
1080   add_button ("Nearby", nearby_towers);
1081   add_button ("Recent", recent_towers);
1082   add_button ("Bookmarks", show_bookmarks);
1083   add_button ("By area", towers_by_area);
1084   add_button ("Search", tower_search);
1085
1086   /* extra buttons for the app menu */
1087   button = gtk_button_new_with_label ("Credits");
1088   g_signal_connect (button, "clicked", G_CALLBACK (show_credits), NULL);
1089   hildon_app_menu_append (menu, GTK_BUTTON (button));
1090
1091   gtk_widget_show_all (GTK_WIDGET (menu));
1092   hildon_window_set_app_menu (HILDON_WINDOW (window), menu);
1093
1094   hbox = gtk_hbox_new (FALSE, 0);
1095   gtk_box_pack_end (GTK_BOX (hbox), buttons, TRUE, TRUE, 0);
1096   gtk_box_pack_end (GTK_BOX (hbox),
1097                     gtk_image_new_from_pixbuf (bell_picture),
1098                     TRUE, TRUE, 0);
1099
1100   gtk_container_add (GTK_CONTAINER (window), hbox);
1101   gtk_widget_show_all (GTK_WIDGET (window));
1102
1103   load_config ();
1104   show_credits (NULL, NULL);
1105
1106   gtk_main ();
1107
1108   return EXIT_SUCCESS;
1109 }