/* vifm
 * Copyright (C) 2001 Ken Steen.
 * Copyright (C) 2011 xaizek.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

/* Save wrefresh identifier behind the macro before it's poisoned in the
 * header. */
#define use_wrefresh wrefresh

#include "ui.h"

#include <curses.h> /* mvwin() werase() */

#ifndef _WIN32
#include <sys/ioctl.h>
#include <termios.h> /* struct winsize */
#endif
#include <unistd.h>

#include <assert.h> /* assert() */
#include <ctype.h> /* isdigit() */
#include <errno.h> /* errno */
#include <stddef.h> /* NULL size_t wchar_t */
#include <stdint.h> /* uint64_t */
#include <stdlib.h> /* abs() free() */
#include <stdio.h> /* snprintf() vsnprintf() */
#include <string.h> /* memset() strcat() strcmp() strcpy() strdup() strlen() */
#include <wchar.h> /* wint_t wcslen() */

#include "../cfg/config.h"
#include "../cfg/info.h"
#include "../compat/curses.h"
#include "../compat/fs_limits.h"
#include "../compat/pthread.h"
#include "../engine/mode.h"
#include "../int/term_title.h"
#include "../lua/vlua.h"
#include "../modes/dialogs/msg_dialog.h"
#include "../modes/menu.h"
#include "../modes/modes.h"
#include "../modes/view.h"
#include "../modes/wk.h"
#include "../utils/darray.h"
#include "../utils/log.h"
#include "../utils/macros.h"
#include "../utils/matchers.h"
#include "../utils/path.h"
#include "../utils/str.h"
#include "../utils/string_array.h"
#include "../utils/utf8.h"
#include "../utils/utils.h"
#include "../compare.h"
#include "../event_loop.h"
#include "../filelist.h"
#include "../flist_sel.h"
#include "../macros.h"
#include "../opt_handlers.h"
#include "../sort.h"
#include "../status.h"
#include "../vifm.h"
#include "private/statusline.h"
#include "cancellation.h"
#include "color_manager.h"
#include "color_scheme.h"
#include "colored_line.h"
#include "colors.h"
#include "fileview.h"
#include "quickview.h"
#include "statusbar.h"
#include "statusline.h"
#include "tabs.h"

/* List of formatted tab labels with some extra information. */
typedef struct
{
	cline_t *labels; /* List of labels (might miss some tabs). */
	int count;       /* Number of labels. */
	int skipped;     /* Number of leading tabs skipped due to lack of space. */
	int current;     /* Index of the current tab (does not take skipped count into
	                    account). */
}
tab_line_info_t;

/* Information for formatting tab title. */
typedef struct
{
	char *name;               /* Tab name. */
	char *num;                /* Tab number. */
	char *escaped_view_title; /* Auto-formatted title. */
	char *escaped_path;       /* Current path (could be a file path). */
	char *cv_title;           /* Prefix of custom view. */
	int tree;                 /* Whether in tree mode. */
	int current;              /* Whether it's a current tab. */
}
tab_title_info_t;

/* Type of path transformation function for format_view_title(). */
typedef char * (*path_func)(const char[]);

/* Window configured to do not wait for input.  Never appears on the screen,
 * sometimes used for reading input. */
static WINDOW *no_delay_window;
/* Window configured to wait for input indefinitely.  Never appears on the
 * screen, sometimes used for reading input. */
static WINDOW *inf_delay_window;

/* Pieces of top line for the left/top view. */
static WINDOW *ltop_line1; /* On top of left border. */
static WINDOW *ltop_line2; /* Middle of right border in horizontal view. */
/* On top of middle border. */
static WINDOW *mtop_line;
/* Pieces of top line for the right/bottom view. */
static WINDOW *rtop_line1; /* On top of right border. */
static WINDOW *rtop_line2; /* Middle of right border in horizontal view. */

unsigned int ui_next_view_id = 3;

view_t lwin = { .id = 1 };
view_t rwin = { .id = 2 };

view_t *other_view;
view_t *curr_view;

WINDOW *status_bar;
WINDOW *stat_win;
WINDOW *job_bar;
WINDOW *ruler_win;
WINDOW *input_win;
WINDOW *menu_win;
WINDOW *sort_win;
WINDOW *change_win;
WINDOW *error_win;
WINDOW *tab_line;

static WINDOW *lborder;
static WINDOW *mborder;
static WINDOW *rborder;

static int init_pair_wrapper(int pair, int fg, int bg);
static int pair_content_wrapper(int pair, int *fg, int *bg);
static int pair_in_use(int pair);
static void pair_moved(int from, int to);
static void create_windows(void);
static void update_geometry(void);
static int update_start(UpdateType update_kind);
static void update_finish(void);
static void adjust_splitter(int screen_w, int screen_h);
static int get_working_area_height(void);
static void clear_border(WINDOW *border);
static int middle_border_is_visible(void);
static void update_views(int reload);
static void reload_lists(void);
static void reload_list(view_t *view);
static void update_view(view_t *view);
static void update_window_lazy(WINDOW *win);
static void update_term_size(void);
static void update_statusbar_layout(void);
static int are_statusbar_widgets_visible(void);
static int get_ruler_width(view_t *view);
static char * expand_ruler_macros(view_t *view, const char format[]);
static int locate_view(unsigned int id, int *tab_idx, view_t **side);
static void switch_panes_content(void);
static void set_splitter(int pos);
static FileType ui_view_entry_target_type(const dir_entry_t *entry);
static void refresh_bottom_lines(void);
static char * path_identity(const char path[]);
static int view_shows_tabline(const view_t *view);
static int get_tabline_height(void);
static void print_tabline(WINDOW *win, view_t *view, col_attr_t base_col,
		path_func pf);
static tab_line_info_t format_tab_labels(view_t *view, int max_width,
		path_func pf);
static void compute_avg_width(int *avg_width, int *spare_width,
		int min_widths[], int max_width, view_t *view, path_func pf);
TSTATIC cline_t make_tab_title(const tab_title_info_t *title_info);
TSTATIC tab_title_info_t make_tab_title_info(const tab_info_t *tab_info,
		path_func pf, int tab_num, int current_tab);
TSTATIC void dispose_tab_title_info(tab_title_info_t *title_info);
static cline_t format_tab_part(const char fmt[],
		const tab_title_info_t *title_info);
static char * format_view_title(const view_t *view, path_func pf,
		int consider_qv);
static void print_view_title(const view_t *view, int active_view, char title[]);
static col_attr_t fixup_titles_attributes(const view_t *view, int active_view);
static int is_in_miller_view(const view_t *view);
static int is_forced_list_mode(const view_t *view);

/* List of macros that are expanded in the ruler. */
static const char RULER_MACROS[] = "-xlLPS%[]";

void
ui_ruler_update(view_t *view, int lazy_redraw)
{
	char *expanded;

	if(!are_statusbar_widgets_visible())
	{
		/* Do nothing, especially don't update layout because it might be a custom
		 * layout at the moment. */
		return;
	}

	update_statusbar_layout();

	expanded = expand_ruler_macros(view, cfg.ruler_format);
	expanded = break_in_two(expanded, getmaxx(ruler_win), "%=");

	ui_ruler_set(expanded);
	if(!lazy_redraw)
	{
		ui_refresh_win(ruler_win);
	}

	free(expanded);
}

void
ui_ruler_set(const char val[])
{
	const int x = getmaxx(ruler_win) - strlen(val);

	werase(ruler_win);
	mvwaddstr(ruler_win, 0, MAX(x, 0), val);
	wnoutrefresh(ruler_win);
}

int
setup_ncurses_interface(void)
{
	int screen_x, screen_y;

	initscr();
	noecho();
	nonl();
	raw();

	ui_set_cursor(/*visibility=*/0);

	getmaxyx(stdscr, screen_y, screen_x);
	/* Screen is too small to be useful. */
	if(screen_y < MIN_TERM_HEIGHT || screen_x < MIN_TERM_WIDTH)
	{
		vifm_finish("Terminal is too small to run vifm.");
	}

	if(!has_colors())
	{
		vifm_finish("Vifm requires a console that can support color.");
	}

	start_color();
	use_default_colors();

	const colmgr_conf_t colmgr_conf = {
		.max_color_pairs = COLOR_PAIRS,
		.max_colors = COLORS,
		.init_pair = &init_pair_wrapper,
		.pair_content = &pair_content_wrapper,
		.pair_in_use = &pair_in_use,
		.pair_moved = &pair_moved,
	};
	colmgr_init(&colmgr_conf);

	cs_load_defaults();

	create_windows();

	cfg.tab_stop = TABSIZE;

#ifdef ENABLE_EXTENDED_KEYS
	keypad(status_bar, TRUE);
#endif /* ENABLE_EXTENDED_KEYS */

#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20081102
#ifdef HAVE_SET_ESCDELAY_FUNC
	/* Use ncurses specific function to make delay after pressing escape key
	 * unnoticeable.  Used to be zero, but in some corner cases multiple bytes
	 * composing a functional key code might be handled to the application with a
	 * delay. */
	set_escdelay(5);
#endif
#endif

#if defined(NCURSES_VERSION) && defined(ENABLE_EXTENDED_KEYS)
	/* Disable 'unsupported' extended keys so that esc-codes will be
	 * received instead of unnamed extended keycode values (> KEY_MAX).
	 * These keys can then be mapped with esc-codes in vifmrc.
	 * An example could be <c-down>:
	 *     qnoremap <esc>[1;5B j
	 * as without this change <c-down> can return a keycode value of 531
	 * (terminfo name kDN5), which is larger than KEY_MAX and has no
	 * pre-defined curses key name. */
	int i;
	for(i = KEY_MAX; i < 2000; ++i)
	{
		keyok(i, FALSE);
	}
#endif

#ifdef HAVE_EXTENDED_COLORS
	curr_stats.direct_color = (COLORS == 0x1000000);
#endif

	ui_resize_all();

	return 1;
}

/* Calls init_pair() from libcurses. */
static int
init_pair_wrapper(int pair, int fg, int bg)
{
#ifdef HAVE_EXTENDED_COLORS
	return init_extended_pair(pair, fg, bg);
#else
	return init_pair(pair, fg, bg);
#endif
}

/* Calls pair_content() from libcurses. */
static int
pair_content_wrapper(int pair, int *fg, int *bg)
{
#ifdef HAVE_EXTENDED_COLORS
	return extended_pair_content(pair, fg, bg);
#else
	short fg_short, bg_short;

	const int result = pair_content(pair, &fg_short, &bg_short);

	*fg = fg_short;
	*bg = bg_short;

	return result;
#endif
}

