blob: 25e54f9db4631d895749d3deec960808bceaa801 [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.
#import "renderer/mac/CandidateView.h"
#include "base/coordinates.h"
#include "renderer/table_layout.h"
#include "renderer/window_util.h"
#include "session/commands.pb.h"
#include "renderer/mac/CandidateController.h"
#include "renderer/mac/CandidateWindow.h"
#include "renderer/mac/InfolistWindow.h"
#include "renderer/mac/mac_view_util.h"
namespace mozc {
using client::SendCommandInterface;
using commands::RendererCommand;
using commands::Candidates;
namespace renderer {
namespace mac{
namespace {
const int kHideWindowDelay = 500; // msec
const int kMarginAbovePreedit = 10; // pixel
// In Cocoa's coordinate system the origin point is left-bottom and the Y-axis
// points up. But in Mozc's coordinate system the Y-axis points down. So we use
// OriginPointInCocoaCoord and RectInMozcCoord to convert the coordinate system.
NSPoint OriginPointInCocoaCoord(const mozc::Rect &rect) {
return NSMakePoint(rect.Left(), - rect.Bottom());
}
mozc::Rect RectInMozcCoord(const NSRect &rect) {
return mozc::Rect(rect.origin.x,
- rect.origin.y - rect.size.height,
rect.size.width,
rect.size.height);
}
// Find the display including the specified point and if it fails to
// find it, pick up the nearest display. Returns the geometry of the
// found display.
mozc::Rect GetNearestDisplayRect(const mozc::Rect &rect) {
NSPoint point = OriginPointInCocoaCoord(rect);
float nearest_distance = FLT_MAX;
NSRect result_rect = NSMakeRect(0, 0, 0, 0);
for (NSScreen *screen in [NSScreen screens]) {
NSRect rect = [screen frame];
CGFloat distance = 0;
if (point.x < rect.origin.x) {
distance += rect.origin.x - point.x;
} else if (point.x > rect.origin.x + rect.size.width) {
distance += point.x - rect.origin.x - rect.size.width;
}
if (point.y < rect.origin.y) {
distance += rect.origin.y - point.y;
} else if (point.y > rect.origin.y + rect.size.height) {
distance += point.y - rect.origin.y - rect.size.height;
}
if (distance < nearest_distance) {
nearest_distance = distance;
result_rect = rect;
}
}
return RectInMozcCoord(result_rect);
}
// Returns the height of the base screen.
int GetBaseScreenHeight() {
NSRect baseFrame = NSZeroRect;
for (NSScreen *baseScreen in [NSScreen screens]) {
baseFrame = [baseScreen frame];
if (baseFrame.origin.x == 0 && baseFrame.origin.y == 0) {
break;
}
}
return baseFrame.size.height;
}
} // anonymous namespace
CandidateController::CandidateController()
: candidate_window_(new mac::CandidateWindow),
cascading_window_(new mac::CandidateWindow),
infolist_window_(new mac::InfolistWindow) {
candidate_window_->SetWindowLevel(NSPopUpMenuWindowLevel);
// Cascading window should be over the normal candidate window.
cascading_window_->SetWindowLevel(NSPopUpMenuWindowLevel + 1);
// Infolist window should be under the normal candidate window.
infolist_window_->SetWindowLevel(NSPopUpMenuWindowLevel - 1);
}
CandidateController::~CandidateController() {
delete candidate_window_;
delete cascading_window_;
delete infolist_window_;
}
bool CandidateController::Activate() {
// Do nothing
return true;
}
bool CandidateController::IsAvailable() const {
return true;
}
void CandidateController::SetSendCommandInterface(
SendCommandInterface *send_command_interface) {
candidate_window_->SetSendCommandInterface(send_command_interface);
cascading_window_->SetSendCommandInterface(send_command_interface);
infolist_window_->SetSendCommandInterface(send_command_interface);
}
bool CandidateController::ExecCommand(const RendererCommand &command) {
if (command.type() != RendererCommand::UPDATE) {
return false;
}
command_.CopyFrom(command);
if (!command_.visible()) {
candidate_window_->Hide();
cascading_window_->Hide();
infolist_window_->Hide();
return true;
}
candidate_window_->SetCandidates(command_.output().candidates());
bool cascading_visible = false;
if (command_.output().has_candidates() &&
command_.output().candidates().has_subcandidates()) {
cascading_window_->SetCandidates(
command_.output().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_window_->SetCandidates(command_.output().candidates());
infolist_visible = true;
}
AlignWindows();
candidate_window_->Show();
if (cascading_visible) {
cascading_window_->Show();
} else {
cascading_window_->Hide();
}
if (infolist_visible && !cascading_visible) {
const Candidates &candidates = command_.output().candidates();
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 delay = max(0u, candidates.usages().delay());
infolist_window_->DelayShow(delay);
} else {
infolist_window_->DelayHide(kHideWindowDelay);
}
} else {
infolist_window_->DelayHide(kHideWindowDelay);
}
} else {
// Hide infolist window immediately.
infolist_window_->Hide();
}
return true;
}
void CandidateController::AlignWindows() {
// If candidate window is not visible, we do nothing for aligning.
if (!command_.has_preedit_rectangle()) {
return;
}
const mozc::Size preedit_size(command_.preedit_rectangle().right() -
command_.preedit_rectangle().left(),
command_.preedit_rectangle().bottom() -
command_.preedit_rectangle().top());
// The origin point of command_.preedit_rectangle() is the left-top of the
// base screen which is set in GoogleJapaneseInputController. It is
// unnecessary calculation but to support older version of GoogleJapaneseInput
// process we should not change it. So we minus the height of the screen here.
mozc::Rect preedit_rect(
mozc::Point(command_.preedit_rectangle().left(),
command_.preedit_rectangle().top() - GetBaseScreenHeight()),
preedit_size);
// Currently preedit_rect doesn't care about the text height -- it
// just means the line under the preedit. So here we fix the height.
// TODO(mukai): replace this hack by calculating actual text height.
preedit_rect.origin.y -= kMarginAbovePreedit;
preedit_rect.size.height += kMarginAbovePreedit;
// Find out the nearest display.
const mozc::Rect display_rect = GetNearestDisplayRect(preedit_rect);
// Align candidate window.
// Initialize the the position. We use (left, bottom) of preedit as
// the top-left position of the window because we want to show the
// window just below of the preedit.
const TableLayout *candidate_layout = candidate_window_->GetTableLayout();
const mozc::Point candidate_zero_point(
candidate_layout->GetColumnRect(COLUMN_CANDIDATE).Left(), 0);
const mozc::Rect candidate_rect =
WindowUtil::GetWindowRectForMainWindowFromPreeditRect(
preedit_rect, candidate_window_->GetWindowSize(),
candidate_zero_point, display_rect);
candidate_window_->MoveWindow(OriginPointInCocoaCoord(candidate_rect));
// Align infolist window
const mozc::Rect infolist_rect =
WindowUtil::GetWindowRectForInfolistWindow(
infolist_window_->GetWindowSize(),
candidate_rect, display_rect);
infolist_window_->MoveWindow(OriginPointInCocoaCoord(infolist_rect));
// If there is no need to show cascading window, we just finish the
// function here.
if (!command_.output().has_candidates() ||
!command_.output().candidates().candidate_size() > 0 ||
!command_.output().candidates().has_subcandidates()) {
return;
}
// Fix cascading window position
// 1. starting position is at the focused row
const Candidates &candidates = command_.output().candidates();
const int focused_row =
candidates.focused_index() - candidates.candidate(0).index();
mozc::Rect focused_rect = candidate_layout->GetRowRect(focused_row);
// move the focused_rect to the monitor's coordinates
focused_rect.origin.x += candidate_rect.origin.x;
focused_rect.origin.y += candidate_rect.origin.y;
// focused_rect doesn't have the width for scroll bar
focused_rect.size.width += candidate_layout->GetVScrollBarRect().Width();
const mozc::Rect cascading_rect =
WindowUtil::GetWindowRectForCascadingWindow(
focused_rect, cascading_window_->GetWindowSize(),
mozc::Point(0, 0), display_rect);
cascading_window_->MoveWindow(OriginPointInCocoaCoord(cascading_rect));
}
} // namespace mozc::renderer::mac
} // namespace mozc::renderer
} // namespace mozc