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 99630264..7bf57d43 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,19 @@ static BOOL isSearchField(NSTextField *tf) return [tf isKindOfClass:[NSSearchField class]]; } +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 +94,28 @@ 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); + ke.Up = ([theEvent type] == NSKeyUp ? 1 : 0); + + if (uiprivFromKeycode([theEvent keyCode], &ke)) + uiprivMapWalkWithData(self->entries, &ke, triggerOnKeyEvent); + + return theEvent; + }; + eventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:(NSKeyDownMask | NSKeyUpMask) handler:eventHandler]; + return self; } - (void)dealloc { + [NSEvent removeMonitor:eventMonitor]; uiprivMapDestroy(self->entries); [super dealloc]; } @@ -155,6 +184,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 +209,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 +260,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..7a91bb75 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 @@ -110,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); diff --git a/ui.h b/ui.h index 40aea949..e73e35b6 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 { @@ -162,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); @@ -297,11 +304,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/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 diff --git a/windows/areaevents.cpp b/windows/areaevents.cpp index c7014ecb..c65a6b8a 100644 --- a/windows/areaevents.cpp +++ b/windows/areaevents.cpp @@ -1,26 +1,10 @@ // 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? -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. @@ -177,81 +161,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 +170,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 @@ -282,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 a7a077f2..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,8 +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); @@ -26,6 +65,11 @@ 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"); + RemoveProp(e->hwnd, L"ON_KEY_EVENT"); + RemoveProp(e->hwnd, L"SELF"); + uiWindowsUnregisterWM_COMMANDHandler(e->hwnd); uiWindowsEnsureDestroyWindow(e->hwnd); uiFreeControl(uiControl(e)); @@ -56,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); @@ -76,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; @@ -105,7 +162,13 @@ 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", (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 new file mode 100644 index 00000000..3b4ddaa3 --- /dev/null +++ b/windows/keyboard.hpp @@ -0,0 +1,121 @@ +#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 }, +}; + +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; +}