/* Checks whether pair is being used at the moment.  Returns non-zero if so and
 * zero otherwise. */
static int
pair_in_use(int pair)
{
	int i;

	for(i = 0; i < MAXNUM_COLOR; ++i)
	{
		if(cfg.cs.pair[i] == pair || lwin.cs.pair[i] == pair ||
				rwin.cs.pair[i] == pair)
		{
			return 1;
		}
	}

	return 0;
}

/* Replaces uses of an old pair number with the new one. */
static void
pair_moved(int from, int to)
{
	int i;
	for(i = 0; i < MAXNUM_COLOR; ++i)
	{
		if(cfg.cs.pair[i] == from)
		{
			cfg.cs.pair[i] = to;
		}
		if(lwin.cs.pair[i] == from)
		{
			lwin.cs.pair[i] = to;
		}
		if(rwin.cs.pair[i] == from)
		{
			rwin.cs.pair[i] = to;
		}
	}
}

void
ui_set_mouse_active(int active)
{
	if(vifm_testing())
	{
		return;
	}

	if(active)
	{
		mousemask(ALL_MOUSE_EVENTS, /*oldmask=*/NULL);
		mouseinterval(0);
	}
	else
	{
		mousemask(/*newmask=*/0, /*oldmask=*/NULL);
	}
}

/* Initializes all WINDOW variables by calling newwin() to create ncurses
 * windows and configures hardware cursor. */
static void
create_windows(void)
{
	no_delay_window = newwin(1, 1, 0, 0);
	wtimeout(no_delay_window, 0);

	inf_delay_window = newwin(1, 1, 0, 0);
	wtimeout(inf_delay_window, -1);

	/* These refreshes prevent curses from drawing these windows on first read
	 * from them.  It seems that this way they get updated early and this keeps
	 * them from being drawn again. */
	wnoutrefresh(no_delay_window);
	wnoutrefresh(inf_delay_window);

	menu_win = newwin(1, 1, 0, 0);
	sort_win = newwin(1, 1, 0, 0);
	change_win = newwin(1, 1, 0, 0);
	error_win = newwin(1, 1, 0, 0);

	lborder = newwin(1, 1, 0, 0);

	lwin.title = newwin(1, 1, 0, 0);
	lwin.win = newwin(1, 1, 0, 0);

	mborder = newwin(1, 1, 0, 0);

	ltop_line1 = newwin(1, 1, 0, 0);
	ltop_line2 = newwin(1, 1, 0, 0);

	mtop_line = newwin(1, 1, 0, 0);
	tab_line = newwin(1, 1, 0, 0);

	rtop_line1 = newwin(1, 1, 0, 0);
	rtop_line2 = newwin(1, 1, 0, 0);

	rwin.title = newwin(1, 1, 0, 0);
	rwin.win = newwin(1, 1, 0, 0);

	rborder = newwin(1, 1, 0, 0);

	stat_win = newwin(1, 1, 0, 0);
	job_bar = newwin(1, 1, 0, 0);
	status_bar = newwin(1, 1, 0, 0);
	ruler_win = newwin(1, 1, 0, 0);
	input_win = newwin(1, 1, 0, 0);

	leaveok(menu_win, FALSE);
	leaveok(sort_win, FALSE);
	leaveok(change_win, FALSE);
	leaveok(error_win, FALSE);
	leaveok(lwin.win, FALSE);
	leaveok(rwin.win, FALSE);
	leaveok(status_bar, FALSE);

	leaveok(lborder, TRUE);
	leaveok(lwin.title, TRUE);
	leaveok(mborder, TRUE);
	leaveok(ltop_line1, TRUE);
	leaveok(ltop_line2, TRUE);
	leaveok(mtop_line, TRUE);
	leaveok(tab_line, TRUE);
	leaveok(rtop_line1, TRUE);
	leaveok(rtop_line2, TRUE);
	leaveok(rwin.title, TRUE);
	leaveok(rborder, TRUE);
	leaveok(stat_win, TRUE);
	leaveok(job_bar, TRUE);
	leaveok(ruler_win, TRUE);
	leaveok(input_win, TRUE);
}

void
ui_update_term_state(void)
{
	int screen_x, screen_y;

	update_term_size();
	getmaxyx(stdscr, screen_y, screen_x);
	(void)stats_update_term_state(screen_x, screen_y);
}

int
ui_char_pressed(wint_t c)
{
	if(curr_stats.load_stage < 2)
	{
		return 0;
	}

	ui_cancellation_push_off();
	wint_t pressed;

	while(1)
	{
		pressed = L'\0';
		/* Query single character in non-blocking mode. */
		if(compat_wget_wch(no_delay_window, &pressed) == ERR)
		{
			break;
		}

		if(c != NC_C_c && pressed == NC_C_c)
		{
			ui_cancellation_request();
		}

		if(pressed == c)
		{
			break;
		}
	}

	ui_cancellation_pop();

	return (pressed == c);
}

void
ui_drain_input(void)
{
	wint_t c;
	while(compat_wget_wch(no_delay_window, &c) != ERR)
	{
		/* Discard input. */
	}
}

static void
correct_size(view_t *view)
{
	getmaxyx(view->win, view->window_rows, view->window_cols);
	fview_update_geometry(view);
}

/* Updates TUI elements sizes and coordinates for single window
 * configuration. */
static void
only_layout(view_t *view, int screen_x)
{
	const int pos_correction = cfg.side_borders_visible ? 1 : 0;
	const int size_correction = cfg.side_borders_visible ? -2 : 0;
	const int y = get_tabline_height();

	wresize(view->title, 1, screen_x + size_correction);
	mvwin(view->title, y, pos_correction);

	mvwin(ltop_line1, y, 0);
	mvwin(ltop_line2, y, 0);

	mvwin(rtop_line1, y, screen_x - 1);
	mvwin(rtop_line2, y, screen_x - 1);

	wresize(tab_line, 1, screen_x);
	mvwin(tab_line, 0, 0);

	wresize(view->win, get_working_area_height(), screen_x + size_correction);
	mvwin(view->win, y + 1, pos_correction);
}

/* Updates TUI elements sizes and coordinates for vertical configuration of
 * panes: left one and right one. */
static void
vertical_layout(int screen_x)
{
	const int pos_correction = cfg.side_borders_visible ? 1 : 0;
	const int size_correction = cfg.side_borders_visible ? -1 : 0;
	const int border_height = get_working_area_height();
	const int y = get_tabline_height();

	int splitter_pos;
	if(curr_stats.splitter_pos < 0)
		splitter_pos = screen_x/2 - 1 + screen_x%2;
	else
		splitter_pos = curr_stats.splitter_pos;

	int splitter_width = (cfg.flexible_splitter ? 2 - screen_x%2 : 1);
	if(splitter_pos < 4)
		splitter_pos = 4;
	if(splitter_pos > screen_x - 4 - splitter_width)
		splitter_pos = screen_x - 4 - splitter_width;
	if(curr_stats.splitter_pos >= 0)
		stats_set_splitter_pos(splitter_pos);

	wresize(lwin.title, 1, splitter_pos + size_correction);
	mvwin(lwin.title, y, pos_correction);

	wresize(lwin.win, border_height, splitter_pos + size_correction);
	mvwin(lwin.win, y + 1, pos_correction);

	wresize(mborder, border_height, splitter_width);
	mvwin(mborder, y + 1, splitter_pos);

	mvwin(ltop_line1, y, 0);
	mvwin(ltop_line2, y, 0);

	wresize(tab_line, 1, screen_x);
	mvwin(tab_line, 0, 0);

	wresize(mtop_line, 1, splitter_width);
	mvwin(mtop_line, y, splitter_pos);

	mvwin(rtop_line1, y, screen_x - 1);
	mvwin(rtop_line2, y, screen_x - 1);

	wresize(rwin.title, 1,
			screen_x - (splitter_pos + splitter_width) + size_correction);
	mvwin(rwin.title, y, splitter_pos + splitter_width);

	wresize(rwin.win, border_height,
			screen_x - (splitter_pos + splitter_width) + size_correction);
	mvwin(rwin.win, y + 1, splitter_pos + splitter_width);
}

/* Updates TUI elements sizes and coordinates for horizontal configuration of
 * panes: top one and bottom one. */
static void
horizontal_layout(int screen_x, int screen_y)
{
	const int pos_correction = cfg.side_borders_visible ? 1 : 0;
	const int size_correction = cfg.side_borders_visible ? -2 : 0;
	const int y = get_tabline_height();

	int splitter_pos;
	if(curr_stats.splitter_pos < 0)
		splitter_pos = screen_y/2 - 1;
	else
		splitter_pos = curr_stats.splitter_pos;
	int splitter_height = (middle_border_is_visible() ? 1 : 0);
	if(splitter_pos < 2)
		splitter_pos = 2;
	if(splitter_pos > get_working_area_height() - 1 - splitter_height)
		splitter_pos = get_working_area_height() - 1 - splitter_height;
	if(curr_stats.splitter_pos >= 0)
		stats_set_splitter_pos(splitter_pos);

	wresize(lwin.title, 1, screen_x + size_correction);
	mvwin(lwin.title, y, pos_correction);

	wresize(lwin.win, splitter_pos - (y + 1), screen_x + size_correction);
	mvwin(lwin.win, y + 1, pos_correction);

	wresize(mborder, splitter_height, screen_x);
	mvwin(mborder, splitter_pos, 0);

	wresize(rwin.title, 1, screen_x + size_correction);
	mvwin(rwin.title, splitter_pos + splitter_height, pos_correction);

	wresize(rwin.win,
			get_working_area_height() - splitter_pos - splitter_height + y,
			screen_x + size_correction);
	mvwin(rwin.win, splitter_pos + splitter_height + 1, pos_correction);

	mvwin(ltop_line1, y, 0);
	mvwin(ltop_line2, splitter_pos, 0);

	wresize(tab_line, 1, screen_x);
	mvwin(tab_line, 0, 0);

	mvwin(rtop_line1, y, screen_x - 1);
	mvwin(rtop_line2, splitter_pos, screen_x - 1);

	wresize(lborder, screen_y - 1, 1);
	mvwin(lborder, y, 0);

	wresize(rborder, screen_y - 1, 1);
	mvwin(rborder, y, screen_x - 1);

	/* Unused in this layout. */
	wresize(mtop_line, 1, 1);
	mvwin(mtop_line, 0, 0);
}

