Beispiel #1
0
void
ui_stat_draw_popup_line(WINDOW *win, const char item[], const char descr[],
		size_t max_width)
{
	char *left, *right, *line;
	const size_t text_width = utf8_strsw(item);
	const size_t win_width = getmaxx(win);
	const int align_columns = (max_width <= win_width/4);
	const char *const fmt = align_columns ? "%-*s  %-*s" : "%-*s  %*s";
	size_t width_left;
	size_t item_width;

	if(text_width >= win_width)
	{
		char *const whole = right_ellipsis(item, win_width, curr_stats.ellipsis);
		wprint(win, whole);
		free(whole);
		return;
	}

	left = right_ellipsis(item, win_width - 3, curr_stats.ellipsis);

	item_width = align_columns ? max_width : utf8_strsw(left);
	width_left = win_width - 2 - MAX(item_width, utf8_strsw(left));

	right = right_ellipsis(descr, width_left, curr_stats.ellipsis);

	line = format_str(fmt, (int)item_width, left, (int)width_left, right);
	free(left);
	free(right);

	wprint(win, line);

	free(line);
}
Beispiel #2
0
/* Truncate the msg to the width by placing ellipsis in the middle and put the
 * result to the buffer. */
static void
truncate_with_ellipsis(const char msg[], size_t width, char buffer[])
{
	const size_t screen_len = utf8_strsw(msg);
	const size_t screen_left_len = (width - 3)/2;
	const size_t screen_right_len = (width - 3) - screen_left_len;
	const size_t left = utf8_nstrsnlen(msg, screen_left_len);
	const size_t right = utf8_nstrsnlen(msg, screen_len - screen_right_len);
	strncpy(buffer, msg, left);
	strcpy(buffer + left, "...");
	strcpy(buffer + left + 3, msg + right);
	assert(utf8_strsw(buffer) == width);
}
Beispiel #3
0
/* Draws suggestion box and all its items (from completion list). */
static void
draw_suggestion_box(void)
{
	/* TODO: consider possibility of multicolumn display. */

	const vle_compl_t *const items = vle_compl_get_items();
	int height;
	WINDOW *const win = prepare_suggestion_box(&height);
	size_t max_title_width;
	int i;

	max_title_width = 0U;
	for(i = 0; i < height; ++i)
	{
		const size_t width = utf8_strsw(items[i].text);
		if(width > max_title_width)
		{
			max_title_width = width;
		}
	}

	for(i = 0; i < height; ++i)
	{
		checked_wmove(win, i, 0);
		ui_stat_draw_popup_line(win, items[i].text, items[i].descr,
				max_title_width);
	}

	ui_refresh_win(win);
}
Beispiel #4
0
/* Draws box and title of the menu. */
static void
draw_menu_frame(const menu_state_t *m)
{
	const size_t title_len = getmaxx(menu_win) - 2*4;
	const char *const suffix = menu_and_view_are_in_sync(m->d, m->view)
	                         ? ""
	                         : replace_home_part(m->d->cwd);
	const char *const at = (suffix[0] == '\0' ? "" : " @ ");
	char *const title = format_str("%s%s%s", m->d->title, at, suffix);

	if(utf8_strsw(title) > title_len)
	{
		const size_t len = utf8_nstrsnlen(title, title_len - 3);
		strcpy(title + len, "...");
	}

	box(menu_win, 0, 0);
	wattron(menu_win, A_BOLD);
	checked_wmove(menu_win, 0, 3);
	wprint(menu_win, " ");
	wprint(menu_win, title);
	wprint(menu_win, " ");
	wattroff(menu_win, A_BOLD);

	free(title);
}
Beispiel #5
0
/* Draws search match highlight on the element. */
static void
draw_search_match(char str[], int start, int end, int line, int width,
		int attrs)
{
	const int len = strlen(str);

	if(end <= 0)
	{
		/* Match is completely at the left. */

		checked_wmove(menu_win, line, 2);
		wprinta(menu_win, "<<<", attrs ^ A_REVERSE);
	}
	else if(start >= len)
	{
		/* Match is completely at the right. */

		checked_wmove(menu_win, line, width - 3);
		wprinta(menu_win, ">>>", attrs ^ A_REVERSE);
	}
	else
	{
		/* Match is at least partially visible. */

		char c;
		int match_start;

		if(start < 0)
		{
			start = 0;
		}
		if(end < len)
		{
			str[end] = '\0';
		}

		/* Calculate number of screen characters before the match. */
		c = str[start];
		str[start] = '\0';
		match_start = utf8_strsw(str);
		str[start] = c;

		checked_wmove(menu_win, line, 2 + match_start);
		wprinta(menu_win, str + start, attrs ^ (A_REVERSE | A_UNDERLINE));
	}
}
Beispiel #6
0
void
ui_stat_update(view_t *view, int lazy_redraw)
{
	if(!cfg.display_statusline || view != curr_view)
	{
		return;
	}

	/* Don't redraw anything until :restart command is finished. */
	if(curr_stats.restart_in_progress)
	{
		return;
	}

	ui_stat_job_bar_check_for_updates();

	if(cfg.status_line[0] == '\0')
	{
		update_stat_window_old(view, lazy_redraw);
		return;
	}

	const int width = getmaxx(stdscr);

	wresize(stat_win, 1, width);
	ui_set_bg(stat_win, &cfg.cs.color[STATUS_LINE_COLOR],
			cfg.cs.pair[STATUS_LINE_COLOR]);
	werase(stat_win);
	checked_wmove(stat_win, 0, 0);

	cchar_t default_attr;
	setcchar(&default_attr, L" ", cfg.cs.color[STATUS_LINE_COLOR].attr,
			cfg.cs.pair[STATUS_LINE_COLOR], NULL);

	LineWithAttrs result = expand_status_line_macros(view, cfg.status_line);
	assert(strlen(result.attrs) == utf8_strsw(result.line) && "Broken attrs!");
	result.line = break_in_two(result.line, width, "%=");
	result.attrs = break_in_two(result.attrs, width, "=");
	print_with_attrs(stat_win, result.line, result.attrs, &default_attr);
	free(result.line);
	free(result.attrs);

	refresh_window(stat_win, lazy_redraw);
}
Beispiel #7
0
static void
status_bar_message_i(const char message[], int error)
{
	/* TODO: Refactor this function status_bar_message_i() */

	static char *msg;
	static int err;

	int len;
	const char *p, *q;
	int lines;
	int status_bar_lines;
	size_t screen_length;
	const char *out_msg;
	char truncated_msg[2048];

	if(curr_stats.load_stage == 0)
	{
		return;
	}

	if(message != NULL)
	{
		if(replace_string(&msg, message))
		{
			return;
		}

		err = error;

		save_status_bar_msg(msg);
	}

	if(msg == NULL || vle_mode_is(CMDLINE_MODE))
	{
		return;
	}

	p = msg;
	q = msg - 1;
	status_bar_lines = 0;
	len = getmaxx(stdscr);
	while((q = strchr(q + 1, '\n')) != NULL)
	{
		status_bar_lines += DIV_ROUND_UP(q - p, len );
		if(q == p)
		{
			++status_bar_lines;
		}
		p = q + 1;
	}
	if(*p == '\0')
	{
		++status_bar_lines;
	}
	screen_length = utf8_strsw(p);
	status_bar_lines += DIV_ROUND_UP(screen_length, len);
	if(status_bar_lines == 0)
	{
		status_bar_lines = 1;
	}

	lines = status_bar_lines;
	if(status_bar_lines > 1 || screen_length > (size_t)getmaxx(status_bar))
	{
		++lines;
	}

	out_msg = msg;

	if(lines > 1)
	{
		if(cfg.trunc_normal_sb_msgs && !err && curr_stats.allow_sb_msg_truncation)
		{
			truncate_with_ellipsis(msg, getmaxx(stdscr) - FIELDS_WIDTH(),
					truncated_msg);
			out_msg = truncated_msg;
			lines = 1;
		}
		else
		{
			const int extra = DIV_ROUND_UP(ARRAY_LEN(PRESS_ENTER_MSG) - 1, len) - 1;
			lines += extra;
		}
	}

	if(lines > getmaxy(stdscr))
	{
		lines = getmaxy(stdscr);
	}

	(void)ui_stat_reposition(lines);
	mvwin(status_bar, getmaxy(stdscr) - lines, 0);
	if(lines == 1)
	{
		wresize(status_bar, lines, getmaxx(stdscr) - FIELDS_WIDTH());
	}
	else
	{
		wresize(status_bar, lines, getmaxx(stdscr));
	}
	checked_wmove(status_bar, 0, 0);

	if(err)
	{
		col_attr_t col = cfg.cs.color[CMD_LINE_COLOR];
		mix_colors(&col, &cfg.cs.color[ERROR_MSG_COLOR]);
		wattron(status_bar, COLOR_PAIR(colmgr_get_pair(col.fg, col.bg)) | col.attr);
	}
	else
	{
		int attr = cfg.cs.color[CMD_LINE_COLOR].attr;
		wattron(status_bar, COLOR_PAIR(cfg.cs.pair[CMD_LINE_COLOR]) | attr);
	}
	werase(status_bar);

	wprint(status_bar, out_msg);
	multiline_status_bar = lines > 1;
	if(multiline_status_bar)
	{
		checked_wmove(status_bar,
				lines - DIV_ROUND_UP(ARRAY_LEN(PRESS_ENTER_MSG), len), 0);
		wclrtoeol(status_bar);
		if(lines < status_bar_lines)
			wprintw(status_bar, "%d of %d lines.  ", lines, status_bar_lines);
		wprintw(status_bar, "%s", PRESS_ENTER_MSG);
	}

	wattrset(status_bar, 0);

	update_all_windows();
	/* This is needed because update_all_windows() doesn't call doupdate() if
	 * curr_stats.load_stage == 1. */
	doupdate();
}
Beispiel #8
0
/* Draws single menu item at position specified by line argument.  Non-zero
 * clear argument suppresses drawing current items in different color. */
