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