1 /* fieldedtextbox.c - for drawing a scrollable text window widget
2    Copyright (C) 1996-2017 Paul Sheer
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., 59 Temple Place, Suite 330, Boston, MA
17    02111-1307, USA.
18  */
19 
20 
21 /*
22  *    format:
23  *
24  *    the printed fields can have the following:
25  *
26  *    '\t': can only occur at the begining and end, or at the beginning.
27  *              At the beginning signals right justification. At the beginning
28  *              and the end signals centering. eg "\tcentred\t" or "\tleft"
29  *    "\v%d": insert pixmap %d at the point. Pixmaps are a special variety
30  *              defined by the create_text_pixmap() function. The value passed
31  *              to this function must appear after the \v and must be less
32  *              than 128.
33  *    "\f%d": advance forward %d pixels. %d must be less than 128.
34  *    "\r%c": puts %c in 'italic' color, default: green.
35  *    "\b%c": puts %c in 'bold' color, default: blue.
36  */
37 
38 
39 #define BDR 8
40 
41 #include <config.h>
42 #include <stdio.h>
43 #include <my_string.h>
44 #include <stdlib.h>
45 #include <stdarg.h>
46 
47 #include <X11/Intrinsic.h>
48 #include <X11/Xatom.h>
49 #include "lkeysym.h"
50 
51 #include "stringtools.h"
52 #include "app_glob.c"
53 #include "edit.h"
54 #include "editcmddef.h"
55 
56 #include "coolwidget.h"
57 #include "coollocal.h"
58 #include "mousemark.h"
59 #include "pool.h"
60 
61 #include "mad.h"
62 
63 extern struct look *look;
64 
65 extern int option_text_fg_normal;
66 extern int option_text_fg_bold;
67 extern int option_text_fg_italic;
68 
69 extern int option_text_bg_normal;
70 extern int option_text_bg_marked;
71 extern int option_text_bg_highlighted;
72 
73 /* for this widget
74    ->column holds the pixel width of the longest line
75  */
76 
77 /* char ** (*get_line) (void *data, int line_number, int *num_fields, int *tagged); */
78 
79 #define pixmap_width(x) 0
80 
81 int eh_fielded_textbox (CWidget * w, XEvent * xevent, CEvent * cwevent);
82 static int calc_text_pos_fielded_textbox (CWidget * w, long b, long *q, int l);
83 
84 /* these are not printable in this module */
85 #define this_is_printable(c) (!strchr ("\r\b\t", c))
86 
this_text_width(char * s)87 static int this_text_width (char *s)
88 {
89     int l = 0;
90     char *p;
91     for (p = s; *p; p++) {
92 	if (*p == '\v')
93 	    l += pixmap_width (++p);
94 	else if (*p == '\f')
95 	    l += *(++p);
96 	else if (this_is_printable (*p))
97 	    l += FONT_PER_CHAR((unsigned char) *p);
98     }
99     return l;
100 }
101 
102 #define INTER_FIELD_SPACING	6
103 #define LINE_OFFSET		0
104 
105 /* result must be free'd */
get_field_sizes(void * data,int * num_lines,int * max_width,char ** (* get_line)(void *,int,int *,int *))106 static int *get_field_sizes (void *data, int *num_lines, int *max_width,
107 			     char **(*get_line) (void *, int, int *, int *))
108 {
109     char **fields;
110     int tagged, i, tab[256], *result, num_fields, max_num_fields = 0, line_number;
111 
112     memset (tab, 0, sizeof (tab));
113 
114     *num_lines = 0;
115 
116     for (line_number = 0;; line_number++) {
117 	fields = (*get_line) (data, line_number, &num_fields, &tagged);
118 	if (!fields)
119 	    break;
120 	(*num_lines)++;
121 	if (max_num_fields < num_fields)
122 	    max_num_fields = num_fields;
123 	for (i = 0; i < num_fields; i++) {
124 	    int l;
125 	    if (!fields[i])
126 		break;
127 	    l = this_text_width (fields[i]);
128 	    if (tab[i] < l)
129 		tab[i] = l;
130 	}
131     }
132     *max_width = 0;
133     for (i = 0; i < max_num_fields; i++)
134 	tab[i] += INTER_FIELD_SPACING;
135     for (i = 0; i < max_num_fields; i++)
136 	*max_width += tab[i];
137 
138     result = CMalloc ((max_num_fields + 1) * sizeof (int));
139     memcpy (result, tab, max_num_fields * sizeof (int));
140     result[max_num_fields] = 0;
141     return result;
142 }
143 
compose_line(void * data,int line_number,unsigned char * line,int * tab,char ** (* get_line)(void *,int,int *,int *),int * tagged)144 static void compose_line (void *data, int line_number, unsigned char *line, int *tab,
145 		     char **(*get_line) (void *, int, int *, int *), int *tagged)
146 {
147     char **fields;
148     int i, num_fields;
149 
150     *line = 0;
151     *tagged = 0;
152 
153     if (!data)
154 	return;
155 
156     fields = (*get_line) (data, line_number, &num_fields, tagged);
157 
158     if (!fields)
159 	return;
160 
161     for (i = 0; i < num_fields; i++) {
162 	int l = 0, t, centred = 0;
163 	char *p;
164 	p = fields[i];
165 	t = tab[i] - this_text_width (p) - INTER_FIELD_SPACING;
166 	if (t < 0)
167 	    t = 0;
168 	if (p[0] == '\t') {
169 	    p++;
170 	    if (p[strlen (p) - 1] == '\t') {
171 		l = t - (t / 2);
172 		t /= 2;
173 		centred = 1;
174 	    } else {
175 		l = t;
176 		t = 0;
177 	    }
178 	}
179 	for (;;) {
180 	    l -= 127;
181 	    if (l >= 0) {
182 		*line++ = '\f';
183 		*line++ = (unsigned char) 127;
184 	    } else {
185 		l += 127;
186 		if (l) {
187 		    *line++ = '\f';
188 		    *line++ = (unsigned char) l;
189 		}
190 		break;
191 	    }
192 	}
193 	strcpy ((char *) line, p);
194 	line += strlen (p) - centred;
195 	if (!fields[i + 1])
196 	    break;
197 	t += INTER_FIELD_SPACING;
198 	for (;;) {
199 	    t -= 127;
200 	    if (t >= 0) {
201 		*line++ = '\f';
202 		*line++ = (unsigned char) 127;
203 	    } else {
204 		t += 127;
205 		if (t) {
206 		    *line++ = '\f';
207 		    *line++ = (unsigned char) t;
208 		}
209 		break;
210 	    }
211 	}
212     }
213     *line = 0;
214 }
215 
compose_line_cached(void * data,int l,int * tab,char ** (* get_line)(void *,int,int *,int *),int * tagged)216 static unsigned char *compose_line_cached (void *data, int l, int *tab, char **(*get_line) (void *, int, int *, int *), int *tagged)
217 {
218     static unsigned char line[4096];
219     static int c_tagged, c_l = -1;
220     if (c_l == l) {
221 	*tagged = c_tagged;
222 	return line;
223     }
224     compose_line (data, l, line, tab, get_line, tagged);
225     c_l = l;
226     c_tagged = *tagged;
227     return line;
228 }
229 
230 static long count_fielded_textbox_lines (CWidget * wdt);
231 void render_fielded_textbox (CWidget * w, int redrawall);
232 
link_scrollbar_to_fielded_textbox(CWidget * scrollbar,CWidget * textbox,XEvent * xevent,CEvent * cwevent,int whichscrbutton)233 void link_scrollbar_to_fielded_textbox (CWidget * scrollbar, CWidget * textbox, XEvent * xevent, CEvent * cwevent, int whichscrbutton)
234 {
235     int redrawtext = 0, count, c;
236     static int r = 0;
237     if ((xevent->type == ButtonRelease || xevent->type == MotionNotify) && whichscrbutton == 3) {
238 	redrawtext = CSetTextboxPos (textbox, TEXT_SET_LINE, (double) scrollbar->firstline * textbox->numlines / 65535.0);
239     } else if (xevent->type == ButtonPress && (cwevent->button == Button1 || cwevent->button == Button2)) {
240 	switch (whichscrbutton) {
241 	case 1:
242 	    redrawtext = CSetTextboxPos (textbox, TEXT_SET_LINE, textbox->firstline - (textbox->height / FONT_PIX_PER_LINE - 2));
243 	    break;
244 	case 2:
245 	    redrawtext = CSetTextboxPos (textbox, TEXT_SET_LINE, textbox->firstline - 1);
246 	    break;
247 	case 5:
248 	    redrawtext = CSetTextboxPos (textbox, TEXT_SET_LINE, textbox->firstline + 1);
249 	    break;
250 	case 4:
251 	    redrawtext = CSetTextboxPos (textbox, TEXT_SET_LINE, textbox->firstline + (textbox->height / FONT_PIX_PER_LINE - 2));
252 	    break;
253 	}
254     }
255     if (xevent->type == ButtonRelease)
256 	render_fielded_textbox (textbox, 0);
257     else {
258 	c = CCheckWindowEvent (xevent->xany.window, ButtonReleaseMask | ButtonMotionMask, 0);
259 	if (redrawtext) {
260 	    if (!c) {
261 		render_fielded_textbox (textbox, 0);
262 		r = 0;
263 	    } else {
264 		r = 1;
265 	    }
266 	} else if (c && r) {
267 	    render_fielded_textbox (textbox, 0);
268 	    r = 0;
269 	}
270     }
271     count = count_fielded_textbox_lines (textbox);
272     if (!count)
273 	count = 1;
274     scrollbar->firstline = (double) 65535.0 *textbox->firstline / (textbox->numlines ? textbox->numlines : 1);
275     scrollbar->numlines = (double) 65535.0 *count / (textbox->numlines ? textbox->numlines : 1);
276 }
277 
link_h_scrollbar_to_fielded_textbox(CWidget * scrollbar,CWidget * textbox,XEvent * xevent,CEvent * cwevent,int whichscrbutton)278 void link_h_scrollbar_to_fielded_textbox (CWidget * scrollbar, CWidget * textbox, XEvent * xevent, CEvent * cwevent, int whichscrbutton)
279 {
280     int redrawtext = 0, c;
281     static int r = 0;
282     if ((xevent->type == ButtonRelease || xevent->type == MotionNotify) && whichscrbutton == 3) {
283 	redrawtext = CSetTextboxPos (textbox, TEXT_SET_COLUMN, (double) scrollbar->firstline * (textbox->column / FONT_MEAN_WIDTH) / 65535.0);
284     } else if (xevent->type == ButtonPress && (cwevent->button == Button1 || cwevent->button == Button2)) {
285 	switch (whichscrbutton) {
286 	case 1:
287 	    redrawtext = CSetTextboxPos (textbox, TEXT_SET_COLUMN, textbox->firstcolumn - (textbox->width / FONT_MEAN_WIDTH - 2));
288 	    break;
289 	case 2:
290 	    redrawtext = CSetTextboxPos (textbox, TEXT_SET_COLUMN, textbox->firstcolumn - 1);
291 	    break;
292 	case 5:
293 	    redrawtext = CSetTextboxPos (textbox, TEXT_SET_COLUMN, textbox->firstcolumn + 1);
294 	    break;
295 	case 4:
296 	    redrawtext = CSetTextboxPos (textbox, TEXT_SET_COLUMN, textbox->firstcolumn + (textbox->width / FONT_MEAN_WIDTH - 2));
297 	    break;
298 	}
299     }
300     if (xevent->type == ButtonRelease)
301 	render_fielded_textbox (textbox, 0);
302     else {
303 	c = CCheckWindowEvent (xevent->xany.window, ButtonReleaseMask | ButtonMotionMask, 0);
304 	if (redrawtext) {
305 	    if (!c) {
306 		render_fielded_textbox (textbox, 0);
307 		r = 0;
308 	    } else {
309 		r = 1;
310 	    }
311 	} else if (c && r) {
312 	    render_fielded_textbox (textbox, 0);
313 	    r = 0;
314 	}
315     }
316     scrollbar->firstline = (double) 65535.0 *(textbox->firstcolumn * FONT_MEAN_WIDTH) / textbox->column;
317     scrollbar->numlines = (double) 65535.0 *(textbox->width - 6) / textbox->column;
318 }
319 
320 void edit_translate_xy (int xs, int ys, int *x, int *y);
321 void selection_clear (void);
322 static long current;
323 
xy(int x,int y,int * x_return,int * y_return)324 static void xy (int x, int y, int *x_return, int *y_return)
325 {
326     edit_translate_xy (x, y, x_return, y_return);
327 }
328 
cp(CWidget * w,int x,int y)329 static long cp (CWidget * w, int x, int y)
330 {
331     long q;
332     y = (y + w->firstline - 1) << 16;
333     if (y < 0)
334 	x = y = 0;
335     if (w->options & TEXTBOX_MARK_WHOLE_LINES)
336 	x = 0;
337     calc_text_pos_fielded_textbox (w, y, &q, --x);
338     return q;
339 }
340 
341 /* return 1 if not marked */
marks(CWidget * w,long * start,long * end)342 static int marks (CWidget * w, long *start, long *end)
343 {
344     if (w->mark1 == w->mark2)
345 	return 1;
346     *start = min (w->mark1, w->mark2);
347     *end = max (w->mark1, w->mark2);
348     return 0;
349 }
350 
351 extern int range (CWidget * w, long start, long end, int click);
352 
move_mark(CWidget * w)353 static void move_mark (CWidget * w)
354 {
355     w->mark2 = w->mark1 = current;
356 }
357 
fin_mark(CWidget * w)358 static void fin_mark (CWidget * w)
359 {
360     w->mark2 = w->mark1 = -1;
361 }
362 
release_mark(CWidget * w,XEvent * event)363 static void release_mark (CWidget * w, XEvent * event)
364 {
365     w->mark2 = current;
366     if (w->mark2 != w->mark1 && event) {
367 	selection_clear ();
368 	XSetSelectionOwner (CDisplay, XA_PRIMARY, w->winid, event->xbutton.time);
369 	XSetSelectionOwner (CDisplay, ATOM_ICCCM_P2P_CLIPBOARD, w->winid, event->xbutton.time);
370     }
371 }
372 
373 /* result must be free'd */
get_block(CWidget * w,long start_mark,long end_mark,int * type,int * l)374 static char *get_block (CWidget * w, long start_mark, long end_mark, int *type, int *l)
375 {
376     POOL *p;
377     int tagged, i;
378     unsigned char c, *t;
379     long x, y, a, b;
380     void *data;
381     CPushFont ("editor", 0);
382 
383     a = min (w->mark2, w->mark1);
384     b = max (w->mark2, w->mark1);
385     x = a & 0xFFFFL;
386     y = a >> 16;
387 
388     p = pool_init ();
389 
390     for (;; y++) {
391 	unsigned char *text;
392 	if (y < w->numlines)
393 	    data = w->hook;
394 	else
395 	    data = 0;
396 	text = compose_line_cached (data, y, w->tab, w->get_line, &tagged);
397 	for (;; x++) {
398 	    if (y == (b >> 16))
399 		if (x >= (b & 0xFFFFL))
400 		    goto done;
401 	    if (y > (b >> 16))
402 	        goto done;
403 	    c = text[x];
404 	    if (!c) {
405 		c = '\n';
406 		pool_write (p, (unsigned char *) &c, 1);
407 		break;
408 	    }
409 	    if (c == '\f') {
410 		int j;
411 #ifdef HAVE_DND
412 		if (w->options & TEXTBOX_FILE_LIST) {	/* this is a filelist, so only get the first field: i.e. the file name */
413 #else
414 		if (*type == DndFiles || *type == DndFile) {	/* this is a filelist, so only get the first field: i.e. the file name */
415 #endif
416 		    c = '\n';
417 		    pool_write (p, (unsigned char *) "\n", 1);
418 		    break;
419 		}
420     		j = text[++x];
421 		while ((j -= FONT_PER_CHAR(' ')) > 0)
422 		    pool_write (p, (unsigned char *) " ", 1);
423 		pool_write (p, (unsigned char *) " ", 1);
424 		continue;
425 	    }
426 	    if (c == '\v') {
427 		int j;
428     		j = pixmap_width (text[++x]);
429 		while ((j -= FONT_PER_CHAR(' ')) > 0)
430 		    pool_write (p, (unsigned char *) " ", 1);
431 		continue;
432 	    }
433 	    if (this_is_printable (c))
434 		pool_write (p, (unsigned char *) &c, 1);
435 	}
436 	x = 0;
437     }
438   done:
439     CPopFont ();
440 #ifdef HAVE_DND
441     *type = DndText;
442 #endif
443     *l = pool_length (p);
444     pool_null (p);
445 #ifdef HAVE_DND
446     if (!(w->options & TEXTBOX_FILE_LIST))
447 #else
448     if (!(*type == DndFiles || *type == DndFile))
449 #endif
450 	return (char *) pool_break (p);
451     t = (unsigned char *) CDndFileList ((char *) pool_start (p), l, &i);
452     pool_free (p);
453     if (i == 1)
454 	*type = DndFile;
455     else
456 	*type = DndFiles;
457     return (char *) t;
458 }
459 
460 static void move (CWidget * w, long click, int row)
461 {
462     int h;
463     current = click;
464     if (w->mark2 == -1)
465 	w->mark1 = current;
466     h = (w->height - BDR) / FONT_PIX_PER_LINE;
467     if (row > h && w->firstline < w->numlines - h - 2)
468 	CSetTextboxPos (w, TEXT_SET_LINE, w->firstline + row - h);
469     if (row < 1)
470 	CSetTextboxPos (w, TEXT_SET_LINE, w->firstline + row - 1);
471     w->mark2 = click;
472 }
473 
474 static void motion (CWidget * w, long click)
475 {
476     w->mark2 = click;
477 }
478 
479 struct mouse_funcs fielded_mouse_funcs = {
480     0,
481     (void (*)(int, int, int *, int *)) xy,
482     (long (*)(void *, int, int)) cp,
483     (int (*)(void *, long *, long *)) marks,
484     (int (*)(void *, long, long, long)) range,
485     (void (*)(void *)) fin_mark,
486     (void (*)(void *)) move_mark,
487     (void (*)(void *, XEvent *)) release_mark,
488     (char *(*)(void *, long, long, int *, int *)) get_block,
489     (void (*)(void *, long, int)) move,
490     (void (*)(void *, long)) motion,
491     0,
492     0,
493     0,
494     0,
495     DndFile
496 };
497 
498 CWidget *CDrawFieldedTextbox (const char *identifier, Window parent, int x, int y,
499 			      int width, int height, int line, int column,
500 			      char **(*get_line) (void *, int, int *, int *),
501 			      long options, void *data)
502 {
503     char *scroll;
504     int numlines;
505     CWidget *wdt;
506     int *tab, wf;
507 
508     int w, h, x_hint, y_hint;
509     CPushFont ("editor", 0);
510 
511     tab = get_field_sizes (data, &numlines, &wf, get_line);
512 
513     if (width == AUTO_WIDTH)
514 	w = wf + 6;
515     else
516 	w = width;
517     if (height == AUTO_HEIGHT)
518 	h = (max (1, numlines)) * FONT_PIX_PER_LINE + 6;
519     else
520 	h = height;
521 
522     wdt = CSetupWidget (identifier, parent, x, y,
523 			w, h, C_FIELDED_TEXTBOX_WIDGET, INPUT_KEY,
524 			color_palette (option_text_bg_normal), 1);
525 
526     xdnd_set_type_list (CDndClass, wdt->winid, xdnd_typelist_send[DndText]);
527 
528     wdt->eh = eh_fielded_textbox;
529     wdt->options = options | WIDGET_TAKES_SELECTION;
530 
531     wdt->firstline = line;
532     wdt->firstcolumn = column;
533     wdt->column = wf;
534     wdt->cursor = 0;
535     wdt->numlines = numlines;
536     wdt->tab = tab;
537     wdt->get_line = get_line;
538     wdt->hook = data;
539     wdt->funcs = mouse_funcs_new (wdt, &fielded_mouse_funcs);
540 
541     if (h > 80 && height != AUTO_HEIGHT) {
542 	wdt->vert_scrollbar = CDrawVerticalScrollbar (scroll = catstrs (identifier, ".vsc", NULL), parent,
543 			 x + w + WIDGET_SPACING, y, h, AUTO_WIDTH, 0, 0);
544 	CSetScrollbarCallback (wdt->vert_scrollbar->ident, wdt->ident, link_scrollbar_to_fielded_textbox);
545 	CGetHintPos (&x_hint, 0);
546     } else {
547 	x_hint = x + w + WIDGET_SPACING;
548     }
549 
550     if (w > 80 && width != AUTO_WIDTH) {
551 	wdt->hori_scrollbar = CDrawHorizontalScrollbar (scroll = catstrs (identifier, ".hsc", NULL), parent,
552 			 x, y + h + WIDGET_SPACING, w, (*look->get_fielded_textbox_hscrollbar_width) (), 0, 65535);
553 	CSetScrollbarCallback (wdt->hori_scrollbar->ident, wdt->ident, link_h_scrollbar_to_fielded_textbox);
554 	CGetHintPos (0, &y_hint);
555     } else {
556 	y_hint = y + h + WIDGET_SPACING;
557     }
558 
559     set_hint_pos (x_hint, y_hint);
560     CPopFont ();
561     return wdt;
562 }
563 
564 
565 CWidget *CRedrawFieldedTextbox (const char *identifier, int preserve)
566 {
567     int numlines;
568     CWidget *wdt;
569     int *tab, w;
570 
571     CPushFont ("editor", 0);
572     wdt = CIdent (identifier);
573     tab = get_field_sizes (wdt->hook, &numlines, &w, wdt->get_line);
574 
575     if (!preserve) {
576 	wdt->firstline = 0;
577 	wdt->firstcolumn = 0;
578 	wdt->cursor = 0;
579     }
580     wdt->column = w;
581     wdt->numlines = numlines;
582     if (wdt->tab)
583 	free (wdt->tab);
584     wdt->tab = tab;
585     wdt->mark1 = wdt->mark2 = -1;
586 
587     CSetColor (color_palette (option_text_bg_normal));
588     CRectangle (wdt->winid, 3, 3, wdt->width - 3 - 1, wdt->height - 3 - 1);
589     CExpose (identifier);
590 
591     CPopFont ();
592     return wdt;
593 }
594 
595 static int calc_text_pos_fielded_textbox (CWidget *w, long b, long *q, int l)
596 {
597     int x = 0, c, xn = 0, tagged;
598     unsigned char *text;
599     long k;
600     void *data = 0;
601 
602     if ((b >> 16) < w->numlines)
603 	data = w->hook;
604     text = compose_line_cached (data, b >> 16, w->tab, w->get_line, &tagged);
605 
606     k = b & 0xFFFFL;
607     if (k == 0xFFFFL)
608 	k = 0;
609     for (;;) {
610 	c = text[k];
611 	switch (c) {
612 	case '\0':
613 	case '\n':
614 	    *q = b;
615 	    return x;
616 	case '\f':
617 	    xn = x + text[k + 1];
618 	    if (xn > l) {
619 		*q = b;
620 		return x;
621 		break;
622 	    }
623 	    k++;
624 	    b++;
625 	    break;
626 	case '\b':
627 	case '\r':
628 	    xn = x + FONT_PER_CHAR(text[k + 1]);
629 	    if (xn > l) {
630 		*q = b;
631 		return x;
632 		break;
633 	    }
634 	    k++;
635 	    b++;
636 	    break;
637 	case '\v':
638 	    xn = x + pixmap_width (text[k + 1]);
639 	    if (xn > l) {
640 		*q = b;
641 		return x;
642 		break;
643 	    }
644 	    k++;
645 	    b++;
646 	    break;
647 	default:
648 	    xn = x + FONT_PER_CHAR(c);
649 	    break;
650 	}
651 	if (xn > l)
652 	    break;
653 	x = xn;
654 	b++;
655 	k++;
656     }
657     *q = b;
658     return x;
659 }
660 
661 extern int highlight_this_line;
662 
663 /* here upper 16 bits of q are the line, lowe 16, the column */
664 static void convert_text_fielded_textbox (CWidget * w, long q, cache_type *line, cache_type *eol, int x, int x_max, int row)
665 {
666     unsigned char *text;
667     int c, tagged, bold = 0, italic = 0;
668     cache_type *p;
669     long m1, m2, k;
670     void *data = 0;
671 
672     m1 = min (w->mark1, w->mark2);
673     m2 = max (w->mark1, w->mark2);
674 
675     if ((q >> 16) < w->numlines)
676 	data = w->hook;
677     text = compose_line_cached (data, q >> 16, w->tab, w->get_line, &tagged);
678 
679     k = q & 0xFFFFL;
680     if (k == 0xFFFFL)
681 	k = 0;
682 
683     p = line;
684     p->c.ch = p->_style = 0;
685     for (;;) {
686 	c = text[k];
687 	p[1]._style = p[1].c.ch = 0;
688 	p->c.fg = p->c.bg = 0xFF;	/* default background colors */
689 	if (highlight_this_line)
690 	    p->c.style |= MOD_HIGHLIGHTED;
691 	if (tagged)
692 	    p->c.style |= MOD_INVERSE;
693 	if (q >= m1 && q < m2)
694 	    p->c.style |= MOD_MARKED;
695 	switch (c) {
696 	case '\0':
697 	case '\n':
698 	    (p++)->c.ch = ' ';
699 	    if (p >= eol)
700 		goto end_loop;
701 	    if (highlight_this_line || tagged) {
702 		q--;
703 		k--;
704 		x += FONT_PER_CHAR(' ');
705 	    } else
706 		return;
707 	    break;
708 	case '\f':
709 	    k++;
710 	    q++;
711 	    p->c.style |= MOD_TAB;
712 	    (p++)->c.ch = text[k];
713 	    if (p >= eol)
714 		goto end_loop;
715 	    x += text[k];
716 	    break;
717 	case '\b':
718 	    bold = 2;
719 	    break;
720 	case '\r':
721 	    italic = 2;
722 	    break;
723 	case '\v':
724 	    k++;
725 	    q++;
726 	    p->c.style |= MOD_TAB;
727 	    (p++)->c.ch = text[k];
728 	    if (p >= eol)
729 		goto end_loop;
730 	    x += pixmap_width (text[k]);
731 	    break;
732 	default:
733 	    x += FONT_PER_CHAR(c);
734 	    p->c.ch = c;
735 	    if (italic > 0)
736 		p->c.style |= MOD_ITALIC;
737 	    if (bold > 0)
738 		p->c.style |= MOD_BOLD;
739 	    p++;
740 	    if (p >= eol)
741 		goto end_loop;
742 	    break;
743 	}
744 	italic--;
745 	bold--;
746 	if (x > x_max)
747 	    break;
748 	q++;
749 	k++;
750     }
751   end_loop:
752     p->c.ch = p->_style = 0;
753 }
754 
755 
756 static void fielded_text_print_line (CWidget * w, long b, int row)
757 {
758     edit_draw_proportional (w,
759 			    (void (*)(void *, long, cache_type *, cache_type *, int, int, int)) convert_text_fielded_textbox,
760       (int (*)(void *, long, long *, int)) calc_text_pos_fielded_textbox,
761 			    -w->firstcolumn * FONT_MEAN_WIDTH, w->winid,
762 			    w->width, b, row, row * FONT_PIX_PER_LINE + EDIT_TEXT_VERTICAL_OFFSET, 0,
763 			    1);
764 }
765 
766 
767 
768 /*
769    ->firstline   is line number of the top line in the window.
770    ->firstcolumn is column shift (positive).
771    ->current     is actual char position of first line in display.
772    ->numlines    is the total number of lines.
773    ->cursor      is the number of the highlighted line.
774    ->textlength  is the length of text excluding trailing NULL.
775    First three must be initialised to proper values (e.g. 0, 0 and 0).
776  */
777 
778 extern int EditExposeRedraw;
779 extern int EditClear;
780 extern unsigned long edit_normal_background_color;
781 
782 void render_fielded_textbox (CWidget * w, int redrawall)
783 {
784     int row, height, isfocussed, curs, i, x;
785     static Window last_win = 0;
786     static int last_firstcolumn = 0;
787     CPushFont ("editor", 0);
788     if (redrawall) {
789 	EditExposeRedraw = 1;
790 	EditClear = 1;
791     }
792     if (last_win == w->winid && last_firstcolumn != w->firstcolumn) {
793 	x = 0;
794         CSetColor (color_palette (option_text_bg_normal));
795 	for (i = 0;; i++) {
796 	    x += w->tab[i];
797 	    if (x >= w->column)
798 		break;
799 	    CLine (w->winid, x + LINE_OFFSET - (last_firstcolumn * FONT_MEAN_WIDTH), 3, x + LINE_OFFSET - (last_firstcolumn * FONT_MEAN_WIDTH), (w->numlines - w->firstline) * FONT_PIX_PER_LINE);
800 	}
801     }
802     last_firstcolumn = w->firstcolumn;
803     last_win = w->winid;
804 
805     height = w->height / FONT_PIX_PER_LINE;
806 
807     isfocussed = (w->winid == CGetFocus ());
808     curs = !(w->options & TEXTBOX_NO_CURSOR || w->mark1 != w->mark2);	/* don't draw the cursor line */
809 
810     edit_set_foreground_colors (color_palette (option_text_fg_normal), color_palette (option_text_fg_bold), color_palette (option_text_fg_italic));
811     edit_set_background_colors (color_palette (option_text_bg_normal), color_palette (0), color_palette (option_text_bg_marked), color_palette (9), color_palette (option_text_bg_highlighted));
812 
813     for (row = 0; row < height; row++) {
814 	if (row + w->firstline == w->cursor && isfocussed && curs)
815 	    highlight_this_line = 1;
816 	else
817 	    highlight_this_line = 0;
818 	fielded_text_print_line (w, (row + w->firstline) << 16, row);
819     }
820 
821     x = 0;
822     CSetColor (COLOR_FLAT);
823     for (i = 0;; i++) {
824 	if (!w->tab[i])
825 	    break;
826 	x += w->tab[i];
827 	if (x >= w->column)
828 	    break;
829 	CLine (w->winid, x + LINE_OFFSET - (w->firstcolumn * FONT_MEAN_WIDTH), 3, x + LINE_OFFSET - (w->firstcolumn * FONT_MEAN_WIDTH), (w->numlines - w->firstline) * FONT_PIX_PER_LINE + 3);
830     }
831     if ((w->numlines - w->firstline) * FONT_PIX_PER_LINE < w->height) {
832 	x = 0;
833         CSetColor (color_palette (option_text_bg_normal));
834 	for (i = 0;; i++) {
835 	    if (!w->tab[i])
836 		break;
837 	    x += w->tab[i];
838 	    if (x >= w->column)
839 		break;
840 	    CLine (w->winid, x + LINE_OFFSET - (w->firstcolumn * FONT_MEAN_WIDTH), (w->numlines - w->firstline) * FONT_PIX_PER_LINE + 3, x + LINE_OFFSET - (w->firstcolumn * FONT_MEAN_WIDTH), w->height - 3);
841 	}
842     }
843     EditExposeRedraw = 0;
844     EditClear = 0;
845 
846     (*look->render_fielded_textbox_tidbits) (w, isfocussed);
847 
848     CPopFont ();
849     return;
850 }
851 
852 /*
853    Count the number of lines that would be printed
854    by the above routine, but don't print anything.
855    If all is non-zero then count all the lines.
856  */
857 static long count_fielded_textbox_lines (CWidget * wdt)
858 {
859     long height;
860     height = wdt->height / FONT_PIX_PER_LINE;
861     if (wdt->numlines - wdt->firstline < height)
862 	return wdt->numlines - wdt->firstline;
863     return height;
864 }
865 
866 static void fielded_text_mouse_mark (CWidget * w, XEvent * event, CEvent * ce)
867 {
868     CPushFont ("editor", 0);
869     mouse_mark (event, ce->double_click, w->funcs);
870     CPopFont ();
871 }
872 
873 static struct selection fieldedtext_selection = {0, 0};
874 
875 /* gets selected text into selection structure, stripping nroff */
876 void fielded_text_get_selection (CWidget * w)
877 {
878     int type;
879     if (fieldedtext_selection.text)
880 	free (fieldedtext_selection.text);
881     fieldedtext_selection.text = (unsigned char *) get_block (w, 0, 0, &type, &fieldedtext_selection.len);
882     selection = fieldedtext_selection;
883 }
884 
885 
886 void selection_send (XSelectionRequestEvent * rq);
887 
888 int eh_fielded_textbox (CWidget * w, XEvent * xevent, CEvent * cwevent)
889 {
890     int handled = 0, redrawall = 0, count;
891 
892     switch (xevent->type) {
893     case SelectionRequest:
894 	fielded_text_get_selection (w);
895 	selection_send (&(xevent->xselectionrequest));
896 	return 1;
897     case Expose:
898 	if (!xevent->xexpose.count)
899 	    redrawall = 1;
900 	break;
901     case ClientMessage:
902 	w->mark1 = w->mark2 = 0;
903 	break;
904     case ButtonPress:
905 	CPushFont ("editor", 0);
906 	CFocus (w);
907 	if (xevent->xbutton.button == Button1)
908 	    w->cursor = (xevent->xbutton.y - BDR) / FONT_PIX_PER_LINE + w->firstline;
909 	if (w->cursor > w->numlines - 1)
910 	    w->cursor = w->numlines - 1;
911 	if (w->cursor < 0)
912 	    w->cursor = 0;
913 	cwevent->ident = w->ident;
914 	cwevent->xt = (xevent->xbutton.x - 7) / FONT_MEAN_WIDTH + w->firstcolumn;
915 	cwevent->yt = w->cursor;
916 	CPopFont ();
917     case ButtonRelease:
918     case MotionNotify:
919 	if (!xevent->xmotion.state && xevent->type == MotionNotify)
920 	    return 0;
921 	resolve_button (xevent, cwevent);
922 	fielded_text_mouse_mark (w, xevent, cwevent);
923 	break;
924     case FocusIn:
925     case FocusOut:
926 	break;
927     case KeyPress:
928 	cwevent->ident = w->ident;
929 	if (!(TEXTBOX_NO_KEYS & w->options)) {
930 	    if (w->options & TEXTBOX_FILE_LIST && w->hook) {
931 		if (cwevent->key == XK_Insert || cwevent->key == XK_KP_Insert) {
932 		    if (w->mark1 == w->mark2) {
933 			struct file_entry *f;
934 			f = (struct file_entry *) w->hook;
935 			if (f[w->cursor].options & FILELIST_TAGGED_ENTRY)
936 			    f[w->cursor].options &= (0xFFFFFFFFUL - FILELIST_TAGGED_ENTRY);
937 			else
938 			    f[w->cursor].options |= FILELIST_TAGGED_ENTRY;
939 			CTextboxCursorMove (w, CK_Down);
940 			handled = 1;
941 			break;
942 		    }
943 		}
944 	    }
945 	    handled = CTextboxCursorMove (w, cwevent->command);
946 	}
947 	break;
948     default:
949 	return 0;
950     }
951 
952 /* Now draw the changed text box, count will contain
953    the number of textlines displayed */
954     render_fielded_textbox (w, redrawall);
955     count = count_fielded_textbox_lines (w);
956 
957 /* now update the scrollbar position */
958     if (w->vert_scrollbar && w->numlines) {
959 	w->vert_scrollbar->firstline = (double) 65535.0 *w->firstline / (w->numlines ? w->numlines : 1);
960 	w->vert_scrollbar->numlines = (double) 65535.0 *count / (w->numlines ? w->numlines : 1);
961 	w->vert_scrollbar->options = 0;
962 	render_scrollbar (w->vert_scrollbar);
963     }
964     if (w->hori_scrollbar && w->column) {
965 	w->hori_scrollbar->firstline = (double) 65535.0 *(w->firstcolumn * FONT_MEAN_WIDTH) / w->column;
966 	w->hori_scrollbar->numlines = (double) 65535.0 *(w->width - 6) / w->column;
967 	w->hori_scrollbar->options = 0;
968 	render_scrollbar (w->hori_scrollbar);
969     }
970     return handled;
971 }
972 
973