1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/base/ime/win/tsf_event_router.h"
11 #include "base/bind.h"
12 #include "base/win/scoped_comptr.h"
13 #include "base/win/metro.h"
14 #include "ui/base/win/atl_module.h"
15 #include "ui/gfx/range/range.h"
20 // TSFEventRouter::Delegate ------------------------------------
22 // The implementation class of ITfUIElementSink, whose member functions will be
23 // called back by TSF when the UI element status is changed, for example when
24 // the candidate window is opened or closed. This class also implements
25 // ITfTextEditSink, whose member function is called back by TSF when the text
26 // editting session is finished.
27 class ATL_NO_VTABLE TSFEventRouter::Delegate
28 : public ATL::CComObjectRootEx<CComSingleThreadModel>,
29 public ITfUIElementSink,
30 public ITfTextEditSink {
32 BEGIN_COM_MAP(Delegate)
33 COM_INTERFACE_ENTRY(ITfUIElementSink)
34 COM_INTERFACE_ENTRY(ITfTextEditSink)
41 STDMETHOD(OnEndEdit)(ITfContext* context, TfEditCookie read_only_cookie,
42 ITfEditRecord* edit_record) OVERRIDE;
45 STDMETHOD(BeginUIElement)(DWORD element_id, BOOL* is_show) OVERRIDE;
46 STDMETHOD(UpdateUIElement)(DWORD element_id) OVERRIDE;
47 STDMETHOD(EndUIElement)(DWORD element_id) OVERRIDE;
49 // Sets |thread_manager| to be monitored. |thread_manager| can be NULL.
50 void SetManager(ITfThreadMgr* thread_manager);
52 // Returns true if the IME is composing text.
53 bool IsImeComposing();
55 // Sets |router| to be forwarded TSF-related events.
56 void SetRouter(TSFEventRouter* router);
59 // Returns current composition range. Returns gfx::Range::InvalidRange if
60 // there is no composition.
61 static gfx::Range GetCompositionRange(ITfContext* context);
63 // Returns true if the given |element_id| represents the candidate window.
64 bool IsCandidateWindowInternal(DWORD element_id);
66 // A context associated with this class.
67 base::win::ScopedComPtr<ITfContext> context_;
69 // The ITfSource associated with |context_|.
70 base::win::ScopedComPtr<ITfSource> context_source_;
72 // The cookie for |context_source_|.
73 DWORD context_source_cookie_;
75 // A UIElementMgr associated with this class.
76 base::win::ScopedComPtr<ITfUIElementMgr> ui_element_manager_;
78 // The ITfSouce associated with |ui_element_manager_|.
79 base::win::ScopedComPtr<ITfSource> ui_source_;
81 // The set of currently opened candidate window ids.
82 std::set<DWORD> open_candidate_window_ids_;
84 // The cookie for |ui_source_|.
85 DWORD ui_source_cookie_;
87 TSFEventRouter* router_;
88 gfx::Range previous_composition_range_;
90 DISALLOW_COPY_AND_ASSIGN(Delegate);
93 TSFEventRouter::Delegate::Delegate()
94 : context_source_cookie_(TF_INVALID_COOKIE),
95 ui_source_cookie_(TF_INVALID_COOKIE),
97 previous_composition_range_(gfx::Range::InvalidRange()) {
100 TSFEventRouter::Delegate::~Delegate() {}
102 void TSFEventRouter::Delegate::SetRouter(TSFEventRouter* router) {
106 STDMETHODIMP TSFEventRouter::Delegate::OnEndEdit(ITfContext* context,
107 TfEditCookie read_only_cookie,
108 ITfEditRecord* edit_record) {
109 if (!edit_record || !context)
114 // |edit_record| can be used to obtain updated ranges in terms of text
115 // contents and/or text attributes. Here we are interested only in text update
116 // so we use TF_GTP_INCL_TEXT and check if there is any range which contains
118 base::win::ScopedComPtr<IEnumTfRanges> ranges;
119 if (FAILED(edit_record->GetTextAndPropertyUpdates(TF_GTP_INCL_TEXT, NULL, 0,
121 return S_OK; // Don't care about failures.
123 ULONG fetched_count = 0;
124 base::win::ScopedComPtr<ITfRange> range;
125 if (FAILED(ranges->Next(1, range.Receive(), &fetched_count)))
126 return S_OK; // Don't care about failures.
128 const gfx::Range composition_range = GetCompositionRange(context);
130 if (!previous_composition_range_.IsValid() && composition_range.IsValid())
131 router_->OnTSFStartComposition();
133 // |fetched_count| != 0 means there is at least one range that contains
135 if (fetched_count != 0)
136 router_->OnTextUpdated(composition_range);
138 if (previous_composition_range_.IsValid() && !composition_range.IsValid())
139 router_->OnTSFEndComposition();
141 previous_composition_range_ = composition_range;
145 STDMETHODIMP TSFEventRouter::Delegate::BeginUIElement(DWORD element_id,
148 *is_show = TRUE; // Without this the UI element will not be shown.
150 if (!IsCandidateWindowInternal(element_id))
153 std::pair<std::set<DWORD>::iterator, bool> insert_result =
154 open_candidate_window_ids_.insert(element_id);
155 // Don't call if |router_| is null or |element_id| is already handled.
156 if (router_ && insert_result.second)
157 router_->OnCandidateWindowCountChanged(open_candidate_window_ids_.size());
162 STDMETHODIMP TSFEventRouter::Delegate::UpdateUIElement(
167 STDMETHODIMP TSFEventRouter::Delegate::EndUIElement(
169 if ((open_candidate_window_ids_.erase(element_id) != 0) && router_)
170 router_->OnCandidateWindowCountChanged(open_candidate_window_ids_.size());
174 void TSFEventRouter::Delegate::SetManager(
175 ITfThreadMgr* thread_manager) {
178 if (context_source_) {
179 context_source_->UnadviseSink(context_source_cookie_);
180 context_source_.Release();
182 context_source_cookie_ = TF_INVALID_COOKIE;
184 ui_element_manager_.Release();
186 ui_source_->UnadviseSink(ui_source_cookie_);
187 ui_source_.Release();
189 ui_source_cookie_ = TF_INVALID_COOKIE;
194 base::win::ScopedComPtr<ITfDocumentMgr> document_manager;
195 if (FAILED(thread_manager->GetFocus(document_manager.Receive())) ||
196 !document_manager.get() ||
197 FAILED(document_manager->GetBase(context_.Receive())) ||
198 FAILED(context_source_.QueryFrom(context_)))
200 context_source_->AdviseSink(IID_ITfTextEditSink,
201 static_cast<ITfTextEditSink*>(this),
202 &context_source_cookie_);
204 if (FAILED(ui_element_manager_.QueryFrom(thread_manager)) ||
205 FAILED(ui_source_.QueryFrom(ui_element_manager_)))
207 ui_source_->AdviseSink(IID_ITfUIElementSink,
208 static_cast<ITfUIElementSink*>(this),
212 bool TSFEventRouter::Delegate::IsImeComposing() {
213 return context_ && GetCompositionRange(context_).IsValid();
217 gfx::Range TSFEventRouter::Delegate::GetCompositionRange(
218 ITfContext* context) {
220 base::win::ScopedComPtr<ITfContextComposition> context_composition;
221 if (FAILED(context_composition.QueryFrom(context)))
222 return gfx::Range::InvalidRange();
223 base::win::ScopedComPtr<IEnumITfCompositionView> enum_composition_view;
224 if (FAILED(context_composition->EnumCompositions(
225 enum_composition_view.Receive())))
226 return gfx::Range::InvalidRange();
227 base::win::ScopedComPtr<ITfCompositionView> composition_view;
228 if (enum_composition_view->Next(1, composition_view.Receive(),
230 return gfx::Range::InvalidRange();
232 base::win::ScopedComPtr<ITfRange> range;
233 if (FAILED(composition_view->GetRange(range.Receive())))
234 return gfx::Range::InvalidRange();
236 base::win::ScopedComPtr<ITfRangeACP> range_acp;
237 if (FAILED(range_acp.QueryFrom(range)))
238 return gfx::Range::InvalidRange();
242 if (FAILED(range_acp->GetExtent(&start, &length)))
243 return gfx::Range::InvalidRange();
245 return gfx::Range(start, start + length);
248 bool TSFEventRouter::Delegate::IsCandidateWindowInternal(DWORD element_id) {
249 DCHECK(ui_element_manager_.get());
250 base::win::ScopedComPtr<ITfUIElement> ui_element;
251 if (FAILED(ui_element_manager_->GetUIElement(element_id,
252 ui_element.Receive())))
254 base::win::ScopedComPtr<ITfCandidateListUIElement> candidate_list_ui_element;
255 return SUCCEEDED(candidate_list_ui_element.QueryFrom(ui_element));
259 // TSFEventRouter ------------------------------------------------------------
261 TSFEventRouter::TSFEventRouter(TSFEventRouterObserver* observer)
262 : observer_(observer),
264 DCHECK(base::win::IsTSFAwareRequired())
265 << "Do not use TSFEventRouter without TSF environment.";
267 CComObject<Delegate>* delegate;
268 ui::win::CreateATLModuleIfNeeded();
269 if (SUCCEEDED(CComObject<Delegate>::CreateInstance(&delegate))) {
271 delegate_.Attach(delegate);
272 delegate_->SetRouter(this);
276 TSFEventRouter::~TSFEventRouter() {
278 delegate_->SetManager(NULL);
279 delegate_->SetRouter(NULL);
283 bool TSFEventRouter::IsImeComposing() {
284 return delegate_->IsImeComposing();
287 void TSFEventRouter::OnCandidateWindowCountChanged(size_t window_count) {
288 observer_->OnCandidateWindowCountChanged(window_count);
291 void TSFEventRouter::OnTSFStartComposition() {
292 observer_->OnTSFStartComposition();
295 void TSFEventRouter::OnTextUpdated(const gfx::Range& composition_range) {
296 observer_->OnTextUpdated(composition_range);
299 void TSFEventRouter::OnTSFEndComposition() {
300 observer_->OnTSFEndComposition();
303 void TSFEventRouter::SetManager(ITfThreadMgr* thread_manager) {
304 delegate_->SetManager(thread_manager);