1 /*
2 *
3 * CLEX File Manager
4 *
5 * Copyright (C) 2001-2018 Vlado Potisk <vlado_potisk@clex.sk>
6 *
7 * CLEX is free software without warranty of any kind; see the
8 * GNU General Public License as set out in the "COPYING" document
9 * which accompanies the CLEX File Manager package.
10 *
11 * CLEX can be downloaded from http://www.clex.sk
12 *
13 */
14
15 #include "clexheaders.h"
16
17 #include <sys/time.h> /* struct timeval */
18 #include <stdarg.h> /* log.h */
19 #include <string.h> /* strcpy() */
20 #include <stdlib.h> /* getenv() */
21 #include <time.h> /* time() */
22 #include <wctype.h> /* iswdigit() */
23 #ifdef HAVE_TERM_H
24 # include <term.h> /* enter_bold_mode */
25 #endif
26 #include "curses.h"
27
28 #include "inout.h"
29
30 #include "cfg.h" /* cfg_num() */
31 #include "control.h" /* get_current_mode() */
32 #include "directory.h" /* dir_split_dir() */
33 #include "edit.h" /* edit_adjust() */
34 #include "log.h" /* msgout() */
35 #include "mbwstring.h" /* convert2w() */
36 #include "panel.h" /* pan_adjust() */
37 #include "signals.h" /* signal_initialize() */
38 #include "tty.h" /* tty_press_enter() */
39
40 #ifndef A_NORMAL
41 # define A_NORMAL 0
42 #endif
43
44 #ifndef A_BOLD
45 # define A_BOLD A_STANDOUT
46 #endif
47
48 #ifndef A_REVERSE
49 # define A_REVERSE A_STANDOUT
50 #endif
51
52 #ifndef A_UNDERLINE
53 # define A_UNDERLINE A_STANDOUT
54 #endif
55
56 #ifndef ACS_HLINE
57 # define ACS_HLINE '-'
58 #endif
59
60 static chtype attrr, attrb; /* reverse and bold or substitutes */
61 static const wchar_t *title; /* panel title (if not generated) */
62 static int framechar; /* panel frame character (note that ACS_HLINE is an int) */
63
64 /* win_position() controls */
65 static struct {
66 CODE resize; /* --( COLSxLINES )-- window size */
67 CODE wait; /* --< PLEASE WAIT >-- message */
68 FLAG wait_ctrlc;/* append "Ctrl-C TO ABORT" to the message above */
69 FLAG update; /* --< CURSOR/TOTAL >-- normal info */
70 /* resize, wait:
71 * 2 = msg should be displayed
72 * 1 = msg is displayed and should be cleared
73 * 0 = msg is not displayed
74 * update:
75 * 1 = data changed --> update the screen
76 * 0 = no change
77 */
78 } posctl;
79
80 /* line numbers and column numbers for better readability */
81 #define LNO_TITLE 0
82 #define LNO_FRAME1 1
83 #define LNO_PANEL 2
84 #define LNO_FRAME2 (disp_data.panlines + 2)
85 #define LNO_INFO (disp_data.panlines + 3)
86 #define LNO_HELP (disp_data.panlines + 4)
87 #define LNO_BAR (disp_data.panlines + 5)
88 #define LNO_EDIT (disp_data.panlines + 6)
89 #define MARGIN1 1 /* left or right margin - 1 column */
90 #define MARGIN2 2 /* left or right margin - 2 columns */
91 #define BOX4 4 /* checkbox or radiobutton */
92 #define CNO_FILTER 15 /* user's filter string starting column */
93 static int filter_width = 0; /* columns written by win_filter() */
94
95 /* help line (the second info line) controls */
96 #define HELPTMPTIME 5 /* duration of help_tmp in seconds */
97 static struct {
98 const wchar_t *help_base; /* default help string for the current mode */
99 /* help_base can be overriden by panel->help */
100 const wchar_t *help_tmp; /* temporary message, highlighted, dismissed automatically
101 after HELPTMPTIME when a key is pressed */
102 time_t exp_tmp; /* expiration time for 'help_tmp' */
103 const wchar_t *info; /* "-- info message --",
104 dismissed automatically when a key is pressed */
105 const wchar_t *warning; /* "Warning message. Press any key to continue" */
106 } helpline;
107
108 /* number of chars written to display lines, used for cursor position calculations */
109 static int chars_in_line[MAX_CMDLINES];
110
111 static wchar_t *bar; /* win_bar() output */
112
113 static void win_helpline(void); /* defined below */
114 static void win_position(void); /* defined below */
115
116 static char type_symbol[][5] = {
117 " ", "exec", "suid", "Suid", "sgid", "/DIR", "/MNT",
118 "Bdev", "Cdev", "FIFO", "sock", "spec", " ??"
119 }; /* must correspond with FT_XXX */
120
121 #define CHECKBOX(X) do { addstr(X ? "[x] " : "[ ] "); } while (0)
122 #define RADIOBUTTON(X) do { addstr(X ? "(x) " : "( ) "); } while (0)
123
124 #define OFFSET0 (textline->offset ? 1 : textline->promptwidth)
125
126 /* draw everything from scratch */
127 static void
screen_draw_all(void)128 screen_draw_all(void)
129 {
130 int y, x;
131
132 for (;/* until break */;) {
133 clear();
134 getmaxyx(stdscr,y,x);
135 disp_data.scrcols = x;
136 disp_data.scrlines = y;
137 disp_data.pancols = x - 2 * MARGIN2;
138 disp_data.panrcol = x - MARGIN2;
139 disp_data.cmdlines = cfg_num(CFG_CMD_LINES);
140 disp_data.panlines = y - disp_data.cmdlines - 6;
141 /*
142 * there are 2 special positions: the bottom-right corner
143 * is always left untouched to prevent automatic scrolling
144 * and the position just before it is reserved for the
145 * '>' continuation mark
146 */
147 if (x >= MIN_COLS && y >= MIN_LINES)
148 break;
149 printw("CLEX: this %dx%d window is too small. "
150 "Press ctrl-C to exit or enlarge the window to at least " STR(MIN_LINES) "x" STR(MIN_COLS)
151 #ifndef KEY_RESIZE
152 " and press a key to continue"
153 #endif
154 ". ",y,x);
155 refresh();
156 if (getch() == CH_CTRL('C'))
157 err_exit("Display window is too small");
158 }
159 attrset(A_NORMAL);
160 win_frame();
161 win_bar();
162 if (panel) {
163 /* panel is NULL only when starting clex */
164 win_title();
165 pan_adjust(panel);
166 win_panel();
167 win_infoline();
168 win_helpline();
169 win_filter();
170 }
171 edit_adjust();
172 win_edit();
173 }
174
175 /* start CURSES */
176 void
curses_initialize(void)177 curses_initialize(void)
178 {
179 int i;
180 const char *term, *astr;
181 static const char *compat[] = { "xterm","kterm","Eterm","dtterm","rxvt","aixterm" };
182 static const char *not_compat[] = { "ansi","vt","linux","dumb" };
183
184 if (disp_data.wait)
185 tty_press_enter();
186
187 initscr(); /* restores signal dispositions on FreeBSD ! */
188 signal_initialize(); /* FreeBSD initscr() bug workaround ! */
189 raw();
190 nonl();
191 noecho();
192 keypad(stdscr,TRUE);
193 notimeout(stdscr,TRUE);
194 scrollok(stdscr,FALSE);
195 clear();
196 refresh();
197 disp_data.curses = 1;
198
199 if (enter_reverse_mode && *enter_reverse_mode)
200 attrr = A_REVERSE;
201 else
202 attrr = A_STANDOUT;
203 if (enter_bold_mode && *enter_bold_mode)
204 attrb = A_BOLD;
205 else if (enter_underline_mode && *enter_underline_mode)
206 attrb = A_UNDERLINE;
207 else
208 attrb = A_STANDOUT;
209
210 win_frame_reconfig();
211 screen_draw_all();
212
213 disp_data.bs177 = key_backspace && strcmp(key_backspace,"\177") == 0;
214
215 astr = "not known";
216 if ( (term = getenv("TERM")) ) {
217 for (i = 0; i < ARRAY_SIZE(compat); i++)
218 if (strncmp(term,compat[i],strlen(compat[i])) == 0) {
219 disp_data.xterm = 1;
220 astr = "yes";
221 break;
222 }
223 if (!disp_data.xterm)
224 for (i = 0; i < ARRAY_SIZE(not_compat); i++)
225 if (strncmp(term,not_compat[i],strlen(not_compat[i])) == 0) {
226 disp_data.noxterm = 1;
227 astr = "no";
228 break;
229 }
230 }
231 msgout(MSG_DEBUG,"Terminal type: \"%s\", can change the window title: %s",
232 term ? term : "undefined", astr);
233
234 disp_data.xwin = getenv("WINDOWID") && getenv("DISPLAY");
235 msgout(MSG_DEBUG,"X Window: %s",
236 disp_data.xwin ? "detected" : "not detected (no $DISPLAY and/or no $WINDOWID)");
237
238 #ifdef KEY_MOUSE
239 # if NCURSES_MOUSE_VERSION >= 2
240 /* version 1 does not work with the mouse wheel */
241 if (key_mouse && *key_mouse) {
242 msgout(MSG_DEBUG,"Mouse interface: ncurses mouse version %d", NCURSES_MOUSE_VERSION);
243 # else
244 if (key_mouse && strcmp(key_mouse,"\033[<") == 0)
245 msgout(MSG_DEBUG,"Mouse interface: xterm SGR 1006 mode is NOT supported by CLEX");
246 /* reason: ncurses mouse is the preferred interface */
247 if (key_mouse && strcmp(key_mouse,"\033[M") == 0) {
248 msgout(MSG_DEBUG,"Mouse interface: xterm normal tracking mode");
249 # endif
250 disp_data.mouse = 1;
251 }
252 else
253 msgout(MSG_DEBUG,"Mouse interface: not found");
254 #endif /* KEY_MOUSE */
255 }
256
257 /* restart CURSES */
258 void
259 curses_restart(void)
260 {
261 if (disp_data.wait)
262 tty_press_enter();
263
264 reset_prog_mode();
265 touchwin(stdscr);
266 disp_data.curses = 1;
267 screen_draw_all();
268 }
269
270 /* this is a cleanup function (see err_exit() in control.c) */
271 void
272 curses_stop(void)
273 {
274 clear();
275 refresh();
276 endwin();
277 disp_data.curses = 0;
278 }
279
280 void
281 curses_cbreak(void)
282 {
283 cbreak();
284 posctl.wait_ctrlc = 1;
285 }
286
287 void
288 curses_raw(void)
289 {
290 raw();
291 posctl.wait_ctrlc = 0;
292 }
293
294 /* set cursor to the proper position and refresh screen */
295 static void
296 screen_refresh(void)
297 {
298 int i, posx, posy, offset;
299
300 if (posctl.wait || posctl.resize || posctl.update)
301 win_position(); /* display/clear message */
302
303 if (panel->filtering == 1)
304 move(LNO_FRAME2,CNO_FILTER + wc_cols(panel->filter->line,0,panel->filter->curs));
305 else {
306 posy = LNO_EDIT;
307 posx = 0;
308 if (textline != 0) {
309 for (i = 0, offset = textline->offset; i < disp_data.cmdlines - 1
310 && textline->curs >= offset + chars_in_line[i]; i++)
311 offset += chars_in_line[i];
312 posx = wc_cols(USTR(textline->line),offset,textline->curs);
313 if (i == 0)
314 posx += OFFSET0;
315 else
316 posy += i;
317 }
318 move(posy,posx);
319 }
320 refresh();
321 }
322
323 /****** mouse input functions ******/
324
325 /* which screen area belongs the line 'ln' to ? */
326 static int
327 screen_area(int ln, int col)
328 {
329 if (ln < 0 || col < 0 || ln >= disp_data.scrlines || col >= disp_data.scrcols)
330 return -1;
331 if (ln == 0)
332 return AREA_TITLE;
333 if (ln == 1)
334 return AREA_TOPFRAME;
335 if ((ln -= 2) < disp_data.panlines)
336 return AREA_PANEL;
337 if ((ln -= disp_data.panlines) == 0)
338 return AREA_BOTTOMFRAME;
339 if (ln == 1)
340 return AREA_INFO;
341 if (ln == 2)
342 return AREA_HELP;
343 if (ln == 3)
344 return AREA_BAR;
345 if (ln == 4 && textline && col < textline->promptwidth)
346 return AREA_PROMPT;
347 return AREA_LINE;
348 }
349
350 /* screen position -> input line cursor */
351 static int
352 scr2curs(int y, int x)
353 {
354 int first, last, i;
355 wchar_t *line;
356
357 if (y == 0 && (x -= OFFSET0) < 0) /* compensate for the prompt/continuation mark */
358 return -1; /* in front of the text */
359
360 /* first = index of the first character in the line */
361 for (first = textline->offset, i = 0; i < y; i++)
362 first += chars_in_line[i];
363 if (first > textline->size)
364 return -1; /* unused line */
365
366 /* last = index of the last character in the line (may be the terminating null) */
367 last = first + chars_in_line[y] - 1;
368 LIMIT_MAX(last,textline->size);
369
370 line = USTR(textline->line);
371 for (i = first; i <= last; i++) {
372 x -= WCW(line[i]);
373 if (x <= 0)
374 return i;
375 }
376 return -1; /* after the text */
377 }
378
379 /* screen position -> filter line cursor */
380 static int
381 scr2curs_filt(int x)
382 {
383 int i;
384 wchar_t *line;
385
386 if ((x -= CNO_FILTER) < 0)
387 return -1; /* in front of the text */
388
389 line = panel->filter->line;
390 for (i = 0; i <= panel->filter->size; i++) {
391 x -= WCW(line[i]);
392 if (x <= 0)
393 return i;
394 }
395 return -1; /* after the text */
396 }
397
398 /* screen position -> help link link number */
399 static int
400 scr2curs_help(int y, int x)
401 {
402 int i, width, curs, links;
403 HELP_LINE *ph;
404
405 curs = panel->top + y;
406 if (curs < 0 || curs >= panel->cnt)
407 return -1;
408
409 ph = panel_help.line[curs];
410 links = ph->links;
411 if (links <= 1)
412 return links - 1;
413
414 /* multiple links */
415 for (width = wc_cols(ph->text,0,-1), i = 0; i < links - 1; i++) {
416 x -= wc_cols(ph[3 * i + 2].text,0,-1) + width;
417 width = wc_cols(ph[3 * i + 3].text,0,-1);
418 if (x <= width / 2)
419 return i;
420 }
421 return i;
422 }
423
424 /* note: single width characters assumed */
425 static int
426 scr2curs_bar(int x)
427 {
428 int curs;
429
430 x--; /* make 0-based */
431 if (x < 0 || x >= wcslen(bar) || bar[x] == L' ')
432 return -1;
433
434 for (curs = 0; --x > 0; ) {
435 if (bar[x] == L'|')
436 return -1;
437 if (bar[x] == L' ' && bar[x-1] != L' ')
438 curs++;
439 }
440 return curs;
441 }
442
443 /* read the mouse tracking data */
444 static int
445 mouse_data(void)
446 {
447 FLAG click;
448 static struct timeval prev, now;
449 static MOUSE_INPUT miprev;
450
451 #if NCURSES_MOUSE_VERSION >= 2
452 mmask_t mstat;
453 MEVENT mevent;
454 static CODE btn; /* active real mouse button (i.e. not wheel) or 0 */
455
456 if (getmouse(&mevent) != OK)
457 return -1;
458 minp.x = mevent.x;
459 minp.y = mevent.y;
460 mstat = mevent.bstate;
461 minp.motion = (mstat & REPORT_MOUSE_POSITION) != 0;
462 if (minp.motion)
463 minp.button = btn;
464 else if (mstat & BUTTON1_PRESSED)
465 btn = minp.button = disp_data.mouse_swap ? 3 : 1;
466 else if (mstat & BUTTON2_PRESSED)
467 btn = minp.button = 2;
468 else if (mstat & BUTTON3_PRESSED)
469 btn = minp.button = disp_data.mouse_swap ? 1 : 3;
470 else if (mstat & BUTTON4_PRESSED)
471 minp.button = 4;
472 else if (mstat & BUTTON5_PRESSED)
473 minp.button = 5;
474 else {
475 /* button release */
476 if (mstat & BUTTON1_RELEASED) {
477 if (btn == 1)
478 btn = 0;
479 }
480 else if (mstat & BUTTON2_RELEASED) {
481 if (btn == 2)
482 btn = 0;
483 }
484 else if (mstat & BUTTON3_RELEASED) {
485 if (btn == 3)
486 btn = 0;
487 }
488 return -1;
489 }
490
491 #else
492 /* fallback: XTERM mouse */
493 int mstat;
494
495 keypad(stdscr,FALSE);
496 mstat = getch() - 32;
497 minp.x = getch() - 33;
498 minp.y = getch() - 33;
499 keypad(stdscr,TRUE);
500 if (mstat < 0)
501 return -1;
502
503 /* button */
504 switch (mstat & 0x43) {
505 case 0:
506 minp.button = disp_data.mouse_swap ? 3 : 1; break;
507 case 1:
508 minp.button = 2; break;
509 case 2:
510 minp.button = disp_data.mouse_swap ? 1 : 3; break;
511 case 64:
512 minp.button = 4; break;
513 case 65:
514 minp.button = 5; break;
515 default:
516 return -1; /* button release or a bogus event */
517 }
518 minp.motion = mstat & 0x20; /* mouse is in motion */
519 #endif
520
521 if ((minp.area = screen_area(minp.y, minp.x)) < 0)
522 return -1;
523
524 /* event accepted */
525 miprev = minp;
526 prev = now;
527 gettimeofday(&now,0);
528
529 click = minp.button >= 1 && minp.button <= 3;
530 minp.doubleclick = !miprev.doubleclick && click && minp.button == miprev.button && !minp.motion
531 && minp.x == miprev.x && minp.y == miprev.y && now.tv_sec - prev.tv_sec < 2
532 && 1000 * (int)(now.tv_sec - prev.tv_sec) + (int)(now.tv_usec - prev.tv_usec) / 1000
533 <= cfg_num(CFG_DOUBLE_CLICK);
534
535 minp.ypanel = (click && minp.area == AREA_PANEL) ? minp.y - LNO_PANEL : -1;
536 minp.cursor = -1;
537 if (click)
538 switch (minp.area) {
539 case AREA_LINE:
540 if (textline)
541 /* input line cursor */
542 minp.cursor = scr2curs(minp.y - LNO_EDIT,minp.x);
543 break;
544 case AREA_BAR:
545 minp.cursor = scr2curs_bar(minp.x);
546 break;
547 case AREA_BOTTOMFRAME:
548 if (panel->filter)
549 /* filter expression cursor */
550 minp.cursor = scr2curs_filt(minp.x);
551 break;
552 case AREA_PANEL:
553 if (panel == panel_help.pd)
554 /* help link index */
555 minp.cursor = scr2curs_help(minp.y - LNO_PANEL,minp.x - MARGIN2);
556 break;
557 }
558
559 return 0;
560 }
561
562 /****** keyboard input functions ******/
563
564 void
565 kbd_rawkey(void)
566 {
567 int retries, type;
568
569 screen_refresh();
570
571 kinp.prev_esc = kinp.fkey == 0 && kinp.key == WCH_ESC;
572 do {
573 retries = 10;
574 do {
575 if (--retries < 0)
576 err_exit("Cannot read the keyboard input");
577 type = get_wch(&kinp.key);
578 } while (type == ERR || kinp.key == WEOF);
579 if (type == KEY_CODE_YES) {
580 #ifdef KEY_MOUSE
581 if (kinp.key == KEY_MOUSE) {
582 kinp.fkey = 2;
583 kinp.key = 0; /* no more #ifdef KEY_MOUSE */
584 }
585 else
586 #endif
587 kinp.fkey = 1;
588 }
589 else
590 kinp.fkey = 0;
591 } while ((kinp.fkey == 0 && kinp.key == L'\0') || (kinp.fkey == 2 && mouse_data() < 0));
592 }
593
594 /*
595 * get next input char, no processing except screen resize and screen redraw,
596 * use this input function to get unfiltered input
597 */
598 static wint_t
599 kbd_getany(void)
600 {
601 for (;/* until return */;) {
602 kbd_rawkey();
603
604 if (kinp.fkey == 0 && kinp.key == WCH_CTRL('L'))
605 wrefresh(curscr); /* redraw screen */
606 else if (kinp.fkey == 0 && kinp.key == WCH_ESC)
607 /* ignore */;
608 #ifdef KEY_RESIZE
609 else if (kinp.fkey == 1 && kinp.key == KEY_RESIZE) {
610 posctl.resize = 2;
611 screen_draw_all();
612 }
613 #endif
614 else
615 return kinp.key;
616 }
617 }
618
619 const char *
620 char_code(int value)
621 {
622 static char buffer[24];
623
624 if (*buffer == '\0')
625 strcpy(buffer,lang_data.utf8 ? "U+" : "\\x");
626 sprintf(buffer + 2,"%0*X",value > 0xFF ? 4 : 2,value);
627 return buffer;
628 }
629
630 static const char *
631 ascii_code(int value)
632 {
633 static char *ascii[] = {
634 "", /* null */
635 ", ctrl-A, SOH (start of heading)",
636 ", ctrl-B, STX (start of text)",
637 ", ctrl-C, ETX (end of text)",
638 ", ctrl-D, EOT (end of transmission)",
639 ", ctrl-E, ENQ (enquiry)",
640 ", ctrl-F, ACK (acknowledge)",
641 ", ctrl-G, BEL (bell)",
642 ", ctrl-H, BS (backspace)",
643 ", ctrl-I, HT (horizontal tab)",
644 ", ctrl-J, LF (new line) (line feed)",
645 ", ctrl-K, VT (vertical tab)",
646 ", ctrl-L, FF (form feed)",
647 ", ctrl-M, CR (carriage return)",
648 ", ctrl-N, SO (shift out)",
649 ", ctrl-O, SI (shift in)",
650 ", ctrl-P, DLE (data link escape)",
651 ", ctrl-Q, DC1 (device control 1)",
652 ", ctrl-R, DC2 (device control 2)",
653 ", ctrl-S, DC3 (device control 3)",
654 ", ctrl-T, DC4 (device control 4)",
655 ", ctrl-U, NAK (negative acknowledgment)",
656 ", ctrl-V, SYN (synchronous idle)",
657 ", ctrl-W, ETB (end of transmission block)",
658 ", ctrl-X, CAN (cancel)",
659 ", ctrl-Y, EM (end of medium)",
660 ", ctrl-Z, SUB (substitute)",
661 ", ESC (escape)",
662 ", FS (file separator)",
663 ", GS (group separator)",
664 ", RS (record separator)",
665 ", US (unit separator)"
666 };
667
668 if (lang_data.utf8 && value == L'\xAD')
669 return ", SHY (soft hyphen)";
670 if (lang_data.utf8 && value == L'\xA0')
671 return ", NBSP (non-breaking space)";
672 return (value > 0 && value < ARRAY_SIZE(ascii)) ? ascii[value] : "";
673 }
674
675 /* check for key + shift combination not understood by the curses library */
676 static wint_t
677 shift_key(wint_t key)
678 {
679 int i;
680 const char *name;
681 static struct {
682 const char *n;
683 wint_t k;
684 } keytable[] = {
685 { "LFT", KEY_LEFT },
686 { "RIT", KEY_RIGHT },
687 { "UP", KEY_UP },
688 { "DN", KEY_DOWN },
689 { "HOM", KEY_HOME },
690 { "END", KEY_END },
691 { "IC", KEY_IC },
692 { "DC", KEY_DC },
693 { "PRV", KEY_PPAGE },
694 { "NXT", KEY_NPAGE }
695 };
696
697 name = keyname(key);
698 if (*name != 'k')
699 return 0;
700
701 name++;
702 for (i = 0; i < ARRAY_SIZE(keytable); i++)
703 if (strncmp(name,keytable[i].n,strlen(keytable[i].n)) == 0)
704 return keytable[i].k;
705
706 return 0;
707 }
708
709 /* get next input char */
710 wint_t
711 kbd_input(void)
712 {
713 wchar_t ch;
714 wint_t shkey;
715
716 /* show ASCII code */
717 if (helpline.info == 0
718 && ((panel->filtering == 1 && (ch = panel->filter->line[panel->filter->curs]) != L'\0')
719 || (textline != 0 && (ch = USTR(textline->line)[textline->curs]) != L'\0'))
720 && !ISWPRINT(ch))
721 msgout(MSG_i,"special character %s%s",char_code(ch),ascii_code(ch));
722
723 kbd_getany();
724
725 /* <esc> 1 --> <F1>, <esc> 2 --> <F2>, ... <esc> 0 --> <F10> */
726 if (kinp.prev_esc && kinp.fkey == 0 && iswdigit(kinp.key)) {
727 kinp.prev_esc = 0;
728 kinp.fkey = 1;
729 kinp.key = KEY_F(((kinp.key - L'0') + 9) % 10 + 1);
730 }
731
732 if (kinp.fkey == 1 && (shkey = shift_key(kinp.key))) {
733 kinp.prev_esc = 1;
734 kinp.key = shkey;
735 }
736
737 /* dismiss remarks (if any) */
738 if (helpline.info)
739 win_sethelp(HELPMSG_INFO,0);
740 else if (helpline.help_tmp && time(0) > helpline.exp_tmp)
741 win_sethelp(HELPMSG_TMP,0);
742
743 return kinp.key;
744 }
745
746 /****** output functions ******/
747
748 /*
749 * following functions write to these screen areas:
750 *
751 * win_title, win_settitle /usr/local
752 * win_frame ----------
753 * win_panel DIR bin
754 * win_panel >DIR etc <
755 * win_panel DIR man
756 * win_frame, win_filter,
757 * win_waitmsg, win_position -< filt >----< 3/9 >-
758 * win_infoline 0755 rwxr-xr-x
759 * win_sethelp alt-M for menu
760 * win_bar [ CLEX file manager ]
761 * win_edit shell $ ls -l_
762 */
763
764 #define BLANK(X) do { char_line(' ',X); } while (0)
765 /* write a line of repeating chars */
766 static void
767 char_line(int ch, int cnt)
768 {
769 while (cnt-- > 0)
770 addch(ch);
771 }
772
773 /*
774 * putwcs_trunc() writes wide string 'str' padding or truncating it
775 * to the total size of 'maxwidth' display columns. Its behavior
776 * may be altered by OPT_XXX options (may be OR-ed together).
777 * Non-printable characters are replaced by a question mark
778 */
779 #define OPT_NOPAD 1 /* do not pad */
780 #define OPT_NOCONT 2 /* truncate without the continuation mark '>' */
781 #define OPT_SQUEEZE 4 /* squeeze long string in the middle: abc...xyz */
782 /*
783 * return value:
784 * if OPT_SQUEEZE is used, the return value is:
785 * -1 if the string had to be squeezed in the middle
786 * >= 0 if the string was short enough to display it normally
787 * if OPT_NOPAD is given, it returns the number of display columns left unused
788 * otherwise it returns the number of characters written including padding,
789 * but excluding the continuation mark
790 */
791 static int
792 putwcs_trunc(const wchar_t *str, int maxwidth, int options)
793 {
794 wchar_t ch;
795 int i, chcnt, remain, width, p2, len, dots, part1, part2;
796 FLAG printable;
797
798 if (maxwidth <= 0)
799 return 0;
800 if (str == 0)
801 str = L"(null!)"; /* should never happen */
802
803 if (options & OPT_SQUEEZE) {
804 len = wcslen(str);
805 if (wc_cols(str,0,len) > maxwidth) {
806 dots = maxwidth >= 6 ? 4 : 1;
807 part1 = 3 * (maxwidth - dots) / 8;
808 part2 = maxwidth - dots - part1;
809 part2 += putwcs_trunc(str,part1,OPT_NOCONT | OPT_NOPAD);
810 /* fine-tune the width */
811 p2 = len - part2;
812 LIMIT_MIN(p2,0);
813 width = wc_cols(str,p2,len);
814 while (width < part2 && p2 > 0) {
815 p2--;
816 width += WCW(str[p2]);
817 }
818 while (width > part2 && p2 < len) {
819 width -= WCW(str[p2]);
820 p2++;
821 }
822 while (utf_iscomposing(str[p2]))
823 p2++;
824 char_line('.',dots + part2 - width);
825 putwcs_trunc(str + p2,part2,0);
826 return -1;
827 }
828 /* else: SQUEEZE option is superfluous --> ignored */
829 }
830
831 chcnt = 0; /* char counter */
832 for (remain = maxwidth, i = 0; (ch = str[i]) != L'\0';) {
833 width = (printable = ISWPRINT(ch)) ? wcwidth(ch) : 1;
834 if (width > 0 && width == remain && !(options & OPT_NOCONT) && str[i + 1] != L'\0')
835 break;
836 if (width > remain)
837 break;
838 remain -= width;
839 if (printable)
840 i++;
841 else {
842 if (i > 0)
843 addnwstr(str,i);
844 addnwstr(&lang_data.repl,1);
845 str += i + 1;
846 chcnt += i + 1;
847 i = 0;
848 }
849 }
850 if (i > 0) {
851 addnwstr(str,i);
852 chcnt += i;
853 }
854
855 if (ch == L'\0')
856 /* more space than text -> padding */
857 chcnt += remain;
858 else
859 /* more text than space -> truncating */
860 if (!(options & OPT_NOCONT)) {
861 addch('>' | attrb); /* continuation mark in bold font */
862 remain--;
863 }
864
865 if (remain && !(options & OPT_NOPAD))
866 BLANK(remain);
867
868 return (options & OPT_NOPAD) ? remain : chcnt;
869 }
870
871 /*
872 * normal (not wide) char version of putwcs_trunc()
873 * - does not support OPT_SQUEEZE
874 * - does not detect unprintable characters
875 * - assumes 1 char = 1 column, should not be used for internationalized text
876 */
877 static int
878 putstr_trunc(const char *str, int maxwidth, int options)
879 {
880 int chcnt, remain;
881
882 if (maxwidth <= 0)
883 return 0;
884
885 chcnt = strlen(str);
886 if (chcnt < maxwidth) {
887 addstr(str);
888 remain = maxwidth - chcnt;
889 }
890 else {
891 if (options & OPT_NOCONT)
892 addnstr(str,chcnt = maxwidth);
893 else {
894 addnstr(str,chcnt = maxwidth - 1);
895 addch('>' | attrb); /* continuation mark in bold font */
896 }
897 remain = 0;
898 }
899 if (remain && !(options & OPT_NOPAD))
900 BLANK(remain);
901
902 return (options & OPT_NOPAD) ? remain : chcnt;
903 }
904
905
906 #pragma GCC diagnostic push
907 #pragma GCC diagnostic ignored "-Wunused-but-set-variable"
908
909 /* like putwcs_trunc, but stopping just before the 'endcol' screen column */
910 static int
911 putwcs_trunc_col(const wchar_t *str, int endcol, int options)
912 {
913 int y, x;
914
915 getyx(stdscr,y,x);
916 return putwcs_trunc(str,endcol - x,options);
917 }
918
919 static int
920 putstr_trunc_col(const char *str, int endcol, int options)
921 {
922 int y, x;
923
924 getyx(stdscr,y,x);
925 return putstr_trunc(str,endcol - x,options);
926 }
927
928 #pragma GCC diagnostic pop
929
930 void
931 win_frame_reconfig(void)
932 {
933 switch(cfg_num(CFG_FRAME)) {
934 case 0:
935 framechar = '-';
936 break;
937 case 1:
938 framechar = '=';
939 break;
940 default:
941 framechar = ACS_HLINE;
942 }
943 }
944
945 void
946 win_frame(void)
947 {
948 move(LNO_FRAME1,0);
949 char_line(framechar,disp_data.scrcols);
950 move(LNO_FRAME2,0);
951 char_line(framechar,disp_data.scrcols);
952 }
953
954 /* file panel title: primary + secondary panel's directory */
955 static void
956 twodirs(void)
957 {
958 int w1, w2, rw1, opt1, opt2, width;
959 const wchar_t *dir1, *dir2;
960
961 /* directory names are separated by 2 spaces; their width is kept in ratio 5:3 */
962 width = disp_data.scrcols - 2; /* available space */
963 dir1 = USTR(ppanel_file->dirw);
964 rw1 = w1 = wc_cols(dir1,0,-1);
965 dir2 = USTR(ppanel_file->other->dirw);
966 w2 = wc_cols(dir2,0,-1);
967 opt1 = opt2 = 0;
968 if (w1 + w2 <= width)
969 w1 = width - w2; /* enough space */
970 else if (w1 <= (5 * width) / 8) {
971 w2 = width - w1; /* squeeze second */
972 opt2 = OPT_SQUEEZE;
973 }
974 else if (w2 <= (3 * width) / 8) {
975 w1 = width - w2; /* squeeze first */
976 opt1 = OPT_SQUEEZE;
977 }
978 else {
979 w1 = (5 * width) / 8; /* squeeze both */
980 w2 = width - w1;
981 opt1 = opt2 = OPT_SQUEEZE;
982 }
983 attron(attrb);
984 putwcs_trunc(dir1,w1,opt1);
985 attroff(attrb);
986 addstr(" ");
987 putwcs_trunc(dir2,w2,opt2);
988
989 disp_data.dir1end = w1 < rw1 ? w1 : rw1;
990 disp_data.dir2start = w1 + 2; /* exactly +3, but +2 feels more comfortable */
991 }
992
993 /* panel title - top screen line */
994 void
995 win_title(void)
996 {
997 move(LNO_TITLE,0);
998 switch (get_current_mode()) {
999 case MODE_COMPL:
1000 addch(' ');
1001 putwcs_trunc(panel_compl.title,disp_data.scrcols,0);
1002 break;
1003 case MODE_FILE:
1004 twodirs();
1005 break;
1006 case MODE_HELP:
1007 addstr(" HELP: ");
1008 attron(attrb);
1009 putwcs_trunc_col(panel_help.title,disp_data.scrcols,0);
1010 attroff(attrb);
1011 break;
1012 case MODE_PREVIEW:
1013 addstr(" PREVIEW: ");
1014 attron(attrb);
1015 putwcs_trunc_col(panel_preview.title,disp_data.scrcols,0);
1016 attroff(attrb);
1017 break;
1018 default:
1019 /* static title */
1020 addch(' ');
1021 putwcs_trunc(title,disp_data.scrcols,0);
1022 }
1023 }
1024
1025 void
1026 win_settitle(const wchar_t *newtitle)
1027 {
1028 title = newtitle;
1029 win_title();
1030 }
1031
1032 static void
1033 print_position(const wchar_t *msg, int bold)
1034 {
1035 static int prev_pos_start = 0;
1036 int pos_start, filter_stop;
1037
1038 pos_start = disp_data.scrcols - wc_cols(msg,0,-1) - MARGIN2;
1039
1040 filter_stop = MARGIN2 + filter_width;
1041 if (filter_stop > pos_start) {
1042 /* clash with the filter -> do not display position */
1043 move (LNO_FRAME2,filter_stop);
1044 char_line(framechar,disp_data.scrcols - filter_stop - MARGIN2);
1045 return;
1046 }
1047
1048 if (pos_start > prev_pos_start) {
1049 move(LNO_FRAME2,prev_pos_start);
1050 char_line(framechar,pos_start - prev_pos_start);
1051 }
1052 else
1053 move(LNO_FRAME2,pos_start);
1054 prev_pos_start = pos_start;
1055
1056 if (bold)
1057 attron(attrb);
1058 addwstr(msg);
1059 if (bold)
1060 attroff(attrb);
1061 }
1062
1063 static void
1064 win_position(void)
1065 {
1066 wchar_t buffer[64], selected[32], *hidden;
1067
1068 if (posctl.resize == 2) {
1069 swprintf(buffer,ARRAY_SIZE(buffer),L"( %dx%d )",disp_data.scrcols,disp_data.scrlines);
1070 print_position(buffer,1);
1071 posctl.resize = 1;
1072 return;
1073 }
1074
1075 if (posctl.wait == 2) {
1076 if (posctl.wait_ctrlc)
1077 print_position(L"< PLEASE WAIT - CTRL-C TO ABORT >",1);
1078 else
1079 print_position(L"< PLEASE WAIT >",1);
1080 posctl.wait = 1;
1081 return;
1082 }
1083
1084 posctl.wait = posctl.resize = posctl.update = 0;
1085
1086 if (panel->cnt == 0) {
1087 print_position(L"< NO DATA >",1);
1088 return;
1089 }
1090
1091 if (panel->curs < 0) {
1092 print_position(L"",0);
1093 return;
1094 }
1095
1096 if (panel->type == PANEL_TYPE_FILE && ppanel_file->selected)
1097 swprintf(selected,ARRAY_SIZE(selected),L" [%d]",ppanel_file->selected);
1098 else
1099 selected[0] = L'\0';
1100 hidden = panel->type == PANEL_TYPE_FILE && ppanel_file->hidden ? L"HIDDEN " : L"";
1101
1102 swprintf(buffer,ARRAY_SIZE(buffer),L"<%ls %d/%d %ls>",
1103 selected,panel->curs + 1,panel->cnt,hidden);
1104 print_position(buffer,0);
1105 }
1106
1107 void
1108 win_waitmsg(void)
1109 {
1110 if (disp_data.curses && posctl.wait <= 1) {
1111 posctl.wait = 2;
1112 screen_refresh();
1113 }
1114 }
1115
1116 void
1117 win_filter(void)
1118 {
1119 int width;
1120 const wchar_t *label, *close;
1121 FLAG filepanel;
1122
1123 move(LNO_FRAME2,MARGIN2);
1124 if (!panel->filtering)
1125 width = 0;
1126 else {
1127 width = CNO_FILTER + wc_cols(panel->filter->line,0,-1);
1128 filepanel = panel->type == PANEL_TYPE_FILE;
1129
1130 if (panel->type == PANEL_TYPE_HELP) {
1131 label = L"( find text: ";
1132 close = L" )";
1133 }
1134 else if (filepanel && ppanel_file->filtype) {
1135 label = L"[ pattern: ";
1136 close = L" ]";
1137 }
1138 else {
1139 label = L"< filter: ";
1140 close = L" >";
1141 }
1142 char_line(framechar,CNO_FILTER - MARGIN2 - wc_cols(label,0,-1));
1143 addwstr(label);
1144 attron(attrb);
1145 putwcs_trunc(panel->filter->line,width - CNO_FILTER,0);
1146 attroff(attrb);
1147 addwstr(close);
1148 }
1149
1150 if (width < filter_width)
1151 char_line(framechar,filter_width - width);
1152 filter_width = width;
1153 }
1154
1155 /* "0644" -> "rw-r--r--" */
1156 static const char *
1157 print_perms(const char *octal)
1158 {
1159 static const char
1160 *set1[8] =
1161 { "---","--x","-w-","-wx","r--","r-x","rw-","rwx" },
1162 *set2[8] =
1163 { "--S","--s","-wS","-ws","r-S","r-s","rwS","rws" },
1164 *set3[8] =
1165 { "--T","--t","-wT","-wt","r-T","r-t","rwT","rwt" };
1166 static char perms[10];
1167
1168 strcpy(perms + 0,((octal[0] - '0') & 4 ? set2 : set1)[(octal[1] - '0') & 7]);
1169 strcpy(perms + 3,((octal[0] - '0') & 2 ? set2 : set1)[(octal[2] - '0') & 7]);
1170 strcpy(perms + 6,((octal[0] - '0') & 1 ? set3 : set1)[(octal[3] - '0') & 7]);
1171 return perms;
1172 }
1173
1174 /* see CFG_LAYOUT1 for more information about 'fields' */
1175 static void
1176 print_fields(FILE_ENTRY *pfe, int width, const wchar_t *fields)
1177 {
1178 const char *txt;
1179 const wchar_t *wtxt;
1180 FLAG field, left_align;
1181 wchar_t ch;
1182 int i, fw;
1183
1184 for (field = left_align = 0; width > 0 && (ch = *fields++); ) {
1185 if (field == 0) {
1186 if (ch == L'$') {
1187 field = 1;
1188 continue;
1189 }
1190 if (!ISWPRINT(ch)) {
1191 ch = lang_data.repl;
1192 fw = 1;
1193 left_align = 1;
1194 }
1195 else {
1196 fw = wcwidth(ch);
1197 if (fw > width)
1198 return;
1199 /* choose proper alignment (left or right) */
1200 left_align = (ch != L' ');
1201 }
1202 addnwstr(&ch,1);
1203 width -= fw;
1204 }
1205 else {
1206 field = 0;
1207 txt = 0;
1208 wtxt = 0;
1209 switch (ch) {
1210 case L'a': /* access date/time */
1211 fw = disp_data.date_len;
1212 wtxt = pfe->atime_str;
1213 break;
1214 case L'd': /* modification date/time */
1215 fw = disp_data.date_len;
1216 wtxt = pfe->mtime_str;
1217 break;
1218 case L'g': /* file age */
1219 fw = FE_AGE_STR - 1 - ppanel_file->cw_age;
1220 txt = pfe->age_str;
1221 if (txt[0])
1222 txt += ppanel_file->cw_age;
1223 break;
1224 case L'i': /* inode change date/time */
1225 fw = disp_data.date_len;
1226 wtxt = pfe->ctime_str;
1227 break;
1228 case L'l': /* links (total number) */
1229 fw = FE_LINKS_STR - 1 - ppanel_file->cw_ln1;
1230 txt = pfe->links_str;
1231 if (txt[0])
1232 txt += ppanel_file->cw_ln1;
1233 break;
1234 case L'L': /* links (flag) */
1235 fw = ppanel_file->cw_lnh;
1236 txt = pfe->links ? "LNK" : "";
1237 break;
1238 case L'm': /* file mode */
1239 fw = FE_MODE_STR - 1;
1240 txt = pfe->mode_str;
1241 break;
1242 case L'M': /* file mode (alternative format) */
1243 fw = ppanel_file->cw_mod;
1244 txt = pfe->normal_mode ? "" : pfe->mode_str;
1245 break;
1246 case L'o': /* owner */
1247 fw = ppanel_file->cw_ow2;
1248 wtxt = pfe->owner_str;
1249 if (wtxt[0])
1250 wtxt += ppanel_file->cw_ow1;
1251 break;
1252 case L'P': /* permissions (alternative format) */
1253 if (pfe->normal_mode) {
1254 fw = ppanel_file->cw_mod ? 9 : 0;
1255 txt = "";
1256 break;
1257 }
1258 /* no break */
1259 case L'p': /* permissions */
1260 fw = 9; /* rwxrwxrwx */
1261 txt = pfe->file_type == FT_NA ? "" : print_perms(pfe->mode_str);
1262 break;
1263 case L'r': /* file size (or device major/minor) */
1264 case L'R':
1265 case L's':
1266 case L'S':
1267 fw = ppanel_file->cw_sz2;
1268 txt = pfe->size_str;
1269 if (txt[0])
1270 txt += ppanel_file->cw_sz1;
1271 break;
1272 case L't': /* file type */
1273 fw = 4;
1274 txt = type_symbol[pfe->file_type];
1275 break;
1276 case L'>': /* symbolic link */
1277 fw = ppanel_file->cw_lns;
1278 txt = pfe->symlink ? "->" : "";
1279 break;
1280 case L'*': /* selection mark */
1281 fw = 1;
1282 txt = pfe->select ? "*" : " ";
1283 break;
1284 case L'$': /* literal $ */
1285 fw = 1;
1286 txt = "$";
1287 break;
1288 case L'|': /* literal | */
1289 fw = 1;
1290 txt = "|";
1291 break;
1292 default: /* syntax error */
1293 fw = 2;
1294 txt = "$?";
1295 }
1296
1297 if (fw > width)
1298 return;
1299
1300 if (txt) {
1301 if (*txt == '\0')
1302 /* txt == "" - leave the field blank */
1303 BLANK(fw);
1304 else if (left_align && *txt == ' ') {
1305 /* change alignment from right to left */
1306 for (i = 1; txt[i] == ' '; i++)
1307 ;
1308 addstr(txt + i);
1309 BLANK(i);
1310 }
1311 else
1312 addstr(txt);
1313 }
1314 else {
1315 if (*wtxt == '\0')
1316 /* txt == "" - leave the field blank */
1317 BLANK(fw);
1318 else if (left_align && *wtxt == L' ') {
1319 /* change alignment from right to left */
1320 for (i = 1; wtxt[i] == L' '; i++)
1321 ;
1322 addwstr(wtxt + i);
1323 BLANK(i);
1324 }
1325 else
1326 addwstr(wtxt);
1327 }
1328 width -= fw;
1329 }
1330 }
1331 }
1332
1333 /* information line */
1334 void
1335 win_infoline(void)
1336 {
1337 static const wchar_t
1338 *info_cmp[] = {
1339 0, 0,
1340 L"The mode is also known as access rights or permissions"
1341 },
1342 *info_sort[] = {
1343 0,
1344 0,
1345 0,
1346 0, /* ------ */
1347 0,
1348 L"Note: directories . and .. are always on the top, despite the sort order",
1349 L"Notes: . and .. always on the top, devices sorted by device number",
1350 0, /* ------ */
1351 L"Example: file42.txt comes after file9.txt, because 42 > 9",
1352 0,
1353 L"The extension is also known as a file name suffix",
1354 0,0,0,0,
1355 L"Useful in a sendmail queue directory"
1356 };
1357 FILE_ENTRY *pfe;
1358 const wchar_t *msg, *ts;
1359 wchar_t *pch;
1360 int curs;
1361
1362 move(LNO_INFO,0);
1363 addstr(" "); /* MARGIN2 */
1364
1365 /* extra panel lines */
1366 if (panel->curs < 0 && panel->min < 0 && !panel->filtering
1367 && (msg = panel->extra[panel->curs - panel->min].info) != 0) {
1368 putwcs_trunc_col(msg,disp_data.scrcols,0);
1369 return;
1370 }
1371
1372 if (!VALID_CURSOR(panel)) {
1373 clrtoeol();
1374 return;
1375 }
1376
1377 /* regular panel lines */
1378 curs = panel->curs;
1379 msg = 0;
1380 switch (panel->type) {
1381 case PANEL_TYPE_CFG:
1382 msg = panel_cfg.config[curs].help;
1383 break;
1384 case PANEL_TYPE_COMPL:
1385 if ((msg = panel_compl.cand[curs]->aux) != 0 && panel_compl.aux != 0)
1386 addwstr(panel_compl.aux);
1387 break;
1388 case PANEL_TYPE_FILE:
1389 pfe = ppanel_file->files[curs];
1390 if (pfe->file_type == FT_NA)
1391 msg = L"no status information available";
1392 else
1393 print_fields(pfe,disp_data.scrcols - 2 * MARGIN2,disp_data.layout_line);
1394 break;
1395 case PANEL_TYPE_LOG:
1396 putwcs_trunc(convert2w(panel_log.line[curs]->levelstr),16,0);
1397 ts = convert2w(panel_log.line[curs]->timestamp);
1398 /* some locales use non-breaking spaces, replace them in-place by regular spaces */
1399 for (pch = (wchar_t *)ts; *pch; pch++)
1400 if (*pch == L'\xa0')
1401 *pch = L' ';
1402 putwcs_trunc_col(ts,disp_data.scrcols - MARGIN2,0);
1403 break;
1404 case PANEL_TYPE_CMP:
1405 if (curs < ARRAY_SIZE(info_cmp))
1406 msg = info_cmp[curs];
1407 break;
1408 case PANEL_TYPE_SORT:
1409 if (curs < ARRAY_SIZE(info_sort))
1410 msg = info_sort[curs];
1411 break;
1412 default:
1413 ;
1414 }
1415
1416 if (msg)
1417 putwcs_trunc(msg,disp_data.scrcols - MARGIN2,0);
1418 else
1419 clrtoeol();
1420 }
1421
1422 /* information line */
1423 static void
1424 win_helpline(void)
1425 {
1426 const wchar_t *msg;
1427 FLAG bold = 0;
1428
1429 move(LNO_HELP,0);
1430
1431 /* warnmsg has greater priority than a remark */
1432 if (helpline.warning) {
1433 flash();
1434 msg = L" Press any key.";
1435 attron(attrb);
1436 putwcs_trunc(helpline.warning,disp_data.scrcols - wc_cols(msg,0,-1) - 1 /* dot */,OPT_NOPAD);
1437 addch('.');
1438 attroff(attrb);
1439 putwcs_trunc_col(msg,disp_data.scrcols,0);
1440 return;
1441 }
1442
1443 if (helpline.info) {
1444 attron(attrb);
1445 addstr("-- ");
1446 putwcs_trunc(helpline.info,disp_data.scrcols - 6 /* dashes */,OPT_NOPAD);
1447 putstr_trunc_col(" --",disp_data.scrcols,0);
1448 attroff(attrb);
1449 return;
1450 }
1451
1452 if ((msg = helpline.help_tmp) != 0) {
1453 bold = 1;
1454 if (helpline.exp_tmp == 0)
1455 helpline.exp_tmp = time(0) + HELPTMPTIME;
1456 }
1457 else if ((msg = panel->help) == 0 && (msg = helpline.help_base) == 0) {
1458 clrtoeol();
1459 return;
1460 }
1461
1462 addch(' ');
1463 BLANK(disp_data.scrcols - wc_cols(msg,0,-1) - 2 * MARGIN1);
1464 if (bold)
1465 attron(attrb);
1466 putwcs_trunc_col(msg,disp_data.scrcols,0);
1467 if (bold)
1468 attroff(attrb);
1469 }
1470
1471 /* HELPMSG_XXX defined in inout.h */
1472 void
1473 win_sethelp(int type, const wchar_t *msg)
1474 {
1475 switch (type) {
1476 case HELPMSG_BASE:
1477 if (msg != 0 && helpline.help_base != 0)
1478 return; /* must reset to 0 first! */
1479 helpline.help_base = msg;
1480 break;
1481 case HELPMSG_OVERRIDE:
1482 panel->help = msg;
1483 break;
1484 case HELPMSG_TMP:
1485 if ((helpline.help_tmp = msg) == 0)
1486 helpline.exp_tmp = 0;
1487 /* exception: HELPMSG_TMP can be called while curses is disabled */
1488 if (!disp_data.curses)
1489 return;
1490 break;
1491 case HELPMSG_INFO:
1492 helpline.info = msg;
1493 break;
1494 case HELPMSG_WARNING:
1495 helpline.warning = msg;
1496 win_helpline();
1497 kbd_getany();
1498 helpline.warning = 0;
1499 }
1500 win_helpline();
1501 }
1502
1503 void
1504 win_bar(void)
1505 {
1506 int pad;
1507 static int len = 0;
1508
1509 if (len == 0)
1510 len = wc_cols(user_data.loginw,0,-1) + 1 /* at sign */
1511 + wc_cols(user_data.hostw,0,-1) + MARGIN1 ;
1512
1513 attron(attrr);
1514 move(LNO_BAR,0);
1515
1516 switch (get_current_mode()) {
1517 case MODE_FILE:
1518 bar = L" F1=help alt-M=menu | CLEX file manager " ;
1519 break;
1520 case MODE_HELP:
1521 bar = L" F1=help ctrl-C=exit <-- | CLEX file manager ";
1522 break;
1523 default:
1524 bar = L" F1=help ctrl-C=exit | CLEX file manager ";
1525 }
1526 pad = putwcs_trunc_col(bar, disp_data.scrcols,OPT_NOPAD) - len;
1527 if (pad < 0)
1528 char_line(' ',len + pad);
1529 else {
1530 BLANK(pad);
1531 putwcs_trunc(user_data.loginw,len,OPT_NOPAD);
1532 addch('@');
1533 putwcs_trunc_col(user_data.hostw,disp_data.scrcols,0);
1534 }
1535 attroff(attrr);
1536 }
1537
1538 void
1539 win_edit(void)
1540 {
1541 int len, width, i, last, written;
1542 const wchar_t *str;
1543
1544 /* special case: no textline */
1545 if (textline == 0) {
1546 move(LNO_EDIT,0);
1547 clrtobot();
1548 return;
1549 }
1550
1551 str = USTR(textline->line) + textline->offset;
1552 len = wcslen(str);
1553 for (i = 0; i < disp_data.cmdlines; i++) {
1554 move(LNO_EDIT + i,0);
1555 last = i == disp_data.cmdlines - 1; /* 1 or 0 */
1556 width = disp_data.scrcols - last; /* avoid writing to the bottom right corner */
1557
1558 if (i == 0) {
1559 /* prompt */
1560 if (textline->offset == 0) {
1561 if (textline->size > 0
1562 && ( (panel->type != PANEL_TYPE_DIR_SPLIT
1563 && panel->type != PANEL_TYPE_DIR) || panel->norev) )
1564 attrset(attrb);
1565 addwstr(USTR(textline->prompt));
1566 width -= textline->promptwidth;
1567 }
1568 else {
1569 attrset(attrb);
1570 addch('<');
1571 width--;
1572 }
1573 attroff(attrb);
1574 }
1575
1576 if (len == 0) {
1577 clrtoeol();
1578 written = width; /* all spaces */
1579 }
1580 else {
1581 written = putwcs_trunc(str,width,last ? 0 : OPT_NOCONT);
1582 if (written > len)
1583 len = 0;
1584 else {
1585 str += written;
1586 len -= written;
1587 }
1588 }
1589 chars_in_line[i] = written;
1590 }
1591 }
1592
1593 /* number of textline characters written to by win_edit() */
1594 int
1595 sum_linechars(void)
1596 {
1597 int i, sum;
1598
1599 sum = chars_in_line[0];
1600 for (i = 1; i < disp_data.cmdlines; i++)
1601 sum += chars_in_line[i];
1602
1603 return sum;
1604 }
1605
1606 /****** win_panel() and friends ******/
1607
1608 void
1609 draw_line_bm(int ln)
1610 {
1611 putwcs_trunc(SDSTR(panel_bm.bm[ln]->name),panel_bm.cw_name,0);
1612 addstr(" ");
1613 putwcs_trunc_col(USTR(panel_bm.bm[ln]->dirw),disp_data.panrcol,OPT_SQUEEZE);
1614 }
1615
1616 void
1617 draw_line_bm_edit(int ln)
1618 {
1619 wchar_t *tag, *msg;
1620
1621 if (ln == 0) {
1622 tag = L" name: ";
1623 msg = SDSTR(panel_bm_edit.bm->name);
1624 } else {
1625 /* ln == 1 */
1626 tag = L"directory: ";
1627 msg = USTR(panel_bm_edit.bm->dir) ? USTR(panel_bm_edit.bm->dirw) : L"";
1628 }
1629
1630 addwstr(tag);
1631 if (*msg == L'\0')
1632 msg = L"-- none --";
1633 putwcs_trunc_col(msg,disp_data.panrcol,0);
1634 }
1635
1636 void
1637 draw_line_cfg(int ln)
1638 {
1639 putstr_trunc(panel_cfg.config[ln].var,CFGVAR_LEN,0);
1640 addstr(" = ");
1641 putwcs_trunc_col(cfg_print_value(ln),disp_data.panrcol,0);
1642 }
1643
1644 void
1645 draw_line_cfg_menu(int ln)
1646 {
1647 putwcs_trunc(panel_cfg_menu.desc[ln],disp_data.pancols,0);
1648 }
1649
1650 void
1651 draw_line_cmp(int ln)
1652 {
1653 /* see also win_infoline() */
1654 static const wchar_t *description[CMP_TOTAL_ + 1] = {
1655 L"restrict to regular files only",
1656 L"compare file size",
1657 L"compare file mode",
1658 L"compare file ownership (user and group)",
1659 L"compare file data (contents)",
1660 L"--> Compare name, type and attributes selected above"
1661 };
1662
1663 if (ln < CMP_TOTAL_)
1664 CHECKBOX(COPT(ln));
1665 putwcs_trunc_col(description[ln],disp_data.panrcol,0);
1666 }
1667
1668 void
1669 draw_line_cmp_sum(int ln)
1670 {
1671 static const wchar_t *description[] = {
1672 L"total number of files in panels",
1673 L"\\_ UNIQUE FILENAMES ",
1674 L"\\_ pairs of files compared ",
1675 L"\\_ DIFFERING",
1676 L"\\_ ERRORS ", /* line #4 is hidden if there are no errors */
1677 L"\\_ equal "
1678 };
1679 wchar_t *txt, buf[64];
1680 int p1, p2;
1681 FLAG marked;
1682
1683 if (ln >= 4 && panel->cnt != ARRAY_SIZE(description))
1684 ln++;
1685
1686 txt = buf;
1687 marked = 0;
1688 switch (ln) {
1689 case 0:
1690 p1 = ppanel_file->pd->cnt - panel_cmp_sum.nonreg1;
1691 p2 = ppanel_file->other->pd->cnt - panel_cmp_sum.nonreg2;
1692 swprintf(txt,ARRAY_SIZE(buf),L"%4d + %d%ls",p1,p2,
1693 COPT(CMP_REGULAR) ? L" (regular files only)" : L"");
1694 break;
1695 case 1:
1696 p1 = ppanel_file->pd->cnt - panel_cmp_sum.nonreg1 - panel_cmp_sum.names;
1697 p2 = ppanel_file->other->pd->cnt - panel_cmp_sum.nonreg2 - panel_cmp_sum.names;
1698 if ( (marked = p1 > 0 || p2 > 0) )
1699 swprintf(txt,ARRAY_SIZE(buf),L"%4d + %d",p1,p2);
1700 else
1701 txt = L" -";
1702 break;
1703 case 2:
1704 swprintf(txt,ARRAY_SIZE(buf),L" %4d",panel_cmp_sum.names);
1705 break;
1706 case 3:
1707 p1 = panel_cmp_sum.names - panel_cmp_sum.equal - panel_cmp_sum.errors;
1708 if ( (marked = p1 > 0) )
1709 swprintf(txt,ARRAY_SIZE(buf),L" %4d",p1);
1710 else
1711 txt = L" -";
1712 break;
1713 case 4:
1714 swprintf(txt,ARRAY_SIZE(buf),L" %4d",panel_cmp_sum.errors);
1715 marked = 1;
1716 break;
1717 case 5:
1718 swprintf(txt,ARRAY_SIZE(buf),L" %4d",panel_cmp_sum.equal);
1719 break;
1720 }
1721 BLANK(32 - wc_cols(description[ln],0,-1));
1722 addwstr(description[ln]);
1723 addstr(": ");
1724 if (marked)
1725 attron(attrb);
1726 putwcs_trunc_col(txt,disp_data.panrcol,0);
1727 if (marked)
1728 attroff(attrb);
1729 }
1730
1731 void
1732 draw_line_compl(int ln)
1733 {
1734 COMPL_ENTRY *pcc;
1735
1736 pcc = panel_compl.cand[ln];
1737 if (panel_compl.filenames) {
1738 addstr(pcc->is_link ? "-> " : " " ); /* 3 */
1739 addstr(type_symbol[pcc->file_type]); /* 4 */
1740 BLANK(2);
1741 }
1742 else
1743 BLANK(9);
1744 putwcs_trunc(SDSTR(pcc->str),disp_data.pancols - 9,0);
1745 }
1746
1747 void
1748 draw_line_dir(int ln)
1749 {
1750 int shlen, width;
1751 const wchar_t *dir;
1752
1753 dir = panel_dir.dir[ln].namew;
1754 shlen = panel_dir.dir[ln].shlen;
1755
1756 if (shlen && (ln == panel_dir.pd->top || shlen >= disp_data.pancols))
1757 shlen = 0;
1758 if (shlen == 0)
1759 width = 0;
1760 else {
1761 width = wc_cols(dir,0,shlen);
1762 BLANK(width - 2);
1763 addstr("__");
1764 }
1765 putwcs_trunc_col(dir + shlen,disp_data.panrcol,0);
1766 }
1767
1768 void
1769 draw_line_dir_split(int ln)
1770 {
1771 putwcs_trunc(convert2w(dir_split_dir(ln)),disp_data.pancols,0);
1772 }
1773
1774 void
1775 draw_line_file(int ln)
1776 {
1777 FILE_ENTRY *pfe;
1778
1779 pfe = ppanel_file->files[ln];
1780 if (pfe->select && !pfe->dotdir)
1781 attron(attrb);
1782
1783 /* 10 columns reserved for the filename */
1784 print_fields(pfe,disp_data.pancols - 10,disp_data.layout_panel);
1785 if (!pfe->symlink)
1786 putwcs_trunc_col(SDSTR(pfe->filew),disp_data.panrcol,0);
1787 else {
1788 putwcs_trunc_col(SDSTR(pfe->filew),disp_data.panrcol,OPT_NOPAD);
1789 putwcs_trunc_col(L" -> ",disp_data.panrcol,OPT_NOPAD);
1790 putwcs_trunc_col(USTR(pfe->linkw),disp_data.panrcol,0);
1791 }
1792
1793 if (pfe->select)
1794 attroff(attrb);
1795 }
1796
1797 void
1798 draw_line_fopt(int ln)
1799 {
1800 static const wchar_t *description[FOPT_TOTAL_] = {
1801 L"substring matching: ignore the case of the characters",
1802 L"pattern matching: wildcards match the dot in hidden .files",
1803 L"file panel filtering: always show directories",
1804 /* must correspond with FOPT_XXX */
1805 };
1806
1807 CHECKBOX(FOPT(ln));
1808 putwcs_trunc(description[ln],disp_data.pancols - BOX4,0);
1809 }
1810
1811 void
1812 draw_line_group(int ln)
1813 {
1814 printw("%6u ",(unsigned int)panel_group.groups[ln].gid);
1815 putwcs_trunc_col(panel_group.groups[ln].group,disp_data.panrcol,0);
1816 }
1817
1818 void
1819 draw_line_help(int ln)
1820 {
1821 HELP_LINE *ph;
1822 int i, active, links;
1823
1824 ph = panel_help.line[ln];
1825 links = ph->links;
1826 putwcs_trunc(ph->text,disp_data.pancols,links ? OPT_NOPAD : 0);
1827 if (links == 0)
1828 return;
1829
1830 active = (ln != panel->curs || panel->filtering) ? -1
1831 : ln == panel_help.lnk_ln ? panel_help.lnk_act : 0;
1832 for (i = 0; i < links; i++) {
1833 attron(i == active ? attrr : attrb);
1834 putwcs_trunc_col(ph[3 * i + 2].text,disp_data.panrcol,OPT_NOPAD);
1835 attroff(i == active ? attrr : attrb);
1836 putwcs_trunc_col(ph[3 * i + 3].text,disp_data.panrcol,i == links - 1 ? 0 : OPT_NOPAD);
1837 }
1838 }
1839
1840 void
1841 draw_line_hist(int ln)
1842 {
1843 static const wchar_t *failstr;
1844 static int faillen = 0;
1845
1846 if (faillen == 0) {
1847 failstr = L"failed: ";
1848 faillen = wc_cols(failstr,0,-1);
1849 }
1850 if (panel_hist.hist[ln]->failed)
1851 addwstr(failstr);
1852 else
1853 BLANK(faillen);
1854 putwcs_trunc(USTR(panel_hist.hist[ln]->cmd),disp_data.pancols - faillen,0);
1855 }
1856
1857 void
1858 draw_line_log(int ln)
1859 {
1860 int i, len;
1861 const wchar_t *msg;
1862 FLAG warn;
1863
1864 msg = USTR(panel_log.line[ln]->msg);
1865 if ( (warn = panel_log.line[ln]->level == MSG_W) )
1866 attron(attrb);
1867 if (panel_log.scroll == 0)
1868 putwcs_trunc(msg,disp_data.pancols,0);
1869 else {
1870 addch('<' | attrb);
1871 for (i = len = 0; msg[i] != L'\0' && len < panel_log.scroll; i++)
1872 len += WCW(msg[i]);
1873 while (utf_iscomposing(msg[i]))
1874 i++;
1875 putwcs_trunc(msg + i,disp_data.pancols - 1,0);
1876 }
1877 if (warn)
1878 attroff(attrb);
1879 }
1880
1881 void
1882 draw_line_mainmenu(int ln)
1883 {
1884 static const wchar_t *description[] = {
1885 L"help <F1>",
1886 L"change working directory alt-W",
1887 L" change into root directory alt-/",
1888 L" change into parent directory alt-.",
1889 L" change into home directory alt-~ or alt-`",
1890 L" bookmarks alt-K",
1891 L"Bookmark the current directory ctrl-D",
1892 L"command history alt-H",
1893 L"sort order for filenames alt-S",
1894 L"re-read the current directory ctrl-R",
1895 L"compare directories alt-=",
1896 L"filter on/off ctrl-F",
1897 L"select files: select using pattern alt-+",
1898 L" deselect using pattern alt--",
1899 L" invert selection alt-*",
1900 L"filtering and pattern matching options alt-O",
1901 L"user (group) information alt-U (alt-G)",
1902 L"message log alt-L",
1903 L"notifications alt-N",
1904 L"configure CLEX alt-C",
1905 L"program version alt-V",
1906 L"quit alt-Q"
1907 /* must correspond with tab_mainmenu[] in control.c */
1908 };
1909
1910 putwcs_trunc(description[ln],disp_data.pancols,0);
1911 }
1912
1913 void
1914 draw_line_notif(int ln)
1915 {
1916 static const wchar_t *description[NOTIF_TOTAL_] = {
1917 L"Warning: rm command deletes files (not 100% reliable)",
1918 L"Warning: command line is too long to be displayed",
1919 L"Reminder: selection marks on . and .. are not honored",
1920 L"Reminder: selected file(s) vs. current file",
1921 L"Notice: file with a timestamp in the future encountered"
1922 /* must correspond with NOPT_XXX */
1923 };
1924
1925 CHECKBOX(!NOPT(ln));
1926 putwcs_trunc(description[ln],disp_data.pancols - BOX4,0);
1927 }
1928
1929 void
1930 draw_line_paste(int ln)
1931 {
1932 static const wchar_t *description[] = {
1933 L"the name to be completed starts at the cursor position",
1934 L"complete a name: automatic",
1935 L" file: any type",
1936 L" file: directory",
1937 L" file: executable",
1938 L" user",
1939 L" group",
1940 L" environment variable",
1941 L"complete a command from the command history alt-P",
1942 L"insert: the current filename <F2>",
1943 L" all selected filenames <esc> <F2>",
1944 L" the full pathname of current file ctrl-A",
1945 L" the secondary working directory name ctrl-E",
1946 L" the current working directory name <esc> ctrl-E",
1947 L" the target of a symbolic link ctrl-O"
1948 /* must correspond with tab_pastemenu[] in control.c */
1949 };
1950 if (ln == 0)
1951 CHECKBOX(panel_paste.wordstart);
1952 putwcs_trunc_col(description[ln],disp_data.panrcol,0);
1953 }
1954
1955 void
1956 draw_line_preview(int ln)
1957 {
1958 if (ln >= panel_preview.realcnt) {
1959 attron(attrb);
1960 putwcs_trunc(L" --- end of preview ---",disp_data.pancols,0);
1961 attroff(attrb);
1962 return;
1963 }
1964
1965 putwcs_trunc(USTR(panel_preview.line[ln]),disp_data.pancols,0);
1966 }
1967
1968 void
1969 draw_line_sort(int ln)
1970 {
1971 int size;
1972
1973 /* see also win_infoline() */
1974 static const wchar_t
1975 *description0[HIDE_TOTAL_] = {
1976 L"show hidden .files",
1977 L"show hidden .files, but not in the home directory",
1978 L"do not show hidden .files"
1979 /* must correspond with HIDE_XXX */
1980 },
1981 *description1[GROUP_TOTAL_] = {
1982 L"do not group files by type",
1983 L"group: directories, special files, plain files",
1984 L"group: directories, devices, special files, plain files"
1985 /* must correspond with GROUP_XXX */
1986 },
1987 *description2[SORT_TOTAL_] = {
1988 L"sort by name and number",
1989 L"sort by name",
1990 L"sort by filename.EXTENSION",
1991 L"sort by size [small -> large]",
1992 L"sort by size [large -> small]",
1993 L"sort by time of last modification [recent -> old]",
1994 L"sort by time of last modification [old -> recent]",
1995 L"sort by reversed name"
1996 /* must correspond with SORT_XXX */
1997 },
1998 *description3[3] = {
1999 L"--> apply to ALL DIRECTORIES",
2000 L"--> apply to this panel and THIS DIRECTORY ONLY"
2001 };
2002
2003 size = ARRAY_SIZE(description0);
2004 if (ln < size) {
2005 RADIOBUTTON(panel_sort.newhide == ln);
2006 putwcs_trunc(description0[ln],disp_data.pancols - BOX4,0);
2007 return;
2008 }
2009 if (ln == size) {
2010 putstr_trunc("----------------",disp_data.pancols,0);
2011 return;
2012 }
2013 ln -= size + 1;
2014
2015 size = ARRAY_SIZE(description1);
2016 if (ln < size) {
2017 RADIOBUTTON(panel_sort.newgroup == ln);
2018 putwcs_trunc(description1[ln],disp_data.pancols - BOX4,0);
2019 return;
2020 }
2021 if (ln == size) {
2022 putstr_trunc("----------------",disp_data.pancols,0);
2023 return;
2024 }
2025 ln -= size + 1;
2026
2027 size = ARRAY_SIZE(description2);
2028 if (ln < size) {
2029 RADIOBUTTON(panel_sort.neworder == ln);
2030 putwcs_trunc(description2[ln],disp_data.pancols - BOX4,0);
2031 return;
2032 }
2033 ln -= size;
2034
2035 putwcs_trunc(description3[ln],disp_data.pancols,0);
2036 return;
2037 }
2038
2039 #define MIN_GECOS 10 /* columns reserved for the gecos field */
2040
2041 void
2042 draw_line_user(int ln)
2043 {
2044 int col;
2045
2046 printw("%6u ",(unsigned int)panel_user.users[ln].uid);
2047 col = MARGIN2 + 8 /*printw above*/ + panel_user.maxlen;
2048 if (col > disp_data.panrcol - MIN_GECOS - 1 /* 1 space */)
2049 col = disp_data.panrcol - MIN_GECOS - 1;
2050 putwcs_trunc_col(panel_user.users[ln].login,MARGIN2 + 8 + panel_user.maxlen,0);
2051 addch(' ');
2052 putwcs_trunc_col(panel_user.users[ln].gecos,disp_data.panrcol,0);
2053 }
2054
2055 static void
2056 draw_panel_line(int curs)
2057 {
2058 const wchar_t *msg;
2059
2060 move(LNO_PANEL + curs - panel->top,0);
2061 if (curs >= panel->cnt) {
2062 clrtoeol();
2063 return;
2064 }
2065
2066 if (panel->curs == curs) {
2067 addch('>');
2068 if (!panel->norev)
2069 attron(attrr);
2070 addch(' ');
2071 }
2072 else
2073 addstr(" ");
2074 if (curs < 0) {
2075 /* extra line */
2076 addstr("--> ");
2077 msg = panel->extra[curs - panel->min].text;
2078 putwcs_trunc(msg ? msg : L"Exit this panel",disp_data.pancols - 4 /* arrow */,0);
2079 }
2080 else
2081 (*panel->drawfn)(curs);
2082 if (panel->curs == curs) {
2083 addch(' ');
2084 attroff(attrr);
2085 addch('<');
2086 }
2087 else
2088 addstr(" ");
2089 }
2090
2091 static void
2092 draw_panel(int optimize)
2093 {
2094 static int save_top, save_curs, save_ptype = -1;
2095 int curs;
2096
2097 if (panel->type != save_ptype) {
2098 /* panel type has changed */
2099 optimize = 0;
2100 save_ptype = panel->type;
2101 }
2102
2103 if (optimize && save_top == panel->top) {
2104 /* redraw only the old and new current lines */
2105 draw_panel_line(save_curs);
2106 if (save_curs != panel->curs) {
2107 posctl.update = 1;
2108 draw_panel_line(panel->curs);
2109 save_curs = panel->curs;
2110 }
2111 }
2112 else {
2113 posctl.update = 1;
2114 /* redraw all lines */
2115 for (curs = panel->top; curs < panel->top + disp_data.panlines; curs++)
2116 draw_panel_line(curs);
2117 save_top = panel->top;
2118 save_curs = panel->curs;
2119 }
2120
2121 win_infoline();
2122 }
2123
2124 /* win_panel() without optimization */
2125 void
2126 win_panel(void)
2127 {
2128 draw_panel(0);
2129 }
2130
2131 /*
2132 * win_panel() with optimization
2133 *
2134 * use this win_panel() version if the only change made since last
2135 * win_panel() call is a cursor movement or a modification of the
2136 * current line
2137 */
2138 void
2139 win_panel_opt(void)
2140 {
2141 draw_panel(1);
2142 }
2143