aboutsummaryrefslogtreecommitdiffstats
path: root/src/ui-day.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui-day.c')
-rw-r--r--src/ui-day.c953
1 files changed, 670 insertions, 283 deletions
diff --git a/src/ui-day.c b/src/ui-day.c
index e0e306b..6a038fa 100644
--- a/src/ui-day.c
+++ b/src/ui-day.c
@@ -1,7 +1,7 @@
/*
* Calcurse - text-based organizer
*
- * Copyright (c) 2004-2017 calcurse Development Team <misc@calcurse.org>
+ * Copyright (c) 2004-2023 calcurse Development Team <misc@calcurse.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -34,9 +34,12 @@
*
*/
+#include <limits.h>
+#include <langinfo.h>
#include "calcurse.h"
-struct day_item day_cut[38] = { {0, 0, 0, {NULL}} };
+/* Cut & paste registers. */
+static struct day_item day_cut[REG_BLACK_HOLE + 1];
/*
* Set the selected day in the calendar from the selected item in the APP panel.
@@ -76,7 +79,7 @@ void ui_day_find_sel(void)
*/
time_t ui_day_sel_date(void)
{
- return update_time_in_date(ui_day_get_sel()->order, 0, 0);
+ return DAY(ui_day_get_sel()->order);
}
/*
@@ -162,32 +165,44 @@ static time_t day_edit_time(time_t start, long duration, int move)
/*
* Change start time or move an item.
* 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
* when the new start time is known.
* 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;
const char *msg_wrong_time =
_("Invalid time: start time must come before end time!");
+ char *msg_match =
+ _("Repetition must begin on start day (%s).");
const char *msg_enter = _("Press [Enter] to continue");
+ char *msg;
for (;;) {
newtime = day_edit_time(*start, *dur, move);
if (!newtime)
break;
- if (move) {
- *start = newtime;
- break;
+ if (rpt && !recur_item_find_occurrence(newtime, *dur, rpt, NULL,
+ DAY(newtime),
+ NULL)) {
+ msg = day_ins(&msg_match, newtime);
+ status_mesg(msg, msg_enter);
+ mem_free(msg);
} else {
- if (newtime <= *start + *dur) {
- *dur -= (newtime - *start);
+ if (move) {
*start = newtime;
break;
+ } else {
+ if (newtime <= *start + *dur) {
+ *dur -= (newtime - *start);
+ *start = newtime;
+ break;
+ }
}
+ status_mesg(msg_wrong_time, msg_enter);
}
- status_mesg(msg_wrong_time, msg_enter);
keys_wgetch(win[KEY].p);
}
return;
@@ -272,8 +287,8 @@ static void update_desc(char **desc)
updatestring(win[STA].p, desc, 0, 1);
}
-/* Edit the list of exception days for a recurrent item. */
-static int update_exc(llist_t *exc)
+/* Edit a list of exception days for a recurrent item. */
+static int edit_exc(llist_t *exc)
{
int updated = 0;
@@ -287,7 +302,7 @@ static int update_exc(llist_t *exc)
while (1) {
ret = updatestring(win[STA].p, &days, 0, 1);
if (ret == GETSTRING_VALID || ret == GETSTRING_RET) {
- if (recur_update_exc(exc, days)) {
+ if (recur_str2exc(exc, days)) {
updated = 1;
break;
} else {
@@ -303,97 +318,446 @@ static int update_exc(llist_t *exc)
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. */
- char *msg_rpt_current = NULL;
- char *msg_rpt_asktype = NULL;
+ 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;
+}
+
+static void help_ilist(int_list_t list, int rule)
+{
+ char *msg1 = "";
+ char *msg2 = "";
+ char *byday_w_d = _("Limit repetition to listed days.");
+ char *byday_w_w = _("Expand repetition to listed days.");
+ char *byday_m_m_1 =
+ _("Expand repetition to listed days, either all or 1st, 2nd, ... of month.");
+ char *byday_m_m_2 =
+ _("Note: limit to monthdays, if any.");
+ char *byday_y_y_1 =
+ _("Expand repetition to listed days, either all or 1st, 2nd, ... of year.");
+ char *byday_y_y_2 =
+ _("Note: expand to listed months, if any; limit to monthdays, if any.");
+ char *bymonth_dwm =
+ _("Limit repetition to listed months.");
+ char *bymonth_y =
+ _("Expand repetition to listed months.");
+ char *bymonthday_d = _("Limit repetition to listed days of month.");
+ char *bymonthday_my = _("Expand repetition to listed days of month.");
+
+
+ switch (list) {
+ case BYDAY_W:
+ switch (rule) {
+ case RECUR_DAILY:
+ msg1 = byday_w_d;
+ msg2 = "";
+ break;
+ case RECUR_WEEKLY:
+ msg1 = byday_w_w;
+ msg2 = "";
+ break;
+ default:
+ EXIT("internal inconsistency");
+ }
+ break;
+ case BYDAY_M:
+ switch (rule) {
+ case RECUR_MONTHLY:
+ msg1 = byday_m_m_1;
+ msg2 = byday_m_m_2;
+ break;
+ default:
+ EXIT("internal inconsistency");
+ }
+ break;
+ case BYDAY_Y:
+ switch (rule) {
+ case RECUR_YEARLY:
+ msg1 = byday_y_y_1;
+ msg2 = byday_y_y_2;
+ break;
+ default:
+ EXIT("internal inconsistency");
+ }
+ break;
+ case BYMONTH:
+ switch (rule) {
+ case RECUR_DAILY:
+ case RECUR_WEEKLY:
+ case RECUR_MONTHLY:
+ msg1 = bymonth_dwm;
+ msg2 = "";
+ break;
+ case RECUR_YEARLY:
+ msg1 = bymonth_y;
+ msg2 = "";
+ break;
+ default:
+ break;
+ }
+ break;
+ case BYMONTHDAY:
+ switch (rule) {
+ case RECUR_DAILY:
+ msg1 = bymonthday_d;
+ msg2 = "";
+ break;
+ case RECUR_MONTHLY:
+ case RECUR_YEARLY:
+ msg1 = bymonthday_my;
+ msg2 = "";
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ status_mesg(msg1, msg2);
+ keys_wgetch(win[KEY].p);
+}
+
+/* Edit an rrule (linked) list of integers. */
+static int edit_ilist(llist_t *ilist, int_list_t list_type, int rule_type)
+{
+ char *msg;
+ char *wday = NULL;
+ char *wday_w = _("Weekdays %s|..|%s, space-separated list, '?' for help:");
+ char *wday_m =
+ _("Weekdays [n]%s|..|[n]%s, space-separated list, n=1,-1,..,5,-5, '?' for help:");
+ char *wday_y =
+ _("Weekdays [n]%s|..|[n]%s, space-separated list, n=1,-1,..,53,-53, '?' for help:");
+ char *month = _("Months 1|..|12, space-separated list, '?' for help:");
+ char *mday = _("Monthdays 1|..|31 or -1|..|-31, space-separated list, '?' for help:");
+ char *invalid = _("Invalid format - try again.");
+ char *cont = _("Press any key to continue.");
+ int updated = 0;
+
+ if (list_type == NOLL)
+ return !updated;
+ char *istr;
+ enum getstr ret;
+
+ switch (list_type) {
+ case BYDAY_W:
+ asprintf(&wday, wday_w,
+ nl_langinfo(ABDAY_2), nl_langinfo(ABDAY_1));
+ msg = wday;
+ break;
+ case BYDAY_M:
+ asprintf(&wday, wday_m,
+ nl_langinfo(ABDAY_2), nl_langinfo(ABDAY_1));
+ msg = wday;
+ break;
+ case BYDAY_Y:
+ asprintf(&wday, wday_y,
+ nl_langinfo(ABDAY_2), nl_langinfo(ABDAY_1));
+ msg = wday;
+ break;
+ case BYMONTH:
+ msg = month;
+ break;
+ case BYMONTHDAY:
+ msg = mday;
+ break;
+ default:
+ msg = NULL;
+ break;
+ }
+ status_mesg(msg, "");
+ istr = int2str(ilist, list_type);
+ while (1) {
+ ret = updatestring(win[STA].p, &istr, 0, 1);
+ if (ret == GETSTRING_VALID || ret == GETSTRING_RET) {
+ if (*(istr + strlen(istr) - 1) == '?')
+ help_ilist(list_type, rule_type);
+ else if (str2int(ilist, istr, list_type)) {
+ updated = 1;
+ break;
+ } else {
+ status_mesg(invalid, cont);
+ keys_wgetch(win[KEY].p);
+ }
+ mem_free(istr);
+ status_mesg(msg, "");
+ istr = int2str(ilist, list_type);
+ } else if (ret == GETSTRING_ESC)
+ break;
+ }
+ mem_free(istr);
+ mem_free(wday);
+
+ return updated;
+}
+
+static int update_rept(time_t start, long dur, struct rpt **rpt, llist_t *exc,
+ int simple)
+{
+ int updated = 0, count;
+ struct rpt nrpt;
+ time_t until;
+ char *types = NULL;
char *freqstr = NULL;
char *timstr = NULL;
char *outstr = NULL;
+ const char *msg_cont = _("Press any key to continue.");
- /* Update repetition type. */
- int newtype;
- const char *msg_rpt_prefix = _("Enter the new repetition type:");
- const char *msg_rpt_daily = _("(d)aily");
- const char *msg_rpt_weekly = _("(w)eekly");
- const char *msg_rpt_monthly = _("(m)onthly");
- const char *msg_rpt_yearly = _("(y)early");
+ LLIST_INIT(&nrpt.exc);
+ LLIST_INIT(&nrpt.bywday);
+ LLIST_INIT(&nrpt.bymonth);
+ LLIST_INIT(&nrpt.bymonthday);
+
+ /* Edit repetition type. */
+ const char *msg_prefix = _("Base period:");
+ const char *daily = _("day");
+ const char *weekly = _("week");
+ const char *monthly = _("month");
+ const char *yearly = _("year");
+ const char *dwmy = _("[dwmy]");
/* Find the current repetition type. */
- const char *rpt_current;
+ const char *current;
switch (recur_def2char((*rpt)->type)) {
case 'D':
- rpt_current = msg_rpt_daily;
+ current = daily;
break;
case 'W':
- rpt_current = msg_rpt_weekly;
+ current = weekly;
break;
case 'M':
- rpt_current = msg_rpt_monthly;
+ current = monthly;
break;
case 'Y':
- rpt_current = msg_rpt_yearly;
+ current = yearly;
break;
default:
- /* NOTREACHED, but makes the compiler happier. */
- rpt_current = msg_rpt_daily;
+ /* New item. */
+ current = "";
}
- asprintf(&msg_rpt_current, _("(currently using %s)"), rpt_current);
- asprintf(&msg_rpt_asktype, "%s %s, %s, %s, %s? %s", msg_rpt_prefix,
- msg_rpt_daily, msg_rpt_weekly, msg_rpt_monthly,
- msg_rpt_yearly, msg_rpt_current);
- const char *msg_rpt_choice = _("[dwmy]");
- switch (status_ask_choice(msg_rpt_asktype, msg_rpt_choice, 4)) {
+ asprintf(&types, "%s %s/%s/%s/%s?",
+ msg_prefix, daily, weekly, monthly, yearly);
+ if (current[0])
+ asprintf(&types, "%s [%s]", types, current);
+ switch (status_ask_choice(types, dwmy, 4)) {
case 1:
- newtype = 'D';
+ nrpt.type = recur_char2def('D');
break;
case 2:
- newtype = 'W';
+ nrpt.type = recur_char2def('W');
break;
case 3:
- newtype = 'M';
+ nrpt.type = recur_char2def('M');
break;
case 4:
- newtype = 'Y';
+ nrpt.type = recur_char2def('Y');
break;
+ case -2: /* user typed RETURN */
+ if (current[0]) {
+ nrpt.type = (*rpt)->type;
+ break;
+ }
default:
goto cleanup;
}
- /* Update frequency. */
- int newfreq;
- const char *msg_wrong_freq = _("Invalid frequency.");
- const char *msg_enter = _("Press [Enter] to continue");
+ /* Edit frequency. */
+ const char *msg_freq = _("Frequency:");
+ const char *msg_inv_freq = _("Invalid frequency.");
do {
- status_mesg(_("Enter the repetition frequency:"), "");
+ status_mesg(msg_freq, "");
mem_free(freqstr);
asprintf(&freqstr, "%d", (*rpt)->freq);
if (updatestring(win[STA].p, &freqstr, 0, 1) !=
GETSTRING_VALID) {
goto cleanup;
}
- newfreq = atoi(freqstr);
- if (newfreq == 0) {
- status_mesg(msg_wrong_freq, msg_enter);
+ nrpt.freq = atoi(freqstr);
+ if (nrpt.freq <= 0) {
+ status_mesg(msg_inv_freq, msg_cont);
keys_wait_for_any_key(win[KEY].p);
}
}
- while (newfreq == 0);
+ while (nrpt.freq <= 0);
- /* Update end date. */
- time_t newuntil;
+ /* Edit until date. */
const char *msg_until_1 =
- _("Enter end date or duration ('?' for input formats):");
+ _("Until date, increment or repeat count ('?' for input formats):");
const char *msg_help_1 =
- _("Date: %s (year or month may be omitted). Endless duration: 0.");
+ _("Date: %s (year, month may be omitted, endless: 0).");
const char *msg_help_2 =
- _("Duration in days: +dd. Duration in weeks and days: +??w??d.");
- const char *msg_wrong_time =
- _("Invalid date: end date must come after start date (%s).");
- const char *msg_wrong_date = _("Invalid date.");
+ _("Increment: +?? (days) or: +??w??d (weeks). "
+ "Repeat count: #?? (number).");
+ const char *msg_inv_until =
+ _("Invalid date: until date must come after start date (%s).");
+ const char *msg_inv_date = _("Invalid date.");
+ const char *msg_count = _("Repeat count is too big.");
for (;;) {
+ count = 0;
mem_free(timstr);
if ((*rpt)->until)
timstr = date_sec2date_str((*rpt)->until, DATEFMT(conf.input_datefmt));
@@ -403,7 +767,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)
goto cleanup;
if (strcmp(timstr, "") == 0 || strcmp(timstr, "0") == 0) {
- newuntil = 0;
+ nrpt.until = 0;
break;
}
if (*(timstr + strlen(timstr) - 1) == '?') {
@@ -415,56 +779,160 @@ static void update_rept(struct rpt **rpt, const time_t start, llist_t *exc)
}
if (*timstr == '+') {
unsigned days;
- if (!parse_date_duration(timstr + 1, &days, start)) {
- status_mesg(msg_wrong_date, msg_enter);
+ if (!parse_date_increment(timstr + 1, &days, start)) {
+ status_mesg(msg_inv_date, msg_cont);
keys_wgetch(win[KEY].p);
continue;
}
/* Until is midnight of the day. */
- newuntil = date_sec_change(
- update_time_in_date(start, 0, 0),
- 0, days
- );
+ nrpt.until = date_sec_change(DAY(start), 0, days);
+ } else if (*timstr == '#') {
+ char *eos;
+ count = strtol(timstr + 1, &eos, 10);
+ if (*eos || !(count > 0))
+ continue;
+ nrpt.until = 0;
+ if (!recur_nth_occurrence(start, dur, &nrpt, exc,
+ count, &until)) {
+ status_mesg(msg_count, msg_cont);
+ keys_wgetch(win[KEY].p);
+ continue;
+ }
+ nrpt.until = DAY(until);
+ break;
} else {
int year, month, day;
if (!parse_date(timstr, conf.input_datefmt, &year,
&month, &day, ui_calendar_get_slctd_day())) {
- status_mesg(msg_wrong_date, msg_enter);
+ status_mesg(msg_inv_date, msg_cont);
keys_wgetch(win[KEY].p);
continue;
}
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. */
- if (newuntil >= update_time_in_date(start, 0, 0))
+ if (nrpt.until >= DAY(start))
break;
mem_free(timstr);
mem_free(outstr);
timstr = date_sec2date_str(start, DATEFMT(conf.input_datefmt));
- asprintf(&outstr, msg_wrong_time, timstr);
- status_mesg(outstr, msg_enter);
+ asprintf(&outstr, msg_inv_until, timstr);
+ status_mesg(outstr, msg_cont);
keys_wgetch(win[KEY].p);
}
- /* Update exception list. */
- if (!update_exc(exc))
+ if (simple) {
+ (*rpt)->type = nrpt.type;
+ (*rpt)->freq = nrpt.freq;
+ (*rpt)->until = nrpt.until;
+ updated = 1;
goto cleanup;
+ }
+
+ /* Edit exception list. */
+ recur_exc_dup(&nrpt.exc, exc);
+ if (!edit_exc(&nrpt.exc))
+ goto cleanup;
+
+ /* 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, nrpt.type))
+ goto cleanup;
+
+ /* Edit BYMONTH list. */
+ recur_int_list_dup(&nrpt.bymonth, &(*rpt)->bymonth);
+ if (!edit_ilist(&nrpt.bymonth, BYMONTH, nrpt.type))
+ goto cleanup;
+
+ /* Edit BYMONTHDAY list. */
+ if (nrpt.type != RECUR_WEEKLY) {
+ recur_int_list_dup(&nrpt.bymonthday, &(*rpt)->bymonthday);
+ if (!edit_ilist(&nrpt.bymonthday, BYMONTHDAY, nrpt.type))
+ goto cleanup;
+ }
+
+ /* The new until may no longer be valid. */
+ if (count) {
+ nrpt.until = 0;
+ if (!recur_nth_occurrence(start, dur, &nrpt, exc,
+ count, &until)) {
+ status_mesg(msg_count, msg_cont);
+ keys_wgetch(win[KEY].p);
+ goto cleanup;
+ }
+ nrpt.until = DAY(until);
+ }
+ /*
+ * 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).
+ */
+ char *msg_match =
+ _("Repetition must begin on start day (%s); "
+ "any change discarded.");
+ if (!recur_item_find_occurrence(start, dur, &nrpt, NULL, DAY(start),
+ NULL)) {
+ mem_free(outstr);
+ outstr = day_ins(&msg_match, start);
+ status_mesg(outstr, msg_cont);
+ keys_wgetch(win[KEY].p);
+ goto cleanup;
+ }
+
+ /* Update all recurrence parameters. */
+ (*rpt)->type = nrpt.type;
+ (*rpt)->freq = nrpt.freq;
+ (*rpt)->until = nrpt.until;
+
+ recur_free_exc_list(exc);
+ recur_exc_dup(exc, &nrpt.exc);
+
+ 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);
- (*rpt)->type = recur_char2def(newtype);
- (*rpt)->freq = newfreq;
- (*rpt)->until = newuntil;
+ recur_free_int_list(&(*rpt)->bymonthday);
+ recur_int_list_dup(&(*rpt)->bymonthday, &nrpt.bymonthday);
+ updated = 1;
cleanup:
- mem_free(msg_rpt_current);
- mem_free(msg_rpt_asktype);
+ mem_free(types);
mem_free(freqstr);
mem_free(timstr);
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);
+
+ return updated;
}
/* Edit an already existing item. */
+#define ADVANCED 0
void ui_day_item_edit(void)
{
struct recur_event *re;
@@ -481,7 +949,7 @@ void ui_day_item_edit(void)
switch (p->type) {
case RECUR_EVNT:
re = p->item.rev;
- const char *choice_recur_evnt[2] = {
+ const char *choice_recur_evnt[] = {
_("Description"),
_("Repetition")
};
@@ -489,11 +957,9 @@ void ui_day_item_edit(void)
(_("Edit: "), choice_recur_evnt, 2)) {
case 1:
update_desc(&re->mesg);
- io_set_modified();
break;
case 2:
- update_rept(&re->rpt, re->day, &re->exc);
- io_set_modified();
+ update_rept(re->day, -1, &re->rpt, &re->exc, ADVANCED);
break;
default:
return;
@@ -502,7 +968,6 @@ void ui_day_item_edit(void)
case EVNT:
e = p->item.ev;
update_desc(&e->mesg);
- io_set_modified();
break;
case RECUR_APPT:
ra = p->item.rapt;
@@ -517,29 +982,25 @@ void ui_day_item_edit(void)
(_("Edit: "), choice_recur_appt, 5)) {
case 1:
need_check_notify = 1;
- update_start_time(&ra->start, &ra->dur, ra->dur == 0);
- io_set_modified();
+ update_start_time(&ra->start, &ra->dur, ra->rpt, ra->dur == 0);
break;
case 2:
update_duration(&ra->start, &ra->dur);
- io_set_modified();
break;
case 3:
if (notify_bar())
need_check_notify =
notify_same_recur_item(ra);
update_desc(&ra->mesg);
- io_set_modified();
break;
case 4:
need_check_notify = 1;
- update_rept(&ra->rpt, ra->start, &ra->exc);
- io_set_modified();
+ update_rept(ra->start, ra->dur, &ra->rpt, &ra->exc,
+ ADVANCED);
break;
case 5:
need_check_notify = 1;
- update_start_time(&ra->start, &ra->dur, 1);
- io_set_modified();
+ update_start_time(&ra->start, &ra->dur, ra->rpt, 1);
break;
default:
return;
@@ -557,24 +1018,20 @@ void ui_day_item_edit(void)
(_("Edit: "), choice_appt, 4)) {
case 1:
need_check_notify = 1;
- update_start_time(&a->start, &a->dur, a->dur == 0);
- io_set_modified();
+ update_start_time(&a->start, &a->dur, NULL, a->dur == 0);
break;
case 2:
update_duration(&a->start, &a->dur);
- io_set_modified();
break;
case 3:
if (notify_bar())
need_check_notify =
notify_same_item(a->start);
update_desc(&a->mesg);
- io_set_modified();
break;
case 4:
need_check_notify = 1;
- update_start_time(&a->start, &a->dur, 1);
- io_set_modified();
+ update_start_time(&a->start, &a->dur, NULL, 1);
break;
default:
return;
@@ -583,12 +1040,13 @@ void ui_day_item_edit(void)
default:
break;
}
-
+ io_set_modified();
ui_calendar_monthly_view_cache_set_invalid();
if (need_check_notify)
notify_check_next_app(1);
}
+#undef ADVANCED
/* Pipe an appointment or event to an external program. */
void ui_day_item_pipe(void)
@@ -609,7 +1067,7 @@ void ui_day_item_pipe(void)
return;
wins_prepare_external();
- if ((pid = shell_exec(NULL, &pout, *arg, arg))) {
+ if ((pid = shell_exec(NULL, &pout, NULL, 0, *arg, arg))) {
fpout = fdopen(pout, "w");
switch (p->type) {
@@ -630,7 +1088,7 @@ void ui_day_item_pipe(void)
}
fclose(fpout);
- child_wait(NULL, &pout, pid);
+ child_wait(NULL, &pout, NULL, pid);
press_any_key();
}
wins_unprepare_external();
@@ -766,81 +1224,85 @@ void ui_day_item_add(void)
/* Delete an item from the appointment list. */
void ui_day_item_delete(unsigned reg)
{
- const char *del_app_str =
- _("Do you really want to delete this item?");
-
- const char *erase_warning =
- _("This item is recurrent. "
- "Delete (a)ll occurences or just this (o)ne?");
- const char *erase_choices = _("[ao]");
- const int nb_erase_choices = 2;
-
- const char *note_warning =
- _("This item has a note attached to it. "
- "Delete (i)tem or just its (n)ote?");
- const char *note_choices = _("[in]");
- const int nb_note_choices = 2;
+ const char *msg, *choices;
+ int nb_choices;
+
time_t occurrence;
if (day_item_count(0) <= 0)
return;
struct day_item *p = ui_day_get_sel();
-
- if (conf.confirm_delete) {
- if (status_ask_bool(del_app_str) != 1) {
- wins_erase_status_bar();
- return;
- }
+ int has_note = (day_item_get_note(p) != NULL);
+ int is_recur = (p->type == RECUR_EVNT || p->type == RECUR_APPT);
+
+ if (has_note && is_recur) {
+ msg = _("This item is recurrent and has a note attached to it. "
+ "Delete (s)elected occurrence, (a)ll occurrences, "
+ "or just its (n)ote?");
+ choices = _("[san]");
+ nb_choices = 3;
+ } else if (has_note) {
+ msg = _("This item has a note attached to it. "
+ "Delete (s)elected occurrence or just its (n)ote?");
+ choices = _("[sn]");
+ nb_choices = 2;
+ } else if (is_recur) {
+ msg = _("This item is recurrent. "
+ "Delete (s)elected occurrence or (a)ll occurrences?");
+ choices = _("[sa]");
+ nb_choices = 2;
+ } else {
+ msg = _("Confirm deletion. "
+ "Delete (s)elected occurrence? Press (s) to confirm.");
+ choices = _("[s]");
+ nb_choices = 1;
}
- if (day_item_get_note(p)) {
- switch (status_ask_choice
- (note_warning, note_choices, nb_note_choices)) {
- case 1:
- break;
- case 2:
- day_item_erase_note(p);
- io_set_modified();
- return;
- default: /* User escaped */
- return;
- }
+ int answer = 1;
+ if (nb_choices > 1 || conf.confirm_delete) {
+ answer = status_ask_choice(msg, choices, nb_choices);
}
- if (p->type == RECUR_EVNT || p->type == RECUR_APPT) {
- switch (status_ask_choice
- (erase_warning, erase_choices, nb_erase_choices)) {
- case 1:
- break;
- case 2:
- if (p->type == RECUR_EVNT) {
- day_item_add_exc(p, ui_day_sel_date());
- } else {
- recur_apoint_find_occurrence(p->item.rapt,
- ui_day_sel_date(),
- &occurrence);
- day_item_add_exc(p, occurrence);
- }
+ /* Always map "all occurrences" to 2 and "note" to 3. */
+ if (has_note && !is_recur && answer == 2)
+ answer = 3;
+ /*
+ * The option "selected occurrence" should be treated like "all
+ * occurrences" for a non-recurrent item (delete the whole item).
+ */
+ if (!is_recur && answer == 1)
+ answer = 2;
- io_set_modified();
- ui_calendar_monthly_view_cache_set_invalid();
- /* Keep the selection on the same day. */
- day_set_sel_data(
- day_get_item(listbox_get_sel(&lb_apt) - 1)
- );
- return;
- default:
- return;
+ switch (answer) {
+ case 1:
+ /* Delete selected occurrence (of a recurrent item) only. */
+ if (p->type == RECUR_EVNT) {
+ day_item_add_exc(p, ui_day_sel_date());
+ } else {
+ recur_apoint_find_occurrence(p->item.rapt,
+ ui_day_sel_date(),
+ &occurrence);
+ day_item_add_exc(p, occurrence);
}
+ /* Keep the selection on the same day. */
+ day_set_sel_data(day_get_item(listbox_get_sel(&lb_apt) - 1));
+ break;
+ case 2:
+ /* Delete all occurrences (or a non-recurrent item). */
+ ui_day_item_cut(reg);
+ /* Keep the selection on the same day. */
+ day_set_sel_data(day_get_item(listbox_get_sel(&lb_apt) - 1));
+ break;
+ case 3:
+ /* Delete note. */
+ day_item_erase_note(p);
+ break;
+ default:
+ /* User escaped, do nothing. */
+ return;
}
- ui_day_item_cut_free(reg);
- p = day_cut_item(listbox_get_sel(&lb_apt));
- day_cut[reg].type = p->type;
- day_cut[reg].item = p->item;
- /* Keep the selection on the same day. */
- day_set_sel_data(day_get_item(listbox_get_sel(&lb_apt) - 1));
io_set_modified();
ui_calendar_monthly_view_cache_set_invalid();
}
@@ -854,163 +1316,88 @@ void ui_day_item_delete(unsigned reg)
*/
void ui_day_item_repeat(void)
{
- char user_input[BUFSIZ] = "";
- const char *msg_rpt_prefix = _("Enter the repetition type:");
- const char *msg_rpt_daily = _("(d)aily");
- const char *msg_rpt_weekly = _("(w)eekly");
- const char *msg_rpt_monthly = _("(m)onthly");
- const char *msg_rpt_yearly = _("(y)early");
- const char *msg_type_choice = _("[dwmy]");
- const char *mesg_freq_1 = _("Enter the repetition frequency:");
- const char *mesg_wrong_freq = _("Invalid frequency.");
- const char *mesg_until_1 = _("Enter end date or duration ('?' for input formats):");
- const char *mesg_help_1 = _("Date: %s (year or month may be omitted). Endless duration: '0'.");
- const char *mesg_help_2 = _("Duration in days: +dd. Duration in weeks and days: +??w??d.");
- const char *mesg_wrong_1 = _("Invalid date.");
- const char *mesg_wrong_2 = _("Press [ENTER] to continue.");
- const char *wrong_type_1 = _("This item is already a repeated one.");
- const char *wrong_type_2 = _("Press [ENTER] to continue.");
- const char *mesg_older = _("Invalid date: end date must come after start date (%s).");
-
- char *msg_asktype;
- asprintf(&msg_asktype, "%s %s, %s, %s, %s", msg_rpt_prefix,
- msg_rpt_daily, msg_rpt_weekly, msg_rpt_monthly,
- msg_rpt_yearly);
-
- int type = 0, freq = 0;
- int item_nb;
+ int item_nb, simple;
struct day_item *p;
- struct recur_apoint *ra;
- time_t until;
- unsigned days;
+ long dur;
+ struct rpt rpt, *r;
+ const char *already = _("Already repeated.");
+ const char *cont = _("Press any key to continue.");
+ const char *repetition = _("A (s)imple or (a)dvanced repetition?");
+ const char *sa = _("[sa]");
if (day_item_count(0) <= 0)
- goto cleanup;
+ return;
item_nb = listbox_get_sel(&lb_apt);
p = day_get_item(item_nb);
if (p->type != APPT && p->type != EVNT) {
- status_mesg(wrong_type_1, wrong_type_2);
+ status_mesg(already, cont);
keys_wait_for_any_key(win[KEY].p);
- goto cleanup;
+ return;
}
- switch (status_ask_choice(msg_asktype, msg_type_choice, 4)) {
+ switch (status_ask_choice(repetition, sa, 2)) {
case 1:
- type = RECUR_DAILY;
+ simple = 1;
break;
case 2:
- type = RECUR_WEEKLY;
- break;
- case 3:
- type = RECUR_MONTHLY;
- break;
- case 4:
- type = RECUR_YEARLY;
+ simple = 0;
break;
default:
- goto cleanup;
- }
-
- while (freq == 0) {
- status_mesg(mesg_freq_1, "");
- if (getstring(win[STA].p, user_input, BUFSIZ, 0, 1) !=
- GETSTRING_VALID)
- goto cleanup;
- freq = atoi(user_input);
- if (freq == 0) {
- status_mesg(mesg_wrong_freq, wrong_type_2);
- keys_wait_for_any_key(win[KEY].p);
- }
- user_input[0] = '\0';
+ return;
}
- char *outstr, *datestr;
- for (;;) {
- status_mesg(mesg_until_1, "");
- if (getstring(win[STA].p, user_input, BUFSIZ, 0, 1) == GETSTRING_ESC)
- goto cleanup;
- if (strcmp(user_input, "") == 0 || strcmp(user_input, "0") == 0) {
- until = 0;
- break;
- }
- if (*user_input == '?') {
- user_input[0] = '\0';
- asprintf(&outstr, mesg_help_1, DATEFMT_DESC(conf.input_datefmt));
- status_mesg(outstr, mesg_help_2);
- mem_free(outstr);
- wgetch(win[KEY].p);
- continue;
- }
- if (*user_input == '+') {
- if (!parse_date_duration(user_input + 1, &days, p->start)) {
- status_mesg(mesg_wrong_1, mesg_wrong_2);
- keys_wgetch(win[KEY].p);
- continue;
- }
- /* Until is midnight of the day. */
- until = date_sec_change(
- update_time_in_date(p->start, 0, 0),
- 0, days
- );
- } else {
- int year, month, day;
- if (!parse_date(user_input, conf.input_datefmt,
- &year, &month, &day, ui_calendar_get_slctd_day())) {
- status_mesg(mesg_wrong_1, mesg_wrong_2);
- keys_wgetch(win[KEY].p);
- continue;
- }
- struct date d = { day, month, year };
- until = date2sec(d, 0, 0);
- }
- /* Compare days (midnights) - until-day may equal start day. */
- if (until >= get_slctd_day())
- break;
-
- datestr = date_sec2date_str(p->start, DATEFMT(conf.input_datefmt));
- asprintf(&outstr, mesg_older, datestr);
- status_mesg(outstr, wrong_type_2);
- mem_free(datestr);
- mem_free(outstr);
- keys_wgetch(win[KEY].p);
- }
+ if (p->type == APPT)
+ dur = p->item.apt->dur;
+ else
+ dur = -1;
+ rpt.type = -1;
+ rpt.freq = 1;
+ rpt.until = 0;
+ LLIST_INIT(&rpt.bymonth);
+ LLIST_INIT(&rpt.bywday);
+ LLIST_INIT(&rpt.bymonthday);
+ LLIST_INIT(&rpt.exc);
+ r = &rpt;
+ if (!update_rept(p->start, dur, &r, &rpt.exc, simple))
+ return;
- /* Set the selected APP item. */
struct day_item d = empty_day;
if (p->type == EVNT) {
struct event *ev = p->item.ev;
d.item.rev = recur_event_new(ev->mesg, ev->note, ev->day,
- ev->id, type, freq, until, NULL);
- } else if (p->type == APPT) {
+ ev->id, &rpt);
+ } else {
struct apoint *apt = p->item.apt;
- d.item.rapt = ra = recur_apoint_new(apt->mesg, apt->note,
+ d.item.rapt = recur_apoint_new(apt->mesg, apt->note,
apt->start, apt->dur,
- apt->state, type, freq,
- until, NULL);
+ apt->state, &rpt);
if (notify_bar())
- notify_check_repeated(ra);
- } else {
- EXIT(_("wrong item type"));
- /* NOTREACHED */
+ notify_check_repeated(d.item.rapt);
}
+ ui_day_item_cut(REG_BLACK_HOLE);
day_set_sel_data(&d);
-
- ui_day_item_cut_free(REG_BLACK_HOLE);
- p = day_cut_item(item_nb);
- day_cut[REG_BLACK_HOLE].type = p->type;
- day_cut[REG_BLACK_HOLE].item = p->item;
io_set_modified();
-
ui_calendar_monthly_view_cache_set_invalid();
+}
-cleanup:
- mem_free(msg_asktype);
+/* Delete an item and save it in a register. */
+void ui_day_item_cut(unsigned reg)
+{
+ struct day_item *p;
+
+ ui_day_item_cut_free(reg);
+
+ p = day_cut_item(listbox_get_sel(&lb_apt));
+ day_cut[reg].type = p->type;
+ day_cut[reg].item = p->item;
}
/* Free the current cut item, if any. */
void ui_day_item_cut_free(unsigned reg)
{
+ EXIT_IF(reg > REG_BLACK_HOLE, "illegal register");
+
if (!day_cut[reg].type) {
/* No previously cut item, don't free anything. */
return;
@@ -1133,7 +1520,7 @@ void ui_day_draw(int n, WINDOW *win, int y, int hilt, void *cb_data)
{
struct day_item *item = day_get_item(n);
/* The item order always indicates the date. */
- time_t date = update_time_in_date(item->order, 0, 0);
+ time_t date = DAY(item->order);
int width = lb_apt.sw.w - 2, is_slctd;
hilt = hilt && (wins_slctd() == APP);