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