diff options
author | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-13 22:01:34 +0000 |
---|---|---|
committer | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-13 22:01:34 +0000 |
commit | 16ead307a2b27b7d02bae66792eaf3c2f904b9c0 (patch) | |
tree | 690db8ac50fa9dbb4ef91ae8c859e8074250b283 | |
parent | 4604d1f4944206f24e864c71e29bc08b4d9500af (diff) | |
download | chromium_src-16ead307a2b27b7d02bae66792eaf3c2f904b9c0.zip chromium_src-16ead307a2b27b7d02bae66792eaf3c2f904b9c0.tar.gz chromium_src-16ead307a2b27b7d02bae66792eaf3c2f904b9c0.tar.bz2 |
this should have been reinstated in the revert
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@11673 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/views/window.cc | 1194 |
1 files changed, 1194 insertions, 0 deletions
diff --git a/chrome/views/window.cc b/chrome/views/window.cc new file mode 100644 index 0000000..4bf24ee --- /dev/null +++ b/chrome/views/window.cc @@ -0,0 +1,1194 @@ +// Copyright (c) 2006-2008 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 "chrome/views/window.h" + +#include <shellapi.h> + +#include "base/win_util.h" +#include "chrome/app/chrome_dll_resource.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/common/gfx/icon_util.h" +#include "chrome/common/gfx/path.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/pref_service.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/win_util.h" +#include "chrome/views/client_view.h" +#include "chrome/views/custom_frame_view.h" +#include "chrome/views/native_frame_view.h" +#include "chrome/views/non_client_view.h" +#include "chrome/views/root_view.h" +#include "chrome/views/window_delegate.h" +#include "grit/generated_resources.h" + +namespace views { + +// A scoping class that prevents a window from being able to redraw in response +// to invalidations that may occur within it for the lifetime of the object. +// +// Why would we want such a thing? Well, it turns out Windows has some +// "unorthodox" behavior when it comes to painting its non-client areas. +// Occasionally, Windows will paint portions of the default non-client area +// right over the top of the custom frame. This is not simply fixed by handling +// WM_NCPAINT/WM_PAINT, with some investigation it turns out that this +// rendering is being done *inside* the default implementation of some message +// handlers and functions: +// . WM_SETTEXT +// . WM_SETICON +// . WM_NCLBUTTONDOWN +// . EnableMenuItem, called from our WM_INITMENU handler +// The solution is to handle these messages and call DefWindowProc ourselves, +// but prevent the window from being able to update itself for the duration of +// the call. We do this with this class, which automatically calls its +// associated Window's lock and unlock functions as it is created and destroyed. +// See documentation in those methods for the technique used. +// +// IMPORTANT: Do not use this scoping object for large scopes or periods of +// time! IT WILL PREVENT THE WINDOW FROM BEING REDRAWN! (duh). +// +// I would love to hear Raymond Chen's explanation for all this. And maybe a +// list of other messages that this applies to ;-) +class Window::ScopedRedrawLock { + public: + explicit ScopedRedrawLock(Window* window) : window_(window) { + window_->LockUpdates(); + } + + ~ScopedRedrawLock() { + window_->UnlockUpdates(); + } + + private: + // The window having its style changed. + Window* window_; +}; + +HCURSOR Window::resize_cursors_[6]; + +// If the hung renderer warning doesn't fit on screen, the amount of padding to +// be left between the edge of the window and the edge of the nearest monitor, +// after the window is nudged back on screen. Pixels. +static const int kMonitorEdgePadding = 10; + +//////////////////////////////////////////////////////////////////////////////// +// Window, public: + +Window::~Window() { +} + +// static +Window* Window::CreateChromeWindow(HWND parent, + const gfx::Rect& bounds, + WindowDelegate* window_delegate) { + Window* window = new Window(window_delegate); + window->non_client_view_->SetFrameView(window->CreateFrameViewForWindow()); + window->Init(parent, bounds); + return window; +} + +gfx::Size Window::CalculateMaximumSize() const { + // If this is a top level window, the maximum size is the size of the working + // rect of the display the window is on, less padding. If this is a child + // (constrained) window, the maximum size of this Window are the bounds of the + // parent window, less padding. + DCHECK(GetNativeView()) << "Cannot calculate maximum size before Init() is called"; + gfx::Rect working_rect; + HWND parent_hwnd = ::GetParent(GetNativeView()); + if (parent_hwnd) { + RECT parent_rect; + ::GetClientRect(parent_hwnd, &parent_rect); + working_rect = parent_rect; + } else { + HMONITOR current_monitor = + ::MonitorFromWindow(GetNativeView(), MONITOR_DEFAULTTONEAREST); + MONITORINFO mi; + mi.cbSize = sizeof(mi); + ::GetMonitorInfo(current_monitor, &mi); + working_rect = mi.rcWork; + } + working_rect.Inset(kMonitorEdgePadding, kMonitorEdgePadding); + return working_rect.size(); +} + +void Window::Show() { + int show_state = GetShowState(); + if (saved_maximized_state_) + show_state = SW_SHOWMAXIMIZED; + Show(show_state); +} + +void Window::Show(int show_state) { + ShowWindow(show_state); + // When launched from certain programs like bash and Windows Live Messenger, + // show_state is set to SW_HIDE, so we need to correct that condition. We + // don't just change show_state to SW_SHOWNORMAL because MSDN says we must + // always first call ShowWindow with the specified value from STARTUPINFO, + // otherwise all future ShowWindow calls will be ignored (!!#@@#!). Instead, + // we call ShowWindow again in this case. + if (show_state == SW_HIDE) { + show_state = SW_SHOWNORMAL; + ShowWindow(show_state); + } + + // We need to explicitly activate the window if we've been shown with a state + // that should activate, because if we're opened from a desktop shortcut while + // an existing window is already running it doesn't seem to be enough to use + // one of these flags to activate the window. + if (show_state == SW_SHOWNORMAL) + Activate(); + + SetInitialFocus(); +} + +int Window::GetShowState() const { + return SW_SHOWNORMAL; +} + +void Window::Activate() { + if (IsMinimized()) + ::ShowWindow(GetNativeView(), SW_RESTORE); + ::SetWindowPos(GetNativeView(), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); + SetForegroundWindow(GetNativeView()); +} + +void Window::SetBounds(const gfx::Rect& bounds) { + SetBounds(bounds, NULL); +} + +void Window::SetBounds(const gfx::Rect& bounds, HWND other_hwnd) { + win_util::SetChildBounds(GetNativeView(), GetParent(), other_hwnd, bounds, + kMonitorEdgePadding, 0); +} + +void Window::Close() { + if (window_closed_) { + // It appears we can hit this code path if you close a modal dialog then + // close the last browser before the destructor is hit, which triggers + // invoking Close again. I'm short circuiting this code path to avoid + // calling into the delegate twice, which is problematic. + return; + } + + if (non_client_view_->CanClose()) { + SaveWindowPosition(); + RestoreEnabledIfNecessary(); + WidgetWin::Close(); + // If the user activates another app after opening us, then comes back and + // closes us, we want our owner to gain activation. But only if the owner + // is visible. If we don't manually force that here, the other app will + // regain activation instead. + if (owning_hwnd_ && GetNativeView() == GetForegroundWindow() && + IsWindowVisible(owning_hwnd_)) { + SetForegroundWindow(owning_hwnd_); + } + window_closed_ = true; + } +} + +bool Window::IsMaximized() const { + return !!::IsZoomed(GetNativeView()); +} + +bool Window::IsMinimized() const { + return !!::IsIconic(GetNativeView()); +} + +void Window::EnableClose(bool enable) { + // If the native frame is rendering its own close button, ask it to disable. + non_client_view_->EnableClose(enable); + + // Disable the native frame's close button regardless of whether or not the + // native frame is in use, since this also affects the system menu. + EnableMenuItem(GetSystemMenu(GetNativeView(), false), + SC_CLOSE, enable ? MF_ENABLED : MF_GRAYED); + + // Let the window know the frame changed. + SetWindowPos(NULL, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOCOPYBITS | + SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREPOSITION | + SWP_NOSENDCHANGING | SWP_NOSIZE | SWP_NOZORDER); +} + +void Window::DisableInactiveRendering() { + disable_inactive_rendering_ = true; + non_client_view_->DisableInactiveRendering(disable_inactive_rendering_); +} + +void Window::UpdateWindowTitle() { + // If the non-client view is rendering its own title, it'll need to relayout + // now. + non_client_view_->Layout(); + + // Update the native frame's text. We do this regardless of whether or not + // the native frame is being used, since this also updates the taskbar, etc. + std::wstring window_title = window_delegate_->GetWindowTitle(); + std::wstring localized_text; + if (l10n_util::AdjustStringForLocaleDirection(window_title, &localized_text)) + window_title.assign(localized_text); + SetWindowText(GetNativeView(), window_title.c_str()); +} + +void Window::UpdateWindowIcon() { + // If the non-client view is rendering its own icon, we need to tell it to + // repaint. + non_client_view_->SchedulePaint(); + + // Update the native frame's icon. We do this regardless of whether or not + // the native frame is being used, since this also updates the taskbar, etc. + SkBitmap icon = window_delegate_->GetWindowIcon(); + if (!icon.isNull()) { + HICON windows_icon = IconUtil::CreateHICONFromSkBitmap(icon); + // We need to make sure to destroy the previous icon, otherwise we'll leak + // these GDI objects until we crash! + HICON old_icon = reinterpret_cast<HICON>( + SendMessage(GetNativeView(), WM_SETICON, ICON_SMALL, + reinterpret_cast<LPARAM>(windows_icon))); + if (old_icon) + DestroyIcon(old_icon); + old_icon = reinterpret_cast<HICON>( + SendMessage(GetNativeView(), WM_SETICON, ICON_BIG, + reinterpret_cast<LPARAM>(windows_icon))); + if (old_icon) + DestroyIcon(old_icon); + } +} + +void Window::ExecuteSystemMenuCommand(int command) { + if (command) + SendMessage(GetNativeView(), WM_SYSCOMMAND, command, 0); +} + +gfx::Rect Window::GetWindowBoundsForClientBounds( + const gfx::Rect& client_bounds) { + return non_client_view_->GetWindowBoundsForClientBounds(client_bounds); +} + +// static +int Window::GetLocalizedContentsWidth(int col_resource_id) { + double chars = _wtof(l10n_util::GetString(col_resource_id).c_str()); + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + ChromeFont font = rb.GetFont(ResourceBundle::BaseFont); + int width = font.GetExpectedTextWidth(static_cast<int>(chars)); + DCHECK(width > 0); + return width; +} + +// static +int Window::GetLocalizedContentsHeight(int row_resource_id) { + double lines = _wtof(l10n_util::GetString(row_resource_id).c_str()); + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + ChromeFont font = rb.GetFont(ResourceBundle::BaseFont); + int height = static_cast<int>(font.height() * lines); + DCHECK(height > 0); + return height; +} + +// static +gfx::Size Window::GetLocalizedContentsSize(int col_resource_id, + int row_resource_id) { + return gfx::Size(GetLocalizedContentsWidth(col_resource_id), + GetLocalizedContentsHeight(row_resource_id)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Window, NotificationObserver implementation: + +void Window::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + // This window is closed when the last app window is closed. + DCHECK(type == NotificationType::ALL_APPWINDOWS_CLOSED); + // Only registered as an observer when we're not an app window. + // XXX DCHECK(!IsAppWindow()); + Close(); +} + +/////////////////////////////////////////////////////////////////////////////// +// Window, protected: + +Window::Window(WindowDelegate* window_delegate) + : WidgetWin(), + focus_on_creation_(true), + window_delegate_(window_delegate), + non_client_view_(new NonClientView(this)), + owning_hwnd_(NULL), + minimum_size_(100, 100), + is_modal_(false), + restored_enabled_(false), + is_always_on_top_(false), + window_closed_(false), + disable_inactive_rendering_(false), + is_active_(false), + lock_updates_(false), + saved_window_style_(0), + saved_maximized_state_(0), + force_hidden_(false) { + InitClass(); + DCHECK(window_delegate_); + window_delegate_->window_.reset(this); + // Initialize these values to 0 so that subclasses can override the default + // behavior before calling Init. + set_window_style(0); + set_window_ex_style(0); +} + +void Window::Init(HWND parent, const gfx::Rect& bounds) { + // We need to save the parent window, since later calls to GetParent() will + // return NULL. + owning_hwnd_ = parent; + // We call this after initializing our members since our implementations of + // assorted WidgetWin functions may be called during initialization. + is_modal_ = window_delegate_->IsModal(); + if (is_modal_) + BecomeModal(); + is_always_on_top_ = window_delegate_->IsAlwaysOnTop(); + + if (window_style() == 0) + set_window_style(CalculateWindowStyle()); + if (window_ex_style() == 0) + set_window_ex_style(CalculateWindowExStyle()); + + WidgetWin::Init(parent, bounds, true); + win_util::SetWindowUserData(GetNativeView(), this); + + // Create the ClientView, add it to the NonClientView and add the + // NonClientView to the RootView. This will cause everything to be parented. + non_client_view_->set_client_view(window_delegate_->CreateClientView(this)); + WidgetWin::SetContentsView(non_client_view_); + + UpdateWindowTitle(); + + SetInitialBounds(bounds); + InitAlwaysOnTopState(); + + if (!IsAppWindow()) { + notification_registrar_.Add( + this, + NotificationType::ALL_APPWINDOWS_CLOSED, + NotificationService::AllSources()); + } + + ResetWindowRegion(false); +} + +NonClientFrameView* Window::CreateFrameViewForWindow() { + if (non_client_view_->UseNativeFrame()) + return new NativeFrameView(this); + return new CustomFrameView(this); +} + +void Window::UpdateFrameAfterFrameChange() { + // We've either gained or lost a custom window region, so reset it now. + ResetWindowRegion(true); +} + +void Window::SizeWindowToDefault() { + win_util::CenterAndSizeWindow(owning_window(), GetNativeView(), + non_client_view_->GetPreferredSize().ToSIZE(), + false); +} + +void Window::RunSystemMenu(const gfx::Point& point) { + // We need to reset and clean up any currently created system menu objects. + // We need to call this otherwise there's a small chance that we aren't going + // to get a system menu. We also can't take the return value of this + // function. We need to call it *again* to get a valid HMENU. + //::GetSystemMenu(GetNativeView(), TRUE); + HMENU system_menu = ::GetSystemMenu(GetNativeView(), FALSE); + int id = ::TrackPopupMenu(system_menu, + TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD, + point.x(), point.y(), 0, GetNativeView(), NULL); + ExecuteSystemMenuCommand(id); +} + +/////////////////////////////////////////////////////////////////////////////// +// Window, WidgetWin overrides: + +void Window::OnActivate(UINT action, BOOL minimized, HWND window) { + if (action == WA_INACTIVE) + SaveWindowPosition(); +} + +void Window::OnActivateApp(BOOL active, DWORD thread_id) { + if (!active && thread_id != GetCurrentThreadId()) { + // Another application was activated, we should reset any state that + // disables inactive rendering now. + disable_inactive_rendering_ = false; + non_client_view_->DisableInactiveRendering(false); + // Update the native frame too, since it could be rendering the non-client + // area. + CallDefaultNCActivateHandler(FALSE); + } +} + +LRESULT Window::OnAppCommand(HWND window, short app_command, WORD device, + int keystate) { + // We treat APPCOMMAND ids as an extension of our command namespace, and just + // let the delegate figure out what to do... + if (!window_delegate_->ExecuteWindowsCommand(app_command)) + return WidgetWin::OnAppCommand(window, app_command, device, keystate); + return 0; +} + +void Window::OnCommand(UINT notification_code, int command_id, HWND window) { + // If the notification code is > 1 it means it is control specific and we + // should ignore it. + if (notification_code > 1 || + window_delegate_->ExecuteWindowsCommand(command_id)) { + WidgetWin::OnCommand(notification_code, command_id, window); + } +} + +void Window::OnDestroy() { + non_client_view_->WindowClosing(); + RestoreEnabledIfNecessary(); + WidgetWin::OnDestroy(); +} + +namespace { +static BOOL CALLBACK SendDwmCompositionChanged(HWND window, LPARAM param) { + SendMessage(window, WM_DWMCOMPOSITIONCHANGED, 0, 0); + return TRUE; +} +} // namespace + +LRESULT Window::OnDwmCompositionChanged(UINT msg, WPARAM w_param, + LPARAM l_param) { + // We respond to this in response to WM_DWMCOMPOSITIONCHANGED since that is + // the only thing we care about - we don't actually respond to WM_THEMECHANGED + // messages. + non_client_view_->SystemThemeChanged(); + + // WM_DWMCOMPOSITIONCHANGED is only sent to top level windows, however we want + // to notify our children too, since we can have MDI child windows who need to + // update their appearance. + EnumChildWindows(GetNativeView(), &SendDwmCompositionChanged, NULL); + return 0; +} + +void Window::OnFinalMessage(HWND window) { + // Delete and NULL the delegate here once we're guaranteed to get no more + // messages. + window_delegate_->DeleteDelegate(); + window_delegate_ = NULL; + WidgetWin::OnFinalMessage(window); +} + +namespace { +static void EnableMenuItem(HMENU menu, UINT command, bool enabled) { + UINT flags = MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_DISABLED | MF_GRAYED); + EnableMenuItem(menu, command, flags); +} +} // namespace + +void Window::OnInitMenu(HMENU menu) { + // We only need to manually enable the system menu if we're not using a native + // frame. + if (non_client_view_->UseNativeFrame()) + WidgetWin::OnInitMenu(menu); + + bool is_minimized = IsMinimized(); + bool is_maximized = IsMaximized(); + bool is_restored = !is_minimized && !is_maximized; + + ScopedRedrawLock lock(this); + EnableMenuItem(menu, SC_RESTORE, !is_restored); + EnableMenuItem(menu, SC_MOVE, is_restored); + EnableMenuItem(menu, SC_SIZE, window_delegate()->CanResize() && is_restored); + EnableMenuItem(menu, SC_MAXIMIZE, + window_delegate()->CanMaximize() && !is_maximized); + EnableMenuItem(menu, SC_MINIMIZE, + window_delegate()->CanMaximize() && !is_minimized); +} + +void Window::OnMouseLeave() { + // We only need to manually track WM_MOUSELEAVE messages between the client + // and non-client area when we're not using the native frame. + if (non_client_view_->UseNativeFrame()) { + SetMsgHandled(FALSE); + return; + } + + bool process_mouse_exited = true; + POINT pt; + if (GetCursorPos(&pt)) { + LRESULT ht_component = + ::SendMessage(GetNativeView(), WM_NCHITTEST, 0, MAKELPARAM(pt.x, pt.y)); + if (ht_component != HTNOWHERE) { + // If the mouse moved into a part of the window's non-client area, then + // don't send a mouse exited event since the mouse is still within the + // bounds of the ChromeView that's rendering the frame. Note that we do + // _NOT_ do this for windows with native frames, since in that case the + // mouse really will have left the bounds of the RootView. + process_mouse_exited = false; + } + } + + if (process_mouse_exited) + ProcessMouseExited(); +} + +LRESULT Window::OnNCActivate(BOOL active) { + is_active_ = !!active; + + // If we're not using the native frame, we need to force a synchronous repaint + // otherwise we'll be left in the wrong activation state until something else + // causes a repaint later. + if (!non_client_view_->UseNativeFrame()) { + // We can get WM_NCACTIVATE before we're actually visible. If we're not + // visible, no need to paint. + if (IsWindowVisible(GetNativeView())) { + non_client_view_->SchedulePaint(); + // We need to force a paint now, as a user dragging a window will block + // painting operations while the move is in progress. + PaintNow(root_view_->GetScheduledPaintRect()); + } + } + + // If we're active again, we should be allowed to render as inactive, so + // tell the non-client view. This must be done independently of the check for + // disable_inactive_rendering_ since that check is valid even if the frame + // is not active, but this can only be done if we've become active. + if (is_active_) + non_client_view_->DisableInactiveRendering(false); + + // Reset the disable inactive rendering state since activation has changed. + if (disable_inactive_rendering_) { + disable_inactive_rendering_ = false; + return CallDefaultNCActivateHandler(TRUE); + } + return CallDefaultNCActivateHandler(active); +} + +LRESULT Window::OnNCCalcSize(BOOL mode, LPARAM l_param) { + // We only need to adjust the client size/paint handling when we're not using + // the native frame. + if (non_client_view_->UseNativeFrame()) + return WidgetWin::OnNCCalcSize(mode, l_param); + + RECT* client_rect = mode ? + &reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param)->rgrc[0] : + reinterpret_cast<RECT*>(l_param); + if (IsMaximized()) { + // Make the maximized mode client rect fit the screen exactly, by + // subtracting the border Windows automatically adds for maximized mode. + int border_thickness = GetSystemMetrics(SM_CXSIZEFRAME); + InflateRect(client_rect, -border_thickness, -border_thickness); + + // Find all auto-hide taskbars along the screen edges and adjust in by the + // thickness of the auto-hide taskbar on each such edge, so the window isn't + // treated as a "fullscreen app", which would cause the taskbars to + // disappear. + HMONITOR monitor = MonitorFromWindow(GetNativeView(), + MONITOR_DEFAULTTONEAREST); + if (win_util::EdgeHasAutoHideTaskbar(ABE_LEFT, monitor)) + client_rect->left += win_util::kAutoHideTaskbarThicknessPx; + if (win_util::EdgeHasAutoHideTaskbar(ABE_TOP, monitor)) + client_rect->top += win_util::kAutoHideTaskbarThicknessPx; + if (win_util::EdgeHasAutoHideTaskbar(ABE_RIGHT, monitor)) + client_rect->right -= win_util::kAutoHideTaskbarThicknessPx; + if (win_util::EdgeHasAutoHideTaskbar(ABE_BOTTOM, monitor)) + client_rect->bottom -= win_util::kAutoHideTaskbarThicknessPx; + + // We cannot return WVR_REDRAW when there is nonclient area, or Windows + // exhibits bugs where client pixels and child HWNDs are mispositioned by + // the width/height of the upper-left nonclient area. + return 0; + } + + // If the window bounds change, we're going to relayout and repaint anyway. + // Returning WVR_REDRAW avoids an extra paint before that of the old client + // pixels in the (now wrong) location, and thus makes actions like resizing a + // window from the left edge look slightly less broken. + return mode ? WVR_REDRAW : 0; +} + +LRESULT Window::OnNCHitTest(const CPoint& point) { + // First, give the NonClientView a chance to test the point to see if it + // provides any of the non-client area. + CPoint temp = point; + MapWindowPoints(HWND_DESKTOP, GetNativeView(), &temp, 1); + int component = non_client_view_->NonClientHitTest(gfx::Point(temp)); + if (component != HTNOWHERE) + return component; + + // Otherwise, we let Windows do all the native frame non-client handling for + // us. + return WidgetWin::OnNCHitTest(point); +} + +namespace { +struct ClipState { + // The window being painted. + HWND parent; + + // DC painting to. + HDC dc; + + // Origin of the window in terms of the screen. + int x; + int y; +}; + +// See comments in OnNCPaint for details of this function. +static BOOL CALLBACK ClipDCToChild(HWND window, LPARAM param) { + ClipState* clip_state = reinterpret_cast<ClipState*>(param); + if (GetParent(window) == clip_state->parent && IsWindowVisible(window)) { + RECT bounds; + GetWindowRect(window, &bounds); + ExcludeClipRect(clip_state->dc, + bounds.left - clip_state->x, + bounds.top - clip_state->y, + bounds.right - clip_state->x, + bounds.bottom - clip_state->y); + } + return TRUE; +} +} // namespace + +void Window::OnNCPaint(HRGN rgn) { + // We only do non-client painting if we're not using the native frame. + if (non_client_view_->UseNativeFrame()) { + WidgetWin::OnNCPaint(rgn); + return; + } + + // We have an NC region and need to paint it. We expand the NC region to + // include the dirty region of the root view. This is done to minimize + // paints. + CRect window_rect; + GetWindowRect(&window_rect); + + if (window_rect.Width() != root_view_->width() || + window_rect.Height() != root_view_->height()) { + // If the size of the window differs from the size of the root view it + // means we're being asked to paint before we've gotten a WM_SIZE. This can + // happen when the user is interactively resizing the window. To avoid + // mass flickering we don't do anything here. Once we get the WM_SIZE we'll + // reset the region of the window which triggers another WM_NCPAINT and + // all is well. + return; + } + + CRect dirty_region; + // A value of 1 indicates paint all. + if (!rgn || rgn == reinterpret_cast<HRGN>(1)) { + dirty_region = CRect(0, 0, window_rect.Width(), window_rect.Height()); + } else { + RECT rgn_bounding_box; + GetRgnBox(rgn, &rgn_bounding_box); + if (!IntersectRect(&dirty_region, &rgn_bounding_box, &window_rect)) + return; // Dirty region doesn't intersect window bounds, bale. + + // rgn_bounding_box is in screen coordinates. Map it to window coordinates. + OffsetRect(&dirty_region, -window_rect.left, -window_rect.top); + } + + // In theory GetDCEx should do what we want, but I couldn't get it to work. + // In particular the docs mentiond DCX_CLIPCHILDREN, but as far as I can tell + // it doesn't work at all. So, instead we get the DC for the window then + // manually clip out the children. + HDC dc = GetWindowDC(GetNativeView()); + ClipState clip_state; + clip_state.x = window_rect.left; + clip_state.y = window_rect.top; + clip_state.parent = GetNativeView(); + clip_state.dc = dc; + EnumChildWindows(GetNativeView(), &ClipDCToChild, + reinterpret_cast<LPARAM>(&clip_state)); + + RootView* root_view = GetRootView(); + CRect old_paint_region = root_view->GetScheduledPaintRectConstrainedToSize(); + + if (!old_paint_region.IsRectEmpty()) { + // The root view has a region that needs to be painted. Include it in the + // region we're going to paint. + + CRect tmp = dirty_region; + UnionRect(&dirty_region, &tmp, &old_paint_region); + } + + root_view->SchedulePaint(gfx::Rect(dirty_region), false); + + // ChromeCanvasPaints destructor does the actual painting. As such, wrap the + // following in a block to force paint to occur so that we can release the dc. + { + ChromeCanvasPaint canvas(dc, opaque(), dirty_region.left, dirty_region.top, + dirty_region.Width(), dirty_region.Height()); + + root_view->ProcessPaint(&canvas); + } + + ReleaseDC(GetNativeView(), dc); +} + +void Window::OnNCLButtonDown(UINT ht_component, const CPoint& point) { + // When we're using a native frame, window controls work without us + // interfering. + if (!non_client_view_->UseNativeFrame()) { + switch (ht_component) { + case HTCLOSE: + case HTMINBUTTON: + case HTMAXBUTTON: { + // When the mouse is pressed down in these specific non-client areas, + // we need to tell the RootView to send the mouse pressed event (which + // sets capture, allowing subsequent WM_LBUTTONUP (note, _not_ + // WM_NCLBUTTONUP) to fire so that the appropriate WM_SYSCOMMAND can be + // sent by the applicable button's ButtonListener. We _have_ to do this + // way rather than letting Windows just send the syscommand itself (as + // would happen if we never did this dance) because for some insane + // reason DefWindowProc for WM_NCLBUTTONDOWN also renders the pressed + // window control button appearance, in the Windows classic style, over + // our view! Ick! By handling this message we prevent Windows from + // doing this undesirable thing, but that means we need to roll the + // sys-command handling ourselves. + ProcessNCMousePress(point, MK_LBUTTON); + return; + } + } + } + + // TODO(beng): figure out why we need to run the system menu manually + // ourselves. This is wrong and causes many subtle bugs. + // From my initial research, it looks like DefWindowProc tries + // to run it but fails before sending the initial WM_MENUSELECT + // for the sysmenu. + if (ht_component == HTSYSMENU) + RunSystemMenu(non_client_view_->GetSystemMenuPoint()); + else + WidgetWin::OnNCLButtonDown(ht_component, point); + + /* TODO(beng): Fix the standard non-client over-painting bug. This code + doesn't work but identifies the problem. + if (!IsMsgHandled()) { + // Window::OnNCLButtonDown set the message as unhandled. This normally + // means WidgetWin::ProcessWindowMessage will pass it to + // DefWindowProc. Sadly, DefWindowProc for WM_NCLBUTTONDOWN does weird + // non-client painting, so we need to call it directly here inside a + // scoped update lock. + ScopedRedrawLock lock(this); + DefWindowProc(GetNativeView(), WM_NCLBUTTONDOWN, ht_component, + MAKELPARAM(point.x, point.y)); + SetMsgHandled(TRUE); + } + */ +} + +void Window::OnNCRButtonDown(UINT ht_component, const CPoint& point) { + if (ht_component == HTCAPTION || ht_component == HTSYSMENU) + RunSystemMenu(gfx::Point(point)); + else + WidgetWin::OnNCRButtonDown(ht_component, point); +} + +LRESULT Window::OnNCUAHDrawCaption(UINT msg, WPARAM w_param, + LPARAM l_param) { + // See comment in widget_win.h at the definition of WM_NCUAHDRAWCAPTION for + // an explanation about why we need to handle this message. + SetMsgHandled(!non_client_view_->UseNativeFrame()); + return 0; +} + +LRESULT Window::OnNCUAHDrawFrame(UINT msg, WPARAM w_param, + LPARAM l_param) { + // See comment in widget_win.h at the definition of WM_NCUAHDRAWCAPTION for + // an explanation about why we need to handle this message. + SetMsgHandled(!non_client_view_->UseNativeFrame()); + return 0; +} + +LRESULT Window::OnSetCursor(HWND window, UINT hittest_code, UINT message) { + // If the window is disabled, it's because we're showing a modal dialog box. + // We need to let DefWindowProc handle the message. That's because + // DefWindowProc for WM_SETCURSOR with message = some kind of mouse button + // down message sends the top level window a WM_ACTIVATEAPP message, which we + // otherwise wouldn't get. The symptom of not doing this is that if the user + // has a window in the background with a modal dialog open, they can't click + // on the disabled background window to bring the entire stack to the front. + // This is annoying because they then have to move all the foreground windows + // out of the way to be able to activate said window. I love how on Windows, + // the answer isn't always logical. + if (!IsWindowEnabled(GetNativeView())) + return WidgetWin::OnSetCursor(window, hittest_code, message); + + int index = RC_NORMAL; + switch (hittest_code) { + case HTTOP: + case HTBOTTOM: + index = RC_VERTICAL; + break; + case HTTOPLEFT: + case HTBOTTOMRIGHT: + index = RC_NWSE; + break; + case HTTOPRIGHT: + case HTBOTTOMLEFT: + index = RC_NESW; + break; + case HTLEFT: + case HTRIGHT: + index = RC_HORIZONTAL; + break; + case HTCAPTION: + case HTCLIENT: + index = RC_NORMAL; + break; + } + SetCursor(resize_cursors_[index]); + return 0; +} + +LRESULT Window::OnSetIcon(UINT size_type, HICON new_icon) { + // This shouldn't hurt even if we're using the native frame. + ScopedRedrawLock lock(this); + return DefWindowProc(GetNativeView(), WM_SETICON, size_type, + reinterpret_cast<LPARAM>(new_icon)); +} + +LRESULT Window::OnSetText(const wchar_t* text) { + // This shouldn't hurt even if we're using the native frame. + ScopedRedrawLock lock(this); + return DefWindowProc(GetNativeView(), WM_SETTEXT, NULL, + reinterpret_cast<LPARAM>(text)); +} + +void Window::OnSize(UINT size_param, const CSize& new_size) { + // Don't no-op if the new_size matches current size. If our normal bounds + // and maximized bounds are the same, then we need to layout (because we + // layout differently when maximized). + SaveWindowPosition(); + ChangeSize(size_param, new_size); + RedrawWindow(GetNativeView(), NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN); + + // ResetWindowRegion is going to trigger WM_NCPAINT. By doing it after we've + // invoked OnSize we ensure the RootView has been laid out. + ResetWindowRegion(false); +} + +void Window::OnSysCommand(UINT notification_code, CPoint click) { + if (!non_client_view_->UseNativeFrame()) { + // Windows uses the 4 lower order bits of |notification_code| for type- + // specific information so we must exclude this when comparing. + static const int sc_mask = 0xFFF0; + if ((notification_code & sc_mask) == SC_MINIMIZE || + (notification_code & sc_mask) == SC_MAXIMIZE || + (notification_code & sc_mask) == SC_RESTORE) { + non_client_view_->ResetWindowControls(); + } else if ((notification_code & sc_mask) == SC_MOVE || + (notification_code & sc_mask) == SC_SIZE) { + if (lock_updates_) { + // We were locked, before entering a resize or move modal loop. Now that + // we've begun to move the window, we need to unlock updates so that the + // sizing/moving feedback can be continuous. + UnlockUpdates(); + } + } + } + + // First see if the delegate can handle it. + if (window_delegate_->ExecuteWindowsCommand(notification_code)) + return; + + if (notification_code == IDC_ALWAYS_ON_TOP) { + is_always_on_top_ = !is_always_on_top_; + + // Change the menu check state. + HMENU system_menu = GetSystemMenu(GetNativeView(), FALSE); + MENUITEMINFO menu_info; + memset(&menu_info, 0, sizeof(MENUITEMINFO)); + menu_info.cbSize = sizeof(MENUITEMINFO); + BOOL r = GetMenuItemInfo(system_menu, IDC_ALWAYS_ON_TOP, + FALSE, &menu_info); + DCHECK(r); + menu_info.fMask = MIIM_STATE; + if (is_always_on_top_) + menu_info.fState = MFS_CHECKED; + r = SetMenuItemInfo(system_menu, IDC_ALWAYS_ON_TOP, FALSE, &menu_info); + + // Now change the actual window's behavior. + AlwaysOnTopChanged(); + } else if ((notification_code == SC_KEYMENU) && (click.x == VK_SPACE)) { + // Run the system menu at the NonClientView's desired location. + RunSystemMenu(non_client_view_->GetSystemMenuPoint()); + } else { + // Use the default implementation for any other command. + DefWindowProc(GetNativeView(), WM_SYSCOMMAND, notification_code, + MAKELPARAM(click.y, click.x)); + } +} + +void Window::OnWindowPosChanging(WINDOWPOS* window_pos) { + if (force_hidden_) { + // Prevent the window from being made visible if we've been asked to do so. + // See comment in header as to why we might want this. + window_pos->flags &= ~SWP_SHOWWINDOW; + } + WidgetWin::OnWindowPosChanging(window_pos); +} + +//////////////////////////////////////////////////////////////////////////////// +// Window, private: + +void Window::BecomeModal() { + // We implement modality by crawling up the hierarchy of windows starting + // at the owner, disabling all of them so that they don't receive input + // messages. + DCHECK(owning_hwnd_) << "Can't create a modal dialog without an owner"; + HWND start = owning_hwnd_; + while (start != NULL) { + ::EnableWindow(start, FALSE); + start = ::GetParent(start); + } +} + +void Window::SetInitialFocus() { + if (!focus_on_creation_) + return; + + View* v = window_delegate_->GetInitiallyFocusedView(); + if (v) { + v->RequestFocus(); + } else { + // The window does not get keyboard messages unless we focus it, not sure + // why. + SetFocus(GetNativeView()); + } +} + +void Window::SetInitialBounds(const gfx::Rect& create_bounds) { + // First we obtain the window's saved show-style and store it. We need to do + // this here, rather than in Show() because by the time Show() is called, + // the window's size will have been reset (below) and the saved maximized + // state will have been lost. Sadly there's no way to tell on Windows when + // a window is restored from maximized state, so we can't more accurately + // track maximized state independently of sizing information. + window_delegate_->GetSavedMaximizedState(&saved_maximized_state_); + + // Restore the window's placement from the controller. + gfx::Rect saved_bounds(create_bounds.ToRECT()); + if (window_delegate_->GetSavedWindowBounds(&saved_bounds)) { + // Make sure the bounds are at least the minimum size. + if (saved_bounds.width() < minimum_size_.cx) { + saved_bounds.SetRect(saved_bounds.x(), saved_bounds.y(), + saved_bounds.right() + minimum_size_.cx - + saved_bounds.width(), + saved_bounds.bottom()); + } + + if (saved_bounds.height() < minimum_size_.cy) { + saved_bounds.SetRect(saved_bounds.x(), saved_bounds.y(), + saved_bounds.right(), + saved_bounds.bottom() + minimum_size_.cy - + saved_bounds.height()); + } + + // "Show state" (maximized, minimized, etc) is handled by Show(). + // Don't use SetBounds here. SetBounds constrains to the size of the + // monitor, but we don't want that when creating a new window as the result + // of dragging out a tab to create a new window. + SetWindowPos(NULL, saved_bounds.x(), saved_bounds.y(), + saved_bounds.width(), saved_bounds.height(), 0); + } else { + if (create_bounds.IsEmpty()) { + // No initial bounds supplied, so size the window to its content and + // center over its parent. + SizeWindowToDefault(); + } else { + // Use the supplied initial bounds. + SetBounds(create_bounds); + } + } +} + +void Window::InitAlwaysOnTopState() { + is_always_on_top_ = false; + if (window_delegate_->GetSavedAlwaysOnTopState(&is_always_on_top_) && + is_always_on_top_ != window_delegate_->IsAlwaysOnTop()) { + AlwaysOnTopChanged(); + } + + if (window_delegate_->HasAlwaysOnTopMenu()) + AddAlwaysOnTopSystemMenuItem(); +} + +void Window::AddAlwaysOnTopSystemMenuItem() { + // The Win32 API requires that we own the text. + always_on_top_menu_text_ = l10n_util::GetString(IDS_ALWAYS_ON_TOP); + + // Let's insert a menu to the window. + HMENU system_menu = ::GetSystemMenu(GetNativeView(), FALSE); + int index = ::GetMenuItemCount(system_menu) - 1; + if (index < 0) { + // Paranoia check. + NOTREACHED(); + index = 0; + } + // First we add the separator. + MENUITEMINFO menu_info; + memset(&menu_info, 0, sizeof(MENUITEMINFO)); + menu_info.cbSize = sizeof(MENUITEMINFO); + menu_info.fMask = MIIM_FTYPE; + menu_info.fType = MFT_SEPARATOR; + ::InsertMenuItem(system_menu, index, TRUE, &menu_info); + + // Then the actual menu. + menu_info.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING | MIIM_STATE; + menu_info.fType = MFT_STRING; + menu_info.fState = MFS_ENABLED; + if (is_always_on_top_) + menu_info.fState |= MFS_CHECKED; + menu_info.wID = IDC_ALWAYS_ON_TOP; + menu_info.dwTypeData = const_cast<wchar_t*>(always_on_top_menu_text_.c_str()); + ::InsertMenuItem(system_menu, index, TRUE, &menu_info); +} + +void Window::RestoreEnabledIfNecessary() { + if (is_modal_ && !restored_enabled_) { + restored_enabled_ = true; + // If we were run modally, we need to undo the disabled-ness we inflicted on + // the owner's parent hierarchy. + HWND start = owning_hwnd_; + while (start != NULL) { + ::EnableWindow(start, TRUE); + start = ::GetParent(start); + } + } +} + +void Window::AlwaysOnTopChanged() { + ::SetWindowPos(GetNativeView(), + is_always_on_top_ ? HWND_TOPMOST : HWND_NOTOPMOST, + 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); +} + +DWORD Window::CalculateWindowStyle() { + DWORD window_styles = + WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_SYSMENU | WS_CAPTION; + bool can_resize = window_delegate_->CanResize(); + bool can_maximize = window_delegate_->CanMaximize(); + if (can_maximize) { + window_styles |= WS_OVERLAPPEDWINDOW; + } else if (can_resize) { + window_styles |= WS_OVERLAPPED | WS_THICKFRAME; + } + if (window_delegate_->AsDialogDelegate()) { + window_styles |= DS_MODALFRAME; + // NOTE: Turning this off means we lose the close button, which is bad. + // Turning it on though means the user can maximize or size the window + // from the system menu, which is worse. We may need to provide our own + // menu to get the close button to appear properly. + // window_styles &= ~WS_SYSMENU; + } + return window_styles; +} + +DWORD Window::CalculateWindowExStyle() { + DWORD window_ex_styles = 0; + if (window_delegate_->AsDialogDelegate()) + window_ex_styles |= WS_EX_DLGMODALFRAME; + if (window_delegate_->IsAlwaysOnTop()) + window_ex_styles |= WS_EX_TOPMOST; + return window_ex_styles; +} + +void Window::SaveWindowPosition() { + // The window delegate does the actual saving for us. It seems like (judging + // by go/crash) that in some circumstances we can end up here after + // WM_DESTROY, at which point the window delegate is likely gone. So just + // bail. + if (!window_delegate_) + return; + + WINDOWPLACEMENT win_placement = { 0 }; + win_placement.length = sizeof(WINDOWPLACEMENT); + + BOOL r = GetWindowPlacement(GetNativeView(), &win_placement); + DCHECK(r); + + bool maximized = (win_placement.showCmd == SW_SHOWMAXIMIZED); + CRect window_bounds(win_placement.rcNormalPosition); + window_delegate_->SaveWindowPlacement( + gfx::Rect(win_placement.rcNormalPosition), maximized, is_always_on_top_); +} + +void Window::LockUpdates() { + lock_updates_ = true; + saved_window_style_ = GetWindowLong(GetNativeView(), GWL_STYLE); + SetWindowLong(GetNativeView(), GWL_STYLE, saved_window_style_ & ~WS_VISIBLE); +} + +void Window::UnlockUpdates() { + SetWindowLong(GetNativeView(), GWL_STYLE, saved_window_style_); + lock_updates_ = false; +} + +void Window::ResetWindowRegion(bool force) { + // A native frame uses the native window region, and we don't want to mess + // with it. + if (non_client_view_->UseNativeFrame()) { + if (force) + SetWindowRgn(NULL, TRUE); + return; + } + + // Changing the window region is going to force a paint. Only change the + // window region if the region really differs. + HRGN current_rgn = CreateRectRgn(0, 0, 0, 0); + int current_rgn_result = GetWindowRgn(GetNativeView(), current_rgn); + + CRect window_rect; + GetWindowRect(&window_rect); + HRGN new_region; + gfx::Path window_mask; + non_client_view_->GetWindowMask( + gfx::Size(window_rect.Width(), window_rect.Height()), &window_mask); + new_region = window_mask.CreateHRGN(); + + if (current_rgn_result == ERROR || !EqualRgn(current_rgn, new_region)) { + // SetWindowRgn takes ownership of the HRGN created by CreateHRGN. + SetWindowRgn(new_region, TRUE); + } else { + DeleteObject(new_region); + } + + DeleteObject(current_rgn); +} + +void Window::ProcessNCMousePress(const CPoint& point, int flags) { + CPoint temp = point; + MapWindowPoints(HWND_DESKTOP, GetNativeView(), &temp, 1); + UINT message_flags = 0; + if ((GetKeyState(VK_CONTROL) & 0x80) == 0x80) + message_flags |= MK_CONTROL; + if ((GetKeyState(VK_SHIFT) & 0x80) == 0x80) + message_flags |= MK_SHIFT; + message_flags |= flags; + ProcessMousePressed(temp, message_flags, false); +} + +LRESULT Window::CallDefaultNCActivateHandler(BOOL active) { + // The DefWindowProc handling for WM_NCACTIVATE renders the classic-look + // window title bar directly, so we need to use a redraw lock here to prevent + // it from doing so. + ScopedRedrawLock lock(this); + return DefWindowProc(GetNativeView(), WM_NCACTIVATE, active, 0); +} + +void Window::InitClass() { + static bool initialized = false; + if (!initialized) { + resize_cursors_[RC_NORMAL] = LoadCursor(NULL, IDC_ARROW); + resize_cursors_[RC_VERTICAL] = LoadCursor(NULL, IDC_SIZENS); + resize_cursors_[RC_HORIZONTAL] = LoadCursor(NULL, IDC_SIZEWE); + resize_cursors_[RC_NESW] = LoadCursor(NULL, IDC_SIZENESW); + resize_cursors_[RC_NWSE] = LoadCursor(NULL, IDC_SIZENWSE); + initialized = true; + } +} + +} // namespace views |