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