fix warning
[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_DISTANCES_KEY "Distances"
23 #define CONFIG_TOWERSORT_KEY "Towersort"
24 #define CONFIG_BOOKMARK_GROUP "Bookmarks"
25 #define CONFIG_RECENT_GROUP "Recent"
26 #define CONFIG_SEEN_CREDITS_KEY "seen_credits"
27 #define CONFIG_DIRECTORY "/home/user/.config/belltower"
28 #define CONFIG_FILENAME CONFIG_DIRECTORY "/belltower.ini"
29
30 /**
31  * Somewhat arbitrary minimum number of belltowers in
32  * one country for the country to be considered to have
33  * "many" belltowers.
34  */
35 #define MANY_BELLTOWERS 10
36
37 char *distances_settings[] = {
38   CONFIG_DISTANCES_KEY,
39   "Distances are measured in...",
40   "Miles",
41   "Kilometres",
42   NULL
43 };
44
45 char *towersort_settings[] = {
46   CONFIG_TOWERSORT_KEY,
47   "Towers are sorted by...",
48   "Name of town",
49   "Distance from you",
50   "Days until practice night",
51   "Number of bells",
52   "Weight of tenor",
53   NULL
54 };
55
56 char **settings[] = {
57   distances_settings,
58   towersort_settings
59 };
60
61 gint settings_value[G_N_ELEMENTS (settings)] = { 0, };
62
63 typedef enum {
64   SETTINGS_DISTANCES,
65   SETTINGS_TOWERSORT
66 } Settings;
67
68 typedef enum {
69   DISTANCES_MILES,
70   DISTANCES_KILOMETRES
71 } DistancesSetting;
72
73 typedef enum {
74   TOWERSORT_TOWN,
75   TOWERSORT_DISTANCE,
76   TOWERSORT_PRACTICE,
77   TOWERSORT_BELLS,
78   TOWERSORT_WEIGHT
79 } TowersortSetting;
80
81 GtkWidget *window;
82 LocationGPSDevice *device;
83 GKeyFile *static_content;
84 GKeyFile *config;
85
86 typedef enum {
87   /** stop scanning the database */
88   FILTER_STOP,
89   /** ignore this one */
90   FILTER_IGNORE,
91   /** add this one to the list */
92   FILTER_ACCEPT
93 } FilterResult;
94
95 /*
96   FIXME:
97   We should really do this by looking at the header row of the table.
98   They might decide to put in new columns some day.
99 */
100 typedef enum {
101   FieldPrimaryKey,
102   FieldNationalGrid,
103   FieldAccRef,
104   FieldSNLat,
105   FieldSNLong,
106   FieldPostcode,
107   FieldTowerBase,
108   FieldCounty,
109   FieldCountry,
110   FieldDiocese,
111   FieldPlace,
112   FieldPlace2,
113   FieldPlaceCL,
114   FieldDedication,
115   FieldBells,
116   FieldWt,
117   FieldApp,
118   FieldNote,
119   FieldHz,
120   FieldDetails,
121   FieldGF,
122   FieldToilet,
123   FieldUR,
124   FieldPDNo,
125   FieldPracticeNight,
126   FieldPSt,
127   FieldPrXF,
128   FieldOvhaulYr,
129   FieldContractor,
130   FieldExtraInfo,
131   FieldWebPage,
132   FieldUpdated,
133   FieldAffiliations,
134   FieldAltName,
135   FieldLat,
136   FieldLong,
137   FieldSimulator
138 } field;
139
140 typedef struct {
141   int serial;
142
143   /* the raw data */
144
145   char* fields[MAX_FIELDS];
146   int n_fields;
147 } tower;
148
149 static void show_towers_from_list (GSList *list, gchar *list_name);
150 static void free_tower_list (GSList *list);
151
152 static void
153 show_message (char *message)
154 {
155   HildonNote* note = HILDON_NOTE
156     (hildon_note_new_information (GTK_WINDOW (window),
157                                   message?message:
158                                   "Some message was supposed to be here."));
159   gtk_dialog_run (GTK_DIALOG (note));
160   gtk_widget_destroy (GTK_WIDGET (note));
161 }
162
163 /**
164  * Loads the content of the static and dynamic data files.
165  * Possibly puts up a warning if we can't load the static file.
166  */
167 static void
168 load_config (void)
169 {
170   gint i;
171
172   static_content = g_key_file_new ();
173
174   if (!g_key_file_load_from_file (static_content,
175                                   "/usr/share/belltower/static.ini",
176                                   G_KEY_FILE_NONE,
177                                   NULL))
178     {
179       show_message ("Could not load static content.  Attempting to continue.");
180     }
181
182   config = g_key_file_new ();
183   /* it doesn't matter if this fails */
184   g_key_file_load_from_file (config,
185                              CONFIG_FILENAME,
186                              G_KEY_FILE_KEEP_COMMENTS,
187                              NULL);
188
189   for (i=0; i<G_N_ELEMENTS (settings); i++)
190     {
191       gchar *value = g_key_file_get_string (config,
192                                             CONFIG_GENERAL_GROUP,
193                                             settings[i][0],
194                                             NULL);
195
196       settings_value[i] = 0;
197
198       if (value)
199         {
200           gint j=0;
201           char **cursor = settings[i]+2;
202
203           while (*cursor)
204             {
205               if (strcmp (value, *cursor)==0)
206                 {
207                   settings_value[i] = j;
208                   break;
209                 }
210               cursor++;
211               j++;
212             }
213         }
214     }
215 }
216
217 /**
218  * Saves the dynamic data file to disk.
219  * Puts up a message if there was any error.
220  */
221 static void
222 save_config (void)
223 {
224   gchar *data;
225
226   g_mkdir_with_parents (CONFIG_DIRECTORY, 0700);
227
228   data = g_key_file_to_data (config, NULL, NULL);
229
230   if (!g_file_set_contents (CONFIG_FILENAME,
231                             data,
232                             -1,
233                             NULL))
234     {
235       show_message ("Could not write config file.");
236     }
237
238   g_free (data);
239 }
240
241 static gint
242 distance_to_tower (tower *details)
243 {
244   char *endptr;
245   double tower_lat;
246   double tower_long;
247   double km_distance;
248   const double km_to_miles = 1.609344;
249
250   tower_lat = strtod(details->fields[FieldLat], &endptr);
251   if (*endptr) return -1;
252   tower_long = strtod(details->fields[FieldLong], &endptr);
253   if (*endptr) return -1;
254
255   km_distance = location_distance_between (device->fix->latitude,
256                                            device->fix->longitude,
257                                            tower_lat,
258                                            tower_long);
259   if (settings_value[SETTINGS_DISTANCES]==DISTANCES_KILOMETRES)
260     return (int) km_distance;
261   else
262     return (int) (km_distance / km_to_miles);
263 }
264
265 static gchar*
266 distance_to_tower_str (tower *details)
267 {
268   int distance = distance_to_tower (details);
269
270   if (distance==-1)
271     {
272       return g_strdup ("unknown");
273     }
274   else if (settings_value[SETTINGS_DISTANCES]==DISTANCES_KILOMETRES)
275     {
276       return g_strdup_printf("%dkm", (int) distance);
277     }
278   else
279     {
280       return g_strdup_printf("%dmi", (int) distance);
281     }
282 }
283
284 /**
285  * Returns the number of days from today until
286  * the tower's practice night.  If the tower's
287  * practice night is unknown, returns 9: this
288  * means that such towers always sort to the end.
289  */
290 static gint
291 days_until_practice_night (tower *details)
292 {
293   /* let's not use the date parsing routines, because
294    * we might get confused by locales, and the day names
295    * used in Dove are constant
296    */
297   time_t now = time (NULL);
298   struct tm *calendar = localtime (&now);
299   const char* dove_days = "SunMonTueWedThuFriSat";
300   char *found;
301   gint practice_night;
302
303   if (strcmp (details->fields[FieldPracticeNight], "")==0)
304     {
305       /* we don't know */
306       return 9;
307     }
308
309   found = strstr (dove_days, details->fields[FieldPracticeNight]);
310
311   if (!found)
312     {
313       return 9;
314     }
315
316   practice_night = (found-dove_days)/3;
317
318   return ((practice_night+7)-calendar->tm_wday)%7;
319 }
320
321 static void
322 call_dbus (DBusBusType type,
323            char *name,
324            char *path,
325            char *interface,
326            char *method,
327            char *parameter)
328 {
329   DBusGConnection *connection;
330   GError *error = NULL;
331
332   DBusGProxy *proxy;
333
334   connection = dbus_g_bus_get (type,
335                                &error);
336   if (connection == NULL)
337     {
338       show_message (error->message);
339       g_error_free (error);
340       return;
341     }
342
343   proxy = dbus_g_proxy_new_for_name (connection, name, path, interface);
344
345   error = NULL;
346   if (!dbus_g_proxy_call (proxy, method, &error,
347                           G_TYPE_STRING, parameter,
348                           G_TYPE_INVALID,
349                           G_TYPE_INVALID))
350     {
351       show_message (error->message);
352       g_error_free (error);
353     }
354 }
355
356 static void
357 show_browser (gchar *url)
358 {
359   call_dbus (DBUS_BUS_SESSION,
360              "com.nokia.osso_browser",
361              "/com/nokia/osso_browser/request",
362              "com.nokia.osso_browser",
363              "load_url",
364              url);
365 }
366
367 typedef FilterResult (*ParseDoveCallback)(tower *details, gpointer data);
368 typedef void (*ButtonCallback)(void);
369
370 GtkWidget *tower_window, *buttons, *tower_table;
371 HildonAppMenu *menu;
372
373 static void
374 add_table_field (char *name,
375                  char *value)
376 {
377   int row;
378   GtkLabel *label;
379   gchar *str;
380
381   g_object_get(tower_table, "n-rows", &row);
382
383   row++;
384
385   gtk_table_resize (GTK_TABLE (tower_table), row, 2);
386
387   label = GTK_LABEL (gtk_label_new (NULL));
388   str = g_strdup_printf("<b>%s</b>", name);
389   gtk_label_set_markup (label, str);
390   g_free (str);
391   gtk_label_set_justify (label, GTK_JUSTIFY_RIGHT);
392   gtk_table_attach_defaults (GTK_TABLE (tower_table),
393                              GTK_WIDGET (label),
394                              0, 1, row, row+1);
395
396   label = GTK_LABEL (gtk_label_new (value));
397   gtk_label_set_justify (label, GTK_JUSTIFY_LEFT);
398   gtk_table_attach_defaults (GTK_TABLE (tower_table),
399                              GTK_WIDGET (label),
400                              1, 2, row, row+1);
401 }
402
403 static void
404 add_button (char *label,
405             ButtonCallback callback)
406 {
407   GtkWidget *button;
408
409   button = gtk_button_new_with_label (label);
410   g_signal_connect (button, "clicked", G_CALLBACK (callback), NULL);
411   hildon_app_menu_append (menu, GTK_BUTTON (button));
412   button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
413                                         HILDON_BUTTON_ARRANGEMENT_VERTICAL,
414                                         label, NULL);
415   g_signal_connect (button, "clicked", G_CALLBACK (callback), NULL);
416   gtk_box_pack_end (GTK_BOX (buttons), button, FALSE, FALSE, 0);
417 }
418
419
420 char *tower_displayed = NULL;
421 char *tower_website = NULL;
422 char *tower_map = NULL;
423 char *tower_directions = NULL;
424 char *peals_list = NULL;
425
426 #define BUTTON_BOOKMARKED_YES "Remove from bookmarks"
427 #define BUTTON_BOOKMARKED_NO "Bookmark"
428
429 static void
430 bookmark_toggled (GtkButton *button,
431                   gpointer dummy)
432 {
433   if (g_key_file_get_boolean (config,
434                               CONFIG_BOOKMARK_GROUP,
435                               tower_displayed,
436                               NULL))
437     {
438
439       /* it's bookmarked; remove the bookmark */
440
441       if (!g_key_file_remove_key (config,
442                                   CONFIG_BOOKMARK_GROUP,
443                                   tower_displayed,
444                                   NULL))
445         {
446           show_message ("Could not remove bookmark.");
447           return;
448         }
449
450       save_config ();
451       gtk_button_set_label (button,
452                             BUTTON_BOOKMARKED_NO);
453     }
454   else
455     {
456       /* it's not bookmarked; add a bookmark */
457
458       g_key_file_set_boolean (config,
459                               CONFIG_BOOKMARK_GROUP,
460                               tower_displayed,
461                               TRUE);
462
463       save_config ();
464       gtk_button_set_label (button,
465                             BUTTON_BOOKMARKED_YES);
466     }
467 }
468
469 static void
470 show_tower_website (void)
471 {
472   show_browser (tower_website);
473 }
474
475 static void
476 show_tower_map (void)
477 {
478   show_browser (tower_map);
479 }
480
481 static void
482 show_tower_directions (void)
483 {
484   if (tower_directions)
485     {
486       show_browser (tower_directions);
487     }
488   else
489     {
490       show_message ("I don't know where you are!");
491     }
492 }
493
494 static void
495 show_peals_list (void)
496 {
497   show_browser (peals_list);
498 }
499
500 static FilterResult
501 get_countries_cb (tower *details,
502                   gpointer data)
503 {
504   GHashTable *hash = (GHashTable *)data;
505   gpointer value;
506
507   if (details->serial==0)
508     return TRUE; /* header row */
509
510   if (!g_hash_table_lookup_extended (hash,
511                                      details->fields[FieldCountry],
512                                      NULL, &value))
513     {
514       g_hash_table_insert (hash,
515                            g_strdup(details->fields[FieldCountry]),
516                            GINT_TO_POINTER (0));
517     }
518   else
519     {
520       g_hash_table_replace (hash,
521                             g_strdup(details->fields[FieldCountry]),
522                             GINT_TO_POINTER (GPOINTER_TO_INT (value)+1));
523     }
524
525   return FILTER_IGNORE;
526 }
527
528 typedef struct {
529   GHashTable *hash;
530   gchar *country_name;
531 } country_cb_data;
532
533 typedef struct {
534   char *country;
535   char *county;
536 } country_and_county;
537
538 static FilterResult
539 get_counties_cb (tower *details,
540                  gpointer data)
541 {
542   country_cb_data *d = (country_cb_data *)data;
543
544   if (details->serial==0)
545     return FILTER_IGNORE; /* header row */
546
547   if (strcmp(details->fields[FieldCountry], d->country_name)!=0)
548     return FILTER_IGNORE; /* wrong country */
549
550   if (!g_hash_table_lookup_extended (d->hash,
551                                     details->fields[FieldCounty],
552                                      NULL, NULL))
553     {
554       g_hash_table_insert (d->hash,
555                            g_strdup(details->fields[FieldCounty]),
556                            g_strdup (details->fields[FieldCounty]));
557     }
558
559   return FILTER_IGNORE;
560 }
561
562 static FilterResult
563 get_nearby_towers_cb (tower *details,
564                       gpointer distance)
565 {
566   if (details->serial==0)
567     return FILTER_IGNORE; /* header row */
568
569   if (distance_to_tower (details) <= GPOINTER_TO_INT (distance))
570     {
571       return FILTER_ACCEPT;
572     }
573   else
574     {
575       return FILTER_IGNORE;
576     }
577 }
578
579 static FilterResult
580 get_towers_by_county_cb (tower *details,
581                          gpointer data)
582 {
583   country_and_county *cac = (country_and_county *) data;
584
585   if ((!cac->county || strcmp (cac->county, details->fields[FieldCounty])==0) &&
586       (!cac->country || strcmp (cac->country, details->fields[FieldCountry])==0))
587     {
588       return FILTER_ACCEPT;
589     }
590   else
591     {
592       return FILTER_IGNORE;
593     }
594 }
595
596 static FilterResult
597 get_towers_by_search_cb (tower *details,
598                          gpointer data)
599 {
600   char *s = (char *) data;
601
602   if (strcasestr(details->fields[FieldCountry], s) ||
603       strcasestr(details->fields[FieldCounty], s) ||
604       strcasestr(details->fields[FieldDedication], s) ||
605       strcasestr(details->fields[FieldPlace], s))
606     {
607       return FILTER_ACCEPT;
608     }
609   else
610     {
611       return FILTER_IGNORE;
612     }
613 }
614
615 /**
616  * A filter which accepts towers based on whether they
617  * appear in a particular group in the config file.
618  *
619  * \param details  the candidate tower
620  * \param data     pointer to a char* which names the group
621  */
622 static FilterResult
623 get_group_of_towers_cb (tower *details,
624                           gpointer data)
625 {
626   if (g_key_file_has_key (config,
627                           (char*) data,
628                           details->fields[FieldPrimaryKey],
629                           NULL))
630     {
631       return FILTER_ACCEPT;
632     }
633   else
634     {
635       return FILTER_IGNORE;
636     }
637 }
638
639 /**
640  * Removes the oldest entry from the [Recent] group in the config
641  * file until there are only five entries left.  Does not save
642  * the file; you have to do that.
643  */
644 static void
645 remove_old_recent_entries (void)
646 {
647   gint count;
648
649   do
650     {
651       gchar **towers;
652       gint oldest_date = 0;
653       gchar *oldest_tower = NULL;
654       gint i;
655
656       /* It is a bit inefficient to do this every
657        * time we go around the loop.  However, it
658        * makes the code far simpler, and we almost
659        * never go around more than once.
660        */
661       towers = g_key_file_get_keys (config,
662                                     CONFIG_RECENT_GROUP,
663                                     &count,
664                                     NULL);
665
666       if (count <= MAX_RECENT)
667         /* everything's fine */
668         return;
669
670       for (i=0; i<count; i++)
671         {
672           gint date = g_key_file_get_integer (config,
673                                               CONFIG_RECENT_GROUP,
674                                               towers[i],
675                                               NULL);
676
677           if (date==0)
678             continue;
679
680           if (oldest_date==0 ||
681               date < oldest_date)
682             {
683               oldest_tower = towers[i];
684               oldest_date = date;
685             }
686         }
687
688       if (oldest_tower)
689         {
690           g_key_file_remove_key (config,
691                                  CONFIG_RECENT_GROUP,
692                                  oldest_tower,
693                                  NULL);
694           count --;
695         }
696       g_strfreev (towers);
697     }
698   while (count > MAX_RECENT);
699 }
700
701 static FilterResult
702 single_tower_cb (tower *details,
703                  gpointer data)
704 {
705
706   GtkWidget *hbox, *button;
707   gchar *str;
708   gint tenor_weight;
709   gchar *primary_key = (gchar*) data;
710   gchar *distance;
711
712   if (strcmp(details->fields[FieldPrimaryKey], primary_key)!=0)
713     {
714       /* not this one; keep going */
715       return FILTER_IGNORE;
716     }
717
718   tower_window = hildon_stackable_window_new ();
719
720   str = g_strdup_printf("%s, %s",
721                         details->fields[FieldDedication],
722                         details->fields[FieldPlace]);
723
724   hildon_window_set_markup (HILDON_WINDOW (tower_window),
725                             str);
726   g_free (str);
727
728   hbox = gtk_hbox_new (FALSE, 0);
729   tower_table = gtk_table_new (0, 2, FALSE);
730   buttons = gtk_vbox_new (TRUE, 0);
731   menu = HILDON_APP_MENU (hildon_app_menu_new ());
732
733   distance = distance_to_tower_str(details);
734
735   add_table_field ("Distance", distance);
736   add_table_field ("Postcode", details->fields[FieldPostcode]);
737   add_table_field ("County", details->fields[FieldCounty]);
738   add_table_field ("Country", details->fields[FieldCountry]);
739   add_table_field ("Diocese", details->fields[FieldDiocese]);
740   add_table_field ("Practice night", details->fields[FieldPracticeNight]);
741   add_table_field ("Bells", details->fields[FieldBells]);
742
743   g_free (distance);
744
745   tenor_weight = atoi (details->fields[FieldWt]);
746   str = g_strdup_printf("%dcwt %dqr %dlb in %s",
747                         tenor_weight/112,
748                         (tenor_weight % 112)/28,
749                         tenor_weight % 28,
750                         details->fields[FieldNote]
751                         );
752   add_table_field ("Tenor", str);
753   g_free (str);
754
755   if (strcmp(details->fields[FieldWebPage], "")!=0)
756     {
757       add_button ("Tower website", show_tower_website);
758     }
759   add_button ("Peals", show_peals_list);
760   add_button ("Map", show_tower_map);
761   add_button ("Directions", show_tower_directions);
762
763   /* don't use a toggle button: it looks stupid */
764   button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
765                                         HILDON_BUTTON_ARRANGEMENT_VERTICAL,
766                                         g_key_file_get_boolean (config,
767                                                                 CONFIG_BOOKMARK_GROUP,
768                                                                 details->fields[FieldPrimaryKey],
769                                                                 NULL)?
770                                         BUTTON_BOOKMARKED_YES: BUTTON_BOOKMARKED_NO,
771                                         NULL);
772   g_signal_connect (button, "clicked", G_CALLBACK (bookmark_toggled), NULL);
773   gtk_box_pack_start (GTK_BOX (buttons), button, FALSE, FALSE, 0);
774
775   gtk_widget_show_all (GTK_WIDGET (menu));
776   hildon_window_set_app_menu (HILDON_WINDOW (tower_window), menu);
777
778   gtk_box_pack_end (GTK_BOX (hbox), buttons, TRUE, TRUE, 0);
779   gtk_box_pack_end (GTK_BOX (hbox), tower_table, TRUE, TRUE, 0);
780
781   gtk_container_add (GTK_CONTAINER (tower_window), hbox);
782
783   g_free (tower_website);
784   tower_website = g_strdup_printf ("http://%s", details->fields[FieldWebPage]);
785   g_free (peals_list);
786   peals_list = g_strdup_printf ("http://www.pealbase.ismysite.co.uk/felstead/tbid.php?tid=%s",
787        details->fields[FieldTowerBase]);
788   g_free (tower_map);
789   tower_map = g_strdup_printf ("http://maps.google.com/maps?q=%s,%s",
790         details->fields[FieldLat],
791         details->fields[FieldLong]);
792   g_free (tower_directions);
793   if (device->fix->fields & LOCATION_GPS_DEVICE_LATLONG_SET)
794     {
795       tower_directions = g_strdup_printf ("http://maps.google.com/maps?q=%f,%f+to+%s,%s",
796                                           device->fix->latitude,
797                                           device->fix->longitude,
798                                           details->fields[FieldLat],
799                                           details->fields[FieldLong]);
800     }
801   g_free (tower_displayed);
802   tower_displayed = g_strdup (details->fields[FieldPrimaryKey]);
803
804   g_key_file_set_integer (config,
805                           CONFIG_RECENT_GROUP,
806                           tower_displayed,
807                           time (NULL));
808   remove_old_recent_entries ();
809   save_config ();
810
811   gtk_widget_show_all (GTK_WIDGET (tower_window));
812
813   return FILTER_STOP;
814 }
815
816 /**
817  * A tower that was accepted by a filter.
818  */
819 typedef struct {
820   char *sortkey;
821   char *primarykey;
822   char *displayname;
823 } FoundTower;
824
825 static FoundTower *
826 found_tower_new (tower *basis)
827 {
828   FoundTower* result = g_new (FoundTower, 1);
829
830   switch (settings_value[SETTINGS_TOWERSORT])
831     {
832     case TOWERSORT_DISTANCE:
833       result->sortkey = g_strdup_printf ("%5d %s",
834                                          distance_to_tower (basis),
835                                          basis->fields[FieldPlace]);
836       break;
837     case TOWERSORT_PRACTICE:
838       result->sortkey = g_strdup_printf ("%d %s",
839                                          days_until_practice_night (basis),
840                                          basis->fields[FieldPlace]);
841       break;
842     case TOWERSORT_BELLS:
843       result->sortkey = g_strdup_printf ("%10s", basis->fields[FieldBells]);
844       break;
845     case TOWERSORT_WEIGHT:
846       result->sortkey = g_strdup_printf ("%10s", basis->fields[FieldWt]);
847       break;
848     case TOWERSORT_TOWN:
849     default:
850       result->sortkey = g_strdup (basis->fields[FieldPlace]);
851     }
852
853   result->primarykey = g_strdup (basis->fields[FieldPrimaryKey]);
854
855   if (device->fix->fields & LOCATION_GPS_DEVICE_LATLONG_SET)
856     {
857       gchar *distance = distance_to_tower_str (basis);
858       result->displayname = g_strdup_printf ("%s, %s (%s, %s) (%s)",
859                                              basis->fields[FieldDedication],
860                                              basis->fields[FieldPlace],
861                                              basis->fields[FieldBells],
862                                              basis->fields[FieldPracticeNight],
863                                              distance);
864       g_free (distance);
865     }
866   else
867     {
868       result->displayname = g_strdup_printf ("%s, %s (%s, %s)",
869                                              basis->fields[FieldDedication],
870                                              basis->fields[FieldPlace],
871                                              basis->fields[FieldBells],
872                                              basis->fields[FieldPracticeNight]);
873     }
874
875   return result;
876 }
877
878 static void
879 found_tower_free (FoundTower *tower)
880 {
881   g_free (tower->sortkey);
882   g_free (tower->primarykey);
883   g_free (tower->displayname);
884   g_free (tower);
885 }
886
887 /**
888  * Comparison function for FoundTower objects.
889  *
890  * \param a   a FoundTower
891  * \param b   another FoundTower
892  */
893 gint found_tower_compare (gconstpointer a,
894                           gconstpointer b)
895 {
896   FoundTower *fta = (FoundTower *)a;
897   FoundTower *ftb = (FoundTower *)b;
898
899   return strcmp (fta->sortkey, ftb->sortkey);
900 }
901
902 /**
903  * Calls a given function once for each tower in the world.
904  * (The first call, however, is a header row.)
905  *
906  * \param callback       The function to call.
907  * \param data           Arbitrary data to pass to the callback.
908  * \param dialogue_title If non-NULL, a list will be displayed
909  *                       with the results.  This is the title
910  *                       used for that dialogue.  (The dialogue
911  *                       will automatically free filter_results.)
912  */
913 static void
914 parse_dove (ParseDoveCallback callback,
915             gpointer data,
916             gchar *dialogue_title)
917 {
918   FILE *dove = fopen("/usr/share/belltower/dove.txt", "r");
919   char tower_rec[4096];
920   tower result;
921   char *i;
922   gboolean seen_newline;
923   GSList *filter_results = NULL;
924
925   if (!dove)
926     {
927       show_message ("Cannot open Dove database!");
928       exit (255);
929     }
930
931   result.serial = 0;
932
933   while (fgets (tower_rec, sizeof (tower_rec), dove))
934     {
935       seen_newline = FALSE;
936       result.fields[0] = tower_rec;
937       result.n_fields = 0;
938       for (i=tower_rec; *i; i++) {
939         if (*i=='\n')
940           {
941             seen_newline = TRUE;
942           }
943         if (*i=='\\' || *i=='\n')
944           {
945             *i = 0;
946             result.n_fields++;
947             result.fields[result.n_fields] = i+1;
948           }
949       }
950
951       if (!seen_newline)
952         {
953           /* keep it simple, stupid */
954           show_message ("Line too long, cannot continue.");
955           exit (255);
956         }
957
958       if (strcmp (result.fields[FieldCountry], "")==0)
959         {
960           result.fields[FieldCountry] = "England";
961         }
962
963       switch (callback (&result, data))
964         {
965         case FILTER_IGNORE:
966           /* nothing */
967           break;
968
969         case FILTER_STOP:
970           fclose (dove);
971           return;
972
973         case FILTER_ACCEPT:
974           filter_results = g_slist_insert_sorted (filter_results,
975                                                   found_tower_new (&result),
976                                                   found_tower_compare);
977         }
978
979       result.serial++;
980     }
981
982   fclose (dove);
983
984   if (dialogue_title)
985     {
986       show_towers_from_list (filter_results,
987                              dialogue_title);
988     }
989   else
990     {
991       free_tower_list (filter_results);
992     }
993 }
994
995 static void
996 show_tower (char *primary_key)
997 {
998   parse_dove (single_tower_cb, primary_key, NULL);
999 }
1000
1001 static void
1002 free_tower_list (GSList *list)
1003 {
1004   GSList *cursor = list;
1005
1006   while (cursor)
1007     {
1008       found_tower_free ((FoundTower*) cursor->data);
1009       cursor = cursor->next;
1010     }
1011
1012   g_slist_free (list);
1013 }
1014
1015 /**
1016  * Displays a list of towers for the user to choose from.
1017  * When one is chosen, we go to the display page for that tower.
1018  * If there are none, this will tell the user there were none.
1019  * If there is only one, we go straight to its display page.
1020  *
1021  * \param list       a GSList of FoundTower objects.
1022  * \param list_name  the title for the dialogue.
1023  */
1024 static void
1025 show_towers_from_list (GSList *list,
1026                        gchar *list_name)
1027 {
1028   GtkWidget *dialog;
1029   GtkWidget *selector;
1030   gint result = -1;
1031   GSList *cursor;
1032
1033   if (!list)
1034     {
1035       hildon_banner_show_information(window,
1036                                      NULL,
1037                                      "No towers found.");
1038       return;
1039     }
1040
1041   if (!list->next)
1042     {
1043       /* only one; don't bother showing the list */
1044       FoundTower* found = (FoundTower*) list->data;
1045
1046       hildon_banner_show_information(window,
1047                                      NULL,
1048                                      "One tower found.");
1049       show_tower (found->primarykey);
1050
1051       free_tower_list (list);
1052       return;
1053     }
1054
1055   dialog = hildon_picker_dialog_new (GTK_WINDOW (window));
1056   selector = hildon_touch_selector_new_text ();
1057   gtk_window_set_title (GTK_WINDOW (dialog), list_name);
1058
1059   for (cursor=list; cursor; cursor=cursor->next)
1060     {
1061       FoundTower* found = (FoundTower*) cursor->data;
1062       hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector),
1063                                          found->displayname);
1064     }
1065
1066   hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (dialog),
1067                                      HILDON_TOUCH_SELECTOR (selector));
1068
1069   gtk_widget_show_all (GTK_WIDGET (dialog));
1070
1071   if (gtk_dialog_run (GTK_DIALOG (dialog))==GTK_RESPONSE_OK)
1072     {
1073       GList *rows = hildon_touch_selector_get_selected_rows (HILDON_TOUCH_SELECTOR (selector),
1074                                                              0);
1075       GtkTreePath *path = (GtkTreePath*) rows->data;
1076       gint *indices = gtk_tree_path_get_indices (path);
1077
1078       result = *indices;
1079     }
1080
1081   gtk_widget_destroy (GTK_WIDGET (dialog));
1082
1083   if (result!=-1)
1084     {
1085       FoundTower *found = (FoundTower *) g_slist_nth_data (list, result);
1086       show_tower (found->primarykey);
1087     }
1088
1089   free_tower_list (list);
1090 }
1091
1092 static gint strcmp_f (gconstpointer a,
1093                       gconstpointer b)
1094 {
1095   return strcmp ((char*)a, (char*)b);
1096 }
1097
1098 static void
1099 put_areas_into_list (gpointer key,
1100                      gpointer value,
1101                      gpointer data)
1102 {
1103   GSList **list = (GSList **)data;
1104   *list = g_slist_insert_sorted (*list,
1105                                  value,
1106                                  strcmp_f);
1107 }
1108
1109 static void
1110 nearby_towers (void)
1111 {
1112   if (!(device->fix->fields & LOCATION_GPS_DEVICE_LATLONG_SET))
1113     {
1114       show_message ("I don't know where you are!");
1115       return;
1116     }
1117
1118   parse_dove (get_nearby_towers_cb,
1119               settings_value[SETTINGS_DISTANCES]==DISTANCES_KILOMETRES?
1120               GINT_TO_POINTER (80) :
1121               GINT_TO_POINTER (50),
1122               settings_value[SETTINGS_DISTANCES]==DISTANCES_KILOMETRES?
1123               "Towers within eighty kilometres of you":
1124               "Towers within fifty miles of you");
1125 }
1126
1127 static void
1128 towers_by_subarea (gchar *area)
1129 {
1130   GtkWidget *dialog = hildon_picker_dialog_new (GTK_WINDOW (window));
1131   GtkWidget *selector = hildon_touch_selector_new_text ();
1132   GHashTable *hash = g_hash_table_new_full (g_str_hash,
1133                                             g_str_equal,
1134                                             g_free,
1135                                             g_free);
1136   GSList *list=NULL, *cursor;
1137   gchar *title = g_strdup_printf ("Areas of %s", area);
1138   country_cb_data d = { hash, area };
1139   country_and_county cac = { area, NULL };
1140
1141   gtk_window_set_title (GTK_WINDOW (dialog), title);
1142   g_free (title);
1143
1144   parse_dove (get_counties_cb, &d, NULL);
1145
1146   g_hash_table_foreach (hash,
1147                         put_areas_into_list,
1148                         &list);
1149
1150   for (cursor=list; cursor; cursor=cursor->next)
1151     {
1152       hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector),
1153                                          cursor->data);
1154     }
1155
1156   hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (dialog),
1157                                      HILDON_TOUCH_SELECTOR (selector));
1158
1159   gtk_widget_show_all (GTK_WIDGET (dialog));
1160
1161   if (gtk_dialog_run (GTK_DIALOG (dialog))==GTK_RESPONSE_OK)
1162     {
1163       gchar *title;
1164       cac.county = g_strdup (hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (selector)));
1165       title = g_strdup_printf ("Towers in %s",
1166                                cac.county);
1167
1168       parse_dove (get_towers_by_county_cb,
1169                   &cac,
1170                   title);
1171       g_free (cac.county);
1172       g_free (title);
1173     }
1174   g_hash_table_unref (hash);
1175   gtk_widget_destroy (GTK_WIDGET (dialog));
1176 }
1177
1178 /**
1179  * Maps a hash table from country names to counts of belltowers to a
1180  * newly-created hash table mapping country names to display
1181  * names, containing only those countries which have many
1182  * (or few) belltowers.
1183  *
1184  * \param source    the source table
1185  * \param want_many true if you want countries with many belltowers;
1186  *                  false if you want countries with few.
1187  */
1188 static GHashTable*
1189 get_countries_with_many (GHashTable *source,
1190                          gboolean want_many)
1191 {
1192   GHashTable *result = g_hash_table_new_full (g_str_hash,
1193                                               g_str_equal,
1194                                               g_free,
1195                                               NULL);
1196   GList *countries = g_hash_table_get_keys (source);
1197   GList *cursor = countries;
1198
1199   while (cursor)
1200     {
1201       gboolean has_many =
1202         GPOINTER_TO_INT (g_hash_table_lookup (source,
1203                                               cursor->data)) >= MANY_BELLTOWERS;
1204
1205       if (has_many == want_many)
1206         {
1207           g_hash_table_insert (result,
1208                                g_strdup (cursor->data),
1209                                g_strdup (cursor->data));
1210         }
1211
1212       cursor = cursor->next;
1213     }
1214
1215   g_list_free (countries);
1216   return result;
1217 }
1218
1219 #define COUNTRIES_WITH_MANY "Countries with many belltowers"
1220 #define COUNTRIES_WITH_FEW "Countries with few belltowers"
1221
1222 /**
1223  * Displays a list of areas of the world with many (or few)
1224  * belltowers.  If you ask for the areas with many, it include
1225  * a link to the areas with few.
1226  *
1227  * \param countries_with_many  True to list countries with many;
1228  *                             false to list countries with few.
1229  */
1230 static void
1231 towers_by_area_with_many (gboolean countries_with_many)
1232 {
1233   GtkWidget *dialog = hildon_picker_dialog_new (GTK_WINDOW (window));
1234   GtkWidget *selector = hildon_touch_selector_new_text ();
1235   GHashTable *countries_to_counts = g_hash_table_new_full (g_str_hash,
1236                                                            g_str_equal,
1237                                                            g_free,
1238                                                            NULL);
1239   GHashTable *country_names;
1240   GSList *list = NULL, *cursor;
1241   gchar *result = NULL;
1242
1243   gtk_window_set_title (GTK_WINDOW (dialog),
1244                         countries_with_many?
1245                         COUNTRIES_WITH_MANY : COUNTRIES_WITH_FEW);
1246
1247   parse_dove (get_countries_cb, countries_to_counts, NULL);
1248
1249   country_names = get_countries_with_many (countries_to_counts,
1250                                            countries_with_many);
1251
1252   g_hash_table_foreach (country_names,
1253                         put_areas_into_list,
1254                         &list);
1255
1256   for (cursor=list; cursor; cursor=cursor->next)
1257     {
1258       hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector),
1259                                          cursor->data);
1260     }
1261
1262   if (countries_with_many)
1263     {
1264       hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector),
1265                                          COUNTRIES_WITH_FEW);
1266     }
1267
1268   hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (dialog),
1269                                      HILDON_TOUCH_SELECTOR (selector));
1270
1271   gtk_widget_show_all (GTK_WIDGET (dialog));
1272
1273   if (gtk_dialog_run (GTK_DIALOG (dialog))==GTK_RESPONSE_OK)
1274     {
1275       result = g_strdup (hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (selector)));
1276     }
1277
1278   g_hash_table_unref (countries_to_counts);
1279   g_hash_table_unref (country_names);
1280   gtk_widget_destroy (GTK_WIDGET (dialog));
1281
1282   if (result)
1283     {
1284       if (countries_with_many)
1285         {
1286           /* these countries have many towers, so
1287            * show the sub-areas
1288            */
1289           if (strcmp (result, COUNTRIES_WITH_FEW)==0)
1290             towers_by_area_with_many (FALSE);
1291           else
1292             towers_by_subarea (result);
1293         }
1294       else
1295         {
1296           country_and_county cac = { result, NULL };
1297           gchar *title = g_strdup_printf ("Belltowers in %s",
1298                                           result);
1299
1300           parse_dove (get_towers_by_county_cb,
1301                       &cac,
1302                       title);
1303
1304           g_free (title);
1305         }
1306
1307       g_free (result);
1308     }
1309 }
1310
1311 /**
1312  * Shows all the towers in areas with many towers.
1313  */
1314 static void
1315 towers_by_area (void)
1316 {
1317   towers_by_area_with_many (TRUE);
1318 }
1319
1320 static void
1321 show_bookmarks (void)
1322 {
1323   parse_dove (get_group_of_towers_cb,
1324               CONFIG_BOOKMARK_GROUP,
1325               "Bookmarks");
1326 }
1327
1328 static void
1329 tower_search (void)
1330 {
1331   GtkWidget *terms = gtk_dialog_new_with_buttons ("What are you looking for?",
1332                                                   GTK_WINDOW (window),
1333                                                   GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
1334                                                   "Search",
1335                                                   GTK_RESPONSE_OK,
1336                                                   NULL);
1337   GtkWidget *entry = gtk_entry_new ();
1338
1339   gtk_box_pack_end (GTK_BOX (GTK_DIALOG (terms)->vbox),
1340                     entry, TRUE, TRUE, 0);
1341
1342   gtk_widget_show_all (GTK_WIDGET (terms));
1343
1344   if (gtk_dialog_run (GTK_DIALOG (terms))==GTK_RESPONSE_OK)
1345     {
1346       parse_dove (get_towers_by_search_cb,
1347                   (char*) gtk_entry_get_text (GTK_ENTRY (entry)),
1348                   "Search results");
1349     }
1350
1351   gtk_widget_destroy (GTK_WIDGET (terms));
1352 }
1353
1354 static void
1355 recent_towers (void)
1356 {
1357   parse_dove (get_group_of_towers_cb,
1358               CONFIG_RECENT_GROUP,
1359               "Towers you have recently viewed");
1360 }
1361
1362 /**
1363  * Displays a web page.
1364  * (Perhaps this should be merged with show_browser().)
1365  *
1366  * \param url  The URL.
1367  */
1368 static void
1369 show_web_page (GtkButton *dummy,
1370                gpointer url)
1371 {
1372   show_browser (url);
1373 }
1374
1375 /**
1376  * Shows the credits.
1377  *
1378  * \param source If non-null, we were called from a button press,
1379  *               so always show the credits.  If null, we were called
1380  *               automatically on startup, so show the credits if
1381  *               they haven't already been seen.
1382  */
1383 static void
1384 show_credits (GtkButton *source,
1385               gpointer dummy)
1386 {
1387   gboolean from_button = (source!=NULL);
1388   GtkWidget *dialog, *label, *button;
1389
1390   if (!from_button &&
1391       g_key_file_get_boolean (config,
1392                               CONFIG_GENERAL_GROUP,
1393                               CONFIG_SEEN_CREDITS_KEY,
1394                               NULL))
1395     {
1396       return;
1397     }
1398                               
1399
1400   dialog = gtk_dialog_new_with_buttons ("Credits",
1401                                         GTK_WINDOW (window),
1402                                         GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
1403                                         NULL
1404                                         );
1405
1406   button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
1407                                         HILDON_BUTTON_ARRANGEMENT_VERTICAL,
1408                                         "Welcome to Belltower.  The program is \xc2\xa9 2009 Thomas Thurman.",
1409                                         "View the program's home page.");
1410   g_signal_connect (button, "clicked", G_CALLBACK (show_web_page),
1411                     "http://belltower.garage.maemo.org");
1412   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1413                     button,
1414                     TRUE, TRUE, 0);
1415
1416   button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
1417                                         HILDON_BUTTON_ARRANGEMENT_VERTICAL,
1418                                         "This program is provided under the GNU GPL, with no warranty.",
1419                                         "View the GNU General Public Licence.");
1420   g_signal_connect (button, "clicked", G_CALLBACK (show_web_page),
1421                     "http://www.gnu.org/copyleft/gpl.html");
1422   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1423                     button,
1424                     TRUE, TRUE, 0);
1425
1426   button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
1427                                         HILDON_BUTTON_ARRANGEMENT_VERTICAL,
1428                                         "The data comes from Dove's Guide for Church Bell Ringers.",
1429                                         "View Dove's Guide.");
1430   g_signal_connect (button, "clicked", G_CALLBACK (show_web_page),
1431                     "http://dove.cccbr.org.uk");
1432   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1433                     button,
1434                     TRUE, TRUE, 0);
1435
1436   button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
1437                                         HILDON_BUTTON_ARRANGEMENT_VERTICAL,
1438                                         "The belfry image is \xc2\xa9 Amanda Slater, cc-by-sa.",
1439                                         "View the original photograph.");
1440   g_signal_connect (button, "clicked", G_CALLBACK (show_web_page),
1441                     "http://www.flickr.com/photos/pikerslanefarm/3398769335/");
1442   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1443                     button,
1444                     TRUE, TRUE, 0);
1445   
1446   gtk_widget_show_all (GTK_WIDGET (dialog));
1447
1448   g_key_file_set_boolean (config,
1449                           CONFIG_GENERAL_GROUP,
1450                           CONFIG_SEEN_CREDITS_KEY,
1451                           TRUE);
1452   save_config ();
1453 }
1454
1455 static void
1456 settings_dialogue (void)
1457 {
1458   GtkWidget *dialog, *button;
1459   GtkWidget *selector[G_N_ELEMENTS (settings)];
1460   gint i;
1461
1462   dialog = gtk_dialog_new_with_buttons ("Settings",
1463                                         GTK_WINDOW (window),
1464                                         GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
1465                                         NULL
1466                                         );
1467
1468   for (i=0; i<G_N_ELEMENTS (settings); i++)
1469     {
1470       char **cursor = settings[i]+2;
1471       selector[i] = hildon_touch_selector_new_text ();
1472
1473       while (*cursor)
1474         {
1475           hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector[i]), *cursor);
1476           cursor++;
1477         }
1478
1479       hildon_touch_selector_set_active (HILDON_TOUCH_SELECTOR (selector[i]), 0, settings_value[i]);
1480
1481       button = hildon_picker_button_new (HILDON_SIZE_AUTO, HILDON_BUTTON_ARRANGEMENT_VERTICAL);
1482       hildon_button_set_title (HILDON_BUTTON (button), settings[i][1]);
1483       hildon_picker_button_set_selector (HILDON_PICKER_BUTTON (button),
1484                                          HILDON_TOUCH_SELECTOR (selector[i]));
1485       gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1486                           button,
1487                           TRUE, TRUE, 0);
1488     }
1489
1490   gtk_widget_show_all (GTK_WIDGET (GTK_DIALOG(dialog)->vbox));  
1491   gtk_dialog_run (GTK_DIALOG (dialog));
1492
1493   for (i=0; i<G_N_ELEMENTS (settings); i++)
1494     {
1495       GList *rows = hildon_touch_selector_get_selected_rows (HILDON_TOUCH_SELECTOR (selector[i]),
1496                                                              0);
1497       GtkTreePath *path = (GtkTreePath*) rows->data;
1498       gint *indices = gtk_tree_path_get_indices (path);
1499
1500       g_key_file_set_string (config,
1501                              CONFIG_GENERAL_GROUP,
1502                              settings[i][0],
1503                              hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (selector[i])));
1504
1505       settings_value[i] = *indices;
1506     }
1507   save_config ();
1508
1509   gtk_widget_destroy (GTK_WIDGET (dialog));
1510 }
1511
1512 int
1513 main(int argc, char **argv)
1514 {
1515   GtkWidget *bell, *button, *hbox;
1516   GdkPixbuf *bell_picture;
1517
1518   gtk_init (&argc, &argv);
1519   g_set_application_name ("Belltower");
1520
1521   device = g_object_new (LOCATION_TYPE_GPS_DEVICE, NULL);
1522
1523   window = hildon_stackable_window_new ();
1524   gtk_window_set_title (GTK_WINDOW (window), "Belltower");
1525   g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL);
1526
1527   bell_picture = gdk_pixbuf_new_from_file ("/usr/share/belltower/bells1.jpg", NULL);
1528
1529   buttons = gtk_vbox_new (TRUE, 0);
1530   menu = HILDON_APP_MENU (hildon_app_menu_new ());
1531
1532   add_button ("Nearby", nearby_towers);
1533   add_button ("Recent", recent_towers);
1534   add_button ("Bookmarks", show_bookmarks);
1535   add_button ("By area", towers_by_area);
1536   add_button ("Search", tower_search);
1537   /* This won't be a button on the main screen
1538    * for always, but we'll put it there for now
1539    * to work around a hildon bug.
1540    */
1541   add_button ("Settings", settings_dialogue);
1542
1543   /* extra buttons for the app menu */
1544   button = gtk_button_new_with_label ("Credits");
1545   g_signal_connect (button, "clicked", G_CALLBACK (show_credits), NULL);
1546   hildon_app_menu_append (menu, GTK_BUTTON (button));
1547
1548   gtk_widget_show_all (GTK_WIDGET (menu));
1549   hildon_window_set_app_menu (HILDON_WINDOW (window), menu);
1550
1551   hbox = gtk_hbox_new (FALSE, 0);
1552   gtk_box_pack_end (GTK_BOX (hbox), buttons, TRUE, TRUE, 0);
1553   gtk_box_pack_end (GTK_BOX (hbox),
1554                     gtk_image_new_from_pixbuf (bell_picture),
1555                     TRUE, TRUE, 0);
1556
1557   gtk_container_add (GTK_CONTAINER (window), hbox);
1558   gtk_widget_show_all (GTK_WIDGET (window));
1559
1560   load_config ();
1561   show_credits (NULL, NULL);
1562
1563   gtk_main ();
1564
1565   return EXIT_SUCCESS;
1566 }