root/win8/metro_driver/ime/text_store.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. input_scope_
  2. Create
  3. Initialize
  4. AdviseSink
  5. FindNextAttrTransition
  6. GetACPFromPoint
  7. GetActiveView
  8. GetEmbedded
  9. GetEndACP
  10. GetFormattedText
  11. GetScreenExt
  12. GetSelection
  13. GetStatus
  14. GetText
  15. GetTextExt
  16. GetWnd
  17. InsertEmbedded
  18. InsertEmbeddedAtSelection
  19. InsertTextAtSelection
  20. QueryInsert
  21. QueryInsertEmbedded
  22. RequestAttrsAtPosition
  23. RequestAttrsTransitioningAtPosition
  24. RequestLock
  25. RequestSupportedAttrs
  26. RetrieveRequestedAttrs
  27. SetSelection
  28. SetText
  29. UnadviseSink
  30. OnStartComposition
  31. OnUpdateComposition
  32. OnEndComposition
  33. OnEndEdit
  34. GetDisplayAttribute
  35. GetCompositionStatus
  36. CancelComposition
  37. ConfirmComposition
  38. SendOnLayoutChange
  39. HasReadLock
  40. HasReadWriteLock

// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "win8/metro_driver/ime/text_store.h"

#include <algorithm>

#include "base/win/scoped_variant.h"
#include "ui/base/win/atl_module.h"
#include "win8/metro_driver/ime/input_scope.h"
#include "win8/metro_driver/ime/text_store_delegate.h"

