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 #define INITGUID  // required for GUID_PROP_INPUTSCOPE
      6 #include "ui/base/ime/win/tsf_text_store.h"
      7 
      8 #include <InputScope.h>
      9 #include <OleCtl.h>
     10 
     11 #include "base/win/scoped_variant.h"
     12 #include "ui/base/ime/text_input_client.h"
     13 #include "ui/base/ime/win/tsf_input_scope.h"
     14 #include "ui/gfx/rect.h"
     15 
     16 namespace ui {
     17 namespace {
     18 
     19 // We support only one view.
     20 const TsViewCookie kViewCookie = 1;
     21 
     22 }  // namespace
     23 
     24 TSFTextStore::TSFTextStore()
     25     : ref_count_(0),
     26       text_store_acp_sink_mask_(0),
     27       window_handle_(NULL),
     28       text_input_client_(NULL),
     29       committed_size_(0),
     30       edit_flag_(false),
     31       current_lock_type_(0) {
     32   if (FAILED(category_manager_.CreateInstance(CLSID_TF_CategoryMgr))) {
     33     LOG(FATAL) << "Failed to initialize CategoryMgr.";
     34     return;
     35   }
     36   if (FAILED(display_attribute_manager_.CreateInstance(
     37           CLSID_TF_DisplayAttributeMgr))) {
     38     LOG(FATAL) << "Failed to initialize DisplayAttributeMgr.";
     39     return;
     40   }
     41 }
     42 
     43 TSFTextStore::~TSFTextStore() {
     44 }
     45 
     46 ULONG STDMETHODCALLTYPE TSFTextStore::AddRef() {
     47   return InterlockedIncrement(&ref_count_);
     48 }
     49 
     50 ULONG STDMETHODCALLTYPE TSFTextStore::Release() {
     51   const LONG count = InterlockedDecrement(&ref_count_);
     52   if (!count) {
     53     delete this;
     54     return 0;
     55   }
     56   return static_cast<ULONG>(count);
     57 }
     58 
     59 STDMETHODIMP TSFTextStore::QueryInterface(REFIID iid, void** result) {
     60   if (iid == IID_IUnknown || iid == IID_ITextStoreACP) {
     61     *result = static_cast<ITextStoreACP*>(this);
     62   } else if (iid == IID_ITfContextOwnerCompositionSink) {
     63     *result = static_cast<ITfContextOwnerCompositionSink*>(this);
     64   } else if (iid == IID_ITfTextEditSink) {
     65     *result = static_cast<ITfTextEditSink*>(this);
     66   } else {
     67     *result = NULL;
     68     return E_NOINTERFACE;
     69   }
     70   AddRef();
     71   return S_OK;
     72 }
     73 
     74 STDMETHODIMP TSFTextStore::AdviseSink(REFIID iid,
     75                                       IUnknown* unknown,
     76                                       DWORD mask) {
     77   if (!IsEqualGUID(iid, IID_ITextStoreACPSink))
     78     return E_INVALIDARG;
     79   if (text_store_acp_sink_) {
     80     if (text_store_acp_sink_.IsSameObject(unknown)) {
     81       text_store_acp_sink_mask_ = mask;
     82       return S_OK;
     83     } else {
     84       return CONNECT_E_ADVISELIMIT;
     85     }
     86   }
     87   if (FAILED(text_store_acp_sink_.QueryFrom(unknown)))
     88     return E_UNEXPECTED;
     89   text_store_acp_sink_mask_ = mask;
     90 
     91   return S_OK;
     92 }
     93 
     94 STDMETHODIMP TSFTextStore::FindNextAttrTransition(
     95     LONG acp_start,
     96     LONG acp_halt,
     97     ULONG num_filter_attributes,
     98     const TS_ATTRID* filter_attributes,
     99     DWORD flags,
    100     LONG* acp_next,
    101     BOOL* found,
    102     LONG* found_offset) {
    103   if (!acp_next || !found || !found_offset)
    104     return E_INVALIDARG;
    105   // We don't support any attributes.
    106   // So we always return "not found".
    107   *acp_next = 0;
    108   *found = FALSE;
    109   *found_offset = 0;
    110   return S_OK;
    111 }
    112 
    113 STDMETHODIMP TSFTextStore::GetACPFromPoint(TsViewCookie view_cookie,
    114                                            const POINT* point,
    115                                            DWORD flags,
    116                                            LONG* acp) {
    117   NOTIMPLEMENTED();
    118   if (view_cookie != kViewCookie)
    119     return E_INVALIDARG;
    120   return E_NOTIMPL;
    121 }
    122 
    123 STDMETHODIMP TSFTextStore::GetActiveView(TsViewCookie* view_cookie) {
    124   if (!view_cookie)
    125     return E_INVALIDARG;
    126   // We support only one view.
    127   *view_cookie = kViewCookie;
    128   return S_OK;
    129 }
    130 
    131 STDMETHODIMP TSFTextStore::GetEmbedded(LONG acp_pos,
    132                                        REFGUID service,
    133                                        REFIID iid,
    134                                        IUnknown** unknown) {
    135   // We don't support any embedded objects.
    136   NOTIMPLEMENTED();
    137   if (!unknown)
    138     return E_INVALIDARG;
    139   *unknown = NULL;
    140   return E_NOTIMPL;
    141 }
    142 
    143 STDMETHODIMP TSFTextStore::GetEndACP(LONG* acp) {
    144   if (!acp)
    145     return E_INVALIDARG;
    146   if (!HasReadLock())
    147     return TS_E_NOLOCK;
    148   *acp = string_buffer_.size();
    149   return S_OK;
    150 }
    151 
    152 STDMETHODIMP TSFTextStore::GetFormattedText(LONG acp_start, LONG acp_end,
    153                                             IDataObject** data_object) {
    154   NOTIMPLEMENTED();
    155   return E_NOTIMPL;
    156 }
    157 
    158 STDMETHODIMP TSFTextStore::GetScreenExt(TsViewCookie view_cookie, RECT* rect) {
    159   if (view_cookie != kViewCookie)
    160     return E_INVALIDARG;
    161   if (!rect)
    162     return E_INVALIDARG;
    163 
    164   // {0, 0, 0, 0} means that the document rect is not currently displayed.
    165   SetRect(rect, 0, 0, 0, 0);
    166 
    167   if (!IsWindow(window_handle_))
    168     return E_FAIL;
    169 
    170   // Currently ui::TextInputClient does not expose the document rect. So use
    171   // the Win32 client rectangle instead.
    172   // TODO(yukawa): Upgrade TextInputClient so that the client can retrieve the
    173   // document rectangle.
    174   RECT client_rect = {};
    175   if (!GetClientRect(window_handle_, &client_rect))
    176     return E_FAIL;
    177   POINT left_top = {client_rect.left, client_rect.top};
    178   POINT right_bottom = {client_rect.right, client_rect.bottom};
    179   if (!ClientToScreen(window_handle_, &left_top))
    180     return E_FAIL;
    181   if (!ClientToScreen(window_handle_, &right_bottom))
    182     return E_FAIL;
    183 
    184   rect->left = left_top.x;
    185   rect->top = left_top.y;
    186   rect->right = right_bottom.x;
    187   rect->bottom = right_bottom.y;
    188   return S_OK;
    189 }
    190 
    191 STDMETHODIMP TSFTextStore::GetSelection(ULONG selection_index,
    192                                         ULONG selection_buffer_size,
    193                                         TS_SELECTION_ACP* selection_buffer,
    194                                         ULONG* fetched_count) {
    195   if (!selection_buffer)
    196     return E_INVALIDARG;
    197   if (!fetched_count)
    198     return E_INVALIDARG;
    199   if (!HasReadLock())
    200     return TS_E_NOLOCK;
    201   *fetched_count = 0;
    202   if ((selection_buffer_size > 0) &&
    203       ((selection_index == 0) || (selection_index == TS_DEFAULT_SELECTION))) {
    204     selection_buffer[0].acpStart = selection_.start();
    205     selection_buffer[0].acpEnd = selection_.end();
    206     selection_buffer[0].style.ase = TS_AE_END;
    207     selection_buffer[0].style.fInterimChar = FALSE;
    208     *fetched_count = 1;
    209   }
    210   return S_OK;
    211 }
    212 
    213 STDMETHODIMP TSFTextStore::GetStatus(TS_STATUS* status) {
    214   if (!status)
    215     return E_INVALIDARG;
    216 
    217   status->dwDynamicFlags = 0;
    218   // We use transitory contexts and we don't support hidden text.
    219   status->dwStaticFlags = TS_SS_TRANSITORY | TS_SS_NOHIDDENTEXT;
    220 
    221   return S_OK;
    222 }
    223 
    224 STDMETHODIMP TSFTextStore::GetText(LONG acp_start,
    225                                    LONG acp_end,
    226                                    wchar_t* text_buffer,
    227                                    ULONG text_buffer_size,
    228                                    ULONG* text_buffer_copied,
    229                                    TS_RUNINFO* run_info_buffer,
    230                                    ULONG run_info_buffer_size,
    231                                    ULONG* run_info_buffer_copied,
    232                                    LONG* next_acp) {
    233   if (!text_buffer_copied || !run_info_buffer_copied)
    234     return E_INVALIDARG;
    235   if (!text_buffer && text_buffer_size != 0)
    236     return E_INVALIDARG;
    237   if (!run_info_buffer && run_info_buffer_size != 0)
    238     return E_INVALIDARG;
    239   if (!next_acp)
    240     return E_INVALIDARG;
    241   if (!HasReadLock())
    242     return TF_E_NOLOCK;
    243   const LONG string_buffer_size = string_buffer_.size();
    244   if (acp_end == -1)
    245     acp_end = string_buffer_size;
    246   if (!((0 <= acp_start) &&
    247         (acp_start <= acp_end) &&
    248         (acp_end <= string_buffer_size))) {
    249     return TF_E_INVALIDPOS;
    250   }
    251   acp_end = std::min(acp_end, acp_start + static_cast<LONG>(text_buffer_size));
    252   *text_buffer_copied = acp_end - acp_start;
    253 
    254   const string16& result =
    255       string_buffer_.substr(acp_start, *text_buffer_copied);
    256   for (size_t i = 0; i < result.size(); ++i) {
    257     text_buffer[i] = result[i];
    258   }
    259 
    260   if (run_info_buffer_size) {
    261     run_info_buffer[0].uCount = *text_buffer_copied;
    262     run_info_buffer[0].type = TS_RT_PLAIN;
    263     *run_info_buffer_copied = 1;
    264   }
    265 
    266   *next_acp = acp_end;
    267   return S_OK;
    268 }
    269 
    270 STDMETHODIMP TSFTextStore::GetTextExt(TsViewCookie view_cookie,
    271                                       LONG acp_start,
    272                                       LONG acp_end,
    273                                       RECT* rect,
    274                                       BOOL* clipped) {
    275   if (!rect || !clipped)
    276     return E_INVALIDARG;
    277   if (!text_input_client_)
    278     return E_UNEXPECTED;
    279   if (view_cookie != kViewCookie)
    280     return E_INVALIDARG;
    281   if (!HasReadLock())
    282     return TS_E_NOLOCK;
    283   if (!((static_cast<LONG>(committed_size_) <= acp_start) &&
    284        (acp_start <= acp_end) &&
    285        (acp_end <= static_cast<LONG>(string_buffer_.size())))) {
    286     return TS_E_INVALIDPOS;
    287   }
    288 
    289   // According to a behavior of notepad.exe and wordpad.exe, top left corner of
    290   // rect indicates a first character's one, and bottom right corner of rect
    291   // indicates a last character's one.
    292   // We use RECT instead of gfx::Rect since left position may be bigger than
    293   // right position when composition has multiple lines.
    294   RECT result;
    295   gfx::Rect tmp_rect;
    296   const uint32 start_pos = acp_start - committed_size_;
    297   const uint32 end_pos = acp_end - committed_size_;
    298 
    299   if (start_pos == end_pos) {
    300     // According to MSDN document, if |acp_start| and |acp_end| are equal it is
    301     // OK to just return E_INVALIDARG.
    302     // http://msdn.microsoft.com/en-us/library/ms538435
    303     // But when using Pinin IME of Windows 8, this method is called with the
    304     // equal values of |acp_start| and |acp_end|. So we handle this condition.
    305     if (start_pos == 0) {
    306       if (text_input_client_->GetCompositionCharacterBounds(0, &tmp_rect)) {
    307         tmp_rect.set_width(0);
    308         result = tmp_rect.ToRECT();
    309       } else if (string_buffer_.size() == committed_size_) {
    310         result = text_input_client_->GetCaretBounds().ToRECT();
    311       } else {
    312         return TS_E_NOLAYOUT;
    313       }
    314     } else if (text_input_client_->GetCompositionCharacterBounds(start_pos - 1,
    315                                                                  &tmp_rect)) {
    316       result.left = tmp_rect.right();
    317       result.right = tmp_rect.right();
    318       result.top = tmp_rect.y();
    319       result.bottom = tmp_rect.bottom();
    320     } else {
    321       return TS_E_NOLAYOUT;
    322     }
    323   } else {
    324     if (text_input_client_->GetCompositionCharacterBounds(start_pos,
    325                                                           &tmp_rect)) {
    326       result.left = tmp_rect.x();
    327       result.top = tmp_rect.y();
    328       result.right = tmp_rect.right();
    329       result.bottom = tmp_rect.bottom();
    330       if (text_input_client_->GetCompositionCharacterBounds(end_pos - 1,
    331                                                             &tmp_rect)) {
    332         result.right = tmp_rect.right();
    333         result.bottom = tmp_rect.bottom();
    334       } else {
    335         // We may not be able to get the last character bounds, so we use the
    336         // first character bounds instead of returning TS_E_NOLAYOUT.
    337       }
    338     } else {
    339       // Hack for PPAPI flash. PPAPI flash does not support GetCaretBounds, so
    340       // it's better to return previous caret rectangle instead.
    341       // TODO(nona, kinaba): Remove this hack.
    342       if (start_pos == 0) {
    343         result = text_input_client_->GetCaretBounds().ToRECT();
    344       } else {
    345         return TS_E_NOLAYOUT;
    346       }
    347     }
    348   }
    349 
    350   *rect =  result;
    351   *clipped = FALSE;
    352   return S_OK;
    353 }
    354 
    355 STDMETHODIMP TSFTextStore::GetWnd(TsViewCookie view_cookie,
    356                                   HWND* window_handle) {
    357   if (!window_handle)
    358     return E_INVALIDARG;
    359   if (view_cookie != kViewCookie)
    360     return E_INVALIDARG;
    361   *window_handle = window_handle_;
    362   return S_OK;
    363 }
    364 
    365 STDMETHODIMP TSFTextStore::InsertEmbedded(DWORD flags,
    366                                           LONG acp_start,
    367                                           LONG acp_end,
    368                                           IDataObject* data_object,
    369                                           TS_TEXTCHANGE* change) {
    370   // We don't support any embedded objects.
    371   NOTIMPLEMENTED();
    372   return E_NOTIMPL;
    373 }
    374 
    375 STDMETHODIMP TSFTextStore::InsertEmbeddedAtSelection(DWORD flags,
    376                                                      IDataObject* data_object,
    377                                                      LONG* acp_start,
    378                                                      LONG* acp_end,
    379                                                      TS_TEXTCHANGE* change) {
    380   // We don't support any embedded objects.
    381   NOTIMPLEMENTED();
    382   return E_NOTIMPL;
    383 }
    384 
    385 STDMETHODIMP TSFTextStore::InsertTextAtSelection(DWORD flags,
    386                                                  const wchar_t* text_buffer,
    387                                                  ULONG text_buffer_size,
    388                                                  LONG* acp_start,
    389                                                  LONG* acp_end,
    390                                                  TS_TEXTCHANGE* text_change) {
    391   const LONG start_pos = selection_.start();
    392   const LONG end_pos = selection_.end();
    393   const LONG new_end_pos = start_pos + text_buffer_size;
    394 
    395   if (flags & TS_IAS_QUERYONLY) {
    396     if (!HasReadLock())
    397       return TS_E_NOLOCK;
    398     if (acp_start)
    399       *acp_start = start_pos;
    400     if (acp_end) {
    401       *acp_end = end_pos;
    402     }
    403     return S_OK;
    404   }
    405 
    406   if (!HasReadWriteLock())
    407     return TS_E_NOLOCK;
    408   if (!text_buffer)
    409     return E_INVALIDARG;
    410 
    411   DCHECK_LE(start_pos, end_pos);
    412   string_buffer_ = string_buffer_.substr(0, start_pos) +
    413                    string16(text_buffer, text_buffer + text_buffer_size) +
    414                    string_buffer_.substr(end_pos);
    415   if (acp_start)
    416     *acp_start = start_pos;
    417   if (acp_end)
    418     *acp_end = new_end_pos;
    419   if (text_change) {
    420     text_change->acpStart = start_pos;
    421     text_change->acpOldEnd = end_pos;
    422     text_change->acpNewEnd = new_end_pos;
    423   }
    424   selection_.set_start(start_pos);
    425   selection_.set_end(new_end_pos);
    426   return S_OK;
    427 }
    428 
    429 STDMETHODIMP TSFTextStore::QueryInsert(
    430     LONG acp_test_start,
    431     LONG acp_test_end,
    432     ULONG text_size,
    433     LONG* acp_result_start,
    434     LONG* acp_result_end) {
    435   if (!acp_result_start || !acp_result_end)
    436     return E_INVALIDARG;
    437   if (!((static_cast<LONG>(committed_size_) <= acp_test_start) &&
    438         (acp_test_start <= acp_test_end) &&
    439         (acp_test_end <= static_cast<LONG>(string_buffer_.size())))) {
    440     return E_INVALIDARG;
    441   }
    442   *acp_result_start = acp_test_start;
    443   *acp_result_end = acp_test_start + text_size;
    444   return S_OK;
    445 }
    446 
    447 STDMETHODIMP TSFTextStore::QueryInsertEmbedded(const GUID* service,
    448                                                const FORMATETC* format,
    449                                                BOOL* insertable) {
    450   if (!format)
    451     return E_INVALIDARG;
    452   // We don't support any embedded objects.
    453   if (insertable)
    454     *insertable = FALSE;
    455   return S_OK;
    456 }
    457 
    458 STDMETHODIMP TSFTextStore::RequestAttrsAtPosition(
    459     LONG acp_pos,
    460     ULONG attribute_buffer_size,
    461     const TS_ATTRID* attribute_buffer,
    462     DWORD flags) {
    463   // We don't support any document attributes.
    464   // This method just returns S_OK, and the subsequently called
    465   // RetrieveRequestedAttrs() returns 0 as the number of supported attributes.
    466   return S_OK;
    467 }
    468 
    469 STDMETHODIMP TSFTextStore::RequestAttrsTransitioningAtPosition(
    470     LONG acp_pos,
    471     ULONG attribute_buffer_size,
    472     const TS_ATTRID* attribute_buffer,
    473     DWORD flags) {
    474   // We don't support any document attributes.
    475   // This method just returns S_OK, and the subsequently called
    476   // RetrieveRequestedAttrs() returns 0 as the number of supported attributes.
    477   return S_OK;
    478 }
    479 
    480 STDMETHODIMP TSFTextStore::RequestLock(DWORD lock_flags, HRESULT* result) {
    481   if (!text_store_acp_sink_.get())
    482     return E_FAIL;
    483   if (!result)
    484     return E_INVALIDARG;
    485 
    486   if (current_lock_type_ != 0) {
    487     if (lock_flags & TS_LF_SYNC) {
    488       // Can't lock synchronously.
    489       *result = TS_E_SYNCHRONOUS;
    490       return S_OK;
    491     }
    492     // Queue the lock request.
    493     lock_queue_.push_back(lock_flags & TS_LF_READWRITE);
    494     *result = TS_S_ASYNC;
    495     return S_OK;
    496   }
    497 
    498   // Lock
    499   current_lock_type_ = (lock_flags & TS_LF_READWRITE);
    500 
    501   edit_flag_ = false;
    502   const size_t last_committed_size = committed_size_;
    503 
    504   // Grant the lock.
    505   *result = text_store_acp_sink_->OnLockGranted(current_lock_type_);
    506 
    507   // Unlock
    508   current_lock_type_ = 0;
    509 
    510   // Handles the pending lock requests.
    511   while (!lock_queue_.empty()) {
    512     current_lock_type_ = lock_queue_.front();
    513     lock_queue_.pop_front();
    514     text_store_acp_sink_->OnLockGranted(current_lock_type_);
    515     current_lock_type_ = 0;
    516   }
    517 
    518   if (!edit_flag_) {
    519     return S_OK;
    520   }
    521 
    522   // If the text store is edited in OnLockGranted(), we may need to call
    523   // TextInputClient::InsertText() or TextInputClient::SetCompositionText().
    524   const size_t new_committed_size = committed_size_;
    525   const string16& new_committed_string =
    526       string_buffer_.substr(last_committed_size,
    527                             new_committed_size - last_committed_size);
    528   const string16& composition_string =
    529       string_buffer_.substr(new_committed_size);
    530 
    531   // If there is new committed string, calls TextInputClient::InsertText().
    532   if ((!new_committed_string.empty()) && text_input_client_) {
    533     text_input_client_->InsertText(new_committed_string);
    534   }
    535 
    536   // Calls TextInputClient::SetCompositionText().
    537   CompositionText composition_text;
    538   composition_text.text = composition_string;
    539   composition_text.underlines = composition_undelines_;
    540   // Adjusts the offset.
    541   for (size_t i = 0; i < composition_text.underlines.size(); ++i) {
    542     composition_text.underlines[i].start_offset -= new_committed_size;
    543     composition_text.underlines[i].end_offset -= new_committed_size;
    544   }
    545   if (selection_.start() < new_committed_size) {
    546     composition_text.selection.set_start(0);
    547   } else {
    548     composition_text.selection.set_start(
    549         selection_.start() - new_committed_size);
    550   }
    551   if (selection_.end() < new_committed_size) {
    552     composition_text.selection.set_end(0);
    553   } else {
    554     composition_text.selection.set_end(selection_.end() - new_committed_size);
    555   }
    556   if (text_input_client_)
    557     text_input_client_->SetCompositionText(composition_text);
    558 
    559   // If there is no composition string, clear the text store status.
    560   // And call OnSelectionChange(), OnLayoutChange(), and OnTextChange().
    561   if ((composition_string.empty()) && (new_committed_size != 0)) {
    562     string_buffer_.clear();
    563     committed_size_ = 0;
    564     selection_.set_start(0);
    565     selection_.set_end(0);
    566     if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE)
    567       text_store_acp_sink_->OnSelectionChange();
    568     if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE)
    569       text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0);
    570     if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) {
    571       TS_TEXTCHANGE textChange;
    572       textChange.acpStart = 0;
    573       textChange.acpOldEnd = new_committed_size;
    574       textChange.acpNewEnd = 0;
    575       text_store_acp_sink_->OnTextChange(0, &textChange);
    576     }
    577   }
    578 
    579   return S_OK;
    580 }
    581 
    582 STDMETHODIMP TSFTextStore::RequestSupportedAttrs(
    583     DWORD /* flags */,  // Seems that we should ignore this.
    584     ULONG attribute_buffer_size,
    585     const TS_ATTRID* attribute_buffer) {
    586   if (!attribute_buffer)
    587     return E_INVALIDARG;
    588   // We support only input scope attribute.
    589   for (size_t i = 0; i < attribute_buffer_size; ++i) {
    590     if (IsEqualGUID(GUID_PROP_INPUTSCOPE, attribute_buffer[i]))
    591       return S_OK;
    592   }
    593   return E_FAIL;
    594 }
    595 
    596 STDMETHODIMP TSFTextStore::RetrieveRequestedAttrs(
    597     ULONG attribute_buffer_size,
    598     TS_ATTRVAL* attribute_buffer,
    599     ULONG* attribute_buffer_copied) {
    600   if (!attribute_buffer_copied)
    601     return E_INVALIDARG;
    602   if (!attribute_buffer)
    603     return E_INVALIDARG;
    604   // We support only input scope attribute.
    605   *attribute_buffer_copied = 0;
    606   if (attribute_buffer_size == 0)
    607     return S_OK;
    608 
    609   attribute_buffer[0].dwOverlapId = 0;
    610   attribute_buffer[0].idAttr = GUID_PROP_INPUTSCOPE;
    611   attribute_buffer[0].varValue.vt = VT_UNKNOWN;
    612   attribute_buffer[0].varValue.punkVal = tsf_inputscope::CreateInputScope(
    613       text_input_client_->GetTextInputType());
    614   attribute_buffer[0].varValue.punkVal->AddRef();
    615   *attribute_buffer_copied = 1;
    616   return S_OK;
    617 }
    618 
    619 STDMETHODIMP TSFTextStore::SetSelection(
    620     ULONG selection_buffer_size,
    621     const TS_SELECTION_ACP* selection_buffer) {
    622   if (!HasReadWriteLock())
    623     return TF_E_NOLOCK;
    624   if (selection_buffer_size > 0) {
    625     const LONG start_pos = selection_buffer[0].acpStart;
    626     const LONG end_pos = selection_buffer[0].acpEnd;
    627     if (!((static_cast<LONG>(committed_size_) <= start_pos) &&
    628           (start_pos <= end_pos) &&
    629           (end_pos <= static_cast<LONG>(string_buffer_.size())))) {
    630       return TF_E_INVALIDPOS;
    631     }
    632     selection_.set_start(start_pos);
    633     selection_.set_end(end_pos);
    634   }
    635   return S_OK;
    636 }
    637 
    638 STDMETHODIMP TSFTextStore::SetText(DWORD flags,
    639                                    LONG acp_start,
    640                                    LONG acp_end,
    641                                    const wchar_t* text_buffer,
    642                                    ULONG text_buffer_size,
    643                                    TS_TEXTCHANGE* text_change) {
    644   if (!HasReadWriteLock())
    645     return TS_E_NOLOCK;
    646   if (!((static_cast<LONG>(committed_size_) <= acp_start) &&
    647         (acp_start <= acp_end) &&
    648         (acp_end <= static_cast<LONG>(string_buffer_.size())))) {
    649     return TS_E_INVALIDPOS;
    650   }
    651 
    652   TS_SELECTION_ACP selection;
    653   selection.acpStart = acp_start;
    654   selection.acpEnd = acp_end;
    655   selection.style.ase = TS_AE_NONE;
    656   selection.style.fInterimChar = 0;
    657 
    658   HRESULT ret;
    659   ret = SetSelection(1, &selection);
    660   if (ret != S_OK)
    661     return ret;
    662 
    663   TS_TEXTCHANGE change;
    664   ret = InsertTextAtSelection(0, text_buffer, text_buffer_size,
    665                               &acp_start, &acp_end, &change);
    666   if (ret != S_OK)
    667     return ret;
    668 
    669   if (text_change)
    670     *text_change = change;
    671 
    672   return S_OK;
    673 }
    674 
    675 STDMETHODIMP TSFTextStore::UnadviseSink(IUnknown* unknown) {
    676   if (!text_store_acp_sink_.IsSameObject(unknown))
    677     return CONNECT_E_NOCONNECTION;
    678   text_store_acp_sink_.Release();
    679   text_store_acp_sink_mask_ = 0;
    680   return S_OK;
    681 }
    682 
    683 STDMETHODIMP TSFTextStore::OnStartComposition(
    684     ITfCompositionView* composition_view,
    685     BOOL* ok) {
    686   if (ok)
    687     *ok = TRUE;
    688   return S_OK;
    689 }
    690 
    691 STDMETHODIMP TSFTextStore::OnUpdateComposition(
    692     ITfCompositionView* composition_view,
    693     ITfRange* range) {
    694   return S_OK;
    695 }
    696 
    697 STDMETHODIMP TSFTextStore::OnEndComposition(
    698     ITfCompositionView* composition_view) {
    699   return S_OK;
    700 }
    701 
    702 STDMETHODIMP TSFTextStore::OnEndEdit(ITfContext* context,
    703                                      TfEditCookie read_only_edit_cookie,
    704                                      ITfEditRecord* edit_record) {
    705   if (!context || !edit_record)
    706     return E_INVALIDARG;
    707 
    708   size_t committed_size;
    709   CompositionUnderlines undelines;
    710   if (!GetCompositionStatus(context, read_only_edit_cookie, &committed_size,
    711                             &undelines)) {
    712     return S_OK;
    713   }
    714   composition_undelines_ = undelines;
    715   committed_size_ = committed_size;
    716   edit_flag_ = true;
    717   return S_OK;
    718 }
    719 
    720 bool TSFTextStore::GetDisplayAttribute(TfGuidAtom guid_atom,
    721                                        TF_DISPLAYATTRIBUTE* attribute) {
    722   GUID guid;
    723   if (FAILED(category_manager_->GetGUID(guid_atom, &guid)))
    724     return false;
    725 
    726   base::win::ScopedComPtr<ITfDisplayAttributeInfo> display_attribute_info;
    727   if (FAILED(display_attribute_manager_->GetDisplayAttributeInfo(
    728           guid, display_attribute_info.Receive(), NULL))) {
    729     return false;
    730   }
    731   return SUCCEEDED(display_attribute_info->GetAttributeInfo(attribute));
    732 }
    733 
    734 bool TSFTextStore::GetCompositionStatus(
    735     ITfContext* context,
    736     const TfEditCookie read_only_edit_cookie,
    737     size_t* committed_size,
    738     CompositionUnderlines* undelines) {
    739   DCHECK(context);
    740   DCHECK(committed_size);
    741   DCHECK(undelines);
    742   const GUID* rgGuids[2] = {&GUID_PROP_COMPOSING, &GUID_PROP_ATTRIBUTE};
    743   base::win::ScopedComPtr<ITfReadOnlyProperty> track_property;
    744   if (FAILED(context->TrackProperties(rgGuids, 2, NULL, 0,
    745                                       track_property.Receive()))) {
    746     return false;
    747   }
    748 
    749   *committed_size = 0;
    750   undelines->clear();
    751   base::win::ScopedComPtr<ITfRange> start_to_end_range;
    752   base::win::ScopedComPtr<ITfRange> end_range;
    753   if (FAILED(context->GetStart(read_only_edit_cookie,
    754                                start_to_end_range.Receive()))) {
    755     return false;
    756   }
    757   if (FAILED(context->GetEnd(read_only_edit_cookie, end_range.Receive())))
    758     return false;
    759   if (FAILED(start_to_end_range->ShiftEndToRange(read_only_edit_cookie,
    760                                                  end_range, TF_ANCHOR_END))) {
    761     return false;
    762   }
    763 
    764   base::win::ScopedComPtr<IEnumTfRanges> ranges;
    765   if (FAILED(track_property->EnumRanges(read_only_edit_cookie, ranges.Receive(),
    766                                         start_to_end_range))) {
    767     return false;
    768   }
    769 
    770   while (true) {
    771     base::win::ScopedComPtr<ITfRange> range;
    772     if (ranges->Next(1, range.Receive(), NULL) != S_OK)
    773       break;
    774     base::win::ScopedVariant value;
    775     base::win::ScopedComPtr<IEnumTfPropertyValue> enum_prop_value;
    776     if (FAILED(track_property->GetValue(read_only_edit_cookie, range,
    777                                         value.Receive()))) {
    778       return false;
    779     }
    780     if (FAILED(enum_prop_value.QueryFrom(value.AsInput()->punkVal)))
    781       return false;
    782 
    783     TF_PROPERTYVAL property_value;
    784     bool is_composition = false;
    785     bool has_display_attribute = false;
    786     TF_DISPLAYATTRIBUTE display_attribute;
    787     while (enum_prop_value->Next(1, &property_value, NULL) == S_OK) {
    788       if (IsEqualGUID(property_value.guidId, GUID_PROP_COMPOSING)) {
    789         is_composition = (property_value.varValue.lVal == TRUE);
    790       } else if (IsEqualGUID(property_value.guidId, GUID_PROP_ATTRIBUTE)) {
    791         TfGuidAtom guid_atom =
    792             static_cast<TfGuidAtom>(property_value.varValue.lVal);
    793         if (GetDisplayAttribute(guid_atom, &display_attribute))
    794           has_display_attribute = true;
    795       }
    796       VariantClear(&property_value.varValue);
    797     }
    798 
    799     base::win::ScopedComPtr<ITfRangeACP> range_acp;
    800     range_acp.QueryFrom(range);
    801     LONG start_pos, length;
    802     range_acp->GetExtent(&start_pos, &length);
    803     if (!is_composition) {
    804       if (*committed_size < static_cast<size_t>(start_pos + length))
    805         *committed_size = start_pos + length;
    806     } else {
    807       CompositionUnderline underline;
    808       underline.start_offset = start_pos;
    809       underline.end_offset = start_pos + length;
    810       underline.color = SK_ColorBLACK;
    811       if (has_display_attribute)
    812         underline.thick = !!display_attribute.fBoldLine;
    813       undelines->push_back(underline);
    814     }
    815   }
    816   return true;
    817 }
    818 
    819 void TSFTextStore::SetFocusedTextInputClient(
    820     HWND focused_window,
    821     TextInputClient* text_input_client) {
    822   window_handle_ = focused_window;
    823   text_input_client_ = text_input_client;
    824 }
    825 
    826 void TSFTextStore::RemoveFocusedTextInputClient(
    827     TextInputClient* text_input_client) {
    828   if (text_input_client_ == text_input_client) {
    829     window_handle_ = NULL;
    830     text_input_client_ = NULL;
    831   }
    832 }
    833 
    834 bool TSFTextStore::CancelComposition() {
    835   // If there is an on-going document lock, we must not edit the text.
    836   if (edit_flag_)
    837     return false;
    838 
    839   if (string_buffer_.empty())
    840     return true;
    841 
    842   // Unlike ImmNotifyIME(NI_COMPOSITIONSTR, CPS_CANCEL, 0) in IMM32, TSF does
    843   // not have a dedicated method to cancel composition. However, CUAS actually
    844   // has a protocol conversion from CPS_CANCEL into TSF operations. According
    845   // to the observations on Windows 7, TIPs are expected to cancel composition
    846   // when an on-going composition text is replaced with an empty string. So
    847   // we use the same operation to cancel composition here to minimize the risk
    848   // of potential compatibility issues.
    849 
    850   const size_t previous_buffer_size = string_buffer_.size();
    851   string_buffer_.clear();
    852   committed_size_ = 0;
    853   selection_.set_start(0);
    854   selection_.set_end(0);
    855   if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE)
    856     text_store_acp_sink_->OnSelectionChange();
    857   if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE)
    858     text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0);
    859   if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) {
    860     TS_TEXTCHANGE textChange = {};
    861     textChange.acpStart = 0;
    862     textChange.acpOldEnd = previous_buffer_size;
    863     textChange.acpNewEnd = 0;
    864     text_store_acp_sink_->OnTextChange(0, &textChange);
    865   }
    866   return true;
    867 }
    868 
    869 bool TSFTextStore::ConfirmComposition() {
    870   // If there is an on-going document lock, we must not edit the text.
    871   if (edit_flag_)
    872     return false;
    873 
    874   if (string_buffer_.empty())
    875     return true;
    876 
    877   // See the comment in TSFTextStore::CancelComposition.
    878   // This logic is based on the observation about how to emulate
    879   // ImmNotifyIME(NI_COMPOSITIONSTR, CPS_COMPLETE, 0) by CUAS.
    880 
    881   const string16& composition_text = string_buffer_.substr(committed_size_);
    882   if (!composition_text.empty())
    883     text_input_client_->InsertText(composition_text);
    884 
    885   const size_t previous_buffer_size = string_buffer_.size();
    886   string_buffer_.clear();
    887   committed_size_ = 0;
    888   selection_.set_start(0);
    889   selection_.set_end(0);
    890   if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE)
    891     text_store_acp_sink_->OnSelectionChange();
    892   if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE)
    893     text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0);
    894   if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) {
    895     TS_TEXTCHANGE textChange = {};
    896     textChange.acpStart = 0;
    897     textChange.acpOldEnd = previous_buffer_size;
    898     textChange.acpNewEnd = 0;
    899     text_store_acp_sink_->OnTextChange(0, &textChange);
    900   }
    901   return true;
    902 }
    903 
    904 void TSFTextStore::SendOnLayoutChange() {
    905   if (text_store_acp_sink_ && (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE))
    906     text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0);
    907 }
    908 
    909 bool TSFTextStore::HasReadLock() const {
    910   return (current_lock_type_ & TS_LF_READ) == TS_LF_READ;
    911 }
    912 
    913 bool TSFTextStore::HasReadWriteLock() const {
    914   return (current_lock_type_ & TS_LF_READWRITE) == TS_LF_READWRITE;
    915 }
    916 
    917 }  // namespace ui
    918