| // Copyright 2010-2015, Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "renderer/win32/window_manager.h" |
| |
| #undef min |
| #undef max |
| #include <limits> |
| |
| #define _ATL_NO_AUTOMATIC_NAMESPACE |
| #define _WTL_NO_AUTOMATIC_NAMESPACE |
| |
| #include "base/coordinates.h" |
| #include "base/logging.h" |
| #include "base/util.h" |
| #include "renderer/renderer_command.pb.h" |
| #include "renderer/renderer_interface.h" |
| #include "renderer/win32/candidate_window.h" |
| #include "renderer/win32/composition_window.h" |
| #include "renderer/win32/indicator_window.h" |
| #include "renderer/win32/infolist_window.h" |
| #include "renderer/win32/win32_renderer_util.h" |
| #include "renderer/window_util.h" |
| |
| namespace mozc { |
| namespace renderer { |
| namespace win32 { |
| |
| using WTL::CPoint; |
| using WTL::CRect; |
| |
| namespace { |
| |
| const uint32 kHideWindowDelay = 500; // msec |
| const POINT kInvalidMousePosition = {-65535, -65535}; |
| |
| } // namespace |
| |
| WindowManager::WindowManager() |
| : main_window_(new CandidateWindow), |
| cascading_window_(new CandidateWindow), |
| composition_window_list_(CompositionWindowList::CreateInstance()), |
| indicator_window_(new IndicatorWindow), |
| infolist_window_(new InfolistWindow), |
| layout_manager_(new LayoutManager), |
| working_area_(WorkingAreaFactory::Create()), |
| send_command_interface_(nullptr), |
| last_position_(kInvalidMousePosition), |
| candidates_finger_print_(0), |
| thread_id_(0) {} |
| |
| WindowManager::~WindowManager() {} |
| |
| void WindowManager::Initialize() { |
| DCHECK(!main_window_->IsWindow()); |
| DCHECK(!cascading_window_->IsWindow()); |
| DCHECK(!infolist_window_->IsWindow()); |
| |
| main_window_->Create(nullptr); |
| main_window_->ShowWindow(SW_HIDE); |
| cascading_window_->Create(nullptr); |
| cascading_window_->ShowWindow(SW_HIDE); |
| indicator_window_->Initialize(); |
| infolist_window_->Create(nullptr); |
| infolist_window_->ShowWindow(SW_HIDE); |
| composition_window_list_->Initialize(); |
| } |
| |
| void WindowManager::AsyncHideAllWindows() { |
| cascading_window_->ShowWindowAsync(SW_HIDE); |
| main_window_->ShowWindowAsync(SW_HIDE); |
| infolist_window_->ShowWindowAsync(SW_HIDE); |
| composition_window_list_->AsyncHide(); |
| } |
| |
| void WindowManager::AsyncQuitAllWindows() { |
| cascading_window_->PostMessage(WM_CLOSE, 0, 0); |
| main_window_->PostMessage(WM_CLOSE, 0, 0); |
| infolist_window_->PostMessage(WM_CLOSE, 0, 0); |
| composition_window_list_->AsyncQuit(); |
| } |
| |
| void WindowManager::DestroyAllWindows() { |
| if (main_window_->IsWindow()) { |
| main_window_->DestroyWindow(); |
| } |
| if (cascading_window_->IsWindow()) { |
| cascading_window_->DestroyWindow(); |
| } |
| indicator_window_->Destroy(); |
| if (infolist_window_->IsWindow()) { |
| infolist_window_->DestroyWindow(); |
| } |
| composition_window_list_->Destroy(); |
| } |
| |
| void WindowManager::HideAllWindows() { |
| main_window_->ShowWindow(SW_HIDE); |
| cascading_window_->ShowWindow(SW_HIDE); |
| indicator_window_->Hide(); |
| infolist_window_->DelayHide(0); |
| composition_window_list_->Hide(); |
| } |
| |
| // TODO(yukawa): Refactor this method by making a new method in LayoutManager |
| // with unit tests so that LayoutManager can handle both composition windows |
| // and candidate windows. |
| void WindowManager::UpdateLayoutIMM32( |
| const commands::RendererCommand &command) { |
| typedef mozc::commands::RendererCommand::CandidateForm CandidateForm; |
| typedef mozc::commands::RendererCommand::ApplicationInfo ApplicationInfo; |
| |
| // Hide all UI elements if |command.visible()| is false. |
| if (!command.visible()) { |
| composition_window_list_->Hide(); |
| cascading_window_->ShowWindow(SW_HIDE); |
| main_window_->ShowWindow(SW_HIDE); |
| indicator_window_->Hide(); |
| infolist_window_->DelayHide(0); |
| return; |
| } |
| |
| // We assume |output| exists in the renderer command |
| // for all IMM32 renderer messages. |
| DCHECK(command.has_output()); |
| const commands::Output &output = command.output(); |
| |
| // We assume |application_info| exists in the renderer command |
| // for all IMM32 renderer messages. |
| DCHECK(command.has_application_info()); |
| |
| const commands::RendererCommand::ApplicationInfo &app_info = |
| command.application_info(); |
| |
| const HWND target_window_handle = |
| reinterpret_cast<HWND>(app_info.target_window_handle()); |
| bool show_candidate = |
| ((app_info.ui_visibilities() & ApplicationInfo::ShowCandidateWindow) == |
| ApplicationInfo::ShowCandidateWindow); |
| bool show_suggest = |
| ((app_info.ui_visibilities() & ApplicationInfo::ShowSuggestWindow) == |
| ApplicationInfo::ShowSuggestWindow); |
| const bool show_composition = |
| ((app_info.ui_visibilities() & ApplicationInfo::ShowCompositionWindow) == |
| ApplicationInfo::ShowCompositionWindow); |
| |
| CandidateWindowLayout candidate_layout; |
| vector<CompositionWindowLayout> layouts; |
| if (show_composition) { |
| if (!layout_manager_->LayoutCompositionWindow( |
| command, &layouts, &candidate_layout)) { |
| candidate_layout.Clear(); |
| layouts.clear(); |
| show_candidate = false; |
| show_suggest = false; |
| } |
| } |
| |
| bool is_suggest = false; |
| bool is_convert_or_predict = false; |
| if (output.has_candidates() && output.candidates().has_category()) { |
| switch (output.candidates().category()) { |
| case commands::SUGGESTION: |
| is_suggest = true; |
| break; |
| case commands::CONVERSION: |
| case commands::PREDICTION: |
| is_convert_or_predict = true; |
| break; |
| default: |
| // do nothing. |
| break; |
| } |
| } |
| |
| // Currently the indicator will be displayed if and only if no other window |
| // (suggestion, prediction, nor conversion) is not displayed. |
| if (is_suggest || is_convert_or_predict) { |
| indicator_window_->Hide(); |
| } else if (app_info.has_indicator_info()) { |
| indicator_window_->OnUpdate(command, layout_manager_.get()); |
| } |
| |
| // CompositionWindowList::UpdateLayout will hides all windows if |
| // |layouts| is empty. |
| composition_window_list_->UpdateLayout(layouts); |
| |
| if (!output.has_candidates()) { |
| // Hide candidate windows because there is no candidate to be displayed. |
| cascading_window_->ShowWindow(SW_HIDE); |
| main_window_->ShowWindow(SW_HIDE); |
| infolist_window_->DelayHide(0); |
| return; |
| } |
| |
| if (is_suggest && !show_suggest) { |
| // The candidate list is for suggestion but the visibility bit is off. |
| cascading_window_->ShowWindow(SW_HIDE); |
| main_window_->ShowWindow(SW_HIDE); |
| infolist_window_->DelayHide(0); |
| return; |
| } |
| |
| if (is_convert_or_predict && !show_candidate) { |
| // The candidate list is for conversion or prediction but the visibility |
| // bit is off. |
| cascading_window_->ShowWindow(SW_HIDE); |
| main_window_->ShowWindow(SW_HIDE); |
| infolist_window_->DelayHide(0); |
| return; |
| } |
| |
| const commands::Candidates &candidates = output.candidates(); |
| if (candidates.candidate_size() == 0) { |
| cascading_window_->ShowWindow(SW_HIDE); |
| main_window_->ShowWindow(SW_HIDE); |
| infolist_window_->DelayHide(0); |
| return; |
| } |
| |
| if (!candidate_layout.initialized()) { |
| candidate_layout.Clear(); |
| if (is_suggest) { |
| layout_manager_->LayoutCandidateWindowForSuggestion( |
| app_info, &candidate_layout); |
| } else if (is_convert_or_predict) { |
| layout_manager_->LayoutCandidateWindowForConversion( |
| app_info, &candidate_layout); |
| } |
| } |
| |
| if (!candidate_layout.initialized()) { |
| cascading_window_->ShowWindow(SW_HIDE); |
| main_window_->ShowWindow(SW_HIDE); |
| infolist_window_->DelayHide(0); |
| return; |
| } |
| |
| // Currently, we do not use finger print. |
| bool candidate_changed = true; |
| |
| if (candidate_changed && (candidates.display_type() == commands::MAIN)) { |
| main_window_->UpdateLayout(candidates); |
| } |
| const Size main_window_size = main_window_->GetLayoutSize(); |
| |
| const Point target_point(candidate_layout.position().x, |
| candidate_layout.position().y); |
| |
| // Obtain the monitor's working area |
| Rect working_area; |
| { |
| CRect area; |
| if (working_area_->GetWorkingAreaFromPoint( |
| CPoint(target_point.x, target_point.y), &area)) { |
| working_area = Rect(area.left, area.top, area.Width(), area.Height()); |
| } |
| } |
| |
| // We prefer the left position of candidate strings is aligned to |
| // that of preedit. |
| const Point main_window_zero_point( |
| main_window_->GetCandidateColumnInClientCord().Left(), 0); |
| |
| Rect main_window_rect; |
| if (candidate_layout.has_exclude_region()) { |
| // Equating |exclusion_area| with |preedit_rect| generally works well and |
| // makes most of users happy. |
| const CRect rect(candidate_layout.exclude_region()); |
| const Rect preedit_rect(rect.left, rect.top, rect.Width(), rect.Height()); |
| const bool vertical = (LayoutManager::GetWritingDirection(app_info) == |
| LayoutManager::VERTICAL_WRITING); |
| // Sometimes |target_point| is set to the top-left of the exclusion area |
| // but WindowUtil does not support this case yet. |
| // As a workaround, use |preedit_rect.Bottom()| for y-coordinate of the |
| // |target_point|. |
| // TODO(yukawa): Fix WindowUtil to support this case. |
| // TODO(yukawa): Add more unit tests. |
| Point new_target_point = target_point; |
| if (!vertical) { |
| new_target_point.y = preedit_rect.Bottom(); |
| } |
| main_window_rect = |
| WindowUtil::GetWindowRectForMainWindowFromTargetPointAndPreedit( |
| new_target_point, preedit_rect, main_window_size, |
| main_window_zero_point, working_area, vertical); |
| } else { |
| main_window_rect = |
| WindowUtil::GetWindowRectForMainWindowFromTargetPoint( |
| target_point, main_window_size, main_window_zero_point, |
| working_area); |
| } |
| |
| const DWORD set_windows_pos_flags = SWP_NOACTIVATE | SWP_SHOWWINDOW; |
| main_window_->SetWindowPos(HWND_TOPMOST, |
| main_window_rect.Left(), |
| main_window_rect.Top(), |
| main_window_rect.Width(), |
| main_window_rect.Height(), |
| set_windows_pos_flags); |
| // This trick ensures that the window is certainly shown as 'inactivated' |
| // in terms of visual effect on DWM-enabled desktop. |
| main_window_->SendMessageW(WM_NCACTIVATE, FALSE); |
| |
| bool cascading_visible = false; |
| |
| if (candidates.has_subcandidates() && |
| candidates.subcandidates().display_type() == commands::CASCADE) { |
| const commands::Candidates &subcandidates = candidates.subcandidates(); |
| cascading_visible = true; |
| } |
| |
| bool infolist_visible = false; |
| if (command.output().has_candidates() && |
| command.output().candidates().has_usages() && |
| command.output().candidates().usages().information_size() > 0) { |
| infolist_visible = true; |
| } |
| |
| if (infolist_visible && !cascading_visible) { |
| if (candidate_changed) { |
| infolist_window_->UpdateLayout(candidates); |
| infolist_window_->Invalidate(); |
| } |
| |
| // Align infolist window |
| const Rect infolist_rect = |
| WindowUtil::GetWindowRectForInfolistWindow( |
| infolist_window_->GetLayoutSize(), |
| main_window_rect, working_area); |
| infolist_window_->MoveWindow(infolist_rect.Left(), |
| infolist_rect.Top(), |
| infolist_rect.Width(), |
| infolist_rect.Height(), |
| TRUE); |
| infolist_window_->SetWindowPos(HWND_TOPMOST, 0, 0, 0, 0, |
| SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); |
| |
| const int mode = layout_manager_->GetCompatibilityMode(app_info); |
| |
| // If SHOW_INFOLIST_IMMEDIATELY flag is set, we should show the InfoList |
| // without delay. See the comment of SHOW_INFOLIST_IMMEDIATELY in |
| // win32_renderer_util.h or b/5824433 for details. |
| uint32 maximum_delay = numeric_limits<int32>::max(); |
| if ((mode & SHOW_INFOLIST_IMMEDIATELY) == SHOW_INFOLIST_IMMEDIATELY) { |
| maximum_delay = 0; |
| } |
| |
| const uint32 hide_window_delay = min(maximum_delay, kHideWindowDelay); |
| if (candidates.has_focused_index() && candidates.candidate_size() > 0) { |
| const int focused_row = |
| candidates.focused_index() - candidates.candidate(0).index(); |
| if (candidates.candidate_size() >= focused_row && |
| candidates.candidate(focused_row).has_information_id()) { |
| const uint32 raw_delay = |
| max(static_cast<uint32>(0), |
| command.output().candidates().usages().delay()); |
| const uint32 delay = min(maximum_delay, raw_delay); |
| infolist_window_->DelayShow(delay); |
| } else { |
| infolist_window_->DelayHide(hide_window_delay); |
| } |
| } else { |
| infolist_window_->DelayHide(hide_window_delay); |
| } |
| } else { |
| // Hide infolist window immediately. |
| infolist_window_->DelayHide(0); |
| } |
| |
| if (cascading_visible) { |
| const commands::Candidates &subcandidates = candidates.subcandidates(); |
| |
| if (candidate_changed) { |
| cascading_window_->UpdateLayout(subcandidates); |
| } |
| |
| // Put the cascading window right to the selected row of this candidate |
| // window. |
| const Rect selected_row = main_window_->GetSelectionRectInScreenCord(); |
| const Rect selected_row_with_window_border( |
| Point(main_window_rect.Left(), selected_row.Top()), |
| Size(main_window_rect.Right() - main_window_rect.Left(), |
| selected_row.Top() - selected_row.Bottom())); |
| |
| // We prefer the top of client area of the cascading window is |
| // aligned to the top of selected candidate in the candidate window. |
| const Point cascading_window_zero_point( |
| 0, cascading_window_->GetFirstRowInClientCord().Top()); |
| |
| const Size cascading_window_size = cascading_window_->GetLayoutSize(); |
| |
| // cascading window should be in the same working area as the main window. |
| const Rect cascading_window_rect = |
| WindowUtil::GetWindowRectForCascadingWindow( |
| selected_row_with_window_border, cascading_window_size, |
| cascading_window_zero_point, working_area); |
| |
| cascading_window_->SetWindowPos(HWND_TOPMOST, |
| cascading_window_rect.Left(), |
| cascading_window_rect.Top(), |
| cascading_window_rect.Width(), |
| cascading_window_rect.Height(), |
| set_windows_pos_flags); |
| // This trick ensures that the window is certainly shown as 'inactivated' |
| // in terms of visual effect on DWM-enabled desktop. |
| cascading_window_->SendMessageW(WM_NCACTIVATE, FALSE); |
| if (candidate_changed) { |
| main_window_->Invalidate(); |
| cascading_window_->Invalidate(); |
| } |
| } else { |
| // no cascading window |
| if (candidate_changed) { |
| main_window_->Invalidate(); |
| } |
| cascading_window_->ShowWindow(SW_HIDE); |
| } |
| } |
| |
| void WindowManager::UpdateLayoutTSF(const commands::RendererCommand &command) { |
| // Currently implemented by IMM32 implementation. |
| // TODO(yukawa): Implement TSF version. |
| UpdateLayoutIMM32(command); |
| } |
| |
| bool WindowManager::IsAvailable() const { |
| return main_window_->IsWindow() && |
| cascading_window_->IsWindow() && |
| infolist_window_->IsWindow(); |
| } |
| |
| void WindowManager::SetSendCommandInterface( |
| client::SendCommandInterface *send_command_interface) { |
| main_window_->SetSendCommandInterface(send_command_interface); |
| cascading_window_->SetSendCommandInterface(send_command_interface); |
| infolist_window_->SetSendCommandInterface(send_command_interface); |
| } |
| |
| void WindowManager::PreTranslateMessage(const MSG &message) { |
| if (message.message != WM_MOUSEMOVE) { |
| return; |
| } |
| |
| // Window manager sometimes generates WM_MOUSEMOVE message when the contents |
| // under the mouse cursor has been changed (e.g. the window is moved) so that |
| // the mouse handler can update its cursor image based on the contents to |
| // which the cursor is newly pointing. |
| // See http://blogs.msdn.com/b/oldnewthing/archive/2003/10/01/55108.aspx for |
| // details about such kind of phantom WM_MOUSEMOVE. See also b/3104996. |
| // Here we compares the screen coordinate of the mouse cursor with the last |
| // one to determine if this WM_MOUSEMOVE is an artificial one or not. |
| // If the coordinate is the same, this is an artificial WM_MOUSEMOVE. |
| bool is_moving = true; |
| const CPoint cursor_pos_in_client_coords(GET_X_LPARAM(message.lParam), |
| GET_Y_LPARAM(message.lParam)); |
| CPoint cursor_pos_in_logical_coords; |
| if (layout_manager_->ClientPointToScreen( |
| message.hwnd, cursor_pos_in_client_coords, |
| &cursor_pos_in_logical_coords)) { |
| // Since the renderer process is DPI-aware, we can safely use this |
| // (logical) coordinates as if it is real (physical) screen coordinates. |
| if (cursor_pos_in_logical_coords == last_position_) { |
| is_moving = false; |
| } |
| last_position_ = cursor_pos_in_logical_coords; |
| } |
| |
| // Notify candidate windows if the cursor is moving or not so that they can |
| // filter unnecessary WM_MOUSEMOVE events. |
| main_window_->set_mouse_moving(is_moving); |
| cascading_window_->set_mouse_moving(is_moving); |
| } |
| |
| } // namespace win32 |
| } // namespace renderer |
| } // namespace mozc |