| OLD | NEW |
| (Empty) |
| 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 base::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 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; | |
| 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 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); | |
| 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 base::string16& composition_text = | |
| 888 string_buffer_.substr(committed_size_); | |
| 889 if (!composition_text.empty()) | |
| 890 text_input_client_->InsertText(composition_text); | |
| 891 | |
| 892 const size_t previous_buffer_size = string_buffer_.size(); | |
| 893 string_buffer_.clear(); | |
| 894 committed_size_ = 0; | |
| 895 selection_.set_start(0); | |
| 896 selection_.set_end(0); | |
| 897 if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE) | |
| 898 text_store_acp_sink_->OnSelectionChange(); | |
| 899 if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE) | |
| 900 text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); | |
| 901 if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) { | |
| 902 TS_TEXTCHANGE textChange = {}; | |
| 903 textChange.acpStart = 0; | |
| 904 textChange.acpOldEnd = previous_buffer_size; | |
| 905 textChange.acpNewEnd = 0; | |
| 906 text_store_acp_sink_->OnTextChange(0, &textChange); | |
| 907 } | |
| 908 return true; | |
| 909 } | |
| 910 | |
| 911 void TSFTextStore::SendOnLayoutChange() { | |
| 912 if (text_store_acp_sink_ && (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE)) | |
| 913 text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); | |
| 914 } | |
| 915 | |
| 916 bool TSFTextStore::HasReadLock() const { | |
| 917 return (current_lock_type_ & TS_LF_READ) == TS_LF_READ; | |
| 918 } | |
| 919 | |
| 920 bool TSFTextStore::HasReadWriteLock() const { | |
| 921 return (current_lock_type_ & TS_LF_READWRITE) == TS_LF_READWRITE; | |
| 922 } | |
| 923 | |
| 924 } // namespace ui | |
| OLD | NEW |