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