ugfx/src/gwin/gwin_keyboard.c

588 lines
18 KiB
C

/*
* 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:
*
* http://ugfx.org/license.html
*/
/**
* @file src/gwin/gwin_keyboard.c
* @brief GWIN sub-system virtual keyboard code
*/
#include "../../gfx.h"
#if GFX_USE_GWIN && GWIN_NEED_KEYBOARD
#include "gwin_class.h"
#include "gwin_keyboard_layout.h"
typedef uint8_t utf8;
typedef uint16_t utf16;
typedef uint32_t utf32;
// A character code - note this is not UTF-32 but a representation of the UTF-8 code stream for a single character.
typedef uint32_t ucode;
static GSourceHandle AllKeyboards;
// Get the length of a UTF-8 string
static int UTF8StrLen(const utf8 *s) {
int len;
len = 0;
if (s) {
while (*s) {
len++;
if (!(s[0] & 0x80))
s++;
else if ((s[0] & 0xE0) == 0xC0 && (s[1] & 0xC0) == 0x80)
s+=2;
else if ((s[0] & 0xF0) == 0xE0 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80)
s+=3;
else if ((s[0] & 0xF8) == 0xF0 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80 && (s[3] & 0xC0) == 0x80)
s+=4;
else
// Invalid UTF-8 sequence - assume a single byte
s++;
}
}
return len;
}
// Return the nth character of a UTF8 string
static ucode UTF8CharAt(const utf8 *s, int n) {
ucode u;
u = 0;
if (!s) return 0;
while(*s) {
if (!(s[0] & 0x80)) {
u = s[0];
s++;
} else if ((s[0] & 0xE0) == 0xC0 && (s[1] & 0xC0) == 0x80) {
u = s[1] | ((ucode)s[0] << 8);
s+=2;
} else if ((s[0] & 0xF0) == 0xE0 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) {
u = s[2] | ((ucode)s[1] << 8) | ((ucode)s[0] << 16);
s+=3;
} else if ((s[0] & 0xF8) == 0xF0 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80 && (s[3] & 0xC0) == 0x80) {
u = s[3] | ((ucode)s[2] << 8) | ((ucode)s[1] << 16) | ((ucode)s[0] << 24);
s+=4;
} else {
// Invalid UTF-8 sequence - assume a single byte
u = s[0];
s++;
}
if (--n < 0)
return u;
}
return 0;
}
// Convert a ucode to a UTF8 string (with no NULL on the end). Returns the number of bytes.
static unsigned UCode2UTF8(utf8 *dst, ucode u) {
if (!(u & 0xFFFFFF00)) {
dst[0] = u;
return 1;
}
if (!(u & 0xFFFF0000)) {
dst[0] = u >> 8;
dst[1] = u;
return 2;
}
if (!(u & 0xFF000000)) {
dst[0] = u >> 16;
dst[1] = u >> 8;
dst[2] = u;
return 3;
}
dst[0] = u >> 24;
dst[1] = u >> 16;
dst[2] = u >> 8;
dst[3] = u;
return 4;
}
static int NumKeyRows(const char **keyset) {
int len;
len = 0;
while(*keyset++)
len++;
return len;
}
static void SendVirtualKeyEventToListener(GSourceListener *psl, GKeyboardObject *gk) {
GEventKeyboard *pe;
const GVSpecialKey *skey;
unsigned i;
// If there is no event buffer just mark a missed event
if (!(pe = (GEventKeyboard *)geventGetEventBuffer(psl))) {
// This listener is missing - save the meta events that have happened
psl->srcflags |= GKEYSTATE_MISSED_EVENT;
return;
}
// The virtual keyboard can't generate repeats
//if ((psl->listenflags & GLISTEN_KEYREPEATSOFF) && (k->keystate & GKEYSTATE_REPEAT))
// return;
// The virtual keyboard can't generate special keys
//if ((psl->listenflags & GLISTEN_KEYNOSPECIALS) && (k->keystate & GKEYSTATE_SPECIAL))
// return;
// The virtual keyboard treats a key release as a keydown
//if (!(psl->listenflags & GLISTEN_KEYUP) && (k->keystate & GKEYSTATE_KEYUP))
// k->cntc = 0;
// The virtual keyboard has no transitions
//if (!(psl->listenflags & GLISTEN_KEYTRANSITIONS) && !k->cntc)
// return;
pe->type = GEVENT_KEYBOARD;
if (gk->key < 0x20) {
skey = &gk->keytable->skeys[gk->key-1];
for(i=0; skey->sendkey[i]; i++)
pe->c[i] = skey->sendkey[i];
} else
i = UCode2UTF8((utf8 *)pe->c, gk->key);
pe->bytecount = i;
for(; i < 8; i++)
pe->c[i] = 0;
pe->keystate = psl->srcflags;
psl->srcflags = 0;
geventSendEvent(psl);
}
static void SendVirtualKeyEvent(GKeyboardObject *gk) {
GSourceListener *psl;
// Send to the "All Keyboards" source listeners
psl = 0;
while ((psl = geventGetSourceListener(AllKeyboards, psl)))
SendVirtualKeyEventToListener(psl, gk);
// Send to the keyboard specific source listeners
psl = 0;
while ((psl = geventGetSourceListener((GSourceHandle)gk, psl)))
SendVirtualKeyEventToListener(psl, gk);
}
#if GINPUT_NEED_MOUSE
// Find the key from the keyset and the x, y position
static void KeyFindKey(GKeyboardObject *gk, gCoord x, gCoord y) {
const utf8 *krow;
fixed f;
int idx;
if (x < 0 || y < 0 || x >= gk->w.g.width || y >= gk->w.g.height) {
gk->keyrow = gk->keycol = GKEY_BAD_ROWCOL;
return;
}
// Get the y parameters
f = FIXED(gk->w.g.height) / NumKeyRows(gk->keyset);
gk->keyrow = FIXED(y) / f;
gk->keyy = NONFIXED(f * gk->keyrow + FIXED0_5);
gk->keycy = NONFIXED(f * (gk->keyrow+1) + FIXED0_5) - gk->keyy;
// Get the current row
krow = (const utf8 *)gk->keyset[gk->keyrow];
// Get the x parameters
f = FIXED(gk->w.g.width) / UTF8StrLen(krow);
gk->keycol = FIXED(x) / f;
// Get the key
gk->key = UTF8CharAt(krow, gk->keycol);
// Amalgamate identical keys into one big key
idx = gk->keycol;
while(gk->keycol > 0 && UTF8CharAt(krow, gk->keycol-1) == gk->key)
gk->keycol--;
while(UTF8CharAt(krow, ++idx) == gk->key);
gk->keyx = NONFIXED(f * gk->keycol + FIXED0_5);
gk->keycx = NONFIXED(f * idx + FIXED0_5) - gk->keyx;
}
// A mouse up has occurred (it may or may not be over the button)
static void KeyMouseUp(GWidgetObject *gw, gCoord x, gCoord y) {
#define gk ((GKeyboardObject *)gw)
KeyFindKey(gk, x, y);
// Do we have a valid key?
if (gk->keyrow == GKEY_BAD_ROWCOL) {
if (gk->lastkeyrow != GKEY_BAD_ROWCOL) {
gw->g.flags |= GKEYBOARD_FLG_QUICKUPDATE;
_gwinUpdate((GHandle)gw);
}
return;
}
// We are turning off the display of the key
gk->keyrow = gk->keycol = GKEY_BAD_ROWCOL;
// Is this one of the special keys
if (gk->key < 0x20) {
// This is a special key
const GVSpecialKey *skey;
skey = &gk->keytable->skeys[gk->key - 1];
if ((skey->flags & GVKEY_SINGLESET)) {
// Single character switch to a new layout
gk->keyset = gk->keytable->ksets[skey->newset];
gk->w.g.flags &= ~(GKEYBOARD_FLG_QUICKUPDATE|GKEYBOARD_FLG_REVERTSET);
gk->w.g.flags |= GKEYBOARD_FLG_REVERTSET;
} else if ((skey->flags & GVKEY_LOCKSET)) {
// Locked switch to a new layout
gk->keyset = gk->keytable->ksets[skey->newset];
gk->w.g.flags &= ~(GKEYBOARD_FLG_QUICKUPDATE|GKEYBOARD_FLG_REVERTSET);
} else if ((gk->w.g.flags & GKEYBOARD_FLG_REVERTSET)) {
// Revert to default layout
gk->keyset = gk->keytable->ksets[0];
gk->w.g.flags &= ~(GKEYBOARD_FLG_QUICKUPDATE|GKEYBOARD_FLG_REVERTSET);
} else {
// Just turning off a key
gw->g.flags |= GKEYBOARD_FLG_QUICKUPDATE;
}
// Send the key if required
if (skey->sendkey && skey->sendkey[0])
SendVirtualKeyEvent(gk);
// Update the display
_gwinUpdate((GHandle)gw);
return;
}
// Do we need to revert to the standard layout?
if ((gk->w.g.flags & GKEYBOARD_FLG_REVERTSET)) {
gk->keyset = gk->keytable->ksets[0];
gk->w.g.flags &= ~(GKEYBOARD_FLG_QUICKUPDATE|GKEYBOARD_FLG_REVERTSET);
} else {
gw->g.flags |= GKEYBOARD_FLG_QUICKUPDATE;
}
// Send the key
SendVirtualKeyEvent(gk);
// Update the display
_gwinUpdate((GHandle)gw);
}
// A mouse move has occurred (it may or may not be over the button)
static void KeyMouseMove(GWidgetObject *gw, gCoord x, gCoord y) {
#define gk ((GKeyboardObject *)gw)
KeyFindKey(gk, x, y);
if (gk->keyrow != gk->lastkeyrow || gk->keycol != gk->lastkeycol) {
gk->w.g.flags |= GKEYBOARD_FLG_QUICKUPDATE;
_gwinUpdate((GHandle)gw);
}
#undef gk
}
#endif
extern const GVKeyTable GWIN_KEYBOARD_DEFAULT_LAYOUT;
// The button VMT table
static const gwidgetVMT keyboardVMT = {
{
"VKeyboard", // The classname
sizeof(GKeyboardObject), // The object size
_gwidgetDestroy, // The destroy routine
_gwidgetRedraw, // The redraw routine
0, // The after-clear routine
},
gwinKeyboardDraw_Normal, // The default drawing routine
#if GINPUT_NEED_MOUSE
{
KeyMouseMove, // Process mouse down events
KeyMouseUp, // Process mouse up events
KeyMouseMove, // Process mouse move events
},
#endif
#if GINPUT_NEED_KEYBOARD || GWIN_NEED_KEYBOARD
{
0 // Process keyboard events
},
#endif
#if GINPUT_NEED_TOGGLE
{
0, // No toggle roles
0, // Assign Toggles
0, // Get Toggles
0, // Process toggle off events
0, // Process toggle on events
},
#endif
#if GINPUT_NEED_DIAL
{
0, // No dial roles
0, // Assign Dials (NOT USED)
0, // Get Dials (NOT USED)
0, // Process dial move events (NOT USED)
},
#endif
};
GHandle gwinGKeyboardCreate(GDisplay *g, GKeyboardObject *gk, const GWidgetInit *pInit) {
if (!(gk = (GKeyboardObject *)_gwidgetCreate(g, &gk->w, pInit, &keyboardVMT)))
return 0;
gk->keytable = &GWIN_KEYBOARD_DEFAULT_LAYOUT;
gk->keyset = gk->keytable->ksets[0];
gk->lastkeyrow = gk->lastkeycol = gk->keyrow = gk->keycol = GKEY_BAD_ROWCOL;
if (!AllKeyboards)
AllKeyboards = ginputGetKeyboard(GKEYBOARD_ALL_INSTANCES);
gwinSetVisible((GHandle)gk, pInit->g.show);
return (GHandle)gk;
}
GSourceHandle gwinKeyboardGetEventSource(GHandle gh) {
if (gh->vmt != (gwinVMT *)&keyboardVMT)
return 0;
return (GSourceHandle)gh;
}
void gwinKeyboardSetLayout(GHandle gh, const struct GVKeyTable *layout) {
#define gk ((GKeyboardObject *)gh)
if (gh->vmt != (gwinVMT *)&keyboardVMT)
return;
if (!layout)
layout = &GWIN_KEYBOARD_DEFAULT_LAYOUT;
gk->keytable = layout;
gk->keyset = gk->keytable->ksets[0];
gk->lastkeyrow = gk->lastkeycol = gk->keyrow = gk->keycol = GKEY_BAD_ROWCOL;
gk->w.g.flags &= ~(GKEYBOARD_FLG_QUICKUPDATE|GKEYBOARD_FLG_REVERTSET);
gwinRedraw(gh);
#undef gk
}
/*----------------------------------------------------------
* Custom Draw Routines
*----------------------------------------------------------*/
/*
static const GColorSet *getDrawColors(GWidgetObject *gw) {
if (!(gw->g.flags & GWIN_FLG_SYSENABLED)) return &gw->pstyle->disabled;
if ((gw->g.flags & GBUTTON_FLG_PRESSED)) return &gw->pstyle->pressed;
return &gw->pstyle->enabled;
}
*/
void gwinKeyboardDraw_Normal(GWidgetObject *gw, void *param) {
#define gk ((GKeyboardObject *)gw)
char cap[5];
const char *pcap;
const utf8 *krow;
gCoord x, y, cx, cy;
uint8_t rows, cols, row, col, kcols;
ucode key;
fixed fx, fy;
const GColorSet *pcol;
(void) param;
// Make sure that this is a keyboard widget object
if (gw->g.vmt != (gwinVMT *)&keyboardVMT)
return;
// Get the y parameters
rows = NumKeyRows(gk->keyset);
fy = FIXED(gk->w.g.height) / rows;
for (row = 0; row < rows; row++) {
y = NONFIXED(fy * row + FIXED0_5);
cy = NONFIXED(fy * (row+1) + FIXED0_5) - y;
// Get the current row
krow = (const utf8 *)gk->keyset[row];
// Get the x parameters
cols = UTF8StrLen(krow);
fx = FIXED(gk->w.g.width) / cols;
for (col = 0; col < cols; col=kcols) {
// Get the correct color set
if (!(gk->w.g.flags & GWIN_FLG_SYSENABLED))
pcol = &gk->w.pstyle->disabled;
else
pcol = &gk->w.pstyle->enabled;
// Get the key
key = UTF8CharAt(krow, col);
// Fuse identical keys into one big key
kcols = col+1;
while (UTF8CharAt(krow, kcols) == key)
kcols++;
// If quick update needed and keyboard already drawn (if not use this flag, then bug when screen touched before keyboard was drawn)
if ( (gk->w.g.flags & GKEYBOARD_FLG_QUICKUPDATE) && !(gk->w.g.flags & GWIN_FLG_BGREDRAW) ) {
// If key pressed
if ( (gk->keyrow != GKEY_BAD_ROWCOL) && (gk->keycol != GKEY_BAD_ROWCOL) ) {
// And previous key have
if ( (gk->lastkeyrow != GKEY_BAD_ROWCOL) && (gk->lastkeycol != GKEY_BAD_ROWCOL) ) {
if (gk->lastkeyrow == row && gk->lastkeycol == col) {
// If keyboard has no "disabled" color
if (pcol != &gk->w.pstyle->disabled)
pcol = &gk->w.pstyle->enabled;
gk->lastkeyrow = gk->lastkeycol = GKEY_BAD_ROWCOL;
} else {
continue;
}
}
// If no previous key
else {
if (gk->keyrow == row && gk->keycol == col) {
if (pcol != &gk->w.pstyle->disabled)
pcol = &gk->w.pstyle->pressed;
gk->lastkeyrow = row;
gk->lastkeycol = col;
}
else if (gk->lastkeyrow == row && gk->lastkeycol == col)
{
if (pcol != &gk->w.pstyle->disabled) pcol = &gk->w.pstyle->enabled;
}
else continue;
}
}
// If key up, and need clear the previous key
else if ( (gk->lastkeyrow != GKEY_BAD_ROWCOL) && (gk->lastkeycol != GKEY_BAD_ROWCOL) )
{
if ( (gk->lastkeyrow == row) && (gk->lastkeycol == col) )
{
if (pcol != &gk->w.pstyle->disabled) pcol = &gk->w.pstyle->enabled;
}
else continue;
}
}
else
{
gk->lastkeyrow = gk->lastkeycol = GKEY_BAD_ROWCOL;
}
x = NONFIXED(fx * col + FIXED0_5);
cx = NONFIXED(fx * kcols + FIXED0_5) - x;
if (key < 0x20) {
pcap = gk->keytable->skeys[key-1].keycap;
} else {
cap[UCode2UTF8((utf8 *)cap, key)] = 0;
pcap = cap;
}
switch(*pcap) {
case '\001': // Shift (up-arrow)
gdispGFillArea(gw->g.display, gw->g.x+x, gw->g.y+y, cx, cy, pcol->fill);
gdispGDrawLine(gw->g.display, gw->g.x+x +cx/4, gw->g.y+y+cy/2, gw->g.x+x+cx/2, gw->g.y+y +cy/4, pcol->text); /* / \ */
gdispGDrawLine(gw->g.display, gw->g.x+x+cx -cx/4, gw->g.y+y+cy/2, gw->g.x+x+cx/2, gw->g.y+y +cy/4, pcol->text);
gdispGDrawLine(gw->g.display, gw->g.x+x +cx/4, gw->g.y+y+cy/2, gw->g.x+x+cx/2-cx/6, gw->g.y+y+cy/2, pcol->text); /* _ _ */
gdispGDrawLine(gw->g.display, gw->g.x+x+cx -cx/4, gw->g.y+y+cy/2, gw->g.x+x+cx/2+cx/6, gw->g.y+y+cy/2, pcol->text);
gdispGDrawLine(gw->g.display, gw->g.x+x+cx/2-cx/6, gw->g.y+y+cy/2, gw->g.x+x+cx/2-cx/6, gw->g.y+y+cy -cy/3, pcol->text); /* || */
gdispGDrawLine(gw->g.display, gw->g.x+x+cx/2+cx/6, gw->g.y+y+cy/2, gw->g.x+x+cx/2+cx/6, gw->g.y+y+cy -cy/3, pcol->text);
gdispGDrawLine(gw->g.display, gw->g.x+x+cx/2-cx/6, gw->g.y+y+cy -cy/3, gw->g.x+x+cx/2+cx/6, gw->g.y+y+cy -cy/3, pcol->text); /* _ */
break;
case '\002': // Shift locked (underlined up-arrow)
gdispGFillArea(gw->g.display, gw->g.x+x, gw->g.y+y, cx, cy, pcol->fill);
gdispGDrawLine(gw->g.display, gw->g.x+x +cx/4, gw->g.y+y+cy/2, gw->g.x+x+cx/2, gw->g.y+y +cy/4, pcol->text); /* / \ */
gdispGDrawLine(gw->g.display, gw->g.x+x+cx -cx/4, gw->g.y+y+cy/2, gw->g.x+x+cx/2, gw->g.y+y +cy/4, pcol->text);
gdispGDrawLine(gw->g.display, gw->g.x+x +cx/4, gw->g.y+y+cy/2, gw->g.x+x+cx/2-cx/6, gw->g.y+y+cy/2, pcol->text); /* _ _ */
gdispGDrawLine(gw->g.display, gw->g.x+x+cx -cx/4, gw->g.y+y+cy/2, gw->g.x+x+cx/2+cx/6, gw->g.y+y+cy/2, pcol->text);
gdispGDrawLine(gw->g.display, gw->g.x+x+cx/2-cx/6, gw->g.y+y+cy/2, gw->g.x+x+cx/2-cx/6, gw->g.y+y+cy -cy/3, pcol->text); /* || */
gdispGDrawLine(gw->g.display, gw->g.x+x+cx/2+cx/6, gw->g.y+y+cy/2, gw->g.x+x+cx/2+cx/6, gw->g.y+y+cy -cy/3, pcol->text);
gdispGDrawLine(gw->g.display, gw->g.x+x+cx/2-cx/6, gw->g.y+y+cy -cy/3, gw->g.x+x+cx/2+cx/6, gw->g.y+y+cy -cy/3, pcol->text); /* _ */
gdispGDrawLine(gw->g.display, gw->g.x+x+cx/2-cx/5, gw->g.y+y+cy -cy/4, gw->g.x+x+cx/2+cx/5, gw->g.y+y+cy -cy/4, pcol->text); /* ___ */
break;
case '\t': // Tabulator
gdispGFillArea(gw->g.display, gw->g.x+x, gw->g.y+y, cx, cy, pcol->fill);
gdispGDrawLine(gw->g.display, gw->g.x+x+1, gw->g.y+y+1, gw->g.x+x+cx-1, gw->g.y+y+cy/2, pcol->text);
gdispGDrawLine(gw->g.display, gw->g.x+x+1, gw->g.y+y+cy-1, gw->g.x+x+cx-1, gw->g.y+y+cy/2, pcol->text);
gdispGDrawLine(gw->g.display, gw->g.x+x+cx-1, gw->g.y+y+1, gw->g.x+x+cx-1, gw->g.y+y+cy-1, pcol->text);
break;
case '\b': // Backspace
gdispGFillArea(gw->g.display, gw->g.x+x, gw->g.y+y, cx, cy, pcol->fill);
gdispGDrawLine(gw->g.display, gw->g.x+x+ cx/8, gw->g.y+y+cy/2, gw->g.x+x+cx/2, gw->g.y+y +cy/3, pcol->text); /* / */
gdispGDrawLine(gw->g.display, gw->g.x+x+ cx/8, gw->g.y+y+cy/2, gw->g.x+x+cx-cx/8, gw->g.y+y+cy/2, pcol->text); /* -- */
gdispGDrawLine(gw->g.display, gw->g.x+x+ cx/8, gw->g.y+y+cy/2, gw->g.x+x+cx/2, gw->g.y+y+cy -cy/3, pcol->text); /* \ */
break;
case '\r': // Enter
gdispGFillArea(gw->g.display, gw->g.x+x, gw->g.y+y, cx, cy, pcol->fill);
gdispGDrawLine(gw->g.display, gw->g.x+x+(cx/3)*2, gw->g.y+y+cy/2, gw->g.x+x+(cx/3)*2, gw->g.y+y+cy/5, pcol->text); /* | */
gdispGDrawLine(gw->g.display, gw->g.x+x+ cx/3, gw->g.y+y+cy/2, gw->g.x+x+cx/3 +cx/8, gw->g.y+y+cy/3, pcol->text); /* / */
gdispGDrawLine(gw->g.display, gw->g.x+x+ cx/3, gw->g.y+y+cy/2, gw->g.x+x+(cx/3)*2, gw->g.y+y+cy/2, pcol->text); /* -- */
gdispGDrawLine(gw->g.display, gw->g.x+x+ cx/3, gw->g.y+y+cy/2, gw->g.x+x+cx/3 +cx/8, gw->g.y+y+cy -cy/3, pcol->text); /* \ */
break;
default: // Regular character
gdispGFillStringBox(gw->g.display, gw->g.x+x, gw->g.y+y, cx, cy, pcap, gw->g.font, pcol->text, pcol->fill, gJustifyCenter);
break;
}
// Draw the frame (border around the entire widget)
gdispGDrawBox(gw->g.display, gw->g.x+x, gw->g.y+y, cx, cy, pcol->edge);
// If key up and we already cleared the previous key
if ( (gk->keyrow == GKEY_BAD_ROWCOL) && (gk->keycol == GKEY_BAD_ROWCOL) && (gk->lastkeyrow == row) && (gk->lastkeycol == col) ) {
gk->lastkeyrow = gk->lastkeycol = GKEY_BAD_ROWCOL;
return;
}
// Just quit the cycle if we did all the work in order not to waste any CPU time
if ( (row >= gk->keyrow && col >= gk->keycol) && (row >= gk->lastkeyrow && col >= gk->lastkeycol) ) {
return;
}
}
}
#undef gk
}
#if !(GFX_USE_GINPUT && GINPUT_NEED_KEYBOARD)
GSourceHandle ginputGetKeyboard(unsigned instance) {
if (instance == GKEYBOARD_ALL_INSTANCES)
return (GSourceHandle)&AllKeyboards;
return 0;
}
#endif
#endif /* GFX_USE_GWIN && GWIN_NEED_KEYBOARD */