Chromium Code Reviews| Index: ui/base/win/tsf_text_store.cc |
| diff --git a/ui/base/win/tsf_text_store.cc b/ui/base/win/tsf_text_store.cc |
| index 2c49d6b0949154ffaec3bf3ba47e5e1efff59d1c..7b74aefce7b2639ce942df33c16937f80296eba8 100644 |
| --- a/ui/base/win/tsf_text_store.cc |
| +++ b/ui/base/win/tsf_text_store.cc |
| @@ -5,13 +5,28 @@ |
| #include "ui/base/win/tsf_text_store.h" |
| #include <OleCtl.h> |
| -#include "base/logging.h" |
| +#include "base/win/scoped_variant.h" |
| +#include "ui/base/ime/text_input_client.h" |
| +#include "ui/gfx/rect.h" |
| namespace ui { |
| TsfTextStore::TsfTextStore() |
| : ref_count_(0), |
| - text_store_acp_sink_mask_(0) { |
| + text_store_acp_sink_mask_(0), |
| + hwnd_(0), |
| + text_input_client_(NULL), |
| + current_lock_type_(0) { |
| + if (FAILED(category_manager_.CreateInstance( |
| + CLSID_TF_CategoryMgr, NULL, CLSCTX_INPROC_SERVER))) { |
| + LOG(FATAL) << "Failed to initialize CategoryMgr."; |
| + return; |
| + } |
| + if (FAILED(display_attribute_manager_.CreateInstance( |
| + CLSID_TF_DisplayAttributeMgr, NULL, CLSCTX_INPROC_SERVER))) { |
| + LOG(FATAL) << "Failed to initialize DisplayAttributeMgr."; |
| + return; |
| + } |
| } |
| TsfTextStore::~TsfTextStore() { |
| @@ -73,8 +88,13 @@ STDMETHODIMP TsfTextStore::FindNextAttrTransition( |
| LONG* acp_next, |
| BOOL* found, |
| LONG* found_offset) { |
| - NOTIMPLEMENTED(); |
| - return E_NOTIMPL; |
| + if (!acp_next || !found || !found_offset) |
| + return E_INVALIDARG; |
| + // We does not support FindNextAttrTransition. |
| + *acp_next = 0; |
| + *found = FALSE; |
| + *found_offset = 0; |
| + return S_OK; |
| } |
| STDMETHODIMP TsfTextStore::GetACPFromPoint( |
| @@ -82,14 +102,16 @@ STDMETHODIMP TsfTextStore::GetACPFromPoint( |
| const POINT* point, |
| DWORD flags, |
| LONG* acp) { |
| + // We does not support GetACPFromPoint. |
| NOTIMPLEMENTED(); |
| return E_NOTIMPL; |
| } |
| STDMETHODIMP TsfTextStore::GetActiveView(TsViewCookie* view_cookie) { |
| - NOTIMPLEMENTED(); |
| if (!view_cookie) |
| return E_INVALIDARG; |
| + // We support only one view. |
| + *view_cookie = 0; |
| return S_OK; |
| } |
| @@ -97,6 +119,7 @@ STDMETHODIMP TsfTextStore::GetEmbedded(LONG acp_pos, |
| REFGUID service, |
| REFIID iid, |
| IUnknown** unknown) { |
| + // We does not support any embedded objects. |
| NOTIMPLEMENTED(); |
| if (!unknown) |
| return E_INVALIDARG; |
| @@ -105,14 +128,17 @@ STDMETHODIMP TsfTextStore::GetEmbedded(LONG acp_pos, |
| } |
| STDMETHODIMP TsfTextStore::GetEndACP(LONG* acp) { |
| - NOTIMPLEMENTED(); |
| if (!acp) |
| return E_INVALIDARG; |
| - return E_NOTIMPL; |
| + if (!ReaderLocked()) |
| + return TS_E_NOLOCK; |
| + *acp = status_.string_buffer_.length(); |
| + return S_OK; |
| } |
| STDMETHODIMP TsfTextStore::GetFormattedText(LONG acp_start, LONG acp_end, |
| IDataObject** data_object) { |
| + // We does not support GetFormattedText. |
| NOTIMPLEMENTED(); |
| return E_NOTIMPL; |
| } |
| @@ -129,19 +155,32 @@ STDMETHODIMP TsfTextStore::GetSelection(ULONG selection_index, |
| ULONG selection_buffer_size, |
| TS_SELECTION_ACP* selection_buffer, |
| ULONG* fetched_count) { |
| - NOTIMPLEMENTED(); |
| if (!selection_buffer) |
| return E_INVALIDARG; |
| if (!fetched_count) |
| return E_INVALIDARG; |
| - return E_NOTIMPL; |
| + if (!ReaderLocked()) |
| + return TS_E_NOLOCK; |
| + *fetched_count = 0; |
| + if ((selection_buffer_size > 0) && |
| + ((selection_index == 0) || (selection_index == TS_DEFAULT_SELECTION))) { |
| + selection_buffer[0].acpStart = status_.selection_.start(); |
| + selection_buffer[0].acpEnd = status_.selection_.end(); |
| + selection_buffer[0].style.ase = TS_AE_END; |
| + selection_buffer[0].style.fInterimChar = FALSE; |
| + *fetched_count = 1; |
| + } |
| + return S_OK; |
| } |
| STDMETHODIMP TsfTextStore::GetStatus(TS_STATUS* status) { |
| - NOTIMPLEMENTED(); |
| if (!status) |
| return E_INVALIDARG; |
| - return E_NOTIMPL; |
| + |
| + status->dwDynamicFlags = 0; |
| + status->dwStaticFlags = TS_SS_NOHIDDENTEXT; |
| + |
| + return S_OK; |
| } |
| STDMETHODIMP TsfTextStore::GetText(LONG acp_start, |
| @@ -153,14 +192,36 @@ STDMETHODIMP TsfTextStore::GetText(LONG acp_start, |
| ULONG run_info_buffer_size, |
| ULONG* run_info_buffer_copied, |
| LONG* next_acp) { |
| - NOTIMPLEMENTED(); |
| if (!text_buffer_copied || !run_info_buffer_copied) |
| return E_INVALIDARG; |
| if (!text_buffer && text_buffer_size != 0) |
| return E_INVALIDARG; |
| if (!run_info_buffer && run_info_buffer_size != 0) |
| return E_INVALIDARG; |
| - return E_NOTIMPL; |
| + if (!ReaderLocked()) |
| + return TF_E_NOLOCK; |
| + |
| + if (acp_end == -1) { |
| + acp_end = status_.string_buffer_.length(); |
| + } |
| + |
| + acp_end = std::min(acp_end, acp_start + (int)text_buffer_size); |
| + *text_buffer_copied = acp_end - acp_start; |
| + |
| + const string16 result = |
| + status_.string_buffer_.substr(acp_start, *text_buffer_copied); |
| + for (size_t i = 0; i < result.size(); ++i) { |
| + text_buffer[i] = result[i]; |
| + } |
| + |
| + if (run_info_buffer_size) { |
| + run_info_buffer[0].uCount = *text_buffer_copied; |
| + run_info_buffer[0].type = TS_RT_PLAIN; |
| + *run_info_buffer_copied = 1; |
| + } |
| + |
| + *next_acp = acp_end; |
| + return S_OK; |
| } |
| STDMETHODIMP TsfTextStore::GetTextExt(TsViewCookie view_cookie, |
| @@ -168,14 +229,62 @@ STDMETHODIMP TsfTextStore::GetTextExt(TsViewCookie view_cookie, |
| LONG acp_end, |
|
Seigo Nonaka
2012/08/21 16:14:06
I'm confusing the |acp_end| spec.
Please let me kn
horo
2012/08/22 02:22:33
Sorry my code was wrong.
I think |acp_end| should
|
| RECT* rect, |
| BOOL* clipped) { |
| - NOTIMPLEMENTED(); |
| - return E_NOTIMPL; |
| + if (!rect || !clipped) |
| + return E_INVALIDARG; |
| + if (!text_input_client_) |
| + return E_UNEXPECTED; |
| + if (!ReaderLocked()) |
| + return TS_E_NOLOCK; |
| + if (acp_start < static_cast<LONG>(status_.commited_size_)) |
| + return TS_E_INVALIDPOS; |
| + if (acp_end < acp_start) |
| + return E_INVALIDARG; |
| + |
| + gfx::Rect result; |
| + gfx::Rect tmp_rect; |
| + uint32 start_pos = acp_start - status_.commited_size_; |
|
Seigo Nonaka
2012/08/21 16:14:06
nit: const?
horo
2012/08/22 02:22:33
Done.
|
| + uint32 end_pos = acp_end - status_.commited_size_; |
|
Seigo Nonaka
2012/08/21 16:14:06
nit: const?
horo
2012/08/22 02:22:33
Done.
|
| + |
| + if (start_pos == end_pos) { |
| + if (text_input_client_->GetCompositionCharacterBounds(start_pos + 1, |
|
Seigo Nonaka
2012/08/21 16:14:06
/start_pos + 1/start_pos/ ?
GetCompositionCharacte
horo
2012/08/22 02:22:33
ah yes, it is my mistake.
Done.
|
| + &tmp_rect)) { |
| + result = tmp_rect; |
| + result.set_width(0); |
| + } else if (text_input_client_->GetCompositionCharacterBounds(start_pos, |
|
Seigo Nonaka
2012/08/21 16:14:06
/start_pos/start_pos -1/ ?
horo
2012/08/22 02:22:33
Done.
|
| + &tmp_rect)) { |
| + result.set_x(tmp_rect.right()); |
| + result.set_y(tmp_rect.y()); |
| + result.set_width(0); |
| + result.set_height(tmp_rect.height()); |
| + } else if (start_pos == 0) { |
| + result = text_input_client_->GetCaretBounds(); |
|
Seigo Nonaka
2012/08/21 16:14:06
Sorry this is my mistake.
Caret does not always ex
horo
2012/08/22 02:22:33
Done.
And added
} else {
return TS_E_NOL
|
| + } else { |
| + return TS_E_NOLAYOUT; |
| + } |
| + } else { |
| + if (!text_input_client_->GetCompositionCharacterBounds(start_pos, |
| + &result)) { |
| + return TS_E_NOLAYOUT; |
| + } |
| + for (uint32 i = start_pos + 1; i < end_pos - 1; ++i) { |
|
Seigo Nonaka
2012/08/21 16:14:06
I think |i| should go |end_pos| -1.
horo
2012/08/22 02:22:33
Done.
|
| + if (!text_input_client_->GetCompositionCharacterBounds(start_pos, |
|
Seigo Nonaka
2012/08/21 16:14:06
/start_pos/i/ ?
horo
2012/08/22 02:22:33
Done.
|
| + &tmp_rect)) { |
| + break; |
| + } |
| + result.Union(tmp_rect); |
|
Seigo Nonaka
2012/08/21 16:14:06
Union function returns new rectangle.
result = res
Seigo Nonaka
2012/08/21 16:14:06
BTW, do we concern multiple line composition strin
horo
2012/08/22 02:22:33
Done.
horo
2012/08/22 02:22:33
There is no problem in the implementation of GetTe
|
| + } |
| + } |
| + *rect = result.ToRECT(); |
| + *clipped = FALSE; |
| + return S_OK; |
| } |
| STDMETHODIMP TsfTextStore::GetWnd(TsViewCookie view_cookie, |
| HWND* window_handle) { |
| - NOTIMPLEMENTED(); |
| - return E_NOTIMPL; |
| + if (!window_handle) |
| + return E_INVALIDARG; |
| + *window_handle = hwnd_; |
| + return S_OK; |
| } |
| STDMETHODIMP TsfTextStore::InsertEmbedded(DWORD flags, |
| @@ -183,6 +292,7 @@ STDMETHODIMP TsfTextStore::InsertEmbedded(DWORD flags, |
| LONG acp_end, |
| IDataObject* data_object, |
| TS_TEXTCHANGE* change) { |
| + // We does not support any embedded objects. |
| NOTIMPLEMENTED(); |
| return E_NOTIMPL; |
| } |
| @@ -192,6 +302,7 @@ STDMETHODIMP TsfTextStore::InsertEmbeddedAtSelection(DWORD flags, |
| LONG* acp_start, |
| LONG* acp_end, |
| TS_TEXTCHANGE* change) { |
| + // We does not support any embedded objects. |
| NOTIMPLEMENTED(); |
| return E_NOTIMPL; |
| } |
| @@ -202,8 +313,38 @@ STDMETHODIMP TsfTextStore::InsertTextAtSelection(DWORD flags, |
| LONG* acp_start, |
| LONG* acp_end, |
| TS_TEXTCHANGE* text_change) { |
| - NOTIMPLEMENTED(); |
| - return E_NOTIMPL; |
| + if (!WriterLocked()) |
| + return TS_E_NOLOCK; |
| + |
| + LONG start_pos = status_.selection_.start(); |
|
Seigo Nonaka
2012/08/21 16:14:06
nit: const?
horo
2012/08/22 02:22:33
Done.
|
| + LONG end_pos = status_.selection_.end(); |
|
Seigo Nonaka
2012/08/21 16:14:06
nit: const?
horo
2012/08/22 02:22:33
Done.
|
| + |
| + if (flags & TS_IAS_QUERYONLY) { |
| + *acp_start = start_pos; |
| + *acp_end = end_pos + text_buffer_size; |
| + return S_OK; |
| + } |
| + |
| + if (!text_buffer) |
| + return E_INVALIDARG; |
| + status_.RemoveText(start_pos, end_pos - start_pos); |
| + status_.InsertText(start_pos, |
| + string16(text_buffer, |
| + text_buffer + text_buffer_size)); |
| + if (acp_start) { |
|
Seigo Nonaka
2012/08/21 16:14:06
nit: remove braces
horo
2012/08/22 02:22:33
Done.
|
| + *acp_start = start_pos; |
| + } |
| + if (acp_end) { |
|
Seigo Nonaka
2012/08/21 16:14:06
nit: remove braces
horo
2012/08/22 02:22:33
Done.
|
| + *acp_end = start_pos + text_buffer_size; |
| + } |
| + if (text_change) { |
| + text_change->acpStart = start_pos; |
| + text_change->acpOldEnd = end_pos; |
| + text_change->acpNewEnd = start_pos + text_buffer_size; |
| + } |
| + status_.MoveSelection(start_pos, start_pos + text_buffer_size); |
| + text_store_acp_sink_->OnSelectionChange(); |
| + return S_OK; |
| } |
| STDMETHODIMP TsfTextStore::QueryInsert( |
| @@ -212,13 +353,17 @@ STDMETHODIMP TsfTextStore::QueryInsert( |
| ULONG text_size, |
| LONG* acp_result_start, |
| LONG* acp_result_end) { |
| - NOTIMPLEMENTED(); |
| - return E_NOTIMPL; |
| + if (acp_result_start) |
| + *acp_result_start = acp_test_start; |
| + if (acp_result_end) |
| + *acp_result_end = acp_test_start + text_size; |
| + return S_OK; |
| } |
| STDMETHODIMP TsfTextStore::QueryInsertEmbedded(const GUID* service, |
| const FORMATETC* format, |
| BOOL* insertable) { |
| + // We does not support any embedded objects. |
| NOTIMPLEMENTED(); |
| return E_NOTIMPL; |
| } |
| @@ -228,8 +373,10 @@ STDMETHODIMP TsfTextStore::RequestAttrsAtPosition( |
| ULONG attribute_buffer_size, |
| const TS_ATTRID* attribute_buffer, |
| DWORD flags) { |
| - NOTIMPLEMENTED(); |
| - return E_NOTIMPL; |
| + // We does not support any document attributes. |
| + // This method just returns S_OK, and the subsequently called |
| + // RetrieveRequestedAttrs() returns 0 as the number of supported attributes. |
| + return S_OK; |
| } |
| STDMETHODIMP TsfTextStore::RequestAttrsTransitioningAtPosition( |
| @@ -237,36 +384,147 @@ STDMETHODIMP TsfTextStore::RequestAttrsTransitioningAtPosition( |
| ULONG attribute_buffer_size, |
| const TS_ATTRID* attribute_buffer, |
| DWORD flags) { |
| - NOTIMPLEMENTED(); |
| - return E_NOTIMPL; |
| + // We does not support any document attributes. |
| + // This method just returns S_OK, and the subsequently called |
| + // RetrieveRequestedAttrs() returns 0 as the number of supported attributes. |
| + return S_OK; |
| } |
| STDMETHODIMP TsfTextStore::RequestLock(DWORD lock_flags, HRESULT* result) { |
| - NOTIMPLEMENTED(); |
| - return E_NOTIMPL; |
| + if (!text_store_acp_sink_.get()) |
| + return E_FAIL; |
| + if (result == NULL) |
| + return E_INVALIDARG; |
| + |
| + if (current_lock_type_ != 0) { |
| + if (lock_flags & TS_LF_SYNC) { |
| + // Can't lock synchronously. |
| + *result = TS_E_SYNCHRONOUS; |
| + return S_OK; |
| + } |
| + // Queue the lock request. |
| + lock_queue_.push_back(lock_flags & TS_LF_READWRITE); |
| + *result = TS_S_ASYNC; |
| + return S_OK; |
| + } |
| + |
| + // Lock |
| + current_lock_type_ = (lock_flags & TS_LF_READWRITE); |
| + |
| + status_.edit_flag_ = false; |
| + size_t last_commited_size = status_.commited_size_; |
|
Seigo Nonaka
2012/08/21 16:14:06
nit: const and /commited/committed/
horo
2012/08/22 02:22:33
Done.
|
| + |
| + // Grant the lock. |
| + *result = text_store_acp_sink_->OnLockGranted(current_lock_type_); |
| + |
| + // Unlock |
| + current_lock_type_ = 0; |
| + |
| + // Handles the pending lock requests. |
| + if(lock_queue_.size() > 0) { |
|
Seigo Nonaka
2012/08/21 16:14:06
nit: /if(/if (/
horo
2012/08/22 02:22:33
Done.
|
| + current_lock_type_ = lock_queue_.front(); |
| + lock_queue_.pop_front(); |
| + text_store_acp_sink_->OnLockGranted(current_lock_type_); |
| + current_lock_type_ = 0; |
| + } |
| + |
| + // If the text store status is edited in OnLockGranted(), we may need to call |
| + // TextInputClient::InsertText() or TextInputClient::SetCompositionText(). |
| + if (status_.edit_flag_) { |
| + size_t new_commited_size = status_.commited_size_; |
| + const string16 new_commited_string = |
| + status_.string_buffer_.substr(last_commited_size, |
| + new_commited_size - last_commited_size); |
| + const string16 composition_string = |
| + status_.string_buffer_.substr(new_commited_size); |
| + |
| + // If there is new commited string, calls TextInputClient::InsertText(). |
| + if (new_commited_string.length() != 0) { |
| + if (text_input_client_) { |
|
Seigo Nonaka
2012/08/21 16:14:06
nit: remove braces
horo
2012/08/22 02:22:33
Done.
|
| + text_input_client_->InsertText(new_commited_string); |
| + } |
| + } |
| + |
| + // Calls TextInputClient::SetCompositionText(). |
| + CompositionText composition_text; |
| + composition_text.text = composition_string; |
| + composition_text.underlines = status_.composition_undelines_; |
| + for (size_t i = 0; i < composition_text.underlines.size(); ++i) { |
| + composition_text.underlines[i].start_offset -= new_commited_size; |
| + composition_text.underlines[i].end_offset -= new_commited_size; |
| + } |
| + if (status_.selection_.start() < new_commited_size) { |
| + composition_text.selection.set_start(0); |
| + } else { |
| + composition_text.selection.set_start( |
| + status_.selection_.start() - new_commited_size); |
| + } |
| + if (status_.selection_.end() < new_commited_size) { |
| + composition_text.selection.set_end(0); |
| + } else { |
| + composition_text.selection.set_end( |
| + status_.selection_.end() - new_commited_size); |
| + } |
| + if (text_input_client_) { |
|
Seigo Nonaka
2012/08/21 16:14:06
nit: remove braces
horo
2012/08/22 02:22:33
Done.
|
| + text_input_client_->SetCompositionText(composition_text); |
| + } |
| + |
| + // If there is no composition string, clear the text store status. |
| + // And call OnSelectionChange(), OnLayoutChange(), and OnTextChange(). |
| + if ((composition_string.length() == 0) && (new_commited_size != 0)) { |
| + status_.RemoveText(0, new_commited_size); |
| + status_.commited_size_ = 0; |
| + status_.MoveSelection(0, 0); |
| + text_store_acp_sink_->OnSelectionChange(); |
| + text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); |
| + TS_TEXTCHANGE textChange; |
| + textChange.acpStart = 0; |
| + textChange.acpOldEnd = new_commited_size; |
| + textChange.acpNewEnd = 0; |
| + text_store_acp_sink_->OnTextChange(0, &textChange); |
| + } |
| + } |
| + |
| + return S_OK; |
| } |
| STDMETHODIMP TsfTextStore::RequestSupportedAttrs( |
| DWORD flags, |
| ULONG attribute_buffer_size, |
| const TS_ATTRID* attribute_buffer) { |
| - NOTIMPLEMENTED(); |
| - return E_NOTIMPL; |
| + // We does not support any document attributes. |
| + // This method just returns S_OK, and the subsequently called |
| + // RetrieveRequestedAttrs() returns 0 as the number of supported attributes. |
| + return S_OK; |
| } |
| STDMETHODIMP TsfTextStore::RetrieveRequestedAttrs( |
| ULONG attribute_buffer_size, |
| TS_ATTRVAL* attribute_buffer, |
| ULONG* attribute_buffer_copied) { |
| - NOTIMPLEMENTED(); |
| - return E_NOTIMPL; |
| + if (attribute_buffer_copied == NULL) |
| + return E_INVALIDARG; |
| + // We does not support any document attributes. |
| + attribute_buffer_copied = 0; |
| + return S_OK; |
| } |
| STDMETHODIMP TsfTextStore::SetSelection( |
| ULONG selection_buffer_size, |
| const TS_SELECTION_ACP* selection_buffer) { |
| - NOTIMPLEMENTED(); |
| - return E_NOTIMPL; |
| + if (!WriterLocked()) |
| + return TF_E_NOLOCK; |
| + if (selection_buffer_size > 0) { |
| + const LONG start_pos = selection_buffer[0].acpStart; |
| + const LONG end_pos = selection_buffer[0].acpEnd; |
| + if ((start_pos < 0) || |
| + (static_cast<LONG>(status_.string_buffer_.length()) < start_pos) || |
| + (end_pos < start_pos) || |
| + (static_cast<LONG>(status_.string_buffer_.length()) < end_pos)) |
|
Seigo Nonaka
2012/08/21 16:14:06
nit: please do not remove braces if the condition
horo
2012/08/22 02:22:33
Done.
|
| + return TF_E_INVALIDPOS; |
| + status_.MoveSelection(start_pos, end_pos); |
| + } |
| + return S_OK; |
| } |
| STDMETHODIMP TsfTextStore::SetText(DWORD flags, |
| @@ -275,8 +533,34 @@ STDMETHODIMP TsfTextStore::SetText(DWORD flags, |
| const wchar_t* text_buffer, |
| ULONG text_buffer_size, |
| TS_TEXTCHANGE* text_change) { |
| - NOTIMPLEMENTED(); |
| - return E_NOTIMPL; |
| + if (!WriterLocked()) |
| + return TS_E_NOLOCK; |
| + if (acp_start < static_cast<LONG>(status_.commited_size_)) |
| + return TS_E_INVALIDPOS; |
| + if (static_cast<LONG>(status_.string_buffer_.length()) < acp_start) |
| + return TS_E_INVALIDPOS; |
| + if (acp_end < acp_start) |
| + return E_INVALIDARG; |
| + if (static_cast<LONG>(status_.string_buffer_.length()) < acp_end) |
| + return TS_E_INVALIDPOS; |
| + |
| + TS_SELECTION_ACP selection; |
| + selection.acpStart = acp_start; |
| + selection.acpEnd = acp_end; |
| + selection.style.ase = TS_AE_NONE; |
| + selection.style.fInterimChar = 0; |
| + if (SetSelection(1, &selection) != S_OK) |
| + return E_UNEXPECTED; |
| + |
| + TS_TEXTCHANGE change; |
| + if (InsertTextAtSelection(0, text_buffer, text_buffer_size, |
| + &acp_start, &acp_end, &change) != S_OK) { |
| + return E_UNEXPECTED; |
| + } |
| + if (text_change) |
| + *text_change = change; |
| + |
| + return S_OK; |
| } |
| STDMETHODIMP TsfTextStore::UnadviseSink(IUnknown* unknown) { |
| @@ -290,7 +574,7 @@ STDMETHODIMP TsfTextStore::UnadviseSink(IUnknown* unknown) { |
| STDMETHODIMP TsfTextStore::OnStartComposition( |
| ITfCompositionView* composition_view, |
| BOOL* ok) { |
| - if (!ok) |
| + if (ok) |
| *ok = TRUE; |
| return S_OK; |
| } |
| @@ -309,7 +593,144 @@ STDMETHODIMP TsfTextStore::OnEndComposition( |
| STDMETHODIMP TsfTextStore::OnEndEdit(ITfContext* context, |
| TfEditCookie read_only_edit_cookie, |
| ITfEditRecord* edit_record) { |
| + if (!context || !edit_record) |
| + return E_INVALIDARG; |
| + |
| + size_t commited_size; |
| + CompositionUnderlines undelines; |
| + if (!GetCompositionStatus(context, read_only_edit_cookie, &commited_size, |
| + &undelines)) { |
| + return S_OK; |
| + } |
| + status_.composition_undelines_ = undelines; |
| + status_.commited_size_ = commited_size; |
| + status_.edit_flag_ = true; |
| return S_OK; |
| } |
| -} // namespace ui |
| +bool TsfTextStore::GetDisplayAttribute(TfGuidAtom guid_atom, |
| + TF_DISPLAYATTRIBUTE* attribute) { |
| + GUID guid; |
| + if (FAILED(category_manager_->GetGUID(guid_atom, &guid))) |
| + return false; |
| + |
| + base::win::ScopedComPtr<ITfDisplayAttributeInfo> display_attribute_info; |
| + if (FAILED(display_attribute_manager_->GetDisplayAttributeInfo( |
| + guid, display_attribute_info.Receive(), NULL))) { |
| + return false; |
| + } |
| + return SUCCEEDED(display_attribute_info->GetAttributeInfo(attribute)); |
| +} |
| + |
| +bool TsfTextStore::GetCompositionStatus( |
| + ITfContext* context, |
| + const TfEditCookie read_only_edit_cookie, |
| + size_t* committed_size, |
| + CompositionUnderlines* undelines) { |
| + DCHECK(context); |
| + DCHECK(committed_size); |
| + DCHECK(undelines); |
| + const GUID* rgGuids[2] = {&GUID_PROP_COMPOSING, &GUID_PROP_ATTRIBUTE}; |
| + base::win::ScopedComPtr<ITfReadOnlyProperty> track_property; |
| + if (FAILED(context->TrackProperties(rgGuids, 2, NULL, 0, |
| + track_property.Receive()))) { |
| + return false; |
| + } |
| + |
| + *committed_size = 0; |
| + undelines->clear(); |
| + base::win::ScopedComPtr<ITfRange> start_to_end_range; |
| + base::win::ScopedComPtr<ITfRange> end_range; |
| + if (FAILED(context->GetStart(read_only_edit_cookie, |
| + start_to_end_range.Receive()))) { |
| + return false; |
| + } |
| + if (FAILED(context->GetEnd(read_only_edit_cookie, end_range.Receive()))) |
| + return false; |
| + if (FAILED(start_to_end_range->ShiftEndToRange(read_only_edit_cookie, |
| + end_range, TF_ANCHOR_END))) { |
| + return false; |
| + } |
| + |
| + base::win::ScopedComPtr<IEnumTfRanges> ranges; |
| + if (FAILED(track_property->EnumRanges(read_only_edit_cookie, ranges.Receive(), |
| + start_to_end_range))) { |
| + return false; |
| + } |
| + |
| + base::win::ScopedComPtr<ITfRange> range; |
| + while (ranges->Next(1, range.Receive(), NULL) == S_OK) { |
| + base::win::ScopedVariant value; |
| + base::win::ScopedComPtr<IEnumTfPropertyValue> enum_prop_value; |
| + if (FAILED(track_property->GetValue(read_only_edit_cookie, range, |
| + value.Receive()))) { |
| + return false; |
| + } |
| + if (FAILED(enum_prop_value.QueryFrom(value.AsInput()->punkVal))) |
| + return false; |
| + |
| + TF_PROPERTYVAL property_value; |
| + bool is_composition = false; |
| + bool has_display_attribute = false; |
| + TF_DISPLAYATTRIBUTE display_attribute; |
| + while (enum_prop_value->Next(1, &property_value, NULL) == S_OK) { |
| + if (IsEqualGUID(property_value.guidId, GUID_PROP_COMPOSING)) { |
| + is_composition = (property_value.varValue.lVal == TRUE); |
| + } else if (IsEqualGUID(property_value.guidId, GUID_PROP_ATTRIBUTE)) { |
| + TfGuidAtom guid_atom = |
| + static_cast<TfGuidAtom>(property_value.varValue.lVal); |
| + if (GetDisplayAttribute(guid_atom, &display_attribute)) |
| + has_display_attribute = true; |
| + } |
| + VariantClear(&property_value.varValue); |
| + } |
| + |
| + base::win::ScopedComPtr<ITfRangeACP> range_acp; |
| + range_acp.QueryFrom(range); |
| + LONG start_pos, length; |
| + range_acp->GetExtent(&start_pos, &length); |
| + if (!is_composition) { |
| + if (*committed_size < static_cast<size_t>(start_pos + length)) |
| + *committed_size = start_pos + length; |
| + } else { |
| + CompositionUnderline underline; |
| + underline.start_offset = start_pos; |
| + underline.end_offset = start_pos + length; |
| + underline.color = SK_ColorBLACK; |
| + if (has_display_attribute) |
| + underline.thick = !!display_attribute.fBoldLine; |
| + undelines->push_back(underline); |
| + } |
| + range.Release(); |
| + } |
| + return true; |
| +} |
| + |
| +void TsfTextStore::SetFocusedTextInputClient( |
| + HWND focused_window, |
| + TextInputClient* text_input_client) { |
| + hwnd_ = focused_window; |
| + text_input_client_ = text_input_client; |
| +} |
| + |
| +void TsfTextStore::RemoveFocusedTextInputClient( |
| + TextInputClient* text_input_client) { |
| + if (text_input_client_ == text_input_client) { |
| + hwnd_ = 0; |
| + text_input_client_ = NULL; |
| + } |
| +} |
| + |
| +void TsfTextStore::SendOnLayoutChange() { |
| + if (text_store_acp_sink_) |
| + text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); |
| +} |
| + |
| +bool TsfTextStore::ReaderLocked() const { |
| + return (current_lock_type_ & TS_LF_READ) == TS_LF_READ; |
| +} |
| + |
| +bool TsfTextStore::WriterLocked() const { |
| + return (current_lock_type_ & TS_LF_READWRITE) == TS_LF_READWRITE; |
| +} |
| +} // namespace ui |