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