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