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. 4 5 #include "win8/metro_driver/ime/text_store.h" 6 7 #include <algorithm> 8 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" 13 14 namespace metro_driver { 15 namespace { 16 17 // We support only one view. 18 const TsViewCookie kViewCookie = 1; 19 20 } // namespace 21 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) { 34 } 35 36 TextStore::~TextStore() { 37 } 38 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>(); 47 } 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>(); 53 } 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>(); 59 } 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>(); 65 } 66 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>(); 74 } 75 object->Initialize(window_handle, 76 category_manager, 77 display_attribute_manager, 78 input_scope, 79 delegate); 80 return scoped_refptr<TextStore>(object); 81 } 82 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; 93 } 94 95 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; 107 } 108 } 109 if (FAILED(text_store_acp_sink_.QueryFrom(unknown))) 110 return E_UNEXPECTED; 111 text_store_acp_sink_mask_ = mask; 112 113 return S_OK; 114 } 115 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; 133 } 134 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; 143 } 144 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; 151 } 152 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; 163 } 164 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; 172 } 173 174 STDMETHODIMP TextStore::GetFormattedText(LONG acp_start, 175 LONG acp_end, 176 IDataObject** data_object) { 177 NOTIMPLEMENTED(); 178 return E_NOTIMPL; 179 } 180 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; 186 187 // {0, 0, 0, 0} means that the document rect is not currently displayed. 188 SetRect(rect, 0, 0, 0, 0); 189 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; 199 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; 205 } 206 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; 225 } 226 return S_OK; 227 } 228 229 STDMETHODIMP TextStore::GetStatus(TS_STATUS* status) { 230 if (!status) 231 return E_INVALIDARG; 232 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; 236 237 return S_OK; 238 } 239 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; 266 } 267 acp_end = std::min(acp_end, acp_start + static_cast<LONG>(text_buffer_size)); 268 *text_buffer_copied = acp_end - acp_start; 269 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]; 274 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; 279 } 280 281 *next_acp = acp_end; 282 return S_OK; 283 } 284 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; 300 } 301 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_; 311 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; 326 } 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; 333 } 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. 343 } 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; 352 } 353 } 354 355 *rect = result; 356 *clipped = FALSE; 357 return S_OK; 358 } 359 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; 368 } 369 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; 378 } 379 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; 388 } 389 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; 399 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; 408 } 409 410 if (!HasReadWriteLock()) 411 return TS_E_NOLOCK; 412 if (!text_buffer) 413 return E_INVALIDARG; 414 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; 427 } 428 selection_start_ = start_pos; 429 selection_end_ = new_end_pos; 430 return S_OK; 431 } 432 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; 448 } 449 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; 459 } 460 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; 470 } 471 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; 481 } 482 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; 488 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; 494 } 495 // Queue the lock request. 496 lock_queue_.push_back(lock_flags & TS_LF_READWRITE); 497 *result = TS_S_ASYNC; 498 return S_OK; 499 } 500 501 // Lock 502 current_lock_type_ = (lock_flags & TS_LF_READWRITE); 503 504 edit_flag_ = false; 505 const uint32 last_committed_size = committed_size_; 506 507 // Grant the lock. 508 *result = text_store_acp_sink_->OnLockGranted(current_lock_type_); 509 510 // Unlock 511 current_lock_type_ = 0; 512 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; 519 } 520 521 if (!edit_flag_) 522 return S_OK; 523 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); 533 534 // If there is new committed string, calls 535 // TextStoreDelegate::ConfirmComposition(). 536 if ((!new_committed_string.empty())) 537 delegate_->OnTextCommitted(new_committed_string); 538 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; 545 } 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); 554 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); 572 } 573 } 574 575 return S_OK; 576 } 577 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; 590 } 591 return E_FAIL; 592 } 593 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; 609 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; 617 } 618 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; 631 } 632 selection_start_ = start_pos; 633 selection_end_ = end_pos; 634 } 635 return S_OK; 636 } 637 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; 650 } 651 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; 657 658 HRESULT ret; 659 ret = SetSelection(1, &selection); 660 if (ret != S_OK) 661 return ret; 662 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; 668 669 if (text_change) 670 *text_change = change; 671 672 return S_OK; 673 } 674 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; 681 } 682 683 STDMETHODIMP TextStore::OnStartComposition( 684 ITfCompositionView* composition_view, 685 BOOL* ok) { 686 if (ok) 687 *ok = TRUE; 688 return S_OK; 689 } 690 691 STDMETHODIMP TextStore::OnUpdateComposition( 692 ITfCompositionView* composition_view, 693 ITfRange* range) { 694 return S_OK; 695 } 696 697 STDMETHODIMP TextStore::OnEndComposition( 698 ITfCompositionView* composition_view) { 699 return S_OK; 700 } 701 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; 710 } 711 edit_flag_ = true; 712 return S_OK; 713 } 714 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; 720 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; 725 } 726 return SUCCEEDED(display_attribute_info->GetAttributeInfo(attribute)); 727 } 728 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; 742 } 743 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; 751 } 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; 757 } 758 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; 763 } 764 765 while (true) { 766 base::win::ScopedComPtr<ITfRange> range; 767 if (ranges->Next(1, range.Receive(), NULL) != S_OK) 768 break; 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; 774 } 775 if (FAILED(enum_prop_value.QueryFrom(value.AsInput()->punkVal))) 776 return false; 777 778 TF_PROPERTYVAL property_value; 779 bool is_composition = false; 780 bool has_display_attribute = false; 781 TF_DISPLAYATTRIBUTE display_attribute; 782 while (enum_prop_value->Next(1, &property_value, NULL) == S_OK) { 783 if (IsEqualGUID(property_value.guidId, GUID_PROP_COMPOSING)) { 784 is_composition = (property_value.varValue.lVal == TRUE); 785 } else if (IsEqualGUID(property_value.guidId, GUID_PROP_ATTRIBUTE)) { 786 TfGuidAtom guid_atom = 787 static_cast<TfGuidAtom>(property_value.varValue.lVal); 788 if (GetDisplayAttribute(guid_atom, &display_attribute)) 789 has_display_attribute = true; 790 } 791 VariantClear(&property_value.varValue); 792 } 793 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 if (*committed_size < static_cast<size_t>(start_pos + length)) 800 *committed_size = start_pos + length; 801 } else { 802 metro_viewer::UnderlineInfo underline; 803 underline.start_offset = start_pos; 804 underline.end_offset = start_pos + length; 805 underline.thick = !!display_attribute.fBoldLine; 806 undelines->push_back(underline); 807 } 808 } 809 return true; 810 } 811 812 bool TextStore::CancelComposition() { 813 // If there is an on-going document lock, we must not edit the text. 814 if (edit_flag_) 815 return false; 816 817 if (string_buffer_.empty()) 818 return true; 819 820 // Unlike ImmNotifyIME(NI_COMPOSITIONSTR, CPS_CANCEL, 0) in IMM32, TSF does 821 // not have a dedicated method to cancel composition. However, CUAS actually 822 // has a protocol conversion from CPS_CANCEL into TSF operations. According 823 // to the observations on Windows 7, TIPs are expected to cancel composition 824 // when an on-going composition text is replaced with an empty string. So 825 // we use the same operation to cancel composition here to minimize the risk 826 // of potential compatibility issues. 827 828 const uint32 previous_buffer_size = 829 static_cast<uint32>(string_buffer_.size()); 830 string_buffer_.clear(); 831 committed_size_ = 0; 832 selection_start_ = 0; 833 selection_end_ = 0; 834 if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE) 835 text_store_acp_sink_->OnSelectionChange(); 836 if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE) 837 text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); 838 if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) { 839 TS_TEXTCHANGE textChange = {}; 840 textChange.acpStart = 0; 841 textChange.acpOldEnd = previous_buffer_size; 842 textChange.acpNewEnd = 0; 843 text_store_acp_sink_->OnTextChange(0, &textChange); 844 } 845 return true; 846 } 847 848 bool TextStore::ConfirmComposition() { 849 // If there is an on-going document lock, we must not edit the text. 850 if (edit_flag_) 851 return false; 852 853 if (string_buffer_.empty()) 854 return true; 855 856 // See the comment in TextStore::CancelComposition. 857 // This logic is based on the observation about how to emulate 858 // ImmNotifyIME(NI_COMPOSITIONSTR, CPS_COMPLETE, 0) by CUAS. 859 860 const base::string16& composition_text = 861 string_buffer_.substr(committed_size_); 862 if (!composition_text.empty()) 863 delegate_->OnTextCommitted(composition_text); 864 865 const uint32 previous_buffer_size = 866 static_cast<uint32>(string_buffer_.size()); 867 string_buffer_.clear(); 868 committed_size_ = 0; 869 selection_start_ = 0; 870 selection_end_ = 0; 871 if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE) 872 text_store_acp_sink_->OnSelectionChange(); 873 if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE) 874 text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); 875 if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) { 876 TS_TEXTCHANGE textChange = {}; 877 textChange.acpStart = 0; 878 textChange.acpOldEnd = previous_buffer_size; 879 textChange.acpNewEnd = 0; 880 text_store_acp_sink_->OnTextChange(0, &textChange); 881 } 882 return true; 883 } 884 885 void TextStore::SendOnLayoutChange() { 886 if (text_store_acp_sink_ && (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE)) 887 text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); 888 } 889 890 bool TextStore::HasReadLock() const { 891 return (current_lock_type_ & TS_LF_READ) == TS_LF_READ; 892 } 893 894 bool TextStore::HasReadWriteLock() const { 895 return (current_lock_type_ & TS_LF_READWRITE) == TS_LF_READWRITE; 896 } 897 898 } // namespace metro_driver 899