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