]> git.tdb.fi Git - ext/subsurface.git/commitdiff
Merge branch 'trips' of git://git.hohndel.org/subsurface
authorLinus Torvalds <torvalds@linux-foundation.org>
Mon, 27 Aug 2012 22:36:27 +0000 (15:36 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Mon, 27 Aug 2012 22:36:27 +0000 (15:36 -0700)
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

1  2 
dive.h
divelist.c
gtk-gui.c
parse-xml.c
save-xml.c

diff --combined dive.h
index f1df0e2258df592b0e97ac23efe970fd3fc03146,a715ea24912d56536109b7bd1c91418c5d26143d..dffe7532587ef28dd8e50e0e2543fb719549fa7f
--- 1/dive.h
--- 2/dive.h
+++ b/dive.h
@@@ -95,7 -95,6 +95,7 @@@ typedef struct 
  extern gboolean cylinder_none(void *_data);
  extern gboolean no_cylinders(cylinder_t *cyl);
  extern gboolean cylinders_equal(cylinder_t *cyl1, cylinder_t *cyl2);
 +extern void copy_cylinders(cylinder_t *cyl1, cylinder_t *cyl2);
  extern gboolean no_weightsystems(weightsystem_t *ws);
  extern gboolean weightsystems_equal(weightsystem_t *ws1, weightsystem_t *ws2);
  
@@@ -235,8 -234,12 +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;
        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
@@@ -296,16 -351,6 +352,16 @@@ static inline struct dive *get_dive(uns
        return dive_table.dives[nr];
  }
  
 +/*
 + * Iterate over each dive, with the first parameter being the index
 + * iterator variable, and the second one being the dive one.
 + *
 + * I don't think anybody really wants the index, and we could make
 + * it local to the for-loop, but that would make us requires C99.
 + */
 +#define for_each_dive(_i,_x) \
 +      for ((_i) = 0; ((_x) = get_dive(_i)) != NULL; (_i)++)
 +
  extern void parse_xml_init(void);
  extern void parse_xml_buffer(const char *url, const char *buf, int size, GError **error);
  extern void set_filename(const char *filename);
@@@ -366,7 -411,7 +422,7 @@@ extern void evn_foreach(void (*callback
  
  extern int add_new_dive(struct dive *dive);
  extern int edit_dive_info(struct dive *dive);
 -extern int edit_multi_dive_info(int idx);
 +extern int edit_multi_dive_info(struct dive *single_dive);
  extern void dive_list_update_dives(void);
  extern void flush_divelist(struct dive *dive);
  
diff --combined divelist.c
index 30bd2d8e97b9024383e3e961457bae46ae06d356,83161b8c55f647ec64b5b901f5196b823101b2c6..a773b60179674084a2dfe251e5537bf37285e64d
@@@ -31,6 -31,10 +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 +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;
@@@ -85,7 -92,7 +92,7 @@@ void dump_selection(void
        struct dive *dive;
  
        printf("currently selected are %d dives:", amount_selected);
 -      for (i = 0; (dive = get_dive(i)) != NULL; i++) {
 +      for_each_dive(i, dive) {
                if (dive->selected)
                        printf(" %d", i);
        }
@@@ -327,16 -334,14 +334,14 @@@ static void date_data_func(GtkTreeViewC
        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 +882,102 @@@ void update_dive_list_col_visibility(vo
        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];
-               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;
+               /* 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,
        }
  
        /* 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;
@@@ -1108,7 -1139,7 +1139,7 @@@ void add_dive_cb(GtkWidget *menuitem, g
  
  void edit_dive_cb(GtkWidget *menuitem, gpointer data)
  {
 -      edit_multi_dive_info(-1);
 +      edit_multi_dive_info(NULL);
  }
  
  static void expand_all_cb(GtkWidget *menuitem, GtkTreeView *tree_view)
diff --combined gtk-gui.c
index 895ef449365ec220bd833c28f1bcf0745b508a21,f9cc8e674914cdec2985ce9f91b049d7d34d4f50..bc8e6e00097e8f8c4f51cb00b3eaadedf415b5d6
+++ b/gtk-gui.c
@@@ -98,7 -98,7 +98,7 @@@ void report_error(GError* error
        {
                return;
        }
 -      
 +
        if (error_info_bar == NULL)
        {
                error_count = 1;
                g_signal_connect(error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
                gtk_info_bar_set_message_type(GTK_INFO_BAR(error_info_bar),
                                              GTK_MESSAGE_ERROR);
 -              
 +
                error_label = gtk_label_new(error->message);
                GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(error_info_bar));
                gtk_container_add(GTK_CONTAINER(container), error_label);
 -              
 +
                gtk_box_pack_start(GTK_BOX(main_vbox), error_info_bar, FALSE, FALSE, 0);
                gtk_widget_show_all(main_vbox);
        }
@@@ -151,7 -151,7 +151,7 @@@ static void file_open(GtkWidget *w, gpo
                GSList *filenames, *fn_glist;
                char *filename;
                filenames = fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
 -              
 +
                GError *error = NULL;
                while(filenames != NULL) {
                        filename = filenames->data;
                                g_error_free(error);
                                error = NULL;
                        }
 -                      
 +
                        g_free(filename);
                        filenames = g_slist_next(filenames);
                }
        gtk_widget_destroy(dialog);
  }
  
 +/* return the path and the file component contained in the full path */
 +static char *path_and_file(char *pathin, char **fileout) {
 +      char *slash = pathin, *next;
 +      char *result;
 +      size_t len, n;
 +
 +      if (! pathin) {
 +              *fileout = strdup("");
 +              return strdup("");
 +      }
 +      while ((next = strpbrk(slash + 1, "\\/")))
 +              slash = next;
 +      if (pathin != slash)
 +              slash++;
 +      *fileout = strdup(slash);
 +
 +      /* strndup(pathin, slash - pathin) */
 +      n = slash - pathin;
 +      len = strlen(pathin);
 +      if (n < len)
 +              len = n;
 +
 +      result = (char *)malloc(len + 1);
 +      if (!result)
 +              return 0;
 +
 +      result[len] = '\0';
 +      return (char *)memcpy(result, pathin, len);
 +}
 +
  static void file_save_as(GtkWidget *w, gpointer data)
  {
        GtkWidget *dialog;
        char *filename = NULL;
 +      char *current_file;
 +      char *current_dir;
 +
        dialog = gtk_file_chooser_dialog_new("Save File As",
                GTK_WINDOW(main_window),
                GTK_FILE_CHOOSER_ACTION_SAVE,
                NULL);
        gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
  
 -      gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), existing_filename);
 +      current_dir = path_and_file(existing_filename, &current_file);
 +      gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), current_dir);
 +      gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), current_file);
 +
 +      free(current_dir);
 +      free(current_file);
 +
        if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
                filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
        }
