User interface for recurrence rules

The function update_rept() is extended with editing of the three
recurrence rule lists for BYMONTH, BYMONTHDAY and BYDAY.

The integers of the bymonth and bymonthday lists are edited directly as
integers, while those of the bywday list are mapped to localized weekday
names (as they appear in the calendar panel) with an optional integer
prefix (in RFC5545 style: 1MO, -2SA).

The RFC5545 (icalendar) requirement that the start day must be the first
occurrence and must match the recurrence rule, is met by testing that an
occurrence indeed appears on the start day, in these circumstances:

- when a recurrent item is loaded from file
- when the recurrence rule of an item is edited interactively
- when a recurrent appointment gets a new start time
- when a recurrent appointment is moved

Copy and paste of a recurrent item will only retain the basic recurrence
properties of type, frequency, until and exception days.

Signed-off-by: Lukas Fleischer <lfleischer@calcurse.org>
This commit is contained in:
Lars Henriksen 2019-12-08 17:26:52 +01:00 committed by Lukas Fleischer
parent eda28d3fef
commit dc161b3b67
3 changed files with 416 additions and 77 deletions

View File

@ -407,6 +407,16 @@ struct rpt {
llist_t exc; /* EXDATE's */ llist_t exc; /* EXDATE's */
}; };
/* Types of integers in rrule lists. */
typedef enum {
BYMONTH,
BYDAY_W,
BYDAY_M,
BYDAY_Y,
BYMONTHDAY,
NOLL
} int_list_t;
/* Recurrent appointment definition. */ /* Recurrent appointment definition. */
struct recur_apoint { struct recur_apoint {
struct rpt *rpt; /* recurrence rule */ struct rpt *rpt; /* recurrence rule */
@ -1042,6 +1052,8 @@ void pcal_export_data(FILE *);
/* recur.c */ /* recur.c */
extern llist_ts_t recur_alist_p; extern llist_ts_t recur_alist_p;
extern llist_t recur_elist; extern llist_t recur_elist;
void recur_free_int_list(llist_t *);
void recur_int_list_dup(llist_t *, llist_t *);
void recur_free_exc_list(llist_t *); void recur_free_exc_list(llist_t *);
void recur_exc_dup(llist_t *, llist_t *); void recur_exc_dup(llist_t *, llist_t *);
int recur_str2exc(llist_t *, char *); int recur_str2exc(llist_t *, char *);

View File

@ -51,13 +51,13 @@ static void free_int(int *i)
mem_free(i); mem_free(i);
} }
static void free_int_list(llist_t *ilist) void recur_free_int_list(llist_t *ilist)
{ {
LLIST_FREE_INNER(ilist, free_int); LLIST_FREE_INNER(ilist, free_int);
LLIST_FREE(ilist); LLIST_FREE(ilist);
} }
static void int_list_dup(llist_t *l, llist_t *ilist) void recur_int_list_dup(llist_t *l, llist_t *ilist)
{ {
llist_item_t *i; llist_item_t *i;
int *o, *p; int *o, *p;
@ -187,9 +187,14 @@ struct recur_event *recur_event_dup(struct recur_event *in)
rev->mesg = mem_strdup(in->mesg); rev->mesg = mem_strdup(in->mesg);
rev->rpt = mem_malloc(sizeof(struct rpt)); rev->rpt = mem_malloc(sizeof(struct rpt));
/* Note. The linked lists are NOT copied and no memory allocated. */
rev->rpt->type = in->rpt->type; rev->rpt->type = in->rpt->type;
rev->rpt->freq = in->rpt->freq; rev->rpt->freq = in->rpt->freq;
rev->rpt->until = in->rpt->until; rev->rpt->until = in->rpt->until;
LLIST_INIT(&rev->rpt->bymonth);
LLIST_INIT(&rev->rpt->bywday);
LLIST_INIT(&rev->rpt->bymonthday);
LLIST_INIT(&rev->rpt->exc);
recur_exc_dup(&rev->exc, &in->exc); recur_exc_dup(&rev->exc, &in->exc);
@ -214,9 +219,14 @@ struct recur_apoint *recur_apoint_dup(struct recur_apoint *in)
rapt->mesg = mem_strdup(in->mesg); rapt->mesg = mem_strdup(in->mesg);
rapt->rpt = mem_malloc(sizeof(struct rpt)); rapt->rpt = mem_malloc(sizeof(struct rpt));
/* Note. The linked lists are NOT copied and no memory allocated. */
rapt->rpt->type = in->rpt->type; rapt->rpt->type = in->rpt->type;
rapt->rpt->freq = in->rpt->freq; rapt->rpt->freq = in->rpt->freq;
rapt->rpt->until = in->rpt->until; rapt->rpt->until = in->rpt->until;
LLIST_INIT(&rapt->rpt->bymonth);
LLIST_INIT(&rapt->rpt->bywday);
LLIST_INIT(&rapt->rpt->bymonthday);
LLIST_INIT(&rapt->rpt->exc);
recur_exc_dup(&rapt->exc, &in->exc); recur_exc_dup(&rapt->exc, &in->exc);
@ -311,12 +321,12 @@ struct recur_apoint *recur_apoint_new(char *mesg, char *note, time_t start,
rapt->state = state; rapt->state = state;
rapt->rpt = mem_malloc(sizeof(struct rpt)); rapt->rpt = mem_malloc(sizeof(struct rpt));
*rapt->rpt = *rpt; *rapt->rpt = *rpt;
int_list_dup(&rapt->rpt->bymonth, &rpt->bymonth); recur_int_list_dup(&rapt->rpt->bymonth, &rpt->bymonth);
free_int_list(&rpt->bymonth); recur_free_int_list(&rpt->bymonth);
int_list_dup(&rapt->rpt->bywday, &rpt->bywday); recur_int_list_dup(&rapt->rpt->bywday, &rpt->bywday);
free_int_list(&rpt->bywday); recur_free_int_list(&rpt->bywday);
int_list_dup(&rapt->rpt->bymonthday, &rpt->bymonthday); recur_int_list_dup(&rapt->rpt->bymonthday, &rpt->bymonthday);
free_int_list(&rpt->bymonthday); recur_free_int_list(&rpt->bymonthday);
/* /*
* Note. The exception dates are in the list rapt->exc. * Note. The exception dates are in the list rapt->exc.
* The (empty) list rapt->rpt->exc is not used. * The (empty) list rapt->rpt->exc is not used.
@ -344,12 +354,12 @@ struct recur_event *recur_event_new(char *mesg, char *note, time_t day,
rev->id = id; rev->id = id;
rev->rpt = mem_malloc(sizeof(struct rpt)); rev->rpt = mem_malloc(sizeof(struct rpt));
*rev->rpt = *rpt; *rev->rpt = *rpt;
int_list_dup(&rev->rpt->bymonth, &rpt->bymonth); recur_int_list_dup(&rev->rpt->bymonth, &rpt->bymonth);
free_int_list(&rpt->bymonth); recur_free_int_list(&rpt->bymonth);
int_list_dup(&rev->rpt->bywday, &rpt->bywday); recur_int_list_dup(&rev->rpt->bywday, &rpt->bywday);
free_int_list(&rpt->bywday); recur_free_int_list(&rpt->bywday);
int_list_dup(&rev->rpt->bymonthday, &rpt->bymonthday); recur_int_list_dup(&rev->rpt->bymonthday, &rpt->bymonthday);
free_int_list(&rpt->bymonthday); recur_free_int_list(&rpt->bymonthday);
/* Similarly as for recurrent appointment. */ /* Similarly as for recurrent appointment. */
recur_exc_dup(&rev->exc, &rpt->exc); recur_exc_dup(&rev->exc, &rpt->exc);
recur_free_exc_list(&rpt->exc); recur_free_exc_list(&rpt->exc);
@ -506,6 +516,12 @@ char *recur_apoint_scan(FILE *f, struct tm start, struct tm end,
if (tstart == -1 || tend == -1 || tstart > tend) if (tstart == -1 || tend == -1 || tstart > tend)
return _("date error in appointment"); return _("date error in appointment");
/* Does it occur on the start day? */
if (!recur_item_find_occurrence(tstart, tend - tstart, rpt, NULL,
update_time_in_date(tstart, 0, 0),
NULL))
return _("recurrence error: not on start day");
/* Filter item. */ /* Filter item. */
if (filter) { if (filter) {
cond = ( cond = (
@ -571,6 +587,12 @@ char *recur_event_scan(FILE * f, struct tm start, int id,
return _("date error in event"); return _("date error in event");
tend = ENDOFDAY(tstart); tend = ENDOFDAY(tstart);
/* Does it occur on the start day? */
if (!recur_item_find_occurrence(tstart, -1, rpt, NULL,
update_time_in_date(tstart, 0, 0),
NULL))
return _("recurrence error: not on start day");
/* Filter item. */ /* Filter item. */
if (filter) { if (filter) {
cond = ( cond = (
@ -973,7 +995,7 @@ static int find_occurrence(time_t start, long dur, struct rpt *rpt, llist_t *exc
return 0; return 0;
/* Exception day? */ /* Exception day? */
if (LLIST_FIND_FIRST(exc, &t, exc_inday)) if (exc && LLIST_FIND_FIRST(exc, &t, exc_inday))
return 0; return 0;
/* Extraneous day? */ /* Extraneous day? */
@ -988,7 +1010,6 @@ static int find_occurrence(time_t start, long dur, struct rpt *rpt, llist_t *exc
*occurrence = t; *occurrence = t;
return 1; return 1;
#undef ITEM_DUR
} }
#undef DUR #undef DUR

View File

@ -34,6 +34,8 @@
* *
*/ */
#include <limits.h>
#include <langinfo.h>
#include "calcurse.h" #include "calcurse.h"
/* Cut & paste registers. */ /* Cut & paste registers. */
@ -163,32 +165,44 @@ static time_t day_edit_time(time_t start, long duration, int move)
/* /*
* Change start time or move an item. * Change start time or move an item.
* Input/output: start and dur. * Input/output: start and dur.
* For recurrent items the new start time must match the repetition pattern.
* If move = 0, end time is fixed, and the new duration is calculated * If move = 0, end time is fixed, and the new duration is calculated
* when the new start time is known. * when the new start time is known.
* If move = 1, duration is fixed, but passed on for validation of new end time. * If move = 1, duration is fixed, but passed on for validation of new end time.
*/ */
static void update_start_time(time_t *start, long *dur, int move) static void update_start_time(time_t *start, long *dur, struct rpt *rpt, int move)
{ {
time_t newtime; time_t newtime;
const char *msg_wrong_time = const char *msg_wrong_time =
_("Invalid time: start time must come before end time!"); _("Invalid time: start time must come before end time!");
const char *msg_match =
_("Repetition must begin on start day.");
const char *msg_enter = _("Press [Enter] to continue"); const char *msg_enter = _("Press [Enter] to continue");
char *msg;
for (;;) { for (;;) {
newtime = day_edit_time(*start, *dur, move); newtime = day_edit_time(*start, *dur, move);
if (!newtime) if (!newtime)
break; break;
if (move) { if (rpt && !recur_item_find_occurrence(
*start = newtime; newtime, *dur, rpt, NULL,
break; update_time_in_date(newtime, 0, 0),
NULL)) {
msg = (char *)msg_match;
} else { } else {
if (newtime <= *start + *dur) { if (move) {
*dur -= (newtime - *start);
*start = newtime; *start = newtime;
break; break;
} else {
if (newtime <= *start + *dur) {
*dur -= (newtime - *start);
*start = newtime;
break;
}
} }
msg = (char *)msg_wrong_time;
} }
status_mesg(msg_wrong_time, msg_enter); status_mesg(msg, msg_enter);
keys_wgetch(win[KEY].p); keys_wgetch(win[KEY].p);
} }
return; return;
@ -304,22 +318,267 @@ static int edit_exc(llist_t *exc)
return updated; return updated;
} }
static void update_rept(struct rpt **rpt, const time_t start, llist_t *exc) /*
* Decode an integer representing a weekday or ordered weekday.
* The return value is the (abbreviated) localized day name.
* The order is returned in the second argument.
*/
static char *int2wday(int i, int *ord, int_list_t type)
{ {
/* Pointers to dynamically allocated memory. */ if (type == BYDAY_W ||
((type == BYDAY_M || type == BYDAY_Y) && -1 < i && i < 7))
*ord = 0;
else if ((type == BYDAY_M && 6 < i && i < 42) ||
(type == BYDAY_Y && 6 < i && i < 378))
*ord = i / 7;
else if ((type == BYDAY_M && -42 < i && i < -6) ||
(type == BYDAY_Y && -378 < i && i < -6)) {
i = -i;
*ord = -(i / 7);
} else
return NULL;
return nl_langinfo(ABDAY_1 + i % 7);
}
/*
* Given a (linked) list of integers representing weekdays, monthdays or months.
* Return a string containing the weekdays or integers separated by spaces.
*/
static char *int2str(llist_t *il, int_list_t type)
{
llist_item_t *i;
int *p, ord = 0;
char *wday;
struct string s;
string_init(&s);
LLIST_FOREACH(il, i) {
p = LLIST_GET_DATA(i);
wday = int2wday(*p, &ord, type);
if (wday)
string_catf(&s, ord ? "%d%s " : "%.0d%s ", ord, wday);
else
string_catf(&s, "%i ", *p);
}
return string_buf(&s);
}
/*
* Encode a weekday or ordered weekday as an integer.
*/
static int wday2int(char *s)
{
int i, ord;
char *tail;
i = strtol(s, &tail, 10);
if (!i && tail == s)
ord = 0;
else
ord = i > 0 ? i : -i;
if (!strcmp(tail, nl_langinfo(ABDAY_1)))
return (i < 0 ? -1 : 1) * (ord * 7 + 0);
else if (!strcmp(tail, nl_langinfo(ABDAY_2)))
return (i < 0 ? -1 : 1) * (ord * 7 + 1);
else if (!strcmp(tail, nl_langinfo(ABDAY_3)))
return (i < 0 ? -1 : 1) * (ord * 7 + 2);
else if (!strcmp(tail, nl_langinfo(ABDAY_4)))
return (i < 0 ? -1 : 1) * (ord * 7 + 3);
else if (!strcmp(tail, nl_langinfo(ABDAY_5)))
return (i < 0 ? -1 : 1) * (ord * 7 + 4);
else if (!strcmp(tail, nl_langinfo(ABDAY_6)))
return (i < 0 ? -1 : 1) * (ord * 7 + 5);
else if (!strcmp(tail, nl_langinfo(ABDAY_7)))
return (i < 0 ? -1 : 1) * (ord * 7 + 6);
else
return -1;
}
/*
* Parse an integer or weekday string. Valid values depend on type.
* On success the integer or integer code is returned in *i.
*/
static int parse_int(char *s, long *i, int_list_t type)
{
char *eos;
if (type == BYDAY_W || type == BYDAY_M || type == BYDAY_Y) {
*i = wday2int(s);
if (*i == -1)
return 0;
} else {
*i = strtol(s, &eos, 10);
if (*eos || *i > INT_MAX)
return 0;
}
switch (type) {
case BYMONTH:
/* 1,..,12 */
if (0 < *i && *i < 13)
return 1;
break;
case BYDAY_W:
/* 0,..,6 */
if (-1 < *i && *i < 7)
return 1;
break;
case BYDAY_M:
/* 0,..,6 or 7,..,41 or -7,..,-41 */
/* 41 = 5*7 + 6, i.e. fifth Saturday of the month */
if ((-42 < *i && *i < -6) || (-1 < *i && *i < 42))
return 1;
break;
case BYDAY_Y:
/* 0,..,6 or 7,..,377 or -7,..,-377 */
/* 377 = 53*7 + 6, i.e. 53th Saturday of the year */
if ((-378 < *i && *i < -6) || (-1 < *i && *i < 378))
return 1;
break;
case BYMONTHDAY:
/* 1,..,31 or -1,..,-31 */
if ((0 < *i && *i < 32) || (-32 < *i && *i < 0))
return 1;
break;
default:
return 0;
}
return 0;
}
/*
* Update a (linked) list of integer values from a string of such values. Any
* positive number of spaces are allowed before, between and after the values.
*/
static int str2int(llist_t *l, char *s, int type) {
int *j, updated = 0;
char *c;
long i;
llist_t nl;
LLIST_INIT(&nl);
while (1) {
while (*s == ' ')
s++;
if ((c = strchr(s, ' ')))
*c = '\0';
else if (!strlen(s))
break;
if (parse_int(s, &i, type)) {
j = mem_malloc(sizeof(int));
*j = i;
LLIST_ADD(&nl, j);
} else
goto cleanup;
if (c)
s = c + 1;
else
break;
}
recur_free_int_list(l);
recur_int_list_dup(l, &nl);
updated = 1;
cleanup:
recur_free_int_list(&nl);
return updated;
}
/* Edit an rrule (linked) list of integers. */
static int edit_ilist(llist_t *ilist, int_list_t type)
{
char *msg;
char *msg_wday = NULL;
char *msg_format_w = _("Weekdays (%s|..|%s):");
char *msg_format_m =
_("Weekdays (%s|..|%s, optional prefix 1..5 or -1..-5)):");
char *msg_format_y =
_("Weekdays (%s|..|%s, optional prefix 1..53 or -1..-53):");
char *msg_month = _("Months (1..12):");
char *msg_mday = _("Monthdays (1..31 or -1..-31):");
char *msg_invalid = _("Invalid format - try again.");
char *msg_cont = _("Press any key to continue.");
int updated = 0;
if (type == NOLL)
return !updated;
char *istr;
enum getstr ret;
switch (type) {
case BYDAY_W:
asprintf(&msg_wday, msg_format_w,
nl_langinfo(ABDAY_2), nl_langinfo(ABDAY_1));
msg = msg_wday;
break;
case BYDAY_M:
asprintf(&msg_wday, msg_format_m,
nl_langinfo(ABDAY_2), nl_langinfo(ABDAY_1));
msg = msg_wday;
break;
case BYDAY_Y:
asprintf(&msg_wday, msg_format_y,
nl_langinfo(ABDAY_2), nl_langinfo(ABDAY_1));
msg = msg_wday;
break;
case BYMONTH:
msg = msg_month;
break;
case BYMONTHDAY:
msg = msg_mday;
break;
default:
msg = NULL;
break;
}
status_mesg(msg, "");
istr = int2str(ilist, type);
while (1) {
ret = updatestring(win[STA].p, &istr, 0, 1);
if (ret == GETSTRING_VALID || ret == GETSTRING_RET) {
if (str2int(ilist, istr, type)) {
updated = 1;
break;
} else {
status_mesg(msg_invalid, msg_cont);
keys_wgetch(win[KEY].p);
mem_free(istr);
status_mesg(msg, "");
istr = int2str(ilist, type);
}
} else if (ret == GETSTRING_ESC)
break;
}
mem_free(istr);
mem_free(msg_wday);
return updated;
}
static void update_rept(time_t start, long dur, struct rpt **rpt, llist_t *exc)
{
struct rpt nrpt;
char *msg_rpt_current = NULL; char *msg_rpt_current = NULL;
char *msg_rpt_asktype = NULL; char *msg_rpt_asktype = NULL;
char *freqstr = NULL; char *freqstr = NULL;
char *timstr = NULL; char *timstr = NULL;
char *outstr = NULL; char *outstr = NULL;
const char *msg_cont = _("Press any key to continue.");
LLIST_INIT(&nrpt.exc);
LLIST_INIT(&nrpt.bywday);
LLIST_INIT(&nrpt.bymonth);
LLIST_INIT(&nrpt.bymonthday);
/* Edit repetition type. */ /* Edit repetition type. */
int newtype; const char *msg_rpt_prefix = _("Enter the repetition type:");
const char *msg_rpt_prefix = _("Enter the new repetition type:");
const char *msg_rpt_daily = _("(d)aily"); const char *msg_rpt_daily = _("(d)aily");
const char *msg_rpt_weekly = _("(w)eekly"); const char *msg_rpt_weekly = _("(w)eekly");
const char *msg_rpt_monthly = _("(m)onthly"); const char *msg_rpt_monthly = _("(m)onthly");
const char *msg_rpt_yearly = _("(y)early"); const char *msg_rpt_yearly = _("(y)early");
const char *msg_rpt_choice = _("[dwmy]");
/* Find the current repetition type. */ /* Find the current repetition type. */
const char *rpt_current; const char *rpt_current;
@ -340,50 +599,48 @@ static void update_rept(struct rpt **rpt, const time_t start, llist_t *exc)
/* NOTREACHED, but makes the compiler happier. */ /* NOTREACHED, but makes the compiler happier. */
rpt_current = msg_rpt_daily; rpt_current = msg_rpt_daily;
} }
asprintf(&msg_rpt_current, _("(currently using %s)"), rpt_current); asprintf(&msg_rpt_current, _("(p.t. %s)"), rpt_current);
asprintf(&msg_rpt_asktype, "%s %s, %s, %s, %s? %s", msg_rpt_prefix, asprintf(&msg_rpt_asktype, "%s %s, %s, %s, %s? %s", msg_rpt_prefix,
msg_rpt_daily, msg_rpt_weekly, msg_rpt_monthly, msg_rpt_daily, msg_rpt_weekly, msg_rpt_monthly,
msg_rpt_yearly, msg_rpt_current); msg_rpt_yearly, msg_rpt_current);
const char *msg_rpt_choice = _("[dwmy]");
switch (status_ask_choice(msg_rpt_asktype, msg_rpt_choice, 4)) { switch (status_ask_choice(msg_rpt_asktype, msg_rpt_choice, 4)) {
case 1: case 1:
newtype = 'D'; nrpt.type = 'D';
break; break;
case 2: case 2:
newtype = 'W'; nrpt.type = 'W';
break; break;
case 3: case 3:
newtype = 'M'; nrpt.type = 'M';
break; break;
case 4: case 4:
newtype = 'Y'; nrpt.type = 'Y';
break; break;
default: default:
goto cleanup; goto cleanup;
} }
nrpt.type = recur_char2def(nrpt.type);
/* Edit frequency. */ /* Edit frequency. */
int newfreq; const char *msg_freq = _("Enter the repetition frequency:");
const char *msg_wrong_freq = _("Invalid frequency."); const char *msg_wrong_freq = _("Invalid frequency.");
const char *msg_enter = _("Press [Enter] to continue");
do { do {
status_mesg(_("Enter the repetition frequency:"), ""); status_mesg(msg_freq, "");
mem_free(freqstr); mem_free(freqstr);
asprintf(&freqstr, "%d", (*rpt)->freq); asprintf(&freqstr, "%d", (*rpt)->freq);
if (updatestring(win[STA].p, &freqstr, 0, 1) != if (updatestring(win[STA].p, &freqstr, 0, 1) !=
GETSTRING_VALID) { GETSTRING_VALID) {
goto cleanup; goto cleanup;
} }
newfreq = atoi(freqstr); nrpt.freq = atoi(freqstr);
if (newfreq == 0) { if (nrpt.freq == 0) {
status_mesg(msg_wrong_freq, msg_enter); status_mesg(msg_wrong_freq, msg_cont);
keys_wait_for_any_key(win[KEY].p); keys_wait_for_any_key(win[KEY].p);
} }
} }
while (newfreq == 0); while (nrpt.freq == 0);
/* Edit end date. */ /* Edit end date. */
time_t newuntil;
const char *msg_until_1 = const char *msg_until_1 =
_("Enter end date or duration ('?' for input formats):"); _("Enter end date or duration ('?' for input formats):");
const char *msg_help_1 = const char *msg_help_1 =
@ -404,7 +661,7 @@ static void update_rept(struct rpt **rpt, const time_t start, llist_t *exc)
if (updatestring(win[STA].p, &timstr, 0, 1) == GETSTRING_ESC) if (updatestring(win[STA].p, &timstr, 0, 1) == GETSTRING_ESC)
goto cleanup; goto cleanup;
if (strcmp(timstr, "") == 0 || strcmp(timstr, "0") == 0) { if (strcmp(timstr, "") == 0 || strcmp(timstr, "0") == 0) {
newuntil = 0; nrpt.until = 0;
break; break;
} }
if (*(timstr + strlen(timstr) - 1) == '?') { if (*(timstr + strlen(timstr) - 1) == '?') {
@ -417,12 +674,12 @@ static void update_rept(struct rpt **rpt, const time_t start, llist_t *exc)
if (*timstr == '+') { if (*timstr == '+') {
unsigned days; unsigned days;
if (!parse_date_duration(timstr + 1, &days, start)) { if (!parse_date_duration(timstr + 1, &days, start)) {
status_mesg(msg_wrong_date, msg_enter); status_mesg(msg_wrong_date, msg_cont);
keys_wgetch(win[KEY].p); keys_wgetch(win[KEY].p);
continue; continue;
} }
/* Until is midnight of the day. */ /* Until is midnight of the day. */
newuntil = date_sec_change( nrpt.until = date_sec_change(
update_time_in_date(start, 0, 0), update_time_in_date(start, 0, 0),
0, days 0, days
); );
@ -430,40 +687,97 @@ static void update_rept(struct rpt **rpt, const time_t start, llist_t *exc)
int year, month, day; int year, month, day;
if (!parse_date(timstr, conf.input_datefmt, &year, if (!parse_date(timstr, conf.input_datefmt, &year,
&month, &day, ui_calendar_get_slctd_day())) { &month, &day, ui_calendar_get_slctd_day())) {
status_mesg(msg_wrong_date, msg_enter); status_mesg(msg_wrong_date, msg_cont);
keys_wgetch(win[KEY].p); keys_wgetch(win[KEY].p);
continue; continue;
} }
struct date d = { day, month, year }; struct date d = { day, month, year };
newuntil = date2sec(d, 0, 0); nrpt.until = date2sec(d, 0, 0);
} }
/* Conmpare days (midnights) - until-day may equal start day. */ /* Conmpare days (midnights) - until-day may equal start day. */
if (newuntil >= update_time_in_date(start, 0, 0)) if (nrpt.until >= update_time_in_date(start, 0, 0))
break; break;
mem_free(timstr); mem_free(timstr);
mem_free(outstr); mem_free(outstr);
timstr = date_sec2date_str(start, DATEFMT(conf.input_datefmt)); timstr = date_sec2date_str(start, DATEFMT(conf.input_datefmt));
asprintf(&outstr, msg_wrong_time, timstr); asprintf(&outstr, msg_wrong_time, timstr);
status_mesg(outstr, msg_enter); status_mesg(outstr, msg_cont);
keys_wgetch(win[KEY].p); keys_wgetch(win[KEY].p);
} }
/* Edit exception list. */ /* Edit exception list. */
llist_t newexc; recur_exc_dup(&nrpt.exc, exc);
recur_exc_dup(&newexc, exc); if (!edit_exc(&nrpt.exc))
if (!edit_exc(&newexc)) { goto cleanup;
recur_free_exc_list(&newexc);
/* Edit BYDAY list. */
int_list_t byday_type;
switch (nrpt.type) {
case RECUR_DAILY:
byday_type = BYDAY_W;
break;
case RECUR_WEEKLY:
byday_type = BYDAY_W;
break;
case RECUR_MONTHLY:
byday_type = BYDAY_M;
break;
case RECUR_YEARLY:
byday_type = BYDAY_Y;
break;
default:
byday_type = NOLL;
break;
}
recur_int_list_dup(&nrpt.bywday, &(*rpt)->bywday);
if (!edit_ilist(&nrpt.bywday, byday_type))
goto cleanup;
/* Edit BYMONTH list. */
recur_int_list_dup(&nrpt.bymonth, &(*rpt)->bymonth);
if (!edit_ilist(&nrpt.bymonth, BYMONTH))
goto cleanup;
/* Edit BYMONTHDAY list. */
if (nrpt.type != RECUR_WEEKLY) {
recur_int_list_dup(&nrpt.bymonthday, &(*rpt)->bymonthday);
if (!edit_ilist(&nrpt.bymonthday, BYMONTHDAY))
goto cleanup;
}
/*
* Check whether the start occurrence matches the recurrence rule, in
* other words, does it occur on the start day? This is required by
* RFC5545 and ensures that the recurrence set is non-empty (unless it
* is an exception day).
*/
const char *msg_match =
_("Repetition must begin on start day; any change discarded.");
if (!recur_item_find_occurrence(start, dur, &nrpt, NULL,
update_time_in_date(start, 0, 0),
NULL)) {
status_mesg(msg_match, msg_cont);
keys_wgetch(win[KEY].p);
goto cleanup; goto cleanup;
} }
/* Update all recurrence parameters. */ /* Update all recurrence parameters. */
(*rpt)->type = recur_char2def(newtype); (*rpt)->type = nrpt.type;
(*rpt)->freq = newfreq; (*rpt)->freq = nrpt.freq;
(*rpt)->until = newuntil; (*rpt)->until = nrpt.until;
recur_free_exc_list(exc); recur_free_exc_list(exc);
recur_exc_dup(exc, &newexc); recur_exc_dup(exc, &nrpt.exc);
recur_free_exc_list(&newexc);
recur_free_int_list(&(*rpt)->bywday);
recur_int_list_dup(&(*rpt)->bywday, &nrpt.bywday);
recur_free_int_list(&(*rpt)->bymonth);
recur_int_list_dup(&(*rpt)->bymonth, &nrpt.bymonth);
recur_free_int_list(&(*rpt)->bymonthday);
recur_int_list_dup(&(*rpt)->bymonthday, &nrpt.bymonthday);
cleanup: cleanup:
mem_free(msg_rpt_current); mem_free(msg_rpt_current);
@ -471,6 +785,10 @@ cleanup:
mem_free(freqstr); mem_free(freqstr);
mem_free(timstr); mem_free(timstr);
mem_free(outstr); mem_free(outstr);
recur_free_exc_list(&nrpt.exc);
recur_free_int_list(&nrpt.bywday);
recur_free_int_list(&nrpt.bymonth);
recur_free_int_list(&nrpt.bymonthday);
} }
/* Edit an already existing item. */ /* Edit an already existing item. */
@ -490,7 +808,7 @@ void ui_day_item_edit(void)
switch (p->type) { switch (p->type) {
case RECUR_EVNT: case RECUR_EVNT:
re = p->item.rev; re = p->item.rev;
const char *choice_recur_evnt[2] = { const char *choice_recur_evnt[] = {
_("Description"), _("Description"),
_("Repetition") _("Repetition")
}; };
@ -498,11 +816,9 @@ void ui_day_item_edit(void)
(_("Edit: "), choice_recur_evnt, 2)) { (_("Edit: "), choice_recur_evnt, 2)) {
case 1: case 1:
update_desc(&re->mesg); update_desc(&re->mesg);
io_set_modified();
break; break;
case 2: case 2:
update_rept(&re->rpt, re->day, &re->exc); update_rept(re->day, -1, &re->rpt, &re->exc);
io_set_modified();
break; break;
default: default:
return; return;
@ -511,7 +827,6 @@ void ui_day_item_edit(void)
case EVNT: case EVNT:
e = p->item.ev; e = p->item.ev;
update_desc(&e->mesg); update_desc(&e->mesg);
io_set_modified();
break; break;
case RECUR_APPT: case RECUR_APPT:
ra = p->item.rapt; ra = p->item.rapt;
@ -526,29 +841,24 @@ void ui_day_item_edit(void)
(_("Edit: "), choice_recur_appt, 5)) { (_("Edit: "), choice_recur_appt, 5)) {
case 1: case 1:
need_check_notify = 1; need_check_notify = 1;
update_start_time(&ra->start, &ra->dur, ra->dur == 0); update_start_time(&ra->start, &ra->dur, ra->rpt, ra->dur == 0);
io_set_modified();
break; break;
case 2: case 2:
update_duration(&ra->start, &ra->dur); update_duration(&ra->start, &ra->dur);
io_set_modified();
break; break;
case 3: case 3:
if (notify_bar()) if (notify_bar())
need_check_notify = need_check_notify =
notify_same_recur_item(ra); notify_same_recur_item(ra);
update_desc(&ra->mesg); update_desc(&ra->mesg);
io_set_modified();
break; break;
case 4: case 4:
need_check_notify = 1; need_check_notify = 1;
update_rept(&ra->rpt, ra->start, &ra->exc); update_rept(ra->start, ra->dur, &ra->rpt, &ra->exc);
io_set_modified();
break; break;
case 5: case 5:
need_check_notify = 1; need_check_notify = 1;
update_start_time(&ra->start, &ra->dur, 1); update_start_time(&ra->start, &ra->dur, ra->rpt, 1);
io_set_modified();
break; break;
default: default:
return; return;
@ -566,24 +876,20 @@ void ui_day_item_edit(void)
(_("Edit: "), choice_appt, 4)) { (_("Edit: "), choice_appt, 4)) {
case 1: case 1:
need_check_notify = 1; need_check_notify = 1;
update_start_time(&a->start, &a->dur, a->dur == 0); update_start_time(&a->start, &a->dur, NULL, a->dur == 0);
io_set_modified();
break; break;
case 2: case 2:
update_duration(&a->start, &a->dur); update_duration(&a->start, &a->dur);
io_set_modified();
break; break;
case 3: case 3:
if (notify_bar()) if (notify_bar())
need_check_notify = need_check_notify =
notify_same_item(a->start); notify_same_item(a->start);
update_desc(&a->mesg); update_desc(&a->mesg);
io_set_modified();
break; break;
case 4: case 4:
need_check_notify = 1; need_check_notify = 1;
update_start_time(&a->start, &a->dur, 1); update_start_time(&a->start, &a->dur, NULL, 1);
io_set_modified();
break; break;
default: default:
return; return;
@ -592,7 +898,7 @@ void ui_day_item_edit(void)
default: default:
break; break;
} }
io_set_modified();
ui_calendar_monthly_view_cache_set_invalid(); ui_calendar_monthly_view_cache_set_invalid();
if (need_check_notify) if (need_check_notify)