namespace metro_driver {
namespace {

// We support only one view.
const TsViewCookie kViewCookie = 1;

}  // namespace

TextStore::TextStore()
    : text_store_acp_sink_mask_(0),
      window_handle_(NULL),
      delegate_(NULL),
      committed_size_(0),
      selection_start_(0),
      selection_end_(0),
      edit_flag_(false),
      current_lock_type_(0),
      category_manager_(NULL),
      display_attribute_manager_(NULL),
      input_scope_(NULL) {
}

TextStore::~TextStore() {
}

// static
scoped_refptr<TextStore> TextStore::Create(
    HWND window_handle,
    const std::vector<InputScope>& input_scopes,
    TextStoreDelegate* delegate) {
  if (!delegate) {
    LOG(ERROR) << "|delegate| must be non-NULL.";
    return scoped_refptr<TextStore>();
  }
  base::win::ScopedComPtr<ITfCategoryMgr> category_manager;
  HRESULT hr = category_manager.CreateInstance(CLSID_TF_CategoryMgr);
  if (FAILED(hr)) {
    LOG(ERROR) << "Failed to initialize CategoryMgr. hr = " << hr;
    return scoped_refptr<TextStore>();
  }
  base::win::ScopedComPtr<ITfDisplayAttributeMgr> display_attribute_manager;
  hr = display_attribute_manager.CreateInstance(CLSID_TF_DisplayAttributeMgr);
  if (FAILED(hr)) {
    LOG(ERROR) << "Failed to initialize DisplayAttributeMgr. hr = " << hr;
    return scoped_refptr<TextStore>();
  }
  base::win::ScopedComPtr<ITfInputScope> input_scope =
      CreteInputScope(input_scopes);
  if (!input_scope) {
    LOG(ERROR) << "Failed to initialize InputScope.";
    return scoped_refptr<TextStore>();
  }

  ui::win::CreateATLModuleIfNeeded();
  CComObject<TextStore>* object = NULL;
  hr = CComObject<TextStore>::CreateInstance(&object);
  if (FAILED(hr)) {
    LOG(ERROR) << "CComObject<TextStore>::CreateInstance failed. hr = "
               << hr;
    return scoped_refptr<TextStore>();
  }
  object->Initialize(window_handle,
                     category_manager,
                     display_attribute_manager,
                     input_scope,
                     delegate);
  return scoped_refptr<TextStore>(object);
}

void TextStore::Initialize(HWND window_handle,
                           ITfCategoryMgr* category_manager,
                           ITfDisplayAttributeMgr* display_attribute_manager,
                           ITfInputScope* input_scope,
                           TextStoreDelegate* delegate) {
  window_handle_ = window_handle;
  category_manager_ = category_manager;
  display_attribute_manager_ = display_attribute_manager;
  input_scope_ = input_scope;
  delegate_ = delegate;
}


STDMETHODIMP TextStore::AdviseSink(REFIID iid,
                                   IUnknown* unknown,
                                   DWORD mask) {
  if (!IsEqualGUID(iid, IID_ITextStoreACPSink))
    return E_INVALIDARG;
  if (text_store_acp_sink_) {
    if (text_store_acp_sink_.IsSameObject(unknown)) {
      text_store_acp_sink_mask_ = mask;
      return S_OK;
    } else {
      return CONNECT_E_ADVISELIMIT;
    }
  }
  if (FAILED(text_store_acp_sink_.QueryFrom(unknown)))
    return E_UNEXPECTED;
  text_store_acp_sink_mask_ = mask;

  return S_OK;
}

STDMETHODIMP TextStore::FindNextAttrTransition(
    LONG acp_start,
    LONG acp_halt,
    ULONG num_filter_attributes,
    const TS_ATTRID* filter_attributes,
    DWORD flags,
    LONG* acp_next,
    BOOL* found,
    LONG* found_offset) {
  if (!acp_next || !found || !found_offset)
    return E_INVALIDARG;
  // We don't support any attributes.
  // So we always return "not found".
  *acp_next = 0;
  *found = FALSE;
  *found_offset = 0;
  return S_OK;
}

STDMETHODIMP TextStore::GetACPFromPoint(TsViewCookie view_cookie,
                                        const POINT* point,
                                        DWORD flags,
                                        LONG* acp) {
  NOTIMPLEMENTED();
  if (view_cookie != kViewCookie)
    return E_INVALIDARG;
  return E_NOTIMPL;
}

STDMETHODIMP TextStore::GetActiveView(TsViewCookie* view_cookie) {
  if (!view_cookie)
    return E_INVALIDARG;
  // We support only one view.
  *view_cookie = kViewCookie;
  return S_OK;
}

STDMETHODIMP TextStore::GetEmbedded(LONG acp_pos,
                                    REFGUID service,
                                    REFIID iid,
                                    IUnknown** unknown) {
  // We don't support any embedded objects.
  NOTIMPLEMENTED();
  if (!unknown)
    return E_INVALIDARG;
  *unknown = NULL;
  return E_NOTIMPL;
}

STDMETHODIMP TextStore::GetEndACP(LONG* acp) {
  if (!acp)
    return E_INVALIDARG;
  if (!HasReadLock())
    return TS_E_NOLOCK;
  *acp = static_cast<LONG>(string_buffer_.size());
  return S_OK;
}

STDMETHODIMP TextStore::GetFormattedText(LONG acp_start,
                                         LONG acp_end,
                                         IDataObject** data_object) {
  NOTIMPLEMENTED();
  return E_NOTIMPL;
}

STDMETHODIMP TextStore::GetScreenExt(TsViewCookie view_cookie, RECT* rect) {
  if (view_cookie != kViewCookie)
    return E_INVALIDARG;
  if (!rect)
    return E_INVALIDARG;

  // {0, 0, 0, 0} means that the document rect is not currently displayed.
  SetRect(rect, 0, 0, 0, 0);

  RECT client_rect = {};
  if (!GetClientRect(window_handle_, &client_rect))
    return E_FAIL;
  POINT left_top = {client_rect.left, client_rect.top};
  POINT right_bottom = {client_rect.right, client_rect.bottom};
  if (!ClientToScreen(window_handle_, &left_top))
    return E_FAIL;
  if (!ClientToScreen(window_handle_, &right_bottom))
    return E_FAIL;

  rect->left = left_top.x;
  rect->top = left_top.y;
  rect->right = right_bottom.x;
  rect->bottom = right_bottom.y;
  return S_OK;
}

STDMETHODIMP TextStore::GetSelection(ULONG selection_index,
                                     ULONG selection_buffer_size,
                                     TS_SELECTION_ACP* selection_buffer,
                                     ULONG* fetched_count) {
  if (!selection_buffer)
    return E_INVALIDARG;
  if (!fetched_count)
    return E_INVALIDARG;
  if (!HasReadLock())
    return TS_E_NOLOCK;
  *fetched_count = 0;
  if ((selection_buffer_size > 0) &&
      ((selection_index == 0) || (selection_index == TS_DEFAULT_SELECTION))) {
    selection_buffer[0].acpStart = selection_start_;
    selection_buffer[0].acpEnd = selection_end_;
    selection_buffer[0].style.ase = TS_AE_END;
    selection_buffer[0].style.fInterimChar = FALSE;
    *fetched_count = 1;
  }
  return S_OK;
}

STDMETHODIMP TextStore::GetStatus(TS_STATUS* status) {
  if (!status)
    return E_INVALIDARG;

  status->dwDynamicFlags = 0;
  // We use transitory contexts and we don't support hidden text.
  status->dwStaticFlags = TS_SS_TRANSITORY | TS_SS_NOHIDDENTEXT;

  return S_OK;
}

STDMETHODIMP TextStore::GetText(LONG acp_start,
                                LONG acp_end,
                                wchar_t* text_buffer,
                                ULONG text_buffer_size,
                                ULONG* text_buffer_copied,
                                TS_RUNINFO* run_info_buffer,
                                ULONG run_info_buffer_size,
                                ULONG* run_info_buffer_copied,
                                LONG* next_acp) {
  if (!text_buffer_copied || !run_info_buffer_copied)
    return E_INVALIDARG;
  if (!text_buffer && text_buffer_size != 0)
    return E_INVALIDARG;
  if (!run_info_buffer && run_info_buffer_size != 0)
    return E_INVALIDARG;
  if (!next_acp)
    return E_INVALIDARG;
  if (!HasReadLock())
    return TF_E_NOLOCK;
  const LONG string_buffer_size = static_cast<LONG>(string_buffer_.size());
  if (acp_end == -1)
    acp_end = string_buffer_size;
  if (!((0 <= acp_start) &&
        (acp_start <= acp_end) &&
        (acp_end <= string_buffer_size))) {
    return TF_E_INVALIDPOS;
  }
  acp_end = std::min(acp_end, acp_start + static_cast<LONG>(text_buffer_size));
  *text_buffer_copied = acp_end - acp_start;

  const base::string16& result =
      string_buffer_.substr(acp_start, *text_buffer_copied);
  for (size_t i = 0; i < result.size(); ++i)
    text_buffer[i] = result[i];

  if (run_info_buffer_size) {
    run_info_buffer[0].uCount = *text_buffer_copied;
    run_info_buffer[0].type = TS_RT_PLAIN;
    *run_info_buffer_copied = 1;
  }

  *next_acp = acp_end;
  return S_OK;
}

STDMETHODIMP TextStore::GetTextExt(TsViewCookie view_cookie,
                                   LONG acp_start,
                                   LONG acp_end,
                                   RECT* rect,
                                   BOOL* clipped) {
  if (!rect || !clipped)
    return E_INVALIDARG;
  if (view_cookie != kViewCookie)
    return E_INVALIDARG;
  if (!HasReadLock())
    return TS_E_NOLOCK;
  if (!((static_cast<LONG>(committed_size_) <= acp_start) &&
       (acp_start <= acp_end) &&
       (acp_end <= static_cast<LONG>(string_buffer_.size())))) {
    return TS_E_INVALIDPOS;
  }

  // According to a behavior of notepad.exe and wordpad.exe, top left corner of
  // rect indicates a first character's one, and bottom right corner of rect
  // indicates a last character's one.
  // We use RECT instead of gfx::Rect since left position may be bigger than
  // right position when composition has multiple lines.
  RECT result;
  RECT tmp_rect;
  const uint32 start_pos = acp_start - committed_size_;
  const uint32 end_pos = acp_end - committed_size_;

  if (start_pos == end_pos) {
    // According to MSDN document, if |acp_start| and |acp_end| are equal it is
    // OK to just return E_INVALIDARG.
    // http://msdn.microsoft.com/en-us/library/ms538435
    // But when using Pinin IME of Windows 8, this method is called with the
    // equal values of |acp_start| and |acp_end|. So we handle this condition.
    if (start_pos == 0) {
      if (delegate_->GetCompositionCharacterBounds(0, &tmp_rect)) {
        tmp_rect.right = tmp_rect.right;
        result = tmp_rect;
      } else if (string_buffer_.size() == committed_size_) {
        result = delegate_->GetCaretBounds();
      } else {
        return TS_E_NOLAYOUT;
      }
    } else if (delegate_->GetCompositionCharacterBounds(start_pos - 1,
                                                        &tmp_rect)) {
      tmp_rect.left = tmp_rect.right;
      result = tmp_rect;
    } else {
      return TS_E_NOLAYOUT;
    }
  } else {
    if (delegate_->GetCompositionCharacterBounds(start_pos, &tmp_rect)) {
      result = tmp_rect;
      if (delegate_->GetCompositionCharacterBounds(end_pos - 1, &tmp_rect)) {
        result.right = tmp_rect.right;
        result.bottom = tmp_rect.bottom;
      } else {
        // We may not be able to get the last character bounds, so we use the
        // first character bounds instead of returning TS_E_NOLAYOUT.
      }
    } else {
      // Hack for PPAPI flash. PPAPI flash does not support GetCaretBounds, so
      // it's better to return previous caret rectangle instead.
      // TODO(nona, kinaba): Remove this hack.
      if (start_pos == 0)
        result = delegate_->GetCaretBounds();
      else
        return TS_E_NOLAYOUT;
    }
  }

  *rect =  result;
  *clipped = FALSE;
  return S_OK;
}

STDMETHODIMP TextStore::GetWnd(TsViewCookie view_cookie,
                               HWND* window_handle) {
  if (!window_handle)
    return E_INVALIDARG;
  if (view_cookie != kViewCookie)
    return E_INVALIDARG;
  *window_handle = window_handle_;
  return S_OK;
}

STDMETHODIMP TextStore::InsertEmbedded(DWORD flags,
                                       LONG acp_start,
                                       LONG acp_end,
                                       IDataObject* data_object,
                                       TS_TEXTCHANGE* change) {
  // We don't support any embedded objects.
  NOTIMPLEMENTED();
  return E_NOTIMPL;
}

STDMETHODIMP TextStore::InsertEmbeddedAtSelection(DWORD flags,
                                                  IDataObject* data_object,
                                                  LONG* acp_start,
                                                  LONG* acp_end,
                                                  TS_TEXTCHANGE* change) {
  // We don't support any embedded objects.
  NOTIMPLEMENTED();
  return E_NOTIMPL;
}

STDMETHODIMP TextStore::InsertTextAtSelection(DWORD flags,
                                              const wchar_t* text_buffer,
                                              ULONG text_buffer_size,
                                              LONG* acp_start,
                                              LONG* acp_end,
                                              TS_TEXTCHANGE* text_change) {
  const LONG start_pos = selection_start_;
  const LONG end_pos = selection_end_;
  const LONG new_end_pos = start_pos + text_buffer_size;

  if (flags & TS_IAS_QUERYONLY) {
    if (!HasReadLock())
      return TS_E_NOLOCK;
    if (acp_start)
      *acp_start = start_pos;
    if (acp_end)
      *acp_end = end_pos;
    return S_OK;
  }

  if (!HasReadWriteLock())
    return TS_E_NOLOCK;
  if (!text_buffer)
    return E_INVALIDARG;

  DCHECK_LE(start_pos, end_pos);
  string_buffer_ = string_buffer_.substr(0, start_pos) +
                   base::string16(text_buffer, text_buffer + text_buffer_size) +
                   string_buffer_.substr(end_pos);
  if (acp_start)
    *acp_start = start_pos;
  if (acp_end)
    *acp_end = new_end_pos;
  if (text_change) {
    text_change->acpStart = start_pos;
    text_change->acpOldEnd = end_pos;
    text_change->acpNewEnd = new_end_pos;
  }
  selection_start_ = start_pos;
  selection_end_ = new_end_pos;
  return S_OK;
}

STDMETHODIMP TextStore::QueryInsert(
    LONG acp_test_start,
    LONG acp_test_end,
    ULONG text_size,
    LONG* acp_result_start,
    LONG* acp_result_end) {
  if (!acp_result_start || !acp_result_end || acp_test_start > acp_test_end)
    return E_INVALIDARG;
  const LONG committed_size = static_cast<LONG>(committed_size_);
  const LONG buffer_size = static_cast<LONG>(string_buffer_.size());
  *acp_result_start = std::min(std::max(committed_size, acp_test_start),
                               buffer_size);
  *acp_result_end = std::min(std::max(committed_size, acp_test_end),
                             buffer_size);
  return S_OK;
}

STDMETHODIMP TextStore::QueryInsertEmbedded(const GUID* service,
                                               const FORMATETC* format,
                                               BOOL* insertable) {
  if (!format)
    return E_INVALIDARG;
  // We don't support any embedded objects.
  if (insertable)
    *insertable = FALSE;
  return S_OK;
}

STDMETHODIMP TextStore::RequestAttrsAtPosition(
    LONG acp_pos,
    ULONG attribute_buffer_size,
    const TS_ATTRID* attribute_buffer,
    DWORD flags) {
  // We don't support any document attributes.
  // This method just returns S_OK, and the subsequently called
  // RetrieveRequestedAttrs() returns 0 as the number of supported attributes.
  return S_OK;
}

STDMETHODIMP TextStore::RequestAttrsTransitioningAtPosition(
    LONG acp_pos,
    ULONG attribute_buffer_size,
    const TS_ATTRID* attribute_buffer,
    DWORD flags) {
  // We don't support any document attributes.
  // This method just returns S_OK, and the subsequently called
  // RetrieveRequestedAttrs() returns 0 as the number of supported attributes.
  return S_OK;
}

STDMETHODIMP TextStore::RequestLock(DWORD lock_flags, HRESULT* result) {
  if (!text_store_acp_sink_.get())
    return E_FAIL;
  if (!result)
    return E_INVALIDARG;

  if (current_lock_type_ != 0) {
    if (lock_flags & TS_LF_SYNC) {
      // Can't lock synchronously.
      *result = TS_E_SYNCHRONOUS;
      return S_OK;
    }
    // Queue the lock request.
    lock_queue_.push_back(lock_flags & TS_LF_READWRITE);
    *result = TS_S_ASYNC;
    return S_OK;
  }

  // Lock
  current_lock_type_ = (lock_flags & TS_LF_READWRITE);

  edit_flag_ = false;
  const uint32 last_committed_size = committed_size_;

  // Grant the lock.
  *result = text_store_acp_sink_->OnLockGranted(current_lock_type_);

  // Unlock
  current_lock_type_ = 0;

  // Handles the pending lock requests.
  while (!lock_queue_.empty()) {
    current_lock_type_ = lock_queue_.front();
    lock_queue_.pop_front();
    text_store_acp_sink_->OnLockGranted(current_lock_type_);
    current_lock_type_ = 0;
  }

  if (!edit_flag_)
    return S_OK;

  // If the text store is edited in OnLockGranted(), we may need to call
  // TextStoreDelegate::ConfirmComposition() or
  // TextStoreDelegate::SetComposition().
  const uint32 new_committed_size = committed_size_;
  const base::string16& new_committed_string =
      string_buffer_.substr(last_committed_size,
                            new_committed_size - last_committed_size);
  const base::string16& composition_string =
      string_buffer_.substr(new_committed_size);

  // If there is new committed string, calls
  // TextStoreDelegate::ConfirmComposition().
  if ((!new_committed_string.empty()))
    delegate_->OnTextCommitted(new_committed_string);

  // Calls TextInputClient::SetCompositionText().
  std::vector<metro_viewer::UnderlineInfo> underlines = underlines_;
  // Adjusts the offset.
  for (size_t i = 0; i < underlines_.size(); ++i) {
    underlines[i].start_offset -= new_committed_size;
    underlines[i].end_offset -= new_committed_size;
  }
  int32 selection_start = 0;
  int32 selection_end = 0;
  if (selection_start_ >= new_committed_size)
    selection_start = selection_start_ - new_committed_size;
  if (selection_end_ >= new_committed_size)
    selection_end = selection_end_ - new_committed_size;
  delegate_->OnCompositionChanged(
        composition_string, selection_start, selection_end, underlines);

  // If there is no composition string, clear the text store status.
  // And call OnSelectionChange(), OnLayoutChange(), and OnTextChange().
  if ((composition_string.empty()) && (new_committed_size != 0)) {
    string_buffer_.clear();
    committed_size_ = 0;
    selection_start_ = 0;
    selection_end_ = 0;
    if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE)
      text_store_acp_sink_->OnSelectionChange();
    if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE)
      text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0);
    if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) {
      TS_TEXTCHANGE textChange;
      textChange.acpStart = 0;
      textChange.acpOldEnd = new_committed_size;
      textChange.acpNewEnd = 0;
      text_store_acp_sink_->OnTextChange(0, &textChange);
    }
  }

  return S_OK;
}

