diff --git a/box.go b/box.go index 8bf244e..535543f 100644 --- a/box.go +++ b/box.go @@ -261,6 +261,9 @@ func (this *box) doLayout () { } func (this *box) setParent (parent parent) { + if this.parent != parent && this.Focused() { + this.SetFocused(false) + } this.parent = parent } @@ -316,3 +319,23 @@ func (this *box) handleMouseUp (button input.Button) { listener(button) } } + +func (this *box) handleKeyDown (key input.Key, numberPad bool) { + for _, listener := range this.on.keyDown.Listeners() { + listener(key, numberPad) + } +} + +func (this *box) handleKeyUp (key input.Key, numberPad bool) { + for _, listener := range this.on.keyUp.Listeners() { + listener(key, numberPad) + } +} + +func (this *box) propagate (callback func (anyBox) bool) bool { + return callback(this) +} + +func (this *box) propagateAlt (callback func (anyBox) bool) bool { + return callback(this) +} diff --git a/containerbox.go b/containerbox.go index 3aeee34..9a00ad5 100644 --- a/containerbox.go +++ b/containerbox.go @@ -227,3 +227,23 @@ func (this *containerBox) boxUnder (point image.Point) anyBox { return this.box.boxUnder(point) } + +func (this *containerBox) propagate (callback func (anyBox) bool) bool { + for _, box := range this.children { + box := box.(anyBox) + if !box.propagate(callback) { return false } + } + + return callback(this) +} + +func (this *containerBox) propagateAlt (callback func (anyBox) bool) bool { + if !callback(this) { return false} + + for _, box := range this.children { + box := box.(anyBox) + if !box.propagateAlt(callback) { return false } + } + + return true +} diff --git a/event.go b/event.go index 020d383..62baac4 100644 --- a/event.go +++ b/event.go @@ -8,6 +8,102 @@ import "git.tebibyte.media/tomo/xgbkb" import "github.com/jezek/xgbutil/xevent" import "git.tebibyte.media/tomo/tomo/input" +var buttonCodeTable = map[xproto.Keysym] input.Key { + 0xFFFFFF: input.KeyNone, + + 0xFF63: input.KeyInsert, + 0xFF67: input.KeyMenu, + 0xFF61: input.KeyPrintScreen, + 0xFF6B: input.KeyPause, + 0xFFE5: input.KeyCapsLock, + 0xFF14: input.KeyScrollLock, + 0xFF7F: input.KeyNumLock, + 0xFF08: input.KeyBackspace, + 0xFF09: input.KeyTab, + 0xFE20: input.KeyTab, + 0xFF0D: input.KeyEnter, + 0xFF1B: input.KeyEscape, + + 0xFF52: input.KeyUp, + 0xFF54: input.KeyDown, + 0xFF51: input.KeyLeft, + 0xFF53: input.KeyRight, + 0xFF55: input.KeyPageUp, + 0xFF56: input.KeyPageDown, + 0xFF50: input.KeyHome, + 0xFF57: input.KeyEnd, + + 0xFFE1: input.KeyLeftShift, + 0xFFE2: input.KeyRightShift, + 0xFFE3: input.KeyLeftControl, + 0xFFE4: input.KeyRightControl, + + 0xFFE7: input.KeyLeftMeta, + 0xFFE8: input.KeyRightMeta, + 0xFFE9: input.KeyLeftAlt, + 0xFFEA: input.KeyRightAlt, + 0xFFEB: input.KeyLeftSuper, + 0xFFEC: input.KeyRightSuper, + 0xFFED: input.KeyLeftHyper, + 0xFFEE: input.KeyRightHyper, + + 0xFFFF: input.KeyDelete, + + 0xFFBE: input.KeyF1, + 0xFFBF: input.KeyF2, + 0xFFC0: input.KeyF3, + 0xFFC1: input.KeyF4, + 0xFFC2: input.KeyF5, + 0xFFC3: input.KeyF6, + 0xFFC4: input.KeyF7, + 0xFFC5: input.KeyF8, + 0xFFC6: input.KeyF9, + 0xFFC7: input.KeyF10, + 0xFFC8: input.KeyF11, + 0xFFC9: input.KeyF12, + + 0xFF20: input.KeyDead, +} + +var keypadCodeTable = map[xproto.Keysym] input.Key { + 0xff80: input.Key(' '), + 0xff89: input.KeyTab, + 0xff8d: input.KeyEnter, + 0xff91: input.KeyF1, + 0xff92: input.KeyF2, + 0xff93: input.KeyF3, + 0xff94: input.KeyF4, + 0xff95: input.KeyHome, + 0xff96: input.KeyLeft, + 0xff97: input.KeyUp, + 0xff98: input.KeyRight, + 0xff99: input.KeyDown, + 0xff9a: input.KeyPageUp, + 0xff9b: input.KeyPageDown, + 0xff9c: input.KeyEnd, + 0xff9d: input.KeyHome, + 0xff9e: input.KeyInsert, + 0xff9f: input.KeyDelete, + 0xffbd: input.Key('='), + 0xffaa: input.Key('*'), + 0xffab: input.Key('+'), + 0xffac: input.Key(','), + 0xffad: input.Key('-'), + 0xffae: input.Key('.'), + 0xffaf: input.Key('/'), + + 0xffb0: input.Key('0'), + 0xffb1: input.Key('1'), + 0xffb2: input.Key('2'), + 0xffb3: input.Key('3'), + 0xffb4: input.Key('4'), + 0xffb5: input.Key('5'), + 0xffb6: input.Key('6'), + 0xffb7: input.Key('7'), + 0xffb8: input.Key('8'), + 0xffb9: input.Key('9'), +} + func (window *window) handleExpose ( connection *xgbutil.XUtil, event xevent.ExposeEvent, @@ -74,6 +170,80 @@ func (window *window) exposeEventFollows (event xproto.ConfigureNotifyEvent) (fo return false } +func keycodeToKey (keycode xproto.Keycode, state uint16) (input.Key, bool) { + keysym, char := xgbkb.KeycodeToKeysym(keycode, state) + + if xgbkb.IsOnNumpad(keysym) { + // look up in keypad table + key := keypadCodeTable[keysym] + return key, true + } else { + // look up in control code table + key, isControl := buttonCodeTable[keysym] + if isControl { + return key, false + } else { + // return as rune + return input.Key(char), false + } + } +} + +func (window *window) handleKeyPress ( + connection *xgbutil.XUtil, + event xevent.KeyPressEvent, +) { + if window.hasModal { return } + + keyEvent := *event.KeyPressEvent + key, numberPad := keycodeToKey(keyEvent.Detail, keyEvent.State) + window.updateModifiers(keyEvent.State) + + if key == input.KeyTab && window.modifiers.Alt { + if window.modifiers.Shift { + window.focusPrevious() + } else { + window.focusNext() + } + } else if key == input.KeyEscape && window.shy { + window.Close() + } else if window.focused != nil { + window.focused.handleKeyDown(key, numberPad) + } +} + +func (window *window) handleKeyRelease ( + connection *xgbutil.XUtil, + event xevent.KeyReleaseEvent, +) { + if window.hasModal { return } + + keyEvent := *event.KeyReleaseEvent + + // do not process this event if it was generated from a key repeat + nextEvents := xevent.Peek(window.backend.x) + if len(nextEvents) > 0 { + untypedEvent := nextEvents[0] + if untypedEvent.Err == nil { + typedEvent, ok := + untypedEvent.Event.(xproto.KeyPressEvent) + + if ok && typedEvent.Detail == keyEvent.Detail && + typedEvent.Event == keyEvent.Event && + typedEvent.State == keyEvent.State { + + return + } + } + } + + key, numberPad := keycodeToKey(keyEvent.Detail, keyEvent.State) + window.updateModifiers(keyEvent.State) + + if window.focused != nil { + window.focused.handleKeyUp(key, numberPad) + } +} func (window *window) handleButtonPress ( connection *xgbutil.XUtil, diff --git a/system.go b/system.go index f071522..41f33b6 100644 --- a/system.go +++ b/system.go @@ -48,6 +48,9 @@ type anyBox interface { boxUnder (image.Point) anyBox recalculateMinimumSize () + propagate (func (anyBox) bool) bool + propagateAlt (func (anyBox) bool) bool + handleFocusEnter () handleFocusLeave () // handleDndEnter () @@ -59,8 +62,8 @@ type anyBox interface { handleMouseDown (input.Button) handleMouseUp (input.Button) // handleScroll (float64, float64) - // handleKeyDown (input.Key, bool) - // handleKeyUp (input.Key, bool) + handleKeyDown (input.Key, bool) + handleKeyUp (input.Key, bool) } func assertAnyBox (unknown tomo.Box) anyBox { @@ -119,17 +122,68 @@ func (window *window) focus (box anyBox) { window.invalidateDraw(previous) previous.handleFocusLeave() } - if box != nil { + if box != nil && box.canBeFocused() { window.invalidateDraw(box) box.handleFocusEnter() } } +func (window *window) anyFocused () bool { + return window.focused != nil +} + func (this *window) boxUnder (point image.Point) anyBox { if this.root == nil { return nil } return this.root.boxUnder(point) } +func (this *window) focusNext () { + found := !this.anyFocused() + focused := false + this.propagateAlt (func (box anyBox) bool { + if found { + // looking for the next box to select + if box.canBeFocused() { + // found it + this.focus(box) + focused = true + return false + } + } else { + // looking for the current focused element + if box == this.focused { + // found it + found = true + } + } + return true + }) + + if !focused { this.focus(nil) } +} + +func (this *window) focusPrevious () { + var behind anyBox + this.propagate (func (box anyBox) bool { + if box == this.focused { + return false + } + if box.canBeFocused() { behind = box } + return true + }) + this.focus(behind) +} + +func (this *window) propagate (callback func (box anyBox) bool) { + if this.root == nil { return } + this.root.propagate(callback) +} + +func (this *window) propagateAlt (callback func (box anyBox) bool) { + if this.root == nil { return } + this.root.propagateAlt(callback) +} + func (window *window) afterEvent () { if window.xCanvas == nil { return } diff --git a/window.go b/window.go index 85da758..ff6ea16 100644 --- a/window.go +++ b/window.go @@ -109,10 +109,10 @@ func (backend *Backend) newWindow ( Connect(backend.x, window.xWindow.Id) xevent.ConfigureNotifyFun(window.handleConfigureNotify). Connect(backend.x, window.xWindow.Id) - // xevent.KeyPressFun(window.handleKeyPress). - // Connect(backend.x, window.xWindow.Id) - // xevent.KeyReleaseFun(window.handleKeyRelease). - // Connect(backend.x, window.xWindow.Id) + xevent.KeyPressFun(window.handleKeyPress). + Connect(backend.x, window.xWindow.Id) + xevent.KeyReleaseFun(window.handleKeyRelease). + Connect(backend.x, window.xWindow.Id) xevent.ButtonPressFun(window.handleButtonPress). Connect(backend.x, window.xWindow.Id) xevent.ButtonReleaseFun(window.handleButtonRelease).