static void
draw_menu_item(menu_state_t *ms, int pos, int line, int clear)
{
	menu_data_t *const m = ms->d;
	int i;
	int off;
	char *item_tail;
	const int width = (curr_stats.load_stage == 0) ? 100 : getmaxx(menu_win) - 2;

	/* Calculate color for the line. */
	int attrs;
	col_attr_t col = cfg.cs.color[WIN_COLOR];
	if(cfg.hl_search && ms->search_highlight &&
			ms->matches != NULL && ms->matches[pos][0] >= 0)
	{
		cs_mix_colors(&col, &cfg.cs.color[SELECTED_COLOR]);
	}
	if(!clear && pos == m->pos)
	{
		cs_mix_colors(&col, &cfg.cs.color[CURR_LINE_COLOR]);
	}
	attrs = COLOR_PAIR(colmgr_get_pair(col.fg, col.bg)) | col.attr;

	/* Calculate offset of m->hor_pos's character in item text. */
	off = 0;
	i = m->hor_pos;
	while(i-- > 0 && m->items[pos][off] != '\0')
	{
		off += utf8_chrw(m->items[pos] + off);
	}

	item_tail = strdup(m->items[pos] + off);
	replace_char(item_tail, '\t', ' ');

	wattron(menu_win, attrs);

	/* Clear the area. */
	checked_wmove(menu_win, line, 1);
	if(curr_stats.load_stage != 0)
	{
		wprintw(menu_win, "%*s", width, "");
	}

	/* Draw visible part of item. */
	checked_wmove(menu_win, line, 2);
	if(utf8_strsw(item_tail) > (size_t)(width - 2))
	{
		void *p;
		const size_t len = utf8_nstrsnlen(item_tail, width - 3 - 2 + 1);
		memset(item_tail + len, ' ', strlen(item_tail) - len);
		p = realloc(item_tail, len + 4);
		if(p != NULL)
		{
			item_tail = p;
			strcpy(item_tail + len - 1, "...");
		}
		wprint(menu_win, item_tail);
	}
	else
	{
		const size_t len = utf8_nstrsnlen(item_tail, width - 2 + 1);
		item_tail[len] = '\0';
		wprint(menu_win, item_tail);
	}

	wattroff(menu_win, attrs);

	if(ms->search_highlight && ms->matches != NULL && ms->matches[pos][0] >= 0)
	{
		draw_search_match(item_tail, ms->matches[pos][0] - m->hor_pos,
				ms->matches[pos][1] - m->hor_pos, line, width, attrs);
	}

	free(item_tail);
}
Beispiel #9
0
/* Expands macros in the *format string advancing the pointer as it goes.  The
 * opt represents conditional expression state, should be zero for non-recursive
 * calls.  Returns newly allocated string, which should be freed by the
 * caller. */