STDMETHODIMP TextStore::RequestSupportedAttrs(
    DWORD /* flags */,  // Seems that we should ignore this.
    ULONG attribute_buffer_size,
    const TS_ATTRID* attribute_buffer) {
  if (!attribute_buffer)
    return E_INVALIDARG;
  if (!input_scope_)
    return E_FAIL;
  // We support only input scope attribute.
  for (size_t i = 0; i < attribute_buffer_size; ++i) {
    if (IsEqualGUID(GUID_PROP_INPUTSCOPE, attribute_buffer[i]))
      return S_OK;
  }
  return E_FAIL;
}

STDMETHODIMP TextStore::RetrieveRequestedAttrs(
    ULONG attribute_buffer_size,
    TS_ATTRVAL* attribute_buffer,
    ULONG* attribute_buffer_copied) {
  if (!attribute_buffer_copied)
    return E_INVALIDARG;
  *attribute_buffer_copied = 0;
  if (!attribute_buffer)
    return E_INVALIDARG;
  if (!input_scope_)
    return E_UNEXPECTED;
  // We support only input scope attribute.
  *attribute_buffer_copied = 0;
  if (attribute_buffer_size == 0)
    return S_OK;

  attribute_buffer[0].dwOverlapId = 0;
  attribute_buffer[0].idAttr = GUID_PROP_INPUTSCOPE;
  attribute_buffer[0].varValue.vt = VT_UNKNOWN;
  attribute_buffer[0].varValue.punkVal = input_scope_.get();
  attribute_buffer[0].varValue.punkVal->AddRef();
  *attribute_buffer_copied = 1;
  return S_OK;
}

