diff options
author | Dirk Hohndel <dirk@hohndel.org> | 2013-01-07 11:23:14 -0800 |
---|---|---|
committer | Dirk Hohndel <dirk@hohndel.org> | 2013-01-07 11:23:14 -0800 |
commit | d3570508b102b238d5c2258e522b4bcea26e44a7 (patch) | |
tree | 11bf324032a47c7a5bf807e3b4a36c2f949f305d /planner.c | |
parent | 989cf37fcf0453b72f3d542f202cf532bf1532db (diff) | |
download | subsurface-d3570508b102b238d5c2258e522b4bcea26e44a7.tar.gz |
Move planner UI into planner.c
There should be NO other changes in this commit - just moving the code and
adjusting the includes (and adding the entry point to display-gtk.h).
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
Diffstat (limited to 'planner.c')
-rw-r--r-- | planner.c | 461 |
1 files changed, 460 insertions, 1 deletions
@@ -4,9 +4,13 @@ * * (c) Dirk Hohndel 2013 */ - +#include <libintl.h> +#include <glib/gi18n.h> +#include <unistd.h> +#include <ctype.h> #include "dive.h" #include "divelist.h" +#include "display-gtk.h" int stoplevels[] = { 0, 3000, 6000, 9000, 12000, 15000, 21000, 30000, 42000, 60000, 90000 }; @@ -285,3 +289,458 @@ void plan(struct diveplan *diveplan, char **cached_datap, struct dive **divep) report_dives(FALSE, FALSE); select_last_dive(); } + + +/* and now the UI for all this */ +/* + * Get a value in tenths (so "10.2" == 102, "9" = 90) + * + * Return negative for errors. + */ +static int get_tenths(char *begin, char **end) +{ + int value = strtol(begin, end, 10); + if (begin == *end) + return -1; + value *= 10; + + /* Fraction? We only look at the first digit */ + if (**end == '.') { + ++*end; + if (!isdigit(**end)) + return -1; + value += **end - '0'; + do { + ++*end; + } while (isdigit(**end)); + } + return value; +} + +static int get_permille(char *begin, char **end) +{ + int value = get_tenths(begin, end); + if (value >= 0) { + /* Allow a percentage sign */ + if (**end == '%') + ++*end; + } + return value; +} + +static int validate_gas(char *text, int *o2_p, int *he_p) +{ + int o2, he; + + if (!text) + return 0; + + while (isspace(*text)) + text++; + + if (!*text) { + o2 = AIR_PERMILLE; he = 0; + } else if (!strcasecmp(text, "air")) { + o2 = AIR_PERMILLE; he = 0; text += 3; + } else if (!strncasecmp(text, "ean", 3)) { + o2 = get_permille(text+3, &text); he = 0; + } else { + o2 = get_permille(text, &text); he = 0; + if (*text == '/') + he = get_permille(text+1, &text); + } + + /* We don't want any extra crud */ + while (isspace(*text)) + text++; + if (*text) + return 0; + + /* Validate the gas mix */ + if (*text || o2 < 1 || o2 > 1000 || he < 0 || o2+he > 1000) + return 0; + + /* Let it rip */ + *o2_p = o2; + *he_p = he; + return 1; +} + +static int validate_time(char *text, int *sec_p, int *rel_p) +{ + int min, sec, rel; + char *end; + + if (!text) + return 0; + + while (isspace(*text)) + text++; + + rel = 0; + if (*text == '+') { + rel = 1; + text++; + while (isspace(*text)) + text++; + } + + min = strtol(text, &end, 10); + if (text == end) + return 0; + + if (min < 0 || min > 1000) + return 0; + + /* Ok, minutes look ok */ + text = end; + sec = 0; + if (*text == ':') { + text++; + sec = strtol(text, &end, 10); + if (end != text+2) + return 0; + if (sec < 0) + return 0; + text = end; + if (*text == ':') { + if (sec >= 60) + return 0; + min = min*60 + sec; + text++; + sec = strtol(text, &end, 10); + if (end != text+2) + return 0; + if (sec < 0) + return 0; + text = end; + } + } + + /* Maybe we should accept 'min' at the end? */ + if (isspace(*text)) + text++; + if (*text) + return 0; + + *sec_p = min*60 + sec; + *rel_p = rel; + return 1; +} + +static int validate_depth(char *text, int *mm_p) +{ + int depth, imperial; + + if (!text) + return 0; + + depth = get_tenths(text, &text); + if (depth < 0) + return 0; + + while (isspace(*text)) + text++; + + imperial = get_output_units()->length == FEET; + if (*text == 'm') { + imperial = 0; + text++; + } else if (!strcasecmp(text, "ft")) { + imperial = 1; + text += 2; + } + while (isspace(*text)) + text++; + if (*text) + return 0; + + if (imperial) { + depth = feet_to_mm(depth / 10.0); + } else { + depth *= 100; + } + *mm_p = depth; + return 1; +} + +static GtkWidget *add_entry_to_box(GtkWidget *box, const char *label) +{ + GtkWidget *entry, *frame; + + entry = gtk_entry_new(); + gtk_entry_set_max_length(GTK_ENTRY(entry), 16); + if (label) { + frame = gtk_frame_new(label); + gtk_container_add(GTK_CONTAINER(frame), entry); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 0); + } else { + gtk_box_pack_start(GTK_BOX(box), entry, FALSE, FALSE, 2); + } + return entry; +} + +#define MAX_WAYPOINTS 8 +GtkWidget *entry_depth[MAX_WAYPOINTS], *entry_duration[MAX_WAYPOINTS], *entry_gas[MAX_WAYPOINTS]; +int nr_waypoints = 0; +static GtkListStore *gas_model = NULL; +struct diveplan diveplan = {}; +char *cache_data = NULL; +struct dive *planned_dive = NULL; + +/* make a copy of the diveplan so far and display the corresponding dive */ +void show_planned_dive(void) +{ + struct diveplan tempplan; + struct divedatapoint *dp, **dpp; + + memcpy(&tempplan, &diveplan, sizeof(struct diveplan)); + dpp = &tempplan.dp; + dp = diveplan.dp; + while(*dpp) { + *dpp = malloc(sizeof(struct divedatapoint)); + memcpy(*dpp, dp, sizeof(struct divedatapoint)); + dp = dp->next; + if (dp && !dp->time) { + /* we have an incomplete entry - stop before it */ + (*dpp)->next = NULL; + break; + } + dpp = &(*dpp)->next; + } + plan(&tempplan, &cache_data, &planned_dive); + free_dps(tempplan.dp); +} + +static gboolean gas_focus_out_cb(GtkWidget *entry, GdkEvent *event, gpointer data) +{ + char *gastext; + int o2, he; + int idx = data - NULL; + + gastext = strdup(gtk_entry_get_text(GTK_ENTRY(entry))); + if (validate_gas(gastext, &o2, &he)) { + add_string_list_entry(gastext, gas_model); + add_gas_to_nth_dp(&diveplan, idx, o2, he); + show_planned_dive(); + } else { + /* we need to instead change the color of the input field or something */ + printf("invalid gas for row %d\n",idx); + } + free(gastext); + return FALSE; +} + +static gboolean depth_focus_out_cb(GtkWidget *entry, GdkEvent *event, gpointer data) +{ + char *depthtext; + int depth; + int idx = data - NULL; + + depthtext = strdup(gtk_entry_get_text(GTK_ENTRY(entry))); + if (validate_depth(depthtext, &depth)) { + add_depth_to_nth_dp(&diveplan, idx, depth); + show_planned_dive(); + } else { + /* we need to instead change the color of the input field or something */ + printf("invalid depth for row %d\n", idx); + } + free(depthtext); + return FALSE; +} + +static gboolean duration_focus_out_cb(GtkWidget *entry, GdkEvent * event, gpointer data) +{ + char *durationtext; + int duration, is_rel; + int idx = data - NULL; + + durationtext = strdup(gtk_entry_get_text(GTK_ENTRY(entry))); + if (validate_time(durationtext, &duration, &is_rel)) { + add_duration_to_nth_dp(&diveplan, idx, duration, is_rel); + show_planned_dive(); + } else { + /* we need to instead change the color of the input field or something */ + printf("invalid duration for row %d\n", idx); + } + free(durationtext); + return FALSE; +} + +static gboolean starttime_focus_out_cb(GtkWidget *entry, GdkEvent * event, gpointer data) +{ + char *starttimetext; + int starttime, is_rel; + + starttimetext = strdup(gtk_entry_get_text(GTK_ENTRY(entry))); + if (validate_time(starttimetext, &starttime, &is_rel)) { + /* we alway make this relative for now */ + diveplan.when = time(NULL) + starttime; + } else { + /* we need to instead change the color of the input field or something */ + printf("invalid starttime\n"); + } + free(starttimetext); + return FALSE; +} + +static GtkWidget *add_gas_combobox_to_box(GtkWidget *box, const char *label) +{ + GtkWidget *frame, *combo; + GtkEntryCompletion *completion; + GtkEntry *entry; + + if (!gas_model) { + gas_model = gtk_list_store_new(1, G_TYPE_STRING); + add_string_list_entry("AIR", gas_model); + add_string_list_entry("EAN32", gas_model); + add_string_list_entry("EAN36 @ 1.6", gas_model); + } + combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(gas_model), 0); + gtk_widget_add_events(combo, GDK_FOCUS_CHANGE_MASK); + g_signal_connect(gtk_bin_get_child(GTK_BIN(combo)), "focus-out-event", G_CALLBACK(gas_focus_out_cb), NULL); + + if (label) { + frame = gtk_frame_new(label); + gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 0); + gtk_container_add(GTK_CONTAINER(frame), combo); + } else { + gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 2); + } + entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo))); + completion = gtk_entry_completion_new(); + gtk_entry_completion_set_text_column(completion, 0); + gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(gas_model)); + gtk_entry_completion_set_inline_completion(completion, TRUE); + gtk_entry_completion_set_inline_selection(completion, TRUE); + gtk_entry_completion_set_popup_single_match(completion, FALSE); + gtk_entry_set_completion(entry, completion); + g_object_unref(completion); + + return combo; +} + +static void add_waypoint_widgets(GtkWidget *box, int idx) +{ + GtkWidget *hbox; + + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0); + if (idx == 0) { + entry_depth[idx] = add_entry_to_box(hbox, _("Ending Depth")); + entry_duration[idx] = add_entry_to_box(hbox, _("Segment Time")); + entry_gas[idx] = add_gas_combobox_to_box(hbox, _("Gas Used")); + } else { + entry_depth[idx] = add_entry_to_box(hbox, NULL); + entry_duration[idx] = add_entry_to_box(hbox, NULL); + entry_gas[idx] = add_gas_combobox_to_box(hbox, NULL); + } + gtk_widget_add_events(entry_depth[idx], GDK_FOCUS_CHANGE_MASK); + g_signal_connect(entry_depth[idx], "focus-out-event", G_CALLBACK(depth_focus_out_cb), NULL + idx); + gtk_widget_add_events(entry_duration[idx], GDK_FOCUS_CHANGE_MASK); + g_signal_connect(entry_duration[idx], "focus-out-event", G_CALLBACK(duration_focus_out_cb), NULL + idx); +} + +static void add_waypoint_cb(GtkButton *button, gpointer _data) +{ + GtkWidget *vbox = _data; + if (nr_waypoints < MAX_WAYPOINTS) { + GtkWidget *ovbox, *dialog; + add_waypoint_widgets(vbox, nr_waypoints); + nr_waypoints++; + ovbox = gtk_widget_get_parent(GTK_WIDGET(button)); + dialog = gtk_widget_get_parent(ovbox); + gtk_widget_show_all(dialog); + } else { + // some error + } +} + +void input_plan() +{ + GtkWidget *planner, *content, *vbox, *outervbox, *add_row, *deltat; + int lasttime = 0; + char starttimebuf[64] = "+60:00"; + + if (diveplan.dp) + free_dps(diveplan.dp); + memset(&diveplan, 0, sizeof(diveplan)); + planned_dive = NULL; + planner = gtk_dialog_new_with_buttons(_("Dive Plan - THIS IS JUST A SIMULATION; DO NOT USE FOR DIVING"), + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + NULL); + + content = gtk_dialog_get_content_area (GTK_DIALOG (planner)); + outervbox = gtk_vbox_new(FALSE, 2); + gtk_container_add (GTK_CONTAINER (content), outervbox); + vbox = gtk_vbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(outervbox), vbox, TRUE, TRUE, 0); + deltat = add_entry_to_box(vbox, _("Dive starts in how many minutes?")); + gtk_entry_set_max_length(GTK_ENTRY(deltat), 12); + gtk_entry_set_text(GTK_ENTRY(deltat), starttimebuf); + gtk_widget_add_events(deltat, GDK_FOCUS_CHANGE_MASK); + g_signal_connect(deltat, "focus-out-event", G_CALLBACK(starttime_focus_out_cb), NULL); + diveplan.when = time(NULL) + 3600; + nr_waypoints = 4; + add_waypoint_widgets(vbox, 0); + add_waypoint_widgets(vbox, 1); + add_waypoint_widgets(vbox, 2); + add_waypoint_widgets(vbox, 3); + add_row = gtk_button_new_with_label(_("Add waypoint")); + g_signal_connect(G_OBJECT(add_row), "clicked", G_CALLBACK(add_waypoint_cb), vbox); + gtk_box_pack_start(GTK_BOX(outervbox), add_row, FALSE, FALSE, 0); + gtk_widget_show_all(planner); + if (gtk_dialog_run(GTK_DIALOG(planner)) == GTK_RESPONSE_ACCEPT) { + int i; + const char *deltattext; + + deltattext = gtk_entry_get_text(GTK_ENTRY(deltat)); + diveplan.when = time(NULL) + 60 * atoi(deltattext); + free_dps(diveplan.dp); + diveplan.dp = 0; + for (i = 0; i < nr_waypoints; i++) { + char *depthtext, *durationtext, *gastext; + int depth, duration, o2, he, is_rel; + GtkWidget *entry; + + depthtext = strdup(gtk_entry_get_text(GTK_ENTRY(entry_depth[i]))); + if (!validate_depth(depthtext, &depth)) { + // mark error and redo? + free(depthtext); + continue; + } + durationtext = strdup(gtk_entry_get_text(GTK_ENTRY(entry_duration[i]))); + if (!validate_time(durationtext, &duration, &is_rel)) { + // mark error and redo? + free(durationtext); + continue; + } + if (!is_rel) + duration -= lasttime; + entry = gtk_bin_get_child(GTK_BIN(entry_gas[i])); + gastext = strdup(gtk_entry_get_text(GTK_ENTRY(entry))); + if (!validate_gas(gastext, &o2, &he)) { + // mark error and redo? + free(gastext); + continue; + } + /* just in case this didn't get added by the callback */ + add_string_list_entry(gastext, gas_model); + + // still need to handle desired pO2 and a setpoint (for CC) + + if (duration == 0) + break; + plan_add_segment(&diveplan, duration, depth, o2, he); + lasttime += duration; + free(depthtext); + free(durationtext); + free(gastext); + } + } + show_planned_dive(); + gtk_widget_destroy(planner); +} |