• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
Delegate()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 
~Delegate()100 TSFEventRouter::Delegate::~Delegate() {}
101 
SetRouter(TSFEventRouter * router)102 void TSFEventRouter::Delegate::SetRouter(TSFEventRouter* router) {
103   router_ = router;
104 }
105 
OnEndEdit(ITfContext * context,TfEditCookie read_only_cookie,ITfEditRecord * edit_record)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 
BeginUIElement(DWORD element_id,BOOL * is_show)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 
UpdateUIElement(DWORD element_id)162 STDMETHODIMP TSFEventRouter::Delegate::UpdateUIElement(
163     DWORD element_id) {
164   return S_OK;
165 }
166 
EndUIElement(DWORD element_id)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 
SetManager(ITfThreadMgr * thread_manager)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 
IsImeComposing()212 bool TSFEventRouter::Delegate::IsImeComposing() {
213   return context_ && GetCompositionRange(context_).IsValid();
214 }
215 
216 // static
GetCompositionRange(ITfContext * context)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 
IsCandidateWindowInternal(DWORD element_id)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 
TSFEventRouter(TSFEventRouterObserver * observer)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 
~TSFEventRouter()276 TSFEventRouter::~TSFEventRouter() {
277   if (delegate_) {
278     delegate_->SetManager(NULL);
279     delegate_->SetRouter(NULL);
280   }
281 }
282 
IsImeComposing()283 bool TSFEventRouter::IsImeComposing() {
284   return delegate_->IsImeComposing();
285 }
286 
OnCandidateWindowCountChanged(size_t window_count)287 void TSFEventRouter::OnCandidateWindowCountChanged(size_t window_count) {
288   observer_->OnCandidateWindowCountChanged(window_count);
289 }
290 
OnTSFStartComposition()291 void TSFEventRouter::OnTSFStartComposition() {
292   observer_->OnTSFStartComposition();
293 }
294 
OnTextUpdated(const gfx::Range & composition_range)295 void TSFEventRouter::OnTextUpdated(const gfx::Range& composition_range) {
296   observer_->OnTextUpdated(composition_range);
297 }
298 
OnTSFEndComposition()299 void TSFEventRouter::OnTSFEndComposition() {
300   observer_->OnTSFEndComposition();
301 }
302 
SetManager(ITfThreadMgr * thread_manager)303 void TSFEventRouter::SetManager(ITfThreadMgr* thread_manager) {
304   delegate_->SetManager(thread_manager);
305 }
306 
307 }  // namespace ui
308