STDMETHODIMP TextStore::SetSelection(
    ULONG selection_buffer_size,
    const TS_SELECTION_ACP* selection_buffer) {
  if (!HasReadWriteLock())
    return TF_E_NOLOCK;
  if (selection_buffer_size > 0) {
    const LONG start_pos = selection_buffer[0].acpStart;
    const LONG end_pos = selection_buffer[0].acpEnd;
    if (!((static_cast<LONG>(committed_size_) <= start_pos) &&
          (start_pos <= end_pos) &&
          (end_pos <= static_cast<LONG>(string_buffer_.size())))) {
      return TF_E_INVALIDPOS;
    }
    selection_start_ = start_pos;
    selection_end_ = end_pos;
  }
  return S_OK;
}

STDMETHODIMP TextStore::SetText(DWORD flags,
                                LONG acp_start,
                                LONG acp_end,
                                const wchar_t* text_buffer,
                                ULONG text_buffer_size,
                                TS_TEXTCHANGE* text_change) {
  if (!HasReadWriteLock())
    return TS_E_NOLOCK;
  if (!((static_cast<LONG>(committed_size_) <= acp_start) &&
        (acp_start <= acp_end) &&
        (acp_end <= static_cast<LONG>(string_buffer_.size())))) {
    return TS_E_INVALIDPOS;
  }

  TS_SELECTION_ACP selection;
  selection.acpStart = acp_start;
  selection.acpEnd = acp_end;
  selection.style.ase = TS_AE_NONE;
  selection.style.fInterimChar = 0;

  HRESULT ret;
  ret = SetSelection(1, &selection);
  if (ret != S_OK)
    return ret;

  TS_TEXTCHANGE change;
  ret = InsertTextAtSelection(0, text_buffer, text_buffer_size,
                              &acp_start, &acp_end, &change);
  if (ret != S_OK)
    return ret;

  if (text_change)
    *text_change = change;

  return S_OK;
}

