1 /* vifm
2  * Copyright (C) 2014 xaizek.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #include "statusbar.h"
20 
21 #include <curses.h> /* mvwin() werase() */
22 
23 #include <assert.h> /* assert() */
24 #include <stdarg.h> /* va_list va_start() va_end() */
25 #include <stdlib.h> /* free() */
26 #include <string.h> /* strcat() strchr() strcmp() strcpy() strdup() strncpy() */
27 
28 #include "../cfg/config.h"
29 #include "../modes/modes.h"
30 #include "../modes/more.h"
31 #include "../utils/macros.h"
32 #include "../utils/str.h"
33 #include "../utils/utf8.h"
34 #include "../status.h"
35 #include "statusline.h"
36 #include "ui.h"
37 
38 static void vstatus_bar_messagef(int error, const char format[], va_list ap);
39 static void status_bar_message(const char message[], int error);
40 static void truncate_with_ellipsis(const char msg[], size_t width,
41 		char buffer[]);
42 
43 /* Message displayed on multi-line or too long status bar message. */
44 static const char PRESS_ENTER_MSG[] = "Press ENTER or type command to continue";
45 
46 /* Last message that was printed on the status bar. */
47 static char *last_message;
48 /* Whether status bar takes up more than single line on a screen. */
49 static int multiline_status_bar;
50 /* Whether status bar is currently in a locked state. */
51 static int is_locked;
52 
53 void
ui_sb_clear(void)54 ui_sb_clear(void)
55 {
56 	(void)ui_stat_reposition(1, 0);
57 
58 	werase(status_bar);
59 	wresize(status_bar, 1, getmaxx(stdscr) - FIELDS_WIDTH());
60 	mvwin(status_bar, getmaxy(stdscr) - 1, 0);
61 	wnoutrefresh(status_bar);
62 
63 	if(curr_stats.load_stage <= 2)
64 	{
65 		multiline_status_bar = 0;
66 		stats_refresh_later();
67 		return;
68 	}
69 
70 	if(multiline_status_bar)
71 	{
72 		multiline_status_bar = 0;
73 		update_screen(UT_FULL);
74 	}
75 	multiline_status_bar = 0;
76 }
77 
78 void
ui_sb_quick_msgf(const char format[],...)79 ui_sb_quick_msgf(const char format[], ...)
80 {
81 	va_list ap;
82 
83 	if(curr_stats.load_stage < 2 || status_bar == NULL)
84 	{
85 		return;
86 	}
87 
88 	va_start(ap, format);
89 
90 	checked_wmove(status_bar, 0, 0);
91 	werase(status_bar);
92 	vw_printw(status_bar, format, ap);
93 	wnoutrefresh(status_bar);
94 	doupdate();
95 
96 	va_end(ap);
97 }
98 
99 void
ui_sb_quick_msg_clear(void)100 ui_sb_quick_msg_clear(void)
101 {
102 	if(curr_stats.save_msg || ui_sb_multiline())
103 	{
104 		ui_sb_msg(NULL);
105 	}
106 	else
107 	{
108 		ui_sb_quick_msgf("%s", "");
109 	}
110 }
111 
112 void
ui_sb_msg(const char message[])113 ui_sb_msg(const char message[])
114 {
115 	status_bar_message(message, 0);
116 }
117 
118 void
ui_sb_msgf(const char format[],...)119 ui_sb_msgf(const char format[], ...)
120 {
121 	va_list ap;
122 
123 	va_start(ap, format);
124 
125 	vstatus_bar_messagef(0, format, ap);
126 
127 	va_end(ap);
128 }
129 
130 void
ui_sb_err(const char message[])131 ui_sb_err(const char message[])
132 {
133 	status_bar_message(message, 1);
134 }
135 
136 void
ui_sb_errf(const char message[],...)137 ui_sb_errf(const char message[], ...)
138 {
139 	va_list ap;
140 
141 	va_start(ap, message);
142 
143 	vstatus_bar_messagef(1, message, ap);
144 
145 	va_end(ap);
146 }
147 
148 static void
vstatus_bar_messagef(int error,const char format[],va_list ap)149 vstatus_bar_messagef(int error, const char format[], va_list ap)
150 {
151 	char buf[1024];
152 
153 	vsnprintf(buf, sizeof(buf), format, ap);
154 	status_bar_message(buf, error);
155 }
156 
157 static void
status_bar_message(const char msg[],int error)158 status_bar_message(const char msg[], int error)
159 {
160 	/* TODO: Refactor this function status_bar_message() */
161 
162 	static int err;
163 
164 	int len;
165 	int lines;
166 	int status_bar_lines;
167 	const char *out_msg;
168 	char truncated_msg[2048];
169 
170 	if(msg != NULL)
171 	{
172 		if(replace_string(&last_message, msg))
173 		{
174 			return;
175 		}
176 
177 		err = error;
178 
179 		stats_save_msg(last_message);
180 	}
181 	else
182 	{
183 		msg = last_message;
184 	}
185 
186 	/* We bail out here instead of right at the top to record the message to make
187 	 * it accessible in tests. */
188 	if(curr_stats.load_stage <= 0)
189 	{
190 		return;
191 	}
192 
193 	if(msg == NULL || is_locked)
194 	{
195 		return;
196 	}
197 
198 	len = getmaxx(stdscr);
199 	status_bar_lines = count_lines(msg, len);
200 
201 	lines = status_bar_lines;
202 	if(status_bar_lines > 1 || utf8_strsw(msg) > (size_t)getmaxx(status_bar))
203 	{
204 		++lines;
205 	}
206 
207 	out_msg = msg;
208 
209 	if(lines > 1)
210 	{
211 		if(cfg.trunc_normal_sb_msgs && !err && curr_stats.allow_sb_msg_truncation)
212 		{
213 			truncate_with_ellipsis(msg, getmaxx(stdscr) - FIELDS_WIDTH(),
214 					truncated_msg);
215 			out_msg = truncated_msg;
216 			lines = 1;
217 		}
218 		else
219 		{
220 			const int extra = DIV_ROUND_UP(ARRAY_LEN(PRESS_ENTER_MSG) - 1, len) - 1;
221 			lines += extra;
222 		}
223 	}
224 
225 	if(lines > getmaxy(stdscr))
226 	{
227 		modmore_enter(msg);
228 		return;
229 	}
230 
231 	(void)ui_stat_reposition(lines, 0);
232 	mvwin(status_bar, getmaxy(stdscr) - lines, 0);
233 	if(lines == 1)
234 	{
235 		wresize(status_bar, lines, getmaxx(stdscr) - FIELDS_WIDTH());
236 	}
237 	else
238 	{
239 		wresize(status_bar, lines, getmaxx(stdscr));
240 	}
241 	checked_wmove(status_bar, 0, 0);
242 
243 	if(err)
244 	{
245 		col_attr_t col = cfg.cs.color[CMD_LINE_COLOR];
246 		cs_mix_colors(&col, &cfg.cs.color[ERROR_MSG_COLOR]);
247 		ui_set_attr(status_bar, &col, -1);
248 	}
249 	else
250 	{
251 		ui_set_attr(status_bar, &cfg.cs.color[CMD_LINE_COLOR],
252 				cfg.cs.pair[CMD_LINE_COLOR]);
253 	}
254 	werase(status_bar);
255 
256 	wprint(status_bar, out_msg);
257 	multiline_status_bar = lines > 1;
258 	if(multiline_status_bar)
259 	{
260 		checked_wmove(status_bar,
261 				lines - DIV_ROUND_UP(ARRAY_LEN(PRESS_ENTER_MSG), len), 0);
262 		wclrtoeol(status_bar);
263 		if(lines < status_bar_lines)
264 			wprintw(status_bar, "%d of %d lines.  ", lines, status_bar_lines);
265 		wprintw(status_bar, "%s", PRESS_ENTER_MSG);
266 	}
267 
268 	ui_drop_attr(status_bar);
269 
270 	update_all_windows();
271 	/* This is needed because update_all_windows() doesn't call doupdate() if
272 	 * curr_stats.load_stage == 1. */
273 	doupdate();
274 }
275 
276 /* Truncates the msg to the width by placing ellipsis in the middle and writes
277  * result into the buffer. */
278 static void
truncate_with_ellipsis(const char msg[],size_t width,char buffer[])279 truncate_with_ellipsis(const char msg[], size_t width, char buffer[])
280 {
281 	const size_t screen_width = utf8_strsw(msg);
282 	const size_t ell_width = utf8_strsw(curr_stats.ellipsis);
283 	const size_t screen_left_width = (width - ell_width)/2;
284 	const size_t screen_right_width = (width - ell_width) - screen_left_width;
285 	const size_t left = utf8_nstrsnlen(msg, screen_left_width);
286 	const size_t right = utf8_nstrsnlen(msg, screen_width - screen_right_width);
287 	strncpy(buffer, msg, left);
288 	strcpy(buffer + left, curr_stats.ellipsis);
289 	strcat(buffer + left, msg + right);
290 	assert(utf8_strsw(buffer) == width);
291 }
292 
293 int
ui_sb_multiline(void)294 ui_sb_multiline(void)
295 {
296 	return multiline_status_bar;
297 }
298 
299 const char *
ui_sb_last(void)300 ui_sb_last(void)
301 {
302 	return last_message;
303 }
304 
305 void
ui_sb_lock(void)306 ui_sb_lock(void)
307 {
308 	assert(!is_locked && "Can't lock status bar that's already locked.");
309 	is_locked = 1;
310 }
311 
312 void
ui_sb_unlock(void)313 ui_sb_unlock(void)
314 {
315 	assert(is_locked && "Can't unlock status bar that's not locked.");
316 	is_locked = 0;
317 }
318 
319 int
ui_sb_locked(void)320 ui_sb_locked(void)
321 {
322 	return is_locked;
323 }
324 
325 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
326 /* vim: set cinoptions+=t0 filetype=c : */
327