Commit 0b15dad9 authored by scottmg@chromium.org's avatar scottmg@chromium.org

Remove InputMethodTSF, TSFEventRouter, TSFTextStore, TSFBridge

My understanding is that these are now unnecessary. Hopefully I
haven't removed too much here.

TBR=ben@chromium.org
R=yukawa@chromium.org
BUG=319122, 5027, 330735

Review URL: https://codereview.chromium.org/149073009

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@248001 0039d316-1c4b-4281-b951-d872f2087c98
parent e0dfae2d
...@@ -45,8 +45,6 @@ ...@@ -45,8 +45,6 @@
'input_method_minimal.cc', 'input_method_minimal.cc',
'input_method_minimal.h', 'input_method_minimal.h',
'input_method_observer.h', 'input_method_observer.h',
'input_method_tsf.cc',
'input_method_tsf.h',
'input_method_win.cc', 'input_method_win.cc',
'input_method_win.h', 'input_method_win.h',
'linux/fake_input_method_context.cc', 'linux/fake_input_method_context.cc',
...@@ -66,14 +64,8 @@ ...@@ -66,14 +64,8 @@
'text_input_type.h', 'text_input_type.h',
'win/imm32_manager.cc', 'win/imm32_manager.cc',
'win/imm32_manager.h', 'win/imm32_manager.h',
'win/tsf_bridge.cc',
'win/tsf_bridge.h',
'win/tsf_event_router.cc',
'win/tsf_event_router.h',
'win/tsf_input_scope.cc', 'win/tsf_input_scope.cc',
'win/tsf_input_scope.h', 'win/tsf_input_scope.h',
'win/tsf_text_store.cc',
'win/tsf_text_store.h',
], ],
'conditions': [ 'conditions': [
['toolkit_views==0 and use_aura==0', { ['toolkit_views==0 and use_aura==0', {
...@@ -99,8 +91,6 @@ ...@@ -99,8 +91,6 @@
'sources!': [ 'sources!': [
'input_method_imm32.cc', 'input_method_imm32.cc',
'input_method_imm32.h', 'input_method_imm32.h',
'input_method_tsf.cc',
'input_method_tsf.h',
], ],
}], }],
['use_aura==0 or (desktop_linux==0 and use_ozone==0)', { ['use_aura==0 or (desktop_linux==0 and use_ozone==0)', {
......
...@@ -8,15 +8,5 @@ ...@@ -8,15 +8,5 @@
'dummy_input_method.h', 'dummy_input_method.h',
'dummy_text_input_client.cc', 'dummy_text_input_client.cc',
'dummy_text_input_client.h', 'dummy_text_input_client.h',
'win/mock_tsf_bridge.cc',
'win/mock_tsf_bridge.h',
],
'conditions': [
['OS!="win"', {
'sources!': [
'win/mock_tsf_bridge.cc',
'win/mock_tsf_bridge.h',
],
}],
], ],
} }
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
'remote_input_method_win_unittest.cc', 'remote_input_method_win_unittest.cc',
'win/imm32_manager_unittest.cc', 'win/imm32_manager_unittest.cc',
'win/tsf_input_scope_unittest.cc', 'win/tsf_input_scope_unittest.cc',
'win/tsf_text_store_unittest.cc',
], ],
'conditions': [ 'conditions': [
['chromeos==0 or use_x11==0', { ['chromeos==0 or use_x11==0', {
......
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
#elif defined(OS_WIN) #elif defined(OS_WIN)
#include "base/win/metro.h" #include "base/win/metro.h"
#include "ui/base/ime/input_method_imm32.h" #include "ui/base/ime/input_method_imm32.h"
#include "ui/base/ime/input_method_tsf.h"
#include "ui/base/ime/remote_input_method_win.h" #include "ui/base/ime/remote_input_method_win.h"
#elif defined(USE_AURA) && defined(OS_LINUX) #elif defined(USE_AURA) && defined(OS_LINUX)
#include "ui/base/ime/input_method_auralinux.h" #include "ui/base/ime/input_method_auralinux.h"
...@@ -45,8 +44,6 @@ scoped_ptr<InputMethod> CreateInputMethod( ...@@ -45,8 +44,6 @@ scoped_ptr<InputMethod> CreateInputMethod(
#if defined(OS_CHROMEOS) && defined(USE_X11) #if defined(OS_CHROMEOS) && defined(USE_X11)
return scoped_ptr<InputMethod>(new InputMethodChromeOS(delegate)); return scoped_ptr<InputMethod>(new InputMethodChromeOS(delegate));
#elif defined(OS_WIN) #elif defined(OS_WIN)
if (base::win::IsTSFAwareRequired())
return scoped_ptr<InputMethod>(new InputMethodTSF(delegate, widget));
if (IsRemoteInputMethodWinRequired(widget)) if (IsRemoteInputMethodWinRequired(widget))
return CreateRemoteInputMethodWin(delegate); return CreateRemoteInputMethodWin(delegate);
return scoped_ptr<InputMethod>(new InputMethodIMM32(delegate, widget)); return scoped_ptr<InputMethod>(new InputMethodIMM32(delegate, widget));
......
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
#elif defined(OS_WIN) #elif defined(OS_WIN)
#include "base/win/metro.h" #include "base/win/metro.h"
#include "ui/base/ime/input_method_factory.h" #include "ui/base/ime/input_method_factory.h"
#include "ui/base/ime/win/tsf_bridge.h"
#endif #endif
namespace { namespace {
...@@ -31,9 +30,6 @@ void InitializeInputMethod() { ...@@ -31,9 +30,6 @@ void InitializeInputMethod() {
chromeos::IMEBridge::Initialize(); chromeos::IMEBridge::Initialize();
#elif defined(USE_AURA) && defined(OS_LINUX) && !defined(USE_OZONE) #elif defined(USE_AURA) && defined(OS_LINUX) && !defined(USE_OZONE)
InputMethodAuraLinux::Initialize(); InputMethodAuraLinux::Initialize();
#elif defined(OS_WIN)
if (base::win::IsTSFAwareRequired())
TSFBridge::Initialize();
#endif #endif
} }
...@@ -42,8 +38,6 @@ void ShutdownInputMethod() { ...@@ -42,8 +38,6 @@ void ShutdownInputMethod() {
chromeos::IMEBridge::Shutdown(); chromeos::IMEBridge::Shutdown();
#elif defined(OS_WIN) #elif defined(OS_WIN)
internal::DestroySharedInputMethod(); internal::DestroySharedInputMethod();
if (base::win::IsTSFAwareRequired())
TSFBridge::Shutdown();
#endif #endif
} }
...@@ -60,12 +54,6 @@ void InitializeInputMethodForTesting() { ...@@ -60,12 +54,6 @@ void InitializeInputMethodForTesting() {
<< "else."; << "else.";
LinuxInputMethodContextFactory::SetInstance( LinuxInputMethodContextFactory::SetInstance(
g_linux_input_method_context_factory); g_linux_input_method_context_factory);
#elif defined(OS_WIN)
if (base::win::IsTSFAwareRequired()) {
// Make sure COM is initialized because TSF depends on COM.
CoInitialize(NULL);
TSFBridge::Initialize();
}
#endif #endif
} }
...@@ -82,10 +70,6 @@ void ShutdownInputMethodForTesting() { ...@@ -82,10 +70,6 @@ void ShutdownInputMethodForTesting() {
g_linux_input_method_context_factory = NULL; g_linux_input_method_context_factory = NULL;
#elif defined(OS_WIN) #elif defined(OS_WIN)
internal::DestroySharedInputMethod(); internal::DestroySharedInputMethod();
if (base::win::IsTSFAwareRequired()) {
TSFBridge::Shutdown();
CoUninitialize();
}
#endif #endif
} }
......
// Copyright (c) 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.
#include "ui/base/ime/input_method_tsf.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/ime/win/tsf_bridge.h"
#include "ui/base/ime/win/tsf_event_router.h"
namespace ui {
class InputMethodTSF::TSFEventObserver : public TSFEventRouterObserver {
public:
TSFEventObserver() : is_candidate_popup_open_(false) {}
// Returns true if we know for sure that a candidate window (or IME suggest,
// etc.) is open.
bool IsCandidatePopupOpen() const { return is_candidate_popup_open_; }
// Overridden from TSFEventRouterObserver:
virtual void OnCandidateWindowCountChanged(size_t window_count) OVERRIDE {
is_candidate_popup_open_ = (window_count != 0);
}
private:
// True if we know for sure that a candidate window is open.
bool is_candidate_popup_open_;
DISALLOW_COPY_AND_ASSIGN(TSFEventObserver);
};
InputMethodTSF::InputMethodTSF(internal::InputMethodDelegate* delegate,
HWND toplevel_window_handle)
: InputMethodWin(delegate, toplevel_window_handle),
tsf_event_observer_(new TSFEventObserver()),
tsf_event_router_(new TSFEventRouter(tsf_event_observer_.get())) {
// In non-Aura environment, appropriate callbacks to OnFocus() and OnBlur()
// are not implemented yet. To work around this limitation, here we use
// "always focused" model.
// TODO(ime): Fix the caller of OnFocus() and OnBlur() so that appropriate
// focus event will be passed.
InputMethodWin::OnFocus();
}
InputMethodTSF::~InputMethodTSF() {}
void InputMethodTSF::OnFocus() {
// Do not call baseclass' OnFocus() and discard the event being in
// "always focused" model. See the comment in the constructor.
// TODO(ime): Implement OnFocus once the callers are fixed.
tsf_event_router_->SetManager(
ui::TSFBridge::GetInstance()->GetThreadManager());
}
void InputMethodTSF::OnBlur() {
// Do not call baseclass' OnBlur() and discard the event being in
// "always focused" model. See the comment in the constructor.
// TODO(ime): Implement OnFocus once the callers are fixed.
tsf_event_router_->SetManager(NULL);
}
bool InputMethodTSF::OnUntranslatedIMEMessage(
const base::NativeEvent& event, InputMethod::NativeEventResult* result) {
LRESULT original_result = 0;
BOOL handled = FALSE;
// Even when TSF is enabled, following IMM32/Win32 messages must be handled.
switch (event.message) {
case WM_IME_REQUEST:
// Some TSF-native TIPs (Text Input Processors) such as ATOK and Mozc
// still rely on WM_IME_REQUEST message to implement reverse conversion.
original_result = OnImeRequest(
event.message, event.wParam, event.lParam, &handled);
break;
case WM_CHAR:
case WM_SYSCHAR:
// ui::InputMethod interface is responsible for handling Win32 character
// messages. For instance, we will be here in the following cases.
// - TIP is not activated. (e.g, the current language profile is English)
// - TIP does not handle and WM_KEYDOWN and WM_KEYDOWN is translated into
// WM_CHAR by TranslateMessage API. (e.g, TIP is turned off)
// - Another application sends WM_CHAR through SendMessage API.
original_result = OnChar(
event.hwnd, event.message, event.wParam, event.lParam, &handled);
break;
case WM_DEADCHAR:
case WM_SYSDEADCHAR:
// See the comment in WM_CHAR/WM_SYSCHAR.
original_result = OnDeadChar(
event.message, event.wParam, event.lParam, &handled);
break;
}
if (result)
*result = original_result;
return !!handled;
}
void InputMethodTSF::OnTextInputTypeChanged(const TextInputClient* client) {
if (!IsTextInputClientFocused(client) || !IsWindowFocused(client))
return;
ui::TSFBridge::GetInstance()->CancelComposition();
ui::TSFBridge::GetInstance()->OnTextInputTypeChanged(client);
}
void InputMethodTSF::OnCaretBoundsChanged(const TextInputClient* client) {
if (!IsTextInputClientFocused(client) || !IsWindowFocused(client))
return;
ui::TSFBridge::GetInstance()->OnTextLayoutChanged();
}
void InputMethodTSF::CancelComposition(const TextInputClient* client) {
if (IsTextInputClientFocused(client) && IsWindowFocused(client))
ui::TSFBridge::GetInstance()->CancelComposition();
}
void InputMethodTSF::DetachTextInputClient(TextInputClient* client) {
InputMethodWin::DetachTextInputClient(client);
ui::TSFBridge::GetInstance()->RemoveFocusedClient(client);
}
bool InputMethodTSF::IsCandidatePopupOpen() const {
return tsf_event_observer_->IsCandidatePopupOpen();
}
void InputMethodTSF::OnWillChangeFocusedClient(TextInputClient* focused_before,
TextInputClient* focused) {
if (IsWindowFocused(focused_before)) {
ConfirmCompositionText();
ui::TSFBridge::GetInstance()->RemoveFocusedClient(focused_before);
}
}
void InputMethodTSF::OnDidChangeFocusedClient(TextInputClient* focused_before,
TextInputClient* focused) {
if (IsWindowFocused(focused) && IsTextInputClientFocused(focused)) {
ui::TSFBridge::GetInstance()->SetFocusedClient(
GetAttachedWindowHandle(focused), focused);
// Force to update the input type since client's TextInputStateChanged()
// function might not be called if text input types before the client loses
// focus and after it acquires focus again are the same.
OnTextInputTypeChanged(focused);
// Force to update caret bounds, in case the client thinks that the caret
// bounds has not changed.
OnCaretBoundsChanged(focused);
}
InputMethodWin::OnDidChangeFocusedClient(focused_before, focused);
}
void InputMethodTSF::ConfirmCompositionText() {
if (!IsTextInputTypeNone())
ui::TSFBridge::GetInstance()->ConfirmComposition();
}
} // namespace ui
// Copyright (c) 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 UI_BASE_IME_INPUT_METHOD_TSF_H_
#define UI_BASE_IME_INPUT_METHOD_TSF_H_
#include <windows.h>
#include <string>
#include "base/memory/scoped_ptr.h"
#include "ui/base/ime/input_method_win.h"
namespace ui {
class TSFEventRouter;
// An InputMethod implementation based on Windows TSF API.
class UI_BASE_EXPORT InputMethodTSF : public InputMethodWin {
public:
InputMethodTSF(internal::InputMethodDelegate* delegate,
HWND toplevel_window_handle);
virtual ~InputMethodTSF();
// Overridden from InputMethod:
virtual void OnFocus() OVERRIDE;
virtual void OnBlur() OVERRIDE;
virtual bool OnUntranslatedIMEMessage(const base::NativeEvent& event,
NativeEventResult* result) OVERRIDE;
virtual void OnTextInputTypeChanged(const TextInputClient* client) OVERRIDE;
virtual void OnCaretBoundsChanged(const TextInputClient* client) OVERRIDE;
virtual void CancelComposition(const TextInputClient* client) OVERRIDE;
virtual void DetachTextInputClient(TextInputClient* client) OVERRIDE;
virtual bool IsCandidatePopupOpen() const OVERRIDE;
// Overridden from InputMethodBase:
virtual void OnWillChangeFocusedClient(TextInputClient* focused_before,
TextInputClient* focused) OVERRIDE;
virtual void OnDidChangeFocusedClient(TextInputClient* focused_before,
TextInputClient* focused) OVERRIDE;
private:
class TSFEventObserver;
// Asks the client to confirm current composition text.
void ConfirmCompositionText();
// TSF event router and observer.
scoped_ptr<TSFEventObserver> tsf_event_observer_;
scoped_ptr<TSFEventRouter> tsf_event_router_;
DISALLOW_COPY_AND_ASSIGN(InputMethodTSF);
};
} // namespace ui
#endif // UI_BASE_IME_INPUT_METHOD_TSF_H_
// Copyright (c) 2012 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.
#include "ui/base/ime/win/mock_tsf_bridge.h"
#include "ui/base/ime/text_input_client.h"
#include "base/logging.h"
namespace ui {
MockTSFBridge::MockTSFBridge()
: enable_ime_call_count_(0),
disalbe_ime_call_count_(0),
cancel_composition_call_count_(0),
confirm_composition_call_count_(0),
on_text_layout_changed_(0),
associate_focus_call_count_(0),
set_focused_client_call_count_(0),
remove_focused_client_call_count_(0),
text_input_client_(NULL),
focused_window_(NULL),
latest_text_input_type_(TEXT_INPUT_TYPE_NONE) {
}
MockTSFBridge::~MockTSFBridge() {
}
bool MockTSFBridge::CancelComposition() {
++cancel_composition_call_count_;
return true;
}
bool MockTSFBridge::ConfirmComposition() {
++confirm_composition_call_count_;
return true;
}
void MockTSFBridge::OnTextInputTypeChanged(const TextInputClient* client) {
latest_text_input_type_ = client->GetTextInputType();
}
void MockTSFBridge::OnTextLayoutChanged() {
++on_text_layout_changed_;
}
void MockTSFBridge::SetFocusedClient(HWND focused_window,
TextInputClient* client) {
++set_focused_client_call_count_;
focused_window_ = focused_window;
text_input_client_ = client;
}
void MockTSFBridge::RemoveFocusedClient(TextInputClient* client) {
++remove_focused_client_call_count_;
DCHECK_EQ(client, text_input_client_);
text_input_client_ = NULL;
focused_window_ = NULL;
}
base::win::ScopedComPtr<ITfThreadMgr> MockTSFBridge::GetThreadManager() {
return thread_manager_;
}
TextInputClient* MockTSFBridge::GetFocusedTextInputClient() const {
return text_input_client_;
}
void MockTSFBridge::Reset() {
enable_ime_call_count_ = 0;
disalbe_ime_call_count_ = 0;
cancel_composition_call_count_ = 0;
confirm_composition_call_count_ = 0;
on_text_layout_changed_ = 0;
associate_focus_call_count_ = 0;
set_focused_client_call_count_ = 0;
remove_focused_client_call_count_ = 0;
text_input_client_ = NULL;
focused_window_ = NULL;
latest_text_input_type_ = TEXT_INPUT_TYPE_NONE;
}
} // namespace ui
// Copyright (c) 2012 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 UI_BASE_IME_WIN_MOCK_TSF_BRIDGE_H_
#define UI_BASE_IME_WIN_MOCK_TSF_BRIDGE_H_
#include <msctf.h>
#include "base/compiler_specific.h"
#include "base/win/scoped_comptr.h"
#include "ui/base/ime/text_input_type.h"
#include "ui/base/ime/win/tsf_bridge.h"
namespace ui {
class MockTSFBridge : public TSFBridge {
public:
MockTSFBridge();
virtual ~MockTSFBridge();
// TSFBridge:
virtual bool CancelComposition() OVERRIDE;
virtual bool ConfirmComposition() OVERRIDE;
virtual void OnTextInputTypeChanged(const TextInputClient* client) OVERRIDE;
virtual void OnTextLayoutChanged() OVERRIDE;
virtual void SetFocusedClient(HWND focused_window,
TextInputClient* client) OVERRIDE;
virtual void RemoveFocusedClient(TextInputClient* client) OVERRIDE;
virtual base::win::ScopedComPtr<ITfThreadMgr> GetThreadManager() OVERRIDE;
virtual TextInputClient* GetFocusedTextInputClient() const OVERRIDE;
// Resets MockTSFBridge state including function call counter.
void Reset();
// Call count of EnableIME().
int enable_ime_call_count() const { return enable_ime_call_count_; }
// Call count of DisableIME().
int disalbe_ime_call_count() const { return disalbe_ime_call_count_; }
// Call count of CancelComposition().
int cancel_composition_call_count() const {
return cancel_composition_call_count_;
}
// Call count of ConfirmComposition().
int confirm_composition_call_count() const {
return confirm_composition_call_count_;
}
// Call count of OnTextLayoutChanged().
int on_text_layout_changed() const {
return on_text_layout_changed_;
}
// Call count of AssociateFocus().
int associate_focus_call_count() const { return associate_focus_call_count_; }
// Call count of SetFocusClient().
int set_focused_client_call_count() const {
return set_focused_client_call_count_;
}
// Call count of RemoveFocusedClient().
int remove_focused_client_call_count() const {
return remove_focused_client_call_count_;
}
// Returns current TextInputClient.
TextInputClient* text_input_clinet() const { return text_input_client_; }
// Returns currently focused window handle.
HWND focused_window() const { return focused_window_; }
// Returns latest text input type.
TextInputType latest_text_iput_type() const {
return latest_text_input_type_;
}
private:
int enable_ime_call_count_;
int disalbe_ime_call_count_;
int cancel_composition_call_count_;
int confirm_composition_call_count_;
int on_text_layout_changed_;
int associate_focus_call_count_;
int set_focused_client_call_count_;
int remove_focused_client_call_count_;
TextInputClient* text_input_client_;
HWND focused_window_;
TextInputType latest_text_input_type_;
base::win::ScopedComPtr<ITfThreadMgr> thread_manager_;
DISALLOW_COPY_AND_ASSIGN(MockTSFBridge);
};
} // namespace ui
#endif // UI_BASE_IME_WIN_MOCK_TSF_BRIDGE_H_
// Copyright (c) 2012 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.
#include <msctf.h>
#include <map>
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/threading/thread_local_storage.h"
#include "base/win/scoped_comptr.h"
#include "base/win/scoped_variant.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/ime/win/tsf_bridge.h"
#include "ui/base/ime/win/tsf_text_store.h"
namespace ui {
namespace {
// We use thread local storage for TSFBridge lifespan management.
base::ThreadLocalStorage::StaticSlot tls_tsf_bridge = TLS_INITIALIZER;
// TsfBridgeDelegate -----------------------------------------------------------
// A TLS implementation of TSFBridge.
class TSFBridgeDelegate : public TSFBridge {
public:
TSFBridgeDelegate();
virtual ~TSFBridgeDelegate();
bool Initialize();
// TsfBridge:
virtual void OnTextInputTypeChanged(const TextInputClient* client) OVERRIDE;
virtual void OnTextLayoutChanged() OVERRIDE;
virtual bool CancelComposition() OVERRIDE;
virtual bool ConfirmComposition() OVERRIDE;
virtual void SetFocusedClient(HWND focused_window,
TextInputClient* client) OVERRIDE;
virtual void RemoveFocusedClient(TextInputClient* client) OVERRIDE;
virtual base::win::ScopedComPtr<ITfThreadMgr> GetThreadManager() OVERRIDE;
virtual TextInputClient* GetFocusedTextInputClient() const OVERRIDE;
private:
// Returns true if |tsf_document_map_| is successfully initialized. This
// method should be called from and only from Initialize().
bool InitializeDocumentMapInternal();
// Returns true if |context| is successfully updated to be a disabled
// context, where an IME should be deactivated. This is suitable for some
// special input context such as password fields.
bool InitializeDisabledContext(ITfContext* context);
// Returns true if a TSF document manager and a TSF context is successfully
// created with associating with given |text_store|. The returned
// |source_cookie| indicates the binding between |text_store| and |context|.
// You can pass NULL to |text_store| and |source_cookie| when text store is
// not necessary.
bool CreateDocumentManager(TSFTextStore* text_store,
ITfDocumentMgr** document_manager,
ITfContext** context,
DWORD* source_cookie);
// Returns true if |document_manager| is the focused document manager.
bool IsFocused(ITfDocumentMgr* document_manager);
// Returns true if already initialized.
bool IsInitialized();
// Updates or clears the association maintained in the TSF runtime between
// |attached_window_handle_| and the current document manager. Keeping this
// association updated solves some tricky event ordering issues between
// logical text input focus managed by Chrome and native text input focus
// managed by the OS.
// Background:
// TSF runtime monitors some Win32 messages such as WM_ACTIVATE to
// change the focused document manager. This is problematic when
// TSFBridge::SetFocusedClient is called first then the target window
// receives WM_ACTIVATE. This actually occurs in Aura environment where
// WM_NCACTIVATE is used as a trigger to restore text input focus.
// Caveats:
// TSF runtime does not increment the reference count of the attached
// document manager. See the comment inside the method body for
// details.
void UpdateAssociateFocus();
void ClearAssociateFocus();
// A triple of document manager, text store and binding cookie between
// a context owned by the document manager and the text store. This is a
// minimum working set of an editable document in TSF.
struct TSFDocument {
public:
TSFDocument() : cookie(TF_INVALID_COOKIE) {}
TSFDocument(const TSFDocument& src)
: document_manager(src.document_manager),
cookie(src.cookie) {}
base::win::ScopedComPtr<ITfDocumentMgr> document_manager;
scoped_refptr<TSFTextStore> text_store;
DWORD cookie;
};
// Returns a pointer to TSFDocument that is associated with the current
// TextInputType of |client_|.
TSFDocument* GetAssociatedDocument();
// An ITfThreadMgr object to be used in focus and document management.
base::win::ScopedComPtr<ITfThreadMgr> thread_manager_;
// A map from TextInputType to an editable document for TSF. We use multiple
// TSF documents that have different InputScopes and TSF attributes based on
// the TextInputType associated with the target document. For a TextInputType
// that is not coverted by this map, a default document, e.g. the document
// for TEXT_INPUT_TYPE_TEXT, should be used.
// Note that some IMEs don't change their state unless the document focus is
// changed. This is why we use multiple documents instead of changing TSF
// metadata of a single document on the fly.
typedef std::map<TextInputType, TSFDocument> TSFDocumentMap;
TSFDocumentMap tsf_document_map_;
// An identifier of TSF client.
TfClientId client_id_;
// Current focused text input client. Do not free |client_|.
TextInputClient* client_;
// Represents the window that is currently owns text input focus.
HWND attached_window_handle_;
DISALLOW_COPY_AND_ASSIGN(TSFBridgeDelegate);
};
TSFBridgeDelegate::TSFBridgeDelegate()
: client_id_(TF_CLIENTID_NULL),
client_(NULL),
attached_window_handle_(NULL) {
}
TSFBridgeDelegate::~TSFBridgeDelegate() {
DCHECK(base::MessageLoopForUI::IsCurrent());
if (!IsInitialized())
return;
for (TSFDocumentMap::iterator it = tsf_document_map_.begin();
it != tsf_document_map_.end(); ++it) {
base::win::ScopedComPtr<ITfContext> context;
base::win::ScopedComPtr<ITfSource> source;
if (it->second.cookie != TF_INVALID_COOKIE &&
SUCCEEDED(it->second.document_manager->GetBase(context.Receive())) &&
SUCCEEDED(source.QueryFrom(context))) {
source->UnadviseSink(it->second.cookie);
}
}
tsf_document_map_.clear();
client_id_ = TF_CLIENTID_NULL;
}
bool TSFBridgeDelegate::Initialize() {
DCHECK(base::MessageLoopForUI::IsCurrent());
if (client_id_ != TF_CLIENTID_NULL) {
DVLOG(1) << "Already initialized.";
return false;
}
if (FAILED(thread_manager_.CreateInstance(CLSID_TF_ThreadMgr))) {
DVLOG(1) << "Failed to create ThreadManager instance.";
return false;
}
if (FAILED(thread_manager_->Activate(&client_id_))) {
DVLOG(1) << "Failed to activate Thread Manager.";
return false;
}
if (!InitializeDocumentMapInternal())
return false;
// Japanese IME expects the default value of this compartment is
// TF_SENTENCEMODE_PHRASEPREDICT like IMM32 implementation. This value is
// managed per thread, so that it is enough to set this value at once. This
// value does not affect other language's IME behaviors.
base::win::ScopedComPtr<ITfCompartmentMgr> thread_compartment_manager;
if (FAILED(thread_compartment_manager.QueryFrom(thread_manager_))) {
DVLOG(1) << "Failed to get ITfCompartmentMgr.";
return false;
}
base::win::ScopedComPtr<ITfCompartment> sentence_compartment;
if (FAILED(thread_compartment_manager->GetCompartment(
GUID_COMPARTMENT_KEYBOARD_INPUTMODE_SENTENCE,
sentence_compartment.Receive()))) {
DVLOG(1) << "Failed to get sentence compartment.";
return false;
}
base::win::ScopedVariant sentence_variant;
sentence_variant.Set(TF_SENTENCEMODE_PHRASEPREDICT);
if (FAILED(sentence_compartment->SetValue(client_id_, &sentence_variant))) {
DVLOG(1) << "Failed to change the sentence mode.";
return false;
}
return true;
}
void TSFBridgeDelegate::OnTextInputTypeChanged(const TextInputClient* client) {
DCHECK(base::MessageLoopForUI::IsCurrent());
DCHECK(IsInitialized());
if (client != client_) {
// Called from not focusing client. Do nothing.
return;
}
UpdateAssociateFocus();
TSFDocument* document = GetAssociatedDocument();
if (!document)
return;
thread_manager_->SetFocus(document->document_manager.get());
OnTextLayoutChanged();
}
void TSFBridgeDelegate::OnTextLayoutChanged() {
TSFDocument* document = GetAssociatedDocument();
if (!document)
return;
if (!document->text_store)
return;
document->text_store->SendOnLayoutChange();
}
bool TSFBridgeDelegate::CancelComposition() {
DCHECK(base::MessageLoopForUI::IsCurrent());
DCHECK(IsInitialized());
TSFDocument* document = GetAssociatedDocument();
if (!document)
return false;
if (!document->text_store)
return false;
return document->text_store->CancelComposition();
}
bool TSFBridgeDelegate::ConfirmComposition() {
DCHECK(base::MessageLoopForUI::IsCurrent());
DCHECK(IsInitialized());
TSFDocument* document = GetAssociatedDocument();
if (!document)
return false;
if (!document->text_store)
return false;
return document->text_store->ConfirmComposition();
}
void TSFBridgeDelegate::SetFocusedClient(HWND focused_window,
TextInputClient* client) {
DCHECK(base::MessageLoopForUI::IsCurrent());
DCHECK(client);
DCHECK(IsInitialized());
if (attached_window_handle_ != focused_window)
ClearAssociateFocus();
client_ = client;
attached_window_handle_ = focused_window;
for (TSFDocumentMap::iterator it = tsf_document_map_.begin();
it != tsf_document_map_.end(); ++it) {
if (it->second.text_store.get() == NULL)
continue;
it->second.text_store->SetFocusedTextInputClient(focused_window,
client);
}
// Synchronize text input type state.
OnTextInputTypeChanged(client);
}
void TSFBridgeDelegate::RemoveFocusedClient(TextInputClient* client) {
DCHECK(base::MessageLoopForUI::IsCurrent());
DCHECK(IsInitialized());
if (client_ != client)
return;
ClearAssociateFocus();
client_ = NULL;
attached_window_handle_ = NULL;
for (TSFDocumentMap::iterator it = tsf_document_map_.begin();
it != tsf_document_map_.end(); ++it) {
if (it->second.text_store.get() == NULL)
continue;
it->second.text_store->SetFocusedTextInputClient(NULL, NULL);
}
}
TextInputClient* TSFBridgeDelegate::GetFocusedTextInputClient() const {
return client_;
}
base::win::ScopedComPtr<ITfThreadMgr> TSFBridgeDelegate::GetThreadManager() {
DCHECK(base::MessageLoopForUI::IsCurrent());
DCHECK(IsInitialized());
return thread_manager_;
}
bool TSFBridgeDelegate::CreateDocumentManager(TSFTextStore* text_store,
ITfDocumentMgr** document_manager,
ITfContext** context,
DWORD* source_cookie) {
if (FAILED(thread_manager_->CreateDocumentMgr(document_manager))) {
DVLOG(1) << "Failed to create Document Manager.";
return false;
}
DWORD edit_cookie = TF_INVALID_EDIT_COOKIE;
if (FAILED((*document_manager)->CreateContext(
client_id_,
0,
static_cast<ITextStoreACP*>(text_store),
context,
&edit_cookie))) {
DVLOG(1) << "Failed to create Context.";
return false;
}
if (FAILED((*document_manager)->Push(*context))) {
DVLOG(1) << "Failed to push context.";
return false;
}
if (!text_store || !source_cookie)
return true;
base::win::ScopedComPtr<ITfSource> source;
if (FAILED(source.QueryFrom(*context))) {
DVLOG(1) << "Failed to get source.";
return false;
}
if (FAILED(source->AdviseSink(IID_ITfTextEditSink,
static_cast<ITfTextEditSink*>(text_store),
source_cookie))) {
DVLOG(1) << "AdviseSink failed.";
return false;
}
if (*source_cookie == TF_INVALID_COOKIE) {
DVLOG(1) << "The result of cookie is invalid.";
return false;
}
return true;
}
bool TSFBridgeDelegate::InitializeDocumentMapInternal() {
const TextInputType kTextInputTypes[] = {
TEXT_INPUT_TYPE_NONE,
TEXT_INPUT_TYPE_TEXT,
TEXT_INPUT_TYPE_PASSWORD,
TEXT_INPUT_TYPE_SEARCH,
TEXT_INPUT_TYPE_EMAIL,
TEXT_INPUT_TYPE_NUMBER,
TEXT_INPUT_TYPE_TELEPHONE,
TEXT_INPUT_TYPE_URL,
};
for (size_t i = 0; i < arraysize(kTextInputTypes); ++i) {
const TextInputType input_type = kTextInputTypes[i];
base::win::ScopedComPtr<ITfContext> context;
base::win::ScopedComPtr<ITfDocumentMgr> document_manager;
DWORD cookie = TF_INVALID_COOKIE;
const bool use_null_text_store = (input_type == TEXT_INPUT_TYPE_NONE);
DWORD* cookie_ptr = use_null_text_store ? NULL : &cookie;
scoped_refptr<TSFTextStore> text_store =
use_null_text_store ? NULL : new TSFTextStore();
if (!CreateDocumentManager(text_store,
document_manager.Receive(),
context.Receive(),
cookie_ptr))
return false;
const bool use_disabled_context =
(input_type == TEXT_INPUT_TYPE_PASSWORD ||
input_type == TEXT_INPUT_TYPE_NONE);
if (use_disabled_context && !InitializeDisabledContext(context))
return false;
tsf_document_map_[input_type].text_store = text_store;
tsf_document_map_[input_type].document_manager = document_manager;
tsf_document_map_[input_type].cookie = cookie;
}
return true;
}
bool TSFBridgeDelegate::InitializeDisabledContext(ITfContext* context) {
base::win::ScopedComPtr<ITfCompartmentMgr> compartment_mgr;
if (FAILED(compartment_mgr.QueryFrom(context))) {
DVLOG(1) << "Failed to get CompartmentMgr.";
return false;
}
base::win::ScopedComPtr<ITfCompartment> disabled_compartment;
if (FAILED(compartment_mgr->GetCompartment(
GUID_COMPARTMENT_KEYBOARD_DISABLED,
disabled_compartment.Receive()))) {
DVLOG(1) << "Failed to get keyboard disabled compartment.";
return false;
}
base::win::ScopedVariant variant;
variant.Set(1);
if (FAILED(disabled_compartment->SetValue(client_id_, &variant))) {
DVLOG(1) << "Failed to disable the DocumentMgr.";
return false;
}
base::win::ScopedComPtr<ITfCompartment> empty_context;
if (FAILED(compartment_mgr->GetCompartment(GUID_COMPARTMENT_EMPTYCONTEXT,
empty_context.Receive()))) {
DVLOG(1) << "Failed to get empty context compartment.";
return false;
}
base::win::ScopedVariant empty_context_variant;
empty_context_variant.Set(static_cast<int32>(1));
if (FAILED(empty_context->SetValue(client_id_, &empty_context_variant))) {
DVLOG(1) << "Failed to set empty context.";
return false;
}
return true;
}
bool TSFBridgeDelegate::IsFocused(ITfDocumentMgr* document_manager) {
base::win::ScopedComPtr<ITfDocumentMgr> focused_document_manager;
if (FAILED(thread_manager_->GetFocus(focused_document_manager.Receive())))
return false;
return focused_document_manager.IsSameObject(document_manager);
}
bool TSFBridgeDelegate::IsInitialized() {
return client_id_ != TF_CLIENTID_NULL;
}
void TSFBridgeDelegate::UpdateAssociateFocus() {
if (attached_window_handle_ == NULL)
return;
TSFDocument* document = GetAssociatedDocument();
if (document == NULL) {
ClearAssociateFocus();
return;
}
// NOTE: ITfThreadMgr::AssociateFocus does not increment the ref count of
// the document manager to be attached. It is our responsibility to make sure
// the attached document manager will not be destroyed while it is attached.
// This should be true as long as TSFBridge::Shutdown() is called late phase
// of UI thread shutdown.
base::win::ScopedComPtr<ITfDocumentMgr> previous_focus;
thread_manager_->AssociateFocus(
attached_window_handle_, document->document_manager.get(),
previous_focus.Receive());
}
void TSFBridgeDelegate::ClearAssociateFocus() {
if (attached_window_handle_ == NULL)
return;
base::win::ScopedComPtr<ITfDocumentMgr> previous_focus;
thread_manager_->AssociateFocus(
attached_window_handle_, NULL, previous_focus.Receive());
}
TSFBridgeDelegate::TSFDocument* TSFBridgeDelegate::GetAssociatedDocument() {
if (!client_)
return NULL;
TSFDocumentMap::iterator it =
tsf_document_map_.find(client_->GetTextInputType());
if (it == tsf_document_map_.end()) {
it = tsf_document_map_.find(TEXT_INPUT_TYPE_TEXT);
// This check is necessary because it's possible that we failed to
// initialize |tsf_document_map_| and it has no TEXT_INPUT_TYPE_TEXT.
if (it == tsf_document_map_.end())
return NULL;
}
return &it->second;
}
} // namespace
// TsfBridge -----------------------------------------------------------------
TSFBridge::TSFBridge() {
}
TSFBridge::~TSFBridge() {
}
// static
bool TSFBridge::Initialize() {
if (!base::MessageLoopForUI::IsCurrent()) {
DVLOG(1) << "Do not use TSFBridge without UI thread.";
return false;
}
if (!tls_tsf_bridge.initialized()) {
tls_tsf_bridge.Initialize(TSFBridge::Finalize);
}
TSFBridgeDelegate* delegate =
static_cast<TSFBridgeDelegate*>(tls_tsf_bridge.Get());
if (delegate)
return true;
delegate = new TSFBridgeDelegate();
tls_tsf_bridge.Set(delegate);
return delegate->Initialize();
}
// static
TSFBridge* TSFBridge::ReplaceForTesting(TSFBridge* bridge) {
if (!base::MessageLoopForUI::IsCurrent()) {
DVLOG(1) << "Do not use TSFBridge without UI thread.";
return NULL;
}
TSFBridge* old_bridge = TSFBridge::GetInstance();
tls_tsf_bridge.Set(bridge);
return old_bridge;
}
// static
void TSFBridge::Shutdown() {
if (!base::MessageLoopForUI::IsCurrent()) {
DVLOG(1) << "Do not use TSFBridge without UI thread.";
}
if (tls_tsf_bridge.initialized()) {
TSFBridgeDelegate* delegate =
static_cast<TSFBridgeDelegate*>(tls_tsf_bridge.Get());
tls_tsf_bridge.Set(NULL);
delete delegate;
}
}
// static
TSFBridge* TSFBridge::GetInstance() {
if (!base::MessageLoopForUI::IsCurrent()) {
DVLOG(1) << "Do not use TSFBridge without UI thread.";
return NULL;
}
TSFBridgeDelegate* delegate =
static_cast<TSFBridgeDelegate*>(tls_tsf_bridge.Get());
DCHECK(delegate) << "Do no call GetInstance before TSFBridge::Initialize.";
return delegate;
}
// static
void TSFBridge::Finalize(void* data) {
TSFBridgeDelegate* delegate = static_cast<TSFBridgeDelegate*>(data);
delete delegate;
}
} // namespace ui
// Copyright (c) 2012 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 UI_BASE_IME_WIN_TSF_BRIDGE_H_
#define UI_BASE_IME_WIN_TSF_BRIDGE_H_
#include <msctf.h>
#include <windows.h>
#include "base/basictypes.h"
#include "base/win/scoped_comptr.h"
#include "ui/base/ui_base_export.h"
namespace ui {
class TextInputClient;
// TSFBridge provides high level IME related operations on top of Text Services
// Framework (TSF). TSFBridge is managed by TLS because TSF related stuff is
// associated with each thread and not allowed to access across thread boundary.
// To be consistent with IMM32 behavior, TSFBridge is shared in the same thread.
// TSFBridge is used by the web content text inputting field, for example
// DisableIME() should be called if a password field is focused.
//
// TSFBridge also manages connectivity between TSFTextStore which is the backend
// of text inputting and current focused TextInputClient.
//
// All methods in this class must be used in UI thread.
class UI_BASE_EXPORT TSFBridge {
public:
virtual ~TSFBridge();
// Returns the thread local TSFBridge instance. Initialize() must be called
// first. Do not cache this pointer and use it after TSFBridge Shutdown().
static TSFBridge* GetInstance();
// Sets the thread local instance. Must be called before any calls to
// GetInstance().
static bool Initialize();
// Injects an alternative TSFBridge such as MockTSFBridge for testing. The
// injected object should be released by the caller. This function returns
// previous TSFBridge pointer with ownership.
static TSFBridge* ReplaceForTesting(TSFBridge* bridge);
// Destroys the thread local instance.
static void Shutdown();
// Handles TextInputTypeChanged event. RWHVW is responsible for calling this
// handler whenever renderer's input text type is changed. Does nothing
// unless |client| is focused.
virtual void OnTextInputTypeChanged(const TextInputClient* client) = 0;
// Sends an event to TSF manager that the text layout should be updated.
virtual void OnTextLayoutChanged() = 0;
// Cancels the ongoing composition if exists.
// Returns true if there is no composition.
// Returns false if an edit session is on-going.
// Returns false if an error occures.
virtual bool CancelComposition() = 0;
// Confirms the ongoing composition if exists.
// Returns true if there is no composition.
// Returns false if an edit session is on-going.
// Returns false if an error occures.
virtual bool ConfirmComposition() = 0;
// Sets currently focused TextInputClient.
// Caller must free |client|.
virtual void SetFocusedClient(HWND focused_window,
TextInputClient* client) = 0;
// Removes currently focused TextInputClient.
// Caller must free |client|.
virtual void RemoveFocusedClient(TextInputClient* client) = 0;
// Obtains current thread manager.
virtual base::win::ScopedComPtr<ITfThreadMgr> GetThreadManager() = 0;
// Returns the focused text input client.
virtual TextInputClient* GetFocusedTextInputClient() const = 0;
protected:
// Uses GetInstance() instead.
TSFBridge();
private:
// Releases TLS instance.
static void Finalize(void* data);
DISALLOW_COPY_AND_ASSIGN(TSFBridge);
};
} // namespace ui
#endif // UI_BASE_IME_WIN_TSF_BRIDGE_H_
// Copyright (c) 2012 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.
#include "ui/base/ime/win/tsf_event_router.h"
#include <msctf.h>
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/win/scoped_comptr.h"
#include "base/win/metro.h"
#include "ui/base/win/atl_module.h"
#include "ui/gfx/range/range.h"
namespace ui {
// TSFEventRouter::Delegate ------------------------------------
// The implementation class of ITfUIElementSink, whose member functions will be
// called back by TSF when the UI element status is changed, for example when
// the candidate window is opened or closed. This class also implements
// ITfTextEditSink, whose member function is called back by TSF when the text
// editting session is finished.
class ATL_NO_VTABLE TSFEventRouter::Delegate
: public ATL::CComObjectRootEx<CComSingleThreadModel>,
public ITfUIElementSink,
public ITfTextEditSink {
public:
BEGIN_COM_MAP(Delegate)
COM_INTERFACE_ENTRY(ITfUIElementSink)
COM_INTERFACE_ENTRY(ITfTextEditSink)
END_COM_MAP()
Delegate();
~Delegate();
// ITfTextEditSink:
STDMETHOD(OnEndEdit)(ITfContext* context, TfEditCookie read_only_cookie,
ITfEditRecord* edit_record) OVERRIDE;
// ITfUiElementSink:
STDMETHOD(BeginUIElement)(DWORD element_id, BOOL* is_show) OVERRIDE;
STDMETHOD(UpdateUIElement)(DWORD element_id) OVERRIDE;
STDMETHOD(EndUIElement)(DWORD element_id) OVERRIDE;
// Sets |thread_manager| to be monitored. |thread_manager| can be NULL.
void SetManager(ITfThreadMgr* thread_manager);
// Returns true if the IME is composing text.
bool IsImeComposing();
// Sets |router| to be forwarded TSF-related events.
void SetRouter(TSFEventRouter* router);
private:
// Returns current composition range. Returns gfx::Range::InvalidRange if
// there is no composition.
static gfx::Range GetCompositionRange(ITfContext* context);
// Returns true if the given |element_id| represents the candidate window.
bool IsCandidateWindowInternal(DWORD element_id);
// A context associated with this class.
base::win::ScopedComPtr<ITfContext> context_;
// The ITfSource associated with |context_|.
base::win::ScopedComPtr<ITfSource> context_source_;
// The cookie for |context_source_|.
DWORD context_source_cookie_;
// A UIElementMgr associated with this class.
base::win::ScopedComPtr<ITfUIElementMgr> ui_element_manager_;
// The ITfSouce associated with |ui_element_manager_|.
base::win::ScopedComPtr<ITfSource> ui_source_;
// The set of currently opened candidate window ids.
std::set<DWORD> open_candidate_window_ids_;
// The cookie for |ui_source_|.
DWORD ui_source_cookie_;
TSFEventRouter* router_;
gfx::Range previous_composition_range_;
DISALLOW_COPY_AND_ASSIGN(Delegate);
};
TSFEventRouter::Delegate::Delegate()
: context_source_cookie_(TF_INVALID_COOKIE),
ui_source_cookie_(TF_INVALID_COOKIE),
router_(NULL),
previous_composition_range_(gfx::Range::InvalidRange()) {
}
TSFEventRouter::Delegate::~Delegate() {}
void TSFEventRouter::Delegate::SetRouter(TSFEventRouter* router) {
router_ = router;
}
STDMETHODIMP TSFEventRouter::Delegate::OnEndEdit(ITfContext* context,
TfEditCookie read_only_cookie,
ITfEditRecord* edit_record) {
if (!edit_record || !context)
return E_INVALIDARG;
if (!router_)
return S_OK;
// |edit_record| can be used to obtain updated ranges in terms of text
// contents and/or text attributes. Here we are interested only in text update
// so we use TF_GTP_INCL_TEXT and check if there is any range which contains
// updated text.
base::win::ScopedComPtr<IEnumTfRanges> ranges;
if (FAILED(edit_record->GetTextAndPropertyUpdates(TF_GTP_INCL_TEXT, NULL, 0,
ranges.Receive())))
return S_OK; // Don't care about failures.
ULONG fetched_count = 0;
base::win::ScopedComPtr<ITfRange> range;
if (FAILED(ranges->Next(1, range.Receive(), &fetched_count)))
return S_OK; // Don't care about failures.
const gfx::Range composition_range = GetCompositionRange(context);
if (!previous_composition_range_.IsValid() && composition_range.IsValid())
router_->OnTSFStartComposition();
// |fetched_count| != 0 means there is at least one range that contains
// updated text.
if (fetched_count != 0)
router_->OnTextUpdated(composition_range);
if (previous_composition_range_.IsValid() && !composition_range.IsValid())
router_->OnTSFEndComposition();
previous_composition_range_ = composition_range;
return S_OK;
}
STDMETHODIMP TSFEventRouter::Delegate::BeginUIElement(DWORD element_id,
BOOL* is_show) {
if (is_show)
*is_show = TRUE; // Without this the UI element will not be shown.
if (!IsCandidateWindowInternal(element_id))
return S_OK;
std::pair<std::set<DWORD>::iterator, bool> insert_result =
open_candidate_window_ids_.insert(element_id);
// Don't call if |router_| is null or |element_id| is already handled.
if (router_ && insert_result.second)
router_->OnCandidateWindowCountChanged(open_candidate_window_ids_.size());
return S_OK;
}
STDMETHODIMP TSFEventRouter::Delegate::UpdateUIElement(
DWORD element_id) {
return S_OK;
}
STDMETHODIMP TSFEventRouter::Delegate::EndUIElement(
DWORD element_id) {
if ((open_candidate_window_ids_.erase(element_id) != 0) && router_)
router_->OnCandidateWindowCountChanged(open_candidate_window_ids_.size());
return S_OK;
}
void TSFEventRouter::Delegate::SetManager(
ITfThreadMgr* thread_manager) {
context_.Release();
if (context_source_) {
context_source_->UnadviseSink(context_source_cookie_);
context_source_.Release();
}
context_source_cookie_ = TF_INVALID_COOKIE;
ui_element_manager_.Release();
if (ui_source_) {
ui_source_->UnadviseSink(ui_source_cookie_);
ui_source_.Release();
}
ui_source_cookie_ = TF_INVALID_COOKIE;
if (!thread_manager)
return;
base::win::ScopedComPtr<ITfDocumentMgr> document_manager;
if (FAILED(thread_manager->GetFocus(document_manager.Receive())) ||
!document_manager.get() ||
FAILED(document_manager->GetBase(context_.Receive())) ||
FAILED(context_source_.QueryFrom(context_)))
return;
context_source_->AdviseSink(IID_ITfTextEditSink,
static_cast<ITfTextEditSink*>(this),
&context_source_cookie_);
if (FAILED(ui_element_manager_.QueryFrom(thread_manager)) ||
FAILED(ui_source_.QueryFrom(ui_element_manager_)))
return;
ui_source_->AdviseSink(IID_ITfUIElementSink,
static_cast<ITfUIElementSink*>(this),
&ui_source_cookie_);
}
bool TSFEventRouter::Delegate::IsImeComposing() {
return context_ && GetCompositionRange(context_).IsValid();
}
// static
gfx::Range TSFEventRouter::Delegate::GetCompositionRange(
ITfContext* context) {
DCHECK(context);
base::win::ScopedComPtr<ITfContextComposition> context_composition;
if (FAILED(context_composition.QueryFrom(context)))
return gfx::Range::InvalidRange();
base::win::ScopedComPtr<IEnumITfCompositionView> enum_composition_view;
if (FAILED(context_composition->EnumCompositions(
enum_composition_view.Receive())))
return gfx::Range::InvalidRange();
base::win::ScopedComPtr<ITfCompositionView> composition_view;
if (enum_composition_view->Next(1, composition_view.Receive(),
NULL) != S_OK)
return gfx::Range::InvalidRange();
base::win::ScopedComPtr<ITfRange> range;
if (FAILED(composition_view->GetRange(range.Receive())))
return gfx::Range::InvalidRange();
base::win::ScopedComPtr<ITfRangeACP> range_acp;
if (FAILED(range_acp.QueryFrom(range)))
return gfx::Range::InvalidRange();
LONG start = 0;
LONG length = 0;
if (FAILED(range_acp->GetExtent(&start, &length)))
return gfx::Range::InvalidRange();
return gfx::Range(start, start + length);
}
bool TSFEventRouter::Delegate::IsCandidateWindowInternal(DWORD element_id) {
DCHECK(ui_element_manager_.get());
base::win::ScopedComPtr<ITfUIElement> ui_element;
if (FAILED(ui_element_manager_->GetUIElement(element_id,
ui_element.Receive())))
return false;
base::win::ScopedComPtr<ITfCandidateListUIElement> candidate_list_ui_element;
return SUCCEEDED(candidate_list_ui_element.QueryFrom(ui_element));
}
// TSFEventRouter ------------------------------------------------------------
TSFEventRouter::TSFEventRouter(TSFEventRouterObserver* observer)
: observer_(observer),
delegate_(NULL) {
DCHECK(base::win::IsTSFAwareRequired())
<< "Do not use TSFEventRouter without TSF environment.";
DCHECK(observer_);
CComObject<Delegate>* delegate;
ui::win::CreateATLModuleIfNeeded();
if (SUCCEEDED(CComObject<Delegate>::CreateInstance(&delegate))) {
delegate->AddRef();
delegate_.Attach(delegate);
delegate_->SetRouter(this);
}
}
TSFEventRouter::~TSFEventRouter() {
if (delegate_) {
delegate_->SetManager(NULL);
delegate_->SetRouter(NULL);
}
}
bool TSFEventRouter::IsImeComposing() {
return delegate_->IsImeComposing();
}
void TSFEventRouter::OnCandidateWindowCountChanged(size_t window_count) {
observer_->OnCandidateWindowCountChanged(window_count);
}
void TSFEventRouter::OnTSFStartComposition() {
observer_->OnTSFStartComposition();
}
void TSFEventRouter::OnTextUpdated(const gfx::Range& composition_range) {
observer_->OnTextUpdated(composition_range);
}
void TSFEventRouter::OnTSFEndComposition() {
observer_->OnTSFEndComposition();
}
void TSFEventRouter::SetManager(ITfThreadMgr* thread_manager) {
delegate_->SetManager(thread_manager);
}
} // namespace ui
// Copyright (c) 2012 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 UI_BASE_IME_WIN_TSF_EVENT_ROUTER_H_
#define UI_BASE_IME_WIN_TSF_EVENT_ROUTER_H_
#include <atlbase.h>
#include <atlcom.h>
#include <msctf.h>
#include <set>
#include "base/basictypes.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "ui/base/ime/text_input_type.h"
#include "ui/base/ui_base_export.h"
#include "ui/gfx/range/range.h"
struct ITfDocumentMgr;
namespace ui {
class TSFEventRouterObserver {
public:
TSFEventRouterObserver() {}
// Called when the number of currently opened candidate windows changes.
virtual void OnCandidateWindowCountChanged(size_t window_count) {}
// Called when a composition is started.
virtual void OnTSFStartComposition() {}
// Called when the text contents are updated. If there is no composition,
// gfx::Range::InvalidRange is passed to |composition_range|.
virtual void OnTextUpdated(const gfx::Range& composition_range) {}
// Called when a composition is terminated.
virtual void OnTSFEndComposition() {}
protected:
virtual ~TSFEventRouterObserver() {}
private:
DISALLOW_COPY_AND_ASSIGN(TSFEventRouterObserver);
};
// This class monitores TSF related events and forwards them to given
// |observer|.
class UI_BASE_EXPORT TSFEventRouter {
public:
// Do not pass NULL to |observer|.
explicit TSFEventRouter(TSFEventRouterObserver* observer);
virtual ~TSFEventRouter();
// Returns true if the IME is composing text.
bool IsImeComposing();
// Callbacks from the TSFEventRouterDelegate:
void OnCandidateWindowCountChanged(size_t window_count);
void OnTSFStartComposition();
void OnTextUpdated(const gfx::Range& composition_range);
void OnTSFEndComposition();
// Sets |thread_manager| to be monitored. |thread_manager| can be NULL.
void SetManager(ITfThreadMgr* thread_manager);
private:
class Delegate;
CComPtr<Delegate> delegate_;
TSFEventRouterObserver* observer_;
DISALLOW_COPY_AND_ASSIGN(TSFEventRouter);
};
} // namespace ui
#endif // UI_BASE_IME_WIN_TSF_EVENT_ROUTER_H_
// Copyright (c) 2012 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.
#define INITGUID // required for GUID_PROP_INPUTSCOPE
#include "ui/base/ime/win/tsf_text_store.h"
#include <InputScope.h>
#include <OleCtl.h>
#include <algorithm>
#include "base/win/scoped_variant.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/ime/win/tsf_input_scope.h"
#include "ui/gfx/rect.h"
namespace ui {
namespace {
// We support only one view.
const TsViewCookie kViewCookie = 1;
} // namespace
TSFTextStore::TSFTextStore()
: ref_count_(0),
text_store_acp_sink_mask_(0),
window_handle_(NULL),
text_input_client_(NULL),
committed_size_(0),
edit_flag_(false),
current_lock_type_(0) {
if (FAILED(category_manager_.CreateInstance(CLSID_TF_CategoryMgr))) {
LOG(FATAL) << "Failed to initialize CategoryMgr.";
return;
}
if (FAILED(display_attribute_manager_.CreateInstance(
CLSID_TF_DisplayAttributeMgr))) {
LOG(FATAL) << "Failed to initialize DisplayAttributeMgr.";
return;
}
}
TSFTextStore::~TSFTextStore() {
}
ULONG STDMETHODCALLTYPE TSFTextStore::AddRef() {
return InterlockedIncrement(&ref_count_);
}
ULONG STDMETHODCALLTYPE TSFTextStore::Release() {
const LONG count = InterlockedDecrement(&ref_count_);
if (!count) {
delete this;
return 0;
}
return static_cast<ULONG>(count);
}
STDMETHODIMP TSFTextStore::QueryInterface(REFIID iid, void** result) {
if (iid == IID_IUnknown || iid == IID_ITextStoreACP) {
*result = static_cast<ITextStoreACP*>(this);
} else if (iid == IID_ITfContextOwnerCompositionSink) {
*result = static_cast<ITfContextOwnerCompositionSink*>(this);
} else if (iid == IID_ITfTextEditSink) {
*result = static_cast<ITfTextEditSink*>(this);
} else {
*result = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
STDMETHODIMP TSFTextStore::AdviseSink(REFIID iid,
IUnknown* unknown,
DWORD mask) {
if (!IsEqualGUID(iid, IID_ITextStoreACPSink))
return E_INVALIDARG;
if (text_store_acp_sink_) {
if (text_store_acp_sink_.IsSameObject(unknown)) {
text_store_acp_sink_mask_ = mask;
return S_OK;
} else {
return CONNECT_E_ADVISELIMIT;
}
}
if (FAILED(text_store_acp_sink_.QueryFrom(unknown)))
return E_UNEXPECTED;
text_store_acp_sink_mask_ = mask;
return S_OK;
}
STDMETHODIMP TSFTextStore::FindNextAttrTransition(
LONG acp_start,
LONG acp_halt,
ULONG num_filter_attributes,
const TS_ATTRID* filter_attributes,
DWORD flags,
LONG* acp_next,
BOOL* found,
LONG* found_offset) {
if (!acp_next || !found || !found_offset)
return E_INVALIDARG;
// We don't support any attributes.
// So we always return "not found".
*acp_next = 0;
*found = FALSE;
*found_offset = 0;
return S_OK;
}
STDMETHODIMP TSFTextStore::GetACPFromPoint(TsViewCookie view_cookie,
const POINT* point,
DWORD flags,
LONG* acp) {
NOTIMPLEMENTED();
if (view_cookie != kViewCookie)
return E_INVALIDARG;
return E_NOTIMPL;
}
STDMETHODIMP TSFTextStore::GetActiveView(TsViewCookie* view_cookie) {
if (!view_cookie)
return E_INVALIDARG;
// We support only one view.
*view_cookie = kViewCookie;
return S_OK;
}
STDMETHODIMP TSFTextStore::GetEmbedded(LONG acp_pos,
REFGUID service,
REFIID iid,
IUnknown** unknown) {
// We don't support any embedded objects.
NOTIMPLEMENTED();
if (!unknown)
return E_INVALIDARG;
*unknown = NULL;
return E_NOTIMPL;
}
STDMETHODIMP TSFTextStore::GetEndACP(LONG* acp) {
if (!acp)
return E_INVALIDARG;
if (!HasReadLock())
return TS_E_NOLOCK;
*acp = string_buffer_.size();
return S_OK;
}
STDMETHODIMP TSFTextStore::GetFormattedText(LONG acp_start, LONG acp_end,
IDataObject** data_object) {
NOTIMPLEMENTED();
return E_NOTIMPL;
}
STDMETHODIMP TSFTextStore::GetScreenExt(TsViewCookie view_cookie, RECT* rect) {
if (view_cookie != kViewCookie)
return E_INVALIDARG;
if (!rect)
return E_INVALIDARG;
// {0, 0, 0, 0} means that the document rect is not currently displayed.
SetRect(rect, 0, 0, 0, 0);
if (!IsWindow(window_handle_))
return E_FAIL;
// Currently ui::TextInputClient does not expose the document rect. So use
// the Win32 client rectangle instead.
// TODO(yukawa): Upgrade TextInputClient so that the client can retrieve the
// document rectangle.
RECT client_rect = {};
if (!GetClientRect(window_handle_, &client_rect))
return E_FAIL;
POINT left_top = {client_rect.left, client_rect.top};
POINT right_bottom = {client_rect.right, client_rect.bottom};
if (!ClientToScreen(window_handle_, &left_top))
return E_FAIL;
if (!ClientToScreen(window_handle_, &right_bottom))
return E_FAIL;
rect->left = left_top.x;
rect->top = left_top.y;
rect->right = right_bottom.x;
rect->bottom = right_bottom.y;
return S_OK;
}
STDMETHODIMP TSFTextStore::GetSelection(ULONG selection_index,
ULONG selection_buffer_size,
TS_SELECTION_ACP* selection_buffer,
ULONG* fetched_count) {
if (!selection_buffer)
return E_INVALIDARG;
if (!fetched_count)
return E_INVALIDARG;
if (!HasReadLock())
return TS_E_NOLOCK;
*fetched_count = 0;
if ((selection_buffer_size > 0) &&
((selection_index == 0) || (selection_index == TS_DEFAULT_SELECTION))) {
selection_buffer[0].acpStart = selection_.start();
selection_buffer[0].acpEnd = selection_.end();
selection_buffer[0].style.ase = TS_AE_END;
selection_buffer[0].style.fInterimChar = FALSE;
*fetched_count = 1;
}
return S_OK;
}
STDMETHODIMP TSFTextStore::GetStatus(TS_STATUS* status) {
if (!status)
return E_INVALIDARG;
status->dwDynamicFlags = 0;
// We use transitory contexts and we don't support hidden text.
status->dwStaticFlags = TS_SS_TRANSITORY | TS_SS_NOHIDDENTEXT;
return S_OK;
}
STDMETHODIMP TSFTextStore::GetText(LONG acp_start,
LONG acp_end,
wchar_t* text_buffer,
ULONG text_buffer_size,
ULONG* text_buffer_copied,
TS_RUNINFO* run_info_buffer,
ULONG run_info_buffer_size,
ULONG* run_info_buffer_copied,
LONG* next_acp) {
if (!text_buffer_copied || !run_info_buffer_copied)
return E_INVALIDARG;
if (!text_buffer && text_buffer_size != 0)
return E_INVALIDARG;
if (!run_info_buffer && run_info_buffer_size != 0)
return E_INVALIDARG;
if (!next_acp)
return E_INVALIDARG;
if (!HasReadLock())
return TF_E_NOLOCK;
const LONG string_buffer_size = string_buffer_.size();
if (acp_end == -1)
acp_end = string_buffer_size;
if (!((0 <= acp_start) &&
(acp_start <= acp_end) &&
(acp_end <= string_buffer_size))) {
return TF_E_INVALIDPOS;
}
acp_end = std::min(acp_end, acp_start + static_cast<LONG>(text_buffer_size));
*text_buffer_copied = acp_end - acp_start;
const base::string16& result =
string_buffer_.substr(acp_start, *text_buffer_copied);
for (size_t i = 0; i < result.size(); ++i) {
text_buffer[i] = result[i];
}
if (run_info_buffer_size) {
run_info_buffer[0].uCount = *text_buffer_copied;
run_info_buffer[0].type = TS_RT_PLAIN;
*run_info_buffer_copied = 1;
}
*next_acp = acp_end;
return S_OK;
}
STDMETHODIMP TSFTextStore::GetTextExt(TsViewCookie view_cookie,
LONG acp_start,
LONG acp_end,
RECT* rect,
BOOL* clipped) {
if (!rect || !clipped)
return E_INVALIDARG;
if (!text_input_client_)
return E_UNEXPECTED;
if (view_cookie != kViewCookie)
return E_INVALIDARG;
if (!HasReadLock())
return TS_E_NOLOCK;
if (!((static_cast<LONG>(committed_size_) <= acp_start) &&
(acp_start <= acp_end) &&
(acp_end <= static_cast<LONG>(string_buffer_.size())))) {
return TS_E_INVALIDPOS;
}
// According to a behavior of notepad.exe and wordpad.exe, top left corner of
// rect indicates a first character's one, and bottom right corner of rect
// indicates a last character's one.
// We use RECT instead of gfx::Rect since left position may be bigger than
// right position when composition has multiple lines.
RECT result;
gfx::Rect tmp_rect;
const uint32 start_pos = acp_start - committed_size_;
const uint32 end_pos = acp_end - committed_size_;
if (start_pos == end_pos) {
// According to MSDN document, if |acp_start| and |acp_end| are equal it is
// OK to just return E_INVALIDARG.
// http://msdn.microsoft.com/en-us/library/ms538435
// But when using Pinin IME of Windows 8, this method is called with the
// equal values of |acp_start| and |acp_end|. So we handle this condition.
if (start_pos == 0) {
if (text_input_client_->GetCompositionCharacterBounds(0, &tmp_rect)) {
tmp_rect.set_width(0);
result = tmp_rect.ToRECT();
} else if (string_buffer_.size() == committed_size_) {
result = text_input_client_->GetCaretBounds().ToRECT();
} else {
return TS_E_NOLAYOUT;
}
} else if (text_input_client_->GetCompositionCharacterBounds(start_pos - 1,
&tmp_rect)) {
result.left = tmp_rect.right();
result.right = tmp_rect.right();
result.top = tmp_rect.y();
result.bottom = tmp_rect.bottom();
} else {
return TS_E_NOLAYOUT;
}
} else {
if (text_input_client_->GetCompositionCharacterBounds(start_pos,
&tmp_rect)) {
result.left = tmp_rect.x();
result.top = tmp_rect.y();
result.right = tmp_rect.right();
result.bottom = tmp_rect.bottom();
if (text_input_client_->GetCompositionCharacterBounds(end_pos - 1,
&tmp_rect)) {
result.right = tmp_rect.right();
result.bottom = tmp_rect.bottom();
} else {
// We may not be able to get the last character bounds, so we use the
// first character bounds instead of returning TS_E_NOLAYOUT.
}
} else {
// Hack for PPAPI flash. PPAPI flash does not support GetCaretBounds, so
// it's better to return previous caret rectangle instead.
// TODO(nona, kinaba): Remove this hack.
if (start_pos == 0) {
result = text_input_client_->GetCaretBounds().ToRECT();
} else {
return TS_E_NOLAYOUT;
}
}
}
*rect = result;
*clipped = FALSE;
return S_OK;
}
STDMETHODIMP TSFTextStore::GetWnd(TsViewCookie view_cookie,
HWND* window_handle) {
if (!window_handle)
return E_INVALIDARG;
if (view_cookie != kViewCookie)
return E_INVALIDARG;
*window_handle = window_handle_;
return S_OK;
}
STDMETHODIMP TSFTextStore::InsertEmbedded(DWORD flags,
LONG acp_start,
LONG acp_end,
IDataObject* data_object,
TS_TEXTCHANGE* change) {
// We don't support any embedded objects.
NOTIMPLEMENTED();
return E_NOTIMPL;
}
STDMETHODIMP TSFTextStore::InsertEmbeddedAtSelection(DWORD flags,
IDataObject* data_object,
LONG* acp_start,
LONG* acp_end,
TS_TEXTCHANGE* change) {
// We don't support any embedded objects.
NOTIMPLEMENTED();
return E_NOTIMPL;
}
STDMETHODIMP TSFTextStore::InsertTextAtSelection(DWORD flags,
const wchar_t* text_buffer,
ULONG text_buffer_size,
LONG* acp_start,
LONG* acp_end,
TS_TEXTCHANGE* text_change) {
const LONG start_pos = selection_.start();
const LONG end_pos = selection_.end();
const LONG new_end_pos = start_pos + text_buffer_size;
if (flags & TS_IAS_QUERYONLY) {
if (!HasReadLock())
return TS_E_NOLOCK;
if (acp_start)
*acp_start = start_pos;
if (acp_end) {
*acp_end = end_pos;
}
return S_OK;
}
if (!HasReadWriteLock())
return TS_E_NOLOCK;
if (!text_buffer)
return E_INVALIDARG;
DCHECK_LE(start_pos, end_pos);
string_buffer_ = string_buffer_.substr(0, start_pos) +
base::string16(text_buffer, text_buffer + text_buffer_size) +
string_buffer_.substr(end_pos);
if (acp_start)
*acp_start = start_pos;
if (acp_end)
*acp_end = new_end_pos;
if (text_change) {
text_change->acpStart = start_pos;
text_change->acpOldEnd = end_pos;
text_change->acpNewEnd = new_end_pos;
}
selection_.set_start(start_pos);
selection_.set_end(new_end_pos);
return S_OK;
}
STDMETHODIMP TSFTextStore::QueryInsert(
LONG acp_test_start,
LONG acp_test_end,
ULONG text_size,
LONG* acp_result_start,
LONG* acp_result_end) {
if (!acp_result_start || !acp_result_end || acp_test_start > acp_test_end)
return E_INVALIDARG;
const LONG committed_size = static_cast<LONG>(committed_size_);
const LONG buffer_size = static_cast<LONG>(string_buffer_.size());
*acp_result_start = std::min(std::max(committed_size, acp_test_start),
buffer_size);
*acp_result_end = std::min(std::max(committed_size, acp_test_end),
buffer_size);
return S_OK;
}
STDMETHODIMP TSFTextStore::QueryInsertEmbedded(const GUID* service,
const FORMATETC* format,
BOOL* insertable) {
if (!format)
return E_INVALIDARG;
// We don't support any embedded objects.
if (insertable)
*insertable = FALSE;
return S_OK;
}
STDMETHODIMP TSFTextStore::RequestAttrsAtPosition(
LONG acp_pos,
ULONG attribute_buffer_size,
const TS_ATTRID* attribute_buffer,
DWORD flags) {
// We don't support any document attributes.
// This method just returns S_OK, and the subsequently called
// RetrieveRequestedAttrs() returns 0 as the number of supported attributes.
return S_OK;
}
STDMETHODIMP TSFTextStore::RequestAttrsTransitioningAtPosition(
LONG acp_pos,
ULONG attribute_buffer_size,
const TS_ATTRID* attribute_buffer,
DWORD flags) {
// We don't support any document attributes.
// This method just returns S_OK, and the subsequently called
// RetrieveRequestedAttrs() returns 0 as the number of supported attributes.
return S_OK;
}
STDMETHODIMP TSFTextStore::RequestLock(DWORD lock_flags, HRESULT* result) {
if (!text_store_acp_sink_.get())
return E_FAIL;
if (!result)
return E_INVALIDARG;
if (current_lock_type_ != 0) {
if (lock_flags & TS_LF_SYNC) {
// Can't lock synchronously.
*result = TS_E_SYNCHRONOUS;
return S_OK;
}
// Queue the lock request.
lock_queue_.push_back(lock_flags & TS_LF_READWRITE);
*result = TS_S_ASYNC;
return S_OK;
}
// Lock
current_lock_type_ = (lock_flags & TS_LF_READWRITE);
edit_flag_ = false;
const size_t last_committed_size = committed_size_;
// Grant the lock.
*result = text_store_acp_sink_->OnLockGranted(current_lock_type_);
// Unlock
current_lock_type_ = 0;
// Handles the pending lock requests.
while (!lock_queue_.empty()) {
current_lock_type_ = lock_queue_.front();
lock_queue_.pop_front();
text_store_acp_sink_->OnLockGranted(current_lock_type_);
current_lock_type_ = 0;
}
if (!edit_flag_) {
return S_OK;
}
// If the text store is edited in OnLockGranted(), we may need to call
// TextInputClient::InsertText() or TextInputClient::SetCompositionText().
const size_t new_committed_size = committed_size_;
const base::string16& new_committed_string =
string_buffer_.substr(last_committed_size,
new_committed_size - last_committed_size);
const base::string16& composition_string =
string_buffer_.substr(new_committed_size);
// If there is new committed string, calls TextInputClient::InsertText().
if ((!new_committed_string.empty()) && text_input_client_) {
text_input_client_->InsertText(new_committed_string);
}
// Calls TextInputClient::SetCompositionText().
CompositionText composition_text;
composition_text.text = composition_string;
composition_text.underlines = composition_undelines_;
// Adjusts the offset.
for (size_t i = 0; i < composition_text.underlines.size(); ++i) {
composition_text.underlines[i].start_offset -= new_committed_size;
composition_text.underlines[i].end_offset -= new_committed_size;
}
if (selection_.start() < new_committed_size) {
composition_text.selection.set_start(0);
} else {
composition_text.selection.set_start(
selection_.start() - new_committed_size);
}
if (selection_.end() < new_committed_size) {
composition_text.selection.set_end(0);
} else {
composition_text.selection.set_end(selection_.end() - new_committed_size);
}
if (text_input_client_)
text_input_client_->SetCompositionText(composition_text);
// If there is no composition string, clear the text store status.
// And call OnSelectionChange(), OnLayoutChange(), and OnTextChange().
if ((composition_string.empty()) && (new_committed_size != 0)) {
string_buffer_.clear();
committed_size_ = 0;
selection_.set_start(0);
selection_.set_end(0);
if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE)
text_store_acp_sink_->OnSelectionChange();
if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE)
text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0);
if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) {
TS_TEXTCHANGE textChange;
textChange.acpStart = 0;
textChange.acpOldEnd = new_committed_size;
textChange.acpNewEnd = 0;
text_store_acp_sink_->OnTextChange(0, &textChange);
}
}
return S_OK;
}
STDMETHODIMP TSFTextStore::RequestSupportedAttrs(
DWORD /* flags */, // Seems that we should ignore this.
ULONG attribute_buffer_size,
const TS_ATTRID* attribute_buffer) {
if (!attribute_buffer)
return E_INVALIDARG;
if (!text_input_client_)
return E_FAIL;
// We support only input scope attribute.
for (size_t i = 0; i < attribute_buffer_size; ++i) {
if (IsEqualGUID(GUID_PROP_INPUTSCOPE, attribute_buffer[i]))
return S_OK;
}
return E_FAIL;
}
STDMETHODIMP TSFTextStore::RetrieveRequestedAttrs(
ULONG attribute_buffer_size,
TS_ATTRVAL* attribute_buffer,
ULONG* attribute_buffer_copied) {
if (!attribute_buffer_copied)
return E_INVALIDARG;
if (!attribute_buffer)
return E_INVALIDARG;
if (!text_input_client_)
return E_UNEXPECTED;
// We support only input scope attribute.
*attribute_buffer_copied = 0;
if (attribute_buffer_size == 0)
return S_OK;
attribute_buffer[0].dwOverlapId = 0;
attribute_buffer[0].idAttr = GUID_PROP_INPUTSCOPE;
attribute_buffer[0].varValue.vt = VT_UNKNOWN;
attribute_buffer[0].varValue.punkVal = tsf_inputscope::CreateInputScope(
text_input_client_->GetTextInputType(),
text_input_client_->GetTextInputMode());
attribute_buffer[0].varValue.punkVal->AddRef();
*attribute_buffer_copied = 1;
return S_OK;
}
STDMETHODIMP TSFTextStore::SetSelection(
ULONG selection_buffer_size,
const TS_SELECTION_ACP* selection_buffer) {
if (!HasReadWriteLock())
return TF_E_NOLOCK;
if (selection_buffer_size > 0) {
const LONG start_pos = selection_buffer[0].acpStart;
const LONG end_pos = selection_buffer[0].acpEnd;
if (!((static_cast<LONG>(committed_size_) <= start_pos) &&
(start_pos <= end_pos) &&
(end_pos <= static_cast<LONG>(string_buffer_.size())))) {
return TF_E_INVALIDPOS;
}
selection_.set_start(start_pos);
selection_.set_end(end_pos);
}
return S_OK;
}
STDMETHODIMP TSFTextStore::SetText(DWORD flags,
LONG acp_start,
LONG acp_end,
const wchar_t* text_buffer,
ULONG text_buffer_size,
TS_TEXTCHANGE* text_change) {
if (!HasReadWriteLock())
return TS_E_NOLOCK;
if (!((static_cast<LONG>(committed_size_) <= acp_start) &&
(acp_start <= acp_end) &&
(acp_end <= static_cast<LONG>(string_buffer_.size())))) {
return TS_E_INVALIDPOS;
}
TS_SELECTION_ACP selection;
selection.acpStart = acp_start;
selection.acpEnd = acp_end;
selection.style.ase = TS_AE_NONE;
selection.style.fInterimChar = 0;
HRESULT ret;
ret = SetSelection(1, &selection);
if (ret != S_OK)
return ret;
TS_TEXTCHANGE change;
ret = InsertTextAtSelection(0, text_buffer, text_buffer_size,
&acp_start, &acp_end, &change);
if (ret != S_OK)
return ret;
if (text_change)
*text_change = change;
return S_OK;
}
STDMETHODIMP TSFTextStore::UnadviseSink(IUnknown* unknown) {
if (!text_store_acp_sink_.IsSameObject(unknown))
return CONNECT_E_NOCONNECTION;
text_store_acp_sink_.Release();
text_store_acp_sink_mask_ = 0;
return S_OK;
}
STDMETHODIMP TSFTextStore::OnStartComposition(
ITfCompositionView* composition_view,
BOOL* ok) {
if (ok)
*ok = TRUE;
return S_OK;
}
STDMETHODIMP TSFTextStore::OnUpdateComposition(
ITfCompositionView* composition_view,
ITfRange* range) {
return S_OK;
}
STDMETHODIMP TSFTextStore::OnEndComposition(
ITfCompositionView* composition_view) {
return S_OK;
}
STDMETHODIMP TSFTextStore::OnEndEdit(ITfContext* context,
TfEditCookie read_only_edit_cookie,
ITfEditRecord* edit_record) {
if (!context || !edit_record)
return E_INVALIDARG;
size_t committed_size;
CompositionUnderlines undelines;
if (!GetCompositionStatus(context, read_only_edit_cookie, &committed_size,
&undelines)) {
return S_OK;
}
composition_undelines_ = undelines;
committed_size_ = committed_size;
edit_flag_ = true;
return S_OK;
}
bool TSFTextStore::GetDisplayAttribute(TfGuidAtom guid_atom,
TF_DISPLAYATTRIBUTE* attribute) {
GUID guid;
if (FAILED(category_manager_->GetGUID(guid_atom, &guid)))
return false;
base::win::ScopedComPtr<ITfDisplayAttributeInfo> display_attribute_info;
if (FAILED(display_attribute_manager_->GetDisplayAttributeInfo(
guid, display_attribute_info.Receive(), NULL))) {
return false;
}
return SUCCEEDED(display_attribute_info->GetAttributeInfo(attribute));
}
bool TSFTextStore::GetCompositionStatus(
ITfContext* context,
const TfEditCookie read_only_edit_cookie,
size_t* committed_size,
CompositionUnderlines* undelines) {
DCHECK(context);
DCHECK(committed_size);
DCHECK(undelines);
const GUID* rgGuids[2] = {&GUID_PROP_COMPOSING, &GUID_PROP_ATTRIBUTE};
base::win::ScopedComPtr<ITfReadOnlyProperty> track_property;
if (FAILED(context->TrackProperties(rgGuids, 2, NULL, 0,
track_property.Receive()))) {
return false;
}
*committed_size = 0;
undelines->clear();
base::win::ScopedComPtr<ITfRange> start_to_end_range;
base::win::ScopedComPtr<ITfRange> end_range;
if (FAILED(context->GetStart(read_only_edit_cookie,
start_to_end_range.Receive()))) {
return false;
}
if (FAILED(context->GetEnd(read_only_edit_cookie, end_range.Receive())))
return false;
if (FAILED(start_to_end_range->ShiftEndToRange(read_only_edit_cookie,
end_range, TF_ANCHOR_END))) {
return false;
}
base::win::ScopedComPtr<IEnumTfRanges> ranges;
if (FAILED(track_property->EnumRanges(read_only_edit_cookie, ranges.Receive(),
start_to_end_range))) {
return false;
}
while (true) {
base::win::ScopedComPtr<ITfRange> range;
if (ranges->Next(1, range.Receive(), NULL) != S_OK)
break;
base::win::ScopedVariant value;
base::win::ScopedComPtr<IEnumTfPropertyValue> enum_prop_value;
if (FAILED(track_property->GetValue(read_only_edit_cookie, range,
value.Receive()))) {
return false;
}
if (FAILED(enum_prop_value.QueryFrom(value.AsInput()->punkVal)))
return false;
TF_PROPERTYVAL property_value;
bool is_composition = false;
bool has_display_attribute = false;
TF_DISPLAYATTRIBUTE display_attribute;
while (enum_prop_value->Next(1, &property_value, NULL) == S_OK) {
if (IsEqualGUID(property_value.guidId, GUID_PROP_COMPOSING)) {
is_composition = (property_value.varValue.lVal == TRUE);
} else if (IsEqualGUID(property_value.guidId, GUID_PROP_ATTRIBUTE)) {
TfGuidAtom guid_atom =
static_cast<TfGuidAtom>(property_value.varValue.lVal);
if (GetDisplayAttribute(guid_atom, &display_attribute))
has_display_attribute = true;
}
VariantClear(&property_value.varValue);
}
base::win::ScopedComPtr<ITfRangeACP> range_acp;
range_acp.QueryFrom(range);
LONG start_pos, length;
range_acp->GetExtent(&start_pos, &length);
if (!is_composition) {
if (*committed_size < static_cast<size_t>(start_pos + length))
*committed_size = start_pos + length;
} else {
CompositionUnderline underline;
underline.start_offset = start_pos;
underline.end_offset = start_pos + length;
underline.color = SK_ColorBLACK;
if (has_display_attribute)
underline.thick = !!display_attribute.fBoldLine;
undelines->push_back(underline);
}
}
return true;
}
void TSFTextStore::SetFocusedTextInputClient(
HWND focused_window,
TextInputClient* text_input_client) {
window_handle_ = focused_window;
text_input_client_ = text_input_client;
}
void TSFTextStore::RemoveFocusedTextInputClient(
TextInputClient* text_input_client) {
if (text_input_client_ == text_input_client) {
window_handle_ = NULL;
text_input_client_ = NULL;
}
}
bool TSFTextStore::CancelComposition() {
// If there is an on-going document lock, we must not edit the text.
if (edit_flag_)
return false;
if (string_buffer_.empty())
return true;
// Unlike ImmNotifyIME(NI_COMPOSITIONSTR, CPS_CANCEL, 0) in IMM32, TSF does
// not have a dedicated method to cancel composition. However, CUAS actually
// has a protocol conversion from CPS_CANCEL into TSF operations. According
// to the observations on Windows 7, TIPs are expected to cancel composition
// when an on-going composition text is replaced with an empty string. So
// we use the same operation to cancel composition here to minimize the risk
// of potential compatibility issues.
const size_t previous_buffer_size = string_buffer_.size();
string_buffer_.clear();
committed_size_ = 0;
selection_.set_start(0);
selection_.set_end(0);
if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE)
text_store_acp_sink_->OnSelectionChange();
if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE)
text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0);
if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) {
TS_TEXTCHANGE textChange = {};
textChange.acpStart = 0;
textChange.acpOldEnd = previous_buffer_size;
textChange.acpNewEnd = 0;
text_store_acp_sink_->OnTextChange(0, &textChange);
}
return true;
}
bool TSFTextStore::ConfirmComposition() {
// If there is an on-going document lock, we must not edit the text.
if (edit_flag_)
return false;
if (string_buffer_.empty())
return true;
// See the comment in TSFTextStore::CancelComposition.
// This logic is based on the observation about how to emulate
// ImmNotifyIME(NI_COMPOSITIONSTR, CPS_COMPLETE, 0) by CUAS.
const base::string16& composition_text =
string_buffer_.substr(committed_size_);
if (!composition_text.empty())
text_input_client_->InsertText(composition_text);
const size_t previous_buffer_size = string_buffer_.size();
string_buffer_.clear();
committed_size_ = 0;
selection_.set_start(0);
selection_.set_end(0);
if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE)
text_store_acp_sink_->OnSelectionChange();
if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE)
text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0);
if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) {
TS_TEXTCHANGE textChange = {};
textChange.acpStart = 0;
textChange.acpOldEnd = previous_buffer_size;
textChange.acpNewEnd = 0;
text_store_acp_sink_->OnTextChange(0, &textChange);
}
return true;
}
void TSFTextStore::SendOnLayoutChange() {
if (text_store_acp_sink_ && (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE))
text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0);
}
bool TSFTextStore::HasReadLock() const {
return (current_lock_type_ & TS_LF_READ) == TS_LF_READ;
}
bool TSFTextStore::HasReadWriteLock() const {
return (current_lock_type_ & TS_LF_READWRITE) == TS_LF_READWRITE;
}
} // namespace ui
// Copyright (c) 2012 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 UI_BASE_IME_WIN_TSF_TEXT_STORE_H_
#define UI_BASE_IME_WIN_TSF_TEXT_STORE_H_
#include <msctf.h>
#include <deque>
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/strings/string16.h"
#include "base/win/scoped_comptr.h"
#include "ui/base/ime/composition_underline.h"
#include "ui/base/ui_base_export.h"
#include "ui/gfx/range/range.h"
namespace ui {
class TextInputClient;
// TSFTextStore is used to interact with the input method via TSF manager.
// TSFTextStore have a string buffer which is manipulated by TSF manager through
// ITextStoreACP interface methods such as SetText().
// When the input method updates the composition, TSFTextStore calls
// TextInputClient::SetCompositionText(). And when the input method finishes the
// composition, TSFTextStore calls TextInputClient::InsertText() and clears the
// buffer.
//
// How TSFTextStore works:
// - The user enters "a".
// - The input method set composition as "a".
// - TSF manager calls TSFTextStore::RequestLock().
// - TSFTextStore callbacks ITextStoreACPSink::OnLockGranted().
// - In OnLockGranted(), TSF manager calls
// - TSFTextStore::OnStartComposition()
// - TSFTextStore::SetText()
// The string buffer is set as "a".
// - TSFTextStore::OnUpdateComposition()
// - TSFTextStore::OnEndEdit()
// TSFTextStore can get the composition information such as underlines.
// - TSFTextStore calls TextInputClient::SetCompositionText().
// "a" is shown with an underline as composition string.
// - The user enters <space>.
// - The input method set composition as "A".
// - TSF manager calls TSFTextStore::RequestLock().
// - TSFTextStore callbacks ITextStoreACPSink::OnLockGranted().
// - In OnLockGranted(), TSF manager calls
// - TSFTextStore::SetText()
// The string buffer is set as "A".
// - TSFTextStore::OnUpdateComposition()
// - TSFTextStore::OnEndEdit()
// - TSFTextStore calls TextInputClient::SetCompositionText().
// "A" is shown with an underline as composition string.
// - The user enters <enter>.
// - The input method commits "A".
// - TSF manager calls TSFTextStore::RequestLock().
// - TSFTextStore callbacks ITextStoreACPSink::OnLockGranted().
// - In OnLockGranted(), TSF manager calls
// - TSFTextStore::OnEndComposition()
// - TSFTextStore::OnEndEdit()
// TSFTextStore knows "A" is committed.
// - TSFTextStore calls TextInputClient::InsertText().
// "A" is shown as committed string.
// - TSFTextStore clears the string buffer.
// - TSFTextStore calls OnSelectionChange(), OnLayoutChange() and
// OnTextChange() of ITextStoreACPSink to let TSF manager know that the
// string buffer has been changed.
//
// About the locking scheme:
// When TSF manager manipulates the string buffer it calls RequestLock() to get
// the lock of the document. If TSFTextStore can grant the lock request, it
// callbacks ITextStoreACPSink::OnLockGranted().
// RequestLock() is called from only one thread, but called recursively in
// OnLockGranted() or OnSelectionChange() or OnLayoutChange() or OnTextChange().
// If the document is locked and the lock request is asynchronous, TSFTextStore
// queues the request. The queued requests will be handled after the current
// lock is removed.
// More information about document locks can be found here:
// http://msdn.microsoft.com/en-us/library/ms538064
//
// More information about TSF can be found here:
// http://msdn.microsoft.com/en-us/library/ms629032
class UI_BASE_EXPORT TSFTextStore : public ITextStoreACP,
public ITfContextOwnerCompositionSink,
public ITfTextEditSink {
public:
TSFTextStore();
virtual ~TSFTextStore();
// ITextStoreACP:
STDMETHOD_(ULONG, AddRef)() OVERRIDE;
STDMETHOD_(ULONG, Release)() OVERRIDE;
STDMETHOD(QueryInterface)(REFIID iid, void** ppv) OVERRIDE;
STDMETHOD(AdviseSink)(REFIID iid, IUnknown* unknown, DWORD mask) OVERRIDE;
STDMETHOD(FindNextAttrTransition)(LONG acp_start,
LONG acp_halt,
ULONG num_filter_attributes,
const TS_ATTRID* filter_attributes,
DWORD flags,
LONG* acp_next,
BOOL* found,
LONG* found_offset) OVERRIDE;
STDMETHOD(GetACPFromPoint)(TsViewCookie view_cookie,
const POINT* point,
DWORD flags,
LONG* acp) OVERRIDE;
STDMETHOD(GetActiveView)(TsViewCookie* view_cookie) OVERRIDE;
STDMETHOD(GetEmbedded)(LONG acp_pos,
REFGUID service,
REFIID iid,
IUnknown** unknown) OVERRIDE;
STDMETHOD(GetEndACP)(LONG* acp) OVERRIDE;
STDMETHOD(GetFormattedText)(LONG acp_start,
LONG acp_end,
IDataObject** data_object) OVERRIDE;
STDMETHOD(GetScreenExt)(TsViewCookie view_cookie, RECT* rect) OVERRIDE;
STDMETHOD(GetSelection)(ULONG selection_index,
ULONG selection_buffer_size,
TS_SELECTION_ACP* selection_buffer,
ULONG* fetched_count) OVERRIDE;
STDMETHOD(GetStatus)(TS_STATUS* pdcs) OVERRIDE;
STDMETHOD(GetText)(LONG acp_start,
LONG acp_end,
wchar_t* text_buffer,
ULONG text_buffer_size,
ULONG* text_buffer_copied,
TS_RUNINFO* run_info_buffer,
ULONG run_info_buffer_size,
ULONG* run_info_buffer_copied,
LONG* next_acp) OVERRIDE;
STDMETHOD(GetTextExt)(TsViewCookie view_cookie,
LONG acp_start,
LONG acp_end,
RECT* rect,
BOOL* clipped) OVERRIDE;
STDMETHOD(GetWnd)(TsViewCookie view_cookie, HWND* window_handle) OVERRIDE;
STDMETHOD(InsertEmbedded)(DWORD flags,
LONG acp_start,
LONG acp_end,
IDataObject* data_object,
TS_TEXTCHANGE* change) OVERRIDE;
STDMETHOD(InsertEmbeddedAtSelection)(DWORD flags,
IDataObject* data_object,
LONG* acp_start,
LONG* acp_end,
TS_TEXTCHANGE* change) OVERRIDE;
STDMETHOD(InsertTextAtSelection)(DWORD flags,
const wchar_t* text_buffer,
ULONG text_buffer_size,
LONG* acp_start,
LONG* acp_end,
TS_TEXTCHANGE* text_change) OVERRIDE;
STDMETHOD(QueryInsert)(LONG acp_test_start,
LONG acp_test_end,
ULONG text_size,
LONG* acp_result_start,
LONG* acp_result_end) OVERRIDE;
STDMETHOD(QueryInsertEmbedded)(const GUID* service,
const FORMATETC* format,
BOOL* insertable) OVERRIDE;
STDMETHOD(RequestAttrsAtPosition)(LONG acp_pos,
ULONG attribute_buffer_size,
const TS_ATTRID* attribute_buffer,
DWORD flags) OVERRIDE;
STDMETHOD(RequestAttrsTransitioningAtPosition)(
LONG acp_pos,
ULONG attribute_buffer_size,
const TS_ATTRID* attribute_buffer,
DWORD flags) OVERRIDE;
STDMETHOD(RequestLock)(DWORD lock_flags, HRESULT* result) OVERRIDE;
STDMETHOD(RequestSupportedAttrs)(DWORD flags,
ULONG attribute_buffer_size,
const TS_ATTRID* attribute_buffer) OVERRIDE;
STDMETHOD(RetrieveRequestedAttrs)(ULONG attribute_buffer_size,
TS_ATTRVAL* attribute_buffer,
ULONG* attribute_buffer_copied) OVERRIDE;
STDMETHOD(SetSelection)(ULONG selection_buffer_size,
const TS_SELECTION_ACP* selection_buffer) OVERRIDE;
STDMETHOD(SetText)(DWORD flags,
LONG acp_start,
LONG acp_end,
const wchar_t* text_buffer,
ULONG text_buffer_size,
TS_TEXTCHANGE* text_change) OVERRIDE;
STDMETHOD(UnadviseSink)(IUnknown* unknown) OVERRIDE;
// ITfContextOwnerCompositionSink:
STDMETHOD(OnStartComposition)(ITfCompositionView* composition_view,
BOOL* ok) OVERRIDE;
STDMETHOD(OnUpdateComposition)(ITfCompositionView* composition_view,
ITfRange* range) OVERRIDE;
STDMETHOD(OnEndComposition)(ITfCompositionView* composition_view) OVERRIDE;
// ITfTextEditSink:
STDMETHOD(OnEndEdit)(ITfContext* context, TfEditCookie read_only_edit_cookie,
ITfEditRecord* edit_record) OVERRIDE;
// Sets currently focused TextInputClient.
void SetFocusedTextInputClient(HWND focused_window,
TextInputClient* text_input_client);
// Removes currently focused TextInputClient.
void RemoveFocusedTextInputClient(TextInputClient* text_input_client);
// Cancels the ongoing composition if exists.
bool CancelComposition();
// Confirms the ongoing composition if exists.
bool ConfirmComposition();
// Sends OnLayoutChange() via |text_store_acp_sink_|.
void SendOnLayoutChange();
private:
friend class TSFTextStoreTest;
friend class TSFTextStoreTestCallback;
// Checks if the document has a read-only lock.
bool HasReadLock() const;
// Checks if the document has a read and write lock.
bool HasReadWriteLock() const;
// Gets the display attribute structure.
bool GetDisplayAttribute(TfGuidAtom guid_atom,
TF_DISPLAYATTRIBUTE* attribute);
// Gets the committed string size and underline information of the context.
bool GetCompositionStatus(ITfContext* context,
const TfEditCookie read_only_edit_cookie,
size_t* committed_size,
CompositionUnderlines* undelines);
// The refrence count of this instance.
volatile LONG ref_count_;
// A pointer of ITextStoreACPSink, this instance is given in AdviseSink.
base::win::ScopedComPtr<ITextStoreACPSink> text_store_acp_sink_;
// The current mask of |text_store_acp_sink_|.
DWORD text_store_acp_sink_mask_;
// HWND of the current view window which is set in SetFocusedTextInputClient.
HWND window_handle_;
// Current TextInputClient which is set in SetFocusedTextInputClient.
TextInputClient* text_input_client_;
// |string_buffer_| contains committed string and composition string.
// Example: "aoi" is committed, and "umi" is under composition.
// |string_buffer_|: "aoiumi"
// |committed_size_|: 3
base::string16 string_buffer_;
size_t committed_size_;
// |selection_start_| and |selection_end_| indicates the selection range.
// Example: "iue" is selected
// |string_buffer_|: "aiueo"
// |selection_.start()|: 1
// |selection_.end()|: 4
gfx::Range selection_;
// |start_offset| and |end_offset| of |composition_undelines_| indicates
// the offsets in |string_buffer_|.
// Example: "aoi" is committed. There are two underlines in "umi" and "no".
// |string_buffer_|: "aoiumino"
// |committed_size_|: 3
// composition_undelines_.underlines[0].start_offset: 3
// composition_undelines_.underlines[0].end_offset: 6
// composition_undelines_.underlines[1].start_offset: 6
// composition_undelines_.underlines[1].end_offset: 8
CompositionUnderlines composition_undelines_;
// |edit_flag_| indicates that the status is edited during
// ITextStoreACPSink::OnLockGranted().
bool edit_flag_;
// The type of current lock.
// 0: No lock.
// TS_LF_READ: read-only lock.
// TS_LF_READWRITE: read/write lock.
DWORD current_lock_type_;
// Queue of the lock request used in RequestLock().
std::deque<DWORD> lock_queue_;
// Category manager and Display attribute manager are used to obtain the
// attributes of the composition string.
base::win::ScopedComPtr<ITfCategoryMgr> category_manager_;
base::win::ScopedComPtr<ITfDisplayAttributeMgr> display_attribute_manager_;
DISALLOW_COPY_AND_ASSIGN(TSFTextStore);
};
} // namespace ui
#endif // UI_BASE_IME_WIN_TSF_TEXT_STORE_H_
// Copyright (c) 2012 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.
#include "ui/base/ime/win/tsf_text_store.h"
#include <initguid.h> // for GUID_NULL and GUID_PROP_INPUTSCOPE
#include <InputScope.h>
#include <OleCtl.h>
#include "base/memory/ref_counted.h"
#include "base/win/scoped_com_initializer.h"
#include "base/win/scoped_variant.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/gfx/rect.h"
using testing::_;
using testing::Invoke;
using testing::Return;
namespace ui {
namespace {
class MockTextInputClient : public TextInputClient {
public:
~MockTextInputClient() {}
MOCK_METHOD1(SetCompositionText, void(const ui::CompositionText&));
MOCK_METHOD0(ConfirmCompositionText, void());
MOCK_METHOD0(ClearCompositionText, void());
MOCK_METHOD1(InsertText, void(const base::string16&));
MOCK_METHOD2(InsertChar, void(base::char16, int));
MOCK_CONST_METHOD0(GetAttachedWindow, gfx::NativeWindow());
MOCK_CONST_METHOD0(GetTextInputType, ui::TextInputType());
MOCK_CONST_METHOD0(GetTextInputMode, ui::TextInputMode());
MOCK_CONST_METHOD0(CanComposeInline, bool());
MOCK_CONST_METHOD0(GetCaretBounds, gfx::Rect());
MOCK_CONST_METHOD2(GetCompositionCharacterBounds, bool(uint32, gfx::Rect*));
MOCK_CONST_METHOD0(HasCompositionText, bool());
MOCK_CONST_METHOD1(GetTextRange, bool(gfx::Range*));
MOCK_CONST_METHOD1(GetCompositionTextRange, bool(gfx::Range*));
MOCK_CONST_METHOD1(GetSelectionRange, bool(gfx::Range*));
MOCK_METHOD1(SetSelectionRange, bool(const gfx::Range&));
MOCK_METHOD1(DeleteRange, bool(const gfx::Range&));
MOCK_CONST_METHOD2(GetTextFromRange, bool(const gfx::Range&,
base::string16*));
MOCK_METHOD0(OnInputMethodChanged, void());
MOCK_METHOD1(ChangeTextDirectionAndLayoutAlignment,
bool(base::i18n::TextDirection));
MOCK_METHOD2(ExtendSelectionAndDelete, void(size_t, size_t));
MOCK_METHOD1(EnsureCaretInRect, void(const gfx::Rect&));
MOCK_METHOD0(OnCandidateWindowShown, void());
MOCK_METHOD0(OnCandidateWindowUpdated, void());
MOCK_METHOD0(OnCandidateWindowHidden, void());
};
class MockStoreACPSink : public ITextStoreACPSink {
public:
MockStoreACPSink() : ref_count_(0) {}
// IUnknown
virtual ULONG STDMETHODCALLTYPE AddRef() OVERRIDE {
return InterlockedIncrement(&ref_count_);
}
virtual ULONG STDMETHODCALLTYPE Release() OVERRIDE {
const LONG count = InterlockedDecrement(&ref_count_);
if (!count) {
delete this;
return 0;
}
return static_cast<ULONG>(count);
}
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
REFIID iid, void** report) OVERRIDE {
if (iid == IID_IUnknown || iid == IID_ITextStoreACPSink) {
*report = static_cast<ITextStoreACPSink*>(this);
} else {
*report = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
// ITextStoreACPSink
MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE, OnTextChange,
HRESULT(DWORD, const TS_TEXTCHANGE*));
MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, OnSelectionChange,
HRESULT());
MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE, OnLayoutChange,
HRESULT(TsLayoutCode, TsViewCookie));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE, OnStatusChange,
HRESULT(DWORD));
MOCK_METHOD4_WITH_CALLTYPE(STDMETHODCALLTYPE, OnAttrsChange,
HRESULT(LONG, LONG, ULONG, const TS_ATTRID*));
MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE, OnLockGranted,
HRESULT(DWORD));
MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, OnStartEditTransaction,
HRESULT());
MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, OnEndEditTransaction,
HRESULT());
private:
virtual ~MockStoreACPSink() {}
volatile LONG ref_count_;
};
const HWND kWindowHandle = reinterpret_cast<HWND>(1);
} // namespace
class TSFTextStoreTest : public testing::Test {
protected:
virtual void SetUp() OVERRIDE {
text_store_ = new TSFTextStore();
sink_ = new MockStoreACPSink();
EXPECT_EQ(S_OK, text_store_->AdviseSink(IID_ITextStoreACPSink,
sink_, TS_AS_ALL_SINKS));
text_store_->SetFocusedTextInputClient(kWindowHandle,
&text_input_client_);
}
virtual void TearDown() OVERRIDE {
EXPECT_EQ(S_OK, text_store_->UnadviseSink(sink_));
sink_ = NULL;
text_store_ = NULL;
}
// Accessors to the internal state of TSFTextStore.
base::string16* string_buffer() { return &text_store_->string_buffer_; }
size_t* committed_size() { return &text_store_->committed_size_; }
base::win::ScopedCOMInitializer com_initializer_;
MockTextInputClient text_input_client_;
scoped_refptr<TSFTextStore> text_store_;
scoped_refptr<MockStoreACPSink> sink_;
};
class TSFTextStoreTestCallback {
public:
explicit TSFTextStoreTestCallback(TSFTextStore* text_store)
: text_store_(text_store) {
CHECK(text_store_);
}
virtual ~TSFTextStoreTestCallback() {}
protected:
// Accessors to the internal state of TSFTextStore.
bool* edit_flag() { return &text_store_->edit_flag_; }
base::string16* string_buffer() { return &text_store_->string_buffer_; }
size_t* committed_size() { return &text_store_->committed_size_; }
gfx::Range* selection() { return &text_store_->selection_; }
CompositionUnderlines* composition_undelines() {
return &text_store_->composition_undelines_;
}
void SetInternalState(const base::string16& new_string_buffer,
LONG new_committed_size, LONG new_selection_start,
LONG new_selection_end) {
ASSERT_LE(0, new_committed_size);
ASSERT_LE(new_committed_size, new_selection_start);
ASSERT_LE(new_selection_start, new_selection_end);
ASSERT_LE(new_selection_end, static_cast<LONG>(new_string_buffer.size()));
*string_buffer() = new_string_buffer;
*committed_size() = new_committed_size;
selection()->set_start(new_selection_start);
selection()->set_end(new_selection_end);
}
bool HasReadLock() const { return text_store_->HasReadLock(); }
bool HasReadWriteLock() const { return text_store_->HasReadWriteLock(); }
void GetSelectionTest(LONG expected_acp_start, LONG expected_acp_end) {
TS_SELECTION_ACP selection = {};
ULONG fetched = 0;
EXPECT_EQ(S_OK, text_store_->GetSelection(0, 1, &selection, &fetched));
EXPECT_EQ(1, fetched);
EXPECT_EQ(expected_acp_start, selection.acpStart);
EXPECT_EQ(expected_acp_end, selection.acpEnd);
}
void SetSelectionTest(LONG acp_start, LONG acp_end, HRESULT expected_result) {
TS_SELECTION_ACP selection = {};
selection.acpStart = acp_start;
selection.acpEnd = acp_end;
selection.style.ase = TS_AE_NONE;
selection.style.fInterimChar = 0;
EXPECT_EQ(expected_result, text_store_->SetSelection(1, &selection));
if (expected_result == S_OK) {
GetSelectionTest(acp_start, acp_end);
}
}
void SetTextTest(LONG acp_start, LONG acp_end,
const base::string16& text, HRESULT error_code) {
TS_TEXTCHANGE change = {};
ASSERT_EQ(error_code,
text_store_->SetText(0, acp_start, acp_end,
text.c_str(), text.size(), &change));
if (error_code == S_OK) {
EXPECT_EQ(acp_start, change.acpStart);
EXPECT_EQ(acp_end, change.acpOldEnd);
EXPECT_EQ(acp_start + text.size(), change.acpNewEnd);
}
}
void GetTextTest(LONG acp_start, LONG acp_end,
const base::string16& expected_string,
LONG expected_next_acp) {
wchar_t buffer[1024] = {};
ULONG text_buffer_copied = 0;
TS_RUNINFO run_info = {};
ULONG run_info_buffer_copied = 0;
LONG next_acp = 0;
ASSERT_EQ(S_OK,
text_store_->GetText(acp_start, acp_end, buffer, 1024,
&text_buffer_copied,
&run_info, 1, &run_info_buffer_copied,
&next_acp));
ASSERT_EQ(expected_string.size(), text_buffer_copied);
EXPECT_EQ(expected_string,
base::string16(buffer, buffer + text_buffer_copied));
EXPECT_EQ(1, run_info_buffer_copied);
EXPECT_EQ(expected_string.size(), run_info.uCount);
EXPECT_EQ(TS_RT_PLAIN, run_info.type);
EXPECT_EQ(expected_next_acp, next_acp);
}
void GetTextErrorTest(LONG acp_start, LONG acp_end, HRESULT error_code) {
wchar_t buffer[1024] = {};
ULONG text_buffer_copied = 0;
TS_RUNINFO run_info = {};
ULONG run_info_buffer_copied = 0;
LONG next_acp = 0;
EXPECT_EQ(error_code,
text_store_->GetText(acp_start, acp_end, buffer, 1024,
&text_buffer_copied,
&run_info, 1, &run_info_buffer_copied,
&next_acp));
}
void InsertTextAtSelectionTest(const wchar_t* buffer, ULONG buffer_size,
LONG expected_start, LONG expected_end,
LONG expected_change_start,
LONG expected_change_old_end,
LONG expected_change_new_end) {
LONG start = 0;
LONG end = 0;
TS_TEXTCHANGE change = {};
EXPECT_EQ(S_OK,
text_store_->InsertTextAtSelection(0, buffer, buffer_size,
&start, &end, &change));
EXPECT_EQ(expected_start, start);
EXPECT_EQ(expected_end, end);
EXPECT_EQ(expected_change_start, change.acpStart);
EXPECT_EQ(expected_change_old_end, change.acpOldEnd);
EXPECT_EQ(expected_change_new_end, change.acpNewEnd);
}
void InsertTextAtSelectionQueryOnlyTest(const wchar_t* buffer,
ULONG buffer_size,
LONG expected_start,
LONG expected_end) {
LONG start = 0;
LONG end = 0;
EXPECT_EQ(S_OK,
text_store_->InsertTextAtSelection(TS_IAS_QUERYONLY, buffer,
buffer_size, &start, &end,
NULL));
EXPECT_EQ(expected_start, start);
EXPECT_EQ(expected_end, end);
}
void GetTextExtTest(TsViewCookie view_cookie, LONG acp_start, LONG acp_end,
LONG expected_left, LONG expected_top,
LONG expected_right, LONG expected_bottom) {
RECT rect = {};
BOOL clipped = FALSE;
EXPECT_EQ(S_OK, text_store_->GetTextExt(view_cookie, acp_start, acp_end,
&rect, &clipped));
EXPECT_EQ(expected_left, rect.left);
EXPECT_EQ(expected_top, rect.top);
EXPECT_EQ(expected_right, rect.right);
EXPECT_EQ(expected_bottom, rect.bottom);
EXPECT_EQ(FALSE, clipped);
}
void GetTextExtNoLayoutTest(TsViewCookie view_cookie, LONG acp_start,
LONG acp_end) {
RECT rect = {};
BOOL clipped = FALSE;
EXPECT_EQ(TS_E_NOLAYOUT,
text_store_->GetTextExt(view_cookie, acp_start, acp_end,
&rect, &clipped));
}
scoped_refptr<TSFTextStore> text_store_;
private:
DISALLOW_COPY_AND_ASSIGN(TSFTextStoreTestCallback);
};
namespace {
const HRESULT kInvalidResult = 0x12345678;
TEST_F(TSFTextStoreTest, GetStatusTest) {
TS_STATUS status = {};
EXPECT_EQ(S_OK, text_store_->GetStatus(&status));
EXPECT_EQ(0, status.dwDynamicFlags);
EXPECT_EQ(TS_SS_TRANSITORY | TS_SS_NOHIDDENTEXT, status.dwStaticFlags);
}
TEST_F(TSFTextStoreTest, QueryInsertTest) {
LONG result_start = 0;
LONG result_end = 0;
*string_buffer() = L"";
*committed_size() = 0;
EXPECT_EQ(E_INVALIDARG,
text_store_->QueryInsert(0, 0, 0, NULL, &result_end));
EXPECT_EQ(E_INVALIDARG,
text_store_->QueryInsert(0, 0, 0, &result_start, NULL));
EXPECT_EQ(S_OK,
text_store_->QueryInsert(0, 0, 0, &result_start, &result_end));
EXPECT_EQ(0, result_start);
EXPECT_EQ(0, result_end);
*string_buffer() = L"1234";
*committed_size() = 1;
EXPECT_EQ(S_OK,
text_store_->QueryInsert(0, 1, 0, &result_start, &result_end));
EXPECT_EQ(1, result_start);
EXPECT_EQ(1, result_end);
EXPECT_EQ(E_INVALIDARG,
text_store_->QueryInsert(1, 0, 0, &result_start, &result_end));
EXPECT_EQ(S_OK,
text_store_->QueryInsert(2, 2, 0, &result_start, &result_end));
EXPECT_EQ(2, result_start);
EXPECT_EQ(2, result_end);
EXPECT_EQ(S_OK,
text_store_->QueryInsert(2, 3, 0, &result_start, &result_end));
EXPECT_EQ(2, result_start);
EXPECT_EQ(3, result_end);
EXPECT_EQ(E_INVALIDARG,
text_store_->QueryInsert(3, 2, 0, &result_start, &result_end));
EXPECT_EQ(S_OK,
text_store_->QueryInsert(3, 4, 0, &result_start, &result_end));
EXPECT_EQ(3, result_start);
EXPECT_EQ(4, result_end);
EXPECT_EQ(S_OK,
text_store_->QueryInsert(3, 5, 0, &result_start, &result_end));
EXPECT_EQ(3, result_start);
EXPECT_EQ(4, result_end);
}
class SyncRequestLockTestCallback : public TSFTextStoreTestCallback {
public:
explicit SyncRequestLockTestCallback(TSFTextStore* text_store)
: TSFTextStoreTestCallback(text_store) {}
HRESULT LockGranted1(DWORD flags) {
EXPECT_TRUE(HasReadLock());
EXPECT_FALSE(HasReadWriteLock());
return S_OK;
}
HRESULT LockGranted2(DWORD flags) {
EXPECT_TRUE(HasReadLock());
EXPECT_TRUE(HasReadWriteLock());
return S_OK;
}
HRESULT LockGranted3(DWORD flags) {
EXPECT_TRUE(HasReadLock());
EXPECT_FALSE(HasReadWriteLock());
HRESULT result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result));
EXPECT_EQ(TS_E_SYNCHRONOUS, result);
return S_OK;
}
HRESULT LockGranted4(DWORD flags) {
EXPECT_TRUE(HasReadLock());
EXPECT_FALSE(HasReadWriteLock());
HRESULT result = kInvalidResult;
EXPECT_EQ(S_OK,
text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result));
EXPECT_EQ(TS_E_SYNCHRONOUS, result);
return S_OK;
}
HRESULT LockGranted5(DWORD flags) {
EXPECT_TRUE(HasReadLock());
EXPECT_TRUE(HasReadWriteLock());
HRESULT result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result));
EXPECT_EQ(TS_E_SYNCHRONOUS, result);
return S_OK;
}
HRESULT LockGranted6(DWORD flags) {
EXPECT_TRUE(HasReadLock());
EXPECT_TRUE(HasReadWriteLock());
HRESULT result = kInvalidResult;
EXPECT_EQ(S_OK,
text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result));
EXPECT_EQ(TS_E_SYNCHRONOUS, result);
return S_OK;
}
private:
DISALLOW_COPY_AND_ASSIGN(SyncRequestLockTestCallback);
};
TEST_F(TSFTextStoreTest, SynchronousRequestLockTest) {
SyncRequestLockTestCallback callback(text_store_);
EXPECT_CALL(*sink_, OnLockGranted(_))
.WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted1))
.WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted2))
.WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted3))
.WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted4))
.WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted5))
.WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted6));
HRESULT result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result));
EXPECT_EQ(S_OK, result);
result = kInvalidResult;
EXPECT_EQ(S_OK,
text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result));
EXPECT_EQ(S_OK, result);
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result));
EXPECT_EQ(S_OK, result);
result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result));
EXPECT_EQ(S_OK, result);
result = kInvalidResult;
EXPECT_EQ(S_OK,
text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result));
EXPECT_EQ(S_OK, result);
result = kInvalidResult;
EXPECT_EQ(S_OK,
text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result));
EXPECT_EQ(S_OK, result);
}
class AsyncRequestLockTestCallback : public TSFTextStoreTestCallback {
public:
explicit AsyncRequestLockTestCallback(TSFTextStore* text_store)
: TSFTextStoreTestCallback(text_store),
state_(0) {}
HRESULT LockGranted1(DWORD flags) {
EXPECT_EQ(0, state_);
state_ = 1;
EXPECT_TRUE(HasReadLock());
EXPECT_FALSE(HasReadWriteLock());
HRESULT result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result));
EXPECT_EQ(TS_S_ASYNC, result);
EXPECT_EQ(1, state_);
state_ = 2;
return S_OK;
}
HRESULT LockGranted2(DWORD flags) {
EXPECT_EQ(2, state_);
EXPECT_TRUE(HasReadLock());
EXPECT_FALSE(HasReadWriteLock());
HRESULT result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
EXPECT_EQ(TS_S_ASYNC, result);
EXPECT_EQ(2, state_);
state_ = 3;
return S_OK;
}
HRESULT LockGranted3(DWORD flags) {
EXPECT_EQ(3, state_);
EXPECT_TRUE(HasReadLock());
EXPECT_TRUE(HasReadWriteLock());
HRESULT result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
EXPECT_EQ(TS_S_ASYNC, result);
EXPECT_EQ(3, state_);
state_ = 4;
return S_OK;
}
HRESULT LockGranted4(DWORD flags) {
EXPECT_EQ(4, state_);
EXPECT_TRUE(HasReadLock());
EXPECT_TRUE(HasReadWriteLock());
HRESULT result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result));
EXPECT_EQ(TS_S_ASYNC, result);
EXPECT_EQ(4, state_);
state_ = 5;
return S_OK;
}
HRESULT LockGranted5(DWORD flags) {
EXPECT_EQ(5, state_);
EXPECT_TRUE(HasReadLock());
EXPECT_FALSE(HasReadWriteLock());
state_ = 6;
return S_OK;
}
private:
int state_;
DISALLOW_COPY_AND_ASSIGN(AsyncRequestLockTestCallback);
};
TEST_F(TSFTextStoreTest, AsynchronousRequestLockTest) {
AsyncRequestLockTestCallback callback(text_store_);
EXPECT_CALL(*sink_, OnLockGranted(_))
.WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted1))
.WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted2))
.WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted3))
.WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted4))
.WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted5));
HRESULT result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result));
EXPECT_EQ(S_OK, result);
}
class RequestLockTextChangeTestCallback : public TSFTextStoreTestCallback {
public:
explicit RequestLockTextChangeTestCallback(TSFTextStore* text_store)
: TSFTextStoreTestCallback(text_store),
state_(0) {}
HRESULT LockGranted1(DWORD flags) {
EXPECT_EQ(0, state_);
state_ = 1;
EXPECT_TRUE(HasReadLock());
EXPECT_TRUE(HasReadWriteLock());
*edit_flag() = true;
SetInternalState(L"012345", 6, 6, 6);
composition_undelines()->clear();
state_ = 2;
return S_OK;
}
void InsertText(const base::string16& text) {
EXPECT_EQ(2, state_);
EXPECT_EQ(L"012345", text);
state_ = 3;
}
void SetCompositionText(const ui::CompositionText& composition) {
EXPECT_EQ(3, state_);
EXPECT_EQ(L"", composition.text);
EXPECT_EQ(0, composition.selection.start());
EXPECT_EQ(0, composition.selection.end());
EXPECT_EQ(0, composition.underlines.size());
state_ = 4;
}
HRESULT OnTextChange(DWORD flags, const TS_TEXTCHANGE* change) {
EXPECT_EQ(4, state_);
HRESULT result = kInvalidResult;
state_ = 5;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
EXPECT_EQ(S_OK, result);
EXPECT_EQ(6, state_);
state_ = 7;
return S_OK;
}
HRESULT LockGranted2(DWORD flags) {
EXPECT_EQ(5, state_);
EXPECT_TRUE(HasReadLock());
EXPECT_TRUE(HasReadWriteLock());
state_ = 6;
return S_OK;
}
private:
int state_;
DISALLOW_COPY_AND_ASSIGN(RequestLockTextChangeTestCallback);
};
TEST_F(TSFTextStoreTest, RequestLockOnTextChangeTest) {
RequestLockTextChangeTestCallback callback(text_store_);
EXPECT_CALL(*sink_, OnLockGranted(_))
.WillOnce(Invoke(&callback,
&RequestLockTextChangeTestCallback::LockGranted1))
.WillOnce(Invoke(&callback,
&RequestLockTextChangeTestCallback::LockGranted2));
EXPECT_CALL(*sink_, OnSelectionChange())
.WillOnce(Return(S_OK));
EXPECT_CALL(*sink_, OnLayoutChange(_, _))
.WillOnce(Return(S_OK));
EXPECT_CALL(*sink_, OnTextChange(_, _))
.WillOnce(Invoke(&callback,
&RequestLockTextChangeTestCallback::OnTextChange));
EXPECT_CALL(text_input_client_, InsertText(_))
.WillOnce(Invoke(&callback,
&RequestLockTextChangeTestCallback::InsertText));
EXPECT_CALL(text_input_client_, SetCompositionText(_))
.WillOnce(Invoke(&callback,
&RequestLockTextChangeTestCallback::SetCompositionText));
HRESULT result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
EXPECT_EQ(S_OK, result);
}
class SelectionTestCallback : public TSFTextStoreTestCallback {
public:
explicit SelectionTestCallback(TSFTextStore* text_store)
: TSFTextStoreTestCallback(text_store) {}
HRESULT ReadLockGranted(DWORD flags) {
SetInternalState(L"", 0, 0, 0);
GetSelectionTest(0, 0);
SetSelectionTest(0, 0, TF_E_NOLOCK);
SetInternalState(L"012345", 0, 0, 3);
GetSelectionTest(0, 3);
SetSelectionTest(0, 0, TF_E_NOLOCK);
return S_OK;
}
HRESULT ReadWriteLockGranted(DWORD flags) {
SetInternalState(L"", 0, 0, 0);
SetSelectionTest(0, 0, S_OK);
GetSelectionTest(0, 0);
SetSelectionTest(0, 1, TF_E_INVALIDPOS);
SetSelectionTest(1, 0, TF_E_INVALIDPOS);
SetSelectionTest(1, 1, TF_E_INVALIDPOS);
SetInternalState(L"0123456", 3, 3, 3);
SetSelectionTest(0, 0, TF_E_INVALIDPOS);
SetSelectionTest(0, 1, TF_E_INVALIDPOS);
SetSelectionTest(0, 3, TF_E_INVALIDPOS);
SetSelectionTest(0, 6, TF_E_INVALIDPOS);
SetSelectionTest(0, 7, TF_E_INVALIDPOS);
SetSelectionTest(0, 8, TF_E_INVALIDPOS);
SetSelectionTest(1, 0, TF_E_INVALIDPOS);
SetSelectionTest(1, 1, TF_E_INVALIDPOS);
SetSelectionTest(1, 3, TF_E_INVALIDPOS);
SetSelectionTest(1, 6, TF_E_INVALIDPOS);
SetSelectionTest(1, 7, TF_E_INVALIDPOS);
SetSelectionTest(1, 8, TF_E_INVALIDPOS);
SetSelectionTest(3, 0, TF_E_INVALIDPOS);
SetSelectionTest(3, 1, TF_E_INVALIDPOS);
SetSelectionTest(3, 3, S_OK);
SetSelectionTest(3, 6, S_OK);
SetSelectionTest(3, 7, S_OK);
SetSelectionTest(3, 8, TF_E_INVALIDPOS);
SetSelectionTest(6, 0, TF_E_INVALIDPOS);
SetSelectionTest(6, 1, TF_E_INVALIDPOS);
SetSelectionTest(6, 3, TF_E_INVALIDPOS);
SetSelectionTest(6, 6, S_OK);
SetSelectionTest(6, 7, S_OK);
SetSelectionTest(6, 8, TF_E_INVALIDPOS);
SetSelectionTest(7, 0, TF_E_INVALIDPOS);
SetSelectionTest(7, 1, TF_E_INVALIDPOS);
SetSelectionTest(7, 3, TF_E_INVALIDPOS);
SetSelectionTest(7, 6, TF_E_INVALIDPOS);
SetSelectionTest(7, 7, S_OK);
SetSelectionTest(7, 8, TF_E_INVALIDPOS);
SetSelectionTest(8, 0, TF_E_INVALIDPOS);
SetSelectionTest(8, 1, TF_E_INVALIDPOS);
SetSelectionTest(8, 3, TF_E_INVALIDPOS);
SetSelectionTest(8, 6, TF_E_INVALIDPOS);
SetSelectionTest(8, 7, TF_E_INVALIDPOS);
SetSelectionTest(8, 8, TF_E_INVALIDPOS);
return S_OK;
}
};
TEST_F(TSFTextStoreTest, SetGetSelectionTest) {
SelectionTestCallback callback(text_store_);
EXPECT_CALL(*sink_, OnLockGranted(_))
.WillOnce(Invoke(&callback, &SelectionTestCallback::ReadLockGranted))
.WillOnce(Invoke(&callback,
&SelectionTestCallback::ReadWriteLockGranted));
TS_SELECTION_ACP selection_buffer = {};
ULONG fetched_count = 0;
EXPECT_EQ(TS_E_NOLOCK,
text_store_->GetSelection(0, 1, &selection_buffer,
&fetched_count));
HRESULT result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result));
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
}
class SetGetTextTestCallback : public TSFTextStoreTestCallback {
public:
explicit SetGetTextTestCallback(TSFTextStore* text_store)
: TSFTextStoreTestCallback(text_store) {}
HRESULT ReadLockGranted(DWORD flags) {
SetTextTest(0, 0, L"", TF_E_NOLOCK);
GetTextTest(0, -1, L"", 0);
GetTextTest(0, 0, L"", 0);
GetTextErrorTest(0, 1, TF_E_INVALIDPOS);
SetInternalState(L"0123456", 3, 3, 3);
GetTextErrorTest(-1, -1, TF_E_INVALIDPOS);
GetTextErrorTest(-1, 0, TF_E_INVALIDPOS);
GetTextErrorTest(-1, 1, TF_E_INVALIDPOS);
GetTextErrorTest(-1, 3, TF_E_INVALIDPOS);
GetTextErrorTest(-1, 6, TF_E_INVALIDPOS);
GetTextErrorTest(-1, 7, TF_E_INVALIDPOS);
GetTextErrorTest(-1, 8, TF_E_INVALIDPOS);
GetTextTest(0, -1, L"0123456", 7);
GetTextTest(0, 0, L"", 0);
GetTextTest(0, 1, L"0", 1);
GetTextTest(0, 3, L"012", 3);
GetTextTest(0, 6, L"012345", 6);
GetTextTest(0, 7, L"0123456", 7);
GetTextErrorTest(0, 8, TF_E_INVALIDPOS);
GetTextTest(1, -1, L"123456", 7);
GetTextErrorTest(1, 0, TF_E_INVALIDPOS);
GetTextTest(1, 1, L"", 1);
GetTextTest(1, 3, L"12", 3);
GetTextTest(1, 6, L"12345", 6);
GetTextTest(1, 7, L"123456", 7);
GetTextErrorTest(1, 8, TF_E_INVALIDPOS);
GetTextTest(3, -1, L"3456", 7);
GetTextErrorTest(3, 0, TF_E_INVALIDPOS);
GetTextErrorTest(3, 1, TF_E_INVALIDPOS);
GetTextTest(3, 3, L"", 3);
GetTextTest(3, 6, L"345", 6);
GetTextTest(3, 7, L"3456", 7);
GetTextErrorTest(3, 8, TF_E_INVALIDPOS);
GetTextTest(6, -1, L"6", 7);
GetTextErrorTest(6, 0, TF_E_INVALIDPOS);
GetTextErrorTest(6, 1, TF_E_INVALIDPOS);
GetTextErrorTest(6, 3, TF_E_INVALIDPOS);
GetTextTest(6, 6, L"", 6);
GetTextTest(6, 7, L"6", 7);
GetTextErrorTest(6, 8, TF_E_INVALIDPOS);
GetTextTest(7, -1, L"", 7);
GetTextErrorTest(7, 0, TF_E_INVALIDPOS);
GetTextErrorTest(7, 1, TF_E_INVALIDPOS);
GetTextErrorTest(7, 3, TF_E_INVALIDPOS);
GetTextErrorTest(7, 6, TF_E_INVALIDPOS);
GetTextTest(7, 7, L"", 7);
GetTextErrorTest(7, 8, TF_E_INVALIDPOS);
GetTextErrorTest(8, -1, TF_E_INVALIDPOS);
GetTextErrorTest(8, 0, TF_E_INVALIDPOS);
GetTextErrorTest(8, 1, TF_E_INVALIDPOS);
GetTextErrorTest(8, 3, TF_E_INVALIDPOS);
GetTextErrorTest(8, 6, TF_E_INVALIDPOS);
GetTextErrorTest(8, 7, TF_E_INVALIDPOS);
GetTextErrorTest(8, 8, TF_E_INVALIDPOS);
return S_OK;
}
HRESULT ReadWriteLockGranted(DWORD flags) {
SetInternalState(L"", 0, 0, 0);
SetTextTest(0, 0, L"", S_OK);
SetInternalState(L"", 0, 0, 0);
SetTextTest(0, 1, L"", TS_E_INVALIDPOS);
SetInternalState(L"0123456", 3, 3, 3);
SetTextTest(0, 0, L"", TS_E_INVALIDPOS);
SetTextTest(0, 1, L"", TS_E_INVALIDPOS);
SetTextTest(0, 3, L"", TS_E_INVALIDPOS);
SetTextTest(0, 6, L"", TS_E_INVALIDPOS);
SetTextTest(0, 7, L"", TS_E_INVALIDPOS);
SetTextTest(0, 8, L"", TS_E_INVALIDPOS);
SetTextTest(1, 0, L"", TS_E_INVALIDPOS);
SetTextTest(1, 1, L"", TS_E_INVALIDPOS);
SetTextTest(1, 3, L"", TS_E_INVALIDPOS);
SetTextTest(1, 6, L"", TS_E_INVALIDPOS);
SetTextTest(1, 7, L"", TS_E_INVALIDPOS);
SetTextTest(1, 8, L"", TS_E_INVALIDPOS);
SetTextTest(3, 0, L"", TS_E_INVALIDPOS);
SetTextTest(3, 1, L"", TS_E_INVALIDPOS);
SetTextTest(3, 3, L"", S_OK);
GetTextTest(0, -1, L"0123456", 7);
GetSelectionTest(3, 3);
SetInternalState(L"0123456", 3, 3, 3);
SetTextTest(3, 6, L"", S_OK);
GetTextTest(0, -1, L"0126", 4);
GetSelectionTest(3, 3);
SetInternalState(L"0123456", 3, 3, 3);
SetTextTest(3, 7, L"", S_OK);
GetTextTest(0, -1, L"012", 3);
GetSelectionTest(3, 3);
SetInternalState(L"0123456", 3, 3, 3);
SetTextTest(3, 8, L"", TS_E_INVALIDPOS);
SetTextTest(6, 0, L"", TS_E_INVALIDPOS);
SetTextTest(6, 1, L"", TS_E_INVALIDPOS);
SetTextTest(6, 3, L"", TS_E_INVALIDPOS);
SetTextTest(6, 6, L"", S_OK);
GetTextTest(0, -1, L"0123456", 7);
GetSelectionTest(6, 6);
SetInternalState(L"0123456", 3, 3, 3);
SetTextTest(6, 7, L"", S_OK);
GetTextTest(0, -1, L"012345", 6);
GetSelectionTest(6, 6);
SetInternalState(L"0123456", 3, 3, 3);
SetTextTest(6, 8, L"", TS_E_INVALIDPOS);
SetTextTest(7, 0, L"", TS_E_INVALIDPOS);
SetTextTest(7, 1, L"", TS_E_INVALIDPOS);
SetTextTest(7, 3, L"", TS_E_INVALIDPOS);
SetTextTest(7, 6, L"", TS_E_INVALIDPOS);
SetTextTest(7, 7, L"", S_OK);
GetTextTest(0, -1, L"0123456", 7);
GetSelectionTest(7, 7);
SetInternalState(L"0123456", 3, 3, 3);
SetTextTest(7, 8, L"", TS_E_INVALIDPOS);
SetInternalState(L"0123456", 3, 3, 3);
SetTextTest(3, 3, L"abc", S_OK);
GetTextTest(0, -1, L"012abc3456", 10);
GetSelectionTest(3, 6);
SetInternalState(L"0123456", 3, 3, 3);
SetTextTest(3, 6, L"abc", S_OK);
GetTextTest(0, -1, L"012abc6", 7);
GetSelectionTest(3, 6);
SetInternalState(L"0123456", 3, 3, 3);
SetTextTest(3, 7, L"abc", S_OK);
GetTextTest(0, -1, L"012abc", 6);
GetSelectionTest(3, 6);
SetInternalState(L"0123456", 3, 3, 3);
SetTextTest(6, 6, L"abc", S_OK);
GetTextTest(0, -1, L"012345abc6", 10);
GetSelectionTest(6, 9);
SetInternalState(L"0123456", 3, 3, 3);
SetTextTest(6, 7, L"abc", S_OK);
GetTextTest(0, -1, L"012345abc", 9);
GetSelectionTest(6, 9);
SetInternalState(L"0123456", 3, 3, 3);
SetTextTest(7, 7, L"abc", S_OK);
GetTextTest(0, -1, L"0123456abc", 10);
GetSelectionTest(7, 10);
return S_OK;
}
private:
DISALLOW_COPY_AND_ASSIGN(SetGetTextTestCallback);
};
TEST_F(TSFTextStoreTest, SetGetTextTest) {
SetGetTextTestCallback callback(text_store_);
EXPECT_CALL(*sink_, OnLockGranted(_))
.WillOnce(Invoke(&callback, &SetGetTextTestCallback::ReadLockGranted))
.WillOnce(Invoke(&callback,
&SetGetTextTestCallback::ReadWriteLockGranted));
wchar_t buffer[1024] = {};
ULONG text_buffer_copied = 0;
TS_RUNINFO run_info = {};
ULONG run_info_buffer_copied = 0;
LONG next_acp = 0;
EXPECT_EQ(TF_E_NOLOCK,
text_store_->GetText(0, -1, buffer, 1024, &text_buffer_copied,
&run_info, 1, &run_info_buffer_copied,
&next_acp));
TS_TEXTCHANGE change = {};
EXPECT_EQ(TF_E_NOLOCK, text_store_->SetText(0, 0, 0, L"abc", 3, &change));
HRESULT result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result));
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
}
class InsertTextAtSelectionTestCallback : public TSFTextStoreTestCallback {
public:
explicit InsertTextAtSelectionTestCallback(TSFTextStore* text_store)
: TSFTextStoreTestCallback(text_store) {}
HRESULT ReadLockGranted(DWORD flags) {
const wchar_t kBuffer[] = L"0123456789";
SetInternalState(L"abcedfg", 0, 0, 0);
InsertTextAtSelectionQueryOnlyTest(kBuffer, 10, 0, 0);
GetSelectionTest(0, 0);
InsertTextAtSelectionQueryOnlyTest(kBuffer, 0, 0, 0);
SetInternalState(L"abcedfg", 0, 2, 5);
InsertTextAtSelectionQueryOnlyTest(kBuffer, 10, 2, 5);
GetSelectionTest(2, 5);
InsertTextAtSelectionQueryOnlyTest(kBuffer, 0, 2, 5);
LONG start = 0;
LONG end = 0;
TS_TEXTCHANGE change = {};
EXPECT_EQ(TS_E_NOLOCK,
text_store_->InsertTextAtSelection(0, kBuffer, 10,
&start, &end, &change));
return S_OK;
}
HRESULT ReadWriteLockGranted(DWORD flags) {
SetInternalState(L"abcedfg", 0, 0, 0);
const wchar_t kBuffer[] = L"0123456789";
InsertTextAtSelectionQueryOnlyTest(kBuffer, 10, 0, 0);
GetSelectionTest(0, 0);
InsertTextAtSelectionQueryOnlyTest(kBuffer, 0, 0, 0);
SetInternalState(L"", 0, 0, 0);
InsertTextAtSelectionTest(kBuffer, 10, 0, 10, 0, 0, 10);
GetSelectionTest(0, 10);
GetTextTest(0, -1, L"0123456789", 10);
SetInternalState(L"abcedfg", 0, 0, 0);
InsertTextAtSelectionTest(kBuffer, 10, 0, 10, 0, 0, 10);
GetSelectionTest(0, 10);
GetTextTest(0, -1, L"0123456789abcedfg", 17);
SetInternalState(L"abcedfg", 0, 0, 3);
InsertTextAtSelectionTest(kBuffer, 0, 0, 0, 0, 3, 0);
GetSelectionTest(0, 0);
GetTextTest(0, -1, L"edfg", 4);
SetInternalState(L"abcedfg", 0, 3, 7);
InsertTextAtSelectionTest(kBuffer, 10, 3, 13, 3, 7, 13);
GetSelectionTest(3, 13);
GetTextTest(0, -1, L"abc0123456789", 13);
SetInternalState(L"abcedfg", 0, 7, 7);
InsertTextAtSelectionTest(kBuffer, 10, 7, 17, 7, 7, 17);
GetSelectionTest(7, 17);
GetTextTest(0, -1, L"abcedfg0123456789", 17);
return S_OK;
}
private:
DISALLOW_COPY_AND_ASSIGN(InsertTextAtSelectionTestCallback);
};
TEST_F(TSFTextStoreTest, InsertTextAtSelectionTest) {
InsertTextAtSelectionTestCallback callback(text_store_);
EXPECT_CALL(*sink_, OnLockGranted(_))
.WillOnce(Invoke(&callback,
&InsertTextAtSelectionTestCallback::ReadLockGranted))
.WillOnce(
Invoke(&callback,
&InsertTextAtSelectionTestCallback::ReadWriteLockGranted));
HRESULT result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result));
EXPECT_EQ(S_OK, result);
result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
EXPECT_EQ(S_OK, result);
}
class ScenarioTestCallback : public TSFTextStoreTestCallback {
public:
explicit ScenarioTestCallback(TSFTextStore* text_store)
: TSFTextStoreTestCallback(text_store) {}
HRESULT LockGranted1(DWORD flags) {
SetSelectionTest(0, 0, S_OK);
SetTextTest(0, 0, L"abc", S_OK);
SetTextTest(1, 2, L"xyz", S_OK);
GetTextTest(0, -1, L"axyzc", 5);
composition_undelines()->clear();
CompositionUnderline underline;
underline.start_offset = 0;
underline.end_offset = 5;
underline.color = SK_ColorBLACK;
underline.thick = false;
composition_undelines()->push_back(underline);
*edit_flag() = true;
*committed_size() = 0;
return S_OK;
}
void SetCompositionText1(const ui::CompositionText& composition) {
EXPECT_EQ(L"axyzc", composition.text);
EXPECT_EQ(1, composition.selection.start());
EXPECT_EQ(4, composition.selection.end());
ASSERT_EQ(1, composition.underlines.size());
EXPECT_EQ(SK_ColorBLACK, composition.underlines[0].color);
EXPECT_EQ(0, composition.underlines[0].start_offset);
EXPECT_EQ(5, composition.underlines[0].end_offset);
EXPECT_FALSE(composition.underlines[0].thick);
}
HRESULT LockGranted2(DWORD flags) {
SetTextTest(3, 4, L"ZCP", S_OK);
GetTextTest(0, -1, L"axyZCPc", 7);
composition_undelines()->clear();
CompositionUnderline underline;
underline.start_offset = 3;
underline.end_offset = 5;
underline.color = SK_ColorBLACK;
underline.thick = true;
composition_undelines()->push_back(underline);
underline.start_offset = 5;
underline.end_offset = 7;
underline.color = SK_ColorBLACK;
underline.thick = false;
composition_undelines()->push_back(underline);
*edit_flag() = true;
*committed_size() = 3;
return S_OK;
}
void InsertText2(const base::string16& text) {
EXPECT_EQ(L"axy", text);
}
void SetCompositionText2(const ui::CompositionText& composition) {
EXPECT_EQ(L"ZCPc", composition.text);
EXPECT_EQ(0, composition.selection.start());
EXPECT_EQ(3, composition.selection.end());
ASSERT_EQ(2, composition.underlines.size());
EXPECT_EQ(SK_ColorBLACK, composition.underlines[0].color);
EXPECT_EQ(0, composition.underlines[0].start_offset);
EXPECT_EQ(2, composition.underlines[0].end_offset);
EXPECT_TRUE(composition.underlines[0].thick);
EXPECT_EQ(SK_ColorBLACK, composition.underlines[1].color);
EXPECT_EQ(2, composition.underlines[1].start_offset);
EXPECT_EQ(4, composition.underlines[1].end_offset);
EXPECT_FALSE(composition.underlines[1].thick);
}
HRESULT LockGranted3(DWORD flags) {
GetTextTest(0, -1, L"axyZCPc", 7);
composition_undelines()->clear();
*edit_flag() = true;
*committed_size() = 7;
return S_OK;
}
void InsertText3(const base::string16& text) {
EXPECT_EQ(L"ZCPc", text);
}
void SetCompositionText3(const ui::CompositionText& composition) {
EXPECT_EQ(L"", composition.text);
EXPECT_EQ(0, composition.selection.start());
EXPECT_EQ(0, composition.selection.end());
EXPECT_EQ(0, composition.underlines.size());
}
private:
DISALLOW_COPY_AND_ASSIGN(ScenarioTestCallback);
};
TEST_F(TSFTextStoreTest, ScenarioTest) {
ScenarioTestCallback callback(text_store_);
EXPECT_CALL(text_input_client_, SetCompositionText(_))
.WillOnce(Invoke(&callback, &ScenarioTestCallback::SetCompositionText1))
.WillOnce(Invoke(&callback, &ScenarioTestCallback::SetCompositionText2))
.WillOnce(Invoke(&callback, &ScenarioTestCallback::SetCompositionText3));
EXPECT_CALL(text_input_client_, InsertText(_))
.WillOnce(Invoke(&callback, &ScenarioTestCallback::InsertText2))
.WillOnce(Invoke(&callback, &ScenarioTestCallback::InsertText3));
EXPECT_CALL(*sink_, OnLockGranted(_))
.WillOnce(Invoke(&callback, &ScenarioTestCallback::LockGranted1))
.WillOnce(Invoke(&callback, &ScenarioTestCallback::LockGranted2))
.WillOnce(Invoke(&callback, &ScenarioTestCallback::LockGranted3));
// OnSelectionChange will be called once after LockGranted3().
EXPECT_CALL(*sink_, OnSelectionChange())
.WillOnce(Return(S_OK));
// OnLayoutChange will be called once after LockGranted3().
EXPECT_CALL(*sink_, OnLayoutChange(_, _))
.WillOnce(Return(S_OK));
// OnTextChange will be called once after LockGranted3().
EXPECT_CALL(*sink_, OnTextChange(_, _))
.WillOnce(Return(S_OK));
HRESULT result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
EXPECT_EQ(S_OK, result);
result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
EXPECT_EQ(S_OK, result);
result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result));
EXPECT_EQ(S_OK, result);
}
class GetTextExtTestCallback : public TSFTextStoreTestCallback {
public:
explicit GetTextExtTestCallback(TSFTextStore* text_store)
: TSFTextStoreTestCallback(text_store),
layout_prepared_character_num_(0) {}
HRESULT LockGranted(DWORD flags) {
SetInternalState(L"0123456789012", 0, 0, 0);
layout_prepared_character_num_ = 13;
TsViewCookie view_cookie = 0;
EXPECT_EQ(S_OK, text_store_->GetActiveView(&view_cookie));
GetTextExtTest(view_cookie, 0, 0, 11, 12, 11, 20);
GetTextExtTest(view_cookie, 0, 1, 11, 12, 20, 20);
GetTextExtTest(view_cookie, 0, 2, 11, 12, 30, 20);
GetTextExtTest(view_cookie, 9, 9, 100, 12, 100, 20);
GetTextExtTest(view_cookie, 9, 10, 101, 12, 110, 20);
GetTextExtTest(view_cookie, 10, 10, 110, 12, 110, 20);
GetTextExtTest(view_cookie, 11, 11, 20, 112, 20, 120);
GetTextExtTest(view_cookie, 11, 12, 21, 112, 30, 120);
GetTextExtTest(view_cookie, 9, 12, 101, 12, 30, 120);
GetTextExtTest(view_cookie, 9, 13, 101, 12, 40, 120);
GetTextExtTest(view_cookie, 0, 13, 11, 12, 40, 120);
GetTextExtTest(view_cookie, 13, 13, 40, 112, 40, 120);
layout_prepared_character_num_ = 12;
GetTextExtNoLayoutTest(view_cookie, 13, 13);
layout_prepared_character_num_ = 0;
GetTextExtNoLayoutTest(view_cookie, 0, 0);
SetInternalState(L"", 0, 0, 0);
GetTextExtTest(view_cookie, 0, 0, 1, 2, 4, 6);
// Last character is not availabe due to timing issue of async API.
// In this case, we will get first character bounds instead of whole text
// bounds.
SetInternalState(L"abc", 0, 0, 3);
layout_prepared_character_num_ = 2;
GetTextExtTest(view_cookie, 0, 0, 11, 12, 11, 20);
// TODO(nona, kinaba): Remove following test case after PPAPI supporting
// GetCompositionCharacterBounds.
SetInternalState(L"a", 0, 0, 1);
layout_prepared_character_num_ = 0;
GetTextExtTest(view_cookie, 0, 1, 1, 2, 4, 6);
return S_OK;
}
bool GetCompositionCharacterBounds(uint32 index, gfx::Rect* rect) {
if (index >= layout_prepared_character_num_)
return false;
rect->set_x((index % 10) * 10 + 11);
rect->set_y((index / 10) * 100 + 12);
rect->set_width(9);
rect->set_height(8);
return true;
}
gfx::Rect GetCaretBounds() {
return gfx::Rect(1, 2, 3, 4);
}
private:
uint32 layout_prepared_character_num_;
DISALLOW_COPY_AND_ASSIGN(GetTextExtTestCallback);
};
TEST_F(TSFTextStoreTest, GetTextExtTest) {
GetTextExtTestCallback callback(text_store_);
EXPECT_CALL(text_input_client_, GetCaretBounds())
.WillRepeatedly(Invoke(&callback,
&GetTextExtTestCallback::GetCaretBounds));
EXPECT_CALL(text_input_client_, GetCompositionCharacterBounds(_, _))
.WillRepeatedly(
Invoke(&callback,
&GetTextExtTestCallback::GetCompositionCharacterBounds));
EXPECT_CALL(*sink_, OnLockGranted(_))
.WillOnce(Invoke(&callback, &GetTextExtTestCallback::LockGranted));
HRESULT result = kInvalidResult;
EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result));
EXPECT_EQ(S_OK, result);
}
TEST_F(TSFTextStoreTest, RequestSupportedAttrs) {
EXPECT_CALL(text_input_client_, GetTextInputType())
.WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
EXPECT_CALL(text_input_client_, GetTextInputMode())
.WillRepeatedly(Return(TEXT_INPUT_MODE_DEFAULT));
EXPECT_HRESULT_FAILED(text_store_->RequestSupportedAttrs(0, 1, NULL));
const TS_ATTRID kUnknownAttributes[] = {GUID_NULL};
EXPECT_HRESULT_FAILED(text_store_->RequestSupportedAttrs(
0, arraysize(kUnknownAttributes), kUnknownAttributes))
<< "Must fail for unknown attributes";
const TS_ATTRID kAttributes[] = {GUID_NULL, GUID_PROP_INPUTSCOPE, GUID_NULL};
EXPECT_EQ(S_OK, text_store_->RequestSupportedAttrs(
0, arraysize(kAttributes), kAttributes))
<< "InputScope must be supported";
{
SCOPED_TRACE("Check if RequestSupportedAttrs fails while focus is lost");
// Emulate focus lost
text_store_->SetFocusedTextInputClient(NULL, NULL);
EXPECT_HRESULT_FAILED(text_store_->RequestSupportedAttrs(0, 0, NULL));
EXPECT_HRESULT_FAILED(text_store_->RequestSupportedAttrs(
0, arraysize(kAttributes), kAttributes));
}
}
TEST_F(TSFTextStoreTest, RetrieveRequestedAttrs) {
EXPECT_CALL(text_input_client_, GetTextInputType())
.WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
EXPECT_CALL(text_input_client_, GetTextInputMode())
.WillRepeatedly(Return(TEXT_INPUT_MODE_DEFAULT));
ULONG num_copied = 0xfffffff;
EXPECT_HRESULT_FAILED(text_store_->RetrieveRequestedAttrs(
1, NULL, &num_copied));
{
SCOPED_TRACE("Make sure if InputScope is supported");
TS_ATTRVAL buffer[2] = {};
num_copied = 0xfffffff;
ASSERT_EQ(S_OK, text_store_->RetrieveRequestedAttrs(
arraysize(buffer), buffer, &num_copied));
bool input_scope_found = false;
for (size_t i = 0; i < num_copied; ++i) {
base::win::ScopedVariant variant;
// Move ownership from |buffer[i].varValue| to |variant|.
std::swap(*variant.Receive(), buffer[i].varValue);
if (IsEqualGUID(buffer[i].idAttr, GUID_PROP_INPUTSCOPE)) {
EXPECT_EQ(VT_UNKNOWN, variant.type());
base::win::ScopedComPtr<ITfInputScope> input_scope;
EXPECT_HRESULT_SUCCEEDED(input_scope.QueryFrom((&variant)->punkVal));
input_scope_found = true;
// we do not break here to clean up all the retrieved VARIANTs.
}
}
EXPECT_TRUE(input_scope_found);
}
{
SCOPED_TRACE("Check if RetrieveRequestedAttrs fails while focus is lost");
// Emulate focus lost
text_store_->SetFocusedTextInputClient(NULL, NULL);
num_copied = 0xfffffff;
TS_ATTRVAL buffer[2] = {};
EXPECT_HRESULT_FAILED(text_store_->RetrieveRequestedAttrs(
arraysize(buffer), buffer, &num_copied));
}
}
} // namespace
} // namespace ui
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
#include "ui/aura/window_property.h" #include "ui/aura/window_property.h"
#include "ui/base/cursor/cursor_loader_win.h" #include "ui/base/cursor/cursor_loader_win.h"
#include "ui/base/ime/input_method.h" #include "ui/base/ime/input_method.h"
#include "ui/base/ime/win/tsf_bridge.h"
#include "ui/base/win/shell.h" #include "ui/base/win/shell.h"
#include "ui/compositor/compositor_constants.h" #include "ui/compositor/compositor_constants.h"
#include "ui/gfx/insets.h" #include "ui/gfx/insets.h"
...@@ -847,8 +846,6 @@ void DesktopWindowTreeHostWin::HandleNativeBlur(HWND focused_window) { ...@@ -847,8 +846,6 @@ void DesktopWindowTreeHostWin::HandleNativeBlur(HWND focused_window) {
} }
bool DesktopWindowTreeHostWin::HandleMouseEvent(const ui::MouseEvent& event) { bool DesktopWindowTreeHostWin::HandleMouseEvent(const ui::MouseEvent& event) {
if (base::win::IsTSFAwareRequired() && event.IsAnyButton())
ui::TSFBridge::GetInstance()->CancelComposition();
return delegate_->OnHostMouseEvent(const_cast<ui::MouseEvent*>(&event)); return delegate_->OnHostMouseEvent(const_cast<ui::MouseEvent*>(&event));
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment