/* info-gtk.c * creates the UI for the info frame - * controlled through the following interfaces: * * void show_dive_info(struct dive *dive) * * called from gtk-ui: * GtkWidget *extended_dive_info_widget(void) */ #include #include #include #include #include #include #include #include "dive.h" #include "display.h" #include "display-gtk.h" #include "divelist.h" #include "info.h" typedef enum { EDIT_NEW_DIVE, EDIT_ALL, EDIT_WHEN } edit_control_t; static GtkEntry *location, *buddy, *divemaster, *rating, *suit; static GtkTextView *notes; static GtkListStore *location_list, *people_list, *star_list, *suit_list; static char *get_text(GtkTextView *view) { GtkTextBuffer *buffer; GtkTextIter start; GtkTextIter end; buffer = gtk_text_view_get_buffer(view); gtk_text_buffer_get_start_iter(buffer, &start); gtk_text_buffer_get_end_iter(buffer, &end); return gtk_text_buffer_get_text(buffer, &start, &end, FALSE); } /* * Get the string from a combo box. * * The "master" string is the string of the current dive - we only consider it * changed if the old string is either empty, or matches that master string. */ static char *get_combo_box_entry_text(GtkComboBox *combo_box, char **textp, const char *master) { const char *newstring = get_active_text(combo_box); return evaluate_string_change(newstring, textp, master); } #define SET_TEXT_VALUE(x) \ gtk_entry_set_text(x, dive && dive->x ? dive->x : "") void show_dive_info(struct dive *dive) { const char *title = get_window_title(dive); gtk_window_set_title(GTK_WINDOW(main_window), title); free((void *)title); SET_TEXT_VALUE(divemaster); SET_TEXT_VALUE(buddy); SET_TEXT_VALUE(location); SET_TEXT_VALUE(suit); gtk_entry_set_text(rating, star_strings[dive ? dive->rating : 0]); gtk_text_buffer_set_text(gtk_text_view_get_buffer(notes), dive && dive->notes ? dive->notes : "", -1); /* I'm not quite sure /why/ this is only called when we have no dive * -- need to investigate */ if (!dive) show_dive_equipment(NULL, W_IDX_PRIMARY); } static void info_menu_edit_cb(GtkMenuItem *menuitem, gpointer user_data) { if (amount_selected) edit_multi_dive_info(NULL); } static void add_menu_item(GtkMenuShell *menu, const char *label, const char *icon, void (*cb)(GtkMenuItem *, gpointer)) { GtkWidget *item; if (icon) { GtkWidget *image; item = gtk_image_menu_item_new_with_label(label); image = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_MENU); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image); } else { item = gtk_menu_item_new_with_label(label); } g_signal_connect(item, "activate", G_CALLBACK(cb), NULL); gtk_widget_show(item); /* Yes, really */ gtk_menu_shell_prepend(menu, item); } static void populate_popup_cb(GtkTextView *entry, GtkMenuShell *menu, gpointer user_data) { if (amount_selected) add_menu_item(menu, _("Edit"), GTK_STOCK_EDIT, info_menu_edit_cb); } static GtkEntry *text_value(GtkWidget *box, const char *label) { GtkWidget *widget; GtkWidget *frame = gtk_frame_new(label); gtk_box_pack_start(GTK_BOX(box), frame, FALSE, TRUE, 0); widget = gtk_entry_new(); gtk_widget_set_can_focus(widget, FALSE); gtk_editable_set_editable(GTK_EDITABLE(widget), FALSE); gtk_container_add(GTK_CONTAINER(frame), widget); g_signal_connect(widget, "populate-popup", G_CALLBACK(populate_popup_cb), NULL); return GTK_ENTRY(widget); } static GtkEntry *single_text_entry(GtkWidget *box, const char *label, const char *text) { GtkEntry *entry; GtkWidget *frame = gtk_frame_new(label); gtk_box_pack_start(GTK_BOX(box), frame, FALSE, TRUE, 0); entry = GTK_ENTRY(gtk_entry_new()); gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(entry)); if (text && *text) gtk_entry_set_text(entry, text); return entry; } static GtkComboBox *text_entry(GtkWidget *box, const char *label, GtkListStore *completions, const char *text) { GtkWidget *combo_box; GtkWidget *frame = gtk_frame_new(label); gtk_box_pack_start(GTK_BOX(box), frame, FALSE, TRUE, 0); combo_box = combo_box_with_model_and_entry(completions); gtk_container_add(GTK_CONTAINER(frame), combo_box); if (text && *text) set_active_text(GTK_COMBO_BOX(combo_box), text); return GTK_COMBO_BOX(combo_box); } enum writable { READ_ONLY, READ_WRITE }; static GtkTextView *text_view(GtkWidget *box, const char *label, enum writable writable) { GtkWidget *view, *vbox; GtkWidget *frame = gtk_frame_new(label); gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 0); box = gtk_hbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(frame), box); vbox = gtk_vbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(box), vbox); GtkWidget* scrolled_window = gtk_scrolled_window_new(0, 0); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN); view = gtk_text_view_new(); if (writable == READ_ONLY) { gtk_widget_set_can_focus(view, FALSE); gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE); gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE); g_signal_connect(view, "populate-popup", G_CALLBACK(populate_popup_cb), NULL); } gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD); gtk_container_add(GTK_CONTAINER(scrolled_window), view); gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0); return GTK_TEXT_VIEW(view); } static GtkTreeIter string_entry_location; static gboolean match_string_entry(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { const char *string = data; char *entry; int cmp; gtk_tree_model_get(model, iter, 0, &entry, -1); cmp = strcmp(entry, string); if (entry) free(entry); /* Stop. The entry is bigger than the new one */ if (cmp > 0) return TRUE; /* Exact match */ if (!cmp) { found_string_entry = MATCH_EXACT; return TRUE; } string_entry_location = *iter; found_string_entry = MATCH_AFTER; return FALSE; } int match_list(GtkListStore *list, const char *string) { found_string_entry = MATCH_PREPEND; gtk_tree_model_foreach(GTK_TREE_MODEL(list), match_string_entry, (void *)string); return found_string_entry; } void add_string_list_entry(const char *string, GtkListStore *list) { GtkTreeIter *iter, loc; if (!string || !*string) return; switch (match_list(list, string)) { case MATCH_EXACT: return; case MATCH_PREPEND: iter = NULL; break; case MATCH_AFTER: iter = &string_entry_location; break; } gtk_list_store_insert_after(list, &loc, iter); gtk_list_store_set(list, &loc, 0, string, -1); } void add_people(const char *string) { add_string_list_entry(string, people_list); } void add_location(const char *string) { add_string_list_entry(string, location_list); } void add_suit(const char *string) { add_string_list_entry(string, suit_list); } /* this helper function is completely Gtk independent... but I really * hope that a new UI would just have a decent star rating widget and * we won't need this kludge... so I'll leave this in the -gtk file */ static int get_rating(const char *string) { int rating_val = 0; int i; for (i = 0; i <= 5; i++) if (!strcmp(star_strings[i],string)) rating_val = i; return rating_val; } struct dive_info { GtkComboBox *location, *divemaster, *buddy, *rating, *suit, *viz; GtkEntry *airtemp, *gps; GtkWidget *gps_icon; GtkTextView *notes; }; static void save_dive_info_changes(struct dive *dive, struct dive *master, struct dive_info *info, int tags_shown) { char *old_text, *new_text; const char *gps_text; char *rating_string; double newtemp; int changed = 0; new_text = get_combo_box_entry_text(info->location, &dive->location, master->location); if (new_text) { add_location(new_text); changed = 1; } gps_text = gtk_entry_get_text(info->gps); if (gps_changed(dive, master, gps_text)) changed = 1; new_text = get_combo_box_entry_text(info->divemaster, &dive->divemaster, master->divemaster); if (new_text) { add_people(new_text); changed = 1; } new_text = get_combo_box_entry_text(info->buddy, &dive->buddy, master->buddy); if (new_text) { add_people(new_text); changed = 1; } new_text = get_combo_box_entry_text(info->suit, &dive->suit, master->suit); if (new_text) { add_suit(new_text); changed = 1; } rating_string = strdup(star_strings[dive->rating]); new_text = get_combo_box_entry_text(info->rating, &rating_string, star_strings[master->rating]); if (new_text) { dive->rating = get_rating(rating_string); changed = 1; } free(rating_string); rating_string = strdup(star_strings[dive->visibility]); new_text = get_combo_box_entry_text(info->viz, &rating_string, star_strings[master->visibility]); if (new_text) { dive->visibility = get_rating(rating_string); changed = 1; } free(rating_string); new_text = (char *)gtk_entry_get_text(info->airtemp); if (sscanf(new_text, "%lf", &newtemp) == 1) { unsigned long mkelvin; switch (prefs.units.temperature) { case CELSIUS: mkelvin = C_to_mkelvin(newtemp); break; case FAHRENHEIT: mkelvin = F_to_mkelvin(newtemp); break; default: mkelvin = 0; } if (mkelvin != dive->airtemp.mkelvin && dive->airtemp.mkelvin == master->airtemp.mkelvin) { dive->airtemp.mkelvin = mkelvin; changed = 1; } } if (info->notes) { old_text = dive->notes; dive->notes = get_text(info->notes); if (text_changed(old_text,dive->notes)) changed = 1; if (old_text) g_free(old_text); } if (tags_shown && dive->dive_tags != master->dive_tags) { changed = 1; dive->dive_tags = master->dive_tags; /* if a dive is selected and we aren't showing invalid dives and it is * now marked as invalid we need to deselect it to keep the selection * state consistent */ if (!prefs.display_invalid_dives && dive->selected && dive->dive_tags & DTAG_INVALID) { dive->selected = 0; amount_selected--; } } if (changed) { mark_divelist_changed(TRUE); update_dive(dive); } } static void dive_trip_widget(GtkWidget *box, dive_trip_t *trip, struct dive_info *info) { GtkWidget *hbox, *label; char buffer[128] = N_("Edit trip summary"); label = gtk_label_new(_(buffer)); gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0); info->location = text_entry(box, _("Location"), location_list, trip->location); hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0); info->notes = text_view(box, _("Notes"), READ_WRITE); if (trip->notes && *trip->notes) gtk_text_buffer_set_text(gtk_text_view_get_buffer(info->notes), trip->notes, -1); } struct location_update { char text[45]; char set_by_hand; GtkEntry *entry; struct dive *dive; void (*callback)(float, float); } location_update; static void update_gps_entry(int lat, int lon) { if (location_update.entry) { print_gps_coordinates(location_update.text, 45, lat, lon); gtk_entry_set_text(location_update.entry, location_update.text); } } static void update_gps_entry_callback(float lat, float lon) { update_gps_entry(lat * 1000000, lon * 1000000); location_update.set_by_hand = 1; } static gboolean gps_map_callback(GtkWidget *w, gpointer data) { double latitude, longitude; const char *gps_text = NULL; struct dive fake_dive; memset(&fake_dive, 0, sizeof(fake_dive)); if (location_update.entry) { gps_text = gtk_entry_get_text(location_update.entry); parse_gps_text(gps_text, &latitude, &longitude); fake_dive.latitude.udeg = rint(latitude * 1000000); fake_dive.longitude.udeg = rint(longitude * 1000000); } show_gps_location(&fake_dive, update_gps_entry_callback); return TRUE; } /* * If somebody sets the string by editing the text entry, * we consider a clear string an opportunity to set things * automatically. * * A non-empty string, on the other hand, means that we * should *not* touch it when we change the location field. */ static gboolean gps_entry_change_cb(GtkEntry *gps, GdkEvent *event, gpointer userdata) { const char *string = gtk_entry_get_text(gps); /* A clear string is never considered to be "set" */ if (!string) { location_update.set_by_hand = 0; return FALSE; } /* * If it wasn't set by hand, and it hasn't changed, * it's still not set by hand */ if (!location_update.set_by_hand) { if (!strcmp(location_update.text, string)) return FALSE; } /* Otherwise, check if it's all empty.. */ while (g_ascii_isspace(*string)) string++; location_update.set_by_hand = !!*string; return FALSE; } static void location_entry_change_cb(GtkComboBox *location, gpointer *userdata) { int i; struct dive *dive; const char *name; /* * Don't do any automatic gps changes of entries that have been * explicitly set to some value! */ if (location_update.set_by_hand) return; name = get_active_text(location); for_each_dive(i, dive) { if (!dive_has_gps_location(dive)) continue; if (!dive->location || strcasecmp(dive->location, name)) continue; update_gps_entry(dive->latitude.udeg, dive->longitude.udeg); return; } update_gps_entry(0, 0); } static void set_dive_button_label(GtkWidget *button, struct dive *dive) { char buffer[256]; /* if there is only one dc and it has no samples we can edit the depth, too */ if (dive->dc.next || dive->dc.samples) divename(buffer, sizeof(buffer), dive, _("(click to edit date/time)")); else divename(buffer, sizeof(buffer), dive, _("(click to edit date/time/depth)")); gtk_button_set_label(GTK_BUTTON(button), buffer); } static int dive_time_widget(struct dive *dive, edit_control_t editing); static gboolean base_data_cb(GtkWidget *w, GdkEvent *event, gpointer _data) { struct dive *dive = _data; /* if there are more than one divecomputers or if there are any sample * then only the start time (well, date and time) can be changed, * otherwise (this is most likely a dive that was added manually in Subsurface * and we can edit duration, max and mean depth, too */ if (dive->dc.next || dive->dc.samples) dive_time_widget(dive, EDIT_WHEN); else dive_time_widget(dive, EDIT_ALL); set_dive_button_label(w, dive); return FALSE; } void divetag_toggle_cb(GtkWidget *widget, gpointer data) { int togglebit = GPOINTER_TO_INT (data); if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) edit_dive.dive_tags |= togglebit; else edit_dive.dive_tags &= ~togglebit; } static void dive_info_widget(GtkWidget *obox, struct dive *dive, struct dive_info *info, int multi, int *show_tags) { GtkWidget *hbox, *frame, *equipment, *ibox, *box, *button, *sbox, *framebox; GtkWidget *image; char buffer[256]; char airtemp[10]; const char *unit; double value; int i, tags; struct dive *otherdive; if (multi) { GtkWidget *label; snprintf(buffer, sizeof(buffer), "%s", _("Edit multiple dives")); label = gtk_label_new(buffer); gtk_box_pack_start(GTK_BOX(obox), label, FALSE, TRUE, 0); } else { GtkWidget *basedata; snprintf(buffer, sizeof(buffer), "%s", _("Edit dive")); basedata = gtk_button_new_with_label(buffer); set_dive_button_label(basedata, dive); g_signal_connect(G_OBJECT(basedata), "button-press-event", G_CALLBACK(base_data_cb), dive); gtk_box_pack_start(GTK_BOX(obox), basedata, FALSE, TRUE, 0); } /* two column layout (inner hbox ibox) within the outer vbox (obox) we are given */ ibox = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(obox), ibox, TRUE, TRUE, 0); box = gtk_vbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(ibox), box, TRUE, TRUE, 0); info->location = text_entry(box, _("Location"), location_list, dive->location); g_signal_connect(G_OBJECT(info->location), "changed", G_CALLBACK(location_entry_change_cb), NULL); hbox = gtk_hbox_new(FALSE, 2); gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0); info->gps = single_text_entry(hbox, _("GPS (WGS84 or GPS format)"), NULL); location_update.entry = info->gps; location_update.dive = dive; update_gps_entry(dive->latitude.udeg, dive->longitude.udeg); location_update.set_by_hand = !!location_update.text[0]; gtk_widget_add_events(GTK_WIDGET(info->gps), GDK_FOCUS_CHANGE_MASK); g_signal_connect(G_OBJECT(info->gps), "focus-out-event", G_CALLBACK(gps_entry_change_cb), NULL); gtk_entry_set_width_chars(info->gps, 30); info->gps_icon = gtk_button_new_with_label(_("Pick on map")); gtk_box_pack_start(GTK_BOX(hbox), info->gps_icon, FALSE, FALSE, 6); image = gtk_image_new_from_pixbuf(get_gps_icon()); gtk_image_set_pixel_size(GTK_IMAGE(image), 128); gtk_button_set_image(GTK_BUTTON(info->gps_icon), image); g_signal_connect(G_OBJECT(info->gps_icon), "clicked", G_CALLBACK(gps_map_callback), NULL); hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0); info->divemaster = text_entry(hbox, _("Dive master"), people_list, dive->divemaster); info->buddy = text_entry(hbox, _("Buddy"), people_list, dive->buddy); hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0); info->rating = text_entry(hbox, _("Rating"), star_list, star_strings[dive->rating]); info->suit = text_entry(hbox, _("Suit"), suit_list, dive->suit); hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0); info->viz = text_entry(hbox, _("Visibility"), star_list, star_strings[dive->visibility]); value = get_temp_units(dive->airtemp.mkelvin, &unit); snprintf(buffer, sizeof(buffer), _("Air Temp in %s"), unit); if (dive->airtemp.mkelvin) snprintf(airtemp, sizeof(airtemp), "%.1f", value); else airtemp[0] = '\0'; info->airtemp = single_text_entry(hbox, buffer, airtemp); frame = gtk_frame_new(_("Dive Tags")); gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 0); framebox = gtk_vbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(frame), framebox); /* we only want to show the tags if we have a single dive or if all selected * dives have the exact same set of tags (like none at all right after import) */ i = 0; *show_tags = 1; tags = dive->dive_tags; for_each_dive(i, otherdive) if (otherdive && otherdive->selected && otherdive->dive_tags != tags) *show_tags = 0; if (*show_tags) { /* check boxes for the (currently fixed) list of tags; * let's do 5 per row */ const int cols = 5; int rows = DTAG_NR / cols + (DTAG_NR % cols) ? 1 : 0; GtkWidget *table = gtk_table_new(rows, cols, TRUE); for (i = 0; i < DTAG_NR; i++) { int x = i % cols; int y = i / cols; button = gtk_check_button_new_with_label(_(dtag_names[i])); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), dive->dive_tags & (1 << i)); gtk_table_attach_defaults(GTK_TABLE(table), button, x, x+1, y, y+1); g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(divetag_toggle_cb), GINT_TO_POINTER(1 << i)); } gtk_box_pack_start(GTK_BOX(framebox), table, TRUE, FALSE, 3); } else { sbox = gtk_label_new(_("Tags are only shown if they are identical for all edited dives")); gtk_box_pack_start(GTK_BOX(framebox), sbox, TRUE, FALSE, 3); } /* only show notes if editing a single dive */ if (multi) { info->notes = NULL; } else { info->notes = text_view(box, _("Notes"), READ_WRITE); gtk_widget_set_size_request(GTK_WIDGET(info->notes), -1, 128); if (dive->notes && *dive->notes) gtk_text_buffer_set_text(gtk_text_view_get_buffer(info->notes), dive->notes, -1); } hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(ibox), hbox, TRUE, TRUE, 0); /* create a secondary Equipment widget */ frame = gtk_frame_new(_("Equipment")); equipment = equipment_widget(W_IDX_SECONDARY); gtk_container_add(GTK_CONTAINER(frame), equipment); gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0); } gboolean edit_trip(dive_trip_t *trip) { GtkWidget *dialog, *vbox; int success; gboolean changed = FALSE; char *old_text, *new_text; struct dive_info info; memset(&info, 0, sizeof(struct dive_info)); dialog = gtk_dialog_new_with_buttons(_("Edit Trip Info"), GTK_WINDOW(main_window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); gtk_window_set_default_size(GTK_WINDOW(dialog), 400, 300); vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); dive_trip_widget(vbox, trip, &info); gtk_widget_show_all(dialog); success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; if (success) { /* we need to store the edited location and notes */ new_text = get_combo_box_entry_text(info.location, &trip->location, trip->location); if (new_text) { add_location(new_text); changed = TRUE; } if (info.notes) { old_text = trip->notes; trip->notes = get_text(info.notes); if (text_changed(old_text, trip->notes)) changed = TRUE; if (old_text) g_free(old_text); } if (changed) mark_divelist_changed(TRUE); } gtk_widget_destroy(dialog); return changed; } int edit_multi_dive_info(struct dive *single_dive) { int success; GtkWidget *dialog, *vbox, *scrolled_window, *viewport; GtkRequisition size; struct dive_info info; struct dive *master; int multi; int tags_shown; scrolled_window = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); dialog = gtk_dialog_new_with_buttons(_("Dive Info"), GTK_WINDOW(main_window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0); vbox = g_object_new(GTK_TYPE_VBOX, NULL); gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window), vbox); master = single_dive; if (!master) master = current_dive; if (!master) return 0; /* See if we should use multi dive mode */ multi = FALSE; if (!single_dive) { int i; struct dive *dive; for_each_dive(i, dive) { if (dive != master && dive->selected) { multi = TRUE; break; } } } /* edit a temporary copy of the master dive; * edit_dive is a global dive structure that is modified by the * cylinder / weightsystem dialogs if we open W_IDX_SECONDARY * edit widgets as we do here */ memcpy(&edit_dive, master, sizeof(struct dive)); dive_info_widget(vbox, &edit_dive, &info, multi, &tags_shown); save_equipment_data(&edit_dive); gtk_widget_show_all(dialog); viewport = gtk_widget_get_ancestor(vbox, GTK_TYPE_VIEWPORT); #if GTK_CHECK_VERSION(3,0,0) gtk_widget_get_preferred_size(viewport, NULL, &size); #else gtk_widget_size_request(viewport, &size); #endif gtk_widget_set_size_request(scrolled_window, size.width, size.height); /* add the equipment post the "blank" layout estimate */ show_dive_equipment(&edit_dive, W_IDX_SECONDARY); success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; if (success) { mark_divelist_changed(TRUE); /* Update the non-current selected dives first */ if (!single_dive) { int i; struct dive *dive; for_each_dive(i, dive) { if (dive == master || !dive->selected) continue; /* copy all "info" fields */ save_dive_info_changes(dive, &edit_dive, &info, tags_shown); /* copy the cylinders / weightsystems */ update_equipment_data(dive, &edit_dive); /* this is extremely inefficient... it loops through all dives to find the right one - but we KNOW the index already */ update_cylinder_related_info(dive); flush_divelist(dive); } } /* Update the master dive last! */ save_dive_info_changes(master, &edit_dive, &info, tags_shown); update_equipment_data(master, &edit_dive); update_cylinder_related_info(master); /* if there was only one dive we might also have changed dive->when * or even the duration and depth information (in a dive without samples) */ if (! multi) { update_time_depth(master, &edit_dive); /* these values could have changed because of the edit - so let's * recreate them */ master->duration.seconds = 0; master->maxdepth.mm = 0; master->meandepth.mm = 0; (void)fixup_dive(master); } dive_list_update_dives(); } gtk_widget_destroy(dialog); location_update.entry = NULL; return success; } int edit_dive_info(struct dive *dive, gboolean newdive) { if (!dive || (!newdive && !amount_selected)) return 0; return edit_multi_dive_info(dive); } static GtkWidget *frame_box(GtkWidget *vbox, const char *fmt, ...) { va_list ap; char buffer[128]; GtkWidget *frame, *hbox; va_start(ap, fmt); vsnprintf(buffer, sizeof(buffer), fmt, ap); va_end(ap); frame = gtk_frame_new(buffer); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, TRUE, 0); hbox = gtk_hbox_new(0, 3); gtk_container_add(GTK_CONTAINER(frame), hbox); return hbox; } /* returns the dialog plus pointers to the calendar, hour and minute widget * plus the hbox that holds the time entry (in case the caller wants to put * a duration entry widget next to the time entry widget */ GtkWidget *create_date_time_widget(struct tm *time, GtkWidget **cal, GtkWidget **h, GtkWidget **m, GtkWidget **timehbox) { GtkWidget *dialog; GtkWidget *hbox, *vbox; GtkWidget *label; dialog = gtk_dialog_new_with_buttons(_("Date and Time"), GTK_WINDOW(main_window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); /* Calendar hbox */ hbox = frame_box(vbox, _("Date:")); *cal = gtk_calendar_new(); gtk_box_pack_start(GTK_BOX(hbox), *cal, FALSE, TRUE, 0); /* Time hbox */ *timehbox = gtk_hbox_new(TRUE, 3); gtk_box_pack_start(GTK_BOX(vbox), *timehbox, FALSE, FALSE, 0); hbox = frame_box(*timehbox, _("Time")); *h = gtk_spin_button_new_with_range (0.0, 23.0, 1.0); *m = gtk_spin_button_new_with_range (0.0, 59.0, 1.0); gtk_calendar_select_month(GTK_CALENDAR(*cal), time->tm_mon, time->tm_year + 1900); gtk_calendar_select_day(GTK_CALENDAR(*cal), time->tm_mday); gtk_spin_button_set_value(GTK_SPIN_BUTTON(*h), time->tm_hour); gtk_spin_button_set_value(GTK_SPIN_BUTTON(*m), time->tm_min); gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(*h), TRUE); gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(*m), TRUE); gtk_box_pack_end(GTK_BOX(hbox), *m, FALSE, FALSE, 0); label = gtk_label_new(":"); gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0); gtk_box_pack_end(GTK_BOX(hbox), *h, FALSE, FALSE, 0); return dialog; } static int mm_from_spinbutton(GtkWidget *depth) { int result; double val = gtk_spin_button_get_value(GTK_SPIN_BUTTON(depth)); if (prefs.units.length == FEET) { result = feet_to_mm(val); } else { result = val * 1000 + 0.5; } return result; } static int dive_time_widget(struct dive *dive, edit_control_t editing) { GtkWidget *dialog; GtkWidget *cal, *vbox, *hbox, *box; GtkWidget *h, *m; GtkWidget *duration, *depth, *avgdepth; guint yval, mval, dval; struct tm tm, *time; int success; double depthinterval; /* * If we have a dive selected, 'add dive' will default * to one hour after the end of that dive. Otherwise, * we'll just take the current time. */ if (editing != EDIT_NEW_DIVE) { utc_mkdate(dive->when, &tm); time = &tm; } else if (amount_selected == 1) { timestamp_t when = current_dive->when; when += current_dive->duration.seconds; when += 60*60; utc_mkdate(when, &tm); time = &tm; } else { time_t now; struct timeval tv; gettimeofday(&tv, NULL); now = tv.tv_sec; time = localtime(&now); } dialog = create_date_time_widget(time, &cal, &h, &m, &hbox); vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); if (editing != EDIT_WHEN) { /* Duration box */ box = frame_box(hbox, _("Duration (min)")); duration = gtk_spin_button_new_with_range (0.0, 1000.0, 1.0); if (editing != EDIT_NEW_DIVE) gtk_spin_button_set_value(GTK_SPIN_BUTTON(duration), dive->dc.duration.seconds / 60.0); gtk_box_pack_end(GTK_BOX(box), duration, FALSE, FALSE, 0); hbox = gtk_hbox_new(TRUE, 3); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); /* Depth box */ box = frame_box(hbox, _("Max Depth (%s):"), prefs.units.length == FEET ? _("ft") : _("m")); if (prefs.units.length == FEET) { depthinterval = 1.0; } else { depthinterval = 0.1; } depth = gtk_spin_button_new_with_range (0.0, 1000.0, depthinterval); if (editing != EDIT_NEW_DIVE) { if (prefs.units.length == FEET) { gtk_spin_button_set_value(GTK_SPIN_BUTTON(depth), mm_to_feet(dive->dc.maxdepth.mm)); } else { gtk_spin_button_set_value(GTK_SPIN_BUTTON(depth), dive->dc.maxdepth.mm / 1000.0); } } gtk_box_pack_end(GTK_BOX(box), depth, FALSE, FALSE, 0); box = frame_box(hbox, _("Avg Depth (%s):"), prefs.units.length == FEET ? _("ft") : _("m")); if (prefs.units.length == FEET) { depthinterval = 1.0; } else { depthinterval = 0.1; } avgdepth = gtk_spin_button_new_with_range (0.0, 1000.0, depthinterval); if (editing != EDIT_NEW_DIVE) { if (prefs.units.length == FEET) { gtk_spin_button_set_value(GTK_SPIN_BUTTON(avgdepth), mm_to_feet(dive->dc.meandepth.mm)); } else { gtk_spin_button_set_value(GTK_SPIN_BUTTON(avgdepth), dive->dc.meandepth.mm / 1000.0); } } gtk_box_pack_end(GTK_BOX(box), avgdepth, FALSE, FALSE, 0); } /* All done, show it and wait for editing */ gtk_widget_show_all(dialog); success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; if (!success) { gtk_widget_destroy(dialog); return 0; } memset(&tm, 0, sizeof(tm)); gtk_calendar_get_date(GTK_CALENDAR(cal), &yval, &mval, &dval); tm.tm_year = yval; tm.tm_mon = mval; tm.tm_mday = dval; tm.tm_hour = gtk_spin_button_get_value(GTK_SPIN_BUTTON(h)); tm.tm_min = gtk_spin_button_get_value(GTK_SPIN_BUTTON(m)); if (editing != EDIT_WHEN) { dive->dc.maxdepth.mm = mm_from_spinbutton(depth); dive->dc.meandepth.mm = mm_from_spinbutton(avgdepth); dive->dc.duration.seconds = gtk_spin_button_get_value(GTK_SPIN_BUTTON(duration))*60; } gtk_widget_destroy(dialog); dive->when = utc_mktime(&tm); return 1; } int add_new_dive(struct dive *dive) { if (!dive) return 0; if (!dive_time_widget(dive, EDIT_NEW_DIVE)) return 0; return edit_dive_info(dive, TRUE); } GtkWidget *extended_dive_info_widget(void) { GtkWidget *vbox, *hbox; vbox = gtk_vbox_new(FALSE, 6); people_list = gtk_list_store_new(1, G_TYPE_STRING); location_list = gtk_list_store_new(1, G_TYPE_STRING); star_list = gtk_list_store_new(1, G_TYPE_STRING); add_string_list_entry(star_strings[0], star_list); add_string_list_entry(star_strings[1], star_list); add_string_list_entry(star_strings[2], star_list); add_string_list_entry(star_strings[3], star_list); add_string_list_entry(star_strings[4], star_list); add_string_list_entry(star_strings[5], star_list); suit_list = gtk_list_store_new(1, G_TYPE_STRING); gtk_container_set_border_width(GTK_CONTAINER(vbox), 6); location = text_value(vbox, _("Location")); hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); divemaster = text_value(hbox, _("Divemaster")); buddy = text_value(hbox, _("Buddy")); hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); rating = text_value(hbox, _("Rating")); suit = text_value(hbox, _("Suit")); notes = text_view(vbox, _("Notes"), READ_ONLY); return vbox; } void info_widget_destroy(void) { g_object_unref(people_list); g_object_unref(location_list); g_object_unref(star_list); g_object_unref(suit_list); }