// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "views/widget/native_widget_win.h" #include "base/scoped_ptr.h" #include "ui/base/system_monitor/system_monitor.h" #include "ui/base/view_prop.h" #include "ui/base/win/hwnd_util.h" #include "ui/gfx/canvas_skia.h" #include "ui/gfx/native_theme_win.h" #include "ui/gfx/path.h" #include "views/view.h" #include "views/widget/widget_impl.h" namespace views { namespace internal { namespace { // Called from NativeWidgetWin::Paint() to asynchronously redraw child windows. BOOL CALLBACK EnumChildProcForRedraw(HWND hwnd, LPARAM lparam) { DWORD process_id; GetWindowThreadProcessId(hwnd, &process_id); gfx::Rect invalid_rect = *reinterpret_cast(lparam); RECT window_rect; GetWindowRect(hwnd, &window_rect); invalid_rect.Offset(-window_rect.left, -window_rect.top); int flags = RDW_INVALIDATE | RDW_NOCHILDREN | RDW_FRAME; if (process_id == GetCurrentProcessId()) flags |= RDW_UPDATENOW; RedrawWindow(hwnd, &invalid_rect.ToRECT(), NULL, flags); return TRUE; } // Links the HWND to its Widget. const char* const kNativeWidgetKey = "__VIEWS_NATIVE_WIDGET__"; // A custom MSAA object id used to determine if a screen reader is actively // listening for MSAA events. const int kMSAAObjectID = 1; } //////////////////////////////////////////////////////////////////////////////// // NativeWidgetWin, public: NativeWidgetWin::NativeWidgetWin(NativeWidgetListener* listener) : listener_(listener), active_mouse_tracking_flags_(0), has_capture_(false) { } NativeWidgetWin::~NativeWidgetWin() { } //////////////////////////////////////////////////////////////////////////////// // NativeWidgetWin, NativeWidget implementation: void NativeWidgetWin::InitWithNativeViewParent(gfx::NativeView parent, const gfx::Rect& bounds) { WindowImpl::Init(parent, bounds); } void NativeWidgetWin::SetNativeWindowProperty(const char* name, void* value) { // Remove the existing property (if any). for (ViewProps::iterator i = props_.begin(); i != props_.end(); ++i) { if ((*i)->Key() == name) { props_.erase(i); break; } } if (value) props_.push_back(new ui::ViewProp(hwnd(), name, value)); } void* NativeWidgetWin::GetNativeWindowProperty(const char* name) const { return ui::ViewProp::GetValue(hwnd(), name); } gfx::Rect NativeWidgetWin::GetWindowScreenBounds() const { RECT r; GetWindowRect(hwnd(), &r); return gfx::Rect(r); } gfx::Rect NativeWidgetWin::GetClientAreaScreenBounds() const { RECT r; GetClientRect(hwnd(), &r); POINT point = { r.left, r.top }; ClientToScreen(hwnd(), &point); return gfx::Rect(point.x, point.y, r.right - r.left, r.bottom - r.top); } void NativeWidgetWin::SetBounds(const gfx::Rect& bounds) { SetWindowPos(hwnd(), NULL, bounds.x(), bounds.y(), bounds.width(), bounds.height(), SWP_NOACTIVATE | SWP_NOZORDER); } void NativeWidgetWin::SetShape(const gfx::Path& shape) { SetWindowRgn(hwnd(), shape.CreateNativeRegion(), TRUE); } gfx::NativeView NativeWidgetWin::GetNativeView() const { return hwnd(); } void NativeWidgetWin::Show() { if (IsWindow(hwnd())) ShowWindow(hwnd(), SW_SHOWNOACTIVATE); // TODO(beng): move to windowposchanging to trap visibility changes instead. if (IsLayeredWindow()) Invalidate(); } void NativeWidgetWin::Hide() { if (IsWindow(hwnd())) { // NOTE: Be careful not to activate any windows here (for example, calling // ShowWindow(SW_HIDE) will automatically activate another window). This // code can be called while a window is being deactivated, and activating // another window will screw up the activation that is already in progress. SetWindowPos(hwnd(), NULL, 0, 0, 0, 0, SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOREPOSITION | SWP_NOSIZE | SWP_NOZORDER); } } void NativeWidgetWin::Close() { DestroyWindow(hwnd()); } void NativeWidgetWin::MoveAbove(NativeWidget* other) { SetWindowPos(hwnd(), other->GetNativeView(), 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE); } void NativeWidgetWin::SetAlwaysOnTop(bool always_on_top) { DWORD style = always_on_top ? window_ex_style() | WS_EX_TOPMOST : window_ex_style() & ~WS_EX_TOPMOST; set_window_ex_style(style); SetWindowLong(hwnd(), GWL_EXSTYLE, window_ex_style()); } bool NativeWidgetWin::IsVisible() const { return !!IsWindowVisible(hwnd()); } bool NativeWidgetWin::IsActive() const { WINDOWINFO info; return ::GetWindowInfo(hwnd(), &info) && ((info.dwWindowStatus & WS_ACTIVECAPTION) != 0); } void NativeWidgetWin::SetMouseCapture() { SetCapture(hwnd()); has_capture_ = true; } void NativeWidgetWin::ReleaseMouseCapture() { ReleaseCapture(); has_capture_ = false; } bool NativeWidgetWin::HasMouseCapture() const { return has_capture_; } bool NativeWidgetWin::ShouldReleaseCaptureOnMouseReleased() const { return true; } void NativeWidgetWin::Invalidate() { ::InvalidateRect(hwnd(), NULL, FALSE); } void NativeWidgetWin::InvalidateRect(const gfx::Rect& invalid_rect) { // InvalidateRect() expects client coordinates. RECT r = invalid_rect.ToRECT(); ::InvalidateRect(hwnd(), &r, FALSE); } void NativeWidgetWin::Paint() { RECT r; GetUpdateRect(hwnd(), &r, FALSE); if (!IsRectEmpty(&r)) { // TODO(beng): WS_EX_TRANSPARENT windows (see WidgetWin::opaque_) // Paint child windows that are in a different process asynchronously. // This prevents a hang in other processes from blocking this process. // Calculate the invalid rect in screen coordinates before the first // RedrawWindow() call to the parent HWND, since that will empty update_rect // (which comes from a member variable) in the OnPaint call. gfx::Rect screen_rect = GetWindowScreenBounds(); gfx::Rect invalid_screen_rect(r); invalid_screen_rect.Offset(screen_rect.x(), screen_rect.y()); RedrawWindow(hwnd(), &r, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_NOCHILDREN); LPARAM lparam = reinterpret_cast(&invalid_screen_rect); EnumChildWindows(hwnd(), EnumChildProcForRedraw, lparam); } } void NativeWidgetWin::FocusNativeView(gfx::NativeView native_view) { if (IsWindow(native_view)) { if (GetFocus() != native_view) SetFocus(native_view); } else { // NULL or invalid |native_view| passed, we consider this to be clearing // focus. Keep the top level window focused so we continue to receive // key events. SetFocus(hwnd()); } } WidgetImpl* NativeWidgetWin::GetWidgetImpl() { return listener_->GetWidgetImpl(); } const WidgetImpl* NativeWidgetWin::GetWidgetImpl() const { return listener_->GetWidgetImpl(); } //////////////////////////////////////////////////////////////////////////////// // NativeWidetWin, MessageLoopForUI::Observer implementation void NativeWidgetWin::WillProcessMessage(const MSG& msg) { } void NativeWidgetWin::DidProcessMessage(const MSG& msg) { // We need to add ourselves as a message loop observer so that we can repaint // aggressively if the contents of our window become invalid. Unfortunately // WM_PAINT messages are starved and we get flickery redrawing when resizing // if we do not do this. Paint(); } //////////////////////////////////////////////////////////////////////////////// // NativeWidgetWin, message handlers: void NativeWidgetWin::OnActivate(UINT action, BOOL minimized, HWND window) { SetMsgHandled(FALSE); } void NativeWidgetWin::OnActivateApp(BOOL active, DWORD thread_id) { SetMsgHandled(FALSE); } LRESULT NativeWidgetWin::OnAppCommand(HWND window, short app_command, WORD device, int keystate) { SetMsgHandled(FALSE); return 0; } void NativeWidgetWin::OnCancelMode() { } void NativeWidgetWin::OnCaptureChanged(HWND hwnd) { has_capture_ = false; listener_->OnMouseCaptureLost(); } void NativeWidgetWin::OnClose() { listener_->OnClose(); } void NativeWidgetWin::OnCommand(UINT notification_code, int command_id, HWND window) { SetMsgHandled(FALSE); } LRESULT NativeWidgetWin::OnCreate(CREATESTRUCT* create_struct) { SetNativeWindowProperty(kNativeWidgetKey, this); listener_->OnNativeWidgetCreated(); MessageLoopForUI::current()->AddObserver(this); return 0; } void NativeWidgetWin::OnDestroy() { // TODO(beng): drop_target_ props_.reset(); } void NativeWidgetWin::OnDisplayChange(UINT bits_per_pixel, CSize screen_size) { listener_->OnDisplayChanged(); } LRESULT NativeWidgetWin::OnDwmCompositionChanged(UINT message, WPARAM w_param, LPARAM l_param) { SetMsgHandled(FALSE); return 0; } void NativeWidgetWin::OnEndSession(BOOL ending, UINT logoff) { SetMsgHandled(FALSE); } void NativeWidgetWin::OnEnterSizeMove() { SetMsgHandled(FALSE); } LRESULT NativeWidgetWin::OnEraseBkgnd(HDC dc) { // This is needed for magical win32 flicker ju-ju return 1; } void NativeWidgetWin::OnExitMenuLoop(BOOL is_track_popup_menu) { SetMsgHandled(FALSE); } void NativeWidgetWin::OnExitSizeMove() { SetMsgHandled(FALSE); } LRESULT NativeWidgetWin::OnGetObject(UINT message, WPARAM w_param, LPARAM l_param) { return static_cast(0L); } void NativeWidgetWin::OnGetMinMaxInfo(MINMAXINFO* minmax_info) { SetMsgHandled(FALSE); } void NativeWidgetWin::OnHScroll(int scroll_type, short position, HWND scrollbar) { SetMsgHandled(FALSE); } void NativeWidgetWin::OnInitMenu(HMENU menu) { SetMsgHandled(FALSE); } void NativeWidgetWin::OnInitMenuPopup(HMENU menu, UINT position, BOOL is_system_menu) { SetMsgHandled(FALSE); } LRESULT NativeWidgetWin::OnKeyDown(UINT message, WPARAM w_param, LPARAM l_param) { MSG msg; MakeMSG(&msg, message, w_param, l_param); SetMsgHandled(listener_->OnKeyEvent(KeyEvent(msg))); return 0; } LRESULT NativeWidgetWin::OnKeyUp(UINT message, WPARAM w_param, LPARAM l_param) { MSG msg; MakeMSG(&msg, message, w_param, l_param); SetMsgHandled(listener_->OnKeyEvent(KeyEvent(msg))); return 0; } void NativeWidgetWin::OnKillFocus(HWND focused_window) { listener_->OnNativeBlur(focused_window); SetMsgHandled(FALSE); } LRESULT NativeWidgetWin::OnMouseActivate(HWND window, UINT hittest_code, UINT message) { SetMsgHandled(FALSE); return MA_ACTIVATE; } LRESULT NativeWidgetWin::OnMouseLeave(UINT message, WPARAM w_param, LPARAM l_param) { // TODO(beng): tooltip MSG msg; MakeMSG(&msg, message, w_param, l_param); //SetMsgHandled(listener_->OnMouseEvent(MouseEvent(msg))); // Reset our tracking flag so that future mouse movement over this WidgetWin // results in a new tracking session. active_mouse_tracking_flags_ = 0; return 0; } void NativeWidgetWin::OnMove(const CPoint& point) { SetMsgHandled(FALSE); } void NativeWidgetWin::OnMoving(UINT param, LPRECT new_bounds) { } LRESULT NativeWidgetWin::OnMouseRange(UINT message, WPARAM w_param, LPARAM l_param) { // TODO(beng): tooltips ProcessMouseRange(message, w_param, l_param, false); return 0; } LRESULT NativeWidgetWin::OnNCActivate(BOOL active) { SetMsgHandled(FALSE); return 0; } LRESULT NativeWidgetWin::OnNCCalcSize(BOOL w_param, LPARAM l_param) { SetMsgHandled(FALSE); return 0; } LRESULT NativeWidgetWin::OnNCHitTest(UINT message, WPARAM w_param, LPARAM l_param) { LRESULT lr = DefWindowProc(hwnd(), message, w_param, l_param); return lr; } LRESULT NativeWidgetWin::OnNCMouseRange(UINT message, WPARAM w_param, LPARAM l_param) { bool processed = ProcessMouseRange(message, w_param, l_param, true); SetMsgHandled(FALSE); return 0; } void NativeWidgetWin::OnNCPaint(HRGN rgn) { SetMsgHandled(FALSE); } LRESULT NativeWidgetWin::OnNCUAHDrawCaption(UINT message, WPARAM w_param, LPARAM l_param) { SetMsgHandled(FALSE); return 0; } LRESULT NativeWidgetWin::OnNCUAHDrawFrame(UINT message, WPARAM w_param, LPARAM l_param) { SetMsgHandled(FALSE); return 0; } LRESULT NativeWidgetWin::OnNotify(int w_param, NMHDR* l_param) { // TODO(beng): tooltips SetMsgHandled(FALSE); return 0; } void NativeWidgetWin::OnPaint(HDC dc) { if (IsLayeredWindow()) { // We need to clip to the dirty rect ourselves. window_contents_->save(SkCanvas::kClip_SaveFlag); RECT r; GetUpdateRect(hwnd(), &r, FALSE); window_contents_->ClipRectInt(r.left, r.top, r.right - r.left, r.bottom - r.top); listener_->OnPaint(window_contents_.get()); window_contents_->restore(); RECT wr; GetWindowRect(hwnd(), &wr); SIZE size = {wr.right - wr.left, wr.bottom - wr.top}; POINT position = {wr.left, wr.top}; HDC dib_dc = window_contents_->getTopPlatformDevice().getBitmapDC(); POINT zero = {0, 0}; BLENDFUNCTION blend = {AC_SRC_OVER, 0, 125, AC_SRC_ALPHA}; UpdateLayeredWindow(hwnd(), NULL, &position, &size, dib_dc, &zero, RGB(0xFF, 0xFF, 0xFF), &blend, ULW_ALPHA); } else { scoped_ptr canvas( gfx::CanvasPaint::CreateCanvasPaint(hwnd())); listener_->OnPaint(canvas->AsCanvas()); } } LRESULT NativeWidgetWin::OnPowerBroadcast(DWORD power_event, DWORD data) { ui::SystemMonitor* monitor = ui::SystemMonitor::Get(); if (monitor) monitor->ProcessWmPowerBroadcastMessage(power_event); SetMsgHandled(FALSE); return 0; } LRESULT NativeWidgetWin::OnReflectedMessage(UINT message, WPARAM w_param, LPARAM l_param) { SetMsgHandled(FALSE); return 0; } void NativeWidgetWin::OnSetFocus(HWND focused_window) { listener_->OnNativeFocus(focused_window); SetMsgHandled(FALSE); } LRESULT NativeWidgetWin::OnSetIcon(UINT size_type, HICON new_icon) { SetMsgHandled(FALSE); return 0; } LRESULT NativeWidgetWin::OnSetText(const wchar_t* text) { SetMsgHandled(FALSE); return 0; } void NativeWidgetWin::OnSettingChange(UINT flags, const wchar_t* section) { if (flags == SPI_SETWORKAREA) listener_->OnWorkAreaChanged(); SetMsgHandled(FALSE); } void NativeWidgetWin::OnSize(UINT param, const CSize& size) { gfx::Size s(size.cx, size.cy); listener_->OnSizeChanged(s); if (IsLayeredWindow()) { window_contents_.reset( new gfx::CanvasSkia(s.width(), s.height(), false)); } } void NativeWidgetWin::OnSysCommand(UINT notification_code, CPoint click) { SetMsgHandled(FALSE); } void NativeWidgetWin::OnThemeChanged() { gfx::NativeTheme::instance()->CloseHandles(); } void NativeWidgetWin::OnVScroll(int scroll_type, short position, HWND scrollbar) { SetMsgHandled(FALSE); } void NativeWidgetWin::OnWindowPosChanging(WINDOWPOS* window_pos) { SetMsgHandled(FALSE); } void NativeWidgetWin::OnWindowPosChanged(WINDOWPOS* window_pos) { SetMsgHandled(FALSE); } void NativeWidgetWin::OnFinalMessage(HWND window) { delete this; } //////////////////////////////////////////////////////////////////////////////// // NativeWidgetWin, WindowImpl overrides: HICON NativeWidgetWin::GetDefaultWindowIcon() const { return NULL; } LRESULT NativeWidgetWin::OnWndProc(UINT message, WPARAM w_param, LPARAM l_param) { LRESULT result = 0; // Otherwise we handle everything else. if (!ProcessWindowMessage(hwnd(), message, w_param, l_param, result)) result = DefWindowProc(hwnd(), message, w_param, l_param); if (message == WM_NCDESTROY) { MessageLoopForUI::current()->RemoveObserver(this); OnFinalMessage(hwnd()); } return result; } //////////////////////////////////////////////////////////////////////////////// // NativeWidgetWin, private: void NativeWidgetWin::TrackMouseEvents(DWORD mouse_tracking_flags) { // Begin tracking mouse events for this HWND so that we get WM_MOUSELEAVE // when the user moves the mouse outside this HWND's bounds. if (active_mouse_tracking_flags_ == 0 || mouse_tracking_flags & TME_CANCEL) { if (mouse_tracking_flags & TME_CANCEL) { // We're about to cancel active mouse tracking, so empty out the stored // state. active_mouse_tracking_flags_ = 0; } else { active_mouse_tracking_flags_ = mouse_tracking_flags; } TRACKMOUSEEVENT tme; tme.cbSize = sizeof(tme); tme.dwFlags = mouse_tracking_flags; tme.hwndTrack = hwnd(); tme.dwHoverTime = 0; TrackMouseEvent(&tme); } else if (mouse_tracking_flags != active_mouse_tracking_flags_) { TrackMouseEvents(active_mouse_tracking_flags_ | TME_CANCEL); TrackMouseEvents(mouse_tracking_flags); } } bool NativeWidgetWin::ProcessMouseRange(UINT message, WPARAM w_param, LPARAM l_param, bool non_client) { MSG msg; MakeMSG(&msg, message, w_param, l_param); if (message == WM_MOUSEWHEEL) { // Reroute the mouse-wheel to the window under the mouse pointer if // applicable. // TODO(beng): //if (views::RerouteMouseWheel(hwnd(), w_param, l_param)) // return 0; //return listener_->OnMouseWheelEvent(MouseWheelEvent(msg)); return 0; } // Windows only fires WM_MOUSELEAVE events if the application begins // "tracking" mouse events for a given HWND during WM_MOUSEMOVE events. // We need to call |TrackMouseEvents| to listen for WM_MOUSELEAVE. if (!has_capture_) TrackMouseEvents(non_client ? TME_NONCLIENT | TME_LEAVE : TME_LEAVE); //return listener_->OnMouseEvent(MouseEvent(msg)); return 0; } void NativeWidgetWin::MakeMSG(MSG* msg, UINT message, WPARAM w_param, LPARAM l_param) const { msg->hwnd = hwnd(); msg->message = message; msg->wParam = w_param; msg->lParam = l_param; msg->time = 0; msg->pt.x = msg->pt.y = 0; } void NativeWidgetWin::CloseNow() { DestroyWindow(hwnd()); } bool NativeWidgetWin::IsLayeredWindow() const { return !!(window_ex_style() & WS_EX_LAYERED); } } // namespace internal //////////////////////////////////////////////////////////////////////////////// // NativeWidget, public: // static NativeWidget* NativeWidget::CreateNativeWidget( internal::NativeWidgetListener* listener) { return new internal::NativeWidgetWin(listener); } // static NativeWidget* NativeWidget::GetNativeWidgetForNativeView( gfx::NativeView native_view) { if (!ui::WindowImpl::IsWindowImpl(native_view)) return NULL; return reinterpret_cast( ui::ViewProp::GetValue(native_view, internal::kNativeWidgetKey)); } // static NativeWidget* NativeWidget::GetNativeWidgetForNativeWindow( gfx::NativeWindow native_window) { return GetNativeWidgetForNativeView(native_window); } // static NativeWidget* NativeWidget::GetTopLevelNativeWidget( gfx::NativeView native_view) { // First, check if the top-level window is a Widget. HWND root = ::GetAncestor(native_view, GA_ROOT); if (!root) return NULL; NativeWidget* widget = GetNativeWidgetForNativeView(root); if (widget) return widget; // Second, try to locate the last Widget window in the parent hierarchy. HWND parent_hwnd = native_view; NativeWidget* parent_widget; do { parent_widget = GetNativeWidgetForNativeView(parent_hwnd); if (parent_widget) { widget = parent_widget; parent_hwnd = ::GetAncestor(parent_hwnd, GA_PARENT); } } while (parent_hwnd != NULL && parent_widget != NULL); return widget; } } // namespace views