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