1 /*
2  * GNT - The GLib Ncurses Toolkit
3  *
4  * GNT is the legal property of its developers, whose names are too numerous
5  * to list here.  Please refer to the COPYRIGHT file distributed with this
6  * source distribution.
7  *
8  * This library is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
21  */
22 
23 #include "gntinternal.h"
24 #undef GNT_LOG_DOMAIN
25 #define GNT_LOG_DOMAIN "TextView"
26 
27 #include "gntstyle.h"
28 #include "gnttextview.h"
29 #include "gntutils.h"
30 
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 
35 enum
36 {
37 	SIGS = 1,
38 };
39 
40 typedef struct
41 {
42 	GntTextFormatFlags tvflag;
43 	chtype flags;
44 	int start;
45 	int end;     /* This is the next byte of the last character of this segment */
46 } GntTextSegment;
47 
48 typedef struct
49 {
50 	GList *segments;         /* A list of GntTextSegments */
51 	int length;              /* The current length of the line so far (ie. onscreen width) */
52 	gboolean soft;           /* TRUE if it's an overflow from prev. line */
53 } GntTextLine;
54 
55 typedef struct
56 {
57 	char *name;
58 	int start;
59 	int end;
60 } GntTextTag;
61 
62 static GntWidgetClass *parent_class = NULL;
63 
64 static gchar *select_start;
65 static gchar *select_end;
66 static gboolean double_click;
67 
68 static void reset_text_view(GntTextView *view);
69 
70 static gboolean
text_view_contains(GntTextView * view,const char * str)71 text_view_contains(GntTextView *view, const char *str)
72 {
73 	return (str >= view->string->str && str < view->string->str + view->string->len);
74 }
75 
76 static void
gnt_text_view_draw(GntWidget * widget)77 gnt_text_view_draw(GntWidget *widget)
78 {
79 	GntTextView *view = GNT_TEXT_VIEW(widget);
80 	int n;
81 	int i = 0;
82 	GList *lines;
83 	int rows, scrcol;
84 	int comp = 0;          /* Used for top-aligned text */
85 	gboolean has_scroll = !(view->flags & GNT_TEXT_VIEW_NO_SCROLL);
86 
87 	wbkgd(widget->window, gnt_color_pair(GNT_COLOR_NORMAL));
88 	werase(widget->window);
89 
90 	n = g_list_length(view->list);
91 	if ((view->flags & GNT_TEXT_VIEW_TOP_ALIGN) &&
92 			n < widget->priv.height) {
93 		GList *now = view->list;
94 		comp = widget->priv.height - n;
95 		view->list = g_list_nth_prev(view->list, comp);
96 		if (!view->list) {
97 			view->list = g_list_first(now);
98 			comp = widget->priv.height - g_list_length(view->list);
99 		} else {
100 			comp = 0;
101 		}
102 	}
103 
104 	for (i = 0, lines = view->list; i < widget->priv.height && lines; i++, lines = lines->next)
105 	{
106 		GList *iter;
107 		GntTextLine *line = lines->data;
108 
109 		(void)wmove(widget->window, widget->priv.height - 1 - i - comp, 0);
110 
111 		for (iter = line->segments; iter; iter = iter->next)
112 		{
113 			GntTextSegment *seg = iter->data;
114 			char *end = view->string->str + seg->end;
115 			char back = *end;
116 			chtype fl = seg->flags;
117 			*end = '\0';
118 			if (select_start && select_start < view->string->str + seg->start && select_end > view->string->str + seg->end) {
119 				fl |= A_REVERSE;
120 				wattrset(widget->window, fl);
121 				wprintw(widget->window, "%s", C_(view->string->str + seg->start));
122 			} else if (select_start && select_end &&
123 				((select_start >= view->string->str + seg->start && select_start <= view->string->str + seg->end) ||
124 				(select_end <= view->string->str + seg->end && select_start <= view->string->str + seg->start))) {
125 				char *cur = view->string->str + seg->start;
126 				while (*cur != '\0') {
127 					gchar *last = g_utf8_next_char(cur);
128 					gchar *str;
129 					if (cur >= select_start && cur <= select_end)
130 						fl |= A_REVERSE;
131 					else
132 						fl = seg->flags;
133 					str = g_strndup(cur, last - cur);
134 					wattrset(widget->window, fl);
135 					waddstr(widget->window, C_(str));
136 					g_free(str);
137 					cur = g_utf8_next_char(cur);
138 				}
139 			} else {
140 				wattrset(widget->window, fl);
141 				wprintw(widget->window, "%s", C_(view->string->str + seg->start));
142 			}
143 			*end = back;
144 		}
145 		wattroff(widget->window, A_UNDERLINE | A_BLINK | A_REVERSE);
146 		whline(widget->window, ' ', widget->priv.width - line->length - has_scroll);
147 	}
148 
149 	scrcol = widget->priv.width - 1;
150 	rows = widget->priv.height - 2;
151 	if (has_scroll && rows > 0)
152 	{
153 		int total = g_list_length(g_list_first(view->list));
154 		int showing, position, up, down;
155 
156 		showing = rows * rows / total + 1;
157 		showing = MIN(rows, showing);
158 
159 		total -= rows;
160 		up = g_list_length(lines);
161 		down = total - up;
162 
163 		position = (rows - showing) * up / MAX(1, up + down);
164 		position = MAX((lines != NULL), position);
165 
166 		if (showing + position > rows)
167 			position = rows - showing;
168 
169 		if (showing + position == rows && view->list && view->list->prev)
170 			position = MAX(1, rows - 1 - showing);
171 		else if (showing + position < rows && view->list && !view->list->prev)
172 			position = rows - showing;
173 
174 		mvwvline(widget->window, position + 1, scrcol,
175 				ACS_CKBOARD | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D), showing);
176 	}
177 
178 	if (has_scroll) {
179 		mvwaddch(widget->window, 0, scrcol,
180 				(lines ? ACS_UARROW : ' ') | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D));
181 		mvwaddch(widget->window, widget->priv.height - 1, scrcol,
182 				((view->list && view->list->prev) ? ACS_DARROW : ' ') |
183 					gnt_color_pair(GNT_COLOR_HIGHLIGHT_D));
184 	}
185 
186 	wmove(widget->window, 0, 0);
187 }
188 
189 static void
gnt_text_view_size_request(GntWidget * widget)190 gnt_text_view_size_request(GntWidget *widget)
191 {
192 	if (!gnt_widget_get_mapped(widget)) {
193 		gnt_widget_set_size(widget, 64, 20);
194 	}
195 }
196 
197 static void
gnt_text_view_map(GntWidget * widget)198 gnt_text_view_map(GntWidget *widget)
199 {
200 	if (widget->priv.width == 0 || widget->priv.height == 0)
201 		gnt_widget_size_request(widget);
202 	GNTDEBUG;
203 }
204 
205 static gboolean
gnt_text_view_key_pressed(GntWidget * widget,const char * text)206 gnt_text_view_key_pressed(GntWidget *widget, const char *text)
207 {
208 	return FALSE;
209 }
210 
211 static void
free_text_segment(gpointer data,gpointer null)212 free_text_segment(gpointer data, gpointer null)
213 {
214 	GntTextSegment *seg = data;
215 	g_free(seg);
216 }
217 
218 static void
free_text_line(gpointer data,gpointer null)219 free_text_line(gpointer data, gpointer null)
220 {
221 	GntTextLine *line = data;
222 	g_list_foreach(line->segments, free_text_segment, NULL);
223 	g_list_free(line->segments);
224 	g_free(line);
225 }
226 
227 static void
free_tag(gpointer data,gpointer null)228 free_tag(gpointer data, gpointer null)
229 {
230 	GntTextTag *tag = data;
231 	g_free(tag->name);
232 	g_free(tag);
233 }
234 
235 static void
gnt_text_view_destroy(GntWidget * widget)236 gnt_text_view_destroy(GntWidget *widget)
237 {
238 	GntTextView *view = GNT_TEXT_VIEW(widget);
239 	view->list = g_list_first(view->list);
240 	g_list_foreach(view->list, free_text_line, NULL);
241 	g_list_free(view->list);
242 	g_list_foreach(view->tags, free_tag, NULL);
243 	g_list_free(view->tags);
244 	g_string_free(view->string, TRUE);
245 }
246 
247 static char *
gnt_text_view_get_p(GntTextView * view,int x,int y)248 gnt_text_view_get_p(GntTextView *view, int x, int y)
249 {
250 	int n;
251 	int i = 0;
252 	GntWidget *wid = GNT_WIDGET(view);
253 	GntTextLine *line;
254 	GList *lines;
255 	GList *segs;
256 	GntTextSegment *seg;
257 	gchar *pos;
258 
259 	n = g_list_length(view->list);
260 	y = wid->priv.height - y;
261 	if (n < y) {
262 		x = 0;
263 		y = n - 1;
264 	}
265 
266 	lines = g_list_nth(view->list, y - 1);
267 	if (!lines)
268 		return NULL;
269 	do {
270 		line = lines->data;
271 		lines = lines->next;
272 	} while (line && !line->segments && lines);
273 
274 	if (!line || !line->segments) /* no valid line */
275 		return NULL;
276 	segs = line->segments;
277 	seg = (GntTextSegment *)segs->data;
278 	pos = view->string->str + seg->start;
279 	x = MIN(x, line->length);
280 	while (++i <= x) {
281 		gunichar *u;
282 		pos = g_utf8_next_char(pos);
283 		u = g_utf8_to_ucs4(pos, -1, NULL, NULL, NULL);
284 		if (u && g_unichar_iswide(*u))
285 			i++;
286 		g_free(u);
287 	}
288 	return pos;
289 }
290 
291 static GString *
select_word_text(GntTextView * view,gchar * c)292 select_word_text(GntTextView *view, gchar *c)
293 {
294 	gchar *start = c;
295 	gchar *end = c;
296 	gchar *t, *endsize;
297 	while ((t = g_utf8_prev_char(start))) {
298 		if (!g_ascii_isspace(*t)) {
299 			if (start == view->string->str)
300 				break;
301 			start = t;
302 		} else
303 			break;
304 	}
305 	while ((t = g_utf8_next_char(end))) {
306 		if (!g_ascii_isspace(*t))
307 			end = t;
308 		else
309 			break;
310 	}
311 	select_start = start;
312 	select_end = end;
313 	endsize = g_utf8_next_char(select_end); /* End at the correct byte */
314 	return g_string_new_len(start, endsize - start);
315 }
316 
too_slow(gpointer n)317 static gboolean too_slow(gpointer n)
318 {
319 	double_click = FALSE;
320 	return FALSE;
321 }
322 
323 static gboolean
gnt_text_view_clicked(GntWidget * widget,GntMouseEvent event,int x,int y)324 gnt_text_view_clicked(GntWidget *widget, GntMouseEvent event, int x, int y)
325 {
326 	if (event == GNT_MOUSE_SCROLL_UP) {
327 		gnt_text_view_scroll(GNT_TEXT_VIEW(widget), -1);
328 	} else if (event == GNT_MOUSE_SCROLL_DOWN) {
329 		gnt_text_view_scroll(GNT_TEXT_VIEW(widget), 1);
330 	} else if (event == GNT_LEFT_MOUSE_DOWN) {
331 		select_start = gnt_text_view_get_p(GNT_TEXT_VIEW(widget), x - widget->priv.x, y - widget->priv.y);
332 		g_timeout_add(500, too_slow, NULL);
333 	} else if (event == GNT_MOUSE_UP) {
334 		GntTextView *view = GNT_TEXT_VIEW(widget);
335 		if (text_view_contains(view, select_start)) {
336 			GString *clip;
337 			select_end = gnt_text_view_get_p(view, x - widget->priv.x, y - widget->priv.y);
338 			if (select_end < select_start) {
339 				gchar *t = select_start;
340 				select_start = select_end;
341 				select_end = t;
342 			}
343 			if (select_start == select_end) {
344 				if (double_click) {
345 					clip = select_word_text(view, select_start);
346 					double_click = FALSE;
347 				} else {
348 					double_click = TRUE;
349 					select_start = 0;
350 					select_end = 0;
351 					gnt_widget_draw(widget);
352 					return TRUE;
353 				}
354 			} else {
355 				gchar *endsize = g_utf8_next_char(select_end); /* End at the correct byte */
356 				clip = g_string_new_len(select_start, endsize - select_start);
357 			}
358 			gnt_widget_draw(widget);
359 			gnt_set_clipboard_string(clip->str);
360 			g_string_free(clip, TRUE);
361 		}
362 	} else
363 		return FALSE;
364 	return TRUE;
365 }
366 
367 static void
gnt_text_view_reflow(GntTextView * view)368 gnt_text_view_reflow(GntTextView *view)
369 {
370 	/* This is pretty ugly, and inefficient. Someone do something about it. */
371 	GntTextLine *line;
372 	GList *back, *iter, *list;
373 	GString *string;
374 	int pos = 0;    /* no. of 'real' lines */
375 
376 	list = view->list;
377 	while (list->prev) {
378 		line = list->data;
379 		if (!line->soft)
380 			pos++;
381 		list = list->prev;
382 	}
383 
384 	back = g_list_last(view->list);
385 	view->list = NULL;
386 
387 	string = view->string;
388 	view->string = NULL;
389 	reset_text_view(view);
390 
391 	view->string = g_string_set_size(view->string, string->len);
392 	view->string->len = 0;
393 	gnt_widget_set_drawing(GNT_WIDGET(view), TRUE);
394 
395 	for (; back; back = back->prev) {
396 		line = back->data;
397 		if (back->next && !line->soft) {
398 			gnt_text_view_append_text_with_flags(view, "\n", GNT_TEXT_FLAG_NORMAL);
399 		}
400 
401 		for (iter = line->segments; iter; iter = iter->next) {
402 			GntTextSegment *seg = iter->data;
403 			char *start = string->str + seg->start;
404 			char *end = string->str + seg->end;
405 			char back = *end;
406 			*end = '\0';
407 			gnt_text_view_append_text_with_flags(view, start, seg->tvflag);
408 			*end = back;
409 		}
410 		free_text_line(line, NULL);
411 	}
412 	g_list_free(list);
413 
414 	list = view->list = g_list_first(view->list);
415 	/* Go back to the line that was in view before resizing started */
416 	while (pos--) {
417 		while (((GntTextLine*)list->data)->soft)
418 			list = list->next;
419 		list = list->next;
420 	}
421 	view->list = list;
422 	gnt_widget_set_drawing(GNT_WIDGET(view), FALSE);
423 	if (GNT_WIDGET(view)->window)
424 		gnt_widget_draw(GNT_WIDGET(view));
425 	g_string_free(string, TRUE);
426 }
427 
428 static void
gnt_text_view_size_changed(GntWidget * widget,int w,int h)429 gnt_text_view_size_changed(GntWidget *widget, int w, int h)
430 {
431 	if (w != widget->priv.width && gnt_widget_get_mapped(widget)) {
432 		gnt_text_view_reflow(GNT_TEXT_VIEW(widget));
433 	}
434 }
435 
436 static void
gnt_text_view_class_init(GntTextViewClass * klass)437 gnt_text_view_class_init(GntTextViewClass *klass)
438 {
439 	parent_class = GNT_WIDGET_CLASS(klass);
440 	parent_class->destroy = gnt_text_view_destroy;
441 	parent_class->draw = gnt_text_view_draw;
442 	parent_class->map = gnt_text_view_map;
443 	parent_class->size_request = gnt_text_view_size_request;
444 	parent_class->key_pressed = gnt_text_view_key_pressed;
445 	parent_class->clicked = gnt_text_view_clicked;
446 	parent_class->size_changed = gnt_text_view_size_changed;
447 
448 	GNTDEBUG;
449 }
450 
451 static void
gnt_text_view_init(GTypeInstance * instance,gpointer class)452 gnt_text_view_init(GTypeInstance *instance, gpointer class)
453 {
454 	GntWidget *widget = GNT_WIDGET(instance);
455 	GntTextView *view = GNT_TEXT_VIEW(widget);
456 	GntTextLine *line = g_new0(GntTextLine, 1);
457 
458 	gnt_widget_set_has_border(widget, FALSE);
459 	gnt_widget_set_has_shadow(widget, FALSE);
460 	gnt_widget_set_grow_x(widget, TRUE);
461 	gnt_widget_set_grow_y(widget, TRUE);
462 	widget->priv.minw = 5;
463 	widget->priv.minh = 2;
464 	view->string = g_string_new(NULL);
465 	view->list = g_list_append(view->list, line);
466 
467 	GNTDEBUG;
468 }
469 
470 /******************************************************************************
471  * GntTextView API
472  *****************************************************************************/
473 GType
gnt_text_view_get_gtype(void)474 gnt_text_view_get_gtype(void)
475 {
476 	static GType type = 0;
477 
478 	if(type == 0)
479 	{
480 		static const GTypeInfo info = {
481 			sizeof(GntTextViewClass),
482 			NULL,					/* base_init		*/
483 			NULL,					/* base_finalize	*/
484 			(GClassInitFunc)gnt_text_view_class_init,
485 			NULL,					/* class_finalize	*/
486 			NULL,					/* class_data		*/
487 			sizeof(GntTextView),
488 			0,						/* n_preallocs		*/
489 			gnt_text_view_init,			/* instance_init	*/
490 			NULL					/* value_table		*/
491 		};
492 
493 		type = g_type_register_static(GNT_TYPE_WIDGET,
494 									  "GntTextView",
495 									  &info, 0);
496 	}
497 
498 	return type;
499 }
500 
gnt_text_view_new()501 GntWidget *gnt_text_view_new()
502 {
503 	GntWidget *widget = g_object_new(GNT_TYPE_TEXT_VIEW, NULL);
504 
505 	return widget;
506 }
507 
gnt_text_view_append_text_with_flags(GntTextView * view,const char * text,GntTextFormatFlags flags)508 void gnt_text_view_append_text_with_flags(GntTextView *view, const char *text, GntTextFormatFlags flags)
509 {
510 	gnt_text_view_append_text_with_tag(view, text, flags, NULL);
511 }
512 
gnt_text_view_append_text_with_tag(GntTextView * view,const char * text,GntTextFormatFlags flags,const char * tagname)513 void gnt_text_view_append_text_with_tag(GntTextView *view, const char *text,
514 			GntTextFormatFlags flags, const char *tagname)
515 {
516 	GntWidget *widget = GNT_WIDGET(view);
517 	chtype fl = 0;
518 	const char *start, *end;
519 	GList *list = view->list;
520 	GntTextLine *line;
521 	int len;
522 	gboolean has_scroll = !(view->flags & GNT_TEXT_VIEW_NO_SCROLL);
523 	gboolean wrap_word = !(view->flags & GNT_TEXT_VIEW_WRAP_CHAR);
524 
525 	if (text == NULL || *text == '\0')
526 		return;
527 
528 	fl = gnt_text_format_flag_to_chtype(flags);
529 
530 	len = view->string->len;
531 	view->string = g_string_append(view->string, text);
532 
533 	if (tagname) {
534 		GntTextTag *tag = g_new0(GntTextTag, 1);
535 		tag->name = g_strdup(tagname);
536 		tag->start = len;
537 		tag->end = view->string->len;
538 		view->tags = g_list_append(view->tags, tag);
539 	}
540 
541 	view->list = g_list_first(view->list);
542 
543 	start = end = view->string->str + len;
544 
545 	while (*start) {
546 		GntTextLine *oldl;
547 		GntTextSegment *seg = NULL;
548 
549 		if (*end == '\n' || *end == '\r') {
550 			if (!strncmp(end, "\r\n", 2))
551 				end++;
552 			end++;
553 			start = end;
554 			gnt_text_view_next_line(view);
555 			view->list = g_list_first(view->list);
556 			continue;
557 		}
558 
559 		line = view->list->data;
560 		if (line->length == widget->priv.width - has_scroll) {
561 			/* The last added line was exactly the same width as the widget */
562 			line = g_new0(GntTextLine, 1);
563 			line->soft = TRUE;
564 			view->list = g_list_prepend(view->list, line);
565 		}
566 
567 		if ((end = strchr(start, '\r')) != NULL ||
568 			(end = strchr(start, '\n')) != NULL) {
569 			len = gnt_util_onscreen_width(start, end - has_scroll);
570 			if (widget->priv.width > 0 &&
571 					len >= widget->priv.width - line->length - has_scroll) {
572 				end = NULL;
573 			}
574 		}
575 
576 		if (end == NULL)
577 			end = gnt_util_onscreen_width_to_pointer(start,
578 					widget->priv.width - line->length - has_scroll, &len);
579 
580 		/* Try to append to the previous segment if possible */
581 		if (line->segments) {
582 			seg = g_list_last(line->segments)->data;
583 			if (seg->flags != fl)
584 				seg = NULL;
585 		}
586 
587 		if (seg == NULL) {
588 			seg = g_new0(GntTextSegment, 1);
589 			seg->start = start - view->string->str;
590 			seg->tvflag = flags;
591 			seg->flags = fl;
592 			line->segments = g_list_append(line->segments, seg);
593 		}
594 
595 		oldl = line;
596 		if (wrap_word && *end && *end != '\n' && *end != '\r') {
597 			const char *tmp = end;
598 			while (end && *end != '\n' && *end != '\r' && !g_ascii_isspace(*end)) {
599 				end = g_utf8_find_prev_char(seg->start + view->string->str, end);
600 			}
601 			if (!end || !g_ascii_isspace(*end))
602 				end = tmp;
603 			else
604 				end++; /* Remove the space */
605 
606 			line = g_new0(GntTextLine, 1);
607 			line->soft = TRUE;
608 			view->list = g_list_prepend(view->list, line);
609 		}
610 		seg->end = end - view->string->str;
611 		oldl->length += len;
612 		start = end;
613 	}
614 
615 	view->list = list;
616 
617 	gnt_widget_draw(widget);
618 }
619 
620 const gchar *
gnt_text_view_get_text(GntTextView * view)621 gnt_text_view_get_text(GntTextView *view)
622 {
623 	g_return_val_if_fail(GNT_IS_TEXT_VIEW(view), NULL);
624 
625 	return view->string->str;
626 }
627 
gnt_text_view_scroll(GntTextView * view,int scroll)628 void gnt_text_view_scroll(GntTextView *view, int scroll)
629 {
630 	if (scroll == 0)
631 	{
632 		view->list = g_list_first(view->list);
633 	}
634 	else if (scroll > 0)
635 	{
636 		GList *list = g_list_nth_prev(view->list, scroll);
637 		if (list == NULL)
638 			list = g_list_first(view->list);
639 		view->list = list;
640 	}
641 	else if (scroll < 0)
642 	{
643 		GList *list = g_list_nth(view->list, -scroll);
644 		if (list == NULL)
645 			list = g_list_last(view->list);
646 		view->list = list;
647 	}
648 
649 	gnt_widget_draw(GNT_WIDGET(view));
650 }
651 
gnt_text_view_next_line(GntTextView * view)652 void gnt_text_view_next_line(GntTextView *view)
653 {
654 	GntTextLine *line = g_new0(GntTextLine, 1);
655 	GList *list = view->list;
656 
657 	view->list = g_list_prepend(g_list_first(view->list), line);
658 	view->list = list;
659 	gnt_widget_draw(GNT_WIDGET(view));
660 }
661 
gnt_text_format_flag_to_chtype(GntTextFormatFlags flags)662 chtype gnt_text_format_flag_to_chtype(GntTextFormatFlags flags)
663 {
664 	chtype fl = 0;
665 
666 	if (flags & GNT_TEXT_FLAG_BOLD)
667 		fl |= A_BOLD;
668 	if (flags & GNT_TEXT_FLAG_UNDERLINE)
669 		fl |= A_UNDERLINE;
670 	if (flags & GNT_TEXT_FLAG_BLINK)
671 		fl |= A_BLINK;
672 
673 	if (flags & GNT_TEXT_FLAG_DIM)
674 		fl |= (A_DIM | gnt_color_pair(GNT_COLOR_DISABLED));
675 	else if (flags & GNT_TEXT_FLAG_HIGHLIGHT)
676 		fl |= (A_DIM | gnt_color_pair(GNT_COLOR_HIGHLIGHT));
677 	else if ((flags & A_COLOR) == 0)
678 		fl |= gnt_color_pair(GNT_COLOR_NORMAL);
679 	else
680 		fl |= (flags & A_COLOR);
681 
682 	return fl;
683 }
684 
reset_text_view(GntTextView * view)685 static void reset_text_view(GntTextView *view)
686 {
687 	GntTextLine *line;
688 
689 	g_list_foreach(view->list, free_text_line, NULL);
690 	g_list_free(view->list);
691 	view->list = NULL;
692 
693 	line = g_new0(GntTextLine, 1);
694 	view->list = g_list_append(view->list, line);
695 	if (view->string)
696 		g_string_free(view->string, TRUE);
697 	view->string = g_string_new(NULL);
698 }
699 
gnt_text_view_clear(GntTextView * view)700 void gnt_text_view_clear(GntTextView *view)
701 {
702 	reset_text_view(view);
703 
704 	g_list_foreach(view->tags, free_tag, NULL);
705 	view->tags = NULL;
706 
707 	if (GNT_WIDGET(view)->window)
708 		gnt_widget_draw(GNT_WIDGET(view));
709 }
710 
gnt_text_view_get_lines_below(GntTextView * view)711 int gnt_text_view_get_lines_below(GntTextView *view)
712 {
713 	int below = 0;
714 	GList *list = view->list;
715 	while ((list = list->prev))
716 		++below;
717 	return below;
718 }
719 
gnt_text_view_get_lines_above(GntTextView * view)720 int gnt_text_view_get_lines_above(GntTextView *view)
721 {
722 	int above = 0;
723 	GList *list;
724 	list = g_list_nth(view->list, GNT_WIDGET(view)->priv.height);
725 	if (!list)
726 		return 0;
727 	while ((list = list->next))
728 		++above;
729 	return above;
730 }
731 
732 /*
733  * XXX: There are quite possibly more than a few bugs here.
734  */
gnt_text_view_tag_change(GntTextView * view,const char * name,const char * text,gboolean all)735 int gnt_text_view_tag_change(GntTextView *view, const char *name, const char *text, gboolean all)
736 {
737 	GList *alllines = g_list_first(view->list);
738 	GList *list, *next, *iter, *inext;
739 	const int text_length = text ? strlen(text) : 0;
740 	int count = 0;
741 	for (list = view->tags; list; list = next) {
742 		GntTextTag *tag = list->data;
743 		next = list->next;
744 		if (strcmp(tag->name, name) == 0) {
745 			int change;
746 			char *before, *after;
747 
748 			count++;
749 
750 			before = g_strndup(view->string->str, tag->start);
751 			after = g_strdup(view->string->str + tag->end);
752 			change = (tag->end - tag->start) - text_length;
753 
754 			g_string_printf(view->string, "%s%s%s", before, text ? text : "", after);
755 			g_free(before);
756 			g_free(after);
757 
758 			/* Update the offsets of the next tags */
759 			for (iter = next; iter; iter = iter->next) {
760 				GntTextTag *t = iter->data;
761 				t->start -= change;
762 				t->end -= change;
763 			}
764 
765 			/* Update the offsets of the segments */
766 			for (iter = alllines; iter; iter = inext) {
767 				GList *segs, *snext;
768 				GntTextLine *line = iter->data;
769 				inext = iter->next;
770 
771 				if (!line) {
772 					g_warn_if_reached();
773 					continue;
774 				}
775 
776 				for (segs = line->segments; segs; segs = snext) {
777 					GntTextSegment *seg = segs->data;
778 
779 					if (!line)
780 						break;
781 
782 					snext = segs->next;
783 					if (seg->start >= tag->end) {
784 						/* The segment is somewhere after the tag */
785 						seg->start -= change;
786 						seg->end -= change;
787 					} else if (seg->end <= tag->start) {
788 						/* This segment is somewhere in front of the tag */
789 					} else if (seg->start >= tag->start) {
790 						/* This segment starts in the middle of the tag */
791 						if (text == NULL) {
792 							free_text_segment(seg, NULL);
793 							line->segments = g_list_delete_link(line->segments, segs);
794 							if (line->segments == NULL) {
795 								free_text_line(line, NULL);
796 								line = NULL;
797 								if (view->list == iter) {
798 									if (inext)
799 										view->list = inext;
800 									else
801 										view->list = iter->prev;
802 								}
803 								alllines = g_list_delete_link(alllines, iter);
804 							}
805 						} else {
806 							/* XXX: (null) */
807 							seg->start = tag->start;
808 							seg->end = tag->end - change;
809 						}
810 						if (line)
811 							line->length -= change;
812 						/* XXX: Make things work if the tagged text spans over several lines. */
813 					} else {
814 						/* XXX: handle the rest of the conditions */
815 						gnt_warning("WTF! This needs to be handled properly!!%s", "");
816 					}
817 				}
818 			}
819 			if (text == NULL) {
820 				/* Remove the tag */
821 				view->tags = g_list_delete_link(view->tags, list);
822 				free_tag(tag, NULL);
823 			} else {
824 				tag->end -= change;
825 			}
826 			if (!all)
827 				break;
828 		}
829 	}
830 	gnt_widget_draw(GNT_WIDGET(view));
831 	return count;
832 }
833 
834 static gboolean
scroll_tv(GntWidget * wid,const char * key,GntTextView * tv)835 scroll_tv(GntWidget *wid, const char *key, GntTextView *tv)
836 {
837 	if (strcmp(key, GNT_KEY_PGUP) == 0) {
838 		gnt_text_view_scroll(tv, -(GNT_WIDGET(tv)->priv.height - 2));
839 	} else if (strcmp(key, GNT_KEY_PGDOWN) == 0) {
840 		gnt_text_view_scroll(tv, GNT_WIDGET(tv)->priv.height - 2);
841 	} else if (strcmp(key, GNT_KEY_DOWN) == 0) {
842 		gnt_text_view_scroll(tv, 1);
843 	} else if (strcmp(key, GNT_KEY_UP) == 0) {
844 		gnt_text_view_scroll(tv, -1);
845 	} else {
846 		return FALSE;
847 	}
848 	return TRUE;
849 }
850 
gnt_text_view_attach_scroll_widget(GntTextView * view,GntWidget * widget)851 void gnt_text_view_attach_scroll_widget(GntTextView *view, GntWidget *widget)
852 {
853 	g_signal_connect(G_OBJECT(widget), "key_pressed", G_CALLBACK(scroll_tv), view);
854 }
855 
gnt_text_view_set_flag(GntTextView * view,GntTextViewFlag flag)856 void gnt_text_view_set_flag(GntTextView *view, GntTextViewFlag flag)
857 {
858 	view->flags |= flag;
859 }
860 
861 /* Pager and editor setups */
862 struct
863 {
864 	GntTextView *tv;
865 	char *file;
866 } pageditor;
867 
868 
869 static void
cleanup_pageditor(void)870 cleanup_pageditor(void)
871 {
872 	unlink(pageditor.file);
873 	g_free(pageditor.file);
874 
875 	pageditor.file = NULL;
876 	pageditor.tv = NULL;
877 }
878 
879 static void
editor_end_cb(int status,gpointer data)880 editor_end_cb(int status, gpointer data)
881 {
882 	if (status == 0) {
883 		char *text = NULL;
884 		if (g_file_get_contents(pageditor.file, &text, NULL, NULL)) {
885 			reset_text_view(pageditor.tv);
886 			gnt_text_view_append_text_with_flags(pageditor.tv, text, GNT_TEXT_FLAG_NORMAL);
887 			gnt_text_view_scroll(GNT_TEXT_VIEW(pageditor.tv), 0);
888 			g_free(text);
889 		}
890 	}
891 	cleanup_pageditor();
892 }
893 
894 static void
pager_end_cb(int status,gpointer data)895 pager_end_cb(int status, gpointer data)
896 {
897 	cleanup_pageditor();
898 }
899 
900 static gboolean
check_for_ext_cb(GntWidget * widget,const char * key,GntTextView * view)901 check_for_ext_cb(GntWidget *widget, const char *key, GntTextView *view)
902 {
903 	static const char *pager = NULL;
904 	static const char *editor = NULL;
905 	char *argv[] = {NULL, NULL, NULL};
906 	static char path[1024];
907 	static int len = -1;
908 	FILE *file;
909 	gboolean ret;
910 	gboolean pg;
911 
912 	if (pager == NULL) {
913 		pager = gnt_key_translate(gnt_style_get_from_name("pager", "key"));
914 		if (pager == NULL)
915 			pager = "\033" "v";
916 		editor = gnt_key_translate(gnt_style_get_from_name("editor", "key"));
917 		if (editor == NULL)
918 			editor = "\033" "e";
919 		len = g_snprintf(path, sizeof(path), "%s" G_DIR_SEPARATOR_S "gnt", g_get_tmp_dir());
920 	} else {
921 		g_snprintf(path + len, sizeof(path) - len, "XXXXXX");
922 	}
923 
924 	if (strcmp(key, pager) == 0) {
925 		if (g_object_get_data(G_OBJECT(widget), "pager-for") != view)
926 			return FALSE;
927 		pg = TRUE;
928 	} else if (strcmp(key, editor) == 0) {
929 		if (g_object_get_data(G_OBJECT(widget), "editor-for") != view)
930 			return FALSE;
931 		pg = FALSE;
932 	} else {
933 		return FALSE;
934 	}
935 
936 	file = fdopen(g_mkstemp(path), "wb");
937 	if (!file)
938 		return FALSE;
939 	fprintf(file, "%s", view->string->str);
940 	fclose(file);
941 
942 	pageditor.tv = view;
943 	pageditor.file = g_strdup(path);
944 
945 	argv[0] = gnt_style_get_from_name(pg ? "pager" : "editor", "path");
946 	argv[0] = argv[0] ? argv[0] : getenv(pg ? "PAGER" : "EDITOR");
947 	argv[0] = argv[0] ? argv[0] : (pg ? "less" : "vim");
948 	argv[1] = path;
949 	ret = gnt_giveup_console(NULL, argv, NULL, NULL, NULL, NULL, pg ? pager_end_cb : editor_end_cb, NULL);
950 	return ret;
951 }
952 
gnt_text_view_attach_pager_widget(GntTextView * view,GntWidget * pager)953 void gnt_text_view_attach_pager_widget(GntTextView *view, GntWidget *pager)
954 {
955 	g_signal_connect(pager, "key_pressed", G_CALLBACK(check_for_ext_cb), view);
956 	g_object_set_data(G_OBJECT(pager), "pager-for", view);
957 }
958 
gnt_text_view_attach_editor_widget(GntTextView * view,GntWidget * wid)959 void gnt_text_view_attach_editor_widget(GntTextView *view, GntWidget *wid)
960 {
961 	g_signal_connect(wid, "key_pressed", G_CALLBACK(check_for_ext_cb), view);
962 	g_object_set_data(G_OBJECT(wid), "editor-for", view);
963 }
964 
965