| 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 |