From 9c7aaed02aef077bda9b376e981fb705b8021750 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 18 Jun 2012 12:45:09 -0700 Subject: [PATCH 01/67] Add tankpressure parsing for UDDF files David McNett sent me some example Cochran CAN file data, along with his UDDF exports of same. I still have absolutely no idea how to decode the CAN files (although the subsurface decrypting code seems to correctly decrypt the data, and I see binary patters rather than just noise), but at least I can make sure we parse the UDDF portion better. See also https://github.com/nugget/cochran2uddf for David's tool to convert the Cochran CSV exports into UDDF. Data-source: David McNett Signed-off-by: Linus Torvalds --- parse-xml.c | 1 + 1 file changed, 1 insertion(+) diff --git a/parse-xml.c b/parse-xml.c index 761cc3b41..d6fc953a7 100644 --- a/parse-xml.c +++ b/parse-xml.c @@ -647,6 +647,7 @@ static int uddf_fill_sample(struct sample *sample, const char *name, int len, ch return MATCH(".divetime", sampletime, &sample->time) || MATCH(".depth", depth, &sample->depth) || MATCH(".temperature", temperature, &sample->temperature) || + MATCH(".tankpressure", pressure, &sample->cylinderpressure) || 0; } From 8add7917ce9da2faa7751798c3fb15a24715ae1e Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 18 Jun 2012 16:52:41 -0700 Subject: [PATCH 02/67] Add some more cochran data parsing code/comments The code is pretty useless, the comments perhaps equally so. I'm trying to figure out what the data pattern is for the cochran CAN files. There definitely *is* a pattern, but it actually seems to be different for the files of different people - and it's not obvious in any case. There probably are multiple versions of the format, and there might be things like "David has a high-pressure sensor, and Alex does not" going on too. Signed-off-by: Linus Torvalds --- cochran.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/cochran.c b/cochran.c index 360f2eb7c..366e5ea30 100644 --- a/cochran.c +++ b/cochran.c @@ -150,6 +150,64 @@ static void parse_cochran_header(const char *filename, free(buf); } +/* + * Cochran export files show that depths seem to be in + * tenth of feet. + * + * Temperature seems to be exported in Fahrenheit. + * + * Cylinder pressure seems to be in multiples of 4 psi. + * + * The data seems to be some byte-stream where the pattern + * appears to be that the two high bits indicate type of + * data. + * + * For '00', the low six bits seem to be positive + * values with a distribution towards zero, probably depth + * deltas. '0 0' exists, but is very rare ("surface"?). 63 + * exists, but is rare. + * + * For '01', the low six bits seem to be a signed binary value, + * with the most common being 0, and 1 and -1 (63) being the + * next most common values. + * + * NOTE! Don's CAN data is different. It shows the reverse pattern + * for 00 and 01 above: 00 looks like signed data, with 01 looking + * like unsigned data. + * + * For '10', there seems to be another positive value distribution, + * but unlike '00' the value 0 is common, and I see examples of 63 + * too ("overflow"?) and a spike at '7'. + * + * Again, Don's data is different. + * + * The values for '11' seem to be some exception case. Possibly + * overflow handling, possibly warning events. It doesn't have + * any clear distribution: values 0, 1, 16, 33, 35, 48, 51, 55 + * and 63 are common. + * + * For David and Don's data, '01' is the most common, with '00' + * and '10' not uncommon. '11' is two orders of magnitude less + * common. + * + * For Alex, '00' is the most common, with 01 about a third as + * common, and 02 a third of that. 11 is least common. + * + * There clearly are variations in the format here. And Alex has + * a different data offset than Don/David too (see the #ifdef DON). + * Christ. Maybe I've misread the patterns entirely. + */ +static void cochran_profile_write(const unsigned char *buf, int size) +{ + int i; + + for (i = 0; i < size; i++) { + unsigned char c = buf[i]; + printf("%d %d\n", + c >> 6, c & 0x3f); + } +} + static void parse_cochran_dive(const char *filename, int dive, const unsigned char *decode, unsigned mod, const unsigned char *in, unsigned size) @@ -187,6 +245,7 @@ static void parse_cochran_dive(const char *filename, int dive, printf("\n%s, dive %d\n\n", filename, dive); cochran_debug_write(filename, buf, size); + cochran_profile_write(buf + offset, size - offset); free(buf); } From e5692a77c3f99fe485f6490f567f6cc9cea2adff Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 19 Jun 2012 12:13:50 -0700 Subject: [PATCH 03/67] Update cochran depth precision: it's in 3-inch increments The Cochran CSV depth exports are indeed in tenths of feet, but the decimal is always 0, 3, 5 or 8. Where the 3 and 8 are obviously 0.25 and 0.75 rounded up to one decimal place. So Cochran does seem to be very much about imperial units, with depth and cylinder pressure scaled by four (depth in quarter-foot increments, pressume in 4-psi increments) Signed-off-by: Linus Torvalds --- cochran.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cochran.c b/cochran.c index 366e5ea30..933e1de1f 100644 --- a/cochran.c +++ b/cochran.c @@ -152,7 +152,7 @@ static void parse_cochran_header(const char *filename, /* * Cochran export files show that depths seem to be in - * tenth of feet. + * quarter feet (rounded up to tenths). * * Temperature seems to be exported in Fahrenheit. * From 80b0c097334a13f9fd69ed87eceb3997a1084311 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 19 Jun 2012 20:06:59 -0700 Subject: [PATCH 04/67] Add a few more conversion helper functions to dive.h Convert feet to mm, psi to mbar, and F to mkelvin. We do this elsewhere too, but I'm going to need it for the Cochran CSV files, so let's do the helpers now. Signed-off-by: Linus Torvalds --- dive.h | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/dive.h b/dive.h index 4d60a77a2..6b5c77816 100644 --- a/dive.h +++ b/dive.h @@ -125,21 +125,31 @@ static inline double mm_to_feet(int mm) return mm * 0.00328084; } +static inline unsigned long feet_to_mm(double feet) +{ + return feet * 304.8 + 0.5; +} + static inline int to_feet(depth_t depth) { return mm_to_feet(depth.mm) + 0.5; } -static double mkelvin_to_C(int mkelvin) +static inline double mkelvin_to_C(int mkelvin) { return (mkelvin - 273150) / 1000.0; } -static double mkelvin_to_F(int mkelvin) +static inline double mkelvin_to_F(int mkelvin) { return mkelvin * 9 / 5000.0 - 459.670; } +static inline unsigned long F_to_mkelvin(double f) +{ + return (f-32) * 1000 / 1.8 + 273150.5; +} + static inline int to_C(temperature_t temp) { if (!temp.mkelvin) @@ -165,6 +175,12 @@ static inline double psi_to_bar(double psi) { return psi / 14.5037738; } + +static inline unsigned long psi_to_mbar(double psi) +{ + return psi_to_bar(psi)*1000 + 0.5; +} + static inline int to_PSI(pressure_t pressure) { return pressure.mbar * 0.0145037738 + 0.5; From ba31e37063308ab74b282db983557797d05f59d1 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 19 Jun 2012 20:07:42 -0700 Subject: [PATCH 05/67] cochran: add support for importing the exported CSV files The Cochran Analyst software can export the basic dive information as CSV files (comma-separated values). Individual CSV files contain just one particular type of information: depth, temperature or cylinder pressure, which is rather inconvenient. However, the way subsurface works, you can just import these CSV files all as individual dives, and then subsurface will automatically merge the dives with the same date and time - and in the process it will also merge all the samples. So it turns out that we don't really need any special handling. You can literally just do subsurface and you're all done. Of course, the CSV files really *are* pretty useless, since they don't contain all the nice information about where the dive took place etc. So you literally just get the dive profile. But that's better than getting nothing at all. I'd love to actually be able to parse the real native Cochran Analyst software CAN files, but in the meantime this is at least a starting point. And if I'm ever able to parse those nasty CAN-files, this makes comparisons with the exports much easier. Signed-off-by: Linus Torvalds --- file.c | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/file.c b/file.c index aff1d51f2..3f06259fa 100644 --- a/file.c +++ b/file.c @@ -94,6 +94,125 @@ static int try_to_open_suunto(const char *filename, struct memblock *mem, GError return success; } +static time_t parse_date(const char *date) +{ + int hour, min, sec; + struct tm tm; + char *p; + + memset(&tm, 0, sizeof(tm)); + tm.tm_mday = strtol(date, &p, 10); + if (tm.tm_mday < 1 || tm.tm_mday > 31) + return 0; + for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) { + if (!memcmp(p, monthname(tm.tm_mon), 3)) + break; + } + if (tm.tm_mon > 11) + return 0; + date = p+3; + tm.tm_year = strtol(date, &p, 10); + if (date == p) + return 0; + if (tm.tm_year < 70) + tm.tm_year += 2000; + if (tm.tm_year < 100) + tm.tm_year += 1900; + if (sscanf(p, "%d:%d:%d", &hour, &min, &sec) != 3) + return 0; + tm.tm_hour = hour; + tm.tm_min = min; + tm.tm_sec = sec; + return utc_mktime(&tm); +} + +enum csv_format { + CSV_DEPTH, CSV_TEMP, CSV_PRESSURE +}; + +static void add_sample_data(struct sample *sample, enum csv_format type, double val) +{ + switch (type) { + case CSV_DEPTH: + sample->depth.mm = feet_to_mm(val); + break; + case CSV_TEMP: + sample->temperature.mkelvin = F_to_mkelvin(val); + break; + case CSV_PRESSURE: + sample->cylinderpressure.mbar = psi_to_mbar(val); + break; + } +} + +/* + * Cochran comma-separated values: depth in feet, temperature in F, pressure in psi. + * + * They start with eight comma-separated fields like: + * + * filename: {C:\Analyst4\can\T036785.can},{C:\Analyst4\can\K031892.can} + * divenr: %d + * datetime: {03Sep11 16:37:22},{15Dec11 18:27:02} + * ??: 1 + * serialnr??: {CCI134},{CCI207} + * computer??: {GeminiII},{CommanderIII} + * computer??: {GeminiII},{CommanderIII} + * ??: 1 + * + * Followed by the data values (all comma-separated, all one long line). + */ +static int try_to_open_csv(const char *filename, struct memblock *mem, enum csv_format type) +{ + char *p = mem->buffer; + char *header[8]; + int i, time; + time_t date; + struct dive *dive; + + for (i = 0; i < 8; i++) { + header[i] = p; + p = strchr(p, ','); + if (!p) + return 0; + p++; + } + + date = parse_date(header[2]); + if (!date) + return 0; + + dive = alloc_dive(); + dive->when = date; + dive->number = atoi(header[1]); + + time = 0; + for (;;) { + char *end; + double val; + struct sample *sample; + + errno = 0; + val = strtod(p,&end); + if (end == p) + break; + if (errno) + break; + + sample = prepare_sample(&dive); + sample->time.seconds = time; + add_sample_data(sample, type, val); + finish_sample(dive); + + time++; + dive->duration.seconds = time; + if (*end != ',') + break; + p = end+1; + } + record_dive(dive); + return 1; +} + static int open_by_filename(const char *filename, const char *fmt, struct memblock *mem, GError **error) { /* Suunto Dive Manager files: SDE */ @@ -104,6 +223,14 @@ static int open_by_filename(const char *filename, const char *fmt, struct memblo if (!strcasecmp(fmt, "CAN")) return try_to_open_cochran(filename, mem, error); + /* Cochran export comma-separated-value files */ + if (!strcasecmp(fmt, "DPT")) + return try_to_open_csv(filename, mem, CSV_DEPTH); + if (!strcasecmp(fmt, "TMP")) + return try_to_open_csv(filename, mem, CSV_TEMP); + if (!strcasecmp(fmt, "HP1")) + return try_to_open_csv(filename, mem, CSV_PRESSURE); + return 0; } From e96a1864be076fcdf870188b95b1d43f16308590 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 19 Jun 2012 22:41:44 -0700 Subject: [PATCH 06/67] Fix cochran CSV pressure data import The cochran CSV pressure data is actually in units of '4 psi', not in just psi. That seems to be the resolution cochran internally keeps things in, and unlike the depth reading there's no conversion to standard units in the export (for depth, the quarter-foot depth resolution is converted to tenths of feet when exporting). Yeah, none of this makes any sense to me either, but I knew it was the case. I had just forgotten that factor-of-four when I did the importer. With this fix, I get the same subsurface data (modulo some rounding differences particularly for temperature) whether I go through David McNett's UDDF converter, or just import the CSV data directly. Signed-off-by: Linus Torvalds --- file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/file.c b/file.c index 3f06259fa..538f5c783 100644 --- a/file.c +++ b/file.c @@ -140,7 +140,7 @@ static void add_sample_data(struct sample *sample, enum csv_format type, double sample->temperature.mkelvin = F_to_mkelvin(val); break; case CSV_PRESSURE: - sample->cylinderpressure.mbar = psi_to_mbar(val); + sample->cylinderpressure.mbar = psi_to_mbar(val*4); break; } } From d4b0ce1c86d420a308770a598a07c3815778dc57 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 22 Jun 2012 13:37:39 -0700 Subject: [PATCH 07/67] Update to new sane libdivecomputer interfaces This does mean that you have to build subsurface against a new version of libdivecomputer, and that version is likely going to have various slightly incompatible changes. But the new interfaces allow for easily adding new supported dive computers without subsurface having to be updated for each new vendor and model, so some slight pain is definitely worth it this time. I'm not even going to try to have some backwards-compatible version here, the libdivecomputer interface changes are so extensive. Native enumeration of devices is just the smallest part of it: the constants and types that libdivecomputer uses now have much nicer names that all start with DC_ or dc_, so you don't get the kinds of name clashes we had with "gasmix_t" etc. Signed-off-by: Linus Torvalds --- gtk-gui.c | 83 ++++++++---- libdivecomputer.c | 324 +++++++++++----------------------------------- libdivecomputer.h | 13 +- 3 files changed, 138 insertions(+), 282 deletions(-) diff --git a/gtk-gui.c b/gtk-gui.c index 1e053e52b..679b1e4db 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -31,12 +31,14 @@ static GtkWidget *dive_profile; visible_cols_t visible_cols = {TRUE, FALSE}; -static const char *default_dive_computer; +static const char *default_dive_computer_vendor; +static const char *default_dive_computer_product; static const char *default_dive_computer_device; -static int is_default_dive_computer(const char *name) +static int is_default_dive_computer(const char *vendor, const char *product) { - return default_dive_computer && !strcmp(name, default_dive_computer); + return default_dive_computer_vendor && !strcmp(vendor, default_dive_computer_vendor) && + default_dive_computer_product && !strcmp(product, default_dive_computer_product); } static int is_default_dive_computer_device(const char *name) @@ -44,14 +46,18 @@ static int is_default_dive_computer_device(const char *name) return default_dive_computer_device && !strcmp(name, default_dive_computer_device); } -static void set_default_dive_computer(const char *name) +static void set_default_dive_computer(const char *vendor, const char *product) { - if (!name || !*name) + if (!vendor || !*vendor) return; - if (is_default_dive_computer(name)) + if (!product || !*product) return; - default_dive_computer = name; - subsurface_set_conf("dive_computer", PREF_STRING, name); + if (is_default_dive_computer(vendor, product)) + return; + default_dive_computer_vendor = vendor; + default_dive_computer_product = product; + subsurface_set_conf("dive_computer_vendor", PREF_STRING, vendor); + subsurface_set_conf("dive_computer_product", PREF_STRING, product); } static void set_default_dive_computer_device(const char *name) @@ -724,7 +730,8 @@ void init_ui(int *argcp, char ***argvp) divelist_font = subsurface_get_conf("divelist_font", PREF_STRING); - default_dive_computer = subsurface_get_conf("dive_computer", PREF_STRING); + default_dive_computer_vendor = subsurface_get_conf("dive_computer_vendor", PREF_STRING); + default_dive_computer_product = subsurface_get_conf("dive_computer_product", PREF_STRING); default_dive_computer_device = subsurface_get_conf("dive_computer_device", PREF_STRING); error_info_bar = NULL; @@ -914,20 +921,45 @@ static int fill_computer_list(GtkListStore *store) { int index = -1, i; GtkTreeIter iter; - struct device_list *list = device_list; + dc_iterator_t *iterator = NULL; + dc_descriptor_t *descriptor = NULL; + + i = 0; + dc_descriptor_iterator(&iterator); + while (dc_iterator_next (iterator, &descriptor) == DC_STATUS_SUCCESS) { + const char *vendor = dc_descriptor_get_vendor(descriptor); + const char *product = dc_descriptor_get_product(descriptor); - for (list = device_list, i = 0 ; list->name ; list++, i++) { gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, - 0, list->name, - 1, list->type, + 0, descriptor, -1); - if (is_default_dive_computer(list->name)) + if (is_default_dive_computer(vendor, product)) index = i; + i++; } + dc_iterator_free(iterator); return index; } +void render_dive_computer(GtkCellLayout *cell, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + char buffer[40]; + dc_descriptor_t *descriptor = NULL; + const char *vendor, *product; + + gtk_tree_model_get(model, iter, 0, &descriptor, -1); + vendor = dc_descriptor_get_vendor(descriptor); + product = dc_descriptor_get_product(descriptor); + snprintf(buffer, sizeof(buffer), "%s %s", vendor, product); + g_object_set(renderer, "text", buffer, NULL); +} + + static GtkComboBox *dive_computer_selector(GtkWidget *vbox) { GtkWidget *hbox, *combo_box, *frame; @@ -938,7 +970,7 @@ static GtkComboBox *dive_computer_selector(GtkWidget *vbox) hbox = gtk_hbox_new(FALSE, 6); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3); - model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT); + model = gtk_list_store_new(1, G_TYPE_POINTER); default_index = fill_computer_list(model); frame = gtk_frame_new("Dive computer"); @@ -949,7 +981,7 @@ static GtkComboBox *dive_computer_selector(GtkWidget *vbox) renderer = gtk_cell_renderer_text_new(); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo_box), renderer, TRUE); - gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo_box), renderer, "text", 0, NULL); + gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(combo_box), renderer, render_dive_computer, NULL, NULL); gtk_combo_box_set_active(GTK_COMBO_BOX(combo_box), default_index); @@ -1094,10 +1126,9 @@ repeat: gtk_widget_show_all(dialog); result = gtk_dialog_run(GTK_DIALOG(dialog)); switch (result) { - int type; + dc_descriptor_t *descriptor; GtkTreeIter iter; GtkTreeModel *model; - const char *comp; GSList *list; case GTK_RESPONSE_ACCEPT: /* what happened - did the user pick a file? In that case @@ -1106,17 +1137,23 @@ repeat: gtk_widget_destroy(info); list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(XMLchooser)); if (g_slist_length(list) == 0) { + const char *vendor, *product; + if (!gtk_combo_box_get_active_iter(computer, &iter)) break; model = gtk_combo_box_get_model(computer); gtk_tree_model_get(model, &iter, - 0, &comp, - 1, &type, + 0, &descriptor, -1); - devicedata.type = type; - devicedata.name = comp; + + vendor = dc_descriptor_get_vendor(descriptor); + product = dc_descriptor_get_product(descriptor); + + devicedata.descriptor = descriptor; + devicedata.vendor = vendor; + devicedata.product = product; devicedata.devname = gtk_entry_get_text(device); - set_default_dive_computer(devicedata.name); + set_default_dive_computer(vendor, product); set_default_dive_computer_device(devicedata.devname); info = import_dive_computer(&devicedata, GTK_DIALOG(dialog)); if (info) diff --git a/libdivecomputer.c b/libdivecomputer.c index e59dd6aff..e362d1d2b 100644 --- a/libdivecomputer.c +++ b/libdivecomputer.c @@ -31,90 +31,22 @@ static GError *error(const char *fmt, ...) return error; } -static parser_status_t create_parser(device_data_t *devdata, parser_t **parser) +static dc_status_t create_parser(device_data_t *devdata, dc_parser_t **parser) { - switch (devdata->type) { - case DEVICE_TYPE_SUUNTO_SOLUTION: - return suunto_solution_parser_create(parser); - - case DEVICE_TYPE_SUUNTO_EON: - return suunto_eon_parser_create(parser, 0); - - case DEVICE_TYPE_SUUNTO_VYPER: - if (devdata->devinfo.model == 0x01) - return suunto_eon_parser_create(parser, 1); - return suunto_vyper_parser_create(parser); - - case DEVICE_TYPE_SUUNTO_VYPER2: - case DEVICE_TYPE_SUUNTO_D9: - return suunto_d9_parser_create(parser, devdata->devinfo.model); - - case DEVICE_TYPE_UWATEC_ALADIN: - case DEVICE_TYPE_UWATEC_MEMOMOUSE: - return uwatec_memomouse_parser_create(parser, devdata->clock.devtime, devdata->clock.systime); - - case DEVICE_TYPE_UWATEC_SMART: - return uwatec_smart_parser_create(parser, devdata->devinfo.model, devdata->clock.devtime, devdata->clock.systime); - - case DEVICE_TYPE_REEFNET_SENSUS: - return reefnet_sensus_parser_create(parser, devdata->clock.devtime, devdata->clock.systime); - - case DEVICE_TYPE_REEFNET_SENSUSPRO: - return reefnet_sensuspro_parser_create(parser, devdata->clock.devtime, devdata->clock.systime); - - case DEVICE_TYPE_REEFNET_SENSUSULTRA: - return reefnet_sensusultra_parser_create(parser, devdata->clock.devtime, devdata->clock.systime); - - case DEVICE_TYPE_OCEANIC_VTPRO: - return oceanic_vtpro_parser_create(parser); - - case DEVICE_TYPE_OCEANIC_VEO250: - return oceanic_veo250_parser_create(parser, devdata->devinfo.model); - - case DEVICE_TYPE_OCEANIC_ATOM2: - return oceanic_atom2_parser_create(parser, devdata->devinfo.model); - - case DEVICE_TYPE_MARES_DARWIN: - return mares_darwin_parser_create(parser, devdata->devinfo.model); - - case DEVICE_TYPE_MARES_NEMO: - case DEVICE_TYPE_MARES_PUCK: - return mares_nemo_parser_create(parser, devdata->devinfo.model); - - case DEVICE_TYPE_MARES_ICONHD: - return mares_iconhd_parser_create(parser, devdata->devinfo.model); - - case DEVICE_TYPE_HW_OSTC: - return hw_ostc_parser_create(parser NOT_FROG); - -#ifdef LIBDIVECOMPUTER_SUPPORTS_FROG - case DEVICE_TYPE_HW_FROG: - return hw_ostc_parser_create(parser, 1); -#endif - - case DEVICE_TYPE_CRESSI_EDY: - case DEVICE_TYPE_ZEAGLE_N2ITION3: - return cressi_edy_parser_create(parser, devdata->devinfo.model); - - case DEVICE_TYPE_ATOMICS_COBALT: - return atomics_cobalt_parser_create(parser); - - default: - return PARSER_STATUS_ERROR; - } + return dc_parser_new(parser, devdata->device); } -static int parse_gasmixes(device_data_t *devdata, struct dive *dive, parser_t *parser, int ngases) +static int parse_gasmixes(device_data_t *devdata, struct dive *dive, dc_parser_t *parser, int ngases) { int i; for (i = 0; i < ngases; i++) { int rc; - gasmix_t gasmix = {0}; + dc_gasmix_t gasmix = {0}; int o2, he; - rc = parser_get_field(parser, FIELD_TYPE_GASMIX, i, &gasmix); - if (rc != PARSER_STATUS_SUCCESS && rc != PARSER_STATUS_UNSUPPORTED) + rc = dc_parser_get_field(parser, DC_FIELD_GASMIX, i, &gasmix); + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) return rc; if (i >= MAX_CYLINDERS) @@ -132,10 +64,10 @@ static int parse_gasmixes(device_data_t *devdata, struct dive *dive, parser_t *p dive->cylinder[i].gasmix.o2.permille = o2; dive->cylinder[i].gasmix.he.permille = he; } - return PARSER_STATUS_SUCCESS; + return DC_STATUS_SUCCESS; } -static void handle_event(struct dive *dive, struct sample *sample, parser_sample_value_t value) +static void handle_event(struct dive *dive, struct sample *sample, dc_sample_value_t value) { int type, time; static const char *events[] = { @@ -173,7 +105,7 @@ static void handle_event(struct dive *dive, struct sample *sample, parser_sample } void -sample_cb(parser_sample_type_t type, parser_sample_value_t value, void *userdata) +sample_cb(dc_sample_type_t type, dc_sample_value_t value, void *userdata) { int i; struct dive **divep = userdata; @@ -181,40 +113,40 @@ sample_cb(parser_sample_type_t type, parser_sample_value_t value, void *userdata struct sample *sample; /* - * We fill in the "previous" sample - except for SAMPLE_TYPE_TIME, + * We fill in the "previous" sample - except for DC_SAMPLE_TIME, * which creates a new one. */ sample = dive->samples ? dive->sample+dive->samples-1 : NULL; switch (type) { - case SAMPLE_TYPE_TIME: + case DC_SAMPLE_TIME: sample = prepare_sample(divep); sample->time.seconds = value.time; finish_sample(*divep); break; - case SAMPLE_TYPE_DEPTH: + case DC_SAMPLE_DEPTH: sample->depth.mm = value.depth * 1000 + 0.5; break; - case SAMPLE_TYPE_PRESSURE: + case DC_SAMPLE_PRESSURE: sample->cylinderindex = value.pressure.tank; sample->cylinderpressure.mbar = value.pressure.value * 1000 + 0.5; break; - case SAMPLE_TYPE_TEMPERATURE: + case DC_SAMPLE_TEMPERATURE: sample->temperature.mkelvin = (value.temperature + 273.15) * 1000 + 0.5; break; - case SAMPLE_TYPE_EVENT: + case DC_SAMPLE_EVENT: handle_event(dive, sample, value); break; - case SAMPLE_TYPE_RBT: + case DC_SAMPLE_RBT: printf(" %u\n", value.rbt); break; - case SAMPLE_TYPE_HEARTBEAT: + case DC_SAMPLE_HEARTBEAT: printf(" %u\n", value.heartbeat); break; - case SAMPLE_TYPE_BEARING: + case DC_SAMPLE_BEARING: printf(" %u\n", value.bearing); break; - case SAMPLE_TYPE_VENDOR: + case DC_SAMPLE_VENDOR: printf(" ", value.vendor.type, value.vendor.size); for (i = 0; i < value.vendor.size; ++i) printf("%02X", ((unsigned char *) value.vendor.data)[i]); @@ -238,10 +170,10 @@ static void dev_info(device_data_t *devdata, const char *fmt, ...) static int import_dive_number = 0; -static int parse_samples(device_data_t *devdata, struct dive **divep, parser_t *parser) +static int parse_samples(device_data_t *devdata, struct dive **divep, dc_parser_t *parser) { // Parse the sample data. - return parser_samples_foreach(parser, sample_cb, divep); + return dc_parser_samples_foreach(parser, sample_cb, divep); } /* @@ -275,31 +207,31 @@ static int dive_cb(const unsigned char *data, unsigned int size, void *userdata) { int rc; - parser_t *parser = NULL; + dc_parser_t *parser = NULL; device_data_t *devdata = userdata; dc_datetime_t dt = {0}; struct tm tm; struct dive *dive; rc = create_parser(devdata, &parser); - if (rc != PARSER_STATUS_SUCCESS) { - dev_info(devdata, "Unable to create parser for %s", devdata->name); + if (rc != DC_STATUS_SUCCESS) { + dev_info(devdata, "Unable to create parser for %s %s", devdata->vendor, devdata->product); return rc; } - rc = parser_set_data(parser, data, size); - if (rc != PARSER_STATUS_SUCCESS) { + rc = dc_parser_set_data(parser, data, size); + if (rc != DC_STATUS_SUCCESS) { dev_info(devdata, "Error registering the data"); - parser_destroy(parser); + dc_parser_destroy(parser); return rc; } import_dive_number++; dive = alloc_dive(); - rc = parser_get_datetime(parser, &dt); - if (rc != PARSER_STATUS_SUCCESS && rc != PARSER_STATUS_UNSUPPORTED) { + rc = dc_parser_get_datetime(parser, &dt); + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { dev_info(devdata, "Error parsing the datetime"); - parser_destroy (parser); + dc_parser_destroy(parser); return rc; } @@ -315,49 +247,49 @@ static int dive_cb(const unsigned char *data, unsigned int size, dev_info(devdata, "Dive %d: %s %d %04d", import_dive_number, monthname(tm.tm_mon), tm.tm_mday, year(tm.tm_year)); unsigned int divetime = 0; - rc = parser_get_field (parser, FIELD_TYPE_DIVETIME, 0, &divetime); - if (rc != PARSER_STATUS_SUCCESS && rc != PARSER_STATUS_UNSUPPORTED) { + rc = dc_parser_get_field (parser, DC_FIELD_DIVETIME, 0, &divetime); + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { dev_info(devdata, "Error parsing the divetime"); - parser_destroy(parser); + dc_parser_destroy(parser); return rc; } dive->duration.seconds = divetime; // Parse the maxdepth. double maxdepth = 0.0; - rc = parser_get_field(parser, FIELD_TYPE_MAXDEPTH, 0, &maxdepth); - if (rc != PARSER_STATUS_SUCCESS && rc != PARSER_STATUS_UNSUPPORTED) { + rc = dc_parser_get_field(parser, DC_FIELD_MAXDEPTH, 0, &maxdepth); + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { dev_info(devdata, "Error parsing the maxdepth"); - parser_destroy(parser); + dc_parser_destroy(parser); return rc; } dive->maxdepth.mm = maxdepth * 1000 + 0.5; // Parse the gas mixes. unsigned int ngases = 0; - rc = parser_get_field(parser, FIELD_TYPE_GASMIX_COUNT, 0, &ngases); - if (rc != PARSER_STATUS_SUCCESS && rc != PARSER_STATUS_UNSUPPORTED) { + rc = dc_parser_get_field(parser, DC_FIELD_GASMIX_COUNT, 0, &ngases); + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { dev_info(devdata, "Error parsing the gas mix count"); - parser_destroy(parser); + dc_parser_destroy(parser); return rc; } rc = parse_gasmixes(devdata, dive, parser, ngases); - if (rc != PARSER_STATUS_SUCCESS) { + if (rc != DC_STATUS_SUCCESS) { dev_info(devdata, "Error parsing the gas mix"); - parser_destroy(parser); + dc_parser_destroy(parser); return rc; } // Initialize the sample data. rc = parse_samples(devdata, &dive, parser); - if (rc != PARSER_STATUS_SUCCESS) { + if (rc != DC_STATUS_SUCCESS) { dev_info(devdata, "Error parsing the samples"); - parser_destroy(parser); + dc_parser_destroy(parser); return rc; } - parser_destroy(parser); + dc_parser_destroy(parser); /* If we already saw this dive, abort. */ if (find_dive(dive, devdata)) @@ -368,112 +300,41 @@ static int dive_cb(const unsigned char *data, unsigned int size, } -static device_status_t import_device_data(device_t *device, device_data_t *devicedata) +static dc_status_t import_device_data(dc_device_t *device, device_data_t *devicedata) { - return device_foreach(device, dive_cb, devicedata); + return dc_device_foreach(device, dive_cb, devicedata); } -static device_status_t device_open(const char *devname, - device_type_t type, - device_t **device) +static dc_status_t device_open(const char *devname, + dc_descriptor_t *descriptor, + dc_device_t **device) { - switch (type) { - case DEVICE_TYPE_SUUNTO_SOLUTION: - return suunto_solution_device_open(device, devname); - - case DEVICE_TYPE_SUUNTO_EON: - return suunto_eon_device_open(device, devname); - - case DEVICE_TYPE_SUUNTO_VYPER: - return suunto_vyper_device_open(device, devname); - - case DEVICE_TYPE_SUUNTO_VYPER2: - return suunto_vyper2_device_open(device, devname); - - case DEVICE_TYPE_SUUNTO_D9: - return suunto_d9_device_open(device, devname); - - case DEVICE_TYPE_UWATEC_ALADIN: - return uwatec_aladin_device_open(device, devname); - - case DEVICE_TYPE_UWATEC_MEMOMOUSE: - return uwatec_memomouse_device_open(device, devname); - - case DEVICE_TYPE_UWATEC_SMART: - return uwatec_smart_device_open(device); - - case DEVICE_TYPE_REEFNET_SENSUS: - return reefnet_sensus_device_open(device, devname); - - case DEVICE_TYPE_REEFNET_SENSUSPRO: - return reefnet_sensuspro_device_open(device, devname); - - case DEVICE_TYPE_REEFNET_SENSUSULTRA: - return reefnet_sensusultra_device_open(device, devname); - - case DEVICE_TYPE_OCEANIC_VTPRO: - return oceanic_vtpro_device_open(device, devname); - - case DEVICE_TYPE_OCEANIC_VEO250: - return oceanic_veo250_device_open(device, devname); - - case DEVICE_TYPE_OCEANIC_ATOM2: - return oceanic_atom2_device_open(device, devname); - - case DEVICE_TYPE_MARES_DARWIN: - return mares_darwin_device_open(device, devname, 0); /// last parameter is model type (taken from example), 0 seems to be standard, 1 is DARWIN_AIR => Darwin Air wont work if this is fixed here? - - case DEVICE_TYPE_MARES_NEMO: - return mares_nemo_device_open(device, devname); - - case DEVICE_TYPE_MARES_PUCK: - return mares_puck_device_open(device, devname); - - case DEVICE_TYPE_MARES_ICONHD: - return mares_iconhd_device_open(device, devname); - - case DEVICE_TYPE_HW_OSTC: - return hw_ostc_device_open(device, devname); - - case DEVICE_TYPE_CRESSI_EDY: - return cressi_edy_device_open(device, devname); - - case DEVICE_TYPE_ZEAGLE_N2ITION3: - return zeagle_n2ition3_device_open(device, devname); - - case DEVICE_TYPE_ATOMICS_COBALT: - return atomics_cobalt_device_open(device); - - default: - return DEVICE_STATUS_ERROR; - } + return dc_device_open(device, descriptor, devname); } -static void event_cb(device_t *device, device_event_t event, const void *data, void *userdata) +static void event_cb(dc_device_t *device, dc_event_type_t event, const void *data, void *userdata) { - const device_progress_t *progress = data; - const device_devinfo_t *devinfo = data; - const device_clock_t *clock = data; + const dc_event_progress_t *progress = data; + const dc_event_devinfo_t *devinfo = data; + const dc_event_clock_t *clock = data; device_data_t *devdata = userdata; switch (event) { - case DEVICE_EVENT_WAITING: + case DC_EVENT_WAITING: dev_info(devdata, "Event: waiting for user action"); break; - case DEVICE_EVENT_PROGRESS: + case DC_EVENT_PROGRESS: update_progressbar(&devdata->progress, (double) progress->current / (double) progress->maximum); break; - case DEVICE_EVENT_DEVINFO: - devdata->devinfo = *devinfo; + case DC_EVENT_DEVINFO: dev_info(devdata, "model=%u (0x%08x), firmware=%u (0x%08x), serial=%u (0x%08x)", devinfo->model, devinfo->model, devinfo->firmware, devinfo->firmware, devinfo->serial, devinfo->serial); break; - case DEVICE_EVENT_CLOCK: - devdata->clock = *clock; + case DC_EVENT_CLOCK: dev_info(devdata, "Event: systime=%"PRId64", devtime=%u\n", (uint64_t)clock->systime, clock->devtime); break; @@ -492,36 +353,37 @@ cancel_cb(void *userdata) static const char *do_libdivecomputer_import(device_data_t *data) { - device_t *device = NULL; - device_status_t rc; + dc_device_t *device = NULL; + dc_status_t rc; import_dive_number = 0; - rc = device_open(data->devname, data->type, &device); - if (rc != DEVICE_STATUS_SUCCESS) - return "Unable to open %s (%s)"; + rc = device_open(data->devname, data->descriptor, &device); + if (rc != DC_STATUS_SUCCESS) + return "Unable to open %s %s (%s)"; + data->device = device; // Register the event handler. - int events = DEVICE_EVENT_WAITING | DEVICE_EVENT_PROGRESS | DEVICE_EVENT_DEVINFO | DEVICE_EVENT_CLOCK; - rc = device_set_events(device, events, event_cb, data); - if (rc != DEVICE_STATUS_SUCCESS) { - device_close(device); + int events = DC_EVENT_WAITING | DC_EVENT_PROGRESS | DC_EVENT_DEVINFO | DC_EVENT_CLOCK; + rc = dc_device_set_events(device, events, event_cb, data); + if (rc != DC_STATUS_SUCCESS) { + dc_device_close(device); return "Error registering the event handler."; } // Register the cancellation handler. - rc = device_set_cancel(device, cancel_cb, data); - if (rc != DEVICE_STATUS_SUCCESS) { - device_close(device); + rc = dc_device_set_cancel(device, cancel_cb, data); + if (rc != DC_STATUS_SUCCESS) { + dc_device_close(device); return "Error registering the cancellation handler."; } rc = import_device_data(device, data); - if (rc != DEVICE_STATUS_SUCCESS) { - device_close(device); + if (rc != DC_STATUS_SUCCESS) { + dc_device_close(device); return "Dive data import error"; } - device_close(device); + dc_device_close(device); return NULL; } @@ -548,42 +410,6 @@ GError *do_import(device_data_t *data) if (pthread_join(pthread, &retval) < 0) retval = "Odd pthread error return"; if (retval) - return error(retval, data->name, data->devname); + return error(retval, data->vendor, data->product, data->devname); return NULL; } - -/* - * Taken from 'example.c' in libdivecomputer. - * - * I really wish there was some way to just have - * libdivecomputer tell us what devices it supports, - * rather than have the application have to know.. - */ -struct device_list device_list[] = { - { "Suunto Solution", DEVICE_TYPE_SUUNTO_SOLUTION }, - { "Suunto Eon", DEVICE_TYPE_SUUNTO_EON }, - { "Suunto Vyper", DEVICE_TYPE_SUUNTO_VYPER }, - { "Suunto Vyper Air", DEVICE_TYPE_SUUNTO_VYPER2 }, - { "Suunto D9", DEVICE_TYPE_SUUNTO_D9 }, - { "Uwatec Aladin", DEVICE_TYPE_UWATEC_ALADIN }, - { "Uwatec Memo Mouse", DEVICE_TYPE_UWATEC_MEMOMOUSE }, - { "Uwatec Smart", DEVICE_TYPE_UWATEC_SMART }, - { "ReefNet Sensus", DEVICE_TYPE_REEFNET_SENSUS }, - { "ReefNet Sensus Pro", DEVICE_TYPE_REEFNET_SENSUSPRO }, - { "ReefNet Sensus Ultra",DEVICE_TYPE_REEFNET_SENSUSULTRA }, - { "Oceanic VT Pro", DEVICE_TYPE_OCEANIC_VTPRO }, - { "Oceanic Veo250", DEVICE_TYPE_OCEANIC_VEO250 }, - { "Oceanic Atom 2", DEVICE_TYPE_OCEANIC_ATOM2 }, - { "Mares Darwin, M1, M2, Airlab", DEVICE_TYPE_MARES_DARWIN }, - { "Mares Nemo, Excel, Apneist", DEVICE_TYPE_MARES_NEMO }, - { "Mares Puck, Nemo Air, Nemo Wide", DEVICE_TYPE_MARES_PUCK }, - { "Mares Icon HD", DEVICE_TYPE_MARES_ICONHD }, - { "OSTC", DEVICE_TYPE_HW_OSTC }, -#ifdef LIBDIVECOMPUTER_SUPPORTS_FROG - { "OSTC Frog", DEVICE_TYPE_HW_FROG }, -#endif - { "Cressi Edy", DEVICE_TYPE_CRESSI_EDY }, - { "Zeagle N2iTiON 3", DEVICE_TYPE_ZEAGLE_N2ITION3 }, - { "Atomics Cobalt", DEVICE_TYPE_ATOMICS_COBALT }, - { NULL } -}; diff --git a/libdivecomputer.h b/libdivecomputer.h index c6e99fa73..633b3b1b1 100644 --- a/libdivecomputer.h +++ b/libdivecomputer.h @@ -20,20 +20,13 @@ /* don't forget to include the UI toolkit specific display-XXX.h first to get the definition of progressbar_t */ typedef struct device_data_t { - device_type_t type; - const char *name, *devname; + dc_descriptor_t *descriptor; + const char *vendor, *product, *devname; + dc_device_t *device; progressbar_t progress; - device_devinfo_t devinfo; - device_clock_t clock; int preexisting; } device_data_t; -struct device_list { - const char *name; - device_type_t type; -}; - -extern struct device_list device_list[]; extern GError *do_import(device_data_t *data); #endif From e3bdfc2dc357e362a695632a3e0ee24095bb5b5a Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 27 Jun 2012 13:11:54 -0700 Subject: [PATCH 08/67] Rough "Add new dive" infrastructure in the divelist Do a right-click to get a menu with the "Add dive" entry. Should do delete too, but that's for later. What's also apparently for later is to make this *useful*. It's the butt-ugliest time entry field ever, and there's no way to set depth for the dive either. So this is more of a RFC than anything truly useful. Signed-off-by: Linus Torvalds --- dive.h | 1 + divelist.c | 45 +++++++++++++++++++++++++++++ info.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+) diff --git a/dive.h b/dive.h index 6b5c77816..a25fafb39 100644 --- a/dive.h +++ b/dive.h @@ -343,6 +343,7 @@ extern void add_location(const char *string); extern void remember_event(const char *eventname); extern void evn_foreach(void (*callback)(const char *, int *, void *), void *data); +extern int add_new_dive(struct dive *dive); extern int edit_dive_info(struct dive *dive); extern void dive_list_update_dives(void); extern void flush_divelist(struct dive *dive); diff --git a/divelist.c b/divelist.c index 2054ca017..c67e42a4f 100644 --- a/divelist.c +++ b/divelist.c @@ -687,6 +687,49 @@ static void row_activated_cb(GtkTreeView *tree_view, edit_dive_info(get_dive(index)); } +static void add_dive_cb(GtkWidget *menuitem, GtkTreeModel *model) +{ + struct dive *dive; + + dive = alloc_dive(); + if (add_new_dive(dive)) { + record_dive(dive); + report_dives(TRUE); + return; + } + free(dive); +} + +static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button) +{ + GtkWidget *menu, *menuitem; + + menu = gtk_menu_new(); + menuitem = gtk_menu_item_new_with_label("Add dive"); + g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), model); + 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, + GtkTreeModel *model) +{ + popup_divelist_menu(tree_view, model, 0); +} + +static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, GtkTreeModel *model) +{ + /* Right-click? Bring up the menu */ + if (event->type == GDK_BUTTON_PRESS && event->button == 3) { + popup_divelist_menu(GTK_TREE_VIEW(treeview), model, 3); + return TRUE; + } + return FALSE; +} + GtkWidget *dive_list_create(void) { GtkTreeSelection *selection; @@ -734,6 +777,8 @@ GtkWidget *dive_list_create(void) 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), dive_list.model); + g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), dive_list.model); + g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), dive_list.model); g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), dive_list.model); dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL); diff --git a/info.c b/info.c index 813d58adc..10a31f558 100644 --- a/info.c +++ b/info.c @@ -420,6 +420,90 @@ int edit_dive_info(struct dive *dive) return success; } +/* Fixme - should do at least depths too - a dive without a depth is kind of pointless */ +static time_t dive_time_widget(struct dive *dive) +{ + GtkWidget *dialog; + GtkWidget *cal, *hbox, *vbox; + GtkWidget *h, *m; + GtkWidget *duration; + GtkWidget *label; + guint yval, mval, dval; + struct tm tm; + int success; + + 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 = gtk_hbox_new(0, 3); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); + + cal = gtk_calendar_new(); + gtk_box_pack_start(GTK_BOX(hbox), cal, FALSE, TRUE, 0); + + /* Time/duration hbox */ + hbox = gtk_hbox_new(0, 3); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); + + 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_spin_button_set_wrap(GTK_SPIN_BUTTON(h), TRUE); + gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(m), TRUE); + + duration = gtk_spin_button_new_with_range (0.0, 1000.0, 1.0); + + gtk_box_pack_start(GTK_BOX(hbox), h, FALSE, FALSE, 0); + label = gtk_label_new(":"); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), m, FALSE, FALSE, 0); + + label = gtk_label_new(" Duration:"); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), duration, FALSE, FALSE, 0); + + 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)); + + dive->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)) + return 0; + + return edit_dive_info(dive); +} + GtkWidget *extended_dive_info_widget(void) { GtkWidget *vbox, *hbox; From a2c2c7e1a84a98bd05505f699c3c17baf50304ce Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 27 Jun 2012 14:29:29 -0700 Subject: [PATCH 09/67] Add depth entry to new dive edit dialog Christ, if you look up "Ugly dialog" on Wikipedia, I think it has a picture of this "New dive" thing. Or it should have. But it kind of works. Although with only a "max depth" entry, you can't currently set average depths etc, so SAC-rates etc cannot be calculated for these kinds of dives. And the dive numbering is wrong. We do auto-number new dives that get added at the end, but we do it as we add them, so when you *edit* the dive information (before it has been added) the dive number shows up as "#0". So there's certainly room for improvement here. Signed-off-by: Linus Torvalds --- info.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/info.c b/info.c index 10a31f558..e451c8600 100644 --- a/info.c +++ b/info.c @@ -426,11 +426,12 @@ static time_t dive_time_widget(struct dive *dive) GtkWidget *dialog; GtkWidget *cal, *hbox, *vbox; GtkWidget *h, *m; - GtkWidget *duration; + GtkWidget *duration, *depth; GtkWidget *label; guint yval, mval, dval; struct tm tm; int success; + double depthinterval, val; dialog = gtk_dialog_new_with_buttons("Date and Time", GTK_WINDOW(main_window), @@ -469,6 +470,22 @@ static time_t dive_time_widget(struct dive *dive) gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox), duration, FALSE, FALSE, 0); + /* Depth box */ + hbox = gtk_hbox_new(0, 3); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); + + if (output_units.length == FEET) { + depthinterval = 1.0; + } else { + depthinterval = 0.1; + } + depth = gtk_spin_button_new_with_range (0.0, 1000.0, depthinterval); + + label = gtk_label_new("Depth: "); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), depth, 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) { @@ -485,6 +502,13 @@ static time_t dive_time_widget(struct dive *dive) tm.tm_hour = gtk_spin_button_get_value(GTK_SPIN_BUTTON(h)); tm.tm_min = gtk_spin_button_get_value(GTK_SPIN_BUTTON(m)); + val = gtk_spin_button_get_value(GTK_SPIN_BUTTON(depth)); + if (output_units.length == FEET) { + dive->maxdepth.mm = feet_to_mm(val); + } else { + dive->maxdepth.mm = val * 1000 + 0.5; + } + dive->duration.seconds = gtk_spin_button_get_value(GTK_SPIN_BUTTON(duration))*60; gtk_widget_destroy(dialog); From 162b36f4a5007d9c267743bb2f09ac5adc8da408 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 27 Jun 2012 18:09:26 -0700 Subject: [PATCH 10/67] Make it possible to do "Add Dive" from just the main dive menu No need for right-clicks. It's inconvenient on lots of laptops etc, so allow just using the Dive menu as an alternative. Signed-off-by: Linus Torvalds --- display-gtk.h | 1 + divelist.c | 2 +- gtk-gui.c | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/display-gtk.h b/display-gtk.h index efbf3fd87..4ce05468c 100644 --- a/display-gtk.h +++ b/display-gtk.h @@ -54,6 +54,7 @@ extern const char *divelist_font; extern void set_divelist_font(const char *); extern void import_dialog(GtkWidget *, gpointer); +extern void add_dive_cb(GtkWidget *, gpointer); extern void report_error(GError* error); extern int process_ui_events(void); extern void update_progressbar(progressbar_t *progress, double value); diff --git a/divelist.c b/divelist.c index c67e42a4f..21f343f7f 100644 --- a/divelist.c +++ b/divelist.c @@ -687,7 +687,7 @@ static void row_activated_cb(GtkTreeView *tree_view, edit_dive_info(get_dive(index)); } -static void add_dive_cb(GtkWidget *menuitem, GtkTreeModel *model) +void add_dive_cb(GtkWidget *menuitem, gpointer data) { struct dive *dive; diff --git a/gtk-gui.c b/gtk-gui.c index 679b1e4db..45aa21263 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -626,6 +626,7 @@ static GtkActionEntry menu_items[] = { { "SaveFile", GTK_STOCK_SAVE, NULL, CTRLCHAR "S", NULL, G_CALLBACK(file_save) }, { "Print", GTK_STOCK_PRINT, NULL, CTRLCHAR "P", NULL, G_CALLBACK(do_print) }, { "Import", NULL, "Import", NULL, NULL, G_CALLBACK(import_dialog) }, + { "AddDive", NULL, "Add Dive", NULL, NULL, G_CALLBACK(add_dive_cb) }, { "Preferences", NULL, "Preferences", PREFERENCE_ACCEL, NULL, G_CALLBACK(preferences_dialog) }, { "Renumber", NULL, "Renumber", NULL, NULL, G_CALLBACK(renumber_dialog) }, { "SelectEvents", NULL, "SelectEvents", NULL, NULL, G_CALLBACK(selectevents_dialog) }, @@ -652,6 +653,7 @@ static const gchar* ui_string = " \ \ \ \ + \ \ \ \ From 03174992a8617c9fa3428583713c2e3e12d293a7 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 27 Jun 2012 18:56:41 -0700 Subject: [PATCH 11/67] Make the 'Add Dive' dialog at least slightly less butt-ugly I still suspect that using spinbuttons for the time handling is the wrong way, and I'm a bit surprised the Calendar widget doesn't have a mode where you can see/set the time too. But this makes things at least minimally prettier, and initializes the time entries to the current time (which is obviously not what anybody really wants, but looks a lot better than defaulting to "midnight" or some other random time that *also* won't be what anybody actually wants). I think this might be something we can live with, although I hope somebody with good taste comes along and say "don't use spinbuttons, do this: xyzzy" and makes things look better yet. Also, I have this suspicion that I should put the time/depth/duration stuff to the right of the calendar. Most displays are wider than they are tall, so tall and skinny dialogs are bad especially if you have limited vertical pixels. I still have flashbacks to my netbook-using days, hating applictions that did that. Signed-off-by: Linus Torvalds --- info.c | 66 ++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/info.c b/info.c index e451c8600..1847a49bf 100644 --- a/info.c +++ b/info.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "dive.h" #include "display.h" @@ -420,16 +421,35 @@ int edit_dive_info(struct dive *dive) return success; } +static GtkWidget *frame_box(GtkWidget *vbox, const char *fmt, ...) +{ + va_list ap; + char buffer[64]; + 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; +} + /* Fixme - should do at least depths too - a dive without a depth is kind of pointless */ static time_t dive_time_widget(struct dive *dive) { GtkWidget *dialog; - GtkWidget *cal, *hbox, *vbox; + GtkWidget *cal, *hbox, *vbox, *box; GtkWidget *h, *m; GtkWidget *duration, *depth; GtkWidget *label; guint yval, mval, dval; - struct tm tm; + struct tm tm, *tmp; + struct timeval tv; + time_t time; int success; double depthinterval, val; @@ -443,47 +463,47 @@ static time_t dive_time_widget(struct dive *dive) vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); /* Calendar hbox */ - hbox = gtk_hbox_new(0, 3); - gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); - + hbox = frame_box(vbox, "Date:"); cal = gtk_calendar_new(); gtk_box_pack_start(GTK_BOX(hbox), cal, FALSE, TRUE, 0); - /* Time/duration hbox */ - hbox = gtk_hbox_new(0, 3); - gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); + /* Time hbox */ + hbox = frame_box(vbox, "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); + gettimeofday(&tv, NULL); + time = tv.tv_sec; + tmp = localtime(&time); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(h), tmp->tm_hour); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(m), (tmp->tm_min / 5)*5); + gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(h), TRUE); gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(m), TRUE); - duration = gtk_spin_button_new_with_range (0.0, 1000.0, 1.0); - - gtk_box_pack_start(GTK_BOX(hbox), h, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(hbox), m, FALSE, FALSE, 0); label = gtk_label_new(":"); - gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(hbox), m, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(hbox), h, FALSE, FALSE, 0); - label = gtk_label_new(" Duration:"); - gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(hbox), duration, FALSE, FALSE, 0); + hbox = gtk_hbox_new(TRUE, 3); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + /* Duration hbox */ + box = frame_box(hbox, "Duration (min)"); + duration = gtk_spin_button_new_with_range (0.0, 1000.0, 1.0); + gtk_box_pack_end(GTK_BOX(box), duration, FALSE, FALSE, 0); /* Depth box */ - hbox = gtk_hbox_new(0, 3); - gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); - + box = frame_box(hbox, "Depth (%s):", output_units.length == FEET ? "ft" : "m"); if (output_units.length == FEET) { depthinterval = 1.0; } else { depthinterval = 0.1; } depth = gtk_spin_button_new_with_range (0.0, 1000.0, depthinterval); - - label = gtk_label_new("Depth: "); - gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(hbox), depth, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(box), depth, FALSE, FALSE, 0); /* All done, show it and wait for editing */ gtk_widget_show_all(dialog); From 4033625567eafbb2c3c69da9189a0b0c87b74abc Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 30 Jun 2012 20:12:11 -0700 Subject: [PATCH 12/67] Fix a couple of possible divide-by-zero conditions in statistics Several people reported the average time problem, but there's another one lurking there too: if the dive duration is zero, you get bogus average depth information too (but because that one was a floating point divide, and by default they are unsignalling on x86, it didn't crash, it just resulted in bogus results). Signed-off-by: Linus Torvalds --- statistics.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/statistics.c b/statistics.c index 34f487b52..19105653c 100644 --- a/statistics.c +++ b/statistics.c @@ -91,6 +91,18 @@ static void process_dive(struct dive *dp, stats_t *stats) stats->max_depth.mm = dp->maxdepth.mm; if (stats->min_depth.mm == 0 || dp->maxdepth.mm < stats->min_depth.mm) stats->min_depth.mm = dp->maxdepth.mm; + if (dp->watertemp.mkelvin) { + if (stats->min_temp == 0 || dp->watertemp.mkelvin < stats->min_temp) + stats->min_temp = dp->watertemp.mkelvin; + if (dp->watertemp.mkelvin > stats->max_temp) + stats->max_temp = dp->watertemp.mkelvin; + stats->combined_temp += get_temp_units(dp->watertemp.mkelvin, &unit); + stats->combined_count++; + } + + /* Maybe we should drop zero-duration dives */ + if (!dp->duration.seconds) + return; stats->avg_depth.mm = (1.0 * old_tt * stats->avg_depth.mm + dp->duration.seconds * dp->meandepth.mm) / stats->total_time.seconds; if (dp->sac > 2800) { /* less than .1 cuft/min (2800ml/min) is bogus */ @@ -103,14 +115,6 @@ static void process_dive(struct dive *dp, stats_t *stats) stats->min_sac.mliter = dp->sac; stats->total_sac_time = sac_time; } - if (dp->watertemp.mkelvin) { - if (stats->min_temp == 0 || dp->watertemp.mkelvin < stats->min_temp) - stats->min_temp = dp->watertemp.mkelvin; - if (dp->watertemp.mkelvin > stats->max_temp) - stats->max_temp = dp->watertemp.mkelvin; - stats->combined_temp += get_temp_units(dp->watertemp.mkelvin, &unit); - stats->combined_count++; - } } static void process_all_dives(struct dive *dive, struct dive **prev_dive) @@ -266,7 +270,7 @@ static void show_single_dive_stats(struct dive *dive) static void show_total_dive_stats(struct dive *dive) { double value; - int decimals; + int decimals, seconds; const char *unit; stats_t *stats_ptr; @@ -287,7 +291,10 @@ static void show_total_dive_stats(struct dive *dive) 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)); - set_label(stats_w.avg_time, get_time_string(stats_ptr->total_time.seconds / stats_ptr->selection_size, 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); From a3ead9fb862207749d23368db9b857559c0dde78 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 10 Jul 2012 12:33:44 -0700 Subject: [PATCH 13/67] Update for libdivecomputer pkg-config include file changes Subsurface doesn't compile on OS X any more, because libdivecomputer changed the way the header inclusion works: the include path from pkg-config no longer includes the final "libdivecomputer" component, and instead of doing #include for libdivecomputer headers, we're now supposed to do #include instead. Which is cleaner anyway. The reason this only bit us on OS X is that I never trusted pkg-config that much for non-system libraries on Linux (maybe it works, maybe it doesn't, I've seen it go both ways), so on Linux we just used our own version of the include path, and thus weren't affected by the libdivecomputer config change. Clean up the includes while at it - we no longer need (or want) the device-specific header files, since we just use the generic functions. Reported-by: Grischa Toedt Signed-off-by: Linus Torvalds --- Makefile | 8 ++++---- libdivecomputer.h | 14 +++----------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 20dff4c85..5bef6f2b0 100644 --- a/Makefile +++ b/Makefile @@ -51,19 +51,19 @@ libdc-usr64 := $(wildcard /usr/lib64/libdivecomputer.a) ifneq ($(strip $(libdc-local)),) LIBDIVECOMPUTERDIR = /usr/local - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include/libdivecomputer + LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib/libdivecomputer.a else ifneq ($(strip $(libdc-local64)),) LIBDIVECOMPUTERDIR = /usr/local - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include/libdivecomputer + LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib64/libdivecomputer.a else ifneq ($(strip $(libdc-usr)),) LIBDIVECOMPUTERDIR = /usr - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include/libdivecomputer + LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib/libdivecomputer.a else ifneq ($(strip $(libdc-usr64)),) LIBDIVECOMPUTERDIR = /usr - LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include/libdivecomputer + LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib64/libdivecomputer.a else $(error Cannot find libdivecomputer - please edit Makefile) diff --git a/libdivecomputer.h b/libdivecomputer.h index 633b3b1b1..8d77a25be 100644 --- a/libdivecomputer.h +++ b/libdivecomputer.h @@ -2,17 +2,9 @@ #define LIBDIVECOMPUTER_H /* libdivecomputer */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include /* handling uemis Zurich SDA files */ #include "uemis.h" From 7fe652ab5738717ba443ae9de2b8f437103fd71b Mon Sep 17 00:00:00 2001 From: Andrew Clayton Date: Thu, 12 Jul 2012 23:28:47 +0100 Subject: [PATCH 14/67] file.c: Fix a file descriptor leak in readfile() In file.c::readfile() the file was being opened once at fd declaration time and then again a few lines later and only being closed once. Remove the open() at fd declaration time leaving the later one where the fd check is done. Signed-off-by: Andrew Clayton Signed-off-by: Linus Torvalds --- file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/file.c b/file.c index 538f5c783..e0163909e 100644 --- a/file.c +++ b/file.c @@ -10,7 +10,7 @@ static int readfile(const char *filename, struct memblock *mem) { - int ret, fd = open(filename, O_RDONLY); + int ret, fd; struct stat st; char *buf; From 618a20ba5f2a9adc0e5a35117535f8eaa9fd34a4 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Sun, 29 Jul 2012 12:13:36 +0300 Subject: [PATCH 15/67] Divide the panes evenly in view_three There was a note by Linus that he doesn't know how to get the size, so I'm fixing that. Signed-off-by: Mikko Rasa --- gtk-gui.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gtk-gui.c b/gtk-gui.c index 45aa21263..bdf2952f0 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -609,11 +609,13 @@ static void view_info(GtkWidget *w, gpointer data) gtk_paned_set_position(GTK_PANED(hpane), 65535); } -/* Ooh. I don't know how to get the half-way size. So I'm just using random numbers */ static void view_three(GtkWidget *w, gpointer data) { - gtk_paned_set_position(GTK_PANED(hpane), 400); - gtk_paned_set_position(GTK_PANED(vpane), 200); + GtkAllocation alloc; + gtk_widget_get_allocation(hpane, &alloc); + gtk_paned_set_position(GTK_PANED(hpane), alloc.width/2); + gtk_widget_get_allocation(vpane, &alloc); + gtk_paned_set_position(GTK_PANED(vpane), alloc.height/2); } static GtkActionEntry menu_items[] = { From a5e822a4d6f742afe2b4b2e6a856af3063b5ffe1 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Sun, 29 Jul 2012 12:52:51 +0300 Subject: [PATCH 16/67] Improved depth info for dives without samples This calculates a mean depth for the dive with a fixed ascent/descent rate and an assumption that all of the bottom time is at the maximum depth. It's not much, but it allows some derived values such as SAC to make more sense. The depth profile for such dives is now also generated with the same assumptions instead of putting the samples at fixed percentages of the dive duration. Signed-off-by: Mikko Rasa --- dive.c | 5 +++++ profile.c | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/dive.c b/dive.c index 9f57aed58..36ee8e78c 100644 --- a/dive.c +++ b/dive.c @@ -451,7 +451,12 @@ struct dive *fixup_dive(struct dive *dive) } } if (end < 0) + { + /* Assume an ascent/descent rate of 9 m/min */ + int asc_desc_time = dive->maxdepth.mm*60/9000; + dive->meandepth.mm = dive->maxdepth.mm*(dive->duration.seconds-asc_desc_time)/dive->duration.seconds; return dive; + } update_duration(&dive->duration, end - start); if (start != end) diff --git a/profile.c b/profile.c index 137ed6f88..614822809 100644 --- a/profile.c +++ b/profile.c @@ -1354,12 +1354,15 @@ void plot(struct graphics_context *gc, cairo_rectangle_int_t *drawing_area, stru int nr = dive->samples; if (!nr) { + /* The dive has no samples, so create a few fake ones. This assumes an + ascent/descent rate of 9 m/min, which is just below the limit for FAST. */ int duration = dive->duration.seconds; int maxdepth = dive->maxdepth.mm; + int asc_desc_time = dive->maxdepth.mm*60/9000; sample = fake; - fake[1].time.seconds = duration * 0.05; + fake[1].time.seconds = asc_desc_time; fake[1].depth.mm = maxdepth; - fake[2].time.seconds = duration * 0.95; + fake[2].time.seconds = duration - asc_desc_time; fake[2].depth.mm = maxdepth; fake[3].time.seconds = duration * 1.00; nr = 4; From d8c8ada6c7f3952394989b6a705624d94e956d7a Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Sun, 29 Jul 2012 13:15:04 +0300 Subject: [PATCH 17/67] Changes to menu icons It's customary for menu bars to not have icons. Some items were lacking icons when there's perfectly good stock icons available. I was a bit torn between the "new" and "add" icons for the "add dive" item, since what it really does is create a new dive, but the "add" icon is an uninteresting sheet of paper in the default icon theme so I decided to use the "add" icon. Signed-off-by: Mikko Rasa --- divelist.c | 6 ++++-- gtk-gui.c | 14 +++++++------- info.c | 16 ++++++++++++---- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/divelist.c b/divelist.c index 21f343f7f..27d24243b 100644 --- a/divelist.c +++ b/divelist.c @@ -702,10 +702,12 @@ void add_dive_cb(GtkWidget *menuitem, gpointer data) static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button) { - GtkWidget *menu, *menuitem; + GtkWidget *menu, *menuitem, *image; menu = gtk_menu_new(); - menuitem = gtk_menu_item_new_with_label("Add dive"); + 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), model); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); gtk_widget_show_all(menu); diff --git a/gtk-gui.c b/gtk-gui.c index bdf2952f0..a76b0024a 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -619,17 +619,17 @@ static void view_three(GtkWidget *w, gpointer data) } static GtkActionEntry menu_items[] = { - { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL}, - { "LogMenuAction", GTK_STOCK_FILE, "Log", NULL, NULL, NULL}, - { "ViewMenuAction", GTK_STOCK_FILE, "View", NULL, NULL, NULL}, - { "FilterMenuAction", GTK_STOCK_FILE, "Filter", NULL, NULL, NULL}, - { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL}, + { "FileMenuAction", NULL, "File", NULL, NULL, NULL}, + { "LogMenuAction", NULL, "Log", NULL, NULL, NULL}, + { "ViewMenuAction", NULL, "View", NULL, NULL, NULL}, + { "FilterMenuAction", NULL, "Filter", NULL, NULL, NULL}, + { "HelpMenuAction", NULL, "Help", NULL, NULL, NULL}, { "OpenFile", GTK_STOCK_OPEN, NULL, CTRLCHAR "O", NULL, G_CALLBACK(file_open) }, { "SaveFile", GTK_STOCK_SAVE, NULL, CTRLCHAR "S", NULL, G_CALLBACK(file_save) }, { "Print", GTK_STOCK_PRINT, NULL, CTRLCHAR "P", NULL, G_CALLBACK(do_print) }, { "Import", NULL, "Import", NULL, NULL, G_CALLBACK(import_dialog) }, - { "AddDive", NULL, "Add Dive", NULL, NULL, G_CALLBACK(add_dive_cb) }, - { "Preferences", NULL, "Preferences", PREFERENCE_ACCEL, NULL, G_CALLBACK(preferences_dialog) }, + { "AddDive", GTK_STOCK_ADD, "Add Dive", NULL, NULL, G_CALLBACK(add_dive_cb) }, + { "Preferences", GTK_STOCK_PREFERENCES, "Preferences", PREFERENCE_ACCEL, NULL, G_CALLBACK(preferences_dialog) }, { "Renumber", NULL, "Renumber", NULL, NULL, G_CALLBACK(renumber_dialog) }, { "SelectEvents", NULL, "SelectEvents", NULL, NULL, G_CALLBACK(selectevents_dialog) }, { "Quit", GTK_STOCK_QUIT, NULL, CTRLCHAR "Q", NULL, G_CALLBACK(quit) }, diff --git a/info.c b/info.c index 1847a49bf..e935823b2 100644 --- a/info.c +++ b/info.c @@ -139,9 +139,17 @@ static void info_menu_delete_cb(GtkMenuItem *menuitem, gpointer user_data) delete_dive_info(current_dive); } -static void add_menu_item(GtkMenu *menu, const char *label, void (*cb)(GtkMenuItem *, gpointer)) +static void add_menu_item(GtkMenu *menu, const char *label, const char *icon, void (*cb)(GtkMenuItem *, gpointer)) { - GtkWidget *item = gtk_menu_item_new_with_label(label); + 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_prepend(menu, item); @@ -149,8 +157,8 @@ static void add_menu_item(GtkMenu *menu, const char *label, void (*cb)(GtkMenuIt static void populate_popup_cb(GtkTextView *entry, GtkMenu *menu, gpointer user_data) { - add_menu_item(menu, "Delete", info_menu_delete_cb); - add_menu_item(menu, "Edit", info_menu_edit_cb); + add_menu_item(menu, "Delete", GTK_STOCK_DELETE, info_menu_delete_cb); + add_menu_item(menu, "Edit", GTK_STOCK_EDIT, info_menu_edit_cb); } static GtkEntry *text_value(GtkWidget *box, const char *label) From 549708c6eaab2d0592c3e520e0debd82d64619b4 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Tue, 31 Jul 2012 20:55:41 +0300 Subject: [PATCH 18/67] Add a separate "Save as" entry to the menu The "Save" entry will now automatically save over the last used file. If no filename has been set, then that entry will also prompt the user for a filename. The filename is set when saving as well, so the next save will use the same filename. Signed-off-by: Mikko Rasa --- gtk-gui.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/gtk-gui.c b/gtk-gui.c index a76b0024a..bef03a04b 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -170,7 +170,7 @@ static void file_open(GtkWidget *w, gpointer data) gtk_widget_destroy(dialog); } -static void file_save(GtkWidget *w, gpointer data) +static void file_save_as(GtkWidget *w, gpointer data) { GtkWidget *dialog; dialog = gtk_file_chooser_dialog_new("Save File", @@ -189,12 +189,21 @@ static void file_save(GtkWidget *w, gpointer data) char *filename; filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); save_dives(filename); + set_filename(filename); g_free(filename); mark_divelist_changed(FALSE); } gtk_widget_destroy(dialog); } +static void file_save(GtkWidget *w, gpointer data) +{ + if (!existing_filename) + return file_save_as(w, data); + + save_dives(existing_filename); +} + static void ask_save_changes() { GtkWidget *dialog, *label, *content; @@ -626,6 +635,7 @@ static GtkActionEntry menu_items[] = { { "HelpMenuAction", NULL, "Help", NULL, NULL, NULL}, { "OpenFile", GTK_STOCK_OPEN, NULL, CTRLCHAR "O", NULL, G_CALLBACK(file_open) }, { "SaveFile", GTK_STOCK_SAVE, NULL, CTRLCHAR "S", NULL, G_CALLBACK(file_save) }, + { "SaveFileAs", GTK_STOCK_SAVE_AS, NULL, CTRLCHAR "S", NULL, G_CALLBACK(file_save_as) }, { "Print", GTK_STOCK_PRINT, NULL, CTRLCHAR "P", NULL, G_CALLBACK(do_print) }, { "Import", NULL, "Import", NULL, NULL, G_CALLBACK(import_dialog) }, { "AddDive", GTK_STOCK_ADD, "Add Dive", NULL, NULL, G_CALLBACK(add_dive_cb) }, @@ -647,6 +657,7 @@ static const gchar* ui_string = " \ \ \ \ + \ \ \ \ @@ -1187,7 +1198,9 @@ void update_progressbar_text(progressbar_t *progress, const char *text) void set_filename(const char *filename) { - if (!existing_filename && filename) + if (existing_filename) + free(existing_filename); + existing_filename = NULL; + if (filename) existing_filename = strdup(filename); - return; } From 8cdf1ab59fed02d4740c13f0906dbdbc85043a53 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 6 Aug 2012 12:55:55 -0700 Subject: [PATCH 19/67] Remember the last weight used per weightsystem With this change, if the user adds a new weightsystem to a dive, on subsequent edits the weight amount for this weightsystem no longer defaults to 0 but to the last weight that was used with this weightsystem. So when the program imports a set of dives from the divecomputer and the user starts editing them, once they enter the weight for the "integrated" weightsystem the first time, for each of the consecutive dives that same weight is the default once "integrated" is selected - which usually will be the correct amount. Signed-off-by: Dirk Hohndel --- equipment.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/equipment.c b/equipment.c index b74d85b7b..9c8ee49a9 100644 --- a/equipment.c +++ b/equipment.c @@ -308,13 +308,15 @@ static GtkTreeIter *add_weightsystem_type(const char *desc, int weight, GtkTreeI model = GTK_TREE_MODEL(weightsystem_model); gtk_tree_model_foreach(model, match_desc, (void *)desc); - if (!found_match) { - GtkListStore *store = GTK_LIST_STORE(model); - - gtk_list_store_append(store, iter); - gtk_list_store_set(store, iter, - 0, desc, - 1, weight, + if (found_match) { + gtk_list_store_set(GTK_LIST_STORE(model), found_match, + WS_WEIGHT, weight, + -1); + } else { + gtk_list_store_append(GTK_LIST_STORE(model), iter); + gtk_list_store_set(GTK_LIST_STORE(model), iter, + WS_DESC, desc, + WS_WEIGHT, weight, -1); return iter; } @@ -623,6 +625,7 @@ static void record_weightsystem_changes(weightsystem_t *ws, struct ws_widget *we GtkComboBox *box; int grams; double value; + GtkTreeIter iter; /* Ignore uninitialized cylinder widgets */ box = weightsystem_widget->description; @@ -638,6 +641,7 @@ static void record_weightsystem_changes(weightsystem_t *ws, struct ws_widget *we grams = value * 1000; ws->weight.grams = grams; ws->description = desc; + add_weightsystem_type(desc, grams, &iter); } /* From 4912951e97b4040941944e1df70a91366a6a5559 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 6 Aug 2012 13:56:46 -0700 Subject: [PATCH 20/67] Remove weightsystem entry with no description This existed in the initial implementation to deal with an implementation problem that was long since resolved. So now it just created just an ugly empty line in the drop down menu for weightsystems. Signed-off-by: Dirk Hohndel --- equipment.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/equipment.c b/equipment.c index 9c8ee49a9..165b9d77c 100644 --- a/equipment.c +++ b/equipment.c @@ -312,7 +312,7 @@ static GtkTreeIter *add_weightsystem_type(const char *desc, int weight, GtkTreeI gtk_list_store_set(GTK_LIST_STORE(model), found_match, WS_WEIGHT, weight, -1); - } else { + } else if (desc && desc[0]) { gtk_list_store_append(GTK_LIST_STORE(model), iter); gtk_list_store_set(GTK_LIST_STORE(model), iter, WS_DESC, desc, @@ -748,8 +748,6 @@ static struct ws_info { const char *name; int grams; } ws_info[100] = { - /* Need an empty entry for the no weight system case */ - { "", }, { "integrated", 0 }, { "belt", 0 }, { "ankle", 0 }, From 39f606350b6025cb7c5cf9e657d7ef092eb026d7 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 6 Aug 2012 14:03:24 -0700 Subject: [PATCH 21/67] Fill the list of weightsystems from data in existing dives This was simply an omission in the current implementation. All the plumbing was there but never got hooked up with the fixup_dive function as intended. Signed-off-by: Dirk Hohndel --- dive.c | 4 ++++ dive.h | 1 + 2 files changed, 5 insertions(+) diff --git a/dive.c b/dive.c index 9f57aed58..57a735c8e 100644 --- a/dive.c +++ b/dive.c @@ -472,6 +472,10 @@ struct dive *fixup_dive(struct dive *dive) if (same_rounded_pressure(cyl->sample_end, cyl->end)) cyl->end.mbar = 0; } + for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) { + weightsystem_t *ws = dive->weightsystem + i; + add_weightsystem_description(ws); + } return dive; } diff --git a/dive.h b/dive.h index a25fafb39..30894e506 100644 --- a/dive.h +++ b/dive.h @@ -338,6 +338,7 @@ extern void exit_ui(void); extern void report_error(GError* error); extern void add_cylinder_description(cylinder_type_t *); +extern void add_weightsystem_description(weightsystem_t *); extern void add_people(const char *string); extern void add_location(const char *string); extern void remember_event(const char *eventname); From 19621bf481c68955184c11dd407c59af4a05130e Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Tue, 7 Aug 2012 11:24:40 -0700 Subject: [PATCH 22/67] Add total weight column to divelist This adds the total weight carried on the dive in different weight systems to the divelist. The column is by default not shown, which can be changed in the preferences. The column is sortable. Signed-off-by: Dirk Hohndel --- display-gtk.h | 1 + dive.c | 22 ++++++++++++++++++++++ dive.h | 5 +++-- divelist.c | 41 ++++++++++++++++++++++++++++++++++++++++- gtk-gui.c | 8 ++++++++ 5 files changed, 74 insertions(+), 3 deletions(-) diff --git a/display-gtk.h b/display-gtk.h index 4ce05468c..9f1b51771 100644 --- a/display-gtk.h +++ b/display-gtk.h @@ -14,6 +14,7 @@ typedef struct { typedef struct { gboolean cylinder; gboolean temperature; + gboolean totalweight; gboolean nitrox; gboolean sac; gboolean otu; diff --git a/dive.c b/dive.c index 57a735c8e..a420a3ef6 100644 --- a/dive.c +++ b/dive.c @@ -120,6 +120,28 @@ double get_depth_units(unsigned int mm, int *frac, const char **units) return d; } +double get_weight_units(unsigned int grams, int *frac, const char **units) +{ + int decimals; + double value; + const char* unit; + + if (output_units.weight == LBS) { + value = grams_to_lbs(grams); + unit = "lbs"; + decimals = 0; + } else { + value = grams / 1000.0; + unit = "kg"; + decimals = 1; + } + if (frac) + *frac = decimals; + if (units) + *units = unit; + return value; +} + struct dive *alloc_dive(void) { const int initial_samples = 5; diff --git a/dive.h b/dive.h index 30894e506..8a867ad0d 100644 --- a/dive.h +++ b/dive.h @@ -97,8 +97,9 @@ extern int weightsystem_none(void *_data); extern int get_pressure_units(unsigned int mb, const char **units); extern double get_depth_units(unsigned int mm, int *frac, const char **units); -extern double get_volume_units(unsigned int mm, int *frac, const char **units); -extern double get_temp_units(unsigned int mm, const char **units); +extern double get_volume_units(unsigned int ml, int *frac, const char **units); +extern double get_temp_units(unsigned int mk, const char **units); +extern double get_weight_units(unsigned int grams, int *frac, const char **units); static inline double grams_to_lbs(int grams) { diff --git a/divelist.c b/divelist.c index 21f343f7f..ce61d20d1 100644 --- a/divelist.c +++ b/divelist.c @@ -26,7 +26,7 @@ struct DiveList { GtkWidget *container_widget; GtkListStore *model; GtkTreeViewColumn *nr, *date, *stars, *depth, *duration, *location; - GtkTreeViewColumn *temperature, *cylinder, *nitrox, *sac, *otu; + GtkTreeViewColumn *temperature, *cylinder, *totalweight, *nitrox, *sac, *otu; int changed; }; @@ -44,6 +44,7 @@ enum { DIVE_DEPTH, /* int: dive->maxdepth in mm */ DIVE_DURATION, /* int: in seconds */ DIVE_TEMPERATURE, /* int: in mkelvin */ + DIVE_TOTALWEIGHT, /* int: in grams */ DIVE_CYLINDER, DIVE_NITROX, /* int: dummy */ DIVE_SAC, /* int: in ml/min */ @@ -269,6 +270,35 @@ newmax: *o2low_p = mino2; } +static int total_weight(struct dive *dive) +{ + int i, total_grams = 0; + + if (dive) + for (i=0; i< MAX_WEIGHTSYSTEMS; i++) + total_grams += dive->weightsystem[i].weight.grams; + 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); + 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, @@ -521,6 +551,7 @@ static void fill_one_dive(struct dive *dive, DIVE_RATING, dive->rating, DIVE_SAC, dive->sac, DIVE_OTU, dive->otu, + DIVE_TOTALWEIGHT, total_weight(dive), -1); } @@ -569,6 +600,9 @@ void update_dive_list_units(void) (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); } @@ -576,6 +610,7 @@ void update_dive_list_col_visibility(void) { gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder); gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature); + gtk_tree_view_column_set_visible(dive_list.totalweight, visible_cols.totalweight); gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox); gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac); gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu); @@ -604,6 +639,7 @@ static void fill_dive_list(void) DIVE_DURATION, dive->duration.seconds, DIVE_LOCATION, "location", DIVE_TEMPERATURE, dive->watertemp.mkelvin, + DIVE_TOTALWEIGHT, 0, DIVE_SAC, 0, -1); } @@ -636,6 +672,7 @@ static struct divelist_column { [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT }, [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT }, [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature }, + [DIVE_TOTALWEIGHT] = { "lbs", weight_data_func, NULL, ALIGN_RIGHT, &visible_cols.totalweight }, [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder }, [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox }, [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac }, @@ -742,6 +779,7 @@ GtkWidget *dive_list_create(void) G_TYPE_INT, /* Depth */ G_TYPE_INT, /* Duration */ G_TYPE_INT, /* Temperature */ + G_TYPE_INT, /* Total weight */ G_TYPE_STRING, /* Cylinder */ G_TYPE_INT, /* Nitrox */ G_TYPE_INT, /* SAC */ @@ -762,6 +800,7 @@ GtkWidget *dive_list_create(void) 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.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); diff --git a/gtk-gui.c b/gtk-gui.c index 45aa21263..0dc2f9734 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -348,6 +348,7 @@ OPTIONCALLBACK(otu_toggle, visible_cols.otu) OPTIONCALLBACK(sac_toggle, visible_cols.sac) OPTIONCALLBACK(nitrox_toggle, visible_cols.nitrox) OPTIONCALLBACK(temperature_toggle, visible_cols.temperature) +OPTIONCALLBACK(totalweight_toggle, visible_cols.totalweight) OPTIONCALLBACK(cylinder_toggle, visible_cols.cylinder) static void event_toggle(GtkWidget *w, gpointer _data) @@ -434,6 +435,11 @@ static void preferences_dialog(GtkWidget *w, gpointer data) 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("Show Weight"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), 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); + font = gtk_font_button_new_with_font(divelist_font); gtk_box_pack_start(GTK_BOX(vbox), font, FALSE, FALSE, 5); @@ -457,6 +463,7 @@ static void preferences_dialog(GtkWidget *w, gpointer data) subsurface_set_conf("fahrenheit", PREF_BOOL, BOOL_TO_PTR(output_units.temperature == FAHRENHEIT)); subsurface_set_conf("lbs", PREF_BOOL, BOOL_TO_PTR(output_units.weight == LBS)); subsurface_set_conf("TEMPERATURE", PREF_BOOL, BOOL_TO_PTR(visible_cols.temperature)); + subsurface_set_conf("TOTALWEIGHT", PREF_BOOL, BOOL_TO_PTR(visible_cols.totalweight)); subsurface_set_conf("CYLINDER", PREF_BOOL, BOOL_TO_PTR(visible_cols.cylinder)); subsurface_set_conf("NITROX", PREF_BOOL, BOOL_TO_PTR(visible_cols.nitrox)); subsurface_set_conf("SAC", PREF_BOOL, BOOL_TO_PTR(visible_cols.sac)); @@ -726,6 +733,7 @@ void init_ui(int *argcp, char ***argvp) /* an unset key is FALSE - all these are hidden by default */ visible_cols.cylinder = PTR_TO_BOOL(subsurface_get_conf("CYLINDER", PREF_BOOL)); visible_cols.temperature = PTR_TO_BOOL(subsurface_get_conf("TEMPERATURE", PREF_BOOL)); + visible_cols.totalweight = PTR_TO_BOOL(subsurface_get_conf("TOTALWEIGHT", PREF_BOOL)); visible_cols.nitrox = PTR_TO_BOOL(subsurface_get_conf("NITROX", PREF_BOOL)); visible_cols.otu = PTR_TO_BOOL(subsurface_get_conf("OTU", PREF_BOOL)); visible_cols.sac = PTR_TO_BOOL(subsurface_get_conf("SAC", PREF_BOOL)); From 3c542b5a418751f22511f6b1d554252c6e472a43 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Fri, 10 Aug 2012 13:43:16 -0700 Subject: [PATCH 23/67] Don't print a total weight of 0 in the weight column For consistency with the rest of the dive_list we should interpret "no weight systems recorded" as "no information" and therefore print nothing instead of printing a total weight of "0" for these dives. Signed-off-by: Dirk Hohndel --- divelist.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/divelist.c b/divelist.c index ce61d20d1..1d31da567 100644 --- a/divelist.c +++ b/divelist.c @@ -294,7 +294,10 @@ static void weight_data_func(GtkTreeViewColumn *col, gtk_tree_model_get(model, iter, DIVE_INDEX, &indx, -1); dive = get_dive(indx); value = get_weight_units(total_weight(dive), &decimals, NULL); - snprintf(buffer, sizeof(buffer), "%.*f", decimals, value); + if (value == 0.0) + *buffer = '\0'; + else + snprintf(buffer, sizeof(buffer), "%.*f", decimals, value); g_object_set(renderer, "text", buffer, NULL); } From 1f3813eb3d0824c9eff4cf9b02e3d34c1c6db71c Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Wed, 8 Aug 2012 09:35:38 -0700 Subject: [PATCH 24/67] Allow date based grouping This is the very first rough cut. It switches things over to a tree model so we can have date based summary nodes. It uses a DIVE_INDEX of -1 for summary nodes to easily tell them apart from actual dives. All the data functions are changed so the summary nodes only show the date they cover. The commit also adds a couple of debug functions to be able to easily peek into the model from the debugger. Lots of things left to do. There is no longer a first dive selected when starting subsurface. Sorting by columns other than date is messed up. We almost certainly want month and year summary entries as well. Signed-off-by: Dirk Hohndel --- divelist.c | 260 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 187 insertions(+), 73 deletions(-) diff --git a/divelist.c b/divelist.c index 21f343f7f..8899d7429 100644 --- a/divelist.c +++ b/divelist.c @@ -24,7 +24,7 @@ struct DiveList { GtkWidget *tree_view; GtkWidget *container_widget; - GtkListStore *model; + GtkTreeStore *model; GtkTreeViewColumn *nr, *date, *stars, *depth, *duration, *location; GtkTreeViewColumn *temperature, *cylinder, *nitrox, *sac, *otu; int changed; @@ -52,6 +52,26 @@ enum { DIVELIST_COLUMNS }; +#ifdef DEBUG_MODEL +static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) +{ + char *location; + int idx, nr, rating, depth; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, DIVE_RATING, &rating, DIVE_DEPTH, &depth, DIVE_LOCATION, &location, -1); + printf("entry #%d : nr %d rating %d depth %d location %s \n", idx, nr, rating, depth, location); + free(location); + + return FALSE; +} + +static void dump_model(GtkListStore *store) +{ + gtk_tree_model_foreach(GTK_TREE_MODEL(store), dump_model_entry, NULL); +} +#endif + static GList *selected_dives; static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model) @@ -77,6 +97,8 @@ static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model) path = g_list_nth_data(selected_dives, 0); if (gtk_tree_model_get_iter(model, &iter, path)) { gtk_tree_model_get_value(model, &iter, DIVE_INDEX, &value); + /* an index of -1 should mean select all dives from that day + * ===> still needs to be implemented */ selected_dive = g_value_get_int(&value); repaint_dive(); } @@ -109,13 +131,17 @@ static void star_data_func(GtkTreeViewColumn *col, GtkTreeIter *iter, gpointer data) { - int nr_stars; + int nr_stars, idx; char buffer[40]; - gtk_tree_model_get(model, iter, DIVE_RATING, &nr_stars, -1); - if (nr_stars < 0 || nr_stars > 5) - nr_stars = 0; - snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]); + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_RATING, &nr_stars, -1); + if (idx == -1) { + *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); } @@ -125,23 +151,30 @@ static void date_data_func(GtkTreeViewColumn *col, GtkTreeIter *iter, gpointer data) { - int val; + int val, idx; struct tm *tm; time_t when; char buffer[40]; - gtk_tree_model_get(model, iter, DIVE_DATE, &val, -1); + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &val, -1); /* 2038 problem */ when = val; tm = gmtime(&when); - snprintf(buffer, sizeof(buffer), - "%s, %s %d, %d %02d:%02d", - weekday(tm->tm_wday), - monthname(tm->tm_mon), - tm->tm_mday, tm->tm_year + 1900, - tm->tm_hour, tm->tm_min); + if (idx == -1) + snprintf(buffer, sizeof(buffer), + "%s, %s %d, %d", + weekday(tm->tm_wday), + monthname(tm->tm_mon), + tm->tm_mday, tm->tm_year + 1900); + else + snprintf(buffer, sizeof(buffer), + "%s, %s %d, %d %02d:%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); } @@ -151,34 +184,37 @@ static void depth_data_func(GtkTreeViewColumn *col, GtkTreeIter *iter, gpointer data) { - int depth, integer, frac, len; + int depth, integer, frac, len, idx; char buffer[40]; - gtk_tree_model_get(model, iter, DIVE_DEPTH, &depth, -1); + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1); - switch (output_units.length) { - case METERS: - /* To tenths of meters */ - depth = (depth + 49) / 100; - integer = depth / 10; - frac = depth % 10; - if (integer < 20) + if (idx == -1) { + *buffer = '\0'; + } else { + switch (output_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; - if (frac >= 5) - integer++; - frac = -1; - break; - case FEET: - integer = mm_to_feet(depth) + 0.5; - frac = -1; - break; - default: - return; + 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); } - 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); } @@ -188,11 +224,14 @@ static void duration_data_func(GtkTreeViewColumn *col, GtkTreeIter *iter, gpointer data) { - unsigned int sec; + unsigned int sec, idx; char buffer[16]; - gtk_tree_model_get(model, iter, DIVE_DURATION, &sec, -1); - snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60); + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1); + if (idx == -1) + *buffer = '\0'; + else + snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60); g_object_set(renderer, "text", buffer, NULL); } @@ -203,13 +242,13 @@ static void temperature_data_func(GtkTreeViewColumn *col, GtkTreeIter *iter, gpointer data) { - int value; + int value, idx; char buffer[80]; - gtk_tree_model_get(model, iter, DIVE_TEMPERATURE, &value, -1); + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1); *buffer = 0; - if (value) { + if (idx != -1 && value) { double deg; switch (output_units.temperature) { case CELSIUS: @@ -227,6 +266,23 @@ static void temperature_data_func(GtkTreeViewColumn *col, g_object_set(renderer, "text", buffer, NULL); } +static void nr_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int idx, nr; + char buffer[40]; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1); + if (idx == -1) + *buffer = '\0'; + else + snprintf(buffer, sizeof(buffer), "%d", nr); + g_object_set(renderer, "text", buffer, NULL); +} + /* * Get "maximal" dive gas for a dive. * Rules: @@ -309,6 +365,10 @@ static void nitrox_data_func(GtkTreeViewColumn *col, struct dive *dive; gtk_tree_model_get(model, iter, DIVE_INDEX, &index, -1); + if (index == -1) { + *buffer = '\0'; + goto exit; + } dive = get_dive(index); get_dive_gas(dive, &o2, &he, &o2low); o2 = (o2 + 5) / 10; @@ -324,7 +384,7 @@ static void nitrox_data_func(GtkTreeViewColumn *col, snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2); else strcpy(buffer, "air"); - +exit: g_object_set(renderer, "text", buffer, NULL); } @@ -335,16 +395,16 @@ static void sac_data_func(GtkTreeViewColumn *col, GtkTreeIter *iter, gpointer data) { - int value; + int value, idx; const char *fmt; char buffer[16]; double sac; - gtk_tree_model_get(model, iter, DIVE_SAC, &value, -1); + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1); - if (!value) { - g_object_set(renderer, "text", "", NULL); - return; + if (idx == -1 || !value) { + *buffer = '\0'; + goto exit; } sac = value / 1000.0; @@ -358,7 +418,7 @@ static void sac_data_func(GtkTreeViewColumn *col, break; } snprintf(buffer, sizeof(buffer), fmt, sac); - +exit: g_object_set(renderer, "text", buffer, NULL); } @@ -369,17 +429,15 @@ static void otu_data_func(GtkTreeViewColumn *col, GtkTreeIter *iter, gpointer data) { - int value; + int value, idx; char buffer[16]; - gtk_tree_model_get(model, iter, DIVE_OTU, &value, -1); + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1); - if (!value) { - g_object_set(renderer, "text", "", NULL); - return; - } - - snprintf(buffer, sizeof(buffer), "%d", value); + if (idx == -1 || !value) + *buffer = '\0'; + else + snprintf(buffer, sizeof(buffer), "%d", value); g_object_set(renderer, "text", buffer, NULL); } @@ -514,7 +572,7 @@ static void fill_one_dive(struct dive *dive, get_cylinder(dive, &cylinder); get_location(dive, &location); - gtk_list_store_set(GTK_LIST_STORE(model), iter, + gtk_tree_store_set(GTK_TREE_STORE(model), iter, DIVE_NR, dive->number, DIVE_LOCATION, location, DIVE_CYLINDER, cylinder, @@ -529,12 +587,14 @@ static gboolean set_one_dive(GtkTreeModel *model, GtkTreeIter *iter, gpointer data) { - GValue value = {0, }; + int idx; struct dive *dive; /* Get the dive number */ - gtk_tree_model_get_value(model, iter, DIVE_INDEX, &value); - dive = get_dive(g_value_get_int(&value)); + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + if (idx == -1) + return TRUE; + dive = get_dive(idx); if (!dive) return TRUE; if (data && dive != data) @@ -582,27 +642,79 @@ void update_dive_list_col_visibility(void) return; } +static int new_day(struct dive *dive, struct dive **last_dive, time_t *tm_date) +{ + if (!last_dive) + return TRUE; + if (!*last_dive) { + *last_dive = dive; + if (tm_date) { + struct tm *tm1 = gmtime(&dive->when); + tm1->tm_sec = 0; + tm1->tm_min = 0; + tm1->tm_hour = 0; + *tm_date = mktime(tm1); + } + return TRUE; + } else { + struct dive *ldive = *last_dive; + struct tm tm1, tm2; + (void) gmtime_r(&dive->when, &tm1); + (void) gmtime_r(&ldive->when, &tm2); + if (tm1.tm_year != tm2.tm_year || + tm1.tm_mon != tm2.tm_mon || + tm1.tm_mday != tm2.tm_mday) { + *last_dive = dive; + if (tm_date) { + tm1.tm_sec = 0; + tm1.tm_min = 0; + tm1.tm_hour = 0; + *tm_date = mktime(&tm1); + } + return TRUE; + } + } + return FALSE; + +} + static void fill_dive_list(void) { int i; - GtkTreeIter iter; - GtkListStore *store; + GtkTreeIter iter, parent_iter, *parent = NULL; + GtkTreeStore *store; + struct dive *last_dive = NULL; + time_t dive_date; - store = GTK_LIST_STORE(dive_list.model); + store = GTK_TREE_STORE(dive_list.model); i = dive_table.nr; while (--i >= 0) { struct dive *dive = dive_table.dives[i]; + if (new_day(dive, &last_dive, &dive_date)) + { + gtk_tree_store_append(store, &parent_iter, NULL); + parent = &parent_iter; + gtk_tree_store_set(store, parent, + DIVE_INDEX, -1, + DIVE_NR, -1, + DIVE_DATE, dive_date, + DIVE_LOCATION, "", + DIVE_TEMPERATURE, 0, + DIVE_SAC, 0, + -1); + } update_cylinder_related_info(dive); - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, + gtk_tree_store_append(store, &iter, parent); + gtk_tree_store_set(store, &iter, DIVE_INDEX, i, DIVE_NR, dive->number, DIVE_DATE, dive->when, DIVE_DEPTH, dive->maxdepth, DIVE_DURATION, dive->duration.seconds, - DIVE_LOCATION, "location", + DIVE_LOCATION, dive->location, + DIVE_RATING, dive->rating, DIVE_TEMPERATURE, dive->watertemp.mkelvin, DIVE_SAC, 0, -1); @@ -618,7 +730,7 @@ static void fill_dive_list(void) void dive_list_update_dives(void) { - gtk_list_store_clear(GTK_LIST_STORE(dive_list.model)); + gtk_tree_store_clear(GTK_TREE_STORE(dive_list.model)); fill_dive_list(); repaint_dive(); } @@ -630,7 +742,7 @@ static struct divelist_column { unsigned int flags; int *visible; } dl_column[] = { - [DIVE_NR] = { "#", NULL, NULL, ALIGN_RIGHT | UNSORTABLE }, + [DIVE_NR] = { "#", nr_data_func, NULL, ALIGN_RIGHT | UNSORTABLE }, [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT }, [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT }, [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT }, @@ -653,7 +765,7 @@ static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_c unsigned int flags = col->flags; int *visible = col->visible; GtkWidget *tree_view = dl->tree_view; - GtkListStore *model = dl->model; + GtkTreeStore *model = dl->model; GtkTreeViewColumn *ret; if (visible && !*visible) @@ -684,7 +796,9 @@ static void row_activated_cb(GtkTreeView *tree_view, if (!gtk_tree_model_get_iter(model, &iter, path)) return; gtk_tree_model_get(model, &iter, DIVE_INDEX, &index, -1); - edit_dive_info(get_dive(index)); + /* an index of -1 is special for the "group by date" entries */ + if (index != -1) + edit_dive_info(get_dive(index)); } void add_dive_cb(GtkWidget *menuitem, gpointer data) @@ -734,7 +848,7 @@ GtkWidget *dive_list_create(void) { GtkTreeSelection *selection; - dive_list.model = gtk_list_store_new(DIVELIST_COLUMNS, + dive_list.model = gtk_tree_store_new(DIVELIST_COLUMNS, G_TYPE_INT, /* index */ G_TYPE_INT, /* nr */ G_TYPE_INT, /* Date */ From 5f1e41de4376bef1968a42f48e3fb04bf10a52cb Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 13 Aug 2012 13:09:40 -0700 Subject: [PATCH 25/67] Improve tree model implementation We now support three hierarchy levels: day, month, and year. Each indicated by a negative DIVE_INDEX for -1 to -3. This allows a nice compact overview when doing date based sorting (the default). As indicated in the previous commit, things still go wrong with sorting by other columns as the entries are only sorted within each day, not globally across the whole dive list. Signed-off-by: Dirk Hohndel --- divelist.c | 152 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 88 insertions(+), 64 deletions(-) diff --git a/divelist.c b/divelist.c index 8899d7429..137b8a148 100644 --- a/divelist.c +++ b/divelist.c @@ -52,6 +52,12 @@ enum { DIVELIST_COLUMNS }; +/* magic numbers that indicate (as negative values) model entries that + * are summary entries for day / month / year */ +#define NEW_DAY 1 +#define NEW_MON 2 +#define NEW_YR 3 + #ifdef DEBUG_MODEL static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) @@ -77,7 +83,6 @@ static GList *selected_dives; static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model) { GtkTreeIter iter; - GValue value = {0, }; GtkTreePath *path; int nr_selected = gtk_tree_selection_count_selected_rows(selection); @@ -96,10 +101,23 @@ static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model) amount_selected = 1; path = g_list_nth_data(selected_dives, 0); if (gtk_tree_model_get_iter(model, &iter, path)) { - gtk_tree_model_get_value(model, &iter, DIVE_INDEX, &value); - /* an index of -1 should mean select all dives from that day - * ===> still needs to be implemented */ - selected_dive = g_value_get_int(&value); + gtk_tree_model_get(model, &iter, DIVE_INDEX, &selected_dive, -1); + /* a negative index means we picked a summary entry; + expand that entry and use first real child instead */ + while (selected_dive < 0) { + GtkTreeIter parent; + GtkTreePath *tpath; + 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 */ + return; + if(gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath)) + gtk_tree_view_collapse_row(GTK_TREE_VIEW(dive_list.tree_view), tpath); + else + gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE); + gtk_tree_model_get(model, &iter, DIVE_INDEX, &selected_dive, -1); + } repaint_dive(); } return; @@ -135,7 +153,7 @@ static void star_data_func(GtkTreeViewColumn *col, char buffer[40]; gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_RATING, &nr_stars, -1); - if (idx == -1) { + if (idx < 0) { *buffer = '\0'; } else { if (nr_stars < 0 || nr_stars > 5) @@ -162,19 +180,32 @@ static void date_data_func(GtkTreeViewColumn *col, when = val; tm = gmtime(&when); - if (idx == -1) + switch(idx) { + case -NEW_DAY: snprintf(buffer, sizeof(buffer), "%s, %s %d, %d", weekday(tm->tm_wday), monthname(tm->tm_mon), tm->tm_mday, tm->tm_year + 1900); - else + break; + case -NEW_MON: + snprintf(buffer, sizeof(buffer), + "%s %d", + monthname(tm->tm_mon), + tm->tm_year + 1900); + break; + case -NEW_YR: + snprintf(buffer, sizeof(buffer), + "%d", tm->tm_year + 1900); + break; + default: snprintf(buffer, sizeof(buffer), "%s, %s %d, %d %02d:%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); } @@ -189,7 +220,7 @@ static void depth_data_func(GtkTreeViewColumn *col, gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1); - if (idx == -1) { + if (idx < 0) { *buffer = '\0'; } else { switch (output_units.length) { @@ -224,11 +255,12 @@ static void duration_data_func(GtkTreeViewColumn *col, GtkTreeIter *iter, gpointer data) { - unsigned int sec, idx; + unsigned int sec; + int idx; char buffer[16]; gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1); - if (idx == -1) + if (idx < 0) *buffer = '\0'; else snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60); @@ -248,7 +280,7 @@ static void temperature_data_func(GtkTreeViewColumn *col, gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1); *buffer = 0; - if (idx != -1 && value) { + if (idx >= 0 && value) { double deg; switch (output_units.temperature) { case CELSIUS: @@ -276,7 +308,7 @@ static void nr_data_func(GtkTreeViewColumn *col, char buffer[40]; gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1); - if (idx == -1) + if (idx < 0) *buffer = '\0'; else snprintf(buffer, sizeof(buffer), "%d", nr); @@ -360,16 +392,16 @@ static void nitrox_data_func(GtkTreeViewColumn *col, GtkTreeIter *iter, gpointer data) { - int index, o2, he, o2low; + int idx, o2, he, o2low; char buffer[80]; struct dive *dive; - gtk_tree_model_get(model, iter, DIVE_INDEX, &index, -1); - if (index == -1) { + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + if (idx < 0) { *buffer = '\0'; goto exit; } - dive = get_dive(index); + dive = get_dive(idx); get_dive_gas(dive, &o2, &he, &o2low); o2 = (o2 + 5) / 10; he = (he + 5) / 10; @@ -402,7 +434,7 @@ static void sac_data_func(GtkTreeViewColumn *col, gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1); - if (idx == -1 || !value) { + if (idx < 0 || !value) { *buffer = '\0'; goto exit; } @@ -434,7 +466,7 @@ static void otu_data_func(GtkTreeViewColumn *col, gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1); - if (idx == -1 || !value) + if (idx < 0 || !value) *buffer = '\0'; else snprintf(buffer, sizeof(buffer), "%d", value); @@ -592,8 +624,8 @@ static gboolean set_one_dive(GtkTreeModel *model, /* Get the dive number */ gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); - if (idx == -1) - return TRUE; + if (idx < 0) + return FALSE; dive = get_dive(idx); if (!dive) return TRUE; @@ -642,46 +674,36 @@ void update_dive_list_col_visibility(void) return; } -static int new_day(struct dive *dive, struct dive **last_dive, time_t *tm_date) +static int new_date(struct dive *dive, struct dive **last_dive, const int flag, time_t *tm_date) { if (!last_dive) return TRUE; - if (!*last_dive) { - *last_dive = dive; - if (tm_date) { - struct tm *tm1 = gmtime(&dive->when); - tm1->tm_sec = 0; - tm1->tm_min = 0; - tm1->tm_hour = 0; - *tm_date = mktime(tm1); - } - return TRUE; - } else { + if (*last_dive) { struct dive *ldive = *last_dive; struct tm tm1, tm2; (void) gmtime_r(&dive->when, &tm1); (void) gmtime_r(&ldive->when, &tm2); - if (tm1.tm_year != tm2.tm_year || - tm1.tm_mon != tm2.tm_mon || - tm1.tm_mday != tm2.tm_mday) { - *last_dive = dive; - if (tm_date) { - tm1.tm_sec = 0; - tm1.tm_min = 0; - tm1.tm_hour = 0; - *tm_date = mktime(&tm1); - } - return TRUE; - } + if (tm1.tm_year == tm2.tm_year && + (tm1.tm_mon == tm2.tm_mon || flag > NEW_MON) && + (tm1.tm_mday == tm2.tm_mday || flag > NEW_DAY)) + return FALSE; } - return FALSE; - + if (flag == NEW_DAY) + *last_dive = dive; + if (tm_date) { + struct tm *tm1 = gmtime(&dive->when); + tm1->tm_sec = 0; + tm1->tm_min = 0; + tm1->tm_hour = 0; + *tm_date = mktime(tm1); + } + return TRUE; } static void fill_dive_list(void) { - int i; - GtkTreeIter iter, parent_iter, *parent = NULL; + int i, j; + GtkTreeIter iter, parent_iter[NEW_YR + 2], *parents[NEW_YR + 2] = {NULL, }; GtkTreeStore *store; struct dive *last_dive = NULL; time_t dive_date; @@ -692,21 +714,23 @@ static void fill_dive_list(void) while (--i >= 0) { struct dive *dive = dive_table.dives[i]; - if (new_day(dive, &last_dive, &dive_date)) - { - gtk_tree_store_append(store, &parent_iter, NULL); - parent = &parent_iter; - gtk_tree_store_set(store, parent, - DIVE_INDEX, -1, - DIVE_NR, -1, - DIVE_DATE, dive_date, - DIVE_LOCATION, "", - DIVE_TEMPERATURE, 0, - DIVE_SAC, 0, - -1); + for (j = NEW_YR; j >= NEW_DAY; j--) { + if (new_date(dive, &last_dive, j, &dive_date)) + { + gtk_tree_store_append(store, &parent_iter[j], parents[j+1]); + parents[j] = &parent_iter[j]; + gtk_tree_store_set(store, parents[j], + DIVE_INDEX, -j, + DIVE_NR, -j, + DIVE_DATE, dive_date, + DIVE_LOCATION, "", + DIVE_TEMPERATURE, 0, + DIVE_SAC, 0, + -1); + } } update_cylinder_related_info(dive); - gtk_tree_store_append(store, &iter, parent); + gtk_tree_store_append(store, &iter, parents[NEW_DAY]); gtk_tree_store_set(store, &iter, DIVE_INDEX, i, DIVE_NR, dive->number, @@ -796,8 +820,8 @@ static void row_activated_cb(GtkTreeView *tree_view, if (!gtk_tree_model_get_iter(model, &iter, path)) return; gtk_tree_model_get(model, &iter, DIVE_INDEX, &index, -1); - /* an index of -1 is special for the "group by date" entries */ - if (index != -1) + /* a negative index is special for the "group by date" entries */ + if (index >= 0) edit_dive_info(get_dive(index)); } From 27a505e579057596ab10f7381c471b870ce86f87 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 13 Aug 2012 14:42:55 -0700 Subject: [PATCH 26/67] Create duplicate list model so sorting by columns works again One major downside of the switch to a tree model is that sorting by columns other than date was broken - it would sort the entries within each date which is not all that useful. After playing with some Gtk trickery that would allow us to filter out those rows it quickly became clear that the much easier solution is to simply maintain TWO models (and therefore two storages). This causes some overhead and requires some careful tracking of all changes, but it turned out to be rather straight forward to do. dive_list now has three model related members: model - current model displayed (which is one of the following two) treemodel - the tree model listmodel - the list model One side effect is that the callbacks no longer can pass the model around (as this could have changed since the callback was registered), but that seems only a minor drawback and was easily addressed. The implementation in this commit still has a couple of obvious flaws: when switching back from the list model to the tree model all the expansion state of the rows is lost and we end up with just a list of the different years visible. Also, selections aren't maintained when switching models. Signed-off-by: Dirk Hohndel --- divelist.c | 125 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 95 insertions(+), 30 deletions(-) diff --git a/divelist.c b/divelist.c index 137b8a148..b70b415ca 100644 --- a/divelist.c +++ b/divelist.c @@ -24,7 +24,7 @@ struct DiveList { GtkWidget *tree_view; GtkWidget *container_widget; - GtkTreeStore *model; + GtkTreeStore *model, *listmodel, *treemodel; GtkTreeViewColumn *nr, *date, *stars, *depth, *duration, *location; GtkTreeViewColumn *temperature, *cylinder, *nitrox, *sac, *otu; int changed; @@ -80,7 +80,7 @@ static void dump_model(GtkListStore *store) static GList *selected_dives; -static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model) +static void selection_cb(GtkTreeSelection *selection, gpointer userdata) { GtkTreeIter iter; GtkTreePath *path; @@ -100,23 +100,23 @@ static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model) /* just pick that dive as selected */ amount_selected = 1; path = g_list_nth_data(selected_dives, 0); - if (gtk_tree_model_get_iter(model, &iter, path)) { - gtk_tree_model_get(model, &iter, DIVE_INDEX, &selected_dive, -1); + if (gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path)) { + gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1); /* a negative index means we picked a summary entry; expand that entry and use first real child instead */ while (selected_dive < 0) { GtkTreeIter parent; GtkTreePath *tpath; memcpy(&parent, &iter, sizeof(parent)); - tpath = gtk_tree_model_get_path(model, &parent); - if (!gtk_tree_model_iter_children(model, &iter, &parent)) + tpath = gtk_tree_model_get_path(GTK_TREE_MODEL(dive_list.model), &parent); + if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(dive_list.model), &iter, &parent)) /* we should never have a parent without child */ return; if(gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath)) gtk_tree_view_collapse_row(GTK_TREE_VIEW(dive_list.tree_view), tpath); else gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE); - gtk_tree_model_get(model, &iter, DIVE_INDEX, &selected_dive, -1); + gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1); } repaint_dive(); } @@ -128,7 +128,7 @@ static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model) * I do however want to keep around which dives have * been selected */ amount_selected = g_list_length(selected_dives); - process_selected_dives(selected_dives, model); + process_selected_dives(selected_dives, GTK_TREE_MODEL(dive_list.model)); repaint_dive(); return; } @@ -593,13 +593,21 @@ static void get_cylinder(struct dive *dive, char **str) /* * Set up anything that could have changed due to editing - * of dive information + * 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; + GtkTreeStore *othermodel; get_cylinder(dive, &cylinder); get_location(dive, &location); @@ -612,6 +620,13 @@ static void fill_one_dive(struct dive *dive, DIVE_SAC, dive->sac, DIVE_OTU, dive->otu, -1); + if (model == GTK_TREE_MODEL(dive_list.treemodel)) + othermodel = dive_list.listmodel; + else + othermodel = dive_list.treemodel; + if (othermodel != dive_list.model) + /* recursive call */ + gtk_tree_model_foreach(GTK_TREE_MODEL(othermodel), set_one_dive, dive); } static gboolean set_one_dive(GtkTreeModel *model, @@ -704,11 +719,12 @@ static void fill_dive_list(void) { int i, j; GtkTreeIter iter, parent_iter[NEW_YR + 2], *parents[NEW_YR + 2] = {NULL, }; - GtkTreeStore *store; + GtkTreeStore *liststore, *treestore; struct dive *last_dive = NULL; time_t dive_date; - store = GTK_TREE_STORE(dive_list.model); + treestore = GTK_TREE_STORE(dive_list.treemodel); + liststore = GTK_TREE_STORE(dive_list.listmodel); i = dive_table.nr; while (--i >= 0) { @@ -717,9 +733,9 @@ static void fill_dive_list(void) for (j = NEW_YR; j >= NEW_DAY; j--) { if (new_date(dive, &last_dive, j, &dive_date)) { - gtk_tree_store_append(store, &parent_iter[j], parents[j+1]); + gtk_tree_store_append(treestore, &parent_iter[j], parents[j+1]); parents[j] = &parent_iter[j]; - gtk_tree_store_set(store, parents[j], + gtk_tree_store_set(treestore, parents[j], DIVE_INDEX, -j, DIVE_NR, -j, DIVE_DATE, dive_date, @@ -730,8 +746,20 @@ static void fill_dive_list(void) } } update_cylinder_related_info(dive); - gtk_tree_store_append(store, &iter, parents[NEW_DAY]); - gtk_tree_store_set(store, &iter, + gtk_tree_store_append(treestore, &iter, parents[NEW_DAY]); + 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_RATING, dive->rating, + DIVE_TEMPERATURE, dive->watertemp.mkelvin, + DIVE_SAC, 0, + -1); + gtk_tree_store_append(liststore, &iter, NULL); + gtk_tree_store_set(liststore, &iter, DIVE_INDEX, i, DIVE_NR, dive->number, DIVE_DATE, dive->when, @@ -754,7 +782,8 @@ static void fill_dive_list(void) void dive_list_update_dives(void) { - gtk_tree_store_clear(GTK_TREE_STORE(dive_list.model)); + gtk_tree_store_clear(GTK_TREE_STORE(dive_list.treemodel)); + gtk_tree_store_clear(GTK_TREE_STORE(dive_list.listmodel)); fill_dive_list(); repaint_dive(); } @@ -812,14 +841,14 @@ static void realize_cb(GtkWidget *tree_view, gpointer userdata) static void row_activated_cb(GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, - GtkTreeModel *model) + gpointer userdata) { int index; GtkTreeIter iter; - if (!gtk_tree_model_get_iter(model, &iter, path)) + if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path)) return; - gtk_tree_model_get(model, &iter, DIVE_INDEX, &index, -1); + gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &index, -1); /* a negative index is special for the "group by date" entries */ if (index >= 0) edit_dive_info(get_dive(index)); @@ -844,7 +873,7 @@ static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int menu = gtk_menu_new(); menuitem = gtk_menu_item_new_with_label("Add dive"); - g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), model); + g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); gtk_widget_show_all(menu); @@ -852,27 +881,46 @@ static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button, gtk_get_current_event_time()); } -static void popup_menu_cb(GtkTreeView *tree_view, - GtkTreeModel *model) +static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata) { - popup_divelist_menu(tree_view, model, 0); + popup_divelist_menu(tree_view, GTK_TREE_MODEL(dive_list.model), 0); } -static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, GtkTreeModel *model) +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, 3); + popup_divelist_menu(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(dive_list.model), 3); return TRUE; } return FALSE; } +/* If the sort column is date (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. */ +static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data) +{ + int colid; + GtkSortType order; + GtkTreeStore *currentmodel = dive_list.model; + + gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order); + if(colid == DIVE_DATE) + dive_list.model = dive_list.treemodel; + else + dive_list.model = dive_list.listmodel; + if (dive_list.model != currentmodel) { + gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model)); + gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, order); + } +} + GtkWidget *dive_list_create(void) { GtkTreeSelection *selection; - dive_list.model = gtk_tree_store_new(DIVELIST_COLUMNS, + dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS, G_TYPE_INT, /* index */ G_TYPE_INT, /* nr */ G_TYPE_INT, /* Date */ @@ -886,6 +934,21 @@ GtkWidget *dive_list_create(void) G_TYPE_INT, /* OTU */ G_TYPE_STRING /* Location */ ); + dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS, + G_TYPE_INT, /* index */ + G_TYPE_INT, /* nr */ + G_TYPE_INT, /* Date */ + G_TYPE_INT, /* Star rating */ + G_TYPE_INT, /* Depth */ + G_TYPE_INT, /* Duration */ + G_TYPE_INT, /* Temperature */ + G_TYPE_STRING, /* Cylinder */ + G_TYPE_INT, /* Nitrox */ + G_TYPE_INT, /* SAC */ + G_TYPE_INT, /* OTU */ + G_TYPE_STRING /* Location */ + ); + dive_list.model = dive_list.treemodel; dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model)); set_divelist_font(divelist_font); @@ -914,10 +977,12 @@ GtkWidget *dive_list_create(void) 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), dive_list.model); - g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), dive_list.model); - g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), dive_list.model); - g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), dive_list.model); + g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_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), NULL); + 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); dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget), From dc9d0e23e5d158ea4775021b2f629e7f90b5377c Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 13 Aug 2012 14:53:07 -0700 Subject: [PATCH 27/67] Maintain selected rows when switching between list model and tree model We keep track of the DIVE_INDEX of all selected dives and simply re-select those dives after changing model (date based sort or sort by other column). There are a few TODOs left. We lose the sort direction (ascending / descending) when switching models. We also don't correctly deal with the user selecting summary rows in the tree model. Signed-off-by: Dirk Hohndel --- display-gtk.h | 2 +- divelist.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++-- statistics.c | 11 ++++++++--- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/display-gtk.h b/display-gtk.h index 4ce05468c..3043c873b 100644 --- a/display-gtk.h +++ b/display-gtk.h @@ -72,7 +72,7 @@ extern GtkWidget *dive_list_create(void); unsigned int amount_selected; -extern void process_selected_dives(GList *, GtkTreeModel *); +extern void process_selected_dives(GList *, int *, GtkTreeModel *); typedef void (*data_func_t)(GtkTreeViewColumn *col, GtkCellRenderer *renderer, diff --git a/divelist.c b/divelist.c index b70b415ca..4260651b7 100644 --- a/divelist.c +++ b/divelist.c @@ -79,6 +79,7 @@ static void dump_model(GtkListStore *store) #endif static GList *selected_dives; +static int *selectiontracker; static void selection_cb(GtkTreeSelection *selection, gpointer userdata) { @@ -92,6 +93,7 @@ static void selection_cb(GtkTreeSelection *selection, gpointer userdata) g_list_free (selected_dives); } selected_dives = gtk_tree_selection_get_selected_rows(selection, NULL); + selectiontracker = realloc(selectiontracker, nr_selected * sizeof(int)); switch (nr_selected) { case 0: /* keep showing the last selected dive */ @@ -118,6 +120,7 @@ static void selection_cb(GtkTreeSelection *selection, gpointer userdata) gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE); gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1); } + selectiontracker[0] = selected_dive; repaint_dive(); } return; @@ -127,8 +130,11 @@ static void selection_cb(GtkTreeSelection *selection, gpointer userdata) * is the most intuitive solution. * I do however want to keep around which dives have * been selected */ + /* TODO: + this also does not handle the case if a summary row is selected; + We should iterate and select all dives under that row */ amount_selected = g_list_length(selected_dives); - process_selected_dives(selected_dives, GTK_TREE_MODEL(dive_list.model)); + process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model)); repaint_dive(); return; } @@ -777,6 +783,8 @@ static void fill_dive_list(void) GtkTreeSelection *selection; selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); gtk_tree_selection_select_iter(selection, &iter); + selectiontracker = realloc(selectiontracker, sizeof(int)); + gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, selectiontracker, -1); } } @@ -896,9 +904,37 @@ static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpoi return FALSE; } +/* we need to have a temporary copy of the selected dives while + switching model as the selection_cb function keeps getting called + by when gtk_tree_selection_select_path is called. */ +static int *oldselection; +static int old_nr_selected; + +/* 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 select_selected(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) +{ + int i, idx; + GtkTreeSelection *selection = GTK_TREE_SELECTION(data); + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + for (i = 0; i < old_nr_selected; i++) + if (oldselection[i] == idx) { + gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path); + gtk_tree_selection_select_path(selection, path); + + return FALSE; + } + return FALSE; + +} + /* If the sort column is date (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. */ + If the model changed, inform the new model of the chosen sort column and make + sure the same dives are still selected. */ static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data) { int colid; @@ -911,8 +947,23 @@ static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data) else dive_list.model = dive_list.listmodel; if (dive_list.model != currentmodel) { + /* TODO + we should remember the sort order we had for each column */ + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + + /* remember what is currently selected, switch models and reselect the selected rows */ + old_nr_selected = amount_selected; + oldselection = malloc(old_nr_selected * sizeof(int)); + memcpy(oldselection, selectiontracker, amount_selected * sizeof(int)); + gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model)); gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, order); + + if (old_nr_selected) { + /* we need to select all the dives that were selected */ + /* this is fundamentally an n^2 algorithm as implemented - YUCK */ + gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), select_selected, selection); + } } } diff --git a/statistics.c b/statistics.c index 19105653c..adfc9c77a 100644 --- a/statistics.c +++ b/statistics.c @@ -142,10 +142,11 @@ static void process_all_dives(struct dive *dive, struct dive **prev_dive) } } -void process_selected_dives(GList *selected_dives, GtkTreeModel *model) +void process_selected_dives(GList *selected_dives, int *selectiontracker, GtkTreeModel *model) { struct dive *dp; unsigned int i; + int idx; GtkTreeIter iter; GtkTreePath *path; @@ -157,9 +158,13 @@ void process_selected_dives(GList *selected_dives, GtkTreeModel *model) path = g_list_nth_data(selected_dives, i); if (gtk_tree_model_get_iter(model, &iter, path)) { gtk_tree_model_get_value(model, &iter, 0, &value); - dp = get_dive(g_value_get_int(&value)); + idx = g_value_get_int(&value); + dp = get_dive(idx); + if (dp) { + selectiontracker[i] = idx; + process_dive(dp, &stats_selection); + } } - process_dive(dp, &stats_selection); } } From 5ba89c13ac09ee7649abf2d0c4514f9a83bb6c8b Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 13 Aug 2012 15:07:38 -0700 Subject: [PATCH 28/67] Apply sort functions to the correct model, don't select summary entries We only set up the column specific sort functions for the default (tree) model, which caused us to not sort correctly in the list model. This commit also somewhat cleans up the handling of selecting summary lines in the tree model, which includes the very first selection made at program start (which happens to be the very last dive). But it still doesn't work the way I expect it to work (i.e., the correct row is not highlighted). Fundamentally I would prefer clicks on the summary lines to instead select (or as ctrl-click, possibly deselect) all the dives under that summary entry. Still TODO. Signed-off-by: Dirk Hohndel --- divelist.c | 112 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 85 insertions(+), 27 deletions(-) diff --git a/divelist.c b/divelist.c index 4260651b7..ea59b0875 100644 --- a/divelist.c +++ b/divelist.c @@ -81,6 +81,30 @@ static void dump_model(GtkListStore *store) static GList *selected_dives; static int *selectiontracker; +/* if we are sorting by date and are using a tree model, we don't want the selection + to be a summary entry, but instead the first child below that entry. So we descend + down the tree until we find a leaf (entry with non-negative 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 */ + return; + /* clicking on a parent should toggle expand status */ + if(gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath)) + gtk_tree_view_collapse_row(GTK_TREE_VIEW(dive_list.tree_view), tpath); + else + gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE); + gtk_tree_model_get(GTK_TREE_MODEL(model), iter, DIVE_INDEX, diveidx, -1); + } +} + static void selection_cb(GtkTreeSelection *selection, gpointer userdata) { GtkTreeIter iter; @@ -104,22 +128,8 @@ static void selection_cb(GtkTreeSelection *selection, gpointer userdata) path = g_list_nth_data(selected_dives, 0); if (gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path)) { gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1); - /* a negative index means we picked a summary entry; - expand that entry and use first real child instead */ - while (selected_dive < 0) { - GtkTreeIter parent; - GtkTreePath *tpath; - memcpy(&parent, &iter, sizeof(parent)); - tpath = gtk_tree_model_get_path(GTK_TREE_MODEL(dive_list.model), &parent); - if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(dive_list.model), &iter, &parent)) - /* we should never have a parent without child */ - return; - if(gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath)) - gtk_tree_view_collapse_row(GTK_TREE_VIEW(dive_list.tree_view), tpath); - else - gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE); - gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1); - } + /* make sure we're not on a summary entry */ + first_leaf (GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive); selectiontracker[0] = selected_dive; repaint_dive(); } @@ -781,10 +791,14 @@ static void fill_dive_list(void) update_dive_list_units(); if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) { GtkTreeSelection *selection; + + gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1); + /* make sure it's an actual dive that is selected */ + first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); gtk_tree_selection_select_iter(selection, &iter); selectiontracker = realloc(selectiontracker, sizeof(int)); - gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, selectiontracker, -1); + *selectiontracker = selected_dive; } } @@ -826,14 +840,20 @@ static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_c unsigned int flags = col->flags; int *visible = col->visible; GtkWidget *tree_view = dl->tree_view; - GtkTreeStore *model = dl->model; + 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) - gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), index, sort_func, NULL, NULL); + if (sort_func) { + /* the sort functions are needed in the corresponding models */ + if (index == DIVE_DATE) + 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; } @@ -906,9 +926,14 @@ static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpoi /* we need to have a temporary copy of the selected dives while switching model as the selection_cb function keeps getting called - by when gtk_tree_selection_select_path is 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 int *oldselection; static int old_nr_selected; +static gboolean second_call = FALSE; +static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, }; +static int lastcol = DIVE_DATE; /* 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. @@ -931,39 +956,72 @@ static gboolean select_selected(GtkTreeModel *model, GtkTreePath *path, } +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; +} + /* If the sort column is date (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. */ + 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; + 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_DATE) dive_list.model = dive_list.treemodel; else dive_list.model = dive_list.listmodel; if (dive_list.model != currentmodel) { - /* TODO - we should remember the sort order we had for each column */ GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); /* remember what is currently selected, switch models and reselect the selected rows */ old_nr_selected = amount_selected; oldselection = malloc(old_nr_selected * sizeof(int)); - memcpy(oldselection, selectiontracker, amount_selected * sizeof(int)); - + if (amount_selected) + memcpy(oldselection, selectiontracker, amount_selected * sizeof(int)); gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model)); - gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, order); + + update_column_and_order(colid); if (old_nr_selected) { /* we need to select all the dives that were selected */ /* this is fundamentally an n^2 algorithm as implemented - YUCK */ gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), select_selected, selection); } + } else { + if (order != sortorder[colid]) { + update_column_and_order(colid); + } } } From 822b6409d752133090df24f5ca38f69656ff82b7 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 13 Aug 2012 21:11:09 -0700 Subject: [PATCH 29/67] Fix selecting and unselecting summary items The dive list now seems to behave intuitively. In order to do this we had to intercept the select function in addition to having a selection-changed callback. That way we can simulate the multi-level selection and unselection that was missing. Signed-off-by: Dirk Hohndel --- divelist.c | 85 +++++++++++++++++++++++++++++++++++++++------------- statistics.c | 23 +++++++++----- 2 files changed, 81 insertions(+), 27 deletions(-) diff --git a/divelist.c b/divelist.c index ea59b0875..112307a1c 100644 --- a/divelist.c +++ b/divelist.c @@ -81,10 +81,9 @@ static void dump_model(GtkListStore *store) static GList *selected_dives; static int *selectiontracker; -/* if we are sorting by date and are using a tree model, we don't want the selection - to be a summary entry, but instead the first child below that entry. So we descend - down the tree until we find a leaf (entry with non-negative index) - */ +/* 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; @@ -96,15 +95,57 @@ static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx) if (!gtk_tree_model_iter_children(model, iter, &parent)) /* we should never have a parent without child */ return; - /* clicking on a parent should toggle expand status */ - if(gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath)) - gtk_tree_view_collapse_row(GTK_TREE_VIEW(dive_list.tree_view), tpath); - else + 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_model_get(GTK_TREE_MODEL(model), iter, DIVE_INDEX, diveidx, -1); } } +/* if we click on a summary dive, we actually want to select / unselect + all the dives "below" it */ +static void select_children(GtkTreeModel *model, GtkTreeSelection * selection, + GtkTreeIter *iter, gboolean was_selected) +{ + int i, nr_children; + GtkTreeIter parent; + GtkTreePath *tpath; + + memcpy(&parent, iter, sizeof(parent)); + + tpath = gtk_tree_model_get_path(model, &parent); + 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); + + nr_children = gtk_tree_model_iter_n_children(model, &parent); + for (i = 0; i < nr_children; i++) { + gtk_tree_model_iter_nth_child(model, iter, &parent, i); + if (was_selected) + gtk_tree_selection_unselect_iter(selection, iter); + else + gtk_tree_selection_select_iter(selection, iter); + } +} + +/* this is called _before_ the selection is changed, for every single entry; + * we simply have it call down the tree to make sure that summary items toggle + * their children */ +gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model, + GtkTreePath *path, gboolean was_selected, gpointer userdata) +{ + GtkTreeIter iter; + int dive_idx; + + if (gtk_tree_model_get_iter(model, &iter, path)) { + gtk_tree_model_get(model, &iter, DIVE_INDEX, &dive_idx, -1); + if (dive_idx < 0) { + select_children(model, selection, &iter, was_selected); + } + } + /* allow this selection to proceed */ + return TRUE; +} + +/* this is called when gtk thinks that the selection has changed */ static void selection_cb(GtkTreeSelection *selection, gpointer userdata) { GtkTreeIter iter; @@ -120,7 +161,9 @@ static void selection_cb(GtkTreeSelection *selection, gpointer userdata) selectiontracker = realloc(selectiontracker, nr_selected * sizeof(int)); switch (nr_selected) { - case 0: /* keep showing the last selected dive */ + case 0: /* there is no clear way to figure out which dive to show */ + amount_selected = 0; + selected_dive = -1; return; case 1: /* just pick that dive as selected */ @@ -128,21 +171,21 @@ static void selection_cb(GtkTreeSelection *selection, gpointer userdata) path = g_list_nth_data(selected_dives, 0); if (gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path)) { gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1); - /* make sure we're not on a summary entry */ - first_leaf (GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive); + /* due to the way this callback gets invoked it is possible that + in the process of unselecting a summary dive we get here with + just one summary dive selected - ignore that case */ + if (selected_dive < 0) { + amount_selected = 0; + return; + } selectiontracker[0] = selected_dive; repaint_dive(); } return; - default: /* multiple selections - what now? At this point I - * don't want to change the selected dive unless - * there is exactly one dive selected; not sure this + default: /* multiple selections - what now? + * We don't change the selected dive unless there is exactly one dive selected; not sure this * is the most intuitive solution. - * I do however want to keep around which dives have - * been selected */ - /* TODO: - this also does not handle the case if a summary row is selected; - We should iterate and select all dives under that row */ + * The dives that have been selected are processed */ amount_selected = g_list_length(selected_dives); process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model)); repaint_dive(); @@ -792,8 +835,8 @@ static void fill_dive_list(void) if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) { GtkTreeSelection *selection; + /* select the last dive (and make sure it's an actual dive that is selected) */ gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1); - /* make sure it's an actual dive that is selected */ first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); gtk_tree_selection_select_iter(selection, &iter); @@ -1093,6 +1136,8 @@ GtkWidget *dive_list_create(void) 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); diff --git a/statistics.c b/statistics.c index adfc9c77a..3a260066a 100644 --- a/statistics.c +++ b/statistics.c @@ -142,30 +142,39 @@ static void process_all_dives(struct dive *dive, struct dive **prev_dive) } } +/* make sure we skip the selected summary entries */ void process_selected_dives(GList *selected_dives, int *selectiontracker, GtkTreeModel *model) { struct dive *dp; - unsigned int i; + unsigned int i, j; int idx; GtkTreeIter iter; GtkTreePath *path; memset(&stats_selection, 0, sizeof(stats_selection)); - stats_selection.selection_size = amount_selected; - for (i = 0; i < amount_selected; ++i) { + /* adjust amount_selected and remove negative index entries from list */ + for (i = 0, j = 0; j < amount_selected; ++i) { GValue value = {0, }; path = g_list_nth_data(selected_dives, i); if (gtk_tree_model_get_iter(model, &iter, path)) { gtk_tree_model_get_value(model, &iter, 0, &value); idx = g_value_get_int(&value); - dp = get_dive(idx); - if (dp) { - selectiontracker[i] = idx; - process_dive(dp, &stats_selection); + if (idx > 0) { + dp = get_dive(idx); + if (dp) { + selectiontracker[j] = idx; + process_dive(dp, &stats_selection); + j++; + continue; + } } } + /* we didn't process it, so shorten the list */ + amount_selected--; } + /* record the actual number of dives selected */ + stats_selection.selection_size = amount_selected; } static void set_label(GtkWidget *w, const char *fmt, ...) From 307240d6f6fefa83618e8c271203bc57df1081c7 Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Wed, 1 Aug 2012 22:19:44 +0300 Subject: [PATCH 30/67] Fixed a small memory leak in divelist.c In fill_one_dive(), cylinder and location strings are obtained via get_string(), which needs to allocated a litte bit of memory. After passing the two pointers ('cylinder' and 'location') as arguments to gtk_list_store_set() it is safe to release them. Signed-off-by: Lubomir I. Ivanov --- divelist.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/divelist.c b/divelist.c index 1d31da567..e994b3252 100644 --- a/divelist.c +++ b/divelist.c @@ -556,6 +556,9 @@ static void fill_one_dive(struct dive *dive, DIVE_OTU, dive->otu, DIVE_TOTALWEIGHT, total_weight(dive), -1); + + free(location); + free(cylinder); } static gboolean set_one_dive(GtkTreeModel *model, From 77a903a6bb26d60e1736f7d5c118b598916b37f2 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Tue, 14 Aug 2012 14:52:14 -0700 Subject: [PATCH 31/67] Fixed another memory leak We need to free the string that gtk_tree_mode_get returns to us. Signed-off-by: Dirk Hohndel --- info.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/info.c b/info.c index 1847a49bf..ca5399548 100644 --- a/info.c +++ b/info.c @@ -242,6 +242,8 @@ static gboolean match_string_entry(GtkTreeModel *model, GtkTreePath *path, GtkTr 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) From e8ec3df371f0496a52ebdc463245d7b9df3be6ff Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Tue, 14 Aug 2012 16:07:25 -0700 Subject: [PATCH 32/67] Add exposure protection tracking For simplicity and shortness, throughout subsurface exposure protection is simply referred to as "suit". Add the fields to the data structures, add the column to the dive_list and the preferences dialog (once again with it being turned invisible by default). Support loading and saving of the suit information. Display the suit information in the Dive Info pane (this may be a bit controversial as people could argue this should be in the Equipment pane) and allow editing of the suit info, with our usual support for completion and drop down lists to pick from. Signed-off-by: Dirk Hohndel --- display-gtk.h | 1 + dive.c | 2 ++ dive.h | 2 ++ divelist.c | 20 ++++++++++++++++++-- gtk-gui.c | 8 ++++++++ info.c | 21 ++++++++++++++++++--- parse-xml.c | 2 ++ save-xml.c | 1 + 8 files changed, 52 insertions(+), 5 deletions(-) diff --git a/display-gtk.h b/display-gtk.h index 9f1b51771..37326d590 100644 --- a/display-gtk.h +++ b/display-gtk.h @@ -15,6 +15,7 @@ typedef struct { gboolean cylinder; gboolean temperature; gboolean totalweight; + gboolean suit; gboolean nitrox; gboolean sac; gboolean otu; diff --git a/dive.c b/dive.c index a420a3ef6..f5d082801 100644 --- a/dive.c +++ b/dive.c @@ -486,6 +486,7 @@ struct dive *fixup_dive(struct dive *dive) add_people(dive->buddy); add_people(dive->divemaster); add_location(dive->location); + add_suit(dive->suit); for (i = 0; i < MAX_CYLINDERS; i++) { cylinder_t *cyl = dive->cylinder + i; add_cylinder_description(&cyl->type); @@ -703,6 +704,7 @@ struct dive *try_to_merge(struct dive *a, struct dive *b) MERGE_TXT(res, a, b, buddy); MERGE_TXT(res, a, b, divemaster); MERGE_MAX(res, a, b, rating); + MERGE_TXT(res, a, b, suit); MERGE_MAX(res, a, b, number); MERGE_MAX(res, a, b, maxdepth.mm); res->meandepth.mm = 0; diff --git a/dive.h b/dive.h index 8a867ad0d..faed89ac9 100644 --- a/dive.h +++ b/dive.h @@ -243,6 +243,7 @@ struct dive { temperature_t airtemp, watertemp; cylinder_t cylinder[MAX_CYLINDERS]; weightsystem_t weightsystem[MAX_WEIGHTSYSTEMS]; + char *suit; int sac, otu; struct event *events; int samples, alloc_samples; @@ -342,6 +343,7 @@ extern void add_cylinder_description(cylinder_type_t *); extern void add_weightsystem_description(weightsystem_t *); extern void add_people(const char *string); extern void add_location(const char *string); +extern void add_suit(const char *string); extern void remember_event(const char *eventname); extern void evn_foreach(void (*callback)(const char *, int *, void *), void *data); diff --git a/divelist.c b/divelist.c index 1d31da567..8cf079391 100644 --- a/divelist.c +++ b/divelist.c @@ -26,7 +26,7 @@ struct DiveList { GtkWidget *container_widget; GtkListStore *model; GtkTreeViewColumn *nr, *date, *stars, *depth, *duration, *location; - GtkTreeViewColumn *temperature, *cylinder, *totalweight, *nitrox, *sac, *otu; + GtkTreeViewColumn *temperature, *cylinder, *totalweight, *suit, *nitrox, *sac, *otu; int changed; }; @@ -45,6 +45,7 @@ enum { 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 */ @@ -534,6 +535,11 @@ static 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) +{ + get_string(str, dive->suit); +} + /* * Set up anything that could have changed due to editing * of dive information @@ -542,10 +548,11 @@ static void fill_one_dive(struct dive *dive, GtkTreeModel *model, GtkTreeIter *iter) { - char *location, *cylinder; + char *location, *cylinder, *suit; get_cylinder(dive, &cylinder); get_location(dive, &location); + get_suit(dive, &suit); gtk_list_store_set(GTK_LIST_STORE(model), iter, DIVE_NR, dive->number, @@ -555,7 +562,11 @@ static void fill_one_dive(struct dive *dive, DIVE_SAC, dive->sac, DIVE_OTU, dive->otu, DIVE_TOTALWEIGHT, total_weight(dive), + DIVE_SUIT, suit, -1); + + /* this will create a merge conflict with the memory leak patches */ + free(suit); } static gboolean set_one_dive(GtkTreeModel *model, @@ -614,6 +625,7 @@ void update_dive_list_col_visibility(void) gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder); gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature); gtk_tree_view_column_set_visible(dive_list.totalweight, visible_cols.totalweight); + gtk_tree_view_column_set_visible(dive_list.suit, visible_cols.suit); gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox); gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac); gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu); @@ -643,6 +655,7 @@ static void fill_dive_list(void) DIVE_LOCATION, "location", DIVE_TEMPERATURE, dive->watertemp.mkelvin, DIVE_TOTALWEIGHT, 0, + DIVE_SUIT, dive->suit, DIVE_SAC, 0, -1); } @@ -676,6 +689,7 @@ static struct divelist_column { [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT }, [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature }, [DIVE_TOTALWEIGHT] = { "lbs", weight_data_func, NULL, ALIGN_RIGHT, &visible_cols.totalweight }, + [DIVE_SUIT] = { "Suit", NULL, NULL, ALIGN_LEFT, &visible_cols.suit }, [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder }, [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox }, [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac }, @@ -783,6 +797,7 @@ GtkWidget *dive_list_create(void) 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 */ @@ -804,6 +819,7 @@ GtkWidget *dive_list_create(void) 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); diff --git a/gtk-gui.c b/gtk-gui.c index 0dc2f9734..9f37c1a0b 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -349,6 +349,7 @@ OPTIONCALLBACK(sac_toggle, visible_cols.sac) OPTIONCALLBACK(nitrox_toggle, visible_cols.nitrox) OPTIONCALLBACK(temperature_toggle, visible_cols.temperature) OPTIONCALLBACK(totalweight_toggle, visible_cols.totalweight) +OPTIONCALLBACK(suit_toggle, visible_cols.suit) OPTIONCALLBACK(cylinder_toggle, visible_cols.cylinder) static void event_toggle(GtkWidget *w, gpointer _data) @@ -440,6 +441,11 @@ static void preferences_dialog(GtkWidget *w, gpointer data) 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("Show Suit"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), 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); + font = gtk_font_button_new_with_font(divelist_font); gtk_box_pack_start(GTK_BOX(vbox), font, FALSE, FALSE, 5); @@ -464,6 +470,7 @@ static void preferences_dialog(GtkWidget *w, gpointer data) subsurface_set_conf("lbs", PREF_BOOL, BOOL_TO_PTR(output_units.weight == LBS)); subsurface_set_conf("TEMPERATURE", PREF_BOOL, BOOL_TO_PTR(visible_cols.temperature)); subsurface_set_conf("TOTALWEIGHT", PREF_BOOL, BOOL_TO_PTR(visible_cols.totalweight)); + subsurface_set_conf("SUIT", PREF_BOOL, BOOL_TO_PTR(visible_cols.suit)); subsurface_set_conf("CYLINDER", PREF_BOOL, BOOL_TO_PTR(visible_cols.cylinder)); subsurface_set_conf("NITROX", PREF_BOOL, BOOL_TO_PTR(visible_cols.nitrox)); subsurface_set_conf("SAC", PREF_BOOL, BOOL_TO_PTR(visible_cols.sac)); @@ -734,6 +741,7 @@ void init_ui(int *argcp, char ***argvp) visible_cols.cylinder = PTR_TO_BOOL(subsurface_get_conf("CYLINDER", PREF_BOOL)); visible_cols.temperature = PTR_TO_BOOL(subsurface_get_conf("TEMPERATURE", PREF_BOOL)); visible_cols.totalweight = PTR_TO_BOOL(subsurface_get_conf("TOTALWEIGHT", PREF_BOOL)); + visible_cols.suit = PTR_TO_BOOL(subsurface_get_conf("SUIT", PREF_BOOL)); visible_cols.nitrox = PTR_TO_BOOL(subsurface_get_conf("NITROX", PREF_BOOL)); visible_cols.otu = PTR_TO_BOOL(subsurface_get_conf("OTU", PREF_BOOL)); visible_cols.sac = PTR_TO_BOOL(subsurface_get_conf("SAC", PREF_BOOL)); diff --git a/info.c b/info.c index 1847a49bf..09d61c835 100644 --- a/info.c +++ b/info.c @@ -19,9 +19,9 @@ #include "display-gtk.h" #include "divelist.h" -static GtkEntry *location, *buddy, *divemaster, *rating; +static GtkEntry *location, *buddy, *divemaster, *rating, *suit; static GtkTextView *notes; -static GtkListStore *location_list, *people_list, *star_list; +static GtkListStore *location_list, *people_list, *star_list, *suit_list; static char *get_text(GtkTextView *view) { @@ -96,6 +96,7 @@ void show_dive_info(struct dive *dive) 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); @@ -296,6 +297,11 @@ 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; @@ -308,7 +314,7 @@ static int get_rating(const char *string) } struct dive_info { - GtkComboBoxEntry *location, *divemaster, *buddy, *rating; + GtkComboBoxEntry *location, *divemaster, *buddy, *rating, *suit; GtkTextView *notes; }; @@ -336,6 +342,12 @@ static void save_dive_info_changes(struct dive *dive, struct dive_info *info) changed = 1; } + new_text = get_combo_box_entry_text(info->suit, &dive->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); if (new_text) { @@ -378,6 +390,7 @@ static void dive_info_widget(GtkWidget *box, struct dive *dive, struct dive_info 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); info->notes = text_view(box, "Notes", READ_WRITE); if (dive->notes && *dive->notes) @@ -562,6 +575,7 @@ GtkWidget *extended_dive_info_widget(void) add_string_list_entry(THREE_STARS, star_list); add_string_list_entry(FOUR_STARS, star_list); add_string_list_entry(FIVE_STARS, 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"); @@ -576,6 +590,7 @@ GtkWidget *extended_dive_info_widget(void) 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; diff --git a/parse-xml.c b/parse-xml.c index d6fc953a7..a36758ca0 100644 --- a/parse-xml.c +++ b/parse-xml.c @@ -1100,6 +1100,8 @@ static void try_to_fill_dive(struct dive **divep, const char *name, char *buf) return; if (MATCH(".location", utf8_string, &dive->location)) return; + if (MATCH(".suit", utf8_string, &dive->suit)) + return; if (MATCH(".notes", utf8_string, &dive->notes)) return; if (MATCH(".divemaster", utf8_string, &dive->divemaster)) diff --git a/save-xml.c b/save-xml.c index ed55c022d..37d6d062e 100644 --- a/save-xml.c +++ b/save-xml.c @@ -183,6 +183,7 @@ static void save_overview(FILE *f, struct dive *dive) show_utf8(f, dive->divemaster, " ","\n"); show_utf8(f, dive->buddy, " ","\n"); show_utf8(f, dive->notes, " ","\n"); + show_utf8(f, dive->suit, " ","\n"); } static void save_cylinder_info(FILE *f, struct dive *dive) From 621761233b2e1b139c07987b562ef2aa299ff35e Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Wed, 15 Aug 2012 15:21:34 -0700 Subject: [PATCH 33/67] Redo dive editing This commit addresses two issues: We now can add / edit / delete equipment from the edit dive dialog We now can edit multiple dives at once The latter feature has some interesting design constraints: It picks the 'selected_dive' as the one to start the edit from - so if this dive already has some information filled in, that information needs to be overwritten before it is stored in all of the dives. Similarly, only changes to the cylinders or weightsystems are recorded. Also, the notes field is not editable in the multi dive edit mode (as that didn't seem useful). The workflow seems to work best if using the multi-edit right after importing new dives from a dive computer. The user then can select all the new dives and only needs to edit things like location, divemaster, buddy, weights, etc. once. This commit will create some obvious conflicts with the commit that adds exposure protection tracking. It was implemented on top of the tree_view changes as it reuses some of the infrastructure for tracking the selected dives. Signed-off-by: Dirk Hohndel --- display-gtk.h | 5 +- dive.h | 7 ++- divelist.c | 13 +++++ equipment.c | 158 +++++++++++++++++++++++++++----------------------- gtk-gui.c | 2 +- info.c | 109 +++++++++++++++++++++++++--------- main.c | 2 +- 7 files changed, 191 insertions(+), 105 deletions(-) diff --git a/display-gtk.h b/display-gtk.h index 3043c873b..1cf150bb6 100644 --- a/display-gtk.h +++ b/display-gtk.h @@ -63,10 +63,11 @@ extern void update_progressbar_text(progressbar_t *progress, const char *text); extern GtkWidget *dive_profile_widget(void); extern GtkWidget *dive_info_frame(void); extern GtkWidget *extended_dive_info_widget(void); -extern GtkWidget *equipment_widget(void); +extern GtkWidget *equipment_widget(int w_idx); extern GtkWidget *single_stats_widget(void); extern GtkWidget *total_stats_widget(void); -extern GtkWidget *cylinder_list_widget(void); +extern GtkWidget *cylinder_list_widget(int w_idx); +extern GtkWidget *weightsystem_list_widget(int w_idx); extern GtkWidget *dive_list_create(void); diff --git a/dive.h b/dive.h index a25fafb39..de95d5e66 100644 --- a/dive.h +++ b/dive.h @@ -227,6 +227,8 @@ struct event { #define MAX_CYLINDERS (8) #define MAX_WEIGHTSYSTEMS (4) +#define W_IDX_PRIMARY 0 +#define W_IDX_SECONDARY 1 struct dive { int number; @@ -282,7 +284,7 @@ extern int selected_dive; static inline struct dive *get_dive(unsigned int nr) { - if (nr >= dive_table.nr) + if (nr >= dive_table.nr || nr < 0) return NULL; return dive_table.dives[nr]; } @@ -299,7 +301,7 @@ extern xmlDoc *test_xslt_transforms(xmlDoc *doc); extern void show_dive_info(struct dive *); -extern void show_dive_equipment(struct dive *); +extern void show_dive_equipment(struct dive *, int w_idx); extern void show_dive_stats(struct dive *); @@ -345,6 +347,7 @@ extern void evn_foreach(void (*callback)(const char *, int *, void *), void *dat extern int add_new_dive(struct dive *dive); extern int edit_dive_info(struct dive *dive); +extern int edit_multi_dive_info(int nr, int *indices); extern void dive_list_update_dives(void); extern void flush_divelist(struct dive *dive); diff --git a/divelist.c b/divelist.c index 112307a1c..a4221f7b9 100644 --- a/divelist.c +++ b/divelist.c @@ -938,14 +938,27 @@ void add_dive_cb(GtkWidget *menuitem, gpointer data) free(dive); } +void edit_dive_cb(GtkWidget *menuitem, gpointer data) +{ + edit_multi_dive_info(amount_selected, selectiontracker); +} + static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button) { GtkWidget *menu, *menuitem; + char editlabel[] = "Edit dives"; menu = gtk_menu_new(); menuitem = gtk_menu_item_new_with_label("Add dive"); g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + if (amount_selected) { + if (amount_selected == 1) + editlabel[strlen(editlabel) - 1] = '\0'; + menuitem = gtk_menu_item_new_with_label(editlabel); + g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_cb), model); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } gtk_widget_show_all(menu); gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, diff --git a/equipment.c b/equipment.c index b74d85b7b..ccfeb1270 100644 --- a/equipment.c +++ b/equipment.c @@ -2,10 +2,10 @@ /* creates the UI for the equipment page - * controlled through the following interfaces: * - * void show_dive_equipment(struct dive *dive) + * void show_dive_equipment(struct dive *dive, int w_idx) * * called from gtk-ui: - * GtkWidget *equipment_widget(void) + * GtkWidget *equipment_widget(int w_idx) */ #include #include @@ -40,10 +40,11 @@ enum { struct equipment_list { int max_index; GtkListStore *model; + GtkTreeView *tree_view; GtkWidget *edit, *add, *del; }; -static struct equipment_list cylinder_list, weightsystem_list; +static struct equipment_list cylinder_list[2], weightsystem_list[2]; struct cylinder_widget { @@ -519,11 +520,11 @@ static void show_equipment(struct dive *dive, int max, } } -void show_dive_equipment(struct dive *dive) +void show_dive_equipment(struct dive *dive, int w_idx) { - show_equipment(dive, MAX_CYLINDERS, &cylinder_list, + show_equipment(dive, MAX_CYLINDERS, &cylinder_list[w_idx], &cyl_ptr, &cylinder_none, &set_one_cylinder); - show_equipment(dive, MAX_WEIGHTSYSTEMS, &weightsystem_list, + show_equipment(dive, MAX_WEIGHTSYSTEMS, &weightsystem_list[w_idx], &ws_ptr, &weightsystem_none, &set_one_weightsystem); } @@ -1061,11 +1062,12 @@ static int get_model_index(GtkListStore *model, GtkTreeIter *iter) return index; } -static void edit_cb(GtkButton *button, GtkTreeView *tree_view) +static void edit_cb(GtkButton *button, int w_idx) { int index; GtkTreeIter iter; - GtkListStore *model = cylinder_list.model; + GtkListStore *model = cylinder_list[w_idx].model; + GtkTreeView *tree_view = cylinder_list[w_idx].tree_view; GtkTreeSelection *selection; cylinder_t cyl; @@ -1083,11 +1085,12 @@ static void edit_cb(GtkButton *button, GtkTreeView *tree_view) repaint_dive(); } -static void add_cb(GtkButton *button, GtkTreeView *tree_view) +static void add_cb(GtkButton *button, int w_idx) { - int index = cylinder_list.max_index; + int index = cylinder_list[w_idx].max_index; GtkTreeIter iter; - GtkListStore *model = cylinder_list.model; + GtkListStore *model = cylinder_list[w_idx].model; + GtkTreeView *tree_view = cylinder_list[w_idx].tree_view; GtkTreeSelection *selection; cylinder_t cyl; @@ -1100,15 +1103,16 @@ static void add_cb(GtkButton *button, GtkTreeView *tree_view) selection = gtk_tree_view_get_selection(tree_view); gtk_tree_selection_select_iter(selection, &iter); - cylinder_list.max_index++; - gtk_widget_set_sensitive(cylinder_list.add, cylinder_list.max_index < MAX_CYLINDERS); + cylinder_list[w_idx].max_index++; + gtk_widget_set_sensitive(cylinder_list[w_idx].add, cylinder_list[w_idx].max_index < MAX_CYLINDERS); } -static void del_cb(GtkButton *button, GtkTreeView *tree_view) +static void del_cb(GtkButton *button, int w_idx) { int index, nr; GtkTreeIter iter; - GtkListStore *model = cylinder_list.model; + GtkListStore *model = cylinder_list[w_idx].model; + GtkTreeView *tree_view = cylinder_list[w_idx].tree_view; GtkTreeSelection *selection; struct dive *dive; cylinder_t *cyl; @@ -1125,27 +1129,28 @@ static void del_cb(GtkButton *button, GtkTreeView *tree_view) if (!dive) return; cyl = dive->cylinder + index; - nr = cylinder_list.max_index - index - 1; + nr = cylinder_list[w_idx].max_index - index - 1; gtk_list_store_remove(model, &iter); - cylinder_list.max_index--; + cylinder_list[w_idx].max_index--; memmove(cyl, cyl+1, nr*sizeof(*cyl)); memset(cyl+nr, 0, sizeof(*cyl)); mark_divelist_changed(TRUE); flush_divelist(dive); - gtk_widget_set_sensitive(cylinder_list.edit, 0); - gtk_widget_set_sensitive(cylinder_list.del, 0); - gtk_widget_set_sensitive(cylinder_list.add, 1); + gtk_widget_set_sensitive(cylinder_list[w_idx].edit, 0); + gtk_widget_set_sensitive(cylinder_list[w_idx].del, 0); + gtk_widget_set_sensitive(cylinder_list[w_idx].add, 1); } -static void ws_edit_cb(GtkButton *button, GtkTreeView *tree_view) +static void ws_edit_cb(GtkButton *button, int w_idx) { int index; GtkTreeIter iter; - GtkListStore *model = weightsystem_list.model; + GtkListStore *model = weightsystem_list[w_idx].model; + GtkTreeView *tree_view = weightsystem_list[w_idx].tree_view; GtkTreeSelection *selection; weightsystem_t ws; @@ -1163,11 +1168,12 @@ static void ws_edit_cb(GtkButton *button, GtkTreeView *tree_view) repaint_dive(); } -static void ws_add_cb(GtkButton *button, GtkTreeView *tree_view) +static void ws_add_cb(GtkButton *button, int w_idx) { - int index = weightsystem_list.max_index; + int index = weightsystem_list[w_idx].max_index; GtkTreeIter iter; - GtkListStore *model = weightsystem_list.model; + GtkListStore *model = weightsystem_list[w_idx].model; + GtkTreeView *tree_view = weightsystem_list[w_idx].tree_view; GtkTreeSelection *selection; weightsystem_t ws; @@ -1180,15 +1186,16 @@ static void ws_add_cb(GtkButton *button, GtkTreeView *tree_view) selection = gtk_tree_view_get_selection(tree_view); gtk_tree_selection_select_iter(selection, &iter); - weightsystem_list.max_index++; - gtk_widget_set_sensitive(weightsystem_list.add, weightsystem_list.max_index < MAX_WEIGHTSYSTEMS); + weightsystem_list[w_idx].max_index++; + gtk_widget_set_sensitive(weightsystem_list[w_idx].add, weightsystem_list[w_idx].max_index < MAX_WEIGHTSYSTEMS); } -static void ws_del_cb(GtkButton *button, GtkTreeView *tree_view) +static void ws_del_cb(GtkButton *button, int w_idx) { int index, nr; GtkTreeIter iter; - GtkListStore *model = weightsystem_list.model; + GtkListStore *model = weightsystem_list[w_idx].model; + GtkTreeView *tree_view = weightsystem_list[w_idx].tree_view; GtkTreeSelection *selection; struct dive *dive; weightsystem_t *ws; @@ -1205,20 +1212,20 @@ static void ws_del_cb(GtkButton *button, GtkTreeView *tree_view) if (!dive) return; ws = dive->weightsystem + index; - nr = weightsystem_list.max_index - index - 1; + nr = weightsystem_list[w_idx].max_index - index - 1; gtk_list_store_remove(model, &iter); - weightsystem_list.max_index--; + weightsystem_list[w_idx].max_index--; memmove(ws, ws+1, nr*sizeof(*ws)); memset(ws+nr, 0, sizeof(*ws)); mark_divelist_changed(TRUE); flush_divelist(dive); - gtk_widget_set_sensitive(weightsystem_list.edit, 0); - gtk_widget_set_sensitive(weightsystem_list.del, 0); - gtk_widget_set_sensitive(weightsystem_list.add, 1); + gtk_widget_set_sensitive(weightsystem_list[w_idx].edit, 0); + gtk_widget_set_sensitive(weightsystem_list[w_idx].del, 0); + gtk_widget_set_sensitive(weightsystem_list[w_idx].add, 1); } static GtkListStore *create_tank_size_model(void) @@ -1338,33 +1345,33 @@ static void selection_cb(GtkTreeSelection *selection, struct equipment_list *lis static void row_activated_cb(GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, - GtkTreeModel *model) + int w_idx) { - edit_cb(NULL, tree_view); + edit_cb(NULL, w_idx); } static void ws_row_activated_cb(GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, - GtkTreeModel *model) + int w_idx) { - ws_edit_cb(NULL, tree_view); + ws_edit_cb(NULL, w_idx); } -GtkWidget *cylinder_list_widget(void) +GtkWidget *cylinder_list_widget(int w_idx) { - GtkListStore *model = cylinder_list.model; + GtkListStore *model = cylinder_list[w_idx].model; GtkWidget *tree_view; GtkTreeSelection *selection; tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); gtk_widget_set_can_focus(tree_view, FALSE); - g_signal_connect(tree_view, "row-activated", G_CALLBACK(row_activated_cb), model); + g_signal_connect(tree_view, "row-activated", G_CALLBACK(row_activated_cb), GINT_TO_POINTER(w_idx)); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE); - g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), &cylinder_list); + g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), &cylinder_list[w_idx]); g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE, "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, @@ -1380,9 +1387,9 @@ GtkWidget *cylinder_list_widget(void) return tree_view; } -GtkWidget *weightsystem_list_widget(void) +GtkWidget *weightsystem_list_widget(int w_idx) { - GtkListStore *model = weightsystem_list.model; + GtkListStore *model = weightsystem_list[w_idx].model; GtkWidget *tree_view; GtkTreeSelection *selection; @@ -1392,7 +1399,7 @@ GtkWidget *weightsystem_list_widget(void) selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE); - g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), &weightsystem_list); + g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), &weightsystem_list[w_idx]); g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE, "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH, @@ -1405,7 +1412,7 @@ GtkWidget *weightsystem_list_widget(void) return tree_view; } -static GtkWidget *cylinder_list_create(void) +static GtkWidget *cylinder_list_create(int w_idx) { GtkListStore *model; @@ -1418,11 +1425,11 @@ static GtkWidget *cylinder_list_create(void) G_TYPE_INT, /* CYL_O2: permille */ G_TYPE_INT /* CYL_HE: permille */ ); - cylinder_list.model = model; - return cylinder_list_widget(); + cylinder_list[w_idx].model = model; + return cylinder_list_widget(w_idx); } -static GtkWidget *weightsystem_list_create(void) +static GtkWidget *weightsystem_list_create(int w_idx) { GtkListStore *model; @@ -1430,11 +1437,11 @@ static GtkWidget *weightsystem_list_create(void) G_TYPE_STRING, /* WS_DESC: utf8 */ G_TYPE_INT /* WS_WEIGHT: grams */ ); - weightsystem_list.model = model; - return weightsystem_list_widget(); + weightsystem_list[w_idx].model = model; + return weightsystem_list_widget(w_idx); } -GtkWidget *equipment_widget(void) +GtkWidget *equipment_widget(int w_idx) { GtkWidget *vbox, *hbox, *frame, *framebox, *tree_view; GtkWidget *add, *del, *edit; @@ -1442,14 +1449,17 @@ GtkWidget *equipment_widget(void) vbox = gtk_vbox_new(FALSE, 3); /* - * We create the cylinder size model at startup, since - * we're going to share it across all cylinders and all - * dives. So if you add a new cylinder type in one dive, - * it will show up when you edit the cylinder types for - * another dive. + * We create the cylinder size (and weightsystem) models + * at startup for the primary cylinder / weightsystem widget, + * since we're going to share it across all cylinders and all + * dives. So if you add a new cylinder type or weightsystem in + * one dive, it will show up when you edit the cylinder types + * or weightsystems for another dive. */ - cylinder_model = create_tank_size_model(); - tree_view = cylinder_list_create(); + if (w_idx == W_IDX_PRIMARY) + cylinder_model = create_tank_size_model(); + tree_view = cylinder_list_create(w_idx); + cylinder_list[w_idx].tree_view = GTK_TREE_VIEW(tree_view); hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3); @@ -1475,19 +1485,21 @@ GtkWidget *equipment_widget(void) gtk_box_pack_start(GTK_BOX(hbox), add, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox), del, FALSE, FALSE, 0); - cylinder_list.edit = edit; - cylinder_list.add = add; - cylinder_list.del = del; + cylinder_list[w_idx].edit = edit; + cylinder_list[w_idx].add = add; + cylinder_list[w_idx].del = del; - g_signal_connect(edit, "clicked", G_CALLBACK(edit_cb), tree_view); - g_signal_connect(add, "clicked", G_CALLBACK(add_cb), tree_view); - g_signal_connect(del, "clicked", G_CALLBACK(del_cb), tree_view); + g_signal_connect(edit, "clicked", G_CALLBACK(edit_cb), GINT_TO_POINTER(w_idx)); + g_signal_connect(add, "clicked", G_CALLBACK(add_cb), GINT_TO_POINTER(w_idx)); + g_signal_connect(del, "clicked", G_CALLBACK(del_cb), GINT_TO_POINTER(w_idx)); hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3); - weightsystem_model = create_weightsystem_model(); - tree_view = weightsystem_list_create(); + if (w_idx == W_IDX_PRIMARY) + weightsystem_model = create_weightsystem_model(); + tree_view = weightsystem_list_create(w_idx); + weightsystem_list[w_idx].tree_view = GTK_TREE_VIEW(tree_view); frame = gtk_frame_new("Weight"); gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, FALSE, 3); @@ -1510,13 +1522,13 @@ GtkWidget *equipment_widget(void) gtk_box_pack_start(GTK_BOX(hbox), add, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox), del, FALSE, FALSE, 0); - weightsystem_list.edit = edit; - weightsystem_list.add = add; - weightsystem_list.del = del; + weightsystem_list[w_idx].edit = edit; + weightsystem_list[w_idx].add = add; + weightsystem_list[w_idx].del = del; - g_signal_connect(edit, "clicked", G_CALLBACK(ws_edit_cb), tree_view); - g_signal_connect(add, "clicked", G_CALLBACK(ws_add_cb), tree_view); - g_signal_connect(del, "clicked", G_CALLBACK(ws_del_cb), tree_view); + g_signal_connect(edit, "clicked", G_CALLBACK(ws_edit_cb), GINT_TO_POINTER(w_idx)); + g_signal_connect(add, "clicked", G_CALLBACK(ws_add_cb), GINT_TO_POINTER(w_idx)); + g_signal_connect(del, "clicked", G_CALLBACK(ws_del_cb), GINT_TO_POINTER(w_idx)); return vbox; } diff --git a/gtk-gui.c b/gtk-gui.c index 45aa21263..f7d3c5008 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -794,7 +794,7 @@ void init_ui(int *argcp, char ***argvp) gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new("Dive Notes")); /* Frame for dive equipment */ - nb_page = equipment_widget(); + 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 */ diff --git a/info.c b/info.c index 1847a49bf..842a15001 100644 --- a/info.c +++ b/info.c @@ -344,25 +344,27 @@ static void save_dive_info_changes(struct dive *dive, struct dive_info *info) changed =1; } - 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 (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_info_widget(GtkWidget *box, struct dive *dive, struct dive_info *info) +static void dive_info_widget(GtkWidget *box, struct dive *dive, struct dive_info *info, gboolean multi) { - GtkWidget *hbox, *label, *cylinder, *frame; - char buffer[80]; + GtkWidget *hbox, *label, *frame, *equipment; + char buffer[80] = "Edit multiple dives"; - divename(buffer, sizeof(buffer), dive); + if (!multi) + divename(buffer, sizeof(buffer), dive); label = gtk_label_new(buffer); gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0); @@ -379,28 +381,57 @@ static void dive_info_widget(GtkWidget *box, struct dive *dive, struct dive_info info->rating = text_entry(hbox, "Rating", star_list, star_strings[dive->rating]); - info->notes = text_view(box, "Notes", READ_WRITE); - if (dive->notes && *dive->notes) - gtk_text_buffer_set_text(gtk_text_view_get_buffer(info->notes), dive->notes, -1); - + /* only show notes if editing a single dive */ + if (multi) { + info->notes = NULL; + } else { + info->notes = text_view(box, "Notes", READ_WRITE); + 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(box), hbox, FALSE, TRUE, 0); - frame = gtk_frame_new("Cylinder"); - cylinder = cylinder_list_widget(); - gtk_container_add(GTK_CONTAINER(frame), cylinder); + /* 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, FALSE, TRUE, 0); } -int edit_dive_info(struct dive *dive) +/* 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]; + +void save_equipment_data(struct dive *dive) { - int success; + if (dive) { + memcpy(remember_cyl, dive->cylinder, sizeof(cylinder_t) * MAX_CYLINDERS); + memcpy(remember_ws, dive->weightsystem, sizeof(weightsystem_t) * MAX_WEIGHTSYSTEMS); + } +} + +void update_equipment_data(struct dive *dive, struct dive *master) +{ + if (dive == master) + return; + if (memcmp(remember_cyl, master->cylinder, sizeof(cylinder_t) * MAX_CYLINDERS)) { + memcpy(dive->cylinder, master->cylinder, sizeof(cylinder_t) * MAX_CYLINDERS); + } + if (memcmp(remember_ws, master->weightsystem, sizeof(weightsystem_t) * MAX_WEIGHTSYSTEMS)) { + memcpy(dive->weightsystem, master->weightsystem, sizeof(weightsystem_t) * MAX_WEIGHTSYSTEMS); + } +} + +int edit_multi_dive_info(int nr, int *indices) +{ + int success, i; GtkWidget *dialog, *vbox; struct dive_info info; + struct dive *dive; - if (!dive) + if (!nr) return 0; - dialog = gtk_dialog_new_with_buttons("Dive Info", GTK_WINDOW(main_window), GTK_DIALOG_DESTROY_WITH_PARENT, @@ -409,18 +440,44 @@ int edit_dive_info(struct dive *dive) NULL); vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - dive_info_widget(vbox, dive, &info); - + /* SCARY STUFF - IS THIS THE BEST WAY TO DO THIS??? + * + * current_dive is one of our selected dives - and that is + * the one that is used to pre-fill the edit widget. Its + * data is used as the starting point for all selected dives + * I think it would be better to somehow collect and combine + * info from all the selected dives */ + dive = current_dive; + dive_info_widget(vbox, dive, &info, (nr > 1)); + show_dive_equipment(dive, W_IDX_SECONDARY); + save_equipment_data(dive); gtk_widget_show_all(dialog); success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; if (success) - save_dive_info_changes(dive, &info); - + for (i = 0; i < nr; i++) { + /* copy all "info" fields */ + save_dive_info_changes(get_dive(indices[i]), &info); + /* copy the cylinders / weightsystems */ + update_equipment_data(get_dive(indices[i]), dive); + /* this is extremely inefficient... it loops through all + dives to find the right one - but we KNOW the index already */ + flush_divelist(get_dive(indices[i])); + } gtk_widget_destroy(dialog); return success; } +int edit_dive_info(struct dive *dive) +{ + int idx; + + if (!dive) + return 0; + idx = dive->number; + return edit_multi_dive_info(1, &idx); +} + static GtkWidget *frame_box(GtkWidget *vbox, const char *fmt, ...) { va_list ap; diff --git a/main.c b/main.c index 60f2902e6..8e579f88c 100644 --- a/main.c +++ b/main.c @@ -190,7 +190,7 @@ void update_dive(struct dive *new_dive) } if (new_dive) { show_dive_info(new_dive); - show_dive_equipment(new_dive); + show_dive_equipment(new_dive, W_IDX_PRIMARY); show_dive_stats(new_dive); } buffered_dive = new_dive; From e6ecddfa3d17901847474b67b6121c0c8f56f078 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Thu, 16 Aug 2012 04:27:03 -0700 Subject: [PATCH 34/67] Switch from date based to dive trip based grouping Linus HATED the date based grouping - too much wasted space visually ("three levels of grouping are way too much") and asked for dive trip based grouping instead. This is a quick change to do just that, with an assumption that no dive in 3 days means it's a new trip. This also changes the summary entry to display a location for the trip, for now we pick the location of the (chronologically) first dive of the trip. Signed-off-by: Dirk Hohndel --- divelist.c | 76 +++++++++++++++++++++++++----------------------------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/divelist.c b/divelist.c index a4221f7b9..5b4ef0a10 100644 --- a/divelist.c +++ b/divelist.c @@ -53,10 +53,8 @@ enum { }; /* magic numbers that indicate (as negative values) model entries that - * are summary entries for day / month / year */ -#define NEW_DAY 1 -#define NEW_MON 2 -#define NEW_YR 3 + * are summary entries for a divetrip */ +#define NEW_TRIP 1 #ifdef DEBUG_MODEL static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path, @@ -240,23 +238,13 @@ static void date_data_func(GtkTreeViewColumn *col, tm = gmtime(&when); switch(idx) { - case -NEW_DAY: + case -NEW_TRIP: snprintf(buffer, sizeof(buffer), - "%s, %s %d, %d", + "Trip %s, %s %d, %d", weekday(tm->tm_wday), monthname(tm->tm_mon), tm->tm_mday, tm->tm_year + 1900); break; - case -NEW_MON: - snprintf(buffer, sizeof(buffer), - "%s %d", - monthname(tm->tm_mon), - tm->tm_year + 1900); - break; - case -NEW_YR: - snprintf(buffer, sizeof(buffer), - "%d", tm->tm_year + 1900); - break; default: snprintf(buffer, sizeof(buffer), "%s, %s %d, %d %02d:%02d", @@ -748,22 +736,20 @@ void update_dive_list_col_visibility(void) return; } -static int new_date(struct dive *dive, struct dive **last_dive, const int flag, time_t *tm_date) +/* random heuristic - not diving in three days implies new dive trip */ +#define TRIP_THRESHOLD 3600*24*3 +static int new_group(struct dive *dive, struct dive **last_dive, time_t *tm_date) { if (!last_dive) return TRUE; if (*last_dive) { struct dive *ldive = *last_dive; - struct tm tm1, tm2; - (void) gmtime_r(&dive->when, &tm1); - (void) gmtime_r(&ldive->when, &tm2); - if (tm1.tm_year == tm2.tm_year && - (tm1.tm_mon == tm2.tm_mon || flag > NEW_MON) && - (tm1.tm_mday == tm2.tm_mday || flag > NEW_DAY)) + if (abs(dive->when - ldive->when) < TRIP_THRESHOLD) { + *last_dive = dive; return FALSE; + } } - if (flag == NEW_DAY) - *last_dive = dive; + *last_dive = dive; if (tm_date) { struct tm *tm1 = gmtime(&dive->when); tm1->tm_sec = 0; @@ -776,10 +762,12 @@ static int new_date(struct dive *dive, struct dive **last_dive, const int flag, static void fill_dive_list(void) { - int i, j; - GtkTreeIter iter, parent_iter[NEW_YR + 2], *parents[NEW_YR + 2] = {NULL, }; + int i; + GtkTreeIter iter, parent_iter; GtkTreeStore *liststore, *treestore; struct dive *last_dive = NULL; + struct dive *first_trip_dive = NULL; + struct dive *last_trip_dive = NULL; time_t dive_date; treestore = GTK_TREE_STORE(dive_list.treemodel); @@ -789,23 +777,29 @@ static void fill_dive_list(void) while (--i >= 0) { struct dive *dive = dive_table.dives[i]; - for (j = NEW_YR; j >= NEW_DAY; j--) { - if (new_date(dive, &last_dive, j, &dive_date)) - { - gtk_tree_store_append(treestore, &parent_iter[j], parents[j+1]); - parents[j] = &parent_iter[j]; - gtk_tree_store_set(treestore, parents[j], - DIVE_INDEX, -j, - DIVE_NR, -j, - DIVE_DATE, dive_date, - DIVE_LOCATION, "", - DIVE_TEMPERATURE, 0, - DIVE_SAC, 0, + if (new_group(dive, &last_dive, &dive_date)) + { + /* make sure we display the first date of the trip in previous summary */ + if (first_trip_dive && last_trip_dive && last_trip_dive->when < first_trip_dive->when) + gtk_tree_store_set(treestore, &parent_iter, + DIVE_DATE, last_trip_dive->when, + DIVE_LOCATION, last_trip_dive->location, -1); - } + first_trip_dive = dive; + + gtk_tree_store_append(treestore, &parent_iter, NULL); + gtk_tree_store_set(treestore, &parent_iter, + DIVE_INDEX, -NEW_TRIP, + DIVE_NR, -NEW_TRIP, + DIVE_DATE, dive_date, + DIVE_LOCATION, dive->location, + DIVE_TEMPERATURE, 0, + DIVE_SAC, 0, + -1); } + last_trip_dive = dive; update_cylinder_related_info(dive); - gtk_tree_store_append(treestore, &iter, parents[NEW_DAY]); + gtk_tree_store_append(treestore, &iter, &parent_iter); gtk_tree_store_set(treestore, &iter, DIVE_INDEX, i, DIVE_NR, dive->number, From 673cf274f8841686019827ff0f7c81d0f04f813b Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 16 Aug 2012 11:03:39 -0700 Subject: [PATCH 35/67] Avoid SIGSEGV when editing multiple dives The multi-dive editing is broken if you right-click on the dive text-fields (instead of the divelist). This just avoids the SIGSEGV, it doesn't really fix the editing. Signed-off-by: Linus Torvalds --- info.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/info.c b/info.c index 1c7b6a9ac..6842d3d5b 100644 --- a/info.c +++ b/info.c @@ -470,13 +470,18 @@ int edit_multi_dive_info(int nr, int *indices) success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; if (success) for (i = 0; i < nr; i++) { + int idx = indices[i]; + struct dive *n = get_dive(idx); + + if (!n) + continue; /* copy all "info" fields */ - save_dive_info_changes(get_dive(indices[i]), &info); + save_dive_info_changes(n, &info); /* copy the cylinders / weightsystems */ - update_equipment_data(get_dive(indices[i]), dive); + update_equipment_data(n, dive); /* this is extremely inefficient... it loops through all dives to find the right one - but we KNOW the index already */ - flush_divelist(get_dive(indices[i])); + flush_divelist(n); } gtk_widget_destroy(dialog); From f6dfb0094cf095241377fae52ed02247cf3d03f5 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Thu, 16 Aug 2012 12:48:29 -0700 Subject: [PATCH 36/67] Fix right click edit in Dive Notes area for multiple dives This fixes the bug that triggered the SIGSEGV that Linus worked around earlier. I had forgotten to update this call path to the edit_multi_dive_info function. Signed-off-by: Dirk Hohndel --- dive.h | 1 + divelist.c | 1 - info.c | 3 ++- profile.c | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dive.h b/dive.h index eb9accb6f..b42668c16 100644 --- a/dive.h +++ b/dive.h @@ -281,6 +281,7 @@ struct dive_table { extern struct dive_table dive_table; +extern int *selectiontracker; extern int selected_dive; #define current_dive (get_dive(selected_dive)) diff --git a/divelist.c b/divelist.c index a85ac7c5e..de64bd88c 100644 --- a/divelist.c +++ b/divelist.c @@ -79,7 +79,6 @@ static void dump_model(GtkListStore *store) #endif static GList *selected_dives; -static int *selectiontracker; /* 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 diff --git a/info.c b/info.c index 6842d3d5b..e9a08ca2a 100644 --- a/info.c +++ b/info.c @@ -132,11 +132,12 @@ static int delete_dive_info(struct dive *dive) static void info_menu_edit_cb(GtkMenuItem *menuitem, gpointer user_data) { - edit_dive_info(current_dive); + edit_multi_dive_info(amount_selected, selectiontracker); } static void info_menu_delete_cb(GtkMenuItem *menuitem, gpointer user_data) { + /* this needs to delete all the selected dives as well, I guess? */ delete_dive_info(current_dive); } diff --git a/profile.c b/profile.c index 137ed6f88..7a0eac497 100644 --- a/profile.c +++ b/profile.c @@ -14,6 +14,7 @@ #include "color.h" int selected_dive = 0; +int *selectiontracker; typedef enum { STABLE, SLOW, MODERATE, FAST, CRAZY } velocity_t; From 7cf0f6d5e19f57449078901a3730f9e40de3a012 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Thu, 16 Aug 2012 16:31:53 -0700 Subject: [PATCH 37/67] Stop relying on gtk to track which dives are selected We spend way too much effort trying to get gtk to manage the dives that are selected. The straw that broke the camel's back is that gtk forces us to expand any nodes that we want to select - so selecting a summary entry for a dive trip forced us to expand all the dives in the dive trip. Which as Linus pointed out really sucked from a user experience. So instead we now completeley ignore gtk's weird idea of what is selected and what isn't and simply track things ourselves. We still need to play some games with gtk to make sure that the correct rows are SHOWN as selected, but still, the overall code seems much cleaner. This commit contains a bunch of debugging code that is ifdef'ed out - this is extremely useful to make sure I didn't mess anything up, but eventually I'll want to remove that again as it just looks ugly in the code. Signed-off-by: Dirk Hohndel --- divelist.c | 148 +++++++++++++++++++++++++++++++++++---------------- statistics.c | 28 +++------- 2 files changed, 108 insertions(+), 68 deletions(-) diff --git a/divelist.c b/divelist.c index 5b4ef0a10..19ab600e8 100644 --- a/divelist.c +++ b/divelist.c @@ -78,6 +78,78 @@ static void dump_model(GtkListStore *store) static GList *selected_dives; static int *selectiontracker; +static int st_size = 0; + +gboolean is_in_st(int idx, int *atpos) +{ + int i; + + for (i = 0; i < amount_selected; i++) + if (selectiontracker[i] == idx) { + if (atpos) + *atpos = i; + return TRUE; + } + return FALSE; +} + +#if DEBUG_SELECTION_TRACKING +void dump_selection(void) +{ + int i; + + printf("currently selected are "); + for (i = 0; i < amount_selected; i++) + printf("%d ", selectiontracker[i]); + printf("\n"); +} +#endif + +void track_select(int idx) +{ + if (idx < 0) + return; + +#if DEBUG_SELECTION_TRACKING + printf("add %d to selection of %d entries\n", idx, amount_selected); +#endif + if (is_in_st(idx, NULL)) + return; + if (amount_selected >= st_size) { + selectiontracker = realloc(selectiontracker, dive_table.nr * sizeof(int)); + st_size = dive_table.nr; + } + selectiontracker[amount_selected] = idx; + amount_selected++; + if (amount_selected == 1) + selected_dive = idx; +#if DEBUG_SELECTION_TRACKING + printf("increased amount_selected to %d\n", amount_selected); + dump_selection(); +#endif +} + +void track_unselect(int idx) +{ + if (idx < 0) + return; + +#if DEBUG_SELECTION_TRACKING + printf("remove %d from selection of %d entries\n", idx, amount_selected); +#endif + int atpos; + + if (! is_in_st(idx, &atpos)) + return; + memmove(selectiontracker + atpos, + selectiontracker + atpos + 1, + (amount_selected - atpos - 1) * sizeof(int)); + amount_selected--; +#if DEBUG_SELECTION_TRACKING + printf("removed %d at pos %d and decreased amount_selected to %d\n", idx, atpos, amount_selected); + dump_selection(); +#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 @@ -96,6 +168,7 @@ static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx) 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_model_get(GTK_TREE_MODEL(model), iter, DIVE_INDEX, diveidx, -1); + track_select(*diveidx); } } @@ -105,15 +178,21 @@ static void select_children(GtkTreeModel *model, GtkTreeSelection * selection, GtkTreeIter *iter, gboolean was_selected) { int i, nr_children; + gboolean unexpand = FALSE; GtkTreeIter parent; GtkTreePath *tpath; memcpy(&parent, iter, sizeof(parent)); tpath = gtk_tree_model_get_path(model, &parent); - 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); + /* stupid gtk doesn't allow us to select rows that are invisible; so if the + user clicks on a row that isn't expanded, we briefly expand it, select the + children, and then unexpand it again */ + if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath)) { + unexpand = TRUE; + gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE); + } nr_children = gtk_tree_model_iter_n_children(model, &parent); for (i = 0; i < nr_children; i++) { gtk_tree_model_iter_nth_child(model, iter, &parent, i); @@ -122,6 +201,18 @@ static void select_children(GtkTreeModel *model, GtkTreeSelection * selection, else gtk_tree_selection_select_iter(selection, iter); } + if (unexpand) + gtk_tree_view_collapse_row(GTK_TREE_VIEW(dive_list.tree_view), tpath); +} + +/* make sure that if we expand a summary row that is selected, the children show + up as selected, too */ +void row_expanded_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data) +{ + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + + if (gtk_tree_selection_path_is_selected(selection, path)) + select_children(GTK_TREE_MODEL(dive_list.model), selection, iter, FALSE); } /* this is called _before_ the selection is changed, for every single entry; @@ -135,6 +226,11 @@ gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model, if (gtk_tree_model_get_iter(model, &iter, path)) { gtk_tree_model_get(model, &iter, DIVE_INDEX, &dive_idx, -1); + /* turns out we need to move the selectiontracker here */ + if (was_selected) + track_unselect(dive_idx); + else + track_select(dive_idx); if (dive_idx < 0) { select_children(model, selection, &iter, was_selected); } @@ -146,49 +242,8 @@ gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model, /* this is called when gtk thinks that the selection has changed */ static void selection_cb(GtkTreeSelection *selection, gpointer userdata) { - GtkTreeIter iter; - GtkTreePath *path; - - int nr_selected = gtk_tree_selection_count_selected_rows(selection); - - if (selected_dives) { - g_list_foreach (selected_dives, (GFunc) gtk_tree_path_free, NULL); - g_list_free (selected_dives); - } - selected_dives = gtk_tree_selection_get_selected_rows(selection, NULL); - selectiontracker = realloc(selectiontracker, nr_selected * sizeof(int)); - - switch (nr_selected) { - case 0: /* there is no clear way to figure out which dive to show */ - amount_selected = 0; - selected_dive = -1; - return; - case 1: - /* just pick that dive as selected */ - amount_selected = 1; - path = g_list_nth_data(selected_dives, 0); - if (gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path)) { - gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1); - /* due to the way this callback gets invoked it is possible that - in the process of unselecting a summary dive we get here with - just one summary dive selected - ignore that case */ - if (selected_dive < 0) { - amount_selected = 0; - return; - } - selectiontracker[0] = selected_dive; - repaint_dive(); - } - return; - default: /* multiple selections - what now? - * We don't change the selected dive unless there is exactly one dive selected; not sure this - * is the most intuitive solution. - * The dives that have been selected are processed */ - amount_selected = g_list_length(selected_dives); - process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model)); - repaint_dive(); - return; - } + process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model)); + repaint_dive(); } const char *star_strings[] = { @@ -834,8 +889,6 @@ static void fill_dive_list(void) first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); gtk_tree_selection_select_iter(selection, &iter); - selectiontracker = realloc(selectiontracker, sizeof(int)); - *selectiontracker = selected_dive; } } @@ -1137,6 +1190,7 @@ GtkWidget *dive_list_create(void) 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, "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), NULL); diff --git a/statistics.c b/statistics.c index 3a260066a..7ff2bfd01 100644 --- a/statistics.c +++ b/statistics.c @@ -146,34 +146,20 @@ static void process_all_dives(struct dive *dive, struct dive **prev_dive) void process_selected_dives(GList *selected_dives, int *selectiontracker, GtkTreeModel *model) { struct dive *dp; - unsigned int i, j; + unsigned int i; int idx; - GtkTreeIter iter; - GtkTreePath *path; memset(&stats_selection, 0, sizeof(stats_selection)); - /* adjust amount_selected and remove negative index entries from list */ - for (i = 0, j = 0; j < amount_selected; ++i) { - GValue value = {0, }; - path = g_list_nth_data(selected_dives, i); - if (gtk_tree_model_get_iter(model, &iter, path)) { - gtk_tree_model_get_value(model, &iter, 0, &value); - idx = g_value_get_int(&value); - if (idx > 0) { - dp = get_dive(idx); - if (dp) { - selectiontracker[j] = idx; - process_dive(dp, &stats_selection); - j++; - continue; - } + for (i = 0; i < amount_selected; ++i) { + idx = selectiontracker[i]; + if (idx > 0) { + dp = get_dive(idx); + if (dp) { + process_dive(dp, &stats_selection); } } - /* we didn't process it, so shorten the list */ - amount_selected--; } - /* record the actual number of dives selected */ stats_selection.selection_size = amount_selected; } From 2f773b97e042799fbb284b15cb682104dfdbba9d Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 16 Aug 2012 20:30:32 -0700 Subject: [PATCH 38/67] multi-dive editing: don't change already set data for other dives When editing multiple dives at the same time, don't change fields that have already been set for a dive, unless the old field contents match the currently selected ("master") dive. So when you edit multiple dives, you can set the dive master or buddy (or suit etc) for all of them in one go, but if one of them already has that field set, it won't be modified just because you set the other ones. Acked-by: Dirk Hohndel Signed-off-by: Linus Torvalds --- info.c | 73 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/info.c b/info.c index e9a08ca2a..846d51efc 100644 --- a/info.c +++ b/info.c @@ -43,12 +43,42 @@ static int text_changed(const char *old, const char *new) (!old && strcmp("",new)); } -static char *get_combo_box_entry_text(GtkComboBoxEntry *combo_box, char **textp) +static char *skip_space(char *str) +{ + if (str) { + while (isspace(*str)) + str++; + if (!*str) + str = NULL; + } + return str; +} + +/* + * 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(GtkComboBoxEntry *combo_box, char **textp, char *master) { char *old = *textp; const gchar *new; GtkEntry *entry; + old = skip_space(old); + master = skip_space(master); + + /* + * If we had a master string, and it doesn't match our old + * string, we will always pick the old value (it means that + * we're editing another dive's info that already had a + * valid value). + */ + if (master && old) + if (strcmp(master, old)) + return NULL; + entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo_box))); new = gtk_entry_get_text(entry); while (isspace(*new)) @@ -321,38 +351,38 @@ struct dive_info { GtkTextView *notes; }; -static void save_dive_info_changes(struct dive *dive, struct dive_info *info) +static void save_dive_info_changes(struct dive *dive, struct dive *master, struct dive_info *info) { char *old_text, *new_text; char *rating_string; int changed = 0; - new_text = get_combo_box_entry_text(info->location, &dive->location); + new_text = get_combo_box_entry_text(info->location, &dive->location, master->location); if (new_text) { add_location(new_text); changed = 1; } - new_text = get_combo_box_entry_text(info->divemaster, &dive->divemaster); + 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); + 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); + 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); + new_text = get_combo_box_entry_text(info->rating, &rating_string, star_strings[master->rating]); if (new_text) { dive->rating = get_rating(rating_string); free(rating_string); @@ -444,7 +474,7 @@ int edit_multi_dive_info(int nr, int *indices) int success, i; GtkWidget *dialog, *vbox; struct dive_info info; - struct dive *dive; + struct dive *master; if (!nr) return 0; @@ -463,27 +493,34 @@ int edit_multi_dive_info(int nr, int *indices) * data is used as the starting point for all selected dives * I think it would be better to somehow collect and combine * info from all the selected dives */ - dive = current_dive; - dive_info_widget(vbox, dive, &info, (nr > 1)); - show_dive_equipment(dive, W_IDX_SECONDARY); - save_equipment_data(dive); + master = current_dive; + dive_info_widget(vbox, master, &info, (nr > 1)); + show_dive_equipment(master, W_IDX_SECONDARY); + save_equipment_data(master); gtk_widget_show_all(dialog); success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; - if (success) + if (success) { + /* Update the other non-current dives first */ for (i = 0; i < nr; i++) { int idx = indices[i]; - struct dive *n = get_dive(idx); + struct dive *dive = get_dive(idx); - if (!n) + if (!dive || dive == master) continue; /* copy all "info" fields */ - save_dive_info_changes(n, &info); + save_dive_info_changes(dive, master, &info); /* copy the cylinders / weightsystems */ - update_equipment_data(n, dive); + update_equipment_data(dive, master); /* this is extremely inefficient... it loops through all dives to find the right one - but we KNOW the index already */ - flush_divelist(n); + flush_divelist(dive); } + + /* Update the master dive last! */ + save_dive_info_changes(master, master, &info); + update_equipment_data(master, master); + flush_divelist(master); + } gtk_widget_destroy(dialog); return success; From 94516177605484dacf724602c43f34b984d06ba8 Mon Sep 17 00:00:00 2001 From: Henrik Brautaset Aronsen Date: Fri, 17 Aug 2012 08:56:03 +0200 Subject: [PATCH 39/67] Remove repetitions of "Show" in Preferences dialog Instead of having "Show Temp", "Show Cyl", etc in the Preferences dialog, rename the group as "Show Columns" and remove "Show " from all the checkboxes. The dialog is tighter/nicer this way. Signed-off-by: Henrik Brautaset Aronsen --- gtk-gui.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/gtk-gui.c b/gtk-gui.c index 619af2f08..7d449dd40 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -405,43 +405,43 @@ static void preferences_dialog(GtkWidget *w, gpointer data) "lbs", set_lbs, (output_units.weight == LBS), NULL); - frame = gtk_frame_new("Columns"); + frame = gtk_frame_new("Show Columns"); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5); box = gtk_hbox_new(FALSE, 6); gtk_container_add(GTK_CONTAINER(frame), box); - button = gtk_check_button_new_with_label("Show Temp"); + button = gtk_check_button_new_with_label("Temp"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.temperature); gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(temperature_toggle), NULL); - button = gtk_check_button_new_with_label("Show Cyl"); + button = gtk_check_button_new_with_label("Cyl"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.cylinder); gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(cylinder_toggle), NULL); - button = gtk_check_button_new_with_label("Show O" UTF8_SUBSCRIPT_2 "%"); + button = gtk_check_button_new_with_label("O" UTF8_SUBSCRIPT_2 "%"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.nitrox); gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(nitrox_toggle), NULL); - button = gtk_check_button_new_with_label("Show SAC"); + button = gtk_check_button_new_with_label("SAC"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.sac); gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(sac_toggle), NULL); - button = gtk_check_button_new_with_label("Show OTU"); + button = gtk_check_button_new_with_label("OTU"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.otu); gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(otu_toggle), NULL); - button = gtk_check_button_new_with_label("Show Weight"); + button = gtk_check_button_new_with_label("Weight"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), 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("Show Suit"); + button = gtk_check_button_new_with_label("Suit"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), 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); From 76298c64e374b65711734ae42ed455fb18aab702 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Tue, 17 Jul 2012 16:05:40 +0200 Subject: [PATCH 40/67] When the file has been opened rely on it to save. When a file is opened, we keep it in memory and when you try to quit while the data has been changed, propose to save back to this same file. Signed-off-by: Pierre-Yves Chibon --- gtk-gui.c | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/gtk-gui.c b/gtk-gui.c index 7d449dd40..f28dde484 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -173,26 +173,28 @@ static void file_open(GtkWidget *w, gpointer data) static void file_save(GtkWidget *w, gpointer data) { GtkWidget *dialog; - dialog = gtk_file_chooser_dialog_new("Save File", + char *filename; + if (!existing_filename) { + dialog = gtk_file_chooser_dialog_new("Save 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 (!existing_filename) { - gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document"); - } else - gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), existing_filename); + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - char *filename; - filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document"); + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + } + gtk_widget_destroy(dialog); + } else { + filename = existing_filename; + } + if (filename){ save_dives(filename); - g_free(filename); mark_divelist_changed(FALSE); } - gtk_widget_destroy(dialog); } static void ask_save_changes() @@ -204,7 +206,18 @@ static void ask_save_changes() GTK_STOCK_NO, GTK_RESPONSE_NO, NULL); content = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); - label = gtk_label_new ("You have unsaved changes\nWould you like to save those before exiting the program?"); + + if (!existing_filename){ + label = gtk_label_new ( + "You have unsaved changes\nWould you like to save those before exiting the program?"); + } else { + char *label_text = (char*) malloc(sizeof(char) * (92 + strlen(existing_filename))); + sprintf(label_text, + "You have unsaved changes to file: %s \nWould you like to save those before exiting the program?", + existing_filename); + label = gtk_label_new (label_text); + g_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); From 1a7695115964887cd19bf1d7e2cef60a2c939482 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Tue, 17 Jul 2012 16:09:29 +0200 Subject: [PATCH 41/67] Allow to cancel while trying to quit and the data was changed. So far, when trying to quit while the data was changed the offer was "Save" or "Don't save". Now, you can also "Cancel" which will bring you back to the main window. This allows you to re-save the data in another file. Signed-off-by: Pierre-Yves Chibon --- gtk-gui.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/gtk-gui.c b/gtk-gui.c index f28dde484..a969e9552 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -197,13 +197,15 @@ static void file_save(GtkWidget *w, gpointer data) } } -static void ask_save_changes() +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)); @@ -221,10 +223,14 @@ static void ask_save_changes() gtk_container_add (GTK_CONTAINER (content), label); gtk_widget_show_all (dialog); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT); - if (gtk_dialog_run(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) { + quit = FALSE; } gtk_widget_destroy(dialog); + return quit; } static gboolean on_delete(GtkWidget* w, gpointer data) @@ -232,10 +238,15 @@ static gboolean on_delete(GtkWidget* w, gpointer data) /* Make sure to flush any modified dive data */ update_dive(NULL); + gboolean quit = TRUE; if (unsaved_changes()) - ask_save_changes(); + quit = ask_save_changes(); - return FALSE; /* go ahead, kill the program, we're good now */ + if (quit){ + 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) @@ -248,9 +259,13 @@ static void quit(GtkWidget *w, gpointer data) /* Make sure to flush any modified dive data */ update_dive(NULL); + gboolean quit = TRUE; if (unsaved_changes()) - ask_save_changes(); - gtk_main_quit(); + quit = ask_save_changes(); + + if (quit){ + gtk_main_quit(); + } } GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title, From 9cb36850303f8ce6c031926512aad3fc2d800889 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Chibon Date: Tue, 17 Jul 2012 16:49:27 +0200 Subject: [PATCH 42/67] Add a 'Save As' entry in the menu. Add a "Save As" entry in the "File" menu allowing the user to specify the file in which to save the data. This is useful as we no longer offer this option through the "Save" entry while the data had been opened from an existing file. Signed-off-by: Pierre-Yves Chibon --- display-gtk.h | 2 ++ gtk-gui.c | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/display-gtk.h b/display-gtk.h index 059c6aa23..1f143077e 100644 --- a/display-gtk.h +++ b/display-gtk.h @@ -31,9 +31,11 @@ typedef enum { #if defined __APPLE__ #define CTRLCHAR "" +#define SHIFTCHAR "" #define PREFERENCE_ACCEL "comma" #else #define CTRLCHAR "" +#define SHIFTCHAR "" #define PREFERENCE_ACCEL NULL #endif diff --git a/gtk-gui.c b/gtk-gui.c index a969e9552..02463d91e 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -197,6 +197,30 @@ static void file_save(GtkWidget *w, gpointer data) } } +static void file_save_as(GtkWidget *w, gpointer data) +{ + GtkWidget *dialog; + char *filename; + 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); + + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), existing_filename); + 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); + mark_divelist_changed(FALSE); + } +} + static gboolean ask_save_changes() { GtkWidget *dialog, *label, *content; @@ -666,6 +690,7 @@ static GtkActionEntry menu_items[] = { { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL}, { "OpenFile", GTK_STOCK_OPEN, NULL, CTRLCHAR "O", NULL, G_CALLBACK(file_open) }, { "SaveFile", GTK_STOCK_SAVE, NULL, CTRLCHAR "S", NULL, G_CALLBACK(file_save) }, + { "SaveAsFile", GTK_STOCK_SAVE_AS, NULL, SHIFTCHAR CTRLCHAR "S", NULL, G_CALLBACK(file_save_as) }, { "Print", GTK_STOCK_PRINT, NULL, CTRLCHAR "P", NULL, G_CALLBACK(do_print) }, { "Import", NULL, "Import", NULL, NULL, G_CALLBACK(import_dialog) }, { "AddDive", NULL, "Add Dive", NULL, NULL, G_CALLBACK(add_dive_cb) }, @@ -687,6 +712,7 @@ static const gchar* ui_string = " \ \ \ \ + \ \ \ \ From 70f903c457a3b82cdaa62e232124cdbba5c11bc5 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 17 Aug 2012 07:39:50 -0700 Subject: [PATCH 43/67] multi-dive editing: don't change fields that weren't changed for the master dive Commit 2f773b97e042 ("multi-dive editing: don't change already set data for other dives") didn't get the multi-dive editing quite right: even if one of the dives in the list of changed dives has an empty field, we should *not* fill it with the edit data unless that edit data was actually changed. So compare the new data with the original master data, and if they match, do nothing. Signed-off-by: Linus Torvalds --- info.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/info.c b/info.c index 846d51efc..5a620e751 100644 --- a/info.c +++ b/info.c @@ -83,6 +83,9 @@ static char *get_combo_box_entry_text(GtkComboBoxEntry *combo_box, char **textp, new = gtk_entry_get_text(entry); while (isspace(*new)) new++; + /* If the master string didn't change, don't change other dives either! */ + if (!text_changed(master,new)) + return NULL; if (!text_changed(old,new)) return NULL; free(old); From aab94d07ccb890caaa03cf220b442ea5ce228fc1 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Fri, 17 Aug 2012 09:36:04 -0700 Subject: [PATCH 44/67] Fix string handling in get_combo_box_entry_text Linus' code dropped the const qualifier from the start rating. While fixing this I stared some more at get_combo_box_entry_text and realized that the existing code could potentially change the "old" pointer and then pass it to free(). Tsk-tsk-tsk. Signed-off-by: Dirk Hohndel --- info.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/info.c b/info.c index 5a620e751..0ee401488 100644 --- a/info.c +++ b/info.c @@ -43,7 +43,7 @@ static int text_changed(const char *old, const char *new) (!old && strcmp("",new)); } -static char *skip_space(char *str) +static const char *skip_space(const char *str) { if (str) { while (isspace(*str)) @@ -60,13 +60,14 @@ static char *skip_space(char *str) * 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(GtkComboBoxEntry *combo_box, char **textp, char *master) +static char *get_combo_box_entry_text(GtkComboBoxEntry *combo_box, char **textp, const char *master) { char *old = *textp; + const char *old_text; const gchar *new; GtkEntry *entry; - old = skip_space(old); + old_text = skip_space(old); master = skip_space(master); /* @@ -75,8 +76,8 @@ static char *get_combo_box_entry_text(GtkComboBoxEntry *combo_box, char **textp, * we're editing another dive's info that already had a * valid value). */ - if (master && old) - if (strcmp(master, old)) + if (master && old_text) + if (strcmp(master, old_text)) return NULL; entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo_box))); From bc1ff9a1213e4c2a0c135974cbce03bc7614d7b5 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Fri, 17 Aug 2012 14:23:38 -0700 Subject: [PATCH 45/67] More fiddling with the selection As expected, this is pretty subtle to get right. But with this change the code becomes simpler and more straight forward, I think. If the dives in a group are collapsed, we don't even try to make gtk keep track of their selection status - we explicitly do so ourselves. This avoids the artificial expand / collapse around our attempt to force gtk to allow us to select children that are hidden. But if a dive is expanded, then we trust gtk to get things right. Signed-off-by: Dirk Hohndel --- divelist.c | 52 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/divelist.c b/divelist.c index 0e1b40dd6..b1b74d45a 100644 --- a/divelist.c +++ b/divelist.c @@ -169,7 +169,6 @@ static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx) 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_model_get(GTK_TREE_MODEL(model), iter, DIVE_INDEX, diveidx, -1); - track_select(*diveidx); } } @@ -179,31 +178,36 @@ static void select_children(GtkTreeModel *model, GtkTreeSelection * selection, GtkTreeIter *iter, gboolean was_selected) { int i, nr_children; - gboolean unexpand = FALSE; + gboolean expanded = FALSE; GtkTreeIter parent; GtkTreePath *tpath; memcpy(&parent, iter, sizeof(parent)); tpath = gtk_tree_model_get_path(model, &parent); - - /* stupid gtk doesn't allow us to select rows that are invisible; so if the - user clicks on a row that isn't expanded, we briefly expand it, select the - children, and then unexpand it again */ - if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath)) { - unexpand = TRUE; - gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE); - } + expanded = gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath); nr_children = gtk_tree_model_iter_n_children(model, &parent); for (i = 0; i < nr_children; i++) { gtk_tree_model_iter_nth_child(model, iter, &parent, i); - if (was_selected) - gtk_tree_selection_unselect_iter(selection, iter); - else - gtk_tree_selection_select_iter(selection, iter); + + /* if the parent is expanded, just (un)select the children and we'll + track their selection status in the callback + otherwise just change the selection status directly without + bothering gtk */ + if (expanded) { + if (was_selected) + gtk_tree_selection_unselect_iter(selection, iter); + else + gtk_tree_selection_select_iter(selection, iter); + } else { + int idx; + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + if (was_selected) + track_unselect(idx); + else + track_select(idx); + } } - if (unexpand) - gtk_tree_view_collapse_row(GTK_TREE_VIEW(dive_list.tree_view), tpath); } /* make sure that if we expand a summary row that is selected, the children show @@ -228,11 +232,17 @@ gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model, if (gtk_tree_model_get_iter(model, &iter, path)) { gtk_tree_model_get(model, &iter, DIVE_INDEX, &dive_idx, -1); /* turns out we need to move the selectiontracker here */ - if (was_selected) - track_unselect(dive_idx); - else - track_select(dive_idx); - if (dive_idx < 0) { + +#if DEBUG_SELECTION_TRACKING + printf("modify_selection_cb with idx %d (according to gtk was %sselected) - ", + dive_idx, was_selected ? "" : "un"); +#endif + if (dive_idx >= 0) { + if (was_selected) + track_unselect(dive_idx); + else + track_select(dive_idx); + } else { select_children(model, selection, &iter, was_selected); } } From 9e9ba73b3d21c2184c16f3ba5cf3a71f7c662a55 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Fri, 17 Aug 2012 15:03:57 -0700 Subject: [PATCH 46/67] Another selection fix The corner cases are getting more and more artificial. Without this patch, the following can happen: Select one or more dives in an (expanded) dive trip. Now collapse that trip with the little triangle. Select a different trip. The previously selected dive(s) are still part of the selection (as you can see, for example, in the statistics tab). With this patch the scenario above works as intended (all the dives in the new trip are selected), but we have another corner case: Just as before, select one or more dives in an expanded dive trip. Collapse that trip and ctrl-click on another trip. Now you lose the originally selected dives. Frankly, if you ctrl-click to add more dives to your selection - just don't collapse the trips the dives are in? As this new corner case seems even more artificial than the previous one, I consider this patch an improvement. But fundamentally I am just battling all the ways in which gtk's selection handling is messed up. When I get the selection call back I cannot tell if this is a new selection or an incremental selection (i.e., a shift-click or ctrl-click). Signed-off-by: Dirk Hohndel --- divelist.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/divelist.c b/divelist.c index b1b74d45a..0f5c90002 100644 --- a/divelist.c +++ b/divelist.c @@ -152,6 +152,11 @@ void track_unselect(int idx) #endif } +void clear_tracker(void) +{ + amount_selected = 0; +} + /* 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) */ @@ -229,12 +234,21 @@ gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreeIter iter; int dive_idx; + /* if gtk thinks nothing is selected we should clear out our + tracker as well - otherwise hidden selected rows can stay + "stuck". The down side is that we now have a different bug: + If you select a dive, collapse the dive trip and ctrl-click + another dive trip, the initial dive is no longer selected. + Just don't do that, ok? */ + if (gtk_tree_selection_count_selected_rows(selection) == 0) + clear_tracker(); + if (gtk_tree_model_get_iter(model, &iter, path)) { gtk_tree_model_get(model, &iter, DIVE_INDEX, &dive_idx, -1); /* turns out we need to move the selectiontracker here */ #if DEBUG_SELECTION_TRACKING - printf("modify_selection_cb with idx %d (according to gtk was %sselected) - ", + printf("modify_selection_cb with idx %d (according to gtk was %sselected)\n", dive_idx, was_selected ? "" : "un"); #endif if (dive_idx >= 0) { From 50f6c6d8bccee167dcaa005964af0468535524a3 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Fri, 17 Aug 2012 19:52:04 -0700 Subject: [PATCH 47/67] When editing multiple files, don't override existing equipment entries This parallels the logic used for all the string entries. Signed-off-by: Dirk Hohndel --- info.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/info.c b/info.c index 38776487b..f718e85dc 100644 --- a/info.c +++ b/info.c @@ -473,12 +473,12 @@ void update_equipment_data(struct dive *dive, struct dive *master) { if (dive == master) return; - if (memcmp(remember_cyl, master->cylinder, sizeof(cylinder_t) * MAX_CYLINDERS)) { + if (memcmp(remember_cyl, master->cylinder, sizeof(cylinder_t) * MAX_CYLINDERS) && + cylinder_none(dive->cylinder)) memcpy(dive->cylinder, master->cylinder, sizeof(cylinder_t) * MAX_CYLINDERS); - } - if (memcmp(remember_ws, master->weightsystem, sizeof(weightsystem_t) * MAX_WEIGHTSYSTEMS)) { + if (memcmp(remember_ws, master->weightsystem, sizeof(weightsystem_t) * MAX_WEIGHTSYSTEMS) && + weightsystem_none(dive->weightsystem)) memcpy(dive->weightsystem, master->weightsystem, sizeof(weightsystem_t) * MAX_WEIGHTSYSTEMS); - } } int edit_multi_dive_info(int nr, int *indices) From 5b56aa1435ddd20e61e5bac515c0a74eb051eca5 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Fri, 17 Aug 2012 19:52:49 -0700 Subject: [PATCH 48/67] Minor Macos menu entry modification fix We have removed a menu separator from the gtk gui and that was still referenced in the Macos code. And just in case, we are now testing for the widget for the other separator to be non-NULL before destroying it. Signed-off-by: Dirk Hohndel --- macos.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/macos.c b/macos.c index 931d4fa1e..1b7da1ec6 100644 --- a/macos.c +++ b/macos.c @@ -98,10 +98,9 @@ void subsurface_ui_setup(GtkSettings *settings, GtkWidget *menubar, gtk_widget_hide (menubar); gtk_osxapplication_set_menu_bar(osx_app, GTK_MENU_SHELL(menubar)); - sep = gtk_ui_manager_get_widget(ui_manager, "/MainMenu/FileMenu/Separator3"); - gtk_widget_destroy(sep); sep = gtk_ui_manager_get_widget(ui_manager, "/MainMenu/FileMenu/Separator2"); - gtk_widget_destroy(sep); + if (sep) + gtk_widget_destroy(sep); menu_item = gtk_ui_manager_get_widget(ui_manager, "/MainMenu/FileMenu/Quit"); gtk_widget_hide (menu_item); From fe32e5128742221e067fbb96fa7fcb07dae2bd22 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Fri, 17 Aug 2012 20:22:37 -0700 Subject: [PATCH 49/67] Import Divesuit information from DivingLog XML file Trivial two-liner patch Signed-off-by: Dirk Hohndel --- parse-xml.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/parse-xml.c b/parse-xml.c index a36758ca0..173314dd4 100644 --- a/parse-xml.c +++ b/parse-xml.c @@ -1102,6 +1102,8 @@ static void try_to_fill_dive(struct dive **divep, const char *name, char *buf) return; if (MATCH(".suit", utf8_string, &dive->suit)) return; + if (MATCH(".divesuit", utf8_string, &dive->suit)) + return; if (MATCH(".notes", utf8_string, &dive->notes)) return; if (MATCH(".divemaster", utf8_string, &dive->divemaster)) From 5487606fda75f133e26900aede1430b8929f8e18 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sat, 18 Aug 2012 06:24:49 -0700 Subject: [PATCH 50/67] Fix crash when editing weight system info I missed one instance where a callback function needed to be passed the widget index w_idx in the signal_connect function. It got passed a pointer to the model instead which of course blew up when trying to dereference the array with that "index". Signed-off-by: Dirk Hohndel --- equipment.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/equipment.c b/equipment.c index 71639c9d1..d10310c16 100644 --- a/equipment.c +++ b/equipment.c @@ -1397,7 +1397,7 @@ GtkWidget *weightsystem_list_widget(int w_idx) tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model)); gtk_widget_set_can_focus(tree_view, FALSE); - g_signal_connect(tree_view, "row-activated", G_CALLBACK(ws_row_activated_cb), model); + g_signal_connect(tree_view, "row-activated", G_CALLBACK(ws_row_activated_cb), GINT_TO_POINTER(w_idx)); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE); From c26c370c2b519cbceaa231cc3c840c78ca92d2a7 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sat, 18 Aug 2012 08:28:52 -0700 Subject: [PATCH 51/67] Correct multi-edit equipment update logic I lied in the commit message for commit 0468535524a3 ("When editing multiple files, don't override existing equipment entries"); the changes there did not parallel the logic for the string entries. Now I think it does. Signed-off-by: Dirk Hohndel --- dive.h | 7 ++++-- equipment.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++--- info.c | 24 ++++++++++++------ 3 files changed, 89 insertions(+), 13 deletions(-) diff --git a/dive.h b/dive.h index b42668c16..41f427a2c 100644 --- a/dive.h +++ b/dive.h @@ -92,8 +92,11 @@ typedef struct { const char *description; /* "integrated", "belt", "ankle" */ } weightsystem_t; -extern int cylinder_none(void *_data); -extern int weightsystem_none(void *_data); +extern gboolean cylinder_none(void *_data); +extern gboolean no_cylinders(cylinder_t *cyl); +extern gboolean cylinders_equal(cylinder_t *cyl1, cylinder_t *cyl2); +extern gboolean no_weightsystems(weightsystem_t *ws); +extern gboolean weightsystems_equal(weightsystem_t *ws1, weightsystem_t *ws2); extern int get_pressure_units(unsigned int mb, const char **units); extern double get_depth_units(unsigned int mm, int *frac, const char **units); diff --git a/equipment.c b/equipment.c index d10310c16..43bb29d59 100644 --- a/equipment.c +++ b/equipment.c @@ -428,7 +428,7 @@ static void show_weightsystem(weightsystem_t *ws, struct ws_widget *weightsystem set_weight_weight_spinbutton(weightsystem_widget, ws->weight.grams); } -int cylinder_none(void *_data) +gboolean cylinder_none(void *_data) { cylinder_t *cyl = _data; return !cyl->type.size.mliter && @@ -442,12 +442,77 @@ int cylinder_none(void *_data) !cyl->end.mbar; } -int weightsystem_none(void *_data) +gboolean no_cylinders(cylinder_t *cyl) +{ + int i; + + for (i = 0; i < MAX_CYLINDERS; i++) + if (!cylinder_none(cyl + i)) + return FALSE; + return TRUE; +} + +/* descriptions are equal if they are both NULL or both non-NULL + and the same text */ +gboolean description_equal(const char *desc1, const char *desc2) +{ + return ((! desc1 && ! desc2) || + (desc1 && desc2 && strcmp(desc1, desc2) == 0)); +} + +/* when checking for the same cylinder we want the size and description to match + but don't compare the start and end pressures */ +static gboolean one_cylinder_equal(cylinder_t *cyl1, cylinder_t *cyl2) +{ + return cyl1->type.size.mliter == cyl2->type.size.mliter && + cyl1->type.workingpressure.mbar == cyl2->type.workingpressure.mbar && + cyl1->gasmix.o2.permille == cyl2->gasmix.o2.permille && + cyl1->gasmix.he.permille == cyl2->gasmix.he.permille && + description_equal(cyl1->type.description, cyl2->type.description); +} + +gboolean cylinders_equal(cylinder_t *cyl1, cylinder_t *cyl2) +{ + int i; + + for (i = 0; i < MAX_CYLINDERS; i++) + if (!one_cylinder_equal(cyl1 + i, cyl2 + i)) + return FALSE; + return TRUE; +} + +static gboolean weightsystem_none(void *_data) { weightsystem_t *ws = _data; return !ws->weight.grams && !ws->description; } +gboolean no_weightsystems(weightsystem_t *ws) +{ + int i; + + for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) + if (!weightsystem_none(ws + i)) + return FALSE; + return TRUE; +} + +static gboolean one_weightsystem_equal(weightsystem_t *ws1, weightsystem_t *ws2) +{ + return ws1->weight.grams == ws2->weight.grams && + description_equal(ws1->description, ws2->description); +} + +gboolean weightsystems_equal(weightsystem_t *ws1, weightsystem_t *ws2) +{ + int i; + + for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) + if (!one_weightsystem_equal(ws1 + i, ws2 + i)) + return FALSE; + return TRUE; +} + static void set_one_cylinder(void *_data, GtkListStore *model, GtkTreeIter *iter) { cylinder_t *cyl = _data; @@ -493,7 +558,7 @@ static void *ws_ptr(struct dive *dive, int idx) static void show_equipment(struct dive *dive, int max, struct equipment_list *equipment_list, void*(*ptr_function)(struct dive*, int), - int(*none_function)(void *), + gboolean(*none_function)(void *), void(*set_one_function)(void*, GtkListStore*, GtkTreeIter *)) { int i, used; diff --git a/info.c b/info.c index f718e85dc..0616eead9 100644 --- a/info.c +++ b/info.c @@ -460,25 +460,33 @@ static void dive_info_widget(GtkWidget *box, struct dive *dive, struct dive_info /* 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 void save_equipment_data(struct dive *dive) { if (dive) { - memcpy(remember_cyl, dive->cylinder, sizeof(cylinder_t) * MAX_CYLINDERS); - memcpy(remember_ws, dive->weightsystem, sizeof(weightsystem_t) * MAX_WEIGHTSYSTEMS); + memcpy(remember_cyl, dive->cylinder, CYL_BYTES); + memcpy(remember_ws, dive->weightsystem, WS_BYTES); } } +/* the editing happens on the master dive; we copy the equipment + 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 */ void update_equipment_data(struct dive *dive, struct dive *master) { if (dive == master) return; - if (memcmp(remember_cyl, master->cylinder, sizeof(cylinder_t) * MAX_CYLINDERS) && - cylinder_none(dive->cylinder)) - memcpy(dive->cylinder, master->cylinder, sizeof(cylinder_t) * MAX_CYLINDERS); - if (memcmp(remember_ws, master->weightsystem, sizeof(weightsystem_t) * MAX_WEIGHTSYSTEMS) && - weightsystem_none(dive->weightsystem)) - memcpy(dive->weightsystem, master->weightsystem, sizeof(weightsystem_t) * MAX_WEIGHTSYSTEMS); + if ( ! cylinders_equal(remember_cyl, master->cylinder) && + (no_cylinders(dive->cylinder) || + cylinders_equal(dive->cylinder, remember_cyl))) + memcpy(dive->cylinder, master->cylinder, CYL_BYTES); + if (! weightsystems_equal(remember_ws, master->weightsystem) && + (no_weightsystems(dive->weightsystem) || + weightsystems_equal(dive->weightsystem, remember_ws))) + memcpy(dive->weightsystem, master->weightsystem, WS_BYTES); } int edit_multi_dive_info(int nr, int *indices) From 1a05f34ae89d35f6ece12dba7b6ab5f60df98705 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 18 Aug 2012 09:02:27 -0700 Subject: [PATCH 52/67] Make fixup_divep robust against insane dive times This fixes the case of the dive duration being zero, or being shorter than the assumed ascent/descent time. Reported-by: Lutz Vieweg Signed-off-by: Linus Torvalds --- dive.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/dive.c b/dive.c index f4bf497a7..aee09d53a 100644 --- a/dive.c +++ b/dive.c @@ -472,11 +472,18 @@ struct dive *fixup_dive(struct dive *dive) cyl->sample_end.mbar = 0; } } - if (end < 0) - { + if (end < 0) { /* Assume an ascent/descent rate of 9 m/min */ - int asc_desc_time = dive->maxdepth.mm*60/9000; - dive->meandepth.mm = dive->maxdepth.mm*(dive->duration.seconds-asc_desc_time)/dive->duration.seconds; + int depth = dive->maxdepth.mm; + int asc_desc_time = depth*60/9000; + int duration = dive->duration.seconds; + + /* Protect against insane dives - make mean be half of max */ + if (duration <= asc_desc_time) { + duration = 2; + asc_desc_time = 1; + } + dive->meandepth.mm = depth*(duration-asc_desc_time)/duration; return dive; } From 52c3d11d2c2a0415bf501abff6c9f99b6a7f2cce Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 18 Aug 2012 09:05:51 -0700 Subject: [PATCH 53/67] Make fill_missing_tank_pressures robust against missing cylinder info The code iterates over a list that can be NULL, but happily dereferenced it anyway. Oops. This function really should be split up and commented more. Signed-off-by: Linus Torvalds --- profile.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/profile.c b/profile.c index 90ed0b609..d3362432e 100644 --- a/profile.c +++ b/profile.c @@ -1049,16 +1049,18 @@ static void fill_missing_tank_pressures(struct plot_info *pi, pr_track_t **track /* there may be multiple segments - so * let's assemble the length */ nlist = list; - pt = list->pressure_time; - while (!nlist->end) { - nlist = nlist->next; - if (!nlist) { - /* oops - we have no end pressure, - * so this means this is a tank without - * gas consumption information */ - break; + if (list) { + pt = list->pressure_time; + while (!nlist->end) { + nlist = nlist->next; + if (!nlist) { + /* oops - we have no end pressure, + * so this means this is a tank without + * gas consumption information */ + break; + } + pt += nlist->pressure_time; } - pt += nlist->pressure_time; } if (!nlist) { /* just continue without calculating From 76fc14f1b4c62506a6a9a8ee27d21ec992a65993 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 18 Aug 2012 09:48:15 -0700 Subject: [PATCH 54/67] Fix uninitialized pointer crash for "Save As" The "filename" variable was only initialized when the user accepted the name, so cancelling the file save would randomly use an uninitialized pointer. Reported-by: Miika Turkia Signed-off-by: Linus Torvalds --- gtk-gui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtk-gui.c b/gtk-gui.c index 5bc46d219..18c19c79f 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -173,7 +173,7 @@ static void file_open(GtkWidget *w, gpointer data) static void file_save_as(GtkWidget *w, gpointer data) { GtkWidget *dialog; - char *filename; + char *filename = NULL; dialog = gtk_file_chooser_dialog_new("Save File As", GTK_WINDOW(main_window), GTK_FILE_CHOOSER_ACTION_SAVE, From 413065dcdccebe3a97813e9c45a654820f98e0e6 Mon Sep 17 00:00:00 2001 From: Miika Turkia Date: Sat, 18 Aug 2012 19:33:40 +0300 Subject: [PATCH 55/67] Add weight and suit support for JDiveLog import Use the suit and weightsystem support of Subsurface when importing divelogs from JDiveLog. (They were previously included in the notes field as support for these fields was missing from Subsurface.) After import the weightsystem is undefined and weight unit is the default of Subsurface. Unfortunately the weight field in JDiveLog is text field and might contain pounds and kilograms mixed in seemingly random order. Thus 2 pounds of weight might be transformed to 2 kg. Signed-off-by: Miika Turkia Signed-off-by: Linus Torvalds --- xslt/jdivelog2subsurface.xslt | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/xslt/jdivelog2subsurface.xslt b/xslt/jdivelog2subsurface.xslt index e2f40c560..6e85cbfbc 100644 --- a/xslt/jdivelog2subsurface.xslt +++ b/xslt/jdivelog2subsurface.xslt @@ -64,6 +64,21 @@ + + + + + + + + + + + + + + + Diveactivity: @@ -73,15 +88,9 @@ Divetype: Visibility: - - -Suit: Gloves: - - -Weight: Comment: From c5be77093ae0b709ff10509fda20e08f09c0bc25 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 18 Aug 2012 11:41:11 -0700 Subject: [PATCH 56/67] Improve divelist group header information This shows the number of dives in the grup in the divelist header field, and also picks the location from the first dive that *had* a location, so that if any dive in the group has a valid location, the group will have a location. It also makes double-clicking a dive group expand/collapse that group. Requested-by: Miika Turkia Signed-off-by: Linus Torvalds --- divelist.c | 64 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/divelist.c b/divelist.c index 90b7684fa..99f3446ce 100644 --- a/divelist.c +++ b/divelist.c @@ -306,12 +306,12 @@ static void date_data_func(GtkTreeViewColumn *col, GtkTreeIter *iter, gpointer data) { - int val, idx; + int val, idx, nr; struct tm *tm; time_t when; char buffer[40]; - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &val, -1); + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &val, DIVE_NR, &nr, -1); /* 2038 problem */ when = val; @@ -320,10 +320,11 @@ static void date_data_func(GtkTreeViewColumn *col, switch(idx) { case -NEW_TRIP: snprintf(buffer, sizeof(buffer), - "Trip %s, %s %d, %d", + "Trip %s, %s %d, %d (%d dive%s)", weekday(tm->tm_wday), monthname(tm->tm_mon), - tm->tm_mday, tm->tm_year + 1900); + tm->tm_mday, tm->tm_year + 1900, + nr, nr > 1 ? "s" : ""); break; default: snprintf(buffer, sizeof(buffer), @@ -892,12 +893,12 @@ static int new_group(struct dive *dive, struct dive **last_dive, time_t *tm_date static void fill_dive_list(void) { - int i; + int i, group_size; GtkTreeIter iter, parent_iter; GtkTreeStore *liststore, *treestore; struct dive *last_dive = NULL; - struct dive *first_trip_dive = NULL; struct dive *last_trip_dive = NULL; + const char *last_location = NULL; time_t dive_date; treestore = GTK_TREE_STORE(dive_list.treemodel); @@ -910,24 +911,29 @@ static void fill_dive_list(void) if (new_group(dive, &last_dive, &dive_date)) { /* make sure we display the first date of the trip in previous summary */ - if (first_trip_dive && last_trip_dive && last_trip_dive->when < first_trip_dive->when) + if (last_trip_dive) gtk_tree_store_set(treestore, &parent_iter, - DIVE_DATE, last_trip_dive->when, - DIVE_LOCATION, last_trip_dive->location, - -1); - first_trip_dive = dive; + DIVE_NR, group_size, + DIVE_DATE, last_trip_dive->when, + DIVE_LOCATION, last_location, + -1); gtk_tree_store_append(treestore, &parent_iter, NULL); gtk_tree_store_set(treestore, &parent_iter, DIVE_INDEX, -NEW_TRIP, - DIVE_NR, -NEW_TRIP, - DIVE_DATE, dive_date, - DIVE_LOCATION, dive->location, + DIVE_NR, 1, DIVE_TEMPERATURE, 0, DIVE_SAC, 0, -1); + + group_size = 0; + /* This might be NULL */ + last_location = dive->location; } + group_size++; last_trip_dive = dive; + if (dive->location) + last_location = dive->location; update_cylinder_related_info(dive); gtk_tree_store_append(treestore, &iter, &parent_iter); gtk_tree_store_set(treestore, &iter, @@ -957,6 +963,14 @@ static void fill_dive_list(void) -1); } + /* make sure we display the first date of the trip in previous summary */ + if (last_trip_dive) + gtk_tree_store_set(treestore, &parent_iter, + DIVE_NR, group_size, + DIVE_DATE, last_trip_dive->when, + DIVE_LOCATION, last_location, + -1); + update_dive_list_units(); if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) { GtkTreeSelection *selection; @@ -1035,6 +1049,20 @@ 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, @@ -1045,10 +1073,14 @@ static void row_activated_cb(GtkTreeView *tree_view, if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path)) return; + gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &index, -1); /* a negative index is special for the "group by date" entries */ - if (index >= 0) - edit_dive_info(get_dive(index)); + if (index < 0) { + collapse_expand(tree_view, path); + return; + } + edit_dive_info(get_dive(index)); } void add_dive_cb(GtkWidget *menuitem, gpointer data) From bc53bbb10b3aaa22503c2d2b7024d1ac170a5733 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 18 Aug 2012 11:47:29 -0700 Subject: [PATCH 57/67] Select better (?) default date for adding new dive We now pick one hour after the end of the currently selected dive as the default starting time for the new dive to be added. If multiple dives (or no dives) are selected, we default to current time as before. The "one hour after the end" is just a random (but not unreasonable) assumption for the surface time if you add multiple dives. Suggested-by: Miika Turkia Signed-off-by: Linus Torvalds --- info.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/info.c b/info.c index 0616eead9..adc6c5902 100644 --- a/info.c +++ b/info.c @@ -582,9 +582,7 @@ static time_t dive_time_widget(struct dive *dive) GtkWidget *duration, *depth; GtkWidget *label; guint yval, mval, dval; - struct tm tm, *tmp; - struct timeval tv; - time_t time; + struct tm tm, *time; int success; double depthinterval, val; @@ -608,11 +606,27 @@ static time_t dive_time_widget(struct dive *dive) 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); - gettimeofday(&tv, NULL); - time = tv.tv_sec; - tmp = localtime(&time); - gtk_spin_button_set_value(GTK_SPIN_BUTTON(h), tmp->tm_hour); - gtk_spin_button_set_value(GTK_SPIN_BUTTON(m), (tmp->tm_min / 5)*5); + /* + * 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 (amount_selected == 1) { + time_t when = current_dive->when; + when += current_dive->duration.seconds; + when += 60*60; + time = gmtime(&when); + } else { + time_t now; + struct timeval tv; + gettimeofday(&tv, NULL); + now = tv.tv_sec; + time = localtime(&now); + } + 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 / 5)*5); gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(h), TRUE); gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(m), TRUE); From ed6356f7d9a69440c463ed750ab5bcb9f67819bc Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 18 Aug 2012 14:37:43 -0700 Subject: [PATCH 58/67] Make the notebook portion (dive notes/equipment/info) a scrollable window This makes things start up with the wrong size, which is somewhat annoying, but by doing so avoids a bigger annoyance, namely that the three panes move around when moving between dives. In particular, if the initial dive didn't have much of an equipment list, the initial size allocated for the notebook is fairly small and determined mainly by the size of the the Dive Notes page. However, when you then scroll around in the dive list, you might hit a dive with lots of equipment, and suddenly the panes dividing the different parts of the subsurface application window will jump around to make room. That's horribly annoying, and actually makes things like double-clicking dives in the dive list not work right, because the first click will select it, and cause the dive to move around (so the second click will hit a totally different dive). Now, making the notebook be in a scrollable window means that if the size of the notebook changes, it might get a scrollbar, but the panes themselves do not move around. The initial sizing of that thing being wrong is annoying, though. We need to figure out a separate solution to that. [ Side note: currently it uses GTK_POLICY_NEVER for the horizontal scroll-bar, just to avoid the horizontal size also starting out wrong, which is *really* nasty. If we can solve the initial size issue, we should make the horizontal scroll-bar be GTK_POLICY_AUTOMATIC too. ] Signed-off-by: Linus Torvalds --- gtk-gui.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gtk-gui.c b/gtk-gui.c index 18c19c79f..4e5b9edfb 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -755,6 +755,7 @@ void init_ui(int *argcp, char ***argvp) GtkWidget *dive_list; GtkWidget *menubar; GtkWidget *vbox; + GtkWidget *scrolled; GdkScreen *screen; GtkIconTheme *icon_theme=NULL; GtkSettings *settings; @@ -826,13 +827,16 @@ void init_ui(int *argcp, char ***argvp) 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(); - gtk_paned_add1(GTK_PANED(hpane), notebook); + 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 */ From ed1ce8ebc8590533291a9c5d6460f8d1c9f857dd Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sat, 18 Aug 2012 18:06:32 -0700 Subject: [PATCH 59/67] Fix default size for scrollable notebook Linus change in commit bcb9f67819bc ("Make the notebook portion (dive notes/equipment/info) a scrollable window") created a really ugly default where the notebook Dive Notes always ended up with a vertical scrollbar. This picks a much saner default layout for the panes. Signed-off-by: Dirk Hohndel --- gtk-gui.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gtk-gui.c b/gtk-gui.c index 4e5b9edfb..306e1a5e7 100644 --- a/gtk-gui.c +++ b/gtk-gui.c @@ -21,6 +21,8 @@ GtkWidget *main_vbox; GtkWidget *error_info_bar; GtkWidget *error_label; GtkWidget *vpane, *hpane; +GtkWidget *notebook; + int error_count; const char *divelist_font; @@ -662,10 +664,14 @@ static void view_info(GtkWidget *w, gpointer data) static void view_three(GtkWidget *w, gpointer data) { GtkAllocation alloc; + GtkRequisition requisition; + gtk_widget_get_allocation(hpane, &alloc); gtk_paned_set_position(GTK_PANED(hpane), alloc.width/2); gtk_widget_get_allocation(vpane, &alloc); - gtk_paned_set_position(GTK_PANED(vpane), alloc.height/2); + gtk_widget_size_request(notebook, &requisition); + /* pick the requested size for the notebook plus 6 pixels for frame */ + gtk_paned_set_position(GTK_PANED(vpane), requisition.height + 6); } static GtkActionEntry menu_items[] = { @@ -750,7 +756,6 @@ static void switch_page(GtkNotebook *notebook, gint arg1, gpointer user_data) void init_ui(int *argcp, char ***argvp) { GtkWidget *win; - GtkWidget *notebook; GtkWidget *nb_page; GtkWidget *dive_list; GtkWidget *menubar; From d7ea559d8b590beda174d7d8d2d06a257e879f65 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sat, 18 Aug 2012 18:08:54 -0700 Subject: [PATCH 60/67] Change default behavior for Stats to show selected dives Previously when only one dive was selected, the Stats notebook page would show the statistics for all dive. That creates a very illogical behavior when clicking on the different dive groups in the dive list. The stats page would always show how many dives where in a group when the group was selected, except when there was only one dive in the group, in which case the statistics for all the dives were shown. With this change we also show the statistics for the selected dives, even if it is just one. If you want the statistics for all dives, simply select them all (Ctrl-A or Command-A on a Mac). Signed-off-by: Dirk Hohndel --- statistics.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/statistics.c b/statistics.c index 7ff2bfd01..0f6fbe047 100644 --- a/statistics.c +++ b/statistics.c @@ -274,10 +274,7 @@ static void show_total_dive_stats(struct dive *dive) const char *unit; stats_t *stats_ptr; - if (amount_selected < 2) - stats_ptr = &stats; - else - stats_ptr = &stats_selection; + stats_ptr = &stats_selection; set_label(stats_w.selection_size, "%d", stats_ptr->selection_size); if (stats_ptr->min_temp) { From 38f92f780ac3a9345bcb34d297ade0eadbd903ea Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 18 Aug 2012 20:06:04 -0700 Subject: [PATCH 61/67] divelist: add 'Expand all' and 'Collapse all' menu items This adds the ability to expand/collapse all the dive groupings in the divelist from the divelist right-click context menu. Should we perhaps add it to the top 'Dive' menu too? Signed-off-by: Linus Torvalds --- divelist.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/divelist.c b/divelist.c index 99f3446ce..0cb03f326 100644 --- a/divelist.c +++ b/divelist.c @@ -1101,6 +1101,16 @@ void edit_dive_cb(GtkWidget *menuitem, gpointer data) edit_multi_dive_info(amount_selected, selectiontracker); } +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); +} + static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button) { GtkWidget *menu, *menuitem, *image; @@ -1119,6 +1129,12 @@ static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_cb), model); 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, From 972669d6363c163ed6d3b737cbd6b1bd534f3d7b Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 20 Aug 2012 05:48:07 -0700 Subject: [PATCH 62/67] Rework dive selection logic This completely changes how we keep track of selected dives: instead of having an array listing the selection ("selectiontracker") or trusting the gtk selection information, just save the information about whether a dive is selected in the dive itself. That makes it trivial to keep track of the state of selection across group collapse/expand events, or when changing the tree view model. It also ends up simplifying the code and logic in other ways. HOWEVER, it does currently (re-)introduce an annoying oddity with gtk: if you collapse a dive trip that has individual selections, gtk will forget those selections ("out of sight, out of mind"), and when you do *new* selections, the old hidden ones remain. So there's some games required to make gtk do sane things. We may need to either explicitly drop selections when collapsing trips, or make sure the group entry gets selected when collapsing a group that has selections in it. Or something. There may be other issues introduced by this too. Signed-off-by: Linus Torvalds --- display-gtk.h | 2 +- dive.h | 4 +- divelist.c | 310 +++++++++++++++++++++++--------------------------- info.c | 52 ++++----- profile.c | 1 - statistics.c | 21 ++-- 6 files changed, 176 insertions(+), 214 deletions(-) diff --git a/display-gtk.h b/display-gtk.h index 1f143077e..dd7e2c4a0 100644 --- a/display-gtk.h +++ b/display-gtk.h @@ -77,7 +77,7 @@ extern GtkWidget *dive_list_create(void); unsigned int amount_selected; -extern void process_selected_dives(GList *, int *, GtkTreeModel *); +extern void process_selected_dives(void); typedef void (*data_func_t)(GtkTreeViewColumn *col, GtkCellRenderer *renderer, diff --git a/dive.h b/dive.h index 41f427a2c..cc27ab861 100644 --- a/dive.h +++ b/dive.h @@ -236,6 +236,7 @@ struct event { struct dive { int number; + int selected; time_t when; char *location; char *notes; @@ -284,7 +285,6 @@ struct dive_table { extern struct dive_table dive_table; -extern int *selectiontracker; extern int selected_dive; #define current_dive (get_dive(selected_dive)) @@ -355,7 +355,7 @@ extern void evn_foreach(void (*callback)(const char *, int *, void *), void *dat extern int add_new_dive(struct dive *dive); extern int edit_dive_info(struct dive *dive); -extern int edit_multi_dive_info(int nr, int *indices); +extern int edit_multi_dive_info(int idx); extern void dive_list_update_dives(void); extern void flush_divelist(struct dive *dive); diff --git a/divelist.c b/divelist.c index 0cb03f326..1c475afea 100644 --- a/divelist.c +++ b/divelist.c @@ -78,85 +78,21 @@ static void dump_model(GtkListStore *store) } #endif -static GList *selected_dives; -static int st_size = 0; - -gboolean is_in_st(int idx, int *atpos) -{ - int i; - - for (i = 0; i < amount_selected; i++) - if (selectiontracker[i] == idx) { - if (atpos) - *atpos = i; - return TRUE; - } - return FALSE; -} - #if DEBUG_SELECTION_TRACKING void dump_selection(void) { int i; + struct dive *dive; - printf("currently selected are "); - for (i = 0; i < amount_selected; i++) - printf("%d ", selectiontracker[i]); + printf("currently selected are %d dives:", amount_selected); + for (i = 0; (dive = get_dive(i)) != NULL; i++) { + if (dive->selected) + printf(" %d", i); + } printf("\n"); } #endif -void track_select(int idx) -{ - if (idx < 0) - return; - -#if DEBUG_SELECTION_TRACKING - printf("add %d to selection of %d entries\n", idx, amount_selected); -#endif - if (is_in_st(idx, NULL)) - return; - if (amount_selected >= st_size) { - selectiontracker = realloc(selectiontracker, dive_table.nr * sizeof(int)); - st_size = dive_table.nr; - } - selectiontracker[amount_selected] = idx; - amount_selected++; - if (amount_selected == 1) - selected_dive = idx; -#if DEBUG_SELECTION_TRACKING - printf("increased amount_selected to %d\n", amount_selected); - dump_selection(); -#endif -} - -void track_unselect(int idx) -{ - if (idx < 0) - return; - -#if DEBUG_SELECTION_TRACKING - printf("remove %d from selection of %d entries\n", idx, amount_selected); -#endif - int atpos; - - if (! is_in_st(idx, &atpos)) - return; - memmove(selectiontracker + atpos, - selectiontracker + atpos + 1, - (amount_selected - atpos - 1) * sizeof(int)); - amount_selected--; -#if DEBUG_SELECTION_TRACKING - printf("removed %d at pos %d and decreased amount_selected to %d\n", idx, atpos, amount_selected); - dump_selection(); -#endif -} - -void clear_tracker(void) -{ - amount_selected = 0; -} - /* 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) */ @@ -177,97 +113,137 @@ static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx) } } -/* if we click on a summary dive, we actually want to select / unselect - all the dives "below" it */ -static void select_children(GtkTreeModel *model, GtkTreeSelection * selection, - GtkTreeIter *iter, gboolean was_selected) -{ - int i, nr_children; - gboolean expanded = FALSE; - GtkTreeIter parent; - GtkTreePath *tpath; - - memcpy(&parent, iter, sizeof(parent)); - - tpath = gtk_tree_model_get_path(model, &parent); - expanded = gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath); - nr_children = gtk_tree_model_iter_n_children(model, &parent); - for (i = 0; i < nr_children; i++) { - gtk_tree_model_iter_nth_child(model, iter, &parent, i); - - /* if the parent is expanded, just (un)select the children and we'll - track their selection status in the callback - otherwise just change the selection status directly without - bothering gtk */ - if (expanded) { - if (was_selected) - gtk_tree_selection_unselect_iter(selection, iter); - else - gtk_tree_selection_select_iter(selection, iter); - } else { - int idx; - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); - if (was_selected) - track_unselect(idx); - else - track_select(idx); - } - } -} - /* make sure that if we expand a summary row that is selected, the children show up as selected, too */ void row_expanded_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data) { + GtkTreeIter child; + GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model); GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); - if (gtk_tree_selection_path_is_selected(selection, path)) - select_children(GTK_TREE_MODEL(dive_list.model), selection, iter, FALSE); + 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)); } -/* this is called _before_ the selection is changed, for every single entry; - * we simply have it call down the tree to make sure that summary items toggle - * their children */ +static GList *selection_changed = NULL; + +/* + * This is called _before_ the selection is changed, for every single entry; + * + * We simply create a list of all changed entries, and make sure that the + * group entries go at the end of the list. + */ gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean was_selected, gpointer userdata) { - GtkTreeIter iter; - int dive_idx; + GtkTreeIter iter, *p; - /* if gtk thinks nothing is selected we should clear out our - tracker as well - otherwise hidden selected rows can stay - "stuck". The down side is that we now have a different bug: - If you select a dive, collapse the dive trip and ctrl-click - another dive trip, the initial dive is no longer selected. - Just don't do that, ok? */ - if (gtk_tree_selection_count_selected_rows(selection) == 0) - clear_tracker(); + if (!gtk_tree_model_get_iter(model, &iter, path)) + return TRUE; - if (gtk_tree_model_get_iter(model, &iter, path)) { - gtk_tree_model_get(model, &iter, DIVE_INDEX, &dive_idx, -1); - /* turns out we need to move the selectiontracker here */ - -#if DEBUG_SELECTION_TRACKING - printf("modify_selection_cb with idx %d (according to gtk was %sselected)\n", - dive_idx, was_selected ? "" : "un"); -#endif - if (dive_idx >= 0) { - if (was_selected) - track_unselect(dive_idx); - else - track_select(dive_idx); - } else { - select_children(model, selection, &iter, was_selected); - } - } - /* allow this selection to proceed */ + /* Add the group entries to the end */ + p = gtk_tree_iter_copy(&iter); + if (gtk_tree_model_iter_has_child(model, p)) + selection_changed = g_list_append(selection_changed, p); + else + selection_changed = g_list_prepend(selection_changed, p); return TRUE; } -/* this is called when gtk thinks that the selection has changed */ -static void selection_cb(GtkTreeSelection *selection, gpointer userdata) +static void select_dive(struct dive *dive, int selected) { - process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model)); + if (dive->selected != selected) { + amount_selected += selected ? 1 : -1; + dive->selected = selected; + } +} + +/* + * This gets called when a dive group has changed selection. + */ +static void select_dive_group(GtkTreeModel *model, GtkTreeSelection *selection, GtkTreeIter *iter, int selected) +{ + int first = 1; + GtkTreeIter child; + + 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); + if (first && selected) + selected_dive = idx; + first = 0; + dive = get_dive(idx); + if (dive->selected == selected) + break; + + select_dive(dive, selected); + if (selected) + gtk_tree_selection_select_iter(selection, &child); + else + gtk_tree_selection_unselect_iter(selection, &child); + } while (gtk_tree_model_iter_next(model, &child)); +} + +/* + * This gets called _after_ the selections have changed, for each entry that + * may have changed. Check if the gtk selection state matches our internal + * selection state to verify. + * + * The group entries are at the end, this guarantees that we have handled + * all the dives before we handle groups. + */ +static void check_selection_cb(GtkTreeIter *iter, GtkTreeSelection *selection) +{ + GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model); + struct dive *dive; + int idx, gtk_selected; + + gtk_tree_model_get(model, iter, + DIVE_INDEX, &idx, + -1); + dive = get_dive(idx); + gtk_selected = gtk_tree_selection_iter_is_selected(selection, iter); + if (idx < 0) + select_dive_group(model, selection, iter, gtk_selected); + else { + select_dive(dive, gtk_selected); + if (gtk_selected) + selected_dive = idx; + } + gtk_tree_iter_free(iter); +} + +/* this is called when gtk thinks that the selection has changed */ +static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model) +{ + GList *changed = selection_changed; + + selection_changed = NULL; + g_list_foreach(changed, (GFunc) check_selection_cb, selection); + g_list_free(changed); +#if DEBUG_SELECTION_TRACKING + dump_selection(); +#endif + + process_selected_dives(); repaint_dive(); } @@ -1098,7 +1074,7 @@ void add_dive_cb(GtkWidget *menuitem, gpointer data) void edit_dive_cb(GtkWidget *menuitem, gpointer data) { - edit_multi_dive_info(amount_selected, selectiontracker); + edit_multi_dive_info(-1); } static void expand_all_cb(GtkWidget *menuitem, GtkTreeView *tree_view) @@ -1161,8 +1137,6 @@ static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpoi 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 int *oldselection; -static int old_nr_selected; static gboolean second_call = FALSE; static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, }; static int lastcol = DIVE_DATE; @@ -1170,20 +1144,27 @@ static int lastcol = DIVE_DATE; /* 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 select_selected(GtkTreeModel *model, GtkTreePath *path, +static gboolean set_selected(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { - int i, idx; GtkTreeSelection *selection = GTK_TREE_SELECTION(data); + int idx, selected; + struct dive *dive; - gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); - for (i = 0; i < old_nr_selected; i++) - if (oldselection[i] == idx) { - gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path); - gtk_tree_selection_select_path(selection, path); - - return FALSE; - } + gtk_tree_model_get(model, iter, + DIVE_INDEX, &idx, + -1); + if (idx < 0) { + GtkTreeIter child; + if (gtk_tree_model_iter_children(model, &child, iter)) + gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1); + } + 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; } @@ -1236,20 +1217,9 @@ static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data) if (dive_list.model != currentmodel) { GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); - /* remember what is currently selected, switch models and reselect the selected rows */ - old_nr_selected = amount_selected; - oldselection = malloc(old_nr_selected * sizeof(int)); - if (amount_selected) - memcpy(oldselection, selectiontracker, amount_selected * sizeof(int)); gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model)); - update_column_and_order(colid); - - if (old_nr_selected) { - /* we need to select all the dives that were selected */ - /* this is fundamentally an n^2 algorithm as implemented - YUCK */ - gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), select_selected, selection); - } + gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), set_selected, selection); } else { if (order != sortorder[colid]) { update_column_and_order(colid); @@ -1328,7 +1298,7 @@ GtkWidget *dive_list_create(void) g_signal_connect(dive_list.tree_view, "row-expanded", G_CALLBACK(row_expanded_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), 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); diff --git a/info.c b/info.c index adc6c5902..468f35772 100644 --- a/info.c +++ b/info.c @@ -166,7 +166,7 @@ static int delete_dive_info(struct dive *dive) static void info_menu_edit_cb(GtkMenuItem *menuitem, gpointer user_data) { - edit_multi_dive_info(amount_selected, selectiontracker); + edit_multi_dive_info(-1); } static void info_menu_delete_cb(GtkMenuItem *menuitem, gpointer user_data) @@ -489,15 +489,14 @@ void update_equipment_data(struct dive *dive, struct dive *master) memcpy(dive->weightsystem, master->weightsystem, WS_BYTES); } -int edit_multi_dive_info(int nr, int *indices) +/* A negative index means "all selected" */ +int edit_multi_dive_info(int index) { - int success, i; + int success; GtkWidget *dialog, *vbox; struct dive_info info; struct dive *master; - if (!nr) - return 0; dialog = gtk_dialog_new_with_buttons("Dive Info", GTK_WINDOW(main_window), GTK_DIALOG_DESTROY_WITH_PARENT, @@ -506,34 +505,31 @@ int edit_multi_dive_info(int nr, int *indices) NULL); vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - /* SCARY STUFF - IS THIS THE BEST WAY TO DO THIS??? - * - * current_dive is one of our selected dives - and that is - * the one that is used to pre-fill the edit widget. Its - * data is used as the starting point for all selected dives - * I think it would be better to somehow collect and combine - * info from all the selected dives */ - master = current_dive; - dive_info_widget(vbox, master, &info, (nr > 1)); + master = get_dive(index); + if (!master) + master = current_dive; + dive_info_widget(vbox, master, &info, index < 0); show_dive_equipment(master, W_IDX_SECONDARY); save_equipment_data(master); gtk_widget_show_all(dialog); success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; if (success) { - /* Update the other non-current dives first */ - for (i = 0; i < nr; i++) { - int idx = indices[i]; - struct dive *dive = get_dive(idx); + /* Update the non-current selected dives first */ + if (index < 0) { + int i; + struct dive *dive; - if (!dive || dive == master) - continue; - /* copy all "info" fields */ - save_dive_info_changes(dive, master, &info); - /* copy the cylinders / weightsystems */ - update_equipment_data(dive, master); - /* this is extremely inefficient... it loops through all - dives to find the right one - but we KNOW the index already */ - flush_divelist(dive); + for (i = 0; (dive = get_dive(i)) != NULL; i++) { + if (dive == master || !dive->selected) + continue; + /* copy all "info" fields */ + save_dive_info_changes(dive, master, &info); + /* copy the cylinders / weightsystems */ + update_equipment_data(dive, master); + /* this is extremely inefficient... it loops through all + dives to find the right one - but we KNOW the index already */ + flush_divelist(dive); + } } /* Update the master dive last! */ @@ -553,7 +549,7 @@ int edit_dive_info(struct dive *dive) if (!dive) return 0; idx = dive->number; - return edit_multi_dive_info(1, &idx); + return edit_multi_dive_info(idx); } static GtkWidget *frame_box(GtkWidget *vbox, const char *fmt, ...) diff --git a/profile.c b/profile.c index d3362432e..9618c4680 100644 --- a/profile.c +++ b/profile.c @@ -14,7 +14,6 @@ #include "color.h" int selected_dive = 0; -int *selectiontracker; typedef enum { STABLE, SLOW, MODERATE, FAST, CRAZY } velocity_t; diff --git a/statistics.c b/statistics.c index 0f6fbe047..0a23f9022 100644 --- a/statistics.c +++ b/statistics.c @@ -143,24 +143,21 @@ static void process_all_dives(struct dive *dive, struct dive **prev_dive) } /* make sure we skip the selected summary entries */ -void process_selected_dives(GList *selected_dives, int *selectiontracker, GtkTreeModel *model) +void process_selected_dives(void) { - struct dive *dp; - unsigned int i; - int idx; + struct dive *dive; + unsigned int i, nr; memset(&stats_selection, 0, sizeof(stats_selection)); - for (i = 0; i < amount_selected; ++i) { - idx = selectiontracker[i]; - if (idx > 0) { - dp = get_dive(idx); - if (dp) { - process_dive(dp, &stats_selection); - } + nr = 0; + for (i = 0; (dive = get_dive(i)) != NULL; ++i) { + if (dive->selected) { + process_dive(dive, &stats_selection); + nr++; } } - stats_selection.selection_size = amount_selected; + stats_selection.selection_size = nr; } static void set_label(GtkWidget *w, const char *fmt, ...) From 5322b4aac11d2a41247a5a86161c1eb7b7983bc2 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 20 Aug 2012 06:27:04 -0700 Subject: [PATCH 63/67] Avoid changing selection status when collapsing/expanding groups This tries to avoid the problem mentioned in commit972669d6363c ("Rework dive selection logic"), where a selection of dives hidden by collapsing the group gets forgotten about by gtk. It does so by always marking the group selected when it is collapsed with any selected children. We also avoid selecting new children when a group is selected that already has at least *some* children selected already. This way we do minimal damage to existing selections when working with dive group selections. Signed-off-by: Linus Torvalds --- divelist.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/divelist.c b/divelist.c index 1c475afea..13db0a5fd 100644 --- a/divelist.c +++ b/divelist.c @@ -138,6 +138,37 @@ void row_expanded_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *pat } while (gtk_tree_model_iter_next(model, &child)); } +static int selected_children(GtkTreeModel *model, GtkTreeIter *iter) +{ + GtkTreeIter child; + + if (!gtk_tree_model_iter_children(model, &child, iter)) + return FALSE; + + do { + int idx; + struct dive *dive; + + gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1); + dive = get_dive(idx); + + if (dive->selected) + return TRUE; + } while (gtk_tree_model_iter_next(model, &child)); + return FALSE; +} + +/* Make sure that if we collapse a summary row with any selected children, the row + shows up as selected too */ +void row_collapsed_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data) +{ + GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model); + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + + if (selected_children(model, iter)) + gtk_tree_selection_select_iter(selection, iter); +} + static GList *selection_changed = NULL; /* @@ -179,6 +210,9 @@ static void select_dive_group(GtkTreeModel *model, GtkTreeSelection *selection, int first = 1; GtkTreeIter child; + if (selected == selected_children(model, iter)) + return; + if (!gtk_tree_model_iter_children(model, &child, iter)) return; @@ -1296,6 +1330,7 @@ GtkWidget *dive_list_create(void) 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); From 5726a50d89d1f76b6bc2cfb96568d41d27f2b63e Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 20 Aug 2012 06:45:56 -0700 Subject: [PATCH 64/67] Improve group selection semantics Now that the last commit tried to avoid changing the child selections if the selected group partially matched, we should always [un]select all children when we actually decide to change something. Before, it would try to minimize selection damage by stopping [un]selecting when it hit a child that already matched the selection, but since we minimize damage differently, the all-or-nothing approach is better, and gets us sane behavior when the group is collapsed. Signed-off-by: Linus Torvalds --- divelist.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/divelist.c b/divelist.c index 13db0a5fd..ef34b065c 100644 --- a/divelist.c +++ b/divelist.c @@ -226,7 +226,7 @@ static void select_dive_group(GtkTreeModel *model, GtkTreeSelection *selection, first = 0; dive = get_dive(idx); if (dive->selected == selected) - break; + continue; select_dive(dive, selected); if (selected) From 0c49d406e0a7dd1fc66da81ece40405fd053302c Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 20 Aug 2012 12:55:10 -0700 Subject: [PATCH 65/67] Add a "Dive details" widget to the print dialog Ok, so the widget doesn't actually *do* anything, but this is where you would add dive printing settings for things like "print list" vs "print profiles" etc. Printing just a dense dive table (no profiles etc) is being discussed on the list, maybe starting the scaffolding will inspire somebody to do something about it ... Signed-off-by: Linus Torvalds --- print.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/print.c b/print.c index bdebcfe15..0bd3110a7 100644 --- a/print.c +++ b/print.c @@ -196,6 +196,22 @@ static void begin_print(GtkPrintOperation *operation, gpointer user_data) { } +static GtkWidget *print_dialog(GtkPrintOperation *operation, gpointer user_data) +{ + GtkWidget *vbox, *hbox, *label; + gtk_print_operation_set_custom_tab_label(operation, "Dive details"); + + vbox = gtk_vbox_new(TRUE, 5); + label = gtk_label_new("Print Dive details"); + gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0); + gtk_widget_show_all(vbox); + return vbox; +} + +static void print_dialog_apply(GtkPrintOperation *operation, GtkWidget *widget, gpointer user_data) +{ +} + static GtkPrintSettings *settings = NULL; void do_print(void) @@ -210,6 +226,8 @@ void do_print(void) gtk_print_operation_set_print_settings(print, settings); pages = (dive_table.nr + 5) / 6; gtk_print_operation_set_n_pages(print, pages); + g_signal_connect(print, "create-custom-widget", G_CALLBACK(print_dialog), NULL); + g_signal_connect(print, "custom-widget-apply", G_CALLBACK(print_dialog_apply), NULL); g_signal_connect(print, "begin_print", G_CALLBACK(begin_print), NULL); g_signal_connect(print, "draw_page", G_CALLBACK(draw_page), NULL); res = gtk_print_operation_run(print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, From e46688d694d33f445ecb2368541898613af0f3b3 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 21 Aug 2012 15:37:38 -0700 Subject: [PATCH 66/67] Fix single-dive editing oddity The multi-dive case does fine, but the single-dive case (used when adding a dive, for example) was somewhat confused between the dive index (which is the location in the dive array) and the dive number. Fix this by just passing the dive pointer instead (where NULL means to use the current dive selection). Reported-by: Jacco van Koll Root-caused-by: Dirk Hohndel Signed-off-by: Linus Torvalds --- dive.h | 2 +- divelist.c | 2 +- info.c | 15 ++++++--------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/dive.h b/dive.h index cc27ab861..7ceab643a 100644 --- a/dive.h +++ b/dive.h @@ -355,7 +355,7 @@ extern void evn_foreach(void (*callback)(const char *, int *, void *), void *dat extern int add_new_dive(struct dive *dive); extern int edit_dive_info(struct dive *dive); -extern int edit_multi_dive_info(int idx); +extern int edit_multi_dive_info(struct dive *single_dive); extern void dive_list_update_dives(void); extern void flush_divelist(struct dive *dive); diff --git a/divelist.c b/divelist.c index ef34b065c..3fd53ffa5 100644 --- a/divelist.c +++ b/divelist.c @@ -1108,7 +1108,7 @@ void add_dive_cb(GtkWidget *menuitem, gpointer data) void edit_dive_cb(GtkWidget *menuitem, gpointer data) { - edit_multi_dive_info(-1); + edit_multi_dive_info(NULL); } static void expand_all_cb(GtkWidget *menuitem, GtkTreeView *tree_view) diff --git a/info.c b/info.c index 468f35772..242e4b24d 100644 --- a/info.c +++ b/info.c @@ -166,7 +166,7 @@ static int delete_dive_info(struct dive *dive) static void info_menu_edit_cb(GtkMenuItem *menuitem, gpointer user_data) { - edit_multi_dive_info(-1); + edit_multi_dive_info(NULL); } static void info_menu_delete_cb(GtkMenuItem *menuitem, gpointer user_data) @@ -490,7 +490,7 @@ void update_equipment_data(struct dive *dive, struct dive *master) } /* A negative index means "all selected" */ -int edit_multi_dive_info(int index) +int edit_multi_dive_info(struct dive *single_dive) { int success; GtkWidget *dialog, *vbox; @@ -505,17 +505,17 @@ int edit_multi_dive_info(int index) NULL); vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); - master = get_dive(index); + master = single_dive; if (!master) master = current_dive; - dive_info_widget(vbox, master, &info, index < 0); + dive_info_widget(vbox, master, &info, !single_dive); show_dive_equipment(master, W_IDX_SECONDARY); save_equipment_data(master); gtk_widget_show_all(dialog); success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; if (success) { /* Update the non-current selected dives first */ - if (index < 0) { + if (!single_dive) { int i; struct dive *dive; @@ -544,12 +544,9 @@ int edit_multi_dive_info(int index) int edit_dive_info(struct dive *dive) { - int idx; - if (!dive) return 0; - idx = dive->number; - return edit_multi_dive_info(idx); + return edit_multi_dive_info(dive); } static GtkWidget *frame_box(GtkWidget *vbox, const char *fmt, ...) From 666538ec7739fe839623bd1b6f9f80ff884ad5a9 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 21 Aug 2012 15:51:34 -0700 Subject: [PATCH 67/67] Add helper 'for_each_dive()' dive iterator It's an easy thing to do, but the for-loop ends up being pretty ugly, so hide it behind the macro. It would be even prettier with one of the (few) useful C99 features: local for-loop variables. However, gcc needs special command line options, and other compilers may not do it at all. So instead of doing #define for_each_dive(_x) \ for (int _i = 0; ((_x) = get_dive(_i)) != NULL; _i++) we require that the user declare the index iterator too, and the use syntax becomes for_each_dive(idx, dive) { ... use idx/dive here ... } And hey, maybe somebody actually will want to use the index, so maybe that's not all bad. Signed-off-by: Linus Torvalds --- dive.h | 10 ++++++++++ divelist.c | 2 +- info.c | 2 +- statistics.c | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/dive.h b/dive.h index 7ceab643a..ab854e37e 100644 --- a/dive.h +++ b/dive.h @@ -295,6 +295,16 @@ static inline struct dive *get_dive(unsigned int nr) return dive_table.dives[nr]; } +/* + * Iterate over each dive, with the first parameter being the index + * iterator variable, and the second one being the dive one. + * + * I don't think anybody really wants the index, and we could make + * it local to the for-loop, but that would make us requires C99. + */ +#define for_each_dive(_i,_x) \ + for ((_i) = 0; ((_x) = get_dive(_i)) != NULL; (_i)++) + extern void parse_xml_init(void); extern void parse_xml_buffer(const char *url, const char *buf, int size, GError **error); extern void set_filename(const char *filename); diff --git a/divelist.c b/divelist.c index 3fd53ffa5..30bd2d8e9 100644 --- a/divelist.c +++ b/divelist.c @@ -85,7 +85,7 @@ void dump_selection(void) struct dive *dive; printf("currently selected are %d dives:", amount_selected); - for (i = 0; (dive = get_dive(i)) != NULL; i++) { + for_each_dive(i, dive) { if (dive->selected) printf(" %d", i); } diff --git a/info.c b/info.c index 242e4b24d..8db606344 100644 --- a/info.c +++ b/info.c @@ -519,7 +519,7 @@ int edit_multi_dive_info(struct dive *single_dive) int i; struct dive *dive; - for (i = 0; (dive = get_dive(i)) != NULL; i++) { + for_each_dive(i, dive) { if (dive == master || !dive->selected) continue; /* copy all "info" fields */ diff --git a/statistics.c b/statistics.c index 0a23f9022..b9d2c3b95 100644 --- a/statistics.c +++ b/statistics.c @@ -151,7 +151,7 @@ void process_selected_dives(void) memset(&stats_selection, 0, sizeof(stats_selection)); nr = 0; - for (i = 0; (dive = get_dive(i)) != NULL; ++i) { + for_each_dive(i, dive) { if (dive->selected) { process_dive(dive, &stats_selection); nr++;