| // 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 "renderer/unix/candidate_window.h" |
| |
| #include <sstream> |
| |
| #include "base/logging.h" |
| #include "base/util.h" |
| #include "client/client_interface.h" |
| #include "renderer/renderer_style_handler.h" |
| #include "renderer/table_layout.h" |
| #include "renderer/unix/cairo_factory_interface.h" |
| #include "renderer/unix/const.h" |
| #include "renderer/unix/draw_tool.h" |
| #include "renderer/unix/font_spec.h" |
| #include "renderer/unix/text_renderer.h" |
| |
| namespace mozc { |
| namespace renderer { |
| namespace gtk { |
| |
| namespace { |
| string GetIndexGuideString(const commands::Candidates &candidates) { |
| if (!candidates.has_footer() || !candidates.footer().index_visible()) { |
| return ""; |
| } |
| |
| const int focused_index = candidates.focused_index(); |
| const int total_items = candidates.size(); |
| |
| stringstream footer_string; |
| return Util::StringPrintf("%d/%d ", focused_index + 1, total_items); |
| } |
| |
| int GetCandidateArrayIndexByCandidateIndex( |
| const commands::Candidates &candidates, |
| int candidate_index) { |
| for (size_t i = 0; i < candidates.candidate_size(); ++i) { |
| const commands::Candidates::Candidate &candidate = |
| candidates.candidate(i); |
| if (candidate.index() == candidate_index) { |
| return i; |
| } |
| } |
| return candidates.candidate_size(); |
| } |
| } // namespace |
| |
| CandidateWindow::CandidateWindow( |
| TableLayoutInterface *table_layout, |
| TextRendererInterface *text_renderer, |
| DrawToolInterface *draw_tool, |
| GtkWrapperInterface *gtk, |
| CairoFactoryInterface *cairo_factory) |
| : GtkWindowBase(gtk), |
| table_layout_(table_layout), |
| text_renderer_(text_renderer), |
| draw_tool_(draw_tool), |
| cairo_factory_(cairo_factory) { |
| } |
| |
| bool CandidateWindow::OnPaint(GtkWidget *widget, GdkEventExpose* event) { |
| draw_tool_->Reset(cairo_factory_->CreateCairoInstance( |
| GetCanvasWidget()->window)); |
| |
| DrawBackground(); |
| DrawShortcutBackground(); |
| DrawSelectedRect(); |
| DrawCells(); |
| DrawInformationIcon(); |
| DrawVScrollBar(); |
| DrawFooter(); |
| DrawFrame(); |
| return true; |
| } |
| |
| void CandidateWindow::DrawBackground() { |
| const Rect window_rect(Point(0, 0), GetWindowSize()); |
| draw_tool_->FillRect(window_rect, kDefaultBackgroundColor); |
| } |
| |
| void CandidateWindow::DrawShortcutBackground() { |
| if (table_layout_->number_of_columns() <= 0) { |
| return; |
| } |
| |
| const Rect first_column_rect = table_layout_->GetColumnRect(0); |
| const Rect first_row_rect = table_layout_->GetRowRect(0); |
| if (first_column_rect.IsRectEmpty() || first_row_rect.IsRectEmpty()) { |
| return; |
| } |
| |
| const Rect shortcut_background_area(first_row_rect.origin, |
| first_column_rect.size); |
| draw_tool_->FillRect(shortcut_background_area, kShortcutBackgroundColor); |
| } |
| |
| void CandidateWindow::DrawSelectedRect() { |
| if (!candidates_.has_focused_index()) { |
| return; |
| } |
| |
| const int selected_row_index = GetCandidateArrayIndexByCandidateIndex( |
| candidates_, candidates_.focused_index()); |
| |
| DCHECK_GE(selected_row_index, 0); |
| |
| if (selected_row_index >= candidates_.candidate_size()) { |
| LOG(ERROR) << "focused index is invalid" << candidates_.focused_index(); |
| return; |
| } |
| |
| const Rect selected_rect = table_layout_->GetRowRect(selected_row_index); |
| draw_tool_->FillRect(selected_rect, kSelectedRowBackgroundColor); |
| draw_tool_->FrameRect(selected_rect, kSelectedRowFrameColor, 1.0); |
| } |
| |
| void CandidateWindow::DrawCells() { |
| for (size_t i = 0; i < candidates_.candidate_size(); ++i) { |
| const commands::Candidates::Candidate &candidate |
| = candidates_.candidate(i); |
| string shortcut, value, description; |
| |
| GetDisplayString(candidate, &shortcut, &value, &description); |
| |
| if (!shortcut.empty()) { |
| text_renderer_->RenderText(shortcut, |
| table_layout_->GetCellRect(i, COLUMN_SHORTCUT), |
| FontSpec::FONTSET_SHORTCUT); |
| } |
| |
| if (!value.empty()) { |
| text_renderer_->RenderText( |
| value, |
| table_layout_->GetCellRect(i, COLUMN_CANDIDATE), |
| FontSpec::FONTSET_CANDIDATE); |
| } |
| |
| if (!description.empty()) { |
| text_renderer_->RenderText( |
| description, |
| table_layout_->GetCellRect(i, COLUMN_DESCRIPTION), |
| FontSpec::FONTSET_DESCRIPTION); |
| } |
| } |
| } |
| |
| void CandidateWindow::DrawInformationIcon() { |
| for (size_t i = 0; i < candidates_.candidate_size(); ++i) { |
| if (!candidates_.candidate(i).has_information_id()) { |
| continue; |
| } |
| const Rect row_rect = table_layout_->GetRowRect(i); |
| const Rect usage_information_indicator_rect( |
| row_rect.origin.x + row_rect.size.width - 6, |
| row_rect.origin.y + 2, |
| 4, |
| row_rect.size.height - 4); |
| |
| draw_tool_->FillRect(usage_information_indicator_rect, kIndicatorColor); |
| draw_tool_->FrameRect(usage_information_indicator_rect, kIndicatorColor, 1); |
| } |
| } |
| |
| void CandidateWindow::DrawVScrollBar() { |
| // TODO(nona): implement this function |
| } |
| |
| void CandidateWindow::DrawFooterSeparator(Rect *footer_content_area) { |
| DCHECK(footer_content_area); |
| const Point dest(footer_content_area->Right(), footer_content_area->Top()); |
| draw_tool_->DrawLine(footer_content_area->origin, dest, kFrameColor, |
| kFooterSeparatorHeight); |
| // The remaining footer content area is the one after removal of above/below |
| // separation line. |
| footer_content_area->origin.y += kFooterSeparatorHeight; |
| footer_content_area->size.height -= kFooterSeparatorHeight; |
| } |
| |
| void CandidateWindow::DrawFooterIndex(Rect *footer_content_rect) { |
| DCHECK(footer_content_rect); |
| if (!candidates_.has_footer() || |
| !candidates_.footer().index_visible() || |
| !candidates_.has_focused_index()) { |
| return; |
| } |
| |
| const string index_guide_string = GetIndexGuideString(candidates_); |
| const Size index_guide_size = text_renderer_->GetPixelSize( |
| FontSpec::FONTSET_FOOTER_INDEX, index_guide_string); |
| // Render as right-aligned. |
| Rect index_rect(footer_content_rect->Right() - index_guide_size.width, |
| footer_content_rect->Top(), |
| index_guide_size.width, |
| footer_content_rect->Height()); |
| text_renderer_->RenderText(index_guide_string, |
| index_rect, |
| FontSpec::FONTSET_FOOTER_INDEX); |
| footer_content_rect->size.width -= index_guide_size.width; |
| } |
| |
| void CandidateWindow::DrawFooterLabel(const Rect &footer_content_rect) { |
| if (footer_content_rect.IsRectEmpty()) { |
| return; |
| } |
| if (candidates_.footer().has_label()) { |
| text_renderer_->RenderText(candidates_.footer().label(), |
| footer_content_rect, |
| FontSpec::FONTSET_FOOTER_LABEL); |
| } else if (candidates_.footer().has_sub_label()) { |
| text_renderer_->RenderText(candidates_.footer().sub_label(), |
| footer_content_rect, |
| FontSpec::FONTSET_FOOTER_SUBLABEL); |
| } |
| } |
| |
| void CandidateWindow::DrawLogo(Rect *footer_content_rect) { |
| DCHECK(footer_content_rect); |
| // TODO(nona): Implement this. |
| // The current implementation is just a padding area. |
| if (candidates_.footer().logo_visible()) { |
| // The 47 pixel is same as icon width to be rendered in future. |
| footer_content_rect->size.width -= 47; |
| footer_content_rect->origin.x += 47; |
| } |
| } |
| |
| void CandidateWindow::DrawFooter() { |
| if (!candidates_.has_footer()) { |
| return; |
| } |
| |
| Rect footer_content_area = table_layout_->GetFooterRect(); |
| if (footer_content_area.IsRectEmpty()) { |
| return; |
| } |
| |
| DrawFooterSeparator(&footer_content_area); |
| DrawLogo(&footer_content_area); |
| DrawFooterIndex(&footer_content_area); |
| DrawFooterLabel(footer_content_area); |
| } |
| |
| void CandidateWindow::DrawFrame() { |
| const Rect client_rect(Point(0, 0), table_layout_->GetTotalSize()); |
| draw_tool_->FrameRect(client_rect, kFrameColor, 1); |
| } |
| |
| void CandidateWindow::Initialize() { |
| text_renderer_->Initialize(GetCanvasWidget()->window); |
| } |
| |
| void CandidateWindow::UpdateScrollBarSize() { |
| // TODO(nona) : Implement this. |
| } |
| |
| void CandidateWindow::UpdateFooterSize() { |
| if (!candidates_.has_footer()) { |
| return; |
| } |
| |
| Size footer_size(0, 0); |
| |
| if (candidates_.footer().has_label()) { |
| const Size label_string_size = text_renderer_->GetPixelSize( |
| FontSpec::FONTSET_FOOTER_LABEL, |
| candidates_.footer().label()); |
| footer_size.width += label_string_size.width; |
| footer_size.height = max(footer_size.height, label_string_size.height); |
| } else if (candidates_.footer().has_sub_label()) { |
| const Size label_string_size = text_renderer_->GetPixelSize( |
| FontSpec::FONTSET_FOOTER_LABEL, |
| candidates_.footer().sub_label()); |
| footer_size.width += label_string_size.width; |
| footer_size.height = max(footer_size.height, label_string_size.height); |
| } |
| |
| if (candidates_.footer().index_visible()) { |
| const Size index_guide_size = text_renderer_->GetPixelSize( |
| FontSpec::FONTSET_FOOTER_INDEX, |
| GetIndexGuideString(candidates_)); |
| footer_size.width += index_guide_size.width; |
| footer_size.height = max(footer_size.height, index_guide_size.height); |
| } |
| |
| if (candidates_.candidate_size() < candidates_.size()) { |
| const Size minimum_size = text_renderer_->GetPixelSize( |
| FontSpec::FONTSET_CANDIDATE, |
| kMinimumCandidateAndDescriptionWidthAsString); |
| table_layout_->EnsureColumnsWidth( |
| COLUMN_CANDIDATE, COLUMN_DESCRIPTION, minimum_size.width); |
| } |
| |
| if (candidates_.footer().logo_visible()) { |
| // TODO(nona): Implement logo sizing. |
| footer_size.width += 47; |
| } |
| footer_size.height += kFooterSeparatorHeight; |
| |
| table_layout_->EnsureFooterSize(footer_size); |
| } |
| |
| void CandidateWindow::UpdateGap1Size() { |
| const Size gap1_size = |
| text_renderer_->GetPixelSize(FontSpec::FONTSET_CANDIDATE, " "); |
| table_layout_->EnsureCellSize(COLUMN_GAP1, gap1_size); |
| } |
| |
| void CandidateWindow::UpdateCandidatesSize(bool *has_description) { |
| DCHECK(has_description); |
| *has_description = false; |
| for (size_t i = 0; i < candidates_.candidate_size(); ++i) { |
| const commands::Candidates::Candidate &candidate = |
| candidates_.candidate(i); |
| |
| string shortcut, description, candidate_string; |
| GetDisplayString(candidate, &shortcut, &candidate_string, &description); |
| |
| if (!shortcut.empty()) { |
| string text; |
| text.push_back(' '); |
| text.append(shortcut); |
| text.push_back(' '); |
| const Size rendering_size = text_renderer_->GetPixelSize( |
| FontSpec::FONTSET_SHORTCUT, text); |
| table_layout_->EnsureCellSize(COLUMN_SHORTCUT, rendering_size); |
| } |
| |
| if (!candidate_string.empty()) { |
| const Size rendering_size = text_renderer_->GetPixelSize( |
| FontSpec::FONTSET_CANDIDATE, candidate_string); |
| table_layout_->EnsureCellSize(COLUMN_CANDIDATE, rendering_size); |
| } |
| |
| if (!description.empty()) { |
| string text; |
| text.append(description); |
| text.push_back(' '); |
| const Size rendering_size = text_renderer_->GetPixelSize( |
| FontSpec::FONTSET_DESCRIPTION, text); |
| table_layout_->EnsureCellSize(COLUMN_DESCRIPTION, rendering_size); |
| *has_description = true; |
| } |
| } |
| } |
| |
| void CandidateWindow::UpdateGap2Size(bool has_description) { |
| const char *gap2_string = (has_description ? " " : " "); |
| const Size gap2_size = text_renderer_->GetPixelSize( |
| FontSpec::FONTSET_CANDIDATE, gap2_string); |
| table_layout_->EnsureCellSize(COLUMN_GAP2, gap2_size); |
| } |
| |
| Size CandidateWindow::Update(const commands::Candidates &candidates) { |
| DCHECK( |
| (candidates_.category() == commands::CONVERSION) || |
| (candidates_.category() == commands::PREDICTION) || |
| (candidates_.category() == commands::TRANSLITERATION) || |
| (candidates_.category() == commands::SUGGESTION) || |
| (candidates_.category() == commands::USAGE)) |
| << "Unknown candidate category" << candidates_.category(); |
| |
| candidates_.CopyFrom(candidates); |
| |
| table_layout_->Initialize(candidates_.candidate_size(), NUMBER_OF_COLUMNS); |
| table_layout_->SetWindowBorder(kWindowBorder); |
| table_layout_->SetRowRectPadding(kRowRectPadding); |
| |
| UpdateScrollBarSize(); |
| UpdateFooterSize(); |
| UpdateGap1Size(); |
| bool has_description; |
| UpdateCandidatesSize(&has_description); |
| UpdateGap2Size(has_description); |
| |
| table_layout_->FreezeLayout(); |
| Resize(table_layout_->GetTotalSize()); |
| Redraw(); |
| return table_layout_->GetTotalSize(); |
| } |
| |
| void CandidateWindow::GetDisplayString( |
| const commands::Candidates::Candidate &candidate, |
| string *shortcut, |
| string *value, |
| string *description) { |
| DCHECK(shortcut); |
| DCHECK(value); |
| DCHECK(description); |
| |
| shortcut->clear(); |
| value->clear(); |
| description->clear(); |
| |
| if (!candidate.has_value()) { |
| return; |
| } |
| value->assign(candidate.value()); |
| |
| if (!candidate.has_annotation()) { |
| return; |
| } |
| |
| const commands::Annotation &annotation = candidate.annotation(); |
| |
| if (annotation.has_shortcut()) { |
| shortcut->assign(annotation.shortcut()); |
| } |
| |
| if (annotation.has_description()) { |
| description->assign(annotation.description()); |
| } |
| |
| if (annotation.has_prefix()) { |
| value->assign(annotation.prefix()); |
| value->append(candidate.value()); |
| } |
| |
| if (annotation.has_suffix()) { |
| value->append(annotation.suffix()); |
| } |
| } |
| |
| Rect CandidateWindow::GetCandidateColumnInClientCord() const { |
| DCHECK(table_layout_->IsLayoutFrozen()) << "Table layout is not frozen."; |
| return table_layout_->GetCellRect(0, COLUMN_CANDIDATE); |
| } |
| |
| void CandidateWindow::OnMouseLeftUp(const Point &pos) { |
| if (send_command_interface_ == NULL) { |
| LOG(ERROR) << "send_command_interface_ is NULL"; |
| return; |
| } |
| |
| const int kSelectedIdx = GetSelectedRowIndex(pos); |
| if (kSelectedIdx == -1) { // out of range |
| return; |
| } |
| |
| const commands::Candidates::Candidate &candidate = |
| candidates_.candidate(kSelectedIdx); |
| commands::SessionCommand command; |
| command.set_type(commands::SessionCommand::SELECT_CANDIDATE); |
| command.set_id(candidate.id()); |
| commands::Output output; |
| send_command_interface_->SendCommand(command, &output); |
| return; |
| } |
| |
| int CandidateWindow::GetSelectedRowIndex(const Point &pos) const { |
| for (size_t i = 0; i < candidates_.candidate_size(); ++i) { |
| const Rect rect = table_layout_->GetRowRect(i); |
| |
| if (rect.PtrInRect(pos)) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| bool CandidateWindow::SetSendCommandInterface( |
| client::SendCommandInterface *send_command_interface) { |
| send_command_interface_ = send_command_interface; |
| return true; |
| } |
| |
| void CandidateWindow::ReloadFontConfig(const string &font_description) { |
| text_renderer_->ReloadFontConfig(font_description); |
| } |
| |
| } // namespace gtk |
| } // namespace renderer |
| } // namespace mozc |