2 /* creates the UI for the info frame -
3 * controlled through the following interfaces:
5 * void show_dive_info(struct dive *dive)
8 * GtkWidget *extended_dive_info_widget(void)
19 #include "display-gtk.h"
22 static GtkEntry *location, *buddy, *divemaster, *rating, *suit;
23 static GtkTextView *notes;
24 static GtkListStore *location_list, *people_list, *star_list, *suit_list;
26 static char *get_text(GtkTextView *view)
28 GtkTextBuffer *buffer;
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);
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)
42 return (old && strcmp(old,new)) ||
43 (!old && strcmp("",new));
46 static const char *skip_space(const char *str)
58 * Get the string from a combo box.
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.
63 static char *get_combo_box_entry_text(GtkComboBoxEntry *combo_box, char **textp, const char *master)
70 old_text = skip_space(old);
71 master = skip_space(master);
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
79 if (master && old_text)
80 if (strcmp(master, old_text))
83 entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo_box)));
84 new = gtk_entry_get_text(entry);
87 /* If the master string didn't change, don't change other dives either! */
88 if (!text_changed(master,new))
90 if (!text_changed(old,new))
97 #define SET_TEXT_VALUE(x) \
98 gtk_entry_set_text(x, dive && dive->x ? dive->x : "")
100 static int divename(char *buf, size_t size, struct dive *dive)
102 struct tm *tm = gmtime(&dive->when);
103 return snprintf(buf, size, "Dive #%d - %s %02d/%02d/%04d at %d:%02d",
105 weekday(tm->tm_wday),
106 tm->tm_mon+1, tm->tm_mday,
108 tm->tm_hour, tm->tm_min);
111 void show_dive_info(struct dive *dive)
116 /* dive number and location (or lacking that, the date) go in the window title */
117 text = dive->location;
121 snprintf(buffer, sizeof(buffer), "Dive #%d - %s", dive->number, text);
123 divename(buffer, sizeof(buffer), dive);
127 text += 10; /* Skip the "Dive #0 - " part */
128 gtk_window_set_title(GTK_WINDOW(main_window), text);
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);
139 static int delete_dive_info(struct dive *dive)
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,
154 gtk_widget_show_all(dialog);
155 success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT;
158 mark_divelist_changed(TRUE);
159 dive_list_update_dives();
162 gtk_widget_destroy(dialog);
167 static void info_menu_edit_cb(GtkMenuItem *menuitem, gpointer user_data)
169 edit_multi_dive_info(amount_selected, selectiontracker);
172 static void info_menu_delete_cb(GtkMenuItem *menuitem, gpointer user_data)
174 /* this needs to delete all the selected dives as well, I guess? */
175 delete_dive_info(current_dive);
178 static void add_menu_item(GtkMenu *menu, const char *label, const char *icon, void (*cb)(GtkMenuItem *, gpointer))
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);
187 item = gtk_menu_item_new_with_label(label);
189 g_signal_connect(item, "activate", G_CALLBACK(cb), NULL);
190 gtk_widget_show(item); /* Yes, really */
191 gtk_menu_prepend(menu, item);
194 static void populate_popup_cb(GtkTextView *entry, GtkMenu *menu, gpointer user_data)
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);
200 static GtkEntry *text_value(GtkWidget *box, const char *label)
203 GtkWidget *frame = gtk_frame_new(label);
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);
214 static GtkComboBoxEntry *text_entry(GtkWidget *box, const char *label, GtkListStore *completions, const char *text)
217 GtkWidget *combo_box;
218 GtkWidget *frame = gtk_frame_new(label);
219 GtkEntryCompletion *completion;
221 gtk_box_pack_start(GTK_BOX(box), frame, FALSE, TRUE, 0);
223 combo_box = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(completions), 0);
224 gtk_container_add(GTK_CONTAINER(frame), combo_box);
226 entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo_box)));
228 gtk_entry_set_text(entry, text);
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);
238 return GTK_COMBO_BOX_ENTRY(combo_box);
246 static GtkTextView *text_view(GtkWidget *box, const char *label, enum writable writable)
248 GtkWidget *view, *vbox;
249 GtkWidget *frame = gtk_frame_new(label);
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);
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);
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);
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);
278 } found_string_entry;
279 static GtkTreeIter string_entry_location;
281 static gboolean match_string_entry(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
283 const char *string = data;
287 gtk_tree_model_get(model, iter, 0, &entry, -1);
288 cmp = strcmp(entry, string);
292 /* Stop. The entry is bigger than the new one */
298 found_string_entry = MATCH_EXACT;
302 string_entry_location = *iter;
303 found_string_entry = MATCH_AFTER;
307 static int match_list(GtkListStore *list, const char *string)
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;
314 static void add_string_list_entry(const char *string, GtkListStore *list)
316 GtkTreeIter *iter, loc;
318 if (!string || !*string)
321 switch (match_list(list, string)) {
328 iter = &string_entry_location;
331 gtk_list_store_insert_after(list, &loc, iter);
332 gtk_list_store_set(list, &loc, 0, string, -1);
335 void add_people(const char *string)
337 add_string_list_entry(string, people_list);
340 void add_location(const char *string)
342 add_string_list_entry(string, location_list);
345 void add_suit(const char *string)
347 add_string_list_entry(string, suit_list);
350 static int get_rating(const char *string)
355 for (i = 0; i <= 5; i++)
356 if (!strcmp(star_strings[i],string))
362 GtkComboBoxEntry *location, *divemaster, *buddy, *rating, *suit;
366 static void save_dive_info_changes(struct dive *dive, struct dive *master, struct dive_info *info)
368 char *old_text, *new_text;
372 new_text = get_combo_box_entry_text(info->location, &dive->location, master->location);
374 add_location(new_text);
378 new_text = get_combo_box_entry_text(info->divemaster, &dive->divemaster, master->divemaster);
380 add_people(new_text);
384 new_text = get_combo_box_entry_text(info->buddy, &dive->buddy, master->buddy);
386 add_people(new_text);
390 new_text = get_combo_box_entry_text(info->suit, &dive->suit, master->suit);
396 rating_string = strdup(star_strings[dive->rating]);
397 new_text = get_combo_box_entry_text(info->rating, &rating_string, star_strings[master->rating]);
399 dive->rating = get_rating(rating_string);
405 old_text = dive->notes;
406 dive->notes = get_text(info->notes);
407 if (text_changed(old_text,dive->notes))
413 mark_divelist_changed(TRUE);
418 static void dive_info_widget(GtkWidget *box, struct dive *dive, struct dive_info *info, gboolean multi)
420 GtkWidget *hbox, *label, *frame, *equipment;
421 char buffer[80] = "Edit multiple dives";
424 divename(buffer, sizeof(buffer), dive);
425 label = gtk_label_new(buffer);
426 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
428 info->location = text_entry(box, "Location", location_list, dive->location);
430 hbox = gtk_hbox_new(FALSE, 3);
431 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0);
433 info->divemaster = text_entry(hbox, "Dive master", people_list, dive->divemaster);
434 info->buddy = text_entry(hbox, "Buddy", people_list, dive->buddy);
436 hbox = gtk_hbox_new(FALSE, 3);
437 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0);
439 info->rating = text_entry(hbox, "Rating", star_list, star_strings[dive->rating]);
440 info->suit = text_entry(hbox, "Suit", suit_list, dive->suit);
442 /* only show notes if editing a single dive */
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);
450 hbox = gtk_hbox_new(FALSE, 3);
451 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0);
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);
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
466 void save_equipment_data(struct dive *dive)
469 memcpy(remember_cyl, dive->cylinder, CYL_BYTES);
470 memcpy(remember_ws, dive->weightsystem, WS_BYTES);
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)
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);
492 int edit_multi_dive_info(int nr, int *indices)
495 GtkWidget *dialog, *vbox;
496 struct dive_info info;
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,
508 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
509 /* SCARY STUFF - IS THIS THE BEST WAY TO DO THIS???
511 * current_dive is one of our selected dives - and that is
512 * the one that is used to pre-fill the edit widget. Its
513 * data is used as the starting point for all selected dives
514 * I think it would be better to somehow collect and combine
515 * info from all the selected dives */
516 master = current_dive;
517 dive_info_widget(vbox, master, &info, (nr > 1));
518 show_dive_equipment(master, W_IDX_SECONDARY);
519 save_equipment_data(master);
520 gtk_widget_show_all(dialog);
521 success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT;
523 /* Update the other non-current dives first */
524 for (i = 0; i < nr; i++) {
525 int idx = indices[i];
526 struct dive *dive = get_dive(idx);
528 if (!dive || dive == master)
530 /* copy all "info" fields */
531 save_dive_info_changes(dive, master, &info);
532 /* copy the cylinders / weightsystems */
533 update_equipment_data(dive, master);
534 /* this is extremely inefficient... it loops through all
535 dives to find the right one - but we KNOW the index already */
536 flush_divelist(dive);
539 /* Update the master dive last! */
540 save_dive_info_changes(master, master, &info);
541 update_equipment_data(master, master);
542 flush_divelist(master);
544 gtk_widget_destroy(dialog);
549 int edit_dive_info(struct dive *dive)
556 return edit_multi_dive_info(1, &idx);
559 static GtkWidget *frame_box(GtkWidget *vbox, const char *fmt, ...)
563 GtkWidget *frame, *hbox;
566 vsnprintf(buffer, sizeof(buffer), fmt, ap);
569 frame = gtk_frame_new(buffer);
570 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, TRUE, 0);
571 hbox = gtk_hbox_new(0, 3);
572 gtk_container_add(GTK_CONTAINER(frame), hbox);
576 /* Fixme - should do at least depths too - a dive without a depth is kind of pointless */
577 static time_t dive_time_widget(struct dive *dive)
580 GtkWidget *cal, *hbox, *vbox, *box;
582 GtkWidget *duration, *depth;
584 guint yval, mval, dval;
587 double depthinterval, val;
589 dialog = gtk_dialog_new_with_buttons("Date and Time",
590 GTK_WINDOW(main_window),
591 GTK_DIALOG_DESTROY_WITH_PARENT,
592 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
593 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
596 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
599 hbox = frame_box(vbox, "Date:");
600 cal = gtk_calendar_new();
601 gtk_box_pack_start(GTK_BOX(hbox), cal, FALSE, TRUE, 0);
604 hbox = frame_box(vbox, "Time");
606 h = gtk_spin_button_new_with_range (0.0, 23.0, 1.0);
607 m = gtk_spin_button_new_with_range (0.0, 59.0, 1.0);
610 * If we have a dive selected, 'add dive' will default
611 * to one hour after the end of that dive. Otherwise,
612 * we'll just take the current time.
614 if (amount_selected == 1) {
615 time_t when = current_dive->when;
616 when += current_dive->duration.seconds;
618 time = gmtime(&when);
622 gettimeofday(&tv, NULL);
624 time = localtime(&now);
626 gtk_calendar_select_month(GTK_CALENDAR(cal), time->tm_mon, time->tm_year + 1900);
627 gtk_calendar_select_day(GTK_CALENDAR(cal), time->tm_mday);
628 gtk_spin_button_set_value(GTK_SPIN_BUTTON(h), time->tm_hour);
629 gtk_spin_button_set_value(GTK_SPIN_BUTTON(m), (time->tm_min / 5)*5);
631 gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(h), TRUE);
632 gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(m), TRUE);
634 gtk_box_pack_end(GTK_BOX(hbox), m, FALSE, FALSE, 0);
635 label = gtk_label_new(":");
636 gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0);
637 gtk_box_pack_end(GTK_BOX(hbox), h, FALSE, FALSE, 0);
639 hbox = gtk_hbox_new(TRUE, 3);
640 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
643 box = frame_box(hbox, "Duration (min)");
644 duration = gtk_spin_button_new_with_range (0.0, 1000.0, 1.0);
645 gtk_box_pack_end(GTK_BOX(box), duration, FALSE, FALSE, 0);
648 box = frame_box(hbox, "Depth (%s):", output_units.length == FEET ? "ft" : "m");
649 if (output_units.length == FEET) {
654 depth = gtk_spin_button_new_with_range (0.0, 1000.0, depthinterval);
655 gtk_box_pack_end(GTK_BOX(box), depth, FALSE, FALSE, 0);
657 /* All done, show it and wait for editing */
658 gtk_widget_show_all(dialog);
659 success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT;
661 gtk_widget_destroy(dialog);
665 memset(&tm, 0, sizeof(tm));
666 gtk_calendar_get_date(GTK_CALENDAR(cal), &yval, &mval, &dval);
671 tm.tm_hour = gtk_spin_button_get_value(GTK_SPIN_BUTTON(h));
672 tm.tm_min = gtk_spin_button_get_value(GTK_SPIN_BUTTON(m));
674 val = gtk_spin_button_get_value(GTK_SPIN_BUTTON(depth));
675 if (output_units.length == FEET) {
676 dive->maxdepth.mm = feet_to_mm(val);
678 dive->maxdepth.mm = val * 1000 + 0.5;
681 dive->duration.seconds = gtk_spin_button_get_value(GTK_SPIN_BUTTON(duration))*60;
683 gtk_widget_destroy(dialog);
684 dive->when = utc_mktime(&tm);
689 int add_new_dive(struct dive *dive)
694 if (!dive_time_widget(dive))
697 return edit_dive_info(dive);
700 GtkWidget *extended_dive_info_widget(void)
702 GtkWidget *vbox, *hbox;
703 vbox = gtk_vbox_new(FALSE, 6);
705 people_list = gtk_list_store_new(1, G_TYPE_STRING);
706 location_list = gtk_list_store_new(1, G_TYPE_STRING);
707 star_list = gtk_list_store_new(1, G_TYPE_STRING);
708 add_string_list_entry(ZERO_STARS, star_list);
709 add_string_list_entry(ONE_STARS, star_list);
710 add_string_list_entry(TWO_STARS, star_list);
711 add_string_list_entry(THREE_STARS, star_list);
712 add_string_list_entry(FOUR_STARS, star_list);
713 add_string_list_entry(FIVE_STARS, star_list);
714 suit_list = gtk_list_store_new(1, G_TYPE_STRING);
716 gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
717 location = text_value(vbox, "Location");
719 hbox = gtk_hbox_new(FALSE, 3);
720 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
722 divemaster = text_value(hbox, "Divemaster");
723 buddy = text_value(hbox, "Buddy");
725 hbox = gtk_hbox_new(FALSE, 3);
726 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
728 rating = text_value(hbox, "Rating");
729 suit = text_value(hbox, "Suit");
731 notes = text_view(vbox, "Notes", READ_ONLY);