diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/apoint.c | 10 | ||||
-rw-r--r-- | src/args.c | 39 | ||||
-rw-r--r-- | src/calcurse.c | 169 | ||||
-rw-r--r-- | src/calcurse.h | 76 | ||||
-rw-r--r-- | src/config.c | 32 | ||||
-rw-r--r-- | src/custom.c | 93 | ||||
-rw-r--r-- | src/day.c | 104 | ||||
-rw-r--r-- | src/dmon.c | 10 | ||||
-rw-r--r-- | src/event.c | 2 | ||||
-rw-r--r-- | src/getstring.c | 4 | ||||
-rw-r--r-- | src/help.c | 6 | ||||
-rw-r--r-- | src/hooks.c | 37 | ||||
-rw-r--r-- | src/htable.h | 2 | ||||
-rw-r--r-- | src/ical.c | 1229 | ||||
-rw-r--r-- | src/io.c | 240 | ||||
-rw-r--r-- | src/keys.c | 335 | ||||
-rw-r--r-- | src/listbox.c | 2 | ||||
-rw-r--r-- | src/llist.c | 204 | ||||
-rw-r--r-- | src/llist.h | 5 | ||||
-rw-r--r-- | src/llist_ts.h | 4 | ||||
-rw-r--r-- | src/mem.c | 2 | ||||
-rw-r--r-- | src/note.c | 17 | ||||
-rw-r--r-- | src/notify.c | 29 | ||||
-rw-r--r-- | src/pcal.c | 6 | ||||
-rw-r--r-- | src/queue.c | 2 | ||||
-rw-r--r-- | src/recur.c | 167 | ||||
-rw-r--r-- | src/sha1.c | 3 | ||||
-rw-r--r-- | src/sha1.h | 4 | ||||
-rw-r--r-- | src/sigs.c | 20 | ||||
-rw-r--r-- | src/strings.c | 2 | ||||
-rw-r--r-- | src/todo.c | 6 | ||||
-rw-r--r-- | src/ui-calendar.c | 123 | ||||
-rw-r--r-- | src/ui-day.c | 270 | ||||
-rw-r--r-- | src/ui-todo.c | 49 | ||||
-rw-r--r-- | src/utf8.c | 2 | ||||
-rw-r--r-- | src/utils.c | 155 | ||||
-rw-r--r-- | src/vars.c | 8 | ||||
-rw-r--r-- | src/vector.c | 2 | ||||
-rw-r--r-- | src/vector.h | 2 | ||||
-rw-r--r-- | src/wins.c | 26 |
40 files changed, 2166 insertions, 1332 deletions
diff --git a/src/apoint.c b/src/apoint.c index 4cb77f6..e138e5e 100644 --- a/src/apoint.c +++ b/src/apoint.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -42,6 +42,8 @@ #include "calcurse.h" #include "sha1.h" +#define APPT_TIME_LENGTH 25 + llist_ts_t alist_p; void apoint_free(struct apoint *apt) @@ -134,16 +136,14 @@ void apoint_sec2str(struct apoint *o, time_t day, char *start, char *end) } else { t = o->start; localtime_r(&t, <); - snprintf(start, HRMIN_SIZE, "%02u:%02u", lt.tm_hour, - lt.tm_min); + strftime(start, APPT_TIME_LENGTH, conf.timefmt, <); } if (o->start + o->dur > day + DAYLEN(day)) { strncpy(end, "..:..", 6); } else { t = o->start + o->dur; localtime_r(&t, <); - snprintf(end, HRMIN_SIZE, "%02u:%02u", lt.tm_hour, - lt.tm_min); + strftime(end, APPT_TIME_LENGTH, conf.timefmt, <); } } @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -97,7 +97,7 @@ static void usage(void) "calcurse [-D <directory>] [-C <directory>] [-c <calendar file>]\n" "calcurse -Q [--from <date>] [--to <date>] [--days <number>]\n" "calcurse -a | -d <date> | -d <number> | -n | -r[<number>] | -s[<date>] | -t[<number>]\n" - "calcurse -h | -v | --status | -G | -P | -g | -i <file> | -x[<file>] | --daemon")); + "calcurse -h | -v | --status | -G | -P | -g | -i <file> | -x[<format>] | --daemon")); } static void usage_try(void) @@ -112,7 +112,7 @@ static void version_arg(void) { printf(_("calcurse %s -- text-based organizer\n"), VERSION); putchar('\n'); - printf("%s\n", _("Copyright (c) 2004-2020 calcurse Development Team.")); + printf("%s\n", _("Copyright (c) 2004-2023 calcurse Development Team.")); printf("%s\n", _("This is free software; see the source for copying conditions.")); } @@ -149,7 +149,7 @@ static void help_arg(void) printf("%s\n", _(" -g, --gc Run the garbage collector")); printf("%s\n", _(" -h, --help Show this help text")); printf("%s\n", _(" -i, --import <file> Import iCal data from file")); - printf("%s\n", _(" -q, --quiet Suppress system dialogs")); + printf("%s\n", _(" -q, --quiet Suppress import/export result message")); printf("%s\n", _(" --read-only Do not save configuration or data files")); printf("%s\n", _(" --status Display status of running instances")); printf("%s\n", _(" -v, --version Show version information")); @@ -398,7 +398,7 @@ cleanup: /* * Parse the command-line arguments and call the appropriate * routines to handle those arguments. Also initialize the data paths. - * Returns the non-interactive value. + * Exit here in case of errors else return the non-interactive value. */ int parse_args(int argc, char **argv) { @@ -428,7 +428,7 @@ int parse_args(int argc, char **argv) const char *cfile = NULL, *confdir = NULL; char *ifile = NULL; - int non_interactive = 1; + int ret, non_interactive = 1; int ch, cpid, type; regex_t reg; char buf[BUFSIZ]; @@ -515,6 +515,10 @@ int parse_args(int argc, char **argv) case 'c': cfile = optarg; break; + case '?': + usage(); + usage_try(); + exit(EXIT_FAILURE); \ } } io_init(cfile, datadir, confdir); @@ -557,7 +561,7 @@ int parse_args(int argc, char **argv) break; case 'h': help_arg(); - goto cleanup; + exit(EXIT_SUCCESS); case 'g': gc = 1; break; @@ -615,7 +619,7 @@ int parse_args(int argc, char **argv) break; case 'v': version_arg(); - goto cleanup; + exit(EXIT_SUCCESS); case 'x': export = 1; if (optarg) { @@ -860,10 +864,6 @@ int parse_args(int argc, char **argv) '\0'; cmd_line = 1; break; - default: - usage(); - usage_try(); - goto cleanup; } } @@ -876,12 +876,8 @@ int parse_args(int argc, char **argv) (format_opt && !(grep + query + dump_imported)) || (query_range && !query) || (purge && !filter.invert) - ) { - ERROR_MSG(_("invalid argument combination")); - usage(); - usage_try(); - goto cleanup; - } + ) + EXIT(_("invalid argument combination")); EXIT_IF(to >= 0 && range, _("cannot specify a range and an end date")); if (from == -1) @@ -967,10 +963,12 @@ int parse_args(int argc, char **argv) fmt_apt = fmt_rapt = fmt_ev = fmt_rev = NULL; fmt_todo = NULL; } - io_import_data(IO_IMPORT_ICAL, ifile, fmt_ev, fmt_rev, fmt_apt, - fmt_rapt, fmt_todo); + ret = io_import_data(IO_IMPORT_ICAL, ifile, fmt_ev, fmt_rev, + fmt_apt, fmt_rapt, fmt_todo); io_save_apts(path_apts); io_save_todo(path_todo); + if (!ret) + exit_calcurse(EXIT_FAILURE); } else if (export) { io_check_file(path_apts); io_check_file(path_todo); @@ -984,7 +982,6 @@ int parse_args(int argc, char **argv) non_interactive = 0; } -cleanup: /* Free filter parameters. */ if (filter.regex) regfree(filter.regex); diff --git a/src/calcurse.c b/src/calcurse.c index fe8b574..c89d1dd 100644 --- a/src/calcurse.c +++ b/src/calcurse.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -41,37 +41,6 @@ #define HANDLE_KEY(key, fn) case key: fn(); break; int count, reg; -/* - * Store events and appointments for a range of days in the day vector - - * beginning with the selected day - and load them into the APP listbox. If no - * day-change occurs, reset the selected APP item and with it the selected day, - * thereby storing and loading the same range of days. - */ -static void do_storage(int day_changed) -{ - int pre_sel; - /* - * Save the selected item before rebuilding the day vector - - * unless a preselection is already set. - */ - if (!(pre_sel = day_check_sel_data())) - day_set_sel_data(ui_day_get_sel()); - - if (!day_changed) - ui_day_sel_reset(); - - /* The day_items vector. */ - day_store_items(get_slctd_day(), 1, day_get_days()); - /* The APP listbox. */ - ui_day_load_items(); - - if (day_changed && !pre_sel) - ui_day_sel_reset(); - else - ui_day_find_sel(); - - day_set_sel_data(&empty_day); -} static inline void key_generic_change_view(void) { @@ -80,6 +49,13 @@ static inline void key_generic_change_view(void) wins_update(FLAG_ALL); } +static inline void key_generic_prev_view(void) +{ + wins_reset_status_page(); + wins_slctd_prev(); + wins_update(FLAG_ALL); +} + static inline void key_generic_other_cmd(void) { wins_other_status_page(); @@ -91,7 +67,7 @@ static inline void key_generic_goto(void) wins_erase_status_bar(); ui_calendar_set_current_date(); ui_calendar_change_day(conf.input_datefmt); - do_storage(1); + day_do_storage(1); wins_update(FLAG_CAL | FLAG_APP | FLAG_STA); } @@ -100,7 +76,7 @@ static inline void key_generic_goto_today(void) wins_erase_status_bar(); ui_calendar_set_current_date(); ui_calendar_goto_today(); - do_storage(1); + day_do_storage(1); wins_update(FLAG_CAL | FLAG_APP | FLAG_STA); } @@ -118,14 +94,14 @@ static inline void key_generic_config_menu(void) wins_erase_status_bar(); wins_reset_status_page(); custom_config_main(); - do_storage(0); + day_do_storage(0); wins_update(FLAG_ALL); } static inline void key_generic_add_appt(void) { ui_day_item_add(); - do_storage(0); + day_do_storage(0); wins_update(FLAG_CAL | FLAG_APP | FLAG_STA); } @@ -141,7 +117,7 @@ static inline void key_add_item(void) case APP: case CAL: ui_day_item_add(); - do_storage(0); + day_do_storage(0); wins_update(FLAG_CAL | FLAG_APP | FLAG_STA); break; case TOD: @@ -157,7 +133,7 @@ static inline void key_edit_item(void) { if (wins_slctd() == APP && !event_dummy(ui_day_get_sel())) { ui_day_item_edit(); - do_storage(0); + day_do_storage(0); wins_update(FLAG_CAL | FLAG_APP | FLAG_STA); } else if (wins_slctd() == TOD) { ui_todo_edit(); @@ -169,7 +145,7 @@ static inline void key_del_item(void) { if (wins_slctd() == APP && !event_dummy(ui_day_get_sel())) { ui_day_item_delete(reg); - do_storage(0); + day_do_storage(0); wins_update(FLAG_CAL | FLAG_APP | FLAG_STA); } else if (wins_slctd() == TOD) { ui_todo_delete(); @@ -187,7 +163,7 @@ static inline void key_generic_paste(void) { if (wins_slctd() == APP) { ui_day_item_paste(reg); - do_storage(0); + day_do_storage(0); wins_update(FLAG_CAL | FLAG_APP); } } @@ -196,7 +172,7 @@ static inline void key_repeat_item(void) { if (wins_slctd() == APP && !event_dummy(ui_day_get_sel())) { ui_day_item_repeat(); - do_storage(0); + day_do_storage(0); wins_update(FLAG_CAL | FLAG_APP | FLAG_STA); } } @@ -205,7 +181,7 @@ static inline void key_flag_item(void) { if (wins_slctd() == APP && !event_dummy(ui_day_get_sel())) { ui_day_flag(); - do_storage(0); + day_do_storage(0); wins_update(FLAG_APP); } else if (wins_slctd() == TOD) { ui_todo_flag(); @@ -244,7 +220,7 @@ static inline void key_edit_note(void) { if (wins_slctd() == APP && !event_dummy(ui_day_get_sel())) { ui_day_edit_note(); - do_storage(0); + day_do_storage(0); } else if (wins_slctd() == TOD) { ui_todo_edit_note(); } @@ -284,7 +260,7 @@ static inline void key_generic_save(void) if (ret == IO_SAVE_RELOAD) { ui_todo_load_items(); ui_todo_sel_reset(); - do_storage(0); + day_do_storage(0); notify_check_next_app(1); ui_calendar_monthly_view_cache_set_invalid(); } @@ -319,7 +295,7 @@ static inline void key_generic_reload(void) ret == IO_RELOAD_MERGE) { ui_todo_load_items(); ui_todo_sel_reset(); - do_storage(0); + day_do_storage(0); notify_check_next_app(1); ui_calendar_monthly_view_cache_set_invalid(); } @@ -349,7 +325,7 @@ static inline void key_generic_import(void) wins_erase_status_bar(); io_import_data(IO_IMPORT_ICAL, NULL, NULL, NULL, NULL, NULL, NULL); ui_calendar_monthly_view_cache_set_invalid(); - do_storage(0); + day_do_storage(0); ui_todo_load_items(); wins_update(FLAG_ALL); } @@ -374,14 +350,14 @@ static inline void key_generic_export() break; } - do_storage(0); + day_do_storage(0); wins_update(FLAG_ALL); } static inline void key_generic_prev_day(void) { ui_calendar_move(DAY_PREV, count); - do_storage(1); + day_do_storage(1); wins_update(FLAG_CAL | FLAG_APP); } @@ -394,7 +370,7 @@ static inline void key_move_left(void) static inline void key_generic_next_day(void) { ui_calendar_move(DAY_NEXT, count); - do_storage(1); + day_do_storage(1); wins_update(FLAG_CAL | FLAG_APP); } @@ -407,7 +383,7 @@ static inline void key_move_right(void) static inline void key_generic_prev_week(void) { ui_calendar_move(WEEK_PREV, count); - do_storage(1); + day_do_storage(1); wins_update(FLAG_CAL | FLAG_APP); } @@ -418,7 +394,7 @@ static inline void key_move_up(void) } else if (wins_slctd() == APP) { if (!ui_day_sel_move(-1)) { ui_calendar_move(DAY_PREV, 1); - do_storage(1); + day_do_storage(1); ui_day_sel_dayend(); } wins_update(FLAG_APP | FLAG_CAL); @@ -431,7 +407,7 @@ static inline void key_move_up(void) static inline void key_generic_next_week(void) { ui_calendar_move(WEEK_NEXT, count); - do_storage(1); + day_do_storage(1); wins_update(FLAG_CAL | FLAG_APP); } @@ -442,7 +418,7 @@ static inline void key_move_down(void) } else if (wins_slctd() == APP) { if (!ui_day_sel_move(1)) { ui_calendar_move(DAY_PREV, day_get_days() - 2); - do_storage(1); + day_do_storage(1); ui_day_sel_daybegin(day_get_days() - 1); } wins_update(FLAG_APP | FLAG_CAL); @@ -455,28 +431,28 @@ static inline void key_move_down(void) static inline void key_generic_prev_month(void) { ui_calendar_move(MONTH_PREV, count); - do_storage(1); + day_do_storage(1); wins_update(FLAG_CAL | FLAG_APP); } static inline void key_generic_next_month(void) { ui_calendar_move(MONTH_NEXT, count); - do_storage(1); + day_do_storage(1); wins_update(FLAG_CAL | FLAG_APP); } static inline void key_generic_prev_year(void) { ui_calendar_move(YEAR_PREV, count); - do_storage(1); + day_do_storage(1); wins_update(FLAG_CAL | FLAG_APP); } static inline void key_generic_next_year(void) { ui_calendar_move(YEAR_NEXT, count); - do_storage(1); + day_do_storage(1); wins_update(FLAG_CAL | FLAG_APP); } @@ -484,7 +460,7 @@ static inline void key_start_of_week(void) { if (wins_slctd() == CAL) { ui_calendar_move(WEEK_START, count); - do_storage(1); + day_do_storage(1); wins_update(FLAG_CAL | FLAG_APP); } } @@ -493,7 +469,7 @@ static inline void key_end_of_week(void) { if (wins_slctd() == CAL) { ui_calendar_move(WEEK_END, count); - do_storage(1); + day_do_storage(1); wins_update(FLAG_CAL | FLAG_APP); } } @@ -557,7 +533,8 @@ static inline void key_generic_cmd(void) int valid = 0, force = 0, ret; char *error_msg; - status_mesg(_("Command: [ h(elp) | w(rite)(!) | q(uit)(!) | wq(!) | n(ext) ]"), ""); + status_mesg(_("Command: " + "[ h(elp) | w(rite)(!) | q(uit)(!) | wq(!) | n(ext) | p(rev) ]"), ""); if (getstring(win[STA].p, cmd, BUFSIZ, 0, 1) != GETSTRING_VALID) goto cleanup; cmd_name = strtok(cmd, " "); @@ -622,8 +599,12 @@ static inline void key_generic_cmd(void) warnbox(error_msg); goto cleanup; } - day = date2sec(*ui_calendar_get_slctd_day(), 0, 0); item = ui_day_get_sel(); + /* + * The selected day need not be the (item) start day + * for multi-day occurrences. + */ + day = DAY(item->start); if (item->type == RECUR_EVNT) { rev = item->item.rev; more = recur_next_occurrence(rev->day, -1, rev->rpt, &rev->exc, @@ -638,14 +619,59 @@ static inline void key_generic_cmd(void) goto cleanup; } if (!more) { - error_msg = _("Last repetition."); + error_msg = _("Last occurrence."); warnbox(error_msg); goto cleanup; } - item->start = next; + item->order = next; ui_calendar_set_slctd_day(sec2date(next)); day_set_sel_data(item); - do_storage(1); + day_do_storage(1); + + valid = 1; + } + if (!strcmp(cmd_name, "prev") || !strcmp(cmd_name, "p")) { + struct day_item *item; + time_t day, prev; + struct recur_apoint *rapt; + struct recur_event *rev; + int more = 0; + + if (wins_slctd() != APP) { + error_msg = _("Select a repeating item in the" + " appointments panel."); + warnbox(error_msg); + goto cleanup; + } + item = ui_day_get_sel(); + /* + * The selected day need not be the (item) start day + * for multi-day occurrences. + */ + day = DAY(item->start); + if (item->type == RECUR_EVNT) { + rev = item->item.rev; + more = recur_prev_occurrence(rev->day, -1, rev->rpt, + &rev->exc, day, &prev); + } else if (item->type == RECUR_APPT) { + rapt = item->item.rapt; + more = recur_prev_occurrence(rapt->start, rapt->dur, + rapt->rpt, &rapt->exc, + day, &prev); + } else { + error_msg = _("Not a repeating item."); + warnbox(error_msg); + goto cleanup; + } + if (!more) { + error_msg = _("First occurrence."); + warnbox(error_msg); + goto cleanup; + } + item->order = prev; + ui_calendar_set_slctd_day(sec2date(prev)); + day_set_sel_data(item); + day_do_storage(1); valid = 1; } @@ -670,8 +696,6 @@ cleanup: */ int main(int argc, char **argv) { - int no_data_file = 1; - #if ENABLE_NLS setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); @@ -686,7 +710,7 @@ int main(int argc, char **argv) /* Non-interactive mode. */ exit_calcurse(EXIT_SUCCESS); } else { - no_data_file = io_check_data_files(); + io_check_data_files(); dmon_stop(); io_set_lock(); } @@ -756,12 +780,8 @@ int main(int argc, char **argv) * implicitly calling wrefresh() later (causing ncurses race conditions). */ wins_wrefresh(win[KEY].p); - if (show_dialogs()) { - wins_update(FLAG_ALL); - io_startup_screen(no_data_file); - } ui_calendar_monthly_view_cache_set_invalid(); - do_storage(1); + day_do_storage(1); ui_todo_load_items(); ui_todo_sel_reset(); wins_update(FLAG_ALL); @@ -781,7 +801,7 @@ int main(int argc, char **argv) que_show(); if (conf.systemevents) { que_save(); - do_storage(0); + day_do_storage(0); } wins_update(FLAG_ALL); que_rem(); @@ -791,7 +811,7 @@ int main(int argc, char **argv) resize = 0; wins_reset(); if (conf.multiple_days) { - do_storage(0); + day_do_storage(0); wins_update(FLAG_APP); } } @@ -807,6 +827,7 @@ int main(int argc, char **argv) wtimeout(win[KEY].p, -1); switch (key) { HANDLE_KEY(KEY_GENERIC_CHANGE_VIEW, key_generic_change_view); + HANDLE_KEY(KEY_GENERIC_PREV_VIEW, key_generic_prev_view); HANDLE_KEY(KEY_GENERIC_OTHER_CMD, key_generic_other_cmd); HANDLE_KEY(KEY_GENERIC_GOTO, key_generic_goto); HANDLE_KEY(KEY_GENERIC_GOTO_TODAY, key_generic_goto_today); diff --git a/src/calcurse.h b/src/calcurse.h index 0c959a9..8322416 100644 --- a/src/calcurse.h +++ b/src/calcurse.h @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -111,9 +111,6 @@ #define STATUSHEIGHT 2 #define MAX_NOTESIZ 40 -/* Format for appointment hours is: HH:MM */ -#define HRMIN_SIZE 6 - /* Maximum number of colors available. */ #define NBUSERCOLORS 6 @@ -142,9 +139,11 @@ */ #define DAYINSEC (DAYINMIN * MININSEC) #define NEXTDAY(date) date_sec_change((date), 0, 1) +#define PREVDAY(date) date_sec_change((date), 0, -1) #define DAYLEN(date) (NEXTDAY(date) - (date)) #define ENDOFDAY(date) (NEXTDAY(date) - 1) #define HOURINSEC (HOURINMIN * MININSEC) +#define DAY(date) (update_time_in_date(date, 0, 0)) /* Calendar window. */ #define CALHEIGHT 8 @@ -153,7 +152,7 @@ * The argument (d) is the "Sunday"-numbering of member tm_wday in struct tm. */ #define WDAY(d) \ - (ui_calendar_week_begins_on_monday() ? ((d ? d : WEEKINDAYS) - 1) : d) + (modify_wday(d, -ui_calendar_get_wday_start())) /* Key definitions. */ #define CTRLVAL 0x1F @@ -291,7 +290,6 @@ struct conf { unsigned confirm_delete; enum win default_panel; unsigned compact_panels; - unsigned system_dialogs; unsigned multiple_days; unsigned header_line; unsigned event_separator; @@ -305,9 +303,11 @@ struct conf { int input_datefmt; /* format for reading date */ enum pos heading_pos; /* left/center/right for heading in appts panel */ char day_heading[BUFSIZ]; /* format for displaying heading in appts panel */ + char timefmt[BUFSIZ]; /* format for displaying time in appts panel*/ }; #define EMPTY_DAY_DEFAULT "--" +#define EMPTY_EVENT_DESC_DEFAULT _("(empty description)") /* Daemon-related configuration. */ struct dmon_conf { @@ -337,6 +337,9 @@ enum datefmt { /* Day heading default format. */ #define DAY_HEADING_DEFAULT "%B %e, %Y" +/* Appointment time default format. */ +#define APPT_TIME_DEFAULT "%H:%M" + /* * Calcurse representation of the date of a day in the calendar. * When time_t is a 32-bit signed integer, the year range is 1902 - 2037. @@ -511,11 +514,11 @@ struct notify_app { struct io_file { FILE *fd; - char name[BUFSIZ]; + char *name; }; -/* Available keys. */ -enum key { +/* Virtual keys. */ +enum vkey { KEY_GENERIC_CANCEL, KEY_GENERIC_SELECT, KEY_GENERIC_CREDITS, @@ -526,6 +529,7 @@ enum key { KEY_GENERIC_COPY, KEY_GENERIC_PASTE, KEY_GENERIC_CHANGE_VIEW, + KEY_GENERIC_PREV_VIEW, KEY_GENERIC_IMPORT, KEY_GENERIC_EXPORT, KEY_GENERIC_GOTO, @@ -565,7 +569,7 @@ enum key { KEY_RAISE_PRIORITY, KEY_LOWER_PRIORITY, - NBKEYS, + NBVKEYS, KEY_UNDEF, /* Non-configurable, context sensitive key bindings. */ @@ -670,7 +674,6 @@ struct nbar { char datefmt[BUFSIZ]; /* format for displaying date */ char timefmt[BUFSIZ]; /* format for displaying time */ char cmd[BUFSIZ]; /* notification command */ - const char *shell; /* user shell to launch notif. cmd */ unsigned notify_all; /* notify all appointments */ pthread_mutex_t mutex; }; @@ -809,7 +812,7 @@ void ui_calendar_set_current_date(void); struct date *ui_calendar_get_today(void); void ui_calendar_set_first_day_of_week(enum wday); void ui_calendar_change_first_day_of_week(void); -unsigned ui_calendar_week_begins_on_monday(void); +int ui_calendar_get_wday_start(void); void ui_calendar_store_current_date(struct date *); void ui_calendar_init_slctd_day(void); struct date *ui_calendar_get_slctd_day(void); @@ -855,6 +858,7 @@ void day_display_item_date(struct day_item *, WINDOW *, int, time_t, int, int); void day_display_item(struct day_item *, WINDOW *, int, int, int, int); void day_write_stdout(time_t, const char *, const char *, const char *, const char *, int *); +void day_do_storage(int day_changed); void day_popup_item(struct day_item *); int day_check_if_item(struct date); unsigned day_chk_busy_slices(struct date, int, int *); @@ -924,9 +928,8 @@ unsigned io_dir_exists(const char *); unsigned io_file_exists(const char *); int io_check_file(const char *); int io_check_data_files(void); -void io_startup_screen(int); void io_export_data(enum export_type, int); -void io_import_data(enum import_type, char *, const char *, const char *, +int io_import_data(enum import_type, char *, const char *, const char *, const char *, const char *, const char *); struct io_file *io_log_init(void); void io_log_print(struct io_file *, int, const char *); @@ -938,7 +941,6 @@ void io_set_lock(void); unsigned io_dump_pid(char *); unsigned io_get_pid(char *); int io_files_equal(const char *, const char *); -int io_file_is_empty(char *); int io_file_cp(const char *, const char *); void io_unset_modified(void); void io_set_modified(void); @@ -948,24 +950,26 @@ int io_get_modified(void); void keys_init(void); void keys_free(void); void keys_dump_defaults(char *); -const char *keys_get_label(enum key); -enum key keys_get_action(int); +const char *keys_get_label(enum vkey); +const char *keys_get_binding(enum vkey); +enum vkey keys_get_action(int); int keys_wgetch(WINDOW *); void keys_wait_for_any_key(WINDOW *); -enum key keys_get(WINDOW *, int *, int *); -int keys_assign_binding(int, enum key); -void keys_remove_binding(int, enum key); +enum vkey keys_get(WINDOW * win, int *, int *); +int keys_assign_binding(int, enum vkey); +void keys_remove_binding(int, enum vkey); int keys_str2int(const char *); char *keys_int2str(int); -int keys_action_count_keys(enum key); -const char *keys_action_firstkey(enum key); -const char *keys_action_nkey(enum key, int); -char *keys_action_allkeys(enum key); +int keys_action_count_keys(enum vkey); +const char *keys_action_firstkey(enum vkey); +const char *keys_action_nkey(enum vkey, int); +char *keys_action_allkeys(enum vkey); void keys_display_bindings_bar(WINDOW *, int *, int, int, int); -void keys_popup_info(enum key); +void keys_popup_info(enum vkey); void keys_save_bindings(FILE *); -int keys_check_missing_bindings(void); -void keys_fill_missing(void); +int keys_check_missing(void); +int keys_check_undefined(void); +int keys_fill_missing(void); /* listbox.c */ void listbox_init(struct listbox *, int, int, int, int, const char *, @@ -1021,6 +1025,7 @@ void edit_note(char **, const char *); void view_note(const char *, const char *); void erase_note(char **); void note_read(char *, FILE *); +void note_read_contents(char *, size_t, FILE *); void note_gc(void); /* notify.c */ @@ -1106,6 +1111,8 @@ void recur_apoint_switch_notify(struct recur_apoint *); void recur_event_paste_item(struct recur_event *, time_t); void recur_apoint_paste_item(struct recur_apoint *, time_t); int recur_next_occurrence(time_t, long, struct rpt *, llist_t *, time_t, time_t *); +int recur_nth_occurrence(time_t, long, struct rpt *, llist_t *, int, time_t *); +int recur_prev_occurrence(time_t, long, struct rpt *, llist_t *, time_t, time_t *); /* sigs.c */ @@ -1219,7 +1226,7 @@ int get_item_min(time_t); struct tm date2tm(struct date, unsigned, unsigned); time_t date2sec(struct date, unsigned, unsigned); struct date sec2date(time_t); -time_t utcdate2sec(struct date, unsigned, unsigned); +time_t tzdate2sec(struct date, unsigned, unsigned, char *); int date_cmp(struct date *, struct date *); int date_cmp_day(time_t, time_t); char *date_sec2date_str(time_t, const char *); @@ -1229,6 +1236,8 @@ time_t date_sec_change(time_t, int, int); time_t update_time_in_date(time_t, unsigned, unsigned); time_t get_sec_date(struct date); long min2sec(unsigned); +int modify_wday(int,int); +char *get_wday_default_string(int); void draw_scrollbar(struct scrollwin *, int); void item_in_popup(const char *, const char *, const char *, const char *); time_t get_today(void); @@ -1245,13 +1254,13 @@ int check_sec(time_t *); int check_time(unsigned, unsigned); int parse_time(const char *, unsigned *, unsigned *); int parse_duration(const char *, unsigned *, time_t); -int parse_date_duration(const char *, unsigned *, time_t); +int parse_date_increment(const char *, unsigned *, time_t); int parse_datetime(const char *, time_t *, time_t); void file_close(FILE *, const char *); void psleep(unsigned); -int fork_exec(int *, int *, const char *, const char *const *); -int shell_exec(int *, int *, const char *, const char *const *); -int child_wait(int *, int *, int); +int fork_exec(int *, int *, int *, int, const char *, const char *const *); +int shell_exec(int *, int *, int *, int, const char *, const char *const *); +int child_wait(int *, int *, int *, int); void press_any_key(void); void print_apoint(const char *, time_t, struct apoint *); void print_event(const char *, time_t, struct event *); @@ -1263,12 +1272,12 @@ int asprintf(char **, const char *, ...); int starts_with(const char *, const char *); int starts_with_ci(const char *, const char *); int hash_matches(const char *, const char *); -int show_dialogs(void); long overflow_add(long, long, long *); long overflow_mul(long, long, long *); time_t next_wday(time_t, int); int wday_per_year(int, int); int wday_per_month(int, int, int); +char *day_ins(char **, time_t); /* vars.c */ extern int col, row; @@ -1324,6 +1333,7 @@ void wins_sbar_wdec(void); enum win wins_slctd(void); void wins_slctd_set(enum win); void wins_slctd_next(void); +void wins_slctd_prev(void); void wins_init(void); void wins_scrollwin_init(struct scrollwin *, int, int, int, int, const char *); void wins_scrollwin_resize(struct scrollwin *, int, int, int, int); diff --git a/src/config.c b/src/config.c index 27324e1..4e0f7db 100644 --- a/src/config.c +++ b/src/config.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -36,6 +36,8 @@ #include <ctype.h> #include <unistd.h> +#include <string.h> +#include <strings.h> #include "calcurse.h" @@ -109,6 +111,7 @@ static const struct confvar confmap[] = { {"format.inputdate", config_parse_input_datefmt, config_serialize_input_datefmt, NULL}, {"format.notifydate", CONFIG_HANDLER_STR(nbar.datefmt)}, {"format.notifytime", CONFIG_HANDLER_STR(nbar.timefmt)}, + {"format.appointmenttime", CONFIG_HANDLER_STR(conf.timefmt)}, {"format.outputdate", config_parse_output_datefmt, config_serialize_output_datefmt, NULL}, {"format.dayheading", CONFIG_HANDLER_STR(conf.day_heading)}, {"general.autogc", CONFIG_HANDLER_BOOL(conf.auto_gc)}, @@ -119,7 +122,6 @@ static const struct confvar confmap[] = { {"general.multipledays", CONFIG_HANDLER_BOOL(conf.multiple_days)}, {"general.periodicsave", CONFIG_HANDLER_UNSIGNED(conf.periodic_save)}, {"general.systemevents", CONFIG_HANDLER_BOOL(conf.systemevents)}, - {"general.systemdialogs", CONFIG_HANDLER_BOOL(conf.system_dialogs)}, {"notification.command", CONFIG_HANDLER_STR(nbar.cmd)}, {"notification.notifyall", config_parse_notifyall, config_serialize_notifyall, NULL}, {"notification.warning", CONFIG_HANDLER_INT(nbar.cntdwn)} @@ -261,14 +263,16 @@ static int config_parse_default_panel(void *dummy, const char *val) static int config_parse_first_day_of_week(void *dummy, const char *val) { - if (!strcmp(val, "monday")) - ui_calendar_set_first_day_of_week(MONDAY); - else if (!strcmp(val, "sunday")) - ui_calendar_set_first_day_of_week(SUNDAY); - else - return 0; + int i; - return 1; + for (i = 0; i < WEEKINDAYS; i++) { + if(!strcasecmp(val, get_wday_default_string(i))) { + ui_calendar_set_first_day_of_week(i); + return 1; + } + } + + return 0; } static int config_parse_color_theme(void *dummy, const char *val) @@ -468,10 +472,9 @@ static int config_serialize_default_panel(char **buf, void *dummy) static int config_serialize_first_day_of_week(char **buf, void *dummy) { - if (ui_calendar_week_begins_on_monday()) - *buf = mem_strdup("monday"); - else - *buf = mem_strdup("sunday"); + *buf = mem_strdup(get_wday_default_string(ui_calendar_get_wday_start())); + /* now stores string with uppercase first letter, changing to lower */ + **buf = tolower(**buf); return 1; } @@ -615,7 +618,8 @@ config_file_walk(config_fn_walk_cb_t fn_cb, * Backwards compatibility for removed configuration options: * ignored on load, omitted on save. */ - if (strcmp(key, "general.progressbar") == 0) + if (strcmp(key, "general.progressbar") == 0 || + strcmp(key, "general.systemdialogs") == 0) continue; if (value && (*value == '\0' || *value == '\n')) { diff --git a/src/custom.c b/src/custom.c index 575960a..2cd385c 100644 --- a/src/custom.c +++ b/src/custom.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -38,6 +38,7 @@ #include <stdlib.h> #include <math.h> #include <ctype.h> +#include <langinfo.h> #include "calcurse.h" @@ -540,12 +541,12 @@ enum { SYSTEM_EVENTS, CONFIRM_QUIT, CONFIRM_DELETE, - SYSTEM_DIAGS, FIRST_DAY_OF_WEEK, OUTPUT_DATE_FMT, INPUT_DATE_FMT, HEADING_POS, DAY_HEADING_FMT, + APPOINTMENT_TIME_FMT, NB_OPTIONS }; @@ -570,12 +571,12 @@ static void print_general_option(int i, WINDOW *win, int y, int hilt, void *cb_d "general.systemevents = ", "general.confirmquit = ", "general.confirmdelete = ", - "general.systemdialogs = ", "general.firstdayofweek = ", "format.outputdate = ", "format.inputdate = ", "appearance.headingposition = ", - "format.dayheading = " + "format.dayheading = ", + "format.appointmenttime = " }; const char *panel; const char *position; @@ -699,18 +700,10 @@ static void print_general_option(int i, WINDOW *win, int y, int hilt, void *cb_d _("(if set to YES, confirmation is required " "before deleting an event)")); break; - case SYSTEM_DIAGS: - print_bool_option_incolor(win, conf.system_dialogs, y, - XPOS + strlen(opt[SYSTEM_DIAGS])); - mvwaddstr(win, y + 1, XPOS, - _("(if set to YES, messages about loaded " - "and saved data will be displayed)")); - break; case FIRST_DAY_OF_WEEK: custom_apply_attr(win, ATTR_HIGHEST); mvwaddstr(win, y, XPOS + strlen(opt[FIRST_DAY_OF_WEEK]), - ui_calendar_week_begins_on_monday()? _("Monday") : - _("Sunday")); + nl_langinfo(DAY_1 + ui_calendar_get_wday_start())); custom_remove_attr(win, ATTR_HIGHEST); mvwaddstr(win, y + 1, XPOS, _("(specifies the first day of week in the calendar view)")); @@ -755,6 +748,14 @@ static void print_general_option(int i, WINDOW *win, int y, int hilt, void *cb_d mvwaddstr(win, y + 1, XPOS, _("(Format of the date displayed in the appointments panel)")); break; + case APPOINTMENT_TIME_FMT: + custom_apply_attr(win, ATTR_HIGHEST); + mvwaddstr(win, y, XPOS + strlen(opt[APPOINTMENT_TIME_FMT]), + conf.timefmt); + custom_remove_attr(win, ATTR_HIGHEST); + mvwaddstr(win, y + 1, XPOS, + _("(Format of the time displayed in the appointments panel)")); + break; } if (hilt) @@ -780,6 +781,8 @@ static void general_option_edit(int i) _("Enter a text string (an empty string for the default text)"); const char *output_datefmt_str = _("Enter the date format (see 'man 3 strftime' for possible formats) "); + const char *output_timefmt_str = + _("Enter the time format (see 'man 3 strftime' for possible formats) "); const char *input_datefmt_prefix = _("Enter the date format: "); const char *periodic_save_str = _("Enter the delay, in minutes, between automatic saves (0 to disable) "); @@ -873,9 +876,6 @@ static void general_option_edit(int i) case CONFIRM_DELETE: conf.confirm_delete = !conf.confirm_delete; break; - case SYSTEM_DIAGS: - conf.system_dialogs = !conf.system_dialogs; - break; case FIRST_DAY_OF_WEEK: ui_calendar_change_first_day_of_week(); ui_calendar_monthly_view_cache_set_invalid(); @@ -905,6 +905,15 @@ static void general_option_edit(int i) conf.day_heading[BUFSIZ - 1] = '\0'; } break; + case APPOINTMENT_TIME_FMT: + status_mesg(output_timefmt_str, ""); + strncpy(buf, conf.timefmt, BUFSIZ); + buf[BUFSIZ - 1] = '\0'; + if (updatestring(win[STA].p, &buf, 0, 1) == 0) { + strncpy(conf.timefmt, buf, BUFSIZ); + conf.timefmt[BUFSIZ - 1] = '\0'; + } + break; } mem_free(buf); @@ -987,10 +996,11 @@ print_keys_bindings(WINDOW * win, int selected_row, int selected_elm, const int XPOS = 1; const int EQUALPOS = 23; const int KEYPOS = 25; - int noelm, action, y; + int noelm, action, y, pos; + const char *key = NULL; noelm = y = 0; - for (action = 0; action < NBKEYS; action++) { + for (action = 0; action < NBVKEYS; action++) { char *actionstr; int nbkeys; @@ -1002,18 +1012,15 @@ print_keys_bindings(WINDOW * win, int selected_row, int selected_elm, mem_free(actionstr); mvwaddstr(win, y, EQUALPOS, "="); if (nbkeys == 0) - mvwaddstr(win, y, KEYPOS, _("undefined")); + mvwaddstr(win, y, KEYPOS, _("UNDEFINED")); if (action == selected_row) custom_remove_attr(win, ATTR_HIGHEST); if (nbkeys > 0) { if (action == selected_row) { - const char *key; - int pos; - + /* Elements may have been added or deleted. */ + wclrtoeol(win); pos = KEYPOS; - while ((key = - keys_action_nkey(action, - noelm)) != NULL) { + while ((key = keys_action_nkey(action, noelm))) { if (noelm == selected_elm) print_key_incolor(win, key, y, pos); @@ -1024,8 +1031,9 @@ print_keys_bindings(WINDOW * win, int selected_row, int selected_elm, pos += utf8_strwidth((char *)key) + 1; } } else { - mvwaddstr(win, y, KEYPOS, - keys_action_allkeys(action)); + key = keys_action_allkeys(action); + mvwaddstr(win, y, KEYPOS, key); + mem_free((char *)key); } } y += yoff; @@ -1057,9 +1065,11 @@ void custom_keys_config(void) const int LABELLINES = 3; clear(); - nbdisplayed = ((notify_bar() ? row - 3 : row - 2) - LABELLINES) / LINESPERKEY; - wins_scrollwin_init(&kwin, 0, 0, notify_bar() ? row - 3 : row - 2, col, _("keys configuration")); - wins_scrollwin_set_pad(&kwin, NBKEYS * LINESPERKEY); + nbdisplayed = ((notify_bar() ? row - 3 : row - 2) - + LABELLINES) / LINESPERKEY; + wins_scrollwin_init(&kwin, 0, 0, notify_bar() ? row - 3 : row - 2, col, + _("keys configuration")); + wins_scrollwin_set_pad(&kwin, NBVKEYS * LINESPERKEY); wins_scrollwin_draw_deco(&kwin, 0); custom_keys_config_bar(); selrow = selelm = 0; @@ -1084,7 +1094,7 @@ void custom_keys_config(void) } break; case KEY_MOVE_DOWN: - if (selrow < NBKEYS - 1) { + if (selrow < NBVKEYS - 1) { selrow++; selelm = 0; if (selrow == lastrow) { @@ -1115,7 +1125,7 @@ void custom_keys_config(void) keys_get_label(selrow), 0); for (;;) { ch = keys_wgetch(grabwin); - enum key action = keys_get_action(ch); + enum vkey action = keys_get_action(ch); /* Is the key already used by this action? */ if (action == selrow) break; @@ -1154,10 +1164,8 @@ void custom_keys_config(void) selelm--; break; case KEY_GENERIC_QUIT: - if (keys_check_missing_bindings() != 0) { - WARN_MSG(_("Some actions do not have any associated " - "key bindings!")); - } + if (keys_check_undefined()) + WARN_MSG(_("Some actions are left undefined!")); wins_scrollwin_delete(&kwin); return; } @@ -1213,7 +1221,10 @@ void custom_config_main(void) wmove(win[STA].p, 0, 0); wins_doupdate(); - while ((ch = keys_wgetch(win[KEY].p)) != 'q') { + while (1) { + ch = keys_wgetch(win[KEY].p); + if (keys_get_action(ch) == KEY_GENERIC_QUIT) + break; switch (ch) { case 'C': case 'c': @@ -1231,7 +1242,7 @@ void custom_config_main(void) old_layout = wins_layout(); custom_layout_config(); if (wins_layout() != old_layout) - wins_reset(); + wins_resize(); break; case 'G': case 'g': @@ -1257,7 +1268,11 @@ void custom_config_main(void) resize = 0; wins_reset(); } - + + /* needed to update app list */ + day_do_storage(0); + + /* wins_update(FLAG_ALL), but with custom bindings */ wins_set_bindings(bindings, ARRAY_SIZE(bindings)); wins_update_border(FLAG_ALL); wins_update_panels(FLAG_ALL); @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -58,7 +58,7 @@ static struct day_item sel_data = { 0, 0, 0, {NULL}}; /* * Save the item to become the selected APP item. - * Public function used to override the setting in do_storage(). + * Public function used to override the setting in day_do_storage(). */ int day_set_sel_data(struct day_item *d) { @@ -100,7 +100,7 @@ int day_sel_index(void) /* If still not found, stay on the same day. */ VECTOR_FOREACH(&day_items, i) { p = VECTOR_NTH(&day_items, i); - if (p->order == update_time_in_date(sel_data.order, 0, 0)) + if (p->order == DAY(sel_data.order)) return i; } return -1; @@ -198,7 +198,8 @@ static void day_add_item(int type, time_t start, time_t order, union aptev_ptr i /* Get the message of an item. */ char *day_item_get_mesg(struct day_item *day) { - switch (day->type) { + switch (day->type) + { case APPT: return day->item.apt->mesg; case EVNT: @@ -212,6 +213,15 @@ char *day_item_get_mesg(struct day_item *day) } } +/* Get the display message of an item. */ +char *day_item_get_display_mesg(struct day_item *day) +{ + char *msg = day_item_get_mesg(day); + if (msg[0] == '\0') + return EMPTY_EVENT_DESC_DEFAULT; + return msg; +} + /* Get the note attached to an item. */ char *day_item_get_note(struct day_item *day) { @@ -529,7 +539,7 @@ day_display_item(struct day_item *day, WINDOW *win, int incolor, int width, if (width <= 0) return; - char *mesg = day_item_get_mesg(day); + char *mesg = day_item_get_display_mesg(day); ch_recur = (day->type == RECUR_EVNT) ? '*' : ' '; ch_note = day_item_get_note(day) ? '>' : ' '; @@ -578,12 +588,66 @@ void day_write_stdout(time_t date, const char *fmt_apt, const char *fmt_rapt, } } +/* + * Store events and appointments for a range of days in the day vector - + * beginning with the selected day - and load them into the APP listbox. If no + * day-change occurs, reset the selected APP item and with it the selected day, + * thereby storing and loading the same range of days. + */ +void day_do_storage(int day_changed) +{ + int pre_sel; + /* + * Save the selected item before rebuilding the day vector - + * unless a preselection is already set. + */ + if(!(pre_sel = day_check_sel_data())) + day_set_sel_data(ui_day_get_sel()); + + if (!day_changed) + ui_day_sel_reset(); + + /* The day_items vector. */ + day_store_items(get_slctd_day(), 1, day_get_days()); + /* The APP listbox. */ + ui_day_load_items(); + + if (day_changed && !pre_sel) + ui_day_sel_reset(); + else + ui_day_find_sel(); + + day_set_sel_data(&empty_day); +} + /* Display an item inside a popup window. */ void day_popup_item(struct day_item *day) { + const char *note_heading = _("Note:"); + size_t note_size = 3500; + if (day->type == EVNT || day->type == RECUR_EVNT) { - item_in_popup(NULL, NULL, day_item_get_mesg(day), - _("Event:")); + if (day_item_get_note(day)) { + char note[note_size]; + char *notepath, *msg; + FILE *fp; + + asprintf(¬epath, "%s%s", path_notes, day_item_get_note(day)); + fp = fopen(notepath, "r"); + if (fp == NULL) { + item_in_popup(NULL, NULL, day_item_get_mesg(day), _("Event:")); + return; + } + note_read_contents(note, note_size, fp); + fclose(fp); + mem_free(notepath); + + asprintf(&msg, "%s\n\n%s\n%s", day_item_get_display_mesg(day), note_heading, note); + item_in_popup(NULL, NULL, msg, _("Event:")); + mem_free(msg); + } else { + item_in_popup(NULL, NULL, day_item_get_display_mesg(day), _("Event:")); + } } else if (day->type == APPT || day->type == RECUR_APPT) { char a_st[100], a_end[100]; @@ -593,8 +657,28 @@ void day_popup_item(struct day_item *day) apt_tmp.start = day->start; apt_tmp.dur = day_item_get_duration(day); apoint_sec2str(&apt_tmp, ui_day_sel_date(), a_st, a_end); - item_in_popup(a_st, a_end, day_item_get_mesg(day), - _("Appointment:")); + + if (day_item_get_note(day)) { + char note[note_size]; + char *notepath, *msg; + FILE *fp; + + asprintf(¬epath, "%s%s", path_notes, day_item_get_note(day)); + fp = fopen(notepath, "r"); + if (fp == NULL) { + item_in_popup(a_st, a_end, day_item_get_mesg(day), _("Appointment:")); + return; + } + note_read_contents(note, note_size, fp); + fclose(fp); + mem_free(notepath); + + asprintf(&msg, "%s\n\n%s\n%s", day_item_get_display_mesg(day), note_heading, note); + item_in_popup(a_st, a_end, msg, _("Appointment:")); + mem_free(msg); + } else { + item_in_popup(a_st, a_end, day_item_get_display_mesg(day), _("Appointment:")); + } } else { EXIT(_("unknown item type")); /* NOTREACHED */ @@ -798,7 +882,7 @@ int day_paste_item(struct day_item *p, time_t date) /* wanted: until = shift + old_until */ if (p->item.rapt->rpt->until && overflow_add( - date - update_time_in_date(p->item.rapt->start, 0, 0), + date - DAY(p->item.rapt->start), p->item.rapt->rpt->until, &until) ) @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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,7 +34,7 @@ * */ -#include <sys/types.h> +#include <sys/wait.h> #include <sys/stat.h> #include <unistd.h> #include <paths.h> @@ -143,8 +143,7 @@ static unsigned daemonize(int status) || !sigs_set_hdlr(SIGTERM, dmon_sigs_hdlr) || !sigs_set_hdlr(SIGALRM, dmon_sigs_hdlr) || !sigs_set_hdlr(SIGQUIT, dmon_sigs_hdlr) - || !sigs_set_hdlr(SIGUSR1, dmon_sigs_hdlr) - || !sigs_set_hdlr(SIGCHLD, SIG_IGN)) + || !sigs_set_hdlr(SIGUSR1, dmon_sigs_hdlr)) return 0; return 1; @@ -203,6 +202,9 @@ void dmon_start(int parent_exit_status) DMON_SLEEP_TIME); psleep(DMON_SLEEP_TIME); DMON_LOG(_("awakened at %s\n"), nowstr()); + /* Reap the user-defined notifications. */ + while (waitpid(0, NULL, WNOHANG) > 0) + ; } } diff --git a/src/event.c b/src/event.c index e0da4c1..7c371e4 100644 --- a/src/event.c +++ b/src/event.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 diff --git a/src/getstring.c b/src/getstring.c index 7cdb84e..8ea5df5 100644 --- a/src/getstring.c +++ b/src/getstring.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -241,9 +241,11 @@ enum getstr getstring(WINDOW * win, char *str, int l, int x, int y) st.len = st.pos; break; case CTRL('A'): /* go to beginning of string */ + case KEY_HOME: st.pos = 0; break; case CTRL('E'): /* go to end of string */ + case KEY_END: st.pos = st.len; break; case KEY_LEFT: /* move one char backward */ @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -105,7 +105,7 @@ int display_help(const char *topic) if (!io_file_exists(path)) { int ch = keys_str2int(topic); - enum key action = keys_get_action(ch); + enum vkey action = keys_get_action(ch); if (ch > 0 && action > 0 && action != KEY_UNDEF) { topic = keys_get_label(action); mem_free(path); @@ -134,6 +134,8 @@ int display_help(const char *topic) topic = "copy-paste"; else if (!strcmp(topic, "generic-change-view")) topic = "tab"; + else if (!strcmp(topic, "generic-prev-view")) + topic = "tab"; else if (!strcmp(topic, "generic-import")) topic = "import"; else if (!strcmp(topic, "generic-export")) diff --git a/src/hooks.c b/src/hooks.c index 20aed69..f649076 100644 --- a/src/hooks.c +++ b/src/hooks.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -35,35 +35,38 @@ */ #include <stddef.h> +#include <sys/wait.h> #include "calcurse.h" int run_hook(const char *name) { - char *hook_path = NULL; + char *hook_path = NULL, *mesg; + int pid, pin, pout, perr, ret = -127; char const *arg[2]; - int pid, ret = -127; - int prepare_wins = (ui_mode == UI_CURSES); asprintf(&hook_path, "%s/%s", path_hooks, name); - arg[0] = hook_path; - arg[1] = NULL; - if (!io_file_exists(hook_path)) goto cleanup; + arg[0] = hook_path; + arg[1] = NULL; - if (prepare_wins) - wins_prepare_external(); - - if ((pid = shell_exec(NULL, NULL, *arg, arg))) { - ret = child_wait(NULL, NULL, pid); - if (ret) - press_any_key(); + if ((pid = shell_exec(&pin, &pout, &perr, 1, *arg, arg))) { + ret = child_wait(&pin, &pout, &perr, pid); + if (ret > 0 && WIFEXITED(ret)) { + asprintf(&mesg, "%s hook: exit status %d", + name, + WEXITSTATUS(ret)); + que_ins(mesg, now(), 3); + mem_free(mesg); + } else if (ret != 0) { + asprintf(&mesg, "%s hook: abnormal termination", + name); + que_ins(mesg, now(), 4); + mem_free(mesg); + } } - if (prepare_wins) - wins_unprepare_external(); - cleanup: mem_free(hook_path); return ret; diff --git a/src/htable.h b/src/htable.h index 5266213..92be6e2 100644 --- a/src/htable.h +++ b/src/htable.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2020 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 @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -36,11 +36,14 @@ #include <strings.h> #include <sys/types.h> +#include <ctype.h> #include "calcurse.h" #define ICALDATEFMT "%Y%m%d" #define ICALDATETIMEFMT "%Y%m%dT%H%M%S" +#define SEPARATOR "-- \n" +#define INDENT " " typedef enum { ICAL_VEVENT, @@ -59,17 +62,9 @@ typedef enum { SUMMARY, DESCRIPTION, LOCATION, - COMMENT, - STATUS + COMMENT } ical_property_e; -typedef struct { - enum recur_type type; - int freq; - long until; - unsigned count; -} ical_rpt_t; - static void ical_export_header(FILE *); static void ical_export_recur_events(FILE *, int); static void ical_export_events(FILE *, int); @@ -81,22 +76,29 @@ static void ical_export_footer(FILE *); static const char *ical_recur_type[NBRECUR] = { "DAILY", "WEEKLY", "MONTHLY", "YEARLY" }; -/* Escape characters in field before printing */ -static void ical_format_line(FILE * stream, char * field, char * msg) +static const char *ical_wday[] = + {"SU", "MO", "TU", "WE", "TH", "FR", "SA"}; + +/* + * Encode a string as a property value of type TEXT (RFC 5545, 3.3.11). + */ +static void ical_format_line(FILE *stream, char *property, char *msg) { char * p; - fputs(field, stream); + fputs(property, stream); for (p = msg; *p; p++) { switch (*p) { + case '\n': + fprintf(stream, "\\%c", 'n'); + break; case ',': case ';': case '\\': fprintf(stream, "\\%c", *p); break; default: - fputc(*p, stream); - break; + fputc(*p, stream); } } fputc('\n', stream); @@ -113,12 +115,156 @@ static void ical_export_valarm(FILE * stream) fputs("END:VALARM\n", stream); } +static void ical_export_rrule(FILE *stream, struct rpt *rpt, ical_vevent_e item, + char *buf) +{ + llist_item_t *j; + int d; + char *fmt = item == EVENT ? ICALDATEFMT : + item == APPOINTMENT ? ICALDATETIMEFMT : + NULL; + + fprintf(stream, "RRULE:FREQ=%s", ical_recur_type[rpt->type]); + if (rpt->freq > 1) + fprintf(stream, ";INTERVAL=%d", rpt->freq); + if (rpt->until) { + date_sec2date_fmt(rpt->until, fmt, buf); + fprintf(stream, ";UNTIL=%s", buf); + } + if (LLIST_FIRST(&rpt->bymonth)) { + fputs(";BYMONTH=", stream); + LLIST_FOREACH(&rpt->bymonth, j) { + d = *(int *)LLIST_GET_DATA(j); + fprintf(stream, "%d", d); + if (LLIST_NEXT(j)) + fputc(',', stream); + } + } + if (LLIST_FIRST(&rpt->bywday)) { + int ord; + char sign; + + fputs(";BYDAY=", stream); + LLIST_FOREACH(&rpt->bywday, j) { + d = *(int *)LLIST_GET_DATA(j); + sign = d < 0 ? '-' : '+'; + d = abs(d); + ord = d / 7; + d = d % 7; + if (ord == 0) + fprintf(stream, "%s", ical_wday[d]); + else + fprintf(stream, "%c%d%s", sign, ord, ical_wday[d]); + if (LLIST_NEXT(j)) + fputc(',', stream); + } + } + if (LLIST_FIRST(&rpt->bymonthday)) { + fputs(";BYMONTHDAY=", stream); + LLIST_FOREACH(&rpt->bymonthday, j) { + d = *(int *)LLIST_GET_DATA(j); + fprintf(stream, "%d", d); + if (LLIST_NEXT(j)) + fputc(',', stream); + } + } + fputc('\n', stream); +} + +/* + * Copy the characters (lines) between "a" and "z" into an allocated string, + * un-indent it and return it. Note that the final character, a newline, is + * overwritten with '\0'. + */ +static char *ical_unindent(char *a, char *z) +{ + char *p, *q; int len; + + len = z - a + 1; + + p = mem_malloc(len); + strncpy(p, a, len); + p[len - 1] = '\0'; + while ((q = strstr(p, "\n" INDENT))) { + while (*(q + 1 + strlen(INDENT))) { + *(q + 1) = *(q + 1 + strlen(INDENT)); + q++; + } + *(q + 1) = '\0'; + } + return p; +} + +static void ical_export_note(FILE *stream, char *name) +{ + char *note_file, *p, *q, *r, *rest; + char *property[] = { + "Location: ", + "Comment: ", + NULL + }; + char *PROPERTY[] = { + "LOCATION:", + "COMMENT:" + }; + struct string note; + char lbuf[BUFSIZ]; + FILE *fp; + int has_desc, has_prop, i; + + asprintf(¬e_file, "%s/%s", path_notes, name); + if (!(fp = fopen(note_file, "r")) || ungetc(getc(fp), fp) == EOF) { + fclose(fp); + return; + } + string_init(¬e); + while (fgets(lbuf, BUFSIZ, fp)) + string_catf(¬e, "%s", lbuf); + fclose(fp); + + has_desc = has_prop = 0; + rest = note.buf; + if ((p = strstr(note.buf, SEPARATOR))) { + has_prop = 1; + rest = p + strlen(SEPARATOR); + if (p != note.buf) { + has_desc = 1; + *(--p) = '\0'; + } + } else { + has_desc = 1; + note.buf[strlen(note.buf) - 1] = '\0'; + } + + if (has_desc) + ical_format_line(stream, "DESCRIPTION:", note.buf); + + if (!has_prop) + goto cleanup; + for (i = 0; property[i]; i++) { + if ((p = strstr(rest, property[i]))) + p += strlen(property[i]); + else + continue; + /* Find end of property. */ + for (q = p; + (q = strchr(q, '\n')) && starts_with(q + 1, INDENT); + q++) ; + /* Extract property line(s). */ + r = ical_unindent(p, q); + ical_format_line(stream, PROPERTY[i], r); + mem_free(r); + } +cleanup: + mem_free(note.buf); +} + /* Export header. */ static void ical_export_header(FILE * stream) { fputs("BEGIN:VCALENDAR\n", stream); - fprintf(stream, "PRODID:-//calcurse//NONSGML v%s//EN\n", VERSION); fputs("VERSION:2.0\n", stream); + fprintf(stream, "PRODID:-//calcurse//NONSGML v%s//EN\n", VERSION); } /* Export footer. */ @@ -131,26 +277,21 @@ static void ical_export_footer(FILE * stream) static void ical_export_recur_events(FILE * stream, int export_uid) { llist_item_t *i, *j; - char ical_date[BUFSIZ]; + char ical_date[BUFSIZ], *hash; LLIST_FOREACH(&recur_elist, i) { struct recur_event *rev = LLIST_GET_DATA(i); - date_sec2date_fmt(rev->day, ICALDATEFMT, ical_date); fputs("BEGIN:VEVENT\n", stream); - fprintf(stream, "DTSTART;VALUE=DATE:%s\n", ical_date); - fprintf(stream, "RRULE:FREQ=%s;INTERVAL=%d", - ical_recur_type[rev->rpt->type], rev->rpt->freq); - - if (rev->rpt->until != 0) { - date_sec2date_fmt(rev->rpt->until, ICALDATEFMT, - ical_date); - fprintf(stream, ";UNTIL=%s\n", ical_date); - } else { - fputc('\n', stream); + if (export_uid) { + hash = recur_event_hash(rev); + fprintf(stream, "UID:%s\n", hash); + mem_free(hash); } - + date_sec2date_fmt(rev->day, ICALDATEFMT, ical_date); + fprintf(stream, "DTSTART;VALUE=DATE:%s\n", ical_date); + ical_export_rrule(stream, rev->rpt, EVENT, ical_date); if (LLIST_FIRST(&rev->exc)) { - fputs("EXDATE:", stream); + fputs("EXDATE;VALUE=DATE:", stream); LLIST_FOREACH(&rev->exc, j) { struct excp *exc = LLIST_GET_DATA(j); date_sec2date_fmt(exc->st, ICALDATETIMEFMT, @@ -159,15 +300,9 @@ static void ical_export_recur_events(FILE * stream, int export_uid) fputc(LLIST_NEXT(j) ? ',' : '\n', stream); } } - ical_format_line(stream, "SUMMARY:", rev->mesg); - - if (export_uid) { - char *hash = recur_event_hash(rev); - fprintf(stream, "UID:%s\n", hash); - mem_free(hash); - } - + if (rev->note) + ical_export_note(stream, rev->note); fputs("END:VEVENT\n", stream); } } @@ -176,21 +311,21 @@ static void ical_export_recur_events(FILE * stream, int export_uid) static void ical_export_events(FILE * stream, int export_uid) { llist_item_t *i; - char ical_date[BUFSIZ]; + char ical_date[BUFSIZ], *hash; LLIST_FOREACH(&eventlist, i) { struct event *ev = LLIST_TS_GET_DATA(i); - date_sec2date_fmt(ev->day, ICALDATEFMT, ical_date); fputs("BEGIN:VEVENT\n", stream); - fprintf(stream, "DTSTART;VALUE=DATE:%s\n", ical_date); - ical_format_line(stream, "SUMMARY:", ev->mesg); - if (export_uid) { - char *hash = event_hash(ev); + hash = event_hash(ev); fprintf(stream, "UID:%s\n", hash); mem_free(hash); } - + date_sec2date_fmt(ev->day, ICALDATEFMT, ical_date); + fprintf(stream, "DTSTART;VALUE=DATE:%s\n", ical_date); + ical_format_line(stream, "SUMMARY:", ev->mesg); + if (ev->note) + ical_export_note(stream, ev->note); fputs("END:VEVENT\n", stream); } } @@ -199,16 +334,30 @@ static void ical_export_events(FILE * stream, int export_uid) static void ical_export_recur_apoints(FILE * stream, int export_uid) { llist_item_t *i, *j; - char ical_datetime[BUFSIZ]; - char ical_date[BUFSIZ]; + char ical_datetime[BUFSIZ], *hash; + time_t tod; LLIST_TS_LOCK(&recur_alist_p); LLIST_TS_FOREACH(&recur_alist_p, i) { struct recur_apoint *rapt = LLIST_TS_GET_DATA(i); + /* + * Add time-of-day to UNTIL/EXDATE. + * In calcurse until/exception is a date (midnight), but in + * RFC 5545 UNTIL/EXDATE is a DATE-TIME value type by default. + */ + tod = get_item_time(rapt->start); + if (rapt->rpt->until) + rapt->rpt->until += tod; + date_sec2date_fmt(rapt->start, ICALDATETIMEFMT, ical_datetime); fputs("BEGIN:VEVENT\n", stream); + if (export_uid) { + hash = recur_apoint_hash(rapt); + fprintf(stream, "UID:%s\n", hash); + mem_free(hash); + } fprintf(stream, "DTSTART:%s\n", ical_datetime); if (rapt->dur > 0) { fprintf(stream, "DURATION:P%ldDT%ldH%ldM%ldS\n", @@ -217,38 +366,22 @@ static void ical_export_recur_apoints(FILE * stream, int export_uid) (rapt->dur / MININSEC) % HOURINMIN, rapt->dur % MININSEC); } - fprintf(stream, "RRULE:FREQ=%s;INTERVAL=%d", - ical_recur_type[rapt->rpt->type], rapt->rpt->freq); - - if (rapt->rpt->until != 0) { - date_sec2date_fmt(rapt->rpt->until + HOURINSEC, - ICALDATEFMT, ical_date); - fprintf(stream, ";UNTIL=%s\n", ical_date); - } else { - fputc('\n', stream); - } - + ical_export_rrule(stream, rapt->rpt, APPOINTMENT, ical_datetime); if (LLIST_FIRST(&rapt->exc)) { fputs("EXDATE:", stream); LLIST_FOREACH(&rapt->exc, j) { struct excp *exc = LLIST_GET_DATA(j); - date_sec2date_fmt(exc->st, ICALDATETIMEFMT, - ical_date); - fprintf(stream, "%s", ical_date); + date_sec2date_fmt(exc->st + tod, ICALDATETIMEFMT, + ical_datetime); + fprintf(stream, "%s", ical_datetime); fputc(LLIST_NEXT(j) ? ',' : '\n', stream); } } - ical_format_line(stream, "SUMMARY:", rapt->mesg); + if (rapt->note) + ical_export_note(stream, rapt->note); if (rapt->state & APOINT_NOTIFY) ical_export_valarm(stream); - - if (export_uid) { - char *hash = recur_apoint_hash(rapt); - fprintf(stream, "UID:%s\n", hash); - mem_free(hash); - } - fputs("END:VEVENT\n", stream); } LLIST_TS_UNLOCK(&recur_alist_p); @@ -258,14 +391,19 @@ static void ical_export_recur_apoints(FILE * stream, int export_uid) static void ical_export_apoints(FILE * stream, int export_uid) { llist_item_t *i; - char ical_datetime[BUFSIZ]; + char ical_datetime[BUFSIZ], *hash; LLIST_TS_LOCK(&alist_p); LLIST_TS_FOREACH(&alist_p, i) { struct apoint *apt = LLIST_TS_GET_DATA(i); + fputs("BEGIN:VEVENT\n", stream); + if (export_uid) { + hash = apoint_hash(apt); + fprintf(stream, "UID:%s\n", hash); + mem_free(hash); + } date_sec2date_fmt(apt->start, ICALDATETIMEFMT, ical_datetime); - fputs("BEGIN:VEVENT\n", stream); fprintf(stream, "DTSTART:%s\n", ical_datetime); if (apt->dur > 0) { fprintf(stream, "DURATION:P%ldDT%ldH%ldM%ldS\n", @@ -275,15 +413,10 @@ static void ical_export_apoints(FILE * stream, int export_uid) apt->dur % MININSEC); } ical_format_line(stream, "SUMMARY:", apt->mesg); + if (apt->note) + ical_export_note(stream, apt->note); if (apt->state & APOINT_NOTIFY) ical_export_valarm(stream); - - if (export_uid) { - char *hash = apoint_hash(apt); - fprintf(stream, "UID:%s\n", hash); - mem_free(hash); - } - fputs("END:VEVENT\n", stream); } LLIST_TS_UNLOCK(&alist_p); @@ -293,22 +426,23 @@ static void ical_export_apoints(FILE * stream, int export_uid) static void ical_export_todo(FILE * stream, int export_uid) { llist_item_t *i; + char *hash; LLIST_FOREACH(&todolist, i) { struct todo *todo = LLIST_TS_GET_DATA(i); fputs("BEGIN:VTODO\n", stream); - if (todo->completed) - fprintf(stream, "STATUS:COMPLETED\n"); - fprintf(stream, "PRIORITY:%d\n", todo->id); - ical_format_line(stream, "SUMMARY:", todo->mesg); - if (export_uid) { - char *hash = todo_hash(todo); + hash = todo_hash(todo); fprintf(stream, "UID:%s\n", hash); mem_free(hash); } - + fprintf(stream, "PRIORITY:%d\n", todo->id); + ical_format_line(stream, "SUMMARY:", todo->mesg); + if (todo->note) + ical_export_note(stream, todo->note); + if (todo->completed) + fprintf(stream, "STATUS:COMPLETED\n"); fputs("END:VTODO\n", stream); } } @@ -373,32 +507,37 @@ static void ical_store_todo(int priority, int completed, char *mesg, erase_note(¬e); } +/* + * Calcurse limitation: events are one-day (all-day), and all multi-day events + * are turned into one-day events; a note has been added by ical_read_event(). + */ static void -ical_store_event(char *mesg, char *note, long day, long end, - ical_rpt_t *irpt, llist_t *exc, const char *fmt_ev, +ical_store_event(char *mesg, char *note, time_t day, time_t end, + struct rpt *rpt, llist_t *exc, const char *fmt_ev, const char *fmt_rev) { const int EVENTID = 1; struct event *ev; struct recur_event *rev; - if (irpt) { - struct rpt rpt; - rpt.type = irpt->type; - rpt.freq = irpt->freq; - rpt.until = irpt->until; - LLIST_INIT(&rpt.bymonth); - LLIST_INIT(&rpt.bywday); - LLIST_INIT(&rpt.bymonthday); - rpt.exc = *exc; - rev = recur_event_new(mesg, note, day, EVENTID, &rpt); - mem_free(irpt); + if (!mesg) + mesg = mem_strdup(_("(empty)")); + EXIT_IF(!mesg, _("ical_store_event: out of memory")); + + /* + * Repeating event. The end day is ignored, and the event becomes + * one-day even if multi-day. + */ + if (rpt) { + rpt->exc = *exc; + rev = recur_event_new(mesg, note, day, EVENTID, rpt); if (fmt_rev) print_recur_event(fmt_rev, day, rev); goto cleanup; } - if (end == 0 || end - day <= DAYINSEC) { + /* Ordinary one-day event. */ + if (end - day <= DAYINSEC) { ev = event_new(mesg, note, day, EVENTID); if (fmt_ev) print_event(fmt_ev, day, ev); @@ -406,22 +545,19 @@ ical_store_event(char *mesg, char *note, long day, long end, } /* - * Here we have an event that spans over several days. - * - * In iCal, the end specifies when the event is supposed to end, in - * calcurse, the end specifies the time that the last occurrence of the - * event starts, so we need to do some conversion here. + * Ordinary multi-day event. The event is turned into a daily repeating + * event until the day before the end. In iCal, the end day is + * exclusive, the until day inclusive. */ - end = day + ((end - day - 1) / DAYINSEC) * DAYINSEC; - struct rpt rpt; - rpt.type = RECUR_DAILY; - rpt.freq = 1; - rpt.until = end; - LLIST_INIT(&rpt.bymonth); - LLIST_INIT(&rpt.bywday); - LLIST_INIT(&rpt.bymonthday); - rpt.exc = *exc; - rev = recur_event_new(mesg, note, day, EVENTID, &rpt); + struct rpt tmp; + tmp.type = RECUR_DAILY; + tmp.freq = 1; + tmp.until = day + ((end - day - 1) / DAYINSEC) * DAYINSEC; + LLIST_INIT(&tmp.bymonth); + LLIST_INIT(&tmp.bywday); + LLIST_INIT(&tmp.bymonthday); + tmp.exc = *exc; + rev = recur_event_new(mesg, note, day, EVENTID, &tmp); if (fmt_rev) print_recur_event(fmt_rev, day, rev); @@ -431,27 +567,41 @@ cleanup: } static void -ical_store_apoint(char *mesg, char *note, long start, long dur, - ical_rpt_t * irpt, llist_t * exc, int has_alarm, +ical_store_apoint(char *mesg, char *note, time_t start, long dur, + struct rpt *rpt, llist_t *exc, int has_alarm, const char *fmt_apt, const char *fmt_rapt) { char state = 0L; struct apoint *apt; struct recur_apoint *rapt; + time_t day; + + if (!mesg) + mesg = mem_strdup(_("(empty)")); + EXIT_IF(!mesg, _("ical_store_event: out of memory")); if (has_alarm) state |= APOINT_NOTIFY; - if (irpt) { - struct rpt rpt; - rpt.type = irpt->type; - rpt.freq = irpt->freq; - rpt.until = irpt->until; - LLIST_INIT(&rpt.bymonth); - LLIST_INIT(&rpt.bywday); - LLIST_INIT(&rpt.bymonthday); - rpt.exc = *exc; - rapt = recur_apoint_new(mesg, note, start, dur, state, &rpt); - mem_free(irpt); + if (rpt) { + /* + * In calcurse, "until" is interpreted as a day (DATE) - hours, + * minutes and seconds are ignored - whereas in iCal the full + * DATE-TIME value of "until" is taken into account. It follows + * that if the event in calcurse has an occurrence on the until + * day, and the start time is after the until value, the + * calcurse until day must be changed to the day before. + */ + if (rpt->until) { + day = DAY(rpt->until); + if (recur_item_find_occurrence(start, dur, rpt, NULL, + day, NULL) && + get_item_time(rpt->until) < get_item_time(start)) + rpt->until = date_sec_change(day, 0, -1); + else + rpt->until = day; + } + rpt->exc = *exc; + rapt = recur_apoint_new(mesg, note, start, dur, state, rpt); if (fmt_rapt) print_recur_apoint(fmt_rapt, start, rapt->start, rapt); } else { @@ -473,7 +623,6 @@ static char *ical_unformat_line(char *line, int eol, int indentation) { struct string s; char *p; - const char *INDENT = " "; string_init(&s); for (p = line; *p; p++) { @@ -579,6 +728,32 @@ ical_chk_header(FILE * fd, char *buf, char *lstore, unsigned *lineno, } /* + * Return the TZID property parameter value from a DTSTART/DTEND/EXDATE property + * in an allocated string. The value may be any text string not containing the + * characters '"', ';', ':' and ',' (RFC 5545, sections 3.2.19 and 3.1). + */ +static char *ical_get_tzid(char *p) +{ + const char param[] = ";TZID="; + char *q; + int s; + + if (!(p = strstr(p, param))) + return NULL; + p += sizeof(param) - 1; + if (*p == '"') + return NULL; + + q = strpbrk(p, ":;"); + s = q - p + 1; + q = mem_malloc(s); + strncpy(q, p, s); + q[s - 1] = '\0'; + + return q; +} + +/* * Return event type from a DTSTART/DTEND/EXDATE property. */ static ical_vevent_e ical_get_type(char *c_line) @@ -601,23 +776,23 @@ static ical_vevent_e ical_get_type(char *c_line) * where DATE is 'YYYYMMDD' and TIME is 'HHMMSS'. * The time and 'T' separator are optional (in the case of an day-long event). * - * The type argument is either APPOINTMENT or EVENT and the time format must - * agree. - * - * The timezone is not yet handled by calcurse. + * The type argument is either APPOINTMENT or EVENT, and the time format must + * match (either DATE-TIME or DATE). The time zone identifier is ignored in an + * EVENT or in an APPOINTMENT with UTC time. */ -static time_t ical_datetime2time_t(char *datestr, ical_vevent_e type) +static time_t ical_datetime2time_t(char *datestr, char *tzid, ical_vevent_e type) { const int INVALID = 0, DATE = 3, DATETIME = 6, DATETIMEZ = 7; struct date date; unsigned hour, min, sec; - char c; + char c, UTC[] = ""; int format; EXIT_IF(type == UNDEFINED, "event type not set"); format = sscanf(datestr, "%04u%02u%02uT%02u%02u%02u%c", &date.yyyy, &date.mm, &date.dd, &hour, &min, &sec, &c); + if (format == DATE && strlen(datestr) > 8) format = INVALID; if (format == DATETIMEZ && c != 'Z') @@ -626,9 +801,9 @@ static time_t ical_datetime2time_t(char *datestr, ical_vevent_e type) if (format == DATE && type == EVENT) return date2sec(date, 0, 0); else if (format == DATETIME && type == APPOINTMENT) - return date2sec(date, hour, min); + return tzdate2sec(date, hour, min, tzid); else if (format == DATETIMEZ && type == APPOINTMENT) - return utcdate2sec(date, hour, min); + return tzdate2sec(date, hour, min, UTC); return 0; } @@ -689,6 +864,7 @@ static long ical_durtime2long(char *timestr) * dur-second = 1*DIGIT "S" * dur-day = 1*DIGIT "D" * + * For events, duration must be days or weeks. * Example: A duration of 15 days, 5 hours and 20 seconds would be: * P15DT5H0M20S * A duration of 7 weeks would be: @@ -717,42 +893,15 @@ static long ical_dur2long(char *durstr, ical_vevent_e type) else if (sscanf(p, "%u%c%n", &day, &c, &bytes_read) == 2 && c == 'D') { /* dur-date */ p += bytes_read; - if (*p == 'T' && type == APPOINTMENT) - return day * DAYINSEC + ical_durtime2long(p); - else if (*p != 'T' && type == EVENT) - return day * DAYINSEC; + return day * DAYINSEC + (*p == 'T' && type == APPOINTMENT ? + ical_durtime2long(p) : + 0); } return 0; } /* - * Compute the vevent repetition end date from the repetition count. - * - * Extract from RFC2445: - * The COUNT rule part defines the number of occurrences at which to - * range-bound the recurrence. The "DTSTART" property value, if specified, - * counts as the first occurrence. - */ -static long ical_compute_rpt_until(long start, ical_rpt_t * rpt) -{ - switch (rpt->type) { - case RECUR_DAILY: - return date_sec_change(start, 0, rpt->freq * (rpt->count - 1)); - case RECUR_WEEKLY: - return date_sec_change(start, 0, - rpt->freq * WEEKINDAYS * (rpt->count - 1)); - case RECUR_MONTHLY: - return date_sec_change(start, rpt->freq * (rpt->count - 1), 0); - case RECUR_YEARLY: - return date_sec_change(start, - rpt->freq * 12 * (rpt->count - 1), 0); - default: - return 0; - } -} - -/* * Skip to the value part of an iCalendar content line. */ static char *ical_get_value(char *p) @@ -770,60 +919,160 @@ static char *ical_get_value(char *p) } /* + * Fill in the bymonth linked list from a comma-separated list of + * unsigned integers terminated by a space or end of string. + */ +static int ical_bymonth(llist_t *ll, char *cl) +{ + unsigned mon; + int *i, n; + + while (!(*cl == ' ' || *cl == '\0')) { + if (!(sscanf(cl, "%u%n", &mon, &n) == 1)) + return 0; + i = mem_malloc(sizeof(int)); + *i = mon; + LLIST_ADD(ll, i); + cl += n; + cl += (*cl == ','); + } + return 1; +} + +/* + * Fill in the bymonthday linked list from a comma-separated list of + * (signed) integers terminated by a space or end of string. + */ +static int ical_bymonthday(llist_t *ll, char *cl) +{ + int mday; + int *i, n; + + while (!(*cl == ' ' || *cl == '\0')) { + if (!(sscanf(cl, "%d%n", &mday, &n) == 1)) + return 0; + i = mem_malloc(sizeof(int)); + *i = mday; + LLIST_ADD(ll, i); + cl += n; + cl += (*cl == ','); + } + return 1; +} + +/* + * Fill in the bywday linked list from a comma-separated list of (ordered) + * weekday names (+1SU, MO, -5SA, 25TU, etc.) terminated by a space or end of + * string. + */ +static int ical_bywday(llist_t *ll, char *cl) +{ + int sign, order, wday, n, *i; + char *owd; + + while (!(*cl == ' ' || *cl == '\0')) { + /* find list separator */ + for (owd = cl; !(*cl == ',' || *cl == ' ' || *cl == '\0'); cl++) + ; + cl += (*cl == ','); + + if (!(sscanf(owd, "%d%n", &order, &n) == 1)) + order = n = 0; + sign = (order < 0) ? -1 : 1; + order *= sign; + owd += n; + if (starts_with(owd, "SU")) + wday = 0; + else if (starts_with(owd, "MO")) + wday = 1; + else if (starts_with(owd, "TU")) + wday = 2; + else if (starts_with(owd, "WE")) + wday = 3; + else if (starts_with(owd, "TH")) + wday = 4; + else if (starts_with(owd, "FR")) + wday = 5; + else if (starts_with(owd, "SA")) + wday = 6; + else + return 0; + + wday = sign * (wday + order * WEEKINDAYS); + i = mem_malloc(sizeof(int)); + *i = wday; + LLIST_ADD(ll, i); + } + return 1; +} + +/* * Read a recurrence rule from an iCalendar RRULE string. * + * RFC 5545, section 3.8.5.3: + * + * Property Name: RRULE + * + * Purpose: This property defines a rule or repeating pattern for + * recurring events, to-dos, journal entries, or time zone definitions. + * + * Value Type: RECUR + * + * RFC 5545, section 3.3.10: + * * Value Name: RECUR * * Purpose: This value type is used to identify properties that contain * a recurrence rule specification. * - * Formal Definition: The value type is defined by the following + * Format Definition: The value type is defined by the following * notation: * - * recur = "FREQ"=freq *( + * recur = recur-rule-part *( ";" recur-rule-part ) + * ; + * ; The rule parts are not ordered in any particular sequence. + * ; + * ; The FREQ rule part is REQUIRED, + * ; but MUST NOT occur more than once. + * ; + * ; The UNTIL or COUNT rule parts are OPTIONAL, + * ; but they MUST NOT occur in the same 'recur'. + * ; + * ; The other rule parts are OPTIONAL, + * ; but MUST NOT occur more than once. * - * ; either UNTIL or COUNT may appear in a 'recur', - * ; but UNTIL and COUNT MUST NOT occur in the same 'recur' - * - * ( ";" "UNTIL" "=" enddate ) / - * ( ";" "COUNT" "=" 1*DIGIT ) / - * - * ; the rest of these keywords are optional, - * ; but MUST NOT occur more than - * ; once - * - * ( ";" "INTERVAL" "=" 1*DIGIT ) / - * ( ";" "BYSECOND" "=" byseclist ) / - * ( ";" "BYMINUTE" "=" byminlist ) / - * ( ";" "BYHOUR" "=" byhrlist ) / - * ( ";" "BYDAY" "=" bywdaylist ) / - * ( ";" "BYMONTHDAY" "=" bymodaylist ) / - * ( ";" "BYYEARDAY" "=" byyrdaylist ) / - * ( ";" "BYWEEKNO" "=" bywknolist ) / - * ( ";" "BYMONTH" "=" bymolist ) / - * ( ";" "BYSETPOS" "=" bysplist ) / - * ( ";" "WKST" "=" weekday ) / - * ( ";" x-name "=" text ) - * ) -*/ -static ical_rpt_t *ical_read_rrule(FILE *log, char *rrulestr, + * recur-rule-part = ( "FREQ"=freq ) + * / ( "UNTIL" "=" enddate ) + * / ( "COUNT" "=" 1*DIGIT ) + * / ( "INTERVAL" "=" 1*DIGIT ) + * / ( "BYSECOND" "=" byseclist ) + * / ( "BYMINUTE" "=" byminlist ) + * / ( "BYHOUR" "=" byhrlist ) + * / ( "BYDAY" "=" bywdaylist ) + * / ( "BYMONTHDAY" "=" bymodaylist ) + * / ( "BYYEARDAY" "=" byyrdaylist ) + * / ( "BYWEEKNO" "=" bywknolist ) + * / ( "BYMONTH" "=" bymolist ) + * / ( "BYSETPOS" "=" bysplist ) + * / ( "WKST" "=" weekday ) + */ +static struct rpt *ical_read_rrule(FILE *log, char *rrulestr, unsigned *noskipped, const int itemline, - ical_vevent_e type) + ical_vevent_e type, + time_t start, + int *count) { - const char count[] = "COUNT="; - const char interv[] = "INTERVAL="; - char freqstr[BUFSIZ]; - unsigned interval; - ical_rpt_t *rpt; - char *p; + char freqstr[8], datestr[17]; + struct rpt *rpt; + char *p, *q; - /* See DTSTART. */ if (type == UNDEFINED) { ical_log(log, ICAL_VEVENT, itemline, _("need DTSTART to determine event type.")); return NULL; } + p = ical_get_value(rrulestr); if (!p) { ical_log(log, ICAL_VEVENT, itemline, @@ -831,69 +1080,136 @@ static ical_rpt_t *ical_read_rrule(FILE *log, char *rrulestr, (*noskipped)++; return NULL; } + /* Prepare for scanf(): replace semicolons by spaces. */ + for (q = p; (q = strchr(q, ';')); *q = ' ', q++) + ; + + rpt = mem_malloc(sizeof(struct rpt)); + memset(rpt, 0, sizeof(struct rpt)); + LLIST_INIT(&rpt->bymonth); + LLIST_INIT(&rpt->bywday); + LLIST_INIT(&rpt->bymonthday); - rpt = mem_malloc(sizeof(ical_rpt_t)); - memset(rpt, 0, sizeof(ical_rpt_t)); - if (sscanf(p, "FREQ=%s", freqstr) != 1) { + /* FREQ rule part */ + if ((p = strstr(rrulestr, "FREQ="))) { + if (sscanf(p, "FREQ=%7s", freqstr) != 1) { + ical_log(log, ICAL_VEVENT, itemline, + _("frequency not set in rrule.")); + (*noskipped)++; + mem_free(rpt); + return NULL; + } + } else { ical_log(log, ICAL_VEVENT, itemline, - _("recurrence frequency not found.")); + _("frequency absent in rrule.")); (*noskipped)++; mem_free(rpt); return NULL; } - if (starts_with(freqstr, "DAILY")) { + if (!strcmp(freqstr, "DAILY")) rpt->type = RECUR_DAILY; - } else if (starts_with(freqstr, "WEEKLY")) { + else if (!strcmp(freqstr, "WEEKLY")) rpt->type = RECUR_WEEKLY; - } else if (starts_with(freqstr, "MONTHLY")) { + else if (!strcmp(freqstr, "MONTHLY")) rpt->type = RECUR_MONTHLY; - } else if (starts_with(freqstr, "YEARLY")) { + else if (!strcmp(freqstr, "YEARLY")) rpt->type = RECUR_YEARLY; - } else { + else { + ical_log(log, ICAL_VEVENT, itemline, + _("rrule frequency not supported.")); + (*noskipped)++; + mem_free(rpt); + return NULL; + } + + /* INTERVAL rule part */ + rpt->freq = 1; + if ((p = strstr(rrulestr, "INTERVAL="))) { + if (sscanf(p, "INTERVAL=%d", &rpt->freq) != 1) { + ical_log(log, ICAL_VEVENT, itemline, _("invalid interval.")); + (*noskipped)++; + mem_free(rpt); + return NULL; + } + } + + /* UNTIL and COUNT rule parts */ + if (strstr(rrulestr, "UNTIL=") && strstr(rrulestr, "COUNT=")) { ical_log(log, ICAL_VEVENT, itemline, - _("recurrence frequency not recognized.")); + _("either until or count.")); (*noskipped)++; mem_free(rpt); return NULL; } + if ((p = strstr(rrulestr, "UNTIL="))) { + if (sscanf(p, "UNTIL=%16s", datestr) != 1) { + ical_log(log, ICAL_VEVENT, itemline, + _("missing until value.")); + (*noskipped)++; + mem_free(rpt); + return NULL; + } + rpt->until = ical_datetime2time_t(datestr, NULL, type); + if (!rpt->until) { + ical_log(log, ICAL_VEVENT, itemline, + _("invalid until format.")); + (*noskipped)++; + mem_free(rpt); + return NULL; + } + } + /* - * The UNTIL rule part defines a date-time value which bounds the - * recurrence rule in an inclusive manner. If not present, and the - * COUNT rule part is also not present, the RRULE is considered to - * repeat forever. - - * The COUNT rule part defines the number of occurrences at which to - * range-bound the recurrence. The "DTSTART" property value, if - * specified, counts as the first occurrence. + * COUNT is converted to UNTIL in ical_read_event() once all recurrence + * parameters are known. */ - if ((p = strstr(rrulestr, "UNTIL")) != NULL) { - rpt->until = ical_datetime2time_t(strchr(p, '=') + 1, type); - if (!(rpt->until)) { + if ((p = strstr(rrulestr, "COUNT="))) { + p = strchr(p, '=') + 1; + if (!(sscanf(p, "%d", count) == 1 && *count)) { ical_log(log, ICAL_VEVENT, itemline, - _("invalid until format.")); + _("invalid count value.")); (*noskipped)++; mem_free(rpt); return NULL; } - } else { - unsigned cnt; - char *countstr; - - rpt->until = 0; - if ((countstr = strstr(rrulestr, count))) { - countstr += sizeof(count) - 1; - if (sscanf(countstr, "%u", &cnt) == 1) - rpt->count = cnt; + } + + /* BYMONTH rule part */ + if ((p = strstr(rrulestr, "BYMONTH="))) { + p = strchr(p, '=') + 1; + if (!ical_bymonth(&rpt->bymonth, p)) { + ical_log(log, ICAL_VEVENT, itemline, + _("invalid bymonth list.")); + (*noskipped)++; + mem_free(rpt); + return NULL; } } - rpt->freq = 1; - if ((p = strstr(rrulestr, interv))) { - p += sizeof(interv) - 1; - if (sscanf(p, "%u", &interval) == 1) - rpt->freq = interval; + /* BYMONTHDAY rule part */ + if ((p = strstr(rrulestr, "BYMONTHDAY="))) { + p = strchr(p, '=') + 1; + if (!ical_bymonthday(&rpt->bymonthday, p)) { + ical_log(log, ICAL_VEVENT, itemline, + _("invalid bymonthday list.")); + (*noskipped)++; + mem_free(rpt); + return NULL; + } + } + + /* BYDAY rule part */ + if ((p = strstr(rrulestr, "BYDAY="))) { + p = strchr(p, '=') + 1; + if (!ical_bywday(&rpt->bywday, p)) { + ical_log(log, ICAL_VEVENT, itemline, + _("invalid byday list.")); + (*noskipped)++; + mem_free(rpt); + return NULL; + } } return rpt; @@ -915,47 +1231,42 @@ static int ical_read_exdate(llist_t * exc, FILE * log, char *exstr, unsigned *noskipped, const int itemline, ical_vevent_e type) { - char *p, *q; + char *p, *q, *tzid = NULL; time_t t; int n; - /* See DTSTART. */ - if (type == UNDEFINED) { - ical_log(log, ICAL_VEVENT, itemline, - _("need DTSTART to determine event type.")); - (*noskipped)++; - return 0; - } if (type != ical_get_type(exstr)) { ical_log(log, ICAL_VEVENT, itemline, _("invalid exception date value type.")); - (*noskipped)++; - return 0; + goto cleanup; } p = ical_get_value(exstr); if (!p) { ical_log(log, ICAL_VEVENT, itemline, _("malformed exceptions line.")); - (*noskipped)++; - return 0; + goto cleanup; } - + tzid = ical_get_tzid(exstr); /* Count the exceptions and replace commas by zeroes */ for (q = p, n = 1; (q = strchr(q, ',')); *q = '\0', q++, n++) ; while (n) { - if (!(t = ical_datetime2time_t(p, type))) { + if (!(t = ical_datetime2time_t(p, tzid, type))) { ical_log(log, ICAL_VEVENT, itemline, _("invalid exception.")); - (*noskipped)++; - return 0; + goto cleanup; } ical_add_exc(exc, t); p = strchr(p, '\0') + 1; n--; } - return 1; + +cleanup: + (*noskipped)++; + if (tzid) + mem_free(tzid); + return 0; } /* @@ -967,7 +1278,7 @@ static char *ical_read_note(char *line, ical_property_e property, unsigned *nosk FILE * log) { const int EOL = 1, - INDENT = (property != DESCRIPTION); + IND = (property != DESCRIPTION); char *p, *pname, *notestr; switch (property) { @@ -980,9 +1291,6 @@ static char *ical_read_note(char *line, ical_property_e property, unsigned *nosk case COMMENT: pname = "comment"; break; - case STATUS: - pname = "status"; - break; default: pname = "no property"; @@ -997,7 +1305,7 @@ static char *ical_read_note(char *line, ical_property_e property, unsigned *nosk goto leave; } - notestr = ical_unformat_line(p, EOL, INDENT); + notestr = ical_unformat_line(p, EOL, IND); if (!notestr) { asprintf(&p, _("malformed %s."), pname); ical_log(log, item_type, itemline, p); @@ -1013,7 +1321,7 @@ static char *ical_read_summary(char *line, unsigned *noskipped, ical_types_e item_type, const int itemline, FILE * log) { - const int EOL = 0, INDENT = 0; + const int EOL = 0, IND = 0; char *p, *summary = NULL; p = ical_get_value(line); @@ -1023,7 +1331,7 @@ static char *ical_read_summary(char *line, unsigned *noskipped, goto leave; } - summary = ical_unformat_line(p, EOL, INDENT); + summary = ical_unformat_line(p, EOL, IND); if (!summary) { ical_log(log, item_type, itemline, _("malformed summary.")); (*noskipped)++; @@ -1031,12 +1339,9 @@ static char *ical_read_summary(char *line, unsigned *noskipped, } /* An event summary is one line only. */ - if (strchr(summary, '\n')) { - ical_log(log, item_type, itemline, _("line break in summary.")); - (*noskipped)++; - mem_free(summary); - summary = NULL; - } + for (p = summary; *p; p++) + if (*p == '\n') + *p = ' '; leave: return summary; } @@ -1050,22 +1355,25 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, const int ITEMLINE = *lineno - !feof(fdi); ical_vevent_e vevent_type; ical_property_e property; - char *p, *note = NULL, *comment; - const char *SEPARATOR = "-- \n"; - struct string s; + char *p, *note, *tzid; + char *dtstart, *dtend, *duration, *rrule; + struct string s, exdate; struct { llist_t exc; - ical_rpt_t *rpt; - char *mesg, *desc, *loc, *comm, *stat, *note; - long start, end, dur; + struct rpt *rpt; + int count; + char *mesg, *desc, *loc, *comm, *imp, *note; + time_t start, end; + long dur; int has_alarm; } vevent; - int skip_alarm, has_note, separator; + int skip_alarm, has_note, separator, has_exdate; vevent_type = UNDEFINED; memset(&vevent, 0, sizeof vevent); LLIST_INIT(&vevent.exc); - skip_alarm = has_note = separator = 0; + note = dtstart = dtend = duration = rrule = NULL; + skip_alarm = has_note = separator = has_exdate =0; while (ical_readline(fdi, buf, lstore, lineno)) { note = NULL; property = NO_PROPERTY; @@ -1079,31 +1387,136 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, continue; } if (starts_with_ci(buf, "END:VEVENT")) { - if (!vevent.mesg) { + /* DTSTART and related properties (picked up earlier). */ + if (!dtstart) { ical_log(log, ICAL_VEVENT, ITEMLINE, - _("could not retrieve item summary.")); + _("item start date not defined.")); goto skip; } - if (vevent.start == 0) { + vevent_type = ical_get_type(dtstart); + if ((tzid = ical_get_tzid(dtstart)) && + vevent_type == APPOINTMENT) { + if (vevent.imp) { + asprintf(&p, "%s, TZID=%s", + vevent.imp, tzid); + mem_free(vevent.imp); + vevent.imp = p; + } else + asprintf(&vevent.imp, "TZID=%s", tzid); + has_note = separator = 1; + } + p = ical_get_value(dtstart); + if (!p) { ical_log(log, ICAL_VEVENT, ITEMLINE, - _("item start date is not defined.")); + _("malformed start time line.")); goto skip; } - if (vevent_type == APPOINTMENT && vevent.dur == 0) { - if (vevent.end != 0) { - vevent.dur = vevent.end - vevent.start; - } - - if (vevent.dur < 0) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("item has a negative duration.")); - goto skip; - } + vevent.start = ical_datetime2time_t(p, tzid, vevent_type); + if (tzid) { + mem_free(tzid); + tzid = NULL; + } + if (!vevent.start) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("invalid or malformed event " + "start time.")); + goto skip; + } + /* DTEND */ + if (!dtend) + goto duration; + if (vevent_type != ical_get_type(dtend)) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("invalid end time value type.")); + goto skip; + } + tzid = ical_get_tzid(dtend); + p = ical_get_value(dtend); + if (!p) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("malformed end time line.")); + goto skip; + } + vevent.end = ical_datetime2time_t(p, tzid, vevent_type); + if (tzid) { + mem_free(tzid); + tzid = NULL; + } + if (!vevent.end) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("malformed event end time.")); + goto skip; + } + if (vevent.end <= vevent.start) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("end must be later than start.")); + goto skip; + } + duration: + if (!duration) + goto rrule; + if (vevent.end) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("either end or duration.")); + goto skip; + } + p = ical_get_value(duration); + if (!p) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("malformed duration line.")); + goto skip; + } + vevent.dur = ical_dur2long(p, vevent_type); + if (!vevent.dur) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("invalid duration.")); + goto skip; } - if (vevent.rpt && vevent.rpt->count) { - vevent.rpt->until = - ical_compute_rpt_until(vevent.start, - vevent.rpt); + rrule: + if (!rrule) + goto exdate; + vevent.rpt = ical_read_rrule(log, rrule, noskipped, + ITEMLINE, vevent_type, vevent.start, + &vevent.count); + if (!vevent.rpt) + goto cleanup; + exdate: + if (!has_exdate) + goto duration_end; + if (!rrule) { + ical_log(log, ICAL_VEVENT, ITEMLINE, + _("exception date, but no recurrence " + "rule.")); + goto skip; + } + if (!ical_read_exdate(&vevent.exc, log, exdate.buf, + noskipped, ITEMLINE, vevent_type)) + goto cleanup; + duration_end: + /* An APPOINTMENT must always have a duration. */ + if (vevent_type == APPOINTMENT && !vevent.dur) { + vevent.dur = vevent.end ? + vevent.end - vevent.start : + 0; + } + /* An EVENT must always have an end. */ + if (vevent_type == EVENT) { + if (!vevent.end) + vevent.end = vevent.start + vevent.dur; + vevent.dur = vevent.end - vevent.start; + if (vevent.dur > DAYINSEC) { + /* Add note on multi-day events. */ + char *md = _("multi-day event changed " + "to one-day event"); + if (vevent.imp) { + asprintf(&p, "%s, %s", + vevent.imp, md); + mem_free(vevent.imp); + vevent.imp = p; + } else + asprintf(&vevent.imp, "%s", md); + has_note = separator = 1; + } } if (has_note) { /* Construct string with note file contents. */ @@ -1111,41 +1524,75 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, if (vevent.desc) { string_catf(&s, "%s", vevent.desc); mem_free(vevent.desc); - if (separator) - string_catf(&s, SEPARATOR); + vevent.desc = NULL; } + if (separator) + string_catf(&s, SEPARATOR); if (vevent.loc) { string_catf(&s, _("Location: %s"), vevent.loc); mem_free(vevent.loc); + vevent.loc = NULL; } if (vevent.comm) { string_catf(&s, _("Comment: %s"), vevent.comm); mem_free(vevent.comm); + vevent.comm = NULL; } - if (vevent.stat) { - string_catf(&s, _("Status: %s"), - vevent.stat); - mem_free(vevent.stat); + if (vevent.imp) { + string_catf(&s, ("Import: %s\n"), + vevent.imp); + mem_free(vevent.imp); + vevent.imp = NULL; } vevent.note = generate_note(string_buf(&s)); mem_free(s.buf); } + if (vevent.rpt) { + time_t day, until; + long dur; + char *msg; + + dur = vevent_type == EVENT ? -1 : vevent.dur; + day = DAY(vevent.start); + msg = _("rrule does not match start day (%s)."); + + if (vevent.count) { + recur_nth_occurrence(vevent.start, + dur, + vevent.rpt, + &vevent.exc, + vevent.count, + &until); + vevent.rpt->until = until; + } + if (!recur_item_find_occurrence(vevent.start, + dur, + vevent.rpt, + NULL, + day, + NULL)) { + char *l = day_ins(&msg, vevent.start); + ical_log(log, ICAL_VEVENT, ITEMLINE, l); + mem_free(l); + goto skip; + } + } switch (vevent_type) { case APPOINTMENT: ical_store_apoint(vevent.mesg, vevent.note, - vevent.start, vevent.dur, - vevent.rpt, &vevent.exc, - vevent.has_alarm, fmt_apt, - fmt_rapt); + vevent.start, vevent.dur, + vevent.rpt, &vevent.exc, + vevent.has_alarm, + fmt_apt, fmt_rapt); (*noapoints)++; break; case EVENT: ical_store_event(vevent.mesg, vevent.note, - vevent.start, vevent.end, - vevent.rpt, &vevent.exc, - fmt_ev, fmt_rev); + vevent.start, vevent.end, + vevent.rpt, &vevent.exc, + fmt_ev, fmt_rev); (*noevents)++; break; case UNDEFINED: @@ -1158,84 +1605,28 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, } if (starts_with_ci(buf, "DTSTART")) { /* - * DTSTART has a value type: either DATE-TIME (by - * default) or DATE. Properties DTEND, DURATION and - * EXDATE and rrule part UNTIL must agree. - * Assume that DTSTART comes before the others even - * though RFC 5545 allows any order. + * DTSTART has a value type: either DATE-TIME or DATE. * In calcurse DATE-TIME implies an appointment, DATE an * event. + * Properties DTEND, DURATION and EXDATE and rrule part + * UNTIL must match the DTSTART value type. */ - vevent_type = ical_get_type(buf); - p = ical_get_value(buf); - if (!p) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("malformed start time line.")); - goto skip; - } - - vevent.start = ical_datetime2time_t(p, vevent_type); - if (!vevent.start) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("invalid or malformed event " - "start time.")); - goto skip; - } + asprintf(&dtstart, "%s", buf); } else if (starts_with_ci(buf, "DTEND")) { - /* See DTSTART. */ - if (vevent_type == UNDEFINED) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("need DTSTART to determine " - "event type.")); - goto skip; - } - if (vevent_type != ical_get_type(buf)) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("invalid end time value type.")); - goto skip; - } - p = ical_get_value(buf); - if (!p) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("malformed end time line.")); - goto skip; - } - - vevent.end = ical_datetime2time_t(p, vevent_type); - if (!vevent.end) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("malformed event end time.")); - goto skip; - } + asprintf(&dtend, "%s", buf); } else if (starts_with_ci(buf, "DURATION")) { - /* See DTSTART. */ - if (vevent_type == UNDEFINED) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("need DTSTART to determine " - "event type.")); - goto skip; - } - p = ical_get_value(buf); - if (!p) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("malformed duration line.")); - goto skip; - } - vevent.dur = ical_dur2long(p, vevent_type); - if (!vevent.dur) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("invalid duration.")); - goto skip; - } + asprintf(&duration, "%s", buf); } else if (starts_with_ci(buf, "RRULE")) { - vevent.rpt = ical_read_rrule(log, buf, noskipped, - ITEMLINE, vevent_type); - if (!vevent.rpt) - goto cleanup; + asprintf(&rrule, "%s", buf); } else if (starts_with_ci(buf, "EXDATE")) { - if (!ical_read_exdate(&vevent.exc, log, buf, noskipped, - ITEMLINE, vevent_type)) - goto cleanup; + if (!has_exdate) { + has_exdate = 1; + string_init(&exdate); + string_catf(&exdate, "%s", buf); + } else { + p = ical_get_value(buf); + string_catf(&exdate, ",%s", p); + } } else if (starts_with_ci(buf, "SUMMARY")) { vevent.mesg = ical_read_summary(buf, noskipped, ICAL_VEVENT, ITEMLINE, log); @@ -1249,15 +1640,14 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, property = LOCATION; } else if (starts_with_ci(buf, "COMMENT")) { property = COMMENT; - } else if (starts_with_ci(buf, "STATUS")) { - property = STATUS; } if (property) { note = ical_read_note(buf, property, noskipped, ICAL_VEVENT, ITEMLINE, log); if (!note) goto cleanup; - separator = (property != DESCRIPTION); + if (!separator) + separator = (property != DESCRIPTION); has_note = 1; } switch (property) { @@ -1280,28 +1670,13 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, case COMMENT: /* There may be more than one. */ if (vevent.comm) { - asprintf(&comment, "%sComment: %s", + asprintf(&p, "%sComment: %s", vevent.comm, note); mem_free(vevent.comm); - vevent.comm = comment; + vevent.comm = p; } else vevent.comm = note; break; - case STATUS: - if (vevent.stat) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("only one status allowed.")); - goto skip; - } - if (!(starts_with(note, "TENTATIVE") || - starts_with(note, "CONFIRMED") || - starts_with(note, "CANCELLED"))) { - ical_log(log, ICAL_VEVENT, ITEMLINE, - _("invalid status value.")); - goto skip; - } - vevent.stat = note; - break; default: break; } @@ -1312,6 +1687,16 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents, skip: (*noskipped)++; cleanup: + if (dtstart) + mem_free(dtstart); + if (dtend) + mem_free(dtend); + if (duration) + mem_free(duration); + if (rrule) + mem_free(rrule); + if (has_exdate) + mem_free(exdate.buf); if (note) mem_free(note); if (vevent.desc) @@ -1320,8 +1705,8 @@ cleanup: mem_free(vevent.loc); if (vevent.comm) mem_free(vevent.comm); - if (vevent.stat) - mem_free(vevent.stat); + if (vevent.imp) + mem_free(vevent.imp); if (vevent.mesg) mem_free(vevent.mesg); if (vevent.rpt) @@ -1335,17 +1720,17 @@ ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped, { const int ITEMLINE = *lineno - !feof(fdi); ical_property_e property; - char *note = NULL, *comment; - const char *SEPARATOR = "-- \n"; + char *p, *note; struct string s; struct { - char *mesg, *desc, *loc, *comm, *stat, *note; + char *mesg, *desc, *loc, *comm, *note; int priority; int completed; } vtodo; int skip_alarm, has_note, separator; memset(&vtodo, 0, sizeof vtodo); + note = NULL; skip_alarm = has_note = separator = 0; while (ical_readline(fdi, buf, lstore, lineno)) { note = NULL; @@ -1371,23 +1756,21 @@ ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped, if (vtodo.desc) { string_catf(&s, "%s", vtodo.desc); mem_free(vtodo.desc); - if (separator) - string_catf(&s, SEPARATOR); + vtodo.desc = NULL; } + if (separator) + string_catf(&s, SEPARATOR); if (vtodo.loc) { string_catf(&s, _("Location: %s"), vtodo.loc); mem_free(vtodo.loc); + vtodo.loc = NULL; } if (vtodo.comm) { string_catf(&s, _("Comment: %s"), vtodo.comm); mem_free(vtodo.comm); - } - if (vtodo.stat) { - string_catf(&s, _("Status: %s"), - vtodo.stat); - mem_free(vtodo.stat); + vtodo.comm = NULL; } vtodo.note = generate_note(string_buf(&s)); mem_free(s.buf); @@ -1407,7 +1790,6 @@ ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped, } } else if (starts_with_ci(buf, "STATUS:COMPLETED")) { vtodo.completed = 1; - property = STATUS; } else if (starts_with_ci(buf, "SUMMARY")) { vtodo.mesg = ical_read_summary(buf, noskipped, ICAL_VTODO, @@ -1422,15 +1804,14 @@ ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped, property = LOCATION; } else if (starts_with_ci(buf, "COMMENT")) { property = COMMENT; - } else if (starts_with_ci(buf, "STATUS")) { - property = STATUS; } if (property) { note = ical_read_note(buf, property, noskipped, ICAL_VTODO, ITEMLINE, log); if (!note) goto cleanup; - separator = (property != DESCRIPTION); + if (!separator) + separator = (property != DESCRIPTION); has_note = 1; } switch (property) { @@ -1453,29 +1834,13 @@ ical_read_todo(FILE * fdi, FILE * log, unsigned *notodos, unsigned *noskipped, case COMMENT: /* There may be more than one. */ if (vtodo.comm) { - asprintf(&comment, "%sComment: %s", + asprintf(&p, "%sComment: %s", vtodo.comm, note); mem_free(vtodo.comm); - vtodo.comm = comment; + vtodo.comm = p; } else vtodo.comm = note; break; - case STATUS: - if (vtodo.stat) { - ical_log(log, ICAL_VTODO, ITEMLINE, - _("only one status allowed.")); - goto skip; - } - if (!(starts_with(note, "NEEDS-ACTION") || - starts_with(note, "COMPLETED") || - starts_with(note, "IN-PROCESS") || - starts_with(note, "CANCELLED"))) { - ical_log(log, ICAL_VTODO, ITEMLINE, - _("invalid status value.")); - goto skip; - } - vtodo.stat = note; - break; default: break; } @@ -1494,8 +1859,6 @@ cleanup: mem_free(vtodo.loc); if (vtodo.comm) mem_free(vtodo.comm); - if (vtodo.stat) - mem_free(vtodo.stat); if (vtodo.mesg) mem_free(vtodo.mesg); } @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -50,8 +50,8 @@ struct ht_keybindings_s { const char *label; - enum key key; - HTABLE_ENTRY(ht_keybindings_s); + enum vkey key; + HTABLE_ENTRY(ht_keybindings_s); }; static void load_keys_ht_getkey(struct ht_keybindings_s *, const char **, @@ -237,25 +237,25 @@ void io_dump_apts(const char *fmt_apt, const char *fmt_rapt, LLIST_FOREACH(&recur_elist, i) { struct recur_event *rev = LLIST_GET_DATA(i); - time_t day = update_time_in_date(rev->day, 0, 0); + time_t day = DAY(rev->day); print_recur_event(fmt_rev, day, rev); } LLIST_TS_FOREACH(&recur_alist_p, i) { struct recur_apoint *rapt = LLIST_GET_DATA(i); - time_t day = update_time_in_date(rapt->start, 0, 0); + time_t day = DAY(rapt->start); print_recur_apoint(fmt_rapt, day, rapt->start, rapt); } LLIST_TS_FOREACH(&alist_p, i) { struct apoint *apt = LLIST_TS_GET_DATA(i); - time_t day = update_time_in_date(apt->start, 0, 0); + time_t day = DAY(apt->start); print_apoint(fmt_apt, day, apt); } LLIST_FOREACH(&eventlist, i) { struct event *ev = LLIST_TS_GET_DATA(i); - time_t day = update_time_in_date(ev->day, 0, 0); + time_t day = DAY(ev->day); print_event(fmt_ev, day, ev); } } @@ -940,16 +940,6 @@ load_keys_ht_compare(struct ht_keybindings_s *data1, } /* - * isblank(3) is protected by the __BSD_VISIBLE macro and this fails to be - * visible in some specific cases. Thus replace it by the following is_blank() - * function. - */ -static int is_blank(int c) -{ - return c == ' ' || c == '\t'; -} - -/* * Load user-definable keys from file. * A hash table is used to speed up loading process in avoiding string * comparisons. @@ -958,21 +948,21 @@ static int is_blank(int c) */ void io_load_keys(const char *pager) { - struct ht_keybindings_s keys[NBKEYS]; + struct ht_keybindings_s virt_keys[NBVKEYS], *ht_elm, ht_entry; FILE *keyfp; - char buf[BUFSIZ]; + char buf[BUFSIZ], key_label[BUFSIZ], key_str[BUFSIZ]; + char *p, *msg; struct io_file *log; - int i, skipped, loaded, line; - const int MAX_ERRORS = 5; + int i, n, skipped, loaded, line, assigned, undefined, key; keys_init(); struct ht_keybindings ht_keys = HTABLE_INITIALIZER(&ht_keys); - for (i = 0; i < NBKEYS; i++) { - keys[i].key = (enum key)i; - keys[i].label = keys_get_label((enum key)i); - HTABLE_INSERT(ht_keybindings, &ht_keys, &keys[i]); + for (i = 0; i < NBVKEYS; i++) { + virt_keys[i].key = (enum vkey)i; + virt_keys[i].label = keys_get_label((enum vkey)i); + HTABLE_INSERT(ht_keybindings, &ht_keys, &virt_keys[i]); } keyfp = fopen(path_keys, "r"); @@ -981,111 +971,97 @@ void io_load_keys(const char *pager) log = io_log_init(); skipped = loaded = line = 0; while (fgets(buf, BUFSIZ, keyfp) != NULL) { - char key_label[BUFSIZ], *p; - struct ht_keybindings_s *ht_elm, ht_entry; - const int AWAITED = 1; - int assigned; - line++; - if (skipped > MAX_ERRORS) { - const char *too_many = - _("\nToo many errors while reading configuration file!\n" - "Please backup your keys file, remove it from directory, " - "and launch calcurse again.\n"); - - io_log_print(log, line, too_many); - break; - } - for (p = buf; is_blank((int)*p); p++) ; - if (p != buf) - memmove(buf, p, strlen(p)); - if (buf[0] == '#' || buf[0] == '\n') + p = buf; + while (*p == ' ' || *p == '\t') p++; + if (*p == '#' || *p == '\n') continue; - if (sscanf(buf, "%s", key_label) != AWAITED) { + /* Find the virtual key by key label. */ + if (sscanf(p, "%s", key_label) != 1) { skipped++; io_log_print(log, line, _("Could not read key label")); continue; } - - /* Skip legacy entries. */ - if (strcmp(key_label, "generic-cut") == 0) - continue; - + p += strlen(key_label); ht_entry.label = key_label; - p = buf + strlen(key_label) + 1; - ht_elm = - HTABLE_LOOKUP(ht_keybindings, &ht_keys, &ht_entry); + ht_elm = HTABLE_LOOKUP(ht_keybindings, &ht_keys, &ht_entry); if (!ht_elm) { skipped++; - io_log_print(log, line, - _("Key label not recognized")); + asprintf(&msg, + _("Key label not recognized: \"%s\""), + key_label); + io_log_print(log, line, msg); + mem_free(msg); continue; } - assigned = 0; - for (;;) { - char key_ch[BUFSIZ], tmpbuf[BUFSIZ]; - - while (*p == ' ') - p++; - (void)strncpy(tmpbuf, p, BUFSIZ); - tmpbuf[BUFSIZ - 1] = '\0'; - if (sscanf(tmpbuf, "%s", key_ch) == AWAITED) { - int ch; - - if ((ch = keys_str2int(key_ch)) < 0) { - char *unknown_key; + /* Assign keyboard keys to the virtual key. */ + assigned = undefined = 0; + for (;;) { + if (sscanf(p, "%s%n", key_str, &n) != 1) { + if (assigned || undefined) + loaded++; + else { skipped++; - asprintf(&unknown_key, - _("Error reading key: \"%s\""), - key_ch); - io_log_print(log, line, unknown_key); - mem_free(unknown_key); - } else { - int used; - - used = - keys_assign_binding(ch, - ht_elm-> - key); - if (used) { - char *already_assigned; - - skipped++; - asprintf(&already_assigned, - _("\"%s\" assigned multiple times!"), - key_ch); - io_log_print(log, line, - already_assigned); - mem_free(already_assigned); - } else { - assigned++; - } + asprintf(&msg, + _("No keys assigned to " + "\"%s\"."), + key_label); + io_log_print(log, line, msg); + mem_free(msg); } - p += strlen(key_ch) + 1; - } else { - if (assigned) - loaded++; break; } + p += n; + if (!strcmp(key_str, "UNDEFINED")) { + undefined++; + keys_assign_binding(-1, ht_elm->key); + } else if ((key = keys_str2int(key_str)) < 0) { + skipped++; + asprintf(&msg, + _("Keyname not recognized: \"%s\""), + key_str); + io_log_print(log, line, msg); + mem_free(msg); + } else if (keys_assign_binding(key, ht_elm->key)) { + skipped++; + asprintf(&msg, + _("\"%s\" assigned twice: \"%s\"."), + key_str, key_label); + io_log_print(log, line, msg); + mem_free(msg); + } else + assigned++; } } file_close(keyfp, __FILE_POS__); + if (loaded < NBVKEYS && (i = keys_fill_missing()) < 1) { + skipped++; + strcpy(key_label, keys_get_label((enum vkey)(-i))); + strcpy(key_str, keys_get_binding((enum vkey)(-i))); + asprintf(&msg, _("Action \"%s\" absent, but default key \"%s\" " + "assigned to another action."), + key_label, key_str); + io_log_print(log, line, msg); + mem_free(msg); + } file_close(log->fd, __FILE_POS__); if (skipped > 0) { - const char *view_log = - _("There were some errors when loading keys file."); - io_log_display(log, view_log, pager); + msg = _("Errors in the keys file."); + io_log_display(log, msg, pager); + WARN_MSG(_("Remove offending line(s) from the keys file, " + "aborting...")); + exit_calcurse(EXIT_FAILURE); } io_log_free(log); - EXIT_IF(skipped > MAX_ERRORS, - _("Too many errors while reading keys file, aborting...")); - if (loaded < NBKEYS) - keys_fill_missing(); - if (keys_check_missing_bindings()) - WARN_MSG(_("Some actions do not have any associated key bindings!")); + /* Default keys were inserted. */ + if (loaded < NBVKEYS) + io_save_keys(); + /* Should never occur. */ + EXIT_IF(keys_check_missing(), + _("Some actions do not have any associated key bindings!")); } int io_check_dir(const char *dir) @@ -1207,21 +1183,6 @@ int io_check_data_files(void) return missing; } -/* Draw the startup screen */ -void io_startup_screen(int no_data_file) -{ - const char *enter = _("Press [ENTER] to continue"); - - if (no_data_file) - status_mesg(_("Welcome to Calcurse. Missing data files were created."), - enter); - else - status_mesg(_("Data files found. Data will be loaded now."), - enter); - - keys_wait_for_any_key(win[KEY].p); -} - /* Export calcurse data. */ void io_export_data(enum export_type type, int export_uid) { @@ -1252,7 +1213,7 @@ void io_export_data(enum export_type type, int export_uid) else if (type == IO_EXPORT_PCAL) pcal_export_data(stream); - if (show_dialogs() && ui_mode == UI_CURSES) { + if (!quiet && ui_mode == UI_CURSES) { fclose(stream); status_mesg(success, enter); keys_wait_for_any_key(win[KEY].p); @@ -1292,7 +1253,7 @@ static FILE *get_import_stream(enum import_type type, char **stream_name) * A temporary log file is created in /tmp to store the import process report, * and is cleared at the end. */ -void io_import_data(enum import_type type, char *stream_name, +int io_import_data(enum import_type type, char *stream_name, const char *fmt_ev, const char *fmt_rev, const char *fmt_apt, const char *fmt_rapt, const char *fmt_todo) @@ -1327,7 +1288,7 @@ void io_import_data(enum import_type type, char *stream_name, } if (stream == NULL) - return; + return 0; memset(&stats, 0, sizeof stats); @@ -1335,7 +1296,7 @@ void io_import_data(enum import_type type, char *stream_name, if (log == NULL) { if (stream != stdin) file_close(stream, __FILE_POS__); - return; + return 0; } if (type == IO_IMPORT_ICAL) @@ -1360,7 +1321,7 @@ void io_import_data(enum import_type type, char *stream_name, stats.todos); asprintf(&stats_str[3], _("%d skipped"), stats.skipped); - if (ui_mode == UI_CURSES && show_dialogs()) { + if (ui_mode == UI_CURSES && !quiet) { char *read, *stat; asprintf(&read, proc_report, stats.lines); @@ -1371,7 +1332,7 @@ void io_import_data(enum import_type type, char *stream_name, mem_free(read); mem_free(stat); keys_wait_for_any_key(win[KEY].p); - } else if (ui_mode == UI_CMDLINE && show_dialogs()) { + } else if (ui_mode == UI_CMDLINE && !quiet) { printf(proc_report, stats.lines); printf("\n%s / %s / %s / %s\n", stats_str[0], stats_str[1], stats_str[2], stats_str[3]); @@ -1392,8 +1353,11 @@ void io_import_data(enum import_type type, char *stream_name, mem_free(stats_str[3]); if (ui_mode == UI_CURSES) mem_free(stream_name); - if (!stats.skipped) + if (!stats.skipped) { io_log_free(log); + return 1; + } else + return 0; } struct io_file *io_log_init(void) @@ -1411,7 +1375,7 @@ struct io_file *io_log_init(void) ERROR_MSG(_("Warning: could not create temporary log file, Aborting...")); goto error; } - strncpy(log->name, logname, sizeof(log->name)); + log->name = mem_strdup(logname); log->fd = fopen(log->name, "w"); if (log->fd == NULL) { ERROR_MSG(_("Warning: could not open temporary log file, Aborting...")); @@ -1461,6 +1425,7 @@ void io_log_free(struct io_file *log) EXIT_IF(unlink(log->name) != 0, _("Warning: could not erase temporary log file %s, Aborting..."), log->name); + mem_free(log->name); mem_free(log); } @@ -1469,7 +1434,8 @@ static void *io_psave_thread(void *arg) { int delay = conf.periodic_save; EXIT_IF(delay < 0, _("Invalid delay")); - char *mesg = _("Periodic save: data files have changed. Save cancelled."); + char *mesg = _("Periodic save cancelled. Data files have changed. " + "Save and merge interactively"); pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); for (;;) { @@ -1589,22 +1555,6 @@ unsigned io_get_pid(char *file) } /* - * Check whether a file is empty. - */ -int io_file_is_empty(char *file) -{ - FILE *fp; - int ret = -1; - - if (file && (fp = fopen(file, "r"))) { - ret = (fgetc(fp) == '\n' && fgetc(fp) == EOF) || feof(fp); - fclose(fp); - } - - return ret; -} - -/* * Check whether two files are equal. */ int io_files_equal(const char *file1, const char *file2) @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -39,26 +39,73 @@ #include "calcurse.h" -#define MAXKEYVAL KEY_MAX /* ncurses defines KEY_MAX as maximum key value */ - -struct keydef_s { - const char *label; - const char *binding; - const char *sb_label; -}; +/* + * The interactive calcurse interface is controlled by "virtual keys", aka + * actions or commands. The virtual keys are defined by the type 'enum vkey', + * see calcurse.h. To each virtual key is assigned (or bound) zero or more + * keyboard keys/characters. A character (generated by a keyboard key) may be + * either an ordinary character or a pseudo-character. [An ordinary character + * is either a singlebyte ASCII character or a multibyte, UTF-8 encoded + * character; a pseudo-character (as supported by curses) is an escape sequence + * generated by a key.] A keyboard key/character is uniquely identified by its + * keyname (a character string) or by an integer (the Unicode code point of the + * character with a slight modification to accomodate the range of curses + * pseudo-characters). Mapping between the two forms is performed by the + * functions keys_str2int() and keys_int2str(). + */ -static llist_t keys[NBKEYS]; -static enum key actions[MAXKEYVAL]; +/* + * Assignment of keys to virtual keys is held in the tabel keys[]. The entry for + * each virtual key is a linked list of keyboard keys (bindings); each list + * element is the keyname as returned by keys_int2str(). + * + * At the very first run default keys are assigned to all virtual keys from a + * built-in table keydef[] and saved to disk in the calcurse config directory. + * Later the user may edit the key configuration and change the key bindings by + * adding/removing keys. If all keys are removed, the virtual key is left + * undefined. This state is also saved in the configuration file. The linked + * list for an undefined virtual key contains a single element with a null + * pointer as data. + */ +static llist_t keys[NBVKEYS]; +/* + * To cater for the other direction (which virtual key is a keyboard key + * assigned to), two constructions are needed: a table actions[] for the + * keyboard keys in the curses range, and a linked list actions_ext for + * multi-byte UTF-8 encoded keyboard characters. + * + * For each keyboard key (integer) in the curses key range, the virtual key + * (action) it is assigned to or, if not assigned, KEY_UNDEF. + */ +static enum vkey actions[KEY_MAX]; +/* + * For the millions of possible keyboard keys above the curses range, a linked + * list of keys which are actually bound to a virtual key. + * Each list element is a key_ext structure. + */ +llist_t actions_ext; struct key_ext { - int ch; - enum key action; + int key; + enum vkey action; }; -llist_t actions_ext; +/* + * Assigning a keyboard key to a virtual key is accomplished by + * 1) either inserting the virtual key in the actions[] entry for the keyboard key + * or adding the pair (key, virtual key) to the list actions_ext + * 2) adding it in keys[] to the list for the virtual key + * See keys_assign_binding() below. + */ +/* The default key bindings for the virtual keys. */ +struct keydef_s { + const char *label; /* Name of the virtual key (action). */ + const char *binding; /* String of space-separated keynames bound to it. */ + const char *sb_label; /* Display name in the status bar menu. */ +}; #define gettext_noop(s) s -static struct keydef_s keydef[NBKEYS] = { +static struct keydef_s keydef[NBVKEYS] = { { "generic-cancel", "ESC", gettext_noop("Cancel") }, { "generic-select", "SPC", gettext_noop("Select") }, { "generic-credits", "@", gettext_noop("Credits") }, @@ -69,6 +116,7 @@ static struct keydef_s keydef[NBKEYS] = { { "generic-copy", "c", gettext_noop("Copy") }, { "generic-paste", "p ^V", gettext_noop("Paste") }, { "generic-change-view", "TAB", gettext_noop("Chg Win") }, + { "generic-prev-view", "KEY_BTAB", gettext_noop("Prev Win") }, { "generic-import", "i I", gettext_noop("Import") }, { "generic-export", "x X", gettext_noop("Export") }, { "generic-goto", "g G", gettext_noop("Go to") }, @@ -132,10 +180,11 @@ void keys_init(void) int i; const char *cp; - for (i = 0; i < MAXKEYVAL; i++) + /* All keys unassigned. */ + for (i = 0; i < KEY_MAX; i++) actions[i] = KEY_UNDEF; LLIST_INIT(&actions_ext); - for (i = 0; i < NBKEYS; i++) + for (i = 0; i < NBVKEYS; i++) LLIST_INIT(&keys[i]); /* Initialization of the keynames table. */ @@ -146,7 +195,7 @@ void keys_init(void) for (i = 1; i < 128; i++) if ((cp = keyname(i))) keynames[i] = mem_strdup(cp); - /* ... and for the ncurses escape keys (pseudokeys). */ + /* ... and for the ncurses pseudo-characters. */ for (i = KEY_MIN; i < KEY_MAX; i++) if ((cp = keyname(i))) keynames[i] = mem_strdup(cp); @@ -189,7 +238,7 @@ void keys_free(void) { int i; - for (i = 0; i < NBKEYS; i++) { + for (i = 0; i < NBVKEYS; i++) { LLIST_FREE_INNER(&keys[i], key_free); LLIST_FREE(&keys[i]); } @@ -205,31 +254,40 @@ void keys_dump_defaults(char *file) _("FATAL ERROR: could not create default keys file.")); dump_intro(fd); - for (i = 0; i < NBKEYS; i++) + for (i = 0; i < NBVKEYS; i++) fprintf(fd, "%s %s\n", keydef[i].label, keydef[i].binding); file_close(fd, __FILE_POS__); } -const char *keys_get_label(enum key key) +const char *keys_get_label(enum vkey key) { EXIT_IF(key < 0 - || key > NBKEYS, + || key > NBVKEYS, _("FATAL ERROR: key value out of bounds")); return keydef[key].label; } +const char *keys_get_binding(enum vkey key) +{ + EXIT_IF(key < 0 + || key > NBVKEYS, + _("FATAL ERROR: key value out of bounds")); + + return keydef[key].binding; +} + static int key_ext_hasch(struct key_ext *k, void *cbdata) { - return (k->ch == *((int *)cbdata)); + return (k->key == *((int *)cbdata)); } -enum key keys_get_action(int pressed) +enum vkey keys_get_action(int pressed) { if (pressed < 0) { return -1; - } else if (pressed > MAXKEYVAL) { + } else if (pressed > KEY_MAX) { llist_item_t *i = LLIST_FIND_FIRST(&actions_ext, &pressed, key_ext_hasch); if (!i) @@ -274,7 +332,7 @@ void keys_wait_for_any_key(WINDOW *win) keys_wgetch(win); } -enum key keys_get(WINDOW *win, int *count, int *reg) +enum vkey keys_get(WINDOW *win, int *count, int *reg) { int ch = '0'; @@ -312,60 +370,89 @@ enum key keys_get(WINDOW *win, int *count, int *reg) } } -static void add_key_str(enum key action, int key) +static void add_if_undefined(enum vkey action) { - if (action > NBKEYS) - return; + /* If list is empty, mark action as UNDEFINED. */ + if (!keys[action].head) + LLIST_ADD(&keys[action], NULL); +} - LLIST_ADD(&keys[action], keys_int2str(key)); +static void del_if_undefined(enum vkey action) +{ + /* Action UNDEFINED? */ + if (!LLIST_GET_DATA(LLIST_FIRST(&keys[action]))) + LLIST_REMOVE(&keys[action], keys[action].head); } -int keys_assign_binding(int key, enum key action) +static void free_key_str(char *str) { - if (key < 0) - return 1; - if (key > KEY_MAX) { - llist_item_t *i = LLIST_FIND_FIRST(&actions_ext, &key, key_ext_hasch); - if (i) - return 1; - struct key_ext *k = mem_malloc(sizeof(struct key_ext)); - k->ch = key; - k->action = action; - LLIST_ADD(&actions_ext, k); - } else { - if (actions[key] != KEY_UNDEF) - return 1; - actions[key] = action; - } - add_key_str(action, key); - return 0; + mem_free(str); } -static void del_key_str(enum key action, int key) +static void add_key_str(enum vkey action, int key) +{ + if (action > NBVKEYS) + return; + + del_if_undefined(action); + LLIST_ADD(&keys[action], keys_int2str(key)); +} + +static void del_key_str(enum vkey action, int key) { llist_item_t *i; - char *oldstr = keys_int2str(key);; + char *oldstr = keys_int2str(key), *j; - if (action > NBKEYS) + if (action > NBVKEYS) return; LLIST_FOREACH(&keys[action], i) { - if (strcmp(LLIST_GET_DATA(i), oldstr) == 0) { + if (strcmp((j = LLIST_GET_DATA(i)), oldstr) == 0) { LLIST_REMOVE(&keys[action], i); + free_key_str(j); goto cleanup; } } cleanup: + add_if_undefined(action); mem_free(oldstr); } -void keys_remove_binding(int key, enum key action) +/* + * Assign keyboard key "key" to virtual key "action" by + * + * - marking keyboard key "key" as used for virtual key "actual" + * - adding "key" to the list of assigned keys for "action" in the tabel keys[] + * + * The former is done by either inserting "action" in the "key" entry of tabel + * actions[], or for keys above the curses range, inserting (key, action) in the + * list actions_ext. + */ +int keys_assign_binding(int key, enum vkey action) +{ + if (key > KEY_MAX) { + if (LLIST_FIND_FIRST(&actions_ext, &key, key_ext_hasch)) + return 1; + struct key_ext *k = mem_malloc(sizeof(struct key_ext)); + k->key = key; + k->action = action; + LLIST_ADD(&actions_ext, k); + } else if (key > -1) { + if (actions[key] != KEY_UNDEF) + return 1; + actions[key] = action; + } + add_key_str(action, key); + return 0; +} + +void keys_remove_binding(int key, enum vkey action) { if (key < 0) return; - if (key <= MAXKEYVAL) { + if (key <= KEY_MAX) { actions[key] = KEY_UNDEF; } else { llist_item_t *i = LLIST_FIND_FIRST(&actions_ext, &key, @@ -410,6 +497,8 @@ char *keys_int2str(int key) { char *res; + if (key == -1) + return NULL; if (key < KEY_MAX) { if (strcmp(keynames[key], "") == 0) return NULL; @@ -421,50 +510,44 @@ char *keys_int2str(int key) } } -int keys_action_count_keys(enum key action) +int keys_action_count_keys(enum vkey action) { llist_item_t *i; int n = 0; + /* Action UNDEFINED? */ + if (!LLIST_GET_DATA(LLIST_FIRST(&keys[action]))) + return 0; + LLIST_FOREACH(&keys[action], i) - n++; + n++; return n; } -const char *keys_action_firstkey(enum key action) +const char *keys_action_firstkey(enum vkey action) { const char *s = LLIST_GET_DATA(LLIST_FIRST(&keys[action])); return (s != NULL) ? s : "XXX"; } -const char *keys_action_nkey(enum key action, int keynum) +const char *keys_action_nkey(enum vkey action, int keynum) { return LLIST_GET_DATA(LLIST_NTH(&keys[action], keynum)); } -char *keys_action_allkeys(enum key action) +char *keys_action_allkeys(enum vkey action) { llist_item_t *i; - static char keystr[BUFSIZ]; - int keystrlen = 0; - int entrylen; - - if (!LLIST_FIRST(&keys[action])) - return NULL; - - keystr[0] = '\0'; - LLIST_FOREACH(&keys[action], i) { - entrylen = strlen(LLIST_GET_DATA(i)) + 1; - if (keystrlen + entrylen >= BUFSIZ) - break; - memcpy(keystr + keystrlen, LLIST_GET_DATA(i), entrylen - 1); - keystr[keystrlen + entrylen - 1] = ' '; - keystrlen += entrylen; - } - - keystr[keystrlen] = '\0'; - return keystr; + struct string keystr; + + string_init(&keystr); + if (!LLIST_GET_DATA(LLIST_FIRST(&keys[action]))) + string_catf(&keystr, "%s", "UNDEFINED"); + else + LLIST_FOREACH(&keys[action], i) + string_catf(&keystr, "%s ", LLIST_GET_DATA(i)); + return string_buf(&keystr); } /* Need this to display keys properly inside status bar. */ @@ -509,7 +592,7 @@ keys_display_bindings_bar(WINDOW * win, int *bindings, int count, const char *label; - if (binding_key < NBKEYS) { + if (binding_key < NBVKEYS) { strncpy(key, keys_action_firstkey(binding_key), UTF8_MAXLEN); key[UTF8_MAXLEN] = '\0'; label = gettext(keydef[binding_key].sb_label); @@ -561,9 +644,9 @@ keys_display_bindings_bar(WINDOW * win, int *bindings, int count, * Display information about the given key. * (could not add the keys descriptions to keydef variable, because of i18n). */ -void keys_popup_info(enum key key) +void keys_popup_info(enum vkey key) { - char *info[NBKEYS]; + char *info[NBVKEYS]; WINDOW *infowin; info[KEY_GENERIC_CANCEL] = _("Cancel the ongoing action."); @@ -582,6 +665,8 @@ void keys_popup_info(enum key key) _("Paste an item at the current position."); info[KEY_GENERIC_CHANGE_VIEW] = _("Select next panel in calcurse main screen."); + info[KEY_GENERIC_PREV_VIEW] = + _("Select previous panel in calcurse main screen."); info[KEY_GENERIC_IMPORT] = _("Import data from an external file."); info[KEY_GENERIC_EXPORT] = _("Export data to a new file format."); info[KEY_GENERIC_GOTO] = _("Select the day to go to."); @@ -651,7 +736,7 @@ void keys_popup_info(enum key key) info[KEY_LOWER_PRIORITY] = _("Lower a task priority inside the todo panel."); - if (key > NBKEYS) + if (key > NBVKEYS) return; #define WINROW 10 @@ -668,59 +753,75 @@ void keys_popup_info(enum key key) void keys_save_bindings(FILE * fd) { int i; - char *action; + char *keys; EXIT_IF(fd == NULL, _("FATAL ERROR: null file pointer.")); dump_intro(fd); - for (i = 0; i < NBKEYS; i++) { - action = keys_action_allkeys(i); - if (action) - fprintf(fd, "%s %s\n", keydef[i].label, action); + for (i = 0; i < NBVKEYS; i++) { + if ((keys = keys_action_allkeys(i))) + fprintf(fd, "%s %s\n", keydef[i].label, keys); } + mem_free(keys); } -int keys_check_missing_bindings(void) +int keys_check_undefined(void) { int i; - for (i = 0; i < NBKEYS; i++) { - if (!LLIST_FIRST(&keys[i])) + for (i = 0; i < NBVKEYS; i++) { + if (!LLIST_GET_DATA(LLIST_FIRST(&keys[i]))) return 1; } return 0; } -void keys_fill_missing(void) +int keys_check_missing(void) { int i; - for (i = 0; i < NBKEYS; i++) { - if (!LLIST_FIRST(&keys[i])) { - char *p, tmpbuf[BUFSIZ]; - - strncpy(tmpbuf, keydef[i].binding, BUFSIZ); - tmpbuf[BUFSIZ - 1] = '\0'; - p = tmpbuf; - for (;;) { - char key_ch[BUFSIZ]; - - while (*p == ' ') - p++; - if (sscanf(p, "%s", key_ch) == 1) { - int ch, used; - - ch = keys_str2int(key_ch); - used = keys_assign_binding(ch, i); - if (used) - WARN_MSG(_("When adding default key for \"%s\", " - "\"%s\" was already assigned!"), - keydef[i].label, - key_ch); - p += strlen(key_ch); - } else { - break; - } - } + for (i = 0; i < NBVKEYS; i++) { + if (!LLIST_FIRST(&keys[i])) + return 1; + } + return 0; +} + +/* + * Insert default keybindings for missing actions. + * Return either the number of actions assigned to (on success) or, if default + * keys could not be assigned, the negative index into the keydef[] table of the + * failing action. + */ +int keys_fill_missing(void) +{ + int i, ch, assign, assigned; + char *p, key_ch[BUFSIZ]; + + for (i = assigned = 0; i < NBVKEYS; i++) { + if (LLIST_FIRST(&keys[i])) + continue; + + p = (char *)keydef[i].binding; + for (assign = 0;;) { + while (*p == ' ') + p++; + if (sscanf(p, "%s", key_ch) == 1) { + ch = keys_str2int(key_ch); + if (keys_assign_binding(ch, i)) + return -i; + else + assign = 1; + p += strlen(key_ch); + } else + break; } + assigned += assign; + } + + if (assigned) { + p = (assigned == 1) ? "": "s"; + WARN_MSG(_("Default key(s) assigned to %d action%s."), + assigned, p); } + return assigned; } diff --git a/src/listbox.c b/src/listbox.c index 2b39aa8..d86f540 100644 --- a/src/listbox.c +++ b/src/listbox.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 diff --git a/src/llist.c b/src/llist.c index aca0a9d..d31f004 100644 --- a/src/llist.c +++ b/src/llist.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -109,6 +109,34 @@ llist_item_t *llist_next(llist_item_t * i) } /* + * Return the predecessor of a list item or, if head, the list item itself, + * or if not in the list, NULL. + * The list item may be supplied either directly (i) or as a pointer to + * the contents (data); the first case takes precedence. + */ +static llist_item_t *llist_prev(llist_t *l, llist_item_t *i, void *data) +{ + llist_item_t *j; + + if (!i && !data) + return NULL; + + if (l->head == i || l->head->data == data) + return l->head; + + if (i) { + for (j = l->head; j; j = j->next) + if (j->next == i) + return j; + } else { + for (j = l->head; j && j->next; j = j->next) + if (j->next->data == data) + return j; + } + return NULL; +} + +/* * Return the successor of a list item if it is matched by some filter * callback. Return NULL otherwise. */ @@ -150,34 +178,94 @@ void llist_add(llist_t * l, void *data) } /* + * Insert an existing item in a sorted list. + */ +static void llist_relink(llist_t *l, llist_item_t *i, llist_fn_cmp_t fn_cmp) +{ + llist_item_t *j; + + if (!i) + return; + + i->next = NULL; + if (!l->head) { + l->head = l->tail = i; + } else if (fn_cmp(i->data, l->tail->data) >= 0) { + l->tail->next = i; + l->tail = i; + } else if (fn_cmp(i->data, l->head->data) < 0) { + i->next = l->head; + l->head = i; + } else { + j = l->head; + while (j->next && fn_cmp(i->data, j->next->data) >= 0) + j = j->next; + i->next = j->next; + j->next = i; + } +} + +/* + * Unlink an item from a list and return it. + */ +static llist_item_t *llist_unlink(llist_t *l, llist_item_t *i) +{ + llist_item_t *p; + + if (!i) + return NULL; + + p = llist_prev(l, i, NULL); + if (!p) + return NULL; + + if (i == l->tail) + l->tail = (i == l->head) ? NULL : p; + if (i == l->head) + l->head = i->next; + else + p->next = i->next; + i->next = NULL; + return i; +} + +/* + * Find an item matched by some filter callback; start from a specified item. + */ +static llist_item_t *llist_find_from(llist_item_t *i, void *data, + llist_fn_match_t fn_match) +{ + if (!i) + return NULL; + + if (fn_match) { + for (; i; i = i->next) { + if (fn_match(i->data, data)) + return i; + } + } else { + for (; i; i = i->next) { + if (i->data == data) + return i; + } + } + + return NULL; +} + +/* * Add an item to a sorted list. */ void llist_add_sorted(llist_t * l, void *data, llist_fn_cmp_t fn_cmp) { llist_item_t *o = mem_malloc(sizeof(llist_item_t)); - llist_item_t *i; if (o) { o->data = data; o->next = NULL; - - if (!l->head) { - l->head = l->tail = o; - } else if (fn_cmp(o->data, l->tail->data) >= 0) { - l->tail->next = o; - l->tail = o; - } else if (fn_cmp(o->data, l->head->data) < 0) { - o->next = l->head; - l->head = o; - } else { - i = l->head; - while (i->next - && fn_cmp(o->data, i->next->data) >= 0) - i = i->next; - o->next = i->next; - i->next = o; - } } + + llist_relink(l, o, fn_cmp); } /* @@ -209,21 +297,7 @@ void llist_remove(llist_t * l, llist_item_t * i) llist_item_t *llist_find_first(llist_t * l, void *data, llist_fn_match_t fn_match) { - llist_item_t *i; - - if (fn_match) { - for (i = l->head; i; i = i->next) { - if (fn_match(i->data, data)) - return i; - } - } else { - for (i = l->head; i; i = i->next) { - if (i->data == data) - return i; - } - } - - return NULL; + return l ? llist_find_from(l->head, data, fn_match) : NULL; } /* @@ -232,22 +306,7 @@ llist_item_t *llist_find_first(llist_t * l, void *data, llist_item_t *llist_find_next(llist_item_t * i, void *data, llist_fn_match_t fn_match) { - if (i) { - i = i->next; - if (fn_match) { - for (; i; i = i->next) { - if (fn_match(i->data, data)) - return i; - } - } else { - for (; i; i = i->next) { - if (i->data == data) - return i; - } - } - } - - return NULL; + return i ? llist_find_from(i->next, data, fn_match) : NULL; } /* @@ -261,17 +320,42 @@ llist_item_t *llist_find_nth(llist_t * l, int n, void *data, if (n < 0) return NULL; - if (fn_match) { - for (i = l->head; i; i = i->next) { - if (fn_match(i->data, data) && (n-- == 0)) - return i; - } - } else { - for (i = l->head; i; i = i->next) { - if ((i->data == data) && (n-- == 0)) - return i; - } + for (i = l->head; i; i = i->next, n--) { + i = llist_find_from(i, data, fn_match); + if (!i || !n) + return i; } return NULL; } + +/* + * Reorder a sorted linked list when an item has changed. + */ +void llist_reorder(llist_t *l, void *data, llist_fn_cmp_t fn_cmp) +{ + llist_item_t *o, *p; + + if (!(p = llist_prev(l, NULL, data))) + return; + + /* List head? */ + if (p->data == data) + o = p; + else + o = p->next; + + /* Sorted already? + * Note: p is either the previous element or o itself. + */ + if (o->next && + fn_cmp(p->data, o->data) <= 0 && + fn_cmp(o->data, o->next->data) <= 0) + return; + if (!o->next && + fn_cmp(p->data, o->data) <= 0) + return; + + /* Link manipulation only. */ + llist_relink(l, llist_unlink(l, o), fn_cmp); +} diff --git a/src/llist.h b/src/llist.h index 9533819..0dd15bf 100644 --- a/src/llist.h +++ b/src/llist.h @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -99,8 +99,11 @@ void *llist_get_data(llist_item_t *); void llist_add(llist_t *, void *); void llist_add_sorted(llist_t *, void *, llist_fn_cmp_t); void llist_remove(llist_t *, llist_item_t *); +void llist_reorder(llist_t *, void *, llist_fn_cmp_t); #define LLIST_ADD(l, data) llist_add(l, data) #define LLIST_ADD_SORTED(l, data, fn_cmp) \ llist_add_sorted(l, data, (llist_fn_cmp_t)fn_cmp) #define LLIST_REMOVE(l, i) llist_remove(l, i) +#define LLIST_REORDER(l, data, fn_cmp) \ + llist_reorder(l, data, (llist_fn_cmp_t)fn_cmp) diff --git a/src/llist_ts.h b/src/llist_ts.h index 6597cde..a4b6184 100644 --- a/src/llist_ts.h +++ b/src/llist_ts.h @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -90,3 +90,5 @@ struct llist_ts { #define LLIST_TS_REMOVE(l_ts, i) llist_remove ((llist_t *)l_ts, i) #define LLIST_TS_ADD_SORTED(l_ts, data, fn_cmp) \ llist_add_sorted ((llist_t *)l_ts, data, (llist_fn_cmp_t)fn_cmp) +#define LLIST_TS_REORDER(l_ts, data, fn_cmp) \ + llist_reorder((llist_t *)l_ts, data, (llist_fn_cmp_t)fn_cmp) @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -94,9 +94,7 @@ void edit_note(char **note, const char *editor) const char *arg[] = { editor, tmppath, NULL }; wins_launch_external(arg); - if (io_file_is_empty(tmppath) > 0) { - erase_note(note); - } else if ((fp = fopen(tmppath, "r"))) { + if ((fp = fopen(tmppath, "r"))) { sha1_stream(fp, sha1); fclose(fp); *note = sha1; @@ -155,6 +153,17 @@ void note_read(char *buffer, FILE * fp) buffer[MAX_NOTESIZ] = '\0'; } +/* Read the contents of a note file */ +void note_read_contents(char *buffer, size_t buffer_len, FILE * fp) +{ + size_t read_count = fread(buffer, 1, buffer_len, fp); + if (read_count != buffer_len) + buffer[read_count] = '\0'; + else + memcpy(&buffer[buffer_len - 4], "...\0", 4); +} + + static void note_gc_extract_key(struct note_gc_hash *data, const char **key, int *len) { diff --git a/src/notify.c b/src/notify.c index a24beb2..6eda361 100644 --- a/src/notify.c +++ b/src/notify.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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,6 +34,7 @@ * */ +#include <sys/wait.h> #include <time.h> #include <stdlib.h> #include <string.h> @@ -136,9 +137,6 @@ void notify_init_vars(void) strncpy(nbar.cmd, cmd, BUFSIZ); nbar.cmd[BUFSIZ - 1] = '\0'; - if ((nbar.shell = getenv("SHELL")) == NULL) - nbar.shell = "/bin/sh"; - nbar.notify_all = 0; pthread_attr_init(&detached_thread_attr); @@ -215,26 +213,18 @@ void notify_reinit_bar(void) /* Launch user defined command as a notification. */ unsigned notify_launch_cmd(void) { - int pid; + char const *arg[2] = { nbar.cmd, NULL }; + int pid, pin, pout, perr; if (notify_app.state & APOINT_NOTIFIED) return 1; notify_app.state |= APOINT_NOTIFIED; - pid = fork(); - - if (pid < 0) { - ERROR_MSG(_("error while launching command: could not fork")); - return 0; - } else if (pid == 0) { - /* Child: launch user defined command */ - if (execlp(nbar.shell, nbar.shell, "-c", nbar.cmd, NULL) < - 0) { - ERROR_MSG(_("error while launching command")); - _exit(1); - } - _exit(0); + if ((pid = shell_exec(&pin, &pout, &perr, 1, *arg, arg))) { + close(pin); + close(pout); + close(perr); } return 1; @@ -358,6 +348,9 @@ static void *notify_main_thread(void *arg) pthread_mutex_unlock(¬ify.mutex); notify_update_bar(); psleep(thread_sleep); + /* Reap the user-defined notifications. */ + while (waitpid(0, NULL, WNOHANG) > 0) + ; elapse += thread_sleep; if (elapse >= check_app) { elapse = 0; @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -102,8 +102,8 @@ static void pcal_export_header(FILE * stream) { fputs("# calcurse pcal export\n", stream); fputs("\n# =======\n# options\n# =======\n", stream); - fprintf(stream, "opt -A -K -l -m -F %s\n", - ui_calendar_week_begins_on_monday()? "Monday" : "Sunday"); + fprintf(stream, "opt -A -K -l -m -F %s\n", get_wday_default_string( + ui_calendar_get_wday_start())); fputs("# Display week number (i.e. 1-52) on every Monday\n", stream); fprintf(stream, "all monday in all week %%w\n"); diff --git a/src/queue.c b/src/queue.c index a00ebad..41cb69b 100644 --- a/src/queue.c +++ b/src/queue.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 diff --git a/src/recur.c b/src/recur.c index 997fb15..12f76b8 100644 --- a/src/recur.c +++ b/src/recur.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -517,9 +517,10 @@ char *recur_apoint_scan(FILE *f, struct tm start, struct tm end, /* 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"); + DAY(tstart), NULL)) { + char *fmt = _("recurrence error: not on start day (%s)"); + return day_ins(&fmt, tstart); + } /* Filter item. */ if (filter) { @@ -588,9 +589,10 @@ char *recur_event_scan(FILE * f, struct tm start, int id, /* 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"); + DAY(tstart), NULL)) { + char *fmt = _("recurrence error: not on start day (%s)"); + return day_ins(&fmt, tstart); + } /* Filter item. */ if (filter) { @@ -1001,14 +1003,20 @@ static int find_occurrence(time_t start, long dur, struct rpt *rpt, llist_t *exc if (rpt->until && t >= NEXTDAY(rpt->until)) return 0; - /* Does it span the given day? */ - if (t + DUR(t) < day) + /* Does it span the given day? + * + * NOTE: An appointment ending at 00:00 is not considered to span the + * given day, unless the appointment is an appointment without + * specified end time, which is internally treated as appointment with + * duration 0. + */ + if (t + DUR(t) >= day || (t == day && dur == 0)) { + if (occurrence) + *occurrence = t; + return 1; + } else { return 0; - - if (occurrence) - *occurrence = t; - - return 1; + } } #undef DUR @@ -1173,7 +1181,7 @@ static int expand_monthly(time_t start, long dur, struct rpt *rpt, llist_t *exc, LLIST_FOREACH(&rpt->bywday, i) { w = LLIST_GET_DATA(i); - int order, wday; + int order, wday, nbwd; localtime_r(&start, &tm_start); /* @@ -1188,6 +1196,11 @@ static int expand_monthly(time_t start, long dur, struct rpt *rpt, llist_t *exc, */ order = *w / WEEKINDAYS; wday = *w % WEEKINDAYS; + nbwd = wday_per_month(tm_day.tm_mon + 1, + tm_day.tm_year + 1900, + wday); + if (nbwd < order) + return 0; r.freq = order; tm_start.tm_mday = 1; tm_start.tm_mon = tm_day.tm_mon; @@ -1200,7 +1213,7 @@ static int expand_monthly(time_t start, long dur, struct rpt *rpt, llist_t *exc, -WEEKINDAYS ); r.until = date_sec_change( - update_time_in_date(nstart, 0, 0), + DAY(nstart), 0, r.freq * WEEKINDAYS ); @@ -1218,11 +1231,12 @@ static int expand_monthly(time_t start, long dur, struct rpt *rpt, llist_t *exc, */ order = -(*w) / WEEKINDAYS; wday = -(*w) % WEEKINDAYS; - r.freq = wday_per_month( - tm_day.tm_mon + 1, - tm_day.tm_year + 1900, - wday - ) - order + 1; + nbwd = wday_per_month(tm_day.tm_mon + 1, + tm_day.tm_year + 1900, + wday); + if (nbwd < order) + return 0; + r.freq = nbwd - order + 1; tm_start.tm_mday = 1; tm_start.tm_mon = tm_day.tm_mon; tm_start.tm_year = tm_day.tm_year; @@ -1233,7 +1247,7 @@ static int expand_monthly(time_t start, long dur, struct rpt *rpt, llist_t *exc, -WEEKINDAYS ); r.until = date_sec_change( - update_time_in_date(nstart, 0, 0), + DAY(nstart), 0, r.freq * WEEKINDAYS ); @@ -1259,7 +1273,7 @@ static int expand_yearly(time_t start, long dur, struct rpt *rpt, llist_t *exc, { struct tm tm_start, tm_day; llist_item_t *i, *j; - int *m, *w, mday, wday, order; + int *m, *w, mday, wday, order, nbwd; time_t nstart; struct rpt r; @@ -1312,6 +1326,19 @@ static int expand_yearly(time_t start, long dur, struct rpt *rpt, llist_t *exc, */ order = *w / WEEKINDAYS; wday = *w % WEEKINDAYS; + if (rpt->bymonth.head) + nbwd = wday_per_month( + tm_day.tm_mon + 1, + tm_day.tm_year + 1900, + wday + ); + else + nbwd = wday_per_year( + tm_day.tm_year + 1900, + wday + ); + if (nbwd < order) + return 0; r.freq = order; tm_start.tm_mday = 1; if (rpt->bymonth.head) @@ -1326,7 +1353,7 @@ static int expand_yearly(time_t start, long dur, struct rpt *rpt, llist_t *exc, -WEEKINDAYS ); r.until = date_sec_change( - update_time_in_date(nstart, 0, 0), + DAY(nstart), 0, r.freq * WEEKINDAYS ); @@ -1344,21 +1371,25 @@ static int expand_yearly(time_t start, long dur, struct rpt *rpt, llist_t *exc, */ order = -(*w) / WEEKINDAYS; wday = -(*w) % WEEKINDAYS; - if (rpt->bymonth.head) { - r.freq = wday_per_month( - tm_day.tm_mon + 1, - tm_day.tm_year + 1900, - wday - ) - order + 1; + if (rpt->bymonth.head) + nbwd = wday_per_month( + tm_day.tm_mon + 1, + tm_day.tm_year + 1900, + wday + ); + else + nbwd = wday_per_year( + tm_day.tm_year + 1900, + wday + ); + if (nbwd < order) + return 0; + r.freq = nbwd - order + 1; + tm_start.tm_mday = 1; + if (rpt->bymonth.head) tm_start.tm_mon = tm_day.tm_mon; - } else { - r.freq = wday_per_year( - tm_day.tm_year + 1900, - wday - ) - order + 1; + else tm_start.tm_mon = 0; - } - tm_start.tm_mday = 1; tm_start.tm_year = tm_day.tm_year; tm_start.tm_isdst = -1; nstart = date_sec_change( @@ -1367,7 +1398,7 @@ static int expand_yearly(time_t start, long dur, struct rpt *rpt, llist_t *exc, -WEEKINDAYS ); r.until = date_sec_change( - update_time_in_date(nstart, 0, 0), + DAY(nstart), 0, r.freq * WEEKINDAYS ); @@ -1778,18 +1809,66 @@ void recur_apoint_paste_item(struct recur_apoint *rapt, time_t date) * buffer. Useful for test of a repeated item. */ int recur_next_occurrence(time_t s, long d, struct rpt *r, llist_t *e, - time_t occur, time_t *next) + time_t day, time_t *next) { int ret = 0; - if (r->until && r->until <= occur) + if (r->until && r->until <= day) return ret; - while (!r->until || occur < r->until) { - occur = NEXTDAY(occur); - if (!check_sec(&occur)) + while (!r->until || day < r->until) { + day = NEXTDAY(day); + if (!check_sec(&day)) + break; + if (recur_item_find_occurrence(s, d, r, e, day, next)) { + /* Multi-day appointment. */ + if (*next < day) + continue; + ret = 1; + break; + } + } + return ret; +} + +/* + * Finds the nth occurrence (incl. start) of a recurrence rule (s, d, r, e) + * and returns it in the provided buffer. + */ +int recur_nth_occurrence(time_t s, long d, struct rpt *r, llist_t *e, int n, + time_t *nth) +{ + time_t day; + + if (n <= 0) + return 0; + + for (n--, *nth = s; n > 0; n--) { + day = DAY(*nth); + if (!recur_next_occurrence(s, d, r, e, day, nth)) break; - if (recur_item_find_occurrence(s, d, r, e, occur, next)) { + } + return !n; +} + +/* + * Finds the previous occurrence - the most recent before day - and returns it + * in the provided buffer. + */ +int recur_prev_occurrence(time_t s, long d, struct rpt *r, llist_t *e, + time_t day, time_t *prev) +{ + int ret = 0; + + if (day <= DAY(s)) + return ret; + + while (DAY(s) < day) { + day = PREVDAY(day); + if (recur_item_find_occurrence(s, d, r, e, day, prev)) { + /* Multi-day appointment. */ + if (d != -1 && *prev < day && day < *prev + d) + continue; ret = 1; break; } @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -43,6 +43,7 @@ #include <stdlib.h> #include <string.h> +#include "config.h" #include "sha1.h" #define rol(val, n) (((val) << (n)) | ((val) >> (32 - (n)))) @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -52,6 +52,6 @@ typedef struct { void sha1_init(sha1_ctx_t *); void sha1_update(sha1_ctx_t *, const uint8_t *, unsigned int); -void sha1_final(sha1_ctx_t *, uint8_t *); +void sha1_final(sha1_ctx_t *, uint8_t[SHA1_DIGESTLEN]); void sha1_digest(const char *, char *); void sha1_stream(FILE *, char *); @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -68,27 +68,18 @@ /* * General signal handling routine. - * Catch return values from children (user-defined notification commands). - * This is needed to avoid zombie processes running on system. - * Also catch CTRL-C (SIGINT), and SIGWINCH to resize screen automatically. + * Catch SIGWINCH to resize screen automatically. */ static void generic_hdlr(int sig) { switch (sig) { - case SIGCHLD: - while (waitpid(WAIT_MYPGRP, NULL, WNOHANG) > 0) ; - break; case SIGWINCH: resize = 1; clearok(curscr, TRUE); ungetch(KEY_RESIZE); break; case SIGTERM: - if (unlink(path_cpid) != 0) { - EXIT(_("Could not remove calcurse lock file: %s\n"), - strerror(errno)); - } - exit(EXIT_SUCCESS); + exit_calcurse(EXIT_SUCCESS); break; case SIGUSR1: want_reload = 1; @@ -117,12 +108,11 @@ unsigned sigs_set_hdlr(int sig, void (*handler) (int)) /* Signal handling init. */ void sigs_init() { - if (!sigs_set_hdlr(SIGCHLD, generic_hdlr) - || !sigs_set_hdlr(SIGWINCH, generic_hdlr) + if (!sigs_set_hdlr(SIGWINCH, generic_hdlr) || !sigs_set_hdlr(SIGTERM, generic_hdlr) || !sigs_set_hdlr(SIGUSR1, generic_hdlr) || !sigs_set_hdlr(SIGINT, SIG_IGN)) - exit_calcurse(1); + exit_calcurse(EXIT_FAILURE); } /* Ignore SIGWINCH and SIGTERM signals. */ diff --git a/src/strings.c b/src/strings.c index b3dc1c4..45c9310 100644 --- a/src/strings.c +++ b/src/strings.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -158,9 +158,7 @@ void todo_delete(struct todo *todo) */ void todo_resort(struct todo *t) { - llist_item_t *i = LLIST_FIND_FIRST(&todolist, t, NULL); - LLIST_REMOVE(&todolist, i); - LLIST_ADD_SORTED(&todolist, t, todo_cmp); + LLIST_REORDER(&todolist, t, todo_cmp); } /* Flag a todo item. */ diff --git a/src/ui-calendar.c b/src/ui-calendar.c index 4a8ef44..c1719d8 100644 --- a/src/ui-calendar.c +++ b/src/ui-calendar.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -45,14 +45,14 @@ #include "calcurse.h" static struct date today, slctd_day; -static unsigned ui_calendar_view, week_begins_on_monday; +static unsigned ui_calendar_view; +static int wday_start; /* this is used in signed arithmetic */ static pthread_mutex_t date_thread_mutex = PTHREAD_MUTEX_INITIALIZER; -static void draw_monthly_view(struct scrollwin *, struct date *, unsigned); -static void draw_weekly_view(struct scrollwin *, struct date *, unsigned); -static void (*draw_calendar[CAL_VIEWS]) (struct scrollwin *, struct date *, - unsigned) = { -draw_monthly_view, draw_weekly_view}; +static void draw_monthly_view(struct scrollwin *, struct date *); +static void draw_weekly_view(struct scrollwin *, struct date *); +static void (*draw_calendar[CAL_VIEWS]) (struct scrollwin *, + struct date *) = {draw_monthly_view, draw_weekly_view}; /* Six weeks cover a month. */ static int monthly_view_cache[WEEKINDAYS * 6]; @@ -148,30 +148,26 @@ struct date *ui_calendar_get_today(void) /* Needed to display sunday or monday as the first day of week in calendar. */ void ui_calendar_set_first_day_of_week(enum wday first_day) { - switch (first_day) { - case SUNDAY: - week_begins_on_monday = 0; - break; - case MONDAY: - week_begins_on_monday = 1; - break; - default: + if (first_day >= 0 && first_day <= 6) + wday_start = first_day; + else { ERROR_MSG(_("ERROR setting first day of week")); - week_begins_on_monday = 0; - /* NOTREACHED */ + wday_start = 0; } } /* Swap first day of week in calendar. */ void ui_calendar_change_first_day_of_week(void) { - week_begins_on_monday = !week_begins_on_monday; + wday_start++; + if(wday_start >= WEEKINDAYS) + wday_start = 0; } /* Return 1 if week begins on monday, 0 otherwise. */ -unsigned ui_calendar_week_begins_on_monday(void) +int ui_calendar_get_wday_start(void) { - return week_begins_on_monday; + return wday_start; } /* Fill in the given variable with the current date. */ @@ -219,18 +215,14 @@ void ui_calendar_monthly_view_cache_set_invalid(void) monthly_view_cache_valid = 0; } -static int weeknum(const struct tm *t, int firstweekday) +static int weeknum(const struct tm *t, int wday_start) { int wday, wnum; wday = t->tm_wday; - if (firstweekday == MONDAY) { - if (wday == SUNDAY) - wday = 6; - else - wday--; - } - wnum = ((t->tm_yday + WEEKINDAYS - wday) / WEEKINDAYS); + wnum = ((t->tm_yday + WEEKINDAYS + -modify_wday(wday, -wday_start)) + / WEEKINDAYS); + if (wnum < 0) wnum = 0; @@ -296,7 +288,7 @@ static int ISO8601weeknum(const struct tm *t) * Return the tm structure for the first day of the first week * (containing a day) of the selected month. */ -static struct tm get_first_day(unsigned sunday_first) +static struct tm get_first_day(int wday_start) { struct tm t; struct date d; @@ -308,26 +300,20 @@ static struct tm get_first_day(unsigned sunday_first) t = date2tm(d, 0, 0); mktime(&t); /* get the first day of the week */ - date_change(&t, 0, - -(sunday_first ? - t.tm_wday : - (t.tm_wday + WEEKINDAYS - 1) % WEEKINDAYS)); + date_change(&t, 0, -modify_wday(t.tm_wday, -wday_start)); + return t; } -static struct tm get_first_weekday(unsigned sunday_first) +static struct tm get_first_weekday(int wday_start) { - int c_wday, days_to_remove; + int c_wday; struct tm t; c_wday = ui_calendar_get_wday(&slctd_day); - if (sunday_first) - days_to_remove = c_wday; - else - days_to_remove = c_wday == 0 ? WEEKINDAYS - 1 : c_wday - 1; - t = date2tm(slctd_day, 0, 0); - date_change(&t, 0, -days_to_remove); + + date_change(&t, 0, -modify_wday(c_wday, -wday_start)); return t; } @@ -346,8 +332,7 @@ static void draw_week_number(struct scrollwin *sw, struct tm t) /* Draw the monthly view inside calendar panel. */ static void -draw_monthly_view(struct scrollwin *sw, struct date *current_day, - unsigned sunday_first) +draw_monthly_view(struct scrollwin *sw, struct date *current_day) { struct date c_day; int slctd, w_day, numdays, j, week = 0; @@ -373,7 +358,7 @@ draw_monthly_view(struct scrollwin *sw, struct date *current_day, * Step forward by week until past the last day of the month. * The first day of the first week may belong to the previous month. */ - t = t_first = get_first_day(sunday_first); + t = t_first = get_first_day(wday_start); t.tm_mday += WEEKINDAYS; mktime(&t); last_day += WEEKINDAYS; @@ -423,7 +408,7 @@ draw_monthly_view(struct scrollwin *sw, struct date *current_day, custom_apply_attr(sw->inner, ATTR_HIGHEST); for (j = 0; j < WEEKINDAYS; j++) { mvwaddstr(sw->inner, ofs_y, ofs_x + weekw + 4 * j, - nl_langinfo(ABDAY_1 + (1 + j - sunday_first) % WEEKINDAYS)); + nl_langinfo(ABDAY_1 + modify_wday(j, wday_start))); } custom_remove_attr(sw->inner, ATTR_HIGHEST); WINS_CALENDAR_UNLOCK; @@ -449,11 +434,9 @@ draw_monthly_view(struct scrollwin *sw, struct date *current_day, if (j == first_day || (mo == 1 && j == WEEKINDAYS) || (mo == 12 && j >= 4 * WEEKINDAYS)) { - if (sunday_first) - date_change(&t, 0, 1); + date_change(&t, 0, WDAY(MONDAY)); week = ISO8601weeknum(&t); - if (sunday_first) - date_change(&t, 0, -1); + date_change(&t, 0, -WDAY(MONDAY)); } else week++; } @@ -506,8 +489,7 @@ draw_monthly_view(struct scrollwin *sw, struct date *current_day, /* Draw the weekly view inside calendar panel. */ static void -draw_weekly_view(struct scrollwin *sw, struct date *current_day, - unsigned sunday_first) +draw_weekly_view(struct scrollwin *sw, struct date *current_day) { #define DAYSLICESNO 6 const int WCALWIDTH = 28; @@ -520,14 +502,14 @@ draw_weekly_view(struct scrollwin *sw, struct date *current_day, OFFX = (wins_sbar_width() - 2 - WCALWIDTH) / 2; /* Print the week number, calculated from monday. */ - t = get_first_weekday(0); + t = get_first_weekday(MONDAY); draw_week_number(sw, t); /* Now draw calendar view. */ for (j = 0; j < WEEKINDAYS; j++) { /* get next day */ if (j == 0) - t = get_first_weekday(sunday_first); + t = get_first_weekday(wday_start); else date_change(&t, 0, 1); @@ -538,7 +520,7 @@ draw_weekly_view(struct scrollwin *sw, struct date *current_day, /* print the day names, with regards to the first day of the week */ custom_apply_attr(sw->inner, ATTR_HIGHEST); mvwaddstr(sw->inner, OFFY, OFFX + 4 * j, - nl_langinfo(ABDAY_1 + (1 + j - sunday_first) % WEEKINDAYS)); + nl_langinfo(ABDAY_1 + modify_wday(j, wday_start))); custom_remove_attr(sw->inner, ATTR_HIGHEST); /* Check if the day to be printed has an item or not. */ @@ -578,9 +560,8 @@ draw_weekly_view(struct scrollwin *sw, struct date *current_day, if (j != WEEKINDAYS - 1 && i != DAYSLICESNO - 1) { WINS_CALENDAR_LOCK; - mvwhline(sw->inner, OFFY + 2 + i, - OFFX + 3 + 4 * j, ACS_S9, - 2); + mvwaddstr(sw->inner, OFFY + 2 + i, + OFFX + 3 + 4 * j, "__"); WINS_CALENDAR_UNLOCK; } if (slices[i]) { @@ -611,9 +592,9 @@ draw_weekly_view(struct scrollwin *sw, struct date *current_day, /* Draw marks to indicate midday on the sides of the calendar. */ WINS_CALENDAR_LOCK; custom_apply_attr(sw->inner, ATTR_HIGHEST); - mvwhline(sw->inner, OFFY + 1 + DAYSLICESNO / 2, OFFX, ACS_S9, 1); - mvwhline(sw->inner, OFFY + 1 + DAYSLICESNO / 2, - OFFX + WCALWIDTH - 1, ACS_S9, 1); + mvwaddch(sw->inner, OFFY + 1 + DAYSLICESNO / 2, OFFX, '<'); + mvwaddch(sw->inner, OFFY + 1 + DAYSLICESNO / 2, + OFFX + WCALWIDTH - 1, '>'); custom_remove_attr(sw->inner, ATTR_HIGHEST); WINS_CALENDAR_UNLOCK; @@ -624,11 +605,9 @@ draw_weekly_view(struct scrollwin *sw, struct date *current_day, void ui_calendar_update_panel(void) { struct date current_day; - unsigned sunday_first; ui_calendar_store_current_date(¤t_day); - sunday_first = !ui_calendar_week_begins_on_monday(); - draw_calendar[ui_calendar_view] (&sw_cal, ¤t_day, sunday_first); + draw_calendar[ui_calendar_view] (&sw_cal, ¤t_day); wins_scrollwin_display(&sw_cal, NOHILT); } @@ -728,28 +707,14 @@ void ui_calendar_move(enum move move, int count) ret = date_change(&t, count * YEARINMONTHS, 0); break; case WEEK_START: - /* Normalize struct tm to get week day number. */ mktime(&t); - if (ui_calendar_week_begins_on_monday()) - days_to_remove = - ((t.tm_wday == - 0) ? WEEKINDAYS - 1 : t.tm_wday - 1); - else - days_to_remove = - ((t.tm_wday == 0) ? 0 : t.tm_wday); + days_to_remove = WDAY(t.tm_wday); days_to_remove += (count - 1) * WEEKINDAYS; ret = date_change(&t, 0, -days_to_remove); break; case WEEK_END: mktime(&t); - if (ui_calendar_week_begins_on_monday()) - days_to_add = - ((t.tm_wday == - 0) ? 0 : WEEKINDAYS - t.tm_wday); - else - days_to_add = ((t.tm_wday == 0) ? - WEEKINDAYS - 1 : WEEKINDAYS - 1 - - t.tm_wday); + days_to_add = modify_wday(-t.tm_wday, wday_start - 1); days_to_add += (count - 1) * WEEKINDAYS; ret = date_change(&t, 0, days_to_add); break; diff --git a/src/ui-day.c b/src/ui-day.c index 5a8d960..6a038fa 100644 --- a/src/ui-day.c +++ b/src/ui-day.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -79,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); } /* @@ -175,8 +175,8 @@ static void update_start_time(time_t *start, long *dur, struct rpt *rpt, int mov time_t newtime; const char *msg_wrong_time = _("Invalid time: start time must come before end time!"); - const char *msg_match = - _("Repetition must begin on start day."); + char *msg_match = + _("Repetition must begin on start day (%s)."); const char *msg_enter = _("Press [Enter] to continue"); char *msg; @@ -184,11 +184,12 @@ static void update_start_time(time_t *start, long *dur, struct rpt *rpt, int mov newtime = day_edit_time(*start, *dur, move); if (!newtime) break; - if (rpt && !recur_item_find_occurrence( - newtime, *dur, rpt, NULL, - update_time_in_date(newtime, 0, 0), - NULL)) { - msg = (char *)msg_match; + 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 (move) { *start = newtime; @@ -200,9 +201,8 @@ static void update_start_time(time_t *start, long *dur, struct rpt *rpt, int mov break; } } - msg = (char *)msg_wrong_time; + status_mesg(msg_wrong_time, msg_enter); } - status_mesg(msg, msg_enter); keys_wgetch(win[KEY].p); } return; @@ -490,22 +490,22 @@ static void help_ilist(int_list_t list, int rule) { char *msg1 = ""; char *msg2 = ""; - char *byday_w_d = _("only on these weekdays"); - char *byday_w_w = _("also on these weekdays"); + char *byday_w_d = _("Limit repetition to listed days."); + char *byday_w_w = _("Expand repetition to listed days."); char *byday_m_m_1 = - _("also on these weekdays of month - or only on given monthdays"); + _("Expand repetition to listed days, either all or 1st, 2nd, ... of month."); char *byday_m_m_2 = - _("either all weekdays or 1st, 2nd, ... weekday or 1st, 2nd, ... from the end"); + _("Note: limit to monthdays, if any."); char *byday_y_y_1 = - _("also on these weekdays of year or given months - or only on given monthdays"); + _("Expand repetition to listed days, either all or 1st, 2nd, ... of year."); char *byday_y_y_2 = - _("positive: 1st, 2nd,... weekday of month or year, negative: 1st, 2nd,... from end"); + _("Note: expand to listed months, if any; limit to monthdays, if any."); char *bymonth_dwm = - _("only in these months"); + _("Limit repetition to listed months."); char *bymonth_y = - _("also in these months"); - char *bymonthday_d = _("only on these days of the month"); - char *bymonthday_my = _("also on these days of the month"); + _("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) { @@ -586,13 +586,13 @@ 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), '?' for help:"); + char *wday_w = _("Weekdays %s|..|%s, space-separated list, '?' for help:"); char *wday_m = - _("Weekdays (%1$s|..|%2$s or 1%1$s|..|5%2$s or -1%1$s|..|-5%2$s), '?' for help:"); + _("Weekdays [n]%s|..|[n]%s, space-separated list, n=1,-1,..,5,-5, '?' for help:"); char *wday_y = - _("Weekdays (%1$s|..|%2$s or 1%1$s|..|53%2$s or -1%1$s|..|-53%2$s), '?' for help:"); - char *month = _("Months (1..12), '?' for help:"); - char *mday = _("Monthdays (1..31 or -1..-31), '?' for help:"); + _("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; @@ -657,8 +657,9 @@ static int edit_ilist(llist_t *ilist, int_list_t list_type, int rule_type) static int update_rept(time_t start, long dur, struct rpt **rpt, llist_t *exc, int simple) { - int updated = 0; + int updated = 0, count; struct rpt nrpt; + time_t until; char *types = NULL; char *freqstr = NULL; char *timstr = NULL; @@ -671,11 +672,11 @@ static int update_rept(time_t start, long dur, struct rpt **rpt, llist_t *exc, LLIST_INIT(&nrpt.bymonthday); /* Edit repetition type. */ - const char *msg_prefix = _("Repetition type:"); - const char *daily = _("(d)aily"); - const char *weekly = _("(w)eekly"); - const char *monthly = _("(m)onthly"); - const char *yearly = _("(y)early"); + 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. */ @@ -697,30 +698,34 @@ static int update_rept(time_t start, long dur, struct rpt **rpt, llist_t *exc, /* New item. */ current = ""; } - asprintf(&types, "%s %s, %s, %s, %s?", + asprintf(&types, "%s %s/%s/%s/%s?", msg_prefix, daily, weekly, monthly, yearly); if (current[0]) - asprintf(&types, "%s (now %s)", types, current); + asprintf(&types, "%s [%s]", types, current); switch (status_ask_choice(types, dwmy, 4)) { case 1: - nrpt.type = 'D'; + nrpt.type = recur_char2def('D'); break; case 2: - nrpt.type = 'W'; + nrpt.type = recur_char2def('W'); break; case 3: - nrpt.type = 'M'; + nrpt.type = recur_char2def('M'); break; case 4: - nrpt.type = 'Y'; + nrpt.type = recur_char2def('Y'); break; + case -2: /* user typed RETURN */ + if (current[0]) { + nrpt.type = (*rpt)->type; + break; + } default: goto cleanup; } - nrpt.type = recur_char2def(nrpt.type); /* Edit frequency. */ - const char *msg_freq = _("Repetition frequency:"); + const char *msg_freq = _("Frequency:"); const char *msg_inv_freq = _("Invalid frequency."); do { status_mesg(msg_freq, ""); @@ -740,16 +745,19 @@ static int update_rept(time_t start, long dur, struct rpt **rpt, llist_t *exc, /* Edit until date. */ const char *msg_until_1 = - _("Until 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."); + _("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)); @@ -771,16 +779,27 @@ static int update_rept(time_t start, long dur, struct rpt **rpt, llist_t *exc, } if (*timstr == '+') { unsigned days; - if (!parse_date_duration(timstr + 1, &days, start)) { + 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. */ - nrpt.until = 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, @@ -793,7 +812,7 @@ static int update_rept(time_t start, long dur, struct rpt **rpt, llist_t *exc, nrpt.until = date2sec(d, 0, 0); } /* Conmpare days (midnights) - until-day may equal start day. */ - if (nrpt.until >= update_time_in_date(start, 0, 0)) + if (nrpt.until >= DAY(start)) break; mem_free(timstr); @@ -852,18 +871,31 @@ static int update_rept(time_t start, long dur, struct rpt **rpt, llist_t *exc, 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). */ - 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), + 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)) { - status_mesg(msg_match, msg_cont); + mem_free(outstr); + outstr = day_ins(&msg_match, start); + status_mesg(outstr, msg_cont); keys_wgetch(win[KEY].p); goto cleanup; } @@ -1035,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) { @@ -1056,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(); @@ -1192,77 +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(reg); - /* 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(); } @@ -1480,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); diff --git a/src/ui-todo.c b/src/ui-todo.c index b546720..46933b3 100644 --- a/src/ui-todo.c +++ b/src/ui-todo.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -158,11 +158,11 @@ void ui_todo_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"); todo_write(item, fpout); fclose(fpout); - child_wait(NULL, &pout, pid); + child_wait(NULL, &pout, NULL, pid); press_any_key(); } wins_unprepare_external(); @@ -207,14 +207,16 @@ void ui_todo_draw(int n, WINDOW *win, int y, int hilt, void *cb_data) if (hilt) custom_apply_attr(win, ATTR_HIGHEST); - if (utf8_strwidth(todo->mesg) < width) { - mesg = todo->mesg; - } else { + mesg = todo->mesg; + if (mesg[0] == '\0') + mesg = EMPTY_EVENT_DESC_DEFAULT; + + if (utf8_strwidth(mesg) >= width) { width -= 3; - for (j = 0; todo->mesg[j] && width > 0; j++) { - if (!UTF8_ISCONT(todo->mesg[j])) - width -= utf8_width(&todo->mesg[j]); - buf[j] = todo->mesg[j]; + for (j = 0; mesg[j] && width > 0; j++) { + if (!UTF8_ISCONT(mesg[j])) + width -= utf8_width(&mesg[j]); + buf[j] = mesg[j]; } if (j) { buf[j - 1] = '.'; @@ -314,7 +316,32 @@ void ui_todo_popup_item(void) if (!item) return; - item_in_popup(NULL, NULL, item->mesg, _("TODO:")); + if (item->note) { + /* Assign a sane default note size that will cleanly + * truncate long notes */ + const char *note_heading = _("Note:"); + size_t note_size = 3500; + char note[note_size]; + char *notepath, *msg; + FILE *fp; + + asprintf(¬epath, "%s%s", path_notes, item->note); + fp = fopen(notepath, "r"); + if (fp == NULL) { + item_in_popup(NULL, NULL, item->mesg, _("TODO:")); + return; + } + + note_read_contents(note, note_size, fp); + fclose(fp); + mem_free(notepath); + + asprintf(&msg, "%s\n\n%s\n%s", item->mesg, note_heading, note); + item_in_popup(NULL, NULL, msg, _("TODO:")); + mem_free(msg); + } else { + item_in_popup(NULL, NULL, item->mesg, _("TODO:")); + } } void ui_todo_flag(void) @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 diff --git a/src/utils.c b/src/utils.c index 5322db1..2d30bfc 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -235,6 +235,8 @@ int status_ask_choice(const char *message, const char choice[], return i + 1; if (ch == ESCAPE) return (-1); + if (ch == RETURN) + return (-2); if (resize) { resize = 0; wins_reset(); @@ -421,22 +423,25 @@ struct date sec2date(time_t t) return d; } -time_t utcdate2sec(struct date day, unsigned hour, unsigned min) +time_t tzdate2sec(struct date day, unsigned hour, unsigned min, char *tznew) { - char *tz; + char *tzold; time_t t; - tz = getenv("TZ"); - if (tz) - tz = mem_strdup(tz); - setenv("TZ", "", 1); + if (!tznew) + return date2sec(day, hour, min); + + tzold = getenv("TZ"); + if (tzold) + tzold = mem_strdup(tzold); + setenv("TZ", tznew, 1); tzset(); t = date2sec(day, hour, min); - if (tz) { - setenv("TZ", tz, 1); - mem_free(tz); + if (tzold) { + setenv("TZ", tzold, 1); + mem_free(tzold); } else { unsetenv("TZ"); } @@ -621,6 +626,42 @@ long min2sec(unsigned minutes) return minutes * MININSEC; } +int modify_wday(int wday, int shift) +{ + return (WEEKINDAYS + wday + shift) % WEEKINDAYS; +} + +/* returns char* representing a wday, used for internal functions */ +char *get_wday_default_string(int wday) +{ + switch(wday) { + case MONDAY: + return "Monday"; + break; + case TUESDAY: + return "Tuesday"; + break; + case WEDNESDAY: + return "Wednesday"; + break; + case THURSDAY: + return "Thursday"; + break; + case FRIDAY: + return "Friday"; + break; + case SATURDAY: + return "Saturday"; + break; + case SUNDAY: + return "Sunday"; + break; + default: + return "Sunday"; + break; + } +} + /* * Display a scroll bar when there are so many items that they * can not be displayed inside the corresponding panel. @@ -985,11 +1026,11 @@ parse_date_interactive(const char *datestr, int *year, int *month, int *day) } /* - * Convert a date duration string into a number of days. + * Convert a date increment string into a number of days. * If start is non-zero, the final end time is validated. * * Allowed formats in lenient BNF: - * <duration> ::= <days> | <period> + * <increment>::= <days> | <period> * <period> ::= [ <weeks>w ][ <days>d ] * Notes: * <days> and <weeks> are any integer >= 0. @@ -997,7 +1038,7 @@ parse_date_interactive(const char *datestr, int *year, int *month, int *day) * * Returns 1 on success and 0 on failure. */ -int parse_date_duration(const char *string, unsigned *days, time_t start) +int parse_date_increment(const char *string, unsigned *days, time_t start) { enum { STATE_INITIAL, @@ -1007,7 +1048,7 @@ int parse_date_duration(const char *string, unsigned *days, time_t start) const char *p; unsigned in = 0, frac = 0, denom = 1; - unsigned dur = 0; + unsigned incr = 0; if (!string || *string == '\0') return 0; @@ -1028,10 +1069,10 @@ int parse_date_duration(const char *string, unsigned *days, time_t start) switch (state) { case STATE_INITIAL: if (*p == 'w') { - dur += in * WEEKINDAYS / denom; + incr += in * WEEKINDAYS / denom; state = STATE_WWDD_DD; } else if (*p == 'd') { - dur += in / denom; + incr += in / denom; state = STATE_DONE; } else { return 0; @@ -1039,7 +1080,7 @@ int parse_date_duration(const char *string, unsigned *days, time_t start) break; case STATE_WWDD_DD: if (*p == 'd') { - dur += in / denom; + incr += in / denom; state = STATE_DONE; } else { return 0; @@ -1055,18 +1096,18 @@ int parse_date_duration(const char *string, unsigned *days, time_t start) } if (state == STATE_DONE && in > 0) return 0; - dur += in; + incr += in; if (start) { - /* wanted: start = start + dur * DAYINSEC */ + /* wanted: start = start + incr * DAYINSEC */ long p; - if (overflow_mul(dur, DAYINSEC, &p)) + if (overflow_mul(incr, DAYINSEC, &p)) return 0; if (overflow_add(start, p, &start)) return 0; if (!check_sec(&start)) return 0; } - *days = dur; + *days = incr; return 1; } @@ -1312,21 +1353,32 @@ void psleep(unsigned secs) /* * Fork and execute an external process. * - * If pfdin and/or pfdout point to a valid address, a pipe is created and the - * appropriate file descriptors are written to pfdin/pfdout. + * If pfdin/pfdout/pfderr point to a valid address, a pipe is created and the + * appropriate file descriptors are written to pfdin/pfdout/pfderr. + * + * If new_session is non-zero, setsid() is called after forking. */ -int fork_exec(int *pfdin, int *pfdout, const char *path, - const char *const *arg) +int fork_exec(int *pfdin, int *pfdout, int *pfderr, int new_session, + const char *path, const char *const *arg) { - int pin[2], pout[2]; + int pin[2], pout[2], perr[2]; int pid; if (pfdin && (pipe(pin) == -1)) return 0; if (pfdout && (pipe(pout) == -1)) return 0; + if (pfderr && (pipe(perr) == -1)) + return 0; if ((pid = fork()) == 0) { + if (pfderr) { + if (dup2(perr[0], STDERR_FILENO) < 0) + _exit(127); + close(perr[0]); + close(perr[1]); + } + if (pfdout) { if (dup2(pout[0], STDIN_FILENO) < 0) _exit(127); @@ -1341,6 +1393,11 @@ int fork_exec(int *pfdin, int *pfdout, const char *path, close(pin[1]); } + if (new_session) { + if ((setsid() < 0)) + _exit(127); + } + execvp(path, (char *const *)arg); _exit(127); } else { @@ -1348,6 +1405,8 @@ int fork_exec(int *pfdin, int *pfdout, const char *path, close(pin[1]); if (pfdout) close(pout[0]); + if (pfderr) + close(perr[0]); if (pid > 0) { if (pfdin) { @@ -1358,11 +1417,17 @@ int fork_exec(int *pfdin, int *pfdout, const char *path, fcntl(pout[1], F_SETFD, FD_CLOEXEC); *pfdout = pout[1]; } + if (pfderr) { + fcntl(perr[1], F_SETFD, FD_CLOEXEC); + *pfderr = perr[1]; + } } else { if (pfdin) close(pin[0]); if (pfdout) close(pout[1]); + if (pfderr) + close(perr[1]); return 0; } } @@ -1371,8 +1436,8 @@ int fork_exec(int *pfdin, int *pfdout, const char *path, /* Execute an external program in a shell. */ int -shell_exec(int *pfdin, int *pfdout, const char *path, - const char *const *arg) +shell_exec(int *pfdin, int *pfdout, int *pfderr, int new_session, + const char *path, const char *const *arg) { int argc, i; const char **narg; @@ -1401,7 +1466,7 @@ shell_exec(int *pfdin, int *pfdout, const char *path, narg[3] = NULL; } - ret = fork_exec(pfdin, pfdout, *narg, narg); + ret = fork_exec(pfdin, pfdout, pfderr, new_session, *narg, narg); if (arg0) mem_free(arg0); @@ -1411,7 +1476,7 @@ shell_exec(int *pfdin, int *pfdout, const char *path, } /* Wait for a child process to terminate. */ -int child_wait(int *pfdin, int *pfdout, int pid) +int child_wait(int *pfdin, int *pfdout, int *pfderr, int pid) { int stat; @@ -1419,9 +1484,13 @@ int child_wait(int *pfdin, int *pfdout, int pid) close(*pfdin); if (pfdout) close(*pfdout); + if (pfderr) + close(*pfderr); - waitpid(pid, &stat, 0); - return stat; + if (waitpid(pid, &stat, 0) == pid) + return stat; + else + return -1; } /* Display "Press any key to continue..." and wait for a key press. */ @@ -1636,7 +1705,7 @@ static void print_date(time_t date, time_t day, const char *extformat) if (!strcmp(extformat, "epoch")) { printf("%ld", (long)date); } else { - time_t day_start = update_time_in_date(day, 0, 0); + time_t day_start = DAY(day); time_t day_end = date_sec_change(day_start, 0, 1); struct tm lt; @@ -1971,11 +2040,6 @@ int hash_matches(const char *pattern, const char *hash) return (starts_with(hash, pattern) != invert); } -int show_dialogs(void) -{ - return (!quiet) && conf.system_dialogs; -} - /* * Overflow check for addition with positive second term. */ @@ -2067,3 +2131,18 @@ int wday_per_month(int month, int year, int weekday) return last_wday / 7 + (last_wday % 7 > 0); } + +/* + * Return allocated string with day of 't' inserted in 'template' in the user's + * preferred format; template must be a "printf" template with exactly one + * string conversion (%s). + */ +char *day_ins(char **template, time_t t) +{ + char *day, *msg; + + day = date_sec2date_str(DAY(t), DATEFMT(conf.input_datefmt)); + asprintf(&msg, *template, day); + mem_free(day); + return msg; +} @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -61,7 +61,7 @@ enum ui_mode ui_mode = UI_CMDLINE; /* Don't save anything if this is set. */ int read_only = 0; -/* Hide system dialogs if set. */ +/* Hide import/export message if set. */ int quiet = 0; /* Applications can trigger a reload by sending SIGUSR1. */ @@ -136,11 +136,11 @@ void vars_init(void) conf.systemevents = 1; conf.default_panel = CAL; conf.compact_panels = 0; - conf.system_dialogs = 1; strncpy(conf.output_datefmt, "%D", 3); conf.input_datefmt = 1; conf.heading_pos = RIGHT; strcpy(conf.day_heading, DAY_HEADING_DEFAULT); + strcpy(conf.timefmt, APPT_TIME_DEFAULT); datefmt_str[0] = _("mm/dd/yyyy"); datefmt_str[1] = _("dd/mm/yyyy"); @@ -175,6 +175,8 @@ void vars_init(void) ui_calendar_set_first_day_of_week(MONDAY); + wins_set_sbar_width(col * SBARMINWIDTH / 100); + /* Pad structure to scroll text inside the appointment panel */ apad.length = 1; apad.first_onscreen = 0; diff --git a/src/vector.c b/src/vector.c index fd468ab..796ef8e 100644 --- a/src/vector.c +++ b/src/vector.c @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 diff --git a/src/vector.h b/src/vector.h index 86cb3b8..7648df2 100644 --- a/src/vector.h +++ b/src/vector.h @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -1,7 +1,7 @@ /* * Calcurse - text-based organizer * - * Copyright (c) 2004-2020 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 @@ -63,7 +63,7 @@ static int layout; /* * The screen_mutex mutex and wins_refresh(), wins_wrefresh(), wins_doupdate() * functions are used to prevent concurrent updates of the screen. - * It was observed that the display could get screwed up when mulitple threads + * It was observed that the display could get screwed up when multiple threads * tried to refresh the screen at the same time. * * Note (2010-03-21): @@ -229,7 +229,7 @@ void wins_sbar_winc(void) void wins_sbar_wdec(void) { - if (sbarwidth_perc > 0) + if (col * sbarwidth_perc / 100 > SBARMINWIDTH) sbarwidth_perc--; } @@ -254,6 +254,15 @@ void wins_slctd_next(void) slctd_win++; } +/* Shift-TAB key was hit in the interface, need to select previous window. */ +void wins_slctd_prev(void) +{ + if (slctd_win == CAL) + slctd_win = TOD; + else + slctd_win--; +} + static void wins_init_panels(void) { wins_scrollwin_init(&sw_cal, win[CAL].y, win[CAL].x, @@ -593,8 +602,6 @@ void wins_prepare_external(void) { if (notify_bar()) notify_stop_main_thread(); - if (conf.periodic_save > 0) - io_stop_psave_thread(); def_prog_mode(); ui_mode = UI_CMDLINE; clear(); @@ -615,8 +622,6 @@ void wins_unprepare_external(void) wins_resize(); if (notify_bar()) notify_start_main_thread(); - if (conf.periodic_save > 0) - io_start_psave_thread(); } /* @@ -628,8 +633,8 @@ void wins_launch_external(const char *arg[]) int pid; wins_prepare_external(); - if ((pid = shell_exec(NULL, NULL, *arg, arg))) - child_wait(NULL, NULL, pid); + if ((pid = shell_exec(NULL, NULL, NULL, 0, *arg, arg))) + child_wait(NULL, NULL, NULL, pid); wins_unprepare_external(); } @@ -655,6 +660,7 @@ void wins_update_bindings(void) static int bindings_cal[] = { KEY_GENERIC_HELP, KEY_GENERIC_QUIT, KEY_GENERIC_SAVE, KEY_GENERIC_RELOAD, KEY_GENERIC_CHANGE_VIEW, + KEY_GENERIC_PREV_VIEW, KEY_GENERIC_SCROLL_DOWN, KEY_GENERIC_SCROLL_UP, KEY_MOVE_UP, KEY_MOVE_DOWN, KEY_MOVE_LEFT, KEY_MOVE_RIGHT, KEY_GENERIC_GOTO, KEY_GENERIC_IMPORT, KEY_GENERIC_EXPORT, KEY_START_OF_WEEK, @@ -670,6 +676,7 @@ void wins_update_bindings(void) static int bindings_apoint[] = { KEY_GENERIC_HELP, KEY_GENERIC_QUIT, KEY_GENERIC_SAVE, KEY_GENERIC_RELOAD, KEY_GENERIC_CHANGE_VIEW, + KEY_GENERIC_PREV_VIEW, KEY_GENERIC_IMPORT, KEY_GENERIC_EXPORT, KEY_ADD_ITEM, KEY_DEL_ITEM, KEY_EDIT_ITEM, KEY_VIEW_ITEM, KEY_PIPE_ITEM, KEY_GENERIC_REDRAW, KEY_REPEAT_ITEM, KEY_FLAG_ITEM, @@ -686,6 +693,7 @@ void wins_update_bindings(void) static int bindings_todo[] = { KEY_GENERIC_HELP, KEY_GENERIC_QUIT, KEY_GENERIC_SAVE, KEY_GENERIC_RELOAD, KEY_GENERIC_CHANGE_VIEW, + KEY_GENERIC_PREV_VIEW, KEY_GENERIC_SCROLL_DOWN, KEY_GENERIC_SCROLL_UP, KEY_GENERIC_IMPORT, KEY_GENERIC_EXPORT, KEY_ADD_ITEM, KEY_DEL_ITEM, KEY_EDIT_ITEM, KEY_VIEW_ITEM, KEY_PIPE_ITEM, |