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