Home | History | Annotate | Download | only in win
      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/range/range.h"
     15 #include "ui/base/win/atl_module.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 ui::Range::InvalidRange if there
     60   // is no composition.
     61   static ui::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   ui::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_(ui::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 ui::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 ui::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 ui::Range::InvalidRange();
    223   base::win::ScopedComPtr<IEnumITfCompositionView> enum_composition_view;
    224   if (FAILED(context_composition->EnumCompositions(
    225       enum_composition_view.Receive())))
    226     return ui::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 ui::Range::InvalidRange();
    231 
    232   base::win::ScopedComPtr<ITfRange> range;
    233   if (FAILED(composition_view->GetRange(range.Receive())))
    234     return ui::Range::InvalidRange();
    235 
    236   base::win::ScopedComPtr<ITfRangeACP> range_acp;
    237   if (FAILED(range_acp.QueryFrom(range)))
    238     return ui::Range::InvalidRange();
    239 
    240   LONG start = 0;
    241   LONG length = 0;
    242   if (FAILED(range_acp->GetExtent(&start, &length)))
    243     return ui::Range::InvalidRange();
    244 
    245   return ui::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 ui::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