/* Calculates height available for main area that contains file lists.  Returns
 * the height. */
static int
get_working_area_height(void)
{
	return getmaxy(stdscr)          /* Total available height. */
	     - 1                        /* Top line. */
	     - ui_stat_height()         /* Status line. */
	     - ui_stat_job_bar_height() /* Job bar. */
	     - 1                        /* Status bar line. */
	     - get_tabline_height();    /* Tab line. */
}

/* Updates internal data structures to reflect actual terminal geometry. */
static void
update_geometry(void)
{
	int screen_x, screen_y;

	update_term_size();

	getmaxyx(stdscr, screen_y, screen_x);
	cfg.lines = screen_y;
	cfg.columns = screen_x;

	LOG_INFO_MSG("New geometry: %dx%d", screen_x, screen_y);

	if(curr_stats.initial_lines == INT_MIN)
	{
		curr_stats.initial_lines = screen_y;
		curr_stats.initial_columns = screen_x;
	}

	load_geometry();
}

void
ui_quit(int write_info, int force)
{
	if(tabs_quit_on_close())
	{
		vifm_try_leave(write_info, 0, force);
	}
	else
	{
		tabs_close();
	}
}

int
cv_unsorted(CVType type)
{
	return type == CV_VERY || cv_compare(type);
}

int
cv_compare(CVType type)
{
	return type == CV_COMPARE || type == CV_DIFF;
}

int
cv_tree(CVType type)
{
	return type == CV_TREE || type == CV_CUSTOM_TREE;
}

const char *
cv_describe(CVType type)
{
	switch(type)
	{
		case CV_REGULAR:
			return "custom";

		case CV_VERY:
			return "very-custom";

		case CV_CUSTOM_TREE:
		case CV_TREE:
			return "tree";

		case CV_DIFF:
		case CV_COMPARE:
			return "compare";
	}

	return "UNKNOWN";
}

void
update_screen(UpdateType update_kind)
{
	if(update_start(update_kind))
	{
		update_all_windows();
		update_finish();
	}
}

void
ui_redraw_as_background(void)
{
	if(update_start(UT_REDRAW))
	{
		update_finish();
	}
}

/* Most of the update logic.  Everything that's done before updating windows.
 * Returns non-zero if update was carried out until the end, otherwise zero is
 * returned. */
static int
update_start(UpdateType update_kind)
{
	if(curr_stats.load_stage < 2 || update_kind == UT_NONE)
	{
		return 0;
	}

	ui_resize_all();

	if(curr_stats.restart_in_progress != RT_NONE)
	{
		return 0;
	}

	update_attributes();

	if(curr_stats.term_state != TS_NORMAL)
	{
		return 0;
	}

	qv_ui_updated();

	update_views(update_kind == UT_FULL);
	/* Redraw message dialog over updated panes.  It's not very nice to do it
	 * here, but for sure better then blocking pane updates by checking for
	 * message mode. */
	if(vle_mode_is(MSG_MODE))
	{
		redraw_msg_dialog(0);
	}

	ui_stat_update(curr_view, 0);

	if(curr_stats.save_msg == 0 && !ui_sb_multiline())
	{
		modes_statusbar_update();

		if(vle_mode_is(VIEW_MODE))
		{
			modview_ruler_update();
		}
		else
		{
			ui_ruler_update(curr_view, 1);
		}
	}

	if(curr_stats.save_msg == 0)
	{
		ui_sb_clear();
	}
	else
	{
		ui_sb_msg(NULL);
	}

	if(vle_mode_is(VIEW_MODE) ||
			(curr_stats.number_of_windows == 2 && other_view->explore_mode))
	{
		modview_redraw();
	}

	return 1;
}

/* Post-windows-update part of an update. */
static void
update_finish(void)
{
	if(!curr_view->explore_mode)
	{
		fview_cursor_redraw(curr_view);
	}

	update_input_buf();
	ui_stat_job_bar_redraw();
}

void
ui_resize_all(void)
{
	update_geometry();

	int screen_w, screen_h;
	getmaxyx(stdscr, screen_h, screen_w);
	LOG_INFO_MSG("screen_h = %d; screen_w = %d", screen_h, screen_w);

	if(stats_update_term_state(screen_w, screen_h) != TS_NORMAL)
	{
		return;
	}

	adjust_splitter(screen_w, screen_h);

	wresize(stdscr, screen_h, screen_w);
	wresize(menu_win, screen_h - 1, screen_w);

	int border_h = get_working_area_height();
	int border_y = 1 + get_tabline_height();

	wresize(lborder, border_h, 1);
	mvwin(lborder, border_y, 0);

	wresize(rborder, border_h, 1);
	mvwin(rborder, border_y, screen_w - 1);

	/* These need a resize at least after terminal size was zero or they grow and
	 * produce bad looking effect. */
	wresize(ltop_line1, 1, 1);
	wresize(ltop_line2, 1, 1);
	wresize(rtop_line1, 1, 1);
	wresize(rtop_line2, 1, 1);

	if(curr_stats.number_of_windows == 1)
	{
		only_layout(&lwin, screen_w);
		only_layout(&rwin, screen_w);
	}
	else
	{
		if(curr_stats.split == HSPLIT)
			horizontal_layout(screen_w, screen_h);
		else
			vertical_layout(screen_w);
	}

	correct_size(&lwin);
	correct_size(&rwin);

	wresize(stat_win, ui_stat_height(), screen_w);
	(void)ui_stat_reposition(1, 0);

	wresize(job_bar, 1, screen_w);

	update_statusbar_layout();

	ui_set_cursor(/*visibility=*/0);
}

/* Adjusts splitter position after screen resize. */
static void
adjust_splitter(int screen_w, int screen_h)
{
	static int prev_w = -1, prev_h = -1;

	if(curr_stats.splitter_pos < 0)
	{
		prev_w = -1;
		prev_h = -1;
		return;
	}

	if(prev_w != screen_w || prev_h != screen_h)
	{
		stats_set_splitter_ratio(curr_stats.splitter_ratio);
		prev_w = screen_w;
		prev_h = screen_h;
	}
}

/* Clears border, possibly by filling it with a pattern (depends on
 * configuration). */
static void
clear_border(WINDOW *border)
{
	int i;
	int height, width;

	werase(border);

	getmaxyx(border, height, width);
	if(height > width)
	{
		/* Vertical split */
		if(strcmp(cfg.vborder_filler, " ") == 0 || *cfg.vborder_filler == '\0')
		{
			return;
		}
		for(i = 0; i < height; ++i)
		{
			mvwaddstr(border, i, 0, cfg.vborder_filler);
		}
	}
	else
	{
		/* Horizontal split */
		if(strcmp(cfg.hborder_filler, " ") == 0 || *cfg.hborder_filler == '\0')
		{
			return;
		}
		for(i = 0; i < width; ++i)
		{
			mvwaddstr(border, 0, i, cfg.hborder_filler);
		}
	}

	wnoutrefresh(border);
}

/* Checks whether mborder should be displayed/updated.  Returns non-zero if so,
 * otherwise zero is returned. */
static int
middle_border_is_visible(void)
{
	if(curr_stats.number_of_windows != 2)
	{
		return 0;
	}

	if(curr_stats.split == VSPLIT)
	{
		/* Always visible for vertical split. */
		return 1;
	}

	/* Optionally visible for horizontal split. */
	return (strlen(cfg.hborder_filler) != 0);
}

/* Updates (redraws or reloads) views. */
static void
update_views(int reload)
{
	if(reload)
		reload_lists();
	else
		redraw_lists();
}

/* Reloads file lists for both views. */
static void
reload_lists(void)
{
	reload_list(curr_view);

	if(curr_stats.number_of_windows == 2)
	{
		ui_view_title_update(other_view);
		if(curr_stats.preview.on)
		{
			qv_draw(curr_view);
		}
		else if(!other_view->explore_mode)
		{
			reload_list(other_view);
		}
	}
}

/* Reloads view handling special case of loading it for the first time during
 * startup. */
static void
reload_list(view_t *view)
{
	if(curr_stats.load_stage < 3)
	{
		const int keep_position = cfg_ch_pos_on(CHPOS_STARTUP)
		                        ? 0
		                        : !is_dir_list_loaded(view);
		load_dir_list(view, keep_position);
		return;
	}

	load_saving_pos(view);
}

void
change_window(void)
{
	swap_view_roles();

	load_view_options(curr_view);

	if(window_shows_dirlist(other_view))
	{
		const col_scheme_t *cs = ui_view_get_cs(other_view);
		if(cs_is_color_set(&cs->color[OTHER_WIN_COLOR]))
		{
			draw_dir_list(other_view);
		}
		else
		{
			fview_draw_inactive_cursor(other_view);
		}
	}

	if(curr_stats.preview.on && !is_dir_list_loaded(curr_view))
	{
		/* This view hasn't been loaded since startup yet, do it now. */
		navigate_to(curr_view, curr_view->curr_dir);
	}
	else
	{
		/* Change working directory, so that %c macro and other cwd-sensitive things
		 * work as expected. */
		(void)vifm_chdir(flist_get_dir(curr_view));
	}

	if(window_shows_dirlist(&lwin) && window_shows_dirlist(&rwin))
	{
		fview_cursor_redraw(curr_view);
		ui_views_update_titles();
	}
	else
	{
		stats_redraw_later();
	}
}

void
swap_view_roles(void)
{
	view_t *const tmp = curr_view;
	curr_view = other_view;
	other_view = tmp;
}

void
update_all_windows(void)
{
	if(curr_stats.load_stage >= 2)
	{
		touch_all_windows();
		doupdate();
	}
}

void
touch_all_windows(void)
{
	if(curr_stats.load_stage < 2)
	{
		return;
	}

	if(!modes_is_menu_like())
	{
		update_window_lazy(tab_line);

		if(middle_border_is_visible())
		{
			update_window_lazy(mborder);
			update_window_lazy(mtop_line);
		}

		if(curr_stats.number_of_windows == 1)
		{
			/* In one window view. */
			update_view(curr_view);
		}
		else
		{
			/* Two pane View. */
			update_view(&lwin);
			update_view(&rwin);
		}

		if(cfg.side_borders_visible)
		{
			/* This needs to be updated before things like status bar and status line
			 * to account for cases when they hide the top line. */
			update_window_lazy(ltop_line1);
			update_window_lazy(ltop_line2);
			update_window_lazy(rtop_line1);
			update_window_lazy(rtop_line2);

			update_window_lazy(lborder);
			update_window_lazy(rborder);
		}

		if(cfg.display_statusline)
		{
			update_window_lazy(stat_win);
		}

		if(ui_stat_job_bar_height() != 0)
		{
			update_window_lazy(job_bar);
		}
	}

	update_window_lazy(ruler_win);
	update_window_lazy(input_win);
	update_window_lazy(status_bar);

	if(vle_mode_is(MSG_MODE))
	{
		/* Location of message dialog can change, so it's better to let it position
		 * itself or multiple copies appear on the screen. */
		redraw_msg_dialog(1);
	}
}

