[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#809739: cdebconf: text frontend: having a way to scroll among choices



Source: cdebconf
Version: 0.201
Severity: normal
Tags: patch

Hello,

In the text frontend, when there are more choices than can fit in the
screen, the question and the first choices get lost in the backlog.
When lucky, the available backscroll is long enough, but that's not
guaranteed, notably with the every-increasing list of supported
languages :)

I have come up with some changes in the text frontend to make it only
display what can fit on the screen and provide shortcuts to circulate
among the list of available choices.  I have attached the patch, and put
a sample built image on
https://people.debian.org/~sthibault/tmp/mini-cdebconf-text-choices.iso
(select the "speech" boot menu entry to easy select the text frontend).

The idea is to make all functions that print text return the number of
printed lines, so that the printlist() function can be given the number
of lines it is allowed to print.  That works quite nicely.

For now I have used '(' and ')' as shortcuts for previous and next
choices, probably better shortcuts could be found ('<' is already
taken).  Also the banners "Other/Previous/Next choices are available
with" can probably be improved.

How do debian-boot people think about this?

Samuel

-- System Information:
Debian Release: stretch/sid
  APT prefers unstable-debug
  APT policy: (500, 'unstable-debug'), (500, 'oldoldstable'), (500, 'buildd-unstable'), (500, 'unstable'), (500, 'testing'), (500, 'stable'), (500, 'oldstable'), (1, 'experimental-debug'), (1, 'buildd-experimental'), (1, 'experimental')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 4.3.0-1-amd64 (SMP w/4 CPU cores)
Locale: LANG=fr_FR.UTF-8, LC_CTYPE=fr_FR.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash
Init: systemd (via /run/systemd/system)

-- 
Samuel
In mutt, type cthis
Dans mutt, taper cceci
diff --git a/src/modules/frontend/text/cdebconf_text.h b/src/modules/frontend/text/cdebconf_text.h
index 7f7088d..a4ce753 100644
--- a/src/modules/frontend/text/cdebconf_text.h
+++ b/src/modules/frontend/text/cdebconf_text.h
@@ -5,6 +5,8 @@
 #define CHAR_GOBACK '<'
 #define CHAR_HELP '?'
 #define CHAR_CLEAR '!'
+#define CHAR_PREV '('
+#define CHAR_NEXT ')'
 
 int cdebconf_text_get_width(const char *text);
 
diff --git a/src/modules/frontend/text/text.c b/src/modules/frontend/text/text.c
index e90b837..d7a1da8 100644
--- a/src/modules/frontend/text/text.c
+++ b/src/modules/frontend/text/text.c
@@ -64,7 +64,7 @@ struct frontend_data {
 	char *previous_title;
 };
 
-typedef int (text_handler)(struct frontend *obj, struct question *q);
+typedef int (text_handler)(struct frontend *obj, unsigned printed, struct question *q);
 
 #define MAKE_UPPER(C) do { if (islower((int) C)) { C = (char) toupper((int) C); } } while(0)
 
@@ -102,13 +102,40 @@ static int getwidth(void)
 }
 
 /*
+ * Function: getheight
+ * Input: none
+ * Output: int - height of screen
+ * Description: get the height of the current terminal
+ * Assumptions: doesn't handle resizing; caches value on first call
+ */
+static int getheight(void)
+{
+	static int res = 25;
+	static int inited = 0;
+	int fd;
+	struct winsize ws;
+
+	if (inited == 0)
+	{
+		inited = 1;
+		if ((fd = open("/dev/tty", O_RDONLY)) > 0)
+		{
+			if (ioctl(fd, TIOCGWINSZ, &ws) == 0 && ws.ws_row > 0)
+				res = ws.ws_row;
+			close(fd);
+		}
+	}
+	return res;
+}
+
+/*
  * Function: wrap_print
  * Input: const char *str - string to display
- * Output: none
+ * Output: unsigned printed - number of printed lines
  * Description: prints a string to the screen with word wrapping 
  * Assumptions: string fits in <500 lines
  */
-static void wrap_print(const char *str)
+static unsigned wrap_print(const char *str)
 {
 	/* Simple greedy line-wrapper */
 	int i, lc;
@@ -121,20 +148,23 @@ static void wrap_print(const char *str)
 		printf("%s\n", lines[i]);
 		DELETE(lines[i]);
 	}
