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.
5 #define INITGUID // required for GUID_PROP_INPUTSCOPE
6 #include "ui/base/ime/win/tsf_text_store.h"
8 #include <InputScope.h>
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"
21 // We support only one view.
22 const TsViewCookie kViewCookie = 1;
26 TSFTextStore::TSFTextStore()
28 text_store_acp_sink_mask_(0),
30 text_input_client_(NULL),
33 current_lock_type_(0) {
34 if (FAILED(category_manager_.CreateInstance(CLSID_TF_CategoryMgr))) {
35 LOG(FATAL) << "Failed to initialize CategoryMgr.";
38 if (FAILED(display_attribute_manager_.CreateInstance(
39 CLSID_TF_DisplayAttributeMgr))) {
40 LOG(FATAL) << "Failed to initialize DisplayAttributeMgr.";
45 TSFTextStore::~TSFTextStore() {
48 ULONG STDMETHODCALLTYPE TSFTextStore::AddRef() {
49 return InterlockedIncrement(&ref_count_);
52 ULONG STDMETHODCALLTYPE TSFTextStore::Release() {
53 const LONG count = InterlockedDecrement(&ref_count_);
58 return static_cast<ULONG>(count);
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);
76 STDMETHODIMP TSFTextStore::AdviseSink(REFIID iid,
79 if (!IsEqualGUID(iid, IID_ITextStoreACPSink))
81 if (text_store_acp_sink_) {
82 if (text_store_acp_sink_.IsSameObject(unknown)) {
83 text_store_acp_sink_mask_ = mask;
86 return CONNECT_E_ADVISELIMIT;
89 if (FAILED(text_store_acp_sink_.QueryFrom(unknown)))
91 text_store_acp_sink_mask_ = mask;
96 STDMETHODIMP TSFTextStore::FindNextAttrTransition(
99 ULONG num_filter_attributes,
100 const TS_ATTRID* filter_attributes,
104 LONG* found_offset) {
105 if (!acp_next || !found || !found_offset)
107 // We don't support any attributes.
108 // So we always return "not found".
115 STDMETHODIMP TSFTextStore::GetACPFromPoint(TsViewCookie view_cookie,
120 if (view_cookie != kViewCookie)
125 STDMETHODIMP TSFTextStore::GetActiveView(TsViewCookie* view_cookie) {
128 // We support only one view.
129 *view_cookie = kViewCookie;
133 STDMETHODIMP TSFTextStore::GetEmbedded(LONG acp_pos,
136 IUnknown** unknown) {
137 // We don't support any embedded objects.
145 STDMETHODIMP TSFTextStore::GetEndACP(LONG* acp) {
150 *acp = string_buffer_.size();
154 STDMETHODIMP TSFTextStore::GetFormattedText(LONG acp_start, LONG acp_end,
155 IDataObject** data_object) {
160 STDMETHODIMP TSFTextStore::GetScreenExt(TsViewCookie view_cookie, RECT* rect) {
161 if (view_cookie != kViewCookie)
166 // {0, 0, 0, 0} means that the document rect is not currently displayed.
167 SetRect(rect, 0, 0, 0, 0);
169 if (!IsWindow(window_handle_))
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))
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))
183 if (!ClientToScreen(window_handle_, &right_bottom))
186 rect->left = left_top.x;
187 rect->top = left_top.y;
188 rect->right = right_bottom.x;
189 rect->bottom = right_bottom.y;
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)
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;
215 STDMETHODIMP TSFTextStore::GetStatus(TS_STATUS* status) {
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;
226 STDMETHODIMP TSFTextStore::GetText(LONG acp_start,
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,
235 if (!text_buffer_copied || !run_info_buffer_copied)
237 if (!text_buffer && text_buffer_size != 0)
239 if (!run_info_buffer && run_info_buffer_size != 0)
245 const LONG string_buffer_size = string_buffer_.size();
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;
253 acp_end = std::min(acp_end, acp_start + static_cast<LONG>(text_buffer_size));
254 *text_buffer_copied = acp_end - acp_start;
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];
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;
272 STDMETHODIMP TSFTextStore::GetTextExt(TsViewCookie view_cookie,
277 if (!rect || !clipped)
279 if (!text_input_client_)
281 if (view_cookie != kViewCookie)
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;
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.
298 const uint32 start_pos = acp_start - committed_size_;
299 const uint32 end_pos = acp_end - committed_size_;
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();
314 return TS_E_NOLAYOUT;
316 } else if (text_input_client_->GetCompositionCharacterBounds(start_pos - 1,
318 result.left = tmp_rect.right();
319 result.right = tmp_rect.right();
320 result.top = tmp_rect.y();
321 result.bottom = tmp_rect.bottom();
323 return TS_E_NOLAYOUT;
326 if (text_input_client_->GetCompositionCharacterBounds(start_pos,
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,
334 result.right = tmp_rect.right();
335 result.bottom = tmp_rect.bottom();
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.
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();
347 return TS_E_NOLAYOUT;
357 STDMETHODIMP TSFTextStore::GetWnd(TsViewCookie view_cookie,
358 HWND* window_handle) {
361 if (view_cookie != kViewCookie)
363 *window_handle = window_handle_;
367 STDMETHODIMP TSFTextStore::InsertEmbedded(DWORD flags,
370 IDataObject* data_object,
371 TS_TEXTCHANGE* change) {
372 // We don't support any embedded objects.
377 STDMETHODIMP TSFTextStore::InsertEmbeddedAtSelection(DWORD flags,
378 IDataObject* data_object,
381 TS_TEXTCHANGE* change) {
382 // We don't support any embedded objects.
387 STDMETHODIMP TSFTextStore::InsertTextAtSelection(DWORD flags,
388 const wchar_t* text_buffer,
389 ULONG text_buffer_size,
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;
397 if (flags & TS_IAS_QUERYONLY) {
401 *acp_start = start_pos;
408 if (!HasReadWriteLock())
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);
418 *acp_start = start_pos;
420 *acp_end = new_end_pos;
422 text_change->acpStart = start_pos;
423 text_change->acpOldEnd = end_pos;
424 text_change->acpNewEnd = new_end_pos;
426 selection_.set_start(start_pos);
427 selection_.set_end(new_end_pos);
431 STDMETHODIMP TSFTextStore::QueryInsert(
435 LONG* acp_result_start,
436 LONG* acp_result_end) {
437 if (!acp_result_start || !acp_result_end || acp_test_start > acp_test_end)
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),
443 *acp_result_end = std::min(std::max(committed_size, acp_test_end),
448 STDMETHODIMP TSFTextStore::QueryInsertEmbedded(const GUID* service,
449 const FORMATETC* format,
453 // We don't support any embedded objects.
459 STDMETHODIMP TSFTextStore::RequestAttrsAtPosition(
461 ULONG attribute_buffer_size,
462 const TS_ATTRID* attribute_buffer,
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.
470 STDMETHODIMP TSFTextStore::RequestAttrsTransitioningAtPosition(
472 ULONG attribute_buffer_size,
473 const TS_ATTRID* attribute_buffer,
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.
481 STDMETHODIMP TSFTextStore::RequestLock(DWORD lock_flags, HRESULT* result) {
482 if (!text_store_acp_sink_.get())
487 if (current_lock_type_ != 0) {
488 if (lock_flags & TS_LF_SYNC) {
489 // Can't lock synchronously.
490 *result = TS_E_SYNCHRONOUS;
493 // Queue the lock request.
494 lock_queue_.push_back(lock_flags & TS_LF_READWRITE);
495 *result = TS_S_ASYNC;
500 current_lock_type_ = (lock_flags & TS_LF_READWRITE);
503 const size_t last_committed_size = committed_size_;
506 *result = text_store_acp_sink_->OnLockGranted(current_lock_type_);
509 current_lock_type_ = 0;
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;
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);
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);
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;
546 if (selection_.start() < new_committed_size) {
547 composition_text.selection.set_start(0);
549 composition_text.selection.set_start(
550 selection_.start() - new_committed_size);
552 if (selection_.end() < new_committed_size) {
553 composition_text.selection.set_end(0);
555 composition_text.selection.set_end(selection_.end() - new_committed_size);
557 if (text_input_client_)
558 text_input_client_->SetCompositionText(composition_text);
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();
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);
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)
589 if (!text_input_client_)
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]))
599 STDMETHODIMP TSFTextStore::RetrieveRequestedAttrs(
600 ULONG attribute_buffer_size,
601 TS_ATTRVAL* attribute_buffer,
602 ULONG* attribute_buffer_copied) {
603 if (!attribute_buffer_copied)
605 if (!attribute_buffer)
607 if (!text_input_client_)
609 // We support only input scope attribute.
610 *attribute_buffer_copied = 0;
611 if (attribute_buffer_size == 0)
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;
625 STDMETHODIMP TSFTextStore::SetSelection(
626 ULONG selection_buffer_size,
627 const TS_SELECTION_ACP* selection_buffer) {
628 if (!HasReadWriteLock())
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;
638 selection_.set_start(start_pos);
639 selection_.set_end(end_pos);
644 STDMETHODIMP TSFTextStore::SetText(DWORD flags,
647 const wchar_t* text_buffer,
648 ULONG text_buffer_size,
649 TS_TEXTCHANGE* text_change) {
650 if (!HasReadWriteLock())
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;
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;
665 ret = SetSelection(1, &selection);
669 TS_TEXTCHANGE change;
670 ret = InsertTextAtSelection(0, text_buffer, text_buffer_size,
671 &acp_start, &acp_end, &change);
676 *text_change = change;
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;
689 STDMETHODIMP TSFTextStore::OnStartComposition(
690 ITfCompositionView* composition_view,
697 STDMETHODIMP TSFTextStore::OnUpdateComposition(
698 ITfCompositionView* composition_view,
703 STDMETHODIMP TSFTextStore::OnEndComposition(
704 ITfCompositionView* composition_view) {
708 STDMETHODIMP TSFTextStore::OnEndEdit(ITfContext* context,
709 TfEditCookie read_only_edit_cookie,
710 ITfEditRecord* edit_record) {
711 if (!context || !edit_record)
714 size_t committed_size;
715 CompositionUnderlines undelines;
716 if (!GetCompositionStatus(context, read_only_edit_cookie, &committed_size,
720 composition_undelines_ = undelines;
721 committed_size_ = committed_size;
726 bool TSFTextStore::GetDisplayAttribute(TfGuidAtom guid_atom,
727 TF_DISPLAYATTRIBUTE* attribute) {
729 if (FAILED(category_manager_->GetGUID(guid_atom, &guid)))
732 base::win::ScopedComPtr<ITfDisplayAttributeInfo> display_attribute_info;
733 if (FAILED(display_attribute_manager_->GetDisplayAttributeInfo(
734 guid, display_attribute_info.Receive(), NULL))) {
737 return SUCCEEDED(display_attribute_info->GetAttributeInfo(attribute));
740 bool TSFTextStore::GetCompositionStatus(
742 const TfEditCookie read_only_edit_cookie,
743 size_t* committed_size,
744 CompositionUnderlines* undelines) {
746 DCHECK(committed_size);
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()))) {
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()))) {
763 if (FAILED(context->GetEnd(read_only_edit_cookie, end_range.Receive())))
765 if (FAILED(start_to_end_range->ShiftEndToRange(read_only_edit_cookie,
766 end_range, TF_ANCHOR_END))) {
770 base::win::ScopedComPtr<IEnumTfRanges> ranges;
771 if (FAILED(track_property->EnumRanges(read_only_edit_cookie, ranges.Receive(),
772 start_to_end_range))) {
777 base::win::ScopedComPtr<ITfRange> range;
778 if (ranges->Next(1, range.Receive(), NULL) != S_OK)
780 base::win::ScopedVariant value;
781 base::win::ScopedComPtr<IEnumTfPropertyValue> enum_prop_value;
782 if (FAILED(track_property->GetValue(read_only_edit_cookie, range,
786 if (FAILED(enum_prop_value.QueryFrom(value.AsInput()->punkVal)))
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;
802 VariantClear(&property_value.varValue);
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;
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);
825 void TSFTextStore::SetFocusedTextInputClient(
827 TextInputClient* text_input_client) {
828 window_handle_ = focused_window;
829 text_input_client_ = text_input_client;
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;
840 bool TSFTextStore::CancelComposition() {
841 // If there is an on-going document lock, we must not edit the text.
845 if (string_buffer_.empty())
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.
856 const size_t previous_buffer_size = string_buffer_.size();
857 string_buffer_.clear();
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);
875 bool TSFTextStore::ConfirmComposition() {
876 // If there is an on-going document lock, we must not edit the text.
880 if (string_buffer_.empty())
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.
887 const base::string16& composition_text =
888 string_buffer_.substr(committed_size_);
889 if (!composition_text.empty())
890 text_input_client_->InsertText(composition_text);
892 const size_t previous_buffer_size = string_buffer_.size();
893 string_buffer_.clear();
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);
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);
916 bool TSFTextStore::HasReadLock() const {
917 return (current_lock_type_ & TS_LF_READ) == TS_LF_READ;
920 bool TSFTextStore::HasReadWriteLock() const {
921 return (current_lock_type_ & TS_LF_READWRITE) == TS_LF_READWRITE;