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