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