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