1/*
2 * textbox.c - show a text box for messages, files or help
3 * Copyright (c) 1998,1999 Tim Janik
4 *                         Jaroslav Kysela <perex@perex.cz>
5 * Copyright (c) 2009      Clemens Ladisch <clemens@ladisch.de>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include "aconfig.h"
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <errno.h>
26#include CURSESINC
27#include <alsa/asoundlib.h>
28#include "gettext_curses.h"
29#include "utils.h"
30#include "die.h"
31#include "mem.h"
32#include "colors.h"
33#include "widget.h"
34#include "textbox.h"
35#include "bindings.h"
36
37static void create_text_box(const char *const *lines, unsigned int count,
38			    const char *title, int attrs);
39
40void show_error(const char *msg, int err)
41{
42	const char *lines[2];
43	unsigned int count;
44
45	lines[0] = msg;
46	count = 1;
47	if (err) {
48		lines[1] = strerror(err);
49		count = 2;
50	}
51	create_text_box(lines, count, _("Error"), attrs.errormsg);
52}
53
54void show_alsa_error(const char *msg, int err)
55{
56	const char *lines[2];
57	unsigned int count;
58
59	lines[0] = msg;
60	count = 1;
61	if (err < 0) {
62		lines[1] = snd_strerror(err);
63		count = 2;
64	}
65	create_text_box(lines, count, _("Error"), attrs.errormsg);
66}
67
68void show_textfile(const char *file_name)
69{
70	char *buf;
71	unsigned int file_size;
72	unsigned int line_count;
73	unsigned int i;
74	const char **lines;
75	const char *start_line;
76
77	buf = read_file(file_name, &file_size);
78	if (!buf) {
79		int err = errno;
80		buf = casprintf(_("Cannot open file \"%s\"."), file_name);
81		show_error(buf, err);
82		return;
83	}
84	line_count = 0;
85	for (i = 0; i < file_size; ++i)
86		line_count += buf[i] == '\n';
87	lines = ccalloc(line_count, sizeof *lines);
88	line_count = 0;
89	start_line = buf;
90	for (i = 0; i < file_size; ++i) {
91		if (buf[i] == '\n') {
92			lines[line_count++] = start_line;
93			buf[i] = '\0';
94			start_line = &buf[i + 1];
95		}
96		if (buf[i] == '\t')
97			buf[i] = ' ';
98	}
99	create_text_box(lines, line_count, file_name, attrs.textbox);
100	free(lines);
101	free(buf);
102}
103
104void show_text(const char *const *lines, unsigned int count, const char *title)
105{
106	create_text_box(lines, count, title, attrs.textbox);
107}
108
109/**********************************************************************/
110
111static struct widget text_widget;
112static char *title;
113static int widget_attrs;
114static char **text_lines;
115static unsigned int text_lines_count;
116static int max_line_width;
117static int text_box_y;
118static int text_box_x;
119static int max_scroll_y;
120static int max_scroll_x;
121static int current_top;
122static int current_left;
123
124static void update_text_lines(void)
125{
126	int i;
127	int width;
128	const char *line_begin;
129	const char *line_end;
130	int cur_y, cur_x;
131	int rest_of_line;
132
133	for (i = 0; i < text_box_y; ++i) {
134		width = current_left;
135		line_begin = mbs_at_width(text_lines[current_top + i], &width, 1);
136		wmove(text_widget.window, i + 1, 1);
137		if (width > current_left)
138			waddch(text_widget.window, ' ');
139		if (*line_begin != '\0') {
140			width = text_box_x - (width > current_left);
141			line_end = mbs_at_width(line_begin, &width, -1);
142			if (width)
143				waddnstr(text_widget.window, line_begin,
144					 line_end - line_begin);
145		}
146		getyx(text_widget.window, cur_y, cur_x);
147		if (cur_y == i + 1) {
148			rest_of_line = text_box_x + 1 - cur_x;
149			if (rest_of_line > 0)
150				wprintw(text_widget.window, "%*s", rest_of_line, "");
151		}
152	}
153}
154
155static void update_y_scroll_bar(void)
156{
157	int length;
158	int begin;
159
160	if (max_scroll_y <= 0 || text_lines_count == 0)
161		return;
162	length = text_box_y * text_box_y / text_lines_count;
163	if (length >= text_box_y)
164		return;
165	begin = current_top * (text_box_y - length) / max_scroll_y;
166	mvwvline(text_widget.window, 1, text_box_x + 1, ' ', text_box_y);
167	mvwvline(text_widget.window, begin + 1, text_box_x + 1, ACS_BOARD, length);
168}
169
170static void update_x_scroll_bar(void)
171{
172	int length;
173	int begin;
174
175	if (max_scroll_x <= 0 || max_line_width <= 0)
176		return;
177	length = text_box_x * text_box_x / max_line_width;
178	if (length >= text_box_x)
179		return;
180	begin = current_left * (text_box_x - length) / max_scroll_x;
181	mvwhline(text_widget.window, text_box_y + 1, 1, ' ', text_box_x);
182	mvwhline(text_widget.window, text_box_y + 1, begin + 1, ACS_BOARD, length);
183}
184
185static void move_x(int delta)
186{
187	int left;
188
189	left = current_left + delta;
190	if (left < 0)
191		left = 0;
192	else if (left > max_scroll_x)
193		left = max_scroll_x;
194	if (left != current_left) {
195		current_left = left;
196		update_text_lines();
197		update_x_scroll_bar();
198	}
199}
200
201static void move_y(int delta)
202{
203	int top;
204
205	top = current_top + delta;
206	if (top < 0)
207		top = 0;
208	else if (top > max_scroll_y)
209		top = max_scroll_y;
210	if (top != current_top) {
211		current_top = top;
212		update_text_lines();
213		update_y_scroll_bar();
214	}
215}
216
217static void on_handle_key(int key)
218{
219	if (key >= (int)ARRAY_SIZE(textbox_bindings))
220		return;
221
222	switch (textbox_bindings[key]) {
223	case CMD_TEXTBOX_CLOSE:
224		text_widget.close();
225		break;
226	case CMD_TEXTBOX_DOWN:
227		move_y(1);
228		break;
229	case CMD_TEXTBOX_UP:
230		move_y(-1);
231		break;
232	case CMD_TEXTBOX_LEFT:
233		move_x(-1);
234		break;
235	case CMD_TEXTBOX_RIGHT:
236		move_x(1);
237		break;
238	case CMD_TEXTBOX_PAGE_DOWN:
239		move_y(text_box_y);
240		break;
241	case CMD_TEXTBOX_PAGE_UP:
242		move_y(-text_box_y);
243		break;
244	case CMD_TEXTBOX_TOP:
245		move_x(-max_scroll_x);
246		break;
247	case CMD_TEXTBOX_BOTTOM:
248		move_x(max_scroll_x);
249		break;
250	case CMD_TEXTBOX_PAGE_RIGHT:
251		move_x(8);
252		break;
253	case CMD_TEXTBOX_PAGE_LEFT:
254		move_x(-8);
255		break;
256	}
257}
258
259static bool create(void)
260{
261	int len, width;
262
263	if (screen_lines < 3 || screen_cols < 8) {
264		text_widget.close();
265		beep();
266		return FALSE;
267	}
268
269	width = max_line_width;
270	len = get_mbs_width(title) + 2;
271	if (width < len)
272		width = len;
273
274	text_box_y = text_lines_count;
275	if (text_box_y > screen_lines - 2)
276		text_box_y = screen_lines - 2;
277	max_scroll_y = text_lines_count - text_box_y;
278	text_box_x = width;
279	if (text_box_x > screen_cols - 2)
280		text_box_x = screen_cols - 2;
281	max_scroll_x = max_line_width - text_box_x;
282
283	widget_init(&text_widget, text_box_y + 2, text_box_x + 2,
284		    SCREEN_CENTER, SCREEN_CENTER, widget_attrs, WIDGET_BORDER);
285	mvwprintw(text_widget.window, 0, (text_box_x + 2 - get_mbs_width(title) - 2) / 2, " %s ", title);
286
287	if (current_top > max_scroll_y)
288		current_top = max_scroll_y;
289	if (current_left > max_scroll_x)
290		current_left = max_scroll_x;
291	update_text_lines();
292	update_y_scroll_bar();
293	update_x_scroll_bar();
294	return TRUE;
295}
296
297static void on_window_size_changed(void)
298{
299	create();
300}
301
302static void on_close(void)
303{
304	unsigned int i;
305
306	for (i = 0; i < text_lines_count; ++i)
307		free(text_lines[i]);
308	free(text_lines);
309	widget_free(&text_widget);
310}
311
312static struct widget text_widget = {
313	.handle_key = on_handle_key,
314	.window_size_changed = on_window_size_changed,
315	.close = on_close,
316};
317
318static void create_text_box(const char *const *lines, unsigned int count,
319			    const char *title_, int attrs)
320{
321	unsigned int i;
322
323	text_lines = ccalloc(count, sizeof *text_lines);
324	for (i = 0; i < count; ++i)
325		text_lines[i] = cstrdup(lines[i]);
326	text_lines_count = count;
327	max_line_width = get_max_mbs_width(lines, count);
328	title = cstrdup(title_);
329	widget_attrs = attrs;
330
331	current_top = 0;
332	current_left = 0;
333
334	create();
335}
336