/* Updates all parts of file view. */
static void
update_view(view_t *view)
{
	update_window_lazy(view->title);

	/* If view displays graphics, we don't want to update it or the image will be
	 * lost. */
	if(!view->explore_mode && !view->displays_graphics &&
			!(curr_stats.preview.on && view == other_view))
	{
		update_window_lazy(view->win);
	}
}

/* Tell curses to internally mark window as changed. */
static void
update_window_lazy(WINDOW *win)
{
	touchwin(win);
	/* Tell curses that it shouldn't assume that screen isn't messed up in any
	 * way. */
	redrawwin(win);
	wnoutrefresh(win);
}

void
update_input_bar(const wchar_t *str)
{
	if(!curr_stats.use_input_bar)
		return;

	if(wcslen(str) > (size_t)getmaxx(input_win))
	{
		str += wcslen(str) - getmaxx(input_win);
	}

	werase(input_win);
	compat_waddwstr(input_win, str);
	ui_refresh_win(input_win);
}

void
clear_num_window(void)
{
	if(curr_stats.use_input_bar)
	{
		werase(input_win);
		ui_refresh_win(input_win);
	}
}

void
show_progress(const char msg[], int period)
{
	static char marks[] = { '|', '/', '-', '\\' };
	static int mark = 0;
	static int count = 1;
	static int total = 0;

	/* Do nothing if UI is not functional. */
	if(curr_stats.load_stage < 1)
	{
		return;
	}

	/* Reset state. */
	if(period == 0)
	{
		count = 1;
		total = 0;
		return;
	}

	/* Advance state. */
	++count;
	++total;

	/* Skip intermediate updates to do not hammer UI with refreshes. */
	if(abs(period) != 1 && count%abs(period) != 1)
	{
		return;
	}
	count = 1;

	/* Assume that period equal to or less than one means that message already
	 * contains counter (maybe along with total number) or doesn't need one. */
	if(period <= 1)
	{
		ui_sb_quick_msgf("%s %c", msg, marks[mark]);
	}
	else
	{
		ui_sb_quick_msgf("%s %c %d", msg, marks[mark], total);
	}

	/* Pick next mark character. */
	mark = (mark + 1) % sizeof(marks);
}

void
redraw_lists(void)
{
	if(curr_stats.load_stage == 0)
	{
		return;
	}

	redraw_current_view();
	if(curr_stats.number_of_windows == 2)
	{
		if(curr_stats.preview.on)
		{
			qv_draw(curr_view);
			refresh_view_win(other_view);
		}
		else if(!other_view->explore_mode)
		{
			fview_cursor_redraw(other_view);
			draw_dir_list(other_view);
			refresh_view_win(other_view);
		}
	}
}

void
update_attributes(void)
{
	if(curr_stats.load_stage < 2)
		return;

	if(cfg.side_borders_visible)
	{
		ui_set_bg(lborder, &cfg.cs.color[BORDER_COLOR], cfg.cs.pair[BORDER_COLOR]);
		clear_border(lborder);
		ui_set_bg(rborder, &cfg.cs.color[BORDER_COLOR], cfg.cs.pair[BORDER_COLOR]);
		clear_border(rborder);

		ui_set_bg(ltop_line1, &cfg.cs.color[TOP_LINE_COLOR],
				cfg.cs.pair[TOP_LINE_COLOR]);
		werase(ltop_line1);

		ui_set_bg(ltop_line2, &cfg.cs.color[TOP_LINE_COLOR],
				cfg.cs.pair[TOP_LINE_COLOR]);
		werase(ltop_line2);

		ui_set_bg(rtop_line1, &cfg.cs.color[TOP_LINE_COLOR],
				cfg.cs.pair[TOP_LINE_COLOR]);
		werase(rtop_line1);

		ui_set_bg(rtop_line2, &cfg.cs.color[TOP_LINE_COLOR],
				cfg.cs.pair[TOP_LINE_COLOR]);
		werase(rtop_line2);
	}

	if(middle_border_is_visible())
	{
		ui_set_bg(mtop_line, &cfg.cs.color[TOP_LINE_COLOR],
				cfg.cs.pair[TOP_LINE_COLOR]);
		werase(mtop_line);
		/* For some reason this wnoutrefresh is needed to make this window appear
		 * at the same time along with others during startup.  Might be an
		 * indication that all windows need a refresh, but that need is masked. */
		wnoutrefresh(mtop_line);

		ui_set_bg(mborder, &cfg.cs.color[BORDER_COLOR], cfg.cs.pair[BORDER_COLOR]);
		clear_border(mborder);
	}

	ui_set_bg(tab_line, &cfg.cs.color[TAB_LINE_COLOR],
			cfg.cs.pair[TAB_LINE_COLOR]);
	werase(tab_line);

	ui_set_bg(stat_win, &cfg.cs.color[STATUS_LINE_COLOR],
			cfg.cs.pair[STATUS_LINE_COLOR]);

	ui_set_bg(job_bar, &cfg.cs.color[JOB_LINE_COLOR],
			cfg.cs.pair[JOB_LINE_COLOR]);

	ui_set_bg(menu_win, &cfg.cs.color[WIN_COLOR], cfg.cs.pair[WIN_COLOR]);
	ui_set_bg(sort_win, &cfg.cs.color[WIN_COLOR], cfg.cs.pair[WIN_COLOR]);
	ui_set_bg(change_win, &cfg.cs.color[WIN_COLOR], cfg.cs.pair[WIN_COLOR]);
	ui_set_bg(error_win, &cfg.cs.color[WIN_COLOR], cfg.cs.pair[WIN_COLOR]);

	ui_set_bg(status_bar, &cfg.cs.color[CMD_LINE_COLOR],
			cfg.cs.pair[CMD_LINE_COLOR]);
	ui_set_bg(ruler_win, &cfg.cs.color[CMD_LINE_COLOR],
			cfg.cs.pair[CMD_LINE_COLOR]);
	ui_set_bg(input_win, &cfg.cs.color[CMD_LINE_COLOR],
			cfg.cs.pair[CMD_LINE_COLOR]);
}

void
ui_refresh_win(WINDOW *win)
{
	if(!stats_silenced_ui())
	{
		use_wrefresh(win);
	}
}

void
wprint(WINDOW *win, const char str[])
{
#ifndef _WIN32
	waddstr(win, str);
#else
	wchar_t *t = to_wide(str);
	if(t == NULL)
	{
		show_error_msg("Memory Error", "Unable to allocate enough memory");
		return;
	}

	compat_waddwstr(win, t);
	free(t);
#endif
}

void
wprinta(WINDOW *win, const char str[], const cchar_t *line_attrs,
		int attrs_xors)
{
	attr_t attrs;
	short color_pair;
	wchar_t wch[getcchar(line_attrs, NULL, &attrs, &color_pair, NULL)];
	getcchar(line_attrs, wch, &attrs, &color_pair, NULL);

	col_attr_t col = {
		.attr = attrs ^ attrs_xors,
		.gui_attr = attrs ^ attrs_xors
	};
	ui_set_attr(win, &col, color_pair);
	wprint(win, str);
	wnoutrefresh(win);
}

int
resize_for_menu_like(void)
{
	int screen_x, screen_y;

	/* Resizing everything to avoid a situation when windows that are not visible
	 * are of wrong size and using redrawwin() on them causes a memory corruption.
	 * I'm pretty sure it's a bug in ncurses. */
	ui_resize_all();
	if(curr_stats.term_state == TS_TOO_SMALL)
	{
		return 1;
	}

	update_term_size();
	flushinp(); /* without it we will get strange character on input */
	getmaxyx(stdscr, screen_y, screen_x);

	werase(stdscr);
	werase(status_bar);
	werase(ruler_win);

	wresize(menu_win, screen_y - 1, screen_x);

	update_statusbar_layout();

	ui_refresh_win(status_bar);
	ui_refresh_win(ruler_win);
	ui_refresh_win(input_win);

	return 0;
}

void
ui_setup_for_menu_like(void)
{
	if(curr_stats.load_stage > 0)
	{
		scrollok(menu_win, FALSE);
		ui_set_cursor(/*visibility=*/0);
		werase(menu_win);
		werase(status_bar);
		werase(ruler_win);
		ui_refresh_win(status_bar);
		ui_refresh_win(ruler_win);
	}
}

/* Query terminal size from the "device" and pass it to curses library. */
static void
update_term_size(void)
{
	if(curr_stats.load_stage < 1)
	{
		/* No terminal in tests. */
		return;
	}

#ifndef _WIN32
	struct winsize ws = { .ws_col = -1, .ws_row = -1 };

	if(ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1)
	{
		LOG_SERROR_MSG(errno, "Failed to query terminal size.");
		vifm_finish("Terminal error.");
	}
	/* Allow 0 as GNU Hurd returns this, is_term_resized() returns false then, but
	 * everything works. */
	if(ws.ws_row == (typeof(ws.ws_row))-1 || ws.ws_col == (typeof(ws.ws_col))-1)
	{
		LOG_INFO_MSG("ws.ws_row = %d; ws.ws_col = %d", ws.ws_row, ws.ws_col);
		vifm_finish("Terminal is unable to run vifm.");
	}

	if(is_term_resized(ws.ws_row, ws.ws_col))
	{
		resizeterm(ws.ws_row, ws.ws_col);
	}
#elif defined(__PDCURSES__)
	if(is_termresized())
	{
		/* This resizes vifm to window size. */
		resize_term(0, 0);
	}
#endif
}

/* Re-layouts windows located on status bar (status bar itself, input and the
 * ruler). */
