| // 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/win32/candidate_window.h" |
| |
| #include <windows.h> |
| |
| #include <sstream> |
| |
| #include "base/coordinates.h" |
| #include "base/logging.h" |
| #include "base/util.h" |
| #include "client/client_interface.h" |
| #include "renderer/renderer_command.pb.h" |
| #include "renderer/renderer_style_handler.h" |
| #include "renderer/table_layout.h" |
| #include "renderer/win32/text_renderer.h" |
| #include "renderer/win_resource.h" |
| |
| namespace mozc { |
| namespace renderer { |
| namespace win32 { |
| |
| using WTL::CBitmap; |
| using WTL::CDC; |
| using WTL::CDCHandle; |
| using WTL::CMemoryDC; |
| using WTL::CPaintDC; |
| using WTL::CPenHandle; |
| using WTL::CPoint; |
| using WTL::CRect; |
| using WTL::CSize; |
| |
| namespace { |
| |
| // 96 DPI is the default DPI in Windows. |
| const int kDefaultDPI = 96; |
| |
| // layout size constants in pixel unit in the default DPI. |
| const int kIndicatorWidthInDefaultDPI = 4; |
| |
| // DPI-invariant layout size constants in pixel unit. |
| const int kWindowBorder = 1; |
| const int kFooterSeparatorHeight = 1; |
| const int kRowRectPadding = 1; |
| |
| // usage type for each column. |
| enum COLUMN_TYPE { |
| COLUMN_SHORTCUT = 0, // show shortcut key |
| COLUMN_GAP1, // padding region |
| COLUMN_CANDIDATE, // show candidate string |
| COLUMN_GAP2, // padding region |
| COLUMN_DESCRIPTION, // show description message |
| NUMBER_OF_COLUMNS, // number of columns. (this item should be last) |
| }; |
| |
| // "そのほかの文字種" |
| const char kMinimumCandidateAndDescriptionWidthAsString[] = |
| "\xe3\x81\x9d\xe3\x81\xae\xe3\x81\xbb\xe3\x81\x8b\xe3\x81\xae\xe6\x96\x87" |
| "\xe5\xad\x97\xe7\xa8\xae"; |
| |
| // Color scheme |
| const COLORREF kFrameColor = RGB(0x96, 0x96, 0x96); |
| const COLORREF kShortcutBackgroundColor = RGB(0xf3, 0xf4, 0xff); |
| const COLORREF kSelectedRowBackgroundColor = RGB(0xd1, 0xea, 0xff); |
| const COLORREF kDefaultBackgroundColor = RGB(0xff, 0xff, 0xff); |
| const COLORREF kSelectedRowFrameColor = RGB(0x7f, 0xac, 0xdd); |
| const COLORREF kIndicatorBackgroundColor = RGB(0xe0, 0xe0, 0xe0); |
| const COLORREF kIndicatorColor = RGB(0x75, 0x90, 0xb8); |
| const COLORREF kFooterTopColor = RGB(0xff, 0xff, 0xff); |
| const COLORREF kFooterBottomColor = RGB(0xee, 0xee, 0xee); |
| |
| // ------------------------------------------------------------------------ |
| // Utility functions |
| // ------------------------------------------------------------------------ |
| WTL::CRect ToCRect(const Rect &rect) { |
| return WTL::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 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(); |
| } |
| |
| // Returns a text which includes the selected index number and |
| // the number of the candidates. For example, "13/123" means |
| // the selected index is "13" (in 1-origin) and the number of |
| // candidates is "123" |
| // Returns an empty string if index string should not be displayed. |
| 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; |
| footer_string << focused_index + 1 |
| << "/" |
| << total_items |
| << " "; // for padding. |
| |
| return footer_string.str(); |
| } |
| |
| // 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 commands::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); |
| } |
| |
| // Retrieves the display string from the specified candidate for the specified |
| // column and returns it. |
| wstring GetDisplayStringByColumn( |
| const commands::Candidates::Candidate &candidate, |
| COLUMN_TYPE column_type) { |
| wstring display_string; |
| |
| switch (column_type) { |
| case COLUMN_SHORTCUT: |
| if (candidate.has_annotation()) { |
| const commands::Annotation &annotation = candidate.annotation(); |
| if (annotation.has_shortcut()) { |
| mozc::Util::UTF8ToWide(annotation.shortcut().c_str(), |
| &display_string); |
| } |
| } |
| break; |
| case COLUMN_CANDIDATE: |
| if (candidate.has_value()) { |
| mozc::Util::UTF8ToWide(candidate.value().c_str(), &display_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); |
| display_string = annotation_prefix + display_string; |
| } |
| if (annotation.has_suffix()) { |
| wstring annotation_suffix; |
| mozc::Util::UTF8ToWide(annotation.suffix().c_str(), |
| &annotation_suffix); |
| display_string += annotation_suffix; |
| } |
| } |
| break; |
| case COLUMN_DESCRIPTION: |
| if (candidate.has_annotation()) { |
| const commands::Annotation &annotation = candidate.annotation(); |
| if (annotation.has_description()) { |
| mozc::Util::UTF8ToWide(annotation.description().c_str(), |
| &display_string); |
| } |
| } |
| break; |
| default: |
| LOG(ERROR) << "Unknown column type: " << column_type; |
| break; |
| } |
| |
| return display_string; |
| } |
| |
| // Loads a DIB from a Win32 resource in the specified module and returns its |
| // handle. This function will fail if you try to load a top-down bitmap in |
| // Windows XP. |
| // Returns nullptr if failed to load the image. |
| // Caller must delete the object if this function returns non-null value. |
| HBITMAP LoadBitmapFromResource(HMODULE module, int resource_id) { |
| // We can use LR_CREATEDIBSECTION to load a 32-bpp bitmap. |
| // You cannot load a a top-down DIB with LoadImage in Windows XP. |
| // http://b/2076264 |
| return reinterpret_cast<HBITMAP>( |
| ::LoadImage(module, MAKEINTRESOURCE(resource_id), IMAGE_BITMAP, |
| 0, 0, LR_CREATEDIBSECTION)); |
| } |
| |
| } // namespace |
| |
| // ------------------------------------------------------------------------ |
| // CandidateWindow |
| // ------------------------------------------------------------------------ |
| |
| CandidateWindow::CandidateWindow() |
| : candidates_(new commands::Candidates), |
| indicator_width_(0), |
| footer_logo_display_size_(0, 0), |
| metrics_changed_(false), |
| mouse_moving_(true), |
| text_renderer_(TextRenderer::Create()), |
| table_layout_(new TableLayout), |
| send_command_interface_(nullptr) { |
| double scale_factor_x = 1.0; |
| double scale_factor_y = 1.0; |
| RendererStyleHandler::GetDPIScalingFactor(&scale_factor_x, |
| &scale_factor_y); |
| double image_scale_factor = 1.0; |
| if (scale_factor_x < 1.125 || scale_factor_y < 1.125) { |
| footer_logo_.Attach(LoadBitmapFromResource( |
| ::GetModuleHandle(nullptr), IDB_FOOTER_LOGO_COLOR_100)); |
| image_scale_factor = 1.0; |
| } else if (scale_factor_x < 1.375 || scale_factor_y < 1.375) { |
| footer_logo_.Attach(LoadBitmapFromResource( |
| ::GetModuleHandle(nullptr), IDB_FOOTER_LOGO_COLOR_125)); |
| image_scale_factor = 1.25; |
| } else if (scale_factor_x < 1.75 || scale_factor_y < 1.75) { |
| footer_logo_.Attach(LoadBitmapFromResource( |
| ::GetModuleHandle(nullptr), IDB_FOOTER_LOGO_COLOR_150)); |
| image_scale_factor = 1.5; |
| } else { |
| footer_logo_.Attach(LoadBitmapFromResource( |
| ::GetModuleHandle(nullptr), IDB_FOOTER_LOGO_COLOR_200)); |
| image_scale_factor = 2.0; |
| } |
| |
| // If DPI is not default value, re-calculate the size based on the DPI. |
| if (!footer_logo_.IsNull()) { |
| CSize size; |
| footer_logo_.GetSize(size); |
| size.cx *= (scale_factor_x / image_scale_factor); |
| size.cy *= (scale_factor_y / image_scale_factor); |
| footer_logo_display_size_ = Size(size.cx, size.cy); |
| } |
| |
| indicator_width_ = kIndicatorWidthInDefaultDPI * scale_factor_x; |
| } |
| |
| CandidateWindow::~CandidateWindow() {} |
| |
| LRESULT CandidateWindow::OnCreate(LPCREATESTRUCT create_struct) { |
| EnableOrDisableWindowForWorkaround(); |
| return 0; |
| } |
| |
| void CandidateWindow::EnableOrDisableWindowForWorkaround() { |
| // Disable the window if SPI_GETACTIVEWINDOWTRACKING is enabled. |
| // See b/2317702 for details. |
| // TODO(yukawa): Support mouse operations before we add a GUI feature which |
| // requires UI interaction by mouse and/or touch. (b/2954874) |
| BOOL is_tracking_enabled = FALSE; |
| if (::SystemParametersInfo(SPI_GETACTIVEWINDOWTRACKING, |
| 0, &is_tracking_enabled, 0)) { |
| EnableWindow(!is_tracking_enabled); |
| } |
| } |
| |
| void CandidateWindow::OnDestroy() { |
| // PostQuitMessage may stop the message loop even though other |
| // windows are not closed. WindowManager should close these windows |
| // before process termination. |
| ::PostQuitMessage(0); |
| } |
| |
| BOOL CandidateWindow::OnEraseBkgnd(CDCHandle dc) { |
| // We do not have to erase background |
| // because all pixels in client area will be drawn in the DoPaint method. |
| return TRUE; |
| } |
| |
| void CandidateWindow::OnGetMinMaxInfo(MINMAXINFO *min_max_info) { |
| // Do not restrict the window size in case the candidate window must be |
| // very small size. |
| min_max_info->ptMinTrackSize.x = 1; |
| min_max_info->ptMinTrackSize.y = 1; |
| SetMsgHandled(TRUE); |
| } |
| |
| void CandidateWindow::HandleMouseEvent( |
| UINT nFlags, const WTL::CPoint &point, bool close_candidatewindow) { |
| if (send_command_interface_ == nullptr) { |
| LOG(ERROR) << "send_command_interface_ is nullptr"; |
| return; |
| } |
| |
| const int focused_array_index = GetFocusedArrayIndex(*candidates_); |
| |
| for (size_t i = 0; i < candidates_->candidate_size(); ++i) { |
| const commands::Candidates::Candidate &candidate |
| = candidates_->candidate(i); |
| |
| const CRect rect = ToCRect(table_layout_->GetRowRect(i)); |
| if (rect.PtInRect(point)) { |
| commands::SessionCommand command; |
| if (close_candidatewindow) { |
| command.set_type(commands::SessionCommand::SELECT_CANDIDATE); |
| } else { |
| command.set_type(commands::SessionCommand::HIGHLIGHT_CANDIDATE); |
| } |
| command.set_id(candidate.id()); |
| commands::Output output; |
| send_command_interface_->SendCommand(command, &output); |
| return; |
| } |
| } |
| } |
| |
| void CandidateWindow::OnLButtonDown(UINT nFlags, CPoint point) { |
| HandleMouseEvent(nFlags, point, false); |
| } |
| |
| void CandidateWindow::OnLButtonUp(UINT nFlags, CPoint point) { |
| HandleMouseEvent(nFlags, point, true); |
| } |
| |
| void CandidateWindow::OnMouseMove(UINT nFlags, WTL::CPoint point) { |
| // Window manager sometimes generates WM_MOUSEMOVE message when the contents |
| // under the mouse cursor has been changed (e.g. the window is moved) so that |
| // the mouse handler can change its cursor image based on the contents to |
| // which the cursor is newly pointing. In order to filter these pseudo |
| // WM_MOUSEMOVE out, |mouse_moving_| is checked here. |
| // See http://blogs.msdn.com/b/oldnewthing/archive/2003/10/01/55108.aspx for |
| // details about such an artificial WM_MOUSEMOVE. See also b/3104996. |
| if (!mouse_moving_) { |
| return; |
| } |
| if ((nFlags & MK_LBUTTON) != MK_LBUTTON) { |
| return; |
| } |
| |
| HandleMouseEvent(nFlags, point, false); |
| } |
| |
| void CandidateWindow::OnPaint(CDCHandle dc) { |
| CRect client_rect; |
| this->GetClientRect(&client_rect); |
| |
| if (dc != nullptr) { |
| CMemoryDC memdc(dc, client_rect); |
| DoPaint(memdc.m_hDC); |
| } else { |
| CPaintDC paint_dc(this->m_hWnd); |
| { // Create a copy of |paint_dc| and render the candidate strings in it. |
| // The image rendered to this |memdc| is to be copied into the original |
| // |paint_dc| in its destructor. So, we don't have to explicitly call |
| // any functions that copy this |memdc| to the |paint_dc| but putting |
| // the following code into a local block. |
| CMemoryDC memdc(paint_dc, client_rect); |
| DoPaint(memdc.m_hDC); |
| } |
| } |
| } |
| |
| void CandidateWindow::OnPrintClient(CDCHandle dc, UINT uFlags) { |
| OnPaint(dc); |
| } |
| |
| void CandidateWindow::DoPaint(CDCHandle dc) { |
| switch (candidates_->category()) { |
| case commands::CONVERSION: |
| case commands::PREDICTION: |
| case commands::TRANSLITERATION: |
| case commands::SUGGESTION: |
| case commands::USAGE: |
| break; |
| default: |
| LOG(INFO) << "Unknown candidates category: " << candidates_->category(); |
| return; |
| } |
| |
| if (!table_layout_->IsLayoutFrozen()) { |
| LOG(WARNING) << "Table layout is not frozen."; |
| return; |
| } |
| |
| dc.SetBkMode(TRANSPARENT); |
| |
| DrawBackground(dc); |
| DrawShortcutBackground(dc); |
| DrawSelectedRect(dc); |
| DrawCells(dc); |
| DrawInformationIcon(dc); |
| DrawVScrollBar(dc); |
| DrawFooter(dc); |
| DrawFrame(dc); |
| } |
| |
| void CandidateWindow::OnSettingChange(UINT uFlags, LPCTSTR /*lpszSection*/) { |
| // Since TextRenderer uses dialog font to render, |
| // we monitor font-related parameters to know when the font style is changed. |
| switch (uFlags) { |
| case 0x1049: // = SPI_SETCLEARTYPE |
| case SPI_SETFONTSMOOTHING: |
| case SPI_SETFONTSMOOTHINGCONTRAST: |
| case SPI_SETFONTSMOOTHINGORIENTATION: |
| case SPI_SETFONTSMOOTHINGTYPE: |
| case SPI_SETNONCLIENTMETRICS: |
| metrics_changed_ = true; |
| break; |
| case SPI_SETACTIVEWINDOWTRACKING: |
| EnableOrDisableWindowForWorkaround(); |
| default: |
| // We ignore other changes. |
| break; |
| } |
| } |
| |
| void CandidateWindow::UpdateLayout(const commands::Candidates &candidates) { |
| candidates_->CopyFrom(candidates); |
| |
| // If we detect any change of font parameters, update text renderer |
| if (metrics_changed_) { |
| text_renderer_->OnThemeChanged(); |
| metrics_changed_ = false; |
| } |
| |
| switch (candidates_->category()) { |
| case commands::CONVERSION: |
| case commands::PREDICTION: |
| case commands::TRANSLITERATION: |
| case commands::SUGGESTION: |
| case commands::USAGE: |
| break; |
| default: |
| LOG(INFO) << "Unknown candidates category: " << candidates_->category(); |
| return; |
| } |
| |
| table_layout_->Initialize(candidates_->candidate_size(), NUMBER_OF_COLUMNS); |
| |
| table_layout_->SetWindowBorder(kWindowBorder); |
| |
| // Add a vertical scroll bar if candidate list consists of more than |
| // one page. |
| if (candidates_->candidate_size() < candidates_->size()) { |
| table_layout_->SetVScrollBar(indicator_width_); |
| } |
| |
| if (candidates_->has_footer()) { |
| Size footer_size(0, 0); |
| |
| // Calculate the size to display a label string. |
| if (candidates_->footer().has_label()) { |
| wstring footer_label; |
| mozc::Util::UTF8ToWide(candidates_->footer().label().c_str(), |
| &footer_label); |
| const Size label_string_size = text_renderer_->MeasureString( |
| TextRenderer::FONTSET_FOOTER_LABEL, |
| L" " + footer_label + L" "); |
| 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()) { |
| // Currently the sub label will not be shown unless (main) label is |
| // absent. |
| // TODO(yukawa): Refactor the layout system for the footer. |
| wstring footer_sub_label; |
| mozc::Util::UTF8ToWide(candidates_->footer().sub_label().c_str(), |
| &footer_sub_label); |
| const Size label_string_size = text_renderer_->MeasureString( |
| TextRenderer::FONTSET_FOOTER_SUBLABEL, |
| L" " + footer_sub_label + L" "); |
| footer_size.width += label_string_size.width; |
| footer_size.height = max(footer_size.height, label_string_size.height); |
| } |
| |
| // Calculate the size to display a index string. |
| if (candidates_->footer().index_visible()) { |
| wstring index_guide_string; |
| mozc::Util::UTF8ToWide( |
| GetIndexGuideString(*candidates_).c_str(), &index_guide_string); |
| const Size index_guide_size = text_renderer_->MeasureString( |
| TextRenderer::FONTSET_FOOTER_INDEX, index_guide_string); |
| footer_size.width += index_guide_size.width; |
| footer_size.height = max(footer_size.height, index_guide_size.height); |
| } |
| |
| // Calculate the size to display a Footer logo. |
| if (!footer_logo_.IsNull()) { |
| if (candidates_->footer().logo_visible()) { |
| footer_size.width += footer_logo_display_size_.width; |
| footer_size.height = max(footer_size.height, |
| footer_logo_display_size_.height); |
| } else if (footer_size.height > 0) { |
| // Ensure the footer height is greater than the Footer logo height |
| // even if the Footer logo is absent. This hack prevents the footer |
| // from changing its height too frequently. |
| footer_size.height = max(footer_size.height, |
| footer_logo_display_size_.height); |
| } |
| } |
| |
| // Ensure minimum columns width if candidate list consists of more than |
| // one page. |
| if (candidates_->candidate_size() < candidates_->size()) { |
| // We use FONTSET_CANDIDATE for calculating the minimum width. |
| wstring minimum_width_as_wstring; |
| mozc::Util::UTF8ToWide( |
| kMinimumCandidateAndDescriptionWidthAsString, |
| &minimum_width_as_wstring); |
| const Size minimum_size = text_renderer_->MeasureString( |
| TextRenderer::FONTSET_CANDIDATE, minimum_width_as_wstring.c_str()); |
| table_layout_->EnsureColumnsWidth( |
| COLUMN_CANDIDATE, COLUMN_DESCRIPTION, minimum_size.width); |
| } |
| |
| // Add separator height |
| footer_size.height += kFooterSeparatorHeight; |
| |
| table_layout_->EnsureFooterSize(footer_size); |
| } |
| |
| 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); |
| |
| bool description_found = false; |
| |
| // calculate table size. |
| for (size_t i = 0; i < candidates_->candidate_size(); ++i) { |
| const commands::Candidates::Candidate &candidate = |
| candidates_->candidate(i); |
| const wstring shortcut = |
| GetDisplayStringByColumn(candidate, COLUMN_SHORTCUT); |
| const wstring description = |
| GetDisplayStringByColumn(candidate, COLUMN_DESCRIPTION); |
| const wstring candidate_string = |
| GetDisplayStringByColumn(candidate, COLUMN_CANDIDATE); |
| |
| if (!shortcut.empty()) { |
| wstring text; |
| text.push_back(L' '); // put a space for padding |
| text.append(shortcut); |
| text.push_back(L' '); // put a space for padding |
| const Size rendering_size = text_renderer_->MeasureString( |
| TextRenderer::FONTSET_SHORTCUT, text); |
| table_layout_->EnsureCellSize(COLUMN_SHORTCUT, rendering_size); |
| } |
| |
| if (!candidate_string.empty()) { |
| wstring text; |
| text.append(candidate_string); |
| |
| const Size rendering_size = text_renderer_->MeasureString( |
| TextRenderer::FONTSET_CANDIDATE, text); |
| table_layout_->EnsureCellSize(COLUMN_CANDIDATE, rendering_size); |
| } |
| |
| if (!description.empty()) { |
| wstring text; |
| text.append(description); |
| text.push_back(L' '); // put a space for padding |
| const Size rendering_size = text_renderer_->MeasureString( |
| TextRenderer::FONTSET_DESCRIPTION, text); |
| table_layout_->EnsureCellSize(COLUMN_DESCRIPTION, rendering_size); |
| |
| description_found = true; |
| } |
| } |
| |
| // Put a padding in COLUMN_GAP2. |
| // We use wide padding if there is any description column. |
| const wchar_t *gap2_string = (description_found ? L" " : L" "); |
| const Size gap2_size = text_renderer_->MeasureString( |
| TextRenderer::FONTSET_CANDIDATE, gap2_string); |
| table_layout_->EnsureCellSize(COLUMN_GAP2, gap2_size); |
| |
| table_layout_->FreezeLayout(); |
| } |
| |
| void CandidateWindow::SetSendCommandInterface( |
| client::SendCommandInterface *send_command_interface) { |
| send_command_interface_ = send_command_interface; |
| } |
| |
| Size CandidateWindow::GetLayoutSize() const { |
| DCHECK(table_layout_->IsLayoutFrozen()) << "Table layout is not frozen."; |
| |
| return table_layout_->GetTotalSize(); |
| } |
| |
| Rect CandidateWindow::GetSelectionRectInScreenCord() const { |
| 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); |
| |
| CRect rect = ToCRect(table_layout_->GetRowRect(focused_array_index)); |
| ClientToScreen(&rect); |
| return Rect(rect.left, rect.top, rect.Width(), rect.Height()); |
| } |
| |
| return Rect(); |
| } |
| |
| Rect CandidateWindow::GetCandidateColumnInClientCord() const { |
| DCHECK(table_layout_->IsLayoutFrozen()) << "Table layout is not frozen."; |
| |
| return table_layout_->GetCellRect(0, COLUMN_CANDIDATE); |
| } |
| |
| Rect CandidateWindow::GetFirstRowInClientCord() const { |
| DCHECK(table_layout_->IsLayoutFrozen()) << "Table layout is not frozen."; |
| DCHECK_GT(table_layout_->number_of_rows(), 0) |
| << "number of rows should be positive"; |
| return table_layout_->GetRowRect(0); |
| } |
| |
| void CandidateWindow::DrawCells(CDCHandle dc) { |
| COLUMN_TYPE kColumnTypes[] = |
| {COLUMN_SHORTCUT, COLUMN_CANDIDATE, COLUMN_DESCRIPTION}; |
| TextRenderer::FONT_TYPE kFontTypes[] = |
| {TextRenderer::FONTSET_SHORTCUT, TextRenderer::FONTSET_CANDIDATE, |
| TextRenderer::FONTSET_DESCRIPTION}; |
| |
| DCHECK_EQ(arraysize(kColumnTypes), arraysize(kFontTypes)); |
| for (size_t type_index = 0; |
| type_index < arraysize(kColumnTypes); ++type_index) { |
| const COLUMN_TYPE column_type = kColumnTypes[type_index]; |
| const TextRenderer::FONT_TYPE font_type = kFontTypes[type_index]; |
| |
| vector<TextRenderingInfo> display_list; |
| for (size_t i = 0; i < candidates_->candidate_size(); ++i) { |
| const commands::Candidates::Candidate &candidate = |
| candidates_->candidate(i); |
| const wstring display_string = |
| GetDisplayStringByColumn(candidate, column_type); |
| const Rect text_rect = |
| table_layout_->GetCellRect(i, column_type); |
| display_list.push_back(TextRenderingInfo(display_string, text_rect)); |
| } |
| text_renderer_->RenderTextList(dc, display_list, font_type); |
| } |
| } |
| |
| void CandidateWindow::DrawVScrollBar(CDCHandle dc) { |
| 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 mozc::Rect &indicator_rect = |
| table_layout_->GetVScrollIndicatorRect( |
| begin_index, end_index, candidates_total); |
| |
| const CRect indicator_crect = ToCRect(indicator_rect); |
| dc.FillSolidRect(&indicator_crect, kIndicatorColor); |
| } |
| } |
| |
| void CandidateWindow::DrawShortcutBackground(CDCHandle dc) { |
| if (table_layout_->number_of_columns() > 0) { |
| Rect shortcut_colmun_rect = table_layout_->GetColumnRect(0); |
| if (!shortcut_colmun_rect.IsRectEmpty()) { |
| // Due to the mismatch of the implementation of the TableLayout class |
| // and the design requiement, we have to *fix* the width and origin |
| // of the rectangle. |
| // If you remove this *fix*, an empty region appears between the |
| // left window border and the colored region of the shortcut column. |
| const Rect row_rect = table_layout_->GetRowRect(0); |
| const int width = shortcut_colmun_rect.Right() - row_rect.Left(); |
| shortcut_colmun_rect.origin.x = row_rect.Left(); |
| shortcut_colmun_rect.size.width = width; |
| const CRect shortcut_colmun_crect = ToCRect(shortcut_colmun_rect); |
| dc.FillSolidRect(&shortcut_colmun_crect, kShortcutBackgroundColor); |
| } |
| } |
| } |
| |
| void CandidateWindow::DrawFooter(CDCHandle dc) { |
| const Rect &footer_rect = table_layout_->GetFooterRect(); |
| if (!candidates_->has_footer() || footer_rect.IsRectEmpty()) { |
| return; |
| } |
| |
| const COLORREF kFooterSeparatorColors[kFooterSeparatorHeight] = { |
| kFrameColor }; |
| |
| // DC pen is available in Windows 2000 and later. |
| CPenHandle prev_pen(dc.SelectPen(static_cast<HPEN>(GetStockObject(DC_PEN)))); |
| for (size_t i = 0, y = footer_rect.Top(); |
| i < kFooterSeparatorHeight; y++, i++) { |
| if (i < ARRAYSIZE(kFooterSeparatorColors)) { |
| dc.SetDCPenColor(kFooterSeparatorColors[i]); |
| dc.MoveTo(footer_rect.Left(), y, nullptr); |
| dc.LineTo(footer_rect.Right(), y); |
| } |
| } |
| dc.SelectPen(prev_pen); |
| |
| const Rect footer_content_rect( |
| footer_rect.Left(), |
| footer_rect.Top() + kFooterSeparatorHeight, |
| footer_rect.Width(), |
| footer_rect.Height() - kFooterSeparatorHeight); |
| |
| // Draw gradient rect in the footer area |
| { |
| TRIVERTEX vertices[] = { |
| { footer_content_rect.Left(), |
| footer_content_rect.Top(), |
| GetRValue(kFooterTopColor) << 8, |
| GetGValue(kFooterTopColor) << 8, |
| GetBValue(kFooterTopColor) << 8, |
| 0xff00 }, |
| { footer_content_rect.Right(), |
| footer_content_rect.Bottom(), |
| GetRValue(kFooterBottomColor) << 8, |
| GetGValue(kFooterBottomColor) << 8, |
| GetBValue(kFooterBottomColor) << 8, |
| 0xff00 } |
| }; |
| GRADIENT_RECT indices[] = { {0, 1} }; |
| dc.GradientFill(&vertices[0], ARRAYSIZE(vertices), |
| &indices[0], ARRAYSIZE(indices), GRADIENT_FILL_RECT_V); |
| } |
| |
| int left_used = 0; |
| |
| if (candidates_->footer().logo_visible() && !footer_logo_.IsNull()) { |
| const int top_offset = |
| (footer_content_rect.Height() - footer_logo_display_size_.height) / 2; |
| CDC src_dc; |
| src_dc.CreateCompatibleDC(dc); |
| const HBITMAP old_bitmap = src_dc.SelectBitmap(footer_logo_); |
| |
| CSize src_size; |
| footer_logo_.GetSize(src_size); |
| |
| // NOTE: AC_SRC_ALPHA requires PBGRA (pre-multiplied alpha) DIB. |
| const BLENDFUNCTION bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; |
| dc.AlphaBlend(footer_content_rect.Left(), |
| footer_content_rect.Top() + top_offset, |
| footer_logo_display_size_.width, |
| footer_logo_display_size_.height, |
| src_dc, 0, 0, src_size.cx, src_size.cy, bf); |
| |
| src_dc.SelectBitmap(old_bitmap); |
| left_used = footer_content_rect.Left() + footer_logo_display_size_.width; |
| } |
| |
| int right_used = 0; |
| if (candidates_->footer().index_visible()) { |
| wstring index_guide_string; |
| mozc::Util::UTF8ToWide( |
| GetIndexGuideString(*candidates_).c_str(), &index_guide_string); |
| const Size index_guide_size = text_renderer_->MeasureString( |
| TextRenderer::FONTSET_FOOTER_INDEX, index_guide_string); |
| const 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(dc, index_guide_string, index_rect, |
| TextRenderer::FONTSET_FOOTER_INDEX); |
| right_used = index_guide_size.width; |
| } |
| |
| if (candidates_->footer().has_label()) { |
| const Rect label_rect(left_used, |
| footer_content_rect.Top(), |
| footer_content_rect.Width() - left_used - right_used, |
| footer_content_rect.Height()); |
| wstring footer_label; |
| mozc::Util::UTF8ToWide(candidates_->footer().label().c_str(), |
| &footer_label); |
| text_renderer_->RenderText(dc, |
| L" " + footer_label + L" ", |
| label_rect, |
| TextRenderer::FONTSET_FOOTER_LABEL); |
| } else if (candidates_->footer().has_sub_label()) { |
| wstring footer_sub_label; |
| mozc::Util::UTF8ToWide(candidates_->footer().sub_label().c_str(), |
| &footer_sub_label); |
| const Rect label_rect(left_used, |
| footer_content_rect.Top(), |
| footer_content_rect.Width() - left_used - right_used, |
| footer_content_rect.Height()); |
| const wstring text = L" " + footer_sub_label + L" "; |
| text_renderer_->RenderText(dc, |
| text, |
| label_rect, |
| TextRenderer::FONTSET_FOOTER_SUBLABEL); |
| } |
| } |
| |
| void CandidateWindow::DrawSelectedRect(CDCHandle dc) { |
| DCHECK(table_layout_->IsLayoutFrozen()) << "Table layout is not frozen."; |
| |
| 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); |
| |
| dc.SetDCBrushColor(kSelectedRowFrameColor); |
| dc.FrameRect(&selected_rect, |
| static_cast<HBRUSH>(GetStockObject(DC_BRUSH))); |
| } |
| } |
| |
| void CandidateWindow::DrawInformationIcon(CDCHandle dc) { |
| DCHECK(table_layout_->IsLayoutFrozen()) << "Table layout is not frozen."; |
| double scale_factor_x = 1.0; |
| double scale_factor_y = 1.0; |
| RendererStyleHandler::GetDPIScalingFactor(&scale_factor_x, |
| &scale_factor_y); |
| for (size_t i = 0; i < candidates_->candidate_size(); ++i) { |
| if (candidates_->candidate(i).has_information_id()) { |
| CRect rect = ToCRect(table_layout_->GetRowRect(i)); |
| rect.left = rect.right - (6.0 * scale_factor_x); |
| rect.right = rect.right - (2.0 * scale_factor_x); |
| rect.top += (2.0 * scale_factor_y); |
| rect.bottom -= (2.0 * scale_factor_y); |
| dc.FillSolidRect(&rect, kIndicatorColor); |
| dc.SetDCBrushColor(kIndicatorColor); |
| dc.FrameRect(&rect, |
| static_cast<HBRUSH>(GetStockObject(DC_BRUSH))); |
| } |
| } |
| } |
| |
| void CandidateWindow::DrawBackground(CDCHandle dc) { |
| const Rect client_rect(Point(0, 0), table_layout_->GetTotalSize()); |
| const CRect client_crect = ToCRect(client_rect); |
| dc.FillSolidRect(&client_crect, kDefaultBackgroundColor); |
| } |
| |
| void CandidateWindow::DrawFrame(CDCHandle dc) { |
| const Rect client_rect(Point(0, 0), table_layout_->GetTotalSize()); |
| const CRect client_crect = ToCRect(client_rect); |
| |
| // DC brush is available in Windows 2000 and later. |
| dc.SetDCBrushColor(kFrameColor); |
| dc.FrameRect(&client_crect, |
| static_cast<HBRUSH>(GetStockObject(DC_BRUSH))); |
| } |
| |
| void CandidateWindow::set_mouse_moving(bool moving) { |
| mouse_moving_ = moving; |
| } |
| } // namespace win32 |
| } // namespace renderer |
| } // namespace mozc |