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 }