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