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.
5 #include "win8/metro_driver/ime/text_store.h"
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"
14 namespace metro_driver
{
17 // We support only one view.
18 const TsViewCookie kViewCookie
= 1;
22 TextStore::TextStore()
23 : text_store_acp_sink_mask_(0),
30 current_lock_type_(0),
31 category_manager_(NULL
),
32 display_attribute_manager_(NULL
),
36 TextStore::~TextStore() {
40 scoped_refptr
<TextStore
> TextStore::Create(
42 const std::vector
<InputScope
>& input_scopes
,
43 TextStoreDelegate
* delegate
) {
45 LOG(ERROR
) << "|delegate| must be non-NULL.";
46 return scoped_refptr
<TextStore
>();
48 base::win::ScopedComPtr
<ITfCategoryMgr
> category_manager
;
49 HRESULT hr
= category_manager
.CreateInstance(CLSID_TF_CategoryMgr
);
51 LOG(ERROR
) << "Failed to initialize CategoryMgr. hr = " << hr
;
52 return scoped_refptr
<TextStore
>();
54 base::win::ScopedComPtr
<ITfDisplayAttributeMgr
> display_attribute_manager
;
55 hr
= display_attribute_manager
.CreateInstance(CLSID_TF_DisplayAttributeMgr
);
57 LOG(ERROR
) << "Failed to initialize DisplayAttributeMgr. hr = " << hr
;
58 return scoped_refptr
<TextStore
>();
60 base::win::ScopedComPtr
<ITfInputScope
> input_scope
=
61 CreteInputScope(input_scopes
);
62 if (!input_scope
.get()) {
63 LOG(ERROR
) << "Failed to initialize InputScope.";
64 return scoped_refptr
<TextStore
>();
67 ui::win::CreateATLModuleIfNeeded();
68 CComObject
<TextStore
>* object
= NULL
;
69 hr
= CComObject
<TextStore
>::CreateInstance(&object
);
71 LOG(ERROR
) << "CComObject<TextStore>::CreateInstance failed. hr = "
73 return scoped_refptr
<TextStore
>();
75 object
->Initialize(window_handle
, category_manager
.get(),
76 display_attribute_manager
.get(), input_scope
.get(),
78 return scoped_refptr
<TextStore
>(object
);
81 void TextStore::Initialize(HWND window_handle
,
82 ITfCategoryMgr
* category_manager
,
83 ITfDisplayAttributeMgr
* display_attribute_manager
,
84 ITfInputScope
* input_scope
,
85 TextStoreDelegate
* delegate
) {
86 window_handle_
= window_handle
;
87 category_manager_
= category_manager
;
88 display_attribute_manager_
= display_attribute_manager
;
89 input_scope_
= input_scope
;
94 STDMETHODIMP
TextStore::AdviseSink(REFIID iid
,
97 if (!IsEqualGUID(iid
, IID_ITextStoreACPSink
))
99 if (text_store_acp_sink_
.get()) {
100 if (text_store_acp_sink_
.IsSameObject(unknown
)) {
101 text_store_acp_sink_mask_
= mask
;
104 return CONNECT_E_ADVISELIMIT
;
107 if (FAILED(text_store_acp_sink_
.QueryFrom(unknown
)))
109 text_store_acp_sink_mask_
= mask
;
114 STDMETHODIMP
TextStore::FindNextAttrTransition(
117 ULONG num_filter_attributes
,
118 const TS_ATTRID
* filter_attributes
,
122 LONG
* found_offset
) {
123 if (!acp_next
|| !found
|| !found_offset
)
125 // We don't support any attributes.
126 // So we always return "not found".
133 STDMETHODIMP
TextStore::GetACPFromPoint(TsViewCookie view_cookie
,
138 if (view_cookie
!= kViewCookie
)
143 STDMETHODIMP
TextStore::GetActiveView(TsViewCookie
* view_cookie
) {
146 // We support only one view.
147 *view_cookie
= kViewCookie
;
151 STDMETHODIMP
TextStore::GetEmbedded(LONG acp_pos
,
154 IUnknown
** unknown
) {
155 // We don't support any embedded objects.
163 STDMETHODIMP
TextStore::GetEndACP(LONG
* acp
) {
168 *acp
= static_cast<LONG
>(string_buffer_
.size());
172 STDMETHODIMP
TextStore::GetFormattedText(LONG acp_start
,
174 IDataObject
** data_object
) {
179 STDMETHODIMP
TextStore::GetScreenExt(TsViewCookie view_cookie
, RECT
* rect
) {
180 if (view_cookie
!= kViewCookie
)
185 // {0, 0, 0, 0} means that the document rect is not currently displayed.
186 SetRect(rect
, 0, 0, 0, 0);
188 RECT client_rect
= {};
189 if (!GetClientRect(window_handle_
, &client_rect
))
191 POINT left_top
= {client_rect
.left
, client_rect
.top
};
192 POINT right_bottom
= {client_rect
.right
, client_rect
.bottom
};
193 if (!ClientToScreen(window_handle_
, &left_top
))
195 if (!ClientToScreen(window_handle_
, &right_bottom
))
198 rect
->left
= left_top
.x
;
199 rect
->top
= left_top
.y
;
200 rect
->right
= right_bottom
.x
;
201 rect
->bottom
= right_bottom
.y
;
205 STDMETHODIMP
TextStore::GetSelection(ULONG selection_index
,
206 ULONG selection_buffer_size
,
207 TS_SELECTION_ACP
* selection_buffer
,
208 ULONG
* fetched_count
) {
209 if (!selection_buffer
)
216 if ((selection_buffer_size
> 0) &&
217 ((selection_index
== 0) || (selection_index
== TS_DEFAULT_SELECTION
))) {
218 selection_buffer
[0].acpStart
= selection_start_
;
219 selection_buffer
[0].acpEnd
= selection_end_
;
220 selection_buffer
[0].style
.ase
= TS_AE_END
;
221 selection_buffer
[0].style
.fInterimChar
= FALSE
;
227 STDMETHODIMP
TextStore::GetStatus(TS_STATUS
* status
) {
231 status
->dwDynamicFlags
= 0;
232 // We use transitory contexts and we don't support hidden text.
233 status
->dwStaticFlags
= TS_SS_TRANSITORY
| TS_SS_NOHIDDENTEXT
;
238 STDMETHODIMP
TextStore::GetText(LONG acp_start
,
240 wchar_t* text_buffer
,
241 ULONG text_buffer_size
,
242 ULONG
* text_buffer_copied
,
243 TS_RUNINFO
* run_info_buffer
,
244 ULONG run_info_buffer_size
,
245 ULONG
* run_info_buffer_copied
,
247 if (!text_buffer_copied
|| !run_info_buffer_copied
)
249 if (!text_buffer
&& text_buffer_size
!= 0)
251 if (!run_info_buffer
&& run_info_buffer_size
!= 0)
257 const LONG string_buffer_size
= static_cast<LONG
>(string_buffer_
.size());
259 acp_end
= string_buffer_size
;
260 if (!((0 <= acp_start
) &&
261 (acp_start
<= acp_end
) &&
262 (acp_end
<= string_buffer_size
))) {
263 return TF_E_INVALIDPOS
;
265 acp_end
= std::min(acp_end
, acp_start
+ static_cast<LONG
>(text_buffer_size
));
266 *text_buffer_copied
= acp_end
- acp_start
;
268 const base::string16
& result
=
269 string_buffer_
.substr(acp_start
, *text_buffer_copied
);
270 for (size_t i
= 0; i
< result
.size(); ++i
)
271 text_buffer
[i
] = result
[i
];
273 if (run_info_buffer_size
) {
274 run_info_buffer
[0].uCount
= *text_buffer_copied
;
275 run_info_buffer
[0].type
= TS_RT_PLAIN
;
276 *run_info_buffer_copied
= 1;
283 STDMETHODIMP
TextStore::GetTextExt(TsViewCookie view_cookie
,
288 if (!rect
|| !clipped
)
290 if (view_cookie
!= kViewCookie
)
294 if (!((static_cast<LONG
>(committed_size_
) <= acp_start
) &&
295 (acp_start
<= acp_end
) &&
296 (acp_end
<= static_cast<LONG
>(string_buffer_
.size())))) {
297 return TS_E_INVALIDPOS
;
300 // According to a behavior of notepad.exe and wordpad.exe, top left corner of
301 // rect indicates a first character's one, and bottom right corner of rect
302 // indicates a last character's one.
303 // We use RECT instead of gfx::Rect since left position may be bigger than
304 // right position when composition has multiple lines.
307 const uint32 start_pos
= acp_start
- committed_size_
;
308 const uint32 end_pos
= acp_end
- committed_size_
;
310 if (start_pos
== end_pos
) {
311 // According to MSDN document, if |acp_start| and |acp_end| are equal it is
312 // OK to just return E_INVALIDARG.
313 // http://msdn.microsoft.com/en-us/library/ms538435
314 // But when using Pinin IME of Windows 8, this method is called with the
315 // equal values of |acp_start| and |acp_end|. So we handle this condition.
316 if (start_pos
== 0) {
317 if (delegate_
->GetCompositionCharacterBounds(0, &tmp_rect
)) {
318 tmp_rect
.right
= tmp_rect
.right
;
320 } else if (string_buffer_
.size() == committed_size_
) {
321 result
= delegate_
->GetCaretBounds();
323 return TS_E_NOLAYOUT
;
325 } else if (delegate_
->GetCompositionCharacterBounds(start_pos
- 1,
327 tmp_rect
.left
= tmp_rect
.right
;
330 return TS_E_NOLAYOUT
;
333 if (delegate_
->GetCompositionCharacterBounds(start_pos
, &tmp_rect
)) {
335 if (delegate_
->GetCompositionCharacterBounds(end_pos
- 1, &tmp_rect
)) {
336 result
.right
= tmp_rect
.right
;
337 result
.bottom
= tmp_rect
.bottom
;
339 // We may not be able to get the last character bounds, so we use the
340 // first character bounds instead of returning TS_E_NOLAYOUT.
343 // Hack for PPAPI flash. PPAPI flash does not support GetCaretBounds, so
344 // it's better to return previous caret rectangle instead.
345 // TODO(nona, kinaba): Remove this hack.
347 result
= delegate_
->GetCaretBounds();
349 return TS_E_NOLAYOUT
;
358 STDMETHODIMP
TextStore::GetWnd(TsViewCookie view_cookie
,
359 HWND
* window_handle
) {
362 if (view_cookie
!= kViewCookie
)
364 *window_handle
= window_handle_
;
368 STDMETHODIMP
TextStore::InsertEmbedded(DWORD flags
,
371 IDataObject
* data_object
,
372 TS_TEXTCHANGE
* change
) {
373 // We don't support any embedded objects.
378 STDMETHODIMP
TextStore::InsertEmbeddedAtSelection(DWORD flags
,
379 IDataObject
* data_object
,
382 TS_TEXTCHANGE
* change
) {
383 // We don't support any embedded objects.
388 STDMETHODIMP
TextStore::InsertTextAtSelection(DWORD flags
,
389 const wchar_t* text_buffer
,
390 ULONG text_buffer_size
,
393 TS_TEXTCHANGE
* text_change
) {
394 const LONG start_pos
= selection_start_
;
395 const LONG end_pos
= selection_end_
;
396 const LONG new_end_pos
= start_pos
+ text_buffer_size
;
398 if (flags
& TS_IAS_QUERYONLY
) {
402 *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_start_
= start_pos
;
427 selection_end_
= new_end_pos
;
431 STDMETHODIMP
TextStore::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
TextStore::QueryInsertEmbedded(const GUID
* service
,
449 const FORMATETC
* format
,
453 // We don't support any embedded objects.
459 STDMETHODIMP
TextStore::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
TextStore::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
TextStore::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 uint32 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;
522 // If the text store is edited in OnLockGranted(), we may need to call
523 // TextStoreDelegate::ConfirmComposition() or
524 // TextStoreDelegate::SetComposition().
525 const uint32 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
533 // TextStoreDelegate::ConfirmComposition().
534 if ((!new_committed_string
.empty()))
535 delegate_
->OnTextCommitted(new_committed_string
);
537 // Calls TextInputClient::SetCompositionText().
538 std::vector
<metro_viewer::UnderlineInfo
> underlines
= underlines_
;
539 // Adjusts the offset.
540 for (size_t i
= 0; i
< underlines_
.size(); ++i
) {
541 underlines
[i
].start_offset
-= new_committed_size
;
542 underlines
[i
].end_offset
-= new_committed_size
;
544 int32 selection_start
= 0;
545 int32 selection_end
= 0;
546 if (selection_start_
>= new_committed_size
)
547 selection_start
= selection_start_
- new_committed_size
;
548 if (selection_end_
>= new_committed_size
)
549 selection_end
= selection_end_
- new_committed_size
;
550 delegate_
->OnCompositionChanged(
551 composition_string
, selection_start
, selection_end
, underlines
);
553 // If there is no composition string, clear the text store status.
554 // And call OnSelectionChange(), OnLayoutChange(), and OnTextChange().
555 if ((composition_string
.empty()) && (new_committed_size
!= 0)) {
556 string_buffer_
.clear();
558 selection_start_
= 0;
560 if (text_store_acp_sink_mask_
& TS_AS_SEL_CHANGE
)
561 text_store_acp_sink_
->OnSelectionChange();
562 if (text_store_acp_sink_mask_
& TS_AS_LAYOUT_CHANGE
)
563 text_store_acp_sink_
->OnLayoutChange(TS_LC_CHANGE
, 0);
564 if (text_store_acp_sink_mask_
& TS_AS_TEXT_CHANGE
) {
565 TS_TEXTCHANGE textChange
;
566 textChange
.acpStart
= 0;
567 textChange
.acpOldEnd
= new_committed_size
;
568 textChange
.acpNewEnd
= 0;
569 text_store_acp_sink_
->OnTextChange(0, &textChange
);
576 STDMETHODIMP
TextStore::RequestSupportedAttrs(
577 DWORD
/* flags */, // Seems that we should ignore this.
578 ULONG attribute_buffer_size
,
579 const TS_ATTRID
* attribute_buffer
) {
580 if (!attribute_buffer
)
582 if (!input_scope_
.get())
584 // We support only input scope attribute.
585 for (size_t i
= 0; i
< attribute_buffer_size
; ++i
) {
586 if (IsEqualGUID(GUID_PROP_INPUTSCOPE
, attribute_buffer
[i
]))
592 STDMETHODIMP
TextStore::RetrieveRequestedAttrs(
593 ULONG attribute_buffer_size
,
594 TS_ATTRVAL
* attribute_buffer
,
595 ULONG
* attribute_buffer_copied
) {
596 if (!attribute_buffer_copied
)
598 *attribute_buffer_copied
= 0;
599 if (!attribute_buffer
)
601 if (!input_scope_
.get())
603 // We support only input scope attribute.
604 *attribute_buffer_copied
= 0;
605 if (attribute_buffer_size
== 0)
608 attribute_buffer
[0].dwOverlapId
= 0;
609 attribute_buffer
[0].idAttr
= GUID_PROP_INPUTSCOPE
;
610 attribute_buffer
[0].varValue
.vt
= VT_UNKNOWN
;
611 attribute_buffer
[0].varValue
.punkVal
= input_scope_
.get();
612 attribute_buffer
[0].varValue
.punkVal
->AddRef();
613 *attribute_buffer_copied
= 1;
617 STDMETHODIMP
TextStore::SetSelection(
618 ULONG selection_buffer_size
,
619 const TS_SELECTION_ACP
* selection_buffer
) {
620 if (!HasReadWriteLock())
622 if (selection_buffer_size
> 0) {
623 const LONG start_pos
= selection_buffer
[0].acpStart
;
624 const LONG end_pos
= selection_buffer
[0].acpEnd
;
625 if (!((static_cast<LONG
>(committed_size_
) <= start_pos
) &&
626 (start_pos
<= end_pos
) &&
627 (end_pos
<= static_cast<LONG
>(string_buffer_
.size())))) {
628 return TF_E_INVALIDPOS
;
630 selection_start_
= start_pos
;
631 selection_end_
= end_pos
;
636 STDMETHODIMP
TextStore::SetText(DWORD flags
,
639 const wchar_t* text_buffer
,
640 ULONG text_buffer_size
,
641 TS_TEXTCHANGE
* text_change
) {
642 if (!HasReadWriteLock())
644 if (!((static_cast<LONG
>(committed_size_
) <= acp_start
) &&
645 (acp_start
<= acp_end
) &&
646 (acp_end
<= static_cast<LONG
>(string_buffer_
.size())))) {
647 return TS_E_INVALIDPOS
;
650 TS_SELECTION_ACP selection
;
651 selection
.acpStart
= acp_start
;
652 selection
.acpEnd
= acp_end
;
653 selection
.style
.ase
= TS_AE_NONE
;
654 selection
.style
.fInterimChar
= 0;
657 ret
= SetSelection(1, &selection
);
661 TS_TEXTCHANGE change
;
662 ret
= InsertTextAtSelection(0, text_buffer
, text_buffer_size
,
663 &acp_start
, &acp_end
, &change
);
668 *text_change
= change
;
673 STDMETHODIMP
TextStore::UnadviseSink(IUnknown
* unknown
) {
674 if (!text_store_acp_sink_
.IsSameObject(unknown
))
675 return CONNECT_E_NOCONNECTION
;
676 text_store_acp_sink_
.Release();
677 text_store_acp_sink_mask_
= 0;
681 STDMETHODIMP
TextStore::OnStartComposition(
682 ITfCompositionView
* composition_view
,
689 STDMETHODIMP
TextStore::OnUpdateComposition(
690 ITfCompositionView
* composition_view
,
695 STDMETHODIMP
TextStore::OnEndComposition(
696 ITfCompositionView
* composition_view
) {
700 STDMETHODIMP
TextStore::OnEndEdit(ITfContext
* context
,
701 TfEditCookie read_only_edit_cookie
,
702 ITfEditRecord
* edit_record
) {
703 if (!context
|| !edit_record
)
705 if (!GetCompositionStatus(context
, read_only_edit_cookie
, &committed_size_
,
713 bool TextStore::GetDisplayAttribute(TfGuidAtom guid_atom
,
714 TF_DISPLAYATTRIBUTE
* attribute
) {
716 if (FAILED(category_manager_
->GetGUID(guid_atom
, &guid
)))
719 base::win::ScopedComPtr
<ITfDisplayAttributeInfo
> display_attribute_info
;
720 if (FAILED(display_attribute_manager_
->GetDisplayAttributeInfo(
721 guid
, display_attribute_info
.Receive(), NULL
))) {
724 return SUCCEEDED(display_attribute_info
->GetAttributeInfo(attribute
));
727 bool TextStore::GetCompositionStatus(
729 const TfEditCookie read_only_edit_cookie
,
730 uint32
* committed_size
,
731 std::vector
<metro_viewer::UnderlineInfo
>* undelines
) {
733 DCHECK(committed_size
);
735 const GUID
* rgGuids
[2] = {&GUID_PROP_COMPOSING
, &GUID_PROP_ATTRIBUTE
};
736 base::win::ScopedComPtr
<ITfReadOnlyProperty
> track_property
;
737 if (FAILED(context
->TrackProperties(rgGuids
, 2, NULL
, 0,
738 track_property
.Receive()))) {
744 base::win::ScopedComPtr
<ITfRange
> start_to_end_range
;
745 base::win::ScopedComPtr
<ITfRange
> end_range
;
746 if (FAILED(context
->GetStart(read_only_edit_cookie
,
747 start_to_end_range
.Receive()))) {
750 if (FAILED(context
->GetEnd(read_only_edit_cookie
, end_range
.Receive())))
752 if (FAILED(start_to_end_range
->ShiftEndToRange(
753 read_only_edit_cookie
, end_range
.get(), TF_ANCHOR_END
))) {
757 base::win::ScopedComPtr
<IEnumTfRanges
> ranges
;
758 if (FAILED(track_property
->EnumRanges(read_only_edit_cookie
, ranges
.Receive(),
759 start_to_end_range
.get()))) {
764 base::win::ScopedComPtr
<ITfRange
> range
;
765 if (ranges
->Next(1, range
.Receive(), NULL
) != S_OK
)
767 base::win::ScopedVariant value
;
768 base::win::ScopedComPtr
<IEnumTfPropertyValue
> enum_prop_value
;
769 if (FAILED(track_property
->GetValue(read_only_edit_cookie
, range
.get(),
773 if (FAILED(enum_prop_value
.QueryFrom(value
.AsInput()->punkVal
)))
776 TF_PROPERTYVAL property_value
;
777 bool is_composition
= false;
778 metro_viewer::UnderlineInfo underline
;
779 while (enum_prop_value
->Next(1, &property_value
, NULL
) == S_OK
) {
780 if (IsEqualGUID(property_value
.guidId
, GUID_PROP_COMPOSING
)) {
781 is_composition
= (property_value
.varValue
.lVal
== TRUE
);
782 } else if (IsEqualGUID(property_value
.guidId
, GUID_PROP_ATTRIBUTE
)) {
783 TfGuidAtom guid_atom
=
784 static_cast<TfGuidAtom
>(property_value
.varValue
.lVal
);
785 TF_DISPLAYATTRIBUTE display_attribute
;
786 if (GetDisplayAttribute(guid_atom
, &display_attribute
))
787 underline
.thick
= !!display_attribute
.fBoldLine
;
789 VariantClear(&property_value
.varValue
);
792 base::win::ScopedComPtr
<ITfRangeACP
> range_acp
;
793 range_acp
.QueryFrom(range
.get());
794 LONG start_pos
, length
;
795 range_acp
->GetExtent(&start_pos
, &length
);
796 if (is_composition
) {
797 underline
.start_offset
= start_pos
;
798 underline
.end_offset
= start_pos
+ length
;
799 undelines
->push_back(underline
);
800 } else if (*committed_size
< static_cast<size_t>(start_pos
+ length
)) {
801 *committed_size
= start_pos
+ length
;
806 bool TextStore::CancelComposition() {
807 // If there is an on-going document lock, we must not edit the text.
811 if (string_buffer_
.empty())
814 // Unlike ImmNotifyIME(NI_COMPOSITIONSTR, CPS_CANCEL, 0) in IMM32, TSF does
815 // not have a dedicated method to cancel composition. However, CUAS actually
816 // has a protocol conversion from CPS_CANCEL into TSF operations. According
817 // to the observations on Windows 7, TIPs are expected to cancel composition
818 // when an on-going composition text is replaced with an empty string. So
819 // we use the same operation to cancel composition here to minimize the risk
820 // of potential compatibility issues.
822 const uint32 previous_buffer_size
=
823 static_cast<uint32
>(string_buffer_
.size());
824 string_buffer_
.clear();
826 selection_start_
= 0;
828 if (text_store_acp_sink_mask_
& TS_AS_SEL_CHANGE
)
829 text_store_acp_sink_
->OnSelectionChange();
830 if (text_store_acp_sink_mask_
& TS_AS_LAYOUT_CHANGE
)
831 text_store_acp_sink_
->OnLayoutChange(TS_LC_CHANGE
, 0);
832 if (text_store_acp_sink_mask_
& TS_AS_TEXT_CHANGE
) {
833 TS_TEXTCHANGE textChange
= {};
834 textChange
.acpStart
= 0;
835 textChange
.acpOldEnd
= previous_buffer_size
;
836 textChange
.acpNewEnd
= 0;
837 text_store_acp_sink_
->OnTextChange(0, &textChange
);
842 bool TextStore::ConfirmComposition() {
843 // If there is an on-going document lock, we must not edit the text.
847 if (string_buffer_
.empty())
850 // See the comment in TextStore::CancelComposition.
851 // This logic is based on the observation about how to emulate
852 // ImmNotifyIME(NI_COMPOSITIONSTR, CPS_COMPLETE, 0) by CUAS.
854 const base::string16
& composition_text
=
855 string_buffer_
.substr(committed_size_
);
856 if (!composition_text
.empty())
857 delegate_
->OnTextCommitted(composition_text
);
859 const uint32 previous_buffer_size
=
860 static_cast<uint32
>(string_buffer_
.size());
861 string_buffer_
.clear();
863 selection_start_
= 0;
865 if (text_store_acp_sink_mask_
& TS_AS_SEL_CHANGE
)
866 text_store_acp_sink_
->OnSelectionChange();
867 if (text_store_acp_sink_mask_
& TS_AS_LAYOUT_CHANGE
)
868 text_store_acp_sink_
->OnLayoutChange(TS_LC_CHANGE
, 0);
869 if (text_store_acp_sink_mask_
& TS_AS_TEXT_CHANGE
) {
870 TS_TEXTCHANGE textChange
= {};
871 textChange
.acpStart
= 0;
872 textChange
.acpOldEnd
= previous_buffer_size
;
873 textChange
.acpNewEnd
= 0;
874 text_store_acp_sink_
->OnTextChange(0, &textChange
);
879 void TextStore::SendOnLayoutChange() {
880 if (text_store_acp_sink_
.get() &&
881 (text_store_acp_sink_mask_
& TS_AS_LAYOUT_CHANGE
)) {
882 text_store_acp_sink_
->OnLayoutChange(TS_LC_CHANGE
, 0);
886 bool TextStore::HasReadLock() const {
887 return (current_lock_type_
& TS_LF_READ
) == TS_LF_READ
;
890 bool TextStore::HasReadWriteLock() const {
891 return (current_lock_type_
& TS_LF_READWRITE
) == TS_LF_READWRITE
;
894 } // namespace metro_driver