]> git.tdb.fi Git - ext/subsurface.git/blob - equipment.c
Fixed another memory leak
[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  *
7  * called from gtk-ui:
8  * GtkWidget *equipment_widget(void)
9  */
10 #include <stdio.h>
11 #include <string.h>
12 #include <stdlib.h>
13 #include <stdarg.h>
14 #include <time.h>
15
16 #include "dive.h"
17 #include "display.h"
18 #include "display-gtk.h"
19 #include "divelist.h"
20
21 static GtkListStore *cylinder_model, *weightsystem_model;
22
23 enum {
24         CYL_DESC,
25         CYL_SIZE,
26         CYL_WORKP,
27         CYL_STARTP,
28         CYL_ENDP,
29         CYL_O2,
30         CYL_HE,
31         CYL_COLUMNS
32 };
33
34 enum {
35         WS_DESC,
36         WS_WEIGHT,
37         WS_COLUMNS
38 };
39
40 struct equipment_list {
41         int max_index;
42         GtkListStore *model;
43         GtkWidget *edit, *add, *del;
44 };
45
46 static struct equipment_list cylinder_list, weightsystem_list;
47
48
49 struct cylinder_widget {
50         int index, changed;
51         const char *name;
52         GtkWidget *hbox;
53         GtkComboBox *description;
54         GtkSpinButton *size, *pressure;
55         GtkWidget *start, *end, *pressure_button;
56         GtkWidget *o2, *he, *gasmix_button;
57 };
58
59 struct ws_widget {
60         int index, changed;
61         const char *name;
62         GtkWidget *hbox;
63         GtkComboBox *description;
64         GtkSpinButton *weight;
65 };
66
67 /* we want bar - so let's not use our unit functions */
68 static int convert_pressure(int mbar, double *p)
69 {
70         int decimals = 1;
71         double pressure;
72
73         if (output_units.pressure == PSI) {
74                 pressure = mbar_to_PSI(mbar);
75                 decimals = 0;
76         } else {
77                 pressure = mbar / 1000.0;
78         }
79
80         *p = pressure;
81         return decimals;
82 }
83
84 static void convert_volume_pressure(int ml, int mbar, double *v, double *p)
85 {
86         double volume, pressure;
87
88         volume = ml / 1000.0;
89         if (mbar) {
90                 if (output_units.volume == CUFT) {
91                         volume = ml_to_cuft(ml);
92                         volume *= bar_to_atm(mbar / 1000.0);
93                 }
94
95                 if (output_units.pressure == PSI) {
96                         pressure = mbar_to_PSI(mbar);
97                 } else
98                         pressure = mbar / 1000.0;
99         }
100         *v = volume;
101         *p = pressure;
102 }
103
104 static int convert_weight(int grams, double *m)
105 {
106         int decimals = 1; /* not sure - do people do less than whole lbs/kg ? */
107         double weight;
108
109         if (output_units.weight == LBS)
110                 weight = grams_to_lbs(grams);
111         else
112                 weight = grams / 1000.0;
113         *m = weight;
114         return decimals;
115 }
116
117 static void set_cylinder_type_spinbuttons(struct cylinder_widget *cylinder, int ml, int mbar)
118 {
119         double volume, pressure;
120
121         convert_volume_pressure(ml, mbar, &volume, &pressure);
122         gtk_spin_button_set_value(cylinder->size, volume);
123         gtk_spin_button_set_value(cylinder->pressure, pressure);
124 }
125
126 static void set_cylinder_pressure_spinbuttons(struct cylinder_widget *cylinder, cylinder_t *cyl)
127 {
128         int set;
129         unsigned int start, end;
130         double pressure;
131
132         start = cyl->start.mbar;
133         end = cyl->end.mbar;
134         set = start || end;
135         if (!set) {
136                 start = cyl->sample_start.mbar;
137                 end = cyl->sample_end.mbar;
138         }
139         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cylinder->pressure_button), set);
140         gtk_widget_set_sensitive(cylinder->start, set);
141         gtk_widget_set_sensitive(cylinder->end, set);
142
143         convert_pressure(start, &pressure);
144         gtk_spin_button_set_value(GTK_SPIN_BUTTON(cylinder->start), pressure);
145         convert_pressure(end, &pressure);
146         gtk_spin_button_set_value(GTK_SPIN_BUTTON(cylinder->end), pressure);
147 }
148
149 static void set_weight_weight_spinbutton(struct ws_widget *ws_widget, int grams)
150 {
151         double weight;
152
153         convert_weight(grams, &weight);
154         gtk_spin_button_set_value(ws_widget->weight, weight);
155 }
156
157 /*
158  * The gtk_tree_model_foreach() interface is bad. It could have
159  * returned whether the callback ever returned true
160  */
161 static GtkTreeIter *found_match = NULL;
162 static GtkTreeIter match_iter;
163
164 static gboolean match_desc(GtkTreeModel *model, GtkTreePath *path,
165                         GtkTreeIter *iter, gpointer data)
166 {
167         int match;
168         gchar *name;
169         const char *desc = data;
170
171         gtk_tree_model_get(model, iter, 0, &name, -1);
172         match = !strcmp(desc, name);
173         g_free(name);
174         if (match) {
175                 match_iter = *iter;
176                 found_match = &match_iter;
177         }
178         return match;
179 }
180
181 static int get_active_item(GtkComboBox *combo_box, GtkTreeIter *iter, GtkListStore *model)
182 {
183         char *desc;
184
185         if (gtk_combo_box_get_active_iter(combo_box, iter))
186                 return TRUE;
187
188         desc = gtk_combo_box_get_active_text(combo_box);
189
190         found_match = NULL;
191         gtk_tree_model_foreach(GTK_TREE_MODEL(model), match_desc, (void *)desc);
192
193         g_free(desc);
194         if (!found_match)
195                 return FALSE;
196
197         *iter = *found_match;
198         gtk_combo_box_set_active_iter(combo_box, iter);
199         return TRUE;
200 }
201
202 static void cylinder_cb(GtkComboBox *combo_box, gpointer data)
203 {
204         GtkTreeIter iter;
205         GtkTreeModel *model = gtk_combo_box_get_model(combo_box);
206         int ml, mbar;
207         struct cylinder_widget *cylinder = data;
208         cylinder_t *cyl = current_dive->cylinder + cylinder->index;
209
210         /* Did the user set it to some non-standard value? */
211         if (!get_active_item(combo_box, &iter, cylinder_model)) {
212                 cylinder->changed = 1;
213                 return;
214         }
215
216         /*
217          * We get "change" signal callbacks just because we set
218          * the description by hand. Whatever. So ignore them if
219          * they are no-ops.
220          */
221         if (!cylinder->changed && cyl->type.description) {
222                 int same;
223                 char *desc = gtk_combo_box_get_active_text(combo_box);
224
225                 same = !strcmp(desc, cyl->type.description);
226                 g_free(desc);
227                 if (same)
228                         return;
229         }
230         cylinder->changed = 1;
231
232         gtk_tree_model_get(model, &iter,
233                 CYL_SIZE, &ml,
234                 CYL_WORKP, &mbar,
235                 -1);
236
237         set_cylinder_type_spinbuttons(cylinder, ml, mbar);
238 }
239
240 static void weight_cb(GtkComboBox *combo_box, gpointer data)
241 {
242         GtkTreeIter iter;
243         GtkTreeModel *model = gtk_combo_box_get_model(combo_box);
244         int weight;
245         struct ws_widget *ws_widget = data;
246         weightsystem_t *ws = current_dive->weightsystem + ws_widget->index;
247
248         /* Did the user set it to some non-standard value? */
249         if (!get_active_item(combo_box, &iter, weightsystem_model)) {
250                 ws_widget->changed = 1;
251                 return;
252         }
253
254         /*
255          * We get "change" signal callbacks just because we set
256          * the description by hand. Whatever. So ignore them if
257          * they are no-ops.
258          */
259         if (!ws_widget->changed && ws->description) {
260                 int same;
261                 char *desc = gtk_combo_box_get_active_text(combo_box);
262
263                 same = !strcmp(desc, ws->description);
264                 g_free(desc);
265                 if (same)
266                         return;
267         }
268         ws_widget->changed = 1;
269
270         gtk_tree_model_get(model, &iter,
271                 WS_WEIGHT, &weight,
272                 -1);
273
274         set_weight_weight_spinbutton(ws_widget, weight);
275 }
276
277 static GtkTreeIter *add_cylinder_type(const char *desc, int ml, int mbar, GtkTreeIter *iter)
278 {
279         GtkTreeModel *model;
280
281         /* Don't even bother adding stuff without a size */
282         if (!ml)
283                 return NULL;
284
285         found_match = NULL;
286         model = GTK_TREE_MODEL(cylinder_model);
287         gtk_tree_model_foreach(model, match_desc, (void *)desc);
288
289         if (!found_match) {
290                 GtkListStore *store = GTK_LIST_STORE(model);
291
292                 gtk_list_store_append(store, iter);
293                 gtk_list_store_set(store, iter,
294                         0, desc,
295                         1, ml,
296                         2, mbar,
297                         -1);
298                 return iter;
299         }
300         return found_match;
301 }
302
303 static GtkTreeIter *add_weightsystem_type(const char *desc, int weight, GtkTreeIter *iter)
304 {
305         GtkTreeModel *model;
306
307         found_match = NULL;
308         model = GTK_TREE_MODEL(weightsystem_model);
309         gtk_tree_model_foreach(model, match_desc, (void *)desc);
310
311         if (found_match) {
312                 gtk_list_store_set(GTK_LIST_STORE(model), found_match,
313                                 WS_WEIGHT, weight,
314                                 -1);
315         } else if (desc && desc[0]) {
316                 gtk_list_store_append(GTK_LIST_STORE(model), iter);
317                 gtk_list_store_set(GTK_LIST_STORE(model), iter,
318                         WS_DESC, desc,
319                         WS_WEIGHT, weight,
320                         -1);
321                 return iter;
322         }
323         return found_match;
324 }
325
326 /*
327  * When adding a dive, we'll add all the pre-existing cylinder
328  * information from that dive to our cylinder model.
329  */
330 void add_cylinder_description(cylinder_type_t *type)
331 {
332         GtkTreeIter iter;
333         const char *desc;
334         unsigned int size, workp;
335
336         desc = type->description;
337         if (!desc)
338                 return;
339         size = type->size.mliter;
340         workp = type->workingpressure.mbar;
341         add_cylinder_type(desc, size, workp, &iter);
342 }
343
344 static void add_cylinder(struct cylinder_widget *cylinder, const char *desc, int ml, int mbar)
345 {
346         GtkTreeIter iter, *match;
347
348         cylinder->name = desc;
349         match = add_cylinder_type(desc, ml, mbar, &iter);
350         if (match)
351                 gtk_combo_box_set_active_iter(cylinder->description, match);
352 }
353
354 void add_weightsystem_description(weightsystem_t *weightsystem)
355 {
356         GtkTreeIter iter;
357         const char *desc;
358         unsigned int weight;
359
360         desc = weightsystem->description;
361         if (!desc)
362                 return;
363         weight = weightsystem->weight.grams;
364         add_weightsystem_type(desc, weight, &iter);
365 }
366
367 static void add_weightsystem(struct ws_widget *ws_widget, const char *desc, int weight)
368 {
369         GtkTreeIter iter, *match;
370
371         ws_widget->name = desc;
372         match = add_weightsystem_type(desc, weight, &iter);
373         if (match)
374                 gtk_combo_box_set_active_iter(ws_widget->description, match);
375 }
376
377 static void show_cylinder(cylinder_t *cyl, struct cylinder_widget *cylinder)
378 {
379         const char *desc;
380         int ml, mbar;
381         int gasmix;
382         double o2, he;
383
384         /* Don't show uninitialized cylinder widgets */
385         if (!cylinder->description)
386                 return;
387
388         desc = cyl->type.description;
389         if (!desc)
390                 desc = "";
391         ml = cyl->type.size.mliter;
392         mbar = cyl->type.workingpressure.mbar;
393         add_cylinder(cylinder, desc, ml, mbar);
394
395         set_cylinder_type_spinbuttons(cylinder,
396                 cyl->type.size.mliter, cyl->type.workingpressure.mbar);
397         set_cylinder_pressure_spinbuttons(cylinder, cyl);
398
399         gasmix = cyl->gasmix.o2.permille || cyl->gasmix.he.permille;
400         gtk_widget_set_sensitive(cylinder->o2, gasmix);
401         gtk_widget_set_sensitive(cylinder->he, gasmix);
402         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cylinder->gasmix_button), gasmix);
403
404         o2 = cyl->gasmix.o2.permille / 10.0;
405         he = cyl->gasmix.he.permille / 10.0;
406         if (!o2)
407                 o2 = AIR_PERMILLE / 10.0;
408         gtk_spin_button_set_value(GTK_SPIN_BUTTON(cylinder->o2), o2);
409         gtk_spin_button_set_value(GTK_SPIN_BUTTON(cylinder->he), he);
410 }
411
412 static void show_weightsystem(weightsystem_t *ws, struct ws_widget *weightsystem_widget)
413 {
414         const char *desc;
415         int grams;
416
417         /* Don't show uninitialized widgets */
418         if (!weightsystem_widget->description)
419                 return;
420
421         desc = ws->description;
422         if (!desc)
423                 desc = "";
424         grams = ws->weight.grams;
425         add_weightsystem(weightsystem_widget, desc, grams);
426
427         set_weight_weight_spinbutton(weightsystem_widget, ws->weight.grams);
428 }
429
430 int cylinder_none(void *_data)
431 {
432         cylinder_t *cyl = _data;
433         return  !cyl->type.size.mliter &&
434                 !cyl->type.workingpressure.mbar &&
435                 !cyl->type.description &&
436                 !cyl->gasmix.o2.permille &&
437                 !cyl->gasmix.he.permille &&
438                 !cyl->sample_start.mbar &&
439                 !cyl->sample_end.mbar &&
440                 !cyl->start.mbar &&
441                 !cyl->end.mbar;
442 }
443
444 int weightsystem_none(void *_data)
445 {
446         weightsystem_t *ws = _data;
447         return !ws->weight.grams && !ws->description;
448 }
449
450 static void set_one_cylinder(void *_data, GtkListStore *model, GtkTreeIter *iter)
451 {
452         cylinder_t *cyl = _data;
453         unsigned int start, end;
454
455         start = cyl->start.mbar ? : cyl->sample_start.mbar;
456         end = cyl->end.mbar ? : cyl->sample_end.mbar;
457         gtk_list_store_set(model, iter,
458                 CYL_DESC, cyl->type.description ? : "",
459                 CYL_SIZE, cyl->type.size.mliter,
460                 CYL_WORKP, cyl->type.workingpressure.mbar,
461                 CYL_STARTP, start,
462                 CYL_ENDP, end,
463                 CYL_O2, cyl->gasmix.o2.permille,
464                 CYL_HE, cyl->gasmix.he.permille,
465                 -1);
466 }
467
468 static void set_one_weightsystem(void *_data, GtkListStore *model, GtkTreeIter *iter)
469 {
470         weightsystem_t *ws = _data;
471
472         gtk_list_store_set(model, iter,
473                 WS_DESC, ws->description ? : "unspecified",
474                 WS_WEIGHT, ws->weight.grams,
475                 -1);
476 }
477
478 static void *cyl_ptr(struct dive *dive, int idx)
479 {
480         if (idx < 0 || idx >= MAX_CYLINDERS)
481                 return NULL;
482         return &dive->cylinder[idx];
483 }
484
485 static void *ws_ptr(struct dive *dive, int idx)
486 {
487         if (idx < 0 || idx >= MAX_WEIGHTSYSTEMS)
488                 return NULL;
489         return &dive->weightsystem[idx];
490 }
491
492 static void show_equipment(struct dive *dive, int max,
493                         struct equipment_list *equipment_list,
494                         void*(*ptr_function)(struct dive*, int),
495                         int(*none_function)(void *),
496                         void(*set_one_function)(void*, GtkListStore*, GtkTreeIter *))
497 {
498         int i, used;
499         void *data;
500         GtkTreeIter iter;
501         GtkListStore *model = equipment_list->model;
502
503         gtk_list_store_clear(model);
504         used = max;
505         do {
506                 data = ptr_function(dive, used-1);
507                 if (!none_function(data))
508                         break;
509         } while (--used);
510
511         equipment_list->max_index = used;
512
513         gtk_widget_set_sensitive(equipment_list->edit, 0);
514         gtk_widget_set_sensitive(equipment_list->del, 0);
515         gtk_widget_set_sensitive(equipment_list->add, used < max);
516
517         for (i = 0; i < used; i++) {
518                 data = ptr_function(dive, i);
519                 gtk_list_store_append(model, &iter);
520                 set_one_function(data, model, &iter);
521         }
522 }
523
524 void show_dive_equipment(struct dive *dive)
525 {
526         show_equipment(dive, MAX_CYLINDERS, &cylinder_list,
527                 &cyl_ptr, &cylinder_none, &set_one_cylinder);
528         show_equipment(dive, MAX_WEIGHTSYSTEMS, &weightsystem_list,
529                 &ws_ptr, &weightsystem_none, &set_one_weightsystem);
530 }
531
532 static GtkWidget *create_spinbutton(GtkWidget *vbox, const char *name, double min, double max, double incr)
533 {
534         GtkWidget *frame, *hbox, *button;
535
536         frame = gtk_frame_new(name);
537         gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, FALSE, 0);
538
539         hbox = gtk_hbox_new(FALSE, 3);
540         gtk_container_add(GTK_CONTAINER(frame), hbox);
541
542         button = gtk_spin_button_new_with_range(min, max, incr);
543         gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
544
545         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID);
546
547         return button;
548 }
549
550 static void fill_cylinder_info(struct cylinder_widget *cylinder, cylinder_t *cyl, const char *desc,
551                 double volume, double pressure, double start, double end, int o2, int he)
552 {
553         int mbar, ml;
554
555         if (output_units.pressure == PSI) {
556                 pressure = psi_to_bar(pressure);
557                 start = psi_to_bar(start);
558                 end = psi_to_bar(end);
559         }
560
561         if (pressure && output_units.volume == CUFT) {
562                 volume = cuft_to_l(volume);
563                 volume /= bar_to_atm(pressure);
564         }
565
566         ml = volume * 1000 + 0.5;
567         mbar = pressure * 1000 + 0.5;
568
569         /* Ignore obviously crazy He values */
570         if (o2 + he > 1000)
571                 he = 0;
572
573         /* We have a rule that normal air is all zeroes */
574         if (!he && o2 > 208 && o2 < 211)
575                 o2 = 0;
576
577         cyl->type.description = desc;
578         cyl->type.size.mliter = ml;
579         cyl->type.workingpressure.mbar = mbar;
580         cyl->start.mbar = start * 1000 + 0.5;
581         cyl->end.mbar = end * 1000 + 0.5;
582         cyl->gasmix.o2.permille = o2;
583         cyl->gasmix.he.permille = he;
584
585         /*
586          * Also, insert it into the model if it doesn't already exist
587          */
588         add_cylinder(cylinder, desc, ml, mbar);
589 }
590
591 static void record_cylinder_changes(cylinder_t *cyl, struct cylinder_widget *cylinder)
592 {
593         const gchar *desc;
594         GtkComboBox *box;
595         double volume, pressure, start, end;
596         int o2, he;
597
598         /* Ignore uninitialized cylinder widgets */
599         box = cylinder->description;
600         if (!box)
601                 return;
602
603         desc = gtk_combo_box_get_active_text(box);
604         volume = gtk_spin_button_get_value(cylinder->size);
605         pressure = gtk_spin_button_get_value(cylinder->pressure);
606         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cylinder->pressure_button))) {
607                 start = gtk_spin_button_get_value(GTK_SPIN_BUTTON(cylinder->start));
608                 end = gtk_spin_button_get_value(GTK_SPIN_BUTTON(cylinder->end));
609         } else {
610                 start = end = 0;
611         }
612         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cylinder->gasmix_button))) {
613                 o2 = gtk_spin_button_get_value(GTK_SPIN_BUTTON(cylinder->o2))*10 + 0.5;
614                 he = gtk_spin_button_get_value(GTK_SPIN_BUTTON(cylinder->he))*10 + 0.5;
615         } else {
616                 o2 = 0;
617                 he = 0;
618         }
619         fill_cylinder_info(cylinder, cyl, desc, volume, pressure, start, end, o2, he);
620 }
621
622 static void record_weightsystem_changes(weightsystem_t *ws, struct ws_widget *weightsystem_widget)
623 {
624         const gchar *desc;
625         GtkComboBox *box;
626         int grams;
627         double value;
628         GtkTreeIter iter;
629
630         /* Ignore uninitialized cylinder widgets */
631         box = weightsystem_widget->description;
632         if (!box)
633                 return;
634
635         desc = gtk_combo_box_get_active_text(box);
636         value = gtk_spin_button_get_value(weightsystem_widget->weight);
637
638         if (output_units.weight == LBS)
639                 grams = lbs_to_grams(value);
640         else
641                 grams = value * 1000;
642         ws->weight.grams = grams;
643         ws->description = desc;
644         add_weightsystem_type(desc, grams, &iter);
645 }
646
647 /*
648  * We hardcode the most common standard cylinders,
649  * we should pick up any other names from the dive
650  * logs directly.
651  */
652 static struct tank_info {
653         const char *name;
654         int cuft, ml, psi, bar;
655 } tank_info[100] = {
656         /* Need an empty entry for the no-cylinder case */
657         { "", },
658
659         /* Size-only metric cylinders */
660         { "10.0 l", .ml = 10000 },
661         { "11.1 l", .ml = 11100 },
662
663         /* Most common AL cylinders */
664         { "AL50",  .cuft =  50, .psi = 3000 },
665         { "AL63",  .cuft =  63, .psi = 3000 },
666         { "AL72",  .cuft =  72, .psi = 3000 },
667         { "AL80",  .cuft =  80, .psi = 3000 },
668         { "AL100", .cuft = 100, .psi = 3300 },
669
670         /* Somewhat common LP steel cylinders */
671         { "LP85",  .cuft =  85, .psi = 2640 },
672         { "LP95",  .cuft =  95, .psi = 2640 },
673         { "LP108", .cuft = 108, .psi = 2640 },
674         { "LP121", .cuft = 121, .psi = 2640 },
675
676         /* Somewhat common HP steel cylinders */
677         { "HP65",  .cuft =  65, .psi = 3442 },
678         { "HP80",  .cuft =  80, .psi = 3442 },
679         { "HP100", .cuft = 100, .psi = 3442 },
680         { "HP119", .cuft = 119, .psi = 3442 },
681         { "HP130", .cuft = 130, .psi = 3442 },
682
683         /* Common European steel cylinders */
684         { "10L 300 bar",  .ml = 10000, .bar = 300 },
685         { "12L 200 bar",  .ml = 12000, .bar = 200 },
686         { "12L 232 bar",  .ml = 12000, .bar = 232 },
687         { "12L 300 bar",  .ml = 12000, .bar = 300 },
688         { "15L 200 bar",  .ml = 15000, .bar = 200 },
689         { "15L 232 bar",  .ml = 15000, .bar = 232 },
690         { "D7 300 bar",   .ml = 14000, .bar = 300 },
691         { "D8.5 232 bar", .ml = 17000, .bar = 232 },
692         { "D12 232 bar",  .ml = 24000, .bar = 232 },
693
694         /* We'll fill in more from the dive log dynamically */
695         { NULL, }
696 };
697
698 static void fill_tank_list(GtkListStore *store)
699 {
700         GtkTreeIter iter;
701         struct tank_info *info = tank_info;
702
703         for (info = tank_info ; info->name; info++) {
704                 int ml = info->ml;
705                 int cuft = info->cuft;
706                 int psi = info->psi;
707                 int mbar;
708                 double bar = info->bar;
709
710                 if (psi && bar)
711                         goto bad_tank_info;
712                 if (ml && cuft)
713                         goto bad_tank_info;
714                 if (cuft && !psi)
715                         goto bad_tank_info;
716
717                 /* Is it in cuft and psi? */
718                 if (psi)
719                         bar = psi_to_bar(psi);
720
721                 if (cuft) {
722                         double airvolume = cuft_to_l(cuft) * 1000.0;
723                         double atm = bar_to_atm(bar);
724
725                         ml = airvolume / atm + 0.5;
726                 }
727
728                 mbar = bar * 1000 + 0.5;
729
730                 gtk_list_store_append(store, &iter);
731                 gtk_list_store_set(store, &iter,
732                         0, info->name,
733                         1, ml,
734                         2, mbar,
735                         -1);
736                 continue;
737
738 bad_tank_info:
739                 fprintf(stderr, "Bad tank info for '%s'\n", info->name);
740         }
741 }
742
743 /*
744  * We hardcode the most common weight system types
745  * This is a bit odd as the weight system types don't usually encode weight
746  */
747 static struct ws_info {
748         const char *name;
749         int grams;
750 } ws_info[100] = {
751         { "integrated", 0 },
752         { "belt", 0 },
753         { "ankle", 0 },
754         { "bar", 0 },
755         { "clip-on", 0 },
756 };
757
758 static void fill_ws_list(GtkListStore *store)
759 {
760         GtkTreeIter iter;
761         struct ws_info *info = ws_info;
762
763         while (info->name) {
764                 gtk_list_store_append(store, &iter);
765                 gtk_list_store_set(store, &iter,
766                         0, info->name,
767                         1, info->grams,
768                         -1);
769                 info++;
770         }
771 }
772
773 static void gasmix_cb(GtkToggleButton *button, gpointer data)
774 {
775         struct cylinder_widget *cylinder = data;
776         int state;
777
778         state = gtk_toggle_button_get_active(button);
779         gtk_widget_set_sensitive(cylinder->o2, state);
780         gtk_widget_set_sensitive(cylinder->he, state);
781 }
782
783 static void pressure_cb(GtkToggleButton *button, gpointer data)
784 {
785         struct cylinder_widget *cylinder = data;
786         int state;
787
788         state = gtk_toggle_button_get_active(button);
789         gtk_widget_set_sensitive(cylinder->start, state);
790         gtk_widget_set_sensitive(cylinder->end, state);
791 }
792
793 static gboolean completion_cb(GtkEntryCompletion *widget, GtkTreeModel *model, GtkTreeIter *iter, struct cylinder_widget *cylinder)
794 {
795         const char *desc;
796         unsigned int ml, mbar;
797
798         gtk_tree_model_get(model, iter, CYL_DESC, &desc, CYL_SIZE, &ml, CYL_WORKP, &mbar, -1);
799         add_cylinder(cylinder, desc, ml, mbar);
800         return TRUE;
801 }
802
803 static void cylinder_activate_cb(GtkComboBox *combo_box, gpointer data)
804 {
805         struct cylinder_widget *cylinder = data;
806         cylinder_cb(cylinder->description, data);
807 }
808
809 /* Return a frame containing a hbox inside a hbox */
810 static GtkWidget *frame_box(const char *title, GtkWidget *vbox)
811 {
812         GtkWidget *hbox, *frame;
813
814         hbox = gtk_hbox_new(FALSE, 10);
815         gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, FALSE, 0);
816
817         frame = gtk_frame_new(title);
818         gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, FALSE, 0);
819
820         hbox = gtk_hbox_new(FALSE, 10);
821         gtk_container_add(GTK_CONTAINER(frame), hbox);
822
823         return hbox;
824 }
825
826 static GtkWidget *labeled_spinbutton(GtkWidget *box, const char *name, double min, double max, double incr)
827 {
828         GtkWidget *hbox, *label, *button;
829
830         hbox = gtk_hbox_new(FALSE, 0);
831         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, FALSE, 0);
832
833         label = gtk_label_new(name);
834         gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, FALSE, 0);
835
836         button = gtk_spin_button_new_with_range(min, max, incr);
837         gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
838
839         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID);
840
841         return button;
842 }
843
844 static void cylinder_widget(GtkWidget *vbox, struct cylinder_widget *cylinder, GtkListStore *model)
845 {
846         GtkWidget *frame, *hbox;
847         GtkEntry *entry;
848         GtkEntryCompletion *completion;
849         GtkWidget *widget;
850
851         /*
852          * Cylinder type: description, size and
853          * working pressure
854          */
855         frame = gtk_frame_new("Cylinder");
856
857         hbox = gtk_hbox_new(FALSE, 3);
858         gtk_container_add(GTK_CONTAINER(frame), hbox);
859
860         widget = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
861         gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
862
863         cylinder->description = GTK_COMBO_BOX(widget);
864         g_signal_connect(widget, "changed", G_CALLBACK(cylinder_cb), cylinder);
865
866         entry = GTK_ENTRY(GTK_BIN(widget)->child);
867         g_signal_connect(entry, "activate", G_CALLBACK(cylinder_activate_cb), cylinder);
868
869         completion = gtk_entry_completion_new();
870         gtk_entry_completion_set_text_column(completion, 0);
871         gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(model));
872         g_signal_connect(completion, "match-selected", G_CALLBACK(completion_cb), cylinder);
873         gtk_entry_set_completion(entry, completion);
874
875         hbox = gtk_hbox_new(FALSE, 3);
876         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
877         gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 0);
878
879         widget = create_spinbutton(hbox, "Size", 0, 300, 0.1);
880         cylinder->size = GTK_SPIN_BUTTON(widget);
881
882         widget = create_spinbutton(hbox, "Pressure", 0, 5000, 1);
883         cylinder->pressure = GTK_SPIN_BUTTON(widget);
884
885         /*
886          * Cylinder start/end pressures
887          */
888         hbox = frame_box("Pressure", vbox);
889
890         widget = labeled_spinbutton(hbox, "Start", 0, 5000, 1);
891         cylinder->start = widget;
892
893         widget = labeled_spinbutton(hbox, "End", 0, 5000, 1);
894         cylinder->end = widget;
895
896         cylinder->pressure_button = gtk_check_button_new();
897         gtk_box_pack_start(GTK_BOX(hbox), cylinder->pressure_button, FALSE, FALSE, 3);
898         g_signal_connect(cylinder->pressure_button, "toggled", G_CALLBACK(pressure_cb), cylinder);
899
900         /*
901          * Cylinder gas mix: Air, Nitrox or Trimix
902          */
903         hbox = frame_box("Gasmix", vbox);
904
905         widget = labeled_spinbutton(hbox, "O"UTF8_SUBSCRIPT_2 "%", 1, 100, 0.1);
906         cylinder->o2 = widget;
907         widget = labeled_spinbutton(hbox, "He%", 0, 100, 0.1);
908         cylinder->he = widget;
909         cylinder->gasmix_button = gtk_check_button_new();
910         gtk_box_pack_start(GTK_BOX(hbox), cylinder->gasmix_button, FALSE, FALSE, 3);
911         g_signal_connect(cylinder->gasmix_button, "toggled", G_CALLBACK(gasmix_cb), cylinder);
912 }
913
914 static gboolean weight_completion_cb(GtkEntryCompletion *widget, GtkTreeModel *model, GtkTreeIter *iter, struct ws_widget *ws_widget)
915 {
916         const char *desc;
917         unsigned int weight;
918
919         gtk_tree_model_get(model, iter, WS_DESC, &desc, WS_WEIGHT, &weight, -1);
920         add_weightsystem(ws_widget, desc, weight);
921         return TRUE;
922 }
923
924 static void weight_activate_cb(GtkComboBox *combo_box, gpointer data)
925 {
926         struct ws_widget *ws_widget = data;
927         weight_cb(ws_widget->description, data);
928 }
929
930 static void ws_widget(GtkWidget *vbox, struct ws_widget *ws_widget, GtkListStore *model)
931 {
932         GtkWidget *frame, *hbox;
933         GtkEntryCompletion *completion;
934         GtkWidget *widget;
935         GtkEntry *entry;
936
937         /*
938          * weight_system: description and weight
939          */
940         frame = gtk_frame_new("Weight");
941
942         hbox = gtk_hbox_new(FALSE, 3);
943         gtk_container_add(GTK_CONTAINER(frame), hbox);
944
945         widget = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
946         gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
947
948         ws_widget->description = GTK_COMBO_BOX(widget);
949         g_signal_connect(widget, "changed", G_CALLBACK(weight_cb), ws_widget);
950
951         entry = GTK_ENTRY(GTK_BIN(widget)->child);
952         g_signal_connect(entry, "activate", G_CALLBACK(weight_activate_cb), ws_widget);
953
954         completion = gtk_entry_completion_new();
955         gtk_entry_completion_set_text_column(completion, 0);
956         gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(model));
957         g_signal_connect(completion, "match-selected", G_CALLBACK(weight_completion_cb), ws_widget);
958         gtk_entry_set_completion(entry, completion);
959
960         hbox = gtk_hbox_new(FALSE, 3);
961         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
962         gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 0);
963
964         if ( output_units.weight == KG)
965                 widget = create_spinbutton(hbox, "kg", 0, 50, 0.5);
966         else
967                 widget = create_spinbutton(hbox, "lbs", 0, 110, 1);
968         ws_widget->weight = GTK_SPIN_BUTTON(widget);
969 }
970
971 static int edit_cylinder_dialog(int index, cylinder_t *cyl)
972 {
973         int success;
974         GtkWidget *dialog, *vbox;
975         struct cylinder_widget cylinder;
976         struct dive *dive;
977
978         cylinder.index = index;
979         cylinder.changed = 0;
980
981         dive = current_dive;
982         if (!dive)
983                 return 0;
984         *cyl = dive->cylinder[index];
985
986         dialog = gtk_dialog_new_with_buttons("Cylinder",
987                 GTK_WINDOW(main_window),
988                 GTK_DIALOG_DESTROY_WITH_PARENT,
989                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
990                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
991                 NULL);
992
993         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
994         cylinder_widget(vbox, &cylinder, cylinder_model);
995
996         show_cylinder(cyl, &cylinder);
997
998         gtk_widget_show_all(dialog);
999         success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT;
1000         if (success) {
1001                 record_cylinder_changes(cyl, &cylinder);
1002                 dive->cylinder[index] = *cyl;
1003                 mark_divelist_changed(TRUE);
1004                 update_cylinder_related_info(dive);
1005                 flush_divelist(dive);
1006         }
1007
1008         gtk_widget_destroy(dialog);
1009
1010         return success;
1011 }
1012
1013 static int edit_weightsystem_dialog(int index, weightsystem_t *ws)
1014 {
1015         int success;
1016         GtkWidget *dialog, *vbox;
1017         struct ws_widget weightsystem_widget;
1018         struct dive *dive;
1019
1020         weightsystem_widget.index = index;
1021         weightsystem_widget.changed = 0;
1022
1023         dive = current_dive;
1024         if (!dive)
1025                 return 0;
1026         *ws = dive->weightsystem[index];
1027
1028         dialog = gtk_dialog_new_with_buttons("Weight System",
1029                 GTK_WINDOW(main_window),
1030                 GTK_DIALOG_DESTROY_WITH_PARENT,
1031                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1032                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1033                 NULL);
1034
1035         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1036         ws_widget(vbox, &weightsystem_widget, weightsystem_model);
1037
1038         show_weightsystem(ws, &weightsystem_widget);
1039
1040         gtk_widget_show_all(dialog);
1041         success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT;
1042         if (success) {
1043                 record_weightsystem_changes(ws, &weightsystem_widget);
1044                 dive->weightsystem[index] = *ws;
1045                 mark_divelist_changed(TRUE);
1046                 flush_divelist(dive);
1047         }
1048
1049         gtk_widget_destroy(dialog);
1050
1051         return success;
1052 }
1053
1054 static int get_model_index(GtkListStore *model, GtkTreeIter *iter)
1055 {
1056         int *p, index;
1057         GtkTreePath *path;
1058
1059         path = gtk_tree_model_get_path(GTK_TREE_MODEL(model), iter);
1060         p = gtk_tree_path_get_indices(path);
1061         index = p ? *p : 0;
1062         gtk_tree_path_free(path);
1063         return index;
1064 }
1065
1066 static void edit_cb(GtkButton *button, GtkTreeView *tree_view)
1067 {
1068         int index;
1069         GtkTreeIter iter;
1070         GtkListStore *model = cylinder_list.model;
1071         GtkTreeSelection *selection;
1072         cylinder_t cyl;
1073
1074         selection = gtk_tree_view_get_selection(tree_view);
1075
1076         /* Nothing selected? This shouldn't happen, since the button should be inactive */
1077         if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
1078                 return;
1079
1080         index = get_model_index(model, &iter);
1081         if (!edit_cylinder_dialog(index, &cyl))
1082                 return;
1083
1084         set_one_cylinder(&cyl, model, &iter);
1085         repaint_dive();
1086 }
1087
1088 static void add_cb(GtkButton *button, GtkTreeView *tree_view)
1089 {
1090         int index = cylinder_list.max_index;
1091         GtkTreeIter iter;
1092         GtkListStore *model = cylinder_list.model;
1093         GtkTreeSelection *selection;
1094         cylinder_t cyl;
1095
1096         if (!edit_cylinder_dialog(index, &cyl))
1097                 return;
1098
1099         gtk_list_store_append(model, &iter);
1100         set_one_cylinder(&cyl, model, &iter);
1101
1102         selection = gtk_tree_view_get_selection(tree_view);
1103         gtk_tree_selection_select_iter(selection, &iter);
1104
1105         cylinder_list.max_index++;
1106         gtk_widget_set_sensitive(cylinder_list.add, cylinder_list.max_index < MAX_CYLINDERS);
1107 }
1108
1109 static void del_cb(GtkButton *button, GtkTreeView *tree_view)
1110 {
1111         int index, nr;
1112         GtkTreeIter iter;
1113         GtkListStore *model = cylinder_list.model;
1114         GtkTreeSelection *selection;
1115         struct dive *dive;
1116         cylinder_t *cyl;
1117
1118         selection = gtk_tree_view_get_selection(tree_view);
1119
1120         /* Nothing selected? This shouldn't happen, since the button should be inactive */
1121         if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
1122                 return;
1123
1124         index = get_model_index(model, &iter);
1125
1126         dive = current_dive;
1127         if (!dive)
1128                 return;
1129         cyl = dive->cylinder + index;
1130         nr = cylinder_list.max_index - index - 1;
1131
1132         gtk_list_store_remove(model, &iter);
1133
1134         cylinder_list.max_index--;
1135         memmove(cyl, cyl+1, nr*sizeof(*cyl));
1136         memset(cyl+nr, 0, sizeof(*cyl));
1137
1138         mark_divelist_changed(TRUE);
1139         flush_divelist(dive);
1140
1141         gtk_widget_set_sensitive(cylinder_list.edit, 0);
1142         gtk_widget_set_sensitive(cylinder_list.del, 0);
1143         gtk_widget_set_sensitive(cylinder_list.add, 1);
1144 }
1145
1146 static void ws_edit_cb(GtkButton *button, GtkTreeView *tree_view)
1147 {
1148         int index;
1149         GtkTreeIter iter;
1150         GtkListStore *model = weightsystem_list.model;
1151         GtkTreeSelection *selection;
1152         weightsystem_t ws;
1153
1154         selection = gtk_tree_view_get_selection(tree_view);
1155
1156         /* Nothing selected? This shouldn't happen, since the button should be inactive */
1157         if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
1158                 return;
1159
1160         index = get_model_index(model, &iter);
1161         if (!edit_weightsystem_dialog(index, &ws))
1162                 return;
1163
1164         set_one_weightsystem(&ws, model, &iter);
1165         repaint_dive();
1166 }
1167
1168 static void ws_add_cb(GtkButton *button, GtkTreeView *tree_view)
1169 {
1170         int index = weightsystem_list.max_index;
1171         GtkTreeIter iter;
1172         GtkListStore *model = weightsystem_list.model;
1173         GtkTreeSelection *selection;
1174         weightsystem_t ws;
1175
1176         if (!edit_weightsystem_dialog(index, &ws))
1177                 return;
1178
1179         gtk_list_store_append(model, &iter);
1180         set_one_weightsystem(&ws, model, &iter);
1181
1182         selection = gtk_tree_view_get_selection(tree_view);
1183         gtk_tree_selection_select_iter(selection, &iter);
1184
1185         weightsystem_list.max_index++;
1186         gtk_widget_set_sensitive(weightsystem_list.add, weightsystem_list.max_index < MAX_WEIGHTSYSTEMS);
1187 }
1188
1189 static void ws_del_cb(GtkButton *button, GtkTreeView *tree_view)
1190 {
1191         int index, nr;
1192         GtkTreeIter iter;
1193         GtkListStore *model = weightsystem_list.model;
1194         GtkTreeSelection *selection;
1195         struct dive *dive;
1196         weightsystem_t *ws;
1197
1198         selection = gtk_tree_view_get_selection(tree_view);
1199
1200         /* Nothing selected? This shouldn't happen, since the button should be inactive */
1201         if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
1202                 return;
1203
1204         index = get_model_index(model, &iter);
1205
1206         dive = current_dive;
1207         if (!dive)
1208                 return;
1209         ws = dive->weightsystem + index;
1210         nr = weightsystem_list.max_index - index - 1;
1211
1212         gtk_list_store_remove(model, &iter);
1213
1214         weightsystem_list.max_index--;
1215         memmove(ws, ws+1, nr*sizeof(*ws));
1216         memset(ws+nr, 0, sizeof(*ws));
1217
1218         mark_divelist_changed(TRUE);
1219         flush_divelist(dive);
1220
1221         gtk_widget_set_sensitive(weightsystem_list.edit, 0);
1222         gtk_widget_set_sensitive(weightsystem_list.del, 0);
1223         gtk_widget_set_sensitive(weightsystem_list.add, 1);
1224 }
1225
1226 static GtkListStore *create_tank_size_model(void)
1227 {
1228         GtkListStore *model;
1229
1230         model = gtk_list_store_new(3,
1231                 G_TYPE_STRING,          /* Tank name */
1232                 G_TYPE_INT,             /* Tank size in mliter */
1233                 G_TYPE_INT,             /* Tank working pressure in mbar */
1234                 -1);
1235
1236         fill_tank_list(model);
1237         return model;
1238 }
1239
1240 static GtkListStore *create_weightsystem_model(void)
1241 {
1242         GtkListStore *model;
1243
1244         model = gtk_list_store_new(2,
1245                 G_TYPE_STRING,          /* Weightsystem description */
1246                 G_TYPE_INT,             /* Weight in grams */
1247                 -1);
1248
1249         fill_ws_list(model);
1250         return model;
1251 }
1252
1253 static void size_data_func(GtkTreeViewColumn *col,
1254                            GtkCellRenderer *renderer,
1255                            GtkTreeModel *model,
1256                            GtkTreeIter *iter,
1257                            gpointer data)
1258 {
1259         int ml, mbar;
1260         double size, pressure;
1261         char buffer[10];
1262
1263         gtk_tree_model_get(model, iter, CYL_SIZE, &ml, CYL_WORKP, &mbar, -1);
1264         convert_volume_pressure(ml, mbar, &size, &pressure);
1265         if (size)
1266                 snprintf(buffer, sizeof(buffer), "%.1f", size);
1267         else
1268                 strcpy(buffer, "unkn");
1269         g_object_set(renderer, "text", buffer, NULL);
1270 }
1271
1272 static void weight_data_func(GtkTreeViewColumn *col,
1273                            GtkCellRenderer *renderer,
1274                            GtkTreeModel *model,
1275                            GtkTreeIter *iter,
1276                            gpointer data)
1277 {
1278         int idx = (long)data;
1279         int grams, decimals;
1280         double value;
1281         char buffer[10];
1282
1283         gtk_tree_model_get(model, iter, idx, &grams, -1);
1284         decimals = convert_weight(grams, &value);
1285         if (grams)
1286                 snprintf(buffer, sizeof(buffer), "%.*f", decimals, value);
1287         else
1288                 strcpy(buffer, "unkn");
1289         g_object_set(renderer, "text", buffer, NULL);
1290 }
1291
1292 static void pressure_data_func(GtkTreeViewColumn *col,
1293                            GtkCellRenderer *renderer,
1294                            GtkTreeModel *model,
1295                            GtkTreeIter *iter,
1296                            gpointer data)
1297 {
1298         int index = (long)data;
1299         int mbar, decimals;
1300         double pressure;
1301         char buffer[10];
1302
1303         gtk_tree_model_get(model, iter, index, &mbar, -1);
1304         decimals = convert_pressure(mbar, &pressure);
1305         if (mbar)
1306                 snprintf(buffer, sizeof(buffer), "%.*f", decimals, pressure);
1307         else
1308                 *buffer = 0;
1309         g_object_set(renderer, "text", buffer, NULL);
1310 }
1311
1312 static void percentage_data_func(GtkTreeViewColumn *col,
1313                            GtkCellRenderer *renderer,
1314                            GtkTreeModel *model,
1315                            GtkTreeIter *iter,
1316                            gpointer data)
1317 {
1318         int index = (long)data;
1319         int permille;
1320         char buffer[10];
1321
1322         gtk_tree_model_get(model, iter, index, &permille, -1);
1323         if (permille)
1324                 snprintf(buffer, sizeof(buffer), "%.1f%%", permille / 10.0);
1325         else
1326                 *buffer = 0;
1327         g_object_set(renderer, "text", buffer, NULL);
1328 }
1329
1330 static void selection_cb(GtkTreeSelection *selection, struct equipment_list *list)
1331 {
1332         GtkTreeIter iter;
1333         int selected;
1334
1335         selected = gtk_tree_selection_get_selected(selection, NULL, &iter);
1336         gtk_widget_set_sensitive(list->edit, selected);
1337         gtk_widget_set_sensitive(list->del, selected);
1338 }
1339
1340 static void row_activated_cb(GtkTreeView *tree_view,
1341                         GtkTreePath *path,
1342                         GtkTreeViewColumn *column,
1343                         GtkTreeModel *model)
1344 {
1345         edit_cb(NULL, tree_view);
1346 }
1347
1348 static void ws_row_activated_cb(GtkTreeView *tree_view,
1349                         GtkTreePath *path,
1350                         GtkTreeViewColumn *column,
1351                         GtkTreeModel *model)
1352 {
1353         ws_edit_cb(NULL, tree_view);
1354 }
1355
1356 GtkWidget *cylinder_list_widget(void)
1357 {
1358         GtkListStore *model = cylinder_list.model;
1359         GtkWidget *tree_view;
1360         GtkTreeSelection *selection;
1361
1362         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1363         gtk_widget_set_can_focus(tree_view, FALSE);
1364
1365         g_signal_connect(tree_view, "row-activated", G_CALLBACK(row_activated_cb), model);
1366
1367         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
1368         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
1369         g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), &cylinder_list);
1370
1371         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
1372                                           "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH,
1373                                           NULL);
1374
1375         tree_view_column(tree_view, CYL_DESC, "Type", NULL, ALIGN_LEFT | UNSORTABLE);
1376         tree_view_column(tree_view, CYL_SIZE, "Size", size_data_func, ALIGN_RIGHT | UNSORTABLE);
1377         tree_view_column(tree_view, CYL_WORKP, "MaxPress", pressure_data_func, ALIGN_RIGHT | UNSORTABLE);
1378         tree_view_column(tree_view, CYL_STARTP, "Start", pressure_data_func, ALIGN_RIGHT | UNSORTABLE);
1379         tree_view_column(tree_view, CYL_ENDP, "End", pressure_data_func, ALIGN_RIGHT | UNSORTABLE);
1380         tree_view_column(tree_view, CYL_O2, "O" UTF8_SUBSCRIPT_2 "%", percentage_data_func, ALIGN_RIGHT | UNSORTABLE);
1381         tree_view_column(tree_view, CYL_HE, "He%", percentage_data_func, ALIGN_RIGHT | UNSORTABLE);
1382         return tree_view;
1383 }
1384
1385 GtkWidget *weightsystem_list_widget(void)
1386 {
1387         GtkListStore *model = weightsystem_list.model;
1388         GtkWidget *tree_view;
1389         GtkTreeSelection *selection;
1390
1391         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1392         gtk_widget_set_can_focus(tree_view, FALSE);
1393         g_signal_connect(tree_view, "row-activated", G_CALLBACK(ws_row_activated_cb), model);
1394
1395         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
1396         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
1397         g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), &weightsystem_list);
1398
1399         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
1400                                           "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH,
1401                                           NULL);
1402
1403         tree_view_column(tree_view, WS_DESC, "Type", NULL, ALIGN_LEFT | UNSORTABLE);
1404         tree_view_column(tree_view, WS_WEIGHT, "weight",
1405                         weight_data_func, ALIGN_RIGHT | UNSORTABLE);
1406
1407         return tree_view;
1408 }
1409
1410 static GtkWidget *cylinder_list_create(void)
1411 {
1412         GtkListStore *model;
1413
1414         model = gtk_list_store_new(CYL_COLUMNS,
1415                 G_TYPE_STRING,          /* CYL_DESC: utf8 */
1416                 G_TYPE_INT,             /* CYL_SIZE: mliter */
1417                 G_TYPE_INT,             /* CYL_WORKP: mbar */
1418                 G_TYPE_INT,             /* CYL_STARTP: mbar */
1419                 G_TYPE_INT,             /* CYL_ENDP: mbar */
1420                 G_TYPE_INT,             /* CYL_O2: permille */
1421                 G_TYPE_INT              /* CYL_HE: permille */
1422                 );
1423         cylinder_list.model = model;
1424         return cylinder_list_widget();
1425 }
1426
1427 static GtkWidget *weightsystem_list_create(void)
1428 {
1429         GtkListStore *model;
1430
1431         model = gtk_list_store_new(WS_COLUMNS,
1432                 G_TYPE_STRING,          /* WS_DESC: utf8 */
1433                 G_TYPE_INT              /* WS_WEIGHT: grams */
1434                 );
1435         weightsystem_list.model = model;
1436         return weightsystem_list_widget();
1437 }
1438
1439 GtkWidget *equipment_widget(void)
1440 {
1441         GtkWidget *vbox, *hbox, *frame, *framebox, *tree_view;
1442         GtkWidget *add, *del, *edit;
1443
1444         vbox = gtk_vbox_new(FALSE, 3);
1445
1446         /*
1447          * We create the cylinder size model at startup, since
1448          * we're going to share it across all cylinders and all
1449          * dives. So if you add a new cylinder type in one dive,
1450          * it will show up when you edit the cylinder types for
1451          * another dive.
1452          */
1453         cylinder_model = create_tank_size_model();
1454         tree_view = cylinder_list_create();
1455
1456         hbox = gtk_hbox_new(FALSE, 3);
1457         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
1458
1459         frame = gtk_frame_new("Cylinders");
1460         gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, FALSE, 3);
1461
1462         framebox = gtk_vbox_new(FALSE, 3);
1463         gtk_container_add(GTK_CONTAINER(frame), framebox);
1464
1465         hbox = gtk_hbox_new(FALSE, 3);
1466         gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3);
1467
1468         gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, FALSE, 3);
1469
1470         hbox = gtk_hbox_new(TRUE, 3);
1471         gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3);
1472
1473         edit = gtk_button_new_from_stock(GTK_STOCK_EDIT);
1474         add = gtk_button_new_from_stock(GTK_STOCK_ADD);
1475         del = gtk_button_new_from_stock(GTK_STOCK_DELETE);
1476         gtk_box_pack_start(GTK_BOX(hbox), edit, FALSE, FALSE, 0);
1477         gtk_box_pack_start(GTK_BOX(hbox), add, FALSE, FALSE, 0);
1478         gtk_box_pack_start(GTK_BOX(hbox), del, FALSE, FALSE, 0);
1479
1480         cylinder_list.edit = edit;
1481         cylinder_list.add = add;
1482         cylinder_list.del = del;
1483
1484         g_signal_connect(edit, "clicked", G_CALLBACK(edit_cb), tree_view);
1485         g_signal_connect(add, "clicked", G_CALLBACK(add_cb), tree_view);
1486         g_signal_connect(del, "clicked", G_CALLBACK(del_cb), tree_view);
1487
1488         hbox = gtk_hbox_new(FALSE, 3);
1489         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
1490
1491         weightsystem_model = create_weightsystem_model();
1492         tree_view = weightsystem_list_create();
1493
1494         frame = gtk_frame_new("Weight");
1495         gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, FALSE, 3);
1496
1497         framebox = gtk_vbox_new(FALSE, 3);
1498         gtk_container_add(GTK_CONTAINER(frame), framebox);
1499
1500         hbox = gtk_hbox_new(FALSE, 3);
1501         gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3);
1502
1503         gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, FALSE, 3);
1504
1505         hbox = gtk_hbox_new(TRUE, 3);
1506         gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3);
1507
1508         edit = gtk_button_new_from_stock(GTK_STOCK_EDIT);
1509         add = gtk_button_new_from_stock(GTK_STOCK_ADD);
1510         del = gtk_button_new_from_stock(GTK_STOCK_DELETE);
1511         gtk_box_pack_start(GTK_BOX(hbox), edit, FALSE, FALSE, 0);
1512         gtk_box_pack_start(GTK_BOX(hbox), add, FALSE, FALSE, 0);
1513         gtk_box_pack_start(GTK_BOX(hbox), del, FALSE, FALSE, 0);
1514
1515         weightsystem_list.edit = edit;
1516         weightsystem_list.add = add;
1517         weightsystem_list.del = del;
1518
1519         g_signal_connect(edit, "clicked", G_CALLBACK(ws_edit_cb), tree_view);
1520         g_signal_connect(add, "clicked", G_CALLBACK(ws_add_cb), tree_view);
1521         g_signal_connect(del, "clicked", G_CALLBACK(ws_del_cb), tree_view);
1522
1523         return vbox;
1524 }