Fix several keyboard issues deadling with special characters/dead keys

This change adds support to write text with dead keys, and assign
keyboard shortcuts to special key combinations with Unicode characters
on macOS and Windows.

Fix #1083, close #796
This commit is contained in:
David Capello 2016-11-17 18:07:00 -03:00
parent b537b5261a
commit 00099390da
27 changed files with 559 additions and 323 deletions

View File

@ -383,6 +383,8 @@ bool CustomizedGuiManager::onProcessMessage(Message* msg)
break;
case kKeyDownMessage: {
auto keymsg = static_cast<KeyMessage*>(msg);
#ifdef _DEBUG
// Ctrl+Shift+Q generates a crash (useful to test the anticrash feature)
if (msg->ctrlPressed() &&
@ -396,7 +398,7 @@ bool CustomizedGuiManager::onProcessMessage(Message* msg)
// Ctrl+Shift+R recover active sprite from the backup store
if (msg->ctrlPressed() &&
msg->shiftPressed() &&
static_cast<KeyMessage*>(msg)->scancode() == kKeyR &&
keymsg->scancode() == kKeyR &&
App::instance()->dataRecovery() &&
App::instance()->dataRecovery()->activeSession() &&
current_editor &&

View File

@ -23,6 +23,7 @@ using namespace ui;
class SelectAccelerator::KeyField : public ui::Entry {
public:
KeyField(const Accelerator& accel) : ui::Entry(256, "") {
setTranslateDeadKeys(false);
setExpansive(true);
setFocusMagnet(true);
setAccel(accel);
@ -38,9 +39,13 @@ public:
protected:
bool onProcessMessage(Message* msg) override {
switch (msg->type()) {
case kKeyDownMessage:
if (hasFocus() && !isReadOnly()) {
KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
if (!keymsg->scancode() && keymsg->unicodeChar() < 32)
break;
KeyModifiers modifiers = keymsg->modifiers();
if (keymsg->scancode() == kKeySpace)

View File

@ -1,5 +1,5 @@
// SHE library
// Copyright (C) 2012-2015 David Capello
// Copyright (C) 2012-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -21,14 +21,12 @@ int key_repeated[KEY_MAX];
int she_keyboard_ucallback(int unicode_char, int* scancode)
{
int c = ((*scancode) & 0x7f);
Event ev;
Event ev;
ev.setType(Event::KeyDown);
ev.setScancode(static_cast<KeyScancode>(c));
if (unicode_char > 0)
ev.setUnicodeChar(unicode_char);
else
ev.setUnicodeChar(::scancode_to_ascii(c));
ev.setRepeat(key_repeated[c]++);
queue_event(ev);

View File

@ -206,6 +206,10 @@ public:
return sur;
}
void setTranslateDeadKeys(bool state) override {
// Do nothing
}
};
System* create_system() {
@ -247,6 +251,14 @@ bool is_key_pressed(KeyScancode scancode)
return key[scancode] ? true: false;
}
int get_unicode_from_scancode(KeyScancode scancode)
{
if (is_key_pressed(scancode))
return scancode_to_ascii(scancode);
else
return false;
}
void clear_keyboard_buffer()
{
clear_keybuf();

View File

@ -56,6 +56,7 @@ namespace she {
m_scancode(kKeyNil),
m_modifiers(kKeyUninitializedModifier),
m_unicodeChar(0),
m_isDead(false),
m_repeat(0),
m_preciseWheel(false),
m_pointerType(PointerType::Unknown),
@ -70,6 +71,7 @@ namespace she {
KeyScancode scancode() const { return m_scancode; }
KeyModifiers modifiers() const { return m_modifiers; }
int unicodeChar() const { return m_unicodeChar; }
bool isDeadKey() const { return m_isDead; }
int repeat() const { return m_repeat; }
gfx::Point position() const { return m_position; }
gfx::Point wheelDelta() const { return m_wheelDelta; }
@ -92,6 +94,7 @@ namespace she {
void setScancode(KeyScancode scancode) { m_scancode = scancode; }
void setModifiers(KeyModifiers modifiers) { m_modifiers = modifiers; }
void setUnicodeChar(int unicodeChar) { m_unicodeChar = unicodeChar; }
void setDeadKey(bool state) { m_isDead = state; }
void setRepeat(int repeat) { m_repeat = repeat; }
void setPosition(const gfx::Point& pos) { m_position = pos; }
void setWheelDelta(const gfx::Point& delta) { m_wheelDelta = delta; }
@ -108,6 +111,7 @@ namespace she {
KeyScancode m_scancode;
KeyModifiers m_modifiers;
int m_unicodeChar;
bool m_isDead;
int m_repeat; // repeat=0 means the first time the key is pressed
gfx::Point m_position;
gfx::Point m_wheelDelta;

View File

@ -1,5 +1,5 @@
// SHE library
// Copyright (C) 2012-2013, 2015 David Capello
// Copyright (C) 2012-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -156,9 +156,17 @@ namespace she {
kKeyScancodes = 127
};
// Deprecated API, use modifiers in she::Event
// TODO mark these functions as deprecated
// TODO move these functions to she::System
// Returns true if the the given scancode key is pressed/actived.
bool is_key_pressed(KeyScancode scancode);
// Returns the latest unicode character that activated the given
// scancode.
int get_unicode_from_scancode(KeyScancode scancode);
// Clears the keyboard buffer (used only in the Allegro port).
// TODO (deprecated)
void clear_keyboard_buffer();
} // namespace she

View File

@ -53,6 +53,8 @@
- (void)updateCurrentCursor;
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender;
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender;
- (void)doCommandBySelector:(SEL)selector;
- (void)setTranslateDeadKeys:(BOOL)state;
@end
#endif

View File

@ -17,14 +17,18 @@
#include "she/osx/generate_drop_files.h"
#include "she/osx/window.h"
#include <Carbon/Carbon.h> // For VK codes
using namespace she;
namespace {
// Internal array of pressed keys used in is_key_pressed()
bool pressed_keys[kKeyScancodes];
int g_pressedKeys[kKeyScancodes];
bool g_translateDeadKeys = false;
UInt32 g_lastDeadKeyState = 0;
inline gfx::Point get_local_mouse_pos(NSView* view, NSEvent* event)
gfx::Point get_local_mouse_pos(NSView* view, NSEvent* event)
{
NSPoint point = [view convertPoint:[event locationInWindow]
fromView:nil];
@ -37,7 +41,7 @@ inline gfx::Point get_local_mouse_pos(NSView* view, NSEvent* event)
(view.bounds.size.height - point.y) / scale);
}
inline Event::MouseButton get_mouse_buttons(NSEvent* event)
Event::MouseButton get_mouse_buttons(NSEvent* event)
{
// Some Wacom drivers on OS X report right-clicks with
// buttonNumber=0, so we've to check the type event anyway.
@ -61,7 +65,7 @@ inline Event::MouseButton get_mouse_buttons(NSEvent* event)
return Event::MouseButton::NoneButton;
}
inline KeyModifiers get_modifiers_from_nsevent(NSEvent* event)
KeyModifiers get_modifiers_from_nsevent(NSEvent* event)
{
int modifiers = kKeyNoneModifier;
NSEventModifierFlags nsFlags = event.modifierFlags;
@ -73,17 +77,43 @@ inline KeyModifiers get_modifiers_from_nsevent(NSEvent* event)
return (KeyModifiers)modifiers;
}
inline int get_unicodechar_from_nsevent(NSEvent* event)
// Based on code from:
// http://stackoverflow.com/questions/22566665/how-to-capture-unicode-from-key-events-without-an-nstextview
// http://stackoverflow.com/questions/12547007/convert-key-code-into-key-equivalent-string
// http://stackoverflow.com/questions/8263618/convert-virtual-key-code-to-unicode-string
//
// It includes a "translateDeadKeys" flag to avoid processing dead
// keys in case that we want to use key
CFStringRef get_unicode_from_key_code(NSEvent* event,
const bool translateDeadKeys)
{
int chr = 0;
// TODO we should use "event.characters" for a new kind of event (Event::Char or something like that)
NSString* chars = event.charactersIgnoringModifiers;
if (chars && chars.length >= 1) {
chr = [chars characterAtIndex:0];
if (chr < 32)
chr = 0;
}
return chr;
TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
CFDataRef keyLayoutData = (CFDataRef)TISGetInputSourceProperty(inputSource, kTISPropertyUnicodeKeyLayoutData);
const UCKeyboardLayout* keyLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(keyLayoutData);
UInt32 deadKeyState = (translateDeadKeys ? g_lastDeadKeyState: 0);
UniChar output[4];
UniCharCount length;
// Reference here:
// https://developer.apple.com/reference/coreservices/1390584-uckeytranslate?language=objc
UCKeyTranslate(
keyLayout,
event.keyCode,
kUCKeyActionDown,
((event.modifierFlags >> 16) & 0xFF),
LMGetKbdType(),
(translateDeadKeys ? 0: kUCKeyTranslateNoDeadKeysMask),
&deadKeyState,
sizeof(output) / sizeof(output[0]),
&length,
output);
if (translateDeadKeys)
g_lastDeadKeyState = deadKeyState;
CFRelease(inputSource);
return CFStringCreateWithCharacters(kCFAllocatorDefault, output, length);
}
} // anonymous namespace
@ -93,11 +123,19 @@ namespace she {
bool is_key_pressed(KeyScancode scancode)
{
if (scancode >= 0 && scancode < kKeyScancodes)
return pressed_keys[scancode];
return (g_pressedKeys[scancode] != 0);
else
return false;
}
int get_unicode_from_scancode(KeyScancode scancode)
{
if (scancode >= 0 && scancode < kKeyScancodes)
return g_pressedKeys[scancode];
else
return 0;
}
} // namespace she
@implementation OSXView
@ -167,17 +205,47 @@ bool is_key_pressed(KeyScancode scancode)
[super keyDown:event];
KeyScancode scancode = cocoavk_to_scancode(event.keyCode);
if (scancode >= 0 && scancode < kKeyScancodes)
pressed_keys[scancode] = true;
Event ev;
ev.setType(Event::KeyDown);
ev.setScancode(scancode);
ev.setModifiers(get_modifiers_from_nsevent(event));
ev.setRepeat(event.ARepeat ? 1: 0);
ev.setUnicodeChar(get_unicodechar_from_nsevent(event));
ev.setUnicodeChar(0);
queue_event(ev);
bool sendMsg = true;
CFStringRef strRef = get_unicode_from_key_code(event, false);
if (strRef) {
int length = CFStringGetLength(strRef);
if (length == 1)
ev.setUnicodeChar(CFStringGetCharacterAtIndex(strRef, 0));
CFRelease(strRef);
}
if (scancode >= 0 && scancode < kKeyScancodes)
g_pressedKeys[scancode] = (ev.unicodeChar() ? ev.unicodeChar(): 1);
if (g_translateDeadKeys) {
strRef = get_unicode_from_key_code(event, true);
if (strRef) {
int length = CFStringGetLength(strRef);
if (length > 0) {
sendMsg = false;
for (int i=0; i<length; ++i) {
ev.setUnicodeChar(CFStringGetCharacterAtIndex(strRef, i));
queue_event(ev);
}
g_lastDeadKeyState = 0;
}
else {
ev.setDeadKey(true);
}
CFRelease(strRef);
}
}
if (sendMsg)
queue_event(ev);
}
- (void)keyUp:(NSEvent*)event
@ -186,14 +254,14 @@ bool is_key_pressed(KeyScancode scancode)
KeyScancode scancode = cocoavk_to_scancode(event.keyCode);
if (scancode >= 0 && scancode < kKeyScancodes)
pressed_keys[scancode] = false;
g_pressedKeys[scancode] = 0;
Event ev;
ev.setType(Event::KeyUp);
ev.setScancode(scancode);
ev.setModifiers(get_modifiers_from_nsevent(event));
ev.setRepeat(event.ARepeat ? 1: 0);
ev.setUnicodeChar(get_unicodechar_from_nsevent(event));
ev.setUnicodeChar(0);
queue_event(ev);
}
@ -230,7 +298,7 @@ bool is_key_pressed(KeyScancode scancode)
((newFlags & flags[i]) != 0 ? Event::KeyDown:
Event::KeyUp));
pressed_keys[scancodes[i]] = ((newFlags & flags[i]) != 0);
g_pressedKeys[scancodes[i]] = ((newFlags & flags[i]) != 0);
ev.setScancode(scancodes[i]);
ev.setModifiers(modifiers);
@ -517,4 +585,15 @@ bool is_key_pressed(KeyScancode scancode)
return NO;
}
- (void)doCommandBySelector:(SEL)selector
{
// Do nothing (avoid beep pressing Escape key)
}
- (void)setTranslateDeadKeys:(BOOL)state
{
g_translateDeadKeys = (state ? true: false);
g_lastDeadKeyState = 0;
}
@end

View File

@ -184,6 +184,11 @@ void SkiaDisplay::setLayout(const std::string& layout)
m_window.setLayout(layout);
}
void SkiaDisplay::setTranslateDeadKeys(bool state)
{
m_window.setTranslateDeadKeys(state);
}
DisplayHandle SkiaDisplay::nativeHandle()
{
return (DisplayHandle)m_window.handle();

View File

@ -60,6 +60,8 @@ public:
std::string getLayout() override;
void setLayout(const std::string& layout) override;
void setTranslateDeadKeys(bool state);
// Returns the HWND on Windows.
DisplayHandle nativeHandle() override;

View File

@ -135,6 +135,15 @@ public:
return loadSurface(filename);
}
void setTranslateDeadKeys(bool state) override {
if (m_defaultDisplay)
m_defaultDisplay->setTranslateDeadKeys(state);
#ifdef _WIN32
g_queue.setTranslateDeadKeys(state);
#endif
}
private:
SkiaDisplay* m_defaultDisplay;
bool m_gpuAcceleration;

View File

@ -1,5 +1,5 @@
// SHE library
// Copyright (C) 2012-2015 David Capello
// Copyright (C) 2012-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -46,6 +46,7 @@ public:
void updateWindow(const gfx::Rect& bounds);
std::string getLayout() { return ""; }
void setLayout(const std::string& layout) { }
void setTranslateDeadKeys(bool state);
void* handle();
private:

View File

@ -15,6 +15,7 @@
#include "gfx/size.h"
#include "she/event.h"
#include "she/event_queue.h"
#include "she/osx/view.h"
#include "she/osx/window.h"
#include "she/skia/skia_display.h"
#include "she/skia/skia_surface.h"
@ -122,6 +123,11 @@ public:
[view displayIfNeeded];
}
void setTranslateDeadKeys(bool state) {
OSXView* view = (OSXView*)m_window.contentView;
[view setTranslateDeadKeys:(state ? YES: NO)];
}
void* handle() {
return (__bridge void*)m_window;
}
@ -450,6 +456,12 @@ void SkiaWindow::updateWindow(const gfx::Rect& bounds)
m_impl->updateWindow(bounds);
}
void SkiaWindow::setTranslateDeadKeys(bool state)
{
if (m_impl)
m_impl->setTranslateDeadKeys(state);
}
void* SkiaWindow::handle()
{
if (m_impl)

View File

@ -44,6 +44,10 @@ public:
std::string getLayout() { return ""; }
void setLayout(const std::string& layout) { }
void setTranslateDeadKeys(bool state) {
// Do nothing
}
private:
void onExpose() override;

View File

@ -49,6 +49,11 @@ namespace she {
virtual Surface* loadRgbaSurface(const char* filename) = 0;
virtual Font* loadSpriteSheetFont(const char* filename, int scale = 1) = 0;
virtual Font* loadTrueTypeFont(const char* filename, int height) = 0;
// Indicates if you want to use dead keys or not. By default it's
// false, which behaves as regular shortcuts. You should set this
// to true when you're inside a text field in your app.
virtual void setTranslateDeadKeys(bool state) = 0;
};
System* create_system();

View File

@ -1,5 +1,5 @@
// SHE library
// Copyright (C) 2012-2015 David Capello
// Copyright (C) 2012-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -19,6 +19,9 @@ namespace she {
class WinEventQueue : public EventQueue {
public:
WinEventQueue() : m_translateDeadKeys(false) {
}
void getEvent(Event& ev, bool canWait) override {
MSG msg;
@ -34,7 +37,18 @@ public:
}
if (res) {
TranslateMessage(&msg);
// Avoid transforming WM_KEYDOWN/UP into WM_DEADCHAR/WM_CHAR
// messages when m_translateDeadKeys is disabled.
//
// From MSDN TranslateMessage() documentation:
// "WM_KEYDOWN and WM_KEYUP combinations produce a WM_CHAR
// or WM_DEADCHAR message."
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644955.aspx
if ((m_translateDeadKeys) ||
(msg.message != WM_KEYDOWN &&
msg.message != WM_KEYUP)) {
TranslateMessage(&msg);
}
DispatchMessage(&msg);
}
else if (!canWait)
@ -54,8 +68,13 @@ public:
m_events.push(ev);
}
void setTranslateDeadKeys(bool state) {
m_translateDeadKeys = state;
}
private:
std::queue<Event> m_events;
bool m_translateDeadKeys;
};
typedef WinEventQueue EventQueueImpl;

View File

@ -8,9 +8,7 @@
#include "config.h"
#endif
#include "she/keys.h"
#include <windows.h>
#include "she/win/vk.h"
namespace she {
@ -337,6 +335,8 @@ static int scancode_to_win32vk(KeyScancode scancode)
return keymap[scancode];
}
// TODO Move these functions to she::System class
bool is_key_pressed(KeyScancode scancode)
{
int vk = scancode_to_win32vk(scancode);
@ -346,4 +346,39 @@ bool is_key_pressed(KeyScancode scancode)
return false;
}
int get_unicode_from_scancode(KeyScancode scancode)
{
int vk = scancode_to_win32vk(scancode);
if (vk && (GetAsyncKeyState(vk) & 0x8000 ? true: false)) {
VkToUnicode tu;
if (tu) {
tu.toUnicode(vk, 0);
if (tu.size() > 0)
return tu[0];
}
#if 0
BYTE keystate[256];
if (GetKeyboardState(&keystate[0])) {
WCHAR buffer[8];
int charsInBuffer = ToUnicode(vk, scancode, keystate, buffer,
sizeof(buffer)/sizeof(buffer[0]), 0);
// ToUnicode() returns -1 if there is dead-key waiting
if (charsInBuffer == -1) {
return buffer[0];
}
// ToUnicode returns several characters inside the buffer in
// case that a dead-key wasn't combined with the next pressed
// character.
else if (charsInBuffer > 0) {
// return buffer[charsInBuffer-1];
return buffer[0];
}
}
#endif
}
return 0;
}
} // namespace she

View File

@ -17,11 +17,12 @@
#include <sstream>
#include "base/base.h"
#include "base/debug.h"
#include "gfx/size.h"
#include "she/event.h"
#include "she/keys.h"
#include "she/native_cursor.h"
#include "she/win/system.h"
#include "she/win/vk.h"
#include "she/win/window_dde.h"
#ifndef WM_MOUSEHWHEEL
@ -30,9 +31,6 @@
namespace she {
KeyScancode win32vk_to_scancode(int vk);
KeyModifiers get_modifiers_from_last_win32_message();
#define SHE_WND_CLASS_NAME L"Aseprite.Window"
template<typename T>
@ -42,6 +40,7 @@ namespace she {
: m_clientSize(1, 1)
, m_restoredSize(0, 0)
, m_isCreated(false)
, m_translateDeadKeys(false)
, m_hasMouse(false)
, m_captureMouse(false)
, m_hpenctx(nullptr)
@ -321,6 +320,35 @@ namespace she {
}
}
void setTranslateDeadKeys(bool state) {
m_translateDeadKeys = state;
// Here we clear dead keys so we don't get those keys in the new
// "translate dead keys" state. E.g. If we focus a text entry
// field and the translation of dead keys is enabled, we don't
// want to get previous dead keys. The same in case we leave the
// text field with a pending dead key, that dead key must be
// discarded.
VkToUnicode tu;
if (tu) {
tu.toUnicode(VK_SPACE, 0);
if (tu.size() != 0)
tu.toUnicode(VK_SPACE, 0);
}
#if 0
BYTE keystate[256];
if (GetKeyboardState(keystate)) {
WCHAR buffer[8];
int charsInBuffer =
ToUnicode(VK_SPACE, 0, keystate, buffer,
sizeof(buffer)/sizeof(buffer[0]), 0);
if (charsInBuffer != 0)
ToUnicode(VK_SPACE, 0, keystate, buffer,
sizeof(buffer)/sizeof(buffer[0]), 0);
}
#endif
}
HWND handle() {
return m_hwnd;
}
@ -531,8 +559,6 @@ namespace she {
(msg == WM_MOUSEWHEEL ? -z: 0));
ev.setWheelDelta(delta);
//LOG("WHEEL: %d %d\n", delta.x, delta.y);
queueEvent(ev);
break;
}
@ -578,8 +604,6 @@ namespace she {
(msg == WM_VSCROLL ? (z-50): 0));
ev.setWheelDelta(delta);
//LOG("SCROLL: %d %d\n", delta.x, delta.y);
SetScrollPos(m_hwnd, bar, 50, FALSE);
queueEvent(ev);
@ -590,34 +614,67 @@ namespace she {
case WM_KEYDOWN: {
int vk = wparam;
int scancode = (lparam >> 16) & 0xff;
BYTE keystate[256];
WCHAR buffer[8];
int charsInBuffer = 0;
if (GetKeyboardState(&keystate[0])) {
// ToUnicode can return several characters inside the
// buffer in case that a dead-key wasn't combined with the
// next pressed character.
charsInBuffer = ToUnicode(vk, scancode, keystate, buffer,
sizeof(buffer)/sizeof(buffer[0]), 0);
}
bool sendMsg = true;
Event ev;
ev.setType(Event::KeyDown);
ev.setModifiers(get_modifiers_from_last_win32_message());
ev.setScancode(win32vk_to_scancode(vk));
ev.setUnicodeChar(0);
ev.setRepeat(MAX(0, (lparam & 0xffff)-1));
if (charsInBuffer < 1) {
ev.setUnicodeChar(0);
queueEvent(ev);
}
else {
for (int i=0; i<charsInBuffer; ++i) {
ev.setUnicodeChar(buffer[i]);
queueEvent(ev);
if (!m_translateDeadKeys) {
VkToUnicode tu;
if (tu) {
tu.toUnicode(vk, scancode);
if (tu.isDeadKey()) {
ev.setUnicodeChar(tu[0]);
tu.toUnicode(vk, scancode); // Call again to remove dead-key
}
else if (tu.size() > 0) {
sendMsg = false;
for (int chr : tu) {
ev.setUnicodeChar(chr);
queueEvent(ev);
}
}
}
#if 0
BYTE keystate[256];
if (GetKeyboardState(keystate)) {
WCHAR buffer[8];
// ToUnicode can return several characters inside the
// buffer in case that a dead-key wasn't combined with the
// next pressed character.
int charsInBuffer =
ToUnicode(vk, scancode, keystate, buffer,
sizeof(buffer)/sizeof(buffer[0]), 0);
// ToUnicode() returns -1 if there is dead-key waiting
if (charsInBuffer == -1) {
// Call again to remove dead-key
ToUnicode(vk, scancode, keystate, buffer,
sizeof(buffer)/sizeof(buffer[0]), 0);
ev.setUnicodeChar(buffer[0]);
}
// ToUnicode returns several characters inside the buffer in
// case that a dead-key wasn't combined with the next pressed
// character.
else if (charsInBuffer > 0) {
sendMsg = false;
for (int i=0; i<charsInBuffer; ++i) {
ev.setUnicodeChar(buffer[i]);
queueEvent(ev);
}
}
}
#endif
}
if (sendMsg)
queueEvent(ev);
return 0;
}
@ -636,6 +693,25 @@ namespace she {
return 0;
}
case WM_DEADCHAR: {
Event ev;
ev.setType(Event::KeyDown);
ev.setDeadKey(true);
ev.setUnicodeChar(wparam);
ev.setRepeat(lparam & 0xffff);
queueEvent(ev);
return 0;
}
case WM_CHAR: {
Event ev;
ev.setType(Event::KeyDown);
ev.setUnicodeChar(wparam);
ev.setRepeat(lparam & 0xffff);
queueEvent(ev);
return 0;
}
case WM_MENUCHAR:
// Avoid playing a sound when Alt+key is pressed and it's not in a native menu
return MAKELONG(0, MNC_CLOSE);
@ -856,6 +932,7 @@ namespace she {
gfx::Size m_restoredSize;
int m_scale;
bool m_isCreated;
bool m_translateDeadKeys;
bool m_hasMouse;
bool m_captureMouse;
bool m_customHcursor;

View File

@ -23,9 +23,6 @@
#include <algorithm>
// #define REPORT_KEYS
#define PREPROCESS_KEYS
namespace ui {
#ifdef _WIN32
@ -34,7 +31,117 @@ namespace ui {
const char* kWinKeyName = "Super";
#endif
static KeyModifiers get_pressed_modifiers_from_she()
namespace {
const char* scancode_to_string[] = { // Same order that she::KeyScancode
nullptr,
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"0 Pad",
"1 Pad",
"2 Pad",
"3 Pad",
"4 Pad",
"5 Pad",
"6 Pad",
"7 Pad",
"8 Pad",
"9 Pad",
"F1",
"F2",
"F3",
"F4",
"F5",
"F6",
"F7",
"F8",
"F9",
"F10",
"F11",
"F12",
"Esc",
"~",
"-",
"=",
"Backspace",
"Tab",
"[",
"]",
"Enter",
";",
"\'",
"\\",
"KEY_BACKSLASH2",
",",
".",
"/",
"Space",
"Ins",
"Del",
"Home",
"End",
"PgUp",
"PgDn",
"Left",
"Right",
"Up",
"Down",
"/ Pad",
"* Pad",
"- Pad",
"+ Pad",
"Del Pad",
"Enter Pad",
"PrtScr",
"Pause",
"KEY_ABNT_C1",
"Yen",
"Kana",
"KEY_CONVERT",
"KEY_NOCONVERT",
"KEY_AT",
"KEY_CIRCUMFLEX",
"KEY_COLON2",
"Kanji",
};
int scancode_to_string_size =
sizeof(scancode_to_string) / sizeof(scancode_to_string[0]);
KeyModifiers get_pressed_modifiers_from_she()
{
KeyModifiers mods = kKeyNoneModifier;
if (she::is_key_pressed(kKeyLShift) ) mods = KeyModifiers(int(mods) | int(kKeyShiftModifier));
@ -48,6 +155,8 @@ static KeyModifiers get_pressed_modifiers_from_she()
return mods;
}
} // anonymous namespace
Accelerator::Accelerator()
: m_modifiers(kKeyNoneModifier)
, m_scancode(kKeyNil)
@ -111,36 +220,8 @@ Accelerator::Accelerator(const std::string& str)
ASSERT(false && "Something wrong converting utf-8 to wchar string");
continue;
}
wchar_t wchr = wstr[0];
wchr = tolower(wchr);
if ((wchr >= 'a') && (wchr <= 'z')) {
m_unicodeChar = wchr;
m_scancode = (KeyScancode)((int)kKeyA + wchr - 'a');
}
else {
m_unicodeChar = wchr;
if ((wchr >= '0') && (wchr <= '9'))
m_scancode = (KeyScancode)((int)kKey0 + wchr - '0');
else {
switch (wchr) {
case '~': m_scancode = kKeyTilde; break;
case '-': m_scancode = kKeyMinus; break;
case '=': m_scancode = kKeyEquals; break;
case '[': m_scancode = kKeyOpenbrace; break;
case ']': m_scancode = kKeyClosebrace; break;
case ';': m_scancode = kKeyColon; break;
case '\'': m_scancode = kKeyQuote; break;
case '\\': m_scancode = kKeyBackslash; break;
case ',': m_scancode = kKeyComma; break;
case '.': m_scancode = kKeyStop; break;
case '/': m_scancode = kKeySlash; break;
case '*': m_scancode = kKeyAsterisk; break;
}
}
}
m_unicodeChar = std::tolower(wstr[0]);
m_scancode = kKeyNil;
}
// Other ones
else {
@ -218,19 +299,8 @@ Accelerator::Accelerator(const std::string& str)
bool Accelerator::operator==(const Accelerator& other) const
{
if (m_modifiers != other.m_modifiers)
return false;
if (m_scancode == other.m_scancode) {
if (m_scancode != kKeyNil)
return true;
else if (m_unicodeChar != 0)
return (std::tolower(m_unicodeChar) == std::tolower(other.m_unicodeChar));
else // Only comparing modifiers, and they are equal
return true;
}
return false;
// TODO improve this, avoid conversion to std::string
return toString() == other.toString();
}
bool Accelerator::isEmpty() const
@ -243,114 +313,6 @@ bool Accelerator::isEmpty() const
std::string Accelerator::toString() const
{
// Same order that she::KeyScancode
static const char* table[] = {
NULL,
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"0 Pad",
"1 Pad",
"2 Pad",
"3 Pad",
"4 Pad",
"5 Pad",
"6 Pad",
"7 Pad",
"8 Pad",
"9 Pad",
"F1",
"F2",
"F3",
"F4",
"F5",
"F6",
"F7",
"F8",
"F9",
"F10",
"F11",
"F12",
"Esc",
"~",
"-",
"=",
"Backspace",
"Tab",
"[",
"]",
"Enter",
";",
"\'",
"\\",
"KEY_BACKSLASH2",
",",
".",
"/",
"Space",
"Ins",
"Del",
"Home",
"End",
"PgUp",
"PgDn",
"Left",
"Right",
"Up",
"Down",
"/ Pad",
"* Pad",
"- Pad",
"+ Pad",
"Del Pad",
"Enter Pad",
"PrtScr",
"Pause",
"KEY_ABNT_C1",
"Yen",
"Kana",
"KEY_CONVERT",
"KEY_NOCONVERT",
"KEY_AT",
"KEY_CIRCUMFLEX",
"KEY_COLON2",
"Kanji",
};
static std::size_t table_size = sizeof(table) / sizeof(table[0]);
std::string buf;
// Shifts
@ -367,11 +329,12 @@ std::string Accelerator::toString() const
// Key
if (m_unicodeChar) {
std::wstring wideUnicodeChar;
wideUnicodeChar.push_back((wchar_t)toupper(m_unicodeChar));
wideUnicodeChar.push_back((wchar_t)std::toupper(m_unicodeChar));
buf += base::to_utf8(wideUnicodeChar);
}
else if (m_scancode && m_scancode > 0 && m_scancode < (int)table_size)
buf += table[m_scancode];
else if (m_scancode > 0 &&
m_scancode < scancode_to_string_size)
buf += scancode_to_string[m_scancode];
else if (!buf.empty() && buf[buf.size()-1] == '+')
buf.erase(buf.size()-1);
@ -380,97 +343,51 @@ std::string Accelerator::toString() const
bool Accelerator::isPressed(KeyModifiers modifiers, KeyScancode scancode, int unicodeChar) const
{
// Preprocess the character to be compared with the accelerator
#ifdef PREPROCESS_KEYS
// Directly scancode
if ((scancode >= kKeyF1 && scancode <= kKeyF12) ||
(scancode == kKeyEsc) ||
(scancode == kKeyBackspace) ||
(scancode == kKeyTab) ||
(scancode == kKeyEnter) ||
(scancode == kKeyBackslash) ||
(scancode == kKeyBackslash2) ||
(scancode >= kKeySpace && scancode <= kKeyDown) ||
(scancode >= kKeyEnterPad && scancode <= kKeyNoconvert) ||
(scancode == kKeyKanji)) {
unicodeChar = 0;
}
// For Ctrl+number
/* scancode unicodeChar
Ctrl+0 27 0
Ctrl+1 28 2
Ctrl+2 29 0
Ctrl+3 30 27
Ctrl+4 31 28
Ctrl+5 32 29
Ctrl+6 33 30
Ctrl+7 34 31
Ctrl+8 35 127
Ctrl+9 36 2
*/
else if ((scancode >= kKey0 && scancode <= kKey9) &&
(unicodeChar < 32 || unicodeChar == 127)) {
unicodeChar = '0' + scancode - kKey0;
scancode = kKeyNil;
}
// For Ctrl+letter
else if (unicodeChar >= 1 && unicodeChar <= 'z'-'a'+1) {
unicodeChar = 'a'+unicodeChar-1;
scancode = kKeyNil;
}
// For any other legal Unicode code
else if (unicodeChar >= ' ') {
unicodeChar = std::tolower(unicodeChar);
/* without shift (because characters like '*' can be trigger with
"Shift+8", so we don't want "Shift+*") */
if (!(unicodeChar >= 'a' && unicodeChar <= 'z'))
modifiers = (KeyModifiers)((int)modifiers & ((int)~kKeyShiftModifier));
scancode = kKeyNil;
}
#endif
#ifdef REPORT_KEYS
printf("%3d==%3d %3d==%3d %s==%s ",
m_scancode, scancode,
m_unicodeChar, unicodeChar,
toString().c_str(),
Accelerator(modifiers, scancode, unicodeChar).toString().c_str());
#endif
if ((m_modifiers == modifiers) &&
((m_scancode != kKeyNil && m_scancode == scancode) ||
(m_unicodeChar && m_unicodeChar == unicodeChar) ||
(m_scancode == kKeyNil && scancode == kKeyNil && !m_unicodeChar && !unicodeChar))) {
#ifdef REPORT_KEYS
printf("true\n");
fflush(stdout);
#endif
return true;
}
#ifdef REPORT_KEYS
printf("false\n");
fflush(stdout);
#endif
return false;
return ((scancode && *this == Accelerator(modifiers, scancode, 0)) ||
(unicodeChar && *this == Accelerator(modifiers, kKeyNil, unicodeChar)));
}
bool Accelerator::isPressed() const
{
KeyModifiers modifiers = get_pressed_modifiers_from_she();
KeyModifiers pressedModifiers = get_pressed_modifiers_from_she();
return ((m_scancode == 0 || she::is_key_pressed(m_scancode)) &&
(m_modifiers == modifiers));
// Check if this shortcut is only
if (m_scancode == kKeyNil && m_unicodeChar == 0)
return (m_modifiers == pressedModifiers);
// Compare with all pressed scancodes
for (int s=int(kKeyNil); s<int(kKeyFirstModifierScancode); ++s) {
if (she::is_key_pressed(KeyScancode(s)) &&
isPressed(pressedModifiers,
KeyScancode(s),
she::get_unicode_from_scancode(KeyScancode(s))))
return true;
}
return false;
}
bool Accelerator::isLooselyPressed() const
{
KeyModifiers modifiers = get_pressed_modifiers_from_she();
KeyModifiers pressedModifiers = get_pressed_modifiers_from_she();
return ((m_scancode == 0 || she::is_key_pressed(m_scancode)) &&
(int(m_modifiers & modifiers) == m_modifiers));
if ((m_modifiers & pressedModifiers) != m_modifiers)
return false;
// Check if this shortcut is only
if (m_scancode == kKeyNil && m_unicodeChar == 0)
return true;
// Compare with all pressed scancodes
for (int s=int(kKeyNil); s<int(kKeyFirstModifierScancode); ++s) {
if (she::is_key_pressed(KeyScancode(s)) &&
isPressed(m_modifiers, // Use same modifiers (we've already compared the modifiers)
KeyScancode(s),
she::get_unicode_from_scancode(KeyScancode(s))))
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////

View File

@ -17,6 +17,7 @@ namespace ui {
extern const char* kWinKeyName;
// TODO rename this class to Shortcut
class Accelerator {
public:
Accelerator();
@ -29,12 +30,12 @@ namespace ui {
bool isPressed(KeyModifiers modifiers, KeyScancode scancode, int unicodeChar) const;
// Returns true if the key is pressed and only its modifiers are
// Returns true if the key is pressed and ONLY its modifiers are
// pressed.
bool isPressed() const;
// Returns true if the key is pressed and the accelerator
// modifiers are pressed (other modifiers are allowed).
// Returns true if the key + its modifiers are pressed (other
// modifiers are allowed too).
bool isLooselyPressed() const;
bool operator==(const Accelerator& other) const;
@ -52,6 +53,7 @@ namespace ui {
int m_unicodeChar;
};
// TODO rename this class to Shortcuts
class Accelerators {
public:
typedef std::vector<Accelerator> List;

View File

@ -14,6 +14,7 @@
#include "base/string.h"
#include "clip/clip.h"
#include "she/font.h"
#include "she/system.h"
#include "ui/manager.h"
#include "ui/menu.h"
#include "ui/message.h"
@ -41,6 +42,7 @@ Entry::Entry(std::size_t maxsize, const char* format, ...)
, m_password(false)
, m_recent_focused(false)
, m_lock_selection(false)
, m_translate_dead_keys(true)
{
enableFlags(CTRL_RIGHT_CLICK);
@ -165,6 +167,11 @@ void Entry::setSuffix(const std::string& suffix)
invalidate();
}
void Entry::setTranslateDeadKeys(bool state)
{
m_translate_dead_keys = state;
}
void Entry::getEntryThemeInfo(int* scroll, int* caret, int* state,
int* selbeg, int* selend)
{
@ -213,6 +220,10 @@ bool Entry::onProcessMessage(Message* msg)
selectAllText();
m_recent_focused = true;
}
// Start processing dead keys
if (m_translate_dead_keys)
she::instance()->setTranslateDeadKeys(true);
break;
case kFocusLeaveMessage:
@ -224,6 +235,10 @@ bool Entry::onProcessMessage(Message* msg)
deselectText();
m_recent_focused = false;
// Stop processing dead keys
if (m_translate_dead_keys)
she::instance()->setTranslateDeadKeys(false);
break;
case kKeyDownMessage:
@ -300,20 +315,24 @@ bool Entry::onProcessMessage(Message* msg)
}
}
else if (manager()->isFocusMovementKey(msg)) {
break;
}
else if (keymsg->unicodeChar() >= 32) {
// Ctrl and Alt must be unpressed to insert a character
// in the text-field.
if ((msg->modifiers() & (kKeyCtrlModifier | kKeyAltModifier)) == 0) {
cmd = EntryCmd::InsertChar;
}
return Widget::onProcessMessage(msg);
}
break;
}
if (cmd == EntryCmd::NoOp)
if (cmd == EntryCmd::NoOp) {
if (keymsg->unicodeChar() >= 32) {
executeCmd(EntryCmd::InsertChar, keymsg->unicodeChar(),
(msg->shiftPressed()) ? true: false);
// Select dead-key
if (keymsg->isDeadKey()) {
selectText(m_caret-1, m_caret);
}
return true;
}
break;
}
executeCmd(cmd, keymsg->unicodeChar(),
(msg->shiftPressed()) ? true: false);

View File

@ -37,6 +37,8 @@ namespace ui {
void setSuffix(const std::string& suffix);
const std::string& getSuffix() { return m_suffix; }
void setTranslateDeadKeys(bool state);
// for themes
void getEntryThemeInfo(int* scroll, int* caret, int* state,
int* selbeg, int* selend);
@ -95,6 +97,7 @@ namespace ui {
bool m_password;
bool m_recent_focused;
bool m_lock_selection;
bool m_translate_dead_keys;
std::string m_suffix;
};

View File

@ -118,7 +118,7 @@ bool IntEntry::onProcessMessage(Message* msg)
if (hasFocus() && !isReadOnly()) {
KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
int chr = keymsg->unicodeChar();
if (chr < '0' || chr > '9') {
if (chr && (chr < '0' || chr > '9')) {
// By-pass Entry::onProcessMessage()
return Widget::onProcessMessage(msg);
}

View File

@ -317,6 +317,10 @@ void Manager::generateMessagesFromSheEvents()
sheEvent.modifiers(),
sheEvent.unicodeChar(),
sheEvent.repeat());
if (sheEvent.isDeadKey())
static_cast<KeyMessage*>(msg)->setDeadKey(true);
broadcastKeyMsg(msg);
enqueueMessage(msg);
break;

View File

@ -485,9 +485,15 @@ bool MenuBox::onProcessMessage(Message* msg)
if (((this->type() == kMenuBoxWidget) && (msg->modifiers() == kKeyNoneModifier || // <-- Inside menu-boxes we can use letters without Alt modifier pressed
msg->modifiers() == kKeyAltModifier)) ||
((this->type() == kMenuBarWidget) && (msg->modifiers() == kKeyAltModifier))) {
// TODO use scancode instead of unicodeChar
selected = check_for_letter(menu,
static_cast<KeyMessage*>(msg)->unicodeChar());
int unicode = static_cast<KeyMessage*>(msg)->unicodeChar();
selected = check_for_letter(menu, unicode);
if (!selected) {
KeyScancode scancode = static_cast<KeyMessage*>(msg)->scancode();
if (scancode >= kKeyA && scancode <= kKeyZ)
selected = check_for_letter(menu, 'a' + scancode - kKeyA);
else if (scancode >= kKey0 && scancode <= kKey9)
selected = check_for_letter(menu, '0' + scancode - kKey0);
}
if (selected) {
menu->highlightItem(selected, true, true, true);
@ -1196,7 +1202,7 @@ static MenuItem* check_for_letter(Menu* menu, int ascii)
MenuItem* menuitem = static_cast<MenuItem*>(child);
int mnemonic = menuitem->mnemonicChar();
if (mnemonic > 0 && mnemonic == tolower(ascii))
if (mnemonic > 0 && mnemonic == std::tolower(ascii))
return menuitem;
}
return NULL;

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2001-2013, 2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -11,6 +11,7 @@
#include "ui/message.h"
#include "base/memory.h"
#include "she/system.h"
#include "ui/manager.h"
#include "ui/widget.h"
@ -29,6 +30,7 @@ Message::Message(MessageType type, KeyModifiers modifiers)
((she::is_key_pressed(kKeyLShift) || she::is_key_pressed(kKeyRShift) ? kKeyShiftModifier: 0) |
(she::is_key_pressed(kKeyLControl) || she::is_key_pressed(kKeyRControl) ? kKeyCtrlModifier: 0) |
(she::is_key_pressed(kKeyAlt) ? kKeyAltModifier: 0) |
(she::is_key_pressed(kKeyAltGr) ? (kKeyCtrlModifier | kKeyAltModifier): 0) |
(she::is_key_pressed(kKeyCommand) ? kKeyCmdModifier: 0) |
(she::is_key_pressed(kKeySpace) ? kKeySpaceModifier: 0) |
(she::is_key_pressed(kKeyLWin) || she::is_key_pressed(kKeyRWin) ? kKeyWinModifier: 0));
@ -84,6 +86,7 @@ KeyMessage::KeyMessage(MessageType type,
, m_scancode(scancode)
, m_unicodeChar(unicodeChar)
, m_repeat(repeat)
, m_isDead(false)
, m_propagate_to_children(false)
, m_propagate_to_parent(true)
{

View File

@ -74,6 +74,8 @@ namespace ui {
KeyScancode scancode() const { return m_scancode; }
int unicodeChar() const { return m_unicodeChar; }
int repeat() const { return m_repeat; }
bool isDeadKey() const { return m_isDead; }
void setDeadKey(bool state) { m_isDead = state; }
bool propagateToChildren() const { return m_propagate_to_children; }
bool propagateToParent() const { return m_propagate_to_parent; }
void setPropagateToChildren(bool flag) { m_propagate_to_children = flag; }
@ -83,8 +85,9 @@ namespace ui {
KeyScancode m_scancode;
int m_unicodeChar;
int m_repeat; // repeat=0 means the first time the key is pressed
bool m_propagate_to_children : 1;
bool m_propagate_to_parent : 1;
bool m_isDead;
bool m_propagate_to_children;
bool m_propagate_to_parent;
};
class PaintMessage : public Message {