1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #define INITGUID // required for GUID_PROP_INPUTSCOPE 6 #include "ui/base/ime/win/tsf_text_store.h" 7 8 #include <InputScope.h> 9 #include <OleCtl.h> 10 11 #include "base/win/scoped_variant.h" 12 #include "ui/base/ime/text_input_client.h" 13 #include "ui/base/ime/win/tsf_input_scope.h" 14 #include "ui/gfx/rect.h" 15 16 namespace ui { 17 namespace { 18 19 // We support only one view. 20 const TsViewCookie kViewCookie = 1; 21 22 } // namespace 23 24 TSFTextStore::TSFTextStore() 25 : ref_count_(0), 26 text_store_acp_sink_mask_(0), 27 window_handle_(NULL), 28 text_input_client_(NULL), 29 committed_size_(0), 30 edit_flag_(false), 31 current_lock_type_(0) { 32 if (FAILED(category_manager_.CreateInstance(CLSID_TF_CategoryMgr))) { 33 LOG(FATAL) << "Failed to initialize CategoryMgr."; 34 return; 35 } 36 if (FAILED(display_attribute_manager_.CreateInstance( 37 CLSID_TF_DisplayAttributeMgr))) { 38 LOG(FATAL) << "Failed to initialize DisplayAttributeMgr."; 39 return; 40 } 41 } 42 43 TSFTextStore::~TSFTextStore() { 44 } 45 46 ULONG STDMETHODCALLTYPE TSFTextStore::AddRef() { 47 return InterlockedIncrement(&ref_count_); 48 } 49 50 ULONG STDMETHODCALLTYPE TSFTextStore::Release() { 51 const LONG count = InterlockedDecrement(&ref_count_); 52 if (!count) { 53 delete this; 54 return 0; 55 } 56 return static_cast<ULONG>(count); 57 } 58 59 STDMETHODIMP TSFTextStore::QueryInterface(REFIID iid, void** result) { 60 if (iid == IID_IUnknown || iid == IID_ITextStoreACP) { 61 *result = static_cast<ITextStoreACP*>(this); 62 } else if (iid == IID_ITfContextOwnerCompositionSink) { 63 *result = static_cast<ITfContextOwnerCompositionSink*>(this); 64 } else if (iid == IID_ITfTextEditSink) { 65 *result = static_cast<ITfTextEditSink*>(this); 66 } else { 67 *result = NULL; 68 return E_NOINTERFACE; 69 } 70 AddRef(); 71 return S_OK; 72 } 73 74 STDMETHODIMP TSFTextStore::AdviseSink(REFIID iid, 75 IUnknown* unknown, 76 DWORD mask) { 77 if (!IsEqualGUID(iid, IID_ITextStoreACPSink)) 78 return E_INVALIDARG; 79 if (text_store_acp_sink_) { 80 if (text_store_acp_sink_.IsSameObject(unknown)) { 81 text_store_acp_sink_mask_ = mask; 82 return S_OK; 83 } else { 84 return CONNECT_E_ADVISELIMIT; 85 } 86 } 87 if (FAILED(text_store_acp_sink_.QueryFrom(unknown))) 88 return E_UNEXPECTED; 89 text_store_acp_sink_mask_ = mask; 90 91 return S_OK; 92 } 93 94 STDMETHODIMP TSFTextStore::FindNextAttrTransition( 95 LONG acp_start, 96 LONG acp_halt, 97 ULONG num_filter_attributes, 98 const TS_ATTRID* filter_attributes, 99 DWORD flags, 100 LONG* acp_next, 101 BOOL* found, 102 LONG* found_offset) { 103 if (!acp_next || !found || !found_offset) 104 return E_INVALIDARG; 105 // We don't support any attributes. 106 // So we always return "not found". 107 *acp_next = 0; 108 *found = FALSE; 109 *found_offset = 0; 110 return S_OK; 111 } 112 113 STDMETHODIMP TSFTextStore::GetACPFromPoint(TsViewCookie view_cookie, 114 const POINT* point, 115 DWORD flags, 116 LONG* acp) { 117 NOTIMPLEMENTED(); 118 if (view_cookie != kViewCookie) 119 return E_INVALIDARG; 120 return E_NOTIMPL; 121 } 122 123 STDMETHODIMP TSFTextStore::GetActiveView(TsViewCookie* view_cookie) { 124 if (!view_cookie) 125 return E_INVALIDARG; 126 // We support only one view. 127 *view_cookie = kViewCookie; 128 return S_OK; 129 } 130 131 STDMETHODIMP TSFTextStore::GetEmbedded(LONG acp_pos, 132 REFGUID service, 133 REFIID iid, 134 IUnknown** unknown) { 135 // We don't support any embedded objects. 136 NOTIMPLEMENTED(); 137 if (!unknown) 138 return E_INVALIDARG; 139 *unknown = NULL; 140 return E_NOTIMPL; 141 } 142 143 STDMETHODIMP TSFTextStore::GetEndACP(LONG* acp) { 144 if (!acp) 145 return E_INVALIDARG; 146 if (!HasReadLock()) 147 return TS_E_NOLOCK; 148 *acp = string_buffer_.size(); 149 return S_OK; 150 } 151 152 STDMETHODIMP TSFTextStore::GetFormattedText(LONG acp_start, LONG acp_end, 153 IDataObject** data_object) { 154 NOTIMPLEMENTED(); 155 return E_NOTIMPL; 156 } 157 158 STDMETHODIMP TSFTextStore::GetScreenExt(TsViewCookie view_cookie, RECT* rect) { 159 if (view_cookie != kViewCookie) 160 return E_INVALIDARG; 161 if (!rect) 162 return E_INVALIDARG; 163 164 // {0, 0, 0, 0} means that the document rect is not currently displayed. 165 SetRect(rect, 0, 0, 0, 0); 166 167 if (!IsWindow(window_handle_)) 168 return E_FAIL; 169 170 // Currently ui::TextInputClient does not expose the document rect. So use 171 // the Win32 client rectangle instead. 172 // TODO(yukawa): Upgrade TextInputClient so that the client can retrieve the 173 // document rectangle. 174 RECT client_rect = {}; 175 if (!GetClientRect(window_handle_, &client_rect)) 176 return E_FAIL; 177 POINT left_top = {client_rect.left, client_rect.top}; 178 POINT right_bottom = {client_rect.right, client_rect.bottom}; 179 if (!ClientToScreen(window_handle_, &left_top)) 180 return E_FAIL; 181 if (!ClientToScreen(window_handle_, &right_bottom)) 182 return E_FAIL; 183 184 rect->left = left_top.x; 185 rect->top = left_top.y; 186 rect->right = right_bottom.x; 187 rect->bottom = right_bottom.y; 188 return S_OK; 189 } 190 191 STDMETHODIMP TSFTextStore::GetSelection(ULONG selection_index, 192 ULONG selection_buffer_size, 193 TS_SELECTION_ACP* selection_buffer, 194 ULONG* fetched_count) { 195 if (!selection_buffer) 196 return E_INVALIDARG; 197 if (!fetched_count) 198 return E_INVALIDARG; 199 if (!HasReadLock()) 200 return TS_E_NOLOCK; 201 *fetched_count = 0; 202 if ((selection_buffer_size > 0) && 203 ((selection_index == 0) || (selection_index == TS_DEFAULT_SELECTION))) { 204 selection_buffer[0].acpStart = selection_.start(); 205 selection_buffer[0].acpEnd = selection_.end(); 206 selection_buffer[0].style.ase = TS_AE_END; 207 selection_buffer[0].style.fInterimChar = FALSE; 208 *fetched_count = 1; 209 } 210 return S_OK; 211 } 212 213 STDMETHODIMP TSFTextStore::GetStatus(TS_STATUS* status) { 214 if (!status) 215 return E_INVALIDARG; 216 217 status->dwDynamicFlags = 0; 218 // We use transitory contexts and we don't support hidden text. 219 status->dwStaticFlags = TS_SS_TRANSITORY | TS_SS_NOHIDDENTEXT; 220 221 return S_OK; 222 } 223 224 STDMETHODIMP TSFTextStore::GetText(LONG acp_start, 225 LONG acp_end, 226 wchar_t* text_buffer, 227 ULONG text_buffer_size, 228 ULONG* text_buffer_copied, 229 TS_RUNINFO* run_info_buffer, 230 ULONG run_info_buffer_size, 231 ULONG* run_info_buffer_copied, 232 LONG* next_acp) { 233 if (!text_buffer_copied || !run_info_buffer_copied) 234 return E_INVALIDARG; 235 if (!text_buffer && text_buffer_size != 0) 236 return E_INVALIDARG; 237 if (!run_info_buffer && run_info_buffer_size != 0) 238 return E_INVALIDARG; 239 if (!next_acp) 240 return E_INVALIDARG; 241 if (!HasReadLock()) 242 return TF_E_NOLOCK; 243 const LONG string_buffer_size = string_buffer_.size(); 244 if (acp_end == -1) 245 acp_end = string_buffer_size; 246 if (!((0 <= acp_start) && 247 (acp_start <= acp_end) && 248 (acp_end <= string_buffer_size))) { 249 return TF_E_INVALIDPOS; 250 } 251 acp_end = std::min(acp_end, acp_start + static_cast<LONG>(text_buffer_size)); 252 *text_buffer_copied = acp_end - acp_start; 253 254 const string16& result = 255 string_buffer_.substr(acp_start, *text_buffer_copied); 256 for (size_t i = 0; i < result.size(); ++i) { 257 text_buffer[i] = result[i]; 258 } 259 260 if (run_info_buffer_size) { 261 run_info_buffer[0].uCount = *text_buffer_copied; 262 run_info_buffer[0].type = TS_RT_PLAIN; 263 *run_info_buffer_copied = 1; 264 } 265 266 *next_acp = acp_end; 267 return S_OK; 268 } 269 270 STDMETHODIMP TSFTextStore::GetTextExt(TsViewCookie view_cookie, 271 LONG acp_start, 272 LONG acp_end, 273 RECT* rect, 274 BOOL* clipped) { 275 if (!rect || !clipped) 276 return E_INVALIDARG; 277 if (!text_input_client_) 278 return E_UNEXPECTED; 279 if (view_cookie != kViewCookie) 280 return E_INVALIDARG; 281 if (!HasReadLock()) 282 return TS_E_NOLOCK; 283 if (!((static_cast<LONG>(committed_size_) <= acp_start) && 284 (acp_start <= acp_end) && 285 (acp_end <= static_cast<LONG>(string_buffer_.size())))) { 286 return TS_E_INVALIDPOS; 287 } 288 289 // According to a behavior of notepad.exe and wordpad.exe, top left corner of 290 // rect indicates a first character's one, and bottom right corner of rect 291 // indicates a last character's one. 292 // We use RECT instead of gfx::Rect since left position may be bigger than 293 // right position when composition has multiple lines. 294 RECT result; 295 gfx::Rect tmp_rect; 296 const uint32 start_pos = acp_start - committed_size_; 297 const uint32 end_pos = acp_end - committed_size_; 298 299 if (start_pos == end_pos) { 300 // According to MSDN document, if |acp_start| and |acp_end| are equal it is 301 // OK to just return E_INVALIDARG. 302 // http://msdn.microsoft.com/en-us/library/ms538435 303 // But when using Pinin IME of Windows 8, this method is called with the 304 // equal values of |acp_start| and |acp_end|. So we handle this condition. 305 if (start_pos == 0) { 306 if (text_input_client_->GetCompositionCharacterBounds(0, &tmp_rect)) { 307 tmp_rect.set_width(0); 308 result = tmp_rect.ToRECT(); 309 } else if (string_buffer_.size() == committed_size_) { 310 result = text_input_client_->GetCaretBounds().ToRECT(); 311 } else { 312 return TS_E_NOLAYOUT; 313 } 314 } else if (text_input_client_->GetCompositionCharacterBounds(start_pos - 1, 315 &tmp_rect)) { 316 result.left = tmp_rect.right(); 317 result.right = tmp_rect.right(); 318 result.top = tmp_rect.y(); 319 result.bottom = tmp_rect.bottom(); 320 } else { 321 return TS_E_NOLAYOUT; 322 } 323 } else { 324 if (text_input_client_->GetCompositionCharacterBounds(start_pos, 325 &tmp_rect)) { 326 result.left = tmp_rect.x(); 327 result.top = tmp_rect.y(); 328 result.right = tmp_rect.right(); 329 result.bottom = tmp_rect.bottom(); 330 if (text_input_client_->GetCompositionCharacterBounds(end_pos - 1, 331 &tmp_rect)) { 332 result.right = tmp_rect.right(); 333 result.bottom = tmp_rect.bottom(); 334 } else { 335 // We may not be able to get the last character bounds, so we use the 336 // first character bounds instead of returning TS_E_NOLAYOUT. 337 } 338 } else { 339 // Hack for PPAPI flash. PPAPI flash does not support GetCaretBounds, so 340 // it's better to return previous caret rectangle instead. 341 // TODO(nona, kinaba): Remove this hack. 342 if (start_pos == 0) { 343 result = text_input_client_->GetCaretBounds().ToRECT(); 344 } else { 345 return TS_E_NOLAYOUT; 346 } 347 } 348 } 349 350 *rect = result; 351 *clipped = FALSE; 352 return S_OK; 353 } 354 355 STDMETHODIMP TSFTextStore::GetWnd(TsViewCookie view_cookie, 356 HWND* window_handle) { 357 if (!window_handle) 358 return E_INVALIDARG; 359 if (view_cookie != kViewCookie) 360 return E_INVALIDARG; 361 *window_handle = window_handle_; 362 return S_OK; 363 } 364 365 STDMETHODIMP TSFTextStore::InsertEmbedded(DWORD flags, 366 LONG acp_start, 367 LONG acp_end, 368 IDataObject* data_object, 369 TS_TEXTCHANGE* change) { 370 // We don't support any embedded objects. 371 NOTIMPLEMENTED(); 372 return E_NOTIMPL; 373 } 374 375 STDMETHODIMP TSFTextStore::InsertEmbeddedAtSelection(DWORD flags, 376 IDataObject* data_object, 377 LONG* acp_start, 378 LONG* acp_end, 379 TS_TEXTCHANGE* change) { 380 // We don't support any embedded objects. 381 NOTIMPLEMENTED(); 382 return E_NOTIMPL; 383 } 384 385 STDMETHODIMP TSFTextStore::InsertTextAtSelection(DWORD flags, 386 const wchar_t* text_buffer, 387 ULONG text_buffer_size, 388 LONG* acp_start, 389 LONG* acp_end, 390 TS_TEXTCHANGE* text_change) { 391 const LONG start_pos = selection_.start(); 392 const LONG end_pos = selection_.end(); 393 const LONG new_end_pos = start_pos + text_buffer_size; 394 395 if (flags & TS_IAS_QUERYONLY) { 396 if (!HasReadLock()) 397 return TS_E_NOLOCK; 398 if (acp_start) 399 *acp_start = start_pos; 400 if (acp_end) { 401 *acp_end = end_pos; 402 } 403 return S_OK; 404 } 405 406 if (!HasReadWriteLock()) 407 return TS_E_NOLOCK; 408 if (!text_buffer) 409 return E_INVALIDARG; 410 411 DCHECK_LE(start_pos, end_pos); 412 string_buffer_ = string_buffer_.substr(0, start_pos) + 413 string16(text_buffer, text_buffer + text_buffer_size) + 414 string_buffer_.substr(end_pos); 415 if (acp_start) 416 *acp_start = start_pos; 417 if (acp_end) 418 *acp_end = new_end_pos; 419 if (text_change) { 420 text_change->acpStart = start_pos; 421 text_change->acpOldEnd = end_pos; 422 text_change->acpNewEnd = new_end_pos; 423 } 424 selection_.set_start(start_pos); 425 selection_.set_end(new_end_pos); 426 return S_OK; 427 } 428 429 STDMETHODIMP TSFTextStore::QueryInsert( 430 LONG acp_test_start, 431 LONG acp_test_end, 432 ULONG text_size, 433 LONG* acp_result_start, 434 LONG* acp_result_end) { 435 if (!acp_result_start || !acp_result_end) 436 return E_INVALIDARG; 437 if (!((static_cast<LONG>(committed_size_) <= acp_test_start) && 438 (acp_test_start <= acp_test_end) && 439 (acp_test_end <= static_cast<LONG>(string_buffer_.size())))) { 440 return E_INVALIDARG; 441 } 442 *acp_result_start = acp_test_start; 443 *acp_result_end = acp_test_start + text_size; 444 return S_OK; 445 } 446 447 STDMETHODIMP TSFTextStore::QueryInsertEmbedded(const GUID* service, 448 const FORMATETC* format, 449 BOOL* insertable) { 450 if (!format) 451 return E_INVALIDARG; 452 // We don't support any embedded objects. 453 if (insertable) 454 *insertable = FALSE; 455 return S_OK; 456 } 457 458 STDMETHODIMP TSFTextStore::RequestAttrsAtPosition( 459 LONG acp_pos, 460 ULONG attribute_buffer_size, 461 const TS_ATTRID* attribute_buffer, 462 DWORD flags) { 463 // We don't support any document attributes. 464 // This method just returns S_OK, and the subsequently called 465 // RetrieveRequestedAttrs() returns 0 as the number of supported attributes. 466 return S_OK; 467 } 468 469 STDMETHODIMP TSFTextStore::RequestAttrsTransitioningAtPosition( 470 LONG acp_pos, 471 ULONG attribute_buffer_size, 472 const TS_ATTRID* attribute_buffer, 473 DWORD flags) { 474 // We don't support any document attributes. 475 // This method just returns S_OK, and the subsequently called 476 // RetrieveRequestedAttrs() returns 0 as the number of supported attributes. 477 return S_OK; 478 } 479 480 STDMETHODIMP TSFTextStore::RequestLock(DWORD lock_flags, HRESULT* result) { 481 if (!text_store_acp_sink_.get()) 482 return E_FAIL; 483 if (!result) 484 return E_INVALIDARG; 485 486 if (current_lock_type_ != 0) { 487 if (lock_flags & TS_LF_SYNC) { 488 // Can't lock synchronously. 489 *result = TS_E_SYNCHRONOUS; 490 return S_OK; 491 } 492 // Queue the lock request. 493 lock_queue_.push_back(lock_flags & TS_LF_READWRITE); 494 *result = TS_S_ASYNC; 495 return S_OK; 496 } 497 498 // Lock 499 current_lock_type_ = (lock_flags & TS_LF_READWRITE); 500 501 edit_flag_ = false; 502 const size_t last_committed_size = committed_size_; 503 504 // Grant the lock. 505 *result = text_store_acp_sink_->OnLockGranted(current_lock_type_); 506 507 // Unlock 508 current_lock_type_ = 0; 509 510 // Handles the pending lock requests. 511 while (!lock_queue_.empty()) { 512 current_lock_type_ = lock_queue_.front(); 513 lock_queue_.pop_front(); 514 text_store_acp_sink_->OnLockGranted(current_lock_type_); 515 current_lock_type_ = 0; 516 } 517 518 if (!edit_flag_) { 519 return S_OK; 520 } 521 522 // If the text store is edited in OnLockGranted(), we may need to call 523 // TextInputClient::InsertText() or TextInputClient::SetCompositionText(). 524 const size_t new_committed_size = committed_size_; 525 const string16& new_committed_string = 526 string_buffer_.substr(last_committed_size, 527 new_committed_size - last_committed_size); 528 const string16& composition_string = 529 string_buffer_.substr(new_committed_size); 530 531 // If there is new committed string, calls TextInputClient::InsertText(). 532 if ((!new_committed_string.empty()) && text_input_client_) { 533 text_input_client_->InsertText(new_committed_string); 534 } 535 536 // Calls TextInputClient::SetCompositionText(). 537 CompositionText composition_text; 538 composition_text.text = composition_string; 539 composition_text.underlines = composition_undelines_; 540 // Adjusts the offset. 541 for (size_t i = 0; i < composition_text.underlines.size(); ++i) { 542 composition_text.underlines[i].start_offset -= new_committed_size; 543 composition_text.underlines[i].end_offset -= new_committed_size; 544 } 545 if (selection_.start() < new_committed_size) { 546 composition_text.selection.set_start(0); 547 } else { 548 composition_text.selection.set_start( 549 selection_.start() - new_committed_size); 550 } 551 if (selection_.end() < new_committed_size) { 552 composition_text.selection.set_end(0); 553 } else { 554 composition_text.selection.set_end(selection_.end() - new_committed_size); 555 } 556 if (text_input_client_) 557 text_input_client_->SetCompositionText(composition_text); 558 559 // If there is no composition string, clear the text store status. 560 // And call OnSelectionChange(), OnLayoutChange(), and OnTextChange(). 561 if ((composition_string.empty()) && (new_committed_size != 0)) { 562 string_buffer_.clear(); 563 committed_size_ = 0; 564 selection_.set_start(0); 565 selection_.set_end(0); 566 if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE) 567 text_store_acp_sink_->OnSelectionChange(); 568 if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE) 569 text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); 570 if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) { 571 TS_TEXTCHANGE textChange; 572 textChange.acpStart = 0; 573 textChange.acpOldEnd = new_committed_size; 574 textChange.acpNewEnd = 0; 575 text_store_acp_sink_->OnTextChange(0, &textChange); 576 } 577 } 578 579 return S_OK; 580 } 581 582 STDMETHODIMP TSFTextStore::RequestSupportedAttrs( 583 DWORD /* flags */, // Seems that we should ignore this. 584 ULONG attribute_buffer_size, 585 const TS_ATTRID* attribute_buffer) { 586 if (!attribute_buffer) 587 return E_INVALIDARG; 588 // We support only input scope attribute. 589 for (size_t i = 0; i < attribute_buffer_size; ++i) { 590 if (IsEqualGUID(GUID_PROP_INPUTSCOPE, attribute_buffer[i])) 591 return S_OK; 592 } 593 return E_FAIL; 594 } 595 596 STDMETHODIMP TSFTextStore::RetrieveRequestedAttrs( 597 ULONG attribute_buffer_size, 598 TS_ATTRVAL* attribute_buffer, 599 ULONG* attribute_buffer_copied) { 600 if (!attribute_buffer_copied) 601 return E_INVALIDARG; 602 if (!attribute_buffer) 603 return E_INVALIDARG; 604 // We support only input scope attribute. 605 *attribute_buffer_copied = 0; 606 if (attribute_buffer_size == 0) 607 return S_OK; 608 609 attribute_buffer[0].dwOverlapId = 0; 610 attribute_buffer[0].idAttr = GUID_PROP_INPUTSCOPE; 611 attribute_buffer[0].varValue.vt = VT_UNKNOWN; 612 attribute_buffer[0].varValue.punkVal = tsf_inputscope::CreateInputScope( 613 text_input_client_->GetTextInputType()); 614 attribute_buffer[0].varValue.punkVal->AddRef(); 615 *attribute_buffer_copied = 1; 616 return S_OK; 617 } 618 619 STDMETHODIMP TSFTextStore::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_.set_start(start_pos); 633 selection_.set_end(end_pos); 634 } 635 return S_OK; 636 } 637 638 STDMETHODIMP TSFTextStore::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 TSFTextStore::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 TSFTextStore::OnStartComposition( 684 ITfCompositionView* composition_view, 685 BOOL* ok) { 686 if (ok) 687 *ok = TRUE; 688 return S_OK; 689 } 690 691 STDMETHODIMP TSFTextStore::OnUpdateComposition( 692 ITfCompositionView* composition_view, 693 ITfRange* range) { 694 return S_OK; 695 } 696 697 STDMETHODIMP TSFTextStore::OnEndComposition( 698 ITfCompositionView* composition_view) { 699 return S_OK; 700 } 701 702 STDMETHODIMP TSFTextStore::OnEndEdit(ITfContext* context, 703 TfEditCookie read_only_edit_cookie, 704 ITfEditRecord* edit_record) { 705 if (!context || !edit_record) 706 return E_INVALIDARG; 707 708 size_t committed_size; 709 CompositionUnderlines undelines; 710 if (!GetCompositionStatus(context, read_only_edit_cookie, &committed_size, 711 &undelines)) { 712 return S_OK; 713 } 714 composition_undelines_ = undelines; 715 committed_size_ = committed_size; 716 edit_flag_ = true; 717 return S_OK; 718 } 719 720 bool TSFTextStore::GetDisplayAttribute(TfGuidAtom guid_atom, 721 TF_DISPLAYATTRIBUTE* attribute) { 722 GUID guid; 723 if (FAILED(category_manager_->GetGUID(guid_atom, &guid))) 724 return false; 725 726 base::win::ScopedComPtr<ITfDisplayAttributeInfo> display_attribute_info; 727 if (FAILED(display_attribute_manager_->GetDisplayAttributeInfo( 728 guid, display_attribute_info.Receive(), NULL))) { 729 return false; 730 } 731 return SUCCEEDED(display_attribute_info->GetAttributeInfo(attribute)); 732 } 733 734 bool TSFTextStore::GetCompositionStatus( 735 ITfContext* context, 736 const TfEditCookie read_only_edit_cookie, 737 size_t* committed_size, 738 CompositionUnderlines* undelines) { 739 DCHECK(context); 740 DCHECK(committed_size); 741 DCHECK(undelines); 742 const GUID* rgGuids[2] = {&GUID_PROP_COMPOSING, &GUID_PROP_ATTRIBUTE}; 743 base::win::ScopedComPtr<ITfReadOnlyProperty> track_property; 744 if (FAILED(context->TrackProperties(rgGuids, 2, NULL, 0, 745 track_property.Receive()))) { 746 return false; 747 } 748 749 *committed_size = 0; 750 undelines->clear(); 751 base::win::ScopedComPtr<ITfRange> start_to_end_range; 752 base::win::ScopedComPtr<ITfRange> end_range; 753 if (FAILED(context->GetStart(read_only_edit_cookie, 754 start_to_end_range.Receive()))) { 755 return false; 756 } 757 if (FAILED(context->GetEnd(read_only_edit_cookie, end_range.Receive()))) 758 return false; 759 if (FAILED(start_to_end_range->ShiftEndToRange(read_only_edit_cookie, 760 end_range, TF_ANCHOR_END))) { 761 return false; 762 } 763 764 base::win::ScopedComPtr<IEnumTfRanges> ranges; 765 if (FAILED(track_property->EnumRanges(read_only_edit_cookie, ranges.Receive(), 766 start_to_end_range))) { 767 return false; 768 } 769 770 while (true) { 771 base::win::ScopedComPtr<ITfRange> range; 772 if (ranges->Next(1, range.Receive(), NULL) != S_OK) 773 break; 774 base::win::ScopedVariant value; 775 base::win::ScopedComPtr<IEnumTfPropertyValue> enum_prop_value; 776 if (FAILED(track_property->GetValue(read_only_edit_cookie, range, 777 value.Receive()))) { 778 return false; 779 } 780 if (FAILED(enum_prop_value.QueryFrom(value.AsInput()->punkVal))) 781 return false; 782 783 TF_PROPERTYVAL property_value; 784 bool is_composition = false; 785 bool has_display_attribute = false; 786 TF_DISPLAYATTRIBUTE display_attribute; 787 while (enum_prop_value->Next(1, &property_value, NULL) == S_OK) { 788 if (IsEqualGUID(property_value.guidId, GUID_PROP_COMPOSING)) { 789 is_composition = (property_value.varValue.lVal == TRUE); 790 } else if (IsEqualGUID(property_value.guidId, GUID_PROP_ATTRIBUTE)) { 791 TfGuidAtom guid_atom = 792 static_cast<TfGuidAtom>(property_value.varValue.lVal); 793 if (GetDisplayAttribute(guid_atom, &display_attribute)) 794 has_display_attribute = true; 795 } 796 VariantClear(&property_value.varValue); 797 } 798 799 base::win::ScopedComPtr<ITfRangeACP> range_acp; 800 range_acp.QueryFrom(range); 801 LONG start_pos, length; 802 range_acp->GetExtent(&start_pos, &length); 803 if (!is_composition) { 804 if (*committed_size < static_cast<size_t>(start_pos + length)) 805 *committed_size = start_pos + length; 806 } else { 807 CompositionUnderline underline; 808 underline.start_offset = start_pos; 809 underline.end_offset = start_pos + length; 810 underline.color = SK_ColorBLACK; 811 if (has_display_attribute) 812 underline.thick = !!display_attribute.fBoldLine; 813 undelines->push_back(underline); 814 } 815 } 816 return true; 817 } 818 819 void TSFTextStore::SetFocusedTextInputClient( 820 HWND focused_window, 821 TextInputClient* text_input_client) { 822 window_handle_ = focused_window; 823 text_input_client_ = text_input_client; 824 } 825 826 void TSFTextStore::RemoveFocusedTextInputClient( 827 TextInputClient* text_input_client) { 828 if (text_input_client_ == text_input_client) { 829 window_handle_ = NULL; 830 text_input_client_ = NULL; 831 } 832 } 833 834 bool TSFTextStore::CancelComposition() { 835 // If there is an on-going document lock, we must not edit the text. 836 if (edit_flag_) 837 return false; 838 839 if (string_buffer_.empty()) 840 return true; 841 842 // Unlike ImmNotifyIME(NI_COMPOSITIONSTR, CPS_CANCEL, 0) in IMM32, TSF does 843 // not have a dedicated method to cancel composition. However, CUAS actually 844 // has a protocol conversion from CPS_CANCEL into TSF operations. According 845 // to the observations on Windows 7, TIPs are expected to cancel composition 846 // when an on-going composition text is replaced with an empty string. So 847 // we use the same operation to cancel composition here to minimize the risk 848 // of potential compatibility issues. 849 850 const size_t previous_buffer_size = string_buffer_.size(); 851 string_buffer_.clear(); 852 committed_size_ = 0; 853 selection_.set_start(0); 854 selection_.set_end(0); 855 if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE) 856 text_store_acp_sink_->OnSelectionChange(); 857 if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE) 858 text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); 859 if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) { 860 TS_TEXTCHANGE textChange = {}; 861 textChange.acpStart = 0; 862 textChange.acpOldEnd = previous_buffer_size; 863 textChange.acpNewEnd = 0; 864 text_store_acp_sink_->OnTextChange(0, &textChange); 865 } 866 return true; 867 } 868 869 bool TSFTextStore::ConfirmComposition() { 870 // If there is an on-going document lock, we must not edit the text. 871 if (edit_flag_) 872 return false; 873 874 if (string_buffer_.empty()) 875 return true; 876 877 // See the comment in TSFTextStore::CancelComposition. 878 // This logic is based on the observation about how to emulate 879 // ImmNotifyIME(NI_COMPOSITIONSTR, CPS_COMPLETE, 0) by CUAS. 880 881 const string16& composition_text = string_buffer_.substr(committed_size_); 882 if (!composition_text.empty()) 883 text_input_client_->InsertText(composition_text); 884 885 const size_t previous_buffer_size = string_buffer_.size(); 886 string_buffer_.clear(); 887 committed_size_ = 0; 888 selection_.set_start(0); 889 selection_.set_end(0); 890 if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE) 891 text_store_acp_sink_->OnSelectionChange(); 892 if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE) 893 text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); 894 if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) { 895 TS_TEXTCHANGE textChange = {}; 896 textChange.acpStart = 0; 897 textChange.acpOldEnd = previous_buffer_size; 898 textChange.acpNewEnd = 0; 899 text_store_acp_sink_->OnTextChange(0, &textChange); 900 } 901 return true; 902 } 903 904 void TSFTextStore::SendOnLayoutChange() { 905 if (text_store_acp_sink_ && (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE)) 906 text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); 907 } 908 909 bool TSFTextStore::HasReadLock() const { 910 return (current_lock_type_ & TS_LF_READ) == TS_LF_READ; 911 } 912 913 bool TSFTextStore::HasReadWriteLock() const { 914 return (current_lock_type_ & TS_LF_READWRITE) == TS_LF_READWRITE; 915 } 916 917 } // namespace ui 918