aboutsummaryrefslogtreecommitdiffstats
path: root/src/gwin/gwin_keyboard.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gwin/gwin_keyboard.c')
-rw-r--r--src/gwin/gwin_keyboard.c471
1 files changed, 471 insertions, 0 deletions
diff --git a/src/gwin/gwin_keyboard.c b/src/gwin/gwin_keyboard.c
new file mode 100644
index 00000000..6b9ab524
--- /dev/null
+++ b/src/gwin/gwin_keyboard.c
@@ -0,0 +1,471 @@
+/*
+ * 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"
+
+
+#define GKEYBOARD_FLG_REVERTSET (GWIN_FIRST_CONTROL_FLAG<<0)
+#define GKEYBOARD_FLG_QUICKUPDATE (GWIN_FIRST_CONTROL_FLAG<<1)
+
+#define BAD_ROWCOL 255
+
+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;
+
+// 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 SendKeyboardEventToListener(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 SendKeyboardEvent(GKeyboardObject *gk) {
+ GSourceListener *psl;
+
+ // Send to the keyboard specific source listeners
+ psl = 0;
+ while ((psl = geventGetSourceListener((GSourceHandle)gk, psl)))
+ SendKeyboardEventToListener(psl, gk);
+}
+
+
+#if GINPUT_NEED_MOUSE
+ // Find the key from the keyset and the x, y position
+ static void FindKey(GKeyboardObject *gk, coord_t x, coord_t 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 = 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 MouseUp(GWidgetObject *gw, coord_t x, coord_t y) {
+ #define gk ((GKeyboardObject *)gw)
+
+ FindKey(gk, x, y);
+
+ // Do we have a valid key?
+ if (gk->keyrow == BAD_ROWCOL) {
+ if (gk->lastkeyrow != 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 = 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])
+ SendKeyboardEvent(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
+ SendKeyboardEvent(gk);
+
+ // Update the display
+ _gwinUpdate((GHandle)gw);
+ }
+
+ // A mouse move has occurred (it may or may not be over the button)
+ static void MouseMove(GWidgetObject *gw, coord_t x, coord_t y) {
+ #define gk ((GKeyboardObject *)gw)
+
+ FindKey(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 GVKeyTable GWIN_KEYBOARD_DEFAULT_LAYOUT;
+void gwinKeyboardDraw_Normal(GWidgetObject *gw, void *param);
+
+// 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
+ {
+ MouseMove, // Process mouse down events
+ MouseUp, // Process mouse up events
+ MouseMove, // Process mouse move 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 = BAD_ROWCOL;
+ 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, 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 = 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;
+ coord_t x, y, cx, cy;
+ uint8_t rows, cols, row, col, kcols;
+ ucode key;
+ fixed fx, fy;
+ const GColorSet *pcol;
+ (void) param;
+
+ 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) {
+
+ // Choose the color
+ if (!(gk->w.g.flags & GWIN_FLG_SYSENABLED))
+ pcol = &gk->w.pstyle->disabled;
+ else if (gk->keyrow == row && gk->keycol == col)
+ pcol = &gk->w.pstyle->pressed;
+ else
+ pcol = &gk->w.pstyle->enabled;
+
+ // Get the key
+ key = UTF8CharAt(krow, col);
+
+ // Amalgamate identical keys into one big key
+ kcols = col+1;
+ while(UTF8CharAt(krow, kcols) == key)
+ kcols++;
+ 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/2, gw->g.y+y+1, gw->g.x+x+1, gw->g.y+y+cy-1, pcol->text);
+ gdispGDrawLine(gw->g.display, gw->g.x+x+cx/2, gw->g.y+y+1, gw->g.x+x+cx-1, gw->g.y+y+cy-1, pcol->text);
+ break;
+ case '\002': // Shift locked (up arrow - bold)
+ 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/2, gw->g.y+y, gw->g.x+x+1, gw->g.y+y+cy-1, pcol->text);
+ gdispGDrawLine(gw->g.display, gw->g.x+x+cx/2, gw->g.y+y, gw->g.x+x+cx-1, gw->g.y+y+cy-1, pcol->text);
+ gdispGDrawBox(gw->g.display, gw->g.x+x, gw->g.y+y, cx, cy, pcol->edge);
+ break;
+ case '\t':
+ 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':
+ 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+cy/2, gw->g.x+x+cx-1, gw->g.y+y+1, pcol->text);
+ gdispGDrawLine(gw->g.display, gw->g.x+x+1, gw->g.y+y+cy/2, gw->g.x+x+cx-1, gw->g.y+y+cy-1, pcol->text);
+ break;
+ case '\r':
+ 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+cy/2, 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+cy/2, gw->g.x+x+cx-1, gw->g.y+y+1, pcol->text);
+ break;
+ default:
+ gdispGFillStringBox(gw->g.display, gw->g.x+x, gw->g.y+y, cx, cy, pcap, gw->g.font, pcol->text, pcol->fill, justifyCenter);
+ }
+ }
+ }
+
+ #undef gk
+}
+
+#endif /* GFX_USE_GWIN && GWIN_NEED_KEYBOARD */