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