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