xref: /plan9/sys/src/libcontrol/text.c (revision da79363fb9fd5a76e4ca65f2f996542f71fcdfef)
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <thread.h>
5 #include <mouse.h>
6 #include <keyboard.h>
7 #include <control.h>
8 
9 static int debug = 0;
10 
11 typedef struct Text Text;
12 
13 struct Text
14 {
15 	Control;
16 	int		border;
17 	int		topline;
18 	int		scroll;
19 	int		nvis;
20 	int		lastbut;
21 	CFont	*font;
22 	CImage	*image;
23 	CImage	*textcolor;
24 	CImage	*bordercolor;
25 	CImage	*selectcolor;
26 	CImage	*selectingcolor;
27 	Rune		**line;
28 	int		selectmode;	// Selsingle, Selmulti
29 	int		selectstyle;	// Seldown, Selup (use Selup only with Selsingle)
30 	uchar	*selected;
31 	int		nline;
32 	int		warp;
33 	int		align;
34 	int		sel;		// line nr of selection made by last button down
35 	int		but;		// last button down (still being hold)
36 	int		offsel;	// we are on selection
37 };
38 
39 enum
40 {
41 	Selsingle,
42 	Selmulti,
43 	Seldown,
44 	Selup,
45 };
46 
47 enum{
48 	EAccumulate,
49 	EAdd,
50 	EAlign,
51 	EBorder,
52 	EBordercolor,
53 	EClear,
54 	EDelete,
55 	EFocus,
56 	EFont,
57 	EHide,
58 	EImage,
59 	ERect,
60 	EReplace,
61 	EReveal,
62 	EScroll,
63 	ESelect,
64 	ESelectcolor,
65 	ESelectingcolor,
66 	ESelectmode,
67 	ESelectstyle,
68 	EShow,
69 	ESize,
70 	ETextcolor,
71 	ETopline,
72 	EValue,
73 	EWarp,
74 };
75 
76 static char *cmds[] = {
77 	[EAccumulate] =	"accumulate",
78 	[EAdd] =			"add",
79 	[EAlign] =			"align",
80 	[EBorder] =		"border",
81 	[EBordercolor] =	"bordercolor",
82 	[EClear] =			"clear",
83 	[EDelete] =		"delete",
84 	[EFocus] = 		"focus",
85 	[EFont] =			"font",
86 	[EHide] =			"hide",
87 	[EImage] =		"image",
88 	[ERect] =			"rect",
89 	[EReplace] =		"replace",
90 	[EReveal] =		"reveal",
91 	[EScroll] =			"scroll",
92 	[ESelect] =		"select",
93 	[ESelectcolor] =	"selectcolor",
94 	[ESelectingcolor] =	"selectingcolor",
95 	[ESelectmode] =	"selectmode",
96 	[ESelectstyle] =		"selectstyle",
97 	[EShow] =			"show",
98 	[ESize] =			"size",
99 	[ETextcolor] =		"textcolor",
100 	[ETopline] =		"topline",
101 	[EValue] =			"value",
102 	[EWarp] =			"warp",
103 	nil
104 };
105 
106 static void	textshow(Text*);
107 static void	texttogglei(Text*, int);
108 static int	textline(Text*, Point);
109 static int	texttoggle(Text*, Point);
110 
111 static void
textmouse(Control * c,Mouse * m)112 textmouse(Control *c, Mouse *m)
113 {
114 	Text *t;
115 	int sel;
116 
117 	t = (Text*)c;
118 	if (debug) fprint(2, "textmouse %s t->lastbut %d; m->buttons %d\n", t->name, t->lastbut, m->buttons);
119 	if (t->warp >= 0)
120 		return;
121 	if ((t->selectstyle == Selup) && (m->buttons&7)) {
122 		sel = textline(t, m->xy);
123 		if (t->sel >= 0) {
124 //			if (debug) fprint(2, "textmouse Selup %q sel=%d t->sel=%d t->but=%d\n",
125 //						t->name, sel, t->sel, t->but);
126 			t->offsel = (sel == t->sel) ? 0 : 1;
127 			if ((sel == t->sel &&
128 				    ((t->selected[t->sel] && !t->but) ||
129 				     ((!t->selected[t->sel]) && t->but))) ||
130 			    (sel != t->sel &&
131 				     ((t->selected[t->sel] && t->but) ||
132                                          ((!t->selected[t->sel]) && (!t->but))))) {
133 				texttogglei(t, t->sel);
134 			}
135 		}
136 	}
137 	if(t->lastbut != (m->buttons&7)){
138 		if(m->buttons & 7){
139 			sel = texttoggle(t, m->xy);
140 			if(sel >= 0) {
141 				if (t->selectstyle == Seldown) {
142 					chanprint(t->event, "%q: select %d %d",
143 						t->name, sel, t->selected[sel] ? (m->buttons & 7) : 0);
144 					if (debug) fprint(2, "textmouse Seldown event %q: select %d %d\n",
145 						t->name, sel, t->selected[sel] ? (m->buttons & 7) : 0);
146 				} else {
147 					if (debug) fprint(2, "textmouse Selup no event yet %q: select %d %d\n",
148 						t->name, sel, t->selected[sel] ? (m->buttons & 7) : 0);
149 					t->sel = sel;
150 					t->but =  t->selected[sel] ? (m->buttons & 7) : 0;
151 				}
152 			}
153 		} else if (t->selectstyle == Selup) {
154 			sel = textline(t, m->xy);
155 			t->offsel = 0;
156 			if ((sel >= 0) && (sel == t->sel)) {
157 				chanprint(t->event, "%q: select %d %d",
158 					t->name, sel, t->but);
159 				if (debug) fprint(2, "textmouse Selup event %q: select %d %d\n",
160 					t->name, sel, t->but);
161 			} else if (sel != t->sel) {
162 				if  ((t->selected[t->sel] && t->but) ||
163                                          ((!t->selected[t->sel]) && (!t->but))) {
164 					texttogglei(t, t->sel);
165 				} else {
166 					textshow(t);
167 				}
168 				if (debug) fprint(2, "textmouse Selup cancel %q: select %d %d\n",
169 					t->name, sel, t->but);
170 			}
171 			t->sel = -1;
172 			t->but = 0;
173 		}
174 		t->lastbut = m->buttons & 7;
175 	}
176 }
177 
178 static void
textfree(Control * c)179 textfree(Control *c)
180 {
181 	int i;
182 	Text *t;
183 
184 	t = (Text*)c;
185 	_putctlfont(t->font);
186 	_putctlimage(t->image);
187 	_putctlimage(t->textcolor);
188 	_putctlimage(t->bordercolor);
189 	_putctlimage(t->selectcolor);
190 	_putctlimage(t->selectingcolor);
191 	for(i=0; i<t->nline; i++)
192 		free(t->line[i]);
193 	free(t->line);
194 	free(t->selected);
195 }
196 
197 static void
textshow(Text * t)198 textshow(Text *t)
199 {
200 	Rectangle r, tr;
201 	Point p;
202 	int i, ntext;
203 	Font *f;
204 	Rune *text;
205 
206 	if (t->hidden)
207 		return;
208 	r = t->rect;
209 	f = t->font->font;
210 	draw(t->screen, r, t->image->image, nil, t->image->image->r.min);
211 	if(t->border > 0){
212 		border(t->screen, r, t->border, t->bordercolor->image, t->bordercolor->image->r.min);
213 		r = insetrect(r, t->border);
214 	}
215 	tr = r;
216 	t->nvis = Dy(r)/f->height;
217 	for(i=t->topline; i<t->nline && i<t->topline+t->nvis; i++){
218 		text = t->line[i];
219 		ntext = runestrlen(text);
220 		r.max.y = r.min.y+f->height;
221 		if(t->sel == i && t->offsel)
222 			draw(t->screen, r, t->selectingcolor->image, nil, ZP);
223 		else if(t->selected[i])
224 			draw(t->screen, r, t->selectcolor->image, nil, ZP);
225 		p = _ctlalignpoint(r,
226 			runestringnwidth(f, text, ntext),
227 			f->height, t->align);
228 		if(t->warp == i) {
229 			Point p2;
230 			 p2.x = p.x + 0.5*runestringnwidth(f, text, ntext);
231 			 p2.y = p.y + 0.5*f->height;
232 			moveto(t->controlset->mousectl, p2);
233 			t->warp = -1;
234 		}
235 		_string(t->screen, p, t->textcolor->image,
236 			ZP, f, nil, text, ntext, tr,
237 			nil, ZP, SoverD);
238 		r.min.y += f->height;
239 	}
240 	flushimage(display, 1);
241 }
242 
243 static void
textctl(Control * c,CParse * cp)244 textctl(Control *c, CParse *cp)
245 {
246 	int cmd, i, n;
247 	Rectangle r;
248 	Text *t;
249 	Rune *rp;
250 
251 	t = (Text*)c;
252 	cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
253 	switch(cmd){
254 	default:
255 		ctlerror("%q: unrecognized message '%s'", t->name, cp->str);
256 		break;
257 	case EAlign:
258 		_ctlargcount(t, cp, 2);
259 		t->align = _ctlalignment(cp->args[1]);
260 		break;
261 	case EBorder:
262 		_ctlargcount(t, cp, 2);
263 		if(cp->iargs[1] < 0)
264 			ctlerror("%q: bad border: %c", t->name, cp->str);
265 		t->border = cp->iargs[1];
266 		break;
267 	case EBordercolor:
268 		_ctlargcount(t, cp, 2);
269 		_setctlimage(t, &t->bordercolor, cp->args[1]);
270 		break;
271 	case EClear:
272 		_ctlargcount(t, cp, 1);
273 		for(i=0; i<t->nline; i++)
274 			free(t->line[i]);
275 		free(t->line);
276 		free(t->selected);
277 		t->line = ctlmalloc(sizeof(Rune*));
278 		t->selected = ctlmalloc(1);
279 		t->nline = 0;
280 		textshow(t);
281 		break;
282 	case EDelete:
283 		_ctlargcount(t, cp, 2);
284 		i = cp->iargs[1];
285 		if(i<0 || i>=t->nline)
286 			ctlerror("%q: line number out of range: %s", t->name, cp->str);
287 		free(t->line[i]);
288 		memmove(t->line+i, t->line+i+1, (t->nline-(i+1))*sizeof(Rune*));
289 		memmove(t->selected+i, t->selected+i+1, t->nline-(i+1));
290 		t->nline--;
291 		textshow(t);
292 		break;
293 	case EFocus:
294 		break;
295 	case EFont:
296 		_ctlargcount(t, cp, 2);
297 		_setctlfont(t, &t->font, cp->args[1]);
298 		break;
299 	case EHide:
300 		_ctlargcount(t, cp, 1);
301 		t->hidden = 1;
302 		break;
303 	case EImage:
304 		_ctlargcount(t, cp, 2);
305 		_setctlimage(t, &t->image, cp->args[1]);
306 		break;
307 	case ERect:
308 		_ctlargcount(t, cp, 5);
309 		r.min.x = cp->iargs[1];
310 		r.min.y = cp->iargs[2];
311 		r.max.x = cp->iargs[3];
312 		r.max.y = cp->iargs[4];
313 		if(Dx(r)<=0 || Dy(r)<=0)
314 			ctlerror("%q: bad rectangle: %s", t->name, cp->str);
315 		t->rect = r;
316 		t->nvis = (Dy(r)-2*t->border)/t->font->font->height;
317 		break;
318 	case EReplace:
319 		_ctlargcount(t, cp, 3);
320 		i = cp->iargs[1];
321 		if(i<0 || i>=t->nline)
322 			ctlerror("%q: line number out of range: %s", t->name, cp->str);
323 		free(t->line[i]);
324 		t->line[i] = _ctlrunestr(cp->args[2]);
325 		textshow(t);
326 		break;
327 	case EReveal:
328 		_ctlargcount(t, cp, 1);
329 		t->hidden = 0;
330 		textshow(t);
331 		break;
332 	case EScroll:
333 		_ctlargcount(t, cp, 2);
334 		t->scroll = cp->iargs[1];
335 		break;
336 	case ESelect:
337 		if(cp->nargs!=2 && cp->nargs!=3)
338 	badselect:
339 			ctlerror("%q: bad select message: %s", t->name, cp->str);
340 		if(cp->nargs == 2){
341 			if(strcmp(cp->args[1], "all") == 0){
342 				memset(t->selected, 1, t->nline);
343 				break;
344 			}
345 			if(strcmp(cp->args[1], "none") == 0){
346 				memset(t->selected, 0, t->nline);
347 				break;
348 			}
349 			if(cp->args[1][0]<'0' && '9'<cp->args[1][0])
350 				goto badselect;
351 			texttogglei(t, cp->iargs[1]);
352 			break;
353 		}
354 		if(cp->iargs[1]<0 || cp->iargs[1]>=t->nline)
355 			ctlerror("%q: selection index out of range (nline %d): %s", t->name, t->nline, cp->str);
356 		if(t->selected[cp->iargs[1]] != (cp->iargs[2]!=0))
357 			texttogglei(t, cp->iargs[1]);
358 		break;
359 	case ESelectcolor:
360 		_ctlargcount(t, cp, 2);
361 		_setctlimage(t, &t->selectcolor, cp->args[1]);
362 		break;
363 	case ESelectmode:
364 		_ctlargcount(t, cp, 2);
365 		if(strcmp(cp->args[1], "single") == 0)
366 			t->selectmode = Selsingle;
367 		else if(strncmp(cp->args[1], "multi", 5) == 0)
368 			t->selectmode = Selmulti;
369 		break;
370 	case ESelectstyle:
371 		_ctlargcount(t, cp, 2);
372 		 if(strcmp(cp->args[1], "down") == 0)
373 			t->selectstyle = Seldown;
374 		else if(strcmp(cp->args[1], "up") == 0)
375 			t->selectstyle = Selup;
376 		break;
377 	case EShow:
378 		_ctlargcount(t, cp, 1);
379 		textshow(t);
380 		break;
381 	case ESize:
382 		if (cp->nargs == 3)
383 			r.max = Pt(10000, 10000);
384 		else{
385 			_ctlargcount(t, cp, 5);
386 			r.max.x = cp->iargs[3];
387 			r.max.y = cp->iargs[4];
388 		}
389 		r.min.x = cp->iargs[1];
390 		r.min.y = cp->iargs[2];
391 		if(r.min.x<=0 || r.min.y<=0 || r.max.x<=0 || r.max.y<=0 || r.max.x < r.min.x || r.max.y < r.min.y)
392 			ctlerror("%q: bad sizes: %s", t->name, cp->str);
393 		t->size.min = r.min;
394 		t->size.max = r.max;
395 		break;
396 	case ETextcolor:
397 		_ctlargcount(t, cp, 2);
398 		_setctlimage(t, &t->textcolor, cp->args[1]);
399 		break;
400 	case ETopline:
401 		_ctlargcount(t, cp, 2);
402 		i = cp->iargs[1];
403 		if(i < 0)
404 			i = 0;
405 		if(i > t->nline)
406 			i = t->nline;
407 		if(t->topline != i){
408 			t->topline = i;
409 			textshow(t);
410 		}
411 		break;
412 	case EValue:
413 		/* set contents to single line */
414 		/* free existing text and fall through to add */
415 		for(i=0; i<t->nline; i++){
416 			free(t->line[i]);
417 			t->line[i] = nil;
418 		}
419 		t->nline = 0;
420 		t->topline = 0;
421 		/* fall through */
422 	case EAccumulate:
423 	case EAdd:
424 		switch (cp->nargs) {
425 		default:
426 			ctlerror("%q: wrong argument count in '%s'", t->name, cp->str);
427 		case 2:
428 			n = t->nline;
429 			break;
430 		case 3:
431 			n = cp->iargs[1];
432 			if(n<0 || n>t->nline)
433 				ctlerror("%q: line number out of range: %s", t->name, cp->str);
434 			break;
435 		}
436 		rp = _ctlrunestr(cp->args[cp->nargs-1]);
437 		t->line = ctlrealloc(t->line, (t->nline+1)*sizeof(Rune*));
438 		memmove(t->line+n+1, t->line+n, (t->nline-n)*sizeof(Rune*));
439 		t->line[n] = rp;
440 		t->selected = ctlrealloc(t->selected, t->nline+1);
441 		memmove(t->selected+n+1, t->selected+n, t->nline-n);
442 		t->selected[n] = (t->selectmode==Selmulti && cmd!=EAccumulate);
443 		t->nline++;
444 		if(t->scroll) {
445 			if(n > t->topline + (t->nvis - 1)){
446 				t->topline = n - (t->nvis - 1);
447 				if(t->topline < 0)
448 					t->topline = 0;
449 			}
450 			if(n < t->topline)
451 				t->topline = n;
452 		}
453 		if(cmd != EAccumulate)
454 			if(t->scroll || t->nline<=t->topline+t->nvis)
455 				textshow(t);
456 		break;
457 	case EWarp:
458 		_ctlargcount(t, cp, 2);
459 		i = cp->iargs[1];
460 		if(i <0 || i>=t->nline)
461 			ctlerror("%q: selection index out of range (nline %d): %s", t->name, t->nline, cp->str);
462 		if(i < t->topline || i >=  t->topline+t->nvis){
463 			t->topline = i;
464 		}
465 		t->warp = cp->iargs[1];
466 		textshow(t);
467 		t->warp = -1;
468 		break;
469 	}
470 }
471 
472 static void
texttogglei(Text * t,int i)473 texttogglei(Text *t, int i)
474 {
475 	int prev;
476 
477 	if(t->selectmode == Selsingle){
478 		/* clear the others */
479 		prev = t->selected[i];
480 		memset(t->selected, 0, t->nline);
481 		t->selected[i] = prev;
482 	}
483 	t->selected[i] ^= 1;
484 	textshow(t);
485 }
486 
487 static int
textline(Text * t,Point p)488 textline(Text *t, Point p)
489 {
490 	Rectangle r;
491 	int i;
492 
493 	r = t->rect;
494 	if(t->border > 0)
495 		r = insetrect(r, t->border);
496 	if(!ptinrect(p, r))
497 		return -1;
498 	i = (p.y-r.min.y)/t->font->font->height;
499 	i += t->topline;
500 	if(i >= t->nline)
501 		return -1;
502 	return i;
503 }
504 
505 static int
texttoggle(Text * t,Point p)506 texttoggle(Text *t, Point p)
507 {
508 	int i;
509 
510 	i = textline(t, p);
511 	if (i >= 0)
512 		texttogglei(t, i);
513 	return i;
514 }
515 
516 Control*
createtext(Controlset * cs,char * name)517 createtext(Controlset *cs, char *name)
518 {
519 	Text *t;
520 
521 	t = (Text*)_createctl(cs, "text", sizeof(Text), name);
522 	t->line = ctlmalloc(sizeof(Rune*));
523 	t->selected = ctlmalloc(1);
524 	t->nline = 0;
525 	t->image = _getctlimage("white");
526 	t->textcolor = _getctlimage("black");
527 	t->bordercolor = _getctlimage("black");
528 	t->selectcolor = _getctlimage("yellow");
529 	t->selectingcolor = _getctlimage("paleyellow");
530 	t->font = _getctlfont("font");
531 	t->selectmode = Selsingle;
532 	t->selectstyle = Selup; // Seldown;
533 	t->lastbut = 0;
534 	t->mouse = textmouse;
535 	t->ctl = textctl;
536 	t->exit = textfree;
537 	t->warp = -1;
538 	t->sel = -1;
539 	t->offsel = 0;
540 	t->but = 0;
541 	return (Control *)t;
542 }
543