static LineWithAttrs
parse_view_macros(view_t *view, const char **format, const char macros[],
		int opt)
{
	const dir_entry_t *const curr = get_current_entry(view);
	LineWithAttrs result = { .line = strdup(""), .attrs = strdup("") };
	char c;
	int nexpansions = 0;
	int has_expander = 0;

	if(curr == NULL)
	{
		return result;
	}

	while((c = **format) != '\0')
	{
		size_t width = 0;
		int left_align = 0;
		char buf[PATH_MAX + 1];
		const char *const next = ++*format;
		int skip, ok;

		if(c != '%' ||
				(!char_is_one_of(macros, *next) && !isdigit(*next) &&
				 (*next != '=' || has_expander)))
		{
			if(strappendch(&result.line, &result.line_len, c) != 0)
			{
				break;
			}
			continue;
		}

		if(*next == '=')
		{
			(void)sync_attrs(&result, 0);

			if(strappend(&result.line, &result.line_len, "%=") != 0 ||
					strappendch(&result.attrs, &result.attrs_len, '=') != 0)
			{
				break;
			}
			++*format;
			has_expander = 1;
			continue;
		}

		if(*next == '-')
		{
			left_align = 1;
			++*format;
		}

		while(isdigit(**format))
		{
			width = width*10 + *(*format)++ - '0';
		}
		c = *(*format)++;

		skip = 0;
		ok = 1;
		buf[0] = '\0';
		switch(c)
		{
			case 'a':
				friendly_size_notation(get_free_space(curr_view->curr_dir), sizeof(buf),
						buf);
				break;
			case 't':
				format_entry_name(curr, NF_FULL, sizeof(buf), buf);
				break;
			case 'T':
				if(curr->type == FT_LINK)
				{
					char full_path[PATH_MAX + 1];
					char link_path[PATH_MAX + 1];  //add by sim1
					get_full_path_of(curr, sizeof(full_path), full_path);
					//mod by sim1 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
					if(get_link_target(full_path, link_path, sizeof(link_path)) != 0)
					{
						copy_str(buf, sizeof(buf), "Failed to resolve link");
					}
					else
					{
						snprintf(buf, sizeof(buf), " -> %s", link_path);
					}
					//mod by sim1 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
				}
				break;
			case 'f':
				get_short_path_of(view, curr, NF_FULL, 0, sizeof(buf), buf);
				break;
			case 'A':
#ifndef _WIN32
				get_perm_string(buf, sizeof(buf), curr->mode);
#else
				copy_str(buf, sizeof(buf), attr_str_long(curr->attrs));
#endif
				break;
			case 'u':
				get_uid_string(curr, 0, sizeof(buf), buf);
				break;
			case 'g':
				get_gid_string(curr, 0, sizeof(buf), buf);
				break;
			case 's':
				friendly_size_notation(fentry_get_size(view, curr), sizeof(buf), buf);
				break;
			//add by sim1 ************************************************
			case 'r':
				{
					char path[PATH_MAX] = {0};
					get_full_path_at(view, view->list_pos, sizeof(path), path);
					(void)get_rating_string(buf, sizeof(buf), path);
				}
				break;
			case 'n':
				{
					int nitems = !fentry_is_dir(curr) ? 0 : (int)fentry_get_nitems(view, curr);
					snprintf(buf, sizeof(buf), "%d", nitems);
				}
				break;
			//add by sim1 ************************************************
			case 'E':
				{
					uint64_t size = 0U;

					typedef int (*iter_f)(view_t *view, dir_entry_t **entry);
					/* No current element for visual mode, since it can contain truly
					 * empty selection when cursor is on ../ directory. */
					iter_f iter = vle_mode_is(VISUAL_MODE) ? &iter_selected_entries
					                                       : &iter_selection_or_current;

					dir_entry_t *entry = NULL;
					while(iter(view, &entry))
					{
						size += fentry_get_size(view, entry);
					}

					friendly_size_notation(size, sizeof(buf), buf);
				}
				break;
			case 'd':
				{
					struct tm *tm_ptr = localtime(&curr->mtime);
					strftime(buf, sizeof(buf), cfg.time_format, tm_ptr);
				}
				break;
			case '-':
			case 'x':
				skip = expand_num(buf, sizeof(buf), view->filtered);
				break;
			case 'l':
				skip = expand_num(buf, sizeof(buf), view->list_pos + 1);
				break;
			case 'L':
				skip = expand_num(buf, sizeof(buf), view->list_rows + view->filtered);
				break;
			case 'S':
				skip = expand_num(buf, sizeof(buf), view->list_rows);
				break;
			case '%':
				copy_str(buf, sizeof(buf), "%");
				break;
			case 'z':
				copy_str(buf, sizeof(buf), get_tip());
				break;
			case 'D':
				if(curr_stats.number_of_windows == 1)
				{
					view_t *const other = (view == curr_view) ? other_view : curr_view;
					//mod by sim1
					//copy_str(buf, sizeof(buf), replace_home_part(other->curr_dir));
					snprintf(buf, sizeof(buf), " ‖ %s", replace_home_part(other->curr_dir));
				}
				break;
			case '[':
				{
					LineWithAttrs opt = parse_view_macros(view, format, macros, 1);
					copy_str(buf, sizeof(buf), opt.line);
					free(opt.line);

					char *attrs = opt.attrs;
					if(sync_attrs(&result, 0) && opt.attrs_len > 0U)
					{
						if(*attrs != ' ')
						{
							result.attrs[result.attrs_len - 1U] = *attrs;
						}
						++attrs;
					}
					strappend(&result.attrs, &result.attrs_len, attrs);
					free(opt.attrs);
					break;
				}
			case ']':
				if(opt)
				{
					if(nexpansions == 0)
					{
						replace_string(&result.line, "");
						replace_string(&result.attrs, "");
						result.line_len = 0U;
						result.attrs_len = 0U;
					}
					if(sync_attrs(&result, 0))
					{
						result.attrs[--result.attrs_len] = '\0';
					}
					return result;
				}

				LOG_INFO_MSG("Unmatched %%]");
				ok = 0;
				break;
			case '{':
				{
					/* Try to find matching closing bracket
					 * TODO: implement the way to escape it, so that the expr may contain
					 * closing brackets */
					const char *e = strchr(*format, '}');
					char *expr = NULL, *resstr = NULL;
					var_t res = var_false();
					ParsingErrors parsing_error;

					/* If there's no matching closing bracket, just add the opening one
					 * literally */
					if(e == NULL)
					{
						ok = 0;
						break;
					}

					/* Create a NULL-terminated copy of the given expr.
					 * TODO: we could temporarily use buf for that, to avoid extra
					 * allocation, but explicitly named variable reads better. */
					expr = calloc(e - (*format) + 1 /* NUL-term */, 1);
					memcpy(expr, *format, e - (*format));

					/* Try to parse expr, and convert the res to string if succeed. */
					parsing_error = parse(expr, 0, &res);
					if(parsing_error == PE_NO_ERROR)
					{
						resstr = var_to_str(res);
					}

					if(resstr != NULL)
					{
						copy_str(buf, sizeof(buf), resstr);
					}
					else
					{
						copy_str(buf, sizeof(buf), "<Invalid expr>");
					}

					var_free(res);
					free(resstr);
					free(expr);

					*format = e + 1 /* closing bracket */;
				}
				break;
			case '*':
				if(width > 9)
				{
					snprintf(buf, sizeof(buf), "%%%d*", (int)width);
					width = 0;
					break;
				}
				(void)sync_attrs(&result, 1);
				result.attrs[result.attrs_len - 1] = '0' + width;
				width = 0;
				break;

			default:
				LOG_INFO_MSG("Unexpected %%-sequence: %%%c", c);
				ok = 0;
				break;
		}

		if(char_is_one_of("tTAugsEd", c) && fentry_is_fake(curr))
		{
			buf[0] = '\0';
		}

		if(!ok)
		{
			*format = next;
			if(strappendch(&result.line, &result.line_len, '%') != 0)
			{
				break;
			}
			continue;
		}

		check_expanded_str(buf, skip, &nexpansions);
		stralign(buf, width, ' ', left_align);

		if(strappend(&result.line, &result.line_len, buf) != 0)
		{
			break;
		}
	}

	/* Unmatched %[. */
	if(opt)
	{
		(void)strprepend(&result.line, &result.line_len, "%[");
	}

	if(sync_attrs(&result, 0))
	{
		result.attrs[--result.attrs_len] = '\0';
	}
	return result;
}

