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/infolist_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 "protocol/renderer_style.pb.h"
42 #include "renderer/renderer_style_handler.h"
43 #include "renderer/table_layout.h"
44 #include "renderer/win32/text_renderer.h"
45 #include "renderer/win_resource.h"
46 
47 namespace mozc {
48 namespace renderer {
49 namespace win32 {
50 
51 using WTL::CBitmap;
52 using WTL::CDC;
53 using WTL::CDCHandle;
54 using WTL::CMemoryDC;
55 using WTL::CPaintDC;
56 using WTL::CPenHandle;
57 using WTL::CPoint;
58 using WTL::CRect;
59 using WTL::CSize;
60 
61 using mozc::commands::Candidates;
62 using mozc::commands::Information;
63 using mozc::commands::InformationList;
64 using mozc::commands::Output;
65 using mozc::commands::SessionCommand;
66 using mozc::renderer::RendererStyle;
67 using mozc::renderer::RendererStyleHandler;
68 
69 namespace {
70 const COLORREF kDefaultBackgroundColor = RGB(0xff, 0xff, 0xff);
71 const UINT_PTR kIdDelayShowHideTimer = 100;
72 
SendUsageStatsEvent(client::SendCommandInterface * command_sender,const SessionCommand::UsageStatsEvent & event)73 bool SendUsageStatsEvent(client::SendCommandInterface *command_sender,
74                          const SessionCommand::UsageStatsEvent &event) {
75   if (command_sender == nullptr) {
76     return false;
77   }
78   SessionCommand command;
79   command.set_type(SessionCommand::USAGE_STATS_EVENT);
80   command.set_usage_stats_event(event);
81   VLOG(2) << "SendUsageStatsEvent " << command.DebugString();
82   Output dummy_output;
83   return command_sender->SendCommand(command, &dummy_output);
84 }
85 }  // namespace
86 
87 
88 // ------------------------------------------------------------------------
89 // InfolistWindow
90 // ------------------------------------------------------------------------
91 
InfolistWindow()92 InfolistWindow::InfolistWindow()
93     : candidates_(new commands::Candidates),
94       metrics_changed_(false),
95       text_renderer_(TextRenderer::Create()),
96       style_(new RendererStyle),
97       visible_(false),
98       send_command_interface_(nullptr) {
99   mozc::renderer::RendererStyleHandler::GetRendererStyle(style_.get());
100 }
101 
~InfolistWindow()102 InfolistWindow::~InfolistWindow() {}
103 
OnDestroy()104 void InfolistWindow::OnDestroy() {
105   // PostQuitMessage may stop the message loop even though other
106   // windows are not closed. WindowManager should close these windows
107   // before process termination.
108   ::PostQuitMessage(0);
109 }
110 
OnEraseBkgnd(CDCHandle dc)111 BOOL InfolistWindow::OnEraseBkgnd(CDCHandle dc) {
112   // We do not have to erase background
113   // because all pixels in client area will be drawn in the DoPaint method.
114   return TRUE;
115 }
116 
OnGetMinMaxInfo(MINMAXINFO * min_max_info)117 void InfolistWindow::OnGetMinMaxInfo(MINMAXINFO *min_max_info) {
118   // Do not restrict the window size in case the candidate window must be
119   // very small size.
120   min_max_info->ptMinTrackSize.x = 1;
121   min_max_info->ptMinTrackSize.y = 1;
122   SetMsgHandled(TRUE);
123 }
124 
OnPaint(CDCHandle dc)125 void InfolistWindow::OnPaint(CDCHandle dc) {
126   CRect client_rect;
127   this->GetClientRect(&client_rect);
128 
129   if (dc != nullptr) {
130     CMemoryDC memdc(dc, client_rect);
131     DoPaint(memdc.m_hDC);
132   } else  {
133     CPaintDC paint_dc(this->m_hWnd);
134     { // Create a copy of |paint_dc| and render the candidate strings in it.
135       // The image rendered to this |memdc| is to be copied into the original
136       // |paint_dc| in its destructor. So, we don't have to explicitly call
137       // any functions that copy this |memdc| to the |paint_dc| but putting
138       // the following code into a local block.
139       CMemoryDC memdc(paint_dc, client_rect);
140       DoPaint(memdc.m_hDC);
141     }
142   }
143 }
144 
OnPrintClient(CDCHandle dc,UINT uFlags)145 void InfolistWindow::OnPrintClient(CDCHandle dc, UINT uFlags) {
146   OnPaint(dc);
147 }
148 
DoPaint(CDCHandle dc)149 Size InfolistWindow::DoPaint(CDCHandle dc) {
150   if (dc.m_hDC != nullptr) {
151     dc.SetBkMode(TRANSPARENT);
152   }
153   const RendererStyle::InfolistStyle &infostyle = style_->infolist_style();
154   const InformationList &usages = candidates_->usages();
155 
156   int ypos = infostyle.window_border();
157 
158   if ((dc.m_hDC != nullptr) && infostyle.has_caption_string()) {
159     const RendererStyle::TextStyle &caption_style =
160       infostyle.caption_style();
161     const int caption_height = infostyle.caption_height();
162     const Rect backgrounnd_rect(infostyle.window_border(), ypos,
163       infostyle.window_width() - infostyle.window_border() * 2,
164       caption_height);
165     const CRect background_crect(
166         backgrounnd_rect.Left(), backgrounnd_rect.Top(),
167         backgrounnd_rect.Right(), backgrounnd_rect.Bottom());
168 
169     dc.FillSolidRect(&background_crect,
170         RGB(infostyle.caption_background_color().r(),
171             infostyle.caption_background_color().g(),
172             infostyle.caption_background_color().b()));
173 
174     std::wstring caption_str;
175     const Rect caption_rect(
176       infostyle.window_border() + infostyle.caption_padding()
177       + caption_style.left_padding(),
178       ypos + infostyle.caption_padding(),
179       infostyle.window_width() - infostyle.window_border() * 2,
180       caption_height);
181     mozc::Util::UTF8ToWide(infostyle.caption_string(), &caption_str);
182 
183     text_renderer_->RenderText(dc,
184                                caption_str,
185                                caption_rect,
186                                TextRenderer::FONTSET_INFOLIST_CAPTION);
187   }
188   ypos += infostyle.caption_height();
189 
190   for (int i = 0; i < usages.information_size(); ++i) {
191     Size size = DoPaintRow(dc, i, ypos);
192     ypos += size.height;
193   }
194   ypos += infostyle.window_border();
195 
196   if (dc.m_hDC != nullptr) {
197     const CRect rect(0, 0, infostyle.window_width(), ypos);
198     dc.SetDCBrushColor(
199         RGB(infostyle.border_color().r(),
200             infostyle.border_color().g(),
201             infostyle.border_color().b()));
202     dc.FrameRect(&rect,
203                  static_cast<HBRUSH>(GetStockObject(DC_BRUSH)));
204   }
205 
206 
207   return Size(style_->infolist_style().window_width(), ypos);
208 }
209 
DoPaintRow(CDCHandle dc,int row,int ypos)210 Size InfolistWindow::DoPaintRow(CDCHandle dc, int row, int ypos) {
211   const RendererStyle::InfolistStyle &infostyle = style_->infolist_style();
212   const InformationList &usages = candidates_->usages();
213   const RendererStyle::TextStyle &title_style = infostyle.title_style();
214   const RendererStyle::TextStyle &desc_style = infostyle.description_style();
215   const int title_width = infostyle.window_width() -
216       title_style.left_padding() - title_style.right_padding() -
217       infostyle.window_border() * 2 -
218       infostyle.row_rect_padding() * 2;
219   const int desc_width = infostyle.window_width() -
220       desc_style.left_padding() - desc_style.right_padding() -
221       infostyle.window_border() * 2 -
222       infostyle.row_rect_padding() * 2;
223   const Information &info = usages.information(row);
224 
225   std::wstring title_str;
226   mozc::Util::UTF8ToWide(info.title(), &title_str);
227   const Size title_size = text_renderer_->MeasureStringMultiLine(
228       TextRenderer::FONTSET_INFOLIST_TITLE, title_str, title_width);
229 
230   std::wstring desc_str;
231   mozc::Util::UTF8ToWide(info.description(), &desc_str);
232   const Size desc_size = text_renderer_->MeasureStringMultiLine(
233       TextRenderer::FONTSET_INFOLIST_DESCRIPTION, desc_str, desc_width);
234 
235   int row_height = title_size.height + desc_size.height +
236                    infostyle.row_rect_padding() * 2;
237 
238   if (dc.m_hDC == nullptr) {
239     return Size(0, row_height);
240   }
241   const Rect title_rect(
242       infostyle.window_border() + infostyle.row_rect_padding() +
243       title_style.left_padding(),
244       ypos + infostyle.row_rect_padding(),
245       title_width, title_size.height);
246   const Rect desc_rect(
247       infostyle.window_border() + infostyle.row_rect_padding() +
248       desc_style.left_padding(),
249       ypos + infostyle.row_rect_padding() + title_rect.size.height,
250       desc_width, desc_size.height);
251 
252   const CRect title_back_crect(infostyle.window_border(), ypos,
253       infostyle.window_width() - infostyle.window_border(),
254       ypos + title_rect.size.height + infostyle.row_rect_padding());
255 
256   const CRect desc_back_crect(infostyle.window_border(),
257       ypos + title_rect.size.height + infostyle.row_rect_padding(),
258       infostyle.window_width() - infostyle.window_border(),
259       ypos + title_rect.size.height + infostyle.row_rect_padding() +
260       desc_rect.size.height + infostyle.row_rect_padding());
261 
262   if (usages.has_focused_index() && (row == usages.focused_index())) {
263     const CRect selected_rect(infostyle.window_border(), ypos,
264         infostyle.window_width() - infostyle.window_border(),
265         ypos + title_rect.size.height + desc_rect.size.height
266          + infostyle.row_rect_padding() * 2);
267     dc.FillSolidRect(&selected_rect,
268         RGB(infostyle.focused_background_color().r(),
269             infostyle.focused_background_color().g(),
270             infostyle.focused_background_color().b()));
271     dc.SetDCBrushColor(
272         RGB(infostyle.focused_border_color().r(),
273             infostyle.focused_border_color().g(),
274             infostyle.focused_border_color().b()));
275     dc.FrameRect(&selected_rect,
276         static_cast<HBRUSH>(GetStockObject(DC_BRUSH)));
277   } else {
278     if (title_style.has_background_color()) {
279       dc.FillSolidRect(&title_back_crect,
280           RGB(title_style.background_color().r(),
281               title_style.background_color().g(),
282               title_style.background_color().b()));
283     } else {
284       dc.FillSolidRect(&title_back_crect,
285           RGB(255, 255, 255));
286     }
287     if (desc_style.has_background_color()) {
288       dc.FillSolidRect(&desc_back_crect,
289           RGB(title_style.background_color().r(),
290               title_style.background_color().g(),
291               title_style.background_color().b()));
292     } else {
293       dc.FillSolidRect(&desc_back_crect,
294           RGB(255, 255, 255));
295     }
296   }
297 
298   text_renderer_->RenderText(dc,  title_str,  title_rect,
299       TextRenderer::FONTSET_INFOLIST_TITLE);
300   text_renderer_->RenderText(dc, desc_str, desc_rect,
301       TextRenderer::FONTSET_INFOLIST_DESCRIPTION);
302   return Size(0, row_height);
303 }
304 
OnSettingChange(UINT uFlags,LPCTSTR)305 void InfolistWindow::OnSettingChange(UINT uFlags, LPCTSTR /*lpszSection*/) {
306   // Since TextRenderer uses dialog font to render,
307   // we monitor font-related parameters to know when the font style is changed.
308   switch (uFlags) {
309     case 0x1049:  // = SPI_SETCLEARTYPE
310     case SPI_SETFONTSMOOTHING:
311     case SPI_SETFONTSMOOTHINGCONTRAST:
312     case SPI_SETFONTSMOOTHINGORIENTATION:
313     case SPI_SETFONTSMOOTHINGTYPE:
314     case SPI_SETNONCLIENTMETRICS:
315       metrics_changed_ = true;
316       break;
317     default:
318       // We ignore other changes.
319       break;
320   }
321 }
322 
OnTimer(UINT_PTR nIDEvent)323 void InfolistWindow::OnTimer(UINT_PTR nIDEvent) {
324   if (nIDEvent != kIdDelayShowHideTimer) {
325     return;
326   }
327   if (visible_) {
328     DelayShow(0);
329   } else {
330     DelayHide(0);
331   }
332 }
333 
DelayShow(UINT mseconds)334 void InfolistWindow::DelayShow(UINT mseconds) {
335   visible_ = true;
336   KillTimer(kIdDelayShowHideTimer);
337   if (mseconds <= 0) {
338     const bool current_visible = (IsWindowVisible() != FALSE);
339     SetWindowPos(HWND_TOPMOST, 0, 0, 0, 0,
340         SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
341     SendMessageW(WM_NCACTIVATE, FALSE);
342     if (!current_visible) {
343       SendUsageStatsEvent(send_command_interface_,
344           SessionCommand::INFOLIST_WINDOW_SHOW);
345     }
346   } else {
347     SetTimer(kIdDelayShowHideTimer, mseconds, nullptr);
348   }
349 }
350 
DelayHide(UINT mseconds)351 void InfolistWindow::DelayHide(UINT mseconds) {
352   visible_ = false;
353   KillTimer(kIdDelayShowHideTimer);
354   if (mseconds <= 0) {
355     const bool current_visible = (IsWindowVisible() != FALSE);
356     ShowWindow(SW_HIDE);
357     if (current_visible) {
358       SendUsageStatsEvent(send_command_interface_,
359           SessionCommand::INFOLIST_WINDOW_HIDE);
360     }
361   } else {
362     SetTimer(kIdDelayShowHideTimer, mseconds, nullptr);
363   }
364 }
365 
UpdateLayout(const commands::Candidates & candidates)366 void InfolistWindow::UpdateLayout(const commands::Candidates &candidates) {
367   candidates_->CopyFrom(candidates);
368 
369   // If we detect any change of font parameters, update text renderer
370   if (metrics_changed_) {
371     text_renderer_->OnThemeChanged();
372     metrics_changed_ = false;
373   }
374 }
375 
SetSendCommandInterface(client::SendCommandInterface * send_command_interface)376 void InfolistWindow::SetSendCommandInterface(
377   client::SendCommandInterface *send_command_interface) {
378   send_command_interface_ = send_command_interface;
379 }
380 
GetLayoutSize()381 Size InfolistWindow::GetLayoutSize() {
382   CDCHandle dmyDc(nullptr);
383   return DoPaint(dmyDc);
384 }
385 }  // namespace win32
386 }  // namespace renderer
387 }  // namespace mozc
388