header row
[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 <glib.h>
12 #include <hildon/hildon.h>
13 #include <gtk/gtk.h>
14 #include <location/location-gps-device.h>
15 #include <dbus/dbus-glib.h>
16
17 #define MAX_FIELDS 50
18
19 #define EM_DASH "\xE2\x80\x94"
20
21 GtkWidget *window;
22
23 /*
24   FIXME:
25   We should really do this by looking at the header row of the table.
26   They might decide to put in new columns some day.
27 */
28 typedef enum {
29   FieldPrimaryKey,
30   FieldNationalGrid,
31   FieldAccRef,
32   FieldSNLat,
33   FieldSNLong,
34   FieldPostcode,
35   FieldTowerBase,
36   FieldCounty,
37   FieldCountry,
38   FieldDiocese,
39   FieldPlace,
40   FieldPlace2,
41   FieldPlaceCL,
42   FieldDedication,
43   FieldBells,
44   FieldWt,
45   FieldApp,
46   FieldNote,
47   FieldHz,
48   FieldDetails,
49   FieldGF,
50   FieldToilet,
51   FieldUR,
52   FieldPDNo,
53   FieldPracticeNight,
54   FieldPSt,
55   FieldPrXF,
56   FieldOvhaulYr,
57   FieldContractor,
58   FieldExtraInfo,
59   FieldWebPage,
60   FieldUpdated,
61   FieldAffiliations,
62   FieldAltName,
63   FieldLat,
64   FieldLong,
65   FieldSimulator
66 } field;
67
68 typedef struct {
69   int serial;
70
71   /* the raw data */
72
73   char* fields[MAX_FIELDS];
74   int n_fields;
75 } tower;
76
77 static void
78 show_message (char *message)
79 {
80   HildonNote* note = HILDON_NOTE
81     (hildon_note_new_information (GTK_WINDOW (window),
82                                   message?message:
83                                   "Some message was supposed to be here."));
84   gtk_dialog_run (GTK_DIALOG (note));
85   gtk_widget_destroy (GTK_WIDGET (note));
86 }
87
88 static void
89 call_dbus (DBusBusType type,
90            char *name,
91            char *path,
92            char *interface,
93            char *method,
94            char *parameter)
95 {
96   DBusGConnection *connection;
97   GError *error = NULL;
98
99   DBusGProxy *proxy;
100
101   connection = dbus_g_bus_get (type,
102                                &error);
103   if (connection == NULL)
104     {
105       show_message (error->message);
106       g_error_free (error);
107       return;
108     }
109
110   proxy = dbus_g_proxy_new_for_name (connection, name, path, interface);
111
112   error = NULL;
113   if (!dbus_g_proxy_call (proxy, method, &error,
114                           G_TYPE_STRING, parameter,
115                           G_TYPE_INVALID,
116                           G_TYPE_INVALID))
117     {
118       show_message (error->message);
119       g_error_free (error);
120     }
121 }
122
123 static void
124 show_browser (gchar *url)
125 {
126   call_dbus (DBUS_BUS_SESSION,
127              "com.nokia.osso_browser",
128              "/com/nokia/osso_browser/request",
129              "com.nokia.osso_browser",
130              "load_url",
131              url);
132 }
133
134 typedef gboolean (*ParseDoveCallback)(tower *details, gpointer data);
135 typedef void (*ButtonCallback)(void);
136
137 GtkWidget *tower_window, *buttons, *tower_table;
138 HildonAppMenu *menu;
139
140 static void
141 add_table_field (char *name,
142                  char *value)
143 {
144   int row;
145   GtkLabel *label;
146   gchar *str;
147
148   g_object_get(tower_table, "n-rows", &row);
149
150   row++;
151
152   gtk_table_resize (GTK_TABLE (tower_table), row, 2);
153
154   label = GTK_LABEL (gtk_label_new (NULL));
155   str = g_strdup_printf("<b>%s</b>", name);
156   gtk_label_set_markup (label, str);
157   g_free (str);
158   gtk_label_set_justify (label, GTK_JUSTIFY_RIGHT);
159   gtk_table_attach_defaults (GTK_TABLE (tower_table),
160                              GTK_WIDGET (label),
161                              0, 1, row, row+1);
162
163   label = GTK_LABEL (gtk_label_new (value));
164   gtk_label_set_justify (label, GTK_JUSTIFY_LEFT);
165   gtk_table_attach_defaults (GTK_TABLE (tower_table),
166                              GTK_WIDGET (label),
167                              1, 2, row, row+1);
168 }
169
170 static void
171 add_button (char *label,
172             ButtonCallback callback)
173 {
174   GtkWidget *button;
175
176   button = gtk_button_new_with_label (label);
177   g_signal_connect (button, "clicked", G_CALLBACK (callback), NULL);
178   hildon_app_menu_append (menu, GTK_BUTTON (button));
179   button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
180                                         HILDON_BUTTON_ARRANGEMENT_VERTICAL,
181                                         label, NULL);
182   g_signal_connect (button, "clicked", G_CALLBACK (callback), NULL);
183   gtk_box_pack_end (GTK_BOX (buttons), button, FALSE, FALSE, 0);
184 }
185
186
187 static void
188 bookmark_toggled (GtkButton *button,
189                   gpointer dummy)
190 {
191   show_message ("Bookmarks are not yet implemented.");
192 }
193
194 char *tower_website = NULL;
195 char *tower_map = NULL;
196 char *tower_directions = NULL;
197 char *peals_list = NULL;
198
199 static void
200 show_tower_website (void)
201 {
202   show_browser (tower_website);
203 }
204
205 static void
206 show_tower_map (void)
207 {
208   show_browser (tower_map);
209 }
210
211 static void
212 show_peals_list (void)
213 {
214   show_browser (peals_list);
215 }
216
217 static gboolean
218 get_areas_cb (tower *details,
219               gpointer data)
220 {
221   GHashTable *hash = (GHashTable *)data;
222
223   if (details->serial==0)
224     return TRUE; /* header row */
225
226   if (!g_hash_table_lookup_extended (hash,
227                                     details->fields[FieldCounty],
228                                      NULL, NULL))
229     {
230       char *display_format;
231
232       if (strcmp (details->fields[FieldCounty], "")==0)
233         {
234           display_format = g_strdup (details->fields[FieldCountry]);
235         }
236       else
237         {
238           display_format = g_strdup_printf ("%s " EM_DASH " %s",
239                                             details->fields[FieldCountry],
240                                             details->fields[FieldCounty]);
241         }
242
243       g_hash_table_insert (hash,
244                            g_strdup(details->fields[FieldCounty]),
245                            display_format);
246     }
247
248   return TRUE;
249 }
250
251 static gboolean
252 single_tower_cb (tower *details,
253                  gpointer data)
254 {
255
256   GtkWidget *hbox, *button;
257   gchar *str;
258   gint tenor_weight;
259   gchar *primary_key = (gchar*) data;
260
261   if (strcmp(details->fields[FieldPrimaryKey], primary_key)!=0)
262     {
263       /* not this one; keep going */
264       return TRUE;
265     }
266
267   tower_window = hildon_stackable_window_new ();
268
269   if (g_str_has_prefix (details->fields[FieldDedication],
270                         "S "))
271     {
272       /* FIXME: This needs to be cleverer, because we can have
273        * e.g. "S Peter and S Paul".
274        * May have to use regexps.
275        * Reallocation in general even when unchanged is okay,
276        * because it's the common case (most towers are S Something)
277        */
278       
279       /* FIXME: Since we're passing this in as markup,
280        * we need to escape the strings.
281        */
282
283       str = g_strdup_printf("S<sup>t</sup> %s, %s",
284                               details->fields[FieldDedication]+2,
285                               details->fields[FieldPlace]);
286
287     }
288   else
289     {
290       str = g_strdup_printf("%s, %s",
291                               details->fields[FieldDedication],
292                               details->fields[FieldPlace]);
293     }
294
295   hildon_window_set_markup (HILDON_WINDOW (tower_window),
296                             str);
297   g_free (str);
298
299   hbox = gtk_hbox_new (FALSE, 0);
300   tower_table = gtk_table_new (0, 2, FALSE);
301   buttons = gtk_vbox_new (TRUE, 0);
302   menu = HILDON_APP_MENU (hildon_app_menu_new ());
303
304   add_table_field ("Postcode", details->fields[FieldPostcode]);
305   add_table_field ("County", details->fields[FieldCounty]);
306   add_table_field ("Country", details->fields[FieldCountry]);
307   add_table_field ("Diocese", details->fields[FieldDiocese]);
308   add_table_field ("Practice night", details->fields[FieldPracticeNight]);
309   add_table_field ("Bells", details->fields[FieldBells]);
310
311   tenor_weight = atoi (details->fields[FieldWt]);
312   str = g_strdup_printf("%dcwt %dqr %dlb in %s",
313                         tenor_weight/112,
314                         (tenor_weight % 112)/28,
315                         tenor_weight % 28,
316                         details->fields[FieldNote]
317                         );
318   add_table_field ("Tenor", str);
319   g_free (str);
320
321   add_button ("Tower website", show_tower_website);
322   add_button ("Peals", show_peals_list);
323   add_button ("Map", show_tower_map);
324   add_button ("Directions", NULL);
325
326   /* don't use a toggle button: it looks stupid */
327   button = hildon_button_new_with_text (HILDON_SIZE_AUTO_WIDTH | HILDON_SIZE_FINGER_HEIGHT,
328                                         HILDON_BUTTON_ARRANGEMENT_VERTICAL,
329                                         "Bookmark", NULL);
330   g_signal_connect (button, "clicked", G_CALLBACK (bookmark_toggled), NULL);
331   gtk_box_pack_start (GTK_BOX (buttons), button, FALSE, FALSE, 0);
332
333   gtk_widget_show_all (GTK_WIDGET (menu));
334   hildon_window_set_app_menu (HILDON_WINDOW (tower_window), menu);
335
336   gtk_box_pack_end (GTK_BOX (hbox), buttons, TRUE, TRUE, 0);
337   gtk_box_pack_end (GTK_BOX (hbox), tower_table, TRUE, TRUE, 0);
338
339   gtk_container_add (GTK_CONTAINER (tower_window), hbox);
340
341   g_free (tower_website);
342   tower_website = g_strdup_printf ("http://%s", details->fields[FieldWebPage]);
343   g_free (peals_list);
344   peals_list = g_strdup_printf ("http://www.pealbase.ismysite.co.uk/felstead/tbid.php?tid=%s",
345        details->fields[FieldTowerBase]);
346   g_free (tower_map);
347   tower_map = g_strdup_printf ("http://maps.google.com/maps?q=%s,%s",
348         details->fields[FieldLat],
349         details->fields[FieldLong]);
350   gtk_widget_show_all (GTK_WIDGET (tower_window));
351
352   return FALSE;
353 }
354
355 static void
356 parse_dove (ParseDoveCallback callback,
357             gpointer data)
358 {
359   FILE *dove = fopen("/usr/share/belltower/dove.txt", "r");
360   char tower_rec[4096];
361   tower result;
362   char *i;
363   gboolean seen_newline;
364
365   if (!dove)
366     {
367       show_message ("Cannot open Dove database!");
368       exit (255);
369     }
370
371   result.serial = 0;
372
373   while (fgets (tower_rec, sizeof (tower_rec), dove))
374     {
375       seen_newline = FALSE;
376       result.fields[0] = tower_rec;
377       result.n_fields = 0;
378       for (i=tower_rec; *i; i++) {
379         if (*i=='\n')
380           {
381             seen_newline = TRUE;
382           }
383         if (*i=='\\' || *i=='\n')
384           {
385             *i = 0;
386             result.n_fields++;
387             result.fields[result.n_fields] = i+1;
388           }
389       }
390
391       if (!seen_newline)
392         {
393           /* keep it simple, stupid */
394           show_message ("Line too long, cannot continue.");
395           exit (255);
396         }
397
398       if (strcmp (result.fields[FieldCountry], "")==0)
399         {
400           result.fields[FieldCountry] = "England";
401         }
402
403       if (!callback (&result, data))
404         {
405           fclose (dove);
406           return;
407         }
408
409       result.serial++;
410     }
411
412   fclose (dove);
413 }
414
415 static void
416 nearby_towers (void)
417 {
418   char buffer[4096];
419   LocationGPSDevice *device;
420   device = g_object_new (LOCATION_TYPE_GPS_DEVICE, NULL);
421
422   sprintf(buffer, "%f %f %x",
423       device->fix->latitude,
424       device->fix->longitude,
425       device->fix->fields);
426   show_message (buffer);
427
428   if (device->fix->fields & LOCATION_GPS_DEVICE_LATLONG_SET)
429     {
430       show_message ("I know where you are!");
431     }
432   else
433     {
434       show_message ("I don't know where you are!");
435     }
436
437   g_object_unref (device);
438 }
439
440 static void
441 show_tower (char *primary_key)
442 {
443   parse_dove (single_tower_cb,
444               primary_key);
445 }
446
447 static void
448 put_areas_into_list (gpointer key,
449                      gpointer value,
450                      gpointer data)
451 {
452   GtkTreeIter iter;
453   GtkListStore *list_store = (GtkListStore*) data;
454   gtk_list_store_append (list_store, &iter);
455   gtk_list_store_set (list_store, &iter,
456                       0, value,
457                       -1);
458 }
459
460 static void
461 towers_by_area (void)
462 {
463   GtkWidget *dialog = gtk_dialog_new ();
464   GtkWidget *vbox = GTK_DIALOG(dialog)->vbox;
465   GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
466   GtkWidget *treeview = hildon_gtk_tree_view_new (HILDON_UI_MODE_NORMAL);
467   GtkWidget *pan = hildon_pannable_area_new ();
468   GtkListStore *list_store = gtk_list_store_new(1, G_TYPE_STRING);
469
470   GHashTable *hash = g_hash_table_new_full (g_str_hash,
471                                             g_str_equal,
472                                             g_free,
473                                             g_free);
474   parse_dove (get_areas_cb,
475               hash);
476
477   g_hash_table_foreach (hash,
478                         put_areas_into_list,
479                         list_store);      
480
481   /*  g_signal_connect (G_OBJECT (dialog), "delete_event", G_CALLBACK (g_hash_table_destroy), hash)*/
482
483   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_store),
484                                         0,
485                                         GTK_SORT_ASCENDING);
486
487   g_object_set (GTK_OBJECT(renderer), "yalign", 0.5, NULL);
488   g_object_set (GTK_OBJECT(renderer), "xpad", 24, NULL);
489
490   gtk_window_set_title (GTK_WINDOW (dialog), "Towers by area");
491
492   gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
493                                                0, "",
494                                                renderer,
495                                                "text", 0,
496                                                NULL);
497
498   gtk_tree_view_set_model (GTK_TREE_VIEW (treeview),
499                            GTK_TREE_MODEL (list_store));
500
501   g_object_unref (list_store);
502
503   hildon_pannable_area_add_with_viewport (HILDON_PANNABLE_AREA (pan),
504                                           treeview);
505
506   gtk_widget_set_size_request (treeview, 480, 800);
507   hildon_pannable_area_set_size_request_policy (HILDON_PANNABLE_AREA (pan),
508                                                 HILDON_SIZE_REQUEST_CHILDREN);
509   gtk_container_add(GTK_CONTAINER (vbox), pan);
510
511   gtk_widget_show_all (GTK_WIDGET (dialog));
512
513   gtk_dialog_run (GTK_DIALOG (dialog));
514   gtk_widget_destroy (GTK_WIDGET (dialog));
515   /* g_hash_table_unref (hash); */
516
517 }
518
519 static void
520 show_bookmarks (void)
521 {
522   /* nothing */
523 }
524
525 static void
526 tower_search (void)
527 {
528   /* nothing */
529 }
530
531 static void
532 recent_towers (void)
533 {
534   show_tower ("NORTON  HE");
535 }
536
537 int
538 main(int argc, char **argv)
539 {
540   GtkWidget *bell, *button, *hbox;
541   GdkPixbuf *bell_picture;
542
543   gtk_init (&argc, &argv);
544   g_set_application_name ("Belltower");
545
546   window = hildon_stackable_window_new ();
547   gtk_window_set_title (GTK_WINDOW (window), "Belltower");
548   g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL);
549
550   bell_picture = gdk_pixbuf_new_from_file ("/usr/share/belltower/bells1.jpg", NULL);
551
552   buttons = gtk_vbox_new (TRUE, 0);
553   menu = HILDON_APP_MENU (hildon_app_menu_new ());
554
555   add_button ("Nearby", nearby_towers);
556   add_button ("Recent", recent_towers);
557   add_button ("Bookmarks", show_bookmarks);
558   add_button ("By area", towers_by_area);
559   add_button ("Search", tower_search);
560
561   /* extra buttons for the app menu */
562   button = gtk_button_new_with_label ("Credits");
563   hildon_app_menu_append (menu, GTK_BUTTON (button));
564   hildon_app_menu_append (menu, GTK_BUTTON (button));
565
566   gtk_widget_show_all (GTK_WIDGET (menu));
567   hildon_window_set_app_menu (HILDON_WINDOW (window), menu);
568
569   hbox = gtk_hbox_new (FALSE, 0);
570   gtk_box_pack_end (GTK_BOX (hbox), buttons, TRUE, TRUE, 0);
571   gtk_box_pack_end (GTK_BOX (hbox),
572                     gtk_image_new_from_pixbuf (bell_picture),
573                     TRUE, TRUE, 0);
574
575   gtk_container_add (GTK_CONTAINER (window), hbox);
576   gtk_widget_show_all (GTK_WIDGET (window));
577
578   gtk_main ();
579
580   return EXIT_SUCCESS;
581 }