@@@ -428,6 -389,7 +428,7 @@@ OPTIONCALLBACK(temperature_toggle, visi
  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 -485,22 +524,22 @@@ static void preferences_dialog(GtkWidge
        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));
                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 -810,8 +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);
diff --combined parse-xml.c
index ab77cb59bff5a77a2badb0a8ea6e41e4b1a6c0db,b70f154449975571541f95572c404aed6dff30c6..5159a334f9153f65b010f0e1cf5c4845cebcf530
@@@ -39,6 -39,11 +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 +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 +540,17 @@@ static void get_index(char *buffer, voi
        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 +1078,8 @@@ static void try_to_fill_dive(struct div
  
        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))
        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 +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 +1280,10 @@@ static void entry(const char *name, in
                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 +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 },
@@@ -1420,8 -1480,8 +1480,8 @@@ void parse_xml_buffer(const char *url, 
                }
                return;
        }
 -      /* we assume that the last (or only) filename passed as argument is a 
 -       * great filename to use as default when saving the dives */ 
 +      /* we assume that the last (or only) filename passed as argument is a
 +       * great filename to use as default when saving the dives */
        set_filename(url);
        reset_all();
        dive_start();
diff --combined save-xml.c
index 3464cd6d31254f67f515b994c76cc14aca2de1bc,9ba7a54687d1d497024cc539fae066434fa82ecf..b797475e5362441b49bfe5cbf87a7a64d4259ae9
@@@ -67,9 -67,10 +67,9 @@@ static void show_pressure(FILE *f, pres
   * characters, but at least libxml2 doesn't like them. It doesn't even
   * allow them quoted. So we just skip them and replace them with '?'.
   *
 - * Nothing else (and if we ever do this using attributes, we'd need to
 - * quote the quotes we use too).
 + * If we do this for attributes, we need to quote the quotes we use too.
   */
 -static void quote(FILE *f, const char *text)
 +static void quote(FILE *f, const char *text, int is_attribute)
  {
        const char *p = text;
  
                        escape = "&amp;";
                        break;
                case '\'':
 +                      if (!is_attribute)
 +                              continue;
                        escape = "&apos;";
                        break;
                case '\"':
 +                      if (!is_attribute)
 +                              continue;
                        escape = "&quot;";
                        break;
                }
        }
  }
  
 -static void show_utf8(FILE *f, const char *text, const char *pre, const char *post)
 +static void show_utf8(FILE *f, const char *text, const char *pre, const char *post, int is_attribute)
  {
        int len;
  
                len--;
        /* FIXME! Quoting! */
        fputs(pre, f);
 -      quote(f, text);
 +      quote(f, text, is_attribute);
        fputs(post, f);
  }
  
