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 #include <agar/core/core.h>
27 
28 #include <agar/gui/gui.h>
29 #include <agar/gui/window.h>
30 #include <agar/gui/titlebar.h>
31 #include <agar/gui/icon.h>
32 #include <agar/gui/primitive.h>
33 #include <agar/gui/icons.h>
34 #include <agar/gui/cursors.h>
35 #include <agar/gui/label.h>
36 
37 #include <agar/config/ag_debug_gui.h>
38 
39 #include <string.h>
40 #include <stdarg.h>
41 
42 static void OnShow(AG_Event *);
43 static void OnHide(AG_Event *);
44 static void OnFocusGain(AG_Event *);
45 static void OnFocusLoss(AG_Event *);
46 
47 int agWindowSideBorderDefault = 0;
48 int agWindowBotBorderDefault = 8;
49 
50 /* Protected by agDrivers VFS lock */
51 AG_WindowQ agWindowDetachQ;			/* Windows to detach */
52 AG_WindowQ agWindowShowQ;			/* Windows to show */
53 AG_WindowQ agWindowHideQ;			/* Windows to hide */
54 AG_Window *agWindowToFocus = NULL;		/* Window to focus */
55 AG_Window *agWindowFocused = NULL;		/* Window holding focus */
56 
57 /* Map enum ag_window_wm_type to EWMH window type */
58 const char *agWindowWmTypeNames[] = {
59 	"_NET_WM_WINDOW_TYPE_NORMAL",
60 	"_NET_WM_WINDOW_TYPE_DESKTOP",
61 	"_NET_WM_WINDOW_TYPE_DOCK",
62 	"_NET_WM_WINDOW_TYPE_TOOLBAR",
63 	"_NET_WM_WINDOW_TYPE_MENU",
64 	"_NET_WM_WINDOW_TYPE_UTILITY",
65 	"_NET_WM_WINDOW_TYPE_SPLASH",
66 	"_NET_WM_WINDOW_TYPE_DIALOG",
67 	"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU",
68 	"_NET_WM_WINDOW_TYPE_POPUP_MENU",
69 	"_NET_WM_WINDOW_TYPE_TOOLTIP",
70 	"_NET_WM_WINDOW_TYPE_NOTIFICATION",
71 	"_NET_WM_WINDOW_TYPE_COMBO",
72 	"_NET_WM_WINDOW_TYPE_DND"
73 };
74 
75 void
AG_InitWindowSystem(void)76 AG_InitWindowSystem(void)
77 {
78 	TAILQ_INIT(&agWindowDetachQ);
79 	TAILQ_INIT(&agWindowShowQ);
80 	TAILQ_INIT(&agWindowHideQ);
81 	agWindowToFocus = NULL;
82 	agWindowFocused = NULL;
83 }
84 
85 void
AG_DestroyWindowSystem(void)86 AG_DestroyWindowSystem(void)
87 {
88 }
89 
90 static void
InitWindow(AG_Window * win,Uint flags)91 InitWindow(AG_Window *win, Uint flags)
92 {
93 	AG_ObjectInit(win, &agWindowClass);
94 	AG_ObjectSetNameS(win, "generic");
95 
96 	/* We use a specific naming system. */
97 	OBJECT(win)->flags &= ~(AG_OBJECT_NAME_ONATTACH);
98 
99 	WIDGET(win)->window = win;
100 	win->flags |= flags;
101 
102 	if (win->flags & AG_WINDOW_MODAL)
103 		win->flags |= AG_WINDOW_NOMAXIMIZE|AG_WINDOW_NOMINIMIZE;
104 	if (win->flags & AG_WINDOW_NORESIZE)
105 		win->flags |= AG_WINDOW_NOMAXIMIZE;
106 #ifdef AG_LEGACY
107 	if (win->flags & AG_WINDOW_POPUP) { win->wmType = AG_WINDOW_WM_POPUP_MENU; }
108 	if (win->flags & AG_WINDOW_DIALOG) { win->wmType = AG_WINDOW_WM_DIALOG; }
109 #endif
110 
111 	/* Default window "close" action should be detach/free. */
112 	AG_SetEvent(win, "window-close", AGWINDETACH(win));
113 }
114 
115 /*
116  * Create a generic window under a specific single-window driver. Return
117  * a pointer to the newly-allocated window, or NULL on failure.
118  */
119 AG_Window *
AG_WindowNewSw(void * pDrv,Uint flags)120 AG_WindowNewSw(void *pDrv, Uint flags)
121 {
122 	AG_Driver *drv = pDrv;
123 	AG_Window *win;
124 
125 	if (!AG_OfClass(drv, "AG_Driver:AG_DriverSw:*")) {
126 		return (NULL);
127 	}
128 
129 	if ((win = TryMalloc(sizeof(AG_Window))) == NULL) {
130 		return (NULL);
131 	}
132 	InitWindow(win, flags);
133 	AG_ObjectAttach(drv, win);
134 
135 	if (!(win->flags & AG_WINDOW_NOTITLE)) {
136 		win->hMin += agTextFontHeight;
137 	}
138 	if (win->flags & AG_WINDOW_NOBORDERS) {
139 		win->wBorderSide = 0;
140 		win->wBorderBot = 0;
141 	}
142 	return (win);
143 }
144 
145 /*
146  * Create a generic window using the default window driver; return
147  * a pointer to the newly-allocated window, or NULL on failure.
148  *
149  * - With single-window drivers, all Window objects are attached to
150  *   the main Driver object.
151  * - With multiple-window drivers: A parent Driver object is created
152  *   for every Window object.
153  */
154 AG_Window *
AG_WindowNew(Uint flags)155 AG_WindowNew(Uint flags)
156 {
157 	AG_Driver *drv;
158 	AG_Window *win;
159 
160 	if ((win = TryMalloc(sizeof(AG_Window))) == NULL) {
161 		return (NULL);
162 	}
163 	InitWindow(win, flags);
164 
165 	switch (agDriverOps->wm) {
166 	case AG_WM_SINGLE:
167 		AG_ObjectAttach(agDriverSw, win);
168 
169 		if (!(win->flags & AG_WINDOW_NOTITLE)) {
170 			win->hMin += agTextFontHeight;
171 		}
172 		if (win->flags & AG_WINDOW_NOBORDERS) {
173 			win->wBorderSide = 0;
174 			win->wBorderBot = 0;
175 		}
176 		break;
177 	case AG_WM_MULTIPLE:
178 		if ((drv = AG_DriverOpen(agDriverOps)) == NULL) {
179 			return (NULL);
180 		}
181 		AG_ObjectAttach(drv, win);
182 		AGDRIVER_MW(drv)->win = win;
183 		win->wBorderSide = 0;
184 		win->wBorderBot = 0;
185 		break;
186 	}
187 	return (win);
188 }
189 
190 /* Create a named window (C string). */
191 AG_Window *
AG_WindowNewNamedS(Uint flags,const char * name)192 AG_WindowNewNamedS(Uint flags, const char *name)
193 {
194 	AG_Window *win;
195 
196 #ifdef AG_DEBUG
197 	const char *p;
198 	if ((p = strchr(name, '/')) != NULL)
199 		AG_FatalError("Window names cannot contain `/' (near \"%s\")", p);
200 #endif
201 
202 	AG_LockVFS(&agDrivers);
203 	if (AG_WindowFocusNamed(name)) {
204 		win = NULL;
205 		goto out;
206 	}
207 	win = AG_WindowNew(flags);
208 	AG_ObjectSetNameS(win, name);
209 	AG_SetEvent(win, "window-close", AGWINHIDE(win));
210 out:
211 	AG_UnlockVFS(&agDrivers);
212 	return (win);
213 }
214 
215 /* Create a named window (format string). */
216 AG_Window *
AG_WindowNewNamed(Uint flags,const char * fmt,...)217 AG_WindowNewNamed(Uint flags, const char *fmt, ...)
218 {
219 	char s[AG_OBJECT_NAME_MAX];
220 	va_list ap;
221 
222 	va_start(ap, fmt);
223 	Vsnprintf(s, sizeof(s), fmt, ap);
224 	va_end(ap);
225 	return AG_WindowNewNamedS(flags, s);
226 }
227 
228 /* Special implementation of AG_ObjectAttach() for AG_Window. */
229 static void
Attach(AG_Event * event)230 Attach(AG_Event *event)
231 {
232 	AG_Window *win = AG_SELF();
233 	AG_Driver *drv = OBJECT(win)->parent;
234 
235 	/* Attach the window. */
236 	if (win->flags & AG_WINDOW_KEEPBELOW) {
237 		TAILQ_INSERT_HEAD(&OBJECT(drv)->children, OBJECT(win), cobjs);
238 	} else {
239 		TAILQ_INSERT_TAIL(&OBJECT(drv)->children, OBJECT(win), cobjs);
240 	}
241 	if (AGDRIVER_MULTIPLE(drv))
242 		AGDRIVER_MW(drv)->win = win;
243 
244 	/*
245 	 * Notify the objects. This will cause the the "drv" and "drvOps"
246 	 * pointers of all widgets to be updated.
247 	 */
248 	AG_PostEvent(drv, win, "attached", NULL);
249 
250 	if (AGDRIVER_SINGLE(drv)) {
251 		/*
252 		 * Initialize the built-in AG_Titlebar and dekstop AG_Icon now
253 		 * that we have an attached driver (we could not do this
254 		 * earlier because surface operations are involved).
255 		 */
256 		if (win->tbar == NULL && !(win->flags & AG_WINDOW_NOTITLE)) {
257 			Uint titlebarFlags = 0;
258 
259 			if (win->flags & AG_WINDOW_NOCLOSE) { titlebarFlags |= AG_TITLEBAR_NO_CLOSE; }
260 			if (win->flags & AG_WINDOW_NOMINIMIZE) { titlebarFlags |= AG_TITLEBAR_NO_MINIMIZE; }
261 			if (win->flags & AG_WINDOW_NOMAXIMIZE) { titlebarFlags |= AG_TITLEBAR_NO_MAXIMIZE; }
262 
263 			win->tbar = AG_TitlebarNew(win, titlebarFlags);
264 		}
265 		if (win->icon == NULL) {
266 			win->icon = AG_IconNew(NULL, 0);
267 		}
268 		WIDGET(win->icon)->drv = drv;
269 		WIDGET(win->icon)->drvOps = AGDRIVER_CLASS(drv);
270 		AG_IconSetSurfaceNODUP(win->icon, agIconWindow.s);
271 		AG_IconSetBackgroundFill(win->icon, 1, AGDRIVER_SW(drv)->bgColor);
272 		AG_SetStyle(win->icon, "font-size", "80%");
273 		AG_WidgetCompileStyle(win->icon);
274 	}
275 
276 	if (win->flags & AG_WINDOW_FOCUSONATTACH)
277 		AG_WindowFocus(win);
278 }
279 
280 /* Special implementation of AG_ObjectDetach() for AG_Window. */
281 static void
Detach(AG_Event * event)282 Detach(AG_Event *event)
283 {
284 	AG_Window *win = AG_SELF();
285 	AG_Driver *drv;
286 	AG_Window *other, *subwin;
287 	AG_Timer *to, *toNext;
288 
289 #ifdef AG_DEBUG_GUI
290 	Debug(NULL, "AG_ObjectDetach(Window %s, \"%s\")\n", OBJECT(win)->name,
291 	    win->caption);
292 #endif
293 
294 	AG_LockVFS(&agDrivers);
295 
296 	/* Mark window detach in progress */
297 	win->flags |= AG_WINDOW_DETACHING;
298 
299 	/* Cancel any running timer attached to the window. */
300 	AG_LockTiming();
301 	for (to = TAILQ_FIRST(&OBJECT(win)->timers);
302 	     to != TAILQ_END(&OBJECT(win)->timers);
303 	     to = toNext) {
304 		toNext = TAILQ_NEXT(to, timers);
305 		AG_DelTimer(win, to);
306 	}
307 	AG_UnlockTiming();
308 
309 	/* Implicitely detach window dependencies. */
310 	AGOBJECT_FOREACH_CHILD(drv, &agDrivers, ag_driver) {
311 		AG_FOREACH_WINDOW(other, drv) {
312 			if (other == win) {
313 				continue;
314 			}
315 			AG_ObjectLock(other);
316 			TAILQ_FOREACH(subwin, &other->subwins, swins) {
317 				if (subwin == win)
318 					break;
319 			}
320 			if (subwin != NULL) {
321 				TAILQ_REMOVE(&other->subwins, subwin, swins);
322 			}
323 			if (other->pinnedTo == win) {
324 				AG_WindowUnpin(other);
325 			}
326 			if (other->parent == win ||
327 			    other->transientFor == win) {
328 				AG_ObjectDetach(other);
329 			}
330 			AG_ObjectUnlock(other);
331 		}
332 	}
333 
334  	/*
335 	 * For the AG_ObjectDetach() call to be safe in (free-threaded) event
336 	 * context, we must defer the actual window hide / detach operation
337 	 * until the end of the current event processing cycle.
338 	 */
339 	TAILQ_INSERT_TAIL(&agWindowDetachQ, win, detach);
340 
341 	/* Queued Show/Hide operations would be redundant. */
342 	TAILQ_FOREACH(other, &agWindowHideQ, visibility) {
343 		if (other == win) {
344 			TAILQ_REMOVE(&agWindowHideQ, win, visibility);
345 			break;
346 		}
347 	}
348 	TAILQ_FOREACH(other, &agWindowShowQ, visibility) {
349 		if (other == win) {
350 			TAILQ_REMOVE(&agWindowShowQ, win, visibility);
351 			break;
352 		}
353 	}
354 
355 	AG_UnlockVFS(&agDrivers);
356 }
357 
358 /* Timer callback for fade-in/fade-out */
359 static Uint32
FadeTimeout(AG_Timer * to,AG_Event * event)360 FadeTimeout(AG_Timer *to, AG_Event *event)
361 {
362 	AG_Window *win = AG_SELF();
363 	int dir = AG_INT(1);
364 
365 	if (dir == 1) {					/* Fade in */
366 		if (win->fadeOpacity < 1.0) {
367 			win->fadeOpacity += win->fadeInIncr;
368 			AG_WindowSetOpacity(win, win->fadeOpacity);
369 			return (to->ival);
370 		} else {
371 			return (0);
372 		}
373 	} else {					/* Fade out */
374 		if (win->fadeOpacity > 0.0) {
375 			win->fadeOpacity -= win->fadeOutIncr;
376 			AG_WindowSetOpacity(win, win->fadeOpacity);
377 			return (to->ival);
378 		} else {
379 			AG_WindowSetOpacity(win, 1.0);
380 
381 			/* Defer operation until AG_WindowProcessQueued(). */
382 			AG_LockVFS(&agDrivers);
383 			TAILQ_INSERT_TAIL(&agWindowHideQ, win, visibility);
384 			AG_UnlockVFS(&agDrivers);
385 			return (0);
386 		}
387 	}
388 }
389 
390 static void
Init(void * obj)391 Init(void *obj)
392 {
393 	AG_Window *win = obj;
394 	AG_Event *ev;
395 	int i;
396 
397 	win->wmType = AG_WINDOW_WM_NORMAL;
398 	win->flags = AG_WINDOW_NOCURSORCHG;
399 	win->visible = 0;
400 	win->alignment = AG_WINDOW_ALIGNMENT_NONE;
401 	win->spacing = 3;
402 	win->lPad = 2;
403 	win->rPad = 2;
404 	win->tPad = 2;
405 	win->bPad = 2;
406 	win->wReq = 0;
407 	win->hReq = 0;
408 	win->wMin = win->lPad + win->rPad + 16;
409 	win->hMin = win->tPad + win->bPad + 16;
410 	win->minPct = 50;
411 	win->wBorderBot = agWindowBotBorderDefault;
412 	win->wBorderSide = agWindowSideBorderDefault;
413 	win->wResizeCtrl = 16;
414 	win->rSaved = AG_RECT(-1,-1,-1,-1);
415 	win->r = AG_RECT(0,0,0,0);
416 	win->caption[0] = '\0';
417 	win->tbar = NULL;
418 	win->icon = NULL;
419 	win->nFocused = 0;
420 	win->parent = NULL;
421 	win->transientFor = NULL;
422 	win->pinnedTo = NULL;
423 	win->widExclMotion = NULL;
424 	win->fadeInTime = 0.06f;
425 	win->fadeInIncr = 0.2f;
426 	win->fadeOutTime = 0.06f;
427 	win->fadeOutIncr = 0.2f;
428 	win->fadeOpacity = 1.0f;
429 	win->zoom = AG_ZOOM_DEFAULT;
430 	TAILQ_INIT(&win->subwins);
431 	TAILQ_INIT(&win->cursorAreas);
432 	for (i = 0; i < 5; i++)
433 		win->caResize[i] = NULL;
434 
435 	AG_InitTimer(&win->fadeTo, "fade", 0);
436 
437 	AG_SetEvent(win, "window-gainfocus", OnFocusGain, NULL);
438 	AG_SetEvent(win, "window-lostfocus", OnFocusLoss, NULL);
439 
440 	/*
441 	 * We wish to forward incoming `widget-shown', `widget-hidden' and
442 	 * `detached' events to all attached child widgets.
443 	 */
444 	ev = AG_SetEvent(win, "widget-shown", OnShow, NULL);
445 	ev->flags |= AG_EVENT_PROPAGATE;
446 	ev = AG_SetEvent(win, "widget-hidden", OnHide, NULL);
447 	ev->flags |= AG_EVENT_PROPAGATE;
448 	ev = AG_SetEvent(win, "detached", NULL, NULL);
449 	ev->flags |= AG_EVENT_PROPAGATE;
450 
451 	/* Custom attach/detach hooks are needed by the window stack. */
452 	AG_ObjectSetAttachFn(win, Attach, NULL);
453 	AG_ObjectSetDetachFn(win, Detach, NULL);
454 
455 	/* Set the inheritable style defaults. */
456 	AG_SetString(win, "font-family", OBJECT(agDefaultFont)->name);
457 	AG_SetString(win, "font-size", AG_Printf("%.02fpts", agDefaultFont->spec.size));
458 	AG_SetString(win, "font-weight", (agDefaultFont->flags & AG_FONT_BOLD) ? "bold" : "normal");
459 	AG_SetString(win, "font-style", (agDefaultFont->flags & AG_FONT_ITALIC) ? "italic" : "normal");
460 	WIDGET(win)->font = agDefaultFont;
461 	WIDGET(win)->pal = agDefaultPalette;
462 
463 #ifdef AG_DEBUG
464 	AG_BindUint(win, "flags", &win->flags);
465 	AG_BindString(win, "caption", win->caption, sizeof(win->caption));
466 	AG_BindInt(win, "visible", &win->visible);
467 	AG_BindInt(win, "spacing", &win->spacing);
468 	AG_BindInt(win, "wReq", &win->wReq);
469 	AG_BindInt(win, "hReq", &win->hReq);
470 	AG_BindInt(win, "wMin", &win->wMin);
471 	AG_BindInt(win, "hMin", &win->hMin);
472 	AG_BindInt(win, "r.x", &win->r.x);
473 	AG_BindInt(win, "r.y", &win->r.y);
474 	AG_BindInt(win, "r.w", &win->r.w);
475 	AG_BindInt(win, "r.h", &win->r.h);
476 #endif /* AG_DEBUG */
477 }
478 
479 /*
480  * Make a window a logical child of the specified window. If the logical
481  * parent window is detached, its child windows will be automatically
482  * detached along with it.
483  */
484 void
AG_WindowAttach(AG_Window * winParent,AG_Window * winChld)485 AG_WindowAttach(AG_Window *winParent, AG_Window *winChld)
486 {
487 	if (winParent == NULL)
488 		return;
489 
490 	AG_LockVFS(&agDrivers);
491 	AG_ObjectLock(winParent);
492 	AG_ObjectLock(winChld);
493 	if (winChld->parent != NULL) {
494 		if (winChld->parent == winParent) {
495 			goto out;
496 		}
497 		AG_WindowDetach(winChld->parent, winChld);
498 	}
499 	winChld->parent = winParent;
500 	winChld->zoom = winParent->zoom;
501 	AG_WidgetCopyStyle(winChld, winParent);
502 	TAILQ_INSERT_HEAD(&winParent->subwins, winChld, swins);
503 out:
504 	AG_ObjectUnlock(winChld);
505 	AG_ObjectUnlock(winParent);
506 	AG_UnlockVFS(&agDrivers);
507 }
508 
509 /* Detach a window from its logical parent. */
510 void
AG_WindowDetach(AG_Window * winParent,AG_Window * winChld)511 AG_WindowDetach(AG_Window *winParent, AG_Window *winChld)
512 {
513 	if (winParent == NULL)
514 		return;
515 
516 	AG_LockVFS(&agDrivers);
517 	AG_ObjectLock(winParent);
518 	AG_ObjectLock(winChld);
519 
520 #ifdef AG_DEBUG
521 	if (winChld->parent != winParent)
522 		AG_FatalError("Inconsistent AG_WindowDetach()");
523 #endif
524 	TAILQ_REMOVE(&winParent->subwins, winChld, swins);
525 	winChld->parent = NULL;
526 
527 	AG_ObjectUnlock(winChld);
528 	AG_ObjectUnlock(winParent);
529 	AG_UnlockVFS(&agDrivers);
530 }
531 
532 /*
533  * Make a window a transient window of another window. The effect of
534  * this setting is WM-dependent (see AG_Window(3) for details).
535  */
536 void
AG_WindowMakeTransient(AG_Window * winParent,AG_Window * winTrans)537 AG_WindowMakeTransient(AG_Window *winParent, AG_Window *winTrans)
538 {
539 	AG_Driver *drv = WIDGET(winTrans)->drv;
540 
541 	AG_LockVFS(&agDrivers);
542 	AG_ObjectLock(winTrans);
543 
544 	if (winParent == NULL) {
545 		if (AGDRIVER_MULTIPLE(drv) &&
546 		    AGDRIVER_MW_CLASS(drv)->setTransientFor != NULL) {
547 			AGDRIVER_MW_CLASS(drv)->setTransientFor(NULL, winTrans);
548 		}
549 		winTrans->transientFor = NULL;
550 	} else {
551 		AG_ObjectLock(winParent);
552 		if (AGDRIVER_MULTIPLE(drv) &&
553 		    AGDRIVER_MW_CLASS(drv)->setTransientFor != NULL) {
554 			AGDRIVER_MW_CLASS(drv)->setTransientFor(winParent, winTrans);
555 		}
556 		winTrans->transientFor = winParent;
557 		AG_ObjectUnlock(winParent);
558 	}
559 
560 	AG_ObjectUnlock(winTrans);
561 	AG_UnlockVFS(&agDrivers);
562 }
563 
564 /* Pin a window against another. */
565 void
AG_WindowPin(AG_Window * winParent,AG_Window * win)566 AG_WindowPin(AG_Window *winParent, AG_Window *win)
567 {
568 #ifdef AG_DEBUG
569 	if (win == winParent) { AG_FatalError("AG_WindowPin"); }
570 #endif
571 	AG_ObjectLock(win);
572 	win->pinnedTo = winParent;
573 	AG_ObjectUnlock(win);
574 }
575 
576 /* Unpin a window. */
577 void
AG_WindowUnpin(AG_Window * win)578 AG_WindowUnpin(AG_Window *win)
579 {
580 	AG_ObjectLock(win);
581 	win->pinnedTo = NULL;
582 	AG_ObjectUnlock(win);
583 }
584 
585 static void
MovePinnedRecursive(AG_Window * win,AG_Window * winParent,int xRel,int yRel)586 MovePinnedRecursive(AG_Window *win, AG_Window *winParent, int xRel, int yRel)
587 {
588 	AG_Rect r;
589 	AG_Window *winOther;
590 	AG_Driver *drv;
591 
592 	r.x = WIDGET(win)->x + xRel;
593 	r.y = WIDGET(win)->y + yRel;
594 	r.w = WIDGET(win)->w;
595 	r.h = WIDGET(win)->h;
596 	AG_WindowSetGeometryRect(win, r, 0);
597 
598 	AGOBJECT_FOREACH_CHILD(drv, &agDrivers, ag_driver) {
599 		AG_FOREACH_WINDOW(winOther, drv) {
600 			if (winOther->pinnedTo == win)
601 				MovePinnedRecursive(winOther, win, xRel, yRel);
602 		}
603 	}
604 }
605 
606 void
AG_WindowMovePinned(AG_Window * winParent,int xRel,int yRel)607 AG_WindowMovePinned(AG_Window *winParent, int xRel, int yRel)
608 {
609 	AG_Driver *drv;
610 	AG_Window *win;
611 
612 	AG_LockVFS(&agDrivers);
613 	AGOBJECT_FOREACH_CHILD(drv, &agDrivers, ag_driver) {
614 		AG_FOREACH_WINDOW(win, drv) {
615 			if (win->pinnedTo == winParent)
616 				MovePinnedRecursive(win, winParent, xRel, yRel);
617 		}
618 	}
619 	AG_UnlockVFS(&agDrivers);
620 }
621 
622 /* Evaluate whether a widget is requesting geometry update. */
623 static int
UpdateNeeded(AG_Widget * wid)624 UpdateNeeded(AG_Widget *wid)
625 {
626 	AG_Widget *chld;
627 
628 	if (wid->flags & AG_WIDGET_UPDATE_WINDOW) {
629 		return (1);
630 	}
631 	OBJECT_FOREACH_CHILD(chld, wid, ag_widget) {
632 		if (UpdateNeeded(chld))
633 			return (1);
634 	}
635 	return (0);
636 }
637 
638 static void
Draw(void * obj)639 Draw(void *obj)
640 {
641 	AG_Window *win = obj;
642 	AG_Widget *chld;
643 	AG_Rect r;
644 	int hBar = (win->tbar != NULL) ? HEIGHT(win->tbar) : 0;
645 
646 	if (UpdateNeeded(WIDGET(win)))
647 		AG_WindowUpdate(win);
648 
649 	/* Render window background. */
650 	if (!(win->flags & AG_WINDOW_NOBACKGROUND) &&
651 	    !(WIDGET(win)->drv->flags & AG_DRIVER_WINDOW_BG)) {
652 		AG_DrawRect(win,
653 		    AG_RECT(0, hBar-1, WIDTH(win), HEIGHT(win)-hBar),
654 		    WCOLOR(win,0));
655 	}
656 
657 	/* Render decorative borders. */
658 	if (win->wBorderBot > 0) {
659 		r.x = 0;
660 		r.y = HEIGHT(win) - win->wBorderBot;
661 		r.h = win->wBorderBot;
662 		if (!(win->flags & AG_WINDOW_NORESIZE) &&
663 		    WIDTH(win) > win->wResizeCtrl*2) {
664 			r.w = win->wResizeCtrl;
665 			AG_DrawBox(win, r,
666 			    AG_WindowSelectedWM(win,AG_WINOP_LRESIZE) ? -1 : 1,
667 			    WCOLOR(win,BORDER_COLOR));
668 			r.x = WIDTH(win) - win->wResizeCtrl;
669 			AG_DrawBox(win, r,
670 			    AG_WindowSelectedWM(win,AG_WINOP_RRESIZE) ? -1 : 1,
671 			    WCOLOR(win,BORDER_COLOR));
672 			r.x = win->wResizeCtrl;
673 			r.w = WIDTH(win) - win->wResizeCtrl*2;
674 			AG_DrawBox(win, r,
675 			    AG_WindowSelectedWM(win,AG_WINOP_HRESIZE) ? -1 : 1,
676 			    WCOLOR(win,BORDER_COLOR));
677 		} else {
678 			r.w = WIDTH(win);
679 			AG_DrawBox(win, r, 1, WCOLOR(win,BORDER_COLOR));
680 		}
681 	}
682 	if (win->wBorderSide > 0) {
683 		r.x = 0;
684 		r.y = hBar;
685 		r.w = win->wBorderSide;
686 		r.h = HEIGHT(win) - win->wBorderBot - hBar;
687 		AG_DrawBox(win, r, 1, WCOLOR(win,BORDER_COLOR));
688 		r.x = WIDTH(win) - win->wBorderSide;
689 		AG_DrawBox(win, r, 1, WCOLOR(win,BORDER_COLOR));
690 	}
691 
692 	if (!(win->flags & AG_WINDOW_NOCLIPPING)) {
693 		AG_PushClipRect(win, win->r);
694 	}
695 	OBJECT_FOREACH_CHILD(chld, win, ag_widget) {
696 		AG_WidgetDraw(chld);
697 	}
698 	if (!(win->flags & AG_WINDOW_NOCLIPPING))
699 		AG_PopClipRect(win);
700 }
701 
702 static void
OnShow(AG_Event * event)703 OnShow(AG_Event *event)
704 {
705 	AG_Window *win = AG_SELF();
706 	AG_Driver *drv = WIDGET(win)->drv;
707 	AG_DriverMw *dmw = AGDRIVER_MW(drv);
708 	AG_SizeReq r;
709 	AG_SizeAlloc a;
710 	int xPref, yPref;
711 	Uint mwFlags = 0;
712 	AG_Variable V;
713 
714 	win->visible = 1;
715 	WIDGET(win)->flags |= AG_WIDGET_VISIBLE;
716 
717 	/* Compile the globally inheritable style attributes. */
718 	AG_WidgetCompileStyle(win);
719 
720 	if (WIDGET(win)->x == -1 && WIDGET(win)->y == -1) {
721 		/*
722 		 * No explicit window geometry was provided; compute a
723 		 * default size from the widget sizeReq() operations,
724 		 * and apply initial window alignment settings.
725 		 */
726 		AG_WidgetSizeReq(win, &r);
727 		if (AGDRIVER_SINGLE(drv)) {
728 			AG_WM_GetPrefPosition(win, &xPref, &yPref, r.w, r.h);
729 			a.x = xPref;
730 			a.y = yPref;
731 		} else {
732 			a.x = 0;
733 			a.y = 0;
734 		}
735 		a.w = r.w;
736 		a.h = r.h;
737 
738 		if (win->alignment != AG_WINDOW_ALIGNMENT_NONE) {
739 			if (!AGDRIVER_SINGLE(drv))
740 				AG_WindowComputeAlignment(win, &a);
741 		} else {
742 			if (dmw->flags & AG_DRIVER_MW_ANYPOS_AVAIL) {
743 				/* Let the WM choose a default position */
744 				mwFlags |= AG_DRIVER_MW_ANYPOS;
745 			} else {
746 				win->alignment = AG_WINDOW_MC;
747 				AG_WindowComputeAlignment(win, &a);
748 			}
749 		}
750 	} else {
751 		a.x = WIDGET(win)->x;
752 		a.y = WIDGET(win)->y;
753 		a.w = WIDTH(win);
754 		a.h = HEIGHT(win);
755 	}
756 	AG_WidgetSizeAlloc(win, &a);
757 
758 	switch (AGDRIVER_CLASS(drv)->wm) {
759 	case AG_WM_SINGLE:
760 		if (win->flags & AG_WINDOW_MODAL) {	/* Per-driver stack */
761 			AG_InitPointer(&V, win);
762 			AG_ListAppend(AGDRIVER_SW(drv)->Lmodal, &V);
763 		}
764 		AG_WidgetUpdateCoords(win, WIDGET(win)->x, WIDGET(win)->y);
765 		break;
766 	case AG_WM_MULTIPLE:
767 		if (win->flags & AG_WINDOW_MODAL) {	/* Global stack */
768 			AG_InitPointer(&V, win);
769 			AG_ListAppend(agModalWindows, &V);
770 		}
771 		/* We expect the driver will call AG_WidgetUpdateCoords(). */
772 		if (!(dmw->flags & AG_DRIVER_MW_OPEN)) {
773 			if (AGDRIVER_MW_CLASS(drv)->openWindow(win,
774 			    AG_RECT(a.x, a.y, a.w, a.h), 0,
775 			    mwFlags) == -1) {
776 				AG_FatalError(NULL);
777 			}
778 			dmw->flags |= AG_DRIVER_MW_OPEN;
779 		}
780 		if (win->flags & AG_WINDOW_FADEIN) {
781 			AG_WindowSetOpacity(win, 0.0);
782 		}
783 		if (AGDRIVER_MW_CLASS(drv)->mapWindow(win) == -1) {
784 			AG_FatalError(NULL);
785 		}
786 		if (AGDRIVER_MW_CLASS(drv)->setWindowCaption != NULL) {
787 			AGDRIVER_MW_CLASS(drv)->setWindowCaption(win,
788 			    win->caption);
789 		}
790 		break;
791 	}
792 
793 	/* Notify widgets that the window is now visible. */
794 	AG_PostEvent(NULL, win, "window-shown", NULL);
795 
796 	/* Implicit focus change. */
797 	if (!(win->flags & AG_WINDOW_DENYFOCUS))
798 		agWindowToFocus = win;
799 
800 	/* Mark for redraw */
801 	win->dirty = 1;
802 
803 	/* We can now allow cursor changes. */
804 	win->flags &= ~(AG_WINDOW_NOCURSORCHG);
805 
806 	if (win->flags & AG_WINDOW_FADEIN) {
807 		AG_AddTimer(win, &win->fadeTo,
808 		    (Uint32)((win->fadeInTime*1000.0)/(1.0/win->fadeInIncr)),
809 		    FadeTimeout, "%i", 1);
810 	}
811 }
812 
813 static void
OnHide(AG_Event * event)814 OnHide(AG_Event *event)
815 {
816 	AG_Window *win = AG_SELF();
817 	AG_Driver *drv = WIDGET(win)->drv;
818 	AG_DriverSw *dsw;
819 	int i;
820 
821 	win->visible = 0;
822 	WIDGET(win)->flags &= ~(AG_WIDGET_VISIBLE);
823 	win->dirty = 0;
824 	win->flags |= AG_WINDOW_NOCURSORCHG;
825 
826 	/* Cancel focus state or any focus change requests. */
827 	if (win == agWindowToFocus)
828 		agWindowToFocus = NULL;
829 
830 	switch (AGDRIVER_CLASS(drv)->wm) {
831 	case AG_WM_SINGLE:
832 		dsw = (AG_DriverSw *)drv;
833 #ifdef AG_DEBUG
834 		if (OBJECT(drv)->parent == NULL)
835 			AG_FatalError("NULL parent");
836 #endif
837 		if (win == agWindowFocused) {
838 			AG_Window *wOther;
839 
840 			AG_PostEvent(NULL, win, "window-lostfocus", NULL);
841 			agWindowFocused = NULL;
842 
843 			AG_FOREACH_WINDOW_REVERSE(wOther, dsw) {
844 				if (wOther->visible &&
845 				  !(wOther->flags & AG_WINDOW_DENYFOCUS))
846 					break;
847 			}
848 			if (wOther != NULL)
849 				agWindowToFocus = wOther;
850 		}
851 
852 		if (win->flags & AG_WINDOW_MODAL) {
853 			for (i = 0; i < dsw->Lmodal->n; i++) {
854 				if (dsw->Lmodal->v[i].data.p == win)
855 					break;
856 			}
857 			if (i < dsw->Lmodal->n)
858 				AG_ListRemove(dsw->Lmodal, i);
859 		}
860 		if (AGDRIVER_CLASS(drv)->type == AG_FRAMEBUFFER) {
861 			AG_DrawRectFilled(win,
862 			    AG_RECT(0,0, WIDTH(win), HEIGHT(win)), dsw->bgColor);
863 			if (AGDRIVER_CLASS(drv)->updateRegion != NULL)
864 				AGDRIVER_CLASS(drv)->updateRegion(drv,
865 				    AG_RECT(WIDGET(win)->x, WIDGET(win)->y,
866 				            WIDTH(win), HEIGHT(win)));
867 		}
868 		break;
869 	case AG_WM_MULTIPLE:
870 		if (win == agWindowFocused) {
871 			AG_PostEvent(NULL, win, "window-lostfocus", NULL);
872 			agWindowFocused = NULL;
873 		}
874 		if (win->flags & AG_WINDOW_MODAL) {
875 			for (i = 0; i < agModalWindows->n; i++) {
876 				if (agModalWindows->v[i].data.p == win)
877 					break;
878 			}
879 			if (i < agModalWindows->n)
880 				AG_ListRemove(agModalWindows, i);
881 		}
882 		if (AGDRIVER_MW(drv)->flags & AG_DRIVER_MW_OPEN) {
883 			if (AGDRIVER_MW_CLASS(drv)->unmapWindow(win) == -1)
884 				AG_FatalError(NULL);
885 		}
886 		break;
887 	}
888 
889 	/* Notify widgets that the window is now hidden. */
890 	AG_PostEvent(NULL, win, "window-hidden", NULL);
891 }
892 
893 static void
WidgetGainFocus(AG_Widget * wid)894 WidgetGainFocus(AG_Widget *wid)
895 {
896 	AG_Widget *chld;
897 
898 	OBJECT_FOREACH_CHILD(chld, wid, ag_widget) {
899 		AG_ObjectLock(chld);
900 		WidgetGainFocus(chld);
901 		AG_ObjectUnlock(chld);
902 	}
903 	if (wid->flags & AG_WIDGET_FOCUSED)
904 		AG_PostEvent(NULL, wid, "widget-gainfocus", NULL);
905 }
906 
907 static void
WidgetLostFocus(AG_Widget * wid)908 WidgetLostFocus(AG_Widget *wid)
909 {
910 	AG_Widget *chld;
911 
912 	OBJECT_FOREACH_CHILD(chld, wid, ag_widget) {
913 		AG_ObjectLock(chld);
914 		WidgetLostFocus(chld);
915 		AG_ObjectUnlock(chld);
916 	}
917 	if (wid->flags & AG_WIDGET_FOCUSED)
918 		AG_PostEvent(NULL, wid, "widget-lostfocus", NULL);
919 }
920 
921 static void
OnFocusGain(AG_Event * event)922 OnFocusGain(AG_Event *event)
923 {
924 	AG_Window *win = AG_SELF();
925 
926 /*	Verbose("%s (\"%s\"): Gained Focus\n", OBJECT(win)->name, win->caption); */
927 	WidgetGainFocus(WIDGET(win));
928 }
929 
930 static void
OnFocusLoss(AG_Event * event)931 OnFocusLoss(AG_Event *event)
932 {
933 	AG_Window *win = AG_SELF();
934 
935 /*	Verbose("%s (\"%s\"): Lost Focus\n", OBJECT(win)->name, win->caption); */
936 	WidgetLostFocus(WIDGET(win));
937 }
938 
939 /* Make a window visible to the user. */
940 void
AG_WindowShow(AG_Window * win)941 AG_WindowShow(AG_Window *win)
942 {
943 	AG_LockVFS(&agDrivers);
944 	AG_ObjectLock(win);
945 	if (!win->visible) {
946 #ifdef AG_THREADS
947 		if (!AG_ThreadEqual(AG_ThreadSelf(), agEventThread)) {
948 			AG_LockVFS(&agDrivers);
949 			TAILQ_INSERT_TAIL(&agWindowShowQ, win, visibility);
950 			AG_UnlockVFS(&agDrivers);
951 		} else
952 #endif
953 		{
954 			AG_PostEvent(NULL, win, "widget-shown", NULL);
955 		}
956 	}
957 	AG_ObjectUnlock(win);
958 	AG_UnlockVFS(&agDrivers);
959 }
960 
961 /* Make a window invisible to the user. */
962 void
AG_WindowHide(AG_Window * win)963 AG_WindowHide(AG_Window *win)
964 {
965 	AG_LockVFS(&agDrivers);
966 	AG_ObjectLock(win);
967 
968 	if (!win->visible) {
969 		goto out;
970 	}
971 	if ((win->flags & AG_WINDOW_FADEOUT) &&
972 	   !(win->flags & AG_WINDOW_DETACHING)) {
973 		AG_AddTimer(win, &win->fadeTo,
974 		    (Uint32)((win->fadeOutTime*1000.0)/(1.0/win->fadeOutIncr)),
975 		    FadeTimeout, "%i", -1);
976 	} else {
977 #ifdef AG_THREADS
978 		if (!AG_ThreadEqual(AG_ThreadSelf(), agEventThread)) {
979 			AG_LockVFS(&agDrivers);
980 			TAILQ_INSERT_TAIL(&agWindowHideQ, win, visibility);
981 			AG_UnlockVFS(&agDrivers);
982 		} else
983 #endif
984 		{
985 			AG_PostEvent(NULL, win, "widget-hidden", NULL);
986 			win->visible = 0;
987 			WIDGET(win)->flags &= ~(AG_WIDGET_VISIBLE);
988 		}
989 	}
990 out:
991 	AG_ObjectUnlock(win);
992 	AG_UnlockVFS(&agDrivers);
993 }
994 
995 /*
996  * Render all windows that need to be redrawn. This is typically invoked
997  * by the main event loop, once events have been processed.
998  */
999 void
AG_WindowDrawQueued(void)1000 AG_WindowDrawQueued(void)
1001 {
1002 	AG_Driver *drv;
1003 	AG_Window *win;
1004 
1005 	AG_LockVFS(&agDrivers);
1006 	AGOBJECT_FOREACH_CHILD(drv, &agDrivers, ag_driver) {
1007 		switch (AGDRIVER_CLASS(drv)->wm) {
1008 		case AG_WM_MULTIPLE:
1009 			if ((win = AGDRIVER_MW(drv)->win) != NULL) {
1010 				AG_ObjectLock(win);
1011 				if (win->visible && win->dirty) {
1012 					AG_BeginRendering(drv);
1013 					AGDRIVER_CLASS(drv)->renderWindow(win);
1014 					AG_EndRendering(drv);
1015 					win->dirty = 0;
1016 				}
1017 				AG_ObjectUnlock(win);
1018 			}
1019 			break;
1020 		case AG_WM_SINGLE:
1021 			{
1022 				AG_DriverSw *dsw = (AG_DriverSw *)drv;
1023 				Uint32 t;
1024 
1025 				t = AG_GetTicks();
1026 				if ((t - dsw->rLast) < dsw->rNom) {
1027 					AG_Delay(1);
1028 					goto out;
1029 				}
1030 				dsw->rLast = t;
1031 
1032 				AG_FOREACH_WINDOW(win, drv) {
1033 					if (win->visible && win->dirty)
1034 						break;
1035 				}
1036 				if (win != NULL ||
1037 				    (dsw->flags & AG_DRIVER_SW_REDRAW)) {
1038 					dsw->flags &= ~(AG_DRIVER_SW_REDRAW);
1039 					AG_BeginRendering(drv);
1040 					AG_FOREACH_WINDOW(win, drv) {
1041 						AG_ObjectLock(win);
1042 						AG_WindowDraw(win);
1043 						AG_ObjectUnlock(win);
1044 					}
1045 					AG_EndRendering(drv);
1046 				}
1047 			}
1048 			break;
1049 		}
1050 	}
1051 out:
1052 	AG_UnlockVFS(&agDrivers);
1053 }
1054 
1055 
1056 /* Build an ordered list of the focusable widgets in a window. */
1057 static void
ListFocusableWidgets(AG_List * L,AG_Widget * wid)1058 ListFocusableWidgets(AG_List *L, AG_Widget *wid)
1059 {
1060 	AG_Widget *chld;
1061 	AG_Variable V;
1062 
1063 	AG_ObjectLock(wid);
1064 	if (wid->flags & AG_WIDGET_FOCUSABLE) {
1065 		AG_InitPointer(&V, wid->focusFwd ? wid->focusFwd : wid);
1066 		AG_ListAppend(L, &V);
1067 	}
1068 	AG_ObjectUnlock(wid);
1069 
1070 	OBJECT_FOREACH_CHILD(chld, wid, ag_widget)
1071 		ListFocusableWidgets(L, chld);
1072 }
1073 
1074 /*
1075  * Move the widget focus inside a window.
1076  * The window must be locked.
1077  */
1078 void
AG_WindowCycleFocus(AG_Window * win,int reverse)1079 AG_WindowCycleFocus(AG_Window *win, int reverse)
1080 {
1081 	AG_List *Lfoc, *Luniq;
1082 	int i, j;
1083 
1084 	/* Generate a list of focusable widgets; eliminate duplicates. */
1085 	Lfoc = AG_ListNew();
1086 	Luniq = AG_ListNew();
1087 	ListFocusableWidgets(Lfoc, WIDGET(win));
1088 	for (i = 0; i < Lfoc->n; i++) {
1089 		for (j = 0; j < Luniq->n; j++) {
1090 			if (Lfoc->v[i].data.p == Luniq->v[j].data.p)
1091 				break;
1092 		}
1093 		if (j == Luniq->n)
1094 			AG_ListAppend(Luniq, &Lfoc->v[i]);
1095 	}
1096 	if (Luniq->n == 0)
1097 		goto out;
1098 
1099 	/* Move focus after/before the currently focused widget. */
1100 	if (reverse) {
1101 		for (i = 0; i < Luniq->n; i++) {
1102 			if (WIDGET(Luniq->v[i].data.p)->flags & AG_WIDGET_FOCUSED)
1103 				break;
1104 		}
1105 		if (i == -1) {
1106 			AG_WidgetFocus(Luniq->v[0].data.p);
1107 		} else {
1108 			if (i-1 < 0) {
1109 				AG_WidgetFocus(Luniq->v[Luniq->n - 1].data.p);
1110 			} else {
1111 				AG_WidgetFocus(Luniq->v[i - 1].data.p);
1112 			}
1113 		}
1114 	} else {
1115 		for (i = Luniq->n-1; i >= 0; i--) {
1116 			if (WIDGET(Luniq->v[i].data.p)->flags & AG_WIDGET_FOCUSED)
1117 				break;
1118 		}
1119 		if (i == Luniq->n) {
1120 			AG_WidgetFocus(Luniq->v[0].data.p);
1121 		} else {
1122 			if (i+1 < Luniq->n) {
1123 				AG_WidgetFocus(Luniq->v[i + 1].data.p);
1124 			} else {
1125 				AG_WidgetFocus(Luniq->v[0].data.p);
1126 			}
1127 		}
1128 	}
1129 out:
1130 	AG_ListDestroy(Lfoc);
1131 	AG_ListDestroy(Luniq);
1132 	win->dirty = 1;
1133 }
1134 
1135 /*
1136  * Give input focus to a window. The actual focus change will take effect at
1137  * the end of the current event cycle.
1138  */
1139 void
AG_WindowFocus(AG_Window * win)1140 AG_WindowFocus(AG_Window *win)
1141 {
1142 	AG_LockVFS(&agDrivers);
1143 
1144 	if (win == NULL) {
1145 		agWindowToFocus = NULL;
1146 		goto out;
1147 	}
1148 
1149 	/*
1150 	 * Avoid clobbering modal windows (single-window drivers are
1151 	 * expected to perform this test internally).
1152 	 */
1153 	if (agDriverOps->wm == AG_WM_MULTIPLE) {
1154 		if (agModalWindows->n > 0)
1155 			goto out;
1156 	}
1157 
1158 	AG_ObjectLock(win);
1159 	if (win->flags & AG_WINDOW_DENYFOCUS) {
1160 		AG_ObjectUnlock(win);
1161 		goto out;
1162 	}
1163 	if (OBJECT(win)->parent == NULL) {
1164 		/* Will focus on future attach */
1165 		win->flags |= AG_WINDOW_FOCUSONATTACH;
1166 	} else {
1167 		agWindowToFocus = win;
1168 	}
1169 	win->dirty = 1;
1170 	AG_ObjectUnlock(win);
1171 out:
1172 	AG_UnlockVFS(&agDrivers);
1173 }
1174 
1175 /* Give focus to a window by name, and show it is if is hidden. */
1176 int
AG_WindowFocusNamed(const char * name)1177 AG_WindowFocusNamed(const char *name)
1178 {
1179 	AG_Driver *drv;
1180 	AG_Window *owin;
1181 	int rv = 0;
1182 
1183 	AG_LockVFS(&agDrivers);
1184 	AGOBJECT_FOREACH_CHILD(drv, &agDrivers, ag_driver) {
1185 		AG_FOREACH_WINDOW(owin, drv) {
1186 			if (strcmp(OBJECT(owin)->name, name) == 0) {
1187 				AG_WindowShow(owin);
1188 				AG_WindowFocus(owin);
1189 			    	rv = 1;
1190 				goto out;
1191 			}
1192 		}
1193 	}
1194 out:
1195 	AG_UnlockVFS(&agDrivers);
1196 	return (rv);
1197 }
1198 
1199 /*
1200  * Focus the window at specified display coordinates x,y
1201  * (single-window drivers only).
1202  *
1203  * Returns 1 if the focus state has changed as a result.
1204  */
1205 int
AG_WindowFocusAtPos(AG_DriverSw * dsw,int x,int y)1206 AG_WindowFocusAtPos(AG_DriverSw *dsw, int x, int y)
1207 {
1208 	AG_Window *win;
1209 
1210 	AG_LockVFS(&agDrivers);
1211 
1212 	AG_ASSERT_CLASS(dsw, "AG_Driver:AG_DriverSw:*");
1213 	agWindowToFocus = NULL;
1214 
1215 	AG_FOREACH_WINDOW_REVERSE(win, dsw) {
1216 		AG_ObjectLock(win);
1217 		if (!win->visible ||
1218 		    !AG_WidgetArea(win, x,y) ||
1219 		    (win->flags & AG_WINDOW_DENYFOCUS)) {
1220 			AG_ObjectUnlock(win);
1221 			continue;
1222 		}
1223 		agWindowToFocus = win;
1224 		AG_ObjectUnlock(win);
1225 		AG_UnlockVFS(&agDrivers);
1226 		return (1);
1227 	}
1228 	AG_UnlockVFS(&agDrivers);
1229 	return (0);
1230 }
1231 
1232 /* Update window background after geometry change in single-display mode. */
1233 /* XXX TODO Avoid drawing over KEEPABOVE windows */
1234 static void
UpdateWindowBG(AG_Window * win,AG_Rect rPrev)1235 UpdateWindowBG(AG_Window *win, AG_Rect rPrev)
1236 {
1237 	AG_Driver *drv = WIDGET(win)->drv;
1238 	AG_Rect r;
1239 
1240 	if (WIDGET(win)->x > rPrev.x) {			/* L-resize */
1241 		r.x = rPrev.x;
1242 		r.y = rPrev.y;
1243 		r.w = WIDGET(win)->x - rPrev.x;
1244 		r.h = HEIGHT(win);
1245 	} else if (WIDTH(win) < rPrev.w) {		/* R-resize */
1246 		r.x = WIDGET(win)->x + WIDTH(win);
1247 		r.y = WIDGET(win)->y;
1248 		r.w = rPrev.w - WIDTH(win);
1249 		r.h = rPrev.h;
1250 	} else {
1251 		r.w = 0;
1252 		r.h = 0;
1253 	}
1254 	if (r.w > 0 && r.h > 0) {
1255 		AGDRIVER_CLASS(drv)->fillRect(drv, r, AGDRIVER_SW(drv)->bgColor);
1256 		if (AGDRIVER_CLASS(drv)->updateRegion != NULL)
1257 			AGDRIVER_CLASS(drv)->updateRegion(drv, r);
1258 	}
1259 	if (HEIGHT(win) < rPrev.h) {				/* H-resize */
1260 		r.x = rPrev.x;
1261 		r.y = WIDGET(win)->y + HEIGHT(win);
1262 		r.w = rPrev.w;
1263 		r.h = rPrev.h - HEIGHT(win);
1264 
1265 		AGDRIVER_CLASS(drv)->fillRect(drv, r, AGDRIVER_SW(drv)->bgColor);
1266 		if (AGDRIVER_CLASS(drv)->updateRegion != NULL)
1267 			AGDRIVER_CLASS(drv)->updateRegion(drv, r);
1268 	}
1269 }
1270 
1271 /* Set the coordinates and geometry of a window. */
1272 int
AG_WindowSetGeometryRect(AG_Window * win,AG_Rect r,int bounded)1273 AG_WindowSetGeometryRect(AG_Window *win, AG_Rect r, int bounded)
1274 {
1275 	AG_Driver *drv = WIDGET(win)->drv;
1276 	AG_DriverClass *dc = AGDRIVER_CLASS(drv);
1277 	AG_SizeReq rWin;
1278 	AG_SizeAlloc a;
1279 	AG_Rect rPrev;
1280 	int new;
1281 	int nw, nh;
1282 	int wMin, hMin;
1283 	Uint wDisp = 0, hDisp = 0;
1284 
1285 	AG_ObjectLock(win);
1286 	rPrev = AG_RECT(WIDGET(win)->x, WIDGET(win)->y,
1287 	                WIDGET(win)->w, WIDGET(win)->h);
1288 	new = ((WIDGET(win)->x == -1 || WIDGET(win)->y == -1));
1289 
1290 	/* Compute the final window size. */
1291 	if (r.w == -1 || r.h == -1) {
1292 		AG_WidgetSizeReq(win, &rWin);
1293 		nw = (r.w == -1) ? rWin.w : r.w;
1294 		nh = (r.h == -1) ? rWin.h : r.h;
1295 	} else {
1296 		nw = r.w;
1297 		nh = r.h;
1298 	}
1299 	if (win->flags & AG_WINDOW_MINSIZEPCT) {
1300 		wMin = win->minPct*win->wReq/100;
1301 		hMin = win->minPct*win->hReq/100;
1302 	} else {
1303 		wMin = win->wMin;
1304 		hMin = win->hMin;
1305 	}
1306 	if (nw < wMin) { nw = wMin; }
1307 	if (nh < hMin) { nh = hMin; }
1308 
1309 	if (WIDGET(win)->x == -1 ||
1310 	    WIDGET(win)->y == -1) {
1311 		if (AG_GetDisplaySize(drv, &wDisp, &hDisp) == -1) {
1312 			wDisp = 0;
1313 			hDisp = 0;
1314 		}
1315 		if (WIDGET(win)->x == -1)
1316 			WIDGET(win)->x = wDisp/2 - nw/2;
1317 		if (WIDGET(win)->y == -1)
1318 			WIDGET(win)->y = hDisp/2 - nh/2;
1319 	}
1320 	a.x = (r.x == -1) ? WIDGET(win)->x : r.x;
1321 	a.y = (r.y == -1) ? WIDGET(win)->y : r.y;
1322 	a.w = nw;
1323 	a.h = nh;
1324 	if (bounded)
1325 		AG_WM_LimitWindowToDisplaySize(drv, &a);
1326 
1327 	/* Invoke the driver-specific pre-resize callback routine. */
1328 	if (win->visible && AGDRIVER_MULTIPLE(drv))
1329 		AGDRIVER_MW_CLASS(drv)->preResizeCallback(win);
1330 
1331 	/*
1332 	 * Resize the widgets and update their coordinates; update
1333 	 * the geometry of the window's Widget structure.
1334 	 */
1335 	AG_WidgetSizeAlloc(win, &a);
1336 	AG_WidgetUpdateCoords(win, a.x, a.y);
1337 
1338 	switch (AGDRIVER_CLASS(drv)->wm) {
1339 	case AG_WM_SINGLE:
1340 		if (dc->type == AG_FRAMEBUFFER && win->visible && !new) {
1341 			UpdateWindowBG(win, rPrev);
1342 		}
1343 		break;
1344 	case AG_WM_MULTIPLE:
1345 		if ((AGDRIVER_MW(drv)->flags & AG_DRIVER_MW_OPEN) &&
1346 		    AGDRIVER_MW_CLASS(drv)->moveResizeWindow(win, &a) == -1) {
1347 			goto fail;
1348 		}
1349 		break;
1350 	}
1351 
1352 	win->dirty = 1;
1353 	AG_ObjectUnlock(win);
1354 	return (0);
1355 fail:
1356 	if (!new) {						/* Revert */
1357 		a.x = rPrev.x;
1358 		a.y = rPrev.y;
1359 		a.w = rPrev.w;
1360 		a.h = rPrev.h;
1361 		AG_WidgetSizeAlloc(win, &a);
1362 		AG_WidgetUpdateCoords(win, a.x, a.y);
1363 	}
1364 	AG_ObjectUnlock(win);
1365 	return (-1);
1366 }
1367 
1368 /* Configure minimum window size in percentage of computed geometry. */
1369 void
AG_WindowSetMinSizePct(AG_Window * win,int pct)1370 AG_WindowSetMinSizePct(AG_Window *win, int pct)
1371 {
1372 	AG_ObjectLock(win);
1373 	win->flags |= AG_WINDOW_MINSIZEPCT;
1374 	win->minPct = pct;
1375 	win->dirty = 1;
1376 	AG_ObjectUnlock(win);
1377 }
1378 
1379 /* Configure minimum window size in pixels. */
1380 void
AG_WindowSetMinSize(AG_Window * win,int w,int h)1381 AG_WindowSetMinSize(AG_Window *win, int w, int h)
1382 {
1383 	AG_ObjectLock(win);
1384 	win->flags &= ~(AG_WINDOW_MINSIZEPCT);
1385 	win->wMin = w;
1386 	win->hMin = h;
1387 	win->dirty = 1;
1388 	AG_ObjectUnlock(win);
1389 }
1390 
1391 /*
1392  * Update the x,y of the a to produce the required alignment for the
1393  * window's current display.
1394  */
1395 void
AG_WindowComputeAlignment(AG_Window * win,AG_SizeAlloc * a)1396 AG_WindowComputeAlignment(AG_Window *win, AG_SizeAlloc *a)
1397 {
1398 	AG_Driver *drv = WIDGET(win)->drv;
1399 	Uint wMax, hMax;
1400 	int w = a->w;
1401 	int h = a->h;
1402 
1403 	if (AG_GetDisplaySize(WIDGET(win)->drv, &wMax, &hMax) == -1)
1404 		return;
1405 
1406 	switch (win->alignment) {
1407 	case AG_WINDOW_TL:
1408 		a->x = 0;
1409 		a->y = 0;
1410 		break;
1411 	case AG_WINDOW_TC:
1412 		a->x = wMax/2 - w/2;
1413 		a->y = 0;
1414 		break;
1415 	case AG_WINDOW_TR:
1416 		a->x = wMax - w;
1417 		a->y = 0;
1418 		break;
1419 	case AG_WINDOW_ML:
1420 		a->x = 0;
1421 		a->y = hMax/2 - h/2;
1422 		break;
1423 	case AG_WINDOW_MR:
1424 		a->x = wMax - w;
1425 		a->y = hMax/2 - h/2;
1426 		break;
1427 	case AG_WINDOW_BL:
1428 		a->x = 0;
1429 		a->y = hMax - h;
1430 		break;
1431 	case AG_WINDOW_BC:
1432 		a->x = wMax/2 - w/2;
1433 		a->y = hMax - h;
1434 		break;
1435 	case AG_WINDOW_BR:
1436 		a->x = wMax - w;
1437 		a->y = hMax - h;
1438 		break;
1439 	case AG_WINDOW_MC:
1440 	case AG_WINDOW_ALIGNMENT_NONE:
1441 	default:
1442 		a->x = wMax/2 - w/2;
1443 		a->y = hMax/2 - h/2;
1444 		break;
1445 	}
1446 	if (AGDRIVER_MULTIPLE(drv) &&
1447 	    AGDRIVER_MW_CLASS(drv)->tweakAlignment != NULL)
1448 		AGDRIVER_MW_CLASS(drv)->tweakAlignment(win, a, wMax, hMax);
1449 }
1450 
1451 /* Assign a window a specific alignment and size in pixels. */
1452 int
AG_WindowSetGeometryAligned(AG_Window * win,enum ag_window_alignment alignment,int w,int h)1453 AG_WindowSetGeometryAligned(AG_Window *win, enum ag_window_alignment alignment,
1454     int w, int h)
1455 {
1456 	AG_SizeAlloc a;
1457 	int rv;
1458 
1459 	AG_ObjectLock(win);
1460 	win->alignment = alignment;
1461 	a.x = 0;
1462 	a.y = 0;
1463 	a.w = (w != -1) ? w : WIDGET(win)->w;
1464 	a.h = (h != -1) ? h : WIDGET(win)->h;
1465 	AG_WindowComputeAlignment(win, &a);
1466 	rv = AG_WindowSetGeometry(win, a.x, a.y, a.w, a.h);
1467 	AG_ObjectUnlock(win);
1468 	return (rv);
1469 }
1470 
1471 /* Assign a window a specific alignment and size in percentage of view area. */
1472 int
AG_WindowSetGeometryAlignedPct(AG_Window * win,enum ag_window_alignment align,int wPct,int hPct)1473 AG_WindowSetGeometryAlignedPct(AG_Window *win, enum ag_window_alignment align,
1474     int wPct, int hPct)
1475 {
1476 	Uint wMax = 0, hMax = 0;
1477 
1478 	AG_GetDisplaySize(WIDGET(win)->drv, &wMax, &hMax);
1479 
1480 	return AG_WindowSetGeometryAligned(win, align,
1481 	                                   wPct*wMax/100,
1482 	                                   hPct*hMax/100);
1483 }
1484 
1485 /* Backup the current window geometry (i.e., before a minimize) */
1486 void
AG_WindowSaveGeometry(AG_Window * win)1487 AG_WindowSaveGeometry(AG_Window *win)
1488 {
1489 	win->rSaved.x = WIDGET(win)->x;
1490 	win->rSaved.y = WIDGET(win)->y;
1491 	win->rSaved.w = WIDTH(win);
1492 	win->rSaved.h = HEIGHT(win);
1493 }
1494 
1495 /* Restore saved geometry (i.e., after an unminimize operation) */
1496 int
AG_WindowRestoreGeometry(AG_Window * win)1497 AG_WindowRestoreGeometry(AG_Window *win)
1498 {
1499 	return AG_WindowSetGeometryRect(win, win->rSaved, 0);
1500 }
1501 
1502 /* Maximize a window */
1503 void
AG_WindowMaximize(AG_Window * win)1504 AG_WindowMaximize(AG_Window *win)
1505 {
1506 	Uint wMax, hMax;
1507 
1508 	AG_WindowSaveGeometry(win);
1509 	AG_GetDisplaySize(WIDGET(win)->drv, &wMax, &hMax);
1510 	if (AG_WindowSetGeometry(win, 0, 0, wMax, hMax) == 0)
1511 		win->flags |= AG_WINDOW_MAXIMIZED;
1512 }
1513 
1514 /* Restore a window's geometry prior to maximization. */
1515 void
AG_WindowUnmaximize(AG_Window * win)1516 AG_WindowUnmaximize(AG_Window *win)
1517 {
1518 	if (AG_WindowRestoreGeometry(win) == 0) {
1519 		win->flags &= ~(AG_WINDOW_MAXIMIZED);
1520 		win->dirty = 1;
1521 	}
1522 }
1523 
1524 static void
IconMotion(AG_Event * event)1525 IconMotion(AG_Event *event)
1526 {
1527 	AG_Icon *icon = AG_SELF();
1528 	AG_Driver *drv = WIDGET(icon)->drv;
1529 	int xRel = AG_INT(3);
1530 	int yRel = AG_INT(4);
1531 	AG_Window *wDND = icon->wDND;
1532 
1533 	if (icon->flags & AG_ICON_DND) {
1534 		if (drv != NULL && AGDRIVER_SINGLE(drv)) {
1535 			AG_Rect r;
1536 			r.x = 0;
1537 			r.y = 0;
1538 			r.w = AGDRIVER_SW(drv)->w;
1539 			r.h = AGDRIVER_SW(drv)->h;
1540 			AGDRIVER_CLASS(drv)->fillRect(drv, r,
1541 			    AGDRIVER_SW(drv)->bgColor);
1542 			r = AG_Rect2ToRect(WIDGET(wDND)->rView);
1543 			if (AGDRIVER_CLASS(drv)->updateRegion != NULL)
1544 				AGDRIVER_CLASS(drv)->updateRegion(drv, r);
1545 		}
1546 		AG_WindowSetGeometryRect(wDND,
1547 		    AG_RECT(WIDGET(wDND)->x + xRel,
1548 		            WIDGET(wDND)->y + yRel,
1549 			    WIDTH(wDND),
1550 			    HEIGHT(wDND)), 1);
1551 
1552 		icon->xSaved = WIDGET(wDND)->x;
1553 		icon->ySaved = WIDGET(wDND)->y;
1554 		icon->wSaved = WIDTH(wDND);
1555 		icon->hSaved = HEIGHT(wDND);
1556 	}
1557 }
1558 
1559 /* Timer for double click on minimized icon. */
1560 static Uint32
IconDoubleClickTimeout(AG_Timer * to,AG_Event * event)1561 IconDoubleClickTimeout(AG_Timer *to, AG_Event *event)
1562 {
1563 	AG_Icon *icon = AG_SELF();
1564 
1565 	icon->flags &= ~(AG_ICON_DBLCLICKED);
1566 	return (0);
1567 }
1568 
1569 static void
IconButtonDown(AG_Event * event)1570 IconButtonDown(AG_Event *event)
1571 {
1572 	AG_Icon *icon = AG_SELF();
1573 	AG_Window *win = AG_PTR(1);
1574 
1575 	WIDGET(icon)->flags |= AG_WIDGET_UNFOCUSED_MOTION|
1576 	                       AG_WIDGET_UNFOCUSED_BUTTONUP;
1577 	if (icon->flags & AG_ICON_DBLCLICKED) {
1578 		AG_DelTimer(icon, &icon->toDblClick);
1579 		AG_WindowUnminimize(win);
1580 		AG_ObjectDetach(win->icon);
1581 		AG_ObjectDetach(icon->wDND);
1582 		icon->wDND = NULL;
1583 		icon->flags &= (AG_ICON_DND|AG_ICON_DBLCLICKED);
1584 	} else {
1585 		icon->flags |= (AG_ICON_DND|AG_ICON_DBLCLICKED);
1586 		AG_AddTimer(icon, &icon->toDblClick, agMouseDblclickDelay,
1587 		    IconDoubleClickTimeout, NULL);
1588 	}
1589 }
1590 
1591 static void
IconButtonUp(AG_Event * event)1592 IconButtonUp(AG_Event *event)
1593 {
1594 	AG_Icon *icon = AG_SELF();
1595 
1596 	WIDGET(icon)->flags &= ~(AG_WIDGET_UNFOCUSED_MOTION);
1597 	WIDGET(icon)->flags &= ~(AG_WIDGET_UNFOCUSED_BUTTONUP);
1598 	icon->flags &= ~(AG_ICON_DND);
1599 }
1600 
1601 /* Minimize a window */
1602 void
AG_WindowMinimize(AG_Window * win)1603 AG_WindowMinimize(AG_Window *win)
1604 {
1605 	AG_Driver *drv = WIDGET(win)->drv;
1606 
1607 	if (win->flags & AG_WINDOW_MINIMIZED) {
1608 		return;
1609 	}
1610 	win->flags |= AG_WINDOW_MINIMIZED;
1611 	AG_WindowHide(win);
1612 
1613 	if (AGDRIVER_SINGLE(drv)) {
1614 		AG_Window *wDND;
1615 		AG_Icon *icon = win->icon;
1616 
1617 		wDND = AG_WindowNew(AG_WINDOW_PLAIN|AG_WINDOW_KEEPBELOW|
1618 		                    AG_WINDOW_DENYFOCUS|AG_WINDOW_NOBACKGROUND);
1619 		AG_ObjectAttach(wDND, icon);
1620 		icon->wDND = wDND;
1621 		icon->flags &= ~(AG_ICON_DND|AG_ICON_DBLCLICKED);
1622 
1623 		AG_SetEvent(icon, "mouse-motion", IconMotion, NULL);
1624 		AG_SetEvent(icon, "mouse-button-up", IconButtonUp, NULL);
1625 		AG_SetEvent(icon, "mouse-button-down", IconButtonDown, "%p", win);
1626 
1627 #if 0
1628 		if (icon->xSaved != -1) {
1629 			AG_WindowShow(wDND);
1630 			AG_WindowSetGeometry(wDND, icon->xSaved, icon->ySaved,
1631 			                     icon->wSaved, icon->hSaved);
1632 		} else {
1633 #endif
1634 		AG_WindowSetPosition(wDND, AG_WINDOW_LOWER_LEFT, 1);
1635 		AG_WindowShow(wDND);
1636 		icon->xSaved = WIDGET(wDND)->x;
1637 		icon->ySaved = WIDGET(wDND)->y;
1638 		icon->wSaved = WIDTH(wDND);
1639 		icon->hSaved = HEIGHT(wDND);
1640 	}
1641 	/* TODO MW: send a WM_CHANGE_STATE */
1642 }
1643 
1644 /* Unminimize a window */
1645 void
1646 AG_WindowUnminimize(AG_Window *win)
1647 {
1648 	if (!win->visible) {
1649 		AG_WindowShow(win);
1650 		win->flags &= ~(AG_WINDOW_MINIMIZED);
1651 	} else {
1652 		AG_WindowFocus(win);
1653 	}
1654 	/* TODO MW: send a WM_CHANGE_STATE */
1655 }
1656 
1657 /* AGWINDETACH(): General-purpose "detach window" event handler. */
1658 void
1659 AG_WindowDetachGenEv(AG_Event *event)
1660 {
1661 	AG_Window *win = AG_PTR(1);
1662 
1663 	AG_ObjectDetach(win);
1664 }
1665 
1666 /* AGWINHIDE(): General-purpose "hide window" event handler. */
1667 void
1668 AG_WindowHideGenEv(AG_Event *event)
1669 {
1670 	AG_WindowHide(AG_PTR(1));
1671 }
1672 
1673 /* AGWINCLOSE(): General-purpose "close window" event handler. */
1674 void
1675 AG_WindowCloseGenEv(AG_Event *event)
1676 {
1677 	AG_PostEvent(NULL, AG_PTR(1), "window-close", NULL);
1678 }
1679 
1680 /* Close the actively focused window. */
1681 void
1682 AG_CloseFocusedWindow(void)
1683 {
1684 	AG_LockVFS(&agDrivers);
1685 	if (agWindowFocused != NULL) {
1686 		AG_PostEvent(NULL, agWindowFocused, "window-close", NULL);
1687 	}
1688 	AG_UnlockVFS(&agDrivers);
1689 }
1690 
1691 static void
1692 SizeRequest(void *obj, AG_SizeReq *r)
1693 {
1694 	AG_Window *win = obj;
1695 	AG_Driver *drv = WIDGET(win)->drv;
1696 	AG_Widget *chld;
1697 	AG_SizeReq rChld, rTbar;
1698 	int nWidgets;
1699 	int wTot;
1700 
1701 	wTot = win->lPad + win->rPad + win->wBorderSide*2;
1702 	r->w = wTot;
1703 	r->h = win->bPad+win->tPad + win->wBorderBot;
1704 
1705 	if (win->tbar != NULL) {
1706 		AG_WidgetSizeReq(win->tbar, &rTbar);
1707 		r->w = MAX(r->w, rTbar.w);
1708 		r->h += rTbar.h;
1709 	}
1710 	nWidgets = 0;
1711 	OBJECT_FOREACH_CHILD(chld, win, ag_widget) {
1712 		if (chld == WIDGET(win->tbar)) {
1713 			continue;
1714 		}
1715 		AG_WidgetSizeReq(chld, &rChld);
1716 		r->w = MAX(r->w, rChld.w + wTot);
1717 		r->h += rChld.h + win->spacing;
1718 		nWidgets++;
1719 	}
1720 	if (nWidgets > 0 && r->h >= win->spacing)
1721 		r->h -= win->spacing;
1722 
1723 	win->wReq = r->w;
1724 	win->hReq = r->h;
1725 
1726 	if (AGDRIVER_SINGLE(drv))
1727 		AG_WM_LimitWindowToView(win);
1728 }
1729 
1730 static int
1731 SizeAllocate(void *obj, const AG_SizeAlloc *a)
1732 {
1733 	AG_Window *win = obj;
1734 	AG_Driver *drv = WIDGET(win)->drv;
1735 	AG_Widget *chld;
1736 	AG_SizeReq rChld;
1737 	AG_SizeAlloc aChld;
1738 	int wAvail, hAvail;
1739 	int totFixed;
1740 	int nWidgets;
1741 	AG_Rect r;
1742 
1743 	if (win->wBorderSide > 0) {
1744 		r.x = 0;
1745 		r.y = 0;
1746 		r.w = win->wBorderSide;
1747 		r.h = a->h - win->wBorderBot;
1748 		AG_SetStockCursor(win, &win->caResize[0], r, AG_HRESIZE_CURSOR);
1749 		r.x = a->w - win->wBorderSide;
1750 		AG_SetStockCursor(win, &win->caResize[4], r, AG_HRESIZE_CURSOR);
1751 	}
1752 	if (win->wBorderBot > 0) {
1753 		r.x = 0;
1754 		r.y = a->h - win->wBorderBot;
1755 		r.w = win->wResizeCtrl;
1756 		r.h = win->wBorderBot;
1757 		AG_SetStockCursor(win, &win->caResize[1], r, AG_LRDIAG_CURSOR);
1758 		r.x = win->wResizeCtrl;
1759 		r.w = a->w - win->wResizeCtrl*2;
1760 		AG_SetStockCursor(win, &win->caResize[2], r, AG_VRESIZE_CURSOR);
1761 		r.x = a->w - win->wResizeCtrl;
1762 		r.w = win->wResizeCtrl;
1763 		AG_SetStockCursor(win, &win->caResize[3], r, AG_LLDIAG_CURSOR);
1764 	}
1765 
1766 	/* Calculate total space available for widgets. */
1767 	wAvail = a->w - win->lPad - win->rPad - win->wBorderSide*2;
1768 	hAvail = a->h - win->bPad - win->tPad - win->wBorderBot;
1769 
1770 	/* Calculate the space occupied by non-fill widgets. */
1771 	nWidgets = 0;
1772 	totFixed = 0;
1773 	OBJECT_FOREACH_CHILD(chld, win, ag_widget) {
1774 		AG_WidgetSizeReq(chld, &rChld);
1775 		if ((chld->flags & AG_WIDGET_VFILL) == 0) {
1776 			totFixed += rChld.h;
1777 		}
1778 		if (chld != WIDGET(win->tbar)) {
1779 			totFixed += win->spacing;
1780 		}
1781 		nWidgets++;
1782 	}
1783 	if (nWidgets > 0 && totFixed >= win->spacing)
1784 		totFixed -= win->spacing;
1785 
1786 	/* Position the widgets. */
1787 	if (win->tbar != NULL) {
1788 		AG_WidgetSizeReq(win->tbar, &rChld);
1789 		aChld.x = 0;
1790 		aChld.y = 0;
1791 		aChld.w = a->w;
1792 		aChld.h = rChld.h;
1793 		AG_WidgetSizeAlloc(win->tbar, &aChld);
1794 		aChld.x = win->lPad + win->wBorderSide;
1795 		aChld.y = rChld.h + win->tPad;
1796 	} else {
1797 		aChld.x = win->lPad + win->wBorderSide;
1798 		aChld.y = win->tPad;
1799 	}
1800 	OBJECT_FOREACH_CHILD(chld, win, ag_widget) {
1801 		AG_WidgetSizeReq(chld, &rChld);
1802 		if (chld == WIDGET(win->tbar)) {
1803 			continue;
1804 		}
1805 		if (chld->flags & AG_WIDGET_NOSPACING) {
1806 			AG_SizeAlloc aTmp;
1807 			AG_Widget *chldNext;
1808 
1809 			aTmp.x = 0;
1810 			aTmp.y = aChld.y;
1811 			aTmp.w = a->w;
1812 			aTmp.h = rChld.h;
1813 			AG_WidgetSizeAlloc(chld, &aTmp);
1814 			aChld.y += aTmp.h;
1815 
1816 			chldNext = OBJECT_NEXT_CHILD(chld, ag_widget);
1817 			if (chldNext == NULL ||
1818 			    !(chldNext->flags & AG_WIDGET_NOSPACING)) {
1819 				aChld.y += win->spacing;
1820 			}
1821 			continue;
1822 		} else {
1823 			aChld.w = (chld->flags & AG_WIDGET_HFILL) ?
1824 			          wAvail : MIN(wAvail,rChld.w);
1825 		}
1826 		aChld.h = (chld->flags & AG_WIDGET_VFILL) ?
1827 		          hAvail-totFixed : rChld.h;
1828 		AG_WidgetSizeAlloc(chld, &aChld);
1829 		aChld.y += aChld.h + win->spacing;
1830 	}
1831 	if (AGDRIVER_SINGLE(drv))
1832 		AG_WM_LimitWindowToView(win);
1833 
1834 	win->r.x = 0;
1835 	win->r.y = 0;
1836 	win->r.w = a->w;
1837 	win->r.h = a->h;
1838 
1839 	return (0);
1840 }
1841 
1842 /* Set the width of the window side borders. */
1843 void
1844 AG_WindowSetSideBorders(AG_Window *win, int pixels)
1845 {
1846 	if (win != NULL) {
1847 		AG_ObjectLock(win);
1848 		win->wBorderSide = pixels;
1849 		win->dirty = 1;
1850 		AG_ObjectUnlock(win);
1851 	} else {
1852 		agWindowSideBorderDefault = pixels;
1853 	}
1854 }
1855 
1856 /* Set the width of the window bottom border. */
1857 void
1858 AG_WindowSetBottomBorder(AG_Window *win, int pixels)
1859 {
1860 	if (win != NULL) {
1861 		AG_ObjectLock(win);
1862 		win->wBorderBot = pixels;
1863 		win->dirty = 1;
1864 		AG_ObjectUnlock(win);
1865 	} else {
1866 		agWindowBotBorderDefault = pixels;
1867 	}
1868 }
1869 
1870 /* Change the spacing between child widgets. */
1871 void
1872 AG_WindowSetSpacing(AG_Window *win, int spacing)
1873 {
1874 	AG_ObjectLock(win);
1875 	win->spacing = spacing;
1876 	win->dirty = 1;
1877 	AG_ObjectUnlock(win);
1878 }
1879 
1880 /* Change the padding around child widgets. */
1881 void
1882 AG_WindowSetPadding(AG_Window *win, int lPad, int rPad, int tPad, int bPad)
1883 {
1884 	AG_ObjectLock(win);
1885 	if (lPad != -1) { win->lPad = lPad; }
1886 	if (rPad != -1) { win->rPad = rPad; }
1887 	if (tPad != -1) { win->tPad = tPad; }
1888 	if (bPad != -1) { win->bPad = bPad; }
1889 	win->dirty = 1;
1890 	AG_ObjectUnlock(win);
1891 }
1892 
1893 /* Request a specific initial window position. */
1894 void
1895 AG_WindowSetPosition(AG_Window *win, enum ag_window_alignment alignment,
1896     int tiling)
1897 {
1898 	AG_ObjectLock(win);
1899 	win->alignment = alignment;
1900 	AG_SETFLAGS(win->flags, AG_WINDOW_TILING, tiling);
1901 	AG_ObjectUnlock(win);
1902 }
1903 
1904 /* Set a default window-close handler. */
1905 void
1906 AG_WindowSetCloseAction(AG_Window *win, enum ag_window_close_action mode)
1907 {
1908 	AG_ObjectLock(win);
1909 
1910 	switch (mode) {
1911 	case AG_WINDOW_HIDE:
1912 		AG_SetEvent(win, "window-close", AGWINHIDE(win));
1913 		break;
1914 	case AG_WINDOW_DETACH:
1915 		AG_SetEvent(win, "window-close", AGWINDETACH(win));
1916 		break;
1917 	default:
1918 		AG_UnsetEvent(win, "window-close");
1919 		break;
1920 	}
1921 
1922 	AG_ObjectUnlock(win);
1923 }
1924 
1925 /* Update Agar's built-in titlebar from window caption. */
1926 static void
1927 UpdateTitlebar(AG_Window *win)
1928 {
1929 	AG_Titlebar *tbar = win->tbar;
1930 
1931 	AG_ObjectLock(tbar);
1932 	AG_LabelTextS(tbar->label, (win->caption != NULL) ? win->caption : "");
1933 	AG_ObjectUnlock(tbar);
1934 }
1935 
1936 /* Update the window's minimized icon caption. */
1937 static void
1938 UpdateIconCaption(AG_Window *win)
1939 {
1940 	AG_Icon *icon = win->icon;
1941 	char s[20], *c;
1942 
1943 	if (Strlcpy(s, win->caption, sizeof(s)) >= sizeof(s)) {	/* Truncate */
1944 		for (c = &s[0]; *c != '\0'; c++) {
1945 			if (*c == ' ')
1946 				*c = '\n';
1947 		}
1948 		AG_IconSetText(icon, "%s...", s);
1949 	} else {
1950 		AG_IconSetTextS(icon, s);
1951 	}
1952 }
1953 
1954 /* Set the text to show inside a window's titlebar (C string). */
1955 void
1956 AG_WindowSetCaptionS(AG_Window *win, const char *s)
1957 {
1958 	AG_Driver *drv = WIDGET(win)->drv;
1959 
1960 	AG_ObjectLock(win);
1961 	Strlcpy(win->caption, s, sizeof(win->caption));
1962 
1963 	if (win->tbar != NULL)
1964 		UpdateTitlebar(win);
1965 	if (win->icon != NULL)
1966 		UpdateIconCaption(win);
1967 
1968 	if (AGDRIVER_MULTIPLE(drv) &&
1969 	    (AGDRIVER_MW(drv)->flags & AG_DRIVER_MW_OPEN) &&
1970 	    (AGDRIVER_MW_CLASS(drv)->setWindowCaption != NULL)) {
1971 		AGDRIVER_MW_CLASS(drv)->setWindowCaption(win, s);
1972 	}
1973 	win->dirty = 1;
1974 	AG_ObjectUnlock(win);
1975 }
1976 
1977 /* Set the text to show inside a window's titlebar (format string). */
1978 void
1979 AG_WindowSetCaption(AG_Window *win, const char *fmt, ...)
1980 {
1981 	char s[AG_LABEL_MAX];
1982 	va_list ap;
1983 
1984 	va_start(ap, fmt);
1985 	Vsnprintf(s, sizeof(s), fmt, ap);
1986 	va_end(ap);
1987 
1988 	AG_WindowSetCaptionS(win, s);
1989 }
1990 
1991 /* Commit any pending focus change. The agDrivers VFS must be locked. */
1992 void
1993 AG_WindowProcessFocusChange(void)
1994 {
1995 	AG_Driver *drv;
1996 
1997 	switch (agDriverOps->wm) {
1998 	case AG_WM_SINGLE:
1999 		AG_WM_CommitWindowFocus(agWindowToFocus);
2000 		break;
2001 	case AG_WM_MULTIPLE:
2002 		if ((drv = AGWIDGET(agWindowToFocus)->drv) != NULL) {
2003 			AGDRIVER_MW_CLASS(drv)->raiseWindow(agWindowToFocus);
2004 			AGDRIVER_MW_CLASS(drv)->setInputFocus(agWindowToFocus);
2005 		}
2006 		break;
2007 	}
2008 	agWindowToFocus = NULL;
2009 }
2010 
2011 /*
2012  * Make windows on the show queue visible.
2013  * The agDrivers VFS must be locked.
2014  */
2015 void
2016 AG_WindowProcessShowQueue(void)
2017 {
2018 	AG_Window *win;
2019 
2020 	TAILQ_FOREACH(win, &agWindowShowQ, visibility) {
2021 		AG_PostEvent(NULL, win, "widget-shown", NULL);
2022 	}
2023 	TAILQ_INIT(&agWindowShowQ);
2024 }
2025 
2026 /*
2027  * Make windows on the hide queue invisible.
2028  * The agDrivers VFS must be locked.
2029  */
2030 void
2031 AG_WindowProcessHideQueue(void)
2032 {
2033 	AG_Window *win;
2034 
2035 	TAILQ_FOREACH(win, &agWindowHideQ, visibility) {
2036 		AG_PostEvent(NULL, win, "widget-hidden", NULL);
2037 	}
2038 	TAILQ_INIT(&agWindowHideQ);
2039 }
2040 
2041 /*
2042  * Close and destroy windows on the detach queue.
2043  * The agDrivers VFS must be locked.
2044  */
2045 void
2046 AG_WindowProcessDetachQueue(void)
2047 {
2048 	AG_Window *win, *winNext;
2049 	AG_Driver *drv;
2050 	int closedMain = 0, nHidden = 0;
2051 
2052 #ifdef AG_DEBUG_GUI
2053 	Debug(NULL, "AG_WindowProcessDetachQueue() Begin\n");
2054 #endif
2055 	TAILQ_FOREACH(win, &agWindowDetachQ, detach) {
2056 		if (!win->visible) {
2057 			continue;
2058 		}
2059 #ifdef AG_DEBUG_GUI
2060 		Debug(NULL, "Hiding: %s (\"%s\")\n", OBJECT(win)->name, win->caption);
2061 #endif
2062 		/*
2063 		 * Note: `widget-hidden' event handlers may cause new windows
2064 		 * to be added to agWindowDetachQ.
2065 		 */
2066 		AG_PostEvent(NULL, win, "widget-hidden", NULL);
2067 		nHidden++;
2068 	}
2069 	if (nHidden > 0) {
2070 		/*
2071 		 * Windows were hidden - defer detach operation until the next
2072 		 * event cycle, just in case the underlying WM cannot hide and
2073 		 * unmap a window in the same event cycle.
2074 		 */
2075 #ifdef AG_DEBUG_GUI
2076 		Debug(NULL, "Defer Detach (%d windows hidden)\n", nHidden);
2077 #endif
2078 		return;
2079 	}
2080 
2081 	for (win = TAILQ_FIRST(&agWindowDetachQ);
2082 	     win != TAILQ_END(&agWindowDetachQ);
2083 	     win = winNext) {
2084 		winNext = TAILQ_NEXT(win, detach);
2085 		drv = WIDGET(win)->drv;
2086 
2087 #ifdef AG_DEBUG_GUI
2088 		Debug(NULL, "Detach: %s (\"%s\")\n", OBJECT(win)->name, win->caption);
2089 #endif
2090 		/* Notify all widgets of the window detach. */
2091 		AG_PostEvent(drv, win, "detached", NULL);
2092 
2093 		/* Release the cursor areas and associated cursors. */
2094 		AG_UnmapAllCursors(win, NULL);
2095 
2096 		if (AGDRIVER_MULTIPLE(drv)) {
2097 			if (AGDRIVER_MW(drv)->flags & AG_DRIVER_MW_OPEN) {
2098 				AGDRIVER_MW_CLASS(drv)->closeWindow(win);
2099 				AGDRIVER_MW(drv)->flags &= ~(AG_DRIVER_MW_OPEN);
2100 			}
2101 		} else {
2102 			win->tbar = NULL;
2103 			if (win->icon != NULL) {
2104 				AG_ObjectDestroy(win->icon);
2105 				win->icon = NULL;
2106 			}
2107 			AGDRIVER_SW(drv)->flags |= AG_DRIVER_SW_REDRAW;
2108 		}
2109 
2110 		/* Do a standard AG_ObjectDetach(). */
2111 		AG_ObjectSetDetachFn(win, NULL, NULL);
2112 		AG_ObjectDetach(win);
2113 
2114 		if (AGDRIVER_MULTIPLE(drv)) {
2115 			/* Destroy the AG_Driver object. */
2116 			AG_UnlockVFS(&agDrivers);
2117 			AG_DriverClose(drv);
2118 			AG_LockVFS(&agDrivers);
2119 		}
2120 		if (win->flags & AG_WINDOW_MAIN) {
2121 			closedMain++;
2122 		}
2123 		AG_PostEvent(NULL, win, "window-detached", NULL);
2124 		AG_ObjectDestroy(win);
2125 	}
2126 	TAILQ_INIT(&agWindowDetachQ);
2127 
2128 	/* Terminate if the last AG_WINDOW_MAIN window was closed. */
2129 	if (closedMain > 0) {
2130 		AGOBJECT_FOREACH_CHILD(drv, &agDrivers, ag_driver) {
2131 			AG_FOREACH_WINDOW(win, drv) {
2132 				if (win->flags & AG_WINDOW_MAIN)
2133 					break;
2134 			}
2135 			if (win != NULL)
2136 				break;
2137 		}
2138 		if (drv == NULL) {
2139 #ifdef AG_DEBUG_GUI
2140 			Debug(NULL, "AG_WindowProcessDetachQueue() Exit Normally\n");
2141 #endif
2142 			AG_Terminate(0);
2143 		}
2144 #ifdef AG_DEBUG_GUI
2145 		Debug(NULL, "AG_WindowProcessDetachQueue() End (MAIN window remains)\n");
2146 #endif
2147 	} else {
2148 #ifdef AG_DEBUG_GUI
2149 		Debug(NULL, "AG_WindowProcessDetachQueue() End\n");
2150 #endif
2151 	}
2152 }
2153 
2154 /*
2155  * Configure a new cursor-change area with a specified cursor. The provided
2156  * cursor will be freed automatically on window detach.
2157  */
2158 AG_CursorArea *
2159 AG_MapCursor(void *obj, AG_Rect r, AG_Cursor *c)
2160 {
2161 	AG_Widget *wid = obj;
2162 	AG_Window *win = wid->window;
2163 	AG_CursorArea *ca;
2164 
2165 	if ((ca = TryMalloc(sizeof(AG_CursorArea))) == NULL) {
2166 		return (NULL);
2167 	}
2168 	ca->stock = -1;
2169 	ca->r = r;
2170 	ca->c = c;
2171 	ca->wid = wid;
2172 
2173 	if (win != NULL) {
2174 		TAILQ_INSERT_TAIL(&win->cursorAreas, ca, cursorAreas);
2175 	} else {
2176 		TAILQ_INSERT_TAIL(&wid->cursorAreas, ca, cursorAreas);
2177 	}
2178 	return (ca);
2179 }
2180 
2181 /* Configure a new cursor-change area with a stock Agar cursor. */
2182 AG_CursorArea *
2183 AG_MapStockCursor(void *obj, AG_Rect r, int name)
2184 {
2185 	AG_Widget *wid = obj;
2186 	AG_Window *win = wid->window;
2187 	AG_Cursor *ac;
2188 	AG_CursorArea *ca;
2189 	int i = 0;
2190 
2191 	TAILQ_FOREACH(ac, &wid->drv->cursors, cursors) {
2192 		if (i++ == name)
2193 			break;
2194 	}
2195 	if (ac == NULL) {
2196 		AG_SetError("No such cursor");
2197 		return (NULL);
2198 	}
2199 
2200 	if ((ca = TryMalloc(sizeof(AG_CursorArea))) == NULL) {
2201 		return (NULL);
2202 	}
2203 	ca->stock = name;
2204 	ca->r = r;
2205 	ca->wid = WIDGET(obj);
2206 
2207 	if (win != NULL) {
2208 		ca->c = ac;
2209 		TAILQ_INSERT_TAIL(&win->cursorAreas, ca, cursorAreas);
2210 	} else {
2211 		/* Will resolve cursor name when widget is later attached. */
2212 		ca->c = NULL;
2213 		TAILQ_INSERT_TAIL(&wid->cursorAreas, ca, cursorAreas);
2214 	}
2215 	return (ca);
2216 }
2217 
2218 /*
2219  * Remove a cursor-change area and release the associated cursor (unless it
2220  * is a stock Agar cursor).
2221  */
2222 void
2223 AG_UnmapCursor(void *obj, AG_CursorArea *ca)
2224 {
2225 	AG_Widget *wid = obj;
2226 	AG_Window *win = wid->window;
2227 
2228 	if (win == NULL) {
2229 		TAILQ_REMOVE(&wid->cursorAreas, ca, cursorAreas);
2230 		free(ca);
2231 	} else {
2232 		AG_Driver *drv = WIDGET(win)->drv;
2233 
2234 		if (ca->c == drv->activeCursor) {
2235 			if (ca->stock == -1) {
2236 				/* XXX TODO it would be safer to defer this operation */
2237 				if (AGDRIVER_CLASS(drv)->unsetCursor != NULL) {
2238 					AGDRIVER_CLASS(drv)->unsetCursor(drv);
2239 				}
2240 				AG_CursorFree(drv, ca->c);
2241 			}
2242 			TAILQ_REMOVE(&win->cursorAreas, ca, cursorAreas);
2243 			free(ca);
2244 		}
2245 	}
2246 }
2247 
2248 /* Destroy all cursors (or all cursors associated with a given widget). */
2249 void
2250 AG_UnmapAllCursors(AG_Window *win, void *wid)
2251 {
2252 	AG_Driver *drv = WIDGET(win)->drv;
2253 	AG_CursorArea *ca, *caNext;
2254 
2255 	if (wid == NULL) {
2256 		for (ca = TAILQ_FIRST(&win->cursorAreas);
2257 		     ca != TAILQ_END(&win->cursorAreas);
2258 		     ca = caNext) {
2259 			caNext = TAILQ_NEXT(ca, cursorAreas);
2260 			if (ca->stock == -1) {
2261 				AG_CursorFree(drv, ca->c);
2262 			}
2263 			free(ca);
2264 		}
2265 		TAILQ_INIT(&win->cursorAreas);
2266 	} else {
2267 scan:
2268 		TAILQ_FOREACH(ca, &win->cursorAreas, cursorAreas) {
2269 			if (ca->wid != wid) {
2270 				continue;
2271 			}
2272 			if (ca->stock == -1) {
2273 				AG_CursorFree(drv, ca->c);
2274 			}
2275 			TAILQ_REMOVE(&win->cursorAreas, ca, cursorAreas);
2276 			free(ca);
2277 			goto scan;
2278 		}
2279 	}
2280 }
2281 
2282 /* Configure per-window opacity (for compositing window managers). */
2283 int
2284 AG_WindowSetOpacity(AG_Window *win, float f)
2285 {
2286 	AG_Driver *drv = OBJECT(win)->parent;
2287 	int rv = -1;
2288 
2289 	AG_ObjectLock(win);
2290 	win->fadeOpacity = (f > 1.0) ? 1.0 : f;
2291 
2292 	if (AGDRIVER_MULTIPLE(drv) &&
2293 	    AGDRIVER_MW_CLASS(drv)->setOpacity != NULL)
2294 		rv = AGDRIVER_MW_CLASS(drv)->setOpacity(win, win->fadeOpacity);
2295 
2296 	/* TODO: support compositing under single-window drivers. */
2297 	AG_ObjectUnlock(win);
2298 	return (rv);
2299 }
2300 
2301 /* Configure fade-in/fade-out parameters. */
2302 void
2303 AG_WindowSetFadeIn(AG_Window *win, float fadeTime, float fadeIncr)
2304 {
2305 	AG_ObjectLock(win);
2306 	win->fadeInTime = fadeTime;
2307 	win->fadeInIncr = fadeIncr;
2308 	AG_ObjectUnlock(win);
2309 }
2310 void
2311 AG_WindowSetFadeOut(AG_Window *win, float fadeTime, float fadeIncr)
2312 {
2313 	AG_ObjectLock(win);
2314 	win->fadeOutTime = fadeTime;
2315 	win->fadeOutIncr = fadeIncr;
2316 	AG_ObjectUnlock(win);
2317 }
2318 
2319 /* Set the window zoom level. The scales are defined in agZoomValues[]. */
2320 void
2321 AG_WindowSetZoom(AG_Window *win, int zoom)
2322 {
2323 	AG_Window *winChld;
2324 
2325 	AG_ObjectLock(win);
2326 	if (zoom < 0 || zoom >= AG_ZOOM_RANGE || zoom == win->zoom) {
2327 		AG_ObjectUnlock(win);
2328 		return;
2329 	}
2330 	AG_SetStyle(win, "font-size", AG_Printf("%.02f%%", agZoomValues[zoom]));
2331 	AG_WindowUpdate(win);
2332 	win->zoom = zoom;
2333 	if (WIDGET(win)->drv != NULL) {
2334 		AG_TextClearGlyphCache(WIDGET(win)->drv);
2335 	}
2336 	TAILQ_FOREACH(winChld, &win->subwins, swins) {
2337 		AG_WindowSetZoom(winChld, zoom);
2338 	}
2339 	AG_ObjectUnlock(win);
2340 }
2341 
2342 #ifdef AG_LEGACY
2343 /* Pre-1.4 */
2344 AG_Window *
2345 AG_FindWindow(const char *name)
2346 {
2347 	AG_Driver *drv;
2348 	AG_Window *win;
2349 
2350 	AGOBJECT_FOREACH_CHILD(drv, &agDrivers, ag_driver) {
2351 		if ((win = AG_ObjectFindS(drv, name)) != NULL)
2352 			return (win);
2353 	}
2354 	return (NULL);
2355 }
2356 void
2357 AG_ViewAttach(struct ag_window *pWin)
2358 {
2359 	AG_ObjectAttach(agDriverSw, pWin);
2360 }
2361 void
2362 AG_ViewDetach(AG_Window *win)
2363 {
2364 	AG_ObjectDetach(win);
2365 }
2366 void
2367 AG_WindowSetVisibility(AG_Window *win, int flag)
2368 {
2369 	AG_ObjectLock(win);
2370 	if (win->visible) {
2371 		AG_WindowHide(win);
2372 	} else {
2373 		AG_WindowShow(win);
2374 	}
2375 	AG_ObjectUnlock(win);
2376 }
2377 int
2378 AG_WindowIntersect(AG_DriverSw *drv, int x, int y)
2379 {
2380 	AG_Window *win;
2381 	int rv = 0;
2382 
2383 	AG_FOREACH_WINDOW(win, drv) {
2384 		if (win->visible &&
2385 		    AG_WidgetArea(win, x, y))
2386 			rv++;
2387 	}
2388 	return (rv);
2389 }
2390 #endif /* AG_LEGACY */
2391 
2392 AG_WidgetClass agWindowClass = {
2393 	{
2394 		"Agar(Widget:Window)",
2395 		sizeof(AG_Window),
2396 		{ 0,0 },
2397 		Init,
2398 		NULL,			/* free */
2399 		NULL,			/* destroy */
2400 		NULL,			/* load */
2401 		NULL,			/* save */
2402 		NULL			/* edit */
2403 	},
2404 	Draw,
2405 	SizeRequest,
2406 	SizeAllocate
2407 };
2408