From 1ce3f20fa49830fee1a03d1682214dda81e8548a Mon Sep 17 00:00:00 2001 From: inmarket Date: Sun, 12 Apr 2015 17:07:38 +1000 Subject: [PATCH] Add GWIN virtual keyboard widget --- demos/modules/gwin/keyboard/demo.mk | 3 + demos/modules/gwin/keyboard/gfxconf.h | 55 +++ demos/modules/gwin/keyboard/main.c | 140 ++++++++ demos/modules/gwin/keyboard/readme.txt | 1 + docs/releases.txt | 1 + gfxconf.example.h | 3 + src/ginput/ginput_keyboard.h | 6 +- src/gwin/gwin.mk | 2 + src/gwin/gwin_keyboard.c | 471 +++++++++++++++++++++++++ src/gwin/gwin_keyboard.h | 111 ++++++ src/gwin/gwin_keyboard_layout.c | 45 +++ src/gwin/gwin_keyboard_layout.h | 69 ++++ src/gwin/gwin_options.h | 28 ++ src/gwin/gwin_rules.h | 2 +- src/gwin/gwin_widget.h | 4 + 15 files changed, 939 insertions(+), 2 deletions(-) create mode 100644 demos/modules/gwin/keyboard/demo.mk create mode 100644 demos/modules/gwin/keyboard/gfxconf.h create mode 100644 demos/modules/gwin/keyboard/main.c create mode 100644 demos/modules/gwin/keyboard/readme.txt create mode 100644 src/gwin/gwin_keyboard.c create mode 100644 src/gwin/gwin_keyboard.h create mode 100644 src/gwin/gwin_keyboard_layout.c create mode 100644 src/gwin/gwin_keyboard_layout.h diff --git a/demos/modules/gwin/keyboard/demo.mk b/demos/modules/gwin/keyboard/demo.mk new file mode 100644 index 00000000..ae1bf7c1 --- /dev/null +++ b/demos/modules/gwin/keyboard/demo.mk @@ -0,0 +1,3 @@ +DEMODIR = $(GFXLIB)/demos/modules/gwin/keyboard +GFXINC += $(DEMODIR) +GFXSRC += $(DEMODIR)/main.c diff --git a/demos/modules/gwin/keyboard/gfxconf.h b/demos/modules/gwin/keyboard/gfxconf.h new file mode 100644 index 00000000..bfae0e7c --- /dev/null +++ b/demos/modules/gwin/keyboard/gfxconf.h @@ -0,0 +1,55 @@ +/** + * This file has a different license to the rest of the uGFX system. + * You can copy, modify and distribute this file as you see fit. + * You do not need to publish your source modifications to this file. + * The only thing you are not permitted to do is to relicense it + * under a different license. + */ + +/** + * Copy this file into your project directory and rename it as gfxconf.h + * Edit your copy to turn on the uGFX features you want to use. + * The values below are the defaults. You should delete anything + * you are leaving as default. + * + * Please use spaces instead of tabs in this file. + */ + +#ifndef _GFXCONF_H +#define _GFXCONF_H + +/* The operating system to use. One of these must be defined - preferably in your Makefile */ +//#define GFX_USE_OS_CHIBIOS TRUE +//#define GFX_USE_OS_WIN32 TRUE +//#define GFX_USE_OS_LINUX TRUE +//#define GFX_USE_OS_OSX TRUE + +#define GFX_USE_GDISP TRUE + +#define GDISP_NEED_VALIDATION TRUE +#define GDISP_NEED_CLIP TRUE +//#define GDISP_NEED_SCROLL TRUE +#define GDISP_NEED_TEXT TRUE + #define GDISP_INCLUDE_FONT_UI2 TRUE + +//#define GDISP_NEED_CONTROL TRUE +//#define GDISP_DEFAULT_ORIENTATION GDISP_ROTATE_LANDSCAPE +#define GDISP_NEED_MULTITHREAD TRUE + +#define GFX_USE_GWIN TRUE +#define GWIN_NEED_WINDOWMANAGER TRUE +#define GWIN_NEED_CONSOLE TRUE +#define GWIN_NEED_WIDGET TRUE + #define GWIN_NEED_KEYBOARD TRUE + +#define GFX_USE_GEVENT TRUE +#define GFX_USE_GTIMER TRUE + +#define GFX_USE_GQUEUE TRUE +#define GQUEUE_NEED_ASYNC TRUE + +#define GFX_USE_GINPUT TRUE +#define GINPUT_NEED_MOUSE TRUE +//#define GINPUT_NEED_KEYBOARD TRUE + +#endif /* _GFXCONF_H */ diff --git a/demos/modules/gwin/keyboard/main.c b/demos/modules/gwin/keyboard/main.c new file mode 100644 index 00000000..47a10fc0 --- /dev/null +++ b/demos/modules/gwin/keyboard/main.c @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2012, 2013, Joel Bodenmann aka Tectu + * Copyright (c) 2012, 2013, Andrew Hannam aka inmarket + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * 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 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. + */ +#include "gfx.h" + +/* The variables we need */ +static font_t font; +static GListener gl; +static GHandle ghConsole; +static GHandle ghKeyboard; + + +/** + * Create the widgets. + */ +static void createWidgets(void) { + GWidgetInit wi; + + gwinWidgetClearInit(&wi); + + // Create the console - set colors before making it visible + wi.g.show = FALSE; + wi.g.x = 0; wi.g.y = 0; + wi.g.width = gdispGetWidth(); wi.g.height = gdispGetHeight()/2; + ghConsole = gwinConsoleCreate(0, &wi.g); + gwinSetColor(ghConsole, Black); + gwinSetBgColor(ghConsole, HTML2COLOR(0xF0F0F0)); + gwinShow(ghConsole); + gwinClear(ghConsole); + + // Create the keyboard + wi.g.show = TRUE; + wi.g.x = 0; wi.g.y = gdispGetHeight()/2; + wi.g.width = gdispGetWidth(); wi.g.height = gdispGetHeight()/2; + ghKeyboard = gwinKeyboardCreate(0, &wi); +} + +int main(void) { + GEvent * pe; + GEventKeyboard * pk; + unsigned i; + + // Initialize the display + gfxInit(); + + // Set the widget defaults + font = gdispOpenFont("*"); // Get the first defined font. + gwinSetDefaultFont(font); + gwinSetDefaultStyle(&WhiteWidgetStyle, FALSE); + gdispClear(White); + + // Create the gwin windows/widgets + createWidgets(); + + // We want to listen for widget events + geventListenerInit(&gl); + gwinAttachListener(&gl); + + // We also want to listen to keyboard events from the virtual keyboard + geventAttachSource(&gl, gwinKeyboardGetEventSource(ghKeyboard), GLISTEN_KEYTRANSITIONS|GLISTEN_KEYUP); + + while(1) { + // Get an Event + pe = geventEventWait(&gl, TIME_INFINITE); + + switch(pe->type) { + case GEVENT_GWIN_KEYBOARD: + // This is a widget event generated on the standard gwin event source + gwinPrintf(ghConsole, "Keyboard visibility has changed\n"); + break; + + case GEVENT_KEYBOARD: + // This is a keyboard event from a keyboard source which must be separately listened to. + // It is not sent on the gwin event source even though in this case it was generated by a gwin widget. + pk = (GEventKeyboard *)pe; + + gwinPrintf(ghConsole, "KEYSTATE: 0x%04X [ %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s]", + pk->keystate, + (!pk->keystate ? "NONE " : ""), + ((pk->keystate & GKEYSTATE_KEYUP) ? "KEYUP " : ""), + ((pk->keystate & GKEYSTATE_REPEAT) ? "REPEAT " : ""), + ((pk->keystate & GKEYSTATE_SPECIAL) ? "SPECIAL " : ""), + ((pk->keystate & GKEYSTATE_RAW) ? "RAW " : ""), + ((pk->keystate & GKEYSTATE_SHIFT_L) ? "LSHIFT " : ""), + ((pk->keystate & GKEYSTATE_SHIFT_R) ? "RSHIFT " : ""), + ((pk->keystate & GKEYSTATE_CTRL_L) ? "LCTRL " : ""), + ((pk->keystate & GKEYSTATE_CTRL_R) ? "RCTRL " : ""), + ((pk->keystate & GKEYSTATE_ALT_L) ? "LALT " : ""), + ((pk->keystate & GKEYSTATE_ALT_R) ? "RALT " : ""), + ((pk->keystate & GKEYSTATE_FN) ? "FN " : ""), + ((pk->keystate & GKEYSTATE_COMPOSE) ? "COMPOSE " : ""), + ((pk->keystate & GKEYSTATE_WINKEY) ? "WINKEY " : ""), + ((pk->keystate & GKEYSTATE_CAPSLOCK) ? "CAPSLOCK " : ""), + ((pk->keystate & GKEYSTATE_NUMLOCK) ? "NUMLOCK " : ""), + ((pk->keystate & GKEYSTATE_SCROLLLOCK) ? "SCROLLLOCK " : "") + ); + if (pk->bytecount) { + gwinPrintf(ghConsole, " Keys:"); + for (i = 0; i < pk->bytecount; i++) + gwinPrintf(ghConsole, " 0x%02X", (uint8_t)pk->c[i]); + gwinPrintf(ghConsole, " ["); + for (i = 0; i < pk->bytecount; i++) + gwinPrintf(ghConsole, "%c", pk->c[i] >= ' ' && pk->c[i] <= '~' ? pk->c[i] : ' '); + gwinPrintf(ghConsole, "]"); + } + gwinPrintf(ghConsole, "\n"); + break; + + default: + gwinPrintf(ghConsole, "Unknown %d\n", pe->type); + break; + } + } + return 0; +} + diff --git a/demos/modules/gwin/keyboard/readme.txt b/demos/modules/gwin/keyboard/readme.txt new file mode 100644 index 00000000..e960716d --- /dev/null +++ b/demos/modules/gwin/keyboard/readme.txt @@ -0,0 +1 @@ +This demo demonstrates the virtual keyboard. diff --git a/docs/releases.txt b/docs/releases.txt index 548386dc..8331c6b3 100644 --- a/docs/releases.txt +++ b/docs/releases.txt @@ -36,6 +36,7 @@ FEATURE: Added SSD1331 gdisp driver FEATURE: Added arduino as a GOS supported operating system FEATURE: Added additional pixel format's FIX: Color components fixed for some strange compilers +FEATURE: Add GWIN virtual keyboard widget *** Release 2.2 *** FEATURE: Added nested containers demo diff --git a/gfxconf.example.h b/gfxconf.example.h index 2d3568d1..6b2f6d9d 100644 --- a/gfxconf.example.h +++ b/gfxconf.example.h @@ -180,6 +180,9 @@ // #define GWIN_NEED_LIST_IMAGES FALSE // #define GWIN_NEED_PROGRESSBAR FALSE // #define GWIN_PROGRESSBAR_AUTO FALSE +// #define GWIN_NEED_KEYBOARD FALSE +// #define GWIN_KEYBOARD_DEFAULT_LAYOUT VirtualKeyboard_English1 +// #define GWIN_NEED_KEYBOARD_ENGLISH1 TRUE // #define GWIN_FLAT_STYLING FALSE // #define GWIN_WIDGET_TAGS FALSE diff --git a/src/ginput/ginput_keyboard.h b/src/ginput/ginput_keyboard.h index ea218af7..1349092f 100644 --- a/src/ginput/ginput_keyboard.h +++ b/src/ginput/ginput_keyboard.h @@ -17,7 +17,7 @@ #ifndef _GINPUT_KEYBOARD_H #define _GINPUT_KEYBOARD_H -#if GINPUT_NEED_KEYBOARD || defined(__DOXYGEN__) +#if GINPUT_NEED_KEYBOARD || GWIN_NEED_KEYBOARD || defined(__DOXYGEN__) /*===========================================================================*/ /* Type definitions */ @@ -164,6 +164,10 @@ typedef struct GEventKeyboard_t { #define GLISTEN_KEYTRANSITIONS 0x0008 // Return transitions to the key state #define GLISTEN_KEYRAW 0x0010 // Return raw scan-codes. This turns off normal character processing. +#endif + +#if GINPUT_NEED_KEYBOARD || defined(__DOXYGEN__) + // All keyboards #define GKEYBOARD_ALL_INSTANCES ((unsigned)-1) diff --git a/src/gwin/gwin.mk b/src/gwin/gwin.mk index 23993346..32a0ef95 100644 --- a/src/gwin/gwin.mk +++ b/src/gwin/gwin.mk @@ -15,5 +15,7 @@ GFXSRC += $(GFXLIB)/src/gwin/gwin.c \ $(GFXLIB)/src/gwin/gwin_frame.c \ $(GFXLIB)/src/gwin/gwin_tabset.c \ $(GFXLIB)/src/gwin/gwin_gl3d.c \ + $(GFXLIB)/src/gwin/gwin_keyboard.c \ + $(GFXLIB)/src/gwin/gwin_keyboard_layout.c GFXINC += $(GFXLIB)/3rdparty/tinygl-0.4-ugfx/include 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 */ diff --git a/src/gwin/gwin_keyboard.h b/src/gwin/gwin_keyboard.h new file mode 100644 index 00000000..7dcff723 --- /dev/null +++ b/src/gwin/gwin_keyboard.h @@ -0,0 +1,111 @@ +/* + * 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_button.h + * @brief GWIN Graphic window subsystem header file. + * + * @defgroup Button Button + * @ingroup Widgets + * + * @details GWIN allows it to easily create buttons with different styles + * and check for different meta states such as: PRESSED, CLICKED, + * RELEASED etc. + * + * @pre GFX_USE_GWIN must be set to TRUE in your gfxconf.h + * @pre GWIN_NEED_BUTTON must be set to TRUE in your gfxconf.h + * @{ + */ + +#ifndef _GWIN_KEYBOARD_H +#define _GWIN_KEYBOARD_H + +/* This file is included within "src/gwin/gwin_widget.h" */ + +/** + * @brief The Event Type for a Button Event + */ +#define GEVENT_GWIN_KEYBOARD (GEVENT_GWIN_CTRL_FIRST+6) + +/** + * @brief A Keyboard Event + * @note There are currently no GEventGWinButton listening flags - use 0 as the flags to @p gwinAttachListener() + */ +typedef GEventGWin GEventGWinKeyboard; + +struct keyinfo { +}; + +/** + * @brief The keyboard widget structure + * @note Do not use the members directly - treat it as a black-box. + */ +typedef struct GKeyboardObject { + GWidgetObject w; + const struct GVKeyTable *keytable; + const char **keyset; + coord_t keyx, keyy; + coord_t keycx, keycy; + uint8_t lastkeyrow, lastkeycol; + uint8_t keyrow, keycol; + uint32_t key; +} GKeyboardObject; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Create a keyboard widget. + * @return NULL if there is no resultant drawing area, otherwise a window handle. + * + * @param[in] g The GDisplay to display this window on + * @param[in] gb The GKeyboardObject structure to initialise. If this is NULL the structure is dynamically allocated. + * @param[in] pInit The initialisation parameters + * + * @note The drawing color and the background color get set to the current defaults. If you haven't called + * @p gwinSetDefaultColor() or @p gwinSetDefaultBgColor() then these are White and Black respectively. + * @note The font gets set to the current default font. If you haven't called @p gwinSetDefaultFont() then there + * is no default font and text drawing operations will no nothing. + * @note A keyboard remembers its normal drawing state. If there is a window manager then it is automatically + * redrawn if the window is moved or its visibility state is changed. + * @note A keyboard supports mouse input. + * + * @api + */ +GHandle gwinGKeyboardCreate(GDisplay *g, GKeyboardObject *gb, const GWidgetInit *pInit); +#define gwinKeyboardCreate(gb, pInit) gwinGKeyboardCreate(GDISP, gb, pInit) + +/** + * @brief Get the keyboard event source for a GWIN virtual keyboard + * @return The event source handle or NULL if this is not a virtual keyboard + * + * @param[in] gh The GWIN virtual keyboard + * + * @note Normal GINPUT Keyboard events are returned by this event source. + */ +GSourceHandle gwinKeyboardGetEventSource(GHandle gh); + +/** + * @brief Set the layout for the virtual keyboard + * + * @param[in] gh The GWIN virtual keyboard + * @param[in] layout The keyboard layout to use (described by gwin_keyboard_layout.h) + * + * @note Changing the layout resets the keyboard to key set 0 of the keyboard and cancels any + * pending shifts. + */ +void gwinKeyboardSetLayout(GHandle gh, struct GVKeyTable *layout); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* _GWIN_KEYBOARD_H */ +/** @} */ diff --git a/src/gwin/gwin_keyboard_layout.c b/src/gwin/gwin_keyboard_layout.c new file mode 100644 index 00000000..05e08165 --- /dev/null +++ b/src/gwin/gwin_keyboard_layout.c @@ -0,0 +1,45 @@ +/* + * 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 + */ + +#include "gfx.h" + +#if GFX_USE_GWIN && GWIN_NEED_KEYBOARD + +#include "gwin_keyboard_layout.h" + +#if GWIN_NEED_KEYBOARD_ENGLISH1 + /* For this keyboard mapping we use: + * Set 0 = Lowercase letters + * Set 1 = Uppercase letters (transient) + * Set 2 = Uppercase letters (locked) + * Set 3 = Numbers + * Set 4 = Symbols + */ + static const GVSpecialKey Eng1SKeys[] = { + { "\001", 0, GVKEY_SINGLESET, 1 }, // \001 (1) = Shift Lower to Upper + { "\001", 0, GVKEY_INVERT|GVKEY_LOCKSET, 2 }, // \002 (2) = Shift Upper to Upper Lock + { "\002", 0, GVKEY_INVERT|GVKEY_LOCKSET, 0 }, // \003 (3) = Shift Upper Lock to Lower + { "123", 0, GVKEY_LOCKSET, 3 }, // \004 (4) = Change to Numbers + { "\010", "\b", 0, 0 }, // \005 (5) = Backspace + { "\015", "\r", 0, 0 }, // \006 (6) = Enter 1 + { "\015", "\r", 0, 0 }, // \007 (7) = Enter 2 (Short keycap) + { "Sym", 0, GVKEY_LOCKSET, 4 }, // \010 (8) = Change to Symbols + { "aA", 0, GVKEY_LOCKSET, 0 }, // \011 (9) = Change to Lower Alpha + }; + static const char Eng1Set0Row3[] = "\004 .\006\006"; + static const char Eng1Set1Row0[] = "QWERTYUIOP"; + static const char Eng1Set1Row1[] = "ASDFGHJKL"; + static const char *Eng1Set0[] = { "qwertyuiop", "asdfghjkl", "\001zxcvbnm\005", Eng1Set0Row3, 0 }; + static const char *Eng1Set1[] = { Eng1Set1Row0, Eng1Set1Row1, "\002ZXCVBNM\005", Eng1Set0Row3, 0 }; + static const char *Eng1Set2[] = { Eng1Set1Row0, Eng1Set1Row1, "\003ZXCVBNM\005", Eng1Set0Row3, 0 }; + static const char *Eng1Set3[] = { "+-*/", "@789", "\007456", "\010123", "\01100.", 0 }; + static const char *Eng1Set4[] = { "#$%^&*()", "~`:;\"'{}", "<>?/\\|[]", "\011\004,! .@", 0 }; + static const GVKeySet Eng1Sets[] = { Eng1Set0, Eng1Set1, Eng1Set2, Eng1Set3, Eng1Set4, 0 }; + const GVKeyTable VirtualKeyboard_English1 = { Eng1SKeys, Eng1Sets }; +#endif // GWIN_NEED_KEYBOARD_ENGLISH1 + +#endif // GFX_USE_GWIN && GWIN_NEED_KEYBOARD diff --git a/src/gwin/gwin_keyboard_layout.h b/src/gwin/gwin_keyboard_layout.h new file mode 100644 index 00000000..320a503e --- /dev/null +++ b/src/gwin/gwin_keyboard_layout.h @@ -0,0 +1,69 @@ +/* + * 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_layout.h + * @brief GWIN Virtual Keyboard Layout structures. + */ + +#ifndef _GWIN_KEYBOARD_LAYOUT_H +#define _GWIN_KEYBOARD_LAYOUT_H + +/** + * A GVKeyTable is a set of definitions that define how the keyboard lays out + * its keys. A GVKeyTable consists of a number of GVKeySets and a special key table. + * + * A GVKeySet is a set of keys that make up the currently visible keyboard. + * Special keys in the GVKeySet can be used to switch between GVKeySets within + * the GVKeyTable. An example is a shift key which switches between the GVKeySet of + * lower case keys and the GVKeySet of upper case keys. GVKeySet number 0 is special + * in that it is the default GVKeySet when the keyboard is first displayed. + * + * A GVKeySet is made up of GVKeyRow's. Each GVKeyRow describes the keys on one row + * of the keyboard. + * + * Each GVKeyRow covers a number of key columns. Different rows can have different numbers of columns. + * eg. 'Q' -> 'P' has 10 keys while 'A' to 'L' has 9. Additionally each key can cover more than one + * column position eg a wide space bar. + * + * Each GVKeyRow is just a string. Each character is the caption for one key. Using the same + * character for two or more adjacent keys merges the keys into one big key covering multiple key columns. + * Characters \001 to \037 (1 to 31) are special keys. How to handle and draw those is described by the + * special key structure array. Special keys do things like changing keysets, returning characters less than 32, + * have multiple character keycaps. + * + * Note: keycaps from the special key table with a single character from 1 to 31 in them may invoke special drawn + * symbols eg. character 13 may cause a special symbol to be drawn for the enter key. Other than those characters + * which are drawn as symbols by the keyboard draw function, all other characters for keycaps are drawn using the + * current widget font. + * + * Special keycaps handled by the standard draw: + * \001 (1) - Shift (up arrow) + * \002 (2) - Shift locked (up arrow - bold) + * \010 (8) - Tab (right arrow) + * \011 (9) - BackSpace (left arrow) + * \015 (13) - Carriage Return (hooked left arrow) + */ + +typedef struct GVSpecialKey { + const char const *keycap; // The caption on the key + const char const *sendkey; // The key to send (NULL means none) + uint8_t flags; // Flags + #define GVKEY_INVERT 0x01 // Invert the color + #define GVKEY_SINGLESET 0x02 // Change set when this key is pressed but only for a single keystroke + #define GVKEY_LOCKSET 0x04 // Change set when this key is pressed but stay there until the set is changed by the user + uint8_t newset; // The new set to change to + } GVSpecialKey; + +typedef const char **GVKeySet; // Array of Rows - Null indicates the end +typedef struct GVKeyTable { + const GVSpecialKey *skeys; // Array of Special Key structures + const GVKeySet *ksets; // Array of KeySets - Null indicates the end + } GVKeyTable; + +#endif /* _GWIN_KEYBOARD_LAYOUT_H */ +/** @} */ diff --git a/src/gwin/gwin_options.h b/src/gwin/gwin_options.h index 106c5e06..b69796c3 100644 --- a/src/gwin/gwin_options.h +++ b/src/gwin/gwin_options.h @@ -135,6 +135,13 @@ #ifndef GWIN_NEED_TABSET #define GWIN_NEED_TABSET FALSE #endif + /** + * @brief Should the virtual keyboard be included. + * @details Defaults to FALSE + */ + #ifndef GWIN_NEED_KEYBOARD + #define GWIN_NEED_KEYBOARD FALSE + #endif /** * @} * @@ -343,6 +350,27 @@ #ifndef GWIN_FLASHING_PERIOD #define GWIN_FLASHING_PERIOD 250 #endif + /** + * @brief The default keyboard layout for the virtual gwin keyboard + * @details Defaults to VirtualKeyboardLayout_English1 + */ + #ifndef GWIN_KEYBOARD_DEFAULT_LAYOUT + #define GWIN_KEYBOARD_DEFAULT_LAYOUT VirtualKeyboard_English1 + #endif +/** + * @} + * + * @name GWIN Virtual Keyboard Layouts + * @brief One or more of these may be defined. They will only be created if GWIN_NEED_KEYBOARD is TRUE. + * @{ + */ + /** + * @brief The default keyboard layout for the virtual gwin keyboard + * @details Defaults to VirtualKeyboardLayout_English1 + */ + #ifndef GWIN_NEED_KEYBOARD_ENGLISH1 + #define GWIN_NEED_KEYBOARD_ENGLISH1 TRUE + #endif /** @} */ #endif /* _GWIN_OPTIONS_H */ diff --git a/src/gwin/gwin_rules.h b/src/gwin/gwin_rules.h index 8d8ef2cc..7678ab7c 100644 --- a/src/gwin/gwin_rules.h +++ b/src/gwin/gwin_rules.h @@ -38,7 +38,7 @@ #endif #endif #if GWIN_NEED_BUTTON || GWIN_NEED_SLIDER || GWIN_NEED_CHECKBOX || GWIN_NEED_LABEL || GWIN_NEED_RADIO || GWIN_NEED_LIST || \ - GWIN_NEED_IMAGE || GWIN_NEED_CHECKBOX || GWIN_NEED_PROGRESSBAR + GWIN_NEED_IMAGE || GWIN_NEED_CHECKBOX || GWIN_NEED_PROGRESSBAR || GWIN_NEED_KEYBOARD #if !GWIN_NEED_WIDGET #if GFX_DISPLAY_RULE_WARNINGS #warning "GWIN: GWIN_NEED_WIDGET is required when a widget is used. It has been turned on for you." diff --git a/src/gwin/gwin_widget.h b/src/gwin/gwin_widget.h index 63d73e77..0ed80a84 100644 --- a/src/gwin/gwin_widget.h +++ b/src/gwin/gwin_widget.h @@ -377,5 +377,9 @@ bool_t gwinAttachListener(GListener *pl); #include "gwin_progressbar.h" #endif +#if GWIN_NEED_KEYBOARD || defined(__DOXYGEN__) + #include "gwin_keyboard.h" +#endif + #endif /* _GWIDGET_H */ /** @} */