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