poe

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

draw.c (25198B)


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