1 // Copyright 2010-2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 //     * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 //     * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 //     * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 
30 #include "renderer/win32/candidate_window.h"
31 
32 #include <windows.h>
33 
34 #include <sstream>
35 
36 #include "base/coordinates.h"
37 #include "base/logging.h"
38 #include "base/util.h"
39 #include "client/client_interface.h"
40 #include "protocol/renderer_command.pb.h"
41 #include "renderer/renderer_style_handler.h"
42 #include "renderer/table_layout.h"
43 #include "renderer/win32/text_renderer.h"
44 #include "renderer/win_resource.h"
45 
46 namespace mozc {
47 namespace renderer {
48 namespace win32 {
49 
50 using WTL::CBitmap;
51 using WTL::CDC;
52 using WTL::CDCHandle;
53 using WTL::CMemoryDC;
54 using WTL::CPaintDC;
55 using WTL::CPenHandle;
56 using WTL::CPoint;
57 using WTL::CRect;
58 using WTL::CSize;
59 
60 namespace {
61 
62 // 96 DPI is the default DPI in Windows.
63 const int kDefaultDPI = 96;
64 
65 // layout size constants in pixel unit in the default DPI.
66 const int kIndicatorWidthInDefaultDPI = 4;
67 
68 // DPI-invariant layout size constants in pixel unit.
69 const int kWindowBorder = 1;
70 const int kFooterSeparatorHeight = 1;
71 const int kRowRectPadding = 1;
72 
73 // usage type for each column.
74 enum COLUMN_TYPE {
75   COLUMN_SHORTCUT = 0,  // show shortcut key
76   COLUMN_GAP1,          // padding region
77   COLUMN_CANDIDATE,     // show candidate string
78   COLUMN_GAP2,          // padding region
79   COLUMN_DESCRIPTION,   // show description message
80   NUMBER_OF_COLUMNS,    // number of columns. (this item should be last)
81 };
82 
83 const char kMinimumCandidateAndDescriptionWidthAsString[] = "そのほかの文字種";
84 
85 // Color scheme
86 const COLORREF kFrameColor = RGB(0x96, 0x96, 0x96);
87 const COLORREF kShortcutBackgroundColor = RGB(0xf3, 0xf4, 0xff);
88 const COLORREF kSelectedRowBackgroundColor = RGB(0xd1, 0xea, 0xff);
89 const COLORREF kDefaultBackgroundColor = RGB(0xff, 0xff, 0xff);
90 const COLORREF kSelectedRowFrameColor = RGB(0x7f, 0xac, 0xdd);
91 const COLORREF kIndicatorBackgroundColor = RGB(0xe0, 0xe0, 0xe0);
92 const COLORREF kIndicatorColor = RGB(0x75, 0x90, 0xb8);
93 const COLORREF kFooterTopColor = RGB(0xff, 0xff, 0xff);
94 const COLORREF kFooterBottomColor = RGB(0xee, 0xee, 0xee);
95 
96 // ------------------------------------------------------------------------
97 // Utility functions
98 // ------------------------------------------------------------------------
ToCRect(const Rect & rect)99 WTL::CRect ToCRect(const Rect &rect) {
100   return WTL::CRect(rect.Left(), rect.Top(), rect.Right(), rect.Bottom());
101 }
102 
103 // Returns the smallest index of the given candidate list which satisfies
104 // candidates.candidate(i) == |candidate_index|.
105 // This function returns the size of the given candidate list when there
106 // aren't any candidates satisfying the above condition.
GetCandidateArrayIndexByCandidateIndex(const commands::Candidates & candidates,int candidate_index)107 int GetCandidateArrayIndexByCandidateIndex(
108     const commands::Candidates &candidates,
109     int candidate_index) {
110 
111   for (size_t i = 0; i < candidates.candidate_size(); ++i) {
112     const commands::Candidates::Candidate &candidate = candidates.candidate(i);
113 
114     if (candidate.index() == candidate_index) {
115       return i;
116     }
117   }
118 
119   return candidates.candidate_size();
120 }
121 
122 // Returns a text which includes the selected index number and
123 // the number of the candidates. For example, "13/123" means
124 // the selected index is "13" (in 1-origin) and the number of
125 // candidates is "123"
126 // Returns an empty string if index string should not be displayed.
GetIndexGuideString(const commands::Candidates & candidates)127 string GetIndexGuideString(const commands::Candidates &candidates) {
128   if (!candidates.has_footer() || !candidates.footer().index_visible()) {
129     return "";
130   }
131 
132   const int focused_index = candidates.focused_index();
133   const int total_items = candidates.size();
134 
135   std::stringstream footer_string;
136   footer_string << focused_index + 1
137                 << "/"
138                 << total_items
139                 << " ";  // for padding.
140 
141   return footer_string.str();
142 }
143 
144 // Returns the smallest index of the given candidate list which satisfies
145 // |candidates.focused_index| == |candidates.candidate(i).index()|.
146 // This function returns the size of the given candidate list when there
147 // aren't any candidates satisfying the above condition.
GetFocusedArrayIndex(const commands::Candidates & candidates)148 int GetFocusedArrayIndex(const commands::Candidates &candidates) {
149   const int kInvalidIndex = candidates.candidate_size();
150 
151   if (!candidates.has_focused_index()) {
152     return kInvalidIndex;
153   }
154 
155   const int focused_index = candidates.focused_index();
156 
157   return GetCandidateArrayIndexByCandidateIndex(candidates, focused_index);
158 }
159 
160 // Retrieves the display string from the specified candidate for the specified
161 // column and returns it.
GetDisplayStringByColumn(const commands::Candidates::Candidate & candidate,COLUMN_TYPE column_type)162 std::wstring GetDisplayStringByColumn(
163     const commands::Candidates::Candidate &candidate,
164     COLUMN_TYPE column_type) {
165   std::wstring display_string;
166 
167   switch (column_type) {
168     case COLUMN_SHORTCUT:
169       if (candidate.has_annotation()) {
170         const commands::Annotation &annotation = candidate.annotation();
171         if (annotation.has_shortcut()) {
172           mozc::Util::UTF8ToWide(annotation.shortcut(), &display_string);
173         }
174       }
175       break;
176     case COLUMN_CANDIDATE:
177       if (candidate.has_value()) {
178         mozc::Util::UTF8ToWide(candidate.value(), &display_string);
179       }
180       if (candidate.has_annotation()) {
181         const commands::Annotation &annotation = candidate.annotation();
182         if (annotation.has_prefix()) {
183           std::wstring annotation_prefix;
184           mozc::Util::UTF8ToWide(annotation.prefix(), &annotation_prefix);
185           display_string = annotation_prefix + display_string;
186         }
187         if (annotation.has_suffix()) {
188           std::wstring annotation_suffix;
189           mozc::Util::UTF8ToWide(annotation.suffix(), &annotation_suffix);
190           display_string += annotation_suffix;
191         }
192       }
193       break;
194     case COLUMN_DESCRIPTION:
195       if (candidate.has_annotation()) {
196         const commands::Annotation &annotation = candidate.annotation();
197         if (annotation.has_description()) {
198           mozc::Util::UTF8ToWide(annotation.description(), &display_string);
199         }
200       }
201       break;
202     default:
203       LOG(ERROR) << "Unknown column type: " << column_type;
204       break;
205   }
206 
207   return display_string;
208 }
209 
210 // Loads a DIB from a Win32 resource in the specified module and returns its
211 // handle.  This function will fail if you try to load a top-down bitmap in
212 // Windows XP.
213 // Returns nullptr if failed to load the image.
214 // Caller must delete the object if this function returns non-null value.
LoadBitmapFromResource(HMODULE module,int resource_id)215 HBITMAP LoadBitmapFromResource(HMODULE module, int resource_id) {
216   // We can use LR_CREATEDIBSECTION to load a 32-bpp bitmap.
217   // You cannot load a a top-down DIB with LoadImage in Windows XP.
218   // http://b/2076264
219   return reinterpret_cast<HBITMAP>(
220       ::LoadImage(module, MAKEINTRESOURCE(resource_id), IMAGE_BITMAP,
221                   0, 0, LR_CREATEDIBSECTION));
222 }
223 
224 }  // namespace
225 
226 // ------------------------------------------------------------------------
227 // CandidateWindow
228 // ------------------------------------------------------------------------
229 
CandidateWindow()230 CandidateWindow::CandidateWindow()
231     : candidates_(new commands::Candidates),
232       indicator_width_(0),
233       footer_logo_display_size_(0, 0),
234       metrics_changed_(false),
235       mouse_moving_(true),
236       text_renderer_(TextRenderer::Create()),
237       table_layout_(new TableLayout),
238       send_command_interface_(nullptr) {
239   double scale_factor_x = 1.0;
240   double scale_factor_y = 1.0;
241   RendererStyleHandler::GetDPIScalingFactor(&scale_factor_x,
242                                             &scale_factor_y);
243   double image_scale_factor = 1.0;
244   if (scale_factor_x < 1.125 || scale_factor_y < 1.125) {
245     footer_logo_.Attach(LoadBitmapFromResource(
246       ::GetModuleHandle(nullptr), IDB_FOOTER_LOGO_COLOR_100));
247     image_scale_factor = 1.0;
248   } else if (scale_factor_x < 1.375 || scale_factor_y < 1.375) {
249     footer_logo_.Attach(LoadBitmapFromResource(
250       ::GetModuleHandle(nullptr), IDB_FOOTER_LOGO_COLOR_125));
251     image_scale_factor = 1.25;
252   } else if (scale_factor_x < 1.75 || scale_factor_y < 1.75) {
253     footer_logo_.Attach(LoadBitmapFromResource(
254       ::GetModuleHandle(nullptr), IDB_FOOTER_LOGO_COLOR_150));
255     image_scale_factor = 1.5;
256   } else {
257     footer_logo_.Attach(LoadBitmapFromResource(
258       ::GetModuleHandle(nullptr), IDB_FOOTER_LOGO_COLOR_200));
259     image_scale_factor = 2.0;
260   }
261 
262   // If DPI is not default value, re-calculate the size based on the DPI.
263   if (!footer_logo_.IsNull()) {
264     CSize size;
265     footer_logo_.GetSize(size);
266     size.cx *= (scale_factor_x / image_scale_factor);
267     size.cy *= (scale_factor_y / image_scale_factor);
268     footer_logo_display_size_ = Size(size.cx, size.cy);
269   }
270 
271   indicator_width_ = kIndicatorWidthInDefaultDPI * scale_factor_x;
272 }
273 
~CandidateWindow()274 CandidateWindow::~CandidateWindow() {}
275 
OnCreate(LPCREATESTRUCT create_struct)276 LRESULT CandidateWindow::OnCreate(LPCREATESTRUCT create_struct) {
277   EnableOrDisableWindowForWorkaround();
278   return 0;
279 }
280 
EnableOrDisableWindowForWorkaround()281 void CandidateWindow::EnableOrDisableWindowForWorkaround() {
282   // Disable the window if SPI_GETACTIVEWINDOWTRACKING is enabled.
283   // See b/2317702 for details.
284   // TODO(yukawa): Support mouse operations before we add a GUI feature which
285   //   requires UI interaction by mouse and/or touch. (b/2954874)
286   BOOL is_tracking_enabled = FALSE;
287   if (::SystemParametersInfo(SPI_GETACTIVEWINDOWTRACKING,
288                              0, &is_tracking_enabled, 0)) {
289     EnableWindow(!is_tracking_enabled);
290   }
291 }
292 
OnDestroy()293 void CandidateWindow::OnDestroy() {
294   // PostQuitMessage may stop the message loop even though other
295   // windows are not closed. WindowManager should close these windows
296   // before process termination.
297   ::PostQuitMessage(0);
298 }
299 
OnEraseBkgnd(CDCHandle dc)300 BOOL CandidateWindow::OnEraseBkgnd(CDCHandle dc) {
301   // We do not have to erase background
302   // because all pixels in client area will be drawn in the DoPaint method.
303   return TRUE;
304 }
305 
OnGetMinMaxInfo(MINMAXINFO * min_max_info)306 void CandidateWindow::OnGetMinMaxInfo(MINMAXINFO *min_max_info) {
307   // Do not restrict the window size in case the candidate window must be
308   // very small size.
309   min_max_info->ptMinTrackSize.x = 1;
310   min_max_info->ptMinTrackSize.y = 1;
311   SetMsgHandled(TRUE);
312 }
313 
HandleMouseEvent(UINT nFlags,const WTL::CPoint & point,bool close_candidatewindow)314 void CandidateWindow::HandleMouseEvent(
315     UINT nFlags, const WTL::CPoint &point, bool close_candidatewindow) {
316   if (send_command_interface_ == nullptr) {
317     LOG(ERROR) << "send_command_interface_ is nullptr";
318     return;
319   }
320 
321   const int focused_array_index = GetFocusedArrayIndex(*candidates_);
322 
323   for (size_t i = 0; i < candidates_->candidate_size(); ++i) {
324     const commands::Candidates::Candidate &candidate
325         = candidates_->candidate(i);
326 
327     const CRect rect = ToCRect(table_layout_->GetRowRect(i));
328     if (rect.PtInRect(point)) {
329       commands::SessionCommand command;
330       if (close_candidatewindow) {
331         command.set_type(commands::SessionCommand::SELECT_CANDIDATE);
332       } else {
333         command.set_type(commands::SessionCommand::HIGHLIGHT_CANDIDATE);
334       }
335       command.set_id(candidate.id());
336       commands::Output output;
337       send_command_interface_->SendCommand(command, &output);
338       return;
339     }
340   }
341 }
342 
OnLButtonDown(UINT nFlags,CPoint point)343 void CandidateWindow::OnLButtonDown(UINT nFlags, CPoint point) {
344   HandleMouseEvent(nFlags, point, false);
345 }
346 
OnLButtonUp(UINT nFlags,CPoint point)347 void CandidateWindow::OnLButtonUp(UINT nFlags, CPoint point) {
348   HandleMouseEvent(nFlags, point, true);
349 }
350 
OnMouseMove(UINT nFlags,WTL::CPoint point)351 void CandidateWindow::OnMouseMove(UINT nFlags, WTL::CPoint point) {
352   // Window manager sometimes generates WM_MOUSEMOVE message when the contents
353   // under the mouse cursor has been changed (e.g. the window is moved) so that
354   // the mouse handler can change its cursor image based on the contents to
355   // which the cursor is newly pointing.  In order to filter these pseudo
356   // WM_MOUSEMOVE out, |mouse_moving_| is checked here.
357   // See http://blogs.msdn.com/b/oldnewthing/archive/2003/10/01/55108.aspx for
358   // details about such an artificial WM_MOUSEMOVE.  See also b/3104996.
359   if (!mouse_moving_) {
360     return;
361   }
362   if ((nFlags & MK_LBUTTON) != MK_LBUTTON) {
363     return;
364   }
365 
366   HandleMouseEvent(nFlags, point, false);
367 }
368 
OnPaint(CDCHandle dc)369 void CandidateWindow::OnPaint(CDCHandle dc) {
370   CRect client_rect;
371   this->GetClientRect(&client_rect);
372 
373   if (dc != nullptr) {
374     CMemoryDC memdc(dc, client_rect);
375     DoPaint(memdc.m_hDC);
376   } else  {
377     CPaintDC paint_dc(this->m_hWnd);
378     { // Create a copy of |paint_dc| and render the candidate strings in it.
379       // The image rendered to this |memdc| is to be copied into the original
380       // |paint_dc| in its destructor. So, we don't have to explicitly call
381       // any functions that copy this |memdc| to the |paint_dc| but putting
382       // the following code into a local block.
383       CMemoryDC memdc(paint_dc, client_rect);
384       DoPaint(memdc.m_hDC);
385     }
386   }
387 }
388 
OnPrintClient(CDCHandle dc,UINT uFlags)389 void CandidateWindow::OnPrintClient(CDCHandle dc, UINT uFlags) {
390   OnPaint(dc);
391 }
392 
DoPaint(CDCHandle dc)393 void CandidateWindow::DoPaint(CDCHandle dc) {
394   switch (candidates_->category()) {
395     case commands::CONVERSION:
396     case commands::PREDICTION:
397     case commands::TRANSLITERATION:
398     case commands::SUGGESTION:
399     case commands::USAGE:
400       break;
401     default:
402       LOG(INFO) << "Unknown candidates category: " << candidates_->category();
403       return;
404   }
405 
406   if (!table_layout_->IsLayoutFrozen()) {
407     LOG(WARNING) << "Table layout is not frozen.";
408     return;
409   }
410 
411   dc.SetBkMode(TRANSPARENT);
412 
413   DrawBackground(dc);
414   DrawShortcutBackground(dc);
415   DrawSelectedRect(dc);
416   DrawCells(dc);
417   DrawInformationIcon(dc);
418   DrawVScrollBar(dc);
419   DrawFooter(dc);
420   DrawFrame(dc);
421 }
422 
OnSettingChange(UINT uFlags,LPCTSTR)423 void CandidateWindow::OnSettingChange(UINT uFlags, LPCTSTR /*lpszSection*/) {
424   // Since TextRenderer uses dialog font to render,
425   // we monitor font-related parameters to know when the font style is changed.
426   switch (uFlags) {
427     case 0x1049:  // = SPI_SETCLEARTYPE
428     case SPI_SETFONTSMOOTHING:
429     case SPI_SETFONTSMOOTHINGCONTRAST:
430     case SPI_SETFONTSMOOTHINGORIENTATION:
431     case SPI_SETFONTSMOOTHINGTYPE:
432     case SPI_SETNONCLIENTMETRICS:
433       metrics_changed_ = true;
434       break;
435     case SPI_SETACTIVEWINDOWTRACKING:
436       EnableOrDisableWindowForWorkaround();
437     default:
438       // We ignore other changes.
439       break;
440   }
441 }
442 
UpdateLayout(const commands::Candidates & candidates)443 void CandidateWindow::UpdateLayout(const commands::Candidates &candidates) {
444   candidates_->CopyFrom(candidates);
445 
446   // If we detect any change of font parameters, update text renderer
447   if (metrics_changed_) {
448     text_renderer_->OnThemeChanged();
449     metrics_changed_ = false;
450   }
451 
452   switch (candidates_->category()) {
453     case commands::CONVERSION:
454     case commands::PREDICTION:
455     case commands::TRANSLITERATION:
456     case commands::SUGGESTION:
457     case commands::USAGE:
458       break;
459     default:
460       LOG(INFO) << "Unknown candidates category: " << candidates_->category();
461       return;
462   }
463 
464   table_layout_->Initialize(candidates_->candidate_size(), NUMBER_OF_COLUMNS);
465 
466   table_layout_->SetWindowBorder(kWindowBorder);
467 
468   // Add a vertical scroll bar if candidate list consists of more than
469   // one page.
470   if (candidates_->candidate_size() < candidates_->size()) {
471     table_layout_->SetVScrollBar(indicator_width_);
472   }
473 
474   if (candidates_->has_footer()) {
475     Size footer_size(0, 0);
476 
477     // Calculate the size to display a label string.
478     if (candidates_->footer().has_label()) {
479       std::wstring footer_label;
480       mozc::Util::UTF8ToWide(candidates_->footer().label(),
481                              &footer_label);
482       const Size label_string_size = text_renderer_->MeasureString(
483           TextRenderer::FONTSET_FOOTER_LABEL,
484           L" " + footer_label + L" ");
485       footer_size.width += label_string_size.width;
486       footer_size.height = std::max(footer_size.height,
487                                     label_string_size.height);
488     } else if (candidates_->footer().has_sub_label()) {
489       // Currently the sub label will not be shown unless (main) label is
490       // absent.
491       // TODO(yukawa): Refactor the layout system for the footer.
492       std::wstring footer_sub_label;
493       mozc::Util::UTF8ToWide(candidates_->footer().sub_label(),
494                              &footer_sub_label);
495       const Size label_string_size = text_renderer_->MeasureString(
496           TextRenderer::FONTSET_FOOTER_SUBLABEL,
497           L" " + footer_sub_label + L" ");
498       footer_size.width += label_string_size.width;
499       footer_size.height = std::max(footer_size.height,
500                                     label_string_size.height);
501     }
502 
503     // Calculate the size to display a index string.
504     if (candidates_->footer().index_visible()) {
505       std::wstring index_guide_string;
506       mozc::Util::UTF8ToWide(GetIndexGuideString(*candidates_),
507                              &index_guide_string);
508       const Size index_guide_size = text_renderer_->MeasureString(
509           TextRenderer::FONTSET_FOOTER_INDEX, index_guide_string);
510       footer_size.width += index_guide_size.width;
511       footer_size.height = std::max(footer_size.height,
512                                     index_guide_size.height);
513     }
514 
515     // Calculate the size to display a Footer logo.
516     if (!footer_logo_.IsNull()) {
517       if (candidates_->footer().logo_visible()) {
518         footer_size.width += footer_logo_display_size_.width;
519         footer_size.height = std::max(footer_size.height,
520                                       footer_logo_display_size_.height);
521       } else if (footer_size.height > 0) {
522         // Ensure the footer height is greater than the Footer logo height
523         // even if the Footer logo is absent.  This hack prevents the footer
524         // from changing its height too frequently.
525         footer_size.height = std::max(footer_size.height,
526                                       footer_logo_display_size_.height);
527       }
528     }
529 
530     // Ensure minimum columns width if candidate list consists of more than
531     // one page.
532     if (candidates_->candidate_size() < candidates_->size()) {
533       // We use FONTSET_CANDIDATE for calculating the minimum width.
534       std::wstring minimum_width_as_wstring;
535       mozc::Util::UTF8ToWide(
536           kMinimumCandidateAndDescriptionWidthAsString,
537           &minimum_width_as_wstring);
538       const Size minimum_size = text_renderer_->MeasureString(
539           TextRenderer::FONTSET_CANDIDATE, minimum_width_as_wstring.c_str());
540       table_layout_->EnsureColumnsWidth(
541           COLUMN_CANDIDATE, COLUMN_DESCRIPTION, minimum_size.width);
542     }
543 
544     // Add separator height
545     footer_size.height += kFooterSeparatorHeight;
546 
547     table_layout_->EnsureFooterSize(footer_size);
548   }
549 
550   table_layout_->SetRowRectPadding(kRowRectPadding);
551 
552   // put a padding in COLUMN_GAP1.
553   // the width is determined to be equal to the width of " ".
554   const Size gap1_size =
555       text_renderer_->MeasureString(TextRenderer::FONTSET_CANDIDATE, L" ");
556   table_layout_->EnsureCellSize(COLUMN_GAP1, gap1_size);
557 
558   bool description_found = false;
559 
560   // calculate table size.
561   for (size_t i = 0; i < candidates_->candidate_size(); ++i) {
562     const commands::Candidates::Candidate &candidate =
563         candidates_->candidate(i);
564     const std::wstring shortcut =
565         GetDisplayStringByColumn(candidate, COLUMN_SHORTCUT);
566     const std::wstring description =
567         GetDisplayStringByColumn(candidate, COLUMN_DESCRIPTION);
568     const std::wstring candidate_string =
569         GetDisplayStringByColumn(candidate, COLUMN_CANDIDATE);
570 
571     if (!shortcut.empty()) {
572       std::wstring text;
573       text.push_back(L' ');  // put a space for padding
574       text.append(shortcut);
575       text.push_back(L' ');  // put a space for padding
576       const Size rendering_size = text_renderer_->MeasureString(
577           TextRenderer::FONTSET_SHORTCUT, text);
578       table_layout_->EnsureCellSize(COLUMN_SHORTCUT, rendering_size);
579     }
580 
581     if (!candidate_string.empty()) {
582       std::wstring text;
583       text.append(candidate_string);
584 
585       const Size rendering_size = text_renderer_->MeasureString(
586           TextRenderer::FONTSET_CANDIDATE, text);
587       table_layout_->EnsureCellSize(COLUMN_CANDIDATE, rendering_size);
588     }
589 
590     if (!description.empty()) {
591       std::wstring text;
592       text.append(description);
593       text.push_back(L' ');  // put a space for padding
594       const Size rendering_size = text_renderer_->MeasureString(
595           TextRenderer::FONTSET_DESCRIPTION, text);
596       table_layout_->EnsureCellSize(COLUMN_DESCRIPTION, rendering_size);
597 
598       description_found = true;
599     }
600   }
601 
602   // Put a padding in COLUMN_GAP2.
603   // We use wide padding if there is any description column.
604   const wchar_t *gap2_string = (description_found ? L"   " : L" ");
605   const Size gap2_size = text_renderer_->MeasureString(
606       TextRenderer::FONTSET_CANDIDATE, gap2_string);
607   table_layout_->EnsureCellSize(COLUMN_GAP2, gap2_size);
608 
609   table_layout_->FreezeLayout();
610 }
611 
SetSendCommandInterface(client::SendCommandInterface * send_command_interface)612 void CandidateWindow::SetSendCommandInterface(
613   client::SendCommandInterface *send_command_interface) {
614   send_command_interface_ = send_command_interface;
615 }
616 
GetLayoutSize() const617 Size CandidateWindow::GetLayoutSize() const {
618   DCHECK(table_layout_->IsLayoutFrozen()) << "Table layout is not frozen.";
619 
620   return table_layout_->GetTotalSize();
621 }
622 
GetSelectionRectInScreenCord() const623 Rect CandidateWindow::GetSelectionRectInScreenCord() const {
624   const int focused_array_index = GetFocusedArrayIndex(*candidates_);
625 
626   if (0 <= focused_array_index &&
627       focused_array_index < candidates_->candidate_size()) {
628     const commands::Candidates::Candidate &candidate =
629         candidates_->candidate(focused_array_index);
630 
631     CRect rect = ToCRect(table_layout_->GetRowRect(focused_array_index));
632     ClientToScreen(&rect);
633     return Rect(rect.left, rect.top, rect.Width(), rect.Height());
634   }
635 
636   return Rect();
637 }
638 
GetCandidateColumnInClientCord() const639 Rect CandidateWindow::GetCandidateColumnInClientCord() const {
640   DCHECK(table_layout_->IsLayoutFrozen()) << "Table layout is not frozen.";
641 
642   return table_layout_->GetCellRect(0, COLUMN_CANDIDATE);
643 }
644 
GetFirstRowInClientCord() const645 Rect CandidateWindow::GetFirstRowInClientCord() const {
646   DCHECK(table_layout_->IsLayoutFrozen()) << "Table layout is not frozen.";
647   DCHECK_GT(table_layout_->number_of_rows(), 0)
648       << "number of rows should be positive";
649   return table_layout_->GetRowRect(0);
650 }
651 
DrawCells(CDCHandle dc)652 void CandidateWindow::DrawCells(CDCHandle dc) {
653   COLUMN_TYPE kColumnTypes[] =
654       {COLUMN_SHORTCUT, COLUMN_CANDIDATE, COLUMN_DESCRIPTION};
655   TextRenderer::FONT_TYPE kFontTypes[] =
656       {TextRenderer::FONTSET_SHORTCUT, TextRenderer::FONTSET_CANDIDATE,
657        TextRenderer::FONTSET_DESCRIPTION};
658 
659   DCHECK_EQ(arraysize(kColumnTypes), arraysize(kFontTypes));
660   for (size_t type_index = 0;
661        type_index < arraysize(kColumnTypes); ++type_index) {
662     const COLUMN_TYPE column_type = kColumnTypes[type_index];
663     const TextRenderer::FONT_TYPE font_type = kFontTypes[type_index];
664 
665     std::vector<TextRenderingInfo> display_list;
666     for (size_t i = 0; i < candidates_->candidate_size(); ++i) {
667       const commands::Candidates::Candidate &candidate =
668           candidates_->candidate(i);
669       const std::wstring display_string =
670           GetDisplayStringByColumn(candidate, column_type);
671       const Rect text_rect =
672           table_layout_->GetCellRect(i, column_type);
673       display_list.push_back(TextRenderingInfo(display_string, text_rect));
674     }
675     text_renderer_->RenderTextList(dc, display_list, font_type);
676   }
677 }
678 
DrawVScrollBar(CDCHandle dc)679 void CandidateWindow::DrawVScrollBar(CDCHandle dc) {
680   const Rect &vscroll_rect = table_layout_->GetVScrollBarRect();
681 
682   if (!vscroll_rect.IsRectEmpty() && candidates_->candidate_size() > 0) {
683     const int begin_index = candidates_->candidate(0).index();
684     const int candidates_in_page = candidates_->candidate_size();
685     const int candidates_total = candidates_->size();
686     const int end_index =
687         candidates_->candidate(candidates_in_page - 1).index();
688 
689     const CRect background_crect = ToCRect(vscroll_rect);
690     dc.FillSolidRect(&background_crect, kIndicatorBackgroundColor);
691 
692     const mozc::Rect &indicator_rect =
693         table_layout_->GetVScrollIndicatorRect(
694             begin_index, end_index, candidates_total);
695 
696     const CRect indicator_crect = ToCRect(indicator_rect);
697     dc.FillSolidRect(&indicator_crect, kIndicatorColor);
698   }
699 }
700 
DrawShortcutBackground(CDCHandle dc)701 void CandidateWindow::DrawShortcutBackground(CDCHandle dc) {
702   if (table_layout_->number_of_columns() > 0) {
703     Rect shortcut_colmun_rect = table_layout_->GetColumnRect(0);
704     if (!shortcut_colmun_rect.IsRectEmpty()) {
705       // Due to the mismatch of the implementation of the TableLayout class
706       // and the design requiement, we have to *fix* the width and origin
707       // of the rectangle.
708       // If you remove this *fix*, an empty region appears between the
709       // left window border and the colored region of the shortcut column.
710       const Rect row_rect = table_layout_->GetRowRect(0);
711       const int width = shortcut_colmun_rect.Right() - row_rect.Left();
712       shortcut_colmun_rect.origin.x = row_rect.Left();
713       shortcut_colmun_rect.size.width = width;
714       const CRect shortcut_colmun_crect = ToCRect(shortcut_colmun_rect);
715       dc.FillSolidRect(&shortcut_colmun_crect, kShortcutBackgroundColor);
716     }
717   }
718 }
719 
DrawFooter(CDCHandle dc)720 void CandidateWindow::DrawFooter(CDCHandle dc) {
721   const Rect &footer_rect = table_layout_->GetFooterRect();
722   if (!candidates_->has_footer() || footer_rect.IsRectEmpty()) {
723     return;
724   }
725 
726   const COLORREF kFooterSeparatorColors[kFooterSeparatorHeight] = {
727       kFrameColor };
728 
729   // DC pen is available in Windows 2000 and later.
730   CPenHandle prev_pen(dc.SelectPen(static_cast<HPEN>(GetStockObject(DC_PEN))));
731   for (size_t i = 0, y = footer_rect.Top();
732        i < kFooterSeparatorHeight; y++, i++) {
733     if (i < ARRAYSIZE(kFooterSeparatorColors)) {
734       dc.SetDCPenColor(kFooterSeparatorColors[i]);
735       dc.MoveTo(footer_rect.Left(), y, nullptr);
736       dc.LineTo(footer_rect.Right(), y);
737     }
738   }
739   dc.SelectPen(prev_pen);
740 
741   const Rect footer_content_rect(
742       footer_rect.Left(),
743       footer_rect.Top() + kFooterSeparatorHeight,
744       footer_rect.Width(),
745       footer_rect.Height() - kFooterSeparatorHeight);
746 
747   // Draw gradient rect in the footer area
748   {
749     TRIVERTEX vertices[] = {
750       { footer_content_rect.Left(),
751         footer_content_rect.Top(),
752         GetRValue(kFooterTopColor) << 8,
753         GetGValue(kFooterTopColor) << 8,
754         GetBValue(kFooterTopColor) << 8,
755         0xff00 },
756       { footer_content_rect.Right(),
757         footer_content_rect.Bottom(),
758         GetRValue(kFooterBottomColor) << 8,
759         GetGValue(kFooterBottomColor) << 8,
760         GetBValue(kFooterBottomColor) << 8,
761         0xff00 }
762     };
763     GRADIENT_RECT indices[] = { {0, 1} };
764     dc.GradientFill(&vertices[0], ARRAYSIZE(vertices),
765                     &indices[0],  ARRAYSIZE(indices), GRADIENT_FILL_RECT_V);
766   }
767 
768   int left_used = 0;
769 
770   if (candidates_->footer().logo_visible() && !footer_logo_.IsNull()) {
771     const int top_offset =
772         (footer_content_rect.Height() - footer_logo_display_size_.height) / 2;
773     CDC src_dc;
774     src_dc.CreateCompatibleDC(dc);
775     const HBITMAP old_bitmap = src_dc.SelectBitmap(footer_logo_);
776 
777     CSize src_size;
778     footer_logo_.GetSize(src_size);
779 
780     // NOTE: AC_SRC_ALPHA requires PBGRA (pre-multiplied alpha) DIB.
781     const BLENDFUNCTION bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
782     dc.AlphaBlend(footer_content_rect.Left(),
783                   footer_content_rect.Top() + top_offset,
784                   footer_logo_display_size_.width,
785                   footer_logo_display_size_.height,
786                   src_dc, 0, 0, src_size.cx, src_size.cy, bf);
787 
788     src_dc.SelectBitmap(old_bitmap);
789     left_used = footer_content_rect.Left() + footer_logo_display_size_.width;
790   }
791 
792   int right_used = 0;
793   if (candidates_->footer().index_visible()) {
794     std::wstring index_guide_string;
795     mozc::Util::UTF8ToWide(GetIndexGuideString(*candidates_),
796                            &index_guide_string);
797     const Size index_guide_size = text_renderer_->MeasureString(
798         TextRenderer::FONTSET_FOOTER_INDEX, index_guide_string);
799     const Rect index_rect(footer_content_rect.Right() - index_guide_size.width,
800                           footer_content_rect.Top(),
801                           index_guide_size.width,
802                           footer_content_rect.Height());
803     text_renderer_->RenderText(dc, index_guide_string, index_rect,
804                                TextRenderer::FONTSET_FOOTER_INDEX);
805     right_used = index_guide_size.width;
806   }
807 
808   if (candidates_->footer().has_label()) {
809     const Rect label_rect(left_used,
810                           footer_content_rect.Top(),
811                           footer_content_rect.Width() - left_used - right_used,
812                           footer_content_rect.Height());
813     std::wstring footer_label;
814     mozc::Util::UTF8ToWide(candidates_->footer().label(), &footer_label);
815     text_renderer_->RenderText(dc,
816                                L" " + footer_label + L" ",
817                                label_rect,
818                                TextRenderer::FONTSET_FOOTER_LABEL);
819   } else if (candidates_->footer().has_sub_label()) {
820     std::wstring footer_sub_label;
821     mozc::Util::UTF8ToWide(candidates_->footer().sub_label(),
822                            &footer_sub_label);
823     const Rect label_rect(left_used,
824                           footer_content_rect.Top(),
825                           footer_content_rect.Width() - left_used - right_used,
826                           footer_content_rect.Height());
827     const std::wstring text = L" " + footer_sub_label + L" ";
828     text_renderer_->RenderText(dc,
829                                text,
830                                label_rect,
831                                TextRenderer::FONTSET_FOOTER_SUBLABEL);
832   }
833 }
834 
DrawSelectedRect(CDCHandle dc)835 void CandidateWindow::DrawSelectedRect(CDCHandle dc) {
836   DCHECK(table_layout_->IsLayoutFrozen()) << "Table layout is not frozen.";
837 
838   const int focused_array_index = GetFocusedArrayIndex(*candidates_);
839 
840   if (0 <= focused_array_index &&
841       focused_array_index < candidates_->candidate_size()) {
842     const commands::Candidates::Candidate &candidate
843         = candidates_->candidate(focused_array_index);
844 
845     const CRect selected_rect =
846         ToCRect(table_layout_->GetRowRect(focused_array_index));
847     dc.FillSolidRect(&selected_rect, kSelectedRowBackgroundColor);
848 
849     dc.SetDCBrushColor(kSelectedRowFrameColor);
850     dc.FrameRect(&selected_rect,
851                  static_cast<HBRUSH>(GetStockObject(DC_BRUSH)));
852   }
853 }
854 
DrawInformationIcon(CDCHandle dc)855 void CandidateWindow::DrawInformationIcon(CDCHandle dc) {
856   DCHECK(table_layout_->IsLayoutFrozen()) << "Table layout is not frozen.";
857   double scale_factor_x = 1.0;
858   double scale_factor_y = 1.0;
859   RendererStyleHandler::GetDPIScalingFactor(&scale_factor_x,
860                                             &scale_factor_y);
861   for (size_t i = 0; i < candidates_->candidate_size(); ++i) {
862     if (candidates_->candidate(i).has_information_id()) {
863       CRect rect = ToCRect(table_layout_->GetRowRect(i));
864       rect.left = rect.right - (6.0 * scale_factor_x);
865       rect.right = rect.right - (2.0 * scale_factor_x);
866       rect.top += (2.0 * scale_factor_y);
867       rect.bottom -= (2.0 * scale_factor_y);
868       dc.FillSolidRect(&rect, kIndicatorColor);
869       dc.SetDCBrushColor(kIndicatorColor);
870       dc.FrameRect(&rect,
871                    static_cast<HBRUSH>(GetStockObject(DC_BRUSH)));
872     }
873   }
874 }
875 
DrawBackground(CDCHandle dc)876 void CandidateWindow::DrawBackground(CDCHandle dc) {
877   const Rect client_rect(Point(0, 0), table_layout_->GetTotalSize());
878   const CRect client_crect = ToCRect(client_rect);
879   dc.FillSolidRect(&client_crect, kDefaultBackgroundColor);
880 }
881 
DrawFrame(CDCHandle dc)882 void CandidateWindow::DrawFrame(CDCHandle dc) {
883   const Rect client_rect(Point(0, 0), table_layout_->GetTotalSize());
884   const CRect client_crect = ToCRect(client_rect);
885 
886   // DC brush is available in Windows 2000 and later.
887   dc.SetDCBrushColor(kFrameColor);
888   dc.FrameRect(&client_crect,
889                static_cast<HBRUSH>(GetStockObject(DC_BRUSH)));
890 }
891 
set_mouse_moving(bool moving)892 void CandidateWindow::set_mouse_moving(bool moving) {
893   mouse_moving_ = moving;
894 }
895 }  // namespace win32
896 }  // namespace renderer
897 }  // namespace mozc
898