From 223f722b1dc03677a9ba1b0646c684d747d75e43 Mon Sep 17 00:00:00 2001
From: Lars Henriksen <LarsHenriksen@get2net.dk>
Date: Fri, 29 May 2020 15:13:28 +0200
Subject: Allow repeat count input (for until) in interactive UI

Shortened repetition type text.

Avoid "duration" in until-contexts (reserve it for appointment duration):
"duration" changed to "increment" in user texts as well as source.

Signed-off-by: Lars Henriksen <LarsHenriksen@get2net.dk>
Signed-off-by: Lukas Fleischer <lfleischer@calcurse.org>
---
 src/calcurse.h |  2 +-
 src/ui-day.c   | 42 ++++++++++++++++++++++++++++++++++++------
 src/utils.c    | 22 +++++++++++-----------
 3 files changed, 48 insertions(+), 18 deletions(-)

(limited to 'src')

diff --git a/src/calcurse.h b/src/calcurse.h
index edf40b6..74e667a 100644
--- a/src/calcurse.h
+++ b/src/calcurse.h
@@ -1246,7 +1246,7 @@ 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);
diff --git a/src/ui-day.c b/src/ui-day.c
index 0d0e1fd..7b2ea0b 100644
--- a/src/ui-day.c
+++ b/src/ui-day.c
@@ -658,8 +658,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;
@@ -745,16 +746,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));
@@ -776,7 +780,7 @@ 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;
@@ -786,6 +790,20 @@ static int update_rept(time_t start, long dur, struct rpt **rpt, llist_t *exc,
 					update_time_in_date(start, 0, 0),
 					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 = update_time_in_date(until, 0, 0);
+			break;
 		} else {
 			int year, month, day;
 			if (!parse_date(timstr, conf.input_datefmt, &year,
@@ -857,6 +875,17 @@ 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 = update_time_in_date(until, 0, 0);
+	}
 	/*
 	 * Check whether the start occurrence matches the recurrence rule, in
 	 * other words, does it occur on the start day?  This is required by
@@ -864,7 +893,8 @@ static int update_rept(time_t start, long dur, struct rpt **rpt, llist_t *exc,
 	 * is an exception day).
 	 */
 	char *msg_match =
-		_("Repetition must begin on start day (%s); any change discarded.");
+		_("Repetition must begin on start day (%s); "
+		"any change discarded.");
 	if (!recur_item_find_occurrence(start, dur, &nrpt, NULL,
 					update_time_in_date(start, 0, 0),
 					NULL)) {
diff --git a/src/utils.c b/src/utils.c
index 552c61e..4da7f79 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -990,11 +990,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.
@@ -1002,7 +1002,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,
@@ -1012,7 +1012,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;
@@ -1033,10 +1033,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;
@@ -1044,7 +1044,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;
@@ -1060,18 +1060,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;
 }
 
-- 
cgit v1.2.3-70-g09d2