]> git.tdb.fi Git - ext/subsurface.git/blob - main.c
Do a dive de-dup pass
[ext/subsurface.git] / main.c
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <time.h>
5
6 #include "dive.h"
7 #include "display.h"
8
9 GtkWidget *main_window;
10
11 static int sortfn(const void *_a, const void *_b)
12 {
13         const struct dive *a = *(void **)_a;
14         const struct dive *b = *(void **)_b;
15
16         if (a->when < b->when)
17                 return -1;
18         if (a->when > b->when)
19                 return 1;
20         return 0;
21 }
22
23 static int alloc_samples;
24
25 /* Don't pick a zero for MERGE_MIN() */
26 #define MERGE_MAX(res, a, b, n) res->n = MAX(a->n, b->n)
27 #define MERGE_MIN(res, a, b, n) res->n = (a->n)?(b->n)?MIN(a->n, b->n):(a->n):(b->n)
28
29 static struct dive *add_sample(struct sample *sample, int time, struct dive *dive)
30 {
31         int nr = dive->samples;
32         struct sample *d;
33
34         if (nr >= alloc_samples) {
35                 alloc_samples = (alloc_samples + 64) * 3 / 2;
36                 dive = realloc(dive, dive_size(alloc_samples));
37                 if (!dive)
38                         return NULL;
39         }
40         dive->samples = nr+1;
41         d = dive->sample + nr;
42
43         *d = *sample;
44         d->time.seconds = time;
45         return dive;
46 }
47
48 /*
49  * Merge samples. Dive 'a' is "offset" seconds before Dive 'b'
50  */
51 static struct dive *merge_samples(struct dive *res, struct dive *a, struct dive *b, int offset)
52 {
53         int asamples = a->samples;
54         int bsamples = b->samples;
55         struct sample *as = a->sample;
56         struct sample *bs = b->sample;
57
58         for (;;) {
59                 int at, bt;
60                 struct sample sample;
61
62                 if (!res)
63                         return NULL;
64
65                 at = asamples ? as->time.seconds : -1;
66                 bt = bsamples ? bs->time.seconds + offset : -1;
67
68                 /* No samples? All done! */
69                 if (at < 0 && bt < 0)
70                         return res;
71
72                 /* Only samples from a? */
73                 if (bt < 0) {
74 add_sample_a:
75                         res = add_sample(as, at, res);
76                         as++;
77                         asamples--;
78                         continue;
79                 }
80
81                 /* Only samples from b? */
82                 if (at < 0) {
83 add_sample_b:
84                         res = add_sample(bs, bt, res);
85                         bs++;
86                         bsamples--;
87                         continue;
88                 }
89
90                 if (at < bt)
91                         goto add_sample_a;
92                 if (at > bt)
93                         goto add_sample_b;
94
95                 /* same-time sample: add a merged sample. Take the non-zero ones */
96                 sample = *bs;
97                 if (as->depth.mm)
98                         sample.depth = as->depth;
99                 if (as->temperature.mkelvin)
100                         sample.temperature = as->temperature;
101                 if (as->tankpressure.mbar)
102                         sample.tankpressure = as->tankpressure;
103                 if (as->tankindex)
104                         sample.tankindex = as->tankindex;
105
106                 res = add_sample(&sample, at, res);
107
108                 as++;
109                 bs++;
110                 asamples--;
111                 bsamples--;
112         }
113 }
114
115 static char *merge_text(const char *a, const char *b)
116 {
117         char *res;
118
119         if (!a || !*a)
120                 return (char *)b;
121         if (!b || !*b)
122                 return (char *)a;
123         if (!strcmp(a,b))
124                 return (char *)a;
125         res = malloc(strlen(a) + strlen(b) + 9);
126         if (!res)
127                 return (char *)a;
128         sprintf(res, "(%s) or (%s)", a, b);
129         return res;
130 }
131
132 /*
133  * This could do a lot more merging. Right now it really only
134  * merges almost exact duplicates - something that happens easily
135  * with overlapping dive downloads.
136  */
137 static struct dive *try_to_merge(struct dive *a, struct dive *b)
138 {
139         int i;
140         struct dive *res;
141
142         if (a->when != b->when)
143                 return NULL;
144
145         alloc_samples = 5;
146         res = malloc(dive_size(alloc_samples));
147         if (!res)
148                 return NULL;
149         memset(res, 0, dive_size(alloc_samples));
150
151         res->when = a->when;
152         res->name = merge_text(a->name, b->name);
153         res->location = merge_text(a->location, b->location);
154         res->notes = merge_text(a->notes, b->notes);
155         MERGE_MAX(res, a, b, maxdepth.mm);
156         MERGE_MAX(res, a, b, meandepth.mm);     /* recalc! */
157         MERGE_MAX(res, a, b, duration.seconds);
158         MERGE_MAX(res, a, b, surfacetime.seconds);
159         MERGE_MAX(res, a, b, airtemp.mkelvin);
160         MERGE_MIN(res, a, b, watertemp.mkelvin);
161         MERGE_MAX(res, a, b, beginning_pressure.mbar);
162         MERGE_MAX(res, a, b, end_pressure.mbar);
163         for (i = 0; i < MAX_MIXES; i++) {
164                 if (a->gasmix[i].o2.permille) {
165                         res->gasmix[i] = a->gasmix[i];
166                         continue;
167                 }
168                 res->gasmix[i] = b->gasmix[i];
169         }
170         return merge_samples(res, a, b, 0);
171 }
172
173 /*
174  * This doesn't really report anything at all. We just sort the
175  * dives, the GUI does the reporting
176  */
177 static void report_dives(void)
178 {
179         int i;
180
181         qsort(dive_table.dives, dive_table.nr, sizeof(struct dive *), sortfn);
182
183         for (i = 1; i < dive_table.nr; i++) {
184                 struct dive **pp = &dive_table.dives[i-1];
185                 struct dive *prev = pp[0];
186                 struct dive *dive = pp[1];
187                 struct dive *merged;
188
189                 if (prev->when + prev->duration.seconds < dive->when)
190                         continue;
191
192                 merged = try_to_merge(prev, dive);
193                 if (!merged)
194                         continue;
195
196                 free(prev);
197                 free(dive);
198                 *pp = merged;
199                 dive_table.nr--;
200                 memmove(pp+1, pp+2, sizeof(*pp)*(dive_table.nr - i));
201
202                 /* Redo the new 'i'th dive */
203                 i--;
204         }
205 }
206
207 static void parse_argument(const char *arg)
208 {
209         const char *p = arg+1;
210
211         do {
212                 switch (*p) {
213                 case 'v':
214                         verbose++;
215                         continue;
216                 default:
217                         fprintf(stderr, "Bad argument '%s'\n", arg);
218                         exit(1);
219                 }
220         } while (*++p);
221 }
222
223 static void on_destroy(GtkWidget* w, gpointer data)
224 {
225         gtk_main_quit();
226 }
227
228 static GtkWidget *dive_profile;
229
230 void repaint_dive(void)
231 {
232         update_dive_info(current_dive);
233         gtk_widget_queue_draw(dive_profile);
234 }
235
236 static char *existing_filename;
237
238 static void file_open(GtkWidget *w, gpointer data)
239 {
240         GtkWidget *dialog;
241         dialog = gtk_file_chooser_dialog_new("Open File",
242                 GTK_WINDOW(main_window),
243                 GTK_FILE_CHOOSER_ACTION_OPEN,
244                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
245                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
246                 NULL);
247
248         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
249                 char *filename;
250                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
251                 printf("Open: '%s'\n", filename);
252                 g_free(filename);
253         }
254         gtk_widget_destroy(dialog);
255 }
256
257 static void file_save(GtkWidget *w, gpointer data)
258 {
259         GtkWidget *dialog;
260         dialog = gtk_file_chooser_dialog_new("Save File",
261                 GTK_WINDOW(main_window),
262                 GTK_FILE_CHOOSER_ACTION_SAVE,
263                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
264                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
265                 NULL);
266         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
267         if (!existing_filename) {
268                 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
269         } else
270                 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), existing_filename);
271
272         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
273                 char *filename;
274                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
275                 save_dives(filename);
276                 g_free(filename);
277         }
278         gtk_widget_destroy(dialog);
279 }
280
281 static GtkItemFactoryEntry menu_items[] = {
282         { "/_File",             NULL,           NULL,           0, "<Branch>" },
283         { "/File/_Open",        "<control>O",   file_open,      0, "<StockItem>", GTK_STOCK_OPEN },
284         { "/File/_Save",        "<control>S",   file_save,      0, "<StockItem>", GTK_STOCK_SAVE },
285 };
286 static gint nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]);
287
288 /* This is just directly from the gtk menubar tutorial. */
289 static GtkWidget *get_menubar_menu(GtkWidget *window)
290 {
291         GtkItemFactory *item_factory;
292         GtkAccelGroup *accel_group;
293
294         accel_group = gtk_accel_group_new();
295         item_factory = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<main>", accel_group);
296
297         gtk_item_factory_create_items(item_factory, nmenu_items, menu_items, NULL);
298         gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
299         return gtk_item_factory_get_widget(item_factory, "<main>");
300 }
301
302 int main(int argc, char **argv)
303 {
304         int i;
305         GtkWidget *win;
306         GtkWidget *divelist;
307         GtkWidget *table;
308         GtkWidget *notebook;
309         GtkWidget *frame;
310         GtkWidget *menubar;
311         GtkWidget *vbox;
312
313         parse_xml_init();
314
315         gtk_init(&argc, &argv);
316
317         for (i = 1; i < argc; i++) {
318                 const char *a = argv[i];
319
320                 if (a[0] == '-') {
321                         parse_argument(a);
322                         continue;
323                 }
324                 parse_xml_file(a);
325         }
326
327         report_dives();
328
329         win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
330         g_signal_connect(G_OBJECT(win), "destroy",      G_CALLBACK(on_destroy), NULL);
331         main_window = win;
332
333         vbox = gtk_vbox_new(FALSE, 0);
334         gtk_container_add(GTK_CONTAINER(win), vbox);
335
336         menubar = get_menubar_menu(win);
337         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
338
339         /* Table for the list of dives, cairo window, and dive info */
340         table = gtk_table_new(2, 2, FALSE);
341         gtk_container_set_border_width(GTK_CONTAINER(table), 5);
342         gtk_box_pack_end(GTK_BOX(vbox), table, TRUE, TRUE, 0);
343         gtk_widget_show(table);
344
345         /* Create the atual divelist */
346         divelist = create_dive_list();
347         gtk_table_attach(GTK_TABLE(table), divelist, 0, 1, 0, 2,
348                 0, GTK_FILL | GTK_SHRINK | GTK_EXPAND, 0, 0);
349
350         /* Frame for minimal dive info */
351         frame = dive_info_frame();
352         gtk_table_attach(GTK_TABLE(table), frame, 1, 2, 0, 1,
353                  GTK_FILL | GTK_SHRINK | GTK_EXPAND, 0, 0, 0);
354
355         /* Notebook for dive info vs profile vs .. */
356         notebook = gtk_notebook_new();
357         gtk_table_attach_defaults(GTK_TABLE(table), notebook, 1, 2, 1, 2);
358
359         /* Frame for dive profile */
360         frame = dive_profile_frame();
361         gtk_notebook_append_page(GTK_NOTEBOOK(notebook), frame, gtk_label_new("Dive Profile"));
362         dive_profile = frame;
363
364         /* Frame for extended dive info */
365         frame = extended_dive_info_frame();
366         gtk_notebook_append_page(GTK_NOTEBOOK(notebook), frame, gtk_label_new("Extended dive Info"));
367
368         gtk_widget_set_app_paintable(win, TRUE);
369         gtk_widget_show_all(win);
370
371         gtk_main();
372         return 0;
373 }