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