/* Makes sure that result->attrs has at least as many elements as result->line
 * contains characters + extra_width.  Returns non-zero if result->attrs has
 * extra characters compared to result->line. */
static int
sync_attrs(LineWithAttrs *result, int extra_width)
{
	const size_t nchars = utf8_strsw(result->line) + extra_width;
	if(result->attrs_len < nchars)
	{
		char *const new_attrs = format_str("%s%*s", result->attrs,
				(int)(nchars - result->attrs_len), "");
		free(result->attrs);
		result->attrs = new_attrs;
		result->attrs_len = nchars;
	}
	return (result->attrs_len > nchars);
}

/* Prints number into the buffer.  Returns non-zero if numeric value is
 * "empty" (zero). */
static int
expand_num(char buf[], size_t buf_len, int val)
{
	snprintf(buf, buf_len, "%d", val);
	return (val == 0);
}
Beispiel #10
0
/* Draws possibly centered formatted message with specified title and control
 * message on error_win. */
static void
draw_msg(const char title[], const char msg[], const char ctrl_msg[],
		int centered, int recommended_width)
{
	enum { margin = 1 };

	int sw, sh;
	int w, h;
	int len;
	size_t ctrl_msg_n;
	size_t wctrl_msg;
	size_t i;

	curs_set(FALSE);

	getmaxyx(stdscr, sh, sw);

	ctrl_msg_n = MAX(measure_sub_lines(ctrl_msg, &wctrl_msg), 1U);

	h = sh - 2 - ctrl_msg_n + !cfg.display_statusline;
	/* The outermost condition is for VLA below (to calm static analyzers). */
	w = MAX(2 + 2*margin, MIN(sw - 2,
	        MAX(MAX(recommended_width, sw/3),
	            (int)MAX(wctrl_msg, determine_width(msg)) + 4)));
	wresize(error_win, h, w);

	werase(error_win);

	len = strlen(msg);
	if(len <= w - 2 && strchr(msg, '\n') == NULL)
	{
		h = 5 + ctrl_msg_n;
		wresize(error_win, h, w);
		mvwin(error_win, (sh - h)/2, (sw - w)/2);
		checked_wmove(error_win, 2, (w - len)/2);
		wprint(error_win, msg);
	}
	else
	{
		int i;
		int cy = 2;
		i = 0;
		while(i < len)
		{
			int j;
			char buf[w - 2 - 2*margin + 1];
			int cx;

			copy_str(buf, sizeof(buf), msg + i);

			for(j = 0; buf[j] != '\0'; j++)
				if(buf[j] == '\n')
					break;

			if(buf[j] != '\0')
				i++;
			buf[j] = '\0';
			i += j;

			if(buf[0] == '\0')
				continue;

			h = cy + 4;
			wresize(error_win, h, w);
			mvwin(error_win, (sh - h)/2, (sw - w)/2);

			cx = centered ? (w - utf8_strsw(buf))/2 : (1 + margin);
			checked_wmove(error_win, cy++, cx);
			wprint(error_win, buf);
		}
	}

	box(error_win, 0, 0);
	if(title[0] != '\0')
	{
		mvwprintw(error_win, 0, (w - strlen(title) - 2)/2, " %s ", title);
	}

	/* Print control message line by line. */
	for(i = ctrl_msg_n; i > 0U; --i)
	{
		const size_t len = strcspn(ctrl_msg, "\n");
		mvwaddnstr(error_win, h - i - 1, MAX(0, (w - (int)len)/2), ctrl_msg, len);
		ctrl_msg = skip_char(ctrl_msg + len + 1U, '/');
	}
}
Beispiel #11
0
/* Draws possibly centered formatted message with specified title and control
 * message on error_win. */
