fix memory leak
[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 free_tower_list (GSList *list)
811 {
812   GSList *cursor = list;
813
814   while (cursor)
815     {
816       found_tower_free ((FoundTower*) cursor->data);
817       cursor = cursor->next;
818     }
819
820   g_slist_free (list);
821 }
822
823 static void
824 show_towers_from_list (GSList *list)
825 {
826   GtkWidget *dialog;
827   GtkWidget *selector;
828   gint result = -1;
829   GSList *cursor;
830   gchar foo[2048];
831
832   if (!list)
833     {
834       hildon_banner_show_information(window,
835                                      NULL,
836                                      "No towers found.");
837       return;
838     }
839
840   if (!list->next)
841     {
842       /* only one; don't bother showing the list */
843       FoundTower* found = (FoundTower*) list->data;
844
845       hildon_banner_show_information(window,
846                                      NULL,
847                                      "One tower found.");
848       show_tower (found->primarykey);
849
850       free_tower_list (list);
851       return;
852     }
853
854   dialog = hildon_picker_dialog_new (GTK_WINDOW (window));
855   selector = hildon_touch_selector_new_text ();
856
857   for (cursor=list; cursor; cursor=cursor->next)
858     {
859       FoundTower* found = (FoundTower*) cursor->data;
860       hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector),
861                                          found->displayname);
862     }
863
864   hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (dialog),
865                                      HILDON_TOUCH_SELECTOR (selector));
866
867   gtk_widget_show_all (GTK_WIDGET (dialog));
868
869   if (gtk_dialog_run (GTK_DIALOG (dialog))==GTK_RESPONSE_OK)
870     {
871       GList *rows = hildon_touch_selector_get_selected_rows (HILDON_TOUCH_SELECTOR (selector),
872                                                              0);
873       GtkTreePath *path = (GtkTreePath*) rows->data;
874       gint *indices = gtk_tree_path_get_indices (path);
875
876       result = *indices;
877     }
878
879   gtk_widget_destroy (GTK_WIDGET (dialog));
880
881   if (result!=-1)
882     {
883       FoundTower *found = (FoundTower *) g_slist_nth_data (list, result);
884       show_tower (found->primarykey);
885     }
886
887   free_tower_list (list);
888 }
889
890 static gint strcmp_f (gconstpointer a,
891                       gconstpointer b)
892 {
893   return strcmp ((char*)a, (char*)b);
894 }
895
896 static void
897 put_areas_into_list (gpointer key,
898                      gpointer value,
899                      gpointer data)
900 {
901   GSList **list = (GSList **)data;
902   *list = g_slist_insert_sorted (*list,
903                                  value,
904                                  strcmp_f);
905 }
906
907 static void
908 nearby_towers (void)
909 {
910   GSList *matches = NULL;
911
912   if (!(device->fix->fields & LOCATION_GPS_DEVICE_LATLONG_SET))
913     {
914       show_message ("I don't know where you are!");
915       return;
916     }
917
918   parse_dove (get_nearby_towers_cb,
919               &matches,
920               NULL);
921
922   show_towers_from_list (matches);
923 }
924
925 static void
926 towers_by_subarea (gchar *area)
927 {
928   GtkWidget *dialog = hildon_picker_dialog_new (GTK_WINDOW (window));
929   GtkWidget *selector = hildon_touch_selector_new_text ();
930   GHashTable *hash = g_hash_table_new_full (g_str_hash,
931                                             g_str_equal,
932                                             g_free,
933                                             g_free);
934   GSList *list=NULL, *cursor;
935   gchar *title = g_strdup_printf ("Areas of %s", area);
936   country_cb_data d = { hash, area };
937   country_and_county cac = { area, NULL };
938
939   gtk_window_set_title (GTK_WINDOW (dialog), title);
940   g_free (title);
941
942   parse_dove (get_counties_cb, NULL, &d);
943
944   g_hash_table_foreach (hash,
945                         put_areas_into_list,
946                         &list);
947
948   for (cursor=list; cursor; cursor=cursor->next)
949     {
950       hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector),
951                                          cursor->data);
952     }
953
954   hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (dialog),
955                                      HILDON_TOUCH_SELECTOR (selector));
956
957   gtk_widget_show_all (GTK_WIDGET (dialog));
958
959   if (gtk_dialog_run (GTK_DIALOG (dialog))==GTK_RESPONSE_OK)
960     {
961       GSList *matches = NULL;
962       cac.county = strdup (hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (selector)));
963
964       parse_dove (get_towers_by_county_cb,
965                   &matches,
966                   &cac);
967       g_free (cac.county);
968
969       show_towers_from_list (matches);
970     }
971   g_hash_table_unref (hash);
972   gtk_widget_destroy (GTK_WIDGET (dialog));
973 }
974
975 static void
976 towers_by_area (void)
977 {
978   GtkWidget *dialog = hildon_picker_dialog_new (GTK_WINDOW (window));
979   GtkWidget *selector = hildon_touch_selector_new_text ();
980   GHashTable *hash = g_hash_table_new_full (g_str_hash,
981                                             g_str_equal,
982                                             g_free,
983                                             g_free);
984   GSList *list = NULL, *cursor;
985   gchar *result = NULL;
986
987   gtk_window_set_title (GTK_WINDOW (dialog), "Areas of the world");
988
989   parse_dove (get_countries_cb, NULL, hash);
990
991   g_hash_table_foreach (hash,
992                         put_areas_into_list,
993                         &list);
994
995   for (cursor=list; cursor; cursor=cursor->next)
996     {
997       hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector),
998                                          cursor->data);
999     }
1000
1001   hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (dialog),
1002                                      HILDON_TOUCH_SELECTOR (selector));
1003
1004   gtk_widget_show_all (GTK_WIDGET (dialog));
1005
1006   if (gtk_dialog_run (GTK_DIALOG (dialog))==GTK_RESPONSE_OK)
1007     {
1008       result = g_strdup (hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (selector)));
1009     }
1010   g_hash_table_unref (hash);
1011   gtk_widget_destroy (GTK_WIDGET (dialog));
1012
1013   if (result)
1014     {
1015       towers_by_subarea (result);
1016       g_free (result);
1017     }
1018 }
1019
1020 static void
1021 show_bookmarks (void)
1022 {
1023   GSList *matches = NULL;
1024
1025   parse_dove (get_group_of_towers_cb,
1026               &matches,
1027               CONFIG_BOOKMARK_GROUP);
1028
1029   show_towers_from_list (matches);
1030 }
1031
1032 static void
1033 tower_search (void)
1034 {
1035   GtkWidget *terms = gtk_dialog_new_with_buttons ("What are you looking for?",
1036                                                   GTK_WINDOW (window),
1037                                                   GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
1038                                                   "Search",
1039                                                   GTK_RESPONSE_OK,
1040                                                   NULL);
1041   GtkWidget *entry = gtk_entry_new ();
1042   GSList *matches = NULL;
1043
1044   gtk_box_pack_end (GTK_BOX (GTK_DIALOG (terms)->vbox),
1045                     entry, TRUE, TRUE, 0);
1046
1047   gtk_widget_show_all (GTK_WIDGET (terms));
1048
1049   if (gtk_dialog_run (GTK_DIALOG (terms))==GTK_RESPONSE_OK)
1050     {
1051       GSList *matches = NULL;
1052
1053       parse_dove (get_towers_by_search_cb,
1054                   &matches,
1055                   (char*) gtk_entry_get_text (GTK_ENTRY (entry)));
1056
1057       show_towers_from_list (matches);
1058     }
1059
1060   gtk_widget_destroy (GTK_WIDGET (terms));
1061 }
1062
1063 static void
1064 recent_towers (void)
1065 {
1066   GSList *matches = NULL;
1067
1068   parse_dove (get_group_of_towers_cb,
1069               &matches,
1070               CONFIG_RECENT_GROUP);
1071
1072   show_towers_from_list (matches);
1073 }
1074
1075 /**
1076  * Displays a web page.
1077  * (Perhaps this should be merged with show_browser().)
1078  *
1079  * \param url  The URL.
1080  */
1081 static void
1082 show_web_page (GtkButton *dummy,
1083                gpointer url)
1084 {
1085   show_browser (url);
1086 }
1087
1088 /**
1089  * Shows the credits.
1090  *
1091  * \param source If non-null, we were called from a button press,
1092  *               so always show the credits.  If null, we were called
1093  *               automatically on startup, so show the credits if
1094  *               they haven't already been seen.
1095  */
1096 static void
1097 show_credits (GtkButton *source,
1098               gpointer dummy)
1099 {
1100   gboolean from_button = (source!=NULL);
1101   GtkWidget *dialog, *label, *button;
1102
1103   if (!from_button &&
1104       g_key_file_get_boolean (config,
1105                               CONFIG_GENERAL_GROUP,
1106                               CONFIG_SEEN_CREDITS_KEY,
1107                               NULL))
1108     {
1109       return;
1110     }
1111                               
1112
1113   dialog = gtk_dialog_new_with_buttons ("Credits",
1114                                         GTK_WINDOW (window),
1115                                         GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
1116                                         NULL
1117                                         );
1118
1119   button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
1120                                         HILDON_BUTTON_ARRANGEMENT_VERTICAL,
1121                                         "View the GNU General Public Licence",
1122                                         "This program is provided under the GPL, with no warranty.");
1123   g_signal_connect (button, "clicked", G_CALLBACK (show_web_page),
1124                     "www.gnu.org/copyleft/gpl.html");
1125   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1126                     button,
1127                     TRUE, TRUE, 0);
1128
1129   button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
1130                                         HILDON_BUTTON_ARRANGEMENT_VERTICAL,
1131                                         "View Dove's Guide for Church Bell Ringers",
1132                                         "The source of this program's data.");
1133   g_signal_connect (button, "clicked", G_CALLBACK (show_web_page),
1134                     "http://dove.cccbr.org.uk");
1135   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1136                     button,
1137                     TRUE, TRUE, 0);
1138
1139   button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
1140                                         HILDON_BUTTON_ARRANGEMENT_VERTICAL,
1141                                         "View belfry photograph",
1142                                         "Image \xc2\xa9 Amanda Slater, cc-by-sa.");
1143   g_signal_connect (button, "clicked", G_CALLBACK (show_web_page),
1144                     "http://www.flickr.com/photos/pikerslanefarm/3398769335/");
1145   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1146                     button,
1147                     TRUE, TRUE, 0);
1148   
1149   gtk_widget_show_all (GTK_WIDGET (dialog));
1150
1151   g_key_file_set_boolean (config,
1152                           CONFIG_GENERAL_GROUP,
1153                           CONFIG_SEEN_CREDITS_KEY,
1154                           TRUE);
1155   save_config ();
1156 }
1157
1158 int
1159 main(int argc, char **argv)
1160 {
1161   GtkWidget *bell, *button, *hbox;
1162   GdkPixbuf *bell_picture;
1163
1164   gtk_init (&argc, &argv);
1165   g_set_application_name ("Belltower");
1166
1167   device = g_object_new (LOCATION_TYPE_GPS_DEVICE, NULL);
1168
1169   window = hildon_stackable_window_new ();
1170   gtk_window_set_title (GTK_WINDOW (window), "Belltower");
1171   g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL);
1172
1173   bell_picture = gdk_pixbuf_new_from_file ("/usr/share/belltower/bells1.jpg", NULL);
1174
1175   buttons = gtk_vbox_new (TRUE, 0);
1176   menu = HILDON_APP_MENU (hildon_app_menu_new ());
1177
1178   add_button ("Nearby", nearby_towers);
1179   add_button ("Recent", recent_towers);
1180   add_button ("Bookmarks", show_bookmarks);
1181   add_button ("By area", towers_by_area);
1182   add_button ("Search", tower_search);
1183
1184   /* extra buttons for the app menu */
1185   button = gtk_button_new_with_label ("Credits");
1186   g_signal_connect (button, "clicked", G_CALLBACK (show_credits), NULL);
1187   hildon_app_menu_append (menu, GTK_BUTTON (button));
1188
1189   gtk_widget_show_all (GTK_WIDGET (menu));
1190   hildon_window_set_app_menu (HILDON_WINDOW (window), menu);
1191
1192   hbox = gtk_hbox_new (FALSE, 0);
1193   gtk_box_pack_end (GTK_BOX (hbox), buttons, TRUE, TRUE, 0);
1194   gtk_box_pack_end (GTK_BOX (hbox),
1195                     gtk_image_new_from_pixbuf (bell_picture),
1196                     TRUE, TRUE, 0);
1197
1198   gtk_container_add (GTK_CONTAINER (window), hbox);
1199   gtk_widget_show_all (GTK_WIDGET (window));
1200
1201   load_config ();
1202   show_credits (NULL, NULL);
1203
1204   gtk_main ();
1205
1206   return EXIT_SUCCESS;
1207 }