| // 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 |