static void
draw_msg(const char title[], const char msg[], const char ctrl_msg[],
		int centered, int recommended_width)
{
	enum { margin = 1 };

	int sw, sh;
	int w, h;
	int max_h;
	int len;
	size_t ctrl_msg_n;
	size_t wctrl_msg;
	size_t i;
	int first_line_x = 1;
	const int first_line_y = 2;

	curs_set(0);

	getmaxyx(stdscr, sh, sw);

	ctrl_msg_n = MAX(measure_sub_lines(ctrl_msg, &wctrl_msg), 1U);

	max_h = sh - 2 - ctrl_msg_n + !cfg.display_statusline;
	h = max_h;
	/* The outermost condition is for VLA below (to calm static analyzers). */
	w = MAX(2 + 2*margin, MIN(sw - 2,
	        MAX(MAX(recommended_width, sw/3),
	            (int)MAX(wctrl_msg, determine_width(msg)) + 4)));
	wresize(error_win, h, w);

	werase(error_win);

	len = strlen(msg);
	if(len <= w - 2 && strchr(msg, '\n') == NULL)
	{
		first_line_x = (w - len)/2;
		h = 5 + ctrl_msg_n;
		wresize(error_win, h, w);
		mvwin(error_win, (sh - h)/2, (sw - w)/2);
		checked_wmove(error_win, first_line_y, first_line_x);
		wprint(error_win, msg);
	}
	else
	{
		int i = 0;
		int cy = first_line_y;
		while(i < len)
		{
			int j;
			char buf[w - 2 - 2*margin + 1];
			int cx;

			copy_str(buf, sizeof(buf), msg + i);

			for(j = 0; buf[j] != '\0'; j++)
				if(buf[j] == '\n')
					break;

			if(buf[j] != '\0')
				i++;
			buf[j] = '\0';
			i += j;

			if(buf[0] == '\0')
				continue;

			if(cy >= max_h - (int)ctrl_msg_n - 3)
			{
				/* Skip trailing part of the message if it's too long, just print how
				 * many lines we're omitting. */
				size_t max_len;
				const int more_lines = 1U + measure_sub_lines(msg + i, &max_len);
				snprintf(buf, sizeof(buf), "<<%d more line%s not shown>>", more_lines,
						(more_lines == 1) ? "" : "s");
				/* Make sure this is the last iteration of the loop. */
				i = len;
			}

			h = 1 + cy + 1 + ctrl_msg_n + 1;
			wresize(error_win, h, w);
			mvwin(error_win, (sh - h)/2, (sw - w)/2);

			cx = centered ? (w - utf8_strsw(buf))/2 : (1 + margin);
			if(cy == first_line_y)
			{
				first_line_x = cx;
			}
			checked_wmove(error_win, cy++, cx);
			wprint(error_win, buf);
		}
	}

	box(error_win, 0, 0);
	if(title[0] != '\0')
	{
		mvwprintw(error_win, 0, (w - strlen(title) - 2)/2, " %s ", title);
	}

	/* Print control message line by line. */
	for(i = ctrl_msg_n; i > 0U; --i)
	{
		const size_t len = strcspn(ctrl_msg, "\n");
		mvwaddnstr(error_win, h - i - 1, MAX(0, (w - (int)len)/2), ctrl_msg, len);
		ctrl_msg = skip_char(ctrl_msg + len + 1U, '/');
	}

	checked_wmove(error_win, first_line_y, first_line_x);
}