blob: bf9871d5a1e3d39a874d0455331077a128e85fc5 [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/indicator_window.h"
#include <windows.h>
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE
#include <atlbase.h>
#include <atlwin.h>
#include <atlapp.h>
#include <atlcrack.h>
#include <atlmisc.h>
#include <algorithm>
#include <vector>
#include "base/const.h"
#include "base/util.h"
#include "renderer/renderer_command.pb.h"
#include "renderer/win32/win32_image_util.h"
#include "renderer/win32/win32_renderer_util.h"
namespace mozc {
namespace renderer {
namespace win32 {
namespace {
using ATL::CWinTraits;
using ATL::CWindow;
using ATL::CWindowImpl;
using WTL::CBitmap;
using WTL::CBitmapHandle;
using WTL::CDC;
using WTL::CLogFont;
using WTL::CPoint;
using WTL::CSize;
using ::mozc::commands::Status;
typedef ::mozc::commands::RendererCommand::ApplicationInfo ApplicationInfo;
// 96 DPI is the default DPI in Windows.
const int kDefaultDPI = 96;
// As Discussed in b/2317702, UI windows are disabled by default because it is
// hard for a user to find out what caused the problem than finding that the
// operations seems to be disabled on the UI window when
// SPI_GETACTIVEWINDOWTRACKING is enabled.
// TODO(yukawa): Support mouse operations before we add a GUI feature which
// requires UI interaction by mouse and/or touch. (b/2954874)
typedef CWinTraits<
WS_POPUP | WS_DISABLED,
WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOACTIVATE>
IndicatorWindowTraits;
struct Sprite {
CBitmap bitmap;
CPoint offset;
};
// Timer event IDs
const UINT_PTR kTimerEventFadeStart = 0;
const UINT_PTR kTimerEventFading = 1;
const DWORD kStartFadingOutDelay = 2500; // msec
const DWORD kFadingOutInterval = 16; // msec
const int kFadingOutAlphaDelta = 32;
double GetDPIScaling() {
CDC desktop_dc(::GetDC(NULL));
const int dpi_x = desktop_dc.GetDeviceCaps(LOGPIXELSX);
return static_cast<double>(dpi_x) / kDefaultDPI;
}
} // namespace
class IndicatorWindow::WindowImpl
: public CWindowImpl<IndicatorWindow::WindowImpl,
CWindow,
IndicatorWindowTraits> {
public:
DECLARE_WND_CLASS_EX(kIndicatorWindowClassName, 0, COLOR_WINDOW);
WindowImpl()
: alpha_(255),
dpi_scaling_(GetDPIScaling()) {
sprites_.resize(commands::NUM_OF_COMPOSITIONS);
}
BEGIN_MSG_MAP_EX(WindowImpl)
MSG_WM_CREATE(OnCreate)
MSG_WM_TIMER(OnTimer)
MSG_WM_SETTINGCHANGE(OnSettingChange)
END_MSG_MAP()
void OnUpdate(const commands::RendererCommand &command,
LayoutManager *layout_manager) {
KillTimer(kTimerEventFading);
KillTimer(kTimerEventFadeStart);
bool visible = false;
IndicatorWindowLayout indicator_layout;
if (command.has_visible() &&
command.visible() &&
command.has_application_info() &&
command.application_info().has_indicator_info() &&
command.application_info().indicator_info().has_status()) {
const ApplicationInfo &app_info = command.application_info();
visible = layout_manager->LayoutIndicatorWindow(app_info,
&indicator_layout);
}
if (!visible) {
HideIndicator();
return;
}
DCHECK(command.has_application_info());
DCHECK(command.application_info().has_indicator_info());
DCHECK(command.application_info().indicator_info().has_status());
const Status &status =
command.application_info().indicator_info().status();
alpha_ = 255;
current_image_ = sprites_[commands::DIRECT].bitmap;
CPoint offset = sprites_[commands::DIRECT].offset;
if (!status.has_activated() || !status.has_mode() ||
!status.activated()) {
current_image_ = sprites_[commands::DIRECT].bitmap;
offset = sprites_[commands::DIRECT].offset;
} else {
const int mode = status.mode();
switch (mode) {
case commands::HIRAGANA:
case commands::FULL_KATAKANA:
case commands::HALF_ASCII:
case commands::FULL_ASCII:
case commands::HALF_KATAKANA:
current_image_ = sprites_[mode].bitmap;
offset = sprites_[mode].offset;
break;
}
}
if (current_image_ == nullptr) {
HideIndicator();
return;
}
top_left_ = CPoint(indicator_layout.window_rect.left - offset.x,
indicator_layout.window_rect.bottom - offset.y);
UpdateWindow();
// Start fading out.
SetTimer(kTimerEventFadeStart, kStartFadingOutDelay);
}
void HideIndicator() {
KillTimer(kTimerEventFading);
KillTimer(kTimerEventFadeStart);
ShowWindow(SW_HIDE);
}
private:
void UpdateWindow() {
CSize size;
current_image_.GetSize(size);
CDC dc;
dc.CreateCompatibleDC();
// Fading out animation.
CPoint top_left = top_left_;
top_left.y += (255 - alpha_) / 32;
CPoint src_left_top(0, 0);
BLENDFUNCTION func = {AC_SRC_OVER, 0, alpha_, AC_SRC_ALPHA};
const CBitmapHandle old_bitmap = dc.SelectBitmap(current_image_);
const BOOL result = ::UpdateLayeredWindow(
m_hWnd, nullptr, &top_left, &size, dc, &src_left_top, 0, &func,
ULW_ALPHA);
dc.SelectBitmap(old_bitmap);
ShowWindow(SW_SHOWNA);
}
LRESULT OnCreate(LPCREATESTRUCT create_struct) {
EnableOrDisableWindowForWorkaround();
const int kModes[] = {
commands::DIRECT,
commands::HIRAGANA,
commands::FULL_KATAKANA,
commands::HALF_ASCII,
commands::FULL_ASCII,
commands::HALF_KATAKANA,
};
for (size_t i = 0; i < arraysize(kModes); ++i) {
LoadSprite(kModes[i]);
}
return 1;
}
void OnTimer(UINT_PTR event_id) {
switch (event_id) {
case kTimerEventFadeStart:
KillTimer(kTimerEventFadeStart);
SetTimer(kTimerEventFading, kFadingOutInterval);
break;
case kTimerEventFading:
alpha_ = max(static_cast<int>(alpha_) - kFadingOutAlphaDelta, 0);
if (alpha_ == 0) {
KillTimer(kTimerEventFading);
}
UpdateWindow();
break;
}
}
void OnSettingChange(UINT flags, LPCTSTR /*lpszSection*/) {
switch (flags) {
case SPI_SETACTIVEWINDOWTRACKING:
EnableOrDisableWindowForWorkaround();
default:
// We ignore other changes.
break;
}
}
void EnableOrDisableWindowForWorkaround() {
// Disable the window if SPI_GETACTIVEWINDOWTRACKING is enabled.
// See b/2317702 for details.
// TODO(yukawa): Support mouse operations before we add a GUI feature which
// requires UI interaction by mouse and/or touch. (b/2954874)
BOOL is_tracking_enabled = FALSE;
if (::SystemParametersInfo(SPI_GETACTIVEWINDOWTRACKING,
0, &is_tracking_enabled, 0)) {
EnableWindow(!is_tracking_enabled);
}
}
void LoadSprite(int mode) {
BalloonImage::BalloonImageInfo info;
CLogFont logfont;
logfont.SetMessageBoxFont();
Util::WideToUTF8(logfont.lfFaceName, &info.label_font);
info.frame_color = RGBColor(1, 122, 204);
info.blur_color = RGBColor(1, 122, 204);
info.rect_width = ceil(dpi_scaling_ * 45.0); // snap to pixel alignment
info.rect_height = ceil(dpi_scaling_ * 45.0); // snap to pixel alignment
info.corner_radius = dpi_scaling_ * 0.0;
info.tail_height = dpi_scaling_ * 5.0;
info.tail_width = dpi_scaling_ * 10.0;
info.blur_sigma = dpi_scaling_ * 3.0;
info.blur_alpha = 0.5;
info.frame_thickness = dpi_scaling_ * 1.0;
info.label_size = 13.0; // no need to be scaled.
info.label_color = RGBColor(0, 0, 0);
info.blur_offset_x = 0;
info.blur_offset_y = 0;
switch (mode) {
case commands::DIRECT:
info.blur_sigma = dpi_scaling_ * 0.0;
info.frame_color = RGBColor(186, 186, 186);
info.label_color = RGBColor(0, 0, 0);
info.blur_sigma = dpi_scaling_ * 0.0;
info.frame_thickness = dpi_scaling_ * 1.0;
info.corner_radius = dpi_scaling_ * 0.0;
info.blur_offset_x = 0;
info.blur_offset_y = 0;
info.label = "A";
break;
case commands::HIRAGANA:
// "あ"
info.label = "\xE3\x81\x82";
break;
case commands::FULL_KATAKANA:
// "ア"
info.label = "\xE3\x82\xA2";
break;
case commands::HALF_ASCII:
info.label = "_A";
break;
case commands::FULL_ASCII:
// "A"
info.label = "\xEF\xBC\xA1";
break;
case commands::HALF_KATAKANA:
// "_ア"
info.label = "\x5F\xEF\xBD\xB1";
break;
}
if (!info.label.empty()) {
sprites_[mode].bitmap.Attach(
BalloonImage::Create(info, &sprites_[mode].offset));
}
}
CBitmapHandle current_image_;
CPoint top_left_;
BYTE alpha_;
double dpi_scaling_;
vector<Sprite> sprites_;
DISALLOW_COPY_AND_ASSIGN(WindowImpl);
};
IndicatorWindow::IndicatorWindow()
: impl_(new WindowImpl) {}
IndicatorWindow::~IndicatorWindow() {
impl_->DestroyWindow();
}
void IndicatorWindow::Initialize() {
impl_->Create(nullptr);
impl_->ShowWindow(SW_HIDE);
}
void IndicatorWindow::Destroy() {
impl_->DestroyWindow();
}
void IndicatorWindow::OnUpdate(const commands::RendererCommand &command,
LayoutManager *layout_manager) {
impl_->OnUpdate(command, layout_manager);
}
void IndicatorWindow::Hide() {
impl_->HideIndicator();
}
} // namespace win32
} // namespace renderer
} // namespace mozc