1 /* vifm
2  * Copyright (C) 2015 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 "more.h"
20 
21 #include <curses.h>
22 
23 #include <assert.h> /* assert() */
24 #include <limits.h> /* INT_MAX */
25 #include <stddef.h> /* NULL size_t */
26 #include <string.h> /* strdup() */
27 
28 #include "../cfg/config.h"
29 #include "../compat/curses.h"
30 #include "../compat/reallocarray.h"
31 #include "../engine/keys.h"
32 #include "../engine/mode.h"
33 #include "../menus/menus.h"
34 #include "../ui/ui.h"
35 #include "../utils/macros.h"
36 #include "../utils/str.h"
37 #include "../utils/utf8.h"
38 #include "cmdline.h"
39 #include "modes.h"
40 #include "wk.h"
41 
42 /* Provide more readable definitions of key codes. */
43 
44 static void calc_vlines_wrapped(void);
45 static void leave_more_mode(void);
46 static const char * get_text_beginning(void);
47 static void draw_all(const char text[]);
48 static void cmd_leave(key_info_t key_info, keys_info_t *keys_info);
49 static void cmd_ctrl_l(key_info_t key_info, keys_info_t *keys_info);
50 static void cmd_colon(key_info_t key_info, keys_info_t *keys_info);
51 static void cmd_bottom(key_info_t key_info, keys_info_t *keys_info);
52 static void cmd_top(key_info_t key_info, keys_info_t *keys_info);
53 static void cmd_down_line(key_info_t key_info, keys_info_t *keys_info);
54 static void cmd_up_line(key_info_t key_info, keys_info_t *keys_info);
55 static void cmd_down_screen(key_info_t key_info, keys_info_t *keys_info);
56 static void cmd_up_screen(key_info_t key_info, keys_info_t *keys_info);
57 static void cmd_down_page(key_info_t key_info, keys_info_t *keys_info);
58 static void cmd_up_page(key_info_t key_info, keys_info_t *keys_info);
59 static void goto_vline(int line);
60 static void goto_vline_below(int by);
61 static void goto_vline_above(int by);
62 
63 /* Whether UI was redrawn while this mode was active. */
64 static int was_redraw;
65 
66 /* Text displayed by the mode. */
67 static char *text;
68 /* (first virtual line, screen width, offset in text) triples per real line. */
69 static int (*data)[3];
70 
71 /* Number of virtual lines. */
72 static int nvlines;
73 
74 /* Current real line number. */
75 static int curr_line;
76 /* Current virtual line number. */
77 static int curr_vline;
78 
79 /* Width of the area text is printed on. */
80 static int viewport_width;
81 /* Height of the area text is printed on. */
82 static int viewport_height;
83 
84 /* List of builtin keys. */
85 static keys_add_info_t builtin_keys[] = {
86 	{WK_C_c,   {{&cmd_leave},     .descr = "leave more mode"}},
87 	{WK_C_j,   {{&cmd_down_line}, .descr = "scroll one line down"}},
88 	{WK_C_l,   {{&cmd_ctrl_l},    .descr = "redraw"}},
89 
90 	{WK_COLON, {{&cmd_colon},       .descr = "switch to cmdline mode"}},
91 	{WK_ESC,   {{&cmd_leave},       .descr = "leave more mode"}},
92 	{WK_CR,    {{&cmd_leave},       .descr = "leave more mode"}},
93 	{WK_SPACE, {{&cmd_down_screen}, .descr = "scroll one screen down"}},
94 
95 	{WK_G,     {{&cmd_bottom},      .descr = "scroll to the end"}},
96 	{WK_b,     {{&cmd_up_screen},   .descr = "scroll one screen up"}},
97 	{WK_d,     {{&cmd_down_page},   .descr = "scroll page down"}},
98 	{WK_f,     {{&cmd_down_screen}, .descr = "scroll one screen down"}},
99 	{WK_g,     {{&cmd_top},         .descr = "scroll to the beginning"}},
100 	{WK_j,     {{&cmd_down_line},   .descr = "scroll one line down"}},
101 	{WK_k,     {{&cmd_up_line},     .descr = "scroll one line up"}},
102 	{WK_q,     {{&cmd_leave},       .descr = "leave more mode"}},
103 	{WK_u,     {{&cmd_up_page},     .descr = "scroll page up"}},
104 
105 #ifdef ENABLE_EXTENDED_KEYS
106 	{{K(KEY_BACKSPACE)}, {{&cmd_up_line},     .descr = "scroll one line up"}},
107 	{{K(KEY_DOWN)},      {{&cmd_down_line},   .descr = "scroll one line down"}},
108 	{{K(KEY_UP)},        {{&cmd_up_line},     .descr = "scroll one line up"}},
109 	{{K(KEY_HOME)},      {{&cmd_top},         .descr = "scroll to the beginning"}},
110 	{{K(KEY_END)},       {{&cmd_bottom},      .descr = "scroll to the end"}},
111 	{{K(KEY_NPAGE)},     {{&cmd_down_screen}, .descr = "scroll one screen down"}},
112 	{{K(KEY_PPAGE)},     {{&cmd_up_screen},   .descr = "scroll one screen up"}},
113 #endif /* ENABLE_EXTENDED_KEYS */
114 };
115 
116 void
modmore_init(void)117 modmore_init(void)
118 {
119 	int ret_code;
120 
121 	ret_code = vle_keys_add(builtin_keys, ARRAY_LEN(builtin_keys), MORE_MODE);
122 	assert(ret_code == 0 && "Failed to initialize more mode keys.");
123 
124 	(void)ret_code;
125 }
126 
127 void
modmore_enter(const char txt[])128 modmore_enter(const char txt[])
129 {
130 	text = strdup(txt);
131 	curr_line = 0;
132 	curr_vline = 0;
133 
134 	vle_mode_set(MORE_MODE, VMT_PRIMARY);
135 
136 	modmore_redraw();
137 	was_redraw = 0;
138 }
139 
140 void
modmore_abort(void)141 modmore_abort(void)
142 {
143 	leave_more_mode();
144 }
145 
146 /* Recalculates virtual lines of a view with line wrapping. */
147 static void
calc_vlines_wrapped(void)148 calc_vlines_wrapped(void)
149 {
150 	const char *p;
151 	char *q;
152 
153 	int i;
154 	const int nlines = count_lines(text, INT_MAX);
155 
156 	data = reallocarray(NULL, nlines, sizeof(*data));
157 
158 	nvlines = 0;
159 
160 	p = text;
161 	q = text - 1;
162 
163 	for(i = 0; i < nlines; ++i)
164 	{
165 		char saved_char;
166 		q = until_first(q + 1, '\n');
167 		saved_char = *q;
168 		*q = '\0';
169 
170 		data[i][0] = nvlines++;
171 		data[i][1] = utf8_strsw_with_tabs(p, cfg.tab_stop);
172 		data[i][2] = p - text;
173 		nvlines += data[i][1]/viewport_width;
174 
175 		*q = saved_char;
176 		p = q + 1;
177 	}
178 }
179 
180 /* Quits the mode restoring previously active one. */
181 static void
leave_more_mode(void)182 leave_more_mode(void)
183 {
184 	update_string(&text, NULL);
185 	free(data);
186 	data = NULL;
187 
188 	vle_mode_set(NORMAL_MODE, VMT_PRIMARY);
189 
190 	if(was_redraw)
191 	{
192 		update_screen(UT_FULL);
193 	}
194 	else
195 	{
196 		update_all_windows();
197 	}
198 }
199 
200 void
modmore_redraw(void)201 modmore_redraw(void)
202 {
203 	if(resize_for_menu_like() != 0)
204 	{
205 		return;
206 	}
207 	wresize(status_bar, 1, getmaxx(stdscr));
208 
209 	viewport_width = getmaxx(menu_win);
210 	viewport_height = getmaxy(menu_win);
211 	calc_vlines_wrapped();
212 	goto_vline(curr_vline);
213 
214 	draw_all(get_text_beginning());
215 	checked_wmove(menu_win, 0, 0);
216 
217 	was_redraw = 1;
218 }
219 
220 /* Retrieves beginning of the text that should be displayed.  Returns the
221  * beginning. */
222 static const char *
get_text_beginning(void)223 get_text_beginning(void)
224 {
225 	int skipped = 0;
226 	const char *text_piece = text + data[curr_line][2];
227 	/* Skip invisible virtual lines (those that are above current one). */
228 	while(skipped < curr_vline - data[curr_line][0])
229 	{
230 		text_piece += utf8_strsnlen(text_piece, viewport_width);
231 		++skipped;
232 	}
233 	return text_piece;
234 }
235 
236 /* Draws all components of the mode onto the screen. */
237 static void
draw_all(const char text[])238 draw_all(const char text[])
239 {
240 	/* Setup correct attributes for the windows. */
241 	ui_set_attr(menu_win, &cfg.cs.color[WIN_COLOR], cfg.cs.pair[WIN_COLOR]);
242 	ui_set_attr(status_bar, &cfg.cs.color[CMD_LINE_COLOR],
243 			cfg.cs.pair[CMD_LINE_COLOR]);
244 
245 	/* Clean up everything. */
246 	werase(menu_win);
247 	werase(status_bar);
248 
249 	/* Draw the text. */
250 	checked_wmove(menu_win, 0, 0);
251 	wprint(menu_win, text);
252 
253 	/* Draw status line. */
254 	checked_wmove(status_bar, 0, 0);
255 	mvwprintw(status_bar, 0, 0, "-- More -- %d-%d/%d", curr_vline + 1,
256 			MIN(nvlines, curr_vline + viewport_height), nvlines);
257 
258 	/* Inform curses of the changes. */
259 	wnoutrefresh(menu_win);
260 	wnoutrefresh(status_bar);
261 
262 	/* Apply all changes. */
263 	doupdate();
264 
265 	was_redraw = 1;
266 }
267 
268 /* Leaves the mode. */
269 static void
cmd_leave(key_info_t key_info,keys_info_t * keys_info)270 cmd_leave(key_info_t key_info, keys_info_t *keys_info)
271 {
272 	leave_more_mode();
273 }
274 
275 /* Redraws the mode. */
276 static void
cmd_ctrl_l(key_info_t key_info,keys_info_t * keys_info)277 cmd_ctrl_l(key_info_t key_info, keys_info_t *keys_info)
278 {
279 	modmore_redraw();
280 }
281 
282 /* Switches to command-line mode. */
283 static void
cmd_colon(key_info_t key_info,keys_info_t * keys_info)284 cmd_colon(key_info_t key_info, keys_info_t *keys_info)
285 {
286 	leave_more_mode();
287 	modcline_enter(CLS_COMMAND, "", NULL);
288 }
289 
290 /* Navigate to the bottom. */
291 static void
cmd_bottom(key_info_t key_info,keys_info_t * keys_info)292 cmd_bottom(key_info_t key_info, keys_info_t *keys_info)
293 {
294 	goto_vline(INT_MAX);
295 }
296 
297 /* Navigate to the top. */
298 static void
cmd_top(key_info_t key_info,keys_info_t * keys_info)299 cmd_top(key_info_t key_info, keys_info_t *keys_info)
300 {
301 	goto_vline(0);
302 }
303 
304 /* Go one line below. */
305 static void
cmd_down_line(key_info_t key_info,keys_info_t * keys_info)306 cmd_down_line(key_info_t key_info, keys_info_t *keys_info)
307 {
308 	goto_vline(curr_vline + 1);
309 }
310 
311 /* Go one line above. */
312 static void
cmd_up_line(key_info_t key_info,keys_info_t * keys_info)313 cmd_up_line(key_info_t key_info, keys_info_t *keys_info)
314 {
315 	goto_vline(curr_vline - 1);
316 }
317 
318 /* Go one screen below. */
319 static void
cmd_down_screen(key_info_t key_info,keys_info_t * keys_info)320 cmd_down_screen(key_info_t key_info, keys_info_t *keys_info)
321 {
322 	goto_vline(curr_vline + viewport_height);
323 }
324 
325 /* Go one screen above. */
326 static void
cmd_up_screen(key_info_t key_info,keys_info_t * keys_info)327 cmd_up_screen(key_info_t key_info, keys_info_t *keys_info)
328 {
329 	goto_vline(curr_vline - viewport_height);
330 }
331 
332 /* Go one page (half of the screen) below. */
333 static void
cmd_down_page(key_info_t key_info,keys_info_t * keys_info)334 cmd_down_page(key_info_t key_info, keys_info_t *keys_info)
335 {
336 	goto_vline(curr_vline + viewport_height/2);
337 }
338 
339 /* Go one page (half of the screen) above. */
340 static void
cmd_up_page(key_info_t key_info,keys_info_t * keys_info)341 cmd_up_page(key_info_t key_info, keys_info_t *keys_info)
342 {
343 	goto_vline(curr_vline - viewport_height/2);
344 }
345 
346 /* Navigates to the specified virtual line taking care of values that are out of
347  * range. */
348 static void
goto_vline(int line)349 goto_vline(int line)
350 {
351 	const int max_vline = nvlines - viewport_height;
352 
353 	if(line > max_vline)
354 	{
355 		line = max_vline;
356 	}
357 	if(line < 0)
358 	{
359 		line = 0;
360 	}
361 
362 	if(curr_vline == line)
363 	{
364 		return;
365 	}
366 
367 	if(line > curr_vline)
368 	{
369 		goto_vline_below(line - curr_vline);
370 	}
371 	else
372 	{
373 		goto_vline_above(curr_vline - line);
374 	}
375 
376 	modmore_redraw();
377 }
378 
379 /* Navigates by virtual lines below. */
380 static void
goto_vline_below(int by)381 goto_vline_below(int by)
382 {
383 	while(by-- > 0)
384 	{
385 		const int height = MAX(DIV_ROUND_UP(data[curr_line][1], viewport_width), 1);
386 		if(curr_vline + 1 >= data[curr_line][0] + height)
387 		{
388 			++curr_line;
389 		}
390 
391 		++curr_vline;
392 	}
393 }
394 
395 /* Navigates by virtual lines above. */
396 static void
goto_vline_above(int by)397 goto_vline_above(int by)
398 {
399 	while(by-- > 0)
400 	{
401 		if(curr_vline - 1 < data[curr_line][0])
402 		{
403 			--curr_line;
404 		}
405 
406 		--curr_vline;
407 	}
408 }
409 
410 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
411 /* vim: set cinoptions+=t0 filetype=c : */
412