poe

Уређивач .po фајлова
git clone https://git.sr.ht/~strahinja/poe
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

draw.c (28152B)


      1 /* This program is licensed under the terms of GNU GPL v3 or (at your option)
      2  * any later version. Copyright (C) 2021-2026  Страхиња Радић.
      3  * See the file LICENSE for exact copyright and license details. */
      4 
      5 #include <assert.h>
      6 #include <errno.h>
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <string.h>
     10 
     11 #include "draw.h"
     12 #include "config.h"
     13 #include "limit.h"
     14 #include "po.h"
     15 #include "util.h"
     16 #include "version.h"
     17 
     18 /* clang-format off */
     19 static const char* help_title = "Main view help";
     20 static const char* help[]     = {
     21 	"Esc         - Close dialog, clear error",
     22 	"Enter       - Edit entry", 
     23 	"H   or F1   - Show this screen",
     24 	"/   or F7   - Incremental search (end = Enter)",
     25 	"n   or F3   - Find next",
     26 	"N   or F4   - Find previous",
     27 	"u   or F8   - Go to next untranslated entry",
     28 	"f   or F9   - Go to next fuzzy entry",
     29 	"w   or C-S  - Save file (with backup)",
     30 	"g   or Home - Go to first entry",
     31 	"C-B or PgUp - Go to previous page",
     32 	"k   or \u2191    - Go to previous entry",
     33 	"j   or \u2193    - Go to next entry",
     34 	"C-F or PgDn - Go to next page",
     35 	"G   or End  - Go to last entry",
     36 	"z   or C-Z  - Toggle fuzzy flag",
     37 	"q   or C-Q  - Quit",
     38 	"",
     39 	"See the manual (man poe) for full help."};
     40 static const char* editbox_title = "Edit translation";
     41 const char* errors[]	       = {
     42 	[ERR_NO_CL_ARG]	       = "No filename given",
     43 	[ERR_NO_CL_ARG_PARAM]  = "Option requires an argument: %s",
     44 	[ERR_BAD_CL_ARG]       = "Invalid argument: %s",
     45 	[ERR_NOTPO_FILE]       = "Not a PO file",
     46 	[ERR_NOTREG_FILE]      = "'%s' is not a regular file",
     47 	[ERR_TB_ERROR]	       = "termbox error",
     48 	[ERR_UNKNOWN_KEY]      = "Unknown key (press F1 for help)",
     49 	[ERR_UNKNOWN_KEY_HELP] = "Unknown key (press H or F1 for help)",
     50 	[ERR_EXIT_KEY]	       = "Press q or C-Q to quit",
     51 	[ERR_DLG_OPEN]	       = "Press Esc to close dialog or q/C-Q to quit",
     52 	[ERR_DLG_OPEN_QUIT]    = "Press Esc to close dialog first",
     53 	[ERR_DLG_FOCUS_EDIT]   = "Switch to edit box with F6 or press Esc",
     54 	[ERR_CANT_ALLOC]       = "Memory allocation failed",
     55 	[ERR_ILLEGAL_ON_FIRST] = "Not allowed on first entry",
     56 	[ERR_SYNTAX]	       = "%s:%ld:%ld: Syntax error",
     57 	[ERR_NO_PLURAL_FORMS]  = "No Plural-Forms (add manually)",
     58 	[ERR_BAD_NPLURALS]     = "Cannot read nplurals",
     59 	[ERR_ERRNO_SET]	       = "System call/library function error"};
     60 const char* actions[]  = {
     61 	[ACT_WRITING]  = "Writing...",
     62 	[ACT_READING]  = "Reading..."
     63 };
     64 const char* flag_strings[]	= {
     65 	[FL_C_FORMAT]		= "c-format",
     66 	[FL_NO_C_FORMAT]	= "no-c-format",
     67 	[FL_FUZZY]		= "fuzzy"};
     68 const char* first_entry_msgid	= "*** SPECIAL: file info ***";
     69 const char* prompt_dirty	= "File changed, save [y/n/ESC]?";
     70 const char* prompt_edit_dirty	= "Entry changed, save [y/n/ESC]?";
     71 const char* prompt_overwrite	= "Not empty, overwrite? [y/n/ESC]?";
     72 /* clang-format on */
     73 
     74 extern int print_error(const int code, const char* msg, ...);
     75 
     76 void
     77 assign_coord(struct Coord* dest, const struct Coord src)
     78 {
     79 	init_coord(dest, src.col, src.row);
     80 }
     81 
     82 /* Calculates buffer length from a display length.
     83  * max_index = the display index up to which the length is calculated
     84  */
     85 ssize_t
     86 buffer_length(uint32_t* buffer, const ssize_t max_index)
     87 {
     88 	uint32_t* pbuf = buffer;
     89 	ssize_t i;
     90 	ssize_t len = 0;
     91 	int w;
     92 
     93 	if (!pbuf)
     94 		return 0;
     95 
     96 	for (i = 0; *pbuf && len < max_index; i++)
     97 	{
     98 		pbuf = &buffer[i];
     99 		if (*pbuf == '\t')
    100 			len += MIN(TAB_SIZE - (len % TAB_SIZE), max_index - len);
    101 		else
    102 		{
    103 			w = wcwidth((wchar_t)*pbuf);
    104 			if (w > 0)
    105 				len += MIN(w, max_index - len);
    106 		}
    107 	}
    108 	return i;
    109 }
    110 
    111 int
    112 calculate_edit_width(const struct DrawState* state)
    113 {
    114 	assert(state != NULL);
    115 	return state->maxx > EDIT_WIDTH + 3 ? EDIT_WIDTH : state->maxx - 3;
    116 }
    117 
    118 int
    119 calculate_help_width(const struct DrawState* state)
    120 {
    121 	assert(state != NULL);
    122 	return state->maxx > HELP_WIDTH + 2 ? HELP_WIDTH : state->maxx - 2;
    123 }
    124 
    125 int
    126 calculate_search_width(const struct DrawState* state)
    127 {
    128 	assert(state != NULL);
    129 	return state->maxx > SEARCH_WIDTH + 2 ? SEARCH_WIDTH : state->maxx - 2;
    130 }
    131 
    132 /* Calculates display length of an input buffer line.
    133  * max_index = the input index up to which the length is calculated
    134  */
    135 ssize_t
    136 display_length(uint32_t* buffer, const ssize_t max_index)
    137 {
    138 	uint32_t* pbuf = buffer;
    139 	ssize_t len    = 0;
    140 	int w;
    141 
    142 	assert(buffer != NULL);
    143 	for (ssize_t i = 0; *pbuf && i < max_index; i++)
    144 	{
    145 		pbuf = &buffer[i];
    146 		if (*pbuf == '\t')
    147 			len += TAB_SIZE - (len % TAB_SIZE);
    148 		else if (!*pbuf)
    149 			len++;
    150 		else
    151 		{
    152 			w = wcwidth((wchar_t)*pbuf);
    153 			if (w > 0)
    154 				len += w;
    155 		}
    156 	}
    157 
    158 	return len;
    159 }
    160 
    161 int
    162 draw(const struct DrawState* state)
    163 {
    164 	assert(state != NULL);
    165 	if (draw_list_visible(state))
    166 		return 1;
    167 	if (state->show_help)
    168 		if (draw_help(state))
    169 			return 1;
    170 	if (state->show_edit)
    171 		if (draw_editbox(state))
    172 			return 1;
    173 	if (state->show_search)
    174 		if (draw_search(state))
    175 			return 1;
    176 	if (draw_status(state))
    177 		return 1;
    178 	return 0;
    179 }
    180 
    181 void
    182 draw_box(const int startx, const int endx, const int starty, const int endy,
    183 	const uint16_t fg, const uint16_t bg)
    184 {
    185 	int x, y;
    186 
    187 	tb_set_cell(startx, starty, BORDER_NW, fg, bg);
    188 	for (x = startx + 1; x < endx - 1; x++)
    189 		tb_set_cell(x, starty, BORDER_HOR, fg, bg);
    190 	tb_set_cell(endx - 1, starty, BORDER_NE, fg, bg);
    191 
    192 	for (y = starty + 1; y < endy - 2; y++)
    193 	{
    194 		tb_set_cell(startx, y, BORDER_VER, fg, bg);
    195 		for (x = startx + 1; x < endx - 1; x++)
    196 			tb_set_cell(x, y, ' ', fg, bg);
    197 		tb_set_cell(endx - 1, y, BORDER_VER, fg, bg);
    198 		tb_set_cell(endx, y, ' ', colors[C_SHADOW].fg,
    199 			colors[C_SHADOW].bg);
    200 	}
    201 
    202 	tb_set_cell(startx, endy - 2, BORDER_SW, fg, bg);
    203 	for (x = startx + 1; x < endx - 1; x++)
    204 		tb_set_cell(x, endy - 2, BORDER_HOR, fg, bg);
    205 	tb_set_cell(endx - 1, endy - 2, BORDER_SE, fg, bg);
    206 	tb_set_cell(endx, endy - 2, ' ', colors[C_SHADOW].fg,
    207 		colors[C_SHADOW].bg);
    208 
    209 	for (x = startx + 1; x < endx + 1; x++)
    210 		tb_set_cell(x, endy - 1, ' ', colors[C_SHADOW].fg,
    211 			colors[C_SHADOW].bg);
    212 }
    213 
    214 int
    215 draw_help(const struct DrawState* state)
    216 {
    217 	int maxx, maxy;
    218 	int row = 0;
    219 	int startx, endx, starty, endy;
    220 	int w, h;
    221 
    222 	assert(state != NULL);
    223 	w    = state->help_width;
    224 	h    = HELP_HEIGHT;
    225 	maxx = state->maxx;
    226 	maxy = state->maxy;
    227 
    228 	startx = w > maxx ? 0 : (maxx - w) / 2;
    229 	endx   = w > maxx ? maxx - 1 : startx + w;
    230 
    231 	starty = h > maxy - 1 ? 0 : (maxy - h) / 2;
    232 	endy   = h > maxy - 1 ? maxy - 2 : starty + h;
    233 
    234 	draw_box(startx, endx, starty, endy, colors[C_DLG].fg, colors[C_DLG].bg);
    235 	if (draw_string(startx, starty, colors[C_TITLE].fg, colors[C_TITLE].bg,
    236 		    help_title, strlen(help_title) + 2, w, 1, 1, CENTER, 0, 0,
    237 		    NULL, 0, 0))
    238 		return 1;
    239 	while ((size_t)row < LEN(help))
    240 	{
    241 		if (draw_string(startx + 1, starty + 1 + row, colors[C_DLG].fg,
    242 			    colors[C_DLG].bg, help[row], w - 2, w - 2, 1, 1,
    243 			    LEFT, 0, 1, NULL, 0, 0))
    244 			return 1;
    245 		row++;
    246 	}
    247 	tb_hide_cursor();
    248 	return 0;
    249 }
    250 
    251 int
    252 draw_editbox(const struct DrawState* state)
    253 {
    254 	struct PoEntry* entry;
    255 	struct BufferLine* curline;
    256 	char flags_buf[MAXFLAGSBUF];
    257 	char sbuf[LINE_DEFAULT];
    258 	ssize_t dlen, i;
    259 	uint16_t fg, bg;
    260 	int maxx, maxy;
    261 	int startx, endx, starty, endy;
    262 	int w, h;
    263 
    264 	assert(state != NULL);
    265 	w     = state->edit_width;
    266 	h     = EDIT_HEIGHT;
    267 	maxx  = state->maxx;
    268 	maxy  = state->maxy;
    269 	entry = state->entries + state->msgid_number - 1;
    270 
    271 	format_flags(flags_buf, MAXFLAGSBUF, entry, 1);
    272 
    273 	startx = w > maxx ? 0 : (maxx - w) / 2;
    274 	endx   = w > maxx ? maxx - 1 : startx + w;
    275 
    276 	starty = h > maxy - 1 ? 0 : (maxy - h) / 2;
    277 	endy   = h > maxy - 1 ? maxy - 2 : starty + h;
    278 
    279 	draw_box(startx, endx, starty, endy, colors[C_DLG].fg, colors[C_DLG].bg);
    280 	if (draw_string(startx + 1, starty, colors[C_TITLE].fg,
    281 		    colors[C_TITLE].bg, editbox_title, strlen(editbox_title) + 2,
    282 		    w - 2, 1, 1, CENTER, 0, 0, NULL, 0, 0))
    283 		return 1;
    284 
    285 	fg = state->edit_info_focused ? colors[C_EDIT_MSGID_FOCUS].fg
    286 				      : colors[C_EDIT_MSGID].fg;
    287 	bg = state->edit_info_focused ? colors[C_EDIT_MSGID_FOCUS].bg
    288 				      : colors[C_EDIT_MSGID].bg;
    289 
    290 	draw_rect(startx + 1, endx - 1, starty + 1, endy - h / 2 - 1, fg, bg);
    291 	for (i = 0; i < (h - 3) / 2 + 1
    292 		&& (ssize_t)state->info_first_shown.row + i
    293 			< state->info_rows_count;
    294 		i++)
    295 	{
    296 		curline = &state->info_buffer[state->info_first_shown.row + i];
    297 		if (!curline || !curline->text)
    298 			return 1;
    299 		dlen = display_length(curline->text, curline->length);
    300 		if (dlen > state->info_first_shown.col)
    301 			u32_draw_string(startx + 1, starty + 1 + i,
    302 				curline->fg == 0 ? fg : curline->fg,
    303 				curline->bg == 0 ? bg : curline->bg,
    304 				curline->text, w - 2, w - 2, 1, 1, LEFT,
    305 				state->info_first_shown.col, 1, NULL, 0, 0);
    306 	}
    307 
    308 	if (state->info_first_shown.row > 0)
    309 		tb_set_cell(endx - 2, starty + 1, ARROW_UP, fg, bg);
    310 	if (state->info_first_shown.row + (h - 3) / 2 < state->info_rows_count)
    311 		tb_set_cell(endx - 2, starty + (h - 3) / 2, ARROW_DOWN, fg, bg);
    312 
    313 	fg = state->edit_info_focused ? colors[C_EDIT_MSGSTR].fg
    314 				      : colors[C_EDIT_MSGSTR_FOCUS].fg;
    315 	bg = state->edit_info_focused ? colors[C_EDIT_MSGSTR].bg
    316 				      : colors[C_EDIT_MSGSTR_FOCUS].bg;
    317 
    318 	draw_rect(startx + 1, endx - 1, endy - h / 2 - 1, endy - 2, fg, bg);
    319 	for (i = 0; i < (h - 3) / 2 + 1
    320 		&& (ssize_t)state->input_first_shown.row + i
    321 			< state->input_rows_count;
    322 		i++)
    323 	{
    324 		curline = &state->input_buffer[state->input_first_shown.row + i];
    325 		if (!curline || !curline->text)
    326 			return 1;
    327 		dlen = display_length(curline->text, curline->length);
    328 		if (dlen > state->input_first_shown.col)
    329 			u32_draw_string(startx + 1, endy - h / 2 - 1 + i, fg,
    330 				bg, curline->text, w - 2, w - 2, 1, 1, LEFT,
    331 				state->input_first_shown.col, 1, NULL, 0, 0);
    332 	}
    333 
    334 	if (state->edit_info_focused)
    335 		tb_hide_cursor();
    336 	else
    337 		tb_set_cursor(startx + 2 + state->input_display_column
    338 				- state->input_first_shown.col,
    339 			endy - h / 2 - 1 + state->coord.row
    340 				- state->input_first_shown.row);
    341 
    342 	if (entry->msgstr_count > 1)
    343 		snprintf(sbuf, LINE_DEFAULT,
    344 			"%d[%d]:%d[%d]/%ld (plural: %d/%d)",
    345 			state->input_display_column,
    346 			state->input_first_shown.col, state->coord.row,
    347 			state->input_first_shown.row, state->input_rows_count,
    348 			state->msgstr_index + 1, entry->msgstr_count);
    349 	else
    350 		snprintf(sbuf, LINE_DEFAULT, "%d[%d]:%d[%d]/%ld",
    351 			state->input_display_column,
    352 			state->input_first_shown.col, state->coord.row,
    353 			state->input_first_shown.row, state->input_rows_count);
    354 	draw_string(startx + 1, endy - 2, colors[C_DLG].fg, colors[C_DLG].bg,
    355 		sbuf, strlen(sbuf) + 2, w - 2, 1, 1, LEFT, 0, 0, NULL, 0, 0);
    356 	if (entry->flags != FL_NONE)
    357 		if (draw_string(endx - MAXFLAGSBUF - 4 - 5, endy - 2,
    358 			    colors[C_DLG].fg, colors[C_DLG].bg, flags_buf,
    359 			    MAXFLAGSBUF + 4 - 1, MAXFLAGSBUF + 4 - 1, 0, 0,
    360 			    LEFT, 0, 0, NULL, 0, 0))
    361 			return 1;
    362 	return 0;
    363 }
    364 
    365 int
    366 draw_list_visible(const struct DrawState* state)
    367 {
    368 	const struct PoEntry* current = NULL;
    369 	ssize_t i		      = 0;
    370 	int cy			      = 0;
    371 	int flags_len;
    372 	int max_screen_entries = 0;
    373 
    374 	assert(state != NULL);
    375 
    376 	if (state->msgid_count == 0)
    377 		return 0;
    378 
    379 	max_screen_entries = state->maxy - 1;
    380 	i		   = state->first_shown_msgid;
    381 
    382 	while (i <= state->real_msgid_count && cy < max_screen_entries)
    383 	{
    384 		char flags_buf[MAXFLAGSBUF];
    385 		int sel;
    386 
    387 		current = state->entries + i - 1;
    388 		if (current->obsolete)
    389 		{
    390 			i++;
    391 			continue;
    392 		}
    393 
    394 		sel = i == state->msgid_number;
    395 
    396 		if (sel)
    397 			draw_rect(0, state->maxx - 1, cy, cy + 1,
    398 				colors[C_LIST_SEL].fg, colors[C_LIST_SEL].bg);
    399 
    400 		format_flags(flags_buf, MAXFLAGSBUF, current, 0);
    401 		flags_len = strlen(flags_buf) + 1;
    402 
    403 		if (draw_string(0, cy,
    404 			    sel ? colors[C_LIST_FLAGS_SEL].fg
    405 				: colors[C_LIST_FLAGS].fg,
    406 			    sel ? colors[C_LIST_FLAGS_SEL].bg
    407 				: colors[C_LIST_FLAGS].bg,
    408 			    flags_buf, flags_len, flags_len, 2, 0, LEFT, 0, 1,
    409 			    NULL, 0, 0))
    410 			return 1;
    411 		if (current->msgid)
    412 			u32_draw_string(flags_len, cy,
    413 				sel ? colors[C_LIST_MSGID_SEL].fg
    414 				    : colors[C_LIST_MSGID].fg,
    415 				sel ? colors[C_LIST_MSGID_SEL].bg
    416 				    : colors[C_LIST_MSGID].bg,
    417 				current->msgid, state->maxx / 2 - flags_len,
    418 				state->maxx / 2 - flags_len, 2, 0, LEFT, 0, 1,
    419 				state->search, colors[C_SEARCH_HL].fg,
    420 				colors[C_SEARCH_HL].bg);
    421 		else if (current == state->entries) /* special */
    422 			draw_string(flags_len, cy,
    423 				sel ? colors[C_LIST_MSGID_SPECIAL_SEL].fg
    424 				    : colors[C_LIST_MSGID_SPECIAL].fg,
    425 				sel ? colors[C_LIST_MSGID_SPECIAL_SEL].bg
    426 				    : colors[C_LIST_MSGID_SPECIAL].bg,
    427 				first_entry_msgid, state->maxx / 2 - flags_len,
    428 				state->maxx / 2 - flags_len, 2, 0, LEFT, 0, 1,
    429 				NULL, 0, 0);
    430 
    431 		/* don't use msgstr_index as then the entire list will shift
    432 		 * when changing currently displayed plural form msgstr in an
    433 		 * edit box */
    434 		if (current->msgstr && current->msgstr_count > 0)
    435 			u32_draw_string(state->maxx / 2, cy,
    436 				current->warning
    437 					? (sel ? colors[C_LIST_MSGSTR_SEL_WARN].fg
    438 					       : colors[C_LIST_MSGSTR_WARN].fg)
    439 					: (sel ? colors[C_LIST_MSGSTR_SEL].fg
    440 					       : colors[C_LIST_MSGSTR].fg),
    441 				current->warning
    442 					? (sel ? colors[C_LIST_MSGSTR_SEL_WARN].bg
    443 					       : colors[C_LIST_MSGSTR_WARN].bg)
    444 					: (sel ? colors[C_LIST_MSGSTR_SEL].bg
    445 					       : colors[C_LIST_MSGSTR].bg),
    446 				*(current->msgstr), state->maxx / 2,
    447 				state->maxx / 2, 2, 2, LEFT, 0, 1,
    448 				state->search, colors[C_SEARCH_HL].fg,
    449 				colors[C_SEARCH_HL].bg);
    450 		i++;
    451 		cy++;
    452 	}
    453 	return 0;
    454 }
    455 
    456 void
    457 draw_rect(const int startx, const int endx, const int starty, const int endy,
    458 	const uint16_t fg, const uint16_t bg)
    459 {
    460 	int x, y;
    461 	for (y = starty; y < endy; y++)
    462 		for (x = startx; x < endx; x++)
    463 			tb_set_cell(x, y, ' ', fg, bg);
    464 }
    465 
    466 int
    467 draw_search(const struct DrawState* state)
    468 {
    469 	ssize_t len;
    470 	ssize_t dlen = 0;
    471 	int maxx, maxy;
    472 	int startx, endx, starty, endy;
    473 	int w, h;
    474 
    475 	assert(state != NULL);
    476 	w    = state->search_width;
    477 	h    = SEARCH_HEIGHT;
    478 	maxx = state->maxx;
    479 	maxy = state->maxy;
    480 	len  = u32_strlen(state->search);
    481 
    482 	startx = w > maxx ? 0 : (maxx - w) / 2;
    483 	endx   = w > maxx ? maxx - 1 : startx + w;
    484 
    485 	starty = maxy - 1 - h;
    486 	endy   = maxy - 1;
    487 
    488 	draw_box(startx, endx, starty, endy, colors[C_SEARCH].fg,
    489 		colors[C_SEARCH].bg);
    490 	draw_rect(startx + 1, endx - 1, starty + 1, endy - 2,
    491 		colors[C_SEARCH_EDIT].fg, colors[C_SEARCH_EDIT].bg);
    492 	dlen = display_length(state->search, len);
    493 	if (dlen > state->search_first_shown_column)
    494 		u32_draw_string(startx + 1, starty + 1,
    495 			colors[C_SEARCH_EDIT].fg, colors[C_SEARCH_EDIT].bg,
    496 			state->search, w - 2, w - 2, 1, 1, LEFT,
    497 			state->search_first_shown_column, 1, NULL, 0, 0);
    498 	tb_set_cursor(startx + 2 + state->search_display_column
    499 			- state->search_first_shown_column,
    500 		starty + 1);
    501 	return 0;
    502 }
    503 
    504 int
    505 draw_status(const struct DrawState* state)
    506 {
    507 	const struct StatusSegment* pseg;
    508 	char* buf = NULL;
    509 	ssize_t seg_size;
    510 	int current_start;
    511 	int last, maxx;
    512 
    513 	assert(state != NULL);
    514 	maxx	      = state->maxx;
    515 	seg_size      = maxx / LEN(status_segments);
    516 	current_start = 0;
    517 	pseg	      = status_segments;
    518 
    519 	if (state->in_prompt && *state->prompt)
    520 	{
    521 		if (draw_string(0, state->maxy - 1, colors[C_PROMPT].fg,
    522 			    colors[C_PROMPT].bg, state->prompt,
    523 			    strlen(state->prompt) + 2, maxx, 1, 1, CENTER, 0, 1,
    524 			    NULL, 0, 0))
    525 			return 1;
    526 	}
    527 	else if (state->action)
    528 	{
    529 		if (draw_string(0, state->maxy - 1, colors[C_ACTION].fg,
    530 			    colors[C_ACTION].bg, actions[state->action],
    531 			    strlen(actions[state->action]) + 2, maxx, 1, 1,
    532 			    CENTER, 0, 1, NULL, 0, 0))
    533 			return 1;
    534 	}
    535 	else if (state->error)
    536 	{
    537 		if (draw_string(0, state->maxy - 1, colors[C_ERROR].fg,
    538 			    colors[C_ERROR].bg, errors[state->error],
    539 			    strlen(errors[state->error]) + 2, maxx, 1, 1,
    540 			    CENTER, 0, 1, NULL, 0, 0))
    541 			return 1;
    542 	}
    543 	else
    544 		while (pseg < status_segments + LEN(status_segments))
    545 		{
    546 			last = pseg - status_segments
    547 				== LEN(status_segments) - 1;
    548 			buf = calloc(limits[L_LINE_MAX], 1);
    549 			if (!buf)
    550 				return 1;
    551 
    552 			*buf = 0;
    553 			if (pseg->callback)
    554 				pseg->callback(buf, limits[L_LINE_MAX],
    555 					pseg->format, seg_size, state);
    556 			else
    557 				memccpy(buf, pseg->format, 0,
    558 					limits[L_LINE_MAX]);
    559 			if (pseg == status_segments + LEN(status_segments) - 1)
    560 				seg_size = maxx - 1 - current_start;
    561 			if (draw_string(current_start, state->maxy - 1,
    562 				    colors[pseg->color_index].fg,
    563 				    colors[pseg->color_index].bg, buf,
    564 				    MIN(seg_size, (ssize_t)(strlen(buf) + 2)),
    565 				    last ? seg_size + 1 : seg_size, 1, 1,
    566 				    pseg->alignment, 0, 1, NULL, 0, 0))
    567 			{
    568 				free(buf);
    569 				return 1;
    570 			}
    571 			free(buf);
    572 			pseg++;
    573 			current_start += seg_size;
    574 		}
    575 	return 0;
    576 }
    577 
    578 /*  mc  - (max cols) pack text and padding into this many columns
    579  *  fc  - (fill cols) this many columns from the left/right/center will be
    580  *  	  filled
    581  *  p_? - (padding) this many cells will be skipped from ? side
    582  *  ds  - (display start) string starts being displayed from this display column
    583  * 	  counted from the start of the string; an "entry point" joining string
    584  * 	  index with screen
    585  * fill - boolean; 1 = fill fc, 0 = don't fill fc
    586  * srch - search string; if empty or NULL no highlighting happens
    587  * sfg  - fg color of search highlght
    588  * sbg  - bg color of search highlight
    589  * */
    590 int
    591 draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg,
    592 	const char* s, const int mc, const int fc, const int p_l, const int p_r,
    593 	const Alignment align, const int ds, const int fill,
    594 	const uint32_t* srch, const uint16_t sfg, const uint16_t sbg)
    595 {
    596 	uint32_t* us = NULL;
    597 	assert(s != NULL);
    598 	us = calloc(strlen(s) + 1, sizeof(uint32_t));
    599 	if (!us)
    600 		return 1;
    601 	u8_string_to_unicode(us, s, mc);
    602 	u32_draw_string(x, y, fg, bg, us, mc, fc, p_l, p_r, align, ds, fill,
    603 		srch, sfg, sbg);
    604 	free(us);
    605 	return 0;
    606 }
    607 
    608 void
    609 format_filename(char* result, ssize_t result_size, const char* format,
    610 	const ssize_t seg_size, const struct DrawState* state)
    611 {
    612 	char* basename	  = NULL;
    613 	const char* pbase = NULL;
    614 	int dirty;
    615 
    616 	assert((state != NULL) && (format != NULL));
    617 	if (!result || seg_size < 4)
    618 		return;
    619 
    620 	pbase	 = u8_basename(state->filename);
    621 	basename = strndup(pbase, strlen(pbase) + 1);
    622 	dirty	 = state->edit_dirty || state->dirty;
    623 	if ((ssize_t)(u8_strlen(basename) + 2) >= seg_size)
    624 	{
    625 		if (dirty)
    626 			basename[seg_size - 4] = 0;
    627 		else
    628 			basename[seg_size - 3] = 0;
    629 		snprintf(result, result_size, format, basename,
    630 			dirty ? "…*" : "…");
    631 	}
    632 	else
    633 		snprintf(result, result_size, format, basename,
    634 			dirty ? "*" : "");
    635 	free(basename);
    636 }
    637 
    638 void
    639 format_msgs(char* result, ssize_t result_size, const char* format,
    640 	const ssize_t seg_size, const struct DrawState* state)
    641 {
    642 	assert((state != NULL) && (format != NULL));
    643 	UNUSED(seg_size);
    644 	if (!result)
    645 		return;
    646 
    647 	snprintf(result, result_size, format,
    648 		state ? real_msgid_to_display_msgid(state) : 0,
    649 		state ? state->msgid_count : 0,
    650 		state ? state->untranslated_count : 0,
    651 		state ? state->fuzzy_count : 0,
    652 		state ? state->obsolete_count : 0);
    653 }
    654 
    655 void
    656 free_bufferline(struct BufferLine* bl)
    657 {
    658 	if (!bl)
    659 		return;
    660 	free(bl->text);
    661 	bl->text   = NULL;
    662 	bl->length = 0;
    663 	bl->fg	   = 0;
    664 	bl->bg	   = 0;
    665 }
    666 
    667 void
    668 free_drawstate(struct DrawState* state)
    669 {
    670 	ssize_t row = 0;
    671 	assert(state != NULL);
    672 	for (row = 0; row < state->input_rows_count; row++)
    673 		free_bufferline(state->input_buffer + row);
    674 	free(state->input_buffer);
    675 	for (row = 0; row < state->info_rows_count; row++)
    676 		free_bufferline(state->info_buffer + row);
    677 	free(state->info_buffer);
    678 	for (row = 0; row < state->real_msgid_count; row++)
    679 		free_po_entry(state->entries + row);
    680 	free(state->search);
    681 	free(state->entries);
    682 }
    683 
    684 void
    685 init_bufferline(struct BufferLine* bl)
    686 {
    687 	if (!bl)
    688 		return;
    689 	bl->text   = NULL;
    690 	bl->length = 0;
    691 	bl->fg	   = 0;
    692 	bl->bg	   = 0;
    693 }
    694 
    695 void
    696 init_coord(struct Coord* c, const int col, const int row)
    697 {
    698 	assert(c != NULL);
    699 	c->col = col;
    700 	c->row = row;
    701 }
    702 
    703 void
    704 init_drawstate(struct DrawState* state, char* filename, char* prompt)
    705 {
    706 	assert(state != NULL);
    707 	state->prompt		  = prompt;
    708 	state->filename		  = filename;
    709 	state->orig_real_filename = NULL;
    710 	state->real_filename	  = NULL;
    711 	state->backup_filename	  = NULL;
    712 	state->rfn_len		  = 0;
    713 	state->search		  = NULL;
    714 	state->error		  = ERR_NONE;
    715 	state->action		  = ACT_IDLE;
    716 	state->input_buffer	  = NULL;
    717 	state->buffer_size	  = 0;
    718 	init_coord(&state->coord, 0, 0);
    719 	init_coord(&state->first_shown, 0, 0);
    720 	init_coord(&state->input_first_shown, 0, 0);
    721 	state->input_rows_count	    = 0;
    722 	state->input_display_column = 0;
    723 	state->input_changed_fuzzy  = UNCHANGED;
    724 	state->saved_column	    = -1;
    725 	state->info_buffer	    = NULL;
    726 	init_coord(&state->info_first_shown, 0, 0);
    727 	state->info_rows_count		 = 0;
    728 	state->info_maxlen		 = 0;
    729 	state->edit_width		 = calculate_edit_width(state);
    730 	state->help_width		 = calculate_help_width(state);
    731 	state->search_width		 = calculate_search_width(state);
    732 	state->paste			 = NULL;
    733 	state->paste_count		 = 0;
    734 	state->paste_size		 = 0;
    735 	state->search_column		 = 0;
    736 	state->search_first_shown_column = 0;
    737 	state->search_display_column	 = 0;
    738 	state->entries			 = NULL;
    739 	state->nplurals			 = 2;
    740 	state->prompt_callback		 = NULL;
    741 	state->running			 = 1;
    742 	state->dirty			 = 0;
    743 	state->edit_dirty		 = 0;
    744 	state->fuzzy_before_edit	 = 0;
    745 	state->in_prompt		 = 0;
    746 	state->reshow_edit		 = 0;
    747 	state->show_edit		 = 0;
    748 	state->show_help		 = 0;
    749 	state->edit_info_focused	 = 0;
    750 	state->show_search		 = 0;
    751 	state->recalculate_size		 = 1;
    752 	state->maxx			 = 0;
    753 	state->maxy			 = 0;
    754 	state->msgid_count		 = 0;
    755 	state->msgid_number		 = state->msgid_count > 0 ? 1 : 0;
    756 	state->msgid_size		 = 0;
    757 	state->fuzzy_count		 = 0;
    758 	state->untranslated_count	 = 0;
    759 	state->obsolete_count		 = 0;
    760 	state->obsolete_at_end_count	 = 0;
    761 	state->real_msgid_count		 = 0;
    762 	state->msgstr_index		 = 0;
    763 	state->first_shown_msgid	 = state->msgid_number;
    764 	state->backup_possible		 = 1;
    765 }
    766 
    767 /* Calculates input length from a display length.
    768  * max_index = the display index up to which the length is calculated
    769  */
    770 ssize_t
    771 input_length(uint32_t* buffer, const ssize_t max_index)
    772 {
    773 	uint32_t* pbuf = buffer;
    774 	ssize_t i      = 0;
    775 	ssize_t len    = 0;
    776 
    777 	assert(buffer != NULL);
    778 	for (i = 0; *pbuf && len < max_index; i++)
    779 	{
    780 		pbuf = buffer + i;
    781 		if (*pbuf == '\t')
    782 			len += MIN(TAB_SIZE - (len % TAB_SIZE), max_index - len);
    783 		else
    784 			len++;
    785 	}
    786 
    787 	return i;
    788 }
    789 
    790 size_t
    791 real_msgid_to_display_msgid(const struct DrawState* state)
    792 {
    793 	struct PoEntry* current;
    794 	size_t ret = 1;
    795 
    796 	assert((state != NULL) && (state->entries != NULL));
    797 	current = state->entries;
    798 	if (!state->msgid_count)
    799 		return 0;
    800 
    801 	while (current != state->entries + state->msgid_number - 1)
    802 	{
    803 		if (current != state->entries + state->msgid_number - 1
    804 			&& !current->obsolete)
    805 			ret++;
    806 		current++;
    807 	}
    808 
    809 	return ret;
    810 }
    811 
    812 void
    813 recalculate_size(struct DrawState* state)
    814 {
    815 	assert(state != NULL);
    816 	state->maxx	    = tb_width();
    817 	state->maxy	    = tb_height();
    818 	state->help_width   = calculate_help_width(state);
    819 	state->edit_width   = calculate_edit_width(state);
    820 	state->search_width = calculate_search_width(state);
    821 }
    822 
    823 /*  mc  - (max cols) pack text and padding into this many columns
    824  *  fc  - (fill cols) this many columns from the left/right/center will be
    825  *  	  filled
    826  *  p_? - (padding) this many cells will be skipped from ? side
    827  *  ds  - (display start) string starts being displayed from this display column
    828  * 	  counted from the start of the string; an "entry point" joining string
    829  * 	  index with screen
    830  * fill - boolean; 1 = fill fc, 0 = don't fill fc
    831  * srch - search string; if empty or NULL no highlighting happens
    832  * sfg  - fg color of search highlght
    833  * sbg  - bg color of search highlight
    834  * */
    835 void
    836 u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg,
    837 	const uint32_t* s, const int mc, const int fc, const int p_l,
    838 	const int p_r, const Alignment align, const int ds, const int fill,
    839 	const uint32_t* srch, const uint16_t sfg, const uint16_t sbg)
    840 {
    841 	/*
    842 	 *                   <-------- visible part ---------->
    843 	 *        +---------+-------+----------------+---------+
    844 	 *        |         |       |                |         |
    845 	 *        |<-- d -->|<-p_l->|<------ c ----->|<- p_r ->|
    846 	 *        |         |<-------------- mc -------------->|
    847 	 *        |<------------------ fc -------------------->|
    848 	 *        x                 |                |
    849 	 *                          ds               ds+c
    850 	 *                           <.....(sc).....>
    851 	 *                      (align == RIGHT in this case)
    852 	 * */
    853 
    854 	int d = 0; /* distance, in screen columns, from x to
    855 		    * visible part of the string, followed by
    856 		    * p_l */
    857 
    858 	int c = 0; /* distance in screen columns from
    859 		    * d + p_l to d + mc - p_r (part of the
    860 		    * displayed string containing text, between
    861 		    * padding; string fits within this many columns
    862 		    * */
    863 
    864 	int sc = 0; /* current screen column; goes from 0..c-1
    865 		     * only increased when actual characters are
    866 		     * written
    867 		     */
    868 
    869 	int ssc = 0; /* current string screen column; goes from start
    870 		      * of the string until ds+c */
    871 
    872 	const int ts = TAB_SIZE;
    873 
    874 	const uint32_t* ps = s; /* pointer to current character in string;
    875 				 * string index: ps-s */
    876 
    877 	uint16_t cfg = fg; /* current fg */
    878 	uint16_t cbg = bg; /* current bg */
    879 
    880 	int hctl = 0; /* highlight "characters to live", when this
    881 		       * reaches zero the highlighting is turned
    882 		       * off */
    883 
    884 	int j;
    885 
    886 	c = mc - p_l - p_r;
    887 
    888 	if (align == LEFT)
    889 		d = 0;
    890 	else if (align == RIGHT)
    891 		d = fc >= mc ? fc - mc : 0;
    892 	else if (align == CENTER)
    893 		d = fc >= mc ? (fc - mc) / 2 : 0;
    894 
    895 	/* fill */
    896 	if (fill)
    897 		for (j = 0; j < fc; j++)
    898 			tb_set_cell(x + j, y, ' ', fg, bg);
    899 
    900 	/* left padding */
    901 	for (j = 0; j < p_l; j++)
    902 		tb_set_cell(x + d + j, y, ' ', fg, bg);
    903 
    904 	sc = 0;
    905 
    906 	/*
    907 	 * main idea: we go from start of the string to the end and count
    908 	 * displayed characters until we get to ds, then we also output
    909 	 * characters; tabs count as spaces up to multiple of ts, counted from
    910 	 * the start of the string
    911 	 *
    912 	 * exit condition is also sc == c, when we have reached the maximum
    913 	 * number of characters to output
    914 	 * */
    915 	while (ps && *ps && sc < c)
    916 	{
    917 		if (srch && *srch && hctl == 0)
    918 		{
    919 			if (u32_starts_with(ps, srch))
    920 			{
    921 				hctl = u32_strlen(srch);
    922 				cfg  = sfg;
    923 				cbg  = sbg;
    924 			}
    925 		}
    926 
    927 		if (*ps == '\t')
    928 		{
    929 			if (ssc < ds)
    930 			{
    931 				/*                     ssc-(ssc%ts)+ts
    932 				 *              \t     |  = ssc+(ts-(ssc%ts))
    933 				 *  ----------|-----[ :|::: ]
    934 				 *            | |     |
    935 				 *            | |     ds
    936 				 * ssc-(ssc%ts) |
    937 				 *              ssc
    938 				 * */
    939 				if (ssc - (ssc % ts) + ts >= ds)
    940 				{
    941 					for (j = 0;
    942 						j < ssc - (ssc % ts) + ts - ds;
    943 						j++)
    944 						tb_set_cell(x + d + p_l + sc + j,
    945 							y, ' ', cfg, cbg);
    946 					sc += ssc + (ts - (ssc % ts)) - ds;
    947 					hctl -= ssc + (ts - (ssc % ts)) - ds;
    948 					if (hctl < 0)
    949 						hctl = 0;
    950 				}
    951 				/* else, just increase counter
    952 				 *
    953 				 * \t
    954 				 * ----------|----[ ::::: ]
    955 				 * |         |      |
    956 				 * |         |      ds
    957 				 * |         ssc-(ssc%ts)+ts
    958 				 * ssc         = ssc + (ts-(ssc%ts))
    959 				 * */
    960 				ssc += (ts - (ssc % ts));
    961 			}
    962 			else /* ssc >= ds */
    963 			{
    964 				/*
    965 				 *   ds          ssc+(ts-(ssc%ts))
    966 				 *   |   \t      |
    967 				 * [ ::::::: ]---|-----
    968 				 *       |  |
    969 				 *       |  ds+c
    970 				 *       ssc
    971 				 * */
    972 				if (ssc + (ts - (ssc % ts)) >= ds + c)
    973 				{
    974 					for (j = 0; j < ds + c - ssc; j++)
    975 						tb_set_cell(x + d + p_l + sc + j,
    976 							y, ' ', cfg, cbg);
    977 					sc   = c;
    978 					ssc  = ds + c;
    979 					hctl = 0;
    980 					cfg  = fg;
    981 					cbg  = bg;
    982 				}
    983 				else /* ssc+(ts-(ssc%ts)) < ds+c */
    984 				{
    985 					/*
    986 					 *      \t
    987 					 * [ :::::::::::|::: ]-----
    988 					 *   |  |       |   |
    989 					 *   |  |       |   ds+c
    990 					 *   |  |       ssc+(ts-(ssc%ts))
    991 					 *   |  ssc
    992 					 *   ds
    993 					 * */
    994 					for (j = 0; j < ts - (ssc % ts); j++)
    995 						tb_set_cell(x + d + p_l + sc + j,
    996 							y, ' ', cfg, cbg);
    997 					sc += ts - (ssc % ts);
    998 					hctl -= ts - (ssc % ts);
    999 					if (hctl < 0)
   1000 						hctl = 0;
   1001 					ssc += ts - (ssc % ts);
   1002 				}
   1003 			}
   1004 		}
   1005 		else
   1006 		{
   1007 			if (ssc >= ds)
   1008 			{
   1009 				if (sc < c)
   1010 					tb_set_cell(x + d + p_l + sc, y, *ps,
   1011 						cfg, cbg);
   1012 				sc++;
   1013 				hctl--;
   1014 				if (hctl < 0)
   1015 					hctl = 0;
   1016 			}
   1017 			ssc++;
   1018 		}
   1019 		if (hctl == 0)
   1020 		{
   1021 			cfg = fg;
   1022 			cbg = bg;
   1023 		}
   1024 		ps++;
   1025 	}
   1026 
   1027 	/* right padding */
   1028 	for (j = 0; j < p_r; j++)
   1029 		tb_set_cell(x + d + p_l + c + j, y, ' ', fg, bg);
   1030 }