static void
update_statusbar_layout(void)
{
	int screen_x, screen_y;
	getmaxyx(stdscr, screen_y, screen_x);

	int max_ruler_width = screen_x - INPUT_WIN_WIDTH - 1;
	int ruler_width = MIN(get_ruler_width(curr_view), max_ruler_width);
	/* The minimal start position is 1, not 0, because otherwise the ruler is
	 * hidden by a single-character status bar window. */
	int fields_pos = screen_x - (INPUT_WIN_WIDTH + ruler_width);

	wresize(ruler_win, 1, ruler_width);
	mvwin(ruler_win, screen_y - 1, fields_pos + INPUT_WIN_WIDTH);

	wresize(input_win, 1, INPUT_WIN_WIDTH);
	mvwin(input_win, screen_y - 1, fields_pos);

	/* We might be in command-line mode in which case we shouldn't change visible
	 * parts of the layout. */
	if(are_statusbar_widgets_visible())
	{
		wresize(status_bar, 1, fields_pos);
		mvwin(status_bar, screen_y - 1, 0);

		wnoutrefresh(ruler_win);
		wnoutrefresh(input_win);
	}
}

/* Checks whether ruler and input bar are visible.  Returns non-zero if so, zero
 * is returned otherwise. */
static int
are_statusbar_widgets_visible(void)
{
	return !ui_sb_multiline() && !ui_sb_locked();
}

/* Gets "recommended" width for the ruler.  Returns the width. */
static int
get_ruler_width(view_t *view)
{
	char *expanded;
	int len;
	int list_pos;

	if(vle_mode_is(MENU_MODE))
	{
		return modmenu_get_ruler_width();
	}

	/* Size must correspond to the "worst case" of the last list item. */
	list_pos = view->list_pos;
	view->list_pos = (view->list_rows == 0) ? 0 : (view->list_rows - 1);

	expanded = expand_ruler_macros(view, cfg.ruler_format);
	len = strlen(expanded);
	free(expanded);

	view->list_pos = list_pos;

	return MAX(POS_WIN_MIN_WIDTH, len);
}

/* Expands view macros to be displayed on the ruler line according to the format
 * string.  Returns newly allocated string, which should be freed by the caller,
 * or NULL if there is not enough memory. */
static char *
expand_ruler_macros(view_t *view, const char format[])
{
	return expand_view_macros(view, format, RULER_MACROS);
}

void
refresh_view_win(view_t *view)
{
	if(curr_stats.restart_in_progress != RT_NONE)
	{
		return;
	}

	ui_refresh_win(view->win);
	refresh_bottom_lines();
}

void
move_window(view_t *view, int horizontally, int first)
{
	const SPLIT split_type = horizontally ? HSPLIT : VSPLIT;
	const view_t *const desired_view = first ? &lwin : &rwin;
	split_view(split_type);
	if(view != desired_view)
	{
		/* Switch two panes saving current windows as the active one (left/top or
		 * right/bottom). */
		switch_panes_content();
		go_to_other_pane();
		tabs_switch_panes();
	}
}

void
switch_panes(void)
{
	switch_panes_content();
	load_view_options(curr_view);
	modview_try_activate();
}

int
ui_focus_view(unsigned int id)
{
	int tab_idx;
	view_t *side;
	if(locate_view(id, &tab_idx, &side) != 0)
	{
		return 1;
	}

	if(cfg.pane_tabs)
	{
		if(curr_view != side)
		{
			go_to_other_pane();
		}
		tabs_goto(tab_idx);
	}
	else
	{
		tabs_goto(tab_idx);
		if(curr_view != side)
		{
			go_to_other_pane();
		}
	}

	return 0;
}

/* Finds tab index and side that correspond to a view identified by its id.
 * Returns zero on success. */
static int
locate_view(unsigned int id, int *tab_idx, view_t **side)
{
	int tab = 0;
	view_t *tab_side = NULL;
	while(1)
	{
		tab_info_t tab_info;

		int left_exists = tabs_enum(&lwin, tab, &tab_info);
		if(left_exists && tab_info.view->id == id)
		{
			tab_side = &lwin;
			break;
		}

		int right_exists = tabs_enum(&rwin, tab, &tab_info);
		if(right_exists && tab_info.view->id == id)
		{
			tab_side = &rwin;
			break;
		}

		if(!left_exists && !right_exists)
		{
			break;
		}

		++tab;
	}

	if(tab_side == NULL)
	{
		return 1;
	}

	*tab_idx = tab;
	*side = tab_side;
	return 0;
}

void
ui_view_pick(view_t *view, view_t **old_curr, view_t **old_other)
{
	*old_curr = curr_view;
	*old_other = other_view;

	curr_view = view;
	other_view = (view == *old_curr) ? *old_other : *old_curr;
	if(curr_view != *old_curr)
	{
		load_view_options(curr_view);
	}
}

void
ui_view_unpick(view_t *view, view_t *old_curr, view_t *old_other)
{
	if(curr_view != view)
	{
		/* Do nothing if view roles were switched from outside. */
		return;
	}

	curr_view = old_curr;
	other_view = old_other;
	if(curr_view != view)
	{
		load_view_options(curr_view);
	}
}

/* Switches panes content. */
static void
switch_panes_content(void)
{
	ui_swap_view_data(&lwin, &rwin);

	flist_update_origins(&lwin);
	flist_update_origins(&rwin);

	modview_panes_swapped();

	stats_redraw_later();
}

void
ui_swap_view_data(view_t *left, view_t *right)
{
	view_t tmp_view;
	WINDOW *tmp;
	int t;

	/* Data in some fields doesn't need to be swapped.  Swap it beforehand so that
	 * swapping structures will put it back. */

	tmp = left->win;
	left->win = right->win;
	right->win = tmp;

	t = left->window_rows;
	left->window_rows = right->window_rows;
	right->window_rows = t;

	t = left->window_cols;
	left->window_cols = right->window_cols;
	right->window_cols = t;

	tmp = left->title;
	left->title = right->title;
	right->title = tmp;

	/* Swap these fields so they reflect updated layout. */

	t = left->custom.diff_stats.unique_left;
	left->custom.diff_stats.unique_left = left->custom.diff_stats.unique_right;
	left->custom.diff_stats.unique_right = t;

	t = right->custom.diff_stats.unique_left;
	right->custom.diff_stats.unique_left = right->custom.diff_stats.unique_right;
	right->custom.diff_stats.unique_right = t;

	const int unique_lr = (CF_SHOW_UNIQUE_LEFT | CF_SHOW_UNIQUE_RIGHT);
	if((left->custom.diff_cmp_flags & unique_lr) == CF_SHOW_UNIQUE_LEFT ||
			(left->custom.diff_cmp_flags & unique_lr) == CF_SHOW_UNIQUE_RIGHT)
	{
		left->custom.diff_cmp_flags ^= unique_lr;
		right->custom.diff_cmp_flags ^= unique_lr;
	}

	tmp_view = *left;
	*left = *right;
	*right = tmp_view;
}

void
go_to_other_pane(void)
{
	change_window();
	modview_try_activate();
}

void
split_view(SPLIT orientation)
{
	if(curr_stats.number_of_windows == 2 && curr_stats.split == orientation)
		return;

	curr_stats.split = orientation;
	curr_stats.number_of_windows = 2;

	if(curr_stats.splitter_pos > 0)
	{
		stats_set_splitter_ratio(curr_stats.splitter_ratio);
	}

	stats_redraw_later();
}

void
only(void)
{
	if(curr_stats.number_of_windows != 1)
	{
		ui_hide_graphics();
		curr_stats.number_of_windows = 1;
		update_screen(UT_REDRAW);
	}
}

void
move_splitter(int by, int fact)
{
	int pos = curr_stats.splitter_pos;

	/* Determine exact splitter position if it's centered at the moment. */
	if(curr_stats.splitter_pos < 0)
	{
		if(curr_stats.split == HSPLIT)
		{
			pos = getmaxy(stdscr)/2 - 1;
		}
		else
		{
			pos = getmaxx(stdscr)/2 - 1 + getmaxx(stdscr)%2;
		}
	}

	set_splitter(pos + fact*by);
}

void
ui_remember_scroll_offset(void)
{
	const int rwin_pos = rwin.top_line/rwin.column_count;
	const int lwin_pos = lwin.top_line/lwin.column_count;
	curr_stats.scroll_bind_off = rwin_pos - lwin_pos;
}

void
ui_view_resize(view_t *view, int to)
{
	int pos;

	if(curr_stats.split == HSPLIT)
	{
		const int height = get_working_area_height();
		pos = (view == &lwin) ? (1 + to) : (height - to);
	}
	else
	{
		const int width = getmaxx(stdscr) - 1;
		pos = (view == &lwin) ? to : (width - to);
	}

	set_splitter(pos);
}

/* Sets splitter position making sure it isn't negative value which has special
 * meaning. */
static void
set_splitter(int pos)
{
	stats_set_splitter_pos(pos < 0 ? 0 : pos);
}

void
format_entry_name(const dir_entry_t *entry, NameFormat fmt, size_t buf_len,
		char buf[])
{
	if(fmt == NF_NONE)
	{
		copy_str(buf, buf_len, entry->name);
		return;
	}

	char tmp_buf[strlen(entry->name) + 1];
	const char *name = entry->name;

	if(fmt == NF_ROOT)
	{
		int root_len;
		const char *ext_pos;

		copy_str(tmp_buf, sizeof(tmp_buf), entry->name);
		split_ext(tmp_buf, &root_len, &ext_pos);
		name = tmp_buf;
	}

	const char *prefix, *suffix;
	ui_get_decors(entry, &prefix, &suffix);
	snprintf(buf, buf_len, "%s%s%s", prefix,
			(is_root_dir(name) && suffix[0] == '/') ? "" : name, suffix);
}

void
ui_get_decors(const dir_entry_t *entry, const char **prefix,
		const char **suffix)
{
	/* The check of actual file type can be relatively slow in some cases, so make
	 * sure we do it only when needed and at most once. */
	FileType type = FT_UNK;

	if(entry->name_dec_num == -1)
	{
		/* Find a match and cache the result. */

		((dir_entry_t *)entry)->name_dec_num = 0;
		if(cfg.name_dec_count != 0)
		{
			char full_path[PATH_MAX + 1];
			int i;

			get_full_path_of(entry, sizeof(full_path) - 1U, full_path);
			type = ui_view_entry_target_type(entry);
			if(type == FT_DIR)
			{
				strcat(full_path, "/");
			}

			for(i = 0; i < cfg.name_dec_count; ++i)
			{
				const file_dec_t *const file_dec = &cfg.name_decs[i];
				if(matchers_match(file_dec->matchers, full_path))
				{
					((dir_entry_t *)entry)->name_dec_num = i + 1;
					break;
				}
			}
		}
	}

	if(entry->name_dec_num == 0)
	{
		type = (type == FT_UNK) ? ui_view_entry_target_type(entry) : type;
		*prefix = cfg.type_decs[type][DECORATION_PREFIX];
		*suffix = cfg.type_decs[type][DECORATION_SUFFIX];
	}
	else
	{
		assert(entry->name_dec_num - 1 >= 0 && "Wrong index.");
		assert(entry->name_dec_num - 1 < cfg.name_dec_count && "Wrong index.");

		*prefix = cfg.name_decs[entry->name_dec_num - 1].prefix;
		*suffix = cfg.name_decs[entry->name_dec_num - 1].suffix;
	}
}

