mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-06-05 21:39:12 +02:00
Standardize IME callbacks for off-screen rendering (issue #1675)
This commit is contained in:
@@ -1204,6 +1204,13 @@ void BrowserWindowOsrGtk::UpdateDragCursor(
|
||||
CEF_REQUIRE_UI_THREAD();
|
||||
}
|
||||
|
||||
void BrowserWindowOsrGtk::OnImeCompositionRangeChanged(
|
||||
CefRefPtr<CefBrowser> browser,
|
||||
const CefRange& selection_range,
|
||||
const CefRenderHandler::RectList& character_bounds) {
|
||||
CEF_REQUIRE_UI_THREAD();
|
||||
}
|
||||
|
||||
void BrowserWindowOsrGtk::Create(ClientWindowHandle parent_handle) {
|
||||
REQUIRE_MAIN_THREAD();
|
||||
DCHECK(!glarea_);
|
||||
|
@@ -76,6 +76,10 @@ class BrowserWindowOsrGtk : public BrowserWindow,
|
||||
int x, int y) OVERRIDE;
|
||||
void UpdateDragCursor(CefRefPtr<CefBrowser> browser,
|
||||
CefRenderHandler::DragOperation operation) OVERRIDE;
|
||||
void OnImeCompositionRangeChanged(
|
||||
CefRefPtr<CefBrowser> browser,
|
||||
const CefRange& selection_range,
|
||||
const CefRenderHandler::RectList& character_bounds) OVERRIDE;
|
||||
|
||||
private:
|
||||
// Create the GTK GlArea.
|
||||
|
@@ -9,6 +9,7 @@
|
||||
#include "cefclient/browser/browser_window.h"
|
||||
#include "cefclient/browser/client_handler_osr.h"
|
||||
#include "cefclient/browser/osr_renderer.h"
|
||||
#include "cefclient/browser/text_input_client_osr_mac.h"
|
||||
|
||||
namespace client {
|
||||
|
||||
@@ -77,6 +78,10 @@ class BrowserWindowOsrMac : public BrowserWindow,
|
||||
int x, int y) OVERRIDE;
|
||||
void UpdateDragCursor(CefRefPtr<CefBrowser> browser,
|
||||
CefRenderHandler::DragOperation operation) OVERRIDE;
|
||||
void OnImeCompositionRangeChanged(
|
||||
CefRefPtr<CefBrowser> browser,
|
||||
const CefRange& selection_range,
|
||||
const CefRenderHandler::RectList& character_bounds) OVERRIDE;
|
||||
|
||||
private:
|
||||
// Create the NSView.
|
||||
|
@@ -14,6 +14,18 @@
|
||||
#include "cefclient/browser/bytes_write_handler.h"
|
||||
#include "cefclient/browser/geometry_util.h"
|
||||
#include "cefclient/browser/main_message_loop.h"
|
||||
#include "cefclient/browser/text_input_client_osr_mac.h"
|
||||
|
||||
namespace {
|
||||
|
||||
CefTextInputClientOSRMac* GetInputClientFromContext(
|
||||
const NSTextInputContext* context) {
|
||||
if (!context)
|
||||
return NULL;
|
||||
return reinterpret_cast<CefTextInputClientOSRMac*>([context client]);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@interface BrowserOpenGLView
|
||||
: NSOpenGLView <NSDraggingSource, NSDraggingDestination> {
|
||||
@@ -29,13 +41,16 @@
|
||||
|
||||
float device_scale_factor_;
|
||||
|
||||
// Drag and drop
|
||||
// Drag and drop.
|
||||
CefRefPtr<CefDragData> current_drag_data_;
|
||||
NSDragOperation current_drag_op_;
|
||||
NSDragOperation current_allowed_ops_;
|
||||
NSPasteboard* pasteboard_;
|
||||
CFStringRef fileUTI_;
|
||||
|
||||
// For intreacting with IME.
|
||||
NSTextInputContext* text_input_context_osr_mac_;
|
||||
|
||||
// Event monitor for scroll wheel end event.
|
||||
id endWheelMonitor_;
|
||||
}
|
||||
@@ -81,7 +96,8 @@
|
||||
- (NSPoint)convertPointToBackingInternal:(NSPoint)aPoint;
|
||||
- (NSRect)convertRectFromBackingInternal:(NSRect)aRect;
|
||||
- (NSRect)convertRectToBackingInternal:(NSRect)aRect;
|
||||
|
||||
- (void)ChangeCompositionRange:(CefRange)range
|
||||
character_bounds:(const CefRenderHandler::RectList&) character_bounds;
|
||||
@end
|
||||
|
||||
|
||||
@@ -315,18 +331,25 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) {
|
||||
|
||||
- (void)keyDown:(NSEvent*)event {
|
||||
CefRefPtr<CefBrowser> browser = [self getBrowser];
|
||||
if (!browser.get())
|
||||
if (!browser.get() || !text_input_context_osr_mac_)
|
||||
return;
|
||||
|
||||
if ([event type] != NSFlagsChanged) {
|
||||
browser->GetHost()->HandleKeyEventBeforeTextInputClient(event);
|
||||
CefTextInputClientOSRMac* client = GetInputClientFromContext(
|
||||
text_input_context_osr_mac_);
|
||||
|
||||
// The return value of this method seems to always be set to YES,
|
||||
// thus we ignore it and ask the host view whether IME is active
|
||||
// or not.
|
||||
[[self inputContext] handleEvent:event];
|
||||
if (client) {
|
||||
[client HandleKeyEventBeforeTextInputClient:event];
|
||||
|
||||
browser->GetHost()->HandleKeyEventAfterTextInputClient(event);
|
||||
// The return value of this method seems to always be set to YES, thus we
|
||||
// ignore it and ask the host view whether IME is active or not.
|
||||
[text_input_context_osr_mac_ handleEvent:event];
|
||||
|
||||
CefKeyEvent keyEvent;
|
||||
[self getKeyEvent:keyEvent forEvent:event];
|
||||
|
||||
[client HandleKeyEventAfterTextInputClient:keyEvent];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,10 +525,15 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) {
|
||||
}
|
||||
|
||||
- (NSTextInputContext*)inputContext {
|
||||
CefRefPtr<CefBrowser> browser = [self getBrowser];
|
||||
if (browser.get())
|
||||
return browser->GetHost()->GetNSTextInputContext();
|
||||
return NULL;
|
||||
if (!text_input_context_osr_mac_) {
|
||||
CefTextInputClientOSRMac* text_input_client =
|
||||
[[CefTextInputClientOSRMac alloc] initWithBrowser:[self getBrowser]];
|
||||
|
||||
text_input_context_osr_mac_ = [[NSTextInputContext alloc] initWithClient:
|
||||
text_input_client];
|
||||
}
|
||||
|
||||
return text_input_context_osr_mac_;
|
||||
}
|
||||
|
||||
- (void)getMouseEvent:(CefMouseEvent&)mouseEvent forEvent:(NSEvent*)event {
|
||||
@@ -1098,6 +1126,13 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) {
|
||||
return [self convertRectToBacking:aRect];
|
||||
}
|
||||
|
||||
- (void)ChangeCompositionRange:(CefRange)range
|
||||
character_bounds:(const CefRenderHandler::RectList&) bounds {
|
||||
CefTextInputClientOSRMac* client =
|
||||
GetInputClientFromContext(text_input_context_osr_mac_);
|
||||
if (client)
|
||||
[client ChangeCompositionRange: range character_bounds:bounds];
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@@ -1437,6 +1472,18 @@ void BrowserWindowOsrMac::UpdateDragCursor(
|
||||
[GLView(nsview_) setCurrentDragOp:operation];
|
||||
}
|
||||
|
||||
void BrowserWindowOsrMac::OnImeCompositionRangeChanged(
|
||||
CefRefPtr<CefBrowser> browser,
|
||||
const CefRange& selection_range,
|
||||
const CefRenderHandler::RectList& bounds) {
|
||||
CEF_REQUIRE_UI_THREAD();
|
||||
|
||||
if (nsview_) {
|
||||
[GLView(nsview_) ChangeCompositionRange:selection_range
|
||||
character_bounds:bounds];
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserWindowOsrMac::Create(ClientWindowHandle parent_handle,
|
||||
const CefRect& rect) {
|
||||
REQUIRE_MAIN_THREAD();
|
||||
|
@@ -135,4 +135,15 @@ void ClientHandlerOsr::UpdateDragCursor(CefRefPtr<CefBrowser> browser,
|
||||
osr_delegate_->UpdateDragCursor(browser, operation);
|
||||
}
|
||||
|
||||
void ClientHandlerOsr::OnImeCompositionRangeChanged(
|
||||
CefRefPtr<CefBrowser> browser,
|
||||
const CefRange& selection_range,
|
||||
const CefRenderHandler::RectList& character_bounds) {
|
||||
CEF_REQUIRE_UI_THREAD();
|
||||
if (!osr_delegate_)
|
||||
return;
|
||||
osr_delegate_->OnImeCompositionRangeChanged(browser, selection_range,
|
||||
character_bounds);
|
||||
}
|
||||
|
||||
} // namespace client
|
||||
|
@@ -56,6 +56,10 @@ class ClientHandlerOsr : public ClientHandler,
|
||||
virtual void UpdateDragCursor(
|
||||
CefRefPtr<CefBrowser> browser,
|
||||
CefRenderHandler::DragOperation operation) = 0;
|
||||
virtual void OnImeCompositionRangeChanged(
|
||||
CefRefPtr<CefBrowser> browser,
|
||||
const CefRange& selection_range,
|
||||
const CefRenderHandler::RectList& character_bounds) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~OsrDelegate() {}
|
||||
@@ -109,6 +113,10 @@ class ClientHandlerOsr : public ClientHandler,
|
||||
int x, int y) OVERRIDE;
|
||||
void UpdateDragCursor(CefRefPtr<CefBrowser> browser,
|
||||
CefRenderHandler::DragOperation operation) OVERRIDE;
|
||||
void OnImeCompositionRangeChanged(
|
||||
CefRefPtr<CefBrowser> browser,
|
||||
const CefRange& selection_range,
|
||||
const CefRenderHandler::RectList& character_bounds) OVERRIDE;
|
||||
|
||||
private:
|
||||
// Only accessed on the UI thread.
|
||||
|
390
tests/cefclient/browser/osr_ime_handler_win.cc
Normal file
390
tests/cefclient/browser/osr_ime_handler_win.cc
Normal file
@@ -0,0 +1,390 @@
|
||||
// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright
|
||||
// 2013 The Chromium Authors. All rights reserved. Use of this source code is
|
||||
// governed by a BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// Implementation based on ui/base/ime/win/imm32_manager.cc from Chromium.
|
||||
|
||||
#include <windowsx.h>
|
||||
#include <msctf.h>
|
||||
|
||||
#include "include/base/cef_build.h"
|
||||
#include "cefclient/browser/geometry_util.h"
|
||||
#include "cefclient/browser/main_message_loop.h"
|
||||
#include "cefclient/browser/resource.h"
|
||||
#include "cefclient/browser/util_win.h"
|
||||
#include "cefclient/browser/osr_ime_handler_win.h"
|
||||
|
||||
#define ColorUNDERLINE 0xFF000000 // Black SkColor value for underline,
|
||||
// same as Blink.
|
||||
#define ColorBKCOLOR 0x00000000 // White SkColor value for background,
|
||||
// same as Blink.
|
||||
|
||||
namespace client {
|
||||
|
||||
namespace {
|
||||
|
||||
// Determines whether or not the given attribute represents a selection
|
||||
bool IsSelectionAttribute(char attribute) {
|
||||
return (attribute == ATTR_TARGET_CONVERTED ||
|
||||
attribute == ATTR_TARGET_NOTCONVERTED);
|
||||
}
|
||||
|
||||
// Helper function for OsrImeHandlerWin::GetCompositionInfo() method,
|
||||
// to get the target range that's selected by the user in the current
|
||||
// composition string.
|
||||
void GetCompositionSelectionRange(HIMC imc, int* target_start,
|
||||
int* target_end) {
|
||||
int attribute_size = ::ImmGetCompositionString(imc, GCS_COMPATTR, NULL, 0);
|
||||
if (attribute_size > 0) {
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
std::vector<char> attribute_data(attribute_size);
|
||||
|
||||
::ImmGetCompositionString(imc, GCS_COMPATTR, &attribute_data[0],
|
||||
attribute_size);
|
||||
for (start = 0; start < attribute_size; ++start) {
|
||||
if (IsSelectionAttribute(attribute_data[start]))
|
||||
break;
|
||||
}
|
||||
for (end = start; end < attribute_size; ++end) {
|
||||
if (!IsSelectionAttribute(attribute_data[end]))
|
||||
break;
|
||||
}
|
||||
|
||||
*target_start = start;
|
||||
*target_end = end;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function for OsrImeHandlerWin::GetCompositionInfo() method, to get
|
||||
// underlines information of the current composition string.
|
||||
void GetCompositionUnderlines(
|
||||
HIMC imc,
|
||||
int target_start,
|
||||
int target_end,
|
||||
std::vector<CefCompositionUnderline> &underlines) {
|
||||
int clause_size = ::ImmGetCompositionString(imc, GCS_COMPCLAUSE, NULL, 0);
|
||||
int clause_length = clause_size / sizeof(uint32);
|
||||
if (clause_length) {
|
||||
std::vector<uint32> clause_data(clause_length);
|
||||
|
||||
::ImmGetCompositionString(imc, GCS_COMPCLAUSE,
|
||||
&clause_data[0], clause_size);
|
||||
for (int i = 0; i < clause_length - 1; ++i) {
|
||||
cef_composition_underline_t underline;
|
||||
underline.range.from = clause_data[i];
|
||||
underline.range.to = clause_data[i + 1];
|
||||
underline.color = ColorUNDERLINE;
|
||||
underline.background_color = ColorBKCOLOR;
|
||||
underline.thick = 0;
|
||||
|
||||
// Use thick underline for the target clause.
|
||||
if (underline.range.from >= target_start &&
|
||||
underline.range.to <= target_end) {
|
||||
underline.thick = 1;
|
||||
}
|
||||
underlines.push_back(underline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OsrImeHandlerWin::OsrImeHandlerWin(HWND hwnd)
|
||||
: ime_status_(false),
|
||||
hwnd_(hwnd),
|
||||
input_language_id_(LANG_USER_DEFAULT),
|
||||
is_composing_(false),
|
||||
cursor_index_(-1),
|
||||
system_caret_(false) {
|
||||
ime_rect_ = { -1, -1, 0, 0 };
|
||||
}
|
||||
|
||||
OsrImeHandlerWin::~OsrImeHandlerWin() {
|
||||
DestroyImeWindow();
|
||||
}
|
||||
|
||||
void OsrImeHandlerWin::SetInputLanguage() {
|
||||
// Retrieve the current input language from the system's keyboard layout.
|
||||
// Using GetKeyboardLayoutName instead of GetKeyboardLayout, because
|
||||
// the language from GetKeyboardLayout is the language under where the
|
||||
// keyboard layout is installed. And the language from GetKeyboardLayoutName
|
||||
// indicates the language of the keyboard layout itself.
|
||||
// See crbug.com/344834.
|
||||
WCHAR keyboard_layout[KL_NAMELENGTH];
|
||||
if (::GetKeyboardLayoutNameW(keyboard_layout)) {
|
||||
input_language_id_ =
|
||||
static_cast<LANGID>(_wtoi(&keyboard_layout[KL_NAMELENGTH >> 1]));
|
||||
} else {
|
||||
input_language_id_ = 0x0409; // Fallback to en-US.
|
||||
}
|
||||
}
|
||||
|
||||
void OsrImeHandlerWin::CreateImeWindow() {
|
||||
// Chinese/Japanese IMEs somehow ignore function calls to
|
||||
// ::ImmSetCandidateWindow(), and use the position of the current system
|
||||
// caret instead -::GetCaretPos().
|
||||
// Therefore, we create a temporary system caret for Chinese IMEs and use
|
||||
// it during this input context.
|
||||
// Since some third-party Japanese IME also uses ::GetCaretPos() to determine
|
||||
// their window position, we also create a caret for Japanese IMEs.
|
||||
if (PRIMARYLANGID(input_language_id_) == LANG_CHINESE ||
|
||||
PRIMARYLANGID(input_language_id_) == LANG_JAPANESE) {
|
||||
if (!system_caret_) {
|
||||
if (::CreateCaret(hwnd_, NULL, 1, 1))
|
||||
system_caret_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OsrImeHandlerWin::DestroyImeWindow() {
|
||||
// Destroy the system caret if we have created for this IME input context.
|
||||
if (system_caret_) {
|
||||
::DestroyCaret();
|
||||
system_caret_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void OsrImeHandlerWin::MoveImeWindow() {
|
||||
// Does nothing when the target window has no input focus.
|
||||
if (GetFocus() != hwnd_)
|
||||
return;
|
||||
|
||||
CefRect rc = ime_rect_;
|
||||
int location = cursor_index_;
|
||||
|
||||
// If location is not specified fall back to the composition range start.
|
||||
if (location == -1)
|
||||
location = composition_range_.from;
|
||||
|
||||
// Offset location by the composition range start if required.
|
||||
if (location >= composition_range_.from)
|
||||
location -= composition_range_.from;
|
||||
|
||||
if (location < static_cast<int>(composition_bounds_.size()))
|
||||
rc = composition_bounds_[location];
|
||||
else
|
||||
return;
|
||||
|
||||
HIMC imc = ::ImmGetContext(hwnd_);
|
||||
if (imc) {
|
||||
const int kCaretMargin = 1;
|
||||
if (PRIMARYLANGID(input_language_id_) == LANG_CHINESE) {
|
||||
// Chinese IMEs ignore function calls to ::ImmSetCandidateWindow()
|
||||
// when a user disables TSF (Text Service Framework) and CUAS (Cicero
|
||||
// Unaware Application Support).
|
||||
// On the other hand, when a user enables TSF and CUAS, Chinese IMEs
|
||||
// ignore the position of the current system caret and use the
|
||||
// parameters given to ::ImmSetCandidateWindow() with its 'dwStyle'
|
||||
// parameter CFS_CANDIDATEPOS.
|
||||
// Therefore, we do not only call ::ImmSetCandidateWindow() but also
|
||||
// set the positions of the temporary system caret if it exists.
|
||||
CANDIDATEFORM candidate_position = {
|
||||
0, CFS_CANDIDATEPOS, { rc.x, rc.y }, { 0, 0, 0, 0 }
|
||||
};
|
||||
::ImmSetCandidateWindow(imc, &candidate_position);
|
||||
}
|
||||
if (system_caret_) {
|
||||
switch (PRIMARYLANGID(input_language_id_)) {
|
||||
case LANG_JAPANESE:
|
||||
::SetCaretPos(rc.x, rc.y + rc.height);
|
||||
break;
|
||||
default:
|
||||
::SetCaretPos(rc.x, rc.y);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (PRIMARYLANGID(input_language_id_) == LANG_KOREAN) {
|
||||
// Korean IMEs require the lower-left corner of the caret to move their
|
||||
// candidate windows.
|
||||
rc.y += kCaretMargin;
|
||||
}
|
||||
|
||||
// Japanese IMEs and Korean IMEs also use the rectangle given to
|
||||
// ::ImmSetCandidateWindow() with its 'dwStyle' parameter CFS_EXCLUDE
|
||||
// Therefore, we also set this parameter here.
|
||||
CANDIDATEFORM exclude_rectangle = {
|
||||
0, CFS_EXCLUDE, { rc.x, rc.y },
|
||||
{ rc.x, rc.y, rc.x + rc.width, rc.y + rc.height }
|
||||
};
|
||||
::ImmSetCandidateWindow(imc, &exclude_rectangle);
|
||||
|
||||
::ImmReleaseContext(hwnd_, imc);
|
||||
}
|
||||
}
|
||||
|
||||
void OsrImeHandlerWin::CleanupComposition() {
|
||||
// Notify the IMM attached to the given window to complete the ongoing
|
||||
// composition (when given window is de-activated while composing and
|
||||
// re-activated) and reset the composition status.
|
||||
if (is_composing_) {
|
||||
HIMC imc = ::ImmGetContext(hwnd_);
|
||||
if (imc) {
|
||||
::ImmNotifyIME(imc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
|
||||
::ImmReleaseContext(hwnd_, imc);
|
||||
}
|
||||
ResetComposition();
|
||||
}
|
||||
}
|
||||
|
||||
void OsrImeHandlerWin::ResetComposition() {
|
||||
// Reset the composition status.
|
||||
is_composing_ = false;
|
||||
cursor_index_ = -1;
|
||||
}
|
||||
|
||||
|
||||
void OsrImeHandlerWin::GetCompositionInfo(
|
||||
HIMC imc,
|
||||
LPARAM lparam,
|
||||
CefString &composition_text,
|
||||
std::vector<CefCompositionUnderline> &underlines,
|
||||
int& composition_start) {
|
||||
// We only care about GCS_COMPATTR, GCS_COMPCLAUSE and GCS_CURSORPOS, and
|
||||
// convert them into underlines and selection range respectively.
|
||||
underlines.clear();
|
||||
|
||||
int length = static_cast<int>(composition_text.length());
|
||||
|
||||
// Find out the range selected by the user.
|
||||
int target_start = length;
|
||||
int target_end = length;
|
||||
if (lparam & GCS_COMPATTR)
|
||||
GetCompositionSelectionRange(imc, &target_start, &target_end);
|
||||
|
||||
// Retrieve the selection range information. If CS_NOMOVECARET is specified
|
||||
// it means the cursor should not be moved and we therefore place the caret at
|
||||
// the beginning of the composition string. Otherwise we should honour the
|
||||
// GCS_CURSORPOS value if it's available.
|
||||
// TODO(suzhe): Due to a bug in WebKit we currently can't use selection range
|
||||
// with composition string.
|
||||
// See: https://bugs.webkit.org/show_bug.cgi?id=40805
|
||||
if (!(lparam & CS_NOMOVECARET) && (lparam & GCS_CURSORPOS)) {
|
||||
// IMM32 does not support non-zero-width selection in a composition. So
|
||||
// always use the caret position as selection range.
|
||||
int cursor = ::ImmGetCompositionString(imc, GCS_CURSORPOS, NULL, 0);
|
||||
composition_start = cursor;
|
||||
} else {
|
||||
composition_start = 0;
|
||||
}
|
||||
|
||||
// Retrieve the clause segmentations and convert them to underlines.
|
||||
if (lparam & GCS_COMPCLAUSE)
|
||||
GetCompositionUnderlines(imc, target_start, target_end, underlines);
|
||||
|
||||
// Set default underlines in case there is no clause information.
|
||||
if (!underlines.size()) {
|
||||
CefCompositionUnderline underline;
|
||||
underline.color = ColorUNDERLINE;
|
||||
underline.background_color = ColorBKCOLOR;
|
||||
if (target_start > 0) {
|
||||
underline.range.from = 0;
|
||||
underline.range.to = target_start;
|
||||
underline.thick = 0;
|
||||
underlines.push_back(underline);
|
||||
}
|
||||
if (target_end > target_start) {
|
||||
underline.range.from = target_start;
|
||||
underline.range.to = target_end;
|
||||
underline.thick = 1;
|
||||
underlines.push_back(underline);
|
||||
}
|
||||
if (target_end < length) {
|
||||
underline.range.from = target_end;
|
||||
underline.range.to = length;
|
||||
underline.thick = 0;
|
||||
underlines.push_back(underline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool OsrImeHandlerWin::GetString(HIMC imc, WPARAM lparam, int type,
|
||||
CefString& result) {
|
||||
if (!(lparam & type))
|
||||
return false;
|
||||
LONG string_size = ::ImmGetCompositionString(imc, type, NULL, 0);
|
||||
if (string_size <= 0)
|
||||
return false;
|
||||
|
||||
// For trailing NULL - ImmGetCompositionString excludes that.
|
||||
string_size += sizeof(WCHAR);
|
||||
|
||||
std::vector<wchar_t> buffer(string_size);
|
||||
::ImmGetCompositionString(imc, type, &buffer[0], string_size);
|
||||
result.FromWString(&buffer[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OsrImeHandlerWin::GetResult(LPARAM lparam, CefString& result) {
|
||||
bool ret = false;
|
||||
HIMC imc = ::ImmGetContext(hwnd_);
|
||||
if (imc) {
|
||||
ret = GetString(imc, lparam, GCS_RESULTSTR, result);
|
||||
::ImmReleaseContext(hwnd_, imc);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool OsrImeHandlerWin::GetComposition(
|
||||
LPARAM lparam,
|
||||
CefString &composition_text,
|
||||
std::vector<CefCompositionUnderline> &underlines,
|
||||
int& composition_start) {
|
||||
bool ret = false;
|
||||
HIMC imc = ::ImmGetContext(hwnd_);
|
||||
if (imc) {
|
||||
// Copy the composition string to the CompositionText object.
|
||||
ret = GetString(imc, lparam, GCS_COMPSTR, composition_text);
|
||||
|
||||
if (ret) {
|
||||
// Retrieve the composition underlines and selection range information.
|
||||
GetCompositionInfo(imc, lparam, composition_text, underlines,
|
||||
composition_start);
|
||||
|
||||
// Mark that there is an ongoing composition.
|
||||
is_composing_ = true;
|
||||
}
|
||||
|
||||
::ImmReleaseContext(hwnd_, imc);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void OsrImeHandlerWin::DisableIME() {
|
||||
CleanupComposition();
|
||||
::ImmAssociateContextEx(hwnd_, NULL, 0);
|
||||
}
|
||||
|
||||
void OsrImeHandlerWin::CancelIME() {
|
||||
if (is_composing_) {
|
||||
HIMC imc = ::ImmGetContext(hwnd_);
|
||||
if (imc) {
|
||||
::ImmNotifyIME(imc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
|
||||
::ImmReleaseContext(hwnd_, imc);
|
||||
}
|
||||
ResetComposition();
|
||||
}
|
||||
}
|
||||
|
||||
void OsrImeHandlerWin::EnableIME() {
|
||||
// Load the default IME context.
|
||||
::ImmAssociateContextEx(hwnd_, NULL, IACE_DEFAULT);
|
||||
}
|
||||
|
||||
void OsrImeHandlerWin::UpdateCaretPosition(int index) {
|
||||
// Save the caret position.
|
||||
cursor_index_ = index;
|
||||
// Move the IME window.
|
||||
MoveImeWindow();
|
||||
}
|
||||
|
||||
void OsrImeHandlerWin::ChangeCompositionRange(
|
||||
const CefRange& selection_range,
|
||||
const std::vector<CefRect>& bounds) {
|
||||
composition_range_ = selection_range;
|
||||
composition_bounds_ = bounds;
|
||||
MoveImeWindow();
|
||||
}
|
||||
|
||||
} // namespace client
|
116
tests/cefclient/browser/osr_ime_handler_win.h
Normal file
116
tests/cefclient/browser/osr_ime_handler_win.h
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright
|
||||
// 2013 The Chromium Authors. All rights reserved. Use of this source code is
|
||||
// governed by a BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef CEF_TESTS_CEFCLIENT_BROWSER_OSR_IME_HANDLER_WIN_H_
|
||||
#define CEF_TESTS_CEFCLIENT_BROWSER_OSR_IME_HANDLER_WIN_H_
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <vector>
|
||||
|
||||
#include "include/internal/cef_types_wrappers.h"
|
||||
|
||||
namespace client {
|
||||
|
||||
// Handles IME for the native parent window that hosts an off-screen browser.
|
||||
// This object is only accessed on the CEF UI thread.
|
||||
class OsrImeHandlerWin {
|
||||
public:
|
||||
explicit OsrImeHandlerWin(HWND hwnd);
|
||||
virtual ~OsrImeHandlerWin();
|
||||
|
||||
// Retrieves whether or not there is an ongoing composition.
|
||||
bool is_composing() const { return is_composing_; }
|
||||
|
||||
// Retrieves the input language from Windows and update it.
|
||||
void SetInputLanguage();
|
||||
|
||||
// Creates the IME caret windows if required.
|
||||
void CreateImeWindow();
|
||||
|
||||
// Destroys the IME caret windows.
|
||||
void DestroyImeWindow();
|
||||
|
||||
// Cleans up the all resources attached to the given IMM32Manager object, and
|
||||
// reset its composition status.
|
||||
void CleanupComposition();
|
||||
|
||||
// Resets the composition status and cancels the ongoing composition.
|
||||
void ResetComposition();
|
||||
|
||||
// Retrieves a composition result of the ongoing composition if it exists.
|
||||
bool GetResult(LPARAM lparam, CefString& result);
|
||||
|
||||
// Retrieves the current composition status of the ongoing composition.
|
||||
// Includes composition text, underline information and selection range in the
|
||||
// composition text. IMM32 does not support char selection.
|
||||
bool GetComposition(LPARAM lparam, CefString &composition_text,
|
||||
std::vector<CefCompositionUnderline> &underlines,
|
||||
int& composition_start);
|
||||
|
||||
// Enables the IME attached to the given window.
|
||||
virtual void EnableIME();
|
||||
|
||||
// Disables the IME attached to the given window.
|
||||
virtual void DisableIME();
|
||||
|
||||
// Cancels an ongoing composition of the IME.
|
||||
virtual void CancelIME();
|
||||
|
||||
// Updates the IME caret position of the given window.
|
||||
void UpdateCaretPosition(int index);
|
||||
|
||||
// Updates the composition range. |selected_range| is the range of characters
|
||||
// that have been selected. |character_bounds| is the bounds of each character
|
||||
// in view device coordinates.
|
||||
void ChangeCompositionRange(const CefRange& selection_range,
|
||||
const std::vector<CefRect>& character_bounds);
|
||||
|
||||
// Updates the position of the IME windows.
|
||||
void MoveImeWindow();
|
||||
|
||||
private:
|
||||
// Retrieves the composition information.
|
||||
void GetCompositionInfo(HIMC imm_context, LPARAM lparam,
|
||||
CefString &composition_text,
|
||||
std::vector<CefCompositionUnderline>& underlines,
|
||||
int& composition_start);
|
||||
|
||||
// Retrieves a string from the IMM.
|
||||
bool GetString(HIMC imm_context, WPARAM lparam, int type, CefString& result);
|
||||
|
||||
// Represents whether or not there is an ongoing composition.
|
||||
bool is_composing_;
|
||||
|
||||
// The current composition character range and its bounds.
|
||||
std::vector<CefRect> composition_bounds_;
|
||||
|
||||
// This value represents whether or not the current input context has IMEs.
|
||||
bool ime_status_;
|
||||
|
||||
// The current input Language ID retrieved from Windows -
|
||||
// used for processing language-specific operations in IME.
|
||||
LANGID input_language_id_;
|
||||
|
||||
// Represents whether or not the current input context has created a system
|
||||
// caret to set the position of its IME candidate window.
|
||||
bool system_caret_;
|
||||
|
||||
// The rectangle of the input caret retrieved from a renderer process.
|
||||
CefRect ime_rect_;
|
||||
|
||||
// The current cursor index in composition string.
|
||||
int cursor_index_;
|
||||
|
||||
// The composition range in the string. This may be used to determine the
|
||||
// offset in composition bounds.
|
||||
CefRange composition_range_;
|
||||
|
||||
// Hwnd associated with this instance.
|
||||
HWND hwnd_;
|
||||
};
|
||||
|
||||
} // namespace client
|
||||
|
||||
#endif // CEF_TESTS_CEFCLIENT_BROWSER_OSR_IME_HANDLER_WIN_H_
|
@@ -11,6 +11,7 @@
|
||||
#include "cefclient/browser/main_message_loop.h"
|
||||
#include "cefclient/browser/resource.h"
|
||||
#include "cefclient/browser/util_win.h"
|
||||
#include "cefclient/browser/osr_ime_handler_win.h"
|
||||
|
||||
namespace client {
|
||||
|
||||
@@ -55,7 +56,6 @@ OsrWindowWin::OsrWindowWin(Delegate* delegate,
|
||||
hwnd_(NULL),
|
||||
hdc_(NULL),
|
||||
hrc_(NULL),
|
||||
client_rect_(),
|
||||
device_scale_factor_(client::GetDeviceScaleFactor()),
|
||||
painting_popup_(false),
|
||||
render_task_pending_(false),
|
||||
@@ -255,6 +255,8 @@ void OsrWindowWin::Create(HWND parent_hwnd, const RECT& rect) {
|
||||
DCHECK_EQ(register_res, S_OK);
|
||||
#endif
|
||||
|
||||
ime_handler_.reset(new OsrImeHandlerWin(hwnd_));
|
||||
|
||||
// Notify the window owner.
|
||||
NotifyNativeWindowCreated(hwnd_);
|
||||
}
|
||||
@@ -273,6 +275,7 @@ void OsrWindowWin::Destroy() {
|
||||
|
||||
// Destroy the native window.
|
||||
::DestroyWindow(hwnd_);
|
||||
ime_handler_.reset();
|
||||
hwnd_ = NULL;
|
||||
}
|
||||
|
||||
@@ -390,6 +393,74 @@ void OsrWindowWin::RegisterOsrClass(HINSTANCE hInstance,
|
||||
RegisterClassEx(&wcex);
|
||||
}
|
||||
|
||||
void OsrWindowWin::OnIMESetContext(UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
// We handle the IME Composition Window ourselves (but let the IME Candidates
|
||||
// Window be handled by IME through DefWindowProc()), so clear the
|
||||
// ISC_SHOWUICOMPOSITIONWINDOW flag:
|
||||
lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
|
||||
::DefWindowProc(hwnd_, message, wParam, lParam);
|
||||
|
||||
// Create Caret Window if required
|
||||
if (ime_handler_) {
|
||||
ime_handler_->CreateImeWindow();
|
||||
ime_handler_->MoveImeWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void OsrWindowWin::OnIMEStartComposition() {
|
||||
if (ime_handler_) {
|
||||
ime_handler_->CreateImeWindow();
|
||||
ime_handler_->MoveImeWindow();
|
||||
ime_handler_->ResetComposition();
|
||||
}
|
||||
}
|
||||
|
||||
void OsrWindowWin::OnIMEComposition(UINT message, WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
if (browser_ && ime_handler_) {
|
||||
CefString cTextStr;
|
||||
if (ime_handler_->GetResult(lParam, cTextStr)) {
|
||||
// Send the text to the browser. The |replacement_range| and
|
||||
// |relative_cursor_pos| params are not used on Windows, so provide
|
||||
// default invalid values.
|
||||
browser_->GetHost()->ImeCommitText(cTextStr,
|
||||
CefRange(UINT32_MAX, UINT32_MAX), 0);
|
||||
ime_handler_->ResetComposition();
|
||||
// Continue reading the composition string - Japanese IMEs send both
|
||||
// GCS_RESULTSTR and GCS_COMPSTR.
|
||||
}
|
||||
|
||||
std::vector<CefCompositionUnderline> underlines;
|
||||
int composition_start = 0;
|
||||
|
||||
if (ime_handler_->GetComposition(lParam, cTextStr, underlines,
|
||||
composition_start)) {
|
||||
// Send the composition string to the browser. The |replacement_range|
|
||||
// param is not used on Windows, so provide a default invalid value.
|
||||
browser_->GetHost()->ImeSetComposition(cTextStr, underlines,
|
||||
CefRange(UINT32_MAX, UINT32_MAX),
|
||||
CefRange(composition_start,
|
||||
static_cast<int>(composition_start + cTextStr.length())));
|
||||
|
||||
// Update the Candidate Window position. The cursor is at the end so
|
||||
// subtract 1. This is safe because IMM32 does not support non-zero-width
|
||||
// in a composition. Also, negative values are safely ignored in
|
||||
// MoveImeWindow
|
||||
ime_handler_->UpdateCaretPosition(composition_start - 1);
|
||||
} else {
|
||||
OnIMECancelCompositionEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OsrWindowWin::OnIMECancelCompositionEvent() {
|
||||
if (browser_ && ime_handler_) {
|
||||
browser_->GetHost()->ImeCancelComposition();
|
||||
ime_handler_->ResetComposition();
|
||||
ime_handler_->DestroyImeWindow();
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
LRESULT CALLBACK OsrWindowWin::OsrWndProc(HWND hWnd, UINT message,
|
||||
WPARAM wParam, LPARAM lParam) {
|
||||
@@ -399,7 +470,22 @@ LRESULT CALLBACK OsrWindowWin::OsrWndProc(HWND hWnd, UINT message,
|
||||
if (!self)
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
|
||||
// We want to handle IME events before the OS does any default handling.
|
||||
switch (message) {
|
||||
case WM_IME_SETCONTEXT:
|
||||
self->OnIMESetContext(message, wParam, lParam);
|
||||
return 0;
|
||||
case WM_IME_STARTCOMPOSITION:
|
||||
self->OnIMEStartComposition();
|
||||
return 0;
|
||||
case WM_IME_COMPOSITION:
|
||||
self->OnIMEComposition(message, wParam, lParam);
|
||||
return 0;
|
||||
case WM_IME_ENDCOMPOSITION:
|
||||
self->OnIMECancelCompositionEvent();
|
||||
// Let WTL call::DefWindowProc() and release its resources.
|
||||
break;
|
||||
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_MBUTTONDOWN:
|
||||
@@ -909,6 +995,24 @@ void OsrWindowWin::UpdateDragCursor(
|
||||
#endif
|
||||
}
|
||||
|
||||
void OsrWindowWin::OnImeCompositionRangeChanged(
|
||||
CefRefPtr<CefBrowser> browser,
|
||||
const CefRange& selection_range,
|
||||
const CefRenderHandler::RectList& character_bounds) {
|
||||
CEF_REQUIRE_UI_THREAD();
|
||||
|
||||
if (ime_handler_) {
|
||||
// Convert from view coordinates to device coordinates.
|
||||
CefRenderHandler::RectList device_bounds;
|
||||
CefRenderHandler::RectList::const_iterator it = character_bounds.begin();
|
||||
for (; it != character_bounds.end(); ++it) {
|
||||
device_bounds.push_back(LogicalToDevice(*it, device_scale_factor_));
|
||||
}
|
||||
|
||||
ime_handler_->ChangeCompositionRange(selection_range, device_bounds);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(CEF_USE_ATL)
|
||||
|
||||
CefBrowserHost::DragOperationsMask
|
||||
|
@@ -16,6 +16,8 @@
|
||||
|
||||
namespace client {
|
||||
|
||||
class OsrImeHandlerWin;
|
||||
|
||||
// Represents the native parent window for an off-screen browser. This object
|
||||
// must live on the CEF UI thread in order to handle CefRenderHandler callbacks.
|
||||
// The methods of this class are thread-safe unless otherwise indicated.
|
||||
@@ -94,6 +96,11 @@ class OsrWindowWin :
|
||||
void OnPaint();
|
||||
bool OnEraseBkgnd();
|
||||
|
||||
void OnIMESetContext(UINT message, WPARAM wParam, LPARAM lParam);
|
||||
void OnIMEStartComposition();
|
||||
void OnIMEComposition(UINT message, WPARAM wParam, LPARAM lParam);
|
||||
void OnIMECancelCompositionEvent();
|
||||
|
||||
// Manage popup bounds.
|
||||
bool IsOverPopupWidget(int x, int y) const;
|
||||
int GetPopupXOffset() const;
|
||||
@@ -133,6 +140,10 @@ class OsrWindowWin :
|
||||
int x, int y) OVERRIDE;
|
||||
void UpdateDragCursor(CefRefPtr<CefBrowser> browser,
|
||||
CefRenderHandler::DragOperation operation) OVERRIDE;
|
||||
void OnImeCompositionRangeChanged(
|
||||
CefRefPtr<CefBrowser> browser,
|
||||
const CefRange& selection_range,
|
||||
const CefRenderHandler::RectList& character_bounds) OVERRIDE;
|
||||
|
||||
#if defined(CEF_USE_ATL)
|
||||
// OsrDragEvents methods.
|
||||
@@ -156,6 +167,9 @@ class OsrWindowWin :
|
||||
HDC hdc_;
|
||||
HGLRC hrc_;
|
||||
|
||||
// Class that encapsulates IMM32 APIs and controls IMEs attached to a window.
|
||||
scoped_ptr<OsrImeHandlerWin> ime_handler_;
|
||||
|
||||
RECT client_rect_;
|
||||
float device_scale_factor_;
|
||||
|
||||
|
77
tests/cefclient/browser/text_input_client_osr_mac.h
Normal file
77
tests/cefclient/browser/text_input_client_osr_mac.h
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright
|
||||
// 2013 The Chromium Authors. All rights reserved. Use of this source code is
|
||||
// governed by a BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef CEF_TESTS_CEFCLIENT_BROWSER_OSR_TEXT_INPUT_CLIENT_OSR_MAC_H_
|
||||
#define CEF_TESTS_CEFCLIENT_BROWSER_OSR_TEXT_INPUT_CLIENT_OSR_MAC_H_
|
||||
#pragma once
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "include/cef_browser.h"
|
||||
#include "include/cef_render_handler.h"
|
||||
|
||||
// Implementation for the NSTextInputClient protocol used for enabling IME on
|
||||
// mac when window rendering is disabled.
|
||||
|
||||
@interface CefTextInputClientOSRMac : NSObject<NSTextInputClient> {
|
||||
@private
|
||||
|
||||
// The range of current marked text inside the whole content of the DOM node
|
||||
// being edited.
|
||||
NSRange markedRange_;
|
||||
|
||||
// The current composition character range and its bounds.
|
||||
CefRange composition_range_;
|
||||
std::vector<CefRect> composition_bounds_;
|
||||
|
||||
// Represents the input-method attributes supported by this object.
|
||||
NSArray* validAttributesForMarkedText_;
|
||||
|
||||
// Indicates if we are currently handling a key down event.
|
||||
BOOL handlingKeyDown_;
|
||||
|
||||
// Indicates if there is any marked text.
|
||||
BOOL hasMarkedText_;
|
||||
|
||||
// Indicates whether there was any marked text prior to handling
|
||||
// the current key event.
|
||||
BOOL oldHasMarkedText_;
|
||||
|
||||
// Indicates if unmarkText is called or not when handling a keyboard
|
||||
// event.
|
||||
BOOL unmarkTextCalled_;
|
||||
|
||||
// The selected range, cached from a message sent by the renderer.
|
||||
NSRange selectedRange_;
|
||||
|
||||
// Text to be inserted which was generated by handling a key down event.
|
||||
std::string textToBeInserted_;
|
||||
|
||||
// Marked text which was generated by handling a key down event.
|
||||
CefString markedText_;
|
||||
|
||||
// Underline information of the |markedText_|.
|
||||
std::vector<CefCompositionUnderline> underlines_;
|
||||
|
||||
// Replacement range information received from |setMarkedText:|.
|
||||
CefRange setMarkedTextReplacementRange_;
|
||||
|
||||
CefRefPtr<CefBrowser> browser_;
|
||||
}
|
||||
|
||||
@property(nonatomic, readonly) NSRange selectedRange;
|
||||
@property(nonatomic) BOOL handlingKeyDown;
|
||||
|
||||
- (id)initWithBrowser:(CefRefPtr<CefBrowser>)browser;
|
||||
- (void)HandleKeyEventBeforeTextInputClient:(NSEvent*)keyEvent;
|
||||
- (void)HandleKeyEventAfterTextInputClient:(CefKeyEvent)keyEvent;
|
||||
- (void)ChangeCompositionRange:(CefRange)range
|
||||
character_bounds:(const CefRenderHandler::RectList&)bounds;
|
||||
- (void)cancelComposition;
|
||||
|
||||
@end
|
||||
|
||||
#endif // CEF_TESTS_CEFCLIENT_BROWSER_OSR_TEXT_INPUT_CLIENT_OSR_MAC_H_
|
343
tests/cefclient/browser/text_input_client_osr_mac.mm
Normal file
343
tests/cefclient/browser/text_input_client_osr_mac.mm
Normal file
@@ -0,0 +1,343 @@
|
||||
// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright
|
||||
// 2013 The Chromium Authors. All rights reserved. Use of this source code is
|
||||
// governed by a BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// Implementation based on
|
||||
// content/browser/renderer_host/render_widget_host_view_mac.mm from Chromium.
|
||||
|
||||
#include "text_input_client_osr_mac.h"
|
||||
#include "include/cef_client.h"
|
||||
|
||||
#define ColorBLACK 0xFF000000 // Same as Blink SKColor.
|
||||
|
||||
namespace {
|
||||
|
||||
// TODO(suzhe): Upstream this function.
|
||||
cef_color_t CefColorFromNSColor(NSColor *color) {
|
||||
CGFloat r, g, b, a;
|
||||
[color getRed:&r green:&g blue:&b alpha:&a];
|
||||
|
||||
return
|
||||
std::max(0, std::min(static_cast<int>(lroundf(255.0f * a)), 255)) << 24 |
|
||||
std::max(0, std::min(static_cast<int>(lroundf(255.0f * r)), 255)) << 16 |
|
||||
std::max(0, std::min(static_cast<int>(lroundf(255.0f * g)), 255)) << 8 |
|
||||
std::max(0, std::min(static_cast<int>(lroundf(255.0f * b)), 255));
|
||||
}
|
||||
|
||||
// Extract underline information from an attributed string. Mostly copied from
|
||||
// third_party/WebKit/Source/WebKit/mac/WebView/WebHTMLView.mm
|
||||
void ExtractUnderlines(NSAttributedString* string,
|
||||
std::vector<CefCompositionUnderline>* underlines) {
|
||||
int length = [[string string] length];
|
||||
int i = 0;
|
||||
while (i < length) {
|
||||
NSRange range;
|
||||
NSDictionary* attrs = [string attributesAtIndex:i
|
||||
longestEffectiveRange:&range
|
||||
inRange:NSMakeRange(i, length - i)];
|
||||
NSNumber *style = [attrs objectForKey: NSUnderlineStyleAttributeName];
|
||||
if (style) {
|
||||
cef_color_t color = ColorBLACK;
|
||||
if (NSColor *colorAttr =
|
||||
[attrs objectForKey:NSUnderlineColorAttributeName]) {
|
||||
color = CefColorFromNSColor(
|
||||
[colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
|
||||
}
|
||||
cef_composition_underline_t line = {
|
||||
{range.location, NSMaxRange(range)}, color, 0, [style intValue] > 1
|
||||
};
|
||||
underlines->push_back(line);
|
||||
}
|
||||
i = range.location + range.length;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
extern NSString* NSTextInputReplacementRangeAttributeName;
|
||||
}
|
||||
|
||||
@implementation CefTextInputClientOSRMac
|
||||
|
||||
@synthesize selectedRange = selectedRange_;
|
||||
@synthesize handlingKeyDown = handlingKeyDown_;
|
||||
|
||||
- (id)initWithBrowser:(CefRefPtr<CefBrowser>)browser {
|
||||
self = [super init];
|
||||
browser_ = browser;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSArray*)validAttributesForMarkedText {
|
||||
if (!validAttributesForMarkedText_) {
|
||||
validAttributesForMarkedText_ = [[NSArray alloc] initWithObjects:
|
||||
NSUnderlineStyleAttributeName,
|
||||
NSUnderlineColorAttributeName,
|
||||
NSMarkedClauseSegmentAttributeName,
|
||||
NSTextInputReplacementRangeAttributeName,
|
||||
nil];
|
||||
}
|
||||
return validAttributesForMarkedText_;
|
||||
}
|
||||
|
||||
- (NSRange)markedRange {
|
||||
return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0);
|
||||
}
|
||||
|
||||
- (BOOL)hasMarkedText {
|
||||
return hasMarkedText_;
|
||||
}
|
||||
|
||||
- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange {
|
||||
BOOL isAttributedString = [aString isKindOfClass:[NSAttributedString class]];
|
||||
NSString* im_text = isAttributedString ? [aString string] : aString;
|
||||
if (handlingKeyDown_) {
|
||||
textToBeInserted_.append([im_text UTF8String]);
|
||||
} else {
|
||||
cef_range_t range = {
|
||||
replacementRange.location,
|
||||
NSMaxRange(replacementRange)
|
||||
};
|
||||
browser_->GetHost()->ImeCommitText([im_text UTF8String], range, 0);
|
||||
}
|
||||
|
||||
// Inserting text will delete all marked text automatically.
|
||||
hasMarkedText_ = NO;
|
||||
}
|
||||
|
||||
- (void)doCommandBySelector:(SEL)aSelector {
|
||||
// An input method calls this function to dispatch an editing command to be
|
||||
// handled by this view.
|
||||
}
|
||||
|
||||
- (void)setMarkedText:(id)aString
|
||||
selectedRange:(NSRange)newSelRange
|
||||
replacementRange:(NSRange)replacementRange {
|
||||
// An input method has updated the composition string. We send the given text
|
||||
// and range to the browser so it can update the composition node of Blink.
|
||||
|
||||
BOOL isAttributedString = [aString isKindOfClass:[NSAttributedString class]];
|
||||
NSString* im_text = isAttributedString ? [aString string] : aString;
|
||||
int length = [im_text length];
|
||||
|
||||
// |markedRange_| will get set in a callback from ImeSetComposition().
|
||||
selectedRange_ = newSelRange;
|
||||
markedText_ = [im_text UTF8String];
|
||||
hasMarkedText_ = (length > 0);
|
||||
underlines_.clear();
|
||||
|
||||
if (isAttributedString) {
|
||||
ExtractUnderlines(aString, &underlines_);
|
||||
} else {
|
||||
// Use a thin black underline by default.
|
||||
cef_composition_underline_t line = {
|
||||
{0, length}, ColorBLACK, 0, false
|
||||
};
|
||||
underlines_.push_back(line);
|
||||
}
|
||||
|
||||
// If we are handling a key down event then ImeSetComposition() will be
|
||||
// called from the keyEvent: method.
|
||||
// Input methods of Mac use setMarkedText calls with empty text to cancel an
|
||||
// ongoing composition. Our input method backend will automatically cancel an
|
||||
// ongoing composition when we send empty text.
|
||||
if (handlingKeyDown_) {
|
||||
setMarkedTextReplacementRange_ = {
|
||||
replacementRange.location,
|
||||
NSMaxRange(replacementRange)
|
||||
};
|
||||
} else if (!handlingKeyDown_) {
|
||||
CefRange replacement_range(replacementRange.location,
|
||||
NSMaxRange(replacementRange));
|
||||
CefRange selection_range(newSelRange.location, NSMaxRange(newSelRange));
|
||||
|
||||
browser_->GetHost()->ImeSetComposition(
|
||||
markedText_, underlines_, replacement_range, selection_range);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)unmarkText {
|
||||
// Delete the composition node of the browser and finish an ongoing
|
||||
// composition.
|
||||
// It seems that, instead of calling this method, an input method will call
|
||||
// the setMarkedText method with empty text to cancel ongoing composition.
|
||||
// Implement this method even though we don't expect it to be called.
|
||||
hasMarkedText_ = NO;
|
||||
markedText_.clear();
|
||||
underlines_.clear();
|
||||
|
||||
// If we are handling a key down event then ImeFinishComposingText() will be
|
||||
// called from the keyEvent: method.
|
||||
if (!handlingKeyDown_)
|
||||
browser_->GetHost()->ImeFinishComposingText(false);
|
||||
else
|
||||
unmarkTextCalled_ = YES;
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
|
||||
actualRange:(NSRangePointer)actualRange {
|
||||
// Modify the attributed string if required.
|
||||
// Not implemented here as we do not want to control the IME window view.
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSRect)firstViewRectForCharacterRange:(NSRange)theRange
|
||||
actualRange:(NSRangePointer)actualRange {
|
||||
NSRect rect;
|
||||
|
||||
NSUInteger location = theRange.location;
|
||||
|
||||
// If location is not specified fall back to the composition range start.
|
||||
if (location == NSNotFound)
|
||||
location = markedRange_.location;
|
||||
|
||||
// Offset location by the composition range start if required.
|
||||
if (location >= markedRange_.location)
|
||||
location -= markedRange_.location;
|
||||
|
||||
if(location < composition_bounds_.size()) {
|
||||
const CefRect& rc = composition_bounds_[location];
|
||||
rect = NSMakeRect(rc.x, rc.y, rc.width, rc.height);
|
||||
}
|
||||
|
||||
if (actualRange)
|
||||
*actualRange = NSMakeRange(location, theRange.length);
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
- (NSRect)screenRectFromViewRect:(NSRect)rect {
|
||||
NSRect screenRect;
|
||||
|
||||
int screenX, screenY;
|
||||
browser_->GetHost()->GetClient()->GetRenderHandler()->GetScreenPoint(
|
||||
browser_, rect.origin.x, rect.origin.y, screenX, screenY);
|
||||
screenRect.origin = NSMakePoint(screenX, screenY);
|
||||
screenRect.size = rect.size;
|
||||
|
||||
return screenRect;
|
||||
}
|
||||
|
||||
- (NSRect)firstRectForCharacterRange:(NSRange)theRange
|
||||
actualRange:(NSRangePointer)actualRange {
|
||||
NSRect rect = [self firstViewRectForCharacterRange:theRange
|
||||
actualRange:actualRange];
|
||||
|
||||
// Convert into screen coordinates for return.
|
||||
rect = [self screenRectFromViewRect:rect];
|
||||
|
||||
if (rect.origin.y >= rect.size.height)
|
||||
rect.origin.y -= rect.size.height;
|
||||
else
|
||||
rect.origin.y = 0;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint {
|
||||
return NSNotFound;
|
||||
}
|
||||
|
||||
- (void)HandleKeyEventBeforeTextInputClient:(NSEvent*)keyEvent {
|
||||
DCHECK([keyEvent type] == NSKeyDown);
|
||||
// Don't call this method recursively.
|
||||
DCHECK(!handlingKeyDown_);
|
||||
|
||||
oldHasMarkedText_ = hasMarkedText_;
|
||||
handlingKeyDown_ = YES;
|
||||
|
||||
// These variables might be set when handling the keyboard event.
|
||||
// Clear them here so that we can know whether they have changed afterwards.
|
||||
textToBeInserted_.clear();
|
||||
markedText_.clear();
|
||||
underlines_.clear();
|
||||
setMarkedTextReplacementRange_ = CefRange(UINT32_MAX, UINT32_MAX);
|
||||
unmarkTextCalled_ = NO;
|
||||
}
|
||||
|
||||
- (void)HandleKeyEventAfterTextInputClient:(CefKeyEvent)keyEvent {
|
||||
handlingKeyDown_ = NO;
|
||||
|
||||
// Send keypress and/or composition related events.
|
||||
// Note that |textToBeInserted_| is a UTF-16 string but it's fine to only
|
||||
// handle BMP characters here as we can always insert non-BMP characters as
|
||||
// text.
|
||||
|
||||
// If the text to be inserted only contains 1 character then we can just send
|
||||
// a keypress event.
|
||||
if (!hasMarkedText_ && !oldHasMarkedText_ &&
|
||||
textToBeInserted_.length() <= 1) {
|
||||
keyEvent.type = KEYEVENT_KEYDOWN;
|
||||
|
||||
browser_->GetHost()->SendKeyEvent(keyEvent);
|
||||
|
||||
// Don't send a CHAR event for non-char keys like arrows, function keys and
|
||||
// clear.
|
||||
if (keyEvent.modifiers & (EVENTFLAG_IS_KEY_PAD)) {
|
||||
if(keyEvent.native_key_code == 71)
|
||||
return;
|
||||
}
|
||||
|
||||
keyEvent.type = KEYEVENT_CHAR;
|
||||
browser_->GetHost()->SendKeyEvent(keyEvent);
|
||||
}
|
||||
|
||||
// If the text to be inserted contains multiple characters then send the text
|
||||
// to the browser using ImeCommitText().
|
||||
BOOL textInserted = NO;
|
||||
if (textToBeInserted_.length() >
|
||||
((hasMarkedText_ || oldHasMarkedText_) ? 0u : 1u)) {
|
||||
browser_->GetHost()->ImeCommitText(
|
||||
textToBeInserted_, CefRange(UINT32_MAX, UINT32_MAX), 0);
|
||||
textToBeInserted_.clear();
|
||||
}
|
||||
|
||||
// Update or cancel the composition. If some text has been inserted then we
|
||||
// don't need to explicitly cancel the composition.
|
||||
if (hasMarkedText_ && markedText_.length()) {
|
||||
// Update the composition by sending marked text to the browser.
|
||||
// |selectedRange_| is the range being selected inside the marked text.
|
||||
browser_->GetHost()->ImeSetComposition(
|
||||
markedText_, underlines_, setMarkedTextReplacementRange_,
|
||||
CefRange(selectedRange_.location, NSMaxRange(selectedRange_)));
|
||||
} else if (oldHasMarkedText_ && !hasMarkedText_ && !textInserted) {
|
||||
// There was no marked text or inserted text. Complete or cancel the
|
||||
// composition.
|
||||
if (unmarkTextCalled_)
|
||||
browser_->GetHost()->ImeFinishComposingText(false);
|
||||
else
|
||||
browser_->GetHost()->ImeCancelComposition();
|
||||
}
|
||||
|
||||
setMarkedTextReplacementRange_ = CefRange(UINT32_MAX, UINT32_MAX);
|
||||
}
|
||||
|
||||
- (void)ChangeCompositionRange:(CefRange)range
|
||||
character_bounds:(const CefRenderHandler::RectList&) bounds {
|
||||
composition_range_ = range;
|
||||
markedRange_ = NSMakeRange(range.from, range.to - range.from);
|
||||
composition_bounds_ = bounds;
|
||||
}
|
||||
|
||||
- (void)cancelComposition {
|
||||
if (!hasMarkedText_)
|
||||
return;
|
||||
|
||||
// Cancel the ongoing composition. [NSInputManager markedTextAbandoned:]
|
||||
// doesn't call any NSTextInput functions, such as setMarkedText or
|
||||
// insertText.
|
||||
// TODO(erikchen): NSInputManager is deprecated since OSX 10.6. Switch to
|
||||
// NSTextInputContext. http://www.crbug.com/479010.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
NSInputManager* currentInputManager = [NSInputManager currentInputManager];
|
||||
[currentInputManager markedTextAbandoned:self];
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
hasMarkedText_ = NO;
|
||||
// Should not call [self unmarkText] here because it'll send unnecessary
|
||||
// cancel composition messages to the browser.
|
||||
}
|
||||
|
||||
@end
|
@@ -173,6 +173,14 @@ enum OSRTestType {
|
||||
OSR_TEST_DRAG_DROP_UPDATE_CURSOR,
|
||||
// dropping element inside drop region will move the element
|
||||
OSR_TEST_DRAG_DROP_DROP,
|
||||
// IMESetComposition will update the composition range
|
||||
OSR_TEST_IME_SET_COMPOSITION,
|
||||
// IMECommitText inserts the specified text
|
||||
OSR_TEST_IME_COMMIT_TEXT,
|
||||
// IMEFinishComposition will commit the text present composition text
|
||||
OSR_TEST_IME_FINISH_COMPOSITION,
|
||||
// IMECancelComposition will update the composition range
|
||||
OSR_TEST_IME_CANCEL_COMPOSITION,
|
||||
|
||||
// Define the range for popup tests.
|
||||
OSR_TEST_POPUP_FIRST = OSR_TEST_POPUP_PAINT,
|
||||
@@ -224,6 +232,27 @@ class OSRTestHandler : public RoutingTestHandler,
|
||||
frame->GetURL().ToString().c_str());
|
||||
DestroySucceededTestSoon();
|
||||
} break;
|
||||
case OSR_TEST_IME_COMMIT_TEXT: {
|
||||
const std::string& expected_url =
|
||||
std::string(kTestUrl) + "?k=osrimecommit";
|
||||
EXPECT_STREQ(expected_url.c_str(),
|
||||
frame->GetURL().ToString().c_str());
|
||||
DestroySucceededTestSoon();
|
||||
} break;
|
||||
case OSR_TEST_IME_FINISH_COMPOSITION: {
|
||||
const std::string& expected_url =
|
||||
std::string(kTestUrl) + "?k=" + kKeyTestWord;
|
||||
EXPECT_STREQ(expected_url.c_str(),
|
||||
frame->GetURL().ToString().c_str());
|
||||
DestroySucceededTestSoon();
|
||||
} break;
|
||||
case OSR_TEST_IME_CANCEL_COMPOSITION: {
|
||||
const std::string& expected_url =
|
||||
std::string(kTestUrl) + "?k=";
|
||||
EXPECT_STREQ(expected_url.c_str(),
|
||||
frame->GetURL().ToString().c_str());
|
||||
DestroySucceededTestSoon();
|
||||
} break;
|
||||
default:
|
||||
// Intentionally left blank
|
||||
break;
|
||||
@@ -717,6 +746,169 @@ class OSRTestHandler : public RoutingTestHandler,
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OSR_TEST_IME_COMMIT_TEXT:
|
||||
{
|
||||
// trigger the IME Set Composition event
|
||||
if (StartTest()) {
|
||||
// click inside edit box so that text could be entered
|
||||
CefMouseEvent mouse_event;
|
||||
mouse_event.x = MiddleX(kEditBoxRect);
|
||||
mouse_event.y = MiddleY(kEditBoxRect);
|
||||
mouse_event.modifiers = 0;
|
||||
browser->GetHost()->SendMouseClickEvent(
|
||||
mouse_event, MBT_LEFT, false, 1);
|
||||
browser->GetHost()->SendMouseClickEvent(
|
||||
mouse_event, MBT_LEFT, true, 1);
|
||||
|
||||
size_t word_length = strlen(kKeyTestWord);
|
||||
// Add some input keys to edit box
|
||||
for (size_t i = 0; i < word_length; ++i) {
|
||||
#if defined(OS_WIN)
|
||||
SendKeyEvent(browser, kKeyTestWord[i]);
|
||||
#elif defined(OS_MACOSX)
|
||||
SendKeyEvent(browser, kKeyTestCodes[i]);
|
||||
#elif defined(OS_LINUX)
|
||||
SendKeyEvent(browser, kNativeKeyTestCodes[i], kKeyTestCodes[i]);
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
}
|
||||
// This text should be honored instead of 'ka' added via key events
|
||||
CefString markedText("osrimecommit");
|
||||
|
||||
CefRange range(0, markedText.length());
|
||||
browser->GetHost()->ImeCommitText(markedText, range, 0);
|
||||
|
||||
// click button to navigate
|
||||
mouse_event.x = MiddleX(kNavigateButtonRect);
|
||||
mouse_event.y = MiddleY(kNavigateButtonRect);
|
||||
browser->GetHost()->SendMouseClickEvent(
|
||||
mouse_event, MBT_LEFT, false, 1);
|
||||
browser->GetHost()->SendMouseClickEvent(
|
||||
mouse_event, MBT_LEFT, true, 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OSR_TEST_IME_FINISH_COMPOSITION:
|
||||
{
|
||||
// trigger the IME Set Composition event
|
||||
if (StartTest()) {
|
||||
// click inside edit box so that text could be entered
|
||||
CefMouseEvent mouse_event;
|
||||
mouse_event.x = MiddleX(kEditBoxRect);
|
||||
mouse_event.y = MiddleY(kEditBoxRect);
|
||||
mouse_event.modifiers = 0;
|
||||
browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT,
|
||||
false, 1);
|
||||
browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT,
|
||||
true, 1);
|
||||
|
||||
size_t word_length = strlen(kKeyTestWord);
|
||||
// Add some input keys to edit box
|
||||
for (size_t i = 0; i < word_length; ++i) {
|
||||
#if defined(OS_WIN)
|
||||
SendKeyEvent(browser, kKeyTestWord[i]);
|
||||
#elif defined(OS_MACOSX)
|
||||
SendKeyEvent(browser, kKeyTestCodes[i]);
|
||||
#elif defined(OS_LINUX)
|
||||
SendKeyEvent(browser, kNativeKeyTestCodes[i], kKeyTestCodes[i]);
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
}
|
||||
|
||||
// Finish Composition should set the existing composition
|
||||
browser->GetHost()->ImeFinishComposingText(true);
|
||||
|
||||
// click button to navigate
|
||||
mouse_event.x = MiddleX(kNavigateButtonRect);
|
||||
mouse_event.y = MiddleY(kNavigateButtonRect);
|
||||
browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT,
|
||||
false, 1);
|
||||
browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT,
|
||||
true, 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OSR_TEST_IME_CANCEL_COMPOSITION:
|
||||
{
|
||||
// trigger the IME Set Composition event
|
||||
if (StartTest()) {
|
||||
// click inside edit box so that text could be entered
|
||||
CefMouseEvent mouse_event;
|
||||
mouse_event.x = MiddleX(kEditBoxRect);
|
||||
mouse_event.y = MiddleY(kEditBoxRect);
|
||||
mouse_event.modifiers = 0;
|
||||
browser->GetHost()->SendMouseClickEvent(
|
||||
mouse_event, MBT_LEFT, false, 1);
|
||||
browser->GetHost()->SendMouseClickEvent(
|
||||
mouse_event, MBT_LEFT, true, 1);
|
||||
// Add some input keys to edit box
|
||||
CefString markedText("か");
|
||||
std::vector<CefCompositionUnderline> underlines;
|
||||
|
||||
// Use a thin black underline by default.
|
||||
cef_range_t range = {0, markedText.length()};
|
||||
cef_composition_underline_t line = {
|
||||
range, 0xFF000000, 0, false
|
||||
};
|
||||
underlines.push_back(line);
|
||||
|
||||
CefRange replacement_range(0, markedText.length());
|
||||
CefRange selection_range(0,markedText.length());
|
||||
|
||||
// Composition should be updated
|
||||
browser->GetHost()->ImeSetComposition(markedText, underlines,
|
||||
replacement_range,selection_range);
|
||||
|
||||
// CancelComposition should clean up the edit text
|
||||
browser->GetHost()->ImeCancelComposition();
|
||||
|
||||
// click button to navigate and verify
|
||||
mouse_event.x = MiddleX(kNavigateButtonRect);
|
||||
mouse_event.y = MiddleY(kNavigateButtonRect);
|
||||
browser->GetHost()->SendMouseClickEvent(
|
||||
mouse_event, MBT_LEFT, false, 1);
|
||||
browser->GetHost()->SendMouseClickEvent(
|
||||
mouse_event, MBT_LEFT, true, 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OSR_TEST_IME_SET_COMPOSITION:
|
||||
{
|
||||
// trigger the IME Set Composition event
|
||||
if (StartTest()) {
|
||||
// click inside edit box so that text could be entered
|
||||
CefMouseEvent mouse_event;
|
||||
mouse_event.x = MiddleX(kEditBoxRect);
|
||||
mouse_event.y = MiddleY(kEditBoxRect);
|
||||
mouse_event.modifiers = 0;
|
||||
browser->GetHost()->SendMouseClickEvent(
|
||||
mouse_event, MBT_LEFT, false, 1);
|
||||
browser->GetHost()->SendMouseClickEvent(
|
||||
mouse_event, MBT_LEFT, true, 1);
|
||||
|
||||
// Now set some intermediate text composition
|
||||
CefString markedText("か");
|
||||
std::vector<CefCompositionUnderline> underlines;
|
||||
|
||||
// Use a thin black underline by default.
|
||||
cef_range_t range = {0, markedText.length()};
|
||||
cef_composition_underline_t line = {
|
||||
range, 0xFF000000, 0, false
|
||||
};
|
||||
underlines.push_back(line);
|
||||
|
||||
CefRange replacement_range(0, markedText.length());
|
||||
CefRange selection_range(0,markedText.length());
|
||||
|
||||
// This should update composition range and
|
||||
// trigger the compositionRangeChanged callback
|
||||
browser->GetHost()->ImeSetComposition(markedText, underlines,
|
||||
replacement_range,selection_range);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -750,6 +942,17 @@ class OSRTestHandler : public RoutingTestHandler,
|
||||
}
|
||||
}
|
||||
|
||||
void OnImeCompositionRangeChanged(CefRefPtr<CefBrowser> browser,
|
||||
const CefRange& range,
|
||||
const CefRenderHandler::RectList& bounds) override {
|
||||
if (test_type_ == OSR_TEST_IME_SET_COMPOSITION && started()) {
|
||||
EXPECT_EQ(range.from, 0);
|
||||
EXPECT_EQ(range.to, 1);
|
||||
EXPECT_EQ(1U, bounds.size());
|
||||
DestroySucceededTestSoon();
|
||||
}
|
||||
}
|
||||
|
||||
bool StartDragging(CefRefPtr<CefBrowser> browser,
|
||||
CefRefPtr<CefDragData> drag_data,
|
||||
CefRenderHandler::DragOperationsMask allowed_ops,
|
||||
@@ -1078,3 +1281,11 @@ OSR_TEST(DragDropUpdateCursor, OSR_TEST_DRAG_DROP_UPDATE_CURSOR, 1.0f);
|
||||
OSR_TEST(DragDropUpdateCursor2x, OSR_TEST_DRAG_DROP_UPDATE_CURSOR, 2.0f);
|
||||
OSR_TEST(DragDropDropElement, OSR_TEST_DRAG_DROP_DROP, 1.0f);
|
||||
OSR_TEST(DragDropDropElement2x, OSR_TEST_DRAG_DROP_DROP, 2.0f);
|
||||
OSR_TEST(IMESetComposition, OSR_TEST_IME_SET_COMPOSITION, 1.0f);
|
||||
OSR_TEST(IMESetComposition2x, OSR_TEST_IME_SET_COMPOSITION, 2.0f);
|
||||
OSR_TEST(IMECommitText, OSR_TEST_IME_COMMIT_TEXT, 1.0f);
|
||||
OSR_TEST(IMECommitText2x, OSR_TEST_IME_COMMIT_TEXT, 2.0f);
|
||||
OSR_TEST(IMEFinishComposition, OSR_TEST_IME_FINISH_COMPOSITION, 1.0f);
|
||||
OSR_TEST(IMEFinishComposition2x, OSR_TEST_IME_FINISH_COMPOSITION, 2.0f);
|
||||
OSR_TEST(IMECancelComposition, OSR_TEST_IME_CANCEL_COMPOSITION, 1.0f);
|
||||
OSR_TEST(IMECancelComposition2x, OSR_TEST_IME_CANCEL_COMPOSITION, 2.0f);
|
||||
|
Reference in New Issue
Block a user