+	return lc;
 }
 
 /*
  * Function: text_handler_displaydesc
  * Input: struct frontend *obj - UI object
+ *        unsigned printed - number of already printed lines
  *        struct question *q - question for which to display the description
- * Output: none
+ * Output: unsigned - number of printed lines
  * Description: displays the description for a given question 
  * Assumptions: none
  */
-static void text_handler_displaydesc(struct frontend *obj, struct question *q) 
+static unsigned text_handler_displaydesc(struct frontend *obj, unsigned printed, struct question *q) 
 {
 	char *descr = q_get_description(obj, q);
 	char *ext_descr = q_get_extended_description(obj, q);
+	printed = 0;
 	if (strcmp(q->template->type, "note") == 0 ||
 	    strcmp(q->template->type, "error") == 0)
 	{
@@ -143,17 +173,19 @@ static void text_handler_displaydesc(struct frontend *obj, struct question *q)
 		else
 			printf("%s", descr);
 		printf("\n\n");
+		printed += 2;
 		if (*ext_descr)
-			wrap_print(ext_descr);
+			printed += wrap_print(ext_descr);
 	}
 	else
 	{
 		if (*ext_descr)
-			wrap_print(ext_descr);
-		wrap_print(descr);
+			printed += wrap_print(ext_descr);
+		printed += wrap_print(descr);
 	}
 	free(descr);
 	free(ext_descr);
+	return printed;
 }
 
 static void
@@ -163,21 +195,24 @@ get_answer(char *answer, int size)
 	CHOMP(answer);
 }
 
