/* * 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. */ /* * Low-level single/multi-line text input widget. This is the base widget * used by AG_Textbox(3), editable cells in AG_Table(3), etc. */ #include "opengl.h" #include #include #include #include #include "view.h" #include "ttf.h" #include "editable.h" #include "text.h" #include "keymap.h" #include "primitive.h" #include "cursors.h" #include #include #include AG_Editable * AG_EditableNew(void *parent, Uint flags) { AG_Editable *ed; ed = Malloc(sizeof(AG_Editable)); AG_ObjectInit(ed, &agEditableClass); if ((flags & AG_EDITABLE_NO_HFILL) == 0) AG_ExpandHoriz(ed); if (flags & AG_EDITABLE_VFILL) AG_ExpandVert(ed); if (flags & AG_EDITABLE_CATCH_TAB) WIDGET(ed)->flags |= AG_WIDGET_CATCH_TAB; ed->flags |= flags; AG_ObjectAttach(parent, ed); return (ed); } /* Bind a UTF-8 text buffer to the widget. */ void AG_EditableBindUTF8(AG_Editable *ed, char *buf, size_t bufSize) { AG_ObjectLock(ed); AG_BindString(ed, "string", buf, bufSize); ed->encoding = AG_ENCODING_UTF8; AG_ObjectUnlock(ed); } /* Bind an ASCII text buffer to the widget. */ void AG_EditableBindASCII(AG_Editable *ed, char *buf, size_t bufSize) { AG_ObjectLock(ed); AG_BindString(ed, "string", buf, bufSize); ed->encoding = AG_ENCODING_ASCII; AG_ObjectUnlock(ed); } /* Enable or disable password entry mode. */ void AG_EditableSetPassword(AG_Editable *ed, int enable) { AG_ObjectLock(ed); AG_SETFLAGS(ed->flags, AG_EDITABLE_PASSWORD, enable); AG_ObjectUnlock(ed); } /* Enable or disable static optimizations. */ void AG_EditableSetStatic(AG_Editable *ed, int enable) { AG_ObjectLock(ed); if (enable) { ed->flags |= AG_EDITABLE_STATIC; } else { ed->flags &= ~(AG_EDITABLE_STATIC); #if 0 /* XXX */ Free(ed->ucsBuf); #endif ed->ucsBuf = NULL; ed->ucsLen = 0; } AG_ObjectUnlock(ed); } void AG_EditableSetFltOnly(AG_Editable *ed, int enable) { AG_ObjectLock(ed); if (enable) { ed->flags |= AG_EDITABLE_FLT_ONLY; ed->flags &= ~(AG_EDITABLE_INT_ONLY); } else { ed->flags &= ~(AG_EDITABLE_FLT_ONLY); } AG_ObjectUnlock(ed); } void AG_EditableSetIntOnly(AG_Editable *ed, int enable) { AG_ObjectLock(ed); if (enable) { ed->flags |= AG_EDITABLE_INT_ONLY; ed->flags &= ~(AG_EDITABLE_FLT_ONLY); } else { ed->flags &= ~(AG_EDITABLE_INT_ONLY); } AG_ObjectUnlock(ed); } /* * Process a keystroke. May be invoked from the repeat timeout routine or * the keydown handler. If we return 1, the current delay/repeat cycle will * be maintained, otherwise it will be cancelled. */ static int ProcessKey(AG_Editable *ed, SDLKey keysym, SDLMod keymod, Uint32 unicode) { AG_Variable *stringb; char *s; int i, rv = 0, len; #ifdef UTF8 Uint32 *ucs; #endif if (keysym == SDLK_ESCAPE) { return (0); } if (keysym == SDLK_RETURN && (ed->flags & AG_EDITABLE_MULTILINE) == 0) return (0); if (keymod == KMOD_NONE && isascii((int)keysym) && isprint((int)keysym)) { if ((ed->flags & AG_EDITABLE_INT_ONLY)) { if (keysym != SDLK_MINUS && keysym != SDLK_PLUS && !isdigit((int)keysym)) { return (0); } } else if ((ed->flags & AG_EDITABLE_FLT_ONLY)) { if (keysym != SDLK_PLUS && keysym != SDLK_MINUS && keysym != SDLK_PERIOD && keysym != SDLK_e && keysym != SDLK_i && keysym != SDLK_n && keysym != SDLK_f && keysym != SDLK_a && unicode != 0x221e && /* Infinity */ !isdigit((int)keysym)) { return (0); } } } stringb = AG_GetVariable(ed, "string", &s); #ifdef UTF8 if (ed->flags & AG_EDITABLE_STATIC) { if (ed->ucsBuf == NULL) { ed->ucsBuf = AG_ImportUnicode(AG_UNICODE_FROM_UTF8, s, stringb->info.size); ed->ucsLen = AG_LengthUCS4(ed->ucsBuf); } ucs = ed->ucsBuf; len = ed->ucsLen; } else { ucs = AG_ImportUnicode(AG_UNICODE_FROM_UTF8, s, stringb->info.size); len = AG_LengthUCS4(ucs); } if (ed->pos < 0) { ed->pos = 0; } if (ed->pos > len) { ed->pos = len; } for (i = 0; ; i++) { const struct ag_keycode_utf8 *kc = &agKeymapUTF8[i]; if (kc->key != SDLK_LAST && (kc->key != keysym || kc->func == NULL)) { continue; } if (kc->key == SDLK_LAST || kc->modmask == 0 || (keymod & kc->modmask)) { AG_PostEvent(NULL, ed, "editable-prechg", NULL); rv = kc->func(ed, keysym, keymod, unicode, ucs, len, stringb->info.size); break; } } if (rv == 1) { if (ed->flags & AG_EDITABLE_STATIC) { ed->ucsLen = AG_LengthUCS4(ucs); } AG_ExportUnicode(AG_UNICODE_TO_UTF8, stringb->data.s, ucs, stringb->info.size); ed->flags |= AG_EDITABLE_MARKPREF; AG_PostEvent(NULL, ed, "editable-postchg", NULL); } if (!(ed->flags & AG_EDITABLE_STATIC)) { Free(ucs); } #else /* !UTF8 */ len = strlen(s); if (ed->pos < 0) { ed->pos = 0; } if (ed->pos > len) { ed->pos = len; } for (i = 0; ; i++) { const struct ag_keycode_ascii *kc = &agKeymapASCII[i]; if (kc->key != SDLK_LAST && (kc->key != keysym || kc->func == NULL)) { continue; } if (kc->key == SDLK_LAST || kc->modmask == 0 || (keymod & kc->modmask)) { AG_PostEvent(NULL, ed, "editable-prechg", NULL); rv = kc->func(ed, keysym, keymod, unicode, s, len, stringb->info.size); break; } } if (rv == 1) { ed->flags |= AG_EDITABLE_MARKPREF; AG_PostEvent(NULL, ed, "editable-postchg", NULL); } #endif /* UTF8 */ AG_UnlockVariable(stringb); return (1); } static Uint32 RepeatTimeout(void *obj, Uint32 ival, void *arg) { AG_Editable *ed = obj; if (ProcessKey(ed, ed->repeatKey, ed->repeatMod, ed->repeatUnicode) == 0) { return (0); } return (agKbdRepeat); } static Uint32 DelayTimeout(void *obj, Uint32 ival, void *arg) { AG_Editable *ed = obj; AG_ScheduleTimeout(ed, &ed->toRepeat, agKbdRepeat); AG_DelTimeout(ed, &ed->toCursorBlink); ed->flags |= AG_EDITABLE_BLINK_ON; return (0); } static Uint32 BlinkTimeout(void *obj, Uint32 ival, void *arg) { AG_Editable *ed = obj; if ((ed->flags & AG_EDITABLE_CURSOR_MOVING) == 0) { AG_INVFLAGS(ed->flags, AG_EDITABLE_BLINK_ON); } return (ival); } static void GainedFocus(AG_Event *event) { AG_Editable *ed = AG_SELF(); AG_LockTimeouts(ed); AG_DelTimeout(ed, &ed->toDelay); AG_DelTimeout(ed, &ed->toRepeat); AG_ScheduleTimeout(ed, &ed->toCursorBlink, agTextBlinkRate); ed->flags |= AG_EDITABLE_BLINK_ON; AG_UnlockTimeouts(ed); } static void LostFocus(AG_Event *event) { AG_Editable *ed = AG_SELF(); AG_LockTimeouts(ed); AG_DelTimeout(ed, &ed->toDelay); AG_DelTimeout(ed, &ed->toRepeat); AG_DelTimeout(ed, &ed->toCursorBlink); ed->flags &= ~(AG_EDITABLE_BLINK_ON|AG_EDITABLE_CURSOR_MOVING); AG_UnlockTimeouts(ed); } static __inline__ void GetStringUCS4(AG_Editable *ed, const char *s, Uint32 **ucs, size_t *len) { #ifdef UTF8 if (ed->flags & AG_EDITABLE_STATIC) { if (ed->ucsBuf == NULL) { ed->ucsBuf = AG_ImportUnicode(AG_UNICODE_FROM_UTF8,s,0); ed->ucsLen = AG_LengthUCS4(ed->ucsBuf); } *ucs = ed->ucsBuf; *len = ed->ucsLen; } else { *ucs = AG_ImportUnicode(AG_UNICODE_FROM_UTF8, s, 0); *len = AG_LengthUCS4(*ucs); } #else *ucs = NULL; *len = strlen(s); #endif } static __inline__ void FreeStringUCS4(AG_Editable *ed, Uint32 *ucs) { #ifdef UTF8 if (!(ed->flags & AG_EDITABLE_STATIC)) Free(ucs); #endif } /* * Map mouse coordinates to a position within the string. */ #define ON_LINE(my,y) \ ( ((my) >= (y) && (my) <= (y)+agTextFontLineSkip) || \ (!absflag && \ (((my) <= ed->y*agTextFontLineSkip && line == 0) || \ ((my) > (nLines*agTextFontLineSkip) && (line == nLines-1)))) ) #define ON_CHAR(mx,x,glyph) \ ((mx) >= (x) && (mx) <= (x)+(glyph)->advance) int AG_EditableMapPosition(AG_Editable *ed, int mx, int my, int *pos, int absflag) { AG_Variable *stringb; AG_Font *font; size_t len; char *s; Uint32 *ucs, ch; int i, x, y, line = 0; int nLines = 1; int yMouse; AG_ObjectLock(ed); yMouse = my + ed->y*agTextFontLineSkip; if (yMouse < 0) { AG_ObjectUnlock(ed); return (-1); } x = 0; y = 0; stringb = AG_GetVariable(ed, "string", &s); GetStringUCS4(ed, s, &ucs, &len); if ((font = AG_FetchFont(NULL, -1, -1)) == NULL) AG_FatalError("AG_Editable: %s", AG_GetError()); for (i = 0; i < len; i++) { #ifdef UTF8 if (ucs[i] == '\n') nLines++; #else if (s[i] == '\n') nLines++; #endif } for (i = 0; i < len; i++) { #ifdef UTF8 ch = ucs[i]; #else ch = (Uint32)s[i]; #endif if (mx <= 0 && ON_LINE(yMouse,y)) { *pos = i; goto in; } if (ch == '\n') { if (ON_LINE(yMouse,y) && mx > x) { *pos = i; goto in; } y += agTextFontLineSkip; x = 0; line++; continue; } else if (ch == '\t') { if (ON_LINE(yMouse,y) && mx >= x && mx <= x+agTextTabWidth) { *pos = (mx < x + agTextTabWidth/2) ? i : i+1; goto in; } x += agTextTabWidth; continue; } switch (font->type) { #ifdef HAVE_FREETYPE case AG_FONT_VECTOR: { AG_TTFFont *ttf = font->ttf; AG_TTFGlyph *glyph; if (AG_TTFFindGlyph(ttf, ch, TTF_CACHED_METRICS|TTF_CACHED_BITMAP) != 0) { continue; } glyph = ttf->current; if (ON_LINE(yMouse,y) && ON_CHAR(mx,x,glyph)) { *pos = (mx < x+glyph->advance/2) ? i : i+1; goto in; } x += glyph->advance; break; } #endif /* HAVE_FREETYPE */ case AG_FONT_BITMAP: { AG_Glyph *gl; gl = AG_TextRenderGlyph(ch); if (ON_LINE(yMouse,y) && mx >= x && mx <= x+gl->su->w) { *pos = i; goto in; } x += gl->su->w; AG_TextUnusedGlyph(gl); break; } default: AG_FatalError("AG_Editable: Unknown font format"); } } FreeStringUCS4(ed, ucs); AG_UnlockVariable(stringb); AG_ObjectUnlock(ed); return (1); in: FreeStringUCS4(ed, ucs); AG_UnlockVariable(stringb); AG_ObjectUnlock(ed); return (0); } #undef ON_LINE #undef ON_CHAR /* Move cursor to the given position in pixels. */ void AG_EditableMoveCursor(AG_Editable *ed, int mx, int my, int absflag) { AG_Variable *stringb; char *s; int rv; AG_ObjectLock(ed); rv = AG_EditableMapPosition(ed, mx, my, &ed->pos, absflag); if (rv == -1) { ed->pos = 0; } else if (rv == 1) { stringb = AG_GetVariable(ed, "string", &s); ed->pos = AG_LengthUTF8(s); AG_UnlockVariable(stringb); } AG_ObjectUnlock(ed); } /* Return the last cursor position. */ int AG_EditableGetCursorPos(AG_Editable *ed) { int rv; AG_ObjectLock(ed); rv = ed->pos; AG_ObjectUnlock(ed); return (rv); } /* Set the cursor position (-1 = end of the string) with bounds checking. */ int AG_EditableSetCursorPos(AG_Editable *ed, int pos) { AG_Variable *stringb; size_t len; char *s; int rv; AG_ObjectLock(ed); stringb = AG_GetVariable(ed, "string", &s); ed->pos = pos; if (ed->pos < 0) { len = AG_LengthUTF8(s); if (pos == -1 || ed->pos > len) ed->pos = len+1; } rv = ed->pos; AG_UnlockVariable(stringb); AG_ObjectUnlock(ed); return (rv); } static void Draw(void *obj) { AG_Editable *ed = obj; AG_Variable *stringb; char *s; int i, dx, dy, x, y; Uint32 *ucs; size_t len; #ifdef HAVE_OPENGL GLboolean blend_sv; GLint blend_sfactor, blend_dfactor; GLfloat texenvmode; #endif stringb = AG_GetVariable(ed, "string", &s); GetStringUCS4(ed, s, &ucs, &len); #ifdef HAVE_OPENGL if (agView->opengl) { glGetTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &texenvmode); glGetBooleanv(GL_BLEND, &blend_sv); glGetIntegerv(GL_BLEND_SRC, &blend_sfactor); glGetIntegerv(GL_BLEND_DST, &blend_dfactor); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } #endif AG_PushClipRect(ed, ed->r); AG_PushTextState(); AG_TextColor(TEXTBOX_TXT_COLOR); x = 0; y = -ed->y*agTextFontLineSkip; ed->xMax = 10; ed->yMax = 1; for (i = 0; i <= len; i++) { AG_Glyph *gl; #ifdef UTF8 Uint32 c = ucs[i]; #else char c = s[i]; #endif if (i == ed->pos && AG_WidgetFocused(ed)) { if ((ed->flags & AG_EDITABLE_BLINK_ON) && ed->y >= 0 && ed->y <= ed->yMax-1) { AG_DrawLineV(ed, x - ed->x, (y + 1), (y + agTextFontLineSkip - 1), AG_COLOR(TEXTBOX_CURSOR_COLOR)); } ed->xCurs = x; if (ed->flags & AG_EDITABLE_MARKPREF) { ed->flags &= ~(AG_EDITABLE_MARKPREF); ed->xCursPref = x; } ed->yCurs = y/agTextFontLineSkip + ed->y; } if (i == len) break; if (c == '\n') { y += agTextFontLineSkip; ed->xMax = MAX(ed->xMax, x+10); ed->yMax++; x = 0; continue; } else if (c == '\t') { x += agTextTabWidth; continue; } c = (ed->flags & AG_EDITABLE_PASSWORD) ? '*' : c; gl = AG_TextRenderGlyph(c); dx = WIDGET(ed)->rView.x1 + x - ed->x; dy = WIDGET(ed)->rView.y1 + y; if (x < (ed->x - gl->su->w*2) || y < -(gl->su->h) || dx > (WIDGET(ed)->rView.x2 + gl->su->w) || dy > WIDGET(ed)->rView.y2) { x += gl->advance; AG_TextUnusedGlyph(gl); continue; } if (!agView->opengl) { AG_SurfaceBlit(gl->su, NULL, agView->v, dx,dy); } #ifdef HAVE_OPENGL else { glBindTexture(GL_TEXTURE_2D, gl->texture); glBegin(GL_TRIANGLE_STRIP); { glTexCoord2f(gl->texcoord[0], gl->texcoord[1]); glVertex2i(dx, dy); glTexCoord2f(gl->texcoord[2], gl->texcoord[1]); glVertex2i(dx+gl->su->w, dy); glTexCoord2f(gl->texcoord[0], gl->texcoord[3]); glVertex2i(dx, dy+gl->su->h); glTexCoord2f(gl->texcoord[2], gl->texcoord[3]); glVertex2i(dx+gl->su->w, dy+gl->su->h); } glEnd(); glBindTexture(GL_TEXTURE_2D, 0); } #endif /* HAVE_OPENGL */ x += gl->advance; AG_TextUnusedGlyph(gl); } if (ed->yMax == 1) ed->xMax = x; if ( !(ed->flags & (AG_EDITABLE_NOSCROLL|AG_EDITABLE_NOSCROLL_ONCE)) ) { if (ed->flags & AG_EDITABLE_MULTILINE) { if (ed->yCurs < ed->y) { ed->y = ed->yCurs; if (ed->y < 0) { ed->y = 0; } } else if (ed->yCurs > ed->y + ed->yVis - 1) { ed->y = ed->yCurs - ed->yVis + 1; } } if (ed->xCurs < ed->x) { ed->x = ed->xCurs; if (ed->x < 0) { ed->x = 0; } } else if (ed->xCurs > ed->x + WIDTH(ed) - 10) { ed->x = ed->xCurs - WIDTH(ed) + 10; } } else { if (ed->yCurs < ed->y) { if (ed->flags & AG_EDITABLE_MULTILINE) { AG_EditableMoveCursor(ed, ed->xCursPref - ed->x, 1, 0); } } else if (ed->yCurs > ed->y + ed->yVis - 1) { if (ed->flags & AG_EDITABLE_MULTILINE) { AG_EditableMoveCursor(ed, ed->xCursPref - ed->x, ed->yVis*agTextFontLineSkip - 1, 0); } } else if (ed->xCurs < ed->x+10) { AG_EditableMoveCursor(ed, ed->x+10, (ed->yCurs - ed->y)*agTextFontLineSkip + 1, 1); } else if (ed->xCurs > ed->x+WIDTH(ed)-10) { AG_EditableMoveCursor(ed, ed->x+WIDTH(ed)-10, (ed->yCurs - ed->y)*agTextFontLineSkip + 1, 1); } } ed->flags &= ~(AG_EDITABLE_NOSCROLL_ONCE); AG_UnlockVariable(stringb); AG_PopTextState(); AG_PopClipRect(); #ifdef HAVE_OPENGL if (agView->opengl) { if (blend_sv) { glEnable(GL_BLEND); } else { glDisable(GL_BLEND); } glBlendFunc(blend_sfactor, blend_dfactor); } #endif FreeStringUCS4(ed, ucs); } void AG_EditableSizeHint(AG_Editable *ed, const char *text) { AG_ObjectLock(ed); AG_TextSize(text, &ed->wPre, &ed->hPre); AG_ObjectUnlock(ed); } void AG_EditableSizeHintPixels(AG_Editable *ed, Uint w, Uint h) { AG_ObjectLock(ed); ed->wPre = w; ed->hPre = h; AG_ObjectUnlock(ed); } void AG_EditableSizeHintLines(AG_Editable *ed, Uint nLines) { AG_ObjectLock(ed); ed->hPre = nLines*agTextFontHeight; AG_ObjectUnlock(ed); } static void SizeRequest(void *obj, AG_SizeReq *r) { AG_Editable *ed = obj; r->w = ed->wPre; r->h = ed->hPre; } static int SizeAllocate(void *obj, const AG_SizeAlloc *a) { AG_Editable *ed = obj; if (a->w < 2 || a->h < 2) return (-1); ed->yVis = a->h/agTextFontLineSkip; ed->r = AG_RECT(-1, -1, a->w-1, a->h-1); return (0); } static void KeyDown(AG_Event *event) { AG_Editable *ed = AG_SELF(); SDLKey keysym = AG_SDLKEY(1); int keymod = AG_INT(2); Uint32 unicode = (Uint32)AG_INT(3); /* XXX use AG_UINT32 */ if (keysym == SDLK_TAB && !(WIDGET(ed)->flags & AG_WIDGET_CATCH_TAB)) return; ed->repeatKey = keysym; ed->repeatMod = keymod; ed->repeatUnicode = unicode; ed->flags |= AG_EDITABLE_BLINK_ON; AG_LockTimeouts(ed); AG_DelTimeout(ed, &ed->toRepeat); if (ProcessKey(ed, keysym, keymod, unicode) == 1) { AG_ScheduleTimeout(ed, &ed->toDelay, agKbdDelay); } else { AG_DelTimeout(ed, &ed->toDelay); } AG_UnlockTimeouts(ed); } static void KeyUp(AG_Event *event) { AG_Editable *ed = AG_SELF(); SDLKey keysym = AG_SDLKEY(1); if (ed->repeatKey == keysym) { AG_LockTimeouts(ed); AG_DelTimeout(ed, &ed->toRepeat); AG_DelTimeout(ed, &ed->toDelay); AG_ScheduleTimeout(ed, &ed->toCursorBlink, agTextBlinkRate); AG_UnlockTimeouts(ed); } if (keysym == SDLK_RETURN && (ed->flags & AG_EDITABLE_MULTILINE) == 0) { if (ed->flags & AG_EDITABLE_ABANDON_FOCUS) { AG_WidgetUnfocus(ed); } AG_PostEvent(NULL, ed, "editable-return", NULL); } } static void MouseButtonDown(AG_Event *event) { AG_Editable *ed = AG_SELF(); int btn = AG_INT(1); int mx = AG_INT(2); int my = AG_INT(3); AG_WidgetFocus(ed); switch (btn) { case SDL_BUTTON_LEFT: ed->flags |= AG_EDITABLE_CURSOR_MOVING|AG_EDITABLE_BLINK_ON; mx += ed->x; AG_EditableMoveCursor(ed, mx, my, 0); ed->flags |= AG_EDITABLE_MARKPREF; break; case SDL_BUTTON_WHEELUP: if (ed->flags & AG_EDITABLE_MULTILINE) { ed->flags |= AG_EDITABLE_NOSCROLL_ONCE; ed->y -= AG_WidgetScrollDelta(&ed->wheelTicks); if (ed->y < 0) { ed->y = 0; } } break; case SDL_BUTTON_WHEELDOWN: if (ed->flags & AG_EDITABLE_MULTILINE) { ed->flags |= AG_EDITABLE_NOSCROLL_ONCE; ed->y += AG_WidgetScrollDelta(&ed->wheelTicks); ed->y = MIN(ed->y, ed->yMax - ed->yVis); } break; default: break; } } static void MouseButtonUp(AG_Event *event) { AG_Editable *ed = AG_SELF(); int btn = AG_INT(1); switch (btn) { case SDL_BUTTON_LEFT: ed->flags &= ~(AG_EDITABLE_CURSOR_MOVING); break; default: break; } } static void MouseMotion(AG_Event *event) { AG_Editable *ed = AG_SELF(); int mx = AG_INT(1); int my = AG_INT(2); if (mx > 0 && my > 0 && mx < WIDTH(ed) && my < HEIGHT(ed)) { AG_SetCursor(AG_TEXT_CURSOR); } if (!AG_WidgetFocused(ed)) return; if ((ed->flags & AG_EDITABLE_CURSOR_MOVING) == 0) return; mx += ed->x; AG_EditableMoveCursor(ed, mx, my, 1); ed->flags |= AG_EDITABLE_MARKPREF; } /* Overwrite the contents of the buffer with the given UTF-8 string. */ void AG_EditableSetString(AG_Editable *ed, const char *text) { AG_Variable *stringb; char *buf; AG_ObjectLock(ed); stringb = AG_GetVariable(ed, "string", &buf); if (text != NULL) { Strlcpy(buf, text, stringb->info.size); ed->pos = AG_LengthUTF8(text); } else { buf[0] = '\0'; ed->pos = 0; } AG_EditableBufferChanged(ed); AG_UnlockVariable(stringb); AG_ObjectUnlock(ed); } /* Overwrite the contents of the buffer with the given UCS-4 string. */ void AG_EditableSetStringUCS4(AG_Editable *ed, const Uint32 *ucs) { AG_Variable *stringb; char *buf; AG_ObjectLock(ed); stringb = AG_GetVariable(ed, "string", &buf); if (ucs != NULL) { AG_ExportUnicode(AG_UNICODE_TO_UTF8, buf, ucs, stringb->info.size); ed->pos = (int)AG_LengthUCS4(ucs); } else { buf[0] = '\0'; ed->pos = 0; } AG_EditableBufferChanged(ed); AG_UnlockVariable(stringb); AG_ObjectUnlock(ed); } void AG_EditablePrintf(AG_Editable *ed, const char *fmt, ...) { AG_Variable *stringb; va_list args; char *s; AG_ObjectLock(ed); stringb = AG_GetVariable(ed, "string", &s); if (fmt != NULL && fmt[0] != '\0') { va_start(args, fmt); Vsnprintf(s, stringb->info.size, fmt, args); va_end(args); ed->pos = AG_LengthUTF8(s); } else { s[0] = '\0'; ed->pos = 0; } AG_EditableBufferChanged(ed); AG_UnlockVariable(stringb); AG_ObjectUnlock(ed); } /* Return a duplicate of the current string. */ char * AG_EditableDupString(AG_Editable *ed) { AG_Variable *stringb; char *s, *sd; stringb = AG_GetVariable(ed, "string", &s); sd = Strdup(s); AG_UnlockVariable(stringb); return (sd); } /* Return a duplicate of the current buffer in UCS-4 format. */ Uint32 * AG_EditableDupStringUCS4(AG_Editable *ed) { AG_Variable *stringb; char *s; Uint32 *ucs; stringb = AG_GetVariable(ed, "string", &s); ucs = AG_ImportUnicode(AG_UNICODE_FROM_UTF8, s, 0); AG_UnlockVariable(stringb); return (ucs); } /* Copy text to a fixed-size buffer and always NUL-terminate. */ size_t AG_EditableCopyString(AG_Editable *ed, char *dst, size_t dst_size) { AG_Variable *stringb; size_t rv; char *s; AG_ObjectLock(ed); stringb = AG_GetVariable(ed, "string", &s); rv = Strlcpy(dst, s, dst_size); AG_UnlockVariable(stringb); AG_ObjectUnlock(ed); return (rv); } /* Copy text to a fixed-size buffer and always NUL-terminate. */ size_t AG_EditableCopyStringUCS4(AG_Editable *ed, Uint32 *dst, size_t dst_size) { AG_Variable *stringb; size_t rv; char *s; Uint32 *ucs; AG_ObjectLock(ed); stringb = AG_GetVariable(ed, "string", &s); ucs = AG_ImportUnicode(AG_UNICODE_FROM_UTF8, s, 0); rv = StrlcpyUCS4(dst, ucs, dst_size); AG_UnlockVariable(stringb); AG_ObjectUnlock(ed); return (rv); } /* Perform trivial conversion from string to int. */ int AG_EditableInt(AG_Editable *ed) { AG_Variable *stringb; char *text; int i; AG_ObjectLock(ed); stringb = AG_GetVariable(ed, "string", &text); i = atoi(text); AG_UnlockVariable(stringb); AG_ObjectUnlock(ed); return (i); } /* Perform trivial conversion from string to float . */ float AG_EditableFlt(AG_Editable *ed) { AG_Variable *stringb; char *text; float flt; AG_ObjectLock(ed); stringb = AG_GetVariable(ed, "string", &text); flt = (float)strtod(text, NULL); AG_UnlockVariable(stringb); AG_ObjectUnlock(ed); return (flt); } /* Perform trivial conversion from string to double. */ double AG_EditableDbl(AG_Editable *ed) { AG_Variable *stringb; char *text; double flt; AG_ObjectLock(ed); stringb = AG_GetVariable(ed, "string", &text); flt = strtod(text, NULL); AG_UnlockVariable(stringb); AG_ObjectUnlock(ed); return (flt); } static void Init(void *obj) { AG_Editable *ed = obj; WIDGET(ed)->flags |= AG_WIDGET_FOCUSABLE|AG_WIDGET_UNFOCUSED_MOTION; AG_BindString(ed, "string", ed->string, sizeof(ed->string)); ed->string[0] = '\0'; ed->encoding = AG_ENCODING_UTF8; ed->flags = AG_EDITABLE_BLINK_ON|AG_EDITABLE_MARKPREF; ed->pos = 0; ed->sel_x1 = 0; ed->sel_x2 = 0; ed->sel_edit = 0; ed->compose = 0; ed->wPre = 0; ed->hPre = agTextFontLineSkip + 2; ed->xCurs = 0; ed->yCurs = 0; ed->xCursPref = 0; ed->x = 0; ed->xMax = 10; ed->y = 0; ed->yMax = 1; ed->yVis = 1; ed->wheelTicks = 0; ed->repeatKey = 0; ed->repeatMod = KMOD_NONE; ed->repeatUnicode = 0; ed->ucsBuf = NULL; ed->ucsLen = 0; ed->r = AG_RECT(0,0,0,0); AG_SetEvent(ed, "window-keydown", KeyDown, NULL); AG_SetEvent(ed, "window-keyup", KeyUp, NULL); AG_SetEvent(ed, "window-mousebuttondown", MouseButtonDown, NULL); AG_SetEvent(ed, "window-mousebuttonup", MouseButtonUp, NULL); AG_SetEvent(ed, "window-mousemotion", MouseMotion, NULL); AG_SetEvent(ed, "widget-gainfocus", GainedFocus, NULL); AG_SetEvent(ed, "widget-lostfocus", LostFocus, NULL); AG_SetEvent(ed, "widget-hidden", LostFocus, NULL); AG_SetTimeout(&ed->toRepeat, RepeatTimeout, NULL, 0); AG_SetTimeout(&ed->toDelay, DelayTimeout, NULL, 0); AG_SetTimeout(&ed->toCursorBlink, BlinkTimeout, NULL, 0); } static void Destroy(void *obj) { AG_Editable *ed = obj; if (ed->flags & AG_EDITABLE_STATIC) Free(ed->ucsBuf); } AG_WidgetClass agEditableClass = { { "Agar(Widget:Editable)", sizeof(AG_Editable), { 0,0 }, Init, NULL, /* free */ Destroy, NULL, /* load */ NULL, /* save */ NULL /* edit */ }, Draw, SizeRequest, SizeAllocate };