blob: 594b5f5e05d1b144f409d5e51143f219be3643f8 [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.
#include "win32/tip/tip_ui_renderer_immersive.h"
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
// Workaround against KB813540
#include <atlbase_mozc.h>
#include <atlapp.h>
#include <atlmisc.h>
#include <memory>
#include "base/util.h"
#include "session/commands.pb.h"
#include "renderer/table_layout.h"
#include "renderer/win32/text_renderer.h"
namespace mozc {
namespace win32 {
namespace tsf {
namespace {
using WTL::CBitmap;
using WTL::CBitmapHandle;
using WTL::CDC;
using WTL::CRect;
using std::unique_ptr;
using ::mozc::commands::Candidates;
using ::mozc::commands::Output;
using ::mozc::renderer::TableLayout;
using ::mozc::renderer::TableLayoutInterface;
using ::mozc::renderer::win32::TextRenderer;
using ::mozc::renderer::win32::TextRenderingInfo;
typedef ::mozc::commands::Preedit_Segment::Annotation Annotation;
typedef ::mozc::commands::Candidates_Candidate Candidate;
// DPI-invariant layout size constants in pixel unit.
const int kWindowBorder = 2;
const int kRowRectPadding = 4;
const int kIndicatorWidth = 4;
// Color scheme
const COLORREF kFrameColor = RGB(0x00, 0x00, 0x00);
const COLORREF kSelectedRowBackgroundColor = RGB(0xd1, 0xea, 0xff);
const COLORREF kDefaultBackgroundColor = RGB(0xff, 0xff, 0xff);
const COLORREF kIndicatorBackgroundColor = RGB(0xe0, 0xe0, 0xe0);
const COLORREF kIndicatorColor = RGB(0xb8, 0xb8, 0xb8);
// usage type for each column.
enum COLUMN_TYPE {
COLUMN_GAP1, // padding region
COLUMN_CANDIDATE, // show candidate string
COLUMN_GAP2, // padding region
NUMBER_OF_COLUMNS, // number of columns. (this item should be last)
};
CRect ToCRect(const Rect &rect) {
return CRect(rect.Left(), rect.Top(), rect.Right(), rect.Bottom());
}
// Returns the smallest index of the given candidate list which satisfies
// candidates.candidate(i) == |candidate_index|.
// This function returns the size of the given candidate list when there
// aren't any candidates satisfying the above condition.
int GetCandidateArrayIndexByCandidateIndex(const Candidates &candidates,
int candidate_index) {
for (size_t i = 0; i < candidates.candidate_size(); ++i) {
const Candidate &candidate = candidates.candidate(i);
if (candidate.index() == candidate_index) {
return i;
}
}
return candidates.candidate_size();
}
// Returns the smallest index of the given candidate list which satisfies
// |candidates.focused_index| == |candidates.candidate(i).index()|.
// This function returns the size of the given candidate list when there
// aren't any candidates satisfying the above condition.
int GetFocusedArrayIndex(const Candidates &candidates) {
const int kInvalidIndex = candidates.candidate_size();
if (!candidates.has_focused_index()) {
return kInvalidIndex;
}
const int focused_index = candidates.focused_index();
return GetCandidateArrayIndexByCandidateIndex(candidates, focused_index);
}
void CalcLayout(const Candidates &candidates,
const TextRenderer &text_renderer,
TableLayout *table_layout,
vector<wstring> *candidate_strings) {
table_layout->Initialize(candidates.candidate_size(), NUMBER_OF_COLUMNS);
table_layout->SetWindowBorder(kWindowBorder);
// Add a positional indicator if candidate list consists of more than one
// page.
if (candidates.candidate_size() < candidates.size()) {
table_layout->SetVScrollBar(kIndicatorWidth);
}
table_layout->SetRowRectPadding(kRowRectPadding);
// put a padding in COLUMN_GAP1.
// the width is determined to be equal to the width of " ".
const Size gap1_size =
text_renderer.MeasureString(TextRenderer::FONTSET_CANDIDATE, L" ");
table_layout->EnsureCellSize(COLUMN_GAP1, gap1_size);
for (size_t i = 0; i < candidates.candidate_size(); ++i) {
wstring candidate_string;
const Candidate &candidate = candidates.candidate(i);
if (candidate.has_value()) {
mozc::Util::UTF8ToWide(candidate.value().c_str(), &candidate_string);
}
if (candidate.has_annotation()) {
const commands::Annotation &annotation = candidate.annotation();
if (annotation.has_prefix()) {
wstring annotation_prefix;
mozc::Util::UTF8ToWide(annotation.prefix().c_str(),
&annotation_prefix);
candidate_string = annotation_prefix + candidate_string;
}
if (annotation.has_suffix()) {
wstring annotation_suffix;
mozc::Util::UTF8ToWide(annotation.suffix().c_str(),
&annotation_suffix);
candidate_string += annotation_suffix;
}
}
candidate_strings->push_back(candidate_string);
if (!candidate_string.empty()) {
const Size rendering_size = text_renderer.MeasureString(
TextRenderer::FONTSET_CANDIDATE, candidate_string);
table_layout->EnsureCellSize(COLUMN_CANDIDATE, rendering_size);
}
}
// Put a padding in COLUMN_GAP2.
const wchar_t *gap2_string = L" ";
const Size gap2_size = text_renderer.MeasureString(
TextRenderer::FONTSET_CANDIDATE, gap2_string);
table_layout->EnsureCellSize(COLUMN_GAP2, gap2_size);
table_layout->FreezeLayout();
}
CBitmapHandle RenderImpl(const Candidates &candidates,
const TableLayout &table_layout,
const TextRenderer &text_renderer,
const vector<wstring> &candidate_strings) {
const int width = table_layout.GetTotalSize().width;
const int height = table_layout.GetTotalSize().height;
CBitmap bitmap;
bitmap.CreateBitmap(width, height, 1, 32, nullptr);
CDC dc;
dc.CreateCompatibleDC();
CBitmapHandle old_bitmap = dc.SelectBitmap(bitmap);
dc.SetBkMode(TRANSPARENT);
const CRect client_crect(0, 0, width, height);
// Background
{
dc.FillSolidRect(&client_crect, kDefaultBackgroundColor);
}
// Focused rectangle
{
const int focused_array_index = GetFocusedArrayIndex(candidates);
if (0 <= focused_array_index &&
focused_array_index < candidates.candidate_size()) {
const commands::Candidates::Candidate &candidate
= candidates.candidate(focused_array_index);
const CRect selected_rect =
ToCRect(table_layout.GetRowRect(focused_array_index));
dc.FillSolidRect(&selected_rect, kSelectedRowBackgroundColor);
}
}
// Candidate strings
{
const COLUMN_TYPE column_type = COLUMN_CANDIDATE;
const TextRenderer::FONT_TYPE font_type =
TextRenderer::FONTSET_CANDIDATE;
vector<TextRenderingInfo> display_list;
for (size_t i = 0; i < candidate_strings.size(); ++i) {
const wstring &candidate_string = candidate_strings[i];
const Rect &text_rect =
table_layout.GetCellRect(i, column_type);
display_list.push_back(TextRenderingInfo(candidate_string, text_rect));
}
text_renderer.RenderTextList(dc.m_hDC, display_list, font_type);
}
// Indicator
{
const Rect &vscroll_rect = table_layout.GetVScrollBarRect();
if (!vscroll_rect.IsRectEmpty() && candidates.candidate_size() > 0) {
const int begin_index = candidates.candidate(0).index();
const int candidates_in_page = candidates.candidate_size();
const int candidates_total = candidates.size();
const int end_index =
candidates.candidate(candidates_in_page - 1).index();
const CRect background_crect = ToCRect(vscroll_rect);
dc.FillSolidRect(&background_crect, kIndicatorBackgroundColor);
const Rect &indicator_rect =
table_layout.GetVScrollIndicatorRect(
begin_index, end_index, candidates_total);
const CRect indicator_crect = ToCRect(indicator_rect);
dc.FillSolidRect(&indicator_crect, kIndicatorColor);
}
}
// Edge frame
{
// DC brush is available in Windows 2000 and later.
dc.SetDCBrushColor(kFrameColor);
CRect crect = client_crect;
for (int i = 0; i < kWindowBorder; ++i) {
dc.FrameRect(&crect,
static_cast<HBRUSH>(GetStockObject(DC_BRUSH)));
crect.DeflateRect(1, 1, 1, 1);
}
}
dc.SelectBitmap(old_bitmap);
return bitmap.Detach();
}
} // namespace
HBITMAP TipUiRendererImmersive::Render(
const Candidates &candidates,
const renderer::win32::TextRenderer *text_renderer,
renderer::TableLayout *table_layout,
SIZE *size, int *left_align_offset) {
vector<wstring> candidate_strings;
CalcLayout(candidates, *text_renderer, table_layout, &candidate_strings);
const Size &total_size = table_layout->GetTotalSize();
if (size != nullptr) {
size->cx = total_size.width;
size->cy = total_size.height;
}
if (left_align_offset != nullptr) {
*left_align_offset = table_layout->GetColumnRect(COLUMN_CANDIDATE).Left();
}
return RenderImpl(
candidates, *table_layout, *text_renderer, candidate_strings);
}
} // namespace tsf
} // namespace win32
} // namespace mozc