@@@ -180,7 -177,7 +180,7 @@@ static void show_location(FILE *f, stru
                }
                prefix = buffer;
        }
 -      show_utf8(f, dive->location, prefix,"</location>\n");
 +      show_utf8(f, dive->location, prefix,"</location>\n", 0);
  }
  
  static void save_overview(FILE *f, struct dive *dive)
        save_temperatures(f, dive);
        show_duration(f, dive->surfacetime, "  <surfacetime>", "</surfacetime>\n");
        show_location(f, dive);
 -      show_utf8(f, dive->divemaster, "  <divemaster>","</divemaster>\n");
 -      show_utf8(f, dive->buddy, "  <buddy>","</buddy>\n");
 -      show_utf8(f, dive->notes, "  <notes>","</notes>\n");
 -      show_utf8(f, dive->suit, "  <suit>","</suit>\n");
 +      show_utf8(f, dive->divemaster, "  <divemaster>","</divemaster>\n", 0);
 +      show_utf8(f, dive->buddy, "  <buddy>","</buddy>\n", 0);
 +      show_utf8(f, dive->notes, "  <notes>","</notes>\n", 0);
 +      show_utf8(f, dive->suit, "  <suit>","</suit>\n", 0);
  }
  
  static void save_cylinder_info(FILE *f, struct dive *dive)
@@@ -271,7 -268,7 +271,7 @@@ static void save_one_event(FILE *f, str
        show_index(f, ev->type, "type='", "'");
        show_index(f, ev->flags, "flags='", "'");
        show_index(f, ev->value, "value='", "'");
 -      show_utf8(f, ev->name, " name='", "'");
 +      show_utf8(f, ev->name, " name='", "'", 1);
        fprintf(f, " />\n");
  }
  
@@@ -284,6 -281,18 +284,18 @@@ static void save_events(FILE *f, struc
        }
  }
  
 -              show_utf8(f, trip->location, " location=\'","\'");
+ 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;
        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'",
  void save_dives(const char *filename)
  {
        int i;
+       GList *trip = NULL;
        FILE *f = fopen(filename, "w");
  
        if (!f)
        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");