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/window_manager.h"
31
32 #include <algorithm>
33 #include <limits>
34
35 #define _ATL_NO_AUTOMATIC_NAMESPACE
36 #define _WTL_NO_AUTOMATIC_NAMESPACE
37
38 #include "base/coordinates.h"
39 #include "base/logging.h"
40 #include "base/util.h"
41 #include "protocol/renderer_command.pb.h"
42 #include "renderer/renderer_interface.h"
43 #include "renderer/win32/candidate_window.h"
44 #include "renderer/win32/composition_window.h"
45 #include "renderer/win32/indicator_window.h"
46 #include "renderer/win32/infolist_window.h"
47 #include "renderer/win32/win32_renderer_util.h"
48 #include "renderer/window_util.h"
49
50 namespace mozc {
51 namespace renderer {
52 namespace win32 {
53
54 using WTL::CPoint;
55 using WTL::CRect;
56
57 namespace {
58
59 const uint32 kHideWindowDelay = 500; // msec
60 const POINT kInvalidMousePosition = {-65535, -65535};
61
62 } // namespace
63
WindowManager()64 WindowManager::WindowManager()
65 : main_window_(new CandidateWindow),
66 cascading_window_(new CandidateWindow),
67 composition_window_list_(CompositionWindowList::CreateInstance()),
68 indicator_window_(new IndicatorWindow),
69 infolist_window_(new InfolistWindow),
70 layout_manager_(new LayoutManager),
71 working_area_(WorkingAreaFactory::Create()),
72 send_command_interface_(nullptr),
73 last_position_(kInvalidMousePosition),
74 candidates_finger_print_(0),
75 thread_id_(0) {}
76
~WindowManager()77 WindowManager::~WindowManager() {}
78
Initialize()79 void WindowManager::Initialize() {
80 DCHECK(!main_window_->IsWindow());
81 DCHECK(!cascading_window_->IsWindow());
82 DCHECK(!infolist_window_->IsWindow());
83
84 main_window_->Create(nullptr);
85 main_window_->ShowWindow(SW_HIDE);
86 cascading_window_->Create(nullptr);
87 cascading_window_->ShowWindow(SW_HIDE);
88 indicator_window_->Initialize();
89 infolist_window_->Create(nullptr);
90 infolist_window_->ShowWindow(SW_HIDE);
91 composition_window_list_->Initialize();
92 }
93
AsyncHideAllWindows()94 void WindowManager::AsyncHideAllWindows() {
95 cascading_window_->ShowWindowAsync(SW_HIDE);
96 main_window_->ShowWindowAsync(SW_HIDE);
97 infolist_window_->ShowWindowAsync(SW_HIDE);
98 composition_window_list_->AsyncHide();
99 }
100
AsyncQuitAllWindows()101 void WindowManager::AsyncQuitAllWindows() {
102 cascading_window_->PostMessage(WM_CLOSE, 0, 0);
103 main_window_->PostMessage(WM_CLOSE, 0, 0);
104 infolist_window_->PostMessage(WM_CLOSE, 0, 0);
105 composition_window_list_->AsyncQuit();
106 }
107
DestroyAllWindows()108 void WindowManager::DestroyAllWindows() {
109 if (main_window_->IsWindow()) {
110 main_window_->DestroyWindow();
111 }
112 if (cascading_window_->IsWindow()) {
113 cascading_window_->DestroyWindow();
114 }
115 indicator_window_->Destroy();
116 if (infolist_window_->IsWindow()) {
117 infolist_window_->DestroyWindow();
118 }
119 composition_window_list_->Destroy();
120 }
121
HideAllWindows()122 void WindowManager::HideAllWindows() {
123 main_window_->ShowWindow(SW_HIDE);
124 cascading_window_->ShowWindow(SW_HIDE);
125 indicator_window_->Hide();
126 infolist_window_->DelayHide(0);
127 composition_window_list_->Hide();
128 }
129
130 // TODO(yukawa): Refactor this method by making a new method in LayoutManager
131 // with unit tests so that LayoutManager can handle both composition windows
132 // and candidate windows.
UpdateLayoutIMM32(const commands::RendererCommand & command)133 void WindowManager::UpdateLayoutIMM32(
134 const commands::RendererCommand &command) {
135 typedef mozc::commands::RendererCommand::CandidateForm CandidateForm;
136 typedef mozc::commands::RendererCommand::ApplicationInfo ApplicationInfo;
137
138 // Hide all UI elements if |command.visible()| is false.
139 if (!command.visible()) {
140 composition_window_list_->Hide();
141 cascading_window_->ShowWindow(SW_HIDE);
142 main_window_->ShowWindow(SW_HIDE);
143 indicator_window_->Hide();
144 infolist_window_->DelayHide(0);
145 return;
146 }
147
148 // We assume |output| exists in the renderer command
149 // for all IMM32 renderer messages.
150 DCHECK(command.has_output());
151 const commands::Output &output = command.output();
152
153 // We assume |application_info| exists in the renderer command
154 // for all IMM32 renderer messages.
155 DCHECK(command.has_application_info());
156
157 const commands::RendererCommand::ApplicationInfo &app_info =
158 command.application_info();
159
160 const HWND target_window_handle =
161 reinterpret_cast<HWND>(app_info.target_window_handle());
162 bool show_candidate =
163 ((app_info.ui_visibilities() & ApplicationInfo::ShowCandidateWindow) ==
164 ApplicationInfo::ShowCandidateWindow);
165 bool show_suggest =
166 ((app_info.ui_visibilities() & ApplicationInfo::ShowSuggestWindow) ==
167 ApplicationInfo::ShowSuggestWindow);
168 const bool show_composition =
169 ((app_info.ui_visibilities() & ApplicationInfo::ShowCompositionWindow) ==
170 ApplicationInfo::ShowCompositionWindow);
171
172 CandidateWindowLayout candidate_layout;
173 std::vector<CompositionWindowLayout> layouts;
174 if (show_composition) {
175 if (!layout_manager_->LayoutCompositionWindow(
176 command, &layouts, &candidate_layout)) {
177 candidate_layout.Clear();
178 layouts.clear();
179 show_candidate = false;
180 show_suggest = false;
181 }
182 }
183
184 bool is_suggest = false;
185 bool is_convert_or_predict = false;
186 if (output.has_candidates() && output.candidates().has_category()) {
187 switch (output.candidates().category()) {
188 case commands::SUGGESTION:
189 is_suggest = true;
190 break;
191 case commands::CONVERSION:
192 case commands::PREDICTION:
193 is_convert_or_predict = true;
194 break;
195 default:
196 // do nothing.
197 break;
198 }
199 }
200
201 // Currently the indicator will be displayed if and only if no other window
202 // (suggestion, prediction, nor conversion) is not displayed.
203 if (is_suggest || is_convert_or_predict) {
204 indicator_window_->Hide();
205 } else if (app_info.has_indicator_info()) {
206 indicator_window_->OnUpdate(command, layout_manager_.get());
207 }
208
209 // CompositionWindowList::UpdateLayout will hides all windows if
210 // |layouts| is empty.
211 composition_window_list_->UpdateLayout(layouts);
212
213 if (!output.has_candidates()) {
214 // Hide candidate windows because there is no candidate to be displayed.
215 cascading_window_->ShowWindow(SW_HIDE);
216 main_window_->ShowWindow(SW_HIDE);
217 infolist_window_->DelayHide(0);
218 return;
219 }
220
221 if (is_suggest && !show_suggest) {
222 // The candidate list is for suggestion but the visibility bit is off.
223 cascading_window_->ShowWindow(SW_HIDE);
224 main_window_->ShowWindow(SW_HIDE);
225 infolist_window_->DelayHide(0);
226 return;
227 }
228
229 if (is_convert_or_predict && !show_candidate) {
230 // The candidate list is for conversion or prediction but the visibility
231 // bit is off.
232 cascading_window_->ShowWindow(SW_HIDE);
233 main_window_->ShowWindow(SW_HIDE);
234 infolist_window_->DelayHide(0);
235 return;
236 }
237
238 const commands::Candidates &candidates = output.candidates();
239 if (candidates.candidate_size() == 0) {
240 cascading_window_->ShowWindow(SW_HIDE);
241 main_window_->ShowWindow(SW_HIDE);
242 infolist_window_->DelayHide(0);
243 return;
244 }
245
246 if (!candidate_layout.initialized()) {
247 candidate_layout.Clear();
248 if (is_suggest) {
249 layout_manager_->LayoutCandidateWindowForSuggestion(
250 app_info, &candidate_layout);
251 } else if (is_convert_or_predict) {
252 layout_manager_->LayoutCandidateWindowForConversion(
253 app_info, &candidate_layout);
254 }
255 }
256
257 if (!candidate_layout.initialized()) {
258 cascading_window_->ShowWindow(SW_HIDE);
259 main_window_->ShowWindow(SW_HIDE);
260 infolist_window_->DelayHide(0);
261 return;
262 }
263
264 // Currently, we do not use finger print.
265 bool candidate_changed = true;
266
267 if (candidate_changed && (candidates.display_type() == commands::MAIN)) {
268 main_window_->UpdateLayout(candidates);
269 }
270 const Size main_window_size = main_window_->GetLayoutSize();
271
272 const Point target_point(candidate_layout.position().x,
273 candidate_layout.position().y);
274
275 // Obtain the monitor's working area
276 Rect working_area;
277 {
278 CRect area;
279 if (working_area_->GetWorkingAreaFromPoint(
280 CPoint(target_point.x, target_point.y), &area)) {
281 working_area = Rect(area.left, area.top, area.Width(), area.Height());
282 }
283 }
284
285 // We prefer the left position of candidate strings is aligned to
286 // that of preedit.
287 const Point main_window_zero_point(
288 main_window_->GetCandidateColumnInClientCord().Left(), 0);
289
290 Rect main_window_rect;
291 if (candidate_layout.has_exclude_region()) {
292 // Equating |exclusion_area| with |preedit_rect| generally works well and
293 // makes most of users happy.
294 const CRect rect(candidate_layout.exclude_region());
295 const Rect preedit_rect(rect.left, rect.top, rect.Width(), rect.Height());
296 const bool vertical = (LayoutManager::GetWritingDirection(app_info) ==
297 LayoutManager::VERTICAL_WRITING);
298 // Sometimes |target_point| is set to the top-left of the exclusion area
299 // but WindowUtil does not support this case yet.
300 // As a workaround, use |preedit_rect.Bottom()| for y-coordinate of the
301 // |target_point|.
302 // TODO(yukawa): Fix WindowUtil to support this case.
303 // TODO(yukawa): Add more unit tests.
304 Point new_target_point = target_point;
305 if (!vertical) {
306 new_target_point.y = preedit_rect.Bottom();
307 }
308 main_window_rect =
309 WindowUtil::GetWindowRectForMainWindowFromTargetPointAndPreedit(
310 new_target_point, preedit_rect, main_window_size,
311 main_window_zero_point, working_area, vertical);
312 } else {
313 main_window_rect =
314 WindowUtil::GetWindowRectForMainWindowFromTargetPoint(
315 target_point, main_window_size, main_window_zero_point,
316 working_area);
317 }
318
319 const DWORD set_windows_pos_flags = SWP_NOACTIVATE | SWP_SHOWWINDOW;
320 main_window_->SetWindowPos(HWND_TOPMOST,
321 main_window_rect.Left(),
322 main_window_rect.Top(),
323 main_window_rect.Width(),
324 main_window_rect.Height(),
325 set_windows_pos_flags);
326 // This trick ensures that the window is certainly shown as 'inactivated'
327 // in terms of visual effect on DWM-enabled desktop.
328 main_window_->SendMessageW(WM_NCACTIVATE, FALSE);
329
330 bool cascading_visible = false;
331
332 if (candidates.has_subcandidates() &&
333 candidates.subcandidates().display_type() == commands::CASCADE) {
334 const commands::Candidates &subcandidates = candidates.subcandidates();
335 cascading_visible = true;
336 }
337
338 bool infolist_visible = false;
339 if (command.output().has_candidates() &&
340 command.output().candidates().has_usages() &&
341 command.output().candidates().usages().information_size() > 0) {
342 infolist_visible = true;
343 }
344
345 if (infolist_visible && !cascading_visible) {
346 if (candidate_changed) {
347 infolist_window_->UpdateLayout(candidates);
348 infolist_window_->Invalidate();
349 }
350
351 // Align infolist window
352 const Rect infolist_rect =
353 WindowUtil::GetWindowRectForInfolistWindow(
354 infolist_window_->GetLayoutSize(),
355 main_window_rect, working_area);
356 infolist_window_->MoveWindow(infolist_rect.Left(),
357 infolist_rect.Top(),
358 infolist_rect.Width(),
359 infolist_rect.Height(),
360 TRUE);
361 infolist_window_->SetWindowPos(HWND_TOPMOST, 0, 0, 0, 0,
362 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
363
364 const int mode = layout_manager_->GetCompatibilityMode(app_info);
365
366 // If SHOW_INFOLIST_IMMEDIATELY flag is set, we should show the InfoList
367 // without delay. See the comment of SHOW_INFOLIST_IMMEDIATELY in
368 // win32_renderer_util.h or b/5824433 for details.
369 uint32 maximum_delay = std::numeric_limits<int32>::max();
370 if ((mode & SHOW_INFOLIST_IMMEDIATELY) == SHOW_INFOLIST_IMMEDIATELY) {
371 maximum_delay = 0;
372 }
373
374 const uint32 hide_window_delay = std::min(maximum_delay, kHideWindowDelay);
375 if (candidates.has_focused_index() && candidates.candidate_size() > 0) {
376 const int focused_row =
377 candidates.focused_index() - candidates.candidate(0).index();
378 if (candidates.candidate_size() >= focused_row &&
379 candidates.candidate(focused_row).has_information_id()) {
380 const uint32 raw_delay =
381 std::max(static_cast<uint32>(0),
382 command.output().candidates().usages().delay());
383 const uint32 delay = std::min(maximum_delay, raw_delay);
384 infolist_window_->DelayShow(delay);
385 } else {
386 infolist_window_->DelayHide(hide_window_delay);
387 }
388 } else {
389 infolist_window_->DelayHide(hide_window_delay);
390 }
391 } else {
392 // Hide infolist window immediately.
393 infolist_window_->DelayHide(0);
394 }
395
396 if (cascading_visible) {
397 const commands::Candidates &subcandidates = candidates.subcandidates();
398
399 if (candidate_changed) {
400 cascading_window_->UpdateLayout(subcandidates);
401 }
402
403 // Put the cascading window right to the selected row of this candidate
404 // window.
405 const Rect selected_row = main_window_->GetSelectionRectInScreenCord();
406 const Rect selected_row_with_window_border(
407 Point(main_window_rect.Left(), selected_row.Top()),
408 Size(main_window_rect.Right() - main_window_rect.Left(),
409 selected_row.Top() - selected_row.Bottom()));
410
411 // We prefer the top of client area of the cascading window is
412 // aligned to the top of selected candidate in the candidate window.
413 const Point cascading_window_zero_point(
414 0, cascading_window_->GetFirstRowInClientCord().Top());
415
416 const Size cascading_window_size = cascading_window_->GetLayoutSize();
417
418 // cascading window should be in the same working area as the main window.
419 const Rect cascading_window_rect =
420 WindowUtil::GetWindowRectForCascadingWindow(
421 selected_row_with_window_border, cascading_window_size,
422 cascading_window_zero_point, working_area);
423
424 cascading_window_->SetWindowPos(HWND_TOPMOST,
425 cascading_window_rect.Left(),
426 cascading_window_rect.Top(),
427 cascading_window_rect.Width(),
428 cascading_window_rect.Height(),
429 set_windows_pos_flags);
430 // This trick ensures that the window is certainly shown as 'inactivated'
431 // in terms of visual effect on DWM-enabled desktop.
432 cascading_window_->SendMessageW(WM_NCACTIVATE, FALSE);
433 if (candidate_changed) {
434 main_window_->Invalidate();
435 cascading_window_->Invalidate();
436 }
437 } else {
438 // no cascading window
439 if (candidate_changed) {
440 main_window_->Invalidate();
441 }
442 cascading_window_->ShowWindow(SW_HIDE);
443 }
444 }
445
UpdateLayoutTSF(const commands::RendererCommand & command)446 void WindowManager::UpdateLayoutTSF(const commands::RendererCommand &command) {
447 // Currently implemented by IMM32 implementation.
448 // TODO(yukawa): Implement TSF version.
449 UpdateLayoutIMM32(command);
450 }
451
IsAvailable() const452 bool WindowManager::IsAvailable() const {
453 return main_window_->IsWindow() &&
454 cascading_window_->IsWindow() &&
455 infolist_window_->IsWindow();
456 }
457
SetSendCommandInterface(client::SendCommandInterface * send_command_interface)458 void WindowManager::SetSendCommandInterface(
459 client::SendCommandInterface *send_command_interface) {
460 main_window_->SetSendCommandInterface(send_command_interface);
461 cascading_window_->SetSendCommandInterface(send_command_interface);
462 infolist_window_->SetSendCommandInterface(send_command_interface);
463 }
464
PreTranslateMessage(const MSG & message)465 void WindowManager::PreTranslateMessage(const MSG &message) {
466 if (message.message != WM_MOUSEMOVE) {
467 return;
468 }
469
470 // Window manager sometimes generates WM_MOUSEMOVE message when the contents
471 // under the mouse cursor has been changed (e.g. the window is moved) so that
472 // the mouse handler can update its cursor image based on the contents to
473 // which the cursor is newly pointing.
474 // See http://blogs.msdn.com/b/oldnewthing/archive/2003/10/01/55108.aspx for
475 // details about such kind of phantom WM_MOUSEMOVE. See also b/3104996.
476 // Here we compares the screen coordinate of the mouse cursor with the last
477 // one to determine if this WM_MOUSEMOVE is an artificial one or not.
478 // If the coordinate is the same, this is an artificial WM_MOUSEMOVE.
479 bool is_moving = true;
480 const CPoint cursor_pos_in_client_coords(GET_X_LPARAM(message.lParam),
481 GET_Y_LPARAM(message.lParam));
482 CPoint cursor_pos_in_logical_coords;
483 if (layout_manager_->ClientPointToScreen(
484 message.hwnd, cursor_pos_in_client_coords,
485 &cursor_pos_in_logical_coords)) {
486 // Since the renderer process is DPI-aware, we can safely use this
487 // (logical) coordinates as if it is real (physical) screen coordinates.
488 if (cursor_pos_in_logical_coords == last_position_) {
489 is_moving = false;
490 }
491 last_position_ = cursor_pos_in_logical_coords;
492 }
493
494 // Notify candidate windows if the cursor is moving or not so that they can
495 // filter unnecessary WM_MOUSEMOVE events.
496 main_window_->set_mouse_moving(is_moving);
497 cascading_window_->set_mouse_moving(is_moving);
498 }
499
500 } // namespace win32
501 } // namespace renderer
502 } // namespace mozc
503