void
ui_view_reset_decor_cache(const view_t *view)
{
	int i;

	for(i = 0; i < view->list_rows; ++i)
	{
		view->dir_entry[i].name_dec_num = -1;
	}

	for(i = 0; i < view->left_column.entries.nentries; ++i)
	{
		view->left_column.entries.entries[i].name_dec_num = -1;
	}

	for(i = 0; i < view->right_column.entries.nentries; ++i)
	{
		view->right_column.entries.entries[i].name_dec_num = -1;
	}
}

/* Gets real type of file view entry.  Returns type of entry, resolving symbolic
 * link if needed. */
static FileType
ui_view_entry_target_type(const dir_entry_t *entry)
{
	return (fentry_is_dir(entry) ? FT_DIR : entry->type);
}

void
checked_wmove(WINDOW *win, int y, int x)
{
	if(wmove(win, y, x) == ERR)
	{
		LOG_INFO_MSG("Error moving cursor on a window to (x=%d, y=%d).", x, y);
	}
}

void
ui_set_cursor(int visibility)
{
	/* PDCurses crashes if curs_set() is called for uninitialized library. */
	if(!vifm_testing())
	{
		(void)curs_set(visibility);
	}
}

int
ui_get_mouse(MEVENT *event)
{
	int ret = getmouse(event);
	if(ret != OK)
	{
		return ret;
	}

	/* Positions after 222 can become negative due to a combination of protocol
	 * limitations and implementation.  This workaround can extend the range at
	 * least a bit when SGR 1006 isn't available. */
	if(event->x < 0)
	{
		event->x &= 0xff;
	}
	if(event->y < 0)
	{
		event->y &= 0xff;
	}

	int mask = M_ALL_MODES;
	switch(vle_mode_get())
	{
		case NORMAL_MODE:  mask |= M_NORMAL_MODE; break;
		case NAV_MODE:
		case CMDLINE_MODE: mask |= M_CMDLINE_MODE; break;
		case VISUAL_MODE:  mask |= M_VISUAL_MODE; break;
		case MENU_MODE:    mask |= M_MENU_MODE; break;
		case VIEW_MODE:    mask |= M_VIEW_MODE; break;

		default:           mask = 0; break;
	}

	return (cfg.mouse & mask ? OK : ERR);
}

WINDOW *
ui_get_tab_line_win(const view_t *view)
{
	return (view_shows_tabline(view) ? view->title : tab_line);
}

int
ui_map_tab_line(view_t *view, int x)
{
	if(!is_null_or_empty(cfg.tab_line))
	{
		/* No mouse mapping for custom tabline. */
		return -1;
	}

	path_func pf = cfg.shorten_title_paths ? &replace_home_part : &path_identity;

	const int max_width = getmaxx(ui_get_tab_line_win(view));
	tab_line_info_t info = format_tab_labels(view, max_width, pf);

	int tab_idx = -1;

	int i;
	for(i = 0; i < info.count; ++i)
	{
		if(tab_idx == -1 && x < (int)info.labels[i].attrs_len)
		{
			tab_idx = info.skipped + i;
		}
		x -= info.labels[i].attrs_len;

		cline_dispose(&info.labels[i]);
	}
	free(info.labels);

	return tab_idx;
}

int
ui_wenclose(const view_t *view, WINDOW *win, int x, int y)
{
	return (ui_view_is_visible(view) && wenclose(win, y, x));
}

void
ui_display_too_small_term_msg(void)
{
	touchwin(stdscr);
	ui_refresh_win(stdscr);

	mvwin(status_bar, 0, 0);
	wresize(status_bar, getmaxy(stdscr), getmaxx(stdscr));
	werase(status_bar);
	waddstr(status_bar, "Terminal is too small for vifm");
	touchwin(status_bar);
	ui_refresh_win(status_bar);
}

void
ui_view_win_changed(view_t *view)
{
	wnoutrefresh(view->win);
	refresh_bottom_lines();
}

/* Makes sure that status line and status bar are drawn over view window after
 * view refresh. */
static void
refresh_bottom_lines(void)
{
	/* Use getmaxy(...) instead of multiline_status_bar to handle command line
	 * mode, which doesn't use this module to show multiline messages. */
	if(cfg.display_statusline && getmaxy(status_bar) > 1)
	{
		touchwin(stat_win);
		wnoutrefresh(stat_win);
		touchwin(status_bar);
		wnoutrefresh(status_bar);
	}
}

void
ui_view_reset_selection_and_reload(view_t *view)
{
	flist_sel_stash(view);
	load_saving_pos(view);
}

void
ui_view_reset_search_highlight(view_t *view)
{
	if(view->matches != 0)
	{
		view->matches = 0;
		ui_view_schedule_redraw(view);
	}
}

void
ui_views_reload_visible_filelists(void)
{
	if(curr_stats.preview.on)
	{
		load_saving_pos(curr_view);
	}
	else
	{
		ui_views_reload_filelists();
	}
}

void
ui_views_reload_filelists(void)
{
	load_saving_pos(curr_view);
	load_saving_pos(other_view);
}

void
ui_views_update_titles(void)
{
	ui_view_title_update(&lwin);
	ui_view_title_update(&rwin);
}

void
ui_view_title_update(view_t *view)
{
	const int gen_view = vle_mode_is(VIEW_MODE) && !curr_view->explore_mode;
	view_t *selected = gen_view ? other_view : curr_view;
	path_func pf = cfg.shorten_title_paths ? &replace_home_part : &path_identity;
	col_attr_t title_col;

	if(curr_stats.load_stage < 2 || !ui_view_is_visible(view))
	{
		return;
	}

	if(view == selected && !stats_silenced_ui())
	{
		char *const term_title = format_view_title(view, pf, /*consider_qv=*/1);
		term_title_update(term_title);
		free(term_title);
	}

	title_col = fixup_titles_attributes(view, view == selected);

	if(view_shows_tabline(view))
	{
		print_tabline(view->title, view, title_col, pf);
	}
	else
	{
		char *const title = format_view_title(view, pf, /*consider_qv=*/1);
		print_view_title(view, view == selected, title);
		wnoutrefresh(view->title);
		free(title);
	}

	if(view == curr_view && get_tabline_height() > 0)
	{
		print_tabline(tab_line, view, cfg.cs.color[TAB_LINE_COLOR], pf);
	}
}

/* Identity path function.  Returns its argument. */
static char *
path_identity(const char path[])
{
	return (char *)path;
}

/* Checks whether view displays list of pane tabs at the moment.  Returns
 * non-zero if so, otherwise zero is returned. */
static int
view_shows_tabline(const view_t *view)
{
	return cfg.pane_tabs
	    && !(curr_stats.preview.on && view == other_view)
	    && cfg.show_tab_line != STL_NEVER
	    && !(cfg.show_tab_line == STL_MULTIPLE && tabs_count(view) == 1);
}

/* Retrieves height of the tab line in lines.  Returns the height. */
static int
get_tabline_height(void)
{
	if(cfg.pane_tabs || cfg.show_tab_line == STL_NEVER)
	{
		return 0;
	}

	return (cfg.show_tab_line == STL_MULTIPLE && tabs_count(curr_view) == 1)
	     ? 0
	     : 1;
}

/* Prints title of the tab on specified curses window. */
static void
print_tabline(WINDOW *win, view_t *view, col_attr_t base_col, path_func pf)
{
	const int max_width = (vifm_testing() ? cfg.columns : getmaxx(win));

	ui_set_bg(win, &base_col, -1);
	werase(win);
	checked_wmove(win, 0, 0);

	if(is_null_or_empty(cfg.tab_line))
	{
		tab_line_info_t info = format_tab_labels(view, max_width, pf);

		int i;
		for(i = 0; i < info.count; ++i)
		{
			col_attr_t col = base_col;
			if(i == info.current - info.skipped)
			{
				cs_mix_colors(&col, &cfg.cs.color[TAB_LINE_SEL_COLOR]);
			}

			cline_print(&info.labels[i], win, &col);
			cline_dispose(&info.labels[i]);
		}
		free(info.labels);
	}
	else
	{
		char *fmt;
		if(vlua_handler_cmd(curr_stats.vlua, cfg.tab_line))
		{
			int other = (view == other_view);
			fmt = vlua_make_tab_line(curr_stats.vlua, cfg.tab_line, other, max_width);
		}
		else
		{
			fmt = strdup(cfg.tab_line);
		}

		if(fmt != NULL)
		{
			cline_t title = ma_expand_colored_custom(fmt, /*nmacros=*/0,
					/*macros=*/NULL, MA_OPT);
			free(fmt);

			cline_print(&title, win, &base_col);
			cline_dispose(&title);
		}
	}

	wnoutrefresh(win);
}

/* Computes layout of the tab line and formats tab labels.  Returns list of tab
 * labels along with supplementary information. */
