]> git.tdb.fi Git - ext/subsurface.git/blob - equipment.c
Merge branch 'weight' of git://subsurface.hohndel.org/subsurface
[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, 2640 },
668         { "LP95",  .cuft =  95, 2640 },
669         { "LP108", .cuft = 108, 2640 },
670         { "LP121", .cuft = 121, 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         while (info->name) {
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                 /* Is it in cuft and psi? */
707                 if (psi) {
708                         bar = psi_to_bar(psi);
709
710                         if (cuft) {
711                                 double airvolume = cuft_to_l(cuft) * 1000.0;
712                                 double atm = bar_to_atm(bar);
713
714                                 ml = airvolume / atm + 0.5;
715                         }
716                 }
717
718                 mbar = bar * 1000 + 0.5;
719
720                 gtk_list_store_append(store, &iter);
721                 gtk_list_store_set(store, &iter,
722                         0, info->name,
723                         1, ml,
724                         2, mbar,
725                         -1);
726                 info++;
727         }
728 }
729
730 /*
731  * We hardcode the most common weight system types
732  * This is a bit odd as the weight system types don't usually encode weight
733  */
734 static struct ws_info {
735         const char *name;
736         int grams;
737 } ws_info[100] = {
738         /* Need an empty entry for the no weight system case */
739         { "", },
740         { "integrated", 0 },
741         { "belt", 0 },
742         { "ankle", 0 },
743         { "bar", 0 },
744         { "clip-on", 0 },
745 };
746
747 static void fill_ws_list(GtkListStore *store)
748 {
749         GtkTreeIter iter;
750         struct ws_info *info = ws_info;
751
752         while (info->name) {
753                 gtk_list_store_append(store, &iter);
754                 gtk_list_store_set(store, &iter,
755                         0, info->name,
756                         1, info->grams,
757                         -1);
758                 info++;
759         }
760 }
761
762 static void gasmix_cb(GtkToggleButton *button, gpointer data)
763 {
764         struct cylinder_widget *cylinder = data;
765         int state;
766
767         state = gtk_toggle_button_get_active(button);
768         gtk_widget_set_sensitive(cylinder->o2, state);
769         gtk_widget_set_sensitive(cylinder->he, state);
770 }
771
772 static void pressure_cb(GtkToggleButton *button, gpointer data)
773 {
774         struct cylinder_widget *cylinder = data;
775         int state;
776
777         state = gtk_toggle_button_get_active(button);
778         gtk_widget_set_sensitive(cylinder->start, state);
779         gtk_widget_set_sensitive(cylinder->end, state);
780 }
781
782 static gboolean completion_cb(GtkEntryCompletion *widget, GtkTreeModel *model, GtkTreeIter *iter, struct cylinder_widget *cylinder)
783 {
784         const char *desc;
785         unsigned int ml, mbar;
786
787         gtk_tree_model_get(model, iter, CYL_DESC, &desc, CYL_SIZE, &ml, CYL_WORKP, &mbar, -1);
788         add_cylinder(cylinder, desc, ml, mbar);
789         return TRUE;
790 }
791
792 static void cylinder_activate_cb(GtkComboBox *combo_box, gpointer data)
793 {
794         struct cylinder_widget *cylinder = data;
795         cylinder_cb(cylinder->description, data);
796 }
797
798 /* Return a frame containing a hbox inside a hbox */
799 static GtkWidget *frame_box(const char *title, GtkWidget *vbox)
800 {
801         GtkWidget *hbox, *frame;
802
803         hbox = gtk_hbox_new(FALSE, 10);
804         gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, FALSE, 0);
805
806         frame = gtk_frame_new(title);
807         gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, FALSE, 0);
808
809         hbox = gtk_hbox_new(FALSE, 10);
810         gtk_container_add(GTK_CONTAINER(frame), hbox);
811
812         return hbox;
813 }
814
815 static GtkWidget *labeled_spinbutton(GtkWidget *box, const char *name, double min, double max, double incr)
816 {
817         GtkWidget *hbox, *label, *button;
818
819         hbox = gtk_hbox_new(FALSE, 0);
820         gtk_box_pack_start(GTK_BOX(box), hbox, TRUE, FALSE, 0);
821
822         label = gtk_label_new(name);
823         gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, FALSE, 0);
824
825         button = gtk_spin_button_new_with_range(min, max, incr);
826         gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
827
828         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID);
829
830         return button;
831 }
832
833 static void cylinder_widget(GtkWidget *vbox, struct cylinder_widget *cylinder, GtkListStore *model)
834 {
835         GtkWidget *frame, *hbox;
836         GtkEntry *entry;
837         GtkEntryCompletion *completion;
838         GtkWidget *widget;
839
840         /*
841          * Cylinder type: description, size and
842          * working pressure
843          */
844         frame = gtk_frame_new("Cylinder");
845
846         hbox = gtk_hbox_new(FALSE, 3);
847         gtk_container_add(GTK_CONTAINER(frame), hbox);
848
849         widget = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
850         gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
851
852         cylinder->description = GTK_COMBO_BOX(widget);
853         g_signal_connect(widget, "changed", G_CALLBACK(cylinder_cb), cylinder);
854
855         entry = GTK_ENTRY(GTK_BIN(widget)->child);
856         g_signal_connect(entry, "activate", G_CALLBACK(cylinder_activate_cb), cylinder);
857
858         completion = gtk_entry_completion_new();
859         gtk_entry_completion_set_text_column(completion, 0);
860         gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(model));
861         g_signal_connect(completion, "match-selected", G_CALLBACK(completion_cb), cylinder);
862         gtk_entry_set_completion(entry, completion);
863
864         hbox = gtk_hbox_new(FALSE, 3);
865         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
866         gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 0);
867
868         widget = create_spinbutton(hbox, "Size", 0, 300, 0.1);
869         cylinder->size = GTK_SPIN_BUTTON(widget);
870
871         widget = create_spinbutton(hbox, "Pressure", 0, 5000, 1);
872         cylinder->pressure = GTK_SPIN_BUTTON(widget);
873
874         /*
875          * Cylinder start/end pressures
876          */
877         hbox = frame_box("Pressure", vbox);
878
879         widget = labeled_spinbutton(hbox, "Start", 0, 5000, 1);
880         cylinder->start = widget;
881
882         widget = labeled_spinbutton(hbox, "End", 0, 5000, 1);
883         cylinder->end = widget;
884
885         cylinder->pressure_button = gtk_check_button_new();
886         gtk_box_pack_start(GTK_BOX(hbox), cylinder->pressure_button, FALSE, FALSE, 3);
887         g_signal_connect(cylinder->pressure_button, "toggled", G_CALLBACK(pressure_cb), cylinder);
888
889         /*
890          * Cylinder gas mix: Air, Nitrox or Trimix
891          */
892         hbox = frame_box("Gasmix", vbox);
893
894         widget = labeled_spinbutton(hbox, "O"UTF8_SUBSCRIPT_2 "%", 1, 100, 0.1);
895         cylinder->o2 = widget;
896         widget = labeled_spinbutton(hbox, "He%", 0, 100, 0.1);
897         cylinder->he = widget;
898         cylinder->gasmix_button = gtk_check_button_new();
899         gtk_box_pack_start(GTK_BOX(hbox), cylinder->gasmix_button, FALSE, FALSE, 3);
900         g_signal_connect(cylinder->gasmix_button, "toggled", G_CALLBACK(gasmix_cb), cylinder);
901 }
902
903 static gboolean weight_completion_cb(GtkEntryCompletion *widget, GtkTreeModel *model, GtkTreeIter *iter, struct ws_widget *ws_widget)
904 {
905         const char *desc;
906         unsigned int weight;
907
908         gtk_tree_model_get(model, iter, WS_DESC, &desc, WS_WEIGHT, &weight, -1);
909         add_weightsystem(ws_widget, desc, weight);
910         return TRUE;
911 }
912
913 static void weight_activate_cb(GtkComboBox *combo_box, gpointer data)
914 {
915         struct ws_widget *ws_widget = data;
916         weight_cb(ws_widget->description, data);
917 }
918
919 static void ws_widget(GtkWidget *vbox, struct ws_widget *ws_widget, GtkListStore *model)
920 {
921         GtkWidget *frame, *hbox;
922         GtkEntryCompletion *completion;
923         GtkWidget *widget;
924         GtkEntry *entry;
925
926         /*
927          * weight_system: description and weight
928          */
929         frame = gtk_frame_new("Weight");
930
931         hbox = gtk_hbox_new(FALSE, 3);
932         gtk_container_add(GTK_CONTAINER(frame), hbox);
933
934         widget = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
935         gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
936
937         ws_widget->description = GTK_COMBO_BOX(widget);
938         g_signal_connect(widget, "changed", G_CALLBACK(weight_cb), ws_widget);
939
940         entry = GTK_ENTRY(GTK_BIN(widget)->child);
941         g_signal_connect(entry, "activate", G_CALLBACK(weight_activate_cb), ws_widget);
942
943         completion = gtk_entry_completion_new();
944         gtk_entry_completion_set_text_column(completion, 0);
945         gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(model));
946         g_signal_connect(completion, "match-selected", G_CALLBACK(weight_completion_cb), ws_widget);
947         gtk_entry_set_completion(entry, completion);
948
949         hbox = gtk_hbox_new(FALSE, 3);
950         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
951         gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 0);
952
953         if ( output_units.weight == KG)
954                 widget = create_spinbutton(hbox, "kg", 0, 50, 0.5);
955         else
956                 widget = create_spinbutton(hbox, "lbs", 0, 110, 1);
957         ws_widget->weight = GTK_SPIN_BUTTON(widget);
958 }
959
960 static int edit_cylinder_dialog(int index, cylinder_t *cyl)
961 {
962         int success;
963         GtkWidget *dialog, *vbox;
964         struct cylinder_widget cylinder;
965         struct dive *dive;
966
967         cylinder.index = index;
968         cylinder.changed = 0;
969
970         dive = current_dive;
971         if (!dive)
972                 return 0;
973         *cyl = dive->cylinder[index];
974
975         dialog = gtk_dialog_new_with_buttons("Cylinder",
976                 GTK_WINDOW(main_window),
977                 GTK_DIALOG_DESTROY_WITH_PARENT,
978                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
979                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
980                 NULL);
981
982         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
983         cylinder_widget(vbox, &cylinder, cylinder_model);
984
985         show_cylinder(cyl, &cylinder);
986
987         gtk_widget_show_all(dialog);
988         success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT;
989         if (success) {
990                 record_cylinder_changes(cyl, &cylinder);
991                 dive->cylinder[index] = *cyl;
992                 mark_divelist_changed(TRUE);
993                 update_cylinder_related_info(dive);
994                 flush_divelist(dive);
995         }
996
997         gtk_widget_destroy(dialog);
998
999         return success;
1000 }
1001
1002 static int edit_weightsystem_dialog(int index, weightsystem_t *ws)
1003 {
1004         int success;
1005         GtkWidget *dialog, *vbox;
1006         struct ws_widget weightsystem_widget;
1007         struct dive *dive;
1008
1009         weightsystem_widget.index = index;
1010         weightsystem_widget.changed = 0;
1011
1012         dive = current_dive;
1013         if (!dive)
1014                 return 0;
1015         *ws = dive->weightsystem[index];
1016
1017         dialog = gtk_dialog_new_with_buttons("Weight System",
1018                 GTK_WINDOW(main_window),
1019                 GTK_DIALOG_DESTROY_WITH_PARENT,
1020                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1021                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1022                 NULL);
1023
1024         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1025         ws_widget(vbox, &weightsystem_widget, weightsystem_model);
1026
1027         show_weightsystem(ws, &weightsystem_widget);
1028
1029         gtk_widget_show_all(dialog);
1030         success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT;
1031         if (success) {
1032                 record_weightsystem_changes(ws, &weightsystem_widget);
1033                 dive->weightsystem[index] = *ws;
1034                 mark_divelist_changed(TRUE);
1035                 flush_divelist(dive);
1036         }
1037
1038         gtk_widget_destroy(dialog);
1039
1040         return success;
1041 }
1042
1043 static int get_model_index(GtkListStore *model, GtkTreeIter *iter)
1044 {
1045         int *p, index;
1046         GtkTreePath *path;
1047
1048         path = gtk_tree_model_get_path(GTK_TREE_MODEL(model), iter);
1049         p = gtk_tree_path_get_indices(path);
1050         index = p ? *p : 0;
1051         gtk_tree_path_free(path);
1052         return index;
1053 }
1054
1055 static void edit_cb(GtkButton *button, GtkTreeView *tree_view)
1056 {
1057         int index;
1058         GtkTreeIter iter;
1059         GtkListStore *model = cylinder_list.model;
1060         GtkTreeSelection *selection;
1061         cylinder_t cyl;
1062
1063         selection = gtk_tree_view_get_selection(tree_view);
1064
1065         /* Nothing selected? This shouldn't happen, since the button should be inactive */
1066         if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
1067                 return;
1068
1069         index = get_model_index(model, &iter);
1070         if (!edit_cylinder_dialog(index, &cyl))
1071                 return;
1072
1073         set_one_cylinder(&cyl, model, &iter);
1074         repaint_dive();
1075 }
1076
1077 static void add_cb(GtkButton *button, GtkTreeView *tree_view)
1078 {
1079         int index = cylinder_list.max_index;
1080         GtkTreeIter iter;
1081         GtkListStore *model = cylinder_list.model;
1082         GtkTreeSelection *selection;
1083         cylinder_t cyl;
1084
1085         if (!edit_cylinder_dialog(index, &cyl))
1086                 return;
1087
1088         gtk_list_store_append(model, &iter);
1089         set_one_cylinder(&cyl, model, &iter);
1090
1091         selection = gtk_tree_view_get_selection(tree_view);
1092         gtk_tree_selection_select_iter(selection, &iter);
1093
1094         cylinder_list.max_index++;
1095         gtk_widget_set_sensitive(cylinder_list.add, cylinder_list.max_index < MAX_CYLINDERS);
1096 }
1097
1098 static void del_cb(GtkButton *button, GtkTreeView *tree_view)
1099 {
1100         int index, nr;
1101         GtkTreeIter iter;
1102         GtkListStore *model = cylinder_list.model;
1103         GtkTreeSelection *selection;
1104         struct dive *dive;
1105         cylinder_t *cyl;
1106
1107         selection = gtk_tree_view_get_selection(tree_view);
1108
1109         /* Nothing selected? This shouldn't happen, since the button should be inactive */
1110         if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
1111                 return;
1112
1113         index = get_model_index(model, &iter);
1114
1115         dive = current_dive;
1116         if (!dive)
1117                 return;
1118         cyl = dive->cylinder + index;
1119         nr = cylinder_list.max_index - index - 1;
1120
1121         gtk_list_store_remove(model, &iter);
1122
1123         cylinder_list.max_index--;
1124         memmove(cyl, cyl+1, nr*sizeof(*cyl));
1125         memset(cyl+nr, 0, sizeof(*cyl));
1126
1127         mark_divelist_changed(TRUE);
1128         flush_divelist(dive);
1129
1130         gtk_widget_set_sensitive(cylinder_list.edit, 0);
1131         gtk_widget_set_sensitive(cylinder_list.del, 0);
1132         gtk_widget_set_sensitive(cylinder_list.add, 1);
1133 }
1134
1135 static void ws_edit_cb(GtkButton *button, GtkTreeView *tree_view)
1136 {
1137         int index;
1138         GtkTreeIter iter;
1139         GtkListStore *model = weightsystem_list.model;
1140         GtkTreeSelection *selection;
1141         weightsystem_t ws;
1142
1143         selection = gtk_tree_view_get_selection(tree_view);
1144
1145         /* Nothing selected? This shouldn't happen, since the button should be inactive */
1146         if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
1147                 return;
1148
1149         index = get_model_index(model, &iter);
1150         if (!edit_weightsystem_dialog(index, &ws))
1151                 return;
1152
1153         set_one_weightsystem(&ws, model, &iter);
1154         repaint_dive();
1155 }
1156
1157 static void ws_add_cb(GtkButton *button, GtkTreeView *tree_view)
1158 {
1159         int index = weightsystem_list.max_index;
1160         GtkTreeIter iter;
1161         GtkListStore *model = weightsystem_list.model;
1162         GtkTreeSelection *selection;
1163         weightsystem_t ws;
1164
1165         if (!edit_weightsystem_dialog(index, &ws))
1166                 return;
1167
1168         gtk_list_store_append(model, &iter);
1169         set_one_weightsystem(&ws, model, &iter);
1170
1171         selection = gtk_tree_view_get_selection(tree_view);
1172         gtk_tree_selection_select_iter(selection, &iter);
1173
1174         weightsystem_list.max_index++;
1175         gtk_widget_set_sensitive(weightsystem_list.add, weightsystem_list.max_index < MAX_WEIGHTSYSTEMS);
1176 }
1177
1178 static void ws_del_cb(GtkButton *button, GtkTreeView *tree_view)
1179 {
1180         int index, nr;
1181         GtkTreeIter iter;
1182         GtkListStore *model = weightsystem_list.model;
1183         GtkTreeSelection *selection;
1184         struct dive *dive;
1185         weightsystem_t *ws;
1186
1187         selection = gtk_tree_view_get_selection(tree_view);
1188
1189         /* Nothing selected? This shouldn't happen, since the button should be inactive */
1190         if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
1191                 return;
1192
1193         index = get_model_index(model, &iter);
1194
1195         dive = current_dive;
1196         if (!dive)
1197                 return;
1198         ws = dive->weightsystem + index;
1199         nr = weightsystem_list.max_index - index - 1;
1200
1201         gtk_list_store_remove(model, &iter);
1202
1203         weightsystem_list.max_index--;
1204         memmove(ws, ws+1, nr*sizeof(*ws));
1205         memset(ws+nr, 0, sizeof(*ws));
1206
1207         mark_divelist_changed(TRUE);
1208         flush_divelist(dive);
1209
1210         gtk_widget_set_sensitive(weightsystem_list.edit, 0);
1211         gtk_widget_set_sensitive(weightsystem_list.del, 0);
1212         gtk_widget_set_sensitive(weightsystem_list.add, 1);
1213 }
1214
1215 static GtkListStore *create_tank_size_model(void)
1216 {
1217         GtkListStore *model;
1218
1219         model = gtk_list_store_new(3,
1220                 G_TYPE_STRING,          /* Tank name */
1221                 G_TYPE_INT,             /* Tank size in mliter */
1222                 G_TYPE_INT,             /* Tank working pressure in mbar */
1223                 -1);
1224
1225         fill_tank_list(model);
1226         return model;
1227 }
1228
1229 static GtkListStore *create_weightsystem_model(void)
1230 {
1231         GtkListStore *model;
1232
1233         model = gtk_list_store_new(2,
1234                 G_TYPE_STRING,          /* Weightsystem description */
1235                 G_TYPE_INT,             /* Weight in grams */
1236                 -1);
1237
1238         fill_ws_list(model);
1239         return model;
1240 }
1241
1242 static void size_data_func(GtkTreeViewColumn *col,
1243                            GtkCellRenderer *renderer,
1244                            GtkTreeModel *model,
1245                            GtkTreeIter *iter,
1246                            gpointer data)
1247 {
1248         int ml, mbar;
1249         double size, pressure;
1250         char buffer[10];
1251
1252         gtk_tree_model_get(model, iter, CYL_SIZE, &ml, CYL_WORKP, &mbar, -1);
1253         convert_volume_pressure(ml, mbar, &size, &pressure);
1254         if (size)
1255                 snprintf(buffer, sizeof(buffer), "%.1f", size);
1256         else
1257                 strcpy(buffer, "unkn");
1258         g_object_set(renderer, "text", buffer, NULL);
1259 }
1260
1261 static void weight_data_func(GtkTreeViewColumn *col,
1262                            GtkCellRenderer *renderer,
1263                            GtkTreeModel *model,
1264                            GtkTreeIter *iter,
1265                            gpointer data)
1266 {
1267         int idx = (long)data;
1268         int grams, decimals;
1269         double value;
1270         char buffer[10];
1271
1272         gtk_tree_model_get(model, iter, idx, &grams, -1);
1273         decimals = convert_weight(grams, &value);
1274         if (grams)
1275                 snprintf(buffer, sizeof(buffer), "%.*f", decimals, value);
1276         else
1277                 strcpy(buffer, "unkn");
1278         g_object_set(renderer, "text", buffer, NULL);
1279 }
1280
1281 static void pressure_data_func(GtkTreeViewColumn *col,
1282                            GtkCellRenderer *renderer,
1283                            GtkTreeModel *model,
1284                            GtkTreeIter *iter,
1285                            gpointer data)
1286 {
1287         int index = (long)data;
1288         int mbar, decimals;
1289         double pressure;
1290         char buffer[10];
1291
1292         gtk_tree_model_get(model, iter, index, &mbar, -1);
1293         decimals = convert_pressure(mbar, &pressure);
1294         if (mbar)
1295                 snprintf(buffer, sizeof(buffer), "%.*f", decimals, pressure);
1296         else
1297                 *buffer = 0;
1298         g_object_set(renderer, "text", buffer, NULL);
1299 }
1300
1301 static void percentage_data_func(GtkTreeViewColumn *col,
1302                            GtkCellRenderer *renderer,
1303                            GtkTreeModel *model,
1304                            GtkTreeIter *iter,
1305                            gpointer data)
1306 {
1307         int index = (long)data;
1308         int permille;
1309         char buffer[10];
1310
1311         gtk_tree_model_get(model, iter, index, &permille, -1);
1312         if (permille)
1313                 snprintf(buffer, sizeof(buffer), "%.1f%%", permille / 10.0);
1314         else
1315                 *buffer = 0;
1316         g_object_set(renderer, "text", buffer, NULL);
1317 }
1318
1319 static void selection_cb(GtkTreeSelection *selection, struct equipment_list *list)
1320 {
1321         GtkTreeIter iter;
1322         int selected;
1323
1324         selected = gtk_tree_selection_get_selected(selection, NULL, &iter);
1325         gtk_widget_set_sensitive(list->edit, selected);
1326         gtk_widget_set_sensitive(list->del, selected);
1327 }
1328
1329 static void row_activated_cb(GtkTreeView *tree_view,
1330                         GtkTreePath *path,
1331                         GtkTreeViewColumn *column,
1332                         GtkTreeModel *model)
1333 {
1334         edit_cb(NULL, tree_view);
1335 }
1336
1337 GtkWidget *cylinder_list_widget(void)
1338 {
1339         GtkListStore *model = cylinder_list.model;
1340         GtkWidget *tree_view;
1341         GtkTreeSelection *selection;
1342
1343         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1344         gtk_widget_set_can_focus(tree_view, FALSE);
1345
1346         g_signal_connect(tree_view, "row-activated", G_CALLBACK(row_activated_cb), model);
1347
1348         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
1349         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
1350         g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), &cylinder_list);
1351
1352         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
1353                                           "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH,
1354                                           NULL);
1355
1356         tree_view_column(tree_view, CYL_DESC, "Type", NULL, ALIGN_LEFT | UNSORTABLE);
1357         tree_view_column(tree_view, CYL_SIZE, "Size", size_data_func, ALIGN_RIGHT | UNSORTABLE);
1358         tree_view_column(tree_view, CYL_WORKP, "MaxPress", pressure_data_func, ALIGN_RIGHT | UNSORTABLE);
1359         tree_view_column(tree_view, CYL_STARTP, "Start", pressure_data_func, ALIGN_RIGHT | UNSORTABLE);
1360         tree_view_column(tree_view, CYL_ENDP, "End", pressure_data_func, ALIGN_RIGHT | UNSORTABLE);
1361         tree_view_column(tree_view, CYL_O2, "O" UTF8_SUBSCRIPT_2 "%", percentage_data_func, ALIGN_RIGHT | UNSORTABLE);
1362         tree_view_column(tree_view, CYL_HE, "He%", percentage_data_func, ALIGN_RIGHT | UNSORTABLE);
1363         return tree_view;
1364 }
1365
1366 GtkWidget *weightsystem_list_widget(void)
1367 {
1368         GtkListStore *model = weightsystem_list.model;
1369         GtkWidget *tree_view;
1370         GtkTreeSelection *selection;
1371
1372         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1373         gtk_widget_set_can_focus(tree_view, FALSE);
1374         g_signal_connect(tree_view, "row-activated", G_CALLBACK(row_activated_cb), model);
1375
1376         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
1377         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
1378         g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), &weightsystem_list);
1379
1380         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
1381                                           "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH,
1382                                           NULL);
1383
1384         tree_view_column(tree_view, WS_DESC, "Type", NULL, ALIGN_LEFT | UNSORTABLE);
1385         tree_view_column(tree_view, WS_WEIGHT, "weight",
1386                         weight_data_func, ALIGN_RIGHT | UNSORTABLE);
1387
1388         return tree_view;
1389 }
1390
1391 static GtkWidget *cylinder_list_create(void)
1392 {
1393         GtkListStore *model;
1394
1395         model = gtk_list_store_new(CYL_COLUMNS,
1396                 G_TYPE_STRING,          /* CYL_DESC: utf8 */
1397                 G_TYPE_INT,             /* CYL_SIZE: mliter */
1398                 G_TYPE_INT,             /* CYL_WORKP: mbar */
1399                 G_TYPE_INT,             /* CYL_STARTP: mbar */
1400                 G_TYPE_INT,             /* CYL_ENDP: mbar */
1401                 G_TYPE_INT,             /* CYL_O2: permille */
1402                 G_TYPE_INT              /* CYL_HE: permille */
1403                 );
1404         cylinder_list.model = model;
1405         return cylinder_list_widget();
1406 }
1407
1408 static GtkWidget *weightsystem_list_create(void)
1409 {
1410         GtkListStore *model;
1411
1412         model = gtk_list_store_new(WS_COLUMNS,
1413                 G_TYPE_STRING,          /* WS_DESC: utf8 */
1414                 G_TYPE_INT              /* WS_WEIGHT: grams */
1415                 );
1416         weightsystem_list.model = model;
1417         return weightsystem_list_widget();
1418 }
1419
1420 GtkWidget *equipment_widget(void)
1421 {
1422         GtkWidget *vbox, *hbox, *frame, *framebox, *tree_view;
1423         GtkWidget *add, *del, *edit;
1424
1425         vbox = gtk_vbox_new(FALSE, 3);
1426
1427         /*
1428          * We create the cylinder size model at startup, since
1429          * we're going to share it across all cylinders and all
1430          * dives. So if you add a new cylinder type in one dive,
1431          * it will show up when you edit the cylinder types for
1432          * another dive.
1433          */
1434         cylinder_model = create_tank_size_model();
1435         tree_view = cylinder_list_create();
1436
1437         hbox = gtk_hbox_new(FALSE, 3);
1438         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
1439
1440         frame = gtk_frame_new("Cylinders");
1441         gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, FALSE, 3);
1442
1443         framebox = gtk_vbox_new(FALSE, 3);
1444         gtk_container_add(GTK_CONTAINER(frame), framebox);
1445
1446         hbox = gtk_hbox_new(FALSE, 3);
1447         gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3);
1448
1449         gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, FALSE, 3);
1450
1451         hbox = gtk_hbox_new(TRUE, 3);
1452         gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3);
1453
1454         edit = gtk_button_new_from_stock(GTK_STOCK_EDIT);
1455         add = gtk_button_new_from_stock(GTK_STOCK_ADD);
1456         del = gtk_button_new_from_stock(GTK_STOCK_DELETE);
1457         gtk_box_pack_start(GTK_BOX(hbox), edit, FALSE, FALSE, 0);
1458         gtk_box_pack_start(GTK_BOX(hbox), add, FALSE, FALSE, 0);
1459         gtk_box_pack_start(GTK_BOX(hbox), del, FALSE, FALSE, 0);
1460
1461         cylinder_list.edit = edit;
1462         cylinder_list.add = add;
1463         cylinder_list.del = del;
1464
1465         g_signal_connect(edit, "clicked", G_CALLBACK(edit_cb), tree_view);
1466         g_signal_connect(add, "clicked", G_CALLBACK(add_cb), tree_view);
1467         g_signal_connect(del, "clicked", G_CALLBACK(del_cb), tree_view);
1468
1469         hbox = gtk_hbox_new(FALSE, 3);
1470         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
1471
1472         weightsystem_model = create_weightsystem_model();
1473         tree_view = weightsystem_list_create();
1474
1475         frame = gtk_frame_new("Weight");
1476         gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, FALSE, 3);
1477
1478         framebox = gtk_vbox_new(FALSE, 3);
1479         gtk_container_add(GTK_CONTAINER(frame), framebox);
1480
1481         hbox = gtk_hbox_new(FALSE, 3);
1482         gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3);
1483
1484         gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, FALSE, 3);
1485
1486         hbox = gtk_hbox_new(TRUE, 3);
1487         gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3);
1488
1489         edit = gtk_button_new_from_stock(GTK_STOCK_EDIT);
1490         add = gtk_button_new_from_stock(GTK_STOCK_ADD);
1491         del = gtk_button_new_from_stock(GTK_STOCK_DELETE);
1492         gtk_box_pack_start(GTK_BOX(hbox), edit, FALSE, FALSE, 0);
1493         gtk_box_pack_start(GTK_BOX(hbox), add, FALSE, FALSE, 0);
1494         gtk_box_pack_start(GTK_BOX(hbox), del, FALSE, FALSE, 0);
1495
1496         weightsystem_list.edit = edit;
1497         weightsystem_list.add = add;
1498         weightsystem_list.del = del;
1499
1500         g_signal_connect(edit, "clicked", G_CALLBACK(ws_edit_cb), tree_view);
1501         g_signal_connect(add, "clicked", G_CALLBACK(ws_add_cb), tree_view);
1502         g_signal_connect(del, "clicked", G_CALLBACK(ws_del_cb), tree_view);
1503
1504         return vbox;
1505 }