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