static tab_line_info_t
format_tab_labels(view_t *view, int max_width, path_func pf)
{
	int i;
	int tab_count = tabs_count(view);
	int width_used = 0;

	int avg_width, spare_width;
	int min_widths[tab_count];
	compute_avg_width(&avg_width, &spare_width, min_widths, max_width, view, pf);

	int min_width = 0;
	for(i = 0; i < tab_count; ++i)
	{
		min_width += min_widths[i];
	}

	cline_t *tab_labels = NULL;
	DA_INSTANCE(tab_labels);

	int current_tab = -1;
	int skipped_tabs = 0;
	tab_info_t tab_info;
	for(i = 0; tabs_get(view, i, &tab_info) && width_used < max_width; ++i)
	{
		int current = (tab_info.view == view);
		if(current)
		{
			current_tab = i;
		}
		else if(current_tab == -1 && min_width > max_width)
		{
			/* Skip a tab that precedes the current one because it doesn't fit in. */
			min_width -= min_widths[i];
			spare_width = max_width - min_width;
			++skipped_tabs;
			continue;
		}

		tab_title_info_t title_info = make_tab_title_info(&tab_info, pf, i,
				current);
		cline_t prefix = format_tab_part(cfg.tab_prefix, &title_info);
		cline_t title = make_tab_title(&title_info);
		cline_t suffix = format_tab_part(cfg.tab_suffix, &title_info);
		dispose_tab_title_info(&title_info);

		const int width_needed = title.attrs_len;
		const int extra_width = prefix.attrs_len + suffix.attrs_len;
		int width = max_width;

		if(!current)
		{
			width = tab_info.last ? (max_width - width_used)
			                      : MIN(avg_width, extra_width + width_needed);
		}

		if(width < width_needed + extra_width)
		{
			if(spare_width > 0)
			{
				width += 1;
				--spare_width;
			}

			cline_left_ellipsis(&title, MAX(0, width - extra_width),
					curr_stats.ellipsis);
		}

		int real_width = prefix.attrs_len + title.attrs_len + suffix.attrs_len;

		if(width < real_width && max_width - width_used >= real_width)
		{
			width = real_width;
		}
		if(width >= real_width)
		{
			cline_t *tab_label = DA_EXTEND(tab_labels);
			if(tab_label != NULL)
			{
				*tab_label = cline_steal(&prefix);
				cline_append(tab_label, &title);
				cline_append(tab_label, &suffix);
				DA_COMMIT(tab_labels);
			}
		}

		width_used += real_width;

		cline_dispose(&prefix);
		cline_dispose(&title);
		cline_dispose(&suffix);
	}

	tab_line_info_t result = {
		.labels = tab_labels,
		.count = DA_SIZE(tab_labels),
		.skipped = skipped_tabs,
		.current = current_tab,
	};
	return result;
}

/* Computes average width of tab tips as well as number of spare character
 * positions. */
static void
compute_avg_width(int *avg_width, int *spare_width, int min_widths[],
		int max_width, view_t *view, path_func pf)
{
	int left = max_width;
	int widths[tabs_count(view)];
	int i;
	tab_info_t tab_info;

	*avg_width = 0;
	*spare_width = 0;

	for(i = 0; tabs_get(view, i, &tab_info); ++i)
	{
		int current = (tab_info.view == view);

		tab_title_info_t title_info = make_tab_title_info(&tab_info, pf, i,
				current);
		cline_t prefix = format_tab_part(cfg.tab_prefix, &title_info);
		cline_t title = make_tab_title(&title_info);
		cline_t suffix = format_tab_part(cfg.tab_suffix, &title_info);
		dispose_tab_title_info(&title_info);

		widths[i] = prefix.attrs_len + title.attrs_len + suffix.attrs_len;
		min_widths[i] = prefix.attrs_len + suffix.attrs_len;

		cline_dispose(&prefix);
		cline_dispose(&title);
		cline_dispose(&suffix);

		if(current)
		{
			min_widths[i] = MIN(max_width, widths[i]);

			if(tabs_count(view) != 1)
			{
				int tab_count = tabs_count(view);

				left = MAX(max_width - widths[i], 0);
				*avg_width = left/(tab_count - 1);
				*spare_width = left%(tab_count - 1);
			}
		}
	}

	int new_avg_width = *avg_width;
	do
	{
		int well_used_width = 0;
		int truncated_count = 0;
		*avg_width = new_avg_width;
		for(i = 0; tabs_get(view, i, &tab_info); ++i)
		{
			if(tab_info.view != view)
			{
				if(widths[i] <= *avg_width)
				{
					well_used_width += widths[i];
				}
				else
				{
					++truncated_count;
				}
			}
		}
		if(truncated_count == 0)
		{
			break;
		}
		new_avg_width = (left - well_used_width)/truncated_count;
		*spare_width = (left - well_used_width)%truncated_count;
	}
	while(new_avg_width != *avg_width);
}

/* Gets title of the tab.  Returns colored line. */
TSTATIC cline_t
make_tab_title(const tab_title_info_t *title_info)
{
	if(!is_null_or_empty(cfg.tab_label))
	{
		return format_tab_part(cfg.tab_label, title_info);
	}

	cline_t result = cline_make();

	if(is_null_or_empty(title_info->name))
	{
		replace_string(&result.line, title_info->escaped_view_title);
	}
	else
	{
		replace_string(&result.line, title_info->name);
	}

	result.line_len = strlen(result.line);
	cline_finish(&result);
	return result;
}

/* Produces and stores information needed to format tab title.  Returns the
 * information. */
TSTATIC tab_title_info_t
make_tab_title_info(const tab_info_t *tab_info, path_func pf, int tab_num,
		int current_tab)
{
	view_t *view = tab_info->view;

	if(cfg.tail_tab_line_paths)
	{
		/* We can just do the replacement, because shortening home part doesn't make
		 * sense for a single path entry. */
		pf = &get_last_path_component;
	}

	char path[PATH_MAX + 1];
	if(view->explore_mode)
	{
		get_current_full_path(view, sizeof(path), path);
	}
	else
	{
		copy_str(path, sizeof(path), flist_get_dir(view));
	}

	tab_title_info_t result = {
		.current = current_tab,
	};

	const char *custom_title = "";
	if(flist_custom_active(view))
	{
		custom_title = view->custom.title;
		result.tree = cv_tree(view->custom.type);
	}

	result.name = escape_unreadable(tab_info->name == NULL ? "" : tab_info->name);
	result.escaped_path = escape_unreadable(path);
	result.cv_title = escape_unreadable(custom_title);

	char *view_title = format_view_title(view, pf, /*consider_qv=*/1);
	result.escaped_view_title = escape_unreadable(view_title);
	free(view_title);

	result.num = format_str("%d", tab_num + 1);

	return result;
}

/* Frees information used to format tab title. */
TSTATIC void
dispose_tab_title_info(tab_title_info_t *title_info)
{
	free(title_info->num);
	free(title_info->escaped_view_title);
	free(title_info->cv_title);
	free(title_info->escaped_path);
	free(title_info->name);
}

/* Formats part of a tab label.  Returns colored line. */
static cline_t
format_tab_part(const char fmt[], const tab_title_info_t *title_info)
{
	custom_macro_t macros[] = {
		{ .letter = 'n', .value = title_info->name },
		{ .letter = 'N', .value = title_info->num },
		{ .letter = 't', .value = title_info->escaped_view_title },
		{ .letter = 'p', .value = title_info->escaped_path,
			.expand_mods = 1, .parent = "/" },
		{ .letter = 'c', .value = title_info->cv_title },

		{ .letter = 'C', .value = (title_info->current ? "*" : ""),
		  .flag = 1},
		{ .letter = 'T', .value = (title_info->tree ? "*" : ""),
			.flag = 1 },
	};

	cline_t title = ma_expand_colored_custom(fmt, ARRAY_LEN(macros), macros,
			MA_OPT);

	return title;
}

char *
ui_view_make_title(const view_t *view)
{
	path_func pf = cfg.shorten_title_paths ? &replace_home_part : &path_identity;
	return format_view_title(view, pf, /*consider_qv=*/0);
}

/* Formats title for the view.  The pf function will be applied to full paths.
 * Returns newly allocated string, which should be freed by the caller, or NULL
 * if there is not enough memory. */
static char *
format_view_title(const view_t *view, path_func pf, int consider_qv)
{
	char *unescaped_title;

	if(view->explore_mode)
	{
		char full_path[PATH_MAX + 1];
		get_current_full_path(view, sizeof(full_path), full_path);
		unescaped_title = strdup(pf(full_path));
	}
	else if(consider_qv && curr_stats.preview.on && view == other_view)
	{
		const char *const viewer = modview_detached_get_viewer();
		if(viewer != NULL)
		{
			unescaped_title = format_str("Command: %s", viewer);
		}
		else
		{
			unescaped_title = format_str("File: %s",
					get_current_file_name(curr_view));
		}
	}
	else if(flist_custom_active(view))
	{
		const char *tree_mark = (cv_tree(view->custom.type) ? "[tree]" : "");
		const char *path = pf(view->custom.orig_dir);
		if(view->custom.title[0] == '\0')
		{
			unescaped_title = format_str("%s @ %s", tree_mark, path);
		}
		else
		{
			unescaped_title = format_str("%s[%s] @ %s", tree_mark, view->custom.title,
					path);
		}
	}
	else
	{
		unescaped_title = strdup(pf(view->curr_dir));
	}

	char *escaped_title = escape_unreadable(unescaped_title);
	free(unescaped_title);
	return escaped_title;
}

/* Prints view title (which can be changed for printing).  Takes care of setting
 * correct attributes. */
static void
print_view_title(const view_t *view, int active_view, char title[])
{
	char *ellipsis;

	const size_t title_width = getmaxx(view->title);
	if(title_width == (size_t)-1)
	{
		return;
	}

	werase(view->title);

	if (cfg.ellipsis_position != 0)
	{
		ellipsis = cfg.ellipsis_position < 0
		         ? left_ellipsis(title, title_width, curr_stats.ellipsis)
		         : right_ellipsis(title, title_width, curr_stats.ellipsis);
	}
	else
	{
		ellipsis = active_view
		         ? left_ellipsis(title, title_width, curr_stats.ellipsis)
		         : right_ellipsis(title, title_width, curr_stats.ellipsis);
	}

	wprint(view->title, ellipsis);
	free(ellipsis);
}

/* Updates attributes for view titles and top line.  Returns base color used for
 * the title. */
static col_attr_t
fixup_titles_attributes(const view_t *view, int active_view)
{
	col_attr_t col = cfg.cs.color[TOP_LINE_COLOR];

	if(view->title == NULL)
	{
		/* Workaround for tests. */
		return col;
	}

	if(active_view)
	{
		cs_mix_colors(&col, &cfg.cs.color[TOP_LINE_SEL_COLOR]);

		ui_set_bg(view->title, &col, -1);
		ui_set_attr(view->title, &col, -1);
	}
	else
	{
		ui_set_bg(view->title, &col, cfg.cs.pair[TOP_LINE_COLOR]);
		ui_set_attr(view->title, &col, cfg.cs.pair[TOP_LINE_COLOR]);

		ui_set_bg(mtop_line, &col, cfg.cs.pair[TOP_LINE_COLOR]);
		werase(mtop_line);
	}

	return col;
}

