]> git.tdb.fi Git - ext/subsurface.git/blob - equipment.c
Make double-clicking on the dive list bring up the dive editor
[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;
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 static struct {
35         int max_index;
36         GtkListStore *model;
37         GtkWidget *tree_view;
38         GtkWidget *edit, *add, *del;
39         GtkTreeViewColumn *desc, *size, *workp, *startp, *endp, *o2, *he;
40 } cylinder_list;
41
42 struct cylinder_widget {
43         int index, changed;
44         const char *name;
45         GtkWidget *hbox;
46         GtkComboBox *description;
47         GtkSpinButton *size, *pressure;
48         GtkWidget *start, *end, *pressure_button;
49         GtkWidget *o2, *gasmix_button;
50 };
51
52 /* we want bar - so let's not use our unit functions */
53 static int convert_pressure(int mbar, double *p)
54 {
55         int decimals = 1;
56         double pressure;
57
58         if (output_units.pressure == PSI) {
59                 pressure = mbar_to_PSI(mbar);
60                 decimals = 0;
61         } else {
62                 pressure = mbar / 1000.0;
63         }
64
65         *p = pressure;
66         return decimals;
67 }
68
69 static int convert_volume_pressure(int ml, int mbar, double *v, double *p)
70 {
71         int decimals = 1;
72         double volume, pressure;
73
74         volume = ml / 1000.0;
75         if (mbar) {
76                 if (output_units.volume == CUFT) {
77                         volume = ml_to_cuft(ml);
78                         volume *= bar_to_atm(mbar / 1000.0);
79                 }
80
81                 if (output_units.pressure == PSI) {
82                         pressure = mbar_to_PSI(mbar);
83                         decimals = 0;
84                 } else
85                         pressure = mbar / 1000.0;
86         }
87         *v = volume;
88         *p = pressure;
89         return decimals;
90 }
91
92 static void set_cylinder_type_spinbuttons(struct cylinder_widget *cylinder, int ml, int mbar)
93 {
94         double volume, pressure;
95
96         convert_volume_pressure(ml, mbar, &volume, &pressure);
97         gtk_spin_button_set_value(cylinder->size, volume);
98         gtk_spin_button_set_value(cylinder->pressure, pressure);
99 }
100
101 static void set_cylinder_pressure_spinbuttons(struct cylinder_widget *cylinder, cylinder_t *cyl)
102 {
103         int set;
104         unsigned int start, end;
105         double pressure;
106
107         start = cyl->start.mbar;
108         end = cyl->end.mbar;
109         set = start || end;
110         if (!set) {
111                 start = cyl->sample_start.mbar;
112                 end = cyl->sample_end.mbar;
113         }
114         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cylinder->pressure_button), set);
115         gtk_widget_set_sensitive(cylinder->start, set);
116         gtk_widget_set_sensitive(cylinder->end, set);
117
118         convert_pressure(start, &pressure);
119         gtk_spin_button_set_value(GTK_SPIN_BUTTON(cylinder->start), pressure);
120         convert_pressure(end, &pressure);
121         gtk_spin_button_set_value(GTK_SPIN_BUTTON(cylinder->end), pressure);
122 }
123
124 /*
125  * The gtk_tree_model_foreach() interface is bad. It could have
126  * returned whether the callback ever returned true
127  */
128 static GtkTreeIter *found_match = NULL;
129 static GtkTreeIter match_iter;
130
131 static gboolean match_cylinder(GtkTreeModel *model,
132                                 GtkTreePath *path,
133                                 GtkTreeIter *iter,
134                                 gpointer data)
135 {
136         int match;
137         gchar *name;
138         const char *desc = data;
139
140         gtk_tree_model_get(model, iter, 0, &name, -1);
141         match = !strcmp(desc, name);
142         g_free(name);
143         if (match) {
144                 match_iter = *iter;
145                 found_match = &match_iter;
146         }
147         return match;
148 }
149
150 static int get_active_cylinder(GtkComboBox *combo_box, GtkTreeIter *iter)
151 {
152         char *desc;
153
154         if (gtk_combo_box_get_active_iter(combo_box, iter))
155                 return TRUE;
156
157         desc = gtk_combo_box_get_active_text(combo_box);
158
159         found_match = NULL;
160         gtk_tree_model_foreach(GTK_TREE_MODEL(cylinder_model), match_cylinder, (void *)desc);
161
162         g_free(desc);
163         if (!found_match)
164                 return FALSE;
165
166         *iter = *found_match;
167         gtk_combo_box_set_active_iter(combo_box, iter);
168         return TRUE;
169 }
170
171 static void cylinder_cb(GtkComboBox *combo_box, gpointer data)
172 {
173         GtkTreeIter iter;
174         GtkTreeModel *model = gtk_combo_box_get_model(combo_box);
175         int ml, mbar;
176         struct cylinder_widget *cylinder = data;
177         cylinder_t *cyl = current_dive->cylinder + cylinder->index;
178
179         /* Did the user set it to some non-standard value? */
180         if (!get_active_cylinder(combo_box, &iter)) {
181                 cylinder->changed = 1;
182                 return;
183         }
184
185         /*
186          * We get "change" signal callbacks just because we set
187          * the description by hand. Whatever. So ignore them if
188          * they are no-ops.
189          */
190         if (!cylinder->changed && cyl->type.description) {
191                 int same;
192                 char *desc = gtk_combo_box_get_active_text(combo_box);
193
194                 same = !strcmp(desc, cyl->type.description);
195                 g_free(desc);
196                 if (same)
197                         return;
198         }
199         cylinder->changed = 1;
200
201         gtk_tree_model_get(model, &iter,
202                 CYL_SIZE, &ml,
203                 CYL_WORKP, &mbar,
204                 -1);
205
206         set_cylinder_type_spinbuttons(cylinder, ml, mbar);
207 }
208
209 static GtkTreeIter *add_cylinder_type(const char *desc, int ml, int mbar, GtkTreeIter *iter)
210 {
211         GtkTreeModel *model;
212
213         /* Don't even bother adding stuff without a size */
214         if (!ml)
215                 return NULL;
216
217         found_match = NULL;
218         model = GTK_TREE_MODEL(cylinder_model);
219         gtk_tree_model_foreach(model, match_cylinder, (void *)desc);
220
221         if (!found_match) {
222                 GtkListStore *store = GTK_LIST_STORE(model);
223
224                 gtk_list_store_append(store, iter);
225                 gtk_list_store_set(store, iter,
226                         0, desc,
227                         1, ml,
228                         2, mbar,
229                         -1);
230                 return iter;
231         }
232         return found_match;
233 }
234
235 /*
236  * When adding a dive, we'll add all the pre-existing cylinder
237  * information from that dive to our cylinder model.
238  */
239 void add_cylinder_description(cylinder_type_t *type)
240 {
241         GtkTreeIter iter;
242         const char *desc;
243         unsigned int size, workp;
244
245         desc = type->description;
246         if (!desc)
247                 return;
248         size = type->size.mliter;
249         workp = type->workingpressure.mbar;
250         add_cylinder_type(desc, size, workp, &iter);
251 }
252
253 static void add_cylinder(struct cylinder_widget *cylinder, const char *desc, int ml, int mbar)
254 {
255         GtkTreeIter iter, *match;
256
257         cylinder->name = desc;
258         match = add_cylinder_type(desc, ml, mbar, &iter);
259         if (match)
260                 gtk_combo_box_set_active_iter(cylinder->description, match);
261 }
262
263 static void show_cylinder(cylinder_t *cyl, struct cylinder_widget *cylinder)
264 {
265         const char *desc;
266         int ml, mbar;
267         double o2;
268
269         /* Don't show uninitialized cylinder widgets */
270         if (!cylinder->description)
271                 return;
272
273         desc = cyl->type.description;
274         if (!desc)
275                 desc = "";
276         ml = cyl->type.size.mliter;
277         mbar = cyl->type.workingpressure.mbar;
278         add_cylinder(cylinder, desc, ml, mbar);
279
280         set_cylinder_type_spinbuttons(cylinder,
281                 cyl->type.size.mliter, cyl->type.workingpressure.mbar);
282         set_cylinder_pressure_spinbuttons(cylinder, cyl);
283         o2 = cyl->gasmix.o2.permille / 10.0;
284         gtk_widget_set_sensitive(cylinder->o2, !!o2);
285         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cylinder->gasmix_button), !!o2);
286         if (!o2)
287                 o2 = 21.0;
288         gtk_spin_button_set_value(GTK_SPIN_BUTTON(cylinder->o2), o2);
289 }
290
291 static int cyl_nothing(cylinder_t *cyl)
292 {
293         return  !cyl->type.size.mliter &&
294                 !cyl->type.workingpressure.mbar &&
295                 !cyl->type.description &&
296                 !cyl->gasmix.o2.permille &&
297                 !cyl->gasmix.he.permille &&
298                 !cyl->sample_start.mbar &&
299                 !cyl->sample_end.mbar &&
300                 !cyl->start.mbar &&
301                 !cyl->end.mbar;
302 }
303
304 static void set_one_cylinder(int index, cylinder_t *cyl, GtkListStore *model, GtkTreeIter *iter)
305 {
306         unsigned int start, end;
307
308         start = cyl->start.mbar ? : cyl->sample_start.mbar;
309         end = cyl->end.mbar ? : cyl->sample_end.mbar;
310         gtk_list_store_set(model, iter,
311                 CYL_DESC, cyl->type.description ? : "",
312                 CYL_SIZE, cyl->type.size.mliter,
313                 CYL_WORKP, cyl->type.workingpressure.mbar,
314                 CYL_STARTP, start,
315                 CYL_ENDP, end,
316                 CYL_O2, cyl->gasmix.o2.permille,
317                 CYL_HE, cyl->gasmix.he.permille,
318                 -1);
319 }
320
321 void show_dive_equipment(struct dive *dive)
322 {
323         int i, max;
324         GtkTreeIter iter;
325         GtkListStore *model;
326
327         model = cylinder_list.model;
328         gtk_list_store_clear(model);
329         max = MAX_CYLINDERS;
330         do {
331                 cylinder_t *cyl = &dive->cylinder[max-1];
332
333                 if (!cyl_nothing(cyl))
334                         break;
335         } while (--max);
336
337         cylinder_list.max_index = max;
338
339         gtk_widget_set_sensitive(cylinder_list.edit, 0);
340         gtk_widget_set_sensitive(cylinder_list.del, 0);
341         gtk_widget_set_sensitive(cylinder_list.add, max < MAX_CYLINDERS);
342
343         for (i = 0; i < max; i++) {
344                 cylinder_t *cyl = dive->cylinder+i;
345
346                 gtk_list_store_append(model, &iter);
347                 set_one_cylinder(i, cyl, model, &iter);
348         }
349 }
350
351 static GtkWidget *create_spinbutton(GtkWidget *vbox, const char *name, double min, double max, double incr)
352 {
353         GtkWidget *frame, *hbox, *button;
354
355         frame = gtk_frame_new(name);
356         gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, FALSE, 0);
357
358         hbox = gtk_hbox_new(FALSE, 3);
359         gtk_container_add(GTK_CONTAINER(frame), hbox);
360
361         button = gtk_spin_button_new_with_range(min, max, incr);
362         gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, FALSE, 0);
363
364         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID);
365
366         return button;
367 }
368
369 static void fill_cylinder_info(struct cylinder_widget *cylinder, cylinder_t *cyl, const char *desc,
370                 double volume, double pressure, double start, double end, int o2)
371 {
372         int mbar, ml;
373
374         if (output_units.pressure == PSI) {
375                 pressure = psi_to_bar(pressure);
376                 start = psi_to_bar(start);
377                 end = psi_to_bar(end);
378         }
379
380         if (pressure && output_units.volume == CUFT) {
381                 volume = cuft_to_l(volume);
382                 volume /= bar_to_atm(pressure);
383         }
384
385         ml = volume * 1000 + 0.5;
386         mbar = pressure * 1000 + 0.5;
387
388         if (o2 < 211)
389                 o2 = 0;
390         cyl->type.description = desc;
391         cyl->type.size.mliter = ml;
392         cyl->type.workingpressure.mbar = mbar;
393         cyl->start.mbar = start * 1000 + 0.5;
394         cyl->end.mbar = end * 1000 + 0.5;
395         cyl->gasmix.o2.permille = o2;
396
397         /*
398          * Also, insert it into the model if it doesn't already exist
399          */
400         add_cylinder(cylinder, desc, ml, mbar);
401 }
402
403 static void record_cylinder_changes(cylinder_t *cyl, struct cylinder_widget *cylinder)
404 {
405         const gchar *desc;
406         GtkComboBox *box;
407         double volume, pressure, start, end;
408         int o2;
409
410         /* Ignore uninitialized cylinder widgets */
411         box = cylinder->description;
412         if (!box)
413                 return;
414
415         desc = gtk_combo_box_get_active_text(box);
416         volume = gtk_spin_button_get_value(cylinder->size);
417         pressure = gtk_spin_button_get_value(cylinder->pressure);
418         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cylinder->pressure_button))) {
419                 start = gtk_spin_button_get_value(GTK_SPIN_BUTTON(cylinder->start));
420                 end = gtk_spin_button_get_value(GTK_SPIN_BUTTON(cylinder->end));
421         } else {
422                 start = end = 0;
423         }
424         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cylinder->gasmix_button)))
425                 o2 = gtk_spin_button_get_value(GTK_SPIN_BUTTON(cylinder->o2))*10 + 0.5;
426         else
427                 o2 = 0;
428         fill_cylinder_info(cylinder, cyl, desc, volume, pressure, start, end, o2);
429 }
430
431 /*
432  * We hardcode the most common standard cylinders,
433  * we should pick up any other names from the dive
434  * logs directly.
435  */
436 static struct tank_info {
437         const char *name;
438         int size;       /* cuft if < 1000, otherwise mliter */
439         int psi;        /* If zero, size is in mliter */
440 } tank_info[100] = {
441         /* Need an empty entry for the no-cylinder case */
442         { "", 0, 0 },
443
444         /* Size-only metric cylinders */
445         { "10.0 l", 10000 },
446         { "11.1 l", 11100 },
447
448         /* Most common AL cylinders */
449         { "AL50",   50, 3000 },
450         { "AL63",   63, 3000 },
451         { "AL72",   72, 3000 },
452         { "AL80",   80, 3000 },
453         { "AL100", 100, 3300 },
454
455         /* Somewhat common LP steel cylinders */
456         { "LP85",   85, 2640 },
457         { "LP95",   95, 2640 },
458         { "LP108", 108, 2640 },
459         { "LP121", 121, 2640 },
460
461         /* Somewhat common HP steel cylinders */
462         { "HP65",   65, 3442 },
463         { "HP80",   80, 3442 },
464         { "HP100", 100, 3442 },
465         { "HP119", 119, 3442 },
466         { "HP130", 130, 3442 },
467
468         /* We'll fill in more from the dive log dynamically */
469         { NULL, }
470 };
471
472 static void fill_tank_list(GtkListStore *store)
473 {
474         GtkTreeIter iter;
475         struct tank_info *info = tank_info;
476
477         while (info->name) {
478                 int size = info->size;
479                 int psi = info->psi;
480                 int mbar = 0, ml = size;
481
482                 /* Is it in cuft and psi? */
483                 if (psi) {
484                         double bar = psi_to_bar(psi);
485                         double airvolume = cuft_to_l(size) * 1000.0;
486                         double atm = bar_to_atm(bar);
487
488                         ml = airvolume / atm + 0.5;
489                         mbar = bar*1000 + 0.5;
490                 }
491
492                 gtk_list_store_append(store, &iter);
493                 gtk_list_store_set(store, &iter,
494                         0, info->name,
495                         1, ml,
496                         2, mbar,
497                         -1);
498                 info++;
499         }
500 }
501
502 static void nitrox_cb(GtkToggleButton *button, gpointer data)
503 {
504         struct cylinder_widget *cylinder = data;
505         int state;
506
507         state = gtk_toggle_button_get_active(button);
508         gtk_widget_set_sensitive(cylinder->o2, state);
509 }
510
511 static void pressure_cb(GtkToggleButton *button, gpointer data)
512 {
513         struct cylinder_widget *cylinder = data;
514         int state;
515
516         state = gtk_toggle_button_get_active(button);
517         gtk_widget_set_sensitive(cylinder->start, state);
518         gtk_widget_set_sensitive(cylinder->end, state);
519 }
520
521 static gboolean completion_cb(GtkEntryCompletion *widget, GtkTreeModel *model, GtkTreeIter *iter, struct cylinder_widget *cylinder)
522 {
523         const char *desc;
524         unsigned int ml, mbar;
525
526         gtk_tree_model_get(model, iter, CYL_DESC, &desc, CYL_SIZE, &ml, CYL_WORKP, &mbar, -1);
527         add_cylinder(cylinder, desc, ml, mbar);
528         return TRUE;
529 }
530
531 static void cylinder_activate_cb(GtkComboBox *combo_box, gpointer data)
532 {
533         struct cylinder_widget *cylinder = data;
534         cylinder_cb(cylinder->description, data);
535 }
536
537 static void cylinder_widget(GtkWidget *vbox, struct cylinder_widget *cylinder, GtkListStore *model)
538 {
539         GtkWidget *frame, *hbox;
540         GtkEntry *entry;
541         GtkEntryCompletion *completion;
542         GtkWidget *widget;
543
544         /*
545          * Cylinder type: description, size and
546          * working pressure
547          */
548         frame = gtk_frame_new("Cylinder");
549
550         hbox = gtk_hbox_new(FALSE, 3);
551         gtk_container_add(GTK_CONTAINER(frame), hbox);
552
553         widget = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
554         gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
555
556         cylinder->description = GTK_COMBO_BOX(widget);
557         g_signal_connect(widget, "changed", G_CALLBACK(cylinder_cb), cylinder);
558
559         entry = GTK_ENTRY(GTK_BIN(widget)->child);
560         g_signal_connect(entry, "activate", G_CALLBACK(cylinder_activate_cb), cylinder);
561
562         completion = gtk_entry_completion_new();
563         gtk_entry_completion_set_text_column(completion, 0);
564         gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(model));
565         g_signal_connect(completion, "match-selected", G_CALLBACK(completion_cb), cylinder);
566         gtk_entry_set_completion(entry, completion);
567
568         hbox = gtk_hbox_new(FALSE, 3);
569         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
570         gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 0);
571
572         widget = create_spinbutton(hbox, "Size", 0, 300, 0.1);
573         cylinder->size = GTK_SPIN_BUTTON(widget);
574
575         widget = create_spinbutton(hbox, "Pressure", 0, 5000, 1);
576         cylinder->pressure = GTK_SPIN_BUTTON(widget);
577
578         /*
579          * Cylinder start/end pressures
580          */
581         hbox = gtk_hbox_new(FALSE, 3);
582         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
583
584         cylinder->pressure_button = gtk_check_button_new();
585         gtk_box_pack_start(GTK_BOX(hbox), cylinder->pressure_button, FALSE, FALSE, 3);
586         g_signal_connect(cylinder->pressure_button, "toggled", G_CALLBACK(pressure_cb), cylinder);
587
588         widget = create_spinbutton(hbox, "Start Pressure", 0, 5000, 1);
589         cylinder->start = widget;
590
591         widget = create_spinbutton(hbox, "End Pressure", 0, 5000, 1);
592         cylinder->end = widget;
593
594         /*
595          * Cylinder gas mix: Air, Nitrox or Trimix
596          */
597         hbox = gtk_hbox_new(FALSE, 3);
598         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
599
600         widget = create_spinbutton(hbox, "Nitrox", 21, 100, 0.1);
601         cylinder->o2 = widget;
602         cylinder->gasmix_button = gtk_check_button_new();
603         gtk_box_pack_start(GTK_BOX(gtk_widget_get_parent(cylinder->o2)),
604                 cylinder->gasmix_button, FALSE, FALSE, 3);
605         g_signal_connect(cylinder->gasmix_button, "toggled", G_CALLBACK(nitrox_cb), cylinder);
606
607         gtk_spin_button_set_range(GTK_SPIN_BUTTON(cylinder->o2), 21.0, 100.0);
608 }
609
610 static int edit_cylinder_dialog(int index, cylinder_t *cyl)
611 {
612         int success;
613         GtkWidget *dialog, *vbox;
614         struct cylinder_widget cylinder;
615         struct dive *dive;
616
617         cylinder.index = index;
618         cylinder.changed = 0;
619
620         dive = current_dive;
621         if (!dive)
622                 return 0;
623         *cyl = dive->cylinder[index];
624
625         dialog = gtk_dialog_new_with_buttons("Cylinder",
626                 GTK_WINDOW(main_window),
627                 GTK_DIALOG_DESTROY_WITH_PARENT,
628                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
629                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
630                 NULL);
631
632         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
633         cylinder_widget(vbox, &cylinder, cylinder_model);
634
635         show_cylinder(cyl, &cylinder);
636
637         gtk_widget_show_all(dialog);
638         success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT;
639         if (success) {
640                 record_cylinder_changes(cyl, &cylinder);
641                 dive->cylinder[index] = *cyl;
642                 mark_divelist_changed(TRUE);
643                 update_cylinder_related_info(dive);
644                 flush_divelist(dive);
645         }
646
647         gtk_widget_destroy(dialog);
648
649         return success;
650 }
651
652 static int get_model_index(GtkListStore *model, GtkTreeIter *iter)
653 {
654         int *p, index;
655         GtkTreePath *path;
656
657         path = gtk_tree_model_get_path(GTK_TREE_MODEL(model), iter);
658         p = gtk_tree_path_get_indices(path);
659         index = p ? *p : 0;
660         gtk_tree_path_free(path);
661         return index;
662 }
663
664 static void edit_cb(GtkButton *button, gpointer data)
665 {
666         int index;
667         GtkTreeIter iter;
668         GtkListStore *model = cylinder_list.model;
669         GtkTreeSelection *selection;
670         cylinder_t cyl;
671
672         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(cylinder_list.tree_view));
673
674         /* Nothing selected? This shouldn't happen, since the button should be inactive */
675         if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
676                 return;
677
678         index = get_model_index(model, &iter);
679         if (!edit_cylinder_dialog(index, &cyl))
680                 return;
681
682         set_one_cylinder(index, &cyl, model, &iter);
683         repaint_dive();
684 }
685
686 static void add_cb(GtkButton *button, gpointer data)
687 {
688         int index = cylinder_list.max_index;
689         GtkTreeIter iter;
690         GtkListStore *model = cylinder_list.model;
691         GtkTreeSelection *selection;
692         cylinder_t cyl;
693
694         if (!edit_cylinder_dialog(index, &cyl))
695                 return;
696
697         gtk_list_store_append(model, &iter);
698         set_one_cylinder(index, &cyl, model, &iter);
699
700         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(cylinder_list.tree_view));
701         gtk_tree_selection_select_iter(selection, &iter);
702
703         cylinder_list.max_index++;
704         gtk_widget_set_sensitive(cylinder_list.add, cylinder_list.max_index < MAX_CYLINDERS);
705 }
706
707 static void del_cb(GtkButton *button, gpointer data)
708 {
709         int index, nr;
710         GtkTreeIter iter;
711         GtkListStore *model = cylinder_list.model;
712         GtkTreeSelection *selection;
713         struct dive *dive;
714         cylinder_t *cyl;
715
716         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(cylinder_list.tree_view));
717
718         /* Nothing selected? This shouldn't happen, since the button should be inactive */
719         if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
720                 return;
721
722         index = get_model_index(model, &iter);
723
724         dive = current_dive;
725         if (!dive)
726                 return;
727         cyl = dive->cylinder + index;
728         nr = cylinder_list.max_index - index - 1;
729
730         gtk_list_store_remove(model, &iter);
731
732         cylinder_list.max_index--;
733         memmove(cyl, cyl+1, nr*sizeof(*cyl));
734         memset(cyl+nr, 0, sizeof(*cyl));
735
736         mark_divelist_changed(TRUE);
737         flush_divelist(dive);
738
739         gtk_widget_set_sensitive(cylinder_list.edit, 0);
740         gtk_widget_set_sensitive(cylinder_list.del, 0);
741         gtk_widget_set_sensitive(cylinder_list.add, 1);
742 }
743
744 static GtkListStore *create_tank_size_model(void)
745 {
746         GtkListStore *model;
747
748         model = gtk_list_store_new(3,
749                 G_TYPE_STRING,          /* Tank name */
750                 G_TYPE_INT,             /* Tank size in mliter */
751                 G_TYPE_INT,             /* Tank working pressure in mbar */
752                 -1);
753
754         fill_tank_list(model);
755         return model;
756 }
757
758 static void size_data_func(GtkTreeViewColumn *col,
759                            GtkCellRenderer *renderer,
760                            GtkTreeModel *model,
761                            GtkTreeIter *iter,
762                            gpointer data)
763 {
764         int ml, mbar;
765         double size, pressure;
766         char buffer[10];
767
768         gtk_tree_model_get(model, iter, CYL_SIZE, &ml, CYL_WORKP, &mbar, -1);
769         convert_volume_pressure(ml, mbar, &size, &pressure);
770         if (size)
771                 snprintf(buffer, sizeof(buffer), "%.1f", size);
772         else
773                 strcpy(buffer, "unkn");
774         g_object_set(renderer, "text", buffer, NULL);
775 }
776
777 static void pressure_data_func(GtkTreeViewColumn *col,
778                            GtkCellRenderer *renderer,
779                            GtkTreeModel *model,
780                            GtkTreeIter *iter,
781                            gpointer data)
782 {
783         int index = (long)data;
784         int mbar, decimals;
785         double pressure;
786         char buffer[10];
787
788         gtk_tree_model_get(model, iter, index, &mbar, -1);
789         decimals = convert_pressure(mbar, &pressure);
790         if (mbar)
791                 snprintf(buffer, sizeof(buffer), "%.*f", decimals, pressure);
792         else
793                 *buffer = 0;
794         g_object_set(renderer, "text", buffer, NULL);
795 }
796
797 static void percentage_data_func(GtkTreeViewColumn *col,
798                            GtkCellRenderer *renderer,
799                            GtkTreeModel *model,
800                            GtkTreeIter *iter,
801                            gpointer data)
802 {
803         int index = (long)data;
804         int permille;
805         char buffer[10];
806
807         gtk_tree_model_get(model, iter, index, &permille, -1);
808         if (permille)
809                 snprintf(buffer, sizeof(buffer), "%.1f%%", permille / 10.0);
810         else
811                 *buffer = 0;
812         g_object_set(renderer, "text", buffer, NULL);
813 }
814
815 static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model)
816 {
817         GtkTreeIter iter;
818         int selected;
819
820         selected = gtk_tree_selection_get_selected(selection, NULL, &iter);
821         gtk_widget_set_sensitive(cylinder_list.edit, selected);
822         gtk_widget_set_sensitive(cylinder_list.del, selected);
823 }
824
825 static void row_activated_cb(GtkTreeView *tree_view,
826                         GtkTreePath *path,
827                         GtkTreeViewColumn *column,
828                         GtkTreeModel *model)
829 {
830         edit_cb(NULL, NULL);
831 }
832
833 static GtkWidget *cylinder_list_create(void)
834 {
835         GtkWidget *tree_view;
836         GtkTreeSelection *selection;
837         GtkListStore *model;
838
839         model = gtk_list_store_new(CYL_COLUMNS,
840                 G_TYPE_STRING,          /* CYL_DESC: utf8 */
841                 G_TYPE_INT,             /* CYL_SIZE: mliter */
842                 G_TYPE_INT,             /* CYL_WORKP: mbar */
843                 G_TYPE_INT,             /* CYL_STARTP: mbar */
844                 G_TYPE_INT,             /* CYL_ENDP: mbar */
845                 G_TYPE_INT,             /* CYL_O2: permille */
846                 G_TYPE_INT              /* CYL_HE: permille */
847                 );
848         cylinder_list.model = model;
849         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
850         gtk_widget_set_can_focus(tree_view, FALSE);
851         g_signal_connect(tree_view, "row-activated", G_CALLBACK(row_activated_cb), model);
852
853         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
854         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
855         g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), model);
856
857         g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
858                                           "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH,
859                                           NULL);
860
861         cylinder_list.desc = tree_view_column(tree_view, CYL_DESC, "Type", NULL, PANGO_ALIGN_LEFT, TRUE);
862         cylinder_list.size = tree_view_column(tree_view, CYL_SIZE, "Size", size_data_func, PANGO_ALIGN_RIGHT, TRUE);
863         cylinder_list.workp = tree_view_column(tree_view, CYL_WORKP, "MaxPress", pressure_data_func, PANGO_ALIGN_RIGHT, TRUE);
864         cylinder_list.startp = tree_view_column(tree_view, CYL_STARTP, "Start", pressure_data_func, PANGO_ALIGN_RIGHT, TRUE);
865         cylinder_list.endp = tree_view_column(tree_view, CYL_ENDP, "End", pressure_data_func, PANGO_ALIGN_RIGHT, TRUE);
866         cylinder_list.o2 = tree_view_column(tree_view, CYL_O2, "O" UTF8_SUBSCRIPT_2 "%", percentage_data_func, PANGO_ALIGN_RIGHT, TRUE);
867         cylinder_list.he = tree_view_column(tree_view, CYL_HE, "He%", percentage_data_func, PANGO_ALIGN_RIGHT, TRUE);
868         return tree_view;
869 }
870
871 GtkWidget *equipment_widget(void)
872 {
873         GtkWidget *vbox, *hbox, *frame, *framebox;
874         GtkWidget *add, *del, *edit;
875
876         vbox = gtk_vbox_new(FALSE, 3);
877
878         /*
879          * We create the cylinder size model at startup, since
880          * we're going to share it across all cylinders and all
881          * dives. So if you add a new cylinder type in one dive,
882          * it will show up when you edit the cylinder types for
883          * another dive.
884          */
885         cylinder_model = create_tank_size_model();
886
887         cylinder_list.tree_view = cylinder_list_create();
888
889         hbox = gtk_hbox_new(FALSE, 3);
890         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
891
892         frame = gtk_frame_new("Cylinders");
893         gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, FALSE, 3);
894
895         framebox = gtk_vbox_new(FALSE, 3);
896         gtk_container_add(GTK_CONTAINER(frame), framebox);
897
898         hbox = gtk_hbox_new(FALSE, 3);
899         gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3);
900
901         gtk_box_pack_start(GTK_BOX(hbox), cylinder_list.tree_view, TRUE, FALSE, 3);
902
903         hbox = gtk_hbox_new(TRUE, 3);
904         gtk_box_pack_start(GTK_BOX(framebox), hbox, TRUE, FALSE, 3);
905
906         edit = gtk_button_new_from_stock(GTK_STOCK_EDIT);
907         add = gtk_button_new_from_stock(GTK_STOCK_ADD);
908         del = gtk_button_new_from_stock(GTK_STOCK_DELETE);
909         gtk_box_pack_start(GTK_BOX(hbox), edit, FALSE, FALSE, 0);
910         gtk_box_pack_start(GTK_BOX(hbox), add, FALSE, FALSE, 0);
911         gtk_box_pack_start(GTK_BOX(hbox), del, FALSE, FALSE, 0);
912
913         cylinder_list.edit = edit;
914         cylinder_list.add = add;
915         cylinder_list.del = del;
916
917         g_signal_connect(edit, "clicked", G_CALLBACK(edit_cb), NULL);
918         g_signal_connect(add, "clicked", G_CALLBACK(add_cb), NULL);
919         g_signal_connect(del, "clicked", G_CALLBACK(del_cb), NULL);
920
921         return vbox;
922 }