Chromium Code Reviews| Index: win8/metro_driver/ime/text_service.cc |
| diff --git a/win8/metro_driver/ime/text_service.cc b/win8/metro_driver/ime/text_service.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..7ffd23a0a9e007c18e95fb3b89c36ea33eaf148d |
| --- /dev/null |
| +++ b/win8/metro_driver/ime/text_service.cc |
| @@ -0,0 +1,348 @@ |
| +// 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 "win8/metro_driver/ime/text_service.h" |
| + |
| +#include <msctf.h> |
| + |
| +#include "base/win/scoped_variant.h" |
| +#include "ui/metro_viewer/ime_types.h" |
| +#include "win8/metro_driver/ime/text_service_delegate.h" |
| +#include "win8/metro_driver/ime/text_store.h" |
| +#include "win8/metro_driver/ime/text_store_delegate.h" |
| + |
| +namespace metro_driver { |
|
ananta
2013/11/27 02:27:44
Can you add some commentary here about how this wh
yukawa
2013/11/27 11:30:04
Done.
|
| +namespace { |
| +typedef TSFTextStore TextStore; |
| + |
| +// Japanese IME expects the default value of this compartment is |
| +// TF_SENTENCEMODE_PHRASEPREDICT like IMM32 implementation. This value is |
| +// managed per thread, thus setting this value at once is enough. This |
| +// value never affects other IMEs except for Japanese. |
| +bool InitializeSentenceMode(ITfThreadMgr2* thread_manager, |
| + TfClientId client_id) { |
| + base::win::ScopedComPtr<ITfCompartmentMgr> thread_compartment_manager; |
| + if (FAILED(thread_compartment_manager.QueryFrom(thread_manager))) |
| + return false; |
| + base::win::ScopedComPtr<ITfCompartment> sentence_compartment; |
| + if (FAILED(thread_compartment_manager->GetCompartment( |
| + GUID_COMPARTMENT_KEYBOARD_INPUTMODE_SENTENCE, |
| + sentence_compartment.Receive()))) { |
| + return false; |
| + } |
| + |
| + base::win::ScopedVariant sentence_variant; |
| + sentence_variant.Set(TF_SENTENCEMODE_PHRASEPREDICT); |
| + if (FAILED(sentence_compartment->SetValue(client_id, &sentence_variant))) |
| + return false; |
| + return true; |
| +} |
| + |
| +// Initializes |context| as disabled context where IMEs will be disabled. |
| +bool InitializeDisabledContext(ITfContext* context, TfClientId client_id) { |
| + base::win::ScopedComPtr<ITfCompartmentMgr> compartment_mgr; |
| + if (FAILED(compartment_mgr.QueryFrom(context))) |
| + return false; |
| + |
| + base::win::ScopedComPtr<ITfCompartment> disabled_compartment; |
| + if (FAILED(compartment_mgr->GetCompartment(GUID_COMPARTMENT_KEYBOARD_DISABLED, |
| + disabled_compartment.Receive()))) { |
| + return false; |
| + } |
| + |
| + base::win::ScopedVariant variant; |
| + variant.Set(1); |
| + if (FAILED(disabled_compartment->SetValue(client_id, &variant))) |
| + return false; |
| + |
| + base::win::ScopedComPtr<ITfCompartment> empty_context; |
| + if (FAILED(compartment_mgr->GetCompartment(GUID_COMPARTMENT_EMPTYCONTEXT, |
| + empty_context.Receive()))) { |
| + 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))) |
| + return false; |
| + |
| + return true; |
| +} |
| + |
| +scoped_refptr<TextStore> CreateTextStore(const std::vector<int32>& input_scopes, |
| + HWND window_handle, |
| + TextStoreDelegate* delegate) { |
| + if (input_scopes.empty()) |
| + return NULL; |
| + std::vector<InputScope> buffer(input_scopes.size()); |
| + for (size_t i = 0; i < input_scopes.size(); ++i) |
| + buffer[i] = static_cast<InputScope>(input_scopes[i]); |
| + return new TextStore(window_handle, buffer, delegate); |
| +} |
| + |
| +// A class that manages the lifetime of the event callback registration. When |
| +// this object is destroyed, corresponding event callback will be unregistered. |
| +class EventSink { |
| + public: |
| + EventSink(DWORD cookie, base::win::ScopedComPtr<ITfSource> source) |
| + : cookie_(cookie), |
| + source_(source) {} |
| + ~EventSink() { |
| + if (!source_ || cookie_ != TF_INVALID_COOKIE) |
| + return; |
| + source_->UnadviseSink(cookie_); |
| + cookie_ = TF_INVALID_COOKIE; |
| + source_.Release(); |
| + } |
| + |
| + private: |
| + DWORD cookie_; |
| + base::win::ScopedComPtr<ITfSource> source_; |
| + DISALLOW_COPY_AND_ASSIGN(EventSink); |
| +}; |
| + |
| +scoped_ptr<EventSink> CreateTextEditSink(ITfContext* context, |
| + ITfTextEditSink* text_store) { |
| + if (!text_store) |
| + return scoped_ptr<EventSink>(); |
| + base::win::ScopedComPtr<ITfSource> source; |
| + DWORD cookie = TF_INVALID_EDIT_COOKIE; |
| + if (FAILED(source.QueryFrom(context))) |
| + return scoped_ptr<EventSink>(); |
| + if (FAILED(source->AdviseSink(IID_ITfTextEditSink, text_store, &cookie))) |
| + return scoped_ptr<EventSink>(); |
| + return scoped_ptr<EventSink>(new EventSink(cookie, source)); |
| +} |
| + |
| +// A set of objects that should have the same lifetime. This contains the text |
| +// buffer (TextStore) implemented by Chrome and corresponding focusable object |
| +// (ITfDocumentMgr) managed by TSF. This also manages the lifetime of the event |
| +// callback registration between TSF subsystem and TextStore. |
| +class DocumentBinding { |
| + public: |
| + ~DocumentBinding() { |
| + } |
| + |
| + static scoped_ptr<DocumentBinding> Create( |
| + ITfThreadMgr2* thread_manager, |
| + TfClientId client_id, |
| + const std::vector<int32>& input_scopes, |
| + HWND window_handle, |
| + TextStoreDelegate* delegate) { |
| + base::win::ScopedComPtr<ITfDocumentMgr> document_manager; |
| + if (!thread_manager) |
| + return scoped_ptr<DocumentBinding>(); |
| + if (FAILED(thread_manager->CreateDocumentMgr(document_manager.Receive()))) |
| + return scoped_ptr<DocumentBinding>(); |
| + |
| + // Note: |text_store| can be NULL and it's not an error. It means that |
| + // the document expects an IME to be disabled while the document is focused. |
| + scoped_refptr<TextStore> text_store = CreateTextStore(input_scopes, |
| + window_handle, |
| + delegate); |
| + |
| + base::win::ScopedComPtr<ITfContext> context; |
| + DWORD edit_cookie = TF_INVALID_EDIT_COOKIE; |
| + if (FAILED(document_manager->CreateContext( |
| + client_id, |
| + 0, |
| + static_cast<ITextStoreACP*>(text_store.get()), |
| + context.Receive(), |
| + &edit_cookie))) { |
| + return scoped_ptr<DocumentBinding>(); |
| + } |
| + if (FAILED(document_manager->Push(context))) |
| + return scoped_ptr<DocumentBinding>(); |
| + const bool is_password_field = |
| + std::find(input_scopes.begin(), input_scopes.end(), IS_PASSWORD) != |
| + input_scopes.end(); |
| + const bool use_disabled_context = input_scopes.empty() || is_password_field; |
| + if (use_disabled_context && !InitializeDisabledContext(context, client_id)) |
| + return scoped_ptr<DocumentBinding>(); |
| + scoped_ptr<EventSink> text_edit_sink = CreateTextEditSink(context, |
| + text_store); |
| + return scoped_ptr<DocumentBinding>( |
| + new DocumentBinding(text_store, |
| + document_manager, |
| + text_edit_sink.Pass())); |
| + } |
| + |
| + ITfDocumentMgr* document_manager() const { |
| + return document_manager_; |
| + } |
| + |
| + scoped_refptr<TextStore> text_store() { |
| + return text_store_; |
| + } |
| + |
| + private: |
| + DocumentBinding(scoped_refptr<TextStore> text_store, |
| + base::win::ScopedComPtr<ITfDocumentMgr> document_manager, |
| + scoped_ptr<EventSink> text_edit_sink) |
| + : text_store_(text_store), |
| + document_manager_(document_manager), |
| + text_edit_sink_(text_edit_sink.Pass()) {} |
| + |
| + scoped_refptr<TextStore> text_store_; |
| + base::win::ScopedComPtr<ITfDocumentMgr> document_manager_; |
| + base::win::ScopedComPtr<ITfContext> context_; |
| + base::win::ScopedComPtr<ITfSource> text_edit_sink_source_; |
| + scoped_ptr<EventSink> text_edit_sink_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(DocumentBinding); |
| +}; |
| + |
| +class TextServiceImpl : public TextService, |
| + public TextStoreDelegate { |
| + public: |
| + TextServiceImpl(base::win::ScopedComPtr<ITfThreadMgr2> thread_manager, |
| + TfClientId client_id, |
| + HWND window_handle, |
| + TextServiceDelegate* delegate) |
| + : client_id_(client_id), |
| + window_handle_(window_handle), |
| + delegate_(delegate), |
| + thread_manager_(thread_manager) { |
| + DCHECK_NE(TF_CLIENTID_NULL, client_id); |
| + DCHECK(window_handle != NULL); |
| + DCHECK(thread_manager_); |
| + } |
| + virtual ~TextServiceImpl() { |
| + thread_manager_->Deactivate(); |
| + } |
| + |
| + private: |
| + // TextService overrides: |
| + virtual void TextService::CancelComposition() OVERRIDE { |
| + DocumentBinding* document = current_document_.get(); |
| + if (!document) |
| + return; |
| + TextStore* text_store = document->text_store(); |
| + if (!text_store) |
| + return; |
| + text_store->CancelComposition(); |
| + } |
| + |
| + virtual void OnDocumentChanged( |
| + const std::vector<int32>& input_scopes, |
| + const std::vector<metro_viewer::CharacterBounds>& character_bounds) |
| + OVERRIDE { |
| + bool document_changed = input_scopes_ != input_scopes; |
| + input_scopes_ = input_scopes; |
| + composition_character_bounds_ = character_bounds; |
| + if (document_changed) |
| + OnDocumentTypeChanged(input_scopes); |
| + } |
| + |
| + virtual void OnWindowActivated() OVERRIDE { |
| + if (!current_document_) |
| + return; |
| + ITfDocumentMgr* document_manager = current_document_->document_manager(); |
| + if (!document_manager) |
| + return; |
| + if (FAILED(thread_manager_->SetFocus(document_manager))) |
| + return; |
| + } |
| + |
| + virtual void OnCompositionChanged( |
| + const string16& text, |
| + int32 selection_start, |
| + int32 selection_end, |
| + const std::vector<metro_viewer::UnderlineInfo>& underlines) OVERRIDE { |
| + if (!delegate_) |
| + return; |
| + delegate_->OnCompositionChanged(text, |
| + selection_start, |
| + selection_end, |
| + underlines); |
| + } |
| + |
| + virtual void OnTextCommitted(const string16& text) OVERRIDE { |
| + if (!delegate_) |
| + return; |
| + delegate_->OnTextCommitted(text); |
| + } |
| + |
| + virtual RECT GetCaretBounds() { |
| + if (composition_character_bounds_.empty()) { |
| + const RECT rect = {}; |
| + return rect; |
| + } |
| + const metro_viewer::CharacterBounds& bounds = |
| + composition_character_bounds_[0]; |
| + POINT left_top = { bounds.left, bounds.top }; |
| + POINT right_bottom = { bounds.right, bounds.bottom }; |
| + ClientToScreen(window_handle_, &left_top); |
| + ClientToScreen(window_handle_, &right_bottom); |
| + const RECT rect = { |
| + left_top.x, |
| + left_top.y, |
| + right_bottom.x, |
| + right_bottom.y, |
| + }; |
| + return rect; |
| + } |
| + |
| + virtual bool GetCompositionCharacterBounds(uint32 index, |
| + RECT* rect) OVERRIDE { |
| + if (index >= composition_character_bounds_.size()) { |
| + return false; |
| + } |
| + const metro_viewer::CharacterBounds& bounds = |
| + composition_character_bounds_[index]; |
| + POINT left_top = { bounds.left, bounds.top }; |
| + POINT right_bottom = { bounds.right, bounds.bottom }; |
| + ClientToScreen(window_handle_, &left_top); |
| + ClientToScreen(window_handle_, &right_bottom); |
| + SetRect(rect, left_top.x, left_top.y, right_bottom.x, right_bottom.y); |
| + return true; |
| + } |
| + |
| + void OnDocumentTypeChanged(const std::vector<int32>& input_scopes) { |
| + scoped_ptr<DocumentBinding> new_document = |
| + DocumentBinding::Create(thread_manager_.get(), |
| + client_id_, |
| + input_scopes, |
| + window_handle_, |
| + this); |
| + if (!new_document) |
| + return; |
| + current_document_.swap(new_document); |
| + OnWindowActivated(); |
| + } |
| + |
| + TfClientId client_id_; |
| + HWND window_handle_; |
| + TextServiceDelegate* delegate_; |
| + scoped_ptr<DocumentBinding> current_document_; |
| + base::win::ScopedComPtr<ITfThreadMgr2> thread_manager_; |
| + std::vector<int32> input_scopes_; |
| + std::vector<metro_viewer::CharacterBounds> composition_character_bounds_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(TextServiceImpl); |
| +}; |
| + |
| +} // namespace |
| + |
| +scoped_ptr<TextService> |
| +CreateTextService(TextServiceDelegate* delegate, HWND window_handle) { |
| + if (!delegate) |
| + return scoped_ptr<TextService>(); |
| + base::win::ScopedComPtr<ITfThreadMgr2> thread_manager; |
| + if (FAILED(thread_manager.CreateInstance(CLSID_TF_ThreadMgr))) |
| + return scoped_ptr<TextService>(); |
| + TfClientId client_id = TF_CLIENTID_NULL; |
| + if (FAILED(thread_manager->ActivateEx(&client_id, 0))) |
| + return scoped_ptr<TextService>(); |
| + if (!InitializeSentenceMode(thread_manager, client_id)) { |
| + thread_manager->Deactivate(); |
| + return scoped_ptr<TextService>(); |
| + } |
| + return scoped_ptr<TextService>(new TextServiceImpl(thread_manager, |
| + client_id, |
| + window_handle, |
| + delegate)); |
| +} |
| + |
| +} // namespace metro_driver |