int
ui_view_sort_list_contains(const signed char sort[SK_COUNT], char key)
{
	int i = -1;
	while(++i < SK_COUNT)
	{
		const int sort_key = abs(sort[i]);
		if(sort_key > SK_LAST)
		{
			return 0;
		}
		else if(sort_key == key)
		{
			return 1;
		}
	}
	return 0;
}

void
ui_view_sort_list_ensure_well_formed(view_t *view, signed char sort_keys[])
{
	int found_name_key = 0;
	int i = -1;

	while(++i < SK_COUNT)
	{
		const int sort_key = abs(sort_keys[i]);
		if(sort_key > SK_LAST)
		{
			break;
		}
		else if(sort_key == SK_BY_NAME || sort_key == SK_BY_INAME)
		{
			found_name_key = 1;
		}
	}

	if(!found_name_key && i < SK_COUNT &&
			(!flist_custom_active(view) ||
			 (sort_keys == view->sort && !ui_view_unsorted(view))))
	{
		sort_keys[i++] = SK_DEFAULT;
	}

	if(i < SK_COUNT)
	{
		memset(&sort_keys[i], SK_NONE, SK_COUNT - i);
	}
}

signed char *
ui_view_sort_list_get(const view_t *view, const signed char sort[])
{
	return (flist_custom_active(view) && ui_view_unsorted(view))
	     ? (signed char *)view->custom.sort
	     : (signed char *)sort;
}

int
ui_view_displays_numbers(const view_t *view)
{
	return view->num_type != NT_NONE && ui_view_displays_columns(view);
}

int
ui_view_is_visible(const view_t *view)
{
	return curr_stats.number_of_windows == 2 || curr_view == view;
}

int
ui_view_displays_columns(const view_t *view)
{
	return !view->ls_view
	    || is_in_miller_view(view)
	    || is_forced_list_mode(view);
}

int
ui_view_main_area(const view_t *view)
{
	return ui_view_main_padded(view) - (cfg.extra_padding ? 2 : 0);
}

int
ui_view_main_padded(const view_t *view)
{
	return view->window_cols
	     - ui_view_left_reserved(view)
	     - ui_view_right_reserved(view);
}

int
ui_view_left_reserved(const view_t *view)
{
	const int total = view->miller_ratios[0] + view->miller_ratios[1]
	                + view->miller_ratios[2];
	return is_in_miller_view(view)
	     ? (view->window_cols*view->miller_ratios[0])/total : 0;
}

int
ui_view_right_reserved(const view_t *view)
{
	dir_entry_t *const entry = get_current_entry(view);

	if(!is_in_miller_view(view) || is_parent_dir(entry->name))
	{
		return 0;
	}

	if(view->miller_preview != MP_ALL &&
			fentry_is_dir(entry) != (view->miller_preview == MP_DIRS))
	{
		return 0;
	}

	const int total = view->miller_ratios[0] + view->miller_ratios[1]
	                + view->miller_ratios[2];
	return (view->window_cols*view->miller_ratios[2])/total;
}

/* Whether miller columns should be displayed.  Returns non-zero if so,
 * otherwise zero is returned. */
static int
is_in_miller_view(const view_t *view)
{
	return view->miller_view
	    && !flist_custom_active(view);
}

/* Whether view must display straight single-column file list.  Returns non-zero
 * if so, otherwise zero is returned. */
static int
is_forced_list_mode(const view_t *view)
{
	return flist_custom_active(view)
	    && (cv_tree(view->custom.type) || cv_compare(view->custom.type));
}

int
ui_qv_left(const view_t *view)
{
	const int with_margin = (!curr_stats.preview.clearing && cfg.extra_padding);
	return with_margin ? 1 : 0;
}

int
ui_qv_top(const view_t *view)
{
	const int with_margin = (!curr_stats.preview.clearing && cfg.extra_padding);
	return with_margin ? 1 : 0;
}

int
ui_qv_height(const view_t *view)
{
	const int with_margin = (!curr_stats.preview.clearing && cfg.extra_padding);
	return with_margin ? view->window_rows - 2 : view->window_rows;
}

int
ui_qv_width(const view_t *view)
{
	const int with_margin = (!curr_stats.preview.clearing && cfg.extra_padding);
	return with_margin ? view->window_cols - 2 : view->window_cols;
}

void
ui_qv_cleanup_if_needed(void)
{
	if(curr_stats.preview.on && curr_stats.preview.kind != VK_TEXTUAL)
	{
		qv_cleanup(other_view, curr_stats.preview.cleanup_cmd);
	}

	if(ui_view_is_visible(&lwin))
	{
		fview_clear_miller_preview(&lwin);
	}
	if(ui_view_is_visible(&rwin))
	{
		fview_clear_miller_preview(&rwin);
	}
}

void
ui_hide_graphics(void)
{
	if(!modes_is_menu_like())
	{
		ui_qv_cleanup_if_needed();
		modview_hide_graphics();
	}
}

void
ui_invalidate_cs(const col_scheme_t *cs)
{
	int i;
	tab_info_t tab_info;
	for(i = 0; tabs_enum_all(i, &tab_info); ++i)
	{
		if(ui_view_get_cs(tab_info.view) == cs)
		{
			fview_reset_cs(tab_info.view);
		}
	}
}

const col_scheme_t *
ui_view_get_cs(const view_t *view)
{
	return view->local_cs ? &view->cs : &cfg.cs;
}

void
ui_view_erase(view_t *view, int use_global_cs)
{
	const col_scheme_t *cs = (use_global_cs ? &cfg.cs : ui_view_get_cs(view));
	col_attr_t col = ui_get_win_color(view, cs);
	ui_set_bg(view->win, &col, -1);
	werase(view->win);
}

col_attr_t
ui_get_win_color(const view_t *view, const col_scheme_t *cs)
{
	col_attr_t col = cs->color[WIN_COLOR];
	if(view == other_view && window_shows_dirlist(view))
	{
		cs_mix_colors(&col, &cs->color[OTHER_WIN_COLOR]);
	}
	return col;
}

int
ui_view_unsorted(const view_t *view)
{
	return cv_unsorted(view->custom.type);
}

void
ui_shutdown(void)
{
	if(curr_stats.load_stage >= 0 && !vifm_testing() && !isendwin())
	{
		ui_hide_graphics();
		def_prog_mode();
		endwin();
	}
}

void
ui_pause(void)
{
#ifdef _WIN32
	/* Refresh the window, because otherwise curses redraws the screen on call to
	 * `compat_wget_wch()` (why does it do this?). */
	use_wrefresh(inf_delay_window);
#endif

	/* Show previous screen state. */
	ui_shutdown();

#ifndef _WIN32
	/* Yet restore program mode to read input without waiting for Enter. */
	reset_prog_mode();
	/* Refresh the window, because otherwise curses redraws the screen on call to
	 * `compat_wget_wch()` (why does it do this?). */
	wnoutrefresh(inf_delay_window);
#endif

	/* Ignore window resize. */
	wint_t pressed;
	do
	{
		/* Nothing. */
	}
	while(compat_wget_wch(inf_delay_window, &pressed) != ERR &&
			pressed == KEY_RESIZE);

	/* Redraw UI to account for all things including graphical preview. */
	stats_redraw_later();
}

void
ui_set_bg(WINDOW *win, const col_attr_t *col, int pair)
{
	if(curr_stats.load_stage >= 1)
	{
		const cchar_t bg = cs_color_to_cchar(col, pair);
		wbkgrndset(win, &bg);
	}
}

void
ui_set_attr(WINDOW *win, const col_attr_t *col, int pair)
{
	/* win can be NULL in tests. */
	if(curr_stats.load_stage < 1 || win == NULL)
	{
		return;
	}

	if(pair < 0)
	{
		pair = cs_load_color(col);
	}

	/*
	 * Compiler complains about unused result of comma operator, because
	 * wattr_set() is a macro and it uses comma to evaluate multiple expressions.
	 * So cast result to void.
	 *
	 * Color pair is specified twice to support both older and newer ncurses
	 * versions in one call.  MIN() might not be necessary for older versions with
	 * at most 32768 color pairs, but it won't hurt and makes the API constraints
	 * clearer.
	 */
	(void)wattr_set(win, cs_color_get_attr(col), MIN(pair, SHRT_MAX), &pair);
}

void
ui_drop_attr(WINDOW *win)
{
	if(curr_stats.load_stage >= 1)
	{
		wattrset(win, 0);
	}
}

void
ui_pass_through(const strlist_t *lines, WINDOW *win, int x, int y)
{
	/* Position hardware cursor on the screen. */
	checked_wmove(win, y, x);
	use_wrefresh(win);

	int i;
	for(i = 0; i < lines->nitems; ++i)
	{
		puts(lines->items[i]);
	}

	/* Make curses synchronize its idea about where cursor is with the terminal
	 * state. */
	touchwin(status_bar);
	checked_wmove(status_bar, 0, 0);
	use_wrefresh(status_bar);
}

int
ui_map_column_name(const char name[])
{
	int pos =
		string_array_pos((char **)&sort_enum[1], ARRAY_LEN(sort_enum) - 1, name);
	if(pos >= 0)
	{
		return 1 + pos;
	}

	return vlua_viewcolumn_map(curr_stats.vlua, name);
}

void
ui_view_schedule_redraw(view_t *view)
{
	pthread_mutex_lock(view->timestamps_mutex);
	view->need_redraw = 1;
	pthread_mutex_unlock(view->timestamps_mutex);
}

void
ui_view_schedule_reload(view_t *view)
{
	pthread_mutex_lock(view->timestamps_mutex);
	view->need_reload = 1;
	pthread_mutex_unlock(view->timestamps_mutex);
}

void
ui_view_redrawn(view_t *view)
{
	pthread_mutex_lock(view->timestamps_mutex);
	view->need_redraw = 0;
	pthread_mutex_unlock(view->timestamps_mutex);
}

UiUpdateEvent
ui_view_query_scheduled_event(view_t *view)
{
	UiUpdateEvent event;

	pthread_mutex_lock(view->timestamps_mutex);

	if(view->need_reload)
	{
		event = UUE_RELOAD;
	}
	else if(view->need_redraw)
	{
		event = UUE_REDRAW;
	}
	else
	{
		event = UUE_NONE;
	}

	view->need_redraw = 0;
	view->need_reload = 0;

	pthread_mutex_unlock(view->timestamps_mutex);

	return event;
}

/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
/* vim: set cinoptions+=t0 filetype=c : */
