Chromium Code Reviews| Index: ui/base/win/tsf_event_router.cc |
| diff --git a/ui/base/win/tsf_event_router.cc b/ui/base/win/tsf_event_router.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..77d09c9c276160cc31d011a51e98e2fff736dd8e |
| --- /dev/null |
| +++ b/ui/base/win/tsf_event_router.cc |
| @@ -0,0 +1,280 @@ |
| +// 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/win/tsf_event_router.h" |
| + |
| +#include <msctf.h> |
| +#include <set> |
| +#include <utility> |
| +#include "base/bind.h" |
|
sky
2012/10/15 22:00:07
newline between 9/10.
Seigo Nonaka
2012/10/16 02:20:47
Done.
|
| +#include "base/win/scoped_comptr.h" |
| +#include "base/win/metro.h" |
| + |
| +namespace ui { |
| + |
| +class TsfEventRouterImpl : public TsfEventRouter, |
| + public ITfUIElementSink, |
| + public ITfTextEditSink { |
| + public: |
| + TsfEventRouterImpl() |
| + : context_source_cookie_(TF_INVALID_COOKIE), |
| + ui_source_cookie_(TF_INVALID_COOKIE), |
| + ref_count_(0) {} |
| + |
| + virtual ~TsfEventRouterImpl() {} |
| + |
| + // ITfTextEditSink override. |
| + virtual ULONG STDMETHODCALLTYPE AddRef() OVERRIDE { |
| + return InterlockedIncrement(&ref_count_); |
| + } |
| + |
| + // ITfTextEditSink override. |
| + virtual ULONG STDMETHODCALLTYPE Release() OVERRIDE { |
| + const LONG count = InterlockedDecrement(&ref_count_); |
| + if (!count) { |
| + delete this; |
| + return 0; |
| + } |
| + return static_cast<ULONG>(count); |
| + } |
| + |
| + // ITfTextEditSink override. |
| + virtual STDMETHODIMP QueryInterface(REFIID iid, void** result) OVERRIDE { |
| + if (!result) |
| + return E_INVALIDARG; |
| + if (iid == IID_IUnknown || iid == IID_ITfTextEditSink) { |
| + *result = static_cast<ITfTextEditSink*>(this); |
| + } else if (iid == IID_ITfUIElementSink) { |
| + *result = static_cast<ITfUIElementSink*>(this); |
| + } else { |
| + *result = NULL; |
| + return E_NOINTERFACE; |
| + } |
| + AddRef(); |
| + return S_OK; |
| + } |
| + |
| + // ITfTextEditSink override. |
| + virtual STDMETHODIMP OnEndEdit(ITfContext* context, |
| + TfEditCookie read_only_cookie, |
| + ITfEditRecord* edit_record) OVERRIDE { |
| + if (!edit_record || !context) |
| + return E_INVALIDARG; |
| + if (text_updated_callback_.is_null()) |
| + 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. |
| + |
| + // |fetched_count| != 0 means there is at least one range that contains |
| + // updated texts. |
| + if (fetched_count != 0) |
| + text_updated_callback_.Run(); |
| + return S_OK; |
| + } |
| + |
| + // ITfUiElementSink override. |
| + virtual STDMETHODIMP 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); |
| + |
| + if (candidat_window_count_changed_callback_.is_null()) |
| + return S_OK; |
| + |
| + // Don't call if |element_id| is already handled. |
| + if (!insert_result.second) |
| + return S_OK; |
| + |
| + candidat_window_count_changed_callback_.Run( |
| + open_candidate_window_ids_.size()); |
| + |
| + return S_OK; |
| + } |
| + |
| + // ITfUiElementSink override. |
| + virtual STDMETHODIMP UpdateUIElement(DWORD element_id) { |
| + return S_OK; |
| + } |
| + |
| + // ITfUiElementSink override. |
| + virtual STDMETHODIMP EndUIElement(DWORD element_id) { |
| + if (open_candidate_window_ids_.erase(element_id) == 0) |
| + return S_OK; |
| + |
| + if (candidat_window_count_changed_callback_.is_null()) |
| + return S_OK; |
| + |
| + candidat_window_count_changed_callback_.Run( |
| + open_candidate_window_ids_.size()); |
| + |
| + return S_OK; |
| + } |
| + |
| + // TsfEventRouter override. |
| + virtual void Associate(ITfThreadMgr* thread_manager) OVERRIDE { |
| + DCHECK(base::win::IsTsfAwareRequired()) |
| + << "Do not call without TSF environment."; |
| + DCHECK(thread_manager); |
| + |
| + base::win::ScopedComPtr<ITfDocumentMgr> document_manager; |
| + if (FAILED(thread_manager->GetFocus(document_manager.Receive()))) |
| + return; |
| + EnsureDeassociated(); |
| + |
| + if (FAILED(document_manager->GetBase(context_.Receive()))) |
| + return; |
| + if (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))) |
| + return; |
| + if (FAILED(ui_source_.QueryFrom(ui_element_manager_))) |
| + return; |
| + ui_source_->AdviseSink(IID_ITfUIElementSink, |
| + static_cast<ITfUIElementSink*>(this), |
| + &ui_source_cookie_); |
| + } |
| + |
| + // TsfEventRouter override. |
| + virtual void EnsureDeassociated() OVERRIDE { |
| + DCHECK(base::win::IsTsfAwareRequired()) |
| + << "Do not call without TSF environment."; |
| + |
| + 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; |
| + } |
| + |
| + // TsfEventRouter override. |
| + virtual bool IsImeComposing() OVERRIDE { |
| + DCHECK(base::win::IsTsfAwareRequired()) |
| + << "Do not call without TSF environment."; |
| + if (!context_) |
| + return false; |
| + return IsImeComposingInternal(context_); |
| + } |
| + |
| + // TsfEventRouter override. |
| + virtual void SetTextUpdatedCallback( |
| + const TextUpdatedCallback& callback) OVERRIDE { |
| + text_updated_callback_ = callback; |
| + } |
| + |
| + // TsfEventRouter override. |
| + virtual void SetCandidateWindowStatusChangedCallback( |
| + const CandidateWindowCountChangedCallback& callback) OVERRIDE { |
| + candidat_window_count_changed_callback_ = callback; |
| + } |
| + |
| + private: |
| + // Returns true if the given |context| is in composing. |
| + static bool IsImeComposingInternal(ITfContext* context) { |
| + DCHECK(base::win::IsTsfAwareRequired()) |
| + << "Do not call without TSF environment."; |
| + DCHECK(context); |
| + base::win::ScopedComPtr<ITfContextComposition> context_composition; |
| + if (FAILED(context_composition.QueryFrom(context))) |
| + return false; |
| + base::win::ScopedComPtr<IEnumITfCompositionView> enum_composition_view; |
| + if (FAILED(context_composition->EnumCompositions( |
| + enum_composition_view.Receive()))) |
| + return false; |
| + base::win::ScopedComPtr<ITfCompositionView> composition_view; |
| + return enum_composition_view->Next(1, composition_view.Receive(), |
| + NULL) == S_OK; |
| + } |
| + |
| + // Returns true if the given |element_id| represents candidate window. |
| + bool 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)); |
| + } |
| + |
| + // Callback function fired when the text contents are updated. |
| + TextUpdatedCallback text_updated_callback_; |
| + |
| + CandidateWindowCountChangedCallback candidat_window_count_changed_callback_; |
| + |
| + // 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 cookie for |ui_source_|. |
| + DWORD ui_source_cookie_; |
| + |
| + // The set of currently opend candidate window ids. |
| + std::set<DWORD> open_candidate_window_ids_; |
| + |
| + // The reference count of this instance. |
| + volatile LONG ref_count_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(TsfEventRouterImpl); |
| +}; |
| + |
| +/////////////////////////////////////////////////////////////////////////////// |
| +// TsfEventRouter |
| + |
| +TsfEventRouter::TsfEventRouter() { |
| +} |
| + |
| +TsfEventRouter::~TsfEventRouter() { |
| +} |
| + |
| +TsfEventRouter* TsfEventRouter::Create() { |
| + return new TsfEventRouterImpl(); |
| +} |
| + |
| +} // namespace ui |