From: Linus Torvalds Date: Fri, 17 Aug 2012 17:57:24 +0000 (-0700) Subject: Merge branch 'misc-fixes' of git://github.com/DataBeaver/subsurface X-Git-Url: http://git.tdb.fi/?p=ext%2Fsubsurface.git;a=commitdiff_plain;h=d0e27c6c533005b5fd161286f68f1325bdabe8e3;hp=-c Merge branch 'misc-fixes' of git://github.com/DataBeaver/subsurface Pull miscellaneous fixes, mostly UI stuff from Mikko Rasa. Both this and the pull from Pierre-Yves Chibon created a "Save As" menu entry and logic. As a result, there were a fair number of conflicts, but I tried to make the end result somewhat reasonable. I might have missed some semantic conflict, though. Series-acked-by: Henrik Brautaset Aronsen * 'misc-fixes' of git://github.com/DataBeaver/subsurface: Add a separate "Save as" entry to the menu Changes to menu icons Improved depth info for dives without samples Divide the panes evenly in view_three --- d0e27c6c533005b5fd161286f68f1325bdabe8e3 diff --combined dive.c index f5d0828,36ee8e7..f4bf497 --- a/dive.c +++ b/dive.c @@@ -120,28 -120,6 +120,28 @@@ double get_depth_units(unsigned int mm 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; @@@ -473,7 -451,12 +473,12 @@@ struct dive *fixup_dive(struct dive *di } } 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) @@@ -486,7 -469,6 +491,7 @@@ 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); @@@ -495,10 -477,6 +500,10 @@@ 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; } @@@ -704,7 -682,6 +709,7 @@@ struct dive *try_to_merge(struct dive * 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 --combined divelist.c index 0e1b40d,27d2424..5d69152 --- a/divelist.c +++ b/divelist.c @@@ -24,9 -24,9 +24,9 @@@ struct DiveList { GtkWidget *tree_view; GtkWidget *container_widget; - GtkListStore *model; + GtkTreeStore *model, *listmodel, *treemodel; GtkTreeViewColumn *nr, *date, *stars, *depth, *duration, *location; - GtkTreeViewColumn *temperature, *cylinder, *nitrox, *sac, *otu; + GtkTreeViewColumn *temperature, *cylinder, *totalweight, *suit, *nitrox, *sac, *otu; int changed; }; @@@ -44,8 -44,6 +44,8 @@@ enum DIVE_DEPTH, /* int: dive->maxdepth in mm */ DIVE_DURATION, /* int: in seconds */ DIVE_TEMPERATURE, /* int: in mkelvin */ + DIVE_TOTALWEIGHT, /* int: in grams */ + DIVE_SUIT, /* "wet, 3mm" */ DIVE_CYLINDER, DIVE_NITROX, /* int: dummy */ DIVE_SAC, /* int: in ml/min */ @@@ -54,199 -52,48 +54,199 @@@ DIVELIST_COLUMNS }; +/* magic numbers that indicate (as negative values) model entries that + * are summary entries for a divetrip */ +#define NEW_TRIP 1 + +#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 int st_size = 0; -static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model) +gboolean is_in_st(int idx, int *atpos) { - GtkTreeIter iter; - GValue value = {0, }; - GtkTreePath *path; + int i; - int nr_selected = gtk_tree_selection_count_selected_rows(selection); + for (i = 0; i < amount_selected; i++) + if (selectiontracker[i] == idx) { + if (atpos) + *atpos = i; + return TRUE; + } + return FALSE; +} - 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); +#if DEBUG_SELECTION_TRACKING +void dump_selection(void) +{ + int i; - switch (nr_selected) { - case 0: /* keep showing the last selected dive */ + 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; - 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(model, &iter, path)) { - gtk_tree_model_get_value(model, &iter, DIVE_INDEX, &value); - selected_dive = g_value_get_int(&value); - repaint_dive(); - } + +#if DEBUG_SELECTION_TRACKING + printf("add %d to selection of %d entries\n", idx, amount_selected); +#endif + if (is_in_st(idx, NULL)) 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 - * is the most intuitive solution. - * 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); - repaint_dive(); + 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 + DIVE_INDEX) */ +static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx) +{ + GtkTreeIter parent; + GtkTreePath *tpath; + + while (*diveidx < 0) { + memcpy(&parent, iter, sizeof(parent)); + tpath = gtk_tree_model_get_path(model, &parent); + if (!gtk_tree_model_iter_children(model, iter, &parent)) + /* we should never have a parent without child */ + return; + if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath)) + gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE); + gtk_tree_model_get(GTK_TREE_MODEL(model), iter, DIVE_INDEX, diveidx, -1); + track_select(*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 unexpand = 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); + } + 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 (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; + * 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); + /* 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); + } + } + /* 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) +{ + process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model)); + repaint_dive(); +} + const char *star_strings[] = { ZERO_STARS, ONE_STARS, @@@ -262,17 -109,13 +262,17 @@@ static void star_data_func(GtkTreeViewC 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 < 0) { + *buffer = '\0'; + } else { + if (nr_stars < 0 || nr_stars > 5) + nr_stars = 0; + snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]); + } g_object_set(renderer, "text", buffer, NULL); } @@@ -282,33 -125,23 +282,33 @@@ static void date_data_func(GtkTreeViewC 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); + switch(idx) { + case -NEW_TRIP: + snprintf(buffer, sizeof(buffer), + "Trip %s, %s %d, %d", + weekday(tm->tm_wday), + monthname(tm->tm_mon), + tm->tm_mday, 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); } @@@ -318,37 -151,34 +318,37 @@@ static void depth_data_func(GtkTreeView 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); - - switch (output_units.length) { - case METERS: - /* To tenths of meters */ - depth = (depth + 49) / 100; - integer = depth / 10; - frac = depth % 10; - if (integer < 20) + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1); + + if (idx < 0) { + *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); } @@@ -359,14 -189,10 +359,14 @@@ static void duration_data_func(GtkTreeV gpointer data) { unsigned int sec; + int 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 < 0) + *buffer = '\0'; + else + snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60); g_object_set(renderer, "text", buffer, NULL); } @@@ -377,13 -203,13 +377,13 @@@ static void temperature_data_func(GtkTr 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 >= 0 && value) { double deg; switch (output_units.temperature) { case CELSIUS: @@@ -401,23 -227,6 +401,23 @@@ 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 < 0) + *buffer = '\0'; + else + snprintf(buffer, sizeof(buffer), "%d", nr); + g_object_set(renderer, "text", buffer, NULL); +} + /* * Get "maximal" dive gas for a dive. * Rules: @@@ -460,38 -269,6 +460,38 @@@ 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); + if (value == 0.0) + *buffer = '\0'; + else + snprintf(buffer, sizeof(buffer), "%.*f", decimals, value); + + g_object_set(renderer, "text", buffer, NULL); +} + static gint nitrox_sort_func(GtkTreeModel *model, GtkTreeIter *iter_a, GtkTreeIter *iter_b, @@@ -527,16 -304,12 +527,16 @@@ static void nitrox_data_func(GtkTreeVie 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); - dive = get_dive(index); + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + if (idx < 0) { + *buffer = '\0'; + goto exit; + } + dive = get_dive(idx); get_dive_gas(dive, &o2, &he, &o2low); o2 = (o2 + 5) / 10; he = (he + 5) / 10; @@@ -551,7 -324,7 +551,7 @@@ snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2); else strcpy(buffer, "air"); - +exit: g_object_set(renderer, "text", buffer, NULL); } @@@ -562,16 -335,16 +562,16 @@@ static void sac_data_func(GtkTreeViewCo 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 < 0 || !value) { + *buffer = '\0'; + goto exit; } sac = value / 1000.0; @@@ -585,7 -358,7 +585,7 @@@ break; } snprintf(buffer, sizeof(buffer), fmt, sac); - +exit: g_object_set(renderer, "text", buffer, NULL); } @@@ -596,15 -369,17 +596,15 @@@ static void otu_data_func(GtkTreeViewCo GtkTreeIter *iter, gpointer data) { - int value; + int value, idx; char buffer[16]; - gtk_tree_model_get(model, iter, DIVE_OTU, &value, -1); - - if (!value) { - g_object_set(renderer, "text", "", NULL); - return; - } + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1); - snprintf(buffer, sizeof(buffer), "%d", value); + if (idx < 0 || !value) + *buffer = '\0'; + else + snprintf(buffer, sizeof(buffer), "%d", value); g_object_set(renderer, "text", buffer, NULL); } @@@ -726,55 -501,27 +726,55 @@@ static void get_cylinder(struct dive *d 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 + * 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; + char *location, *cylinder, *suit; + GtkTreeStore *othermodel; get_cylinder(dive, &cylinder); get_location(dive, &location); + get_suit(dive, &suit); - 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, DIVE_RATING, dive->rating, DIVE_SAC, dive->sac, DIVE_OTU, dive->otu, + DIVE_TOTALWEIGHT, total_weight(dive), + DIVE_SUIT, suit, -1); + + free(location); + free(cylinder); + free(suit); + + 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, @@@ -782,14 -529,12 +782,14 @@@ 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 < 0) + return FALSE; + dive = get_dive(idx); if (!dive) return TRUE; if (data && dive != data) @@@ -824,9 -569,6 +824,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); } @@@ -834,112 -576,41 +834,112 @@@ void update_dive_list_col_visibility(vo { 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); return; } +/* 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; + if (abs(dive->when - ldive->when) < TRIP_THRESHOLD) { + *last_dive = dive; + return FALSE; + } + } + *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; - GtkListStore *store; + 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; - store = GTK_LIST_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) { struct dive *dive = dive_table.dives[i]; + 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_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, + gtk_tree_store_append(treestore, &iter, &parent_iter); + 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, "location", + 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, + DIVE_DEPTH, dive->maxdepth, + DIVE_DURATION, dive->duration.seconds, + DIVE_LOCATION, dive->location, + DIVE_RATING, dive->rating, + DIVE_TEMPERATURE, dive->watertemp.mkelvin, + DIVE_TOTALWEIGHT, 0, + DIVE_SUIT, dive->suit, + DIVE_SAC, 0, + -1); } update_dive_list_units(); if (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); + 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); } @@@ -947,8 -618,7 +947,8 @@@ 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.treemodel)); + gtk_tree_store_clear(GTK_TREE_STORE(dive_list.listmodel)); fill_dive_list(); repaint_dive(); } @@@ -960,14 -630,12 +960,14 @@@ 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 }, [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 }, @@@ -985,20 -653,14 +985,20 @@@ static GtkTreeViewColumn *divelist_colu unsigned int flags = col->flags; int *visible = col->visible; GtkWidget *tree_view = dl->tree_view; - GtkListStore *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; } @@@ -1014,17 -676,15 +1014,17 @@@ static void realize_cb(GtkWidget *tree_ 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); - edit_dive_info(get_dive(index)); + 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)); } void add_dive_cb(GtkWidget *menuitem, gpointer data) @@@ -1040,170 -700,43 +1040,172 @@@ 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; + GtkWidget *menu, *menuitem, *image; + char editlabel[] = "Edit dives"; 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); + 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, 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; } +/* we need to have a temporary copy of the selected dives while + switching model as the selection_cb function keeps getting called + when gtk_tree_selection_select_path is called. We also need to + keep copies of the sort order so we can restore that as well after + switching models. */ +static 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. + * 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; + +} + +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. + + 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) { + 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); + } + } else { + if (order != sortorder[colid]) { + update_column_and_order(colid); + } + } +} + GtkWidget *dive_list_create(void) { GtkTreeSelection *selection; - dive_list.model = gtk_list_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 */ + G_TYPE_INT, /* Star rating */ + G_TYPE_INT, /* Depth */ + G_TYPE_INT, /* Duration */ + G_TYPE_INT, /* Temperature */ + G_TYPE_INT, /* Total weight */ + G_TYPE_STRING, /* Suit */ + G_TYPE_STRING, /* Cylinder */ + G_TYPE_INT, /* Nitrox */ + G_TYPE_INT, /* SAC */ + G_TYPE_INT, /* OTU */ + G_TYPE_STRING /* Location */ + ); + dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS, G_TYPE_INT, /* index */ G_TYPE_INT, /* nr */ G_TYPE_INT, /* Date */ @@@ -1211,15 -744,12 +1213,15 @@@ G_TYPE_INT, /* Depth */ G_TYPE_INT, /* Duration */ G_TYPE_INT, /* Temperature */ + G_TYPE_INT, /* Total weight */ + G_TYPE_STRING, /* Suit */ G_TYPE_STRING, /* Cylinder */ G_TYPE_INT, /* Nitrox */ G_TYPE_INT, /* SAC */ G_TYPE_INT, /* OTU */ G_TYPE_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); @@@ -1234,8 -764,6 +1236,8 @@@ dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH); dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION); dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE); + dive_list.totalweight = divelist_column(&dive_list, dl_column + DIVE_TOTALWEIGHT); + dive_list.suit = divelist_column(&dive_list, dl_column + DIVE_SUIT); dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER); dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX); dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC); @@@ -1250,15 -778,10 +1252,15 @@@ 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, "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(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), diff --combined gtk-gui.c index 02463d9,bef03a0..5bc46d2 --- a/gtk-gui.c +++ b/gtk-gui.c @@@ -170,91 -170,57 +170,75 @@@ static void file_open(GtkWidget *w, gpo 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", + char *filename; - if (!existing_filename) { - dialog = gtk_file_chooser_dialog_new("Save File", ++ 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), "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); - mark_divelist_changed(FALSE); - } - } - - 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); - 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_current_name(GTK_FILE_CHOOSER(dialog), existing_filename); if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - char *filename; filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + } + gtk_widget_destroy(dialog); + + if (filename){ 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); ++ mark_divelist_changed(FALSE); + } + -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)); - 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); - gint *outcode = gtk_dialog_run(GTK_DIALOG(dialog)); - 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) @@@ -262,15 -228,10 +246,15 @@@ /* 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) @@@ -283,13 -244,9 +267,13 @@@ static void quit(GtkWidget *w, gpointe /* 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, @@@ -400,8 -357,6 +384,8 @@@ OPTIONCALLBACK(otu_toggle, visible_cols 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) @@@ -457,47 -412,37 +441,47 @@@ static void preferences_dialog(GtkWidge "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("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("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); @@@ -521,8 -466,6 +505,8 @@@ 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("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)); @@@ -675,26 -618,28 +659,28 @@@ static void view_info(GtkWidget *w, gpo 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[] = { - { "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) }, - { "SaveFileAs", GTK_STOCK_SAVE_AS, NULL, CTRLCHAR "S", NULL, G_CALLBACK(file_save_as) }, + { "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) }, - { "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) }, @@@ -712,7 -657,7 +698,7 @@@ static const gchar* ui_string = " \ \ \ - \ + \ \ \ \ @@@ -794,8 -739,6 +780,8 @@@ 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.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)); @@@ -864,7 -807,7 +850,7 @@@ 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 */ @@@ -1255,7 -1198,9 +1241,9 @@@ void update_progressbar_text(progressba 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; } diff --combined info.c index 0ee4014,e935823..3877648 --- a/info.c +++ b/info.c @@@ -19,9 -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) { @@@ -43,50 -43,16 +43,50 @@@ static int text_changed(const char *old (!old && strcmp("",new)); } -static char *get_combo_box_entry_text(GtkComboBoxEntry *combo_box, char **textp) +static const char *skip_space(const 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, const char *master) { char *old = *textp; + const char *old_text; const gchar *new; GtkEntry *entry; + old_text = 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_text) + if (strcmp(master, old_text)) + return NULL; + entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo_box))); 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); @@@ -130,7 -96,6 +130,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); @@@ -166,18 -131,25 +166,26 @@@ static int delete_dive_info(struct div 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); } - 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); @@@ -185,8 -157,8 +193,8 @@@ 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) @@@ -278,8 -250,6 +286,8 @@@ static gboolean match_string_entry(GtkT 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) @@@ -334,11 -304,6 +342,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; @@@ -351,69 -316,61 +359,69 @@@ } struct dive_info { - GtkComboBoxEntry *location, *divemaster, *buddy, *rating; + GtkComboBoxEntry *location, *divemaster, *buddy, *rating, *suit; 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, 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); 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); @@@ -429,59 -386,29 +437,59 @@@ 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) - 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 *master; - if (!dive) + if (!nr) return 0; - dialog = gtk_dialog_new_with_buttons("Dive Info", GTK_WINDOW(main_window), GTK_DIALOG_DESTROY_WITH_PARENT, @@@ -490,56 -417,18 +498,56 @@@ 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 */ + 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) - save_dive_info_changes(dive, &info); - + 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); + + 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); + } + + /* 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; } +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; @@@ -681,7 -570,6 +689,7 @@@ GtkWidget *extended_dive_info_widget(vo 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"); @@@ -696,7 -584,6 +704,7 @@@ 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 --combined profile.c index 7a0eac4,6148228..90ed0b6 --- a/profile.c +++ b/profile.c @@@ -14,7 -14,6 +14,7 @@@ #include "color.h" int selected_dive = 0; +int *selectiontracker; typedef enum { STABLE, SLOW, MODERATE, FAST, CRAZY } velocity_t; @@@ -1355,12 -1354,15 +1355,15 @@@ void plot(struct graphics_context *gc, 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;