/* * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #include "common.h" #include #include "GlassApplication.h" #include "ViewContainer.h" #include "GlassView.h" #include "KeyTable.h" #include "Utils.h" #include "GlassDnD.h" #include "GlassInputTextInfo.h" #include "ManipulationEvents.h" #include "BaseWnd.h" #include "com_sun_glass_events_ViewEvent.h" #include "com_sun_glass_events_KeyEvent.h" #include "com_sun_glass_events_MouseEvent.h" #include "com_sun_glass_events_DndEvent.h" #include "com_sun_glass_events_TouchEvent.h" static UINT LangToCodePage(LANGID idLang) { WCHAR strCodePage[8]; // use the LANGID to create a LCID LCID idLocale = MAKELCID(idLang, SORT_DEFAULT); // get the ANSI code page associated with this locale if (::GetLocaleInfo(idLocale, LOCALE_IDEFAULTANSICODEPAGE, strCodePage, sizeof(strCodePage) / sizeof(WCHAR)) > 0) { return _wtoi(strCodePage); } else { return ::GetACP(); } } namespace { bool IsTouchEvent() { // Read this link if you wonder why we need to hard code the mask and signature: // http://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx //"The lower 8 bits returned from GetMessageExtraInfo are variable. // Of those bits, 7 (the lower 7, masked by 0x7F) are used to represent the cursor ID, // zero for the mouse or a variable value for the pen ID. // Additionally, in Windows Vista, the eighth bit, masked by 0x80, is used to // differentiate touch input from pen input (0 = pen, 1 = touch)." UINT SIGNATURE = 0xFF515780; UINT MASK = 0xFFFFFF80; UINT v = (UINT) GetMessageExtraInfo(); return ((v & MASK) == SIGNATURE); } } // namespace ViewContainer::ViewContainer() : m_view(NULL), m_bTrackingMouse(FALSE), m_manipProc(NULL), m_inertiaProc(NULL), m_manipEventSink(NULL), m_gestureSupportCls(NULL), m_lastMouseMovePosition(-1), m_mouseButtonDownCounter(0), m_deadKeyWParam(0) { m_kbLayout = ::GetKeyboardLayout(0); m_idLang = LOWORD(m_kbLayout); m_codePage = LangToCodePage(m_idLang); m_lastTouchInputCount = 0; } jobject ViewContainer::GetView() { return GetGlassView() != NULL ? GetGlassView()->GetView() : NULL; } void ViewContainer::InitDropTarget(HWND hwnd) { if (!hwnd) { return; } m_spDropTarget = std::auto_ptr(new GlassDropTarget(this, hwnd)); } void ViewContainer::ReleaseDropTarget() { m_spDropTarget = std::auto_ptr(); } void ViewContainer::InitManipProcessor(HWND hwnd) { if (IS_WIN7) { ::RegisterTouchWindow(hwnd, TWF_WANTPALM); HRESULT hr = ::CoCreateInstance(CLSID_ManipulationProcessor, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (VOID**)(&m_manipProc) ); if (SUCCEEDED(hr)) { ::CoCreateInstance(CLSID_InertiaProcessor, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (VOID**)(&m_inertiaProc) ); m_manipEventSink = new ManipulationEventSinkWithInertia(m_manipProc, m_inertiaProc, this, hwnd); } const DWORD_PTR dwHwndTabletProperty = TABLET_DISABLE_PENTAPFEEDBACK | TABLET_DISABLE_PENBARRELFEEDBACK | TABLET_DISABLE_FLICKS; ::SetProp(hwnd, MICROSOFT_TABLETPENSERVICE_PROPERTY, reinterpret_cast(dwHwndTabletProperty)); if (!m_gestureSupportCls) { JNIEnv *env = GetEnv(); const jclass cls = GlassApplication::ClassForName(env, "com.sun.glass.ui.win.WinGestureSupport"); m_gestureSupportCls = (jclass)env->NewGlobalRef(cls); env->DeleteLocalRef(cls); ASSERT(m_gestureSupportCls); } } } void ViewContainer::ReleaseManipProcessor() { if (IS_WIN7) { if (m_manipProc) { m_manipProc->Release(); m_manipProc = NULL; } if (m_inertiaProc) { m_inertiaProc->Release(); m_inertiaProc = NULL; } if (m_manipEventSink) { m_manipEventSink->Release(); m_manipEventSink = NULL; } } if (m_gestureSupportCls) { JNIEnv *env = GetEnv(); env->DeleteGlobalRef(m_gestureSupportCls); m_gestureSupportCls = 0; } } void ViewContainer::HandleViewInputLangChange(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (!GetGlassView()) { return; } m_kbLayout = reinterpret_cast(lParam); m_idLang = LOWORD(m_kbLayout); m_codePage = LangToCodePage(m_idLang); m_deadKeyWParam = 0; } void ViewContainer::NotifyViewSize(HWND hwnd) { if (!hwnd || !GetGlassView()) { return; } RECT r; if (::GetClientRect(hwnd, &r)) { JNIEnv* env = GetEnv(); env->CallVoidMethod(GetView(), javaIDs.View.notifyResize, r.right-r.left, r.bottom - r.top); CheckAndClearException(env); } } void ViewContainer::HandleViewPaintEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (!GetGlassView()) { return; } RECT r; if (!::GetUpdateRect(hwnd, &r, FALSE)) { return; } JNIEnv* env = GetEnv(); env->CallVoidMethod(GetView(), javaIDs.View.notifyRepaint, r.left, r.top, r.right-r.left, r.bottom-r.top); CheckAndClearException(env); } LRESULT ViewContainer::HandleViewGetAccessible(HWND hwnd, WPARAM wParam, LPARAM lParam) { if (!GetGlassView()) { return NULL; } /* WM_GETOBJECT is sent to request different object types, * always test the type to avoid unnecessary work. */ LRESULT lr = NULL; if (static_cast(lParam) == static_cast(UiaRootObjectId)) { /* The client is requesting UI Automation. */ JNIEnv* env = GetEnv(); if (!env) return NULL; jlong pProvider = env->CallLongMethod(GetView(), javaIDs.View.getAccessible); CheckAndClearException(env); /* It is possible WM_GETOBJECT is sent before the toolkit is ready to * create the accessible object (getAccessible returns NULL). * On Windows 7, calling UiaReturnRawElementProvider() with a NULL provider * returns an invalid LRESULT which stops further WM_GETOBJECT to be sent, * effectively disabling accessibility for the window. */ if (pProvider) { lr = UiaReturnRawElementProvider(hwnd, wParam, lParam, reinterpret_cast(pProvider)); } } else if (static_cast(lParam) == static_cast(OBJID_CLIENT)) { /* By default JAWS does not send WM_GETOBJECT with UiaRootObjectId till * a focus event is raised by UiaRaiseAutomationEvent(). * In some systems (i.e. touch monitors), OBJID_CLIENT are sent when * no screen reader is active. Test for SPI_GETSCREENREADER and * UiaClientsAreListening() to avoid initializing accessibility * unnecessarily. */ UINT screenReader = 0; ::SystemParametersInfo(SPI_GETSCREENREADER, 0, &screenReader, 0); if (screenReader && UiaClientsAreListening()) { JNIEnv* env = GetEnv(); if (env) { /* Calling getAccessible() initializes accessibility which * eventually raises the focus events required to indicate to * JAWS to use UIA for this window. * * Note: do not return the accessible object for OBJID_CLIENT, * that would create an UIA-MSAA bridge. That problem with the * bridge is that it does not respect * ProviderOptions_UseComThreading. */ env->CallLongMethod(GetView(), javaIDs.View.getAccessible); CheckAndClearException(env); } } } return lr; } void ViewContainer::HandleViewSizeEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (wParam == SIZE_MINIMIZED) { return; } NotifyViewSize(hwnd); } void ViewContainer::HandleViewMenuEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (!GetGlassView()) { return; } if ((HWND)wParam != hwnd) { return; } jboolean isKeyboardTrigger = lParam == (LPARAM)-1; if (isKeyboardTrigger) { lParam = ::GetMessagePos (); } POINT pt; int absX = pt.x = GET_X_LPARAM(lParam); int absY = pt.y = GET_Y_LPARAM(lParam); ::ScreenToClient (hwnd, &pt); if (!isKeyboardTrigger) { RECT rect; ::GetClientRect(hwnd, &rect); if (!::PtInRect(&rect, pt)) { return; } } // unmirror the x coordinate LONG style = ::GetWindowLong(hwnd, GWL_EXSTYLE); if (style & WS_EX_LAYOUTRTL) { RECT rect = {0}; ::GetClientRect(hwnd, &rect); pt.x = max(0, rect.right - rect.left) - pt.x; } JNIEnv* env = GetEnv(); env->CallVoidMethod(GetView(), javaIDs.View.notifyMenu, pt.x, pt.y, absX, absY, isKeyboardTrigger); CheckAndClearException(env); } void ViewContainer::HandleViewKeyEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (!GetGlassView()) { return; } static const BYTE KEY_STATE_DOWN = 0x80; UINT wKey = static_cast(wParam); UINT flags = HIWORD(lParam); jint jKeyCode = WindowsKeyToJavaKey(wKey); if (flags & (1 << 8)) { // this is an extended key (e.g. Right ALT == AltGr) switch (jKeyCode) { case com_sun_glass_events_KeyEvent_VK_ALT: jKeyCode = com_sun_glass_events_KeyEvent_VK_ALT_GRAPH; break; } } BYTE kbState[256]; if (!::GetKeyboardState(kbState)) { return; } jint jModifiers = GetModifiers(); if (jModifiers & com_sun_glass_events_KeyEvent_MODIFIER_CONTROL) { kbState[VK_CONTROL] &= ~KEY_STATE_DOWN; } WORD mbChar; UINT scancode = ::MapVirtualKeyEx(wKey, 0, m_kbLayout); // Depress modifiers to map a Unicode char to a key code kbState[VK_CONTROL] &= ~0x80; kbState[VK_SHIFT] &= ~0x80; kbState[VK_MENU] &= ~0x80; int converted = ::ToAsciiEx(wKey, scancode, kbState, &mbChar, 0, m_kbLayout); wchar_t wChar[4] = {0}; int unicodeConverted = ::ToUnicodeEx(wKey, scancode, kbState, wChar, 4, 0, m_kbLayout); // Some virtual codes require special handling switch (wKey) { case 0x00BA:// VK_OEM_1 case 0x00BB:// VK_OEM_PLUS case 0x00BC:// VK_OEM_COMMA case 0x00BD:// VK_OEM_MINUS case 0x00BE:// VK_OEM_PERIOD case 0x00BF:// VK_OEM_2 case 0x00C0:// VK_OEM_3 case 0x00DB:// VK_OEM_4 case 0x00DC:// VK_OEM_5 case 0x00DD:// VK_OEM_6 case 0x00DE:// VK_OEM_7 case 0x00DF:// VK_OEM_8 case 0x00E2:// VK_OEM_102 if (unicodeConverted < 0) { // Dead key switch (wChar[0]) { case L'`': jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_GRAVE; break; case L'\'': jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_ACUTE; break; case 0x00B4: jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_ACUTE; break; case L'^': jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_CIRCUMFLEX; break; case L'~': jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_TILDE; break; case 0x02DC: jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_TILDE; break; case 0x00AF: jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_MACRON; break; case 0x02D8: jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_BREVE; break; case 0x02D9: jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_ABOVEDOT; break; case L'"': jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_DIAERESIS; break; case 0x00A8: jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_DIAERESIS; break; case 0x02DA: jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_ABOVERING; break; case 0x02DD: jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_DOUBLEACUTE; break; case 0x02C7: jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_CARON; break; // aka hacek case L',': jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_CEDILLA; break; case 0x00B8: jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_CEDILLA; break; case 0x02DB: jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_OGONEK; break; case 0x037A: jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_IOTA; break; // ASCII ??? case 0x309B: jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_VOICED_SOUND; break; case 0x309C: jKeyCode = com_sun_glass_events_KeyEvent_VK_DEAD_SEMIVOICED_SOUND; break; default: jKeyCode = com_sun_glass_events_KeyEvent_VK_UNDEFINED; break; }; } else if (unicodeConverted == 1) { switch (wChar[0]) { case L'!': jKeyCode = com_sun_glass_events_KeyEvent_VK_EXCLAMATION; break; case L'"': jKeyCode = com_sun_glass_events_KeyEvent_VK_DOUBLE_QUOTE; break; case L'#': jKeyCode = com_sun_glass_events_KeyEvent_VK_NUMBER_SIGN; break; case L'$': jKeyCode = com_sun_glass_events_KeyEvent_VK_DOLLAR; break; case L'&': jKeyCode = com_sun_glass_events_KeyEvent_VK_AMPERSAND; break; case L'\'': jKeyCode = com_sun_glass_events_KeyEvent_VK_QUOTE; break; case L'(': jKeyCode = com_sun_glass_events_KeyEvent_VK_LEFT_PARENTHESIS; break; case L')': jKeyCode = com_sun_glass_events_KeyEvent_VK_RIGHT_PARENTHESIS; break; case L'*': jKeyCode = com_sun_glass_events_KeyEvent_VK_ASTERISK; break; case L'+': jKeyCode = com_sun_glass_events_KeyEvent_VK_PLUS; break; case L',': jKeyCode = com_sun_glass_events_KeyEvent_VK_COMMA; break; case L'-': jKeyCode = com_sun_glass_events_KeyEvent_VK_MINUS; break; case L'.': jKeyCode = com_sun_glass_events_KeyEvent_VK_PERIOD; break; case L'/': jKeyCode = com_sun_glass_events_KeyEvent_VK_SLASH; break; case L':': jKeyCode = com_sun_glass_events_KeyEvent_VK_COLON; break; case L';': jKeyCode = com_sun_glass_events_KeyEvent_VK_SEMICOLON; break; case L'<': jKeyCode = com_sun_glass_events_KeyEvent_VK_LESS; break; case L'=': jKeyCode = com_sun_glass_events_KeyEvent_VK_EQUALS; break; case L'>': jKeyCode = com_sun_glass_events_KeyEvent_VK_GREATER; break; case L'@': jKeyCode = com_sun_glass_events_KeyEvent_VK_AT; break; case L'[': jKeyCode = com_sun_glass_events_KeyEvent_VK_OPEN_BRACKET; break; case L'\\': jKeyCode = com_sun_glass_events_KeyEvent_VK_BACK_SLASH; break; case L']': jKeyCode = com_sun_glass_events_KeyEvent_VK_CLOSE_BRACKET; break; case L'^': jKeyCode = com_sun_glass_events_KeyEvent_VK_CIRCUMFLEX; break; case L'_': jKeyCode = com_sun_glass_events_KeyEvent_VK_UNDERSCORE; break; case L'`': jKeyCode = com_sun_glass_events_KeyEvent_VK_BACK_QUOTE; break; case L'{': jKeyCode = com_sun_glass_events_KeyEvent_VK_BRACELEFT; break; case L'}': jKeyCode = com_sun_glass_events_KeyEvent_VK_BRACERIGHT; break; case 0x00A1: jKeyCode = com_sun_glass_events_KeyEvent_VK_INV_EXCLAMATION; break; case 0x20A0: jKeyCode = com_sun_glass_events_KeyEvent_VK_EURO_SIGN; break; default: jKeyCode = com_sun_glass_events_KeyEvent_VK_UNDEFINED; break; } } else if (unicodeConverted == 0 || unicodeConverted > 1) { jKeyCode = com_sun_glass_events_KeyEvent_VK_UNDEFINED; } break; }; int keyCharCount = 0; jchar keyChars[4]; const bool isAutoRepeat = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) && (lParam & (1 << 30)); if (converted < 0) { // Dead key return; } else if (converted == 0) { // No translation available keyCharCount = 0; // This includes SHIFT, CONTROL, ALT, etc. // RT-17062: suppress auto-repeated events for modifier keys if (isAutoRepeat) { switch (jKeyCode) { case com_sun_glass_events_KeyEvent_VK_SHIFT: case com_sun_glass_events_KeyEvent_VK_CONTROL: case com_sun_glass_events_KeyEvent_VK_ALT: case com_sun_glass_events_KeyEvent_VK_ALT_GRAPH: case com_sun_glass_events_KeyEvent_VK_WINDOWS: return; } } } else { // Handle some special cases if ((wKey == VK_BACK) || (wKey == VK_ESCAPE)) { keyCharCount = 0; } else { keyCharCount = ::MultiByteToWideChar(m_codePage, MB_PRECOMPOSED, (LPCSTR)&mbChar, 2, (LPWSTR)keyChars, 4 * sizeof(jchar)) - 1; if (keyCharCount <= 0) { return; } } } JNIEnv* env = GetEnv(); jcharArray jKeyChars = env->NewCharArray(keyCharCount); if (jKeyChars) { if (keyCharCount) { env->SetCharArrayRegion(jKeyChars, 0, keyCharCount, keyChars); CheckAndClearException(env); } if (jKeyCode == com_sun_glass_events_KeyEvent_VK_PRINTSCREEN && (msg == WM_KEYUP || msg == WM_SYSKEYUP)) { // MS Windows doesn't send WM_KEYDOWN for the PrintScreen key, // so we synthesize one env->CallVoidMethod(GetView(), javaIDs.View.notifyKey, com_sun_glass_events_KeyEvent_PRESS, jKeyCode, jKeyChars, jModifiers); CheckAndClearException(env); } if (GetGlassView()) { env->CallVoidMethod(GetView(), javaIDs.View.notifyKey, (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) ? com_sun_glass_events_KeyEvent_PRESS : com_sun_glass_events_KeyEvent_RELEASE, jKeyCode, jKeyChars, jModifiers); CheckAndClearException(env); } // MS Windows doesn't send WM_CHAR for the Delete key, // so we synthesize one if (jKeyCode == com_sun_glass_events_KeyEvent_VK_DELETE && (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) && GetGlassView()) { // 0x7F == U+007F - a Unicode character for DELETE SendViewTypedEvent(1, (jchar)0x7F); } env->DeleteLocalRef(jKeyChars); } } void ViewContainer::SendViewTypedEvent(int repCount, jchar wChar) { if (!GetGlassView()) { return; } JNIEnv* env = GetEnv(); jcharArray jKeyChars = env->NewCharArray(repCount); if (jKeyChars) { jchar* nKeyChars = env->GetCharArrayElements(jKeyChars, NULL); if (nKeyChars) { for (int i = 0; i < repCount; i++) { nKeyChars[i] = wChar; } env->ReleaseCharArrayElements(jKeyChars, nKeyChars, 0); env->CallVoidMethod(GetView(), javaIDs.View.notifyKey, com_sun_glass_events_KeyEvent_TYPED, com_sun_glass_events_KeyEvent_VK_UNDEFINED, jKeyChars, GetModifiers()); CheckAndClearException(env); } env->DeleteLocalRef(jKeyChars); } } void ViewContainer::HandleViewDeadKeyEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (!GetGlassView()) { return; } if (!m_deadKeyWParam) { // HandleViewKeyEvent() calls ::ToAsciiEx and ::ToUnicodeEx which clear // the dead key status from the keyboard layout. We store the current dead // key here to use it when processing WM_CHAR in order to get the // actual character typed. m_deadKeyWParam = wParam; } else { // There already was another dead key pressed previously. Clear it // and send two separate TYPED events instead to emulate native behavior. SendViewTypedEvent(1, (jchar)m_deadKeyWParam); SendViewTypedEvent(1, (jchar)wParam); m_deadKeyWParam = 0; } // Since we handle dead keys ourselves, reset the keyboard dead key status (if any) static BYTE kbState[256]; ::GetKeyboardState(kbState); WORD ignored; ::ToAsciiEx(VK_SPACE, ::MapVirtualKey(VK_SPACE, 0), kbState, &ignored, 0, m_kbLayout); } void ViewContainer::HandleViewTypedEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (!GetGlassView()) { return; } int repCount = LOWORD(lParam); jchar wChar; if (!m_deadKeyWParam) { wChar = (jchar)wParam; } else { // The character is composed together with the dead key, which // may be translated into one or more combining characters. const size_t COMP_SIZE = 5; wchar_t comp[COMP_SIZE] = { (wchar_t)wParam }; // Some dead keys need additional translation: // http://www.fileformat.info/info/unicode/block/combining_diacritical_marks/images.htm // Also see awt_Component.cpp for the original dead keys table if (LOBYTE(m_idLang) == LANG_GREEK) { switch (m_deadKeyWParam) { case L']': comp[1] = 0x300; break; // varia case L';': comp[1] = 0x301; break; // oxia (wrong? generates tonos, not oxia) case L'-': comp[1] = 0x304; break; // macron case L'_': comp[1] = 0x306; break; // vrachy case L':': comp[1] = 0x308; break; // dialytika case L'"': comp[1] = 0x314; break; // dasia case 0x0384: comp[1] = 0x341; break; // tonos case L'[': comp[1] = 0x342; break; // perispomeni case L'\'': comp[1] = 0x343; break; // psili case L'~': comp[1] = 0x344; break; // dialytika oxia case L'{': comp[1] = 0x345; break; // ypogegrammeni case L'`': comp[1] = 0x308; comp[2] = 0x300; break; // dialytika varia case L'\\': comp[1] = 0x313; comp[2] = 0x300; break; // psili varia case L'/': comp[1] = 0x313; comp[2] = 0x301; break; // psili oxia case L'=': comp[1] = 0x313; comp[2] = 0x342; break; // psili perispomeni case L'|': comp[1] = 0x314; comp[2] = 0x300; break; // dasia varia case L'?': comp[1] = 0x314; comp[2] = 0x301; break; // dasia oxia case L'+': comp[1] = 0x314; comp[2] = 0x342; break; // dasia perispomeni // AltGr dead chars don't work. Maybe kbd isn't reset properly? // case 0x1fc1: comp[1] = 0x308; comp[2] = 0x342; break; // dialytika perispomeni // case 0x1fde: comp[1] = 0x314; comp[2] = 0x301; comp[3] = 0x345; break; // dasia oxia ypogegrammeni default: comp[1] = static_cast(m_deadKeyWParam); break; } } else if (HIWORD(m_kbLayout) == 0xF0B1 && LOBYTE(m_idLang) == LANG_LATVIAN) { // The Latvian (Standard) keyboard, available in Win 8.1 and later. switch (m_deadKeyWParam) { case L'\'': case L'"': // Note: " is Shift-' and automatically capitalizes the typed // character in native Win 8.1 apps. We don't do this, so the user // needs to keep the Shift key down. This is probably the common use // case anyway. switch (wParam) { case L'A': case L'a': case L'E': case L'e': case L'I': case L'i': case L'U': case L'u': comp[1] = 0x304; break; // macron case L'C': case L'c': case L'S': case L's': case L'Z': case L'z': comp[1] = 0x30c; break; // caron case L'G': case L'g': case L'K': case L'k': case L'L': case L'l': case L'N': case L'n': comp[1] = 0x327; break; // cedilla default: comp[1] = static_cast(m_deadKeyWParam); break; } break; default: comp[1] = static_cast(m_deadKeyWParam); break; } } else { switch (m_deadKeyWParam) { case L'`': comp[1] = 0x300; break; case L'\'': comp[1] = 0x301; break; case 0x00B4: comp[1] = 0x301; break; case L'^': comp[1] = 0x302; break; case L'~': comp[1] = 0x303; break; case 0x02DC: comp[1] = 0x303; break; case 0x00AF: comp[1] = 0x304; break; case 0x02D8: comp[1] = 0x306; break; case 0x02D9: comp[1] = 0x307; break; case L'"': comp[1] = 0x308; break; case 0x00A8: comp[1] = 0x308; break; case 0x00B0: comp[1] = 0x30A; break; case 0x02DA: comp[1] = 0x30A; break; case 0x02DD: comp[1] = 0x30B; break; case 0x02C7: comp[1] = 0x30C; break; case L',': comp[1] = 0x327; break; case 0x00B8: comp[1] = 0x327; break; case 0x02DB: comp[1] = 0x328; break; default: comp[1] = static_cast(m_deadKeyWParam); break; } } int compSize = 3; for (int i = 1; i < COMP_SIZE; i++) { if (comp[i] == L'\0') { compSize = i + 1; break; } } wchar_t out[3]; int res = ::FoldString(MAP_PRECOMPOSED, (LPWSTR)comp, compSize, (LPWSTR)out, 3); if (res > 0) { wChar = (jchar)out[0]; if (res == 3) { // The character cannot be accented, so we send a TYPED event // for the dead key itself first. SendViewTypedEvent(1, (jchar)m_deadKeyWParam); } } else { // Folding failed. Use the untranslated original character then wChar = (jchar)wParam; } // Clear the dead key m_deadKeyWParam = 0; } SendViewTypedEvent(repCount, wChar); } BOOL ViewContainer::HandleViewMouseEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (!GetGlassView()) { return FALSE; } int type = 0; int button = com_sun_glass_events_MouseEvent_BUTTON_NONE; POINT pt; // client coords jdouble wheelRotation = 0.0; if (msg == WM_MOUSELEAVE) { type = com_sun_glass_events_MouseEvent_EXIT; // get the coords (the message does not contain them) lParam = ::GetMessagePos(); pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); // this is screen coords, convert to client ::ScreenToClient(hwnd, &pt); // Windows has finished tracking mouse pointer already m_bTrackingMouse = FALSE; m_lastMouseMovePosition = -1; } else { // for all other messages lParam contains cursor coords pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); switch (msg) { case WM_MOUSEMOVE: if (lParam == m_lastMouseMovePosition) { // Avoid sending synthetic MOVE/DRAG events if // the pointer hasn't moved actually. // Just consume the messages. return TRUE; } else { m_lastMouseMovePosition = lParam; } // See RT-11305 regarding the GetCapture() check if ((wParam & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON)) != 0 && ::GetCapture() == hwnd) { type = com_sun_glass_events_MouseEvent_DRAG; } else { type = com_sun_glass_events_MouseEvent_MOVE; } // Due to RT-11305 we should report the pressed button for both // MOVE and DRAG. This also enables one to filter out these // events in client code in case they're undesired. if (wParam & MK_RBUTTON) { button = com_sun_glass_events_MouseEvent_BUTTON_RIGHT; } else if (wParam & MK_LBUTTON) { button = com_sun_glass_events_MouseEvent_BUTTON_LEFT; } else if (wParam & MK_MBUTTON) { button = com_sun_glass_events_MouseEvent_BUTTON_OTHER; } break; case WM_LBUTTONDOWN: type = com_sun_glass_events_MouseEvent_DOWN; button = com_sun_glass_events_MouseEvent_BUTTON_LEFT; break; case WM_LBUTTONUP: type = com_sun_glass_events_MouseEvent_UP; button = com_sun_glass_events_MouseEvent_BUTTON_LEFT; break; case WM_RBUTTONDOWN: type = com_sun_glass_events_MouseEvent_DOWN; button = com_sun_glass_events_MouseEvent_BUTTON_RIGHT; break; case WM_RBUTTONUP: type = com_sun_glass_events_MouseEvent_UP; button = com_sun_glass_events_MouseEvent_BUTTON_RIGHT; break; case WM_MBUTTONDOWN: type = com_sun_glass_events_MouseEvent_DOWN; button = com_sun_glass_events_MouseEvent_BUTTON_OTHER; break; case WM_MBUTTONUP: type = com_sun_glass_events_MouseEvent_UP; button = com_sun_glass_events_MouseEvent_BUTTON_OTHER; break; case WM_MOUSEWHEEL: case WM_MOUSEHWHEEL: { // MS Windows always sends WHEEL events to the focused window. // Redirect the message to a Glass window under the mouse // cursor instead to match Mac behavior HWND hwndUnderCursor = ::ChildWindowFromPointEx( ::GetDesktopWindow(), pt, CWP_SKIPDISABLED | CWP_SKIPINVISIBLE); if (hwndUnderCursor && hwndUnderCursor != hwnd) { DWORD hWndUnderCursorProcess; ::GetWindowThreadProcessId(hwndUnderCursor, &hWndUnderCursorProcess); if (::GetCurrentProcessId() == hWndUnderCursorProcess) { return (BOOL)::SendMessage(hwndUnderCursor, msg, wParam, lParam); } } // if there's none, proceed as usual type = com_sun_glass_events_MouseEvent_WHEEL; wheelRotation = (jdouble)GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA; } break; } } switch (type) { case 0: // not handled return FALSE; case com_sun_glass_events_MouseEvent_DOWN: m_mouseButtonDownCounter++; if (::GetCapture() != hwnd) { ::SetCapture(hwnd); } break; case com_sun_glass_events_MouseEvent_UP: if (m_mouseButtonDownCounter) { m_mouseButtonDownCounter--; } //else { internal inconsistency; quite unimportant though } if (::GetCapture() == hwnd && !m_mouseButtonDownCounter) { ::ReleaseCapture(); } break; } // get screen coords POINT ptAbs = pt; if (type == com_sun_glass_events_MouseEvent_WHEEL) { ::ScreenToClient(hwnd, &pt); } else { ::ClientToScreen(hwnd, &ptAbs); } // unmirror the x coordinate LONG style = ::GetWindowLong(hwnd, GWL_EXSTYLE); if (style & WS_EX_LAYOUTRTL) { RECT rect = {0}; ::GetClientRect(hwnd, &rect); pt.x = max(0, rect.right - rect.left) - pt.x; } jint jModifiers = GetModifiers(); const jboolean isSynthesized = jboolean(IsTouchEvent()); JNIEnv *env = GetEnv(); if (!m_bTrackingMouse && type != com_sun_glass_events_MouseEvent_EXIT) { TRACKMOUSEEVENT trackData; trackData.cbSize = sizeof(trackData); trackData.dwFlags = TME_LEAVE; trackData.hwndTrack = hwnd; trackData.dwHoverTime = HOVER_DEFAULT; if (::TrackMouseEvent(&trackData)) { // Mouse tracking will be canceled automatically upon receiving WM_MOUSELEAVE m_bTrackingMouse = TRUE; } // Note that (ViewContainer*)this != (BaseWnd*)this. We could use // dynamic_case<>() instead, but it would fail later if 'this' is // already deleted. So we use FromHandle() which is safe. const BaseWnd *origWnd = BaseWnd::FromHandle(hwnd); env->CallVoidMethod(GetView(), javaIDs.View.notifyMouse, com_sun_glass_events_MouseEvent_ENTER, com_sun_glass_events_MouseEvent_BUTTON_NONE, pt.x, pt.y, ptAbs.x, ptAbs.y, jModifiers, JNI_FALSE, isSynthesized); CheckAndClearException(env); // At this point 'this' might have already been deleted if the app // closed the window while processing the ENTER event. Hence the check: if (!::IsWindow(hwnd) || BaseWnd::FromHandle(hwnd) != origWnd || !GetGlassView()) { return TRUE; } } switch (type) { case com_sun_glass_events_MouseEvent_DOWN: GlassDropSource::SetDragButton(button); break; case com_sun_glass_events_MouseEvent_UP: GlassDropSource::SetDragButton(0); break; } if (type == com_sun_glass_events_MouseEvent_WHEEL) { jdouble dx, dy; if (msg == WM_MOUSEHWHEEL) { // native horizontal scroll // Negate the value to be more "natural" dx = -wheelRotation; dy = 0.0; } else if (msg == WM_MOUSEWHEEL && LOWORD(wParam) & MK_SHIFT) { // Do not negate the emulated horizontal scroll amount dx = wheelRotation; dy = 0.0; } else { // vertical scroll dx = 0.0; dy = wheelRotation; } jint ls, cs; UINT val = 0; ::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &val, 0); ls = (jint)val; val = 0; ::SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &val, 0); cs = (jint)val; env->CallVoidMethod(GetView(), javaIDs.View.notifyScroll, pt.x, pt.y, ptAbs.x, ptAbs.y, dx, dy, jModifiers, ls, cs, 3, 3, (jdouble)40.0, (jdouble)40.0); } else { env->CallVoidMethod(GetView(), javaIDs.View.notifyMouse, type, button, pt.x, pt.y, ptAbs.x, ptAbs.y, jModifiers, type == com_sun_glass_events_MouseEvent_UP && button == com_sun_glass_events_MouseEvent_BUTTON_RIGHT, isSynthesized); } CheckAndClearException(env); return TRUE; } void ViewContainer::NotifyCaptureChanged(HWND hwnd, HWND to) { m_mouseButtonDownCounter = 0; } void ViewContainer::ResetMouseTracking(HWND hwnd) { if (!m_bTrackingMouse) { return; } // We don't expect WM_MOUSELEAVE anymore, so we cancel mouse tracking manually TRACKMOUSEEVENT trackData; trackData.cbSize = sizeof(trackData); trackData.dwFlags = TME_LEAVE | TME_CANCEL; trackData.hwndTrack = hwnd; trackData.dwHoverTime = HOVER_DEFAULT; ::TrackMouseEvent(&trackData); m_bTrackingMouse = FALSE; if (!GetGlassView()) { return; } POINT ptAbs; ::GetCursorPos(&ptAbs); POINT pt = ptAbs; ::ScreenToClient(hwnd, &pt); // unmirror the x coordinate LONG style = ::GetWindowLong(hwnd, GWL_EXSTYLE); if (style & WS_EX_LAYOUTRTL) { RECT rect = {0}; ::GetClientRect(hwnd, &rect); pt.x = max(0, rect.right - rect.left) - pt.x; } JNIEnv *env = GetEnv(); env->CallVoidMethod(GetView(), javaIDs.View.notifyMouse, com_sun_glass_events_MouseEvent_EXIT, 0, pt.x, pt.y, ptAbs.x, ptAbs.y, GetModifiers(), JNI_FALSE, JNI_FALSE); CheckAndClearException(env); } BOOL ViewContainer::HandleViewInputMethodEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { GlassView* gv = GetGlassView(); if (!gv) { return FALSE; } switch (msg) { case WM_IME_ENDCOMPOSITION: SendInputMethodEvent(NULL, 0, NULL, 0, NULL, NULL, 0, 0, 0); case WM_IME_STARTCOMPOSITION: return gv->IsInputMethodEventEnabled(); case WM_IME_COMPOSITION: if (gv->IsInputMethodEventEnabled()) { WmImeComposition(hwnd, wParam, lParam); return TRUE; } break; case WM_IME_NOTIFY: if (gv->IsInputMethodEventEnabled()) { WmImeNotify(hwnd, wParam, lParam); } break; default: return FALSE; } return FALSE; } void ViewContainer::WmImeComposition(HWND hwnd, WPARAM wParam, LPARAM lParam) { BOOL ret = FALSE; JNIEnv *env = GetEnv(); int* bndClauseW = NULL; int* bndAttrW = NULL; BYTE* valAttrW = NULL; int cClauseW = 0; GlassInputTextInfo textInfo = GlassInputTextInfo(this); HIMC hIMC = ImmGetContext(hwnd); ASSERT(hIMC!=0); try { textInfo.GetContextData(hIMC, lParam); jstring jtextString = textInfo.GetText(); if ((lParam & GCS_RESULTSTR && jtextString != NULL) || (lParam & GCS_COMPSTR)) { int cursorPosW = textInfo.GetCursorPosition(); int cAttrW = textInfo.GetAttributeInfo(bndAttrW, valAttrW); cClauseW = textInfo.GetClauseInfo(bndClauseW); SendInputMethodEvent(jtextString, cClauseW, bndClauseW, cAttrW, bndAttrW, valAttrW, textInfo.GetCommittedTextLength(), cursorPosW, cursorPosW); } ImmReleaseContext(hwnd, hIMC); } catch (...) { // since GetClauseInfo and GetAttributeInfo could throw exception, we have to release // the pointer here. delete [] bndClauseW; delete [] bndAttrW; delete [] valAttrW; ImmReleaseContext(hwnd, hIMC); throw; } /* Free the storage allocated. Since jtextString won't be passed from threads * to threads, we just use the local ref and it will be deleted within the destructor * of GlassInputTextInfo object. */ delete [] bndClauseW; delete [] bndAttrW; delete [] valAttrW; CheckAndClearException(env); } void ViewContainer::WmImeNotify(HWND hwnd, WPARAM wParam, LPARAM lParam) { if (wParam == IMN_OPENCANDIDATE || wParam == IMN_CHANGECANDIDATE) { JNIEnv *env = GetEnv(); POINT curPos; UINT bits = 1; HIMC hIMC = ImmGetContext(hwnd); CANDIDATEFORM cf; GetCandidatePos(&curPos); ::ScreenToClient(hwnd, &curPos); for (int iCandType=0; iCandType<32; iCandType++, bits<<=1) { if (lParam & bits) { cf.dwIndex = iCandType; cf.dwStyle = CFS_CANDIDATEPOS; // The constant offset is needed because Windows is moving the IM window cf.ptCurrentPos.x = curPos.x - 6; cf.ptCurrentPos.y = curPos.y - 15; ::ImmSetCandidateWindow(hIMC, &cf); } } ImmReleaseContext(hwnd, hIMC); } } // // generate and post InputMethodEvent // void ViewContainer::SendInputMethodEvent(jstring text, int cClause, int* rgClauseBoundary, int cAttrBlock, int* rgAttrBoundary, BYTE *rgAttrValue, int commitedTextLength, int caretPos, int visiblePos) { JNIEnv *env = GetEnv(); // assumption for array type casting ASSERT(sizeof(int)==sizeof(jint)); ASSERT(sizeof(BYTE)==sizeof(jbyte)); // caluse information jintArray clauseBoundary = NULL; if (cClause && rgClauseBoundary) { // convert clause boundary offset array to java array clauseBoundary = env->NewIntArray(cClause+1); if (clauseBoundary) { env->SetIntArrayRegion(clauseBoundary, 0, cClause+1, (jint *)rgClauseBoundary); CheckAndClearException(env); } } // attribute information jintArray attrBoundary = NULL; jbyteArray attrValue = NULL; if (cAttrBlock && rgAttrBoundary && rgAttrValue) { // convert attribute boundary offset array to java array attrBoundary = env->NewIntArray(cAttrBlock+1); if (attrBoundary) { env->SetIntArrayRegion(attrBoundary, 0, cAttrBlock+1, (jint *)rgAttrBoundary); CheckAndClearException(env); } // convert attribute value byte array to java array attrValue = env->NewByteArray(cAttrBlock); if (attrValue) { env->SetByteArrayRegion(attrValue, 0, cAttrBlock, (jbyte *)rgAttrValue); CheckAndClearException(env); } } env->CallBooleanMethod(GetView(), javaIDs.View.notifyInputMethod, text, clauseBoundary, attrBoundary, attrValue, commitedTextLength, caretPos, visiblePos); CheckAndClearException(env); if (clauseBoundary) { env->DeleteLocalRef(clauseBoundary); } if (attrBoundary) { env->DeleteLocalRef(attrBoundary); } if (attrValue) { env->DeleteLocalRef(attrValue); } } // Gets the candidate position void ViewContainer::GetCandidatePos(LPPOINT curPos) { JNIEnv *env = GetEnv(); double* nativePos; jdoubleArray pos = (jdoubleArray)env->CallObjectMethod(GetView(), javaIDs.View.notifyInputMethodCandidatePosRequest, 0); nativePos = env->GetDoubleArrayElements(pos, NULL); if (nativePos) { curPos->x = (int)nativePos[0]; curPos->y = (int)nativePos[1]; env->ReleaseDoubleArrayElements(pos, nativePos, 0); } } namespace { class AutoTouchInputHandle { HTOUCHINPUT m_h; private: AutoTouchInputHandle(const AutoTouchInputHandle&); AutoTouchInputHandle& operator=(const AutoTouchInputHandle&); public: explicit AutoTouchInputHandle(LPARAM lParam): m_h((HTOUCHINPUT)lParam) { } ~AutoTouchInputHandle() { if (m_h) { ::CloseTouchInputHandle(m_h); } } operator HTOUCHINPUT() const { return m_h; } }; static BOOL debugTouch = false; static char * touchEventName(unsigned int dwFlags) { if (dwFlags & TOUCHEVENTF_MOVE) { return "MOVE"; } if (dwFlags & TOUCHEVENTF_DOWN) { return "PRESS"; } if (dwFlags & TOUCHEVENTF_UP) { return "RELEASE"; } return "UNKOWN"; } void NotifyTouchInput( HWND hWnd, jobject view, jclass gestureSupportCls, const TOUCHINPUT* ti, unsigned count) { JNIEnv *env = GetEnv(); // TBD: set to 'true' if source device is a touch screen // and to 'false' if source device is a touch pad. // So far assume source device on Windows is always a touch screen. const bool isDirect = true; jint modifiers = GetModifiers(); env->CallStaticObjectMethod(gestureSupportCls, javaIDs.Gestures.notifyBeginTouchEventMID, view, modifiers, jboolean(isDirect), jint(count)); CheckAndClearException(env); for (; count; --count, ++ti) { jlong touchID = jlong(ti->dwID); jint eventID = 0; if (ti->dwFlags & TOUCHEVENTF_MOVE) { eventID = com_sun_glass_events_TouchEvent_TOUCH_MOVED; } if (ti->dwFlags & TOUCHEVENTF_DOWN) { eventID = com_sun_glass_events_TouchEvent_TOUCH_PRESSED; } if (ti->dwFlags & TOUCHEVENTF_UP) { eventID = com_sun_glass_events_TouchEvent_TOUCH_RELEASED; } POINT screen; POINT client; client.x = screen.x = LONG(ti->x / 100); client.y = screen.y = LONG(ti->y / 100); ScreenToClient(hWnd, &client); // unmirror the x coordinate LONG style = ::GetWindowLong(hWnd, GWL_EXSTYLE); if (style & WS_EX_LAYOUTRTL) { RECT rect = {0}; ::GetClientRect(hWnd, &rect); client.x = max(0, rect.right - rect.left) - client.x; } env->CallStaticObjectMethod(gestureSupportCls, javaIDs.Gestures.notifyNextTouchEventMID, view, eventID, touchID, jint(client.x), jint(client.y), jint(screen.x), jint(screen.y)); CheckAndClearException(env); } env->CallStaticObjectMethod( gestureSupportCls, javaIDs.Gestures.notifyEndTouchEventMID, view); CheckAndClearException(env); } void NotifyManipulationProcessor( IManipulationProcessor& manipProc, const TOUCHINPUT* ti, unsigned count) { for (; count; --count, ++ti) { if (ti->dwFlags & TOUCHEVENTF_DOWN) { manipProc.ProcessDownWithTime(ti->dwID, FLOAT(ti->x), FLOAT(ti->y), ti->dwTime); } if (ti->dwFlags & TOUCHEVENTF_MOVE) { manipProc.ProcessMoveWithTime(ti->dwID, FLOAT(ti->x), FLOAT(ti->y), ti->dwTime); } if (ti->dwFlags & TOUCHEVENTF_UP) { manipProc.ProcessUpWithTime(ti->dwID, FLOAT(ti->x), FLOAT(ti->y), ti->dwTime); } } } } // namespace unsigned int ViewContainer::HandleViewTouchEvent( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { const UINT newCount = static_cast(LOWORD(wParam)); TOUCHINPUT * tempTouchInputBuf; unsigned int bufsz = newCount > 10 ? newCount : 10; if (m_thisTouchInputBuf.size() < bufsz) { m_thisTouchInputBuf.resize(bufsz); } if (newCount > 0) { tempTouchInputBuf = new TOUCHINPUT[newCount]; do { AutoTouchInputHandle inputInfo(lParam); if (!::GetTouchInputInfo(inputInfo, newCount, tempTouchInputBuf, sizeof(TOUCHINPUT))) { return 0; } } while(0); // scope for 'inputInfo' } // Fix up the touch point stream. Some drivers seem to lose touch events, // dropping PRESS, MOVE, UP, so we need to add them back in. unsigned int activeCount = 0; unsigned int pointsCount = 0; // check first for any "lost" touches // these need to get added to the send list of points for (unsigned int i = 0 ; i < m_lastTouchInputCount; i++) { if (!(m_lastTouchInputBuf[i].dwFlags & TOUCHEVENTF_UP)) { // looking for a dwID that is // not present in the new batch // was not UP in the old batch bool found = false; for (unsigned int j = 0; j < newCount; j++) { if (m_lastTouchInputBuf[i].dwID == tempTouchInputBuf[j].dwID) { found = true; //break; } } if (!found) { // We have a old event but not a new one, so release it m_thisTouchInputBuf[pointsCount].dwFlags = TOUCHEVENTF_UP; m_thisTouchInputBuf[pointsCount].dwID = m_lastTouchInputBuf[i].dwID; m_thisTouchInputBuf[pointsCount].x = m_lastTouchInputBuf[i].x; m_thisTouchInputBuf[pointsCount].y = m_lastTouchInputBuf[i].y; if (newCount > 0) { //use the time of the first new element for our inserted event m_thisTouchInputBuf[pointsCount].dwTime = tempTouchInputBuf[0].dwTime; } else { m_thisTouchInputBuf[pointsCount].dwTime = m_lastTouchInputBuf[i].dwTime; } m_thisTouchInputBuf[pointsCount].dwMask = m_lastTouchInputBuf[i].dwMask; if (debugTouch) { printf("TOUCH FIX UP %d, %s\n", m_lastTouchInputBuf[i].dwID, touchEventName(m_lastTouchInputBuf[i].dwFlags)); } pointsCount++; } } } if (pointsCount + newCount > m_thisTouchInputBuf.size()) { bufsz = pointsCount + newCount; m_thisTouchInputBuf.resize(bufsz); } // now fold in the current touch points for (unsigned int i = 0 ; i < newCount; i++) { bool found = false; for (unsigned int j = 0 ; j < m_lastTouchInputCount; j++) { if (m_lastTouchInputBuf[j].dwID == tempTouchInputBuf[i].dwID) { found = true; break; } } m_thisTouchInputBuf[pointsCount].dwFlags = tempTouchInputBuf[i].dwFlags; m_thisTouchInputBuf[pointsCount].dwID = tempTouchInputBuf[i].dwID; m_thisTouchInputBuf[pointsCount].dwTime = tempTouchInputBuf[i].dwTime; m_thisTouchInputBuf[pointsCount].dwMask = tempTouchInputBuf[i].dwMask; m_thisTouchInputBuf[pointsCount].x = tempTouchInputBuf[i].x; m_thisTouchInputBuf[pointsCount].y = tempTouchInputBuf[i].y; if (m_thisTouchInputBuf[pointsCount].dwFlags & TOUCHEVENTF_DOWN) { pointsCount++; activeCount ++; } else if (m_thisTouchInputBuf[pointsCount].dwFlags & TOUCHEVENTF_MOVE) { if (!found) { if (debugTouch) { printf("TOUCH FIX MV->DOWN %d, %s\n", m_thisTouchInputBuf[pointsCount].dwID, touchEventName(m_thisTouchInputBuf[pointsCount].dwFlags)); } m_thisTouchInputBuf[pointsCount].dwFlags = TOUCHEVENTF_DOWN; } pointsCount++; activeCount ++; } else if (m_thisTouchInputBuf[pointsCount].dwFlags & TOUCHEVENTF_UP) { if (found) { pointsCount++; } else { // UP without a previous DOWN, ignore it } } } if (debugTouch) { printf("Touch Sequence %d/%d win=%d view=%d %d,%d,%d\n",pointsCount,activeCount, hWnd, GetView(), m_lastTouchInputCount, newCount, pointsCount); for (unsigned int i = 0 ; i < m_lastTouchInputCount; i++) { printf(" old %d, %s\n", m_lastTouchInputBuf[i].dwID, touchEventName(m_lastTouchInputBuf[i].dwFlags)); } for (unsigned int i = 0 ; i < newCount; i++) { printf(" in %d, %s\n", tempTouchInputBuf[i].dwID, touchEventName(tempTouchInputBuf[i].dwFlags)); } for (unsigned int i = 0 ; i < pointsCount; i++) { printf(" this %d, %d\n", m_thisTouchInputBuf[i].dwID, m_thisTouchInputBuf[i].dwFlags & 0x07); } printf(" ---\n"); fflush(stdout); } if (pointsCount > 0) { NotifyTouchInput(hWnd, GetView(), m_gestureSupportCls, &m_thisTouchInputBuf[0], pointsCount); if (m_manipProc) { NotifyManipulationProcessor(*m_manipProc, &m_thisTouchInputBuf[0], pointsCount); } std::swap(m_lastTouchInputBuf, m_thisTouchInputBuf); m_lastTouchInputCount = pointsCount; } if ( newCount > 0) { delete [] tempTouchInputBuf; } return activeCount; } void ViewContainer::HandleViewTimerEvent(HWND hwnd, UINT_PTR timerID) { if (IDT_GLASS_INERTIAPROCESSOR == timerID) { BOOL completed = FALSE; HRESULT hr = m_inertiaProc->Process(&completed); if (SUCCEEDED(hr) && completed) { StopTouchInputInertia(hwnd); JNIEnv *env = GetEnv(); env->CallStaticVoidMethod(m_gestureSupportCls, javaIDs.Gestures.inertiaGestureFinishedMID, GetView()); CheckAndClearException(env); } } } void ViewContainer::NotifyGesturePerformed(HWND hWnd, bool isDirect, bool isInertia, FLOAT x, FLOAT y, FLOAT deltaX, FLOAT deltaY, FLOAT scaleDelta, FLOAT expansionDelta, FLOAT rotationDelta, FLOAT cumulativeDeltaX, FLOAT cumulativeDeltaY, FLOAT cumulativeScale, FLOAT cumulativeExpansion, FLOAT cumulativeRotation) { JNIEnv *env = GetEnv(); POINT screen; screen.x = LONG((x + 0.5) / 100); screen.y = LONG((y + 0.5) / 100); POINT client; client.x = screen.x; client.y = screen.y; ScreenToClient(hWnd, &client); // unmirror the x coordinate LONG style = ::GetWindowLong(hWnd, GWL_EXSTYLE); if (style & WS_EX_LAYOUTRTL) { RECT rect = {0}; ::GetClientRect(hWnd, &rect); client.x = max(0, rect.right - rect.left) - client.x; } jint modifiers = GetModifiers(); env->CallStaticVoidMethod(m_gestureSupportCls, javaIDs.Gestures.gesturePerformedMID, GetView(), modifiers, jboolean(isDirect), jboolean(isInertia), jint(client.x), jint(client.y), jint(screen.x), jint(screen.y), deltaX / 100, deltaY / 100, cumulativeDeltaX / 100, cumulativeDeltaY / 100, cumulativeScale, cumulativeExpansion / 100, cumulativeRotation); CheckAndClearException(env); } void ViewContainer::StartTouchInputInertia(HWND hwnd) { // TBD: check errors // // Collect initial inertia data // FLOAT vX, vY; m_manipProc->GetVelocityX(&vX); m_manipProc->GetVelocityY(&vY); const FLOAT VELOCITY_THRESHOLD = 10.0f; if (fabs(vX) < VELOCITY_THRESHOLD && fabs(vY) < VELOCITY_THRESHOLD) { return; } // TBD: check errors POINT origin; GetCursorPos(&origin); // // Setup inertia. // m_inertiaProc->Reset(); m_inertiaProc->put_DesiredDeceleration(0.23f); // Set initial origins. m_inertiaProc->put_InitialOriginX(origin.x * 100.0f); m_inertiaProc->put_InitialOriginY(origin.y * 100.0f); // Set initial velocities. m_inertiaProc->put_InitialVelocityX(vX); m_inertiaProc->put_InitialVelocityY(vY); // TBD: check errors ::SetTimer(hwnd, IDT_GLASS_INERTIAPROCESSOR, 16, NULL); } void ViewContainer::StopTouchInputInertia(HWND hwnd) { // TBD: check errors ::KillTimer(hwnd, IDT_GLASS_INERTIAPROCESSOR); } extern "C" { JNIEXPORT void JNICALL Java_com_sun_glass_ui_win_WinGestureSupport__1initIDs( JNIEnv *env, jclass cls) { javaIDs.Gestures.gesturePerformedMID = env->GetStaticMethodID(cls, "gesturePerformed", "(Lcom/sun/glass/ui/View;IZZIIIIFFFFFFF)V"); CheckAndClearException(env); javaIDs.Gestures.inertiaGestureFinishedMID = env->GetStaticMethodID(cls, "inertiaGestureFinished", "(Lcom/sun/glass/ui/View;)V"); CheckAndClearException(env); javaIDs.Gestures.notifyBeginTouchEventMID = env->GetStaticMethodID(cls, "notifyBeginTouchEvent", "(Lcom/sun/glass/ui/View;IZI)V"); CheckAndClearException(env); javaIDs.Gestures.notifyNextTouchEventMID = env->GetStaticMethodID(cls, "notifyNextTouchEvent", "(Lcom/sun/glass/ui/View;IJIIII)V"); CheckAndClearException(env); javaIDs.Gestures.notifyEndTouchEventMID = env->GetStaticMethodID(cls, "notifyEndTouchEvent", "(Lcom/sun/glass/ui/View;)V"); CheckAndClearException(env); } } // extern "C"