blob: 6145e45f2678490fe9b9bbd8af61fa423e1f853b [file] [log] [blame]
/*
Copyright 2014 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef GOOPY_COMMON_BASE_WINDOW_H__
#define GOOPY_COMMON_BASE_WINDOW_H__
#include <atlbase.h>
#include <atlwin.h>
#include <algorithm>
#include "base/basictypes.h"
#include "common/window_shadow.h"
namespace ime_goopy {
namespace common {
// Other code should use this wrapper class to call Windows API, so that it's
// possible to replace this class with a mock one to write unit tests.
class WindowsAPIWrapper {
public:
inline BOOL GetCursorPos(POINT* point) {
return ::GetCursorPos(point);
}
inline HCURSOR SetCursor(HCURSOR cursor) {
return ::SetCursor(cursor);
}
inline HCURSOR LoadCursor(HINSTANCE instance, LPCTSTR cursor_name) {
return ::LoadCursor(instance, cursor_name);
}
inline BOOL ReleaseCapture() {
return ::ReleaseCapture();
}
inline HMONITOR MonitorFromPoint(POINT pt, DWORD flags) {
return ::MonitorFromPoint(pt, flags);
}
inline BOOL GetMonitorInfo(HMONITOR monitor, LPMONITORINFO lpmi) {
return ::GetMonitorInfo(monitor, lpmi);
}
};
// IME window should not has input focus, so it must be disabled and can not
// receive any mouse messages. Fortunately, it can receive WM_SETCURSOR message.
// Use this macro, you can handle the mouse events in WM_SETCURSOR.
//
// When user click on a disabled window, the windows system will beep by
// default. To override this behavior, you should always handle WM_SETCURSOR
// yourself and do not pass this message to default windows message handler.
// You should put your WM_SETCURSOR handler after SETCURSOR_HANDLER.
// TODO(zengjian): The "standard" way to implement non-active window is to
// add a WM_MOUSEACTIVE handler, rather than disabling the window.
// See code in Toolbar and Chrome. In that way we can still catch mouse
// events like a normal window.
// Use the "standard" implementation for all the non-active windows.
#define SETCURSOR_HANDLER(msg, func) \
if ((uMsg == WM_SETCURSOR) && (HIWORD(lParam) == msg)) { \
bHandled = TRUE; \
POINT point; \
GetCursorPos(&point); \
ScreenToClient(&point); \
lResult = !func(msg, 0, MAKELONG(point.x, point.y), bHandled); \
if (bHandled) return TRUE; \
}
// Same as above but used in class template.
#define TEMPLATE_SETCURSOR_HANDLER(msg, func) \
if ((uMsg == WM_SETCURSOR) && (HIWORD(lParam) == msg)) { \
bHandled = TRUE; \
POINT point; \
static_cast<T*>(this)->GetCursorPos(&point); \
static_cast<T*>(this)->ScreenToClient(&point); \
lResult = !func(msg, 0, MAKELONG(point.x, point.y), bHandled); \
if (bHandled) return TRUE; \
}
// Usually, you can call TrackMouseEvent and then get notified if the mouse is
// leaving your window. However, this doesn't work for a disabled window.
// This class emulates this behavior by using a timer. Your inherited class
// will receive a WM_MOUSEHOVER if the mouse is entering the window, and a
// WM_MOUSELEAVE if it's leaving.
template <class T>
class MouseLeavingTracker {
public:
BEGIN_MSG_MAP(MouseLeavingTracker)
TEMPLATE_SETCURSOR_HANDLER(WM_MOUSEMOVE, OnMouseMove)
MESSAGE_HANDLER(WM_TIMER, OnTimer)
END_MSG_MAP()
MouseLeavingTracker() : tracking_mouse_(true), mouse_inside_(false) {
}
LRESULT OnMouseMove(UINT msg, WPARAM wparam, LPARAM lparam, BOOL& handled) {
// Track mouse and send notify if the mouse cursor leaves the window.
T* self = static_cast<T*>(this);
if (tracking_mouse_ && !mouse_inside_) {
mouse_inside_ = true;
self->SendMessage(WM_MOUSEHOVER, 0, 0);
self->SetTimer(
kMouseLeaveCheckingTimerID,
kMouseLeaveCheckingTimerInteval,
NULL);
}
handled = FALSE;
// Returning a non-zero value means we don't process this message.
return 1;
}
LRESULT OnTimer(UINT msg, WPARAM wparam, LPARAM lparam, BOOL& handled) {
T* self = static_cast<T*>(this);
if (wparam == kMouseLeaveCheckingTimerID) {
POINT point;
self->GetCursorPos(&point);
RECT rect;
self->GetWindowRect(&rect);
if (!PtInRect(&rect, point)) {
mouse_inside_ = false;
self->KillTimer(kMouseLeaveCheckingTimerID);
self->SendMessage(WM_MOUSELEAVE, 0, 0);
}
return 0;
} else {
handled = FALSE;
// Returning a non-zero value means we don't process this message.
return 1;
}
}
void set_tracking_mouse(bool value) { tracking_mouse_ = value; }
bool mouse_inside() { return mouse_inside_; }
static const int kMouseLeaveCheckingTimerID = 1325;
static const int kMouseLeaveCheckingTimerInteval = 200;
private:
bool tracking_mouse_;
bool mouse_inside_;
};
// You can make a window dragable by inherit this class. If you want disable
// dragging for some time or some area of the window, don't pass WM_LBUTTONDOWN
// to this class.
template <class T>
class DragableWindow {
public:
BEGIN_MSG_MAP(DragableWindow)
TEMPLATE_SETCURSOR_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
TEMPLATE_SETCURSOR_HANDLER(WM_LBUTTONUP, OnLButtonUp)
TEMPLATE_SETCURSOR_HANDLER(WM_MOUSEMOVE, OnMouseMove)
// After SetCapture(), system will send real mouse messages to our window,
// so handle them as well.
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)
MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
MESSAGE_HANDLER(WM_SETCURSOR, OnSetCursor)
END_MSG_MAP()
DragableWindow() : is_dragging_(false) {
}
LRESULT OnSetCursor(UINT msg, WPARAM wparam, LPARAM lparam, BOOL& handled) {
POINT cursor;
T* self = static_cast<T*>(this);
self->GetCursorPos(&cursor);
self->ScreenToClient(&cursor);
if (!self->IsInDraggableRect(cursor)) {
handled = FALSE;
return 1;
}
if (HIWORD(lparam) != 0) {
self->SetCursor(
self->LoadCursor(NULL, IDC_SIZEALL));
}
return 0;
}
LRESULT OnLButtonDown(UINT msg, WPARAM wparam, LPARAM lparam, BOOL& handled) {
// Record the coordinate offset from the cursor and window position.
POINT cursor;
T* self = static_cast<T*>(this);
self->GetCursorPos(&cursor);
POINT test_cursor = cursor;
self->ScreenToClient(&test_cursor);
if (!self->IsInDraggableRect(test_cursor)) {
handled = FALSE;
return 1;
}
RECT rect;
self->GetWindowRect(&rect);
offset_cursor_to_window_.cx = cursor.x - rect.left;
offset_cursor_to_window_.cy = cursor.y - rect.top;
is_dragging_ = true;
self->SetCapture();
self->SetWindowPos(HWND_TOP, 0, 0, 0, 0,
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER |
SWP_NOSIZE);
return 0;
}
LRESULT OnLButtonUp(UINT msg, WPARAM wparam, LPARAM lparam, BOOL& handled) {
if (!is_dragging_) {
handled = FALSE;
// Returning a non-zero value means we don't process this message.
return 1;
}
is_dragging_ = false;
T* self = static_cast<T*>(this);
self->ReleaseCapture();
self->OnDragComplete();
return 0;
}
LRESULT OnMouseMove(UINT msg, WPARAM wparam, LPARAM lparam, BOOL& handled) {
if (!is_dragging_) {
handled = FALSE;
// Returning a non-zero value means we don't process this message.
return 1;
}
POINT cursor;
T* self = static_cast<T*>(this);
self->GetCursorPos(&cursor);
POINT window = {
cursor.x - offset_cursor_to_window_.cx,
cursor.y - offset_cursor_to_window_.cy,
};
RECT rect;
self->GetWindowRect(&rect);
SIZE window_size = {
rect.right - rect.left,
rect.bottom - rect.top,
};
self->AdjustInDesktop(cursor, window_size, &window);
self->SetWindowPos(
NULL,
window.x,
window.y,
0,
0,
SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
self->SetCursor(
self->LoadCursor(NULL, IDC_SIZEALL));
return 0;
}
// Use referrence to get the current desktop, and change the point value to
// adjust the window inside that desktop.
void AdjustInDesktop(const POINT& reference,
const SIZE& window_size,
POINT* window_point) {
T* self = static_cast<T*>(this);
HMONITOR monitor = self->MonitorFromPoint(
reference, MONITOR_DEFAULTTONEAREST);
MONITORINFO monitor_info;
monitor_info.cbSize = sizeof(monitor_info);
self->GetMonitorInfo(monitor, &monitor_info);
if (window_point->x < monitor_info.rcWork.left)
window_point->x = monitor_info.rcWork.left;
if (window_point->y < monitor_info.rcWork.top)
window_point->y = monitor_info.rcWork.top;
if (window_point->x + window_size.cx > monitor_info.rcWork.right)
window_point->x = max(monitor_info.rcWork.right - window_size.cx,
monitor_info.rcWork.left);
if (window_point->y + window_size.cy > monitor_info.rcWork.bottom)
window_point->y = max(monitor_info.rcWork.bottom - window_size.cy,
monitor_info.rcWork.top);
}
bool IsInDesktop(const POINT& reference,
const SIZE& window_size,
const POINT& window_point) {
T* self = static_cast<T*>(this);
HMONITOR monitor = self->MonitorFromPoint(
reference, MONITOR_DEFAULTTONEAREST);
MONITORINFO monitor_info;
monitor_info.cbSize = sizeof(monitor_info);
self->GetMonitorInfo(monitor, &monitor_info);
return window_point.x >= monitor_info.rcWork.left &&
window_point.y >= monitor_info.rcWork.top &&
window_point.x + window_size.cx <= monitor_info.rcWork.right &&
window_point.y + window_size.cy <= monitor_info.rcWork.bottom;
}
// Nofity.
void OnDragComplete() { }
bool IsInDraggableRect(const POINT& cursor) {
return true;
}
private:
SIZE offset_cursor_to_window_;
bool is_dragging_;
};
// Enable shadow for the window.
template <class T>
class DropShadowWindow {
public:
BEGIN_MSG_MAP(DropShadowWindow)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
MESSAGE_HANDLER(WM_SHOWWINDOW, OnShowWindow)
MESSAGE_HANDLER(WM_WINDOWPOSCHANGED, OnWindowsPosChanged)
END_MSG_MAP()
void set_alpha(int alpha) {
DCHECK_GE(alpha, 0);
DCHECK_LE(alpha, 255);
shadow_.set_alpha(alpha);
}
void ShowShadow() {
shadow_.UpdatePosition(static_cast<T*>(this)->m_hWnd);
shadow_.Show(true);
}
private:
LRESULT OnCreate(UINT msg, WPARAM wparam, LPARAM lparam, BOOL& handled) {
CREATESTRUCT* info = reinterpret_cast<CREATESTRUCT*>(lparam);
shadow_.Create(info->hInstance, GetParent(static_cast<T*>(this)->m_hWnd));
handled = FALSE;
return 0;
}
LRESULT OnDestroy(UINT msg, WPARAM wparam, LPARAM lparam, BOOL& handled) {
shadow_.Destroy();
handled = FALSE;
return 0;
}
LRESULT OnShowWindow(UINT msg, WPARAM wparam, LPARAM lparam, BOOL& handled) {
shadow_.Show(wparam != 0);
handled = FALSE;
return 0;
}
LRESULT OnWindowsPosChanged(UINT msg,
WPARAM wparam,
LPARAM lparam,
BOOL& handled) {
WINDOWPOS* window_pos = reinterpret_cast<WINDOWPOS*>(lparam);
shadow_.UpdatePosition(static_cast<T*>(this)->m_hWnd);
handled = FALSE;
return 0;
}
WindowShadow shadow_;
};
// This class is for creating layered windows (i.e. translucent windows).
// Need a bitmap with alpha channel for the content of the window.
template <class T>
class LayeredWindow {
public:
void EnableLayered() {
static_cast<T*>(this)->SetWindowLongPtr(GWL_EXSTYLE, WS_EX_LAYERED);
}
// Display a bitmap, and use alpha channel from bitmap.
void SetAlphaBitmap(HDC dest_dc, POINT dest_point, SIZE window_size,
HDC src_dc, POINT src_point, int alpha) {
BLENDFUNCTION bf;
bf.AlphaFormat = AC_SRC_ALPHA;
bf.BlendFlags = 0;
bf.BlendOp = AC_SRC_OVER;
bf.SourceConstantAlpha = alpha;
::UpdateLayeredWindow(static_cast<T*>(this)->m_hWnd,
dest_dc,
&dest_point,
&window_size,
src_dc,
&src_point,
0,
&bf,
ULW_ALPHA);
}
};
template <class T>
class ResizableWindow {
public:
BEGIN_MSG_MAP(ResizableWindow)
TEMPLATE_SETCURSOR_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
TEMPLATE_SETCURSOR_HANDLER(WM_LBUTTONUP, OnLButtonUp)
TEMPLATE_SETCURSOR_HANDLER(WM_MOUSEMOVE, OnMouseMove)
// After SetCapture(), system will send real mouse messages to our window,
// so handle them as well.
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)
MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
MESSAGE_HANDLER(WM_SETCURSOR, OnSetCursor)
END_MSG_MAP()
ResizableWindow()
: border_width_(0), is_resizing_(false), border_type_(BORDER_NONE) {
}
LRESULT OnSetCursor(UINT msg, WPARAM wparam, LPARAM lparam, BOOL& handled) {
POINT cursor;
T* self = static_cast<T*>(this);
self->GetCursorPos(&cursor);
self->ScreenToClient(&cursor);
BorderType border_type;
if (!IsOnWindowBorder(cursor, &border_type)) {
handled = FALSE;
return 1;
}
if (HIWORD(lparam) != 0) {
SetCursorByBorderType(border_type);
}
return 0;
}
LRESULT OnLButtonDown(UINT msg, WPARAM wparam, LPARAM lparam, BOOL& handled) {
// Record the coordinate offset from the cursor and window position.
POINT cursor;
T* self = static_cast<T*>(this);
self->GetCursorPos(&cursor);
self->ScreenToClient(&cursor);
if (!IsOnWindowBorder(cursor, &border_type_)) {
handled = FALSE;
return 1;
}
is_resizing_ = true;
self->SetCapture();
return 0;
}
LRESULT OnLButtonUp(UINT msg, WPARAM wparam, LPARAM lparam, BOOL& handled) {
if (!is_resizing_) {
handled = FALSE;
// Returning a non-zero value means we don't process this message.
return 1;
}
is_resizing_ = false;
T* self = static_cast<T*>(this);
self->ReleaseCapture();
return 0;
}
LRESULT OnMouseMove(UINT msg, WPARAM wparam, LPARAM lparam, BOOL& handled) {
if (!is_resizing_) {
handled = FALSE;
// Returning a non-zero value means we don't process this message.
return 1;
}
POINT cursor;
T* self = static_cast<T*>(this);
self->GetCursorPos(&cursor);
RECT rect;
self->GetWindowRect(&rect);
SIZE window_size = {0};
switch (border_type_) {
case BORDER_TOPLEFT:
window_size.cx = rect.right - cursor.x;
window_size.cy = rect.bottom - cursor.y;
break;
case BORDER_TOP:
window_size.cx = rect.right - rect.left;
window_size.cy = rect.bottom - cursor.y;
break;
case BORDER_TOPRIGHT:
window_size.cx = cursor.x - rect.left;
window_size.cy = rect.bottom - cursor.y;
break;
case BORDER_LEFT:
window_size.cx = rect.right - cursor.x;
window_size.cy = rect.bottom - rect.top;
break;
case BORDER_RIGHT:
window_size.cx = cursor.x - rect.left;
window_size.cy = rect.bottom - rect.top;
break;
case BORDER_BOTTOMLEFT:
window_size.cx = rect.right - cursor.x;
window_size.cy = cursor.y - rect.top;
break;
case BORDER_BOTTOM:
window_size.cx = rect.right - rect.left;
window_size.cy = cursor.y - rect.top;
break;
case BORDER_BOTTOMRIGHT:
window_size.cx = cursor.x - rect.left;
window_size.cy = cursor.y - rect.top;
break;
default:
NOTREACHED();
}
if (window_size.cx < min_window_size_.cx) {
window_size.cx = min_window_size_.cx;
}
if (window_size.cy < min_window_size_.cy) {
window_size.cy = min_window_size_.cy;
}
POINT window;
switch (border_type_) {
case BORDER_TOPLEFT:
case BORDER_TOP:
case BORDER_TOPRIGHT:
window.y = rect.bottom - window_size.cy;
break;
default:
window.y = rect.top;
}
switch (border_type_) {
case BORDER_TOPLEFT:
case BORDER_LEFT:
case BORDER_BOTTOMLEFT:
window.x = rect.right - window_size.cx;
break;
default:
window.x = rect.left;
}
self->SetWindowPos(
NULL,
window.x,
window.y,
window_size.cx,
window_size.cy,
SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOZORDER);
self->InvalidateRect(NULL, FALSE);
SetCursorByBorderType(border_type_);
return 0;
}
void set_border_width(int pixel) {
DCHECK_GE(pixel, 0);
border_width_ = pixel;
}
void set_min_window_size(const SIZE& size) {
min_window_size_ = size;
}
private:
enum BorderType {
BORDER_NONE = 0,
BORDER_TOPLEFT,
BORDER_TOP,
BORDER_TOPRIGHT,
BORDER_LEFT,
BORDER_RIGHT,
BORDER_BOTTOMLEFT,
BORDER_BOTTOM,
BORDER_BOTTOMRIGHT,
};
bool IsOnWindowBorder(const POINT& cursor, BorderType* border_type) {
RECT rect;
T* self = static_cast<T*>(this);
self->GetClientRect(&rect);
if (cursor.y < border_width_) {
if (border_type)
*border_type = (cursor.x < border_width_) ? BORDER_TOPLEFT :
(cursor.x > rect.right - border_width_) ?
BORDER_TOPRIGHT : BORDER_TOP;
return true;
} else if (cursor.y > rect.bottom - border_width_) {
if (border_type)
*border_type = (cursor.x < border_width_) ? BORDER_BOTTOMLEFT :
(cursor.x > rect.right - border_width_) ?
BORDER_BOTTOMRIGHT : BORDER_BOTTOM;
return true;
} else {
if (cursor.x < border_width_) {
if (border_type) *border_type = BORDER_LEFT;
return true;
}
if (cursor.x > rect.right - border_width_) {
if (border_type) *border_type = BORDER_RIGHT;
return true;
}
}
return false;
}
void SetCursorByBorderType(BorderType border_type) {
T* self = static_cast<T*>(this);
switch (border_type) {
case BORDER_TOPLEFT:
case BORDER_BOTTOMRIGHT:
self->SetCursor(
self->LoadCursor(NULL, IDC_SIZENWSE));
break;
case BORDER_TOPRIGHT:
case BORDER_BOTTOMLEFT:
self->SetCursor(
self->LoadCursor(NULL, IDC_SIZENESW));
break;
case BORDER_TOP:
case BORDER_BOTTOM:
self->SetCursor(
self->LoadCursor(NULL, IDC_SIZENS));
break;
case BORDER_LEFT:
case BORDER_RIGHT:
self->SetCursor(
self->LoadCursor(NULL, IDC_SIZEWE));
break;
default:
NOTREACHED();
}
}
int border_width_;
bool is_resizing_;
BorderType border_type_;
SIZE min_window_size_;
};
} // namespace common
} // namespace ime_goopy
#endif // GOOPY_COMMON_BASE_WINDOW_H__