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