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