OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "ui/base/ime/win/tsf_event_router.h" | |
6 | |
7 #include <msctf.h> | |
8 #include <set> | |
9 #include <utility> | |
10 | |
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" | |
16 | |
17 namespace ui { | |
18 | |
19 | |
20 // TSFEventRouter::Delegate ------------------------------------ | |
21 | |
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 { | |
31 public: | |
32 BEGIN_COM_MAP(Delegate) | |
33 COM_INTERFACE_ENTRY(ITfUIElementSink) | |
34 COM_INTERFACE_ENTRY(ITfTextEditSink) | |
35 END_COM_MAP() | |
36 | |
37 Delegate(); | |
38 ~Delegate(); | |
39 | |
40 // ITfTextEditSink: | |
41 STDMETHOD(OnEndEdit)(ITfContext* context, TfEditCookie read_only_cookie, | |
42 ITfEditRecord* edit_record) OVERRIDE; | |
43 | |
44 // ITfUiElementSink: | |
45 STDMETHOD(BeginUIElement)(DWORD element_id, BOOL* is_show) OVERRIDE; | |
46 STDMETHOD(UpdateUIElement)(DWORD element_id) OVERRIDE; | |
47 STDMETHOD(EndUIElement)(DWORD element_id) OVERRIDE; | |
48 | |
49 // Sets |thread_manager| to be monitored. |thread_manager| can be NULL. | |
50 void SetManager(ITfThreadMgr* thread_manager); | |
51 | |
52 // Returns true if the IME is composing text. | |
53 bool IsImeComposing(); | |
54 | |
55 // Sets |router| to be forwarded TSF-related events. | |
56 void SetRouter(TSFEventRouter* router); | |
57 | |
58 private: | |
59 // Returns current composition range. Returns gfx::Range::InvalidRange if | |
60 // there is no composition. | |
61 static gfx::Range GetCompositionRange(ITfContext* context); | |
62 | |
63 // Returns true if the given |element_id| represents the candidate window. | |
64 bool IsCandidateWindowInternal(DWORD element_id); | |
65 | |
66 // A context associated with this class. | |
67 base::win::ScopedComPtr<ITfContext> context_; | |
68 | |
69 // The ITfSource associated with |context_|. | |
70 base::win::ScopedComPtr<ITfSource> context_source_; | |
71 | |
72 // The cookie for |context_source_|. | |
73 DWORD context_source_cookie_; | |
74 | |
75 // A UIElementMgr associated with this class. | |
76 base::win::ScopedComPtr<ITfUIElementMgr> ui_element_manager_; | |
77 | |
78 // The ITfSouce associated with |ui_element_manager_|. | |
79 base::win::ScopedComPtr<ITfSource> ui_source_; | |
80 | |
81 // The set of currently opened candidate window ids. | |
82 std::set<DWORD> open_candidate_window_ids_; | |
83 | |
84 // The cookie for |ui_source_|. | |
85 DWORD ui_source_cookie_; | |
86 | |
87 TSFEventRouter* router_; | |
88 gfx::Range previous_composition_range_; | |
89 | |
90 DISALLOW_COPY_AND_ASSIGN(Delegate); | |
91 }; | |
92 | |
93 TSFEventRouter::Delegate::Delegate() | |
94 : context_source_cookie_(TF_INVALID_COOKIE), | |
95 ui_source_cookie_(TF_INVALID_COOKIE), | |
96 router_(NULL), | |
97 previous_composition_range_(gfx::Range::InvalidRange()) { | |
98 } | |
99 | |
100 TSFEventRouter::Delegate::~Delegate() {} | |
101 | |
102 void TSFEventRouter::Delegate::SetRouter(TSFEventRouter* router) { | |
103 router_ = router; | |
104 } | |
105 | |
106 STDMETHODIMP TSFEventRouter::Delegate::OnEndEdit(ITfContext* context, | |
107 TfEditCookie read_only_cookie, | |
108 ITfEditRecord* edit_record) { | |
109 if (!edit_record || !context) | |
110 return E_INVALIDARG; | |
111 if (!router_) | |
112 return S_OK; | |
113 | |
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 | |
117 // updated text. | |
118 base::win::ScopedComPtr<IEnumTfRanges> ranges; | |
119 if (FAILED(edit_record->GetTextAndPropertyUpdates(TF_GTP_INCL_TEXT, NULL, 0, | |
120 ranges.Receive()))) | |
121 return S_OK; // Don't care about failures. | |
122 | |
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. | |
127 | |
128 const gfx::Range composition_range = GetCompositionRange(context); | |
129 | |
130 if (!previous_composition_range_.IsValid() && composition_range.IsValid()) | |
131 router_->OnTSFStartComposition(); | |
132 | |
133 // |fetched_count| != 0 means there is at least one range that contains | |
134 // updated text. | |
135 if (fetched_count != 0) | |
136 router_->OnTextUpdated(composition_range); | |
137 | |
138 if (previous_composition_range_.IsValid() && !composition_range.IsValid()) | |
139 router_->OnTSFEndComposition(); | |
140 | |
141 previous_composition_range_ = composition_range; | |
142 return S_OK; | |
143 } | |
144 | |
145 STDMETHODIMP TSFEventRouter::Delegate::BeginUIElement(DWORD element_id, | |
146 BOOL* is_show) { | |
147 if (is_show) | |
148 *is_show = TRUE; // Without this the UI element will not be shown. | |
149 | |
150 if (!IsCandidateWindowInternal(element_id)) | |
151 return S_OK; | |
152 | |
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()); | |
158 | |
159 return S_OK; | |
160 } | |
161 | |
162 STDMETHODIMP TSFEventRouter::Delegate::UpdateUIElement( | |
163 DWORD element_id) { | |
164 return S_OK; | |
165 } | |
166 | |
167 STDMETHODIMP TSFEventRouter::Delegate::EndUIElement( | |
168 DWORD element_id) { | |
169 if ((open_candidate_window_ids_.erase(element_id) != 0) && router_) | |
170 router_->OnCandidateWindowCountChanged(open_candidate_window_ids_.size()); | |
171 return S_OK; | |
172 } | |
173 | |
174 void TSFEventRouter::Delegate::SetManager( | |
175 ITfThreadMgr* thread_manager) { | |
176 context_.Release(); | |
177 | |
178 if (context_source_) { | |
179 context_source_->UnadviseSink(context_source_cookie_); | |
180 context_source_.Release(); | |
181 } | |
182 context_source_cookie_ = TF_INVALID_COOKIE; | |
183 | |
184 ui_element_manager_.Release(); | |
185 if (ui_source_) { | |
186 ui_source_->UnadviseSink(ui_source_cookie_); | |
187 ui_source_.Release(); | |
188 } | |
189 ui_source_cookie_ = TF_INVALID_COOKIE; | |
190 | |
191 if (!thread_manager) | |
192 return; | |
193 | |
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_))) | |
199 return; | |
200 context_source_->AdviseSink(IID_ITfTextEditSink, | |
201 static_cast<ITfTextEditSink*>(this), | |
202 &context_source_cookie_); | |
203 | |
204 if (FAILED(ui_element_manager_.QueryFrom(thread_manager)) || | |
205 FAILED(ui_source_.QueryFrom(ui_element_manager_))) | |
206 return; | |
207 ui_source_->AdviseSink(IID_ITfUIElementSink, | |
208 static_cast<ITfUIElementSink*>(this), | |
209 &ui_source_cookie_); | |
210 } | |
211 | |
212 bool TSFEventRouter::Delegate::IsImeComposing() { | |
213 return context_ && GetCompositionRange(context_).IsValid(); | |
214 } | |
215 | |
216 // static | |
217 gfx::Range TSFEventRouter::Delegate::GetCompositionRange( | |
218 ITfContext* context) { | |
219 DCHECK(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(), | |
229 NULL) != S_OK) | |
230 return gfx::Range::InvalidRange(); | |
231 | |
232 base::win::ScopedComPtr<ITfRange> range; | |
233 if (FAILED(composition_view->GetRange(range.Receive()))) | |
234 return gfx::Range::InvalidRange(); | |
235 | |
236 base::win::ScopedComPtr<ITfRangeACP> range_acp; | |
237 if (FAILED(range_acp.QueryFrom(range))) | |
238 return gfx::Range::InvalidRange(); | |
239 | |
240 LONG start = 0; | |
241 LONG length = 0; | |
242 if (FAILED(range_acp->GetExtent(&start, &length))) | |
243 return gfx::Range::InvalidRange(); | |
244 | |
245 return gfx::Range(start, start + length); | |
246 } | |
247 | |
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()))) | |
253 return false; | |
254 base::win::ScopedComPtr<ITfCandidateListUIElement> candidate_list_ui_element; | |
255 return SUCCEEDED(candidate_list_ui_element.QueryFrom(ui_element)); | |
256 } | |
257 | |
258 | |
259 // TSFEventRouter ------------------------------------------------------------ | |
260 | |
261 TSFEventRouter::TSFEventRouter(TSFEventRouterObserver* observer) | |
262 : observer_(observer), | |
263 delegate_(NULL) { | |
264 DCHECK(base::win::IsTSFAwareRequired()) | |
265 << "Do not use TSFEventRouter without TSF environment."; | |
266 DCHECK(observer_); | |
267 CComObject<Delegate>* delegate; | |
268 ui::win::CreateATLModuleIfNeeded(); | |
269 if (SUCCEEDED(CComObject<Delegate>::CreateInstance(&delegate))) { | |
270 delegate->AddRef(); | |
271 delegate_.Attach(delegate); | |
272 delegate_->SetRouter(this); | |
273 } | |
274 } | |
275 | |
276 TSFEventRouter::~TSFEventRouter() { | |
277 if (delegate_) { | |
278 delegate_->SetManager(NULL); | |
279 delegate_->SetRouter(NULL); | |
280 } | |
281 } | |
282 | |
283 bool TSFEventRouter::IsImeComposing() { | |
284 return delegate_->IsImeComposing(); | |
285 } | |
286 | |
287 void TSFEventRouter::OnCandidateWindowCountChanged(size_t window_count) { | |
288 observer_->OnCandidateWindowCountChanged(window_count); | |
289 } | |
290 | |
291 void TSFEventRouter::OnTSFStartComposition() { | |
292 observer_->OnTSFStartComposition(); | |
293 } | |
294 | |
295 void TSFEventRouter::OnTextUpdated(const gfx::Range& composition_range) { | |
296 observer_->OnTextUpdated(composition_range); | |
297 } | |
298 | |
299 void TSFEventRouter::OnTSFEndComposition() { | |
300 observer_->OnTSFEndComposition(); | |
301 } | |
302 | |
303 void TSFEventRouter::SetManager(ITfThreadMgr* thread_manager) { | |
304 delegate_->SetManager(thread_manager); | |
305 } | |
306 | |
307 } // namespace ui | |
OLD | NEW |