1 /* vifm
2  * Copyright (C) 2001 Ken Steen.
3  * Copyright (C) 2011 xaizek.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 /* Save wrefresh identifier behind the macro before it's poisoned in the
21  * header. */
22 #define use_wrefresh wrefresh
23 
24 #include "ui.h"
25 
26 #include <curses.h> /* mvwin() werase() */
27 
28 #ifndef _WIN32
29 #include <sys/ioctl.h>
30 #include <termios.h> /* struct winsize */
31 #endif
32 #include <unistd.h>
33 
34 #include <assert.h> /* assert() */
35 #include <ctype.h> /* isdigit() */
36 #include <errno.h> /* errno */
37 #include <stddef.h> /* NULL size_t wchar_t */
38 #include <stdint.h> /* uint64_t */
39 #include <stdlib.h> /* abs() free() */
40 #include <stdio.h> /* snprintf() vsnprintf() */
41 #include <string.h> /* memset() strcat() strcmp() strcpy() strdup() strlen() */
42 #include <wchar.h> /* wint_t wcslen() */
43 
44 #include "../cfg/config.h"
45 #include "../cfg/info.h"
46 #include "../compat/curses.h"
47 #include "../compat/fs_limits.h"
48 #include "../compat/pthread.h"
49 #include "../engine/mode.h"
50 #include "../int/term_title.h"
51 #include "../modes/dialogs/msg_dialog.h"
52 #include "../modes/modes.h"
53 #include "../modes/view.h"
54 #include "../modes/wk.h"
55 #include "../utils/fs.h"
56 #include "../utils/log.h"
57 #include "../utils/macros.h"
58 #include "../utils/matchers.h"
59 #include "../utils/path.h"
60 #include "../utils/str.h"
61 #include "../utils/string_array.h"
62 #include "../utils/utf8.h"
63 #include "../utils/utils.h"
64 #include "../event_loop.h"
65 #include "../filelist.h"
66 #include "../flist_sel.h"
67 #include "../macros.h"
68 #include "../opt_handlers.h"
69 #include "../status.h"
70 #include "../vifm.h"
71 #include "private/statusline.h"
72 #include "cancellation.h"
73 #include "color_manager.h"
74 #include "color_scheme.h"
75 #include "colored_line.h"
76 #include "colors.h"
77 #include "fileview.h"
78 #include "quickview.h"
79 #include "statusbar.h"
80 #include "statusline.h"
81 #include "tabs.h"
82 
83 /* Information for formatting tab title. */
84 typedef struct
85 {
86 	char *name;               /* Tab name. */
87 	char *num;                /* Tab number. */
88 	char *escaped_view_title; /* Auto-formatted title. */
89 	char *escaped_path;       /* Current path (could be a file path). */
90 	char *cv_title;           /* Prefix of custom view. */
91 	int tree;                 /* Whether in tree mode. */
92 	int current;              /* Whether it's a current tab. */
93 }
94 tab_title_info_t;
95 
96 /* Type of path transformation function for format_view_title(). */
97 typedef char * (*path_func)(const char[]);
98 
99 /* Window configured to do not wait for input.  Never appears on the screen,
100  * sometimes used for reading input. */
101 static WINDOW *no_delay_window;
102 /* Window configured to wait for input indefinitely.  Never appears on the
103  * screen, sometimes used for reading input. */
104 static WINDOW *inf_delay_window;
105 
106 static WINDOW *ltop_line1;
107 static WINDOW *ltop_line2;
108 static WINDOW *rtop_line1;
109 static WINDOW *rtop_line2;
110 
111 /* Mutexes for views, located out of view_t so that they are never moved nor
112  * copied, which would yield undefined behaviour. */
113 static pthread_mutex_t lwin_timestamps_mutex = PTHREAD_MUTEX_INITIALIZER;
114 static pthread_mutex_t rwin_timestamps_mutex = PTHREAD_MUTEX_INITIALIZER;
115 
116 view_t lwin = { .timestamps_mutex = &lwin_timestamps_mutex };
117 view_t rwin = { .timestamps_mutex = &rwin_timestamps_mutex };
118 
119 view_t *other_view;
120 view_t *curr_view;
121 
122 WINDOW *status_bar;
123 WINDOW *stat_win;
124 WINDOW *job_bar;
125 WINDOW *ruler_win;
126 WINDOW *input_win;
127 WINDOW *menu_win;
128 WINDOW *sort_win;
129 WINDOW *change_win;
130 WINDOW *error_win;
131 
132 static WINDOW *top_line;
133 static WINDOW *tab_line;
134 
135 static WINDOW *lborder;
136 static WINDOW *mborder;
137 static WINDOW *rborder;
138 
139 static int pair_in_use(short int pair);
140 static void move_pair(short int from, short int to);
141 static void create_windows(void);
142 static void update_geometry(void);
143 static void adjust_splitter(int screen_w, int screen_h);
144 static int get_working_area_height(void);
145 static void clear_border(WINDOW *border);
146 static int middle_border_is_visible(void);
147 static void update_views(int reload);
148 static void reload_lists(void);
149 static void reload_list(view_t *view);
150 static void update_view(view_t *view);
151 static void update_window_lazy(WINDOW *win);
152 static void update_term_size(void);
153 static void update_statusbar_layout(void);
154 static int are_statusbar_widgets_visible(void);
155 static int get_ruler_width(view_t *view);
156 static char * expand_ruler_macros(view_t *view, const char format[]);
157 static void switch_panes_content(void);
158 static void set_splitter(int pos);
159 static FileType ui_view_entry_target_type(const dir_entry_t *entry);
160 static void refresh_bottom_lines(void);
161 static char * path_identity(const char path[]);
162 static int view_shows_tabline(const view_t *view);
163 static int get_tabline_height(void);
164 static void print_tabline(WINDOW *win, view_t *view, col_attr_t base_col,
165 		path_func pf);
166 static void compute_avg_width(int *avg_width, int *spare_width,
167 		int min_widths[], int max_width, view_t *view, path_func pf);
168 TSTATIC cline_t make_tab_title(const tab_title_info_t *title_info);
169 TSTATIC tab_title_info_t make_tab_title_info(const tab_info_t *tab_info,
170 		path_func pf, int tab_num, int current_tab);
171 TSTATIC void dispose_tab_title_info(tab_title_info_t *title_info);
172 static cline_t format_tab_part(const char fmt[],
173 		const tab_title_info_t *title_info);
174 static char * format_view_title(const view_t *view, path_func pf);
175 static void print_view_title(const view_t *view, int active_view, char title[]);
176 static col_attr_t fixup_titles_attributes(const view_t *view, int active_view);
177 static int is_in_miller_view(const view_t *view);
178 static int is_forced_list_mode(const view_t *view);
179 
180 void
ui_ruler_update(view_t * view,int lazy_redraw)181 ui_ruler_update(view_t *view, int lazy_redraw)
182 {
183 	char *expanded;
184 
185 	if(!are_statusbar_widgets_visible())
186 	{
187 		/* Do nothing, especially don't update layout because it might be a custom
188 		 * layout at the moment. */
189 		return;
190 	}
191 
192 	update_statusbar_layout();
193 
194 	expanded = expand_ruler_macros(view, cfg.ruler_format);
195 	expanded = break_in_two(expanded, getmaxx(ruler_win), "%=");
196 
197 	ui_ruler_set(expanded);
198 	if(!lazy_redraw)
199 	{
200 		ui_refresh_win(ruler_win);
201 	}
202 
203 	free(expanded);
204 }
205 
206 void
ui_ruler_set(const char val[])207 ui_ruler_set(const char val[])
208 {
209 	const int x = getmaxx(ruler_win) - strlen(val);
210 
211 	werase(ruler_win);
212 	mvwaddstr(ruler_win, 0, MAX(x, 0), val);
213 	wnoutrefresh(ruler_win);
214 }
215 
216 int
setup_ncurses_interface(void)217 setup_ncurses_interface(void)
218 {
219 	int screen_x, screen_y;
220 
221 	initscr();
222 	noecho();
223 	nonl();
224 	raw();
225 
226 	curs_set(0);
227 
228 	getmaxyx(stdscr, screen_y, screen_x);
229 	/* Screen is too small to be useful. */
230 	if(screen_y < MIN_TERM_HEIGHT || screen_x < MIN_TERM_WIDTH)
231 	{
232 		vifm_finish("Terminal is too small to run vifm.");
233 	}
234 
235 	if(!has_colors())
236 	{
237 		vifm_finish("Vifm requires a console that can support color.");
238 	}
239 
240 	start_color();
241 	use_default_colors();
242 
243 	const colmgr_conf_t colmgr_conf = {
244 		.max_color_pairs = COLOR_PAIRS,
245 		.max_colors = COLORS,
246 		.init_pair = &init_pair,
247 		.pair_content = &pair_content,
248 		.pair_in_use = &pair_in_use,
249 		.move_pair = &move_pair,
250 	};
251 	colmgr_init(&colmgr_conf);
252 
253 	cs_load_defaults();
254 
255 	create_windows();
256 
257 	cfg.tab_stop = TABSIZE;
258 
259 #ifdef ENABLE_EXTENDED_KEYS
260 	keypad(status_bar, TRUE);
261 #endif /* ENABLE_EXTENDED_KEYS */
262 
263 #if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20081102
264 #ifdef HAVE_SET_ESCDELAY_FUNC
265 	/* Use ncurses specific function to make delay after pressing escape key
266 	 * unnoticeable.  Used to be zero, but in some corner cases multiple bytes
267 	 * composing a functional key code might be handled to the application with a
268 	 * delay. */
269 	set_escdelay(5);
270 #endif
271 #endif
272 
273 	ui_resize_all();
274 
275 	return 1;
276 }
277 
278 /* Checks whether pair is being used at the moment.  Returns non-zero if so and
279  * zero otherwise. */
280 static int
pair_in_use(short int pair)281 pair_in_use(short int pair)
282 {
283 	int i;
284 
285 	for(i = 0; i < MAXNUM_COLOR; ++i)
286 	{
287 		if(cfg.cs.pair[i] == pair || lwin.cs.pair[i] == pair ||
288 				rwin.cs.pair[i] == pair)
289 		{
290 			return 1;
291 		}
292 	}
293 
294 	return 0;
295 }
296 
297 /* Substitutes old pair number with the new one. */
298 static void
move_pair(short int from,short int to)299 move_pair(short int from, short int to)
300 {
301 	int i;
302 	for(i = 0; i < MAXNUM_COLOR; ++i)
303 	{
304 		if(cfg.cs.pair[i] == from)
305 		{
306 			cfg.cs.pair[i] = to;
307 		}
308 		if(lwin.cs.pair[i] == from)
309 		{
310 			lwin.cs.pair[i] = to;
311 		}
312 		if(rwin.cs.pair[i] == from)
313 		{
314 			rwin.cs.pair[i] = to;
315 		}
316 	}
317 }
318 
319 /* Initializes all WINDOW variables by calling newwin() to create ncurses
320  * windows and configures hardware cursor. */
321 static void
create_windows(void)322 create_windows(void)
323 {
324 	no_delay_window = newwin(1, 1, 0, 0);
325 	wtimeout(no_delay_window, 0);
326 
327 	inf_delay_window = newwin(1, 1, 0, 0);
328 	wtimeout(inf_delay_window, -1);
329 
330 	/* These refreshes prevent curses from drawing these windows on first read
331 	 * from them.  It seems that this way they get updated early and this keeps
332 	 * them from being drawn again. */
333 	wnoutrefresh(no_delay_window);
334 	wnoutrefresh(inf_delay_window);
335 
336 	menu_win = newwin(1, 1, 0, 0);
337 	sort_win = newwin(1, 1, 0, 0);
338 	change_win = newwin(1, 1, 0, 0);
339 	error_win = newwin(1, 1, 0, 0);
340 
341 	lborder = newwin(1, 1, 0, 0);
342 
343 	lwin.title = newwin(1, 1, 0, 0);
344 	lwin.win = newwin(1, 1, 0, 0);
345 
346 	mborder = newwin(1, 1, 0, 0);
347 
348 	ltop_line1 = newwin(1, 1, 0, 0);
349 	ltop_line2 = newwin(1, 1, 0, 0);
350 
351 	top_line = newwin(1, 1, 0, 0);
352 	tab_line = newwin(1, 1, 0, 0);
353 
354 	rtop_line1 = newwin(1, 1, 0, 0);
355 	rtop_line2 = newwin(1, 1, 0, 0);
356 
357 	rwin.title = newwin(1, 1, 0, 0);
358 	rwin.win = newwin(1, 1, 0, 0);
359 
360 	rborder = newwin(1, 1, 0, 0);
361 
362 	stat_win = newwin(1, 1, 0, 0);
363 	job_bar = newwin(1, 1, 0, 0);
364 	status_bar = newwin(1, 1, 0, 0);
365 	ruler_win = newwin(1, 1, 0, 0);
366 	input_win = newwin(1, 1, 0, 0);
367 
368 	leaveok(menu_win, FALSE);
369 	leaveok(sort_win, FALSE);
370 	leaveok(change_win, FALSE);
371 	leaveok(error_win, FALSE);
372 	leaveok(lwin.win, FALSE);
373 	leaveok(rwin.win, FALSE);
374 	leaveok(status_bar, FALSE);
375 
376 	leaveok(lborder, TRUE);
377 	leaveok(lwin.title, TRUE);
378 	leaveok(mborder, TRUE);
379 	leaveok(ltop_line1, TRUE);
380 	leaveok(ltop_line2, TRUE);
381 	leaveok(top_line, TRUE);
382 	leaveok(tab_line, TRUE);
383 	leaveok(rtop_line1, TRUE);
384 	leaveok(rtop_line2, TRUE);
385 	leaveok(rwin.title, TRUE);
386 	leaveok(rborder, TRUE);
387 	leaveok(stat_win, TRUE);
388 	leaveok(job_bar, TRUE);
389 	leaveok(ruler_win, TRUE);
390 	leaveok(input_win, TRUE);
391 }
392 
393 void
ui_update_term_state(void)394 ui_update_term_state(void)
395 {
396 	int screen_x, screen_y;
397 
398 	update_term_size();
399 	getmaxyx(stdscr, screen_y, screen_x);
400 	(void)stats_update_term_state(screen_x, screen_y);
401 }
402 
403 int
ui_char_pressed(wint_t c)404 ui_char_pressed(wint_t c)
405 {
406 	if(curr_stats.load_stage < 2)
407 	{
408 		return 0;
409 	}
410 
411 	ui_cancellation_push_off();
412 	wint_t pressed;
413 
414 	while(1)
415 	{
416 		pressed = L'\0';
417 		/* Query single character in non-blocking mode. */
418 		if(compat_wget_wch(no_delay_window, &pressed) == ERR)
419 		{
420 			break;
421 		}
422 
423 		if(c != NC_C_c && pressed == NC_C_c)
424 		{
425 			ui_cancellation_request();
426 		}
427 
428 		if(pressed == c)
429 		{
430 			break;
431 		}
432 	}
433 
434 	ui_cancellation_pop();
435 
436 	return (pressed == c);
437 }
438 
439 void
ui_drain_input(void)440 ui_drain_input(void)
441 {
442 	wint_t c;
443 	while(compat_wget_wch(no_delay_window, &c) != ERR)
444 	{
445 		/* Discard input. */
446 	}
447 }
448 
449 static void
correct_size(view_t * view)450 correct_size(view_t *view)
451 {
452 	getmaxyx(view->win, view->window_rows, view->window_cols);
453 	fview_update_geometry(view);
454 }
455 
456 /* Updates TUI elements sizes and coordinates for single window
457  * configuration. */
458 static void
only_layout(view_t * view,int screen_x)459 only_layout(view_t *view, int screen_x)
460 {
461 	const int pos_correction = cfg.side_borders_visible ? 1 : 0;
462 	const int size_correction = cfg.side_borders_visible ? -2 : 0;
463 	const int y = get_tabline_height();
464 
465 	wresize(view->title, 1, screen_x + size_correction);
466 	mvwin(view->title, y, pos_correction);
467 
468 	mvwin(ltop_line1, y, 0);
469 	mvwin(ltop_line2, y, 0);
470 
471 	mvwin(rtop_line1, y, screen_x - 1);
472 	mvwin(rtop_line2, y, screen_x - 1);
473 
474 	wresize(tab_line, 1, screen_x);
475 	mvwin(tab_line, 0, 0);
476 
477 	wresize(view->win, get_working_area_height(), screen_x + size_correction);
478 	mvwin(view->win, y + 1, pos_correction);
479 }
480 
481 /* Updates TUI elements sizes and coordinates for vertical configuration of
482  * panes: left one and right one. */
483 static void
vertical_layout(int screen_x)484 vertical_layout(int screen_x)
485 {
486 	const int pos_correction = cfg.side_borders_visible ? 1 : 0;
487 	const int size_correction = cfg.side_borders_visible ? -1 : 0;
488 	const int border_height = get_working_area_height();
489 	const int y = get_tabline_height();
490 
491 	int splitter_pos;
492 	if(curr_stats.splitter_pos < 0)
493 		splitter_pos = screen_x/2 - 1 + screen_x%2;
494 	else
495 		splitter_pos = curr_stats.splitter_pos;
496 
497 	int splitter_width = (cfg.flexible_splitter ? 2 - screen_x%2 : 1);
498 	if(splitter_pos < 4)
499 		splitter_pos = 4;
500 	if(splitter_pos > screen_x - 4 - splitter_width)
501 		splitter_pos = screen_x - 4 - splitter_width;
502 	if(curr_stats.splitter_pos >= 0)
503 		stats_set_splitter_pos(splitter_pos);
504 
505 	wresize(lwin.title, 1, splitter_pos + size_correction);
506 	mvwin(lwin.title, y, pos_correction);
507 
508 	wresize(lwin.win, border_height, splitter_pos + size_correction);
509 	mvwin(lwin.win, y + 1, pos_correction);
510 
511 	ui_set_bg(mborder, &cfg.cs.color[BORDER_COLOR], cfg.cs.pair[BORDER_COLOR]);
512 	wresize(mborder, border_height, splitter_width);
513 	mvwin(mborder, y + 1, splitter_pos);
514 
515 	mvwin(ltop_line1, y, 0);
516 	mvwin(ltop_line2, y, 0);
517 
518 	wresize(tab_line, 1, screen_x);
519 	mvwin(tab_line, 0, 0);
520 
521 	wresize(top_line, 1, splitter_width);
522 	mvwin(top_line, y, splitter_pos);
523 
524 	mvwin(rtop_line1, y, screen_x - 1);
525 	mvwin(rtop_line2, y, screen_x - 1);
526 
527 	wresize(rwin.title, 1,
528 			screen_x - (splitter_pos + splitter_width) + size_correction);
529 	mvwin(rwin.title, y, splitter_pos + splitter_width);
530 
531 	wresize(rwin.win, border_height,
532 			screen_x - (splitter_pos + splitter_width) + size_correction);
533 	mvwin(rwin.win, y + 1, splitter_pos + splitter_width);
534 }
535 
536 /* Updates TUI elements sizes and coordinates for horizontal configuration of
537  * panes: top one and bottom one. */
538 static void
horizontal_layout(int screen_x,int screen_y)539 horizontal_layout(int screen_x, int screen_y)
540 {
541 	const int pos_correction = cfg.side_borders_visible ? 1 : 0;
542 	const int size_correction = cfg.side_borders_visible ? -2 : 0;
543 	const int y = get_tabline_height();
544 
545 	int splitter_pos;
546 
547 	if(curr_stats.splitter_pos < 0)
548 		splitter_pos = screen_y/2 - 1;
549 	else
550 		splitter_pos = curr_stats.splitter_pos;
551 	if(splitter_pos < 2)
552 		splitter_pos = 2;
553 	if(splitter_pos > get_working_area_height() - 1)
554 		splitter_pos = get_working_area_height() - 1;
555 	if(curr_stats.splitter_pos >= 0)
556 		stats_set_splitter_pos(splitter_pos);
557 
558 	wresize(lwin.title, 1, screen_x + size_correction);
559 	mvwin(lwin.title, y, pos_correction);
560 
561 	wresize(rwin.title, 1, screen_x + size_correction);
562 	mvwin(rwin.title, splitter_pos, pos_correction);
563 
564 	wresize(lwin.win, splitter_pos - (y + 1), screen_x + size_correction);
565 	mvwin(lwin.win, y + 1, pos_correction);
566 
567 	wresize(rwin.win, get_working_area_height() - splitter_pos + y,
568 			screen_x + size_correction);
569 	mvwin(rwin.win, splitter_pos + 1, pos_correction);
570 
571 	ui_set_bg(mborder, &cfg.cs.color[BORDER_COLOR], cfg.cs.pair[BORDER_COLOR]);
572 	wresize(mborder, 1, screen_x);
573 	mvwin(mborder, splitter_pos, 0);
574 
575 	mvwin(ltop_line1, y, 0);
576 	mvwin(ltop_line2, splitter_pos, 0);
577 
578 	wresize(tab_line, 1, screen_x);
579 	mvwin(tab_line, 0, 0);
580 
581 	wresize(top_line, 1, 2 - screen_x%2);
582 	mvwin(top_line, y, screen_x/2 - 1 + screen_x%2);
583 
584 	mvwin(rtop_line1, y, screen_x - 1);
585 	mvwin(rtop_line2, splitter_pos, screen_x - 1);
586 
587 	wresize(lborder, screen_y - 1, 1);
588 	mvwin(lborder, y, 0);
589 
590 	wresize(rborder, screen_y - 1, 1);
591 	mvwin(rborder, y, screen_x - 1);
592 }
593 
594 /* Calculates height available for main area that contains file lists.  Returns
595  * the height. */
596 static int
get_working_area_height(void)597 get_working_area_height(void)
598 {
599 	return getmaxy(stdscr)                  /* Total available height. */
600 	     - 1                                /* Top line. */
601 	     - (cfg.display_statusline ? 1 : 0) /* Status line. */
602 	     - ui_stat_job_bar_height()         /* Job bar. */
603 	     - 1                                /* Status bar line. */
604 	     - get_tabline_height();            /* Tab line. */
605 }
606 
607 /* Updates internal data structures to reflect actual terminal geometry. */
608 static void
update_geometry(void)609 update_geometry(void)
610 {
611 	int screen_x, screen_y;
612 
613 	update_term_size();
614 
615 	getmaxyx(stdscr, screen_y, screen_x);
616 	cfg.lines = screen_y;
617 	cfg.columns = screen_x;
618 
619 	LOG_INFO_MSG("New geometry: %dx%d", screen_x, screen_y);
620 
621 	if(curr_stats.initial_lines == INT_MIN)
622 	{
623 		curr_stats.initial_lines = screen_y;
624 		curr_stats.initial_columns = screen_x;
625 	}
626 
627 	load_geometry();
628 }
629 
630 void
ui_quit(int write_info,int force)631 ui_quit(int write_info, int force)
632 {
633 	if(tabs_quit_on_close())
634 	{
635 		vifm_try_leave(write_info, 0, force);
636 	}
637 	else
638 	{
639 		tabs_close();
640 	}
641 }
642 
643 int
cv_unsorted(CVType type)644 cv_unsorted(CVType type)
645 {
646 	return type == CV_VERY || cv_compare(type);
647 }
648 
649 int
cv_compare(CVType type)650 cv_compare(CVType type)
651 {
652 	return type == CV_COMPARE || type == CV_DIFF;
653 }
654 
655 int
cv_tree(CVType type)656 cv_tree(CVType type)
657 {
658 	return type == CV_TREE || type == CV_CUSTOM_TREE;
659 }
660 
661 void
update_screen(UpdateType update_kind)662 update_screen(UpdateType update_kind)
663 {
664 	if(curr_stats.load_stage < 2)
665 		return;
666 
667 	if(update_kind == UT_NONE)
668 		return;
669 
670 	ui_resize_all();
671 
672 	if(curr_stats.restart_in_progress)
673 	{
674 		return;
675 	}
676 
677 	update_attributes();
678 
679 	if(cfg.side_borders_visible)
680 	{
681 		clear_border(lborder);
682 		clear_border(rborder);
683 	}
684 	if(middle_border_is_visible())
685 	{
686 		clear_border(mborder);
687 	}
688 
689 	if(curr_stats.term_state != TS_NORMAL)
690 	{
691 		return;
692 	}
693 
694 	qv_ui_updated();
695 
696 	update_views(update_kind == UT_FULL);
697 	/* Redraw message dialog over updated panes.  It's not very nice to do it
698 	 * here, but for sure better then blocking pane updates by checking for
699 	 * message mode. */
700 	if(vle_mode_is(MSG_MODE))
701 	{
702 		redraw_msg_dialog(0);
703 	}
704 
705 	ui_stat_update(curr_view, 0);
706 
707 	if(!ui_sb_multiline())
708 	{
709 		if(curr_view->selected_files)
710 		{
711 			print_selected_msg();
712 		}
713 		else
714 		{
715 			ui_sb_clear();
716 		}
717 
718 		if(vle_mode_is(VIEW_MODE))
719 		{
720 			modview_ruler_update();
721 		}
722 		else
723 		{
724 			ui_ruler_update(curr_view, 1);
725 		}
726 	}
727 
728 	if(curr_stats.save_msg == 0)
729 	{
730 		ui_sb_clear();
731 	}
732 
733 	if(vle_mode_is(VIEW_MODE) ||
734 			(curr_stats.number_of_windows == 2 && other_view->explore_mode))
735 	{
736 		modview_redraw();
737 	}
738 
739 	update_all_windows();
740 
741 	if(!curr_view->explore_mode)
742 	{
743 		fview_cursor_redraw(curr_view);
744 	}
745 
746 	update_input_buf();
747 	ui_stat_job_bar_redraw();
748 }
749 
750 void
ui_resize_all(void)751 ui_resize_all(void)
752 {
753 	update_geometry();
754 
755 	int screen_w, screen_h;
756 	getmaxyx(stdscr, screen_h, screen_w);
757 	LOG_INFO_MSG("screen_h = %d; screen_w = %d", screen_h, screen_w);
758 
759 	if(stats_update_term_state(screen_w, screen_h) != TS_NORMAL)
760 	{
761 		return;
762 	}
763 
764 	adjust_splitter(screen_w, screen_h);
765 
766 	wresize(stdscr, screen_h, screen_w);
767 	wresize(menu_win, screen_h - 1, screen_w);
768 
769 	int border_h = get_working_area_height();
770 	int border_y = 1 + get_tabline_height();
771 
772 	/* TODO: ideally we shouldn't set any colors here (why do we do it?). */
773 	ui_set_bg(lborder, &cfg.cs.color[BORDER_COLOR], cfg.cs.pair[BORDER_COLOR]);
774 	wresize(lborder, border_h, 1);
775 	mvwin(lborder, border_y, 0);
776 
777 	ui_set_bg(rborder, &cfg.cs.color[BORDER_COLOR], cfg.cs.pair[BORDER_COLOR]);
778 	wresize(rborder, border_h, 1);
779 	mvwin(rborder, border_y, screen_w - 1);
780 
781 	/* These need a resize at least after terminal size was zero or they grow and
782 	 * produce bad looking effect. */
783 	wresize(ltop_line1, 1, 1);
784 	wresize(ltop_line2, 1, 1);
785 	wresize(rtop_line1, 1, 1);
786 	wresize(rtop_line2, 1, 1);
787 
788 	if(curr_stats.number_of_windows == 1)
789 	{
790 		only_layout(&lwin, screen_w);
791 		only_layout(&rwin, screen_w);
792 	}
793 	else
794 	{
795 		if(curr_stats.split == HSPLIT)
796 			horizontal_layout(screen_w, screen_h);
797 		else
798 			vertical_layout(screen_w);
799 	}
800 
801 	correct_size(&lwin);
802 	correct_size(&rwin);
803 
804 	wresize(stat_win, 1, screen_w);
805 	(void)ui_stat_reposition(1, 0);
806 
807 	wresize(job_bar, 1, screen_w);
808 
809 	update_statusbar_layout();
810 
811 	curs_set(0);
812 }
813 
814 /* Adjusts splitter position after screen resize. */
815 static void
adjust_splitter(int screen_w,int screen_h)816 adjust_splitter(int screen_w, int screen_h)
817 {
818 	static int prev_w = -1, prev_h = -1;
819 
820 	if(curr_stats.splitter_pos < 0)
821 	{
822 		prev_w = -1;
823 		prev_h = -1;
824 		return;
825 	}
826 
827 	if(prev_w != screen_w || prev_h != screen_h)
828 	{
829 		stats_set_splitter_ratio(curr_stats.splitter_ratio);
830 		prev_w = screen_w;
831 		prev_h = screen_h;
832 	}
833 }
834 
835 /* Clears border, possibly by filling it with a pattern (depends on
836  * configuration). */
837 static void
clear_border(WINDOW * border)838 clear_border(WINDOW *border)
839 {
840 	int i;
841 	int height;
842 
843 	werase(border);
844 
845 	if(strcmp(cfg.border_filler, " ") == 0)
846 	{
847 		return;
848 	}
849 
850 	height = getmaxy(border);
851 	for(i = 0; i < height; ++i)
852 	{
853 		mvwaddstr(border, i, 0, cfg.border_filler);
854 	}
855 
856 	wnoutrefresh(border);
857 }
858 
859 /* Checks whether mborder should be displayed/updated.  Returns non-zero if so,
860  * otherwise zero is returned. */
861 static int
middle_border_is_visible(void)862 middle_border_is_visible(void)
863 {
864 	return (curr_stats.number_of_windows == 2 && curr_stats.split == VSPLIT);
865 }
866 
867 /* Updates (redraws or reloads) views. */
868 static void
update_views(int reload)869 update_views(int reload)
870 {
871 	if(reload)
872 		reload_lists();
873 	else
874 		redraw_lists();
875 }
876 
877 /* Reloads file lists for both views. */
878 static void
reload_lists(void)879 reload_lists(void)
880 {
881 	reload_list(curr_view);
882 
883 	if(curr_stats.number_of_windows == 2)
884 	{
885 		ui_view_title_update(other_view);
886 		if(curr_stats.preview.on)
887 		{
888 			qv_draw(curr_view);
889 		}
890 		else if(!other_view->explore_mode)
891 		{
892 			reload_list(other_view);
893 		}
894 	}
895 }
896 
897 /* Reloads view handling special case of loading it for the first time during
898  * startup. */
899 static void
reload_list(view_t * view)900 reload_list(view_t *view)
901 {
902 	if(curr_stats.load_stage < 3)
903 	{
904 		const int keep_position = cfg_ch_pos_on(CHPOS_STARTUP)
905 		                        ? 0
906 		                        : !is_dir_list_loaded(view);
907 		load_dir_list(view, keep_position);
908 		return;
909 	}
910 
911 	load_saving_pos(view);
912 }
913 
914 void
change_window(void)915 change_window(void)
916 {
917 	swap_view_roles();
918 
919 	load_view_options(curr_view);
920 
921 	if(window_shows_dirlist(other_view))
922 	{
923 		const col_scheme_t *cs = ui_view_get_cs(other_view);
924 		if(cs_is_color_set(&cs->color[OTHER_WIN_COLOR]))
925 		{
926 			draw_dir_list(other_view);
927 		}
928 		else
929 		{
930 			fview_draw_inactive_cursor(other_view);
931 		}
932 	}
933 
934 	if(curr_stats.preview.on && !is_dir_list_loaded(curr_view))
935 	{
936 		/* This view hasn't been loaded since startup yet, do it now. */
937 		navigate_to(curr_view, curr_view->curr_dir);
938 	}
939 	else
940 	{
941 		/* Change working directory, so that %c macro and other cwd-sensitive things
942 		 * work as expected. */
943 		(void)vifm_chdir(flist_get_dir(curr_view));
944 	}
945 
946 	if(window_shows_dirlist(&lwin) && window_shows_dirlist(&rwin))
947 	{
948 		fview_cursor_redraw(curr_view);
949 		ui_views_update_titles();
950 	}
951 	else
952 	{
953 		stats_redraw_later();
954 	}
955 }
956 
957 void
swap_view_roles(void)958 swap_view_roles(void)
959 {
960 	view_t *const tmp = curr_view;
961 	curr_view = other_view;
962 	other_view = tmp;
963 }
964 
965 void
update_all_windows(void)966 update_all_windows(void)
967 {
968 	if(curr_stats.load_stage >= 2)
969 	{
970 		touch_all_windows();
971 		doupdate();
972 	}
973 }
974 
975 void
touch_all_windows(void)976 touch_all_windows(void)
977 {
978 	if(curr_stats.load_stage < 2)
979 	{
980 		return;
981 	}
982 
983 	if(!is_in_menu_like_mode())
984 	{
985 		update_window_lazy(tab_line);
986 
987 		if(curr_stats.number_of_windows == 1)
988 		{
989 			/* In one window view. */
990 			update_view(curr_view);
991 		}
992 		else
993 		{
994 			/* Two pane View. */
995 			update_window_lazy(mborder);
996 			update_window_lazy(top_line);
997 
998 			update_view(&lwin);
999 			update_view(&rwin);
1000 		}
1001 
1002 		if(cfg.side_borders_visible)
1003 		{
1004 			/* This needs to be updated before things like status bar and status line
1005 			 * to account for cases when they hide the top line. */
1006 			update_window_lazy(ltop_line1);
1007 			update_window_lazy(ltop_line2);
1008 			update_window_lazy(rtop_line1);
1009 			update_window_lazy(rtop_line2);
1010 
1011 			update_window_lazy(lborder);
1012 			update_window_lazy(rborder);
1013 		}
1014 
1015 		if(cfg.display_statusline)
1016 		{
1017 			update_window_lazy(stat_win);
1018 		}
1019 
1020 		if(ui_stat_job_bar_height() != 0)
1021 		{
1022 			update_window_lazy(job_bar);
1023 		}
1024 	}
1025 
1026 	update_window_lazy(ruler_win);
1027 	update_window_lazy(input_win);
1028 	update_window_lazy(status_bar);
1029 
1030 	if(vle_mode_is(MSG_MODE))
1031 	{
1032 		/* Location of message dialog can change, so it's better to let it position
1033 		 * itself or multiple copies appear on the screen. */
1034 		redraw_msg_dialog(1);
1035 	}
1036 }
1037 
1038 /* Updates all parts of file view. */
1039 static void
update_view(view_t * view)1040 update_view(view_t *view)
1041 {
1042 	update_window_lazy(view->title);
1043 
1044 	/* If view displays graphics, we don't want to update it or the image will be
1045 	 * lost. */
1046 	if(!view->explore_mode && !view->displays_graphics &&
1047 			!(curr_stats.preview.on && view == other_view))
1048 	{
1049 		update_window_lazy(view->win);
1050 	}
1051 }
1052 
1053 /* Tell curses to internally mark window as changed. */
1054 static void
update_window_lazy(WINDOW * win)1055 update_window_lazy(WINDOW *win)
1056 {
1057 	touchwin(win);
1058 	/*
1059 	 * redrawwin() shouldn't be needed.  But without it there is a
1060 	 * lot of flickering when redrawing the windows?
1061 	 */
1062 	redrawwin(win);
1063 	wnoutrefresh(win);
1064 }
1065 
1066 void
update_input_bar(const wchar_t * str)1067 update_input_bar(const wchar_t *str)
1068 {
1069 	if(!curr_stats.use_input_bar)
1070 		return;
1071 
1072 	if(wcslen(str) > (size_t)getmaxx(input_win))
1073 	{
1074 		str += wcslen(str) - getmaxx(input_win);
1075 	}
1076 
1077 	werase(input_win);
1078 	compat_waddwstr(input_win, str);
1079 	ui_refresh_win(input_win);
1080 }
1081 
1082 void
clear_num_window(void)1083 clear_num_window(void)
1084 {
1085 	if(curr_stats.use_input_bar)
1086 	{
1087 		werase(input_win);
1088 		ui_refresh_win(input_win);
1089 	}
1090 }
1091 
1092 void
show_progress(const char msg[],int period)1093 show_progress(const char msg[], int period)
1094 {
1095 	static char marks[] = { '|', '/', '-', '\\' };
1096 	static int mark = 0;
1097 	static int count = 1;
1098 	static int total = 0;
1099 
1100 	/* Do nothing if UI is not functional. */
1101 	if(curr_stats.load_stage < 1)
1102 	{
1103 		return;
1104 	}
1105 
1106 	/* Reset state. */
1107 	if(period == 0)
1108 	{
1109 		count = 1;
1110 		total = 0;
1111 		return;
1112 	}
1113 
1114 	/* Advance state. */
1115 	++count;
1116 	++total;
1117 
1118 	/* Skip intermediate updates to do not hammer UI with refreshes. */
1119 	if(abs(period) != 1 && count%abs(period) != 1)
1120 	{
1121 		return;
1122 	}
1123 	count = 1;
1124 
1125 	/* Assume that period equal to or less than one means that message already
1126 	 * contains counter (maybe along with total number) or doesn't need one. */
1127 	if(period <= 1)
1128 	{
1129 		ui_sb_quick_msgf("%s %c", msg, marks[mark]);
1130 	}
1131 	else
1132 	{
1133 		ui_sb_quick_msgf("%s %c %d", msg, marks[mark], total);
1134 	}
1135 
1136 	/* Pick next mark character. */
1137 	mark = (mark + 1) % sizeof(marks);
1138 }
1139 
1140 void
redraw_lists(void)1141 redraw_lists(void)
1142 {
1143 	if(curr_stats.load_stage == 0)
1144 	{
1145 		return;
1146 	}
1147 
1148 	redraw_current_view();
1149 	if(curr_stats.number_of_windows == 2)
1150 	{
1151 		if(curr_stats.preview.on)
1152 		{
1153 			qv_draw(curr_view);
1154 			refresh_view_win(other_view);
1155 		}
1156 		else if(!other_view->explore_mode)
1157 		{
1158 			fview_cursor_redraw(other_view);
1159 			draw_dir_list(other_view);
1160 			refresh_view_win(other_view);
1161 		}
1162 	}
1163 }
1164 
1165 void
update_attributes(void)1166 update_attributes(void)
1167 {
1168 	if(curr_stats.load_stage < 2)
1169 		return;
1170 
1171 	if(cfg.side_borders_visible)
1172 	{
1173 		ui_set_bg(lborder, &cfg.cs.color[BORDER_COLOR], cfg.cs.pair[BORDER_COLOR]);
1174 		werase(lborder);
1175 		ui_set_bg(rborder, &cfg.cs.color[BORDER_COLOR], cfg.cs.pair[BORDER_COLOR]);
1176 		werase(rborder);
1177 
1178 		ui_set_bg(ltop_line1, &cfg.cs.color[TOP_LINE_COLOR],
1179 				cfg.cs.pair[TOP_LINE_COLOR]);
1180 		werase(ltop_line1);
1181 
1182 		ui_set_bg(ltop_line2, &cfg.cs.color[TOP_LINE_COLOR],
1183 				cfg.cs.pair[TOP_LINE_COLOR]);
1184 		werase(ltop_line2);
1185 
1186 		ui_set_bg(top_line, &cfg.cs.color[TOP_LINE_COLOR],
1187 				cfg.cs.pair[TOP_LINE_COLOR]);
1188 		werase(top_line);
1189 
1190 		ui_set_bg(rtop_line1, &cfg.cs.color[TOP_LINE_COLOR],
1191 				cfg.cs.pair[TOP_LINE_COLOR]);
1192 		werase(rtop_line1);
1193 
1194 		ui_set_bg(rtop_line2, &cfg.cs.color[TOP_LINE_COLOR],
1195 				cfg.cs.pair[TOP_LINE_COLOR]);
1196 		werase(rtop_line2);
1197 	}
1198 
1199 	ui_set_bg(mborder, &cfg.cs.color[BORDER_COLOR], cfg.cs.pair[BORDER_COLOR]);
1200 	werase(mborder);
1201 
1202 	ui_set_bg(tab_line, &cfg.cs.color[TAB_LINE_COLOR],
1203 			cfg.cs.pair[TAB_LINE_COLOR]);
1204 	werase(tab_line);
1205 
1206 	ui_set_bg(stat_win, &cfg.cs.color[STATUS_LINE_COLOR],
1207 			cfg.cs.pair[STATUS_LINE_COLOR]);
1208 
1209 	ui_set_bg(job_bar, &cfg.cs.color[JOB_LINE_COLOR],
1210 			cfg.cs.pair[JOB_LINE_COLOR]);
1211 
1212 	ui_set_bg(menu_win, &cfg.cs.color[WIN_COLOR], cfg.cs.pair[WIN_COLOR]);
1213 	ui_set_bg(sort_win, &cfg.cs.color[WIN_COLOR], cfg.cs.pair[WIN_COLOR]);
1214 	ui_set_bg(change_win, &cfg.cs.color[WIN_COLOR], cfg.cs.pair[WIN_COLOR]);
1215 	ui_set_bg(error_win, &cfg.cs.color[WIN_COLOR], cfg.cs.pair[WIN_COLOR]);
1216 
1217 	ui_set_bg(status_bar, &cfg.cs.color[CMD_LINE_COLOR],
1218 			cfg.cs.pair[CMD_LINE_COLOR]);
1219 	ui_set_bg(ruler_win, &cfg.cs.color[CMD_LINE_COLOR],
1220 			cfg.cs.pair[CMD_LINE_COLOR]);
1221 	ui_set_bg(input_win, &cfg.cs.color[CMD_LINE_COLOR],
1222 			cfg.cs.pair[CMD_LINE_COLOR]);
1223 }
1224 
1225 void
ui_refresh_win(WINDOW * win)1226 ui_refresh_win(WINDOW *win)
1227 {
1228 	if(!stats_silenced_ui())
1229 	{
1230 		use_wrefresh(win);
1231 	}
1232 }
1233 
1234 void
wprint(WINDOW * win,const char str[])1235 wprint(WINDOW *win, const char str[])
1236 {
1237 #ifndef _WIN32
1238 	waddstr(win, str);
1239 #else
1240 	wchar_t *t = to_wide(str);
1241 	if(t == NULL)
1242 	{
1243 		show_error_msg("Memory Error", "Unable to allocate enough memory");
1244 		return;
1245 	}
1246 
1247 	compat_waddwstr(win, t);
1248 	free(t);
1249 #endif
1250 }
1251 
1252 void
wprinta(WINDOW * win,const char str[],const cchar_t * line_attrs,int attrs_xors)1253 wprinta(WINDOW *win, const char str[], const cchar_t *line_attrs,
1254 		int attrs_xors)
1255 {
1256 	attr_t attrs;
1257 	short color_pair;
1258 	wchar_t wch[getcchar(line_attrs, NULL, &attrs, &color_pair, NULL)];
1259 	getcchar(line_attrs, wch, &attrs, &color_pair, NULL);
1260 
1261 	col_attr_t col = { .attr = attrs ^ attrs_xors };
1262 	ui_set_attr(win, &col, color_pair);
1263 	wprint(win, str);
1264 	wnoutrefresh(win);
1265 }
1266 
1267 int
resize_for_menu_like(void)1268 resize_for_menu_like(void)
1269 {
1270 	int screen_x, screen_y;
1271 
1272 	ui_update_term_state();
1273 	if(curr_stats.term_state == TS_TOO_SMALL)
1274 	{
1275 		return 1;
1276 	}
1277 
1278 	update_term_size();
1279 	flushinp(); /* without it we will get strange character on input */
1280 	getmaxyx(stdscr, screen_y, screen_x);
1281 
1282 	werase(stdscr);
1283 	werase(status_bar);
1284 	werase(ruler_win);
1285 
1286 	wresize(menu_win, screen_y - 1, screen_x);
1287 
1288 	update_statusbar_layout();
1289 
1290 	ui_refresh_win(status_bar);
1291 	ui_refresh_win(ruler_win);
1292 	ui_refresh_win(input_win);
1293 
1294 	return 0;
1295 }
1296 
1297 void
ui_setup_for_menu_like(void)1298 ui_setup_for_menu_like(void)
1299 {
1300 	if(curr_stats.load_stage > 0)
1301 	{
1302 		scrollok(menu_win, FALSE);
1303 		curs_set(0);
1304 		werase(menu_win);
1305 		werase(status_bar);
1306 		werase(ruler_win);
1307 		ui_refresh_win(status_bar);
1308 		ui_refresh_win(ruler_win);
1309 	}
1310 }
1311 
1312 /* Query terminal size from the "device" and pass it to curses library. */
1313 static void
update_term_size(void)1314 update_term_size(void)
1315 {
1316 	if(curr_stats.load_stage < 1)
1317 	{
1318 		/* No terminal in tests. */
1319 		return;
1320 	}
1321 
1322 #ifndef _WIN32
1323 	struct winsize ws = { .ws_col = -1, .ws_row = -1 };
1324 
1325 	if(ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1)
1326 	{
1327 		LOG_SERROR_MSG(errno, "Failed to query terminal size.");
1328 		vifm_finish("Terminal error.");
1329 	}
1330 	/* Allow 0 as GNU Hurd returns this, is_term_resized() returns false then, but
1331 	 * everything works. */
1332 	if(ws.ws_row == (typeof(ws.ws_row))-1 || ws.ws_col == (typeof(ws.ws_col))-1)
1333 	{
1334 		LOG_INFO_MSG("ws.ws_row = %d; ws.ws_col = %d", ws.ws_row, ws.ws_col);
1335 		vifm_finish("Terminal is unable to run vifm.");
1336 	}
1337 
1338 	if(is_term_resized(ws.ws_row, ws.ws_col))
1339 	{
1340 		resizeterm(ws.ws_row, ws.ws_col);
1341 	}
1342 #elif defined(__PDCURSES__)
1343 	if(is_termresized())
1344 	{
1345 		/* This resizes vifm to window size. */
1346 		resize_term(0, 0);
1347 	}
1348 #endif
1349 }
1350 
1351 /* Re-layouts windows located on status bar (status bar itself, input and the
1352  * ruler). */
1353 static void
update_statusbar_layout(void)1354 update_statusbar_layout(void)
1355 {
1356 	int screen_x, screen_y;
1357 
1358 	int ruler_width;
1359 	int fields_pos;
1360 
1361 	getmaxyx(stdscr, screen_y, screen_x);
1362 
1363 	ruler_width = get_ruler_width(curr_view);
1364 	fields_pos = screen_x - (INPUT_WIN_WIDTH + ruler_width);
1365 
1366 	wresize(ruler_win, 1, ruler_width);
1367 	mvwin(ruler_win, screen_y - 1, fields_pos + INPUT_WIN_WIDTH);
1368 
1369 	wresize(input_win, 1, INPUT_WIN_WIDTH);
1370 	mvwin(input_win, screen_y - 1, fields_pos);
1371 
1372 	/* We might be in command-line mode in which case we shouldn't change visible
1373 	 * parts of the layout. */
1374 	if(are_statusbar_widgets_visible())
1375 	{
1376 		wresize(status_bar, 1, fields_pos);
1377 		mvwin(status_bar, screen_y - 1, 0);
1378 
1379 		wnoutrefresh(ruler_win);
1380 		wnoutrefresh(input_win);
1381 	}
1382 }
1383 
1384 /* Checks whether ruler and input bar are visible.  Returns non-zero if so, zero
1385  * is returned otherwise. */
1386 static int
are_statusbar_widgets_visible(void)1387 are_statusbar_widgets_visible(void)
1388 {
1389 	return !ui_sb_multiline() && !ui_sb_locked();
1390 }
1391 
1392 /* Gets "recommended" width for the ruler.  Returns the width. */
1393 static int
get_ruler_width(view_t * view)1394 get_ruler_width(view_t *view)
1395 {
1396 	char *expanded;
1397 	int len;
1398 	int list_pos;
1399 
1400 	/* Size must correspond to the "worst case" of the last list item. */
1401 	list_pos = view->list_pos;
1402 	view->list_pos = (view->list_rows == 0) ? 0 : (view->list_rows - 1);
1403 
1404 	expanded = expand_ruler_macros(view, cfg.ruler_format);
1405 	len = strlen(expanded);
1406 	free(expanded);
1407 
1408 	view->list_pos = list_pos;
1409 
1410 	return MAX(POS_WIN_MIN_WIDTH, len);
1411 }
1412 
1413 /* Expands view macros to be displayed on the ruler line according to the format
1414  * string.  Returns newly allocated string, which should be freed by the caller,
1415  * or NULL if there is not enough memory. */
1416 static char *
expand_ruler_macros(view_t * view,const char format[])1417 expand_ruler_macros(view_t *view, const char format[])
1418 {
1419 	return expand_view_macros(view, format, "-xlLS%[]");
1420 }
1421 
1422 void
refresh_view_win(view_t * view)1423 refresh_view_win(view_t *view)
1424 {
1425 	if(curr_stats.restart_in_progress)
1426 	{
1427 		return;
1428 	}
1429 
1430 	ui_refresh_win(view->win);
1431 	refresh_bottom_lines();
1432 }
1433 
1434 void
move_window(view_t * view,int horizontally,int first)1435 move_window(view_t *view, int horizontally, int first)
1436 {
1437 	const SPLIT split_type = horizontally ? HSPLIT : VSPLIT;
1438 	const view_t *const desired_view = first ? &lwin : &rwin;
1439 	split_view(split_type);
1440 	if(view != desired_view)
1441 	{
1442 		/* Switch two panes saving current windows as the active one (left/top or
1443 		 * right/bottom). */
1444 		switch_panes_content();
1445 		go_to_other_pane();
1446 		tabs_switch_panes();
1447 	}
1448 }
1449 
1450 void
switch_panes(void)1451 switch_panes(void)
1452 {
1453 	switch_panes_content();
1454 	modview_try_activate();
1455 }
1456 
1457 void
ui_view_pick(view_t * view,view_t ** old_curr,view_t ** old_other)1458 ui_view_pick(view_t *view, view_t **old_curr, view_t **old_other)
1459 {
1460 	*old_curr = curr_view;
1461 	*old_other = other_view;
1462 
1463 	curr_view = view;
1464 	other_view = (view == *old_curr) ? *old_other : *old_curr;
1465 	if(curr_view != *old_curr)
1466 	{
1467 		load_view_options(curr_view);
1468 	}
1469 }
1470 
1471 void
ui_view_unpick(view_t * view,view_t * old_curr,view_t * old_other)1472 ui_view_unpick(view_t *view, view_t *old_curr, view_t *old_other)
1473 {
1474 	if(curr_view != view)
1475 	{
1476 		/* Do nothing if view roles were switched from outside. */
1477 		return;
1478 	}
1479 
1480 	curr_view = old_curr;
1481 	other_view = old_other;
1482 	if(curr_view != view)
1483 	{
1484 		load_view_options(curr_view);
1485 	}
1486 }
1487 
1488 /* Switches panes content. */
1489 static void
switch_panes_content(void)1490 switch_panes_content(void)
1491 {
1492 	ui_swap_view_data(&lwin, &rwin);
1493 
1494 	flist_update_origins(&lwin, &rwin.curr_dir[0], &lwin.curr_dir[0]);
1495 	flist_update_origins(&rwin, &lwin.curr_dir[0], &rwin.curr_dir[0]);
1496 
1497 	modview_panes_swapped();
1498 
1499 	stats_redraw_later();
1500 }
1501 
1502 void
ui_swap_view_data(view_t * left,view_t * right)1503 ui_swap_view_data(view_t *left, view_t *right)
1504 {
1505 	view_t tmp_view;
1506 	WINDOW *tmp;
1507 	int t;
1508 
1509 	tmp = left->win;
1510 	left->win = right->win;
1511 	right->win = tmp;
1512 
1513 	t = left->window_rows;
1514 	left->window_rows = right->window_rows;
1515 	right->window_rows = t;
1516 
1517 	t = left->window_cols;
1518 	left->window_cols = right->window_cols;
1519 	right->window_cols = t;
1520 
1521 	tmp = left->title;
1522 	left->title = right->title;
1523 	right->title = tmp;
1524 
1525 	tmp_view = *left;
1526 	*left = *right;
1527 	*right = tmp_view;
1528 }
1529 
1530 void
go_to_other_pane(void)1531 go_to_other_pane(void)
1532 {
1533 	change_window();
1534 	modview_try_activate();
1535 }
1536 
1537 void
split_view(SPLIT orientation)1538 split_view(SPLIT orientation)
1539 {
1540 	if(curr_stats.number_of_windows == 2 && curr_stats.split == orientation)
1541 		return;
1542 
1543 	curr_stats.split = orientation;
1544 	curr_stats.number_of_windows = 2;
1545 
1546 	if(curr_stats.number_of_windows == 2 && curr_stats.splitter_pos > 0)
1547 	{
1548 		stats_set_splitter_ratio(curr_stats.splitter_ratio);
1549 	}
1550 
1551 	stats_redraw_later();
1552 }
1553 
1554 void
only(void)1555 only(void)
1556 {
1557 	if(curr_stats.number_of_windows != 1)
1558 	{
1559 		curr_stats.number_of_windows = 1;
1560 		update_screen(UT_REDRAW);
1561 	}
1562 }
1563 
1564 void
move_splitter(int by,int fact)1565 move_splitter(int by, int fact)
1566 {
1567 	int pos = curr_stats.splitter_pos;
1568 
1569 	/* Determine exact splitter position if it's centered at the moment. */
1570 	if(curr_stats.splitter_pos < 0)
1571 	{
1572 		if(curr_stats.split == HSPLIT)
1573 		{
1574 			pos = getmaxy(stdscr)/2 - 1;
1575 		}
1576 		else
1577 		{
1578 			pos = getmaxx(stdscr)/2 - 1 + getmaxx(stdscr)%2;
1579 		}
1580 	}
1581 
1582 	set_splitter(pos + fact*by);
1583 }
1584 
1585 void
ui_view_resize(view_t * view,int to)1586 ui_view_resize(view_t *view, int to)
1587 {
1588 	int pos;
1589 
1590 	if(curr_stats.split == HSPLIT)
1591 	{
1592 		const int height = get_working_area_height();
1593 		pos = (view == &lwin) ? (1 + to) : (height - to);
1594 	}
1595 	else
1596 	{
1597 		const int width = getmaxx(stdscr) - 1;
1598 		pos = (view == &lwin) ? to : (width - to);
1599 	}
1600 
1601 	set_splitter(pos);
1602 }
1603 
1604 /* Sets splitter position making sure it isn't negative value which has special
1605  * meaning. */
1606 static void
set_splitter(int pos)1607 set_splitter(int pos)
1608 {
1609 	stats_set_splitter_pos(pos < 0 ? 0 : pos);
1610 }
1611 
1612 void
format_entry_name(const dir_entry_t * entry,NameFormat fmt,size_t buf_len,char buf[])1613 format_entry_name(const dir_entry_t *entry, NameFormat fmt, size_t buf_len,
1614 		char buf[])
1615 {
1616 	const char *prefix, *suffix;
1617 	char tmp_buf[strlen(entry->name) + 1];
1618 	const char *name = entry->name;
1619 
1620 	if(fmt == NF_NONE)
1621 	{
1622 		copy_str(buf, buf_len, entry->name);
1623 		return;
1624 	}
1625 
1626 	if(fmt == NF_ROOT)
1627 	{
1628 		int root_len;
1629 		const char *ext_pos;
1630 
1631 		copy_str(tmp_buf, sizeof(tmp_buf), entry->name);
1632 		split_ext(tmp_buf, &root_len, &ext_pos);
1633 		name = tmp_buf;
1634 	}
1635 
1636 	ui_get_decors(entry, &prefix, &suffix);
1637 	snprintf(buf, buf_len, "%s%s%s", prefix,
1638 			(is_root_dir(entry->name) && suffix[0] == '/') ? "" : name, suffix);
1639 }
1640 
1641 void
ui_get_decors(const dir_entry_t * entry,const char ** prefix,const char ** suffix)1642 ui_get_decors(const dir_entry_t *entry, const char **prefix,
1643 		const char **suffix)
1644 {
1645 	/* The check of actual file type can be relatively slow in some cases, so make
1646 	 * sure we do it only when needed and at most once. */
1647 	FileType type = FT_UNK;
1648 
1649 	if(entry->name_dec_num == -1)
1650 	{
1651 		/* Find a match and cache the result. */
1652 
1653 		((dir_entry_t *)entry)->name_dec_num = 0;
1654 		if(cfg.name_dec_count != 0)
1655 		{
1656 			char full_path[PATH_MAX + 1];
1657 			int i;
1658 
1659 			get_full_path_of(entry, sizeof(full_path) - 1U, full_path);
1660 			type = ui_view_entry_target_type(entry);
1661 			if(type == FT_DIR)
1662 			{
1663 				strcat(full_path, "/");
1664 			}
1665 
1666 			for(i = 0; i < cfg.name_dec_count; ++i)
1667 			{
1668 				const file_dec_t *const file_dec = &cfg.name_decs[i];
1669 				if(matchers_match(file_dec->matchers, full_path))
1670 				{
1671 					((dir_entry_t *)entry)->name_dec_num = i + 1;
1672 					break;
1673 				}
1674 			}
1675 		}
1676 	}
1677 
1678 	if(entry->name_dec_num == 0)
1679 	{
1680 		type = (type == FT_UNK) ? ui_view_entry_target_type(entry) : type;
1681 		*prefix = cfg.type_decs[type][DECORATION_PREFIX];
1682 		*suffix = cfg.type_decs[type][DECORATION_SUFFIX];
1683 	}
1684 	else
1685 	{
1686 		assert(entry->name_dec_num - 1 >= 0 && "Wrong index.");
1687 		assert(entry->name_dec_num - 1 < cfg.name_dec_count && "Wrong index.");
1688 
1689 		*prefix = cfg.name_decs[entry->name_dec_num - 1].prefix;
1690 		*suffix = cfg.name_decs[entry->name_dec_num - 1].suffix;
1691 	}
1692 }
1693 
1694 void
ui_view_reset_decor_cache(const view_t * view)1695 ui_view_reset_decor_cache(const view_t *view)
1696 {
1697 	int i;
1698 
1699 	for(i = 0; i < view->list_rows; ++i)
1700 	{
1701 		view->dir_entry[i].name_dec_num = -1;
1702 	}
1703 
1704 	for(i = 0; i < view->left_column.entries.nentries; ++i)
1705 	{
1706 		view->left_column.entries.entries[i].name_dec_num = -1;
1707 	}
1708 
1709 	for(i = 0; i < view->right_column.entries.nentries; ++i)
1710 	{
1711 		view->right_column.entries.entries[i].name_dec_num = -1;
1712 	}
1713 }
1714 
1715 /* Gets real type of file view entry.  Returns type of entry, resolving symbolic
1716  * link if needed. */
1717 static FileType
ui_view_entry_target_type(const dir_entry_t * entry)1718 ui_view_entry_target_type(const dir_entry_t *entry)
1719 {
1720 	if(entry->type == FT_LINK)
1721 	{
1722 		char *const full_path = format_str("%s/%s", entry->origin, entry->name);
1723 		const FileType type = (get_symlink_type(full_path) != SLT_UNKNOWN)
1724 		                    ? FT_DIR
1725 		                    : FT_LINK;
1726 		free(full_path);
1727 		return type;
1728 	}
1729 
1730 	return entry->type;
1731 }
1732 
1733 void
checked_wmove(WINDOW * win,int y,int x)1734 checked_wmove(WINDOW *win, int y, int x)
1735 {
1736 	if(wmove(win, y, x) == ERR)
1737 	{
1738 		LOG_INFO_MSG("Error moving cursor on a window to (x=%d, y=%d).", x, y);
1739 	}
1740 }
1741 
1742 void
ui_display_too_small_term_msg(void)1743 ui_display_too_small_term_msg(void)
1744 {
1745 	touchwin(stdscr);
1746 	ui_refresh_win(stdscr);
1747 
1748 	mvwin(status_bar, 0, 0);
1749 	wresize(status_bar, getmaxy(stdscr), getmaxx(stdscr));
1750 	werase(status_bar);
1751 	waddstr(status_bar, "Terminal is too small for vifm");
1752 	touchwin(status_bar);
1753 	ui_refresh_win(status_bar);
1754 }
1755 
1756 void
ui_view_win_changed(view_t * view)1757 ui_view_win_changed(view_t *view)
1758 {
1759 	wnoutrefresh(view->win);
1760 	refresh_bottom_lines();
1761 }
1762 
1763 /* Makes sure that statusline and statusbar are drawn over view window after
1764  * view refresh. */
1765 static void
refresh_bottom_lines(void)1766 refresh_bottom_lines(void)
1767 {
1768 	/* Use getmaxy(...) instead of multiline_status_bar to handle command line
1769 	 * mode, which doesn't use this module to show multiline messages. */
1770 	if(cfg.display_statusline && getmaxy(status_bar) > 1)
1771 	{
1772 		touchwin(stat_win);
1773 		wnoutrefresh(stat_win);
1774 		touchwin(status_bar);
1775 		wnoutrefresh(status_bar);
1776 	}
1777 }
1778 
1779 void
ui_view_reset_selection_and_reload(view_t * view)1780 ui_view_reset_selection_and_reload(view_t *view)
1781 {
1782 	flist_sel_stash(view);
1783 	load_saving_pos(view);
1784 }
1785 
1786 void
ui_view_reset_search_highlight(view_t * view)1787 ui_view_reset_search_highlight(view_t *view)
1788 {
1789 	if(view->matches != 0)
1790 	{
1791 		view->matches = 0;
1792 		ui_view_schedule_redraw(view);
1793 	}
1794 }
1795 
1796 void
ui_views_reload_visible_filelists(void)1797 ui_views_reload_visible_filelists(void)
1798 {
1799 	if(curr_stats.preview.on)
1800 	{
1801 		load_saving_pos(curr_view);
1802 	}
1803 	else
1804 	{
1805 		ui_views_reload_filelists();
1806 	}
1807 }
1808 
1809 void
ui_views_reload_filelists(void)1810 ui_views_reload_filelists(void)
1811 {
1812 	load_saving_pos(curr_view);
1813 	load_saving_pos(other_view);
1814 }
1815 
1816 void
ui_views_update_titles(void)1817 ui_views_update_titles(void)
1818 {
1819 	ui_view_title_update(&lwin);
1820 	ui_view_title_update(&rwin);
1821 }
1822 
1823 void
ui_view_title_update(view_t * view)1824 ui_view_title_update(view_t *view)
1825 {
1826 	const int gen_view = vle_mode_is(VIEW_MODE) && !curr_view->explore_mode;
1827 	view_t *selected = gen_view ? other_view : curr_view;
1828 	path_func pf = cfg.shorten_title_paths ? &replace_home_part : &path_identity;
1829 	col_attr_t title_col;
1830 
1831 	if(curr_stats.load_stage < 2 || !ui_view_is_visible(view))
1832 	{
1833 		return;
1834 	}
1835 
1836 	if(view == selected && !stats_silenced_ui())
1837 	{
1838 		char *const term_title = format_view_title(view, pf);
1839 		term_title_update(term_title);
1840 		free(term_title);
1841 	}
1842 
1843 	title_col = fixup_titles_attributes(view, view == selected);
1844 
1845 	if(view_shows_tabline(view))
1846 	{
1847 		print_tabline(view->title, view, title_col, pf);
1848 	}
1849 	else
1850 	{
1851 		char *const title = format_view_title(view, pf);
1852 		print_view_title(view, view == selected, title);
1853 		wnoutrefresh(view->title);
1854 		free(title);
1855 	}
1856 
1857 	if(view == curr_view && get_tabline_height() > 0)
1858 	{
1859 		print_tabline(tab_line, view, cfg.cs.color[TAB_LINE_COLOR], pf);
1860 	}
1861 }
1862 
1863 /* Identity path function.  Returns its argument. */
1864 static char *
path_identity(const char path[])1865 path_identity(const char path[])
1866 {
1867 	return (char *)path;
1868 }
1869 
1870 /* Checks whether view displays list of pane tabs at the moment.  Returns
1871  * non-zero if so, otherwise zero is returned. */
1872 static int
view_shows_tabline(const view_t * view)1873 view_shows_tabline(const view_t *view)
1874 {
1875 	return cfg.pane_tabs
1876 	    && !(curr_stats.preview.on && view == other_view)
1877 	    && cfg.show_tab_line != STL_NEVER
1878 	    && !(cfg.show_tab_line == STL_MULTIPLE && tabs_count(view) == 1);
1879 }
1880 
1881 /* Retrieves height of the tab line in lines.  Returns the height. */
1882 static int
get_tabline_height(void)1883 get_tabline_height(void)
1884 {
1885 	if(cfg.pane_tabs || cfg.show_tab_line == STL_NEVER)
1886 	{
1887 		return 0;
1888 	}
1889 
1890 	return (cfg.show_tab_line == STL_MULTIPLE && tabs_count(curr_view) == 1)
1891 	     ? 0
1892 	     : 1;
1893 }
1894 
1895 /* Prints title of the tab on specified curses window. */
1896 static void
print_tabline(WINDOW * win,view_t * view,col_attr_t base_col,path_func pf)1897 print_tabline(WINDOW *win, view_t *view, col_attr_t base_col, path_func pf)
1898 {
1899 	int i;
1900 	tab_info_t tab_info;
1901 
1902 	const int max_width = (vifm_testing() ? cfg.columns : getmaxx(win));
1903 	int width_used = 0;
1904 	int avg_width, spare_width;
1905 
1906 	int min_widths[tabs_count(view)];
1907 
1908 	ui_set_bg(win, &base_col, -1);
1909 	werase(win);
1910 	checked_wmove(win, 0, 0);
1911 
1912 	compute_avg_width(&avg_width, &spare_width, min_widths, max_width, view, pf);
1913 
1914 	int tab_count = tabs_count(view);
1915 	int min_width = 0;
1916 	for(i = 0; i < tab_count; ++i)
1917 	{
1918 		min_width += min_widths[i];
1919 	}
1920 
1921 	int before_current = 1;
1922 
1923 	for(i = 0; tabs_get(view, i, &tab_info) && width_used < max_width; ++i)
1924 	{
1925 		int current = (tab_info.view == view);
1926 		if(current)
1927 		{
1928 			before_current = 0;
1929 		}
1930 		else if(before_current && min_width > max_width)
1931 		{
1932 			min_width -= min_widths[i];
1933 			spare_width = max_width - min_width;
1934 			continue;
1935 		}
1936 
1937 		tab_title_info_t title_info = make_tab_title_info(&tab_info, pf, i,
1938 				current);
1939 		cline_t prefix = format_tab_part(cfg.tab_prefix, &title_info);
1940 		cline_t title = make_tab_title(&title_info);
1941 		cline_t suffix = format_tab_part(cfg.tab_suffix, &title_info);
1942 		dispose_tab_title_info(&title_info);
1943 
1944 		const int width_needed = title.attrs_len;
1945 		const int extra_width = prefix.attrs_len + suffix.attrs_len;
1946 		int width = max_width;
1947 
1948 		col_attr_t col = base_col;
1949 		if(current)
1950 		{
1951 			cs_mix_colors(&col, &cfg.cs.color[TAB_LINE_SEL_COLOR]);
1952 		}
1953 
1954 		if(!current)
1955 		{
1956 			width = tab_info.last ? (max_width - width_used)
1957 			                      : MIN(avg_width, extra_width + width_needed);
1958 		}
1959 
1960 		if(width < width_needed + extra_width)
1961 		{
1962 			if(spare_width > 0)
1963 			{
1964 				width += 1;
1965 				--spare_width;
1966 			}
1967 
1968 			cline_left_ellipsis(&title, MAX(0, width - extra_width),
1969 					curr_stats.ellipsis);
1970 		}
1971 
1972 		ui_set_attr(win, &col, -1);
1973 
1974 		int real_width = prefix.attrs_len + title.attrs_len + suffix.attrs_len;
1975 
1976 		if(width < real_width && max_width - width_used >= real_width)
1977 		{
1978 			width = real_width;
1979 		}
1980 		if(width >= real_width)
1981 		{
1982 			cline_print(&prefix, win, &col);
1983 			cline_print(&title, win, &col);
1984 			cline_print(&suffix, win, &col);
1985 		}
1986 
1987 		width_used += real_width;
1988 
1989 		cline_dispose(&prefix);
1990 		cline_dispose(&title);
1991 		cline_dispose(&suffix);
1992 	}
1993 
1994 	wnoutrefresh(win);
1995 }
1996 
1997 /* Computes average width of tab tips as well as number of spare character
1998  * positions. */
1999 static void
compute_avg_width(int * avg_width,int * spare_width,int min_widths[],int max_width,view_t * view,path_func pf)2000 compute_avg_width(int *avg_width, int *spare_width, int min_widths[],
2001 		int max_width, view_t *view, path_func pf)
2002 {
2003 	int left = max_width;
2004 	int widths[tabs_count(view)];
2005 	int i;
2006 	tab_info_t tab_info;
2007 
2008 	*avg_width = 0;
2009 	*spare_width = 0;
2010 
2011 	for(i = 0; tabs_get(view, i, &tab_info); ++i)
2012 	{
2013 		int current = (tab_info.view == view);
2014 
2015 		tab_title_info_t title_info = make_tab_title_info(&tab_info, pf, i,
2016 				current);
2017 		cline_t prefix = format_tab_part(cfg.tab_prefix, &title_info);
2018 		cline_t title = make_tab_title(&title_info);
2019 		cline_t suffix = format_tab_part(cfg.tab_suffix, &title_info);
2020 		dispose_tab_title_info(&title_info);
2021 
2022 		widths[i] = prefix.attrs_len + title.attrs_len + suffix.attrs_len;
2023 		min_widths[i] = prefix.attrs_len + suffix.attrs_len;
2024 
2025 		cline_dispose(&prefix);
2026 		cline_dispose(&title);
2027 		cline_dispose(&suffix);
2028 
2029 		if(current)
2030 		{
2031 			min_widths[i] = MIN(max_width, widths[i]);
2032 
2033 			if(tabs_count(view) != 1)
2034 			{
2035 				int tab_count = tabs_count(view);
2036 
2037 				left = MAX(max_width - widths[i], 0);
2038 				*avg_width = left/(tab_count - 1);
2039 				*spare_width = left%(tab_count - 1);
2040 			}
2041 		}
2042 	}
2043 
2044 	int new_avg_width = *avg_width;
2045 	do
2046 	{
2047 		int well_used_width = 0;
2048 		int truncated_count = 0;
2049 		*avg_width = new_avg_width;
2050 		for(i = 0; tabs_get(view, i, &tab_info); ++i)
2051 		{
2052 			if(tab_info.view != view)
2053 			{
2054 				if(widths[i] <= *avg_width)
2055 				{
2056 					well_used_width += widths[i];
2057 				}
2058 				else
2059 				{
2060 					++truncated_count;
2061 				}
2062 			}
2063 		}
2064 		if(truncated_count == 0)
2065 		{
2066 			break;
2067 		}
2068 		new_avg_width = (left - well_used_width)/truncated_count;
2069 		*spare_width = (left - well_used_width)%truncated_count;
2070 	}
2071 	while(new_avg_width != *avg_width);
2072 }
2073 
2074 /* Gets title of the tab.  Returns colored line. */
2075 TSTATIC cline_t
make_tab_title(const tab_title_info_t * title_info)2076 make_tab_title(const tab_title_info_t *title_info)
2077 {
2078 	if(!is_null_or_empty(cfg.tab_label))
2079 	{
2080 		return format_tab_part(cfg.tab_label, title_info);
2081 	}
2082 
2083 	cline_t result = cline_make();
2084 
2085 	if(is_null_or_empty(title_info->name))
2086 	{
2087 		replace_string(&result.line, title_info->escaped_view_title);
2088 	}
2089 	else
2090 	{
2091 		replace_string(&result.line, title_info->name);
2092 	}
2093 
2094 	result.line_len = strlen(result.line);
2095 	cline_finish(&result);
2096 	return result;
2097 }
2098 
2099 /* Produces and stores information needed to format tab title.  Returns the
2100  * information. */
2101 TSTATIC tab_title_info_t
make_tab_title_info(const tab_info_t * tab_info,path_func pf,int tab_num,int current_tab)2102 make_tab_title_info(const tab_info_t *tab_info, path_func pf, int tab_num,
2103 		int current_tab)
2104 {
2105 	view_t *view = tab_info->view;
2106 
2107 	if(cfg.tail_tab_line_paths)
2108 	{
2109 		/* We can just do the replacement, because shortening home part doesn't make
2110 		 * sense for a single path entry. */
2111 		pf = &get_last_path_component;
2112 	}
2113 
2114 	char path[PATH_MAX + 1];
2115 	if(view->explore_mode)
2116 	{
2117 		get_current_full_path(view, sizeof(path), path);
2118 	}
2119 	else
2120 	{
2121 		copy_str(path, sizeof(path), flist_get_dir(view));
2122 	}
2123 
2124 	tab_title_info_t result = {
2125 		.current = current_tab,
2126 	};
2127 
2128 	const char *custom_title = "";
2129 	if(flist_custom_active(view))
2130 	{
2131 		custom_title = view->custom.title;
2132 		result.tree = cv_tree(view->custom.type);
2133 	}
2134 
2135 	result.name = escape_unreadable(tab_info->name == NULL ? "" : tab_info->name);
2136 	result.escaped_path = escape_unreadable(path);
2137 	result.cv_title = escape_unreadable(custom_title);
2138 
2139 	char *view_title = format_view_title(view, pf);
2140 	result.escaped_view_title = escape_unreadable(view_title);
2141 	free(view_title);
2142 
2143 	result.num = format_str("%d", tab_num + 1);
2144 
2145 	return result;
2146 }
2147 
2148 /* Frees information used to format tab title. */
2149 TSTATIC void
dispose_tab_title_info(tab_title_info_t * title_info)2150 dispose_tab_title_info(tab_title_info_t *title_info)
2151 {
2152 	free(title_info->num);
2153 	free(title_info->escaped_view_title);
2154 	free(title_info->cv_title);
2155 	free(title_info->escaped_path);
2156 	free(title_info->name);
2157 }
2158 
2159 /* Formats part of a tab label.  Returns colored line. */
2160 static cline_t
format_tab_part(const char fmt[],const tab_title_info_t * title_info)2161 format_tab_part(const char fmt[], const tab_title_info_t *title_info)
2162 {
2163 	custom_macro_t macros[] = {
2164 		{ .letter = 'n', .value = title_info->name },
2165 		{ .letter = 'N', .value = title_info->num },
2166 		{ .letter = 't', .value = title_info->escaped_view_title },
2167 		{ .letter = 'p', .value = title_info->escaped_path,
2168 			.expand_mods = 1, .parent = "/" },
2169 		{ .letter = 'c', .value = title_info->cv_title },
2170 
2171 		{ .letter = 'C', .value = (title_info->current ? "*" : ""),
2172 		  .flag = 1},
2173 		{ .letter = 'T', .value = (title_info->tree ? "*" : ""),
2174 			.flag = 1 },
2175 	};
2176 
2177 	cline_t title = ma_expand_colored_custom(fmt, ARRAY_LEN(macros), macros,
2178 			MA_OPT);
2179 
2180 	return title;
2181 }
2182 
2183 /* Formats title for the view.  The pf function will be applied to full paths.
2184  * Returns newly allocated string, which should be freed by the caller, or NULL
2185  * if there is not enough memory. */
2186 static char *
format_view_title(const view_t * view,path_func pf)2187 format_view_title(const view_t *view, path_func pf)
2188 {
2189 	char *unescaped_title;
2190 
2191 	if(view->explore_mode)
2192 	{
2193 		char full_path[PATH_MAX + 1];
2194 		get_current_full_path(view, sizeof(full_path), full_path);
2195 		unescaped_title = strdup(pf(full_path));
2196 	}
2197 	else if(curr_stats.preview.on && view == other_view)
2198 	{
2199 		const char *const viewer = modview_detached_get_viewer();
2200 		if(viewer != NULL)
2201 		{
2202 			unescaped_title = format_str("Command: %s", viewer);
2203 		}
2204 		else
2205 		{
2206 			unescaped_title = format_str("File: %s",
2207 					get_current_file_name(curr_view));
2208 		}
2209 	}
2210 	else if(flist_custom_active(view))
2211 	{
2212 		const char *tree_mark = (cv_tree(view->custom.type) ? "[tree]" : "");
2213 		const char *path = pf(view->custom.orig_dir);
2214 		if(view->custom.title[0] == '\0')
2215 		{
2216 			unescaped_title = format_str("%s @ %s", tree_mark, path);
2217 		}
2218 		else
2219 		{
2220 			unescaped_title = format_str("%s[%s] @ %s", tree_mark, view->custom.title,
2221 					path);
2222 		}
2223 	}
2224 	else
2225 	{
2226 		unescaped_title = strdup(pf(view->curr_dir));
2227 	}
2228 
2229 	char *escaped_title = escape_unreadable(unescaped_title);
2230 	free(unescaped_title);
2231 	return escaped_title;
2232 }
2233 
2234 /* Prints view title (which can be changed for printing).  Takes care of setting
2235  * correct attributes. */
2236 static void
print_view_title(const view_t * view,int active_view,char title[])2237 print_view_title(const view_t *view, int active_view, char title[])
2238 {
2239 	char *ellipsis;
2240 
2241 	const size_t title_width = getmaxx(view->title);
2242 	if(title_width == (size_t)-1)
2243 	{
2244 		return;
2245 	}
2246 
2247 	werase(view->title);
2248 
2249 	ellipsis = active_view
2250 	         ? left_ellipsis(title, title_width, curr_stats.ellipsis)
2251 	         : right_ellipsis(title, title_width, curr_stats.ellipsis);
2252 
2253 	wprint(view->title, ellipsis);
2254 	free(ellipsis);
2255 }
2256 
2257 /* Updates attributes for view titles and top line.  Returns base color used for
2258  * the title. */
2259 static col_attr_t
fixup_titles_attributes(const view_t * view,int active_view)2260 fixup_titles_attributes(const view_t *view, int active_view)
2261 {
2262 	col_attr_t col = cfg.cs.color[TOP_LINE_COLOR];
2263 
2264 	if(view->title == NULL)
2265 	{
2266 		/* Workaround for tests. */
2267 		return col;
2268 	}
2269 
2270 	if(active_view)
2271 	{
2272 		cs_mix_colors(&col, &cfg.cs.color[TOP_LINE_SEL_COLOR]);
2273 
2274 		ui_set_bg(view->title, &col, -1);
2275 		ui_set_attr(view->title, &col, -1);
2276 	}
2277 	else
2278 	{
2279 		ui_set_bg(view->title, &col, cfg.cs.pair[TOP_LINE_COLOR]);
2280 		ui_set_attr(view->title, &col, cfg.cs.pair[TOP_LINE_COLOR]);
2281 
2282 		ui_set_bg(top_line, &col, cfg.cs.pair[TOP_LINE_COLOR]);
2283 		werase(top_line);
2284 	}
2285 
2286 	return col;
2287 }
2288 
2289 int
ui_view_sort_list_contains(const signed char sort[SK_COUNT],char key)2290 ui_view_sort_list_contains(const signed char sort[SK_COUNT], char key)
2291 {
2292 	int i = -1;
2293 	while(++i < SK_COUNT)
2294 	{
2295 		const int sort_key = abs(sort[i]);
2296 		if(sort_key > SK_LAST)
2297 		{
2298 			return 0;
2299 		}
2300 		else if(sort_key == key)
2301 		{
2302 			return 1;
2303 		}
2304 	}
2305 	return 0;
2306 }
2307 
2308 void
ui_view_sort_list_ensure_well_formed(view_t * view,signed char sort_keys[])2309 ui_view_sort_list_ensure_well_formed(view_t *view, signed char sort_keys[])
2310 {
2311 	int found_name_key = 0;
2312 	int i = -1;
2313 
2314 	while(++i < SK_COUNT)
2315 	{
2316 		const int sort_key = abs(sort_keys[i]);
2317 		if(sort_key > SK_LAST)
2318 		{
2319 			break;
2320 		}
2321 		else if(sort_key == SK_BY_NAME || sort_key == SK_BY_INAME)
2322 		{
2323 			found_name_key = 1;
2324 		}
2325 	}
2326 
2327 	if(!found_name_key && i < SK_COUNT &&
2328 			(!flist_custom_active(view) ||
2329 			 (sort_keys == view->sort && !ui_view_unsorted(view))))
2330 	{
2331 		sort_keys[i++] = SK_DEFAULT;
2332 	}
2333 
2334 	if(i < SK_COUNT)
2335 	{
2336 		memset(&sort_keys[i], SK_NONE, SK_COUNT - i);
2337 	}
2338 }
2339 
2340 signed char *
ui_view_sort_list_get(const view_t * view,const signed char sort[])2341 ui_view_sort_list_get(const view_t *view, const signed char sort[])
2342 {
2343 	return (flist_custom_active(view) && ui_view_unsorted(view))
2344 	     ? (signed char *)view->custom.sort
2345 	     : (signed char *)sort;
2346 }
2347 
2348 int
ui_view_displays_numbers(const view_t * view)2349 ui_view_displays_numbers(const view_t *view)
2350 {
2351 	return view->num_type != NT_NONE && ui_view_displays_columns(view);
2352 }
2353 
2354 int
ui_view_is_visible(const view_t * view)2355 ui_view_is_visible(const view_t *view)
2356 {
2357 	return curr_stats.number_of_windows == 2 || curr_view == view;
2358 }
2359 
2360 int
ui_view_displays_columns(const view_t * view)2361 ui_view_displays_columns(const view_t *view)
2362 {
2363 	return !view->ls_view
2364 	    || is_in_miller_view(view)
2365 	    || is_forced_list_mode(view);
2366 }
2367 
2368 int
ui_view_available_width(const view_t * view)2369 ui_view_available_width(const view_t *view)
2370 {
2371 	const int correction = cfg.extra_padding ? -2 : 0;
2372 	return view->window_cols + correction
2373 	     - ui_view_left_reserved(view) - ui_view_right_reserved(view);
2374 }
2375 
2376 int
ui_view_left_reserved(const view_t * view)2377 ui_view_left_reserved(const view_t *view)
2378 {
2379 	const int total = view->miller_ratios[0] + view->miller_ratios[1]
2380 	                + view->miller_ratios[2];
2381 	return is_in_miller_view(view)
2382 	     ? (view->window_cols*view->miller_ratios[0])/total : 0;
2383 }
2384 
2385 int
ui_view_right_reserved(const view_t * view)2386 ui_view_right_reserved(const view_t *view)
2387 {
2388 	dir_entry_t *const entry = get_current_entry(view);
2389 	const int total = view->miller_ratios[0] + view->miller_ratios[1]
2390 	                + view->miller_ratios[2];
2391 	return is_in_miller_view(view)
2392 	    && !is_parent_dir(entry->name)
2393 	    && (fentry_is_dir(entry) || view->miller_preview_files)
2394 	     ? (view->window_cols*view->miller_ratios[2])/total
2395 	     : 0;
2396 }
2397 
2398 /* Whether miller columns should be displayed.  Returns non-zero if so,
2399  * otherwise zero is returned. */
2400 static int
is_in_miller_view(const view_t * view)2401 is_in_miller_view(const view_t *view)
2402 {
2403 	return view->miller_view
2404 	    && !flist_custom_active(view);
2405 }
2406 
2407 /* Whether view must display straight single-column file list.  Returns non-zero
2408  * if so, otherwise zero is returned. */
2409 static int
is_forced_list_mode(const view_t * view)2410 is_forced_list_mode(const view_t *view)
2411 {
2412 	return flist_custom_active(view)
2413 	    && (cv_tree(view->custom.type) || cv_compare(view->custom.type));
2414 }
2415 
2416 int
ui_qv_left(const view_t * view)2417 ui_qv_left(const view_t *view)
2418 {
2419 	const int with_margin = (!curr_stats.preview.clearing && cfg.extra_padding);
2420 	return with_margin ? 1 : 0;
2421 }
2422 
2423 int
ui_qv_top(const view_t * view)2424 ui_qv_top(const view_t *view)
2425 {
2426 	const int with_margin = (!curr_stats.preview.clearing && cfg.extra_padding);
2427 	return with_margin ? 1 : 0;
2428 }
2429 
2430 int
ui_qv_height(const view_t * view)2431 ui_qv_height(const view_t *view)
2432 {
2433 	const int with_margin = (!curr_stats.preview.clearing && cfg.extra_padding);
2434 	return with_margin ? view->window_rows - 2 : view->window_rows;
2435 }
2436 
2437 int
ui_qv_width(const view_t * view)2438 ui_qv_width(const view_t *view)
2439 {
2440 	const int with_margin = (!curr_stats.preview.clearing && cfg.extra_padding);
2441 	return with_margin ? view->window_cols - 2 : view->window_cols;
2442 }
2443 
2444 void
ui_qv_cleanup_if_needed(void)2445 ui_qv_cleanup_if_needed(void)
2446 {
2447 	if(curr_stats.preview.on && curr_stats.preview.cleanup_cmd != NULL)
2448 	{
2449 		qv_cleanup(other_view, curr_stats.preview.cleanup_cmd);
2450 	}
2451 }
2452 
2453 void
ui_invalidate_cs(const col_scheme_t * cs)2454 ui_invalidate_cs(const col_scheme_t *cs)
2455 {
2456 	int i;
2457 	tab_info_t tab_info;
2458 	for(i = 0; tabs_enum_all(i, &tab_info); ++i)
2459 	{
2460 		if(ui_view_get_cs(tab_info.view) == cs)
2461 		{
2462 			fview_reset_cs(tab_info.view);
2463 		}
2464 	}
2465 }
2466 
2467 const col_scheme_t *
ui_view_get_cs(const view_t * view)2468 ui_view_get_cs(const view_t *view)
2469 {
2470 	return view->local_cs ? &view->cs : &cfg.cs;
2471 }
2472 
2473 void
ui_view_erase(view_t * view,int use_global_cs)2474 ui_view_erase(view_t *view, int use_global_cs)
2475 {
2476 	const col_scheme_t *cs = (use_global_cs ? &cfg.cs : ui_view_get_cs(view));
2477 	col_attr_t col = ui_get_win_color(view, cs);
2478 	ui_set_bg(view->win, &col, -1);
2479 	werase(view->win);
2480 }
2481 
2482 col_attr_t
ui_get_win_color(const view_t * view,const col_scheme_t * cs)2483 ui_get_win_color(const view_t *view, const col_scheme_t *cs)
2484 {
2485 	col_attr_t col = cs->color[WIN_COLOR];
2486 	if(view == other_view && window_shows_dirlist(view))
2487 	{
2488 		cs_mix_colors(&col, &cs->color[OTHER_WIN_COLOR]);
2489 	}
2490 	return col;
2491 }
2492 
2493 int
ui_view_unsorted(const view_t * view)2494 ui_view_unsorted(const view_t *view)
2495 {
2496 	return cv_unsorted(view->custom.type);
2497 }
2498 
2499 void
ui_shutdown(void)2500 ui_shutdown(void)
2501 {
2502 	if(curr_stats.load_stage >= 0 && !vifm_testing() && !isendwin())
2503 	{
2504 		ui_qv_cleanup_if_needed();
2505 		modview_hide_graphics();
2506 		def_prog_mode();
2507 		endwin();
2508 	}
2509 }
2510 
2511 void
ui_pause(void)2512 ui_pause(void)
2513 {
2514 	/* Show previous screen state. */
2515 	ui_shutdown();
2516 	/* Yet restore program mode to read input without waiting for Enter. */
2517 	reset_prog_mode();
2518 	/* Refresh the window, because otherwise curses redraws the screen on call to
2519 	 * `compat_wget_wch()` (why does it do this?). */
2520 	wnoutrefresh(inf_delay_window);
2521 
2522 	/* Ignore window resize. */
2523 	wint_t pressed;
2524 	do
2525 	{
2526 		/* Nothing. */
2527 	}
2528 	while(compat_wget_wch(inf_delay_window, &pressed) != ERR &&
2529 			pressed == KEY_RESIZE);
2530 
2531 	/* Redraw UI to account for all things including graphical preview. */
2532 	stats_redraw_later();
2533 }
2534 
2535 void
ui_set_bg(WINDOW * win,const col_attr_t * col,int pair)2536 ui_set_bg(WINDOW *win, const col_attr_t *col, int pair)
2537 {
2538 	if(curr_stats.load_stage < 1)
2539 	{
2540 		return;
2541 	}
2542 
2543 	if(pair < 0)
2544 	{
2545 		pair = colmgr_get_pair(col->fg, col->bg);
2546 	}
2547 
2548 	cchar_t bg;
2549 	setcchar(&bg, L" ", col->attr, pair, NULL);
2550 	wbkgrndset(win, &bg);
2551 }
2552 
2553 void
ui_set_attr(WINDOW * win,const col_attr_t * col,int pair)2554 ui_set_attr(WINDOW *win, const col_attr_t *col, int pair)
2555 {
2556 	/* win can be NULL in tests. */
2557 	if(curr_stats.load_stage < 1 || win == NULL)
2558 	{
2559 		return;
2560 	}
2561 
2562 	if(pair < 0)
2563 	{
2564 		pair = colmgr_get_pair(col->fg, col->bg);
2565 	}
2566 
2567 	/* Compiler complains about unused result of comma operator, because
2568 	 * wattr_set() is a macro and it uses comma to evaluate multiple expresions.
2569 	 * So cast result to void.*/
2570 	(void)wattr_set(win, col->attr, pair, NULL);
2571 }
2572 
2573 void
ui_drop_attr(WINDOW * win)2574 ui_drop_attr(WINDOW *win)
2575 {
2576 	if(curr_stats.load_stage >= 1)
2577 	{
2578 		wattrset(win, 0);
2579 	}
2580 }
2581 
2582 void
ui_pass_through(const strlist_t * lines,WINDOW * win,int x,int y)2583 ui_pass_through(const strlist_t *lines, WINDOW *win, int x, int y)
2584 {
2585 	/* Position hardware cursor on the screen. */
2586 	checked_wmove(win, y, x);
2587 	use_wrefresh(win);
2588 
2589 	int i;
2590 	for(i = 0; i < lines->nitems; ++i)
2591 	{
2592 		puts(lines->items[i]);
2593 	}
2594 
2595 	/* Make curses synchronize its idea about where cursor is with the terminal
2596 	 * state. */
2597 	touchwin(status_bar);
2598 	checked_wmove(status_bar, 0, 0);
2599 	use_wrefresh(status_bar);
2600 }
2601 
2602 void
ui_view_schedule_redraw(view_t * view)2603 ui_view_schedule_redraw(view_t *view)
2604 {
2605 	pthread_mutex_lock(view->timestamps_mutex);
2606 	view->need_redraw = 1;
2607 	pthread_mutex_unlock(view->timestamps_mutex);
2608 }
2609 
2610 void
ui_view_schedule_reload(view_t * view)2611 ui_view_schedule_reload(view_t *view)
2612 {
2613 	pthread_mutex_lock(view->timestamps_mutex);
2614 	view->need_reload = 1;
2615 	pthread_mutex_unlock(view->timestamps_mutex);
2616 }
2617 
2618 void
ui_view_redrawn(view_t * view)2619 ui_view_redrawn(view_t *view)
2620 {
2621 	pthread_mutex_lock(view->timestamps_mutex);
2622 	view->need_redraw = 0;
2623 	pthread_mutex_unlock(view->timestamps_mutex);
2624 }
2625 
2626 UiUpdateEvent
ui_view_query_scheduled_event(view_t * view)2627 ui_view_query_scheduled_event(view_t *view)
2628 {
2629 	UiUpdateEvent event;
2630 
2631 	pthread_mutex_lock(view->timestamps_mutex);
2632 
2633 	if(view->need_reload)
2634 	{
2635 		event = UUE_RELOAD;
2636 	}
2637 	else if(view->need_redraw)
2638 	{
2639 		event = UUE_REDRAW;
2640 	}
2641 	else
2642 	{
2643 		event = UUE_NONE;
2644 	}
2645 
2646 	view->need_redraw = 0;
2647 	view->need_reload = 0;
2648 
2649 	pthread_mutex_unlock(view->timestamps_mutex);
2650 
2651 	return event;
2652 }
2653 
2654 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
2655 /* vim: set cinoptions+=t0 filetype=c : */
2656