Merge branch 'trips' of git://git.hohndel.org/subsurface

Merge the initial 'track trips explicitly' code from Dirk Hohndel.

Fix up trivial conflicts in save-xml.c due to the new 'is_attribute'
flag.

* 'trips' of git://git.hohndel.org/subsurface:
  Fix an issue with trips that have dives from multiple input files
  Some simple test dives for the trips code
  First cut of explicit trip tracking
This commit is contained in:
Linus Torvalds 2012-08-27 15:36:27 -07:00
commit c89f88378a
8 changed files with 289 additions and 75 deletions

56
dive.h
View file

@ -235,8 +235,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;
@ -257,6 +261,58 @@ 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 FIND_TRIP(_trip, _list) g_list_find_custom((_list), (_trip), dive_date_cmp)
#ifdef DEBUG_TRIP
static void dump_trip_list(void)
{
GList *p = NULL;
int i=0;
while ((p = NEXT_TRIP(p, dive_trip_list))) {
struct tm *tm = gmtime(&DIVE_TRIP(p)->when);
printf("trip %d to \"%s\" on %04u-%02u-%02u\n", ++i, DIVE_TRIP(p)->location,
tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday);
}
printf("-----\n");
}
#endif
/* insert the trip into the list - but ensure you don't have two trips
* for the same date; but if you have, make sure you don't keep the
* one with less information */
static inline GList *insert_trip(struct dive *_trip, GList *_list)
{
GList *result = FIND_TRIP(_trip, _list);
if (result) {
if (! DIVE_TRIP(result)->location)
DIVE_TRIP(result)->location = _trip->location;
} else {
result = g_list_insert_sorted((_list), (_trip), dive_date_cmp);
}
#ifdef DEBUG_TRIP
dump_trip_list();
#endif
return result;
}
/*
* We keep our internal data in well-specified units, but
* the input and output may come in some random format. This

View file

@ -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,102 @@ 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);
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];
struct dive *dive = get_dive(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,
/* 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);
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,
-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;
/* the dive_trip info might have been killed by a previous UNGROUPED dive */
if (trip)
dive_trip = DIVE_TRIP(trip);
/* 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 +1006,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;

9
dives/test21.xml Normal file
View file

@ -0,0 +1,9 @@
<dives>
<program name='subsurface' version='1'></program>
<trip date='2011-12-02' />
<dive number='20' tripflag='INTRIP' date='2011-12-02' time='14:00:00' duration='30:00 min'>
<depth max='20.0 m' mean='15.0 m' />
<location>20th test dive - this should be in a trip with same location</location>
<notes>We are testing that the location of the dive is picked up in the trip if the trip has no location</notes>
</dive>
</dives>

9
dives/test22.xml Normal file
View file

@ -0,0 +1,9 @@
<dives>
<program name='subsurface' version='1'></program>
<trip date='2011-12-02' location='trip location' />
<dive number='21' tripflag='INTRIP' date='2011-12-02' time='15:00:00' duration='30:00 min'>
<depth max='20.0 m' mean='15.0 m' />
<location>21st test dive - this should be in a trip with a trip location</location>
<notes>We are testing that the location of the dive is not picked up in the trip if the trip has a location</notes>
</dive>
</dives>

9
dives/test23.xml Normal file
View file

@ -0,0 +1,9 @@
<dives>
<program name='subsurface' version='1'></program>
<trip date='2011-12-02' location='trip location' />
<dive number='22' tripflag='NOTRIP' date='2011-12-09' time='6:00:00' duration='30:00 min'>
<depth max='20.0 m' mean='15.0 m' />
<location>22nd test dive - this should not be in a trip</location>
<notes>We are testing that the NOTRIP flag works</notes>
</dive>
</dives>

View file

@ -428,6 +428,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)
{
@ -523,8 +524,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));
@ -553,6 +568,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();
@ -833,6 +849,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);

View file

@ -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 },

View file

@ -284,6 +284,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=\'","\'", 1);
fprintf(f, " />\n");
}
static void save_dive(FILE *f, struct dive *dive)
{
int i;
@ -292,6 +304,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'",
@ -314,6 +328,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)
@ -323,6 +339,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");