From 42365ede79ec34dba58447458bbc3f1c66cba7d1 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Mon, 1 Apr 2013 12:25:15 +0300 Subject: Setup Makefile for Qt/C++ builds Setup the build variables for building with QtWidgets, and add rules for processing the Q_OBJECT macros with moc and generate widget code from .ui files with uic. Signed-off-by: Alberto Mardegan --- Makefile | 48 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index b13caf7d6..3d3febd9a 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,15 @@ VERSION=3.0.2 CC=gcc CFLAGS=-Wall -Wno-pointer-sign -g $(CLCFLAGS) -DGSEAL_ENABLE +CXX=g++ +CXXFLAGS=-Wall -g $(CLCFLAGS) INSTALL=install PKGCONFIG=pkg-config XML2CONFIG=xml2-config XSLCONFIG=xslt-config +QMAKE=qmake +MOC=moc +UIC=uic # these locations seem to work for SuSE and Fedora # prefix = $(HOME) @@ -96,6 +101,18 @@ endif # about it if it doesn't. LIBUSB = $(shell $(PKGCONFIG) --libs libusb-1.0 2> /dev/null) +# Use qmake to find out which Qt version we are building for. +QT_VERSION_MAJOR = $(shell $(QMAKE) -query QT_VERSION | cut -d. -f1) +ifeq ($(QT_VERSION_MAJOR), 5) + QT_MODULES = Qt5Widgets + QT_CORE = Qt5Core +else + QT_MODULES = QtGui + QT_CORE = QtCore +endif +LIBQT = $(shell $(PKGCONFIG) --libs $(QT_MODULES)) +QTCXXFLAGS = $(shell $(PKGCONFIG) --cflags $(QT_MODULES)) + LIBGTK = $(shell $(PKGCONFIG) --libs gtk+-2.0 glib-2.0) LIBDIVECOMPUTERCFLAGS = $(LIBDIVECOMPUTERINCLUDES) LIBDIVECOMPUTER = $(LIBDIVECOMPUTERARCHIVE) $(LIBUSB) @@ -130,6 +147,9 @@ ifneq (,$(filter $(UNAME),linux kfreebsd gnu)) GCONF2CFLAGS = $(shell $(PKGCONFIG) --cflags gconf-2.0) OSSUPPORT = linux OSSUPPORT_CFLAGS = $(GTKCFLAGS) $(GCONF2CFLAGS) + ifneq ($(findstring reduce_relocations, $(shell $(PKGCONFIG) --variable qt_config $(QT_CORE))),) + CXXFLAGS += -fPIE + endif else ifeq ($(UNAME), darwin) OSSUPPORT = macos OSSUPPORT_CFLAGS = $(GTKCFLAGS) @@ -157,7 +177,7 @@ ifneq ($(strip $(LIBXSLT)),) XSLT=-DXSLT='"$(XSLTDIR)"' endif -LIBS = $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGTK) $(LIBGCONF2) $(LIBDIVECOMPUTER) $(EXTRALIBS) $(LIBZIP) -lpthread -lm $(LIBOSMGPSMAP) $(LIBSOUP) $(LIBWINSOCK) +LIBS = $(LIBQT) $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGTK) $(LIBGCONF2) $(LIBDIVECOMPUTER) $(EXTRALIBS) $(LIBZIP) -lpthread -lm $(LIBOSMGPSMAP) $(LIBSOUP) $(LIBWINSOCK) MSGLANGS=$(notdir $(wildcard po/*.po)) MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.mo)) @@ -173,7 +193,7 @@ DEPS = $(wildcard .dep/*.dep) all: $(NAME) $(NAME): gen_version_file $(OBJS) $(MSGOBJS) $(INFOPLIST) - $(CC) $(LDFLAGS) -o $(NAME) $(OBJS) $(LIBS) + $(CXX) $(LDFLAGS) -o $(NAME) $(OBJS) $(LIBS) gen_version_file: ifneq ($(STORED_VERSION_STRING),$(VERSION_STRING)) @@ -270,15 +290,37 @@ update-po-files: tx push -s tx pull -af -EXTRA_FLAGS = $(GTKCFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) \ +EXTRA_FLAGS = $(QTCXXFLAGS) $(GTKCFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) \ $(XSLT) $(ZIP) $(SQLITE3) $(LIBDIVECOMPUTERCFLAGS) \ $(LIBSOUPCFLAGS) $(OSMGPSMAPFLAGS) $(GCONF2CFLAGS) +MOCFLAGS = $(filter -I%, $(CXXFLAGS) $(EXTRA_FLAGS)) $(filter -D%, $(CXXFLAGS) $(EXTRA_FLAGS)) + %.o: %.c @echo ' CC' $< @mkdir -p .dep @$(CC) $(CFLAGS) $(EXTRA_FLAGS) -MD -MF .dep/$@.dep -c -o $@ $< +%.o: %.cpp + @echo ' CXX' $< + @mkdir -p .dep + @$(CXX) $(CXXFLAGS) $(EXTRA_FLAGS) -MD -MF .dep/$@.dep -c -o $@ $< + +%.moc.cpp: %.h + @echo ' MOC' $< + @$(MOC) $(MOCFLAGS) $< -o $@ + +# This rule is for running the moc on QObject subclasses defined in the .cpp files; +# remember to #include ".moc.cpp" at the end of the .cpp file, or you'll +# get linker errors ("undefined vtable for...") +%.moc.cpp: %.cpp + @echo ' MOC' $< + @$(MOC) -i $(MOCFLAGS) $< -o $@ + +%.ui.h: ui/%.ui + @echo ' UIC' $< + @$(UIC) $< -o $@ + share/locale/%.UTF-8/LC_MESSAGES/subsurface.mo: po/%.po po/%.aliases mkdir -p $(dir $@) msgfmt -c -o $@ po/$*.po -- cgit v1.2.3-70-g09d2 From 578d633d0148a13397f330aa91af1470843d73c1 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Mon, 1 Apr 2013 13:51:49 +0300 Subject: Have some C++ file in the project Rename gtk-gui.c to qt-gui.cpp, and make the necessary changes so that the project still builds. Signed-off-by: Alberto Mardegan --- Makefile | 2 +- callbacks-gtk.h | 2 +- device.h | 8 + display-gtk.h | 8 + display.h | 8 + dive.h | 10 +- divelist.h | 9 + gtk-gui.c | 2363 ------------------------------------------------------ linux.c | 16 +- macos.c | 14 +- pref.h | 22 +- qt-gui.cpp | 2365 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ webservice.h | 8 + windows.c | 10 +- 14 files changed, 2452 insertions(+), 2393 deletions(-) delete mode 100644 gtk-gui.c create mode 100644 qt-gui.cpp (limited to 'Makefile') diff --git a/Makefile b/Makefile index 3d3febd9a..a38586ff0 100644 --- a/Makefile +++ b/Makefile @@ -184,7 +184,7 @@ MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.m OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o deco.o planner.o \ parse-xml.o save-xml.o libdivecomputer.o print.o uemis.o uemis-downloader.o \ - gtk-gui.o statistics.o file.o cochran.o device.o download-dialog.o prefs.o \ + qt-gui.o statistics.o file.o cochran.o device.o download-dialog.o prefs.o \ webservice.o sha1.o $(GPSOBJ) $(OSSUPPORT).o $(RESFILE) DEPS = $(wildcard .dep/*.dep) diff --git a/callbacks-gtk.h b/callbacks-gtk.h index 08c159b4d..568916f6c 100644 --- a/callbacks-gtk.h +++ b/callbacks-gtk.h @@ -9,7 +9,7 @@ static void name(GtkWidget *w, gpointer data) \ #define OPTIONCALLBACK(name, option) \ static void name(GtkWidget *w, gpointer data) \ { \ - GtkWidget **entry = data; \ + GtkWidget **entry = (GtkWidget**)data; \ option = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)); \ update_screen(); \ if (entry) \ diff --git a/device.h b/device.h index 8a306ef78..636eb73c6 100644 --- a/device.h +++ b/device.h @@ -1,6 +1,10 @@ #ifndef DEVICE_INFO_H #define DEVICE_INFO_H +#ifdef __cplusplus +extern "C" { +#endif + struct device_info { const char *model; uint32_t deviceid; @@ -17,4 +21,8 @@ extern struct device_info *create_device_info(const char *model, uint32_t device extern struct device_info *remove_device_info(const char *model, uint32_t deviceid); extern struct device_info *head_of_device_info_list(void); +#ifdef __cplusplus +} +#endif + #endif diff --git a/display-gtk.h b/display-gtk.h index 7c1bcad4a..4cc86659f 100644 --- a/display-gtk.h +++ b/display-gtk.h @@ -8,6 +8,10 @@ #include #endif +#ifdef __cplusplus +extern "C" { +#endif + extern GtkWidget *main_window; /* we want a progress bar as part of the device_data_t - let's abstract this out */ @@ -117,4 +121,8 @@ GError *uemis_download(const char *path, progressbar_t *progress, GtkDialog *dia /* from planner.c */ extern void input_plan(void); +#ifdef __cplusplus +} +#endif + #endif diff --git a/display.h b/display.h index 8200770d7..d5c69e81b 100644 --- a/display.h +++ b/display.h @@ -3,6 +3,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + #define SCALE_SCREEN 1.0 #define SCALE_PRINT (1.0 / get_screen_dpi()) @@ -63,4 +67,8 @@ struct options { extern char zoomed_plot, dc_number; +#ifdef __cplusplus +} +#endif + #endif diff --git a/dive.h b/dive.h index 7d08828a8..4528ac1db 100644 --- a/dive.h +++ b/dive.h @@ -15,6 +15,10 @@ #include "sha1.h" +#ifdef __cplusplus +extern "C" { +#endif + #define O2_IN_AIR 209 // permille #define N2_IN_AIR 781 #define O2_DENSITY 1429 // mg/Liter @@ -523,7 +527,7 @@ static inline struct divecomputer *get_dive_dc(struct dive *dive, int nr) #define for_each_gps_location(_i,_x) \ for ((_i) = 0; ((_x) = get_gps_location(_i, &gps_location_table)) != NULL; (_i)++) -static inline struct dive *get_dive_by_diveid(int diveid, int deviceid) +static inline struct dive *get_dive_by_diveid(uint32_t diveid, uint32_t deviceid) { int i; struct dive *dive; @@ -700,6 +704,10 @@ extern char *debugfilename; extern FILE *debugfile; #endif +#ifdef __cplusplus +} +#endif + #include "pref.h" #endif /* DIVE_H */ diff --git a/divelist.h b/divelist.h index 856318e2d..c9ec973f7 100644 --- a/divelist.h +++ b/divelist.h @@ -1,6 +1,10 @@ #ifndef DIVELIST_H #define DIVELIST_H +#ifdef __cplusplus +extern "C" { +#endif + struct dive; extern void dive_list_update_dives(void); @@ -16,4 +20,9 @@ extern void select_prev_dive(void); extern void show_and_select_dive(struct dive *dive); extern double init_decompression(struct dive * dive); extern void export_all_dives_uddf_cb(); + +#ifdef __cplusplus +} +#endif + #endif diff --git a/gtk-gui.c b/gtk-gui.c deleted file mode 100644 index 91a70acf0..000000000 --- a/gtk-gui.c +++ /dev/null @@ -1,2363 +0,0 @@ -/* gtk-gui.c */ -/* gtk UI implementation */ -/* creates the window and overall layout - * divelist, dive info, equipment and printing are handled in their own source files - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dive.h" -#include "divelist.h" -#include "display.h" -#include "display-gtk.h" -#include "callbacks-gtk.h" -#include "uemis.h" -#include "device.h" -#include "webservice.h" -#include "version.h" -#include "libdivecomputer.h" - -#include -#include -#include "subsurface-icon.h" - -#if HAVE_OSM_GPS_MAP -#include -#endif - -GtkWidget *main_window; -GtkWidget *main_vbox; -GtkWidget *error_info_bar; -GtkWidget *error_label; -GtkWidget *vpane, *hpane; -GtkWidget *notebook; - -int error_count; -const char *existing_filename; - -typedef enum { PANE_INFO, PANE_PROFILE, PANE_LIST, PANE_THREE } pane_conf_t; -static pane_conf_t pane_conf; - -static struct device_info *holdnicknames = NULL; -static GtkWidget *dive_profile_widget(void); -static void import_files(GtkWidget *, gpointer); - -static void remember_dc(const char *model, uint32_t deviceid, const char *nickname) -{ - struct device_info *nn_entry; - - nn_entry = create_device_info(model, deviceid); - if (!nn_entry) - return; - if (!nickname || !*nickname) { - nn_entry->nickname = NULL; - return; - } - nn_entry->nickname = strdup(nickname); -} - -static void remove_dc(const char *model, uint32_t deviceid) -{ - free(remove_device_info(model, deviceid)); -} - -static GtkWidget *dive_profile; - -GtkActionGroup *action_group; - -void repaint_dive(void) -{ - update_dive(current_dive); - if (dive_profile) - gtk_widget_queue_draw(dive_profile); -} - -static gboolean need_icon = TRUE; - -static void on_info_bar_response(GtkWidget *widget, gint response, - gpointer data) -{ - if (response == GTK_RESPONSE_OK) - { - gtk_widget_destroy(widget); - error_info_bar = NULL; - } -} - -void report_error(GError* error) -{ - if (error == NULL) - { - return; - } - - if (error_info_bar == NULL) - { - error_count = 1; - error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK, - GTK_RESPONSE_OK, - NULL); - g_signal_connect(error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL); - gtk_info_bar_set_message_type(GTK_INFO_BAR(error_info_bar), - GTK_MESSAGE_ERROR); - - error_label = gtk_label_new(error->message); - GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(error_info_bar)); - gtk_container_add(GTK_CONTAINER(container), error_label); - - gtk_box_pack_start(GTK_BOX(main_vbox), error_info_bar, FALSE, FALSE, 0); - gtk_widget_show_all(main_vbox); - } - else - { - error_count++; - char buffer[256]; - snprintf(buffer, sizeof(buffer), _("Failed to open %i files."), error_count); - gtk_label_set_text(GTK_LABEL(error_label), buffer); - } -} - -static GtkFileFilter *setup_filter(void) -{ - GtkFileFilter *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, "*.uddf"); - gtk_file_filter_add_pattern(filter, "*.UDDF"); - gtk_file_filter_add_pattern(filter, "*.udcf"); - gtk_file_filter_add_pattern(filter, "*.UDCF"); - gtk_file_filter_add_pattern(filter, "*.jlb"); - gtk_file_filter_add_pattern(filter, "*.JLB"); -#ifdef LIBZIP - gtk_file_filter_add_pattern(filter, "*.sde"); - gtk_file_filter_add_pattern(filter, "*.SDE"); - gtk_file_filter_add_pattern(filter, "*.dld"); - gtk_file_filter_add_pattern(filter, "*.DLD"); -#endif -#ifdef SQLITE3 - gtk_file_filter_add_pattern(filter, "*.DB"); - gtk_file_filter_add_pattern(filter, "*.db"); -#endif - - gtk_file_filter_add_mime_type(filter, "text/xml"); - gtk_file_filter_set_name(filter, _("XML file")); - - return filter; -} - -static void file_save_as(GtkWidget *w, gpointer data) -{ - GtkWidget *dialog; - char *filename = NULL; - char *current_file; - char *current_dir; - - dialog = gtk_file_chooser_dialog_new(_("Save File As"), - GTK_WINDOW(main_window), - GTK_FILE_CHOOSER_ACTION_SAVE, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, - NULL); - gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); - - if (existing_filename) { - current_dir = g_path_get_dirname(existing_filename); - current_file = g_path_get_basename(existing_filename); - } else { - const char *current_default = prefs.default_filename; - current_dir = g_path_get_dirname(current_default); - current_file = g_path_get_basename(current_default); - } - gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), current_dir); - gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), current_file); - - free(current_dir); - free(current_file); - - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); - } - gtk_widget_destroy(dialog); - - if (filename){ - save_dives(filename); - set_filename(filename, TRUE); - g_free(filename); - mark_divelist_changed(FALSE); - } -} - -static void file_save(GtkWidget *w, gpointer data) -{ - const char *current_default; - - if (!existing_filename) - return file_save_as(w, data); - - current_default = prefs.default_filename; - if (strcmp(existing_filename, current_default) == 0) { - /* if we are using the default filename the directory - * that we are creating the file in may not exist */ - char *current_def_dir; - struct stat sb; - - current_def_dir = g_path_get_dirname(existing_filename); - if (stat(current_def_dir, &sb) != 0) { - g_mkdir(current_def_dir, S_IRWXU); - } - free(current_def_dir); - } - save_dives(existing_filename); - mark_divelist_changed(FALSE); -} - -static gboolean ask_save_changes() -{ - GtkWidget *dialog, *label, *content; - gboolean quit = TRUE; - dialog = gtk_dialog_new_with_buttons(_("Save Changes?"), - GTK_WINDOW(main_window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, - GTK_STOCK_NO, GTK_RESPONSE_NO, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - NULL); - content = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); - - if (!existing_filename){ - label = gtk_label_new ( - _("You have unsaved changes\nWould you like to save those before closing the datafile?")); - } else { - char *label_text = (char*) malloc(sizeof(char) * - (strlen(_("You have unsaved changes to file: %s \nWould you like to save those before closing the datafile?")) + - strlen(existing_filename))); - sprintf(label_text, - _("You have unsaved changes to file: %s \nWould you like to save those before closing the datafile?"), - existing_filename); - label = gtk_label_new (label_text); - free(label_text); - } - gtk_container_add (GTK_CONTAINER (content), label); - gtk_widget_show_all (dialog); - gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT); - gint outcode = gtk_dialog_run(GTK_DIALOG(dialog)); - if (outcode == GTK_RESPONSE_ACCEPT) { - file_save(NULL,NULL); - } else if (outcode == GTK_RESPONSE_CANCEL || outcode == GTK_RESPONSE_DELETE_EVENT) { - quit = FALSE; - } - gtk_widget_destroy(dialog); - return quit; -} - -static void file_close(GtkWidget *w, gpointer data) -{ - if (unsaved_changes()) - if (ask_save_changes() == FALSE) - return; - - if (existing_filename) - free((void *)existing_filename); - existing_filename = NULL; - - /* free the dives and trips */ - while (dive_table.nr) - delete_single_dive(0); - mark_divelist_changed(FALSE); - - /* clear the selection and the statistics */ - selected_dive = 0; - process_selected_dives(); - clear_stats_widgets(); - clear_events(); - show_dive_stats(NULL); - - /* clear the equipment page */ - clear_equipment_widgets(); - - /* redraw the screen */ - dive_list_update_dives(); - show_dive_info(NULL); -} - -static void file_open(GtkWidget *w, gpointer data) -{ - GtkWidget *dialog; - GtkFileFilter *filter; - const char *current_default; - - 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); - current_default = prefs.default_filename; - gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), current_default); - /* when opening the data file we should allow only one file to be chosen */ - gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE); - - filter = setup_filter(); - gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter); - - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - GSList *fn_glist; - char *filename; - - /* first, close the existing file, if any, and forget its name */ - file_close(w, data); - free((void *)existing_filename); - existing_filename = NULL; - - /* we know there is only one filename */ - fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); - - GError *error = NULL; - filename = fn_glist->data; - parse_file(filename, &error, TRUE); - if (error != NULL) - { - report_error(error); - g_error_free(error); - error = NULL; - } - g_free(filename); - g_slist_free(fn_glist); - report_dives(FALSE, FALSE); - } - gtk_widget_destroy(dialog); -} - -void save_pane_position() -{ - gint vpane_position = gtk_paned_get_position(GTK_PANED(vpane)); - gint hpane_position = gtk_paned_get_position(GTK_PANED(hpane)); - gboolean is_maximized = gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(main_window))) & - GDK_WINDOW_STATE_MAXIMIZED; - - if (pane_conf == PANE_THREE){ - if (is_maximized) { - subsurface_set_conf_int("vpane_position_maximized", vpane_position); - subsurface_set_conf_int("hpane_position_maximized", hpane_position); - } else { - subsurface_set_conf_int("vpane_position", vpane_position); - subsurface_set_conf_int("hpane_position", hpane_position); - } - } -} - -/* Since we want direct control of what set of parameters to retrive, this function - * has an input argument. */ -void restore_pane_position(gboolean maximized) -{ - if (maximized) { - gtk_paned_set_position(GTK_PANED(vpane), subsurface_get_conf_int("vpane_position_maximized")); - gtk_paned_set_position(GTK_PANED(hpane), subsurface_get_conf_int("hpane_position_maximized")); - } else { - gtk_paned_set_position(GTK_PANED(vpane), subsurface_get_conf_int("vpane_position")); - gtk_paned_set_position(GTK_PANED(hpane), subsurface_get_conf_int("hpane_position")); - } -} - -void save_window_geometry(void) -{ - /* GDK_GRAVITY_NORTH_WEST assumed ( it is the default ) */ - int window_width, window_height; - gboolean is_maximized; - - gtk_window_get_size(GTK_WINDOW(main_window), &window_width, &window_height); - is_maximized = gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(main_window))) & - GDK_WINDOW_STATE_MAXIMIZED; - subsurface_set_conf_int("window_width", window_width); - subsurface_set_conf_int("window_height", window_height); - subsurface_set_conf_bool("window_maximized", is_maximized); - save_pane_position(); - subsurface_flush_conf(); -} - -void restore_window_geometry(void) -{ - int window_width, window_height; - gboolean is_maximized = subsurface_get_conf_bool("window_maximized") > 0; - - window_height = subsurface_get_conf_int("window_height"); - window_width = subsurface_get_conf_int("window_width"); - - window_height == -1 ? window_height = 300 : window_height; - window_width == -1 ? window_width = 700 : window_width; - - restore_pane_position(is_maximized); - /* don't resize the window if in maximized state */ - if (is_maximized) - gtk_window_maximize(GTK_WINDOW(main_window)); - else - gtk_window_resize(GTK_WINDOW(main_window), window_width, window_height); -} - -gboolean on_delete(GtkWidget* w, gpointer data) -{ - /* Make sure to flush any modified dive data */ - update_dive(NULL); - - gboolean quit = TRUE; - if (unsaved_changes()) - quit = ask_save_changes(); - - if (quit){ - save_window_geometry(); - return FALSE; /* go ahead, kill the program, we're good now */ - } else { - return TRUE; /* We are not leaving */ - } -} - -static void on_destroy(GtkWidget* w, gpointer data) -{ - dive_list_destroy(); - info_widget_destroy(); - gtk_main_quit(); -} - -/* This "window-state-event" callback will be called after the actual action, such - * as maximize or restore. This means that if you have methods here that check - * for the current window state, they will obtain the already updated state... */ -static gboolean on_state(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data) -{ - gint vpane_position, hpane_position; - if (event->changed_mask & GDK_WINDOW_STATE_WITHDRAWN || - event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) - return TRUE; /* do nothing if the window is shown for the first time or minimized */ - if (pane_conf == PANE_THREE) { - hpane_position = gtk_paned_get_position(GTK_PANED(hpane)); - vpane_position = gtk_paned_get_position(GTK_PANED(vpane)); - if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) { /* maximize */ - subsurface_set_conf_int("vpane_position", vpane_position); - subsurface_set_conf_int("hpane_position", hpane_position); - restore_pane_position(TRUE); - } else if (event->new_window_state == 0) { /* restore */ - subsurface_set_conf_int("vpane_position_maximized", vpane_position); - subsurface_set_conf_int("hpane_position_maximized", hpane_position); - restore_pane_position(FALSE); - } - } - return TRUE; -} - -static void quit(GtkWidget *w, gpointer data) -{ - /* Make sure to flush any modified dive data */ - update_dive(NULL); - - gboolean quit = TRUE; - if (unsaved_changes()) - quit = ask_save_changes(); - - if (quit){ - save_window_geometry(); - dive_list_destroy(); - gtk_main_quit(); - } -} - -GtkTreeViewColumn *tree_view_column_add_pixbuf(GtkWidget *tree_view, data_func_t data_func, GtkTreeViewColumn *col) -{ - GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new(); - gtk_tree_view_column_pack_start(col, renderer, FALSE); - gtk_tree_view_column_set_cell_data_func(col, renderer, data_func, NULL, NULL); - g_signal_connect(tree_view, "button-press-event", G_CALLBACK(icon_click_cb), col); - return col; -} - -GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title, - data_func_t data_func, unsigned int flags) -{ - GtkCellRenderer *renderer; - GtkTreeViewColumn *col; - double xalign = 0.0; /* left as default */ - PangoAlignment align; - gboolean visible; - - align = (flags & ALIGN_LEFT) ? PANGO_ALIGN_LEFT : - (flags & ALIGN_RIGHT) ? PANGO_ALIGN_RIGHT : - PANGO_ALIGN_CENTER; - visible = !(flags & INVISIBLE); - - renderer = gtk_cell_renderer_text_new(); - col = gtk_tree_view_column_new(); - - if (flags & EDITABLE) { - g_object_set(renderer, "editable", TRUE, NULL); - g_signal_connect(renderer, "edited", (GCallback) data_func, tree_view); - data_func = NULL; - } - - gtk_tree_view_column_set_title(col, title); - if (!(flags & UNSORTABLE)) - gtk_tree_view_column_set_sort_column_id(col, index); - gtk_tree_view_column_set_resizable(col, TRUE); - /* all but one column have only one renderer - so packing from the end - * makes no difference; for the location column we want to be able to - * prepend the icon in front of the text - so this works perfectly */ - gtk_tree_view_column_pack_end(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); - 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; -} - -/* Helper functions for gtk combo boxes */ -GtkEntry *get_entry(GtkComboBox *combo_box) -{ - return GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo_box))); -} - -const char *get_active_text(GtkComboBox *combo_box) -{ - return gtk_entry_get_text(get_entry(combo_box)); -} - -void set_active_text(GtkComboBox *combo_box, const char *text) -{ - gtk_entry_set_text(get_entry(combo_box), text); -} - -GtkWidget *combo_box_with_model_and_entry(GtkListStore *model) -{ - GtkWidget *widget; - GtkEntryCompletion *completion; - -#if GTK_CHECK_VERSION(2,24,0) - widget = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model)); - gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(widget), 0); -#else - widget = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0); - gtk_combo_box_entry_set_text_column(GTK_COMBO_BOX_ENTRY(widget), 0); -#endif - - completion = gtk_entry_completion_new(); - gtk_entry_completion_set_text_column(completion, 0); - gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(model)); - gtk_entry_completion_set_inline_completion(completion, TRUE); - gtk_entry_completion_set_inline_selection(completion, TRUE); - gtk_entry_completion_set_popup_single_match(completion, FALSE); - gtk_entry_set_completion(get_entry(GTK_COMBO_BOX(widget)), completion); - g_object_unref(completion); - - return widget; -} - -static void create_radio(GtkWidget *vbox, const char *w_name, ...) -{ - va_list args; - GtkRadioButton *group = NULL; - GtkWidget *box, *label; - - box = gtk_hbox_new(TRUE, 10); - gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 0); - - label = gtk_label_new(w_name); - gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0); - - va_start(args, w_name); - for (;;) { - int enabled; - const char *name; - GtkWidget *button; - void *callback_fn; - - name = va_arg(args, char *); - if (!name) - break; - callback_fn = va_arg(args, void *); - enabled = va_arg(args, int); - - button = gtk_radio_button_new_with_label_from_widget(group, name); - group = GTK_RADIO_BUTTON(button); - gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), enabled); - g_signal_connect(button, "toggled", G_CALLBACK(callback_fn), NULL); - } - va_end(args); -} - -void update_screen() -{ - update_dive_list_units(); - repaint_dive(); - update_dive_list_col_visibility(); -} - -UNITCALLBACK(set_meter, length, METERS) -UNITCALLBACK(set_feet, length, FEET) -UNITCALLBACK(set_bar, pressure, BAR) -UNITCALLBACK(set_psi, pressure, PSI) -UNITCALLBACK(set_liter, volume, LITER) -UNITCALLBACK(set_cuft, volume, CUFT) -UNITCALLBACK(set_celsius, temperature, CELSIUS) -UNITCALLBACK(set_fahrenheit, temperature, FAHRENHEIT) -UNITCALLBACK(set_kg, weight, KG) -UNITCALLBACK(set_lbs, weight, LBS) - -OPTIONCALLBACK(otu_toggle, prefs.visible_cols.otu) -OPTIONCALLBACK(maxcns_toggle, prefs.visible_cols.maxcns) -OPTIONCALLBACK(sac_toggle, prefs.visible_cols.sac) -OPTIONCALLBACK(nitrox_toggle, prefs.visible_cols.nitrox) -OPTIONCALLBACK(temperature_toggle, prefs.visible_cols.temperature) -OPTIONCALLBACK(totalweight_toggle, prefs.visible_cols.totalweight) -OPTIONCALLBACK(suit_toggle, prefs.visible_cols.suit) -OPTIONCALLBACK(cylinder_toggle, prefs.visible_cols.cylinder) -OPTIONCALLBACK(po2_toggle, prefs.pp_graphs.po2) -OPTIONCALLBACK(pn2_toggle, prefs.pp_graphs.pn2) -OPTIONCALLBACK(phe_toggle, prefs.pp_graphs.phe) -OPTIONCALLBACK(mod_toggle, prefs.mod) -OPTIONCALLBACK(ead_toggle, prefs.ead) -OPTIONCALLBACK(red_ceiling_toggle, prefs.profile_red_ceiling) -OPTIONCALLBACK(calc_ceiling_toggle, prefs.profile_calc_ceiling) -OPTIONCALLBACK(calc_ceiling_3m_toggle, prefs.calc_ceiling_3m_incr) - -static gboolean gflow_edit(GtkWidget *w, GdkEvent *event, gpointer _data) -{ - double gflow; - const char *buf; - if (event->type == GDK_FOCUS_CHANGE) { - buf = gtk_entry_get_text(GTK_ENTRY(w)); - sscanf(buf, "%lf", &gflow); - prefs.gflow = gflow / 100.0; - set_gf(prefs.gflow, -1.0); - update_screen(); - } - return FALSE; -} - -static gboolean gfhigh_edit(GtkWidget *w, GdkEvent *event, gpointer _data) -{ - double gfhigh; - const char *buf; - if (event->type == GDK_FOCUS_CHANGE) { - buf = gtk_entry_get_text(GTK_ENTRY(w)); - sscanf(buf, "%lf", &gfhigh); - prefs.gfhigh = gfhigh / 100.0; - set_gf(-1.0, prefs.gfhigh); - update_screen(); - } - return FALSE; -} - -static void event_toggle(GtkWidget *w, gpointer _data) -{ - gboolean *plot_ev = _data; - - *plot_ev = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)); -} - -static void pick_default_file(GtkWidget *w, GtkButton *button) -{ - GtkWidget *fs_dialog, *parent; - const char *current_default; - char *current_def_file, *current_def_dir; - GtkFileFilter *filter; - struct stat sb; - - fs_dialog = gtk_file_chooser_dialog_new(_("Choose Default XML File"), - GTK_WINDOW(main_window), - GTK_FILE_CHOOSER_ACTION_SAVE, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - NULL); - parent = gtk_widget_get_ancestor(w, GTK_TYPE_DIALOG); - gtk_widget_set_sensitive(parent, FALSE); - gtk_window_set_transient_for(GTK_WINDOW(fs_dialog), GTK_WINDOW(parent)); - - current_default = prefs.default_filename; - current_def_dir = g_path_get_dirname(current_default); - current_def_file = g_path_get_basename(current_default); - - /* it's possible that the directory doesn't exist (especially for the default) - * For gtk's file select box to make sense we create it */ - if (stat(current_def_dir, &sb) != 0) - g_mkdir(current_def_dir, S_IRWXU); - - gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs_dialog), current_def_dir); - gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(fs_dialog), current_def_file); - gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(fs_dialog), FALSE); - filter = setup_filter(); - gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fs_dialog), filter); - gtk_widget_show_all(fs_dialog); - if (gtk_dialog_run(GTK_DIALOG(fs_dialog)) == GTK_RESPONSE_ACCEPT) { - GSList *list; - - list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(fs_dialog)); - if (g_slist_length(list) == 1) - gtk_button_set_label(button, list->data); - g_slist_free(list); - } - - free(current_def_dir); - free(current_def_file); - gtk_widget_destroy(fs_dialog); - - gtk_widget_set_sensitive(parent, TRUE); -} - -#if HAVE_OSM_GPS_MAP -static GtkWidget * map_provider_widget() -{ - OsmGpsMapSource_t i; -#if GTK_CHECK_VERSION(2,24,0) - GtkWidget *combobox = gtk_combo_box_text_new(); - - /* several of the providers seem to be redundant or non-functional; - * we may have to skip more than just the last three eventually */ - for (i = OSM_GPS_MAP_SOURCE_OPENSTREETMAP; i < OSM_GPS_MAP_SOURCE_LAST; i++) - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combobox), osm_gps_map_source_get_friendly_name(i)); -#else - GtkWidget *combobox = gtk_combo_box_new_text(); - for (i = OSM_GPS_MAP_SOURCE_OPENSTREETMAP; i < OSM_GPS_MAP_SOURCE_LAST; i++) - gtk_combo_box_append_text(GTK_COMBO_BOX(combobox), osm_gps_map_source_get_friendly_name(i)); -#endif - /* we don't offer choice 0 (none), so the index here is off by one */ - gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), prefs.map_provider - 1); - return combobox; -} -#endif - -static void preferences_dialog(GtkWidget *w, gpointer data) -{ - int result; - GtkWidget *dialog, *notebook, *font, *frame, *box, *hbox, *vbox, *button; - GtkWidget *xmlfile_button; -#if HAVE_OSM_GPS_MAP - GtkWidget *map_provider; -#endif - GtkWidget *entry_po2, *entry_pn2, *entry_phe, *entry_mod, *entry_gflow, *entry_gfhigh; - const char *current_default, *new_default; - char threshold_text[10], mod_text[10], utf8_buf[128]; - struct preferences oldprefs = prefs; - - dialog = gtk_dialog_new_with_buttons(_("Preferences"), - GTK_WINDOW(main_window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - NULL); - - /* create the notebook for the preferences and attach it to dialog */ - notebook = gtk_notebook_new(); - vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - gtk_box_pack_start(GTK_BOX(vbox), notebook, FALSE, FALSE, 5); - - /* vbox that holds the first notebook page */ - vbox = gtk_vbox_new(FALSE, 6); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, - gtk_label_new(_("General Settings"))); - frame = gtk_frame_new(_("Units")); - gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); - - box = gtk_vbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(frame), box); - - create_radio(box, _("Depth:"), - _("Meter"), set_meter, (prefs.units.length == METERS), - _("Feet"), set_feet, (prefs.units.length == FEET), - NULL); - - create_radio(box, _("Pressure:"), - _("Bar"), set_bar, (prefs.units.pressure == BAR), - _("PSI"), set_psi, (prefs.units.pressure == PSI), - NULL); - - create_radio(box, _("Volume:"), - _("Liter"), set_liter, (prefs.units.volume == LITER), - _("CuFt"), set_cuft, (prefs.units.volume == CUFT), - NULL); - - create_radio(box, _("Temperature:"), - _("Celsius"), set_celsius, (prefs.units.temperature == CELSIUS), - _("Fahrenheit"), set_fahrenheit, (prefs.units.temperature == FAHRENHEIT), - NULL); - - create_radio(box, _("Weight:"), - _("kg"), set_kg, (prefs.units.weight == KG), - _("lbs"), set_lbs, (prefs.units.weight == LBS), - NULL); - - frame = gtk_frame_new(_("Show Columns")); - gtk_box_pack_start(GTK_BOX(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(_("Temp")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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(_("Cyl")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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("O" UTF8_SUBSCRIPT_2 "%"); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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(_("SAC")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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(_("Weight")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.visible_cols.totalweight); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(totalweight_toggle), NULL); - - button = gtk_check_button_new_with_label(_("Suit")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.visible_cols.suit); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(suit_toggle), NULL); - - frame = gtk_frame_new(_("Divelist Font")); - gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); - - font = gtk_font_button_new_with_font(prefs.divelist_font); - gtk_container_add(GTK_CONTAINER(frame),font); - - frame = gtk_frame_new(_("Misc. Options")); - gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); - - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(frame), box); - - frame = gtk_frame_new(_("Default XML Data File")); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 5); - hbox = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(frame), hbox); - current_default = prefs.default_filename; - xmlfile_button = gtk_button_new_with_label(current_default); - g_signal_connect(G_OBJECT(xmlfile_button), "clicked", - G_CALLBACK(pick_default_file), xmlfile_button); - gtk_box_pack_start(GTK_BOX(hbox), xmlfile_button, FALSE, FALSE, 6); -#if HAVE_OSM_GPS_MAP - frame = gtk_frame_new(_("Map provider")); - map_provider = map_provider_widget(); - gtk_container_add(GTK_CONTAINER(frame), map_provider); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 3); -#endif - /* vbox that holds the second notebook page */ - vbox = gtk_vbox_new(FALSE, 6); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, - gtk_label_new(_("Tec Settings"))); - - frame = gtk_frame_new(_("Show Columns")); - gtk_box_pack_start(GTK_BOX(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(_("OTU")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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); - - button = gtk_check_button_new_with_label(_("maxCNS")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.visible_cols.maxcns); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(maxcns_toggle), NULL); - - frame = gtk_frame_new(_("Profile Settings")); - gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); - - vbox = gtk_vbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(frame), vbox); - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(vbox), box); - - sprintf(utf8_buf, _("Show pO%s graph"), UTF8_SUBSCRIPT_2); - button = gtk_check_button_new_with_label(utf8_buf); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.pp_graphs.po2); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(po2_toggle), &entry_po2); - - sprintf(utf8_buf, _("pO%s threshold"), UTF8_SUBSCRIPT_2); - frame = gtk_frame_new(utf8_buf); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); - entry_po2 = gtk_entry_new(); - gtk_entry_set_max_length(GTK_ENTRY(entry_po2), 4); - snprintf(threshold_text, sizeof(threshold_text), "%.1f", prefs.pp_graphs.po2_threshold); - gtk_entry_set_text(GTK_ENTRY(entry_po2), threshold_text); - gtk_widget_set_sensitive(entry_po2, prefs.pp_graphs.po2); - gtk_container_add(GTK_CONTAINER(frame), entry_po2); - - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(vbox), box); - - sprintf(utf8_buf, _("Show pN%s graph"), UTF8_SUBSCRIPT_2); - button = gtk_check_button_new_with_label(utf8_buf); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.pp_graphs.pn2); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(pn2_toggle), &entry_pn2); - - sprintf(utf8_buf, _("pN%s threshold"), UTF8_SUBSCRIPT_2); - frame = gtk_frame_new(utf8_buf); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); - entry_pn2 = gtk_entry_new(); - gtk_entry_set_max_length(GTK_ENTRY(entry_pn2), 4); - snprintf(threshold_text, sizeof(threshold_text), "%.1f", prefs.pp_graphs.pn2_threshold); - gtk_entry_set_text(GTK_ENTRY(entry_pn2), threshold_text); - gtk_widget_set_sensitive(entry_pn2, prefs.pp_graphs.pn2); - gtk_container_add(GTK_CONTAINER(frame), entry_pn2); - - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(vbox), box); - - button = gtk_check_button_new_with_label(_("Show pHe graph")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.pp_graphs.phe); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(phe_toggle), &entry_phe); - - frame = gtk_frame_new(_("pHe threshold")); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); - entry_phe = gtk_entry_new(); - gtk_entry_set_max_length(GTK_ENTRY(entry_phe), 4); - snprintf(threshold_text, sizeof(threshold_text), "%.1f", prefs.pp_graphs.phe_threshold); - gtk_entry_set_text(GTK_ENTRY(entry_phe), threshold_text); - gtk_widget_set_sensitive(entry_phe, prefs.pp_graphs.phe); - gtk_container_add(GTK_CONTAINER(frame), entry_phe); - - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(vbox), box); - - button = gtk_check_button_new_with_label(_("Show MOD")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.mod); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(mod_toggle), &entry_mod); - - frame = gtk_frame_new(_("max ppO2")); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); - entry_mod = gtk_entry_new(); - gtk_entry_set_max_length(GTK_ENTRY(entry_mod), 4); - snprintf(mod_text, sizeof(mod_text), "%.1f", prefs.mod_ppO2); - gtk_entry_set_text(GTK_ENTRY(entry_mod), mod_text); - gtk_widget_set_sensitive(entry_mod, prefs.mod); - gtk_container_add(GTK_CONTAINER(frame), entry_mod); - - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(vbox), box); - - button = gtk_check_button_new_with_label(_("Show EAD, END, EADD")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.ead); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(ead_toggle), NULL); - - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(vbox), box); - - button = gtk_check_button_new_with_label(_("Show dc reported ceiling in red")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.profile_red_ceiling); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(red_ceiling_toggle), NULL); - - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(vbox), box); - - button = gtk_check_button_new_with_label(_("Show calculated ceiling")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.profile_calc_ceiling); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(calc_ceiling_toggle), NULL); - - button = gtk_check_button_new_with_label(_("3m increments for calculated ceiling")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.calc_ceiling_3m_incr); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(calc_ceiling_3m_toggle), NULL); - - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(vbox), box); - - frame = gtk_frame_new(_("GFlow")); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); - entry_gflow = gtk_entry_new(); - gtk_entry_set_max_length(GTK_ENTRY(entry_gflow), 4); - snprintf(threshold_text, sizeof(threshold_text), "%.0f", prefs.gflow * 100); - gtk_entry_set_text(GTK_ENTRY(entry_gflow), threshold_text); - gtk_container_add(GTK_CONTAINER(frame), entry_gflow); - gtk_widget_add_events(entry_gflow, GDK_FOCUS_CHANGE_MASK); - g_signal_connect(G_OBJECT(entry_gflow), "event", G_CALLBACK(gflow_edit), NULL); - - frame = gtk_frame_new(_("GFhigh")); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); - entry_gfhigh = gtk_entry_new(); - gtk_entry_set_max_length(GTK_ENTRY(entry_gfhigh), 4); - snprintf(threshold_text, sizeof(threshold_text), "%.0f", prefs.gfhigh * 100); - gtk_entry_set_text(GTK_ENTRY(entry_gfhigh), threshold_text); - gtk_container_add(GTK_CONTAINER(frame), entry_gfhigh); - gtk_widget_add_events(entry_gfhigh, GDK_FOCUS_CHANGE_MASK); - g_signal_connect(G_OBJECT(entry_gfhigh), "event", G_CALLBACK(gfhigh_edit), NULL); - - gtk_widget_show_all(dialog); - result = gtk_dialog_run(GTK_DIALOG(dialog)); - if (result == GTK_RESPONSE_ACCEPT) { - const char *po2_threshold_text, *pn2_threshold_text, *phe_threshold_text, *mod_text, *gflow_text, *gfhigh_text; - /* Make sure to flush any modified old dive data with old units */ - update_dive(NULL); - - prefs.divelist_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font))); - set_divelist_font(prefs.divelist_font); - po2_threshold_text = gtk_entry_get_text(GTK_ENTRY(entry_po2)); - sscanf(po2_threshold_text, "%lf", &prefs.pp_graphs.po2_threshold); - pn2_threshold_text = gtk_entry_get_text(GTK_ENTRY(entry_pn2)); - sscanf(pn2_threshold_text, "%lf", &prefs.pp_graphs.pn2_threshold); - phe_threshold_text = gtk_entry_get_text(GTK_ENTRY(entry_phe)); - sscanf(phe_threshold_text, "%lf", &prefs.pp_graphs.phe_threshold); - mod_text = gtk_entry_get_text(GTK_ENTRY(entry_mod)); - sscanf(mod_text, "%lf", &prefs.mod_ppO2); - gflow_text = gtk_entry_get_text(GTK_ENTRY(entry_gflow)); - sscanf(gflow_text, "%lf", &prefs.gflow); - gfhigh_text = gtk_entry_get_text(GTK_ENTRY(entry_gfhigh)); - sscanf(gfhigh_text, "%lf", &prefs.gfhigh); - prefs.gflow /= 100.0; - prefs.gfhigh /= 100.0; - set_gf(prefs.gflow, prefs.gfhigh); - - update_screen(); - - new_default = strdup(gtk_button_get_label(GTK_BUTTON(xmlfile_button))); - - /* if we opened the default file and are changing its name, - * update existing_filename */ - if (existing_filename) { - if (strcmp(current_default, existing_filename) == 0) { - free((void *)existing_filename); - existing_filename = strdup(new_default); - } - } - if (strcmp(current_default, new_default)) { - prefs.default_filename = new_default; - } -#if HAVE_OSM_GPS_MAP - /* get the map provider selected */ - OsmGpsMapSource_t i; -#if GTK_CHECK_VERSION(2,24,0) - char *provider = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(map_provider)); -#else - char *provider = gtk_combo_box_get_active_text(GTK_COMBO_BOX(map_provider)); -#endif - for (i = OSM_GPS_MAP_SOURCE_OPENSTREETMAP; i <= OSM_GPS_MAP_SOURCE_YAHOO_STREET; i++) - if (!strcmp(provider,osm_gps_map_source_get_friendly_name(i))) { - prefs.map_provider = i; - break; - } - free((void *)provider); -#endif - save_preferences(); - } else if (result == GTK_RESPONSE_CANCEL) { - prefs = oldprefs; - set_gf(prefs.gflow, prefs.gfhigh); - update_screen(); - } - 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, *label; - - dialog = gtk_dialog_new_with_buttons(_("Select Events"), - 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); - if (!evn_foreach(&create_toggle, table)) { - g_object_ref_sink(G_OBJECT(table)); - label = gtk_label_new(_("\nNo Events\n")); - gtk_container_add(GTK_CONTAINER(frame), label); - } else { - gtk_container_add(GTK_CONTAINER(frame), table); - } - - gtk_widget_show_all(dialog); - result = gtk_dialog_run(GTK_DIALOG(dialog)); - if (result == GTK_RESPONSE_ACCEPT) { - repaint_dive(); - } - gtk_widget_destroy(dialog); -} - -static void autogroup_cb(GtkWidget *w, gpointer data) -{ - autogroup = !autogroup; - if (! autogroup) - remove_autogen_trips(); - dive_list_update_dives(); -} - -void set_autogroup(gboolean value) -{ - GtkAction *autogroup_action; - - if (value == autogroup) - return; - - autogroup_action = gtk_action_group_get_action(action_group, "Autogroup"); - gtk_action_activate(autogroup_action); -} - -static void renumber_dialog(GtkWidget *w, gpointer data) -{ - int result; - struct dive *dive; - GtkWidget *dialog, *frame, *button, *vbox; - - dialog = gtk_dialog_new_with_buttons(_("Renumber"), - GTK_WINDOW(main_window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, - NULL); - - vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - - frame = gtk_frame_new(_("New starting number")); - 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) { - int nr = gtk_spin_button_get_value(GTK_SPIN_BUTTON(button)); - renumber_dives(nr); - repaint_dive(); - } - gtk_widget_destroy(dialog); -} - -static void about_dialog_link_cb(GtkAboutDialog *dialog, const gchar *link, gpointer data) -{ - subsurface_launch_for_uri(link); -} - -static void about_dialog(GtkWidget *w, gpointer data) -{ - const char *logo_property = NULL; - GdkPixbuf *logo = NULL; - GtkWidget *dialog; - - if (need_icon) { - logo_property = "logo"; - logo = gdk_pixbuf_from_pixdata(&subsurface_icon_pixbuf, TRUE, NULL); - } - dialog = gtk_about_dialog_new(); -#if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 24) - gtk_about_dialog_set_url_hook(about_dialog_link_cb, NULL, NULL); /* deprecated since GTK 2.24 */ -#else - g_signal_connect(GTK_ABOUT_DIALOG(dialog), "activate-link", G_CALLBACK(about_dialog_link_cb), NULL); -#endif - g_object_set(GTK_OBJECT(dialog), - "title", _("About Subsurface"), - "program-name", "Subsurface", - "comments", _("Multi-platform divelog software in C"), - "website", "http://subsurface.hohndel.org", - "license", "GNU General Public License, version 2\nhttp://www.gnu.org/licenses/old-licenses/gpl-2.0.html", - "version", VERSION_STRING, - "copyright", _("Linus Torvalds, Dirk Hohndel, and others, 2011, 2012, 2013"), - /*++GETTEXT the term translator-credits is magic - list the names of the tranlators here */ - "translator_credits", _("translator-credits"), - "logo-icon-name", "subsurface", - /* Must be last: */ - logo_property, logo, - NULL); - if (logo) - g_object_unref(logo); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); -} - -static void show_user_manual(GtkWidget *w, gpointer data) -{ - subsurface_launch_for_uri("http://subsurface.hohndel.org/documentation/user-manual/"); -} - -static void view_list(GtkWidget *w, gpointer data) -{ - save_pane_position(); - gtk_paned_set_position(GTK_PANED(vpane), 0); - pane_conf = PANE_LIST; -} - -static void view_profile(GtkWidget *w, gpointer data) -{ - save_pane_position(); - gtk_paned_set_position(GTK_PANED(hpane), 0); - gtk_paned_set_position(GTK_PANED(vpane), 65535); - pane_conf = PANE_PROFILE; -} - -static void view_info(GtkWidget *w, gpointer data) -{ - - save_pane_position(); - gtk_paned_set_position(GTK_PANED(vpane), 65535); - gtk_paned_set_position(GTK_PANED(hpane), 65535); - pane_conf = PANE_INFO; -} - -static void view_three(GtkWidget *w, gpointer data) -{ - GtkAllocation alloc; - GtkRequisition requisition; - - int vpane_position = subsurface_get_conf_int("vpane_position"); - int hpane_position = subsurface_get_conf_int("hpane_position"); - - gtk_widget_get_allocation(hpane, &alloc); - - if (hpane_position) - gtk_paned_set_position(GTK_PANED(hpane), hpane_position); - else - gtk_paned_set_position(GTK_PANED(hpane), alloc.width/2); - - gtk_widget_get_allocation(vpane, &alloc); - gtk_widget_size_request(notebook, &requisition); - /* pick the requested size for the notebook plus 6 pixels for frame */ - if (vpane_position) - gtk_paned_set_position(GTK_PANED(vpane), vpane_position); - else - gtk_paned_set_position(GTK_PANED(vpane), requisition.height + 6); - - pane_conf = PANE_THREE; -} - -static void toggle_zoom(GtkWidget *w, gpointer data) -{ - zoomed_plot = (zoomed_plot)?0 : 1; - /*Update dive*/ - repaint_dive(); -} - -static void prev_dc(GtkWidget *w, gpointer data) -{ - dc_number--; - /* If the dc number underflows, we'll "wrap around" and use the last dc */ - repaint_dive(); -} - -static void next_dc(GtkWidget *w, gpointer data) -{ - dc_number++; - /* If the dc number overflows, we'll "wrap around" and zero it */ - repaint_dive(); -} - - -/* list columns for nickname edit treeview */ -enum { - NE_MODEL, - NE_ID_STR, - NE_NICKNAME, - NE_NCOL -}; - -/* delete a selection of nicknames */ -static void edit_dc_delete_rows(GtkTreeView *view) -{ - GtkTreeModel *model; - GtkTreePath *path; - GtkTreeIter iter; - GtkTreeRowReference *ref; - GtkTreeSelection *selection; - GList *selected_rows, *list, *row_references = NULL; - guint len; - /* params for delete op */ - const char *model_str; - const char *deviceid_string; /* convert to deviceid */ - uint32_t deviceid; - - selection = gtk_tree_view_get_selection(view); - selected_rows = gtk_tree_selection_get_selected_rows(selection, &model); - - for (list = selected_rows; list; list = g_list_next(list)) { - path = list->data; - ref = gtk_tree_row_reference_new(model, path); - row_references = g_list_append(row_references, ref); - } - len = g_list_length(row_references); - if (len == 0) - /* Warn about empty selection? */ - return; - - for (list = row_references; list; list = g_list_next(list)) { - path = gtk_tree_row_reference_get_path((GtkTreeRowReference *)(list->data)); - gtk_tree_model_get_iter(model, &iter, path); - gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, - NE_MODEL, &model_str, - NE_ID_STR, &deviceid_string, - -1); - if (sscanf(deviceid_string, "0x%x8", &deviceid) == 1) - remove_dc(model_str, deviceid); - - gtk_list_store_remove(GTK_LIST_STORE (model), &iter); - gtk_tree_path_free(path); - } - g_list_free(selected_rows); - g_list_free(row_references); - g_list_free(list); - - if (gtk_tree_model_get_iter_first(model, &iter)) - gtk_tree_selection_select_iter(selection, &iter); -} - -/* repopulate the edited nickname cell of a DC */ -static void cell_edited_cb(GtkCellRendererText *cell, gchar *path, - gchar *new_text, gpointer store) -{ - GtkTreeIter iter; - const char *model; - const char *deviceid_string; - uint32_t deviceid; - int matched; - - gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, path); - /* display new text */ - gtk_list_store_set(GTK_LIST_STORE(store), &iter, NE_NICKNAME, new_text, -1); - /* and new_text */ - gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, - NE_MODEL, &model, - NE_ID_STR, &deviceid_string, - -1); - /* extract deviceid */ - matched = sscanf(deviceid_string, "0x%x8", &deviceid); - - /* remember pending commit - * Need to extend list rather than wipe and store only one result */ - if (matched == 1){ - if (holdnicknames == NULL){ - holdnicknames = (struct device_info *) malloc(sizeof(struct device_info)); - holdnicknames->model = strdup(model); - holdnicknames->deviceid = deviceid; - holdnicknames->serial_nr = NULL; - holdnicknames->firmware = NULL; - holdnicknames->nickname = strdup(new_text); - holdnicknames->next = NULL; - } else { - struct device_info * top; - struct device_info * last = holdnicknames; - top = (struct device_info *) malloc(sizeof(struct device_info)); - top->model = strdup(model); - top->deviceid = deviceid; - top->serial_nr = NULL; - top->firmware = NULL; - top->nickname = strdup(new_text); - top->next = last; - holdnicknames = top; - } - } -} - -#define SUB_RESPONSE_DELETE 1 /* no delete response in gtk+2 */ -#define SUB_DONE 2 /* enable escape when done */ - -/* show the dialog to edit dc nicknames */ -static void edit_dc_nicknames(GtkWidget *w, gpointer data) -{ - const gchar *C_INACTIVE = "#e8e8ee", *C_ACTIVE = "#ffffff"; /* cell colours */ - GtkWidget *dialog, *confirm, *view, *scroll, *vbox; - GtkCellRenderer *renderer; - GtkTreeSelection *selection; - GtkTreeModel *model; - GtkListStore *store; - GtkTreeIter iter; - gint res = -1; - char id_string[11] = {0}; - struct device_info * nnl; - - dialog = gtk_dialog_new_with_buttons(_("Edit Dive Computer Nicknames"), - GTK_WINDOW(main_window), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_DELETE, - SUB_RESPONSE_DELETE, - GTK_STOCK_CANCEL, - GTK_RESPONSE_CANCEL, - GTK_STOCK_APPLY, - GTK_RESPONSE_APPLY, - NULL); - gtk_widget_set_size_request(dialog, 700, 400); - - scroll = gtk_scrolled_window_new(NULL, NULL); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); - - view = gtk_tree_view_new(); - store = gtk_list_store_new(NE_NCOL, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); - model = GTK_TREE_MODEL(store); - - /* columns */ - renderer = gtk_cell_renderer_text_new(); - g_object_set(renderer, "background", C_INACTIVE, NULL); - gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, _("Model"), - renderer, "text", NE_MODEL, NULL); - - renderer = gtk_cell_renderer_text_new(); - g_object_set(renderer, "background", C_INACTIVE, NULL); - gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, _("Device Id"), - renderer, "text", NE_ID_STR, NULL); - - renderer = gtk_cell_renderer_text_new(); - g_object_set(renderer, "background", C_INACTIVE, NULL); - gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, _("Nickname"), - renderer, "text", NE_NICKNAME, NULL); - g_object_set(renderer, "editable", TRUE, NULL); - g_object_set(renderer, "background", C_ACTIVE, NULL); - g_signal_connect(renderer, "edited", G_CALLBACK(cell_edited_cb), store); - - gtk_tree_view_set_model(GTK_TREE_VIEW(view), model); - g_object_unref(model); - - /* populate list store from device_info_list */ - nnl = head_of_device_info_list(); - while (nnl) { - sprintf(&id_string[0], "%#08x", nnl->deviceid); - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, - NE_MODEL, nnl->model, - NE_ID_STR, id_string, - NE_NICKNAME, nnl->nickname, - -1); - nnl = nnl->next; - } - - selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); - gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_SINGLE); - - vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - gtk_container_add(GTK_CONTAINER(scroll), view); - gtk_container_add(GTK_CONTAINER(vbox), - gtk_label_new(_("Edit a dive computer nickname by double-clicking the in the relevant nickname field"))); - gtk_container_add(GTK_CONTAINER(vbox), scroll); - gtk_widget_set_size_request(scroll, 500, 300); - gtk_widget_show_all(dialog); - - do { - res = gtk_dialog_run(GTK_DIALOG(dialog)); - if (res == SUB_RESPONSE_DELETE) { - confirm = gtk_dialog_new_with_buttons(_("Delete a dive computer information entry"), - GTK_WINDOW(dialog), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_YES, - GTK_RESPONSE_YES, - GTK_STOCK_NO, - GTK_RESPONSE_NO, - NULL); - gtk_widget_set_size_request(confirm, 350, 90); - vbox = gtk_dialog_get_content_area(GTK_DIALOG(confirm)); - gtk_container_add(GTK_CONTAINER(vbox), - gtk_label_new(_("Ok to delete the selected entry?"))); - gtk_widget_show_all(confirm); - if (gtk_dialog_run(GTK_DIALOG(confirm)) == GTK_RESPONSE_YES) { - edit_dc_delete_rows(GTK_TREE_VIEW(view)); - res = SUB_DONE; /* want to close ** both ** dialogs now */ - } - mark_divelist_changed(TRUE); - gtk_widget_destroy(confirm); - } - if (res == GTK_RESPONSE_APPLY && holdnicknames && holdnicknames->model != NULL) { - struct device_info * walk = holdnicknames; - struct device_info * release = holdnicknames; - struct device_info * track = holdnicknames->next; - while (walk) { - remember_dc(walk->model, walk->deviceid, walk->nickname); - walk = walk->next; - } - /* clear down list */ - while (release){ - free(release); - release = track; - if (track) - track = track->next; - } - holdnicknames = NULL; - mark_divelist_changed(TRUE); - } - } while (res != SUB_DONE && res != GTK_RESPONSE_CANCEL && res != GTK_RESPONSE_DELETE_EVENT && res != GTK_RESPONSE_APPLY); - gtk_widget_destroy(dialog); -} - -static GtkActionEntry menu_items[] = { - { "FileMenuAction", NULL, N_("File"), NULL, NULL, NULL}, - { "LogMenuAction", NULL, N_("Log"), NULL, NULL, NULL}, - { "ViewMenuAction", NULL, N_("View"), NULL, NULL, NULL}, - { "FilterMenuAction", NULL, N_("Filter"), NULL, NULL, NULL}, - { "PlannerMenuAction", NULL, N_("Planner"), NULL, NULL, NULL}, - { "HelpMenuAction", NULL, N_("Help"), NULL, NULL, NULL}, - { "NewFile", GTK_STOCK_NEW, N_("New"), CTRLCHAR "N", NULL, G_CALLBACK(file_close) }, - { "OpenFile", GTK_STOCK_OPEN, N_("Open..."), CTRLCHAR "O", NULL, G_CALLBACK(file_open) }, - { "SaveFile", GTK_STOCK_SAVE, N_("Save..."), CTRLCHAR "S", NULL, G_CALLBACK(file_save) }, - { "SaveAsFile", GTK_STOCK_SAVE_AS, N_("Save As..."), SHIFTCHAR CTRLCHAR "S", NULL, G_CALLBACK(file_save_as) }, - { "CloseFile", GTK_STOCK_CLOSE, N_("Close"), NULL, NULL, G_CALLBACK(file_close) }, - { "Print", GTK_STOCK_PRINT, N_("Print..."), CTRLCHAR "P", NULL, G_CALLBACK(do_print) }, - { "ImportFile", NULL, N_("Import File(s)..."), CTRLCHAR "I", NULL, G_CALLBACK(import_files) }, - { "ExportUDDF", NULL, N_("Export UDDF..."), NULL, NULL, G_CALLBACK(export_all_dives_uddf_cb) }, - { "DownloadLog", NULL, N_("Download From Dive Computer..."), CTRLCHAR "D", NULL, G_CALLBACK(download_dialog) }, - { "DownloadWeb", GTK_STOCK_CONNECT, N_("Download From Web Service..."), NULL, NULL, G_CALLBACK(webservice_download_dialog) }, - { "AddDive", GTK_STOCK_ADD, N_("Add Dive..."), NULL, NULL, G_CALLBACK(add_dive_cb) }, - { "Preferences", GTK_STOCK_PREFERENCES, N_("Preferences..."), PREFERENCE_ACCEL, NULL, G_CALLBACK(preferences_dialog) }, - { "Renumber", NULL, N_("Renumber..."), NULL, NULL, G_CALLBACK(renumber_dialog) }, - { "YearlyStats", NULL, N_("Yearly Statistics"), NULL, NULL, G_CALLBACK(show_yearly_stats) }, -#if HAVE_OSM_GPS_MAP - { "DivesLocations", NULL, N_("Dives Locations"), CTRLCHAR "M", NULL, G_CALLBACK(show_gps_locations) }, -#endif - { "SelectEvents", NULL, N_("Select Events..."), NULL, NULL, G_CALLBACK(selectevents_dialog) }, - { "Quit", GTK_STOCK_QUIT, N_("Quit"), CTRLCHAR "Q", NULL, G_CALLBACK(quit) }, - { "About", GTK_STOCK_ABOUT, N_("About Subsurface"), NULL, NULL, G_CALLBACK(about_dialog) }, - { "UserManual", GTK_STOCK_HELP, N_("User Manual"), NULL, NULL, G_CALLBACK(show_user_manual) }, - { "ViewList", NULL, N_("List"), CTRLCHAR "1", NULL, G_CALLBACK(view_list) }, - { "ViewProfile", NULL, N_("Profile"), CTRLCHAR "2", NULL, G_CALLBACK(view_profile) }, - { "ViewInfo", NULL, N_("Info"), CTRLCHAR "3", NULL, G_CALLBACK(view_info) }, - { "ViewThree", NULL, N_("Three"), CTRLCHAR "4", NULL, G_CALLBACK(view_three) }, - { "EditNames", NULL, N_("Edit Device Names"), CTRLCHAR "E", NULL, G_CALLBACK(edit_dc_nicknames) }, - { "PrevDC", NULL, N_("Prev DC"), NULL, NULL, G_CALLBACK(prev_dc) }, - { "NextDC", NULL, N_("Next DC"), NULL, NULL, G_CALLBACK(next_dc) }, - { "InputPlan", NULL, N_("Input Plan"), NULL, NULL, G_CALLBACK(input_plan) }, -}; -static gint nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]); - -static GtkToggleActionEntry toggle_items[] = { - { "Autogroup", NULL, N_("Autogroup"), NULL, NULL, G_CALLBACK(autogroup_cb), FALSE }, - { "ToggleZoom", NULL, N_("Toggle Zoom"), CTRLCHAR "0", NULL, G_CALLBACK(toggle_zoom), FALSE }, -}; -static gint ntoggle_items = sizeof (toggle_items) / sizeof (toggle_items[0]); - -static const gchar* ui_string = " \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - " -#if HAVE_OSM_GPS_MAP - " " -#endif - " \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ -"; - -static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager) -{ - action_group = gtk_action_group_new("Menu"); - gtk_action_group_set_translation_domain(action_group, "subsurface"); - gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0); - toggle_items[0].is_active = autogroup; - gtk_action_group_add_toggle_actions(action_group, toggle_items, ntoggle_items, 0); - - gtk_ui_manager_insert_action_group(ui_manager, action_group, 0); - GError* error = 0; - gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error); - - gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager)); - GtkWidget* menu = gtk_ui_manager_get_widget(ui_manager, "/MainMenu"); - - return menu; -} - -static void switch_page(GtkNotebook *notebook, gint arg1, gpointer user_data) -{ - repaint_dive(); -} - -static gboolean on_key_press(GtkWidget *w, GdkEventKey *event, GtkWidget *divelist) -{ - if (event->type != GDK_KEY_PRESS || event->state != 0) - return FALSE; - switch (event->keyval) { - case GDK_Up: - select_prev_dive(); - return TRUE; - case GDK_Down: - select_next_dive(); - return TRUE; - case GDK_Left: - prev_dc(NULL, NULL); - return TRUE; - case GDK_Right: - next_dc(NULL, NULL); - return TRUE; - } - return FALSE; -} - -static gboolean notebook_tooltip (GtkWidget *widget, gint x, gint y, - gboolean keyboard_mode, GtkTooltip *tooltip, gpointer data) -{ - if (amount_selected > 0 && gtk_notebook_get_current_page(GTK_NOTEBOOK(widget)) == 0) { - gtk_tooltip_set_text(tooltip, _("To edit dive information\ndouble click on it in the dive list")); - return TRUE; - } else { - return FALSE; - } -} - -void init_ui(int *argcp, char ***argvp) -{ - GtkWidget *win; - GtkWidget *nb_page; - GtkWidget *dive_list; - GtkWidget *menubar; - GtkWidget *vbox; - GtkWidget *scrolled; - GdkScreen *screen; - GtkIconTheme *icon_theme=NULL; - GtkSettings *settings; - GtkUIManager *ui_manager; - - gtk_init(argcp, argvp); - settings = gtk_settings_get_default(); - gtk_settings_set_long_property(settings, "gtk-tooltip-timeout", 10, "subsurface setting"); - gtk_settings_set_long_property(settings, "gtk-menu-images", 1, "subsurface setting"); - gtk_settings_set_long_property(settings, "gtk-button-images", 1, "subsurface setting"); - - /* check if utf8 stars are available as a default OS feature */ - if (!subsurface_os_feature_available(UTF8_FONT_WITH_STARS)) { - star_strings[0] = " "; - star_strings[1] = "* "; - star_strings[2] = "** "; - star_strings[3] = "*** "; - star_strings[4] = "**** "; - star_strings[5] = "*****"; - } - g_type_init(); - - subsurface_open_conf(); - - load_preferences(); - - default_dive_computer_vendor = subsurface_get_conf("dive_computer_vendor"); - default_dive_computer_product = subsurface_get_conf("dive_computer_product"); - default_dive_computer_device = subsurface_get_conf("dive_computer_device"); - error_info_bar = NULL; - win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - 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) { - const char *icon_name = subsurface_icon_name(); - if (!access(icon_name, R_OK)) - gtk_window_set_icon_from_file(GTK_WINDOW(win), icon_name, NULL); - } - 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); - g_signal_connect(G_OBJECT(win), "window-state-event", G_CALLBACK(on_state), NULL); - main_window = win; - - vbox = gtk_vbox_new(FALSE, 0); - gtk_container_add(GTK_CONTAINER(win), vbox); - main_vbox = vbox; - - ui_manager = gtk_ui_manager_new(); - menubar = get_menubar_menu(win, ui_manager); - - subsurface_ui_setup(settings, menubar, vbox, ui_manager); - - vpane = gtk_vpaned_new(); - gtk_box_pack_start(GTK_BOX(vbox), vpane, TRUE, TRUE, 3); - hpane = gtk_hpaned_new(); - gtk_paned_add1(GTK_PANED(vpane), hpane); - g_signal_connect_after(G_OBJECT(vbox), "realize", G_CALLBACK(view_three), NULL); - - /* Notebook for dive info vs profile vs .. */ - notebook = gtk_notebook_new(); - scrolled = gtk_scrolled_window_new(NULL, NULL); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); - gtk_paned_add1(GTK_PANED(hpane), scrolled); - gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled), notebook); - g_signal_connect(notebook, "switch-page", G_CALLBACK(switch_page), NULL); - - /* Create the actual divelist */ - dive_list = dive_list_create(); - gtk_widget_set_name(dive_list, "Dive List"); - gtk_paned_add2(GTK_PANED(vpane), dive_list); - - /* Frame for dive profile */ - dive_profile = dive_profile_widget(); - gtk_widget_set_name(dive_profile, "Dive Profile"); - gtk_paned_add2(GTK_PANED(hpane), dive_profile); - - /* Frame for extended dive info */ - nb_page = extended_dive_info_widget(); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new(_("Dive Notes"))); - - /* Frame for dive equipment */ - nb_page = equipment_widget(W_IDX_PRIMARY); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new(_("Equipment"))); - - /* Frame for single dive statistics */ - nb_page = single_stats_widget(); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new(_("Dive Info"))); - - /* Frame for total dive statistics */ - nb_page = total_stats_widget(); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new(_("Stats"))); - - /* add tooltip that tells people how to edit things */ - g_object_set(notebook, "has-tooltip", TRUE, NULL); - g_signal_connect(notebook, "query-tooltip", G_CALLBACK(notebook_tooltip), NULL); - - /* handle some keys globally (to deal with gtk focus issues) */ - g_signal_connect (G_OBJECT (win), "key_press_event", G_CALLBACK (on_key_press), dive_list); - - gtk_widget_set_app_paintable(win, TRUE); - restore_window_geometry(); - gtk_widget_show_all(win); - - return; -} - -void run_ui(void) -{ - gtk_main(); -} - -void exit_ui(void) -{ - subsurface_close_conf(); - if (existing_filename) - free((void *)existing_filename); - if (default_dive_computer_device) - free((void *)default_dive_computer_device); -} - -typedef struct { - cairo_rectangle_t rect; - const char *text; - struct event *event; -} 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, struct event *event) -{ - cairo_rectangle_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 = strdup(text); - tooltip_rects[tooltips].event = event; - tooltips++; -} - -#define INSIDE_RECT(_r,_x,_y) ((_r.x <= _x) && (_r.x + _r.width >= _x) && \ - (_r.y <= _y) && (_r.y + _r.height >= _y)) -#define INSIDE_RECT_X(_r, _x) ((_r.x <= _x) && (_r.x + _r.width >= _x)) - -static gboolean profile_tooltip (GtkWidget *widget, gint x, gint y, - gboolean keyboard_mode, GtkTooltip *tooltip, struct graphics_context *gc) -{ - int i; - cairo_rectangle_t *drawing_area = &gc->drawing_area; - gint tx = x - drawing_area->x; /* get transformed coordinates */ - gint ty = y - drawing_area->y; - gint width, height, time = -1; - char buffer[2048], plot[1024]; - const char *event = ""; - - if (tx < 0 || ty < 0) - return FALSE; - - /* don't draw a tooltip if nothing is there */ - if (amount_selected == 0 || gc->pi.nr == 0) - return FALSE; - - width = drawing_area->width - 2*drawing_area->x; - height = drawing_area->height - 2*drawing_area->y; - if (width <= 0 || height <= 0) - return FALSE; - - if (tx > width || ty > height) - return FALSE; - - time = (tx * gc->maxtime) / width; - - /* are we over an event marker ? */ - for (i = 0; i < tooltips; i++) { - if (INSIDE_RECT(tooltip_rects[i].rect, tx, ty)) { - event = tooltip_rects[i].text; - break; - } - } - get_plot_details(gc, time, plot, sizeof(plot)); - - snprintf(buffer, sizeof(buffer), "@ %d:%02d%c%s%c%s", time / 60, time % 60, - *plot ? '\n' : ' ', plot, - *event ? '\n' : ' ', event); - gtk_tooltip_set_text(tooltip, buffer); - return TRUE; - -} - -static double zoom_factor = 1.0; -static int zoom_x = -1, zoom_y = -1; - -static void common_drawing_function(GtkWidget *widget, struct graphics_context *gc) -{ - int i = 0; - struct dive *dive = current_dive; - - gc->drawing_area.x = MIN(50,gc->drawing_area.width / 20.0); - gc->drawing_area.y = MIN(50,gc->drawing_area.height / 20.0); - - g_object_set(widget, "has-tooltip", TRUE, NULL); - g_signal_connect(widget, "query-tooltip", G_CALLBACK(profile_tooltip), gc); - init_profile_background(gc); - cairo_paint(gc->cr); - - if (zoom_factor > 1.0) { - double n = -(zoom_factor-1); - cairo_translate(gc->cr, n*zoom_x, n*zoom_y); - cairo_scale(gc->cr, zoom_factor, zoom_factor); - } - - if (dive) { - if (tooltip_rects) { - while (i < tooltips) { - if (tooltip_rects[i].text) - free((void *)tooltip_rects[i].text); - i++; - } - free(tooltip_rects); - tooltip_rects = NULL; - } - tooltips = 0; - plot(gc, dive, SC_SCREEN); - } -} - -#if GTK_CHECK_VERSION(3,0,0) - -static gboolean draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) -{ - guint width, height; - static struct graphics_context gc = { .printer = 0 }; - - width = gtk_widget_get_allocated_width(widget); - height = gtk_widget_get_allocated_height(widget); - - gc.drawing_area.width = width; - gc.drawing_area.height = height; - gc.cr = cr; - - common_drawing_function(widget, &gc); - return FALSE; -} - -#else /* gtk2 */ - -static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data) -{ - GtkAllocation allocation; - static struct graphics_context gc = { .printer = 0 }; - - /* 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 */ - gtk_widget_get_allocation(widget, &allocation); - gc.drawing_area.width = allocation.width; - gc.drawing_area.height = allocation.height; - gc.cr = gdk_cairo_create(gtk_widget_get_window(widget)); - - common_drawing_function(widget, &gc); - cairo_destroy(gc.cr); - return FALSE; -} - -#endif - -static void zoom_event(int x, int y, double inc) -{ - zoom_x = x; - zoom_y = y; - inc += zoom_factor; - if (inc < 1.0) - inc = 1.0; - else if (inc > 10) - inc = 10; - zoom_factor = inc; -} - -static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer user_data) -{ - switch (event->direction) { - case GDK_SCROLL_UP: - zoom_event(event->x, event->y, 0.1); - break; - case GDK_SCROLL_DOWN: - zoom_event(event->x, event->y, -0.1); - break; - default: - return TRUE; - } - gtk_widget_queue_draw(widget); - return TRUE; -} - -static void add_gas_change_cb(GtkWidget *menuitem, gpointer data) -{ - double *x = data; - int when = x_to_time(*x); - int cylnr = select_cylinder(current_dive, when); - if (cylnr >= 0) { - cylinder_t *cyl = ¤t_dive->cylinder[cylnr]; - int value = cyl->gasmix.o2.permille / 10 | ((cyl->gasmix.he.permille / 10) << 16); - add_event(current_dc, when, 25, 0, value, "gaschange"); - mark_divelist_changed(TRUE); - report_dives(FALSE, FALSE); - dive_list_update_dives(); - } -} - -int confirm_dialog(int when, char *action_text, char *event_text) -{ - GtkWidget *dialog, *vbox, *label; - int confirmed; - char title[80]; - - snprintf(title, sizeof(title), "%s %s", action_text, event_text); - dialog = gtk_dialog_new_with_buttons(title, - GTK_WINDOW(main_window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, - NULL); - - vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - label = create_label(_("%s event at %d:%02u"), title, FRACTION(when, 60)); - gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); - gtk_widget_show_all(dialog); - confirmed = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; - - gtk_widget_destroy(dialog); - - return confirmed; -} - -static void add_bookmark_cb(GtkWidget *menuitem, gpointer data) -{ - double *x = data; - int when = x_to_time(*x); - - if (confirm_dialog(when, _("Add"), _("bookmark"))){ - add_event(current_dc, when, 8, 0, 0, "bookmark"); - mark_divelist_changed(TRUE); - report_dives(FALSE, FALSE); - } -} - -static struct event *event_at_x(double rel_x) -{ - /* is there an event marker at this x coordinate */ - struct event *ret = NULL; - int i; - int x = x_abs(rel_x); - - for (i = 0; i < tooltips; i++) { - if (INSIDE_RECT_X(tooltip_rects[i].rect, x)) { - ret = tooltip_rects[i].event; - break; - } - } - return ret; -} - -static void remove_event_cb(GtkWidget *menuitem, gpointer data) -{ - struct event *event = data; - if (confirm_dialog(event->time.seconds, _("Remove"), _(event->name))){ - struct event **ep = ¤t_dc->events; - while (ep && *ep != event) - ep = &(*ep)->next; - if (ep) { - *ep = event->next; - free(event); - } - mark_divelist_changed(TRUE); - report_dives(FALSE, FALSE); - } -} - -static void popup_profile_menu(GtkWidget *widget, GdkEventButton *gtk_event) -{ - GtkWidget *menu, *menuitem, *image; - static double x; - struct event *event; - - if (!gtk_event || !current_dive) - return; - x = gtk_event->x; - menu = gtk_menu_new(); - menuitem = gtk_image_menu_item_new_with_label(_("Add gas change event here")); - image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU); - gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); - g_signal_connect(menuitem, "activate", G_CALLBACK(add_gas_change_cb), &x); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - menuitem = gtk_image_menu_item_new_with_label(_("Add bookmark event here")); - image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU); - gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); - g_signal_connect(menuitem, "activate", G_CALLBACK(add_bookmark_cb), &x); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - if ((event = event_at_x(x)) != NULL) { - menuitem = gtk_image_menu_item_new_with_label(_("Remove event here")); - image = gtk_image_new_from_stock(GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU); - gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); - g_signal_connect(menuitem, "activate", G_CALLBACK(remove_event_cb), event); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - } - - gtk_widget_show_all(menu); - - gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, - gtk_event->button, gtk_get_current_event_time()); - -} - -static gboolean clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data) -{ - switch (event->button) { - case 1: - zoom_x = event->x; - zoom_y = event->y; - zoom_factor = 2.5; - break; - case 3: - popup_profile_menu(widget, event); - break; - default: - return TRUE; - } - gtk_widget_queue_draw(widget); - return TRUE; -} - -static gboolean released(GtkWidget *widget, GdkEventButton *event, gpointer user_data) -{ - switch (event->button) { - case 1: - zoom_x = zoom_y = -1; - zoom_factor = 1.0; - break; - default: - return TRUE; - } - gtk_widget_queue_draw(widget); - return TRUE; -} - -static gboolean motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data) -{ - if (zoom_x < 0) - return TRUE; - - zoom_x = event->x; - zoom_y = event->y; - gtk_widget_queue_draw(widget); - return TRUE; -} - -static GtkWidget *dive_profile_widget(void) -{ - GtkWidget *da; - - da = gtk_drawing_area_new(); - gtk_widget_set_size_request(da, 350, 250); -#if GTK_CHECK_VERSION(3,0,0) - g_signal_connect(da, "draw", G_CALLBACK (draw_callback), NULL); -#else - g_signal_connect(da, "expose_event", G_CALLBACK(expose_event), NULL); -#endif - g_signal_connect(da, "button-press-event", G_CALLBACK(clicked), NULL); - g_signal_connect(da, "scroll-event", G_CALLBACK(scroll_event), NULL); - g_signal_connect(da, "button-release-event", G_CALLBACK(released), NULL); - g_signal_connect(da, "motion-notify-event", G_CALLBACK(motion), NULL); - gtk_widget_add_events(da, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK); - - return da; -} - -static void do_import_file(gpointer data, gpointer user_data) -{ - GError *error = NULL; - parse_file(data, &error, FALSE); - - if (error != NULL) - { - report_error(error); - g_error_free(error); - error = NULL; - } -} - -static void import_files(GtkWidget *w, gpointer data) -{ - GtkWidget *fs_dialog; - const char *current_default; - char *current_def_dir; - GtkFileFilter *filter; - struct stat sb; - GSList *filenames = NULL; - - fs_dialog = gtk_file_chooser_dialog_new(_("Choose XML Files To Import Into Current Data File"), - GTK_WINDOW(main_window), - GTK_FILE_CHOOSER_ACTION_OPEN, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, - NULL); - - /* I'm not sure what the best default path should be... */ - if (existing_filename) { - current_def_dir = g_path_get_dirname(existing_filename); - } else { - current_default = prefs.default_filename; - current_def_dir = g_path_get_dirname(current_default); - } - - /* it's possible that the directory doesn't exist (especially for the default) - * For gtk's file select box to make sense we create it */ - if (stat(current_def_dir, &sb) != 0) - g_mkdir(current_def_dir, S_IRWXU); - - gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs_dialog), current_def_dir); - gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(fs_dialog), TRUE); - filter = setup_filter(); - gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fs_dialog), filter); - gtk_widget_show_all(fs_dialog); - if (gtk_dialog_run(GTK_DIALOG(fs_dialog)) == GTK_RESPONSE_ACCEPT) { - /* grab the selected file list, import each file and update the list */ - filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(fs_dialog)); - if (filenames) { - g_slist_foreach(filenames, do_import_file, NULL); - report_dives(TRUE, FALSE); - g_slist_free(filenames); - } - } - - free(current_def_dir); - gtk_widget_destroy(fs_dialog); -} - -void set_filename(const char *filename, gboolean force) -{ - if (!force && existing_filename) - return; - free((void *)existing_filename); - if (filename) - existing_filename = strdup(filename); - else - existing_filename = NULL; -} - -const char *get_dc_nickname(const char *model, uint32_t deviceid) -{ - struct device_info *known = get_device_info(model, deviceid); - if (known) { - if (known->nickname && *known->nickname) - return known->nickname; - else - return known->model; - } - return NULL; -} - -void set_dc_nickname(struct dive *dive) -{ - GtkWidget *dialog, *vbox, *entry, *frame, *label; - char nickname[160] = ""; - char dialogtext[2048]; - const char *name = nickname; - struct divecomputer *dc = &dive->dc; - - if (!dive) - return; - while (dc) { -#if NICKNAME_DEBUG & 16 - fprintf(debugfile, "set_dc_nickname for model %s deviceid %8x\n", dc->model ? : "", dc->deviceid); -#endif - if (get_dc_nickname(dc->model, dc->deviceid) == NULL) { - struct device_info *nn_entry = get_different_device_info(dc->model, dc->deviceid); - if (nn_entry) { - dialog = gtk_dialog_new_with_buttons( - _("Dive Computer Nickname"), - GTK_WINDOW(main_window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - NULL); - vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - snprintf(dialogtext, sizeof(dialogtext), - _("You already have a dive computer of this model\n" - "named %s\n" - "Subsurface can maintain a nickname for this device to " - "distinguish it from the existing one. " - "The default is the model and device ID as shown below.\n" - "If you don't want to name this dive computer click " - "'Cancel' and Subsurface will simply display its model " - "as its name (which may mean that you cannot tell the two " - "dive computers apart in the logs)."), - nn_entry->nickname && *nn_entry->nickname ? nn_entry->nickname : - (nn_entry->model && *nn_entry->model ? nn_entry->model : _("(nothing)"))); - label = gtk_label_new(dialogtext); - gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); - gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 3); - frame = gtk_frame_new(_("Nickname")); - gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, TRUE, 3); - entry = gtk_entry_new(); - gtk_container_add(GTK_CONTAINER(frame), entry); - gtk_entry_set_max_length(GTK_ENTRY(entry), 68); - snprintf(nickname, sizeof(nickname), "%s (%08x)", dc->model, dc->deviceid); - gtk_entry_set_text(GTK_ENTRY(entry), nickname); - gtk_widget_show_all(dialog); - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - if (strcmp(dc->model, gtk_entry_get_text(GTK_ENTRY(entry)))) { - name = gtk_entry_get_text(GTK_ENTRY(entry)); - remember_dc(dc->model, dc->deviceid, name); - mark_divelist_changed(TRUE); - } - } else { - /* Remember that we declined the nickname */ - remember_dc(dc->model, dc->deviceid, NULL); - } - gtk_widget_destroy(dialog); - } else { - remember_dc(dc->model, dc->deviceid, NULL); - } - } - dc = dc->next; - } -} - -gdouble get_screen_dpi(void) -{ - const gdouble mm_per_inch = 25.4; - GdkScreen *scr = gdk_screen_get_default(); - gdouble h_mm = gdk_screen_get_height_mm(scr); - gdouble h = gdk_screen_get_height(scr); - gdouble dpi_h = floor((h / h_mm) * mm_per_inch); - return dpi_h; -} diff --git a/linux.c b/linux.c index 4add7bb01..bf7383822 100644 --- a/linux.c +++ b/linux.c @@ -9,7 +9,7 @@ const char system_divelist_default_font[] = "Sans 8"; GConfClient *gconf; -static char *gconf_name(char *name) +static char *gconf_name(const char *name) { static char buf[255] = "/apps/subsurface/"; @@ -23,32 +23,32 @@ void subsurface_open_conf(void) gconf = gconf_client_get_default(); } -void subsurface_unset_conf(char *name) +void subsurface_unset_conf(const char *name) { gconf_client_unset(gconf, gconf_name(name), NULL); } -void subsurface_set_conf(char *name, const char *value) +void subsurface_set_conf(const char *name, const char *value) { gconf_client_set_string(gconf, gconf_name(name), value, NULL); } -void subsurface_set_conf_bool(char *name, int value) +void subsurface_set_conf_bool(const char *name, int value) { gconf_client_set_bool(gconf, gconf_name(name), value > 0, NULL); } -void subsurface_set_conf_int(char *name, int value) +void subsurface_set_conf_int(const char *name, int value) { gconf_client_set_int(gconf, gconf_name(name), value , NULL); } -const void *subsurface_get_conf(char *name) +const char *subsurface_get_conf(const char *name) { return gconf_client_get_string(gconf, gconf_name(name), NULL); } -int subsurface_get_conf_bool(char *name) +int subsurface_get_conf_bool(const char *name) { GConfValue *val; gboolean ret; @@ -61,7 +61,7 @@ int subsurface_get_conf_bool(char *name) return ret; } -int subsurface_get_conf_int(char *name) +int subsurface_get_conf_int(const char *name) { int val = gconf_client_get_int(gconf, gconf_name(name), NULL); if(!val) diff --git a/macos.c b/macos.c index aee4c73ca..93bc00eea 100644 --- a/macos.c +++ b/macos.c @@ -30,29 +30,29 @@ void subsurface_open_conf(void) /* nothing at this time */ } -void subsurface_unset_conf(char *name) +void subsurface_unset_conf(const char *name) { CFPreferencesSetAppValue(CFSTR_VAR(name), NULL, SUBSURFACE_PREFERENCES); } -void subsurface_set_conf(char *name, const char *value) +void subsurface_set_conf(const char *name, const char *value) { CFPreferencesSetAppValue(CFSTR_VAR(name), CFSTR_VAR(value), SUBSURFACE_PREFERENCES); } -void subsurface_set_conf_bool(char *name, int value) +void subsurface_set_conf_bool(const char *name, int value) { CFPreferencesSetAppValue(CFSTR_VAR(name), value ? kCFBooleanTrue : kCFBooleanFalse, SUBSURFACE_PREFERENCES); } -void subsurface_set_conf_int(char *name, int value) +void subsurface_set_conf_int(const char *name, int value) { CFNumberRef numRef = CFNumberCreate(NULL, kCFNumberIntType, &value); CFPreferencesSetAppValue(CFSTR_VAR(name), numRef, SUBSURFACE_PREFERENCES); } -const void *subsurface_get_conf(char *name) +const char *subsurface_get_conf(char *name) { CFPropertyListRef strpref; @@ -62,7 +62,7 @@ const void *subsurface_get_conf(char *name) return strdup(CFStringGetCStringPtr(strpref, kCFStringEncodingMacRoman)); } -int subsurface_get_conf_bool(char *name) +int subsurface_get_conf_bool(const char *name) { Boolean boolpref, exists; @@ -72,7 +72,7 @@ int subsurface_get_conf_bool(char *name) return boolpref; } -int subsurface_get_conf_int(char *name) +int subsurface_get_conf_int(const char *name) { Boolean exists; CFIndex value; diff --git a/pref.h b/pref.h index 60fe1104c..d622ebf3a 100644 --- a/pref.h +++ b/pref.h @@ -1,6 +1,10 @@ #ifndef PREF_H #define PREF_H +#ifdef __cplusplus +extern "C" { +#endif + typedef struct { gboolean cylinder; gboolean temperature; @@ -43,13 +47,13 @@ extern struct preferences prefs, default_prefs; #define PP_GRAPHS_ENABLED (prefs.pp_graphs.po2 || prefs.pp_graphs.pn2 || prefs.pp_graphs.phe) extern void subsurface_open_conf(void); -extern void subsurface_set_conf(char *name, const char *value); -extern void subsurface_set_conf_bool(char *name, gboolean value); -extern void subsurface_set_conf_int(char *name, int value); -extern void subsurface_unset_conf(char *name); -extern const void *subsurface_get_conf(char *name); -extern int subsurface_get_conf_bool(char *name); -extern int subsurface_get_conf_int(char *name); +extern void subsurface_set_conf(const char *name, const char *value); +extern void subsurface_set_conf_bool(const char *name, gboolean value); +extern void subsurface_set_conf_int(const char *name, int value); +extern void subsurface_unset_conf(const char *name); +extern const char *subsurface_get_conf(const char *name); +extern int subsurface_get_conf_bool(const char *name); +extern int subsurface_get_conf_int(const char *name); extern void subsurface_flush_conf(void); extern void subsurface_close_conf(void); @@ -59,4 +63,8 @@ extern const char *system_default_filename(); extern void load_preferences(void); extern void save_preferences(void); +#ifdef __cplusplus +} +#endif + #endif /* PREF_H */ diff --git a/qt-gui.cpp b/qt-gui.cpp new file mode 100644 index 000000000..54aa582b4 --- /dev/null +++ b/qt-gui.cpp @@ -0,0 +1,2365 @@ +/* gtk-gui.c */ +/* gtk UI implementation */ +/* creates the window and overall layout + * divelist, dive info, equipment and printing are handled in their own source files + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dive.h" +#include "divelist.h" +#include "display.h" +#include "display-gtk.h" +#include "callbacks-gtk.h" +#include "uemis.h" +#include "device.h" +#include "webservice.h" +#include "version.h" +#include "libdivecomputer.h" + +#include +#include + +#if HAVE_OSM_GPS_MAP +#include +#endif + +static const GdkPixdata subsurface_icon_pixbuf = {}; + +GtkWidget *main_window; +GtkWidget *main_vbox; +GtkWidget *error_info_bar; +GtkWidget *error_label; +GtkWidget *vpane, *hpane; +GtkWidget *notebook; + +int error_count; +const char *existing_filename; + +typedef enum { PANE_INFO, PANE_PROFILE, PANE_LIST, PANE_THREE } pane_conf_t; +static pane_conf_t pane_conf; + +static struct device_info *holdnicknames = NULL; +static GtkWidget *dive_profile_widget(void); +static void import_files(GtkWidget *, gpointer); + +static void remember_dc(const char *model, uint32_t deviceid, const char *nickname) +{ + struct device_info *nn_entry; + + nn_entry = create_device_info(model, deviceid); + if (!nn_entry) + return; + if (!nickname || !*nickname) { + nn_entry->nickname = NULL; + return; + } + nn_entry->nickname = strdup(nickname); +} + +static void remove_dc(const char *model, uint32_t deviceid) +{ + free(remove_device_info(model, deviceid)); +} + +static GtkWidget *dive_profile; + +GtkActionGroup *action_group; + +void repaint_dive(void) +{ + update_dive(current_dive); + if (dive_profile) + gtk_widget_queue_draw(dive_profile); +} + +static gboolean need_icon = TRUE; + +static void on_info_bar_response(GtkWidget *widget, gint response, + gpointer data) +{ + if (response == GTK_RESPONSE_OK) + { + gtk_widget_destroy(widget); + error_info_bar = NULL; + } +} + +void report_error(GError* error) +{ + if (error == NULL) + { + return; + } + + if (error_info_bar == NULL) + { + error_count = 1; + error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK, + GTK_RESPONSE_OK, + NULL); + g_signal_connect(error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL); + gtk_info_bar_set_message_type(GTK_INFO_BAR(error_info_bar), + GTK_MESSAGE_ERROR); + + error_label = gtk_label_new(error->message); + GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(error_info_bar)); + gtk_container_add(GTK_CONTAINER(container), error_label); + + gtk_box_pack_start(GTK_BOX(main_vbox), error_info_bar, FALSE, FALSE, 0); + gtk_widget_show_all(main_vbox); + } + else + { + error_count++; + char buffer[256]; + snprintf(buffer, sizeof(buffer), _("Failed to open %i files."), error_count); + gtk_label_set_text(GTK_LABEL(error_label), buffer); + } +} + +static GtkFileFilter *setup_filter(void) +{ + GtkFileFilter *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, "*.uddf"); + gtk_file_filter_add_pattern(filter, "*.UDDF"); + gtk_file_filter_add_pattern(filter, "*.udcf"); + gtk_file_filter_add_pattern(filter, "*.UDCF"); + gtk_file_filter_add_pattern(filter, "*.jlb"); + gtk_file_filter_add_pattern(filter, "*.JLB"); +#ifdef LIBZIP + gtk_file_filter_add_pattern(filter, "*.sde"); + gtk_file_filter_add_pattern(filter, "*.SDE"); + gtk_file_filter_add_pattern(filter, "*.dld"); + gtk_file_filter_add_pattern(filter, "*.DLD"); +#endif +#ifdef SQLITE3 + gtk_file_filter_add_pattern(filter, "*.DB"); + gtk_file_filter_add_pattern(filter, "*.db"); +#endif + + gtk_file_filter_add_mime_type(filter, "text/xml"); + gtk_file_filter_set_name(filter, _("XML file")); + + return filter; +} + +static void file_save_as(GtkWidget *w, gpointer data) +{ + GtkWidget *dialog; + char *filename = NULL; + char *current_file; + char *current_dir; + + dialog = gtk_file_chooser_dialog_new(_("Save File As"), + GTK_WINDOW(main_window), + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); + + if (existing_filename) { + current_dir = g_path_get_dirname(existing_filename); + current_file = g_path_get_basename(existing_filename); + } else { + const char *current_default = prefs.default_filename; + current_dir = g_path_get_dirname(current_default); + current_file = g_path_get_basename(current_default); + } + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), current_dir); + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), current_file); + + free(current_dir); + free(current_file); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + } + gtk_widget_destroy(dialog); + + if (filename){ + save_dives(filename); + set_filename(filename, TRUE); + g_free(filename); + mark_divelist_changed(FALSE); + } +} + +static void file_save(GtkWidget *w, gpointer data) +{ + const char *current_default; + + if (!existing_filename) + return file_save_as(w, data); + + current_default = prefs.default_filename; + if (strcmp(existing_filename, current_default) == 0) { + /* if we are using the default filename the directory + * that we are creating the file in may not exist */ + char *current_def_dir; + struct stat sb; + + current_def_dir = g_path_get_dirname(existing_filename); + if (stat(current_def_dir, &sb) != 0) { + g_mkdir(current_def_dir, S_IRWXU); + } + free(current_def_dir); + } + save_dives(existing_filename); + mark_divelist_changed(FALSE); +} + +static gboolean ask_save_changes() +{ + GtkWidget *dialog, *label, *content; + gboolean quit = TRUE; + dialog = gtk_dialog_new_with_buttons(_("Save Changes?"), + GTK_WINDOW(main_window), GtkDialogFlags(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + GTK_STOCK_NO, GTK_RESPONSE_NO, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + NULL); + content = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + + if (!existing_filename){ + label = gtk_label_new ( + _("You have unsaved changes\nWould you like to save those before closing the datafile?")); + } else { + char *label_text = (char*) malloc(sizeof(char) * + (strlen(_("You have unsaved changes to file: %s \nWould you like to save those before closing the datafile?")) + + strlen(existing_filename))); + sprintf(label_text, + _("You have unsaved changes to file: %s \nWould you like to save those before closing the datafile?"), + existing_filename); + label = gtk_label_new (label_text); + free(label_text); + } + gtk_container_add (GTK_CONTAINER (content), label); + gtk_widget_show_all (dialog); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT); + gint outcode = gtk_dialog_run(GTK_DIALOG(dialog)); + if (outcode == GTK_RESPONSE_ACCEPT) { + file_save(NULL,NULL); + } else if (outcode == GTK_RESPONSE_CANCEL || outcode == GTK_RESPONSE_DELETE_EVENT) { + quit = FALSE; + } + gtk_widget_destroy(dialog); + return quit; +} + +static void file_close(GtkWidget *w, gpointer data) +{ + if (unsaved_changes()) + if (ask_save_changes() == FALSE) + return; + + if (existing_filename) + free((void *)existing_filename); + existing_filename = NULL; + + /* free the dives and trips */ + while (dive_table.nr) + delete_single_dive(0); + mark_divelist_changed(FALSE); + + /* clear the selection and the statistics */ + selected_dive = 0; + process_selected_dives(); + clear_stats_widgets(); + clear_events(); + show_dive_stats(NULL); + + /* clear the equipment page */ + clear_equipment_widgets(); + + /* redraw the screen */ + dive_list_update_dives(); + show_dive_info(NULL); +} + +static void file_open(GtkWidget *w, gpointer data) +{ + GtkWidget *dialog; + GtkFileFilter *filter; + const char *current_default; + + 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); + current_default = prefs.default_filename; + gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), current_default); + /* when opening the data file we should allow only one file to be chosen */ + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE); + + filter = setup_filter(); + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + GSList *fn_glist; + char *filename; + + /* first, close the existing file, if any, and forget its name */ + file_close(w, data); + free((void *)existing_filename); + existing_filename = NULL; + + /* we know there is only one filename */ + fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); + + GError *error = NULL; + filename = (char *)fn_glist->data; + parse_file(filename, &error, TRUE); + if (error != NULL) + { + report_error(error); + g_error_free(error); + error = NULL; + } + g_free(filename); + g_slist_free(fn_glist); + report_dives(FALSE, FALSE); + } + gtk_widget_destroy(dialog); +} + +void save_pane_position() +{ + gint vpane_position = gtk_paned_get_position(GTK_PANED(vpane)); + gint hpane_position = gtk_paned_get_position(GTK_PANED(hpane)); + gboolean is_maximized = gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(main_window))) & + GDK_WINDOW_STATE_MAXIMIZED; + + if (pane_conf == PANE_THREE){ + if (is_maximized) { + subsurface_set_conf_int("vpane_position_maximized", vpane_position); + subsurface_set_conf_int("hpane_position_maximized", hpane_position); + } else { + subsurface_set_conf_int("vpane_position", vpane_position); + subsurface_set_conf_int("hpane_position", hpane_position); + } + } +} + +/* Since we want direct control of what set of parameters to retrive, this function + * has an input argument. */ +void restore_pane_position(gboolean maximized) +{ + if (maximized) { + gtk_paned_set_position(GTK_PANED(vpane), subsurface_get_conf_int("vpane_position_maximized")); + gtk_paned_set_position(GTK_PANED(hpane), subsurface_get_conf_int("hpane_position_maximized")); + } else { + gtk_paned_set_position(GTK_PANED(vpane), subsurface_get_conf_int("vpane_position")); + gtk_paned_set_position(GTK_PANED(hpane), subsurface_get_conf_int("hpane_position")); + } +} + +void save_window_geometry(void) +{ + /* GDK_GRAVITY_NORTH_WEST assumed ( it is the default ) */ + int window_width, window_height; + gboolean is_maximized; + + gtk_window_get_size(GTK_WINDOW(main_window), &window_width, &window_height); + is_maximized = gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(main_window))) & + GDK_WINDOW_STATE_MAXIMIZED; + subsurface_set_conf_int("window_width", window_width); + subsurface_set_conf_int("window_height", window_height); + subsurface_set_conf_bool("window_maximized", is_maximized); + save_pane_position(); + subsurface_flush_conf(); +} + +void restore_window_geometry(void) +{ + int window_width, window_height; + gboolean is_maximized = subsurface_get_conf_bool("window_maximized") > 0; + + window_height = subsurface_get_conf_int("window_height"); + window_width = subsurface_get_conf_int("window_width"); + + window_height == -1 ? window_height = 300 : window_height; + window_width == -1 ? window_width = 700 : window_width; + + restore_pane_position(is_maximized); + /* don't resize the window if in maximized state */ + if (is_maximized) + gtk_window_maximize(GTK_WINDOW(main_window)); + else + gtk_window_resize(GTK_WINDOW(main_window), window_width, window_height); +} + +gboolean on_delete(GtkWidget* w, gpointer data) +{ + /* Make sure to flush any modified dive data */ + update_dive(NULL); + + gboolean quit = TRUE; + if (unsaved_changes()) + quit = ask_save_changes(); + + if (quit){ + save_window_geometry(); + return FALSE; /* go ahead, kill the program, we're good now */ + } else { + return TRUE; /* We are not leaving */ + } +} + +static void on_destroy(GtkWidget* w, gpointer data) +{ + dive_list_destroy(); + info_widget_destroy(); + gtk_main_quit(); +} + +/* This "window-state-event" callback will be called after the actual action, such + * as maximize or restore. This means that if you have methods here that check + * for the current window state, they will obtain the already updated state... */ +static gboolean on_state(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data) +{ + gint vpane_position, hpane_position; + if (event->changed_mask & GDK_WINDOW_STATE_WITHDRAWN || + event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) + return TRUE; /* do nothing if the window is shown for the first time or minimized */ + if (pane_conf == PANE_THREE) { + hpane_position = gtk_paned_get_position(GTK_PANED(hpane)); + vpane_position = gtk_paned_get_position(GTK_PANED(vpane)); + if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) { /* maximize */ + subsurface_set_conf_int("vpane_position", vpane_position); + subsurface_set_conf_int("hpane_position", hpane_position); + restore_pane_position(TRUE); + } else if (event->new_window_state == 0) { /* restore */ + subsurface_set_conf_int("vpane_position_maximized", vpane_position); + subsurface_set_conf_int("hpane_position_maximized", hpane_position); + restore_pane_position(FALSE); + } + } + return TRUE; +} + +static void quit(GtkWidget *w, gpointer data) +{ + /* Make sure to flush any modified dive data */ + update_dive(NULL); + + gboolean quit = TRUE; + if (unsaved_changes()) + quit = ask_save_changes(); + + if (quit){ + save_window_geometry(); + dive_list_destroy(); + gtk_main_quit(); + } +} + +GtkTreeViewColumn *tree_view_column_add_pixbuf(GtkWidget *tree_view, data_func_t data_func, GtkTreeViewColumn *col) +{ + GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new(); + gtk_tree_view_column_pack_start(col, renderer, FALSE); + gtk_tree_view_column_set_cell_data_func(col, renderer, data_func, NULL, NULL); + g_signal_connect(tree_view, "button-press-event", G_CALLBACK(icon_click_cb), col); + return col; +} + +GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title, + data_func_t data_func, unsigned int flags) +{ + GtkCellRenderer *renderer; + GtkTreeViewColumn *col; + double xalign = 0.0; /* left as default */ + PangoAlignment align; + gboolean visible; + + align = (flags & ALIGN_LEFT) ? PANGO_ALIGN_LEFT : + (flags & ALIGN_RIGHT) ? PANGO_ALIGN_RIGHT : + PANGO_ALIGN_CENTER; + visible = !(flags & INVISIBLE); + + renderer = gtk_cell_renderer_text_new(); + col = gtk_tree_view_column_new(); + + if (flags & EDITABLE) { + g_object_set(renderer, "editable", TRUE, NULL); + g_signal_connect(renderer, "edited", (GCallback) data_func, tree_view); + data_func = NULL; + } + + gtk_tree_view_column_set_title(col, title); + if (!(flags & UNSORTABLE)) + gtk_tree_view_column_set_sort_column_id(col, index); + gtk_tree_view_column_set_resizable(col, TRUE); + /* all but one column have only one renderer - so packing from the end + * makes no difference; for the location column we want to be able to + * prepend the icon in front of the text - so this works perfectly */ + gtk_tree_view_column_pack_end(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); + 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; +} + +/* Helper functions for gtk combo boxes */ +GtkEntry *get_entry(GtkComboBox *combo_box) +{ + return GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo_box))); +} + +const char *get_active_text(GtkComboBox *combo_box) +{ + return gtk_entry_get_text(get_entry(combo_box)); +} + +void set_active_text(GtkComboBox *combo_box, const char *text) +{ + gtk_entry_set_text(get_entry(combo_box), text); +} + +GtkWidget *combo_box_with_model_and_entry(GtkListStore *model) +{ + GtkWidget *widget; + GtkEntryCompletion *completion; + +#if GTK_CHECK_VERSION(2,24,0) + widget = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model)); + gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(widget), 0); +#else + widget = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0); + gtk_combo_box_entry_set_text_column(GTK_COMBO_BOX_ENTRY(widget), 0); +#endif + + completion = gtk_entry_completion_new(); + gtk_entry_completion_set_text_column(completion, 0); + gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(model)); + gtk_entry_completion_set_inline_completion(completion, TRUE); + gtk_entry_completion_set_inline_selection(completion, TRUE); + gtk_entry_completion_set_popup_single_match(completion, FALSE); + gtk_entry_set_completion(get_entry(GTK_COMBO_BOX(widget)), completion); + g_object_unref(completion); + + return widget; +} + +static void create_radio(GtkWidget *vbox, const char *w_name, ...) +{ + va_list args; + GtkRadioButton *group = NULL; + GtkWidget *box, *label; + + box = gtk_hbox_new(TRUE, 10); + gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 0); + + label = gtk_label_new(w_name); + gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0); + + va_start(args, w_name); + for (;;) { + int enabled; + const char *name; + GtkWidget *button; + void *callback_fn; + + name = va_arg(args, char *); + if (!name) + break; + callback_fn = va_arg(args, void *); + enabled = va_arg(args, int); + + button = gtk_radio_button_new_with_label_from_widget(group, name); + group = GTK_RADIO_BUTTON(button); + gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), enabled); + g_signal_connect(button, "toggled", G_CALLBACK(callback_fn), NULL); + } + va_end(args); +} + +void update_screen() +{ + update_dive_list_units(); + repaint_dive(); + update_dive_list_col_visibility(); +} + +UNITCALLBACK(set_meter, length, units::METERS) +UNITCALLBACK(set_feet, length, units::FEET) +UNITCALLBACK(set_bar, pressure, units::BAR) +UNITCALLBACK(set_psi, pressure, units::PSI) +UNITCALLBACK(set_liter, volume, units::LITER) +UNITCALLBACK(set_cuft, volume, units::CUFT) +UNITCALLBACK(set_celsius, temperature, units::CELSIUS) +UNITCALLBACK(set_fahrenheit, temperature, units::FAHRENHEIT) +UNITCALLBACK(set_kg, weight, units::KG) +UNITCALLBACK(set_lbs, weight, units::LBS) + +OPTIONCALLBACK(otu_toggle, prefs.visible_cols.otu) +OPTIONCALLBACK(maxcns_toggle, prefs.visible_cols.maxcns) +OPTIONCALLBACK(sac_toggle, prefs.visible_cols.sac) +OPTIONCALLBACK(nitrox_toggle, prefs.visible_cols.nitrox) +OPTIONCALLBACK(temperature_toggle, prefs.visible_cols.temperature) +OPTIONCALLBACK(totalweight_toggle, prefs.visible_cols.totalweight) +OPTIONCALLBACK(suit_toggle, prefs.visible_cols.suit) +OPTIONCALLBACK(cylinder_toggle, prefs.visible_cols.cylinder) +OPTIONCALLBACK(po2_toggle, prefs.pp_graphs.po2) +OPTIONCALLBACK(pn2_toggle, prefs.pp_graphs.pn2) +OPTIONCALLBACK(phe_toggle, prefs.pp_graphs.phe) +OPTIONCALLBACK(mod_toggle, prefs.mod) +OPTIONCALLBACK(ead_toggle, prefs.ead) +OPTIONCALLBACK(red_ceiling_toggle, prefs.profile_red_ceiling) +OPTIONCALLBACK(calc_ceiling_toggle, prefs.profile_calc_ceiling) +OPTIONCALLBACK(calc_ceiling_3m_toggle, prefs.calc_ceiling_3m_incr) + +static gboolean gflow_edit(GtkWidget *w, GdkEvent *event, gpointer _data) +{ + double gflow; + const char *buf; + if (event->type == GDK_FOCUS_CHANGE) { + buf = gtk_entry_get_text(GTK_ENTRY(w)); + sscanf(buf, "%lf", &gflow); + prefs.gflow = gflow / 100.0; + set_gf(prefs.gflow, -1.0); + update_screen(); + } + return FALSE; +} + +static gboolean gfhigh_edit(GtkWidget *w, GdkEvent *event, gpointer _data) +{ + double gfhigh; + const char *buf; + if (event->type == GDK_FOCUS_CHANGE) { + buf = gtk_entry_get_text(GTK_ENTRY(w)); + sscanf(buf, "%lf", &gfhigh); + prefs.gfhigh = gfhigh / 100.0; + set_gf(-1.0, prefs.gfhigh); + update_screen(); + } + return FALSE; +} + +static void event_toggle(GtkWidget *w, gpointer _data) +{ + gboolean *plot_ev = (gboolean *)_data; + + *plot_ev = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)); +} + +static void pick_default_file(GtkWidget *w, GtkButton *button) +{ + GtkWidget *fs_dialog, *parent; + const char *current_default; + char *current_def_file, *current_def_dir; + GtkFileFilter *filter; + struct stat sb; + + fs_dialog = gtk_file_chooser_dialog_new(_("Choose Default XML File"), + GTK_WINDOW(main_window), + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + parent = gtk_widget_get_ancestor(w, GTK_TYPE_DIALOG); + gtk_widget_set_sensitive(parent, FALSE); + gtk_window_set_transient_for(GTK_WINDOW(fs_dialog), GTK_WINDOW(parent)); + + current_default = prefs.default_filename; + current_def_dir = g_path_get_dirname(current_default); + current_def_file = g_path_get_basename(current_default); + + /* it's possible that the directory doesn't exist (especially for the default) + * For gtk's file select box to make sense we create it */ + if (stat(current_def_dir, &sb) != 0) + g_mkdir(current_def_dir, S_IRWXU); + + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs_dialog), current_def_dir); + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(fs_dialog), current_def_file); + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(fs_dialog), FALSE); + filter = setup_filter(); + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fs_dialog), filter); + gtk_widget_show_all(fs_dialog); + if (gtk_dialog_run(GTK_DIALOG(fs_dialog)) == GTK_RESPONSE_ACCEPT) { + GSList *list; + + list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(fs_dialog)); + if (g_slist_length(list) == 1) + gtk_button_set_label(button, (const gchar *)list->data); + g_slist_free(list); + } + + free(current_def_dir); + free(current_def_file); + gtk_widget_destroy(fs_dialog); + + gtk_widget_set_sensitive(parent, TRUE); +} + +#if HAVE_OSM_GPS_MAP +static GtkWidget * map_provider_widget() +{ + OsmGpsMapSource_t i; +#if GTK_CHECK_VERSION(2,24,0) + GtkWidget *combobox = gtk_combo_box_text_new(); + + /* several of the providers seem to be redundant or non-functional; + * we may have to skip more than just the last three eventually */ + for (i = OSM_GPS_MAP_SOURCE_OPENSTREETMAP; i < OSM_GPS_MAP_SOURCE_LAST; i++) + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combobox), osm_gps_map_source_get_friendly_name(i)); +#else + GtkWidget *combobox = gtk_combo_box_new_text(); + for (i = OSM_GPS_MAP_SOURCE_OPENSTREETMAP; i < OSM_GPS_MAP_SOURCE_LAST; i++) + gtk_combo_box_append_text(GTK_COMBO_BOX(combobox), osm_gps_map_source_get_friendly_name(i)); +#endif + /* we don't offer choice 0 (none), so the index here is off by one */ + gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), prefs.map_provider - 1); + return combobox; +} +#endif + +static void preferences_dialog(GtkWidget *w, gpointer data) +{ + int result; + GtkWidget *dialog, *notebook, *font, *frame, *box, *hbox, *vbox, *button; + GtkWidget *xmlfile_button; +#if HAVE_OSM_GPS_MAP + GtkWidget *map_provider; +#endif + GtkWidget *entry_po2, *entry_pn2, *entry_phe, *entry_mod, *entry_gflow, *entry_gfhigh; + const char *current_default, *new_default; + char threshold_text[10], mod_text[10], utf8_buf[128]; + struct preferences oldprefs = prefs; + + dialog = gtk_dialog_new_with_buttons(_("Preferences"), + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + NULL); + + /* create the notebook for the preferences and attach it to dialog */ + notebook = gtk_notebook_new(); + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_box_pack_start(GTK_BOX(vbox), notebook, FALSE, FALSE, 5); + + /* vbox that holds the first notebook page */ + vbox = gtk_vbox_new(FALSE, 6); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, + gtk_label_new(_("General Settings"))); + frame = gtk_frame_new(_("Units")); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); + + box = gtk_vbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(frame), box); + + create_radio(box, _("Depth:"), + _("Meter"), set_meter, (prefs.units.length == units::METERS), + _("Feet"), set_feet, (prefs.units.length == units::FEET), + NULL); + + create_radio(box, _("Pressure:"), + _("Bar"), set_bar, (prefs.units.pressure == units::BAR), + _("PSI"), set_psi, (prefs.units.pressure == units::PSI), + NULL); + + create_radio(box, _("Volume:"), + _("Liter"), set_liter, (prefs.units.volume == units::LITER), + _("CuFt"), set_cuft, (prefs.units.volume == units::CUFT), + NULL); + + create_radio(box, _("Temperature:"), + _("Celsius"), set_celsius, (prefs.units.temperature == units::CELSIUS), + _("Fahrenheit"), set_fahrenheit, (prefs.units.temperature == units::FAHRENHEIT), + NULL); + + create_radio(box, _("Weight:"), + _("kg"), set_kg, (prefs.units.weight == units::KG), + _("lbs"), set_lbs, (prefs.units.weight == units::LBS), + NULL); + + frame = gtk_frame_new(_("Show Columns")); + gtk_box_pack_start(GTK_BOX(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(_("Temp")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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(_("Cyl")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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("O" UTF8_SUBSCRIPT_2 "%"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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(_("SAC")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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(_("Weight")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.visible_cols.totalweight); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(totalweight_toggle), NULL); + + button = gtk_check_button_new_with_label(_("Suit")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.visible_cols.suit); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(suit_toggle), NULL); + + frame = gtk_frame_new(_("Divelist Font")); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); + + font = gtk_font_button_new_with_font(prefs.divelist_font); + gtk_container_add(GTK_CONTAINER(frame),font); + + frame = gtk_frame_new(_("Misc. Options")); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(frame), box); + + frame = gtk_frame_new(_("Default XML Data File")); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 5); + hbox = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(frame), hbox); + current_default = prefs.default_filename; + xmlfile_button = gtk_button_new_with_label(current_default); + g_signal_connect(G_OBJECT(xmlfile_button), "clicked", + G_CALLBACK(pick_default_file), xmlfile_button); + gtk_box_pack_start(GTK_BOX(hbox), xmlfile_button, FALSE, FALSE, 6); +#if HAVE_OSM_GPS_MAP + frame = gtk_frame_new(_("Map provider")); + map_provider = map_provider_widget(); + gtk_container_add(GTK_CONTAINER(frame), map_provider); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 3); +#endif + /* vbox that holds the second notebook page */ + vbox = gtk_vbox_new(FALSE, 6); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, + gtk_label_new(_("Tec Settings"))); + + frame = gtk_frame_new(_("Show Columns")); + gtk_box_pack_start(GTK_BOX(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(_("OTU")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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); + + button = gtk_check_button_new_with_label(_("maxCNS")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.visible_cols.maxcns); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(maxcns_toggle), NULL); + + frame = gtk_frame_new(_("Profile Settings")); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); + + vbox = gtk_vbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(frame), vbox); + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(vbox), box); + + sprintf(utf8_buf, _("Show pO%s graph"), UTF8_SUBSCRIPT_2); + button = gtk_check_button_new_with_label(utf8_buf); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.pp_graphs.po2); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(po2_toggle), &entry_po2); + + sprintf(utf8_buf, _("pO%s threshold"), UTF8_SUBSCRIPT_2); + frame = gtk_frame_new(utf8_buf); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); + entry_po2 = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(entry_po2), 4); + snprintf(threshold_text, sizeof(threshold_text), "%.1f", prefs.pp_graphs.po2_threshold); + gtk_entry_set_text(GTK_ENTRY(entry_po2), threshold_text); + gtk_widget_set_sensitive(entry_po2, prefs.pp_graphs.po2); + gtk_container_add(GTK_CONTAINER(frame), entry_po2); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(vbox), box); + + sprintf(utf8_buf, _("Show pN%s graph"), UTF8_SUBSCRIPT_2); + button = gtk_check_button_new_with_label(utf8_buf); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.pp_graphs.pn2); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(pn2_toggle), &entry_pn2); + + sprintf(utf8_buf, _("pN%s threshold"), UTF8_SUBSCRIPT_2); + frame = gtk_frame_new(utf8_buf); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); + entry_pn2 = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(entry_pn2), 4); + snprintf(threshold_text, sizeof(threshold_text), "%.1f", prefs.pp_graphs.pn2_threshold); + gtk_entry_set_text(GTK_ENTRY(entry_pn2), threshold_text); + gtk_widget_set_sensitive(entry_pn2, prefs.pp_graphs.pn2); + gtk_container_add(GTK_CONTAINER(frame), entry_pn2); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(vbox), box); + + button = gtk_check_button_new_with_label(_("Show pHe graph")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.pp_graphs.phe); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(phe_toggle), &entry_phe); + + frame = gtk_frame_new(_("pHe threshold")); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); + entry_phe = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(entry_phe), 4); + snprintf(threshold_text, sizeof(threshold_text), "%.1f", prefs.pp_graphs.phe_threshold); + gtk_entry_set_text(GTK_ENTRY(entry_phe), threshold_text); + gtk_widget_set_sensitive(entry_phe, prefs.pp_graphs.phe); + gtk_container_add(GTK_CONTAINER(frame), entry_phe); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(vbox), box); + + button = gtk_check_button_new_with_label(_("Show MOD")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.mod); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(mod_toggle), &entry_mod); + + frame = gtk_frame_new(_("max ppO2")); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); + entry_mod = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(entry_mod), 4); + snprintf(mod_text, sizeof(mod_text), "%.1f", prefs.mod_ppO2); + gtk_entry_set_text(GTK_ENTRY(entry_mod), mod_text); + gtk_widget_set_sensitive(entry_mod, prefs.mod); + gtk_container_add(GTK_CONTAINER(frame), entry_mod); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(vbox), box); + + button = gtk_check_button_new_with_label(_("Show EAD, END, EADD")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.ead); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(ead_toggle), NULL); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(vbox), box); + + button = gtk_check_button_new_with_label(_("Show dc reported ceiling in red")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.profile_red_ceiling); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(red_ceiling_toggle), NULL); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(vbox), box); + + button = gtk_check_button_new_with_label(_("Show calculated ceiling")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.profile_calc_ceiling); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(calc_ceiling_toggle), NULL); + + button = gtk_check_button_new_with_label(_("3m increments for calculated ceiling")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.calc_ceiling_3m_incr); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(calc_ceiling_3m_toggle), NULL); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(vbox), box); + + frame = gtk_frame_new(_("GFlow")); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); + entry_gflow = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(entry_gflow), 4); + snprintf(threshold_text, sizeof(threshold_text), "%.0f", prefs.gflow * 100); + gtk_entry_set_text(GTK_ENTRY(entry_gflow), threshold_text); + gtk_container_add(GTK_CONTAINER(frame), entry_gflow); + gtk_widget_add_events(entry_gflow, GDK_FOCUS_CHANGE_MASK); + g_signal_connect(G_OBJECT(entry_gflow), "event", G_CALLBACK(gflow_edit), NULL); + + frame = gtk_frame_new(_("GFhigh")); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); + entry_gfhigh = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(entry_gfhigh), 4); + snprintf(threshold_text, sizeof(threshold_text), "%.0f", prefs.gfhigh * 100); + gtk_entry_set_text(GTK_ENTRY(entry_gfhigh), threshold_text); + gtk_container_add(GTK_CONTAINER(frame), entry_gfhigh); + gtk_widget_add_events(entry_gfhigh, GDK_FOCUS_CHANGE_MASK); + g_signal_connect(G_OBJECT(entry_gfhigh), "event", G_CALLBACK(gfhigh_edit), NULL); + + gtk_widget_show_all(dialog); + result = gtk_dialog_run(GTK_DIALOG(dialog)); + if (result == GTK_RESPONSE_ACCEPT) { + const char *po2_threshold_text, *pn2_threshold_text, *phe_threshold_text, *mod_text, *gflow_text, *gfhigh_text; + /* Make sure to flush any modified old dive data with old units */ + update_dive(NULL); + + prefs.divelist_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font))); + set_divelist_font(prefs.divelist_font); + po2_threshold_text = gtk_entry_get_text(GTK_ENTRY(entry_po2)); + sscanf(po2_threshold_text, "%lf", &prefs.pp_graphs.po2_threshold); + pn2_threshold_text = gtk_entry_get_text(GTK_ENTRY(entry_pn2)); + sscanf(pn2_threshold_text, "%lf", &prefs.pp_graphs.pn2_threshold); + phe_threshold_text = gtk_entry_get_text(GTK_ENTRY(entry_phe)); + sscanf(phe_threshold_text, "%lf", &prefs.pp_graphs.phe_threshold); + mod_text = gtk_entry_get_text(GTK_ENTRY(entry_mod)); + sscanf(mod_text, "%lf", &prefs.mod_ppO2); + gflow_text = gtk_entry_get_text(GTK_ENTRY(entry_gflow)); + sscanf(gflow_text, "%lf", &prefs.gflow); + gfhigh_text = gtk_entry_get_text(GTK_ENTRY(entry_gfhigh)); + sscanf(gfhigh_text, "%lf", &prefs.gfhigh); + prefs.gflow /= 100.0; + prefs.gfhigh /= 100.0; + set_gf(prefs.gflow, prefs.gfhigh); + + update_screen(); + + new_default = strdup(gtk_button_get_label(GTK_BUTTON(xmlfile_button))); + + /* if we opened the default file and are changing its name, + * update existing_filename */ + if (existing_filename) { + if (strcmp(current_default, existing_filename) == 0) { + free((void *)existing_filename); + existing_filename = strdup(new_default); + } + } + if (strcmp(current_default, new_default)) { + prefs.default_filename = new_default; + } +#if HAVE_OSM_GPS_MAP + /* get the map provider selected */ + OsmGpsMapSource_t i; +#if GTK_CHECK_VERSION(2,24,0) + char *provider = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(map_provider)); +#else + char *provider = gtk_combo_box_get_active_text(GTK_COMBO_BOX(map_provider)); +#endif + for (i = OSM_GPS_MAP_SOURCE_OPENSTREETMAP; i <= OSM_GPS_MAP_SOURCE_YAHOO_STREET; i++) + if (!strcmp(provider,osm_gps_map_source_get_friendly_name(i))) { + prefs.map_provider = i; + break; + } + free((void *)provider); +#endif + save_preferences(); + } else if (result == GTK_RESPONSE_CANCEL) { + prefs = oldprefs; + set_gf(prefs.gflow, prefs.gfhigh); + update_screen(); + } + gtk_widget_destroy(dialog); +} + +static void create_toggle(const char* label, int *on, void *_data) +{ + GtkWidget *button, *table = GTK_WIDGET(_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, *label; + + dialog = gtk_dialog_new_with_buttons(_("Select Events"), + 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); + if (!evn_foreach(&create_toggle, table)) { + g_object_ref_sink(G_OBJECT(table)); + label = gtk_label_new(_("\nNo Events\n")); + gtk_container_add(GTK_CONTAINER(frame), label); + } else { + gtk_container_add(GTK_CONTAINER(frame), table); + } + + gtk_widget_show_all(dialog); + result = gtk_dialog_run(GTK_DIALOG(dialog)); + if (result == GTK_RESPONSE_ACCEPT) { + repaint_dive(); + } + gtk_widget_destroy(dialog); +} + +static void autogroup_cb(GtkWidget *w, gpointer data) +{ + autogroup = !autogroup; + if (! autogroup) + remove_autogen_trips(); + dive_list_update_dives(); +} + +void set_autogroup(gboolean value) +{ + GtkAction *autogroup_action; + + if (value == autogroup) + return; + + autogroup_action = gtk_action_group_get_action(action_group, "Autogroup"); + gtk_action_activate(autogroup_action); +} + +static void renumber_dialog(GtkWidget *w, gpointer data) +{ + int result; + struct dive *dive; + GtkWidget *dialog, *frame, *button, *vbox; + + dialog = gtk_dialog_new_with_buttons(_("Renumber"), + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + + frame = gtk_frame_new(_("New starting number")); + 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) { + int nr = gtk_spin_button_get_value(GTK_SPIN_BUTTON(button)); + renumber_dives(nr); + repaint_dive(); + } + gtk_widget_destroy(dialog); +} + +static void about_dialog_link_cb(GtkAboutDialog *dialog, const gchar *link, gpointer data) +{ + subsurface_launch_for_uri(link); +} + +static void about_dialog(GtkWidget *w, gpointer data) +{ + const char *logo_property = NULL; + GdkPixbuf *logo = NULL; + GtkWidget *dialog; + + if (need_icon) { + logo_property = "logo"; + logo = gdk_pixbuf_from_pixdata(&subsurface_icon_pixbuf, TRUE, NULL); + } + dialog = gtk_about_dialog_new(); +#if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 24) + gtk_about_dialog_set_url_hook(about_dialog_link_cb, NULL, NULL); /* deprecated since GTK 2.24 */ +#else + g_signal_connect(GTK_ABOUT_DIALOG(dialog), "activate-link", G_CALLBACK(about_dialog_link_cb), NULL); +#endif + g_object_set(GTK_OBJECT(dialog), + "title", _("About Subsurface"), + "program-name", "Subsurface", + "comments", _("Multi-platform divelog software in C"), + "website", "http://subsurface.hohndel.org", + "license", "GNU General Public License, version 2\nhttp://www.gnu.org/licenses/old-licenses/gpl-2.0.html", + "version", VERSION_STRING, + "copyright", _("Linus Torvalds, Dirk Hohndel, and others, 2011, 2012, 2013"), + /*++GETTEXT the term translator-credits is magic - list the names of the tranlators here */ + "translator_credits", _("translator-credits"), + "logo-icon-name", "subsurface", + /* Must be last: */ + logo_property, logo, + NULL); + if (logo) + g_object_unref(logo); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + +static void show_user_manual(GtkWidget *w, gpointer data) +{ + subsurface_launch_for_uri("http://subsurface.hohndel.org/documentation/user-manual/"); +} + +static void view_list(GtkWidget *w, gpointer data) +{ + save_pane_position(); + gtk_paned_set_position(GTK_PANED(vpane), 0); + pane_conf = PANE_LIST; +} + +static void view_profile(GtkWidget *w, gpointer data) +{ + save_pane_position(); + gtk_paned_set_position(GTK_PANED(hpane), 0); + gtk_paned_set_position(GTK_PANED(vpane), 65535); + pane_conf = PANE_PROFILE; +} + +static void view_info(GtkWidget *w, gpointer data) +{ + + save_pane_position(); + gtk_paned_set_position(GTK_PANED(vpane), 65535); + gtk_paned_set_position(GTK_PANED(hpane), 65535); + pane_conf = PANE_INFO; +} + +static void view_three(GtkWidget *w, gpointer data) +{ + GtkAllocation alloc; + GtkRequisition requisition; + + int vpane_position = subsurface_get_conf_int("vpane_position"); + int hpane_position = subsurface_get_conf_int("hpane_position"); + + gtk_widget_get_allocation(hpane, &alloc); + + if (hpane_position) + gtk_paned_set_position(GTK_PANED(hpane), hpane_position); + else + gtk_paned_set_position(GTK_PANED(hpane), alloc.width/2); + + gtk_widget_get_allocation(vpane, &alloc); + gtk_widget_size_request(notebook, &requisition); + /* pick the requested size for the notebook plus 6 pixels for frame */ + if (vpane_position) + gtk_paned_set_position(GTK_PANED(vpane), vpane_position); + else + gtk_paned_set_position(GTK_PANED(vpane), requisition.height + 6); + + pane_conf = PANE_THREE; +} + +static void toggle_zoom(GtkWidget *w, gpointer data) +{ + zoomed_plot = (zoomed_plot)?0 : 1; + /*Update dive*/ + repaint_dive(); +} + +static void prev_dc(GtkWidget *w, gpointer data) +{ + dc_number--; + /* If the dc number underflows, we'll "wrap around" and use the last dc */ + repaint_dive(); +} + +static void next_dc(GtkWidget *w, gpointer data) +{ + dc_number++; + /* If the dc number overflows, we'll "wrap around" and zero it */ + repaint_dive(); +} + + +/* list columns for nickname edit treeview */ +enum { + NE_MODEL, + NE_ID_STR, + NE_NICKNAME, + NE_NCOL +}; + +/* delete a selection of nicknames */ +static void edit_dc_delete_rows(GtkTreeView *view) +{ + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + GtkTreeRowReference *ref; + GtkTreeSelection *selection; + GList *selected_rows, *list, *row_references = NULL; + guint len; + /* params for delete op */ + const char *model_str; + const char *deviceid_string; /* convert to deviceid */ + uint32_t deviceid; + + selection = gtk_tree_view_get_selection(view); + selected_rows = gtk_tree_selection_get_selected_rows(selection, &model); + + for (list = selected_rows; list; list = g_list_next(list)) { + path = (GtkTreePath *)list->data; + ref = gtk_tree_row_reference_new(model, path); + row_references = g_list_append(row_references, ref); + } + len = g_list_length(row_references); + if (len == 0) + /* Warn about empty selection? */ + return; + + for (list = row_references; list; list = g_list_next(list)) { + path = gtk_tree_row_reference_get_path((GtkTreeRowReference *)(list->data)); + gtk_tree_model_get_iter(model, &iter, path); + gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, + NE_MODEL, &model_str, + NE_ID_STR, &deviceid_string, + -1); + if (sscanf(deviceid_string, "0x%x8", &deviceid) == 1) + remove_dc(model_str, deviceid); + + gtk_list_store_remove(GTK_LIST_STORE (model), &iter); + gtk_tree_path_free(path); + } + g_list_free(selected_rows); + g_list_free(row_references); + g_list_free(list); + + if (gtk_tree_model_get_iter_first(model, &iter)) + gtk_tree_selection_select_iter(selection, &iter); +} + +/* repopulate the edited nickname cell of a DC */ +static void cell_edited_cb(GtkCellRendererText *cell, gchar *path, + gchar *new_text, gpointer store) +{ + GtkTreeIter iter; + const char *model; + const char *deviceid_string; + uint32_t deviceid; + int matched; + + gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, path); + /* display new text */ + gtk_list_store_set(GTK_LIST_STORE(store), &iter, NE_NICKNAME, new_text, -1); + /* and new_text */ + gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, + NE_MODEL, &model, + NE_ID_STR, &deviceid_string, + -1); + /* extract deviceid */ + matched = sscanf(deviceid_string, "0x%x8", &deviceid); + + /* remember pending commit + * Need to extend list rather than wipe and store only one result */ + if (matched == 1){ + if (holdnicknames == NULL){ + holdnicknames = (struct device_info *) malloc(sizeof(struct device_info)); + holdnicknames->model = strdup(model); + holdnicknames->deviceid = deviceid; + holdnicknames->serial_nr = NULL; + holdnicknames->firmware = NULL; + holdnicknames->nickname = strdup(new_text); + holdnicknames->next = NULL; + } else { + struct device_info * top; + struct device_info * last = holdnicknames; + top = (struct device_info *) malloc(sizeof(struct device_info)); + top->model = strdup(model); + top->deviceid = deviceid; + top->serial_nr = NULL; + top->firmware = NULL; + top->nickname = strdup(new_text); + top->next = last; + holdnicknames = top; + } + } +} + +#define SUB_RESPONSE_DELETE 1 /* no delete response in gtk+2 */ +#define SUB_DONE 2 /* enable escape when done */ + +/* show the dialog to edit dc nicknames */ +static void edit_dc_nicknames(GtkWidget *w, gpointer data) +{ + const gchar *C_INACTIVE = "#e8e8ee", *C_ACTIVE = "#ffffff"; /* cell colours */ + GtkWidget *dialog, *confirm, *view, *scroll, *vbox; + GtkCellRenderer *renderer; + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkListStore *store; + GtkTreeIter iter; + gint res = -1; + char id_string[11] = {0}; + struct device_info * nnl; + + dialog = gtk_dialog_new_with_buttons(_("Edit Dive Computer Nicknames"), + GTK_WINDOW(main_window), + GtkDialogFlags(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), + GTK_STOCK_DELETE, + SUB_RESPONSE_DELETE, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, + GTK_STOCK_APPLY, + GTK_RESPONSE_APPLY, + NULL); + gtk_widget_set_size_request(dialog, 700, 400); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + + view = gtk_tree_view_new(); + store = gtk_list_store_new(NE_NCOL, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + model = GTK_TREE_MODEL(store); + + /* columns */ + renderer = gtk_cell_renderer_text_new(); + g_object_set(renderer, "background", C_INACTIVE, NULL); + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, _("Model"), + renderer, "text", NE_MODEL, NULL); + + renderer = gtk_cell_renderer_text_new(); + g_object_set(renderer, "background", C_INACTIVE, NULL); + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, _("Device Id"), + renderer, "text", NE_ID_STR, NULL); + + renderer = gtk_cell_renderer_text_new(); + g_object_set(renderer, "background", C_INACTIVE, NULL); + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, _("Nickname"), + renderer, "text", NE_NICKNAME, NULL); + g_object_set(renderer, "editable", TRUE, NULL); + g_object_set(renderer, "background", C_ACTIVE, NULL); + g_signal_connect(renderer, "edited", G_CALLBACK(cell_edited_cb), store); + + gtk_tree_view_set_model(GTK_TREE_VIEW(view), model); + g_object_unref(model); + + /* populate list store from device_info_list */ + nnl = head_of_device_info_list(); + while (nnl) { + sprintf(&id_string[0], "%#08x", nnl->deviceid); + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, + NE_MODEL, nnl->model, + NE_ID_STR, id_string, + NE_NICKNAME, nnl->nickname, + -1); + nnl = nnl->next; + } + + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); + gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_SINGLE); + + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_container_add(GTK_CONTAINER(scroll), view); + gtk_container_add(GTK_CONTAINER(vbox), + gtk_label_new(_("Edit a dive computer nickname by double-clicking the in the relevant nickname field"))); + gtk_container_add(GTK_CONTAINER(vbox), scroll); + gtk_widget_set_size_request(scroll, 500, 300); + gtk_widget_show_all(dialog); + + do { + res = gtk_dialog_run(GTK_DIALOG(dialog)); + if (res == SUB_RESPONSE_DELETE) { + confirm = gtk_dialog_new_with_buttons(_("Delete a dive computer information entry"), + GTK_WINDOW(dialog), + GtkDialogFlags(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), + GTK_STOCK_YES, + GTK_RESPONSE_YES, + GTK_STOCK_NO, + GTK_RESPONSE_NO, + NULL); + gtk_widget_set_size_request(confirm, 350, 90); + vbox = gtk_dialog_get_content_area(GTK_DIALOG(confirm)); + gtk_container_add(GTK_CONTAINER(vbox), + gtk_label_new(_("Ok to delete the selected entry?"))); + gtk_widget_show_all(confirm); + if (gtk_dialog_run(GTK_DIALOG(confirm)) == GTK_RESPONSE_YES) { + edit_dc_delete_rows(GTK_TREE_VIEW(view)); + res = SUB_DONE; /* want to close ** both ** dialogs now */ + } + mark_divelist_changed(TRUE); + gtk_widget_destroy(confirm); + } + if (res == GTK_RESPONSE_APPLY && holdnicknames && holdnicknames->model != NULL) { + struct device_info * walk = holdnicknames; + struct device_info * release = holdnicknames; + struct device_info * track = holdnicknames->next; + while (walk) { + remember_dc(walk->model, walk->deviceid, walk->nickname); + walk = walk->next; + } + /* clear down list */ + while (release){ + free(release); + release = track; + if (track) + track = track->next; + } + holdnicknames = NULL; + mark_divelist_changed(TRUE); + } + } while (res != SUB_DONE && res != GTK_RESPONSE_CANCEL && res != GTK_RESPONSE_DELETE_EVENT && res != GTK_RESPONSE_APPLY); + gtk_widget_destroy(dialog); +} + +static GtkActionEntry menu_items[] = { + { "FileMenuAction", NULL, N_("File"), NULL, NULL, NULL}, + { "LogMenuAction", NULL, N_("Log"), NULL, NULL, NULL}, + { "ViewMenuAction", NULL, N_("View"), NULL, NULL, NULL}, + { "FilterMenuAction", NULL, N_("Filter"), NULL, NULL, NULL}, + { "PlannerMenuAction", NULL, N_("Planner"), NULL, NULL, NULL}, + { "HelpMenuAction", NULL, N_("Help"), NULL, NULL, NULL}, + { "NewFile", GTK_STOCK_NEW, N_("New"), CTRLCHAR "N", NULL, G_CALLBACK(file_close) }, + { "OpenFile", GTK_STOCK_OPEN, N_("Open..."), CTRLCHAR "O", NULL, G_CALLBACK(file_open) }, + { "SaveFile", GTK_STOCK_SAVE, N_("Save..."), CTRLCHAR "S", NULL, G_CALLBACK(file_save) }, + { "SaveAsFile", GTK_STOCK_SAVE_AS, N_("Save As..."), SHIFTCHAR CTRLCHAR "S", NULL, G_CALLBACK(file_save_as) }, + { "CloseFile", GTK_STOCK_CLOSE, N_("Close"), NULL, NULL, G_CALLBACK(file_close) }, + { "Print", GTK_STOCK_PRINT, N_("Print..."), CTRLCHAR "P", NULL, G_CALLBACK(do_print) }, + { "ImportFile", NULL, N_("Import File(s)..."), CTRLCHAR "I", NULL, G_CALLBACK(import_files) }, + { "ExportUDDF", NULL, N_("Export UDDF..."), NULL, NULL, G_CALLBACK(export_all_dives_uddf_cb) }, + { "DownloadLog", NULL, N_("Download From Dive Computer..."), CTRLCHAR "D", NULL, G_CALLBACK(download_dialog) }, + { "DownloadWeb", GTK_STOCK_CONNECT, N_("Download From Web Service..."), NULL, NULL, G_CALLBACK(webservice_download_dialog) }, + { "AddDive", GTK_STOCK_ADD, N_("Add Dive..."), NULL, NULL, G_CALLBACK(add_dive_cb) }, + { "Preferences", GTK_STOCK_PREFERENCES, N_("Preferences..."), PREFERENCE_ACCEL, NULL, G_CALLBACK(preferences_dialog) }, + { "Renumber", NULL, N_("Renumber..."), NULL, NULL, G_CALLBACK(renumber_dialog) }, + { "YearlyStats", NULL, N_("Yearly Statistics"), NULL, NULL, G_CALLBACK(show_yearly_stats) }, +#if HAVE_OSM_GPS_MAP + { "DivesLocations", NULL, N_("Dives Locations"), CTRLCHAR "M", NULL, G_CALLBACK(show_gps_locations) }, +#endif + { "SelectEvents", NULL, N_("Select Events..."), NULL, NULL, G_CALLBACK(selectevents_dialog) }, + { "Quit", GTK_STOCK_QUIT, N_("Quit"), CTRLCHAR "Q", NULL, G_CALLBACK(quit) }, + { "About", GTK_STOCK_ABOUT, N_("About Subsurface"), NULL, NULL, G_CALLBACK(about_dialog) }, + { "UserManual", GTK_STOCK_HELP, N_("User Manual"), NULL, NULL, G_CALLBACK(show_user_manual) }, + { "ViewList", NULL, N_("List"), CTRLCHAR "1", NULL, G_CALLBACK(view_list) }, + { "ViewProfile", NULL, N_("Profile"), CTRLCHAR "2", NULL, G_CALLBACK(view_profile) }, + { "ViewInfo", NULL, N_("Info"), CTRLCHAR "3", NULL, G_CALLBACK(view_info) }, + { "ViewThree", NULL, N_("Three"), CTRLCHAR "4", NULL, G_CALLBACK(view_three) }, + { "EditNames", NULL, N_("Edit Device Names"), CTRLCHAR "E", NULL, G_CALLBACK(edit_dc_nicknames) }, + { "PrevDC", NULL, N_("Prev DC"), NULL, NULL, G_CALLBACK(prev_dc) }, + { "NextDC", NULL, N_("Next DC"), NULL, NULL, G_CALLBACK(next_dc) }, + { "InputPlan", NULL, N_("Input Plan"), NULL, NULL, G_CALLBACK(input_plan) }, +}; +static gint nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]); + +static GtkToggleActionEntry toggle_items[] = { + { "Autogroup", NULL, N_("Autogroup"), NULL, NULL, G_CALLBACK(autogroup_cb), FALSE }, + { "ToggleZoom", NULL, N_("Toggle Zoom"), CTRLCHAR "0", NULL, G_CALLBACK(toggle_zoom), FALSE }, +}; +static gint ntoggle_items = sizeof (toggle_items) / sizeof (toggle_items[0]); + +static const gchar* ui_string = " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " +#if HAVE_OSM_GPS_MAP + " " +#endif + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +"; + +static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager) +{ + action_group = gtk_action_group_new("Menu"); + gtk_action_group_set_translation_domain(action_group, "subsurface"); + gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0); + toggle_items[0].is_active = autogroup; + gtk_action_group_add_toggle_actions(action_group, toggle_items, ntoggle_items, 0); + + gtk_ui_manager_insert_action_group(ui_manager, action_group, 0); + GError* error = 0; + gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error); + + gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager)); + GtkWidget* menu = gtk_ui_manager_get_widget(ui_manager, "/MainMenu"); + + return menu; +} + +static void switch_page(GtkNotebook *notebook, gint arg1, gpointer user_data) +{ + repaint_dive(); +} + +static gboolean on_key_press(GtkWidget *w, GdkEventKey *event, GtkWidget *divelist) +{ + if (event->type != GDK_KEY_PRESS || event->state != 0) + return FALSE; + switch (event->keyval) { + case GDK_Up: + select_prev_dive(); + return TRUE; + case GDK_Down: + select_next_dive(); + return TRUE; + case GDK_Left: + prev_dc(NULL, NULL); + return TRUE; + case GDK_Right: + next_dc(NULL, NULL); + return TRUE; + } + return FALSE; +} + +static gboolean notebook_tooltip (GtkWidget *widget, gint x, gint y, + gboolean keyboard_mode, GtkTooltip *tooltip, gpointer data) +{ + if (amount_selected > 0 && gtk_notebook_get_current_page(GTK_NOTEBOOK(widget)) == 0) { + gtk_tooltip_set_text(tooltip, _("To edit dive information\ndouble click on it in the dive list")); + return TRUE; + } else { + return FALSE; + } +} + +void init_ui(int *argcp, char ***argvp) +{ + GtkWidget *win; + GtkWidget *nb_page; + GtkWidget *dive_list; + GtkWidget *menubar; + GtkWidget *vbox; + GtkWidget *scrolled; + GdkScreen *screen; + GtkIconTheme *icon_theme=NULL; + GtkSettings *settings; + GtkUIManager *ui_manager; + + gtk_init(argcp, argvp); + settings = gtk_settings_get_default(); + gtk_settings_set_long_property(settings, "gtk-tooltip-timeout", 10, "subsurface setting"); + gtk_settings_set_long_property(settings, "gtk-menu-images", 1, "subsurface setting"); + gtk_settings_set_long_property(settings, "gtk-button-images", 1, "subsurface setting"); + + /* check if utf8 stars are available as a default OS feature */ + if (!subsurface_os_feature_available(UTF8_FONT_WITH_STARS)) { + star_strings[0] = " "; + star_strings[1] = "* "; + star_strings[2] = "** "; + star_strings[3] = "*** "; + star_strings[4] = "**** "; + star_strings[5] = "*****"; + } + g_type_init(); + + subsurface_open_conf(); + + load_preferences(); + + default_dive_computer_vendor = subsurface_get_conf("dive_computer_vendor"); + default_dive_computer_product = subsurface_get_conf("dive_computer_product"); + default_dive_computer_device = subsurface_get_conf("dive_computer_device"); + error_info_bar = NULL; + win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + 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) { + const char *icon_name = subsurface_icon_name(); + if (!access(icon_name, R_OK)) + gtk_window_set_icon_from_file(GTK_WINDOW(win), icon_name, NULL); + } + 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); + g_signal_connect(G_OBJECT(win), "window-state-event", G_CALLBACK(on_state), NULL); + main_window = win; + + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(win), vbox); + main_vbox = vbox; + + ui_manager = gtk_ui_manager_new(); + menubar = get_menubar_menu(win, ui_manager); + + subsurface_ui_setup(settings, menubar, vbox, ui_manager); + + vpane = gtk_vpaned_new(); + gtk_box_pack_start(GTK_BOX(vbox), vpane, TRUE, TRUE, 3); + hpane = gtk_hpaned_new(); + gtk_paned_add1(GTK_PANED(vpane), hpane); + g_signal_connect_after(G_OBJECT(vbox), "realize", G_CALLBACK(view_three), NULL); + + /* Notebook for dive info vs profile vs .. */ + notebook = gtk_notebook_new(); + scrolled = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_paned_add1(GTK_PANED(hpane), scrolled); + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled), notebook); + g_signal_connect(notebook, "switch-page", G_CALLBACK(switch_page), NULL); + + /* Create the actual divelist */ + dive_list = dive_list_create(); + gtk_widget_set_name(dive_list, "Dive List"); + gtk_paned_add2(GTK_PANED(vpane), dive_list); + + /* Frame for dive profile */ + dive_profile = dive_profile_widget(); + gtk_widget_set_name(dive_profile, "Dive Profile"); + gtk_paned_add2(GTK_PANED(hpane), dive_profile); + + /* Frame for extended dive info */ + nb_page = extended_dive_info_widget(); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new(_("Dive Notes"))); + + /* Frame for dive equipment */ + nb_page = equipment_widget(W_IDX_PRIMARY); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new(_("Equipment"))); + + /* Frame for single dive statistics */ + nb_page = single_stats_widget(); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new(_("Dive Info"))); + + /* Frame for total dive statistics */ + nb_page = total_stats_widget(); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new(_("Stats"))); + + /* add tooltip that tells people how to edit things */ + g_object_set(notebook, "has-tooltip", TRUE, NULL); + g_signal_connect(notebook, "query-tooltip", G_CALLBACK(notebook_tooltip), NULL); + + /* handle some keys globally (to deal with gtk focus issues) */ + g_signal_connect (G_OBJECT (win), "key_press_event", G_CALLBACK (on_key_press), dive_list); + + gtk_widget_set_app_paintable(win, TRUE); + restore_window_geometry(); + gtk_widget_show_all(win); + + return; +} + +void run_ui(void) +{ + gtk_main(); +} + +void exit_ui(void) +{ + subsurface_close_conf(); + if (existing_filename) + free((void *)existing_filename); + if (default_dive_computer_device) + free((void *)default_dive_computer_device); +} + +typedef struct { + cairo_rectangle_t rect; + const char *text; + struct event *event; +} 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, struct event *event) +{ + cairo_rectangle_t *rect; + tooltip_rects = (tooltip_record_t *) + 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 = strdup(text); + tooltip_rects[tooltips].event = event; + tooltips++; +} + +#define INSIDE_RECT(_r,_x,_y) ((_r.x <= _x) && (_r.x + _r.width >= _x) && \ + (_r.y <= _y) && (_r.y + _r.height >= _y)) +#define INSIDE_RECT_X(_r, _x) ((_r.x <= _x) && (_r.x + _r.width >= _x)) + +static gboolean profile_tooltip (GtkWidget *widget, gint x, gint y, + gboolean keyboard_mode, GtkTooltip *tooltip, struct graphics_context *gc) +{ + int i; + cairo_rectangle_t *drawing_area = &gc->drawing_area; + gint tx = x - drawing_area->x; /* get transformed coordinates */ + gint ty = y - drawing_area->y; + gint width, height, time = -1; + char buffer[2048], plot[1024]; + const char *event = ""; + + if (tx < 0 || ty < 0) + return FALSE; + + /* don't draw a tooltip if nothing is there */ + if (amount_selected == 0 || gc->pi.nr == 0) + return FALSE; + + width = drawing_area->width - 2*drawing_area->x; + height = drawing_area->height - 2*drawing_area->y; + if (width <= 0 || height <= 0) + return FALSE; + + if (tx > width || ty > height) + return FALSE; + + time = (tx * gc->maxtime) / width; + + /* are we over an event marker ? */ + for (i = 0; i < tooltips; i++) { + if (INSIDE_RECT(tooltip_rects[i].rect, tx, ty)) { + event = tooltip_rects[i].text; + break; + } + } + get_plot_details(gc, time, plot, sizeof(plot)); + + snprintf(buffer, sizeof(buffer), "@ %d:%02d%c%s%c%s", time / 60, time % 60, + *plot ? '\n' : ' ', plot, + *event ? '\n' : ' ', event); + gtk_tooltip_set_text(tooltip, buffer); + return TRUE; + +} + +static double zoom_factor = 1.0; +static int zoom_x = -1, zoom_y = -1; + +static void common_drawing_function(GtkWidget *widget, struct graphics_context *gc) +{ + int i = 0; + struct dive *dive = current_dive; + + gc->drawing_area.x = MIN(50,gc->drawing_area.width / 20.0); + gc->drawing_area.y = MIN(50,gc->drawing_area.height / 20.0); + + g_object_set(widget, "has-tooltip", TRUE, NULL); + g_signal_connect(widget, "query-tooltip", G_CALLBACK(profile_tooltip), gc); + init_profile_background(gc); + cairo_paint(gc->cr); + + if (zoom_factor > 1.0) { + double n = -(zoom_factor-1); + cairo_translate(gc->cr, n*zoom_x, n*zoom_y); + cairo_scale(gc->cr, zoom_factor, zoom_factor); + } + + if (dive) { + if (tooltip_rects) { + while (i < tooltips) { + if (tooltip_rects[i].text) + free((void *)tooltip_rects[i].text); + i++; + } + free(tooltip_rects); + tooltip_rects = NULL; + } + tooltips = 0; + plot(gc, dive, SC_SCREEN); + } +} + +#if GTK_CHECK_VERSION(3,0,0) + +static gboolean draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) +{ + guint width, height; + static struct graphics_context gc = { .printer = 0 }; + + width = gtk_widget_get_allocated_width(widget); + height = gtk_widget_get_allocated_height(widget); + + gc.drawing_area.width = width; + gc.drawing_area.height = height; + gc.cr = cr; + + common_drawing_function(widget, &gc); + return FALSE; +} + +#else /* gtk2 */ + +static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data) +{ + GtkAllocation allocation; + static struct graphics_context gc = { 0 }; + + /* 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 */ + gtk_widget_get_allocation(widget, &allocation); + gc.drawing_area.width = allocation.width; + gc.drawing_area.height = allocation.height; + gc.cr = gdk_cairo_create(gtk_widget_get_window(widget)); + + common_drawing_function(widget, &gc); + cairo_destroy(gc.cr); + return FALSE; +} + +#endif + +static void zoom_event(int x, int y, double inc) +{ + zoom_x = x; + zoom_y = y; + inc += zoom_factor; + if (inc < 1.0) + inc = 1.0; + else if (inc > 10) + inc = 10; + zoom_factor = inc; +} + +static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer user_data) +{ + switch (event->direction) { + case GDK_SCROLL_UP: + zoom_event(event->x, event->y, 0.1); + break; + case GDK_SCROLL_DOWN: + zoom_event(event->x, event->y, -0.1); + break; + default: + return TRUE; + } + gtk_widget_queue_draw(widget); + return TRUE; +} + +static void add_gas_change_cb(GtkWidget *menuitem, gpointer data) +{ + double *x = (double *)data; + int when = x_to_time(*x); + int cylnr = select_cylinder(current_dive, when); + if (cylnr >= 0) { + cylinder_t *cyl = ¤t_dive->cylinder[cylnr]; + int value = cyl->gasmix.o2.permille / 10 | ((cyl->gasmix.he.permille / 10) << 16); + add_event(current_dc, when, 25, 0, value, "gaschange"); + mark_divelist_changed(TRUE); + report_dives(FALSE, FALSE); + dive_list_update_dives(); + } +} + +int confirm_dialog(int when, char *action_text, char *event_text) +{ + GtkWidget *dialog, *vbox, *label; + int confirmed; + char title[80]; + + snprintf(title, sizeof(title), "%s %s", action_text, event_text); + dialog = gtk_dialog_new_with_buttons(title, + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + label = create_label(_("%s event at %d:%02u"), title, FRACTION(when, 60)); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); + gtk_widget_show_all(dialog); + confirmed = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; + + gtk_widget_destroy(dialog); + + return confirmed; +} + +static void add_bookmark_cb(GtkWidget *menuitem, gpointer data) +{ + double *x = (double *)data; + int when = x_to_time(*x); + + if (confirm_dialog(when, _("Add"), _("bookmark"))){ + add_event(current_dc, when, 8, 0, 0, "bookmark"); + mark_divelist_changed(TRUE); + report_dives(FALSE, FALSE); + } +} + +static struct event *event_at_x(double rel_x) +{ + /* is there an event marker at this x coordinate */ + struct event *ret = NULL; + int i; + int x = x_abs(rel_x); + + for (i = 0; i < tooltips; i++) { + if (INSIDE_RECT_X(tooltip_rects[i].rect, x)) { + ret = tooltip_rects[i].event; + break; + } + } + return ret; +} + +static void remove_event_cb(GtkWidget *menuitem, gpointer data) +{ + struct event *event = (struct event *)data; + if (confirm_dialog(event->time.seconds, _("Remove"), _(event->name))){ + struct event **ep = ¤t_dc->events; + while (ep && *ep != event) + ep = &(*ep)->next; + if (ep) { + *ep = event->next; + free(event); + } + mark_divelist_changed(TRUE); + report_dives(FALSE, FALSE); + } +} + +static void popup_profile_menu(GtkWidget *widget, GdkEventButton *gtk_event) +{ + GtkWidget *menu, *menuitem, *image; + static double x; + struct event *event; + + if (!gtk_event || !current_dive) + return; + x = gtk_event->x; + menu = gtk_menu_new(); + menuitem = gtk_image_menu_item_new_with_label(_("Add gas change event here")); + image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); + g_signal_connect(menuitem, "activate", G_CALLBACK(add_gas_change_cb), &x); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + menuitem = gtk_image_menu_item_new_with_label(_("Add bookmark event here")); + image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); + g_signal_connect(menuitem, "activate", G_CALLBACK(add_bookmark_cb), &x); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + if ((event = event_at_x(x)) != NULL) { + menuitem = gtk_image_menu_item_new_with_label(_("Remove event here")); + image = gtk_image_new_from_stock(GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); + g_signal_connect(menuitem, "activate", G_CALLBACK(remove_event_cb), event); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } + + gtk_widget_show_all(menu); + + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, + gtk_event->button, gtk_get_current_event_time()); + +} + +static gboolean clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data) +{ + switch (event->button) { + case 1: + zoom_x = event->x; + zoom_y = event->y; + zoom_factor = 2.5; + break; + case 3: + popup_profile_menu(widget, event); + break; + default: + return TRUE; + } + gtk_widget_queue_draw(widget); + return TRUE; +} + +static gboolean released(GtkWidget *widget, GdkEventButton *event, gpointer user_data) +{ + switch (event->button) { + case 1: + zoom_x = zoom_y = -1; + zoom_factor = 1.0; + break; + default: + return TRUE; + } + gtk_widget_queue_draw(widget); + return TRUE; +} + +static gboolean motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data) +{ + if (zoom_x < 0) + return TRUE; + + zoom_x = event->x; + zoom_y = event->y; + gtk_widget_queue_draw(widget); + return TRUE; +} + +static GtkWidget *dive_profile_widget(void) +{ + GtkWidget *da; + + da = gtk_drawing_area_new(); + gtk_widget_set_size_request(da, 350, 250); +#if GTK_CHECK_VERSION(3,0,0) + g_signal_connect(da, "draw", G_CALLBACK (draw_callback), NULL); +#else + g_signal_connect(da, "expose_event", G_CALLBACK(expose_event), NULL); +#endif + g_signal_connect(da, "button-press-event", G_CALLBACK(clicked), NULL); + g_signal_connect(da, "scroll-event", G_CALLBACK(scroll_event), NULL); + g_signal_connect(da, "button-release-event", G_CALLBACK(released), NULL); + g_signal_connect(da, "motion-notify-event", G_CALLBACK(motion), NULL); + gtk_widget_add_events(da, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK); + + return da; +} + +static void do_import_file(gpointer data, gpointer user_data) +{ + GError *error = NULL; + parse_file((const char *)data, &error, FALSE); + + if (error != NULL) + { + report_error(error); + g_error_free(error); + error = NULL; + } +} + +static void import_files(GtkWidget *w, gpointer data) +{ + GtkWidget *fs_dialog; + const char *current_default; + char *current_def_dir; + GtkFileFilter *filter; + struct stat sb; + GSList *filenames = NULL; + + fs_dialog = gtk_file_chooser_dialog_new(_("Choose XML Files To Import Into Current Data File"), + GTK_WINDOW(main_window), + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + + /* I'm not sure what the best default path should be... */ + if (existing_filename) { + current_def_dir = g_path_get_dirname(existing_filename); + } else { + current_default = prefs.default_filename; + current_def_dir = g_path_get_dirname(current_default); + } + + /* it's possible that the directory doesn't exist (especially for the default) + * For gtk's file select box to make sense we create it */ + if (stat(current_def_dir, &sb) != 0) + g_mkdir(current_def_dir, S_IRWXU); + + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs_dialog), current_def_dir); + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(fs_dialog), TRUE); + filter = setup_filter(); + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fs_dialog), filter); + gtk_widget_show_all(fs_dialog); + if (gtk_dialog_run(GTK_DIALOG(fs_dialog)) == GTK_RESPONSE_ACCEPT) { + /* grab the selected file list, import each file and update the list */ + filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(fs_dialog)); + if (filenames) { + g_slist_foreach(filenames, do_import_file, NULL); + report_dives(TRUE, FALSE); + g_slist_free(filenames); + } + } + + free(current_def_dir); + gtk_widget_destroy(fs_dialog); +} + +void set_filename(const char *filename, gboolean force) +{ + if (!force && existing_filename) + return; + free((void *)existing_filename); + if (filename) + existing_filename = strdup(filename); + else + existing_filename = NULL; +} + +const char *get_dc_nickname(const char *model, uint32_t deviceid) +{ + struct device_info *known = get_device_info(model, deviceid); + if (known) { + if (known->nickname && *known->nickname) + return known->nickname; + else + return known->model; + } + return NULL; +} + +void set_dc_nickname(struct dive *dive) +{ + GtkWidget *dialog, *vbox, *entry, *frame, *label; + char nickname[160] = ""; + char dialogtext[2048]; + const char *name = nickname; + struct divecomputer *dc = &dive->dc; + + if (!dive) + return; + while (dc) { +#if NICKNAME_DEBUG & 16 + fprintf(debugfile, "set_dc_nickname for model %s deviceid %8x\n", dc->model ? : "", dc->deviceid); +#endif + if (get_dc_nickname(dc->model, dc->deviceid) == NULL) { + struct device_info *nn_entry = get_different_device_info(dc->model, dc->deviceid); + if (nn_entry) { + dialog = gtk_dialog_new_with_buttons( + _("Dive Computer Nickname"), + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + NULL); + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + snprintf(dialogtext, sizeof(dialogtext), + _("You already have a dive computer of this model\n" + "named %s\n" + "Subsurface can maintain a nickname for this device to " + "distinguish it from the existing one. " + "The default is the model and device ID as shown below.\n" + "If you don't want to name this dive computer click " + "'Cancel' and Subsurface will simply display its model " + "as its name (which may mean that you cannot tell the two " + "dive computers apart in the logs)."), + nn_entry->nickname && *nn_entry->nickname ? nn_entry->nickname : + (nn_entry->model && *nn_entry->model ? nn_entry->model : _("(nothing)"))); + label = gtk_label_new(dialogtext); + gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 3); + frame = gtk_frame_new(_("Nickname")); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, TRUE, 3); + entry = gtk_entry_new(); + gtk_container_add(GTK_CONTAINER(frame), entry); + gtk_entry_set_max_length(GTK_ENTRY(entry), 68); + snprintf(nickname, sizeof(nickname), "%s (%08x)", dc->model, dc->deviceid); + gtk_entry_set_text(GTK_ENTRY(entry), nickname); + gtk_widget_show_all(dialog); + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + if (strcmp(dc->model, gtk_entry_get_text(GTK_ENTRY(entry)))) { + name = gtk_entry_get_text(GTK_ENTRY(entry)); + remember_dc(dc->model, dc->deviceid, name); + mark_divelist_changed(TRUE); + } + } else { + /* Remember that we declined the nickname */ + remember_dc(dc->model, dc->deviceid, NULL); + } + gtk_widget_destroy(dialog); + } else { + remember_dc(dc->model, dc->deviceid, NULL); + } + } + dc = dc->next; + } +} + +gdouble get_screen_dpi(void) +{ + const gdouble mm_per_inch = 25.4; + GdkScreen *scr = gdk_screen_get_default(); + gdouble h_mm = gdk_screen_get_height_mm(scr); + gdouble h = gdk_screen_get_height(scr); + gdouble dpi_h = floor((h / h_mm) * mm_per_inch); + return dpi_h; +} diff --git a/webservice.h b/webservice.h index bb3a71f3b..ee07e9438 100644 --- a/webservice.h +++ b/webservice.h @@ -1,3 +1,11 @@ +#ifdef __cplusplus +extern "C" { +#endif + extern void webservice_download_dialog(void); extern gboolean webservice_request_user_xml(const gchar *, gchar **, guint *, guint *); extern int divelogde_upload(char *fn); + +#ifdef __cplusplus +} +#endif diff --git a/windows.c b/windows.c index f06ffc7a8..23b6d2f4c 100644 --- a/windows.c +++ b/windows.c @@ -20,12 +20,12 @@ void subsurface_open_conf(void) printf("CreateKey Software\\subsurface failed %ld\n", success); } -void subsurface_unset_conf(char *name) +void subsurface_unset_conf(const char *name) { RegDeleteValue(hkey, (LPCTSTR)name); } -void subsurface_set_conf(char *name, const char *value) +void subsurface_set_conf(const char *name, const char *value) { /* since we are using the pointer 'value' as both an actual * pointer to the string setting and as a way to pass the @@ -52,17 +52,17 @@ void subsurface_set_conf(char *name, const char *value) free(wname); } -void subsurface_set_conf_int(char *name, int value) +void subsurface_set_conf_int(const char *name, int value) { RegSetValueEx(hkey, (LPCTSTR)name, 0, REG_DWORD, (const BYTE *)&value, 4); } -void subsurface_set_conf_bool(char *name, int value) +void subsurface_set_conf_bool(const char *name, int value) { subsurface_set_conf_int(name, value); } -const void *subsurface_get_conf(char *name) +const char *subsurface_get_conf(const char *name) { const int csize = 64; int blen = 0; -- cgit v1.2.3-70-g09d2 From 7ea22811802cda1df227e7fb4b76720fc15b2dcd Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Mon, 1 Apr 2013 13:57:51 +0300 Subject: Introduce QApplication Instantiate a QApplication and let Qt handle the event loop. Add a QTranslator subclass to translate the UI via gettext. Signed-off-by: Alberto Mardegan --- Makefile | 2 ++ qt-gui.cpp | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index a38586ff0..a9da8dd1a 100644 --- a/Makefile +++ b/Makefile @@ -317,6 +317,8 @@ MOCFLAGS = $(filter -I%, $(CXXFLAGS) $(EXTRA_FLAGS)) $(filter -D%, $(CXXFLAGS) $ @echo ' MOC' $< @$(MOC) -i $(MOCFLAGS) $< -o $@ +qt-gui.o: qt-gui.moc.cpp + %.ui.h: ui/%.ui @echo ' UIC' $< @$(UIC) $< -o $@ diff --git a/qt-gui.cpp b/qt-gui.cpp index 54aa582b4..3285d8e27 100644 --- a/qt-gui.cpp +++ b/qt-gui.cpp @@ -27,11 +27,36 @@ #include #include +#include +#include #if HAVE_OSM_GPS_MAP #include #endif +class Translator: public QTranslator +{ + Q_OBJECT + +public: + Translator(QObject *parent = 0); + ~Translator() {} + + virtual QString translate(const char *context, const char *sourceText, + const char *disambiguation = NULL) const; +}; + +Translator::Translator(QObject *parent): + QTranslator(parent) +{ +} + +QString Translator::translate(const char *context, const char *sourceText, + const char *disambiguation) const +{ + return QString::fromUtf8(gettext(sourceText)); +} + static const GdkPixdata subsurface_icon_pixbuf = {}; GtkWidget *main_window; @@ -40,6 +65,7 @@ GtkWidget *error_info_bar; GtkWidget *error_label; GtkWidget *vpane, *hpane; GtkWidget *notebook; +static QApplication *application = NULL; int error_count; const char *existing_filename; @@ -1719,6 +1745,9 @@ void init_ui(int *argcp, char ***argvp) GtkSettings *settings; GtkUIManager *ui_manager; + application = new QApplication(*argcp, *argvp); + application->installTranslator(new Translator(application)); + gtk_init(argcp, argvp); settings = gtk_settings_get_default(); gtk_settings_set_long_property(settings, "gtk-tooltip-timeout", 10, "subsurface setting"); @@ -1832,11 +1861,12 @@ void init_ui(int *argcp, char ***argvp) void run_ui(void) { - gtk_main(); + application->exec(); } void exit_ui(void) { + delete application; subsurface_close_conf(); if (existing_filename) free((void *)existing_filename); @@ -2363,3 +2393,5 @@ gdouble get_screen_dpi(void) gdouble dpi_h = floor((h / h_mm) * mm_per_inch); return dpi_h; } + +#include "qt-gui.moc.cpp" -- cgit v1.2.3-70-g09d2 From b5b14f1d95b0362a13fa3f11ac3f334012750099 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Mon, 1 Apr 2013 23:03:47 +0300 Subject: Define QT_NO_KEYWORDS This prevents Qt headers from defining macro symbols such as "emit" and "signals", therefore avoiding conflicts when struct members with the same names are defined in some C header. Signed-off-by: Alberto Mardegan --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index a9da8dd1a..aeb3cca0f 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ VERSION=3.0.2 CC=gcc CFLAGS=-Wall -Wno-pointer-sign -g $(CLCFLAGS) -DGSEAL_ENABLE CXX=g++ -CXXFLAGS=-Wall -g $(CLCFLAGS) +CXXFLAGS=-Wall -g $(CLCFLAGS) -DQT_NO_KEYWORDS INSTALL=install PKGCONFIG=pkg-config XML2CONFIG=xml2-config -- cgit v1.2.3-70-g09d2 From 40e3671bd2656be9657abe8c6d955cef612805c5 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Mon, 1 Apr 2013 23:08:13 +0300 Subject: Improve Makefile rules for running moc The previous rules were conflicting, and the naming of the moc-generated file to be included in .cpp files was deviating from what's most used in Qt: the usual way is to #include "file.moc" and not #include "file.moc.cpp" Signed-off-by: Alberto Mardegan --- Makefile | 15 ++++++++++----- qt-gui.cpp | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index aeb3cca0f..c0114386c 100644 --- a/Makefile +++ b/Makefile @@ -306,18 +306,23 @@ MOCFLAGS = $(filter -I%, $(CXXFLAGS) $(EXTRA_FLAGS)) $(filter -D%, $(CXXFLAGS) $ @mkdir -p .dep @$(CXX) $(CXXFLAGS) $(EXTRA_FLAGS) -MD -MF .dep/$@.dep -c -o $@ $< +# This rule is for running the moc on QObject subclasses defined in the .h +# files. +# To activate this rule, add .moc.o to the OBJS variable. %.moc.cpp: %.h @echo ' MOC' $< @$(MOC) $(MOCFLAGS) $< -o $@ -# This rule is for running the moc on QObject subclasses defined in the .cpp files; -# remember to #include ".moc.cpp" at the end of the .cpp file, or you'll -# get linker errors ("undefined vtable for...") -%.moc.cpp: %.cpp +# This rule is for running the moc on QObject subclasses defined in the .cpp +# files; remember to #include ".moc" at the end of the .cpp file, or +# you'll get linker errors ("undefined vtable for...") +# To activate this rule, you need another rule on the .o file, like: +# file.o: file.moc +%.moc: %.cpp @echo ' MOC' $< @$(MOC) -i $(MOCFLAGS) $< -o $@ -qt-gui.o: qt-gui.moc.cpp +qt-gui.o: qt-gui.moc %.ui.h: ui/%.ui @echo ' UIC' $< diff --git a/qt-gui.cpp b/qt-gui.cpp index 3285d8e27..0fbcf2555 100644 --- a/qt-gui.cpp +++ b/qt-gui.cpp @@ -2394,4 +2394,4 @@ gdouble get_screen_dpi(void) return dpi_h; } -#include "qt-gui.moc.cpp" +#include "qt-gui.moc" -- cgit v1.2.3-70-g09d2 From a412753b0a2eb6323f350e98287b004f5b3b6c5c Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Tue, 2 Apr 2013 19:49:17 +0300 Subject: Add a Qt main window This is just an empty window with a File menu and a few items. It shows how to hook up functions to menu actions. Signed-off-by: Alberto Mardegan --- Makefile | 2 +- qt-gui.cpp | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ ui/main-window.ui | 101 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 ui/main-window.ui (limited to 'Makefile') diff --git a/Makefile b/Makefile index c0114386c..59e2f9522 100644 --- a/Makefile +++ b/Makefile @@ -322,7 +322,7 @@ MOCFLAGS = $(filter -I%, $(CXXFLAGS) $(EXTRA_FLAGS)) $(filter -D%, $(CXXFLAGS) $ @echo ' MOC' $< @$(MOC) -i $(MOCFLAGS) $< -o $@ -qt-gui.o: qt-gui.moc +qt-gui.o: main-window.ui.h qt-gui.moc %.ui.h: ui/%.ui @echo ' UIC' $< diff --git a/qt-gui.cpp b/qt-gui.cpp index 2e3f2c903..285f2082d 100644 --- a/qt-gui.cpp +++ b/qt-gui.cpp @@ -24,10 +24,14 @@ #include "webservice.h" #include "version.h" #include "libdivecomputer.h" +#include "main-window.ui.h" #include #include #include +#include +#include +#include #include #if HAVE_OSM_GPS_MAP @@ -1733,6 +1737,120 @@ static gboolean notebook_tooltip (GtkWidget *widget, gint x, gint y, } } +class MainWindow: public QMainWindow, private Ui::MainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = 0); + ~MainWindow() {} + + void setCurrentFileName(const QString &fileName); + +private Q_SLOTS: + void on_actionNew_triggered() { on_actionClose_triggered(); } + void on_actionOpen_triggered(); + void on_actionSave_triggered() { file_save(NULL, NULL); } + void on_actionSaveAs_triggered() { file_save_as(NULL, NULL); } + void on_actionClose_triggered(); + +private: + QStringList fileNameFilters() const; + +private: + QString m_currentFileName; +}; + +MainWindow::MainWindow(QWidget *parent): + QMainWindow(parent) +{ + setupUi(this); +} + +void MainWindow::setCurrentFileName(const QString &fileName) +{ + if (fileName == m_currentFileName) return; + m_currentFileName = fileName; + + QString title = tr("Subsurface"); + if (!m_currentFileName.isEmpty()) { + QFileInfo fileInfo(m_currentFileName); + title += " - " + fileInfo.fileName(); + } + setWindowTitle(title); +} + +void MainWindow::on_actionOpen_triggered() +{ + QString defaultFileName = QString::fromUtf8(prefs.default_filename); + QFileInfo fileInfo(defaultFileName); + + QFileDialog dialog(this, tr("Open File"), fileInfo.path()); + dialog.setFileMode(QFileDialog::ExistingFile); + dialog.selectFile(defaultFileName); + dialog.setNameFilters(fileNameFilters()); + if (dialog.exec()) { + /* first, close the existing file, if any */ + file_close(NULL, NULL); + + /* we know there is only one filename */ + QString fileName = dialog.selectedFiles().first(); + GError *error = NULL; + parse_file(fileName.toUtf8().constData(), &error); + if (error != NULL) { + report_error(error); + g_error_free(error); + error = NULL; + } else { + setCurrentFileName(fileName); + } + report_dives(FALSE, FALSE); + } +} + +void MainWindow::on_actionClose_triggered() +{ + if (unsaved_changes()) + if (ask_save_changes() == FALSE) + return; + + setCurrentFileName(QString()); + + /* free the dives and trips */ + while (dive_table.nr) + delete_single_dive(0); + mark_divelist_changed(FALSE); + + /* clear the selection and the statistics */ + selected_dive = 0; + process_selected_dives(); + clear_stats_widgets(); + clear_events(); + show_dive_stats(NULL); + + /* clear the equipment page */ + clear_equipment_widgets(); + + /* redraw the screen */ + dive_list_update_dives(); + show_dive_info(NULL); +} + +QStringList MainWindow::fileNameFilters() const +{ + QStringList filters; + + filters << "*.xml *.uddf *.udcf *.jlb" +#ifdef LIBZIP + " *.sde *.dld" +#endif +#ifdef SQLITE3 + " *.db" +#endif + ; + return filters; +} + void init_ui(int *argcp, char ***argvp) { GtkWidget *win; @@ -1748,6 +1866,8 @@ void init_ui(int *argcp, char ***argvp) application = new QApplication(*argcp, *argvp); application->installTranslator(new Translator(application)); + MainWindow *window = new MainWindow(); + window->show(); gtk_init(argcp, argvp); settings = gtk_settings_get_default(); diff --git a/ui/main-window.ui b/ui/main-window.ui new file mode 100644 index 000000000..58ed198fd --- /dev/null +++ b/ui/main-window.ui @@ -0,0 +1,101 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + Subsurface + + + + + + 0 + 0 + 800 + 23 + + + + + File + + + + + + + + + + + + + + + + + + New + + + Ctrl+N + + + + + + + + + + Open... + + + Ctrl+O + + + + + + + + Save... + + + Ctrl+S + + + + + + + + Save As... + + + Ctrl+Shift+S + + + + + + + + Close + + + Ctrl+W + + + + + + -- cgit v1.2.3-70-g09d2 From 1d61955be973cfcc67f4e82a65487721a9ae84b8 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sat, 6 Apr 2013 20:49:06 -0700 Subject: Separate Gtk related code from core logic: divelist This is simplistic & brute force: any function that touches Gtk related data structures is moved to divelist-gtk.c, everything else stays in divelist.c. Header files have been adjusted so that this still compiles and appears to work. More thought is needed to truly abstract this out, but this seems to be a good point to commit this change. Signed-off-by: Dirk Hohndel --- Makefile | 2 +- display-gtk.h | 2 +- dive.h | 2 +- divelist-gtk.c | 2325 +++++++++++++++++++++++++++++++++++++++++++++++++ divelist.c | 2451 +++------------------------------------------------- divelist.h | 22 +- uemis-downloader.c | 4 +- 7 files changed, 2452 insertions(+), 2356 deletions(-) create mode 100644 divelist-gtk.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index 59e2f9522..aa42db2cf 100644 --- a/Makefile +++ b/Makefile @@ -182,7 +182,7 @@ LIBS = $(LIBQT) $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGTK) $(LIBGCONF2) $(LIB MSGLANGS=$(notdir $(wildcard po/*.po)) MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.mo)) -OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o deco.o planner.o \ +OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o divelist-gtk.o deco.o planner.o \ parse-xml.o save-xml.o libdivecomputer.o print.o uemis.o uemis-downloader.o \ qt-gui.o statistics.o file.o cochran.o device.o download-dialog.o prefs.o \ webservice.o sha1.o $(GPSOBJ) $(OSSUPPORT).o $(RESFILE) diff --git a/display-gtk.h b/display-gtk.h index 4cc86659f..904d5efce 100644 --- a/display-gtk.h +++ b/display-gtk.h @@ -81,7 +81,7 @@ extern GtkWidget *create_label(const char *fmt, ...); extern gboolean icon_click_cb(GtkWidget *w, GdkEventButton *event, gpointer data); -unsigned int amount_selected; +extern unsigned int amount_selected; extern void process_selected_dives(void); diff --git a/dive.h b/dive.h index 5efde438d..aff856856 100644 --- a/dive.h +++ b/dive.h @@ -414,7 +414,7 @@ static inline int rel_mbar_to_depth(int mbar, struct dive *dive) * be able to edit a dive without unintended side effects */ extern struct dive edit_dive; -extern gboolean autogroup; +extern short autogroup; /* random threashold: three days without diving -> new trip * this works very well for people who usually dive as part of a trip and don't * regularly dive at a local facility; this is why trips are an optional feature */ diff --git a/divelist-gtk.c b/divelist-gtk.c new file mode 100644 index 000000000..8587e11c6 --- /dev/null +++ b/divelist-gtk.c @@ -0,0 +1,2325 @@ +/* divelist-gtk.c */ +/* this creates the UI for the dive list - + * controlled through the following interfaces: + * + * GdkPixbuf *get_gps_icon(void) + * void flush_divelist(struct dive *dive) + * void set_divelist_font(const char *font) + * void dive_list_update_dives(void) + * void update_dive_list_units(void) + * void update_dive_list_col_visibility(void) + * void dive_list_update_dives(void) + * void add_dive_cb(GtkWidget *menuitem, gpointer data) + * gboolean icon_click_cb(GtkWidget *w, GdkEventButton *event, gpointer data) + * void export_all_dives_uddf_cb() + * GtkWidget dive_list_create(void) + * void dive_list_destroy(void) + * void show_and_select_dive(struct dive *dive) + * void select_next_dive(void) + * void select_prev_dive(void) + */ +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef LIBZIP +#include +#endif +#ifdef XSLT +#include +#endif + +#include "dive.h" +#include "divelist.h" +#include "display.h" +#include "display-gtk.h" +#include "webservice.h" + +#include +#include "satellite.h" + +struct DiveList { + GtkWidget *tree_view; + GtkWidget *container_widget; + GtkTreeStore *model, *listmodel, *treemodel; + GtkTreeViewColumn *nr, *date, *stars, *depth, *duration, *location; + GtkTreeViewColumn *temperature, *cylinder, *totalweight, *suit, *nitrox, *sac, *otu, *maxcns; + int changed; +}; + +static struct DiveList dive_list; +#define MODEL(_dl) GTK_TREE_MODEL((_dl).model) +#define TREEMODEL(_dl) GTK_TREE_MODEL((_dl).treemodel) +#define LISTMODEL(_dl) GTK_TREE_MODEL((_dl).listmodel) +#define STORE(_dl) GTK_TREE_STORE((_dl).model) +#define TREESTORE(_dl) GTK_TREE_STORE((_dl).treemodel) +#define LISTSTORE(_dl) GTK_TREE_STORE((_dl).listmodel) + +dive_trip_t *dive_trip_list; +short autogroup = FALSE; +static gboolean in_set_cursor = FALSE; +static gboolean set_selected(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer data); + +/* + * The dive list has the dive data in both string format (for showing) + * and in "raw" format (for sorting purposes) + */ +enum { + DIVE_INDEX = 0, + DIVE_NR, /* int: dive->nr */ + DIVE_DATE, /* timestamp_t: dive->when */ + DIVE_RATING, /* int: 0-5 stars */ + DIVE_DEPTH, /* int: dive->maxdepth in mm */ + DIVE_DURATION, /* int: in seconds */ + DIVE_TEMPERATURE, /* int: in mkelvin */ + DIVE_TOTALWEIGHT, /* int: in grams */ + DIVE_SUIT, /* "wet, 3mm" */ + DIVE_CYLINDER, + DIVE_NITROX, /* int: dummy */ + DIVE_SAC, /* int: in ml/min */ + DIVE_OTU, /* int: in OTUs */ + DIVE_MAXCNS, /* int: in % */ + DIVE_LOCATION, /* "2nd Cathedral, Lanai" */ + DIVE_LOC_ICON, /* pixbuf for gps icon */ + DIVELIST_COLUMNS +}; + +static void merge_dive_into_trip_above_cb(GtkWidget *menuitem, GtkTreePath *path); + +#ifdef DEBUG_MODEL +static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) +{ + char *location; + int idx, nr, duration; + struct dive *dive; + timestamp_t when; + struct tm tm; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, DIVE_DATE, &when, + DIVE_DURATION, &duration, DIVE_LOCATION, &location, -1); + utc_mkdate(when, &tm); + printf("iter %x:%x entry #%d : nr %d @ %04d-%02d-%02d %02d:%02d:%02d duration %d location %s ", + iter->stamp, iter->user_data, idx, nr, + tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, + duration, location); + dive = get_dive(idx); + if (dive) + printf("tripflag %d\n", dive->tripflag); + else + printf("without matching dive\n"); + + free(location); + + return FALSE; +} + +static void dump_model(GtkListStore *store) +{ + gtk_tree_model_foreach(GTK_TREE_MODEL(store), dump_model_entry, NULL); + printf("\n---\n\n"); +} +#endif + +/* when subsurface starts we want to have the last dive selected. So we simply + walk to the first leaf (and skip the summary entries - which have negative + DIVE_INDEX) */ +static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx) +{ + GtkTreeIter parent; + GtkTreePath *tpath; + + while (*diveidx < 0) { + memcpy(&parent, iter, sizeof(parent)); + tpath = gtk_tree_model_get_path(model, &parent); + if (!gtk_tree_model_iter_children(model, iter, &parent)) { + /* we should never have a parent without child */ + gtk_tree_path_free(tpath); + return; + } + if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath)) + gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE); + gtk_tree_path_free(tpath); + gtk_tree_model_get(model, iter, DIVE_INDEX, diveidx, -1); + } +} + +static struct dive *dive_from_path(GtkTreePath *path) +{ + GtkTreeIter iter; + int idx; + + if (gtk_tree_model_get_iter(MODEL(dive_list), &iter, path)) { + gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); + return get_dive(idx); + } else { + return NULL; + } + +} + +static int get_path_index(GtkTreePath *path) +{ + GtkTreeIter iter; + int idx; + + gtk_tree_model_get_iter(MODEL(dive_list), &iter, path); + gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); + return idx; +} + +/* make sure that if we expand a summary row that is selected, the children show + up as selected, too */ +static void row_expanded_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data) +{ + GtkTreeIter child; + GtkTreeModel *model = MODEL(dive_list); + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + dive_trip_t *trip; + + trip = find_trip_by_idx(get_path_index(path)); + if (!trip) + return; + + trip->expanded = 1; + if (!gtk_tree_model_iter_children(model, &child, iter)) + return; + + do { + int idx; + struct dive *dive; + + gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1); + dive = get_dive(idx); + + if (dive->selected) + gtk_tree_selection_select_iter(selection, &child); + else + gtk_tree_selection_unselect_iter(selection, &child); + } while (gtk_tree_model_iter_next(model, &child)); +} + +/* Make sure that if we collapse a summary row with any selected children, the row + shows up as selected too */ +static void row_collapsed_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data) +{ + dive_trip_t *trip; + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + + trip = find_trip_by_idx(get_path_index(path)); + if (!trip) + return; + + trip->expanded = 0; + if (trip_has_selected_dives(trip)) { + gtk_tree_selection_select_iter(selection, iter); + trip->selected = 1; + } +} + +const char *star_strings[] = { + ZERO_STARS, + ONE_STARS, + TWO_STARS, + THREE_STARS, + FOUR_STARS, + FIVE_STARS +}; + +static void star_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int nr_stars, idx; + char buffer[40]; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_RATING, &nr_stars, -1); + if (idx < 0) { + *buffer = '\0'; + } else { + if (nr_stars < 0 || nr_stars > 5) + nr_stars = 0; + snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]); + } + g_object_set(renderer, "text", buffer, NULL); +} + +static void date_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int idx, nr; + struct tm tm; + timestamp_t when; + /* this should be enought for most languages. if not increase the value. */ + char buffer[256]; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &when, -1); + nr = gtk_tree_model_iter_n_children(model, iter); + + utc_mkdate(when, &tm); + if (idx < 0) { + snprintf(buffer, sizeof(buffer), + /*++GETTEXT 60 char buffer weekday, monthname, day of month, year, nr dives */ + ngettext("Trip %1$s, %2$s %3$d, %4$d (%5$d dive)", + "Trip %1$s, %2$s %3$d, %4$d (%5$d dives)", nr), + weekday(tm.tm_wday), + monthname(tm.tm_mon), + tm.tm_mday, tm.tm_year + 1900, + nr); + } else { + snprintf(buffer, sizeof(buffer), + /*++GETTEXT 60 char buffer weekday, monthname, day of month, year, hour:min */ + _("%1$s, %2$s %3$d, %4$d %5$02d:%6$02d"), + weekday(tm.tm_wday), + monthname(tm.tm_mon), + tm.tm_mday, tm.tm_year + 1900, + tm.tm_hour, tm.tm_min); + } + g_object_set(renderer, "text", buffer, NULL); +} + +static void depth_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int depth, integer, frac, len, idx; + char buffer[40]; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1); + + if (idx < 0) { + *buffer = '\0'; + } else { + switch (prefs.units.length) { + case METERS: + /* To tenths of meters */ + depth = (depth + 49) / 100; + integer = depth / 10; + frac = depth % 10; + if (integer < 20) + break; + if (frac >= 5) + integer++; + frac = -1; + break; + case FEET: + integer = mm_to_feet(depth) + 0.5; + frac = -1; + break; + default: + return; + } + len = snprintf(buffer, sizeof(buffer), "%d", integer); + if (frac >= 0) + len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac); + } + g_object_set(renderer, "text", buffer, NULL); +} + +static void duration_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + unsigned int sec; + int idx; + char buffer[40]; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1); + if (idx < 0) + *buffer = '\0'; + else + snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60); + + g_object_set(renderer, "text", buffer, NULL); +} + +static void temperature_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int value, idx; + char buffer[80]; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1); + + *buffer = 0; + if (idx >= 0 && value) { + double deg; + switch (prefs.units.temperature) { + case CELSIUS: + deg = mkelvin_to_C(value); + break; + case FAHRENHEIT: + deg = mkelvin_to_F(value); + break; + default: + return; + } + snprintf(buffer, sizeof(buffer), "%.1f", deg); + } + + g_object_set(renderer, "text", buffer, NULL); +} + +static void gpsicon_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int idx; + GdkPixbuf *icon; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_LOC_ICON, &icon, -1); + g_object_set(renderer, "pixbuf", icon, NULL); +} + +static void nr_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int idx, nr; + char buffer[40]; + struct dive *dive; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1); + if (idx < 0) { + *buffer = '\0'; + } else { + /* make dives that are not in trips stand out */ + dive = get_dive(idx); + if (!DIVE_IN_TRIP(dive)) + snprintf(buffer, sizeof(buffer), "%d", nr); + else + snprintf(buffer, sizeof(buffer), "%d", nr); + } + g_object_set(renderer, "markup", buffer, NULL); +} + +static void weight_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int indx, decimals; + double value; + char buffer[80]; + struct dive *dive; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &indx, -1); + dive = get_dive(indx); + value = get_weight_units(total_weight(dive), &decimals, NULL); + if (value == 0.0) + *buffer = '\0'; + else + snprintf(buffer, sizeof(buffer), "%.*f", decimals, value); + + g_object_set(renderer, "text", buffer, NULL); +} + +static gint nitrox_sort_func(GtkTreeModel *model, + GtkTreeIter *iter_a, + GtkTreeIter *iter_b, + gpointer user_data) +{ + int index_a, index_b; + struct dive *a, *b; + int a_o2, b_o2; + int a_he, b_he; + int a_o2low, b_o2low; + + gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1); + gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1); + a = get_dive(index_a); + b = get_dive(index_b); + get_dive_gas(a, &a_o2, &a_he, &a_o2low); + get_dive_gas(b, &b_o2, &b_he, &b_o2low); + + /* Sort by Helium first, O2 second */ + if (a_he == b_he) { + if (a_o2 == b_o2) + return a_o2low - b_o2low; + return a_o2 - b_o2; + } + return a_he - b_he; +} + +#define UTF8_ELLIPSIS "\xE2\x80\xA6" + +static void nitrox_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int idx, o2, he, o2low; + char buffer[80]; + struct dive *dive; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + if (idx < 0) { + *buffer = '\0'; + goto exit; + } + dive = get_dive(idx); + get_dive_gas(dive, &o2, &he, &o2low); + o2 = (o2 + 5) / 10; + he = (he + 5) / 10; + o2low = (o2low + 5) / 10; + + if (he) + snprintf(buffer, sizeof(buffer), "%d/%d", o2, he); + else if (o2) + if (o2 == o2low) + snprintf(buffer, sizeof(buffer), "%d", o2); + else + snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2); + else + strcpy(buffer, _("air")); +exit: + g_object_set(renderer, "text", buffer, NULL); +} + +/* Render the SAC data (integer value of "ml / min") */ +static void sac_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int value, idx; + const char *fmt; + char buffer[16]; + double sac; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1); + + if (idx < 0 || !value) { + *buffer = '\0'; + goto exit; + } + + sac = value / 1000.0; + switch (prefs.units.volume) { + case LITER: + fmt = "%4.1f"; + break; + case CUFT: + fmt = "%4.2f"; + sac = ml_to_cuft(sac * 1000); + break; + } + snprintf(buffer, sizeof(buffer), fmt, sac); +exit: + g_object_set(renderer, "text", buffer, NULL); +} + +/* Render the OTU data (integer value of "OTU") */ +static void otu_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int value, idx; + char buffer[16]; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1); + + if (idx < 0 || !value) + *buffer = '\0'; + else + snprintf(buffer, sizeof(buffer), "%d", value); + + g_object_set(renderer, "text", buffer, NULL); +} + +/* Render the CNS data (in full %) */ +static void cns_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int value, idx; + char buffer[16]; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_MAXCNS, &value, -1); + + if (idx < 0 || !value) + *buffer = '\0'; + else + snprintf(buffer, sizeof(buffer), "%d%%", value); + + g_object_set(renderer, "text", buffer, NULL); +} + +GdkPixbuf *get_gps_icon(void) +{ + return gdk_pixbuf_from_pixdata(&satellite_pixbuf, TRUE, NULL); +} + +static GdkPixbuf *get_gps_icon_for_dive(struct dive *dive) +{ + if (dive_has_gps_location(dive)) + return get_gps_icon(); + else + return NULL; +} + +/* + * Set up anything that could have changed due to editing + * of dive information; we need to do this for both models, + * so we simply call set_one_dive again with the non-current model + */ +/* forward declaration for recursion */ +static gboolean set_one_dive(GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data); + +static void fill_one_dive(struct dive *dive, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + char *location, *cylinder, *suit; + GtkTreeModel *othermodel; + GdkPixbuf *icon; + + get_cylinder(dive, &cylinder); + get_location(dive, &location); + get_suit(dive, &suit); + icon = get_gps_icon_for_dive(dive); + gtk_tree_store_set(GTK_TREE_STORE(model), iter, + DIVE_NR, dive->number, + DIVE_LOCATION, location, + DIVE_LOC_ICON, icon, + DIVE_CYLINDER, cylinder, + DIVE_RATING, dive->rating, + DIVE_SAC, dive->sac, + DIVE_OTU, dive->otu, + DIVE_MAXCNS, dive->maxcns, + DIVE_TOTALWEIGHT, total_weight(dive), + DIVE_SUIT, suit, + -1); + + if (icon) + g_object_unref(icon); + free(location); + free(cylinder); + free(suit); + + if (model == TREEMODEL(dive_list)) + othermodel = LISTMODEL(dive_list); + else + othermodel = TREEMODEL(dive_list); + if (othermodel != MODEL(dive_list)) + /* recursive call */ + gtk_tree_model_foreach(othermodel, set_one_dive, dive); +} + +static gboolean set_one_dive(GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + int idx; + struct dive *dive; + + /* Get the dive number */ + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + if (idx < 0) + return FALSE; + dive = get_dive(idx); + if (!dive) + return TRUE; + if (data && dive != data) + return FALSE; + + fill_one_dive(dive, model, iter); + return dive == data; +} + +void flush_divelist(struct dive *dive) +{ + GtkTreeModel *model = MODEL(dive_list); + + gtk_tree_model_foreach(model, set_one_dive, dive); +} + +void set_divelist_font(const char *font) +{ + PangoFontDescription *font_desc = pango_font_description_from_string(font); + gtk_widget_modify_font(dive_list.tree_view, font_desc); + pango_font_description_free(font_desc); +} + +void update_dive_list_units(void) +{ + const char *unit; + GtkTreeModel *model = MODEL(dive_list); + + (void) get_depth_units(0, NULL, &unit); + gtk_tree_view_column_set_title(dive_list.depth, unit); + + (void) get_temp_units(0, &unit); + gtk_tree_view_column_set_title(dive_list.temperature, unit); + + (void) get_weight_units(0, NULL, &unit); + gtk_tree_view_column_set_title(dive_list.totalweight, unit); + + gtk_tree_model_foreach(model, set_one_dive, NULL); +} + +void update_dive_list_col_visibility(void) +{ + gtk_tree_view_column_set_visible(dive_list.cylinder, prefs.visible_cols.cylinder); + gtk_tree_view_column_set_visible(dive_list.temperature, prefs.visible_cols.temperature); + gtk_tree_view_column_set_visible(dive_list.totalweight, prefs.visible_cols.totalweight); + gtk_tree_view_column_set_visible(dive_list.suit, prefs.visible_cols.suit); + gtk_tree_view_column_set_visible(dive_list.nitrox, prefs.visible_cols.nitrox); + gtk_tree_view_column_set_visible(dive_list.sac, prefs.visible_cols.sac); + gtk_tree_view_column_set_visible(dive_list.otu, prefs.visible_cols.otu); + gtk_tree_view_column_set_visible(dive_list.maxcns, prefs.visible_cols.maxcns); + return; +} + +static void clear_trip_indexes(void) +{ + dive_trip_t *trip; + + for (trip = dive_trip_list; trip != NULL; trip = trip->next) + trip->index = 0; +} + +/* Select the iter asked for, and set the keyboard focus on it */ +static void go_to_iter(GtkTreeSelection *selection, GtkTreeIter *iter); +static void fill_dive_list(void) +{ + int i, trip_index = 0; + GtkTreeIter iter, parent_iter, lookup, *parent_ptr = NULL; + GtkTreeStore *liststore, *treestore; + GdkPixbuf *icon; + + /* Do we need to create any dive groups automatically? */ + if (autogroup) + autogroup_dives(); + + treestore = TREESTORE(dive_list); + liststore = LISTSTORE(dive_list); + + clear_trip_indexes(); + + i = dive_table.nr; + while (--i >= 0) { + struct dive *dive = get_dive(i); + dive_trip_t *trip = dive->divetrip; + + if (!trip) { + parent_ptr = NULL; + } else if (!trip->index) { + trip->index = ++trip_index; + + /* Create new trip entry */ + gtk_tree_store_append(treestore, &parent_iter, NULL); + parent_ptr = &parent_iter; + + /* a duration of 0 (and negative index) identifies a group */ + gtk_tree_store_set(treestore, parent_ptr, + DIVE_INDEX, -trip_index, + DIVE_DATE, trip->when, + DIVE_LOCATION, trip->location, + DIVE_DURATION, 0, + -1); + } else { + int idx, ok; + GtkTreeModel *model = TREEMODEL(dive_list); + + parent_ptr = NULL; + ok = gtk_tree_model_get_iter_first(model, &lookup); + while (ok) { + gtk_tree_model_get(model, &lookup, DIVE_INDEX, &idx, -1); + if (idx == -trip->index) { + parent_ptr = &lookup; + break; + } + ok = gtk_tree_model_iter_next(model, &lookup); + } + } + + /* store dive */ + update_cylinder_related_info(dive); + gtk_tree_store_append(treestore, &iter, parent_ptr); + icon = get_gps_icon_for_dive(dive); + gtk_tree_store_set(treestore, &iter, + DIVE_INDEX, i, + DIVE_NR, dive->number, + DIVE_DATE, dive->when, + DIVE_DEPTH, dive->maxdepth, + DIVE_DURATION, dive->duration.seconds, + DIVE_LOCATION, dive->location, + DIVE_LOC_ICON, icon, + DIVE_RATING, dive->rating, + DIVE_TEMPERATURE, dive->watertemp.mkelvin, + DIVE_SAC, 0, + -1); + if (icon) + g_object_unref(icon); + gtk_tree_store_append(liststore, &iter, NULL); + gtk_tree_store_set(liststore, &iter, + DIVE_INDEX, i, + DIVE_NR, dive->number, + DIVE_DATE, dive->when, + DIVE_DEPTH, dive->maxdepth, + DIVE_DURATION, dive->duration.seconds, + DIVE_LOCATION, dive->location, + DIVE_LOC_ICON, icon, + DIVE_RATING, dive->rating, + DIVE_TEMPERATURE, dive->watertemp.mkelvin, + DIVE_TOTALWEIGHT, 0, + DIVE_SUIT, dive->suit, + DIVE_SAC, 0, + -1); + } + + update_dive_list_units(); + if (amount_selected == 0 && gtk_tree_model_get_iter_first(MODEL(dive_list), &iter)) { + GtkTreeSelection *selection; + + /* select the last dive (and make sure it's an actual dive that is selected) */ + gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &selected_dive, -1); + first_leaf(MODEL(dive_list), &iter, &selected_dive); + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + go_to_iter(selection, &iter); + } +} + +static void restore_tree_state(void); + +void dive_list_update_dives(void) +{ + dive_table.preexisting = dive_table.nr; + gtk_tree_store_clear(TREESTORE(dive_list)); + gtk_tree_store_clear(LISTSTORE(dive_list)); + fill_dive_list(); + restore_tree_state(); + repaint_dive(); +} + +static gint dive_nr_sort(GtkTreeModel *model, + GtkTreeIter *iter_a, + GtkTreeIter *iter_b, + gpointer user_data) +{ + int idx_a, idx_b; + timestamp_t when_a, when_b; + struct dive *a, *b; + dive_trip_t *tripa = NULL, *tripb = NULL; + + gtk_tree_model_get(model, iter_a, DIVE_INDEX, &idx_a, DIVE_DATE, &when_a, -1); + gtk_tree_model_get(model, iter_b, DIVE_INDEX, &idx_b, DIVE_DATE, &when_b, -1); + + if (idx_a < 0) { + a = NULL; + tripa = find_trip_by_idx(idx_a); + } else { + a = get_dive(idx_a); + if (a) + tripa = a->divetrip; + } + + if (idx_b < 0) { + b = NULL; + tripb = find_trip_by_idx(idx_b); + } else { + b = get_dive(idx_b); + if (b) + tripb = b->divetrip; + } + + /* + * Compare dive dates within the same trip (or when there + * are no trips involved at all). But if we have two + * different trips use the trip dates for comparison + */ + if (tripa != tripb) { + if (tripa) + when_a = tripa->when; + if (tripb) + when_b = tripb->when; + } + return when_a - when_b; +} + + +static struct divelist_column { + const char *header; + data_func_t data; + sort_func_t sort; + unsigned int flags; + int *visible; +} dl_column[] = { + [DIVE_NR] = { "#", nr_data_func, dive_nr_sort, ALIGN_RIGHT }, + [DIVE_DATE] = { N_("Date"), date_data_func, NULL, ALIGN_LEFT }, + [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT }, + [DIVE_DEPTH] = { N_("ft"), depth_data_func, NULL, ALIGN_RIGHT }, + [DIVE_DURATION] = { N_("min"), duration_data_func, NULL, ALIGN_RIGHT }, + [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &prefs.visible_cols.temperature }, + [DIVE_TOTALWEIGHT] = { N_("lbs"), weight_data_func, NULL, ALIGN_RIGHT, &prefs.visible_cols.totalweight }, + [DIVE_SUIT] = { N_("Suit"), NULL, NULL, ALIGN_LEFT, &prefs.visible_cols.suit }, + [DIVE_CYLINDER] = { N_("Cyl"), NULL, NULL, 0, &prefs.visible_cols.cylinder }, + [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &prefs.visible_cols.nitrox }, + [DIVE_SAC] = { N_("SAC"), sac_data_func, NULL, 0, &prefs.visible_cols.sac }, + [DIVE_OTU] = { N_("OTU"), otu_data_func, NULL, 0, &prefs.visible_cols.otu }, + [DIVE_MAXCNS] = { N_("maxCNS"), cns_data_func, NULL, 0, &prefs.visible_cols.maxcns }, + [DIVE_LOCATION] = { N_("Location"), NULL, NULL, ALIGN_LEFT }, +}; + + +static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col) +{ + int index = col - &dl_column[0]; + const char *title = _(col->header); + data_func_t data_func = col->data; + sort_func_t sort_func = col->sort; + unsigned int flags = col->flags; + int *visible = col->visible; + GtkWidget *tree_view = dl->tree_view; + GtkTreeStore *treemodel = dl->treemodel; + GtkTreeStore *listmodel = dl->listmodel; + GtkTreeViewColumn *ret; + + if (visible && !*visible) + flags |= INVISIBLE; + ret = tree_view_column(tree_view, index, title, data_func, flags); + if (sort_func) { + /* the sort functions are needed in the corresponding models */ + if (index == DIVE_NR) + gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treemodel), index, sort_func, NULL, NULL); + else + gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(listmodel), index, sort_func, NULL, NULL); + } + return ret; +} + +/* + * This is some crazy crap. The only way to get default focus seems + * to be to grab focus as the widget is being shown the first time. + */ +static void realize_cb(GtkWidget *tree_view, gpointer userdata) +{ + gtk_widget_grab_focus(tree_view); +} + +/* + * Double-clicking on a group entry will expand a collapsed group + * and vice versa. + */ +static void collapse_expand(GtkTreeView *tree_view, GtkTreePath *path) +{ + if (!gtk_tree_view_row_expanded(tree_view, path)) + gtk_tree_view_expand_row(tree_view, path, FALSE); + else + gtk_tree_view_collapse_row(tree_view, path); + +} + +/* Double-click on a dive list */ +static void row_activated_cb(GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer userdata) +{ + int index; + GtkTreeIter iter; + + if (!gtk_tree_model_get_iter(MODEL(dive_list), &iter, path)) + return; + + gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &index, -1); + /* a negative index is special for the "group by date" entries */ + if (index < 0) { + collapse_expand(tree_view, path); + return; + } + edit_dive_info(get_dive(index), FALSE); +} + +void add_dive_cb(GtkWidget *menuitem, gpointer data) +{ + struct dive *dive; + + dive = alloc_dive(); + if (add_new_dive(dive)) { + record_dive(dive); + report_dives(TRUE, FALSE); + return; + } + free(dive); +} + +static void edit_trip_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + int idx; + GtkTreeIter iter; + dive_trip_t *dive_trip; + + gtk_tree_model_get_iter(MODEL(dive_list), &iter, path); + gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); + dive_trip = find_trip_by_idx(idx); + if (edit_trip(dive_trip)) + gtk_tree_store_set(STORE(dive_list), &iter, DIVE_LOCATION, dive_trip->location, -1); +} + +static void edit_selected_dives_cb(GtkWidget *menuitem, gpointer data) +{ + edit_multi_dive_info(NULL); +} + +static void edit_dive_from_path_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + struct dive *dive = dive_from_path(path); + + edit_multi_dive_info(dive); +} + +static void edit_dive_when_cb(GtkWidget *menuitem, struct dive *dive) +{ + GtkWidget *dialog, *cal, *h, *m, *timehbox; + timestamp_t when; + + guint yval, mval, dval; + int success; + struct tm tm; + + if (!dive) + return; + + when = dive->when; + utc_mkdate(when, &tm); + dialog = create_date_time_widget(&tm, &cal, &h, &m, &timehbox); + + gtk_widget_show_all(dialog); + success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; + if (!success) { + gtk_widget_destroy(dialog); + return; + } + memset(&tm, 0, sizeof(tm)); + gtk_calendar_get_date(GTK_CALENDAR(cal), &yval, &mval, &dval); + tm.tm_year = yval; + tm.tm_mon = mval; + tm.tm_mday = dval; + tm.tm_hour = gtk_spin_button_get_value(GTK_SPIN_BUTTON(h)); + tm.tm_min = gtk_spin_button_get_value(GTK_SPIN_BUTTON(m)); + + gtk_widget_destroy(dialog); + when = utc_mktime(&tm); + if (dive->when != when) { + /* if this is the only dive in the trip, just change the trip time */ + if (dive->divetrip && dive->divetrip->nrdives == 1) + dive->divetrip->when = when; + /* if this is suddenly before the start of the trip, remove it from the trip */ + else if (dive->divetrip && dive->divetrip->when > when) + remove_dive_from_trip(dive); + else if (find_matching_trip(when) != dive->divetrip) + remove_dive_from_trip(dive); + dive->when = when; + mark_divelist_changed(TRUE); + report_dives(FALSE, FALSE); + dive_list_update_dives(); + } +} + +#if HAVE_OSM_GPS_MAP +static void show_gps_location_cb(GtkWidget *menuitem, struct dive *dive) +{ + show_gps_location(dive, NULL); +} +#endif + +gboolean icon_click_cb(GtkWidget *w, GdkEventButton *event, gpointer data) +{ +#if HAVE_OSM_GPS_MAP + GtkTreePath *path = NULL; + GtkTreeIter iter; + GtkTreeViewColumn *col; + int idx; + struct dive *dive; + + /* left click ? */ + if (event->button == 1 && + gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(dive_list.tree_view), event->x, event->y, &path, &col, NULL, NULL)) { + /* is it the icon column ? (we passed the correct column in when registering the callback) */ + if (col == data) { + gtk_tree_model_get_iter(MODEL(dive_list), &iter, path); + gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); + dive = get_dive(idx); + if (dive && dive_has_gps_location(dive)) + show_gps_location(dive, NULL); + } + if (path) + gtk_tree_path_free(path); + } +#endif + /* keep processing the click */ + return FALSE; +} + +static void save_as_cb(GtkWidget *menuitem, struct dive *dive) +{ + GtkWidget *dialog; + char *filename = NULL; + + dialog = gtk_file_chooser_dialog_new(_("Save File As"), + GTK_WINDOW(main_window), + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + } + gtk_widget_destroy(dialog); + + if (filename){ + save_dives_logic(filename, TRUE); + g_free(filename); + } +} + +static void expand_all_cb(GtkWidget *menuitem, GtkTreeView *tree_view) +{ + gtk_tree_view_expand_all(tree_view); +} + +static void collapse_all_cb(GtkWidget *menuitem, GtkTreeView *tree_view) +{ + gtk_tree_view_collapse_all(tree_view); +} + +/* Move a top-level dive into the trip above it */ +static void merge_dive_into_trip_above_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + int idx; + struct dive *dive; + dive_trip_t *trip; + + idx = get_path_index(path); + dive = get_dive(idx); + + /* Needs to be a dive, and at the top level */ + if (!dive || dive->divetrip) + return; + + /* Find the "trip above". */ + for (;;) { + if (!gtk_tree_path_prev(path)) + return; + idx = get_path_index(path); + trip = find_trip_by_idx(idx); + if (trip) + break; + } + + add_dive_to_trip(dive, trip); + if (dive->selected) { + for_each_dive(idx, dive) { + if (!dive->selected) + continue; + add_dive_to_trip(dive, trip); + } + } + + trip->expanded = 1; + dive_list_update_dives(); + mark_divelist_changed(TRUE); +} + +static void insert_trip_before_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + int idx; + struct dive *dive; + dive_trip_t *trip; + + idx = get_path_index(path); + dive = get_dive(idx); + if (!dive) + return; + trip = create_and_hookup_trip_from_dive(dive); + if (dive->selected) { + for_each_dive(idx, dive) { + if (!dive->selected) + continue; + add_dive_to_trip(dive, trip); + } + } + trip->expanded = 1; + dive_list_update_dives(); + mark_divelist_changed(TRUE); +} + +static void remove_from_trip_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + struct dive *dive; + int idx; + + idx = get_path_index(path); + if (idx < 0) + return; + dive = get_dive(idx); + + if (dive->selected) { + /* remove all the selected dives */ + for_each_dive(idx, dive) { + if (!dive->selected) + continue; + remove_dive_from_trip(dive); + } + } else { + /* just remove the dive the mouse pointer is on */ + remove_dive_from_trip(dive); + } + dive_list_update_dives(); + mark_divelist_changed(TRUE); +} + +static void remove_trip(GtkTreePath *trippath) +{ + int idx, i; + dive_trip_t *trip; + struct dive *dive; + + idx = get_path_index(trippath); + trip = find_trip_by_idx(idx); + if (!trip) + return; + + for_each_dive(i, dive) { + if (dive->divetrip != trip) + continue; + remove_dive_from_trip(dive); + } + + dive_list_update_dives(); + +#ifdef DEBUG_TRIP + dump_trip_list(); +#endif +} + +static void remove_trip_cb(GtkWidget *menuitem, GtkTreePath *trippath) +{ + int success; + GtkWidget *dialog; + + dialog = gtk_dialog_new_with_buttons(_("Remove Trip"), + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + + gtk_widget_show_all(dialog); + success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; + gtk_widget_destroy(dialog); + if (!success) + return; + + remove_trip(trippath); + mark_divelist_changed(TRUE); +} + +static void merge_trips_cb(GtkWidget *menuitem, GtkTreePath *trippath) +{ + GtkTreePath *prevpath; + GtkTreeIter thistripiter, prevtripiter; + GtkTreeModel *tm = MODEL(dive_list); + dive_trip_t *thistrip, *prevtrip; + timestamp_t when; + + /* this only gets called when we are on a trip and there is another trip right before */ + prevpath = gtk_tree_path_copy(trippath); + gtk_tree_path_prev(prevpath); + gtk_tree_model_get_iter(tm, &thistripiter, trippath); + gtk_tree_model_get(tm, &thistripiter, DIVE_DATE, &when, -1); + thistrip = find_matching_trip(when); + gtk_tree_model_get_iter(tm, &prevtripiter, prevpath); + gtk_tree_model_get(tm, &prevtripiter, DIVE_DATE, &when, -1); + prevtrip = find_matching_trip(when); + /* move dives from trip */ + assert(thistrip != prevtrip); + while (thistrip->dives) + add_dive_to_trip(thistrip->dives, prevtrip); + dive_list_update_dives(); + mark_divelist_changed(TRUE); +} + +static gboolean restore_node_state(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + int idx; + struct dive *dive; + dive_trip_t *trip; + GtkTreeView *tree_view = GTK_TREE_VIEW(dive_list.tree_view); + GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view); + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + if (idx < 0) { + trip = find_trip_by_idx(idx); + if (trip && trip->expanded) + gtk_tree_view_expand_row(tree_view, path, FALSE); + if (trip && trip->selected) + gtk_tree_selection_select_iter(selection, iter); + } else { + dive = get_dive(idx); + if (dive && dive->selected) + gtk_tree_selection_select_iter(selection, iter); + } + /* continue foreach */ + return FALSE; +} + +/* restore expanded and selected state */ +static void restore_tree_state(void) +{ + gtk_tree_model_foreach(MODEL(dive_list), restore_node_state, NULL); +} + +/* called when multiple dives are selected and one of these is right-clicked for delete */ +static void delete_selected_dives_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + int i; + struct dive *dive; + int success; + GtkWidget *dialog; + char *dialog_title; + + if (!amount_selected) + return; + if (amount_selected == 1) + dialog_title = _("Delete dive"); + else + dialog_title = _("Delete dives"); + + dialog = gtk_dialog_new_with_buttons(dialog_title, + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + + gtk_widget_show_all(dialog); + success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; + gtk_widget_destroy(dialog); + if (!success) + return; + + /* walk the dive list in chronological order */ + for (i = 0; i < dive_table.nr; i++) { + dive = get_dive(i); + if (!dive) + continue; + if (!dive->selected) + continue; + /* now remove the dive from the table and free it. also move the iterator back, + * so that we don't skip a dive */ + delete_single_dive(i); + i--; + } + dive_list_update_dives(); + + /* if no dives are selected at this point clear the display widgets */ + if (!amount_selected) { + selected_dive = 0; + process_selected_dives(); + clear_stats_widgets(); + clear_equipment_widgets(); + show_dive_info(NULL); + } + mark_divelist_changed(TRUE); +} + +/* this gets called with path pointing to a dive, either in the top level + * or as part of a trip */ +static void delete_dive_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + int idx; + GtkTreeIter iter; + int success; + GtkWidget *dialog; + + dialog = gtk_dialog_new_with_buttons(_("Delete dive"), + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + + gtk_widget_show_all(dialog); + success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; + gtk_widget_destroy(dialog); + if (!success) + return; + + if (!gtk_tree_model_get_iter(MODEL(dive_list), &iter, path)) + return; + gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); + delete_single_dive(idx); + dive_list_update_dives(); + mark_divelist_changed(TRUE); +} + +#if defined(LIBZIP) && defined(XSLT) +static void export_selected_dives_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + int i; + struct dive *dive; + FILE *f; + char filename[PATH_MAX], *tempfile; + size_t streamsize; + char *membuf; + xmlDoc *doc; + xsltStylesheetPtr xslt = NULL; + xmlDoc *transformed; + struct zip_source *s[dive_table.nr]; + struct zip *zip; + const gchar *tmpdir = g_get_tmp_dir(); + + /* + * Creating a temporary .DLD file to be eventually uploaded to + * divelogs.de. I wonder if this could be done in-memory. + */ + tempfile = g_build_filename(tmpdir, "export.DLD-XXXXXX", NULL); + int fd = g_mkstemp(tempfile); + if (fd != -1) + close(fd); + zip = zip_open(tempfile, ZIP_CREATE, NULL); + + if (!zip) + return; + + if (!amount_selected) + return; + + /* walk the dive list in chronological order */ + for (i = 0; i < dive_table.nr; i++) { + + dive = get_dive(i); + if (!dive) + continue; + if (!dive->selected) + continue; + + f = tmpfile(); + if (!f) + return; + save_dive(f, dive); + fseek(f, 0, SEEK_END); + streamsize = ftell(f); + rewind(f); + membuf = malloc(streamsize + 1); + if (!membuf || !fread(membuf, streamsize, 1, f)) + return; + membuf[streamsize] = 0; + fclose(f); + + /* + * Parse the memory buffer into XML document and + * transform it to divelogs.de format, finally dumping + * the XML into a character buffer. + */ + doc = xmlReadMemory(membuf, strlen(membuf), "divelog", NULL, 0); + if (!doc) + continue; + + free((void *)membuf); + xslt = get_stylesheet("divelogs-export.xslt"); + if (!xslt) + return; + transformed = xsltApplyStylesheet(xslt, doc, NULL); + xsltFreeStylesheet(xslt); + xmlDocDumpMemory(transformed, (xmlChar **) &membuf, (int *)&streamsize); + xmlFreeDoc(doc); + xmlFreeDoc(transformed); + + /* + * Save the XML document into a zip file. + */ + snprintf(filename, PATH_MAX, "%d.xml", i + 1); + s[i] = zip_source_buffer(zip, membuf, streamsize, 1); + if (s[i]) { + int64_t ret = zip_add(zip, filename, s[i]); + if (ret == -1) + fprintf(stderr, "failed to include dive %d\n", i); + } + } + zip_close(zip); + if (divelogde_upload(tempfile)) + g_unlink(tempfile); + else + fprintf(stderr,"upload of %s failed\n", tempfile); + g_free(tempfile); +} +#endif + +#if defined(XSLT) +static void export_dives_uddf(const gboolean selected) +{ + FILE *f; + char *filename = NULL; + size_t streamsize; + char *membuf; + xmlDoc *doc; + xsltStylesheetPtr xslt = NULL; + xmlDoc *transformed; + GtkWidget *dialog; + + dialog = gtk_file_chooser_dialog_new(_("Export As UDDF File"), + GTK_WINDOW(main_window), + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + } + gtk_widget_destroy(dialog); + + if (!filename) + return; + + /* Save XML to file and convert it into a memory buffer */ + save_dives_logic(filename, selected); + f = fopen(filename, "r"); + fseek(f, 0, SEEK_END); + streamsize = ftell(f); + rewind(f); + + membuf = malloc(streamsize + 1); + if (!membuf || !fread(membuf, streamsize, 1, f)) { + fprintf(stderr, "Failed to read memory buffer\n"); + return; + } + membuf[streamsize] = 0; + fclose(f); + g_unlink(filename); + + /* + * Parse the memory buffer into XML document and + * transform it to UDDF format, finally dumping + * the XML into a character buffer. + */ + doc = xmlReadMemory(membuf, strlen(membuf), "divelog", NULL, 0); + if (!doc) { + fprintf(stderr, "Failed to read XML memory\n"); + return; + } + free((void *)membuf); + + /* Convert to UDDF format */ + xslt = get_stylesheet("uddf-export.xslt"); + if (!xslt) { + fprintf(stderr, "Failed to open UDDF conversion stylesheet\n"); + return; + } + transformed = xsltApplyStylesheet(xslt, doc, NULL); + xsltFreeStylesheet(xslt); + xmlFreeDoc(doc); + + /* Write the transformed XML to file */ + f = g_fopen(filename, "w"); + xmlDocFormatDump(f, transformed, 1); + xmlFreeDoc(transformed); + + fclose(f); + g_free(filename); +} + +static void export_selected_dives_uddf_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + export_dives_uddf(TRUE); +} + +void export_all_dives_uddf_cb() +{ + export_dives_uddf(FALSE); +} +#endif + +static void merge_dives_cb(GtkWidget *menuitem, void *unused) +{ + int i; + struct dive *dive; + + for_each_dive(i, dive) { + if (dive->selected) { + merge_dive_index(i, dive); + return; + } + } +} + +/* Called if there are exactly two selected dives and the dive at idx is one of them */ +static void add_dive_merge_label(int idx, GtkMenuShell *menu) +{ + struct dive *a, *b; + GtkWidget *menuitem; + + /* The other selected dive must be next to it.. */ + a = get_dive(idx); + b = get_dive(idx+1); + if (!b || !b->selected) { + b = a; + a = get_dive(idx-1); + if (!a || !a->selected) + return; + } + + /* .. and they had better be in the same dive trip */ + if (a->divetrip != b->divetrip) + return; + + /* .. and if the surface interval is excessive, you must be kidding us */ + if (b->when > a->when + a->duration.seconds + 30*60) + return; + + /* If so, we can add a "merge dive" menu entry */ + menuitem = gtk_menu_item_new_with_label(_("Merge dives")); + g_signal_connect(menuitem, "activate", G_CALLBACK(merge_dives_cb), NULL); + gtk_menu_shell_append(menu, menuitem); +} + +static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button, GdkEventButton *event) +{ + GtkWidget *menu, *menuitem, *image; + char editplurallabel[] = N_("Edit dives"); + char editsinglelabel[] = N_("Edit dive"); + char *editlabel; + char deleteplurallabel[] = N_("Delete dives"); + char deletesinglelabel[] = N_("Delete dive"); + char *deletelabel; +#if defined(XSLT) + char exportuddflabel[] = N_("Export dive(s) to UDDF"); +#endif +#if defined(LIBZIP) && defined(XSLT) + char exportlabel[] = N_("Export dive(s)"); +#endif + GtkTreePath *path, *prevpath, *nextpath; + GtkTreeIter iter, previter, nextiter; + int idx, previdx, nextidx; + struct dive *dive; + + if (!event || !gtk_tree_view_get_path_at_pos(tree_view, event->x, event->y, &path, NULL, NULL, NULL)) + return; + gtk_tree_model_get_iter(MODEL(dive_list), &iter, path); + gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); + + menu = gtk_menu_new(); + menuitem = gtk_image_menu_item_new_with_label(_("Add dive")); + image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); + g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + if (idx < 0) { + /* mouse pointer is on a trip summary entry */ + menuitem = gtk_menu_item_new_with_label(_("Edit Trip Summary")); + g_signal_connect(menuitem, "activate", G_CALLBACK(edit_trip_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + prevpath = gtk_tree_path_copy(path); + if (gtk_tree_path_prev(prevpath) && + gtk_tree_model_get_iter(MODEL(dive_list), &previter, prevpath)) { + gtk_tree_model_get(MODEL(dive_list), &previter, DIVE_INDEX, &previdx, -1); + if (previdx < 0) { + menuitem = gtk_menu_item_new_with_label(_("Merge trip with trip above")); + g_signal_connect(menuitem, "activate", G_CALLBACK(merge_trips_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } + } + nextpath = gtk_tree_path_copy(path); + gtk_tree_path_next(nextpath); + if (gtk_tree_model_get_iter(MODEL(dive_list), &nextiter, nextpath)) { + gtk_tree_model_get(MODEL(dive_list), &nextiter, DIVE_INDEX, &nextidx, -1); + if (nextidx < 0) { + menuitem = gtk_menu_item_new_with_label(_("Merge trip with trip below")); + g_signal_connect(menuitem, "activate", G_CALLBACK(merge_trips_cb), nextpath); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } + } + menuitem = gtk_menu_item_new_with_label(_("Remove Trip")); + g_signal_connect(menuitem, "activate", G_CALLBACK(remove_trip_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } else { + dive = get_dive(idx); + /* if we right click on selected dive(s), edit or delete those */ + if (dive->selected) { + if (amount_selected == 1) { + deletelabel = _(deletesinglelabel); + editlabel = _(editsinglelabel); + menuitem = gtk_menu_item_new_with_label(_("Edit dive date/time")); + g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_when_cb), dive); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } else { + deletelabel = _(deleteplurallabel); + editlabel = _(editplurallabel); + } + menuitem = gtk_menu_item_new_with_label(_("Save as")); + g_signal_connect(menuitem, "activate", G_CALLBACK(save_as_cb), dive); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + menuitem = gtk_menu_item_new_with_label(deletelabel); + g_signal_connect(menuitem, "activate", G_CALLBACK(delete_selected_dives_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + +#if defined(LIBZIP) && defined(XSLT) + menuitem = gtk_menu_item_new_with_label(exportlabel); + g_signal_connect(menuitem, "activate", G_CALLBACK(export_selected_dives_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); +#endif + +#if defined(XSLT) + menuitem = gtk_menu_item_new_with_label(exportuddflabel); + g_signal_connect(menuitem, "activate", G_CALLBACK(export_selected_dives_uddf_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); +#endif + + menuitem = gtk_menu_item_new_with_label(editlabel); + g_signal_connect(menuitem, "activate", G_CALLBACK(edit_selected_dives_cb), NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + /* Two contiguous selected dives? */ + if (amount_selected == 2) + add_dive_merge_label(idx, GTK_MENU_SHELL(menu)); + } else { + menuitem = gtk_menu_item_new_with_label(_("Edit dive date/time")); + g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_when_cb), dive); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + deletelabel = _(deletesinglelabel); + menuitem = gtk_menu_item_new_with_label(deletelabel); + g_signal_connect(menuitem, "activate", G_CALLBACK(delete_dive_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + editlabel = _(editsinglelabel); + menuitem = gtk_menu_item_new_with_label(editlabel); + g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_from_path_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } +#if HAVE_OSM_GPS_MAP + /* Only offer to show on map if it has a location. */ + if (dive_has_gps_location(dive)) { + menuitem = gtk_menu_item_new_with_label(_("Show in map")); + g_signal_connect(menuitem, "activate", G_CALLBACK(show_gps_location_cb), dive); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } +#endif + /* only offer trip editing options when we are displaying the tree model */ + if (dive_list.model == dive_list.treemodel) { + int depth = gtk_tree_path_get_depth(path); + int *indices = gtk_tree_path_get_indices(path); + /* top level dive or child dive that is not the first child */ + if (depth == 1 || indices[1] > 0) { + menuitem = gtk_menu_item_new_with_label(_("Create new trip above")); + g_signal_connect(menuitem, "activate", G_CALLBACK(insert_trip_before_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } + prevpath = gtk_tree_path_copy(path); + /* top level dive with a trip right before it */ + if (depth == 1 && + gtk_tree_path_prev(prevpath) && + gtk_tree_model_get_iter(MODEL(dive_list), &previter, prevpath) && + gtk_tree_model_iter_n_children(model, &previter)) { + menuitem = gtk_menu_item_new_with_label(_("Add to trip above")); + g_signal_connect(menuitem, "activate", G_CALLBACK(merge_dive_into_trip_above_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } + if (DIVE_IN_TRIP(dive)) { + if (dive->selected && amount_selected > 1) + menuitem = gtk_menu_item_new_with_label(_("Remove selected dives from trip")); + else + menuitem = gtk_menu_item_new_with_label(_("Remove dive from trip")); + g_signal_connect(menuitem, "activate", G_CALLBACK(remove_from_trip_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } + } + } + menuitem = gtk_menu_item_new_with_label(_("Expand all")); + g_signal_connect(menuitem, "activate", G_CALLBACK(expand_all_cb), tree_view); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + menuitem = gtk_menu_item_new_with_label(_("Collapse all")); + g_signal_connect(menuitem, "activate", G_CALLBACK(collapse_all_cb), tree_view); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + gtk_widget_show_all(menu); + + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, + button, gtk_get_current_event_time()); +} + +static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata) +{ + popup_divelist_menu(tree_view, MODEL(dive_list), 0, NULL); +} + +static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata) +{ + /* Right-click? Bring up the menu */ + if (event->type == GDK_BUTTON_PRESS && event->button == 3) { + popup_divelist_menu(GTK_TREE_VIEW(treeview), MODEL(dive_list), 3, event); + return TRUE; + } + return FALSE; +} + +/* make sure 'path' is shown in the divelist widget; since set_cursor changes the + * selection to be only 'path' we need to let our selection handling callbacks know + * that we didn't really mean this */ +static void scroll_to_path(GtkTreePath *path) +{ + GtkTreeSelection *selection; + + gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(dive_list.tree_view), path, NULL, FALSE, 0, 0); + in_set_cursor = TRUE; + gtk_tree_view_set_cursor(GTK_TREE_VIEW(dive_list.tree_view), path, NULL, FALSE); + in_set_cursor = FALSE; + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + gtk_tree_model_foreach(MODEL(dive_list), set_selected, selection); + +} + +/* we need to have a temporary copy of the selected dives while + switching model as the selection_cb function keeps getting called + when gtk_tree_selection_select_path is called. We also need to + keep copies of the sort order so we can restore that as well after + switching models. */ +static gboolean second_call = FALSE; +static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, }; +static int lastcol = DIVE_NR; + +/* Check if this dive was selected previously and select it again in the new model; + * This is used after we switch models to maintain consistent selections. + * We always return FALSE to iterate through all dives */ +static gboolean set_selected(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) +{ + GtkTreeSelection *selection = GTK_TREE_SELECTION(data); + int idx, selected; + struct dive *dive; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + if (idx < 0) { + /* this is a trip - restore its state */ + dive_trip_t *trip = find_trip_by_idx(idx); + if (trip && trip->expanded) + gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path); + if (trip && trip->selected) + gtk_tree_selection_select_path(selection, path); + } else { + dive = get_dive(idx); + selected = dive && dive->selected; + if (selected) { + gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path); + gtk_tree_selection_select_path(selection, path); + } + } + return FALSE; +} + +static gboolean scroll_to_this(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) +{ + int idx; + struct dive *dive; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + dive = get_dive(idx); + if (dive == current_dive) { + scroll_to_path(path); + return TRUE; + } + return FALSE; +} + +static void scroll_to_current(GtkTreeModel *model) +{ + if (current_dive) + gtk_tree_model_foreach(model, scroll_to_this, current_dive); +} + +static void update_column_and_order(int colid) +{ + /* Careful: the index into treecolumns is off by one as we don't have a + tree_view column for DIVE_INDEX */ + GtkTreeViewColumn **treecolumns = &dive_list.nr; + + /* this will trigger a second call into sort_column_change_cb, + so make sure we don't start an infinite recursion... */ + second_call = TRUE; + gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, sortorder[colid]); + gtk_tree_view_column_set_sort_order(treecolumns[colid - 1], sortorder[colid]); + second_call = FALSE; + scroll_to_current(GTK_TREE_MODEL(dive_list.model)); +} + +/* If the sort column is nr (default), show the tree model. + For every other sort column only show the list model. + If the model changed, inform the new model of the chosen sort column and make + sure the same dives are still selected. + + The challenge with this function is that once we change the model + we also need to change the sort column again (as it was changed in + the other model) and that causes this function to be called + recursively - so we need to catch that. +*/ +static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data) +{ + int colid; + GtkSortType order; + GtkTreeStore *currentmodel = dive_list.model; + + gtk_widget_grab_focus(dive_list.tree_view); + if (second_call) + return; + + gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order); + if (colid == lastcol) { + /* we just changed sort order */ + sortorder[colid] = order; + return; + } else { + lastcol = colid; + } + if (colid == DIVE_NR) + dive_list.model = dive_list.treemodel; + else + dive_list.model = dive_list.listmodel; + if (dive_list.model != currentmodel) { + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + + gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), MODEL(dive_list)); + update_column_and_order(colid); + gtk_tree_model_foreach(MODEL(dive_list), set_selected, selection); + } else { + if (order != sortorder[colid]) { + update_column_and_order(colid); + } + } +} + +static gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model, + GtkTreePath *path, gboolean was_selected, gpointer userdata) +{ + int idx; + GtkTreeIter iter; + + if (!was_selected || in_set_cursor) + return TRUE; + gtk_tree_model_get_iter(model, &iter, path); + gtk_tree_model_get(model, &iter, DIVE_INDEX, &idx, -1); + if (idx < 0) { + int i; + struct dive *dive; + dive_trip_t *trip = find_trip_by_idx(idx); + if (!trip) + return TRUE; + + trip->selected = 0; + /* If this is expanded, let the gtk selection happen for each dive under it */ + if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), path)) + return TRUE; + /* Otherwise, consider each dive under it deselected */ + for_each_dive(i, dive) { + if (dive->divetrip == trip) + deselect_dive(i); + } + } else { + deselect_dive(idx); + } + return TRUE; +} + +/* This gets called for each selected entry after a selection has changed */ +static void entry_selected(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + int idx; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + if (idx < 0) { + int i; + struct dive *dive; + dive_trip_t *trip = find_trip_by_idx(idx); + + if (!trip) + return; + trip->selected = 1; + + /* If this is expanded, let the gtk selection happen for each dive under it */ + if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), path)) { + trip->fixup = 1; + return; + } + + /* Otherwise, consider each dive under it selected */ + for_each_dive(i, dive) { + if (dive->divetrip == trip) + select_dive(i); + } + trip->fixup = 0; + } else { + select_dive(idx); + } +} + +static void update_gtk_selection(GtkTreeSelection *selection, GtkTreeModel *model) +{ + GtkTreeIter iter; + + if (!gtk_tree_model_get_iter_first(model, &iter)) + return; + do { + GtkTreeIter child; + + if (!gtk_tree_model_iter_children(model, &child, &iter)) + continue; + + do { + int idx; + struct dive *dive; + dive_trip_t *trip; + + gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1); + dive = get_dive(idx); + if (!dive || !dive->selected) + break; + trip = dive->divetrip; + if (!trip) + break; + gtk_tree_selection_select_iter(selection, &child); + } while (gtk_tree_model_iter_next(model, &child)); + } while (gtk_tree_model_iter_next(model, &iter)); +} + +/* this is called when gtk thinks that the selection has changed */ +static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model) +{ + int i, fixup; + struct dive *dive; + + gtk_tree_selection_selected_foreach(selection, entry_selected, model); + + /* + * Go through all the dives, if there is a trip that is selected but no + * dives under it are selected, force-select all the dives + */ + + /* First, clear "fixup" for any trip that has selected dives */ + for_each_dive(i, dive) { + dive_trip_t *trip = dive->divetrip; + if (!trip || !trip->fixup) + continue; + if (dive->selected || !trip->selected) + trip->fixup = 0; + } + + /* + * Ok, not fixup is only set for trips that are selected + * but have no selected dives in them. Select all dives + * for such trips. + */ + fixup = 0; + for_each_dive(i, dive) { + dive_trip_t *trip = dive->divetrip; + if (!trip || !trip->fixup) + continue; + fixup = 1; + select_dive(i); + } + + /* + * Ok, we did a forced selection of dives, now we need to update the gtk + * view of what is selected too.. + */ + if (fixup) + update_gtk_selection(selection, model); + +#if DEBUG_SELECTION_TRACKING + dump_selection(); +#endif + + process_selected_dives(); + repaint_dive(); +} + +GtkWidget *dive_list_create(void) +{ + GtkTreeSelection *selection; + + dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS, + G_TYPE_INT, /* index */ + G_TYPE_INT, /* nr */ + G_TYPE_INT64, /* Date */ + G_TYPE_INT, /* Star rating */ + G_TYPE_INT, /* Depth */ + G_TYPE_INT, /* Duration */ + G_TYPE_INT, /* Temperature */ + G_TYPE_INT, /* Total weight */ + G_TYPE_STRING, /* Suit */ + G_TYPE_STRING, /* Cylinder */ + G_TYPE_INT, /* Nitrox */ + G_TYPE_INT, /* SAC */ + G_TYPE_INT, /* OTU */ + G_TYPE_INT, /* MAXCNS */ + G_TYPE_STRING, /* Location */ + GDK_TYPE_PIXBUF /* GPS icon */ + ); + dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS, + G_TYPE_INT, /* index */ + G_TYPE_INT, /* nr */ + G_TYPE_INT64, /* Date */ + G_TYPE_INT, /* Star rating */ + G_TYPE_INT, /* Depth */ + G_TYPE_INT, /* Duration */ + G_TYPE_INT, /* Temperature */ + G_TYPE_INT, /* Total weight */ + G_TYPE_STRING, /* Suit */ + G_TYPE_STRING, /* Cylinder */ + G_TYPE_INT, /* Nitrox */ + G_TYPE_INT, /* SAC */ + G_TYPE_INT, /* OTU */ + G_TYPE_INT, /* MAXCNS */ + G_TYPE_STRING, /* Location */ + GDK_TYPE_PIXBUF /* GPS icon */ + ); + dive_list.model = dive_list.treemodel; + dive_list.tree_view = gtk_tree_view_new_with_model(TREEMODEL(dive_list)); + set_divelist_font(prefs.divelist_font); + + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + + gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE); + gtk_widget_set_size_request(dive_list.tree_view, 200, 200); + + /* check if utf8 stars are available as a default OS feature */ + if (!subsurface_os_feature_available(UTF8_FONT_WITH_STARS)) + dl_column[3].header = "*"; + + dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR); + dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE); + dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING); + dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH); + dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION); + dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE); + dive_list.totalweight = divelist_column(&dive_list, dl_column + DIVE_TOTALWEIGHT); + dive_list.suit = divelist_column(&dive_list, dl_column + DIVE_SUIT); + dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER); + dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX); + dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC); + dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU); + dive_list.maxcns = divelist_column(&dive_list, dl_column + DIVE_MAXCNS); + dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION); + gtk_tree_view_column_set_sort_indicator(dive_list.nr, TRUE); + gtk_tree_view_column_set_sort_order(dive_list.nr, GTK_SORT_DESCENDING); + /* now add the GPS icon to the location column */ + tree_view_column_add_pixbuf(dive_list.tree_view, gpsicon_data_func, dive_list.location); + + fill_dive_list(); + + g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE, + "search-column", DIVE_LOCATION, + "rules-hint", TRUE, + NULL); + + g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL); + g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL); + g_signal_connect(dive_list.tree_view, "row-expanded", G_CALLBACK(row_expanded_cb), NULL); + g_signal_connect(dive_list.tree_view, "row-collapsed", G_CALLBACK(row_collapsed_cb), NULL); + g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL); + g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL); + g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), dive_list.model); + g_signal_connect(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL); + g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL); + + gtk_tree_selection_set_select_function(selection, modify_selection_cb, NULL, NULL); + + dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view); + + dive_list.changed = 0; + + return dive_list.container_widget; +} + +void dive_list_destroy(void) +{ + gtk_widget_destroy(dive_list.tree_view); + g_object_unref(dive_list.treemodel); + g_object_unref(dive_list.listmodel); +} + +struct iteridx { + int idx; + GtkTreeIter *iter; +}; + +static gboolean iter_has_idx(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer _data) +{ + struct iteridx *iteridx = _data; + int idx; + /* Get the dive number */ + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + if (idx == iteridx->idx) { + iteridx->iter = gtk_tree_iter_copy(iter); + return TRUE; /* end foreach */ + } + return FALSE; +} + +static GtkTreeIter *get_iter_from_idx(int idx) +{ + struct iteridx iteridx = {idx, }; + gtk_tree_model_foreach(MODEL(dive_list), iter_has_idx, &iteridx); + return iteridx.iter; +} + +static void scroll_to_selected(GtkTreeIter *iter) +{ + GtkTreePath *treepath; + treepath = gtk_tree_model_get_path(MODEL(dive_list), iter); + scroll_to_path(treepath); + gtk_tree_path_free(treepath); +} + +static void go_to_iter(GtkTreeSelection *selection, GtkTreeIter *iter) +{ + gtk_tree_selection_unselect_all(selection); + gtk_tree_selection_select_iter(selection, iter); + scroll_to_selected(iter); +} + +void show_and_select_dive(struct dive *dive) +{ + GtkTreeSelection *selection; + GtkTreeIter *iter; + struct dive *odive; + int i, divenr; + + divenr = get_divenr(dive); + if (divenr < 0) + /* we failed to find the dive */ + return; + iter = get_iter_from_idx(divenr); + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + for_each_dive(i, odive) + odive->selected = FALSE; + amount_selected = 1; + selected_dive = divenr; + dive->selected = TRUE; + go_to_iter(selection, iter); + gtk_tree_iter_free(iter); +} + +void select_next_dive(void) +{ + GtkTreeIter *nextiter, *parent = NULL; + GtkTreeIter *iter = get_iter_from_idx(selected_dive); + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + int idx; + + if (!iter) + return; + nextiter = gtk_tree_iter_copy(iter); + if (!gtk_tree_model_iter_next(MODEL(dive_list), nextiter)) { + if (!gtk_tree_model_iter_parent(MODEL(dive_list), nextiter, iter)) { + /* we're at the last top level node */ + goto free_iter; + } + if (!gtk_tree_model_iter_next(MODEL(dive_list), nextiter)) { + /* last trip */ + goto free_iter; + } + } + gtk_tree_model_get(MODEL(dive_list), nextiter, DIVE_INDEX, &idx, -1); + if (idx < 0) { + /* need the first child */ + parent = gtk_tree_iter_copy(nextiter); + if (! gtk_tree_model_iter_children(MODEL(dive_list), nextiter, parent)) + goto free_iter; + } + go_to_iter(selection, nextiter); +free_iter: + if (nextiter) + gtk_tree_iter_free(nextiter); + if (parent) + gtk_tree_iter_free(parent); + gtk_tree_iter_free(iter); +} + +void select_prev_dive(void) +{ + GtkTreeIter previter, *parent = NULL; + GtkTreeIter *iter = get_iter_from_idx(selected_dive); + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + GtkTreePath *treepath; + int idx; + + if (!iter) + return; + treepath = gtk_tree_model_get_path(MODEL(dive_list), iter); + if (!gtk_tree_path_prev(treepath)) { + if (!gtk_tree_model_iter_parent(MODEL(dive_list), &previter, iter)) + /* we're at the last top level node */ + goto free_iter; + gtk_tree_path_free(treepath); + treepath = gtk_tree_model_get_path(MODEL(dive_list), &previter); + if (!gtk_tree_path_prev(treepath)) + /* first trip */ + goto free_iter; + if (!gtk_tree_model_get_iter(MODEL(dive_list), &previter, treepath)) + goto free_iter; + } + if (!gtk_tree_model_get_iter(MODEL(dive_list), &previter, treepath)) + goto free_iter; + gtk_tree_model_get(MODEL(dive_list), &previter, DIVE_INDEX, &idx, -1); + if (idx < 0) { + /* need the last child */ + parent = gtk_tree_iter_copy(&previter); + if (! gtk_tree_model_iter_nth_child(MODEL(dive_list), &previter, parent, + gtk_tree_model_iter_n_children(MODEL(dive_list), parent) - 1)) + goto free_iter; + } + go_to_iter(selection, &previter); +free_iter: + gtk_tree_path_free(treepath); + if (parent) + gtk_tree_iter_free(parent); + gtk_tree_iter_free(iter); +} diff --git a/divelist.c b/divelist.c index 198e2d643..f574a31fd 100644 --- a/divelist.c +++ b/divelist.c @@ -1,14 +1,36 @@ /* divelist.c */ -/* this creates the UI for the dive list - - * controlled through the following interfaces: +/* core logic for the dive list - + * accessed through the following interfaces: * - * void flush_divelist(struct dive *dive) - * GtkWidget dive_list_create(void) - * void dive_list_update_dives(void) - * void update_dive_list_units(void) - * void set_divelist_font(const char *font) + * dive_trip_t *dive_trip_list; + * unsigned int amount_selected; + * void dump_selection(void) + * dive_trip_t *find_trip_by_idx(int idx) + * int trip_has_selected_dives(dive_trip_t *trip) + * void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p) + * int total_weight(struct dive *dive) + * int get_divenr(struct dive *dive) + * double init_decompression(struct dive *dive) + * void update_cylinder_related_info(struct dive *dive) + * void get_location(struct dive *dive, char **str) + * void get_cylinder(struct dive *dive, char **str) + * void get_suit(struct dive *dive, char **str) + * void dump_trip_list(void) + * dive_trip_t *find_matching_trip(timestamp_t when) + * void insert_trip(dive_trip_t **dive_trip_p) + * void remove_dive_from_trip(struct dive *dive) + * void add_dive_to_trip(struct dive *dive, dive_trip_t *trip) + * dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive) + * void autogroup_dives(void) + * void clear_trip_indexes(void) + * void delete_single_dive(int idx) + * void add_single_dive(int idx, struct dive *dive) + * void merge_dive_index(int i, struct dive *a) + * void select_dive(int idx) + * void deselect_dive(int idx) * void mark_divelist_changed(int changed) * int unsaved_changes() + * void remove_autogen_trips() */ #include #include @@ -25,99 +47,16 @@ #include #endif -#include "divelist.h" #include "dive.h" +#include "divelist.h" #include "display.h" -#include "display-gtk.h" #include "webservice.h" -#include -#include "satellite.h" - -struct DiveList { - GtkWidget *tree_view; - GtkWidget *container_widget; - GtkTreeStore *model, *listmodel, *treemodel; - GtkTreeViewColumn *nr, *date, *stars, *depth, *duration, *location; - GtkTreeViewColumn *temperature, *cylinder, *totalweight, *suit, *nitrox, *sac, *otu, *maxcns; - int changed; -}; - -static struct DiveList dive_list; -#define MODEL(_dl) GTK_TREE_MODEL((_dl).model) -#define TREEMODEL(_dl) GTK_TREE_MODEL((_dl).treemodel) -#define LISTMODEL(_dl) GTK_TREE_MODEL((_dl).listmodel) -#define STORE(_dl) GTK_TREE_STORE((_dl).model) -#define TREESTORE(_dl) GTK_TREE_STORE((_dl).treemodel) -#define LISTSTORE(_dl) GTK_TREE_STORE((_dl).listmodel) +static short dive_list_changed = FALSE; dive_trip_t *dive_trip_list; -gboolean autogroup = FALSE; -static gboolean in_set_cursor = FALSE; -static gboolean set_selected(GtkTreeModel *model, GtkTreePath *path, - GtkTreeIter *iter, gpointer data); - -/* - * The dive list has the dive data in both string format (for showing) - * and in "raw" format (for sorting purposes) - */ -enum { - DIVE_INDEX = 0, - DIVE_NR, /* int: dive->nr */ - DIVE_DATE, /* timestamp_t: dive->when */ - DIVE_RATING, /* int: 0-5 stars */ - DIVE_DEPTH, /* int: dive->maxdepth in mm */ - DIVE_DURATION, /* int: in seconds */ - DIVE_TEMPERATURE, /* int: in mkelvin */ - DIVE_TOTALWEIGHT, /* int: in grams */ - DIVE_SUIT, /* "wet, 3mm" */ - DIVE_CYLINDER, - DIVE_NITROX, /* int: dummy */ - DIVE_SAC, /* int: in ml/min */ - DIVE_OTU, /* int: in OTUs */ - DIVE_MAXCNS, /* int: in % */ - DIVE_LOCATION, /* "2nd Cathedral, Lanai" */ - DIVE_LOC_ICON, /* pixbuf for gps icon */ - DIVELIST_COLUMNS -}; - -static void merge_dive_into_trip_above_cb(GtkWidget *menuitem, GtkTreePath *path); - -#ifdef DEBUG_MODEL -static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path, - GtkTreeIter *iter, gpointer data) -{ - char *location; - int idx, nr, duration; - struct dive *dive; - timestamp_t when; - struct tm tm; - - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, DIVE_DATE, &when, - DIVE_DURATION, &duration, DIVE_LOCATION, &location, -1); - utc_mkdate(when, &tm); - printf("iter %x:%x entry #%d : nr %d @ %04d-%02d-%02d %02d:%02d:%02d duration %d location %s ", - iter->stamp, iter->user_data, idx, nr, - tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, - tm.tm_hour, tm.tm_min, tm.tm_sec, - duration, location); - dive = get_dive(idx); - if (dive) - printf("tripflag %d\n", dive->tripflag); - else - printf("without matching dive\n"); - free(location); - - return FALSE; -} - -static void dump_model(GtkListStore *store) -{ - gtk_tree_model_foreach(GTK_TREE_MODEL(store), dump_model_entry, NULL); - printf("\n---\n\n"); -} -#endif +unsigned int amount_selected; #if DEBUG_SELECTION_TRACKING void dump_selection(void) @@ -134,44 +73,7 @@ void dump_selection(void) } #endif -/* when subsurface starts we want to have the last dive selected. So we simply - walk to the first leaf (and skip the summary entries - which have negative - DIVE_INDEX) */ -static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx) -{ - GtkTreeIter parent; - GtkTreePath *tpath; - - while (*diveidx < 0) { - memcpy(&parent, iter, sizeof(parent)); - tpath = gtk_tree_model_get_path(model, &parent); - if (!gtk_tree_model_iter_children(model, iter, &parent)) { - /* we should never have a parent without child */ - gtk_tree_path_free(tpath); - return; - } - if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath)) - gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE); - gtk_tree_path_free(tpath); - gtk_tree_model_get(model, iter, DIVE_INDEX, diveidx, -1); - } -} - -static struct dive *dive_from_path(GtkTreePath *path) -{ - GtkTreeIter iter; - int idx; - - if (gtk_tree_model_get_iter(MODEL(dive_list), &iter, path)) { - gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); - return get_dive(idx); - } else { - return NULL; - } - -} - -static dive_trip_t *find_trip_by_idx(int idx) +dive_trip_t *find_trip_by_idx(int idx) { dive_trip_t *trip = dive_trip_list; @@ -186,48 +88,7 @@ static dive_trip_t *find_trip_by_idx(int idx) return NULL; } -static int get_path_index(GtkTreePath *path) -{ - GtkTreeIter iter; - int idx; - - gtk_tree_model_get_iter(MODEL(dive_list), &iter, path); - gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); - return idx; -} - -/* make sure that if we expand a summary row that is selected, the children show - up as selected, too */ -static void row_expanded_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data) -{ - GtkTreeIter child; - GtkTreeModel *model = MODEL(dive_list); - GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); - dive_trip_t *trip; - - trip = find_trip_by_idx(get_path_index(path)); - if (!trip) - return; - - trip->expanded = 1; - if (!gtk_tree_model_iter_children(model, &child, iter)) - return; - - do { - int idx; - struct dive *dive; - - gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1); - dive = get_dive(idx); - - if (dive->selected) - gtk_tree_selection_select_iter(selection, &child); - else - gtk_tree_selection_unselect_iter(selection, &child); - } while (gtk_tree_model_iter_next(model, &child)); -} - -static int trip_has_selected_dives(dive_trip_t *trip) +int trip_has_selected_dives(dive_trip_t *trip) { struct dive *dive; for (dive = trip->dives; dive; dive = dive->next) { @@ -237,216 +98,6 @@ static int trip_has_selected_dives(dive_trip_t *trip) return 0; } -/* Make sure that if we collapse a summary row with any selected children, the row - shows up as selected too */ -static void row_collapsed_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data) -{ - dive_trip_t *trip; - GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); - - trip = find_trip_by_idx(get_path_index(path)); - if (!trip) - return; - - trip->expanded = 0; - if (trip_has_selected_dives(trip)) { - gtk_tree_selection_select_iter(selection, iter); - trip->selected = 1; - } -} - -const char *star_strings[] = { - ZERO_STARS, - ONE_STARS, - TWO_STARS, - THREE_STARS, - FOUR_STARS, - FIVE_STARS -}; - -static void star_data_func(GtkTreeViewColumn *col, - GtkCellRenderer *renderer, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - int nr_stars, idx; - char buffer[40]; - - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_RATING, &nr_stars, -1); - if (idx < 0) { - *buffer = '\0'; - } else { - if (nr_stars < 0 || nr_stars > 5) - nr_stars = 0; - snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]); - } - g_object_set(renderer, "text", buffer, NULL); -} - -static void date_data_func(GtkTreeViewColumn *col, - GtkCellRenderer *renderer, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - int idx, nr; - struct tm tm; - timestamp_t when; - /* this should be enought for most languages. if not increase the value. */ - char buffer[256]; - - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &when, -1); - nr = gtk_tree_model_iter_n_children(model, iter); - - utc_mkdate(when, &tm); - if (idx < 0) { - snprintf(buffer, sizeof(buffer), - /*++GETTEXT 60 char buffer weekday, monthname, day of month, year, nr dives */ - ngettext("Trip %1$s, %2$s %3$d, %4$d (%5$d dive)", - "Trip %1$s, %2$s %3$d, %4$d (%5$d dives)", nr), - weekday(tm.tm_wday), - monthname(tm.tm_mon), - tm.tm_mday, tm.tm_year + 1900, - nr); - } else { - snprintf(buffer, sizeof(buffer), - /*++GETTEXT 60 char buffer weekday, monthname, day of month, year, hour:min */ - _("%1$s, %2$s %3$d, %4$d %5$02d:%6$02d"), - weekday(tm.tm_wday), - monthname(tm.tm_mon), - tm.tm_mday, tm.tm_year + 1900, - tm.tm_hour, tm.tm_min); - } - g_object_set(renderer, "text", buffer, NULL); -} - -static void depth_data_func(GtkTreeViewColumn *col, - GtkCellRenderer *renderer, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - int depth, integer, frac, len, idx; - char buffer[40]; - - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1); - - if (idx < 0) { - *buffer = '\0'; - } else { - switch (prefs.units.length) { - case METERS: - /* To tenths of meters */ - depth = (depth + 49) / 100; - integer = depth / 10; - frac = depth % 10; - if (integer < 20) - break; - if (frac >= 5) - integer++; - frac = -1; - break; - case FEET: - integer = mm_to_feet(depth) + 0.5; - frac = -1; - break; - default: - return; - } - len = snprintf(buffer, sizeof(buffer), "%d", integer); - if (frac >= 0) - len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac); - } - g_object_set(renderer, "text", buffer, NULL); -} - -static void duration_data_func(GtkTreeViewColumn *col, - GtkCellRenderer *renderer, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - unsigned int sec; - int idx; - char buffer[40]; - - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1); - if (idx < 0) - *buffer = '\0'; - else - snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60); - - g_object_set(renderer, "text", buffer, NULL); -} - -static void temperature_data_func(GtkTreeViewColumn *col, - GtkCellRenderer *renderer, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - int value, idx; - char buffer[80]; - - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1); - - *buffer = 0; - if (idx >= 0 && value) { - double deg; - switch (prefs.units.temperature) { - case CELSIUS: - deg = mkelvin_to_C(value); - break; - case FAHRENHEIT: - deg = mkelvin_to_F(value); - break; - default: - return; - } - snprintf(buffer, sizeof(buffer), "%.1f", deg); - } - - g_object_set(renderer, "text", buffer, NULL); -} - -static void gpsicon_data_func(GtkTreeViewColumn *col, - GtkCellRenderer *renderer, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - int idx; - GdkPixbuf *icon; - - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_LOC_ICON, &icon, -1); - g_object_set(renderer, "pixbuf", icon, NULL); -} - -static void nr_data_func(GtkTreeViewColumn *col, - GtkCellRenderer *renderer, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - int idx, nr; - char buffer[40]; - struct dive *dive; - - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1); - if (idx < 0) { - *buffer = '\0'; - } else { - /* make dives that are not in trips stand out */ - dive = get_dive(idx); - if (!DIVE_IN_TRIP(dive)) - snprintf(buffer, sizeof(buffer), "%d", nr); - else - snprintf(buffer, sizeof(buffer), "%d", nr); - } - g_object_set(renderer, "markup", buffer, NULL); -} - /* * Get "maximal" dive gas for a dive. * Rules: @@ -454,7 +105,7 @@ static void nr_data_func(GtkTreeViewColumn *col, * - Nitrox trumps air (even if hypoxic) * These are the same rules as the inter-dive sorting rules. */ -static void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p) +void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p) { int i; int maxo2 = -1, maxhe = -1, mino2 = 1000; @@ -535,165 +186,6 @@ int total_weight(struct dive *dive) return total_grams; } -static void weight_data_func(GtkTreeViewColumn *col, - GtkCellRenderer *renderer, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - int indx, decimals; - double value; - char buffer[80]; - struct dive *dive; - - gtk_tree_model_get(model, iter, DIVE_INDEX, &indx, -1); - dive = get_dive(indx); - value = get_weight_units(total_weight(dive), &decimals, NULL); - if (value == 0.0) - *buffer = '\0'; - else - snprintf(buffer, sizeof(buffer), "%.*f", decimals, value); - - g_object_set(renderer, "text", buffer, NULL); -} - -static gint nitrox_sort_func(GtkTreeModel *model, - GtkTreeIter *iter_a, - GtkTreeIter *iter_b, - gpointer user_data) -{ - int index_a, index_b; - struct dive *a, *b; - int a_o2, b_o2; - int a_he, b_he; - int a_o2low, b_o2low; - - gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1); - gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1); - a = get_dive(index_a); - b = get_dive(index_b); - get_dive_gas(a, &a_o2, &a_he, &a_o2low); - get_dive_gas(b, &b_o2, &b_he, &b_o2low); - - /* Sort by Helium first, O2 second */ - if (a_he == b_he) { - if (a_o2 == b_o2) - return a_o2low - b_o2low; - return a_o2 - b_o2; - } - return a_he - b_he; -} - -#define UTF8_ELLIPSIS "\xE2\x80\xA6" - -static void nitrox_data_func(GtkTreeViewColumn *col, - GtkCellRenderer *renderer, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - int idx, o2, he, o2low; - char buffer[80]; - struct dive *dive; - - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); - if (idx < 0) { - *buffer = '\0'; - goto exit; - } - dive = get_dive(idx); - get_dive_gas(dive, &o2, &he, &o2low); - o2 = (o2 + 5) / 10; - he = (he + 5) / 10; - o2low = (o2low + 5) / 10; - - if (he) - snprintf(buffer, sizeof(buffer), "%d/%d", o2, he); - else if (o2) - if (o2 == o2low) - snprintf(buffer, sizeof(buffer), "%d", o2); - else - snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2); - else - strcpy(buffer, _("air")); -exit: - g_object_set(renderer, "text", buffer, NULL); -} - -/* Render the SAC data (integer value of "ml / min") */ -static void sac_data_func(GtkTreeViewColumn *col, - GtkCellRenderer *renderer, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - int value, idx; - const char *fmt; - char buffer[16]; - double sac; - - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1); - - if (idx < 0 || !value) { - *buffer = '\0'; - goto exit; - } - - sac = value / 1000.0; - switch (prefs.units.volume) { - case LITER: - fmt = "%4.1f"; - break; - case CUFT: - fmt = "%4.2f"; - sac = ml_to_cuft(sac * 1000); - break; - } - snprintf(buffer, sizeof(buffer), fmt, sac); -exit: - g_object_set(renderer, "text", buffer, NULL); -} - -/* Render the OTU data (integer value of "OTU") */ -static void otu_data_func(GtkTreeViewColumn *col, - GtkCellRenderer *renderer, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - int value, idx; - char buffer[16]; - - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1); - - if (idx < 0 || !value) - *buffer = '\0'; - else - snprintf(buffer, sizeof(buffer), "%d", value); - - g_object_set(renderer, "text", buffer, NULL); -} - -/* Render the CNS data (in full %) */ -static void cns_data_func(GtkTreeViewColumn *col, - GtkCellRenderer *renderer, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - int value, idx; - char buffer[16]; - - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_MAXCNS, &value, -1); - - if (idx < 0 || !value) - *buffer = '\0'; - else - snprintf(buffer, sizeof(buffer), "%d%%", value); - - g_object_set(renderer, "text", buffer, NULL); -} - static int active_o2(struct dive *dive, struct divecomputer *dc, duration_t time) { int o2permille = dive->cylinder[0].gasmix.o2.permille; @@ -806,7 +298,7 @@ static void add_dive_to_deco(struct dive *dive) } } -static int get_divenr(struct dive *dive) +int get_divenr(struct dive *dive) { int divenr = -1; while (++divenr < dive_table.nr && get_dive(divenr) != dive) @@ -913,157 +405,27 @@ static void get_string(char **str, const char *s) *str = n; } -static void get_location(struct dive *dive, char **str) +void get_location(struct dive *dive, char **str) { get_string(str, dive->location); } -static void get_cylinder(struct dive *dive, char **str) +void get_cylinder(struct dive *dive, char **str) { get_string(str, dive->cylinder[0].type.description); } -static void get_suit(struct dive *dive, char **str) +void get_suit(struct dive *dive, char **str) { get_string(str, dive->suit); } -GdkPixbuf *get_gps_icon(void) -{ - return gdk_pixbuf_from_pixdata(&satellite_pixbuf, TRUE, NULL); -} - -static GdkPixbuf *get_gps_icon_for_dive(struct dive *dive) -{ - if (dive_has_gps_location(dive)) - return get_gps_icon(); - else - return NULL; -} - -/* - * Set up anything that could have changed due to editing - * of dive information; we need to do this for both models, - * so we simply call set_one_dive again with the non-current model - */ -/* forward declaration for recursion */ -static gboolean set_one_dive(GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data); - -static void fill_one_dive(struct dive *dive, - GtkTreeModel *model, - GtkTreeIter *iter) -{ - char *location, *cylinder, *suit; - GtkTreeModel *othermodel; - GdkPixbuf *icon; - - get_cylinder(dive, &cylinder); - get_location(dive, &location); - get_suit(dive, &suit); - icon = get_gps_icon_for_dive(dive); - gtk_tree_store_set(GTK_TREE_STORE(model), iter, - DIVE_NR, dive->number, - DIVE_LOCATION, location, - DIVE_LOC_ICON, icon, - DIVE_CYLINDER, cylinder, - DIVE_RATING, dive->rating, - DIVE_SAC, dive->sac, - DIVE_OTU, dive->otu, - DIVE_MAXCNS, dive->maxcns, - DIVE_TOTALWEIGHT, total_weight(dive), - DIVE_SUIT, suit, - -1); - - if (icon) - g_object_unref(icon); - free(location); - free(cylinder); - free(suit); - - if (model == TREEMODEL(dive_list)) - othermodel = LISTMODEL(dive_list); - else - othermodel = TREEMODEL(dive_list); - if (othermodel != MODEL(dive_list)) - /* recursive call */ - gtk_tree_model_foreach(othermodel, set_one_dive, dive); -} - -static gboolean set_one_dive(GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - int idx; - struct dive *dive; - - /* Get the dive number */ - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); - if (idx < 0) - return FALSE; - dive = get_dive(idx); - if (!dive) - return TRUE; - if (data && dive != data) - return FALSE; - - fill_one_dive(dive, model, iter); - return dive == data; -} - -void flush_divelist(struct dive *dive) -{ - GtkTreeModel *model = MODEL(dive_list); - - gtk_tree_model_foreach(model, set_one_dive, dive); -} - -void set_divelist_font(const char *font) -{ - PangoFontDescription *font_desc = pango_font_description_from_string(font); - gtk_widget_modify_font(dive_list.tree_view, font_desc); - pango_font_description_free(font_desc); -} - -void update_dive_list_units(void) -{ - const char *unit; - GtkTreeModel *model = MODEL(dive_list); - - (void) get_depth_units(0, NULL, &unit); - gtk_tree_view_column_set_title(dive_list.depth, unit); - - (void) get_temp_units(0, &unit); - gtk_tree_view_column_set_title(dive_list.temperature, unit); - - (void) get_weight_units(0, NULL, &unit); - gtk_tree_view_column_set_title(dive_list.totalweight, unit); - - gtk_tree_model_foreach(model, set_one_dive, NULL); -} - -void update_dive_list_col_visibility(void) -{ - gtk_tree_view_column_set_visible(dive_list.cylinder, prefs.visible_cols.cylinder); - gtk_tree_view_column_set_visible(dive_list.temperature, prefs.visible_cols.temperature); - gtk_tree_view_column_set_visible(dive_list.totalweight, prefs.visible_cols.totalweight); - gtk_tree_view_column_set_visible(dive_list.suit, prefs.visible_cols.suit); - gtk_tree_view_column_set_visible(dive_list.nitrox, prefs.visible_cols.nitrox); - gtk_tree_view_column_set_visible(dive_list.sac, prefs.visible_cols.sac); - gtk_tree_view_column_set_visible(dive_list.otu, prefs.visible_cols.otu); - gtk_tree_view_column_set_visible(dive_list.maxcns, prefs.visible_cols.maxcns); - return; -} - /* * helper functions for dive_trip handling */ #ifdef DEBUG_TRIP -static void dump_trip_list(void) +void dump_trip_list(void) { dive_trip_t *trip; int i=0; @@ -1086,7 +448,7 @@ static void dump_trip_list(void) #endif /* this finds the last trip that at or before the time given */ -static dive_trip_t *find_matching_trip(timestamp_t when) +dive_trip_t *find_matching_trip(timestamp_t when) { dive_trip_t *trip = dive_trip_list; @@ -1181,7 +543,7 @@ static void find_new_trip_start_time(dive_trip_t *trip) trip->when = when; } -static void remove_dive_from_trip(struct dive *dive) +void remove_dive_from_trip(struct dive *dive) { struct dive *next, **pprev; dive_trip_t *trip = dive->divetrip; @@ -1226,7 +588,7 @@ void add_dive_to_trip(struct dive *dive, dive_trip_t *trip) trip->when = dive->when; } -static dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive) +dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive) { dive_trip_t *dive_trip = calloc(sizeof(dive_trip_t),1); dive_trip->when = dive->when; @@ -1242,7 +604,7 @@ static dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive) /* * Walk the dives from the oldest dive, and see if we can autogroup them */ -static void autogroup_dives(void) +void autogroup_dives(void) { int i; struct dive *dive, *lastdive = NULL; @@ -1280,7 +642,7 @@ static void autogroup_dives(void) #endif } -static void clear_trip_indexes(void) +void clear_trip_indexes(void) { dive_trip_t *trip; @@ -1288,1296 +650,76 @@ static void clear_trip_indexes(void) trip->index = 0; } -/* Select the iter asked for, and set the keyboard focus on it */ -static void go_to_iter(GtkTreeSelection *selection, GtkTreeIter *iter); -static void fill_dive_list(void) +/* this implements the mechanics of removing the dive from the table, + * but doesn't deal with updating dive trips, etc */ +void delete_single_dive(int idx) { - int i, trip_index = 0; - GtkTreeIter iter, parent_iter, lookup, *parent_ptr = NULL; - GtkTreeStore *liststore, *treestore; - GdkPixbuf *icon; - - /* Do we need to create any dive groups automatically? */ - if (autogroup) - autogroup_dives(); - - treestore = TREESTORE(dive_list); - liststore = LISTSTORE(dive_list); - - clear_trip_indexes(); - - i = dive_table.nr; - while (--i >= 0) { - struct dive *dive = get_dive(i); - dive_trip_t *trip = dive->divetrip; - - if (!trip) { - parent_ptr = NULL; - } else if (!trip->index) { - trip->index = ++trip_index; - - /* Create new trip entry */ - gtk_tree_store_append(treestore, &parent_iter, NULL); - parent_ptr = &parent_iter; - - /* a duration of 0 (and negative index) identifies a group */ - gtk_tree_store_set(treestore, parent_ptr, - DIVE_INDEX, -trip_index, - DIVE_DATE, trip->when, - DIVE_LOCATION, trip->location, - DIVE_DURATION, 0, - -1); - } else { - int idx, ok; - GtkTreeModel *model = TREEMODEL(dive_list); - - parent_ptr = NULL; - ok = gtk_tree_model_get_iter_first(model, &lookup); - while (ok) { - gtk_tree_model_get(model, &lookup, DIVE_INDEX, &idx, -1); - if (idx == -trip->index) { - parent_ptr = &lookup; - break; - } - ok = gtk_tree_model_iter_next(model, &lookup); - } - } - - /* store dive */ - update_cylinder_related_info(dive); - gtk_tree_store_append(treestore, &iter, parent_ptr); - icon = get_gps_icon_for_dive(dive); - gtk_tree_store_set(treestore, &iter, - DIVE_INDEX, i, - DIVE_NR, dive->number, - DIVE_DATE, dive->when, - DIVE_DEPTH, dive->maxdepth, - DIVE_DURATION, dive->duration.seconds, - DIVE_LOCATION, dive->location, - DIVE_LOC_ICON, icon, - DIVE_RATING, dive->rating, - DIVE_TEMPERATURE, dive->watertemp.mkelvin, - DIVE_SAC, 0, - -1); - if (icon) - g_object_unref(icon); - gtk_tree_store_append(liststore, &iter, NULL); - gtk_tree_store_set(liststore, &iter, - DIVE_INDEX, i, - DIVE_NR, dive->number, - DIVE_DATE, dive->when, - DIVE_DEPTH, dive->maxdepth, - DIVE_DURATION, dive->duration.seconds, - DIVE_LOCATION, dive->location, - DIVE_LOC_ICON, icon, - DIVE_RATING, dive->rating, - DIVE_TEMPERATURE, dive->watertemp.mkelvin, - DIVE_TOTALWEIGHT, 0, - DIVE_SUIT, dive->suit, - DIVE_SAC, 0, - -1); - } - - update_dive_list_units(); - if (amount_selected == 0 && gtk_tree_model_get_iter_first(MODEL(dive_list), &iter)) { - GtkTreeSelection *selection; - - /* select the last dive (and make sure it's an actual dive that is selected) */ - gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &selected_dive, -1); - first_leaf(MODEL(dive_list), &iter, &selected_dive); - selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); - go_to_iter(selection, &iter); - } + int i; + struct dive *dive = get_dive(idx); + if (!dive) + return; /* this should never happen */ + remove_dive_from_trip(dive); + for (i = idx; i < dive_table.nr - 1; i++) + dive_table.dives[i] = dive_table.dives[i+1]; + dive_table.dives[--dive_table.nr] = NULL; + if (dive->selected) + amount_selected--; + /* free all allocations */ + free(dive->dc.sample); + if (dive->location) + free((void *)dive->location); + if (dive->notes) + free((void *)dive->notes); + if (dive->divemaster) + free((void *)dive->divemaster); + if (dive->buddy) + free((void *)dive->buddy); + if (dive->suit) + free((void *)dive->suit); + free(dive); } -static void restore_tree_state(void); - -void dive_list_update_dives(void) +void add_single_dive(int idx, struct dive *dive) { - dive_table.preexisting = dive_table.nr; - gtk_tree_store_clear(TREESTORE(dive_list)); - gtk_tree_store_clear(LISTSTORE(dive_list)); - fill_dive_list(); - restore_tree_state(); - repaint_dive(); + int i; + dive_table.nr++; + if (dive->selected) + amount_selected++; + for (i = idx; i < dive_table.nr ; i++) { + struct dive *tmp = dive_table.dives[i]; + dive_table.dives[i] = dive; + dive = tmp; + } } -static gint dive_nr_sort(GtkTreeModel *model, - GtkTreeIter *iter_a, - GtkTreeIter *iter_b, - gpointer user_data) +void merge_dive_index(int i, struct dive *a) { - int idx_a, idx_b; - timestamp_t when_a, when_b; - struct dive *a, *b; - dive_trip_t *tripa = NULL, *tripb = NULL; - - gtk_tree_model_get(model, iter_a, DIVE_INDEX, &idx_a, DIVE_DATE, &when_a, -1); - gtk_tree_model_get(model, iter_b, DIVE_INDEX, &idx_b, DIVE_DATE, &when_b, -1); + struct dive *b = get_dive(i+1); + struct dive *res; - if (idx_a < 0) { - a = NULL; - tripa = find_trip_by_idx(idx_a); - } else { - a = get_dive(idx_a); - if (a) - tripa = a->divetrip; - } + res = merge_dives(a, b, b->when - a->when, FALSE); + if (!res) + return; - if (idx_b < 0) { - b = NULL; - tripb = find_trip_by_idx(idx_b); - } else { - b = get_dive(idx_b); - if (b) - tripb = b->divetrip; - } + add_single_dive(i, res); + delete_single_dive(i+1); + delete_single_dive(i+1); - /* - * Compare dive dates within the same trip (or when there - * are no trips involved at all). But if we have two - * different trips use the trip dates for comparison - */ - if (tripa != tripb) { - if (tripa) - when_a = tripa->when; - if (tripb) - when_b = tripb->when; - } - return when_a - when_b; + dive_list_update_dives(); + mark_divelist_changed(TRUE); } - -static struct divelist_column { - const char *header; - data_func_t data; - sort_func_t sort; - unsigned int flags; - int *visible; -} dl_column[] = { - [DIVE_NR] = { "#", nr_data_func, dive_nr_sort, ALIGN_RIGHT }, - [DIVE_DATE] = { N_("Date"), date_data_func, NULL, ALIGN_LEFT }, - [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT }, - [DIVE_DEPTH] = { N_("ft"), depth_data_func, NULL, ALIGN_RIGHT }, - [DIVE_DURATION] = { N_("min"), duration_data_func, NULL, ALIGN_RIGHT }, - [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &prefs.visible_cols.temperature }, - [DIVE_TOTALWEIGHT] = { N_("lbs"), weight_data_func, NULL, ALIGN_RIGHT, &prefs.visible_cols.totalweight }, - [DIVE_SUIT] = { N_("Suit"), NULL, NULL, ALIGN_LEFT, &prefs.visible_cols.suit }, - [DIVE_CYLINDER] = { N_("Cyl"), NULL, NULL, 0, &prefs.visible_cols.cylinder }, - [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &prefs.visible_cols.nitrox }, - [DIVE_SAC] = { N_("SAC"), sac_data_func, NULL, 0, &prefs.visible_cols.sac }, - [DIVE_OTU] = { N_("OTU"), otu_data_func, NULL, 0, &prefs.visible_cols.otu }, - [DIVE_MAXCNS] = { N_("maxCNS"), cns_data_func, NULL, 0, &prefs.visible_cols.maxcns }, - [DIVE_LOCATION] = { N_("Location"), NULL, NULL, ALIGN_LEFT }, -}; - - -static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col) +void select_dive(int idx) { - int index = col - &dl_column[0]; - const char *title = _(col->header); - data_func_t data_func = col->data; - sort_func_t sort_func = col->sort; - unsigned int flags = col->flags; - int *visible = col->visible; - GtkWidget *tree_view = dl->tree_view; - GtkTreeStore *treemodel = dl->treemodel; - GtkTreeStore *listmodel = dl->listmodel; - GtkTreeViewColumn *ret; - - if (visible && !*visible) - flags |= INVISIBLE; - ret = tree_view_column(tree_view, index, title, data_func, flags); - if (sort_func) { - /* the sort functions are needed in the corresponding models */ - if (index == DIVE_NR) - gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treemodel), index, sort_func, NULL, NULL); - else - gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(listmodel), index, sort_func, NULL, NULL); + struct dive *dive = get_dive(idx); + if (dive && !dive->selected) { + dive->selected = 1; + amount_selected++; + selected_dive = idx; } - return ret; -} - -/* - * This is some crazy crap. The only way to get default focus seems - * to be to grab focus as the widget is being shown the first time. - */ -static void realize_cb(GtkWidget *tree_view, gpointer userdata) -{ - gtk_widget_grab_focus(tree_view); } -/* - * Double-clicking on a group entry will expand a collapsed group - * and vice versa. - */ -static void collapse_expand(GtkTreeView *tree_view, GtkTreePath *path) -{ - if (!gtk_tree_view_row_expanded(tree_view, path)) - gtk_tree_view_expand_row(tree_view, path, FALSE); - else - gtk_tree_view_collapse_row(tree_view, path); - -} - -/* Double-click on a dive list */ -static void row_activated_cb(GtkTreeView *tree_view, - GtkTreePath *path, - GtkTreeViewColumn *column, - gpointer userdata) -{ - int index; - GtkTreeIter iter; - - if (!gtk_tree_model_get_iter(MODEL(dive_list), &iter, path)) - return; - - gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &index, -1); - /* a negative index is special for the "group by date" entries */ - if (index < 0) { - collapse_expand(tree_view, path); - return; - } - edit_dive_info(get_dive(index), FALSE); -} - -void add_dive_cb(GtkWidget *menuitem, gpointer data) -{ - struct dive *dive; - - dive = alloc_dive(); - if (add_new_dive(dive)) { - record_dive(dive); - report_dives(TRUE, FALSE); - return; - } - free(dive); -} - -static void edit_trip_cb(GtkWidget *menuitem, GtkTreePath *path) -{ - int idx; - GtkTreeIter iter; - dive_trip_t *dive_trip; - - gtk_tree_model_get_iter(MODEL(dive_list), &iter, path); - gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); - dive_trip = find_trip_by_idx(idx); - if (edit_trip(dive_trip)) - gtk_tree_store_set(STORE(dive_list), &iter, DIVE_LOCATION, dive_trip->location, -1); -} - -static void edit_selected_dives_cb(GtkWidget *menuitem, gpointer data) -{ - edit_multi_dive_info(NULL); -} - -static void edit_dive_from_path_cb(GtkWidget *menuitem, GtkTreePath *path) -{ - struct dive *dive = dive_from_path(path); - - edit_multi_dive_info(dive); -} - -static void edit_dive_when_cb(GtkWidget *menuitem, struct dive *dive) -{ - GtkWidget *dialog, *cal, *h, *m, *timehbox; - timestamp_t when; - - guint yval, mval, dval; - int success; - struct tm tm; - - if (!dive) - return; - - when = dive->when; - utc_mkdate(when, &tm); - dialog = create_date_time_widget(&tm, &cal, &h, &m, &timehbox); - - gtk_widget_show_all(dialog); - success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; - if (!success) { - gtk_widget_destroy(dialog); - return; - } - memset(&tm, 0, sizeof(tm)); - gtk_calendar_get_date(GTK_CALENDAR(cal), &yval, &mval, &dval); - tm.tm_year = yval; - tm.tm_mon = mval; - tm.tm_mday = dval; - tm.tm_hour = gtk_spin_button_get_value(GTK_SPIN_BUTTON(h)); - tm.tm_min = gtk_spin_button_get_value(GTK_SPIN_BUTTON(m)); - - gtk_widget_destroy(dialog); - when = utc_mktime(&tm); - if (dive->when != when) { - /* if this is the only dive in the trip, just change the trip time */ - if (dive->divetrip && dive->divetrip->nrdives == 1) - dive->divetrip->when = when; - /* if this is suddenly before the start of the trip, remove it from the trip */ - else if (dive->divetrip && dive->divetrip->when > when) - remove_dive_from_trip(dive); - else if (find_matching_trip(when) != dive->divetrip) - remove_dive_from_trip(dive); - dive->when = when; - mark_divelist_changed(TRUE); - report_dives(FALSE, FALSE); - dive_list_update_dives(); - } -} - -#if HAVE_OSM_GPS_MAP -static void show_gps_location_cb(GtkWidget *menuitem, struct dive *dive) -{ - show_gps_location(dive, NULL); -} -#endif - -gboolean icon_click_cb(GtkWidget *w, GdkEventButton *event, gpointer data) -{ -#if HAVE_OSM_GPS_MAP - GtkTreePath *path = NULL; - GtkTreeIter iter; - GtkTreeViewColumn *col; - int idx; - struct dive *dive; - - /* left click ? */ - if (event->button == 1 && - gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(dive_list.tree_view), event->x, event->y, &path, &col, NULL, NULL)) { - /* is it the icon column ? (we passed the correct column in when registering the callback) */ - if (col == data) { - gtk_tree_model_get_iter(MODEL(dive_list), &iter, path); - gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); - dive = get_dive(idx); - if (dive && dive_has_gps_location(dive)) - show_gps_location(dive, NULL); - } - if (path) - gtk_tree_path_free(path); - } -#endif - /* keep processing the click */ - return FALSE; -} - -static void save_as_cb(GtkWidget *menuitem, struct dive *dive) -{ - GtkWidget *dialog; - char *filename = NULL; - - dialog = gtk_file_chooser_dialog_new(_("Save File As"), - GTK_WINDOW(main_window), - GTK_FILE_CHOOSER_ACTION_SAVE, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, - NULL); - gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); - - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); - } - gtk_widget_destroy(dialog); - - if (filename){ - save_dives_logic(filename, TRUE); - g_free(filename); - } -} - -static void expand_all_cb(GtkWidget *menuitem, GtkTreeView *tree_view) -{ - gtk_tree_view_expand_all(tree_view); -} - -static void collapse_all_cb(GtkWidget *menuitem, GtkTreeView *tree_view) -{ - gtk_tree_view_collapse_all(tree_view); -} - -/* Move a top-level dive into the trip above it */ -static void merge_dive_into_trip_above_cb(GtkWidget *menuitem, GtkTreePath *path) -{ - int idx; - struct dive *dive; - dive_trip_t *trip; - - idx = get_path_index(path); - dive = get_dive(idx); - - /* Needs to be a dive, and at the top level */ - if (!dive || dive->divetrip) - return; - - /* Find the "trip above". */ - for (;;) { - if (!gtk_tree_path_prev(path)) - return; - idx = get_path_index(path); - trip = find_trip_by_idx(idx); - if (trip) - break; - } - - add_dive_to_trip(dive, trip); - if (dive->selected) { - for_each_dive(idx, dive) { - if (!dive->selected) - continue; - add_dive_to_trip(dive, trip); - } - } - - trip->expanded = 1; - dive_list_update_dives(); - mark_divelist_changed(TRUE); -} - -static void insert_trip_before_cb(GtkWidget *menuitem, GtkTreePath *path) -{ - int idx; - struct dive *dive; - dive_trip_t *trip; - - idx = get_path_index(path); - dive = get_dive(idx); - if (!dive) - return; - trip = create_and_hookup_trip_from_dive(dive); - if (dive->selected) { - for_each_dive(idx, dive) { - if (!dive->selected) - continue; - add_dive_to_trip(dive, trip); - } - } - trip->expanded = 1; - dive_list_update_dives(); - mark_divelist_changed(TRUE); -} - -static void remove_from_trip_cb(GtkWidget *menuitem, GtkTreePath *path) -{ - struct dive *dive; - int idx; - - idx = get_path_index(path); - if (idx < 0) - return; - dive = get_dive(idx); - - if (dive->selected) { - /* remove all the selected dives */ - for_each_dive(idx, dive) { - if (!dive->selected) - continue; - remove_dive_from_trip(dive); - } - } else { - /* just remove the dive the mouse pointer is on */ - remove_dive_from_trip(dive); - } - dive_list_update_dives(); - mark_divelist_changed(TRUE); -} - -static void remove_trip(GtkTreePath *trippath) -{ - int idx, i; - dive_trip_t *trip; - struct dive *dive; - - idx = get_path_index(trippath); - trip = find_trip_by_idx(idx); - if (!trip) - return; - - for_each_dive(i, dive) { - if (dive->divetrip != trip) - continue; - remove_dive_from_trip(dive); - } - - dive_list_update_dives(); - -#ifdef DEBUG_TRIP - dump_trip_list(); -#endif -} - -static void remove_trip_cb(GtkWidget *menuitem, GtkTreePath *trippath) -{ - int success; - GtkWidget *dialog; - - dialog = gtk_dialog_new_with_buttons(_("Remove Trip"), - GTK_WINDOW(main_window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, - NULL); - - gtk_widget_show_all(dialog); - success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; - gtk_widget_destroy(dialog); - if (!success) - return; - - remove_trip(trippath); - mark_divelist_changed(TRUE); -} - -static void merge_trips_cb(GtkWidget *menuitem, GtkTreePath *trippath) -{ - GtkTreePath *prevpath; - GtkTreeIter thistripiter, prevtripiter; - GtkTreeModel *tm = MODEL(dive_list); - dive_trip_t *thistrip, *prevtrip; - timestamp_t when; - - /* this only gets called when we are on a trip and there is another trip right before */ - prevpath = gtk_tree_path_copy(trippath); - gtk_tree_path_prev(prevpath); - gtk_tree_model_get_iter(tm, &thistripiter, trippath); - gtk_tree_model_get(tm, &thistripiter, DIVE_DATE, &when, -1); - thistrip = find_matching_trip(when); - gtk_tree_model_get_iter(tm, &prevtripiter, prevpath); - gtk_tree_model_get(tm, &prevtripiter, DIVE_DATE, &when, -1); - prevtrip = find_matching_trip(when); - /* move dives from trip */ - assert(thistrip != prevtrip); - while (thistrip->dives) - add_dive_to_trip(thistrip->dives, prevtrip); - dive_list_update_dives(); - mark_divelist_changed(TRUE); -} - -/* this implements the mechanics of removing the dive from the table, - * but doesn't deal with updating dive trips, etc */ -void delete_single_dive(int idx) -{ - int i; - struct dive *dive = get_dive(idx); - if (!dive) - return; /* this should never happen */ - remove_dive_from_trip(dive); - for (i = idx; i < dive_table.nr - 1; i++) - dive_table.dives[i] = dive_table.dives[i+1]; - dive_table.dives[--dive_table.nr] = NULL; - if (dive->selected) - amount_selected--; - /* free all allocations */ - free(dive->dc.sample); - if (dive->location) - free((void *)dive->location); - if (dive->notes) - free((void *)dive->notes); - if (dive->divemaster) - free((void *)dive->divemaster); - if (dive->buddy) - free((void *)dive->buddy); - if (dive->suit) - free((void *)dive->suit); - free(dive); -} - -void add_single_dive(int idx, struct dive *dive) -{ - int i; - dive_table.nr++; - if (dive->selected) - amount_selected++; - for (i = idx; i < dive_table.nr ; i++) { - struct dive *tmp = dive_table.dives[i]; - dive_table.dives[i] = dive; - dive = tmp; - } -} - -static gboolean restore_node_state(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) -{ - int idx; - struct dive *dive; - dive_trip_t *trip; - GtkTreeView *tree_view = GTK_TREE_VIEW(dive_list.tree_view); - GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view); - - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); - if (idx < 0) { - trip = find_trip_by_idx(idx); - if (trip && trip->expanded) - gtk_tree_view_expand_row(tree_view, path, FALSE); - if (trip && trip->selected) - gtk_tree_selection_select_iter(selection, iter); - } else { - dive = get_dive(idx); - if (dive && dive->selected) - gtk_tree_selection_select_iter(selection, iter); - } - /* continue foreach */ - return FALSE; -} - -/* restore expanded and selected state */ -static void restore_tree_state(void) -{ - gtk_tree_model_foreach(MODEL(dive_list), restore_node_state, NULL); -} - -/* called when multiple dives are selected and one of these is right-clicked for delete */ -static void delete_selected_dives_cb(GtkWidget *menuitem, GtkTreePath *path) -{ - int i; - struct dive *dive; - int success; - GtkWidget *dialog; - char *dialog_title; - - if (!amount_selected) - return; - if (amount_selected == 1) - dialog_title = _("Delete dive"); - else - dialog_title = _("Delete dives"); - - dialog = gtk_dialog_new_with_buttons(dialog_title, - GTK_WINDOW(main_window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, - NULL); - - gtk_widget_show_all(dialog); - success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; - gtk_widget_destroy(dialog); - if (!success) - return; - - /* walk the dive list in chronological order */ - for (i = 0; i < dive_table.nr; i++) { - dive = get_dive(i); - if (!dive) - continue; - if (!dive->selected) - continue; - /* now remove the dive from the table and free it. also move the iterator back, - * so that we don't skip a dive */ - delete_single_dive(i); - i--; - } - dive_list_update_dives(); - - /* if no dives are selected at this point clear the display widgets */ - if (!amount_selected) { - selected_dive = 0; - process_selected_dives(); - clear_stats_widgets(); - clear_equipment_widgets(); - show_dive_info(NULL); - } - mark_divelist_changed(TRUE); -} - -/* this gets called with path pointing to a dive, either in the top level - * or as part of a trip */ -static void delete_dive_cb(GtkWidget *menuitem, GtkTreePath *path) -{ - int idx; - GtkTreeIter iter; - int success; - GtkWidget *dialog; - - dialog = gtk_dialog_new_with_buttons(_("Delete dive"), - GTK_WINDOW(main_window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, - NULL); - - gtk_widget_show_all(dialog); - success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; - gtk_widget_destroy(dialog); - if (!success) - return; - - if (!gtk_tree_model_get_iter(MODEL(dive_list), &iter, path)) - return; - gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); - delete_single_dive(idx); - dive_list_update_dives(); - mark_divelist_changed(TRUE); -} - -#if defined(LIBZIP) && defined(XSLT) -static void export_selected_dives_cb(GtkWidget *menuitem, GtkTreePath *path) -{ - int i; - struct dive *dive; - FILE *f; - char filename[PATH_MAX], *tempfile; - size_t streamsize; - char *membuf; - xmlDoc *doc; - xsltStylesheetPtr xslt = NULL; - xmlDoc *transformed; - struct zip_source *s[dive_table.nr]; - struct zip *zip; - const gchar *tmpdir = g_get_tmp_dir(); - - /* - * Creating a temporary .DLD file to be eventually uploaded to - * divelogs.de. I wonder if this could be done in-memory. - */ - tempfile = g_build_filename(tmpdir, "export.DLD-XXXXXX", NULL); - int fd = g_mkstemp(tempfile); - if (fd != -1) - close(fd); - zip = zip_open(tempfile, ZIP_CREATE, NULL); - - if (!zip) - return; - - if (!amount_selected) - return; - - /* walk the dive list in chronological order */ - for (i = 0; i < dive_table.nr; i++) { - - dive = get_dive(i); - if (!dive) - continue; - if (!dive->selected) - continue; - - f = tmpfile(); - if (!f) - return; - save_dive(f, dive); - fseek(f, 0, SEEK_END); - streamsize = ftell(f); - rewind(f); - membuf = malloc(streamsize + 1); - if (!membuf || !fread(membuf, streamsize, 1, f)) - return; - membuf[streamsize] = 0; - fclose(f); - - /* - * Parse the memory buffer into XML document and - * transform it to divelogs.de format, finally dumping - * the XML into a character buffer. - */ - doc = xmlReadMemory(membuf, strlen(membuf), "divelog", NULL, 0); - if (!doc) - continue; - - free((void *)membuf); - xslt = get_stylesheet("divelogs-export.xslt"); - if (!xslt) - return; - transformed = xsltApplyStylesheet(xslt, doc, NULL); - xsltFreeStylesheet(xslt); - xmlDocDumpMemory(transformed, (xmlChar **) &membuf, (int *)&streamsize); - xmlFreeDoc(doc); - xmlFreeDoc(transformed); - - /* - * Save the XML document into a zip file. - */ - snprintf(filename, PATH_MAX, "%d.xml", i + 1); - s[i] = zip_source_buffer(zip, membuf, streamsize, 1); - if (s[i]) { - int64_t ret = zip_add(zip, filename, s[i]); - if (ret == -1) - fprintf(stderr, "failed to include dive %d\n", i); - } - } - zip_close(zip); - if (divelogde_upload(tempfile)) - g_unlink(tempfile); - else - fprintf(stderr,"upload of %s failed\n", tempfile); - g_free(tempfile); -} -#endif - -#if defined(XSLT) -static void export_dives_uddf(const gboolean selected) -{ - FILE *f; - char *filename = NULL; - size_t streamsize; - char *membuf; - xmlDoc *doc; - xsltStylesheetPtr xslt = NULL; - xmlDoc *transformed; - GtkWidget *dialog; - - dialog = gtk_file_chooser_dialog_new(_("Export As UDDF File"), - GTK_WINDOW(main_window), - GTK_FILE_CHOOSER_ACTION_SAVE, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, - NULL); - gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); - - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); - } - gtk_widget_destroy(dialog); - - if (!filename) - return; - - /* Save XML to file and convert it into a memory buffer */ - save_dives_logic(filename, selected); - f = fopen(filename, "r"); - fseek(f, 0, SEEK_END); - streamsize = ftell(f); - rewind(f); - - membuf = malloc(streamsize + 1); - if (!membuf || !fread(membuf, streamsize, 1, f)) { - fprintf(stderr, "Failed to read memory buffer\n"); - return; - } - membuf[streamsize] = 0; - fclose(f); - g_unlink(filename); - - /* - * Parse the memory buffer into XML document and - * transform it to UDDF format, finally dumping - * the XML into a character buffer. - */ - doc = xmlReadMemory(membuf, strlen(membuf), "divelog", NULL, 0); - if (!doc) { - fprintf(stderr, "Failed to read XML memory\n"); - return; - } - free((void *)membuf); - - /* Convert to UDDF format */ - xslt = get_stylesheet("uddf-export.xslt"); - if (!xslt) { - fprintf(stderr, "Failed to open UDDF conversion stylesheet\n"); - return; - } - transformed = xsltApplyStylesheet(xslt, doc, NULL); - xsltFreeStylesheet(xslt); - xmlFreeDoc(doc); - - /* Write the transformed XML to file */ - f = g_fopen(filename, "w"); - xmlDocFormatDump(f, transformed, 1); - xmlFreeDoc(transformed); - - fclose(f); - g_free(filename); -} - -static void export_selected_dives_uddf_cb(GtkWidget *menuitem, GtkTreePath *path) -{ - export_dives_uddf(TRUE); -} - -void export_all_dives_uddf_cb() -{ - export_dives_uddf(FALSE); -} -#endif - -static void merge_dive_index(int i, struct dive *a) -{ - struct dive *b = get_dive(i+1); - struct dive *res; - - res = merge_dives(a, b, b->when - a->when, FALSE); - if (!res) - return; - - add_single_dive(i, res); - delete_single_dive(i+1); - delete_single_dive(i+1); - - dive_list_update_dives(); - mark_divelist_changed(TRUE); -} - -static void merge_dives_cb(GtkWidget *menuitem, void *unused) -{ - int i; - struct dive *dive; - - for_each_dive(i, dive) { - if (dive->selected) { - merge_dive_index(i, dive); - return; - } - } -} - -/* Called if there are exactly two selected dives and the dive at idx is one of them */ -static void add_dive_merge_label(int idx, GtkMenuShell *menu) -{ - struct dive *a, *b; - GtkWidget *menuitem; - - /* The other selected dive must be next to it.. */ - a = get_dive(idx); - b = get_dive(idx+1); - if (!b || !b->selected) { - b = a; - a = get_dive(idx-1); - if (!a || !a->selected) - return; - } - - /* .. and they had better be in the same dive trip */ - if (a->divetrip != b->divetrip) - return; - - /* .. and if the surface interval is excessive, you must be kidding us */ - if (b->when > a->when + a->duration.seconds + 30*60) - return; - - /* If so, we can add a "merge dive" menu entry */ - menuitem = gtk_menu_item_new_with_label(_("Merge dives")); - g_signal_connect(menuitem, "activate", G_CALLBACK(merge_dives_cb), NULL); - gtk_menu_shell_append(menu, menuitem); -} - -static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button, GdkEventButton *event) -{ - GtkWidget *menu, *menuitem, *image; - char editplurallabel[] = N_("Edit dives"); - char editsinglelabel[] = N_("Edit dive"); - char *editlabel; - char deleteplurallabel[] = N_("Delete dives"); - char deletesinglelabel[] = N_("Delete dive"); - char *deletelabel; -#if defined(XSLT) - char exportuddflabel[] = N_("Export dive(s) to UDDF"); -#endif -#if defined(LIBZIP) && defined(XSLT) - char exportlabel[] = N_("Export dive(s)"); -#endif - GtkTreePath *path, *prevpath, *nextpath; - GtkTreeIter iter, previter, nextiter; - int idx, previdx, nextidx; - struct dive *dive; - - if (!event || !gtk_tree_view_get_path_at_pos(tree_view, event->x, event->y, &path, NULL, NULL, NULL)) - return; - gtk_tree_model_get_iter(MODEL(dive_list), &iter, path); - gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); - - menu = gtk_menu_new(); - menuitem = gtk_image_menu_item_new_with_label(_("Add dive")); - image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU); - gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); - g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - - if (idx < 0) { - /* mouse pointer is on a trip summary entry */ - menuitem = gtk_menu_item_new_with_label(_("Edit Trip Summary")); - g_signal_connect(menuitem, "activate", G_CALLBACK(edit_trip_cb), path); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - prevpath = gtk_tree_path_copy(path); - if (gtk_tree_path_prev(prevpath) && - gtk_tree_model_get_iter(MODEL(dive_list), &previter, prevpath)) { - gtk_tree_model_get(MODEL(dive_list), &previter, DIVE_INDEX, &previdx, -1); - if (previdx < 0) { - menuitem = gtk_menu_item_new_with_label(_("Merge trip with trip above")); - g_signal_connect(menuitem, "activate", G_CALLBACK(merge_trips_cb), path); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - } - } - nextpath = gtk_tree_path_copy(path); - gtk_tree_path_next(nextpath); - if (gtk_tree_model_get_iter(MODEL(dive_list), &nextiter, nextpath)) { - gtk_tree_model_get(MODEL(dive_list), &nextiter, DIVE_INDEX, &nextidx, -1); - if (nextidx < 0) { - menuitem = gtk_menu_item_new_with_label(_("Merge trip with trip below")); - g_signal_connect(menuitem, "activate", G_CALLBACK(merge_trips_cb), nextpath); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - } - } - menuitem = gtk_menu_item_new_with_label(_("Remove Trip")); - g_signal_connect(menuitem, "activate", G_CALLBACK(remove_trip_cb), path); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - } else { - dive = get_dive(idx); - /* if we right click on selected dive(s), edit or delete those */ - if (dive->selected) { - if (amount_selected == 1) { - deletelabel = _(deletesinglelabel); - editlabel = _(editsinglelabel); - menuitem = gtk_menu_item_new_with_label(_("Edit dive date/time")); - g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_when_cb), dive); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - } else { - deletelabel = _(deleteplurallabel); - editlabel = _(editplurallabel); - } - menuitem = gtk_menu_item_new_with_label(_("Save as")); - g_signal_connect(menuitem, "activate", G_CALLBACK(save_as_cb), dive); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - - menuitem = gtk_menu_item_new_with_label(deletelabel); - g_signal_connect(menuitem, "activate", G_CALLBACK(delete_selected_dives_cb), path); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - -#if defined(LIBZIP) && defined(XSLT) - menuitem = gtk_menu_item_new_with_label(exportlabel); - g_signal_connect(menuitem, "activate", G_CALLBACK(export_selected_dives_cb), path); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); -#endif - -#if defined(XSLT) - menuitem = gtk_menu_item_new_with_label(exportuddflabel); - g_signal_connect(menuitem, "activate", G_CALLBACK(export_selected_dives_uddf_cb), path); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); -#endif - - menuitem = gtk_menu_item_new_with_label(editlabel); - g_signal_connect(menuitem, "activate", G_CALLBACK(edit_selected_dives_cb), NULL); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - - /* Two contiguous selected dives? */ - if (amount_selected == 2) - add_dive_merge_label(idx, GTK_MENU_SHELL(menu)); - } else { - menuitem = gtk_menu_item_new_with_label(_("Edit dive date/time")); - g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_when_cb), dive); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - - deletelabel = _(deletesinglelabel); - menuitem = gtk_menu_item_new_with_label(deletelabel); - g_signal_connect(menuitem, "activate", G_CALLBACK(delete_dive_cb), path); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - - editlabel = _(editsinglelabel); - menuitem = gtk_menu_item_new_with_label(editlabel); - g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_from_path_cb), path); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - } -#if HAVE_OSM_GPS_MAP - /* Only offer to show on map if it has a location. */ - if (dive_has_gps_location(dive)) { - menuitem = gtk_menu_item_new_with_label(_("Show in map")); - g_signal_connect(menuitem, "activate", G_CALLBACK(show_gps_location_cb), dive); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - } -#endif - /* only offer trip editing options when we are displaying the tree model */ - if (dive_list.model == dive_list.treemodel) { - int depth = gtk_tree_path_get_depth(path); - int *indices = gtk_tree_path_get_indices(path); - /* top level dive or child dive that is not the first child */ - if (depth == 1 || indices[1] > 0) { - menuitem = gtk_menu_item_new_with_label(_("Create new trip above")); - g_signal_connect(menuitem, "activate", G_CALLBACK(insert_trip_before_cb), path); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - } - prevpath = gtk_tree_path_copy(path); - /* top level dive with a trip right before it */ - if (depth == 1 && - gtk_tree_path_prev(prevpath) && - gtk_tree_model_get_iter(MODEL(dive_list), &previter, prevpath) && - gtk_tree_model_iter_n_children(model, &previter)) { - menuitem = gtk_menu_item_new_with_label(_("Add to trip above")); - g_signal_connect(menuitem, "activate", G_CALLBACK(merge_dive_into_trip_above_cb), path); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - } - if (DIVE_IN_TRIP(dive)) { - if (dive->selected && amount_selected > 1) - menuitem = gtk_menu_item_new_with_label(_("Remove selected dives from trip")); - else - menuitem = gtk_menu_item_new_with_label(_("Remove dive from trip")); - g_signal_connect(menuitem, "activate", G_CALLBACK(remove_from_trip_cb), path); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - } - } - } - menuitem = gtk_menu_item_new_with_label(_("Expand all")); - g_signal_connect(menuitem, "activate", G_CALLBACK(expand_all_cb), tree_view); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - - menuitem = gtk_menu_item_new_with_label(_("Collapse all")); - g_signal_connect(menuitem, "activate", G_CALLBACK(collapse_all_cb), tree_view); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - - gtk_widget_show_all(menu); - - gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, - button, gtk_get_current_event_time()); -} - -static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata) -{ - popup_divelist_menu(tree_view, MODEL(dive_list), 0, NULL); -} - -static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata) -{ - /* Right-click? Bring up the menu */ - if (event->type == GDK_BUTTON_PRESS && event->button == 3) { - popup_divelist_menu(GTK_TREE_VIEW(treeview), MODEL(dive_list), 3, event); - return TRUE; - } - return FALSE; -} - -/* make sure 'path' is shown in the divelist widget; since set_cursor changes the - * selection to be only 'path' we need to let our selection handling callbacks know - * that we didn't really mean this */ -static void scroll_to_path(GtkTreePath *path) -{ - GtkTreeSelection *selection; - - gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path); - gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(dive_list.tree_view), path, NULL, FALSE, 0, 0); - in_set_cursor = TRUE; - gtk_tree_view_set_cursor(GTK_TREE_VIEW(dive_list.tree_view), path, NULL, FALSE); - in_set_cursor = FALSE; - selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); - gtk_tree_model_foreach(MODEL(dive_list), set_selected, selection); - -} - -/* we need to have a temporary copy of the selected dives while - switching model as the selection_cb function keeps getting called - when gtk_tree_selection_select_path is called. We also need to - keep copies of the sort order so we can restore that as well after - switching models. */ -static gboolean second_call = FALSE; -static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, }; -static int lastcol = DIVE_NR; - -/* Check if this dive was selected previously and select it again in the new model; - * This is used after we switch models to maintain consistent selections. - * We always return FALSE to iterate through all dives */ -static gboolean set_selected(GtkTreeModel *model, GtkTreePath *path, - GtkTreeIter *iter, gpointer data) -{ - GtkTreeSelection *selection = GTK_TREE_SELECTION(data); - int idx, selected; - struct dive *dive; - - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); - if (idx < 0) { - /* this is a trip - restore its state */ - dive_trip_t *trip = find_trip_by_idx(idx); - if (trip && trip->expanded) - gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path); - if (trip && trip->selected) - gtk_tree_selection_select_path(selection, path); - } else { - dive = get_dive(idx); - selected = dive && dive->selected; - if (selected) { - gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path); - gtk_tree_selection_select_path(selection, path); - } - } - return FALSE; -} - -static gboolean scroll_to_this(GtkTreeModel *model, GtkTreePath *path, - GtkTreeIter *iter, gpointer data) -{ - int idx; - struct dive *dive; - - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); - dive = get_dive(idx); - if (dive == current_dive) { - scroll_to_path(path); - return TRUE; - } - return FALSE; -} - -static void scroll_to_current(GtkTreeModel *model) -{ - if (current_dive) - gtk_tree_model_foreach(model, scroll_to_this, current_dive); -} - -static void update_column_and_order(int colid) -{ - /* Careful: the index into treecolumns is off by one as we don't have a - tree_view column for DIVE_INDEX */ - GtkTreeViewColumn **treecolumns = &dive_list.nr; - - /* this will trigger a second call into sort_column_change_cb, - so make sure we don't start an infinite recursion... */ - second_call = TRUE; - gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, sortorder[colid]); - gtk_tree_view_column_set_sort_order(treecolumns[colid - 1], sortorder[colid]); - second_call = FALSE; - scroll_to_current(GTK_TREE_MODEL(dive_list.model)); -} - -/* If the sort column is nr (default), show the tree model. - For every other sort column only show the list model. - If the model changed, inform the new model of the chosen sort column and make - sure the same dives are still selected. - - The challenge with this function is that once we change the model - we also need to change the sort column again (as it was changed in - the other model) and that causes this function to be called - recursively - so we need to catch that. -*/ -static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data) -{ - int colid; - GtkSortType order; - GtkTreeStore *currentmodel = dive_list.model; - - gtk_widget_grab_focus(dive_list.tree_view); - if (second_call) - return; - - gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order); - if (colid == lastcol) { - /* we just changed sort order */ - sortorder[colid] = order; - return; - } else { - lastcol = colid; - } - if (colid == DIVE_NR) - dive_list.model = dive_list.treemodel; - else - dive_list.model = dive_list.listmodel; - if (dive_list.model != currentmodel) { - GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); - - gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), MODEL(dive_list)); - update_column_and_order(colid); - gtk_tree_model_foreach(MODEL(dive_list), set_selected, selection); - } else { - if (order != sortorder[colid]) { - update_column_and_order(colid); - } - } -} - -static void select_dive(int idx) -{ - struct dive *dive = get_dive(idx); - if (dive && !dive->selected) { - dive->selected = 1; - amount_selected++; - selected_dive = idx; - } -} - -static void deselect_dive(int idx) +void deselect_dive(int idx) { struct dive *dive = get_dive(idx); if (dive && dive->selected) { @@ -2602,266 +744,14 @@ static void deselect_dive(int idx) } } -static gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model, - GtkTreePath *path, gboolean was_selected, gpointer userdata) -{ - int idx; - GtkTreeIter iter; - - if (!was_selected || in_set_cursor) - return TRUE; - gtk_tree_model_get_iter(model, &iter, path); - gtk_tree_model_get(model, &iter, DIVE_INDEX, &idx, -1); - if (idx < 0) { - int i; - struct dive *dive; - dive_trip_t *trip = find_trip_by_idx(idx); - if (!trip) - return TRUE; - - trip->selected = 0; - /* If this is expanded, let the gtk selection happen for each dive under it */ - if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), path)) - return TRUE; - /* Otherwise, consider each dive under it deselected */ - for_each_dive(i, dive) { - if (dive->divetrip == trip) - deselect_dive(i); - } - } else { - deselect_dive(idx); - } - return TRUE; -} - -/* This gets called for each selected entry after a selection has changed */ -static void entry_selected(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) -{ - int idx; - - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); - if (idx < 0) { - int i; - struct dive *dive; - dive_trip_t *trip = find_trip_by_idx(idx); - - if (!trip) - return; - trip->selected = 1; - - /* If this is expanded, let the gtk selection happen for each dive under it */ - if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), path)) { - trip->fixup = 1; - return; - } - - /* Otherwise, consider each dive under it selected */ - for_each_dive(i, dive) { - if (dive->divetrip == trip) - select_dive(i); - } - trip->fixup = 0; - } else { - select_dive(idx); - } -} - -static void update_gtk_selection(GtkTreeSelection *selection, GtkTreeModel *model) -{ - GtkTreeIter iter; - - if (!gtk_tree_model_get_iter_first(model, &iter)) - return; - do { - GtkTreeIter child; - - if (!gtk_tree_model_iter_children(model, &child, &iter)) - continue; - - do { - int idx; - struct dive *dive; - dive_trip_t *trip; - - gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1); - dive = get_dive(idx); - if (!dive || !dive->selected) - break; - trip = dive->divetrip; - if (!trip) - break; - gtk_tree_selection_select_iter(selection, &child); - } while (gtk_tree_model_iter_next(model, &child)); - } while (gtk_tree_model_iter_next(model, &iter)); -} - -/* this is called when gtk thinks that the selection has changed */ -static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model) -{ - int i, fixup; - struct dive *dive; - - gtk_tree_selection_selected_foreach(selection, entry_selected, model); - - /* - * Go through all the dives, if there is a trip that is selected but no - * dives under it are selected, force-select all the dives - */ - - /* First, clear "fixup" for any trip that has selected dives */ - for_each_dive(i, dive) { - dive_trip_t *trip = dive->divetrip; - if (!trip || !trip->fixup) - continue; - if (dive->selected || !trip->selected) - trip->fixup = 0; - } - - /* - * Ok, not fixup is only set for trips that are selected - * but have no selected dives in them. Select all dives - * for such trips. - */ - fixup = 0; - for_each_dive(i, dive) { - dive_trip_t *trip = dive->divetrip; - if (!trip || !trip->fixup) - continue; - fixup = 1; - select_dive(i); - } - - /* - * Ok, we did a forced selection of dives, now we need to update the gtk - * view of what is selected too.. - */ - if (fixup) - update_gtk_selection(selection, model); - -#if DEBUG_SELECTION_TRACKING - dump_selection(); -#endif - - process_selected_dives(); - repaint_dive(); -} - -GtkWidget *dive_list_create(void) -{ - GtkTreeSelection *selection; - - dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS, - G_TYPE_INT, /* index */ - G_TYPE_INT, /* nr */ - G_TYPE_INT64, /* Date */ - G_TYPE_INT, /* Star rating */ - G_TYPE_INT, /* Depth */ - G_TYPE_INT, /* Duration */ - G_TYPE_INT, /* Temperature */ - G_TYPE_INT, /* Total weight */ - G_TYPE_STRING, /* Suit */ - G_TYPE_STRING, /* Cylinder */ - G_TYPE_INT, /* Nitrox */ - G_TYPE_INT, /* SAC */ - G_TYPE_INT, /* OTU */ - G_TYPE_INT, /* MAXCNS */ - G_TYPE_STRING, /* Location */ - GDK_TYPE_PIXBUF /* GPS icon */ - ); - dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS, - G_TYPE_INT, /* index */ - G_TYPE_INT, /* nr */ - G_TYPE_INT64, /* Date */ - G_TYPE_INT, /* Star rating */ - G_TYPE_INT, /* Depth */ - G_TYPE_INT, /* Duration */ - G_TYPE_INT, /* Temperature */ - G_TYPE_INT, /* Total weight */ - G_TYPE_STRING, /* Suit */ - G_TYPE_STRING, /* Cylinder */ - G_TYPE_INT, /* Nitrox */ - G_TYPE_INT, /* SAC */ - G_TYPE_INT, /* OTU */ - G_TYPE_INT, /* MAXCNS */ - G_TYPE_STRING, /* Location */ - GDK_TYPE_PIXBUF /* GPS icon */ - ); - dive_list.model = dive_list.treemodel; - dive_list.tree_view = gtk_tree_view_new_with_model(TREEMODEL(dive_list)); - set_divelist_font(prefs.divelist_font); - - selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); - - gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE); - gtk_widget_set_size_request(dive_list.tree_view, 200, 200); - - /* check if utf8 stars are available as a default OS feature */ - if (!subsurface_os_feature_available(UTF8_FONT_WITH_STARS)) - dl_column[3].header = "*"; - - dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR); - dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE); - dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING); - dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH); - dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION); - dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE); - dive_list.totalweight = divelist_column(&dive_list, dl_column + DIVE_TOTALWEIGHT); - dive_list.suit = divelist_column(&dive_list, dl_column + DIVE_SUIT); - dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER); - dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX); - dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC); - dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU); - dive_list.maxcns = divelist_column(&dive_list, dl_column + DIVE_MAXCNS); - dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION); - gtk_tree_view_column_set_sort_indicator(dive_list.nr, TRUE); - gtk_tree_view_column_set_sort_order(dive_list.nr, GTK_SORT_DESCENDING); - /* now add the GPS icon to the location column */ - tree_view_column_add_pixbuf(dive_list.tree_view, gpsicon_data_func, dive_list.location); - - fill_dive_list(); - - g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE, - "search-column", DIVE_LOCATION, - "rules-hint", TRUE, - NULL); - - g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL); - g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL); - g_signal_connect(dive_list.tree_view, "row-expanded", G_CALLBACK(row_expanded_cb), NULL); - g_signal_connect(dive_list.tree_view, "row-collapsed", G_CALLBACK(row_collapsed_cb), NULL); - g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL); - g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL); - g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), dive_list.model); - g_signal_connect(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL); - g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL); - - gtk_tree_selection_set_select_function(selection, modify_selection_cb, NULL, NULL); - - dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget), - GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view); - - dive_list.changed = 0; - - return dive_list.container_widget; -} - -void dive_list_destroy(void) -{ - gtk_widget_destroy(dive_list.tree_view); - g_object_unref(dive_list.treemodel); - g_object_unref(dive_list.listmodel); -} - void mark_divelist_changed(int changed) { - dive_list.changed = changed; + dive_list_changed = changed; } int unsaved_changes() { - return dive_list.changed; + return dive_list_changed; } void remove_autogen_trips() @@ -2877,142 +767,3 @@ void remove_autogen_trips() } } -struct iteridx { - int idx; - GtkTreeIter *iter; -}; - -static gboolean iter_has_idx(GtkTreeModel *model, GtkTreePath *path, - GtkTreeIter *iter, gpointer _data) -{ - struct iteridx *iteridx = _data; - int idx; - /* Get the dive number */ - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); - if (idx == iteridx->idx) { - iteridx->iter = gtk_tree_iter_copy(iter); - return TRUE; /* end foreach */ - } - return FALSE; -} - -static GtkTreeIter *get_iter_from_idx(int idx) -{ - struct iteridx iteridx = {idx, }; - gtk_tree_model_foreach(MODEL(dive_list), iter_has_idx, &iteridx); - return iteridx.iter; -} - -static void scroll_to_selected(GtkTreeIter *iter) -{ - GtkTreePath *treepath; - treepath = gtk_tree_model_get_path(MODEL(dive_list), iter); - scroll_to_path(treepath); - gtk_tree_path_free(treepath); -} - -static void go_to_iter(GtkTreeSelection *selection, GtkTreeIter *iter) -{ - gtk_tree_selection_unselect_all(selection); - gtk_tree_selection_select_iter(selection, iter); - scroll_to_selected(iter); -} - -void show_and_select_dive(struct dive *dive) -{ - GtkTreeSelection *selection; - GtkTreeIter *iter; - struct dive *odive; - int i, divenr; - - divenr = get_divenr(dive); - if (divenr < 0) - /* we failed to find the dive */ - return; - iter = get_iter_from_idx(divenr); - selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); - for_each_dive(i, odive) - odive->selected = FALSE; - amount_selected = 1; - selected_dive = divenr; - dive->selected = TRUE; - go_to_iter(selection, iter); - gtk_tree_iter_free(iter); -} - -void select_next_dive(void) -{ - GtkTreeIter *nextiter, *parent = NULL; - GtkTreeIter *iter = get_iter_from_idx(selected_dive); - GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); - int idx; - - if (!iter) - return; - nextiter = gtk_tree_iter_copy(iter); - if (!gtk_tree_model_iter_next(MODEL(dive_list), nextiter)) { - if (!gtk_tree_model_iter_parent(MODEL(dive_list), nextiter, iter)) { - /* we're at the last top level node */ - goto free_iter; - } - if (!gtk_tree_model_iter_next(MODEL(dive_list), nextiter)) { - /* last trip */ - goto free_iter; - } - } - gtk_tree_model_get(MODEL(dive_list), nextiter, DIVE_INDEX, &idx, -1); - if (idx < 0) { - /* need the first child */ - parent = gtk_tree_iter_copy(nextiter); - if (! gtk_tree_model_iter_children(MODEL(dive_list), nextiter, parent)) - goto free_iter; - } - go_to_iter(selection, nextiter); -free_iter: - if (nextiter) - gtk_tree_iter_free(nextiter); - if (parent) - gtk_tree_iter_free(parent); - gtk_tree_iter_free(iter); -} - -void select_prev_dive(void) -{ - GtkTreeIter previter, *parent = NULL; - GtkTreeIter *iter = get_iter_from_idx(selected_dive); - GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); - GtkTreePath *treepath; - int idx; - - if (!iter) - return; - treepath = gtk_tree_model_get_path(MODEL(dive_list), iter); - if (!gtk_tree_path_prev(treepath)) { - if (!gtk_tree_model_iter_parent(MODEL(dive_list), &previter, iter)) - /* we're at the last top level node */ - goto free_iter; - gtk_tree_path_free(treepath); - treepath = gtk_tree_model_get_path(MODEL(dive_list), &previter); - if (!gtk_tree_path_prev(treepath)) - /* first trip */ - goto free_iter; - if (!gtk_tree_model_get_iter(MODEL(dive_list), &previter, treepath)) - goto free_iter; - } - if (!gtk_tree_model_get_iter(MODEL(dive_list), &previter, treepath)) - goto free_iter; - gtk_tree_model_get(MODEL(dive_list), &previter, DIVE_INDEX, &idx, -1); - if (idx < 0) { - /* need the last child */ - parent = gtk_tree_iter_copy(&previter); - if (! gtk_tree_model_iter_nth_child(MODEL(dive_list), &previter, parent, - gtk_tree_model_iter_n_children(MODEL(dive_list), parent) - 1)) - goto free_iter; - } - go_to_iter(selection, &previter); -free_iter: - gtk_tree_path_free(treepath); - if (parent) - gtk_tree_iter_free(parent); - gtk_tree_iter_free(iter); -} diff --git a/divelist.h b/divelist.h index c9ec973f7..6a0b98bc9 100644 --- a/divelist.h +++ b/divelist.h @@ -4,7 +4,6 @@ #ifdef __cplusplus extern "C" { #endif - struct dive; extern void dive_list_update_dives(void); @@ -21,6 +20,27 @@ extern void show_and_select_dive(struct dive *dive); extern double init_decompression(struct dive * dive); extern void export_all_dives_uddf_cb(); +/* divelist core logic functions */ +extern dive_trip_t *find_trip_by_idx(int idx); +extern int trip_has_selected_dives(dive_trip_t *trip); +extern void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p); +extern int get_divenr(struct dive *dive); +extern void get_location(struct dive *dive, char **str); +extern void get_cylinder(struct dive *dive, char **str); +extern void get_suit(struct dive *dive, char **str); +extern dive_trip_t *find_matching_trip(timestamp_t when); +extern void remove_dive_from_trip(struct dive *dive); +extern dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive); +extern void autogroup_dives(void); +extern void merge_dive_index(int i, struct dive *a); +extern void select_dive(int idx); +extern void deselect_dive(int idx); + +#ifdef DEBUG_TRIP +extern void dump_selection(void); +extern void dump_trip_list(void); +#endif + #ifdef __cplusplus } #endif diff --git a/uemis-downloader.c b/uemis-downloader.c index 9b54b0f3c..c5113d95a 100644 --- a/uemis-downloader.c +++ b/uemis-downloader.c @@ -731,7 +731,7 @@ static void process_raw_buffer(uint32_t deviceid, char *inbuf, char **max_divenr return; } -static char *get_divenr(char *deviceidstr) +static char *uemis_get_divenr(char *deviceidstr) { uint32_t deviceid, maxdiveid = 0; int i; @@ -789,7 +789,7 @@ static char *do_uemis_download(struct argument_block *args) /* if we have an empty divelist or force it, then we start downloading from the * first dive on the Uemis; otherwise check which was the last dive downloaded */ if (!args->force_download && dive_table.nr > 0) - newmax = get_divenr(deviceid); + newmax = uemis_get_divenr(deviceid); else newmax = strdup("0"); start = atoi(newmax); -- cgit v1.2.3-70-g09d2 From ba712c3b5420ed461b8b7a1fdd14849e622ae974 Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Sun, 7 Apr 2013 15:20:43 -0700 Subject: Start creating the Qt UI This is based on several commits from Tomaz - mingled together and mildly extended by Dirk (mostly Makefile hacking). All Qt UI related stuff should eventually move into the qt-ui directory. So the Makefile rules for moc and uic have been adjusted accordingly. The MainWindow class has been moved into its own file in qt-ui (but just with a placeholder, the existing class has simply been ifdef'ed out in qt-gui.cpp for the moment). We still have a couple of Qt things in qt-gui.cpp in the main directory... all this needs to move into the qt-ui directory and be built with separate .h files. Right now we have the one-off Makefile rule to create the qt-gui.moc file from the qt-gui.cpp file. Signed-off-by: Tomaz Canabrava Signed-off-by: Dirk Hohndel --- Makefile | 40 ++- qt-gui.cpp | 7 +- qt-ui/maintab.cpp | 10 + qt-ui/maintab.h | 20 ++ qt-ui/maintab.ui | 810 ++++++++++++++++++++++++++++++++++++++++++++++++ qt-ui/mainwindow.cpp | 11 + qt-ui/mainwindow.h | 24 ++ qt-ui/mainwindow.ui | 297 ++++++++++++++++++ qt-ui/plotareascene.cpp | 0 qt-ui/plotareascene.h | 0 10 files changed, 1204 insertions(+), 15 deletions(-) create mode 100644 qt-ui/maintab.cpp create mode 100644 qt-ui/maintab.h create mode 100644 qt-ui/maintab.ui create mode 100644 qt-ui/mainwindow.cpp create mode 100644 qt-ui/mainwindow.h create mode 100644 qt-ui/mainwindow.ui create mode 100644 qt-ui/plotareascene.cpp create mode 100644 qt-ui/plotareascene.h (limited to 'Makefile') diff --git a/Makefile b/Makefile index aa42db2cf..c14d0b931 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ VERSION=3.0.2 CC=gcc CFLAGS=-Wall -Wno-pointer-sign -g $(CLCFLAGS) -DGSEAL_ENABLE CXX=g++ -CXXFLAGS=-Wall -g $(CLCFLAGS) -DQT_NO_KEYWORDS +CXXFLAGS=-Wall -g $(CLCFLAGS) -fPIC -DQT_NO_KEYWORDS INSTALL=install PKGCONFIG=pkg-config XML2CONFIG=xml2-config @@ -182,10 +182,13 @@ LIBS = $(LIBQT) $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGTK) $(LIBGCONF2) $(LIB MSGLANGS=$(notdir $(wildcard po/*.po)) MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.mo)) + +QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o + OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o divelist-gtk.o deco.o planner.o \ parse-xml.o save-xml.o libdivecomputer.o print.o uemis.o uemis-downloader.o \ qt-gui.o statistics.o file.o cochran.o device.o download-dialog.o prefs.o \ - webservice.o sha1.o $(GPSOBJ) $(OSSUPPORT).o $(RESFILE) + webservice.o sha1.o $(GPSOBJ) $(OSSUPPORT).o $(RESFILE) $(QTOBJS) DEPS = $(wildcard .dep/*.dep) @@ -298,36 +301,45 @@ MOCFLAGS = $(filter -I%, $(CXXFLAGS) $(EXTRA_FLAGS)) $(filter -D%, $(CXXFLAGS) $ %.o: %.c @echo ' CC' $< - @mkdir -p .dep + @mkdir -p .dep .dep/qt-ui @$(CC) $(CFLAGS) $(EXTRA_FLAGS) -MD -MF .dep/$@.dep -c -o $@ $< %.o: %.cpp @echo ' CXX' $< - @mkdir -p .dep + @mkdir -p .dep .dep/qt-ui @$(CXX) $(CXXFLAGS) $(EXTRA_FLAGS) -MD -MF .dep/$@.dep -c -o $@ $< -# This rule is for running the moc on QObject subclasses defined in the .h -# files. -# To activate this rule, add .moc.o to the OBJS variable. -%.moc.cpp: %.h - @echo ' MOC' $< - @$(MOC) $(MOCFLAGS) $< -o $@ - # This rule is for running the moc on QObject subclasses defined in the .cpp # files; remember to #include ".moc" at the end of the .cpp file, or # you'll get linker errors ("undefined vtable for...") # To activate this rule, you need another rule on the .o file, like: # file.o: file.moc + +qt-ui/%.moc: qt-ui/%.h + @echo ' MOC' $< + @$(MOC) -i $(MOCFLAGS) $< -o $@ + +# this is just here for qt-gui.cpp +# should be removed once all the Qt UI code has been moved into qt-ui + %.moc: %.cpp @echo ' MOC' $< @$(MOC) -i $(MOCFLAGS) $< -o $@ -qt-gui.o: main-window.ui.h qt-gui.moc +# This creates the ui headers. +# To activate this rule, you need to add the ui_*.h file to the .o file: +# file.o: ui_file.h -%.ui.h: ui/%.ui +qt-ui/ui_%.h: qt-ui/%.ui @echo ' UIC' $< @$(UIC) $< -o $@ +qt-gui.o: qt-gui.moc + +qt-ui/maintab.o: qt-ui/maintab.moc qt-ui/ui_maintab.h + +qt-ui/mainwindow.o: qt-ui/mainwindow.moc qt-ui/ui_mainwindow.h + share/locale/%.UTF-8/LC_MESSAGES/subsurface.mo: po/%.po po/%.aliases mkdir -p $(dir $@) msgfmt -c -o $@ po/$*.po @@ -355,7 +367,7 @@ doc: clean: rm -f $(OBJS) *~ $(NAME) $(NAME).exe po/*~ po/subsurface-new.pot \ - $(VERSION_FILE) + $(VERSION_FILE) qt-ui/*.moc qt-ui/ui_*.h rm -rf share .dep -include $(DEPS) diff --git a/qt-gui.cpp b/qt-gui.cpp index 285f2082d..e46f4765f 100644 --- a/qt-gui.cpp +++ b/qt-gui.cpp @@ -24,10 +24,11 @@ #include "webservice.h" #include "version.h" #include "libdivecomputer.h" -#include "main-window.ui.h" +#include "qt-ui/mainwindow.h" #include #include + #include #include #include @@ -1737,6 +1738,9 @@ static gboolean notebook_tooltip (GtkWidget *widget, gint x, gint y, } } +#if NEEDS_TO_MOVE_TO_QT_UI +/* this appears to have moved - but it's very different in qt-ui */ + class MainWindow: public QMainWindow, private Ui::MainWindow { Q_OBJECT @@ -1850,6 +1854,7 @@ QStringList MainWindow::fileNameFilters() const ; return filters; } +#endif /* NEEDS_TO_MOVE_TO_QT_UI */ void init_ui(int *argcp, char ***argvp) { diff --git a/qt-ui/maintab.cpp b/qt-ui/maintab.cpp new file mode 100644 index 000000000..4569958c8 --- /dev/null +++ b/qt-ui/maintab.cpp @@ -0,0 +1,10 @@ +#include "maintab.h" +#include "ui_maintab.h" + +MainTab::MainTab(QWidget *parent) : QTabWidget(parent), + ui(new Ui::MainTab()) +{ + ui->setupUi(this); +} + +#include "maintab.moc" diff --git a/qt-ui/maintab.h b/qt-ui/maintab.h new file mode 100644 index 000000000..40904ab12 --- /dev/null +++ b/qt-ui/maintab.h @@ -0,0 +1,20 @@ +#ifndef MAINTAB_H +#define MAINTAB_H + +#include + +namespace Ui +{ + class MainTab; +} + +class MainTab : public QTabWidget +{ + Q_OBJECT +public: + MainTab(QWidget *parent); +private: + Ui::MainTab *ui; +}; + +#endif \ No newline at end of file diff --git a/qt-ui/maintab.ui b/qt-ui/maintab.ui new file mode 100644 index 000000000..a0aec4358 --- /dev/null +++ b/qt-ui/maintab.ui @@ -0,0 +1,810 @@ + + + MainTab + + + + 0 + 0 + 400 + 320 + + + + TabWidget + + + 0 + + + + Dive Info + + + + + + + + + 75 + true + + + + SAC: + + + + + + + + 75 + true + + + + OTU: + + + + + + + + 75 + true + + + + 0²/He: + + + + + + + + 75 + true + + + + Gas Used: + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + + 10 + + + + + + 75 + true + + + + Dive Time: + + + + + + + + 75 + true + + + + Surf Interv: + + + + + + + + 75 + true + + + + Avg Depth: + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + 75 + true + + + + Air Press: + + + + + + + + 75 + true + + + + Max Depth: + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + 75 + true + + + + Air Temp: + + + + + + + TextLabel + + + + + + + + 75 + true + + + + Visibility: + + + + + + + + 75 + true + + + + Water Temp: + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + 75 + true + + + + Date: + + + + + + + + + Qt::Vertical + + + + 20 + 112 + + + + + + + + + Dive Notes + + + + + + Location + + + + + + + + + + Divemaster + + + + + + + Buddy + + + + + + + + + + + + + Rating + + + + + + + Suit + + + + + + + + + + + + + Notes + + + + + + + + + + + Equipment + + + + + + Qt::Vertical + + + + Cylinders + + + + + + + + + + + Edit + + + + + + + Add + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Delete + + + + + + + + + + Weight + + + + + + + + + + + Edit + + + + + + + Add + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Delete + + + + + + + + + + + + + + Stats + + + + + + 10 + + + + + + 75 + true + + + + Max Depth + + + + + + + + 75 + true + + + + Min Depth + + + + + + + + 75 + true + + + + Avg Depth + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + 75 + true + + + + Max SAC + + + + + + + + 75 + true + + + + Min SAC + + + + + + + + 75 + true + + + + Avg SAC + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + + 10 + + + 0 + + + + + + 75 + true + + + + Dives + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + + 75 + true + + + + Max Temp + + + + + + + + 75 + true + + + + Min Temp + + + + + + + + 75 + true + + + + Avg Temp + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + 75 + true + + + + Total Time + + + + + + + + 75 + true + + + + Avg Time + + + + + + + + 75 + true + + + + Longest Dive + + + + + + + + 75 + true + + + + Shortest Dive + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + + Qt::Vertical + + + + 20 + 85 + + + + + + + + + + diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp new file mode 100644 index 000000000..851d1aa1c --- /dev/null +++ b/qt-ui/mainwindow.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include + +MainWindow::MainWindow() : ui(new Ui::MainWindow()) +{ + ui->setupUi(this); +} + +#include "mainwindow.moc" diff --git a/qt-ui/mainwindow.h b/qt-ui/mainwindow.h new file mode 100644 index 000000000..9e15e58b4 --- /dev/null +++ b/qt-ui/mainwindow.h @@ -0,0 +1,24 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +namespace Ui +{ + class MainWindow; +} + +class DiveInfo; +class DiveNotes; +class Stats; +class Equipment; + +class MainWindow : public QMainWindow{ + Q_OBJECT +public: + MainWindow(); +private: + Ui::MainWindow *ui; +}; + +#endif \ No newline at end of file diff --git a/qt-ui/mainwindow.ui b/qt-ui/mainwindow.ui new file mode 100644 index 000000000..b99d10222 --- /dev/null +++ b/qt-ui/mainwindow.ui @@ -0,0 +1,297 @@ + + + MainWindow + + + + 0 + 0 + 763 + 548 + + + + MainWindow + + + + + + + Qt::Vertical + + + + Qt::Horizontal + + + + + + + + + + + Qt::Horizontal + + + + + + + + + 0 + 0 + 763 + 19 + + + + + File + + + + + + + + + + + + + + + + + + + Log + + + + + + + + + + + + + + + View + + + + + + + + + + + Filter + + + + + + Planner + + + + + + Help + + + + + + + + + + + + + + New + + + Ctrl+N + + + + + Open + + + Ctrl+O + + + + + Save + + + Ctrl+S + + + + + Save as + + + Ctrl+Shift+S + + + + + Close + + + Ctrl+W + + + + + Import Files + + + Ctrl+I + + + + + Export UDDF + + + + + Print + + + Ctrl+P + + + + + Preferences + + + + + Quit + + + Ctrl+Q + + + + + Download from Dive computer + + + + + Download from Web Service + + + + + Edit Device Names + + + + + Add Dive + + + + + Renumber + + + + + Auto Group + + + + + Toggle Zoom + + + + + Yearly Statistics + + + + + List + + + + + Profile + + + + + Info + + + + + Tree + + + + + Prev DC + + + + + Next DC + + + + + Select Events + + + + + Input Plan + + + + + About Subsurface + + + + + User Manual + + + + + + MainTab + QWidget +
maintab.h
+ 1 +
+
+ + +
diff --git a/qt-ui/plotareascene.cpp b/qt-ui/plotareascene.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/qt-ui/plotareascene.h b/qt-ui/plotareascene.h new file mode 100644 index 000000000..e69de29bb -- cgit v1.2.3-70-g09d2 From 1d02ba12a3342039c0156f48cb7a5103801aa368 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sun, 7 Apr 2013 20:20:25 -0700 Subject: Separate Gtk related code from core logic: planner Relatively straight forward, just a handful of places where we call show_error() (a UI function) from the logic code. In the process I noticed a few places where error returns weren't dealt with correctly. Added a new planner.h files for the necessary declarations. This should make no difference to functionality. Signed-off-by: Dirk Hohndel --- Makefile | 3 +- dive.h | 2 - planner.c | 458 ++++++-------------------------------------------------------- planner.h | 21 +++ 4 files changed, 67 insertions(+), 417 deletions(-) create mode 100644 planner.h (limited to 'Makefile') diff --git a/Makefile b/Makefile index c14d0b931..28b7e0365 100644 --- a/Makefile +++ b/Makefile @@ -185,7 +185,8 @@ MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.m QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o -OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o divelist-gtk.o deco.o planner.o \ +OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o divelist-gtk.o deco.o \ + planner.o planner-gtk.o \ parse-xml.o save-xml.o libdivecomputer.o print.o uemis.o uemis-downloader.o \ qt-gui.o statistics.o file.o cochran.o device.o download-dialog.o prefs.o \ webservice.o sha1.o $(GPSOBJ) $(OSSUPPORT).o $(RESFILE) $(QTOBJS) diff --git a/dive.h b/dive.h index aff856856..734aa2269 100644 --- a/dive.h +++ b/dive.h @@ -689,9 +689,7 @@ struct diveplan { struct divedatapoint *dp; }; -void plan(struct diveplan *diveplan, char **cache_datap, struct dive **divep); void plan_add_segment(struct diveplan *diveplan, int duration, int depth, int o2, int he, int po2); -void add_duration_to_nth_dp(struct diveplan *diveplan, int idx, int duration, gboolean is_rel); void add_depth_to_nth_dp(struct diveplan *diveplan, int idx, int depth); void add_gas_to_nth_dp(struct diveplan *diveplan, int idx, int o2, int he); void free_dps(struct divedatapoint *dp); diff --git a/planner.c b/planner.c index 68f444882..0f739b1f0 100644 --- a/planner.c +++ b/planner.c @@ -10,7 +10,7 @@ #include #include "dive.h" #include "divelist.h" -#include "display-gtk.h" +#include "planner.h" int decostoplevels[] = { 0, 3000, 6000, 9000, 12000, 15000, 18000, 21000, 24000, 27000, 30000, 33000, 36000, 39000, 42000, 45000, 48000, 51000, 54000, 57000, @@ -21,7 +21,6 @@ int decostoplevels[] = { 0, 3000, 6000, 9000, 12000, 15000, 18000, 21000, 24000, }; double plangflow, plangfhigh; char *disclaimer; -GtkWidget *planner, *planner_error_bar, *error_label; #if DEBUG_PLAN void dump_plan(struct diveplan *diveplan) @@ -48,46 +47,6 @@ void dump_plan(struct diveplan *diveplan) } #endif -static void on_error_bar_response(GtkWidget *widget, gint response, gpointer data) -{ - if (response == GTK_RESPONSE_OK) - { - gtk_widget_destroy(widget); - planner_error_bar = NULL; - error_label = NULL; - } -} - -static void show_error(const char *fmt, ...) -{ - va_list args; - GError *error; - GtkWidget *box, *container; - gboolean bar_is_visible = TRUE; - - va_start(args, fmt); - error = g_error_new_valist(g_quark_from_string("subsurface"), DIVE_ERROR_PLAN, fmt, args); - va_end(args); - if (!planner_error_bar) { - planner_error_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); - g_signal_connect(planner_error_bar, "response", G_CALLBACK(on_error_bar_response), NULL); - gtk_info_bar_set_message_type(GTK_INFO_BAR(planner_error_bar), GTK_MESSAGE_ERROR); - bar_is_visible = FALSE; - } - container = gtk_info_bar_get_content_area(GTK_INFO_BAR(planner_error_bar)); - if (error_label) - gtk_container_remove(GTK_CONTAINER(container), error_label); - error_label = gtk_label_new(error->message); - gtk_container_add(GTK_CONTAINER(container), error_label); - box = gtk_dialog_get_content_area(GTK_DIALOG(planner)); - if (!bar_is_visible) - gtk_box_pack_start(GTK_BOX(box), planner_error_bar, FALSE, FALSE, 0); - gtk_widget_show_all(box); - /* make sure this actually gets shown BEFORE the calculations run */ - while (gtk_events_pending()) - gtk_main_iteration_do(FALSE); -} - void get_gas_from_events(struct divecomputer *dc, int time, int *o2, int *he) { struct event *event = dc->events; @@ -132,13 +91,14 @@ void get_gas_string(int o2, int he, char *text, int len) } /* returns the tissue tolerance at the end of this (partial) dive */ -double tissue_at_end(struct dive *dive, char **cached_datap) +double tissue_at_end(struct dive *dive, char **cached_datap, char **error_string_p) { struct divecomputer *dc; struct sample *sample, *psample; int i, j, t0, t1, gasidx, lastdepth; int o2, he; double tissue_tolerance; + static char buf[200]; if (!dive) return 0.0; @@ -160,7 +120,8 @@ double tissue_at_end(struct dive *dive, char **cached_datap) t1 = sample->time.seconds; get_gas_from_events(&dive->dc, t0, &o2, &he); if ((gasidx = get_gasidx(dive, o2, he)) == -1) { - show_error(_("Can't find gas %d/%d"), (o2 + 5) / 10, (he + 5) / 10); + snprintf(buf, sizeof(buf),_("Can't find gas %d/%d"), (o2 + 5) / 10, (he + 5) / 10); + *error_string_p = buf; gasidx = 0; } if (i > 0) @@ -177,7 +138,7 @@ double tissue_at_end(struct dive *dive, char **cached_datap) } /* how many seconds until we can ascend to the next stop? */ -int time_at_last_depth(struct dive *dive, int o2, int he, int next_stop, char **cached_data_p) +int time_at_last_depth(struct dive *dive, int o2, int he, int next_stop, char **cached_data_p, char **error_string_p) { int depth, gasidx; double surface_pressure, tissue_tolerance; @@ -187,7 +148,7 @@ int time_at_last_depth(struct dive *dive, int o2, int he, int next_stop, char ** if (!dive) return 0; surface_pressure = dive->dc.surface_pressure.mbar / 1000.0; - tissue_tolerance = tissue_at_end(dive, cached_data_p); + tissue_tolerance = tissue_at_end(dive, cached_data_p, error_string_p); sample = &dive->dc.sample[dive->dc.samples - 1]; depth = sample->depth.mm; gasidx = get_gasidx(dive, o2, he); @@ -214,7 +175,6 @@ int add_gas(struct dive *dive, int o2, int he) return i; } if (i == MAX_CYLINDERS) { - show_error(_("Too many gas mixes")); return -1; } mix->o2.permille = o2; @@ -225,7 +185,7 @@ int add_gas(struct dive *dive, int o2, int he) return i; } -struct dive *create_dive_from_plan(struct diveplan *diveplan) +struct dive *create_dive_from_plan(struct diveplan *diveplan, char **error_string) { struct dive *dive; struct divedatapoint *dp; @@ -235,6 +195,7 @@ struct dive *create_dive_from_plan(struct diveplan *diveplan) int oldpo2 = 0; int lasttime = 0; + *error_string = NULL; if (!diveplan || !diveplan->dp) return NULL; #if DEBUG_PLAN & 4 @@ -256,7 +217,8 @@ struct dive *create_dive_from_plan(struct diveplan *diveplan) sample = prepare_sample(dc); sample->po2 = dp->po2; finish_sample(dc); - add_gas(dive, oldo2, oldhe); + if (add_gas(dive, oldo2, oldhe) < 0) + goto gas_error_exit; while (dp) { int o2 = dp->o2, he = dp->he; int po2 = dp->po2; @@ -266,7 +228,8 @@ struct dive *create_dive_from_plan(struct diveplan *diveplan) if (time == 0) { /* special entries that just inform the algorithm about * additional gases that are available */ - add_gas(dive, o2, he); + if (add_gas(dive, o2, he) < 0) + goto gas_error_exit; dp = dp->next; continue; } @@ -289,7 +252,8 @@ struct dive *create_dive_from_plan(struct diveplan *diveplan) int plano2 = (o2 + 5) / 10 * 10; int planhe = (he + 5) / 10 * 10; int value; - add_gas(dive, plano2, planhe); + if (add_gas(dive, plano2, planhe) < 0) + goto gas_error_exit; value = (plano2 / 10) | ((planhe / 10) << 16); add_event(dc, lasttime, 25, 0, value, "gaschange"); // SAMPLE_EVENT_GASCHANGE2 oldo2 = o2; oldhe = he; @@ -316,6 +280,11 @@ struct dive *create_dive_from_plan(struct diveplan *diveplan) save_dive(stdout, dive); #endif return dive; + +gas_error_exit: + free(dive); + *error_string = _("Too many gas mixes"); + return NULL; } void free_dps(struct divedatapoint *dp) @@ -359,7 +328,8 @@ struct divedatapoint *get_nth_dp(struct diveplan *diveplan, int idx) return dp; } -void add_duration_to_nth_dp(struct diveplan *diveplan, int idx, int duration, gboolean is_rel) +/* return -1 to warn about potentially very long calculation */ +int add_duration_to_nth_dp(struct diveplan *diveplan, int idx, int duration, gboolean is_rel) { struct divedatapoint *pdp, *dp = get_nth_dp(diveplan, idx); if (idx > 0) { @@ -369,7 +339,8 @@ void add_duration_to_nth_dp(struct diveplan *diveplan, int idx, int duration, gb } dp->time = duration; if (duration > 180 * 60) - show_error(_("Warning - extremely long dives can cause long calculation time")); + return -1; + return 0; } /* this function is ONLY called from the dialog callback - so it @@ -612,7 +583,7 @@ static void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive) dive->notes = strdup(buffer); } -void plan(struct diveplan *diveplan, char **cached_datap, struct dive **divep) +void plan(struct diveplan *diveplan, char **cached_datap, struct dive **divep, char **error_string_p) { struct dive *dive; struct sample *sample; @@ -629,7 +600,7 @@ void plan(struct diveplan *diveplan, char **cached_datap, struct dive **divep) diveplan->surface_pressure = SURFACE_PRESSURE; if (*divep) delete_single_dive(dive_table.nr - 1); - *divep = dive = create_dive_from_plan(diveplan); + *divep = dive = create_dive_from_plan(diveplan, error_string_p); if (!dive) return; record_dive(dive); @@ -641,7 +612,7 @@ void plan(struct diveplan *diveplan, char **cached_datap, struct dive **divep) get_gas_from_events(&dive->dc, sample->time.seconds, &o2, &he); po2 = dive->dc.sample[dive->dc.samples - 1].po2; depth = dive->dc.sample[dive->dc.samples - 1].depth.mm; - tissue_tolerance = tissue_at_end(dive, cached_datap); + tissue_tolerance = tissue_at_end(dive, cached_datap, error_string_p); ceiling = deco_allowed_depth(tissue_tolerance, diveplan->surface_pressure / 1000.0, dive, 1); #if DEBUG_PLAN & 4 printf("gas %d/%d\n", o2, he); @@ -673,7 +644,9 @@ void plan(struct diveplan *diveplan, char **cached_datap, struct dive **divep) plan_add_segment(diveplan, transitiontime, stoplevels[stopidx], o2, he, po2); /* re-create the dive */ delete_single_dive(dive_table.nr - 1); - *divep = dive = create_dive_from_plan(diveplan); + *divep = dive = create_dive_from_plan(diveplan, error_string_p); + if (!dive) + goto error_exit; record_dive(dive); } while (stopidx > 0) { /* this indicates that we aren't surfacing directly */ @@ -686,12 +659,12 @@ void plan(struct diveplan *diveplan, char **cached_datap, struct dive **divep) #endif gi--; } - wait_time = time_at_last_depth(dive, o2, he, stoplevels[stopidx - 1], cached_datap); + wait_time = time_at_last_depth(dive, o2, he, stoplevels[stopidx - 1], cached_datap, error_string_p); /* typically deco plans are done in one minute increments; we may want to * make this configurable at some point */ wait_time = ((wait_time + 59) / 60) * 60; #if DEBUG_PLAN & 2 - tissue_tolerance = tissue_at_end(dive, cached_datap); + tissue_tolerance = tissue_at_end(dive, cached_datap, error_string_p); ceiling = deco_allowed_depth(tissue_tolerance, diveplan->surface_pressure / 1000.0, dive, 1); printf("waittime %d:%02d at depth %5.2lfm; ceiling %5.2lfm\n", FRACTION(wait_time, 60), stoplevels[stopidx] / 1000.0, ceiling / 1000.0); @@ -705,7 +678,9 @@ void plan(struct diveplan *diveplan, char **cached_datap, struct dive **divep) plan_add_segment(diveplan, transitiontime, stoplevels[stopidx - 1], o2, he, po2); /* re-create the dive */ delete_single_dive(dive_table.nr - 1); - *divep = dive = create_dive_from_plan(diveplan); + *divep = dive = create_dive_from_plan(diveplan, error_string_p); + if (!dive) + goto error_exit; record_dive(dive); stopidx--; } @@ -713,12 +688,11 @@ void plan(struct diveplan *diveplan, char **cached_datap, struct dive **divep) /* now make the dive visible in the dive list */ report_dives(FALSE, FALSE); show_and_select_dive(dive); +error_exit: free(stoplevels); free(gaschanges); } - -/* and now the UI for all this */ /* * Get a value in tenths (so "10.2" == 102, "9" = 90) * @@ -782,7 +756,7 @@ static int get_permille(const char *begin, const char **end) return value; } -static int validate_gas(const char *text, int *o2_p, int *he_p) +int validate_gas(const char *text, int *o2_p, int *he_p) { int o2, he; @@ -821,7 +795,7 @@ static int validate_gas(const char *text, int *o2_p, int *he_p) return 1; } -static int validate_time(const char *text, int *sec_p, int *rel_p) +int validate_time(const char *text, int *sec_p, int *rel_p) { int min, sec, rel; char *end; @@ -888,7 +862,7 @@ static int validate_time(const char *text, int *sec_p, int *rel_p) return 1; } -static int validate_depth(const char *text, int *mm_p) +int validate_depth(const char *text, int *mm_p) { int depth, imperial; @@ -927,7 +901,7 @@ static int validate_depth(const char *text, int *mm_p) return 1; } -static int validate_po2(const char *text, int *mbar_po2) +int validate_po2(const char *text, int *mbar_po2) { int po2; @@ -950,7 +924,7 @@ static int validate_po2(const char *text, int *mbar_po2) return 1; } -static int validate_volume(const char *text, int *sac) +int validate_volume(const char *text, int *sac) { int volume, imperial; @@ -986,32 +960,12 @@ static int validate_volume(const char *text, int *sac) return 1; } -static GtkWidget *add_entry_to_box(GtkWidget *box, const char *label) -{ - GtkWidget *entry, *frame; - - entry = gtk_entry_new(); - gtk_entry_set_max_length(GTK_ENTRY(entry), 16); - if (label) { - frame = gtk_frame_new(label); - gtk_container_add(GTK_CONTAINER(frame), entry); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 0); - } else { - gtk_box_pack_start(GTK_BOX(box), entry, FALSE, FALSE, 2); - } - return entry; -} - -#define MAX_WAYPOINTS 12 -GtkWidget *entry_depth[MAX_WAYPOINTS], *entry_duration[MAX_WAYPOINTS], *entry_gas[MAX_WAYPOINTS], *entry_po2[MAX_WAYPOINTS]; -int nr_waypoints = 0; -static GtkListStore *gas_model = NULL; struct diveplan diveplan = {}; char *cache_data = NULL; struct dive *planned_dive = NULL; /* make a copy of the diveplan so far and display the corresponding dive */ -void show_planned_dive(void) +void show_planned_dive(char **error_string_p) { struct diveplan tempplan; struct divedatapoint *dp, **dpp; @@ -1029,342 +983,18 @@ void show_planned_dive(void) printf("in show_planned_dive:\n"); dump_plan(&tempplan); #endif - plan(&tempplan, &cache_data, &planned_dive); + plan(&tempplan, &cache_data, &planned_dive, error_string_p); free_dps(tempplan.dp); } -static gboolean gas_focus_out_cb(GtkWidget *entry, GdkEvent *event, gpointer data) -{ - const char *gastext; - int o2, he; - int idx = data - NULL; - - gastext = gtk_entry_get_text(GTK_ENTRY(entry)); - o2 = he = 0; - if (validate_gas(gastext, &o2, &he)) - add_string_list_entry(gastext, gas_model); - add_gas_to_nth_dp(&diveplan, idx, o2, he); - show_planned_dive(); - return FALSE; -} - -static void gas_changed_cb(GtkWidget *combo, gpointer data) -{ - const char *gastext; - int o2, he; - int idx = data - NULL; - - gastext = get_active_text(GTK_COMBO_BOX(combo)); - /* stupidly this gets called for two reasons: - * a) any keystroke into the entry field - * b) mouse selection of a dropdown - * we only care about b) (a) is handled much better with the focus-out event) - * so let's check that the text returned is actually in our model before going on - */ - if (match_list(gas_model, gastext) != MATCH_EXACT) - return; - o2 = he = 0; - if (!validate_gas(gastext, &o2, &he)) { - /* this should never happen as only validated texts should be - * in the dropdown */ - show_error(_("Invalid gas for row %d"),idx); - } - add_gas_to_nth_dp(&diveplan, idx, o2, he); - show_planned_dive(); -} - -static gboolean depth_focus_out_cb(GtkWidget *entry, GdkEvent *event, gpointer data) -{ - const char *depthtext; - int depth = -1; - int idx = data - NULL; - - depthtext = gtk_entry_get_text(GTK_ENTRY(entry)); - - if (validate_depth(depthtext, &depth)) { - if (depth > 150000) - show_error(_("Warning - planning very deep dives can take excessive amounts of time")); - add_depth_to_nth_dp(&diveplan, idx, depth); - show_planned_dive(); - } else { - /* it might be better to instead change the color of the input field or something */ - if (depth == -1) - show_error(_("Invalid depth - could not parse \"%s\""), depthtext); - else - show_error(_("Invalid depth - values deeper than 400m not supported")); - } - return FALSE; -} - -static gboolean duration_focus_out_cb(GtkWidget *entry, GdkEvent * event, gpointer data) -{ - const char *durationtext; - int duration, is_rel; - int idx = data - NULL; - - durationtext = gtk_entry_get_text(GTK_ENTRY(entry)); - if (validate_time(durationtext, &duration, &is_rel)) - add_duration_to_nth_dp(&diveplan, idx, duration, is_rel); - show_planned_dive(); - return FALSE; -} - -static gboolean po2_focus_out_cb(GtkWidget *entry, GdkEvent * event, gpointer data) -{ - const char *po2text; - int po2; - int idx = data - NULL; - - po2text = gtk_entry_get_text(GTK_ENTRY(entry)); - if (validate_po2(po2text, &po2)) - add_po2_to_nth_dp(&diveplan, idx, po2); - show_planned_dive(); - return FALSE; -} - /* Subsurface follows the lead of most divecomputers to use times * without timezone - so all times are implicitly assumed to be * local time of the dive location; so in order to give the current * time in that way we actually need to add the timezone offset */ -static timestamp_t current_time_notz(void) +timestamp_t current_time_notz(void) { time_t now = time(NULL); struct tm *local = localtime(&now); return utc_mktime(local); } -static gboolean starttime_focus_out_cb(GtkWidget *entry, GdkEvent * event, gpointer data) -{ - const char *starttimetext; - int starttime, is_rel; - - starttimetext = gtk_entry_get_text(GTK_ENTRY(entry)); - if (validate_time(starttimetext, &starttime, &is_rel)) { - /* we alway make this relative - either from the current time or from the - * end of the last dive, whichever is later */ - timestamp_t cur = current_time_notz(); - if (diveplan.lastdive_nr >= 0) { - struct dive *last_dive = get_dive(diveplan.lastdive_nr); - if (last_dive && last_dive->when + last_dive->dc.duration.seconds > cur) - cur = last_dive->when + last_dive->dc.duration.seconds; - } - diveplan.when = cur + starttime; - show_planned_dive(); - } else { - /* it might be better to instead change the color of the input field or something */ - show_error(_("Invalid starttime")); - } - return FALSE; -} - -static gboolean surfpres_focus_out_cb(GtkWidget *entry, GdkEvent * event, gpointer data) -{ - const char *surfprestext; - - surfprestext = gtk_entry_get_text(GTK_ENTRY(entry)); - diveplan.surface_pressure = atoi(surfprestext); - show_planned_dive(); - return FALSE; -} - -static gboolean sac_focus_out_cb(GtkWidget *entry, GdkEvent * event, gpointer data) -{ - const char *sactext; - - sactext = gtk_entry_get_text(GTK_ENTRY(entry)); - if (validate_volume(sactext, data)) - show_planned_dive(); - return FALSE; -} - -static gboolean gf_focus_out_cb(GtkWidget *entry, GdkEvent * event, gpointer data) -{ - const char *gftext; - int gf; - double *gfp = data; - - gftext = gtk_entry_get_text(GTK_ENTRY(entry)); - if (sscanf(gftext, "%d", &gf) == 1) { - *gfp = gf / 100.0; - show_planned_dive(); - } - return FALSE; -} - -static GtkWidget *add_gas_combobox_to_box(GtkWidget *box, const char *label, int idx) -{ - GtkWidget *frame, *combo; - - if (!gas_model) { - gas_model = gtk_list_store_new(1, G_TYPE_STRING); - add_string_list_entry(_("AIR"), gas_model); - add_string_list_entry(_("EAN32"), gas_model); - add_string_list_entry(_("EAN36"), gas_model); - } - combo = combo_box_with_model_and_entry(gas_model); - gtk_widget_add_events(combo, GDK_FOCUS_CHANGE_MASK); - g_signal_connect(gtk_bin_get_child(GTK_BIN(combo)), "focus-out-event", G_CALLBACK(gas_focus_out_cb), NULL + idx); - g_signal_connect(combo, "changed", G_CALLBACK(gas_changed_cb), NULL + idx); - if (label) { - frame = gtk_frame_new(label); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 0); - gtk_container_add(GTK_CONTAINER(frame), combo); - } else { - gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 2); - } - - return combo; -} - -static void add_waypoint_widgets(GtkWidget *box, int idx) -{ - GtkWidget *hbox; - - hbox = gtk_hbox_new(FALSE, 0); - gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0); - if (idx == 0) { - entry_depth[idx] = add_entry_to_box(hbox, _("Ending Depth")); - entry_duration[idx] = add_entry_to_box(hbox, _("Segment Time")); - entry_gas[idx] = add_gas_combobox_to_box(hbox, C_("Type of","Gas Used"), idx); - entry_po2[idx] = add_entry_to_box(hbox, _("CC SetPoint")); - } else { - entry_depth[idx] = add_entry_to_box(hbox, NULL); - entry_duration[idx] = add_entry_to_box(hbox, NULL); - entry_gas[idx] = add_gas_combobox_to_box(hbox, NULL, idx); - entry_po2[idx] = add_entry_to_box(hbox, NULL); - } - gtk_widget_add_events(entry_depth[idx], GDK_FOCUS_CHANGE_MASK); - g_signal_connect(entry_depth[idx], "focus-out-event", G_CALLBACK(depth_focus_out_cb), NULL + idx); - gtk_widget_add_events(entry_duration[idx], GDK_FOCUS_CHANGE_MASK); - g_signal_connect(entry_duration[idx], "focus-out-event", G_CALLBACK(duration_focus_out_cb), NULL + idx); - gtk_widget_add_events(entry_po2[idx], GDK_FOCUS_CHANGE_MASK); - g_signal_connect(entry_po2[idx], "focus-out-event", G_CALLBACK(po2_focus_out_cb), NULL + idx); -} - -static void add_waypoint_cb(GtkButton *button, gpointer _data) -{ - GtkWidget *vbox = _data; - if (nr_waypoints < MAX_WAYPOINTS) { - GtkWidget *ovbox, *dialog; - add_waypoint_widgets(vbox, nr_waypoints); - nr_waypoints++; - ovbox = gtk_widget_get_parent(GTK_WIDGET(button)); - dialog = gtk_widget_get_parent(ovbox); - gtk_widget_show_all(dialog); - } else { - show_error(_("Too many waypoints")); - } -} - -static void add_entry_with_callback(GtkWidget *box, int length, char *label, char *initialtext, - gboolean (*callback)(GtkWidget *, GdkEvent *, gpointer), gpointer data) -{ - GtkWidget *entry = add_entry_to_box(box, label); - gtk_entry_set_max_length(GTK_ENTRY(entry), length); - gtk_entry_set_text(GTK_ENTRY(entry), initialtext); - gtk_widget_add_events(entry, GDK_FOCUS_CHANGE_MASK); - g_signal_connect(entry, "focus-out-event", G_CALLBACK(callback), data); -} - -/* set up the dialog where the user can input their dive plan */ -void input_plan() -{ - GtkWidget *content, *vbox, *hbox, *outervbox, *add_row, *label; - char *bottom_sac, *deco_sac, gflowstring[4], gfhighstring[4]; - char *explanationtext = _("Add segments below.\nEach line describes part of the planned dive.\n" - "An entry with depth, time and gas describes a segment that ends " - "at the given depth, takes the given time (if relative, e.g. '+3:30') " - "or ends at the given time (if absolute e.g '@5:00', 'runtime'), and uses the given gas.\n" - "An empty gas means 'use previous gas' (or AIR if no gas was specified).\n" - "An entry that has a depth and a gas given but no time is special; it " - "informs the planner that the gas specified is available for the ascent " - "once the depth given has been reached.\n" - "CC SetPoint specifies CC (rebreather) dives, leave empty for OC.\n"); - char *labeltext; - int len; - - disclaimer = _("DISCLAIMER / WARNING: THIS IS A NEW IMPLEMENTATION OF THE BUHLMANN " - "ALGORITHM AND A DIVE PLANNER IMPLEMENTION BASED ON THAT WHICH HAS " - "RECEIVED ONLY A LIMITED AMOUNT OF TESTING. WE STRONGLY RECOMMEND NOT TO " - "PLAN DIVES SIMPLY BASED ON THE RESULTS GIVEN HERE."); - if (diveplan.dp) - free_dps(diveplan.dp); - memset(&diveplan, 0, sizeof(diveplan)); - diveplan.lastdive_nr = dive_table.nr - 1; - free(cache_data); - cache_data = NULL; - planned_dive = NULL; - planner = gtk_dialog_new_with_buttons(_("Dive Plan - THIS IS JUST A SIMULATION; DO NOT USE FOR DIVING"), - GTK_WINDOW(main_window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - NULL); - content = gtk_dialog_get_content_area (GTK_DIALOG (planner)); - outervbox = gtk_vbox_new(FALSE, 2); - gtk_container_add (GTK_CONTAINER (content), outervbox); - - len = strlen(explanationtext) + strlen(disclaimer) + sizeof(""); - labeltext = malloc(len); - snprintf(labeltext, len, "%s%s", explanationtext, disclaimer); - label = gtk_label_new(labeltext); - gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); - gtk_label_set_use_markup(GTK_LABEL(label), TRUE); - gtk_label_set_width_chars(GTK_LABEL(label), 60); - gtk_box_pack_start(GTK_BOX(outervbox), label, TRUE, TRUE, 0); - vbox = gtk_vbox_new(FALSE, 0); - gtk_box_pack_start(GTK_BOX(outervbox), vbox, TRUE, TRUE, 0); - hbox = gtk_hbox_new(FALSE, 0); - gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); - add_entry_with_callback(hbox, 12, _("Dive starts when?"), "+60:00", starttime_focus_out_cb, NULL); - add_entry_with_callback(hbox, 12, _("Surface Pressure (mbar)"), SURFACE_PRESSURE_STRING, surfpres_focus_out_cb, NULL); - - hbox = gtk_hbox_new(FALSE, 0); - gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0); - if (get_units()->volume == CUFT) { - bottom_sac = _("0.7 cuft/min"); - deco_sac = _("0.6 cuft/min"); - diveplan.bottomsac = 1000 * cuft_to_l(0.7); - diveplan.decosac = 1000 * cuft_to_l(0.6); - } else { - bottom_sac = _("20 l/min"); - deco_sac = _("17 l/min"); - diveplan.bottomsac = 20000; - diveplan.decosac = 17000; - } - add_entry_with_callback(hbox, 12, _("SAC during dive"), bottom_sac, sac_focus_out_cb, &diveplan.bottomsac); - add_entry_with_callback(hbox, 12, _("SAC during decostop"), deco_sac, sac_focus_out_cb, &diveplan.decosac); - plangflow = prefs.gflow; - plangfhigh = prefs.gfhigh; - snprintf(gflowstring, sizeof(gflowstring), "%3.0f", 100 * plangflow); - snprintf(gfhighstring, sizeof(gflowstring), "%3.0f", 100 * plangfhigh); - add_entry_with_callback(hbox, 5, _("GFlow for plan"), gflowstring, gf_focus_out_cb, &plangflow); - add_entry_with_callback(hbox, 5, _("GFhigh for plan"), gfhighstring, gf_focus_out_cb, &plangfhigh); - diveplan.when = current_time_notz() + 3600; - diveplan.surface_pressure = SURFACE_PRESSURE; - nr_waypoints = 4; - add_waypoint_widgets(vbox, 0); - add_waypoint_widgets(vbox, 1); - add_waypoint_widgets(vbox, 2); - add_waypoint_widgets(vbox, 3); - add_row = gtk_button_new_with_label(_("Add waypoint")); - g_signal_connect(G_OBJECT(add_row), "clicked", G_CALLBACK(add_waypoint_cb), vbox); - gtk_box_pack_start(GTK_BOX(outervbox), add_row, FALSE, FALSE, 0); - gtk_widget_show_all(planner); - if (gtk_dialog_run(GTK_DIALOG(planner)) == GTK_RESPONSE_ACCEPT) { - plan(&diveplan, &cache_data, &planned_dive); - mark_divelist_changed(TRUE); - } else { - if (planned_dive) { - /* we have added a dive during the dynamic construction - * in the dialog; get rid of it */ - delete_single_dive(dive_table.nr - 1); - report_dives(FALSE, FALSE); - planned_dive = NULL; - } - } - gtk_widget_destroy(planner); - planner_error_bar = NULL; - error_label = NULL; - set_gf(prefs.gflow, prefs.gfhigh); -} diff --git a/planner.h b/planner.h new file mode 100644 index 000000000..099f640fc --- /dev/null +++ b/planner.h @@ -0,0 +1,21 @@ +#ifndef PLANNER_H +#define PLANNER_H + +extern void plan(struct diveplan *diveplan, char **cache_datap, struct dive **divep, char **error_string_p); +extern int validate_gas(const char *text, int *o2_p, int *he_p); +extern int validate_time(const char *text, int *sec_p, int *rel_p); +extern int validate_depth(const char *text, int *mm_p); +extern int validate_po2(const char *text, int *mbar_po2); +extern int validate_volume(const char *text, int *sac); +extern timestamp_t current_time_notz(void); +extern void show_planned_dive(char **error_string_p); +extern int add_duration_to_nth_dp(struct diveplan *diveplan, int idx, int duration, gboolean is_rel); +extern void add_po2_to_nth_dp(struct diveplan *diveplan, int idx, int po2); + +extern struct diveplan diveplan; +extern struct dive *planned_dive; +extern char *cache_data; +extern char *disclaimer; +extern double plangflow, plangfhigh; + +#endif /* PLANNER_H */ -- cgit v1.2.3-70-g09d2 From 99153de715955a9a80097c9951eaf096efeb5752 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Tue, 9 Apr 2013 19:34:26 +0400 Subject: Makefile: detect which files need moc and uic Add some magic rules to detect which files need to be processed by the moc or uic tools, as well as a way to manually specify exceptions. Signed-off-by: Alberto Mardegan Signed-off-by: Dirk Hohndel --- Makefile | 54 ++++++++++++++++++++++++++++++---------------------- qt-ui/maintab.cpp | 2 -- qt-ui/mainwindow.cpp | 2 -- 3 files changed, 31 insertions(+), 27 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 28b7e0365..c89816de5 100644 --- a/Makefile +++ b/Makefile @@ -191,13 +191,24 @@ OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o divelist-gtk qt-gui.o statistics.o file.o cochran.o device.o download-dialog.o prefs.o \ webservice.o sha1.o $(GPSOBJ) $(OSSUPPORT).o $(RESFILE) $(QTOBJS) -DEPS = $(wildcard .dep/*.dep) +# Add files to the following variables if the auto-detection based on the +# filename fails +OBJS_NEEDING_MOC = +OBJS_NEEDING_UIC = +HEADERS_NEEDING_MOC = + +# Add the objects for the header files which define QObject subclasses +HEADERS_NEEDING_MOC += $(shell grep -l -s 'Q_OBJECT' $(OBJS:.o=.h)) +MOC_OBJS = $(HEADERS_NEEDING_MOC:.h=.moc.o) +ALL_OBJS = $(OBJS) $(MOC_OBJS) + +DEPS = $(wildcard .dep/*.dep) all: $(NAME) -$(NAME): gen_version_file $(OBJS) $(MSGOBJS) $(INFOPLIST) - $(CXX) $(LDFLAGS) -o $(NAME) $(OBJS) $(LIBS) +$(NAME): gen_version_file $(ALL_OBJS) $(MSGOBJS) $(INFOPLIST) + $(CXX) $(LDFLAGS) -o $(NAME) $(ALL_OBJS) $(LIBS) gen_version_file: ifneq ($(STORED_VERSION_STRING),$(VERSION_STRING)) @@ -310,36 +321,33 @@ MOCFLAGS = $(filter -I%, $(CXXFLAGS) $(EXTRA_FLAGS)) $(filter -D%, $(CXXFLAGS) $ @mkdir -p .dep .dep/qt-ui @$(CXX) $(CXXFLAGS) $(EXTRA_FLAGS) -MD -MF .dep/$@.dep -c -o $@ $< -# This rule is for running the moc on QObject subclasses defined in the .cpp -# files; remember to #include ".moc" at the end of the .cpp file, or -# you'll get linker errors ("undefined vtable for...") -# To activate this rule, you need another rule on the .o file, like: -# file.o: file.moc +# Detect which files require the moc or uic tools to be run +CPP_NEEDING_MOC = $(shell grep -l -s '^\#include \".*\.moc\"' $(OBJS:.o=.cpp)) +OBJS_NEEDING_MOC += $(CPP_NEEDING_MOC:.cpp=.o) -qt-ui/%.moc: qt-ui/%.h - @echo ' MOC' $< - @$(MOC) -i $(MOCFLAGS) $< -o $@ +CPP_NEEDING_UIC = $(shell grep -l -s '^\#include \"ui_.*\.h\"' $(OBJS:.o=.cpp)) +OBJS_NEEDING_UIC += $(CPP_NEEDING_UIC:.cpp=.o) -# this is just here for qt-gui.cpp -# should be removed once all the Qt UI code has been moved into qt-ui +# This rule is for running the moc on QObject subclasses defined in the .h +# files. +%.moc.cpp: %.h + @echo ' MOC' $< + @$(MOC) $(MOCFLAGS) $< -o $@ +# This rule is for running the moc on QObject subclasses defined in the .cpp +# files; remember to #include ".moc" at the end of the .cpp file, or +# you'll get linker errors ("undefined vtable for...") %.moc: %.cpp @echo ' MOC' $< @$(MOC) -i $(MOCFLAGS) $< -o $@ # This creates the ui headers. -# To activate this rule, you need to add the ui_*.h file to the .o file: -# file.o: ui_file.h - -qt-ui/ui_%.h: qt-ui/%.ui +ui_%.h: %.ui @echo ' UIC' $< @$(UIC) $< -o $@ -qt-gui.o: qt-gui.moc - -qt-ui/maintab.o: qt-ui/maintab.moc qt-ui/ui_maintab.h - -qt-ui/mainwindow.o: qt-ui/mainwindow.moc qt-ui/ui_mainwindow.h +$(OBJS_NEEDING_MOC): %.o: %.moc +$(OBJS_NEEDING_UIC): qt-ui/%.o: qt-ui/ui_%.h share/locale/%.UTF-8/LC_MESSAGES/subsurface.mo: po/%.po po/%.aliases mkdir -p $(dir $@) @@ -367,7 +375,7 @@ doc: $(MAKE) -C Documentation doc clean: - rm -f $(OBJS) *~ $(NAME) $(NAME).exe po/*~ po/subsurface-new.pot \ + rm -f $(ALL_OBJS) *~ $(NAME) $(NAME).exe po/*~ po/subsurface-new.pot \ $(VERSION_FILE) qt-ui/*.moc qt-ui/ui_*.h rm -rf share .dep diff --git a/qt-ui/maintab.cpp b/qt-ui/maintab.cpp index 4569958c8..c630965d2 100644 --- a/qt-ui/maintab.cpp +++ b/qt-ui/maintab.cpp @@ -6,5 +6,3 @@ MainTab::MainTab(QWidget *parent) : QTabWidget(parent), { ui->setupUi(this); } - -#include "maintab.moc" diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index d3e5603be..03e14377f 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -13,5 +13,3 @@ void MainWindow::on_actionNew_triggered() { qDebug() << "actionNew"; } - -#include "mainwindow.moc" -- cgit v1.2.3-70-g09d2 From 76f71b4ca07dbef1069aec3db1138c199927b1bb Mon Sep 17 00:00:00 2001 From: Amit Chaudhuri Date: Fri, 12 Apr 2013 08:24:07 +0100 Subject: Add dive list view to main window Add files for dive list model/view implementation. Replace TableView with the custom list view. Amendments to makefile to match. Note: we don't yet handle trips and may want to add additional columns to describe the dive. A single, dummy dive is added to show how this works (get root; item is child of root). Purely to illustrate - needs proper integration etc. Amend member names for dive list view components Various naming changes to conform to coding style. Required changes to members (remove prefix) and methods (avoid clash with members). Clean up indentation (swap spaces for tabs). Code for model/view was written with a different editor which had different settings :-/ [Dirk Hohndel: minor whitespace cleanup] Signed-off-by: Amit Chaudhuri Signed-off-by: Dirk Hohndel --- Makefile | 2 +- qt-ui/divelistview.cpp | 5 ++ qt-ui/divelistview.h | 19 +++++++ qt-ui/divetripmodel.cpp | 136 ++++++++++++++++++++++++++++++++++++++++++++++++ qt-ui/divetripmodel.h | 80 ++++++++++++++++++++++++++++ qt-ui/mainwindow.cpp | 29 +++++++++++ qt-ui/mainwindow.h | 2 + qt-ui/mainwindow.ui | 7 ++- 8 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 qt-ui/divelistview.cpp create mode 100644 qt-ui/divelistview.h create mode 100644 qt-ui/divetripmodel.cpp create mode 100644 qt-ui/divetripmodel.h (limited to 'Makefile') diff --git a/Makefile b/Makefile index c89816de5..9e8e69b2d 100644 --- a/Makefile +++ b/Makefile @@ -183,7 +183,7 @@ MSGLANGS=$(notdir $(wildcard po/*.po)) MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.mo)) -QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o +QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o qt-ui/divelistview.o qt-ui/divetripmodel.o OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o divelist-gtk.o deco.o \ planner.o planner-gtk.o \ diff --git a/qt-ui/divelistview.cpp b/qt-ui/divelistview.cpp new file mode 100644 index 000000000..eafbdd384 --- /dev/null +++ b/qt-ui/divelistview.cpp @@ -0,0 +1,5 @@ +#include "divelistview.h" + +DiveListView::DiveListView(QWidget *parent) : QTreeView(parent) +{ +} diff --git a/qt-ui/divelistview.h b/qt-ui/divelistview.h new file mode 100644 index 000000000..3ac123a14 --- /dev/null +++ b/qt-ui/divelistview.h @@ -0,0 +1,19 @@ +#ifndef DIVELISTVIEW_H +#define DIVELISTVIEW_H + +/*! A view subclass for use with dives + + Note: calling this a list view might be misleading? + + +*/ + +#include + +class DiveListView : public QTreeView +{ +public: + DiveListView(QWidget *parent = 0); +}; + +#endif // DIVELISTVIEW_H diff --git a/qt-ui/divetripmodel.cpp b/qt-ui/divetripmodel.cpp new file mode 100644 index 000000000..a03603bcf --- /dev/null +++ b/qt-ui/divetripmodel.cpp @@ -0,0 +1,136 @@ +#include "divetripmodel.h" + + +DiveItem::DiveItem(int num, QString dt, float dur, float dep, QString loc, DiveItem *p): + number(num), dateTime(dt), duration(dur), depth(dep), location(loc), parentItem(p) +{ + if (parentItem) + parentItem->addChild(this); +} + + +DiveTripModel::DiveTripModel(const QString &filename, QObject *parent) : QAbstractItemModel(parent), filename(filename) +{ + rootItem = new DiveItem; +} + + +Qt::ItemFlags DiveTripModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags diveFlags = QAbstractItemModel::flags(index); + if (index.isValid()) { + diveFlags |= Qt::ItemIsSelectable|Qt::ItemIsEnabled; + } + return diveFlags; +} + + +QVariant DiveTripModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (role != Qt::DisplayRole) + return QVariant(); + + DiveItem *item = static_cast(index.internalPointer()); + + QVariant retVal; + switch (index.column()) { + case DIVE_NUMBER: + retVal = QVariant(item->diveNumber()); + break; + case DIVE_DATE_TIME: + retVal = QVariant(item->diveDateTime()); + break; + case DIVE_DURATION: + retVal = QVariant(item->diveDuration()); + break; + case DIVE_DEPTH: + retVal = QVariant(item->diveDepth()); + break; + case DIVE_LOCATION: + retVal = QVariant(item->diveLocation()); + break; + default: + return QVariant(); + }; + return retVal; +} + + +QVariant DiveTripModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + if (section == DIVE_NUMBER) { + return tr("Dive number"); + } else if (section == DIVE_DATE_TIME) { + return tr("Date"); + } else if (section == DIVE_DURATION) { + return tr("Duration"); + } else if (section == DIVE_DEPTH) { + return tr("Depth"); + } else if (section == DIVE_LOCATION) { + return tr("Location"); + } + } + return QVariant(); +} + +int DiveTripModel::rowCount(const QModelIndex &parent) const +{ + /* only allow kids in column 0 */ + if (parent.isValid() && parent.column() > 0){ + return 0; + } + DiveItem *item = itemForIndex(parent); + return item ? item->childCount() : 0; +} + + + +int DiveTripModel::columnCount(const QModelIndex &parent) const +{ + return parent.isValid() && parent.column() != 0 ? 0 : COLUMNS; +} + + +QModelIndex DiveTripModel::index(int row, int column, const QModelIndex &parent) const +{ + + if (!rootItem || row < 0 || column < 0 || column >= COLUMNS || + (parent.isValid() && parent.column() != 0)) + return QModelIndex(); + + DiveItem *parentItem = itemForIndex(parent); + Q_ASSERT(parentItem); + if (DiveItem *item = parentItem->childAt(row)){ + return createIndex(row, column, item); + } + return QModelIndex(); +} + + +QModelIndex DiveTripModel::parent(const QModelIndex &childIndex) const +{ + if (!childIndex.isValid()) + return QModelIndex(); + + DiveItem *child = static_cast(childIndex.internalPointer()); + DiveItem *parent = child->parent(); + + if (parent == rootItem) + return QModelIndex(); + + return createIndex(parent->rowOfChild(child), 0, parent); +} + + +DiveItem* DiveTripModel::itemForIndex(const QModelIndex &index) const +{ + if (index.isValid()) { + DiveItem *item = static_cast(index.internalPointer()); + return item; + } + return rootItem; +} diff --git a/qt-ui/divetripmodel.h b/qt-ui/divetripmodel.h new file mode 100644 index 000000000..8c8a829e2 --- /dev/null +++ b/qt-ui/divetripmodel.h @@ -0,0 +1,80 @@ +#ifndef DIVETRIPMODEL_H +#define DIVETRIPMODEL_H + +#include + +/*! A DiveItem for use with a DiveTripModel + * + * A simple class which wraps basic stats for a dive (e.g. duration, depth) and + * tidies up after it's children. This is done manually as we don't inherit from + * QObject. + * +*/ +class DiveItem +{ +public: + explicit DiveItem(): number(0), dateTime(QString()), duration(0.0), depth(0.0), location(QString()) {parentItem = 0;} + explicit DiveItem(int num, QString dt, float, float, QString loc, DiveItem *parent = 0); + ~DiveItem() { qDeleteAll(childlist); } + + int diveNumber() const { return number; } + QString diveDateTime() const { return dateTime; } + float diveDuration() const { return duration; } + float diveDepth() const { return depth; } + QString diveLocation() const { return location; } + + DiveItem *parent() const { return parentItem; } + DiveItem *childAt(int row) const { return childlist.value(row); } + int rowOfChild(DiveItem *child) const { return childlist.indexOf(child); } + int childCount() const { return childlist.count(); } + bool hasChildren() const { return !childlist.isEmpty(); } + QList children() const { return childlist; } + void addChild(DiveItem* item) { item->parentItem = this; childlist << item; } /* parent = self */ + + +private: + + int number; + QString dateTime; + float duration; + float depth; + QString location; + + DiveItem *parentItem; + QList childlist; + +}; + + +enum Column {DIVE_NUMBER, DIVE_DATE_TIME, DIVE_DURATION, DIVE_DEPTH, DIVE_LOCATION, COLUMNS}; + + +/*! An AbstractItemModel for recording dive trip information such as a list of dives. +* +*/ +class DiveTripModel : public QAbstractItemModel +{ +public: + + DiveTripModel(const QString &filename, QObject *parent = 0); + + Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + int rowCount(const QModelIndex &parent) const; + + int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const; + virtual QModelIndex parent(const QModelIndex &child) const; + + DiveItem *itemForIndex(const QModelIndex &) const; + +private: + + DiveItem *rootItem; + QString filename; + +}; + +#endif // DIVETRIPMODEL_H diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index 56c39015b..03f6b6ca6 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -4,9 +4,38 @@ #include #include +#include "divelistview.h" +#include "divetripmodel.h" + MainWindow::MainWindow() : ui(new Ui::MainWindow()) { ui->setupUi(this); + + /* may want to change ctor to avoid filename as 1st param. + * here we just use an empty string + */ + model = new DiveTripModel("",this); + if (model){ + ui->ListWidget->setModel(model); + } + /* add in dives here. + * we need to root to parent all top level dives + * trips need more work as it complicates parent/child stuff. + * + * We show how to obtain the root and add a demo dive + * + * Todo: work through integration with current list of dives/trips + * Todo: look at alignment/format of e.g. duration in view + */ + DiveItem *dive = 0; + DiveItem *root = model->itemForIndex(QModelIndex()); + if (root){ + dive = new DiveItem(1,QString("01/03/13"),14.2, 29.0,QString("Wraysbury"),root); + + Q_UNUSED(dive) + } + + } void MainWindow::on_actionNew_triggered() diff --git a/qt-ui/mainwindow.h b/qt-ui/mainwindow.h index 51b428c5f..662d07cc5 100644 --- a/qt-ui/mainwindow.h +++ b/qt-ui/mainwindow.h @@ -3,6 +3,7 @@ #include +class DiveTripModel; namespace Ui { @@ -60,6 +61,7 @@ private Q_SLOTS: private: Ui::MainWindow *ui; + DiveTripModel *model; }; #endif diff --git a/qt-ui/mainwindow.ui b/qt-ui/mainwindow.ui index 65b18c757..6ece13f57 100644 --- a/qt-ui/mainwindow.ui +++ b/qt-ui/mainwindow.ui @@ -27,7 +27,7 @@ - + @@ -291,6 +291,11 @@
maintab.h
1 + + DiveListView + QTreeView +
divelistview.h
+
-- cgit v1.2.3-70-g09d2 From 92397a2bad3d1a2bc261dee67d230e3caa13b8d8 Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Sat, 13 Apr 2013 10:17:59 -0300 Subject: Started the real code on the Qt Interface. 1 - Open File already open files, it tries to not break the Gtk version, but some methods on the GTK version still need to be called inside Qt because the code is too tight-coupled. 2 - Close file already close files, same comments for the open file dialog applies here. 3 - The code for adding new cylinders in the cylinder dialog is done, already works and it's integrated with the system. There's a need to implement the edit and delete now, but it will be easyer since I'm starting to not get lost on the code. 4 - Some functions that were used to convert unities have been moved to convert.h ( can be changed later, put there because it's easyer to find something that converts in a convert.h =p ) because they were static functions that operated in the GTK version but I need those functions in the Qt version too. [Dirk Hohndel: lots and lots of whitespace and coding style changes] Signed-off-by: Tomaz Canabrava Signed-off-by: Dirk Hohndel --- Makefile | 6 +- conversions.h | 16 +++ equipment.c | 6 +- qt-gui.cpp | 8 ++ qt-ui/addcylinderdialog.cpp | 48 +++++++ qt-ui/addcylinderdialog.h | 24 ++++ qt-ui/addcylinderdialog.ui | 303 ++++++++++++++++++++++++++++++++++++++++++++ qt-ui/maintab.cpp | 68 +++++++++- qt-ui/maintab.h | 17 ++- qt-ui/maintab.ui | 12 +- qt-ui/mainwindow.cpp | 107 +++++++++++++++- qt-ui/mainwindow.h | 3 + qt-ui/models.cpp | 175 +++++++++++++++++++++++++ qt-ui/models.h | 44 +++++++ 14 files changed, 823 insertions(+), 14 deletions(-) create mode 100644 conversions.h create mode 100644 qt-ui/addcylinderdialog.cpp create mode 100644 qt-ui/addcylinderdialog.h create mode 100644 qt-ui/addcylinderdialog.ui create mode 100644 qt-ui/models.cpp create mode 100644 qt-ui/models.h (limited to 'Makefile') diff --git a/Makefile b/Makefile index 9e8e69b2d..81e3648c9 100644 --- a/Makefile +++ b/Makefile @@ -177,13 +177,15 @@ ifneq ($(strip $(LIBXSLT)),) XSLT=-DXSLT='"$(XSLTDIR)"' endif -LIBS = $(LIBQT) $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGTK) $(LIBGCONF2) $(LIBDIVECOMPUTER) $(EXTRALIBS) $(LIBZIP) -lpthread -lm $(LIBOSMGPSMAP) $(LIBSOUP) $(LIBWINSOCK) +LIBS = $(LIBQT) $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGTK) $(LIBGCONF2) $(LIBDIVECOMPUTER) \ + $(EXTRALIBS) $(LIBZIP) -lpthread -lm $(LIBOSMGPSMAP) $(LIBSOUP) $(LIBWINSOCK) MSGLANGS=$(notdir $(wildcard po/*.po)) MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.mo)) -QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o qt-ui/divelistview.o qt-ui/divetripmodel.o +QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o qt-ui/divelistview.o \ + qt-ui/divetripmodel.o qt-ui/addcylinderdialog.o qt-ui/models.o OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o divelist-gtk.o deco.o \ planner.o planner-gtk.o \ diff --git a/conversions.h b/conversions.h new file mode 100644 index 000000000..f59fcc96b --- /dev/null +++ b/conversions.h @@ -0,0 +1,16 @@ +/* + * conversions.h + * + * Helpers to convert between different units + * + */ +#ifdef __cplusplus +extern "C" { +#endif + +void convert_volume_pressure(int ml, int mbar, double *v, double *p); +int convert_pressure(int mbar, double *p); + +#ifdef __cplusplus +} +#endif diff --git a/equipment.c b/equipment.c index 2d90f7a52..a1d156f94 100644 --- a/equipment.c +++ b/equipment.c @@ -18,6 +18,7 @@ #include "display.h" #include "display-gtk.h" #include "divelist.h" +#include "conversions.h" static GtkListStore *cylinder_model, *weightsystem_model; @@ -70,7 +71,7 @@ struct ws_widget { }; /* we want bar - so let's not use our unit functions */ -static int convert_pressure(int mbar, double *p) +int convert_pressure(int mbar, double *p) { int decimals = 1; double pressure; @@ -86,7 +87,7 @@ static int convert_pressure(int mbar, double *p) return decimals; } -static void convert_volume_pressure(int ml, int mbar, double *v, double *p) +void convert_volume_pressure(int ml, int mbar, double *v, double *p) { double volume, pressure; @@ -724,6 +725,7 @@ static void fill_cylinder_info(struct cylinder_widget *cylinder, cylinder_t *cyl /* * Also, insert it into the model if it doesn't already exist */ + // WARNING: GTK-Specific Code. add_cylinder(cylinder, desc, ml, mbar); } diff --git a/qt-gui.cpp b/qt-gui.cpp index e667e97bf..745457763 100644 --- a/qt-gui.cpp +++ b/qt-gui.cpp @@ -126,6 +126,7 @@ static void on_info_bar_response(GtkWidget *widget, gint response, void report_error(GError* error) { + qDebug("Warning: Calling GTK-Specific Code."); if (error == NULL) { return; @@ -253,6 +254,8 @@ static void file_save(GtkWidget *w, gpointer data) static gboolean ask_save_changes() { + //WARNING: Porting to Qt + qDebug("This method is being ported to Qt, please, stop using it. "); GtkWidget *dialog, *label, *content; gboolean quit = TRUE; dialog = gtk_dialog_new_with_buttons(_("Save Changes?"), @@ -291,6 +294,7 @@ static gboolean ask_save_changes() static void file_close(GtkWidget *w, gpointer data) { + qDebug("Calling an already ported-to-qt Gtk method"); if (unsaved_changes()) if (ask_save_changes() == FALSE) return; @@ -319,8 +323,12 @@ static void file_close(GtkWidget *w, gpointer data) show_dive_info(NULL); } +//##################################################################### +//###### ALREAADY PORTED TO Qt. DELETE ME WHEN NOT MORE USERFUL. # +//##################################################################### static void file_open(GtkWidget *w, gpointer data) { + qDebug("Calling an already ported-to-qt Gtk method."); GtkWidget *dialog; GtkFileFilter *filter; const char *current_default; diff --git a/qt-ui/addcylinderdialog.cpp b/qt-ui/addcylinderdialog.cpp new file mode 100644 index 000000000..6f2294a25 --- /dev/null +++ b/qt-ui/addcylinderdialog.cpp @@ -0,0 +1,48 @@ +#include "addcylinderdialog.h" +#include "ui_addcylinderdialog.h" +#include +#include +#include "../conversions.h" + + +AddCylinderDialog::AddCylinderDialog(QWidget *parent) : ui(new Ui::AddCylinderDialog()) +{ + ui->setupUi(this); +} + +void AddCylinderDialog::setCylinder(cylinder_t *cylinder) +{ + double volume, pressure; + int index; + + currentCylinder = cylinder; + convert_volume_pressure(cylinder->type.size.mliter, cylinder->type.workingpressure.mbar, &volume, &pressure); + + index = ui->cylinderType->findText(QString(cylinder->type.description)); + ui->cylinderType->setCurrentIndex(index); + ui->size->setValue(volume); + ui->pressure->setValue(pressure); + + ui->o2percent->setValue(cylinder->gasmix.o2.permille / 10.0); + ui->hepercent->setValue(cylinder->gasmix.he.permille / 10.0); + + convert_pressure(cylinder->start.mbar, &pressure); + ui->start->setValue(pressure); + + convert_pressure(cylinder->end.mbar, &pressure); + ui->end->setValue(pressure); +} + +void AddCylinderDialog::updateCylinder() +{ + QByteArray description = ui->cylinderType->currentText().toLocal8Bit(); + + currentCylinder->type.description = description.data(); + currentCylinder->type.size.mliter = ui->size->value(); + currentCylinder->type.workingpressure.mbar = ui->pressure->value(); + currentCylinder->gasmix.o2.permille = ui->o2percent->value(); + currentCylinder->gasmix.he.permille = ui->hepercent->value(); + currentCylinder->start.mbar = ui->start->value(); + currentCylinder->end.mbar = ui->end->value(); +} + diff --git a/qt-ui/addcylinderdialog.h b/qt-ui/addcylinderdialog.h new file mode 100644 index 000000000..b32494c05 --- /dev/null +++ b/qt-ui/addcylinderdialog.h @@ -0,0 +1,24 @@ +#ifndef ADDCYLINDERDIALOG_H +#define ADDCYLINDERDIALOG_H + +#include +#include "../dive.h" + +namespace Ui{ + class AddCylinderDialog; +} + +class AddCylinderDialog : public QDialog{ + Q_OBJECT +public: + explicit AddCylinderDialog(QWidget* parent = 0); + void setCylinder(cylinder_t *cylinder); + void updateCylinder(); + +private: + Ui::AddCylinderDialog *ui; + cylinder_t *currentCylinder; +}; + + +#endif diff --git a/qt-ui/addcylinderdialog.ui b/qt-ui/addcylinderdialog.ui new file mode 100644 index 000000000..8bb0428b7 --- /dev/null +++ b/qt-ui/addcylinderdialog.ui @@ -0,0 +1,303 @@ + + + AddCylinderDialog + + + + 0 + 0 + 408 + 298 + + + + Dialog + + + + + + Cylinder + + + + QFormLayout::ExpandingFieldsGrow + + + + + Type + + + + + + + + 0 + 0 + + + + + + + + Size + + + + + + + + 0 + 0 + + + + + + + + Pressure + + + + + + + + 0 + 0 + + + + + + + + + + + Pressure + + + + + + Start + + + + + + + End + + + + + + + false + + + + 0 + 0 + + + + + + + + false + + + + 0 + 0 + + + + + + + + + + + + + + + + + + Gas Mix + + + + + + O2% + + + + + + + false + + + + 0 + 0 + + + + + + + + He% + + + + + + + false + + + + 0 + 0 + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + AddCylinderDialog + accept() + + + 248 + 269 + + + 157 + 260 + + + + + buttonBox + rejected() + AddCylinderDialog + reject() + + + 290 + 269 + + + 286 + 260 + + + + + checkBox + clicked(bool) + start + setEnabled(bool) + + + 216 + 46 + + + 280 + 66 + + + + + checkBox + clicked(bool) + end + setEnabled(bool) + + + 226 + 48 + + + 268 + 100 + + + + + checkBox_2 + clicked(bool) + o2percent + setEnabled(bool) + + + 214 + 165 + + + 260 + 190 + + + + + checkBox_2 + clicked(bool) + hepercent + setEnabled(bool) + + + 228 + 165 + + + 262 + 216 + + + + + diff --git a/qt-ui/maintab.cpp b/qt-ui/maintab.cpp index c630965d2..b957fd1c7 100644 --- a/qt-ui/maintab.cpp +++ b/qt-ui/maintab.cpp @@ -1,8 +1,74 @@ #include "maintab.h" #include "ui_maintab.h" +#include "addcylinderdialog.h" + +#include MainTab::MainTab(QWidget *parent) : QTabWidget(parent), - ui(new Ui::MainTab()) + ui(new Ui::MainTab()), + weightModel(new WeightModel()), + cylindersModel(new CylindersModel()) { ui->setupUi(this); + ui->cylinders->setModel(cylindersModel); + ui->weights->setModel(weightModel); +} + +void MainTab::clearEquipment() +{ +} + +void MainTab::clearInfo() +{ + QList labels; + labels << ui->sac << ui->otu << ui->oxygenhelium << ui->gasused + << ui->date << ui->divetime << ui->surfinterval + << ui->maxdepth << ui->avgdepth << ui->visibility + << ui->watertemperature << ui->airtemperature << ui->airpress; + + Q_FOREACH(QLabel *l, labels){ + l->setText(QString()); + } +} + +void MainTab::clearStats() +{ + QList labels; + labels << ui->maxdepth_2 << ui->mindepth << ui->avgdepth + << ui->maxsac << ui->minsac << ui->avgsac + << ui->dives << ui->maxtemp << ui->mintemp << ui->avgtemp + << ui->totaltime << ui->avgtime << ui->longestdive << ui->shortestdive; + + Q_FOREACH(QLabel *l, labels){ + l->setText(QString()); + } +} + +void MainTab::on_addCylinder_clicked() +{ + AddCylinderDialog dialog(this); + cylinder_t *newCylinder = (cylinder_t*) malloc(sizeof(cylinder_t)); + newCylinder->type.description = ""; + + dialog.setCylinder(newCylinder); + int result = dialog.exec(); + if (result == QDialog::Rejected){ + return; + } + + dialog.updateCylinder(); + cylindersModel->add(newCylinder); +} + +void MainTab::on_editCylinder_clicked() +{ +} + +void MainTab::on_delCylinder_clicked() +{ +} + +void MainTab::reload() +{ + cylindersModel->update(); } diff --git a/qt-ui/maintab.h b/qt-ui/maintab.h index 40904ab12..0e9f285ac 100644 --- a/qt-ui/maintab.h +++ b/qt-ui/maintab.h @@ -2,6 +2,9 @@ #define MAINTAB_H #include +#include + +#include "models.h" namespace Ui { @@ -13,8 +16,20 @@ class MainTab : public QTabWidget Q_OBJECT public: MainTab(QWidget *parent); + void clearStats(); + void clearInfo(); + void clearEquipment(); + void reload(); + +public Q_SLOTS: + void on_addCylinder_clicked(); + void on_editCylinder_clicked(); + void on_delCylinder_clicked(); + private: Ui::MainTab *ui; + WeightModel *weightModel; + CylindersModel *cylindersModel; }; -#endif \ No newline at end of file +#endif diff --git a/qt-ui/maintab.ui b/qt-ui/maintab.ui index 7432dc466..0f46d91a5 100644 --- a/qt-ui/maintab.ui +++ b/qt-ui/maintab.ui @@ -14,7 +14,7 @@ TabWidget - 0 + 2 @@ -390,19 +390,19 @@ - + - + Edit - + Add @@ -422,7 +422,7 @@ - + Delete @@ -438,7 +438,7 @@ - + diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index 03f6b6ca6..fdc823d9c 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -2,11 +2,19 @@ #include "ui_mainwindow.h" #include +#include +#include #include #include "divelistview.h" #include "divetripmodel.h" +#include "glib.h" +#include "../dive.h" +#include "../divelist.h" +#include "../pref.h" + + MainWindow::MainWindow() : ui(new Ui::MainWindow()) { ui->setupUi(this); @@ -45,7 +53,31 @@ void MainWindow::on_actionNew_triggered() void MainWindow::on_actionOpen_triggered() { - qDebug("actionOpen"); + QString filename = QFileDialog::getOpenFileName(this, tr("Open File"), QDir::homePath(), filter()); + if (filename.isEmpty()){ + return; + } + + // Needed to convert to char* + QByteArray fileNamePtr = filename.toLocal8Bit(); + + on_actionClose_triggered(); + + GError *error = NULL; + parse_file(fileNamePtr.data(), &error); + set_filename(fileNamePtr.data(), TRUE); + + if (error != NULL) + { + QMessageBox::warning(this, "Error", error->message); + g_error_free(error); + error = NULL; + } + + //WARNING: Port This method to Qt + report_dives(FALSE, FALSE); + + ui->InfoWidget->reload(); } void MainWindow::on_actionSave_triggered() @@ -59,7 +91,37 @@ void MainWindow::on_actionSaveAs_triggered() } void MainWindow::on_actionClose_triggered() { - qDebug("actionClose"); + if (unsaved_changes() && (askSaveChanges() == FALSE)) + { + return; + } + + /* free the dives and trips */ + while (dive_table.nr) + { + delete_single_dive(0); + } + mark_divelist_changed(FALSE); + + /* clear the selection and the statistics */ + selected_dive = 0; + + //WARNING: Port this to Qt. + //process_selected_dives(); + + ui->InfoWidget->clearStats(); + ui->InfoWidget->clearInfo(); + ui->InfoWidget->clearEquipment(); + + clear_events(); + show_dive_stats(NULL); + + /* redraw the screen */ + //WARNING: Port this to Qt. + dive_list_update_dives(); + + // WARNING? Port this to Qt. + show_dive_info(NULL); } void MainWindow::on_actionImport_triggered() @@ -190,3 +252,44 @@ void MainWindow::on_actionUserManual_triggered() { qDebug("actionUserManual"); } + +QString MainWindow::filter() +{ + QString f; + f += "ALL ( *.xml *.XML *.uddf *.udcf *.UDFC *.jlb *.JLB "; +#ifdef LIBZIP + f += "*.sde *.SDE *.dld *.DLD "; +#endif +#ifdef SQLITE3 + f += "*.db"; +#endif + f += ");;"; + + f += "XML (*.xml *.XML);;"; + f += "UDDF (*.uddf);;"; + f += "UDCF (*.udcf *.UDCF);;"; + f += "JLB (*.jlb *.JLB);;"; + +#ifdef LIBZIP + f += "SDE (*.sde *.SDE);;"; + f += "DLD (*.dld *.DLD);;"; +#endif +#ifdef SQLITE3 + f += "DB (*.db)"; +#endif + + return f; +} + +bool MainWindow::askSaveChanges() +{ + QString message = ! existing_filename ? tr("You have unsaved changes\nWould you like to save those before closing the datafile?") + : tr("You have unsaved changes to file: %1 \nWould you like to save those before closing the datafile?").arg(existing_filename); + + if (QMessageBox::question(this, tr("Save Changes?"), message) == QMessageBox::Ok){ + // WARNING: Port. + // file_save(NULL,NULL); + return true; + } + return false; +} diff --git a/qt-ui/mainwindow.h b/qt-ui/mainwindow.h index 662d07cc5..34acf2b67 100644 --- a/qt-ui/mainwindow.h +++ b/qt-ui/mainwindow.h @@ -62,6 +62,9 @@ private Q_SLOTS: private: Ui::MainWindow *ui; DiveTripModel *model; + QString filter(); + bool askSaveChanges(); + }; #endif diff --git a/qt-ui/models.cpp b/qt-ui/models.cpp new file mode 100644 index 000000000..dac1c7215 --- /dev/null +++ b/qt-ui/models.cpp @@ -0,0 +1,175 @@ +#include "models.h" +#include "../dive.h" + +CylindersModel::CylindersModel(QObject* parent): QAbstractTableModel(parent) +{ +} + +QVariant CylindersModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant ret; + if (orientation == Qt::Vertical) { + return ret; + } + + if (role == Qt::DisplayRole) { + switch(section) { + case TYPE: + ret = tr("Type"); + break; + case SIZE: + ret = tr("Size"); + break; + case MAXPRESS: + ret = tr("MaxPress"); + break; + case START: + ret = tr("Start"); + break; + case END: + ret = tr("End"); + break; + case O2: + ret = tr("O2%"); + break; + case HE: + ret = tr("He%"); + break; + } + } + return ret; +} + +int CylindersModel::columnCount(const QModelIndex& parent) const +{ + return 7; +} + +QVariant CylindersModel::data(const QModelIndex& index, int role) const +{ + QVariant ret; + if (!index.isValid() || index.row() >= MAX_CYLINDERS) { + return ret; + } + + dive *d = get_dive(selected_dive); + cylinder_t& cyl = d->cylinder[index.row()]; + + if (role == Qt::DisplayRole) { + switch(index.column()) { + case TYPE: + ret = QString(cyl.type.description); + break; + case SIZE: + ret = cyl.type.size.mliter; + break; + case MAXPRESS: + ret = cyl.type.workingpressure.mbar; + break; + case START: + ret = cyl.start.mbar; + break; + case END: + ret = cyl.end.mbar; + break; + case O2: + ret = cyl.gasmix.o2.permille; + break; + case HE: + ret = cyl.gasmix.he.permille; + break; + } + } + return ret; +} + +int CylindersModel::rowCount(const QModelIndex& parent) const +{ + return usedRows[currentDive]; +} + +void CylindersModel::add(cylinder_t* cyl) +{ + if (usedRows[currentDive] >= MAX_CYLINDERS) { + free(cyl); + } + + int row = usedRows[currentDive]; + + cylinder_t& cylinder = currentDive->cylinder[row]; + + cylinder.end.mbar = cyl->end.mbar; + cylinder.start.mbar = cyl->start.mbar; + + beginInsertRows(QModelIndex(), row, row); + usedRows[currentDive]++; + endInsertRows(); +} + +void CylindersModel::update() +{ + if (usedRows[currentDive] > 0) { + beginRemoveRows(QModelIndex(), 0, usedRows[currentDive]-1); + endRemoveRows(); + } + + currentDive = get_dive(selected_dive); + if (usedRows[currentDive] > 0) { + beginInsertRows(QModelIndex(), 0, usedRows[currentDive]-1); + endInsertRows(); + } +} + +void CylindersModel::clear() +{ + if (usedRows[currentDive] > 0) { + beginRemoveRows(QModelIndex(), 0, usedRows[currentDive]-1); + usedRows[currentDive] = 0; + endRemoveRows(); + } +} + +void WeightModel::clear() +{ +} + +int WeightModel::columnCount(const QModelIndex& parent) const +{ + return 0; +} + +QVariant WeightModel::data(const QModelIndex& index, int role) const +{ + return QVariant(); +} + +int WeightModel::rowCount(const QModelIndex& parent) const +{ + return rows; +} + +QVariant WeightModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant ret; + if (orientation == Qt::Vertical) { + return ret; + } + + switch(section){ + case TYPE: + ret = tr("Type"); + break; + case WEIGHT: + ret = tr("Weight"); + break; + } + return ret; +} + +void WeightModel::add(weight_t* weight) +{ +} + +void WeightModel::update() +{ +} diff --git a/qt-ui/models.h b/qt-ui/models.h new file mode 100644 index 000000000..0d0c7b41d --- /dev/null +++ b/qt-ui/models.h @@ -0,0 +1,44 @@ +#ifndef MODELS_H +#define MODELS_H + +#include +#include "../dive.h" + +class CylindersModel : public QAbstractTableModel { +Q_OBJECT +public: + enum {TYPE, SIZE, MAXPRESS, START, END, O2, HE}; + + explicit CylindersModel(QObject* parent = 0); + /*reimp*/ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + /*reimp*/ int columnCount(const QModelIndex& parent = QModelIndex()) const; + /*reimp*/ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + /*reimp*/ int rowCount(const QModelIndex& parent = QModelIndex()) const; + + void add(cylinder_t *cyl); + void clear(); + void update(); +private: + dive *currentDive; + + /* Since the dive doesn`t stores the number of cylinders that + * it has ( max 8 ) and since I don`t want to make a + * model-for-each-dive, let`s hack this here instead. */ + QMap usedRows; +}; + +class WeightModel : public QAbstractTableModel { + enum{TYPE, WEIGHT}; + /*reimp*/ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + /*reimp*/ int columnCount(const QModelIndex& parent = QModelIndex()) const; + /*reimp*/ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + /*reimp*/ int rowCount(const QModelIndex& parent = QModelIndex()) const; + + void add(weight_t *weight); + void clear(); + void update(); +private: + int rows; +}; + +#endif -- cgit v1.2.3-70-g09d2 From 14e133321f4ac2cff9eb0b84ba9687e5d42e3e46 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Sat, 13 Apr 2013 18:11:57 -0700 Subject: Remove the mandatory -fPIC from CXXFLAGS This isn't necessary. There's code below adding -fPIE when necessary only. Signed-off-by: Thiago Macieira Signed-off-by: Dirk Hohndel --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 81e3648c9..8bbd26fb7 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ VERSION=3.0.2 CC=gcc CFLAGS=-Wall -Wno-pointer-sign -g $(CLCFLAGS) -DGSEAL_ENABLE CXX=g++ -CXXFLAGS=-Wall -g $(CLCFLAGS) -fPIC -DQT_NO_KEYWORDS +CXXFLAGS=-Wall -g $(CLCFLAGS) -DQT_NO_KEYWORDS INSTALL=install PKGCONFIG=pkg-config XML2CONFIG=xml2-config -- cgit v1.2.3-70-g09d2 From f5c958ad73db696e473aaa35e144f4c9d8ae24de Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sat, 13 Apr 2013 20:44:02 -0700 Subject: Add Qtr_ macros that uses gettext in a tr() compatible manner This should wrap gettext nicely and replace the "_()" macros we use in C code. Also added comments to the top of all the new files. Suggested-by: Thiago Macieira Signed-off-by: Dirk Hohndel --- Makefile | 6 ++++-- qt-gui.cpp | 5 +++-- qt-ui/addcylinderdialog.cpp | 6 ++++++ qt-ui/addcylinderdialog.h | 6 ++++++ qt-ui/divelistview.cpp | 6 ++++++ qt-ui/divelistview.h | 6 ++++++ qt-ui/divetripmodel.cpp | 17 ++++++++++++----- qt-ui/divetripmodel.h | 6 ++++++ qt-ui/maintab.cpp | 7 +++++++ qt-ui/maintab.h | 6 ++++++ qt-ui/mainwindow.cpp | 15 +++++++++++---- qt-ui/mainwindow.h | 6 ++++++ qt-ui/models.cpp | 25 ++++++++++++++++--------- qt-ui/models.h | 6 ++++++ qt-ui/plotareascene.cpp | 6 ++++++ qt-ui/plotareascene.h | 6 ++++++ 16 files changed, 113 insertions(+), 22 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 8bbd26fb7..e33d92053 100644 --- a/Makefile +++ b/Makefile @@ -110,8 +110,10 @@ else QT_MODULES = QtGui QT_CORE = QtCore endif + +# we need GLIB2CFLAGS for gettext +QTCXXFLAGS = $(shell $(PKGCONFIG) --cflags $(QT_MODULES)) $(GLIB2CFLAGS) LIBQT = $(shell $(PKGCONFIG) --libs $(QT_MODULES)) -QTCXXFLAGS = $(shell $(PKGCONFIG) --cflags $(QT_MODULES)) LIBGTK = $(shell $(PKGCONFIG) --libs gtk+-2.0 glib-2.0) LIBDIVECOMPUTERCFLAGS = $(LIBDIVECOMPUTERINCLUDES) @@ -303,7 +305,7 @@ $(INFOPLIST): $(INFOPLISTINPUT) # Transifex merge the translations update-po-files: - xgettext -o po/subsurface-new.pot -s -k_ -kN_ --keyword=C_:1c,2 --add-comments="++GETTEXT" *.c + xgettext -o po/subsurface-new.pot -s -k_ -kN_ -kQtr_ --keyword=C_:1c,2 --add-comments="++GETTEXT" *.c qt-ui/*.cpp tx push -s tx pull -af diff --git a/qt-gui.cpp b/qt-gui.cpp index 745457763..1621317c2 100644 --- a/qt-gui.cpp +++ b/qt-gui.cpp @@ -25,6 +25,7 @@ #include "version.h" #include "libdivecomputer.h" #include "qt-ui/mainwindow.h" +#include "qt-ui/common.h" #include #include @@ -1784,7 +1785,7 @@ void MainWindow::setCurrentFileName(const QString &fileName) if (fileName == m_currentFileName) return; m_currentFileName = fileName; - QString title = tr("Subsurface"); + QString title = Qtr_("Subsurface"); if (!m_currentFileName.isEmpty()) { QFileInfo fileInfo(m_currentFileName); title += " - " + fileInfo.fileName(); @@ -1797,7 +1798,7 @@ void MainWindow::on_actionOpen_triggered() QString defaultFileName = QString::fromUtf8(prefs.default_filename); QFileInfo fileInfo(defaultFileName); - QFileDialog dialog(this, tr("Open File"), fileInfo.path()); + QFileDialog dialog(this, Qtr_("Open File"), fileInfo.path()); dialog.setFileMode(QFileDialog::ExistingFile); dialog.selectFile(defaultFileName); dialog.setNameFilters(fileNameFilters()); diff --git a/qt-ui/addcylinderdialog.cpp b/qt-ui/addcylinderdialog.cpp index 6f2294a25..043f29907 100644 --- a/qt-ui/addcylinderdialog.cpp +++ b/qt-ui/addcylinderdialog.cpp @@ -1,3 +1,9 @@ +/* + * addcylinderdialog.cpp + * + * classes for the add cylinder dialog of Subsurface + * + */ #include "addcylinderdialog.h" #include "ui_addcylinderdialog.h" #include diff --git a/qt-ui/addcylinderdialog.h b/qt-ui/addcylinderdialog.h index b32494c05..652f7b362 100644 --- a/qt-ui/addcylinderdialog.h +++ b/qt-ui/addcylinderdialog.h @@ -1,3 +1,9 @@ +/* + * addcylinderdialog.h + * + * header file for the add cylinder dialog of Subsurface + * + */ #ifndef ADDCYLINDERDIALOG_H #define ADDCYLINDERDIALOG_H diff --git a/qt-ui/divelistview.cpp b/qt-ui/divelistview.cpp index eafbdd384..a8b1eff05 100644 --- a/qt-ui/divelistview.cpp +++ b/qt-ui/divelistview.cpp @@ -1,3 +1,9 @@ +/* + * divelistview.cpp + * + * classes for the divelist of Subsurface + * + */ #include "divelistview.h" DiveListView::DiveListView(QWidget *parent) : QTreeView(parent) diff --git a/qt-ui/divelistview.h b/qt-ui/divelistview.h index 3ac123a14..be9774c5c 100644 --- a/qt-ui/divelistview.h +++ b/qt-ui/divelistview.h @@ -1,3 +1,9 @@ +/* + * divelistview.h + * + * header file for the dive list of Subsurface + * + */ #ifndef DIVELISTVIEW_H #define DIVELISTVIEW_H diff --git a/qt-ui/divetripmodel.cpp b/qt-ui/divetripmodel.cpp index a03603bcf..0a2944d8b 100644 --- a/qt-ui/divetripmodel.cpp +++ b/qt-ui/divetripmodel.cpp @@ -1,3 +1,10 @@ +/* + * divetripmodel.cpp + * + * classes for the dive trip list in Subsurface + */ + +#include "common.h" #include "divetripmodel.h" @@ -63,15 +70,15 @@ QVariant DiveTripModel::headerData(int section, Qt::Orientation orientation, int { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { if (section == DIVE_NUMBER) { - return tr("Dive number"); + return Qtr_("Dive number"); } else if (section == DIVE_DATE_TIME) { - return tr("Date"); + return Qtr_("Date"); } else if (section == DIVE_DURATION) { - return tr("Duration"); + return Qtr_("Duration"); } else if (section == DIVE_DEPTH) { - return tr("Depth"); + return Qtr_("Depth"); } else if (section == DIVE_LOCATION) { - return tr("Location"); + return Qtr_("Location"); } } return QVariant(); diff --git a/qt-ui/divetripmodel.h b/qt-ui/divetripmodel.h index 8c8a829e2..ad1815798 100644 --- a/qt-ui/divetripmodel.h +++ b/qt-ui/divetripmodel.h @@ -1,3 +1,9 @@ +/* + * divetripmodel.h + * + * header file for the divetrip model of Subsurface + * + */ #ifndef DIVETRIPMODEL_H #define DIVETRIPMODEL_H diff --git a/qt-ui/maintab.cpp b/qt-ui/maintab.cpp index b957fd1c7..72d8dfebc 100644 --- a/qt-ui/maintab.cpp +++ b/qt-ui/maintab.cpp @@ -1,3 +1,10 @@ +/* + * maintab.cpp + * + * classes for the "notebook" area of the main window of Subsurface + * + */ +#include "common.h" #include "maintab.h" #include "ui_maintab.h" #include "addcylinderdialog.h" diff --git a/qt-ui/maintab.h b/qt-ui/maintab.h index 0e9f285ac..44815fafc 100644 --- a/qt-ui/maintab.h +++ b/qt-ui/maintab.h @@ -1,3 +1,9 @@ +/* + * maintab.h + * + * header file for the main tab of Subsurface + * + */ #ifndef MAINTAB_H #define MAINTAB_H diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index fdc823d9c..d1cde044a 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -1,3 +1,10 @@ +/* + * mainwindow.cpp + * + * classes for the main UI window in Subsurface + */ + +#include "common.h" #include "mainwindow.h" #include "ui_mainwindow.h" @@ -53,7 +60,7 @@ void MainWindow::on_actionNew_triggered() void MainWindow::on_actionOpen_triggered() { - QString filename = QFileDialog::getOpenFileName(this, tr("Open File"), QDir::homePath(), filter()); + QString filename = QFileDialog::getOpenFileName(this, Qtr_("Open File"), QDir::homePath(), filter()); if (filename.isEmpty()){ return; } @@ -283,10 +290,10 @@ QString MainWindow::filter() bool MainWindow::askSaveChanges() { - QString message = ! existing_filename ? tr("You have unsaved changes\nWould you like to save those before closing the datafile?") - : tr("You have unsaved changes to file: %1 \nWould you like to save those before closing the datafile?").arg(existing_filename); + QString message = ! existing_filename ? Qtr_("You have unsaved changes\nWould you like to save those before closing the datafile?") + : Qtr_("You have unsaved changes to file: %1 \nWould you like to save those before closing the datafile?").arg(existing_filename); - if (QMessageBox::question(this, tr("Save Changes?"), message) == QMessageBox::Ok){ + if (QMessageBox::question(this, Qtr_("Save Changes?"), message) == QMessageBox::Ok){ // WARNING: Port. // file_save(NULL,NULL); return true; diff --git a/qt-ui/mainwindow.h b/qt-ui/mainwindow.h index 34acf2b67..43ebde7f5 100644 --- a/qt-ui/mainwindow.h +++ b/qt-ui/mainwindow.h @@ -1,3 +1,9 @@ +/* + * mainwindow.h + * + * header file for the main window of Subsurface + * + */ #ifndef MAINWINDOW_H #define MAINWINDOW_H diff --git a/qt-ui/models.cpp b/qt-ui/models.cpp index dac1c7215..a341c0c70 100644 --- a/qt-ui/models.cpp +++ b/qt-ui/models.cpp @@ -1,3 +1,10 @@ +/* + * models.cpp + * + * classes for the equipment models of Subsurface + * + */ +#include "common.h" #include "models.h" #include "../dive.h" @@ -15,25 +22,25 @@ QVariant CylindersModel::headerData(int section, Qt::Orientation orientation, in if (role == Qt::DisplayRole) { switch(section) { case TYPE: - ret = tr("Type"); + ret = Qtr_("Type"); break; case SIZE: - ret = tr("Size"); + ret = Qtr_("Size"); break; case MAXPRESS: - ret = tr("MaxPress"); + ret = Qtr_("MaxPress"); break; case START: - ret = tr("Start"); + ret = Qtr_("Start"); break; case END: - ret = tr("End"); + ret = Qtr_("End"); break; case O2: - ret = tr("O2%"); + ret = Qtr_("O2%"); break; case HE: - ret = tr("He%"); + ret = Qtr_("He%"); break; } } @@ -157,10 +164,10 @@ QVariant WeightModel::headerData(int section, Qt::Orientation orientation, int r switch(section){ case TYPE: - ret = tr("Type"); + ret = Qtr_("Type"); break; case WEIGHT: - ret = tr("Weight"); + ret = Qtr_("Weight"); break; } return ret; diff --git a/qt-ui/models.h b/qt-ui/models.h index 0d0c7b41d..697096f92 100644 --- a/qt-ui/models.h +++ b/qt-ui/models.h @@ -1,3 +1,9 @@ +/* + * models.h + * + * header file for the equipment models of Subsurface + * + */ #ifndef MODELS_H #define MODELS_H diff --git a/qt-ui/plotareascene.cpp b/qt-ui/plotareascene.cpp index e69de29bb..a728040f5 100644 --- a/qt-ui/plotareascene.cpp +++ b/qt-ui/plotareascene.cpp @@ -0,0 +1,6 @@ +/* + * plotareascene.cpp + * + * classes for profile plot area scene of Subsurface + * + */ diff --git a/qt-ui/plotareascene.h b/qt-ui/plotareascene.h index e69de29bb..a5b07d1be 100644 --- a/qt-ui/plotareascene.h +++ b/qt-ui/plotareascene.h @@ -0,0 +1,6 @@ +/* + * plotareascene.h + * + * header file for the profile plot area scene of Subsurface + * + */ -- cgit v1.2.3-70-g09d2 From d8e11439ad27063b0dad05b2f8f0f4cd7f3e7de1 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sun, 14 Apr 2013 06:44:29 -0700 Subject: Undoing the last Qtr_ hack The Qtr_ hack isn't needed as in commit 720fc15b2dcd ("Introduce QApplication") had already made sure that we are using gettext. I didn't revert the two commits as I wanted to keep the added header comments and fix the tooling in the Makefile as well. Signed-off-by: Dirk Hohndel --- Makefile | 2 +- qt-gui.cpp | 5 ++--- qt-ui/common.h | 20 -------------------- qt-ui/divetripmodel.cpp | 12 +++++------- qt-ui/maintab.cpp | 1 - qt-ui/mainwindow.cpp | 10 ++++------ qt-ui/models.cpp | 19 +++++++++---------- 7 files changed, 21 insertions(+), 48 deletions(-) delete mode 100644 qt-ui/common.h (limited to 'Makefile') diff --git a/Makefile b/Makefile index e33d92053..7778d2aa8 100644 --- a/Makefile +++ b/Makefile @@ -305,7 +305,7 @@ $(INFOPLIST): $(INFOPLISTINPUT) # Transifex merge the translations update-po-files: - xgettext -o po/subsurface-new.pot -s -k_ -kN_ -kQtr_ --keyword=C_:1c,2 --add-comments="++GETTEXT" *.c qt-ui/*.cpp + xgettext -o po/subsurface-new.pot -s -k_ -kN_ -ktr --keyword=C_:1c,2 --add-comments="++GETTEXT" *.c qt-ui/*.cpp tx push -s tx pull -af diff --git a/qt-gui.cpp b/qt-gui.cpp index 1621317c2..745457763 100644 --- a/qt-gui.cpp +++ b/qt-gui.cpp @@ -25,7 +25,6 @@ #include "version.h" #include "libdivecomputer.h" #include "qt-ui/mainwindow.h" -#include "qt-ui/common.h" #include #include @@ -1785,7 +1784,7 @@ void MainWindow::setCurrentFileName(const QString &fileName) if (fileName == m_currentFileName) return; m_currentFileName = fileName; - QString title = Qtr_("Subsurface"); + QString title = tr("Subsurface"); if (!m_currentFileName.isEmpty()) { QFileInfo fileInfo(m_currentFileName); title += " - " + fileInfo.fileName(); @@ -1798,7 +1797,7 @@ void MainWindow::on_actionOpen_triggered() QString defaultFileName = QString::fromUtf8(prefs.default_filename); QFileInfo fileInfo(defaultFileName); - QFileDialog dialog(this, Qtr_("Open File"), fileInfo.path()); + QFileDialog dialog(this, tr("Open File"), fileInfo.path()); dialog.setFileMode(QFileDialog::ExistingFile); dialog.selectFile(defaultFileName); dialog.setNameFilters(fileNameFilters()); diff --git a/qt-ui/common.h b/qt-ui/common.h deleted file mode 100644 index 6e00c80ed..000000000 --- a/qt-ui/common.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * common.h - * - * shared by all Qt/C++ code - * - */ - -#ifndef COMMON_H -#define COMMON_H - -#include -#include - -/* use this instead of tr() for translated string literals */ -inline QString Qtr_(const char *str) -{ - return QString::fromUtf8(gettext(str)); -} - -#endif diff --git a/qt-ui/divetripmodel.cpp b/qt-ui/divetripmodel.cpp index 0a2944d8b..5082494a0 100644 --- a/qt-ui/divetripmodel.cpp +++ b/qt-ui/divetripmodel.cpp @@ -3,8 +3,6 @@ * * classes for the dive trip list in Subsurface */ - -#include "common.h" #include "divetripmodel.h" @@ -70,15 +68,15 @@ QVariant DiveTripModel::headerData(int section, Qt::Orientation orientation, int { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { if (section == DIVE_NUMBER) { - return Qtr_("Dive number"); + return tr("Dive number"); } else if (section == DIVE_DATE_TIME) { - return Qtr_("Date"); + return tr("Date"); } else if (section == DIVE_DURATION) { - return Qtr_("Duration"); + return tr("Duration"); } else if (section == DIVE_DEPTH) { - return Qtr_("Depth"); + return tr("Depth"); } else if (section == DIVE_LOCATION) { - return Qtr_("Location"); + return tr("Location"); } } return QVariant(); diff --git a/qt-ui/maintab.cpp b/qt-ui/maintab.cpp index 72d8dfebc..53926cb25 100644 --- a/qt-ui/maintab.cpp +++ b/qt-ui/maintab.cpp @@ -4,7 +4,6 @@ * classes for the "notebook" area of the main window of Subsurface * */ -#include "common.h" #include "maintab.h" #include "ui_maintab.h" #include "addcylinderdialog.h" diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index d1cde044a..577b7fb67 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -3,8 +3,6 @@ * * classes for the main UI window in Subsurface */ - -#include "common.h" #include "mainwindow.h" #include "ui_mainwindow.h" @@ -60,7 +58,7 @@ void MainWindow::on_actionNew_triggered() void MainWindow::on_actionOpen_triggered() { - QString filename = QFileDialog::getOpenFileName(this, Qtr_("Open File"), QDir::homePath(), filter()); + QString filename = QFileDialog::getOpenFileName(this, tr("Open File"), QDir::homePath(), filter()); if (filename.isEmpty()){ return; } @@ -290,10 +288,10 @@ QString MainWindow::filter() bool MainWindow::askSaveChanges() { - QString message = ! existing_filename ? Qtr_("You have unsaved changes\nWould you like to save those before closing the datafile?") - : Qtr_("You have unsaved changes to file: %1 \nWould you like to save those before closing the datafile?").arg(existing_filename); + QString message = ! existing_filename ? tr("You have unsaved changes\nWould you like to save those before closing the datafile?") + : tr("You have unsaved changes to file: %1 \nWould you like to save those before closing the datafile?").arg(existing_filename); - if (QMessageBox::question(this, Qtr_("Save Changes?"), message) == QMessageBox::Ok){ + if (QMessageBox::question(this, tr("Save Changes?"), message) == QMessageBox::Ok){ // WARNING: Port. // file_save(NULL,NULL); return true; diff --git a/qt-ui/models.cpp b/qt-ui/models.cpp index a341c0c70..64fa6bac3 100644 --- a/qt-ui/models.cpp +++ b/qt-ui/models.cpp @@ -4,7 +4,6 @@ * classes for the equipment models of Subsurface * */ -#include "common.h" #include "models.h" #include "../dive.h" @@ -22,25 +21,25 @@ QVariant CylindersModel::headerData(int section, Qt::Orientation orientation, in if (role == Qt::DisplayRole) { switch(section) { case TYPE: - ret = Qtr_("Type"); + ret = tr("Type"); break; case SIZE: - ret = Qtr_("Size"); + ret = tr("Size"); break; case MAXPRESS: - ret = Qtr_("MaxPress"); + ret = tr("MaxPress"); break; case START: - ret = Qtr_("Start"); + ret = tr("Start"); break; case END: - ret = Qtr_("End"); + ret = tr("End"); break; case O2: - ret = Qtr_("O2%"); + ret = tr("O2%"); break; case HE: - ret = Qtr_("He%"); + ret = tr("He%"); break; } } @@ -164,10 +163,10 @@ QVariant WeightModel::headerData(int section, Qt::Orientation orientation, int r switch(section){ case TYPE: - ret = Qtr_("Type"); + ret = tr("Type"); break; case WEIGHT: - ret = Qtr_("Weight"); + ret = tr("Weight"); break; } return ret; -- cgit v1.2.3-70-g09d2 From 4c73c70ecb30ab412248e0eff4285a6a59af30fa Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sun, 14 Apr 2013 11:31:26 -0700 Subject: Separate Gtk related code from core logic: info Surprisingly straight forward, just a couple of places where we really mix significant logic with UI code (for example setting the window title). I had to move amount_selected from display-gtk.h to display.h - I guess the number of dives that are selected is UI independent. But I wonder if we still will track this as a global variable in a Qt UI (since the Gtk selection logic is the main reason this existed in the first place). Added a new info.h files for the necessary declarations. This should make no difference to functionality. Signed-off-by: Dirk Hohndel --- Makefile | 7 +- display-gtk.h | 2 - display.h | 2 + info-gtk.c | 965 +++++++++++++++++++++++++++++++++++++++++++++++++++ info.c | 1068 ++++----------------------------------------------------- info.h | 20 ++ 6 files changed, 1061 insertions(+), 1003 deletions(-) create mode 100644 info-gtk.c create mode 100644 info.h (limited to 'Makefile') diff --git a/Makefile b/Makefile index 7778d2aa8..af33dde33 100644 --- a/Makefile +++ b/Makefile @@ -189,11 +189,12 @@ MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.m QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o qt-ui/divelistview.o \ qt-ui/divetripmodel.o qt-ui/addcylinderdialog.o qt-ui/models.o -OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o divelist-gtk.o deco.o \ - planner.o planner-gtk.o \ +GTKOBJS = info-gtk.o divelist-gtk.o planner-gtk.o + +OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o deco.o planner.o \ parse-xml.o save-xml.o libdivecomputer.o print.o uemis.o uemis-downloader.o \ qt-gui.o statistics.o file.o cochran.o device.o download-dialog.o prefs.o \ - webservice.o sha1.o $(GPSOBJ) $(OSSUPPORT).o $(RESFILE) $(QTOBJS) + webservice.o sha1.o $(GPSOBJ) $(OSSUPPORT).o $(RESFILE) $(QTOBJS) $(GTKOBJS) # Add files to the following variables if the auto-detection based on the # filename fails diff --git a/display-gtk.h b/display-gtk.h index 904d5efce..ad286b0d3 100644 --- a/display-gtk.h +++ b/display-gtk.h @@ -81,8 +81,6 @@ extern GtkWidget *create_label(const char *fmt, ...); extern gboolean icon_click_cb(GtkWidget *w, GdkEventButton *event, gpointer data); -extern unsigned int amount_selected; - extern void process_selected_dives(void); typedef void (*data_func_t)(GtkTreeViewColumn *col, diff --git a/display.h b/display.h index d5c69e81b..9e01c73a9 100644 --- a/display.h +++ b/display.h @@ -67,6 +67,8 @@ struct options { extern char zoomed_plot, dc_number; +extern unsigned int amount_selected; + #ifdef __cplusplus } #endif diff --git a/info-gtk.c b/info-gtk.c new file mode 100644 index 000000000..ebba07b73 --- /dev/null +++ b/info-gtk.c @@ -0,0 +1,965 @@ +/* info-gtk.c + * creates the UI for the info frame - + * controlled through the following interfaces: + * + * void show_dive_info(struct dive *dive) + * + * called from gtk-ui: + * GtkWidget *extended_dive_info_widget(void) + */ +#include +#include +#include +#include +#include +#include +#include + +#include "dive.h" +#include "display.h" +#include "display-gtk.h" +#include "divelist.h" +#include "info.h" + +typedef enum { EDIT_NEW_DIVE, EDIT_ALL, EDIT_WHEN } edit_control_t; +static GtkEntry *location, *buddy, *divemaster, *rating, *suit; +static GtkTextView *notes; +static GtkListStore *location_list, *people_list, *star_list, *suit_list; + +static char *get_text(GtkTextView *view) +{ + GtkTextBuffer *buffer; + GtkTextIter start; + GtkTextIter end; + + buffer = gtk_text_view_get_buffer(view); + gtk_text_buffer_get_start_iter(buffer, &start); + gtk_text_buffer_get_end_iter(buffer, &end); + return gtk_text_buffer_get_text(buffer, &start, &end, FALSE); +} + +/* + * Get the string from a combo box. + * + * The "master" string is the string of the current dive - we only consider it + * changed if the old string is either empty, or matches that master string. + */ +static char *get_combo_box_entry_text(GtkComboBox *combo_box, char **textp, const char *master) +{ + const char *newstring = get_active_text(combo_box); + return evaluate_string_change(newstring, textp, master); +} + +#define SET_TEXT_VALUE(x) \ + gtk_entry_set_text(x, dive && dive->x ? dive->x : "") + +void show_dive_info(struct dive *dive) +{ + const char *title = get_window_title(dive); + gtk_window_set_title(GTK_WINDOW(main_window), title); + free((void *)title); + SET_TEXT_VALUE(divemaster); + SET_TEXT_VALUE(buddy); + SET_TEXT_VALUE(location); + SET_TEXT_VALUE(suit); + gtk_entry_set_text(rating, star_strings[dive ? dive->rating : 0]); + gtk_text_buffer_set_text(gtk_text_view_get_buffer(notes), + dive && dive->notes ? dive->notes : "", -1); + /* I'm not quite sure /why/ this is only called when we have no dive + * -- need to investigate */ + if (!dive) + show_dive_equipment(NULL, W_IDX_PRIMARY); +} + +static void info_menu_edit_cb(GtkMenuItem *menuitem, gpointer user_data) +{ + if (amount_selected) + edit_multi_dive_info(NULL); +} + +static void add_menu_item(GtkMenuShell *menu, const char *label, const char *icon, void (*cb)(GtkMenuItem *, gpointer)) +{ + GtkWidget *item; + if (icon) { + GtkWidget *image; + item = gtk_image_menu_item_new_with_label(label); + image = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image); + } else { + item = gtk_menu_item_new_with_label(label); + } + g_signal_connect(item, "activate", G_CALLBACK(cb), NULL); + gtk_widget_show(item); /* Yes, really */ + gtk_menu_shell_prepend(menu, item); +} + +static void populate_popup_cb(GtkTextView *entry, GtkMenuShell *menu, gpointer user_data) +{ + if (amount_selected) + add_menu_item(menu, _("Edit"), GTK_STOCK_EDIT, info_menu_edit_cb); +} + +static GtkEntry *text_value(GtkWidget *box, const char *label) +{ + GtkWidget *widget; + GtkWidget *frame = gtk_frame_new(label); + + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, TRUE, 0); + widget = gtk_entry_new(); + gtk_widget_set_can_focus(widget, FALSE); + gtk_editable_set_editable(GTK_EDITABLE(widget), FALSE); + gtk_container_add(GTK_CONTAINER(frame), widget); + g_signal_connect(widget, "populate-popup", G_CALLBACK(populate_popup_cb), NULL); + return GTK_ENTRY(widget); +} + +static GtkEntry *single_text_entry(GtkWidget *box, const char *label, const char *text) +{ + GtkEntry *entry; + GtkWidget *frame = gtk_frame_new(label); + + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, TRUE, 0); + entry = GTK_ENTRY(gtk_entry_new()); + gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(entry)); + if (text && *text) + gtk_entry_set_text(entry, text); + return entry; +} + +static GtkComboBox *text_entry(GtkWidget *box, const char *label, GtkListStore *completions, const char *text) +{ + GtkWidget *combo_box; + GtkWidget *frame = gtk_frame_new(label); + + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, TRUE, 0); + + combo_box = combo_box_with_model_and_entry(completions); + gtk_container_add(GTK_CONTAINER(frame), combo_box); + + if (text && *text) + set_active_text(GTK_COMBO_BOX(combo_box), text); + + return GTK_COMBO_BOX(combo_box); +} + +enum writable { + READ_ONLY, + READ_WRITE +}; + +static GtkTextView *text_view(GtkWidget *box, const char *label, enum writable writable) +{ + GtkWidget *view, *vbox; + GtkWidget *frame = gtk_frame_new(label); + + gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 0); + box = gtk_hbox_new(FALSE, 3); + gtk_container_add(GTK_CONTAINER(frame), box); + vbox = gtk_vbox_new(FALSE, 3); + gtk_container_add(GTK_CONTAINER(box), vbox); + + GtkWidget* scrolled_window = gtk_scrolled_window_new(0, 0); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN); + + view = gtk_text_view_new(); + if (writable == READ_ONLY) { + gtk_widget_set_can_focus(view, FALSE); + gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE); + gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE); + g_signal_connect(view, "populate-popup", G_CALLBACK(populate_popup_cb), NULL); + } + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD); + gtk_container_add(GTK_CONTAINER(scrolled_window), view); + gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0); + return GTK_TEXT_VIEW(view); +} + +static GtkTreeIter string_entry_location; + +static gboolean match_string_entry(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + const char *string = data; + char *entry; + int cmp; + + gtk_tree_model_get(model, iter, 0, &entry, -1); + cmp = strcmp(entry, string); + if (entry) + free(entry); + + /* Stop. The entry is bigger than the new one */ + if (cmp > 0) + return TRUE; + + /* Exact match */ + if (!cmp) { + found_string_entry = MATCH_EXACT; + return TRUE; + } + + string_entry_location = *iter; + found_string_entry = MATCH_AFTER; + return FALSE; +} + +int match_list(GtkListStore *list, const char *string) +{ + found_string_entry = MATCH_PREPEND; + gtk_tree_model_foreach(GTK_TREE_MODEL(list), match_string_entry, (void *)string); + return found_string_entry; +} + +void add_string_list_entry(const char *string, GtkListStore *list) +{ + GtkTreeIter *iter, loc; + + if (!string || !*string) + return; + + switch (match_list(list, string)) { + case MATCH_EXACT: + return; + case MATCH_PREPEND: + iter = NULL; + break; + case MATCH_AFTER: + iter = &string_entry_location; + break; + } + gtk_list_store_insert_after(list, &loc, iter); + gtk_list_store_set(list, &loc, 0, string, -1); +} + +void add_people(const char *string) +{ + add_string_list_entry(string, people_list); +} + +void add_location(const char *string) +{ + add_string_list_entry(string, location_list); +} + +void add_suit(const char *string) +{ + add_string_list_entry(string, suit_list); +} + +/* this helper function is completely Gtk independent... but I really + * hope that a new UI would just have a decent star rating widget and + * we won't need this kludge... so I'll leave this in the -gtk file */ +static int get_rating(const char *string) +{ + int rating_val = 0; + int i; + + for (i = 0; i <= 5; i++) + if (!strcmp(star_strings[i],string)) + rating_val = i; + return rating_val; +} + +struct dive_info { + GtkComboBox *location, *divemaster, *buddy, *rating, *suit, *viz; + GtkEntry *airtemp, *gps; + GtkWidget *gps_icon; + GtkTextView *notes; +}; + +static void save_dive_info_changes(struct dive *dive, struct dive *master, struct dive_info *info) +{ + char *old_text, *new_text; + const char *gps_text; + char *rating_string; + double newtemp; + int changed = 0; + + new_text = get_combo_box_entry_text(info->location, &dive->location, master->location); + if (new_text) { + add_location(new_text); + changed = 1; + } + + gps_text = gtk_entry_get_text(info->gps); + if (gps_changed(dive, master, gps_text)) + changed = 1; + + new_text = get_combo_box_entry_text(info->divemaster, &dive->divemaster, master->divemaster); + if (new_text) { + add_people(new_text); + changed = 1; + } + + new_text = get_combo_box_entry_text(info->buddy, &dive->buddy, master->buddy); + if (new_text) { + add_people(new_text); + changed = 1; + } + + new_text = get_combo_box_entry_text(info->suit, &dive->suit, master->suit); + if (new_text) { + add_suit(new_text); + changed = 1; + } + + rating_string = strdup(star_strings[dive->rating]); + new_text = get_combo_box_entry_text(info->rating, &rating_string, star_strings[master->rating]); + if (new_text) { + dive->rating = get_rating(rating_string); + changed = 1; + } + free(rating_string); + + rating_string = strdup(star_strings[dive->visibility]); + new_text = get_combo_box_entry_text(info->viz, &rating_string, star_strings[master->visibility]); + if (new_text) { + dive->visibility = get_rating(rating_string); + changed = 1; + } + free(rating_string); + + new_text = (char *)gtk_entry_get_text(info->airtemp); + if (sscanf(new_text, "%lf", &newtemp) == 1) { + unsigned long mkelvin; + switch (prefs.units.temperature) { + case CELSIUS: + mkelvin = C_to_mkelvin(newtemp); + break; + case FAHRENHEIT: + mkelvin = F_to_mkelvin(newtemp); + break; + default: + mkelvin = 0; + } + if (mkelvin != dive->airtemp.mkelvin && dive->airtemp.mkelvin == master->airtemp.mkelvin) { + dive->airtemp.mkelvin = mkelvin; + changed = 1; + } + } + + if (info->notes) { + old_text = dive->notes; + dive->notes = get_text(info->notes); + if (text_changed(old_text,dive->notes)) + changed = 1; + if (old_text) + g_free(old_text); + } + if (changed) { + mark_divelist_changed(TRUE); + update_dive(dive); + } +} + +static void dive_trip_widget(GtkWidget *box, dive_trip_t *trip, struct dive_info *info) +{ + GtkWidget *hbox, *label; + char buffer[128] = N_("Edit trip summary"); + + label = gtk_label_new(_(buffer)); + gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0); + + info->location = text_entry(box, _("Location"), location_list, trip->location); + + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0); + + info->notes = text_view(box, _("Notes"), READ_WRITE); + if (trip->notes && *trip->notes) + gtk_text_buffer_set_text(gtk_text_view_get_buffer(info->notes), trip->notes, -1); +} + +struct location_update { + char text[45]; + char set_by_hand; + GtkEntry *entry; + struct dive *dive; + void (*callback)(float, float); +} location_update; + +static void update_gps_entry(int lat, int lon) +{ + if (location_update.entry) { + print_gps_coordinates(location_update.text, 45, lat, lon); + gtk_entry_set_text(location_update.entry, location_update.text); + } +} + +#if HAVE_OSM_GPS_MAP +static void update_gps_entry_callback(float lat, float lon) +{ + update_gps_entry(lat * 1000000, lon * 1000000); + location_update.set_by_hand = 1; +} + +static gboolean gps_map_callback(GtkWidget *w, gpointer data) +{ + double latitude, longitude; + const char *gps_text = NULL; + struct dive fake_dive; + + memset(&fake_dive, 0, sizeof(fake_dive)); + if (location_update.entry) { + gps_text = gtk_entry_get_text(location_update.entry); + parse_gps_text(gps_text, &latitude, &longitude); + fake_dive.latitude.udeg = rint(latitude * 1000000); + fake_dive.longitude.udeg = rint(longitude * 1000000); + } + show_gps_location(&fake_dive, update_gps_entry_callback); + return TRUE; +} +#endif + +/* + * If somebody sets the string by editing the text entry, + * we consider a clear string an opportunity to set things + * automatically. + * + * A non-empty string, on the other hand, means that we + * should *not* touch it when we change the location field. + */ +static gboolean gps_entry_change_cb(GtkEntry *gps, GdkEvent *event, gpointer userdata) +{ + const char *string = gtk_entry_get_text(gps); + + /* A clear string is never considered to be "set" */ + if (!string) { + location_update.set_by_hand = 0; + return FALSE; + } + + /* + * If it wasn't set by hand, and it hasn't changed, + * it's still not set by hand + */ + if (!location_update.set_by_hand) { + if (!strcmp(location_update.text, string)) + return FALSE; + } + + /* Otherwise, check if it's all empty.. */ + while (g_ascii_isspace(*string)) + string++; + location_update.set_by_hand = !!*string; + + return FALSE; +} + +static void location_entry_change_cb(GtkComboBox *location, gpointer *userdata) +{ + int i; + struct dive *dive; + const char *name; + + /* + * Don't do any automatic gps changes of entries that have been + * explicitly set to some value! + */ + if (location_update.set_by_hand) + return; + + name = get_active_text(location); + for_each_dive(i, dive) { + if (!dive_has_gps_location(dive)) + continue; + if (!dive->location || strcasecmp(dive->location, name)) + continue; + update_gps_entry(dive->latitude.udeg, dive->longitude.udeg); + return; + } + update_gps_entry(0, 0); +} + +static void set_dive_button_label(GtkWidget *button, struct dive *dive) +{ + char buffer[256]; + + /* if there is only one dc and it has no samples we can edit the depth, too */ + if (dive->dc.next || dive->dc.samples) + divename(buffer, sizeof(buffer), dive, _("(click to edit date/time)")); + else + divename(buffer, sizeof(buffer), dive, _("(click to edit date/time/depth)")); + gtk_button_set_label(GTK_BUTTON(button), buffer); +} + +static int dive_time_widget(struct dive *dive, edit_control_t editing); + +static gboolean base_data_cb(GtkWidget *w, GdkEvent *event, gpointer _data) +{ + struct dive *dive = _data; + + /* if there are more than one divecomputers or if there are any sample + * then only the start time (well, date and time) can be changed, + * otherwise (this is most likely a dive that was added manually in Subsurface + * and we can edit duration, max and mean depth, too */ + if (dive->dc.next || dive->dc.samples) + dive_time_widget(dive, EDIT_WHEN); + else + dive_time_widget(dive, EDIT_ALL); + set_dive_button_label(w, dive); + return FALSE; +} + +static void dive_info_widget(GtkWidget *obox, struct dive *dive, struct dive_info *info, gboolean multi) +{ + GtkWidget *hbox, *frame, *equipment, *ibox, *box; +#if HAVE_OSM_GPS_MAP + GtkWidget *image; +#endif + char buffer[256]; + char airtemp[10]; + const char *unit; + double value; + + if (multi) { + GtkWidget *label; + snprintf(buffer, sizeof(buffer), "%s", _("Edit multiple dives")); + label = gtk_label_new(buffer); + gtk_box_pack_start(GTK_BOX(obox), label, FALSE, TRUE, 0); + } else { + GtkWidget *basedata = gtk_button_new_with_label(buffer); + set_dive_button_label(basedata, dive); + g_signal_connect(G_OBJECT(basedata), "button-press-event", G_CALLBACK(base_data_cb), dive); + gtk_box_pack_start(GTK_BOX(obox), basedata, FALSE, TRUE, 0); + } + /* two column layout (inner hbox ibox) within the outer vbox (obox) we are given */ + ibox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(obox), ibox, TRUE, TRUE, 0); + box = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(ibox), box, TRUE, TRUE, 0); + + info->location = text_entry(box, _("Location"), location_list, dive->location); + g_signal_connect(G_OBJECT(info->location), "changed", G_CALLBACK(location_entry_change_cb), NULL); + + hbox = gtk_hbox_new(FALSE, 2); + gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0); + info->gps = single_text_entry(hbox, _("GPS (WGS84 or GPS format)"), NULL); + + location_update.entry = info->gps; + location_update.dive = dive; + update_gps_entry(dive->latitude.udeg, dive->longitude.udeg); + location_update.set_by_hand = !!location_update.text[0]; + + gtk_widget_add_events(GTK_WIDGET(info->gps), GDK_FOCUS_CHANGE_MASK); + g_signal_connect(G_OBJECT(info->gps), "focus-out-event", G_CALLBACK(gps_entry_change_cb), NULL); + gtk_entry_set_width_chars(info->gps, 30); +#if HAVE_OSM_GPS_MAP + info->gps_icon = gtk_button_new_with_label(_("Pick on map")); + gtk_box_pack_start(GTK_BOX(hbox), info->gps_icon, FALSE, FALSE, 6); + image = gtk_image_new_from_pixbuf(get_gps_icon()); + gtk_image_set_pixel_size(GTK_IMAGE(image), 128); + gtk_button_set_image(GTK_BUTTON(info->gps_icon), image); + + g_signal_connect(G_OBJECT(info->gps_icon), "clicked", G_CALLBACK(gps_map_callback), NULL); +#endif + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0); + + info->divemaster = text_entry(hbox, _("Dive master"), people_list, dive->divemaster); + info->buddy = text_entry(hbox, _("Buddy"), people_list, dive->buddy); + + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0); + + info->rating = text_entry(hbox, _("Rating"), star_list, star_strings[dive->rating]); + info->suit = text_entry(hbox, _("Suit"), suit_list, dive->suit); + + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0); + + info->viz = text_entry(hbox, _("Visibility"), star_list, star_strings[dive->visibility]); + + value = get_temp_units(dive->airtemp.mkelvin, &unit); + snprintf(buffer, sizeof(buffer), _("Air Temp in %s"), unit); + if (dive->airtemp.mkelvin) + snprintf(airtemp, sizeof(airtemp), "%.1f", value); + else + airtemp[0] = '\0'; + info->airtemp = single_text_entry(hbox, buffer, airtemp); + + /* only show notes if editing a single dive */ + if (multi) { + info->notes = NULL; + } else { + info->notes = text_view(box, _("Notes"), READ_WRITE); + gtk_widget_set_size_request(GTK_WIDGET(info->notes), -1, 128); + if (dive->notes && *dive->notes) + gtk_text_buffer_set_text(gtk_text_view_get_buffer(info->notes), dive->notes, -1); + } + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(ibox), hbox, TRUE, TRUE, 0); + + /* create a secondary Equipment widget */ + frame = gtk_frame_new(_("Equipment")); + equipment = equipment_widget(W_IDX_SECONDARY); + gtk_container_add(GTK_CONTAINER(frame), equipment); + gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0); +} + +gboolean edit_trip(dive_trip_t *trip) +{ + GtkWidget *dialog, *vbox; + int success; + gboolean changed = FALSE; + char *old_text, *new_text; + struct dive_info info; + + memset(&info, 0, sizeof(struct dive_info)); + dialog = gtk_dialog_new_with_buttons(_("Edit Trip Info"), + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + gtk_window_set_default_size(GTK_WINDOW(dialog), 400, 300); + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + dive_trip_widget(vbox, trip, &info); + gtk_widget_show_all(dialog); + success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; + if (success) { + /* we need to store the edited location and notes */ + new_text = get_combo_box_entry_text(info.location, &trip->location, trip->location); + if (new_text) { + add_location(new_text); + changed = TRUE; + } + if (info.notes) { + old_text = trip->notes; + trip->notes = get_text(info.notes); + if (text_changed(old_text, trip->notes)) + changed = TRUE; + if (old_text) + g_free(old_text); + } + if (changed) + mark_divelist_changed(TRUE); + } + gtk_widget_destroy(dialog); + return changed; +} + +int edit_multi_dive_info(struct dive *single_dive) +{ + int success; + GtkWidget *dialog, *vbox, *scrolled_window, *viewport; + GtkRequisition size; + struct dive_info info; + struct dive *master; + gboolean multi; + + scrolled_window = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + dialog = gtk_dialog_new_with_buttons(_("Dive Info"), + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0); + vbox = g_object_new(GTK_TYPE_VBOX, NULL); + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window), vbox); + master = single_dive; + if (!master) + master = current_dive; + if (!master) + return 0; + /* See if we should use multi dive mode */ + multi = FALSE; + if (!single_dive) { + int i; + struct dive *dive; + + for_each_dive(i, dive) { + if (dive != master && dive->selected) { + multi = TRUE; + break; + } + } + } + /* edit a temporary copy of the master dive; + * edit_dive is a global dive structure that is modified by the + * cylinder / weightsystem dialogs if we open W_IDX_SECONDARY + * edit widgets as we do here */ + memcpy(&edit_dive, master, sizeof(struct dive)); + + dive_info_widget(vbox, &edit_dive, &info, multi); + save_equipment_data(&edit_dive); + gtk_widget_show_all(dialog); + viewport = gtk_widget_get_ancestor(vbox, GTK_TYPE_VIEWPORT); +#if GTK_CHECK_VERSION(3,0,0) + gtk_widget_get_preferred_size(viewport, NULL, &size); +#else + gtk_widget_size_request(viewport, &size); +#endif + gtk_widget_set_size_request(scrolled_window, size.width, size.height); + /* add the equipment post the "blank" layout estimate */ + show_dive_equipment(&edit_dive, W_IDX_SECONDARY); + success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; + if (success) { + mark_divelist_changed(TRUE); + /* Update the non-current selected dives first */ + if (!single_dive) { + int i; + struct dive *dive; + + for_each_dive(i, dive) { + if (dive == master || !dive->selected) + continue; + /* copy all "info" fields */ + save_dive_info_changes(dive, &edit_dive, &info); + /* copy the cylinders / weightsystems */ + update_equipment_data(dive, &edit_dive); + /* this is extremely inefficient... it loops through all + dives to find the right one - but we KNOW the index already */ + update_cylinder_related_info(dive); + flush_divelist(dive); + } + } + + /* Update the master dive last! */ + save_dive_info_changes(master, &edit_dive, &info); + update_equipment_data(master, &edit_dive); + update_cylinder_related_info(master); + /* if there was only one dive we might also have changed dive->when + * or even the duration and depth information (in a dive without samples) */ + if (! multi) + update_time_depth(master, &edit_dive); + dive_list_update_dives(); + } + gtk_widget_destroy(dialog); + location_update.entry = NULL; + + return success; +} + +static GtkWidget *frame_box(GtkWidget *vbox, const char *fmt, ...) +{ + va_list ap; + char buffer[128]; + GtkWidget *frame, *hbox; + + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + frame = gtk_frame_new(buffer); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, TRUE, 0); + hbox = gtk_hbox_new(0, 3); + gtk_container_add(GTK_CONTAINER(frame), hbox); + return hbox; +} + +/* returns the dialog plus pointers to the calendar, hour and minute widget + * plus the hbox that holds the time entry (in case the caller wants to put + * a duration entry widget next to the time entry widget */ +GtkWidget *create_date_time_widget(struct tm *time, GtkWidget **cal, GtkWidget **h, + GtkWidget **m, GtkWidget **timehbox) +{ + GtkWidget *dialog; + GtkWidget *hbox, *vbox; + GtkWidget *label; + + dialog = gtk_dialog_new_with_buttons(_("Date and Time"), + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + + /* Calendar hbox */ + hbox = frame_box(vbox, _("Date:")); + *cal = gtk_calendar_new(); + gtk_box_pack_start(GTK_BOX(hbox), *cal, FALSE, TRUE, 0); + + /* Time hbox */ + *timehbox = gtk_hbox_new(TRUE, 3); + gtk_box_pack_start(GTK_BOX(vbox), *timehbox, FALSE, FALSE, 0); + hbox = frame_box(*timehbox, _("Time")); + + *h = gtk_spin_button_new_with_range (0.0, 23.0, 1.0); + *m = gtk_spin_button_new_with_range (0.0, 59.0, 1.0); + + gtk_calendar_select_month(GTK_CALENDAR(*cal), time->tm_mon, time->tm_year + 1900); + gtk_calendar_select_day(GTK_CALENDAR(*cal), time->tm_mday); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(*h), time->tm_hour); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(*m), time->tm_min); + + gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(*h), TRUE); + gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(*m), TRUE); + + gtk_box_pack_end(GTK_BOX(hbox), *m, FALSE, FALSE, 0); + label = gtk_label_new(":"); + gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(hbox), *h, FALSE, FALSE, 0); + + return dialog; +} + +static int mm_from_spinbutton(GtkWidget *depth) +{ + int result; + double val = gtk_spin_button_get_value(GTK_SPIN_BUTTON(depth)); + if (prefs.units.length == FEET) { + result = feet_to_mm(val); + } else { + result = val * 1000 + 0.5; + } + return result; +} + +static int dive_time_widget(struct dive *dive, edit_control_t editing) +{ + GtkWidget *dialog; + GtkWidget *cal, *vbox, *hbox, *box; + GtkWidget *h, *m; + GtkWidget *duration, *depth, *avgdepth; + guint yval, mval, dval; + struct tm tm, *time; + int success; + double depthinterval; + + /* + * If we have a dive selected, 'add dive' will default + * to one hour after the end of that dive. Otherwise, + * we'll just take the current time. + */ + if (editing != EDIT_NEW_DIVE) { + utc_mkdate(dive->when, &tm); + time = &tm; + } else if (amount_selected == 1) { + timestamp_t when = current_dive->when; + when += current_dive->duration.seconds; + when += 60*60; + utc_mkdate(when, &tm); + time = &tm; + } else { + time_t now; + struct timeval tv; + gettimeofday(&tv, NULL); + now = tv.tv_sec; + time = localtime(&now); + } + dialog = create_date_time_widget(time, &cal, &h, &m, &hbox); + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + + if (editing != EDIT_WHEN) { + /* Duration box */ + box = frame_box(hbox, _("Duration (min)")); + duration = gtk_spin_button_new_with_range (0.0, 1000.0, 1.0); + if (editing != EDIT_NEW_DIVE) + gtk_spin_button_set_value(GTK_SPIN_BUTTON(duration), dive->dc.duration.seconds / 60.0); + gtk_box_pack_end(GTK_BOX(box), duration, FALSE, FALSE, 0); + + hbox = gtk_hbox_new(TRUE, 3); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + /* Depth box */ + box = frame_box(hbox, _("Max Depth (%s):"), prefs.units.length == FEET ? _("ft") : _("m")); + if (prefs.units.length == FEET) { + depthinterval = 1.0; + } else { + depthinterval = 0.1; + } + depth = gtk_spin_button_new_with_range (0.0, 1000.0, depthinterval); + if (editing != EDIT_NEW_DIVE) + gtk_spin_button_set_value(GTK_SPIN_BUTTON(depth), dive->dc.maxdepth.mm / 1000.0); + gtk_box_pack_end(GTK_BOX(box), depth, FALSE, FALSE, 0); + + box = frame_box(hbox, _("Avg Depth (%s):"), prefs.units.length == FEET ? _("ft") : _("m")); + if (prefs.units.length == FEET) { + depthinterval = 1.0; + } else { + depthinterval = 0.1; + } + avgdepth = gtk_spin_button_new_with_range (0.0, 1000.0, depthinterval); + if (editing != EDIT_NEW_DIVE) + gtk_spin_button_set_value(GTK_SPIN_BUTTON(avgdepth), dive->dc.meandepth.mm / 1000.0); + gtk_box_pack_end(GTK_BOX(box), avgdepth, FALSE, FALSE, 0); + } + /* All done, show it and wait for editing */ + gtk_widget_show_all(dialog); + success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; + if (!success) { + gtk_widget_destroy(dialog); + return 0; + } + + memset(&tm, 0, sizeof(tm)); + gtk_calendar_get_date(GTK_CALENDAR(cal), &yval, &mval, &dval); + tm.tm_year = yval; + tm.tm_mon = mval; + tm.tm_mday = dval; + + tm.tm_hour = gtk_spin_button_get_value(GTK_SPIN_BUTTON(h)); + tm.tm_min = gtk_spin_button_get_value(GTK_SPIN_BUTTON(m)); + + if (editing != EDIT_WHEN) { + dive->dc.maxdepth.mm = mm_from_spinbutton(depth); + dive->dc.meandepth.mm = mm_from_spinbutton(avgdepth); + dive->dc.duration.seconds = gtk_spin_button_get_value(GTK_SPIN_BUTTON(duration))*60; + } + gtk_widget_destroy(dialog); + dive->when = utc_mktime(&tm); + + return 1; +} + +int add_new_dive(struct dive *dive) +{ + if (!dive) + return 0; + + if (!dive_time_widget(dive, EDIT_NEW_DIVE)) + return 0; + + return edit_dive_info(dive, TRUE); +} + +GtkWidget *extended_dive_info_widget(void) +{ + GtkWidget *vbox, *hbox; + vbox = gtk_vbox_new(FALSE, 6); + + people_list = gtk_list_store_new(1, G_TYPE_STRING); + location_list = gtk_list_store_new(1, G_TYPE_STRING); + star_list = gtk_list_store_new(1, G_TYPE_STRING); + add_string_list_entry(star_strings[0], star_list); + add_string_list_entry(star_strings[1], star_list); + add_string_list_entry(star_strings[2], star_list); + add_string_list_entry(star_strings[3], star_list); + add_string_list_entry(star_strings[4], star_list); + add_string_list_entry(star_strings[5], star_list); + suit_list = gtk_list_store_new(1, G_TYPE_STRING); + + gtk_container_set_border_width(GTK_CONTAINER(vbox), 6); + location = text_value(vbox, _("Location")); + + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); + + divemaster = text_value(hbox, _("Divemaster")); + buddy = text_value(hbox, _("Buddy")); + + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); + + rating = text_value(hbox, _("Rating")); + suit = text_value(hbox, _("Suit")); + + notes = text_view(vbox, _("Notes"), READ_ONLY); + return vbox; +} + +void info_widget_destroy(void) +{ + g_object_unref(people_list); + g_object_unref(location_list); + g_object_unref(star_list); + g_object_unref(suit_list); +} diff --git a/info.c b/info.c index 455fbea1a..0abee1c1b 100644 --- a/info.c +++ b/info.c @@ -1,11 +1,17 @@ -/* info.c */ -/* creates the UI for the info frame - - * controlled through the following interfaces: +/* info.c * - * void show_dive_info(struct dive *dive) + * UI toolkit independent logic used for the info frame * - * called from gtk-ui: - * GtkWidget *extended_dive_info_widget(void) + * gboolean gps_changed(struct dive *dive, struct dive *master, const char *gps_text); + * void print_gps_coordinates(char *buffer, int len, int lat, int lon); + * void save_equipment_data(struct dive *dive); + * void update_equipment_data(struct dive *dive, struct dive *master); + * void update_time_depth(struct dive *dive, struct dive *edited); + * const char *get_window_title(struct dive *dive); + * char *evaluate_string_change(const char *newstring, char **textp, const char *master); + * int text_changed(const char *old, const char *new); + * gboolean parse_gps_text(const char *gps_text, double *latitude, double *longitude); + * int divename(char *buf, size_t size, struct dive *dive, char *trailer); */ #include #include @@ -17,29 +23,11 @@ #include "dive.h" #include "display.h" -#include "display-gtk.h" #include "divelist.h" -typedef enum { EDIT_NEW_DIVE, EDIT_ALL, EDIT_WHEN } edit_control_t; -static GtkEntry *location, *buddy, *divemaster, *rating, *suit; -static GtkTextView *notes; -static GtkListStore *location_list, *people_list, *star_list, *suit_list; - -static char *get_text(GtkTextView *view) -{ - GtkTextBuffer *buffer; - GtkTextIter start; - GtkTextIter end; - - buffer = gtk_text_view_get_buffer(view); - gtk_text_buffer_get_start_iter(buffer, &start); - gtk_text_buffer_get_end_iter(buffer, &end); - return gtk_text_buffer_get_text(buffer, &start, &end, FALSE); -} - /* old is NULL or a valid string, new is a valid string - * NOTW: NULL and "" need to be treated as "unchanged" */ -static int text_changed(const char *old, const char *new) + * NOTE: NULL and "" need to be treated as "unchanged" */ +int text_changed(const char *old, const char *new) { return (old && strcmp(old,new)) || (!old && strcmp("",new)); @@ -57,16 +45,14 @@ static const char *skip_space(const char *str) } /* - * Get the string from a combo box. - * + * should this string be changed? * The "master" string is the string of the current dive - we only consider it * changed if the old string is either empty, or matches that master string. */ -static char *get_combo_box_entry_text(GtkComboBox *combo_box, char **textp, const char *master) +char *evaluate_string_change(const char *newstring, char **textp, const char *master) { char *old = *textp; const char *old_text; - const gchar *new; old_text = skip_space(old); master = skip_space(master); @@ -81,23 +67,19 @@ static char *get_combo_box_entry_text(GtkComboBox *combo_box, char **textp, cons if (strcmp(master, old_text)) return NULL; - new = get_active_text(combo_box); - while (g_ascii_isspace(*new)) - new++; + while (g_ascii_isspace(*newstring)) + newstring++; /* If the master string didn't change, don't change other dives either! */ - if (!text_changed(master,new)) + if (!text_changed(master, newstring)) return NULL; - if (!text_changed(old,new)) + if (!text_changed(old, newstring)) return NULL; free(old); - *textp = strdup(new); + *textp = strdup(newstring); return *textp; } -#define SET_TEXT_VALUE(x) \ - gtk_entry_set_text(x, dive && dive->x ? dive->x : "") - -static int divename(char *buf, size_t size, struct dive *dive, char *trailer) +int divename(char *buf, size_t size, struct dive *dive, char *trailer) { struct tm tm; @@ -112,9 +94,9 @@ static int divename(char *buf, size_t size, struct dive *dive, char *trailer) trailer); } -void show_dive_info(struct dive *dive) +/* caller should free the string returned after it is no longer needed */ +const char *get_window_title(struct dive *dive) { - const char *subs = "Subsurface: "; const char *text; const int maxlen = 128; char *basename; @@ -124,262 +106,56 @@ void show_dive_info(struct dive *dive) if (!dive) { if (existing_filename) { + char *basename; basename = g_path_get_basename(existing_filename); - len1 = strlen(subs); + len1 = sizeof("Subsurface: "); len2 = g_utf8_strlen(basename, -1); - sz = (len1 + len2 + 1) * sizeof(gunichar); + sz = (len1 + len2) * sizeof(gunichar); title = malloc(sz); - strncpy(title, subs, len1); + strncpy(title, "Subsurface: ", len1); g_utf8_strncpy(title + len1, basename, len2); - gtk_window_set_title(GTK_WINDOW(main_window), title); - free(basename); - free(title); } else { - gtk_window_set_title(GTK_WINDOW(main_window), "Subsurface"); + title = strdup("Subsurface"); } - SET_TEXT_VALUE(divemaster); - SET_TEXT_VALUE(buddy); - SET_TEXT_VALUE(location); - SET_TEXT_VALUE(suit); - gtk_entry_set_text(rating, star_strings[0]); - gtk_text_buffer_set_text(gtk_text_view_get_buffer(notes), "", -1); - show_dive_equipment(NULL, W_IDX_PRIMARY); - return; - } - - /* dive number and location (or lacking that, the date) go in the window title */ - text = dive->location; - if (!text) - text = ""; - if (*text) { - if (dive->number) { - len1 = g_utf8_strlen(text, -1); - sz = (len1 + 32) * sizeof(gunichar); + } else { + /* dive number and location (or lacking that, the date) go in the window title */ + text = dive->location; + if (!text) + text = ""; + if (*text) { + if (dive->number) { + len1 = g_utf8_strlen(text, -1); + sz = (len1 + 32) * sizeof(gunichar); + buffer = malloc(sz); + snprintf(buffer, sz, _("Dive #%d - "), dive->number); + g_utf8_strncpy(buffer + strlen(buffer), text, len1); + text = buffer; + } + } else { + sz = (maxlen + 32) * sizeof(gunichar); buffer = malloc(sz); - snprintf(buffer, sz, _("Dive #%d - "), dive->number); - g_utf8_strncpy(buffer + strlen(buffer), text, len1); + divename(buffer, sz, dive, ""); text = buffer; } - } else { - sz = (maxlen + 32) * sizeof(gunichar); - buffer = malloc(sz); - divename(buffer, sz, dive, ""); - text = buffer; - } - - /* put it all together */ - if (existing_filename) { - basename = g_path_get_basename(existing_filename); - len1 = g_utf8_strlen(basename, -1); - len2 = g_utf8_strlen(text, -1); - if (len2 > maxlen) - len2 = maxlen; - sz = (len1 + len2 + 3) * sizeof(gunichar); /* reserver space for ": " */ - title = malloc(sz); - g_utf8_strncpy(title, basename, len1); - strncpy(title + strlen(basename), (const char *)": ", 2); - g_utf8_strncpy(title + strlen(basename) + 2, text, len2); - gtk_window_set_title(GTK_WINDOW(main_window), title); - free(basename); - free(title); - } else { - gtk_window_set_title(GTK_WINDOW(main_window), text); - } - if (buffer) - free(buffer); - SET_TEXT_VALUE(divemaster); - SET_TEXT_VALUE(buddy); - SET_TEXT_VALUE(location); - SET_TEXT_VALUE(suit); - gtk_entry_set_text(rating, star_strings[dive->rating]); - gtk_text_buffer_set_text(gtk_text_view_get_buffer(notes), - dive && dive->notes ? dive->notes : "", -1); -} - -static void info_menu_edit_cb(GtkMenuItem *menuitem, gpointer user_data) -{ - if (amount_selected) - edit_multi_dive_info(NULL); -} - -static void add_menu_item(GtkMenuShell *menu, const char *label, const char *icon, void (*cb)(GtkMenuItem *, gpointer)) -{ - GtkWidget *item; - if (icon) { - GtkWidget *image; - item = gtk_image_menu_item_new_with_label(label); - image = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_MENU); - gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image); - } else { - item = gtk_menu_item_new_with_label(label); - } - g_signal_connect(item, "activate", G_CALLBACK(cb), NULL); - gtk_widget_show(item); /* Yes, really */ - gtk_menu_shell_prepend(menu, item); -} - -static void populate_popup_cb(GtkTextView *entry, GtkMenuShell *menu, gpointer user_data) -{ - if (amount_selected) - add_menu_item(menu, _("Edit"), GTK_STOCK_EDIT, info_menu_edit_cb); -} - -static GtkEntry *text_value(GtkWidget *box, const char *label) -{ - GtkWidget *widget; - GtkWidget *frame = gtk_frame_new(label); - - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, TRUE, 0); - widget = gtk_entry_new(); - gtk_widget_set_can_focus(widget, FALSE); - gtk_editable_set_editable(GTK_EDITABLE(widget), FALSE); - gtk_container_add(GTK_CONTAINER(frame), widget); - g_signal_connect(widget, "populate-popup", G_CALLBACK(populate_popup_cb), NULL); - return GTK_ENTRY(widget); -} - -static GtkEntry *single_text_entry(GtkWidget *box, const char *label, const char *text) -{ - GtkEntry *entry; - GtkWidget *frame = gtk_frame_new(label); - - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, TRUE, 0); - entry = GTK_ENTRY(gtk_entry_new()); - gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(entry)); - if (text && *text) - gtk_entry_set_text(entry, text); - return entry; -} - -static GtkComboBox *text_entry(GtkWidget *box, const char *label, GtkListStore *completions, const char *text) -{ - GtkWidget *combo_box; - GtkWidget *frame = gtk_frame_new(label); - - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, TRUE, 0); - - combo_box = combo_box_with_model_and_entry(completions); - gtk_container_add(GTK_CONTAINER(frame), combo_box); - - if (text && *text) - set_active_text(GTK_COMBO_BOX(combo_box), text); - - return GTK_COMBO_BOX(combo_box); -} - -enum writable { - READ_ONLY, - READ_WRITE -}; - -static GtkTextView *text_view(GtkWidget *box, const char *label, enum writable writable) -{ - GtkWidget *view, *vbox; - GtkWidget *frame = gtk_frame_new(label); - - gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 0); - box = gtk_hbox_new(FALSE, 3); - gtk_container_add(GTK_CONTAINER(frame), box); - vbox = gtk_vbox_new(FALSE, 3); - gtk_container_add(GTK_CONTAINER(box), vbox); - - GtkWidget* scrolled_window = gtk_scrolled_window_new(0, 0); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN); - - view = gtk_text_view_new(); - if (writable == READ_ONLY) { - gtk_widget_set_can_focus(view, FALSE); - gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE); - gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE); - g_signal_connect(view, "populate-popup", G_CALLBACK(populate_popup_cb), NULL); - } - gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD); - gtk_container_add(GTK_CONTAINER(scrolled_window), view); - gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0); - return GTK_TEXT_VIEW(view); -} - -static GtkTreeIter string_entry_location; - -static gboolean match_string_entry(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) -{ - const char *string = data; - char *entry; - int cmp; - - gtk_tree_model_get(model, iter, 0, &entry, -1); - cmp = strcmp(entry, string); - if (entry) - free(entry); - - /* Stop. The entry is bigger than the new one */ - if (cmp > 0) - return TRUE; - - /* Exact match */ - if (!cmp) { - found_string_entry = MATCH_EXACT; - return TRUE; - } - - string_entry_location = *iter; - found_string_entry = MATCH_AFTER; - return FALSE; -} - -int match_list(GtkListStore *list, const char *string) -{ - found_string_entry = MATCH_PREPEND; - gtk_tree_model_foreach(GTK_TREE_MODEL(list), match_string_entry, (void *)string); - return found_string_entry; -} - -void add_string_list_entry(const char *string, GtkListStore *list) -{ - GtkTreeIter *iter, loc; - - if (!string || !*string) - return; - - switch (match_list(list, string)) { - case MATCH_EXACT: - return; - case MATCH_PREPEND: - iter = NULL; - break; - case MATCH_AFTER: - iter = &string_entry_location; - break; + /* put it all together */ + if (existing_filename) { + basename = g_path_get_basename(existing_filename); + len1 = g_utf8_strlen(basename, -1); + len2 = g_utf8_strlen(text, -1); + if (len2 > maxlen) + len2 = maxlen; + sz = (len1 + len2 + 3) * sizeof(gunichar); /* reserver space for ": " */ + title = malloc(sz); + g_utf8_strncpy(title, basename, len1); + strncpy(title + strlen(basename), (const char *)": ", 2); + g_utf8_strncpy(title + strlen(basename) + 2, text, len2); + } else { + title = strdup(text); + } + if (buffer) + free(buffer); } - gtk_list_store_insert_after(list, &loc, iter); - gtk_list_store_set(list, &loc, 0, string, -1); -} - -void add_people(const char *string) -{ - add_string_list_entry(string, people_list); -} - -void add_location(const char *string) -{ - add_string_list_entry(string, location_list); -} - -void add_suit(const char *string) -{ - add_string_list_entry(string, suit_list); -} - -static int get_rating(const char *string) -{ - int rating_val = 0; - int i; - - for (i = 0; i <= 5; i++) - if (!strcmp(star_strings[i],string)) - rating_val = i; - return rating_val; + return title; } /* this is used to skip the cardinal directions (or check if they are @@ -401,7 +177,7 @@ static int string_advance_cardinal(const char *text, const char *look) } /* this has to be done with UTF8 as people might want to enter the degree symbol */ -static gboolean parse_gps_text(const char *gps_text, double *latitude, double *longitude) +gboolean parse_gps_text(const char *gps_text, double *latitude, double *longitude) { const char *text = gps_text; char *endptr; @@ -494,7 +270,7 @@ static gboolean parse_gps_text(const char *gps_text, double *latitude, double *l return TRUE; } -static gboolean gps_changed(struct dive *dive, struct dive *master, const char *gps_text) +gboolean gps_changed(struct dive *dive, struct dive *master, const char *gps_text) { double latitude, longitude; int latudeg, longudeg; @@ -520,127 +296,9 @@ static gboolean gps_changed(struct dive *dive, struct dive *master, const char * return TRUE; } -struct dive_info { - GtkComboBox *location, *divemaster, *buddy, *rating, *suit, *viz; - GtkEntry *airtemp, *gps; - GtkWidget *gps_icon; - GtkTextView *notes; -}; - -static void save_dive_info_changes(struct dive *dive, struct dive *master, struct dive_info *info) -{ - char *old_text, *new_text; - const char *gps_text; - char *rating_string; - double newtemp; - int changed = 0; - - new_text = get_combo_box_entry_text(info->location, &dive->location, master->location); - if (new_text) { - add_location(new_text); - changed = 1; - } - - gps_text = gtk_entry_get_text(info->gps); - if (gps_changed(dive, master, gps_text)) - changed = 1; - - new_text = get_combo_box_entry_text(info->divemaster, &dive->divemaster, master->divemaster); - if (new_text) { - add_people(new_text); - changed = 1; - } - - new_text = get_combo_box_entry_text(info->buddy, &dive->buddy, master->buddy); - if (new_text) { - add_people(new_text); - changed = 1; - } - - new_text = get_combo_box_entry_text(info->suit, &dive->suit, master->suit); - if (new_text) { - add_suit(new_text); - changed = 1; - } - - rating_string = strdup(star_strings[dive->rating]); - new_text = get_combo_box_entry_text(info->rating, &rating_string, star_strings[master->rating]); - if (new_text) { - dive->rating = get_rating(rating_string); - changed = 1; - } - free(rating_string); - - rating_string = strdup(star_strings[dive->visibility]); - new_text = get_combo_box_entry_text(info->viz, &rating_string, star_strings[master->visibility]); - if (new_text) { - dive->visibility = get_rating(rating_string); - changed = 1; - } - free(rating_string); - - new_text = (char *)gtk_entry_get_text(info->airtemp); - if (sscanf(new_text, "%lf", &newtemp) == 1) { - unsigned long mkelvin; - switch (prefs.units.temperature) { - case CELSIUS: - mkelvin = C_to_mkelvin(newtemp); - break; - case FAHRENHEIT: - mkelvin = F_to_mkelvin(newtemp); - break; - default: - mkelvin = 0; - } - if (mkelvin != dive->airtemp.mkelvin && dive->airtemp.mkelvin == master->airtemp.mkelvin) { - dive->airtemp.mkelvin = mkelvin; - changed = 1; - } - } - - if (info->notes) { - old_text = dive->notes; - dive->notes = get_text(info->notes); - if (text_changed(old_text,dive->notes)) - changed = 1; - if (old_text) - g_free(old_text); - } - if (changed) { - mark_divelist_changed(TRUE); - update_dive(dive); - } -} - -static void dive_trip_widget(GtkWidget *box, dive_trip_t *trip, struct dive_info *info) -{ - GtkWidget *hbox, *label; - char buffer[128] = N_("Edit trip summary"); - - label = gtk_label_new(_(buffer)); - gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0); - - info->location = text_entry(box, _("Location"), location_list, trip->location); - - hbox = gtk_hbox_new(FALSE, 3); - gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0); - - info->notes = text_view(box, _("Notes"), READ_WRITE); - if (trip->notes && *trip->notes) - gtk_text_buffer_set_text(gtk_text_view_get_buffer(info->notes), trip->notes, -1); -} - -struct location_update { - char text[45]; - char set_by_hand; - GtkEntry *entry; - struct dive *dive; - void (*callback)(float, float); -} location_update; - /* take latitude and longitude in udeg and print them in a human readable * form, without losing precision */ -static void print_gps_coordinates(char *buffer, int len, int lat, int lon) +void print_gps_coordinates(char *buffer, int len, int lat, int lon) { unsigned int latdeg, londeg; double latmin, lonmin; @@ -670,232 +328,13 @@ static void print_gps_coordinates(char *buffer, int len, int lat, int lon) lonh, londeg, UTF8_DEGREE, dbuf_lon); } -static void update_gps_entry(int lat, int lon) -{ - if (location_update.entry) { - print_gps_coordinates(location_update.text, 45, lat, lon); - gtk_entry_set_text(location_update.entry, location_update.text); - } -} - -#if HAVE_OSM_GPS_MAP -static void update_gps_entry_callback(float lat, float lon) -{ - update_gps_entry(lat * 1000000, lon * 1000000); - location_update.set_by_hand = 1; -} - -static gboolean gps_map_callback(GtkWidget *w, gpointer data) -{ - double latitude, longitude; - const char *gps_text = NULL; - struct dive fake_dive; - - memset(&fake_dive, 0, sizeof(fake_dive)); - if (location_update.entry) { - gps_text = gtk_entry_get_text(location_update.entry); - parse_gps_text(gps_text, &latitude, &longitude); - fake_dive.latitude.udeg = rint(latitude * 1000000); - fake_dive.longitude.udeg = rint(longitude * 1000000); - } - show_gps_location(&fake_dive, update_gps_entry_callback); - return TRUE; -} -#endif - -/* - * If somebody sets the string by editing the text entry, - * we consider a clear string an opportunity to set things - * automatically. - * - * A non-empty string, on the other hand, means that we - * should *not* touch it when we change the location field. - */ -static gboolean gps_entry_change_cb(GtkEntry *gps, GdkEvent *event, gpointer userdata) -{ - const char *string = gtk_entry_get_text(gps); - - /* A clear string is never considered to be "set" */ - if (!string) { - location_update.set_by_hand = 0; - return FALSE; - } - - /* - * If it wasn't set by hand, and it hasn't changed, - * it's still not set by hand - */ - if (!location_update.set_by_hand) { - if (!strcmp(location_update.text, string)) - return FALSE; - } - - /* Otherwise, check if it's all empty.. */ - while (g_ascii_isspace(*string)) - string++; - location_update.set_by_hand = !!*string; - - return FALSE; -} - -static void location_entry_change_cb(GtkComboBox *location, gpointer *userdata) -{ - int i; - struct dive *dive; - const char *name; - - /* - * Don't do any automatic gps changes of entries that have been - * explicitly set to some value! - */ - if (location_update.set_by_hand) - return; - - name = get_active_text(location); - for_each_dive(i, dive) { - if (!dive_has_gps_location(dive)) - continue; - if (!dive->location || strcasecmp(dive->location, name)) - continue; - update_gps_entry(dive->latitude.udeg, dive->longitude.udeg); - return; - } - update_gps_entry(0, 0); -} - -static void set_dive_button_label(GtkWidget *button, struct dive *dive) -{ - char buffer[256]; - - /* if there is only one dc and it has no samples we can edit the depth, too */ - if (dive->dc.next || dive->dc.samples) - divename(buffer, sizeof(buffer), dive, _("(click to edit date/time)")); - else - divename(buffer, sizeof(buffer), dive, _("(click to edit date/time/depth)")); - gtk_button_set_label(GTK_BUTTON(button), buffer); -} - -static int dive_time_widget(struct dive *dive, edit_control_t editing); - -static gboolean base_data_cb(GtkWidget *w, GdkEvent *event, gpointer _data) -{ - struct dive *dive = _data; - - /* if there are more than one divecomputers or if there are any sample - * then only the start time (well, date and time) can be changed, - * otherwise (this is most likely a dive that was added manually in Subsurface - * and we can edit duration, max and mean depth, too */ - if (dive->dc.next || dive->dc.samples) - dive_time_widget(dive, EDIT_WHEN); - else - dive_time_widget(dive, EDIT_ALL); - set_dive_button_label(w, dive); - return FALSE; -} - -static void dive_info_widget(GtkWidget *obox, struct dive *dive, struct dive_info *info, gboolean multi) -{ - GtkWidget *hbox, *frame, *equipment, *ibox, *box; -#if HAVE_OSM_GPS_MAP - GtkWidget *image; -#endif - char buffer[256]; - char airtemp[10]; - const char *unit; - double value; - - if (multi) { - GtkWidget *label; - snprintf(buffer, sizeof(buffer), "%s", _("Edit multiple dives")); - label = gtk_label_new(buffer); - gtk_box_pack_start(GTK_BOX(obox), label, FALSE, TRUE, 0); - } else { - GtkWidget *basedata = gtk_button_new_with_label(buffer); - set_dive_button_label(basedata, dive); - g_signal_connect(G_OBJECT(basedata), "button-press-event", G_CALLBACK(base_data_cb), dive); - gtk_box_pack_start(GTK_BOX(obox), basedata, FALSE, TRUE, 0); - } - /* two column layout (inner hbox ibox) within the outer vbox (obox) we are given */ - ibox = gtk_hbox_new(FALSE, 0); - gtk_box_pack_start(GTK_BOX(obox), ibox, TRUE, TRUE, 0); - box = gtk_vbox_new(FALSE, 0); - gtk_box_pack_start(GTK_BOX(ibox), box, TRUE, TRUE, 0); - - info->location = text_entry(box, _("Location"), location_list, dive->location); - g_signal_connect(G_OBJECT(info->location), "changed", G_CALLBACK(location_entry_change_cb), NULL); - - hbox = gtk_hbox_new(FALSE, 2); - gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0); - info->gps = single_text_entry(hbox, _("GPS (WGS84 or GPS format)"), NULL); - - location_update.entry = info->gps; - location_update.dive = dive; - update_gps_entry(dive->latitude.udeg, dive->longitude.udeg); - location_update.set_by_hand = !!location_update.text[0]; - - gtk_widget_add_events(GTK_WIDGET(info->gps), GDK_FOCUS_CHANGE_MASK); - g_signal_connect(G_OBJECT(info->gps), "focus-out-event", G_CALLBACK(gps_entry_change_cb), NULL); - gtk_entry_set_width_chars(info->gps, 30); -#if HAVE_OSM_GPS_MAP - info->gps_icon = gtk_button_new_with_label(_("Pick on map")); - gtk_box_pack_start(GTK_BOX(hbox), info->gps_icon, FALSE, FALSE, 6); - image = gtk_image_new_from_pixbuf(get_gps_icon()); - gtk_image_set_pixel_size(GTK_IMAGE(image), 128); - gtk_button_set_image(GTK_BUTTON(info->gps_icon), image); - - g_signal_connect(G_OBJECT(info->gps_icon), "clicked", G_CALLBACK(gps_map_callback), NULL); -#endif - hbox = gtk_hbox_new(FALSE, 3); - gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0); - - info->divemaster = text_entry(hbox, _("Dive master"), people_list, dive->divemaster); - info->buddy = text_entry(hbox, _("Buddy"), people_list, dive->buddy); - - hbox = gtk_hbox_new(FALSE, 3); - gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0); - - info->rating = text_entry(hbox, _("Rating"), star_list, star_strings[dive->rating]); - info->suit = text_entry(hbox, _("Suit"), suit_list, dive->suit); - - hbox = gtk_hbox_new(FALSE, 3); - gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0); - - info->viz = text_entry(hbox, _("Visibility"), star_list, star_strings[dive->visibility]); - - value = get_temp_units(dive->airtemp.mkelvin, &unit); - snprintf(buffer, sizeof(buffer), _("Air Temp in %s"), unit); - if (dive->airtemp.mkelvin) - snprintf(airtemp, sizeof(airtemp), "%.1f", value); - else - airtemp[0] = '\0'; - info->airtemp = single_text_entry(hbox, buffer, airtemp); - - /* only show notes if editing a single dive */ - if (multi) { - info->notes = NULL; - } else { - info->notes = text_view(box, _("Notes"), READ_WRITE); - gtk_widget_set_size_request(GTK_WIDGET(info->notes), -1, 128); - if (dive->notes && *dive->notes) - gtk_text_buffer_set_text(gtk_text_view_get_buffer(info->notes), dive->notes, -1); - } - hbox = gtk_hbox_new(FALSE, 3); - gtk_box_pack_start(GTK_BOX(ibox), hbox, TRUE, TRUE, 0); - - /* create a secondary Equipment widget */ - frame = gtk_frame_new(_("Equipment")); - equipment = equipment_widget(W_IDX_SECONDARY); - gtk_container_add(GTK_CONTAINER(frame), equipment); - gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0); -} - /* we use these to find out if we edited the cylinder or weightsystem entries */ static cylinder_t remember_cyl[MAX_CYLINDERS]; static weightsystem_t remember_ws[MAX_WEIGHTSYSTEMS]; #define CYL_BYTES sizeof(cylinder_t) * MAX_CYLINDERS #define WS_BYTES sizeof(weightsystem_t) * MAX_WEIGHTSYSTEMS -static void save_equipment_data(struct dive *dive) +void save_equipment_data(struct dive *dive) { if (dive) { memcpy(remember_cyl, dive->cylinder, CYL_BYTES); @@ -984,7 +423,7 @@ static void update_cylinder(cylinder_t *dst, cylinder_t *src, cylinder_t *orig) data if it has changed in the master dive and the other dive either has no entries for the equipment or the same entries as the master dive had before it was edited */ -static void update_equipment_data(struct dive *dive, struct dive *master) +void update_equipment_data(struct dive *dive, struct dive *master) { int i; @@ -998,52 +437,10 @@ static void update_equipment_data(struct dive *dive, struct dive *master) memcpy(dive->weightsystem, master->weightsystem, WS_BYTES); } -gboolean edit_trip(dive_trip_t *trip) -{ - GtkWidget *dialog, *vbox; - int success; - gboolean changed = FALSE; - char *old_text, *new_text; - struct dive_info info; - - memset(&info, 0, sizeof(struct dive_info)); - dialog = gtk_dialog_new_with_buttons(_("Edit Trip Info"), - GTK_WINDOW(main_window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, - NULL); - gtk_window_set_default_size(GTK_WINDOW(dialog), 400, 300); - vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - dive_trip_widget(vbox, trip, &info); - gtk_widget_show_all(dialog); - success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; - if (success) { - /* we need to store the edited location and notes */ - new_text = get_combo_box_entry_text(info.location, &trip->location, trip->location); - if (new_text) { - add_location(new_text); - changed = TRUE; - } - if (info.notes) { - old_text = trip->notes; - trip->notes = get_text(info.notes); - if (text_changed(old_text, trip->notes)) - changed = TRUE; - if (old_text) - g_free(old_text); - } - if (changed) - mark_divelist_changed(TRUE); - } - gtk_widget_destroy(dialog); - return changed; -} - /* we can simply overwrite these - this only gets called if we edited * a single dive and the dive was first copied into edited - so we can * just take those values */ -static void update_time_depth(struct dive *dive, struct dive *edited) +void update_time_depth(struct dive *dive, struct dive *edited) { dive->when = edited->when; dive->dc.duration.seconds = edited->dc.duration.seconds; @@ -1051,103 +448,6 @@ static void update_time_depth(struct dive *dive, struct dive *edited) dive->dc.meandepth.mm = edited->dc.meandepth.mm; } -int edit_multi_dive_info(struct dive *single_dive) -{ - int success; - GtkWidget *dialog, *vbox, *scrolled_window, *viewport; - GtkRequisition size; - struct dive_info info; - struct dive *master; - gboolean multi; - - scrolled_window = gtk_scrolled_window_new(NULL, NULL); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), - GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); - dialog = gtk_dialog_new_with_buttons(_("Dive Info"), - GTK_WINDOW(main_window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, - NULL); - - vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0); - vbox = g_object_new(GTK_TYPE_VBOX, NULL); - gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window), vbox); - master = single_dive; - if (!master) - master = current_dive; - if (!master) - return 0; - /* See if we should use multi dive mode */ - multi = FALSE; - if (!single_dive) { - int i; - struct dive *dive; - - for_each_dive(i, dive) { - if (dive != master && dive->selected) { - multi = TRUE; - break; - } - } - } - /* edit a temporary copy of the master dive; - * edit_dive is a global dive structure that is modified by the - * cylinder / weightsystem dialogs if we open W_IDX_SECONDARY - * edit widgets as we do here */ - memcpy(&edit_dive, master, sizeof(struct dive)); - - dive_info_widget(vbox, &edit_dive, &info, multi); - save_equipment_data(&edit_dive); - gtk_widget_show_all(dialog); - viewport = gtk_widget_get_ancestor(vbox, GTK_TYPE_VIEWPORT); -#if GTK_CHECK_VERSION(3,0,0) - gtk_widget_get_preferred_size(viewport, NULL, &size); -#else - gtk_widget_size_request(viewport, &size); -#endif - gtk_widget_set_size_request(scrolled_window, size.width, size.height); - /* add the equipment post the "blank" layout estimate */ - show_dive_equipment(&edit_dive, W_IDX_SECONDARY); - success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; - if (success) { - mark_divelist_changed(TRUE); - /* Update the non-current selected dives first */ - if (!single_dive) { - int i; - struct dive *dive; - - for_each_dive(i, dive) { - if (dive == master || !dive->selected) - continue; - /* copy all "info" fields */ - save_dive_info_changes(dive, &edit_dive, &info); - /* copy the cylinders / weightsystems */ - update_equipment_data(dive, &edit_dive); - /* this is extremely inefficient... it loops through all - dives to find the right one - but we KNOW the index already */ - update_cylinder_related_info(dive); - flush_divelist(dive); - } - } - - /* Update the master dive last! */ - save_dive_info_changes(master, &edit_dive, &info); - update_equipment_data(master, &edit_dive); - update_cylinder_related_info(master); - /* if there was only one dive we might also have changed dive->when - * or even the duration and depth information (in a dive without samples) */ - if (! multi) - update_time_depth(master, &edit_dive); - dive_list_update_dives(); - } - gtk_widget_destroy(dialog); - location_update.entry = NULL; - - return success; -} - int edit_dive_info(struct dive *dive, gboolean newdive) { if (!dive || (!newdive && !amount_selected)) @@ -1155,231 +455,3 @@ int edit_dive_info(struct dive *dive, gboolean newdive) return edit_multi_dive_info(dive); } - -static GtkWidget *frame_box(GtkWidget *vbox, const char *fmt, ...) -{ - va_list ap; - char buffer[128]; - GtkWidget *frame, *hbox; - - va_start(ap, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, ap); - va_end(ap); - - frame = gtk_frame_new(buffer); - gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, TRUE, 0); - hbox = gtk_hbox_new(0, 3); - gtk_container_add(GTK_CONTAINER(frame), hbox); - return hbox; -} - -/* returns the dialog plus pointers to the calendar, hour and minute widget - * plus the hbox that holds the time entry (in case the caller wants to put - * a duration entry widget next to the time entry widget */ -GtkWidget *create_date_time_widget(struct tm *time, GtkWidget **cal, GtkWidget **h, - GtkWidget **m, GtkWidget **timehbox) -{ - GtkWidget *dialog; - GtkWidget *hbox, *vbox; - GtkWidget *label; - - dialog = gtk_dialog_new_with_buttons(_("Date and Time"), - GTK_WINDOW(main_window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, - NULL); - - vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - - /* Calendar hbox */ - hbox = frame_box(vbox, _("Date:")); - *cal = gtk_calendar_new(); - gtk_box_pack_start(GTK_BOX(hbox), *cal, FALSE, TRUE, 0); - - /* Time hbox */ - *timehbox = gtk_hbox_new(TRUE, 3); - gtk_box_pack_start(GTK_BOX(vbox), *timehbox, FALSE, FALSE, 0); - hbox = frame_box(*timehbox, _("Time")); - - *h = gtk_spin_button_new_with_range (0.0, 23.0, 1.0); - *m = gtk_spin_button_new_with_range (0.0, 59.0, 1.0); - - gtk_calendar_select_month(GTK_CALENDAR(*cal), time->tm_mon, time->tm_year + 1900); - gtk_calendar_select_day(GTK_CALENDAR(*cal), time->tm_mday); - gtk_spin_button_set_value(GTK_SPIN_BUTTON(*h), time->tm_hour); - gtk_spin_button_set_value(GTK_SPIN_BUTTON(*m), time->tm_min); - - gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(*h), TRUE); - gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(*m), TRUE); - - gtk_box_pack_end(GTK_BOX(hbox), *m, FALSE, FALSE, 0); - label = gtk_label_new(":"); - gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0); - gtk_box_pack_end(GTK_BOX(hbox), *h, FALSE, FALSE, 0); - - return dialog; -} - -static int mm_from_spinbutton(GtkWidget *depth) -{ - int result; - double val = gtk_spin_button_get_value(GTK_SPIN_BUTTON(depth)); - if (prefs.units.length == FEET) { - result = feet_to_mm(val); - } else { - result = val * 1000 + 0.5; - } - return result; -} - -static int dive_time_widget(struct dive *dive, edit_control_t editing) -{ - GtkWidget *dialog; - GtkWidget *cal, *vbox, *hbox, *box; - GtkWidget *h, *m; - GtkWidget *duration, *depth, *avgdepth; - guint yval, mval, dval; - struct tm tm, *time; - int success; - double depthinterval; - - /* - * If we have a dive selected, 'add dive' will default - * to one hour after the end of that dive. Otherwise, - * we'll just take the current time. - */ - if (editing != EDIT_NEW_DIVE) { - utc_mkdate(dive->when, &tm); - time = &tm; - } else if (amount_selected == 1) { - timestamp_t when = current_dive->when; - when += current_dive->duration.seconds; - when += 60*60; - utc_mkdate(when, &tm); - time = &tm; - } else { - time_t now; - struct timeval tv; - gettimeofday(&tv, NULL); - now = tv.tv_sec; - time = localtime(&now); - } - dialog = create_date_time_widget(time, &cal, &h, &m, &hbox); - vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - - if (editing != EDIT_WHEN) { - /* Duration box */ - box = frame_box(hbox, _("Duration (min)")); - duration = gtk_spin_button_new_with_range (0.0, 1000.0, 1.0); - if (editing != EDIT_NEW_DIVE) - gtk_spin_button_set_value(GTK_SPIN_BUTTON(duration), dive->dc.duration.seconds / 60.0); - gtk_box_pack_end(GTK_BOX(box), duration, FALSE, FALSE, 0); - - hbox = gtk_hbox_new(TRUE, 3); - gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); - - /* Depth box */ - box = frame_box(hbox, _("Max Depth (%s):"), prefs.units.length == FEET ? _("ft") : _("m")); - if (prefs.units.length == FEET) { - depthinterval = 1.0; - } else { - depthinterval = 0.1; - } - depth = gtk_spin_button_new_with_range (0.0, 1000.0, depthinterval); - if (editing != EDIT_NEW_DIVE) - gtk_spin_button_set_value(GTK_SPIN_BUTTON(depth), dive->dc.maxdepth.mm / 1000.0); - gtk_box_pack_end(GTK_BOX(box), depth, FALSE, FALSE, 0); - - box = frame_box(hbox, _("Avg Depth (%s):"), prefs.units.length == FEET ? _("ft") : _("m")); - if (prefs.units.length == FEET) { - depthinterval = 1.0; - } else { - depthinterval = 0.1; - } - avgdepth = gtk_spin_button_new_with_range (0.0, 1000.0, depthinterval); - if (editing != EDIT_NEW_DIVE) - gtk_spin_button_set_value(GTK_SPIN_BUTTON(avgdepth), dive->dc.meandepth.mm / 1000.0); - gtk_box_pack_end(GTK_BOX(box), avgdepth, FALSE, FALSE, 0); - } - /* All done, show it and wait for editing */ - gtk_widget_show_all(dialog); - success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; - if (!success) { - gtk_widget_destroy(dialog); - return 0; - } - - memset(&tm, 0, sizeof(tm)); - gtk_calendar_get_date(GTK_CALENDAR(cal), &yval, &mval, &dval); - tm.tm_year = yval; - tm.tm_mon = mval; - tm.tm_mday = dval; - - tm.tm_hour = gtk_spin_button_get_value(GTK_SPIN_BUTTON(h)); - tm.tm_min = gtk_spin_button_get_value(GTK_SPIN_BUTTON(m)); - - if (editing != EDIT_WHEN) { - dive->dc.maxdepth.mm = mm_from_spinbutton(depth); - dive->dc.meandepth.mm = mm_from_spinbutton(avgdepth); - dive->dc.duration.seconds = gtk_spin_button_get_value(GTK_SPIN_BUTTON(duration))*60; - } - gtk_widget_destroy(dialog); - dive->when = utc_mktime(&tm); - - return 1; -} - -int add_new_dive(struct dive *dive) -{ - if (!dive) - return 0; - - if (!dive_time_widget(dive, EDIT_NEW_DIVE)) - return 0; - - return edit_dive_info(dive, TRUE); -} - -GtkWidget *extended_dive_info_widget(void) -{ - GtkWidget *vbox, *hbox; - vbox = gtk_vbox_new(FALSE, 6); - - people_list = gtk_list_store_new(1, G_TYPE_STRING); - location_list = gtk_list_store_new(1, G_TYPE_STRING); - star_list = gtk_list_store_new(1, G_TYPE_STRING); - add_string_list_entry(star_strings[0], star_list); - add_string_list_entry(star_strings[1], star_list); - add_string_list_entry(star_strings[2], star_list); - add_string_list_entry(star_strings[3], star_list); - add_string_list_entry(star_strings[4], star_list); - add_string_list_entry(star_strings[5], star_list); - suit_list = gtk_list_store_new(1, G_TYPE_STRING); - - gtk_container_set_border_width(GTK_CONTAINER(vbox), 6); - location = text_value(vbox, _("Location")); - - hbox = gtk_hbox_new(FALSE, 3); - gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); - - divemaster = text_value(hbox, _("Divemaster")); - buddy = text_value(hbox, _("Buddy")); - - hbox = gtk_hbox_new(FALSE, 3); - gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); - - rating = text_value(hbox, _("Rating")); - suit = text_value(hbox, _("Suit")); - - notes = text_view(vbox, _("Notes"), READ_ONLY); - return vbox; -} - -void info_widget_destroy(void) -{ - g_object_unref(people_list); - g_object_unref(location_list); - g_object_unref(star_list); - g_object_unref(suit_list); -} diff --git a/info.h b/info.h new file mode 100644 index 000000000..a27373d89 --- /dev/null +++ b/info.h @@ -0,0 +1,20 @@ +/* + * info.h + * + * logic functions used from info-gtk.c + */ +#ifndef INFO_H +#define INFO_H + +extern gboolean gps_changed(struct dive *dive, struct dive *master, const char *gps_text); +extern void print_gps_coordinates(char *buffer, int len, int lat, int lon); +extern void save_equipment_data(struct dive *dive); +extern void update_equipment_data(struct dive *dive, struct dive *master); +extern void update_time_depth(struct dive *dive, struct dive *edited); +extern const char *get_window_title(struct dive *dive); +extern char *evaluate_string_change(const char *newstring, char **textp, const char *master); +extern int text_changed(const char *old, const char *new); +extern gboolean parse_gps_text(const char *gps_text, double *latitude, double *longitude); +extern int divename(char *buf, size_t size, struct dive *dive, char *trailer); + +#endif -- cgit v1.2.3-70-g09d2 From 944d286132fe9529f7f6281c71b291af3ed937bc Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sun, 14 Apr 2013 20:10:25 -0700 Subject: Separate Gtk related code from core logic: statistics Fairly straight forward, so far just one tiny bit of code restructuring, everything else separated cleanly. Added statistics-gtk.c and statistics.h This should make no difference to functionality. Signed-off-by: Dirk Hohndel --- Makefile | 2 +- statistics-gtk.c | 630 ++++++++++++++++++++++++++++++++++++++++++++++++++++ statistics.c | 660 ++----------------------------------------------------- statistics.h | 33 +++ 4 files changed, 678 insertions(+), 647 deletions(-) create mode 100644 statistics-gtk.c create mode 100644 statistics.h (limited to 'Makefile') diff --git a/Makefile b/Makefile index af33dde33..d64be2edf 100644 --- a/Makefile +++ b/Makefile @@ -189,7 +189,7 @@ MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.m QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o qt-ui/divelistview.o \ qt-ui/divetripmodel.o qt-ui/addcylinderdialog.o qt-ui/models.o -GTKOBJS = info-gtk.o divelist-gtk.o planner-gtk.o +GTKOBJS = info-gtk.o divelist-gtk.o planner-gtk.o statistics-gtk.o OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o deco.o planner.o \ parse-xml.o save-xml.o libdivecomputer.o print.o uemis.o uemis-downloader.o \ diff --git a/statistics-gtk.c b/statistics-gtk.c new file mode 100644 index 000000000..775a3bf3b --- /dev/null +++ b/statistics-gtk.c @@ -0,0 +1,630 @@ +/* statistics-gtk.c */ +/* creates the UI for the Info & Stats page - + * controlled through the following interfaces: + * + * void show_dive_stats(struct dive *dive) + * + * called from gtk-ui: + * GtkWidget *stats_widget(void) + */ +#include +#include + +#include "dive.h" +#include "display.h" +#include "display-gtk.h" +#include "divelist.h" +#include "statistics.h" + +typedef struct { + GtkWidget *date, + *dive_time, + *surf_intv, + *max_depth, + *avg_depth, + *viz, + *water_temp, + *air_temp, + *air_press, + *sac, + *otu, + *o2he, + *gas_used; +} single_stat_widget_t; + +static single_stat_widget_t single_w; + +typedef struct { + GtkWidget *total_time, + *avg_time, + *shortest_time, + *longest_time, + *max_overall_depth, + *min_overall_depth, + *avg_overall_depth, + *min_sac, + *avg_sac, + *max_sac, + *selection_size, + *max_temp, + *avg_temp, + *min_temp, + *framelabel; +} total_stats_widget_t; + +static total_stats_widget_t stats_w; + +GtkWidget *yearly_tree = NULL; + +enum { + YEAR, + DIVES, + TOTAL_TIME, + AVERAGE_TIME, + SHORTEST_TIME, + LONGEST_TIME, + AVG_DEPTH, + MIN_DEPTH, + MAX_DEPTH, + AVG_SAC, + MIN_SAC, + MAX_SAC, + AVG_TEMP, + MIN_TEMP, + MAX_TEMP, + N_COLUMNS +}; + +static void init_tree() +{ + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkTreeStore *store; + int i; + PangoFontDescription *font_desc = pango_font_description_from_string(prefs.divelist_font); + + gtk_widget_modify_font(yearly_tree, font_desc); + pango_font_description_free(font_desc); + + renderer = gtk_cell_renderer_text_new (); + /* don't use empty strings "" - they confuse gettext */ + char *columnstop[] = { N_("Year"), N_("#"), N_("Duration"), " ", " ", " ", N_("Depth"), " ", " ", N_("SAC"), " ", " ", N_("Temperature"), " ", " " }; + const char *columnsbot[15]; + columnsbot[0] = C_("Stats", " > Month"); + columnsbot[1] = " "; + columnsbot[2] = C_("Duration","Total"); + columnsbot[3] = C_("Duration","Average"); + columnsbot[4] = C_("Duration","Shortest"); + columnsbot[5] = C_("Duration","Longest"); + columnsbot[6] = C_("Depth", "Average"); + columnsbot[7] = C_("Depth","Minimum"); + columnsbot[8] = C_("Depth","Maximum"); + columnsbot[9] = C_("SAC","Average"); + columnsbot[10]= C_("SAC","Minimum"); + columnsbot[11]= C_("SAC","Maximum"); + columnsbot[12]= C_("Temp","Average"); + columnsbot[13]= C_("Temp","Minimum"); + columnsbot[14]= C_("Temp","Maximum"); + + /* Add all the columns to the tree view */ + for (i = 0; i < N_COLUMNS; ++i) { + char buf[256]; + column = gtk_tree_view_column_new(); + snprintf(buf, sizeof(buf), "%s\n%s", _(columnstop[i]), columnsbot[i]); + gtk_tree_view_column_set_title(column, buf); + gtk_tree_view_append_column(GTK_TREE_VIEW(yearly_tree), column); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_column_pack_start(column, renderer, TRUE); + gtk_tree_view_column_add_attribute(column, renderer, "text", i); + gtk_tree_view_column_set_resizable(column, TRUE); + } + + /* Field types */ + store = gtk_tree_store_new ( + N_COLUMNS, // Columns in structure + G_TYPE_STRING, // Period (year or month) + G_TYPE_STRING, // Number of dives + G_TYPE_STRING, // Total duration + G_TYPE_STRING, // Average dive duation + G_TYPE_STRING, // Shortest dive + G_TYPE_STRING, // Longest dive + G_TYPE_STRING, // Average depth + G_TYPE_STRING, // Shallowest dive + G_TYPE_STRING, // Deepest dive + G_TYPE_STRING, // Average air consumption (SAC) + G_TYPE_STRING, // Minimum SAC + G_TYPE_STRING, // Maximum SAC + G_TYPE_STRING, // Average temperature + G_TYPE_STRING, // Minimum temperature + G_TYPE_STRING // Maximum temperature + ); + + gtk_tree_view_set_model (GTK_TREE_VIEW (yearly_tree), GTK_TREE_MODEL (store)); + g_object_unref (store); +} + +static void add_row_to_tree(GtkTreeStore *store, char *value, int index, GtkTreeIter *row_iter, GtkTreeIter *parent) +{ + gtk_tree_store_append(store, row_iter, parent); + gtk_tree_store_set(store, row_iter, index, value, -1); +} + +static void add_cell_to_tree(GtkTreeStore *store, char *value, int index, GtkTreeIter *parent) +{ + gtk_tree_store_set(store, parent, index, value, -1); +} + +static void add_cell(GtkTreeStore *store, GtkTreeIter *parent, unsigned int val, int cell, gboolean depth_not_volume) +{ + double value; + int decimals; + const char *unit; + char value_str[40]; + + if (depth_not_volume) { + value = get_depth_units(val, &decimals, &unit); + snprintf(value_str, sizeof(value_str), "%.*f %s", decimals, value, unit); + } else { + value = get_volume_units(val, &decimals, &unit); + snprintf(value_str, sizeof(value_str), _("%.*f %s/min"), decimals, value, unit); + } + add_cell_to_tree(store, value_str, cell, parent); +} + +static void process_interval_stats(stats_t stats_interval, GtkTreeIter *parent, GtkTreeIter *row) +{ + double value; + const char *unit; + char value_str[40]; + GtkTreeStore *store; + + store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(yearly_tree))); + + /* Year or month */ + snprintf(value_str, sizeof(value_str), "%d", stats_interval.period); + add_row_to_tree(store, value_str, 0, row, parent); + /* Dives */ + snprintf(value_str, sizeof(value_str), "%d", stats_interval.selection_size); + add_cell_to_tree(store, value_str, 1, row); + /* Total duration */ + add_cell_to_tree(store, get_time_string(stats_interval.total_time.seconds, 0), 2, row); + /* Average dive duration */ + add_cell_to_tree(store, get_minutes(stats_interval.total_time.seconds / stats_interval.selection_size), 3, row); + /* Shortest duration */ + add_cell_to_tree(store, get_minutes(stats_interval.shortest_time.seconds), 4, row); + /* Longest duration */ + add_cell_to_tree(store, get_minutes(stats_interval.longest_time.seconds), 5, row); + /* Average depth */ + add_cell(store, row, stats_interval.avg_depth.mm, 6, TRUE); + /* Smallest maximum depth */ + add_cell(store, row, stats_interval.min_depth.mm, 7, TRUE); + /* Deepest maximum depth */ + add_cell(store, row, stats_interval.max_depth.mm, 8, TRUE); + /* Average air consumption */ + add_cell(store, row, stats_interval.avg_sac.mliter, 9, FALSE); + /* Smallest average air consumption */ + add_cell(store, row, stats_interval.min_sac.mliter, 10, FALSE); + /* Biggest air consumption */ + add_cell(store, row, stats_interval.max_sac.mliter, 11, FALSE); + /* Average water temperature */ + value = get_temp_units(stats_interval.min_temp, &unit); + if (stats_interval.combined_temp && stats_interval.combined_count) { + snprintf(value_str, sizeof(value_str), "%.1f %s", stats_interval.combined_temp / stats_interval.combined_count, unit); + add_cell_to_tree(store, value_str, 12, row); + } else { + add_cell_to_tree(store, "", 12, row); + } + /* Coldest water temperature */ + if (value > -100.0) { + snprintf(value_str, sizeof(value_str), "%.1f %s\t", value, unit); + add_cell_to_tree(store, value_str, 13, row); + } else { + add_cell_to_tree(store, "", 13, row); + } + /* Warmest water temperature */ + value = get_temp_units(stats_interval.max_temp, &unit); + if (value > -100.0) { + snprintf(value_str, sizeof(value_str), "%.1f %s", value, unit); + add_cell_to_tree(store, value_str, 14, row); + } else { + add_cell_to_tree(store, "", 14, row); + } +} + +static void clear_statistics() +{ + GtkTreeStore *store; + + store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(yearly_tree))); + gtk_tree_store_clear(store); + yearly_tree = NULL; +} + +static gboolean stat_on_delete(GtkWidget *window, GdkEvent *event, gpointer data) +{ + clear_statistics(); + gtk_widget_destroy(window); + return TRUE; +} + +static void key_press_event(GtkWidget *window, GdkEventKey *event, gpointer data) +{ + if ((event->string != NULL && event->keyval == GDK_Escape) || + (event->string != NULL && event->keyval == GDK_w && event->state & GDK_CONTROL_MASK)) { + clear_statistics(); + gtk_widget_destroy(window); + } +} + +static void update_yearly_stats() +{ + int i, j, combined_months, month = 0; + GtkTreeIter year_iter, month_iter; + GtkTreeStore *store; + + store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(yearly_tree))); + gtk_tree_store_clear(store); + + for (i = 0; stats_yearly != NULL && stats_yearly[i].period; ++i) { + process_interval_stats(stats_yearly[i], NULL, &year_iter); + combined_months = 0; + + for (j = 0; combined_months < stats_yearly[i].selection_size; ++j) { + combined_months += stats_monthly[month].selection_size; + process_interval_stats(stats_monthly[month], &year_iter, &month_iter); + month++; + } + } +} + +void show_yearly_stats() +{ + GtkWidget *window; + GtkWidget *sw; + + if (yearly_tree) + return; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + sw = gtk_scrolled_window_new (NULL, NULL); + yearly_tree = gtk_tree_view_new (); + + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_default_size(GTK_WINDOW(window), 640, 480); + gtk_window_set_title(GTK_WINDOW(window), _("Yearly Statistics")); + gtk_container_set_border_width(GTK_CONTAINER(window), 5); + gtk_window_set_resizable(GTK_WINDOW(window), TRUE); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_ETCHED_IN); + + gtk_container_add (GTK_CONTAINER (sw), yearly_tree); + gtk_container_add (GTK_CONTAINER (window), sw); + + /* Display the yearly statistics on top level + * Monthly statistics are available by expanding a year */ + init_tree(); + update_yearly_stats(); + + g_signal_connect (G_OBJECT (window), "key_press_event", G_CALLBACK (key_press_event), NULL); + g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (stat_on_delete), NULL); + gtk_widget_show_all(window); +} + +static void set_label(GtkWidget *w, const char *fmt, ...) +{ + char buf[256]; + va_list args; + + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + gtk_label_set_text(GTK_LABEL(w), buf); +} + +/* we try to show the data from the currently selected divecomputer + * right now for some values (e.g., surface pressure) we could fall back + * to dive data, but for consistency we don't. */ +static void show_single_dive_stats(struct dive *dive) +{ + char buf[256]; + double value; + int decimals; + const char *unit; + int idx, offset, gas_used, mbar; + struct dive *prev_dive; + struct tm tm; + struct divecomputer *dc; + + process_all_dives(dive, &prev_dive); + if (yearly_tree) + update_yearly_stats(); + if (!dive) + return; + dc = select_dc(&dive->dc); + utc_mkdate(dive->when, &tm); + snprintf(buf, sizeof(buf), + /*++GETTEXT 80 chars: weekday, monthname, day, year, hour, min */ + _("%1$s, %2$s %3$d, %4$d %5$2d:%6$02d"), + weekday(tm.tm_wday), + monthname(tm.tm_mon), + tm.tm_mday, tm.tm_year + 1900, + tm.tm_hour, tm.tm_min); + + set_label(single_w.date, buf); + set_label(single_w.dive_time, _("%d min"), (dive->duration.seconds + 30) / 60); + if (prev_dive) + set_label(single_w.surf_intv, + get_time_string(dive->when - (prev_dive->when + prev_dive->duration.seconds), 4)); + else + set_label(single_w.surf_intv, _("unknown")); + value = get_depth_units(dc->maxdepth.mm, &decimals, &unit); + set_label(single_w.max_depth, "%.*f %s", decimals, value, unit); + value = get_depth_units(dc->meandepth.mm, &decimals, &unit); + set_label(single_w.avg_depth, "%.*f %s", decimals, value, unit); + set_label(single_w.viz, star_strings[dive->visibility]); + if (dc->watertemp.mkelvin) { + value = get_temp_units(dc->watertemp.mkelvin, &unit); + set_label(single_w.water_temp, "%.1f %s", value, unit); + } else { + set_label(single_w.water_temp, ""); + } + if (dc->airtemp.mkelvin) { + value = get_temp_units(dc->airtemp.mkelvin, &unit); + set_label(single_w.air_temp, "%.1f %s", value, unit); + } else { + if (dive->airtemp.mkelvin) { + value = get_temp_units(dive->airtemp.mkelvin, &unit); + set_label(single_w.air_temp, "%.1f %s", value, unit); + } else { + set_label(single_w.air_temp, ""); + } + } + mbar = dc->surface_pressure.mbar; + /* it would be easy to get dive data here: + * if (!mbar) + * mbar = get_surface_pressure_in_mbar(dive, FALSE); + */ + if (mbar) { + set_label(single_w.air_press, "%d mbar", mbar); + } else { + set_label(single_w.air_press, ""); + } + value = get_volume_units(dive->sac, &decimals, &unit); + if (value > 0) + set_label(single_w.sac, _("%.*f %s/min"), decimals, value, unit); + else + set_label(single_w.sac, ""); + set_label(single_w.otu, "%d", dive->otu); + offset = 0; + gas_used = 0; + buf[0] = '\0'; + /* for the O2/He readings just create a list of them */ + for (idx = 0; idx < MAX_CYLINDERS; idx++) { + cylinder_t *cyl = &dive->cylinder[idx]; + pressure_t start, end; + + start = cyl->start.mbar ? cyl->start : cyl->sample_start; + end = cyl->end.mbar ?cyl->sample_end : cyl->sample_end; + if (!cylinder_none(cyl)) { + /* 0% O2 strangely means air, so 21% - I don't like that at all */ + int o2 = get_o2(&cyl->gasmix); + int he = get_he(&cyl->gasmix); + if (offset > 0) { + snprintf(buf+offset, 80-offset, ", "); + offset += 2; + } + snprintf(buf+offset, 80-offset, "%d/%d", (o2 + 5) / 10, (he + 5) / 10); + offset = strlen(buf); + } + /* and if we have size, start and end pressure, we can + * calculate the total gas used */ + if (start.mbar && end.mbar) + gas_used += gas_volume(cyl, start) - gas_volume(cyl, end); + } + set_label(single_w.o2he, buf); + if (gas_used) { + value = get_volume_units(gas_used, &decimals, &unit); + set_label(single_w.gas_used, "%.*f %s", decimals, value, unit); + } else { + set_label(single_w.gas_used, ""); + } +} + +static void show_total_dive_stats(void) +{ + double value; + int decimals, seconds; + const char *unit; + char buffer[60]; + stats_t *stats_ptr; + + if (!stats_w.framelabel) + return; + stats_ptr = &stats_selection; + + get_selected_dives_text(buffer, sizeof(buffer)); + set_label(stats_w.framelabel, _("Statistics %s"), buffer); + set_label(stats_w.selection_size, "%d", stats_ptr->selection_size); + if (stats_ptr->selection_size == 0) { + clear_stats_widgets(); + return; + } + if (stats_ptr->min_temp) { + value = get_temp_units(stats_ptr->min_temp, &unit); + set_label(stats_w.min_temp, "%.1f %s", value, unit); + } + if (stats_ptr->combined_temp && stats_ptr->combined_count) + set_label(stats_w.avg_temp, "%.1f %s", stats_ptr->combined_temp / stats_ptr->combined_count, unit); + if (stats_ptr->max_temp) { + value = get_temp_units(stats_ptr->max_temp, &unit); + set_label(stats_w.max_temp, "%.1f %s", value, unit); + } + set_label(stats_w.total_time, get_time_string(stats_ptr->total_time.seconds, 0)); + seconds = stats_ptr->total_time.seconds; + if (stats_ptr->selection_size) + seconds /= stats_ptr->selection_size; + set_label(stats_w.avg_time, get_time_string(seconds, 0)); + set_label(stats_w.longest_time, get_time_string(stats_ptr->longest_time.seconds, 0)); + set_label(stats_w.shortest_time, get_time_string(stats_ptr->shortest_time.seconds, 0)); + value = get_depth_units(stats_ptr->max_depth.mm, &decimals, &unit); + set_label(stats_w.max_overall_depth, "%.*f %s", decimals, value, unit); + value = get_depth_units(stats_ptr->min_depth.mm, &decimals, &unit); + set_label(stats_w.min_overall_depth, "%.*f %s", decimals, value, unit); + value = get_depth_units(stats_ptr->avg_depth.mm, &decimals, &unit); + set_label(stats_w.avg_overall_depth, "%.*f %s", decimals, value, unit); + value = get_volume_units(stats_ptr->max_sac.mliter, &decimals, &unit); + set_label(stats_w.max_sac, _("%.*f %s/min"), decimals, value, unit); + value = get_volume_units(stats_ptr->min_sac.mliter, &decimals, &unit); + set_label(stats_w.min_sac, _("%.*f %s/min"), decimals, value, unit); + value = get_volume_units(stats_ptr->avg_sac.mliter, &decimals, &unit); + set_label(stats_w.avg_sac, _("%.*f %s/min"), decimals, value, unit); +} + +void show_dive_stats(struct dive *dive) +{ + /* they have to be called in this order, as 'total' depends on + * calculations done in 'single' */ + show_single_dive_stats(dive); + show_total_dive_stats(); +} + +static GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label) +{ + GtkWidget *label_widget; + GtkWidget *frame; + + frame = gtk_frame_new(label); + label_widget = gtk_label_new(NULL); + gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3); + gtk_container_add(GTK_CONTAINER(frame), label_widget); + + return label_widget; +} + +GtkWidget *total_stats_widget(void) +{ + GtkWidget *vbox, *hbox, *statsframe, *framebox; + + vbox = gtk_vbox_new(FALSE, 3); + + statsframe = gtk_frame_new(_("Statistics")); + stats_w.framelabel = gtk_frame_get_label_widget(GTK_FRAME(statsframe)); + gtk_label_set_max_width_chars(GTK_LABEL(stats_w.framelabel), 60); + gtk_box_pack_start(GTK_BOX(vbox), statsframe, FALSE, FALSE, 3); + framebox = gtk_vbox_new(FALSE, 3); + gtk_container_add(GTK_CONTAINER(statsframe), framebox); + + /* first row */ + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); + stats_w.selection_size = new_info_label_in_frame(hbox, _("Dives")); + stats_w.max_temp = new_info_label_in_frame(hbox, _("Max Temp")); + stats_w.min_temp = new_info_label_in_frame(hbox, _("Min Temp")); + stats_w.avg_temp = new_info_label_in_frame(hbox, _("Avg Temp")); + + /* second row */ + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); + + stats_w.total_time = new_info_label_in_frame(hbox, _("Total Time")); + stats_w.avg_time = new_info_label_in_frame(hbox, _("Avg Time")); + stats_w.longest_time = new_info_label_in_frame(hbox, _("Longest Dive")); + stats_w.shortest_time = new_info_label_in_frame(hbox, _("Shortest Dive")); + + /* third row */ + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); + + stats_w.max_overall_depth = new_info_label_in_frame(hbox, _("Max Depth")); + stats_w.min_overall_depth = new_info_label_in_frame(hbox, _("Min Depth")); + stats_w.avg_overall_depth = new_info_label_in_frame(hbox, _("Avg Depth")); + + /* fourth row */ + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); + + stats_w.max_sac = new_info_label_in_frame(hbox, _("Max SAC")); + stats_w.min_sac = new_info_label_in_frame(hbox, _("Min SAC")); + stats_w.avg_sac = new_info_label_in_frame(hbox, _("Avg SAC")); + + return vbox; +} + +GtkWidget *single_stats_widget(void) +{ + GtkWidget *vbox, *hbox, *infoframe, *framebox; + + vbox = gtk_vbox_new(FALSE, 3); + + infoframe = gtk_frame_new(_("Dive Info")); + gtk_box_pack_start(GTK_BOX(vbox), infoframe, FALSE, FALSE, 3); + framebox = gtk_vbox_new(FALSE, 3); + gtk_container_add(GTK_CONTAINER(infoframe), framebox); + + /* first row */ + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); + + single_w.date = new_info_label_in_frame(hbox, _("Date")); + single_w.dive_time = new_info_label_in_frame(hbox, _("Dive Time")); + single_w.surf_intv = new_info_label_in_frame(hbox, _("Surf Intv")); + + /* second row */ + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); + + single_w.max_depth = new_info_label_in_frame(hbox, _("Max Depth")); + single_w.avg_depth = new_info_label_in_frame(hbox, _("Avg Depth")); + single_w.viz = new_info_label_in_frame(hbox, _("Visibility")); + + /* third row */ + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); + + single_w.water_temp = new_info_label_in_frame(hbox, _("Water Temp")); + single_w.air_temp = new_info_label_in_frame(hbox, _("Air Temp")); + single_w.air_press = new_info_label_in_frame(hbox, _("Air Press")); + + /* fourth row */ + hbox = gtk_hbox_new(FALSE, 3); + gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); + + single_w.sac = new_info_label_in_frame(hbox, _("SAC")); + single_w.otu = new_info_label_in_frame(hbox, _("OTU")); + single_w.o2he = new_info_label_in_frame(hbox, "O" UTF8_SUBSCRIPT_2 " / He"); + single_w.gas_used = new_info_label_in_frame(hbox, C_("Amount","Gas Used")); + + return vbox; +} + +void clear_stats_widgets(void) +{ + set_label(single_w.date, ""); + set_label(single_w.dive_time, ""); + set_label(single_w.surf_intv, ""); + set_label(single_w.max_depth, ""); + set_label(single_w.avg_depth, ""); + set_label(single_w.viz, ""); + set_label(single_w.water_temp, ""); + set_label(single_w.air_temp, ""); + set_label(single_w.air_press, ""); + set_label(single_w.sac, ""); + set_label(single_w.sac, ""); + set_label(single_w.otu, ""); + set_label(single_w.o2he, ""); + set_label(single_w.gas_used, ""); + set_label(stats_w.total_time,""); + set_label(stats_w.avg_time,""); + set_label(stats_w.shortest_time,""); + set_label(stats_w.longest_time,""); + set_label(stats_w.max_overall_depth,""); + set_label(stats_w.min_overall_depth,""); + set_label(stats_w.avg_overall_depth,""); + set_label(stats_w.min_sac,""); + set_label(stats_w.avg_sac,""); + set_label(stats_w.max_sac,""); + set_label(stats_w.selection_size,""); + set_label(stats_w.max_temp,""); + set_label(stats_w.avg_temp,""); + set_label(stats_w.min_temp,""); +} diff --git a/statistics.c b/statistics.c index 502c06cb4..7532e346e 100644 --- a/statistics.c +++ b/statistics.c @@ -1,105 +1,25 @@ -/* statistics.c */ -/* creates the UI for the Info & Stats page - - * controlled through the following interfaces: +/* statistics.c * - * void show_dive_stats(struct dive *dive) - * - * called from gtk-ui: - * GtkWidget *stats_widget(void) + * core logic for the Info & Stats page - + * char *get_time_string(int seconds, int maxdays); + * char *get_minutes(int seconds); + * void process_all_dives(struct dive *dive, struct dive **prev_dive); + * void get_selected_dives_text(char *buffer, int size); */ #include #include #include "dive.h" #include "display.h" -#include "display-gtk.h" #include "divelist.h" - -typedef struct { - GtkWidget *date, - *dive_time, - *surf_intv, - *max_depth, - *avg_depth, - *viz, - *water_temp, - *air_temp, - *air_press, - *sac, - *otu, - *o2he, - *gas_used; -} single_stat_widget_t; - -static single_stat_widget_t single_w; - -typedef struct { - GtkWidget *total_time, - *avg_time, - *shortest_time, - *longest_time, - *max_overall_depth, - *min_overall_depth, - *avg_overall_depth, - *min_sac, - *avg_sac, - *max_sac, - *selection_size, - *max_temp, - *avg_temp, - *min_temp, - *framelabel; -} total_stats_widget_t; - -static total_stats_widget_t stats_w; - -typedef struct { - int period; - duration_t total_time; - /* avg_time is simply total_time / nr -- let's not keep this */ - duration_t shortest_time; - duration_t longest_time; - depth_t max_depth; - depth_t min_depth; - depth_t avg_depth; - volume_t max_sac; - volume_t min_sac; - volume_t avg_sac; - int max_temp; - int min_temp; - double combined_temp; - unsigned int combined_count; - unsigned int selection_size; - unsigned int total_sac_time; -} stats_t; +#include "statistics.h" static stats_t stats; -static stats_t stats_selection; -static stats_t *stats_monthly = NULL; -static stats_t *stats_yearly = NULL; - -GtkWidget *yearly_tree = NULL; +stats_t stats_selection; +stats_t *stats_monthly = NULL; +stats_t *stats_yearly = NULL; -enum { - YEAR, - DIVES, - TOTAL_TIME, - AVERAGE_TIME, - SHORTEST_TIME, - LONGEST_TIME, - AVG_DEPTH, - MIN_DEPTH, - MAX_DEPTH, - AVG_SAC, - MIN_SAC, - MAX_SAC, - AVG_TEMP, - MIN_TEMP, - MAX_TEMP, - N_COLUMNS -}; -static char *get_time_string(int seconds, int maxdays); static void process_temperatures(struct dive *dp, stats_t *stats) { @@ -159,248 +79,14 @@ static void process_dive(struct dive *dp, stats_t *stats) } } -static void init_tree() -{ - GtkCellRenderer *renderer; - GtkTreeViewColumn *column; - GtkTreeStore *store; - int i; - PangoFontDescription *font_desc = pango_font_description_from_string(prefs.divelist_font); - - gtk_widget_modify_font(yearly_tree, font_desc); - pango_font_description_free(font_desc); - - renderer = gtk_cell_renderer_text_new (); - /* don't use empty strings "" - they confuse gettext */ - char *columnstop[] = { N_("Year"), N_("#"), N_("Duration"), " ", " ", " ", N_("Depth"), " ", " ", N_("SAC"), " ", " ", N_("Temperature"), " ", " " }; - const char *columnsbot[15]; - columnsbot[0] = C_("Stats", " > Month"); - columnsbot[1] = " "; - columnsbot[2] = C_("Duration","Total"); - columnsbot[3] = C_("Duration","Average"); - columnsbot[4] = C_("Duration","Shortest"); - columnsbot[5] = C_("Duration","Longest"); - columnsbot[6] = C_("Depth", "Average"); - columnsbot[7] = C_("Depth","Minimum"); - columnsbot[8] = C_("Depth","Maximum"); - columnsbot[9] = C_("SAC","Average"); - columnsbot[10]= C_("SAC","Minimum"); - columnsbot[11]= C_("SAC","Maximum"); - columnsbot[12]= C_("Temp","Average"); - columnsbot[13]= C_("Temp","Minimum"); - columnsbot[14]= C_("Temp","Maximum"); - - /* Add all the columns to the tree view */ - for (i = 0; i < N_COLUMNS; ++i) { - char buf[256]; - column = gtk_tree_view_column_new(); - snprintf(buf, sizeof(buf), "%s\n%s", _(columnstop[i]), columnsbot[i]); - gtk_tree_view_column_set_title(column, buf); - gtk_tree_view_append_column(GTK_TREE_VIEW(yearly_tree), column); - renderer = gtk_cell_renderer_text_new(); - gtk_tree_view_column_pack_start(column, renderer, TRUE); - gtk_tree_view_column_add_attribute(column, renderer, "text", i); - gtk_tree_view_column_set_resizable(column, TRUE); - } - - /* Field types */ - store = gtk_tree_store_new ( - N_COLUMNS, // Columns in structure - G_TYPE_STRING, // Period (year or month) - G_TYPE_STRING, // Number of dives - G_TYPE_STRING, // Total duration - G_TYPE_STRING, // Average dive duation - G_TYPE_STRING, // Shortest dive - G_TYPE_STRING, // Longest dive - G_TYPE_STRING, // Average depth - G_TYPE_STRING, // Shallowest dive - G_TYPE_STRING, // Deepest dive - G_TYPE_STRING, // Average air consumption (SAC) - G_TYPE_STRING, // Minimum SAC - G_TYPE_STRING, // Maximum SAC - G_TYPE_STRING, // Average temperature - G_TYPE_STRING, // Minimum temperature - G_TYPE_STRING // Maximum temperature - ); - - gtk_tree_view_set_model (GTK_TREE_VIEW (yearly_tree), GTK_TREE_MODEL (store)); - g_object_unref (store); -} - -static void add_row_to_tree(GtkTreeStore *store, char *value, int index, GtkTreeIter *row_iter, GtkTreeIter *parent) -{ - gtk_tree_store_append(store, row_iter, parent); - gtk_tree_store_set(store, row_iter, index, value, -1); -} - -static void add_cell_to_tree(GtkTreeStore *store, char *value, int index, GtkTreeIter *parent) -{ - gtk_tree_store_set(store, parent, index, value, -1); -} -static char *get_minutes(int seconds) +char *get_minutes(int seconds) { static char buf[80]; snprintf(buf, sizeof(buf), "%d:%.2d", FRACTION(seconds, 60)); return buf; } -static void add_cell(GtkTreeStore *store, GtkTreeIter *parent, unsigned int val, int cell, gboolean depth_not_volume) -{ - double value; - int decimals; - const char *unit; - char value_str[40]; - - if (depth_not_volume) { - value = get_depth_units(val, &decimals, &unit); - snprintf(value_str, sizeof(value_str), "%.*f %s", decimals, value, unit); - } else { - value = get_volume_units(val, &decimals, &unit); - snprintf(value_str, sizeof(value_str), _("%.*f %s/min"), decimals, value, unit); - } - add_cell_to_tree(store, value_str, cell, parent); -} - -static void process_interval_stats(stats_t stats_interval, GtkTreeIter *parent, GtkTreeIter *row) -{ - double value; - const char *unit; - char value_str[40]; - GtkTreeStore *store; - - store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(yearly_tree))); - - /* Year or month */ - snprintf(value_str, sizeof(value_str), "%d", stats_interval.period); - add_row_to_tree(store, value_str, 0, row, parent); - /* Dives */ - snprintf(value_str, sizeof(value_str), "%d", stats_interval.selection_size); - add_cell_to_tree(store, value_str, 1, row); - /* Total duration */ - add_cell_to_tree(store, get_time_string(stats_interval.total_time.seconds, 0), 2, row); - /* Average dive duration */ - add_cell_to_tree(store, get_minutes(stats_interval.total_time.seconds / stats_interval.selection_size), 3, row); - /* Shortest duration */ - add_cell_to_tree(store, get_minutes(stats_interval.shortest_time.seconds), 4, row); - /* Longest duration */ - add_cell_to_tree(store, get_minutes(stats_interval.longest_time.seconds), 5, row); - /* Average depth */ - add_cell(store, row, stats_interval.avg_depth.mm, 6, TRUE); - /* Smallest maximum depth */ - add_cell(store, row, stats_interval.min_depth.mm, 7, TRUE); - /* Deepest maximum depth */ - add_cell(store, row, stats_interval.max_depth.mm, 8, TRUE); - /* Average air consumption */ - add_cell(store, row, stats_interval.avg_sac.mliter, 9, FALSE); - /* Smallest average air consumption */ - add_cell(store, row, stats_interval.min_sac.mliter, 10, FALSE); - /* Biggest air consumption */ - add_cell(store, row, stats_interval.max_sac.mliter, 11, FALSE); - /* Average water temperature */ - value = get_temp_units(stats_interval.min_temp, &unit); - if (stats_interval.combined_temp && stats_interval.combined_count) { - snprintf(value_str, sizeof(value_str), "%.1f %s", stats_interval.combined_temp / stats_interval.combined_count, unit); - add_cell_to_tree(store, value_str, 12, row); - } else { - add_cell_to_tree(store, "", 12, row); - } - /* Coldest water temperature */ - if (value > -100.0) { - snprintf(value_str, sizeof(value_str), "%.1f %s\t", value, unit); - add_cell_to_tree(store, value_str, 13, row); - } else { - add_cell_to_tree(store, "", 13, row); - } - /* Warmest water temperature */ - value = get_temp_units(stats_interval.max_temp, &unit); - if (value > -100.0) { - snprintf(value_str, sizeof(value_str), "%.1f %s", value, unit); - add_cell_to_tree(store, value_str, 14, row); - } else { - add_cell_to_tree(store, "", 14, row); - } -} - -static void clear_statistics() -{ - GtkTreeStore *store; - - store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(yearly_tree))); - gtk_tree_store_clear(store); - yearly_tree = NULL; -} - -static gboolean stat_on_delete(GtkWidget *window, GdkEvent *event, gpointer data) -{ - clear_statistics(); - gtk_widget_destroy(window); - return TRUE; -} - -static void key_press_event(GtkWidget *window, GdkEventKey *event, gpointer data) -{ - if ((event->string != NULL && event->keyval == GDK_Escape) || - (event->string != NULL && event->keyval == GDK_w && event->state & GDK_CONTROL_MASK)) { - clear_statistics(); - gtk_widget_destroy(window); - } -} - -static void update_yearly_stats() -{ - int i, j, combined_months, month = 0; - GtkTreeIter year_iter, month_iter; - GtkTreeStore *store; - - store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(yearly_tree))); - gtk_tree_store_clear(store); - - for (i = 0; stats_yearly != NULL && stats_yearly[i].period; ++i) { - process_interval_stats(stats_yearly[i], NULL, &year_iter); - combined_months = 0; - - for (j = 0; combined_months < stats_yearly[i].selection_size; ++j) { - combined_months += stats_monthly[month].selection_size; - process_interval_stats(stats_monthly[month], &year_iter, &month_iter); - month++; - } - } -} - -void show_yearly_stats() -{ - GtkWidget *window; - GtkWidget *sw; - - if (yearly_tree) - return; - - window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - sw = gtk_scrolled_window_new (NULL, NULL); - yearly_tree = gtk_tree_view_new (); - - gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); - gtk_window_set_default_size(GTK_WINDOW(window), 640, 480); - gtk_window_set_title(GTK_WINDOW(window), _("Yearly Statistics")); - gtk_container_set_border_width(GTK_CONTAINER(window), 5); - gtk_window_set_resizable(GTK_WINDOW(window), TRUE); - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_ETCHED_IN); - - gtk_container_add (GTK_CONTAINER (sw), yearly_tree); - gtk_container_add (GTK_CONTAINER (window), sw); - - /* Display the yearly statistics on top level - * Monthly statistics are available by expanding a year */ - init_tree(); - update_yearly_stats(); - - g_signal_connect (G_OBJECT (window), "key_press_event", G_CALLBACK (key_press_event), NULL); - g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (stat_on_delete), NULL); - gtk_widget_show_all(window); -} - -static void process_all_dives(struct dive *dive, struct dive **prev_dive) +void process_all_dives(struct dive *dive, struct dive **prev_dive) { int idx; struct dive *dp; @@ -476,8 +162,6 @@ static void process_all_dives(struct dive *dive, struct dive **prev_dive) prev_month = current_month; prev_year = current_year; } - if (yearly_tree) - update_yearly_stats(); } /* make sure we skip the selected summary entries */ @@ -498,18 +182,7 @@ void process_selected_dives(void) stats_selection.selection_size = nr; } -static void set_label(GtkWidget *w, const char *fmt, ...) -{ - char buf[256]; - va_list args; - - va_start(args, fmt); - vsnprintf(buf, sizeof(buf), fmt, args); - va_end(args); - gtk_label_set_text(GTK_LABEL(w), buf); -} - -static char *get_time_string(int seconds, int maxdays) +char *get_time_string(int seconds, int maxdays) { static char buf[80]; if (maxdays && seconds > 3600 * 24 * maxdays) { @@ -526,113 +199,6 @@ static char *get_time_string(int seconds, int maxdays) return buf; } -/* we try to show the data from the currently selected divecomputer - * right now for some values (e.g., surface pressure) we could fall back - * to dive data, but for consistency we don't. */ -static void show_single_dive_stats(struct dive *dive) -{ - char buf[256]; - double value; - int decimals; - const char *unit; - int idx, offset, gas_used, mbar; - struct dive *prev_dive; - struct tm tm; - struct divecomputer *dc; - - process_all_dives(dive, &prev_dive); - if (!dive) - return; - dc = select_dc(&dive->dc); - utc_mkdate(dive->when, &tm); - snprintf(buf, sizeof(buf), - /*++GETTEXT 80 chars: weekday, monthname, day, year, hour, min */ - _("%1$s, %2$s %3$d, %4$d %5$2d:%6$02d"), - weekday(tm.tm_wday), - monthname(tm.tm_mon), - tm.tm_mday, tm.tm_year + 1900, - tm.tm_hour, tm.tm_min); - - set_label(single_w.date, buf); - set_label(single_w.dive_time, _("%d min"), (dive->duration.seconds + 30) / 60); - if (prev_dive) - set_label(single_w.surf_intv, - get_time_string(dive->when - (prev_dive->when + prev_dive->duration.seconds), 4)); - else - set_label(single_w.surf_intv, _("unknown")); - value = get_depth_units(dc->maxdepth.mm, &decimals, &unit); - set_label(single_w.max_depth, "%.*f %s", decimals, value, unit); - value = get_depth_units(dc->meandepth.mm, &decimals, &unit); - set_label(single_w.avg_depth, "%.*f %s", decimals, value, unit); - set_label(single_w.viz, star_strings[dive->visibility]); - if (dc->watertemp.mkelvin) { - value = get_temp_units(dc->watertemp.mkelvin, &unit); - set_label(single_w.water_temp, "%.1f %s", value, unit); - } else { - set_label(single_w.water_temp, ""); - } - if (dc->airtemp.mkelvin) { - value = get_temp_units(dc->airtemp.mkelvin, &unit); - set_label(single_w.air_temp, "%.1f %s", value, unit); - } else { - if (dive->airtemp.mkelvin) { - value = get_temp_units(dive->airtemp.mkelvin, &unit); - set_label(single_w.air_temp, "%.1f %s", value, unit); - } else { - set_label(single_w.air_temp, ""); - } - } - mbar = dc->surface_pressure.mbar; - /* it would be easy to get dive data here: - * if (!mbar) - * mbar = get_surface_pressure_in_mbar(dive, FALSE); - */ - if (mbar) { - set_label(single_w.air_press, "%d mbar", mbar); - } else { - set_label(single_w.air_press, ""); - } - value = get_volume_units(dive->sac, &decimals, &unit); - if (value > 0) - set_label(single_w.sac, _("%.*f %s/min"), decimals, value, unit); - else - set_label(single_w.sac, ""); - set_label(single_w.otu, "%d", dive->otu); - offset = 0; - gas_used = 0; - buf[0] = '\0'; - /* for the O2/He readings just create a list of them */ - for (idx = 0; idx < MAX_CYLINDERS; idx++) { - cylinder_t *cyl = &dive->cylinder[idx]; - pressure_t start, end; - - start = cyl->start.mbar ? cyl->start : cyl->sample_start; - end = cyl->end.mbar ?cyl->sample_end : cyl->sample_end; - if (!cylinder_none(cyl)) { - /* 0% O2 strangely means air, so 21% - I don't like that at all */ - int o2 = get_o2(&cyl->gasmix); - int he = get_he(&cyl->gasmix); - if (offset > 0) { - snprintf(buf+offset, 80-offset, ", "); - offset += 2; - } - snprintf(buf+offset, 80-offset, "%d/%d", (o2 + 5) / 10, (he + 5) / 10); - offset = strlen(buf); - } - /* and if we have size, start and end pressure, we can - * calculate the total gas used */ - if (start.mbar && end.mbar) - gas_used += gas_volume(cyl, start) - gas_volume(cyl, end); - } - set_label(single_w.o2he, buf); - if (gas_used) { - value = get_volume_units(gas_used, &decimals, &unit); - set_label(single_w.gas_used, "%.*f %s", decimals, value, unit); - } else { - set_label(single_w.gas_used, ""); - } -} - /* this gets called when at least two but not all dives are selected */ static void get_ranges(char *buffer, int size) { @@ -677,7 +243,7 @@ static void get_ranges(char *buffer, int size) } } -static void get_selected_dives_text(char *buffer, int size) +void get_selected_dives_text(char *buffer, int size) { if (amount_selected == 1) { if (current_dive) @@ -701,201 +267,3 @@ static void get_selected_dives_text(char *buffer, int size) } } -static void show_total_dive_stats(void) -{ - double value; - int decimals, seconds; - const char *unit; - char buffer[60]; - stats_t *stats_ptr; - - if (!stats_w.framelabel) - return; - stats_ptr = &stats_selection; - - get_selected_dives_text(buffer, sizeof(buffer)); - set_label(stats_w.framelabel, _("Statistics %s"), buffer); - set_label(stats_w.selection_size, "%d", stats_ptr->selection_size); - if (stats_ptr->selection_size == 0) { - clear_stats_widgets(); - return; - } - if (stats_ptr->min_temp) { - value = get_temp_units(stats_ptr->min_temp, &unit); - set_label(stats_w.min_temp, "%.1f %s", value, unit); - } - if (stats_ptr->combined_temp && stats_ptr->combined_count) - set_label(stats_w.avg_temp, "%.1f %s", stats_ptr->combined_temp / stats_ptr->combined_count, unit); - if (stats_ptr->max_temp) { - value = get_temp_units(stats_ptr->max_temp, &unit); - set_label(stats_w.max_temp, "%.1f %s", value, unit); - } - set_label(stats_w.total_time, get_time_string(stats_ptr->total_time.seconds, 0)); - seconds = stats_ptr->total_time.seconds; - if (stats_ptr->selection_size) - seconds /= stats_ptr->selection_size; - set_label(stats_w.avg_time, get_time_string(seconds, 0)); - set_label(stats_w.longest_time, get_time_string(stats_ptr->longest_time.seconds, 0)); - set_label(stats_w.shortest_time, get_time_string(stats_ptr->shortest_time.seconds, 0)); - value = get_depth_units(stats_ptr->max_depth.mm, &decimals, &unit); - set_label(stats_w.max_overall_depth, "%.*f %s", decimals, value, unit); - value = get_depth_units(stats_ptr->min_depth.mm, &decimals, &unit); - set_label(stats_w.min_overall_depth, "%.*f %s", decimals, value, unit); - value = get_depth_units(stats_ptr->avg_depth.mm, &decimals, &unit); - set_label(stats_w.avg_overall_depth, "%.*f %s", decimals, value, unit); - value = get_volume_units(stats_ptr->max_sac.mliter, &decimals, &unit); - set_label(stats_w.max_sac, _("%.*f %s/min"), decimals, value, unit); - value = get_volume_units(stats_ptr->min_sac.mliter, &decimals, &unit); - set_label(stats_w.min_sac, _("%.*f %s/min"), decimals, value, unit); - value = get_volume_units(stats_ptr->avg_sac.mliter, &decimals, &unit); - set_label(stats_w.avg_sac, _("%.*f %s/min"), decimals, value, unit); -} - -void show_dive_stats(struct dive *dive) -{ - /* they have to be called in this order, as 'total' depends on - * calculations done in 'single' */ - show_single_dive_stats(dive); - show_total_dive_stats(); -} - -static GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label) -{ - GtkWidget *label_widget; - GtkWidget *frame; - - frame = gtk_frame_new(label); - label_widget = gtk_label_new(NULL); - gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3); - gtk_container_add(GTK_CONTAINER(frame), label_widget); - - return label_widget; -} - -GtkWidget *total_stats_widget(void) -{ - GtkWidget *vbox, *hbox, *statsframe, *framebox; - - vbox = gtk_vbox_new(FALSE, 3); - - statsframe = gtk_frame_new(_("Statistics")); - stats_w.framelabel = gtk_frame_get_label_widget(GTK_FRAME(statsframe)); - gtk_label_set_max_width_chars(GTK_LABEL(stats_w.framelabel), 60); - gtk_box_pack_start(GTK_BOX(vbox), statsframe, FALSE, FALSE, 3); - framebox = gtk_vbox_new(FALSE, 3); - gtk_container_add(GTK_CONTAINER(statsframe), framebox); - - /* first row */ - hbox = gtk_hbox_new(FALSE, 3); - gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); - stats_w.selection_size = new_info_label_in_frame(hbox, _("Dives")); - stats_w.max_temp = new_info_label_in_frame(hbox, _("Max Temp")); - stats_w.min_temp = new_info_label_in_frame(hbox, _("Min Temp")); - stats_w.avg_temp = new_info_label_in_frame(hbox, _("Avg Temp")); - - /* second row */ - hbox = gtk_hbox_new(FALSE, 3); - gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); - - stats_w.total_time = new_info_label_in_frame(hbox, _("Total Time")); - stats_w.avg_time = new_info_label_in_frame(hbox, _("Avg Time")); - stats_w.longest_time = new_info_label_in_frame(hbox, _("Longest Dive")); - stats_w.shortest_time = new_info_label_in_frame(hbox, _("Shortest Dive")); - - /* third row */ - hbox = gtk_hbox_new(FALSE, 3); - gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); - - stats_w.max_overall_depth = new_info_label_in_frame(hbox, _("Max Depth")); - stats_w.min_overall_depth = new_info_label_in_frame(hbox, _("Min Depth")); - stats_w.avg_overall_depth = new_info_label_in_frame(hbox, _("Avg Depth")); - - /* fourth row */ - hbox = gtk_hbox_new(FALSE, 3); - gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); - - stats_w.max_sac = new_info_label_in_frame(hbox, _("Max SAC")); - stats_w.min_sac = new_info_label_in_frame(hbox, _("Min SAC")); - stats_w.avg_sac = new_info_label_in_frame(hbox, _("Avg SAC")); - - return vbox; -} - -GtkWidget *single_stats_widget(void) -{ - GtkWidget *vbox, *hbox, *infoframe, *framebox; - - vbox = gtk_vbox_new(FALSE, 3); - - infoframe = gtk_frame_new(_("Dive Info")); - gtk_box_pack_start(GTK_BOX(vbox), infoframe, FALSE, FALSE, 3); - framebox = gtk_vbox_new(FALSE, 3); - gtk_container_add(GTK_CONTAINER(infoframe), framebox); - - /* first row */ - hbox = gtk_hbox_new(FALSE, 3); - gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); - - single_w.date = new_info_label_in_frame(hbox, _("Date")); - single_w.dive_time = new_info_label_in_frame(hbox, _("Dive Time")); - single_w.surf_intv = new_info_label_in_frame(hbox, _("Surf Intv")); - - /* second row */ - hbox = gtk_hbox_new(FALSE, 3); - gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); - - single_w.max_depth = new_info_label_in_frame(hbox, _("Max Depth")); - single_w.avg_depth = new_info_label_in_frame(hbox, _("Avg Depth")); - single_w.viz = new_info_label_in_frame(hbox, _("Visibility")); - - /* third row */ - hbox = gtk_hbox_new(FALSE, 3); - gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); - - single_w.water_temp = new_info_label_in_frame(hbox, _("Water Temp")); - single_w.air_temp = new_info_label_in_frame(hbox, _("Air Temp")); - single_w.air_press = new_info_label_in_frame(hbox, _("Air Press")); - - /* fourth row */ - hbox = gtk_hbox_new(FALSE, 3); - gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3); - - single_w.sac = new_info_label_in_frame(hbox, _("SAC")); - single_w.otu = new_info_label_in_frame(hbox, _("OTU")); - single_w.o2he = new_info_label_in_frame(hbox, "O" UTF8_SUBSCRIPT_2 " / He"); - single_w.gas_used = new_info_label_in_frame(hbox, C_("Amount","Gas Used")); - - return vbox; -} - -void clear_stats_widgets(void) -{ - set_label(single_w.date, ""); - set_label(single_w.dive_time, ""); - set_label(single_w.surf_intv, ""); - set_label(single_w.max_depth, ""); - set_label(single_w.avg_depth, ""); - set_label(single_w.viz, ""); - set_label(single_w.water_temp, ""); - set_label(single_w.air_temp, ""); - set_label(single_w.air_press, ""); - set_label(single_w.sac, ""); - set_label(single_w.sac, ""); - set_label(single_w.otu, ""); - set_label(single_w.o2he, ""); - set_label(single_w.gas_used, ""); - set_label(stats_w.total_time,""); - set_label(stats_w.avg_time,""); - set_label(stats_w.shortest_time,""); - set_label(stats_w.longest_time,""); - set_label(stats_w.max_overall_depth,""); - set_label(stats_w.min_overall_depth,""); - set_label(stats_w.avg_overall_depth,""); - set_label(stats_w.min_sac,""); - set_label(stats_w.avg_sac,""); - set_label(stats_w.max_sac,""); - set_label(stats_w.selection_size,""); - set_label(stats_w.max_temp,""); - set_label(stats_w.avg_temp,""); - set_label(stats_w.min_temp,""); -} diff --git a/statistics.h b/statistics.h new file mode 100644 index 000000000..d2709ee93 --- /dev/null +++ b/statistics.h @@ -0,0 +1,33 @@ +/* + * statistics.h + * + * core logic functions called from statistics UI + * common types and variables + */ +typedef struct { + int period; + duration_t total_time; + /* avg_time is simply total_time / nr -- let's not keep this */ + duration_t shortest_time; + duration_t longest_time; + depth_t max_depth; + depth_t min_depth; + depth_t avg_depth; + volume_t max_sac; + volume_t min_sac; + volume_t avg_sac; + int max_temp; + int min_temp; + double combined_temp; + unsigned int combined_count; + unsigned int selection_size; + unsigned int total_sac_time; +} stats_t; +extern stats_t stats_selection; +extern stats_t *stats_yearly; +extern stats_t *stats_monthly; + +extern char *get_time_string(int seconds, int maxdays); +extern char *get_minutes(int seconds); +extern void process_all_dives(struct dive *dive, struct dive **prev_dive); +extern void get_selected_dives_text(char *buffer, int size); -- cgit v1.2.3-70-g09d2 From 115ee47bfc0aa8ca2b2bdaca047ccf595bbb7120 Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Mon, 15 Apr 2013 15:04:35 -0300 Subject: Added the code that will load and populate the Tank Info Added the code that will load and populate the Tank Info ComboBox that`s used by the user to select the Cylinder description. Code curerntly implements more than the GTK version since the GTK version of it was a plain-list, this one is a table based model that can be used in ListViews ( like we use now in the ComboBox ) but also in TableViews ( if there`s a need in the future to see everything that`s catalogued in the Tank Info struct. ) Signed-off-by: Tomaz Canabrava Signed-off-by: Dirk Hohndel --- Makefile | 2 +- dive.h | 13 ++++++ equipment.c | 5 +-- qt-ui/addcylinderdialog.cpp | 4 +- qt-ui/addcylinderdialog.h | 3 ++ qt-ui/models.cpp | 103 +++++++++++++++++++++++++++++++++++++++++++- qt-ui/models.h | 20 +++++++++ 7 files changed, 143 insertions(+), 7 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index d64be2edf..5984d0123 100644 --- a/Makefile +++ b/Makefile @@ -149,7 +149,7 @@ ifneq (,$(filter $(UNAME),linux kfreebsd gnu)) GCONF2CFLAGS = $(shell $(PKGCONFIG) --cflags gconf-2.0) OSSUPPORT = linux OSSUPPORT_CFLAGS = $(GTKCFLAGS) $(GCONF2CFLAGS) - ifneq ($(findstring reduce_relocations, $(shell $(PKGCONFIG) --variable qt_config $(QT_CORE))),) + ifneq ($(filter reduce_relocations, $(shell $(PKGCONFIG) --variable qt_config $(QT_CORE))), ) CXXFLAGS += -fPIE endif else ifeq ($(UNAME), darwin) diff --git a/dive.h b/dive.h index 734aa2269..c276fe6de 100644 --- a/dive.h +++ b/dive.h @@ -697,6 +697,19 @@ void get_gas_string(int o2, int he, char *buf, int len); struct event *get_next_event(struct event *event, char *name); + +/* this struct holds the information that + * describes the cylinders of air. + * it is a global variable initialized in equipment.c + * used to fill the combobox in the add/edit cylinder + * dialog + */ + +struct tank_info { + const char *name; + int cuft, ml, psi, bar; +}; + #ifdef DEBUGFILE extern char *debugfilename; extern FILE *debugfile; diff --git a/equipment.c b/equipment.c index a1d156f94..d126b4327 100644 --- a/equipment.c +++ b/equipment.c @@ -790,10 +790,7 @@ static void record_weightsystem_changes(weightsystem_t *ws, struct ws_widget *we * we should pick up any other names from the dive * logs directly. */ -static struct tank_info { - const char *name; - int cuft, ml, psi, bar; -} tank_info[100] = { +struct tank_info tank_info[100] = { /* Need an empty entry for the no-cylinder case */ { "", }, diff --git a/qt-ui/addcylinderdialog.cpp b/qt-ui/addcylinderdialog.cpp index 043f29907..5b91617ed 100644 --- a/qt-ui/addcylinderdialog.cpp +++ b/qt-ui/addcylinderdialog.cpp @@ -9,11 +9,13 @@ #include #include #include "../conversions.h" - +#include "models.h" AddCylinderDialog::AddCylinderDialog(QWidget *parent) : ui(new Ui::AddCylinderDialog()) +, tankInfoModel(new TankInfoModel()) { ui->setupUi(this); + ui->cylinderType->setModel(tankInfoModel); } void AddCylinderDialog::setCylinder(cylinder_t *cylinder) diff --git a/qt-ui/addcylinderdialog.h b/qt-ui/addcylinderdialog.h index 652f7b362..fc68faa72 100644 --- a/qt-ui/addcylinderdialog.h +++ b/qt-ui/addcylinderdialog.h @@ -14,6 +14,8 @@ namespace Ui{ class AddCylinderDialog; } +class TankInfoModel; + class AddCylinderDialog : public QDialog{ Q_OBJECT public: @@ -24,6 +26,7 @@ public: private: Ui::AddCylinderDialog *ui; cylinder_t *currentCylinder; + TankInfoModel *tankInfoModel; }; diff --git a/qt-ui/models.cpp b/qt-ui/models.cpp index 64fa6bac3..d1b8dc0a0 100644 --- a/qt-ui/models.cpp +++ b/qt-ui/models.cpp @@ -7,6 +7,8 @@ #include "models.h" #include "../dive.h" +extern struct tank_info tank_info[100]; + CylindersModel::CylindersModel(QObject* parent): QAbstractTableModel(parent) { } @@ -161,7 +163,7 @@ QVariant WeightModel::headerData(int section, Qt::Orientation orientation, int r return ret; } - switch(section){ + switch(section) { case TYPE: ret = tr("Type"); break; @@ -179,3 +181,102 @@ void WeightModel::add(weight_t* weight) void WeightModel::update() { } + +void TankInfoModel::add(const QString& description) +{ + // When the user `creates` a new one on the combobox. + // for now, empty till dirk cleans the GTK code. +} + +void TankInfoModel::clear() +{ +} + +int TankInfoModel::columnCount(const QModelIndex& parent) const +{ + return 3; +} + +QVariant TankInfoModel::data(const QModelIndex& index, int role) const +{ + QVariant ret; + if (!index.isValid()) { + return ret; + } + struct tank_info *info = &tank_info[index.row()]; + + int ml = info->ml; + + int bar = ((info->psi) ? psi_to_bar(info->psi) : info->bar) * 1000 + 0.5; + + if (info->cuft) { + double airvolume = cuft_to_l(info->cuft) * 1000.0; + ml = airvolume / bar_to_atm(bar) + 0.5; + } + if (role == Qt::DisplayRole) { + switch(index.column()) { + case BAR: ret = bar; + break; + case ML: ret = ml; + break; + case DESCRIPTION: + ret = QString(info->name); + break; + } + } + return ret; +} + +QVariant TankInfoModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant ret; + + if (orientation != Qt::Horizontal) + return ret; + + if (role == Qt::DisplayRole) { + switch(section) { + case BAR: + ret = tr("Bar"); + break; + case ML: + ret = tr("Ml"); + break; + case DESCRIPTION: + ret = tr("Description"); + break; + } + } + return ret; +} + +int TankInfoModel::rowCount(const QModelIndex& parent) const +{ + return rows+1; +} + +TankInfoModel::TankInfoModel() : QAbstractTableModel(), rows(-1) +{ + struct tank_info *info = tank_info; + for (info = tank_info ; info->name; info++, rows++); + + if (rows > -1) { + beginInsertRows(QModelIndex(), 0, rows); + endInsertRows(); + } +} + +void TankInfoModel::update() +{ + if(rows > -1) { + beginRemoveRows(QModelIndex(), 0, rows); + endRemoveRows(); + } + struct tank_info *info = tank_info; + for (info = tank_info ; info->name; info++, rows++); + + if (rows > -1) { + beginInsertRows(QModelIndex(), 0, rows); + endInsertRows(); + } +} diff --git a/qt-ui/models.h b/qt-ui/models.h index 697096f92..8d86102cb 100644 --- a/qt-ui/models.h +++ b/qt-ui/models.h @@ -10,6 +10,26 @@ #include #include "../dive.h" +/* Encapsulates the tank_info global variable + * to show on Qt`s Model View System.*/ +class TankInfoModel : public QAbstractTableModel { +Q_OBJECT +public: + enum { DESCRIPTION, ML, BAR}; + TankInfoModel(); + + /*reimp*/ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + /*reimp*/ int columnCount(const QModelIndex& parent = QModelIndex()) const; + /*reimp*/ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + /*reimp*/ int rowCount(const QModelIndex& parent = QModelIndex()) const; + + void add(const QString& description); + void clear(); + void update(); +private: + int rows; +}; + class CylindersModel : public QAbstractTableModel { Q_OBJECT public: -- cgit v1.2.3-70-g09d2 From a0280ae7d2cdd483bc53c7bc91a8aa438f9234de Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Sun, 21 Apr 2013 22:12:36 -0300 Subject: Move model related code from MainWindow and adjustments. Moves the DiveTrip model related code to models.h The DiveTripModel was implemented in three parts: DiveTripModel.h DiveTripModel.cpp MainWindow.cpp (the code to populate the model) This patch changes the DiveTripModel from it's original implementation to the file models.h, wich should store all models (Dirk requested the Qt developers to not create 2 files per class, but instead to use a file for functionality, and data-models are one functionality.) Besides that, this code cleans up a bit the style: removed operator<< for .push_back, const references where they apply, moved the internal DiveItem class to the .cpp since it should be visible only to the DiveTripModel class, and redesigned the current interface of the model to be identical of the GTK one (used the UTF8-star and 2 subscribed, for instance). Amit (the creator of the original code) should comment here if it's ok with my changes. Signed-off-by: Tomaz Canabrava Signed-off-by: Dirk Hohndel --- Makefile | 2 +- qt-ui/divetripmodel.cpp | 141 ------------------------------ qt-ui/divetripmodel.h | 86 ------------------- qt-ui/mainwindow.cpp | 41 +-------- qt-ui/models.cpp | 224 ++++++++++++++++++++++++++++++++++++++++++++++++ qt-ui/models.h | 34 +++++++- 6 files changed, 259 insertions(+), 269 deletions(-) delete mode 100644 qt-ui/divetripmodel.cpp delete mode 100644 qt-ui/divetripmodel.h (limited to 'Makefile') diff --git a/Makefile b/Makefile index 5984d0123..7b7ec0476 100644 --- a/Makefile +++ b/Makefile @@ -187,7 +187,7 @@ MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.m QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o qt-ui/divelistview.o \ - qt-ui/divetripmodel.o qt-ui/addcylinderdialog.o qt-ui/models.o + qt-ui/addcylinderdialog.o qt-ui/models.o GTKOBJS = info-gtk.o divelist-gtk.o planner-gtk.o statistics-gtk.o diff --git a/qt-ui/divetripmodel.cpp b/qt-ui/divetripmodel.cpp deleted file mode 100644 index 5082494a0..000000000 --- a/qt-ui/divetripmodel.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/* - * divetripmodel.cpp - * - * classes for the dive trip list in Subsurface - */ -#include "divetripmodel.h" - - -DiveItem::DiveItem(int num, QString dt, float dur, float dep, QString loc, DiveItem *p): - number(num), dateTime(dt), duration(dur), depth(dep), location(loc), parentItem(p) -{ - if (parentItem) - parentItem->addChild(this); -} - - -DiveTripModel::DiveTripModel(const QString &filename, QObject *parent) : QAbstractItemModel(parent), filename(filename) -{ - rootItem = new DiveItem; -} - - -Qt::ItemFlags DiveTripModel::flags(const QModelIndex &index) const -{ - Qt::ItemFlags diveFlags = QAbstractItemModel::flags(index); - if (index.isValid()) { - diveFlags |= Qt::ItemIsSelectable|Qt::ItemIsEnabled; - } - return diveFlags; -} - - -QVariant DiveTripModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - if (role != Qt::DisplayRole) - return QVariant(); - - DiveItem *item = static_cast(index.internalPointer()); - - QVariant retVal; - switch (index.column()) { - case DIVE_NUMBER: - retVal = QVariant(item->diveNumber()); - break; - case DIVE_DATE_TIME: - retVal = QVariant(item->diveDateTime()); - break; - case DIVE_DURATION: - retVal = QVariant(item->diveDuration()); - break; - case DIVE_DEPTH: - retVal = QVariant(item->diveDepth()); - break; - case DIVE_LOCATION: - retVal = QVariant(item->diveLocation()); - break; - default: - return QVariant(); - }; - return retVal; -} - - -QVariant DiveTripModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { - if (section == DIVE_NUMBER) { - return tr("Dive number"); - } else if (section == DIVE_DATE_TIME) { - return tr("Date"); - } else if (section == DIVE_DURATION) { - return tr("Duration"); - } else if (section == DIVE_DEPTH) { - return tr("Depth"); - } else if (section == DIVE_LOCATION) { - return tr("Location"); - } - } - return QVariant(); -} - -int DiveTripModel::rowCount(const QModelIndex &parent) const -{ - /* only allow kids in column 0 */ - if (parent.isValid() && parent.column() > 0){ - return 0; - } - DiveItem *item = itemForIndex(parent); - return item ? item->childCount() : 0; -} - - - -int DiveTripModel::columnCount(const QModelIndex &parent) const -{ - return parent.isValid() && parent.column() != 0 ? 0 : COLUMNS; -} - - -QModelIndex DiveTripModel::index(int row, int column, const QModelIndex &parent) const -{ - - if (!rootItem || row < 0 || column < 0 || column >= COLUMNS || - (parent.isValid() && parent.column() != 0)) - return QModelIndex(); - - DiveItem *parentItem = itemForIndex(parent); - Q_ASSERT(parentItem); - if (DiveItem *item = parentItem->childAt(row)){ - return createIndex(row, column, item); - } - return QModelIndex(); -} - - -QModelIndex DiveTripModel::parent(const QModelIndex &childIndex) const -{ - if (!childIndex.isValid()) - return QModelIndex(); - - DiveItem *child = static_cast(childIndex.internalPointer()); - DiveItem *parent = child->parent(); - - if (parent == rootItem) - return QModelIndex(); - - return createIndex(parent->rowOfChild(child), 0, parent); -} - - -DiveItem* DiveTripModel::itemForIndex(const QModelIndex &index) const -{ - if (index.isValid()) { - DiveItem *item = static_cast(index.internalPointer()); - return item; - } - return rootItem; -} diff --git a/qt-ui/divetripmodel.h b/qt-ui/divetripmodel.h deleted file mode 100644 index ad1815798..000000000 --- a/qt-ui/divetripmodel.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * divetripmodel.h - * - * header file for the divetrip model of Subsurface - * - */ -#ifndef DIVETRIPMODEL_H -#define DIVETRIPMODEL_H - -#include - -/*! A DiveItem for use with a DiveTripModel - * - * A simple class which wraps basic stats for a dive (e.g. duration, depth) and - * tidies up after it's children. This is done manually as we don't inherit from - * QObject. - * -*/ -class DiveItem -{ -public: - explicit DiveItem(): number(0), dateTime(QString()), duration(0.0), depth(0.0), location(QString()) {parentItem = 0;} - explicit DiveItem(int num, QString dt, float, float, QString loc, DiveItem *parent = 0); - ~DiveItem() { qDeleteAll(childlist); } - - int diveNumber() const { return number; } - QString diveDateTime() const { return dateTime; } - float diveDuration() const { return duration; } - float diveDepth() const { return depth; } - QString diveLocation() const { return location; } - - DiveItem *parent() const { return parentItem; } - DiveItem *childAt(int row) const { return childlist.value(row); } - int rowOfChild(DiveItem *child) const { return childlist.indexOf(child); } - int childCount() const { return childlist.count(); } - bool hasChildren() const { return !childlist.isEmpty(); } - QList children() const { return childlist; } - void addChild(DiveItem* item) { item->parentItem = this; childlist << item; } /* parent = self */ - - -private: - - int number; - QString dateTime; - float duration; - float depth; - QString location; - - DiveItem *parentItem; - QList childlist; - -}; - - -enum Column {DIVE_NUMBER, DIVE_DATE_TIME, DIVE_DURATION, DIVE_DEPTH, DIVE_LOCATION, COLUMNS}; - - -/*! An AbstractItemModel for recording dive trip information such as a list of dives. -* -*/ -class DiveTripModel : public QAbstractItemModel -{ -public: - - DiveTripModel(const QString &filename, QObject *parent = 0); - - Qt::ItemFlags flags(const QModelIndex &index) const; - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role) const; - int rowCount(const QModelIndex &parent) const; - - int columnCount(const QModelIndex &parent = QModelIndex()) const; - virtual QModelIndex index(int row, int column, - const QModelIndex &parent = QModelIndex()) const; - virtual QModelIndex parent(const QModelIndex &child) const; - - DiveItem *itemForIndex(const QModelIndex &) const; - -private: - - DiveItem *rootItem; - QString filename; - -}; - -#endif // DIVETRIPMODEL_H diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index 551c28faa..46ce076d4 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -13,7 +13,6 @@ #include #include "divelistview.h" -#include "divetripmodel.h" #include "glib.h" #include "../dive.h" @@ -21,45 +20,11 @@ #include "../pref.h" -MainWindow::MainWindow() : ui(new Ui::MainWindow()) +MainWindow::MainWindow() : ui(new Ui::MainWindow()), + model(new DiveTripModel(this)) { ui->setupUi(this); - - /* may want to change ctor to avoid filename as 1st param. - * here we just use an empty string - */ - model = new DiveTripModel("",this); - if (model) { - ui->ListWidget->setModel(model); - } - /* we need root to parent all top level dives - * trips need more work as it complicates parent/child stuff. - * - * Todo: look at alignment/format of e.g. duration in view - * - */ - DiveItem *dive = 0; - DiveItem *root = model->itemForIndex(QModelIndex()); - if (root) { - int i; - Q_UNUSED(dive) - - struct dive *d; - qDebug("address of dive_table %p", &dive_table); - for_each_dive(i, d) { - struct tm tm; - char *buffer; - utc_mkdate(d->when, &tm); - buffer = get_dive_date_string(&tm); - dive = new DiveItem(d->number, - buffer, - (float)d->duration.seconds/60, - (float)d->maxdepth.mm/1000 , - d->location, - root); - free(buffer); - } - } + ui->ListWidget->setModel(model); } void MainWindow::on_actionNew_triggered() diff --git a/qt-ui/models.cpp b/qt-ui/models.cpp index d1b8dc0a0..40307d022 100644 --- a/qt-ui/models.cpp +++ b/qt-ui/models.cpp @@ -6,6 +6,7 @@ */ #include "models.h" #include "../dive.h" +#include "../divelist.h" extern struct tank_info tank_info[100]; @@ -280,3 +281,226 @@ void TankInfoModel::update() endInsertRows(); } } + +/*! A DiveItem for use with a DiveTripModel + * + * A simple class which wraps basic stats for a dive (e.g. duration, depth) and + * tidies up after it's children. This is done manually as we don't inherit from + * QObject. + * +*/ +class DiveItem +{ +public: + explicit DiveItem(): number(0), dateTime(QString()), duration(0.0), depth(0.0), location(QString()) {parentItem = 0;} + explicit DiveItem(int num, QString dt, float, float, QString loc, DiveItem *parent = 0); + ~DiveItem() { qDeleteAll(childlist); } + + int diveNumber() const { return number; } + const QString& diveDateTime() const { return dateTime; } + float diveDuration() const { return duration; } + float diveDepth() const { return depth; } + const QString& diveLocation() const { return location; } + DiveItem *parent() const { return parentItem; } + const QList& children() const { return childlist; } + + void addChild(DiveItem* item) { + item->parentItem = this; + childlist.push_back(item); + } /* parent = self */ + +private: + int number; + QString dateTime; + float duration; + float depth; + QString location; + + DiveItem *parentItem; + QList childlist; + +}; + +DiveItem::DiveItem(int num, QString dt, float dur, float dep, QString loc, DiveItem *p): + number(num), dateTime(dt), duration(dur), depth(dep), location(loc), parentItem(p) +{ + if (parentItem) + parentItem->addChild(this); +} + +DiveTripModel::DiveTripModel(QObject *parent) : QAbstractItemModel(parent) +{ + rootItem = new DiveItem; + int i; + struct dive *d; + + for_each_dive(i, d) { + struct tm tm; + char *buffer; + utc_mkdate(d->when, &tm); + buffer = get_dive_date_string(&tm); + new DiveItem(d->number, + buffer, + d->duration.seconds/60.0, + d->maxdepth.mm/1000.0 , + d->location, + rootItem); + free(buffer); + } +} + + +Qt::ItemFlags DiveTripModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags diveFlags = QAbstractItemModel::flags(index); + if (index.isValid()) { + diveFlags |= Qt::ItemIsSelectable|Qt::ItemIsEnabled; + } + return diveFlags; +} + + +QVariant DiveTripModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + DiveItem *item = static_cast(index.internalPointer()); + + QVariant retVal; + if (role == Qt::DisplayRole){ + switch (index.column()) { + case NR: + retVal = item->diveNumber(); + break; + case DATE: + retVal = item->diveDateTime(); + break; + case DURATION: + retVal = item->diveDuration(); + break; + case DEPTH: + retVal = item->diveDepth(); + break; + case LOCATION: + retVal = item->diveLocation(); + break; + } + } + return retVal; +} + + +QVariant DiveTripModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant ret; + if (orientation != Qt::Horizontal){ + return ret; + } + + if (role == Qt::DisplayRole) { + switch(section){ + case NR: + ret = tr("#"); + break; + case DATE: + ret = tr("Date"); + break; + case RATING: + ret = UTF8_BLACKSTAR; + break; + case DEPTH: + ret = tr("ft"); + break; + case DURATION: + ret = tr("min"); + break; + case TEMPERATURE: + ret = UTF8_DEGREE "F"; + break; + case TOTALWEIGHT: + ret = tr("lbs"); + break; + case SUIT: + ret = tr("Suit"); + break; + case CYLINDER: + ret = tr("Cyl"); + break; + case NITROX: + ret = "O" UTF8_SUBSCRIPT_2 "%"; + break; + case SAC: + ret = tr("SAC"); + break; + case OTU: + ret = tr("OTU"); + break; + case MAXCNS: + ret = tr("maxCNS"); + break; + case LOCATION: + ret = tr("Location"); + break; + } + } + return ret; +} + +int DiveTripModel::rowCount(const QModelIndex &parent) const +{ + /* only allow kids in column 0 */ + if (parent.isValid() && parent.column() > 0){ + return 0; + } + DiveItem *item = itemForIndex(parent); + return item ? item->children().count() : 0; +} + + + +int DiveTripModel::columnCount(const QModelIndex &parent) const +{ + return parent.isValid() && parent.column() != 0 ? 0 : COLUMNS; +} + + +QModelIndex DiveTripModel::index(int row, int column, const QModelIndex &parent) const +{ + + if (!rootItem || row < 0 || column < 0 || column >= COLUMNS || + (parent.isValid() && parent.column() != 0)) + return QModelIndex(); + + DiveItem *parentItem = itemForIndex(parent); + Q_ASSERT(parentItem); + if (DiveItem *item = parentItem->children().at(row)){ + return createIndex(row, column, item); + } + return QModelIndex(); +} + + +QModelIndex DiveTripModel::parent(const QModelIndex &childIndex) const +{ + if (!childIndex.isValid()) + return QModelIndex(); + + DiveItem *child = static_cast(childIndex.internalPointer()); + DiveItem *parent = child->parent(); + + if (parent == rootItem) + return QModelIndex(); + + return createIndex(parent->children().indexOf(child), 0, parent); +} + + +DiveItem* DiveTripModel::itemForIndex(const QModelIndex &index) const +{ + if (index.isValid()) { + DiveItem *item = static_cast(index.internalPointer()); + return item; + } + return rootItem; +} diff --git a/qt-ui/models.h b/qt-ui/models.h index 8d86102cb..d64faf0bd 100644 --- a/qt-ui/models.h +++ b/qt-ui/models.h @@ -15,7 +15,7 @@ class TankInfoModel : public QAbstractTableModel { Q_OBJECT public: - enum { DESCRIPTION, ML, BAR}; + enum Column { DESCRIPTION, ML, BAR}; TankInfoModel(); /*reimp*/ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; @@ -30,10 +30,12 @@ private: int rows; }; +/* Encapsulation of the Cylinder Model, that presents the + * Current cylinders that are used on a dive. */ class CylindersModel : public QAbstractTableModel { Q_OBJECT public: - enum {TYPE, SIZE, MAXPRESS, START, END, O2, HE}; + enum Column {TYPE, SIZE, MAXPRESS, START, END, O2, HE}; explicit CylindersModel(QObject* parent = 0); /*reimp*/ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; @@ -53,8 +55,10 @@ private: QMap usedRows; }; +/* Encapsulation of the Weight Model, that represents + * the current weights on a dive. */ class WeightModel : public QAbstractTableModel { - enum{TYPE, WEIGHT}; + enum Column {TYPE, WEIGHT}; /*reimp*/ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; /*reimp*/ int columnCount(const QModelIndex& parent = QModelIndex()) const; /*reimp*/ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; @@ -67,4 +71,28 @@ private: int rows; }; +/*! An AbstractItemModel for recording dive trip information such as a list of dives. +* +*/ +class DiveItem; // Represents a single item on the model, implemented in the .cpp since it's private for this class. +class DiveTripModel : public QAbstractItemModel +{ +public: + enum Column {NR, DATE, RATING, DEPTH, DURATION, TEMPERATURE, TOTALWEIGHT, SUIT, CYLINDER, NITROX, SAC, OTU, MAXCNS, LOCATION, COLUMNS }; + + DiveTripModel(QObject *parent = 0); + + /*reimp*/ Qt::ItemFlags flags(const QModelIndex &index) const; + /*reimp*/ QVariant data(const QModelIndex &index, int role) const; + /*reimp*/ QVariant headerData(int section, Qt::Orientation orientation, int role) const; + /*reimp*/ int rowCount(const QModelIndex &parent) const; + /*reimp*/ int columnCount(const QModelIndex &parent = QModelIndex()) const; + /*reimp*/ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + /*reimp*/ QModelIndex parent(const QModelIndex &child) const; + +private: + DiveItem *itemForIndex(const QModelIndex& index) const; + DiveItem *rootItem; +}; + #endif -- cgit v1.2.3-70-g09d2 From 9ffb707d9d9d478b7538a8cd9ef9ef7c6edae25c Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Mon, 22 Apr 2013 16:00:27 -0700 Subject: Initial version of the Star Picker Widget. A very simple to use widget: StarWidget *stars = new StarWidget( windowParent); stars->setMaximumStars(10); stars->setCurrentStars( rand()%10); stars->show(); connect(stars, SIGNAL(valueChanged(int)), someObj, SLOT(starsChangedValue(int))); It currently uses a 'star.svg' file on the same folder as the binary. Signed-off-by: Tomaz Canabrava Signed-off-by: Dirk Hohndel --- Makefile | 6 +-- qt-ui/mainwindow.cpp | 6 +++ qt-ui/starwidget.cpp | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++ qt-ui/starwidget.h | 42 +++++++++++++++++++++ resources.qrc | 5 +++ star.svg | 74 +++++++++++++++++++++++++++++++++++++ 6 files changed, 232 insertions(+), 3 deletions(-) create mode 100644 qt-ui/starwidget.cpp create mode 100644 qt-ui/starwidget.h create mode 100644 resources.qrc create mode 100644 star.svg (limited to 'Makefile') diff --git a/Makefile b/Makefile index 7b7ec0476..076b5f123 100644 --- a/Makefile +++ b/Makefile @@ -104,10 +104,10 @@ LIBUSB = $(shell $(PKGCONFIG) --libs libusb-1.0 2> /dev/null) # Use qmake to find out which Qt version we are building for. QT_VERSION_MAJOR = $(shell $(QMAKE) -query QT_VERSION | cut -d. -f1) ifeq ($(QT_VERSION_MAJOR), 5) - QT_MODULES = Qt5Widgets + QT_MODULES = Qt5Widgets Qt5Svg QT_CORE = Qt5Core else - QT_MODULES = QtGui + QT_MODULES = QtGui QtSvg QT_CORE = QtCore endif @@ -187,7 +187,7 @@ MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.m QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o qt-ui/divelistview.o \ - qt-ui/addcylinderdialog.o qt-ui/models.o + qt-ui/addcylinderdialog.o qt-ui/models.o qt-ui/starwidget.o GTKOBJS = info-gtk.o divelist-gtk.o planner-gtk.o statistics-gtk.o diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index 46ce076d4..bb4bda1af 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -13,6 +13,7 @@ #include #include "divelistview.h" +#include "starwidget.h" #include "glib.h" #include "../dive.h" @@ -25,6 +26,11 @@ MainWindow::MainWindow() : ui(new Ui::MainWindow()), { ui->setupUi(this); ui->ListWidget->setModel(model); + // Just to test the star widgets, can be safely removed. + StarWidget *star = new StarWidget(0); + star->setMaximumStars(10); + star->setCurrentStars(3); + star->show(); } void MainWindow::on_actionNew_triggered() diff --git a/qt-ui/starwidget.cpp b/qt-ui/starwidget.cpp new file mode 100644 index 000000000..457946feb --- /dev/null +++ b/qt-ui/starwidget.cpp @@ -0,0 +1,102 @@ +#include "starwidget.h" +#include +#include +#include +#include +#include + +int StarWidget::currentStars() const +{ + return current; +} + +void StarWidget::enableHalfStars(bool enabled) +{ + halfStar = enabled; + update(); +} + +bool StarWidget::halfStarsEnabled() const +{ + return halfStar; +} + +int StarWidget::maxStars() const +{ + return stars; +} + +void StarWidget::mouseReleaseEvent(QMouseEvent* event) +{ + int starClicked = event->pos().x() / IMG_SIZE + 1; + if (starClicked > stars) + starClicked = stars; + + if (current == starClicked) + current -= 1; + else + current = starClicked; + + update(); +} + +void StarWidget::paintEvent(QPaintEvent* event) +{ + QPainter p(this); + + for(int i = 0; i < current; i++) + p.drawPixmap(i * IMG_SIZE + SPACING, 0, activeStar); + + for(int i = current; i < stars; i++) + p.drawPixmap(i * IMG_SIZE + SPACING, 0, inactiveStar); +} + +void StarWidget::setCurrentStars(int value) +{ + current = value; + update(); + Q_EMIT valueChanged(current); +} + +void StarWidget::setMaximumStars(int maximum) +{ + stars = maximum; + update(); +} + +StarWidget::StarWidget(QWidget* parent, Qt::WindowFlags f): + QWidget(parent, f), + stars(5), + current(0), + halfStar(false) +{ + QSvgRenderer render(QString("star.svg")); + QPixmap renderedStar(IMG_SIZE, IMG_SIZE); + + renderedStar.fill(Qt::transparent); + QPainter painter(&renderedStar); + + render.render(&painter, QRectF(0, 0, IMG_SIZE, IMG_SIZE)); + activeStar = renderedStar; + inactiveStar = grayImage(&renderedStar); +} + +QPixmap StarWidget::grayImage(QPixmap* coloredImg) +{ + QImage img = coloredImg->toImage(); + for (int i = 0; i < img.width(); ++i) { + for (int j = 0; j < img.height(); ++j) { + QRgb col = img.pixel(i, j); + if (!col) + continue; + int gray = QColor(Qt::darkGray).rgb(); + img.setPixel(i, j, qRgb(gray, gray, gray)); + } + } + return QPixmap::fromImage(img); +} + +QSize StarWidget::sizeHint() const +{ + return QSize(IMG_SIZE * stars + SPACING * (stars-1), IMG_SIZE); +} diff --git a/qt-ui/starwidget.h b/qt-ui/starwidget.h new file mode 100644 index 000000000..cdbb3ab5c --- /dev/null +++ b/qt-ui/starwidget.h @@ -0,0 +1,42 @@ +#ifndef STARWIDGET_H +#define STARWIDGET_H + +#include + + +class StarWidget : public QWidget +{ + Q_OBJECT +public: + explicit StarWidget(QWidget* parent = 0, Qt::WindowFlags f = 0); + + int maxStars() const; + int currentStars() const; + bool halfStarsEnabled() const; + + /*reimp*/ QSize sizeHint() const; + + enum {SPACING = 2, IMG_SIZE = 16}; + +Q_SIGNALS: + void valueChanged(int stars); + +public Q_SLOTS: + void setCurrentStars(int value); + void setMaximumStars(int maximum); + void enableHalfStars(bool enabled); + +protected: + /*reimp*/ void mouseReleaseEvent(QMouseEvent* ); + /*reimp*/ void paintEvent(QPaintEvent* ); + +private: + int stars; + int current; + bool halfStar; + QPixmap activeStar; + QPixmap inactiveStar; + QPixmap grayImage(QPixmap *coloredImg); +}; + +#endif // STARWIDGET_H diff --git a/resources.qrc b/resources.qrc new file mode 100644 index 000000000..e257bf8f5 --- /dev/null +++ b/resources.qrc @@ -0,0 +1,5 @@ + + + star.svg + + diff --git a/star.svg b/star.svg new file mode 100644 index 000000000..e4345eb48 --- /dev/null +++ b/star.svg @@ -0,0 +1,74 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + -- cgit v1.2.3-70-g09d2 From d326a91c865caff097af9b1736740291fb3e63ac Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Mon, 22 Apr 2013 22:46:38 -0700 Subject: Move some of the checks a little bit up Signed-off-by: Thiago Macieira --- Makefile | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 076b5f123..56f2c7941 100644 --- a/Makefile +++ b/Makefile @@ -114,8 +114,20 @@ endif # we need GLIB2CFLAGS for gettext QTCXXFLAGS = $(shell $(PKGCONFIG) --cflags $(QT_MODULES)) $(GLIB2CFLAGS) LIBQT = $(shell $(PKGCONFIG) --libs $(QT_MODULES)) +ifneq ($(filter reduce_relocations, $(shell $(PKGCONFIG) --variable qt_config $(QT_CORE))), ) + QTCXXFLAGS += -fPIE +endif LIBGTK = $(shell $(PKGCONFIG) --libs gtk+-2.0 glib-2.0) +ifneq (,$(filter $(UNAME),linux kfreebsd gnu)) + LIBGCONF2 = $(shell $(PKGCONFIG) --libs gconf-2.0) + GCONF2CFLAGS = $(shell $(PKGCONFIG) --cflags gconf-2.0) +else ifeq ($(UNAME), darwin) + LIBGTK += $(shell $(PKGCONFIG) --libs gtk-mac-integration) -framework CoreFoundation -framework CoreServices + GTKCFLAGS += $(shell $(PKGCONFIG) --cflags gtk-mac-integration) + GTK_MAC_BUNDLER = ~/.local/bin/gtk-mac-bundler +endif + LIBDIVECOMPUTERCFLAGS = $(LIBDIVECOMPUTERINCLUDES) LIBDIVECOMPUTER = $(LIBDIVECOMPUTERARCHIVE) $(LIBUSB) @@ -145,13 +157,8 @@ ifneq ($(strip $(LIBSQLITE3)),) endif ifneq (,$(filter $(UNAME),linux kfreebsd gnu)) - LIBGCONF2 = $(shell $(PKGCONFIG) --libs gconf-2.0) - GCONF2CFLAGS = $(shell $(PKGCONFIG) --cflags gconf-2.0) OSSUPPORT = linux OSSUPPORT_CFLAGS = $(GTKCFLAGS) $(GCONF2CFLAGS) - ifneq ($(filter reduce_relocations, $(shell $(PKGCONFIG) --variable qt_config $(QT_CORE))), ) - CXXFLAGS += -fPIE - endif else ifeq ($(UNAME), darwin) OSSUPPORT = macos OSSUPPORT_CFLAGS = $(GTKCFLAGS) @@ -160,10 +167,7 @@ else ifeq ($(UNAME), darwin) MACOSXSTAGING = $(MACOSXFILES)/Subsurface.app INFOPLIST = $(MACOSXFILES)/Info.plist INFOPLISTINPUT = $(INFOPLIST).in - EXTRALIBS = $(shell $(PKGCONFIG) --libs gtk-mac-integration) -framework CoreFoundation -framework CoreServices - CFLAGS += $(shell $(PKGCONFIG) --cflags gtk-mac-integration) LDFLAGS += -headerpad_max_install_names -sectcreate __TEXT __info_plist $(INFOPLIST) - GTK_MAC_BUNDLER = ~/.local/bin/gtk-mac-bundler else OSSUPPORT = windows OSSUPPORT_CFLAGS = $(GTKCFLAGS) -- cgit v1.2.3-70-g09d2 From e9ec2dcc455450262b56a901e9d397988d384480 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Mon, 22 Apr 2013 23:09:23 -0700 Subject: Split the SQLite3, libzip, libxslt and osmgpsmap detection from use Signed-off-by: Thiago Macieira --- Makefile | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 56f2c7941..0eade3f36 100644 --- a/Makefile +++ b/Makefile @@ -136,25 +136,17 @@ LIBXSLT = $(shell $(XSLCONFIG) --libs) XML2CFLAGS = $(shell $(XML2CONFIG) --cflags) GLIB2CFLAGS = $(shell $(PKGCONFIG) --cflags glib-2.0) GTKCFLAGS = $(shell $(PKGCONFIG) --cflags gtk+-2.0) -CFLAGS += $(shell $(XSLCONFIG) --cflags) +XSLCFLAGS = $(shell $(XSLCONFIG) --cflags) OSMGPSMAPFLAGS += $(shell $(PKGCONFIG) --cflags osmgpsmap 2> /dev/null) LIBOSMGPSMAP += $(shell $(PKGCONFIG) --libs osmgpsmap 2> /dev/null) -ifneq ($(strip $(LIBOSMGPSMAP)),) - GPSOBJ = gps.o - CFLAGS += -DHAVE_OSM_GPS_MAP -endif LIBSOUPCFLAGS = $(shell $(PKGCONFIG) --cflags libsoup-2.4) LIBSOUP = $(shell $(PKGCONFIG) --libs libsoup-2.4) LIBZIP = $(shell $(PKGCONFIG) --libs libzip 2> /dev/null) -ifneq ($(strip $(LIBZIP)),) - ZIP = -DLIBZIP $(shell $(PKGCONFIG) --cflags libzip) -endif +ZIPFLAGS = $(strip $(shell $(PKGCONFIG) --cflags libzip 2> /dev/null)) LIBSQLITE3 = $(shell $(PKGCONFIG) --libs sqlite3 2> /dev/null) -ifneq ($(strip $(LIBSQLITE3)),) - SQLITE3 = -DSQLITE3 $(shell $(PKGCONFIG) --cflags sqlite3) -endif +SQLITE3FLAGS = $(strip $(shell $(PKGCONFIG) --cflags sqlite3)) ifneq (,$(filter $(UNAME),linux kfreebsd gnu)) OSSUPPORT = linux @@ -179,9 +171,6 @@ else XSLTDIR = .\\xslt endif -ifneq ($(strip $(LIBXSLT)),) - XSLT=-DXSLT='"$(XSLTDIR)"' -endif LIBS = $(LIBQT) $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGTK) $(LIBGCONF2) $(LIBDIVECOMPUTER) \ $(EXTRALIBS) $(LIBZIP) -lpthread -lm $(LIBOSMGPSMAP) $(LIBSOUP) $(LIBWINSOCK) @@ -198,7 +187,7 @@ GTKOBJS = info-gtk.o divelist-gtk.o planner-gtk.o statistics-gtk.o OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o deco.o planner.o \ parse-xml.o save-xml.o libdivecomputer.o print.o uemis.o uemis-downloader.o \ qt-gui.o statistics.o file.o cochran.o device.o download-dialog.o prefs.o \ - webservice.o sha1.o $(GPSOBJ) $(OSSUPPORT).o $(RESFILE) $(QTOBJS) $(GTKOBJS) + webservice.o sha1.o $(OSSUPPORT).o $(RESFILE) $(QTOBJS) $(GTKOBJS) # Add files to the following variables if the auto-detection based on the # filename fails @@ -315,8 +304,22 @@ update-po-files: tx pull -af EXTRA_FLAGS = $(QTCXXFLAGS) $(GTKCFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) \ - $(XSLT) $(ZIP) $(SQLITE3) $(LIBDIVECOMPUTERCFLAGS) \ - $(LIBSOUPCFLAGS) $(OSMGPSMAPFLAGS) $(GCONF2CFLAGS) + $(LIBDIVECOMPUTERCFLAGS) \ + $(LIBSOUPCFLAGS) $(GCONF2CFLAGS) + +ifneq ($(SQLITE3FLAGS),) + EXTRA_FLAGS += -DSQLITE3 $(SQLITE3FLAGS) +endif +ifneq ($(ZIPFLAGS),) + EXTRA_FLAGS += -DLIBZIP $(ZIPFLAGS) +endif +ifneq ($(strip $(LIBXSLT)),) + EXTRA_FLAGS += -DXSLT='"$(XSLTDIR)"' $(XSLCFLAGS) +endif +ifneq ($(strip $(LIBOSMGPSMAP)),) + OBJS += gps.o + EXTRA_FLAGS += -DHAVE_OSM_GPS_MAP $(OSMGPSMAPFLAGS) +endif MOCFLAGS = $(filter -I%, $(CXXFLAGS) $(EXTRA_FLAGS)) $(filter -D%, $(CXXFLAGS) $(EXTRA_FLAGS)) -- cgit v1.2.3-70-g09d2 From 83309d93692b7db7b6fce8c6c1f17eee228d626d Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Mon, 22 Apr 2013 23:13:10 -0700 Subject: Move the list of objects a little further up This also implies we don't need the OSSUPPORT variable anymore. We simply add to OBJS. Signed-off-by: Thiago Macieira --- Makefile | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 0eade3f36..b699b56f2 100644 --- a/Makefile +++ b/Makefile @@ -148,11 +148,22 @@ ZIPFLAGS = $(strip $(shell $(PKGCONFIG) --cflags libzip 2> /dev/null)) LIBSQLITE3 = $(shell $(PKGCONFIG) --libs sqlite3 2> /dev/null) SQLITE3FLAGS = $(strip $(shell $(PKGCONFIG) --cflags sqlite3)) +QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o qt-ui/divelistview.o \ + qt-ui/addcylinderdialog.o qt-ui/models.o qt-ui/starwidget.o + +GTKOBJS = info-gtk.o divelist-gtk.o planner-gtk.o statistics-gtk.o + +OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o divelist-gtk.o deco.o \ + planner.o planner-gtk.o \ + parse-xml.o save-xml.o libdivecomputer.o print.o uemis.o uemis-downloader.o \ + qt-gui.o statistics.o file.o cochran.o device.o download-dialog.o prefs.o \ + webservice.o sha1.o $(RESFILE) $(QTOBJS) $(GTKOBJS) + ifneq (,$(filter $(UNAME),linux kfreebsd gnu)) - OSSUPPORT = linux + OBJS += linux.o OSSUPPORT_CFLAGS = $(GTKCFLAGS) $(GCONF2CFLAGS) else ifeq ($(UNAME), darwin) - OSSUPPORT = macos + OBJS += macos.o OSSUPPORT_CFLAGS = $(GTKCFLAGS) MACOSXINSTALL = /Applications/Subsurface.app MACOSXFILES = packaging/macosx @@ -161,7 +172,7 @@ else ifeq ($(UNAME), darwin) INFOPLISTINPUT = $(INFOPLIST).in LDFLAGS += -headerpad_max_install_names -sectcreate __TEXT __info_plist $(INFOPLIST) else - OSSUPPORT = windows + OBSJ += windows.o OSSUPPORT_CFLAGS = $(GTKCFLAGS) WINDOWSSTAGING = ./packaging/windows WINMSGDIRS=$(addprefix share/locale/,$(shell ls po/*.po | sed -e 's/po\/\(..\)_.*/\1\/LC_MESSAGES/')) @@ -179,16 +190,6 @@ MSGLANGS=$(notdir $(wildcard po/*.po)) MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.mo)) -QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o qt-ui/divelistview.o \ - qt-ui/addcylinderdialog.o qt-ui/models.o qt-ui/starwidget.o - -GTKOBJS = info-gtk.o divelist-gtk.o planner-gtk.o statistics-gtk.o - -OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o deco.o planner.o \ - parse-xml.o save-xml.o libdivecomputer.o print.o uemis.o uemis-downloader.o \ - qt-gui.o statistics.o file.o cochran.o device.o download-dialog.o prefs.o \ - webservice.o sha1.o $(OSSUPPORT).o $(RESFILE) $(QTOBJS) $(GTKOBJS) - # Add files to the following variables if the auto-detection based on the # filename fails OBJS_NEEDING_MOC = -- cgit v1.2.3-70-g09d2 From 50420d48d93a79866d34b0462625531257cb6151 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Mon, 22 Apr 2013 23:15:35 -0700 Subject: Remove the unused OSSUPPORT_CFLAGS variable Signed-off-by: Thiago Macieira --- Makefile | 3 --- 1 file changed, 3 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index b699b56f2..37dc3437d 100644 --- a/Makefile +++ b/Makefile @@ -161,10 +161,8 @@ OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o divelist-gtk ifneq (,$(filter $(UNAME),linux kfreebsd gnu)) OBJS += linux.o - OSSUPPORT_CFLAGS = $(GTKCFLAGS) $(GCONF2CFLAGS) else ifeq ($(UNAME), darwin) OBJS += macos.o - OSSUPPORT_CFLAGS = $(GTKCFLAGS) MACOSXINSTALL = /Applications/Subsurface.app MACOSXFILES = packaging/macosx MACOSXSTAGING = $(MACOSXFILES)/Subsurface.app @@ -173,7 +171,6 @@ else ifeq ($(UNAME), darwin) LDFLAGS += -headerpad_max_install_names -sectcreate __TEXT __info_plist $(INFOPLIST) else OBSJ += windows.o - OSSUPPORT_CFLAGS = $(GTKCFLAGS) WINDOWSSTAGING = ./packaging/windows WINMSGDIRS=$(addprefix share/locale/,$(shell ls po/*.po | sed -e 's/po\/\(..\)_.*/\1\/LC_MESSAGES/')) NSIINPUTFILE = $(WINDOWSSTAGING)/subsurface.nsi.in -- cgit v1.2.3-70-g09d2 From 77ebc2f4c854748edaff6bc518e105b56f656a47 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Mon, 22 Apr 2013 23:18:36 -0700 Subject: Move the MSGOBJS variable definition a little further down Move it closer to the actual rules. This is not a variable that we'll usually modify. Signed-off-by: Thiago Macieira --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 37dc3437d..c5f72ccf8 100644 --- a/Makefile +++ b/Makefile @@ -184,8 +184,6 @@ LIBS = $(LIBQT) $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGTK) $(LIBGCONF2) $(LIB $(EXTRALIBS) $(LIBZIP) -lpthread -lm $(LIBOSMGPSMAP) $(LIBSOUP) $(LIBWINSOCK) MSGLANGS=$(notdir $(wildcard po/*.po)) -MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.mo)) - # Add files to the following variables if the auto-detection based on the # filename fails @@ -193,6 +191,8 @@ OBJS_NEEDING_MOC = OBJS_NEEDING_UIC = HEADERS_NEEDING_MOC = +MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.mo)) + # Add the objects for the header files which define QObject subclasses HEADERS_NEEDING_MOC += $(shell grep -l -s 'Q_OBJECT' $(OBJS:.o=.h)) MOC_OBJS = $(HEADERS_NEEDING_MOC:.h=.moc.o) -- cgit v1.2.3-70-g09d2 From ddbb942d0fce58f5ef7335f23da703ddb721bcf9 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Mon, 22 Apr 2013 23:26:33 -0700 Subject: Reorder the Makefile Create three sections: 1) the detection rules 2) the main rules, what we usually edit 3) the build rules, which we usually don't change Signed-off-by: Thiago Macieira --- Makefile | 112 ++++++++++++++++++++++++++++++++++----------------------------- 1 file changed, 60 insertions(+), 52 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index c5f72ccf8..e882a353d 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,8 @@ CFLAGS=-Wall -Wno-pointer-sign -g $(CLCFLAGS) -DGSEAL_ENABLE CXX=g++ CXXFLAGS=-Wall -g $(CLCFLAGS) -DQT_NO_KEYWORDS INSTALL=install + +# This are the detection rules PKGCONFIG=pkg-config XML2CONFIG=xml2-config XSLCONFIG=xslt-config @@ -12,40 +14,7 @@ QMAKE=qmake MOC=moc UIC=uic -# these locations seem to work for SuSE and Fedora -# prefix = $(HOME) -prefix = $(DESTDIR)/usr -BINDIR = $(prefix)/bin -DATADIR = $(prefix)/share -DESKTOPDIR = $(DATADIR)/applications -ICONPATH = $(DATADIR)/icons/hicolor -ICONDIR = $(ICONPATH)/scalable/apps -MANDIR = $(DATADIR)/man/man1 -XSLTDIR = $(DATADIR)/subsurface/xslt -gtk_update_icon_cache = gtk-update-icon-cache -f -t $(ICONPATH) - -NAME = subsurface -ICONFILE = $(NAME)-icon.svg -DESKTOPFILE = $(NAME).desktop -MANFILES = $(NAME).1 -XSLTFILES = xslt/*.xslt - -VERSION_FILE = version.h -# There's only one line in $(VERSION_FILE); use the shell builtin `read' -STORED_VERSION_STRING = \ - $(subst ",,$(shell [ ! -r $(VERSION_FILE) ] || \ - read ignore ignore v <$(VERSION_FILE) && echo $$v)) -#" workaround editor syntax highlighting quirk - UNAME := $(shell $(CC) -dumpmachine 2>&1 | grep -E -o "linux|darwin|win|gnu|kfreebsd") -GET_VERSION = ./scripts/get-version -VERSION_STRING := $(shell $(GET_VERSION) linux || echo "v$(VERSION)") -# Mac Info.plist style with three numbers 1.2.3 -CFBUNDLEVERSION_STRING := $(shell $(GET_VERSION) darwin $(VERSION_STRING) || \ - echo "$(VERSION).0") -# Windows .nsi style with four numbers 1.2.3.4 -PRODVERSION_STRING := $(shell $(GET_VERSION) win $(VERSION_STRING) || \ - echo "$(VERSION).0.0") # find libdivecomputer # First deal with the cross compile environment and with Mac. @@ -148,6 +117,30 @@ ZIPFLAGS = $(strip $(shell $(PKGCONFIG) --cflags libzip 2> /dev/null)) LIBSQLITE3 = $(shell $(PKGCONFIG) --libs sqlite3 2> /dev/null) SQLITE3FLAGS = $(strip $(shell $(PKGCONFIG) --cflags sqlite3)) +# These are the main rules + +# these locations seem to work for SuSE and Fedora +# prefix = $(HOME) +prefix = $(DESTDIR)/usr +BINDIR = $(prefix)/bin +DATADIR = $(prefix)/share +DESKTOPDIR = $(DATADIR)/applications +ICONPATH = $(DATADIR)/icons/hicolor +ICONDIR = $(ICONPATH)/scalable/apps +MANDIR = $(DATADIR)/man/man1 +XSLTDIR = $(DATADIR)/subsurface/xslt +gtk_update_icon_cache = gtk-update-icon-cache -f -t $(ICONPATH) + +NAME = subsurface +ICONFILE = $(NAME)-icon.svg +DESKTOPFILE = $(NAME).desktop +MANFILES = $(NAME).1 +XSLTFILES = xslt/*.xslt + +EXTRA_FLAGS = $(QTCXXFLAGS) $(GTKCFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) \ + $(LIBDIVECOMPUTERCFLAGS) \ + $(LIBSOUPCFLAGS) $(GCONF2CFLAGS) + QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o qt-ui/divelistview.o \ qt-ui/addcylinderdialog.o qt-ui/models.o qt-ui/starwidget.o @@ -159,6 +152,20 @@ OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o divelist-gtk qt-gui.o statistics.o file.o cochran.o device.o download-dialog.o prefs.o \ webservice.o sha1.o $(RESFILE) $(QTOBJS) $(GTKOBJS) +ifneq ($(SQLITE3FLAGS),) + EXTRA_FLAGS += -DSQLITE3 $(SQLITE3FLAGS) +endif +ifneq ($(ZIPFLAGS),) + EXTRA_FLAGS += -DLIBZIP $(ZIPFLAGS) +endif +ifneq ($(strip $(LIBXSLT)),) + EXTRA_FLAGS += -DXSLT='"$(XSLTDIR)"' $(XSLCFLAGS) +endif +ifneq ($(strip $(LIBOSMGPSMAP)),) + OBJS += gps.o + EXTRA_FLAGS += -DHAVE_OSM_GPS_MAP $(OSMGPSMAPFLAGS) +endif + ifneq (,$(filter $(UNAME),linux kfreebsd gnu)) OBJS += linux.o else ifeq ($(UNAME), darwin) @@ -179,7 +186,6 @@ else XSLTDIR = .\\xslt endif - LIBS = $(LIBQT) $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGTK) $(LIBGCONF2) $(LIBDIVECOMPUTER) \ $(EXTRALIBS) $(LIBZIP) -lpthread -lm $(LIBOSMGPSMAP) $(LIBSOUP) $(LIBWINSOCK) @@ -191,6 +197,26 @@ OBJS_NEEDING_MOC = OBJS_NEEDING_UIC = HEADERS_NEEDING_MOC = +# These are the generic rules for building + +# Rules for building and creating the version file + +VERSION_FILE = version.h +# There's only one line in $(VERSION_FILE); use the shell builtin `read' +STORED_VERSION_STRING = \ + $(subst ",,$(shell [ ! -r $(VERSION_FILE) ] || \ + read ignore ignore v <$(VERSION_FILE) && echo $$v)) +#" workaround editor syntax highlighting quirk + +GET_VERSION = ./scripts/get-version +VERSION_STRING := $(shell $(GET_VERSION) linux || echo "v$(VERSION)") +# Mac Info.plist style with three numbers 1.2.3 +CFBUNDLEVERSION_STRING := $(shell $(GET_VERSION) darwin $(VERSION_STRING) || \ + echo "$(VERSION).0") +# Windows .nsi style with four numbers 1.2.3.4 +PRODVERSION_STRING := $(shell $(GET_VERSION) win $(VERSION_STRING) || \ + echo "$(VERSION).0.0") + MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.mo)) # Add the objects for the header files which define QObject subclasses @@ -301,24 +327,6 @@ update-po-files: tx push -s tx pull -af -EXTRA_FLAGS = $(QTCXXFLAGS) $(GTKCFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) \ - $(LIBDIVECOMPUTERCFLAGS) \ - $(LIBSOUPCFLAGS) $(GCONF2CFLAGS) - -ifneq ($(SQLITE3FLAGS),) - EXTRA_FLAGS += -DSQLITE3 $(SQLITE3FLAGS) -endif -ifneq ($(ZIPFLAGS),) - EXTRA_FLAGS += -DLIBZIP $(ZIPFLAGS) -endif -ifneq ($(strip $(LIBXSLT)),) - EXTRA_FLAGS += -DXSLT='"$(XSLTDIR)"' $(XSLCFLAGS) -endif -ifneq ($(strip $(LIBOSMGPSMAP)),) - OBJS += gps.o - EXTRA_FLAGS += -DHAVE_OSM_GPS_MAP $(OSMGPSMAPFLAGS) -endif - MOCFLAGS = $(filter -I%, $(CXXFLAGS) $(EXTRA_FLAGS)) $(filter -D%, $(CXXFLAGS) $(EXTRA_FLAGS)) %.o: %.c -- cgit v1.2.3-70-g09d2 From d47b9045802f5f05dfafad982cdf4ab4b86a2c2c Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Fri, 12 Apr 2013 23:42:14 -0700 Subject: Trim Makefile, Configure.mk and Rules.mk Configure.mk contains the detection rules, whereas Rules.mk contains the rules to actually build Subsurface. This simplifies Makefile greatly, which is the file that should be actually modified during regular updates to the codebase. Signed-off-by: Thiago Macieira --- Configure.mk | 292 +----------------------------------------------------- Makefile | 316 +---------------------------------------------------------- Rules.mk | 202 +------------------------------------- 3 files changed, 5 insertions(+), 805 deletions(-) (limited to 'Makefile') diff --git a/Configure.mk b/Configure.mk index e882a353d..f9b9e0674 100644 --- a/Configure.mk +++ b/Configure.mk @@ -1,12 +1,6 @@ -VERSION=3.0.2 +# -*- Makefile -*- +# This file contains the detection rules -CC=gcc -CFLAGS=-Wall -Wno-pointer-sign -g $(CLCFLAGS) -DGSEAL_ENABLE -CXX=g++ -CXXFLAGS=-Wall -g $(CLCFLAGS) -DQT_NO_KEYWORDS -INSTALL=install - -# This are the detection rules PKGCONFIG=pkg-config XML2CONFIG=xml2-config XSLCONFIG=xslt-config @@ -116,285 +110,3 @@ ZIPFLAGS = $(strip $(shell $(PKGCONFIG) --cflags libzip 2> /dev/null)) LIBSQLITE3 = $(shell $(PKGCONFIG) --libs sqlite3 2> /dev/null) SQLITE3FLAGS = $(strip $(shell $(PKGCONFIG) --cflags sqlite3)) - -# These are the main rules - -# these locations seem to work for SuSE and Fedora -# prefix = $(HOME) -prefix = $(DESTDIR)/usr -BINDIR = $(prefix)/bin -DATADIR = $(prefix)/share -DESKTOPDIR = $(DATADIR)/applications -ICONPATH = $(DATADIR)/icons/hicolor -ICONDIR = $(ICONPATH)/scalable/apps -MANDIR = $(DATADIR)/man/man1 -XSLTDIR = $(DATADIR)/subsurface/xslt -gtk_update_icon_cache = gtk-update-icon-cache -f -t $(ICONPATH) - -NAME = subsurface -ICONFILE = $(NAME)-icon.svg -DESKTOPFILE = $(NAME).desktop -MANFILES = $(NAME).1 -XSLTFILES = xslt/*.xslt - -EXTRA_FLAGS = $(QTCXXFLAGS) $(GTKCFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) \ - $(LIBDIVECOMPUTERCFLAGS) \ - $(LIBSOUPCFLAGS) $(GCONF2CFLAGS) - -QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o qt-ui/divelistview.o \ - qt-ui/addcylinderdialog.o qt-ui/models.o qt-ui/starwidget.o - -GTKOBJS = info-gtk.o divelist-gtk.o planner-gtk.o statistics-gtk.o - -OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o divelist-gtk.o deco.o \ - planner.o planner-gtk.o \ - parse-xml.o save-xml.o libdivecomputer.o print.o uemis.o uemis-downloader.o \ - qt-gui.o statistics.o file.o cochran.o device.o download-dialog.o prefs.o \ - webservice.o sha1.o $(RESFILE) $(QTOBJS) $(GTKOBJS) - -ifneq ($(SQLITE3FLAGS),) - EXTRA_FLAGS += -DSQLITE3 $(SQLITE3FLAGS) -endif -ifneq ($(ZIPFLAGS),) - EXTRA_FLAGS += -DLIBZIP $(ZIPFLAGS) -endif -ifneq ($(strip $(LIBXSLT)),) - EXTRA_FLAGS += -DXSLT='"$(XSLTDIR)"' $(XSLCFLAGS) -endif -ifneq ($(strip $(LIBOSMGPSMAP)),) - OBJS += gps.o - EXTRA_FLAGS += -DHAVE_OSM_GPS_MAP $(OSMGPSMAPFLAGS) -endif - -ifneq (,$(filter $(UNAME),linux kfreebsd gnu)) - OBJS += linux.o -else ifeq ($(UNAME), darwin) - OBJS += macos.o - MACOSXINSTALL = /Applications/Subsurface.app - MACOSXFILES = packaging/macosx - MACOSXSTAGING = $(MACOSXFILES)/Subsurface.app - INFOPLIST = $(MACOSXFILES)/Info.plist - INFOPLISTINPUT = $(INFOPLIST).in - LDFLAGS += -headerpad_max_install_names -sectcreate __TEXT __info_plist $(INFOPLIST) -else - OBSJ += windows.o - WINDOWSSTAGING = ./packaging/windows - WINMSGDIRS=$(addprefix share/locale/,$(shell ls po/*.po | sed -e 's/po\/\(..\)_.*/\1\/LC_MESSAGES/')) - NSIINPUTFILE = $(WINDOWSSTAGING)/subsurface.nsi.in - NSIFILE = $(WINDOWSSTAGING)/subsurface.nsi - MAKENSIS = makensis - XSLTDIR = .\\xslt -endif - -LIBS = $(LIBQT) $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGTK) $(LIBGCONF2) $(LIBDIVECOMPUTER) \ - $(EXTRALIBS) $(LIBZIP) -lpthread -lm $(LIBOSMGPSMAP) $(LIBSOUP) $(LIBWINSOCK) - -MSGLANGS=$(notdir $(wildcard po/*.po)) - -# Add files to the following variables if the auto-detection based on the -# filename fails -OBJS_NEEDING_MOC = -OBJS_NEEDING_UIC = -HEADERS_NEEDING_MOC = - -# These are the generic rules for building - -# Rules for building and creating the version file - -VERSION_FILE = version.h -# There's only one line in $(VERSION_FILE); use the shell builtin `read' -STORED_VERSION_STRING = \ - $(subst ",,$(shell [ ! -r $(VERSION_FILE) ] || \ - read ignore ignore v <$(VERSION_FILE) && echo $$v)) -#" workaround editor syntax highlighting quirk - -GET_VERSION = ./scripts/get-version -VERSION_STRING := $(shell $(GET_VERSION) linux || echo "v$(VERSION)") -# Mac Info.plist style with three numbers 1.2.3 -CFBUNDLEVERSION_STRING := $(shell $(GET_VERSION) darwin $(VERSION_STRING) || \ - echo "$(VERSION).0") -# Windows .nsi style with four numbers 1.2.3.4 -PRODVERSION_STRING := $(shell $(GET_VERSION) win $(VERSION_STRING) || \ - echo "$(VERSION).0.0") - -MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.mo)) - -# Add the objects for the header files which define QObject subclasses -HEADERS_NEEDING_MOC += $(shell grep -l -s 'Q_OBJECT' $(OBJS:.o=.h)) -MOC_OBJS = $(HEADERS_NEEDING_MOC:.h=.moc.o) - -ALL_OBJS = $(OBJS) $(MOC_OBJS) - -DEPS = $(wildcard .dep/*.dep) - -all: $(NAME) - -$(NAME): gen_version_file $(ALL_OBJS) $(MSGOBJS) $(INFOPLIST) - $(CXX) $(LDFLAGS) -o $(NAME) $(ALL_OBJS) $(LIBS) - -gen_version_file: -ifneq ($(STORED_VERSION_STRING),$(VERSION_STRING)) - $(info updating $(VERSION_FILE) to $(VERSION_STRING)) - @echo \#define VERSION_STRING \"$(VERSION_STRING)\" >$(VERSION_FILE) -endif - -install: all - $(INSTALL) -d -m 755 $(BINDIR) - $(INSTALL) $(NAME) $(BINDIR) - $(INSTALL) -d -m 755 $(DESKTOPDIR) - $(INSTALL) $(DESKTOPFILE) $(DESKTOPDIR) - $(INSTALL) -d -m 755 $(ICONDIR) - $(INSTALL) -m 644 $(ICONFILE) $(ICONDIR) - @-if test -z "$(DESTDIR)"; then \ - $(gtk_update_icon_cache); \ - fi - $(INSTALL) -d -m 755 $(MANDIR) - $(INSTALL) -m 644 $(MANFILES) $(MANDIR) - @-if test ! -z "$(XSLT)"; then \ - $(INSTALL) -d -m 755 $(DATADIR)/subsurface; \ - $(INSTALL) -d -m 755 $(XSLTDIR); \ - $(INSTALL) -m 644 $(XSLTFILES) $(XSLTDIR); \ - fi - for LOC in $(wildcard share/locale/*/LC_MESSAGES); do \ - $(INSTALL) -d $(prefix)/$$LOC; \ - $(INSTALL) -m 644 $$LOC/subsurface.mo $(prefix)/$$LOC/subsurface.mo; \ - done - - -install-macosx: all - $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/Resources - $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/MacOS - $(INSTALL) $(NAME) $(MACOSXINSTALL)/Contents/MacOS/$(NAME)-bin - $(INSTALL) $(MACOSXFILES)/$(NAME).sh $(MACOSXINSTALL)/Contents/MacOS/$(NAME) - $(INSTALL) $(MACOSXFILES)/PkgInfo $(MACOSXINSTALL)/Contents/ - $(INSTALL) $(MACOSXFILES)/Info.plist $(MACOSXINSTALL)/Contents/ - $(INSTALL) $(ICONFILE) $(MACOSXINSTALL)/Contents/Resources/ - $(INSTALL) $(MACOSXFILES)/Subsurface.icns $(MACOSXINSTALL)/Contents/Resources/ - for LOC in $(wildcard share/locale/*/LC_MESSAGES); do \ - $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/Resources/$$LOC; \ - $(INSTALL) $$LOC/subsurface.mo $(MACOSXINSTALL)/Contents/Resources/$$LOC/subsurface.mo; \ - done - @-if test ! -z "$(XSLT)"; then \ - $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/Resources/xslt; \ - $(INSTALL) -m 644 $(XSLTFILES) $(MACOSXINSTALL)/Contents/Resources/xslt/; \ - fi - - -create-macosx-bundle: all - $(INSTALL) -d -m 755 $(MACOSXSTAGING)/Contents/Resources - $(INSTALL) -d -m 755 $(MACOSXSTAGING)/Contents/MacOS - $(INSTALL) $(NAME) $(MACOSXSTAGING)/Contents/MacOS/ - $(INSTALL) $(MACOSXFILES)/PkgInfo $(MACOSXSTAGING)/Contents/ - $(INSTALL) $(MACOSXFILES)/Info.plist $(MACOSXSTAGING)/Contents/ - $(INSTALL) $(ICONFILE) $(MACOSXSTAGING)/Contents/Resources/ - $(INSTALL) $(MACOSXFILES)/Subsurface.icns $(MACOSXSTAGING)/Contents/Resources/ - for LOC in $(wildcard share/locale/*/LC_MESSAGES); do \ - $(INSTALL) -d -m 755 $(MACOSXSTAGING)/Contents/Resources/$$LOC; \ - $(INSTALL) $$LOC/subsurface.mo $(MACOSXSTAGING)/Contents/Resources/$$LOC/subsurface.mo; \ - done - @-if test ! -z "$(XSLT)"; then \ - $(INSTALL) -d -m 755 $(MACOSXSTAGING)/Contents/Resources/xslt; \ - $(INSTALL) -m 644 $(XSLTFILES) $(MACOSXSTAGING)/Contents/Resources/xslt/; \ - fi - $(GTK_MAC_BUNDLER) packaging/macosx/subsurface.bundle - -sign-macosx-bundle: all - codesign -s "3A8CE62A483083EDEA5581A61E770EC1FA8BECE8" /Applications/Subsurface.app/Contents/MacOS/subsurface-bin - -install-cross-windows: all - $(INSTALL) -d -m 755 $(WINDOWSSTAGING)/share/locale - for MSG in $(WINMSGDIRS); do\ - $(INSTALL) -d -m 755 $(WINDOWSSTAGING)/$$MSG;\ - $(INSTALL) $(CROSS_PATH)/$$MSG/* $(WINDOWSSTAGING)/$$MSG;\ - done - for LOC in $(wildcard share/locale/*/LC_MESSAGES); do \ - $(INSTALL) -d -m 755 $(WINDOWSSTAGING)/$$LOC; \ - $(INSTALL) $$LOC/subsurface.mo $(WINDOWSSTAGING)/$$LOC/subsurface.mo; \ - done - -create-windows-installer: all $(NSIFILE) install-cross-windows - $(MAKENSIS) $(NSIFILE) - -$(NSIFILE): $(NSIINPUTFILE) - $(shell cat $(NSIINPUTFILE) | sed -e 's/VERSIONTOKEN/$(VERSION_STRING)/;s/PRODVTOKEN/$(PRODVERSION_STRING)/' > $(NSIFILE)) - -$(INFOPLIST): $(INFOPLISTINPUT) - $(shell cat $(INFOPLISTINPUT) | sed -e 's/CFBUNDLEVERSION_TOKEN/$(CFBUNDLEVERSION_STRING)/' > $(INFOPLIST)) - -# Transifex merge the translations -update-po-files: - xgettext -o po/subsurface-new.pot -s -k_ -kN_ -ktr --keyword=C_:1c,2 --add-comments="++GETTEXT" *.c qt-ui/*.cpp - tx push -s - tx pull -af - -MOCFLAGS = $(filter -I%, $(CXXFLAGS) $(EXTRA_FLAGS)) $(filter -D%, $(CXXFLAGS) $(EXTRA_FLAGS)) - -%.o: %.c - @echo ' CC' $< - @mkdir -p .dep .dep/qt-ui - @$(CC) $(CFLAGS) $(EXTRA_FLAGS) -MD -MF .dep/$@.dep -c -o $@ $< - -%.o: %.cpp - @echo ' CXX' $< - @mkdir -p .dep .dep/qt-ui - @$(CXX) $(CXXFLAGS) $(EXTRA_FLAGS) -MD -MF .dep/$@.dep -c -o $@ $< - -# Detect which files require the moc or uic tools to be run -CPP_NEEDING_MOC = $(shell grep -l -s '^\#include \".*\.moc\"' $(OBJS:.o=.cpp)) -OBJS_NEEDING_MOC += $(CPP_NEEDING_MOC:.cpp=.o) - -CPP_NEEDING_UIC = $(shell grep -l -s '^\#include \"ui_.*\.h\"' $(OBJS:.o=.cpp)) -OBJS_NEEDING_UIC += $(CPP_NEEDING_UIC:.cpp=.o) - -# This rule is for running the moc on QObject subclasses defined in the .h -# files. -%.moc.cpp: %.h - @echo ' MOC' $< - @$(MOC) $(MOCFLAGS) $< -o $@ - -# This rule is for running the moc on QObject subclasses defined in the .cpp -# files; remember to #include ".moc" at the end of the .cpp file, or -# you'll get linker errors ("undefined vtable for...") -%.moc: %.cpp - @echo ' MOC' $< - @$(MOC) -i $(MOCFLAGS) $< -o $@ - -# This creates the ui headers. -ui_%.h: %.ui - @echo ' UIC' $< - @$(UIC) $< -o $@ - -$(OBJS_NEEDING_MOC): %.o: %.moc -$(OBJS_NEEDING_UIC): qt-ui/%.o: qt-ui/ui_%.h - -share/locale/%.UTF-8/LC_MESSAGES/subsurface.mo: po/%.po po/%.aliases - mkdir -p $(dir $@) - msgfmt -c -o $@ po/$*.po - @-if test -s po/$*.aliases; then \ - for ALIAS in `cat po/$*.aliases`; do \ - mkdir -p share/locale/$$ALIAS/LC_MESSAGES; \ - cp $@ share/locale/$$ALIAS/LC_MESSAGES; \ - done; \ - fi - -satellite.png: satellite.svg - convert -transparent white -resize 11x16 -depth 8 $< $@ - -# This should work, but it doesn't get the colors quite right - so I manually converted with Gimp -# convert -colorspace RGB -transparent white -resize 256x256 subsurface-icon.svg subsurface-icon.png -# -# The following creates the pixbuf data in .h files with the basename followed by '_pixmap' -# as name of the data structure -%.h: %.png - @echo ' gdk-pixbuf-csource' $< - @gdk-pixbuf-csource --struct --name `echo $* | sed 's/-/_/g'`_pixbuf $< > $@ - -doc: - $(MAKE) -C Documentation doc - -clean: - rm -f $(ALL_OBJS) *~ $(NAME) $(NAME).exe po/*~ po/subsurface-new.pot \ - $(VERSION_FILE) qt-ui/*.moc qt-ui/ui_*.h - rm -rf share .dep - --include $(DEPS) diff --git a/Makefile b/Makefile index e882a353d..3308c9f41 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,4 @@ +include Configure.mk VERSION=3.0.2 CC=gcc @@ -6,119 +7,6 @@ CXX=g++ CXXFLAGS=-Wall -g $(CLCFLAGS) -DQT_NO_KEYWORDS INSTALL=install -# This are the detection rules -PKGCONFIG=pkg-config -XML2CONFIG=xml2-config -XSLCONFIG=xslt-config -QMAKE=qmake -MOC=moc -UIC=uic - -UNAME := $(shell $(CC) -dumpmachine 2>&1 | grep -E -o "linux|darwin|win|gnu|kfreebsd") - -# find libdivecomputer -# First deal with the cross compile environment and with Mac. -# For the native case, Linus doesn't want to trust pkg-config given -# how young libdivecomputer still is - so we check the typical -# subdirectories of /usr/local and /usr and then we give up. You can -# override by simply setting it here -# -ifeq ($(CC), i686-w64-mingw32-gcc) -# ok, we are cross building for Windows - LIBDIVECOMPUTERINCLUDES = $(shell $(PKGCONFIG) --cflags libdivecomputer) - LIBDIVECOMPUTERARCHIVE = $(shell $(PKGCONFIG) --libs libdivecomputer) - RESFILE = packaging/windows/subsurface.res - LDFLAGS += -Wl,-subsystem,windows - LIBWINSOCK = -lwsock32 -else ifeq ($(UNAME), darwin) - LIBDIVECOMPUTERINCLUDES = $(shell $(PKGCONFIG) --cflags libdivecomputer) - LIBDIVECOMPUTERARCHIVE = $(shell $(PKGCONFIG) --libs libdivecomputer) -else -libdc-local := $(wildcard /usr/local/lib/libdivecomputer.a) -libdc-local64 := $(wildcard /usr/local/lib64/libdivecomputer.a) -libdc-usr := $(wildcard /usr/lib/libdivecomputer.a) -libdc-usr64 := $(wildcard /usr/lib64/libdivecomputer.a) - -ifneq ($(LIBDCDEVEL),) - LIBDIVECOMPUTERDIR = ../libdivecomputer - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include - LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/src/.libs/libdivecomputer.a -else ifneq ($(strip $(libdc-local)),) - LIBDIVECOMPUTERDIR = /usr/local - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include - LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib/libdivecomputer.a -else ifneq ($(strip $(libdc-local64)),) - LIBDIVECOMPUTERDIR = /usr/local - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include - LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib64/libdivecomputer.a -else ifneq ($(strip $(libdc-usr)),) - LIBDIVECOMPUTERDIR = /usr - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include - LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib/libdivecomputer.a -else ifneq ($(strip $(libdc-usr64)),) - LIBDIVECOMPUTERDIR = /usr - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include - LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib64/libdivecomputer.a -else -$(error Cannot find libdivecomputer - please edit Makefile) -endif -endif - -# Libusb-1.0 is only required if libdivecomputer was built with it. -# And libdivecomputer is only built with it if libusb-1.0 is -# installed. So get libusb if it exists, but don't complain -# about it if it doesn't. -LIBUSB = $(shell $(PKGCONFIG) --libs libusb-1.0 2> /dev/null) - -# Use qmake to find out which Qt version we are building for. -QT_VERSION_MAJOR = $(shell $(QMAKE) -query QT_VERSION | cut -d. -f1) -ifeq ($(QT_VERSION_MAJOR), 5) - QT_MODULES = Qt5Widgets Qt5Svg - QT_CORE = Qt5Core -else - QT_MODULES = QtGui QtSvg - QT_CORE = QtCore -endif - -# we need GLIB2CFLAGS for gettext -QTCXXFLAGS = $(shell $(PKGCONFIG) --cflags $(QT_MODULES)) $(GLIB2CFLAGS) -LIBQT = $(shell $(PKGCONFIG) --libs $(QT_MODULES)) -ifneq ($(filter reduce_relocations, $(shell $(PKGCONFIG) --variable qt_config $(QT_CORE))), ) - QTCXXFLAGS += -fPIE -endif - -LIBGTK = $(shell $(PKGCONFIG) --libs gtk+-2.0 glib-2.0) -ifneq (,$(filter $(UNAME),linux kfreebsd gnu)) - LIBGCONF2 = $(shell $(PKGCONFIG) --libs gconf-2.0) - GCONF2CFLAGS = $(shell $(PKGCONFIG) --cflags gconf-2.0) -else ifeq ($(UNAME), darwin) - LIBGTK += $(shell $(PKGCONFIG) --libs gtk-mac-integration) -framework CoreFoundation -framework CoreServices - GTKCFLAGS += $(shell $(PKGCONFIG) --cflags gtk-mac-integration) - GTK_MAC_BUNDLER = ~/.local/bin/gtk-mac-bundler -endif - -LIBDIVECOMPUTERCFLAGS = $(LIBDIVECOMPUTERINCLUDES) -LIBDIVECOMPUTER = $(LIBDIVECOMPUTERARCHIVE) $(LIBUSB) - -LIBXML2 = $(shell $(XML2CONFIG) --libs) -LIBXSLT = $(shell $(XSLCONFIG) --libs) -XML2CFLAGS = $(shell $(XML2CONFIG) --cflags) -GLIB2CFLAGS = $(shell $(PKGCONFIG) --cflags glib-2.0) -GTKCFLAGS = $(shell $(PKGCONFIG) --cflags gtk+-2.0) -XSLCFLAGS = $(shell $(XSLCONFIG) --cflags) -OSMGPSMAPFLAGS += $(shell $(PKGCONFIG) --cflags osmgpsmap 2> /dev/null) -LIBOSMGPSMAP += $(shell $(PKGCONFIG) --libs osmgpsmap 2> /dev/null) -LIBSOUPCFLAGS = $(shell $(PKGCONFIG) --cflags libsoup-2.4) -LIBSOUP = $(shell $(PKGCONFIG) --libs libsoup-2.4) - -LIBZIP = $(shell $(PKGCONFIG) --libs libzip 2> /dev/null) -ZIPFLAGS = $(strip $(shell $(PKGCONFIG) --cflags libzip 2> /dev/null)) - -LIBSQLITE3 = $(shell $(PKGCONFIG) --libs sqlite3 2> /dev/null) -SQLITE3FLAGS = $(strip $(shell $(PKGCONFIG) --cflags sqlite3)) - -# These are the main rules - # these locations seem to work for SuSE and Fedora # prefix = $(HOME) prefix = $(DESTDIR)/usr @@ -197,204 +85,4 @@ OBJS_NEEDING_MOC = OBJS_NEEDING_UIC = HEADERS_NEEDING_MOC = -# These are the generic rules for building - -# Rules for building and creating the version file - -VERSION_FILE = version.h -# There's only one line in $(VERSION_FILE); use the shell builtin `read' -STORED_VERSION_STRING = \ - $(subst ",,$(shell [ ! -r $(VERSION_FILE) ] || \ - read ignore ignore v <$(VERSION_FILE) && echo $$v)) -#" workaround editor syntax highlighting quirk - -GET_VERSION = ./scripts/get-version -VERSION_STRING := $(shell $(GET_VERSION) linux || echo "v$(VERSION)") -# Mac Info.plist style with three numbers 1.2.3 -CFBUNDLEVERSION_STRING := $(shell $(GET_VERSION) darwin $(VERSION_STRING) || \ - echo "$(VERSION).0") -# Windows .nsi style with four numbers 1.2.3.4 -PRODVERSION_STRING := $(shell $(GET_VERSION) win $(VERSION_STRING) || \ - echo "$(VERSION).0.0") - -MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.mo)) - -# Add the objects for the header files which define QObject subclasses -HEADERS_NEEDING_MOC += $(shell grep -l -s 'Q_OBJECT' $(OBJS:.o=.h)) -MOC_OBJS = $(HEADERS_NEEDING_MOC:.h=.moc.o) - -ALL_OBJS = $(OBJS) $(MOC_OBJS) - -DEPS = $(wildcard .dep/*.dep) - -all: $(NAME) - -$(NAME): gen_version_file $(ALL_OBJS) $(MSGOBJS) $(INFOPLIST) - $(CXX) $(LDFLAGS) -o $(NAME) $(ALL_OBJS) $(LIBS) - -gen_version_file: -ifneq ($(STORED_VERSION_STRING),$(VERSION_STRING)) - $(info updating $(VERSION_FILE) to $(VERSION_STRING)) - @echo \#define VERSION_STRING \"$(VERSION_STRING)\" >$(VERSION_FILE) -endif - -install: all - $(INSTALL) -d -m 755 $(BINDIR) - $(INSTALL) $(NAME) $(BINDIR) - $(INSTALL) -d -m 755 $(DESKTOPDIR) - $(INSTALL) $(DESKTOPFILE) $(DESKTOPDIR) - $(INSTALL) -d -m 755 $(ICONDIR) - $(INSTALL) -m 644 $(ICONFILE) $(ICONDIR) - @-if test -z "$(DESTDIR)"; then \ - $(gtk_update_icon_cache); \ - fi - $(INSTALL) -d -m 755 $(MANDIR) - $(INSTALL) -m 644 $(MANFILES) $(MANDIR) - @-if test ! -z "$(XSLT)"; then \ - $(INSTALL) -d -m 755 $(DATADIR)/subsurface; \ - $(INSTALL) -d -m 755 $(XSLTDIR); \ - $(INSTALL) -m 644 $(XSLTFILES) $(XSLTDIR); \ - fi - for LOC in $(wildcard share/locale/*/LC_MESSAGES); do \ - $(INSTALL) -d $(prefix)/$$LOC; \ - $(INSTALL) -m 644 $$LOC/subsurface.mo $(prefix)/$$LOC/subsurface.mo; \ - done - - -install-macosx: all - $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/Resources - $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/MacOS - $(INSTALL) $(NAME) $(MACOSXINSTALL)/Contents/MacOS/$(NAME)-bin - $(INSTALL) $(MACOSXFILES)/$(NAME).sh $(MACOSXINSTALL)/Contents/MacOS/$(NAME) - $(INSTALL) $(MACOSXFILES)/PkgInfo $(MACOSXINSTALL)/Contents/ - $(INSTALL) $(MACOSXFILES)/Info.plist $(MACOSXINSTALL)/Contents/ - $(INSTALL) $(ICONFILE) $(MACOSXINSTALL)/Contents/Resources/ - $(INSTALL) $(MACOSXFILES)/Subsurface.icns $(MACOSXINSTALL)/Contents/Resources/ - for LOC in $(wildcard share/locale/*/LC_MESSAGES); do \ - $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/Resources/$$LOC; \ - $(INSTALL) $$LOC/subsurface.mo $(MACOSXINSTALL)/Contents/Resources/$$LOC/subsurface.mo; \ - done - @-if test ! -z "$(XSLT)"; then \ - $(INSTALL) -d -m 755 $(MACOSXINSTALL)/Contents/Resources/xslt; \ - $(INSTALL) -m 644 $(XSLTFILES) $(MACOSXINSTALL)/Contents/Resources/xslt/; \ - fi - - -create-macosx-bundle: all - $(INSTALL) -d -m 755 $(MACOSXSTAGING)/Contents/Resources - $(INSTALL) -d -m 755 $(MACOSXSTAGING)/Contents/MacOS - $(INSTALL) $(NAME) $(MACOSXSTAGING)/Contents/MacOS/ - $(INSTALL) $(MACOSXFILES)/PkgInfo $(MACOSXSTAGING)/Contents/ - $(INSTALL) $(MACOSXFILES)/Info.plist $(MACOSXSTAGING)/Contents/ - $(INSTALL) $(ICONFILE) $(MACOSXSTAGING)/Contents/Resources/ - $(INSTALL) $(MACOSXFILES)/Subsurface.icns $(MACOSXSTAGING)/Contents/Resources/ - for LOC in $(wildcard share/locale/*/LC_MESSAGES); do \ - $(INSTALL) -d -m 755 $(MACOSXSTAGING)/Contents/Resources/$$LOC; \ - $(INSTALL) $$LOC/subsurface.mo $(MACOSXSTAGING)/Contents/Resources/$$LOC/subsurface.mo; \ - done - @-if test ! -z "$(XSLT)"; then \ - $(INSTALL) -d -m 755 $(MACOSXSTAGING)/Contents/Resources/xslt; \ - $(INSTALL) -m 644 $(XSLTFILES) $(MACOSXSTAGING)/Contents/Resources/xslt/; \ - fi - $(GTK_MAC_BUNDLER) packaging/macosx/subsurface.bundle - -sign-macosx-bundle: all - codesign -s "3A8CE62A483083EDEA5581A61E770EC1FA8BECE8" /Applications/Subsurface.app/Contents/MacOS/subsurface-bin - -install-cross-windows: all - $(INSTALL) -d -m 755 $(WINDOWSSTAGING)/share/locale - for MSG in $(WINMSGDIRS); do\ - $(INSTALL) -d -m 755 $(WINDOWSSTAGING)/$$MSG;\ - $(INSTALL) $(CROSS_PATH)/$$MSG/* $(WINDOWSSTAGING)/$$MSG;\ - done - for LOC in $(wildcard share/locale/*/LC_MESSAGES); do \ - $(INSTALL) -d -m 755 $(WINDOWSSTAGING)/$$LOC; \ - $(INSTALL) $$LOC/subsurface.mo $(WINDOWSSTAGING)/$$LOC/subsurface.mo; \ - done - -create-windows-installer: all $(NSIFILE) install-cross-windows - $(MAKENSIS) $(NSIFILE) - -$(NSIFILE): $(NSIINPUTFILE) - $(shell cat $(NSIINPUTFILE) | sed -e 's/VERSIONTOKEN/$(VERSION_STRING)/;s/PRODVTOKEN/$(PRODVERSION_STRING)/' > $(NSIFILE)) - -$(INFOPLIST): $(INFOPLISTINPUT) - $(shell cat $(INFOPLISTINPUT) | sed -e 's/CFBUNDLEVERSION_TOKEN/$(CFBUNDLEVERSION_STRING)/' > $(INFOPLIST)) - -# Transifex merge the translations -update-po-files: - xgettext -o po/subsurface-new.pot -s -k_ -kN_ -ktr --keyword=C_:1c,2 --add-comments="++GETTEXT" *.c qt-ui/*.cpp - tx push -s - tx pull -af - -MOCFLAGS = $(filter -I%, $(CXXFLAGS) $(EXTRA_FLAGS)) $(filter -D%, $(CXXFLAGS) $(EXTRA_FLAGS)) - -%.o: %.c - @echo ' CC' $< - @mkdir -p .dep .dep/qt-ui - @$(CC) $(CFLAGS) $(EXTRA_FLAGS) -MD -MF .dep/$@.dep -c -o $@ $< - -%.o: %.cpp - @echo ' CXX' $< - @mkdir -p .dep .dep/qt-ui - @$(CXX) $(CXXFLAGS) $(EXTRA_FLAGS) -MD -MF .dep/$@.dep -c -o $@ $< - -# Detect which files require the moc or uic tools to be run -CPP_NEEDING_MOC = $(shell grep -l -s '^\#include \".*\.moc\"' $(OBJS:.o=.cpp)) -OBJS_NEEDING_MOC += $(CPP_NEEDING_MOC:.cpp=.o) - -CPP_NEEDING_UIC = $(shell grep -l -s '^\#include \"ui_.*\.h\"' $(OBJS:.o=.cpp)) -OBJS_NEEDING_UIC += $(CPP_NEEDING_UIC:.cpp=.o) - -# This rule is for running the moc on QObject subclasses defined in the .h -# files. -%.moc.cpp: %.h - @echo ' MOC' $< - @$(MOC) $(MOCFLAGS) $< -o $@ - -# This rule is for running the moc on QObject subclasses defined in the .cpp -# files; remember to #include ".moc" at the end of the .cpp file, or -# you'll get linker errors ("undefined vtable for...") -%.moc: %.cpp - @echo ' MOC' $< - @$(MOC) -i $(MOCFLAGS) $< -o $@ - -# This creates the ui headers. -ui_%.h: %.ui - @echo ' UIC' $< - @$(UIC) $< -o $@ - -$(OBJS_NEEDING_MOC): %.o: %.moc -$(OBJS_NEEDING_UIC): qt-ui/%.o: qt-ui/ui_%.h - -share/locale/%.UTF-8/LC_MESSAGES/subsurface.mo: po/%.po po/%.aliases - mkdir -p $(dir $@) - msgfmt -c -o $@ po/$*.po - @-if test -s po/$*.aliases; then \ - for ALIAS in `cat po/$*.aliases`; do \ - mkdir -p share/locale/$$ALIAS/LC_MESSAGES; \ - cp $@ share/locale/$$ALIAS/LC_MESSAGES; \ - done; \ - fi - -satellite.png: satellite.svg - convert -transparent white -resize 11x16 -depth 8 $< $@ - -# This should work, but it doesn't get the colors quite right - so I manually converted with Gimp -# convert -colorspace RGB -transparent white -resize 256x256 subsurface-icon.svg subsurface-icon.png -# -# The following creates the pixbuf data in .h files with the basename followed by '_pixmap' -# as name of the data structure -%.h: %.png - @echo ' gdk-pixbuf-csource' $< - @gdk-pixbuf-csource --struct --name `echo $* | sed 's/-/_/g'`_pixbuf $< > $@ - -doc: - $(MAKE) -C Documentation doc - -clean: - rm -f $(ALL_OBJS) *~ $(NAME) $(NAME).exe po/*~ po/subsurface-new.pot \ - $(VERSION_FILE) qt-ui/*.moc qt-ui/ui_*.h - rm -rf share .dep - --include $(DEPS) +include Rules.mk diff --git a/Rules.mk b/Rules.mk index e882a353d..59e73e13d 100644 --- a/Rules.mk +++ b/Rules.mk @@ -1,204 +1,4 @@ -VERSION=3.0.2 - -CC=gcc -CFLAGS=-Wall -Wno-pointer-sign -g $(CLCFLAGS) -DGSEAL_ENABLE -CXX=g++ -CXXFLAGS=-Wall -g $(CLCFLAGS) -DQT_NO_KEYWORDS -INSTALL=install - -# This are the detection rules -PKGCONFIG=pkg-config -XML2CONFIG=xml2-config -XSLCONFIG=xslt-config -QMAKE=qmake -MOC=moc -UIC=uic - -UNAME := $(shell $(CC) -dumpmachine 2>&1 | grep -E -o "linux|darwin|win|gnu|kfreebsd") - -# find libdivecomputer -# First deal with the cross compile environment and with Mac. -# For the native case, Linus doesn't want to trust pkg-config given -# how young libdivecomputer still is - so we check the typical -# subdirectories of /usr/local and /usr and then we give up. You can -# override by simply setting it here -# -ifeq ($(CC), i686-w64-mingw32-gcc) -# ok, we are cross building for Windows - LIBDIVECOMPUTERINCLUDES = $(shell $(PKGCONFIG) --cflags libdivecomputer) - LIBDIVECOMPUTERARCHIVE = $(shell $(PKGCONFIG) --libs libdivecomputer) - RESFILE = packaging/windows/subsurface.res - LDFLAGS += -Wl,-subsystem,windows - LIBWINSOCK = -lwsock32 -else ifeq ($(UNAME), darwin) - LIBDIVECOMPUTERINCLUDES = $(shell $(PKGCONFIG) --cflags libdivecomputer) - LIBDIVECOMPUTERARCHIVE = $(shell $(PKGCONFIG) --libs libdivecomputer) -else -libdc-local := $(wildcard /usr/local/lib/libdivecomputer.a) -libdc-local64 := $(wildcard /usr/local/lib64/libdivecomputer.a) -libdc-usr := $(wildcard /usr/lib/libdivecomputer.a) -libdc-usr64 := $(wildcard /usr/lib64/libdivecomputer.a) - -ifneq ($(LIBDCDEVEL),) - LIBDIVECOMPUTERDIR = ../libdivecomputer - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include - LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/src/.libs/libdivecomputer.a -else ifneq ($(strip $(libdc-local)),) - LIBDIVECOMPUTERDIR = /usr/local - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include - LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib/libdivecomputer.a -else ifneq ($(strip $(libdc-local64)),) - LIBDIVECOMPUTERDIR = /usr/local - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include - LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib64/libdivecomputer.a -else ifneq ($(strip $(libdc-usr)),) - LIBDIVECOMPUTERDIR = /usr - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include - LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib/libdivecomputer.a -else ifneq ($(strip $(libdc-usr64)),) - LIBDIVECOMPUTERDIR = /usr - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include - LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib64/libdivecomputer.a -else -$(error Cannot find libdivecomputer - please edit Makefile) -endif -endif - -# Libusb-1.0 is only required if libdivecomputer was built with it. -# And libdivecomputer is only built with it if libusb-1.0 is -# installed. So get libusb if it exists, but don't complain -# about it if it doesn't. -LIBUSB = $(shell $(PKGCONFIG) --libs libusb-1.0 2> /dev/null) - -# Use qmake to find out which Qt version we are building for. -QT_VERSION_MAJOR = $(shell $(QMAKE) -query QT_VERSION | cut -d. -f1) -ifeq ($(QT_VERSION_MAJOR), 5) - QT_MODULES = Qt5Widgets Qt5Svg - QT_CORE = Qt5Core -else - QT_MODULES = QtGui QtSvg - QT_CORE = QtCore -endif - -# we need GLIB2CFLAGS for gettext -QTCXXFLAGS = $(shell $(PKGCONFIG) --cflags $(QT_MODULES)) $(GLIB2CFLAGS) -LIBQT = $(shell $(PKGCONFIG) --libs $(QT_MODULES)) -ifneq ($(filter reduce_relocations, $(shell $(PKGCONFIG) --variable qt_config $(QT_CORE))), ) - QTCXXFLAGS += -fPIE -endif - -LIBGTK = $(shell $(PKGCONFIG) --libs gtk+-2.0 glib-2.0) -ifneq (,$(filter $(UNAME),linux kfreebsd gnu)) - LIBGCONF2 = $(shell $(PKGCONFIG) --libs gconf-2.0) - GCONF2CFLAGS = $(shell $(PKGCONFIG) --cflags gconf-2.0) -else ifeq ($(UNAME), darwin) - LIBGTK += $(shell $(PKGCONFIG) --libs gtk-mac-integration) -framework CoreFoundation -framework CoreServices - GTKCFLAGS += $(shell $(PKGCONFIG) --cflags gtk-mac-integration) - GTK_MAC_BUNDLER = ~/.local/bin/gtk-mac-bundler -endif - -LIBDIVECOMPUTERCFLAGS = $(LIBDIVECOMPUTERINCLUDES) -LIBDIVECOMPUTER = $(LIBDIVECOMPUTERARCHIVE) $(LIBUSB) - -LIBXML2 = $(shell $(XML2CONFIG) --libs) -LIBXSLT = $(shell $(XSLCONFIG) --libs) -XML2CFLAGS = $(shell $(XML2CONFIG) --cflags) -GLIB2CFLAGS = $(shell $(PKGCONFIG) --cflags glib-2.0) -GTKCFLAGS = $(shell $(PKGCONFIG) --cflags gtk+-2.0) -XSLCFLAGS = $(shell $(XSLCONFIG) --cflags) -OSMGPSMAPFLAGS += $(shell $(PKGCONFIG) --cflags osmgpsmap 2> /dev/null) -LIBOSMGPSMAP += $(shell $(PKGCONFIG) --libs osmgpsmap 2> /dev/null) -LIBSOUPCFLAGS = $(shell $(PKGCONFIG) --cflags libsoup-2.4) -LIBSOUP = $(shell $(PKGCONFIG) --libs libsoup-2.4) - -LIBZIP = $(shell $(PKGCONFIG) --libs libzip 2> /dev/null) -ZIPFLAGS = $(strip $(shell $(PKGCONFIG) --cflags libzip 2> /dev/null)) - -LIBSQLITE3 = $(shell $(PKGCONFIG) --libs sqlite3 2> /dev/null) -SQLITE3FLAGS = $(strip $(shell $(PKGCONFIG) --cflags sqlite3)) - -# These are the main rules - -# these locations seem to work for SuSE and Fedora -# prefix = $(HOME) -prefix = $(DESTDIR)/usr -BINDIR = $(prefix)/bin -DATADIR = $(prefix)/share -DESKTOPDIR = $(DATADIR)/applications -ICONPATH = $(DATADIR)/icons/hicolor -ICONDIR = $(ICONPATH)/scalable/apps -MANDIR = $(DATADIR)/man/man1 -XSLTDIR = $(DATADIR)/subsurface/xslt -gtk_update_icon_cache = gtk-update-icon-cache -f -t $(ICONPATH) - -NAME = subsurface -ICONFILE = $(NAME)-icon.svg -DESKTOPFILE = $(NAME).desktop -MANFILES = $(NAME).1 -XSLTFILES = xslt/*.xslt - -EXTRA_FLAGS = $(QTCXXFLAGS) $(GTKCFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) \ - $(LIBDIVECOMPUTERCFLAGS) \ - $(LIBSOUPCFLAGS) $(GCONF2CFLAGS) - -QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o qt-ui/divelistview.o \ - qt-ui/addcylinderdialog.o qt-ui/models.o qt-ui/starwidget.o - -GTKOBJS = info-gtk.o divelist-gtk.o planner-gtk.o statistics-gtk.o - -OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o divelist-gtk.o deco.o \ - planner.o planner-gtk.o \ - parse-xml.o save-xml.o libdivecomputer.o print.o uemis.o uemis-downloader.o \ - qt-gui.o statistics.o file.o cochran.o device.o download-dialog.o prefs.o \ - webservice.o sha1.o $(RESFILE) $(QTOBJS) $(GTKOBJS) - -ifneq ($(SQLITE3FLAGS),) - EXTRA_FLAGS += -DSQLITE3 $(SQLITE3FLAGS) -endif -ifneq ($(ZIPFLAGS),) - EXTRA_FLAGS += -DLIBZIP $(ZIPFLAGS) -endif -ifneq ($(strip $(LIBXSLT)),) - EXTRA_FLAGS += -DXSLT='"$(XSLTDIR)"' $(XSLCFLAGS) -endif -ifneq ($(strip $(LIBOSMGPSMAP)),) - OBJS += gps.o - EXTRA_FLAGS += -DHAVE_OSM_GPS_MAP $(OSMGPSMAPFLAGS) -endif - -ifneq (,$(filter $(UNAME),linux kfreebsd gnu)) - OBJS += linux.o -else ifeq ($(UNAME), darwin) - OBJS += macos.o - MACOSXINSTALL = /Applications/Subsurface.app - MACOSXFILES = packaging/macosx - MACOSXSTAGING = $(MACOSXFILES)/Subsurface.app - INFOPLIST = $(MACOSXFILES)/Info.plist - INFOPLISTINPUT = $(INFOPLIST).in - LDFLAGS += -headerpad_max_install_names -sectcreate __TEXT __info_plist $(INFOPLIST) -else - OBSJ += windows.o - WINDOWSSTAGING = ./packaging/windows - WINMSGDIRS=$(addprefix share/locale/,$(shell ls po/*.po | sed -e 's/po\/\(..\)_.*/\1\/LC_MESSAGES/')) - NSIINPUTFILE = $(WINDOWSSTAGING)/subsurface.nsi.in - NSIFILE = $(WINDOWSSTAGING)/subsurface.nsi - MAKENSIS = makensis - XSLTDIR = .\\xslt -endif - -LIBS = $(LIBQT) $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGTK) $(LIBGCONF2) $(LIBDIVECOMPUTER) \ - $(EXTRALIBS) $(LIBZIP) -lpthread -lm $(LIBOSMGPSMAP) $(LIBSOUP) $(LIBWINSOCK) - -MSGLANGS=$(notdir $(wildcard po/*.po)) - -# Add files to the following variables if the auto-detection based on the -# filename fails -OBJS_NEEDING_MOC = -OBJS_NEEDING_UIC = -HEADERS_NEEDING_MOC = - -# These are the generic rules for building - +# -*- Makefile -*- # Rules for building and creating the version file VERSION_FILE = version.h -- cgit v1.2.3-70-g09d2 From c8b360c3b5fdeb9f878cb5db648a121a10e33772 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Sat, 13 Apr 2013 09:19:03 -0700 Subject: Add a HEADERS variable to the Makefile Similar to the qmake variable of the same name, this lists (at least) the headers that may need moc to be run on. Adding more headers is not a problem. Signed-off-by: Thiago Macieira --- Makefile | 10 ++++++++++ Rules.mk | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 3308c9f41..41e5f093b 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,16 @@ EXTRA_FLAGS = $(QTCXXFLAGS) $(GTKCFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) \ $(LIBDIVECOMPUTERCFLAGS) \ $(LIBSOUPCFLAGS) $(GCONF2CFLAGS) +HEADERS = \ + qt-ui/addcylinderdialog.h \ + qt-ui/divelistview.h \ + qt-ui/maintab.h \ + qt-ui/mainwindow.h \ + qt-ui/models.h \ + qt-ui/plotareascene.h \ + qt-ui/starwidget.h \ + + QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o qt-ui/divelistview.o \ qt-ui/addcylinderdialog.o qt-ui/models.o qt-ui/starwidget.o diff --git a/Rules.mk b/Rules.mk index 5374e060b..8ffb88b86 100644 --- a/Rules.mk +++ b/Rules.mk @@ -20,7 +20,7 @@ PRODVERSION_STRING := $(shell $(GET_VERSION) win $(VERSION_STRING) || \ MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.mo)) # Add the objects for the header files which define QObject subclasses -HEADERS_NEEDING_MOC += $(shell grep -l -s 'Q_OBJECT' $(OBJS:.o=.h)) +HEADERS_NEEDING_MOC += $(shell grep -l -s 'Q_OBJECT' $(HEADERS)) MOC_OBJS = $(HEADERS_NEEDING_MOC:.h=.moc.o) ALL_OBJS = $(OBJS) $(MOC_OBJS) -- cgit v1.2.3-70-g09d2 From d773c02bf36f3b09584123bfd0e84a56304b33fb Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Sat, 13 Apr 2013 09:27:55 -0700 Subject: Add a SOURCES variable to the Makefile, replacing OBJS Instead of listing objects, let's list sources. This matches also what qmake and most other buildsystems do. The notable exception is the kernel. The reason that listing the sources will be interesting is because I'm about to add rules to create the dependency files. Signed-off-by: Thiago Macieira --- Makefile | 55 +++++++++++++++++++++++++++++++++++++++++-------------- Rules.mk | 5 +++++ 2 files changed, 46 insertions(+), 14 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 41e5f093b..055d7b138 100644 --- a/Makefile +++ b/Makefile @@ -39,16 +39,43 @@ HEADERS = \ qt-ui/starwidget.h \ -QTOBJS = qt-ui/maintab.o qt-ui/mainwindow.o qt-ui/plotareascene.o qt-ui/divelistview.o \ - qt-ui/addcylinderdialog.o qt-ui/models.o qt-ui/starwidget.o - -GTKOBJS = info-gtk.o divelist-gtk.o planner-gtk.o statistics-gtk.o - -OBJS = main.o dive.o time.o profile.o info.o equipment.o divelist.o divelist-gtk.o deco.o \ - planner.o planner-gtk.o \ - parse-xml.o save-xml.o libdivecomputer.o print.o uemis.o uemis-downloader.o \ - qt-gui.o statistics.o file.o cochran.o device.o download-dialog.o prefs.o \ - webservice.o sha1.o $(RESFILE) $(QTOBJS) $(GTKOBJS) +SOURCES = \ + cochran.c \ + deco.c \ + device.c \ + dive.c \ + divelist.c \ + divelist-gtk.c \ + download-dialog.c \ + equipment.c \ + file.c \ + info.c \ + info-gtk.c \ + libdivecomputer.c \ + main.c \ + parse-xml.c \ + planner.c \ + planner-gtk.c \ + prefs.c \ + print.c \ + profile.c \ + save-xml.c \ + sha1.c \ + statistics.c \ + statistics-gtk.c \ + time.c \ + uemis.c \ + uemis-downloader.c \ + webservice.c \ + qt-gui.cpp \ + qt-ui/addcylinderdialog.cpp \ + qt-ui/divelistview.cpp \ + qt-ui/maintab.cpp \ + qt-ui/mainwindow.cpp \ + qt-ui/models.cpp \ + qt-ui/plotareascene.cpp \ + qt-ui/starwidget.cpp \ + $(RESFILE) ifneq ($(SQLITE3FLAGS),) EXTRA_FLAGS += -DSQLITE3 $(SQLITE3FLAGS) @@ -60,14 +87,14 @@ ifneq ($(strip $(LIBXSLT)),) EXTRA_FLAGS += -DXSLT='"$(XSLTDIR)"' $(XSLCFLAGS) endif ifneq ($(strip $(LIBOSMGPSMAP)),) - OBJS += gps.o + SOURCES += gps.c EXTRA_FLAGS += -DHAVE_OSM_GPS_MAP $(OSMGPSMAPFLAGS) endif ifneq (,$(filter $(UNAME),linux kfreebsd gnu)) - OBJS += linux.o + SOURCES += linux.c else ifeq ($(UNAME), darwin) - OBJS += macos.o + SOURCES += macos.c MACOSXINSTALL = /Applications/Subsurface.app MACOSXFILES = packaging/macosx MACOSXSTAGING = $(MACOSXFILES)/Subsurface.app @@ -75,7 +102,7 @@ else ifeq ($(UNAME), darwin) INFOPLISTINPUT = $(INFOPLIST).in LDFLAGS += -headerpad_max_install_names -sectcreate __TEXT __info_plist $(INFOPLIST) else - OBSJ += windows.o + SOURCES += windows.c WINDOWSSTAGING = ./packaging/windows WINMSGDIRS=$(addprefix share/locale/,$(shell ls po/*.po | sed -e 's/po\/\(..\)_.*/\1\/LC_MESSAGES/')) NSIINPUTFILE = $(WINDOWSSTAGING)/subsurface.nsi.in diff --git a/Rules.mk b/Rules.mk index 8ffb88b86..e7108fd4b 100644 --- a/Rules.mk +++ b/Rules.mk @@ -19,6 +19,11 @@ PRODVERSION_STRING := $(shell $(GET_VERSION) win $(VERSION_STRING) || \ MSGOBJS=$(addprefix share/locale/,$(MSGLANGS:.po=.UTF-8/LC_MESSAGES/subsurface.mo)) +C_SOURCES = $(filter %.c, $(SOURCES)) +CXX_SOURCES = $(filter %.cpp, $(SOURCES)) +OTHER_SOURCES = $(filter-out %.c %.cpp, $(SOURCES)) +OBJS = $(C_SOURCES:.c=.o) $(CXX_SOURCES:.cpp=.o) $(OTHER_SOURCES) + # Add the objects for the header files which define QObject subclasses HEADERS_NEEDING_MOC += $(shell grep -l -s 'Q_OBJECT' $(HEADERS)) MOC_OBJS = $(HEADERS_NEEDING_MOC:.h=.moc.o) -- cgit v1.2.3-70-g09d2 From c5d244eeea0583eaef3a30e01e7efb2e8a060af8 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Sat, 13 Apr 2013 23:24:47 -0700 Subject: Add support for Qt resources in Subsurface Signed-off-by: Thiago Macieira --- .gitignore | 1 + Makefile | 3 +++ Rules.mk | 9 ++++++++- subsurface.qrc | 1 + 4 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 subsurface.qrc (limited to 'Makefile') diff --git a/.gitignore b/.gitignore index b62d8982b..5a90c6d4e 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ Documentation/user-manual.text packaging/windows/subsurface.nsi packaging/macos/Info.plist config.cache +*.qrc.cpp diff --git a/Makefile b/Makefile index 055d7b138..3e0af9993 100644 --- a/Makefile +++ b/Makefile @@ -77,6 +77,9 @@ SOURCES = \ qt-ui/starwidget.cpp \ $(RESFILE) + +RESOURCES = subsurface.qrc + ifneq ($(SQLITE3FLAGS),) EXTRA_FLAGS += -DSQLITE3 $(SQLITE3FLAGS) endif diff --git a/Rules.mk b/Rules.mk index fd26d74d0..ee99773ba 100644 --- a/Rules.mk +++ b/Rules.mk @@ -28,7 +28,7 @@ else endif C_SOURCES = $(filter %.c, $(SOURCES)) -CXX_SOURCES = $(filter %.cpp, $(SOURCES)) +CXX_SOURCES = $(filter %.cpp, $(SOURCES)) $(RESOURCES:.qrc=.qrc.cpp) OTHER_SOURCES = $(filter-out %.c %.cpp, $(SOURCES)) OBJS = $(C_SOURCES:.c=.o) $(CXX_SOURCES:.cpp=.o) $(OTHER_SOURCES) @@ -164,6 +164,12 @@ MOCFLAGS = $(filter -I%, $(CXXFLAGS) $(EXTRA_FLAGS)) $(filter -D%, $(CXXFLAGS) $ @$(PRETTYECHO) ' MOC' $< $(COMPILE_PREFIX)$(MOC) -i $(MOCFLAGS) $< -o $@ +# This creates the Qt resource sources. +%.qrc.cpp: %.qrc + @$(PRETTYECHO) ' RCC' $< + $(COMPILE_PREFIX)$(RCC) $< -o $@ +%.qrc: + # This creates the ui headers. ui_%.h: %.ui @$(PRETTYECHO) ' UIC' $< @@ -205,6 +211,7 @@ doc: clean: rm -f $(ALL_OBJS) *~ $(NAME) $(NAME).exe po/*~ po/subsurface-new.pot \ $(VERSION_FILE) qt-ui/*.moc qt-ui/ui_*.h + rm -f $(RESOURCES:.qrc=.qrc.cpp) rm -rf share confclean: clean diff --git a/subsurface.qrc b/subsurface.qrc new file mode 100644 index 000000000..2496c8778 --- /dev/null +++ b/subsurface.qrc @@ -0,0 +1 @@ + \ No newline at end of file -- cgit v1.2.3-70-g09d2 From b073823e3acbeb4b15c95101e9708d60cc7b2703 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Sun, 14 Apr 2013 00:30:25 -0700 Subject: Clean up the moc intermediates too in make clean Signed-off-by: Thiago Macieira --- Makefile | 2 +- Rules.mk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 3e0af9993..6ebba4dd5 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ VERSION=3.0.2 CC=gcc CFLAGS=-Wall -Wno-pointer-sign -g $(CLCFLAGS) -DGSEAL_ENABLE CXX=g++ -CXXFLAGS=-Wall -g $(CLCFLAGS) -DQT_NO_KEYWORDS +CXXFLAGS=-Wall -g $(CLCXXFLAGS) -DQT_NO_KEYWORDS INSTALL=install # these locations seem to work for SuSE and Fedora diff --git a/Rules.mk b/Rules.mk index d3c8c79a8..d5bc31246 100644 --- a/Rules.mk +++ b/Rules.mk @@ -210,7 +210,7 @@ doc: clean: rm -f $(ALL_OBJS) *~ $(NAME) $(NAME).exe po/*~ po/subsurface-new.pot \ - $(VERSION_FILE) qt-ui/*.moc qt-ui/ui_*.h + $(VERSION_FILE) $(HEADERS_NEEDING_MOC:.h=.moc) *.moc qt-ui/*.moc qt-ui/ui_*.h rm -f $(RESOURCES:.qrc=.qrc.cpp) rm -rf share -- cgit v1.2.3-70-g09d2 From 2f4d6bbe535a195046b4746fd3a771087ee4a6c4 Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Sat, 27 Apr 2013 12:27:27 -0300 Subject: Added support for showing the Stars on the DiveTable For the stars on the dive table I had to rework a bit my StarRating widget, because it used a pixmap for each widget that were created. Not it uses only 2 pixmaps: the active and inactive ones. A new file was created named modeldelegates(h, cpp) that should hold all delegates of the models. For the GTK / C folks, a 'Delegate' ia s way to bypass the default behavior of the view that's displaying the data. I also added the code to display the stars if no delegate is set ( good for debugging. ) Signed-off-by: Tomaz Canabrava --- Makefile | 2 ++ qt-ui/divelistview.cpp | 2 ++ qt-ui/mainwindow.cpp | 3 ++- qt-ui/modeldelegates.cpp | 36 +++++++++++++++++++++++++ qt-ui/modeldelegates.h | 12 +++++++++ qt-ui/models.cpp | 19 +++++++++---- qt-ui/models.h | 5 ++-- qt-ui/starwidget.cpp | 70 +++++++++++++++++++++++------------------------- qt-ui/starwidget.h | 15 ++++------- 9 files changed, 110 insertions(+), 54 deletions(-) create mode 100644 qt-ui/modeldelegates.cpp create mode 100644 qt-ui/modeldelegates.h (limited to 'Makefile') diff --git a/Makefile b/Makefile index 6ebba4dd5..aac1759bc 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,7 @@ HEADERS = \ qt-ui/models.h \ qt-ui/plotareascene.h \ qt-ui/starwidget.h \ + qt-ui/modeldelegates.h \ SOURCES = \ @@ -75,6 +76,7 @@ SOURCES = \ qt-ui/models.cpp \ qt-ui/plotareascene.cpp \ qt-ui/starwidget.cpp \ + qt-ui/modeldelegates.cpp \ $(RESFILE) diff --git a/qt-ui/divelistview.cpp b/qt-ui/divelistview.cpp index 676d7c463..e8a3d2311 100644 --- a/qt-ui/divelistview.cpp +++ b/qt-ui/divelistview.cpp @@ -5,6 +5,8 @@ * */ #include "divelistview.h" +#include "models.h" +#include "modeldelegates.h" DiveListView::DiveListView(QWidget *parent) : QTreeView(parent) { diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index 8cdc60193..4c4143e98 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -20,7 +20,7 @@ #include "../dive.h" #include "../divelist.h" #include "../pref.h" - +#include "modeldelegates.h" MainWindow::MainWindow() : ui(new Ui::MainWindow()), model(new DiveTripModel(this)), @@ -69,6 +69,7 @@ void MainWindow::on_actionOpen_triggered() model->deleteLater(); model = new DiveTripModel(this); sortModel->setSourceModel(model); + ui->ListWidget->setItemDelegateForColumn(DiveTripModel::RATING, new StarWidgetsDelegate()); } void MainWindow::on_actionSave_triggered() diff --git a/qt-ui/modeldelegates.cpp b/qt-ui/modeldelegates.cpp new file mode 100644 index 000000000..1bbf1061b --- /dev/null +++ b/qt-ui/modeldelegates.cpp @@ -0,0 +1,36 @@ +#include "modeldelegates.h" +#include "../dive.h" +#include "../divelist.h" +#include "starwidget.h" +#include "models.h" + +#include +#include + +void StarWidgetsDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + if (!index.isValid()){ + return; + } + + int rating = index.model()->data(index, DiveTripModel::DelegatesRole).toInt(); + + if (option.state & QStyle::State_Selected) + painter->fillRect(option.rect, option.palette.highlight()); + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, true); + + for(int i = 0; i < rating; i++) + painter->drawPixmap(option.rect.x() + i * IMG_SIZE + SPACING, option.rect.y(), StarWidget::starActive()); + + for(int i = rating; i < TOTALSTARS; i++) + painter->drawPixmap(option.rect.x() + i * IMG_SIZE + SPACING, option.rect.y(), StarWidget::starInactive()); + + painter->restore(); +} + +QSize StarWidgetsDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + return QSize(IMG_SIZE * TOTALSTARS + SPACING * (TOTALSTARS-1), IMG_SIZE); +} diff --git a/qt-ui/modeldelegates.h b/qt-ui/modeldelegates.h new file mode 100644 index 000000000..eacbb5a1e --- /dev/null +++ b/qt-ui/modeldelegates.h @@ -0,0 +1,12 @@ +#ifndef MODELDELEGATES_H +#define MODELDELEGATES_H + +#include + +class StarWidgetsDelegate : public QAbstractItemDelegate { + Q_OBJECT +public: + virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; +}; +#endif diff --git a/qt-ui/models.cpp b/qt-ui/models.cpp index 5f803766f..0944fe3e3 100644 --- a/qt-ui/models.cpp +++ b/qt-ui/models.cpp @@ -5,9 +5,6 @@ * */ #include "models.h" -#include "../dive.h" -#include "../divelist.h" - #include extern struct tank_info tank_info[100]; @@ -284,6 +281,7 @@ void TankInfoModel::update() } } + /*! A DiveItem for use with a DiveTripModel * * A simple class which wraps basic stats for a dive (e.g. duration, depth) and @@ -314,6 +312,8 @@ public: return tw.grams; } + int diveRating() const { return dive->rating; } + QString displayDuration() const; QString displayDepth() const; QString displayTemperature() const; @@ -335,6 +335,7 @@ private: QList childlist; }; + DiveItem::DiveItem(struct dive *d, DiveItem *p): dive(d), parentItem(p) @@ -490,6 +491,16 @@ QVariant DiveTripModel::data(const QModelIndex &index, int role) const case LOCATION: retVal = item->diveLocation(); break; + case RATING: + retVal = item->diveRating(); + break; + } + } + if (role == DelegatesRole){ + switch(index.column()){ + case RATING: + retVal = item->diveRating(); + break; } } return retVal; @@ -569,8 +580,6 @@ int DiveTripModel::rowCount(const QModelIndex &parent) const return item ? item->children().count() : 0; } - - int DiveTripModel::columnCount(const QModelIndex &parent) const { return parent.isValid() && parent.column() != 0 ? 0 : COLUMNS; diff --git a/qt-ui/models.h b/qt-ui/models.h index d64faf0bd..f4d9c8d3b 100644 --- a/qt-ui/models.h +++ b/qt-ui/models.h @@ -9,7 +9,7 @@ #include #include "../dive.h" - +#include "../divelist.h" /* Encapsulates the tank_info global variable * to show on Qt`s Model View System.*/ class TankInfoModel : public QAbstractTableModel { @@ -74,11 +74,12 @@ private: /*! An AbstractItemModel for recording dive trip information such as a list of dives. * */ -class DiveItem; // Represents a single item on the model, implemented in the .cpp since it's private for this class. +class DiveItem; class DiveTripModel : public QAbstractItemModel { public: enum Column {NR, DATE, RATING, DEPTH, DURATION, TEMPERATURE, TOTALWEIGHT, SUIT, CYLINDER, NITROX, SAC, OTU, MAXCNS, LOCATION, COLUMNS }; + enum { DelegatesRole = Qt::UserRole }; DiveTripModel(QObject *parent = 0); diff --git a/qt-ui/starwidget.cpp b/qt-ui/starwidget.cpp index 866fb5834..4d1fa066c 100644 --- a/qt-ui/starwidget.cpp +++ b/qt-ui/starwidget.cpp @@ -5,32 +5,29 @@ #include #include -int StarWidget::currentStars() const -{ - return current; -} +QPixmap* StarWidget::activeStar = 0; +QPixmap* StarWidget::inactiveStar = 0; -void StarWidget::enableHalfStars(bool enabled) +QPixmap StarWidget::starActive() { - halfStar = enabled; - update(); + return (*activeStar); } -bool StarWidget::halfStarsEnabled() const +QPixmap StarWidget::starInactive() { - return halfStar; + return (*inactiveStar); } -int StarWidget::maxStars() const +int StarWidget::currentStars() const { - return stars; + return current; } void StarWidget::mouseReleaseEvent(QMouseEvent* event) { int starClicked = event->pos().x() / IMG_SIZE + 1; - if (starClicked > stars) - starClicked = stars; + if (starClicked > TOTALSTARS) + starClicked = TOTALSTARS; if (current == starClicked) current -= 1; @@ -45,10 +42,10 @@ void StarWidget::paintEvent(QPaintEvent* event) QPainter p(this); for(int i = 0; i < current; i++) - p.drawPixmap(i * IMG_SIZE + SPACING, 0, activeStar); + p.drawPixmap(i * IMG_SIZE + SPACING, 0, starActive()); - for(int i = current; i < stars; i++) - p.drawPixmap(i * IMG_SIZE + SPACING, 0, inactiveStar); + for(int i = current; i < TOTALSTARS; i++) + p.drawPixmap(i * IMG_SIZE + SPACING, 0, starInactive()); } void StarWidget::setCurrentStars(int value) @@ -58,27 +55,25 @@ void StarWidget::setCurrentStars(int value) Q_EMIT valueChanged(current); } -void StarWidget::setMaximumStars(int maximum) -{ - stars = maximum; - update(); -} - StarWidget::StarWidget(QWidget* parent, Qt::WindowFlags f): QWidget(parent, f), - stars(5), - current(0), - halfStar(false) + current(0) { - QSvgRenderer render(QString(":star")); - QPixmap renderedStar(IMG_SIZE, IMG_SIZE); + if(!activeStar){ + activeStar = new QPixmap(); + QSvgRenderer render(QString(":star")); + QPixmap renderedStar(IMG_SIZE, IMG_SIZE); - renderedStar.fill(Qt::transparent); - QPainter painter(&renderedStar); + renderedStar.fill(Qt::transparent); + QPainter painter(&renderedStar); - render.render(&painter, QRectF(0, 0, IMG_SIZE, IMG_SIZE)); - activeStar = renderedStar; - inactiveStar = grayImage(&renderedStar); + render.render(&painter, QRectF(0, 0, IMG_SIZE, IMG_SIZE)); + (*activeStar) = renderedStar; + } + if(!inactiveStar){ + inactiveStar = new QPixmap(); + (*inactiveStar) = grayImage(activeStar); + } } QPixmap StarWidget::grayImage(QPixmap* coloredImg) @@ -86,17 +81,20 @@ QPixmap StarWidget::grayImage(QPixmap* coloredImg) QImage img = coloredImg->toImage(); for (int i = 0; i < img.width(); ++i) { for (int j = 0; j < img.height(); ++j) { - QRgb col = img.pixel(i, j); - if (!col) + QRgb rgb = img.pixel(i, j); + if (!rgb) continue; - int gray = QColor(Qt::darkGray).rgb(); + + QColor c(rgb); + int gray = (c.red() + c.green() + c.blue()) / 3; img.setPixel(i, j, qRgb(gray, gray, gray)); } } + return QPixmap::fromImage(img); } QSize StarWidget::sizeHint() const { - return QSize(IMG_SIZE * stars + SPACING * (stars-1), IMG_SIZE); + return QSize(IMG_SIZE * TOTALSTARS + SPACING * (TOTALSTARS-1), IMG_SIZE); } diff --git a/qt-ui/starwidget.h b/qt-ui/starwidget.h index cdbb3ab5c..d92be5a98 100644 --- a/qt-ui/starwidget.h +++ b/qt-ui/starwidget.h @@ -3,39 +3,34 @@ #include +enum StarConfig {SPACING = 2, IMG_SIZE = 16, TOTALSTARS = 5}; class StarWidget : public QWidget { Q_OBJECT public: explicit StarWidget(QWidget* parent = 0, Qt::WindowFlags f = 0); - - int maxStars() const; int currentStars() const; - bool halfStarsEnabled() const; /*reimp*/ QSize sizeHint() const; - enum {SPACING = 2, IMG_SIZE = 16}; + static QPixmap starActive(); + static QPixmap starInactive(); Q_SIGNALS: void valueChanged(int stars); public Q_SLOTS: void setCurrentStars(int value); - void setMaximumStars(int maximum); - void enableHalfStars(bool enabled); protected: /*reimp*/ void mouseReleaseEvent(QMouseEvent* ); /*reimp*/ void paintEvent(QPaintEvent* ); private: - int stars; int current; - bool halfStar; - QPixmap activeStar; - QPixmap inactiveStar; + static QPixmap* activeStar; + static QPixmap* inactiveStar; QPixmap grayImage(QPixmap *coloredImg); }; -- cgit v1.2.3-70-g09d2 From f45618f0c7ce5af85e0f69aab49ef20a1663bca1 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Wed, 1 May 2013 15:37:41 -0700 Subject: Create Add Weightsystem dialog My first attempt to create a Qt dialog and to hook it up with the program. Unsurprisingly this doesn't quite work as expected (i.e., the values I enter aren't populated in the model), but it's a start... Signed-off-by: Dirk Hohndel --- Makefile | 2 + qt-ui/addweightsystemdialog.cpp | 38 ++++++++++++++ qt-ui/addweightsystemdialog.h | 30 +++++++++++ qt-ui/addweightsystemdialog.ui | 109 ++++++++++++++++++++++++++++++++++++++++ qt-ui/maintab.cpp | 14 ++++-- 5 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 qt-ui/addweightsystemdialog.cpp create mode 100644 qt-ui/addweightsystemdialog.h create mode 100644 qt-ui/addweightsystemdialog.ui (limited to 'Makefile') diff --git a/Makefile b/Makefile index aac1759bc..1bb080363 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,7 @@ EXTRA_FLAGS = $(QTCXXFLAGS) $(GTKCFLAGS) $(GLIB2CFLAGS) $(XML2CFLAGS) \ HEADERS = \ qt-ui/addcylinderdialog.h \ + qt-ui/addweightsystemdialog.h \ qt-ui/divelistview.h \ qt-ui/maintab.h \ qt-ui/mainwindow.h \ @@ -70,6 +71,7 @@ SOURCES = \ webservice.c \ qt-gui.cpp \ qt-ui/addcylinderdialog.cpp \ + qt-ui/addweightsystemdialog.cpp \ qt-ui/divelistview.cpp \ qt-ui/maintab.cpp \ qt-ui/mainwindow.cpp \ diff --git a/qt-ui/addweightsystemdialog.cpp b/qt-ui/addweightsystemdialog.cpp new file mode 100644 index 000000000..6ac1c02f9 --- /dev/null +++ b/qt-ui/addweightsystemdialog.cpp @@ -0,0 +1,38 @@ +/* + * addweightsystemdialog.cpp + * + * classes for the add weightsystem dialog of Subsurface + * + */ +#include "addweightsystemdialog.h" +#include "ui_addweightsystemdialog.h" +#include +#include +#include "../conversions.h" +#include "models.h" + +AddWeightsystemDialog::AddWeightsystemDialog(QWidget *parent) : ui(new Ui::AddWeightsystemDialog()) +{ + ui->setupUi(this); +} + +void AddWeightsystemDialog::setWeightsystem(weightsystem_t *ws) +{ + currentWeightsystem = ws; + + ui->description->insert(QString(ws->description)); + if (get_units()->weight == units::KG) + ui->weight->setValue(ws->weight.grams / 1000); + else + ui->weight->setValue(grams_to_lbs(ws->weight.grams)); +} + +void AddWeightsystemDialog::updateWeightsystem() +{ + currentWeightsystem->description = strdup(ui->description->text().toUtf8().data()); + if (get_units()->weight == units::KG) + currentWeightsystem->weight.grams = ui->weight->value() * 1000; + else + currentWeightsystem->weight.grams = lbs_to_grams(ui->weight->value()); +} + diff --git a/qt-ui/addweightsystemdialog.h b/qt-ui/addweightsystemdialog.h new file mode 100644 index 000000000..e99dc08d8 --- /dev/null +++ b/qt-ui/addweightsystemdialog.h @@ -0,0 +1,30 @@ +/* + * addweightsystemdialog.h + * + * header file for the add weightsystem dialog of Subsurface + * + */ +#ifndef ADDWEIGHTSYSTEMDIALOG_H +#define ADDWEIGHTSYSTEMDIALOG_H + +#include +#include "../dive.h" + +namespace Ui{ + class AddWeightsystemDialog; +} + +class AddWeightsystemDialog : public QDialog{ + Q_OBJECT +public: + explicit AddWeightsystemDialog(QWidget* parent = 0); + void setWeightsystem(weightsystem_t *ws); + void updateWeightsystem(); + +private: + Ui::AddWeightsystemDialog *ui; + weightsystem_t *currentWeightsystem; +}; + + +#endif diff --git a/qt-ui/addweightsystemdialog.ui b/qt-ui/addweightsystemdialog.ui new file mode 100644 index 000000000..11e60d562 --- /dev/null +++ b/qt-ui/addweightsystemdialog.ui @@ -0,0 +1,109 @@ + + + AddWeightsystemDialog + + + + 0 + 0 + 408 + 186 + + + + Dialog + + + + + + Weightsystem + + + + QFormLayout::ExpandingFieldsGrow + + + + + Description + + + + + + + Weight + + + + + + + + 0 + 0 + + + + Qt::ImhFormattedNumbersOnly + + + true + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + AddWeightsystemDialog + accept() + + + 248 + 269 + + + 157 + 260 + + + + + buttonBox + rejected() + AddWeightsystemDialog + reject() + + + 290 + 269 + + + 286 + 260 + + + + + diff --git a/qt-ui/maintab.cpp b/qt-ui/maintab.cpp index 4174ce5dd..f26a72a8a 100644 --- a/qt-ui/maintab.cpp +++ b/qt-ui/maintab.cpp @@ -7,6 +7,7 @@ #include "maintab.h" #include "ui_maintab.h" #include "addcylinderdialog.h" +#include "addweightsystemdialog.h" #include @@ -91,10 +92,17 @@ void MainTab::on_addWeight_clicked() if (weightModel->rowCount() >= MAX_WEIGHTSYSTEMS) return; - /* this needs a dialog - right now we just fill in a dummy */ + AddWeightsystemDialog dialog(this); weightsystem_t *newWeightsystem = (weightsystem_t *) malloc(sizeof(weightsystem_t)); - newWeightsystem->description = "Just testing"; - newWeightsystem->weight.grams = 15000; + newWeightsystem->description = ""; + newWeightsystem->weight.grams = 0; + + dialog.setWeightsystem(newWeightsystem); + int result = dialog.exec(); + if (result == QDialog::Rejected) + return; + + dialog.updateWeightsystem(); weightModel->add(newWeightsystem); } -- cgit v1.2.3-70-g09d2 From 8677721e85a344e29782cfc2ab838edb8da9215b Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Fri, 3 May 2013 11:04:51 -0700 Subject: Remove the majority of the Gtk related code - rip all Gtk code from qt-gui.cpp - don't compile Gtk specific files - don't link against Gtk libraries - don't compile modules we don't use at all (yet) - use #if USE_GTK_UI on the remaining files to disable Gtk related parts - disable the non-functional Cochran support while I'm at it Signed-off-by: Dirk Hohndel --- Makefile | 16 +- display-gtk.h | 6 +- display.h | 6 + divelist-gtk.c | 1 - divelist.c | 12 +- download-dialog.c | 11 +- equipment.c | 33 +- file.c | 2 + gtk-gui.c | 2550 ++++++++++++++++++++++++++++++++++++++++++++++++++ info-gtk.c | 8 + info.c | 15 +- libdivecomputer.c | 4 + libdivecomputer.h | 4 +- linux.c | 1 + main.c | 14 + planner.c | 2 + profile.c | 18 +- qt-gui.cpp | 2456 +----------------------------------------------- qt-ui/mainwindow.cpp | 2 + uemis-downloader.c | 4 + webservice.c | 2 + 21 files changed, 2703 insertions(+), 2464 deletions(-) create mode 100644 gtk-gui.c (limited to 'Makefile') diff --git a/Makefile b/Makefile index 1bb080363..4a13de5eb 100644 --- a/Makefile +++ b/Makefile @@ -42,33 +42,21 @@ HEADERS = \ SOURCES = \ - cochran.c \ deco.c \ device.c \ dive.c \ divelist.c \ - divelist-gtk.c \ download-dialog.c \ equipment.c \ file.c \ info.c \ - info-gtk.c \ - libdivecomputer.c \ main.c \ parse-xml.c \ - planner.c \ - planner-gtk.c \ prefs.c \ - print.c \ profile.c \ save-xml.c \ sha1.c \ - statistics.c \ - statistics-gtk.c \ time.c \ - uemis.c \ - uemis-downloader.c \ - webservice.c \ qt-gui.cpp \ qt-ui/addcylinderdialog.cpp \ qt-ui/addweightsystemdialog.cpp \ @@ -93,10 +81,12 @@ endif ifneq ($(strip $(LIBXSLT)),) EXTRA_FLAGS += -DXSLT='"$(XSLTDIR)"' $(XSLCFLAGS) endif +ifeq ($(USE_GTK_UI),1) ifneq ($(strip $(LIBOSMGPSMAP)),) SOURCES += gps.c EXTRA_FLAGS += -DHAVE_OSM_GPS_MAP $(OSMGPSMAPFLAGS) endif +endif ifneq (,$(filter $(UNAME),linux kfreebsd gnu)) SOURCES += linux.c @@ -118,7 +108,7 @@ else XSLTDIR = .\\xslt endif -LIBS = $(LIBQT) $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGTK) $(LIBGCONF2) $(LIBDIVECOMPUTER) \ +LIBS = $(LIBQT) $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGCONF2) $(LIBDIVECOMPUTER) \ $(EXTRALIBS) $(LIBZIP) -lpthread -lm $(LIBOSMGPSMAP) $(LIBSOUP) $(LIBWINSOCK) MSGLANGS=$(notdir $(wildcard po/*.po)) diff --git a/display-gtk.h b/display-gtk.h index ad286b0d3..736616b1a 100644 --- a/display-gtk.h +++ b/display-gtk.h @@ -39,15 +39,11 @@ extern void set_divelist_font(const char *); extern void update_screen(void); extern void download_dialog(GtkWidget *, gpointer); -extern int is_default_dive_computer_device(const char *); -extern int is_default_dive_computer(const char *, const char *); + extern void add_dive_cb(GtkWidget *, gpointer); extern void update_progressbar(progressbar_t *progress, double value); extern void update_progressbar_text(progressbar_t *progress, const char *text); -extern const char *default_dive_computer_vendor; -extern const char *default_dive_computer_product; -extern const char *default_dive_computer_device; // info.c enum { diff --git a/display.h b/display.h index 9e01c73a9..b1a034e88 100644 --- a/display.h +++ b/display.h @@ -69,6 +69,12 @@ extern char zoomed_plot, dc_number; extern unsigned int amount_selected; +extern int is_default_dive_computer_device(const char *); +extern int is_default_dive_computer(const char *, const char *); +extern const char *default_dive_computer_vendor; +extern const char *default_dive_computer_product; +extern const char *default_dive_computer_device; + #ifdef __cplusplus } #endif diff --git a/divelist-gtk.c b/divelist-gtk.c index 18ef0d122..0c26430c1 100644 --- a/divelist-gtk.c +++ b/divelist-gtk.c @@ -59,7 +59,6 @@ static struct DiveList dive_list; #define TREESTORE(_dl) GTK_TREE_STORE((_dl).treemodel) #define LISTSTORE(_dl) GTK_TREE_STORE((_dl).listmodel) -short autogroup = FALSE; static gboolean in_set_cursor = FALSE; static gboolean set_selected(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data); diff --git a/divelist.c b/divelist.c index 58d89d5f6..e22dd331a 100644 --- a/divelist.c +++ b/divelist.c @@ -54,6 +54,8 @@ static short dive_list_changed = FALSE; +short autogroup = FALSE; + dive_trip_t *dive_trip_list; unsigned int amount_selected; @@ -73,6 +75,13 @@ void dump_selection(void) } #endif +void set_autogroup(gboolean value) +{ + /* if we keep the UI paradigm, this needs to toggle + * the checkbox on the autogroup menu item */ + autogroup = value; +} + dive_trip_t *find_trip_by_idx(int idx) { dive_trip_t *trip = dive_trip_list; @@ -906,8 +915,9 @@ void merge_dive_index(int i, struct dive *a) add_single_dive(i, res); delete_single_dive(i+1); delete_single_dive(i+1); - +#if USE_GTK_UI dive_list_update_dives(); +#endif mark_divelist_changed(TRUE); } diff --git a/download-dialog.c b/download-dialog.c index 5d5c1de04..729f3a6b6 100644 --- a/download-dialog.c +++ b/download-dialog.c @@ -3,19 +3,23 @@ #include "dive.h" #include "divelist.h" #include "display.h" +#if USE_GTK_UI #include "display-gtk.h" #include "callbacks-gtk.h" +#endif #include "libdivecomputer.h" const char *default_dive_computer_vendor; const char *default_dive_computer_product; const char *default_dive_computer_device; +#if USE_GTK_UI static gboolean force_download; static gboolean prefer_downloaded; OPTIONCALLBACK(force_toggle, force_download) OPTIONCALLBACK(prefer_dl_toggle, prefer_downloaded) +#endif struct product { const char *product; @@ -38,6 +42,7 @@ struct mydescriptor { struct vendor *dc_list; +#if USE_GTK_UI static void render_dc_vendor(GtkCellLayout *cell, GtkCellRenderer *renderer, GtkTreeModel *model, @@ -63,6 +68,7 @@ static void render_dc_product(GtkCellLayout *cell, product = dc_descriptor_get_product(descriptor); g_object_set(renderer, "text", product, NULL); } +#endif int is_default_dive_computer(const char *vendor, const char *product) { @@ -105,6 +111,7 @@ static void set_default_dive_computer_device(const char *name) subsurface_set_conf("dive_computer_device", name); } +#if USE_GTK_UI static void dive_computer_selector_changed(GtkWidget *combo, gpointer data) { GtkWidget *import, *button; @@ -149,7 +156,7 @@ static GtkWidget *import_dive_computer(device_data_t *data, GtkDialog *dialog) gtk_box_pack_start(GTK_BOX(vbox), info, FALSE, FALSE, 0); return info; } - +#endif /* create a list of lists and keep the elements sorted */ static void add_dc(const char *vendor, const char *product, dc_descriptor_t *descriptor) @@ -195,6 +202,7 @@ static void add_dc(const char *vendor, const char *product, dc_descriptor_t *des pl->descriptor = descriptor; } +#if USE_GTK_UI /* fill the vendors and create and fill the respective product stores; return the longest product name * and also the indices of the default vendor / product */ static int fill_computer_list(GtkListStore *vendorstore, GtkListStore ***productstore, int *vendor_index, int *product_index) @@ -469,3 +477,4 @@ void update_progressbar_text(progressbar_t *progress, const char *text) { gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress->bar), text); } +#endif diff --git a/equipment.c b/equipment.c index d126b4327..93f26dab1 100644 --- a/equipment.c +++ b/equipment.c @@ -16,10 +16,14 @@ #include "dive.h" #include "display.h" +#if USE_GTK_UI #include "display-gtk.h" +#endif #include "divelist.h" #include "conversions.h" +#if USE_GTK_UI +#include "display-gtk.h" static GtkListStore *cylinder_model, *weightsystem_model; enum { @@ -69,6 +73,7 @@ struct ws_widget { GtkSpinButton *weight; int w_idx; }; +#endif /* USE_GTK_UI */ /* we want bar - so let's not use our unit functions */ int convert_pressure(int mbar, double *p) @@ -120,6 +125,7 @@ static int convert_weight(int grams, double *m) return decimals; } +#if USE_GTK_UI static void set_cylinder_description(struct cylinder_widget *cylinder, const char *desc) { set_active_text(cylinder->description, desc); @@ -451,6 +457,28 @@ static void show_weightsystem(weightsystem_t *ws, struct ws_widget *weightsystem set_weight_description(weightsystem_widget, desc); set_weight_weight_spinbutton(weightsystem_widget, ws->weight.grams); } +#else +/* placeholders for a few functions that we need to redesign for the Qt UI */ +void add_cylinder_description(cylinder_type_t *type) +{ + const char *desc; + + desc = type->description; + if (!desc) + return; + /* now do something with it... */ +} +void add_weightsystem_description(weightsystem_t *weightsystem) +{ + const char *desc; + + desc = weightsystem->description; + if (!desc) + return; + /* now do something with it... */ +} + +#endif /* USE_GTK_UI */ gboolean cylinder_nodata(cylinder_t *cyl) { @@ -515,6 +543,7 @@ gboolean weightsystems_equal(weightsystem_t *ws1, weightsystem_t *ws2) return TRUE; } +#if USE_GTK_UI static void set_one_cylinder(void *_data, GtkListStore *model, GtkTreeIter *iter) { cylinder_t *cyl = _data; @@ -784,7 +813,7 @@ static void record_weightsystem_changes(weightsystem_t *ws, struct ws_widget *we ws->description = desc; add_weightsystem_type(desc, grams, &iter); } - +#endif /* USE_GTK_UI */ /* * We hardcode the most common standard cylinders, * we should pick up any other names from the dive @@ -833,6 +862,7 @@ struct tank_info tank_info[100] = { { NULL, } }; +#if USE_GTK_UI static void fill_tank_list(GtkListStore *store) { GtkTreeIter iter; @@ -1679,3 +1709,4 @@ void clear_equipment_widgets() gtk_list_store_clear(cylinder_list[W_IDX_PRIMARY].model); gtk_list_store_clear(weightsystem_list[W_IDX_PRIMARY].model); } +#endif /* USE_GTK_UI */ diff --git a/file.c b/file.c index 401bd5c36..350ad25e1 100644 --- a/file.c +++ b/file.c @@ -239,9 +239,11 @@ static int open_by_filename(const char *filename, const char *fmt, struct memblo if (!strcasecmp(fmt, "DLD")) return try_to_open_zip(filename, mem, error); +#if ONCE_COCHRAN_IS_SUPPORTED /* Truly nasty intentionally obfuscated Cochran Anal software */ if (!strcasecmp(fmt, "CAN")) return try_to_open_cochran(filename, mem, error); +#endif /* Cochran export comma-separated-value files */ if (!strcasecmp(fmt, "DPT")) diff --git a/gtk-gui.c b/gtk-gui.c new file mode 100644 index 000000000..389257817 --- /dev/null +++ b/gtk-gui.c @@ -0,0 +1,2550 @@ +/* gtk-gui.c */ +/* gtk UI implementation */ +/* creates the window and overall layout + * divelist, dive info, equipment and printing are handled in their own source files + */ +/* + * This is the former qt-gui.cpp - so it already contains some Qt related + * functions. It's renamed back to gtk-ui.c to keep the old Gtk code + * around for reference in case we still need it... all that has now been + * ripped out of qt-gui.cpp */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dive.h" +#include "divelist.h" +#include "display.h" +#include "display-gtk.h" +#include "callbacks-gtk.h" +#include "uemis.h" +#include "device.h" +#include "webservice.h" +#include "version.h" +#include "libdivecomputer.h" +#include "qt-ui/mainwindow.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#if HAVE_OSM_GPS_MAP +#include +#endif + +class Translator: public QTranslator +{ + Q_OBJECT + +public: + Translator(QObject *parent = 0); + ~Translator() {} + + virtual QString translate(const char *context, const char *sourceText, + const char *disambiguation = NULL) const; +}; + +Translator::Translator(QObject *parent): + QTranslator(parent) +{ +} + +QString Translator::translate(const char *context, const char *sourceText, + const char *disambiguation) const +{ + return gettext(sourceText); +} + +static const GdkPixdata subsurface_icon_pixbuf = {}; + +GtkWidget *main_window; +GtkWidget *main_vbox; +GtkWidget *error_info_bar; +GtkWidget *error_label; +GtkWidget *vpane, *hpane; +GtkWidget *notebook; +static QApplication *application = NULL; + +int error_count; +const char *existing_filename; + +typedef enum { PANE_INFO, PANE_PROFILE, PANE_LIST, PANE_THREE } pane_conf_t; +static pane_conf_t pane_conf; + +static struct device_info *holdnicknames = NULL; +static GtkWidget *dive_profile_widget(void); +static void import_files(GtkWidget *, gpointer); + +static void remember_dc(const char *model, uint32_t deviceid, const char *nickname) +{ + struct device_info *nn_entry; + + nn_entry = create_device_info(model, deviceid); + if (!nn_entry) + return; + if (!nickname || !*nickname) { + nn_entry->nickname = NULL; + return; + } + nn_entry->nickname = strdup(nickname); +} + +static void remove_dc(const char *model, uint32_t deviceid) +{ + free(remove_device_info(model, deviceid)); +} + +static GtkWidget *dive_profile; + +GtkActionGroup *action_group; + +void repaint_dive(void) +{ + update_dive(current_dive); + if (dive_profile) + gtk_widget_queue_draw(dive_profile); +} + +static gboolean need_icon = TRUE; + +static void on_info_bar_response(GtkWidget *widget, gint response, + gpointer data) +{ + if (response == GTK_RESPONSE_OK) + { + gtk_widget_destroy(widget); + error_info_bar = NULL; + } +} + +void report_error(GError* error) +{ + qDebug("Warning: Calling GTK-Specific Code."); + if (error == NULL) + { + return; + } + + if (error_info_bar == NULL) + { + error_count = 1; + error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK, + GTK_RESPONSE_OK, + NULL); + g_signal_connect(error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL); + gtk_info_bar_set_message_type(GTK_INFO_BAR(error_info_bar), + GTK_MESSAGE_ERROR); + + error_label = gtk_label_new(error->message); + GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(error_info_bar)); + gtk_container_add(GTK_CONTAINER(container), error_label); + + gtk_box_pack_start(GTK_BOX(main_vbox), error_info_bar, FALSE, FALSE, 0); + gtk_widget_show_all(main_vbox); + } + else + { + error_count++; + char buffer[256]; + snprintf(buffer, sizeof(buffer), _("Failed to open %i files."), error_count); + gtk_label_set_text(GTK_LABEL(error_label), buffer); + } +} + +static GtkFileFilter *setup_filter(void) +{ + GtkFileFilter *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, "*.uddf"); + gtk_file_filter_add_pattern(filter, "*.UDDF"); + gtk_file_filter_add_pattern(filter, "*.udcf"); + gtk_file_filter_add_pattern(filter, "*.UDCF"); + gtk_file_filter_add_pattern(filter, "*.jlb"); + gtk_file_filter_add_pattern(filter, "*.JLB"); +#ifdef LIBZIP + gtk_file_filter_add_pattern(filter, "*.sde"); + gtk_file_filter_add_pattern(filter, "*.SDE"); + gtk_file_filter_add_pattern(filter, "*.dld"); + gtk_file_filter_add_pattern(filter, "*.DLD"); +#endif +#ifdef SQLITE3 + gtk_file_filter_add_pattern(filter, "*.DB"); + gtk_file_filter_add_pattern(filter, "*.db"); +#endif + + gtk_file_filter_add_mime_type(filter, "text/xml"); + gtk_file_filter_set_name(filter, _("XML file")); + + return filter; +} + +static void file_save_as(GtkWidget *w, gpointer data) +{ + GtkWidget *dialog; + char *filename = NULL; + char *current_file; + char *current_dir; + + dialog = gtk_file_chooser_dialog_new(_("Save File As"), + GTK_WINDOW(main_window), + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); + + if (existing_filename) { + current_dir = g_path_get_dirname(existing_filename); + current_file = g_path_get_basename(existing_filename); + } else { + const char *current_default = prefs.default_filename; + current_dir = g_path_get_dirname(current_default); + current_file = g_path_get_basename(current_default); + } + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), current_dir); + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), current_file); + + free(current_dir); + free(current_file); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + } + gtk_widget_destroy(dialog); + + if (filename){ + save_dives(filename); + set_filename(filename, TRUE); + g_free(filename); + mark_divelist_changed(FALSE); + } +} + +static void file_save(GtkWidget *w, gpointer data) +{ + const char *current_default; + + if (!existing_filename) + return file_save_as(w, data); + + current_default = prefs.default_filename; + if (strcmp(existing_filename, current_default) == 0) { + /* if we are using the default filename the directory + * that we are creating the file in may not exist */ + char *current_def_dir; + struct stat sb; + + current_def_dir = g_path_get_dirname(existing_filename); + if (stat(current_def_dir, &sb) != 0) { + g_mkdir(current_def_dir, S_IRWXU); + } + free(current_def_dir); + } + save_dives(existing_filename); + mark_divelist_changed(FALSE); +} + +static gboolean ask_save_changes() +{ + //WARNING: Porting to Qt + qDebug("This method is being ported to Qt, please, stop using it. "); + GtkWidget *dialog, *label, *content; + gboolean quit = TRUE; + dialog = gtk_dialog_new_with_buttons(_("Save Changes?"), + GTK_WINDOW(main_window), GtkDialogFlags(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + GTK_STOCK_NO, GTK_RESPONSE_NO, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + NULL); + content = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + + if (!existing_filename){ + label = gtk_label_new ( + _("You have unsaved changes\nWould you like to save those before closing the datafile?")); + } else { + char *label_text = (char*) malloc(sizeof(char) * + (strlen(_("You have unsaved changes to file: %s \nWould you like to save those before closing the datafile?")) + + strlen(existing_filename))); + sprintf(label_text, + _("You have unsaved changes to file: %s \nWould you like to save those before closing the datafile?"), + existing_filename); + label = gtk_label_new (label_text); + free(label_text); + } + gtk_container_add (GTK_CONTAINER (content), label); + gtk_widget_show_all (dialog); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT); + gint outcode = gtk_dialog_run(GTK_DIALOG(dialog)); + if (outcode == GTK_RESPONSE_ACCEPT) { + file_save(NULL,NULL); + } else if (outcode == GTK_RESPONSE_CANCEL || outcode == GTK_RESPONSE_DELETE_EVENT) { + quit = FALSE; + } + gtk_widget_destroy(dialog); + return quit; +} + +static void file_close(GtkWidget *w, gpointer data) +{ + qDebug("Calling an already ported-to-qt Gtk method"); + if (unsaved_changes()) + if (ask_save_changes() == FALSE) + return; + + if (existing_filename) + free((void *)existing_filename); + existing_filename = NULL; + + /* free the dives and trips */ + while (dive_table.nr) + delete_single_dive(0); + mark_divelist_changed(FALSE); + + /* clear the selection and the statistics */ + selected_dive = 0; + process_selected_dives(); + clear_stats_widgets(); + clear_events(); + show_dive_stats(NULL); + + /* clear the equipment page */ + clear_equipment_widgets(); + + /* redraw the screen */ + dive_list_update_dives(); + show_dive_info(NULL); +} + +//##################################################################### +//###### ALREAADY PORTED TO Qt. DELETE ME WHEN NOT MORE USERFUL. # +//##################################################################### +static void file_open(GtkWidget *w, gpointer data) +{ + qDebug("Calling an already ported-to-qt Gtk method."); + GtkWidget *dialog; + GtkFileFilter *filter; + const char *current_default; + + 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); + current_default = prefs.default_filename; + gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), current_default); + /* when opening the data file we should allow only one file to be chosen */ + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE); + + filter = setup_filter(); + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + GSList *fn_glist; + char *filename; + + /* first, close the existing file, if any, and forget its name */ + file_close(w, data); + free((void *)existing_filename); + existing_filename = NULL; + + /* we know there is only one filename */ + fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); + + GError *error = NULL; + filename = (char *)fn_glist->data; + parse_file(filename, &error); + set_filename(filename, TRUE); + if (error != NULL) + { + report_error(error); + g_error_free(error); + error = NULL; + } + g_free(filename); + g_slist_free(fn_glist); + report_dives(FALSE, FALSE); + } + gtk_widget_destroy(dialog); +} + +void save_pane_position() +{ + gint vpane_position = gtk_paned_get_position(GTK_PANED(vpane)); + gint hpane_position = gtk_paned_get_position(GTK_PANED(hpane)); + gboolean is_maximized = gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(main_window))) & + GDK_WINDOW_STATE_MAXIMIZED; + + if (pane_conf == PANE_THREE){ + if (is_maximized) { + subsurface_set_conf_int("vpane_position_maximized", vpane_position); + subsurface_set_conf_int("hpane_position_maximized", hpane_position); + } else { + subsurface_set_conf_int("vpane_position", vpane_position); + subsurface_set_conf_int("hpane_position", hpane_position); + } + } +} + +/* Since we want direct control of what set of parameters to retrive, this function + * has an input argument. */ +void restore_pane_position(gboolean maximized) +{ + if (maximized) { + gtk_paned_set_position(GTK_PANED(vpane), subsurface_get_conf_int("vpane_position_maximized")); + gtk_paned_set_position(GTK_PANED(hpane), subsurface_get_conf_int("hpane_position_maximized")); + } else { + gtk_paned_set_position(GTK_PANED(vpane), subsurface_get_conf_int("vpane_position")); + gtk_paned_set_position(GTK_PANED(hpane), subsurface_get_conf_int("hpane_position")); + } +} + +void save_window_geometry(void) +{ + /* GDK_GRAVITY_NORTH_WEST assumed ( it is the default ) */ + int window_width, window_height; + gboolean is_maximized; + + gtk_window_get_size(GTK_WINDOW(main_window), &window_width, &window_height); + is_maximized = gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(main_window))) & + GDK_WINDOW_STATE_MAXIMIZED; + subsurface_set_conf_int("window_width", window_width); + subsurface_set_conf_int("window_height", window_height); + subsurface_set_conf_bool("window_maximized", is_maximized); + save_pane_position(); + subsurface_flush_conf(); +} + +void restore_window_geometry(void) +{ + int window_width, window_height; + gboolean is_maximized = subsurface_get_conf_bool("window_maximized") > 0; + + window_height = subsurface_get_conf_int("window_height"); + window_width = subsurface_get_conf_int("window_width"); + + window_height == -1 ? window_height = 300 : window_height; + window_width == -1 ? window_width = 700 : window_width; + + restore_pane_position(is_maximized); + /* don't resize the window if in maximized state */ + if (is_maximized) + gtk_window_maximize(GTK_WINDOW(main_window)); + else + gtk_window_resize(GTK_WINDOW(main_window), window_width, window_height); +} + +gboolean on_delete(GtkWidget* w, gpointer data) +{ + /* Make sure to flush any modified dive data */ + update_dive(NULL); + + gboolean quit = TRUE; + if (unsaved_changes()) + quit = ask_save_changes(); + + if (quit){ + save_window_geometry(); + return FALSE; /* go ahead, kill the program, we're good now */ + } else { + return TRUE; /* We are not leaving */ + } +} + +static void on_destroy(GtkWidget* w, gpointer data) +{ + dive_list_destroy(); + info_widget_destroy(); + gtk_main_quit(); +} + +/* This "window-state-event" callback will be called after the actual action, such + * as maximize or restore. This means that if you have methods here that check + * for the current window state, they will obtain the already updated state... */ +static gboolean on_state(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data) +{ + gint vpane_position, hpane_position; + if (event->changed_mask & GDK_WINDOW_STATE_WITHDRAWN || + event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) + return TRUE; /* do nothing if the window is shown for the first time or minimized */ + if (pane_conf == PANE_THREE) { + hpane_position = gtk_paned_get_position(GTK_PANED(hpane)); + vpane_position = gtk_paned_get_position(GTK_PANED(vpane)); + if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) { /* maximize */ + subsurface_set_conf_int("vpane_position", vpane_position); + subsurface_set_conf_int("hpane_position", hpane_position); + restore_pane_position(TRUE); + } else if (event->new_window_state == 0) { /* restore */ + subsurface_set_conf_int("vpane_position_maximized", vpane_position); + subsurface_set_conf_int("hpane_position_maximized", hpane_position); + restore_pane_position(FALSE); + } + } + return TRUE; +} + +static void quit(GtkWidget *w, gpointer data) +{ + /* Make sure to flush any modified dive data */ + update_dive(NULL); + + gboolean quit = TRUE; + if (unsaved_changes()) + quit = ask_save_changes(); + + if (quit){ + save_window_geometry(); + dive_list_destroy(); + gtk_main_quit(); + } +} + +GtkTreeViewColumn *tree_view_column_add_pixbuf(GtkWidget *tree_view, data_func_t data_func, GtkTreeViewColumn *col) +{ + GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new(); + gtk_tree_view_column_pack_start(col, renderer, FALSE); + gtk_tree_view_column_set_cell_data_func(col, renderer, data_func, NULL, NULL); + g_signal_connect(tree_view, "button-press-event", G_CALLBACK(icon_click_cb), col); + return col; +} + +GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title, + data_func_t data_func, unsigned int flags) +{ + GtkCellRenderer *renderer; + GtkTreeViewColumn *col; + double xalign = 0.0; /* left as default */ + PangoAlignment align; + gboolean visible; + + align = (flags & ALIGN_LEFT) ? PANGO_ALIGN_LEFT : + (flags & ALIGN_RIGHT) ? PANGO_ALIGN_RIGHT : + PANGO_ALIGN_CENTER; + visible = !(flags & INVISIBLE); + + renderer = gtk_cell_renderer_text_new(); + col = gtk_tree_view_column_new(); + + if (flags & EDITABLE) { + g_object_set(renderer, "editable", TRUE, NULL); + g_signal_connect(renderer, "edited", (GCallback) data_func, tree_view); + data_func = NULL; + } + + gtk_tree_view_column_set_title(col, title); + if (!(flags & UNSORTABLE)) + gtk_tree_view_column_set_sort_column_id(col, index); + gtk_tree_view_column_set_resizable(col, TRUE); + /* all but one column have only one renderer - so packing from the end + * makes no difference; for the location column we want to be able to + * prepend the icon in front of the text - so this works perfectly */ + gtk_tree_view_column_pack_end(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); + 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; +} + +/* Helper functions for gtk combo boxes */ +GtkEntry *get_entry(GtkComboBox *combo_box) +{ + return GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo_box))); +} + +const char *get_active_text(GtkComboBox *combo_box) +{ + return gtk_entry_get_text(get_entry(combo_box)); +} + +void set_active_text(GtkComboBox *combo_box, const char *text) +{ + gtk_entry_set_text(get_entry(combo_box), text); +} + +GtkWidget *combo_box_with_model_and_entry(GtkListStore *model) +{ + GtkWidget *widget; + GtkEntryCompletion *completion; + +#if GTK_CHECK_VERSION(2,24,0) + widget = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model)); + gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(widget), 0); +#else + widget = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0); + gtk_combo_box_entry_set_text_column(GTK_COMBO_BOX_ENTRY(widget), 0); +#endif + + completion = gtk_entry_completion_new(); + gtk_entry_completion_set_text_column(completion, 0); + gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(model)); + gtk_entry_completion_set_inline_completion(completion, TRUE); + gtk_entry_completion_set_inline_selection(completion, TRUE); + gtk_entry_completion_set_popup_single_match(completion, FALSE); + gtk_entry_set_completion(get_entry(GTK_COMBO_BOX(widget)), completion); + g_object_unref(completion); + + return widget; +} + +static void create_radio(GtkWidget *vbox, const char *w_name, ...) +{ + va_list args; + GtkRadioButton *group = NULL; + GtkWidget *box, *label; + + box = gtk_hbox_new(TRUE, 10); + gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 0); + + label = gtk_label_new(w_name); + gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0); + + va_start(args, w_name); + for (;;) { + int enabled; + const char *name; + GtkWidget *button; + void *callback_fn; + + name = va_arg(args, char *); + if (!name) + break; + callback_fn = va_arg(args, void *); + enabled = va_arg(args, int); + + button = gtk_radio_button_new_with_label_from_widget(group, name); + group = GTK_RADIO_BUTTON(button); + gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), enabled); + g_signal_connect(button, "toggled", G_CALLBACK(callback_fn), NULL); + } + va_end(args); +} + +void update_screen() +{ + update_dive_list_units(); + repaint_dive(); + update_dive_list_col_visibility(); +} + +UNITCALLBACK(set_meter, length, units::METERS) +UNITCALLBACK(set_feet, length, units::FEET) +UNITCALLBACK(set_bar, pressure, units::BAR) +UNITCALLBACK(set_psi, pressure, units::PSI) +UNITCALLBACK(set_liter, volume, units::LITER) +UNITCALLBACK(set_cuft, volume, units::CUFT) +UNITCALLBACK(set_celsius, temperature, units::CELSIUS) +UNITCALLBACK(set_fahrenheit, temperature, units::FAHRENHEIT) +UNITCALLBACK(set_kg, weight, units::KG) +UNITCALLBACK(set_lbs, weight, units::LBS) + +OPTIONCALLBACK(otu_toggle, prefs.visible_cols.otu) +OPTIONCALLBACK(maxcns_toggle, prefs.visible_cols.maxcns) +OPTIONCALLBACK(sac_toggle, prefs.visible_cols.sac) +OPTIONCALLBACK(nitrox_toggle, prefs.visible_cols.nitrox) +OPTIONCALLBACK(temperature_toggle, prefs.visible_cols.temperature) +OPTIONCALLBACK(totalweight_toggle, prefs.visible_cols.totalweight) +OPTIONCALLBACK(suit_toggle, prefs.visible_cols.suit) +OPTIONCALLBACK(cylinder_toggle, prefs.visible_cols.cylinder) +OPTIONCALLBACK(po2_toggle, prefs.pp_graphs.po2) +OPTIONCALLBACK(pn2_toggle, prefs.pp_graphs.pn2) +OPTIONCALLBACK(phe_toggle, prefs.pp_graphs.phe) +OPTIONCALLBACK(mod_toggle, prefs.mod) +OPTIONCALLBACK(ead_toggle, prefs.ead) +OPTIONCALLBACK(red_ceiling_toggle, prefs.profile_red_ceiling) +OPTIONCALLBACK(calc_ceiling_toggle, prefs.profile_calc_ceiling) +OPTIONCALLBACK(calc_ceiling_3m_toggle, prefs.calc_ceiling_3m_incr) + +static gboolean gflow_edit(GtkWidget *w, GdkEvent *event, gpointer _data) +{ + double gflow; + const char *buf; + if (event->type == GDK_FOCUS_CHANGE) { + buf = gtk_entry_get_text(GTK_ENTRY(w)); + sscanf(buf, "%lf", &gflow); + prefs.gflow = gflow / 100.0; + set_gf(prefs.gflow, -1.0); + update_screen(); + } + return FALSE; +} + +static gboolean gfhigh_edit(GtkWidget *w, GdkEvent *event, gpointer _data) +{ + double gfhigh; + const char *buf; + if (event->type == GDK_FOCUS_CHANGE) { + buf = gtk_entry_get_text(GTK_ENTRY(w)); + sscanf(buf, "%lf", &gfhigh); + prefs.gfhigh = gfhigh / 100.0; + set_gf(-1.0, prefs.gfhigh); + update_screen(); + } + return FALSE; +} + +static void event_toggle(GtkWidget *w, gpointer _data) +{ + gboolean *plot_ev = (gboolean *)_data; + + *plot_ev = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)); +} + +static void pick_default_file(GtkWidget *w, GtkButton *button) +{ + GtkWidget *fs_dialog, *parent; + const char *current_default; + char *current_def_file, *current_def_dir; + GtkFileFilter *filter; + struct stat sb; + + fs_dialog = gtk_file_chooser_dialog_new(_("Choose Default XML File"), + GTK_WINDOW(main_window), + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + parent = gtk_widget_get_ancestor(w, GTK_TYPE_DIALOG); + gtk_widget_set_sensitive(parent, FALSE); + gtk_window_set_transient_for(GTK_WINDOW(fs_dialog), GTK_WINDOW(parent)); + + current_default = prefs.default_filename; + current_def_dir = g_path_get_dirname(current_default); + current_def_file = g_path_get_basename(current_default); + + /* it's possible that the directory doesn't exist (especially for the default) + * For gtk's file select box to make sense we create it */ + if (stat(current_def_dir, &sb) != 0) + g_mkdir(current_def_dir, S_IRWXU); + + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs_dialog), current_def_dir); + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(fs_dialog), current_def_file); + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(fs_dialog), FALSE); + filter = setup_filter(); + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fs_dialog), filter); + gtk_widget_show_all(fs_dialog); + if (gtk_dialog_run(GTK_DIALOG(fs_dialog)) == GTK_RESPONSE_ACCEPT) { + GSList *list; + + list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(fs_dialog)); + if (g_slist_length(list) == 1) + gtk_button_set_label(button, (const gchar *)list->data); + g_slist_free(list); + } + + free(current_def_dir); + free(current_def_file); + gtk_widget_destroy(fs_dialog); + + gtk_widget_set_sensitive(parent, TRUE); +} + +#if HAVE_OSM_GPS_MAP +static GtkWidget * map_provider_widget() +{ + int i; +#if GTK_CHECK_VERSION(2,24,0) + GtkWidget *combobox = gtk_combo_box_text_new(); + + /* several of the providers seem to be redundant or non-functional; + * we may have to skip more than just the last three eventually */ + for (i = OSM_GPS_MAP_SOURCE_OPENSTREETMAP; i < OSM_GPS_MAP_SOURCE_LAST; i++) + gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combobox), osm_gps_map_source_get_friendly_name((OsmGpsMapSource_t)i)); +#else + GtkWidget *combobox = gtk_combo_box_new_text(); + for (i = OSM_GPS_MAP_SOURCE_OPENSTREETMAP; i < OSM_GPS_MAP_SOURCE_LAST; i++) + gtk_combo_box_append_text(GTK_COMBO_BOX(combobox), osm_gps_map_source_get_friendly_name(i)); +#endif + /* we don't offer choice 0 (none), so the index here is off by one */ + gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), prefs.map_provider - 1); + return combobox; +} +#endif + +static void preferences_dialog(GtkWidget *w, gpointer data) +{ + int result; + GtkWidget *dialog, *notebook, *font, *frame, *box, *hbox, *vbox, *button; + GtkWidget *xmlfile_button; +#if HAVE_OSM_GPS_MAP + GtkWidget *map_provider; +#endif + GtkWidget *entry_po2, *entry_pn2, *entry_phe, *entry_mod, *entry_gflow, *entry_gfhigh; + const char *current_default, *new_default; + char threshold_text[10], mod_text[10], utf8_buf[128]; + struct preferences oldprefs = prefs; + + dialog = gtk_dialog_new_with_buttons(_("Preferences"), + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + NULL); + + /* create the notebook for the preferences and attach it to dialog */ + notebook = gtk_notebook_new(); + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_box_pack_start(GTK_BOX(vbox), notebook, FALSE, FALSE, 5); + + /* vbox that holds the first notebook page */ + vbox = gtk_vbox_new(FALSE, 6); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, + gtk_label_new(_("General Settings"))); + frame = gtk_frame_new(_("Units")); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); + + box = gtk_vbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(frame), box); + + create_radio(box, _("Depth:"), + _("Meter"), set_meter, (prefs.units.length == units::METERS), + _("Feet"), set_feet, (prefs.units.length == units::FEET), + NULL); + + create_radio(box, _("Pressure:"), + _("Bar"), set_bar, (prefs.units.pressure == units::BAR), + _("PSI"), set_psi, (prefs.units.pressure == units::PSI), + NULL); + + create_radio(box, _("Volume:"), + _("Liter"), set_liter, (prefs.units.volume == units::LITER), + _("CuFt"), set_cuft, (prefs.units.volume == units::CUFT), + NULL); + + create_radio(box, _("Temperature:"), + _("Celsius"), set_celsius, (prefs.units.temperature == units::CELSIUS), + _("Fahrenheit"), set_fahrenheit, (prefs.units.temperature == units::FAHRENHEIT), + NULL); + + create_radio(box, _("Weight:"), + _("kg"), set_kg, (prefs.units.weight == units::KG), + _("lbs"), set_lbs, (prefs.units.weight == units::LBS), + NULL); + + frame = gtk_frame_new(_("Show Columns")); + gtk_box_pack_start(GTK_BOX(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(_("Temp")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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(_("Cyl")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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("O" UTF8_SUBSCRIPT_2 "%"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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(_("SAC")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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(_("Weight")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.visible_cols.totalweight); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(totalweight_toggle), NULL); + + button = gtk_check_button_new_with_label(_("Suit")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.visible_cols.suit); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(suit_toggle), NULL); + + frame = gtk_frame_new(_("Divelist Font")); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); + + font = gtk_font_button_new_with_font(prefs.divelist_font); + gtk_container_add(GTK_CONTAINER(frame),font); + + frame = gtk_frame_new(_("Misc. Options")); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(frame), box); + + frame = gtk_frame_new(_("Default XML Data File")); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 5); + hbox = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(frame), hbox); + current_default = prefs.default_filename; + xmlfile_button = gtk_button_new_with_label(current_default); + g_signal_connect(G_OBJECT(xmlfile_button), "clicked", + G_CALLBACK(pick_default_file), xmlfile_button); + gtk_box_pack_start(GTK_BOX(hbox), xmlfile_button, FALSE, FALSE, 6); +#if HAVE_OSM_GPS_MAP + frame = gtk_frame_new(_("Map provider")); + map_provider = map_provider_widget(); + gtk_container_add(GTK_CONTAINER(frame), map_provider); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 3); +#endif + /* vbox that holds the second notebook page */ + vbox = gtk_vbox_new(FALSE, 6); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, + gtk_label_new(_("Tec Settings"))); + + frame = gtk_frame_new(_("Show Columns")); + gtk_box_pack_start(GTK_BOX(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(_("OTU")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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); + + button = gtk_check_button_new_with_label(_("maxCNS")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.visible_cols.maxcns); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(maxcns_toggle), NULL); + + frame = gtk_frame_new(_("Profile Settings")); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); + + vbox = gtk_vbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(frame), vbox); + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(vbox), box); + + sprintf(utf8_buf, _("Show pO%s graph"), UTF8_SUBSCRIPT_2); + button = gtk_check_button_new_with_label(utf8_buf); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.pp_graphs.po2); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(po2_toggle), &entry_po2); + + sprintf(utf8_buf, _("pO%s threshold"), UTF8_SUBSCRIPT_2); + frame = gtk_frame_new(utf8_buf); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); + entry_po2 = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(entry_po2), 4); + snprintf(threshold_text, sizeof(threshold_text), "%.1f", prefs.pp_graphs.po2_threshold); + gtk_entry_set_text(GTK_ENTRY(entry_po2), threshold_text); + gtk_widget_set_sensitive(entry_po2, prefs.pp_graphs.po2); + gtk_container_add(GTK_CONTAINER(frame), entry_po2); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(vbox), box); + + sprintf(utf8_buf, _("Show pN%s graph"), UTF8_SUBSCRIPT_2); + button = gtk_check_button_new_with_label(utf8_buf); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.pp_graphs.pn2); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(pn2_toggle), &entry_pn2); + + sprintf(utf8_buf, _("pN%s threshold"), UTF8_SUBSCRIPT_2); + frame = gtk_frame_new(utf8_buf); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); + entry_pn2 = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(entry_pn2), 4); + snprintf(threshold_text, sizeof(threshold_text), "%.1f", prefs.pp_graphs.pn2_threshold); + gtk_entry_set_text(GTK_ENTRY(entry_pn2), threshold_text); + gtk_widget_set_sensitive(entry_pn2, prefs.pp_graphs.pn2); + gtk_container_add(GTK_CONTAINER(frame), entry_pn2); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(vbox), box); + + button = gtk_check_button_new_with_label(_("Show pHe graph")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.pp_graphs.phe); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(phe_toggle), &entry_phe); + + frame = gtk_frame_new(_("pHe threshold")); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); + entry_phe = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(entry_phe), 4); + snprintf(threshold_text, sizeof(threshold_text), "%.1f", prefs.pp_graphs.phe_threshold); + gtk_entry_set_text(GTK_ENTRY(entry_phe), threshold_text); + gtk_widget_set_sensitive(entry_phe, prefs.pp_graphs.phe); + gtk_container_add(GTK_CONTAINER(frame), entry_phe); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(vbox), box); + + button = gtk_check_button_new_with_label(_("Show MOD")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.mod); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(mod_toggle), &entry_mod); + + frame = gtk_frame_new(_("max ppO2")); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); + entry_mod = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(entry_mod), 4); + snprintf(mod_text, sizeof(mod_text), "%.1f", prefs.mod_ppO2); + gtk_entry_set_text(GTK_ENTRY(entry_mod), mod_text); + gtk_widget_set_sensitive(entry_mod, prefs.mod); + gtk_container_add(GTK_CONTAINER(frame), entry_mod); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(vbox), box); + + button = gtk_check_button_new_with_label(_("Show EAD, END, EADD")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.ead); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(ead_toggle), NULL); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(vbox), box); + + button = gtk_check_button_new_with_label(_("Show dc reported ceiling in red")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.profile_red_ceiling); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(red_ceiling_toggle), NULL); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(vbox), box); + + button = gtk_check_button_new_with_label(_("Show calculated ceiling")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.profile_calc_ceiling); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(calc_ceiling_toggle), NULL); + + button = gtk_check_button_new_with_label(_("3m increments for calculated ceiling")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.calc_ceiling_3m_incr); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(calc_ceiling_3m_toggle), NULL); + + box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(vbox), box); + + frame = gtk_frame_new(_("GFlow")); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); + entry_gflow = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(entry_gflow), 4); + snprintf(threshold_text, sizeof(threshold_text), "%.0f", prefs.gflow * 100); + gtk_entry_set_text(GTK_ENTRY(entry_gflow), threshold_text); + gtk_container_add(GTK_CONTAINER(frame), entry_gflow); + gtk_widget_add_events(entry_gflow, GDK_FOCUS_CHANGE_MASK); + g_signal_connect(G_OBJECT(entry_gflow), "event", G_CALLBACK(gflow_edit), NULL); + + frame = gtk_frame_new(_("GFhigh")); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); + entry_gfhigh = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(entry_gfhigh), 4); + snprintf(threshold_text, sizeof(threshold_text), "%.0f", prefs.gfhigh * 100); + gtk_entry_set_text(GTK_ENTRY(entry_gfhigh), threshold_text); + gtk_container_add(GTK_CONTAINER(frame), entry_gfhigh); + gtk_widget_add_events(entry_gfhigh, GDK_FOCUS_CHANGE_MASK); + g_signal_connect(G_OBJECT(entry_gfhigh), "event", G_CALLBACK(gfhigh_edit), NULL); + + gtk_widget_show_all(dialog); + result = gtk_dialog_run(GTK_DIALOG(dialog)); + if (result == GTK_RESPONSE_ACCEPT) { + const char *po2_threshold_text, *pn2_threshold_text, *phe_threshold_text, *mod_text, *gflow_text, *gfhigh_text; + /* Make sure to flush any modified old dive data with old units */ + update_dive(NULL); + + prefs.divelist_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font))); + set_divelist_font(prefs.divelist_font); + po2_threshold_text = gtk_entry_get_text(GTK_ENTRY(entry_po2)); + sscanf(po2_threshold_text, "%lf", &prefs.pp_graphs.po2_threshold); + pn2_threshold_text = gtk_entry_get_text(GTK_ENTRY(entry_pn2)); + sscanf(pn2_threshold_text, "%lf", &prefs.pp_graphs.pn2_threshold); + phe_threshold_text = gtk_entry_get_text(GTK_ENTRY(entry_phe)); + sscanf(phe_threshold_text, "%lf", &prefs.pp_graphs.phe_threshold); + mod_text = gtk_entry_get_text(GTK_ENTRY(entry_mod)); + sscanf(mod_text, "%lf", &prefs.mod_ppO2); + gflow_text = gtk_entry_get_text(GTK_ENTRY(entry_gflow)); + sscanf(gflow_text, "%lf", &prefs.gflow); + gfhigh_text = gtk_entry_get_text(GTK_ENTRY(entry_gfhigh)); + sscanf(gfhigh_text, "%lf", &prefs.gfhigh); + prefs.gflow /= 100.0; + prefs.gfhigh /= 100.0; + set_gf(prefs.gflow, prefs.gfhigh); + + update_screen(); + + new_default = strdup(gtk_button_get_label(GTK_BUTTON(xmlfile_button))); + + /* if we opened the default file and are changing its name, + * update existing_filename */ + if (existing_filename) { + if (strcmp(current_default, existing_filename) == 0) { + free((void *)existing_filename); + existing_filename = strdup(new_default); + } + } + if (strcmp(current_default, new_default)) { + prefs.default_filename = new_default; + } +#if HAVE_OSM_GPS_MAP + /* get the map provider selected */ + int i; +#if GTK_CHECK_VERSION(2,24,0) + char *provider = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(map_provider)); +#else + char *provider = gtk_combo_box_get_active_text(GTK_COMBO_BOX(map_provider)); +#endif + for (i = OSM_GPS_MAP_SOURCE_OPENSTREETMAP; i <= OSM_GPS_MAP_SOURCE_YAHOO_STREET; i++) + if (!strcmp(provider,osm_gps_map_source_get_friendly_name((OsmGpsMapSource_t)i))) { + prefs.map_provider = i; + break; + } + free((void *)provider); +#endif + save_preferences(); + } else if (result == GTK_RESPONSE_CANCEL) { + prefs = oldprefs; + set_gf(prefs.gflow, prefs.gfhigh); + update_screen(); + } + gtk_widget_destroy(dialog); +} + +static void create_toggle(const char* label, int *on, void *_data) +{ + GtkWidget *button, *table = GTK_WIDGET(_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, *label; + + dialog = gtk_dialog_new_with_buttons(_("Select Events"), + 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); + if (!evn_foreach(&create_toggle, table)) { + g_object_ref_sink(G_OBJECT(table)); + label = gtk_label_new(_("\nNo Events\n")); + gtk_container_add(GTK_CONTAINER(frame), label); + } else { + gtk_container_add(GTK_CONTAINER(frame), table); + } + + gtk_widget_show_all(dialog); + result = gtk_dialog_run(GTK_DIALOG(dialog)); + if (result == GTK_RESPONSE_ACCEPT) { + repaint_dive(); + } + gtk_widget_destroy(dialog); +} + +static void autogroup_cb(GtkWidget *w, gpointer data) +{ + autogroup = !autogroup; + if (! autogroup) + remove_autogen_trips(); + dive_list_update_dives(); +} + +void set_autogroup(gboolean value) +{ + GtkAction *autogroup_action; + + if (value == autogroup) + return; + + autogroup_action = gtk_action_group_get_action(action_group, "Autogroup"); + gtk_action_activate(autogroup_action); +} + +static void renumber_dialog(GtkWidget *w, gpointer data) +{ + int result; + struct dive *dive; + GtkWidget *dialog, *frame, *button, *vbox; + + dialog = gtk_dialog_new_with_buttons(_("Renumber"), + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + + frame = gtk_frame_new(_("New starting number")); + 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) { + int nr = gtk_spin_button_get_value(GTK_SPIN_BUTTON(button)); + renumber_dives(nr); + repaint_dive(); + } + gtk_widget_destroy(dialog); +} + +static void about_dialog_link_cb(GtkAboutDialog *dialog, const gchar *link, gpointer data) +{ + subsurface_launch_for_uri(link); +} + +static void about_dialog(GtkWidget *w, gpointer data) +{ + const char *logo_property = NULL; + GdkPixbuf *logo = NULL; + GtkWidget *dialog; + + if (need_icon) { + logo_property = "logo"; + logo = gdk_pixbuf_from_pixdata(&subsurface_icon_pixbuf, TRUE, NULL); + } + dialog = gtk_about_dialog_new(); +#if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 24) + gtk_about_dialog_set_url_hook(about_dialog_link_cb, NULL, NULL); /* deprecated since GTK 2.24 */ +#else + g_signal_connect(GTK_ABOUT_DIALOG(dialog), "activate-link", G_CALLBACK(about_dialog_link_cb), NULL); +#endif + g_object_set(GTK_OBJECT(dialog), + "title", _("About Subsurface"), + "program-name", "Subsurface", + "comments", _("Multi-platform divelog software in C"), + "website", "http://subsurface.hohndel.org", + "license", "GNU General Public License, version 2\nhttp://www.gnu.org/licenses/old-licenses/gpl-2.0.html", + "version", VERSION_STRING, + "copyright", _("Linus Torvalds, Dirk Hohndel, and others, 2011, 2012, 2013"), + /*++GETTEXT the term translator-credits is magic - list the names of the tranlators here */ + "translator_credits", _("translator-credits"), + "logo-icon-name", "subsurface", + /* Must be last: */ + logo_property, logo, + NULL); + if (logo) + g_object_unref(logo); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + +static void show_user_manual(GtkWidget *w, gpointer data) +{ + subsurface_launch_for_uri("http://subsurface.hohndel.org/documentation/user-manual/"); +} + +static void view_list(GtkWidget *w, gpointer data) +{ + save_pane_position(); + gtk_paned_set_position(GTK_PANED(vpane), 0); + pane_conf = PANE_LIST; +} + +static void view_profile(GtkWidget *w, gpointer data) +{ + save_pane_position(); + gtk_paned_set_position(GTK_PANED(hpane), 0); + gtk_paned_set_position(GTK_PANED(vpane), 65535); + pane_conf = PANE_PROFILE; +} + +static void view_info(GtkWidget *w, gpointer data) +{ + + save_pane_position(); + gtk_paned_set_position(GTK_PANED(vpane), 65535); + gtk_paned_set_position(GTK_PANED(hpane), 65535); + pane_conf = PANE_INFO; +} + +static void view_three(GtkWidget *w, gpointer data) +{ + GtkAllocation alloc; + GtkRequisition requisition; + + int vpane_position = subsurface_get_conf_int("vpane_position"); + int hpane_position = subsurface_get_conf_int("hpane_position"); + + gtk_widget_get_allocation(hpane, &alloc); + + if (hpane_position) + gtk_paned_set_position(GTK_PANED(hpane), hpane_position); + else + gtk_paned_set_position(GTK_PANED(hpane), alloc.width/2); + + gtk_widget_get_allocation(vpane, &alloc); + gtk_widget_size_request(notebook, &requisition); + /* pick the requested size for the notebook plus 6 pixels for frame */ + if (vpane_position) + gtk_paned_set_position(GTK_PANED(vpane), vpane_position); + else + gtk_paned_set_position(GTK_PANED(vpane), requisition.height + 6); + + pane_conf = PANE_THREE; +} + +static void toggle_zoom(GtkWidget *w, gpointer data) +{ + zoomed_plot = (zoomed_plot)?0 : 1; + /*Update dive*/ + repaint_dive(); +} + +static void prev_dc(GtkWidget *w, gpointer data) +{ + dc_number--; + /* If the dc number underflows, we'll "wrap around" and use the last dc */ + repaint_dive(); +} + +static void next_dc(GtkWidget *w, gpointer data) +{ + dc_number++; + /* If the dc number overflows, we'll "wrap around" and zero it */ + repaint_dive(); +} + + +/* list columns for nickname edit treeview */ +enum { + NE_MODEL, + NE_ID_STR, + NE_NICKNAME, + NE_NCOL +}; + +/* delete a selection of nicknames */ +static void edit_dc_delete_rows(GtkTreeView *view) +{ + GtkTreeModel *model; + GtkTreePath *path; + GtkTreeIter iter; + GtkTreeRowReference *ref; + GtkTreeSelection *selection; + GList *selected_rows, *list, *row_references = NULL; + guint len; + /* params for delete op */ + const char *model_str; + const char *deviceid_string; /* convert to deviceid */ + uint32_t deviceid; + + selection = gtk_tree_view_get_selection(view); + selected_rows = gtk_tree_selection_get_selected_rows(selection, &model); + + for (list = selected_rows; list; list = g_list_next(list)) { + path = (GtkTreePath *)list->data; + ref = gtk_tree_row_reference_new(model, path); + row_references = g_list_append(row_references, ref); + } + len = g_list_length(row_references); + if (len == 0) + /* Warn about empty selection? */ + return; + + for (list = row_references; list; list = g_list_next(list)) { + path = gtk_tree_row_reference_get_path((GtkTreeRowReference *)(list->data)); + gtk_tree_model_get_iter(model, &iter, path); + gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, + NE_MODEL, &model_str, + NE_ID_STR, &deviceid_string, + -1); + if (sscanf(deviceid_string, "0x%x8", &deviceid) == 1) + remove_dc(model_str, deviceid); + + gtk_list_store_remove(GTK_LIST_STORE (model), &iter); + gtk_tree_path_free(path); + } + g_list_free(selected_rows); + g_list_free(row_references); + g_list_free(list); + + if (gtk_tree_model_get_iter_first(model, &iter)) + gtk_tree_selection_select_iter(selection, &iter); +} + +/* repopulate the edited nickname cell of a DC */ +static void cell_edited_cb(GtkCellRendererText *cell, gchar *path, + gchar *new_text, gpointer store) +{ + GtkTreeIter iter; + const char *model; + const char *deviceid_string; + uint32_t deviceid; + int matched; + + gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, path); + /* display new text */ + gtk_list_store_set(GTK_LIST_STORE(store), &iter, NE_NICKNAME, new_text, -1); + /* and new_text */ + gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, + NE_MODEL, &model, + NE_ID_STR, &deviceid_string, + -1); + /* extract deviceid */ + matched = sscanf(deviceid_string, "0x%x8", &deviceid); + + /* remember pending commit + * Need to extend list rather than wipe and store only one result */ + if (matched == 1){ + if (holdnicknames == NULL){ + holdnicknames = (struct device_info *) malloc(sizeof(struct device_info)); + holdnicknames->model = strdup(model); + holdnicknames->deviceid = deviceid; + holdnicknames->serial_nr = NULL; + holdnicknames->firmware = NULL; + holdnicknames->nickname = strdup(new_text); + holdnicknames->next = NULL; + } else { + struct device_info * top; + struct device_info * last = holdnicknames; + top = (struct device_info *) malloc(sizeof(struct device_info)); + top->model = strdup(model); + top->deviceid = deviceid; + top->serial_nr = NULL; + top->firmware = NULL; + top->nickname = strdup(new_text); + top->next = last; + holdnicknames = top; + } + } +} + +#define SUB_RESPONSE_DELETE 1 /* no delete response in gtk+2 */ +#define SUB_DONE 2 /* enable escape when done */ + +/* show the dialog to edit dc nicknames */ +static void edit_dc_nicknames(GtkWidget *w, gpointer data) +{ + const gchar *C_INACTIVE = "#e8e8ee", *C_ACTIVE = "#ffffff"; /* cell colours */ + GtkWidget *dialog, *confirm, *view, *scroll, *vbox; + GtkCellRenderer *renderer; + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkListStore *store; + GtkTreeIter iter; + gint res = -1; + char id_string[11] = {0}; + struct device_info * nnl; + + dialog = gtk_dialog_new_with_buttons(_("Edit Dive Computer Nicknames"), + GTK_WINDOW(main_window), + GtkDialogFlags(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), + GTK_STOCK_DELETE, + SUB_RESPONSE_DELETE, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, + GTK_STOCK_APPLY, + GTK_RESPONSE_APPLY, + NULL); + gtk_widget_set_size_request(dialog, 700, 400); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + + view = gtk_tree_view_new(); + store = gtk_list_store_new(NE_NCOL, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + model = GTK_TREE_MODEL(store); + + /* columns */ + renderer = gtk_cell_renderer_text_new(); + g_object_set(renderer, "background", C_INACTIVE, NULL); + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, _("Model"), + renderer, "text", NE_MODEL, NULL); + + renderer = gtk_cell_renderer_text_new(); + g_object_set(renderer, "background", C_INACTIVE, NULL); + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, _("Device Id"), + renderer, "text", NE_ID_STR, NULL); + + renderer = gtk_cell_renderer_text_new(); + g_object_set(renderer, "background", C_INACTIVE, NULL); + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, _("Nickname"), + renderer, "text", NE_NICKNAME, NULL); + g_object_set(renderer, "editable", TRUE, NULL); + g_object_set(renderer, "background", C_ACTIVE, NULL); + g_signal_connect(renderer, "edited", G_CALLBACK(cell_edited_cb), store); + + gtk_tree_view_set_model(GTK_TREE_VIEW(view), model); + g_object_unref(model); + + /* populate list store from device_info_list */ + nnl = head_of_device_info_list(); + while (nnl) { + sprintf(&id_string[0], "%#08x", nnl->deviceid); + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, + NE_MODEL, nnl->model, + NE_ID_STR, id_string, + NE_NICKNAME, nnl->nickname, + -1); + nnl = nnl->next; + } + + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); + gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_SINGLE); + + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_container_add(GTK_CONTAINER(scroll), view); + gtk_container_add(GTK_CONTAINER(vbox), + gtk_label_new(_("Edit a dive computer nickname by double-clicking the in the relevant nickname field"))); + gtk_container_add(GTK_CONTAINER(vbox), scroll); + gtk_widget_set_size_request(scroll, 500, 300); + gtk_widget_show_all(dialog); + + do { + res = gtk_dialog_run(GTK_DIALOG(dialog)); + if (res == SUB_RESPONSE_DELETE) { + confirm = gtk_dialog_new_with_buttons(_("Delete a dive computer information entry"), + GTK_WINDOW(dialog), + GtkDialogFlags(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), + GTK_STOCK_YES, + GTK_RESPONSE_YES, + GTK_STOCK_NO, + GTK_RESPONSE_NO, + NULL); + gtk_widget_set_size_request(confirm, 350, 90); + vbox = gtk_dialog_get_content_area(GTK_DIALOG(confirm)); + gtk_container_add(GTK_CONTAINER(vbox), + gtk_label_new(_("Ok to delete the selected entry?"))); + gtk_widget_show_all(confirm); + if (gtk_dialog_run(GTK_DIALOG(confirm)) == GTK_RESPONSE_YES) { + edit_dc_delete_rows(GTK_TREE_VIEW(view)); + res = SUB_DONE; /* want to close ** both ** dialogs now */ + } + mark_divelist_changed(TRUE); + gtk_widget_destroy(confirm); + } + if (res == GTK_RESPONSE_APPLY && holdnicknames && holdnicknames->model != NULL) { + struct device_info * walk = holdnicknames; + struct device_info * release = holdnicknames; + struct device_info * track = holdnicknames->next; + while (walk) { + remember_dc(walk->model, walk->deviceid, walk->nickname); + walk = walk->next; + } + /* clear down list */ + while (release){ + free(release); + release = track; + if (track) + track = track->next; + } + holdnicknames = NULL; + mark_divelist_changed(TRUE); + } + } while (res != SUB_DONE && res != GTK_RESPONSE_CANCEL && res != GTK_RESPONSE_DELETE_EVENT && res != GTK_RESPONSE_APPLY); + gtk_widget_destroy(dialog); +} + +static GtkActionEntry menu_items[] = { + { "FileMenuAction", NULL, N_("File"), NULL, NULL, NULL}, + { "LogMenuAction", NULL, N_("Log"), NULL, NULL, NULL}, + { "ViewMenuAction", NULL, N_("View"), NULL, NULL, NULL}, + { "FilterMenuAction", NULL, N_("Filter"), NULL, NULL, NULL}, + { "PlannerMenuAction", NULL, N_("Planner"), NULL, NULL, NULL}, + { "HelpMenuAction", NULL, N_("Help"), NULL, NULL, NULL}, + { "NewFile", GTK_STOCK_NEW, N_("New"), CTRLCHAR "N", NULL, G_CALLBACK(file_close) }, + { "OpenFile", GTK_STOCK_OPEN, N_("Open..."), CTRLCHAR "O", NULL, G_CALLBACK(file_open) }, + { "SaveFile", GTK_STOCK_SAVE, N_("Save..."), CTRLCHAR "S", NULL, G_CALLBACK(file_save) }, + { "SaveAsFile", GTK_STOCK_SAVE_AS, N_("Save As..."), SHIFTCHAR CTRLCHAR "S", NULL, G_CALLBACK(file_save_as) }, + { "CloseFile", GTK_STOCK_CLOSE, N_("Close"), NULL, NULL, G_CALLBACK(file_close) }, + { "Print", GTK_STOCK_PRINT, N_("Print..."), CTRLCHAR "P", NULL, G_CALLBACK(do_print) }, + { "ImportFile", NULL, N_("Import File(s)..."), CTRLCHAR "I", NULL, G_CALLBACK(import_files) }, + { "ExportUDDF", NULL, N_("Export UDDF..."), NULL, NULL, G_CALLBACK(export_all_dives_uddf_cb) }, + { "DownloadLog", NULL, N_("Download From Dive Computer..."), CTRLCHAR "D", NULL, G_CALLBACK(download_dialog) }, + { "DownloadWeb", GTK_STOCK_CONNECT, N_("Download From Web Service..."), NULL, NULL, G_CALLBACK(webservice_download_dialog) }, + { "AddDive", GTK_STOCK_ADD, N_("Add Dive..."), NULL, NULL, G_CALLBACK(add_dive_cb) }, + { "Preferences", GTK_STOCK_PREFERENCES, N_("Preferences..."), PREFERENCE_ACCEL, NULL, G_CALLBACK(preferences_dialog) }, + { "Renumber", NULL, N_("Renumber..."), NULL, NULL, G_CALLBACK(renumber_dialog) }, + { "YearlyStats", NULL, N_("Yearly Statistics"), NULL, NULL, G_CALLBACK(show_yearly_stats) }, +#if HAVE_OSM_GPS_MAP + { "DivesLocations", NULL, N_("Dives Locations"), CTRLCHAR "M", NULL, G_CALLBACK(show_gps_locations) }, +#endif + { "SelectEvents", NULL, N_("Select Events..."), NULL, NULL, G_CALLBACK(selectevents_dialog) }, + { "Quit", GTK_STOCK_QUIT, N_("Quit"), CTRLCHAR "Q", NULL, G_CALLBACK(quit) }, + { "About", GTK_STOCK_ABOUT, N_("About Subsurface"), NULL, NULL, G_CALLBACK(about_dialog) }, + { "UserManual", GTK_STOCK_HELP, N_("User Manual"), NULL, NULL, G_CALLBACK(show_user_manual) }, + { "ViewList", NULL, N_("List"), CTRLCHAR "1", NULL, G_CALLBACK(view_list) }, + { "ViewProfile", NULL, N_("Profile"), CTRLCHAR "2", NULL, G_CALLBACK(view_profile) }, + { "ViewInfo", NULL, N_("Info"), CTRLCHAR "3", NULL, G_CALLBACK(view_info) }, + { "ViewThree", NULL, N_("Three"), CTRLCHAR "4", NULL, G_CALLBACK(view_three) }, + { "EditNames", NULL, N_("Edit Device Names"), CTRLCHAR "E", NULL, G_CALLBACK(edit_dc_nicknames) }, + { "PrevDC", NULL, N_("Prev DC"), NULL, NULL, G_CALLBACK(prev_dc) }, + { "NextDC", NULL, N_("Next DC"), NULL, NULL, G_CALLBACK(next_dc) }, + { "InputPlan", NULL, N_("Input Plan"), NULL, NULL, G_CALLBACK(input_plan) }, +}; +static gint nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]); + +static GtkToggleActionEntry toggle_items[] = { + { "Autogroup", NULL, N_("Autogroup"), NULL, NULL, G_CALLBACK(autogroup_cb), FALSE }, + { "ToggleZoom", NULL, N_("Toggle Zoom"), CTRLCHAR "0", NULL, G_CALLBACK(toggle_zoom), FALSE }, +}; +static gint ntoggle_items = sizeof (toggle_items) / sizeof (toggle_items[0]); + +static const gchar* ui_string = " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " +#if HAVE_OSM_GPS_MAP + " " +#endif + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +"; + +static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager) +{ + action_group = gtk_action_group_new("Menu"); + gtk_action_group_set_translation_domain(action_group, "subsurface"); + gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0); + toggle_items[0].is_active = autogroup; + gtk_action_group_add_toggle_actions(action_group, toggle_items, ntoggle_items, 0); + + gtk_ui_manager_insert_action_group(ui_manager, action_group, 0); + GError* error = 0; + gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error); + + gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager)); + GtkWidget* menu = gtk_ui_manager_get_widget(ui_manager, "/MainMenu"); + + return menu; +} + +static void switch_page(GtkNotebook *notebook, gint arg1, gpointer user_data) +{ + repaint_dive(); +} + +static gboolean on_key_press(GtkWidget *w, GdkEventKey *event, GtkWidget *divelist) +{ + if (event->type != GDK_KEY_PRESS || event->state != 0) + return FALSE; + switch (event->keyval) { + case GDK_Up: + select_prev_dive(); + return TRUE; + case GDK_Down: + select_next_dive(); + return TRUE; + case GDK_Left: + prev_dc(NULL, NULL); + return TRUE; + case GDK_Right: + next_dc(NULL, NULL); + return TRUE; + } + return FALSE; +} + +static gboolean notebook_tooltip (GtkWidget *widget, gint x, gint y, + gboolean keyboard_mode, GtkTooltip *tooltip, gpointer data) +{ + if (amount_selected > 0 && gtk_notebook_get_current_page(GTK_NOTEBOOK(widget)) == 0) { + gtk_tooltip_set_text(tooltip, _("To edit dive information\ndouble click on it in the dive list")); + return TRUE; + } else { + return FALSE; + } +} + +#if NEEDS_TO_MOVE_TO_QT_UI +/* this appears to have moved - but it's very different in qt-ui */ + +class MainWindow: public QMainWindow, private Ui::MainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = 0); + ~MainWindow() {} + + void setCurrentFileName(const QString &fileName); + +private Q_SLOTS: + void on_actionNew_triggered() { on_actionClose_triggered(); } + void on_actionOpen_triggered(); + void on_actionSave_triggered() { file_save(NULL, NULL); } + void on_actionSaveAs_triggered() { file_save_as(NULL, NULL); } + void on_actionClose_triggered(); + +private: + QStringList fileNameFilters() const; + +private: + QString m_currentFileName; +}; + +MainWindow::MainWindow(QWidget *parent): + QMainWindow(parent) +{ + setupUi(this); +} + +void MainWindow::setCurrentFileName(const QString &fileName) +{ + if (fileName == m_currentFileName) return; + m_currentFileName = fileName; + + QString title = tr("Subsurface"); + if (!m_currentFileName.isEmpty()) { + QFileInfo fileInfo(m_currentFileName); + title += " - " + fileInfo.fileName(); + } + setWindowTitle(title); +} + +void MainWindow::on_actionOpen_triggered() +{ + QString defaultFileName = prefs.default_filename; + QFileInfo fileInfo(defaultFileName); + + QFileDialog dialog(this, tr("Open File"), fileInfo.path()); + dialog.setFileMode(QFileDialog::ExistingFile); + dialog.selectFile(defaultFileName); + dialog.setNameFilters(fileNameFilters()); + if (dialog.exec()) { + /* first, close the existing file, if any */ + file_close(NULL, NULL); + + /* we know there is only one filename */ + QString fileName = dialog.selectedFiles().first(); + GError *error = NULL; + parse_file(fileName.toUtf8().constData(), &error); + if (error != NULL) { + report_error(error); + g_error_free(error); + error = NULL; + } else { + setCurrentFileName(fileName); + } + report_dives(FALSE, FALSE); + } +} + +void MainWindow::on_actionClose_triggered() +{ + if (unsaved_changes()) + if (ask_save_changes() == FALSE) + return; + + setCurrentFileName(QString()); + + /* free the dives and trips */ + while (dive_table.nr) + delete_single_dive(0); + mark_divelist_changed(FALSE); + + /* clear the selection and the statistics */ + selected_dive = 0; + process_selected_dives(); + clear_stats_widgets(); + clear_events(); + show_dive_stats(NULL); + + /* clear the equipment page */ + clear_equipment_widgets(); + + /* redraw the screen */ + dive_list_update_dives(); + show_dive_info(NULL); +} + +QStringList MainWindow::fileNameFilters() const +{ + QStringList filters; + + filters << "*.xml *.uddf *.udcf *.jlb" +#ifdef LIBZIP + " *.sde *.dld" +#endif +#ifdef SQLITE3 + " *.db" +#endif + ; + return filters; +} +#endif /* NEEDS_TO_MOVE_TO_QT_UI */ + +void init_qt_ui(int *argcp, char ***argvp) +{ + application->installTranslator(new Translator(application)); + MainWindow *window = new MainWindow(); + window->show(); +} + +void init_ui(int *argcp, char ***argvp) +{ + application = new QApplication(*argcp, *argvp); + +#if QT_VERSION < 0x050000 + // ask QString in Qt 4 to interpret all char* as UTF-8, + // like Qt 5 does. + // 106 is "UTF-8", this is faster than lookup by name + // [http://www.iana.org/assignments/character-sets/character-sets.xml] + QTextCodec::setCodecForCStrings(QTextCodec::codecForMib(106)); +#endif + + GtkWidget *win; + GtkWidget *nb_page; + GtkWidget *dive_list; + GtkWidget *menubar; + GtkWidget *vbox; + GtkWidget *scrolled; + GdkScreen *screen; + GtkIconTheme *icon_theme=NULL; + GtkSettings *settings; + GtkUIManager *ui_manager; + + gtk_init(argcp, argvp); + settings = gtk_settings_get_default(); + gtk_settings_set_long_property(settings, "gtk-tooltip-timeout", 10, "subsurface setting"); + gtk_settings_set_long_property(settings, "gtk-menu-images", 1, "subsurface setting"); + gtk_settings_set_long_property(settings, "gtk-button-images", 1, "subsurface setting"); + + /* check if utf8 stars are available as a default OS feature */ + if (!subsurface_os_feature_available(UTF8_FONT_WITH_STARS)) { + star_strings[0] = " "; + star_strings[1] = "* "; + star_strings[2] = "** "; + star_strings[3] = "*** "; + star_strings[4] = "**** "; + star_strings[5] = "*****"; + } +#if !GLIB_CHECK_VERSION(2,3,6) + g_type_init(); +#endif + subsurface_open_conf(); + + load_preferences(); + + default_dive_computer_vendor = subsurface_get_conf("dive_computer_vendor"); + default_dive_computer_product = subsurface_get_conf("dive_computer_product"); + default_dive_computer_device = subsurface_get_conf("dive_computer_device"); + error_info_bar = NULL; + win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + 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) { + const char *icon_name = subsurface_icon_name(); + if (!access(icon_name, R_OK)) + gtk_window_set_icon_from_file(GTK_WINDOW(win), icon_name, NULL); + } + 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); + g_signal_connect(G_OBJECT(win), "window-state-event", G_CALLBACK(on_state), NULL); + main_window = win; + + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(win), vbox); + main_vbox = vbox; + + ui_manager = gtk_ui_manager_new(); + menubar = get_menubar_menu(win, ui_manager); + + subsurface_ui_setup(settings, menubar, vbox, ui_manager); + + vpane = gtk_vpaned_new(); + gtk_box_pack_start(GTK_BOX(vbox), vpane, TRUE, TRUE, 3); + hpane = gtk_hpaned_new(); + gtk_paned_add1(GTK_PANED(vpane), hpane); + g_signal_connect_after(G_OBJECT(vbox), "realize", G_CALLBACK(view_three), NULL); + + /* Notebook for dive info vs profile vs .. */ + notebook = gtk_notebook_new(); + scrolled = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_paned_add1(GTK_PANED(hpane), scrolled); + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled), notebook); + g_signal_connect(notebook, "switch-page", G_CALLBACK(switch_page), NULL); + + /* Create the actual divelist */ + dive_list = dive_list_create(); + gtk_widget_set_name(dive_list, "Dive List"); + gtk_paned_add2(GTK_PANED(vpane), dive_list); + + /* Frame for dive profile */ + dive_profile = dive_profile_widget(); + gtk_widget_set_name(dive_profile, "Dive Profile"); + gtk_paned_add2(GTK_PANED(hpane), dive_profile); + + /* Frame for extended dive info */ + nb_page = extended_dive_info_widget(); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new(_("Dive Notes"))); + + /* Frame for dive equipment */ + nb_page = equipment_widget(W_IDX_PRIMARY); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new(_("Equipment"))); + + /* Frame for single dive statistics */ + nb_page = single_stats_widget(); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new(_("Dive Info"))); + + /* Frame for total dive statistics */ + nb_page = total_stats_widget(); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new(_("Stats"))); + + /* add tooltip that tells people how to edit things */ + g_object_set(notebook, "has-tooltip", TRUE, NULL); + g_signal_connect(notebook, "query-tooltip", G_CALLBACK(notebook_tooltip), NULL); + + /* handle some keys globally (to deal with gtk focus issues) */ + g_signal_connect (G_OBJECT (win), "key_press_event", G_CALLBACK (on_key_press), dive_list); + + gtk_widget_set_app_paintable(win, TRUE); + restore_window_geometry(); + gtk_widget_show_all(win); + + return; +} + +void run_ui(void) +{ + application->exec(); +} + +void exit_ui(void) +{ + delete application; + subsurface_close_conf(); + if (existing_filename) + free((void *)existing_filename); + if (default_dive_computer_device) + free((void *)default_dive_computer_device); +} + +typedef struct { + cairo_rectangle_t rect; + const char *text; + struct event *event; +} 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, struct event *event) +{ + cairo_rectangle_t *rect; + tooltip_rects = (tooltip_record_t *) + 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 = strdup(text); + tooltip_rects[tooltips].event = event; + tooltips++; +} + +#define INSIDE_RECT(_r,_x,_y) ((_r.x <= _x) && (_r.x + _r.width >= _x) && \ + (_r.y <= _y) && (_r.y + _r.height >= _y)) +#define INSIDE_RECT_X(_r, _x) ((_r.x <= _x) && (_r.x + _r.width >= _x)) + +static gboolean profile_tooltip (GtkWidget *widget, gint x, gint y, + gboolean keyboard_mode, GtkTooltip *tooltip, struct graphics_context *gc) +{ + int i; + cairo_rectangle_t *drawing_area = &gc->drawing_area; + gint tx = x - drawing_area->x; /* get transformed coordinates */ + gint ty = y - drawing_area->y; + gint width, height, time = -1; + char buffer[2048], plot[1024]; + const char *event = ""; + + if (tx < 0 || ty < 0) + return FALSE; + + /* don't draw a tooltip if nothing is there */ + if (amount_selected == 0 || gc->pi.nr == 0) + return FALSE; + + width = drawing_area->width - 2*drawing_area->x; + height = drawing_area->height - 2*drawing_area->y; + if (width <= 0 || height <= 0) + return FALSE; + + if (tx > width || ty > height) + return FALSE; + + time = (tx * gc->maxtime) / width; + + /* are we over an event marker ? */ + for (i = 0; i < tooltips; i++) { + if (INSIDE_RECT(tooltip_rects[i].rect, tx, ty)) { + event = tooltip_rects[i].text; + break; + } + } + get_plot_details(gc, time, plot, sizeof(plot)); + + snprintf(buffer, sizeof(buffer), "@ %d:%02d%c%s%c%s", time / 60, time % 60, + *plot ? '\n' : ' ', plot, + *event ? '\n' : ' ', event); + gtk_tooltip_set_text(tooltip, buffer); + return TRUE; + +} + +static double zoom_factor = 1.0; +static int zoom_x = -1, zoom_y = -1; + +static void common_drawing_function(GtkWidget *widget, struct graphics_context *gc) +{ + int i = 0; + struct dive *dive = current_dive; + + gc->drawing_area.x = MIN(50,gc->drawing_area.width / 20.0); + gc->drawing_area.y = MIN(50,gc->drawing_area.height / 20.0); + + g_object_set(widget, "has-tooltip", TRUE, NULL); + g_signal_connect(widget, "query-tooltip", G_CALLBACK(profile_tooltip), gc); + init_profile_background(gc); + cairo_paint(gc->cr); + + if (zoom_factor > 1.0) { + double n = -(zoom_factor-1); + cairo_translate(gc->cr, n*zoom_x, n*zoom_y); + cairo_scale(gc->cr, zoom_factor, zoom_factor); + } + + if (dive) { + if (tooltip_rects) { + while (i < tooltips) { + if (tooltip_rects[i].text) + free((void *)tooltip_rects[i].text); + i++; + } + free(tooltip_rects); + tooltip_rects = NULL; + } + tooltips = 0; + plot(gc, dive, SC_SCREEN); + } +} + +#if GTK_CHECK_VERSION(3,0,0) + +static gboolean draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) +{ + guint width, height; + static struct graphics_context gc = { .printer = 0 }; + + width = gtk_widget_get_allocated_width(widget); + height = gtk_widget_get_allocated_height(widget); + + gc.drawing_area.width = width; + gc.drawing_area.height = height; + gc.cr = cr; + + common_drawing_function(widget, &gc); + return FALSE; +} + +#else /* gtk2 */ + +static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data) +{ + GtkAllocation allocation; + static struct graphics_context gc = { 0 }; + + /* 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 */ + gtk_widget_get_allocation(widget, &allocation); + gc.drawing_area.width = allocation.width; + gc.drawing_area.height = allocation.height; + gc.cr = gdk_cairo_create(gtk_widget_get_window(widget)); + + common_drawing_function(widget, &gc); + cairo_destroy(gc.cr); + return FALSE; +} + +#endif + +static void zoom_event(int x, int y, double inc) +{ + zoom_x = x; + zoom_y = y; + inc += zoom_factor; + if (inc < 1.0) + inc = 1.0; + else if (inc > 10) + inc = 10; + zoom_factor = inc; +} + +static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer user_data) +{ + switch (event->direction) { + case GDK_SCROLL_UP: + zoom_event(event->x, event->y, 0.1); + break; + case GDK_SCROLL_DOWN: + zoom_event(event->x, event->y, -0.1); + break; + default: + return TRUE; + } + gtk_widget_queue_draw(widget); + return TRUE; +} + +static void add_gas_change_cb(GtkWidget *menuitem, gpointer data) +{ + double *x = (double *)data; + int when = x_to_time(*x); + int cylnr = select_cylinder(current_dive, when); + if (cylnr >= 0) { + cylinder_t *cyl = ¤t_dive->cylinder[cylnr]; + int value = cyl->gasmix.o2.permille / 10 | ((cyl->gasmix.he.permille / 10) << 16); + add_event(current_dc, when, 25, 0, value, "gaschange"); + mark_divelist_changed(TRUE); + report_dives(FALSE, FALSE); + dive_list_update_dives(); + } +} + +int confirm_dialog(int when, char *action_text, char *event_text) +{ + GtkWidget *dialog, *vbox, *label; + int confirmed; + char title[80]; + + snprintf(title, sizeof(title), "%s %s", action_text, event_text); + dialog = gtk_dialog_new_with_buttons(title, + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + label = create_label(_("%s event at %d:%02u"), title, FRACTION(when, 60)); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); + gtk_widget_show_all(dialog); + confirmed = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; + + gtk_widget_destroy(dialog); + + return confirmed; +} + +static void add_bookmark_cb(GtkWidget *menuitem, gpointer data) +{ + double *x = (double *)data; + int when = x_to_time(*x); + + if (confirm_dialog(when, _("Add"), _("bookmark"))){ + add_event(current_dc, when, 8, 0, 0, "bookmark"); + mark_divelist_changed(TRUE); + report_dives(FALSE, FALSE); + } +} + +static struct event *event_at_x(double rel_x) +{ + /* is there an event marker at this x coordinate */ + struct event *ret = NULL; + int i; + int x = x_abs(rel_x); + + for (i = 0; i < tooltips; i++) { + if (INSIDE_RECT_X(tooltip_rects[i].rect, x)) { + ret = tooltip_rects[i].event; + break; + } + } + return ret; +} + +static void remove_event_cb(GtkWidget *menuitem, gpointer data) +{ + struct event *event = (struct event *)data; + if (confirm_dialog(event->time.seconds, _("Remove"), _(event->name))){ + struct event **ep = ¤t_dc->events; + while (ep && *ep != event) + ep = &(*ep)->next; + if (ep) { + *ep = event->next; + free(event); + } + mark_divelist_changed(TRUE); + report_dives(FALSE, FALSE); + } +} + +static void popup_profile_menu(GtkWidget *widget, GdkEventButton *gtk_event) +{ + GtkWidget *menu, *menuitem, *image; + static double x; + struct event *event; + + if (!gtk_event || !current_dive) + return; + x = gtk_event->x; + menu = gtk_menu_new(); + menuitem = gtk_image_menu_item_new_with_label(_("Add gas change event here")); + image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); + g_signal_connect(menuitem, "activate", G_CALLBACK(add_gas_change_cb), &x); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + menuitem = gtk_image_menu_item_new_with_label(_("Add bookmark event here")); + image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); + g_signal_connect(menuitem, "activate", G_CALLBACK(add_bookmark_cb), &x); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + if ((event = event_at_x(x)) != NULL) { + menuitem = gtk_image_menu_item_new_with_label(_("Remove event here")); + image = gtk_image_new_from_stock(GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); + g_signal_connect(menuitem, "activate", G_CALLBACK(remove_event_cb), event); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } + + gtk_widget_show_all(menu); + + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, + gtk_event->button, gtk_get_current_event_time()); + +} + +static gboolean clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data) +{ + switch (event->button) { + case 1: + zoom_x = event->x; + zoom_y = event->y; + zoom_factor = 2.5; + break; + case 3: + popup_profile_menu(widget, event); + break; + default: + return TRUE; + } + gtk_widget_queue_draw(widget); + return TRUE; +} + +static gboolean released(GtkWidget *widget, GdkEventButton *event, gpointer user_data) +{ + switch (event->button) { + case 1: + zoom_x = zoom_y = -1; + zoom_factor = 1.0; + break; + default: + return TRUE; + } + gtk_widget_queue_draw(widget); + return TRUE; +} + +static gboolean motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data) +{ + if (zoom_x < 0) + return TRUE; + + zoom_x = event->x; + zoom_y = event->y; + gtk_widget_queue_draw(widget); + return TRUE; +} + +static GtkWidget *dive_profile_widget(void) +{ + GtkWidget *da; + + da = gtk_drawing_area_new(); + gtk_widget_set_size_request(da, 350, 250); +#if GTK_CHECK_VERSION(3,0,0) + g_signal_connect(da, "draw", G_CALLBACK (draw_callback), NULL); +#else + g_signal_connect(da, "expose_event", G_CALLBACK(expose_event), NULL); +#endif + g_signal_connect(da, "button-press-event", G_CALLBACK(clicked), NULL); + g_signal_connect(da, "scroll-event", G_CALLBACK(scroll_event), NULL); + g_signal_connect(da, "button-release-event", G_CALLBACK(released), NULL); + g_signal_connect(da, "motion-notify-event", G_CALLBACK(motion), NULL); + gtk_widget_add_events(da, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK); + + return da; +} + +static void do_import_file(gpointer data, gpointer user_data) +{ + GError *error = NULL; + parse_file((const char *)data, &error); + + if (error != NULL) + { + report_error(error); + g_error_free(error); + error = NULL; + } +} + +static void import_files(GtkWidget *w, gpointer data) +{ + GtkWidget *fs_dialog; + const char *current_default; + char *current_def_dir; + GtkFileFilter *filter; + struct stat sb; + GSList *filenames = NULL; + + fs_dialog = gtk_file_chooser_dialog_new(_("Choose XML Files To Import Into Current Data File"), + GTK_WINDOW(main_window), + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + + /* I'm not sure what the best default path should be... */ + if (existing_filename) { + current_def_dir = g_path_get_dirname(existing_filename); + } else { + current_default = prefs.default_filename; + current_def_dir = g_path_get_dirname(current_default); + } + + /* it's possible that the directory doesn't exist (especially for the default) + * For gtk's file select box to make sense we create it */ + if (stat(current_def_dir, &sb) != 0) + g_mkdir(current_def_dir, S_IRWXU); + + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs_dialog), current_def_dir); + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(fs_dialog), TRUE); + filter = setup_filter(); + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fs_dialog), filter); + gtk_widget_show_all(fs_dialog); + if (gtk_dialog_run(GTK_DIALOG(fs_dialog)) == GTK_RESPONSE_ACCEPT) { + /* grab the selected file list, import each file and update the list */ + filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(fs_dialog)); + if (filenames) { + g_slist_foreach(filenames, do_import_file, NULL); + report_dives(TRUE, FALSE); + g_slist_free(filenames); + } + } + + free(current_def_dir); + gtk_widget_destroy(fs_dialog); +} + +void set_filename(const char *filename, gboolean force) +{ + if (!force && existing_filename) + return; + free((void *)existing_filename); + if (filename) + existing_filename = strdup(filename); + else + existing_filename = NULL; +} + +const char *get_dc_nickname(const char *model, uint32_t deviceid) +{ + struct device_info *known = get_device_info(model, deviceid); + if (known) { + if (known->nickname && *known->nickname) + return known->nickname; + else + return known->model; + } + return NULL; +} + +void set_dc_nickname(struct dive *dive) +{ + GtkWidget *dialog, *vbox, *entry, *frame, *label; + char nickname[160] = ""; + char dialogtext[2048]; + const char *name = nickname; + struct divecomputer *dc = &dive->dc; + + if (!dive) + return; + while (dc) { +#if NICKNAME_DEBUG & 16 + fprintf(debugfile, "set_dc_nickname for model %s deviceid %8x\n", dc->model ? : "", dc->deviceid); +#endif + if (get_dc_nickname(dc->model, dc->deviceid) == NULL) { + struct device_info *nn_entry = get_different_device_info(dc->model, dc->deviceid); + if (nn_entry) { + dialog = gtk_dialog_new_with_buttons( + _("Dive Computer Nickname"), + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + NULL); + vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + snprintf(dialogtext, sizeof(dialogtext), + _("You already have a dive computer of this model\n" + "named %s\n" + "Subsurface can maintain a nickname for this device to " + "distinguish it from the existing one. " + "The default is the model and device ID as shown below.\n" + "If you don't want to name this dive computer click " + "'Cancel' and Subsurface will simply display its model " + "as its name (which may mean that you cannot tell the two " + "dive computers apart in the logs)."), + nn_entry->nickname && *nn_entry->nickname ? nn_entry->nickname : + (nn_entry->model && *nn_entry->model ? nn_entry->model : _("(nothing)"))); + label = gtk_label_new(dialogtext); + gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 3); + frame = gtk_frame_new(_("Nickname")); + gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, TRUE, 3); + entry = gtk_entry_new(); + gtk_container_add(GTK_CONTAINER(frame), entry); + gtk_entry_set_max_length(GTK_ENTRY(entry), 68); + snprintf(nickname, sizeof(nickname), "%s (%08x)", dc->model, dc->deviceid); + gtk_entry_set_text(GTK_ENTRY(entry), nickname); + gtk_widget_show_all(dialog); + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + if (strcmp(dc->model, gtk_entry_get_text(GTK_ENTRY(entry)))) { + name = gtk_entry_get_text(GTK_ENTRY(entry)); + remember_dc(dc->model, dc->deviceid, name); + mark_divelist_changed(TRUE); + } + } else { + /* Remember that we declined the nickname */ + remember_dc(dc->model, dc->deviceid, NULL); + } + gtk_widget_destroy(dialog); + } else { + remember_dc(dc->model, dc->deviceid, NULL); + } + } + dc = dc->next; + } +} + +gdouble get_screen_dpi(void) +{ + const gdouble mm_per_inch = 25.4; + GdkScreen *scr = gdk_screen_get_default(); + gdouble h_mm = gdk_screen_get_height_mm(scr); + gdouble h = gdk_screen_get_height(scr); + gdouble dpi_h = floor((h / h_mm) * mm_per_inch); + return dpi_h; +} + +#include "qt-gui.moc" diff --git a/info-gtk.c b/info-gtk.c index ebba07b73..b386b9d4d 100644 --- a/info-gtk.c +++ b/info-gtk.c @@ -736,6 +736,14 @@ int edit_multi_dive_info(struct dive *single_dive) return success; } +int edit_dive_info(struct dive *dive, gboolean newdive) +{ + if (!dive || (!newdive && !amount_selected)) + return 0; + + return edit_multi_dive_info(dive); +} + static GtkWidget *frame_box(GtkWidget *vbox, const char *fmt, ...) { va_list ap; diff --git a/info.c b/info.c index 0abee1c1b..4e0c3cadb 100644 --- a/info.c +++ b/info.c @@ -448,10 +448,15 @@ void update_time_depth(struct dive *dive, struct dive *edited) dive->dc.meandepth.mm = edited->dc.meandepth.mm; } -int edit_dive_info(struct dive *dive, gboolean newdive) +void add_people(const char *string) { - if (!dive || (!newdive && !amount_selected)) - return 0; - - return edit_multi_dive_info(dive); + /* add names to the completion list for people */ +} +void add_location(const char *string) +{ + /* add names to the completion list for locations */ +} +void add_suit(const char *string) +{ + /* add names to the completion list for suits */ } diff --git a/libdivecomputer.c b/libdivecomputer.c index 9ad5df594..cc1c0be28 100644 --- a/libdivecomputer.c +++ b/libdivecomputer.c @@ -26,6 +26,7 @@ static double progress_bar_fraction = 0.0; static int stoptime, stopdepth, ndl, po2, cns; static gboolean in_deco, first_temp_is_air; +#if USE_GTK_UI static GError *error(const char *fmt, ...) { va_list args; @@ -38,6 +39,7 @@ static GError *error(const char *fmt, ...) va_end(args); return error; } +#endif static dc_status_t create_parser(device_data_t *devdata, dc_parser_t **parser) { @@ -708,6 +710,7 @@ static const char *do_libdivecomputer_import(device_data_t *data) return err; } +#if USE_GTK_UI static void *pthread_wrapper(void *_data) { device_data_t *data = _data; @@ -772,3 +775,4 @@ GError *do_import(device_data_t *data) return error(retval, data->vendor, data->product, data->devname); return NULL; } +#endif diff --git a/libdivecomputer.h b/libdivecomputer.h index 0950d32ae..f5b65f70f 100644 --- a/libdivecomputer.h +++ b/libdivecomputer.h @@ -17,10 +17,12 @@ typedef struct device_data_t { unsigned int deviceid, diveid; dc_device_t *device; dc_context_t *context; - progressbar_t progress; int preexisting; gboolean force_download; +#if USE_GTK_UI + progressbar_t progress; GtkDialog *dialog; +#endif } device_data_t; extern GError *do_import(device_data_t *data); diff --git a/linux.c b/linux.c index bf7383822..0c31b6c43 100644 --- a/linux.c +++ b/linux.c @@ -1,6 +1,7 @@ /* linux.c */ /* implements Linux specific functions */ #include "dive.h" +#include "display.h" #include "display-gtk.h" #include #include diff --git a/main.c b/main.c index 40492a7e1..424750d28 100644 --- a/main.c +++ b/main.c @@ -99,7 +99,11 @@ static void parse_argument(const char *arg) if (strcmp(arg,"--import") == 0) { /* mark the dives so far as the base, * everything after is imported */ +#if USE_GTK_UI report_dives(FALSE, FALSE); +#else + process_dives(FALSE, FALSE); +#endif imported = TRUE; return; } @@ -119,6 +123,7 @@ static void parse_argument(const char *arg) void update_dive(struct dive *new_dive) { +#if USE_GTK_UI static struct dive *buffered_dive; struct dive *old_dive = buffered_dive; @@ -129,6 +134,7 @@ void update_dive(struct dive *new_dive) show_dive_equipment(new_dive, W_IDX_PRIMARY); show_dive_stats(new_dive); buffered_dive = new_dive; +#endif } void renumber_dives(int nr) @@ -138,7 +144,9 @@ void renumber_dives(int nr) for (i = 0; i < dive_table.nr; i++) { struct dive *dive = dive_table.dives[i]; dive->number = nr + i; +#if USE_GTK_UI flush_divelist(dive); +#endif } mark_divelist_changed(TRUE); } @@ -229,7 +237,9 @@ int main(int argc, char **argv) } if (error != NULL) { +#if USE_GTK_UI report_error(error); +#endif g_error_free(error); error = NULL; } @@ -242,9 +252,13 @@ int main(int argc, char **argv) sure we remember this as the filename in use */ set_filename(filename, FALSE); } +#if USE_GTK_UI report_dives(imported, FALSE); if (dive_table.nr == 0) show_dive_info(NULL); +#else + process_dives(imported, FALSE); +#endif parse_xml_exit(); subsurface_command_line_exit(&argc, &argv); diff --git a/planner.c b/planner.c index 0f739b1f0..822d2e789 100644 --- a/planner.c +++ b/planner.c @@ -685,9 +685,11 @@ void plan(struct diveplan *diveplan, char **cached_datap, struct dive **divep, c stopidx--; } add_plan_to_notes(diveplan, dive); +#if USE_GTK_UI /* now make the dive visible in the dive list */ report_dives(FALSE, FALSE); show_and_select_dive(dive); +#endif error_exit: free(stoplevels); free(gaschanges); diff --git a/profile.c b/profile.c index 18960fcff..bd395f127 100644 --- a/profile.c +++ b/profile.c @@ -136,6 +136,8 @@ static const color_t profile_color[] = { }; +#if USE_GTK_UI + /* Scale to 0,0 -> maxx,maxy */ #define SCALEX(gc,x) (((x)-gc->leftx)/(gc->rightx-gc->leftx)*gc->maxx) #define SCALEY(gc,y) (((y)-gc->topy)/(gc->bottomy-gc->topy)*gc->maxy) @@ -189,8 +191,7 @@ static void pattern_add_color_stop_rgba(struct graphics_context *gc, cairo_patte struct rgba rgb = col->media[gc->printer]; cairo_pattern_add_color_stop_rgba(pat, o, rgb.r, rgb.g, rgb.b, rgb.a); } - -#define ROUND_UP(x,y) ((((x)+(y)-1)/(y))*(y)) +#endif /* USE_GTK_UI */ /* debugging tool - not normally used */ static void dump_pi (struct plot_info *pi) @@ -215,6 +216,8 @@ static void dump_pi (struct plot_info *pi) printf(" }\n"); } +#define ROUND_UP(x,y) ((((x)+(y)-1)/(y))*(y)) + /* * When showing dive profiles, we scale things to the * current dive. However, we don't scale past less than @@ -276,6 +279,7 @@ typedef struct { #define MIDDLE (0) #define BOTTOM (-1) +#if USE_GTK_UI static void plot_text(struct graphics_context *gc, const text_render_options_t *tro, double x, double y, const char *fmt, ...) { @@ -308,6 +312,7 @@ static void plot_text(struct graphics_context *gc, const text_render_options_t * set_source_rgba(gc, tro->color); cairo_show_text(cr, buffer); } +#endif /* USE_GTK_UI */ /* collect all event names and whether we display them */ struct ev_select { @@ -357,6 +362,7 @@ void remember_event(const char *eventname) evn_used++; } +#if USE_GTK_UI static void plot_one_event(struct graphics_context *gc, struct plot_info *pi, struct event *event) { int i, depth = 0; @@ -1027,6 +1033,7 @@ static void set_sac_color(struct graphics_context *gc, int sac, int avg_sac) set_source_rgba(gc, SAC_DEFAULT); } } +#endif /* USE_GTK_UI */ /* Get local sac-rate (in ml/min) between entry1 and entry2 */ static int get_local_sac(struct plot_data *entry1, struct plot_data *entry2, struct dive *dive) @@ -1065,6 +1072,7 @@ static int get_local_sac(struct plot_data *entry1, struct plot_data *entry2, str #define SAC_WINDOW 45 /* sliding window in seconds for current SAC calculation */ +#if USE_GTK_UI static void plot_cylinder_pressure(struct graphics_context *gc, struct plot_info *pi, struct dive *dive, struct divecomputer *dc) { @@ -1189,6 +1197,7 @@ static void plot_deco_text(struct graphics_context *gc, struct plot_info *pi) plot_text(gc, &tro, x, y, "GF %.0f/%.0f", prefs.gflow * 100, prefs.gfhigh * 100); } } +#endif /* USE_GTK_UI */ static void analyze_plot_info_minmax_minute(struct plot_data *entry, struct plot_data *first, struct plot_data *last, int index) { @@ -1259,6 +1268,7 @@ static velocity_t velocity(int speed) return v; } + static struct plot_info *analyze_plot_info(struct plot_info *pi) { int i; @@ -1974,6 +1984,7 @@ static struct plot_info *create_plot_info(struct dive *dive, struct divecomputer return analyze_plot_info(pi); } +#if USE_GTK_UI static void plot_set_scale(scale_mode_t scale) { switch (scale) { @@ -1986,6 +1997,7 @@ static void plot_set_scale(scale_mode_t scale) break; } } +#endif /* make sure you pass this the FIRST dc - it just walks the list */ static int nr_dcs(struct divecomputer *main) @@ -2015,6 +2027,7 @@ struct divecomputer *select_dc(struct divecomputer *main) return main; } +#if USE_GTK_UI void plot(struct graphics_context *gc, struct dive *dive, scale_mode_t scale) { struct plot_info *pi; @@ -2132,6 +2145,7 @@ void plot(struct graphics_context *gc, struct dive *dive, scale_mode_t scale) pi->nr = 0; } } +#endif /* USE_GTK_UI */ static void plot_string(struct plot_data *entry, char *buf, size_t bufsize, int depth, int pressure, int temp, gboolean has_ndl) diff --git a/qt-gui.cpp b/qt-gui.cpp index 7853cc4c0..2155f0029 100644 --- a/qt-gui.cpp +++ b/qt-gui.cpp @@ -1,8 +1,5 @@ -/* gtk-gui.c */ -/* gtk UI implementation */ -/* creates the window and overall layout - * divelist, dive info, equipment and printing are handled in their own source files - */ +/* qt-gui.cpp */ +/* Qt UI implementation */ #include #include #include @@ -17,8 +14,6 @@ #include "dive.h" #include "divelist.h" #include "display.h" -#include "display-gtk.h" -#include "callbacks-gtk.h" #include "uemis.h" #include "device.h" #include "webservice.h" @@ -26,1844 +21,40 @@ #include "libdivecomputer.h" #include "qt-ui/mainwindow.h" -#include -#include - -#include -#include -#include -#include -#include -#include - -#if HAVE_OSM_GPS_MAP -#include -#endif - -class Translator: public QTranslator -{ - Q_OBJECT - -public: - Translator(QObject *parent = 0); - ~Translator() {} - - virtual QString translate(const char *context, const char *sourceText, - const char *disambiguation = NULL) const; -}; - -Translator::Translator(QObject *parent): - QTranslator(parent) -{ -} - -QString Translator::translate(const char *context, const char *sourceText, - const char *disambiguation) const -{ - return gettext(sourceText); -} - -static const GdkPixdata subsurface_icon_pixbuf = {}; - -GtkWidget *main_window; -GtkWidget *main_vbox; -GtkWidget *error_info_bar; -GtkWidget *error_label; -GtkWidget *vpane, *hpane; -GtkWidget *notebook; -static QApplication *application = NULL; - -int error_count; -const char *existing_filename; - -typedef enum { PANE_INFO, PANE_PROFILE, PANE_LIST, PANE_THREE } pane_conf_t; -static pane_conf_t pane_conf; - -static struct device_info *holdnicknames = NULL; -static GtkWidget *dive_profile_widget(void); -static void import_files(GtkWidget *, gpointer); - -static void remember_dc(const char *model, uint32_t deviceid, const char *nickname) -{ - struct device_info *nn_entry; - - nn_entry = create_device_info(model, deviceid); - if (!nn_entry) - return; - if (!nickname || !*nickname) { - nn_entry->nickname = NULL; - return; - } - nn_entry->nickname = strdup(nickname); -} - -static void remove_dc(const char *model, uint32_t deviceid) -{ - free(remove_device_info(model, deviceid)); -} - -static GtkWidget *dive_profile; - -GtkActionGroup *action_group; - -void repaint_dive(void) -{ - update_dive(current_dive); - if (dive_profile) - gtk_widget_queue_draw(dive_profile); -} - -static gboolean need_icon = TRUE; - -static void on_info_bar_response(GtkWidget *widget, gint response, - gpointer data) -{ - if (response == GTK_RESPONSE_OK) - { - gtk_widget_destroy(widget); - error_info_bar = NULL; - } -} - -void report_error(GError* error) -{ - qDebug("Warning: Calling GTK-Specific Code."); - if (error == NULL) - { - return; - } - - if (error_info_bar == NULL) - { - error_count = 1; - error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK, - GTK_RESPONSE_OK, - NULL); - g_signal_connect(error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL); - gtk_info_bar_set_message_type(GTK_INFO_BAR(error_info_bar), - GTK_MESSAGE_ERROR); - - error_label = gtk_label_new(error->message); - GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(error_info_bar)); - gtk_container_add(GTK_CONTAINER(container), error_label); - - gtk_box_pack_start(GTK_BOX(main_vbox), error_info_bar, FALSE, FALSE, 0); - gtk_widget_show_all(main_vbox); - } - else - { - error_count++; - char buffer[256]; - snprintf(buffer, sizeof(buffer), _("Failed to open %i files."), error_count); - gtk_label_set_text(GTK_LABEL(error_label), buffer); - } -} - -static GtkFileFilter *setup_filter(void) -{ - GtkFileFilter *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, "*.uddf"); - gtk_file_filter_add_pattern(filter, "*.UDDF"); - gtk_file_filter_add_pattern(filter, "*.udcf"); - gtk_file_filter_add_pattern(filter, "*.UDCF"); - gtk_file_filter_add_pattern(filter, "*.jlb"); - gtk_file_filter_add_pattern(filter, "*.JLB"); -#ifdef LIBZIP - gtk_file_filter_add_pattern(filter, "*.sde"); - gtk_file_filter_add_pattern(filter, "*.SDE"); - gtk_file_filter_add_pattern(filter, "*.dld"); - gtk_file_filter_add_pattern(filter, "*.DLD"); -#endif -#ifdef SQLITE3 - gtk_file_filter_add_pattern(filter, "*.DB"); - gtk_file_filter_add_pattern(filter, "*.db"); -#endif - - gtk_file_filter_add_mime_type(filter, "text/xml"); - gtk_file_filter_set_name(filter, _("XML file")); - - return filter; -} - -static void file_save_as(GtkWidget *w, gpointer data) -{ - GtkWidget *dialog; - char *filename = NULL; - char *current_file; - char *current_dir; - - dialog = gtk_file_chooser_dialog_new(_("Save File As"), - GTK_WINDOW(main_window), - GTK_FILE_CHOOSER_ACTION_SAVE, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, - NULL); - gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); - - if (existing_filename) { - current_dir = g_path_get_dirname(existing_filename); - current_file = g_path_get_basename(existing_filename); - } else { - const char *current_default = prefs.default_filename; - current_dir = g_path_get_dirname(current_default); - current_file = g_path_get_basename(current_default); - } - gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), current_dir); - gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), current_file); - - free(current_dir); - free(current_file); - - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); - } - gtk_widget_destroy(dialog); - - if (filename){ - save_dives(filename); - set_filename(filename, TRUE); - g_free(filename); - mark_divelist_changed(FALSE); - } -} - -static void file_save(GtkWidget *w, gpointer data) -{ - const char *current_default; - - if (!existing_filename) - return file_save_as(w, data); - - current_default = prefs.default_filename; - if (strcmp(existing_filename, current_default) == 0) { - /* if we are using the default filename the directory - * that we are creating the file in may not exist */ - char *current_def_dir; - struct stat sb; - - current_def_dir = g_path_get_dirname(existing_filename); - if (stat(current_def_dir, &sb) != 0) { - g_mkdir(current_def_dir, S_IRWXU); - } - free(current_def_dir); - } - save_dives(existing_filename); - mark_divelist_changed(FALSE); -} - -static gboolean ask_save_changes() -{ - //WARNING: Porting to Qt - qDebug("This method is being ported to Qt, please, stop using it. "); - GtkWidget *dialog, *label, *content; - gboolean quit = TRUE; - dialog = gtk_dialog_new_with_buttons(_("Save Changes?"), - GTK_WINDOW(main_window), GtkDialogFlags(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), - GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, - GTK_STOCK_NO, GTK_RESPONSE_NO, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - NULL); - content = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); - - if (!existing_filename){ - label = gtk_label_new ( - _("You have unsaved changes\nWould you like to save those before closing the datafile?")); - } else { - char *label_text = (char*) malloc(sizeof(char) * - (strlen(_("You have unsaved changes to file: %s \nWould you like to save those before closing the datafile?")) + - strlen(existing_filename))); - sprintf(label_text, - _("You have unsaved changes to file: %s \nWould you like to save those before closing the datafile?"), - existing_filename); - label = gtk_label_new (label_text); - free(label_text); - } - gtk_container_add (GTK_CONTAINER (content), label); - gtk_widget_show_all (dialog); - gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT); - gint outcode = gtk_dialog_run(GTK_DIALOG(dialog)); - if (outcode == GTK_RESPONSE_ACCEPT) { - file_save(NULL,NULL); - } else if (outcode == GTK_RESPONSE_CANCEL || outcode == GTK_RESPONSE_DELETE_EVENT) { - quit = FALSE; - } - gtk_widget_destroy(dialog); - return quit; -} - -static void file_close(GtkWidget *w, gpointer data) -{ - qDebug("Calling an already ported-to-qt Gtk method"); - if (unsaved_changes()) - if (ask_save_changes() == FALSE) - return; - - if (existing_filename) - free((void *)existing_filename); - existing_filename = NULL; - - /* free the dives and trips */ - while (dive_table.nr) - delete_single_dive(0); - mark_divelist_changed(FALSE); - - /* clear the selection and the statistics */ - selected_dive = 0; - process_selected_dives(); - clear_stats_widgets(); - clear_events(); - show_dive_stats(NULL); - - /* clear the equipment page */ - clear_equipment_widgets(); - - /* redraw the screen */ - dive_list_update_dives(); - show_dive_info(NULL); -} - -//##################################################################### -//###### ALREAADY PORTED TO Qt. DELETE ME WHEN NOT MORE USERFUL. # -//##################################################################### -static void file_open(GtkWidget *w, gpointer data) -{ - qDebug("Calling an already ported-to-qt Gtk method."); - GtkWidget *dialog; - GtkFileFilter *filter; - const char *current_default; - - 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); - current_default = prefs.default_filename; - gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), current_default); - /* when opening the data file we should allow only one file to be chosen */ - gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE); - - filter = setup_filter(); - gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter); - - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - GSList *fn_glist; - char *filename; - - /* first, close the existing file, if any, and forget its name */ - file_close(w, data); - free((void *)existing_filename); - existing_filename = NULL; - - /* we know there is only one filename */ - fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); - - GError *error = NULL; - filename = (char *)fn_glist->data; - parse_file(filename, &error); - set_filename(filename, TRUE); - if (error != NULL) - { - report_error(error); - g_error_free(error); - error = NULL; - } - g_free(filename); - g_slist_free(fn_glist); - report_dives(FALSE, FALSE); - } - gtk_widget_destroy(dialog); -} - -void save_pane_position() -{ - gint vpane_position = gtk_paned_get_position(GTK_PANED(vpane)); - gint hpane_position = gtk_paned_get_position(GTK_PANED(hpane)); - gboolean is_maximized = gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(main_window))) & - GDK_WINDOW_STATE_MAXIMIZED; - - if (pane_conf == PANE_THREE){ - if (is_maximized) { - subsurface_set_conf_int("vpane_position_maximized", vpane_position); - subsurface_set_conf_int("hpane_position_maximized", hpane_position); - } else { - subsurface_set_conf_int("vpane_position", vpane_position); - subsurface_set_conf_int("hpane_position", hpane_position); - } - } -} - -/* Since we want direct control of what set of parameters to retrive, this function - * has an input argument. */ -void restore_pane_position(gboolean maximized) -{ - if (maximized) { - gtk_paned_set_position(GTK_PANED(vpane), subsurface_get_conf_int("vpane_position_maximized")); - gtk_paned_set_position(GTK_PANED(hpane), subsurface_get_conf_int("hpane_position_maximized")); - } else { - gtk_paned_set_position(GTK_PANED(vpane), subsurface_get_conf_int("vpane_position")); - gtk_paned_set_position(GTK_PANED(hpane), subsurface_get_conf_int("hpane_position")); - } -} - -void save_window_geometry(void) -{ - /* GDK_GRAVITY_NORTH_WEST assumed ( it is the default ) */ - int window_width, window_height; - gboolean is_maximized; - - gtk_window_get_size(GTK_WINDOW(main_window), &window_width, &window_height); - is_maximized = gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(main_window))) & - GDK_WINDOW_STATE_MAXIMIZED; - subsurface_set_conf_int("window_width", window_width); - subsurface_set_conf_int("window_height", window_height); - subsurface_set_conf_bool("window_maximized", is_maximized); - save_pane_position(); - subsurface_flush_conf(); -} - -void restore_window_geometry(void) -{ - int window_width, window_height; - gboolean is_maximized = subsurface_get_conf_bool("window_maximized") > 0; - - window_height = subsurface_get_conf_int("window_height"); - window_width = subsurface_get_conf_int("window_width"); - - window_height == -1 ? window_height = 300 : window_height; - window_width == -1 ? window_width = 700 : window_width; - - restore_pane_position(is_maximized); - /* don't resize the window if in maximized state */ - if (is_maximized) - gtk_window_maximize(GTK_WINDOW(main_window)); - else - gtk_window_resize(GTK_WINDOW(main_window), window_width, window_height); -} - -gboolean on_delete(GtkWidget* w, gpointer data) -{ - /* Make sure to flush any modified dive data */ - update_dive(NULL); - - gboolean quit = TRUE; - if (unsaved_changes()) - quit = ask_save_changes(); - - if (quit){ - save_window_geometry(); - return FALSE; /* go ahead, kill the program, we're good now */ - } else { - return TRUE; /* We are not leaving */ - } -} - -static void on_destroy(GtkWidget* w, gpointer data) -{ - dive_list_destroy(); - info_widget_destroy(); - gtk_main_quit(); -} - -/* This "window-state-event" callback will be called after the actual action, such - * as maximize or restore. This means that if you have methods here that check - * for the current window state, they will obtain the already updated state... */ -static gboolean on_state(GtkWidget *widget, GdkEventWindowState *event, gpointer user_data) -{ - gint vpane_position, hpane_position; - if (event->changed_mask & GDK_WINDOW_STATE_WITHDRAWN || - event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) - return TRUE; /* do nothing if the window is shown for the first time or minimized */ - if (pane_conf == PANE_THREE) { - hpane_position = gtk_paned_get_position(GTK_PANED(hpane)); - vpane_position = gtk_paned_get_position(GTK_PANED(vpane)); - if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) { /* maximize */ - subsurface_set_conf_int("vpane_position", vpane_position); - subsurface_set_conf_int("hpane_position", hpane_position); - restore_pane_position(TRUE); - } else if (event->new_window_state == 0) { /* restore */ - subsurface_set_conf_int("vpane_position_maximized", vpane_position); - subsurface_set_conf_int("hpane_position_maximized", hpane_position); - restore_pane_position(FALSE); - } - } - return TRUE; -} - -static void quit(GtkWidget *w, gpointer data) -{ - /* Make sure to flush any modified dive data */ - update_dive(NULL); - - gboolean quit = TRUE; - if (unsaved_changes()) - quit = ask_save_changes(); - - if (quit){ - save_window_geometry(); - dive_list_destroy(); - gtk_main_quit(); - } -} - -GtkTreeViewColumn *tree_view_column_add_pixbuf(GtkWidget *tree_view, data_func_t data_func, GtkTreeViewColumn *col) -{ - GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new(); - gtk_tree_view_column_pack_start(col, renderer, FALSE); - gtk_tree_view_column_set_cell_data_func(col, renderer, data_func, NULL, NULL); - g_signal_connect(tree_view, "button-press-event", G_CALLBACK(icon_click_cb), col); - return col; -} - -GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title, - data_func_t data_func, unsigned int flags) -{ - GtkCellRenderer *renderer; - GtkTreeViewColumn *col; - double xalign = 0.0; /* left as default */ - PangoAlignment align; - gboolean visible; - - align = (flags & ALIGN_LEFT) ? PANGO_ALIGN_LEFT : - (flags & ALIGN_RIGHT) ? PANGO_ALIGN_RIGHT : - PANGO_ALIGN_CENTER; - visible = !(flags & INVISIBLE); - - renderer = gtk_cell_renderer_text_new(); - col = gtk_tree_view_column_new(); - - if (flags & EDITABLE) { - g_object_set(renderer, "editable", TRUE, NULL); - g_signal_connect(renderer, "edited", (GCallback) data_func, tree_view); - data_func = NULL; - } - - gtk_tree_view_column_set_title(col, title); - if (!(flags & UNSORTABLE)) - gtk_tree_view_column_set_sort_column_id(col, index); - gtk_tree_view_column_set_resizable(col, TRUE); - /* all but one column have only one renderer - so packing from the end - * makes no difference; for the location column we want to be able to - * prepend the icon in front of the text - so this works perfectly */ - gtk_tree_view_column_pack_end(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); - 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; -} - -/* Helper functions for gtk combo boxes */ -GtkEntry *get_entry(GtkComboBox *combo_box) -{ - return GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo_box))); -} - -const char *get_active_text(GtkComboBox *combo_box) -{ - return gtk_entry_get_text(get_entry(combo_box)); -} - -void set_active_text(GtkComboBox *combo_box, const char *text) -{ - gtk_entry_set_text(get_entry(combo_box), text); -} - -GtkWidget *combo_box_with_model_and_entry(GtkListStore *model) -{ - GtkWidget *widget; - GtkEntryCompletion *completion; - -#if GTK_CHECK_VERSION(2,24,0) - widget = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model)); - gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(widget), 0); -#else - widget = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0); - gtk_combo_box_entry_set_text_column(GTK_COMBO_BOX_ENTRY(widget), 0); -#endif - - completion = gtk_entry_completion_new(); - gtk_entry_completion_set_text_column(completion, 0); - gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(model)); - gtk_entry_completion_set_inline_completion(completion, TRUE); - gtk_entry_completion_set_inline_selection(completion, TRUE); - gtk_entry_completion_set_popup_single_match(completion, FALSE); - gtk_entry_set_completion(get_entry(GTK_COMBO_BOX(widget)), completion); - g_object_unref(completion); - - return widget; -} - -static void create_radio(GtkWidget *vbox, const char *w_name, ...) -{ - va_list args; - GtkRadioButton *group = NULL; - GtkWidget *box, *label; - - box = gtk_hbox_new(TRUE, 10); - gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 0); - - label = gtk_label_new(w_name); - gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0); - - va_start(args, w_name); - for (;;) { - int enabled; - const char *name; - GtkWidget *button; - void *callback_fn; - - name = va_arg(args, char *); - if (!name) - break; - callback_fn = va_arg(args, void *); - enabled = va_arg(args, int); - - button = gtk_radio_button_new_with_label_from_widget(group, name); - group = GTK_RADIO_BUTTON(button); - gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), enabled); - g_signal_connect(button, "toggled", G_CALLBACK(callback_fn), NULL); - } - va_end(args); -} - -void update_screen() -{ - update_dive_list_units(); - repaint_dive(); - update_dive_list_col_visibility(); -} - -UNITCALLBACK(set_meter, length, units::METERS) -UNITCALLBACK(set_feet, length, units::FEET) -UNITCALLBACK(set_bar, pressure, units::BAR) -UNITCALLBACK(set_psi, pressure, units::PSI) -UNITCALLBACK(set_liter, volume, units::LITER) -UNITCALLBACK(set_cuft, volume, units::CUFT) -UNITCALLBACK(set_celsius, temperature, units::CELSIUS) -UNITCALLBACK(set_fahrenheit, temperature, units::FAHRENHEIT) -UNITCALLBACK(set_kg, weight, units::KG) -UNITCALLBACK(set_lbs, weight, units::LBS) - -OPTIONCALLBACK(otu_toggle, prefs.visible_cols.otu) -OPTIONCALLBACK(maxcns_toggle, prefs.visible_cols.maxcns) -OPTIONCALLBACK(sac_toggle, prefs.visible_cols.sac) -OPTIONCALLBACK(nitrox_toggle, prefs.visible_cols.nitrox) -OPTIONCALLBACK(temperature_toggle, prefs.visible_cols.temperature) -OPTIONCALLBACK(totalweight_toggle, prefs.visible_cols.totalweight) -OPTIONCALLBACK(suit_toggle, prefs.visible_cols.suit) -OPTIONCALLBACK(cylinder_toggle, prefs.visible_cols.cylinder) -OPTIONCALLBACK(po2_toggle, prefs.pp_graphs.po2) -OPTIONCALLBACK(pn2_toggle, prefs.pp_graphs.pn2) -OPTIONCALLBACK(phe_toggle, prefs.pp_graphs.phe) -OPTIONCALLBACK(mod_toggle, prefs.mod) -OPTIONCALLBACK(ead_toggle, prefs.ead) -OPTIONCALLBACK(red_ceiling_toggle, prefs.profile_red_ceiling) -OPTIONCALLBACK(calc_ceiling_toggle, prefs.profile_calc_ceiling) -OPTIONCALLBACK(calc_ceiling_3m_toggle, prefs.calc_ceiling_3m_incr) - -static gboolean gflow_edit(GtkWidget *w, GdkEvent *event, gpointer _data) -{ - double gflow; - const char *buf; - if (event->type == GDK_FOCUS_CHANGE) { - buf = gtk_entry_get_text(GTK_ENTRY(w)); - sscanf(buf, "%lf", &gflow); - prefs.gflow = gflow / 100.0; - set_gf(prefs.gflow, -1.0); - update_screen(); - } - return FALSE; -} - -static gboolean gfhigh_edit(GtkWidget *w, GdkEvent *event, gpointer _data) -{ - double gfhigh; - const char *buf; - if (event->type == GDK_FOCUS_CHANGE) { - buf = gtk_entry_get_text(GTK_ENTRY(w)); - sscanf(buf, "%lf", &gfhigh); - prefs.gfhigh = gfhigh / 100.0; - set_gf(-1.0, prefs.gfhigh); - update_screen(); - } - return FALSE; -} - -static void event_toggle(GtkWidget *w, gpointer _data) -{ - gboolean *plot_ev = (gboolean *)_data; - - *plot_ev = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)); -} - -static void pick_default_file(GtkWidget *w, GtkButton *button) -{ - GtkWidget *fs_dialog, *parent; - const char *current_default; - char *current_def_file, *current_def_dir; - GtkFileFilter *filter; - struct stat sb; - - fs_dialog = gtk_file_chooser_dialog_new(_("Choose Default XML File"), - GTK_WINDOW(main_window), - GTK_FILE_CHOOSER_ACTION_SAVE, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - NULL); - parent = gtk_widget_get_ancestor(w, GTK_TYPE_DIALOG); - gtk_widget_set_sensitive(parent, FALSE); - gtk_window_set_transient_for(GTK_WINDOW(fs_dialog), GTK_WINDOW(parent)); - - current_default = prefs.default_filename; - current_def_dir = g_path_get_dirname(current_default); - current_def_file = g_path_get_basename(current_default); - - /* it's possible that the directory doesn't exist (especially for the default) - * For gtk's file select box to make sense we create it */ - if (stat(current_def_dir, &sb) != 0) - g_mkdir(current_def_dir, S_IRWXU); - - gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs_dialog), current_def_dir); - gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(fs_dialog), current_def_file); - gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(fs_dialog), FALSE); - filter = setup_filter(); - gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fs_dialog), filter); - gtk_widget_show_all(fs_dialog); - if (gtk_dialog_run(GTK_DIALOG(fs_dialog)) == GTK_RESPONSE_ACCEPT) { - GSList *list; - - list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(fs_dialog)); - if (g_slist_length(list) == 1) - gtk_button_set_label(button, (const gchar *)list->data); - g_slist_free(list); - } - - free(current_def_dir); - free(current_def_file); - gtk_widget_destroy(fs_dialog); - - gtk_widget_set_sensitive(parent, TRUE); -} - -#if HAVE_OSM_GPS_MAP -static GtkWidget * map_provider_widget() -{ - int i; -#if GTK_CHECK_VERSION(2,24,0) - GtkWidget *combobox = gtk_combo_box_text_new(); - - /* several of the providers seem to be redundant or non-functional; - * we may have to skip more than just the last three eventually */ - for (i = OSM_GPS_MAP_SOURCE_OPENSTREETMAP; i < OSM_GPS_MAP_SOURCE_LAST; i++) - gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combobox), osm_gps_map_source_get_friendly_name((OsmGpsMapSource_t)i)); -#else - GtkWidget *combobox = gtk_combo_box_new_text(); - for (i = OSM_GPS_MAP_SOURCE_OPENSTREETMAP; i < OSM_GPS_MAP_SOURCE_LAST; i++) - gtk_combo_box_append_text(GTK_COMBO_BOX(combobox), osm_gps_map_source_get_friendly_name(i)); -#endif - /* we don't offer choice 0 (none), so the index here is off by one */ - gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), prefs.map_provider - 1); - return combobox; -} -#endif - -static void preferences_dialog(GtkWidget *w, gpointer data) -{ - int result; - GtkWidget *dialog, *notebook, *font, *frame, *box, *hbox, *vbox, *button; - GtkWidget *xmlfile_button; -#if HAVE_OSM_GPS_MAP - GtkWidget *map_provider; -#endif - GtkWidget *entry_po2, *entry_pn2, *entry_phe, *entry_mod, *entry_gflow, *entry_gfhigh; - const char *current_default, *new_default; - char threshold_text[10], mod_text[10], utf8_buf[128]; - struct preferences oldprefs = prefs; - - dialog = gtk_dialog_new_with_buttons(_("Preferences"), - GTK_WINDOW(main_window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - NULL); - - /* create the notebook for the preferences and attach it to dialog */ - notebook = gtk_notebook_new(); - vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - gtk_box_pack_start(GTK_BOX(vbox), notebook, FALSE, FALSE, 5); - - /* vbox that holds the first notebook page */ - vbox = gtk_vbox_new(FALSE, 6); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, - gtk_label_new(_("General Settings"))); - frame = gtk_frame_new(_("Units")); - gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); - - box = gtk_vbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(frame), box); - - create_radio(box, _("Depth:"), - _("Meter"), set_meter, (prefs.units.length == units::METERS), - _("Feet"), set_feet, (prefs.units.length == units::FEET), - NULL); - - create_radio(box, _("Pressure:"), - _("Bar"), set_bar, (prefs.units.pressure == units::BAR), - _("PSI"), set_psi, (prefs.units.pressure == units::PSI), - NULL); - - create_radio(box, _("Volume:"), - _("Liter"), set_liter, (prefs.units.volume == units::LITER), - _("CuFt"), set_cuft, (prefs.units.volume == units::CUFT), - NULL); - - create_radio(box, _("Temperature:"), - _("Celsius"), set_celsius, (prefs.units.temperature == units::CELSIUS), - _("Fahrenheit"), set_fahrenheit, (prefs.units.temperature == units::FAHRENHEIT), - NULL); - - create_radio(box, _("Weight:"), - _("kg"), set_kg, (prefs.units.weight == units::KG), - _("lbs"), set_lbs, (prefs.units.weight == units::LBS), - NULL); - - frame = gtk_frame_new(_("Show Columns")); - gtk_box_pack_start(GTK_BOX(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(_("Temp")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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(_("Cyl")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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("O" UTF8_SUBSCRIPT_2 "%"); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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(_("SAC")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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(_("Weight")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.visible_cols.totalweight); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(totalweight_toggle), NULL); - - button = gtk_check_button_new_with_label(_("Suit")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.visible_cols.suit); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(suit_toggle), NULL); - - frame = gtk_frame_new(_("Divelist Font")); - gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); - - font = gtk_font_button_new_with_font(prefs.divelist_font); - gtk_container_add(GTK_CONTAINER(frame),font); - - frame = gtk_frame_new(_("Misc. Options")); - gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); - - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(frame), box); - - frame = gtk_frame_new(_("Default XML Data File")); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 5); - hbox = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(frame), hbox); - current_default = prefs.default_filename; - xmlfile_button = gtk_button_new_with_label(current_default); - g_signal_connect(G_OBJECT(xmlfile_button), "clicked", - G_CALLBACK(pick_default_file), xmlfile_button); - gtk_box_pack_start(GTK_BOX(hbox), xmlfile_button, FALSE, FALSE, 6); -#if HAVE_OSM_GPS_MAP - frame = gtk_frame_new(_("Map provider")); - map_provider = map_provider_widget(); - gtk_container_add(GTK_CONTAINER(frame), map_provider); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 3); -#endif - /* vbox that holds the second notebook page */ - vbox = gtk_vbox_new(FALSE, 6); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox, - gtk_label_new(_("Tec Settings"))); - - frame = gtk_frame_new(_("Show Columns")); - gtk_box_pack_start(GTK_BOX(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(_("OTU")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.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); - - button = gtk_check_button_new_with_label(_("maxCNS")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.visible_cols.maxcns); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(maxcns_toggle), NULL); - - frame = gtk_frame_new(_("Profile Settings")); - gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5); - - vbox = gtk_vbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(frame), vbox); - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(vbox), box); - - sprintf(utf8_buf, _("Show pO%s graph"), UTF8_SUBSCRIPT_2); - button = gtk_check_button_new_with_label(utf8_buf); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.pp_graphs.po2); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(po2_toggle), &entry_po2); - - sprintf(utf8_buf, _("pO%s threshold"), UTF8_SUBSCRIPT_2); - frame = gtk_frame_new(utf8_buf); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); - entry_po2 = gtk_entry_new(); - gtk_entry_set_max_length(GTK_ENTRY(entry_po2), 4); - snprintf(threshold_text, sizeof(threshold_text), "%.1f", prefs.pp_graphs.po2_threshold); - gtk_entry_set_text(GTK_ENTRY(entry_po2), threshold_text); - gtk_widget_set_sensitive(entry_po2, prefs.pp_graphs.po2); - gtk_container_add(GTK_CONTAINER(frame), entry_po2); - - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(vbox), box); - - sprintf(utf8_buf, _("Show pN%s graph"), UTF8_SUBSCRIPT_2); - button = gtk_check_button_new_with_label(utf8_buf); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.pp_graphs.pn2); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(pn2_toggle), &entry_pn2); - - sprintf(utf8_buf, _("pN%s threshold"), UTF8_SUBSCRIPT_2); - frame = gtk_frame_new(utf8_buf); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); - entry_pn2 = gtk_entry_new(); - gtk_entry_set_max_length(GTK_ENTRY(entry_pn2), 4); - snprintf(threshold_text, sizeof(threshold_text), "%.1f", prefs.pp_graphs.pn2_threshold); - gtk_entry_set_text(GTK_ENTRY(entry_pn2), threshold_text); - gtk_widget_set_sensitive(entry_pn2, prefs.pp_graphs.pn2); - gtk_container_add(GTK_CONTAINER(frame), entry_pn2); - - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(vbox), box); - - button = gtk_check_button_new_with_label(_("Show pHe graph")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.pp_graphs.phe); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(phe_toggle), &entry_phe); - - frame = gtk_frame_new(_("pHe threshold")); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); - entry_phe = gtk_entry_new(); - gtk_entry_set_max_length(GTK_ENTRY(entry_phe), 4); - snprintf(threshold_text, sizeof(threshold_text), "%.1f", prefs.pp_graphs.phe_threshold); - gtk_entry_set_text(GTK_ENTRY(entry_phe), threshold_text); - gtk_widget_set_sensitive(entry_phe, prefs.pp_graphs.phe); - gtk_container_add(GTK_CONTAINER(frame), entry_phe); - - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(vbox), box); - - button = gtk_check_button_new_with_label(_("Show MOD")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.mod); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(mod_toggle), &entry_mod); - - frame = gtk_frame_new(_("max ppO2")); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); - entry_mod = gtk_entry_new(); - gtk_entry_set_max_length(GTK_ENTRY(entry_mod), 4); - snprintf(mod_text, sizeof(mod_text), "%.1f", prefs.mod_ppO2); - gtk_entry_set_text(GTK_ENTRY(entry_mod), mod_text); - gtk_widget_set_sensitive(entry_mod, prefs.mod); - gtk_container_add(GTK_CONTAINER(frame), entry_mod); - - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(vbox), box); - - button = gtk_check_button_new_with_label(_("Show EAD, END, EADD")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.ead); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(ead_toggle), NULL); - - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(vbox), box); - - button = gtk_check_button_new_with_label(_("Show dc reported ceiling in red")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.profile_red_ceiling); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(red_ceiling_toggle), NULL); - - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(vbox), box); - - button = gtk_check_button_new_with_label(_("Show calculated ceiling")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.profile_calc_ceiling); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(calc_ceiling_toggle), NULL); - - button = gtk_check_button_new_with_label(_("3m increments for calculated ceiling")); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), prefs.calc_ceiling_3m_incr); - gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); - g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(calc_ceiling_3m_toggle), NULL); - - box = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(vbox), box); - - frame = gtk_frame_new(_("GFlow")); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); - entry_gflow = gtk_entry_new(); - gtk_entry_set_max_length(GTK_ENTRY(entry_gflow), 4); - snprintf(threshold_text, sizeof(threshold_text), "%.0f", prefs.gflow * 100); - gtk_entry_set_text(GTK_ENTRY(entry_gflow), threshold_text); - gtk_container_add(GTK_CONTAINER(frame), entry_gflow); - gtk_widget_add_events(entry_gflow, GDK_FOCUS_CHANGE_MASK); - g_signal_connect(G_OBJECT(entry_gflow), "event", G_CALLBACK(gflow_edit), NULL); - - frame = gtk_frame_new(_("GFhigh")); - gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 6); - entry_gfhigh = gtk_entry_new(); - gtk_entry_set_max_length(GTK_ENTRY(entry_gfhigh), 4); - snprintf(threshold_text, sizeof(threshold_text), "%.0f", prefs.gfhigh * 100); - gtk_entry_set_text(GTK_ENTRY(entry_gfhigh), threshold_text); - gtk_container_add(GTK_CONTAINER(frame), entry_gfhigh); - gtk_widget_add_events(entry_gfhigh, GDK_FOCUS_CHANGE_MASK); - g_signal_connect(G_OBJECT(entry_gfhigh), "event", G_CALLBACK(gfhigh_edit), NULL); - - gtk_widget_show_all(dialog); - result = gtk_dialog_run(GTK_DIALOG(dialog)); - if (result == GTK_RESPONSE_ACCEPT) { - const char *po2_threshold_text, *pn2_threshold_text, *phe_threshold_text, *mod_text, *gflow_text, *gfhigh_text; - /* Make sure to flush any modified old dive data with old units */ - update_dive(NULL); - - prefs.divelist_font = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON(font))); - set_divelist_font(prefs.divelist_font); - po2_threshold_text = gtk_entry_get_text(GTK_ENTRY(entry_po2)); - sscanf(po2_threshold_text, "%lf", &prefs.pp_graphs.po2_threshold); - pn2_threshold_text = gtk_entry_get_text(GTK_ENTRY(entry_pn2)); - sscanf(pn2_threshold_text, "%lf", &prefs.pp_graphs.pn2_threshold); - phe_threshold_text = gtk_entry_get_text(GTK_ENTRY(entry_phe)); - sscanf(phe_threshold_text, "%lf", &prefs.pp_graphs.phe_threshold); - mod_text = gtk_entry_get_text(GTK_ENTRY(entry_mod)); - sscanf(mod_text, "%lf", &prefs.mod_ppO2); - gflow_text = gtk_entry_get_text(GTK_ENTRY(entry_gflow)); - sscanf(gflow_text, "%lf", &prefs.gflow); - gfhigh_text = gtk_entry_get_text(GTK_ENTRY(entry_gfhigh)); - sscanf(gfhigh_text, "%lf", &prefs.gfhigh); - prefs.gflow /= 100.0; - prefs.gfhigh /= 100.0; - set_gf(prefs.gflow, prefs.gfhigh); - - update_screen(); - - new_default = strdup(gtk_button_get_label(GTK_BUTTON(xmlfile_button))); - - /* if we opened the default file and are changing its name, - * update existing_filename */ - if (existing_filename) { - if (strcmp(current_default, existing_filename) == 0) { - free((void *)existing_filename); - existing_filename = strdup(new_default); - } - } - if (strcmp(current_default, new_default)) { - prefs.default_filename = new_default; - } -#if HAVE_OSM_GPS_MAP - /* get the map provider selected */ - int i; -#if GTK_CHECK_VERSION(2,24,0) - char *provider = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(map_provider)); -#else - char *provider = gtk_combo_box_get_active_text(GTK_COMBO_BOX(map_provider)); -#endif - for (i = OSM_GPS_MAP_SOURCE_OPENSTREETMAP; i <= OSM_GPS_MAP_SOURCE_YAHOO_STREET; i++) - if (!strcmp(provider,osm_gps_map_source_get_friendly_name((OsmGpsMapSource_t)i))) { - prefs.map_provider = i; - break; - } - free((void *)provider); -#endif - save_preferences(); - } else if (result == GTK_RESPONSE_CANCEL) { - prefs = oldprefs; - set_gf(prefs.gflow, prefs.gfhigh); - update_screen(); - } - gtk_widget_destroy(dialog); -} - -static void create_toggle(const char* label, int *on, void *_data) -{ - GtkWidget *button, *table = GTK_WIDGET(_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, *label; - - dialog = gtk_dialog_new_with_buttons(_("Select Events"), - 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); - if (!evn_foreach(&create_toggle, table)) { - g_object_ref_sink(G_OBJECT(table)); - label = gtk_label_new(_("\nNo Events\n")); - gtk_container_add(GTK_CONTAINER(frame), label); - } else { - gtk_container_add(GTK_CONTAINER(frame), table); - } - - gtk_widget_show_all(dialog); - result = gtk_dialog_run(GTK_DIALOG(dialog)); - if (result == GTK_RESPONSE_ACCEPT) { - repaint_dive(); - } - gtk_widget_destroy(dialog); -} - -static void autogroup_cb(GtkWidget *w, gpointer data) -{ - autogroup = !autogroup; - if (! autogroup) - remove_autogen_trips(); - dive_list_update_dives(); -} - -void set_autogroup(gboolean value) -{ - GtkAction *autogroup_action; - - if (value == autogroup) - return; - - autogroup_action = gtk_action_group_get_action(action_group, "Autogroup"); - gtk_action_activate(autogroup_action); -} - -static void renumber_dialog(GtkWidget *w, gpointer data) -{ - int result; - struct dive *dive; - GtkWidget *dialog, *frame, *button, *vbox; - - dialog = gtk_dialog_new_with_buttons(_("Renumber"), - GTK_WINDOW(main_window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, - NULL); - - vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - - frame = gtk_frame_new(_("New starting number")); - 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) { - int nr = gtk_spin_button_get_value(GTK_SPIN_BUTTON(button)); - renumber_dives(nr); - repaint_dive(); - } - gtk_widget_destroy(dialog); -} - -static void about_dialog_link_cb(GtkAboutDialog *dialog, const gchar *link, gpointer data) -{ - subsurface_launch_for_uri(link); -} - -static void about_dialog(GtkWidget *w, gpointer data) -{ - const char *logo_property = NULL; - GdkPixbuf *logo = NULL; - GtkWidget *dialog; - - if (need_icon) { - logo_property = "logo"; - logo = gdk_pixbuf_from_pixdata(&subsurface_icon_pixbuf, TRUE, NULL); - } - dialog = gtk_about_dialog_new(); -#if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 24) - gtk_about_dialog_set_url_hook(about_dialog_link_cb, NULL, NULL); /* deprecated since GTK 2.24 */ -#else - g_signal_connect(GTK_ABOUT_DIALOG(dialog), "activate-link", G_CALLBACK(about_dialog_link_cb), NULL); -#endif - g_object_set(GTK_OBJECT(dialog), - "title", _("About Subsurface"), - "program-name", "Subsurface", - "comments", _("Multi-platform divelog software in C"), - "website", "http://subsurface.hohndel.org", - "license", "GNU General Public License, version 2\nhttp://www.gnu.org/licenses/old-licenses/gpl-2.0.html", - "version", VERSION_STRING, - "copyright", _("Linus Torvalds, Dirk Hohndel, and others, 2011, 2012, 2013"), - /*++GETTEXT the term translator-credits is magic - list the names of the tranlators here */ - "translator_credits", _("translator-credits"), - "logo-icon-name", "subsurface", - /* Must be last: */ - logo_property, logo, - NULL); - if (logo) - g_object_unref(logo); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); -} - -static void show_user_manual(GtkWidget *w, gpointer data) -{ - subsurface_launch_for_uri("http://subsurface.hohndel.org/documentation/user-manual/"); -} - -static void view_list(GtkWidget *w, gpointer data) -{ - save_pane_position(); - gtk_paned_set_position(GTK_PANED(vpane), 0); - pane_conf = PANE_LIST; -} - -static void view_profile(GtkWidget *w, gpointer data) -{ - save_pane_position(); - gtk_paned_set_position(GTK_PANED(hpane), 0); - gtk_paned_set_position(GTK_PANED(vpane), 65535); - pane_conf = PANE_PROFILE; -} - -static void view_info(GtkWidget *w, gpointer data) -{ - - save_pane_position(); - gtk_paned_set_position(GTK_PANED(vpane), 65535); - gtk_paned_set_position(GTK_PANED(hpane), 65535); - pane_conf = PANE_INFO; -} - -static void view_three(GtkWidget *w, gpointer data) -{ - GtkAllocation alloc; - GtkRequisition requisition; - - int vpane_position = subsurface_get_conf_int("vpane_position"); - int hpane_position = subsurface_get_conf_int("hpane_position"); - - gtk_widget_get_allocation(hpane, &alloc); - - if (hpane_position) - gtk_paned_set_position(GTK_PANED(hpane), hpane_position); - else - gtk_paned_set_position(GTK_PANED(hpane), alloc.width/2); - - gtk_widget_get_allocation(vpane, &alloc); - gtk_widget_size_request(notebook, &requisition); - /* pick the requested size for the notebook plus 6 pixels for frame */ - if (vpane_position) - gtk_paned_set_position(GTK_PANED(vpane), vpane_position); - else - gtk_paned_set_position(GTK_PANED(vpane), requisition.height + 6); - - pane_conf = PANE_THREE; -} - -static void toggle_zoom(GtkWidget *w, gpointer data) -{ - zoomed_plot = (zoomed_plot)?0 : 1; - /*Update dive*/ - repaint_dive(); -} - -static void prev_dc(GtkWidget *w, gpointer data) -{ - dc_number--; - /* If the dc number underflows, we'll "wrap around" and use the last dc */ - repaint_dive(); -} - -static void next_dc(GtkWidget *w, gpointer data) -{ - dc_number++; - /* If the dc number overflows, we'll "wrap around" and zero it */ - repaint_dive(); -} - - -/* list columns for nickname edit treeview */ -enum { - NE_MODEL, - NE_ID_STR, - NE_NICKNAME, - NE_NCOL -}; - -/* delete a selection of nicknames */ -static void edit_dc_delete_rows(GtkTreeView *view) -{ - GtkTreeModel *model; - GtkTreePath *path; - GtkTreeIter iter; - GtkTreeRowReference *ref; - GtkTreeSelection *selection; - GList *selected_rows, *list, *row_references = NULL; - guint len; - /* params for delete op */ - const char *model_str; - const char *deviceid_string; /* convert to deviceid */ - uint32_t deviceid; - - selection = gtk_tree_view_get_selection(view); - selected_rows = gtk_tree_selection_get_selected_rows(selection, &model); - - for (list = selected_rows; list; list = g_list_next(list)) { - path = (GtkTreePath *)list->data; - ref = gtk_tree_row_reference_new(model, path); - row_references = g_list_append(row_references, ref); - } - len = g_list_length(row_references); - if (len == 0) - /* Warn about empty selection? */ - return; - - for (list = row_references; list; list = g_list_next(list)) { - path = gtk_tree_row_reference_get_path((GtkTreeRowReference *)(list->data)); - gtk_tree_model_get_iter(model, &iter, path); - gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, - NE_MODEL, &model_str, - NE_ID_STR, &deviceid_string, - -1); - if (sscanf(deviceid_string, "0x%x8", &deviceid) == 1) - remove_dc(model_str, deviceid); - - gtk_list_store_remove(GTK_LIST_STORE (model), &iter); - gtk_tree_path_free(path); - } - g_list_free(selected_rows); - g_list_free(row_references); - g_list_free(list); - - if (gtk_tree_model_get_iter_first(model, &iter)) - gtk_tree_selection_select_iter(selection, &iter); -} - -/* repopulate the edited nickname cell of a DC */ -static void cell_edited_cb(GtkCellRendererText *cell, gchar *path, - gchar *new_text, gpointer store) -{ - GtkTreeIter iter; - const char *model; - const char *deviceid_string; - uint32_t deviceid; - int matched; - - gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, path); - /* display new text */ - gtk_list_store_set(GTK_LIST_STORE(store), &iter, NE_NICKNAME, new_text, -1); - /* and new_text */ - gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, - NE_MODEL, &model, - NE_ID_STR, &deviceid_string, - -1); - /* extract deviceid */ - matched = sscanf(deviceid_string, "0x%x8", &deviceid); - - /* remember pending commit - * Need to extend list rather than wipe and store only one result */ - if (matched == 1){ - if (holdnicknames == NULL){ - holdnicknames = (struct device_info *) malloc(sizeof(struct device_info)); - holdnicknames->model = strdup(model); - holdnicknames->deviceid = deviceid; - holdnicknames->serial_nr = NULL; - holdnicknames->firmware = NULL; - holdnicknames->nickname = strdup(new_text); - holdnicknames->next = NULL; - } else { - struct device_info * top; - struct device_info * last = holdnicknames; - top = (struct device_info *) malloc(sizeof(struct device_info)); - top->model = strdup(model); - top->deviceid = deviceid; - top->serial_nr = NULL; - top->firmware = NULL; - top->nickname = strdup(new_text); - top->next = last; - holdnicknames = top; - } - } -} - -#define SUB_RESPONSE_DELETE 1 /* no delete response in gtk+2 */ -#define SUB_DONE 2 /* enable escape when done */ - -/* show the dialog to edit dc nicknames */ -static void edit_dc_nicknames(GtkWidget *w, gpointer data) -{ - const gchar *C_INACTIVE = "#e8e8ee", *C_ACTIVE = "#ffffff"; /* cell colours */ - GtkWidget *dialog, *confirm, *view, *scroll, *vbox; - GtkCellRenderer *renderer; - GtkTreeSelection *selection; - GtkTreeModel *model; - GtkListStore *store; - GtkTreeIter iter; - gint res = -1; - char id_string[11] = {0}; - struct device_info * nnl; - - dialog = gtk_dialog_new_with_buttons(_("Edit Dive Computer Nicknames"), - GTK_WINDOW(main_window), - GtkDialogFlags(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), - GTK_STOCK_DELETE, - SUB_RESPONSE_DELETE, - GTK_STOCK_CANCEL, - GTK_RESPONSE_CANCEL, - GTK_STOCK_APPLY, - GTK_RESPONSE_APPLY, - NULL); - gtk_widget_set_size_request(dialog, 700, 400); - - scroll = gtk_scrolled_window_new(NULL, NULL); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); - - view = gtk_tree_view_new(); - store = gtk_list_store_new(NE_NCOL, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); - model = GTK_TREE_MODEL(store); - - /* columns */ - renderer = gtk_cell_renderer_text_new(); - g_object_set(renderer, "background", C_INACTIVE, NULL); - gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, _("Model"), - renderer, "text", NE_MODEL, NULL); - - renderer = gtk_cell_renderer_text_new(); - g_object_set(renderer, "background", C_INACTIVE, NULL); - gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, _("Device Id"), - renderer, "text", NE_ID_STR, NULL); - - renderer = gtk_cell_renderer_text_new(); - g_object_set(renderer, "background", C_INACTIVE, NULL); - gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, _("Nickname"), - renderer, "text", NE_NICKNAME, NULL); - g_object_set(renderer, "editable", TRUE, NULL); - g_object_set(renderer, "background", C_ACTIVE, NULL); - g_signal_connect(renderer, "edited", G_CALLBACK(cell_edited_cb), store); - - gtk_tree_view_set_model(GTK_TREE_VIEW(view), model); - g_object_unref(model); - - /* populate list store from device_info_list */ - nnl = head_of_device_info_list(); - while (nnl) { - sprintf(&id_string[0], "%#08x", nnl->deviceid); - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, - NE_MODEL, nnl->model, - NE_ID_STR, id_string, - NE_NICKNAME, nnl->nickname, - -1); - nnl = nnl->next; - } - - selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); - gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_SINGLE); - - vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - gtk_container_add(GTK_CONTAINER(scroll), view); - gtk_container_add(GTK_CONTAINER(vbox), - gtk_label_new(_("Edit a dive computer nickname by double-clicking the in the relevant nickname field"))); - gtk_container_add(GTK_CONTAINER(vbox), scroll); - gtk_widget_set_size_request(scroll, 500, 300); - gtk_widget_show_all(dialog); - - do { - res = gtk_dialog_run(GTK_DIALOG(dialog)); - if (res == SUB_RESPONSE_DELETE) { - confirm = gtk_dialog_new_with_buttons(_("Delete a dive computer information entry"), - GTK_WINDOW(dialog), - GtkDialogFlags(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), - GTK_STOCK_YES, - GTK_RESPONSE_YES, - GTK_STOCK_NO, - GTK_RESPONSE_NO, - NULL); - gtk_widget_set_size_request(confirm, 350, 90); - vbox = gtk_dialog_get_content_area(GTK_DIALOG(confirm)); - gtk_container_add(GTK_CONTAINER(vbox), - gtk_label_new(_("Ok to delete the selected entry?"))); - gtk_widget_show_all(confirm); - if (gtk_dialog_run(GTK_DIALOG(confirm)) == GTK_RESPONSE_YES) { - edit_dc_delete_rows(GTK_TREE_VIEW(view)); - res = SUB_DONE; /* want to close ** both ** dialogs now */ - } - mark_divelist_changed(TRUE); - gtk_widget_destroy(confirm); - } - if (res == GTK_RESPONSE_APPLY && holdnicknames && holdnicknames->model != NULL) { - struct device_info * walk = holdnicknames; - struct device_info * release = holdnicknames; - struct device_info * track = holdnicknames->next; - while (walk) { - remember_dc(walk->model, walk->deviceid, walk->nickname); - walk = walk->next; - } - /* clear down list */ - while (release){ - free(release); - release = track; - if (track) - track = track->next; - } - holdnicknames = NULL; - mark_divelist_changed(TRUE); - } - } while (res != SUB_DONE && res != GTK_RESPONSE_CANCEL && res != GTK_RESPONSE_DELETE_EVENT && res != GTK_RESPONSE_APPLY); - gtk_widget_destroy(dialog); -} - -static GtkActionEntry menu_items[] = { - { "FileMenuAction", NULL, N_("File"), NULL, NULL, NULL}, - { "LogMenuAction", NULL, N_("Log"), NULL, NULL, NULL}, - { "ViewMenuAction", NULL, N_("View"), NULL, NULL, NULL}, - { "FilterMenuAction", NULL, N_("Filter"), NULL, NULL, NULL}, - { "PlannerMenuAction", NULL, N_("Planner"), NULL, NULL, NULL}, - { "HelpMenuAction", NULL, N_("Help"), NULL, NULL, NULL}, - { "NewFile", GTK_STOCK_NEW, N_("New"), CTRLCHAR "N", NULL, G_CALLBACK(file_close) }, - { "OpenFile", GTK_STOCK_OPEN, N_("Open..."), CTRLCHAR "O", NULL, G_CALLBACK(file_open) }, - { "SaveFile", GTK_STOCK_SAVE, N_("Save..."), CTRLCHAR "S", NULL, G_CALLBACK(file_save) }, - { "SaveAsFile", GTK_STOCK_SAVE_AS, N_("Save As..."), SHIFTCHAR CTRLCHAR "S", NULL, G_CALLBACK(file_save_as) }, - { "CloseFile", GTK_STOCK_CLOSE, N_("Close"), NULL, NULL, G_CALLBACK(file_close) }, - { "Print", GTK_STOCK_PRINT, N_("Print..."), CTRLCHAR "P", NULL, G_CALLBACK(do_print) }, - { "ImportFile", NULL, N_("Import File(s)..."), CTRLCHAR "I", NULL, G_CALLBACK(import_files) }, - { "ExportUDDF", NULL, N_("Export UDDF..."), NULL, NULL, G_CALLBACK(export_all_dives_uddf_cb) }, - { "DownloadLog", NULL, N_("Download From Dive Computer..."), CTRLCHAR "D", NULL, G_CALLBACK(download_dialog) }, - { "DownloadWeb", GTK_STOCK_CONNECT, N_("Download From Web Service..."), NULL, NULL, G_CALLBACK(webservice_download_dialog) }, - { "AddDive", GTK_STOCK_ADD, N_("Add Dive..."), NULL, NULL, G_CALLBACK(add_dive_cb) }, - { "Preferences", GTK_STOCK_PREFERENCES, N_("Preferences..."), PREFERENCE_ACCEL, NULL, G_CALLBACK(preferences_dialog) }, - { "Renumber", NULL, N_("Renumber..."), NULL, NULL, G_CALLBACK(renumber_dialog) }, - { "YearlyStats", NULL, N_("Yearly Statistics"), NULL, NULL, G_CALLBACK(show_yearly_stats) }, -#if HAVE_OSM_GPS_MAP - { "DivesLocations", NULL, N_("Dives Locations"), CTRLCHAR "M", NULL, G_CALLBACK(show_gps_locations) }, -#endif - { "SelectEvents", NULL, N_("Select Events..."), NULL, NULL, G_CALLBACK(selectevents_dialog) }, - { "Quit", GTK_STOCK_QUIT, N_("Quit"), CTRLCHAR "Q", NULL, G_CALLBACK(quit) }, - { "About", GTK_STOCK_ABOUT, N_("About Subsurface"), NULL, NULL, G_CALLBACK(about_dialog) }, - { "UserManual", GTK_STOCK_HELP, N_("User Manual"), NULL, NULL, G_CALLBACK(show_user_manual) }, - { "ViewList", NULL, N_("List"), CTRLCHAR "1", NULL, G_CALLBACK(view_list) }, - { "ViewProfile", NULL, N_("Profile"), CTRLCHAR "2", NULL, G_CALLBACK(view_profile) }, - { "ViewInfo", NULL, N_("Info"), CTRLCHAR "3", NULL, G_CALLBACK(view_info) }, - { "ViewThree", NULL, N_("Three"), CTRLCHAR "4", NULL, G_CALLBACK(view_three) }, - { "EditNames", NULL, N_("Edit Device Names"), CTRLCHAR "E", NULL, G_CALLBACK(edit_dc_nicknames) }, - { "PrevDC", NULL, N_("Prev DC"), NULL, NULL, G_CALLBACK(prev_dc) }, - { "NextDC", NULL, N_("Next DC"), NULL, NULL, G_CALLBACK(next_dc) }, - { "InputPlan", NULL, N_("Input Plan"), NULL, NULL, G_CALLBACK(input_plan) }, -}; -static gint nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]); - -static GtkToggleActionEntry toggle_items[] = { - { "Autogroup", NULL, N_("Autogroup"), NULL, NULL, G_CALLBACK(autogroup_cb), FALSE }, - { "ToggleZoom", NULL, N_("Toggle Zoom"), CTRLCHAR "0", NULL, G_CALLBACK(toggle_zoom), FALSE }, -}; -static gint ntoggle_items = sizeof (toggle_items) / sizeof (toggle_items[0]); - -static const gchar* ui_string = " \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - " -#if HAVE_OSM_GPS_MAP - " " -#endif - " \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ - \ -"; - -static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager) -{ - action_group = gtk_action_group_new("Menu"); - gtk_action_group_set_translation_domain(action_group, "subsurface"); - gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0); - toggle_items[0].is_active = autogroup; - gtk_action_group_add_toggle_actions(action_group, toggle_items, ntoggle_items, 0); - - gtk_ui_manager_insert_action_group(ui_manager, action_group, 0); - GError* error = 0; - gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error); - - gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager)); - GtkWidget* menu = gtk_ui_manager_get_widget(ui_manager, "/MainMenu"); - - return menu; -} - -static void switch_page(GtkNotebook *notebook, gint arg1, gpointer user_data) -{ - repaint_dive(); -} - -static gboolean on_key_press(GtkWidget *w, GdkEventKey *event, GtkWidget *divelist) -{ - if (event->type != GDK_KEY_PRESS || event->state != 0) - return FALSE; - switch (event->keyval) { - case GDK_Up: - select_prev_dive(); - return TRUE; - case GDK_Down: - select_next_dive(); - return TRUE; - case GDK_Left: - prev_dc(NULL, NULL); - return TRUE; - case GDK_Right: - next_dc(NULL, NULL); - return TRUE; - } - return FALSE; -} - -static gboolean notebook_tooltip (GtkWidget *widget, gint x, gint y, - gboolean keyboard_mode, GtkTooltip *tooltip, gpointer data) -{ - if (amount_selected > 0 && gtk_notebook_get_current_page(GTK_NOTEBOOK(widget)) == 0) { - gtk_tooltip_set_text(tooltip, _("To edit dive information\ndouble click on it in the dive list")); - return TRUE; - } else { - return FALSE; - } -} - -#if NEEDS_TO_MOVE_TO_QT_UI -/* this appears to have moved - but it's very different in qt-ui */ +#include +#include +#include +#include +#include +#include -class MainWindow: public QMainWindow, private Ui::MainWindow +class Translator: public QTranslator { Q_OBJECT public: - MainWindow(QWidget *parent = 0); - ~MainWindow() {} - - void setCurrentFileName(const QString &fileName); - -private Q_SLOTS: - void on_actionNew_triggered() { on_actionClose_triggered(); } - void on_actionOpen_triggered(); - void on_actionSave_triggered() { file_save(NULL, NULL); } - void on_actionSaveAs_triggered() { file_save_as(NULL, NULL); } - void on_actionClose_triggered(); - -private: - QStringList fileNameFilters() const; + Translator(QObject *parent = 0); + ~Translator() {} -private: - QString m_currentFileName; + virtual QString translate(const char *context, const char *sourceText, + const char *disambiguation = NULL) const; }; -MainWindow::MainWindow(QWidget *parent): - QMainWindow(parent) -{ - setupUi(this); -} - -void MainWindow::setCurrentFileName(const QString &fileName) -{ - if (fileName == m_currentFileName) return; - m_currentFileName = fileName; - - QString title = tr("Subsurface"); - if (!m_currentFileName.isEmpty()) { - QFileInfo fileInfo(m_currentFileName); - title += " - " + fileInfo.fileName(); - } - setWindowTitle(title); -} - -void MainWindow::on_actionOpen_triggered() +Translator::Translator(QObject *parent): + QTranslator(parent) { - QString defaultFileName = prefs.default_filename; - QFileInfo fileInfo(defaultFileName); - - QFileDialog dialog(this, tr("Open File"), fileInfo.path()); - dialog.setFileMode(QFileDialog::ExistingFile); - dialog.selectFile(defaultFileName); - dialog.setNameFilters(fileNameFilters()); - if (dialog.exec()) { - /* first, close the existing file, if any */ - file_close(NULL, NULL); - - /* we know there is only one filename */ - QString fileName = dialog.selectedFiles().first(); - GError *error = NULL; - parse_file(fileName.toUtf8().constData(), &error); - if (error != NULL) { - report_error(error); - g_error_free(error); - error = NULL; - } else { - setCurrentFileName(fileName); - } - report_dives(FALSE, FALSE); - } } -void MainWindow::on_actionClose_triggered() +QString Translator::translate(const char *context, const char *sourceText, + const char *disambiguation) const { - if (unsaved_changes()) - if (ask_save_changes() == FALSE) - return; - - setCurrentFileName(QString()); - - /* free the dives and trips */ - while (dive_table.nr) - delete_single_dive(0); - mark_divelist_changed(FALSE); - - /* clear the selection and the statistics */ - selected_dive = 0; - process_selected_dives(); - clear_stats_widgets(); - clear_events(); - show_dive_stats(NULL); - - /* clear the equipment page */ - clear_equipment_widgets(); - - /* redraw the screen */ - dive_list_update_dives(); - show_dive_info(NULL); + return gettext(sourceText); } -QStringList MainWindow::fileNameFilters() const -{ - QStringList filters; +static QApplication *application = NULL; - filters << "*.xml *.uddf *.udcf *.jlb" -#ifdef LIBZIP - " *.sde *.dld" -#endif -#ifdef SQLITE3 - " *.db" -#endif - ; - return filters; -} -#endif /* NEEDS_TO_MOVE_TO_QT_UI */ +int error_count; +const char *existing_filename; void init_qt_ui(int *argcp, char ***argvp) { @@ -1884,35 +75,6 @@ void init_ui(int *argcp, char ***argvp) QTextCodec::setCodecForCStrings(QTextCodec::codecForMib(106)); #endif - GtkWidget *win; - GtkWidget *nb_page; - GtkWidget *dive_list; - GtkWidget *menubar; - GtkWidget *vbox; - GtkWidget *scrolled; - GdkScreen *screen; - GtkIconTheme *icon_theme=NULL; - GtkSettings *settings; - GtkUIManager *ui_manager; - - gtk_init(argcp, argvp); - settings = gtk_settings_get_default(); - gtk_settings_set_long_property(settings, "gtk-tooltip-timeout", 10, "subsurface setting"); - gtk_settings_set_long_property(settings, "gtk-menu-images", 1, "subsurface setting"); - gtk_settings_set_long_property(settings, "gtk-button-images", 1, "subsurface setting"); - - /* check if utf8 stars are available as a default OS feature */ - if (!subsurface_os_feature_available(UTF8_FONT_WITH_STARS)) { - star_strings[0] = " "; - star_strings[1] = "* "; - star_strings[2] = "** "; - star_strings[3] = "*** "; - star_strings[4] = "**** "; - star_strings[5] = "*****"; - } -#if !GLIB_CHECK_VERSION(2,3,6) - g_type_init(); -#endif subsurface_open_conf(); load_preferences(); @@ -1920,89 +82,6 @@ void init_ui(int *argcp, char ***argvp) default_dive_computer_vendor = subsurface_get_conf("dive_computer_vendor"); default_dive_computer_product = subsurface_get_conf("dive_computer_product"); default_dive_computer_device = subsurface_get_conf("dive_computer_device"); - error_info_bar = NULL; - win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - 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) { - const char *icon_name = subsurface_icon_name(); - if (!access(icon_name, R_OK)) - gtk_window_set_icon_from_file(GTK_WINDOW(win), icon_name, NULL); - } - 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); - g_signal_connect(G_OBJECT(win), "window-state-event", G_CALLBACK(on_state), NULL); - main_window = win; - - vbox = gtk_vbox_new(FALSE, 0); - gtk_container_add(GTK_CONTAINER(win), vbox); - main_vbox = vbox; - - ui_manager = gtk_ui_manager_new(); - menubar = get_menubar_menu(win, ui_manager); - - subsurface_ui_setup(settings, menubar, vbox, ui_manager); - - vpane = gtk_vpaned_new(); - gtk_box_pack_start(GTK_BOX(vbox), vpane, TRUE, TRUE, 3); - hpane = gtk_hpaned_new(); - gtk_paned_add1(GTK_PANED(vpane), hpane); - g_signal_connect_after(G_OBJECT(vbox), "realize", G_CALLBACK(view_three), NULL); - - /* Notebook for dive info vs profile vs .. */ - notebook = gtk_notebook_new(); - scrolled = gtk_scrolled_window_new(NULL, NULL); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); - gtk_paned_add1(GTK_PANED(hpane), scrolled); - gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled), notebook); - g_signal_connect(notebook, "switch-page", G_CALLBACK(switch_page), NULL); - - /* Create the actual divelist */ - dive_list = dive_list_create(); - gtk_widget_set_name(dive_list, "Dive List"); - gtk_paned_add2(GTK_PANED(vpane), dive_list); - - /* Frame for dive profile */ - dive_profile = dive_profile_widget(); - gtk_widget_set_name(dive_profile, "Dive Profile"); - gtk_paned_add2(GTK_PANED(hpane), dive_profile); - - /* Frame for extended dive info */ - nb_page = extended_dive_info_widget(); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new(_("Dive Notes"))); - - /* Frame for dive equipment */ - nb_page = equipment_widget(W_IDX_PRIMARY); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new(_("Equipment"))); - - /* Frame for single dive statistics */ - nb_page = single_stats_widget(); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new(_("Dive Info"))); - - /* Frame for total dive statistics */ - nb_page = total_stats_widget(); - gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new(_("Stats"))); - - /* add tooltip that tells people how to edit things */ - g_object_set(notebook, "has-tooltip", TRUE, NULL); - g_signal_connect(notebook, "query-tooltip", G_CALLBACK(notebook_tooltip), NULL); - - /* handle some keys globally (to deal with gtk focus issues) */ - g_signal_connect (G_OBJECT (win), "key_press_event", G_CALLBACK (on_key_press), dive_list); - - gtk_widget_set_app_paintable(win, TRUE); - restore_window_geometry(); - gtk_widget_show_all(win); return; } @@ -2022,426 +101,6 @@ void exit_ui(void) free((void *)default_dive_computer_device); } -typedef struct { - cairo_rectangle_t rect; - const char *text; - struct event *event; -} 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, struct event *event) -{ - cairo_rectangle_t *rect; - tooltip_rects = (tooltip_record_t *) - 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 = strdup(text); - tooltip_rects[tooltips].event = event; - tooltips++; -} - -#define INSIDE_RECT(_r,_x,_y) ((_r.x <= _x) && (_r.x + _r.width >= _x) && \ - (_r.y <= _y) && (_r.y + _r.height >= _y)) -#define INSIDE_RECT_X(_r, _x) ((_r.x <= _x) && (_r.x + _r.width >= _x)) - -static gboolean profile_tooltip (GtkWidget *widget, gint x, gint y, - gboolean keyboard_mode, GtkTooltip *tooltip, struct graphics_context *gc) -{ - int i; - cairo_rectangle_t *drawing_area = &gc->drawing_area; - gint tx = x - drawing_area->x; /* get transformed coordinates */ - gint ty = y - drawing_area->y; - gint width, height, time = -1; - char buffer[2048], plot[1024]; - const char *event = ""; - - if (tx < 0 || ty < 0) - return FALSE; - - /* don't draw a tooltip if nothing is there */ - if (amount_selected == 0 || gc->pi.nr == 0) - return FALSE; - - width = drawing_area->width - 2*drawing_area->x; - height = drawing_area->height - 2*drawing_area->y; - if (width <= 0 || height <= 0) - return FALSE; - - if (tx > width || ty > height) - return FALSE; - - time = (tx * gc->maxtime) / width; - - /* are we over an event marker ? */ - for (i = 0; i < tooltips; i++) { - if (INSIDE_RECT(tooltip_rects[i].rect, tx, ty)) { - event = tooltip_rects[i].text; - break; - } - } - get_plot_details(gc, time, plot, sizeof(plot)); - - snprintf(buffer, sizeof(buffer), "@ %d:%02d%c%s%c%s", time / 60, time % 60, - *plot ? '\n' : ' ', plot, - *event ? '\n' : ' ', event); - gtk_tooltip_set_text(tooltip, buffer); - return TRUE; - -} - -static double zoom_factor = 1.0; -static int zoom_x = -1, zoom_y = -1; - -static void common_drawing_function(GtkWidget *widget, struct graphics_context *gc) -{ - int i = 0; - struct dive *dive = current_dive; - - gc->drawing_area.x = MIN(50,gc->drawing_area.width / 20.0); - gc->drawing_area.y = MIN(50,gc->drawing_area.height / 20.0); - - g_object_set(widget, "has-tooltip", TRUE, NULL); - g_signal_connect(widget, "query-tooltip", G_CALLBACK(profile_tooltip), gc); - init_profile_background(gc); - cairo_paint(gc->cr); - - if (zoom_factor > 1.0) { - double n = -(zoom_factor-1); - cairo_translate(gc->cr, n*zoom_x, n*zoom_y); - cairo_scale(gc->cr, zoom_factor, zoom_factor); - } - - if (dive) { - if (tooltip_rects) { - while (i < tooltips) { - if (tooltip_rects[i].text) - free((void *)tooltip_rects[i].text); - i++; - } - free(tooltip_rects); - tooltip_rects = NULL; - } - tooltips = 0; - plot(gc, dive, SC_SCREEN); - } -} - -#if GTK_CHECK_VERSION(3,0,0) - -static gboolean draw_callback(GtkWidget *widget, cairo_t *cr, gpointer data) -{ - guint width, height; - static struct graphics_context gc = { .printer = 0 }; - - width = gtk_widget_get_allocated_width(widget); - height = gtk_widget_get_allocated_height(widget); - - gc.drawing_area.width = width; - gc.drawing_area.height = height; - gc.cr = cr; - - common_drawing_function(widget, &gc); - return FALSE; -} - -#else /* gtk2 */ - -static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data) -{ - GtkAllocation allocation; - static struct graphics_context gc = { 0 }; - - /* 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 */ - gtk_widget_get_allocation(widget, &allocation); - gc.drawing_area.width = allocation.width; - gc.drawing_area.height = allocation.height; - gc.cr = gdk_cairo_create(gtk_widget_get_window(widget)); - - common_drawing_function(widget, &gc); - cairo_destroy(gc.cr); - return FALSE; -} - -#endif - -static void zoom_event(int x, int y, double inc) -{ - zoom_x = x; - zoom_y = y; - inc += zoom_factor; - if (inc < 1.0) - inc = 1.0; - else if (inc > 10) - inc = 10; - zoom_factor = inc; -} - -static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer user_data) -{ - switch (event->direction) { - case GDK_SCROLL_UP: - zoom_event(event->x, event->y, 0.1); - break; - case GDK_SCROLL_DOWN: - zoom_event(event->x, event->y, -0.1); - break; - default: - return TRUE; - } - gtk_widget_queue_draw(widget); - return TRUE; -} - -static void add_gas_change_cb(GtkWidget *menuitem, gpointer data) -{ - double *x = (double *)data; - int when = x_to_time(*x); - int cylnr = select_cylinder(current_dive, when); - if (cylnr >= 0) { - cylinder_t *cyl = ¤t_dive->cylinder[cylnr]; - int value = cyl->gasmix.o2.permille / 10 | ((cyl->gasmix.he.permille / 10) << 16); - add_event(current_dc, when, 25, 0, value, "gaschange"); - mark_divelist_changed(TRUE); - report_dives(FALSE, FALSE); - dive_list_update_dives(); - } -} - -int confirm_dialog(int when, char *action_text, char *event_text) -{ - GtkWidget *dialog, *vbox, *label; - int confirmed; - char title[80]; - - snprintf(title, sizeof(title), "%s %s", action_text, event_text); - dialog = gtk_dialog_new_with_buttons(title, - GTK_WINDOW(main_window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, - NULL); - - vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - label = create_label(_("%s event at %d:%02u"), title, FRACTION(when, 60)); - gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); - gtk_widget_show_all(dialog); - confirmed = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; - - gtk_widget_destroy(dialog); - - return confirmed; -} - -static void add_bookmark_cb(GtkWidget *menuitem, gpointer data) -{ - double *x = (double *)data; - int when = x_to_time(*x); - - if (confirm_dialog(when, _("Add"), _("bookmark"))){ - add_event(current_dc, when, 8, 0, 0, "bookmark"); - mark_divelist_changed(TRUE); - report_dives(FALSE, FALSE); - } -} - -static struct event *event_at_x(double rel_x) -{ - /* is there an event marker at this x coordinate */ - struct event *ret = NULL; - int i; - int x = x_abs(rel_x); - - for (i = 0; i < tooltips; i++) { - if (INSIDE_RECT_X(tooltip_rects[i].rect, x)) { - ret = tooltip_rects[i].event; - break; - } - } - return ret; -} - -static void remove_event_cb(GtkWidget *menuitem, gpointer data) -{ - struct event *event = (struct event *)data; - if (confirm_dialog(event->time.seconds, _("Remove"), _(event->name))){ - struct event **ep = ¤t_dc->events; - while (ep && *ep != event) - ep = &(*ep)->next; - if (ep) { - *ep = event->next; - free(event); - } - mark_divelist_changed(TRUE); - report_dives(FALSE, FALSE); - } -} - -static void popup_profile_menu(GtkWidget *widget, GdkEventButton *gtk_event) -{ - GtkWidget *menu, *menuitem, *image; - static double x; - struct event *event; - - if (!gtk_event || !current_dive) - return; - x = gtk_event->x; - menu = gtk_menu_new(); - menuitem = gtk_image_menu_item_new_with_label(_("Add gas change event here")); - image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU); - gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); - g_signal_connect(menuitem, "activate", G_CALLBACK(add_gas_change_cb), &x); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - menuitem = gtk_image_menu_item_new_with_label(_("Add bookmark event here")); - image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU); - gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); - g_signal_connect(menuitem, "activate", G_CALLBACK(add_bookmark_cb), &x); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - if ((event = event_at_x(x)) != NULL) { - menuitem = gtk_image_menu_item_new_with_label(_("Remove event here")); - image = gtk_image_new_from_stock(GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU); - gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); - g_signal_connect(menuitem, "activate", G_CALLBACK(remove_event_cb), event); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); - } - - gtk_widget_show_all(menu); - - gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, - gtk_event->button, gtk_get_current_event_time()); - -} - -static gboolean clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data) -{ - switch (event->button) { - case 1: - zoom_x = event->x; - zoom_y = event->y; - zoom_factor = 2.5; - break; - case 3: - popup_profile_menu(widget, event); - break; - default: - return TRUE; - } - gtk_widget_queue_draw(widget); - return TRUE; -} - -static gboolean released(GtkWidget *widget, GdkEventButton *event, gpointer user_data) -{ - switch (event->button) { - case 1: - zoom_x = zoom_y = -1; - zoom_factor = 1.0; - break; - default: - return TRUE; - } - gtk_widget_queue_draw(widget); - return TRUE; -} - -static gboolean motion(GtkWidget *widget, GdkEventMotion *event, gpointer user_data) -{ - if (zoom_x < 0) - return TRUE; - - zoom_x = event->x; - zoom_y = event->y; - gtk_widget_queue_draw(widget); - return TRUE; -} - -static GtkWidget *dive_profile_widget(void) -{ - GtkWidget *da; - - da = gtk_drawing_area_new(); - gtk_widget_set_size_request(da, 350, 250); -#if GTK_CHECK_VERSION(3,0,0) - g_signal_connect(da, "draw", G_CALLBACK (draw_callback), NULL); -#else - g_signal_connect(da, "expose_event", G_CALLBACK(expose_event), NULL); -#endif - g_signal_connect(da, "button-press-event", G_CALLBACK(clicked), NULL); - g_signal_connect(da, "scroll-event", G_CALLBACK(scroll_event), NULL); - g_signal_connect(da, "button-release-event", G_CALLBACK(released), NULL); - g_signal_connect(da, "motion-notify-event", G_CALLBACK(motion), NULL); - gtk_widget_add_events(da, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK); - - return da; -} - -static void do_import_file(gpointer data, gpointer user_data) -{ - GError *error = NULL; - parse_file((const char *)data, &error); - - if (error != NULL) - { - report_error(error); - g_error_free(error); - error = NULL; - } -} - -static void import_files(GtkWidget *w, gpointer data) -{ - GtkWidget *fs_dialog; - const char *current_default; - char *current_def_dir; - GtkFileFilter *filter; - struct stat sb; - GSList *filenames = NULL; - - fs_dialog = gtk_file_chooser_dialog_new(_("Choose XML Files To Import Into Current Data File"), - GTK_WINDOW(main_window), - GTK_FILE_CHOOSER_ACTION_OPEN, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, - NULL); - - /* I'm not sure what the best default path should be... */ - if (existing_filename) { - current_def_dir = g_path_get_dirname(existing_filename); - } else { - current_default = prefs.default_filename; - current_def_dir = g_path_get_dirname(current_default); - } - - /* it's possible that the directory doesn't exist (especially for the default) - * For gtk's file select box to make sense we create it */ - if (stat(current_def_dir, &sb) != 0) - g_mkdir(current_def_dir, S_IRWXU); - - gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs_dialog), current_def_dir); - gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(fs_dialog), TRUE); - filter = setup_filter(); - gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fs_dialog), filter); - gtk_widget_show_all(fs_dialog); - if (gtk_dialog_run(GTK_DIALOG(fs_dialog)) == GTK_RESPONSE_ACCEPT) { - /* grab the selected file list, import each file and update the list */ - filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(fs_dialog)); - if (filenames) { - g_slist_foreach(filenames, do_import_file, NULL); - report_dives(TRUE, FALSE); - g_slist_free(filenames); - } - } - - free(current_def_dir); - gtk_widget_destroy(fs_dialog); -} - void set_filename(const char *filename, gboolean force) { if (!force && existing_filename) @@ -2467,79 +126,8 @@ const char *get_dc_nickname(const char *model, uint32_t deviceid) void set_dc_nickname(struct dive *dive) { - GtkWidget *dialog, *vbox, *entry, *frame, *label; - char nickname[160] = ""; - char dialogtext[2048]; - const char *name = nickname; - struct divecomputer *dc = &dive->dc; - - if (!dive) - return; - while (dc) { -#if NICKNAME_DEBUG & 16 - fprintf(debugfile, "set_dc_nickname for model %s deviceid %8x\n", dc->model ? : "", dc->deviceid); -#endif - if (get_dc_nickname(dc->model, dc->deviceid) == NULL) { - struct device_info *nn_entry = get_different_device_info(dc->model, dc->deviceid); - if (nn_entry) { - dialog = gtk_dialog_new_with_buttons( - _("Dive Computer Nickname"), - GTK_WINDOW(main_window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - NULL); - vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - snprintf(dialogtext, sizeof(dialogtext), - _("You already have a dive computer of this model\n" - "named %s\n" - "Subsurface can maintain a nickname for this device to " - "distinguish it from the existing one. " - "The default is the model and device ID as shown below.\n" - "If you don't want to name this dive computer click " - "'Cancel' and Subsurface will simply display its model " - "as its name (which may mean that you cannot tell the two " - "dive computers apart in the logs)."), - nn_entry->nickname && *nn_entry->nickname ? nn_entry->nickname : - (nn_entry->model && *nn_entry->model ? nn_entry->model : _("(nothing)"))); - label = gtk_label_new(dialogtext); - gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); - gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 3); - frame = gtk_frame_new(_("Nickname")); - gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, TRUE, 3); - entry = gtk_entry_new(); - gtk_container_add(GTK_CONTAINER(frame), entry); - gtk_entry_set_max_length(GTK_ENTRY(entry), 68); - snprintf(nickname, sizeof(nickname), "%s (%08x)", dc->model, dc->deviceid); - gtk_entry_set_text(GTK_ENTRY(entry), nickname); - gtk_widget_show_all(dialog); - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - if (strcmp(dc->model, gtk_entry_get_text(GTK_ENTRY(entry)))) { - name = gtk_entry_get_text(GTK_ENTRY(entry)); - remember_dc(dc->model, dc->deviceid, name); - mark_divelist_changed(TRUE); - } - } else { - /* Remember that we declined the nickname */ - remember_dc(dc->model, dc->deviceid, NULL); - } - gtk_widget_destroy(dialog); - } else { - remember_dc(dc->model, dc->deviceid, NULL); - } - } - dc = dc->next; - } + /* needs Qt implementation */ } -gdouble get_screen_dpi(void) -{ - const gdouble mm_per_inch = 25.4; - GdkScreen *scr = gdk_screen_get_default(); - gdouble h_mm = gdk_screen_get_height_mm(scr); - gdouble h = gdk_screen_get_height(scr); - gdouble dpi_h = floor((h / h_mm) * mm_per_inch); - return dpi_h; -} #include "qt-gui.moc" diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index 3193eb2a8..bd202b24f 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -116,6 +116,7 @@ void MainWindow::on_actionClose_triggered() ui->InfoWidget->clearEquipment(); clear_events(); +#if USE_GTK_UI show_dive_stats(NULL); /* redraw the screen */ @@ -124,6 +125,7 @@ void MainWindow::on_actionClose_triggered() // WARNING? Port this to Qt. show_dive_info(NULL); +#endif /* USE_GTK_UI */ } void MainWindow::on_actionImport_triggered() diff --git a/uemis-downloader.c b/uemis-downloader.c index d33f08b8c..53d4f6876 100644 --- a/uemis-downloader.c +++ b/uemis-downloader.c @@ -922,14 +922,18 @@ GError *uemis_download(const char *mountpath, progressbar_t *progress, if (!import_thread_cancelled) { int result; g_timeout_add(100, timeout_func, dialog); +#if USE_GTK_UI update_progressbar(args.progress, progress_bar_fraction); update_progressbar_text(args.progress, progress_bar_text); +#endif result = gtk_dialog_run(dialog); if (result == GTK_RESPONSE_CANCEL) import_thread_cancelled = TRUE; } else { +#if USE_GTK_UI update_progressbar(args.progress, progress_bar_fraction); update_progressbar_text(args.progress, _("Cancelled, exiting cleanly...")); +#endif usleep(100000); } } diff --git a/webservice.c b/webservice.c index 8f00025d2..6dbe32a38 100644 --- a/webservice.c +++ b/webservice.c @@ -164,7 +164,9 @@ static void download_dialog_response_cb(GtkDialog *d, gint response, gpointer da /* now merge the data in the gps_location table into the dive_table */ if (merge_locations_into_dives()) { mark_divelist_changed(TRUE); +#if USE_GTK_UI dive_list_update_dives(); +#endif } /* store last entered uid in config */ subsurface_set_conf("webservice_uid", gtk_entry_get_text(GTK_ENTRY(state->uid))); -- cgit v1.2.3-70-g09d2 From 1e43106a28900b4e7924ae5060fbe313bdf5c66a Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Fri, 3 May 2013 12:53:45 -0700 Subject: Fix Mac build Signed-off-by: Dirk Hohndel --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 4a13de5eb..9961f04c7 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,7 @@ else ifeq ($(UNAME), darwin) MACOSXSTAGING = $(MACOSXFILES)/Subsurface.app INFOPLIST = $(MACOSXFILES)/Info.plist INFOPLISTINPUT = $(INFOPLIST).in - LDFLAGS += -headerpad_max_install_names -sectcreate __TEXT __info_plist $(INFOPLIST) + LDFLAGS += -headerpad_max_install_names else SOURCES += windows.c WINDOWSSTAGING = ./packaging/windows -- cgit v1.2.3-70-g09d2 From 8353d571643c83514012a84cdc904353d2c4972e Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Sat, 4 May 2013 16:12:43 -0300 Subject: Started the code for the Profile Plotting This small patch adds a new class - ProfileGraphicsView it's a QGraphicsView based class that will holds all graphics-items for the plotting. The setup is simple, just call ui->ListView->plot( dive ) ( that's already a ProfileGraphicsView and magic will happen. Since Im using a QGraphicsView , the size of the canvas doesn't matter and I'm fixing it at 0,0,100,100. when a resize is done, the resizeEvent will be called, fitting the scene's rectangle on the view. Signed-off-by: Tomaz Canabrava Signed-off-by: Dirk Hohndel --- Makefile | 2 ++ qt-ui/mainwindow.cpp | 2 +- qt-ui/mainwindow.ui | 9 +++++++-- qt-ui/profilegraphics.cpp | 26 ++++++++++++++++++++++++++ qt-ui/profilegraphics.h | 17 +++++++++++++++++ 5 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 qt-ui/profilegraphics.cpp create mode 100644 qt-ui/profilegraphics.h (limited to 'Makefile') diff --git a/Makefile b/Makefile index 9961f04c7..338f81a7c 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,7 @@ HEADERS = \ qt-ui/plotareascene.h \ qt-ui/starwidget.h \ qt-ui/modeldelegates.h \ + qt-ui/profilegraphics.h \ SOURCES = \ @@ -67,6 +68,7 @@ SOURCES = \ qt-ui/plotareascene.cpp \ qt-ui/starwidget.cpp \ qt-ui/modeldelegates.cpp \ + qt-ui/profilegraphics.cpp \ $(RESFILE) diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index 3f545171e..8ac0ad871 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -67,7 +67,6 @@ void MainWindow::on_actionOpen_triggered() g_error_free(error); error = NULL; } - process_dives(FALSE, FALSE); ui->InfoWidget->reload(); @@ -94,6 +93,7 @@ void MainWindow::dive_selection_changed(const QItemSelection& newSelection, cons continue; select_dive(get_divenr(d)); } + ui->ProfileWidget->plot(get_dive(selected_dive)); } void MainWindow::on_actionSave_triggered() diff --git a/qt-ui/mainwindow.ui b/qt-ui/mainwindow.ui index fe97d98b4..7dfbae746 100644 --- a/qt-ui/mainwindow.ui +++ b/qt-ui/mainwindow.ui @@ -25,7 +25,7 @@ Qt::Horizontal - + @@ -88,7 +88,7 @@ 0 0 763 - 19 + 25 @@ -339,6 +339,11 @@ QTreeView
divelistview.h
+ + ProfileGraphicsView + QGraphicsView +
profilegraphics.h
+
diff --git a/qt-ui/profilegraphics.cpp b/qt-ui/profilegraphics.cpp new file mode 100644 index 000000000..59a47826f --- /dev/null +++ b/qt-ui/profilegraphics.cpp @@ -0,0 +1,26 @@ +#include "profilegraphics.h" + +#include +#include + +#include + +ProfileGraphicsView::ProfileGraphicsView(QWidget* parent) : QGraphicsView(parent) +{ + setScene(new QGraphicsScene()); + scene()->setSceneRect(0,0,100,100); +} + +void ProfileGraphicsView::plot(struct dive *d) +{ + qDebug() << "Start the plotting of the dive here."; +} + +void ProfileGraphicsView::resizeEvent(QResizeEvent *event) +{ + // Fits the scene's rectangle on the view. + // I can pass some parameters to this - + // like Qt::IgnoreAspectRatio or Qt::KeepAspectRatio + QRectF r = scene()->sceneRect(); + fitInView ( r.x() - 2, r.y() -2, r.width() + 4, r.height() + 4); // do a little bit of spacing; +} diff --git a/qt-ui/profilegraphics.h b/qt-ui/profilegraphics.h new file mode 100644 index 000000000..e3380abcd --- /dev/null +++ b/qt-ui/profilegraphics.h @@ -0,0 +1,17 @@ +#ifndef PROFILEGRAPHICS_H +#define PROFILEGRAPHICS_H + +#include + +class ProfileGraphicsView : public QGraphicsView { +Q_OBJECT +public: + ProfileGraphicsView(QWidget* parent = 0); + void plot(struct dive *d); + +protected: + void resizeEvent(QResizeEvent *event); + +}; + +#endif -- cgit v1.2.3-70-g09d2 From b75a89aa868d39be29d2bb220a6e0c50d5a2b0ac Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 6 May 2013 20:36:37 -0700 Subject: Start populating the maintab Dive Info widget Establish some useful helpers and use them when updating the values. One of the helpers (from statistics.c) puzzlingly doesn't link - so that's ifdefed out. Also had to re-arrange the settings reading code (it came too late) and to extract the expanding code of the top dive from the settings reading code (as it had no business being there to begin with). Signed-off-by: Dirk Hohndel --- Makefile | 1 + qt-gui.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ qt-ui/maintab.cpp | 23 +++++++++++++++++++++- qt-ui/mainwindow.cpp | 14 ++++++-------- qt-ui/models.cpp | 1 + statistics.c | 15 +++++++++++++++ statistics.h | 1 + 7 files changed, 100 insertions(+), 9 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 338f81a7c..add90b308 100644 --- a/Makefile +++ b/Makefile @@ -57,6 +57,7 @@ SOURCES = \ profile.c \ save-xml.c \ sha1.c \ + statistics.c \ time.c \ qt-gui.cpp \ qt-ui/addcylinderdialog.cpp \ diff --git a/qt-gui.cpp b/qt-gui.cpp index a4801f760..1e2c86e69 100644 --- a/qt-gui.cpp +++ b/qt-gui.cpp @@ -133,5 +133,59 @@ void set_dc_nickname(struct dive *dive) /* needs Qt implementation */ } +QString get_depth_string(depth_t depth, bool showunit) +{ + if (prefs.units.length == units::METERS) { + double meters = depth.mm / 1000.0; + return QString("%1%2").arg(meters, 0, 'f', meters >= 20.0 ? 0 : 1 ).arg(showunit ? _("m") : ""); + } else { + double feet = mm_to_feet(depth.mm); + return QString("%1%2").arg(feet, 0, 'f', 1). arg(showunit ? _("ft") : ""); + } +} + +QString get_weight_string(weight_t weight, bool showunit) +{ + if (prefs.units.weight == units::KG) { + double kg = weight.grams / 1000.0; + return QString("%1%2").arg(kg, 0, 'f', kg >= 20.0 ? 0 : 1 ).arg(showunit ? _("kg") : ""); + } else { + double lbs = grams_to_lbs(weight.grams); + return QString("%1%2").arg(lbs, 0, 'f', lbs >= 40.0 ? 0 : 1 ).arg(showunit ? _("lbs") : ""); + } +} + +QString get_temperature_string(temperature_t temp, bool showunit) +{ + if (prefs.units.temperature == units::CELSIUS) { + double celsius = mkelvin_to_C(temp.mkelvin); + return QString("%1%2").arg(celsius, 0, 'f', 1).arg(showunit ? _("C") : ""); + } else { + double fahrenheit = mkelvin_to_F(temp.mkelvin); + return QString("%1%2").arg(fahrenheit, 0, 'f', 1).arg(showunit ? _("F") : ""); + } +} + +QString get_volume_string(volume_t volume, bool showunit) +{ + if (prefs.units.volume == units::LITER) { + double liter = volume.mliter / 1000.0; + return QString("%1%2").arg(liter, 0, 'f', liter >= 40.0 ? 0 : 1 ).arg(showunit ? _("l") : ""); + } else { + double cuft = ml_to_cuft(volume.mliter); + return QString("%1%2").arg(cuft, 0, 'f', cuft >= 20.0 ? 0 : (cuft >= 2.0 ? 1 : 2)).arg(showunit ? _("cuft") : ""); + } +} + +QString get_pressure_string(pressure_t pressure, bool showunit) +{ + if (prefs.units.pressure == units::BAR) { + double bar = pressure.mbar / 1000.0; + return QString("%1%2").arg(bar, 0, 'f', 1).arg(showunit ? _("bar") : ""); + } else { + double psi = mbar_to_PSI(pressure.mbar); + return QString("%1%2").arg(psi, 0, 'f', 0).arg(showunit ? _("psi") : ""); + } +} #include "qt-gui.moc" diff --git a/qt-ui/maintab.cpp b/qt-ui/maintab.cpp index df6ee69eb..5f668b2be 100644 --- a/qt-ui/maintab.cpp +++ b/qt-ui/maintab.cpp @@ -8,6 +8,8 @@ #include "ui_maintab.h" #include "addcylinderdialog.h" #include "addweightsystemdialog.h" +#include "../helpers.h" +#include "../statistics.h" #include @@ -66,6 +68,7 @@ void MainTab::clearStats() else \ ui->field->setText(d->field) + void MainTab::updateDiveInfo(int dive) { // So, this is what happens now: @@ -77,7 +80,7 @@ void MainTab::updateDiveInfo(int dive) // open the file maintab.ui on the designer // click on the item and check its objectName, // the access is ui->objectName from here on. - + volume_t sacVal; struct dive *d = get_dive(dive); UPDATE_TEXT(d, notes); UPDATE_TEXT(d, location); @@ -88,6 +91,24 @@ void MainTab::updateDiveInfo(int dive) ui->rating->setCurrentStars(d->rating); else ui->rating->setCurrentStars(0); + ui->maximumDepthText->setText(get_depth_string(d->maxdepth, TRUE)); + ui->averageDepthText->setText(get_depth_string(d->meandepth, TRUE)); + sacVal.mliter = d ? d->sac : 0; + ui->sacText->setText(get_volume_string(sacVal, TRUE).append("/min")); + ui->otuText->setText(QString("%1").arg( d ? d->otu : 0)); + ui->waterTemperatureText->setText(d ? get_temperature_string(d->watertemp, TRUE) : ""); + ui->airTemperatureText->setText(d ? get_temperature_string(d->airtemp, TRUE) : ""); + if (d && d->surface_pressure.mbar) + /* this is ALWAYS displayed in mbar */ + ui->airPressureText->setText(QString("%1mbar").arg(d->surface_pressure.mbar)); + else + ui->airPressureText->setText(QString("")); +#if 0 /* this fails to link, even though the function is defined in statistics.c / statistics.h */ + if (d) + ui->gasUsedText->setText(get_volume_string(get_gas_used(d), TRUE)); + else +#endif + ui->gasUsedText->setText(""); } void MainTab::on_addCylinder_clicked() diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp index e8fe80460..56ae64b0c 100644 --- a/qt-ui/mainwindow.cpp +++ b/qt-ui/mainwindow.cpp @@ -32,14 +32,17 @@ MainWindow::MainWindow() : ui(new Ui::MainWindow()), sortModel(new QSortFilterProxyModel()) { ui->setupUi(this); + readSettings(); sortModel->setSourceModel(model); ui->ListWidget->setModel(sortModel); setWindowIcon(QIcon(":subsurface-icon")); - connect(ui->ListWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(dive_selection_changed(QItemSelection,QItemSelection))); - - readSettings(); + QModelIndex firstDiveOrTrip = sortModel->index(0,0); + if (sortModel->index(0,0, firstDiveOrTrip).isValid()) + ui->ListWidget->setCurrentIndex(sortModel->index(0,0, firstDiveOrTrip)); + else + ui->ListWidget->setCurrentIndex(firstDiveOrTrip); } void MainWindow::on_actionNew_triggered() @@ -343,11 +346,6 @@ void MainWindow::readSettings() } ui->ListWidget->collapseAll(); ui->ListWidget->expand(sortModel->index(0,0)); - QModelIndex firstDiveOrTrip = sortModel->index(0,0); - if (sortModel->index(0,0, firstDiveOrTrip).isValid()) - ui->ListWidget->setCurrentIndex(sortModel->index(0,0, firstDiveOrTrip)); - else - ui->ListWidget->setCurrentIndex(firstDiveOrTrip); settings.endGroup(); settings.beginGroup("Units"); GET_UNIT(v, "feet", length, units::METERS, units::FEET); diff --git a/qt-ui/models.cpp b/qt-ui/models.cpp index eb4d8974b..9a6edce98 100644 --- a/qt-ui/models.cpp +++ b/qt-ui/models.cpp @@ -677,6 +677,7 @@ void DiveTripModel::setupModelData() while (--i >= 0) { struct dive* dive = get_dive(i); + update_cylinder_related_info(dive); dive_trip_t* trip = dive->divetrip; DiveItem* diveItem = new DiveItem(); diff --git a/statistics.c b/statistics.c index 7532e346e..bee5837d6 100644 --- a/statistics.c +++ b/statistics.c @@ -267,3 +267,18 @@ void get_selected_dives_text(char *buffer, int size) } } +volume_t get_gas_used(struct dive *dive) +{ + int idx; + volume_t gas_used = { 0 }; + for (idx = 0; idx < MAX_CYLINDERS; idx++) { + cylinder_t *cyl = &dive->cylinder[idx]; + pressure_t start, end; + + start = cyl->start.mbar ? cyl->start : cyl->sample_start; + end = cyl->end.mbar ?cyl->sample_end : cyl->sample_end; + if (start.mbar && end.mbar) + gas_used.mliter += gas_volume(cyl, start) - gas_volume(cyl, end); + } + return gas_used; +} diff --git a/statistics.h b/statistics.h index d2709ee93..95f2957e8 100644 --- a/statistics.h +++ b/statistics.h @@ -31,3 +31,4 @@ extern char *get_time_string(int seconds, int maxdays); extern char *get_minutes(int seconds); extern void process_all_dives(struct dive *dive, struct dive **prev_dive); extern void get_selected_dives_text(char *buffer, int size); +extern volume_t get_gas_used(struct dive *dive); -- cgit v1.2.3-70-g09d2 From 4098922b553c8a412b457a3b6fabbbd936317a65 Mon Sep 17 00:00:00 2001 From: Tomaz Canabrava Date: Fri, 17 May 2013 08:14:10 -0300 Subject: Adds preliminary support for Marble Widget Adds preliminary support for marble widget, alongside with the dive list. my idea is to let the view stay there at the left of the dive list since we got a lot of unused space and a globe is something nice to have - so you can look around where did you dived, the dives near the one that's currectly selected, and so on. I'm not using OpenStreetMaps right now, but a good thing about marble is that it is skinnable - so for instance, a dive school could present a dive lesson using subsurface with a globe from the 1600, to make it feel like 'history'. This version will only compile to Qt4. Signed-off-by: Tomaz Canabrava --- Configure.mk | 26 ++++++++++++++-------- Makefile | 4 +++- qt-ui/globe.cpp | 25 +++++++++++++++++++++ qt-ui/globe.h | 13 +++++++++++ qt-ui/mainwindow.ui | 62 ++++++++++++++++++++++++++++++++--------------------- 5 files changed, 95 insertions(+), 35 deletions(-) create mode 100644 qt-ui/globe.cpp create mode 100644 qt-ui/globe.h (limited to 'Makefile') diff --git a/Configure.mk b/Configure.mk index a08a5541f..fd9467ef0 100644 --- a/Configure.mk +++ b/Configure.mk @@ -83,20 +83,28 @@ endif # Use qmake to find out which Qt version we are building for. QT_VERSION_MAJOR = $(shell $(QMAKE) -query QT_VERSION | cut -d. -f1) ifeq ($(QT_VERSION_MAJOR), 5) - QT_MODULES = Qt5Widgets Qt5Svg - QT_CORE = Qt5Core - QTBINDIR = $(shell $(QMAKE) -query QT_HOST_BINS) - # Tool paths are not stored in .pc files in Qt 5.0 - MOC = $(QTBINDIR)/moc - UIC = $(QTBINDIR)/uic - RCC = $(QTBINDIR)/rcc -else +# QT_MODULES = Qt5Widgets Qt5Svg +# QT_CORE = Qt5Core +# QTBINDIR = $(shell $(QMAKE) -query QT_HOST_BINS) +# # Tool paths are not stored in .pc files in Qt 5.0 +# MOC = $(QTBINDIR)/moc +# UIC = $(QTBINDIR)/uic +# RCC = $(QTBINDIR)/rcc +# if qmake is qt5, try to get the qt4 one. + QMAKE = { qmake-qt4 -v >/dev/null 2>&1 && echo qmake-qt4; } +#else +endif + +ifeq ($(strip $(QMAKE)),) +$(error Could not find qmake or qmake-qt4 in $$PATH for the Qt4 version they failed) +endif + QT_MODULES = QtGui QtSvg QT_CORE = QtCore MOC = $(shell $(PKGCONFIG) --variable=moc_location QtCore) UIC = $(shell $(PKGCONFIG) --variable=uic_location QtGui) RCC = $(shell $(PKGCONFIG) --variable=rcc_location QtGui) -endif +#endif # we need GLIB2CFLAGS for gettext QTCXXFLAGS = $(shell $(PKGCONFIG) --cflags $(QT_MODULES)) $(GLIB2CFLAGS) diff --git a/Makefile b/Makefile index add90b308..0bd3e5c27 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,7 @@ HEADERS = \ qt-ui/starwidget.h \ qt-ui/modeldelegates.h \ qt-ui/profilegraphics.h \ + qt-ui/globe.h SOURCES = \ @@ -70,6 +71,7 @@ SOURCES = \ qt-ui/starwidget.cpp \ qt-ui/modeldelegates.cpp \ qt-ui/profilegraphics.cpp \ + qt-ui/globe.cpp \ $(RESFILE) @@ -112,7 +114,7 @@ else endif LIBS = $(LIBQT) $(LIBXML2) $(LIBXSLT) $(LIBSQLITE3) $(LIBGCONF2) $(LIBDIVECOMPUTER) \ - $(EXTRALIBS) $(LIBZIP) -lpthread -lm $(LIBOSMGPSMAP) $(LIBSOUP) $(LIBWINSOCK) + $(EXTRALIBS) $(LIBZIP) -lpthread -lm $(LIBOSMGPSMAP) $(LIBSOUP) $(LIBWINSOCK) -lmarblewidget MSGLANGS=$(notdir $(wildcard po/*.po)) diff --git a/qt-ui/globe.cpp b/qt-ui/globe.cpp new file mode 100644 index 000000000..770e51b02 --- /dev/null +++ b/qt-ui/globe.cpp @@ -0,0 +1,25 @@ +#include "globe.h" +#include + +using namespace Marble; + +GlobeGPS::GlobeGPS(QWidget* parent) : MarbleWidget(parent) +{ + setMapThemeId("earth/bluemarble/bluemarble.dgml"); + setProjection( Marble::Spherical ); + + // Enable the cloud cover and enable the country borders + setShowClouds( true ); + setShowBorders( true ); + + // Hide the FloatItems: Compass and StatusBar + setShowOverviewMap(false); + setShowScaleBar(false); + + Q_FOREACH( AbstractFloatItem * floatItem, floatItems() ){ + if ( floatItem && floatItem->nameId() == "compass" ) { + floatItem->setPosition( QPoint( 10, 10 ) ); + floatItem->setContentSize( QSize( 50, 50 ) ); + } + } +} diff --git a/qt-ui/globe.h b/qt-ui/globe.h new file mode 100644 index 000000000..bdf40fb1a --- /dev/null +++ b/qt-ui/globe.h @@ -0,0 +1,13 @@ +#ifndef GLOBE_H +#define GLOBE_H + +#include + +class GlobeGPS : public Marble::MarbleWidget{ + Q_OBJECT +public: + GlobeGPS(QWidget *parent); + +}; + +#endif diff --git a/qt-ui/mainwindow.ui b/qt-ui/mainwindow.ui index b2969ac61..ab0cd5f49 100644 --- a/qt-ui/mainwindow.ui +++ b/qt-ui/mainwindow.ui @@ -14,8 +14,8 @@ MainWindow - - + + Qt::Vertical @@ -27,9 +27,13 @@ - - - QTreeView { + + + Qt::Horizontal + + + + QTreeView { show-decoration-selected: 1; } @@ -58,25 +62,27 @@ } - - - true - - - QAbstractItemView::ExtendedSelection - - - true - - - true - - - true - - - true - + + + true + + + QAbstractItemView::ExtendedSelection + + + true + + + true + + + true + + + true + + + @@ -88,7 +94,7 @@ 0 0 763 - 20 + 25 @@ -365,6 +371,12 @@ QGraphicsView
profilegraphics.h
+ + GlobeGPS + QWidget +
globe.h
+ 1 +
-- cgit v1.2.3-70-g09d2