Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / win8 / metro_driver / ime / text_store.cc
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