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
14namespace metro_driver {
15namespace {
16
17// We support only one view.
18const TsViewCookie kViewCookie = 1;
19
20}  // namespace
21
22TextStore::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
36TextStore::~TextStore() {
37}
38
39// static
40scoped_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
83void 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
96STDMETHODIMP 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
116STDMETHODIMP 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
135STDMETHODIMP 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
145STDMETHODIMP 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
153STDMETHODIMP 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
165STDMETHODIMP 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
174STDMETHODIMP TextStore::GetFormattedText(LONG acp_start,
175                                         LONG acp_end,
176                                         IDataObject** data_object) {
177  NOTIMPLEMENTED();
178  return E_NOTIMPL;
179}
180
181STDMETHODIMP 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
207STDMETHODIMP 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
229STDMETHODIMP 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
240STDMETHODIMP 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
285STDMETHODIMP 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
360STDMETHODIMP 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
370STDMETHODIMP 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
380STDMETHODIMP 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
390STDMETHODIMP 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
433STDMETHODIMP 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
450STDMETHODIMP 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
461STDMETHODIMP 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
472STDMETHODIMP 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
483STDMETHODIMP 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
578STDMETHODIMP 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
594STDMETHODIMP 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
619STDMETHODIMP 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
638STDMETHODIMP 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
675STDMETHODIMP 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
683STDMETHODIMP TextStore::OnStartComposition(
684    ITfCompositionView* composition_view,
685    BOOL* ok) {
686  if (ok)
687    *ok = TRUE;
688  return S_OK;
689}
690
691STDMETHODIMP TextStore::OnUpdateComposition(
692    ITfCompositionView* composition_view,
693    ITfRange* range) {
694  return S_OK;
695}
696
697STDMETHODIMP TextStore::OnEndComposition(
698    ITfCompositionView* composition_view) {
699  return S_OK;
700}
701
702STDMETHODIMP 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
715bool 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
729bool 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
808bool 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
844bool 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
881void 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
886bool TextStore::HasReadLock() const {
887  return (current_lock_type_ & TS_LF_READ) == TS_LF_READ;
888}
889
890bool TextStore::HasReadWriteLock() const {
891  return (current_lock_type_ & TS_LF_READWRITE) == TS_LF_READWRITE;
892}
893
894}  // namespace metro_driver
895