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 |