diff options
-rw-r--r-- | dive.h | 25 | ||||
-rw-r--r-- | divelist.c | 178 | ||||
-rw-r--r-- | gtk-gui.c | 20 | ||||
-rw-r--r-- | parse-xml.c | 62 | ||||
-rw-r--r-- | save-xml.c | 28 |
5 files changed, 237 insertions, 76 deletions
@@ -234,8 +234,12 @@ struct event { #define W_IDX_PRIMARY 0 #define W_IDX_SECONDARY 1 +typedef enum { TF_NONE, NO_TRIP, IN_TRIP, NUM_TRIPFLAGS } tripflag_t; +extern const char *tripflag_names[NUM_TRIPFLAGS]; + struct dive { int number; + tripflag_t tripflag; int selected; time_t when; char *location; @@ -256,6 +260,27 @@ struct dive { struct sample sample[]; }; +extern GList *dive_trip_list; +extern gboolean autogroup; +/* random threashold: three days without diving -> new trip + * this works very well for people who usually dive as part of a trip and don't + * regularly dive at a local facility; this is why trips are an optional feature */ +#define TRIP_THRESHOLD 3600*24*3 + +#define UNGROUPED_DIVE(_dive) ((_dive)->tripflag == NO_TRIP) +#define DIVE_IN_TRIP(_dive) ((_dive)->tripflag == IN_TRIP) +#define NEXT_TRIP(_entry, _list) ((_entry) ? g_list_next(_entry) : (_list)) +#define PREV_TRIP(_entry, _list) ((_entry) ? g_list_previous(_entry) : g_list_last(_list)) +#define DIVE_TRIP(_trip) ((struct dive *)(_trip)->data) +#define DIVE_FITS_TRIP(_dive, _dive_trip) ((_dive_trip)->when - TRIP_THRESHOLD <= (_dive)->when) + +static inline int dive_date_cmp(gconstpointer _a, gconstpointer _b) { + return ((struct dive *)(_a))->when - ((struct dive *)(_b))->when; +} + +#define INSERT_TRIP(_trip, _list) g_list_insert_sorted((_list), (_trip), dive_date_cmp) +#define FIND_TRIP(_trip, _list) g_list_find_custom((_list), (_trip), dive_date_cmp) + /* * We keep our internal data in well-specified units, but * the input and output may come in some random format. This diff --git a/divelist.c b/divelist.c index ef34b065c..a3bb1fb5e 100644 --- a/divelist.c +++ b/divelist.c @@ -31,6 +31,10 @@ struct DiveList { }; static struct DiveList dive_list; +GList *dive_trip_list; +gboolean autogroup = FALSE; + +const char *tripflag_names[NUM_TRIPFLAGS] = { "TF_NONE", "NOTRIP", "INTRIP" }; /* * The dive list has the dive data in both string format (for showing) @@ -54,19 +58,22 @@ enum { 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; + int idx, nr, duration; + struct dive *dive; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, DIVE_DURATION, &duration, DIVE_LOCATION, &location, -1); + printf("entry #%d : nr %d duration %d location %s ", idx, nr, duration, location); + dive = get_dive(idx); + if (dive) + printf("tripflag %d\n", dive->tripflag); + else + printf("without matching dive\n"); - 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; @@ -327,16 +334,14 @@ static void date_data_func(GtkTreeViewColumn *col, when = val; tm = gmtime(&when); - switch(idx) { - case -NEW_TRIP: + if (idx < 0) { snprintf(buffer, sizeof(buffer), "Trip %s, %s %d, %d (%d dive%s)", weekday(tm->tm_wday), monthname(tm->tm_mon), tm->tm_mday, tm->tm_year + 1900, nr, nr > 1 ? "s" : ""); - break; - default: + } else { snprintf(buffer, sizeof(buffer), "%s, %s %d, %d %02d:%02d", weekday(tm->tm_wday), @@ -877,75 +882,101 @@ void update_dive_list_col_visibility(void) 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, group_size; - GtkTreeIter iter, parent_iter; + int i; + GtkTreeIter iter, parent_iter, *parent_ptr = NULL; GtkTreeStore *liststore, *treestore; - struct dive *last_dive = NULL; - struct dive *last_trip_dive = NULL; - const char *last_location = NULL; - time_t dive_date; + struct dive *last_trip = NULL; + GList *trip; + struct dive *dive_trip = NULL; + + /* if we have pre-existing trips, start on the last one */ + trip = g_list_last(dive_trip_list); + if (trip) + dive_trip = DIVE_TRIP(trip); 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 (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); - - gtk_tree_store_append(treestore, &parent_iter, NULL); - gtk_tree_store_set(treestore, &parent_iter, - DIVE_INDEX, -NEW_TRIP, - DIVE_NR, 1, - DIVE_TEMPERATURE, 0, - DIVE_SAC, 0, + struct dive *dive = get_dive(i); + + /* make sure we display the first date of the trip in previous summary */ + if (dive_trip && parent_ptr) { + gtk_tree_store_set(treestore, parent_ptr, + DIVE_NR, dive_trip->number, + DIVE_DATE, dive_trip->when, + DIVE_LOCATION, dive_trip->location, -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; + /* tripflag defines how dives are handled; + * TF_NONE "not handled yet" - create time based group if autogroup == TRUE + * NO_TRIP "set as no group" - simply leave at top level + * IN_TRIP "use the trip with the largest trip time (when) that is <= this dive" + */ + if (UNGROUPED_DIVE(dive)) { + /* first dives that go to the top level */ + parent_ptr = NULL; + dive_trip = NULL; + } else if (autogroup && !DIVE_IN_TRIP(dive)) { + if ( ! dive_trip || ! DIVE_FITS_TRIP(dive, dive_trip)) { + /* allocate new trip - all fields default to 0 + and get filled in further down */ + dive_trip = alloc_dive(); + dive_trip_list = INSERT_TRIP(dive_trip, dive_trip_list); + trip = FIND_TRIP(dive_trip, dive_trip_list); + } + } else { /* either the dive has a trip or we aren't creating trips */ + if (! (trip && DIVE_FITS_TRIP(dive, DIVE_TRIP(trip)))) { + GList *last_trip = trip; + trip = PREV_TRIP(trip, dive_trip_list); + if (! (trip && DIVE_FITS_TRIP(dive, DIVE_TRIP(trip)))) { + /* we could get here if there are no trips in the XML file + * and we aren't creating trips, either. + * Otherwise we need to create a new trip */ + if (autogroup) { + dive_trip = alloc_dive(); + dive_trip_list = INSERT_TRIP(dive_trip, dive_trip_list); + trip = FIND_TRIP(dive_trip, dive_trip_list); + } else { + /* let's go back to the last valid trip */ + trip = last_trip; + } + } else { + dive_trip = trip->data; + dive_trip->number = 0; + } + } + } + /* update dive_trip to include this dive, increase number of dives in + the trip and update location if necessary */ + if (dive_trip) { + dive->tripflag = IN_TRIP; + dive_trip->number++; + dive_trip->when = dive->when; + if (!dive_trip->location && dive->location) + dive_trip->location = dive->location; + if (dive_trip != last_trip) { + last_trip = dive_trip; + /* create trip entry */ + gtk_tree_store_append(treestore, &parent_iter, NULL); + parent_ptr = &parent_iter; + /* a duration of 0 (and negative index) identifies a group */ + gtk_tree_store_set(treestore, parent_ptr, + DIVE_INDEX, -1, + DIVE_NR, dive_trip->number, + DIVE_DATE, dive_trip->when, + DIVE_LOCATION, dive_trip->location, + DIVE_DURATION, 0, + -1); + } + } + + /* store dive */ update_cylinder_related_info(dive); - gtk_tree_store_append(treestore, &iter, &parent_iter); + gtk_tree_store_append(treestore, &iter, parent_ptr); gtk_tree_store_set(treestore, &iter, DIVE_INDEX, i, DIVE_NR, dive->number, @@ -974,13 +1005,12 @@ static void fill_dive_list(void) } /* 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, + if (parent_ptr && dive_trip) + gtk_tree_store_set(treestore, parent_ptr, + DIVE_NR, dive_trip->number, + DIVE_DATE, dive_trip->when, + DIVE_LOCATION, dive_trip->location, -1); - update_dive_list_units(); if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) { GtkTreeSelection *selection; @@ -389,6 +389,7 @@ OPTIONCALLBACK(temperature_toggle, visible_cols.temperature) OPTIONCALLBACK(totalweight_toggle, visible_cols.totalweight) OPTIONCALLBACK(suit_toggle, visible_cols.suit) OPTIONCALLBACK(cylinder_toggle, visible_cols.cylinder) +OPTIONCALLBACK(autogroup_toggle, autogroup) static void event_toggle(GtkWidget *w, gpointer _data) { @@ -484,8 +485,22 @@ 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(suit_toggle), NULL); + frame = gtk_frame_new("Divelist Font"); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5); + font = gtk_font_button_new_with_font(divelist_font); - gtk_box_pack_start(GTK_BOX(vbox), font, FALSE, FALSE, 5); + gtk_container_add(GTK_CONTAINER(frame),font); + + frame = gtk_frame_new("Misc. Options"); + 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("Automatically group dives in trips"); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), autogroup); + gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6); + g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(autogroup_toggle), NULL); gtk_widget_show_all(dialog); result = gtk_dialog_run(GTK_DIALOG(dialog)); @@ -514,6 +529,7 @@ static void preferences_dialog(GtkWidget *w, gpointer data) subsurface_set_conf("SAC", PREF_BOOL, BOOL_TO_PTR(visible_cols.sac)); subsurface_set_conf("OTU", PREF_BOOL, BOOL_TO_PTR(visible_cols.otu)); subsurface_set_conf("divelist_font", PREF_STRING, divelist_font); + subsurface_set_conf("autogroup", PREF_BOOL, BOOL_TO_PTR(autogroup)); /* Flush the changes out to the system */ subsurface_flush_conf(); @@ -794,6 +810,8 @@ void init_ui(int *argcp, char ***argvp) divelist_font = subsurface_get_conf("divelist_font", PREF_STRING); + autogroup = PTR_TO_BOOL(subsurface_get_conf("autogroup", PREF_BOOL)); + 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); diff --git a/parse-xml.c b/parse-xml.c index 173314dd4..00538dad9 100644 --- a/parse-xml.c +++ b/parse-xml.c @@ -39,6 +39,11 @@ void record_dive(struct dive *dive) dive_table.nr = nr+1; } +void record_trip(struct dive *trip) +{ + dive_trip_list = INSERT_TRIP(trip, dive_trip_list); +} + static void delete_dive_renumber(struct dive **dives, int i, int nr) { struct dive *dive = dives[i]; @@ -156,7 +161,7 @@ const struct units IMPERIAL_units = { /* * Dive info as it is being built up.. */ -static struct dive *cur_dive; +static struct dive *cur_dive, *cur_trip = NULL; static struct sample *cur_sample; static struct { int active; @@ -535,6 +540,17 @@ static void get_index(char *buffer, void *_i) free(buffer); } +static void get_tripflag(char *buffer, void *_tf) +{ + tripflag_t *tf = _tf; + tripflag_t i; + + *tf = TF_NONE; + for (i = NO_TRIP; i < NUM_TRIPFLAGS; i++) + if(! strcmp(buffer, tripflag_names[i])) + *tf = i; +} + static void centibar(char *buffer, void *_pressure) { pressure_t *pressure = _pressure; @@ -1062,6 +1078,8 @@ static void try_to_fill_dive(struct dive **divep, const char *name, char *buf) if (MATCH(".number", get_index, &dive->number)) return; + if (MATCH(".tripflag", get_tripflag, &dive->tripflag)) + return; if (MATCH(".date", divedate, &dive->when)) return; if (MATCH(".time", divetime, &dive->when)) @@ -1138,6 +1156,27 @@ static void try_to_fill_dive(struct dive **divep, const char *name, char *buf) nonmatch("dive", name, buf); } +/* We're in the top-level trip xml. Try to convert whatever value to a trip value */ +static void try_to_fill_trip(struct dive **divep, const char *name, char *buf) +{ + int len = strlen(name); + + start_match("trip", name, buf); + + struct dive *dive = *divep; + + if (MATCH(".date", divedate, &dive->when)) { + dive->when = utc_mktime(&cur_tm); + return; + } + if (MATCH(".location", utf8_string, &dive->location)) + return; + if (MATCH(".notes", utf8_string, &dive->notes)) + return; + + nonmatch("trip", name, buf); +} + /* * File boundaries are dive boundaries. But sometimes there are * multiple dives per file, so there can be other events too that @@ -1162,6 +1201,22 @@ static void dive_end(void) cur_ws_index = 0; } +static void trip_start(void) +{ + if (cur_trip) + return; + cur_trip = alloc_dive(); + memset(&cur_tm, 0, sizeof(cur_tm)); +} + +static void trip_end(void) +{ + if (!cur_trip) + return; + record_trip(cur_trip); + cur_trip = NULL; +} + static void event_start(void) { memset(&cur_event, 0, sizeof(cur_event)); @@ -1225,6 +1280,10 @@ static void entry(const char *name, int size, const char *raw) try_to_fill_sample(cur_sample, name, buf); return; } + if (cur_trip) { + try_to_fill_trip(&cur_trip, name, buf); + return; + } if (cur_dive) { try_to_fill_dive(&cur_dive, name, buf); return; @@ -1350,6 +1409,7 @@ static struct nesting { } nesting[] = { { "dive", dive_start, dive_end }, { "Dive", dive_start, dive_end }, + { "trip", trip_start, trip_end }, { "sample", sample_start, sample_end }, { "waypoint", sample_start, sample_end }, { "SAMPLE", sample_start, sample_end }, diff --git a/save-xml.c b/save-xml.c index 37d6d062e..9ba7a5468 100644 --- a/save-xml.c +++ b/save-xml.c @@ -97,6 +97,12 @@ static void quote(FILE *f, const char *text) case '&': escape = "&"; break; + case '\'': + escape = "'"; + break; + case '\"': + escape = """; + break; } fwrite(text, (p - text - 1), 1, f); if (!escape) @@ -275,6 +281,18 @@ static void save_events(FILE *f, struct event *ev) } } +static void save_trip(FILE *f, struct dive *trip) +{ + struct tm *tm = gmtime(&trip->when); + + fprintf(f, "<trip"); + fprintf(f, " date='%04u-%02u-%02u'", + tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday); + if (trip->location) + show_utf8(f, trip->location, " location=\'","\'"); + fprintf(f, " />\n"); +} + static void save_dive(FILE *f, struct dive *dive) { int i; @@ -283,6 +301,8 @@ static void save_dive(FILE *f, struct dive *dive) fputs("<dive", f); if (dive->number) fprintf(f, " number='%d'", dive->number); + if (dive->tripflag != TF_NONE) + fprintf(f, " tripflag='%s'", tripflag_names[dive->tripflag]); if (dive->rating) fprintf(f, " rating='%d'", dive->rating); fprintf(f, " date='%04u-%02u-%02u'", @@ -305,6 +325,8 @@ static void save_dive(FILE *f, struct dive *dive) void save_dives(const char *filename) { int i; + GList *trip = NULL; + FILE *f = fopen(filename, "w"); if (!f) @@ -314,6 +336,12 @@ void save_dives(const char *filename) update_dive(current_dive); fprintf(f, "<dives>\n<program name='subsurface' version='%d'></program>\n", VERSION); + + /* save the trips */ + while ((trip = NEXT_TRIP(trip, dive_trip_list)) != 0) + save_trip(f, trip->data); + + /* save the dives */ for (i = 0; i < dive_table.nr; i++) save_dive(f, get_dive(i)); fprintf(f, "</dives>\n"); |