STDMETHODIMP TextStore::UnadviseSink(IUnknown* unknown) {
  if (!text_store_acp_sink_.IsSameObject(unknown))
    return CONNECT_E_NOCONNECTION;
  text_store_acp_sink_.Release();
  text_store_acp_sink_mask_ = 0;
  return S_OK;
}

STDMETHODIMP TextStore::OnStartComposition(
    ITfCompositionView* composition_view,
    BOOL* ok) {
  if (ok)
    *ok = TRUE;
  return S_OK;
}

STDMETHODIMP TextStore::OnUpdateComposition(
    ITfCompositionView* composition_view,
    ITfRange* range) {
  return S_OK;
}

STDMETHODIMP TextStore::OnEndComposition(
    ITfCompositionView* composition_view) {
  return S_OK;
}

STDMETHODIMP TextStore::OnEndEdit(ITfContext* context,
                                  TfEditCookie read_only_edit_cookie,
                                  ITfEditRecord* edit_record) {
  if (!context || !edit_record)
    return E_INVALIDARG;
  if (!GetCompositionStatus(context, read_only_edit_cookie, &committed_size_,
                            &underlines_)) {
    return S_OK;
  }
  edit_flag_ = true;
  return S_OK;
}

bool TextStore::GetDisplayAttribute(TfGuidAtom guid_atom,
                                    TF_DISPLAYATTRIBUTE* attribute) {
  GUID guid;
  if (FAILED(category_manager_->GetGUID(guid_atom, &guid)))
    return false;

  base::win::ScopedComPtr<ITfDisplayAttributeInfo> display_attribute_info;
  if (FAILED(display_attribute_manager_->GetDisplayAttributeInfo(
          guid, display_attribute_info.Receive(), NULL))) {
    return false;
  }
  return SUCCEEDED(display_attribute_info->GetAttributeInfo(attribute));
}

