| 1 | #include <ncurses.h> |
| 2 | #include <stdbool.h> |
| 3 | #include <strings.h> |
| 4 | #include <stddef.h> |
| 5 | #include <string.h> |
| 6 | #include <locale.h> |
| 7 | #include <stdlib.h> |
| 8 | #include <ctype.h> |
| 9 | #include <stdio.h> |
| 10 | #include <time.h> |
| 11 | |
| 12 | #include "ui.h" |
| 13 | #include "dir.h" |
| 14 | #include "main.h" |
| 15 | #include "config.h" |
| 16 | #include "cvector.h" |
| 17 | |
| 18 | #define ARRLEN(x) ((sizeof(x)) / (sizeof*(x))) |
| 19 | |
| 20 | #define MAX(x, y) (((x) > (y)) ? (x) : (y)) |
| 21 | #define MIN(x, y) (((x) > (y)) ? (y) : (x)) |
| 22 | |
| 23 | #define TITLEWINSTARTY 0 |
| 24 | #define TITLEWINLINES 1 |
| 25 | #define TITLEWINCOLS COLS |
| 26 | |
| 27 | #define STATUSWINSTARTY 1 |
| 28 | #define STATUSWINLINES 1 |
| 29 | #define STATUSWINCOLS COLS |
| 30 | |
| 31 | #define FILEWINSTARTY 2 |
| 32 | #define FILEWINLINES (LINES - 3) |
| 33 | #define FILEWINCOLS COLS |
| 34 | |
| 35 | #define INPUTWINSTARTY (LINES - 1) |
| 36 | #define INPUTWINLINES 1 |
| 37 | #define INPUTWINCOLS COLS |
| 38 | |
| 39 | #define PROMPTWINSTARTY (LINES / 2) |
| 40 | #define PROMPTWINSTARTX (COLS / 2) |
| 41 | #define PROMPTWINMAXLINES (LINES * 3 / 4) |
| 42 | #define PROMPTWINMAXCOLS (COLS * 3 / 4) |
| 43 | |
| 44 | #define INPUTBUFSZ 2048 |
| 45 | |
| 46 | static WINDOW * titlewin = NULL; |
| 47 | static WINDOW * statuswin = NULL; |
| 48 | static WINDOW * filewin = NULL; |
| 49 | static WINDOW * inputwin = NULL; |
| 50 | |
| 51 | static void win_set(WINDOW * win, const char * str, int attrs); |
| 52 | static int ui_dirent_color(const dirent_t * de); |
| 53 | static int ui_link_color(const dirent_t * de); |
| 54 | static void ui_print_dirent(const dirent_t * de, size_t pos, bool selected, const dir_t * dir); |
| 55 | static void ui_wlpadstr(WINDOW * win, const char * s, size_t len); |
| 56 | static void ui_wrpadstr(WINDOW * win, const char * s, size_t len); |
| 57 | static void ui_get_first_last(size_t n_dirents, size_t cursor, size_t * first, size_t * last); |
| 58 | |
| 59 | void ui_init(void) { |
| 60 | setlocale(LC_CTYPE, ""); |
| 61 | |
| 62 | initscr(); |
| 63 | raw(); |
| 64 | keypad(stdscr, TRUE); |
| 65 | curs_set(0); |
| 66 | noecho(); |
| 67 | |
| 68 | titlewin = newwin(TITLEWINLINES, TITLEWINCOLS, TITLEWINSTARTY, 0); |
| 69 | statuswin = newwin(STATUSWINLINES, STATUSWINCOLS, STATUSWINSTARTY, 0); |
| 70 | filewin = newwin(FILEWINLINES, FILEWINCOLS, FILEWINSTARTY, 0); |
| 71 | inputwin = newwin(INPUTWINLINES, INPUTWINCOLS, INPUTWINSTARTY, 0); |
| 72 | |
| 73 | clear(); |
| 74 | refresh(); |
| 75 | |
| 76 | use_default_colors(); |
| 77 | start_color(); |
| 78 | |
| 79 | init_pair(COLOR_FILE, -1, -1); |
| 80 | init_pair(COLOR_DIR, COLOR_BLUE, -1); |
| 81 | init_pair(COLOR_FIFO, COLOR_YELLOW, -1); |
| 82 | init_pair(COLOR_LINK, COLOR_CYAN, -1); |
| 83 | init_pair(COLOR_BLOCK, COLOR_YELLOW, -1); |
| 84 | init_pair(COLOR_CHAR, COLOR_YELLOW, -1); |
| 85 | init_pair(COLOR_SOCKET, COLOR_MAGENTA, -1); |
| 86 | init_pair(COLOR_UNKNOWN, COLOR_RED, -1); |
| 87 | init_pair(COLOR_EXEC, COLOR_GREEN, -1); |
| 88 | |
| 89 | init_pair(RED, COLOR_RED, -1); |
| 90 | init_pair(YELLOW, COLOR_YELLOW, -1); |
| 91 | init_pair(MAGENTA, COLOR_MAGENTA, -1); |
| 92 | init_pair(WHITE, -1, -1); |
| 93 | } |
| 94 | |
| 95 | void ui_deinit(void) { |
| 96 | endwin(); |
| 97 | } |
| 98 | |
| 99 | void ui_reinit(void) { |
| 100 | refresh(); |
| 101 | ui_refresh(); |
| 102 | } |
| 103 | |
| 104 | void win_set(WINDOW * win, const char * str, int attrs) { |
| 105 | werase(win); |
| 106 | wattron(win, attrs); |
| 107 | waddstr(win, str); |
| 108 | wattroff(win, attrs); |
| 109 | } |
| 110 | |
| 111 | void ui_set_title(const char * title, const char * home) { |
| 112 | size_t homelen = strlen(home); |
| 113 | if (strncmp(title, home, homelen) == 0) { |
| 114 | title += homelen; |
| 115 | win_set(titlewin, "~", 0); |
| 116 | waddstr(titlewin, title); |
| 117 | } else { |
| 118 | win_set(titlewin, title, 0); |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | void ui_status_info(const char * status) { |
| 123 | win_set(statuswin, status, 0); |
| 124 | } |
| 125 | |
| 126 | void ui_status_error(const char * status) { |
| 127 | win_set(statuswin, status, COLOR_PAIR(RED)); |
| 128 | } |
| 129 | |
| 130 | void ui_erase(void) { |
| 131 | werase(filewin); |
| 132 | werase(inputwin); |
| 133 | } |
| 134 | |
| 135 | void ui_refresh(void) { |
| 136 | wnoutrefresh(titlewin); |
| 137 | wnoutrefresh(statuswin); |
| 138 | wnoutrefresh(filewin); |
| 139 | wnoutrefresh(inputwin); |
| 140 | doupdate(); |
| 141 | } |
| 142 | |
| 143 | static void ui_wlpadstr(WINDOW * win, const char * s, size_t len) { |
| 144 | size_t slen = strlen(s); |
| 145 | |
| 146 | for (size_t i = slen; i < len; i++) { |
| 147 | waddch(win, ' '); |
| 148 | } |
| 149 | |
| 150 | waddstr(win, s); |
| 151 | } |
| 152 | |
| 153 | static void ui_wrpadstr(WINDOW * win, const char * s, size_t len) { |
| 154 | size_t slen = strlen(s); |
| 155 | |
| 156 | waddstr(win, s); |
| 157 | |
| 158 | for (size_t i = slen; i < len; i++) { |
| 159 | waddch(win, ' '); |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | void ui_print_dirent(const dirent_t * de, size_t pos, bool selected, const dir_t * dir) { |
| 164 | static const enum ui_color mode_colors[] = { |
| 165 | ['r'] = RED, ['w'] = MAGENTA, ['x'] = COLOR_EXEC, |
| 166 | ['s'] = YELLOW, ['S'] = YELLOW, ['t'] = RED, |
| 167 | ['T'] = RED, ['-'] = WHITE, ['?'] = RED, |
| 168 | |
| 169 | ['.'] = WHITE, |
| 170 | ['d'] = COLOR_DIR, ['b'] = COLOR_BLOCK, ['c'] = COLOR_CHAR, |
| 171 | ['l'] = COLOR_LINK, ['|'] = COLOR_FIFO, ['='] = COLOR_SOCKET, |
| 172 | }; |
| 173 | |
| 174 | static char stime[128]; |
| 175 | static char isbuf[40]; // should be big enough to hold any integer |
| 176 | |
| 177 | size_t lines, cols; |
| 178 | getmaxyx(filewin, lines, cols); |
| 179 | |
| 180 | size_t dirlen = dir_len(dir); |
| 181 | |
| 182 | if (config.longmode && (config.longinline || (!config.longinline && selected))) { |
| 183 | if (config.longinline) wmove(filewin, pos, 0); |
| 184 | else { |
| 185 | // place the longmode info right on top of the "cursor" info |
| 186 | if (dirlen < lines - 3) wmove(filewin, dirlen + 1, 0); |
| 187 | else wmove(filewin, lines - 2, 0); |
| 188 | } |
| 189 | |
| 190 | const char * pmode = dirent_prettymode(de); |
| 191 | for (const char * p = pmode; *p; p++) { |
| 192 | int color = COLOR_PAIR(mode_colors[(unsigned)*p]); |
| 193 | if (*p == '-' || *p == '.') color |= A_DIM; |
| 194 | wattron(filewin, color); |
| 195 | waddch(filewin, *p); |
| 196 | wattroff(filewin, color); |
| 197 | } |
| 198 | |
| 199 | if (de->uname) ui_wlpadstr(filewin, de->uname, dir->longest_uname + 1); |
| 200 | else waddstr(filewin, " ?"); |
| 201 | |
| 202 | if (de->gname) ui_wlpadstr(filewin, de->gname, dir->longest_gname + 1); |
| 203 | else waddstr(filewin, " ?"); |
| 204 | |
| 205 | if (config.humansize) { |
| 206 | const char * unit = dirent_size_unit(de); |
| 207 | snprintf(isbuf, sizeof(isbuf), "%u", de->size_small); |
| 208 | ui_wlpadstr(filewin, isbuf, dir->longest_size_small + 1); |
| 209 | ui_wrpadstr(filewin, unit, dir->longest_size_unit); |
| 210 | } else { |
| 211 | snprintf(isbuf, sizeof(isbuf), "%zu", de->size); |
| 212 | ui_wlpadstr(filewin, isbuf, dir->longest_size + 1); |
| 213 | } |
| 214 | |
| 215 | strftime(stime, sizeof(stime), "%b %d %H:%M %Y", localtime(&de->mtime)); |
| 216 | wprintw(filewin, " %s ", stime); |
| 217 | } |
| 218 | |
| 219 | if (!config.longmode || (config.longmode && !config.longinline)) |
| 220 | wmove(filewin, pos, 0); |
| 221 | |
| 222 | int color = COLOR_PAIR(ui_dirent_color(de)); |
| 223 | if (selected) color |= A_REVERSE; |
| 224 | if (de->marked) waddch(filewin, '+'); |
| 225 | else waddch(filewin, ' '); |
| 226 | wattron(filewin, color); |
| 227 | waddstr(filewin, de->name); |
| 228 | wattroff(filewin, color); |
| 229 | char c = dirent_crepr(de); |
| 230 | if (c) waddch(filewin, c); |
| 231 | |
| 232 | if (de->type == DE_LINK) { |
| 233 | waddstr(filewin, " -> "); |
| 234 | color = COLOR_PAIR(ui_link_color(de)); |
| 235 | if (selected) color |= A_REVERSE; |
| 236 | wattron(filewin, color); |
| 237 | waddstr(filewin, de->linkname); |
| 238 | wattroff(filewin, color); |
| 239 | char c = dirent_creprl(de); |
| 240 | if (c) waddch(filewin, c); |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | static int ui_local_bsearch_str_compar(const void * a, const void * b) { |
| 245 | return strcasecmp(*(const char**)a, *(const char**)b); |
| 246 | } |
| 247 | |
| 248 | static int ui_extension_color(const char * name) { |
| 249 | static const char * const mediaexts[] = { |
| 250 | "3g2", "3gp", "ai", "amv", "asf", "avi", |
| 251 | "bmp", "drc", "eps", "f4p", "f4v", "flv", |
| 252 | "gif", "gifv", "h264", "heif", "ico", "jpeg", |
| 253 | "jpg", "m2ts", "m2v", "m4v", "mkv", "mng", |
| 254 | "mov", "mp4", "mpeg", "mpg", "mts", "mxf", |
| 255 | "nsv", "ogv", "pbm", "pgm", "png", "pnm", |
| 256 | "ppm", "ps", "psd", "qt", "rm", "rmvb", |
| 257 | "roq", "svg", "svi", "tif", "tiff", "ts", |
| 258 | "viv", "vob", "webm", "webp", "wmv", "yuv", |
| 259 | }; |
| 260 | static const char * const archiveexts[] = { |
| 261 | "1", "7z", "7zip", "a", "alz", "bz2", |
| 262 | "cbr", "cpgz", "cso", "dar", "dbz", "deb", |
| 263 | "dz", "ear", "gip", "gz", "htmlz", "igz", |
| 264 | "ipa", "lz", "lz4", "maff", "mpq", "npk", |
| 265 | "nxz", "pkg", "pup", "pz", "pzip", "rar", |
| 266 | "rpa", "rpm", "sar", "shar", "sis", "sisx", |
| 267 | "tar", "tgz", "txz", "uha", "vsix", "xap", |
| 268 | "xz", "xzm", "xzn", "z", "zab", "zi", |
| 269 | "zip", "zlib", "zst", "zstd", "zxp", "zz", |
| 270 | }; |
| 271 | // uncomment this check when changing the extensions arrays |
| 272 | // static bool check_sorted = false; |
| 273 | // // the file extensions must be sorted |
| 274 | // if (!check_sorted) { |
| 275 | // for (unsigned i = 1; i < ARRLEN(mediaexts); i++) { |
| 276 | // if (strcmp(mediaexts[i - 1], mediaexts[i]) > 0) { |
| 277 | // ui_deinit(); |
| 278 | // puts("Error: ui_dirent_extension_color: Media extensions not sorted"); |
| 279 | // exit(1); |
| 280 | // } |
| 281 | // } |
| 282 | // for (unsigned i = 1; i < ARRLEN(archiveexts); i++) { |
| 283 | // if (strcmp(archiveexts[i - 1], archiveexts[i]) > 0) { |
| 284 | // ui_deinit(); |
| 285 | // puts("Error: ui_dirent_extension_color: Archive extensions not sorted"); |
| 286 | // exit(1); |
| 287 | // } |
| 288 | // } |
| 289 | // check_sorted = true; |
| 290 | // } |
| 291 | |
| 292 | const char * ext = strrchr(name, '.'); |
| 293 | if (!ext) return COLOR_FILE; |
| 294 | if (!*(++ext)) return COLOR_FILE; |
| 295 | |
| 296 | const char * match = bsearch( |
| 297 | &ext, |
| 298 | mediaexts, |
| 299 | ARRLEN(mediaexts), |
| 300 | sizeof(mediaexts[0]), |
| 301 | ui_local_bsearch_str_compar |
| 302 | ); |
| 303 | if (match) return MAGENTA; |
| 304 | |
| 305 | match = bsearch( |
| 306 | &ext, |
| 307 | archiveexts, |
| 308 | ARRLEN(archiveexts), |
| 309 | sizeof(archiveexts[0]), |
| 310 | ui_local_bsearch_str_compar |
| 311 | ); |
| 312 | if (match) return RED; |
| 313 | |
| 314 | return COLOR_FILE; |
| 315 | } |
| 316 | |
| 317 | static int ui_dirent_color(const dirent_t * de) { |
| 318 | int color; |
| 319 | switch (de->type) { |
| 320 | case DE_FILE: color = COLOR_FILE; break; |
| 321 | case DE_DIR: color = COLOR_DIR; break; |
| 322 | case DE_FIFO: color = COLOR_FIFO; break; |
| 323 | case DE_LINK: color = COLOR_LINK; break; |
| 324 | case DE_BLOCK: color = COLOR_BLOCK; break; |
| 325 | case DE_CHAR: color = COLOR_CHAR; break; |
| 326 | case DE_SOCKET: color = COLOR_SOCKET; break; |
| 327 | case DE_UNKNOWN: color = COLOR_UNKNOWN; break; |
| 328 | default: color = WHITE; break; |
| 329 | } |
| 330 | |
| 331 | if (de->type == DE_FILE && dirent_isexec(de)) { |
| 332 | color = COLOR_EXEC; |
| 333 | } |
| 334 | |
| 335 | if (color == COLOR_FILE) color = ui_extension_color(de->name); |
| 336 | |
| 337 | return color; |
| 338 | } |
| 339 | |
| 340 | static int ui_link_color(const dirent_t * de) { |
| 341 | switch (de->linktype) { |
| 342 | case DE_FILE: return ui_extension_color(de->linkname); |
| 343 | case DE_DIR: return COLOR_DIR; |
| 344 | case DE_FIFO: return COLOR_FIFO; |
| 345 | case DE_LINK: return COLOR_LINK; |
| 346 | case DE_BLOCK: return COLOR_BLOCK; |
| 347 | case DE_CHAR: return COLOR_CHAR; |
| 348 | case DE_SOCKET: return COLOR_SOCKET; |
| 349 | case DE_UNKNOWN: return COLOR_UNKNOWN; |
| 350 | default: return WHITE; |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | void ui_print_dir(const dir_t * dir, size_t cursor) { |
| 355 | size_t first; |
| 356 | size_t last; |
| 357 | size_t len = dir_len(dir); |
| 358 | ui_get_first_last(len, cursor, &first, &last); |
| 359 | |
| 360 | for (size_t i = first; i < last; i++) { |
| 361 | size_t pos = i - first; |
| 362 | dirent_t * de = dir_get_entry(dir, i); |
| 363 | ui_print_dirent(de, pos, cursor == i, dir); |
| 364 | } |
| 365 | } |
| 366 | |
| 367 | void ui_print_cursor(size_t cursor, size_t total, enum prog_mode mode, size_t total_marked) { |
| 368 | size_t lines, cols; |
| 369 | getmaxyx(filewin, lines, cols); |
| 370 | if (config.longmode && !config.longinline) { |
| 371 | if (total < lines - 3) wmove(filewin, total + 2, 0); |
| 372 | else wmove(filewin, lines - 1, 0); |
| 373 | } else { |
| 374 | if (total < lines - 2) wmove(filewin, total + 1, 0); |
| 375 | else wmove(filewin, lines - 1, 0); |
| 376 | } |
| 377 | |
| 378 | if (total > 0) { |
| 379 | wprintw(filewin, "%zu/%zu", cursor + 1, total); |
| 380 | } else { |
| 381 | waddstr(filewin, "0/0"); |
| 382 | } |
| 383 | |
| 384 | if (total_marked > 0) switch (mode) { |
| 385 | case MODE_NORMAL: |
| 386 | wprintw(filewin, " | %zu Marked", total_marked); |
| 387 | break; |
| 388 | case MODE_CUT: |
| 389 | wprintw(filewin, " | Cutting %zu", total_marked); |
| 390 | break; |
| 391 | default: break; |
| 392 | } |
| 393 | } |
| 394 | |
| 395 | static void ui_get_first_last(size_t dir_len, size_t cursor, size_t * first, size_t * last) { |
| 396 | size_t lines, cols; |
| 397 | getmaxyx(filewin, lines, cols); |
| 398 | lines -= 2; |
| 399 | if (config.longmode && !config.longinline) lines--; |
| 400 | // try to keep cursor in the middle of the window |
| 401 | if (dir_len < lines) { |
| 402 | *first = 0; |
| 403 | *last = dir_len; |
| 404 | } else { |
| 405 | size_t off_lo = lines / 2; |
| 406 | size_t off_hi = lines - off_lo; |
| 407 | if (cursor < off_lo) { |
| 408 | *first = 0; |
| 409 | if (dir_len > lines) *last = lines; |
| 410 | else *last = dir_len; |
| 411 | } else { |
| 412 | *first = cursor - off_lo; |
| 413 | if (dir_len > cursor + off_hi) *last = cursor + off_hi; |
| 414 | else *last = dir_len; |
| 415 | } |
| 416 | } |
| 417 | } |
| 418 | |
| 419 | void ui_resize(void) { |
| 420 | // unless a WINCH handler is specified, ncurses resizes the terminal |
| 421 | // automatically, so this function just resizes the windows |
| 422 | wresize(titlewin, TITLEWINLINES, TITLEWINCOLS); |
| 423 | wresize(statuswin, STATUSWINLINES, STATUSWINCOLS); |
| 424 | wresize(filewin, FILEWINLINES, FILEWINCOLS); |
| 425 | wresize(inputwin, INPUTWINLINES, INPUTWINCOLS); |
| 426 | |
| 427 | mvwin(titlewin, TITLEWINSTARTY, 0); |
| 428 | mvwin(statuswin, STATUSWINSTARTY, 0); |
| 429 | mvwin(filewin, FILEWINSTARTY, 0); |
| 430 | mvwin(inputwin, INPUTWINSTARTY, 0); |
| 431 | |
| 432 | ui_deinit(); |
| 433 | ui_reinit(); |
| 434 | } |
| 435 | |
| 436 | const char * ui_readline(const char * prompt) { |
| 437 | static char inputbuf[INPUTBUFSZ]; |
| 438 | |
| 439 | char * p = inputbuf; |
| 440 | *p = 0; |
| 441 | |
| 442 | werase(inputwin); |
| 443 | waddstr(inputwin, prompt); |
| 444 | wattron(inputwin, A_REVERSE); |
| 445 | waddch(inputwin, ' '); |
| 446 | wattroff(inputwin, A_REVERSE); |
| 447 | wrefresh(inputwin); |
| 448 | |
| 449 | int c; |
| 450 | while ((c = wgetch(inputwin)) != '\n') { |
| 451 | if (c == KEY_ESC) { |
| 452 | curs_set(0); |
| 453 | return NULL; |
| 454 | } |
| 455 | |
| 456 | if (c == KEY_DEL || c == KEY_BACKSPACE) { |
| 457 | if (p != inputbuf) *--p = 0; |
| 458 | else return NULL; |
| 459 | } else if (isprint(c)) { |
| 460 | if (p - inputbuf < INPUTBUFSZ - 1) { |
| 461 | *p++ = (char)c; |
| 462 | *p = 0; |
| 463 | } |
| 464 | } |
| 465 | |
| 466 | werase(inputwin); |
| 467 | waddstr(inputwin, prompt); |
| 468 | waddstr(inputwin, inputbuf); |
| 469 | wattron(inputwin, A_REVERSE); |
| 470 | waddch(inputwin, ' '); |
| 471 | wattroff(inputwin, A_REVERSE); |
| 472 | wrefresh(inputwin); |
| 473 | } |
| 474 | |
| 475 | if (p == inputbuf) return NULL; |
| 476 | return inputbuf; |
| 477 | } |
| 478 | |
| 479 | const char * ui_prompt(const char * prompt, prompt_opts_t opts) { |
| 480 | size_t n_opts = 0; |
| 481 | for (const char * const * p = opts; *p; p++) { |
| 482 | n_opts++; |
| 483 | } |
| 484 | |
| 485 | size_t prompt_len = strlen(prompt); |
| 486 | // find width of options, assuming they all fit on one line |
| 487 | // there are n_opts - 1 spaces in total between the options |
| 488 | size_t opts_len = n_opts - 1; |
| 489 | for (size_t i = 0; i < n_opts; i++) { |
| 490 | opts_len += strlen(opts[i]); |
| 491 | } |
| 492 | |
| 493 | /* top mid btm prompt opts */ |
| 494 | size_t oglines = 1 + 1 + 1 + 1 + 1; |
| 495 | /* border left right */ |
| 496 | size_t ogcols = 2 + 1 + 1 + MAX(prompt_len, opts_len); |
| 497 | size_t lines, cols; |
| 498 | |
| 499 | lines = MIN(oglines, (unsigned)PROMPTWINMAXLINES); |
| 500 | cols = MIN(ogcols, (unsigned)PROMPTWINMAXCOLS); |
| 501 | |
| 502 | WINDOW * promptwin = newwin( |
| 503 | lines, |
| 504 | cols, |
| 505 | PROMPTWINSTARTY - lines / 2, |
| 506 | PROMPTWINSTARTX - cols / 2 |
| 507 | ); |
| 508 | |
| 509 | keypad(promptwin, TRUE); |
| 510 | wrefresh(promptwin); |
| 511 | |
| 512 | size_t cursor = 0; |
| 513 | |
| 514 | bool done = false; |
| 515 | const char * ret = NULL; |
| 516 | while (!done) { |
| 517 | werase(promptwin); |
| 518 | box(promptwin, 0, 0); |
| 519 | |
| 520 | wmove(promptwin, 1, cols / 2 - prompt_len / 2); |
| 521 | waddstr(promptwin, prompt); |
| 522 | |
| 523 | wmove(promptwin, 3, cols / 2 - opts_len / 2); |
| 524 | for (size_t i = 0; i < n_opts; i++) { |
| 525 | int attr = 0; |
| 526 | if (cursor == i) attr = A_REVERSE; |
| 527 | wattron(promptwin, attr); |
| 528 | waddstr(promptwin, opts[i]); |
| 529 | wattroff(promptwin, attr); |
| 530 | if (i < n_opts - 1) waddch(promptwin, ' '); |
| 531 | } |
| 532 | wrefresh(promptwin); |
| 533 | |
| 534 | int c = wgetch(promptwin); |
| 535 | if (n_opts == 0) { |
| 536 | ret = NULL; |
| 537 | done = true; |
| 538 | } else switch (c) { |
| 539 | LEFT_KEYS: |
| 540 | if (cursor > 0) cursor--; |
| 541 | break; |
| 542 | RIGHT_KEYS: |
| 543 | if (cursor < n_opts - 1) cursor++; |
| 544 | break; |
| 545 | case '\n': |
| 546 | case KEY_ENTER: |
| 547 | ret = opts[cursor]; |
| 548 | done = true; |
| 549 | break; |
| 550 | case KEY_RESIZE: |
| 551 | ui_resize(); |
| 552 | |
| 553 | lines = MIN(oglines, (unsigned)PROMPTWINMAXLINES); |
| 554 | cols = MIN(ogcols, (unsigned)PROMPTWINMAXCOLS); |
| 555 | |
| 556 | wresize(promptwin, lines, cols); |
| 557 | mvwin(promptwin, |
| 558 | PROMPTWINSTARTY - lines / 2, |
| 559 | PROMPTWINSTARTX - cols / 2 |
| 560 | ); |
| 561 | wrefresh(promptwin); |
| 562 | break; |
| 563 | case 'q': |
| 564 | case KEY_ESC: |
| 565 | ret = NULL; |
| 566 | done = true; |
| 567 | break; |
| 568 | } |
| 569 | } |
| 570 | |
| 571 | delwin(promptwin); |
| 572 | return ret; |
| 573 | } |
| 574 | |
| 575 | static bool ui_internal_prompt_dirent_deletion(const dirent_t * de) { |
| 576 | static const char * no = "No"; |
| 577 | static const char * yes = "Yes"; |
| 578 | |
| 579 | size_t nfiles = 0; |
| 580 | if (de->type == DE_DIR) { |
| 581 | ui_status_info("listing..."); |
| 582 | ui_refresh(); |
| 583 | nfiles = dirent_subfiles(de); |
| 584 | ui_status_info(""); |
| 585 | ui_refresh(); |
| 586 | } |
| 587 | |
| 588 | char * prompt; |
| 589 | if (nfiles != 0) { |
| 590 | char * fstr = "Delete '%s' and all %zu files inside it?"; |
| 591 | size_t len = snprintf(NULL, 0, fstr, de->name, nfiles); |
| 592 | prompt = malloc(len + 1); |
| 593 | sprintf(prompt, fstr, de->name, nfiles); |
| 594 | } else { |
| 595 | char * fstr = "Delete '%s'?"; |
| 596 | size_t len = snprintf(NULL, 0, fstr, de->name); |
| 597 | prompt = malloc(len + 1); |
| 598 | sprintf(prompt, fstr, de->name); |
| 599 | } |
| 600 | |
| 601 | const char * ret = ui_prompt(prompt, (prompt_opts_t){no, yes, NULL}); |
| 602 | free(prompt); |
| 603 | if (ret != yes) return false; |
| 604 | |
| 605 | if (nfiles != 0) { |
| 606 | ret = ui_prompt("This action cannot be undone. Proceed?", (prompt_opts_t){no, yes, NULL}); |
| 607 | if (ret != yes) return false; |
| 608 | } |
| 609 | |
| 610 | return true; |
| 611 | } |
| 612 | |
| 613 | static bool ui_internal_prompt_marked_deletion(size_t total_marked) { |
| 614 | static const char * no = "No"; |
| 615 | static const char * yes = "Yes"; |
| 616 | |
| 617 | ui_status_info("listing..."); |
| 618 | ui_refresh(); |
| 619 | size_t subfiles = dir_marked_subfiles(); |
| 620 | ui_status_info(""); |
| 621 | ui_refresh(); |
| 622 | |
| 623 | char * prompt; |
| 624 | if (subfiles == 0) { |
| 625 | const char * fstr = "Delete %zu marked files?"; |
| 626 | size_t len = snprintf(NULL, 0, fstr, total_marked); |
| 627 | prompt = malloc(len + 1); |
| 628 | sprintf(prompt, fstr, total_marked); |
| 629 | } else { |
| 630 | const char * fstr = "Delete %zu marked files and %zu subfiles?"; |
| 631 | size_t len = snprintf(NULL, 0, fstr, total_marked, subfiles); |
| 632 | prompt = malloc(len + 1); |
| 633 | sprintf(prompt, fstr, total_marked, subfiles); |
| 634 | } |
| 635 | |
| 636 | const char * ret = ui_prompt(prompt, (prompt_opts_t){no, yes, NULL}); |
| 637 | free(prompt); |
| 638 | if (ret != yes) return false; |
| 639 | |
| 640 | ret = ui_prompt("This action cannot be undone. Proceed?", (prompt_opts_t){no, yes, NULL}); |
| 641 | if (ret != yes) return false; |
| 642 | return true; |
| 643 | } |
| 644 | |
| 645 | bool ui_prompt_deletion(const dirent_t * de, size_t total_marked) { |
| 646 | if (total_marked > 0) |
| 647 | return ui_internal_prompt_marked_deletion(total_marked); |
| 648 | else |
| 649 | return ui_internal_prompt_dirent_deletion(de); |
| 650 | } |
| 651 | |
| 652 | bool ui_prompt_paste(size_t total_marked) { |
| 653 | static const char * no = "No"; |
| 654 | static const char * yes = "Yes"; |
| 655 | static const char * fstr = "Paste %zu files into this directory?"; |
| 656 | |
| 657 | size_t len = snprintf(NULL, 0, fstr, total_marked); |
| 658 | char * prompt = malloc(len + 1); |
| 659 | sprintf(prompt, fstr, total_marked); |
| 660 | |
| 661 | const char * ret = ui_prompt(prompt, (prompt_opts_t){no, yes, NULL}); |
| 662 | free(prompt); |
| 663 | if (ret != yes) return false; |
| 664 | return true; |
| 665 | } |
| 666 | |