/*
* Copyright (c) 2001-2008 Hypertriton, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include
#include
#include "window.h"
#include "titlebar.h"
#include "icon.h"
#include "primitive.h"
#include "icons.h"
#include "cursors.h"
#include
#include
static void ProcessWM_Resize(int, AG_Window *, int, int);
static void ProcessWM_Move(AG_Window *, int, int);
static void ClampToView(AG_Window *);
static void Shown(AG_Event *);
static void Hidden(AG_Event *);
static void GainFocus(AG_Event *);
static void LostFocus(AG_Event *);
AG_Mutex agWindowLock;
int agWindowCurX[AG_WINDOW_ALIGNMENT_LAST];
int agWindowCurY[AG_WINDOW_ALIGNMENT_LAST];
int agWindowXOutLimit = 32;
int agWindowBotOutLimit = 32;
int agWindowIconWidth = 32;
int agWindowIconHeight = 32;
int agWindowSideBorderDefault = 0;
int agWindowBotBorderDefault = 6;
extern SDL_Cursor *agCursorToSet; /* widget.c */
void
AG_InitWindowSystem(void)
{
int i;
AG_MutexInit(&agWindowLock);
for (i = 0; i < AG_WINDOW_ALIGNMENT_LAST; i++) {
agWindowCurX[i] = 0;
agWindowCurY[i] = 0;
}
}
void
AG_DestroyWindowSystem(void)
{
AG_MutexDestroy(&agWindowLock);
}
/* Create a generic window. */
AG_Window *
AG_WindowNew(Uint flags)
{
AG_Window *win;
Uint titlebarFlags = 0;
win = Malloc(sizeof(AG_Window));
AG_ObjectInit(win, &agWindowClass);
AG_ObjectSetName(win, "generic");
OBJECT(win)->flags &= ~(AG_OBJECT_NAME_ONATTACH);
win->flags |= flags;
if ((win->flags & AG_WINDOW_NOTITLE) == 0)
win->hMin += agTextFontHeight;
if (win->flags & AG_WINDOW_MODAL)
win->flags |= AG_WINDOW_NOMAXIMIZE|AG_WINDOW_NOMINIMIZE;
if (win->flags & AG_WINDOW_NORESIZE)
win->flags |= AG_WINDOW_NOMAXIMIZE;
if (win->flags & AG_WINDOW_NOCLOSE)
titlebarFlags |= AG_TITLEBAR_NO_CLOSE;
if (win->flags & AG_WINDOW_NOMINIMIZE)
titlebarFlags |= AG_TITLEBAR_NO_MINIMIZE;
if (win->flags & AG_WINDOW_NOMAXIMIZE)
titlebarFlags |= AG_TITLEBAR_NO_MAXIMIZE;
if ((win->flags & AG_WINDOW_NOTITLE) == 0)
win->tbar = AG_TitlebarNew(win, titlebarFlags);
if (win->flags & AG_WINDOW_NOBORDERS) {
win->wBorderSide = 0;
win->wBorderBot = 0;
}
AG_SetEvent(win, "window-close", AGWINDETACH(win));
AG_ViewAttach(win);
return (win);
}
/* Create a named window */
AG_Window *
AG_WindowNewNamed(Uint flags, const char *fmt, ...)
{
char name[AG_OBJECT_NAME_MAX], *c;
AG_Window *win;
va_list ap;
AG_LockVFS(agView);
va_start(ap, fmt);
Vsnprintf(name, sizeof(name), fmt, ap);
va_end(ap);
for (c = &name[0]; *c != '\0'; c++) {
if (*c == '/')
*c = '_';
}
if (AG_WindowFocusNamed(name)) {
win = NULL;
goto out;
}
win = AG_WindowNew(flags);
AG_ObjectSetName(win, "%s", name);
AG_SetEvent(win, "window-close", AGWINHIDE(win));
out:
AG_UnlockVFS(agView);
return (win);
}
static void
ChildAttached(AG_Event *event)
{
AG_Window *win = AG_SELF();
AG_Widget *wid = AG_PTR(1);
if (win->visible) {
AG_WindowUpdate(win);
AG_PostEvent(NULL, wid, "widget-shown", NULL);
}
}
static void
Init(void *obj)
{
AG_Window *win = obj;
AG_Event *ev;
win->flags = 0;
win->visible = 0;
win->alignment = AG_WINDOW_CENTER;
win->spacing = 3;
win->lPad = 2;
win->rPad = 2;
win->tPad = 2;
win->bPad = 2;
win->wReq = 0;
win->hReq = 0;
win->wMin = win->lPad + win->rPad + 16;
win->hMin = win->tPad + win->bPad + 16;
win->minPct = 50;
win->wBorderBot = agWindowBotBorderDefault;
win->wBorderSide = agWindowSideBorderDefault;
win->wResizeCtrl = 16;
win->rSaved = AG_RECT(-1,-1,-1,-1);
win->r = AG_RECT(0,0,0,0);
win->caption[0] = '\0';
win->tbar = NULL;
win->icon = AG_IconNew(NULL, 0);
win->nFocused = 0;
TAILQ_INIT(&win->subwins);
AG_IconSetSurfaceNODUP(win->icon, agIconWindow.s);
AG_IconSetBackgroundFill(win->icon, 1, AG_COLOR(BG_COLOR));
AG_SetEvent(win, "window-gainfocus", GainFocus, NULL);
AG_SetEvent(win, "window-lostfocus", LostFocus, NULL);
ev = AG_SetEvent(win, "widget-shown", Shown, NULL);
ev->flags |= AG_EVENT_PROPAGATE;
ev = AG_SetEvent(win, "widget-hidden", Hidden, NULL);
ev->flags |= AG_EVENT_PROPAGATE;
ev = AG_SetEvent(win, "detached", NULL, NULL);
ev->flags |= AG_EVENT_PROPAGATE;
AG_SetEvent(win, "child-attached", ChildAttached, NULL);
}
/* Attach a sub-window. */
void
AG_WindowAttach(AG_Window *win, AG_Window *subwin)
{
if (win == NULL) {
return;
}
AG_LockVFS(agView);
AG_ObjectLock(win);
TAILQ_INSERT_HEAD(&win->subwins, subwin, swins);
AG_ObjectUnlock(win);
AG_UnlockVFS(agView);
}
/* Detach a sub-window. */
void
AG_WindowDetach(AG_Window *win, AG_Window *subwin)
{
if (win == NULL) {
return;
}
AG_LockVFS(agView);
AG_ObjectLock(win);
TAILQ_REMOVE(&win->subwins, subwin, swins);
AG_ObjectUnlock(win);
AG_UnlockVFS(agView);
}
static void
Draw(void *obj)
{
AG_Window *win = obj;
AG_Widget *chld;
STYLE(win)->Window(win);
if (!(win->flags & AG_WINDOW_NOCLIPPING)) {
AG_PushClipRect(win, win->r);
}
WIDGET_FOREACH_CHILD(chld, win) {
AG_WidgetDraw(chld);
}
if (!(win->flags & AG_WINDOW_NOCLIPPING))
AG_PopClipRect();
}
/* Apply initial alignment parameter. */
static void
ApplyAlignment(AG_Window *win)
{
int xOffs = 0, yOffs = 0;
AG_MutexLock(&agWindowLock);
if (win->flags & AG_WINDOW_CASCADE) {
xOffs = agWindowCurX[win->alignment];
yOffs = agWindowCurY[win->alignment];
agWindowCurX[win->alignment] += 16;
agWindowCurY[win->alignment] += 16;
if (agWindowCurX[win->alignment] > agView->w)
agWindowCurX[win->alignment] = 0;
if (agWindowCurY[win->alignment] > agView->h)
agWindowCurY[win->alignment] = 0;
}
switch (win->alignment) {
case AG_WINDOW_TL:
WIDGET(win)->x = xOffs;
WIDGET(win)->y = yOffs;
break;
case AG_WINDOW_TC:
WIDGET(win)->x = agView->w/2 - WIDTH(win)/2 + xOffs;
WIDGET(win)->y = 0;
break;
case AG_WINDOW_TR:
WIDGET(win)->x = agView->w - WIDTH(win) - xOffs;
WIDGET(win)->y = yOffs;
break;
case AG_WINDOW_ML:
WIDGET(win)->x = xOffs;
WIDGET(win)->y = agView->h/2 - HEIGHT(win)/2 + yOffs;
break;
case AG_WINDOW_MC:
WIDGET(win)->x = agView->w/2 - WIDTH(win)/2 + xOffs;
WIDGET(win)->y = agView->h/2 - HEIGHT(win)/2 + yOffs;
break;
case AG_WINDOW_MR:
WIDGET(win)->x = agView->w - WIDTH(win) - xOffs;
WIDGET(win)->y = agView->h/2 - HEIGHT(win)/2 + yOffs;
break;
case AG_WINDOW_BL:
WIDGET(win)->x = xOffs;
WIDGET(win)->y = agView->h - HEIGHT(win) - yOffs;
break;
case AG_WINDOW_BC:
WIDGET(win)->x = agView->w/2 - WIDTH(win)/2 + xOffs;
WIDGET(win)->y = agView->h - HEIGHT(win);
break;
case AG_WINDOW_BR:
WIDGET(win)->x = agView->w - WIDTH(win) - xOffs;
WIDGET(win)->y = agView->h - HEIGHT(win) - yOffs;
break;
default:
break;
}
ClampToView(win);
AG_MutexUnlock(&agWindowLock);
}
static void
Shown(AG_Event *event)
{
AG_Window *win = AG_SELF();
AG_SizeReq r;
AG_SizeAlloc a;
if ((win->flags & AG_WINDOW_DENYFOCUS) == 0) {
AG_WindowFocus(win);
}
if (win->flags & AG_WINDOW_MODAL) {
agView->winModal = Realloc(agView->winModal,
(agView->nModal+1) * sizeof(AG_Window *));
agView->winModal[agView->nModal] = win;
agView->nModal++;
}
if (WIDGET(win)->x == -1 && WIDGET(win)->y == -1) {
AG_WidgetSizeReq(win, &r);
if (r.w > agView->w) { r.w = agView->w; }
if (r.h > agView->h) { r.h = agView->h; }
a.x = 0;
a.y = 0;
a.w = r.w;
a.h = r.h;
AG_WidgetSizeAlloc(win, &a);
ApplyAlignment(win);
} else {
a.x = WIDGET(win)->x;
a.y = WIDGET(win)->y;
a.w = WIDTH(win);
a.h = HEIGHT(win);
AG_WidgetSizeAlloc(win, &a);
}
AG_WidgetUpdateCoords(win, WIDGET(win)->x, WIDGET(win)->y);
AG_PostEvent(NULL, win, "window-shown", NULL);
AG_PostEvent(NULL, win, "window-gainfocus", NULL);
}
static void
Hidden(AG_Event *event)
{
AG_Window *win = AG_SELF();
if ((win->flags & AG_WINDOW_DENYFOCUS) == 0) {
/* Remove the focus. XXX cycle */
agView->winToFocus = NULL;
}
if (win->flags & AG_WINDOW_MODAL) {
agView->nModal--;
}
/* Update the background if necessary. */
/* XXX TODO Avoid drawing over KEEPABOVE windows */
// if (!AG_WindowIsSurrounded(win)) {
if (!agView->opengl) {
AG_DrawRectFilled(win,
AG_RECT(0,0, WIDTH(win), HEIGHT(win)),
AG_COLOR(BG_COLOR));
AG_ViewUpdateFB(&WIDGET(win)->rView);
}
// }
AG_PostEvent(NULL, win, "window-hidden", NULL);
}
static void
WidgetGainFocus(AG_Widget *wid)
{
AG_Widget *chld;
WIDGET_FOREACH_CHILD(chld, wid) {
AG_ObjectLock(chld);
WidgetGainFocus(chld);
AG_ObjectUnlock(chld);
}
if (wid->flags & AG_WIDGET_FOCUSED)
AG_PostEvent(NULL, wid, "widget-gainfocus", NULL);
}
static void
WidgetLostFocus(AG_Widget *wid)
{
AG_Widget *chld;
WIDGET_FOREACH_CHILD(chld, wid) {
AG_ObjectLock(chld);
WidgetLostFocus(chld);
AG_ObjectUnlock(chld);
}
if (wid->flags & AG_WIDGET_FOCUSED)
AG_PostEvent(NULL, wid, "widget-lostfocus", NULL);
}
static void
GainFocus(AG_Event *event)
{
WidgetGainFocus(WIDGET(AG_SELF()));
}
static void
LostFocus(AG_Event *event)
{
WidgetLostFocus(WIDGET(AG_SELF()));
}
/*
* Verify whether a given window resides inside the area of some other
* window, to see whether is it necessary to update the background.
*
* The window's parent View must be locked.
*/
int
AG_WindowIsSurrounded(AG_Window *win)
{
AG_Window *owin;
TAILQ_FOREACH(owin, &agView->windows, windows) {
AG_ObjectLock(owin);
if (owin->visible &&
AG_WidgetArea(owin, WIDGET(win)->x, WIDGET(win)->y) &&
AG_WidgetArea(owin,
WIDGET(win)->x + WIDTH(win),
WIDGET(win)->y + HEIGHT(win))) {
AG_ObjectUnlock(owin);
return (1);
}
AG_ObjectUnlock(owin);
}
return (0);
}
/* Set the visibility state of a window. */
void
AG_WindowSetVisibility(AG_Window *win, int flag)
{
AG_ObjectLock(win);
if (win->visible) {
AG_WindowHide(win);
} else {
AG_WindowShow(win);
}
AG_ObjectUnlock(win);
}
/* Set the visibility bit of a window. */
void
AG_WindowShow(AG_Window *win)
{
AG_ObjectLock(win);
if (!win->visible) {
AG_PostEvent(NULL, win, "widget-shown", NULL);
win->visible++;
}
AG_ObjectUnlock(win);
}
/* Clear the visibility bit of a window. */
void
AG_WindowHide(AG_Window *win)
{
AG_ObjectLock(win);
if (win->visible) {
win->visible = 0;
AG_PostEvent(NULL, win, "widget-hidden", NULL);
}
AG_ObjectUnlock(win);
}
/* Build an ordered list of the focusable widgets in a window. */
static void
ListFocusableWidgets(AG_List *L, AG_Widget *wid)
{
AG_Widget *chld;
AG_ObjectLock(wid);
if (wid->flags & AG_WIDGET_FOCUSABLE) {
AG_ListAppendPointer(L, wid->focusFwd ? wid->focusFwd : wid);
}
AG_ObjectUnlock(wid);
WIDGET_FOREACH_CHILD(chld, wid)
ListFocusableWidgets(L, chld);
}
/*
* Move the widget focus inside a window.
* The window must be locked.
*/
void
AG_WindowCycleFocus(AG_Window *win, int reverse)
{
AG_List *Lfoc, *Luniq;
int i, j;
/* Generate a list of focusable widgets; eliminate duplicates. */
Lfoc = AG_ListNew();
Luniq = AG_ListNew();
ListFocusableWidgets(Lfoc, WIDGET(win));
for (i = 0; i < Lfoc->n; i++) {
for (j = 0; j < Luniq->n; j++) {
if (Lfoc->v[i].data.p == Luniq->v[j].data.p)
break;
}
if (j == Luniq->n)
AG_ListAppendPointer(Luniq, Lfoc->v[i].data.p);
}
if (Luniq->n == 0)
goto out;
/* Move focus after/before the currently focused widget. */
if (reverse) {
for (i = 0; i < Luniq->n; i++) {
if (WIDGET(Luniq->v[i].data.p)->flags & AG_WIDGET_FOCUSED)
break;
}
if (i == -1) {
AG_WidgetFocus(Luniq->v[0].data.p);
} else {
if (i-1 < 0) {
AG_WidgetFocus(Luniq->v[Luniq->n - 1].data.p);
} else {
AG_WidgetFocus(Luniq->v[i - 1].data.p);
}
}
} else {
for (i = Luniq->n-1; i >= 0; i--) {
if (WIDGET(Luniq->v[i].data.p)->flags & AG_WIDGET_FOCUSED)
break;
}
if (i == Luniq->n) {
AG_WidgetFocus(Luniq->v[0].data.p);
} else {
if (i+1 < Luniq->n) {
AG_WidgetFocus(Luniq->v[i + 1].data.p);
} else {
AG_WidgetFocus(Luniq->v[0].data.p);
}
}
}
out:
AG_ListDestroy(Lfoc);
AG_ListDestroy(Luniq);
}
/*
* Clamp the window geometry/coordinates down to the view area.
* The window must be locked.
*/
static void
ClampToView(AG_Window *win)
{
AG_Widget *w = WIDGET(win);
if (w->x < agWindowXOutLimit - w->w) {
w->x = agWindowXOutLimit - w->w;
} else if (w->x > agView->w - agWindowXOutLimit) {
w->x = agView->w - agWindowXOutLimit;
}
if (w->y < 0) {
w->y = 0;
} else if (w->y > agView->h - agWindowBotOutLimit) {
w->y = agView->h - agWindowBotOutLimit;
}
#if 0
if (w->x + w->w > agView->w) { w->x = agView->w - w->w; }
if (w->y + w->h > agView->h) { w->y = agView->h - w->h; }
if (w->x < 0) { w->x = 0; }
if (w->y < 0) { w->y = 0; }
if (w->x+w->w > agView->w) {
w->x = 0;
w->w = agView->w - 1;
}
if (w->y+w->h > agView->h) {
w->y = 0;
w->h = agView->h - 1;
}
#endif
}
/* Process a window move initiated by the WM. */
static void
ProcessWM_Move(AG_Window *win, int xRel, int yRel)
{
AG_Rect rPrev, rNew;
SDL_Rect rFill1, rFill2;
rPrev.x = WIDGET(win)->x;
rPrev.y = WIDGET(win)->y;
rPrev.w = WIDTH(win);
rPrev.h = HEIGHT(win);
WIDGET(win)->x += xRel;
WIDGET(win)->y += yRel;
ClampToView(win);
AG_WidgetUpdateCoords(win, WIDGET(win)->x, WIDGET(win)->y);
/* Update the background. */
/* XXX TODO Avoid drawing over KEEPABOVE windows */
rNew.x = WIDGET(win)->x;
rNew.y = WIDGET(win)->y;
rNew.w = WIDTH(win);
rNew.h = HEIGHT(win);
rFill1.w = 0;
rFill2.w = 0;
if (rNew.x > rPrev.x) { /* Right */
rFill1.x = rPrev.x;
rFill1.y = rPrev.y;
rFill1.w = rNew.x - rPrev.x;
rFill1.h = rNew.h;
} else if (rNew.x < rPrev.x) { /* Left */
rFill1.x = rNew.x + rNew.w;
rFill1.y = rNew.y;
rFill1.w = rPrev.x - rNew.x;
rFill1.h = rPrev.h;
}
if (rNew.y > rPrev.y) { /* Downward */
rFill2.x = rPrev.x;
rFill2.y = rPrev.y;
rFill2.w = rNew.w;
rFill2.h = rNew.y - rPrev.y;
} else if (rNew.y < rPrev.y) { /* Upward */
rFill2.x = rPrev.x;
rFill2.y = rNew.y + rNew.h;
rFill2.w = rPrev.w;
rFill2.h = rPrev.y - rNew.y;
}
if (!agView->opengl) {
if (rFill1.w > 0) {
SDL_FillRect(agView->v, &rFill1, AG_COLOR(BG_COLOR));
SDL_UpdateRects(agView->v, 1, &rFill1);
}
if (rFill2.w > 0) {
SDL_FillRect(agView->v, &rFill2, AG_COLOR(BG_COLOR));
SDL_UpdateRects(agView->v, 1, &rFill2);
}
}
AG_PostEvent(NULL, win, "window-user-move", "%d,%d",
WIDGET(win)->x, WIDGET(win)->y);
}
/*
* Give focus to a window. This will only take effect at the end
* of the current event cycle.
*/
void
AG_WindowFocus(AG_Window *win)
{
AG_LockVFS(agView);
if (win == NULL) {
agView->winToFocus = NULL;
goto out;
}
AG_ObjectLock(win);
if ((win->flags & AG_WINDOW_DENYFOCUS) == 0) {
win->flags |= AG_WINDOW_FOCUSONATTACH;
agView->winToFocus = win;
}
AG_ObjectUnlock(win);
out:
AG_UnlockVFS(agView);
}
/* Give focus to a window by name, and show it is if is hidden. */
int
AG_WindowFocusNamed(const char *name)
{
AG_Window *owin;
int rv = 0;
AG_LockVFS(agView);
TAILQ_FOREACH(owin, &agView->windows, windows) {
if (strcmp(OBJECT(owin)->name, name) == 0) {
AG_WindowShow(owin);
AG_WindowFocus(owin);
rv = 1;
goto out;
}
}
out:
AG_UnlockVFS(agView);
return (rv);
}
/*
* Check if the given coordinates overlap a window control, and if so
* which operation is related to it.
*/
static __inline__ int
AG_WindowMouseOverCtrl(AG_Window *win, int x, int y)
{
if ((y - WIDGET(win)->y) > (HEIGHT(win) - win->wBorderBot)) {
int xRel = x - WIDGET(win)->x;
if (xRel < win->wResizeCtrl) {
return (AG_WINOP_LRESIZE);
} else if (xRel > (WIDTH(win) - win->wResizeCtrl)) {
return (AG_WINOP_RRESIZE);
} else if ((win->flags & AG_WINDOW_NOVRESIZE) == 0) {
return (AG_WINOP_HRESIZE);
}
}
return (AG_WINOP_NONE);
}
/*
* If there is a modal window, request its shutdown if a click is
* detected outside of its area.
*/
static int
ModalClose(AG_Window *win, int x, int y)
{
AG_ObjectLock(win);
if (!AG_WidgetArea(win, x, y)) {
AG_PostEvent(NULL, win, "window-modal-close", NULL);
AG_ObjectUnlock(win);
return (1);
}
AG_ObjectUnlock(win);
return (0);
}
/*
* Place focus on a Window following a click at the given coordinates.
* Returns 1 if the focus state has changed as a result.
*/
static int
FocusWindowAt(int x, int y)
{
AG_Window *win;
agView->winToFocus = NULL;
TAILQ_FOREACH_REVERSE(win, &agView->windows, ag_windowq, windows) {
AG_ObjectLock(win);
if (!win->visible ||
!AG_WidgetArea(win, x,y) ||
(win->flags & AG_WINDOW_DENYFOCUS)) {
AG_ObjectUnlock(win);
continue;
}
agView->winToFocus = win;
AG_ObjectUnlock(win);
return (1);
}
return (0);
}
/* WM-specific processing of mousemotion events. */
static int
ProcessWM_MouseMotion(AG_Window *win, int xRel, int yRel)
{
switch (agView->winop) {
case AG_WINOP_MOVE:
ProcessWM_Move(win, xRel, yRel);
return (1);
case AG_WINOP_LRESIZE:
ProcessWM_Resize(AG_WINOP_LRESIZE, win, xRel, yRel);
return (1);
case AG_WINOP_RRESIZE:
ProcessWM_Resize(AG_WINOP_RRESIZE, win, xRel, yRel);
return (1);
case AG_WINOP_HRESIZE:
ProcessWM_Resize(AG_WINOP_HRESIZE, win, xRel, yRel);
return (1);
default:
break;
}
return (0);
}
/*
* Reorder the window list and post the appropriate Window events following
* a change in focus.
*/
static void
ChangeWindowFocus(void)
{
AG_Window *winLast = TAILQ_LAST(&agView->windows, ag_windowq);
AG_Window *winToFocus = agView->winToFocus;
if (winLast != NULL) {
if (winToFocus != NULL &&
winToFocus == winLast) {
/* Nothing to do */
goto out;
}
AG_ObjectLock(winLast);
if (winLast->flags & AG_WINDOW_KEEPABOVE) {
AG_ObjectUnlock(winLast);
goto out;
}
AG_PostEvent(NULL, winLast, "window-lostfocus", NULL);
AG_ObjectUnlock(winLast);
}
if (winToFocus != NULL) {
AG_ObjectLock(winToFocus);
if (winToFocus->flags & AG_WINDOW_KEEPBELOW) {
AG_ObjectUnlock(winToFocus);
goto out;
}
TAILQ_REMOVE(&agView->windows, winToFocus, windows);
TAILQ_INSERT_TAIL(&agView->windows, winToFocus, windows);
AG_PostEvent(NULL, winToFocus, "window-gainfocus", NULL);
AG_ObjectUnlock(winToFocus);
}
out:
agView->winToFocus = NULL;
}
/*
* Dispatch events to widgets.
* The view must be locked, and the window list must be nonempty.
* Returns 1 if the event was processed, otherwise 0.
*/
int
AG_WindowEvent(SDL_Event *ev)
{
static AG_Window *winLastKeydown = NULL;
AG_Window *win;
AG_Widget *wid, *wFoc;
int focusChg = 0, tabCycle;
int rv = 0;
agCursorToSet = NULL;
if (agView->nModal > 0) {
win = agView->winModal[agView->nModal-1];
switch (ev->type) {
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
if (ModalClose(win, ev->button.x, ev->button.y)) {
return (1);
}
break;
case SDL_MOUSEMOTION:
if (ModalClose(win, ev->motion.x, ev->motion.y)) {
return (1);
}
break;
default:
break;
}
goto scan; /* Skip WM events */
}
/* Process WM events */
switch (ev->type) {
case SDL_MOUSEBUTTONDOWN: /* Focus on window */
if (FocusWindowAt(ev->button.x, ev->button.y)) {
focusChg++;
}
break;
case SDL_MOUSEBUTTONUP: /* Terminate WM op */
agView->winop = AG_WINOP_NONE;
break;
}
scan:
/*
* Iterate over the visible windows and deliver the appropriate Agar
* events.
*/
TAILQ_FOREACH_REVERSE(win, &agView->windows, ag_windowq, windows) {
AG_ObjectLock(win);
/* XXX TODO move invisible windows to different tailq! */
if (!win->visible || (agView->nModal > 0 &&
win != agView->winModal[agView->nModal-1])) {
AG_ObjectUnlock(win);
continue;
}
switch (ev->type) {
case SDL_MOUSEMOTION:
if (agView->winop != AG_WINOP_NONE &&
agView->winSelected != win) {
AG_ObjectUnlock(win);
continue;
}
if (ProcessWM_MouseMotion(win,
ev->motion.xrel, ev->motion.yrel)) {
AG_ObjectUnlock(win);
rv = 1;
goto out;
}
WIDGET_FOREACH_CHILD(wid, win) {
AG_ObjectLock(wid);
/* XXX hack */
if (wid->flags & AG_WIDGET_PRIO_MOTION) {
AG_WidgetMouseMotion(win, wid,
ev->motion.x,
ev->motion.y,
ev->motion.xrel,
ev->motion.yrel,
ev->motion.state);
AG_ObjectUnlock(wid);
AG_ObjectUnlock(win);
goto out;
}
AG_ObjectUnlock(wid);
}
WIDGET_FOREACH_CHILD(wid, win) {
AG_WidgetMouseMotion(win, wid,
ev->motion.x, ev->motion.y,
ev->motion.xrel, ev->motion.yrel,
ev->motion.state);
}
/* Change the cursor if a RESIZE op is in progress. */
if (agCursorToSet == NULL &&
(win->wBorderBot > 0) &&
!(win->flags & AG_WINDOW_NORESIZE) &&
AG_WidgetArea(win, ev->motion.x, ev->motion.y))
{
switch (AG_WindowMouseOverCtrl(win,
ev->motion.x, ev->motion.y))
{
case AG_WINOP_LRESIZE:
AG_SetCursor(AG_LLDIAG_CURSOR);
break;
case AG_WINOP_RRESIZE:
AG_SetCursor(AG_LRDIAG_CURSOR);
break;
case AG_WINOP_HRESIZE:
AG_SetCursor(AG_VRESIZE_CURSOR);
break;
default:
break;
}
}
if (agCursorToSet == NULL) {
/*
* Prevent widgets in other windows from
* changing the cursor.
*/
agCursorToSet = agDefaultCursor;
}
break;
case SDL_MOUSEBUTTONUP:
/* Terminate active window operations. */
/* XXX redundant? */
if (agView->winop != AG_WINOP_NONE) {
agView->winop = AG_WINOP_NONE;
agView->winSelected = NULL;
}
/*
* Forward to all widgets that either hold focus or have
* the AG_WIDGET_UNFOCUSED_BUTTONUP flag set.
*/
WIDGET_FOREACH_CHILD(wid, win) {
AG_WidgetMouseButtonUp(win, wid,
ev->button.button,
ev->button.x, ev->button.y);
}
if (focusChg) {
AG_ObjectUnlock(win);
rv = 1;
goto out;
}
break;
case SDL_MOUSEBUTTONDOWN:
if (!AG_WidgetArea(win, ev->button.x, ev->button.y)) {
AG_ObjectUnlock(win);
continue;
}
if (win->wBorderBot > 0 &&
!(win->flags & AG_WINDOW_NORESIZE)) {
agView->winop = AG_WindowMouseOverCtrl(win,
ev->button.x, ev->button.y);
if (agView->winop != AG_WINOP_NONE) {
agView->winSelected = win;
AG_ObjectUnlock(win);
goto out;
}
}
/* Forward to overlapping widgets. */
WIDGET_FOREACH_CHILD(wid, win) {
if (AG_WidgetMouseButtonDown(win, wid,
ev->button.button, ev->button.x,
ev->button.y)) {
AG_ObjectUnlock(win);
rv = 1;
goto out;
}
}
if (focusChg) {
AG_ObjectUnlock(win);
rv = 1;
goto out;
}
break;
case SDL_KEYUP:
if (winLastKeydown != NULL && winLastKeydown != win) {
/*
* Key was initially pressed while another
* window was holding focus, ignore.
*/
winLastKeydown = NULL;
break;
}
/* FALLTHROUGH */
case SDL_KEYDOWN:
switch (ev->type) {
case SDL_KEYUP:
AG_WidgetUnfocusedKeyUp(WIDGET(win),
(int)ev->key.keysym.sym,
(int)ev->key.keysym.mod,
(int)ev->key.keysym.unicode);
break;
case SDL_KEYDOWN:
AG_WidgetUnfocusedKeyDown(WIDGET(win),
(int)ev->key.keysym.sym,
(int)ev->key.keysym.mod,
(int)ev->key.keysym.unicode);
break;
}
switch (ev->key.keysym.sym) { /* XXX */
case SDLK_LSHIFT:
case SDLK_RSHIFT:
case SDLK_LALT:
case SDLK_RALT:
case SDLK_LCTRL:
case SDLK_RCTRL:
/* Always ignore modifiers */
AG_ObjectUnlock(win);
return (0);
default:
break;
}
tabCycle = 1;
if (AG_WindowIsFocused(win) &&
(wFoc = AG_WidgetFindFocused(win)) != NULL) {
AG_ObjectLock(wFoc);
if (ev->key.keysym.sym != SDLK_TAB ||
wFoc->flags & AG_WIDGET_CATCH_TAB) {
if (wFoc->flags & AG_WIDGET_CATCH_TAB) {
tabCycle = 0;
}
AG_PostEvent(NULL, wFoc,
(ev->type == SDL_KEYUP) ?
"window-keyup" : "window-keydown",
"%i, %i, %i",
(int)ev->key.keysym.sym,
(int)ev->key.keysym.mod,
(int)ev->key.keysym.unicode);
/*
* Ensure the keyup event is posted to
* this window when the key is released,
* in case a keydown event handler
* changes the window focus.
*/
winLastKeydown = win;
rv = 1;
}
AG_ObjectUnlock(wFoc);
}
if (tabCycle &&
ev->key.keysym.sym == SDLK_TAB &&
ev->type == SDL_KEYUP) {
AG_WindowCycleFocus(win,
(ev->key.keysym.mod & KMOD_SHIFT));
rv = 1;
}
AG_ObjectUnlock(win);
goto out;
}
AG_ObjectUnlock(win);
}
if (agCursorToSet != NULL && SDL_GetCursor() != agCursorToSet) {
SDL_SetCursor(agCursorToSet);
}
out:
/*
* Reorder the window list, if needed, to reflect any change
* in the window focus state.
*
* focusChg is set if focus change was requested by the WM (in which
* case winToFocus could be NULL). Use of AG_WindowFocus() from event
* handler routines can also affect winToFocus.
*/
if (focusChg || agView->winToFocus != NULL) {
ChangeWindowFocus();
}
return (rv);
}
/*
* Set window coordinates and geometry. This should be used instead of
* a direct WidgetSizeAlloc() since it redraws portions of the window
* background when needed.
*/
int
AG_WindowSetGeometryRect(AG_Window *win, AG_Rect r, int bounded)
{
AG_SizeReq rWin;
AG_SizeAlloc aWin;
SDL_Rect rFill;
int new;
int ox, oy, ow, oh;
int nw, nh;
int wMin, hMin;
AG_ObjectLock(win);
ox = WIDGET(win)->x;
oy = WIDGET(win)->y;
ow = WIDTH(win);
oh = HEIGHT(win);
new = ((WIDGET(win)->x == -1 || WIDGET(win)->y == -1));
if (r.w == -1 || r.h == -1) {
AG_WidgetSizeReq(win, &rWin);
nw = (r.w == -1) ? rWin.w : r.w;
nh = (r.h == -1) ? rWin.h : r.h;
} else {
nw = r.w;
nh = r.h;
}
if (win->flags & AG_WINDOW_MINSIZEPCT) {
wMin = win->minPct*win->wReq/100;
hMin = win->minPct*win->hReq/100;
} else {
wMin = win->wMin;
hMin = win->hMin;
}
if (nw < wMin) { nw = wMin; }
if (nh < hMin) { nh = hMin; }
/* Limit the window to the view boundaries. */
aWin.x = (r.x == -1) ? WIDGET(win)->x : r.x;
aWin.y = (r.y == -1) ? WIDGET(win)->y : r.y;
aWin.w = nw;
aWin.h = nh;
if (bounded) {
if (aWin.x+aWin.w > agView->w) {
aWin.w = agView->w - aWin.x;
}
if (aWin.y+aWin.h > agView->h) {
aWin.h = agView->h - aWin.y;
}
if (aWin.w < 0) {
aWin.x = 0;
aWin.w = agView->w;
}
if (aWin.h < 0) {
aWin.y = 0;
aWin.h = agView->h;
}
}
/* Size the widgets and update their coordinates. */
if (AG_WidgetSizeAlloc(win, &aWin) == -1) {
if (!new) { /* Revert */
aWin.x = ox;
aWin.y = oy;
aWin.w = ow;
aWin.h = oh;
AG_WidgetSizeAlloc(win, &aWin);
}
goto fail;
}
AG_WidgetUpdateCoords(win, aWin.x, aWin.y);
/* Update the background. */
/* XXX TODO Avoid drawing over KEEPABOVE windows */
if (win->visible && !new && !agView->opengl) {
if (WIDGET(win)->x > ox) { /* L-resize */
rFill.x = ox;
rFill.y = oy;
rFill.w = WIDGET(win)->x - ox;
rFill.h = HEIGHT(win);
} else if (WIDTH(win) < ow) { /* R-resize */
rFill.x = WIDGET(win)->x + WIDTH(win);
rFill.y = WIDGET(win)->y;
rFill.w = ow - WIDTH(win);
rFill.h = oh;
} else {
rFill.w = 0;
rFill.h = 0;
}
if (rFill.w > 0 && rFill.h > 0) {
SDL_FillRect(agView->v, &rFill, AG_COLOR(BG_COLOR));
SDL_UpdateRects(agView->v, 1, &rFill);
}
if (HEIGHT(win) < oh) { /* H-resize */
rFill.x = ox;
rFill.y = WIDGET(win)->y + HEIGHT(win);
rFill.w = ow;
rFill.h = oh - HEIGHT(win);
SDL_FillRect(agView->v, &rFill, AG_COLOR(BG_COLOR));
SDL_UpdateRects(agView->v, 1, &rFill);
}
}
AG_ObjectUnlock(win);
return (0);
fail:
AG_ObjectUnlock(win);
return (-1);
}
/* Configure minimum window size in percentage of computed geometry. */
void
AG_WindowSetMinSizePct(AG_Window *win, int pct)
{
AG_ObjectLock(win);
win->flags |= AG_WINDOW_MINSIZEPCT;
win->minPct = pct;
AG_ObjectUnlock(win);
}
/* Configure minimum window size in pixels. */
void
AG_WindowSetMinSize(AG_Window *win, int w, int h)
{
AG_ObjectLock(win);
win->flags &= ~(AG_WINDOW_MINSIZEPCT);
win->wMin = w;
win->hMin = h;
AG_ObjectUnlock(win);
}
/* Assign a window a specific alignment and size in pixels. */
int
AG_WindowSetGeometryAligned(AG_Window *win, enum ag_window_alignment align,
int w, int h)
{
int x, y;
switch (align) {
case AG_WINDOW_TL:
x = 0;
y = 0;
break;
case AG_WINDOW_TC:
x = agView->w/2 - w/2;
y = 0;
break;
case AG_WINDOW_TR:
x = agView->w - w;
y = 0;
break;
case AG_WINDOW_ML:
x = 0;
y = agView->h/2 - h/2;
break;
case AG_WINDOW_MR:
x = agView->w - w;
y = agView->h/2 - h/2;
break;
case AG_WINDOW_BL:
x = 0;
y = agView->h - h;
break;
case AG_WINDOW_BC:
x = agView->w/2 - w/2;
y = agView->h - h;
break;
case AG_WINDOW_BR:
x = agView->w - w;
y = agView->h - h;
break;
case AG_WINDOW_MC:
default:
x = agView->w/2 - w/2;
y = agView->h/2 - h/2;
break;
}
return AG_WindowSetGeometry(win, x, y, w, h);
}
int
AG_WindowSetGeometryAlignedPct(AG_Window *win, enum ag_window_alignment align,
int wPct, int hPct)
{
return AG_WindowSetGeometryAligned(win, align,
wPct*agView->w/100,
hPct*agView->h/100);
}
void
AG_WindowSaveGeometry(AG_Window *win)
{
win->rSaved.x = WIDGET(win)->x;
win->rSaved.y = WIDGET(win)->y;
win->rSaved.w = WIDTH(win);
win->rSaved.h = HEIGHT(win);
}
int
AG_WindowRestoreGeometry(AG_Window *win)
{
return AG_WindowSetGeometryRect(win, win->rSaved, 0);
}
void
AG_WindowMaximize(AG_Window *win)
{
AG_WindowSaveGeometry(win);
if (AG_WindowSetGeometry(win, 0, 0, agView->w, agView->h) == 0)
win->flags |= AG_WINDOW_MAXIMIZED;
}
void
AG_WindowUnmaximize(AG_Window *win)
{
if (AG_WindowRestoreGeometry(win) == 0) {
win->flags &= ~(AG_WINDOW_MAXIMIZED);
if (!agView->opengl) {
SDL_FillRect(agView->v, NULL, AG_COLOR(BG_COLOR));
SDL_UpdateRect(agView->v, 0, 0, agView->v->w,
agView->v->h);
}
}
}
static void
IconMotion(AG_Event *event)
{
AG_Icon *icon = AG_SELF();
int xRel = AG_INT(3);
int yRel = AG_INT(4);
AG_Window *wDND = icon->wDND;
if (icon->flags & AG_ICON_DND) {
if (!agView->opengl) {
SDL_FillRect(agView->v, NULL, AG_COLOR(BG_COLOR));
AG_ViewUpdateFB(&WIDGET(wDND)->rView);
}
AG_WindowSetGeometryRect(wDND,
AG_RECT(WIDGET(wDND)->x + xRel,
WIDGET(wDND)->y + yRel,
WIDTH(wDND),
HEIGHT(wDND)), 1);
icon->xSaved = WIDGET(wDND)->x;
icon->ySaved = WIDGET(wDND)->y;
icon->wSaved = WIDTH(wDND);
icon->hSaved = HEIGHT(wDND);
}
}
static void
IconButtonDown(AG_Event *event)
{
AG_Icon *icon = AG_SELF();
AG_Window *win = AG_PTR(1);
WIDGET(icon)->flags |= AG_WIDGET_UNFOCUSED_MOTION|
AG_WIDGET_UNFOCUSED_BUTTONUP;
if (icon->flags & AG_ICON_DBLCLICKED) {
AG_CancelEvent(icon, "dblclick-expire");
AG_WindowUnminimize(win);
AG_ObjectDetach(win->icon);
AG_ViewDetach(icon->wDND);
icon->wDND = NULL;
icon->flags &= (AG_ICON_DND|AG_ICON_DBLCLICKED);
} else {
icon->flags |= (AG_ICON_DND|AG_ICON_DBLCLICKED);
AG_SchedEvent(NULL, icon, agMouseDblclickDelay,
"dblclick-expire", NULL);
}
}
static void
IconButtonUp(AG_Event *event)
{
AG_Icon *icon = AG_SELF();
WIDGET(icon)->flags &= ~(AG_WIDGET_UNFOCUSED_MOTION);
WIDGET(icon)->flags &= ~(AG_WIDGET_UNFOCUSED_BUTTONUP);
icon->flags &= ~(AG_ICON_DND);
}
static void
DoubleClickTimeout(AG_Event *event)
{
AG_Icon *icon = AG_SELF();
icon->flags &= ~(AG_ICON_DBLCLICKED);
}
void
AG_WindowMinimize(AG_Window *win)
{
AG_Window *wDND;
AG_Icon *icon = win->icon;
if (win->flags & AG_WINDOW_MINIMIZED) {
return;
}
win->flags |= AG_WINDOW_MINIMIZED;
AG_WindowHide(win);
wDND = AG_WindowNew(AG_WINDOW_PLAIN|AG_WINDOW_KEEPBELOW|
AG_WINDOW_DENYFOCUS|AG_WINDOW_NOBACKGROUND);
AG_ObjectAttach(wDND, icon);
icon->wDND = wDND;
icon->flags &= ~(AG_ICON_DND|AG_ICON_DBLCLICKED);
AG_SetEvent(icon, "dblclick-expire", DoubleClickTimeout, NULL);
AG_SetEvent(icon, "window-mousemotion", IconMotion, NULL);
AG_SetEvent(icon, "window-mousebuttonup", IconButtonUp, NULL);
AG_SetEvent(icon, "window-mousebuttondown", IconButtonDown, "%p", win);
if (icon->xSaved != -1) {
AG_WindowShow(wDND);
AG_WindowSetGeometry(wDND, icon->xSaved, icon->ySaved,
icon->wSaved, icon->hSaved);
} else {
AG_WindowSetPosition(wDND, AG_WINDOW_LOWER_LEFT, 1);
AG_WindowShow(wDND);
icon->xSaved = WIDGET(wDND)->x;
icon->ySaved = WIDGET(wDND)->y;
icon->wSaved = WIDTH(wDND);
icon->hSaved = HEIGHT(wDND);
}
}
void
AG_WindowUnminimize(AG_Window *win)
{
if (!win->visible) {
AG_WindowShow(win);
win->flags &= ~(AG_WINDOW_MINIMIZED);
} else {
AG_WindowFocus(win);
}
}
/* Process a window resize operation initiated by the WM. */
static void
ProcessWM_Resize(int op, AG_Window *win, int xRel, int yRel)
{
int x = WIDGET(win)->x;
int y = WIDGET(win)->y;
int w = WIDTH(win);
int h = HEIGHT(win);
switch (op) {
case AG_WINOP_LRESIZE:
if (!(win->flags & AG_WINDOW_NOHRESIZE)) {
if (xRel < 0) {
w -= xRel;
x += xRel;
} else if (xRel > 0) {
w -= xRel;
x += xRel;
}
}
if (!(win->flags & AG_WINDOW_NOVRESIZE)) {
if (yRel < 0 || yRel > 0)
h += yRel;
}
break;
case AG_WINOP_RRESIZE:
if (!(win->flags & AG_WINDOW_NOHRESIZE)) {
if (xRel < 0 || xRel > 0)
w += xRel;
}
if (!(win->flags & AG_WINDOW_NOVRESIZE)) {
if (yRel < 0 || yRel > 0)
h += yRel;
}
break;
case AG_WINOP_HRESIZE:
if (!(win->flags & AG_WINDOW_NOHRESIZE)) {
if (yRel < 0 || yRel > 0)
h += yRel;
}
break;
default:
break;
}
AG_WindowSetGeometry(win, x, y, w, h);
AG_PostEvent(NULL, win, "window-user-resize", "%d,%d", w, h);
}
void
AG_WindowDetachGenEv(AG_Event *event)
{
AG_Window *win = AG_PTR(1);
AG_ViewDetach(win);
}
void
AG_WindowShowGenEv(AG_Event *event)
{
AG_WindowShow(AG_PTR(1));
}
void
AG_WindowHideGenEv(AG_Event *event)
{
AG_WindowHide(AG_PTR(1));
}
void
AG_WindowCloseGenEv(AG_Event *event)
{
AG_PostEvent(NULL, AG_PTR(1), "window-close", NULL);
}
static void
SizeRequest(void *obj, AG_SizeReq *r)
{
AG_Window *win = obj;
AG_Widget *chld;
AG_SizeReq rChld, rTbar;
int nWidgets;
int wTot;
wTot = win->lPad + win->rPad + win->wBorderSide*2;
r->w = wTot;
r->h = win->bPad+win->tPad + win->wBorderBot;
if (win->tbar != NULL) {
AG_WidgetSizeReq(win->tbar, &rTbar);
r->w = MAX(r->w, rTbar.w);
r->h += rTbar.h;
}
nWidgets = 0;
WIDGET_FOREACH_CHILD(chld, win) {
if (chld == WIDGET(win->tbar)) {
continue;
}
AG_WidgetSizeReq(chld, &rChld);
r->w = MAX(r->w, rChld.w + wTot);
r->h += rChld.h + win->spacing;
nWidgets++;
}
if (nWidgets > 0 && r->h >= win->spacing)
r->h -= win->spacing;
win->wReq = r->w;
win->hReq = r->h;
ClampToView(win);
}
static int
SizeAllocate(void *obj, const AG_SizeAlloc *a)
{
AG_Window *win = obj;
AG_Widget *chld;
AG_SizeReq rChld;
AG_SizeAlloc aChld;
int wAvail, hAvail;
int totFixed;
int nWidgets;
/* Calculate total space available for widgets. */
wAvail = a->w - win->lPad - win->rPad - win->wBorderSide*2;
hAvail = a->h - win->bPad - win->tPad - win->wBorderBot;
/* Calculate the space occupied by non-fill widgets. */
nWidgets = 0;
totFixed = 0;
WIDGET_FOREACH_CHILD(chld, win) {
AG_WidgetSizeReq(chld, &rChld);
if ((chld->flags & AG_WIDGET_VFILL) == 0) {
totFixed += rChld.h;
}
if (chld != WIDGET(win->tbar)) {
totFixed += win->spacing;
}
nWidgets++;
}
if (nWidgets > 0 && totFixed >= win->spacing)
totFixed -= win->spacing;
/* Position the widgets. */
if (win->tbar != NULL) {
AG_WidgetSizeReq(win->tbar, &rChld);
aChld.x = 0;
aChld.y = 0;
aChld.w = a->w;
aChld.h = rChld.h;
AG_WidgetSizeAlloc(win->tbar, &aChld);
aChld.x = win->lPad + win->wBorderSide;
aChld.y = rChld.h + win->tPad;
} else {
aChld.x = win->lPad + win->wBorderSide;
aChld.y = win->tPad;
}
WIDGET_FOREACH_CHILD(chld, win) {
AG_WidgetSizeReq(chld, &rChld);
if (chld == WIDGET(win->tbar)) {
continue;
}
if (chld->flags & AG_WIDGET_NOSPACING) {
AG_SizeAlloc aTmp;
AG_Widget *chldNext;
aTmp.x = 0;
aTmp.y = aChld.y;
aTmp.w = a->w;
aTmp.h = rChld.h;
AG_WidgetSizeAlloc(chld, &aTmp);
aChld.y += aTmp.h;
chldNext = WIDGET_NEXT_CHILD(chld);
if (chldNext == NULL ||
!(chldNext->flags & AG_WIDGET_NOSPACING)) {
aChld.y += win->spacing;
}
continue;
} else {
aChld.w = (chld->flags & AG_WIDGET_HFILL) ?
wAvail : MIN(wAvail,rChld.w);
}
aChld.h = (chld->flags & AG_WIDGET_VFILL) ?
hAvail-totFixed : rChld.h;
AG_WidgetSizeAlloc(chld, &aChld);
aChld.y += aChld.h + win->spacing;
}
ClampToView(win);
win->r.x = 0;
win->r.y = 0;
win->r.w = a->w;
win->r.h = a->h;
return (0);
}
/* Set the width of the window side borders. */
void
AG_WindowSetSideBorders(AG_Window *win, int pixels)
{
if (win != NULL) {
AG_ObjectLock(win);
win->wBorderSide = pixels;
AG_ObjectUnlock(win);
} else {
agWindowSideBorderDefault = pixels;
}
}
/* Set the width of the window bottom border. */
void
AG_WindowSetBottomBorder(AG_Window *win, int pixels)
{
if (win != NULL) {
AG_ObjectLock(win);
win->wBorderBot = pixels;
AG_ObjectUnlock(win);
} else {
agWindowBotBorderDefault = pixels;
}
}
/* Change the spacing between child widgets. */
void
AG_WindowSetSpacing(AG_Window *win, int spacing)
{
AG_ObjectLock(win);
win->spacing = spacing;
AG_ObjectUnlock(win);
}
/* Change the padding around child widgets. */
void
AG_WindowSetPadding(AG_Window *win, int lPad, int rPad, int tPad, int bPad)
{
AG_ObjectLock(win);
if (lPad != -1) { win->lPad = lPad; }
if (rPad != -1) { win->rPad = rPad; }
if (tPad != -1) { win->tPad = tPad; }
if (bPad != -1) { win->bPad = bPad; }
AG_ObjectUnlock(win);
}
/* Request a specific initial window position. */
void
AG_WindowSetPosition(AG_Window *win, enum ag_window_alignment alignment,
int cascade)
{
AG_ObjectLock(win);
win->alignment = alignment;
AG_SETFLAGS(win->flags, AG_WINDOW_CASCADE, cascade);
AG_ObjectUnlock(win);
}
/* Set a default window-close handler. */
void
AG_WindowSetCloseAction(AG_Window *win, enum ag_window_close_action mode)
{
AG_ObjectLock(win);
switch (mode) {
case AG_WINDOW_HIDE:
AG_SetEvent(win, "window-close", AGWINHIDE(win));
break;
case AG_WINDOW_DETACH:
AG_SetEvent(win, "window-close", AGWINDETACH(win));
break;
default:
AG_UnsetEvent(win, "window-close");
break;
}
AG_ObjectUnlock(win);
}
/* Set the text to show inside a window's titlebar. */
void
AG_WindowSetCaption(AG_Window *win, const char *fmt, ...)
{
char s[AG_LABEL_MAX];
va_list ap;
AG_ObjectLock(win);
if (win->tbar != NULL) {
va_start(ap, fmt);
Vsnprintf(s, sizeof(s), fmt, ap);
va_end(ap);
Strlcpy(win->caption, s, sizeof(win->caption));
AG_WindowUpdateCaption(win);
}
AG_ObjectUnlock(win);
}
void
AG_WindowUpdateCaption(AG_Window *win)
{
char iconCap[16], *c;
AG_ObjectLock(win);
if (win->tbar != NULL) {
AG_ObjectLock(win->tbar);
AG_LabelString(win->tbar->label, (win->caption != NULL) ?
win->caption : "");
/* XXX */
if (Strlcpy(iconCap, win->caption, sizeof(iconCap)) >=
sizeof(iconCap)) {
for (c = &iconCap[0]; *c != '\0'; c++) {
if (*c == ' ')
*c = '\n';
}
AG_IconSetText(win->icon, "%s...", iconCap);
} else {
AG_IconSetText(win->icon, "%s", iconCap);
}
AG_ObjectUnlock(win->tbar);
}
AG_ObjectUnlock(win);
}
AG_WidgetClass agWindowClass = {
{
"Agar(Widget:Window)",
sizeof(AG_Window),
{ 0,0 },
Init,
NULL, /* free */
NULL, /* destroy */
NULL, /* load */
NULL, /* save */
NULL /* edit */
},
Draw,
SizeRequest,
SizeAllocate
};