/*
* Copyright (c) 2002-2007 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 "button.h"
#include "window.h"
#include "primitive.h"
#include "label.h"
#include
static int GetState(AG_Button *, AG_Variable *, void *);
static void SetState(AG_Variable *, void *, int);
static void mousemotion(AG_Event *);
static void mousebuttonup(AG_Event *);
static void mousebuttondown(AG_Event *);
static void keyup(AG_Event *);
static void keydown(AG_Event *);
AG_Button *
AG_ButtonNew(void *parent, Uint flags, const char *fmt, ...)
{
AG_Button *bu;
va_list args;
bu = Malloc(sizeof(AG_Button));
AG_ObjectInit(bu, &agButtonClass);
if (fmt != NULL) {
char text[AG_LABEL_MAX];
va_start(args, fmt);
Vsnprintf(text, sizeof(text), fmt, args);
va_end(args);
bu->lbl = AG_LabelNewString(bu, 0, text);
AG_LabelJustify(bu->lbl, bu->justify);
AG_LabelValign(bu->lbl, bu->valign);
}
bu->flags |= flags;
if (flags & AG_BUTTON_HFILL) { AG_ExpandHoriz(bu); }
if (flags & AG_BUTTON_VFILL) { AG_ExpandVert(bu); }
AG_ObjectAttach(parent, bu);
return (bu);
}
AG_Button *
AG_ButtonNewFn(void *parent, Uint flags, const char *caption, AG_EventFn fn,
const char *fmt, ...)
{
AG_Button *bu;
AG_Event *ev;
bu = AG_ButtonNew(parent, flags, caption);
ev = AG_SetEvent(bu, "button-pushed", fn, NULL);
AG_EVENT_GET_ARGS(ev, fmt);
return (bu);
}
AG_Button *
AG_ButtonNewInt(void *parent, Uint flags, const char *caption, int *v)
{
AG_Button *bu = AG_ButtonNew(parent, flags, caption);
AG_BindInt(bu, "state", v);
return (bu);
}
AG_Button *
AG_ButtonNewUint8(void *parent, Uint flags, const char *caption, Uint8 *v)
{
AG_Button *bu = AG_ButtonNew(parent, flags, caption);
AG_BindUint8(bu, "state", v);
return (bu);
}
AG_Button *
AG_ButtonNewUint16(void *parent, Uint flags, const char *caption, Uint16 *v)
{
AG_Button *bu = AG_ButtonNew(parent, flags, caption);
AG_BindUint16(bu, "state", v);
return (bu);
}
AG_Button *
AG_ButtonNewUint32(void *parent, Uint flags, const char *caption, Uint32 *v)
{
AG_Button *bu = AG_ButtonNew(parent, flags, caption);
AG_BindUint32(bu, "state", v);
return (bu);
}
AG_Button *
AG_ButtonNewFlag(void *parent, Uint flags, const char *caption,
Uint *p, Uint bitmask)
{
AG_Button *bu = AG_ButtonNew(parent, flags, caption);
AG_BindFlag(bu, "state", p, bitmask);
return (bu);
}
AG_Button *
AG_ButtonNewFlag8(void *parent, Uint flags, const char *caption,
Uint8 *p, Uint8 bitmask)
{
AG_Button *bu = AG_ButtonNew(parent, flags, caption);
AG_BindFlag8(bu, "state", p, bitmask);
return (bu);
}
AG_Button *
AG_ButtonNewFlag16(void *parent, Uint flags, const char *caption,
Uint16 *p, Uint16 bitmask)
{
AG_Button *bu = AG_ButtonNew(parent, flags, caption);
AG_BindFlag16(bu, "state", p, bitmask);
return (bu);
}
AG_Button *
AG_ButtonNewFlag32(void *parent, Uint flags, const char *caption,
Uint32 *p, Uint32 bitmask)
{
AG_Button *bu = AG_ButtonNew(parent, flags, caption);
AG_BindFlag32(bu, "state", p, bitmask);
return (bu);
}
static Uint32
ExpireRepeat(void *obj, Uint32 ival, void *arg)
{
AG_PostEvent(NULL, obj, "button-pushed", "%i", 1);
return (agMouseSpinIval);
}
static Uint32
ExpireDelay(void *obj, Uint32 ival, void *arg)
{
AG_Button *bu = obj;
AG_ScheduleTimeout(bu, &bu->repeat_to, agMouseSpinIval);
return (0);
}
static void
Init(void *obj)
{
AG_Button *bu = obj;
/* TODO replace the unfocused motion flag with a timer */
WIDGET(bu)->flags |= AG_WIDGET_FOCUSABLE|
AG_WIDGET_UNFOCUSED_MOTION|
AG_WIDGET_UNFOCUSED_BUTTONUP;
AG_BindInt(bu, "state", &bu->state);
bu->flags = 0;
bu->lbl = NULL;
bu->surface = -1;
bu->state = 0;
bu->justify = AG_TEXT_CENTER;
bu->valign = AG_TEXT_MIDDLE;
bu->lPad = 4;
bu->rPad = 4;
bu->tPad = 3;
bu->bPad = 3;
AG_SetTimeout(&bu->repeat_to, ExpireRepeat, NULL, 0);
AG_SetTimeout(&bu->delay_to, ExpireDelay, NULL, 0);
AG_SetEvent(bu, "window-mousebuttonup", mousebuttonup, NULL);
AG_SetEvent(bu, "window-mousebuttondown", mousebuttondown, NULL);
AG_SetEvent(bu, "window-mousemotion", mousemotion, NULL);
AG_SetEvent(bu, "window-keyup", keyup, NULL);
AG_SetEvent(bu, "window-keydown", keydown, NULL);
}
static void
SizeRequest(void *p, AG_SizeReq *r)
{
AG_Button *bu = p;
AG_SizeReq rLbl;
r->w = bu->lPad + bu->rPad;
r->h = bu->tPad + bu->bPad;
if (bu->surface != -1) {
r->w += WSURFACE(bu,bu->surface)->w;
r->h += WSURFACE(bu,bu->surface)->h;
} else {
if (bu->lbl != NULL) {
AG_WidgetSizeReq(bu->lbl, &rLbl);
r->w += rLbl.w;
}
r->h += agTextFontHeight;
}
}
static int
SizeAllocate(void *p, const AG_SizeAlloc *a)
{
AG_Button *bu = p;
AG_SizeAlloc aLbl;
if (a->w < 2 || a->h < 2) {
return (-1);
}
if (bu->lbl != NULL) {
aLbl.x = bu->lPad;
aLbl.y = bu->tPad;
aLbl.w = a->w - (bu->lPad+bu->rPad);
aLbl.h = a->h - (bu->tPad+bu->bPad);
AG_WidgetSizeAlloc(bu->lbl, &aLbl);
}
return (0);
}
static void
Draw(void *p)
{
AG_Button *bu = p;
AG_Variable *binding;
void *pState;
int pressed;
binding = AG_GetVariable(bu, "state", &pState);
pressed = GetState(bu, binding, pState);
AG_UnlockVariable(binding);
STYLE(bu)->ButtonBackground(bu, pressed);
if (bu->lbl != NULL) {
AG_WidgetDraw(bu->lbl);
return;
}
if (bu->surface != -1) {
int x = 0, y = 0, w, h;
w = WSURFACE(bu,bu->surface)->w;
h = WSURFACE(bu,bu->surface)->h;
switch (bu->justify) {
case AG_TEXT_LEFT: x = bu->lPad; break;
case AG_TEXT_CENTER: x = WIDTH(bu)/2 - w/2; break;
case AG_TEXT_RIGHT: x = WIDTH(bu) - w - bu->rPad; break;
}
switch (bu->valign) {
case AG_TEXT_TOP: x = bu->tPad; break;
case AG_TEXT_MIDDLE: x = HEIGHT(bu)/2 - h/2; break;
case AG_TEXT_BOTTOM: x = HEIGHT(bu) - h - bu->bPad; break;
}
STYLE(bu)->ButtonTextOffset(bu, pressed, &x, &y);
AG_WidgetBlitSurface(bu, bu->surface, x, y);
}
}
static int
GetState(AG_Button *bu, AG_Variable *binding, void *p)
{
int v = 0;
switch (AG_VARIABLE_TYPE(binding)) {
case AG_VARIABLE_INT:
v = *(int *)p;
break;
case AG_VARIABLE_UINT8:
v = (int)(*(Uint8 *)p);
break;
case AG_VARIABLE_UINT16:
v = (int)(*(Uint16 *)p);
break;
case AG_VARIABLE_UINT32:
v = (int)(*(Uint32 *)p);
break;
case AG_VARIABLE_P_FLAG:
v = (*(int *)p & (int)binding->info.bitmask);
break;
case AG_VARIABLE_P_FLAG8:
v = (int)(*(Uint8 *)p & (Uint8)binding->info.bitmask);
break;
case AG_VARIABLE_P_FLAG16:
v = (int)(*(Uint16 *)p & (Uint16)binding->info.bitmask);
break;
case AG_VARIABLE_P_FLAG32:
v = (int)(*(Uint32 *)p & (Uint32)binding->info.bitmask);
break;
default:
break;
}
if (bu->flags & AG_BUTTON_INVSTATE) {
v = !v;
}
return (v);
}
static void
SetState(AG_Variable *binding, void *p, int v)
{
switch (AG_VARIABLE_TYPE(binding)) {
case AG_VARIABLE_INT:
*(int *)p = v;
break;
case AG_VARIABLE_UINT8:
*(Uint8 *)p = v;
break;
case AG_VARIABLE_UINT16:
*(Uint16 *)p = v;
break;
case AG_VARIABLE_UINT32:
*(Uint32 *)p = v;
break;
case AG_VARIABLE_P_FLAG:
AG_SETFLAGS(*(int *)p, (int)binding->info.bitmask, v);
break;
case AG_VARIABLE_P_FLAG8:
AG_SETFLAGS(*(Uint8 *)p, (Uint8)binding->info.bitmask, v);
break;
case AG_VARIABLE_P_FLAG16:
AG_SETFLAGS(*(Uint16 *)p, (Uint16)binding->info.bitmask, v);
break;
case AG_VARIABLE_P_FLAG32:
AG_SETFLAGS(*(Uint32 *)p, (Uint32)binding->info.bitmask, v);
break;
default:
break;
}
}
static void
mousemotion(AG_Event *event)
{
AG_Button *bu = AG_SELF();
AG_Variable *binding;
int x = AG_INT(1);
int y = AG_INT(2);
void *pState;
if (AG_WidgetDisabled(bu))
return;
binding = AG_GetVariable(bu, "state", &pState);
if (!AG_WidgetRelativeArea(bu, x, y)) {
if ((bu->flags & AG_BUTTON_STICKY) == 0 &&
GetState(bu, binding, pState) == 1) {
SetState(binding, pState, 0);
}
if (bu->flags & AG_BUTTON_MOUSEOVER) {
bu->flags &= ~(AG_BUTTON_MOUSEOVER);
AG_PostEvent(NULL, bu, "button-mouseoverlap", "%i", 0);
}
} else {
bu->flags |= AG_BUTTON_MOUSEOVER;
AG_PostEvent(NULL, bu, "button-mouseoverlap", "%i", 1);
}
AG_UnlockVariable(binding);
}
static void
mousebuttondown(AG_Event *event)
{
AG_Button *bu = AG_SELF();
int button = AG_INT(1);
AG_Variable *binding;
void *pState;
int newState;
if (AG_WidgetDisabled(bu))
return;
AG_WidgetFocus(bu);
if (button != SDL_BUTTON_LEFT)
return;
binding = AG_GetVariable(bu, "state", &pState);
if (!(bu->flags & AG_BUTTON_STICKY)) {
SetState(binding, pState, 1);
} else {
newState = !GetState(bu, binding, pState);
SetState(binding, pState, newState);
AG_PostEvent(NULL, bu, "button-pushed", "%i", newState);
}
AG_UnlockVariable(binding);
if (bu->flags & AG_BUTTON_REPEAT) {
AG_DelTimeout(bu, &bu->repeat_to);
AG_ScheduleTimeout(bu, &bu->delay_to, agMouseSpinDelay);
}
}
static void
mousebuttonup(AG_Event *event)
{
AG_Button *bu = AG_SELF();
int button = AG_INT(1);
AG_Variable *binding;
void *pState;
int x = AG_INT(2);
int y = AG_INT(3);
if (bu->flags & AG_BUTTON_REPEAT) {
AG_DelTimeout(bu, &bu->repeat_to);
AG_DelTimeout(bu, &bu->delay_to);
}
if (AG_WidgetDisabled(bu) ||
x < 0 || y < 0 ||
x > WIDGET(bu)->w || y > WIDGET(bu)->h) {
return;
}
binding = AG_GetVariable(bu, "state", &pState);
if (GetState(bu, binding, pState) && button == SDL_BUTTON_LEFT &&
!(bu->flags & AG_BUTTON_STICKY)) {
SetState(binding, pState, 0);
AG_PostEvent(NULL, bu, "button-pushed", "%i", 0);
}
AG_UnlockVariable(binding);
}
static void
keydown(AG_Event *event)
{
AG_Button *bu = AG_SELF();
AG_Variable *binding;
void *pState;
int keysym = AG_INT(1);
if (AG_WidgetDisabled(bu))
return;
if (keysym != SDLK_RETURN && /* TODO configurable */
keysym != SDLK_SPACE) {
return;
}
binding = AG_GetVariable(bu, "state", &pState);
SetState(binding, pState, 1);
AG_PostEvent(NULL, bu, "button-pushed", "%i", 1);
if (bu->flags & AG_BUTTON_REPEAT) {
AG_DelTimeout(bu, &bu->repeat_to);
AG_ScheduleTimeout(bu, &bu->delay_to, 800);
}
AG_UnlockVariable(binding);
}
static void
keyup(AG_Event *event)
{
AG_Button *bu = AG_SELF();
AG_Variable *binding;
void *pState;
int keysym = AG_INT(1);
if (AG_WidgetDisabled(bu)) {
return;
}
if (bu->flags & AG_BUTTON_REPEAT) {
AG_DelTimeout(bu, &bu->delay_to);
AG_DelTimeout(bu, &bu->repeat_to);
}
if (keysym != SDLK_RETURN && /* TODO configurable */
keysym != SDLK_SPACE) {
return;
}
binding = AG_GetVariable(bu, "state", &pState);
SetState(binding, pState, 0);
AG_UnlockVariable(binding);
AG_PostEvent(NULL, bu, "button-pushed", "%i", 0);
}
void
AG_ButtonSetPadding(AG_Button *bu, int lPad, int rPad, int tPad, int bPad)
{
AG_ObjectLock(bu);
if (lPad != -1) { bu->lPad = lPad; }
if (rPad != -1) { bu->rPad = rPad; }
if (tPad != -1) { bu->tPad = tPad; }
if (bPad != -1) { bu->bPad = bPad; }
AG_ObjectUnlock(bu);
}
void
AG_ButtonSetFocusable(AG_Button *bu, int focusable)
{
AG_ObjectLock(bu);
if (focusable) {
WIDGET(bu)->flags |= AG_WIDGET_FOCUSABLE;
WIDGET(bu)->flags &= ~(AG_WIDGET_UNFOCUSED_BUTTONUP);
} else {
WIDGET(bu)->flags &= ~(AG_WIDGET_FOCUSABLE);
WIDGET(bu)->flags |= AG_WIDGET_UNFOCUSED_BUTTONUP;
}
AG_ObjectUnlock(bu);
}
void
AG_ButtonSetSticky(AG_Button *bu, int flag)
{
AG_ObjectLock(bu);
AG_SETFLAGS(bu->flags, AG_BUTTON_STICKY, flag);
AG_ObjectUnlock(bu);
}
void
AG_ButtonInvertState(AG_Button *bu, int flag)
{
AG_ObjectLock(bu);
AG_SETFLAGS(bu->flags, AG_BUTTON_INVSTATE, flag);
AG_ObjectUnlock(bu);
}
void
AG_ButtonSetJustification(AG_Button *bu, enum ag_text_justify jus)
{
AG_ObjectLock(bu);
bu->justify = jus;
if (bu->lbl != NULL) {
AG_LabelJustify(bu->lbl, jus);
}
AG_ObjectUnlock(bu);
}
void
AG_ButtonValign(AG_Button *bu, enum ag_text_valign va)
{
AG_ObjectLock(bu);
bu->valign = va;
if (bu->lbl != NULL) {
AG_LabelValign(bu->lbl, va);
}
AG_ObjectUnlock(bu);
}
void
AG_ButtonSurface(AG_Button *bu, AG_Surface *su)
{
AG_Surface *suDup = (su != NULL) ? AG_DupSurface(su) : NULL;
AG_ObjectLock(bu);
if (bu->lbl != NULL) {
AG_ObjectDetach(bu->lbl);
AG_ObjectDestroy(bu->lbl);
bu->lbl = NULL;
}
if (bu->surface != -1) {
AG_WidgetReplaceSurface(bu, bu->surface, suDup);
} else {
bu->surface = AG_WidgetMapSurface(bu, suDup);
}
AG_ObjectUnlock(bu);
}
void
AG_ButtonSurfaceNODUP(AG_Button *bu, AG_Surface *su)
{
AG_ObjectLock(bu);
if (bu->lbl != NULL) {
AG_ObjectDetach(bu->lbl);
AG_ObjectDestroy(bu->lbl);
bu->lbl = NULL;
}
if (bu->surface != -1) {
AG_WidgetReplaceSurfaceNODUP(bu, bu->surface, su);
} else {
bu->surface = AG_WidgetMapSurfaceNODUP(bu, su);
}
AG_ObjectUnlock(bu);
}
void
AG_ButtonSetRepeatMode(AG_Button *bu, int repeat)
{
AG_ObjectLock(bu);
if (repeat) {
bu->flags |= (AG_BUTTON_REPEAT);
} else {
AG_DelTimeout(bu, &bu->repeat_to);
AG_DelTimeout(bu, &bu->delay_to);
bu->flags &= ~(AG_BUTTON_REPEAT);
}
AG_ObjectUnlock(bu);
}
void
AG_ButtonText(AG_Button *bu, const char *fmt, ...)
{
va_list args;
char text[AG_LABEL_MAX];
va_start(args, fmt);
Vsnprintf(text, sizeof(text), fmt, args);
va_end(args);
AG_ObjectLock(bu);
if (bu->surface != -1) {
AG_ButtonSurface(bu, NULL);
}
if (bu->lbl != NULL) {
AG_LabelString(bu->lbl, text);
} else {
bu->lbl = AG_LabelNewString(bu, 0, text);
AG_LabelJustify(bu->lbl, bu->justify);
AG_LabelValign(bu->lbl, bu->valign);
}
AG_ObjectUnlock(bu);
}
AG_WidgetClass agButtonClass = {
{
"Agar(Widget:Button)",
sizeof(AG_Button),
{ 0,0 },
Init,
NULL, /* free */
NULL, /* destroy */
NULL, /* load */
NULL, /* save */
NULL /* edit */
},
Draw,
SizeRequest,
SizeAllocate
};