/*
* 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.
*/
/*
* Single or multi-line text input widget. This is a simple subclass of
* AG_Editable(3) adding a built-in label, pixel padding and scrollbars.
*/
#include "opengl.h"
#include
#include
#include "ttf.h"
#include "textbox.h"
#include "text.h"
#include "keymap.h"
#include "primitive.h"
#include "window.h"
#include
#include
#include
static void
BeginScrollbarDrag(AG_Event *event)
{
AG_Textbox *tb = AG_PTR(1);
AG_WidgetFocus(tb->ed);
tb->ed->flags |= AG_EDITABLE_NOSCROLL;
}
static void
EndScrollbarDrag(AG_Event *event)
{
AG_Textbox *tb = AG_PTR(1);
tb->ed->flags &= ~(AG_EDITABLE_NOSCROLL);
}
AG_Textbox *
AG_TextboxNew(void *parent, Uint flags, const char *label)
{
AG_Textbox *tb;
tb = Malloc(sizeof(AG_Textbox));
AG_ObjectInit(tb, &agTextboxClass);
if (!(flags & AG_TEXTBOX_NO_HFILL))
AG_ExpandHoriz(tb);
if ( flags & AG_TEXTBOX_VFILL)
AG_ExpandVert(tb);
if (flags & AG_TEXTBOX_READONLY) {
AG_WidgetDisable(tb);
AG_WidgetDisable(tb->ed);
}
if (flags & AG_TEXTBOX_PASSWORD)
tb->ed->flags |= AG_EDITABLE_PASSWORD;
if (flags & AG_TEXTBOX_ABANDON_FOCUS)
tb->ed->flags |= AG_EDITABLE_ABANDON_FOCUS;
if (flags & AG_TEXTBOX_INT_ONLY)
tb->ed->flags |= AG_EDITABLE_INT_ONLY;
if (flags & AG_TEXTBOX_FLT_ONLY)
tb->ed->flags |= AG_EDITABLE_FLT_ONLY;
if (flags & AG_TEXTBOX_CATCH_TAB) {
WIDGET(tb)->flags |= AG_WIDGET_CATCH_TAB;
WIDGET(tb->ed)->flags |= AG_WIDGET_CATCH_TAB;
}
if (flags & AG_TEXTBOX_STATIC)
tb->ed->flags |= AG_EDITABLE_STATIC;
if (flags & AG_TEXTBOX_NOEMACS)
tb->ed->flags |= AG_EDITABLE_NOEMACS;
if (flags & AG_TEXTBOX_NOWORDSEEK)
tb->ed->flags |= AG_EDITABLE_NOWORDSEEK;
if (flags & AG_TEXTBOX_NOLATIN1)
tb->ed->flags |= AG_EDITABLE_NOLATIN1;
if (flags & AG_TEXTBOX_MULTILINE) {
tb->ed->flags |= AG_EDITABLE_MULTILINE;
tb->hBar = AG_ScrollbarNew(tb, AG_SCROLLBAR_HORIZ, 0);
tb->vBar = AG_ScrollbarNew(tb, AG_SCROLLBAR_VERT, 0);
AG_BindInt(tb->hBar, "value", &tb->ed->x);
AG_BindInt(tb->vBar, "value", &tb->ed->y);
AG_BindInt(tb->hBar, "max", &tb->ed->xMax);
AG_BindInt(tb->vBar, "max", &tb->ed->yMax);
AG_BindInt(tb->hBar, "visible", &WIDTH(tb->ed));
AG_BindInt(tb->vBar, "visible", &tb->ed->yVis);
AG_SetEvent(tb->hBar, "scrollbar-drag-begin", BeginScrollbarDrag, "%p", tb);
AG_SetEvent(tb->vBar, "scrollbar-drag-begin", BeginScrollbarDrag, "%p", tb);
AG_SetEvent(tb->hBar, "scrollbar-drag-end", EndScrollbarDrag, "%p", tb);
AG_SetEvent(tb->vBar, "scrollbar-drag-end", EndScrollbarDrag, "%p", tb);
AG_TextboxSizeHintLines(tb, 4);
}
tb->flags |= flags;
if (label != NULL) {
tb->labelText = Strdup(label);
}
AG_ObjectAttach(parent, tb);
return (tb);
}
static void
Destroy(void *p)
{
AG_Textbox *tb = p;
Free(tb->labelText);
}
static void
Draw(void *p)
{
AG_Textbox *tb = p;
STYLE(tb)->TextboxBackground(tb, tb->r, (tb->flags & AG_TEXTBOX_COMBO));
if (tb->labelText != NULL && tb->label == -1) {
AG_PushTextState();
AG_TextColor(TEXTBOX_TXT_COLOR);
tb->label = AG_WidgetMapSurface(tb,
AG_TextRender(tb->labelText));
AG_PopTextState();
}
if (tb->label != -1) {
AG_Surface *lblSu = WSURFACE(tb,tb->label);
AG_PushClipRect(tb, tb->rLbl);
AG_WidgetBlitSurface(tb, tb->label,
tb->lblPadL,
HEIGHT(tb)/2 - lblSu->h/2);
AG_PopClipRect();
}
AG_PushClipRect(tb, tb->r);
if (tb->flags & AG_TEXTBOX_MULTILINE) {
int d;
if (tb->vBar != NULL && AG_ScrollbarVisible(tb->vBar)) {
d = WIDTH(tb->vBar);
AG_DrawBox(tb,
AG_RECT(WIDTH(tb)-d, HEIGHT(tb)-d, d, d), -1,
AG_COLOR(TEXTBOX_COLOR));
} else if (tb->hBar != NULL && AG_ScrollbarVisible(tb->hBar)) {
d = HEIGHT(tb->hBar);
AG_DrawBox(tb,
AG_RECT(WIDTH(tb)-d, HEIGHT(tb)-d, d, d), -1,
AG_COLOR(TEXTBOX_COLOR));
}
AG_WindowUpdate(AG_ParentWindow(tb));
}
AG_WidgetDraw(tb->ed);
if (tb->hBar != NULL) { AG_WidgetDraw(tb->hBar); }
if (tb->vBar != NULL) { AG_WidgetDraw(tb->vBar); }
AG_PopClipRect();
}
static void
SizeRequest(void *obj, AG_SizeReq *r)
{
AG_Textbox *tb = obj;
AG_SizeReq rEd;
int wLbl, hLbl;
AG_WidgetSizeReq(tb->ed, &rEd);
r->w = tb->boxPadX*2 + rEd.w;
r->h = tb->boxPadY*2 + rEd.h;
if (tb->labelText != NULL) {
if (tb->label != -1) {
wLbl = WSURFACE(tb,tb->label)->w;
hLbl = WSURFACE(tb,tb->label)->h;
} else {
AG_TextSize(tb->labelText, &wLbl, &hLbl);
}
r->w += tb->lblPadL + wLbl + tb->lblPadR;
}
r->h = MAX(r->h, agTextFontLineSkip);
}
static int
SizeAllocate(void *obj, const AG_SizeAlloc *a)
{
AG_Textbox *tb = obj;
int boxPadW = tb->boxPadX*2;
int boxPadH = tb->boxPadY*2;
int lblPadW = tb->lblPadL + tb->lblPadR;
int wBar = 0, hBar = 0;
AG_SizeAlloc aChld;
AG_SizeReq r;
int d;
tb->wLbl = 0;
tb->hLbl = 0;
if (tb->labelText == NULL && (a->w < boxPadW || a->h < boxPadH))
return (-1);
if (a->w < boxPadW + lblPadW || a->h < boxPadH)
return (-1);
if (tb->label != -1) {
tb->wLbl = WSURFACE(tb,tb->label)->w;
tb->hLbl = WSURFACE(tb,tb->label)->h;
} else {
AG_TextSize(tb->labelText, &tb->wLbl, &tb->hLbl);
}
if (a->w < (boxPadW+lblPadW) + tb->wLbl+tb->ed->wPre) {
tb->wLbl = a->w - (boxPadW-lblPadW) - tb->ed->wPre;
if (tb->wLbl <= 0) {
if (a->w > boxPadW+lblPadW) {
tb->wLbl = 0;
} else {
return (-1);
}
}
}
if (tb->flags & AG_TEXTBOX_MULTILINE) {
AG_WidgetSizeReq(tb->hBar, &r);
d = MIN(r.h, a->h);
aChld.x = 0;
aChld.y = a->h - d;
aChld.w = a->w - d + 1;
aChld.h = d;
AG_WidgetSizeAlloc(tb->hBar, &aChld);
if (AG_ScrollbarVisible(tb->hBar))
hBar = aChld.h;
AG_WidgetSizeReq(tb->vBar, &r);
d = MIN(r.w, a->w);
aChld.x = a->w - d;
aChld.y = 0;
aChld.w = d;
aChld.h = a->h - d + 1;
AG_WidgetSizeAlloc(tb->vBar, &aChld);
if (AG_ScrollbarVisible(tb->vBar))
wBar = aChld.w;
tb->r.x = 0;
tb->r.y = 0;
tb->r.w = a->w;
tb->r.h = a->h;
} else {
tb->r.x = tb->wLbl + tb->lblPadL + tb->lblPadR;
tb->r.y = 0;
tb->r.w = a->w - tb->r.x;
tb->r.h = a->h;
}
tb->rLbl = AG_RECT(0, 0, tb->wLbl, a->h);
aChld.x = lblPadW + tb->wLbl + tb->boxPadX;
aChld.y = tb->boxPadY;
aChld.w = a->w - boxPadW - aChld.x - wBar;
aChld.h = a->h - boxPadH - hBar;
AG_WidgetSizeAlloc(tb->ed, &aChld);
return (0);
}
/* Set the text from a format string. */
void
AG_TextboxPrintf(AG_Textbox *tb, const char *fmt, ...)
{
AG_Variable *stringb;
va_list args;
char *text;
AG_ObjectLock(tb->ed);
stringb = AG_GetVariable(tb->ed, "string", &text);
if (fmt != NULL && fmt[0] != '\0') {
va_start(args, fmt);
Vsnprintf(text, stringb->info.size, fmt, args);
va_end(args);
tb->ed->pos = AG_LengthUTF8(text);
} else {
text[0] = '\0';
tb->ed->pos = 0;
}
AG_TextboxBufferChanged(tb);
AG_UnlockVariable(stringb);
AG_ObjectUnlock(tb->ed);
}
void
AG_TextboxSetLabel(AG_Textbox *tb, const char *fmt, ...)
{
va_list ap;
AG_ObjectLock(tb);
va_start(ap, fmt);
Free(tb->labelText);
Vasprintf(&tb->labelText, fmt, ap);
va_end(ap);
if (tb->label != -1) {
AG_WidgetUnmapSurface(tb, tb->label);
tb->label = -1;
}
AG_ObjectUnlock(tb);
}
static void
MouseButtonDown(AG_Event *event)
{
AG_Textbox *tb = AG_SELF();
AG_ForwardEvent(NULL, tb->ed, event);
}
static void
Disabled(AG_Event *event)
{
AG_Textbox *tb = AG_SELF();
AG_WidgetDisable(tb->ed);
}
static void
Enabled(AG_Event *event)
{
AG_Textbox *tb = AG_SELF();
AG_WidgetEnable(tb->ed);
}
#ifdef AG_DEBUG
static void
Bound(AG_Event *event)
{
AG_FatalError("Use AG_TextboxBindUTF8() or AG_TextboxBindASCII()");
}
#endif
static void
EditablePreChg(AG_Event *event)
{
AG_PostEvent(NULL, AG_PTR(1), "textbox-prechg", NULL);
}
static void
EditablePostChg(AG_Event *event)
{
AG_PostEvent(NULL, AG_PTR(1), "textbox-postchg", NULL);
}
static void
EditableReturn(AG_Event *event)
{
AG_PostEvent(NULL, AG_PTR(1), "textbox-return", NULL);
}
static void
Init(void *obj)
{
AG_Textbox *tb = obj;
tb->ed = AG_EditableNew(tb, 0);
tb->boxPadX = 2;
tb->boxPadY = 2;
tb->lblPadL = 2;
tb->lblPadR = 2;
tb->wLbl = 0;
tb->flags = 0;
tb->label = -1;
tb->labelText = NULL;
tb->hBar = NULL;
tb->vBar = NULL;
tb->r = AG_RECT(0,0,0,0);
tb->rLbl = AG_RECT(0,0,0,0);
AG_SetEvent(tb, "window-mousebuttondown", MouseButtonDown, NULL);
AG_SetEvent(tb, "widget-disabled", Disabled, NULL);
AG_SetEvent(tb, "widget-enabled", Enabled, NULL);
#ifdef AG_DEBUG
AG_SetEvent(tb, "bound", Bound, NULL);
#endif
AG_SetEvent(tb->ed, "editable-prechg", EditablePreChg, "%p", tb);
AG_SetEvent(tb->ed, "editable-postchg", EditablePostChg, "%p", tb);
AG_SetEvent(tb->ed, "editable-return", EditableReturn, "%p", tb);
AG_WidgetForwardFocus(tb, tb->ed);
}
AG_WidgetClass agTextboxClass = {
{
"Agar(Widget:Textbox)",
sizeof(AG_Textbox),
{ 0,0 },
Init,
NULL, /* free */
Destroy,
NULL, /* load */
NULL, /* save */
NULL /* edit */
},
Draw,
SizeRequest,
SizeAllocate
};