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 308