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/win/tsf_event_router.h" | |
6 | |
7 #include <msctf.h> | |
8 #include <set> | |
9 #include <utility> | |
10 #include "base/bind.h" | |
sky
2012/10/15 22:00:07
newline between 9/10.
Seigo Nonaka
2012/10/16 02:20:47
Done.
| |
11 #include "base/win/scoped_comptr.h" | |
12 #include "base/win/metro.h" | |
13 | |
14 namespace ui { | |
15 | |
16 class TsfEventRouterImpl : public TsfEventRouter, | |
17 public ITfUIElementSink, | |
18 public ITfTextEditSink { | |
19 public: | |
20 TsfEventRouterImpl() | |
21 : context_source_cookie_(TF_INVALID_COOKIE), | |
22 ui_source_cookie_(TF_INVALID_COOKIE), | |
23 ref_count_(0) {} | |
24 | |
25 virtual ~TsfEventRouterImpl() {} | |
26 | |
27 // ITfTextEditSink override. | |
28 virtual ULONG STDMETHODCALLTYPE AddRef() OVERRIDE { | |
29 return InterlockedIncrement(&ref_count_); | |
30 } | |
31 | |
32 // ITfTextEditSink override. | |
33 virtual ULONG STDMETHODCALLTYPE Release() OVERRIDE { | |
34 const LONG count = InterlockedDecrement(&ref_count_); | |
35 if (!count) { | |
36 delete this; | |
37 return 0; | |
38 } | |
39 return static_cast<ULONG>(count); | |
40 } | |
41 | |
42 // ITfTextEditSink override. | |
43 virtual STDMETHODIMP QueryInterface(REFIID iid, void** result) OVERRIDE { | |
44 if (!result) | |
45 return E_INVALIDARG; | |
46 if (iid == IID_IUnknown || iid == IID_ITfTextEditSink) { | |
47 *result = static_cast<ITfTextEditSink*>(this); | |
48 } else if (iid == IID_ITfUIElementSink) { | |
49 *result = static_cast<ITfUIElementSink*>(this); | |
50 } else { | |
51 *result = NULL; | |
52 return E_NOINTERFACE; | |
53 } | |
54 AddRef(); | |
55 return S_OK; | |
56 } | |
57 | |
58 // ITfTextEditSink override. | |
59 virtual STDMETHODIMP OnEndEdit(ITfContext* context, | |
60 TfEditCookie read_only_cookie, | |
61 ITfEditRecord* edit_record) OVERRIDE { | |
62 if (!edit_record || !context) | |
63 return E_INVALIDARG; | |
64 if (text_updated_callback_.is_null()) | |
65 return S_OK; | |
66 | |
67 // |edit_record| can be used to obtain updated ranges in terms of text | |
68 // contents and/or text attributes. Here we are interested only in text | |
69 // update so we use TF_GTP_INCL_TEXT and check if there is any range which | |
70 // contains updated text. | |
71 base::win::ScopedComPtr<IEnumTfRanges> ranges; | |
72 if (FAILED(edit_record->GetTextAndPropertyUpdates(TF_GTP_INCL_TEXT, | |
73 NULL, | |
74 0, | |
75 ranges.Receive()))) { | |
76 return S_OK; // don't care about failures. | |
77 } | |
78 | |
79 ULONG fetched_count = 0; | |
80 base::win::ScopedComPtr<ITfRange> range; | |
81 if (FAILED(ranges->Next(1, range.Receive(), &fetched_count))) | |
82 return S_OK; // don't care about failures. | |
83 | |
84 // |fetched_count| != 0 means there is at least one range that contains | |
85 // updated texts. | |
86 if (fetched_count != 0) | |
87 text_updated_callback_.Run(); | |
88 return S_OK; | |
89 } | |
90 | |
91 // ITfUiElementSink override. | |
92 virtual STDMETHODIMP BeginUIElement(DWORD element_id, BOOL* is_show) { | |
93 if (is_show) | |
94 *is_show = TRUE; // without this the UI element will not be shown. | |
95 | |
96 if (!IsCandidateWindowInternal(element_id)) | |
97 return S_OK; | |
98 | |
99 std::pair<std::set<DWORD>::iterator, bool> insert_result = | |
100 open_candidate_window_ids_.insert(element_id); | |
101 | |
102 if (candidat_window_count_changed_callback_.is_null()) | |
103 return S_OK; | |
104 | |
105 // Don't call if |element_id| is already handled. | |
106 if (!insert_result.second) | |
107 return S_OK; | |
108 | |
109 candidat_window_count_changed_callback_.Run( | |
110 open_candidate_window_ids_.size()); | |
111 | |
112 return S_OK; | |
113 } | |
114 | |
115 // ITfUiElementSink override. | |
116 virtual STDMETHODIMP UpdateUIElement(DWORD element_id) { | |
117 return S_OK; | |
118 } | |
119 | |
120 // ITfUiElementSink override. | |
121 virtual STDMETHODIMP EndUIElement(DWORD element_id) { | |
122 if (open_candidate_window_ids_.erase(element_id) == 0) | |
123 return S_OK; | |
124 | |
125 if (candidat_window_count_changed_callback_.is_null()) | |
126 return S_OK; | |
127 | |
128 candidat_window_count_changed_callback_.Run( | |
129 open_candidate_window_ids_.size()); | |
130 | |
131 return S_OK; | |
132 } | |
133 | |
134 // TsfEventRouter override. | |
135 virtual void Associate(ITfThreadMgr* thread_manager) OVERRIDE { | |
136 DCHECK(base::win::IsTsfAwareRequired()) | |
137 << "Do not call without TSF environment."; | |
138 DCHECK(thread_manager); | |
139 | |
140 base::win::ScopedComPtr<ITfDocumentMgr> document_manager; | |
141 if (FAILED(thread_manager->GetFocus(document_manager.Receive()))) | |
142 return; | |
143 EnsureDeassociated(); | |
144 | |
145 if (FAILED(document_manager->GetBase(context_.Receive()))) | |
146 return; | |
147 if (FAILED(context_source_.QueryFrom(context_))) | |
148 return; | |
149 context_source_->AdviseSink(IID_ITfTextEditSink, | |
150 static_cast<ITfTextEditSink*>(this), | |
151 &context_source_cookie_); | |
152 | |
153 if (FAILED(ui_element_manager_.QueryFrom(thread_manager))) | |
154 return; | |
155 if (FAILED(ui_source_.QueryFrom(ui_element_manager_))) | |
156 return; | |
157 ui_source_->AdviseSink(IID_ITfUIElementSink, | |
158 static_cast<ITfUIElementSink*>(this), | |
159 &ui_source_cookie_); | |
160 } | |
161 | |
162 // TsfEventRouter override. | |
163 virtual void EnsureDeassociated() OVERRIDE { | |
164 DCHECK(base::win::IsTsfAwareRequired()) | |
165 << "Do not call without TSF environment."; | |
166 | |
167 context_.Release(); | |
168 | |
169 if (context_source_) { | |
170 context_source_->UnadviseSink(context_source_cookie_); | |
171 context_source_.Release(); | |
172 } | |
173 context_source_cookie_ = TF_INVALID_COOKIE; | |
174 | |
175 ui_element_manager_.Release(); | |
176 if (ui_source_) { | |
177 ui_source_->UnadviseSink(ui_source_cookie_); | |
178 ui_source_.Release(); | |
179 } | |
180 ui_source_cookie_ = TF_INVALID_COOKIE; | |
181 } | |
182 | |
183 // TsfEventRouter override. | |
184 virtual bool IsImeComposing() OVERRIDE { | |
185 DCHECK(base::win::IsTsfAwareRequired()) | |
186 << "Do not call without TSF environment."; | |
187 if (!context_) | |
188 return false; | |
189 return IsImeComposingInternal(context_); | |
190 } | |
191 | |
192 // TsfEventRouter override. | |
193 virtual void SetTextUpdatedCallback( | |
194 const TextUpdatedCallback& callback) OVERRIDE { | |
195 text_updated_callback_ = callback; | |
196 } | |
197 | |
198 // TsfEventRouter override. | |
199 virtual void SetCandidateWindowStatusChangedCallback( | |
200 const CandidateWindowCountChangedCallback& callback) OVERRIDE { | |
201 candidat_window_count_changed_callback_ = callback; | |
202 } | |
203 | |
204 private: | |
205 // Returns true if the given |context| is in composing. | |
206 static bool IsImeComposingInternal(ITfContext* context) { | |
207 DCHECK(base::win::IsTsfAwareRequired()) | |
208 << "Do not call without TSF environment."; | |
209 DCHECK(context); | |
210 base::win::ScopedComPtr<ITfContextComposition> context_composition; | |
211 if (FAILED(context_composition.QueryFrom(context))) | |
212 return false; | |
213 base::win::ScopedComPtr<IEnumITfCompositionView> enum_composition_view; | |
214 if (FAILED(context_composition->EnumCompositions( | |
215 enum_composition_view.Receive()))) | |
216 return false; | |
217 base::win::ScopedComPtr<ITfCompositionView> composition_view; | |
218 return enum_composition_view->Next(1, composition_view.Receive(), | |
219 NULL) == S_OK; | |
220 } | |
221 | |
222 // Returns true if the given |element_id| represents candidate window. | |
223 bool IsCandidateWindowInternal(DWORD element_id) { | |
224 DCHECK(ui_element_manager_.get()); | |
225 base::win::ScopedComPtr<ITfUIElement> ui_element; | |
226 if (FAILED(ui_element_manager_->GetUIElement(element_id, | |
227 ui_element.Receive()))) { | |
228 return false; | |
229 } | |
230 base::win::ScopedComPtr<ITfCandidateListUIElement> | |
231 candidate_list_ui_element; | |
232 return SUCCEEDED(candidate_list_ui_element.QueryFrom(ui_element)); | |
233 } | |
234 | |
235 // Callback function fired when the text contents are updated. | |
236 TextUpdatedCallback text_updated_callback_; | |
237 | |
238 CandidateWindowCountChangedCallback candidat_window_count_changed_callback_; | |
239 | |
240 // A context associated with this class. | |
241 base::win::ScopedComPtr<ITfContext> context_; | |
242 | |
243 // The ITfSource associated with |context_|. | |
244 base::win::ScopedComPtr<ITfSource> context_source_; | |
245 | |
246 // The cookie for |context_source_|. | |
247 DWORD context_source_cookie_; | |
248 | |
249 // A UiElementMgr associated with this class. | |
250 base::win::ScopedComPtr<ITfUIElementMgr> ui_element_manager_; | |
251 | |
252 // The ITfSouce associated with |ui_element_manager_|. | |
253 base::win::ScopedComPtr<ITfSource> ui_source_; | |
254 | |
255 // The cookie for |ui_source_|. | |
256 DWORD ui_source_cookie_; | |
257 | |
258 // The set of currently opend candidate window ids. | |
259 std::set<DWORD> open_candidate_window_ids_; | |
260 | |
261 // The reference count of this instance. | |
262 volatile LONG ref_count_; | |
263 | |
264 DISALLOW_COPY_AND_ASSIGN(TsfEventRouterImpl); | |
265 }; | |
266 | |
267 /////////////////////////////////////////////////////////////////////////////// | |
268 // TsfEventRouter | |
269 | |
270 TsfEventRouter::TsfEventRouter() { | |
271 } | |
272 | |
273 TsfEventRouter::~TsfEventRouter() { | |
274 } | |
275 | |
276 TsfEventRouter* TsfEventRouter::Create() { | |
277 return new TsfEventRouterImpl(); | |
278 } | |
279 | |
280 } // namespace ui | |
OLD | NEW |