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