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