Supervised user whitelists: Cleanup
[chromium-blink-merge.git] / win8 / metro_driver / ime / text_store.cc
blobeff30b7abe15b038d345c4531fa873fcb42669e4
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.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);
70 if (FAILED(hr)) {
71 LOG(ERROR) << "CComObject<TextStore>::CreateInstance failed. hr = "
72 << hr;
73 return scoped_refptr<TextStore>();
75 object->Initialize(window_handle, category_manager.get(),
76 display_attribute_manager.get(), input_scope.get(),
77 delegate);
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;
90 delegate_ = delegate;
94 STDMETHODIMP TextStore::AdviseSink(REFIID iid,
95 IUnknown* unknown,
96 DWORD mask) {
97 if (!IsEqualGUID(iid, IID_ITextStoreACPSink))
98 return E_INVALIDARG;
99 if (text_store_acp_sink_.get()) {
100 if (text_store_acp_sink_.IsSameObject(unknown)) {
101 text_store_acp_sink_mask_ = mask;
102 return S_OK;
103 } else {
104 return CONNECT_E_ADVISELIMIT;
107 if (FAILED(text_store_acp_sink_.QueryFrom(unknown)))
108 return E_UNEXPECTED;
109 text_store_acp_sink_mask_ = mask;
111 return S_OK;
114 STDMETHODIMP TextStore::FindNextAttrTransition(
115 LONG acp_start,
116 LONG acp_halt,
117 ULONG num_filter_attributes,
118 const TS_ATTRID* filter_attributes,
119 DWORD flags,
120 LONG* acp_next,
121 BOOL* found,
122 LONG* found_offset) {
123 if (!acp_next || !found || !found_offset)
124 return E_INVALIDARG;
125 // We don't support any attributes.
126 // So we always return "not found".
127 *acp_next = 0;
128 *found = FALSE;
129 *found_offset = 0;
130 return S_OK;
133 STDMETHODIMP TextStore::GetACPFromPoint(TsViewCookie view_cookie,
134 const POINT* point,
135 DWORD flags,
136 LONG* acp) {
137 NOTIMPLEMENTED();
138 if (view_cookie != kViewCookie)
139 return E_INVALIDARG;
140 return E_NOTIMPL;
143 STDMETHODIMP TextStore::GetActiveView(TsViewCookie* view_cookie) {
144 if (!view_cookie)
145 return E_INVALIDARG;
146 // We support only one view.
147 *view_cookie = kViewCookie;
148 return S_OK;
151 STDMETHODIMP TextStore::GetEmbedded(LONG acp_pos,
152 REFGUID service,
153 REFIID iid,
154 IUnknown** unknown) {
155 // We don't support any embedded objects.
156 NOTIMPLEMENTED();
157 if (!unknown)
158 return E_INVALIDARG;
159 *unknown = NULL;
160 return E_NOTIMPL;
163 STDMETHODIMP TextStore::GetEndACP(LONG* acp) {
164 if (!acp)
165 return E_INVALIDARG;
166 if (!HasReadLock())
167 return TS_E_NOLOCK;
168 *acp = static_cast<LONG>(string_buffer_.size());
169 return S_OK;
172 STDMETHODIMP TextStore::GetFormattedText(LONG acp_start,
173 LONG acp_end,
174 IDataObject** data_object) {
175 NOTIMPLEMENTED();
176 return E_NOTIMPL;
179 STDMETHODIMP TextStore::GetScreenExt(TsViewCookie view_cookie, RECT* rect) {
180 if (view_cookie != kViewCookie)
181 return E_INVALIDARG;
182 if (!rect)
183 return E_INVALIDARG;
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))
190 return E_FAIL;
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))
194 return E_FAIL;
195 if (!ClientToScreen(window_handle_, &right_bottom))
196 return E_FAIL;
198 rect->left = left_top.x;
199 rect->top = left_top.y;
200 rect->right = right_bottom.x;
201 rect->bottom = right_bottom.y;
202 return S_OK;
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)
210 return E_INVALIDARG;
211 if (!fetched_count)
212 return E_INVALIDARG;
213 if (!HasReadLock())
214 return TS_E_NOLOCK;
215 *fetched_count = 0;
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;
222 *fetched_count = 1;
224 return S_OK;
227 STDMETHODIMP TextStore::GetStatus(TS_STATUS* status) {
228 if (!status)
229 return E_INVALIDARG;
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;
235 return S_OK;
238 STDMETHODIMP TextStore::GetText(LONG acp_start,
239 LONG acp_end,
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,
246 LONG* next_acp) {
247 if (!text_buffer_copied || !run_info_buffer_copied)
248 return E_INVALIDARG;
249 if (!text_buffer && text_buffer_size != 0)
250 return E_INVALIDARG;
251 if (!run_info_buffer && run_info_buffer_size != 0)
252 return E_INVALIDARG;
253 if (!next_acp)
254 return E_INVALIDARG;
255 if (!HasReadLock())
256 return TF_E_NOLOCK;
257 const LONG string_buffer_size = static_cast<LONG>(string_buffer_.size());
258 if (acp_end == -1)
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;
279 *next_acp = acp_end;
280 return S_OK;
283 STDMETHODIMP TextStore::GetTextExt(TsViewCookie view_cookie,
284 LONG acp_start,
285 LONG acp_end,
286 RECT* rect,
287 BOOL* clipped) {
288 if (!rect || !clipped)
289 return E_INVALIDARG;
290 if (view_cookie != kViewCookie)
291 return E_INVALIDARG;
292 if (!HasReadLock())
293 return TS_E_NOLOCK;
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.
305 RECT result;
306 RECT tmp_rect;
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;
319 result = tmp_rect;
320 } else if (string_buffer_.size() == committed_size_) {
321 result = delegate_->GetCaretBounds();
322 } else {
323 return TS_E_NOLAYOUT;
325 } else if (delegate_->GetCompositionCharacterBounds(start_pos - 1,
326 &tmp_rect)) {
327 tmp_rect.left = tmp_rect.right;
328 result = tmp_rect;
329 } else {
330 return TS_E_NOLAYOUT;
332 } else {
333 if (delegate_->GetCompositionCharacterBounds(start_pos, &tmp_rect)) {
334 result = tmp_rect;
335 if (delegate_->GetCompositionCharacterBounds(end_pos - 1, &tmp_rect)) {
336 result.right = tmp_rect.right;
337 result.bottom = tmp_rect.bottom;
338 } else {
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.
342 } else {
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.
346 if (start_pos == 0)
347 result = delegate_->GetCaretBounds();
348 else
349 return TS_E_NOLAYOUT;
353 *rect = result;
354 *clipped = FALSE;
355 return S_OK;
358 STDMETHODIMP TextStore::GetWnd(TsViewCookie view_cookie,
359 HWND* window_handle) {
360 if (!window_handle)
361 return E_INVALIDARG;
362 if (view_cookie != kViewCookie)
363 return E_INVALIDARG;
364 *window_handle = window_handle_;
365 return S_OK;
368 STDMETHODIMP TextStore::InsertEmbedded(DWORD flags,
369 LONG acp_start,
370 LONG acp_end,
371 IDataObject* data_object,
372 TS_TEXTCHANGE* change) {
373 // We don't support any embedded objects.
374 NOTIMPLEMENTED();
375 return E_NOTIMPL;
378 STDMETHODIMP TextStore::InsertEmbeddedAtSelection(DWORD flags,
379 IDataObject* data_object,
380 LONG* acp_start,
381 LONG* acp_end,
382 TS_TEXTCHANGE* change) {
383 // We don't support any embedded objects.
384 NOTIMPLEMENTED();
385 return E_NOTIMPL;
388 STDMETHODIMP TextStore::InsertTextAtSelection(DWORD flags,
389 const wchar_t* text_buffer,
390 ULONG text_buffer_size,
391 LONG* acp_start,
392 LONG* acp_end,
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) {
399 if (!HasReadLock())
400 return TS_E_NOLOCK;
401 if (acp_start)
402 *acp_start = start_pos;
403 if (acp_end)
404 *acp_end = end_pos;
405 return S_OK;
408 if (!HasReadWriteLock())
409 return TS_E_NOLOCK;
410 if (!text_buffer)
411 return E_INVALIDARG;
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;
426 selection_start_ = start_pos;
427 selection_end_ = new_end_pos;
428 return S_OK;
431 STDMETHODIMP TextStore::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;
448 STDMETHODIMP TextStore::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;
459 STDMETHODIMP TextStore::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;
470 STDMETHODIMP TextStore::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;
481 STDMETHODIMP TextStore::RequestLock(DWORD lock_flags, HRESULT* result) {
482 if (!text_store_acp_sink_.get())
483 return E_FAIL;
484 if (!result)
485 return E_INVALIDARG;
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;
493 // Queue the lock request.
494 lock_queue_.push_back(lock_flags & TS_LF_READWRITE);
495 *result = TS_S_ASYNC;
496 return S_OK;
499 // Lock
500 current_lock_type_ = (lock_flags & TS_LF_READWRITE);
502 edit_flag_ = false;
503 const uint32 last_committed_size = committed_size_;
505 // Grant the lock.
506 *result = text_store_acp_sink_->OnLockGranted(current_lock_type_);
508 // Unlock
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;
519 if (!edit_flag_)
520 return S_OK;
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();
557 committed_size_ = 0;
558 selection_start_ = 0;
559 selection_end_ = 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);
573 return S_OK;
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)
581 return E_INVALIDARG;
582 if (!input_scope_.get())
583 return E_FAIL;
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]))
587 return S_OK;
589 return E_FAIL;
592 STDMETHODIMP TextStore::RetrieveRequestedAttrs(
593 ULONG attribute_buffer_size,
594 TS_ATTRVAL* attribute_buffer,
595 ULONG* attribute_buffer_copied) {
596 if (!attribute_buffer_copied)
597 return E_INVALIDARG;
598 *attribute_buffer_copied = 0;
599 if (!attribute_buffer)
600 return E_INVALIDARG;
601 if (!input_scope_.get())
602 return E_UNEXPECTED;
603 // We support only input scope attribute.
604 *attribute_buffer_copied = 0;
605 if (attribute_buffer_size == 0)
606 return S_OK;
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;
614 return S_OK;
617 STDMETHODIMP TextStore::SetSelection(
618 ULONG selection_buffer_size,
619 const TS_SELECTION_ACP* selection_buffer) {
620 if (!HasReadWriteLock())
621 return TF_E_NOLOCK;
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;
633 return S_OK;
636 STDMETHODIMP TextStore::SetText(DWORD flags,
637 LONG acp_start,
638 LONG acp_end,
639 const wchar_t* text_buffer,
640 ULONG text_buffer_size,
641 TS_TEXTCHANGE* text_change) {
642 if (!HasReadWriteLock())
643 return TS_E_NOLOCK;
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;
656 HRESULT ret;
657 ret = SetSelection(1, &selection);
658 if (ret != S_OK)
659 return ret;
661 TS_TEXTCHANGE change;
662 ret = InsertTextAtSelection(0, text_buffer, text_buffer_size,
663 &acp_start, &acp_end, &change);
664 if (ret != S_OK)
665 return ret;
667 if (text_change)
668 *text_change = change;
670 return S_OK;
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;
678 return S_OK;
681 STDMETHODIMP TextStore::OnStartComposition(
682 ITfCompositionView* composition_view,
683 BOOL* ok) {
684 if (ok)
685 *ok = TRUE;
686 return S_OK;
689 STDMETHODIMP TextStore::OnUpdateComposition(
690 ITfCompositionView* composition_view,
691 ITfRange* range) {
692 return S_OK;
695 STDMETHODIMP TextStore::OnEndComposition(
696 ITfCompositionView* composition_view) {
697 return S_OK;
700 STDMETHODIMP TextStore::OnEndEdit(ITfContext* context,
701 TfEditCookie read_only_edit_cookie,
702 ITfEditRecord* edit_record) {
703 if (!context || !edit_record)
704 return E_INVALIDARG;
705 if (!GetCompositionStatus(context, read_only_edit_cookie, &committed_size_,
706 &underlines_)) {
707 return S_OK;
709 edit_flag_ = true;
710 return S_OK;
713 bool TextStore::GetDisplayAttribute(TfGuidAtom guid_atom,
714 TF_DISPLAYATTRIBUTE* attribute) {
715 GUID guid;
716 if (FAILED(category_manager_->GetGUID(guid_atom, &guid)))
717 return false;
719 base::win::ScopedComPtr<ITfDisplayAttributeInfo> display_attribute_info;
720 if (FAILED(display_attribute_manager_->GetDisplayAttributeInfo(
721 guid, display_attribute_info.Receive(), NULL))) {
722 return false;
724 return SUCCEEDED(display_attribute_info->GetAttributeInfo(attribute));
727 bool TextStore::GetCompositionStatus(
728 ITfContext* context,
729 const TfEditCookie read_only_edit_cookie,
730 uint32* committed_size,
731 std::vector<metro_viewer::UnderlineInfo>* undelines) {
732 DCHECK(context);
733 DCHECK(committed_size);
734 DCHECK(undelines);
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()))) {
739 return false;
742 *committed_size = 0;
743 undelines->clear();
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()))) {
748 return false;
750 if (FAILED(context->GetEnd(read_only_edit_cookie, end_range.Receive())))
751 return false;
752 if (FAILED(start_to_end_range->ShiftEndToRange(
753 read_only_edit_cookie, end_range.get(), TF_ANCHOR_END))) {
754 return false;
757 base::win::ScopedComPtr<IEnumTfRanges> ranges;
758 if (FAILED(track_property->EnumRanges(read_only_edit_cookie, ranges.Receive(),
759 start_to_end_range.get()))) {
760 return false;
763 while (true) {
764 base::win::ScopedComPtr<ITfRange> range;
765 if (ranges->Next(1, range.Receive(), NULL) != S_OK)
766 return true;
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(),
770 value.Receive()))) {
771 return false;
773 if (FAILED(enum_prop_value.QueryFrom(value.AsInput()->punkVal)))
774 return false;
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.
808 if (edit_flag_)
809 return false;
811 if (string_buffer_.empty())
812 return true;
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();
825 committed_size_ = 0;
826 selection_start_ = 0;
827 selection_end_ = 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);
839 return true;
842 bool TextStore::ConfirmComposition() {
843 // If there is an on-going document lock, we must not edit the text.
844 if (edit_flag_)
845 return false;
847 if (string_buffer_.empty())
848 return true;
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();
862 committed_size_ = 0;
863 selection_start_ = 0;
864 selection_end_ = 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);
876 return true;
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