summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile19
-rw-r--r--README6
-rw-r--r--dive.c88
-rw-r--r--dive.h18
-rw-r--r--gpl-2.0.txt339
-rw-r--r--gtk-gui.c155
-rw-r--r--libdivecomputer.c71
-rw-r--r--libdivecomputer.h3
-rw-r--r--parse-xml.c45
-rw-r--r--profile.c28
-rw-r--r--save-xml.c26
11 files changed, 744 insertions, 54 deletions
diff --git a/Makefile b/Makefile
index c019cf8c7..f962c0f01 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,12 @@
+VERSION=1.0
+
CC=gcc
CFLAGS=-Wall -Wno-pointer-sign -g
+INSTALL=install
+
+prefix = $(HOME)
+DESTDIR = $(prefix)/bin
+NAME = subsurface
LIBDIVECOMPUTERDIR = /usr/local
LIBDIVECOMPUTERINCLUDES = $(LIBDIVECOMPUTERDIR)/include/libdivecomputer
@@ -21,8 +28,12 @@ OBJS = main.o dive.o profile.o info.o equipment.o divelist.o \
parse-xml.o save-xml.o libdivecomputer.o print.o uemis.o \
gtk-gui.o
-subsurface: $(OBJS)
- $(CC) $(LDFLAGS) -o subsurface $(OBJS) $(LIBS)
+$(NAME): $(OBJS)
+ $(CC) $(LDFLAGS) -o $(NAME) $(OBJS) $(LIBS)
+
+install: $(NAME)
+ $(INSTALL) -d -m 755 '$(DESTDIR)'
+ $(INSTALL) $(NAME) '$(DESTDIR)'
parse-xml.o: parse-xml.c dive.h
$(CC) $(CFLAGS) `pkg-config --cflags glib-2.0` -c `xml2-config --cflags` parse-xml.c
@@ -60,7 +71,11 @@ libdivecomputer.o: libdivecomputer.c dive.h display.h display-gtk.h libdivecompu
gtk-gui.o: gtk-gui.c dive.h display.h divelist.h display-gtk.h libdivecomputer.h
$(CC) $(CFLAGS) `pkg-config --cflags gtk+-2.0 glib-2.0 gconf-2.0` \
-I$(LIBDIVECOMPUTERINCLUDES) \
+ -DVERSION_STRING='"v$(VERSION)"' \
-c gtk-gui.c
uemis.o: uemis.c uemis.h
$(CC) $(CFLAGS) `pkg-config --cflags gtk+-2.0 glib-2.0` -c uemis.c
+
+clean:
+ rm -f $(OBJS) *~ $(NAME)
diff --git a/README b/README
index 1660f66eb..74e4b96ea 100644
--- a/README
+++ b/README
@@ -32,14 +32,14 @@ Usage:
to see my dives (with no notes or commentary).
-Or, if you have a dive computer supported by libdivecomputer (and
-connected to /dev/ttyUSB0), you can just do
+Or, if you have a dive computer supported by libdivecomputer, you can
+just do
make
./subsurface
and select "Import" from the File menu, tell it what dive computer you
-have, and hit "OK".
+have (and where it is connected if you need to), and hit "OK".
There's a lot of duplicates in the XML files that come as an example,
and subsurface will de-duplicate the ones that are exactly the same
diff --git a/dive.c b/dive.c
index 41bbabd69..d934d1438 100644
--- a/dive.c
+++ b/dive.c
@@ -5,6 +5,29 @@
#include "dive.h"
+void add_event(struct dive *dive, int time, int type, int flags, int value, const char *name)
+{
+ struct event *ev, **p;
+ unsigned int size, len = strlen(name);
+
+ size = sizeof(*ev) + len + 1;
+ ev = malloc(size);
+ if (!ev)
+ return;
+ memset(ev, 0, size);
+ memcpy(ev->name, name, len);
+ ev->time.seconds = time;
+ ev->type = type;
+ ev->flags = flags;
+ ev->value = value;
+ ev->next = NULL;
+
+ p = &dive->events;
+ while (*p)
+ p = &(*p)->next;
+ *p = ev;
+}
+
double get_depth_units(unsigned int mm, int *frac, const char **units)
{
int decimals;
@@ -198,6 +221,8 @@ struct dive *fixup_dive(struct dive *dive)
/* Don't pick a zero for MERGE_MIN() */
#define MERGE_MAX(res, a, b, n) res->n = MAX(a->n, b->n)
#define MERGE_MIN(res, a, b, n) res->n = (a->n)?(b->n)?MIN(a->n, b->n):(a->n):(b->n)
+#define MERGE_TXT(res, a, b, n) res->n = merge_text(a->n, b->n)
+#define MERGE_NONZERO(res, a, b, n) res->n = a->n ? a->n : b->n
static struct dive *add_sample(struct sample *sample, int time, struct dive *dive)
{
@@ -295,6 +320,59 @@ static char *merge_text(const char *a, const char *b)
return res;
}
+#define SORT(a,b,field) \
+ if (a->field != b->field) return a->field < b->field ? -1 : 1
+
+static int sort_event(struct event *a, struct event *b)
+{
+ SORT(a,b,time.seconds);
+ SORT(a,b,type);
+ SORT(a,b,flags);
+ SORT(a,b,value);
+ return strcmp(a->name, b->name);
+}
+
+static void merge_events(struct dive *res, struct dive *src1, struct dive *src2, int offset)
+{
+ struct event *a, *b;
+ struct event **p = &res->events;
+
+ a = src1->events;
+ b = src2->events;
+ while (b) {
+ b->time.seconds += offset;
+ b = b->next;
+ }
+ b = src2->events;
+
+ while (a || b) {
+ int s;
+ if (!b) {
+ *p = a;
+ break;
+ }
+ if (!a) {
+ *p = b;
+ break;
+ }
+ s = sort_event(a, b);
+ /* Pick b */
+ if (s > 0) {
+ *p = b;
+ p = &b->next;
+ b = b->next;
+ continue;
+ }
+ /* Pick 'a' or neither */
+ if (s < 0) {
+ *p = a;
+ p = &a->next;
+ }
+ a = a->next;
+ continue;
+ }
+}
+
/* Pick whichever has any info (if either). Prefer 'a' */
static void merge_cylinder_type(cylinder_type_t *res, cylinder_type_t *a, cylinder_type_t *b)
{
@@ -334,8 +412,12 @@ struct dive *try_to_merge(struct dive *a, struct dive *b)
res = alloc_dive();
res->when = a->when;
- res->location = merge_text(a->location, b->location);
- res->notes = merge_text(a->notes, b->notes);
+ MERGE_NONZERO(res, a, b, latitude);
+ MERGE_NONZERO(res, a, b, longitude);
+ MERGE_TXT(res, a, b, location);
+ MERGE_TXT(res, a, b, notes);
+ MERGE_TXT(res, a, b, buddy);
+ MERGE_TXT(res, a, b, divemaster);
MERGE_MAX(res, a, b, number);
MERGE_MAX(res, a, b, maxdepth.mm);
res->meandepth.mm = 0;
@@ -345,6 +427,6 @@ struct dive *try_to_merge(struct dive *a, struct dive *b)
MERGE_MIN(res, a, b, watertemp.mkelvin);
for (i = 0; i < MAX_CYLINDERS; i++)
merge_cylinder_info(res->cylinder+i, a->cylinder + i, b->cylinder + i);
-
+ merge_events(res, a, b, 0);
return merge_samples(res, a, b, 0);
}
diff --git a/dive.h b/dive.h
index 5cb324123..42d91d630 100644
--- a/dive.h
+++ b/dive.h
@@ -147,6 +147,21 @@ struct sample {
int cylinderindex;
};
+/*
+ * Events are currently pretty meaningless. This is
+ * just based on the random data that libdivecomputer
+ * gives us. I'm not sure what a real "architected"
+ * event model would actually look like, but right
+ * now you can associate a list of events with a dive,
+ * and we'll do something about it.
+ */
+struct event {
+ struct event *next;
+ duration_t time;
+ int type, flags, value;
+ char name[];
+};
+
#define MAX_CYLINDERS (8)
struct dive {
@@ -162,6 +177,7 @@ struct dive {
temperature_t airtemp, watertemp;
cylinder_t cylinder[MAX_CYLINDERS];
int otu;
+ struct event *events;
int samples, alloc_samples;
struct sample sample[];
};
@@ -233,6 +249,8 @@ extern struct dive *try_to_merge(struct dive *a, struct dive *b);
extern void renumber_dives(int nr);
+extern void add_event(struct dive *dive, int time, int type, int flags, int value, const char *name);
+
/* UI related protopypes */
extern void init_ui(int argc, char **argv);
diff --git a/gpl-2.0.txt b/gpl-2.0.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/gpl-2.0.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/gtk-gui.c b/gtk-gui.c
index 77e46c952..4b6795621 100644
--- a/gtk-gui.c
+++ b/gtk-gui.c
@@ -17,7 +17,7 @@
#include "libdivecomputer.h"
-GtkWidget *main_window;
+GtkWidget *main_window, *divelist_window;
GtkWidget *main_vbox;
GtkWidget *error_info_bar;
GtkWidget *error_label;
@@ -38,7 +38,8 @@ visible_cols_t visible_cols = {TRUE, FALSE};
void repaint_dive(void)
{
update_dive(current_dive);
- gtk_widget_queue_draw(dive_profile);
+ if (dive_profile)
+ gtk_widget_queue_draw(dive_profile);
}
static char *existing_filename;
@@ -89,6 +90,8 @@ void report_error(GError* error)
static void file_open(GtkWidget *w, gpointer data)
{
GtkWidget *dialog;
+ GtkFileFilter *filter;
+
dialog = gtk_file_chooser_dialog_new("Open File",
GTK_WINDOW(main_window),
GTK_FILE_CHOOSER_ACTION_OPEN,
@@ -97,6 +100,13 @@ static void file_open(GtkWidget *w, gpointer data)
NULL);
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
+ filter = gtk_file_filter_new();
+ gtk_file_filter_add_pattern(filter, "*.xml");
+ gtk_file_filter_add_pattern(filter, "*.XML");
+ gtk_file_filter_add_mime_type(filter, "text/xml");
+ gtk_file_filter_set_name(filter, "XML file");
+ gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
+
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
GSList *filenames;
char *filename;
@@ -257,7 +267,7 @@ OPTIONCALLBACK(sac_toggle, visible_cols.sac)
static void preferences_dialog(GtkWidget *w, gpointer data)
{
int result;
- GtkWidget *dialog, *font, *frame, *box, *button;
+ GtkWidget *dialog, *font, *frame, *box, *vbox, *button;
menu_units = output_units;
@@ -269,7 +279,8 @@ static void preferences_dialog(GtkWidget *w, gpointer data)
NULL);
frame = gtk_frame_new("Units");
- gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
+ vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+ gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
box = gtk_vbox_new(FALSE, 6);
gtk_container_add(GTK_CONTAINER(frame), box);
@@ -311,7 +322,7 @@ static void preferences_dialog(GtkWidget *w, gpointer data)
g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(otu_toggle), NULL);
font = gtk_font_button_new_with_font(divelist_font);
- gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), font, FALSE, FALSE, 5);
+ gtk_box_pack_start(GTK_BOX(vbox), font, FALSE, FALSE, 5);
gtk_widget_show_all(dialog);
result = gtk_dialog_run(GTK_DIALOG(dialog));
@@ -340,7 +351,7 @@ static void preferences_dialog(GtkWidget *w, gpointer data)
static void renumber_dialog(GtkWidget *w, gpointer data)
{
int result;
- GtkWidget *dialog, *frame, *button;
+ GtkWidget *dialog, *frame, *button, *vbox;
dialog = gtk_dialog_new_with_buttons("Renumber",
GTK_WINDOW(main_window),
@@ -349,8 +360,10 @@ static void renumber_dialog(GtkWidget *w, gpointer data)
GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
NULL);
+ vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+
frame = gtk_frame_new("New starting number");
- gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), frame);
+ gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
button = gtk_spin_button_new_with_range(1, 50000, 1);
gtk_container_add(GTK_CONTAINER(frame), button);
@@ -365,9 +378,32 @@ static void renumber_dialog(GtkWidget *w, gpointer data)
gtk_widget_destroy(dialog);
}
+static void about_dialog(GtkWidget *w, gpointer data)
+{
+ const char *logo_property = NULL;
+ GdkPixbuf *logo = NULL;
+ GtkWidget *image = gtk_image_new_from_file("icon.svg");
+
+ if (image) {
+ logo = gtk_image_get_pixbuf(GTK_IMAGE(image));
+ logo_property = "logo";
+ }
+
+ gtk_show_about_dialog(NULL,
+ "program-name", "SubSurface",
+ "comments", "Half-arsed divelog software in C",
+ "license", "GPLv2",
+ "version", VERSION_STRING,
+ "copyright", "Linus Torvalds 2011",
+ /* Must be last: */
+ logo_property, logo,
+ NULL);
+}
+
static GtkActionEntry menu_items[] = {
{ "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
{ "LogMenuAction", GTK_STOCK_FILE, "Log", NULL, NULL, NULL},
+ { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
{ "OpenFile", GTK_STOCK_OPEN, NULL, "<control>O", NULL, G_CALLBACK(file_open) },
{ "SaveFile", GTK_STOCK_SAVE, NULL, "<control>S", NULL, G_CALLBACK(file_save) },
{ "Print", GTK_STOCK_PRINT, NULL, "<control>P", NULL, G_CALLBACK(do_print) },
@@ -375,6 +411,7 @@ static GtkActionEntry menu_items[] = {
{ "Preferences", NULL, "Preferences", NULL, NULL, G_CALLBACK(preferences_dialog) },
{ "Renumber", NULL, "Renumber", NULL, NULL, G_CALLBACK(renumber_dialog) },
{ "Quit", GTK_STOCK_QUIT, NULL, "<control>Q", NULL, G_CALLBACK(quit) },
+ { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_dialog) },
};
static gint nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]);
@@ -395,6 +432,9 @@ static const gchar* ui_string = " \
<menu name=\"LogMenu\" action=\"LogMenuAction\"> \
<menuitem name=\"Renumber\" action=\"Renumber\" /> \
</menu> \
+ <menu name=\"Help\" action=\"HelpMenuAction\"> \
+ <menuitem name=\"About\" action=\"About\" /> \
+ </menu> \
</menubar> \
</ui> \
";
@@ -420,11 +460,41 @@ static void switch_page(GtkNotebook *notebook, gint arg1, gpointer user_data)
repaint_dive();
}
+static GtkNotebook *create_new_notebook_window(GtkNotebook *source,
+ GtkWidget *page,
+ gint x, gint y, gpointer data)
+{
+ GtkWidget *win, *notebook, *vbox;
+
+ /* We don't detatch twice */
+ if (divelist_window)
+ return NULL;
+
+ divelist_window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title(GTK_WINDOW(win), "Dive List");
+ gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(main_window));
+ gtk_window_set_destroy_with_parent(GTK_WINDOW(win), 1);
+ gtk_window_move(GTK_WINDOW(win), x, y);
+
+ /* Destroying the dive list will kill the application */
+ g_signal_connect(G_OBJECT(win), "delete-event", G_CALLBACK(on_delete), NULL);
+ g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(on_destroy), NULL);
+
+ vbox = gtk_vbox_new(FALSE, 0);
+ gtk_container_add(GTK_CONTAINER(win), vbox);
+
+ notebook = gtk_notebook_new();
+ gtk_notebook_set_group_id(GTK_NOTEBOOK(notebook), gtk_notebook_get_group_id(source));
+ gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 6);
+ gtk_widget_set_size_request(notebook, 350, 250);
+
+ gtk_widget_show_all(win);
+ return GTK_NOTEBOOK(notebook);
+}
+
void init_ui(int argc, char **argv)
{
GtkWidget *win;
- GtkWidget *paned;
- GtkWidget *info_box;
GtkWidget *notebook;
GtkWidget *dive_info;
GtkWidget *dive_list;
@@ -457,7 +527,7 @@ void init_ui(int argc, char **argv)
error_info_bar = NULL;
win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_icon_from_file(GTK_WINDOW(win), "icon.svg", NULL);
- g_signal_connect(G_OBJECT(win), "delete-event", G_CALLBACK (on_delete), NULL);
+ g_signal_connect(G_OBJECT(win), "delete-event", G_CALLBACK(on_delete), NULL);
g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(on_destroy), NULL);
main_window = win;
@@ -468,22 +538,16 @@ void init_ui(int argc, char **argv)
menubar = get_menubar_menu(win);
gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
- /* HPane for left the dive list, and right the dive info */
- paned = gtk_vpaned_new();
- gtk_box_pack_end(GTK_BOX(vbox), paned, TRUE, TRUE, 0);
-
- /* Create the actual divelist */
- dive_list = dive_list_create();
- gtk_paned_add2(GTK_PANED(paned), dive_list);
-
- /* VBox for dive info, and tabs */
- info_box = gtk_vbox_new(FALSE, 6);
- gtk_paned_add1(GTK_PANED(paned), info_box);
-
/* Notebook for dive info vs profile vs .. */
notebook = gtk_notebook_new();
+ gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 6);
+ gtk_notebook_set_window_creation_hook(create_new_notebook_window, NULL, NULL);
g_signal_connect(notebook, "switch-page", G_CALLBACK(switch_page), NULL);
- gtk_box_pack_start(GTK_BOX(info_box), notebook, TRUE, TRUE, 6);
+
+ /* Create the actual divelist */
+ dive_list = dive_list_create();
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook), dive_list, gtk_label_new("Dive List"));
+ gtk_notebook_set_tab_detachable(GTK_NOTEBOOK(notebook), dive_list, 1);
/* Frame for dive profile */
dive_profile = dive_profile_widget();
@@ -608,20 +672,23 @@ static void fill_computer_list(GtkListStore *store)
}
}
-static GtkComboBox *dive_computer_selector(GtkWidget *dialog)
+static GtkComboBox *dive_computer_selector(GtkWidget *vbox)
{
- GtkWidget *hbox, *combo_box;
+ GtkWidget *hbox, *combo_box, *frame;
GtkListStore *model;
GtkCellRenderer *renderer;
hbox = gtk_hbox_new(FALSE, 6);
- gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, FALSE, FALSE, 3);
+ gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
fill_computer_list(model);
+ frame = gtk_frame_new("Dive computer");
+ gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 3);
+
combo_box = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
- gtk_box_pack_start(GTK_BOX(hbox), combo_box, FALSE, TRUE, 3);
+ gtk_container_add(GTK_CONTAINER(frame), combo_box);
renderer = gtk_cell_renderer_text_new();
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo_box), renderer, TRUE);
@@ -630,13 +697,31 @@ static GtkComboBox *dive_computer_selector(GtkWidget *dialog)
return GTK_COMBO_BOX(combo_box);
}
+static GtkEntry *dive_computer_device(GtkWidget *vbox)
+{
+ GtkWidget *hbox, *entry, *frame;
+
+ hbox = gtk_hbox_new(FALSE, 6);
+ gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
+
+ frame = gtk_frame_new("Device name");
+ gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 3);
+
+ entry = gtk_entry_new();
+ gtk_container_add(GTK_CONTAINER(frame), entry);
+ gtk_entry_set_text(GTK_ENTRY(entry), "/dev/ttyUSB0");
+
+ return GTK_ENTRY(entry);
+}
+
void import_dialog(GtkWidget *w, gpointer data)
{
int result;
- GtkWidget *dialog, *hbox;
+ GtkWidget *dialog, *hbox, *vbox;
GtkComboBox *computer;
+ GtkEntry *device;
device_data_t devicedata = {
- .devname = "/dev/ttyUSB0",
+ .devname = NULL,
};
dialog = gtk_dialog_new_with_buttons("Import from dive computer",
@@ -646,12 +731,15 @@ void import_dialog(GtkWidget *w, gpointer data)
GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
NULL);
- computer = dive_computer_selector(dialog);
+ vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+
+ computer = dive_computer_selector(vbox);
+ device = dive_computer_device(vbox);
hbox = gtk_hbox_new(FALSE, 6);
- gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, FALSE, TRUE, 3);
- devicedata.progress->bar = gtk_progress_bar_new();
- gtk_container_add(GTK_CONTAINER(hbox), devicedata.progress->bar);
+ gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 3);
+ devicedata.progress.bar = gtk_progress_bar_new();
+ gtk_container_add(GTK_CONTAINER(hbox), devicedata.progress.bar);
gtk_widget_show_all(dialog);
result = gtk_dialog_run(GTK_DIALOG(dialog));
@@ -670,6 +758,7 @@ void import_dialog(GtkWidget *w, gpointer data)
-1);
devicedata.type = type;
devicedata.name = comp;
+ devicedata.devname = gtk_entry_get_text(device);
do_import(&devicedata);
break;
default:
diff --git a/libdivecomputer.c b/libdivecomputer.c
index cf8b048c9..5fea5c28c 100644
--- a/libdivecomputer.c
+++ b/libdivecomputer.c
@@ -70,7 +70,7 @@ static parser_status_t create_parser(device_data_t *devdata, parser_t **parser)
return mares_nemo_parser_create(parser, devdata->devinfo.model);
case DEVICE_TYPE_MARES_ICONHD:
- return mares_iconhd_parser_create(parser);
+ return mares_iconhd_parser_create(parser, devdata->devinfo.model);
case DEVICE_TYPE_HW_OSTC:
return hw_ostc_parser_create(parser);
@@ -118,16 +118,47 @@ static int parse_gasmixes(struct dive *dive, parser_t *parser, int ngases)
return PARSER_STATUS_SUCCESS;
}
-void
-sample_cb(parser_sample_type_t type, parser_sample_value_t value, void *userdata)
+static void handle_event(struct dive *dive, struct sample *sample, parser_sample_value_t value)
{
- int i;
+ int type, time;
static const char *events[] = {
"none", "deco", "rbt", "ascent", "ceiling", "workload", "transmitter",
"violation", "bookmark", "surface", "safety stop", "gaschange",
"safety stop (voluntary)", "safety stop (mandatory)", "deepstop",
"ceiling (safety stop)", "unknown", "divetime", "maxdepth",
- "OLF", "PO2", "airtime", "rgbm", "heading", "tissue level warning"};
+ "OLF", "PO2", "airtime", "rgbm", "heading", "tissue level warning"
+ };
+ const int nr_events = sizeof(events) / sizeof(const char *);
+ const char *name;
+
+ /*
+ * Just ignore surface events. They are pointless. What "surface"
+ * means depends on the dive computer (and possibly even settings
+ * in the dive computer). It does *not* necessarily mean "depth 0",
+ * so don't even turn it into that.
+ */
+ if (value.event.type == SAMPLE_EVENT_SURFACE)
+ return;
+
+ /*
+ * Other evens might be more interesting, but for now we just print them out.
+ */
+ type = value.event.type;
+ name = "invalid event number";
+ if (type < nr_events)
+ name = events[type];
+
+ time = value.event.time;
+ if (sample)
+ time += sample->time.seconds;
+
+ add_event(dive, time, type, value.event.flags, value.event.value, name);
+}
+
+void
+sample_cb(parser_sample_type_t type, parser_sample_value_t value, void *userdata)
+{
+ int i;
struct dive **divep = userdata;
struct dive *dive = *divep;
struct sample *sample;
@@ -155,8 +186,7 @@ sample_cb(parser_sample_type_t type, parser_sample_value_t value, void *userdata
sample->temperature.mkelvin = (value.temperature + 273.15) * 1000 + 0.5;
break;
case SAMPLE_TYPE_EVENT:
- printf(" <event type=\"%u\" time=\"%u\" flags=\"%u\" value=\"%u\">%s</event>\n",
- value.event.type, value.event.time, value.event.flags, value.event.value, events[value.event.type]);
+ handle_event(dive, sample, value);
break;
case SAMPLE_TYPE_RBT:
printf(" <rbt>%u</rbt>\n", value.rbt);
@@ -186,6 +216,23 @@ static int parse_samples(struct dive **divep, parser_t *parser)
return parser_samples_foreach(parser, sample_cb, divep);
}
+/*
+ * Check if this dive already existed before the import
+ */
+static int find_dive(struct dive *dive, device_data_t *devdata)
+{
+ int i;
+
+ for (i = 0; i < devdata->preexisting; i++) {
+ struct dive *old = dive_table.dives[i];
+
+ if (dive->when != old->when)
+ continue;
+ return 1;
+ }
+ return 0;
+}
+
static int dive_cb(const unsigned char *data, unsigned int size,
const unsigned char *fingerprint, unsigned int fsize,
void *userdata)
@@ -272,15 +319,21 @@ static int dive_cb(const unsigned char *data, unsigned int size,
parser_destroy(parser);
return rc;
}
- record_dive(dive);
parser_destroy(parser);
+
+ /* If we already saw this dive, abort. */
+ if (find_dive(dive, devdata))
+ return 0;
+
+ record_dive(dive);
return 1;
}
static device_status_t import_device_data(device_t *device, device_data_t *devicedata)
{
+ devicedata->preexisting = dive_table.nr;
return device_foreach(device, dive_cb, devicedata);
}
@@ -369,7 +422,7 @@ static void event_cb(device_t *device, device_event_t event, const void *data, v
printf("Event: waiting for user action\n");
break;
case DEVICE_EVENT_PROGRESS:
- update_progressbar(devdata->progress,
+ update_progressbar(&devdata->progress,
(double) progress->current / (double) progress->maximum);
break;
case DEVICE_EVENT_DEVINFO:
diff --git a/libdivecomputer.h b/libdivecomputer.h
index 205f28e7c..57d274cc6 100644
--- a/libdivecomputer.h
+++ b/libdivecomputer.h
@@ -22,9 +22,10 @@
typedef struct device_data_t {
device_type_t type;
const char *name, *devname;
- progressbar_t *progress;
+ progressbar_t progress;
device_devinfo_t devinfo;
device_clock_t clock;
+ int preexisting;
} device_data_t;
struct device_list {
diff --git a/parse-xml.c b/parse-xml.c
index 8f916b09a..6eafb0d3a 100644
--- a/parse-xml.c
+++ b/parse-xml.c
@@ -92,8 +92,14 @@ const struct units IMPERIAL_units = {
*/
static struct dive *dive;
static struct sample *sample;
+static struct {
+ int active;
+ duration_t time;
+ int type, flags, value;
+ const char *name;
+} event;
static struct tm tm;
-static int event_index, cylinder_index;
+static int cylinder_index;
static enum import_source {
UNKNOWN,
@@ -558,6 +564,32 @@ static int uddf_fill_sample(struct sample *sample, const char *name, int len, ch
0;
}
+static void eventtime(char *buffer, void *_duration)
+{
+ duration_t *duration = _duration;
+ sampletime(buffer, duration);
+ if (sample)
+ duration->seconds += sample->time.seconds;
+}
+
+static void try_to_fill_event(const char *name, char *buf)
+{
+ int len = strlen(name);
+
+ start_match("event", name, buf);
+ if (MATCH(".event", utf8_string, &event.name))
+ return;
+ if (MATCH(".name", utf8_string, &event.name))
+ return;
+ if (MATCH(".time", eventtime, &event.time))
+ return;
+ if (MATCH(".type", get_index, &event.type))
+ return;
+ if (MATCH(".flags", get_index, &event.flags))
+ return;
+ nonmatch("event", name, buf);
+}
+
/* We're in samples - try to convert the random xml value to something useful */
static void try_to_fill_sample(struct sample *sample, const char *name, char *buf)
{
@@ -1137,11 +1169,15 @@ static void dive_end(void)
static void event_start(void)
{
+ memset(&event, 0, sizeof(event));
+ event.active = 1;
}
static void event_end(void)
{
- event_index++;
+ if (event.name && strcmp(event.name, "surface") != 0)
+ add_event(dive, event.time.seconds, event.type, event.flags, event.value, event.name);
+ event.active = 0;
}
static void cylinder_start(void)
@@ -1156,7 +1192,6 @@ static void cylinder_end(void)
static void sample_start(void)
{
sample = prepare_sample(&dive);
- event_index = 0;
}
static void sample_end(void)
@@ -1176,6 +1211,10 @@ static void entry(const char *name, int size, const char *raw)
return;
memcpy(buf, raw, size);
buf[size] = 0;
+ if (event.active) {
+ try_to_fill_event(name, buf);
+ return;
+ }
if (sample) {
try_to_fill_sample(sample, name, buf);
return;
diff --git a/profile.c b/profile.c
index 1b7db1d90..0e5361704 100644
--- a/profile.c
+++ b/profile.c
@@ -156,6 +156,33 @@ static void plot_text(struct graphics_context *gc, const text_render_options_t *
cairo_show_text(cr, buffer);
}
+static void plot_one_event(struct graphics_context *gc, struct plot_info *pi, struct event *event, const text_render_options_t *tro)
+{
+ int i, depth = 0;
+
+ for (i = 0; i < pi->nr; i++) {
+ struct plot_data *data = pi->entry + i;
+ if (event->time.seconds < data->sec)
+ break;
+ depth = data->val;
+ }
+ plot_text(gc, tro, event->time.seconds, depth, "%s", event->name);
+}
+
+static void plot_events(struct graphics_context *gc, struct plot_info *pi, struct dive *dive)
+{
+ static const text_render_options_t tro = {14, 1.0, 0.2, 0.2, CENTER, TOP};
+ struct event *event = dive->events;
+
+ if (gc->printer)
+ return;
+
+ while (event) {
+ plot_one_event(gc, pi, event, &tro);
+ event = event->next;
+ }
+}
+
static void render_depth_sample(struct graphics_context *gc, struct plot_data *entry, const text_render_options_t *tro)
{
int sec = entry->sec, decimals;
@@ -715,6 +742,7 @@ void plot(struct graphics_context *gc, int w, int h, struct dive *dive)
/* Depth profile */
plot_depth_profile(gc, pi);
+ plot_events(gc, pi, dive);
/* Text on top of all graphs.. */
plot_temperature_text(gc, pi);
diff --git a/save-xml.c b/save-xml.c
index e64b380a4..d6774b5c7 100644
--- a/save-xml.c
+++ b/save-xml.c
@@ -218,6 +218,12 @@ static void save_cylinder_info(FILE *f, struct dive *dive)
}
}
+static void show_index(FILE *f, int value, const char *pre, const char *post)
+{
+ if (value)
+ fprintf(f, " %s%d%s", pre, value, post);
+}
+
static void save_sample(FILE *f, struct sample *sample)
{
fprintf(f, " <sample time='%u:%02u min'", FRACTION(sample->time.seconds,60));
@@ -229,6 +235,25 @@ static void save_sample(FILE *f, struct sample *sample)
fprintf(f, " />\n");
}
+static void save_one_event(FILE *f, struct event *ev)
+{
+ fprintf(f, " <event time='%d:%02d min'", FRACTION(ev->time.seconds,60));
+ show_index(f, ev->type, "type='", "'");
+ show_index(f, ev->flags, "flags='", "'");
+ show_index(f, ev->value, "value='", "'");
+ show_utf8(f, ev->name, " name='", "'");
+ fprintf(f, " />\n");
+}
+
+
+static void save_events(FILE *f, struct event *ev)
+{
+ while (ev) {
+ save_one_event(f, ev);
+ ev = ev->next;
+ }
+}
+
static void save_dive(FILE *f, struct dive *dive)
{
int i;
@@ -245,6 +270,7 @@ static void save_dive(FILE *f, struct dive *dive)
FRACTION(dive->duration.seconds, 60));
save_overview(f, dive);
save_cylinder_info(f, dive);
+ save_events(f, dive->events);
for (i = 0; i < dive->samples; i++)
save_sample(f, dive->sample+i);
fprintf(f, "</dive>\n");