diff options
Diffstat (limited to 'src/ui-day.c')
-rw-r--r-- | src/ui-day.c | 953 |
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); |