bool TextStore::GetCompositionStatus(
    ITfContext* context,
    const TfEditCookie read_only_edit_cookie,
    uint32* committed_size,
    std::vector<metro_viewer::UnderlineInfo>* undelines) {
  DCHECK(context);
  DCHECK(committed_size);
  DCHECK(undelines);
  const GUID* rgGuids[2] = {&GUID_PROP_COMPOSING, &GUID_PROP_ATTRIBUTE};
  base::win::ScopedComPtr<ITfReadOnlyProperty> track_property;
  if (FAILED(context->TrackProperties(rgGuids, 2, NULL, 0,
                                      track_property.Receive()))) {
    return false;
  }

  *committed_size = 0;
  undelines->clear();
  base::win::ScopedComPtr<ITfRange> start_to_end_range;
  base::win::ScopedComPtr<ITfRange> end_range;
  if (FAILED(context->GetStart(read_only_edit_cookie,
                               start_to_end_range.Receive()))) {
    return false;
  }
  if (FAILED(context->GetEnd(read_only_edit_cookie, end_range.Receive())))
    return false;
  if (FAILED(start_to_end_range->ShiftEndToRange(read_only_edit_cookie,
                                                 end_range, TF_ANCHOR_END))) {
    return false;
  }

  base::win::ScopedComPtr<IEnumTfRanges> ranges;
  if (FAILED(track_property->EnumRanges(read_only_edit_cookie, ranges.Receive(),
                                        start_to_end_range))) {
    return false;
  }

  while (true) {
    base::win::ScopedComPtr<ITfRange> range;
    if (ranges->Next(1, range.Receive(), NULL) != S_OK)
      break;
    base::win::ScopedVariant value;
    base::win::ScopedComPtr<IEnumTfPropertyValue> enum_prop_value;
    if (FAILED(track_property->GetValue(read_only_edit_cookie, range,
                                        value.Receive()))) {
      return false;
    }
    if (FAILED(enum_prop_value.QueryFrom(value.AsInput()->punkVal)))
      return false;

    TF_PROPERTYVAL property_value;
    bool is_composition = false;
    bool has_display_attribute = false;
    TF_DISPLAYATTRIBUTE display_attribute;
    while (enum_prop_value->Next(1, &property_value, NULL) == S_OK) {
      if (IsEqualGUID(property_value.guidId, GUID_PROP_COMPOSING)) {
        is_composition = (property_value.varValue.lVal == TRUE);
      } else if (IsEqualGUID(property_value.guidId, GUID_PROP_ATTRIBUTE)) {
        TfGuidAtom guid_atom =
            static_cast<TfGuidAtom>(property_value.varValue.lVal);
        if (GetDisplayAttribute(guid_atom, &display_attribute))
          has_display_attribute = true;
      }
      VariantClear(&property_value.varValue);
    }

    base::win::ScopedComPtr<ITfRangeACP> range_acp;
    range_acp.QueryFrom(range);
    LONG start_pos, length;
    range_acp->GetExtent(&start_pos, &length);
    if (!is_composition) {
      if (*committed_size < static_cast<size_t>(start_pos + length))
        *committed_size = start_pos + length;
    } else {
      metro_viewer::UnderlineInfo underline;
      underline.start_offset = start_pos;
      underline.end_offset = start_pos + length;
      underline.thick = !!display_attribute.fBoldLine;
      undelines->push_back(underline);
    }
  }
  return true;
}

