| 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..1036fc3b1808890794bf1623e1b11c3731b0ce7d
|
| --- /dev/null
|
| +++ b/ui/base/win/tsf_event_router.cc
|
| @@ -0,0 +1,288 @@
|
| +// 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"
|
| +#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 SetManager(ITfThreadMgr* manager) OVERRIDE {
|
| + EnsureDeassociated();
|
| + if (manager)
|
| + Associate(manager);
|
| + }
|
| +
|
| + // 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));
|
| + }
|
| +
|
| + // Associates this class with specified |manager|.
|
| + void Associate(ITfThreadMgr* thread_manager) {
|
| + 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;
|
| +
|
| + 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_);
|
| + }
|
| +
|
| + // Resets the association, this function is safe to call if there is no
|
| + // associated thread manager.
|
| + void EnsureDeassociated() {
|
| + 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;
|
| + }
|
| +
|
| + // 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
|
|
|