2013-01-05 07:11:42 +00:00
|
|
|
/* planner.c
|
|
|
|
*
|
|
|
|
* code that allows us to plan future dives
|
|
|
|
*
|
|
|
|
* (c) Dirk Hohndel 2013
|
|
|
|
*/
|
2013-01-07 19:23:14 +00:00
|
|
|
#include <libintl.h>
|
|
|
|
#include <glib/gi18n.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <ctype.h>
|
2013-01-05 07:11:42 +00:00
|
|
|
#include "dive.h"
|
|
|
|
#include "divelist.h"
|
2013-01-07 19:23:14 +00:00
|
|
|
#include "display-gtk.h"
|
2013-01-05 07:11:42 +00:00
|
|
|
|
2013-01-07 06:09:12 +00:00
|
|
|
int stoplevels[] = { 0, 3000, 6000, 9000, 12000, 15000, 21000, 30000, 42000, 60000, 90000 };
|
2013-01-05 07:11:42 +00:00
|
|
|
|
2013-01-07 20:49:07 +00:00
|
|
|
#if DEBUG_PLAN
|
|
|
|
void dump_plan(struct diveplan *diveplan)
|
|
|
|
{
|
|
|
|
struct divedatapoint *dp;
|
|
|
|
struct tm tm;
|
|
|
|
|
|
|
|
if (!diveplan) {
|
|
|
|
printf ("Diveplan NULL\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
utc_mkdate(diveplan->when, &tm);
|
|
|
|
|
|
|
|
printf("Diveplan @ %04d-%02d-%02d %02d:%02d:%02d (surfpres %dmbar):\n",
|
|
|
|
tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
|
|
|
|
tm.tm_hour, tm.tm_min, tm.tm_sec,
|
|
|
|
diveplan->surface_pressure);
|
|
|
|
dp = diveplan->dp;
|
|
|
|
while (dp) {
|
|
|
|
printf("\t%3u:%02u: %dmm gas: %d o2 %d h2\n", FRACTION(dp->time, 60), dp->depth, dp->o2, dp->he);
|
|
|
|
dp = dp->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2013-01-05 07:11:42 +00:00
|
|
|
/* returns the tissue tolerance at the end of this (partial) dive */
|
2013-01-06 19:13:46 +00:00
|
|
|
double tissue_at_end(struct dive *dive, char **cached_datap)
|
2013-01-05 07:11:42 +00:00
|
|
|
{
|
|
|
|
struct divecomputer *dc;
|
|
|
|
struct sample *sample, *psample;
|
|
|
|
int i, j, t0, t1;
|
|
|
|
double tissue_tolerance;
|
|
|
|
|
|
|
|
if (!dive)
|
|
|
|
return 0.0;
|
2013-01-06 19:13:46 +00:00
|
|
|
if (*cached_datap) {
|
|
|
|
tissue_tolerance = restore_deco_state(*cached_datap);
|
|
|
|
} else {
|
|
|
|
tissue_tolerance = init_decompression(dive);
|
|
|
|
cache_deco_state(tissue_tolerance, cached_datap);
|
|
|
|
}
|
2013-01-05 07:11:42 +00:00
|
|
|
dc = &dive->dc;
|
|
|
|
if (!dc->samples)
|
2013-01-06 19:13:46 +00:00
|
|
|
return tissue_tolerance;
|
2013-01-05 07:11:42 +00:00
|
|
|
psample = sample = dc->sample;
|
|
|
|
t0 = 0;
|
|
|
|
for (i = 0; i < dc->samples; i++, sample++) {
|
|
|
|
t1 = sample->time.seconds;
|
|
|
|
for (j = t0; j < t1; j++) {
|
|
|
|
int depth = psample->depth.mm + (j - t0) * (sample->depth.mm - psample->depth.mm) / (t1 - t0);
|
|
|
|
tissue_tolerance = add_segment(depth_to_mbar(depth, dive) / 1000.0,
|
|
|
|
&dive->cylinder[sample->sensor].gasmix, 1, sample->po2);
|
|
|
|
}
|
|
|
|
psample = sample;
|
|
|
|
t0 = t1;
|
|
|
|
}
|
|
|
|
return tissue_tolerance;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* how many seconds until we can ascend to the next stop? */
|
2013-01-06 19:13:46 +00:00
|
|
|
int time_at_last_depth(struct dive *dive, int next_stop, char **cached_data_p)
|
2013-01-05 07:11:42 +00:00
|
|
|
{
|
|
|
|
int depth;
|
|
|
|
double surface_pressure, tissue_tolerance;
|
|
|
|
int wait = 0;
|
|
|
|
struct sample *sample;
|
|
|
|
|
|
|
|
if (!dive)
|
|
|
|
return 0;
|
|
|
|
surface_pressure = dive->surface_pressure.mbar / 1000.0;
|
2013-01-06 19:13:46 +00:00
|
|
|
tissue_tolerance = tissue_at_end(dive, cached_data_p);
|
2013-01-05 07:11:42 +00:00
|
|
|
sample = &dive->dc.sample[dive->dc.samples - 1];
|
|
|
|
depth = sample->depth.mm;
|
|
|
|
while (deco_allowed_depth(tissue_tolerance, surface_pressure, dive, 1) > next_stop) {
|
|
|
|
wait++;
|
|
|
|
tissue_tolerance = add_segment(depth_to_mbar(depth, dive) / 1000.0,
|
|
|
|
&dive->cylinder[sample->sensor].gasmix, 1, sample->po2);
|
|
|
|
}
|
|
|
|
return wait;
|
|
|
|
}
|
|
|
|
|
|
|
|
int add_gas(struct dive *dive, int o2, int he)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
struct gasmix *mix;
|
|
|
|
cylinder_t *cyl;
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_CYLINDERS; i++) {
|
|
|
|
cyl = dive->cylinder + i;
|
|
|
|
if (cylinder_nodata(cyl))
|
|
|
|
break;
|
|
|
|
mix = &cyl->gasmix;
|
|
|
|
if (o2 == mix->o2.permille && he == mix->he.permille)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
if (i == MAX_CYLINDERS) {
|
|
|
|
printf("too many cylinders\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
mix = &cyl->gasmix;
|
|
|
|
mix->o2.permille = o2;
|
|
|
|
mix->he.permille = he;
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct dive *create_dive_from_plan(struct diveplan *diveplan)
|
|
|
|
{
|
|
|
|
struct dive *dive;
|
|
|
|
struct divedatapoint *dp;
|
|
|
|
struct divecomputer *dc;
|
2013-01-07 21:13:10 +00:00
|
|
|
int oldo2 = AIR_PERMILLE, oldhe = 0;
|
2013-01-05 07:11:42 +00:00
|
|
|
|
|
|
|
if (!diveplan || !diveplan->dp)
|
|
|
|
return NULL;
|
|
|
|
dive = alloc_dive();
|
|
|
|
dive->when = diveplan->when;
|
|
|
|
dive->surface_pressure.mbar = diveplan->surface_pressure;
|
|
|
|
dc = &dive->dc;
|
|
|
|
dc->model = "Simulated Dive";
|
|
|
|
dp = diveplan->dp;
|
2013-01-07 21:13:10 +00:00
|
|
|
|
2013-01-07 16:38:55 +00:00
|
|
|
/* let's start with the gas given on the first segment */
|
2013-01-07 21:13:10 +00:00
|
|
|
if (dp->o2 || dp->he) {
|
|
|
|
oldo2 = dp->o2;
|
|
|
|
oldhe = dp->he;
|
|
|
|
}
|
|
|
|
add_gas(dive, oldo2, oldhe);
|
|
|
|
while (dp) {
|
|
|
|
int o2 = dp->o2, he = dp->he;
|
|
|
|
int time = dp->time;
|
|
|
|
int depth = dp->depth;
|
|
|
|
struct sample *sample;
|
|
|
|
|
|
|
|
if (!o2 && !he) {
|
|
|
|
o2 = oldo2;
|
|
|
|
he = oldhe;
|
2013-01-07 16:38:55 +00:00
|
|
|
}
|
2013-01-07 21:13:10 +00:00
|
|
|
|
|
|
|
/* Create new gas, and gas change event if necessary */
|
|
|
|
if (o2 != oldo2 || he != oldhe) {
|
|
|
|
int value = (o2 / 10) | (he / 10 << 16);
|
|
|
|
add_gas(dive, o2, he);
|
|
|
|
add_event(dc, time, 11, 0, value, "gaschange");
|
|
|
|
oldo2 = o2; oldhe = he;
|
2013-01-05 07:11:42 +00:00
|
|
|
}
|
2013-01-07 21:13:10 +00:00
|
|
|
|
|
|
|
/* Create sample */
|
2013-01-05 07:11:42 +00:00
|
|
|
sample = prepare_sample(dc);
|
2013-01-07 21:13:10 +00:00
|
|
|
sample->time.seconds = time;
|
|
|
|
sample->depth.mm = depth;
|
|
|
|
finish_sample(dc);
|
|
|
|
|
2013-01-05 07:11:42 +00:00
|
|
|
dp = dp->next;
|
|
|
|
}
|
2013-01-07 06:09:12 +00:00
|
|
|
if (dc->samples == 0) {
|
|
|
|
/* not enough there yet to create a dive - most likely the first time is missing */
|
|
|
|
free(dive);
|
|
|
|
dive = NULL;
|
|
|
|
}
|
2013-01-05 07:11:42 +00:00
|
|
|
return dive;
|
|
|
|
}
|
|
|
|
|
2013-01-07 06:09:12 +00:00
|
|
|
void free_dps(struct divedatapoint *dp)
|
|
|
|
{
|
|
|
|
while (dp) {
|
|
|
|
struct divedatapoint *ndp = dp->next;
|
|
|
|
free(dp);
|
|
|
|
dp = ndp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-05 07:11:42 +00:00
|
|
|
struct divedatapoint *create_dp(int time_incr, int depth, int o2, int he)
|
|
|
|
{
|
|
|
|
struct divedatapoint *dp;
|
|
|
|
|
|
|
|
dp = malloc(sizeof(struct divedatapoint));
|
|
|
|
dp->time = time_incr;
|
|
|
|
dp->depth = depth;
|
|
|
|
dp->o2 = o2;
|
|
|
|
dp->he = he;
|
|
|
|
dp->next = NULL;
|
|
|
|
return dp;
|
|
|
|
}
|
|
|
|
|
2013-01-07 06:09:12 +00:00
|
|
|
struct divedatapoint *get_nth_dp(struct diveplan *diveplan, int idx)
|
|
|
|
{
|
|
|
|
struct divedatapoint **ldpp, *dp = diveplan->dp;
|
|
|
|
int i = 0;
|
|
|
|
ldpp = &diveplan->dp;
|
|
|
|
|
|
|
|
while (dp && i++ < idx) {
|
|
|
|
ldpp = &dp->next;
|
|
|
|
dp = dp->next;
|
|
|
|
}
|
|
|
|
while (i++ <= idx) {
|
|
|
|
*ldpp = dp = create_dp(0, 0, 0, 0);
|
|
|
|
ldpp = &((*ldpp)->next);
|
|
|
|
}
|
|
|
|
return dp;
|
|
|
|
}
|
|
|
|
|
2013-01-07 16:13:23 +00:00
|
|
|
void add_duration_to_nth_dp(struct diveplan *diveplan, int idx, int duration, gboolean is_rel)
|
2013-01-07 06:09:12 +00:00
|
|
|
{
|
2013-01-07 16:13:23 +00:00
|
|
|
struct divedatapoint *pdp, *dp = get_nth_dp(diveplan, idx);
|
2013-01-08 00:21:26 +00:00
|
|
|
if (idx > 0) {
|
2013-01-07 16:13:23 +00:00
|
|
|
pdp = get_nth_dp(diveplan, idx - 1);
|
2013-01-08 00:21:26 +00:00
|
|
|
if (is_rel || dp->time <= pdp->time)
|
|
|
|
duration += pdp->time;
|
2013-01-07 16:13:23 +00:00
|
|
|
}
|
2013-01-07 06:09:12 +00:00
|
|
|
dp->time = duration;
|
|
|
|
}
|
|
|
|
|
|
|
|
void add_depth_to_nth_dp(struct diveplan *diveplan, int idx, int depth)
|
|
|
|
{
|
|
|
|
struct divedatapoint *dp = get_nth_dp(diveplan, idx);
|
|
|
|
dp->depth = depth;
|
|
|
|
}
|
|
|
|
|
|
|
|
void add_gas_to_nth_dp(struct diveplan *diveplan, int idx, int o2, int he)
|
|
|
|
{
|
|
|
|
struct divedatapoint *dp = get_nth_dp(diveplan, idx);
|
2013-01-07 21:13:10 +00:00
|
|
|
dp->o2 = o2;
|
|
|
|
dp->he = he;
|
2013-01-07 06:09:12 +00:00
|
|
|
}
|
2013-01-05 07:11:42 +00:00
|
|
|
void add_to_end_of_diveplan(struct diveplan *diveplan, struct divedatapoint *dp)
|
|
|
|
{
|
|
|
|
struct divedatapoint **lastdp = &diveplan->dp;
|
|
|
|
struct divedatapoint *ldp = *lastdp;
|
|
|
|
while(*lastdp) {
|
|
|
|
ldp = *lastdp;
|
|
|
|
lastdp = &(*lastdp)->next;
|
|
|
|
}
|
|
|
|
*lastdp = dp;
|
|
|
|
if (ldp)
|
|
|
|
dp->time += ldp->time;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plan_add_segment(struct diveplan *diveplan, int duration, int depth, int o2, int he)
|
|
|
|
{
|
|
|
|
struct divedatapoint *dp = create_dp(duration, depth, o2, he);
|
|
|
|
add_to_end_of_diveplan(diveplan, dp);
|
|
|
|
}
|
|
|
|
|
2013-01-07 06:09:12 +00:00
|
|
|
void plan(struct diveplan *diveplan, char **cached_datap, struct dive **divep)
|
2013-01-05 07:11:42 +00:00
|
|
|
{
|
|
|
|
struct dive *dive;
|
|
|
|
struct sample *sample;
|
|
|
|
int wait_time, o2, he;
|
|
|
|
int ceiling, depth, transitiontime;
|
|
|
|
int stopidx;
|
|
|
|
double tissue_tolerance;
|
|
|
|
|
|
|
|
if (!diveplan->surface_pressure)
|
|
|
|
diveplan->surface_pressure = 1013;
|
2013-01-07 06:09:12 +00:00
|
|
|
if (*divep)
|
|
|
|
delete_single_dive(dive_table.nr - 1);
|
|
|
|
*divep = dive = create_dive_from_plan(diveplan);
|
2013-01-05 20:56:45 +00:00
|
|
|
if (!dive)
|
|
|
|
return;
|
2013-01-05 07:11:42 +00:00
|
|
|
record_dive(dive);
|
|
|
|
|
|
|
|
sample = &dive->dc.sample[dive->dc.samples - 1];
|
|
|
|
o2 = dive->cylinder[sample->sensor].gasmix.o2.permille;
|
|
|
|
he = dive->cylinder[sample->sensor].gasmix.he.permille;
|
|
|
|
|
2013-01-07 06:09:12 +00:00
|
|
|
tissue_tolerance = tissue_at_end(dive, cached_datap);
|
2013-01-05 07:11:42 +00:00
|
|
|
ceiling = deco_allowed_depth(tissue_tolerance, diveplan->surface_pressure / 1000.0, dive, 1);
|
|
|
|
|
2013-01-07 06:09:12 +00:00
|
|
|
for (stopidx = 1; stopidx < sizeof(stoplevels) / sizeof(int); stopidx++)
|
2013-01-05 07:11:42 +00:00
|
|
|
if (stoplevels[stopidx] >= ceiling)
|
|
|
|
break;
|
|
|
|
|
2013-01-07 06:09:12 +00:00
|
|
|
while (stopidx > 0) {
|
2013-01-05 07:11:42 +00:00
|
|
|
depth = dive->dc.sample[dive->dc.samples - 1].depth.mm;
|
|
|
|
if (depth > stoplevels[stopidx]) {
|
|
|
|
transitiontime = (depth - stoplevels[stopidx]) / 150;
|
|
|
|
plan_add_segment(diveplan, transitiontime, stoplevels[stopidx], o2, he);
|
|
|
|
/* re-create the dive */
|
|
|
|
delete_single_dive(dive_table.nr - 1);
|
2013-01-07 06:09:12 +00:00
|
|
|
*divep = dive = create_dive_from_plan(diveplan);
|
2013-01-05 07:11:42 +00:00
|
|
|
record_dive(dive);
|
|
|
|
}
|
2013-01-07 06:09:12 +00:00
|
|
|
wait_time = time_at_last_depth(dive, stoplevels[stopidx - 1], cached_datap);
|
2013-01-05 07:11:42 +00:00
|
|
|
if (wait_time)
|
|
|
|
plan_add_segment(diveplan, wait_time, stoplevels[stopidx], o2, he);
|
|
|
|
transitiontime = (stoplevels[stopidx] - stoplevels[stopidx - 1]) / 150;
|
|
|
|
plan_add_segment(diveplan, transitiontime, stoplevels[stopidx - 1], o2, he);
|
|
|
|
/* re-create the dive */
|
|
|
|
delete_single_dive(dive_table.nr - 1);
|
2013-01-07 06:09:12 +00:00
|
|
|
*divep = dive = create_dive_from_plan(diveplan);
|
2013-01-05 07:11:42 +00:00
|
|
|
record_dive(dive);
|
|
|
|
stopidx--;
|
|
|
|
}
|
|
|
|
/* now make the dive visible as last dive of the dive list */
|
|
|
|
report_dives(FALSE, FALSE);
|
2013-01-07 06:09:12 +00:00
|
|
|
select_last_dive();
|
2013-01-05 07:11:42 +00:00
|
|
|
}
|
2013-01-07 19:23:14 +00:00
|
|
|
|
|
|
|
|
|
|
|
/* and now the UI for all this */
|
|
|
|
/*
|
|
|
|
* Get a value in tenths (so "10.2" == 102, "9" = 90)
|
|
|
|
*
|
|
|
|
* Return negative for errors.
|
|
|
|
*/
|
2013-01-07 21:13:10 +00:00
|
|
|
static int get_tenths(const char *begin, const char **endp)
|
2013-01-07 19:23:14 +00:00
|
|
|
{
|
2013-01-07 21:13:10 +00:00
|
|
|
char *end;
|
|
|
|
int value = strtol(begin, &end, 10);
|
|
|
|
|
|
|
|
*endp = end;
|
|
|
|
if (begin == end)
|
2013-01-07 19:23:14 +00:00
|
|
|
return -1;
|
|
|
|
value *= 10;
|
|
|
|
|
|
|
|
/* Fraction? We only look at the first digit */
|
2013-01-07 21:13:10 +00:00
|
|
|
if (*end == '.') {
|
2013-01-07 19:23:14 +00:00
|
|
|
++*end;
|
2013-01-07 21:13:10 +00:00
|
|
|
if (!isdigit(*end))
|
2013-01-07 19:23:14 +00:00
|
|
|
return -1;
|
2013-01-07 21:13:10 +00:00
|
|
|
value += *end - '0';
|
2013-01-07 19:23:14 +00:00
|
|
|
do {
|
|
|
|
++*end;
|
2013-01-07 21:13:10 +00:00
|
|
|
} while (isdigit(*end));
|
2013-01-07 19:23:14 +00:00
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2013-01-07 21:13:10 +00:00
|
|
|
static int get_permille(const char *begin, const char **end)
|
2013-01-07 19:23:14 +00:00
|
|
|
{
|
|
|
|
int value = get_tenths(begin, end);
|
|
|
|
if (value >= 0) {
|
|
|
|
/* Allow a percentage sign */
|
|
|
|
if (**end == '%')
|
|
|
|
++*end;
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2013-01-07 21:13:10 +00:00
|
|
|
static int validate_gas(const char *text, int *o2_p, int *he_p)
|
2013-01-07 19:23:14 +00:00
|
|
|
{
|
|
|
|
int o2, he;
|
|
|
|
|
|
|
|
if (!text)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
while (isspace(*text))
|
|
|
|
text++;
|
|
|
|
|
2013-01-07 21:13:10 +00:00
|
|
|
if (!*text)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!strcasecmp(text, "air")) {
|
2013-01-07 19:23:14 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2013-01-07 21:13:10 +00:00
|
|
|
static int validate_time(const char *text, int *sec_p, int *rel_p)
|
2013-01-07 19:23:14 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2013-01-07 21:13:10 +00:00
|
|
|
static int validate_depth(const char *text, int *mm_p)
|
2013-01-07 19:23:14 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
2013-01-07 20:49:07 +00:00
|
|
|
#if DEBUG_PLAN & 1
|
|
|
|
dump_plan(&tempplan);
|
|
|
|
#endif
|
2013-01-07 19:23:14 +00:00
|
|
|
plan(&tempplan, &cache_data, &planned_dive);
|
|
|
|
free_dps(tempplan.dp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean gas_focus_out_cb(GtkWidget *entry, GdkEvent *event, gpointer data)
|
|
|
|
{
|
2013-01-07 21:13:10 +00:00
|
|
|
const char *gastext;
|
2013-01-07 19:23:14 +00:00
|
|
|
int o2, he;
|
|
|
|
int idx = data - NULL;
|
|
|
|
|
2013-01-07 21:13:10 +00:00
|
|
|
gastext = gtk_entry_get_text(GTK_ENTRY(entry));
|
|
|
|
o2 = he = 0;
|
|
|
|
if (validate_gas(gastext, &o2, &he))
|
2013-01-07 19:23:14 +00:00
|
|
|
add_string_list_entry(gastext, gas_model);
|
2013-01-07 21:13:10 +00:00
|
|
|
add_gas_to_nth_dp(&diveplan, idx, o2, he);
|
|
|
|
show_planned_dive();
|
2013-01-07 19:23:14 +00:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2013-01-07 20:50:02 +00:00
|
|
|
static void gas_changed_cb(GtkWidget *combo, gpointer data)
|
|
|
|
{
|
2013-01-07 21:13:10 +00:00
|
|
|
const char *gastext;
|
2013-01-07 20:50:02 +00:00
|
|
|
int o2, he;
|
|
|
|
int idx = data - NULL;
|
|
|
|
|
2013-01-07 21:13:10 +00:00
|
|
|
gastext = gtk_combo_box_get_active_text(GTK_COMBO_BOX(combo));
|
|
|
|
o2 = he = 0;
|
|
|
|
if (!validate_gas(gastext, &o2, &he)) {
|
2013-01-07 20:50:02 +00:00
|
|
|
/* this should never happen as only validated texts should be
|
|
|
|
* in the dropdown */
|
|
|
|
printf("invalid gas for row %d\n",idx);
|
|
|
|
}
|
2013-01-07 21:13:10 +00:00
|
|
|
add_gas_to_nth_dp(&diveplan, idx, o2, he);
|
|
|
|
show_planned_dive();
|
2013-01-07 20:50:02 +00:00
|
|
|
}
|
|
|
|
|
2013-01-07 19:23:14 +00:00
|
|
|
static gboolean depth_focus_out_cb(GtkWidget *entry, GdkEvent *event, gpointer data)
|
|
|
|
{
|
2013-01-07 21:13:10 +00:00
|
|
|
const char *depthtext;
|
2013-01-07 19:23:14 +00:00
|
|
|
int depth;
|
|
|
|
int idx = data - NULL;
|
|
|
|
|
2013-01-07 21:13:10 +00:00
|
|
|
depthtext = gtk_entry_get_text(GTK_ENTRY(entry));
|
2013-01-07 19:23:14 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean duration_focus_out_cb(GtkWidget *entry, GdkEvent * event, gpointer data)
|
|
|
|
{
|
2013-01-07 21:13:10 +00:00
|
|
|
const char *durationtext;
|
2013-01-07 19:23:14 +00:00
|
|
|
int duration, is_rel;
|
|
|
|
int idx = data - NULL;
|
|
|
|
|
2013-01-07 21:13:10 +00:00
|
|
|
durationtext = gtk_entry_get_text(GTK_ENTRY(entry));
|
2013-01-07 19:23:14 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean starttime_focus_out_cb(GtkWidget *entry, GdkEvent * event, gpointer data)
|
|
|
|
{
|
2013-01-07 21:13:10 +00:00
|
|
|
const char *starttimetext;
|
2013-01-07 19:23:14 +00:00
|
|
|
int starttime, is_rel;
|
|
|
|
|
2013-01-07 21:13:10 +00:00
|
|
|
starttimetext = gtk_entry_get_text(GTK_ENTRY(entry));
|
2013-01-07 19:23:14 +00:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2013-01-07 20:50:02 +00:00
|
|
|
static GtkWidget *add_gas_combobox_to_box(GtkWidget *box, const char *label, int idx)
|
2013-01-07 19:23:14 +00:00
|
|
|
{
|
|
|
|
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);
|
2013-01-07 20:50:02 +00:00
|
|
|
g_signal_connect(gtk_bin_get_child(GTK_BIN(combo)), "focus-out-event", G_CALLBACK(gas_focus_out_cb), NULL + idx);
|
|
|
|
g_signal_connect(combo, "changed", G_CALLBACK(gas_changed_cb), NULL + idx);
|
2013-01-07 19:23:14 +00:00
|
|
|
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"));
|
2013-01-07 20:50:02 +00:00
|
|
|
entry_gas[idx] = add_gas_combobox_to_box(hbox, _("Gas Used"), idx);
|
2013-01-07 19:23:14 +00:00
|
|
|
} else {
|
|
|
|
entry_depth[idx] = add_entry_to_box(hbox, NULL);
|
|
|
|
entry_duration[idx] = add_entry_to_box(hbox, NULL);
|
2013-01-07 20:50:02 +00:00
|
|
|
entry_gas[idx] = add_gas_combobox_to_box(hbox, NULL, idx);
|
2013-01-07 19:23:14 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
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) {
|
2013-01-07 21:13:10 +00:00
|
|
|
plan(&diveplan, &cache_data, &planned_dive);
|
2013-01-07 22:52:31 +00:00
|
|
|
} else {
|
|
|
|
if (planned_dive) {
|
|
|
|
/* we have added a dive during the dynamic construction
|
|
|
|
* in the dialog; get rid of it */
|
|
|
|
delete_single_dive(dive_table.nr - 1);
|
|
|
|
report_dives(FALSE, FALSE);
|
|
|
|
planned_dive = NULL;
|
|
|
|
}
|
2013-01-07 19:23:14 +00:00
|
|
|
}
|
|
|
|
gtk_widget_destroy(planner);
|
|
|
|
}
|