xref: /plan9/sys/src/libcontrol/text.c (revision ff8c3af2f44d95267f67219afa20ba82ff6cf7e4)
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 typedef struct Text Text;
10 
11 struct Text
12 {
13 	Control;
14 	int		border;
15 	int		topline;
16 	int		scroll;
17 	int		nvis;
18 	int		lastbut;
19 	CFont	*font;
20 	CImage	*image;
21 	CImage	*textcolor;
22 	CImage	*bordercolor;
23 	CImage	*selectcolor;
24 	Rune		**line;
25 	int		selectmode;
26 	uchar	*selected;
27 	int		nline;
28 	int		align;
29 };
30 
31 enum
32 {
33 	Selsingle,
34 	Selmulti,
35 };
36 
37 enum{
38 	EAccumulate,
39 	EAdd,
40 	EAlign,
41 	EBorder,
42 	EBordercolor,
43 	EClear,
44 	EDelete,
45 	EFocus,
46 	EFont,
47 	EHide,
48 	EImage,
49 	ERect,
50 	EReplace,
51 	EReveal,
52 	EScroll,
53 	ESelect,
54 	ESelectcolor,
55 	ESelectmode,
56 	EShow,
57 	ESize,
58 	ETextcolor,
59 	ETopline,
60 	EValue,
61 };
62 
63 static char *cmds[] = {
64 	[EAccumulate] =	"accumulate",
65 	[EAdd] =			"add",
66 	[EAlign] =			"align",
67 	[EBorder] =		"border",
68 	[EBordercolor] =	"bordercolor",
69 	[EClear] =			"clear",
70 	[EDelete] =		"delete",
71 	[EFocus] = 		"focus",
72 	[EFont] =			"font",
73 	[EHide] =			"hide",
74 	[EImage] =		"image",
75 	[ERect] =			"rect",
76 	[EReplace] =		"replace",
77 	[EReveal] =		"reveal",
78 	[EScroll] =			"scroll",
79 	[ESelect] =		"select",
80 	[ESelectcolor] =	"selectcolor",
81 	[ESelectmode] =	"selectmode",
82 	[EShow] =			"show",
83 	[ESize] =			"size",
84 	[ETextcolor] =		"textcolor",
85 	[ETopline] =		"topline",
86 	[EValue] =			"value",
87 	nil
88 };
89 
90 static void	textshow(Text*);
91 static void	texttogglei(Text*, int);
92 static int	texttoggle(Text*, Point);
93 
94 static void
95 textmouse(Control *c, Mouse *m)
96 {
97 	Text *t;
98 	int sel;
99 
100 	t = (Text*)c;
101 	if(t->lastbut != (m->buttons&7)){
102 		if(m->buttons & 7){
103 			sel = texttoggle(t, m->xy);
104 			if(sel >= 0)
105 				chanprint(t->event, "%q: select %d %d",
106 					t->name, sel, t->selected[sel] ? (m->buttons & 7) : 0);
107 		}
108 		t->lastbut = m->buttons & 7;
109 	}
110 }
111 
112 static void
113 textfree(Control *c)
114 {
115 	int i;
116 	Text *t;
117 
118 	t = (Text*)c;
119 	_putctlfont(t->font);
120 	_putctlimage(t->image);
121 	_putctlimage(t->textcolor);
122 	_putctlimage(t->bordercolor);
123 	_putctlimage(t->selectcolor);
124 	for(i=0; i<t->nline; i++)
125 		free(t->line[i]);
126 	free(t->line);
127 	free(t->selected);
128 }
129 
130 static void
131 textshow(Text *t)
132 {
133 	Rectangle r, tr;
134 	Point p;
135 	int i, ntext;
136 	Font *f;
137 	Rune *text;
138 
139 	if (t->hidden)
140 		return;
141 	r = t->rect;
142 	f = t->font->font;
143 	draw(t->screen, r, t->image->image, nil, t->image->image->r.min);
144 	if(t->border > 0){
145 		border(t->screen, r, t->border, t->bordercolor->image, t->bordercolor->image->r.min);
146 		r = insetrect(r, t->border);
147 	}
148 	tr = r;
149 	t->nvis = Dy(r)/f->height;
150 	for(i=t->topline; i<t->nline && i<t->topline+t->nvis; i++){
151 		text = t->line[i];
152 		ntext = runestrlen(text);
153 		r.max.y = r.min.y+f->height;
154 		if(t->selected[i])
155 			draw(t->screen, r, t->selectcolor->image, nil, ZP);
156 		p = _ctlalignpoint(r,
157 			runestringnwidth(f, text, ntext),
158 			f->height, t->align);
159 		_string(t->screen, p, t->textcolor->image,
160 			ZP, f, nil, text, ntext, tr,
161 			nil, ZP, SoverD);
162 		r.min.y += f->height;
163 	}
164 	flushimage(display, 1);
165 }
166 
167 static void
168 textctl(Control *c, CParse *cp)
169 {
170 	int cmd, i, n;
171 	Rectangle r;
172 	Text *t;
173 	Rune *rp;
174 
175 	t = (Text*)c;
176 	cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
177 	switch(cmd){
178 	default:
179 		ctlerror("%q: unrecognized message '%s'", t->name, cp->str);
180 		break;
181 	case EAlign:
182 		_ctlargcount(t, cp, 2);
183 		t->align = _ctlalignment(cp->args[1]);
184 		break;
185 	case EBorder:
186 		_ctlargcount(t, cp, 2);
187 		if(cp->iargs[1] < 0)
188 			ctlerror("%q: bad border: %c", t->name, cp->str);
189 		t->border = cp->iargs[1];
190 		break;
191 	case EBordercolor:
192 		_ctlargcount(t, cp, 2);
193 		_setctlimage(t, &t->bordercolor, cp->args[1]);
194 		break;
195 	case EClear:
196 		_ctlargcount(t, cp, 1);
197 		for(i=0; i<t->nline; i++)
198 			free(t->line[i]);
199 		free(t->line);
200 		free(t->selected);
201 		t->line = ctlmalloc(sizeof(Rune*));
202 		t->selected = ctlmalloc(1);
203 		t->nline = 0;
204 		textshow(t);
205 		break;
206 	case EDelete:
207 		_ctlargcount(t, cp, 2);
208 		i = cp->iargs[1];
209 		if(i<0 || i>=t->nline)
210 			ctlerror("%q: line number out of range: %s", t->name, cp->str);
211 		free(t->line[i]);
212 		memmove(t->line+i, t->line+i+1, (t->nline-(i+1))*sizeof(Rune*));
213 		memmove(t->selected+i, t->selected+i+1, t->nline-(i+1));
214 		t->nline--;
215 		textshow(t);
216 		break;
217 	case EFocus:
218 		break;
219 	case EFont:
220 		_ctlargcount(t, cp, 2);
221 		_setctlfont(t, &t->font, cp->args[1]);
222 		break;
223 	case EHide:
224 		_ctlargcount(t, cp, 1);
225 		t->hidden = 1;
226 		break;
227 	case EImage:
228 		_ctlargcount(t, cp, 2);
229 		_setctlimage(t, &t->image, cp->args[1]);
230 		break;
231 	case ERect:
232 		_ctlargcount(t, cp, 5);
233 		r.min.x = cp->iargs[1];
234 		r.min.y = cp->iargs[2];
235 		r.max.x = cp->iargs[3];
236 		r.max.y = cp->iargs[4];
237 		if(Dx(r)<=0 || Dy(r)<=0)
238 			ctlerror("%q: bad rectangle: %s", t->name, cp->str);
239 		t->rect = r;
240 		t->nvis = (Dy(r)-2*t->border)/t->font->font->height;
241 		break;
242 	case EReplace:
243 		_ctlargcount(t, cp, 3);
244 		i = cp->iargs[1];
245 		if(i<0 || i>=t->nline)
246 			ctlerror("%q: line number out of range: %s", t->name, cp->str);
247 		free(t->line[i]);
248 		t->line[i] = _ctlrunestr(cp->args[2]);
249 		textshow(t);
250 		break;
251 	case EReveal:
252 		_ctlargcount(t, cp, 1);
253 		t->hidden = 0;
254 		textshow(t);
255 		break;
256 	case EScroll:
257 		_ctlargcount(t, cp, 2);
258 		t->scroll = cp->iargs[1];
259 		break;
260 	case ESelect:
261 		if(cp->nargs!=2 && cp->nargs!=3)
262 	badselect:
263 			ctlerror("%q: bad select message: %s", t->name, cp->str);
264 		if(cp->nargs == 2){
265 			if(strcmp(cp->args[1], "all") == 0){
266 				memset(t->selected, 1, t->nline);
267 				break;
268 			}
269 			if(strcmp(cp->args[1], "none") == 0){
270 				memset(t->selected, 0, t->nline);
271 				break;
272 			}
273 			if(cp->args[1][0]<'0' && '9'<cp->args[1][0])
274 				goto badselect;
275 			texttogglei(t, cp->iargs[1]);
276 			break;
277 		}
278 		if(cp->iargs[1]<0 || cp->iargs[1]>=t->nline)
279 			ctlerror("%q: selection index out of range (nline %d): %s", t->name, t->nline, cp->str);
280 		if(t->selected[cp->iargs[1]] != (cp->iargs[2]!=0))
281 			texttogglei(t, cp->iargs[1]);
282 		break;
283 	case ESelectcolor:
284 		_ctlargcount(t, cp, 2);
285 		_setctlimage(t, &t->selectcolor, cp->args[1]);
286 		break;
287 	case ESelectmode:
288 		_ctlargcount(t, cp, 2);
289 		if(strcmp(cp->args[1], "single") == 0)
290 			t->selectmode = Selsingle;
291 		else if(strncmp(cp->args[1], "multi", 5) == 0)
292 			t->selectmode = Selmulti;
293 		break;
294 	case EShow:
295 		_ctlargcount(t, cp, 1);
296 		textshow(t);
297 		break;
298 	case ESize:
299 		if (cp->nargs == 3)
300 			r.max = Pt(10000, 10000);
301 		else{
302 			_ctlargcount(t, cp, 5);
303 			r.max.x = cp->iargs[3];
304 			r.max.y = cp->iargs[4];
305 		}
306 		r.min.x = cp->iargs[1];
307 		r.min.y = cp->iargs[2];
308 		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)
309 			ctlerror("%q: bad sizes: %s", t->name, cp->str);
310 		t->size.min = r.min;
311 		t->size.max = r.max;
312 		break;
313 	case ETextcolor:
314 		_ctlargcount(t, cp, 2);
315 		_setctlimage(t, &t->textcolor, cp->args[1]);
316 		break;
317 	case ETopline:
318 		_ctlargcount(t, cp, 2);
319 		i = cp->iargs[1];
320 		if(i < 0)
321 			i = 0;
322 		if(i > t->nline)
323 			i = t->nline;
324 		if(t->topline != i){
325 			t->topline = i;
326 			textshow(t);
327 		}
328 		break;
329 	case EValue:
330 		/* set contents to single line */
331 		/* free existing text and fall through to add */
332 		for(i=0; i<t->nline; i++){
333 			free(t->line[i]);
334 			t->line[i] = nil;
335 		}
336 		t->nline = 0;
337 		t->topline = 0;
338 		/* fall through */
339 	case EAccumulate:
340 	case EAdd:
341 		switch (cp->nargs) {
342 		default:
343 			ctlerror("%q: wrong argument count in '%s'", t->name, cp->str);
344 		case 2:
345 			n = t->nline;
346 			break;
347 		case 3:
348 			n = cp->iargs[1];
349 			if(n<0 || n>t->nline)
350 				ctlerror("%q: line number out of range: %s", t->name, cp->str);
351 			break;
352 		}
353 		rp = _ctlrunestr(cp->args[cp->nargs-1]);
354 		t->line = ctlrealloc(t->line, (t->nline+1)*sizeof(Rune*));
355 		memmove(t->line+n+1, t->line+n, (t->nline-n)*sizeof(Rune*));
356 		t->line[n] = rp;
357 		t->selected = ctlrealloc(t->selected, t->nline+1);
358 		memmove(t->selected+n+1, t->selected+n, t->nline-n);
359 		t->selected[n] = (t->selectmode==Selmulti && cmd!=EAccumulate);
360 		t->nline++;
361 		if(t->scroll) {
362 			if(n > t->topline + (t->nvis - 1)){
363 				t->topline = n - (t->nvis - 1);
364 				if(t->topline < 0)
365 					t->topline = 0;
366 			}
367 			if(n < t->topline)
368 				t->topline = n;
369 		}
370 		if(cmd != EAccumulate)
371 			if(t->scroll || t->nline<=t->topline+t->nvis)
372 				textshow(t);
373 		break;
374 	}
375 }
376 
377 static void
378 texttogglei(Text *t, int i)
379 {
380 	int prev;
381 
382 	if(t->selectmode == Selsingle){
383 		/* clear the others */
384 		prev = t->selected[i];
385 		memset(t->selected, 0, t->nline);
386 		t->selected[i] = prev;
387 	}
388 	t->selected[i] ^= 1;
389 	textshow(t);
390 }
391 
392 static int
393 texttoggle(Text *t, Point p)
394 {
395 	Rectangle r;
396 	int i;
397 
398 	r = t->rect;
399 	if(t->border > 0)
400 		r = insetrect(r, t->border);
401 	if(!ptinrect(p, r))
402 		return -1;
403 	i = (p.y-r.min.y)/t->font->font->height;
404 	i += t->topline;
405 	if(i >= t->nline)
406 		return -1;
407 	texttogglei(t, i);
408 	return i;
409 }
410 
411 Control*
412 createtext(Controlset *cs, char *name)
413 {
414 	Text *t;
415 
416 	t = (Text*)_createctl(cs, "text", sizeof(Text), name);
417 	t->line = ctlmalloc(sizeof(Rune*));
418 	t->selected = ctlmalloc(1);
419 	t->nline = 0;
420 	t->image = _getctlimage("white");
421 	t->textcolor = _getctlimage("black");
422 	t->bordercolor = _getctlimage("black");
423 	t->selectcolor = _getctlimage("yellow");
424 	t->font = _getctlfont("font");
425 	t->selectmode = Selsingle;
426 	t->lastbut = 0;
427 	t->mouse = textmouse;
428 	t->ctl = textctl;
429 	t->exit = textfree;
430 	return (Control *)t;
431 }
432