blob: 8890b42f74b8aa4d8e237c47acfcec8d04245f9d [file] [log] [blame]
// Copyright 2010-2015, 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.
// Class functions to be used for output by the Session class.
#include "session/internal/session_output.h"
#include <algorithm>
#include <map>
#include <string>
#include <vector>
#include "base/logging.h"
#include "base/port.h"
#include "base/text_normalizer.h"
#include "base/util.h"
#include "base/version.h"
#include "composer/composer.h"
#include "converter/segments.h"
#include "session/internal/candidate_list.h"
namespace mozc {
namespace session {
namespace {
bool FillAnnotation(const Segment::Candidate &candidate_value,
commands::Annotation *annotation) {
bool is_modified = false;
if (!candidate_value.prefix.empty()) {
annotation->set_prefix(candidate_value.prefix);
is_modified = true;
}
if (!candidate_value.suffix.empty()) {
annotation->set_suffix(candidate_value.suffix);
is_modified = true;
}
if (!candidate_value.description.empty()) {
annotation->set_description(candidate_value.description);
is_modified = true;
}
if (candidate_value.attributes &
Segment::Candidate::USER_HISTORY_PREDICTION) {
annotation->set_deletable(true);
is_modified = true;
}
return is_modified;
}
void FillAllCandidateWordsInternal(
const Segment &segment,
const CandidateList &candidate_list,
const int focused_id,
commands::CandidateList *candidate_list_proto) {
for (size_t i = 0; i < candidate_list.size(); ++i) {
const Candidate &candidate = candidate_list.candidate(i);
if (candidate.IsSubcandidateList()) {
FillAllCandidateWordsInternal(
segment, candidate.subcandidate_list(), focused_id,
candidate_list_proto);
continue;
}
commands::CandidateWord *candidate_word_proto =
candidate_list_proto->add_candidates();
// id
const int id = candidate.id();
candidate_word_proto->set_id(id);
// index
const int index = candidate_list_proto->candidates_size() - 1;
candidate_word_proto->set_index(index);
// check focused id
if (id == focused_id && candidate_list.focused()) {
candidate_list_proto->set_focused_index(index);
}
const Segment::Candidate &segment_candidate = segment.candidate(id);
// key
if (segment.key() != segment_candidate.content_key) {
candidate_word_proto->set_key(segment_candidate.content_key);
}
// value
candidate_word_proto->set_value(segment_candidate.value);
// annotations
commands::Annotation annotation;
if (FillAnnotation(segment_candidate, &annotation)) {
candidate_word_proto->mutable_annotation()->CopyFrom(annotation);
}
}
}
} // namespace
// static
void SessionOutput::FillCandidate(
const Segment &segment,
const Candidate &candidate,
commands::Candidates_Candidate *candidate_proto) {
if (candidate.IsSubcandidateList()) {
candidate_proto->set_value(candidate.subcandidate_list().name());
candidate_proto->set_id(candidate.subcandidate_list().focused_id());
return;
}
const Segment::Candidate &candidate_value = segment.candidate(candidate.id());
candidate_proto->set_value(candidate_value.value);
candidate_proto->set_id(candidate.id());
// Set annotations
commands::Annotation annotation;
if (FillAnnotation(candidate_value, &annotation)) {
candidate_proto->mutable_annotation()->CopyFrom(annotation);
}
if (!candidate_value.usage_title.empty()) {
candidate_proto->set_information_id(candidate_value.usage_id);
}
}
// static
void SessionOutput::FillCandidates(const Segment &segment,
const CandidateList &candidate_list,
const size_t position,
commands::Candidates *candidates_proto) {
if (candidate_list.focused()) {
candidates_proto->set_focused_index(candidate_list.focused_index());
}
candidates_proto->set_size(candidate_list.size());
candidates_proto->set_page_size(candidate_list.page_size());
candidates_proto->set_position(position);
size_t c_begin = 0;
size_t c_end = 0;
candidate_list.GetPageRange(candidate_list.focused_index(),
&c_begin, &c_end);
// Store candidates.
for (size_t i = c_begin; i <= c_end; ++i) {
commands::Candidates_Candidate *candidate_proto =
candidates_proto->add_candidate();
candidate_proto->set_index(i);
FillCandidate(segment, candidate_list.candidate(i), candidate_proto);
}
// Store subcandidates.
if (candidate_list.focused_candidate().IsSubcandidateList()) {
FillCandidates(segment,
candidate_list.focused_candidate().subcandidate_list(),
candidate_list.focused_index(),
candidates_proto->mutable_subcandidates());
}
// Store usages.
FillUsages(segment, candidate_list, candidates_proto);
}
// static
void SessionOutput::FillAllCandidateWords(
const Segment &segment,
const CandidateList &candidate_list,
const commands::Category category,
commands::CandidateList *candidate_list_proto) {
candidate_list_proto->set_category(category);
FillAllCandidateWordsInternal(
segment, candidate_list, candidate_list.focused_id(),
candidate_list_proto);
}
// static
bool SessionOutput::ShouldShowUsages(const Segment &segment,
const CandidateList &cand_list) {
// Check if the shown candidate have the usage data.
size_t c_begin = 0;
size_t c_end = 0;
cand_list.GetPageRange(cand_list.focused_index(), &c_begin, &c_end);
for (size_t i = c_begin; i <= c_end; ++i) {
if (cand_list.candidate(i).IsSubcandidateList()) {
continue;
}
const Segment::Candidate &candidate =
segment.candidate(cand_list.candidate(i).id());
if (candidate.usage_title.empty()) {
continue;
}
return true;
}
return false;
}
// static
void SessionOutput::FillUsages(const Segment &segment,
const CandidateList &cand_list,
commands::Candidates *candidates_proto) {
if (!ShouldShowUsages(segment, cand_list)) {
return;
}
commands::InformationList *usages = candidates_proto->mutable_usages();
size_t c_begin = 0;
size_t c_end = 0;
cand_list.GetPageRange(cand_list.focused_index(), &c_begin, &c_end);
typedef pair<int32, commands::Information *> IndexInfoPair;
map<int32, IndexInfoPair> usageid_information_map;
// Store usages.
for (size_t i = c_begin; i <= c_end; ++i) {
if (cand_list.candidate(i).IsSubcandidateList()) {
continue;
}
const Segment::Candidate &candidate =
segment.candidate(cand_list.candidate(i).id());
if (candidate.usage_title.empty()) {
continue;
}
int index;
commands::Information *info;
map<int32, IndexInfoPair>::iterator info_itr =
usageid_information_map.find(candidate.usage_id);
if (info_itr == usageid_information_map.end()) {
index = usages->information_size();
info = usages->add_information();
info->set_id(candidate.usage_id);
info->set_title(candidate.usage_title);
info->set_description(candidate.usage_description);
info->add_candidate_id(cand_list.candidate(i).id());
usageid_information_map.insert(
make_pair(candidate.usage_id, make_pair(index, info)));
} else {
index = info_itr->second.first;
info = info_itr->second.second;
info->add_candidate_id(cand_list.candidate(i).id());
}
if (cand_list.candidate(i).id() == cand_list.focused_id()) {
usages->set_focused_index(index);
}
}
}
// static
void SessionOutput::FillShortcuts(const string &shortcuts,
commands::Candidates *candidates_proto) {
const size_t num_loop = min(
static_cast<size_t>(candidates_proto->candidate_size()),
shortcuts.size());
for (size_t i = 0; i < num_loop; ++i) {
const string shortcut = shortcuts.substr(i, 1);
candidates_proto->mutable_candidate(i)->mutable_annotation()->
set_shortcut(shortcut);
}
}
// static
void SessionOutput::FillSubLabel(commands::Footer *footer) {
// Delete the label because sub_label will be drawn on the same
// place for the label.
footer->clear_label();
// Append third number of the version to sub_label.
const string version = Version::GetMozcVersion();
vector<string> version_numbers;
Util::SplitStringUsing(version, ".", &version_numbers);
if (version_numbers.size() > 2) {
string sub_label("build ");
sub_label.append(version_numbers[2]);
footer->set_sub_label(sub_label);
} else {
LOG(ERROR) << "Unkonwn version format: " << version;
}
}
// static
bool SessionOutput::FillFooter(const commands::Category category,
commands::Candidates *candidates) {
if (category != commands::SUGGESTION &&
category != commands::PREDICTION &&
category != commands::CONVERSION) {
return false;
}
bool show_build_number = true;
commands::Footer *footer = candidates->mutable_footer();
if (category == commands::SUGGESTION) {
// TODO(komatsu): Enable to localized the message.
// "Tabキーで選択"
const char kLabel[] = ("Tab\xE3\x82\xAD\xE3\x83\xBC\xE3\x81\xA7"
"\xE9\x81\xB8\xE6\x8A\x9E");
// TODO(komatsu): Need to check if Tab is not changed to other key binding.
footer->set_label(kLabel);
} else {
// category is commands::PREDICTION or commands::CONVERSION.
footer->set_index_visible(true);
footer->set_logo_visible(true);
// If the selected candidate is a user prediction history, tell the user
// that it can be removed by Ctrl-Delete.
if (candidates->has_focused_index()) {
for (size_t i = 0; i < candidates->candidate_size(); ++i) {
const commands::Candidates::Candidate &cand = candidates->candidate(i);
if (cand.index() != candidates->focused_index()) {
continue;
}
if (cand.has_annotation() && cand.annotation().deletable()) {
// TODO(noriyukit): Change the message depending on user's keymap.
#if defined(OS_MACOSX)
// "control+fn+deleteで履歴から削除"
const char kDeleteInstruction[] =
"\x63\x6F\x6E\x74\x72\x6F\x6C\x2B\x66\x6E\x2B\x64\x65\x6C\x65"
"\x74\x65\xE3\x81\xA7\xE5\xB1\xA5\xE6\xAD\xB4\xE3\x81\x8B\xE3"
"\x82\x89\xE5\x89\x8A\xE9\x99\xA4";
#elif defined(__native_client__)
// "ctrl+alt+backspaceで履歴から削除"
const char kDeleteInstruction[] =
"\x63\x74\x72\x6C\x2B\x61\x6C\x74\x2B\x62\x61\x63\x6B\x73\x70"
"\x61\x63\x65\xE3\x81\xA7\xE5\xB1\xA5\xE6\xAD\xB4\xE3\x81\x8B"
"\xE3\x82\x89\xE5\x89\x8A\xE9\x99\xA4";
#else // !OS_MACOSX && !__native_client__
// "Ctrl+Delで履歴から削除"
const char kDeleteInstruction[] =
"\x43\x74\x72\x6C\x2B\x44\x65\x6C\xE3\x81\xA7\xE5\xB1\xA5"
"\xE6\xAD\xB4\xE3\x81\x8B\xE3\x82\x89\xE5\x89\x8A\xE9\x99\xA4";
#endif // OS_MACOSX || __native_client__
footer->set_label(kDeleteInstruction);
show_build_number = false;
}
break;
}
}
}
// Show the build number on the footer label for debugging when the build
// configuration is official dev channel.
if (show_build_number) {
#if defined(CHANNEL_DEV) && defined(GOOGLE_JAPANESE_INPUT_BUILD)
FillSubLabel(footer);
#endif // CHANNEL_DEV && GOOGLE_JAPANESE_INPUT_BUILD
}
return true;
}
// static
bool SessionOutput::AddSegment(const string &key,
const string &value,
const uint32 segment_type_mask,
commands::Preedit *preedit) {
// Key is always normalized as a preedit text.
string normalized_key;
TextNormalizer::NormalizePreeditText(key, &normalized_key);
string normalized_value;
if (segment_type_mask & PREEDIT) {
TextNormalizer::NormalizePreeditText(value, &normalized_value);
} else if (segment_type_mask & CONVERSION) {
normalized_value = value;
} else {
LOG(WARNING) << "Unknown segment type" << segment_type_mask;
normalized_value = value;
}
if (normalized_value.empty()) {
return false;
}
commands::Preedit::Segment *segment = preedit->add_segment();
segment->set_key(normalized_key);
segment->set_value(normalized_value);
segment->set_value_length(Util::CharsLen(normalized_value));
segment->set_annotation(commands::Preedit::Segment::UNDERLINE);
if ((segment_type_mask & CONVERSION) && (segment_type_mask & FOCUSED)) {
segment->set_annotation(commands::Preedit::Segment::HIGHLIGHT);
} else {
segment->set_annotation(commands::Preedit::Segment::UNDERLINE);
}
return true;
}
// static
void SessionOutput::FillPreedit(const composer::Composer &composer,
commands::Preedit *preedit) {
string output;
composer.GetStringForPreedit(&output);
const uint32 kBaseType = PREEDIT;
AddSegment(output, output, kBaseType, preedit);
preedit->set_cursor(static_cast<uint32>(composer.GetCursor()));
}
// static
void SessionOutput::FillConversion(const Segments &segments,
const size_t segment_index,
const int candidate_id,
commands::Preedit *preedit) {
const uint32 kBaseType = CONVERSION;
// Cursor position in conversion state should be the end of the preedit.
size_t cursor = 0;
for (size_t i = 0; i < segments.conversion_segments_size(); ++i) {
const Segment &segment = segments.conversion_segment(i);
if (i == segment_index) {
const string &value = segment.candidate(candidate_id).value;
if (AddSegment(segment.key(), value, kBaseType | FOCUSED, preedit) &&
(!preedit->has_highlighted_position())) {
preedit->set_highlighted_position(cursor);
}
cursor += Util::CharsLen(value);
} else {
const string &value = segment.candidate(0).value;
AddSegment(segment.key(), value, kBaseType, preedit);
cursor += Util::CharsLen(value);
}
}
preedit->set_cursor(cursor);
}
// static
void SessionOutput::FillConversionResultWithoutNormalization(
const string &key,
const string &result,
commands::Result *result_proto) {
result_proto->set_type(commands::Result::STRING);
result_proto->set_key(key);
result_proto->set_value(result);
}
// static
void SessionOutput::FillConversionResult(const string &key,
const string &result,
commands::Result *result_proto) {
// Key should be normalized as a preedit text.
string normalized_key;
TextNormalizer::NormalizePreeditText(key, &normalized_key);
// value is already normalized by converter.
FillConversionResultWithoutNormalization(
normalized_key, result, result_proto);
}
// static
void SessionOutput::FillPreeditResult(const string &preedit,
commands::Result *result_proto) {
string normalized_preedit;
TextNormalizer::NormalizePreeditText(preedit, &normalized_preedit);
FillConversionResultWithoutNormalization(
normalized_preedit, normalized_preedit, result_proto);
}
} // namespace session
} // namespace mozc