blob: d3f319ff34724c4f25c9046912a9a7161d19037b [file] [log] [blame]
// 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