From 7bce5e41cf9bb979ee93c7be551993d9fb6f1193 Mon Sep 17 00:00:00 2001 From: Rustam Gamidov Date: Wed, 26 Feb 2020 18:14:51 +0200 Subject: [PATCH 1/7] area: extract windows related keymaps to separate file to reuse in the entry --- ui.h | 11 +++--- windows/areaevents.cpp | 78 +----------------------------------------- windows/keyboard.hpp | 76 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 82 deletions(-) create mode 100644 windows/keyboard.hpp diff --git a/ui.h b/ui.h index 40aea949..4d16b4fd 100644 --- a/ui.h +++ b/ui.h @@ -45,6 +45,12 @@ _UI_ENUM(uiForEach) { uiForEachStop, }; +typedef struct uiArea uiArea; +typedef struct uiAreaHandler uiAreaHandler; +typedef struct uiAreaDrawParams uiAreaDrawParams; +typedef struct uiAreaMouseEvent uiAreaMouseEvent; +typedef struct uiAreaKeyEvent uiAreaKeyEvent; + typedef struct uiInitOptions uiInitOptions; struct uiInitOptions { @@ -297,11 +303,6 @@ _UI_EXTERN char *uiSaveFile(uiWindow *parent); _UI_EXTERN void uiMsgBox(uiWindow *parent, const char *title, const char *description); _UI_EXTERN void uiMsgBoxError(uiWindow *parent, const char *title, const char *description); -typedef struct uiArea uiArea; -typedef struct uiAreaHandler uiAreaHandler; -typedef struct uiAreaDrawParams uiAreaDrawParams; -typedef struct uiAreaMouseEvent uiAreaMouseEvent; -typedef struct uiAreaKeyEvent uiAreaKeyEvent; typedef struct uiDrawContext uiDrawContext; diff --git a/windows/areaevents.cpp b/windows/areaevents.cpp index c7014ecb..3dc4a38e 100644 --- a/windows/areaevents.cpp +++ b/windows/areaevents.cpp @@ -1,6 +1,7 @@ // 8 september 2015 #include "uipriv_windows.hpp" #include "area.hpp" +#include "keyboard.hpp" // TODO https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/multimedia/DirectWrite/PadWrite/TextEditor.cpp notes on explicit RTL handling under MirrorXCoordinate(); also in areadraw.cpp too? @@ -177,81 +178,6 @@ static void onMouseLeft(uiArea *a) uiprivClickCounterReset(&(a->cc)); } -// we use VK_SNAPSHOT as a sentinel because libui will never support the print screen key; that key belongs to the user -struct extkeymap { - WPARAM vk; - uiExtKey extkey; -}; - -// all mappings come from GLFW - https://github.com/glfw/glfw/blob/master/src/win32_window.c#L152 -static const struct extkeymap numpadExtKeys[] = { - { VK_HOME, uiExtKeyN7 }, - { VK_UP, uiExtKeyN8 }, - { VK_PRIOR, uiExtKeyN9 }, - { VK_LEFT, uiExtKeyN4 }, - { VK_CLEAR, uiExtKeyN5 }, - { VK_RIGHT, uiExtKeyN6 }, - { VK_END, uiExtKeyN1 }, - { VK_DOWN, uiExtKeyN2 }, - { VK_NEXT, uiExtKeyN3 }, - { VK_INSERT, uiExtKeyN0 }, - { VK_DELETE, uiExtKeyNDot }, - { VK_SNAPSHOT, 0 }, -}; - -static const struct extkeymap extKeys[] = { - { VK_ESCAPE, uiExtKeyEscape }, - { VK_INSERT, uiExtKeyInsert }, - { VK_DELETE, uiExtKeyDelete }, - { VK_HOME, uiExtKeyHome }, - { VK_END, uiExtKeyEnd }, - { VK_PRIOR, uiExtKeyPageUp }, - { VK_NEXT, uiExtKeyPageDown }, - { VK_UP, uiExtKeyUp }, - { VK_DOWN, uiExtKeyDown }, - { VK_LEFT, uiExtKeyLeft }, - { VK_RIGHT, uiExtKeyRight }, - { VK_F1, uiExtKeyF1 }, - { VK_F2, uiExtKeyF2 }, - { VK_F3, uiExtKeyF3 }, - { VK_F4, uiExtKeyF4 }, - { VK_F5, uiExtKeyF5 }, - { VK_F6, uiExtKeyF6 }, - { VK_F7, uiExtKeyF7 }, - { VK_F8, uiExtKeyF8 }, - { VK_F9, uiExtKeyF9 }, - { VK_F10, uiExtKeyF10 }, - { VK_F11, uiExtKeyF11 }, - { VK_F12, uiExtKeyF12 }, - // numpad numeric keys and . are handled in common/areaevents.c - // numpad enter is handled in code below - { VK_ADD, uiExtKeyNAdd }, - { VK_SUBTRACT, uiExtKeyNSubtract }, - { VK_MULTIPLY, uiExtKeyNMultiply }, - { VK_DIVIDE, uiExtKeyNDivide }, - { VK_SNAPSHOT, 0 }, -}; - -static const struct { - WPARAM vk; - uiModifiers mod; -} modKeys[] = { - // even if the separate left/right aren't necessary, have them here anyway, just to be safe - { VK_CONTROL, uiModifierCtrl }, - { VK_LCONTROL, uiModifierCtrl }, - { VK_RCONTROL, uiModifierCtrl }, - { VK_MENU, uiModifierAlt }, - { VK_LMENU, uiModifierAlt }, - { VK_RMENU, uiModifierAlt }, - { VK_SHIFT, uiModifierShift }, - { VK_LSHIFT, uiModifierShift }, - { VK_RSHIFT, uiModifierShift }, - // there's no combined Windows key virtual-key code as there is with the others - { VK_LWIN, uiModifierSuper }, - { VK_RWIN, uiModifierSuper }, - { VK_SNAPSHOT, 0 }, -}; - static int areaKeyEvent(uiArea *a, int up, WPARAM wParam, LPARAM lParam) { uiAreaKeyEvent ke; @@ -261,9 +187,7 @@ static int areaKeyEvent(uiArea *a, int up, WPARAM wParam, LPARAM lParam) ke.Key = 0; ke.ExtKey = 0; ke.Modifier = 0; - ke.Modifiers = getModifiers(); - ke.Up = up; // the numeric keypad keys when Num Lock is off are considered left-hand keys as the separate navigation buttons were added later diff --git a/windows/keyboard.hpp b/windows/keyboard.hpp new file mode 100644 index 00000000..06815072 --- /dev/null +++ b/windows/keyboard.hpp @@ -0,0 +1,76 @@ +#pragma once + +// we use VK_SNAPSHOT as a sentinel because libui will never support the print screen key; that key belongs to the user +struct extkeymap { + WPARAM vk; + uiExtKey extkey; +}; + +// all mappings come from GLFW - https://github.com/glfw/glfw/blob/master/src/win32_window.c#L152 +static const struct extkeymap numpadExtKeys[] = { + { VK_HOME, uiExtKeyN7 }, + { VK_UP, uiExtKeyN8 }, + { VK_PRIOR, uiExtKeyN9 }, + { VK_LEFT, uiExtKeyN4 }, + { VK_CLEAR, uiExtKeyN5 }, + { VK_RIGHT, uiExtKeyN6 }, + { VK_END, uiExtKeyN1 }, + { VK_DOWN, uiExtKeyN2 }, + { VK_NEXT, uiExtKeyN3 }, + { VK_INSERT, uiExtKeyN0 }, + { VK_DELETE, uiExtKeyNDot }, + { VK_SNAPSHOT, 0 }, +}; + +static const struct extkeymap extKeys[] = { + { VK_ESCAPE, uiExtKeyEscape }, + { VK_INSERT, uiExtKeyInsert }, + { VK_DELETE, uiExtKeyDelete }, + { VK_HOME, uiExtKeyHome }, + { VK_END, uiExtKeyEnd }, + { VK_PRIOR, uiExtKeyPageUp }, + { VK_NEXT, uiExtKeyPageDown }, + { VK_UP, uiExtKeyUp }, + { VK_DOWN, uiExtKeyDown }, + { VK_LEFT, uiExtKeyLeft }, + { VK_RIGHT, uiExtKeyRight }, + { VK_F1, uiExtKeyF1 }, + { VK_F2, uiExtKeyF2 }, + { VK_F3, uiExtKeyF3 }, + { VK_F4, uiExtKeyF4 }, + { VK_F5, uiExtKeyF5 }, + { VK_F6, uiExtKeyF6 }, + { VK_F7, uiExtKeyF7 }, + { VK_F8, uiExtKeyF8 }, + { VK_F9, uiExtKeyF9 }, + { VK_F10, uiExtKeyF10 }, + { VK_F11, uiExtKeyF11 }, + { VK_F12, uiExtKeyF12 }, + // numpad numeric keys and . are handled in common/areaevents.c + // numpad enter is handled in code below + { VK_ADD, uiExtKeyNAdd }, + { VK_SUBTRACT, uiExtKeyNSubtract }, + { VK_MULTIPLY, uiExtKeyNMultiply }, + { VK_DIVIDE, uiExtKeyNDivide }, + { VK_SNAPSHOT, 0 }, +}; + +static const struct { + WPARAM vk; + uiModifiers mod; +} modKeys[] = { + // even if the separate left/right aren't necessary, have them here anyway, just to be safe + { VK_CONTROL, uiModifierCtrl }, + { VK_LCONTROL, uiModifierCtrl }, + { VK_RCONTROL, uiModifierCtrl }, + { VK_MENU, uiModifierAlt }, + { VK_LMENU, uiModifierAlt }, + { VK_RMENU, uiModifierAlt }, + { VK_SHIFT, uiModifierShift }, + { VK_LSHIFT, uiModifierShift }, + { VK_RSHIFT, uiModifierShift }, + // there's no combined Windows key virtual-key code as there is with the others + { VK_LWIN, uiModifierSuper }, + { VK_RWIN, uiModifierSuper }, + { VK_SNAPSHOT, 0 }, +}; From 49cc39c2917e3270d4d1541c009110df8c1ffec3 Mon Sep 17 00:00:00 2001 From: Rustam Gamidov Date: Wed, 26 Feb 2020 18:26:23 +0200 Subject: [PATCH 2/7] entry: wrap entry default WndProc to catch keyboard events --- windows/entry.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/windows/entry.cpp b/windows/entry.cpp index a7a077f2..2d2b9d56 100644 --- a/windows/entry.cpp +++ b/windows/entry.cpp @@ -7,8 +7,26 @@ struct uiEntry { void (*onChanged)(uiEntry *, void *); void *onChangedData; BOOL inhibitChanged; + WNDPROC native_wndproc; }; + + +static LRESULT CALLBACK entryWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_GETDLGCODE: + return DLGC_HASSETSEL | DLGC_WANTALLKEYS; + break; + case WM_KEYUP: + case WM_KEYDOWN: + break; + } + + WNDPROC native_wndproc = (WNDPROC)GetProp(hwnd, L"NATIVE_WNDPROC"); + return CallWindowProcW(native_wndproc, hwnd, uMsg, wParam, lParam); +} static BOOL onWM_COMMAND(uiControl *c, HWND hwnd, WORD code, LRESULT *lResult) { uiEntry *e = uiEntry(c); @@ -26,6 +44,9 @@ static void uiEntryDestroy(uiControl *c) { uiEntry *e = uiEntry(c); + (WNDPROC)SetWindowLongPtrW(e->hwnd, GWLP_WNDPROC, (LONG_PTR)e->native_wndproc); + RemoveProp(e->hwnd, L"NATIVE_WNDPROC"); + uiWindowsUnregisterWM_COMMANDHandler(e->hwnd); uiWindowsEnsureDestroyWindow(e->hwnd); uiFreeControl(uiControl(e)); @@ -105,6 +126,10 @@ static uiEntry *finishNewEntry(DWORD style) TRUE); uiWindowsRegisterWM_COMMANDHandler(e->hwnd, onWM_COMMAND, uiControl(e)); + + e->native_wndproc = (WNDPROC)SetWindowLongPtrW(e->hwnd, GWLP_WNDPROC, (LONG_PTR)entryWndProc); + SetProp(e->hwnd, L"NATIVE_WNDPROC", (HANDLE)e->native_wndproc); + uiEntryOnChanged(e, defaultOnChanged, NULL); return e; From 599f48391433ec2d0859efa3722b5c398ab56739 Mon Sep 17 00:00:00 2001 From: Rustam Gamidov Date: Wed, 26 Feb 2020 18:45:03 +0200 Subject: [PATCH 3/7] entry: add onKeyEvent handler taking uiAreaKeyEvent --- ui.h | 1 + windows/areaevents.cpp | 37 +--------------------------------- windows/entry.cpp | 42 +++++++++++++++++++++++++++++++++++++-- windows/keyboard.hpp | 45 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 38 deletions(-) diff --git a/ui.h b/ui.h index 4d16b4fd..e73e35b6 100644 --- a/ui.h +++ b/ui.h @@ -168,6 +168,7 @@ typedef struct uiEntry uiEntry; _UI_EXTERN char *uiEntryText(uiEntry *e); _UI_EXTERN void uiEntrySetText(uiEntry *e, const char *text); _UI_EXTERN void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *e, void *data), void *data); +_UI_EXTERN void uiEntryOnKeyEvent(uiEntry *e, int (*f)(uiEntry *e, uiAreaKeyEvent *event)); _UI_EXTERN int uiEntryReadOnly(uiEntry *e); _UI_EXTERN void uiEntrySetReadOnly(uiEntry *e, int readonly); _UI_EXTERN uiEntry *uiNewEntry(void); diff --git a/windows/areaevents.cpp b/windows/areaevents.cpp index 3dc4a38e..c65a6b8a 100644 --- a/windows/areaevents.cpp +++ b/windows/areaevents.cpp @@ -5,23 +5,6 @@ // TODO https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/multimedia/DirectWrite/PadWrite/TextEditor.cpp notes on explicit RTL handling under MirrorXCoordinate(); also in areadraw.cpp too? -static uiModifiers getModifiers(void) -{ - uiModifiers m = 0; - - if ((GetKeyState(VK_CONTROL) & 0x80) != 0) - m |= uiModifierCtrl; - if ((GetKeyState(VK_MENU) & 0x80) != 0) - m |= uiModifierAlt; - if ((GetKeyState(VK_SHIFT) & 0x80) != 0) - m |= uiModifierShift; - if ((GetKeyState(VK_LWIN) & 0x80) != 0) - m |= uiModifierSuper; - if ((GetKeyState(VK_RWIN) & 0x80) != 0) - m |= uiModifierSuper; - return m; -} - /* Windows doesn't natively support mouse crossing events. @@ -206,25 +189,7 @@ static int areaKeyEvent(uiArea *a, int up, WPARAM wParam, LPARAM lParam) goto keyFound; } - // okay, those above cases didn't match anything - // first try the extended keys - for (i = 0; extKeys[i].vk != VK_SNAPSHOT; i++) - if (extKeys[i].vk == wParam) { - ke.ExtKey = extKeys[i].extkey; - goto keyFound; - } - - // then try modifier keys - for (i = 0; modKeys[i].vk != VK_SNAPSHOT; i++) - if (modKeys[i].vk == wParam) { - ke.Modifier = modKeys[i].mod; - // and don't include the key in Modifiers - ke.Modifiers &= ~ke.Modifier; - goto keyFound; - } - - // and finally everything else - if (uiprivFromScancode((lParam >> 16) & 0xFF, &ke)) + if (fillKeyEvent(ke, wParam, lParam)) goto keyFound; // not a supported key, assume unhandled diff --git a/windows/entry.cpp b/windows/entry.cpp index 2d2b9d56..6dadf908 100644 --- a/windows/entry.cpp +++ b/windows/entry.cpp @@ -1,5 +1,6 @@ // 8 april 2015 #include "uipriv_windows.hpp" +#include "keyboard.hpp" struct uiEntry { uiWindowsControl c; @@ -7,26 +8,46 @@ struct uiEntry { void (*onChanged)(uiEntry *, void *); void *onChangedData; BOOL inhibitChanged; + int (*onKeyEvent)(uiEntry *, uiAreaKeyEvent *); + void *self; WNDPROC native_wndproc; }; - static LRESULT CALLBACK entryWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + BOOL righthand = FALSE; + uiAreaKeyEvent ke; + ke.Key = 0; + ke.ExtKey = 0; + ke.Modifier = 0; + ke.Modifiers = getModifiers(); + ke.Up = 0; + + int (*onKeyEvent)(uiEntry *, uiAreaKeyEvent *) = (int (*)(uiEntry *, uiAreaKeyEvent *))GetProp(hwnd, L"ON_KEY_EVENT"); + uiEntry *self = (uiEntry *)GetProp(hwnd, L"SELF"); + switch (uMsg) { case WM_GETDLGCODE: return DLGC_HASSETSEL | DLGC_WANTALLKEYS; break; case WM_KEYUP: + ke.Up = 1; case WM_KEYDOWN: + if (onKeyEvent) { + fillKeyEvent(ke, wParam, lParam); + if ((*onKeyEvent)(self, &ke)) { + return TRUE; + } + } break; } WNDPROC native_wndproc = (WNDPROC)GetProp(hwnd, L"NATIVE_WNDPROC"); return CallWindowProcW(native_wndproc, hwnd, uMsg, wParam, lParam); } + static BOOL onWM_COMMAND(uiControl *c, HWND hwnd, WORD code, LRESULT *lResult) { uiEntry *e = uiEntry(c); @@ -46,6 +67,8 @@ static void uiEntryDestroy(uiControl *c) (WNDPROC)SetWindowLongPtrW(e->hwnd, GWLP_WNDPROC, (LONG_PTR)e->native_wndproc); RemoveProp(e->hwnd, L"NATIVE_WNDPROC"); + RemoveProp(e->hwnd, L"ON_KEY_EVENT"); + RemoveProp(e->hwnd, L"SELF"); uiWindowsUnregisterWM_COMMANDHandler(e->hwnd); uiWindowsEnsureDestroyWindow(e->hwnd); @@ -77,6 +100,13 @@ static void defaultOnChanged(uiEntry *e, void *data) // do nothing } +static int defaultOnKeyEvent(uiEntry *e, uiAreaKeyEvent *event) +{ + // do nothing + return FALSE; +} + + char *uiEntryText(uiEntry *e) { return uiWindowsWindowText(e->hwnd); @@ -97,6 +127,12 @@ void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data) e->onChangedData = data; } +void uiEntryOnKeyEvent(uiEntry *e, int (*f)(uiEntry *, uiAreaKeyEvent *)) +{ + e->onKeyEvent = f; + SetProp(e->hwnd, L"ON_KEY_EVENT", (HGLOBAL)(f)); +} + int uiEntryReadOnly(uiEntry *e) { return (getStyle(e->hwnd) & ES_READONLY) != 0; @@ -128,9 +164,11 @@ static uiEntry *finishNewEntry(DWORD style) uiWindowsRegisterWM_COMMANDHandler(e->hwnd, onWM_COMMAND, uiControl(e)); e->native_wndproc = (WNDPROC)SetWindowLongPtrW(e->hwnd, GWLP_WNDPROC, (LONG_PTR)entryWndProc); - SetProp(e->hwnd, L"NATIVE_WNDPROC", (HANDLE)e->native_wndproc); + SetProp(e->hwnd, L"NATIVE_WNDPROC", (HGLOBAL)e->native_wndproc); + SetProp(e->hwnd, L"SELF", (HGLOBAL)e); uiEntryOnChanged(e, defaultOnChanged, NULL); + uiEntryOnKeyEvent(e, defaultOnKeyEvent); return e; } diff --git a/windows/keyboard.hpp b/windows/keyboard.hpp index 06815072..3b4ddaa3 100644 --- a/windows/keyboard.hpp +++ b/windows/keyboard.hpp @@ -74,3 +74,48 @@ static const struct { { VK_RWIN, uiModifierSuper }, { VK_SNAPSHOT, 0 }, }; + +static uiModifiers getModifiers(void) +{ + uiModifiers m = 0; + + if ((GetKeyState(VK_CONTROL) & 0x80) != 0) + m |= uiModifierCtrl; + if ((GetKeyState(VK_MENU) & 0x80) != 0) + m |= uiModifierAlt; + if ((GetKeyState(VK_SHIFT) & 0x80) != 0) + m |= uiModifierShift; + if ((GetKeyState(VK_LWIN) & 0x80) != 0) + m |= uiModifierSuper; + if ((GetKeyState(VK_RWIN) & 0x80) != 0) + m |= uiModifierSuper; + return m; +} + + +static BOOL fillKeyEvent(uiAreaKeyEvent &ke, WPARAM wParam, LPARAM lParam) +{ + int i = 0; + + for (i = 0; extKeys[i].vk != VK_SNAPSHOT; i++) { + if (extKeys[i].vk == wParam) { + ke.ExtKey = extKeys[i].extkey; + return TRUE; + } + } + + for (i = 0; modKeys[i].vk != VK_SNAPSHOT; i++) { + if (modKeys[i].vk == wParam) { + ke.Modifier = modKeys[i].mod; + ke.Modifiers &= ~ke.Modifier; + return TRUE; + } + } + + // TODO the original code only did this if ke.Modifiers == 0 - why? + if (uiprivFromScancode((lParam >> 16) & 0xFF, &ke)) { + return TRUE; + } + + return FALSE; +} From 7f8712d2f5987afe891dd5c2efa4a10d29c91ca5 Mon Sep 17 00:00:00 2001 From: Rustam Gamidov Date: Mon, 9 Mar 2020 20:46:35 +0200 Subject: [PATCH 4/7] entry: add onKeyEvent for macos --- darwin/entry.m | 58 ++++++++++++++++++++++++++++++++++++++++++ darwin/map.m | 14 ++++++++++ darwin/uipriv_darwin.h | 1 + 3 files changed, 73 insertions(+) diff --git a/darwin/entry.m b/darwin/entry.m index 99630264..9d9bbb07 100644 --- a/darwin/entry.m +++ b/darwin/entry.m @@ -59,6 +59,7 @@ struct uiEntry { NSTextField *textfield; void (*onChanged)(uiEntry *, void *); void *onChangedData; + int (*onKeyEvent)(uiEntry *, uiAreaKeyEvent *); }; static BOOL isSearchField(NSTextField *tf) @@ -66,8 +67,37 @@ static BOOL isSearchField(NSTextField *tf) return [tf isKindOfClass:[NSSearchField class]]; } +static uiModifiers parseModifiers(NSEvent *e) +{ + NSEventModifierFlags mods; + uiModifiers m; + + m = 0; + mods = [e modifierFlags]; + if ((mods & NSControlKeyMask) != 0) + m |= uiModifierCtrl; + if ((mods & NSAlternateKeyMask) != 0) + m |= uiModifierAlt; + if ((mods & NSShiftKeyMask) != 0) + m |= uiModifierShift; + if ((mods & NSCommandKeyMask) != 0) + m |= uiModifierSuper; + return m; +} + +static void triggerOnKeyEvent(void *key, void *e, void *data) +{ + uiEntry *entry = (uiEntry *)e; + void *firstResponder = [entry->textfield window].firstResponder; + BOOL sameObj = (entry->textfield == firstResponder); + BOOL currentEditor = (entry->textfield.currentEditor && entry->textfield.currentEditor == firstResponder); + if (sameObj || currentEditor) + entry->onKeyEvent(entry, data); +} + @interface entryDelegateClass : NSObject { uiprivMap *entries; + id eventMonitor; } - (void)controlTextDidChange:(NSNotification *)note; - (IBAction)onSearch:(id)sender; @@ -82,11 +112,27 @@ static BOOL isSearchField(NSTextField *tf) self = [super init]; if (self) self->entries = uiprivNewMap(); + + NSEvent* (^eventHandler)(NSEvent*) = ^(NSEvent *theEvent) { + uiAreaKeyEvent ke; + ke.Key = 0; + ke.ExtKey = 0; + ke.Modifier = 0; + ke.Modifiers = parseModifiers(theEvent); + + if (uiprivFromKeycode([theEvent keyCode], &ke)) + uiprivMapWalkWithData(self->entries, &ke, triggerOnKeyEvent); + + return theEvent; + }; + eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:eventHandler]; + return self; } - (void)dealloc { + [NSEvent removeMonitor:eventMonitor]; uiprivMapDestroy(self->entries); [super dealloc]; } @@ -155,6 +201,11 @@ void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data) e->onChangedData = data; } +void uiEntryOnKeyEvent(uiEntry *e, int (*f)(uiEntry *, uiAreaKeyEvent *)) +{ + e->onKeyEvent = f; +} + int uiEntryReadOnly(uiEntry *e) { return [e->textfield isEditable] == NO; @@ -175,6 +226,12 @@ static void defaultOnChanged(uiEntry *e, void *data) // do nothing } +static int defaultOnKeyEvent(uiEntry *e, uiAreaKeyEvent *ke) +{ + // do nothing + return FALSE; +} + // these are based on interface builder defaults; my comments in the old code weren't very good so I don't really know what talked about what, sorry :/ void uiprivFinishNewTextField(NSTextField *t, BOOL isEntry) { @@ -220,6 +277,7 @@ static uiEntry *finishNewEntry(Class class) } [entryDelegate registerEntry:e]; uiEntryOnChanged(e, defaultOnChanged, NULL); + uiEntryOnKeyEvent(e, defaultOnKeyEvent); return e; } diff --git a/darwin/map.m b/darwin/map.m index a9774170..aac7f6f3 100644 --- a/darwin/map.m +++ b/darwin/map.m @@ -55,6 +55,20 @@ void uiprivMapWalk(uiprivMap *m, void (*f)(void *key, void *value)) NSEndMapTableEnumeration(&e); } +void uiprivMapWalkWithData(uiprivMap *m, void *data, void (*f)(void *key, void *value, void *data)) +{ + NSMapEnumerator e; + void *k, *v; + + e = NSEnumerateMapTable(m->m); + k = NULL; + v = NULL; + while (NSNextMapEnumeratorPair(&e, &k, &v)) + f(k, v, data); + NSEndMapTableEnumeration(&e); +} + + void uiprivMapReset(uiprivMap *m) { NSResetMapTable(m->m); diff --git a/darwin/uipriv_darwin.h b/darwin/uipriv_darwin.h index 5d50f623..8e43fb5a 100644 --- a/darwin/uipriv_darwin.h +++ b/darwin/uipriv_darwin.h @@ -31,6 +31,7 @@ extern void *uiprivMapGet(uiprivMap *m, void *key); extern void uiprivMapSet(uiprivMap *m, void *key, void *value); extern void uiprivMapDelete(uiprivMap *m, void *key); extern void uiprivMapWalk(uiprivMap *m, void (*f)(void *key, void *value)); +extern void uiprivMapWalkWithData(uiprivMap *m, void *data, void (*f)(void *key, void *value, void *data)); extern void uiprivMapReset(uiprivMap *m); // menu.m From 0a6068d58fe9f1497a3195f5d04414b5bd16ff98 Mon Sep 17 00:00:00 2001 From: Rustam Gamidov Date: Tue, 10 Mar 2020 10:49:17 +0200 Subject: [PATCH 5/7] events: move darwin parseModifiers to areaevents as it is used in entry --- darwin/area.m | 25 +++---------------------- darwin/areaevents.m | 18 ++++++++++++++++++ darwin/entry.m | 18 ------------------ darwin/uipriv_darwin.h | 1 + 4 files changed, 22 insertions(+), 40 deletions(-) diff --git a/darwin/area.m b/darwin/area.m index a184bc4a..4d128a8a 100644 --- a/darwin/area.m +++ b/darwin/area.m @@ -11,7 +11,6 @@ BOOL libui_enabled; } - (id)initWithFrame:(NSRect)r area:(uiArea *)a; -- (uiModifiers)parseModifiers:(NSEvent *)e; - (void)doMouseEvent:(NSEvent *)e; - (int)sendKeyEvent:(uiAreaKeyEvent *)ke; - (int)doKeyDownUp:(NSEvent *)e up:(int)up; @@ -87,24 +86,6 @@ struct uiArea { return YES; } -- (uiModifiers)parseModifiers:(NSEvent *)e -{ - NSEventModifierFlags mods; - uiModifiers m; - - m = 0; - mods = [e modifierFlags]; - if ((mods & NSControlKeyMask) != 0) - m |= uiModifierCtrl; - if ((mods & NSAlternateKeyMask) != 0) - m |= uiModifierAlt; - if ((mods & NSShiftKeyMask) != 0) - m |= uiModifierShift; - if ((mods & NSCommandKeyMask) != 0) - m |= uiModifierSuper; - return m; -} - - (void)setupNewTrackingArea { self->libui_ta = [[NSTrackingArea alloc] initWithRect:[self bounds] @@ -178,7 +159,7 @@ struct uiArea { break; } - me.Modifiers = [self parseModifiers:e]; + me.Modifiers = parseModifiers(e); pmb = [NSEvent pressedMouseButtons]; me.Held1To64 = 0; @@ -260,7 +241,7 @@ mouseEvent(otherMouseUp) ke.ExtKey = 0; ke.Modifier = 0; - ke.Modifiers = [self parseModifiers:e]; + ke.Modifiers = parseModifiers(e); ke.Up = up; @@ -292,7 +273,7 @@ mouseEvent(otherMouseUp) if (!uiprivKeycodeModifier([e keyCode], &whichmod)) return 0; ke.Modifier = whichmod; - ke.Modifiers = [self parseModifiers:e]; + ke.Modifiers = parseModifiers(e); ke.Up = (ke.Modifiers & ke.Modifier) == 0; // and then drop the current modifier from Modifiers ke.Modifiers &= ~ke.Modifier; diff --git a/darwin/areaevents.m b/darwin/areaevents.m index 27b5dd64..80703104 100644 --- a/darwin/areaevents.m +++ b/darwin/areaevents.m @@ -157,3 +157,21 @@ BOOL uiprivKeycodeModifier(unsigned short keycode, uiModifiers *mod) } return NO; } + +uiModifiers parseModifiers(NSEvent *e) +{ + NSEventModifierFlags mods; + uiModifiers m; + + m = 0; + mods = [e modifierFlags]; + if ((mods & NSControlKeyMask) != 0) + m |= uiModifierCtrl; + if ((mods & NSAlternateKeyMask) != 0) + m |= uiModifierAlt; + if ((mods & NSShiftKeyMask) != 0) + m |= uiModifierShift; + if ((mods & NSCommandKeyMask) != 0) + m |= uiModifierSuper; + return m; +} diff --git a/darwin/entry.m b/darwin/entry.m index 9d9bbb07..c754db8d 100644 --- a/darwin/entry.m +++ b/darwin/entry.m @@ -67,24 +67,6 @@ static BOOL isSearchField(NSTextField *tf) return [tf isKindOfClass:[NSSearchField class]]; } -static uiModifiers parseModifiers(NSEvent *e) -{ - NSEventModifierFlags mods; - uiModifiers m; - - m = 0; - mods = [e modifierFlags]; - if ((mods & NSControlKeyMask) != 0) - m |= uiModifierCtrl; - if ((mods & NSAlternateKeyMask) != 0) - m |= uiModifierAlt; - if ((mods & NSShiftKeyMask) != 0) - m |= uiModifierShift; - if ((mods & NSCommandKeyMask) != 0) - m |= uiModifierSuper; - return m; -} - static void triggerOnKeyEvent(void *key, void *e, void *data) { uiEntry *entry = (uiEntry *)e; diff --git a/darwin/uipriv_darwin.h b/darwin/uipriv_darwin.h index 8e43fb5a..7a91bb75 100644 --- a/darwin/uipriv_darwin.h +++ b/darwin/uipriv_darwin.h @@ -111,6 +111,7 @@ extern int uiprivSendAreaEvents(NSEvent *); // areaevents.m extern BOOL uiprivFromKeycode(unsigned short keycode, uiAreaKeyEvent *ke); extern BOOL uiprivKeycodeModifier(unsigned short keycode, uiModifiers *mod); +extern uiModifiers parseModifiers(NSEvent *e); // draw.m extern uiDrawContext *uiprivDrawNewContext(CGContextRef, CGFloat); From 209fb979027da863f7a54e6631c204fac19e901d Mon Sep 17 00:00:00 2001 From: Rustam Gamidov Date: Tue, 10 Mar 2020 12:44:43 +0200 Subject: [PATCH 6/7] entry: catch keyUp events in addition to the keyDown --- darwin/entry.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/darwin/entry.m b/darwin/entry.m index c754db8d..7bf57d43 100644 --- a/darwin/entry.m +++ b/darwin/entry.m @@ -101,13 +101,14 @@ static void triggerOnKeyEvent(void *key, void *e, void *data) ke.ExtKey = 0; ke.Modifier = 0; ke.Modifiers = parseModifiers(theEvent); + ke.Up = ([theEvent type] == NSKeyUp ? 1 : 0); if (uiprivFromKeycode([theEvent keyCode], &ke)) uiprivMapWalkWithData(self->entries, &ke, triggerOnKeyEvent); return theEvent; }; - eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:eventHandler]; + eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:(NSKeyDownMask | NSKeyUpMask) handler:eventHandler]; return self; } From 64348a152e19d8367b7048dd84a1579f71f7b826 Mon Sep 17 00:00:00 2001 From: Rustam Gamidov Date: Sun, 15 Mar 2020 08:53:40 +0200 Subject: [PATCH 7/7] entry: handle keyboard events in unix part --- unix/area.c | 124 ++---------------------------------------- unix/entry.c | 28 ++++++++++ unix/keyboard.h | 130 +++++++++++++++++++++++++++++++++++++++++++++ unix/uipriv_unix.h | 1 + 4 files changed, 164 insertions(+), 119 deletions(-) create mode 100644 unix/keyboard.h diff --git a/unix/area.c b/unix/area.c index 19b34634..fe8edeeb 100644 --- a/unix/area.c +++ b/unix/area.c @@ -1,5 +1,6 @@ // 4 september 2015 #include "uipriv_unix.h" +#include "keyboard.h" // notes: // - G_DECLARE_DERIVABLE/FINAL_INTERFACE() requires glib 2.44 and that's starting with debian stretch (testing) (GTK+ 3.18) and ubuntu 15.04 (GTK+ 3.14) - debian jessie has 2.42 (GTK+ 3.14) @@ -170,36 +171,6 @@ static void areaWidget_get_preferred_width(GtkWidget *w, gint *min, gint *nat) } } -static guint translateModifiers(guint state, GdkWindow *window) -{ - GdkModifierType statetype; - - // GDK doesn't initialize the modifier flags fully; we have to explicitly tell it to (thanks to Daniel_S and daniels (two different people) in irc.gimp.net/#gtk+) - statetype = state; - gdk_keymap_add_virtual_modifiers( - gdk_keymap_get_for_display(gdk_window_get_display(window)), - &statetype); - return statetype; -} - -static uiModifiers toModifiers(guint state) -{ - uiModifiers m; - - m = 0; - if ((state & GDK_CONTROL_MASK) != 0) - m |= uiModifierCtrl; - if ((state & GDK_META_MASK) != 0) - m |= uiModifierAlt; - if ((state & GDK_MOD1_MASK) != 0) // GTK+ itself requires this to be Alt (just read through gtkaccelgroup.c) - m |= uiModifierAlt; - if ((state & GDK_SHIFT_MASK) != 0) - m |= uiModifierShift; - if ((state & GDK_SUPER_MASK) != 0) - m |= uiModifierSuper; - return m; -} - // capture on drag is done automatically on GTK+ static void finishMouseEvent(uiArea *a, uiAreaMouseEvent *me, guint mb, gdouble x, gdouble y, guint state, GdkWindow *window) { @@ -326,99 +297,14 @@ static gboolean areaWidget_leave_notify_event(GtkWidget *w, GdkEventCrossing *e) // note: there is no equivalent to WM_CAPTURECHANGED on GTK+; there literally is no way to break a grab like that (at least not on X11 and Wayland) // even if I invoke the task switcher and switch processes, the mouse grab will still be held until I let go of all buttons // therefore, no DragBroken() - -// we use GDK_KEY_Print as a sentinel because libui will never support the print screen key; that key belongs to the user - -static const struct { - guint keyval; - uiExtKey extkey; -} extKeys[] = { - { GDK_KEY_Escape, uiExtKeyEscape }, - { GDK_KEY_Insert, uiExtKeyInsert }, - { GDK_KEY_Delete, uiExtKeyDelete }, - { GDK_KEY_Home, uiExtKeyHome }, - { GDK_KEY_End, uiExtKeyEnd }, - { GDK_KEY_Page_Up, uiExtKeyPageUp }, - { GDK_KEY_Page_Down, uiExtKeyPageDown }, - { GDK_KEY_Up, uiExtKeyUp }, - { GDK_KEY_Down, uiExtKeyDown }, - { GDK_KEY_Left, uiExtKeyLeft }, - { GDK_KEY_Right, uiExtKeyRight }, - { GDK_KEY_F1, uiExtKeyF1 }, - { GDK_KEY_F2, uiExtKeyF2 }, - { GDK_KEY_F3, uiExtKeyF3 }, - { GDK_KEY_F4, uiExtKeyF4 }, - { GDK_KEY_F5, uiExtKeyF5 }, - { GDK_KEY_F6, uiExtKeyF6 }, - { GDK_KEY_F7, uiExtKeyF7 }, - { GDK_KEY_F8, uiExtKeyF8 }, - { GDK_KEY_F9, uiExtKeyF9 }, - { GDK_KEY_F10, uiExtKeyF10 }, - { GDK_KEY_F11, uiExtKeyF11 }, - { GDK_KEY_F12, uiExtKeyF12 }, - // numpad numeric keys and . are handled in events.c - { GDK_KEY_KP_Enter, uiExtKeyNEnter }, - { GDK_KEY_KP_Add, uiExtKeyNAdd }, - { GDK_KEY_KP_Subtract, uiExtKeyNSubtract }, - { GDK_KEY_KP_Multiply, uiExtKeyNMultiply }, - { GDK_KEY_KP_Divide, uiExtKeyNDivide }, - { GDK_KEY_Print, 0 }, -}; - -static const struct { - guint keyval; - uiModifiers mod; -} modKeys[] = { - { GDK_KEY_Control_L, uiModifierCtrl }, - { GDK_KEY_Control_R, uiModifierCtrl }, - { GDK_KEY_Alt_L, uiModifierAlt }, - { GDK_KEY_Alt_R, uiModifierAlt }, - { GDK_KEY_Meta_L, uiModifierAlt }, - { GDK_KEY_Meta_R, uiModifierAlt }, - { GDK_KEY_Shift_L, uiModifierShift }, - { GDK_KEY_Shift_R, uiModifierShift }, - { GDK_KEY_Super_L, uiModifierSuper }, - { GDK_KEY_Super_R, uiModifierSuper }, - { GDK_KEY_Print, 0 }, -}; - static int areaKeyEvent(uiArea *a, int up, GdkEventKey *e) { uiAreaKeyEvent ke; - guint state; - int i; - - ke.Key = 0; - ke.ExtKey = 0; - ke.Modifier = 0; - - state = translateModifiers(e->state, e->window); - ke.Modifiers = toModifiers(state); - - ke.Up = up; - - for (i = 0; extKeys[i].keyval != GDK_KEY_Print; i++) - if (extKeys[i].keyval == e->keyval) { - ke.ExtKey = extKeys[i].extkey; - goto keyFound; - } - - for (i = 0; modKeys[i].keyval != GDK_KEY_Print; i++) - if (modKeys[i].keyval == e->keyval) { - ke.Modifier = modKeys[i].mod; - // don't include the modifier in ke.Modifiers - ke.Modifiers &= ~ke.Modifier; - goto keyFound; - } - - if (uiprivFromScancode(e->hardware_keycode - 8, &ke)) - goto keyFound; - - // no supported key found; treat as unhandled + if (fillUiKeyEvent(&ke, e)) { + ke.Up = up; + return (*(a->ah->KeyEvent))(a->ah, a, &ke); + } return 0; - -keyFound: - return (*(a->ah->KeyEvent))(a->ah, a, &ke); } static gboolean areaWidget_key_press_event(GtkWidget *w, GdkEventKey *e) diff --git a/unix/entry.c b/unix/entry.c index 4a9a1d04..f7856aa1 100644 --- a/unix/entry.c +++ b/unix/entry.c @@ -1,5 +1,6 @@ // 11 june 2015 #include "uipriv_unix.h" +#include "keyboard.h" struct uiEntry { uiUnixControl c; @@ -9,6 +10,7 @@ struct uiEntry { void (*onChanged)(uiEntry *, void *); void *onChangedData; gulong onChangedSignal; + int (*onKeyEvent)(uiEntry *, uiAreaKeyEvent *); }; uiUnixControlAllDefaults(uiEntry) @@ -25,6 +27,22 @@ static void defaultOnChanged(uiEntry *e, void *data) // do nothing } +static gboolean onKeyEvent(GtkEditable *editable, GdkEventKey *event, gpointer data) +{ + uiEntry *e = (uiEntry*)(data); + uiAreaKeyEvent ke; + if (fillUiKeyEvent(&ke, event)) + return (*(e->onKeyEvent))(e, &ke); + return GDK_EVENT_PROPAGATE; +} + +static int defaultOnKeyEvent(uiEntry *e, uiAreaKeyEvent *uke) +{ + // do nothing + // return GDK_EVENT_STOP; // to stop further key handling + return GDK_EVENT_PROPAGATE; +} + char *uiEntryText(uiEntry *e) { return uiUnixStrdupText(gtk_entry_get_text(e->entry)); @@ -45,6 +63,11 @@ void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data) e->onChangedData = data; } +void uiEntryOnKeyEvent(uiEntry *e, int (*f)(uiEntry *, uiAreaKeyEvent *)) +{ + e->onKeyEvent = f; +} + int uiEntryReadOnly(uiEntry *e) { return gtk_editable_get_editable(e->editable) == FALSE; @@ -73,6 +96,11 @@ static uiEntry *finishNewEntry(GtkWidget *w, const gchar *signal) e->onChangedSignal = g_signal_connect(e->widget, signal, G_CALLBACK(onChanged), e); uiEntryOnChanged(e, defaultOnChanged, NULL); + gtk_widget_add_events(e->widget, GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK); + g_signal_connect(e->widget, "key_press_event", G_CALLBACK(onKeyEvent), e); + g_signal_connect(e->widget, "key_release_event", G_CALLBACK(onKeyEvent), e); + uiEntryOnKeyEvent(e, defaultOnKeyEvent); + return e; } diff --git a/unix/keyboard.h b/unix/keyboard.h new file mode 100644 index 00000000..81bb97fd --- /dev/null +++ b/unix/keyboard.h @@ -0,0 +1,130 @@ +#pragma once +#include "uipriv_unix.h" + + +static guint translateModifiers(guint state, GdkWindow *window) +{ + GdkModifierType statetype; + + // GDK doesn't initialize the modifier flags fully; we have to explicitly tell it to (thanks to Daniel_S and daniels (two different people) in irc.gimp.net/#gtk+) + statetype = state; + gdk_keymap_add_virtual_modifiers( + gdk_keymap_get_for_display(gdk_window_get_display(window)), + &statetype); + return statetype; +} + + +static uiModifiers toModifiers(guint state) +{ + uiModifiers m; + + m = 0; + if ((state & GDK_CONTROL_MASK) != 0) + m |= uiModifierCtrl; + if ((state & GDK_META_MASK) != 0) + m |= uiModifierAlt; + if ((state & GDK_MOD1_MASK) != 0) // GTK+ itself requires this to be Alt (just read through gtkaccelgroup.c) + m |= uiModifierAlt; + if ((state & GDK_SHIFT_MASK) != 0) + m |= uiModifierShift; + if ((state & GDK_SUPER_MASK) != 0) + m |= uiModifierSuper; + return m; +} + + +// we use GDK_KEY_Print as a sentinel because libui will never support the print screen key; that key belongs to the user + +static const struct { + guint keyval; + uiExtKey extkey; +} extKeys[] = { + { GDK_KEY_Escape, uiExtKeyEscape }, + { GDK_KEY_Insert, uiExtKeyInsert }, + { GDK_KEY_Delete, uiExtKeyDelete }, + { GDK_KEY_Home, uiExtKeyHome }, + { GDK_KEY_End, uiExtKeyEnd }, + { GDK_KEY_Page_Up, uiExtKeyPageUp }, + { GDK_KEY_Page_Down, uiExtKeyPageDown }, + { GDK_KEY_Up, uiExtKeyUp }, + { GDK_KEY_Down, uiExtKeyDown }, + { GDK_KEY_Left, uiExtKeyLeft }, + { GDK_KEY_Right, uiExtKeyRight }, + { GDK_KEY_F1, uiExtKeyF1 }, + { GDK_KEY_F2, uiExtKeyF2 }, + { GDK_KEY_F3, uiExtKeyF3 }, + { GDK_KEY_F4, uiExtKeyF4 }, + { GDK_KEY_F5, uiExtKeyF5 }, + { GDK_KEY_F6, uiExtKeyF6 }, + { GDK_KEY_F7, uiExtKeyF7 }, + { GDK_KEY_F8, uiExtKeyF8 }, + { GDK_KEY_F9, uiExtKeyF9 }, + { GDK_KEY_F10, uiExtKeyF10 }, + { GDK_KEY_F11, uiExtKeyF11 }, + { GDK_KEY_F12, uiExtKeyF12 }, + // numpad numeric keys and . are handled in events.c + { GDK_KEY_KP_Enter, uiExtKeyNEnter }, + { GDK_KEY_KP_Add, uiExtKeyNAdd }, + { GDK_KEY_KP_Subtract, uiExtKeyNSubtract }, + { GDK_KEY_KP_Multiply, uiExtKeyNMultiply }, + { GDK_KEY_KP_Divide, uiExtKeyNDivide }, + { GDK_KEY_Print, 0 }, +}; + +static const struct { + guint keyval; + uiModifiers mod; +} modKeys[] = { + { GDK_KEY_Control_L, uiModifierCtrl }, + { GDK_KEY_Control_R, uiModifierCtrl }, + { GDK_KEY_Alt_L, uiModifierAlt }, + { GDK_KEY_Alt_R, uiModifierAlt }, + { GDK_KEY_Meta_L, uiModifierAlt }, + { GDK_KEY_Meta_R, uiModifierAlt }, + { GDK_KEY_Shift_L, uiModifierShift }, + { GDK_KEY_Shift_R, uiModifierShift }, + { GDK_KEY_Super_L, uiModifierSuper }, + { GDK_KEY_Super_R, uiModifierSuper }, + { GDK_KEY_Print, 0 }, +}; + + +static gboolean fillUiKeyEvent(uiAreaKeyEvent *ke, GdkEventKey *e) +{ + guint state; + int i; + + ke->Key = 0; + ke->ExtKey = 0; + ke->Modifier = 0; + + state = translateModifiers(e->state, e->window); + ke->Modifiers = toModifiers(state); + for (i = 0; extKeys[i].keyval != GDK_KEY_Print; i++) + if (extKeys[i].keyval == e->keyval) { + ke->ExtKey = extKeys[i].extkey; + goto keyFound; + } + + for (i = 0; modKeys[i].keyval != GDK_KEY_Print; i++) + if (modKeys[i].keyval == e->keyval) { + ke->Modifier = modKeys[i].mod; + // don't include the modifier in ke->Modifiers + ke->Modifiers &= ~ke->Modifier; + goto keyFound; + } + + if (uiprivFromScancode(e->hardware_keycode - 8, ke)) + goto keyFound; + + // no supported key found; treat as unhandled + return FALSE; + +keyFound: + ke->Up = 0; + if (e->type == GDK_KEY_PRESS) + ke->Up = 1; + + return TRUE; +} diff --git a/unix/uipriv_unix.h b/unix/uipriv_unix.h index de604ced..b240a175 100644 --- a/unix/uipriv_unix.h +++ b/unix/uipriv_unix.h @@ -1,4 +1,5 @@ // 22 april 2015 +#pragma once #define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_40 #define GLIB_VERSION_MAX_ALLOWED GLIB_VERSION_2_40 #define GDK_VERSION_MIN_REQUIRED GDK_VERSION_3_10