X-Git-Url: http://git.tdb.fi/?a=blobdiff_plain;f=gtk-gui.c;h=c78d7e6677feeadabf850c738f2450dfc5132884;hb=4891fa812f04da1f46075433d28fa0f33145e125;hp=754a77772e9ac70e7bc8ee2aa72b62f3ca012504;hpb=c0adfdc41bc5bea580f85a29dc073678fcb617e8;p=ext%2Fsubsurface.git diff --git a/gtk-gui.c b/gtk-gui.c index 754a777..c78d7e6 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -8,7 +8,11 @@ #include #include +#ifndef WIN32 #include +#else +#include +#endif #include "dive.h" #include "divelist.h" @@ -26,20 +30,26 @@ int error_count; #define DIVELIST_DEFAULT_FONT "Sans 8" const char *divelist_font; +#ifndef WIN32 GConfClient *gconf; -struct units output_units; - #define GCONF_NAME(x) "/apps/subsurface/" #x +#endif + +struct units output_units; static GtkWidget *dive_profile; +visible_cols_t visible_cols = {TRUE, FALSE}; + void repaint_dive(void) { update_dive(current_dive); - gtk_widget_queue_draw(dive_profile); + if (dive_profile) + gtk_widget_queue_draw(dive_profile); } static char *existing_filename; +static gboolean need_icon = TRUE; static void on_info_bar_response(GtkWidget *widget, gint response, gpointer data) @@ -87,6 +97,8 @@ void report_error(GError* error) static void file_open(GtkWidget *w, gpointer data) { GtkWidget *dialog; + GtkFileFilter *filter; + dialog = gtk_file_chooser_dialog_new("Open File", GTK_WINDOW(main_window), GTK_FILE_CHOOSER_ACTION_OPEN, @@ -95,6 +107,15 @@ static void file_open(GtkWidget *w, gpointer data) NULL); gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE); + filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.xml"); + gtk_file_filter_add_pattern(filter, "*.XML"); + gtk_file_filter_add_pattern(filter, "*.sda"); + gtk_file_filter_add_pattern(filter, "*.SDA"); + gtk_file_filter_add_mime_type(filter, "text/xml"); + gtk_file_filter_set_name(filter, "XML file"); + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter); + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { GSList *filenames; char *filename; @@ -102,7 +123,7 @@ static void file_open(GtkWidget *w, gpointer data) GError *error = NULL; while(filenames != NULL) { - filename = (char *)filenames->data; + filename = filenames->data; parse_xml_file(filename, &error); if (error != NULL) { @@ -115,8 +136,7 @@ static void file_open(GtkWidget *w, gpointer data) filenames = g_slist_next(filenames); } g_slist_free(filenames); - report_dives(); - dive_list_update_dives(); + report_dives(FALSE); } gtk_widget_destroy(dialog); } @@ -191,6 +211,42 @@ static void quit(GtkWidget *w, gpointer data) gtk_main_quit(); } +GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title, + data_func_t data_func, PangoAlignment align, gboolean visible) +{ + GtkCellRenderer *renderer; + GtkTreeViewColumn *col; + double xalign = 0.0; /* left as default */ + + renderer = gtk_cell_renderer_text_new(); + col = gtk_tree_view_column_new(); + + gtk_tree_view_column_set_title(col, title); + gtk_tree_view_column_set_sort_column_id(col, index); + gtk_tree_view_column_set_resizable(col, TRUE); + gtk_tree_view_column_pack_start(col, renderer, TRUE); + if (data_func) + gtk_tree_view_column_set_cell_data_func(col, renderer, data_func, (void *)(long)index, NULL); + else + gtk_tree_view_column_add_attribute(col, renderer, "text", index); + gtk_object_set(GTK_OBJECT(renderer), "alignment", align, NULL); + switch (align) { + case PANGO_ALIGN_LEFT: + xalign = 0.0; + break; + case PANGO_ALIGN_CENTER: + xalign = 0.5; + break; + case PANGO_ALIGN_RIGHT: + xalign = 1.0; + break; + } + gtk_cell_renderer_set_alignment(GTK_CELL_RENDERER(renderer), xalign, 0.5); + gtk_tree_view_column_set_visible(col, visible); + gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), col); + return col; +} + static void create_radio(GtkWidget *vbox, const char *name, ...) { va_list args; @@ -243,10 +299,29 @@ UNITCALLBACK(set_cuft, volume, CUFT) UNITCALLBACK(set_celsius, temperature, CELSIUS) UNITCALLBACK(set_fahrenheit, temperature, FAHRENHEIT) +#define OPTIONCALLBACK(name, option) \ +static void name(GtkWidget *w, gpointer data) \ +{ \ + option = GTK_TOGGLE_BUTTON(w)->active; \ +} + +OPTIONCALLBACK(otu_toggle, visible_cols.otu) +OPTIONCALLBACK(sac_toggle, visible_cols.sac) +OPTIONCALLBACK(nitrox_toggle, visible_cols.nitrox) +OPTIONCALLBACK(temperature_toggle, visible_cols.temperature) +OPTIONCALLBACK(cylinder_toggle, visible_cols.cylinder) + +static void event_toggle(GtkWidget *w, gpointer _data) +{ + gboolean *plot_ev = _data; + + *plot_ev = GTK_TOGGLE_BUTTON(w)->active; +} + static void preferences_dialog(GtkWidget *w, gpointer data) { int result; - GtkWidget *dialog, *font, *frame, *box; + GtkWidget *dialog, *font, *frame, *box, *vbox, *button; menu_units = output_units; @@ -258,7 +333,8 @@ static void preferences_dialog(GtkWidget *w, gpointer data) NULL); frame = gtk_frame_new("Units"); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5); + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); box = gtk_vbox_new(FALSE, 6); gtk_container_add(GTK_CONTAINER(frame), box); @@ -283,8 +359,39 @@ static void preferences_dialog(GtkWidget *w, gpointer data) "Fahrenheit", set_fahrenheit, (output_units.temperature == FAHRENHEIT), NULL); + frame = gtk_frame_new("Columns"); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(frame), box); + + button = gtk_check_button_new_with_label("Show Temp"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.temperature); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(temperature_toggle), NULL); + + button = gtk_check_button_new_with_label("Show Cyl"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.cylinder); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(cylinder_toggle), NULL); + + button = gtk_check_button_new_with_label("Show O" UTF8_SUBSCRIPT_2 "%"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.nitrox); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(nitrox_toggle), NULL); + + button = gtk_check_button_new_with_label("Show SAC"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.sac); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(sac_toggle), NULL); + + button = gtk_check_button_new_with_label("Show OTU"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.otu); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(otu_toggle), NULL); + font = gtk_font_button_new_with_font(divelist_font); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), font, FALSE, FALSE, 5); + gtk_box_pack_start(GTK_BOX(vbox), font, FALSE, FALSE, 5); gtk_widget_show_all(dialog); result = gtk_dialog_run(GTK_DIALOG(dialog)); @@ -298,11 +405,104 @@ static void preferences_dialog(GtkWidget *w, gpointer data) output_units = menu_units; update_dive_list_units(); repaint_dive(); + update_dive_list_col_visibility(); +#ifndef WIN32 gconf_client_set_bool(gconf, GCONF_NAME(feet), output_units.length == FEET, NULL); gconf_client_set_bool(gconf, GCONF_NAME(psi), output_units.pressure == PSI, NULL); gconf_client_set_bool(gconf, GCONF_NAME(cuft), output_units.volume == CUFT, NULL); gconf_client_set_bool(gconf, GCONF_NAME(fahrenheit), output_units.temperature == FAHRENHEIT, NULL); + gconf_client_set_bool(gconf, GCONF_NAME(TEMPERATURE), visible_cols.temperature, NULL); + gconf_client_set_bool(gconf, GCONF_NAME(CYLINDER), visible_cols.cylinder, NULL); + gconf_client_set_bool(gconf, GCONF_NAME(NITROX), visible_cols.nitrox, NULL); + gconf_client_set_bool(gconf, GCONF_NAME(SAC), visible_cols.sac, NULL); + gconf_client_set_bool(gconf, GCONF_NAME(OTU), visible_cols.otu, NULL); gconf_client_set_string(gconf, GCONF_NAME(divelist_font), divelist_font, NULL); +#else + HKEY hkey; + LONG success = RegCreateKeyEx(HKEY_CURRENT_USER, TEXT("Software\\subsurface"), + 0L, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, + NULL, &hkey, NULL); + if (success != ERROR_SUCCESS) + printf("CreateKey Software\\subsurface failed %ld\n", success); + DWORD value; + +#define StoreInReg(_key, _val) { \ + value = (_val) ; \ + RegSetValueEx(hkey, TEXT(_key), 0, REG_DWORD, &value, 4); \ + } + + StoreInReg("feet", output_units.length == FEET); + StoreInReg("psi", output_units.pressure == PSI); + StoreInReg("cuft", output_units.volume == CUFT); + StoreInReg("fahrenheit", output_units.temperature == FAHRENHEIT); + StoreInReg("temperature", visible_cols.temperature); + StoreInReg("cylinder", visible_cols.cylinder); + StoreInReg("nitrox", visible_cols.nitrox); + StoreInReg("sac", visible_cols.sac); + StoreInReg("otu", visible_cols.otu); + RegSetValueEx(hkey, TEXT("divelist_font"), 0, REG_SZ, divelist_font, strlen(divelist_font)); + if (RegFlushKey(hkey) != ERROR_SUCCESS) + printf("RegFlushKey failed %ld\n"); + RegCloseKey(hkey); +#endif + } + gtk_widget_destroy(dialog); +} + +static void create_toggle(const char* label, int *on, void *_data) +{ + GtkWidget *button, *table = _data; + int rows, cols, x, y; + static int count; + + if (table == NULL) { + /* magic way to reset the number of toggle buttons + * that we have already added - call this before you + * create the dialog */ + count = 0; + return; + } + g_object_get(G_OBJECT(table), "n-columns", &cols, "n-rows", &rows, NULL); + if (count > rows * cols) { + gtk_table_resize(GTK_TABLE(table),rows+1,cols); + rows++; + } + x = count % cols; + y = count / cols; + button = gtk_check_button_new_with_label(label); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), *on); + gtk_table_attach_defaults(GTK_TABLE(table), button, x, x+1, y, y+1); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(event_toggle), on); + count++; +} + +static void selectevents_dialog(GtkWidget *w, gpointer data) +{ + int result; + GtkWidget *dialog, *frame, *vbox, *table; + + dialog = gtk_dialog_new_with_buttons("SelectEvents", + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + /* initialize the function that fills the table */ + create_toggle(NULL, NULL, NULL); + + frame = gtk_frame_new("Enable / Disable Events"); + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); + + table = gtk_table_new(1, 4, TRUE); + gtk_container_add(GTK_CONTAINER(frame), table); + + evn_foreach(&create_toggle, table); + + gtk_widget_show_all(dialog); + result = gtk_dialog_run(GTK_DIALOG(dialog)); + if (result == GTK_RESPONSE_ACCEPT) { + repaint_dive(); } gtk_widget_destroy(dialog); } @@ -310,7 +510,8 @@ static void preferences_dialog(GtkWidget *w, gpointer data) static void renumber_dialog(GtkWidget *w, gpointer data) { int result; - GtkWidget *dialog, *frame, *button; + struct dive *dive; + GtkWidget *dialog, *frame, *button, *vbox; dialog = gtk_dialog_new_with_buttons("Renumber", GTK_WINDOW(main_window), @@ -319,12 +520,22 @@ static void renumber_dialog(GtkWidget *w, gpointer data) GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + frame = gtk_frame_new("New starting number"); - gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), frame); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); button = gtk_spin_button_new_with_range(1, 50000, 1); gtk_container_add(GTK_CONTAINER(frame), button); + /* + * Do we have a number for the first dive already? Use that + * as the default. + */ + dive = get_dive(0); + if (dive && dive->number) + gtk_spin_button_set_value(GTK_SPIN_BUTTON(button), dive->number); + gtk_widget_show_all(dialog); result = gtk_dialog_run(GTK_DIALOG(dialog)); if (result == GTK_RESPONSE_ACCEPT) { @@ -335,16 +546,50 @@ static void renumber_dialog(GtkWidget *w, gpointer data) gtk_widget_destroy(dialog); } +static void about_dialog(GtkWidget *w, gpointer data) +{ + const char *logo_property = NULL; + GdkPixbuf *logo = NULL; + + if (need_icon) { +#ifndef WIN32 + GtkWidget *image = gtk_image_new_from_file("subsurface.svg"); +#else + GtkWidget *image = gtk_image_new_from_file("subsurface.ico"); +#endif + + if (image) { + logo = gtk_image_get_pixbuf(GTK_IMAGE(image)); + logo_property = "logo"; + } + } + + gtk_show_about_dialog(NULL, + "program-name", "SubSurface", + "comments", "Half-arsed divelog software in C", + "license", "GPLv2", + "version", VERSION_STRING, + "copyright", "Linus Torvalds 2011", + "logo-icon-name", "subsurface", + /* Must be last: */ + logo_property, logo, + NULL); +} + static GtkActionEntry menu_items[] = { { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL}, { "LogMenuAction", GTK_STOCK_FILE, "Log", NULL, NULL, NULL}, + { "FilterMenuAction", GTK_STOCK_FILE, "Filter", NULL, NULL, NULL}, + { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL}, { "OpenFile", GTK_STOCK_OPEN, NULL, "O", NULL, G_CALLBACK(file_open) }, { "SaveFile", GTK_STOCK_SAVE, NULL, "S", NULL, G_CALLBACK(file_save) }, { "Print", GTK_STOCK_PRINT, NULL, "P", NULL, G_CALLBACK(do_print) }, { "Import", NULL, "Import", NULL, NULL, G_CALLBACK(import_dialog) }, { "Preferences", NULL, "Preferences", NULL, NULL, G_CALLBACK(preferences_dialog) }, { "Renumber", NULL, "Renumber", NULL, NULL, G_CALLBACK(renumber_dialog) }, + { "SelectEvents", NULL, "SelectEvents", NULL, NULL, G_CALLBACK(selectevents_dialog) }, { "Quit", GTK_STOCK_QUIT, NULL, "Q", NULL, G_CALLBACK(quit) }, + { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_dialog) }, }; static gint nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]); @@ -365,6 +610,12 @@ static const gchar* ui_string = " \ \ \ \ + \ + \ + \ + \ + \ + \ \ \ "; @@ -390,21 +641,128 @@ static void switch_page(GtkNotebook *notebook, gint arg1, gpointer user_data) repaint_dive(); } -void init_ui(int argc, char **argv) +static const char notebook_group[] = "123"; +#define GRP_ID ((void *)notebook_group) +typedef struct { + char *name; + GtkWidget *widget; + GtkWidget *box; + gulong delete_handler; + gulong destroy_handler; +} notebook_data_t; + +static notebook_data_t nbd[2]; /* we rip at most two notebook pages off */ + +static GtkNotebook *create_new_notebook_window(GtkNotebook *source, + GtkWidget *page, + gint x, gint y, gpointer data) +{ + GtkWidget *win, *notebook, *vbox; + notebook_data_t *nbdp; + GtkAllocation allocation; + + /* pick the right notebook page data and return if both are detached */ + if (nbd[0].widget == NULL) + nbdp = nbd; + else if (nbd[1].widget == NULL) + nbdp = nbd + 1; + else + return NULL; + + nbdp->name = strdup(gtk_widget_get_name(page)); + nbdp->widget = win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(win), nbdp->name); + gtk_window_move(GTK_WINDOW(win), x, y); + + /* Destroying the dive list will kill the application */ + nbdp->delete_handler = g_signal_connect(G_OBJECT(win), "delete-event", G_CALLBACK(on_delete), NULL); + nbdp->destroy_handler = g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(on_destroy), NULL); + + nbdp->box = vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(win), vbox); + + notebook = gtk_notebook_new(); + gtk_notebook_set_group(GTK_NOTEBOOK(notebook), GRP_ID); + gtk_widget_set_name(notebook, nbdp->name); + /* disallow drop events */ + gtk_drag_dest_set(notebook, 0, NULL, 0, 0); + gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 6); + gtk_widget_get_allocation(page, &allocation); + gtk_window_set_default_size(GTK_WINDOW(win), allocation.width, allocation.height); + + gtk_widget_show_all(win); + return GTK_NOTEBOOK(notebook); +} + +static gboolean drag_cb(GtkWidget *widget, GdkDragContext *context, + gint x, gint y, guint time, + gpointer user_data) +{ + GtkWidget *source; + notebook_data_t *nbdp; + + gtk_drag_finish(context, TRUE, TRUE, time); + source = gtk_drag_get_source_widget(context); + if (nbd[0].name && ! strcmp(nbd[0].name,gtk_widget_get_name(source))) + nbdp = nbd; + else if (nbd[1].name && ! strcmp(nbd[1].name,gtk_widget_get_name(source))) + nbdp = nbd + 1; + else /* just on ourselves */ + return TRUE; + + /* we no longer need the widget - but getting rid of this is hard; + * remove the signal handler, remove the notebook from the box + * then destroy the widget (and clear out our data structure) */ + g_signal_handler_disconnect(nbdp->widget,nbdp->delete_handler); + g_signal_handler_disconnect(nbdp->widget,nbdp->destroy_handler); + gtk_container_remove(GTK_CONTAINER(nbdp->box), source); + gtk_widget_destroy(nbdp->widget); + nbdp->widget = NULL; + free(nbdp->name); + nbdp->name = NULL; + + return TRUE; +} + +#ifdef WIN32 +static int get_from_registry(HKEY hkey, const char *key) +{ + DWORD value; + DWORD len = 4; + LONG success; + + success = RegQueryValueEx(hkey, TEXT(key), NULL, NULL, + (LPBYTE) &value, &len ); + if (success != ERROR_SUCCESS) + return FALSE; /* that's what happens the first time we start */ + return value; +} +#endif + +void init_ui(int *argcp, char ***argvp) { GtkWidget *win; - GtkWidget *paned; - GtkWidget *info_box; GtkWidget *notebook; GtkWidget *dive_info; GtkWidget *dive_list; GtkWidget *equipment; + GtkWidget *stats; GtkWidget *menubar; GtkWidget *vbox; + GdkScreen *screen; + GtkIconTheme *icon_theme=NULL; + GtkSettings *settings; + static const GtkTargetEntry notebook_target = { + "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, 0 + }; - gtk_init(&argc, &argv); + gtk_init(argcp, argvp); + settings = gtk_settings_get_default(); + gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "subsurface setting"); g_type_init(); + +#ifndef WIN32 gconf = gconf_client_get_default(); if (gconf_client_get_bool(gconf, GCONF_NAME(feet), NULL)) @@ -415,15 +773,68 @@ void init_ui(int argc, char **argv) output_units.volume = CUFT; if (gconf_client_get_bool(gconf, GCONF_NAME(fahrenheit), NULL)) output_units.temperature = FAHRENHEIT; - + /* an unset key is FALSE - all these are hidden by default */ + visible_cols.cylinder = gconf_client_get_bool(gconf, GCONF_NAME(CYLINDER), NULL); + visible_cols.temperature = gconf_client_get_bool(gconf, GCONF_NAME(TEMPERATURE), NULL); + visible_cols.nitrox = gconf_client_get_bool(gconf, GCONF_NAME(NITROX), NULL); + visible_cols.otu = gconf_client_get_bool(gconf, GCONF_NAME(OTU), NULL); + visible_cols.sac = gconf_client_get_bool(gconf, GCONF_NAME(SAC), NULL); + divelist_font = gconf_client_get_string(gconf, GCONF_NAME(divelist_font), NULL); +#else + DWORD len = 4; + LONG success; + HKEY hkey; + + success = RegOpenKeyEx( HKEY_CURRENT_USER, TEXT("Software\\subsurface"), 0, + KEY_QUERY_VALUE, &hkey); + + output_units.length = get_from_registry(hkey, "feet"); + output_units.pressure = get_from_registry(hkey, "psi"); + output_units.volume = get_from_registry(hkey, "cuft"); + output_units.temperature = get_from_registry(hkey, "fahrenheit"); + visible_cols.temperature = get_from_registry(hkey, "temperature"); + visible_cols.cylinder = get_from_registry(hkey, "cylinder"); + visible_cols.nitrox = get_from_registry(hkey, "nitrox"); + visible_cols.sac = get_from_registry(hkey, "sac"); + visible_cols.otu = get_from_registry(hkey, "otu"); + + divelist_font = malloc(80); + len = 80; + success = RegQueryValueEx(hkey, TEXT("divelist_font"), NULL, NULL, + (LPBYTE) divelist_font, &len ); + if (success != ERROR_SUCCESS) { + /* that's what happens the first time we start - just use the default */ + free(divelist_font); + divelist_font = NULL; + } + RegCloseKey(hkey); + +#endif if (!divelist_font) divelist_font = DIVELIST_DEFAULT_FONT; error_info_bar = NULL; win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_icon_from_file(GTK_WINDOW(win), "icon.svg", NULL); - g_signal_connect(G_OBJECT(win), "delete-event", G_CALLBACK (on_delete), NULL); + g_set_application_name ("subsurface"); + /* Let's check if the subsurface icon has been installed or if + * we need to try to load it from the current directory */ + screen = gdk_screen_get_default(); + if (screen) + icon_theme = gtk_icon_theme_get_for_screen(screen); + if (icon_theme) { + if (gtk_icon_theme_has_icon(icon_theme, "subsurface")) { + need_icon = FALSE; + gtk_window_set_default_icon_name ("subsurface"); + } + } + if (need_icon) +#ifndef WIN32 + gtk_window_set_icon_from_file(GTK_WINDOW(win), "subsurface.svg", NULL); +#else + gtk_window_set_icon_from_file(GTK_WINDOW(win), "subsurface.ico", NULL); +#endif + g_signal_connect(G_OBJECT(win), "delete-event", G_CALLBACK(on_delete), NULL); g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(on_destroy), NULL); main_window = win; @@ -434,26 +845,28 @@ void init_ui(int argc, char **argv) menubar = get_menubar_menu(win); gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); - /* HPane for left the dive list, and right the dive info */ - paned = gtk_vpaned_new(); - gtk_box_pack_end(GTK_BOX(vbox), paned, TRUE, TRUE, 0); - - /* Create the actual divelist */ - dive_list = dive_list_create(); - gtk_paned_add2(GTK_PANED(paned), dive_list); - - /* VBox for dive info, and tabs */ - info_box = gtk_vbox_new(FALSE, 6); - gtk_paned_add1(GTK_PANED(paned), info_box); - /* Notebook for dive info vs profile vs .. */ notebook = gtk_notebook_new(); + gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 6); + gtk_notebook_set_group(GTK_NOTEBOOK(notebook), GRP_ID); + g_signal_connect(notebook, "create-window", G_CALLBACK(create_new_notebook_window), NULL); + gtk_drag_dest_set(notebook, GTK_DEST_DEFAULT_ALL, ¬ebook_target, 1, GDK_ACTION_MOVE); + g_signal_connect(notebook, "drag-drop", G_CALLBACK(drag_cb), notebook); g_signal_connect(notebook, "switch-page", G_CALLBACK(switch_page), NULL); - gtk_box_pack_start(GTK_BOX(info_box), notebook, TRUE, TRUE, 6); + + /* Create the actual divelist */ + dive_list = dive_list_create(); + gtk_widget_set_name(dive_list, "Dive List"); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), dive_list, gtk_label_new("Dive List")); + gtk_notebook_set_tab_detachable(GTK_NOTEBOOK(notebook), dive_list, 1); + gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(notebook), dive_list, 1); /* Frame for dive profile */ dive_profile = dive_profile_widget(); + gtk_widget_set_name(dive_profile, "Dive Profile"); gtk_notebook_append_page(GTK_NOTEBOOK(notebook), dive_profile, gtk_label_new("Dive Profile")); + gtk_notebook_set_tab_detachable(GTK_NOTEBOOK(notebook), dive_profile, 1); + gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(notebook), dive_profile, 1); /* Frame for extended dive info */ dive_info = extended_dive_info_widget(); @@ -463,6 +876,10 @@ void init_ui(int argc, char **argv) equipment = equipment_widget(); gtk_notebook_append_page(GTK_NOTEBOOK(notebook), equipment, gtk_label_new("Equipment")); + /* Frame for dive statistics */ + stats = stats_widget(); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), stats, gtk_label_new("Info & Stats")); + gtk_widget_set_app_paintable(win, TRUE); gtk_widget_show_all(win); @@ -474,62 +891,75 @@ void run_ui(void) gtk_main(); } -/* get the filenames the user selects and call the parsing function - * on them - * return 0 if the user cancelled the dialog - */ -int open_import_file_dialog(char *filterpattern, char *filtertext, - void(* parse_function)(char *)) +typedef struct { + cairo_rectangle_int_t rect; + const char *text; +} tooltip_record_t; + +static tooltip_record_t *tooltip_rects; +static int tooltips; + +void attach_tooltip(int x, int y, int w, int h, const char *text) { - int ret=0; + cairo_rectangle_int_t *rect; + tooltip_rects = realloc(tooltip_rects, (tooltips + 1) * sizeof(tooltip_record_t)); + rect = &tooltip_rects[tooltips].rect; + rect->x = x; + rect->y = y; + rect->width = w; + rect->height = h; + tooltip_rects[tooltips].text = text; + tooltips++; +} - GtkWidget *dialog; - GtkFileFilter *filter = gtk_file_filter_new (); - gtk_file_filter_add_pattern (filter, filterpattern); - gtk_file_filter_set_name(filter, filtertext); - dialog = gtk_file_chooser_dialog_new("Open File", - GTK_WINDOW(main_window), - GTK_FILE_CHOOSER_ACTION_OPEN, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, - NULL); - gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE); - gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog),filter); +#define INSIDE_RECT(_r,_x,_y) ((_r.x <= _x) && (_r.x + _r.width >= _x) && \ + (_r.y <= _y) && (_r.y + _r.height >= _y)) - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - GSList *filenames; - char *filename; - filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); - while(filenames != NULL) { - filename = (char *)filenames->data; - parse_function(filename); - g_free(filename); - filenames = g_slist_next(filenames); +static gboolean profile_tooltip (GtkWidget *widget, gint x, gint y, + gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data) +{ + int i; + cairo_rectangle_int_t *drawing_area = user_data; + gint tx = x - drawing_area->x; /* get transformed coordinates */ + gint ty = y - drawing_area->y; + + /* are we over an event marker ? */ + for (i = 0; i < tooltips; i++) { + if (INSIDE_RECT(tooltip_rects[i].rect, tx, ty)) { + gtk_tooltip_set_text(tooltip,tooltip_rects[i].text); + return TRUE; /* show tooltip */ } - g_slist_free(filenames); - ret = 1; } - gtk_widget_destroy(dialog); - - return ret; + return FALSE; /* don't show tooltip */ } static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data) { struct dive *dive = current_dive; struct graphics_context gc = { .printer = 0 }; - int w,h; + static cairo_rectangle_int_t drawing_area; - w = widget->allocation.width; - h = widget->allocation.height; + /* the drawing area gives TOTAL width * height - x,y is used as the topx/topy offset + * so effective drawing area is width-2x * height-2y */ + drawing_area.width = widget->allocation.width; + drawing_area.height = widget->allocation.height; + drawing_area.x = drawing_area.width / 20.0; + drawing_area.y = drawing_area.height / 20.0; gc.cr = gdk_cairo_create(widget->window); + g_object_set(widget, "has-tooltip", TRUE, NULL); + g_signal_connect(widget, "query-tooltip", G_CALLBACK(profile_tooltip), &drawing_area); set_source_rgb(&gc, 0, 0, 0); cairo_paint(gc.cr); - if (dive) - plot(&gc, w, h, dive); - + if (dive) { + if (tooltip_rects) { + free(tooltip_rects); + tooltip_rects = NULL; + } + tooltips = 0; + plot(&gc, &drawing_area, dive); + } cairo_destroy(gc.cr); return FALSE; @@ -574,20 +1004,23 @@ static void fill_computer_list(GtkListStore *store) } } -static GtkComboBox *dive_computer_selector(GtkWidget *dialog) +static GtkComboBox *dive_computer_selector(GtkWidget *vbox) { - GtkWidget *hbox, *combo_box; + GtkWidget *hbox, *combo_box, *frame; GtkListStore *model; GtkCellRenderer *renderer; hbox = gtk_hbox_new(FALSE, 6); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, FALSE, FALSE, 3); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3); model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT); fill_computer_list(model); + frame = gtk_frame_new("Dive computer"); + gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 3); + combo_box = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model)); - gtk_box_pack_start(GTK_BOX(hbox), combo_box, FALSE, TRUE, 3); + gtk_container_add(GTK_CONTAINER(frame), combo_box); renderer = gtk_cell_renderer_text_new(); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo_box), renderer, TRUE); @@ -596,13 +1029,89 @@ static GtkComboBox *dive_computer_selector(GtkWidget *dialog) return GTK_COMBO_BOX(combo_box); } +static GtkEntry *dive_computer_device(GtkWidget *vbox) +{ + GtkWidget *hbox, *entry, *frame; + + hbox = gtk_hbox_new(FALSE, 6); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3); + + frame = gtk_frame_new("Device name"); + gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 3); + + entry = gtk_entry_new(); + gtk_container_add(GTK_CONTAINER(frame), entry); + gtk_entry_set_text(GTK_ENTRY(entry), "/dev/ttyUSB0"); + + return GTK_ENTRY(entry); +} + +/* once a file is selected in the FileChooserButton we want to exit the import dialog */ +static void on_file_set(GtkFileChooserButton *widget, gpointer _data) +{ + GtkDialog *main_dialog = _data; + + gtk_dialog_response(main_dialog, GTK_RESPONSE_ACCEPT); +} + +static GtkWidget *xml_file_selector(GtkWidget *vbox, GtkWidget *main_dialog) +{ + GtkWidget *hbox, *frame, *chooser, *dialog; + GtkFileFilter *filter; + + hbox = gtk_hbox_new(FALSE, 6); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3); + + frame = gtk_frame_new("XML file name"); + gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 3); + dialog = gtk_file_chooser_dialog_new("Open XML File", + GTK_WINDOW(main_window), + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE); + + filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.xml"); + gtk_file_filter_add_pattern(filter, "*.XML"); + gtk_file_filter_add_pattern(filter, "*.sda"); + gtk_file_filter_add_pattern(filter, "*.SDA"); + gtk_file_filter_add_mime_type(filter, "text/xml"); + gtk_file_filter_set_name(filter, "XML file"); + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter); + + chooser = gtk_file_chooser_button_new_with_dialog(dialog); + g_signal_connect(G_OBJECT(chooser), "file-set", G_CALLBACK(on_file_set), main_dialog); + + gtk_file_chooser_button_set_width_chars(GTK_FILE_CHOOSER_BUTTON(chooser), 30); + gtk_container_add(GTK_CONTAINER(frame), chooser); + + return chooser; +} + +static void do_import_file(gpointer data, gpointer user_data) +{ + GError *error = NULL; + parse_xml_file(data, &error); + + if (error != NULL) + { + report_error(error); + g_error_free(error); + error = NULL; + } +} + void import_dialog(GtkWidget *w, gpointer data) { int result; - GtkWidget *dialog, *hbox; + GtkWidget *dialog, *hbox, *vbox, *label; GtkComboBox *computer; + GtkEntry *device; + GtkWidget *XMLchooser; device_data_t devicedata = { - .devname = "/dev/ttyUSB0", + .devname = NULL, }; dialog = gtk_dialog_new_with_buttons("Import from dive computer", @@ -612,10 +1121,14 @@ void import_dialog(GtkWidget *w, gpointer data) GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); - computer = dive_computer_selector(dialog); - + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + label = gtk_label_new("Import: \nLoad XML file or import directly from dive computer"); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 3); + XMLchooser = xml_file_selector(vbox, dialog); + computer = dive_computer_selector(vbox); + device = dive_computer_device(vbox); hbox = gtk_hbox_new(FALSE, 6); - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, FALSE, TRUE, 3); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 3); devicedata.progress.bar = gtk_progress_bar_new(); gtk_container_add(GTK_CONTAINER(hbox), devicedata.progress.bar); @@ -626,25 +1139,34 @@ void import_dialog(GtkWidget *w, gpointer data) GtkTreeIter iter; GtkTreeModel *model; const char *comp; + GSList *list; case GTK_RESPONSE_ACCEPT: - if (!gtk_combo_box_get_active_iter(computer, &iter)) - break; - model = gtk_combo_box_get_model(computer); - gtk_tree_model_get(model, &iter, - 0, &comp, - 1, &type, - -1); - devicedata.type = type; - devicedata.name = comp; - do_import(&devicedata); + /* what happened - did the user pick a file? In that case + * we ignore whether a dive computer model was picked */ + list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(XMLchooser)); + if (g_slist_length(list) == 0) { + if (!gtk_combo_box_get_active_iter(computer, &iter)) + break; + model = gtk_combo_box_get_model(computer); + gtk_tree_model_get(model, &iter, + 0, &comp, + 1, &type, + -1); + devicedata.type = type; + devicedata.name = comp; + devicedata.devname = gtk_entry_get_text(device); + do_import(&devicedata); + } else { + g_slist_foreach(list,do_import_file,NULL); + g_slist_free(list); + } break; default: break; } gtk_widget_destroy(dialog); - report_dives(); - dive_list_update_dives(); + report_dives(TRUE); } void update_progressbar(progressbar_t *progress, double value) @@ -655,7 +1177,7 @@ void update_progressbar(progressbar_t *progress, double value) void set_filename(const char *filename) { - if (filename) + if (!existing_filename && filename) existing_filename = strdup(filename); return; }