1 /*
2  * Copyright (c) 2001-2015 Hypertriton, Inc. <http://hypertriton.com/>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
18  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
23  * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 /*
27  * Implementation of the AG_Widget(3) object.
28  */
29 
30 #include <agar/core/core.h>
31 #include <agar/gui/gui.h>
32 #include <agar/gui/widget.h>
33 #include <agar/gui/window.h>
34 #include <agar/gui/cursors.h>
35 #include <agar/gui/primitive.h>
36 #include <agar/gui/gui_math.h>
37 #include <agar/gui/opengl.h>
38 #include <agar/gui/text_cache.h>
39 
40 #include <stdarg.h>
41 #include <string.h>
42 #include <ctype.h>
43 
44 const char *agWidgetPropNames[] = {
45 	"font-family",
46 	"font-size",
47 	"font-weight",
48 	"font-style",
49 	"color",
50 	"text-color",
51 	"line-color",
52 	"shape-color",
53 	"border-color",
54 	NULL
55 };
56 const char *agWidgetStateNames[] = {
57 	"",
58 	"#disabled",
59 	"#focused",
60 	"#hover",
61 	"#selected",
62 	NULL
63 };
64 const char *agWidgetColorNames[] = {
65 	"color",
66 	"text-color",
67 	"line-color",
68 	"shape-color",
69 	"border-color",
70 	NULL
71 };
72 AG_WidgetPalette agDefaultPalette = {{
73 	/* "color"           "text-color"        "line-color"    "shape-color"	"border-color" */
74 /*def*/	{ {125,125,125,255}, {240,240,240,255},  {50,50,50,255}, {200,200,200,255},	{100,100,100,255} },
75 /*dis*/	{ {160,160,160,255}, {240,240,240,255},  {70,70,70,255}, {150,150,150,255},	{100,100,100,255} },
76 /*foc*/	{ {125,125,125,255}, {240,240,240,255},  {50,50,50,255}, {200,200,200,255},	{100,100,100,255} },
77 /*hov*/	{ {130,130,130,255}, {240,240,240,255},  {50,50,50,255}, {220,220,220,255},	{100,100,100,255} },
78 /*sel*/	{ {50,50,120,255},   {255,255,255,255},  {50,50,60,255}, {50,50,50,255},	{100,100,100,255} },
79 }};
80 
81 /* Set the parent window/driver pointers on a widget and its children. */
82 static void
SetParentWindow(AG_Widget * wid,AG_Window * win)83 SetParentWindow(AG_Widget *wid, AG_Window *win)
84 {
85 	AG_Widget *chld;
86 	AG_CursorArea *ca, *caNext;
87 
88 	wid->window = win;
89 
90 	if (win != NULL) {
91 		wid->drv = (AG_Driver *)OBJECT(win)->parent;
92 		wid->drvOps = AGDRIVER_CLASS(wid->drv);
93 
94 		/*
95 		 * Commit any previously deferred AG_MapStockCursor()
96 		 * operation.
97 		 */
98 		for (ca = TAILQ_FIRST(&wid->cursorAreas);
99 		     ca != TAILQ_END(&wid->cursorAreas);
100 		     ca = caNext) {
101 			caNext = TAILQ_NEXT(ca, cursorAreas);
102 			if (ca->stock >= 0 &&
103 			    ca->stock < wid->drv->nCursors) {
104 				AG_Cursor *ac;
105 				int i = 0;
106 
107 				TAILQ_FOREACH(ac, &wid->drv->cursors, cursors) {
108 					if (i++ == ca->stock)
109 						break;
110 				}
111 				if (ac != NULL) {
112 					ca->c = ac;
113 					TAILQ_INSERT_TAIL(&win->cursorAreas,
114 					    ca, cursorAreas);
115 				} else {
116 					free(ca);
117 				}
118 			} else {
119 				free(ca);
120 			}
121 		}
122 		TAILQ_INIT(&wid->cursorAreas);
123 	} else {
124 		wid->drv = NULL;
125 		wid->drvOps = NULL;
126 	}
127 	OBJECT_FOREACH_CHILD(chld, wid, ag_widget)
128 		SetParentWindow(chld, win);
129 }
130 
131 /* Set the parent driver pointers on a widget and its children. */
132 static void
SetParentDriver(AG_Widget * wid,AG_Driver * drv)133 SetParentDriver(AG_Widget *wid, AG_Driver *drv)
134 {
135 	AG_Widget *chld;
136 
137 	if (drv != NULL) {
138 		wid->drv = (AG_Driver *)drv;
139 		wid->drvOps = AGDRIVER_CLASS(drv);
140 	} else {
141 		wid->drv = NULL;
142 		wid->drvOps = NULL;
143 	}
144 	OBJECT_FOREACH_CHILD(chld, wid, ag_widget)
145 		SetParentDriver(chld, drv);
146 }
147 
148 static void
OnAttach(AG_Event * event)149 OnAttach(AG_Event *event)
150 {
151 	void *parent = AG_SENDER();
152 	AG_Widget *w = AG_SELF();
153 
154 	if (AG_OfClass(parent, "AG_Widget:AG_Window:*") &&
155 	    AG_OfClass(w, "AG_Widget:*")) {
156 		AG_Widget *widParent = (AG_Widget *)parent;
157 		Uint i;
158 
159 		Debug(w, "Attach to window (%s)\n", OBJECT(parent)->name);
160 
161 		SetParentWindow(w, AGWINDOW(widParent));
162 		if (AGWINDOW(widParent)->visible) {
163 			w->flags |= AG_WIDGET_UPDATE_WINDOW;
164 			AG_PostEvent(NULL, w, "widget-shown", NULL);
165 		}
166 
167 		/*
168 		 * Widget may have previously been detached from another
169 		 * driver; textures may need regenerating.
170 		 */
171 		for (i = 0; i < w->nsurfaces; i++) {
172 			w->textures[i] = 0;
173 		}
174 	} else if (AG_OfClass(parent, "AG_Widget:*") &&
175 	           AG_OfClass(w, "AG_Widget:*")) {
176 		AG_Widget *widParent = (AG_Widget *)parent;
177 
178 		Debug(w, "Attach to widget (%s in %s)\n", OBJECT(parent)->name,
179 		    widParent->window != NULL ? OBJECT(widParent->window)->name : "NULL");
180 
181 		SetParentWindow(w, widParent->window);
182 		if (widParent->window != NULL &&
183 		    widParent->window->visible) {
184 			AG_PostEvent(NULL, w, "widget-shown", NULL);
185 		}
186 	} else if (AG_OfClass(parent, "AG_Driver:*") &&
187 	           AG_OfClass(w, "AG_Widget:AG_Window:*")) {
188 		AG_Driver *drvParent = (AG_Driver *)parent;
189 
190 		Debug(w, "Attach to driver (%s)\n", OBJECT(parent)->name);
191 		SetParentDriver(w, drvParent);
192 	} else {
193 		AG_FatalError("Inconsistent widget attach");
194 	}
195 }
196 
197 static void
OnDetach(AG_Event * event)198 OnDetach(AG_Event *event)
199 {
200 	void *parent = AG_SENDER();
201 	AG_Widget *w = AG_SELF();
202 
203 	if (AG_OfClass(parent, "AG_Widget:*") &&
204 	    AG_OfClass(w, "AG_Widget:*")) {
205 		if (w->window != NULL) {
206 			AG_UnmapAllCursors(w->window, w);
207 		}
208 		SetParentWindow(w, NULL);
209 	} else if (AG_OfClass(parent, "AG_Driver:*") &&
210 	           AG_OfClass(w, "AG_Widget:AG_Window:*")) {
211 		SetParentDriver(w, NULL);
212 	} else {
213 		AG_FatalError("Inconsistent widget detach");
214 	}
215 }
216 
217 /* Timer callback for AG_RedrawOnTick(). */
218 static Uint32
RedrawOnTickTimeout(AG_Timer * to,AG_Event * event)219 RedrawOnTickTimeout(AG_Timer *to, AG_Event *event)
220 {
221 	AG_Widget *wid = event->argv[0].data.p;
222 
223 	if (wid->window != NULL) {
224 		wid->window->dirty = 1;
225 	}
226 	return (to->ival);
227 }
228 
229 /* Timer callback for AG_RedrawOnChange(). */
230 static Uint32
RedrawOnChangeTimeout(AG_Timer * to,AG_Event * event)231 RedrawOnChangeTimeout(AG_Timer *to, AG_Event *event)
232 {
233 	AG_Widget *wid = AG_SELF();
234 	AG_RedrawTie *rt = AG_PTR(1);
235 	AG_Variable *V, Vd;
236 	void *p;
237 
238 	if ((V = AG_GetVariable(wid, rt->name, &p)) == NULL) {
239 		return (to->ival);
240 	}
241 	AG_DerefVariable(&Vd, V);
242 	if (!rt->VlastInited || AG_CompareVariables(&Vd, &rt->Vlast) != 0) {
243 		if (wid->window != NULL) {
244 			wid->window->dirty = 1;
245 		}
246 		AG_CopyVariable(&rt->Vlast, &Vd);
247 		rt->VlastInited = 1;
248 	}
249 	AG_UnlockVariable(V);
250 	return (to->ival);
251 }
252 
253 static void
OnShow(AG_Event * event)254 OnShow(AG_Event *event)
255 {
256 	AG_Widget *wid = AG_SELF();
257 	AG_RedrawTie *rt;
258 
259 	if (wid->font == NULL)
260 		AG_FatalError("%s style not compiled", OBJECT(wid)->name);
261 
262 	wid->flags |= AG_WIDGET_VISIBLE;
263 
264 	TAILQ_FOREACH(rt, &wid->redrawTies, redrawTies) {
265 		switch (rt->type) {
266 		case AG_REDRAW_ON_TICK:
267 			AG_AddTimer(wid, &rt->to, rt->ival,
268 			    RedrawOnTickTimeout, NULL);
269 			break;
270 		case AG_REDRAW_ON_CHANGE:
271 			AG_AddTimer(wid, &rt->to, rt->ival,
272 			    RedrawOnChangeTimeout, "%p", rt);
273 			break;
274 		}
275 	}
276 }
277 
278 static void
OnHide(AG_Event * event)279 OnHide(AG_Event *event)
280 {
281 	AG_Widget *wid = AG_SELF();
282 	AG_RedrawTie *rt;
283 
284 	wid->flags &= ~(AG_WIDGET_VISIBLE);
285 
286 	TAILQ_FOREACH(rt, &wid->redrawTies, redrawTies) {
287 		switch (rt->type) {
288 		case AG_REDRAW_ON_TICK:
289 		case AG_REDRAW_ON_CHANGE:
290 			AG_DelTimer(wid, &rt->to);
291 			break;
292 		}
293 	}
294 }
295 
296 static void
Init(void * obj)297 Init(void *obj)
298 {
299 	AG_Widget *wid = obj;
300 	AG_Event *ev;
301 
302 	OBJECT(wid)->save_pfx = "/widgets";
303 	OBJECT(wid)->flags |= AG_OBJECT_NAME_ONATTACH;
304 
305 	wid->flags = 0;
306 	wid->rView = AG_RECT2(-1,-1,-1,-1);
307 	wid->rSens = AG_RECT2(0,0,0,0);
308 	wid->x = -1;
309 	wid->y = -1;
310 	wid->w = -1;
311 	wid->h = -1;
312 	wid->focusFwd = NULL;
313 	wid->window = NULL;
314 	wid->drv = NULL;
315 	wid->drvOps = NULL;
316 	wid->nsurfaces = 0;
317 	wid->surfaces = NULL;
318 	wid->surfaceFlags = NULL;
319 	wid->textures = NULL;
320 	wid->texcoords = NULL;
321 	AG_TblInit(&wid->actions, 32, 0);
322 	TAILQ_INIT(&wid->mouseActions);
323 	TAILQ_INIT(&wid->keyActions);
324 
325 	wid->css = NULL;
326 	wid->cState = AG_DEFAULT_STATE;
327 	wid->font = agDefaultFont;
328 	wid->pal = agDefaultPalette;
329 
330 	AG_SetEvent(wid, "attached", OnAttach, NULL);
331 	AG_SetEvent(wid, "detached", OnDetach, NULL);
332 	ev = AG_SetEvent(wid, "widget-shown", OnShow, NULL);
333 	ev->flags |= AG_EVENT_PROPAGATE;
334 	ev = AG_SetEvent(wid, "widget-hidden", OnHide, NULL);
335 	ev->flags |= AG_EVENT_PROPAGATE;
336 
337 	TAILQ_INIT(&wid->redrawTies);
338 	TAILQ_INIT(&wid->cursorAreas);
339 }
340 
341 /* Arrange for a redraw whenever a given binding value changes. */
342 void
AG_RedrawOnChange(void * obj,int refresh_ms,const char * name)343 AG_RedrawOnChange(void *obj, int refresh_ms, const char *name)
344 {
345 	AG_Widget *wid = obj;
346 	AG_RedrawTie *rt;
347 
348 	TAILQ_FOREACH(rt, &wid->redrawTies, redrawTies) {
349 		if (rt->type == AG_REDRAW_ON_CHANGE &&
350 		    strcmp(rt->name, name) == 0 &&
351 		    rt->ival == refresh_ms)
352 			break;
353 	}
354 	if (rt != NULL) {
355 		AG_ResetTimer(wid, &rt->to, refresh_ms);
356 		return;
357 	}
358 
359 	rt = Malloc(sizeof(AG_RedrawTie));
360 	rt->type = AG_REDRAW_ON_CHANGE;
361 	rt->ival = refresh_ms;
362 	rt->VlastInited = 0;
363 	Strlcpy(rt->name, name, sizeof(rt->name));
364 	AG_InitTimer(&rt->to, "redrawTie-", 0);
365 #ifdef AG_DEBUG
366 	Strlcat(rt->to.name, name, sizeof(rt->to.name));
367 #endif
368 	TAILQ_INSERT_TAIL(&wid->redrawTies, rt, redrawTies);
369 
370 	if (wid->flags & AG_WIDGET_VISIBLE) {
371 		AG_AddTimer(wid, &rt->to, rt->ival, RedrawOnChangeTimeout, "%p", rt);
372 	} else {
373 		/* Fire from OnShow() */
374 	}
375 }
376 
377 /* Arrange for an unconditional redraw at a periodic interval. */
378 void
AG_RedrawOnTick(void * obj,int refresh_ms)379 AG_RedrawOnTick(void *obj, int refresh_ms)
380 {
381 	AG_Widget *wid = obj;
382 	AG_RedrawTie *rt;
383 
384 	if (refresh_ms == -1) {
385 		TAILQ_FOREACH(rt, &wid->redrawTies, redrawTies) {
386 			if (rt->type == AG_REDRAW_ON_TICK)
387 				break;
388 		}
389 		if (rt != NULL) {
390 			TAILQ_REMOVE(&wid->redrawTies, rt, redrawTies);
391 			AG_DelTimer(wid, &rt->to);
392 			free(rt);
393 		}
394 		return;
395 	}
396 
397 	rt = Malloc(sizeof(AG_RedrawTie));
398 	rt->type = AG_REDRAW_ON_TICK;
399 	rt->ival = refresh_ms;
400 	rt->name[0] = '\0';
401 	AG_InitTimer(&rt->to, "redrawTick", 0);
402 	TAILQ_INSERT_TAIL(&wid->redrawTies, rt, redrawTies);
403 
404 	if (wid->flags & AG_WIDGET_VISIBLE) {
405 		AG_AddTimer(wid, &rt->to, rt->ival, RedrawOnTickTimeout, NULL);
406 	} else {
407 		/* Fire from OnShow() */
408 	}
409 }
410 
411 /* Default event handler for "key-down" (for widgets using Actions). */
412 void
AG_WidgetStdKeyDown(AG_Event * event)413 AG_WidgetStdKeyDown(AG_Event *event)
414 {
415 	AG_Widget *wid = AG_SELF();
416 	int sym = AG_INT(1);
417 	int mod = AG_INT(2);
418 
419 	AG_ExecKeyAction(wid, AG_ACTION_ON_KEYDOWN, sym, mod);
420 }
421 
422 /* Default event handler for "key-up" (for widgets using Actions). */
423 void
AG_WidgetStdKeyUp(AG_Event * event)424 AG_WidgetStdKeyUp(AG_Event *event)
425 {
426 	AG_Widget *wid = AG_SELF();
427 	int sym = AG_INT(1);
428 	int mod = AG_INT(2);
429 
430 	AG_ExecKeyAction(wid, AG_ACTION_ON_KEYUP, sym, mod);
431 }
432 
433 /* Default event handler for "mouse-button-down" (for widgets using Actions). */
434 void
AG_WidgetStdMouseButtonDown(AG_Event * event)435 AG_WidgetStdMouseButtonDown(AG_Event *event)
436 {
437 	AG_Widget *wid = AG_SELF();
438 	int btn = AG_INT(1);
439 	int x = AG_INT(2);
440 	int y = AG_INT(3);
441 
442 	if (!AG_WidgetIsFocused(wid)) {
443 		AG_WidgetFocus(wid);
444 	}
445 	AG_ExecMouseAction(wid, AG_ACTION_ON_BUTTONDOWN, btn, x, y);
446 }
447 
448 /* Default event handler for "mouse-button-up" (for widgets using Actions). */
449 void
AG_WidgetStdMouseButtonUp(AG_Event * event)450 AG_WidgetStdMouseButtonUp(AG_Event *event)
451 {
452 	AG_Widget *wid = AG_SELF();
453 	int btn = AG_INT(1);
454 	int x = AG_INT(2);
455 	int y = AG_INT(3);
456 
457 	AG_ExecMouseAction(wid, AG_ACTION_ON_BUTTONUP, btn, x, y);
458 }
459 
460 /* Tie an action to a mouse-button-down event. */
461 void
AG_ActionOnButtonDown(void * obj,int button,const char * action)462 AG_ActionOnButtonDown(void *obj, int button, const char *action)
463 {
464 	AG_Widget *wid = obj;
465 	AG_ActionTie *at;
466 
467 	at = Malloc(sizeof(AG_ActionTie));
468 	at->type = AG_ACTION_ON_BUTTONDOWN;
469 	at->data.button = (AG_MouseButton)button;
470 	Strlcpy(at->action, action, sizeof(at->action));
471 	TAILQ_INSERT_TAIL(&wid->mouseActions, at, ties);
472 
473 	if (AG_FindEventHandler(wid, "mouse-button-down") == NULL)
474 		AG_SetEvent(wid, "mouse-button-down", AG_WidgetStdMouseButtonDown, NULL);
475 }
476 
477 /* Tie an action to a mouse-button-up event. */
478 void
AG_ActionOnButtonUp(void * obj,int button,const char * action)479 AG_ActionOnButtonUp(void *obj, int button, const char *action)
480 {
481 	AG_Widget *wid = obj;
482 	AG_ActionTie *at;
483 
484 	at = Malloc(sizeof(AG_ActionTie));
485 	at->type = AG_ACTION_ON_BUTTONUP;
486 	at->data.button = (AG_MouseButton)button;
487 	Strlcpy(at->action, action, sizeof(at->action));
488 	TAILQ_INSERT_TAIL(&wid->mouseActions, at, ties);
489 
490 	if (AG_FindEventHandler(wid, "mouse-button-up") == NULL)
491 		AG_SetEvent(wid, "mouse-button-up", AG_WidgetStdMouseButtonUp, NULL);
492 }
493 
494 /* Tie an action to a key-down event. */
495 void
AG_ActionOnKeyDown(void * obj,AG_KeySym sym,AG_KeyMod mod,const char * action)496 AG_ActionOnKeyDown(void *obj, AG_KeySym sym, AG_KeyMod mod, const char *action)
497 {
498 	AG_Widget *wid = obj;
499 	AG_ActionTie *at;
500 
501 	at = Malloc(sizeof(AG_ActionTie));
502 	at->type = AG_ACTION_ON_KEYDOWN;
503 	at->data.key.sym = sym;
504 	at->data.key.mod = mod;
505 	Strlcpy(at->action, action, sizeof(at->action));
506 	TAILQ_INSERT_TAIL(&wid->keyActions, at, ties);
507 
508 	if (AG_FindEventHandler(wid, "key-down") == NULL)
509 		AG_SetEvent(wid, "key-down", AG_WidgetStdKeyDown, NULL);
510 }
511 
512 /* Tie an action to a key-up event. */
513 void
AG_ActionOnKeyUp(void * obj,AG_KeySym sym,AG_KeyMod mod,const char * action)514 AG_ActionOnKeyUp(void *obj, AG_KeySym sym, AG_KeyMod mod, const char *action)
515 {
516 	AG_Widget *wid = obj;
517 	AG_ActionTie *at;
518 
519 	at = Malloc(sizeof(AG_ActionTie));
520 	at->type = AG_ACTION_ON_KEYUP;
521 	at->data.key.sym = sym;
522 	at->data.key.mod = mod;
523 	Strlcpy(at->action, action, sizeof(at->action));
524 	TAILQ_INSERT_TAIL(&wid->keyActions, at, ties);
525 
526 	if (AG_FindEventHandler(wid, "key-up") == NULL)
527 		AG_SetEvent(wid, "key-up", AG_WidgetStdKeyUp, NULL);
528 }
529 
530 /* Timer callback for AG_ACTION_ON_KEYREPEAT actions. */
531 static Uint32
ActionKeyRepeatTimeout(AG_Timer * to,AG_Event * event)532 ActionKeyRepeatTimeout(AG_Timer *to, AG_Event *event)
533 {
534 	AG_Widget *wid = AG_SELF();
535 	AG_ActionTie *at = AG_PTR(1);
536 	AG_Action *a;
537 
538 	if (AG_TblLookupPointer(&wid->actions, at->action, (void *)&a) == -1 ||
539 	    a == NULL) {
540 		return (0);
541 	}
542 	(void)AG_ExecAction(wid, a);
543 	return (agKbdRepeat);
544 }
545 
546 /* Tie an action to a key-down event, with key repeat. */
547 void
AG_ActionOnKey(void * obj,AG_KeySym sym,AG_KeyMod mod,const char * action)548 AG_ActionOnKey(void *obj, AG_KeySym sym, AG_KeyMod mod, const char *action)
549 {
550 	AG_Widget *wid = obj;
551 	AG_ActionTie *at;
552 
553 	at = Malloc(sizeof(AG_ActionTie));
554 	at->type = AG_ACTION_ON_KEYREPEAT;
555 	at->data.key.sym = sym;
556 	at->data.key.mod = mod;
557 	AG_InitTimer(&at->data.key.toRepeat, "actionKeyRepeat-", 0);
558 #ifdef AG_DEBUG
559 	Strlcat(at->data.key.toRepeat.name, action,
560 	    sizeof(at->data.key.toRepeat.name));
561 #endif
562 	Strlcpy(at->action, action, sizeof(at->action));
563 	TAILQ_INSERT_TAIL(&wid->keyActions, at, ties);
564 
565 	if (AG_FindEventHandler(wid, "key-up") == NULL &&
566 	    AG_FindEventHandler(wid, "key-down") == NULL) {
567 		AG_SetEvent(wid, "key-up", AG_WidgetStdKeyUp, NULL);
568 		AG_SetEvent(wid, "key-down", AG_WidgetStdKeyDown, NULL);
569 	}
570 }
571 
572 /* Configure a widget action. */
573 AG_Action *
AG_ActionFn(void * obj,const char * name,AG_EventFn fn,const char * fnArgs,...)574 AG_ActionFn(void *obj, const char *name, AG_EventFn fn, const char *fnArgs,...)
575 {
576 	AG_Widget *w = obj;
577 	AG_Action *a;
578 
579 	AG_ObjectLock(w);
580 	a = Malloc(sizeof(AG_Action));
581 	a->type = AG_ACTION_FN;
582 	a->widget = w;
583 	a->fn = AG_SetEvent(w, NULL, fn, NULL);
584 	AG_EVENT_GET_ARGS(a->fn, fnArgs);
585 	AG_TblInsertPointer(&w->actions, name, a);
586 	AG_ObjectUnlock(w);
587 	return (a);
588 }
589 
590 /* Configure a widget action for setting an integer flag. */
591 AG_Action *
AG_ActionSetInt(void * obj,const char * name,int * p,int val)592 AG_ActionSetInt(void *obj, const char *name, int *p, int val)
593 {
594 	AG_Widget *w = obj;
595 	AG_Action *a;
596 
597 	AG_ObjectLock(w);
598 	a = Malloc(sizeof(AG_Action));
599 	a->type = AG_ACTION_SET_INT;
600 	a->widget = w;
601 	a->fn = NULL;
602 	a->p = (void *)p;
603 	a->val = val;
604 	AG_TblInsertPointer(&w->actions, name, a);
605 	AG_ObjectUnlock(w);
606 	return (a);
607 }
608 
609 /* Configure a widget action for toggling an integer flag. */
610 AG_Action *
AG_ActionToggleInt(void * obj,const char * name,int * p)611 AG_ActionToggleInt(void *obj, const char *name, int *p)
612 {
613 	AG_Widget *w = obj;
614 	AG_Action *a;
615 
616 	AG_ObjectLock(w);
617 	a = Malloc(sizeof(AG_Action));
618 	a->type = AG_ACTION_TOGGLE_INT;
619 	a->widget = w;
620 	a->fn = NULL;
621 	a->p = (void *)p;
622 	AG_TblInsertPointer(&w->actions, name, a);
623 	AG_ObjectUnlock(w);
624 	return (a);
625 }
626 
627 /* Configure a widget action for setting bitwise flags. */
628 AG_Action *
AG_ActionSetFlag(void * obj,const char * name,Uint * p,Uint bitmask,int val)629 AG_ActionSetFlag(void *obj, const char *name, Uint *p, Uint bitmask, int val)
630 {
631 	AG_Widget *w = obj;
632 	AG_Action *a;
633 
634 	AG_ObjectLock(w);
635 	a = Malloc(sizeof(AG_Action));
636 	a->type = AG_ACTION_SET_INT;
637 	a->widget = w;
638 	a->fn = NULL;
639 	a->p = (void *)p;
640 	a->bitmask = bitmask;
641 	a->val = val;
642 	AG_TblInsertPointer(&w->actions, name, a);
643 	AG_ObjectUnlock(w);
644 	return (a);
645 }
646 
647 /* Configure a widget action for toggling bitwise flags. */
648 AG_Action *
AG_ActionToggleFlag(void * obj,const char * name,Uint * p,Uint bitmask)649 AG_ActionToggleFlag(void *obj, const char *name, Uint *p, Uint bitmask)
650 {
651 	AG_Widget *w = obj;
652 	AG_Action *a;
653 
654 	AG_ObjectLock(w);
655 	a = Malloc(sizeof(AG_Action));
656 	a->type = AG_ACTION_TOGGLE_FLAG;
657 	a->widget = w;
658 	a->fn = NULL;
659 	a->p = (void *)p;
660 	a->bitmask = bitmask;
661 	AG_TblInsertPointer(&w->actions, name, a);
662 	AG_ObjectUnlock(w);
663 	return (a);
664 }
665 
666 /* Execute an action (usually called internally from AG_ExecFooAction()) */
667 int
AG_ExecAction(void * obj,AG_Action * a)668 AG_ExecAction(void *obj, AG_Action *a)
669 {
670 	switch (a->type) {
671 	case AG_ACTION_FN:
672 		if (a->fn != NULL) {
673 			a->fn->fn.fnVoid(a->fn);
674 			return (1);
675 		}
676 		return (0);
677 	case AG_ACTION_SET_INT:
678 		*(int *)a->p = a->val;
679 		return (1);
680 	case AG_ACTION_TOGGLE_INT:
681 		*(int *)a->p = !(*(int *)a->p);
682 		return (1);
683 	case AG_ACTION_SET_FLAG:
684 		if (a->val) {
685 			*(Uint *)a->p |= a->bitmask;
686 		} else {
687 			*(Uint *)a->p &= ~(a->bitmask);
688 		}
689 		return (1);
690 	case AG_ACTION_TOGGLE_FLAG:
691 		if (*(Uint *)a->p & a->bitmask) {
692 			*(Uint *)a->p &= ~(a->bitmask);
693 		} else {
694 			*(Uint *)a->p |= a->bitmask;
695 		}
696 		return (1);
697 	}
698 	return (0);
699 }
700 
701 /*
702  * Run any action tied to a mouse-button event. Exceptionally, we pass
703  * mouse event arguments to the function.
704  */
705 int
AG_ExecMouseAction(void * obj,AG_ActionEventType et,int button,int xCurs,int yCurs)706 AG_ExecMouseAction(void *obj, AG_ActionEventType et, int button,
707     int xCurs, int yCurs)
708 {
709 	AG_Widget *wid = obj;
710 	AG_ActionTie *at;
711 	AG_Action *a;
712 
713 #ifdef AG_DEBUG
714 	if (et != AG_ACTION_ON_BUTTONDOWN &&
715 	    et != AG_ACTION_ON_BUTTONUP)
716 		AG_FatalError("Invalid type arg to AG_ExecMouseAction()");
717 #endif
718 	TAILQ_FOREACH(at, &wid->mouseActions, ties) {
719 		if (at->type == et &&
720 		    ((button == at->data.button) ||
721 		     (at->data.button == AG_MOUSE_ANY)))
722 			break;
723 	}
724 	if (at == NULL) {
725 		return (0);
726 	}
727 	if (AG_TblLookupPointer(&wid->actions, at->action, (void *)&a) == -1 ||
728 	    a == NULL) {
729 		return (0);
730 	}
731 	if (a->fn != NULL) {
732 		AG_PostEventByPtr(NULL, wid, a->fn, "%i,%i,%i", button,
733 		    xCurs, yCurs);
734 		return (1);
735 	}
736 	return (0);
737 }
738 
739 /* Run any action tied to a key-down event. */
740 int
AG_ExecKeyAction(void * obj,AG_ActionEventType et,AG_KeySym sym,AG_KeyMod mod)741 AG_ExecKeyAction(void *obj, AG_ActionEventType et, AG_KeySym sym, AG_KeyMod mod)
742 {
743 	AG_Widget *wid = obj;
744 	AG_ActionTie *at;
745 	AG_Action *a;
746 	int rv;
747 
748 #ifdef AG_DEBUG
749 	if (et != AG_ACTION_ON_KEYDOWN &&
750 	    et != AG_ACTION_ON_KEYUP)
751 		AG_FatalError("AG_ExecKeyAction() type");
752 #endif
753 	TAILQ_FOREACH(at, &wid->keyActions, ties) {
754 		if (at->type != et &&
755 		    at->type != AG_ACTION_ON_KEYREPEAT) {
756 			continue;
757 		}
758 		if ((at->data.key.mod == AG_KEYMOD_ANY ||
759 		     at->data.key.mod & mod) &&
760 		    (at->data.key.sym == AG_KEY_ANY ||
761 		     at->data.key.sym == sym))
762 			break;
763 	}
764 	if (at == NULL) {
765 		return (0);
766 	}
767 	if (AG_TblLookupPointer(&wid->actions, at->action, (void *)&a) == -1 ||
768 	    a == NULL)
769 		return (0);
770 
771 	rv = AG_ExecAction(wid, a);
772 
773 	if (at->type == AG_ACTION_ON_KEYREPEAT) {
774 		if (et == AG_ACTION_ON_KEYDOWN) {
775 			AG_AddTimer(wid, &at->data.key.toRepeat, agKbdDelay,
776 			    ActionKeyRepeatTimeout, "%p", at);
777 		} else {
778 			AG_DelTimer(wid, &at->data.key.toRepeat);
779 		}
780 	}
781 	return (rv);
782 }
783 
784 static void *
WidgetFindPath(const AG_Object * parent,const char * name)785 WidgetFindPath(const AG_Object *parent, const char *name)
786 {
787 	char node_name[AG_OBJECT_PATH_MAX];
788 	void *rv;
789 	char *s;
790 	AG_Object *chld;
791 
792 	Strlcpy(node_name, name, sizeof(node_name));
793 	if ((s = strchr(node_name, '/')) != NULL) {
794 		*s = '\0';
795 	}
796 	if (AG_OfClass(parent, "AG_View:*")) {
797 		AG_Driver *drv = AGDRIVER(parent);
798 		AG_Window *win;
799 
800 		AG_FOREACH_WINDOW(win, drv) {
801 			if (strcmp(AGOBJECT(win)->name, node_name) != 0) {
802 				continue;
803 			}
804 			if ((s = strchr(name, '/')) != NULL) {
805 				rv = WidgetFindPath(AGOBJECT(win), &s[1]);
806 				if (rv != NULL) {
807 					return (rv);
808 				} else {
809 					return (NULL);
810 				}
811 			}
812 			return (win);
813 		}
814 	} else {
815 		TAILQ_FOREACH(chld, &parent->children, cobjs) {
816 			if (strcmp(chld->name, node_name) != 0) {
817 				continue;
818 			}
819 			if ((s = strchr(name, '/')) != NULL) {
820 				rv = WidgetFindPath(chld, &s[1]);
821 				if (rv != NULL) {
822 					return (rv);
823 				} else {
824 					return (NULL);
825 				}
826 			}
827 			return (chld);
828 		}
829 	}
830 	return (NULL);
831 }
832 
833 /*
834  * Find a widget by name (e.g., "Window/Widget1/Widget2"). This works
835  * similarly to the more general AG_ObjectFind(3). Return value is only
836  * valid as long as the Driver VFS is locked.
837  */
838 void *
AG_WidgetFind(void * obj,const char * name)839 AG_WidgetFind(void *obj, const char *name)
840 {
841 	AG_Driver *drv = obj;
842 	void *rv;
843 
844 #ifdef AG_DEBUG
845 	if (name[0] != '/')
846 		AG_FatalError("WidgetFind: Not an absolute path: %s", name);
847 #endif
848 	AG_LockVFS(drv);
849 	rv = WidgetFindPath(OBJECT(drv), &name[1]);
850 	AG_UnlockVFS(drv);
851 	if (rv == NULL) {
852 		AG_SetError(_("The widget `%s' does not exist."), name);
853 	}
854 	return (rv);
855 }
856 
857 /* Set the FOCUSABLE flag on a widget. */
858 void
AG_WidgetSetFocusable(void * obj,int flag)859 AG_WidgetSetFocusable(void *obj, int flag)
860 {
861 	AG_Widget *wid = obj;
862 
863 	AG_ObjectLock(wid);
864 	AG_SETFLAGS(wid->flags, AG_WIDGET_FOCUSABLE, flag);
865 	AG_ObjectUnlock(wid);
866 }
867 
868 /* Set widget to "enabled" state for input. */
869 void
AG_WidgetEnable(void * obj)870 AG_WidgetEnable(void *obj)
871 {
872 	AG_Widget *wid = obj;
873 
874 	AG_ObjectLock(wid);
875 	if (wid->flags & AG_WIDGET_DISABLED) {
876 		wid->flags &= ~(AG_WIDGET_DISABLED);
877 		AG_PostEvent(NULL, wid, "widget-enabled", NULL);
878 		AG_Redraw(wid);
879 	}
880 	AG_ObjectUnlock(wid);
881 }
882 
883 /* Set widget to "disabled" state for input. */
884 void
AG_WidgetDisable(void * obj)885 AG_WidgetDisable(void *obj)
886 {
887 	AG_Widget *wid = obj;
888 
889 	AG_ObjectLock(wid);
890 	if (!(wid->flags & AG_WIDGET_DISABLED)) {
891 		wid->flags |= AG_WIDGET_DISABLED;
892 		AG_PostEvent(NULL, wid, "widget-disabled", NULL);
893 		AG_Redraw(wid);
894 	}
895 	AG_ObjectUnlock(wid);
896 }
897 
898 /* Arrange for a widget to automatically forward focus to another widget. */
899 void
AG_WidgetForwardFocus(void * obj,void * objFwd)900 AG_WidgetForwardFocus(void *obj, void *objFwd)
901 {
902 	AG_Widget *wid = obj;
903 
904 	AG_ObjectLock(wid);
905 	if (objFwd != NULL) {
906 		wid->flags |= AG_WIDGET_FOCUSABLE;
907 		wid->focusFwd = WIDGET(objFwd);
908 	} else {
909 		wid->flags &= ~(AG_WIDGET_FOCUSABLE);
910 		wid->focusFwd = NULL;
911 	}
912 	AG_ObjectUnlock(wid);
913 }
914 
915 static void
Destroy(void * obj)916 Destroy(void *obj)
917 {
918 	AG_Widget *wid = obj;
919 	AG_CursorArea *ca, *caNext;
920 	AG_RedrawTie *rt, *rtNext;
921 	AG_ActionTie *at, *atNext;
922 	AG_Variable *V;
923 	Uint i, j;
924 
925 	for (ca = TAILQ_FIRST(&wid->cursorAreas);
926 	     ca != TAILQ_END(&wid->cursorAreas);
927 	     ca = caNext) {
928 		caNext = TAILQ_NEXT(ca, cursorAreas);
929 		free(ca);
930 	}
931 	for (rt = TAILQ_FIRST(&wid->redrawTies);
932 	     rt != TAILQ_END(&wid->redrawTies);
933 	     rt = rtNext) {
934 		rtNext = TAILQ_NEXT(rt, redrawTies);
935 		free(rt);
936 	}
937 	for (at = TAILQ_FIRST(&wid->mouseActions);
938 	     at != TAILQ_END(&wid->mouseActions);
939 	     at = atNext) {
940 		atNext = TAILQ_NEXT(at, ties);
941 		free(at);
942 	}
943 	for (at = TAILQ_FIRST(&wid->keyActions);
944 	     at != TAILQ_END(&wid->keyActions);
945 	     at = atNext) {
946 		atNext = TAILQ_NEXT(at, ties);
947 		free(at);
948 	}
949 
950 	/* Free the action tables. */
951 	AG_TBL_FOREACH(V, i,j, &wid->actions) {
952 		Free(V->data.p);
953 	}
954 	AG_TblDestroy(&wid->actions);
955 
956 	/*
957 	 * Free surfaces. We can assume that drivers have already deleted
958 	 * any associated resources.
959 	 */
960 	for (i = 0; i < wid->nsurfaces; i++) {
961 		if (wid->surfaces[i] != NULL && !WSURFACE_NODUP(wid,i))
962 			AG_SurfaceFree(wid->surfaces[i]);
963 	}
964 	Free(wid->surfaces);
965 	Free(wid->surfaceFlags);
966 	Free(wid->textures);
967 	Free(wid->texcoords);
968 }
969 
970 #ifdef HAVE_OPENGL
971 /*
972  * Variants of AG_WidgetBlit*() without explicit source or destination
973  * rectangle parameter (for OpenGL-only widgets).
974  */
975 void
AG_WidgetBlitGL(void * obj,AG_Surface * su,float w,float h)976 AG_WidgetBlitGL(void *obj, AG_Surface *su, float w, float h)
977 {
978 	AG_Widget *wid = obj;
979 	wid->drvOps->blitSurfaceGL(wid->drv, wid, su, w, h);
980 }
981 void
AG_WidgetBlitSurfaceGL(void * obj,int name,float w,float h)982 AG_WidgetBlitSurfaceGL(void *obj, int name, float w, float h)
983 {
984 	AG_Widget *wid = obj;
985 	wid->drvOps->blitSurfaceFromGL(wid->drv, wid, name, w, h);
986 }
987 void
AG_WidgetBlitSurfaceFlippedGL(void * obj,int name,float w,float h)988 AG_WidgetBlitSurfaceFlippedGL(void *obj, int name, float w, float h)
989 {
990 	AG_Widget *wid = obj;
991 	wid->drvOps->blitSurfaceFlippedGL(wid->drv, wid, name, w, h);
992 }
993 
994 /*
995  * Release/backup and regenerate the GL resources associated with a widget
996  * and its descendents.
997  *
998  * If some textures exist without a corresponding surface, allocate a
999  * software surface and copy their contents to be later restored. These
1000  * routines are necessary for dealing with GL context loss.
1001  */
1002 void
AG_WidgetFreeResourcesGL(void * obj)1003 AG_WidgetFreeResourcesGL(void *obj)
1004 {
1005 	AG_Widget *wid = obj, *cwid;
1006 
1007 	if (wid->drvOps->backupSurfaces != NULL) {
1008 		wid->drvOps->backupSurfaces(wid->drv, wid);
1009 	}
1010 	OBJECT_FOREACH_CHILD(cwid, wid, ag_widget)
1011 		AG_WidgetFreeResourcesGL(cwid);
1012 }
1013 void
AG_WidgetRegenResourcesGL(void * obj)1014 AG_WidgetRegenResourcesGL(void *obj)
1015 {
1016 	AG_Widget *wid = obj, *cwid;
1017 
1018 	if (wid->drvOps->restoreSurfaces != NULL) {
1019 		wid->drvOps->restoreSurfaces(wid->drv, wid);
1020 	}
1021 	OBJECT_FOREACH_CHILD(cwid, wid, ag_widget)
1022 		AG_WidgetRegenResourcesGL(cwid);
1023 }
1024 #endif /* HAVE_OPENGL */
1025 
1026 /* Acquire widget focus */
1027 static __inline__ void
FocusWidget(AG_Widget * w)1028 FocusWidget(AG_Widget *w)
1029 {
1030 	w->flags |= AG_WIDGET_FOCUSED;
1031 	if (w->window != NULL) {
1032 		AG_PostEvent(w->window, w, "widget-gainfocus", NULL);
1033 		w->window->nFocused++;
1034 		w->window->dirty = 1;
1035 	} else {
1036 		Verbose("%s: Gained focus, but no parent window\n",
1037 		    OBJECT(w)->name);
1038 	}
1039 }
1040 
1041 /* Give up widget focus */
1042 static __inline__ void
UnfocusWidget(AG_Widget * w)1043 UnfocusWidget(AG_Widget *w)
1044 {
1045 	w->flags &= ~(AG_WIDGET_FOCUSED);
1046 	if (w->window != NULL) {
1047 		AG_PostEvent(w->window, w, "widget-lostfocus", NULL);
1048 		w->window->nFocused--;
1049 		w->window->dirty = 1;
1050 	}
1051 }
1052 
1053 /* Remove focus from a widget and its children. */
1054 void
AG_WidgetUnfocus(void * p)1055 AG_WidgetUnfocus(void *p)
1056 {
1057 	AG_Widget *wid = p, *cwid;
1058 
1059 	AG_ObjectLock(wid);
1060 	if (wid->focusFwd != NULL) {
1061 		AG_ObjectLock(wid->focusFwd);
1062 		if (wid->focusFwd->flags & AG_WIDGET_FOCUSED) {
1063 			UnfocusWidget(wid->focusFwd);
1064 		}
1065 		AG_ObjectUnlock(wid->focusFwd);
1066 	}
1067 	if (wid->flags & AG_WIDGET_FOCUSED) {
1068 		UnfocusWidget(wid);
1069 	}
1070 	OBJECT_FOREACH_CHILD(cwid, wid, ag_widget) {
1071 		AG_WidgetUnfocus(cwid);
1072 	}
1073 	AG_ObjectUnlock(wid);
1074 }
1075 
1076 /* Move the focus over a widget (and its parents). */
1077 int
AG_WidgetFocus(void * obj)1078 AG_WidgetFocus(void *obj)
1079 {
1080 	AG_Widget *wid = obj, *wParent = wid;
1081 	AG_Window *win = wid->window;
1082 
1083 	AG_LockVFS(wid);
1084 	AG_ObjectLock(wid);
1085 
1086 	if (AG_WidgetIsFocused(wid))
1087 		goto out;
1088 
1089 	if (!(wid->flags & AG_WIDGET_FOCUSABLE)) {
1090 		if (wid->focusFwd != NULL &&
1091 		    !(wid->focusFwd->flags & AG_WIDGET_FOCUSED)) {
1092 			AG_ObjectLock(wid->focusFwd);
1093 			FocusWidget(wid->focusFwd);
1094 			AG_ObjectUnlock(wid->focusFwd);
1095 			goto out;
1096 		}
1097 		goto fail;
1098 	}
1099 
1100 	/* Remove any existing focus. XXX inefficient */
1101 	if (win != NULL && win->nFocused > 0)
1102 		AG_WidgetUnfocus(win);
1103 
1104 	/*
1105 	 * Set the focus flag on the widget and its parents, up
1106 	 * to the parent window.
1107 	 */
1108 	do {
1109 		if (AG_OfClass(wParent, "AG_Widget:AG_Window:*")) {
1110 			AG_WindowFocus(AGWINDOW(wParent));
1111 			break;
1112 		}
1113 		AG_ObjectLock(wParent);
1114 		if ((wParent->flags & AG_WIDGET_FOCUSED) == 0) {
1115 			if (wParent->focusFwd != NULL &&
1116 			    !(wParent->focusFwd->flags & AG_WIDGET_FOCUSED)) {
1117 				FocusWidget(wParent->focusFwd);
1118 			}
1119 			FocusWidget(wParent);
1120 		}
1121 		AG_ObjectUnlock(wParent);
1122 	} while ((wParent = OBJECT(wParent)->parent) != NULL);
1123 out:
1124 	AG_ObjectUnlock(wid);
1125 	AG_UnlockVFS(wid);
1126 	return (1);
1127 fail:
1128 	AG_ObjectUnlock(wid);
1129 	AG_UnlockVFS(wid);
1130 	return (0);
1131 }
1132 
1133 #ifdef HAVE_OPENGL
1134 
1135 static void
DrawPrologueGL_Reshape(AG_Widget * wid)1136 DrawPrologueGL_Reshape(AG_Widget *wid)
1137 {
1138 	glMatrixMode(GL_PROJECTION); glPushMatrix();
1139 	glMatrixMode(GL_MODELVIEW);  glPushMatrix();
1140 
1141 	AG_PostEvent(NULL, wid, "widget-reshape", NULL);
1142 	wid->flags &= ~(AG_WIDGET_GL_RESHAPE);
1143 
1144 	glGetFloatv(GL_PROJECTION, wid->gl.mProjection);
1145 	glGetFloatv(GL_MODELVIEW, wid->gl.mModelview);
1146 
1147 	glMatrixMode(GL_PROJECTION); glPopMatrix();
1148 	glMatrixMode(GL_MODELVIEW);  glPopMatrix();
1149 }
1150 
1151 static void
DrawPrologueGL(AG_Widget * wid)1152 DrawPrologueGL(AG_Widget *wid)
1153 {
1154 	Uint hView;
1155 
1156 	AG_PostEvent(NULL, wid, "widget-underlay", NULL);
1157 
1158 	glPushAttrib(GL_TRANSFORM_BIT | GL_VIEWPORT_BIT | GL_TEXTURE_BIT);
1159 
1160 	if (wid->flags & AG_WIDGET_GL_RESHAPE)
1161 		DrawPrologueGL_Reshape(wid);
1162 
1163 	hView = AGDRIVER_SINGLE(wid->drv) ? AGDRIVER_SW(wid->drv)->h :
1164 	                                    HEIGHT(wid->window);
1165 	glViewport(wid->rView.x1, (hView - wid->rView.y2),
1166 	           WIDTH(wid), HEIGHT(wid));
1167 
1168 	glMatrixMode(GL_TEXTURE);
1169 	glPushMatrix();
1170 	glLoadIdentity();
1171 
1172 	glMatrixMode(GL_PROJECTION);
1173 	glPushMatrix();
1174 	glLoadMatrixf(wid->gl.mProjection);
1175 
1176 	glMatrixMode(GL_MODELVIEW);
1177 	glPushMatrix();
1178 	glLoadMatrixf(wid->gl.mModelview);
1179 
1180 	glDisable(GL_CLIP_PLANE0);
1181 	glDisable(GL_CLIP_PLANE1);
1182 	glDisable(GL_CLIP_PLANE2);
1183 	glDisable(GL_CLIP_PLANE3);
1184 }
1185 
1186 static void
DrawEpilogueGL(AG_Widget * wid)1187 DrawEpilogueGL(AG_Widget *wid)
1188 {
1189 	glMatrixMode(GL_MODELVIEW);	glPopMatrix();
1190 	glMatrixMode(GL_PROJECTION);	glPopMatrix();
1191 	glMatrixMode(GL_TEXTURE);	glPopMatrix();
1192 
1193 	glPopAttrib(); /* GL_TRANSFORM_BIT | GL_VIEWPORT_BIT */
1194 
1195 	AG_PostEvent(NULL, wid, "widget-overlay", NULL);
1196 }
1197 #endif /* HAVE_OPENGL */
1198 
1199 /*
1200  * Render a widget to the display.
1201  * Must be invoked from GUI rendering context.
1202  */
1203 void
AG_WidgetDraw(void * p)1204 AG_WidgetDraw(void *p)
1205 {
1206 	AG_Widget *wid = p;
1207 
1208 	AG_ObjectLock(wid);
1209 
1210 	if (!(wid->flags & AG_WIDGET_VISIBLE) ||
1211 	     (wid->flags & AG_WIDGET_UNDERSIZE) ||
1212 	     WIDGET_OPS(wid)->draw == NULL)
1213 		goto out;
1214 
1215 	if (wid->flags & AG_WIDGET_DISABLED) {       wid->cState = AG_DISABLED_STATE; }
1216 	else if (wid->flags & AG_WIDGET_MOUSEOVER) { wid->cState = AG_HOVER_STATE; }
1217 	else if (wid->flags & AG_WIDGET_FOCUSED) {   wid->cState = AG_FOCUSED_STATE; }
1218 	else {                                       wid->cState = AG_DEFAULT_STATE; }
1219 
1220 	if (wid->flags & AG_WIDGET_USE_TEXT) {
1221 		AG_PushTextState();
1222 		AG_TextFont(wid->font);
1223 		AG_TextColor(wid->pal.c[wid->cState][AG_TEXT_COLOR]);
1224 	}
1225 #ifdef HAVE_OPENGL
1226 	if (wid->flags & AG_WIDGET_USE_OPENGL)
1227 		DrawPrologueGL(wid);
1228 #endif
1229 
1230 	WIDGET_OPS(wid)->draw(wid);
1231 
1232 #ifdef HAVE_OPENGL
1233 	if (wid->flags & AG_WIDGET_USE_OPENGL)
1234 		DrawEpilogueGL(wid);
1235 #endif
1236 	if (wid->flags & AG_WIDGET_USE_TEXT)
1237 		AG_PopTextState();
1238 out:
1239 	AG_ObjectUnlock(wid);
1240 }
1241 
1242 static void
SizeRequest(void * p,AG_SizeReq * r)1243 SizeRequest(void *p, AG_SizeReq *r)
1244 {
1245 	r->w = 0;
1246 	r->h = 0;
1247 }
1248 
1249 static int
SizeAllocate(void * p,const AG_SizeAlloc * a)1250 SizeAllocate(void *p, const AG_SizeAlloc *a)
1251 {
1252 	return (0);
1253 }
1254 
1255 void
AG_WidgetSizeReq(void * obj,AG_SizeReq * r)1256 AG_WidgetSizeReq(void *obj, AG_SizeReq *r)
1257 {
1258 	AG_Widget *w = obj;
1259 
1260 	r->w = 0;
1261 	r->h = 0;
1262 
1263 	AG_ObjectLock(w);
1264 	if (w->flags & AG_WIDGET_USE_TEXT) {
1265 		AG_PushTextState();
1266 		AG_TextFont(w->font);
1267 	}
1268 	if (WIDGET_OPS(w)->size_request != NULL) {
1269 		WIDGET_OPS(w)->size_request(w, r);
1270 	}
1271 	if (w->flags & AG_WIDGET_USE_TEXT) {
1272 		AG_PopTextState();
1273 	}
1274 	AG_ObjectUnlock(w);
1275 }
1276 
1277 void
AG_WidgetSizeAlloc(void * obj,AG_SizeAlloc * a)1278 AG_WidgetSizeAlloc(void *obj, AG_SizeAlloc *a)
1279 {
1280 	AG_Widget *w = obj;
1281 
1282 	AG_ObjectLock(w);
1283 
1284 	if (w->flags & AG_WIDGET_USE_TEXT) {
1285 		AG_PushTextState();
1286 		AG_TextFont(w->font);
1287 	}
1288 	if (a->w <= 0 || a->h <= 0) {
1289 		a->w = 0;
1290 		a->h = 0;
1291 		w->flags |= AG_WIDGET_UNDERSIZE;
1292 	} else {
1293 		w->flags &= ~(AG_WIDGET_UNDERSIZE);
1294 	}
1295 	w->x = a->x;
1296 	w->y = a->y;
1297 	w->w = a->w;
1298 	w->h = a->h;
1299 	if (WIDGET_OPS(w)->size_allocate != NULL) {
1300 		if (WIDGET_OPS(w)->size_allocate(w, a) == -1) {
1301 			w->flags |= AG_WIDGET_UNDERSIZE;
1302 		} else {
1303 			w->flags &= ~(AG_WIDGET_UNDERSIZE);
1304 		}
1305 	}
1306 	if (w->flags & AG_WIDGET_USE_TEXT) {
1307 		AG_PopTextState();
1308 	}
1309 #ifdef HAVE_OPENGL
1310 	w->flags |= AG_WIDGET_GL_RESHAPE;
1311 #endif
1312 	AG_ObjectUnlock(w);
1313 }
1314 
1315 /*
1316  * Test whether view coordinates x,y lie in widget's sensitivity rectangle
1317  * (intersected against those of all parent widgets).
1318  */
1319 int
AG_WidgetSensitive(void * obj,int x,int y)1320 AG_WidgetSensitive(void *obj, int x, int y)
1321 {
1322 	AG_Widget *wt = WIDGET(obj);
1323 	AG_Widget *wtParent = wt;
1324 	AG_Rect2 rx = wt->rSens;
1325 
1326 	while ((wtParent = OBJECT(wtParent)->parent) != NULL) {
1327 		if (AG_OfClass(wtParent, "AG_Widget:AG_Window:*")) {
1328 			break;
1329 		}
1330 		rx = AG_RectIntersect2(&rx, &wtParent->rSens);
1331 	}
1332 	return AG_RectInside2(&rx, x,y);
1333 }
1334 
1335 /*
1336  * Search for a focused widget inside a window. Return value is only valid
1337  * as long as the Driver VFS is locked.
1338  */
1339 AG_Widget *
AG_WidgetFindFocused(void * p)1340 AG_WidgetFindFocused(void *p)
1341 {
1342 	AG_Widget *wid = p;
1343 	AG_Widget *cwid, *fwid;
1344 
1345 	AG_LockVFS(wid);
1346 	AG_ObjectLock(wid);
1347 
1348 	if (!AG_OfClass(wid, "AG_Widget:AG_Window:*")) {
1349 		if ((wid->flags & AG_WIDGET_FOCUSED) == 0 ||
1350 		    (wid->flags & AG_WIDGET_VISIBLE) == 0 ||
1351 		    (wid->flags & AG_WIDGET_DISABLED)) {
1352 			goto fail;
1353 		}
1354 	}
1355 	/* Search for a better match. */
1356 	OBJECT_FOREACH_CHILD(cwid, wid, ag_widget) {
1357 		if ((fwid = AG_WidgetFindFocused(cwid)) != NULL) {
1358 			AG_ObjectUnlock(wid);
1359 			AG_UnlockVFS(wid);
1360 			return (fwid);
1361 		}
1362 	}
1363 
1364 	AG_ObjectUnlock(wid);
1365 	AG_UnlockVFS(wid);
1366 	return (wid);
1367 fail:
1368 	AG_ObjectUnlock(wid);
1369 	AG_UnlockVFS(wid);
1370 	return (NULL);
1371 }
1372 
1373 /* Compute the absolute view coordinates of a widget and its descendents. */
1374 void
AG_WidgetUpdateCoords(void * obj,int x,int y)1375 AG_WidgetUpdateCoords(void *obj, int x, int y)
1376 {
1377 	AG_Widget *wid = obj, *chld;
1378 	AG_Rect2 rPrev;
1379 
1380 	AG_LockVFS(wid);
1381 	AG_ObjectLock(wid);
1382 	wid->flags &= ~(AG_WIDGET_UPDATE_WINDOW);
1383 
1384 	if (wid->drv != NULL && AGDRIVER_MULTIPLE(wid->drv) &&
1385 	    AG_OfClass(wid, "AG_Widget:AG_Window:*")) {
1386 		/* Multiple-window drivers use window coordinate systems */
1387 		x = 0;
1388 		y = 0;
1389 	}
1390 
1391 	rPrev = wid->rView;
1392 	wid->rView.x1 = x;
1393 	wid->rView.y1 = y;
1394 	wid->rView.w = wid->w;
1395 	wid->rView.h = wid->h;
1396 	wid->rView.x2 = x + wid->w;
1397 	wid->rView.y2 = y + wid->h;
1398 
1399 	wid->rSens.x1 = x;
1400 	wid->rSens.y1 = y;
1401 	wid->rSens.w = wid->w;
1402 	wid->rSens.h = wid->h;
1403 	wid->rSens.x2 = x + wid->w;
1404 	wid->rSens.y2 = y + wid->h;
1405 
1406 	if (AG_RectCompare2(&wid->rView, &rPrev) != 0) {
1407 		AG_PostEvent(NULL, wid, "widget-moved", NULL);
1408 #ifdef HAVE_OPENGL
1409 		wid->flags |= AG_WIDGET_GL_RESHAPE;
1410 #endif
1411 	}
1412 	OBJECT_FOREACH_CHILD(chld, wid, ag_widget) {
1413 		AG_WidgetUpdateCoords(chld,
1414 		    wid->rView.x1 + chld->x,
1415 		    wid->rView.y1 + chld->y);
1416 	}
1417 
1418 	AG_ObjectUnlock(wid);
1419 	AG_UnlockVFS(wid);
1420 }
1421 
1422 /* Parse a generic size specification. */
1423 enum ag_widget_sizespec
AG_WidgetParseSizeSpec(const char * input,int * w)1424 AG_WidgetParseSizeSpec(const char *input, int *w)
1425 {
1426 	char spec[1024], *p;
1427 	size_t len;
1428 
1429 	Strlcpy(spec, input, sizeof(spec));
1430 	len = strlen(spec);
1431 	if (len == 0) { goto syntax; }
1432 	p = &spec[len-1];
1433 
1434 	switch (*p) {
1435 	case '-':
1436 		*w = 0;
1437 		return (AG_WIDGET_FILL);
1438 	case '%':
1439 		*p = '\0';
1440 		*w = (int)strtol(spec, NULL, 10);
1441 		return (AG_WIDGET_PERCENT);
1442 	case '>':
1443 		if (spec[0] != '<') { goto syntax; }
1444 		*p = '\0';
1445 		AG_TextSize(&spec[1], w, NULL);
1446 		return (AG_WIDGET_STRINGLEN);
1447 	case 'x':
1448 		if (p > &spec[0] && p[-1] != 'p') { goto syntax; }
1449 		p[-1] = '\0';
1450 		*w = (int)strtol(spec, NULL, 10);
1451 		return (AG_WIDGET_PIXELS);
1452 	}
1453 syntax:
1454 	Verbose("Warning: Bad SizeSpec: \"%s\"\n", input);
1455 	*w = 0;
1456 	return (AG_WIDGET_BAD_SPEC);
1457 }
1458 
1459 int
AG_WidgetScrollDelta(Uint32 * t1)1460 AG_WidgetScrollDelta(Uint32 *t1)
1461 {
1462 	Uint32 t2 = AG_GetTicks();
1463 	int delta;
1464 
1465 	if (*t1 != 0 && ((delta = (t2 - *t1))) < 250) {
1466 		return (((250-delta)<<3)>>9);
1467 	}
1468 	*t1 = AG_GetTicks();
1469 	return (1);
1470 }
1471 
1472 /* Show a widget */
1473 void
AG_WidgetShow(void * obj)1474 AG_WidgetShow(void *obj)
1475 {
1476 	AG_Widget *wid = obj;
1477 
1478 	AG_ObjectLock(wid);
1479 	wid->flags &= ~(AG_WIDGET_HIDE);
1480 	AG_PostEvent(NULL, wid, "widget-shown", NULL);
1481 	AG_WindowUpdate(wid->window);
1482 	AG_ObjectUnlock(wid);
1483 }
1484 
1485 /* Hide a widget */
1486 void
AG_WidgetHide(void * obj)1487 AG_WidgetHide(void *obj)
1488 {
1489 	AG_Widget *wid = obj;
1490 
1491 	AG_ObjectLock(wid);
1492 	wid->flags |= AG_WIDGET_HIDE;
1493 	AG_PostEvent(NULL, wid, "widget-hidden", NULL);
1494 	AG_WindowUpdate(wid->window);
1495 	AG_ObjectUnlock(wid);
1496 }
1497 
1498 /* Make a widget and all of its children visible. */
1499 void
AG_WidgetShowAll(void * p)1500 AG_WidgetShowAll(void *p)
1501 {
1502 	AG_Widget *wid = p;
1503 	AG_Widget *chld;
1504 
1505 	AG_LockVFS(wid);
1506 	AG_ObjectLock(wid);
1507 
1508 	OBJECT_FOREACH_CHILD(chld, wid, ag_widget)
1509 		AG_WidgetShowAll(chld);
1510 
1511 	AG_PostEvent(NULL, wid, "widget-shown", NULL);
1512 
1513 	AG_ObjectUnlock(wid);
1514 	AG_UnlockVFS(wid);
1515 }
1516 
1517 /* Make a widget and all of its children invisible. */
1518 void
AG_WidgetHideAll(void * p)1519 AG_WidgetHideAll(void *p)
1520 {
1521 	AG_Widget *wid = p;
1522 	AG_Widget *chld;
1523 
1524 	AG_LockVFS(wid);
1525 	AG_ObjectLock(wid);
1526 
1527 	OBJECT_FOREACH_CHILD(chld, wid, ag_widget)
1528 		AG_WidgetHideAll(chld);
1529 
1530 	AG_PostEvent(NULL, wid, "widget-hidden", NULL);
1531 
1532 	AG_ObjectUnlock(wid);
1533 	AG_UnlockVFS(wid);
1534 }
1535 
1536 static void *
FindAtPoint(AG_Widget * parent,const char * type,int x,int y)1537 FindAtPoint(AG_Widget *parent, const char *type, int x, int y)
1538 {
1539 	AG_Widget *chld;
1540 	void *p;
1541 
1542 	OBJECT_FOREACH_CHILD(chld, parent, ag_widget) {
1543 		if ((p = FindAtPoint(chld, type, x, y)) != NULL)
1544 			return (p);
1545 	}
1546 	if ((parent->flags & AG_WIDGET_VISIBLE) &&
1547 	    AG_OfClass(parent, type) &&
1548 	    AG_WidgetArea(parent, x, y)) {
1549 		return (parent);
1550 	}
1551 	return (NULL);
1552 }
1553 
1554 /* Search for widgets of the specified class enclosing the given point. */
1555 void *
AG_WidgetFindPoint(const char * type,int x,int y)1556 AG_WidgetFindPoint(const char *type, int x, int y)
1557 {
1558 	AG_Driver *drv;
1559 	AG_Window *win;
1560 	void *p;
1561 
1562 	AG_LockVFS(&agDrivers);
1563 	OBJECT_FOREACH_CHILD(drv, &agDrivers, ag_driver) {
1564 		AG_FOREACH_WINDOW_REVERSE(win, drv) {
1565 			if ((p = FindAtPoint(WIDGET(win), type, x, y)) != NULL) {
1566 				AG_UnlockVFS(&agDrivers);
1567 				return (p);
1568 			}
1569 		}
1570 	}
1571 	AG_UnlockVFS(&agDrivers);
1572 	return (NULL);
1573 }
1574 
1575 static void *
FindRectOverlap(AG_Widget * parent,const char * type,int x,int y,int w,int h)1576 FindRectOverlap(AG_Widget *parent, const char *type, int x, int y, int w, int h)
1577 {
1578 	AG_Widget *chld;
1579 	void *p;
1580 
1581 	OBJECT_FOREACH_CHILD(chld, parent, ag_widget) {
1582 		if ((p = FindRectOverlap(chld, type, x,y,w,h)) != NULL)
1583 			return (p);
1584 	}
1585 	if (AG_OfClass(parent, type) &&
1586 	    !(x+w < parent->rView.x1 || x > parent->rView.x2 ||
1587 	      y+w < parent->rView.y1 || y > parent->rView.y2)) {
1588 		return (parent);
1589 	}
1590 	return (NULL);
1591 }
1592 
1593 /*
1594  * Search for widgets of the specified class enclosing the given rectangle.
1595  * Result is only accurate as long as the Driver VFS is locked.
1596  */
1597 void *
AG_WidgetFindRect(const char * type,int x,int y,int w,int h)1598 AG_WidgetFindRect(const char *type, int x, int y, int w, int h)
1599 {
1600 	AG_Driver *drv;
1601 	AG_Window *win;
1602 	void *p;
1603 
1604 	AG_LockVFS(&agDrivers);
1605 	OBJECT_FOREACH_CHILD(drv, &agDrivers, ag_driver) {
1606 		AG_FOREACH_WINDOW_REVERSE(win, drv) {
1607 			if ((p = FindRectOverlap(WIDGET(win), type, x,y,w,h)) != NULL) {
1608 				AG_UnlockVFS(&agDrivers);
1609 				return (p);
1610 			}
1611 		}
1612 	}
1613 	AG_UnlockVFS(&agDrivers);
1614 	return (NULL);
1615 }
1616 
1617 /* Generic inherited draw() routine. */
1618 void
AG_WidgetInheritDraw(void * obj)1619 AG_WidgetInheritDraw(void *obj)
1620 {
1621 	WIDGET_SUPER_OPS(obj)->draw(obj);
1622 }
1623 
1624 /* Generic inherited size_request() routine. */
1625 void
AG_WidgetInheritSizeRequest(void * obj,AG_SizeReq * r)1626 AG_WidgetInheritSizeRequest(void *obj, AG_SizeReq *r)
1627 {
1628 	WIDGET_SUPER_OPS(obj)->size_request(obj, r);
1629 }
1630 
1631 /* Generic inherited size_allocate() routine. */
1632 int
AG_WidgetInheritSizeAllocate(void * obj,const AG_SizeAlloc * a)1633 AG_WidgetInheritSizeAllocate(void *obj, const AG_SizeAlloc *a)
1634 {
1635 	return WIDGET_SUPER_OPS(obj)->size_allocate(obj, a);
1636 }
1637 
1638 /* Render a widget to an AG_Surface(3). */
1639 AG_Surface *
AG_WidgetSurface(void * obj)1640 AG_WidgetSurface(void *obj)
1641 {
1642 	AG_Widget *wid = obj;
1643 	AG_Surface *su;
1644 	int rv;
1645 
1646 	AG_LockVFS(wid);
1647 	rv = wid->drvOps->renderToSurface(wid->drv, wid, &su);
1648 	AG_UnlockVFS(wid);
1649 	return (rv == 0) ? su : NULL;
1650 }
1651 
1652 /* Map a new AG_Surface(3) with a widget; return the new surface handle. */
1653 int
AG_WidgetMapSurface(void * obj,AG_Surface * su)1654 AG_WidgetMapSurface(void *obj, AG_Surface *su)
1655 {
1656 	AG_Widget *wid = obj;
1657 	int i, s = -1;
1658 
1659 	if (su == NULL)
1660 		return (-1);
1661 
1662 	AG_ObjectLock(wid);
1663 	for (i = 0; i < wid->nsurfaces; i++) {
1664 		if (wid->surfaces[i] == NULL) {
1665 			s = i;
1666 			break;
1667 		}
1668 	}
1669 	if (i == wid->nsurfaces) {
1670 		wid->surfaces = Realloc(wid->surfaces,
1671 		    (wid->nsurfaces+1)*sizeof(AG_Surface *));
1672 		wid->surfaceFlags = Realloc(wid->surfaceFlags,
1673 		    (wid->nsurfaces+1)*sizeof(Uint));
1674 		wid->textures = Realloc(wid->textures,
1675 		    (wid->nsurfaces+1)*sizeof(Uint));
1676 		wid->texcoords = Realloc(wid->texcoords,
1677 		    (wid->nsurfaces+1)*sizeof(AG_TexCoord));
1678 		s = wid->nsurfaces++;
1679 	}
1680 	wid->surfaces[s] = su;
1681 	wid->surfaceFlags[s] = 0;
1682 	wid->textures[s] = 0;
1683 	AG_ObjectUnlock(wid);
1684 	return (s);
1685 }
1686 
1687 /* Replace the contents of a mapped surface. */
1688 void
AG_WidgetReplaceSurface(void * obj,int s,AG_Surface * su)1689 AG_WidgetReplaceSurface(void *obj, int s, AG_Surface *su)
1690 {
1691 	AG_Widget *wid = (AG_Widget *)obj;
1692 
1693 	AG_ObjectLock(wid);
1694 #ifdef AG_DEBUG
1695 	if (s < 0 || s >= wid->nsurfaces)
1696 		AG_FatalError("Invalid surface handle");
1697 #endif
1698 	if (wid->surfaces[s] != NULL) {
1699 		if (!WSURFACE_NODUP(wid,s))
1700 			AG_SurfaceFree(wid->surfaces[s]);
1701 	}
1702 	wid->surfaces[s] = su;
1703 	wid->surfaceFlags[s] &= ~(AG_WIDGET_SURFACE_NODUP);
1704 
1705 	/*
1706 	 * Queue the previous texture for deletion and set the texture handle
1707 	 * to 0 so the texture will be regenerated at the next blit.
1708 	 */
1709 	if (wid->textures[s] != 0 &&
1710 	    wid->drv != NULL &&
1711 	    wid->drvOps->deleteTexture != NULL) {
1712 		wid->drvOps->deleteTexture(wid->drv, wid->textures[s]);
1713 		wid->textures[s] = 0;
1714 	}
1715 	AG_ObjectUnlock(wid);
1716 }
1717 
1718 /*
1719  * Rebuild the effective style parameters of a widget and its descendants
1720  * based on its style attributes. Any required fonts are loaded. This
1721  * should be called whenever style attributes are changed.
1722  */
1723 static void
CompileStyleRecursive(AG_Widget * wid,const char * parentFace,double parentPtSize,Uint parentFlags,AG_WidgetPalette parentPalette)1724 CompileStyleRecursive(AG_Widget *wid, const char *parentFace,
1725     double parentPtSize, Uint parentFlags,
1726     AG_WidgetPalette parentPalette)
1727 {
1728 	AG_StyleSheet *css = &agDefaultCSS;
1729 	char face[256];
1730 	double ptSize;
1731 	Uint flags = parentFlags;
1732 	AG_Widget *chld;
1733 	AG_Variable *V;
1734 	int i, j;
1735 	AG_Object *po;
1736 	char *cssData;
1737 	double v;
1738 	char *ep;
1739 
1740 	/* Select the effective style sheet for this widget. */
1741 	for (po = OBJECT(wid);
1742 	     po->parent != NULL && AG_OfClass(po->parent, "AG_Widget:*");
1743 	     po = po->parent) {
1744 		if (WIDGET(po)->css != NULL) {
1745 			css = WIDGET(po)->css;
1746 			break;
1747 		}
1748 	}
1749 
1750 	/* Set the font attributes. */
1751 	if ((V = AG_GetVariableLocked(wid, "font-family")) != NULL) {
1752 		Strlcpy(face, V->data.s, sizeof(face));
1753 		AG_UnlockVariable(V);
1754 	} else if (AG_LookupStyleSheet(css, wid, "font-family", &cssData)) {
1755 		Strlcpy(face, cssData, sizeof(face));
1756 	} else {
1757 		Strlcpy(face, parentFace, sizeof(face));
1758 	}
1759 	if ((V = AG_GetVariableLocked(wid, "font-size")) != NULL) {
1760 		v = strtod(V->data.s, &ep);
1761 		ptSize = (*ep == '%') ? parentPtSize*(v/100.0) : v;
1762 		AG_UnlockVariable(V);
1763 	} else if (AG_LookupStyleSheet(css, wid, "font-size", &cssData)) {
1764 		v = strtod(cssData, &ep);
1765 		ptSize = (*ep == '%') ? parentPtSize*(v/100.0) : v;
1766 	} else {
1767 		ptSize = parentPtSize;
1768 	}
1769 	if ((V = AG_GetVariableLocked(wid, "font-weight")) != NULL) {
1770 		if (AG_Strcasecmp(V->data.s, "bold") == 0) {
1771 			flags |= AG_FONT_BOLD;
1772 		} else if (AG_Strcasecmp(V->data.s, "normal") == 0) {
1773 			flags &= ~(AG_FONT_BOLD);
1774 		}
1775 		AG_UnlockVariable(V);
1776 	} else if (AG_LookupStyleSheet(css, wid, "font-weight", &cssData)) {
1777 		if (AG_Strcasecmp(cssData, "bold") == 0) {
1778 			flags |= AG_FONT_BOLD;
1779 		} else if (AG_Strcasecmp(cssData, "normal") == 0) {
1780 			flags &= ~(AG_FONT_BOLD);
1781 		}
1782 	}
1783 	if ((V = AG_GetVariableLocked(wid, "font-style")) != NULL) {
1784 		if (AG_Strcasecmp(V->data.s, "italic") == 0) {
1785 			flags |= AG_FONT_ITALIC;
1786 		} else if (AG_Strcasecmp(V->data.s, "normal") == 0) {
1787 			flags &= ~(AG_FONT_ITALIC);
1788 		}
1789 		AG_UnlockVariable(V);
1790 	}
1791 
1792 	/* Set the color attributes. */
1793 	for (i = 0; i < AG_WIDGET_NSTATES; i++) {
1794 		for (j = 0; j < AG_WIDGET_NCOLORS; j++) {
1795 			AG_Color *parentColor = &parentPalette.c[i][j];
1796 			char vName[AG_VARIABLE_NAME_MAX];
1797 
1798 			Strlcpy(vName, agWidgetColorNames[j], sizeof(vName));
1799 			Strlcat(vName, agWidgetStateNames[i], sizeof(vName));
1800 			if ((V = AG_GetVariableLocked(wid, vName)) != NULL) {
1801 				wid->pal.c[i][j] = AG_ColorFromString(V->data.s,
1802 				    parentColor);
1803 				AG_UnlockVariable(V);
1804 			} else if (AG_LookupStyleSheet(css, wid, vName, &cssData)) {
1805 				wid->pal.c[i][j] = AG_ColorFromString(cssData,
1806 				    parentColor);
1807 			} else {
1808 				Strlcpy(vName, agWidgetColorNames[j], sizeof(vName));
1809 				if (AG_LookupStyleSheet(css, wid, vName, &cssData)) {
1810 					wid->pal.c[i][j] = AG_ColorFromString(cssData,
1811 					    parentColor);
1812 				} else {
1813 					wid->pal.c[i][j] = *parentColor;
1814 				}
1815 			}
1816 		}
1817 	}
1818 
1819 	if (wid->flags & AG_WIDGET_USE_TEXT) {
1820 		char *pFace = face, *tok;
1821 		AG_Font *fontNew = NULL;
1822 
1823 		while ((tok = AG_Strsep(&pFace, ",")) != NULL) {
1824 			fontNew = AG_FetchFont(face, (int)ptSize, (int)flags);
1825 			if (fontNew != NULL)
1826 				break;
1827 		}
1828 		if (fontNew == NULL) {
1829 			fontNew = AG_FetchFont(NULL, (int)ptSize, (int)flags);
1830 		}
1831 		if (fontNew != NULL && wid->font != fontNew) {
1832 			if (wid->font != NULL) {
1833 				AG_UnusedFont(wid->font);
1834 			}
1835 			wid->font = fontNew;
1836 			AG_PushTextState();
1837 			AG_TextFont(wid->font);
1838 			AG_PostEvent(NULL, wid, "font-changed", NULL);
1839 			AG_PopTextState();
1840 			AG_Redraw(wid);
1841 		}
1842 	}
1843 
1844 
1845 	OBJECT_FOREACH_CHILD(chld, wid, ag_widget)
1846 		CompileStyleRecursive(chld, face, ptSize, flags, wid->pal);
1847 }
1848 void
AG_WidgetCompileStyle(void * obj)1849 AG_WidgetCompileStyle(void *obj)
1850 {
1851 	AG_Widget *wid = obj;
1852 	AG_Widget *parent;
1853 
1854 	AG_LockVFS(wid);
1855 	AG_MutexLock(&agTextLock);
1856 
1857 	if ((parent = OBJECT(wid)->parent) != NULL &&
1858 	    AG_OfClass(parent, "AG_Widget:*") &&
1859 	    parent->font != NULL) {
1860 		CompileStyleRecursive(wid,
1861 		    OBJECT(parent->font)->name,
1862 		    parent->font->spec.size,
1863 		    parent->font->flags,
1864 		    parent->pal);
1865 	} else {
1866 
1867 		CompileStyleRecursive(wid,
1868 		    OBJECT(agDefaultFont)->name,
1869 		    agDefaultFont->spec.size,
1870 		    agDefaultFont->flags,
1871 		    agDefaultPalette);
1872 	}
1873 	AG_MutexUnlock(&agTextLock);
1874 	AG_UnlockVFS(wid);
1875 }
1876 
1877 /*
1878  * Clear the style parameters of a widget and its descendants. Called
1879  * on detach. The widget's VFS must be locked.
1880  */
1881 void
AG_WidgetFreeStyle(void * obj)1882 AG_WidgetFreeStyle(void *obj)
1883 {
1884 	AG_Widget *wid = obj;
1885 	AG_Widget *chld;
1886 
1887 	if (wid->font != NULL) {
1888 		AG_UnusedFont(wid->font);
1889 		wid->font = NULL;
1890 	}
1891 	OBJECT_FOREACH_CHILD(chld, wid, ag_widget)
1892 		AG_WidgetFreeStyle(chld);
1893 }
1894 
1895 /* Copy all style properties from one widget to another. */
1896 void
AG_WidgetCopyStyle(void * objDst,void * objSrc)1897 AG_WidgetCopyStyle(void *objDst, void *objSrc)
1898 {
1899 	AG_Widget *widSrc = objSrc;
1900 	AG_Widget *widDst = objDst;
1901 	AG_Variable *V;
1902 	const char **s;
1903 
1904 	AG_ObjectLock(widSrc);
1905 	AG_ObjectLock(widDst);
1906 	for (s = &agWidgetPropNames[0]; *s != NULL; s++) {
1907 		if ((V = AG_GetVariableLocked(widSrc, *s)) != NULL) {
1908 			AG_SetString(widDst, *s, V->data.s);
1909 			AG_UnlockVariable(V);
1910 		}
1911 
1912 	}
1913 	AG_ObjectUnlock(widDst);
1914 	AG_ObjectUnlock(widSrc);
1915 
1916 	AG_WidgetCompileStyle(widDst);
1917 	AG_Redraw(widDst);
1918 }
1919 
1920 /*
1921  * Set the default font parameters for a widget.
1922  * If a NULL argument is provided, the parameter is inherited from parent.
1923  */
1924 void
AG_SetFont(void * obj,const AG_Font * font)1925 AG_SetFont(void *obj, const AG_Font *font)
1926 {
1927 	AG_Widget *wid = obj;
1928 
1929 	AG_SetString(wid, "font-family", OBJECT(font)->name);
1930 	AG_SetString(wid, "font-size", AG_Printf("%.2fpts", font->spec.size));
1931 	AG_SetString(wid, "font-weight", (font->flags & AG_FONT_BOLD) ? "bold" : "normal");
1932 	AG_SetString(wid, "font-style", (font->flags & AG_FONT_ITALIC) ? "italic" : "normal");
1933 	AG_WidgetCompileStyle(wid);
1934 	AG_Redraw(wid);
1935 }
1936 void
AG_SetStyle(void * obj,const char * which,const char * value)1937 AG_SetStyle(void *obj, const char *which, const char *value)
1938 {
1939 	AG_Widget *wid = obj;
1940 
1941 	if (value != NULL) {
1942 		AG_SetString(wid, which, value);
1943 	} else {
1944 		AG_Unset(wid, which);				/* inherit */
1945 	}
1946 	AG_WidgetCompileStyle(wid);
1947 	AG_Redraw(wid);
1948 }
1949 
1950 AG_WidgetClass agWidgetClass = {
1951 	{
1952 		"Agar(Widget)",
1953 		sizeof(AG_Widget),
1954 		{ 0,0 },
1955 		Init,
1956 		NULL,		/* free */
1957 		Destroy,
1958 		NULL,		/* load */
1959 		NULL,		/* save */
1960 		NULL		/* edit */
1961 	},
1962 	NULL,			/* draw */
1963 	SizeRequest,
1964 	SizeAllocate
1965 };
1966