blob: 3900a2204f8225f25524a9c4dfc260c850b5be2e [file] [log] [blame]
// Copyright 2010-2014, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "win32/ime/ime_composition_string.h"
#include <ime.h>
#include <strsafe.h>
#include "google/protobuf/stubs/common.h"
#include "base/logging.h"
#include "base/util.h"
#include "session/commands.pb.h"
#include "win32/base/immdev.h"
#include "win32/base/string_util.h"
#include "win32/ime/ime_message_queue.h"
#include "win32/ime/ime_scoped_context.h"
namespace mozc {
namespace win32 {
namespace {
const DWORD kPreeditUpdateFlags =
(GCS_COMPREADSTR | GCS_COMPREADATTR | GCS_COMPREADCLAUSE | GCS_COMPSTR |
GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS | GCS_DELTASTART);
const DWORD kResultUpdateFlags =
(GCS_RESULTREADSTR | GCS_RESULTREADCLAUSE | GCS_RESULTSTR |
GCS_RESULTCLAUSE);
const DWORD kPreeditAndResultUpdateFlags =
(kPreeditUpdateFlags | kResultUpdateFlags);
} // anonymous namespace
bool CompositionString::Initialize() {
::ZeroMemory(this, sizeof(*this));
info.dwSize = sizeof(CompositionString);
// Composition string.
info.dwCompStrOffset = offsetof(CompositionString, composition_);
info.dwCompAttrOffset = offsetof(CompositionString, composition_attribute_);
info.dwCompClauseOffset = offsetof(CompositionString, composition_clause_);
// Composition reading string.
info.dwCompReadStrOffset = offsetof(CompositionString, composition_reading_);
info.dwCompReadAttrOffset =
offsetof(CompositionString, composition_reading_attribute_);
info.dwCompReadClauseOffset =
offsetof(CompositionString, composition_reading_clause_);
// Result.
info.dwResultStrOffset = offsetof(CompositionString, result_);
info.dwResultClauseOffset = offsetof(CompositionString, result_clause_);
// Result reading.
info.dwResultReadStrOffset = offsetof(CompositionString, result_reading_);
info.dwResultReadClauseOffset =
offsetof(CompositionString, result_reading_clause_);
return true;
}
bool CompositionString::Update(
const mozc::commands::Output &output, vector<UIMessage> *messages) {
CompositionString prev_composition;
::CopyMemory(&prev_composition, this, sizeof(CompositionString));
Initialize();
DWORD update_flags = 0;
if (!UpdateInternal(output, &update_flags)) {
return false;
}
const bool is_oneshot_composition =
(prev_composition.info.dwCompStrLen == 0) &&
(info.dwCompStrLen == 0) &&
(info.dwResultReadStrLen > 0);
const bool prev_has_composition = (prev_composition.info.dwCompStrLen > 0);
const bool has_composition = (info.dwCompStrLen > 0);
// Check start composition.
if (!prev_has_composition && (is_oneshot_composition || has_composition)) {
messages->push_back(UIMessage(WM_IME_STARTCOMPOSITION, 0, 0));
}
{
if (update_flags != 0) {
// TODO(yukawa): support wparam of WM_IME_COMPOSITION.
messages->push_back(UIMessage(WM_IME_COMPOSITION, 0, update_flags));
}
}
// Check end composition.
if ((prev_has_composition && !has_composition) || is_oneshot_composition) {
// In OOo, we need this message to restore caret status.
// We should not send this null-WM_IME_COMPOSITION when
// |info.dwResultStrLen != 0|. Otherwise, the result string will be
// commited twice in wordpad.exe.
if (info.dwResultStrLen == 0) {
messages->push_back(UIMessage(WM_IME_COMPOSITION, 0, 0));
}
messages->push_back(UIMessage(WM_IME_ENDCOMPOSITION, 0, 0));
}
return true;
}
bool CompositionString::HandleResult(const mozc::commands::Output &output) {
if (!output.has_result()) {
return true;
}
HRESULT result = S_OK;
wstring result_string;
mozc::Util::UTF8ToWide(output.result().value(), &result_string);
result = ::StringCchCopyN(result_,
arraysize(result_),
result_string.c_str(),
arraysize(result_));
if (FAILED(result)) {
return false;
}
info.dwResultStrLen = result_string.size();
// Since the Mozc server does not support clause information for the
// result string, we always declare the result string to be one segment.
// TODO(yukawa): Set clause after b/3135804 is implemented.
static_assert(arraysize(result_reading_clause_) >= 2,
"|result_reading_clause_| must has at least 2 elements.");
info.dwResultClauseLen = sizeof(result_clause_[0]) +
sizeof(result_clause_[1]);
result_clause_[0] = 0;
result_clause_[1] = info.dwResultStrLen;
if (output.result().has_key()) {
// Reading string should be stored as half-width katakana like
// other major IMEs. See b/1793283 for details.
const wstring &reading_string =
StringUtil::KeyToReading(output.result().key());
result = ::StringCchCopyN(result_reading_,
arraysize(result_reading_),
reading_string.c_str(),
arraysize(result_reading_));
if (FAILED(result)) {
return false;
}
info.dwResultReadStrLen = reading_string.size();
// Some applications such as Excel 2003 do not use the result string
// unless clause information is also available. (b/2959222)
// Since the Mozc server does not return clause information for the
// result string, we always declare the result string to be one segment.
// TODO(yukawa): Set clause after b/3135804 is implemented.
static_assert(arraysize(result_reading_clause_) >= 2,
"|result_reading_clause_| must has at least 2 elements.");
info.dwResultReadClauseLen = sizeof(result_reading_clause_[0]) +
sizeof(result_reading_clause_[1]);
result_reading_clause_[0] = 0;
result_reading_clause_[1] = info.dwResultReadStrLen;
}
return true;
}
bool CompositionString::HandlePreedit(const mozc::commands::Output &output) {
if (!output.has_preedit()) {
return true;
}
const mozc::commands::Preedit &preedit = output.preedit();
vector<BYTE> reading_attributes;
vector<DWORD> reading_clauses;
reading_clauses.push_back(0);
vector<BYTE> composition_attributes;
vector<DWORD> composition_clauses;
composition_clauses.push_back(0);
wstring reading_string;
wstring composition_string;
// As filed in b/2962397, we should use ATTR_CONVERTED as default
// attribute when the preedit state is 'Convert' ("変換") or 'Prediction'
// ("サジェスト選択中"). Fortunately, these states can be identified
// with |has_highlighted_position()| for the moment. This strategy also
// satisfies the requirement of b/2955151.
const BYTE default_attribute =
(preedit.has_highlighted_position() ? ATTR_CONVERTED : ATTR_INPUT);
string preedit_utf8;
for (size_t segment_index = 0;
segment_index < preedit.segment_size(); ++segment_index) {
const mozc::commands::Preedit::Segment &segment =
preedit.segment(segment_index);
if (segment.has_key()) {
// Reading string should be stored as half-width katakana like
// other major IMEs. See b/1793283 for details.
const wstring &segment_reading =
StringUtil::KeyToReading(segment.key());
reading_string.append(segment_reading);
for (size_t i = 0; i < segment_reading.size(); ++i) {
switch (segment.annotation()) {
case mozc::commands::Preedit::Segment::HIGHLIGHT:
reading_attributes.push_back(ATTR_TARGET_CONVERTED);
break;
case mozc::commands::Preedit::Segment::UNDERLINE:
case mozc::commands::Preedit::Segment::NONE:
default:
reading_attributes.push_back(default_attribute);
break;
}
}
}
reading_clauses.push_back(reading_string.size());
DCHECK(segment.has_value());
{
wstring segment_composition;
mozc::Util::UTF8ToWide(segment.value(), &segment_composition);
composition_string.append(segment_composition);
preedit_utf8.append(segment.value());
for (size_t i = 0; i < segment_composition.size(); ++i) {
switch (segment.annotation()) {
case mozc::commands::Preedit::Segment::HIGHLIGHT:
composition_attributes.push_back(ATTR_TARGET_CONVERTED);
break;
case mozc::commands::Preedit::Segment::UNDERLINE:
case mozc::commands::Preedit::Segment::NONE:
default:
composition_attributes.push_back(default_attribute);
break;
}
}
}
composition_clauses.push_back(composition_string.size());
}
if (preedit.has_cursor()) {
// |info.dwCursorPos| is supposed to be wide character index but
// |preedit.cursor()| is the number of Unicode characters. In case
// surrogate pair appears, use Util::WideCharsLen to calculate the
// cursor position as wide character index. See b/4163234 for details.
info.dwCursorPos = Util::WideCharsLen(
Util::SubString(preedit_utf8, 0, preedit.cursor()));
}
if (preedit.has_highlighted_position()) {
// Calculate the wide char index of the highlight segment so that
// prediction/candidate windows are aligned to the highlight segment.
// Note that |preedit.cursor()| is the number of Unicode characters.
// In case surrogate pair appears, use Util::WideCharsLen to calculate
// the highlighted position as wide character index.
// See b/4163234 for details.
const size_t highlighted_position_as_wchar_index = Util::WideCharsLen(
Util::SubString(preedit_utf8, 0, preedit.highlighted_position()));
focused_character_index_ = highlighted_position_as_wchar_index;
// TODO(yukawa): do not update cursor pos here if target application
// suppors IMECHARPOSITION protocol.
info.dwCursorPos = highlighted_position_as_wchar_index;
}
// Currently we can assume the suggest window is always aligned to the
// first character in the preedit. Perhaps we might want to have a
// dedicated field for this purpose in future.
if (output.has_candidates() && output.candidates().has_category() &&
output.candidates().category() == commands::SUGGESTION) {
focused_character_index_ = 0;
}
// Always set 0 to |dwDeltaStart| so that Excel updates composition.
// See b/2959161 for details.
// TODO(yukawa): Optimize this values so that Excel can optimize redraw
// region in the composition string.
// TODO(yukawa): Use Util::WideCharsLen to support surrogate-pair.
info.dwDeltaStart = 0;
DCHECK_EQ(composition_string.size(), composition_attributes.size());
info.dwCompAttrLen = composition_attributes.size();
for (size_t i = 0; i < composition_attributes.size(); ++i) {
composition_attribute_[i] = composition_attributes[i];
}
DCHECK_EQ(reading_string.size(), reading_attributes.size());
info.dwCompReadAttrLen = reading_attributes.size();
for (size_t i = 0; i < reading_attributes.size(); ++i) {
composition_reading_attribute_[i] = reading_attributes[i];
}
HRESULT result = S_OK;
result = ::StringCchCopyN(composition_,
arraysize(composition_),
composition_string.c_str(),
arraysize(composition_));
if (FAILED(result)) {
return false;
}
info.dwCompStrLen = composition_string.size();
result = ::StringCchCopyN(composition_reading_,
arraysize(composition_reading_),
reading_string.c_str(),
arraysize(composition_reading_));
if (FAILED(result)) {
return false;
}
info.dwCompReadStrLen = reading_string.size();
if (arraysize(composition_clause_) <= composition_clauses.size()) {
return false;
}
info.dwCompClauseLen = composition_clauses.size() * sizeof(DWORD);
for (size_t i = 0; i < composition_clauses.size(); ++i) {
composition_clause_[i] = composition_clauses[i];
}
if (arraysize(composition_reading_clause_) <= reading_clauses.size()) {
return false;
}
info.dwCompReadClauseLen = reading_clauses.size() * sizeof(DWORD);
for (size_t i = 0; i < reading_clauses.size(); ++i) {
composition_reading_clause_[i] = reading_clauses[i];
}
return true;
}
bool CompositionString::UpdateInternal(const mozc::commands::Output &output,
DWORD *update) {
info.dwCursorPos = -1;
if (output.has_result() && !HandleResult(output)) {
return false;
}
if (output.has_preedit() && !HandlePreedit(output)) {
return false;
}
// We always set update flags as predefined combination regardless of which
// field is actually updated. Otherwise, some applications such as wordpad
// OOo Writer 3.0 will update neither composition window nor caret state
// properly.
if (output.has_preedit() && output.has_result()) {
// This situation actually occurs when you type a printable character in
// candidate selection mode.
// This situation also occurs by partial commit.
*update = kPreeditAndResultUpdateFlags;
} else if (output.has_preedit()) {
*update = kPreeditUpdateFlags;
} else if (output.has_result()) {
*update = kResultUpdateFlags;
}
return true;
}
DWORD CompositionString::focused_character_index() const {
return focused_character_index_;
}
} // namespace win32
} // namespace mozc