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/indicator_window.h"
31 
32 #include <windows.h>
33 #define _ATL_NO_AUTOMATIC_NAMESPACE
34 #define _WTL_NO_AUTOMATIC_NAMESPACE
35 #include <atlbase.h>
36 #include <atlwin.h>
37 #include <atlapp.h>
38 #include <atlcrack.h>
39 #include <atlmisc.h>
40 
41 #include <algorithm>
42 #include <vector>
43 
44 #include "base/const.h"
45 #include "base/logging.h"
46 #include "base/util.h"
47 #include "protocol/renderer_command.pb.h"
48 #include "renderer/win32/win32_image_util.h"
49 #include "renderer/win32/win32_renderer_util.h"
50 
51 namespace mozc {
52 namespace renderer {
53 namespace win32 {
54 
55 namespace {
56 
57 using ATL::CWinTraits;
58 using ATL::CWindow;
59 using ATL::CWindowImpl;
60 using WTL::CBitmap;
61 using WTL::CBitmapHandle;
62 using WTL::CDC;
63 using WTL::CLogFont;
64 using WTL::CPoint;
65 using WTL::CSize;
66 
67 using ::mozc::commands::Status;
68 typedef ::mozc::commands::RendererCommand::ApplicationInfo ApplicationInfo;
69 
70 // 96 DPI is the default DPI in Windows.
71 const int kDefaultDPI = 96;
72 
73 
74 // As Discussed in b/2317702, UI windows are disabled by default because it is
75 // hard for a user to find out what caused the problem than finding that the
76 // operations seems to be disabled on the UI window when
77 // SPI_GETACTIVEWINDOWTRACKING is enabled.
78 // TODO(yukawa): Support mouse operations before we add a GUI feature which
79 //     requires UI interaction by mouse and/or touch. (b/2954874)
80 typedef CWinTraits<
81     WS_POPUP | WS_DISABLED,
82     WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOACTIVATE>
83     IndicatorWindowTraits;
84 
85 struct Sprite {
86   CBitmap bitmap;
87   CPoint offset;
88 };
89 
90 // Timer event IDs
91 const UINT_PTR kTimerEventFadeStart = 0;
92 const UINT_PTR kTimerEventFading = 1;
93 
94 const DWORD kStartFadingOutDelay = 2500;  // msec
95 const DWORD kFadingOutInterval = 16;      // msec
96 const int kFadingOutAlphaDelta = 32;
97 
GetDPIScaling()98 double GetDPIScaling() {
99   CDC desktop_dc(::GetDC(NULL));
100   const int dpi_x = desktop_dc.GetDeviceCaps(LOGPIXELSX);
101   return static_cast<double>(dpi_x) / kDefaultDPI;
102 }
103 
104 }  // namespace
105 
106 class IndicatorWindow::WindowImpl
107     : public CWindowImpl<IndicatorWindow::WindowImpl,
108                          CWindow,
109                          IndicatorWindowTraits> {
110  public:
111   DECLARE_WND_CLASS_EX(kIndicatorWindowClassName, 0, COLOR_WINDOW);
WindowImpl()112   WindowImpl()
113       : alpha_(255),
114         dpi_scaling_(GetDPIScaling()) {
115     sprites_.resize(commands::NUM_OF_COMPOSITIONS);
116   }
117 
118   BEGIN_MSG_MAP_EX(WindowImpl)
MSG_WM_CREATE(OnCreate)119     MSG_WM_CREATE(OnCreate)
120     MSG_WM_TIMER(OnTimer)
121     MSG_WM_SETTINGCHANGE(OnSettingChange)
122   END_MSG_MAP()
123 
124   void OnUpdate(const commands::RendererCommand &command,
125                 LayoutManager *layout_manager) {
126     KillTimer(kTimerEventFading);
127     KillTimer(kTimerEventFadeStart);
128 
129     bool visible = false;
130     IndicatorWindowLayout indicator_layout;
131     if (command.has_visible() &&
132         command.visible() &&
133         command.has_application_info() &&
134         command.application_info().has_indicator_info() &&
135         command.application_info().indicator_info().has_status()) {
136       const ApplicationInfo &app_info = command.application_info();
137       visible = layout_manager->LayoutIndicatorWindow(app_info,
138                                                       &indicator_layout);
139     }
140     if (!visible) {
141       HideIndicator();
142       return;
143     }
144     DCHECK(command.has_application_info());
145     DCHECK(command.application_info().has_indicator_info());
146     DCHECK(command.application_info().indicator_info().has_status());
147     const Status &status =
148         command.application_info().indicator_info().status();
149 
150     alpha_ = 255;
151     current_image_ = sprites_[commands::DIRECT].bitmap;
152     CPoint offset = sprites_[commands::DIRECT].offset;
153     if (!status.has_activated() || !status.has_mode() ||
154         !status.activated()) {
155       current_image_ = sprites_[commands::DIRECT].bitmap;
156       offset = sprites_[commands::DIRECT].offset;
157     } else {
158       const int mode = status.mode();
159       switch (mode) {
160         case commands::HIRAGANA:
161         case commands::FULL_KATAKANA:
162         case commands::HALF_ASCII:
163         case commands::FULL_ASCII:
164         case commands::HALF_KATAKANA:
165           current_image_ = sprites_[mode].bitmap;
166           offset = sprites_[mode].offset;
167           break;
168       }
169     }
170     if (current_image_ == nullptr) {
171       HideIndicator();
172       return;
173     }
174     top_left_ = CPoint(indicator_layout.window_rect.left - offset.x,
175                        indicator_layout.window_rect.bottom - offset.y);
176     UpdateWindow();
177 
178     // Start fading out.
179     SetTimer(kTimerEventFadeStart, kStartFadingOutDelay);
180   }
181 
HideIndicator()182   void HideIndicator() {
183     KillTimer(kTimerEventFading);
184     KillTimer(kTimerEventFadeStart);
185     ShowWindow(SW_HIDE);
186   }
187 
188  private:
UpdateWindow()189   void UpdateWindow() {
190     CSize size;
191     current_image_.GetSize(size);
192 
193     CDC dc;
194     dc.CreateCompatibleDC();
195 
196     // Fading out animation.
197     CPoint top_left = top_left_;
198     top_left.y += (255 - alpha_) / 32;
199 
200     CPoint src_left_top(0, 0);
201     BLENDFUNCTION func = {AC_SRC_OVER, 0, alpha_, AC_SRC_ALPHA};
202 
203     const CBitmapHandle old_bitmap = dc.SelectBitmap(current_image_);
204     const BOOL result = ::UpdateLayeredWindow(
205         m_hWnd, nullptr, &top_left, &size, dc, &src_left_top, 0, &func,
206         ULW_ALPHA);
207     dc.SelectBitmap(old_bitmap);
208     ShowWindow(SW_SHOWNA);
209   }
210 
OnCreate(LPCREATESTRUCT create_struct)211   LRESULT OnCreate(LPCREATESTRUCT create_struct) {
212     EnableOrDisableWindowForWorkaround();
213     const int kModes[] = {
214       commands::DIRECT,
215       commands::HIRAGANA,
216       commands::FULL_KATAKANA,
217       commands::HALF_ASCII,
218       commands::FULL_ASCII,
219       commands::HALF_KATAKANA,
220     };
221     for (size_t i = 0; i < arraysize(kModes); ++i) {
222       LoadSprite(kModes[i]);
223     }
224     return 1;
225   }
226 
OnTimer(UINT_PTR event_id)227   void OnTimer(UINT_PTR event_id) {
228     switch (event_id) {
229       case kTimerEventFadeStart:
230         KillTimer(kTimerEventFadeStart);
231         SetTimer(kTimerEventFading, kFadingOutInterval);
232         break;
233       case kTimerEventFading:
234         alpha_ = std::max(static_cast<int>(alpha_) - kFadingOutAlphaDelta, 0);
235         if (alpha_ == 0) {
236           KillTimer(kTimerEventFading);
237         }
238         UpdateWindow();
239         break;
240     }
241   }
242 
OnSettingChange(UINT flags,LPCTSTR)243   void OnSettingChange(UINT flags, LPCTSTR /*lpszSection*/) {
244     switch (flags) {
245       case SPI_SETACTIVEWINDOWTRACKING:
246         EnableOrDisableWindowForWorkaround();
247       default:
248         // We ignore other changes.
249         break;
250     }
251   }
252 
EnableOrDisableWindowForWorkaround()253   void EnableOrDisableWindowForWorkaround() {
254     // Disable the window if SPI_GETACTIVEWINDOWTRACKING is enabled.
255     // See b/2317702 for details.
256     // TODO(yukawa): Support mouse operations before we add a GUI feature which
257     //   requires UI interaction by mouse and/or touch. (b/2954874)
258     BOOL is_tracking_enabled = FALSE;
259     if (::SystemParametersInfo(SPI_GETACTIVEWINDOWTRACKING,
260                                0, &is_tracking_enabled, 0)) {
261       EnableWindow(!is_tracking_enabled);
262     }
263   }
264 
LoadSprite(int mode)265   void LoadSprite(int mode) {
266     BalloonImage::BalloonImageInfo info;
267     CLogFont logfont;
268     logfont.SetMessageBoxFont();
269     Util::WideToUTF8(logfont.lfFaceName, &info.label_font);
270 
271     info.frame_color = RGBColor(1, 122, 204);
272     info.blur_color = RGBColor(1, 122, 204);
273     info.rect_width = ceil(dpi_scaling_ * 45.0);   // snap to pixel alignment
274     info.rect_height = ceil(dpi_scaling_ * 45.0);  // snap to pixel alignment
275     info.corner_radius = dpi_scaling_ * 0.0;
276     info.tail_height = dpi_scaling_ * 5.0;
277     info.tail_width = dpi_scaling_ * 10.0;
278     info.blur_sigma = dpi_scaling_ * 3.0;
279     info.blur_alpha = 0.5;
280     info.frame_thickness = dpi_scaling_ * 1.0;
281     info.label_size = 13.0;  // no need to be scaled.
282     info.label_color = RGBColor(0, 0, 0);
283     info.blur_offset_x = 0;
284     info.blur_offset_y = 0;
285 
286     switch (mode) {
287       case commands::DIRECT:
288         info.blur_sigma = dpi_scaling_ * 0.0;
289         info.frame_color = RGBColor(186, 186, 186);
290         info.label_color = RGBColor(0, 0, 0);
291         info.blur_sigma = dpi_scaling_ * 0.0;
292         info.frame_thickness = dpi_scaling_ * 1.0;
293         info.corner_radius = dpi_scaling_ * 0.0;
294         info.blur_offset_x = 0;
295         info.blur_offset_y = 0;
296         info.label = "A";
297         break;
298       case commands::HIRAGANA:
299         info.label = "あ";
300         break;
301       case commands::FULL_KATAKANA:
302         info.label = "ア";
303         break;
304       case commands::HALF_ASCII:
305         info.label = "_A";
306         break;
307       case commands::FULL_ASCII:
308         info.label = "A";
309         break;
310       case commands::HALF_KATAKANA:
311         info.label = "_ア";
312         break;
313     }
314     if (!info.label.empty()) {
315       sprites_[mode].bitmap.Attach(
316           BalloonImage::Create(info, &sprites_[mode].offset));
317     }
318   }
319 
320   CBitmapHandle current_image_;
321   CPoint top_left_;
322   BYTE alpha_;
323   double dpi_scaling_;
324   std::vector<Sprite> sprites_;
325 
326   DISALLOW_COPY_AND_ASSIGN(WindowImpl);
327 };
328 
IndicatorWindow()329 IndicatorWindow::IndicatorWindow()
330     : impl_(new WindowImpl) {}
331 
~IndicatorWindow()332 IndicatorWindow::~IndicatorWindow() {
333   impl_->DestroyWindow();
334 }
335 
Initialize()336 void IndicatorWindow::Initialize() {
337   impl_->Create(nullptr);
338   impl_->ShowWindow(SW_HIDE);
339 }
340 
Destroy()341 void IndicatorWindow::Destroy() {
342   impl_->DestroyWindow();
343 }
344 
OnUpdate(const commands::RendererCommand & command,LayoutManager * layout_manager)345 void IndicatorWindow::OnUpdate(const commands::RendererCommand &command,
346                                LayoutManager *layout_manager) {
347   impl_->OnUpdate(command, layout_manager);
348 }
349 
Hide()350 void IndicatorWindow::Hide() {
351   impl_->HideIndicator();
352 }
353 
354 }  // namespace win32
355 }  // namespace renderer
356 }  // namespace mozc
357