]> git.tdb.fi Git - ext/subsurface.git/blob - divelist.c
Calculate OTUs for every dive
[ext/subsurface.git] / divelist.c
1 /* divelist.c */
2 /* this creates the UI for the dive list -
3  * controlled through the following interfaces:
4  *
5  * void flush_divelist(struct dive *dive)
6  * GtkWidget dive_list_create(void)
7  * void dive_list_update_dives(void)
8  * void update_dive_list_units(void)
9  * void set_divelist_font(const char *font)
10  * void mark_divelist_changed(int changed)
11  * int unsaved_changes()
12  */
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <time.h>
17 #include <math.h>
18
19 #include "divelist.h"
20 #include "dive.h"
21 #include "display.h"
22 #include "display-gtk.h"
23
24 struct DiveList {
25         GtkWidget    *tree_view;
26         GtkWidget    *container_widget;
27         GtkListStore *model;
28         GtkTreeViewColumn *date, *depth, *duration, *location;
29         GtkTreeViewColumn *temperature, *cylinder, *nitrox, *sac;
30         int changed;
31 };
32
33 static struct DiveList dive_list;
34
35 /*
36  * The dive list has the dive data in both string format (for showing)
37  * and in "raw" format (for sorting purposes)
38  */
39 enum {
40         DIVE_INDEX = 0,
41         DIVE_DATE,              /* time_t: dive->when */
42         DIVE_DEPTH,             /* int: dive->maxdepth in mm */
43         DIVE_DURATION,          /* int: in seconds */
44         DIVE_TEMPERATURE,       /* int: in mkelvin */
45         DIVE_CYLINDER,
46         DIVE_NITROX,            /* int: in permille */
47         DIVE_SAC,               /* int: in ml/min */
48         DIVE_LOCATION,          /* "2nd Cathedral, Lanai" */
49         DIVELIST_COLUMNS
50 };
51
52 static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model)
53 {
54         GtkTreeIter iter;
55         GValue value = {0, };
56
57         if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
58                 return;
59
60         gtk_tree_model_get_value(model, &iter, DIVE_INDEX, &value);
61         selected_dive = g_value_get_int(&value);
62         repaint_dive();
63 }
64
65 static void date_data_func(GtkTreeViewColumn *col,
66                            GtkCellRenderer *renderer,
67                            GtkTreeModel *model,
68                            GtkTreeIter *iter,
69                            gpointer data)
70 {
71         int val;
72         struct tm *tm;
73         time_t when;
74         char buffer[40];
75
76         gtk_tree_model_get(model, iter, DIVE_DATE, &val, -1);
77
78         /* 2038 problem */
79         when = val;
80
81         tm = gmtime(&when);
82         snprintf(buffer, sizeof(buffer),
83                 "%s, %s %d, %d %02d:%02d",
84                 weekday(tm->tm_wday),
85                 monthname(tm->tm_mon),
86                 tm->tm_mday, tm->tm_year + 1900,
87                 tm->tm_hour, tm->tm_min);
88         g_object_set(renderer, "text", buffer, NULL);
89 }
90
91 static void depth_data_func(GtkTreeViewColumn *col,
92                             GtkCellRenderer *renderer,
93                             GtkTreeModel *model,
94                             GtkTreeIter *iter,
95                             gpointer data)
96 {
97         int depth, integer, frac, len;
98         char buffer[40];
99
100         gtk_tree_model_get(model, iter, DIVE_DEPTH, &depth, -1);
101
102         switch (output_units.length) {
103         case METERS:
104                 /* To tenths of meters */
105                 depth = (depth + 49) / 100;
106                 integer = depth / 10;
107                 frac = depth % 10;
108                 if (integer < 20)
109                         break;
110                 frac = -1;
111                 /* Rounding? */
112                 break;
113         case FEET:
114                 integer = mm_to_feet(depth) + 0.5;
115                 frac = -1;
116                 break;
117         default:
118                 return;
119         }
120         len = snprintf(buffer, sizeof(buffer), "%d", integer);
121         if (frac >= 0)
122                 len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
123
124         g_object_set(renderer, "text", buffer, NULL);
125 }
126
127 static void duration_data_func(GtkTreeViewColumn *col,
128                                GtkCellRenderer *renderer,
129                                GtkTreeModel *model,
130                                GtkTreeIter *iter,
131                                gpointer data)
132 {
133         unsigned int sec;
134         char buffer[16];
135
136         gtk_tree_model_get(model, iter, DIVE_DURATION, &sec, -1);
137         snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
138
139         g_object_set(renderer, "text", buffer, NULL);
140 }
141
142 static void temperature_data_func(GtkTreeViewColumn *col,
143                                   GtkCellRenderer *renderer,
144                                   GtkTreeModel *model,
145                                   GtkTreeIter *iter,
146                                   gpointer data)
147 {
148         int value;
149         char buffer[80];
150
151         gtk_tree_model_get(model, iter, DIVE_TEMPERATURE, &value, -1);
152
153         *buffer = 0;
154         if (value) {
155                 double deg;
156                 switch (output_units.temperature) {
157                 case CELSIUS:
158                         deg = mkelvin_to_C(value);
159                         break;
160                 case FAHRENHEIT:
161                         deg = mkelvin_to_F(value);
162                         break;
163                 default:
164                         return;
165                 }
166                 snprintf(buffer, sizeof(buffer), "%.1f", deg);
167         }
168
169         g_object_set(renderer, "text", buffer, NULL);
170 }
171
172 static void nitrox_data_func(GtkTreeViewColumn *col,
173                              GtkCellRenderer *renderer,
174                              GtkTreeModel *model,
175                              GtkTreeIter *iter,
176                              gpointer data)
177 {
178         int value;
179         char buffer[80];
180
181         gtk_tree_model_get(model, iter, DIVE_NITROX, &value, -1);
182
183         if (value)
184                 snprintf(buffer, sizeof(buffer), "%.1f", value/10.0);
185         else
186                 strcpy(buffer, "air");
187
188         g_object_set(renderer, "text", buffer, NULL);
189 }
190
191 /* Render the SAC data (integer value of "ml / min") */
192 static void sac_data_func(GtkTreeViewColumn *col,
193                           GtkCellRenderer *renderer,
194                           GtkTreeModel *model,
195                           GtkTreeIter *iter,
196                           gpointer data)
197 {
198         int value;
199         const double liters_per_cuft = 28.317;
200         const char *fmt;
201         char buffer[16];
202         double sac;
203
204         gtk_tree_model_get(model, iter, DIVE_SAC, &value, -1);
205
206         if (!value) {
207                 g_object_set(renderer, "text", "", NULL);
208                 return;
209         }
210
211         sac = value / 1000.0;
212         switch (output_units.volume) {
213         case LITER:
214                 fmt = "%4.1f";
215                 break;
216         case CUFT:
217                 fmt = "%4.2f";
218                 sac /= liters_per_cuft;
219                 break;
220         }
221         snprintf(buffer, sizeof(buffer), fmt, sac);
222
223         g_object_set(renderer, "text", buffer, NULL);
224 }
225
226 /* calculate OTU for a dive */
227 static double calculate_otu(struct dive *dive)
228 {
229         int i;
230         double otu = 0.0;
231
232         for (i = 1; i < dive->samples; i++) {
233                 int t;
234                 double po2;
235                 struct sample *sample = dive->sample + i;
236                 struct sample *psample = sample - 1;
237                 t = sample->time.seconds - psample->time.seconds;
238                 po2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille / 1000.0 *
239                         (sample->depth.mm + 10000) / 10000.0;
240                 if (po2 >= 0.5)
241                         otu += pow(po2 - 0.5, 0.83) * t / 30.0;
242         }
243         return otu;
244 }
245 /*
246  * Return air usage (in liters).
247  */
248 static double calculate_airuse(struct dive *dive)
249 {
250         double airuse = 0;
251         int i;
252
253         for (i = 0; i < MAX_CYLINDERS; i++) {
254                 cylinder_t *cyl = dive->cylinder + i;
255                 int size = cyl->type.size.mliter;
256                 double kilo_atm;
257
258                 if (!size)
259                         continue;
260
261                 kilo_atm = (cyl->start.mbar - cyl->end.mbar) / 1013250.0;
262
263                 /* Liters of air at 1 atm == milliliters at 1k atm*/
264                 airuse += kilo_atm * size;
265         }
266         return airuse;
267 }
268
269 static void get_sac(struct dive *dive, int *val)
270 {
271         double airuse, pressure, sac;
272
273         *val = 0;
274         airuse = calculate_airuse(dive);
275         if (!airuse)
276                 return;
277         if (!dive->duration.seconds)
278                 return;
279
280         /* Mean pressure in atm: 1 atm per 10m */
281         pressure = 1 + (dive->meandepth.mm / 10000.0);
282         sac = airuse / pressure * 60 / dive->duration.seconds;
283
284         /* milliliters per minute.. */
285         *val = sac * 1000;
286 }
287
288 static void get_string(char **str, const char *s)
289 {
290         int len;
291         char *n;
292
293         if (!s)
294                 s = "";
295         len = strlen(s);
296         if (len > 40)
297                 len = 40;
298         n = malloc(len+1);
299         memcpy(n, s, len);
300         n[len] = 0;
301         *str = n;
302 }
303
304 static void get_location(struct dive *dive, char **str)
305 {
306         get_string(str, dive->location);
307 }
308
309 static void get_cylinder(struct dive *dive, char **str)
310 {
311         get_string(str, dive->cylinder[0].type.description);
312 }
313
314 static void fill_one_dive(struct dive *dive,
315                           GtkTreeModel *model,
316                           GtkTreeIter *iter)
317 {
318         int sac;
319         char *location, *cylinder;
320
321         get_cylinder(dive, &cylinder);
322         get_location(dive, &location);
323         get_sac(dive, &sac);
324
325         /*
326          * We only set the fields that changed: the strings.
327          * The core data itself is unaffected by units
328          */
329         gtk_list_store_set(GTK_LIST_STORE(model), iter,
330                 DIVE_LOCATION, location,
331                 DIVE_CYLINDER, cylinder,
332                 DIVE_SAC, sac,
333                 -1);
334 }
335
336 static gboolean set_one_dive(GtkTreeModel *model,
337                              GtkTreePath *path,
338                              GtkTreeIter *iter,
339                              gpointer data)
340 {
341         GValue value = {0, };
342         struct dive *dive;
343
344         /* Get the dive number */
345         gtk_tree_model_get_value(model, iter, DIVE_INDEX, &value);
346         dive = get_dive(g_value_get_int(&value));
347         if (!dive)
348                 return TRUE;
349         if (data && dive != data)
350                 return FALSE;
351
352         fill_one_dive(dive, model, iter);
353         return dive == data;
354 }
355
356 void flush_divelist(struct dive *dive)
357 {
358         GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
359
360         gtk_tree_model_foreach(model, set_one_dive, dive);
361 }
362
363 void set_divelist_font(const char *font)
364 {
365         PangoFontDescription *font_desc = pango_font_description_from_string(font);
366         gtk_widget_modify_font(dive_list.tree_view, font_desc);
367         pango_font_description_free(font_desc);
368 }
369
370 void update_dive_list_units(void)
371 {
372         const char *unit;
373         GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
374
375         switch (output_units.length) {
376         case METERS:
377                 unit = "m";
378                 break;
379         case FEET:
380                 unit = "ft";
381                 break;
382         }
383         gtk_tree_view_column_set_title(dive_list.depth, unit);
384
385         switch (output_units.temperature) {
386         case CELSIUS:
387                 unit = UTF8_DEGREE "C";
388                 break;
389         case FAHRENHEIT:
390                 unit = UTF8_DEGREE "F";
391                 break;
392         case KELVIN:
393                 unit = "Kelvin";
394                 break;
395         }
396         gtk_tree_view_column_set_title(dive_list.temperature, unit);
397
398         gtk_tree_model_foreach(model, set_one_dive, NULL);
399 }
400
401 static void fill_dive_list(void)
402 {
403         int i;
404         GtkTreeIter iter;
405         GtkListStore *store;
406
407         store = GTK_LIST_STORE(dive_list.model);
408
409         for (i = 0; i < dive_table.nr; i++) {
410                 struct dive *dive = dive_table.dives[i];
411
412                 dive->otu = calculate_otu(dive);
413                 gtk_list_store_append(store, &iter);
414                 gtk_list_store_set(store, &iter,
415                         DIVE_INDEX, i,
416                         DIVE_DATE, dive->when,
417                         DIVE_DEPTH, dive->maxdepth,
418                         DIVE_DURATION, dive->duration.seconds,
419                         DIVE_LOCATION, "location",
420                         DIVE_TEMPERATURE, dive->watertemp.mkelvin,
421                         DIVE_NITROX, dive->cylinder[0].gasmix.o2,
422                         DIVE_SAC, 0,
423                         -1);
424         }
425
426         update_dive_list_units();
427         if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
428                 GtkTreeSelection *selection;
429                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
430                 gtk_tree_selection_select_iter(selection, &iter);
431         }
432 }
433
434 void dive_list_update_dives(void)
435 {
436         gtk_list_store_clear(GTK_LIST_STORE(dive_list.model));
437         fill_dive_list();
438         repaint_dive();
439 }
440
441 typedef void (*data_func_t)(GtkTreeViewColumn *col,
442                             GtkCellRenderer *renderer,
443                             GtkTreeModel *model,
444                             GtkTreeIter *iter,
445                             gpointer data);
446
447 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, int index, const char *title,
448                                         data_func_t data_func, PangoAlignment align)
449 {
450         GtkCellRenderer *renderer;
451         GtkTreeViewColumn *col;
452         double xalign = 0.0; /* left as default */
453
454         renderer = gtk_cell_renderer_text_new();
455         col = gtk_tree_view_column_new();
456
457         gtk_tree_view_column_set_title(col, title);
458         gtk_tree_view_column_set_sort_column_id(col, index);
459         gtk_tree_view_column_set_resizable(col, TRUE);
460         gtk_tree_view_column_pack_start(col, renderer, TRUE);
461         if (data_func)
462                 gtk_tree_view_column_set_cell_data_func(col, renderer, data_func, NULL, NULL);
463         else
464                 gtk_tree_view_column_add_attribute(col, renderer, "text", index);
465         gtk_object_set(GTK_OBJECT(renderer), "alignment", align, NULL);
466         switch (align) {
467         case PANGO_ALIGN_LEFT:
468                 xalign = 0.0;
469                 break;
470         case PANGO_ALIGN_CENTER:
471                 xalign = 0.5;
472                 break;
473         case PANGO_ALIGN_RIGHT:
474                 xalign = 1.0;
475                 break;
476         }
477         gtk_cell_renderer_set_alignment(GTK_CELL_RENDERER(renderer), xalign, 0.5);
478         gtk_tree_view_append_column(GTK_TREE_VIEW(dl->tree_view), col);
479         return col;
480 }
481
482 /*
483  * This is some crazy crap. The only way to get default focus seems
484  * to be to grab focus as the widget is being shown the first time.
485  */
486 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
487 {
488         gtk_widget_grab_focus(tree_view);
489 }
490
491 GtkWidget *dive_list_create(void)
492 {
493         GtkTreeSelection  *selection;
494
495         dive_list.model = gtk_list_store_new(DIVELIST_COLUMNS,
496                                 G_TYPE_INT,                     /* index */
497                                 G_TYPE_INT,                     /* Date */
498                                 G_TYPE_INT,                     /* Depth */
499                                 G_TYPE_INT,                     /* Duration */
500                                 G_TYPE_INT,                     /* Temperature */
501                                 G_TYPE_STRING,                  /* Cylinder */
502                                 G_TYPE_INT,                     /* Nitrox */
503                                 G_TYPE_INT,                     /* SAC */
504                                 G_TYPE_STRING                   /* Location */
505                                 );
506         dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
507         set_divelist_font(divelist_font);
508
509         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
510
511         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
512         gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
513
514         dive_list.date = divelist_column(&dive_list, DIVE_DATE, "Date", date_data_func, PANGO_ALIGN_LEFT);
515         dive_list.depth = divelist_column(&dive_list, DIVE_DEPTH, "ft", depth_data_func, PANGO_ALIGN_RIGHT);
516         dive_list.duration = divelist_column(&dive_list, DIVE_DURATION, "min", duration_data_func, PANGO_ALIGN_RIGHT);
517         dive_list.temperature = divelist_column(&dive_list, DIVE_TEMPERATURE, UTF8_DEGREE "F", temperature_data_func, PANGO_ALIGN_RIGHT);
518         dive_list.cylinder = divelist_column(&dive_list, DIVE_CYLINDER, "Cyl", NULL, PANGO_ALIGN_CENTER);
519         dive_list.nitrox = divelist_column(&dive_list, DIVE_NITROX, "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, PANGO_ALIGN_CENTER);
520         dive_list.sac = divelist_column(&dive_list, DIVE_SAC, "SAC", sac_data_func, PANGO_ALIGN_CENTER);
521         dive_list.location = divelist_column(&dive_list, DIVE_LOCATION, "Location", NULL, PANGO_ALIGN_LEFT);
522
523         fill_dive_list();
524
525         g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
526                                           "search-column", DIVE_LOCATION,
527                                           "rules-hint", TRUE,
528                                           NULL);
529
530         g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
531         g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), dive_list.model);
532
533         dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
534         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
535                                GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
536         gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
537
538         dive_list.changed = 0;
539
540         return dive_list.container_widget;
541 }
542
543 void mark_divelist_changed(int changed)
544 {
545         dive_list.changed = changed;
546 }
547
548 int unsaved_changes()
549 {
550         return dive_list.changed;
551 }