]> git.tdb.fi Git - ext/subsurface.git/blob - info.c
Check if multi-dive editing is actually needed
[ext/subsurface.git] / info.c
1 /* info.c */
2 /* creates the UI for the info frame - 
3  * controlled through the following interfaces:
4  * 
5  * void show_dive_info(struct dive *dive)
6  *
7  * called from gtk-ui:
8  * GtkWidget *extended_dive_info_widget(void)
9  */
10 #include <stdio.h>
11 #include <string.h>
12 #include <stdlib.h>
13 #include <time.h>
14 #include <ctype.h>
15 #include <sys/time.h>
16
17 #include "dive.h"
18 #include "display.h"
19 #include "display-gtk.h"
20 #include "divelist.h"
21
22 static GtkEntry *location, *buddy, *divemaster, *rating, *suit;
23 static GtkTextView *notes;
24 static GtkListStore *location_list, *people_list, *star_list, *suit_list;
25
26 static char *get_text(GtkTextView *view)
27 {
28         GtkTextBuffer *buffer;
29         GtkTextIter start;
30         GtkTextIter end;
31
32         buffer = gtk_text_view_get_buffer(view);
33         gtk_text_buffer_get_start_iter(buffer, &start);
34         gtk_text_buffer_get_end_iter(buffer, &end);
35         return gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
36 }
37
38 /* old is NULL or a valid string, new is a valid string
39  * NOTW: NULL and "" need to be treated as "unchanged" */
40 static int text_changed(const char *old, const char *new)
41 {
42         return (old && strcmp(old,new)) ||
43                 (!old && strcmp("",new));
44 }
45
46 static const char *skip_space(const char *str)
47 {
48         if (str) {
49                 while (isspace(*str))
50                         str++;
51                 if (!*str)
52                         str = NULL;
53         }
54         return str;
55 }
56
57 /*
58  * Get the string from a combo box.
59  *
60  * The "master" string is the string of the current dive - we only consider it
61  * changed if the old string is either empty, or matches that master string.
62  */
63 static char *get_combo_box_entry_text(GtkComboBoxEntry *combo_box, char **textp, const char *master)
64 {
65         char *old = *textp;
66         const char *old_text;
67         const gchar *new;
68         GtkEntry *entry;
69
70         old_text = skip_space(old);
71         master = skip_space(master);
72
73         /*
74          * If we had a master string, and it doesn't match our old
75          * string, we will always pick the old value (it means that
76          * we're editing another dive's info that already had a
77          * valid value).
78          */
79         if (master && old_text)
80                 if (strcmp(master, old_text))
81                         return NULL;
82
83         entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo_box)));
84         new = gtk_entry_get_text(entry);
85         while (isspace(*new))
86                 new++;
87         /* If the master string didn't change, don't change other dives either! */
88         if (!text_changed(master,new))
89                 return NULL;
90         if (!text_changed(old,new))
91                 return NULL;
92         free(old);
93         *textp = strdup(new);
94         return *textp;
95 }
96
97 #define SET_TEXT_VALUE(x) \
98         gtk_entry_set_text(x, dive && dive->x ? dive->x : "")
99
100 static int divename(char *buf, size_t size, struct dive *dive)
101 {
102         struct tm *tm = gmtime(&dive->when);
103         return snprintf(buf, size, "Dive #%d - %s %02d/%02d/%04d at %d:%02d",
104                 dive->number,
105                 weekday(tm->tm_wday),
106                 tm->tm_mon+1, tm->tm_mday,
107                 tm->tm_year+1900,
108                 tm->tm_hour, tm->tm_min);
109 }
110
111 void show_dive_info(struct dive *dive)
112 {
113         const char *text;
114         char buffer[80];
115
116         /* dive number and location (or lacking that, the date) go in the window title */
117         text = dive->location;
118         if (!text)
119                 text = "";
120         if (*text) {
121                 snprintf(buffer, sizeof(buffer), "Dive #%d - %s", dive->number, text);
122         } else {
123                 divename(buffer, sizeof(buffer), dive);
124         }
125         text = buffer;
126         if (!dive->number)
127                 text += 10;     /* Skip the "Dive #0 - " part */
128         gtk_window_set_title(GTK_WINDOW(main_window), text);
129
130         SET_TEXT_VALUE(divemaster);
131         SET_TEXT_VALUE(buddy);
132         SET_TEXT_VALUE(location);
133         SET_TEXT_VALUE(suit);
134         gtk_entry_set_text(rating, star_strings[dive->rating]);
135         gtk_text_buffer_set_text(gtk_text_view_get_buffer(notes),
136                 dive && dive->notes ? dive->notes : "", -1);
137 }
138
139 static int delete_dive_info(struct dive *dive)
140 {
141         int success;
142         GtkWidget *dialog;
143
144         if (!dive)
145                 return 0;
146
147         dialog = gtk_dialog_new_with_buttons("Delete Dive",
148                 GTK_WINDOW(main_window),
149                 GTK_DIALOG_DESTROY_WITH_PARENT,
150                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
151                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
152                 NULL);
153
154         gtk_widget_show_all(dialog);
155         success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT;
156         if (success) {
157                 delete_dive(dive);
158                 mark_divelist_changed(TRUE);
159                 dive_list_update_dives();
160         }
161
162         gtk_widget_destroy(dialog);
163
164         return success;
165 }
166
167 static void info_menu_edit_cb(GtkMenuItem *menuitem, gpointer user_data)
168 {
169         edit_multi_dive_info(-1);
170 }
171
172 static void info_menu_delete_cb(GtkMenuItem *menuitem, gpointer user_data)
173 {
174         /* this needs to delete all the selected dives as well, I guess? */
175         delete_dive_info(current_dive);
176 }
177
178 static void add_menu_item(GtkMenu *menu, const char *label, const char *icon, void (*cb)(GtkMenuItem *, gpointer))
179 {
180         GtkWidget *item;
181         if (icon) {
182                 GtkWidget *image;
183                 item = gtk_image_menu_item_new_with_label(label);
184                 image = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_MENU);
185                 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
186         } else {
187                 item = gtk_menu_item_new_with_label(label);
188         }
189         g_signal_connect(item, "activate", G_CALLBACK(cb), NULL);
190         gtk_widget_show(item); /* Yes, really */
191         gtk_menu_prepend(menu, item);
192 }
193
194 static void populate_popup_cb(GtkTextView *entry, GtkMenu *menu, gpointer user_data)
195 {
196         add_menu_item(menu, "Delete", GTK_STOCK_DELETE, info_menu_delete_cb);
197         add_menu_item(menu, "Edit", GTK_STOCK_EDIT, info_menu_edit_cb);
198 }
199
200 static GtkEntry *text_value(GtkWidget *box, const char *label)
201 {
202         GtkWidget *widget;
203         GtkWidget *frame = gtk_frame_new(label);
204
205         gtk_box_pack_start(GTK_BOX(box), frame, FALSE, TRUE, 0);
206         widget = gtk_entry_new();
207         gtk_widget_set_can_focus(widget, FALSE);
208         gtk_editable_set_editable(GTK_EDITABLE(widget), FALSE);
209         gtk_container_add(GTK_CONTAINER(frame), widget);
210         g_signal_connect(widget, "populate-popup", G_CALLBACK(populate_popup_cb), NULL);
211         return GTK_ENTRY(widget);
212 }
213
214 static GtkComboBoxEntry *text_entry(GtkWidget *box, const char *label, GtkListStore *completions, const char *text)
215 {
216         GtkEntry *entry;
217         GtkWidget *combo_box;
218         GtkWidget *frame = gtk_frame_new(label);
219         GtkEntryCompletion *completion;
220
221         gtk_box_pack_start(GTK_BOX(box), frame, FALSE, TRUE, 0);
222
223         combo_box = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(completions), 0);
224         gtk_container_add(GTK_CONTAINER(frame), combo_box);
225
226         entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo_box)));
227         if (text && *text)
228                 gtk_entry_set_text(entry, text);
229
230         completion = gtk_entry_completion_new();
231         gtk_entry_completion_set_text_column(completion, 0);
232         gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(completions));
233         gtk_entry_completion_set_inline_completion(completion, TRUE);
234         gtk_entry_completion_set_inline_selection(completion, TRUE);
235         gtk_entry_completion_set_popup_single_match(completion, FALSE);
236         gtk_entry_set_completion(entry, completion);
237
238         return GTK_COMBO_BOX_ENTRY(combo_box);
239 }
240
241 enum writable {
242         READ_ONLY,
243         READ_WRITE
244 };
245
246 static GtkTextView *text_view(GtkWidget *box, const char *label, enum writable writable)
247 {
248         GtkWidget *view, *vbox;
249         GtkWidget *frame = gtk_frame_new(label);
250
251         gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 0);
252         box = gtk_hbox_new(FALSE, 3);
253         gtk_container_add(GTK_CONTAINER(frame), box);
254         vbox = gtk_vbox_new(FALSE, 3);
255         gtk_container_add(GTK_CONTAINER(box), vbox);
256
257         GtkWidget* scrolled_window = gtk_scrolled_window_new(0, 0);
258         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
259         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN);
260
261         view = gtk_text_view_new();
262         if (writable == READ_ONLY) {
263                 gtk_widget_set_can_focus(view, FALSE);
264                 gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
265                 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE);
266                 g_signal_connect(view, "populate-popup", G_CALLBACK(populate_popup_cb), NULL);
267         }
268         gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD);
269         gtk_container_add(GTK_CONTAINER(scrolled_window), view);
270         gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0);
271         return GTK_TEXT_VIEW(view);
272 }
273
274 static enum {
275         MATCH_EXACT,
276         MATCH_PREPEND,
277         MATCH_AFTER
278 } found_string_entry;
279 static GtkTreeIter string_entry_location;
280
281 static gboolean match_string_entry(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
282 {
283         const char *string = data;
284         char *entry;
285         int cmp;
286
287         gtk_tree_model_get(model, iter, 0, &entry, -1);
288         cmp = strcmp(entry, string);
289         if (entry)
290                 free(entry);
291
292         /* Stop. The entry is bigger than the new one */
293         if (cmp > 0)
294                 return TRUE;
295
296         /* Exact match */
297         if (!cmp) {
298                 found_string_entry = MATCH_EXACT;
299                 return TRUE;
300         }
301
302         string_entry_location = *iter;
303         found_string_entry = MATCH_AFTER;
304         return FALSE;
305 }
306
307 static int match_list(GtkListStore *list, const char *string)
308 {
309         found_string_entry = MATCH_PREPEND;
310         gtk_tree_model_foreach(GTK_TREE_MODEL(list), match_string_entry, (void *)string);
311         return found_string_entry;
312 }
313
314 static void add_string_list_entry(const char *string, GtkListStore *list)
315 {
316         GtkTreeIter *iter, loc;
317
318         if (!string || !*string)
319                 return;
320
321         switch (match_list(list, string)) {
322         case MATCH_EXACT:
323                 return;
324         case MATCH_PREPEND:
325                 iter = NULL;
326                 break;
327         case MATCH_AFTER:
328                 iter = &string_entry_location;
329                 break;
330         }
331         gtk_list_store_insert_after(list, &loc, iter);
332         gtk_list_store_set(list, &loc, 0, string, -1);
333 }
334
335 void add_people(const char *string)
336 {
337         add_string_list_entry(string, people_list);
338 }
339
340 void add_location(const char *string)
341 {
342         add_string_list_entry(string, location_list);
343 }
344
345 void add_suit(const char *string)
346 {
347         add_string_list_entry(string, suit_list);
348 }
349
350 static int get_rating(const char *string)
351 {
352         int rating_val = 0;
353         int i;
354
355         for (i = 0; i <= 5; i++)
356                 if (!strcmp(star_strings[i],string))
357                         rating_val = i;
358         return rating_val;
359 }
360
361 struct dive_info {
362         GtkComboBoxEntry *location, *divemaster, *buddy, *rating, *suit;
363         GtkTextView *notes;
364 };
365
366 static void save_dive_info_changes(struct dive *dive, struct dive *master, struct dive_info *info)
367 {
368         char *old_text, *new_text;
369         char *rating_string;
370         int changed = 0;
371
372         new_text = get_combo_box_entry_text(info->location, &dive->location, master->location);
373         if (new_text) {
374                 add_location(new_text);
375                 changed = 1;
376         }
377
378         new_text = get_combo_box_entry_text(info->divemaster, &dive->divemaster, master->divemaster);
379         if (new_text) {
380                 add_people(new_text);
381                 changed = 1;
382         }
383
384         new_text = get_combo_box_entry_text(info->buddy, &dive->buddy, master->buddy);
385         if (new_text) {
386                 add_people(new_text);
387                 changed = 1;
388         }
389
390         new_text = get_combo_box_entry_text(info->suit, &dive->suit, master->suit);
391         if (new_text) {
392                 add_suit(new_text);
393                 changed = 1;
394         }
395
396         rating_string = strdup(star_strings[dive->rating]);
397         new_text = get_combo_box_entry_text(info->rating, &rating_string, star_strings[master->rating]);
398         if (new_text) {
399                 dive->rating = get_rating(rating_string);
400                 free(rating_string);
401                 changed =1;
402         }
403
404         if (info->notes) {
405                 old_text = dive->notes;
406                 dive->notes = get_text(info->notes);
407                 if (text_changed(old_text,dive->notes))
408                         changed = 1;
409                 if (old_text)
410                         g_free(old_text);
411         }
412         if (changed) {
413                 mark_divelist_changed(TRUE);
414                 update_dive(dive);
415         }
416 }
417
418 static void dive_info_widget(GtkWidget *box, struct dive *dive, struct dive_info *info, gboolean multi)
419 {
420         GtkWidget *hbox, *label, *frame, *equipment;
421         char buffer[80] = "Edit multiple dives";
422
423         if (!multi)
424                 divename(buffer, sizeof(buffer), dive);
425         label = gtk_label_new(buffer);
426         gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
427
428         info->location = text_entry(box, "Location", location_list, dive->location);
429
430         hbox = gtk_hbox_new(FALSE, 3);
431         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0);
432
433         info->divemaster = text_entry(hbox, "Dive master", people_list, dive->divemaster);
434         info->buddy = text_entry(hbox, "Buddy", people_list, dive->buddy);
435
436         hbox = gtk_hbox_new(FALSE, 3);
437         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0);
438
439         info->rating = text_entry(hbox, "Rating", star_list, star_strings[dive->rating]);
440         info->suit = text_entry(hbox, "Suit", suit_list, dive->suit);
441
442         /* only show notes if editing a single dive */
443         if (multi) {
444                 info->notes = NULL;
445         } else {
446                 info->notes = text_view(box, "Notes", READ_WRITE);
447                 if (dive->notes && *dive->notes)
448                         gtk_text_buffer_set_text(gtk_text_view_get_buffer(info->notes), dive->notes, -1);
449         }
450         hbox = gtk_hbox_new(FALSE, 3);
451         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0);
452
453         /* create a secondary Equipment widget */
454         frame = gtk_frame_new("Equipment");
455         equipment = equipment_widget(W_IDX_SECONDARY);
456         gtk_container_add(GTK_CONTAINER(frame), equipment);
457         gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 0);
458 }
459
460 /* we use these to find out if we edited the cylinder or weightsystem entries */
461 static cylinder_t remember_cyl[MAX_CYLINDERS];
462 static weightsystem_t remember_ws[MAX_WEIGHTSYSTEMS];
463 #define CYL_BYTES sizeof(cylinder_t) * MAX_CYLINDERS
464 #define WS_BYTES sizeof(weightsystem_t) * MAX_WEIGHTSYSTEMS
465
466 void save_equipment_data(struct dive *dive)
467 {
468         if (dive) {
469                 memcpy(remember_cyl, dive->cylinder, CYL_BYTES);
470                 memcpy(remember_ws, dive->weightsystem, WS_BYTES);
471         }
472 }
473
474 /* the editing happens on the master dive; we copy the equipment
475    data if it has changed in the master dive and the other dive
476    either has no entries for the equipment or the same entries
477    as the master dive had before it was edited */
478 void update_equipment_data(struct dive *dive, struct dive *master)
479 {
480         if (dive == master)
481                 return;
482         if ( ! cylinders_equal(remember_cyl, master->cylinder) &&
483                 (no_cylinders(dive->cylinder) ||
484                         cylinders_equal(dive->cylinder, remember_cyl)))
485                 memcpy(dive->cylinder, master->cylinder, CYL_BYTES);
486         if (! weightsystems_equal(remember_ws, master->weightsystem) &&
487                 (no_weightsystems(dive->weightsystem) ||
488                         weightsystems_equal(dive->weightsystem, remember_ws)))
489                 memcpy(dive->weightsystem, master->weightsystem, WS_BYTES);
490 }
491
492 /* A negative index means "all selected" */
493 int edit_multi_dive_info(int index)
494 {
495         int success;
496         GtkWidget *dialog, *vbox;
497         struct dive_info info;
498         struct dive *master;
499         gboolean multi;
500
501         dialog = gtk_dialog_new_with_buttons("Dive Info",
502                 GTK_WINDOW(main_window),
503                 GTK_DIALOG_DESTROY_WITH_PARENT,
504                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
505                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
506                 NULL);
507
508         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
509         master = get_dive(index);
510         if (!master)
511                 master = current_dive;
512
513         /* See if we should use multi dive mode */
514         multi = FALSE;
515         if (index < 0)
516         {
517                 int i;
518                 struct dive *dive;
519
520                 for (i = 0; (dive = get_dive(i)) != NULL; i++) {
521                         if (dive != master && dive->selected) {
522                                 multi = TRUE;
523                                 break;
524                         }
525                 }
526         }
527
528         dive_info_widget(vbox, master, &info, multi);
529         show_dive_equipment(master, W_IDX_SECONDARY);
530         save_equipment_data(master);
531         gtk_widget_show_all(dialog);
532         success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT;
533         if (success) {
534                 /* Update the non-current selected dives first */
535                 if (index < 0) {
536                         int i;
537                         struct dive *dive;
538
539                         for (i = 0; (dive = get_dive(i)) != NULL; i++) {
540                                 if (dive == master || !dive->selected)
541                                         continue;
542                                 /* copy all "info" fields */
543                                 save_dive_info_changes(dive, master, &info);
544                                 /* copy the cylinders / weightsystems */
545                                 update_equipment_data(dive, master);
546                                 /* this is extremely inefficient... it loops through all
547                                    dives to find the right one - but we KNOW the index already */
548                                 flush_divelist(dive);
549                         }
550                 }
551
552                 /* Update the master dive last! */
553                 save_dive_info_changes(master, master, &info);
554                 update_equipment_data(master, master);
555                 flush_divelist(master);
556         }
557         gtk_widget_destroy(dialog);
558
559         return success;
560 }
561
562 int edit_dive_info(struct dive *dive)
563 {
564         int idx;
565
566         if (!dive)
567                 return 0;
568         idx = dive->number;
569         return edit_multi_dive_info(idx);
570 }
571
572 static GtkWidget *frame_box(GtkWidget *vbox, const char *fmt, ...)
573 {
574         va_list ap;
575         char buffer[64];
576         GtkWidget *frame, *hbox;
577
578         va_start(ap, fmt);
579         vsnprintf(buffer, sizeof(buffer), fmt, ap);
580         va_end(ap);
581
582         frame = gtk_frame_new(buffer);
583         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, TRUE, 0);
584         hbox = gtk_hbox_new(0, 3);
585         gtk_container_add(GTK_CONTAINER(frame), hbox);
586         return hbox;
587 }
588
589 /* Fixme - should do at least depths too - a dive without a depth is kind of pointless */
590 static time_t dive_time_widget(struct dive *dive)
591 {
592         GtkWidget *dialog;
593         GtkWidget *cal, *hbox, *vbox, *box;
594         GtkWidget *h, *m;
595         GtkWidget *duration, *depth;
596         GtkWidget *label;
597         guint yval, mval, dval;
598         struct tm tm, *time;
599         int success;
600         double depthinterval, val;
601
602         dialog = gtk_dialog_new_with_buttons("Date and Time",
603                 GTK_WINDOW(main_window),
604                 GTK_DIALOG_DESTROY_WITH_PARENT,
605                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
606                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
607                 NULL);
608
609         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
610
611         /* Calendar hbox */
612         hbox = frame_box(vbox, "Date:");
613         cal = gtk_calendar_new();
614         gtk_box_pack_start(GTK_BOX(hbox), cal, FALSE, TRUE, 0);
615
616         /* Time hbox */
617         hbox = frame_box(vbox, "Time");
618
619         h = gtk_spin_button_new_with_range (0.0, 23.0, 1.0);
620         m = gtk_spin_button_new_with_range (0.0, 59.0, 1.0);
621
622         /*
623          * If we have a dive selected, 'add dive' will default
624          * to one hour after the end of that dive. Otherwise,
625          * we'll just take the current time.
626          */
627         if (amount_selected == 1) {
628                 time_t when = current_dive->when;
629                 when += current_dive->duration.seconds;
630                 when += 60*60;
631                 time = gmtime(&when);
632         } else {
633                 time_t now;
634                 struct timeval tv;
635                 gettimeofday(&tv, NULL);
636                 now = tv.tv_sec;
637                 time = localtime(&now);
638         }
639         gtk_calendar_select_month(GTK_CALENDAR(cal), time->tm_mon, time->tm_year + 1900);
640         gtk_calendar_select_day(GTK_CALENDAR(cal), time->tm_mday);
641         gtk_spin_button_set_value(GTK_SPIN_BUTTON(h), time->tm_hour);
642         gtk_spin_button_set_value(GTK_SPIN_BUTTON(m), (time->tm_min / 5)*5);
643
644         gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(h), TRUE);
645         gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(m), TRUE);
646
647         gtk_box_pack_end(GTK_BOX(hbox), m, FALSE, FALSE, 0);
648         label = gtk_label_new(":");
649         gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0);
650         gtk_box_pack_end(GTK_BOX(hbox), h, FALSE, FALSE, 0);
651
652         hbox = gtk_hbox_new(TRUE, 3);
653         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
654
655         /* Duration hbox */
656         box = frame_box(hbox, "Duration (min)");
657         duration = gtk_spin_button_new_with_range (0.0, 1000.0, 1.0);
658         gtk_box_pack_end(GTK_BOX(box), duration, FALSE, FALSE, 0);
659
660         /* Depth box */
661         box = frame_box(hbox, "Depth (%s):", output_units.length == FEET ? "ft" : "m");
662         if (output_units.length == FEET) {
663                 depthinterval = 1.0;
664         } else {
665                 depthinterval = 0.1;
666         }
667         depth = gtk_spin_button_new_with_range (0.0, 1000.0, depthinterval);
668         gtk_box_pack_end(GTK_BOX(box), depth, FALSE, FALSE, 0);
669
670         /* All done, show it and wait for editing */
671         gtk_widget_show_all(dialog);
672         success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT;
673         if (!success) {
674                 gtk_widget_destroy(dialog);
675                 return 0;
676         }
677
678         memset(&tm, 0, sizeof(tm));
679         gtk_calendar_get_date(GTK_CALENDAR(cal), &yval, &mval, &dval);
680         tm.tm_year = yval;
681         tm.tm_mon = mval;
682         tm.tm_mday = dval;
683
684         tm.tm_hour = gtk_spin_button_get_value(GTK_SPIN_BUTTON(h));
685         tm.tm_min = gtk_spin_button_get_value(GTK_SPIN_BUTTON(m));
686
687         val = gtk_spin_button_get_value(GTK_SPIN_BUTTON(depth));
688         if (output_units.length == FEET) {
689                 dive->maxdepth.mm = feet_to_mm(val);
690         } else {
691                 dive->maxdepth.mm = val * 1000 + 0.5;
692         }
693
694         dive->duration.seconds = gtk_spin_button_get_value(GTK_SPIN_BUTTON(duration))*60;
695
696         gtk_widget_destroy(dialog);
697         dive->when = utc_mktime(&tm);
698
699         return 1;
700 }
701
702 int add_new_dive(struct dive *dive)
703 {
704         if (!dive)
705                 return 0;
706
707         if (!dive_time_widget(dive))
708                 return 0;
709
710         return edit_dive_info(dive);
711 }
712
713 GtkWidget *extended_dive_info_widget(void)
714 {
715         GtkWidget *vbox, *hbox;
716         vbox = gtk_vbox_new(FALSE, 6);
717
718         people_list = gtk_list_store_new(1, G_TYPE_STRING);
719         location_list = gtk_list_store_new(1, G_TYPE_STRING);
720         star_list = gtk_list_store_new(1, G_TYPE_STRING);
721         add_string_list_entry(ZERO_STARS, star_list);
722         add_string_list_entry(ONE_STARS, star_list);
723         add_string_list_entry(TWO_STARS, star_list);
724         add_string_list_entry(THREE_STARS, star_list);
725         add_string_list_entry(FOUR_STARS, star_list);
726         add_string_list_entry(FIVE_STARS, star_list);
727         suit_list = gtk_list_store_new(1, G_TYPE_STRING);
728
729         gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
730         location = text_value(vbox, "Location");
731
732         hbox = gtk_hbox_new(FALSE, 3);
733         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
734
735         divemaster = text_value(hbox, "Divemaster");
736         buddy = text_value(hbox, "Buddy");
737
738         hbox = gtk_hbox_new(FALSE, 3);
739         gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
740
741         rating = text_value(hbox, "Rating");
742         suit = text_value(hbox, "Suit");
743
744         notes = text_view(vbox, "Notes", READ_ONLY);
745         return vbox;
746 }