]> git.tdb.fi Git - ext/subsurface.git/blob - gtk-gui.c
Merge branch 'change_quit2' of http://ambre.pingoured.fr/cgit/subsurface
[ext/subsurface.git] / gtk-gui.c
1 /* gtk-gui.c */
2 /* gtk UI implementation */
3 /* creates the window and overall layout
4  * divelist, dive info, equipment and printing are handled in their own source files
5  */
6 #include <stdio.h>
7 #include <string.h>
8 #include <stdlib.h>
9 #include <time.h>
10 #include <unistd.h>
11
12 #include "dive.h"
13 #include "divelist.h"
14 #include "display.h"
15 #include "display-gtk.h"
16
17 #include "libdivecomputer.h"
18
19 GtkWidget *main_window;
20 GtkWidget *main_vbox;
21 GtkWidget *error_info_bar;
22 GtkWidget *error_label;
23 GtkWidget *vpane, *hpane;
24 int        error_count;
25
26 const char *divelist_font;
27
28 struct units output_units;
29
30 static GtkWidget *dive_profile;
31
32 visible_cols_t visible_cols = {TRUE, FALSE};
33
34 static const char *default_dive_computer_vendor;
35 static const char *default_dive_computer_product;
36 static const char *default_dive_computer_device;
37
38 static int is_default_dive_computer(const char *vendor, const char *product)
39 {
40         return default_dive_computer_vendor && !strcmp(vendor, default_dive_computer_vendor) &&
41                 default_dive_computer_product && !strcmp(product, default_dive_computer_product);
42 }
43
44 static int is_default_dive_computer_device(const char *name)
45 {
46         return default_dive_computer_device && !strcmp(name, default_dive_computer_device);
47 }
48
49 static void set_default_dive_computer(const char *vendor, const char *product)
50 {
51         if (!vendor || !*vendor)
52                 return;
53         if (!product || !*product)
54                 return;
55         if (is_default_dive_computer(vendor, product))
56                 return;
57         default_dive_computer_vendor = vendor;
58         default_dive_computer_product = product;
59         subsurface_set_conf("dive_computer_vendor", PREF_STRING, vendor);
60         subsurface_set_conf("dive_computer_product", PREF_STRING, product);
61 }
62
63 static void set_default_dive_computer_device(const char *name)
64 {
65         if (!name || !*name)
66                 return;
67         if (is_default_dive_computer_device(name))
68                 return;
69         default_dive_computer_device = name;
70         subsurface_set_conf("dive_computer_device", PREF_STRING, name);
71 }
72
73 void repaint_dive(void)
74 {
75         update_dive(current_dive);
76         if (dive_profile)
77                 gtk_widget_queue_draw(dive_profile);
78 }
79
80 static char *existing_filename;
81 static gboolean need_icon = TRUE;
82
83 static void on_info_bar_response(GtkWidget *widget, gint response,
84                                  gpointer data)
85 {
86         if (response == GTK_RESPONSE_OK)
87         {
88                 gtk_widget_destroy(widget);
89                 error_info_bar = NULL;
90         }
91 }
92
93 void report_error(GError* error)
94 {
95         if (error == NULL)
96         {
97                 return;
98         }
99         
100         if (error_info_bar == NULL)
101         {
102                 error_count = 1;
103                 error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
104                                                                GTK_RESPONSE_OK,
105                                                                NULL);
106                 g_signal_connect(error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
107                 gtk_info_bar_set_message_type(GTK_INFO_BAR(error_info_bar),
108                                               GTK_MESSAGE_ERROR);
109                 
110                 error_label = gtk_label_new(error->message);
111                 GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(error_info_bar));
112                 gtk_container_add(GTK_CONTAINER(container), error_label);
113                 
114                 gtk_box_pack_start(GTK_BOX(main_vbox), error_info_bar, FALSE, FALSE, 0);
115                 gtk_widget_show_all(main_vbox);
116         }
117         else
118         {
119                 error_count++;
120                 char buffer[256];
121                 snprintf(buffer, sizeof(buffer), "Failed to open %i files.", error_count);
122                 gtk_label_set(GTK_LABEL(error_label), buffer);
123         }
124 }
125
126 static void file_open(GtkWidget *w, gpointer data)
127 {
128         GtkWidget *dialog;
129         GtkFileFilter *filter;
130
131         dialog = gtk_file_chooser_dialog_new("Open File",
132                 GTK_WINDOW(main_window),
133                 GTK_FILE_CHOOSER_ACTION_OPEN,
134                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
135                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
136                 NULL);
137         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
138
139         filter = gtk_file_filter_new();
140         gtk_file_filter_add_pattern(filter, "*.xml");
141         gtk_file_filter_add_pattern(filter, "*.XML");
142         gtk_file_filter_add_pattern(filter, "*.sda");
143         gtk_file_filter_add_pattern(filter, "*.SDA");
144         gtk_file_filter_add_mime_type(filter, "text/xml");
145         gtk_file_filter_set_name(filter, "XML file");
146         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
147
148         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
149                 GSList *filenames, *fn_glist;
150                 char *filename;
151                 filenames = fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
152                 
153                 GError *error = NULL;
154                 while(filenames != NULL) {
155                         filename = filenames->data;
156                         parse_file(filename, &error);
157                         if (error != NULL)
158                         {
159                                 report_error(error);
160                                 g_error_free(error);
161                                 error = NULL;
162                         }
163                         
164                         g_free(filename);
165                         filenames = g_slist_next(filenames);
166                 }
167                 g_slist_free(fn_glist);
168                 report_dives(FALSE);
169         }
170         gtk_widget_destroy(dialog);
171 }
172
173 static void file_save(GtkWidget *w, gpointer data)
174 {
175         GtkWidget *dialog;
176         char *filename;
177         if (!existing_filename) {
178                 dialog = gtk_file_chooser_dialog_new("Save File",
179                 GTK_WINDOW(main_window),
180                 GTK_FILE_CHOOSER_ACTION_SAVE,
181                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
182                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
183                 NULL);
184                 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
185
186                 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
187                 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
188                         filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
189                 }
190                 gtk_widget_destroy(dialog);
191         } else {
192                 filename = existing_filename;
193         }
194         if (filename){
195                 save_dives(filename);
196                 mark_divelist_changed(FALSE);
197         }
198 }
199
200 static void file_save_as(GtkWidget *w, gpointer data)
201 {
202         GtkWidget *dialog;
203         char *filename;
204         dialog = gtk_file_chooser_dialog_new("Save File As",
205         GTK_WINDOW(main_window),
206         GTK_FILE_CHOOSER_ACTION_SAVE,
207         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
208         GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
209         NULL);
210         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
211
212         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), existing_filename);
213         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
214                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
215         }
216         gtk_widget_destroy(dialog);
217
218         if (filename){
219                 save_dives(filename);
220                 mark_divelist_changed(FALSE);
221         }
222 }
223
224 static gboolean ask_save_changes()
225 {
226         GtkWidget *dialog, *label, *content;
227         gboolean quit = TRUE;
228         dialog = gtk_dialog_new_with_buttons("Save Changes?",
229                 GTK_WINDOW(main_window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
230                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
231                 GTK_STOCK_NO, GTK_RESPONSE_NO,
232                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
233                 NULL);
234         content = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
235
236         if (!existing_filename){
237                 label = gtk_label_new (
238                         "You have unsaved changes\nWould you like to save those before exiting the program?");
239         } else {
240                 char *label_text = (char*) malloc(sizeof(char) * (92 + strlen(existing_filename)));
241                 sprintf(label_text,
242                         "You have unsaved changes to file: %s \nWould you like to save those before exiting the program?",
243                         existing_filename);
244                 label = gtk_label_new (label_text);
245                 g_free(label_text);
246         }
247         gtk_container_add (GTK_CONTAINER (content), label);
248         gtk_widget_show_all (dialog);
249         gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
250         gint *outcode = gtk_dialog_run(GTK_DIALOG(dialog));
251         if (outcode == GTK_RESPONSE_ACCEPT) {
252                 file_save(NULL,NULL);
253         } else if (outcode == GTK_RESPONSE_CANCEL) {
254                 quit = FALSE;
255         }
256         gtk_widget_destroy(dialog);
257         return quit;
258 }
259
260 static gboolean on_delete(GtkWidget* w, gpointer data)
261 {
262         /* Make sure to flush any modified dive data */
263         update_dive(NULL);
264
265         gboolean quit = TRUE;
266         if (unsaved_changes())
267                 quit = ask_save_changes();
268
269         if (quit){
270                 return FALSE; /* go ahead, kill the program, we're good now */
271         } else {
272                 return TRUE; /* We are not leaving */
273         }
274 }
275
276 static void on_destroy(GtkWidget* w, gpointer data)
277 {
278         gtk_main_quit();
279 }
280
281 static void quit(GtkWidget *w, gpointer data)
282 {
283         /* Make sure to flush any modified dive data */
284         update_dive(NULL);
285
286         gboolean quit = TRUE;
287         if (unsaved_changes())
288                 quit = ask_save_changes();
289
290         if (quit){
291                 gtk_main_quit();
292         }
293 }
294
295 GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title,
296                                 data_func_t data_func, unsigned int flags)
297 {
298         GtkCellRenderer *renderer;
299         GtkTreeViewColumn *col;
300         double xalign = 0.0; /* left as default */
301         PangoAlignment align;
302         gboolean visible;
303
304         align = (flags & ALIGN_LEFT) ? PANGO_ALIGN_LEFT :
305                 (flags & ALIGN_RIGHT) ? PANGO_ALIGN_RIGHT :
306                 PANGO_ALIGN_CENTER;
307         visible = !(flags & INVISIBLE);
308
309         renderer = gtk_cell_renderer_text_new();
310         col = gtk_tree_view_column_new();
311
312         gtk_tree_view_column_set_title(col, title);
313         if (!(flags & UNSORTABLE))
314                 gtk_tree_view_column_set_sort_column_id(col, index);
315         gtk_tree_view_column_set_resizable(col, TRUE);
316         gtk_tree_view_column_pack_start(col, renderer, TRUE);
317         if (data_func)
318                 gtk_tree_view_column_set_cell_data_func(col, renderer, data_func, (void *)(long)index, NULL);
319         else
320                 gtk_tree_view_column_add_attribute(col, renderer, "text", index);
321         gtk_object_set(GTK_OBJECT(renderer), "alignment", align, NULL);
322         switch (align) {
323         case PANGO_ALIGN_LEFT:
324                 xalign = 0.0;
325                 break;
326         case PANGO_ALIGN_CENTER:
327                 xalign = 0.5;
328                 break;
329         case PANGO_ALIGN_RIGHT:
330                 xalign = 1.0;
331                 break;
332         }
333         gtk_cell_renderer_set_alignment(GTK_CELL_RENDERER(renderer), xalign, 0.5);
334         gtk_tree_view_column_set_visible(col, visible);
335         gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), col);
336         return col;
337 }
338
339 static void create_radio(GtkWidget *vbox, const char *w_name, ...)
340 {
341         va_list args;
342         GtkRadioButton *group = NULL;
343         GtkWidget *box, *label;
344
345         box = gtk_hbox_new(TRUE, 10);
346         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 0);
347
348         label = gtk_label_new(w_name);
349         gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0);
350
351         va_start(args, w_name);
352         for (;;) {
353                 int enabled;
354                 const char *name;
355                 GtkWidget *button;
356                 void *callback_fn;
357
358                 name = va_arg(args, char *);
359                 if (!name)
360                         break;
361                 callback_fn = va_arg(args, void *);
362                 enabled = va_arg(args, int);
363
364                 button = gtk_radio_button_new_with_label_from_widget(group, name);
365                 group = GTK_RADIO_BUTTON(button);
366                 gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
367                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), enabled);
368                 g_signal_connect(button, "toggled", G_CALLBACK(callback_fn), NULL);
369         }
370         va_end(args);
371 }
372
373 #define UNITCALLBACK(name, type, value)                         \
374 static void name(GtkWidget *w, gpointer data)                   \
375 {                                                               \
376         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w))) \
377                 menu_units.type = value;                        \
378 }
379
380 static struct units menu_units;
381
382 UNITCALLBACK(set_meter, length, METERS)
383 UNITCALLBACK(set_feet, length, FEET)
384 UNITCALLBACK(set_bar, pressure, BAR)
385 UNITCALLBACK(set_psi, pressure, PSI)
386 UNITCALLBACK(set_liter, volume, LITER)
387 UNITCALLBACK(set_cuft, volume, CUFT)
388 UNITCALLBACK(set_celsius, temperature, CELSIUS)
389 UNITCALLBACK(set_fahrenheit, temperature, FAHRENHEIT)
390 UNITCALLBACK(set_kg, weight, KG)
391 UNITCALLBACK(set_lbs, weight, LBS)
392
393 #define OPTIONCALLBACK(name, option) \
394 static void name(GtkWidget *w, gpointer data) \
395 { \
396         option = GTK_TOGGLE_BUTTON(w)->active; \
397 }
398
399 OPTIONCALLBACK(otu_toggle, visible_cols.otu)
400 OPTIONCALLBACK(sac_toggle, visible_cols.sac)
401 OPTIONCALLBACK(nitrox_toggle, visible_cols.nitrox)
402 OPTIONCALLBACK(temperature_toggle, visible_cols.temperature)
403 OPTIONCALLBACK(totalweight_toggle, visible_cols.totalweight)
404 OPTIONCALLBACK(suit_toggle, visible_cols.suit)
405 OPTIONCALLBACK(cylinder_toggle, visible_cols.cylinder)
406
407 static void event_toggle(GtkWidget *w, gpointer _data)
408 {
409         gboolean *plot_ev = _data;
410
411         *plot_ev = GTK_TOGGLE_BUTTON(w)->active;
412 }
413
414 static void preferences_dialog(GtkWidget *w, gpointer data)
415 {
416         int result;
417         GtkWidget *dialog, *font, *frame, *box, *vbox, *button;
418
419         menu_units = output_units;
420
421         dialog = gtk_dialog_new_with_buttons("Preferences",
422                 GTK_WINDOW(main_window),
423                 GTK_DIALOG_DESTROY_WITH_PARENT,
424                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
425                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
426                 NULL);
427
428         frame = gtk_frame_new("Units");
429         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
430         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
431
432         box = gtk_vbox_new(FALSE, 6);
433         gtk_container_add(GTK_CONTAINER(frame), box);
434
435         create_radio(box, "Depth:",
436                 "Meter", set_meter, (output_units.length == METERS),
437                 "Feet",  set_feet, (output_units.length == FEET),
438                 NULL);
439
440         create_radio(box, "Pressure:",
441                 "Bar", set_bar, (output_units.pressure == BAR),
442                 "PSI",  set_psi, (output_units.pressure == PSI),
443                 NULL);
444
445         create_radio(box, "Volume:",
446                 "Liter",  set_liter, (output_units.volume == LITER),
447                 "CuFt", set_cuft, (output_units.volume == CUFT),
448                 NULL);
449
450         create_radio(box, "Temperature:",
451                 "Celsius", set_celsius, (output_units.temperature == CELSIUS),
452                 "Fahrenheit",  set_fahrenheit, (output_units.temperature == FAHRENHEIT),
453                 NULL);
454
455         create_radio(box, "Weight:",
456                 "kg", set_kg, (output_units.weight == KG),
457                 "lbs",  set_lbs, (output_units.weight == LBS),
458                 NULL);
459
460         frame = gtk_frame_new("Show Columns");
461         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
462
463         box = gtk_hbox_new(FALSE, 6);
464         gtk_container_add(GTK_CONTAINER(frame), box);
465
466         button = gtk_check_button_new_with_label("Temp");
467         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.temperature);
468         gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6);
469         g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(temperature_toggle), NULL);
470
471         button = gtk_check_button_new_with_label("Cyl");
472         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.cylinder);
473         gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6);
474         g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(cylinder_toggle), NULL);
475
476         button = gtk_check_button_new_with_label("O" UTF8_SUBSCRIPT_2 "%");
477         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.nitrox);
478         gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6);
479         g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(nitrox_toggle), NULL);
480
481         button = gtk_check_button_new_with_label("SAC");
482         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.sac);
483         gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6);
484         g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(sac_toggle), NULL);
485
486         button = gtk_check_button_new_with_label("OTU");
487         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.otu);
488         gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6);
489         g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(otu_toggle), NULL);
490
491         button = gtk_check_button_new_with_label("Weight");
492         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.totalweight);
493         gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6);
494         g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(totalweight_toggle), NULL);
495
496         button = gtk_check_button_new_with_label("Suit");
497         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.suit);
498         gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6);
499         g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(suit_toggle), NULL);
500
501         font = gtk_font_button_new_with_font(divelist_font);
502         gtk_box_pack_start(GTK_BOX(vbox), font, FALSE, FALSE, 5);
503
504         gtk_widget_show_all(dialog);
505         result = gtk_dialog_run(GTK_DIALOG(dialog));
506         if (result == GTK_RESPONSE_ACCEPT) {
507                 /* Make sure to flush any modified old dive data with old units */
508                 update_dive(NULL);
509
510                 divelist_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font)));
511                 set_divelist_font(divelist_font);
512
513                 output_units = menu_units;
514                 update_dive_list_units();
515                 repaint_dive();
516                 update_dive_list_col_visibility();
517
518                 subsurface_set_conf("feet", PREF_BOOL, BOOL_TO_PTR(output_units.length == FEET));
519                 subsurface_set_conf("psi", PREF_BOOL, BOOL_TO_PTR(output_units.pressure == PSI));
520                 subsurface_set_conf("cuft", PREF_BOOL, BOOL_TO_PTR(output_units.volume == CUFT));
521                 subsurface_set_conf("fahrenheit", PREF_BOOL, BOOL_TO_PTR(output_units.temperature == FAHRENHEIT));
522                 subsurface_set_conf("lbs", PREF_BOOL, BOOL_TO_PTR(output_units.weight == LBS));
523                 subsurface_set_conf("TEMPERATURE", PREF_BOOL, BOOL_TO_PTR(visible_cols.temperature));
524                 subsurface_set_conf("TOTALWEIGHT", PREF_BOOL, BOOL_TO_PTR(visible_cols.totalweight));
525                 subsurface_set_conf("SUIT", PREF_BOOL, BOOL_TO_PTR(visible_cols.suit));
526                 subsurface_set_conf("CYLINDER", PREF_BOOL, BOOL_TO_PTR(visible_cols.cylinder));
527                 subsurface_set_conf("NITROX", PREF_BOOL, BOOL_TO_PTR(visible_cols.nitrox));
528                 subsurface_set_conf("SAC", PREF_BOOL, BOOL_TO_PTR(visible_cols.sac));
529                 subsurface_set_conf("OTU", PREF_BOOL, BOOL_TO_PTR(visible_cols.otu));
530                 subsurface_set_conf("divelist_font", PREF_STRING, divelist_font);
531
532                 /* Flush the changes out to the system */
533                 subsurface_flush_conf();
534         }
535         gtk_widget_destroy(dialog);
536 }
537
538 static void create_toggle(const char* label, int *on, void *_data)
539 {
540         GtkWidget *button, *table = _data;
541         int rows, cols, x, y;
542         static int count;
543
544         if (table == NULL) {
545                 /* magic way to reset the number of toggle buttons
546                  * that we have already added - call this before you
547                  * create the dialog */
548                 count = 0;
549                 return;
550         }
551         g_object_get(G_OBJECT(table), "n-columns", &cols, "n-rows", &rows, NULL);
552         if (count > rows * cols) {
553                 gtk_table_resize(GTK_TABLE(table),rows+1,cols);
554                 rows++;
555         }
556         x = count % cols;
557         y = count / cols;
558         button = gtk_check_button_new_with_label(label);
559         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), *on);
560         gtk_table_attach_defaults(GTK_TABLE(table), button, x, x+1, y, y+1);
561         g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(event_toggle), on);
562         count++;
563 }
564
565 static void selectevents_dialog(GtkWidget *w, gpointer data)
566 {
567         int result;
568         GtkWidget *dialog, *frame, *vbox, *table;
569
570         dialog = gtk_dialog_new_with_buttons("SelectEvents",
571                 GTK_WINDOW(main_window),
572                 GTK_DIALOG_DESTROY_WITH_PARENT,
573                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
574                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
575                 NULL);
576         /* initialize the function that fills the table */
577         create_toggle(NULL, NULL, NULL);
578
579         frame = gtk_frame_new("Enable / Disable Events");
580         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
581         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
582
583         table = gtk_table_new(1, 4, TRUE);
584         gtk_container_add(GTK_CONTAINER(frame), table);
585
586         evn_foreach(&create_toggle, table);
587
588         gtk_widget_show_all(dialog);
589         result = gtk_dialog_run(GTK_DIALOG(dialog));
590         if (result == GTK_RESPONSE_ACCEPT) {
591                 repaint_dive();
592         }
593         gtk_widget_destroy(dialog);
594 }
595
596 static void renumber_dialog(GtkWidget *w, gpointer data)
597 {
598         int result;
599         struct dive *dive;
600         GtkWidget *dialog, *frame, *button, *vbox;
601
602         dialog = gtk_dialog_new_with_buttons("Renumber",
603                 GTK_WINDOW(main_window),
604                 GTK_DIALOG_DESTROY_WITH_PARENT,
605                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
606                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
607                 NULL);
608
609         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
610
611         frame = gtk_frame_new("New starting number");
612         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
613
614         button = gtk_spin_button_new_with_range(1, 50000, 1);
615         gtk_container_add(GTK_CONTAINER(frame), button);
616
617         /*
618          * Do we have a number for the first dive already? Use that
619          * as the default.
620          */
621         dive = get_dive(0);
622         if (dive && dive->number)
623                 gtk_spin_button_set_value(GTK_SPIN_BUTTON(button), dive->number);
624
625         gtk_widget_show_all(dialog);
626         result = gtk_dialog_run(GTK_DIALOG(dialog));
627         if (result == GTK_RESPONSE_ACCEPT) {
628                 int nr = gtk_spin_button_get_value(GTK_SPIN_BUTTON(button));
629                 renumber_dives(nr);
630                 repaint_dive();
631         }
632         gtk_widget_destroy(dialog);
633 }
634
635 static void about_dialog(GtkWidget *w, gpointer data)
636 {
637         const char *logo_property = NULL;
638         GdkPixbuf *logo = NULL;
639
640         if (need_icon) {
641                 GtkWidget *image = gtk_image_new_from_file(subsurface_icon_name());
642
643                 if (image) {
644                         logo = gtk_image_get_pixbuf(GTK_IMAGE(image));
645                         logo_property = "logo";
646                 }
647         }
648
649         gtk_show_about_dialog(NULL,
650                 "program-name", "SubSurface",
651                 "comments", "Half-arsed divelog software in C",
652                 "license", "GPLv2",
653                 "version", VERSION_STRING,
654                 "copyright", "Linus Torvalds 2011",
655                 "logo-icon-name", "subsurface",
656                 /* Must be last: */
657                 logo_property, logo,
658                 NULL);
659 }
660
661 static void view_list(GtkWidget *w, gpointer data)
662 {
663         gtk_paned_set_position(GTK_PANED(vpane), 0);
664 }
665
666 static void view_profile(GtkWidget *w, gpointer data)
667 {
668         gtk_paned_set_position(GTK_PANED(hpane), 0);
669         gtk_paned_set_position(GTK_PANED(vpane), 65535);
670 }
671
672 static void view_info(GtkWidget *w, gpointer data)
673 {
674         gtk_paned_set_position(GTK_PANED(vpane), 65535);
675         gtk_paned_set_position(GTK_PANED(hpane), 65535);
676 }
677
678 /* Ooh. I don't know how to get the half-way size. So I'm just using random numbers */
679 static void view_three(GtkWidget *w, gpointer data)
680 {
681         gtk_paned_set_position(GTK_PANED(hpane), 400);
682         gtk_paned_set_position(GTK_PANED(vpane), 200);
683 }
684
685 static GtkActionEntry menu_items[] = {
686         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
687         { "LogMenuAction",  GTK_STOCK_FILE, "Log", NULL, NULL, NULL},
688         { "ViewMenuAction",  GTK_STOCK_FILE, "View", NULL, NULL, NULL},
689         { "FilterMenuAction",  GTK_STOCK_FILE, "Filter", NULL, NULL, NULL},
690         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
691         { "OpenFile",       GTK_STOCK_OPEN, NULL,   CTRLCHAR "O", NULL, G_CALLBACK(file_open) },
692         { "SaveFile",       GTK_STOCK_SAVE, NULL,   CTRLCHAR "S", NULL, G_CALLBACK(file_save) },
693         { "SaveAsFile",     GTK_STOCK_SAVE_AS, NULL,   SHIFTCHAR CTRLCHAR "S", NULL, G_CALLBACK(file_save_as) },
694         { "Print",          GTK_STOCK_PRINT, NULL,  CTRLCHAR "P", NULL, G_CALLBACK(do_print) },
695         { "Import",         NULL, "Import", NULL, NULL, G_CALLBACK(import_dialog) },
696         { "AddDive",        NULL, "Add Dive", NULL, NULL, G_CALLBACK(add_dive_cb) },
697         { "Preferences",    NULL, "Preferences", PREFERENCE_ACCEL, NULL, G_CALLBACK(preferences_dialog) },
698         { "Renumber",       NULL, "Renumber", NULL, NULL, G_CALLBACK(renumber_dialog) },
699         { "SelectEvents",   NULL, "SelectEvents", NULL, NULL, G_CALLBACK(selectevents_dialog) },
700         { "Quit",           GTK_STOCK_QUIT, NULL,   CTRLCHAR "Q", NULL, G_CALLBACK(quit) },
701         { "About",          GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
702         { "ViewList",       NULL, "List",  CTRLCHAR "1", NULL, G_CALLBACK(view_list) },
703         { "ViewProfile",    NULL, "Profile", CTRLCHAR "2", NULL, G_CALLBACK(view_profile) },
704         { "ViewInfo",       NULL, "Info", CTRLCHAR "3", NULL, G_CALLBACK(view_info) },
705         { "ViewThree",       NULL, "Three", CTRLCHAR "4", NULL, G_CALLBACK(view_three) },
706 };
707 static gint nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]);
708
709 static const gchar* ui_string = " \
710         <ui> \
711                 <menubar name=\"MainMenu\"> \
712                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
713                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
714                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
715                                 <menuitem name=\"Save As\" action=\"SaveAsFile\" /> \
716                                 <menuitem name=\"Print\" action=\"Print\" /> \
717                                 <separator name=\"Separator1\"/> \
718                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
719                                 <separator name=\"Separator2\"/> \
720                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
721                         </menu> \
722                         <menu name=\"LogMenu\" action=\"LogMenuAction\"> \
723                                 <menuitem name=\"Import\" action=\"Import\" /> \
724                                 <menuitem name=\"Add Dive\" action=\"AddDive\" /> \
725                                 <separator name=\"Separator\"/> \
726                                 <menuitem name=\"Renumber\" action=\"Renumber\" /> \
727                                 <menu name=\"View\" action=\"ViewMenuAction\"> \
728                                         <menuitem name=\"List\" action=\"ViewList\" /> \
729                                         <menuitem name=\"Profile\" action=\"ViewProfile\" /> \
730                                         <menuitem name=\"Info\" action=\"ViewInfo\" /> \
731                                         <menuitem name=\"Paned\" action=\"ViewThree\" /> \
732                                 </menu> \
733                         </menu> \
734                         <menu name=\"FilterMenu\" action=\"FilterMenuAction\"> \
735                                 <menuitem name=\"SelectEvents\" action=\"SelectEvents\" /> \
736                         </menu> \
737                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
738                                 <menuitem name=\"About\" action=\"About\" /> \
739                         </menu> \
740                 </menubar> \
741         </ui> \
742 ";
743
744 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager)
745 {
746         GtkActionGroup *action_group = gtk_action_group_new("Menu");
747         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0);
748
749         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
750         GError* error = 0;
751         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
752
753         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
754         GtkWidget* menu = gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
755
756         return menu;
757 }
758
759 static void switch_page(GtkNotebook *notebook, gint arg1, gpointer user_data)
760 {
761         repaint_dive();
762 }
763
764 void init_ui(int *argcp, char ***argvp)
765 {
766         GtkWidget *win;
767         GtkWidget *notebook;
768         GtkWidget *nb_page;
769         GtkWidget *dive_list;
770         GtkWidget *menubar;
771         GtkWidget *vbox;
772         GdkScreen *screen;
773         GtkIconTheme *icon_theme=NULL;
774         GtkSettings *settings;
775         GtkUIManager *ui_manager;
776
777         gtk_init(argcp, argvp);
778         settings = gtk_settings_get_default();
779         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "subsurface setting");
780
781         g_type_init();
782
783         subsurface_open_conf();
784         if (subsurface_get_conf("feet", PREF_BOOL))
785                 output_units.length = FEET;
786         if (subsurface_get_conf("psi", PREF_BOOL))
787                 output_units.pressure = PSI;
788         if (subsurface_get_conf("cuft", PREF_BOOL))
789                 output_units.volume = CUFT;
790         if (subsurface_get_conf("fahrenheit", PREF_BOOL))
791                 output_units.temperature = FAHRENHEIT;
792         if (subsurface_get_conf("lbs", PREF_BOOL))
793                 output_units.weight = LBS;
794         /* an unset key is FALSE - all these are hidden by default */
795         visible_cols.cylinder = PTR_TO_BOOL(subsurface_get_conf("CYLINDER", PREF_BOOL));
796         visible_cols.temperature = PTR_TO_BOOL(subsurface_get_conf("TEMPERATURE", PREF_BOOL));
797         visible_cols.totalweight = PTR_TO_BOOL(subsurface_get_conf("TOTALWEIGHT", PREF_BOOL));
798         visible_cols.suit = PTR_TO_BOOL(subsurface_get_conf("SUIT", PREF_BOOL));
799         visible_cols.nitrox = PTR_TO_BOOL(subsurface_get_conf("NITROX", PREF_BOOL));
800         visible_cols.otu = PTR_TO_BOOL(subsurface_get_conf("OTU", PREF_BOOL));
801         visible_cols.sac = PTR_TO_BOOL(subsurface_get_conf("SAC", PREF_BOOL));
802
803         divelist_font = subsurface_get_conf("divelist_font", PREF_STRING);
804
805         default_dive_computer_vendor = subsurface_get_conf("dive_computer_vendor", PREF_STRING);
806         default_dive_computer_product = subsurface_get_conf("dive_computer_product", PREF_STRING);
807         default_dive_computer_device = subsurface_get_conf("dive_computer_device", PREF_STRING);
808
809         error_info_bar = NULL;
810         win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
811         g_set_application_name ("subsurface");
812         /* Let's check if the subsurface icon has been installed or if
813          * we need to try to load it from the current directory */
814         screen = gdk_screen_get_default();
815         if (screen)
816                 icon_theme = gtk_icon_theme_get_for_screen(screen);
817         if (icon_theme) {
818                 if (gtk_icon_theme_has_icon(icon_theme, "subsurface")) {
819                         need_icon = FALSE;
820                         gtk_window_set_default_icon_name ("subsurface");
821                 }
822         }
823         if (need_icon) {
824                 const char *icon_name = subsurface_icon_name();
825                 if (!access(icon_name, R_OK))
826                         gtk_window_set_icon_from_file(GTK_WINDOW(win), icon_name, NULL);
827         }
828         g_signal_connect(G_OBJECT(win), "delete-event", G_CALLBACK(on_delete), NULL);
829         g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(on_destroy), NULL);
830         main_window = win;
831
832         vbox = gtk_vbox_new(FALSE, 0);
833         gtk_container_add(GTK_CONTAINER(win), vbox);
834         main_vbox = vbox;
835
836         ui_manager = gtk_ui_manager_new();
837         menubar = get_menubar_menu(win, ui_manager);
838
839         subsurface_ui_setup(settings, menubar, vbox, ui_manager);
840
841         vpane = gtk_vpaned_new();
842         gtk_box_pack_start(GTK_BOX(vbox), vpane, TRUE, TRUE, 3);
843
844         hpane = gtk_hpaned_new();
845         gtk_paned_add1(GTK_PANED(vpane), hpane);
846
847         /* Notebook for dive info vs profile vs .. */
848         notebook = gtk_notebook_new();
849         gtk_paned_add1(GTK_PANED(hpane), notebook);
850         g_signal_connect(notebook, "switch-page", G_CALLBACK(switch_page), NULL);
851
852         /* Create the actual divelist */
853         dive_list = dive_list_create();
854         gtk_widget_set_name(dive_list, "Dive List");
855         gtk_paned_add2(GTK_PANED(vpane), dive_list);
856
857         /* Frame for dive profile */
858         dive_profile = dive_profile_widget();
859         gtk_widget_set_name(dive_profile, "Dive Profile");
860         gtk_paned_add2(GTK_PANED(hpane), dive_profile);
861
862         /* Frame for extended dive info */
863         nb_page = extended_dive_info_widget();
864         gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new("Dive Notes"));
865
866         /* Frame for dive equipment */
867         nb_page = equipment_widget(W_IDX_PRIMARY);
868         gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new("Equipment"));
869
870         /* Frame for single dive statistics */
871         nb_page = single_stats_widget();
872         gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new("Dive Info"));
873
874         /* Frame for total dive statistics */
875         nb_page = total_stats_widget();
876         gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new("Stats"));
877
878         gtk_widget_set_app_paintable(win, TRUE);
879         gtk_widget_show_all(win);
880
881         return;
882 }
883
884 void run_ui(void)
885 {
886         gtk_main();
887 }
888
889 void exit_ui(void)
890 {
891         subsurface_close_conf();
892 }
893
894 typedef struct {
895         cairo_rectangle_int_t rect;
896         const char *text;
897 } tooltip_record_t;
898
899 static tooltip_record_t *tooltip_rects;
900 static int tooltips;
901
902 void attach_tooltip(int x, int y, int w, int h, const char *text)
903 {
904         cairo_rectangle_int_t *rect;
905         tooltip_rects = realloc(tooltip_rects, (tooltips + 1) * sizeof(tooltip_record_t));
906         rect = &tooltip_rects[tooltips].rect;
907         rect->x = x;
908         rect->y = y;
909         rect->width = w;
910         rect->height = h;
911         tooltip_rects[tooltips].text = text;
912         tooltips++;
913 }
914
915 #define INSIDE_RECT(_r,_x,_y)   ((_r.x <= _x) && (_r.x + _r.width >= _x) && \
916                                 (_r.y <= _y) && (_r.y + _r.height >= _y))
917
918 static gboolean profile_tooltip (GtkWidget *widget, gint x, gint y,
919                         gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data)
920 {
921         int i;
922         cairo_rectangle_int_t *drawing_area = user_data;
923         gint tx = x - drawing_area->x; /* get transformed coordinates */
924         gint ty = y - drawing_area->y;
925
926         /* are we over an event marker ? */
927         for (i = 0; i < tooltips; i++) {
928                 if (INSIDE_RECT(tooltip_rects[i].rect, tx, ty)) {
929                         gtk_tooltip_set_text(tooltip,tooltip_rects[i].text);
930                         return TRUE; /* show tooltip */
931                 }
932         }
933         return FALSE; /* don't show tooltip */
934 }
935
936 static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
937 {
938         struct dive *dive = current_dive;
939         struct graphics_context gc = { .printer = 0 };
940         static cairo_rectangle_int_t drawing_area;
941
942         /* the drawing area gives TOTAL width * height - x,y is used as the topx/topy offset
943          * so effective drawing area is width-2x * height-2y */
944         drawing_area.width = widget->allocation.width;
945         drawing_area.height = widget->allocation.height;
946         drawing_area.x = drawing_area.width / 20.0;
947         drawing_area.y = drawing_area.height / 20.0;
948
949         gc.cr = gdk_cairo_create(widget->window);
950         g_object_set(widget, "has-tooltip", TRUE, NULL);
951         g_signal_connect(widget, "query-tooltip", G_CALLBACK(profile_tooltip), &drawing_area);
952         init_profile_background(&gc);
953         cairo_paint(gc.cr);
954
955         if (dive) {
956                 if (tooltip_rects) {
957                         free(tooltip_rects);
958                         tooltip_rects = NULL;
959                 }
960                 tooltips = 0;
961                 plot(&gc, &drawing_area, dive);
962         }
963         cairo_destroy(gc.cr);
964
965         return FALSE;
966 }
967
968 GtkWidget *dive_profile_widget(void)
969 {
970         GtkWidget *da;
971
972         da = gtk_drawing_area_new();
973         gtk_widget_set_size_request(da, 350, 250);
974         g_signal_connect(da, "expose_event", G_CALLBACK(expose_event), NULL);
975
976         return da;
977 }
978
979 int process_ui_events(void)
980 {
981         int ret=0;
982
983         while (gtk_events_pending()) {
984                 if (gtk_main_iteration_do(0)) {
985                         ret = 1;
986                         break;
987                 }
988         }
989         return ret;
990 }
991
992 static int fill_computer_list(GtkListStore *store)
993 {
994         int index = -1, i;
995         GtkTreeIter iter;
996         dc_iterator_t *iterator = NULL;
997         dc_descriptor_t *descriptor = NULL;
998
999         i = 0;
1000         dc_descriptor_iterator(&iterator);
1001         while (dc_iterator_next (iterator, &descriptor) == DC_STATUS_SUCCESS) {
1002                 const char *vendor = dc_descriptor_get_vendor(descriptor);
1003                 const char *product = dc_descriptor_get_product(descriptor);
1004
1005                 gtk_list_store_append(store, &iter);
1006                 gtk_list_store_set(store, &iter,
1007                         0, descriptor,
1008                         -1);
1009                 if (is_default_dive_computer(vendor, product))
1010                         index = i;
1011                 i++;
1012         }
1013         dc_iterator_free(iterator);
1014         return index;
1015 }
1016
1017 void render_dive_computer(GtkCellLayout *cell,
1018                 GtkCellRenderer *renderer,
1019                 GtkTreeModel *model,
1020                 GtkTreeIter *iter,
1021                 gpointer data)
1022 {
1023         char buffer[40];
1024         dc_descriptor_t *descriptor = NULL;
1025         const char *vendor, *product;
1026
1027         gtk_tree_model_get(model, iter, 0, &descriptor, -1);
1028         vendor = dc_descriptor_get_vendor(descriptor);
1029         product = dc_descriptor_get_product(descriptor);
1030         snprintf(buffer, sizeof(buffer), "%s %s", vendor, product);
1031         g_object_set(renderer, "text", buffer, NULL);
1032 }
1033
1034
1035 static GtkComboBox *dive_computer_selector(GtkWidget *vbox)
1036 {
1037         GtkWidget *hbox, *combo_box, *frame;
1038         GtkListStore *model;
1039         GtkCellRenderer *renderer;
1040         int default_index;
1041
1042         hbox = gtk_hbox_new(FALSE, 6);
1043         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
1044
1045         model = gtk_list_store_new(1, G_TYPE_POINTER);
1046         default_index = fill_computer_list(model);
1047
1048         frame = gtk_frame_new("Dive computer");
1049         gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 3);
1050
1051         combo_box = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
1052         gtk_container_add(GTK_CONTAINER(frame), combo_box);
1053
1054         renderer = gtk_cell_renderer_text_new();
1055         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo_box), renderer, TRUE);
1056         gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(combo_box), renderer, render_dive_computer, NULL, NULL);
1057
1058         gtk_combo_box_set_active(GTK_COMBO_BOX(combo_box), default_index);
1059
1060         return GTK_COMBO_BOX(combo_box);
1061 }
1062
1063 const char *subsurface_device_name()
1064 {
1065         if (!default_dive_computer_device || !*default_dive_computer_device)
1066                 return subsurface_USB_name();
1067         else
1068                 return default_dive_computer_device;
1069 }
1070
1071 static GtkEntry *dive_computer_device(GtkWidget *vbox)
1072 {
1073         GtkWidget *hbox, *entry, *frame;
1074
1075         hbox = gtk_hbox_new(FALSE, 6);
1076         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
1077
1078         frame = gtk_frame_new("Device name");
1079         gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 3);
1080
1081         entry = gtk_entry_new();
1082         gtk_container_add(GTK_CONTAINER(frame), entry);
1083         gtk_entry_set_text(GTK_ENTRY(entry), subsurface_device_name());
1084
1085         return GTK_ENTRY(entry);
1086 }
1087
1088 /* once a file is selected in the FileChooserButton we want to exit the import dialog */
1089 static void on_file_set(GtkFileChooserButton *widget, gpointer _data)
1090 {
1091         GtkDialog *main_dialog = _data;
1092
1093         gtk_dialog_response(main_dialog, GTK_RESPONSE_ACCEPT);
1094 }
1095
1096 static GtkWidget *xml_file_selector(GtkWidget *vbox, GtkWidget *main_dialog)
1097 {
1098         GtkWidget *hbox, *frame, *chooser, *dialog;
1099         GtkFileFilter *filter;
1100
1101         hbox = gtk_hbox_new(FALSE, 6);
1102         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
1103
1104         frame = gtk_frame_new("XML file name");
1105         gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 3);
1106         dialog = gtk_file_chooser_dialog_new("Open XML File",
1107                 GTK_WINDOW(main_window),
1108                 GTK_FILE_CHOOSER_ACTION_OPEN,
1109                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1110                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
1111                 NULL);
1112         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
1113
1114         filter = gtk_file_filter_new();
1115         gtk_file_filter_add_pattern(filter, "*.xml");
1116         gtk_file_filter_add_pattern(filter, "*.XML");
1117         gtk_file_filter_add_pattern(filter, "*.sda");
1118         gtk_file_filter_add_pattern(filter, "*.SDA");
1119         gtk_file_filter_add_mime_type(filter, "text/xml");
1120         gtk_file_filter_set_name(filter, "XML file");
1121         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
1122
1123         chooser = gtk_file_chooser_button_new_with_dialog(dialog);
1124         g_signal_connect(G_OBJECT(chooser), "file-set", G_CALLBACK(on_file_set), main_dialog);
1125
1126         gtk_file_chooser_button_set_width_chars(GTK_FILE_CHOOSER_BUTTON(chooser), 30);
1127         gtk_container_add(GTK_CONTAINER(frame), chooser);
1128
1129         return chooser;
1130 }
1131
1132 static void do_import_file(gpointer data, gpointer user_data)
1133 {
1134         GError *error = NULL;
1135         parse_file(data, &error);
1136
1137         if (error != NULL)
1138         {
1139                 report_error(error);
1140                 g_error_free(error);
1141                 error = NULL;
1142         }
1143 }
1144
1145 static GtkWidget *import_dive_computer(device_data_t *data, GtkDialog *dialog)
1146 {
1147         GError *error;
1148         GtkWidget *vbox, *info, *container, *label, *button;
1149
1150         error = do_import(data);
1151         if (!error)
1152                 return NULL;
1153
1154         button = gtk_dialog_get_widget_for_response(dialog, GTK_RESPONSE_ACCEPT);
1155         gtk_button_set_use_stock(GTK_BUTTON(button), 0);
1156         gtk_button_set_label(GTK_BUTTON(button), "Retry");
1157
1158         vbox = gtk_dialog_get_content_area(dialog);
1159
1160         info = gtk_info_bar_new();
1161         container = gtk_info_bar_get_content_area(GTK_INFO_BAR(info));
1162         label = gtk_label_new(error->message);
1163         gtk_container_add(GTK_CONTAINER(container), label);
1164         gtk_box_pack_start(GTK_BOX(vbox), info, FALSE, FALSE, 0);
1165         return info;
1166 }
1167
1168 void import_dialog(GtkWidget *w, gpointer data)
1169 {
1170         int result;
1171         GtkWidget *dialog, *hbox, *vbox, *label, *info = NULL;
1172         GtkComboBox *computer;
1173         GtkEntry *device;
1174         GtkWidget *XMLchooser;
1175         device_data_t devicedata = {
1176                 .devname = NULL,
1177         };
1178
1179         dialog = gtk_dialog_new_with_buttons("Import from dive computer",
1180                 GTK_WINDOW(main_window),
1181                 GTK_DIALOG_DESTROY_WITH_PARENT,
1182                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1183                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1184                 NULL);
1185
1186         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1187         label = gtk_label_new("Import: \nLoad XML file or import directly from dive computer");
1188         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 3);
1189         XMLchooser = xml_file_selector(vbox, dialog);
1190         computer = dive_computer_selector(vbox);
1191         device = dive_computer_device(vbox);
1192         hbox = gtk_hbox_new(FALSE, 6);
1193         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 3);
1194         devicedata.progress.bar = gtk_progress_bar_new();
1195         gtk_container_add(GTK_CONTAINER(hbox), devicedata.progress.bar);
1196
1197 repeat:
1198         gtk_widget_show_all(dialog);
1199         result = gtk_dialog_run(GTK_DIALOG(dialog));
1200         switch (result) {
1201                 dc_descriptor_t *descriptor;
1202                 GtkTreeIter iter;
1203                 GtkTreeModel *model;
1204                 GSList *list;
1205         case GTK_RESPONSE_ACCEPT:
1206                 /* what happened - did the user pick a file? In that case
1207                  * we ignore whether a dive computer model was picked */
1208                 if (info)
1209                         gtk_widget_destroy(info);
1210                 list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(XMLchooser));
1211                 if (g_slist_length(list) == 0) {
1212                         const char *vendor, *product;
1213
1214                         if (!gtk_combo_box_get_active_iter(computer, &iter))
1215                                 break;
1216                         model = gtk_combo_box_get_model(computer);
1217                         gtk_tree_model_get(model, &iter,
1218                                         0, &descriptor,
1219                                         -1);
1220
1221                         vendor = dc_descriptor_get_vendor(descriptor);
1222                         product = dc_descriptor_get_product(descriptor);
1223
1224                         devicedata.descriptor = descriptor;
1225                         devicedata.vendor = vendor;
1226                         devicedata.product = product;
1227                         devicedata.devname = gtk_entry_get_text(device);
1228                         set_default_dive_computer(vendor, product);
1229                         set_default_dive_computer_device(devicedata.devname);
1230                         info = import_dive_computer(&devicedata, GTK_DIALOG(dialog));
1231                         if (info)
1232                                 goto repeat;
1233                 } else {
1234                         g_slist_foreach(list,do_import_file,NULL);
1235                         g_slist_free(list);
1236                 }
1237                 break;
1238         default:
1239                 break;
1240         }
1241         gtk_widget_destroy(dialog);
1242
1243         report_dives(TRUE);
1244 }
1245
1246 void update_progressbar(progressbar_t *progress, double value)
1247 {
1248         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress->bar), value);
1249 }
1250
1251 void update_progressbar_text(progressbar_t *progress, const char *text)
1252 {
1253         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress->bar), text);
1254 }
1255
1256 void set_filename(const char *filename)
1257 {
1258         if (!existing_filename && filename)
1259                 existing_filename = strdup(filename);
1260         return;
1261 }