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 }