tarina

git clone https://git.tarina.org/tarina
Log | Files | Refs | README | LICENSE

textbox.c (8600B)


      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 
     36 #define MAX_FILE_SIZE 1048576
     37 
     38 static void create_text_box(const char *const *lines, unsigned int count,
     39 			    const char *title, int attrs);
     40 
     41 void show_error(const char *msg, int err)
     42 {
     43 	const char *lines[2];
     44 	unsigned int count;
     45 
     46 	lines[0] = msg;
     47 	count = 1;
     48 	if (err) {
     49 		lines[1] = strerror(err);
     50 		count = 2;
     51 	}
     52 	create_text_box(lines, count, _("Error"), attr_errormsg);
     53 }
     54 
     55 void show_alsa_error(const char *msg, int err)
     56 {
     57 	const char *lines[2];
     58 	unsigned int count;
     59 
     60 	lines[0] = msg;
     61 	count = 1;
     62 	if (err < 0) {
     63 		lines[1] = snd_strerror(err);
     64 		count = 2;
     65 	}
     66 	create_text_box(lines, count, _("Error"), attr_errormsg);
     67 }
     68 
     69 static char *read_file(const char *file_name, unsigned int *file_size)
     70 {
     71 	FILE *f;
     72 	int err;
     73 	char *buf;
     74 	unsigned int allocated = 2048;
     75 	unsigned int bytes_read;
     76 
     77 	f = fopen(file_name, "r");
     78 	if (!f) {
     79 		err = errno;
     80 		buf = casprintf(_("Cannot open file \"%s\"."), file_name);
     81 		show_error(buf, err);
     82 		free(buf);
     83 		return NULL;
     84 	}
     85 	*file_size = 0;
     86 	buf = NULL;
     87 	do {
     88 		allocated *= 2;
     89 		buf = crealloc(buf, allocated);
     90 		bytes_read = fread(buf + *file_size, 1, allocated - *file_size, f);
     91 		*file_size += bytes_read;
     92 	} while (*file_size == allocated && allocated < MAX_FILE_SIZE);
     93 	fclose(f);
     94 	if (*file_size > 0 && buf[*file_size - 1] != '\n' && *file_size < allocated) {
     95 		buf[*file_size] = '\n';
     96 		++*file_size;
     97 	}
     98 	return buf;
     99 }
    100 
    101 void show_textfile(const char *file_name)
    102 {
    103 	char *buf;
    104 	unsigned int file_size;
    105 	unsigned int line_count;
    106 	unsigned int i;
    107 	const char **lines;
    108 	const char *start_line;
    109 
    110 	buf = read_file(file_name, &file_size);
    111 	if (!buf)
    112 		return;
    113 	line_count = 0;
    114 	for (i = 0; i < file_size; ++i)
    115 		line_count += buf[i] == '\n';
    116 	lines = ccalloc(line_count, sizeof *lines);
    117 	line_count = 0;
    118 	start_line = buf;
    119 	for (i = 0; i < file_size; ++i) {
    120 		if (buf[i] == '\n') {
    121 			lines[line_count++] = start_line;
    122 			buf[i] = '\0';
    123 			start_line = &buf[i + 1];
    124 		}
    125 		if (buf[i] == '\t')
    126 			buf[i] = ' ';
    127 	}
    128 	create_text_box(lines, line_count, file_name, attr_textbox);
    129 	free(lines);
    130 	free(buf);
    131 }
    132 
    133 void show_text(const char *const *lines, unsigned int count, const char *title)
    134 {
    135 	create_text_box(lines, count, title, attr_textbox);
    136 }
    137 
    138 /**********************************************************************/
    139 
    140 static struct widget text_widget;
    141 static char *title;
    142 static int widget_attrs;
    143 static char **text_lines;
    144 static unsigned int text_lines_count;
    145 static int max_line_width;
    146 static int text_box_y;
    147 static int text_box_x;
    148 static int max_scroll_y;
    149 static int max_scroll_x;
    150 static int current_top;
    151 static int current_left;
    152 
    153 static void update_text_lines(void)
    154 {
    155 	int i;
    156 	int width;
    157 	const char *line_begin;
    158 	const char *line_end;
    159 	int cur_y, cur_x;
    160 	int rest_of_line;
    161 
    162 	for (i = 0; i < text_box_y; ++i) {
    163 		width = current_left;
    164 		line_begin = mbs_at_width(text_lines[current_top + i], &width, 1);
    165 		wmove(text_widget.window, i + 1, 1);
    166 		if (width > current_left)
    167 			waddch(text_widget.window, ' ');
    168 		if (*line_begin != '\0') {
    169 			width = text_box_x - (width > current_left);
    170 			line_end = mbs_at_width(line_begin, &width, -1);
    171 			if (width)
    172 				waddnstr(text_widget.window, line_begin,
    173 					 line_end - line_begin);
    174 		}
    175 		getyx(text_widget.window, cur_y, cur_x);
    176 		if (cur_y == i + 1) {
    177 			rest_of_line = text_box_x + 1 - cur_x;
    178 			if (rest_of_line > 0)
    179 				wprintw(text_widget.window, "%*s", rest_of_line, "");
    180 		}
    181 	}
    182 }
    183 
    184 static void update_y_scroll_bar(void)
    185 {
    186 	int length;
    187 	int begin, end;
    188 	int i;
    189 
    190 	if (max_scroll_y <= 0 || text_lines_count == 0)
    191 		return;
    192 	length = text_box_y * text_box_y / text_lines_count;
    193 	if (length >= text_box_y)
    194 		return;
    195 	begin = current_top * (text_box_y - length) / max_scroll_y;
    196 	end = begin + length;
    197 	for (i = 0; i < text_box_y; ++i)
    198 		mvwaddch(text_widget.window, i + 1, text_box_x + 1,
    199 			 i >= begin && i < end ? ACS_BOARD : ' ');
    200 }
    201 
    202 static void update_x_scroll_bar(void)
    203 {
    204 	int length;
    205 	int begin, end;
    206 	int i;
    207 
    208 	if (max_scroll_x <= 0 || max_line_width <= 0)
    209 		return;
    210 	length = text_box_x * text_box_x / max_line_width;
    211 	if (length >= text_box_x)
    212 		return;
    213 	begin = current_left * (text_box_x - length) / max_scroll_x;
    214 	end = begin + length;
    215 	wmove(text_widget.window, text_box_y + 1, 1);
    216 	for (i = 0; i < text_box_x; ++i)
    217 		waddch(text_widget.window, i >= begin && i < end ? ACS_BOARD : ' ');
    218 }
    219 
    220 static void move_x(int delta)
    221 {
    222 	int left;
    223 
    224 	left = current_left + delta;
    225 	if (left < 0)
    226 		left = 0;
    227 	else if (left > max_scroll_x)
    228 		left = max_scroll_x;
    229 	if (left != current_left) {
    230 		current_left = left;
    231 		update_text_lines();
    232 		update_x_scroll_bar();
    233 	}
    234 }
    235 
    236 static void move_y(int delta)
    237 {
    238 	int top;
    239 
    240 	top = current_top + delta;
    241 	if (top < 0)
    242 		top = 0;
    243 	else if (top > max_scroll_y)
    244 		top = max_scroll_y;
    245 	if (top != current_top) {
    246 		current_top = top;
    247 		update_text_lines();
    248 		update_y_scroll_bar();
    249 	}
    250 }
    251 
    252 static void on_handle_key(int key)
    253 {
    254 	switch (key) {
    255 	case 10:
    256 	case 13:
    257 	case 27:
    258 	case KEY_CANCEL:
    259 	case KEY_ENTER:
    260 	case KEY_CLOSE:
    261 	case KEY_EXIT:
    262 		text_widget.close();
    263 		break;
    264 	case KEY_DOWN:
    265 	case KEY_SF:
    266 	case 'J':
    267 	case 'j':
    268 	case 'X':
    269 	case 'x':
    270 		move_y(1);
    271 		break;
    272 	case KEY_UP:
    273 	case KEY_SR:
    274 	case 'K':
    275 	case 'k':
    276 	case 'W':
    277 	case 'w':
    278 		move_y(-1);
    279 		break;
    280 	case KEY_LEFT:
    281 	case 'H':
    282 	case 'h':
    283 	case 'P':
    284 	case 'p':
    285 		move_x(-1);
    286 		break;
    287 	case KEY_RIGHT:
    288 	case 'L':
    289 	case 'l':
    290 	case 'N':
    291 	case 'n':
    292 		move_x(1);
    293 		break;
    294 	case KEY_NPAGE:
    295 	case ' ':
    296 		move_y(text_box_y);
    297 		break;
    298 	case KEY_PPAGE:
    299 	case KEY_BACKSPACE:
    300 	case 'B':
    301 	case 'b':
    302 		move_y(-text_box_y);
    303 		break;
    304 	case KEY_HOME:
    305 	case KEY_BEG:
    306 		move_x(-max_scroll_x);
    307 		break;
    308 	case KEY_LL:
    309 	case KEY_END:
    310 		move_x(max_scroll_x);
    311 		break;
    312 	case '\t':
    313 		move_x(8);
    314 		break;
    315 	case KEY_BTAB:
    316 		move_x(-8);
    317 		break;
    318 	}
    319 }
    320 
    321 static bool create(void)
    322 {
    323 	int len, width;
    324 
    325 	if (screen_lines < 3 || screen_cols < 8) {
    326 		text_widget.close();
    327 		beep();
    328 		return FALSE;
    329 	}
    330 
    331 	width = max_line_width;
    332 	len = get_mbs_width(title) + 2;
    333 	if (width < len)
    334 		width = len;
    335 
    336 	text_box_y = text_lines_count;
    337 	if (text_box_y > screen_lines - 2)
    338 		text_box_y = screen_lines - 2;
    339 	max_scroll_y = text_lines_count - text_box_y;
    340 	text_box_x = width;
    341 	if (text_box_x > screen_cols - 2)
    342 		text_box_x = screen_cols - 2;
    343 	max_scroll_x = max_line_width - text_box_x;
    344 
    345 	widget_init(&text_widget, text_box_y + 2, text_box_x + 2,
    346 		    SCREEN_CENTER, SCREEN_CENTER, widget_attrs, WIDGET_BORDER);
    347 	mvwprintw(text_widget.window, 0, (text_box_x + 2 - get_mbs_width(title) - 2) / 2, " %s ", title);
    348 
    349 	if (current_top > max_scroll_y)
    350 		current_top = max_scroll_y;
    351 	if (current_left > max_scroll_x)
    352 		current_left = max_scroll_x;
    353 	update_text_lines();
    354 	update_y_scroll_bar();
    355 	update_x_scroll_bar();
    356 	return TRUE;
    357 }
    358 
    359 static void on_window_size_changed(void)
    360 {
    361 	create();
    362 }
    363 
    364 static void on_close(void)
    365 {
    366 	unsigned int i;
    367 
    368 	for (i = 0; i < text_lines_count; ++i)
    369 		free(text_lines[i]);
    370 	free(text_lines);
    371 	widget_free(&text_widget);
    372 }
    373 
    374 static struct widget text_widget = {
    375 	.handle_key = on_handle_key,
    376 	.window_size_changed = on_window_size_changed,
    377 	.close = on_close,
    378 };
    379 
    380 static void create_text_box(const char *const *lines, unsigned int count,
    381 			    const char *title_, int attrs)
    382 {
    383 	unsigned int i;
    384 
    385 	text_lines = ccalloc(count, sizeof *text_lines);
    386 	for (i = 0; i < count; ++i)
    387 		text_lines[i] = cstrdup(lines[i]);
    388 	text_lines_count = count;
    389 	max_line_width = get_max_mbs_width(lines, count);
    390 	title = cstrdup(title_);
    391 	widget_attrs = attrs;
    392 
    393 	current_top = 0;
    394 	current_left = 0;
    395 
    396 	create();
    397 }