2015-08-12 17:36:14 +00:00
|
|
|
/*
|
|
|
|
* This file is subject to the terms of the GFX License. If a copy of
|
|
|
|
* the license was not distributed with this file, you can obtain one at:
|
|
|
|
*
|
2018-10-01 15:32:39 +00:00
|
|
|
* http://ugfx.io/license.html
|
2015-08-12 17:36:14 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @file src/gwin/gwin_textedit.c
|
|
|
|
* @brief GWIN TextEdit widget header file
|
|
|
|
*/
|
|
|
|
|
2015-11-21 09:27:08 +00:00
|
|
|
#include "../../gfx.h"
|
2015-08-12 17:36:14 +00:00
|
|
|
|
|
|
|
#if GFX_USE_GWIN && GWIN_NEED_TEXTEDIT
|
|
|
|
|
|
|
|
#include "gwin_class.h"
|
2015-08-14 16:33:16 +00:00
|
|
|
#include <string.h>
|
2015-08-12 17:36:14 +00:00
|
|
|
|
2015-08-14 16:33:16 +00:00
|
|
|
// Some settings
|
2015-10-12 14:58:31 +00:00
|
|
|
#define TEXT_PADDING_LEFT 4
|
|
|
|
#define CURSOR_PADDING_LEFT 0
|
|
|
|
#define CURSOR_EXTRA_HEIGHT 1
|
2015-08-14 16:33:16 +00:00
|
|
|
|
|
|
|
// Macros to assist in data type conversions
|
|
|
|
#define gh2obj ((GTexteditObject *)gh)
|
|
|
|
#define gw2obj ((GTexteditObject *)gw)
|
|
|
|
|
2017-08-26 01:15:56 +00:00
|
|
|
static void TextEditRemoveChar(GHandle gh) {
|
2017-08-08 23:02:33 +00:00
|
|
|
char *p;
|
|
|
|
const char *q;
|
|
|
|
unsigned sz;
|
|
|
|
unsigned pos;
|
|
|
|
|
2017-08-26 01:15:56 +00:00
|
|
|
sz = strlen(gh2obj->w.text);
|
|
|
|
pos = gh2obj->cursorPos;
|
2018-11-17 08:50:25 +00:00
|
|
|
if (pos > sz)
|
|
|
|
pos = gh2obj->cursorPos = sz;
|
2017-08-26 01:15:56 +00:00
|
|
|
q = gh2obj->w.text+pos;
|
2017-08-08 23:02:33 +00:00
|
|
|
|
2017-08-26 01:15:56 +00:00
|
|
|
if (!(gh->flags & GWIN_FLG_ALLOCTXT)) {
|
2017-08-08 23:02:33 +00:00
|
|
|
// Allocate and then copy
|
|
|
|
if (!(p = gfxAlloc(sz)))
|
|
|
|
return;
|
|
|
|
if (pos)
|
2017-08-26 01:15:56 +00:00
|
|
|
memcpy(p, gh2obj->w.text, pos);
|
2017-08-08 23:02:33 +00:00
|
|
|
memcpy(p+pos, q+1, sz-pos);
|
2017-08-26 01:15:56 +00:00
|
|
|
gh->flags |= GWIN_FLG_ALLOCTXT;
|
2017-08-08 23:02:33 +00:00
|
|
|
} else {
|
|
|
|
// Copy and then reallocate
|
|
|
|
memcpy((char *)q, q+1, sz-pos);
|
2017-08-26 01:15:56 +00:00
|
|
|
if (!(p = gfxRealloc((char *)gh2obj->w.text, sz+1, sz))) // This should never fail as we are making it smaller
|
2017-08-08 23:02:33 +00:00
|
|
|
return;
|
|
|
|
}
|
2017-08-26 01:15:56 +00:00
|
|
|
gh2obj->w.text = p;
|
2017-08-08 23:02:33 +00:00
|
|
|
}
|
|
|
|
|
2018-06-23 03:02:07 +00:00
|
|
|
static gBool TextEditAddChars(GHandle gh, unsigned cnt) {
|
2017-08-08 23:02:33 +00:00
|
|
|
char *p;
|
|
|
|
const char *q;
|
|
|
|
unsigned sz;
|
|
|
|
unsigned pos;
|
|
|
|
|
|
|
|
// Get the size of the text buffer
|
2017-08-26 01:15:56 +00:00
|
|
|
sz = strlen(gh2obj->w.text)+1;
|
|
|
|
pos = gh2obj->cursorPos;
|
2018-11-17 08:50:25 +00:00
|
|
|
if (pos >= sz)
|
|
|
|
pos = gh2obj->cursorPos = sz-1;
|
2017-08-08 23:02:33 +00:00
|
|
|
|
2017-08-26 01:15:56 +00:00
|
|
|
if (!(gh->flags & GWIN_FLG_ALLOCTXT)) {
|
2017-08-08 23:02:33 +00:00
|
|
|
if (!(p = gfxAlloc(sz+cnt)))
|
2018-06-23 03:02:07 +00:00
|
|
|
return gFalse;
|
2017-08-26 01:15:56 +00:00
|
|
|
memcpy(p, gh2obj->w.text, pos);
|
|
|
|
memcpy(p+pos+cnt, gh2obj->w.text+pos, sz-pos);
|
|
|
|
gh->flags |= GWIN_FLG_ALLOCTXT;
|
|
|
|
gh2obj->w.text = p;
|
2017-08-08 23:02:33 +00:00
|
|
|
} else {
|
2017-08-26 01:15:56 +00:00
|
|
|
if (!(p = gfxRealloc((char *)gh2obj->w.text, sz, sz+cnt)))
|
2018-06-23 03:02:07 +00:00
|
|
|
return gFalse;
|
2017-08-26 01:15:56 +00:00
|
|
|
gh2obj->w.text = p;
|
2017-08-08 23:02:33 +00:00
|
|
|
q = p+pos;
|
|
|
|
p += sz;
|
|
|
|
while(--p >= q)
|
|
|
|
p[cnt] = p[0];
|
2015-08-14 16:33:16 +00:00
|
|
|
}
|
2018-06-23 03:02:07 +00:00
|
|
|
return gTrue;
|
2015-08-14 16:33:16 +00:00
|
|
|
}
|
2015-08-12 17:36:14 +00:00
|
|
|
|
2015-12-13 17:46:53 +00:00
|
|
|
// Function that allows to set the cursor to any position in the string
|
|
|
|
// This should be optimized. Currently it is an O(n^2) problem and therefore very
|
|
|
|
// slow. An optimized version would copy the behavior of mf_get_string_width()
|
|
|
|
// and do the comparation directly inside of that loop so we only iterate
|
|
|
|
// the string once.
|
2018-07-08 00:54:19 +00:00
|
|
|
static void TextEditMouseDown(GWidgetObject* gw, gCoord x, gCoord y) {
|
2018-11-03 00:51:23 +00:00
|
|
|
gU16 i = 0;
|
2015-12-13 17:46:53 +00:00
|
|
|
|
2015-12-18 23:51:47 +00:00
|
|
|
(void)y;
|
|
|
|
|
2015-12-13 17:46:53 +00:00
|
|
|
// Directly jump to the end of the string
|
|
|
|
if (x > gdispGetStringWidth(gw->text, gw->g.font)) {
|
|
|
|
gw2obj->cursorPos = strlen(gw->text);
|
|
|
|
|
|
|
|
// Otherwise iterate through each character and get the size in pixels to compare
|
|
|
|
} else {
|
|
|
|
i = 1;
|
|
|
|
while (gdispGetStringWidthCount(gw->text, gw->g.font, i) < x) {
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
gw2obj->cursorPos = i-1;
|
|
|
|
}
|
|
|
|
|
|
|
|
_gwinUpdate((GHandle)gw);
|
|
|
|
}
|
|
|
|
|
2015-10-12 14:58:31 +00:00
|
|
|
#if (GFX_USE_GINPUT && GINPUT_NEED_KEYBOARD) || GWIN_NEED_KEYBOARD
|
|
|
|
static void TextEditKeyboard(GWidgetObject* gw, GEventKeyboard* pke) {
|
2015-08-15 23:35:46 +00:00
|
|
|
// Only react on KEYDOWN events. Ignore KEYUP events.
|
2015-10-12 14:58:31 +00:00
|
|
|
if ((pke->keystate & GKEYSTATE_KEYUP) || !pke->bytecount)
|
2015-08-15 23:35:46 +00:00
|
|
|
return;
|
|
|
|
|
2015-08-14 16:33:16 +00:00
|
|
|
// Is it a special key?
|
|
|
|
if (pke->keystate & GKEYSTATE_SPECIAL) {
|
|
|
|
// Arrow keys to move the cursor
|
2018-11-03 00:51:23 +00:00
|
|
|
gwinTextEditSendSpecialKey(&gw->g, (gU8)pke->c[0]);
|
2017-08-26 01:15:56 +00:00
|
|
|
return;
|
2015-10-12 14:58:31 +00:00
|
|
|
|
2015-08-12 17:36:14 +00:00
|
|
|
}
|
|
|
|
|
2017-08-26 01:15:56 +00:00
|
|
|
gwinTextEditSendKey(&gw->g, pke->c, pke->bytecount);
|
2015-08-12 17:36:14 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static const gwidgetVMT texteditVMT = {
|
|
|
|
{
|
|
|
|
"TextEdit", // The class name
|
|
|
|
sizeof(GTexteditObject), // The object size
|
|
|
|
_gwidgetDestroy, // The destroy routine
|
|
|
|
_gwidgetRedraw, // The redraw routine
|
|
|
|
0, // The after-clear routine
|
|
|
|
},
|
|
|
|
gwinTexteditDefaultDraw, // default drawing routine
|
|
|
|
#if GINPUT_NEED_MOUSE
|
|
|
|
{
|
2015-12-13 17:46:53 +00:00
|
|
|
TextEditMouseDown, // Process mouse down events (NOT USED)
|
2015-08-12 17:36:14 +00:00
|
|
|
0, // Process mouse up events (NOT USED)
|
|
|
|
0, // Process mouse move events (NOT USED)
|
|
|
|
},
|
|
|
|
#endif
|
2015-10-12 14:58:31 +00:00
|
|
|
#if (GFX_USE_GINPUT && GINPUT_NEED_KEYBOARD) || GWIN_NEED_KEYBOARD
|
2015-08-12 17:36:14 +00:00
|
|
|
{
|
2015-08-16 11:53:47 +00:00
|
|
|
TextEditKeyboard // Process keyboard key down events
|
2015-08-12 17:36:14 +00:00
|
|
|
},
|
|
|
|
#endif
|
|
|
|
#if GINPUT_NEED_TOGGLE
|
|
|
|
{
|
|
|
|
0, // No toggle role
|
|
|
|
0, // Assign Toggles (NOT USED)
|
|
|
|
0, // Get Toggles (NOT USED)
|
|
|
|
0, // Process toggle off event (NOT USED)
|
|
|
|
0, // Process toggle on event (NOT USED)
|
|
|
|
},
|
|
|
|
#endif
|
|
|
|
#if GINPUT_NEED_DIAL
|
|
|
|
{
|
|
|
|
0, // No dial roles
|
|
|
|
0, // Assign Dials (NOT USED)
|
|
|
|
0, // Get Dials (NOT USED)
|
|
|
|
0, // Procees dial move events (NOT USED)
|
|
|
|
},
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
2018-11-03 00:51:23 +00:00
|
|
|
GHandle gwinGTexteditCreate(GDisplay* g, GTexteditObject* wt, GWidgetInit* pInit, gMemSize maxSize)
|
2015-08-12 17:36:14 +00:00
|
|
|
{
|
2015-08-14 16:33:16 +00:00
|
|
|
// Create the underlying widget
|
2015-08-16 11:53:47 +00:00
|
|
|
if (!(wt = (GTexteditObject*)_gwidgetCreate(g, &wt->w, pInit, &texteditVMT)))
|
2015-08-12 17:36:14 +00:00
|
|
|
return 0;
|
|
|
|
|
2015-08-16 11:53:47 +00:00
|
|
|
wt->maxSize = maxSize;
|
2015-08-14 16:33:16 +00:00
|
|
|
|
2017-08-08 23:02:33 +00:00
|
|
|
// Set cursor position
|
2015-10-12 14:58:31 +00:00
|
|
|
wt->cursorPos = strlen(wt->w.text);
|
2015-08-14 18:48:41 +00:00
|
|
|
|
2015-08-16 11:53:47 +00:00
|
|
|
gwinSetVisible(&wt->w.g, pInit->g.show);
|
2015-08-14 19:12:56 +00:00
|
|
|
|
2015-08-16 11:53:47 +00:00
|
|
|
return (GHandle)wt;
|
2015-08-14 19:12:56 +00:00
|
|
|
}
|
|
|
|
|
2017-08-26 01:15:56 +00:00
|
|
|
#if (GFX_USE_GINPUT && GINPUT_NEED_KEYBOARD) || GWIN_NEED_KEYBOARD
|
2018-11-03 00:51:23 +00:00
|
|
|
void gwinTextEditSendSpecialKey(GHandle gh, gU8 key) {
|
2018-11-17 08:50:25 +00:00
|
|
|
unsigned sz;
|
|
|
|
|
|
|
|
// Is it a valid handle?
|
|
|
|
if (gh->vmt != (gwinVMT*)&texteditVMT)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Check that cursor position is within buffer (in case text has been changed)
|
|
|
|
sz = strlen(gh2obj->w.text);
|
|
|
|
if (gh2obj->cursorPos > sz)
|
|
|
|
gh2obj->cursorPos = sz;
|
2017-08-26 01:15:56 +00:00
|
|
|
|
|
|
|
// Arrow keys to move the cursor
|
|
|
|
switch (key) {
|
|
|
|
case GKEY_LEFT:
|
|
|
|
if (!gh2obj->cursorPos)
|
|
|
|
return;
|
|
|
|
gh2obj->cursorPos--;
|
|
|
|
break;
|
|
|
|
case GKEY_RIGHT:
|
|
|
|
if (!gh2obj->w.text[gh2obj->cursorPos])
|
|
|
|
return;
|
|
|
|
gh2obj->cursorPos++;
|
|
|
|
break;
|
|
|
|
case GKEY_HOME:
|
|
|
|
if (!gh2obj->cursorPos)
|
|
|
|
return;
|
|
|
|
gh2obj->cursorPos = 0;
|
|
|
|
break;
|
|
|
|
case GKEY_END:
|
|
|
|
if (!gh2obj->w.text[gh2obj->cursorPos])
|
|
|
|
return;
|
2018-11-17 08:50:25 +00:00
|
|
|
gh2obj->cursorPos = sz;
|
2017-08-26 01:15:56 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_gwinUpdate(gh);
|
|
|
|
}
|
|
|
|
|
|
|
|
void gwinTextEditSendKey(GHandle gh, char *key, unsigned len) {
|
|
|
|
// Is it a valid handle?
|
|
|
|
if (gh->vmt != (gwinVMT*)&texteditVMT || !key || !len)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Normal key press
|
2018-11-03 00:51:23 +00:00
|
|
|
switch((gU8)key[0]) {
|
2017-08-26 01:15:56 +00:00
|
|
|
case GKEY_BACKSPACE:
|
|
|
|
// Backspace
|
|
|
|
if (!gh2obj->cursorPos)
|
|
|
|
return;
|
|
|
|
gh2obj->cursorPos--;
|
|
|
|
TextEditRemoveChar(gh);
|
|
|
|
break;
|
|
|
|
case GKEY_TAB:
|
|
|
|
case GKEY_LF:
|
|
|
|
case GKEY_CR:
|
|
|
|
// Move to the next field
|
|
|
|
_gwinMoveFocus();
|
|
|
|
return;
|
|
|
|
case GKEY_DEL:
|
|
|
|
// Delete
|
|
|
|
if (!gh2obj->w.text[gh2obj->cursorPos])
|
|
|
|
return;
|
|
|
|
TextEditRemoveChar(gh);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// Ignore any other control characters
|
2018-11-03 00:51:23 +00:00
|
|
|
if ((gU8)key[0] < GKEY_SPACE)
|
2017-08-26 01:15:56 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
// Keep the edit length to less than the maximum
|
|
|
|
if (gh2obj->maxSize && strlen(gh2obj->w.text)+len > gh2obj->maxSize)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Make space
|
|
|
|
if (TextEditAddChars(gh, len)) {
|
|
|
|
// Insert the characters
|
|
|
|
memcpy((char *)gh2obj->w.text+gh2obj->cursorPos, key, len);
|
|
|
|
gh2obj->cursorPos += len;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
_gwinUpdate(gh);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2015-11-03 19:39:16 +00:00
|
|
|
void gwinTexteditDefaultDraw(GWidgetObject* gw, void* param)
|
2015-08-12 17:36:14 +00:00
|
|
|
{
|
2015-12-18 23:51:18 +00:00
|
|
|
const char* p;
|
2018-07-08 00:54:19 +00:00
|
|
|
gCoord cpos, tpos;
|
2015-12-18 23:51:18 +00:00
|
|
|
const GColorSet* pcol;
|
|
|
|
|
2015-08-14 16:33:16 +00:00
|
|
|
(void)param;
|
2015-08-12 17:36:14 +00:00
|
|
|
|
|
|
|
// Is it a valid handle?
|
2015-10-12 14:58:31 +00:00
|
|
|
if (gw->g.vmt != (gwinVMT*)&texteditVMT)
|
2015-08-12 17:36:14 +00:00
|
|
|
return;
|
|
|
|
|
2015-08-14 16:33:16 +00:00
|
|
|
// Retrieve colors
|
2015-12-18 23:51:18 +00:00
|
|
|
if ((gw->g.flags & GWIN_FLG_SYSENABLED))
|
|
|
|
pcol = &gw->pstyle->enabled;
|
|
|
|
else
|
|
|
|
pcol = &gw->pstyle->disabled;
|
2015-10-12 14:58:31 +00:00
|
|
|
|
|
|
|
// Adjust the text position so the cursor fits in the window
|
|
|
|
p = gw->text;
|
|
|
|
if (!gw2obj->cursorPos)
|
|
|
|
tpos = 0;
|
|
|
|
else {
|
|
|
|
for(cpos = gw2obj->cursorPos; ; p++, cpos--) {
|
|
|
|
tpos = gdispGetStringWidthCount(p, gw->g.font, cpos);
|
|
|
|
if (tpos < gw->g.width-(TEXT_PADDING_LEFT+CURSOR_PADDING_LEFT))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2015-08-12 17:36:14 +00:00
|
|
|
|
2015-08-14 16:33:16 +00:00
|
|
|
// Render background and string
|
2015-10-12 14:58:31 +00:00
|
|
|
#if TEXT_PADDING_LEFT
|
2015-12-18 23:51:18 +00:00
|
|
|
gdispGFillArea(gw->g.display, gw->g.x, gw->g.y, TEXT_PADDING_LEFT, gw->g.height, pcol->fill);
|
2015-10-12 14:58:31 +00:00
|
|
|
#endif
|
2018-07-08 02:19:30 +00:00
|
|
|
gdispGFillStringBox(gw->g.display, gw->g.x + TEXT_PADDING_LEFT, gw->g.y, gw->g.width-TEXT_PADDING_LEFT, gw->g.height, p, gw->g.font, pcol->text, pcol->fill, gJustifyLeft);
|
2015-08-14 21:51:28 +00:00
|
|
|
|
2015-08-14 16:33:16 +00:00
|
|
|
// Render cursor (if focused)
|
2015-08-14 18:48:41 +00:00
|
|
|
if (gwinGetFocus() == (GHandle)gw) {
|
2015-08-14 16:33:16 +00:00
|
|
|
// Calculate cursor stuff
|
|
|
|
|
|
|
|
// Draw cursor
|
2018-07-08 02:50:05 +00:00
|
|
|
tpos += gw->g.x + CURSOR_PADDING_LEFT + TEXT_PADDING_LEFT + gdispGetFontMetric(gw->g.font, gFontBaselineX)/2;
|
|
|
|
cpos = (gw->g.height - gdispGetFontMetric(gw->g.font, gFontHeight))/2 - CURSOR_EXTRA_HEIGHT;
|
2015-12-18 23:51:18 +00:00
|
|
|
gdispGDrawLine(gw->g.display, tpos, gw->g.y + cpos, tpos, gw->g.y + gw->g.height - cpos, pcol->edge);
|
2015-08-14 16:33:16 +00:00
|
|
|
}
|
2015-10-12 14:58:31 +00:00
|
|
|
|
|
|
|
// Render border
|
2015-12-18 23:51:18 +00:00
|
|
|
gdispGDrawBox(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, pcol->edge);
|
2015-10-12 14:58:31 +00:00
|
|
|
|
2015-10-19 05:16:24 +00:00
|
|
|
// Render highlighted border if focused
|
2015-12-18 23:51:18 +00:00
|
|
|
_gwidgetDrawFocusRect(gw, 0, 0, gw->g.width, gw->g.height);
|
2015-10-12 14:58:31 +00:00
|
|
|
|
2015-08-12 17:36:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#undef gh2obj
|
|
|
|
#undef gw2obj
|
|
|
|
|
|
|
|
#endif // GFX_USE_GWIN && GWIN_NEED_TEXTEDIT
|