-static void
+static unsigned
 show_help (struct frontend *obj, struct question *q)
 {
 	char *descr = q_get_description(obj, q);
 	char *help = q_get_help(obj, q);
+	unsigned printed = 0;
 	if (*help) {
 		struct question *help_q = obj->qdb->methods.get(obj->qdb, help);
 		if (help_q) {
 			char *help_descr = q_get_description(obj, help_q);
 			char *help_ext_descr = q_get_extended_description(obj, help_q);
-			wrap_print(help_descr);
+			printed += wrap_print(help_descr);
 			printf("\n");
+			printed++;
 			if (*help_ext_descr) {
-				wrap_print(help_ext_descr);
+				printed += wrap_print(help_ext_descr);
 				printf("\n");
+				printed++;
 			}
 			free(help_ext_descr);
 			free(help_descr);
@@ -186,13 +221,16 @@ show_help (struct frontend *obj, struct question *q)
 		free(help);
 	}
 	printf("%s\n", question_get_text(obj, "debconf/text-help-keystrokes", "KEYSTROKES:"));
+	printed++;
 	printf(" ");
 	printf(question_get_text(obj, "debconf/text-help-keystroke", "'%c'"), CHAR_HELP);
 	printf(" %s\n", question_get_text(obj, "debconf/text-help-help", "Display this help message"));
+	printed++;
 	if (obj->methods.can_go_back (obj, q)) {
 		printf(" ");
 		printf(question_get_text(obj, "debconf/text-help-keystroke", "'%c'"), CHAR_GOBACK);
 		printf(" %s\n", question_get_text(obj, "debconf/text-help-goback", "Go back to previous question"));
+		printed++;
 	}
 	if (strcmp(q->template->type, "string") == 0 ||
 	    strcmp(q->template->type, "password") == 0 ||
@@ -200,9 +238,11 @@ show_help (struct frontend *obj, struct question *q)
 		printf(" ");
 		printf(question_get_text(obj, "debconf/text-help-keystroke", "'%c'"), CHAR_CLEAR);
 		printf(" %s\n", question_get_text(obj, "debconf/text-help-clear", "Select an empty entry"));
+		printed++;
 	}
-	wrap_print(descr);
+	printed += wrap_print(descr);
 	free(descr);
+	return printed;
 }
 
 struct choices {
@@ -213,8 +253,21 @@ struct choices {
 	int *tindex;
 };
 
-static void
-printlist (struct frontend *obj, struct question *q, const struct choices *choices)
+/*
+ * Function: printlist
+ * Input: struct frontend *obj - UI object
+ *        unsigned max_lines - maximum number of lines to print
+ *        unsigned start - which line of choices should be printed first.
+ *        struct question *q - question for which to display the description
+ * Output: bool - whether we managed to print all choices from 'start' to last or not
+ * Description: displays the description for a given question
+ * Assumptions: none
+ *
+ * If it didn't fit, it will have printed a one-line comment about it, thus one
+ * line of choices less.
+ */
+static unsigned
+printlist (struct frontend *obj, unsigned max_lines, unsigned start, struct question *q, const struct choices *choices)
 {
 	int choice_min = -1;
 	int num_cols, num_lines;
@@ -229,6 +282,7 @@ printlist (struct frontend *obj, struct question *q, const struct choices *choic
 	int width = getwidth();
 	char **fchoices = malloc(sizeof(char *) * choices->count);
 	int horiz = 0;
+	bool all = false;
 
 	if (getenv("DEBCONF_TEXT_HORIZ"))
 		horiz = 1;
@@ -304,6 +358,7 @@ printlist (struct frontend *obj, struct question *q, const struct choices *choic
 		num_lines = choices->count;
 		num_cols = 1;
 	}
+
 	output = malloc(sizeof(char *) * num_lines);
 	for (i = 0; i < num_lines; i++)
 	{
@@ -337,27 +392,57 @@ printlist (struct frontend *obj, struct question *q, const struct choices *choic
 			max_len = 0;
 		}
 	}
-	for (l = 0; l < num_lines; l++)
+
+	if (max_lines >= num_lines - start)
+	{
+		/* More than enough room */
+		max_lines = num_lines - start;
+		all = true;
+	}
+	else
+		/* Keep one line for the "more choices" prompt */
+		max_lines--;
+	/* Don't display the beginning */
+	for (l = 0; l < start; l++)
+		free(output[l]);
+	/* Display only what fits in the screen */
+	for ( ; l < start + max_lines; l++)
 	{
 		printf("%s\n", output[l]);
 		free(output[l]);
 	}
+	/* Don't display the end */
+	for ( ; l < num_lines; l++)
+		free(output[l]);
+
 	free(output);
 	free(col_width);
 	for (i = 0; i < choices->count; i++)
 		free(fchoices[i]);
 	free(fchoices);
+
+	if (start > 0 && !all)
+		printf(question_get_text(obj, "debconf/text-help-otherchoices", "Other choices are available with '%c' and '%c'"), CHAR_PREV, CHAR_NEXT);
+	if (start > 0 && all)
+		printf(question_get_text(obj, "debconf/text-help-prevchoices", "Previous choices are available with '%c'"), CHAR_PREV);
+	if (start == 0 && !all)
+		printf(question_get_text(obj, "debconf/text-help-nextchoices", "Next choices are available with '%c'"), CHAR_NEXT);
+	if (start > 0 || !all)
+		printf("\n");
+
+	return all;
 }
 
 /*
  * Function: text_handler_boolean
  * Input: struct frontend *obj - frontend object
+ *        unsigned printed - number of already printed lines
  *        struct question *q - question to ask
  * Output: int - DC_OK, DC_NOTOK, DC_GOBACK
  * Description: handler for the boolean question type
  * Assumptions: none
  */
-static int text_handler_boolean(struct frontend *obj, struct question *q)
+static int text_handler_boolean(struct frontend *obj, unsigned printed, struct question *q)
 {
 	char buf[30];
 	int ans = 0;
@@ -385,7 +470,7 @@ static int text_handler_boolean(struct frontend *obj, struct question *q)
 					"Prompt: '%c' for help> "), CHAR_HELP);
 		get_answer(buf, sizeof(buf));
 		if (buf[0] == CHAR_HELP && buf[1] == 0)
-			show_help(obj, q);
+			printed += show_help(obj, q);
 		else if (obj->methods.can_go_back (obj, q) &&
 		         buf[0] == CHAR_GOBACK && buf[1] == 0)
 			return DC_GOBACK;
@@ -433,6 +518,7 @@ static void choices_delete(struct choices *c)
 /*
  * Function: choices_get
  * Input: struct frontend *obj - frontend object
+ *        unsigned printed - number of already printed lines
  *        struct question *q - question to ask
  * Output: struct choices * - choices values, translations, indices, and select
  * Description: retrieve question choices, translations, indices
@@ -480,12 +566,13 @@ static struct choices *choices_get(struct frontend *obj, struct question *q)
 /*
  * Function: text_handler_multiselect
  * Input: struct frontend *obj - frontend object
+ *        unsigned printed - number of already printed lines
  *        struct question *q - question to ask
  * Output: int - DC_OK, DC_NOTOK
  * Description: handler for the multiselect question type
  * Assumptions: none
  */
-static int text_handler_multiselect(struct frontend *obj, struct question *q)
+static int text_handler_multiselect(struct frontend *obj, unsigned printed, struct question *q)
 {
 	struct choices *choices = NULL;
 	char **defaults;
@@ -493,6 +580,8 @@ static int text_handler_multiselect(struct frontend *obj, struct question *q)
 	char answer[4096] = {0};
 	int i, j, dcount, choice;
 	int ret = DC_OK;
+	unsigned start = 0;
+	bool all;
 
 	choices = choices_get(obj, q);
 	if (choices == NULL)
@@ -525,13 +614,14 @@ static int text_handler_multiselect(struct frontend *obj, struct question *q)
 		}
 
   DISPLAY:
-	printlist (obj, q, choices);
+	all = printlist (obj, getheight() - printed - 1, start, q, choices);
 	printf(question_get_text(obj, "debconf/text-prompt-default-string", 
 		"Prompt: '%c' for help, default=%s> "), CHAR_HELP, defval);
 	get_answer(answer, sizeof(answer));
 	if (answer[0] == CHAR_HELP && answer[1] == 0)
 	{
-		show_help(obj, q);
+		printed = 0;
+		printed += show_help(obj, q);
 		goto DISPLAY;
 	}
 	else if (answer[0] == CHAR_CLEAR && answer[1] == 0)
@@ -545,6 +635,27 @@ static int text_handler_multiselect(struct frontend *obj, struct question *q)
 		ret = DC_GOBACK;
 		goto CleanUp_DEFVAL;
 	}
+	else if (answer[0] == CHAR_NEXT && answer[1] == 0)
+	{
+		if (!all)
+			start += getheight() - printed - 2;
+		printed = 0;
+		goto DISPLAY;
+	}
+	else if (answer[0] == CHAR_PREV && answer[1] == 0)
+	{
+		/* Note: 'printed' may not always contain the same value, when
+		 * the title banner is not printed again notably.  This however
+		 * only happens once, the second time the question in answered,
+		 * and start will have then been 0 already so this does not
+		 * pose problem.  */
+		if (start <= getheight() - printed - 2)
+			start = 0;
+		else
+			start -= getheight() - printed - 2;
+		printed = 0;
+		goto DISPLAY;
+	}
 
 	if (!(ISEMPTY(answer)))
 	{
@@ -580,6 +691,7 @@ static int text_handler_multiselect(struct frontend *obj, struct question *q)
 /*
  * Function: text_handler_select
  * Input: struct frontend *obj - frontend object
+ *        unsigned printed - number of already printed lines
  *        struct question *q - question to ask
  * Output: int - DC_OK, DC_NOTOK
  * Description: handler for the select question type
@@ -587,13 +699,15 @@ static int text_handler_multiselect(struct frontend *obj, struct question *q)
  *
  * TODO: factor common code with multiselect
  */
-static int text_handler_select(struct frontend *obj, struct question *q)
+static int text_handler_select(struct frontend *obj, unsigned printed, struct question *q)
 {
 	struct choices *choices = NULL;
 	char answer[128];
 	int i, choice, def = -1;
 	const char *defval;
 	int ret = DC_OK;
+	unsigned start = 0;
+	bool all;
 
 	choices = choices_get(obj, q);
 	if (choices == NULL)
@@ -617,7 +731,7 @@ static int text_handler_select(struct frontend *obj, struct question *q)
 	i = 0;
 	choice = -1;
 	do {
-		printlist (obj, q, choices);
+		all = printlist (obj, getheight() - printed - 1, start, q, choices);
 		if (def >= 0 && choices->choices_translated[def]) {
 			printf(question_get_text(obj, "debconf/text-prompt-default", 
 				"Prompt: '%c' for help, default=%d> "),
@@ -629,7 +743,7 @@ static int text_handler_select(struct frontend *obj, struct question *q)
 		get_answer(answer, sizeof(answer));
 		if (answer[0] == CHAR_HELP)
 		{
-			show_help(obj, q);
+			printed += show_help(obj, q);
 			continue;
 		}
 		if (obj->methods.can_go_back (obj, q) &&
@@ -638,6 +752,27 @@ static int text_handler_select(struct frontend *obj, struct question *q)
 			ret = DC_GOBACK;
 			goto CleanUp_SELECTED;
 		}
+		else if (answer[0] == CHAR_NEXT && answer[1] == 0)
+		{
+			if (!all)
+				start += getheight() - printed - 2;
+			printed = 0;
+			continue;
+		}
+		else if (answer[0] == CHAR_PREV && answer[1] == 0)
+		{
+			/* Note: 'printed' may not always contain the same value, when
+			 * the title banner is not printed again notably.  This however
+			 * only happens once, the second time the question in answered,
+			 * and start will have then been 0 already so this does not
+			 * pose problem.  */
+			if (start < getheight() - printed - 2)
+				start = 0;
+			else
+				start -= getheight() - printed - 2;
+			printed = 0;
+			continue;
+		}
 		if (ISEMPTY(answer))
 			choice = def;
 		else {
@@ -653,6 +788,7 @@ static int text_handler_select(struct frontend *obj, struct question *q)
 				}
 			}
 		}
+		printed = 0;
 	} while (choice < 0 || choice >= choices->count);
 	question_setvalue(q, choices->choices[choices->tindex[choice]]);
 
@@ -665,12 +801,13 @@ static int text_handler_select(struct frontend *obj, struct question *q)
 /*
  * Function: text_handler_note
  * Input: struct frontend *obj - frontend object
+ *        unsigned printed - number of already printed lines
  *        struct question *q - question to ask
  * Output: int - DC_OK, DC_NOTOK, DC_GOBACK
  * Description: handler for the note question type
  * Assumptions: none
  */
-static int text_handler_note(struct frontend *obj, struct question *q)
+static int text_handler_note(struct frontend *obj, unsigned printed, struct question *q)
 {
 	char buf[100] = {0};
 	printf("%s ", question_get_text(obj, "debconf/cont-prompt",
@@ -680,7 +817,7 @@ static int text_handler_note(struct frontend *obj, struct question *q)
 	{
 		get_answer(buf, sizeof(buf));
 		if (buf[0] == CHAR_HELP && buf[1] == 0)
-			show_help(obj, q);
+			printed += show_help(obj, q);
 		else if (obj->methods.can_go_back (obj, q) &&
 		         buf[0] == CHAR_GOBACK && buf[1] == 0)
 			return DC_GOBACK;
@@ -693,6 +830,7 @@ static int text_handler_note(struct frontend *obj, struct question *q)
 /*
  * Function: text_handler_password
  * Input: struct frontend *obj - frontend object
+ *        unsigned printed - number of already printed lines
  *        struct question *q - question to ask
  * Output: int - DC_OK, DC_NOTOK
  * Description: handler for the password question type
@@ -700,7 +838,7 @@ static int text_handler_note(struct frontend *obj, struct question *q)
  *
  * TODO: this can be *MUCH* improved. no editing is possible right now
  */
-static int text_handler_password(struct frontend *obj, struct question *q)
+static int text_handler_password(struct frontend *obj, unsigned printed, struct question *q)
 {
 	struct termios oldt, newt;
 	char passwd[256] = {0};
@@ -729,7 +867,7 @@ static int text_handler_password(struct frontend *obj, struct question *q)
 		passwd[i] = 0;
 		tcsetattr(0, TCSANOW, &oldt);
 		if (passwd[0] == CHAR_HELP && passwd[1] == 0)
-			show_help(obj, q);
+			printed += show_help(obj, q);
 		else
 			break;
 	}
@@ -746,12 +884,13 @@ static int text_handler_password(struct frontend *obj, struct question *q)
 /*
  * Function: text_handler_string
  * Input: struct frontend *obj - frontend object
+ *        unsigned printed - number of already printed lines
  *        struct question *q - question to ask
  * Output: int - DC_OK, DC_NOTOK
  * Description: handler for the string question type
  * Assumptions: none
  */
-static int text_handler_string(struct frontend *obj, struct question *q)
+static int text_handler_string(struct frontend *obj, unsigned printed, struct question *q)
 {
 	char buf[1024] = {0};
 	const char *defval = question_getvalue(q, "");
@@ -763,7 +902,7 @@ static int text_handler_string(struct frontend *obj, struct question *q)
 		fflush(stdout);
 		get_answer(buf, sizeof(buf));
 		if (buf[0] == CHAR_HELP && buf[1] == 0)
-			show_help(obj, q);
+			printed += show_help(obj, q);
 		else
 			break;
 	}
@@ -784,27 +923,29 @@ static int text_handler_string(struct frontend *obj, struct question *q)
 /*
  * Function: text_handler_text
  * Input: struct frontend *obj - frontend object
+ *        unsigned printed - number of already printed lines
  *        struct question *q - question to ask
  * Output: int - DC_OK, DC_NOTOK, DC_GOBACK
  * Description: handler for the text question type
  * Assumptions: none
  */
-static int text_handler_text(struct frontend *obj, struct question *q)
+static int text_handler_text(struct frontend *obj, unsigned printed, struct question *q)
 {
-	return text_handler_note(obj, q);
+	return text_handler_note(obj, printed, q);
 }
 
 /*
  * Function: text_handler_error
  * Input: struct frontend *obj - frontend object
+ *        unsigned printed - number of already printed lines
  *        struct question *q - question to ask
  * Output: int - DC_OK, DC_NOTOK, DC_GOBACK
  * Description: handler for the error question type. Currently equal to _note
  * Assumptions: none
  */
-static int text_handler_error(struct frontend *obj, struct question *q)
+static int text_handler_error(struct frontend *obj, unsigned printed, struct question *q)
 {
-	return text_handler_note(obj, q);
+	return text_handler_note(obj, printed, q);
 }
 
 /* ----------------------------------------------------------------------- */
@@ -867,6 +1008,7 @@ static int text_shutdown(struct frontend *obj)
 /*
  * Function: text_can_go_back
  * Input: struct frontend *obj - frontend object
+ *        unsigned printed - number of already printed lines
  *        struct question *q - question object
  * Output: int - DC_OK, DC_NOTOK
  * Description: tells whether confmodule supports backing up
@@ -881,6 +1023,7 @@ text_can_go_back(struct frontend *obj, struct question *q)
 /*
  * Function: text_can_align
  * Input: struct frontend *obj - frontend object
+ *        unsigned printed - number of already printed lines
  *        struct question *q - question object
  * Output: int - DC_OK, DC_NOTOK
  * Description: tells whether confmodule supports aligning columns
@@ -929,12 +1072,14 @@ static int text_go(struct frontend *obj)
 	struct question *q = obj->questions;
 	int i;
 	int ret = DC_OK;
+	unsigned printed;
 
 	while (q != NULL) {
 		for (i = 0; i < DIM(question_handlers); i++) {
 			text_handler *handler;
 			struct plugin *plugin = NULL;
 
+			printed = 0;
 			if (*question_handlers[i].type)
 				handler = question_handlers[i].handler;
 			else {
@@ -968,12 +1113,13 @@ static int text_go(struct frontend *obj)
 					memset(underline, '-', underline_len);
 					underline[underline_len] = '\0';
 					printf("%s\n%s\n\n", obj->title, underline);
+					printed += 3;
 					free(underline);
 					free(data->previous_title);
 					data->previous_title = strdup(obj->title);
 				}
-				text_handler_displaydesc(obj, q);
-				ret = handler(obj, q);
+				printed += text_handler_displaydesc(obj, printed, q);
+				ret = handler(obj, printed, q);
 				putchar('\n');
 				if (ret == DC_OK)
 					frontend_qdb_set(obj->qdb, q, 0);

Reply to: