aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/apoint.c10
-rw-r--r--src/args.c39
-rw-r--r--src/calcurse.c169
-rw-r--r--src/calcurse.h76
-rw-r--r--src/config.c32
-rw-r--r--src/custom.c93
-rw-r--r--src/day.c104
-rw-r--r--src/dmon.c10
-rw-r--r--src/event.c2
-rw-r--r--src/getstring.c4
-rw-r--r--src/help.c6
-rw-r--r--src/hooks.c37
-rw-r--r--src/htable.h2
-rw-r--r--src/ical.c1229
-rw-r--r--src/io.c240
-rw-r--r--src/keys.c335
-rw-r--r--src/listbox.c2
-rw-r--r--src/llist.c204
-rw-r--r--src/llist.h5
-rw-r--r--src/llist_ts.h4
-rw-r--r--src/mem.c2
-rw-r--r--src/note.c17
-rw-r--r--src/notify.c29
-rw-r--r--src/pcal.c6
-rw-r--r--src/queue.c2
-rw-r--r--src/recur.c167
-rw-r--r--src/sha1.c3
-rw-r--r--src/sha1.h4
-rw-r--r--src/sigs.c20
-rw-r--r--src/strings.c2
-rw-r--r--src/todo.c6
-rw-r--r--src/ui-calendar.c123
-rw-r--r--src/ui-day.c270
-rw-r--r--src/ui-todo.c49
-rw-r--r--src/utf8.c2
-rw-r--r--src/utils.c155
-rw-r--r--src/vars.c8
-rw-r--r--src/vector.c2
-rw-r--r--src/vector.h2
-rw-r--r--src/wins.c26
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, &lt);
- snprintf(start, HRMIN_SIZE, "%02u:%02u", lt.tm_hour,
- lt.tm_min);
+ strftime(start, APPT_TIME_LENGTH, conf.timefmt, &lt);
}
if (o->start + o->dur > day + DAYLEN(day)) {
strncpy(end, "..:..", 6);
} else {
t = o->start + o->dur;
localtime_r(&t, &lt);
- snprintf(end, HRMIN_SIZE, "%02u:%02u", lt.tm_hour,
- lt.tm_min);
+ strftime(end, APPT_TIME_LENGTH, conf.timefmt, &lt);
}
}
diff --git a/src/args.c b/src/args.c
index b2ccb0a..57cbe63 100644
--- a/src/args.c
+++ b/src/args.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
@@ -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);
diff --git a/src/day.c b/src/day.c
index 2a1e1a8..78f4acf 100644
--- a/src/day.c
+++ b/src/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
@@ -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(&notepath, "%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(&notepath, "%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)
)
diff --git a/src/dmon.c b/src/dmon.c
index f9bbfd2..11d3458 100644
--- a/src/dmon.c
+++ b/src/dmon.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,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 */
diff --git a/src/help.c b/src/help.c
index 398fb6d..3f2c94f 100644
--- a/src/help.c
+++ b/src/help.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
@@ -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
diff --git a/src/ical.c b/src/ical.c
index 5ab6846..4a7738e 100644
--- a/src/ical.c
+++ b/src/ical.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,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(&note_file, "%s/%s", path_notes, name);
+ if (!(fp = fopen(note_file, "r")) || ungetc(getc(fp), fp) == EOF) {
+ fclose(fp);
+ return;
+ }
+ string_init(&note);
+ while (fgets(lbuf, BUFSIZ, fp))
+ string_catf(&note, "%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(&note);
}
+/*
+ * 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);
}
diff --git a/src/io.c b/src/io.c
index 1caa6d3..d596aab 100644
--- a/src/io.c
+++ b/src/io.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
@@ -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)
diff --git a/src/keys.c b/src/keys.c
index 3a25a0b..ae0375c 100644
--- a/src/keys.c
+++ b/src/keys.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
@@ -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)
diff --git a/src/mem.c b/src/mem.c
index 8b8a5d5..ce3cf80 100644
--- a/src/mem.c
+++ b/src/mem.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/note.c b/src/note.c
index 92c1ecf..655ad89 100644
--- a/src/note.c
+++ b/src/note.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
@@ -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(&notify.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;
diff --git a/src/pcal.c b/src/pcal.c
index 78da0bb..435656a 100644
--- a/src/pcal.c
+++ b/src/pcal.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
@@ -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;
}
diff --git a/src/sha1.c b/src/sha1.c
index 3826c76..6ab63f8 100644
--- a/src/sha1.c
+++ b/src/sha1.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
@@ -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))))
diff --git a/src/sha1.h b/src/sha1.h
index bdba4e8..25d60b9 100644
--- a/src/sha1.h
+++ b/src/sha1.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
@@ -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 *);
diff --git a/src/sigs.c b/src/sigs.c
index 02457ae..9320652 100644
--- a/src/sigs.c
+++ b/src/sigs.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
@@ -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
diff --git a/src/todo.c b/src/todo.c
index aa77b15..9bd8f8a 100644
--- a/src/todo.c
+++ b/src/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,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(&current_day);
- sunday_first = !ui_calendar_week_begins_on_monday();
- draw_calendar[ui_calendar_view] (&sw_cal, &current_day, sunday_first);
+ draw_calendar[ui_calendar_view] (&sw_cal, &current_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(&notepath, "%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)
diff --git a/src/utf8.c b/src/utf8.c
index 1fd4c3b..997a4fa 100644
--- a/src/utf8.c
+++ b/src/utf8.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/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;
+}
diff --git a/src/vars.c b/src/vars.c
index 8c1a042..97a129a 100644
--- a/src/vars.c
+++ b/src/vars.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
@@ -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
diff --git a/src/wins.c b/src/wins.c
index 485c59b..46ac98e 100644
--- a/src/wins.c
+++ b/src/wins.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
@@ -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,