/*
* 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 "opengl.h"
#include
#include "widget.h"
#include "window.h"
#include "cursors.h"
#include "menu.h"
#include "primitive.h"
#include "notebook.h"
#include "gui_math.h"
#include
#include
#include
/* #define DEBUG_CLIPPING */
SDL_Cursor *agCursorToSet = NULL; /* Set cursor at end of event cycle */
static void
ChildAttached(AG_Event *event)
{
AG_Widget *pwid = AG_SELF();
AG_Widget *wid = AG_SENDER();
AG_Style *style = pwid->style;
/* Inherit style from parent widget. */
if (style != NULL)
wid->style = style;
}
#ifdef AG_LEGACY
/*
* Forward object-level `bound' event to `widget-bound'.
*/
static void
Bound(AG_Event *event)
{
AG_Widget *wid = AG_SELF();
AG_Variable *V = AG_PTR(1);
AG_PostEvent(NULL, wid, "widget-bound", "%p", V);
}
#endif /* AG_LEGACY */
static void
Init(void *obj)
{
AG_Widget *wid = obj;
OBJECT(wid)->save_pfx = "/widgets";
OBJECT(wid)->flags |= AG_OBJECT_NAME_ONATTACH;
wid->flags = 0;
wid->rView = AG_RECT2(-1,-1,-1,-1);
wid->rSens = AG_RECT2(0,0,0,0);
wid->x = -1;
wid->y = -1;
wid->w = -1;
wid->h = -1;
wid->style = &agStyleDefault;
SLIST_INIT(&wid->menus);
wid->focusFwd = NULL;
wid->nsurfaces = 0;
wid->surfaces = NULL;
wid->surfaceFlags = NULL;
#ifdef HAVE_OPENGL
wid->textures = NULL;
wid->texcoords = NULL;
wid->textureGC = NULL;
wid->nTextureGC = 0;
#endif
/*
* Arrange for immediate children to inherit the style settings
* of the parent on attachment.
*/
AG_SetEvent(wid, "child-attached", ChildAttached, NULL);
#ifdef AG_LEGACY
AG_SetEvent(wid, "bound", Bound, NULL);
#endif
}
/* Traverse the widget tree using a pathname. */
static void *
WidgetFindPath(const AG_Object *parent, const char *name)
{
char node_name[AG_OBJECT_PATH_MAX];
void *rv;
char *s;
AG_Object *chld;
Strlcpy(node_name, name, sizeof(node_name));
if ((s = strchr(node_name, '/')) != NULL) {
*s = '\0';
}
if (AG_OfClass(parent, "AG_Display:*")) {
AG_Display *disp = (AG_Display *)parent;
AG_Window *win;
TAILQ_FOREACH(win, &disp->windows, windows) {
if (strcmp(AGOBJECT(win)->name, node_name) != 0) {
continue;
}
if ((s = strchr(name, '/')) != NULL) {
rv = WidgetFindPath(AGOBJECT(win), &s[1]);
if (rv != NULL) {
return (rv);
} else {
return (NULL);
}
}
return (win);
}
} else if (AG_OfClass(parent, "AG_Widget:AG_Notebook:*")) {
AG_Notebook *book = (AG_Notebook *)parent;
AG_NotebookTab *tab;
/*
* This hack allows Notebook tabs to be treated as
* separate objects, even though they are not attached
* to the widget hierarchy.
*/
AG_ObjectLock(book);
TAILQ_FOREACH(tab, &book->tabs, tabs) {
if (strcmp(OBJECT(tab)->name, node_name) != 0) {
continue;
}
if ((s = strchr(name, '/')) != NULL) {
rv = WidgetFindPath(OBJECT(tab), &s[1]);
if (rv != NULL) {
AG_ObjectUnlock(book);
return (rv);
} else {
AG_ObjectUnlock(book);
return (NULL);
}
}
AG_ObjectUnlock(book);
return (tab);
}
AG_ObjectUnlock(book);
} else {
TAILQ_FOREACH(chld, &parent->children, cobjs) {
if (strcmp(chld->name, node_name) != 0) {
continue;
}
if ((s = strchr(name, '/')) != NULL) {
rv = WidgetFindPath(chld, &s[1]);
if (rv != NULL) {
return (rv);
} else {
return (NULL);
}
}
return (chld);
}
}
return (NULL);
}
/*
* Find a widget by name. Return value is only valid as long as the
* View VFS is locked.
*/
void *
AG_WidgetFind(AG_Display *view, const char *name)
{
void *rv;
#ifdef AG_DEBUG
if (name[0] != '/')
AG_FatalError("WidgetFind: Not an absolute path: %s", name);
#endif
AG_LockVFS(view);
rv = WidgetFindPath(OBJECT(view), &name[1]);
AG_UnlockVFS(view);
if (rv == NULL) {
AG_SetError(_("The widget `%s' does not exist."), name);
}
return (rv);
}
/* Set the FOCUSABLE flag on a widget. */
void
AG_WidgetSetFocusable(void *obj, int flag)
{
AG_Widget *wid = obj;
AG_ObjectLock(wid);
AG_SETFLAGS(wid->flags, AG_WIDGET_FOCUSABLE, flag);
AG_ObjectUnlock(wid);
}
/* Arrange for a widget to automatically forward focus to another widget. */
void
AG_WidgetForwardFocus(void *obj, void *objFwd)
{
AG_Widget *wid = obj;
AG_ObjectLock(wid);
if (objFwd != NULL) {
wid->flags |= AG_WIDGET_FOCUSABLE;
wid->focusFwd = WIDGET(objFwd);
} else {
wid->flags &= ~(AG_WIDGET_FOCUSABLE);
wid->focusFwd = NULL;
}
AG_ObjectUnlock(wid);
}
#ifdef AG_LEGACY
/*
* Duplicate a widget binding.
* LEGACY Interface.
*/
int
AG_WidgetCopyBinding(void *wDst, const char *nDst, AG_Variable *Vsrc)
{
AG_Variable *Vdst;
if ((Vdst = AG_GetVariable(wDst, nDst, NULL)) == NULL) {
return (-1);
}
Vdst->type = Vsrc->type;
Vdst->mutex = Vsrc->mutex;
Vdst->data.p = Vsrc->data.p;
switch (Vdst->type) {
case AG_VARIABLE_P_FLAG:
case AG_VARIABLE_P_FLAG8:
case AG_VARIABLE_P_FLAG16:
case AG_VARIABLE_P_FLAG32:
Vdst->info.bitmask = Vsrc->info.bitmask;
break;
default:
break;
}
if (AG_VARIABLE_TYPE(Vdst) == AG_VARIABLE_STRING) {
Vdst->info.size = Vsrc->info.size;
}
AG_PostEvent(NULL, wDst, "bound", "%p", Vdst);
AG_UnlockVariable(Vdst);
return (0);
}
/*
* Bind a mutex-protected variable to a widget.
* LEGACY Interface: new code should use AG_Variable(3).
*/
AG_Variable *
AG_WidgetBindMp(void *obj, const char *name, AG_Mutex *mutex,
enum ag_variable_type type, ...)
{
AG_Widget *wid = obj;
AG_Variable *V;
va_list ap;
void *p = NULL;
Uint bitmask = 0;
size_t size = 0;
AG_ObjectLock(wid);
va_start(ap, type);
switch (type) {
case AG_VARIABLE_P_FLAG:
case AG_VARIABLE_P_FLAG8:
case AG_VARIABLE_P_FLAG16:
case AG_VARIABLE_P_FLAG32:
p = va_arg(ap, void *);
bitmask = va_arg(ap, Uint);
break;
case AG_VARIABLE_STRING:
case AG_VARIABLE_P_STRING:
case AG_VARIABLE_CONST_STRING:
case AG_VARIABLE_P_CONST_STRING:
p = (void *)va_arg(ap, char *);
size = va_arg(ap, size_t);
break;
default:
p = va_arg(ap, void *);
break;
}
va_end(ap);
switch (type) {
case AG_VARIABLE_P_INT:
V = AG_BindInt(wid, name, (int *)p);
break;
case AG_VARIABLE_P_UINT8:
V = AG_BindUint8(wid, name, (Uint8 *)p);
break;
case AG_VARIABLE_P_SINT8:
V = AG_BindSint8(wid, name, (Sint8 *)p);
break;
case AG_VARIABLE_P_UINT16:
V = AG_BindUint16(wid, name, (Uint16 *)p);
break;
case AG_VARIABLE_P_SINT16:
V = AG_BindSint16(wid, name, (Sint16 *)p);
break;
case AG_VARIABLE_P_UINT32:
V = AG_BindUint32(wid, name, (Uint32 *)p);
break;
case AG_VARIABLE_P_SINT32:
V = AG_BindSint32(wid, name, (Sint32 *)p);
break;
case AG_VARIABLE_P_FLOAT:
V = AG_BindFloat(wid, name, (float *)p);
break;
case AG_VARIABLE_P_DOUBLE:
V = AG_BindDouble(wid, name, (double *)p);
break;
case AG_VARIABLE_P_FLAG:
V = AG_BindFlag(wid, name, (Uint *)p, bitmask);
break;
case AG_VARIABLE_P_FLAG8:
V = AG_BindFlag8(wid, name, (Uint8 *)p, (Uint8)bitmask);
break;
case AG_VARIABLE_P_FLAG16:
V = AG_BindFlag16(wid, name, (Uint16 *)p, (Uint16)bitmask);
break;
case AG_VARIABLE_P_FLAG32:
V = AG_BindFlag32(wid, name, (Uint32 *)p, (Uint32)bitmask);
break;
case AG_VARIABLE_P_STRING:
case AG_VARIABLE_P_CONST_STRING:
V = AG_BindString(wid, name, (char *)p, size);
break;
default:
AG_ObjectUnlock(wid);
return (NULL);
}
V->mutex = mutex;
AG_ObjectUnlock(wid);
return (V);
}
/*
* Bind a non mutex-protected variable to a widget.
* LEGACY Interface: new code should use AG_Variable(3).
*/
AG_Variable *
AG_WidgetBind(void *pObj, const char *name, enum ag_variable_type type, ...)
{
AG_Object *obj = pObj;
AG_Variable *V;
va_list ap;
Uint i;
AG_ObjectLock(obj);
for (i = 0; i < obj->nVars; i++) {
V = &obj->vars[i];
if (strcmp(obj->vars[i].name, name) == 0)
break;
}
if (i == obj->nVars) { /* Create new binding */
obj->vars = Realloc(obj->vars,
(obj->nVars+1)*sizeof(AG_Variable));
V = &obj->vars[obj->nVars++];
Strlcpy(V->name, name, sizeof(V->name));
}
V->type = type;
V->mutex = NULL;
V->fn.fnVoid = NULL;
va_start(ap, type);
switch (type) {
case AG_VARIABLE_P_FLAG:
case AG_VARIABLE_P_FLAG8:
case AG_VARIABLE_P_FLAG16:
case AG_VARIABLE_P_FLAG32:
V->data.p = va_arg(ap, void *);
V->info.bitmask = va_arg(ap, Uint);
break;
case AG_VARIABLE_STRING:
case AG_VARIABLE_P_STRING:
case AG_VARIABLE_CONST_STRING:
case AG_VARIABLE_P_CONST_STRING:
V->data.p = va_arg(ap, char *);
V->info.size = va_arg(ap, size_t);
break;
default:
V->data.p = va_arg(ap, void *);
V->info.bitmask = 0;
break;
}
va_end(ap);
AG_PostEvent(NULL, obj, "bound", "%p", V);
AG_ObjectUnlock(obj);
return (V);
}
/*
* LEGACY Interfaces: new code should use AG_Variable(3).
*/
size_t
AG_WidgetCopyString(void *wid, const char *name, char *dst, size_t dst_size)
{
AG_Variable *V;
char *s;
size_t rv;
if ((V = AG_GetVariable(wid, name, &s)) == NULL) {
AG_FatalError("%s", AG_GetError());
}
rv = Strlcpy(dst, s, dst_size);
AG_UnlockVariable(V);
return (rv);
}
int
AG_WidgetInt(void *wid, const char *name)
{
AG_Variable *b;
int *i, rv;
if ((b = AG_GetVariable(wid, name, &i)) == NULL) { AG_FatalError(NULL); }
rv = *i;
AG_UnlockVariable(b);
return (rv);
}
Uint
AG_WidgetUint(void *wid, const char *name)
{
AG_Variable *b;
Uint *i, rv;
if ((b = AG_GetVariable(wid, name, &i)) == NULL) { AG_FatalError(NULL); }
rv = *i;
AG_UnlockVariable(b);
return (rv);
}
Uint8
AG_WidgetUint8(void *wid, const char *name)
{
AG_Variable *b;
Uint8 *i, rv;
if ((b = AG_GetVariable(wid, name, &i)) == NULL) { AG_FatalError(NULL); }
rv = *i;
AG_UnlockVariable(b);
return (rv);
}
Sint8
AG_WidgetSint8(void *wid, const char *name)
{
AG_Variable *b;
Sint8 *i, rv;
if ((b = AG_GetVariable(wid, name, &i)) == NULL) { AG_FatalError(NULL); }
rv = *i;
AG_UnlockVariable(b);
return (rv);
}
Uint16
AG_WidgetUint16(void *wid, const char *name)
{
AG_Variable *b;
Uint16 *i, rv;
if ((b = AG_GetVariable(wid, name, &i)) == NULL) { AG_FatalError(NULL); }
rv = *i;
AG_UnlockVariable(b);
return (rv);
}
Sint16
AG_WidgetSint16(void *wid, const char *name)
{
AG_Variable *b;
Sint16 *i, rv;
if ((b = AG_GetVariable(wid, name, &i)) == NULL) { AG_FatalError(NULL); }
rv = *i;
AG_UnlockVariable(b);
return (rv);
}
Uint32
AG_WidgetUint32(void *wid, const char *name)
{
AG_Variable *b;
Uint32 *i, rv;
if ((b = AG_GetVariable(wid, name, &i)) == NULL) { AG_FatalError(NULL); }
rv = *i;
AG_UnlockVariable(b);
return (rv);
}
Sint32
AG_WidgetSint32(void *wid, const char *name)
{
AG_Variable *b;
Sint32 *i, rv;
if ((b = AG_GetVariable(wid, name, &i)) == NULL) { AG_FatalError(NULL); }
rv = *i;
AG_UnlockVariable(b);
return (rv);
}
float
AG_WidgetFloat(void *wid, const char *name)
{
AG_Variable *b;
float *f, rv;
if ((b = AG_GetVariable(wid, name, &f)) == NULL) { AG_FatalError(NULL); }
rv = *f;
AG_UnlockVariable(b);
return (rv);
}
double
AG_WidgetDouble(void *wid, const char *name)
{
AG_Variable *b;
double *d, rv;
if ((b = AG_GetVariable(wid, name, &d)) == NULL) { AG_FatalError(NULL); }
rv = *d;
AG_UnlockVariable(b);
return (rv);
}
char *
AG_WidgetString(void *wid, const char *name)
{
AG_Variable *b;
char *s, *sd;
if ((b = AG_GetVariable(wid, name, &s)) == NULL) { AG_FatalError(NULL); }
sd = AG_Strdup(s);
AG_UnlockVariable(b);
return (sd);
}
void *
AG_WidgetPointer(void *wid, const char *name)
{
AG_Variable *b;
void **p, *rv;
if ((b = AG_GetVariable(wid, name, &p)) == NULL) { AG_FatalError(NULL); }
rv = *p;
AG_UnlockVariable(b);
return (p);
}
void
AG_WidgetSetInt(void *wid, const char *name, int ni)
{
AG_Variable *V;
int *i;
if ((V = AG_GetVariable(wid, name, &i)) == NULL) { AG_FatalError(NULL); }
*i = ni;
AG_UnlockVariable(V);
}
void
AG_WidgetSetUint(void *wid, const char *name, Uint ni)
{
AG_Variable *V;
Uint *i;
if ((V = AG_GetVariable(wid, name, &i)) == NULL) { AG_FatalError(NULL); }
*i = ni;
AG_UnlockVariable(V);
}
void
AG_WidgetSetUint8(void *wid, const char *name, Uint8 ni)
{
AG_Variable *V;
Uint8 *i;
if ((V = AG_GetVariable(wid, name, &i)) == NULL) { AG_FatalError(NULL); }
*i = ni;
AG_UnlockVariable(V);
}
void
AG_WidgetSetSint8(void *wid, const char *name, Sint8 ni)
{
AG_Variable *V;
Sint8 *i;
if ((V = AG_GetVariable(wid, name, &i)) == NULL) { AG_FatalError(NULL); }
*i = ni;
AG_UnlockVariable(V);
}
void
AG_WidgetSetUint16(void *wid, const char *name, Uint16 ni)
{
AG_Variable *V;
Uint16 *i;
if ((V = AG_GetVariable(wid, name, &i)) == NULL) { AG_FatalError(NULL); }
*i = ni;
AG_UnlockVariable(V);
}
void
AG_WidgetSetSint16(void *wid, const char *name, Sint16 ni)
{
AG_Variable *V;
Sint16 *i;
if ((V = AG_GetVariable(wid, name, &i)) == NULL) { AG_FatalError(NULL); }
*i = ni;
AG_UnlockVariable(V);
}
void
AG_WidgetSetUint32(void *wid, const char *name, Uint32 ni)
{
AG_Variable *V;
Uint32 *i;
if ((V = AG_GetVariable(wid, name, &i)) == NULL) { AG_FatalError(NULL); }
*i = ni;
AG_UnlockVariable(V);
}
void
AG_WidgetSetSint32(void *wid, const char *name, Sint32 ni)
{
AG_Variable *V;
Sint32 *i;
if ((V = AG_GetVariable(wid, name, &i)) == NULL) { AG_FatalError(NULL); }
*i = ni;
AG_UnlockVariable(V);
}
void
AG_WidgetSetFloat(void *wid, const char *name, float nf)
{
AG_Variable *V;
float *f;
if ((V = AG_GetVariable(wid, name, &f)) == NULL) { AG_FatalError(NULL); }
*f = nf;
AG_UnlockVariable(V);
}
void
AG_WidgetSetDouble(void *wid, const char *name, double nd)
{
AG_Variable *V;
double *d;
if ((V = AG_GetVariable(wid, name, &d)) == NULL) { AG_FatalError(NULL); }
*d = nd;
AG_UnlockVariable(V);
}
void
AG_WidgetSetPointer(void *wid, const char *name, void *np)
{
AG_Variable *V;
void **p;
if ((V = AG_GetVariable(wid, name, &p)) == NULL) { AG_FatalError(NULL); }
*p = np;
AG_UnlockVariable(V);
}
#endif /* AG_LEGACY */
/*
* Register a surface with the given widget. In OpenGL mode, a texture will
* be generated when the surface is first blitted.
*
* The surface is not duplicated, but will be freed automatically along
* with other widget data unless the NODUP flag is set.
*
* Safe to call in any context, but the returned name is only valid as long
* as the widget is locked.
*/
int
AG_WidgetMapSurface(void *p, AG_Surface *su)
{
AG_Widget *wid = p;
int i, idx = -1;
AG_ObjectLock(wid);
for (i = 0; i < wid->nsurfaces; i++) {
if (wid->surfaces[i] == NULL) {
idx = i;
break;
}
}
if (i == wid->nsurfaces) {
wid->surfaces = Realloc(wid->surfaces,
(wid->nsurfaces+1)*sizeof(AG_Surface *));
wid->surfaceFlags = Realloc(wid->surfaceFlags,
(wid->nsurfaces+1)*sizeof(Uint));
#ifdef HAVE_OPENGL
if (agView->opengl) {
wid->textures = Realloc(wid->textures,
(wid->nsurfaces+1)*sizeof(GLuint));
wid->texcoords = Realloc(wid->texcoords,
(wid->nsurfaces+1)*sizeof(GLfloat)*4);
}
#endif
idx = wid->nsurfaces++;
}
wid->surfaces[idx] = su;
wid->surfaceFlags[idx] = 0;
#ifdef HAVE_OPENGL
if (agView->opengl)
wid->textures[idx] = 0;
#endif
AG_ObjectUnlock(wid);
return (idx);
}
/*
* Variant of WidgetMapSurface() that sets the NODUP flag such that
* the surface is not freed automatically with the widget.
*
* Safe to call in any context, but the returned name is only valid as long
* as the widget is locked.
*/
int
AG_WidgetMapSurfaceNODUP(void *p, AG_Surface *su)
{
AG_Widget *wid = p;
int name;
AG_ObjectLock(wid);
name = AG_WidgetMapSurface(wid, su);
wid->surfaceFlags[name] |= AG_WIDGET_SURFACE_NODUP;
AG_ObjectUnlock(wid);
return (name);
}
/*
* Replace the contents of a mapped surface. Unless NODUP is set, the current
* source surface is freed.
*
* This is safe to call in any context, with the drawback that in OpenGL mode,
* the previous texture cannot be deleted immediately and is instead queued
* for future deletion within rendering context.
*/
void
AG_WidgetReplaceSurface(void *p, int name, AG_Surface *su)
{
AG_Widget *wid = p;
AG_ObjectLock(wid);
if (wid->surfaces[name] != NULL) {
if (!WSURFACE_NODUP(wid,name))
AG_SurfaceFree(wid->surfaces[name]);
}
wid->surfaces[name] = su;
wid->surfaceFlags[name] &= ~(AG_WIDGET_SURFACE_NODUP);
#ifdef HAVE_OPENGL
if (agView->opengl && wid->textures[name] != 0) {
wid->textureGC = Realloc(wid->textureGC, (wid->nTextureGC+1)*
sizeof(Uint));
wid->textureGC[wid->nTextureGC++] = wid->textures[name];
wid->textures[name] = 0; /* Will be regenerated */
}
#endif
AG_ObjectUnlock(wid);
}
/* Variant of WidgetReplaceSurface() that sets the NODUP flag. */
void
AG_WidgetReplaceSurfaceNODUP(void *p, int name, AG_Surface *su)
{
AG_Widget *wid = p;
AG_ObjectLock(wid);
AG_WidgetReplaceSurface(wid, name, su);
wid->surfaceFlags[name] |= AG_WIDGET_SURFACE_NODUP;
AG_ObjectUnlock(wid);
}
#ifdef HAVE_OPENGL
/* Remove surfaces which were previously queued for deletion. */
static void
DeleteQueuedTextures(AG_Widget *wid)
{
Uint i;
for (i = 0; i < wid->nTextureGC; i++) {
glDeleteTextures(1, (GLuint *)&wid->textureGC[i]);
}
wid->nTextureGC = 0;
}
#endif
/*
* NOTE: Texture operations are involved so this is only safe to invoke
* in rendering context.
*/
static void
Destroy(void *obj)
{
AG_Widget *wid = obj;
AG_PopupMenu *pm, *pm2;
Uint i;
for (pm = SLIST_FIRST(&wid->menus);
pm != SLIST_END(&wid->menus);
pm = pm2) {
pm2 = SLIST_NEXT(pm, menus);
AG_PopupDestroy(NULL, pm);
}
#ifdef HAVE_OPENGL
if (wid->textureGC > 0)
DeleteQueuedTextures(wid);
#endif
for (i = 0; i < wid->nsurfaces; i++) {
if (wid->surfaces[i] != NULL && !WSURFACE_NODUP(wid,i))
AG_SurfaceFree(wid->surfaces[i]);
#ifdef HAVE_OPENGL
if (agView->opengl) {
if (wid->textures[i] != 0) {
glDeleteTextures(1,
(GLuint *)&wid->textures[i]);
}
}
#endif
}
Free(wid->surfaces);
Free(wid->surfaceFlags);
#ifdef HAVE_OPENGL
if (agView->opengl) {
Free(wid->textures);
Free(wid->texcoords);
Free(wid->textureGC);
}
#endif
}
/*
* Perform a software blit from a source surface to the display, at
* coordinates relative to the widget, using clipping.
*
* Only safe to call from rendering context.
* XXX glDrawPixels() is probably faster.
*/
void
AG_WidgetBlit(void *p, AG_Surface *srcsu, int wx, int wy)
{
AG_Widget *wid = p;
int x = wid->rView.x1 + wx;
int y = wid->rView.y1 + wy;
#ifdef HAVE_OPENGL
if (agView->opengl) {
GLuint texture;
GLfloat texcoord[4];
int alpha = (srcsu->flags & (AG_SRCALPHA|AG_SRCCOLORKEY));
GLboolean blend_sv;
GLint blend_sfactor, blend_dfactor;
GLfloat texenvmode;
texture = AG_SurfaceTexture(srcsu, texcoord);
glGetTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &texenvmode);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
if (alpha) {
glGetBooleanv(GL_BLEND, &blend_sv);
glGetIntegerv(GL_BLEND_SRC, &blend_sfactor);
glGetIntegerv(GL_BLEND_DST, &blend_dfactor);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
glBindTexture(GL_TEXTURE_2D, texture);
glBegin(GL_TRIANGLE_STRIP);
{
glTexCoord2f(texcoord[0], texcoord[1]);
glVertex2i(x, y);
glTexCoord2f(texcoord[2], texcoord[1]);
glVertex2i(x+srcsu->w, y);
glTexCoord2f(texcoord[0], texcoord[3]);
glVertex2i(x, y+srcsu->h);
glTexCoord2f(texcoord[2], texcoord[3]);
glVertex2i(x+srcsu->w, y+srcsu->h);
}
glEnd();
glBindTexture(GL_TEXTURE_2D, 0);
glDeleteTextures(1, &texture);
if (alpha) {
if (blend_sv) {
glEnable(GL_BLEND);
} else {
glDisable(GL_BLEND);
}
glBlendFunc(blend_sfactor, blend_dfactor);
}
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, texenvmode);
} else
#endif /* HAVE_OPENGL */
{
AG_SurfaceBlit(srcsu, NULL, agView->v, x,y);
}
}
#ifdef HAVE_OPENGL
static __inline__ void
UpdateTexture(AG_Widget *wid, int name)
{
if (wid->textures[name] == 0) {
wid->textures[name] = AG_SurfaceTexture(wid->surfaces[name],
&wid->texcoords[name*4]);
} else if (wid->surfaceFlags[name] & AG_WIDGET_SURFACE_REGEN) {
wid->surfaceFlags[name] &= ~(AG_WIDGET_SURFACE_REGEN);
AG_UpdateTexture(wid->surfaces[name], wid->textures[name]);
}
}
#endif
/*
* Perform a hardware or software blit from a mapped surface to the display
* at coordinates relative to the widget, using clipping.
*
* Only safe to call from rendering context.
*/
void
AG_WidgetBlitFrom(void *pDst, void *pSrc, int name, AG_Rect *rSrc,
int wx, int wy)
{
AG_Widget *wDst = pDst;
AG_Widget *wSrc = pSrc;
AG_Surface *su = wSrc->surfaces[name];
int x, y;
if (name == -1 || su == NULL)
return;
x = wDst->rView.x1 + wx;
y = wDst->rView.y1 + wy;
#ifdef HAVE_OPENGL
if (agView->opengl) {
GLfloat tmptexcoord[4];
GLfloat *texcoord;
GLboolean blend_sv;
GLint blend_sfactor, blend_dfactor;
GLfloat texenvmode;
int alpha = su->flags & (AG_SRCALPHA|AG_SRCCOLORKEY);
UpdateTexture(wSrc, name);
if (rSrc == NULL) {
texcoord = &wSrc->texcoords[name*4];
} else {
texcoord = &tmptexcoord[0];
texcoord[0] = (GLfloat)rSrc->x/PowOf2i(rSrc->x);
texcoord[1] = (GLfloat)rSrc->y/PowOf2i(rSrc->y);
texcoord[2] = (GLfloat)rSrc->w/PowOf2i(rSrc->w);
texcoord[3] = (GLfloat)rSrc->h/PowOf2i(rSrc->h);
}
glGetTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &texenvmode);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
if (alpha) {
glGetBooleanv(GL_BLEND, &blend_sv);
glGetIntegerv(GL_BLEND_SRC, &blend_sfactor);
glGetIntegerv(GL_BLEND_DST, &blend_dfactor);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
glBindTexture(GL_TEXTURE_2D, wSrc->textures[name]);
glBegin(GL_TRIANGLE_STRIP);
{
glTexCoord2f(texcoord[0], texcoord[1]);
glVertex2i(x, y);
glTexCoord2f(texcoord[2], texcoord[1]);
glVertex2i(x+su->w, y);
glTexCoord2f(texcoord[0], texcoord[3]);
glVertex2i(x, y+su->h);
glTexCoord2f(texcoord[2], texcoord[3]);
glVertex2i(x+su->w, y+su->h);
}
glEnd();
glBindTexture(GL_TEXTURE_2D, 0);
if (alpha) {
if (blend_sv) {
glEnable(GL_BLEND);
} else {
glDisable(GL_BLEND);
}
glBlendFunc(blend_sfactor, blend_dfactor);
}
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, texenvmode);
} else
#endif /* HAVE_OPENGL */
{
AG_SurfaceBlit(su, rSrc, agView->v, x,y);
#ifdef DEBUG_CLIPPING
{
AG_Rect rClip;
rClip.x = agView->v->clip_rect.x - wDst->rView.x1;
rClip.y = agView->v->clip_rect.y - wDst->rView.y1;
rClip.w = agView->v->clip_rect.w;
rClip.h = agView->v->clip_rect.h;
AG_DrawRectOutline(wDst, rClip,
AG_MapRGB(agVideoFmt,0,0,0));
}
#endif
}
}
#ifdef HAVE_OPENGL
/*
* OpenGL-only version of AG_WidgetBlit() without explicit source or
* destination rectangle parameter.
*
* Only safe to call from rendering context.
* XXX glDrawPixels() is probably faster.
*/
void
AG_WidgetBlitGL(void *pWidget, AG_Surface *su, float w, float h)
{
GLuint texname;
GLfloat texcoord[4];
GLboolean blend_sv;
GLint blend_sfactor, blend_dfactor;
GLfloat texenvmode;
int alpha = su->flags & (AG_SRCALPHA|AG_SRCCOLORKEY);
float w2 = w/2.0f;
float h2 = h/2.0f;
texname = AG_SurfaceTexture(su, texcoord);
glGetTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &texenvmode);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
if (alpha) {
glGetBooleanv(GL_BLEND, &blend_sv);
glGetIntegerv(GL_BLEND_SRC, &blend_sfactor);
glGetIntegerv(GL_BLEND_DST, &blend_dfactor);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
glBindTexture(GL_TEXTURE_2D, texname);
glBegin(GL_TRIANGLE_STRIP);
{
glTexCoord2f(texcoord[0], texcoord[1]);
glVertex2f(w2, h2);
glTexCoord2f(texcoord[2], texcoord[1]);
glVertex2f(-w2, h2);
glTexCoord2f(texcoord[0], texcoord[3]);
glVertex2f(w2, -h2);
glTexCoord2f(texcoord[2], texcoord[3]);
glVertex2f(-w2, -h2);
}
glEnd();
glBindTexture(GL_TEXTURE_2D, 0);
glDeleteTextures(1, &texname);
if (alpha) {
if (blend_sv) {
glEnable(GL_BLEND);
} else {
glDisable(GL_BLEND);
}
glBlendFunc(blend_sfactor, blend_dfactor);
}
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, texenvmode);
}
/*
* OpenGL-only version of AG_WidgetBlitSurface() without explicit
* source or destination rectangle parameter.
*/
void
AG_WidgetBlitSurfaceGL(void *pWidget, int name, float w, float h)
{
AG_Widget *wid = pWidget;
GLuint texname;
GLfloat *texcoord;
GLboolean blend_sv;
GLint blend_sfactor, blend_dfactor;
GLfloat texenvmode;
AG_Surface *su = wid->surfaces[name];
int alpha = su->flags & (AG_SRCALPHA|AG_SRCCOLORKEY);
float w2 = w/2.0f;
float h2 = h/2.0f;
UpdateTexture(wid, name);
texname = wid->textures[name];
texcoord = &wid->texcoords[name*4];
glGetTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &texenvmode);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
if (alpha) {
glGetBooleanv(GL_BLEND, &blend_sv);
glGetIntegerv(GL_BLEND_SRC, &blend_sfactor);
glGetIntegerv(GL_BLEND_DST, &blend_dfactor);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
glBindTexture(GL_TEXTURE_2D, texname);
glBegin(GL_TRIANGLE_STRIP);
{
glTexCoord2f(texcoord[0], texcoord[1]);
glVertex2f(w2, h2);
glTexCoord2f(texcoord[2], texcoord[1]);
glVertex2f(-w2, h2);
glTexCoord2f(texcoord[0], texcoord[3]);
glVertex2f(w2, -h2);
glTexCoord2f(texcoord[2], texcoord[3]);
glVertex2f(-w2, -h2);
}
glEnd();
glBindTexture(GL_TEXTURE_2D, 0);
if (alpha) {
if (blend_sv) {
glEnable(GL_BLEND);
} else {
glDisable(GL_BLEND);
}
glBlendFunc(blend_sfactor, blend_dfactor);
}
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, texenvmode);
}
/*
* OpenGL-only version of AG_WidgetBlitSurface() without explicit
* source or destination rectangle parameter (flipped).
*/
void
AG_WidgetBlitSurfaceFlippedGL(void *pWidget, int name, float w, float h)
{
AG_Widget *wid = pWidget;
GLuint texname;
GLfloat *texcoord;
GLboolean blend_sv;
GLint blend_sfactor, blend_dfactor;
GLfloat texenvmode;
AG_Surface *su = wid->surfaces[name];
int alpha = su->flags & (AG_SRCALPHA|AG_SRCCOLORKEY);
UpdateTexture(wid, name);
texname = wid->textures[name];
texcoord = &wid->texcoords[name*4];
glGetTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &texenvmode);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
if (alpha) {
glGetBooleanv(GL_BLEND, &blend_sv);
glGetIntegerv(GL_BLEND_SRC, &blend_sfactor);
glGetIntegerv(GL_BLEND_DST, &blend_dfactor);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
glBindTexture(GL_TEXTURE_2D, texname);
glBegin(GL_TRIANGLE_STRIP);
{
glTexCoord2f(texcoord[2], texcoord[1]);
glVertex2f(0.0, 0.0);
glTexCoord2f(texcoord[0], texcoord[1]);
glVertex2f((GLfloat)w, 0.0);
glTexCoord2f(texcoord[2], texcoord[3]);
glVertex2f(0.0, (GLfloat)h);
glTexCoord2f(texcoord[0], texcoord[3]);
glVertex2f((GLfloat)w, (GLfloat)h);
}
glEnd();
glBindTexture(GL_TEXTURE_2D, 0);
if (alpha) {
if (blend_sv) {
glEnable(GL_BLEND);
} else {
glDisable(GL_BLEND);
}
glBlendFunc(blend_sfactor, blend_dfactor);
}
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, texenvmode);
}
/* Emulate 32-bit putpixel behavior */
void
AG_WidgetPutPixel32_GL(void *p, int x, int y, Uint32 color)
{
Uint8 r, g, b;
AG_GetRGB(color, agVideoFmt, &r,&g,&b);
glBegin(GL_POINTS);
glColor3ub(r, g, b);
glVertex2s(x, y);
glEnd();
}
/* Emulate RGB putpixel behavior */
void
AG_WidgetPutPixelRGB_GL(void *p, int x, int y, Uint8 r, Uint8 g, Uint8 b)
{
glBegin(GL_POINTS);
glColor3ub(r, g, b);
glVertex2s(x, y);
glEnd();
}
/*
* Release the OpenGL resources associated with a widget.
* GL lock must be held.
*/
void
AG_WidgetFreeResourcesGL(AG_Widget *wid)
{
AG_ObjectLock(wid);
glDeleteTextures(wid->nsurfaces, (GLuint *)wid->textures);
memset(wid->textures, 0, wid->nsurfaces*sizeof(Uint));
AG_ObjectUnlock(wid);
}
/*
* Regenerate the OpenGL textures associated with a widget.
* GL lock must be held.
*/
void
AG_WidgetRegenResourcesGL(AG_Widget *wid)
{
Uint i;
AG_ObjectLock(wid);
for (i = 0; i < wid->nsurfaces; i++) {
if (wid->surfaces[i] != NULL) {
wid->textures[i] = AG_SurfaceTexture(wid->surfaces[i],
&wid->texcoords[i*4]);
} else {
wid->textures[i] = 0;
}
}
AG_ObjectUnlock(wid);
}
#endif /* HAVE_OPENGL */
/* Acquire widget focus */
static __inline__ void
FocusWidget(AG_Widget *w)
{
AG_Window *winParent;
w->flags |= AG_WIDGET_FOCUSED;
if ((winParent = AG_ParentWindow(w)) != NULL) {
AG_PostEvent(winParent, w, "widget-gainfocus", NULL);
winParent->nFocused++;
}
}
/* Give up widget focus */
static __inline__ void
UnfocusWidget(AG_Widget *w)
{
AG_Window *winParent;
w->flags &= ~(AG_WIDGET_FOCUSED);
if ((winParent = AG_ParentWindow(w)) != NULL) {
AG_PostEvent(winParent, w, "widget-lostfocus", NULL);
winParent->nFocused--;
}
}
/* Remove focus from a widget and its children. */
void
AG_WidgetUnfocus(void *p)
{
AG_Widget *wid = p, *cwid;
AG_ObjectLock(wid);
if (wid->focusFwd != NULL) {
AG_ObjectLock(wid->focusFwd);
if (wid->focusFwd->flags & AG_WIDGET_FOCUSED) {
UnfocusWidget(wid->focusFwd);
}
AG_ObjectUnlock(wid->focusFwd);
}
if (wid->flags & AG_WIDGET_FOCUSED) {
UnfocusWidget(wid);
}
OBJECT_FOREACH_CHILD(cwid, wid, ag_widget) {
AG_WidgetUnfocus(cwid);
}
AG_ObjectUnlock(wid);
}
/*
* Find the parent window of a widget. Result is only valid as long as
* the View VFS is locked.
*/
AG_Window *
AG_ParentWindow(void *p)
{
AG_Widget *wid = p;
AG_Widget *pwid = wid;
if (AG_OfClass(wid, "AG_Widget:AG_Window:*"))
return ((AG_Window *)wid);
AG_LockVFS(agView);
while ((pwid = OBJECT(pwid)->parent) != NULL) {
if (AG_OfClass(pwid, "AG_Widget:AG_Window:*"))
break;
}
AG_UnlockVFS(agView);
return ((AG_Window *)pwid);
}
/* Move the focus over a widget (and its parents). */
void
AG_WidgetFocus(void *p)
{
AG_Widget *wid = p, *wParent = wid;
AG_Window *win;
AG_LockVFS(agView);
AG_ObjectLock(wid);
/* Remove any existing focus. */
TAILQ_FOREACH(win, &agView->windows, windows) {
if (win->nFocused > 0)
AG_WidgetUnfocus(win);
}
/* Set the focus flag on the widget and its parents. */
do {
if (AG_OfClass(wParent, "AG_Widget:AG_Window:*")) {
AG_WindowFocus(AGWINDOW(wParent));
break;
}
AG_ObjectLock(wParent);
if ((wParent->flags & AG_WIDGET_FOCUSED) == 0) {
if (wParent->focusFwd != NULL &&
!(wParent->focusFwd->flags & AG_WIDGET_FOCUSED)) {
FocusWidget(wParent->focusFwd);
}
FocusWidget(wParent);
}
AG_ObjectUnlock(wParent);
} while ((wParent = OBJECT(wParent)->parent) != NULL);
AG_ObjectUnlock(wid);
AG_UnlockVFS(agView);
}
/*
* Evaluate whether a given widget is at least partially visible.
* The Widget and View must be locked.
*/
/* TODO optimize on a per window basis */
static __inline__ int
OccultedWidget(AG_Widget *wid)
{
AG_Window *wParent;
AG_Window *w;
if ((wParent = AG_ObjectFindParent(wid, NULL, "AG_Widget:AG_Window")) == NULL ||
(w = TAILQ_NEXT(wParent, windows)) == NULL) {
return (0);
}
for (; w != TAILQ_END(&agView->windows); w = TAILQ_NEXT(w, windows)) {
if (w->visible &&
wid->rView.x1 > WIDGET(w)->x &&
wid->rView.y1 > WIDGET(w)->y &&
wid->rView.x2 < WIDGET(w)->x+WIDGET(w)->w &&
wid->rView.y2 < WIDGET(w)->y+WIDGET(w)->h)
return (1);
}
return (0);
}
void
AG_SetCursor(int cursor)
{
AG_LockVFS(agView);
if (agCursorToSet == NULL) {
agCursorToSet = agCursors[cursor];
}
AG_UnlockVFS(agView);
}
void
AG_UnsetCursor(void)
{
AG_LockVFS(agView);
agCursorToSet = agDefaultCursor;
AG_UnlockVFS(agView);
}
/*
* Push a clipping rectangle onto the clipping rectangle stack.
* Must be invoked from GUI rendering context.
*/
void
AG_PushClipRect(void *obj, AG_Rect r)
{
AG_ClipRect *cr, *crPrev;
r.x += WIDGET(obj)->rView.x1;
r.y += WIDGET(obj)->rView.y1;
agClipRects = Realloc(agClipRects, (agClipRectCount+1) *
sizeof(AG_ClipRect));
crPrev = &agClipRects[agClipRectCount-1];
cr = &agClipRects[agClipRectCount++];
#ifdef HAVE_OPENGL
if (agView->opengl) {
cr->eqns[0][0] = 1.0;
cr->eqns[0][1] = 0.0;
cr->eqns[0][2] = 0.0;
cr->eqns[0][3] = MIN(crPrev->eqns[0][3], -(double)(r.x));
glClipPlane(GL_CLIP_PLANE0, (const GLdouble *)&cr->eqns[0]);
cr->eqns[1][0] = 0.0;
cr->eqns[1][1] = 1.0;
cr->eqns[1][2] = 0.0;
cr->eqns[1][3] = MIN(crPrev->eqns[1][3], -(double)(r.y));
glClipPlane(GL_CLIP_PLANE1, (const GLdouble *)&cr->eqns[1]);
cr->eqns[2][0] = -1.0;
cr->eqns[2][1] = 0.0;
cr->eqns[2][2] = 0.0;
cr->eqns[2][3] = MIN(crPrev->eqns[2][3], (double)(r.x+r.w));
glClipPlane(GL_CLIP_PLANE2, (const GLdouble *)&cr->eqns[2]);
cr->eqns[3][0] = 0.0;
cr->eqns[3][1] = -1.0;
cr->eqns[3][2] = 0.0;
cr->eqns[3][3] = MIN(crPrev->eqns[3][3], (double)(r.y+r.h));
glClipPlane(GL_CLIP_PLANE3, (const GLdouble *)&cr->eqns[3]);
} else
#endif
{
cr->r = AG_RectIntersect(&crPrev->r, &r);
AG_SetClipRect(agView->v, &cr->r);
}
}
/*
* Pop a clipping rectangle off the clipping rectangle stack.
* Must be invoked from GUI rendering context.
*/
void
AG_PopClipRect(void)
{
AG_ClipRect *cr;
#ifdef AG_DEBUG
if (agClipRectCount < 1)
AG_FatalError("PopClipRect() without PushClipRect()");
#endif
cr = &agClipRects[agClipRectCount-2];
agClipRectCount--;
#ifdef HAVE_OPENGL
if (agView->opengl) {
glClipPlane(GL_CLIP_PLANE0, (const GLdouble *)&cr->eqns[0]);
glClipPlane(GL_CLIP_PLANE1, (const GLdouble *)&cr->eqns[1]);
glClipPlane(GL_CLIP_PLANE2, (const GLdouble *)&cr->eqns[2]);
glClipPlane(GL_CLIP_PLANE3, (const GLdouble *)&cr->eqns[3]);
} else
#endif
{
AG_SetClipRect(agView->v, &cr->r);
}
}
/*
* Render a widget to the display.
* Must be invoked from GUI rendering context.
*/
void
AG_WidgetDraw(void *p)
{
AG_Widget *wid = p;
AG_ObjectLock(wid);
#ifdef HAVE_OPENGL
if (wid->textureGC > 0)
DeleteQueuedTextures(wid);
#endif
if (!(wid->flags & (AG_WIDGET_HIDE|AG_WIDGET_UNDERSIZE)) &&
!OccultedWidget(wid) &&
WIDGET_OPS(wid)->draw != NULL) {
WIDGET_OPS(wid)->draw(wid);
#ifdef AG_DEBUG
if (wid->flags & AG_WIDGET_DEBUG_RSENS) {
static Uint8 c1[4] = { 200, 0, 0, 25 };
AG_Rect r = AG_Rect2ToRect(wid->rSens);
/* if (r.x != wid->rView.x1 || r.y != wid->rView.y1 || */
/* r.w != wid->rView.w || r.h != wid->rView.h) { */
r.x -= wid->rView.x1;
r.y -= wid->rView.y1;
AG_DrawRectBlended(wid, r,
c1, AG_ALPHA_SRC);
AG_DrawRectOutline(wid, r,
AG_MapRGB(agVideoFmt,100,0,0));
/* } */
}
#endif /* AG_DEBUG */
}
AG_ObjectUnlock(wid);
}
static void
SizeRequest(void *p, AG_SizeReq *r)
{
r->w = 0;
r->h = 0;
}
static int
SizeAllocate(void *p, const AG_SizeAlloc *a)
{
return (0);
}
void
AG_WidgetSizeReq(void *w, AG_SizeReq *r)
{
r->w = 0;
r->h = 0;
AG_ObjectLock(w);
if (WIDGET_OPS(w)->size_request != NULL) {
WIDGET_OPS(w)->size_request(w, r);
}
AG_ObjectUnlock(w);
}
int
AG_WidgetSizeAlloc(void *obj, AG_SizeAlloc *a)
{
AG_Widget *w = obj;
AG_ObjectLock(w);
if (a->w <= 0 || a->h <= 0) {
a->w = 0;
a->h = 0;
w->flags |= AG_WIDGET_UNDERSIZE;
}
w->x = a->x;
w->y = a->y;
w->w = a->w;
w->h = a->h;
if (WIDGET_OPS(w)->size_allocate != NULL) {
if (WIDGET_OPS(w)->size_allocate(w, a) == -1) {
w->flags |= AG_WIDGET_UNDERSIZE;
goto fail;
} else {
w->flags &= ~(AG_WIDGET_UNDERSIZE);
}
}
AG_ObjectUnlock(w);
return (0);
fail:
AG_ObjectUnlock(w);
return (-1);
}
/*
* Blend with the pixel at widget-relative x,y coordinates, with clipping
* to display area.
*
* Must be invoked from GUI rendering context. In SDL mode, the display
* surface must be locked.
*/
void
AG_WidgetBlendPixelRGBA(void *p, int x, int y, Uint8 c[4], AG_BlendFn fn)
{
AG_Widget *wid = p;
#ifdef HAVE_OPENGL
if (agView->opengl) {
GLboolean svBlendBit;
GLint svBlendSrc, svBlendDst;
glGetBooleanv(GL_BLEND, &svBlendBit);
glGetIntegerv(GL_BLEND_SRC, &svBlendSrc);
glGetIntegerv(GL_BLEND_DST, &svBlendDst);
glEnable(GL_BLEND);
glBlendFunc(GL_DST_ALPHA, GL_ZERO);
switch (fn) {
case AG_ALPHA_OVERLAY:
/* XXX TODO emulate using glReadPixels()? */
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
break;
case AG_ALPHA_SRC:
glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA);
break;
case AG_ALPHA_DST:
glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA);
break;
case AG_ALPHA_ONE_MINUS_DST:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA);
break;
case AG_ALPHA_ONE_MINUS_SRC:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
break;
}
glBegin(GL_POINTS);
glColor4ubv((GLubyte *)c);
glVertex2s(wid->rView.x1 + x,
wid->rView.y1 + y);
glEnd();
if (!svBlendBit) {
glDisable(GL_BLEND);
}
glBlendFunc(GL_SRC_ALPHA, svBlendSrc);
glBlendFunc(GL_DST_ALPHA, svBlendDst);
} else
#endif /* HAVE_OPENGL */
{
AG_BLEND_RGBA2_CLIPPED(agView->v,
wid->rView.x1 + x,
wid->rView.y1 + y,
c[0], c[1], c[2], c[3], fn);
}
}
/*
* Test whether view coordinates x,y lie in widget's sensitivity rectangle
* (intersected against those of all parent widgets).
*/
int
AG_WidgetSensitive(void *obj, int x, int y)
{
AG_Widget *wt = WIDGET(obj);
AG_Widget *wtParent = wt;
AG_Rect2 rx = wt->rSens;
while ((wtParent = OBJECT(wtParent)->parent) != NULL) {
if (AG_ObjectIsClass(wtParent, "AG_Widget:AG_Window:*")) {
break;
}
rx = AG_RectIntersect2(&rx, &wtParent->rSens);
}
return AG_RectInside2(&rx, x,y);
}
/*
* Post a mousemotion event to widgets that either hold focus or have the
* UNFOCUSED_MOTION flag set. View must be locked.
*/
void
AG_WidgetMouseMotion(AG_Window *win, AG_Widget *wid, int x, int y,
int xrel, int yrel, int state)
{
AG_Widget *cwid;
AG_ObjectLock(wid);
if ((AG_WindowIsFocused(win) && AG_WidgetFocused(wid)) ||
(wid->flags & AG_WIDGET_UNFOCUSED_MOTION)) {
AG_PostEvent(NULL, wid, "window-mousemotion",
"%i,%i,%i,%i,%i",
x - wid->rView.x1,
y - wid->rView.y1,
xrel,
yrel,
state);
if (wid->flags & AG_WIDGET_PRIO_MOTION)
goto out;
}
OBJECT_FOREACH_CHILD(cwid, wid, ag_widget)
AG_WidgetMouseMotion(win, cwid, x, y, xrel, yrel, state);
out:
AG_ObjectUnlock(wid);
}
/*
* Post a mousebuttonup event to widgets that either hold focus or have the
* UNFOCUSED_BUTTONUP flag set. View must be locked.
*/
void
AG_WidgetMouseButtonUp(AG_Window *win, AG_Widget *wid, int button,
int x, int y)
{
AG_Widget *cwid;
AG_ObjectLock(wid);
if ((AG_WindowIsFocused(win) && AG_WidgetFocused(wid)) ||
(wid->flags & AG_WIDGET_UNFOCUSED_BUTTONUP)) {
AG_PostEvent(NULL, wid, "window-mousebuttonup", "%i,%i,%i",
button,
x - wid->rView.x1,
y - wid->rView.y1);
}
OBJECT_FOREACH_CHILD(cwid, wid, ag_widget) {
AG_WidgetMouseButtonUp(win, cwid, button, x, y);
}
AG_ObjectUnlock(wid);
}
/* Process a mousebuttondown event. View must be locked. */
int
AG_WidgetMouseButtonDown(AG_Window *win, AG_Widget *wid, int button,
int x, int y)
{
AG_Widget *cwid;
AG_Event *ev;
AG_ObjectLock(wid);
/* Search for a better match. */
OBJECT_FOREACH_CHILD(cwid, wid, ag_widget) {
if (AG_WidgetMouseButtonDown(win, cwid, button, x, y))
goto match;
}
if (!AG_WidgetSensitive(wid, x, y)) {
goto out;
}
TAILQ_FOREACH(ev, &OBJECT(wid)->events, events) {
if (strcmp(ev->name, "window-mousebuttondown") == 0)
break;
}
if (ev != NULL) {
AG_PostEvent(NULL, wid, "window-mousebuttondown", "%i,%i,%i",
button,
x - wid->rView.x1,
y - wid->rView.y1);
goto match;
}
out:
AG_ObjectUnlock(wid);
return (0);
match:
AG_ObjectUnlock(wid);
return (1);
}
/*
* Post a keyup event to widgets with the UNFOCUSED_KEYUP flag set.
* View must be locked.
*/
void
AG_WidgetUnfocusedKeyUp(AG_Widget *wid, int ksym, int kmod, int unicode)
{
AG_Widget *cwid;
AG_ObjectLock(wid);
if (wid->flags & AG_WIDGET_UNFOCUSED_KEYUP) {
AG_PostEvent(NULL, wid, "window-keyup", "%i, %i, %i",
ksym, kmod, unicode);
}
OBJECT_FOREACH_CHILD(cwid, wid, ag_widget) {
AG_WidgetUnfocusedKeyUp(cwid, ksym, kmod, unicode);
}
AG_ObjectUnlock(wid);
}
/*
* Post a keydown event to widgets with the UNFOCUSED_KEYDOWN flag set.
* View must be locked.
*/
void
AG_WidgetUnfocusedKeyDown(AG_Widget *wid, int ksym, int kmod, int unicode)
{
AG_Widget *cwid;
AG_ObjectLock(wid);
if (wid->flags & AG_WIDGET_UNFOCUSED_KEYDOWN) {
AG_PostEvent(NULL, wid, "window-keydown", "%i, %i, %i",
ksym, kmod, unicode);
}
OBJECT_FOREACH_CHILD(cwid, wid, ag_widget) {
AG_WidgetUnfocusedKeyDown(cwid, ksym, kmod, unicode);
}
AG_ObjectUnlock(wid);
}
/*
* Search for a focused widget inside a window. Return value is only valid
* as long as the View VFS is locked.
*/
AG_Widget *
AG_WidgetFindFocused(void *p)
{
AG_Widget *wid = p;
AG_Widget *cwid, *fwid;
AG_LockVFS(agView);
AG_ObjectLock(wid);
if (!AG_OfClass(wid, "AG_Widget:AG_Window:*") &&
(wid->flags & AG_WIDGET_FOCUSED) == 0) {
goto fail;
}
/* Search for a better match. */
OBJECT_FOREACH_CHILD(cwid, wid, ag_widget) {
if ((fwid = AG_WidgetFindFocused(cwid)) != NULL) {
AG_ObjectUnlock(wid);
AG_UnlockVFS(agView);
return (fwid);
}
}
AG_ObjectUnlock(wid);
AG_UnlockVFS(agView);
return (wid);
fail:
AG_ObjectUnlock(wid);
AG_UnlockVFS(agView);
return (NULL);
}
/* Compute the absolute view coordinates of a widget and its descendents. */
void
AG_WidgetUpdateCoords(void *obj, int x, int y)
{
AG_Widget *wid = obj, *chld;
AG_Rect2 rPrev;
AG_LockVFS(wid);
AG_ObjectLock(wid);
rPrev = wid->rView;
wid->rView.x1 = x;
wid->rView.y1 = y;
wid->rView.w = wid->w;
wid->rView.h = wid->h;
wid->rView.x2 = x + wid->w;
wid->rView.y2 = y + wid->h;
wid->rSens.x1 = x;
wid->rSens.y1 = y;
wid->rSens.w = wid->w;
wid->rSens.h = wid->h;
wid->rSens.x2 = x + wid->w;
wid->rSens.y2 = y + wid->h;
if (AG_RectCompare2(&wid->rView, &rPrev) != 0) {
AG_PostEvent(NULL, wid, "widget-moved", NULL);
}
OBJECT_FOREACH_CHILD(chld, wid, ag_widget) {
AG_WidgetUpdateCoords(chld,
wid->rView.x1 + chld->x,
wid->rView.y1 + chld->y);
}
AG_ObjectUnlock(wid);
AG_UnlockVFS(wid);
}
/* Parse a generic size specification. */
enum ag_widget_sizespec
AG_WidgetParseSizeSpec(const char *input, int *w)
{
char spec[AG_SIZE_SPEC_MAX], *p;
size_t len;
Strlcpy(spec, input, sizeof(spec));
len = strlen(spec);
if (len == 0) { goto syntax; }
p = &spec[len-1];
switch (*p) {
case '-':
*w = 0;
return (AG_WIDGET_FILL);
case '%':
*p = '\0';
*w = (int)strtol(spec, NULL, 10);
return (AG_WIDGET_PERCENT);
case '>':
if (spec[0] != '<') { goto syntax; }
*p = '\0';
AG_TextSize(&spec[1], w, NULL);
return (AG_WIDGET_STRINGLEN);
case 'x':
if (p > &spec[0] && p[-1] != 'p') { goto syntax; }
p[-1] = '\0';
*w = (int)strtol(spec, NULL, 10);
return (AG_WIDGET_PIXELS);
default:
break;
}
syntax:
Verbose("Warning: Bad SizeSpec: \"%s\"\n", input);
*w = 0;
return (AG_WIDGET_BAD_SPEC);
}
int
AG_WidgetScrollDelta(Uint32 *t1)
{
Uint32 t2 = SDL_GetTicks();
int delta;
if (*t1 != 0 && ((delta = (t2 - *t1))) < 250) {
return (((250-delta)<<3)>>9);
}
*t1 = SDL_GetTicks();
return (1);
}
/*
* Raise `widget-shown' on a widget and its children.
* Used by containers such as Notebook.
*/
void
AG_WidgetShownRecursive(void *p)
{
AG_Widget *wid = p;
AG_Widget *chld;
AG_LockVFS(agView);
AG_ObjectLock(wid);
OBJECT_FOREACH_CHILD(chld, wid, ag_widget) {
AG_WidgetShownRecursive(chld);
}
AG_PostEvent(NULL, wid, "widget-shown", NULL);
AG_ObjectUnlock(wid);
AG_UnlockVFS(agView);
}
/*
* Raise `widget-hidden' on a widget and its children.
* Used by containers such as Notebook.
*/
void
AG_WidgetHiddenRecursive(void *p)
{
AG_Widget *wid = p;
AG_Widget *chld;
AG_LockVFS(agView);
AG_ObjectLock(wid);
OBJECT_FOREACH_CHILD(chld, wid, ag_widget) {
AG_WidgetHiddenRecursive(chld);
}
AG_PostEvent(NULL, wid, "widget-hidden", NULL);
AG_ObjectUnlock(wid);
AG_UnlockVFS(agView);
}
static void *
FindAtPoint(AG_Widget *parent, const char *type, int x, int y)
{
AG_Widget *chld;
void *p;
OBJECT_FOREACH_CHILD(chld, parent, ag_widget) {
if ((p = FindAtPoint(chld, type, x, y)) != NULL)
return (p);
}
if (!(parent->flags & AG_WIDGET_HIDE) &&
AG_OfClass(parent, type) &&
AG_WidgetArea(parent, x, y)) {
return (parent);
}
return (NULL);
}
/*
* Search for widgets of the specified class enclosing the given point.
* Result is only accurate as long as the View VFS is locked.
*/
void *
AG_WidgetFindPoint(const char *type, int x, int y)
{
AG_Window *win;
void *p;
AG_LockVFS(agView);
TAILQ_FOREACH_REVERSE(win, &agView->windows, ag_windowq, windows) {
if ((p = FindAtPoint(WIDGET(win), type, x, y)) != NULL) {
AG_UnlockVFS(agView);
return (p);
}
}
AG_UnlockVFS(agView);
return (NULL);
}
static void *
FindRectOverlap(AG_Widget *parent, const char *type, int x, int y, int w, int h)
{
AG_Widget *chld;
void *p;
OBJECT_FOREACH_CHILD(chld, parent, ag_widget) {
if ((p = FindRectOverlap(chld, type, x,y,w,h)) != NULL)
return (p);
}
if (AG_OfClass(parent, type) &&
!(x+w < parent->rView.x1 || x > parent->rView.x2 ||
y+w < parent->rView.y1 || y > parent->rView.y2)) {
return (parent);
}
return (NULL);
}
/*
* Search for widgets of the specified class enclosing the given rectangle.
* Result is only accurate as long as the View VFS is locked.
*/
void *
AG_WidgetFindRect(const char *type, int x, int y, int w, int h)
{
AG_Window *win;
void *p;
AG_LockVFS(agView);
TAILQ_FOREACH_REVERSE(win, &agView->windows, ag_windowq, windows) {
if ((p = FindRectOverlap(WIDGET(win), type, x,y,w,h)) != NULL) {
AG_UnlockVFS(agView);
return (p);
}
}
AG_UnlockVFS(agView);
return (NULL);
}
/* Generic inherited draw() routine. */
void
AG_WidgetInheritDraw(void *obj)
{
WIDGET_SUPER_OPS(obj)->draw(obj);
}
/* Generic inherited size_request() routine. */
void
AG_WidgetInheritSizeRequest(void *obj, AG_SizeReq *r)
{
WIDGET_SUPER_OPS(obj)->size_request(obj, r);
}
/* Generic inherited size_allocate() routine. */
int
AG_WidgetInheritSizeAllocate(void *obj, const AG_SizeAlloc *a)
{
return WIDGET_SUPER_OPS(obj)->size_allocate(obj, a);
}
AG_WidgetClass agWidgetClass = {
{
"Agar(Widget)",
sizeof(AG_Widget),
{ 0,0 },
Init,
NULL, /* free */
Destroy,
NULL, /* load */
NULL, /* save */
NULL /* edit */
},
NULL, /* draw */
SizeRequest,
SizeAllocate
};