Supervised users: Properly display long custodian emails in the disclaimer.
[chromium-blink-merge.git] / win8 / metro_driver / ime / text_store.cc
blob0c0389bfb3c8f340f9903bf38b28c90f7edcacb4
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"
7 #include <algorithm>
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 {
15 namespace {
17 // We support only one view.
18 const TsViewCookie kViewCookie = 1;
20 } // namespace
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) {
36 TextStore::~TextStore() {
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>();
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>();
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>();
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>();
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>();
75 object->Initialize(window_handle,
76 category_manager,
77 display_attribute_manager,
78 input_scope,
79 delegate);
80 return scoped_refptr<TextStore>(object);
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;
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;
109 if (FAILED(text_store_acp_sink_.QueryFrom(unknown)))
110 return E_UNEXPECTED;
111 text_store_acp_sink_mask_ = mask;
113 return S_OK;
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;
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;
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;
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;
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;
174 STDMETHODIMP TextStore::GetFormattedText(LONG acp_start,
175 LONG acp_end,
176 IDataObject** data_object) {
177 NOTIMPLEMENTED();
178 return E_NOTIMPL;
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;
187 // {0, 0, 0, 0} means that the document rect is not currently displayed.
188 SetRect(rect, 0, 0, 0, 0);
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;
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;
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;
226 return S_OK;
229 STDMETHODIMP TextStore::GetStatus(TS_STATUS* status) {
230 if (!status)
231 return E_INVALIDARG;
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;
237 return S_OK;
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;
267 acp_end = std::min(acp_end, acp_start + static_cast<LONG>(text_buffer_size));
268 *text_buffer_copied = acp_end - acp_start;
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];
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;
281 *next_acp = acp_end;
282 return S_OK;
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;
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_;
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;
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;
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.
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;
355 *rect = result;
356 *clipped = FALSE;
357 return S_OK;
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;
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;
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;
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;
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;
410 if (!HasReadWriteLock())
411 return TS_E_NOLOCK;
412 if (!text_buffer)
413 return E_INVALIDARG;
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;
428 selection_start_ = start_pos;
429 selection_end_ = new_end_pos;
430 return S_OK;
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;
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;
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;
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;
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;
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;
495 // Queue the lock request.
496 lock_queue_.push_back(lock_flags & TS_LF_READWRITE);
497 *result = TS_S_ASYNC;
498 return S_OK;
501 // Lock
502 current_lock_type_ = (lock_flags & TS_LF_READWRITE);
504 edit_flag_ = false;
505 const uint32 last_committed_size = committed_size_;
507 // Grant the lock.
508 *result = text_store_acp_sink_->OnLockGranted(current_lock_type_);
510 // Unlock
511 current_lock_type_ = 0;
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;
521 if (!edit_flag_)
522 return S_OK;
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);
534 // If there is new committed string, calls
535 // TextStoreDelegate::ConfirmComposition().
536 if ((!new_committed_string.empty()))
537 delegate_->OnTextCommitted(new_committed_string);
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;
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);
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);
575 return S_OK;
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;
591 return E_FAIL;
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;
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;
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;
632 selection_start_ = start_pos;
633 selection_end_ = end_pos;
635 return S_OK;
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;
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;
658 HRESULT ret;
659 ret = SetSelection(1, &selection);
660 if (ret != S_OK)
661 return ret;
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;
669 if (text_change)
670 *text_change = change;
672 return S_OK;
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;
683 STDMETHODIMP TextStore::OnStartComposition(
684 ITfCompositionView* composition_view,
685 BOOL* ok) {
686 if (ok)
687 *ok = TRUE;
688 return S_OK;
691 STDMETHODIMP TextStore::OnUpdateComposition(
692 ITfCompositionView* composition_view,
693 ITfRange* range) {
694 return S_OK;
697 STDMETHODIMP TextStore::OnEndComposition(
698 ITfCompositionView* composition_view) {
699 return S_OK;
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;
711 edit_flag_ = true;
712 return S_OK;
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;
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;
726 return SUCCEEDED(display_attribute_info->GetAttributeInfo(attribute));
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;
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;
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;
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;
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;
775 if (FAILED(enum_prop_value.QueryFrom(value.AsInput()->punkVal)))
776 return false;
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;
791 VariantClear(&property_value.varValue);
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;
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;
813 if (string_buffer_.empty())
814 return true;
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.
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);
841 return true;
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;
849 if (string_buffer_.empty())
850 return true;
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.
856 const base::string16& composition_text =
857 string_buffer_.substr(committed_size_);
858 if (!composition_text.empty())
859 delegate_->OnTextCommitted(composition_text);
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);
878 return true;
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);
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