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