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