]> git.tdb.fi Git - ext/subsurface.git/blob - equipment.c
Start re-organizing the cylinder entry in equipment.c
[ext/subsurface.git] / equipment.c
1 /* equipment.c */
2 /* creates the UI for the equipment page -
3  * controlled through the following interfaces:
4  *
5  * void show_dive_equipment(struct dive *dive)
6  * void flush_dive_equipment_changes(struct dive *dive)
7  *
8  * called from gtk-ui:
9  * GtkWidget *equipment_widget(void)
10  */
11 #include <stdio.h>
12 #include <string.h>
13 #include <stdlib.h>
14 #include <stdarg.h>
15 #include <time.h>
16
17 #include "dive.h"
18 #include "display.h"
19 #include "display-gtk.h"
20 #include "divelist.h"
21
22 GtkListStore *cylinder_model;
23
24 enum {
25         CYL_INDEX,
26         CYL_DESC,
27         CYL_SIZE,
28         CYL_WORKP,
29         CYL_STARTP,
30         CYL_ENDP,
31         CYL_O2,
32         CYL_HE,
33         CYL_COLUMNS
34 };
35
36 static struct {
37         GtkListStore *model;
38         GtkWidget *tree_view;
39         GtkWidget *edit, *add, *del;
40         GtkTreeViewColumn *desc, *size, *workp, *startp, *endp, *o2, *he;
41 } cylinder_list;
42
43 struct cylinder_widget {
44         int index, changed;
45         const char *name;
46         GtkWidget *hbox;
47         GtkComboBox *description;
48         GtkSpinButton *size, *pressure;
49         GtkWidget *o2, *gasmix_button;
50 };
51
52 static struct cylinder_widget gtk_cylinder[MAX_CYLINDERS];
53
54 static int convert_pressure(int mbar, double *p)
55 {
56         int decimals = 1;
57         double pressure;
58
59         pressure = mbar / 1000.0;
60         if (mbar) {
61                 if (output_units.pressure == PSI) {
62                         pressure *= 14.5037738; /* Bar to PSI */
63                         decimals = 0;
64                 }
65         }
66         *p = pressure;
67         return decimals;
68 }
69
70 static int convert_volume_pressure(int ml, int mbar, double *v, double *p)
71 {
72         int decimals = 1;
73         double volume, pressure;
74
75         volume = ml / 1000.0;
76         pressure = mbar / 1000.0;
77         if (mbar) {
78                 if (output_units.volume == CUFT) {
79                         volume /= 28.3168466;   /* Liters to cuft */
80                         volume *= pressure / 1.01325;
81                 }
82                 if (output_units.pressure == PSI) {
83                         pressure *= 14.5037738; /* Bar to PSI */
84                         decimals = 0;
85                 }
86         }
87         *v = volume;
88         *p = pressure;
89         return decimals;
90 }
91
92 static void set_cylinder_spinbuttons(struct cylinder_widget *cylinder, int ml, int mbar)
93 {
94         double volume, pressure;
95
96         convert_volume_pressure(ml, mbar, &volume, &pressure);
97         gtk_spin_button_set_value(cylinder->size, volume);
98         gtk_spin_button_set_value(cylinder->pressure, pressure);
99 }
100
101 static void cylinder_cb(GtkComboBox *combo_box, gpointer data)
102 {
103         GtkTreeIter iter;
104         GtkTreeModel *model = gtk_combo_box_get_model(combo_box);
105         GValue value1 = {0, }, value2 = {0,};
106         struct cylinder_widget *cylinder = data;
107         cylinder_t *cyl = current_dive->cylinder + cylinder->index;
108
109         /* Did the user set it to some non-standard value? */
110         if (!gtk_combo_box_get_active_iter(combo_box, &iter)) {
111                 cylinder->changed = 1;
112                 return;
113         }
114
115         /*
116          * We get "change" signal callbacks just because we set
117          * the description by hand. Whatever. So ignore them if
118          * they are no-ops.
119          */
120         if (!cylinder->changed && cyl->type.description) {
121                 int same;
122                 char *desc = gtk_combo_box_get_active_text(combo_box);
123
124                 same = !strcmp(desc, cyl->type.description);
125                 g_free(desc);
126                 if (same)
127                         return;
128         }
129         cylinder->changed = 1;
130
131         gtk_tree_model_get_value(model, &iter, 1, &value1);
132         gtk_tree_model_get_value(model, &iter, 2, &value2);
133
134         set_cylinder_spinbuttons(cylinder, g_value_get_int(&value1), g_value_get_int(&value2));
135 }
136
137 /*
138  * The gtk_tree_model_foreach() interface is bad. It could have
139  * returned whether the callback ever returned true
140  */
141 static int found_match = 0;
142
143 static gboolean match_cylinder(GtkTreeModel *model,
144                                 GtkTreePath *path,
145                                 GtkTreeIter *iter,
146                                 gpointer data)
147 {
148         const char *name;
149         struct cylinder_widget *cylinder = data;
150         GValue value = {0, };
151
152         gtk_tree_model_get_value(model, iter, 0, &value);
153         name = g_value_get_string(&value);
154         if (strcmp(cylinder->name, name))
155                 return FALSE;
156         gtk_combo_box_set_active_iter(cylinder->description, iter);
157         found_match = 1;
158         return TRUE;
159 }
160
161 static void add_cylinder(struct cylinder_widget *cylinder, const char *desc, int ml, int mbar)
162 {
163         GtkTreeModel *model;
164
165         found_match = 0;
166         model = gtk_combo_box_get_model(cylinder->description);
167         cylinder->name = desc;
168         gtk_tree_model_foreach(model, match_cylinder, cylinder);
169
170         if (!found_match) {
171                 GtkListStore *store = GTK_LIST_STORE(model);
172                 GtkTreeIter iter;
173
174                 gtk_list_store_append(store, &iter);
175                 gtk_list_store_set(store, &iter,
176                         0, desc,
177                         1, ml,
178                         2, mbar,
179                         -1);
180                 gtk_combo_box_set_active_iter(cylinder->description, &iter);
181         }
182 }
183
184 static void show_cylinder(cylinder_t *cyl, struct cylinder_widget *cylinder)
185 {
186         const char *desc;
187         int ml, mbar;
188         double o2;
189
190         /* Don't show uninitialized cylinder widgets */
191         if (!cylinder->description)
192                 return;
193
194         desc = cyl->type.description;
195         if (!desc)
196                 desc = "";
197         ml = cyl->type.size.mliter;
198         mbar = cyl->type.workingpressure.mbar;
199         add_cylinder(cylinder, desc, ml, mbar);
200
201         set_cylinder_spinbuttons(cylinder, cyl->type.size.mliter, cyl->type.workingpressure.mbar);
202         o2 = cyl->gasmix.o2.permille / 10.0;
203         gtk_widget_set_sensitive(cylinder->o2, !!o2);
204         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cylinder->gasmix_button), !!o2);
205         if (!o2)
206                 o2 = 21.0;
207         gtk_spin_button_set_value(GTK_SPIN_BUTTON(cylinder->o2), o2);
208 }
209
210 static int cyl_nothing(cylinder_t *cyl)
211 {
212         return  !cyl->type.size.mliter &&
213                 !cyl->type.workingpressure.mbar &&
214                 !cyl->type.description &&
215                 !cyl->gasmix.o2.permille &&
216                 !cyl->gasmix.he.permille &&
217                 !cyl->start.mbar &&
218                 !cyl->end.mbar;
219 }
220
221 void show_dive_equipment(struct dive *dive)
222 {
223         int i, max;
224         GtkTreeIter iter;
225         GtkListStore *model;
226
227         model = cylinder_list.model;
228         gtk_list_store_clear(model);
229         max = MAX_CYLINDERS;
230         do {
231                 cylinder_t *cyl = &dive->cylinder[max-1];
232
233                 if (!cyl_nothing(cyl))
234                         break;
235         } while (--max);
236
237         for (i = 0; i < max; i++) {
238                 cylinder_t *cyl = dive->cylinder+i;
239
240                 gtk_list_store_append(model, &iter);
241                 gtk_list_store_set(model, &iter,
242                         CYL_INDEX, i,
243                         CYL_DESC, cyl->type.description ? : "",
244                         CYL_SIZE, cyl->type.size.mliter,
245                         CYL_WORKP, cyl->type.workingpressure.mbar,
246                         CYL_STARTP, cyl->start.mbar,
247                         CYL_ENDP, cyl->end.mbar,
248                         CYL_O2, cyl->gasmix.o2.permille,
249                         CYL_HE, cyl->gasmix.he.permille,
250                         -1);
251         }
252 }
253
254 static GtkWidget *create_spinbutton(GtkWidget *vbox, const char *name, double min, double max, double incr)
255 {
256         GtkWidget *frame, *hbox, *button;
257
258         frame = gtk_frame_new(name);
259         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, TRUE, 0);
260
261         hbox = gtk_hbox_new(FALSE, 3);
262         gtk_container_add(GTK_CONTAINER(frame), hbox);
263
264         button = gtk_spin_button_new_with_range(min, max, incr);
265         gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, TRUE, 0);
266
267         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID);
268
269         return button;
270 }
271
272 static void fill_cylinder_info(struct cylinder_widget *cylinder, cylinder_t *cyl, const char *desc, double volume, double pressure, int o2)
273 {
274         int mbar, ml;
275
276         if (output_units.pressure == PSI)
277                 pressure /= 14.5037738;
278
279         if (pressure && output_units.volume == CUFT) {
280                 volume *= 28.3168466;   /* CUFT to liter */
281                 volume /= pressure / 1.01325;
282         }
283
284         ml = volume * 1000 + 0.5;
285         mbar = pressure * 1000 + 0.5;
286
287         if (o2 < 211)
288                 o2 = 0;
289         cyl->type.description = desc;
290         cyl->type.size.mliter = ml;
291         cyl->type.workingpressure.mbar = mbar;
292         cyl->gasmix.o2.permille = o2;
293
294         /*
295          * Also, insert it into the model if it doesn't already exist
296          */
297         add_cylinder(cylinder, desc, ml, mbar);
298 }
299
300 static void record_cylinder_changes(cylinder_t *cyl, struct cylinder_widget *cylinder)
301 {
302         const gchar *desc;
303         GtkComboBox *box;
304         double volume, pressure;
305         int o2;
306
307         /* Ignore uninitialized cylinder widgets */
308         box = cylinder->description;
309         if (!box)
310                 return;
311
312         desc = gtk_combo_box_get_active_text(box);
313         volume = gtk_spin_button_get_value(cylinder->size);
314         pressure = gtk_spin_button_get_value(cylinder->pressure);
315         o2 = gtk_spin_button_get_value(GTK_SPIN_BUTTON(cylinder->o2))*10 + 0.5;
316         if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cylinder->gasmix_button)))
317                 o2 = 0;
318         fill_cylinder_info(cylinder, cyl, desc, volume, pressure, o2);
319 }
320
321 void flush_dive_equipment_changes(struct dive *dive)
322 {
323         /* We do nothing: we require the "Ok" button press */
324 }
325
326 static void apply_cb(GtkButton *button, gpointer data)
327 {
328         int i;
329         struct dive *dive = current_dive;
330
331         if (!dive)
332                 return;
333
334         for (i = 0; i < MAX_CYLINDERS; i++)
335                 record_cylinder_changes(dive->cylinder+i, gtk_cylinder+i);
336         mark_divelist_changed(TRUE);
337         flush_divelist(dive);
338 }
339
340 static void cancel_cb(GtkButton *button, gpointer data)
341 {
342         struct dive *dive = current_dive;
343
344         if (!dive)
345                 return;
346
347         show_dive_equipment(current_dive);
348 }
349
350 /*
351  * We hardcode the most common standard cylinders,
352  * we should pick up any other names from the dive
353  * logs directly.
354  */
355 static struct tank_info {
356         const char *name;
357         int size;       /* cuft if < 1000, otherwise mliter */
358         int psi;        /* If zero, size is in mliter */
359 } tank_info[100] = {
360         /* Need an empty entry for the no-cylinder case */
361         { "", 0, 0 },
362
363         /* Size-only metric cylinders */
364         { "10.0 l", 10000 },
365         { "11.1 l", 11100 },
366
367         /* Most common AL cylinders */
368         { "AL50",   50, 3000 },
369         { "AL63",   63, 3000 },
370         { "AL72",   72, 3000 },
371         { "AL80",   80, 3000 },
372         { "AL100", 100, 3300 },
373
374         /* Somewhat common LP steel cylinders */
375         { "LP85",   85, 2640 },
376         { "LP95",   95, 2640 },
377         { "LP108", 108, 2640 },
378         { "LP121", 121, 2640 },
379
380         /* Somewhat common HP steel cylinders */
381         { "HP65",   65, 3442 },
382         { "HP80",   80, 3442 },
383         { "HP100", 100, 3442 },
384         { "HP119", 119, 3442 },
385         { "HP130", 130, 3442 },
386
387         /* We'll fill in more from the dive log dynamically */
388         { NULL, }
389 };
390
391 static void fill_tank_list(GtkListStore *store)
392 {
393         GtkTreeIter iter;
394         struct tank_info *info = tank_info;
395
396         while (info->name) {
397                 int size = info->size;
398                 int psi = info->psi;
399                 int mbar = 0, ml = size;
400
401                 /* Is it in cuft and psi? */
402                 if (psi) {
403                         double bar = 0.0689475729 * psi;
404                         double airvolume = 28316.8466 * size;
405                         double atm = bar / 1.01325;
406
407                         ml = airvolume / atm + 0.5;
408                         mbar = bar*1000 + 0.5;
409                 }
410
411                 gtk_list_store_append(store, &iter);
412                 gtk_list_store_set(store, &iter,
413                         0, info->name,
414                         1, ml,
415                         2, mbar,
416                         -1);
417                 info++;
418         }
419 }
420
421 static void nitrox_cb(GtkToggleButton *button, gpointer data)
422 {
423         struct cylinder_widget *cylinder = data;
424         int state;
425
426         state = gtk_toggle_button_get_active(button);
427         gtk_widget_set_sensitive(cylinder->o2, state);
428 }
429
430 static void cylinder_widget(int nr, GtkListStore *model)
431 {
432         struct cylinder_widget *cylinder;
433         GtkWidget *frame, *hbox, *hbox2;
434         GtkWidget *widget;
435         char buffer[80];
436
437         cylinder = gtk_cylinder + nr;
438         cylinder->index = nr;
439
440         hbox = gtk_hbox_new(FALSE, 3);
441         cylinder->hbox = hbox;
442
443         snprintf(buffer, sizeof(buffer), "Cylinder %d", nr+1);
444         frame = gtk_frame_new(buffer);
445         gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 0);
446
447         hbox2 = gtk_hbox_new(FALSE, 3);
448         gtk_container_add(GTK_CONTAINER(frame), hbox2);
449
450         widget = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
451         gtk_box_pack_start(GTK_BOX(hbox2), widget, FALSE, TRUE, 0);
452
453         cylinder->description = GTK_COMBO_BOX(widget);
454         g_signal_connect(widget, "changed", G_CALLBACK(cylinder_cb), cylinder);
455
456         widget = create_spinbutton(hbox, "Size", 0, 300, 0.1);
457         cylinder->size = GTK_SPIN_BUTTON(widget);
458
459         widget = create_spinbutton(hbox, "Pressure", 0, 5000, 1);
460         cylinder->pressure = GTK_SPIN_BUTTON(widget);
461
462         widget = create_spinbutton(hbox, "Nitrox", 21, 100, 0.1);
463         cylinder->o2 = widget;
464         cylinder->gasmix_button = gtk_check_button_new();
465         gtk_box_pack_start(GTK_BOX(gtk_widget_get_parent(cylinder->o2)),
466                 cylinder->gasmix_button, FALSE, FALSE, 3);
467         g_signal_connect(cylinder->gasmix_button, "toggled", G_CALLBACK(nitrox_cb), cylinder);
468
469         gtk_spin_button_set_range(GTK_SPIN_BUTTON(cylinder->o2), 21.0, 100.0);
470 }
471
472 static void edit_cb(GtkButton *button, gpointer data)
473 {
474 }
475
476 static void add_cb(GtkButton *button, gpointer data)
477 {
478 }
479
480 static void del_cb(GtkButton *button, gpointer data)
481 {
482 }
483
484 static GtkListStore *create_tank_size_model(void)
485 {
486         GtkListStore *model;
487
488         model = gtk_list_store_new(3,
489                 G_TYPE_STRING,          /* Tank name */
490                 G_TYPE_INT,             /* Tank size in mliter */
491                 G_TYPE_INT,             /* Tank working pressure in mbar */
492                 -1);
493
494         fill_tank_list(model);
495         return model;
496 }
497
498 static void size_data_func(GtkTreeViewColumn *col,
499                            GtkCellRenderer *renderer,
500                            GtkTreeModel *model,
501                            GtkTreeIter *iter,
502                            gpointer data)
503 {
504         int ml, mbar;
505         double size, pressure;
506         char buffer[10];
507
508         gtk_tree_model_get(model, iter, CYL_SIZE, &ml, CYL_WORKP, &mbar, -1);
509         convert_volume_pressure(ml, mbar, &size, &pressure);
510         if (size)
511                 snprintf(buffer, sizeof(buffer), "%.1f", size);
512         else
513                 strcpy(buffer, "unkn");
514         g_object_set(renderer, "text", buffer, NULL);
515 }
516
517 static void pressure_data_func(GtkTreeViewColumn *col,
518                            GtkCellRenderer *renderer,
519                            GtkTreeModel *model,
520                            GtkTreeIter *iter,
521                            gpointer data)
522 {
523         int index = (long)data;
524         int mbar, decimals;
525         double pressure;
526         char buffer[10];
527
528         gtk_tree_model_get(model, iter, index, &mbar, -1);
529         decimals = convert_pressure(mbar, &pressure);
530         if (mbar)
531                 snprintf(buffer, sizeof(buffer), "%.*f", decimals, pressure);
532         else
533                 *buffer = 0;
534         g_object_set(renderer, "text", buffer, NULL);
535 }
536
537 static void percentage_data_func(GtkTreeViewColumn *col,
538                            GtkCellRenderer *renderer,
539                            GtkTreeModel *model,
540                            GtkTreeIter *iter,
541                            gpointer data)
542 {
543         int index = (long)data;
544         int permille;
545         char buffer[10];
546
547         gtk_tree_model_get(model, iter, index, &permille, -1);
548         if (permille)
549                 snprintf(buffer, sizeof(buffer), "%.1f%%", permille / 10.0);
550         else
551                 *buffer = 0;
552         g_object_set(renderer, "text", buffer, NULL);
553 }
554
555 static GtkWidget *cylinder_list_create(void)
556 {
557         GtkWidget *tree_view;
558         GtkListStore *model;
559
560         model = gtk_list_store_new(CYL_COLUMNS,
561                 G_TYPE_INT,             /* CYL_INDEX */
562                 G_TYPE_STRING,          /* CYL_DESC: utf8 */
563                 G_TYPE_INT,             /* CYL_SIZE: mliter */
564                 G_TYPE_INT,             /* CYL_WORKP: mbar */
565                 G_TYPE_INT,             /* CYL_STARTP: mbar */
566                 G_TYPE_INT,             /* CYL_ENDP: mbar */
567                 G_TYPE_INT,             /* CYL_O2: permille */
568                 G_TYPE_INT              /* CYL_HE: permille */
569                 );
570         cylinder_list.model = model;
571         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
572         cylinder_list.desc = tree_view_column(tree_view, CYL_DESC, "Type", NULL, PANGO_ALIGN_LEFT, TRUE);
573         cylinder_list.size = tree_view_column(tree_view, CYL_SIZE, "Size", size_data_func, PANGO_ALIGN_RIGHT, TRUE);
574         cylinder_list.workp = tree_view_column(tree_view, CYL_WORKP, "MaxPress", pressure_data_func, PANGO_ALIGN_RIGHT, TRUE);
575         cylinder_list.startp = tree_view_column(tree_view, CYL_STARTP, "Start", pressure_data_func, PANGO_ALIGN_RIGHT, TRUE);
576         cylinder_list.endp = tree_view_column(tree_view, CYL_ENDP, "End", pressure_data_func, PANGO_ALIGN_RIGHT, TRUE);
577         cylinder_list.o2 = tree_view_column(tree_view, CYL_O2, "O" UTF8_SUBSCRIPT_2 "%", percentage_data_func, PANGO_ALIGN_RIGHT, TRUE);
578         cylinder_list.he = tree_view_column(tree_view, CYL_HE, "He%", percentage_data_func, PANGO_ALIGN_RIGHT, TRUE);
579         return tree_view;
580 }
581
582 GtkWidget *equipment_widget(void)
583 {
584         GtkWidget *vbox, *hbox, *frame, *framebox;
585         GtkWidget *add, *del, *edit;
586
587         vbox = gtk_vbox_new(FALSE, 3);
588
589         /*
590          * We create the cylinder size model at startup, since
591          * we're going to share it across all cylinders and all
592          * dives. So if you add a new cylinder type in one dive,
593          * it will show up when you edit the cylinder types for
594          * another dive.
595          */
596         cylinder_model = create_tank_size_model();
597
598         cylinder_list.tree_view = cylinder_list_create();
599
600         hbox = gtk_hbox_new(FALSE, 3);
601         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
602
603         frame = gtk_frame_new("Cylinders");
604         gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, FALSE, 3);
605
606         framebox = gtk_vbox_new(FALSE, 3);
607         gtk_container_add(GTK_CONTAINER(frame), framebox);
608
609         hbox = gtk_hbox_new(FALSE, 3);
610         gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3);
611
612         gtk_box_pack_start(GTK_BOX(hbox), cylinder_list.tree_view, TRUE, FALSE, 3);
613
614         hbox = gtk_hbox_new(TRUE, 3);
615         gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3);
616
617         edit = gtk_button_new_from_stock(GTK_STOCK_EDIT);
618         add = gtk_button_new_from_stock(GTK_STOCK_ADD);
619         del = gtk_button_new_from_stock(GTK_STOCK_DELETE);
620         gtk_box_pack_start(GTK_BOX(hbox), edit, FALSE, FALSE, 0);
621         gtk_box_pack_start(GTK_BOX(hbox), add, FALSE, FALSE, 0);
622         gtk_box_pack_start(GTK_BOX(hbox), del, FALSE, FALSE, 0);
623
624         cylinder_list.edit = edit;
625         cylinder_list.add = add;
626         cylinder_list.del = del;
627
628         g_signal_connect(edit, "clicked", G_CALLBACK(edit_cb), NULL);
629         g_signal_connect(add, "clicked", G_CALLBACK(add_cb), NULL);
630         g_signal_connect(del, "clicked", G_CALLBACK(del_cb), NULL);
631
632         return vbox;
633 }