bool TextStore::CancelComposition() {
  // If there is an on-going document lock, we must not edit the text.
  if (edit_flag_)
    return false;

  if (string_buffer_.empty())
    return true;

  // Unlike ImmNotifyIME(NI_COMPOSITIONSTR, CPS_CANCEL, 0) in IMM32, TSF does
  // not have a dedicated method to cancel composition. However, CUAS actually
  // has a protocol conversion from CPS_CANCEL into TSF operations. According
  // to the observations on Windows 7, TIPs are expected to cancel composition
  // when an on-going composition text is replaced with an empty string. So
  // we use the same operation to cancel composition here to minimize the risk
  // of potential compatibility issues.

  const uint32 previous_buffer_size =
      static_cast<uint32>(string_buffer_.size());
  string_buffer_.clear();
  committed_size_ = 0;
  selection_start_ = 0;
  selection_end_ = 0;
  if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE)
    text_store_acp_sink_->OnSelectionChange();
  if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE)
    text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0);
  if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) {
    TS_TEXTCHANGE textChange = {};
    textChange.acpStart = 0;
    textChange.acpOldEnd = previous_buffer_size;
    textChange.acpNewEnd = 0;
    text_store_acp_sink_->OnTextChange(0, &textChange);
  }
  return true;
}

bool TextStore::ConfirmComposition() {
  // If there is an on-going document lock, we must not edit the text.
  if (edit_flag_)
    return false;

  if (string_buffer_.empty())
    return true;

  // See the comment in TextStore::CancelComposition.
  // This logic is based on the observation about how to emulate
  // ImmNotifyIME(NI_COMPOSITIONSTR, CPS_COMPLETE, 0) by CUAS.

  const base::string16& composition_text =
      string_buffer_.substr(committed_size_);
  if (!composition_text.empty())
    delegate_->OnTextCommitted(composition_text);

  const uint32 previous_buffer_size =
      static_cast<uint32>(string_buffer_.size());
  string_buffer_.clear();
  committed_size_ = 0;
  selection_start_ = 0;
  selection_end_ = 0;
  if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE)
    text_store_acp_sink_->OnSelectionChange();
  if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE)
    text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0);
  if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) {
    TS_TEXTCHANGE textChange = {};
    textChange.acpStart = 0;
    textChange.acpOldEnd = previous_buffer_size;
    textChange.acpNewEnd = 0;
    text_store_acp_sink_->OnTextChange(0, &textChange);
  }
  return true;
}

void TextStore::SendOnLayoutChange() {
  if (text_store_acp_sink_ && (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE))
    text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0);
}

bool TextStore::HasReadLock() const {
  return (current_lock_type_ & TS_LF_READ) == TS_LF_READ;
}

bool TextStore::HasReadWriteLock() const {
  return (current_lock_type_ & TS_LF_READWRITE) == TS_LF_READWRITE;
}

}  // namespace metro_driver

/* [<][>][^][v][top][bottom][index][help] */