diff options
author | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-08 00:34:05 +0000 |
---|---|---|
committer | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-08 00:34:05 +0000 |
commit | 2362e4fe2905ab75d3230ebc3e307ae53e2b8362 (patch) | |
tree | e6d88357a2021811e0e354f618247217be8bb3da /views/window | |
parent | db23ac3e713dc17509b2b15d3ee634968da45715 (diff) | |
download | chromium_src-2362e4fe2905ab75d3230ebc3e307ae53e2b8362.zip chromium_src-2362e4fe2905ab75d3230ebc3e307ae53e2b8362.tar.gz chromium_src-2362e4fe2905ab75d3230ebc3e307ae53e2b8362.tar.bz2 |
Move src/chrome/views to src/views. RS=darin http://crbug.com/11387
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15604 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views/window')
-rw-r--r-- | views/window/client_view.cc | 61 | ||||
-rw-r--r-- | views/window/client_view.h | 84 | ||||
-rw-r--r-- | views/window/custom_frame_view.cc | 695 | ||||
-rw-r--r-- | views/window/custom_frame_view.h | 122 | ||||
-rw-r--r-- | views/window/dialog_client_view.cc | 440 | ||||
-rw-r--r-- | views/window/dialog_client_view.h | 123 | ||||
-rw-r--r-- | views/window/dialog_delegate.cc | 54 | ||||
-rw-r--r-- | views/window/dialog_delegate.h | 113 | ||||
-rw-r--r-- | views/window/native_frame_view.cc | 60 | ||||
-rw-r--r-- | views/window/native_frame_view.h | 39 | ||||
-rw-r--r-- | views/window/non_client_view.cc | 255 | ||||
-rw-r--r-- | views/window/non_client_view.h | 227 | ||||
-rw-r--r-- | views/window/window.h | 135 | ||||
-rw-r--r-- | views/window/window_delegate.cc | 96 | ||||
-rw-r--r-- | views/window/window_delegate.h | 162 | ||||
-rw-r--r-- | views/window/window_resources.h | 30 | ||||
-rw-r--r-- | views/window/window_win.cc | 1446 | ||||
-rw-r--r-- | views/window/window_win.h | 307 |
18 files changed, 4449 insertions, 0 deletions
diff --git a/views/window/client_view.cc b/views/window/client_view.cc new file mode 100644 index 0000000..5b5afb1 --- /dev/null +++ b/views/window/client_view.cc @@ -0,0 +1,61 @@ +// 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 "base/logging.h" +#include "views/window/client_view.h" +#include "views/window/window.h" +#include "views/window/window_delegate.h" + +namespace views { + +/////////////////////////////////////////////////////////////////////////////// +// ClientView, public: + +ClientView::ClientView(Window* window, View* contents_view) + : window_(window), + contents_view_(contents_view) { +} + +int ClientView::NonClientHitTest(const gfx::Point& point) { + return bounds().Contains(point) ? HTCLIENT : HTNOWHERE; +} + +void ClientView::WindowClosing() { + window_->GetDelegate()->WindowClosing(); +} + +/////////////////////////////////////////////////////////////////////////////// +// ClientView, View overrides: + +gfx::Size ClientView::GetPreferredSize() { + // |contents_view_| is allowed to be NULL up until the point where this view + // is attached to a Container. + if (contents_view_) + return contents_view_->GetPreferredSize(); + return gfx::Size(); +} + +void ClientView::Layout() { + // |contents_view_| is allowed to be NULL up until the point where this view + // is attached to a Container. + if (contents_view_) + contents_view_->SetBounds(0, 0, width(), height()); +} + +void ClientView::ViewHierarchyChanged(bool is_add, View* parent, View* child) { + if (is_add && child == this) { + DCHECK(GetWidget()); + DCHECK(contents_view_); // |contents_view_| must be valid now! + AddChildView(contents_view_); + } +} + +void ClientView::DidChangeBounds(const gfx::Rect& previous, + const gfx::Rect& current) { + // Overridden to do nothing. The NonClientView manually calls Layout on the + // ClientView when it is itself laid out, see comment in + // NonClientView::Layout. +} + +} // namespace views diff --git a/views/window/client_view.h b/views/window/client_view.h new file mode 100644 index 0000000..b56fcfd --- /dev/null +++ b/views/window/client_view.h @@ -0,0 +1,84 @@ +// 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. + +#ifndef VIEWS_WINDOW_CLIENT_VIEW_H_ +#define VIEWS_WINDOW_CLIENT_VIEW_H_ + +#include "views/view.h" + +namespace views { + +class DialogClientView; +class Window; + +/////////////////////////////////////////////////////////////////////////////// +// ClientView +// +// A ClientView is a View subclass that is used to occupy the "client area" +// of a window. It provides basic information to the window that contains it +// such as non-client hit testing information, sizing etc. Sub-classes of +// ClientView are used to create more elaborate contents, e.g. +// "DialogClientView". +class ClientView : public View { + public: + // Constructs a ClientView object for the specified window with the specified + // contents. Since this object is created during the process of creating + // |window|, |contents_view| must be valid if you want the initial size of + // the window to be based on |contents_view|'s preferred size. + ClientView(Window* window, View* contents_view); + virtual ~ClientView() {} + + // Manual RTTI ftw. + virtual DialogClientView* AsDialogClientView() { return NULL; } + + // Returns true to signal that the Window can be closed. Specialized + // ClientView subclasses can override this default behavior to allow the + // close to be blocked until the user corrects mistakes, accepts a warning + // dialog, etc. + virtual bool CanClose() const { return true; } + + // Notification that the window is closing. The default implementation + // forwards the notification to the delegate. + virtual void WindowClosing(); + + // Tests to see if the specified point (in view coordinates) is within the + // bounds of this view. If so, it returns HTCLIENT in this default + // implementation. If it is outside the bounds of this view, this must return + // HTNOWHERE to tell the caller to do further processing to determine where + // in the non-client area it is (if it is). + // Subclasses of ClientView can extend this logic by overriding this method + // to detect if regions within the client area count as parts of the "non- + // client" area. A good example of this is the size box at the bottom right + // corner of resizable dialog boxes. + virtual int NonClientHitTest(const gfx::Point& point); + + // Overridden from View: + virtual gfx::Size GetPreferredSize(); + virtual void Layout(); + + protected: + // Overridden from View: + virtual void ViewHierarchyChanged(bool is_add, View* parent, View* child); + virtual void DidChangeBounds(const gfx::Rect& previous, + const gfx::Rect& current); + + // Accessors for private data members. + Window* window() const { return window_; } + void set_window(Window* window) { window_ = window; } + View* contents_view() const { return contents_view_; } + void set_contents_view(View* contents_view) { + contents_view_ = contents_view; + } + + private: + // The Window that hosts this ClientView. + Window* window_; + + // The View that this ClientView contains. + View* contents_view_; +}; + +} // namespace views + +#endif // #ifndef VIEWS_WINDOW_CLIENT_VIEW_H_ diff --git a/views/window/custom_frame_view.cc b/views/window/custom_frame_view.cc new file mode 100644 index 0000000..cf8173f --- /dev/null +++ b/views/window/custom_frame_view.cc @@ -0,0 +1,695 @@ +// Copyright (c) 2009 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/window/custom_frame_view.h" + +#include "app/gfx/chrome_canvas.h" +#include "app/gfx/chrome_font.h" +#include "app/gfx/path.h" +#include "app/resource_bundle.h" +#include "base/win_util.h" +#include "chrome/common/win_util.h" +#include "grit/theme_resources.h" +#include "views/window/client_view.h" +#include "views/window/window_delegate.h" + +namespace views { + +// An enumeration of bitmap resources used by this window. +enum { + FRAME_PART_BITMAP_FIRST = 0, // Must be first. + + // Window Controls. + FRAME_CLOSE_BUTTON_ICON, + FRAME_CLOSE_BUTTON_ICON_H, + FRAME_CLOSE_BUTTON_ICON_P, + FRAME_CLOSE_BUTTON_ICON_SA, + FRAME_CLOSE_BUTTON_ICON_SA_H, + FRAME_CLOSE_BUTTON_ICON_SA_P, + FRAME_RESTORE_BUTTON_ICON, + FRAME_RESTORE_BUTTON_ICON_H, + FRAME_RESTORE_BUTTON_ICON_P, + FRAME_MAXIMIZE_BUTTON_ICON, + FRAME_MAXIMIZE_BUTTON_ICON_H, + FRAME_MAXIMIZE_BUTTON_ICON_P, + FRAME_MINIMIZE_BUTTON_ICON, + FRAME_MINIMIZE_BUTTON_ICON_H, + FRAME_MINIMIZE_BUTTON_ICON_P, + + // Window Frame Border. + FRAME_BOTTOM_EDGE, + FRAME_BOTTOM_LEFT_CORNER, + FRAME_BOTTOM_RIGHT_CORNER, + FRAME_LEFT_EDGE, + FRAME_RIGHT_EDGE, + FRAME_TOP_EDGE, + FRAME_TOP_LEFT_CORNER, + FRAME_TOP_RIGHT_CORNER, + + // Client Edge Border. + FRAME_CLIENT_EDGE_TOP_LEFT, + FRAME_CLIENT_EDGE_TOP, + FRAME_CLIENT_EDGE_TOP_RIGHT, + FRAME_CLIENT_EDGE_RIGHT, + FRAME_CLIENT_EDGE_BOTTOM_RIGHT, + FRAME_CLIENT_EDGE_BOTTOM, + FRAME_CLIENT_EDGE_BOTTOM_LEFT, + FRAME_CLIENT_EDGE_LEFT, + + FRAME_PART_BITMAP_COUNT // Must be last. +}; + +class ActiveWindowResources : public WindowResources { + public: + ActiveWindowResources() { + InitClass(); + } + virtual ~ActiveWindowResources() { + } + + // WindowResources implementation: + virtual SkBitmap* GetPartBitmap(FramePartBitmap part) const { + return standard_frame_bitmaps_[part]; + } + + private: + static void InitClass() { + static bool initialized = false; + if (!initialized) { + static const int kFramePartBitmapIds[] = { + 0, + IDR_CLOSE, IDR_CLOSE_H, IDR_CLOSE_P, + IDR_CLOSE_SA, IDR_CLOSE_SA_H, IDR_CLOSE_SA_P, + IDR_RESTORE, IDR_RESTORE_H, IDR_RESTORE_P, + IDR_MAXIMIZE, IDR_MAXIMIZE_H, IDR_MAXIMIZE_P, + IDR_MINIMIZE, IDR_MINIMIZE_H, IDR_MINIMIZE_P, + IDR_WINDOW_BOTTOM_CENTER, IDR_WINDOW_BOTTOM_LEFT_CORNER, + IDR_WINDOW_BOTTOM_RIGHT_CORNER, IDR_WINDOW_LEFT_SIDE, + IDR_WINDOW_RIGHT_SIDE, IDR_WINDOW_TOP_CENTER, + IDR_WINDOW_TOP_LEFT_CORNER, IDR_WINDOW_TOP_RIGHT_CORNER, + IDR_APP_TOP_LEFT, IDR_APP_TOP_CENTER, IDR_APP_TOP_RIGHT, + IDR_CONTENT_RIGHT_SIDE, IDR_CONTENT_BOTTOM_RIGHT_CORNER, + IDR_CONTENT_BOTTOM_CENTER, IDR_CONTENT_BOTTOM_LEFT_CORNER, + IDR_CONTENT_LEFT_SIDE, + 0 + }; + + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + for (int i = 0; i < FRAME_PART_BITMAP_COUNT; ++i) { + int id = kFramePartBitmapIds[i]; + if (id != 0) + standard_frame_bitmaps_[i] = rb.GetBitmapNamed(id); + } + initialized = true; + } + } + + static SkBitmap* standard_frame_bitmaps_[FRAME_PART_BITMAP_COUNT]; + + DISALLOW_EVIL_CONSTRUCTORS(ActiveWindowResources); +}; + +class InactiveWindowResources : public WindowResources { + public: + InactiveWindowResources() { + InitClass(); + } + virtual ~InactiveWindowResources() { + } + + // WindowResources implementation: + virtual SkBitmap* GetPartBitmap(FramePartBitmap part) const { + return standard_frame_bitmaps_[part]; + } + + private: + static void InitClass() { + static bool initialized = false; + if (!initialized) { + static const int kFramePartBitmapIds[] = { + 0, + IDR_CLOSE, IDR_CLOSE_H, IDR_CLOSE_P, + IDR_CLOSE_SA, IDR_CLOSE_SA_H, IDR_CLOSE_SA_P, + IDR_RESTORE, IDR_RESTORE_H, IDR_RESTORE_P, + IDR_MAXIMIZE, IDR_MAXIMIZE_H, IDR_MAXIMIZE_P, + IDR_MINIMIZE, IDR_MINIMIZE_H, IDR_MINIMIZE_P, + IDR_DEWINDOW_BOTTOM_CENTER, IDR_DEWINDOW_BOTTOM_LEFT_CORNER, + IDR_DEWINDOW_BOTTOM_RIGHT_CORNER, IDR_DEWINDOW_LEFT_SIDE, + IDR_DEWINDOW_RIGHT_SIDE, IDR_DEWINDOW_TOP_CENTER, + IDR_DEWINDOW_TOP_LEFT_CORNER, IDR_DEWINDOW_TOP_RIGHT_CORNER, + IDR_APP_TOP_LEFT, IDR_APP_TOP_CENTER, IDR_APP_TOP_RIGHT, + IDR_CONTENT_RIGHT_SIDE, IDR_CONTENT_BOTTOM_RIGHT_CORNER, + IDR_CONTENT_BOTTOM_CENTER, IDR_CONTENT_BOTTOM_LEFT_CORNER, + IDR_CONTENT_LEFT_SIDE, + 0 + }; + + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + for (int i = 0; i < FRAME_PART_BITMAP_COUNT; ++i) { + int id = kFramePartBitmapIds[i]; + if (id != 0) + standard_frame_bitmaps_[i] = rb.GetBitmapNamed(id); + } + initialized = true; + } + } + + static SkBitmap* standard_frame_bitmaps_[FRAME_PART_BITMAP_COUNT]; + + DISALLOW_EVIL_CONSTRUCTORS(InactiveWindowResources); +}; + +// static +SkBitmap* ActiveWindowResources::standard_frame_bitmaps_[]; +SkBitmap* InactiveWindowResources::standard_frame_bitmaps_[]; + +// static +WindowResources* CustomFrameView::active_resources_ = NULL; +WindowResources* CustomFrameView::inactive_resources_ = NULL; +ChromeFont* CustomFrameView::title_font_ = NULL; + +namespace { +// The frame border is only visible in restored mode and is hardcoded to 4 px on +// each side regardless of the system window border size. +const int kFrameBorderThickness = 4; +// Various edges of the frame border have a 1 px shadow along their edges; in a +// few cases we shift elements based on this amount for visual appeal. +const int kFrameShadowThickness = 1; +// While resize areas on Windows are normally the same size as the window +// borders, our top area is shrunk by 1 px to make it easier to move the window +// around with our thinner top grabbable strip. (Incidentally, our side and +// bottom resize areas don't match the frame border thickness either -- they +// span the whole nonclient area, so there's no "dead zone" for the mouse.) +const int kTopResizeAdjust = 1; +// In the window corners, the resize areas don't actually expand bigger, but the +// 16 px at the end of each edge triggers diagonal resizing. +const int kResizeAreaCornerSize = 16; +// The titlebar never shrinks to less than 18 px tall, plus the height of the +// frame border and any bottom edge. +const int kTitlebarMinimumHeight = 18; +// The icon is inset 2 px from the left frame border. +const int kIconLeftSpacing = 2; +// The icon takes up 16/25th of the available titlebar height. (This is +// expressed as two ints to avoid precision losses leading to off-by-one pixel +// errors.) +const int kIconHeightFractionNumerator = 16; +const int kIconHeightFractionDenominator = 25; +// The icon never shrinks below 16 px on a side. +const int kIconMinimumSize = 16; +// Because our frame border has a different "3D look" than Windows', with a less +// cluttered top edge, we need to shift the icon up by 1 px in restored mode so +// it looks more centered. +const int kIconRestoredAdjust = 1; +// There is a 4 px gap between the icon and the title text. +const int kIconTitleSpacing = 4; +// The title text starts 2 px below the bottom of the top frame border. +const int kTitleTopSpacing = 2; +// There is a 5 px gap between the title text and the caption buttons. +const int kTitleCaptionSpacing = 5; +// The caption buttons are always drawn 1 px down from the visible top of the +// window (the true top in restored mode, or the top of the screen in maximized +// mode). +const int kCaptionTopSpacing = 1; +} + +/////////////////////////////////////////////////////////////////////////////// +// CustomFrameView, public: + +CustomFrameView::CustomFrameView(Window* frame) + : NonClientFrameView(), + close_button_(new ImageButton(this)), + restore_button_(new ImageButton(this)), + maximize_button_(new ImageButton(this)), + minimize_button_(new ImageButton(this)), + system_menu_button_(new ImageButton(this)), + should_show_minmax_buttons_(false), + frame_(frame) { + InitClass(); + WindowResources* resources = active_resources_; + + // Close button images will be set in LayoutWindowControls(). + AddChildView(close_button_); + + restore_button_->SetImage(CustomButton::BS_NORMAL, + resources->GetPartBitmap(FRAME_RESTORE_BUTTON_ICON)); + restore_button_->SetImage(CustomButton::BS_HOT, + resources->GetPartBitmap(FRAME_RESTORE_BUTTON_ICON_H)); + restore_button_->SetImage(CustomButton::BS_PUSHED, + resources->GetPartBitmap(FRAME_RESTORE_BUTTON_ICON_P)); + AddChildView(restore_button_); + + maximize_button_->SetImage(CustomButton::BS_NORMAL, + resources->GetPartBitmap(FRAME_MAXIMIZE_BUTTON_ICON)); + maximize_button_->SetImage(CustomButton::BS_HOT, + resources->GetPartBitmap(FRAME_MAXIMIZE_BUTTON_ICON_H)); + maximize_button_->SetImage(CustomButton::BS_PUSHED, + resources->GetPartBitmap(FRAME_MAXIMIZE_BUTTON_ICON_P)); + AddChildView(maximize_button_); + + minimize_button_->SetImage(CustomButton::BS_NORMAL, + resources->GetPartBitmap(FRAME_MINIMIZE_BUTTON_ICON)); + minimize_button_->SetImage(CustomButton::BS_HOT, + resources->GetPartBitmap(FRAME_MINIMIZE_BUTTON_ICON_H)); + minimize_button_->SetImage(CustomButton::BS_PUSHED, + resources->GetPartBitmap(FRAME_MINIMIZE_BUTTON_ICON_P)); + AddChildView(minimize_button_); + + should_show_minmax_buttons_ = frame_->GetDelegate()->CanMaximize(); + + AddChildView(system_menu_button_); +} + +CustomFrameView::~CustomFrameView() { +} + +/////////////////////////////////////////////////////////////////////////////// +// CustomFrameView, NonClientFrameView implementation: + +gfx::Rect CustomFrameView::GetBoundsForClientView() const { + return client_view_bounds_; +} + +gfx::Rect CustomFrameView::GetWindowBoundsForClientBounds( + const gfx::Rect& client_bounds) const { + int top_height = NonClientTopBorderHeight(); + int border_thickness = NonClientBorderThickness(); + return gfx::Rect(std::max(0, client_bounds.x() - border_thickness), + std::max(0, client_bounds.y() - top_height), + client_bounds.width() + (2 * border_thickness), + client_bounds.height() + top_height + border_thickness); +} + +gfx::Point CustomFrameView::GetSystemMenuPoint() const { + gfx::Point system_menu_point( + MirroredXCoordinateInsideView(FrameBorderThickness()), + NonClientTopBorderHeight() - BottomEdgeThicknessWithinNonClientHeight()); + ConvertPointToScreen(this, &system_menu_point); + return system_menu_point; +} + +int CustomFrameView::NonClientHitTest(const gfx::Point& point) { + // Then see if the point is within any of the window controls. + if (close_button_->GetBounds(APPLY_MIRRORING_TRANSFORMATION).Contains(point)) + return HTCLOSE; + if (restore_button_->GetBounds(APPLY_MIRRORING_TRANSFORMATION).Contains( + point)) + return HTMAXBUTTON; + if (maximize_button_->GetBounds(APPLY_MIRRORING_TRANSFORMATION).Contains( + point)) + return HTMAXBUTTON; + if (minimize_button_->GetBounds(APPLY_MIRRORING_TRANSFORMATION).Contains( + point)) + return HTMINBUTTON; + if (system_menu_button_->GetBounds(APPLY_MIRRORING_TRANSFORMATION).Contains( + point)) + return HTSYSMENU; + + int window_component = GetHTComponentForFrame(point, FrameBorderThickness(), + NonClientBorderThickness(), kResizeAreaCornerSize, kResizeAreaCornerSize, + frame_->GetDelegate()->CanResize()); + // Fall back to the caption if no other component matches. + return (window_component == HTNOWHERE) ? HTCAPTION : window_component; +} + +void CustomFrameView::GetWindowMask(const gfx::Size& size, + gfx::Path* window_mask) { + DCHECK(window_mask); + + if (frame_->IsMaximized()) + return; + + // Redefine the window visible region for the new size. + window_mask->moveTo(0, 3); + window_mask->lineTo(1, 2); + window_mask->lineTo(1, 1); + window_mask->lineTo(2, 1); + window_mask->lineTo(3, 0); + + window_mask->lineTo(SkIntToScalar(size.width() - 3), 0); + window_mask->lineTo(SkIntToScalar(size.width() - 2), 1); + window_mask->lineTo(SkIntToScalar(size.width() - 1), 1); + window_mask->lineTo(SkIntToScalar(size.width() - 1), 2); + window_mask->lineTo(SkIntToScalar(size.width()), 3); + + window_mask->lineTo(SkIntToScalar(size.width()), + SkIntToScalar(size.height())); + window_mask->lineTo(0, SkIntToScalar(size.height())); + window_mask->close(); +} + +void CustomFrameView::EnableClose(bool enable) { + close_button_->SetEnabled(enable); +} + +void CustomFrameView::ResetWindowControls() { + restore_button_->SetState(CustomButton::BS_NORMAL); + minimize_button_->SetState(CustomButton::BS_NORMAL); + maximize_button_->SetState(CustomButton::BS_NORMAL); + // The close button isn't affected by this constraint. +} + +/////////////////////////////////////////////////////////////////////////////// +// CustomFrameView, View overrides: + +void CustomFrameView::Paint(ChromeCanvas* canvas) { + if (frame_->IsMaximized()) + PaintMaximizedFrameBorder(canvas); + else + PaintRestoredFrameBorder(canvas); + PaintTitleBar(canvas); + if (!frame_->IsMaximized()) + PaintRestoredClientEdge(canvas); +} + +void CustomFrameView::Layout() { + LayoutWindowControls(); + LayoutTitleBar(); + LayoutClientView(); +} + +gfx::Size CustomFrameView::GetPreferredSize() { + gfx::Size pref = frame_->GetClientView()->GetPreferredSize(); + gfx::Rect bounds(0, 0, pref.width(), pref.height()); + return frame_->GetNonClientView()->GetWindowBoundsForClientBounds( + bounds).size(); +} + +/////////////////////////////////////////////////////////////////////////////// +// CustomFrameView, ButtonListener implementation: + +void CustomFrameView::ButtonPressed(Button* sender) { + if (sender == close_button_) + frame_->Close(); + else if (sender == minimize_button_) + frame_->Minimize(); + else if (sender == maximize_button_) + frame_->Maximize(); + else if (sender == restore_button_) + frame_->Restore(); +} + +/////////////////////////////////////////////////////////////////////////////// +// CustomFrameView, private: + +int CustomFrameView::FrameBorderThickness() const { + return frame_->IsMaximized() ? 0 : kFrameBorderThickness; +} + +int CustomFrameView::NonClientBorderThickness() const { + // In maximized mode, we don't show a client edge. + return FrameBorderThickness() + + (frame_->IsMaximized() ? 0 : kClientEdgeThickness); +} + +int CustomFrameView::NonClientTopBorderHeight() const { + int title_top_spacing, title_thickness; + return TitleCoordinates(&title_top_spacing, &title_thickness); +} + +int CustomFrameView::BottomEdgeThicknessWithinNonClientHeight() const { + return kFrameShadowThickness + + (frame_->IsMaximized() ? 0 : kClientEdgeThickness); +} + +int CustomFrameView::TitleCoordinates(int* title_top_spacing, + int* title_thickness) const { + int frame_thickness = FrameBorderThickness(); + int min_titlebar_height = kTitlebarMinimumHeight + frame_thickness; + *title_top_spacing = frame_thickness + kTitleTopSpacing; + // The bottom spacing should be the same apparent height as the top spacing. + // Because the actual top spacing height varies based on the system border + // thickness, we calculate this based on the restored top spacing and then + // adjust for maximized mode. We also don't include the frame shadow here, + // since while it's part of the bottom spacing it will be added in at the end. + int title_bottom_spacing = + kFrameBorderThickness + kTitleTopSpacing - kFrameShadowThickness; + if (frame_->IsMaximized()) { + // When we maximize, the top border appears to be chopped off; shift the + // title down to stay centered within the remaining space. + int title_adjust = (kFrameBorderThickness / 2); + *title_top_spacing += title_adjust; + title_bottom_spacing -= title_adjust; + } + *title_thickness = std::max(title_font_->height(), + min_titlebar_height - *title_top_spacing - title_bottom_spacing); + return *title_top_spacing + *title_thickness + title_bottom_spacing + + BottomEdgeThicknessWithinNonClientHeight(); +} + +void CustomFrameView::PaintRestoredFrameBorder(ChromeCanvas* canvas) { + SkBitmap* top_left_corner = resources()->GetPartBitmap(FRAME_TOP_LEFT_CORNER); + SkBitmap* top_right_corner = + resources()->GetPartBitmap(FRAME_TOP_RIGHT_CORNER); + SkBitmap* top_edge = resources()->GetPartBitmap(FRAME_TOP_EDGE); + SkBitmap* right_edge = resources()->GetPartBitmap(FRAME_RIGHT_EDGE); + SkBitmap* left_edge = resources()->GetPartBitmap(FRAME_LEFT_EDGE); + SkBitmap* bottom_left_corner = + resources()->GetPartBitmap(FRAME_BOTTOM_LEFT_CORNER); + SkBitmap* bottom_right_corner = + resources()->GetPartBitmap(FRAME_BOTTOM_RIGHT_CORNER); + SkBitmap* bottom_edge = resources()->GetPartBitmap(FRAME_BOTTOM_EDGE); + + // Top. + canvas->DrawBitmapInt(*top_left_corner, 0, 0); + canvas->TileImageInt(*top_edge, top_left_corner->width(), 0, + width() - top_right_corner->width(), top_edge->height()); + canvas->DrawBitmapInt(*top_right_corner, + width() - top_right_corner->width(), 0); + + // Right. + canvas->TileImageInt(*right_edge, width() - right_edge->width(), + top_right_corner->height(), right_edge->width(), + height() - top_right_corner->height() - + bottom_right_corner->height()); + + // Bottom. + canvas->DrawBitmapInt(*bottom_right_corner, + width() - bottom_right_corner->width(), + height() - bottom_right_corner->height()); + canvas->TileImageInt(*bottom_edge, bottom_left_corner->width(), + height() - bottom_edge->height(), + width() - bottom_left_corner->width() - + bottom_right_corner->width(), + bottom_edge->height()); + canvas->DrawBitmapInt(*bottom_left_corner, 0, + height() - bottom_left_corner->height()); + + // Left. + canvas->TileImageInt(*left_edge, 0, top_left_corner->height(), + left_edge->width(), + height() - top_left_corner->height() - bottom_left_corner->height()); +} + +void CustomFrameView::PaintMaximizedFrameBorder( + ChromeCanvas* canvas) { + SkBitmap* top_edge = resources()->GetPartBitmap(FRAME_TOP_EDGE); + canvas->TileImageInt(*top_edge, 0, FrameBorderThickness(), width(), + top_edge->height()); + + // The bottom of the titlebar actually comes from the top of the Client Edge + // graphic, with the actual client edge clipped off the bottom. + SkBitmap* titlebar_bottom = resources()->GetPartBitmap(FRAME_CLIENT_EDGE_TOP); + int edge_height = titlebar_bottom->height() - kClientEdgeThickness; + canvas->TileImageInt(*titlebar_bottom, 0, + frame_->GetClientView()->y() - edge_height, width(), edge_height); +} + +void CustomFrameView::PaintTitleBar(ChromeCanvas* canvas) { + WindowDelegate* d = frame_->GetDelegate(); + + // It seems like in some conditions we can be asked to paint after the window + // that contains us is WM_DESTROYed. At this point, our delegate is NULL. The + // correct long term fix may be to shut down the RootView in WM_DESTROY. + if (!d) + return; + + canvas->DrawStringInt(d->GetWindowTitle(), *title_font_, SK_ColorWHITE, + MirroredLeftPointForRect(title_bounds_), title_bounds_.y(), + title_bounds_.width(), title_bounds_.height()); +} + +void CustomFrameView::PaintRestoredClientEdge(ChromeCanvas* canvas) { + gfx::Rect client_area_bounds = frame_->GetClientView()->bounds(); + int client_area_top = client_area_bounds.y(); + + SkBitmap* top_left = resources()->GetPartBitmap(FRAME_CLIENT_EDGE_TOP_LEFT); + SkBitmap* top = resources()->GetPartBitmap(FRAME_CLIENT_EDGE_TOP); + SkBitmap* top_right = resources()->GetPartBitmap(FRAME_CLIENT_EDGE_TOP_RIGHT); + SkBitmap* right = resources()->GetPartBitmap(FRAME_CLIENT_EDGE_RIGHT); + SkBitmap* bottom_right = + resources()->GetPartBitmap(FRAME_CLIENT_EDGE_BOTTOM_RIGHT); + SkBitmap* bottom = resources()->GetPartBitmap(FRAME_CLIENT_EDGE_BOTTOM); + SkBitmap* bottom_left = + resources()->GetPartBitmap(FRAME_CLIENT_EDGE_BOTTOM_LEFT); + SkBitmap* left = resources()->GetPartBitmap(FRAME_CLIENT_EDGE_LEFT); + + // Top. + // This next calculation is necessary because the top center bitmap is shorter + // than the top left and right bitmaps. We need their top edges to line up, + // and we need the left and right edges to start below the corners' bottoms. + int top_edge_y = client_area_top - top->height(); + client_area_top = top_edge_y + top_left->height(); + canvas->DrawBitmapInt(*top_left, client_area_bounds.x() - top_left->width(), + top_edge_y); + canvas->TileImageInt(*top, client_area_bounds.x(), top_edge_y, + client_area_bounds.width(), top->height()); + canvas->DrawBitmapInt(*top_right, client_area_bounds.right(), top_edge_y); + + // Right. + int client_area_bottom = + std::max(client_area_top, client_area_bounds.bottom()); + int client_area_height = client_area_bottom - client_area_top; + canvas->TileImageInt(*right, client_area_bounds.right(), client_area_top, + right->width(), client_area_height); + + // Bottom. + canvas->DrawBitmapInt(*bottom_right, client_area_bounds.right(), + client_area_bottom); + canvas->TileImageInt(*bottom, client_area_bounds.x(), client_area_bottom, + client_area_bounds.width(), bottom_right->height()); + canvas->DrawBitmapInt(*bottom_left, + client_area_bounds.x() - bottom_left->width(), client_area_bottom); + + // Left. + canvas->TileImageInt(*left, client_area_bounds.x() - left->width(), + client_area_top, left->width(), client_area_height); +} + +void CustomFrameView::LayoutWindowControls() { + close_button_->SetImageAlignment(ImageButton::ALIGN_LEFT, + ImageButton::ALIGN_BOTTOM); + // Maximized buttons start at window top so that even if their images aren't + // drawn flush with the screen edge, they still obey Fitts' Law. + bool is_maximized = frame_->IsMaximized(); + int frame_thickness = FrameBorderThickness(); + int caption_y = is_maximized ? frame_thickness : kCaptionTopSpacing; + int top_extra_height = is_maximized ? kCaptionTopSpacing : 0; + // There should always be the same number of non-shadow pixels visible to the + // side of the caption buttons. In maximized mode we extend the rightmost + // button to the screen corner to obey Fitts' Law. + int right_extra_width = is_maximized ? + (kFrameBorderThickness - kFrameShadowThickness) : 0; + gfx::Size close_button_size = close_button_->GetPreferredSize(); + close_button_->SetBounds(width() - close_button_size.width() - + right_extra_width - frame_thickness, caption_y, + close_button_size.width() + right_extra_width, + close_button_size.height() + top_extra_height); + + // When the window is restored, we show a maximized button; otherwise, we show + // a restore button. + bool is_restored = !is_maximized && !frame_->IsMinimized(); + views::ImageButton* invisible_button = is_restored ? + restore_button_ : maximize_button_; + invisible_button->SetVisible(false); + + views::ImageButton* visible_button = is_restored ? + maximize_button_ : restore_button_; + FramePartBitmap normal_part, hot_part, pushed_part; + if (should_show_minmax_buttons_) { + visible_button->SetVisible(true); + visible_button->SetImageAlignment(ImageButton::ALIGN_LEFT, + ImageButton::ALIGN_BOTTOM); + gfx::Size visible_button_size = visible_button->GetPreferredSize(); + visible_button->SetBounds(close_button_->x() - visible_button_size.width(), + caption_y, visible_button_size.width(), + visible_button_size.height() + top_extra_height); + + minimize_button_->SetVisible(true); + minimize_button_->SetImageAlignment(ImageButton::ALIGN_LEFT, + ImageButton::ALIGN_BOTTOM); + gfx::Size minimize_button_size = minimize_button_->GetPreferredSize(); + minimize_button_->SetBounds( + visible_button->x() - minimize_button_size.width(), caption_y, + minimize_button_size.width(), + minimize_button_size.height() + top_extra_height); + + normal_part = FRAME_CLOSE_BUTTON_ICON; + hot_part = FRAME_CLOSE_BUTTON_ICON_H; + pushed_part = FRAME_CLOSE_BUTTON_ICON_P; + } else { + visible_button->SetVisible(false); + minimize_button_->SetVisible(false); + + normal_part = FRAME_CLOSE_BUTTON_ICON_SA; + hot_part = FRAME_CLOSE_BUTTON_ICON_SA_H; + pushed_part = FRAME_CLOSE_BUTTON_ICON_SA_P; + } + + close_button_->SetImage(CustomButton::BS_NORMAL, + active_resources_->GetPartBitmap(normal_part)); + close_button_->SetImage(CustomButton::BS_HOT, + active_resources_->GetPartBitmap(hot_part)); + close_button_->SetImage(CustomButton::BS_PUSHED, + active_resources_->GetPartBitmap(pushed_part)); +} + +void CustomFrameView::LayoutTitleBar() { + // Always lay out the icon, even when it's not present, so we can lay out the + // window title based on its position. + int frame_thickness = FrameBorderThickness(); + int icon_x = frame_thickness + kIconLeftSpacing; + + // The usable height of the titlebar area is the total height minus the top + // resize border and any edge area we draw at its bottom. + int title_top_spacing, title_thickness; + int top_height = TitleCoordinates(&title_top_spacing, &title_thickness); + int available_height = top_height - frame_thickness - + BottomEdgeThicknessWithinNonClientHeight(); + + // The icon takes up a constant fraction of the available height, down to a + // minimum size, and is always an even number of pixels on a side (presumably + // to make scaled icons look better). It's centered within the usable height. + int icon_size = std::max((available_height * kIconHeightFractionNumerator / + kIconHeightFractionDenominator) / 2 * 2, kIconMinimumSize); + int icon_y = ((available_height - icon_size) / 2) + frame_thickness; + + // Hack: Our frame border has a different "3D look" than Windows'. Theirs has + // a more complex gradient on the top that they push their icon/title below; + // then the maximized window cuts this off and the icon/title are centered in + // the remaining space. Because the apparent shape of our border is simpler, + // using the same positioning makes things look slightly uncentered with + // restored windows, so we come up to compensate. + if (!frame_->IsMaximized()) + icon_y -= kIconRestoredAdjust; + + views::WindowDelegate* d = frame_->GetDelegate(); + if (!d->ShouldShowWindowIcon()) + icon_size = 0; + system_menu_button_->SetBounds(icon_x, icon_y, icon_size, icon_size); + + // Size the title. + int icon_right = icon_x + icon_size; + int title_x = + icon_right + (d->ShouldShowWindowIcon() ? kIconTitleSpacing : 0); + int title_right = (should_show_minmax_buttons_ ? + minimize_button_->x() : close_button_->x()) - kTitleCaptionSpacing; + title_bounds_.SetRect(title_x, + title_top_spacing + ((title_thickness - title_font_->height()) / 2), + std::max(0, title_right - title_x), title_font_->height()); +} + +void CustomFrameView::LayoutClientView() { + int top_height = NonClientTopBorderHeight(); + int border_thickness = NonClientBorderThickness(); + client_view_bounds_.SetRect( + border_thickness, + top_height, + std::max(0, width() - (2 * border_thickness)), + std::max(0, height() - top_height - border_thickness)); +} + +// static +void CustomFrameView::InitClass() { + static bool initialized = false; + if (!initialized) { + active_resources_ = new ActiveWindowResources; + inactive_resources_ = new InactiveWindowResources; + + title_font_ = new ChromeFont(win_util::GetWindowTitleFont()); + + initialized = true; + } +} + +} // namespace views diff --git a/views/window/custom_frame_view.h b/views/window/custom_frame_view.h new file mode 100644 index 0000000..f071692 --- /dev/null +++ b/views/window/custom_frame_view.h @@ -0,0 +1,122 @@ +// Copyright (c) 2009 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. + +#ifndef VIEWS_WINDOW_CUSTOM_FRAME_VIEW_H_ +#define VIEWS_WINDOW_CUSTOM_FRAME_VIEW_H_ + +#include "views/controls/button/image_button.h" +#include "views/window/non_client_view.h" +#include "views/window/window.h" +#include "views/window/window_resources.h" + +namespace gfx{ +class Size; +class Path; +class Point; +} +class ChromeCanvas; +class ChromeFont; + +namespace views { + +/////////////////////////////////////////////////////////////////////////////// +// +// CustomFrameView +// +// A ChromeView that provides the non client frame for Windows. This means +// rendering the non-standard window caption, border, and controls. +// +//////////////////////////////////////////////////////////////////////////////// +class CustomFrameView : public NonClientFrameView, + public ButtonListener { + public: + explicit CustomFrameView(Window* frame); + virtual ~CustomFrameView(); + + // Overridden from views::NonClientFrameView: + virtual gfx::Rect GetBoundsForClientView() const; + virtual gfx::Rect GetWindowBoundsForClientBounds( + const gfx::Rect& client_bounds) const; + virtual gfx::Point GetSystemMenuPoint() const; + virtual int NonClientHitTest(const gfx::Point& point); + virtual void GetWindowMask(const gfx::Size& size, gfx::Path* window_mask); + virtual void EnableClose(bool enable); + virtual void ResetWindowControls(); + + // View overrides: + virtual void Paint(ChromeCanvas* canvas); + virtual void Layout(); + virtual gfx::Size GetPreferredSize(); + + // ButtonListener implementation: + virtual void ButtonPressed(Button* sender); + + private: + // Returns the thickness of the border that makes up the window frame edges. + // This does not include any client edge. + int FrameBorderThickness() const; + + // Returns the thickness of the entire nonclient left, right, and bottom + // borders, including both the window frame and any client edge. + int NonClientBorderThickness() const; + + // Returns the height of the entire nonclient top border, including the window + // frame, any title area, and any connected client edge. + int NonClientTopBorderHeight() const; + + // A bottom border, and, in restored mode, a client edge are drawn at the + // bottom of the titlebar. This returns the total height drawn. + int BottomEdgeThicknessWithinNonClientHeight() const; + + // Calculates multiple values related to title layout. Returns the height of + // the entire titlebar including any connected client edge. + int TitleCoordinates(int* title_top_spacing, + int* title_thickness) const; + + // Paint various sub-components of this view. + void PaintRestoredFrameBorder(ChromeCanvas* canvas); + void PaintMaximizedFrameBorder(ChromeCanvas* canvas); + void PaintTitleBar(ChromeCanvas* canvas); + void PaintRestoredClientEdge(ChromeCanvas* canvas); + + // Layout various sub-components of this view. + void LayoutWindowControls(); + void LayoutTitleBar(); + void LayoutClientView(); + + // Returns the resource collection to be used when rendering the window. + WindowResources* resources() const { + return frame_->IsActive() || paint_as_active() ? active_resources_ + : inactive_resources_; + } + + // The bounds of the client view, in this view's coordinates. + gfx::Rect client_view_bounds_; + + // The layout rect of the title, if visible. + gfx::Rect title_bounds_; + + // Window controls. + ImageButton* close_button_; + ImageButton* restore_button_; + ImageButton* maximize_button_; + ImageButton* minimize_button_; + ImageButton* system_menu_button_; // Uses the window icon if visible. + bool should_show_minmax_buttons_; + + // The window that owns this view. + Window* frame_; + + // Initialize various static resources. + static void InitClass(); + static WindowResources* active_resources_; + static WindowResources* inactive_resources_; + static ChromeFont* title_font_; + + DISALLOW_EVIL_CONSTRUCTORS(CustomFrameView); +}; + +} // namespace views + +#endif // #ifndef VIEWS_WINDOW_CUSTOM_FRAME_VIEW_H_ diff --git a/views/window/dialog_client_view.cc b/views/window/dialog_client_view.cc new file mode 100644 index 0000000..b7989ac --- /dev/null +++ b/views/window/dialog_client_view.cc @@ -0,0 +1,440 @@ +// 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 "views/window/dialog_client_view.h" + +#include <windows.h> +#include <uxtheme.h> +#include <vsstyle.h> + +#include "app/gfx/chrome_canvas.h" +#include "app/gfx/chrome_font.h" +#include "app/l10n_util.h" +#include "app/resource_bundle.h" +#include "base/gfx/native_theme.h" +#include "chrome/browser/views/standard_layout.h" +#include "grit/generated_resources.h" +#include "views/controls/button/native_button.h" +#include "views/window/dialog_delegate.h" +#include "views/window/window.h" + +namespace views { + +namespace { + +// Updates any of the standard buttons according to the delegate. +void UpdateButtonHelper(NativeButton* button_view, + DialogDelegate* delegate, + MessageBoxFlags::DialogButton button) { + std::wstring label = delegate->GetDialogButtonLabel(button); + if (!label.empty()) + button_view->SetLabel(label); + button_view->SetEnabled(delegate->IsDialogButtonEnabled(button)); + button_view->SetVisible(delegate->IsDialogButtonVisible(button)); +} + +void FillViewWithSysColor(ChromeCanvas* canvas, View* view, COLORREF color) { + SkColor sk_color = + SkColorSetRGB(GetRValue(color), GetGValue(color), GetBValue(color)); + canvas->FillRectInt(sk_color, 0, 0, view->width(), view->height()); +} + +// DialogButton ---------------------------------------------------------------- + +// DialogButtons is used for the ok/cancel buttons of the window. DialogButton +// forwards AcceleratorPressed to the delegate. + +class DialogButton : public NativeButton { + public: + DialogButton(ButtonListener* listener, + Window* owner, + MessageBoxFlags::DialogButton type, + const std::wstring& title, + bool is_default) + : NativeButton(listener, title), + owner_(owner), + type_(type) { + SetIsDefault(is_default); + } + + // Overridden to forward to the delegate. + virtual bool AcceleratorPressed(const Accelerator& accelerator) { + if (!owner_->GetDelegate()->AsDialogDelegate()-> + AreAcceleratorsEnabled(type_)) { + return false; + } + return NativeButton::AcceleratorPressed(accelerator); + } + + private: + Window* owner_; + const MessageBoxFlags::DialogButton type_; + + DISALLOW_COPY_AND_ASSIGN(DialogButton); +}; + +} // namespace + +// static +ChromeFont* DialogClientView::dialog_button_font_ = NULL; +static const int kDialogMinButtonWidth = 75; +static const int kDialogButtonLabelSpacing = 16; +static const int kDialogButtonContentSpacing = 5; + +// The group used by the buttons. This name is chosen voluntarily big not to +// conflict with other groups that could be in the dialog content. +static const int kButtonGroup = 6666; + +/////////////////////////////////////////////////////////////////////////////// +// DialogClientView, public: + +DialogClientView::DialogClientView(Window* owner, View* contents_view) + : ClientView(owner, contents_view), + ok_button_(NULL), + cancel_button_(NULL), + extra_view_(NULL), + accepted_(false), + default_button_(NULL) { + InitClass(); +} + +DialogClientView::~DialogClientView() { +} + +void DialogClientView::ShowDialogButtons() { + DialogDelegate* dd = GetDialogDelegate(); + int buttons = dd->GetDialogButtons(); + if (buttons & MessageBoxFlags::DIALOGBUTTON_OK && !ok_button_) { + std::wstring label = + dd->GetDialogButtonLabel(MessageBoxFlags::DIALOGBUTTON_OK); + if (label.empty()) + label = l10n_util::GetString(IDS_OK); + bool is_default_button = + (dd->GetDefaultDialogButton() & MessageBoxFlags::DIALOGBUTTON_OK) != 0; + ok_button_ = new DialogButton(this, window(), + MessageBoxFlags::DIALOGBUTTON_OK, label, + is_default_button); + ok_button_->SetGroup(kButtonGroup); + if (is_default_button) + default_button_ = ok_button_; + if (!(buttons & MessageBoxFlags::DIALOGBUTTON_CANCEL)) + ok_button_->AddAccelerator(Accelerator(VK_ESCAPE, false, false, false)); + AddChildView(ok_button_); + } + if (buttons & MessageBoxFlags::DIALOGBUTTON_CANCEL && !cancel_button_) { + std::wstring label = + dd->GetDialogButtonLabel(MessageBoxFlags::DIALOGBUTTON_CANCEL); + if (label.empty()) { + if (buttons & MessageBoxFlags::DIALOGBUTTON_OK) { + label = l10n_util::GetString(IDS_CANCEL); + } else { + label = l10n_util::GetString(IDS_CLOSE); + } + } + bool is_default_button = + (dd->GetDefaultDialogButton() & MessageBoxFlags::DIALOGBUTTON_CANCEL) + != 0; + cancel_button_ = new DialogButton(this, window(), + MessageBoxFlags::DIALOGBUTTON_CANCEL, + label, is_default_button); + cancel_button_->SetGroup(kButtonGroup); + cancel_button_->AddAccelerator(Accelerator(VK_ESCAPE, false, false, false)); + if (is_default_button) + default_button_ = ok_button_; + AddChildView(cancel_button_); + } + if (!buttons) { + // Register the escape key as an accelerator which will close the window + // if there are no dialog buttons. + AddAccelerator(Accelerator(VK_ESCAPE, false, false, false)); + } +} + +void DialogClientView::SetDefaultButton(NativeButton* new_default_button) { + if (default_button_ && default_button_ != new_default_button) { + default_button_->SetIsDefault(false); + default_button_ = NULL; + } + + if (new_default_button) { + default_button_ = new_default_button; + default_button_->SetIsDefault(true); + } +} + +void DialogClientView::FocusWillChange(View* focused_before, + View* focused_now) { + NativeButton* new_default_button = NULL; + if (focused_now && + focused_now->GetClassName() == NativeButton::kViewClassName) { + new_default_button = static_cast<NativeButton*>(focused_now); + } else { + // The focused view is not a button, get the default button from the + // delegate. + DialogDelegate* dd = GetDialogDelegate(); + if ((dd->GetDefaultDialogButton() & MessageBoxFlags::DIALOGBUTTON_OK) != 0) + new_default_button = ok_button_; + if ((dd->GetDefaultDialogButton() & MessageBoxFlags::DIALOGBUTTON_CANCEL) + != 0) + new_default_button = cancel_button_; + } + SetDefaultButton(new_default_button); +} + +// Changing dialog labels will change button widths. +void DialogClientView::UpdateDialogButtons() { + DialogDelegate* dd = GetDialogDelegate(); + int buttons = dd->GetDialogButtons(); + + if (buttons & MessageBoxFlags::DIALOGBUTTON_OK) + UpdateButtonHelper(ok_button_, dd, MessageBoxFlags::DIALOGBUTTON_OK); + + if (buttons & MessageBoxFlags::DIALOGBUTTON_CANCEL) { + UpdateButtonHelper(cancel_button_, dd, + MessageBoxFlags::DIALOGBUTTON_CANCEL); + } + + LayoutDialogButtons(); + SchedulePaint(); +} + +void DialogClientView::AcceptWindow() { + if (accepted_) { + // We should only get into AcceptWindow once. + NOTREACHED(); + return; + } + if (GetDialogDelegate()->Accept(false)) { + accepted_ = true; + window()->Close(); + } +} + +void DialogClientView::CancelWindow() { + // Call the standard Close handler, which checks with the delegate before + // proceeding. This checking _isn't_ done here, but in the WM_CLOSE handler, + // so that the close box on the window also shares this code path. + window()->Close(); +} + +/////////////////////////////////////////////////////////////////////////////// +// DialogClientView, ClientView overrides: + +bool DialogClientView::CanClose() const { + if (!accepted_) { + DialogDelegate* dd = GetDialogDelegate(); + int buttons = dd->GetDialogButtons(); + if (buttons & MessageBoxFlags::DIALOGBUTTON_CANCEL) + return dd->Cancel(); + if (buttons & MessageBoxFlags::DIALOGBUTTON_OK) + return dd->Accept(true); + } + return true; +} + +void DialogClientView::WindowClosing() { + FocusManager* focus_manager = GetFocusManager(); + DCHECK(focus_manager); + if (focus_manager) + focus_manager->RemoveFocusChangeListener(this); + ClientView::WindowClosing(); +} + +int DialogClientView::NonClientHitTest(const gfx::Point& point) { + if (size_box_bounds_.Contains(point.x() - x(), point.y() - y())) + return HTBOTTOMRIGHT; + return ClientView::NonClientHitTest(point); +} + +//////////////////////////////////////////////////////////////////////////////// +// DialogClientView, View overrides: + +void DialogClientView::Paint(ChromeCanvas* canvas) { + FillViewWithSysColor(canvas, this, GetSysColor(COLOR_3DFACE)); +} + +void DialogClientView::PaintChildren(ChromeCanvas* canvas) { + View::PaintChildren(canvas); + if (!window()->IsMaximized() && !window()->IsMinimized()) + PaintSizeBox(canvas); +} + +void DialogClientView::Layout() { + if (has_dialog_buttons()) + LayoutDialogButtons(); + LayoutContentsView(); +} + +void DialogClientView::ViewHierarchyChanged(bool is_add, View* parent, + View* child) { + if (is_add && child == this) { + // Can only add and update the dialog buttons _after_ they are added to the + // view hierarchy since they are native controls and require the + // Container's HWND. + ShowDialogButtons(); + ClientView::ViewHierarchyChanged(is_add, parent, child); + + FocusManager* focus_manager = GetFocusManager(); + // Listen for focus change events so we can update the default button. + DCHECK(focus_manager); // bug #1291225: crash reports seem to indicate it + // can be NULL. + if (focus_manager) + focus_manager->AddFocusChangeListener(this); + + // The "extra view" must be created and installed after the contents view + // has been inserted into the view hierarchy. + CreateExtraView(); + UpdateDialogButtons(); + Layout(); + } +} + +gfx::Size DialogClientView::GetPreferredSize() { + gfx::Size prefsize = contents_view()->GetPreferredSize(); + int button_height = 0; + if (has_dialog_buttons()) { + if (cancel_button_) + button_height = cancel_button_->height(); + else + button_height = ok_button_->height(); + // Account for padding above and below the button. + button_height += kDialogButtonContentSpacing + kButtonVEdgeMargin; + } + prefsize.Enlarge(0, button_height); + return prefsize; +} + +bool DialogClientView::AcceleratorPressed(const Accelerator& accelerator) { + DCHECK(accelerator.GetKeyCode() == VK_ESCAPE); // We only expect Escape key. + window()->Close(); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// DialogClientView, ButtonListener implementation: + +void DialogClientView::ButtonPressed(Button* sender) { + if (sender == ok_button_) { + AcceptWindow(); + } else if (sender == cancel_button_) { + CancelWindow(); + } else { + NOTREACHED(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// DialogClientView, private: + +void DialogClientView::PaintSizeBox(ChromeCanvas* canvas) { + if (window()->GetDelegate()->CanResize() || + window()->GetDelegate()->CanMaximize()) { + HDC dc = canvas->beginPlatformPaint(); + SIZE gripper_size = { 0, 0 }; + gfx::NativeTheme::instance()->GetThemePartSize( + gfx::NativeTheme::STATUS, dc, SP_GRIPPER, 1, NULL, TS_TRUE, + &gripper_size); + + // TODO(beng): (http://b/1085509) In "classic" rendering mode, there isn't + // a theme-supplied gripper. We should probably improvise + // something, which would also require changing |gripper_size| + // to have different default values, too... + size_box_bounds_ = GetLocalBounds(false); + size_box_bounds_.set_x(size_box_bounds_.right() - gripper_size.cx); + size_box_bounds_.set_y(size_box_bounds_.bottom() - gripper_size.cy); + RECT native_bounds = size_box_bounds_.ToRECT(); + gfx::NativeTheme::instance()->PaintStatusGripper( + dc, SP_PANE, 1, 0, &native_bounds); + canvas->endPlatformPaint(); + } +} + +int DialogClientView::GetButtonWidth(int button) const { + DialogDelegate* dd = GetDialogDelegate(); + std::wstring button_label = dd->GetDialogButtonLabel( + static_cast<MessageBoxFlags::DialogButton>(button)); + int string_width = dialog_button_font_->GetStringWidth(button_label); + return std::max(string_width + kDialogButtonLabelSpacing, + kDialogMinButtonWidth); +} + +int DialogClientView::GetButtonsHeight() const { + if (has_dialog_buttons()) { + if (cancel_button_) + return cancel_button_->height() + kDialogButtonContentSpacing; + return ok_button_->height() + kDialogButtonContentSpacing; + } + return 0; +} + +void DialogClientView::LayoutDialogButtons() { + gfx::Rect extra_bounds; + if (cancel_button_) { + gfx::Size ps = cancel_button_->GetPreferredSize(); + gfx::Rect lb = GetLocalBounds(false); + int button_width = GetButtonWidth(MessageBoxFlags::DIALOGBUTTON_CANCEL); + int button_x = lb.right() - button_width - kButtonHEdgeMargin; + int button_y = lb.bottom() - ps.height() - kButtonVEdgeMargin; + cancel_button_->SetBounds(button_x, button_y, button_width, ps.height()); + // The extra view bounds are dependent on this button. + extra_bounds.set_width(std::max(0, cancel_button_->x())); + extra_bounds.set_y(cancel_button_->y()); + } + if (ok_button_) { + gfx::Size ps = ok_button_->GetPreferredSize(); + gfx::Rect lb = GetLocalBounds(false); + int button_width = GetButtonWidth(MessageBoxFlags::DIALOGBUTTON_OK); + int ok_button_right = lb.right() - kButtonHEdgeMargin; + if (cancel_button_) + ok_button_right = cancel_button_->x() - kRelatedButtonHSpacing; + int button_x = ok_button_right - button_width; + int button_y = lb.bottom() - ps.height() - kButtonVEdgeMargin; + ok_button_->SetBounds(button_x, button_y, ok_button_right - button_x, + ps.height()); + // The extra view bounds are dependent on this button. + extra_bounds.set_width(std::max(0, ok_button_->x())); + extra_bounds.set_y(ok_button_->y()); + } + if (extra_view_) { + gfx::Size ps = extra_view_->GetPreferredSize(); + gfx::Rect lb = GetLocalBounds(false); + extra_bounds.set_x(lb.x() + kButtonHEdgeMargin); + extra_bounds.set_height(ps.height()); + extra_view_->SetBounds(extra_bounds); + } +} + +void DialogClientView::LayoutContentsView() { + gfx::Rect lb = GetLocalBounds(false); + lb.set_height(std::max(0, lb.height() - GetButtonsHeight())); + contents_view()->SetBounds(lb); + contents_view()->Layout(); +} + +void DialogClientView::CreateExtraView() { + View* extra_view = GetDialogDelegate()->GetExtraView(); + if (extra_view && !extra_view_) { + extra_view_ = extra_view; + extra_view_->SetGroup(kButtonGroup); + AddChildView(extra_view_); + } +} + +DialogDelegate* DialogClientView::GetDialogDelegate() const { + DialogDelegate* dd = window()->GetDelegate()->AsDialogDelegate(); + DCHECK(dd); + return dd; +} + +// static +void DialogClientView::InitClass() { + static bool initialized = false; + if (!initialized) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + dialog_button_font_ = new ChromeFont(rb.GetFont(ResourceBundle::BaseFont)); + initialized = true; + } +} + +} // namespace views diff --git a/views/window/dialog_client_view.h b/views/window/dialog_client_view.h new file mode 100644 index 0000000..347b61c --- /dev/null +++ b/views/window/dialog_client_view.h @@ -0,0 +1,123 @@ +// 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. + +#ifndef VIEWS_WINDOW_DIALOG_CLIENT_VIEW_H_ +#define VIEWS_WINDOW_DIALOG_CLIENT_VIEW_H_ + +#include "app/gfx/chrome_font.h" +#include "views/focus/focus_manager.h" +#include "views/controls/button/button.h" +#include "views/window/client_view.h" + +namespace views { + +class DialogDelegate; +class NativeButton; +class Window; + +/////////////////////////////////////////////////////////////////////////////// +// DialogClientView +// +// This ClientView subclass provides the content of a typical dialog box, +// including a strip of buttons at the bottom right of the window, default +// accelerator handlers for accept and cancel, and the ability for the +// embedded contents view to provide extra UI to be shown in the row of +// buttons. +// +class DialogClientView : public ClientView, + public ButtonListener, + public FocusChangeListener { + public: + DialogClientView(Window* window, View* contents_view); + virtual ~DialogClientView(); + + // Adds the dialog buttons required by the supplied WindowDelegate to the + // view. + void ShowDialogButtons(); + + // Updates the enabled state and label of the buttons required by the + // supplied WindowDelegate + void UpdateDialogButtons(); + + // Accept the changes made in the window that contains this ClientView. + void AcceptWindow(); + + // Cancel the changes made in the window that contains this ClientView. + void CancelWindow(); + + // Accessors in case the user wishes to adjust these buttons. + NativeButton* ok_button() const { return ok_button_; } + NativeButton* cancel_button() const { return cancel_button_; } + + // Overridden from ClientView: + virtual bool CanClose() const; + virtual void WindowClosing(); + virtual int NonClientHitTest(const gfx::Point& point); + virtual DialogClientView* AsDialogClientView() { return this; } + + // FocusChangeListener implementation: + virtual void FocusWillChange(View* focused_before, View* focused_now); + + protected: + // View overrides: + virtual void Paint(ChromeCanvas* canvas); + virtual void PaintChildren(ChromeCanvas* canvas); + virtual void Layout(); + virtual void ViewHierarchyChanged(bool is_add, View* parent, View* child); + virtual gfx::Size GetPreferredSize(); + virtual bool AcceleratorPressed(const Accelerator& accelerator); + + // ButtonListener implementation: + virtual void ButtonPressed(Button* sender); + + private: + // Paint the size box in the bottom right corner of the window if it is + // resizable. + void PaintSizeBox(ChromeCanvas* canvas); + + // Returns the width of the specified dialog button using the correct font. + int GetButtonWidth(int button) const; + int GetButtonsHeight() const; + + // Position and size various sub-views. + void LayoutDialogButtons(); + void LayoutContentsView(); + + // Makes the specified button the default button. + void SetDefaultButton(NativeButton* button); + + bool has_dialog_buttons() const { return ok_button_ || cancel_button_; } + + // Create and add the extra view, if supplied by the delegate. + void CreateExtraView(); + + // Returns the DialogDelegate for the window. + DialogDelegate* GetDialogDelegate() const; + + // The dialog buttons. + NativeButton* ok_button_; + NativeButton* cancel_button_; + + // The button that is currently the default button if any. + NativeButton* default_button_; + + // The button-level extra view, NULL unless the dialog delegate supplies one. + View* extra_view_; + + // The layout rect of the size box, when visible. + gfx::Rect size_box_bounds_; + + // True if the window was Accepted by the user using the OK button. + bool accepted_; + + // Static resource initialization + static void InitClass(); + static ChromeFont* dialog_button_font_; + + DISALLOW_COPY_AND_ASSIGN(DialogClientView); +}; + +} // namespace views + +#endif // #ifndef VIEWS_WINDOW_DIALOG_CLIENT_VIEW_H_ diff --git a/views/window/dialog_delegate.cc b/views/window/dialog_delegate.cc new file mode 100644 index 0000000..4ae9a91 --- /dev/null +++ b/views/window/dialog_delegate.cc @@ -0,0 +1,54 @@ +// 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 "views/window/dialog_delegate.h" + +#include "base/logging.h" +#include "views/controls/button/native_button.h" +#include "views/window/window.h" + +namespace views { + +// Overridden from WindowDelegate: + +int DialogDelegate::GetDefaultDialogButton() const { + if (GetDialogButtons() & MessageBoxFlags::DIALOGBUTTON_OK) + return MessageBoxFlags::DIALOGBUTTON_OK; + if (GetDialogButtons() & MessageBoxFlags::DIALOGBUTTON_CANCEL) + return MessageBoxFlags::DIALOGBUTTON_CANCEL; + return MessageBoxFlags::DIALOGBUTTON_NONE; +} + +View* DialogDelegate::GetInitiallyFocusedView() { + // Focus the default button if any. + DialogClientView* dcv = GetDialogClientView(); + int default_button = GetDefaultDialogButton(); + if (default_button == MessageBoxFlags::DIALOGBUTTON_NONE) + return NULL; + + if ((default_button & GetDialogButtons()) == 0) { + // The default button is a button we don't have. + NOTREACHED(); + return NULL; + } + + if (default_button & MessageBoxFlags::DIALOGBUTTON_OK) + return dcv->ok_button(); + if (default_button & MessageBoxFlags::DIALOGBUTTON_CANCEL) + return dcv->cancel_button(); + return NULL; +} + +ClientView* DialogDelegate::CreateClientView(Window* window) { + return new DialogClientView(window, GetContentsView()); +} + +DialogClientView* DialogDelegate::GetDialogClientView() const { + ClientView* client_view = window()->GetClientView(); + DialogClientView* dialog_client_view = client_view->AsDialogClientView(); + DCHECK(dialog_client_view); + return dialog_client_view; +} + +} // namespace views diff --git a/views/window/dialog_delegate.h b/views/window/dialog_delegate.h new file mode 100644 index 0000000..4418e61f --- /dev/null +++ b/views/window/dialog_delegate.h @@ -0,0 +1,113 @@ +// 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. + +#ifndef VIEWS_WINDOW_DIALOG_DELEGATE_H_ +#define VIEWS_WINDOW_DIALOG_DELEGATE_H_ + +#include "app/message_box_flags.h" +#include "views/window/dialog_client_view.h" +#include "views/window/window_delegate.h" + +namespace views { + +class View; + +/////////////////////////////////////////////////////////////////////////////// +// +// DialogDelegate +// +// DialogDelegate is an interface implemented by objects that wish to show a +// dialog box Window. The window that is displayed uses this interface to +// determine how it should be displayed and notify the delegate object of +// certain events. +// +/////////////////////////////////////////////////////////////////////////////// +class DialogDelegate : public WindowDelegate { + public: + virtual DialogDelegate* AsDialogDelegate() { return this; } + + // Returns a mask specifying which of the available DialogButtons are visible + // for the dialog. Note: If an OK button is provided, you should provide a + // CANCEL button. A dialog box with just an OK button is frowned upon and + // considered a very special case, so if you're planning on including one, + // you should reconsider, or beng says there will be stabbings. + // + // To use the extra button you need to override GetDialogButtons() + virtual int GetDialogButtons() const { + return MessageBoxFlags::DIALOGBUTTON_OK | + MessageBoxFlags::DIALOGBUTTON_CANCEL; + } + + // Returns whether accelerators are enabled on the button. This is invoked + // when an accelerator is pressed, not at construction time. This + // returns true. + virtual bool AreAcceleratorsEnabled(MessageBoxFlags::DialogButton button) { + return true; + } + + // Returns the label of the specified DialogButton. + virtual std::wstring GetDialogButtonLabel( + MessageBoxFlags::DialogButton button) const { + // empty string results in defaults for MessageBoxFlags::DIALOGBUTTON_OK, + // MessageBoxFlags::DIALOGBUTTON_CANCEL. + return L""; + } + + // Override this function if with a view which will be shown in the same + // row as the OK and CANCEL buttons but flush to the left and extending + // up to the buttons. + virtual View* GetExtraView() { return NULL; } + + // Returns the default dialog button. This should not be a mask as only + // one button should ever be the default button. Return + // MessageBoxFlags::DIALOGBUTTON_NONE if there is no default. Default + // behavior is to return MessageBoxFlags::DIALOGBUTTON_OK or + // MessageBoxFlags::DIALOGBUTTON_CANCEL (in that order) if they are + // present, MessageBoxFlags::DIALOGBUTTON_NONE otherwise. + virtual int GetDefaultDialogButton() const; + + // Returns whether the specified dialog button is enabled. + virtual bool IsDialogButtonEnabled( + MessageBoxFlags::DialogButton button) const { + return true; + } + + // Returns whether the specified dialog button is visible. + virtual bool IsDialogButtonVisible( + MessageBoxFlags::DialogButton button) const { + return true; + } + + // For Dialog boxes, if there is a "Cancel" button, this is called when the + // user presses the "Cancel" button or the Close button on the window or + // in the system menu, or presses the Esc key. This function should return + // true if the window can be closed after it returns, or false if it must + // remain open. + virtual bool Cancel() { return true; } + + // For Dialog boxes, this is called when the user presses the "OK" button, + // or the Enter key. Can also be called on Esc key or close button + // presses if there is no "Cancel" button. This function should return + // true if the window can be closed after the window can be closed after + // it returns, or false if it must remain open. If |window_closing| is + // true, it means that this handler is being called because the window is + // being closed (e.g. by Window::Close) and there is no Cancel handler, + // so Accept is being called instead. + virtual bool Accept(bool window_closing) { return Accept(); } + virtual bool Accept() { return true; } + + // Overridden from WindowDelegate: + virtual View* GetInitiallyFocusedView(); + + // Overridden from WindowDelegate: + virtual ClientView* CreateClientView(Window* window); + + // A helper for accessing the DialogClientView object contained by this + // delegate's Window. + DialogClientView* GetDialogClientView() const; +}; + +} // namespace views + +#endif // VIEWS_WINDOW_DIALOG_DELEGATE_H_ diff --git a/views/window/native_frame_view.cc b/views/window/native_frame_view.cc new file mode 100644 index 0000000..05a8dcb --- /dev/null +++ b/views/window/native_frame_view.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2009 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/window/native_frame_view.h" + +#include "views/window/window_win.h" + +namespace views { + +//////////////////////////////////////////////////////////////////////////////// +// NativeFrameView, public: + +NativeFrameView::NativeFrameView(WindowWin* frame) + : NonClientFrameView(), + frame_(frame) { +} + +NativeFrameView::~NativeFrameView() { +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeFrameView, NonClientFrameView overrides: + +gfx::Rect NativeFrameView::GetBoundsForClientView() const { + return gfx::Rect(0, 0, width(), height()); +} + +gfx::Rect NativeFrameView::GetWindowBoundsForClientBounds( + const gfx::Rect& client_bounds) const { + RECT rect = client_bounds.ToRECT(); + AdjustWindowRectEx(&rect, frame_->window_style(), FALSE, + frame_->window_ex_style()); + return gfx::Rect(rect); +} + +gfx::Point NativeFrameView::GetSystemMenuPoint() const { + POINT temp = {0, -kFrameShadowThickness }; + MapWindowPoints(frame_->GetNativeView(), HWND_DESKTOP, &temp, 1); + return gfx::Point(temp); +} + +int NativeFrameView::NonClientHitTest(const gfx::Point& point) { + return HTNOWHERE; +} + +void NativeFrameView::GetWindowMask(const gfx::Size& size, + gfx::Path* window_mask) { + // Nothing to do, we use the default window mask. +} + +void NativeFrameView::EnableClose(bool enable) { + // Nothing to do, handled automatically by Window. +} + +void NativeFrameView::ResetWindowControls() { + // Nothing to do. +} + +} // namespace views diff --git a/views/window/native_frame_view.h b/views/window/native_frame_view.h new file mode 100644 index 0000000..e2efa9d --- /dev/null +++ b/views/window/native_frame_view.h @@ -0,0 +1,39 @@ +// Copyright (c) 2009 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. + +#ifndef VIEWS_WINDOW_NATIVE_FRAME_VIEW_H_ +#define VIEWS_WINDOW_NATIVE_FRAME_VIEW_H_ + +#include "views/window/non_client_view.h" + +namespace views { + +class WindowWin; + +class NativeFrameView : public NonClientFrameView { + public: + explicit NativeFrameView(WindowWin* frame); + virtual ~NativeFrameView(); + + // NonClientFrameView overrides: + virtual gfx::Rect GetBoundsForClientView() const; + virtual gfx::Rect GetWindowBoundsForClientBounds( + const gfx::Rect& client_bounds) const; + virtual gfx::Point GetSystemMenuPoint() const; + virtual int NonClientHitTest(const gfx::Point& point); + virtual void GetWindowMask(const gfx::Size& size, + gfx::Path* window_mask); + virtual void EnableClose(bool enable); + virtual void ResetWindowControls(); + + private: + // Our containing frame. + WindowWin* frame_; + + DISALLOW_COPY_AND_ASSIGN(NativeFrameView); +}; + +} // namespace views + +#endif // #ifndef VIEWS_WINDOW_NATIVE_FRAME_VIEW_H_ diff --git a/views/window/non_client_view.cc b/views/window/non_client_view.cc new file mode 100644 index 0000000..9a06cfc --- /dev/null +++ b/views/window/non_client_view.cc @@ -0,0 +1,255 @@ +// 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 "views/window/non_client_view.h" + +#include "chrome/common/win_util.h" +#include "views/widget/root_view.h" +#include "views/widget/widget.h" +#include "views/window/window.h" + +namespace views { + +const int NonClientFrameView::kFrameShadowThickness = 1; +const int NonClientFrameView::kClientEdgeThickness = 1; + +// The frame view and the client view are always at these specific indices, +// because the RootView message dispatch sends messages to items higher in the +// z-order first and we always want the client view to have first crack at +// handling mouse messages. +static const int kFrameViewIndex = 0; +static const int kClientViewIndex = 1; + +//////////////////////////////////////////////////////////////////////////////// +// NonClientView, public: + +NonClientView::NonClientView(Window* frame) + : frame_(frame), + client_view_(NULL), + use_native_frame_(win_util::ShouldUseVistaFrame()) { +} + +NonClientView::~NonClientView() { + // This value may have been reset before the window hierarchy shuts down, + // so we need to manually remove it. + RemoveChildView(frame_view_.get()); +} + +void NonClientView::SetFrameView(NonClientFrameView* frame_view) { + // See comment in header about ownership. + frame_view->SetParentOwned(false); + if (frame_view_.get()) + RemoveChildView(frame_view_.get()); + frame_view_.reset(frame_view); + if (GetParent()) + AddChildView(kFrameViewIndex, frame_view_.get()); +} + +bool NonClientView::CanClose() const { + return client_view_->CanClose(); +} + +void NonClientView::WindowClosing() { + client_view_->WindowClosing(); +} + +void NonClientView::SetUseNativeFrame(bool use_native_frame) { + use_native_frame_ = use_native_frame; + SetFrameView(frame_->CreateFrameViewForWindow()); + GetRootView()->ThemeChanged(); + Layout(); + SchedulePaint(); + frame_->UpdateFrameAfterFrameChange(); +} + +bool NonClientView::UseNativeFrame() const { + // The frame view may always require a custom frame, e.g. Constrained Windows. + bool always_use_custom_frame = + frame_view_.get() && frame_view_->AlwaysUseCustomFrame(); + return !always_use_custom_frame && use_native_frame_; +} + +void NonClientView::DisableInactiveRendering(bool disable) { + frame_view_->DisableInactiveRendering(disable); +} + +gfx::Rect NonClientView::GetWindowBoundsForClientBounds( + const gfx::Rect client_bounds) const { + return frame_view_->GetWindowBoundsForClientBounds(client_bounds); +} + +gfx::Point NonClientView::GetSystemMenuPoint() const { + return frame_view_->GetSystemMenuPoint(); +} + +int NonClientView::NonClientHitTest(const gfx::Point& point) { + // Sanity check. + if (!bounds().Contains(point)) + return HTNOWHERE; + + // The ClientView gets first crack, since it overlays the NonClientFrameView + // in the display stack. + int frame_component = client_view_->NonClientHitTest(point); + if (frame_component != HTNOWHERE) + return frame_component; + + // Finally ask the NonClientFrameView. It's at the back of the display stack + // so it gets asked last. + return frame_view_->NonClientHitTest(point); +} + +void NonClientView::GetWindowMask(const gfx::Size& size, + gfx::Path* window_mask) { + frame_view_->GetWindowMask(size, window_mask); +} + +void NonClientView::EnableClose(bool enable) { + frame_view_->EnableClose(enable); +} + +void NonClientView::ResetWindowControls() { + frame_view_->ResetWindowControls(); +} + +void NonClientView::LayoutFrameView() { + // First layout the NonClientFrameView, which determines the size of the + // ClientView... + frame_view_->SetBounds(0, 0, width(), height()); + + // We need to manually call Layout here because layout for the frame view can + // change independently of the bounds changing - e.g. after the initial + // display of the window the metrics of the native window controls can change, + // which does not change the bounds of the window but requires a re-layout to + // trigger a repaint. We override DidChangeBounds for the NonClientFrameView + // to do nothing so that SetBounds above doesn't cause Layout to be called + // twice. + frame_view_->Layout(); +} + +//////////////////////////////////////////////////////////////////////////////// +// NonClientView, View overrides: + +gfx::Size NonClientView::GetPreferredSize() { + // TODO(pkasting): This should probably be made to look similar to + // GetMinimumSize() below. This will require implementing GetPreferredSize() + // better in the various frame views. + gfx::Rect client_bounds(gfx::Point(), client_view_->GetPreferredSize()); + return GetWindowBoundsForClientBounds(client_bounds).size(); +} + +gfx::Size NonClientView::GetMinimumSize() { + return frame_view_->GetMinimumSize(); +} + +void NonClientView::Layout() { + LayoutFrameView(); + + // Then layout the ClientView, using those bounds. + client_view_->SetBounds(frame_view_->GetBoundsForClientView()); + + // We need to manually call Layout on the ClientView as well for the same + // reason as above. + client_view_->Layout(); +} + +void NonClientView::ViewHierarchyChanged(bool is_add, View* parent, + View* child) { + // Add our two child views here as we are added to the Widget so that if we + // are subsequently resized all the parent-child relationships are + // established. + if (is_add && GetWidget() && child == this) { + AddChildView(kFrameViewIndex, frame_view_.get()); + AddChildView(kClientViewIndex, client_view_); + } +} + +views::View* NonClientView::GetViewForPoint(const gfx::Point& point) { + return GetViewForPoint(point, false); +} + +views::View* NonClientView::GetViewForPoint(const gfx::Point& point, + bool can_create_floating) { + // Because of the z-ordering of our child views (the client view is positioned + // over the non-client frame view, if the client view ever overlaps the frame + // view visually (as it does for the browser window), then it will eat mouse + // events for the window controls. We override this method here so that we can + // detect this condition and re-route the events to the non-client frame view. + // The assumption is that the frame view's implementation of HitTest will only + // return true for area not occupied by the client view. + gfx::Point point_in_child_coords(point); + View::ConvertPointToView(this, frame_view_.get(), &point_in_child_coords); + if (frame_view_->HitTest(point_in_child_coords)) + return frame_view_->GetViewForPoint(point); + + return View::GetViewForPoint(point, can_create_floating); +} + +//////////////////////////////////////////////////////////////////////////////// +// NonClientFrameView, View overrides: + +bool NonClientFrameView::HitTest(const gfx::Point& l) const { + // For the default case, we assume the non-client frame view never overlaps + // the client view. + return !GetWindow()->GetClientView()->bounds().Contains(l); +} + +void NonClientFrameView::DidChangeBounds(const gfx::Rect& previous, + const gfx::Rect& current) { + // Overridden to do nothing. The NonClientView manually calls Layout on the + // FrameView when it is itself laid out, see comment in NonClientView::Layout. +} + +//////////////////////////////////////////////////////////////////////////////// +// NonClientFrameView, protected: + +int NonClientFrameView::GetHTComponentForFrame(const gfx::Point& point, + int top_resize_border_height, + int resize_border_thickness, + int top_resize_corner_height, + int resize_corner_width, + bool can_resize) { + // Tricky: In XP, native behavior is to return HTTOPLEFT and HTTOPRIGHT for + // a |resize_corner_size|-length strip of both the side and top borders, but + // only to return HTBOTTOMLEFT/HTBOTTOMRIGHT along the bottom border + corner + // (not the side border). Vista goes further and doesn't return these on any + // of the side borders. We allow callers to match either behavior. + int component; + if (point.x() < resize_border_thickness) { + if (point.y() < top_resize_corner_height) + component = HTTOPLEFT; + else if (point.y() >= (height() - resize_border_thickness)) + component = HTBOTTOMLEFT; + else + component = HTLEFT; + } else if (point.x() >= (width() - resize_border_thickness)) { + if (point.y() < top_resize_corner_height) + component = HTTOPRIGHT; + else if (point.y() >= (height() - resize_border_thickness)) + component = HTBOTTOMRIGHT; + else + component = HTRIGHT; + } else if (point.y() < top_resize_border_height) { + if (point.x() < resize_corner_width) + component = HTTOPLEFT; + else if (point.x() >= (width() - resize_corner_width)) + component = HTTOPRIGHT; + else + component = HTTOP; + } else if (point.y() >= (height() - resize_border_thickness)) { + if (point.x() < resize_corner_width) + component = HTBOTTOMLEFT; + else if (point.x() >= (width() - resize_corner_width)) + component = HTBOTTOMRIGHT; + else + component = HTBOTTOM; + } else { + return HTNOWHERE; + } + + // If the window can't be resized, there are no resize boundaries, just + // window borders. + return can_resize ? component : HTBORDER; +} + +} // namespace views diff --git a/views/window/non_client_view.h b/views/window/non_client_view.h new file mode 100644 index 0000000..d93f423 --- /dev/null +++ b/views/window/non_client_view.h @@ -0,0 +1,227 @@ +// 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. + +#ifndef VIEWS_WINDOW_NON_CLIENT_VIEW_H_ +#define VIEWS_WINDOW_NON_CLIENT_VIEW_H_ + +#include "base/task.h" +#include "views/view.h" +#include "views/window/client_view.h" + +namespace gfx { +class Path; +} + +namespace views { + +//////////////////////////////////////////////////////////////////////////////// +// NonClientFrameView +// +// An object that subclasses NonClientFrameView is a View that renders and +// responds to events within the frame portions of the non-client area of a +// window. This view does _not_ contain the ClientView, but rather is a sibling +// of it. +class NonClientFrameView : public View { + public: + // Various edges of the frame border have a 1 px shadow along their edges; in + // a few cases we shift elements based on this amount for visual appeal. + static const int kFrameShadowThickness; + // In restored mode, we draw a 1 px edge around the content area inside the + // frame border. + static const int kClientEdgeThickness; + + void DisableInactiveRendering(bool disable) { + paint_as_active_ = disable; + if (!paint_as_active_) + SchedulePaint(); + } + + // Returns the bounds (in this View's parent's coordinates) that the client + // view should be laid out within. + virtual gfx::Rect GetBoundsForClientView() const = 0; + + // Returns true if this FrameView should always use the custom frame, + // regardless of the system settings. An example is the Constrained Window, + // which is a child window and must always provide its own frame. + virtual bool AlwaysUseCustomFrame() const { return false; } + + virtual gfx::Rect GetWindowBoundsForClientBounds( + const gfx::Rect& client_bounds) const = 0; + virtual gfx::Point GetSystemMenuPoint() const = 0; + virtual int NonClientHitTest(const gfx::Point& point) = 0; + virtual void GetWindowMask(const gfx::Size& size, + gfx::Path* window_mask) = 0; + virtual void EnableClose(bool enable) = 0; + virtual void ResetWindowControls() = 0; + + // Overridden from View: + virtual bool HitTest(const gfx::Point& l) const; + + protected: + virtual void DidChangeBounds(const gfx::Rect& previous, + const gfx::Rect& current); + + NonClientFrameView() : paint_as_active_(false) {} + + + // Helper for non-client view implementations to determine which area of the + // window border the specified |point| falls within. The other parameters are + // the size of the sizing edges, and whether or not the window can be + // resized. + int GetHTComponentForFrame(const gfx::Point& point, + int top_resize_border_height, + int resize_border_thickness, + int top_resize_corner_height, + int resize_corner_width, + bool can_resize); + + // Accessor for paint_as_active_. + bool paint_as_active() const { return paint_as_active_; } + + private: + // True when the non-client view should always be rendered as if the window + // were active, regardless of whether or not the top level window actually + // is active. + bool paint_as_active_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// NonClientView +// +// The NonClientView is the logical root of all Views contained within a +// Window, except for the RootView which is its parent and of which it is the +// sole child. The NonClientView has two children, the NonClientFrameView which +// is responsible for painting and responding to events from the non-client +// portions of the window, and the ClientView, which is responsible for the +// same for the client area of the window: +// +// +- views::Window ------------------------------------+ +// | +- views::RootView ------------------------------+ | +// | | +- views::NonClientView ---------------------+ | | +// | | | +- views::NonClientView subclass ---+ | | | +// | | | | | | | | +// | | | | << all painting and event receiving >> | | | | +// | | | | << of the non-client areas of a >> | | | | +// | | | | << views::Window. >> | | | | +// | | | | | | | | +// | | | +----------------------------------------+ | | | +// | | | +- views::ClientView or subclass --------+ | | | +// | | | | | | | | +// | | | | << all painting and event receiving >> | | | | +// | | | | << of the client areas of a >> | | | | +// | | | | << views::Window. >> | | | | +// | | | | | | | | +// | | | +----------------------------------------+ | | | +// | | +--------------------------------------------+ | | +// | +------------------------------------------------+ | +// +----------------------------------------------------+ +// +// The NonClientFrameView and ClientView are siblings because due to theme +// changes the NonClientFrameView may be replaced with different +// implementations (e.g. during the switch from DWM/Aero-Glass to Vista Basic/ +// Classic rendering). +// +class NonClientView : public View { + public: + explicit NonClientView(Window* frame); + virtual ~NonClientView(); + + // Replaces the current NonClientFrameView (if any) with the specified one. + void SetFrameView(NonClientFrameView* frame_view); + + // Returns true if the ClientView determines that the containing window can be + // closed, false otherwise. + bool CanClose() const; + + // Called by the containing Window when it is closed. + void WindowClosing(); + + // Changes the frame from native to custom depending on the value of + // |use_native_frame|. + void SetUseNativeFrame(bool use_native_frame); + + // Returns true if the native window frame should be used, false if the + // NonClientView provides its own frame implementation. + bool UseNativeFrame() const; + + // Prevents the window from being rendered as deactivated when |disable| is + // true, until called with |disable| false. Used when a sub-window is to be + // shown that shouldn't visually de-activate the window. + // Subclasses can override this to perform additional actions when this value + // changes. + void DisableInactiveRendering(bool disable); + + // Returns the bounds of the window required to display the content area at + // the specified bounds. + gfx::Rect GetWindowBoundsForClientBounds(const gfx::Rect client_bounds) const; + + // Returns the point, in screen coordinates, where the system menu should + // be shown so it shows up anchored to the system menu icon. + gfx::Point GetSystemMenuPoint() const; + + // Determines the windows HT* code when the mouse cursor is at the + // specified point, in window coordinates. + int NonClientHitTest(const gfx::Point& point); + + // Returns a mask to be used to clip the top level window for the given + // size. This is used to create the non-rectangular window shape. + void GetWindowMask(const gfx::Size& size, gfx::Path* window_mask); + + // Toggles the enable state for the Close button (and the Close menu item in + // the system menu). + void EnableClose(bool enable); + + // Tells the window controls as rendered by the NonClientView to reset + // themselves to a normal state. This happens in situations where the + // containing window does not receive a normal sequences of messages that + // would lead to the controls returning to this normal state naturally, e.g. + // when the window is maximized, minimized or restored. + void ResetWindowControls(); + + // Get/Set client_view property. + ClientView* client_view() const { return client_view_; } + void set_client_view(ClientView* client_view) { + client_view_ = client_view; + } + + // Layout just the frame view. This is necessary on Windows when non-client + // metrics such as the position of the window controls changes independently + // of a window resize message. + void LayoutFrameView(); + + // NonClientView, View overrides: + virtual gfx::Size GetPreferredSize(); + virtual gfx::Size GetMinimumSize(); + virtual void Layout(); + + protected: + // NonClientView, View overrides: + virtual void ViewHierarchyChanged(bool is_add, View* parent, View* child); + virtual views::View* GetViewForPoint(const gfx::Point& point); + virtual views::View* GetViewForPoint(const gfx::Point& point, + bool can_create_floating); + + private: + // The frame that hosts this NonClientView. + Window* frame_; + + // A ClientView object or subclass, responsible for sizing the contents view + // of the window, hit testing and perhaps other tasks depending on the + // implementation. + ClientView* client_view_; + + // The NonClientFrameView that renders the non-client portions of the window. + // This object is not owned by the view hierarchy because it can be replaced + // dynamically as the system settings change. + scoped_ptr<NonClientFrameView> frame_view_; + + // Whether or not we should use the native frame. + bool use_native_frame_; + + DISALLOW_COPY_AND_ASSIGN(NonClientView); +}; + +} // namespace views + +#endif // #ifndef VIEWS_WINDOW_NON_CLIENT_VIEW_H_ diff --git a/views/window/window.h b/views/window/window.h new file mode 100644 index 0000000..17a2ff5 --- /dev/null +++ b/views/window/window.h @@ -0,0 +1,135 @@ +// Copyright (c) 2009 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. + +#ifndef VIEWS_WINDOW_WINDOW_H_ +#define VIEWS_WINDOW_WINDOW_H_ + +#include "base/gfx/native_widget_types.h" + +namespace gfx { +class Rect; +class Size; +} + +namespace views { + +class ClientView; +class NonClientFrameView; +class NonClientView; +class WindowDelegate; + +// An interface implemented by an object that provides a top level window. +class Window { + public: + virtual ~Window() {} + + // Creates an instance of an object implementing this interface. + static Window* CreateChromeWindow(gfx::NativeWindow parent, + const gfx::Rect& bounds, + WindowDelegate* window_delegate); + + // Returns the preferred size of the contents view of this window based on + // its localized size data. The width in cols is held in a localized string + // resource identified by |col_resource_id|, the height in the same fashion. + // TODO(beng): This should eventually live somewhere else, probably closer to + // ClientView. + static int GetLocalizedContentsWidth(int col_resource_id); + static int GetLocalizedContentsHeight(int row_resource_id); + static gfx::Size GetLocalizedContentsSize(int col_resource_id, + int row_resource_id); + + // Closes all windows that aren't identified as "app windows" via + // IsAppWindow. Called during application shutdown when the last "app window" + // is closed. + static void CloseAllSecondaryWindows(); + + // Retrieves the window's bounds, including its frame. + virtual gfx::Rect GetBounds() const = 0; + + // Retrieves the restored bounds for the window. + virtual gfx::Rect GetNormalBounds() const = 0; + + // Sizes and/or places the window to the specified bounds, size or position. + virtual void SetBounds(const gfx::Rect& bounds) = 0; + + // As above, except the window is inserted after |other_window| in the window + // Z-order. If this window is not yet visible, other_window's monitor is used + // as the constraining rectangle, rather than this window's monitor. + virtual void SetBounds(const gfx::Rect& bounds, + gfx::NativeWindow other_window) = 0; + + // Makes the window visible. + virtual void Show() = 0; + + // Activate the window, assuming it already exists and is visible. + virtual void Activate() = 0; + + // Closes the window, ultimately destroying it. This isn't immediate (it + // occurs after a return to the message loop. Implementors must also make sure + // that invoking Close multiple times doesn't cause bad things to happen, + // since it can happen. + virtual void Close() = 0; + + // Maximizes/minimizes/restores the window. + virtual void Maximize() = 0; + virtual void Minimize() = 0; + virtual void Restore() = 0; + + // Whether or not the window is currently active. + virtual bool IsActive() const = 0; + + // Whether or not the window is currently visible. + virtual bool IsVisible() const = 0; + + // Whether or not the window is maximized or minimized. + virtual bool IsMaximized() const = 0; + virtual bool IsMinimized() const = 0; + + // Accessors for fullscreen state. + virtual void SetFullscreen(bool fullscreen) = 0; + virtual bool IsFullscreen() const = 0; + + // Returns true if the Window is considered to be an "app window" - i.e. + // any window which when it is the last of its type closed causes the + // application to exit. + virtual bool IsAppWindow() const { return false; } + + // Toggles the enable state for the Close button (and the Close menu item in + // the system menu). + virtual void EnableClose(bool enable) = 0; + + // Prevents the window from being rendered as deactivated the next time it is. + // This state is reset automatically as soon as the window becomes activated + // again. There is no ability to control the state through this API as this + // leads to sync problems. + virtual void DisableInactiveRendering() = 0; + + // Tell the window to update its title from the delegate. + virtual void UpdateWindowTitle() = 0; + + // Tell the window to update its icon from the delegate. + virtual void UpdateWindowIcon() = 0; + + // Creates an appropriate NonClientFrameView for this window. + virtual NonClientFrameView* CreateFrameViewForWindow() = 0; + + // Updates the frame after an event caused it to be changed. + virtual void UpdateFrameAfterFrameChange() = 0; + + // Retrieves the Window's delegate. + virtual WindowDelegate* GetDelegate() const = 0; + + // Retrieves the Window's non-client view. + virtual NonClientView* GetNonClientView() const = 0; + + // Retrieves the Window's client view. + virtual ClientView* GetClientView() const = 0; + + // Retrieves the Window's native window handle. + virtual gfx::NativeWindow GetNativeWindow() const = 0; +}; + +} // namespace views + +#endif // #ifndef VIEWS_WINDOW_WINDOW_H_ diff --git a/views/window/window_delegate.cc b/views/window/window_delegate.cc new file mode 100644 index 0000000..7b5f251 --- /dev/null +++ b/views/window/window_delegate.cc @@ -0,0 +1,96 @@ +// 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 "views/window/window_delegate.h" + +// TODO(beng): hrmp. Fix this in http://crbug.com/4406 +#include "chrome/browser/browser_process.h" +#include "chrome/common/pref_service.h" +#include "views/window/client_view.h" +#include "views/window/window.h" +#include "skia/include/SkBitmap.h" + +namespace views { + +WindowDelegate::WindowDelegate() { +} + +WindowDelegate::~WindowDelegate() { + ReleaseWindow(); +} + +// Returns the icon to be displayed in the window. +SkBitmap WindowDelegate::GetWindowIcon() { + return SkBitmap(); +} + +void WindowDelegate::SaveWindowPlacement(const gfx::Rect& bounds, + bool maximized, + bool always_on_top) { + std::wstring window_name = GetWindowName(); + if (window_name.empty() || !g_browser_process->local_state()) + return; + + DictionaryValue* window_preferences = + g_browser_process->local_state()->GetMutableDictionary( + window_name.c_str()); + window_preferences->SetInteger(L"left", bounds.x()); + window_preferences->SetInteger(L"top", bounds.y()); + window_preferences->SetInteger(L"right", bounds.right()); + window_preferences->SetInteger(L"bottom", bounds.bottom()); + window_preferences->SetBoolean(L"maximized", maximized); + window_preferences->SetBoolean(L"always_on_top", always_on_top); +} + +bool WindowDelegate::GetSavedWindowBounds(gfx::Rect* bounds) const { + std::wstring window_name = GetWindowName(); + if (window_name.empty()) + return false; + + const DictionaryValue* dictionary = + g_browser_process->local_state()->GetDictionary(window_name.c_str()); + int left, top, right, bottom; + if (!dictionary || !dictionary->GetInteger(L"left", &left) || + !dictionary->GetInteger(L"top", &top) || + !dictionary->GetInteger(L"right", &right) || + !dictionary->GetInteger(L"bottom", &bottom)) + return false; + + bounds->SetRect(left, top, right - left, bottom - top); + return true; +} + +bool WindowDelegate::GetSavedMaximizedState(bool* maximized) const { + std::wstring window_name = GetWindowName(); + if (window_name.empty()) + return false; + + const DictionaryValue* dictionary = + g_browser_process->local_state()->GetDictionary(window_name.c_str()); + return dictionary && dictionary->GetBoolean(L"maximized", maximized); +} + +bool WindowDelegate::GetSavedAlwaysOnTopState(bool* always_on_top) const { + if (!g_browser_process->local_state()) + return false; + + std::wstring window_name = GetWindowName(); + if (window_name.empty()) + return false; + + const DictionaryValue* dictionary = + g_browser_process->local_state()->GetDictionary(window_name.c_str()); + return dictionary && dictionary->GetBoolean(L"always_on_top", always_on_top); +} + + +ClientView* WindowDelegate::CreateClientView(Window* window) { + return new ClientView(window, GetContentsView()); +} + +void WindowDelegate::ReleaseWindow() { + window_.release(); +} + +} // namespace views diff --git a/views/window/window_delegate.h b/views/window/window_delegate.h new file mode 100644 index 0000000..af8285c --- /dev/null +++ b/views/window/window_delegate.h @@ -0,0 +1,162 @@ +// 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. + +#ifndef VIEWS_WINDOW_WINDOW_DELEGATE_H_ +#define VIEWS_WINDOW_WINDOW_DELEGATE_H_ + +#include <string> + +#include "base/scoped_ptr.h" + +class SkBitmap; + +namespace gfx { +class Rect; +} +// TODO(maruel): Remove once gfx::Rect is used instead. +namespace WTL { +class CRect; +} +using WTL::CRect; + +namespace views { + +class ClientView; +class DialogDelegate; +class View; +class Window; + +/////////////////////////////////////////////////////////////////////////////// +// +// WindowDelegate +// +// WindowDelegate is an interface implemented by objects that wish to show a +// Window. The window that is displayed uses this interface to determine how +// it should be displayed and notify the delegate object of certain events. +// +/////////////////////////////////////////////////////////////////////////////// +class WindowDelegate { + public: + WindowDelegate(); + virtual ~WindowDelegate(); + + virtual DialogDelegate* AsDialogDelegate() { return NULL; } + + // Returns true if the window can ever be resized. + virtual bool CanResize() const { + return false; + } + + // Returns true if the window can ever be maximized. + virtual bool CanMaximize() const { + return false; + } + + // Returns true if the window should be placed on top of all other windows on + // the system, even when it is not active. If HasAlwaysOnTopMenu() returns + // true, then this method is only used the first time the window is opened, it + // is stored in the preferences for next runs. + virtual bool IsAlwaysOnTop() const { + return false; + } + + // Returns whether an "always on top" menu should be added to the system menu + // of the window. + virtual bool HasAlwaysOnTopMenu() const { + return false; + } + + // Returns true if the dialog should be displayed modally to the window that + // opened it. Only windows with WindowType == DIALOG can be modal. + virtual bool IsModal() const { + return false; + } + + // Returns the text to be displayed in the window title. + virtual std::wstring GetWindowTitle() const { + return L""; + } + + // Returns the view that should have the focus when the dialog is opened. If + // NULL no view is focused. + virtual View* GetInitiallyFocusedView() { return NULL; } + + // Returns true if the window should show a title in the title bar. + virtual bool ShouldShowWindowTitle() const { + return true; + } + + // Returns the icon to be displayed in the window. + virtual SkBitmap GetWindowIcon(); + + // Returns true if a window icon should be shown. + virtual bool ShouldShowWindowIcon() const { + return false; + } + + // Execute a command in the window's controller. Returns true if the command + // was handled, false if it was not. + virtual bool ExecuteWindowsCommand(int command_id) { return false; } + + // Returns the window's name identifier. Used to identify this window for + // state restoration. + virtual std::wstring GetWindowName() const { + return std::wstring(); + } + + // Saves the window's bounds, maximized and always-on-top states. By default + // this uses the process' local state keyed by window name (See GetWindowName + // above). This behavior can be overridden to provide additional + // functionality. + virtual void SaveWindowPlacement(const gfx::Rect& bounds, + bool maximized, + bool always_on_top); + + // Retrieves the window's bounds, maximized and always-on-top states. By + // default, this uses the process' local state keyed by window name (See + // GetWindowName above). This behavior can be overridden to provide + // additional functionality. + virtual bool GetSavedWindowBounds(gfx::Rect* bounds) const; + virtual bool GetSavedMaximizedState(bool* maximized) const; + virtual bool GetSavedAlwaysOnTopState(bool* always_on_top) const; + + // Called when the window closes. + virtual void WindowClosing() { } + + // Called when the window is destroyed. No events must be sent or received + // after this point. The delegate can use this opportunity to delete itself at + // this time if necessary. + virtual void DeleteDelegate() { } + + // Returns the View that is contained within this Window. + virtual View* GetContentsView() { + return NULL; + } + + // Called by the Window to create the Client View used to host the contents + // of the window. + virtual ClientView* CreateClientView(Window* window); + + // An accessor to the Window this delegate is bound to. + Window* window() const { return window_.get(); } + + protected: + // Releases the Window* we maintain. This should be done by a delegate in its + // WindowClosing handler if it intends to be re-cycled to be used on a + // different Window. + void ReleaseWindow(); + + private: + friend class WindowWin; + // This is a little unusual. We use a scoped_ptr here because it's + // initialized to NULL automatically. We do this because we want to allow + // people using this helper to not have to call a ctor on this object. + // Instead we just release the owning ref this pointer has when we are + // destroyed. + scoped_ptr<Window> window_; +}; + +} // namespace views + +#endif // VIEWS_WINDOW_WINDOW_DELEGATE_H_ diff --git a/views/window/window_resources.h b/views/window/window_resources.h new file mode 100644 index 0000000..e79476e --- /dev/null +++ b/views/window/window_resources.h @@ -0,0 +1,30 @@ +// 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. + +#ifndef VIEWS_WINDOW_WINDOW_RESOURCES_H_ +#define VIEWS_WINDOW_WINDOW_RESOURCES_H_ + +class SkBitmap; + +namespace views { + +typedef int FramePartBitmap; + +/////////////////////////////////////////////////////////////////////////////// +// WindowResources +// +// An interface implemented by an object providing bitmaps to render the +// contents of a window frame. The Window may swap in different +// implementations of this interface to render different modes. The definition +// of FramePartBitmap depends on the implementation. +// +class WindowResources { + public: + virtual ~WindowResources() { } + virtual SkBitmap* GetPartBitmap(FramePartBitmap part) const = 0; +}; + +} // namespace views + +#endif // VIEWS_WINDOW_WINDOW_RESOURCES_H_ diff --git a/views/window/window_win.cc b/views/window/window_win.cc new file mode 100644 index 0000000..f41ef18 --- /dev/null +++ b/views/window/window_win.cc @@ -0,0 +1,1446 @@ +// 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 "views/window/window_win.h" + +#include <shellapi.h> + +#include "app/gfx/chrome_canvas.h" +#include "app/gfx/chrome_font.h" +#include "app/gfx/icon_util.h" +#include "app/gfx/path.h" +#include "app/l10n_util.h" +#include "app/resource_bundle.h" +#include "base/win_util.h" +#include "chrome/app/chrome_dll_resource.h" +#include "chrome/common/win_util.h" +#include "grit/generated_resources.h" +#include "views/widget/root_view.h" +#include "views/window/client_view.h" +#include "views/window/custom_frame_view.h" +#include "views/window/native_frame_view.h" +#include "views/window/non_client_view.h" +#include "views/window/window_delegate.h" + +namespace { + +bool GetMonitorAndRects(const RECT& rect, + HMONITOR* monitor, + gfx::Rect* monitor_rect, + gfx::Rect* work_area) { + DCHECK(monitor); + DCHECK(monitor_rect); + DCHECK(work_area); + *monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONULL); + if (!*monitor) + return false; + MONITORINFO monitor_info = { 0 }; + monitor_info.cbSize = sizeof(monitor_info); + GetMonitorInfo(*monitor, &monitor_info); + *monitor_rect = monitor_info.rcMonitor; + *work_area = monitor_info.rcWork; + return true; +} + +} // namespace + +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 WindowWin::ScopedRedrawLock { + public: + explicit ScopedRedrawLock(WindowWin* window) : window_(window) { + window_->LockUpdates(); + } + + ~ScopedRedrawLock() { + window_->UnlockUpdates(); + } + + private: + // The window having its style changed. + WindowWin* window_; +}; + +HCURSOR WindowWin::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; + +//////////////////////////////////////////////////////////////////////////////// +// WindowWin, public: + +WindowWin::~WindowWin() { +} + +// static +Window* Window::CreateChromeWindow(gfx::NativeWindow parent, + const gfx::Rect& bounds, + WindowDelegate* window_delegate) { + WindowWin* window = new WindowWin(window_delegate); + window->GetNonClientView()->SetFrameView(window->CreateFrameViewForWindow()); + window->Init(parent, bounds); + return window; +} + +gfx::Rect WindowWin::GetBounds() const { + gfx::Rect bounds; + WidgetWin::GetBounds(&bounds, true); + return bounds; +} + +gfx::Rect WindowWin::GetNormalBounds() const { + // If we're in fullscreen mode, we've changed the normal bounds to the monitor + // rect, so return the saved bounds instead. + if (IsFullscreen()) + return gfx::Rect(saved_window_info_.window_rect); + + WINDOWPLACEMENT wp; + wp.length = sizeof(wp); + const bool ret = !!GetWindowPlacement(GetNativeView(), &wp); + DCHECK(ret); + return gfx::Rect(wp.rcNormalPosition); +} + +void WindowWin::SetBounds(const gfx::Rect& bounds) { + SetBounds(bounds, NULL); +} + +void WindowWin::SetBounds(const gfx::Rect& bounds, + gfx::NativeWindow other_window) { + win_util::SetChildBounds(GetNativeView(), GetParent(), other_window, bounds, + kMonitorEdgePadding, 0); +} + +void WindowWin::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 WindowWin::GetShowState() const { + return SW_SHOWNORMAL; +} + +void WindowWin::ExecuteSystemMenuCommand(int command) { + if (command) + SendMessage(GetNativeView(), WM_SYSCOMMAND, command, 0); +} + +void WindowWin::PushForceHidden() { + if (force_hidden_count_++ == 0) + Hide(); +} + +void WindowWin::PopForceHidden() { + if (--force_hidden_count_ <= 0) { + force_hidden_count_ = 0; + ShowWindow(SW_SHOW); + } +} + +// 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)); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowWin, Window implementation: + +void WindowWin::Show() { + int show_state = GetShowState(); + if (saved_maximized_state_) + show_state = SW_SHOWMAXIMIZED; + Show(show_state); +} + +void WindowWin::Activate() { + if (IsMinimized()) + ::ShowWindow(GetNativeView(), SW_RESTORE); + ::SetWindowPos(GetNativeView(), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); + SetForegroundWindow(GetNativeView()); +} + +void WindowWin::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; + } +} + +void WindowWin::Maximize() { + ExecuteSystemMenuCommand(SC_MAXIMIZE); +} + +void WindowWin::Minimize() { + ExecuteSystemMenuCommand(SC_MINIMIZE); +} + +void WindowWin::Restore() { + ExecuteSystemMenuCommand(SC_RESTORE); +} + +bool WindowWin::IsActive() const { + return is_active_; +} + +bool WindowWin::IsVisible() const { + return !!::IsWindowVisible(GetNativeView()); +} + +bool WindowWin::IsMaximized() const { + return !!::IsZoomed(GetNativeView()); +} + +bool WindowWin::IsMinimized() const { + return !!::IsIconic(GetNativeView()); +} + +void WindowWin::SetFullscreen(bool fullscreen) { + if (fullscreen_ == fullscreen) + return; // Nothing to do. + + // Reduce jankiness during the following position changes by hiding the window + // until it's in the final position. + PushForceHidden(); + + // Size/position/style window appropriately. + if (!fullscreen_) { + // Save current window information. We force the window into restored mode + // before going fullscreen because Windows doesn't seem to hide the + // taskbar if the window is in the maximized state. + saved_window_info_.maximized = IsMaximized(); + if (saved_window_info_.maximized) + Restore(); + saved_window_info_.style = GetWindowLong(GWL_STYLE); + saved_window_info_.ex_style = GetWindowLong(GWL_EXSTYLE); + GetWindowRect(&saved_window_info_.window_rect); + } + + // Toggle fullscreen mode. + fullscreen_ = fullscreen; + + if (fullscreen_) { + // Set new window style and size. + MONITORINFO monitor_info; + monitor_info.cbSize = sizeof(monitor_info); + GetMonitorInfo(MonitorFromWindow(GetNativeView(), MONITOR_DEFAULTTONEAREST), + &monitor_info); + gfx::Rect monitor_rect(monitor_info.rcMonitor); + SetWindowLong(GWL_STYLE, + saved_window_info_.style & ~(WS_CAPTION | WS_THICKFRAME)); + SetWindowLong(GWL_EXSTYLE, + saved_window_info_.ex_style & ~(WS_EX_DLGMODALFRAME | + WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE)); + SetWindowPos(NULL, monitor_rect.x(), monitor_rect.y(), + monitor_rect.width(), monitor_rect.height(), + SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + } else { + // Reset original window style and size. The multiple window size/moves + // here are ugly, but if SetWindowPos() doesn't redraw, the taskbar won't be + // repainted. Better-looking methods welcome. + gfx::Rect new_rect(saved_window_info_.window_rect); + SetWindowLong(GWL_STYLE, saved_window_info_.style); + SetWindowLong(GWL_EXSTYLE, saved_window_info_.ex_style); + SetWindowPos(NULL, new_rect.x(), new_rect.y(), new_rect.width(), + new_rect.height(), + SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + if (saved_window_info_.maximized) + Maximize(); + } + + // Undo our anti-jankiness hacks. + PopForceHidden(); +} + +bool WindowWin::IsFullscreen() const { + return fullscreen_; +} + +void WindowWin::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 WindowWin::DisableInactiveRendering() { + disable_inactive_rendering_ = true; + non_client_view_->DisableInactiveRendering(disable_inactive_rendering_); +} + +void WindowWin::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 WindowWin::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); + } +} + +NonClientFrameView* WindowWin::CreateFrameViewForWindow() { + if (non_client_view_->UseNativeFrame()) + return new NativeFrameView(this); + return new CustomFrameView(this); +} + +void WindowWin::UpdateFrameAfterFrameChange() { + // We've either gained or lost a custom window region, so reset it now. + ResetWindowRegion(true); +} + +WindowDelegate* WindowWin::GetDelegate() const { + return window_delegate_; +} + +NonClientView* WindowWin::GetNonClientView() const { + return non_client_view_; +} + +ClientView* WindowWin::GetClientView() const { + return non_client_view_->client_view(); +} + +gfx::NativeWindow WindowWin::GetNativeWindow() const { + return GetNativeView(); +} + +/////////////////////////////////////////////////////////////////////////////// +// WindowWin, protected: + +WindowWin::WindowWin(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), + fullscreen_(false), + window_closed_(false), + disable_inactive_rendering_(false), + is_active_(false), + lock_updates_(false), + saved_window_style_(0), + saved_maximized_state_(0), + ignore_window_pos_changes_(false), + ignore_pos_changes_factory_(this), + force_hidden_count_(0), + last_monitor_(NULL) { + is_window_ = true; + 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 WindowWin::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(); + + GetMonitorAndRects(bounds.ToRECT(), &last_monitor_, &last_monitor_rect_, + &last_work_area_); + ResetWindowRegion(false); +} + +void WindowWin::SizeWindowToDefault() { + win_util::CenterAndSizeWindow(owning_window(), GetNativeView(), + non_client_view_->GetPreferredSize().ToSIZE(), + false); +} + +void WindowWin::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); + UINT flags = TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD; + if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) + flags |= TPM_RIGHTALIGN; + HMENU system_menu = ::GetSystemMenu(GetNativeView(), FALSE); + int id = ::TrackPopupMenu(system_menu, flags, + point.x(), point.y(), 0, GetNativeView(), NULL); + ExecuteSystemMenuCommand(id); +} + +/////////////////////////////////////////////////////////////////////////////// +// WindowWin, WidgetWin overrides: + +void WindowWin::OnActivate(UINT action, BOOL minimized, HWND window) { + if (action == WA_INACTIVE) + SaveWindowPosition(); +} + +void WindowWin::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 WindowWin::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 WindowWin::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 WindowWin::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 WindowWin::OnDwmCompositionChanged(UINT msg, WPARAM w_param, + LPARAM l_param) { + // The window may try to paint in SetUseNativeFrame, and as a result it can + // get into a state where it is very unhappy with itself - rendering black + // behind the entire client area. This is because for some reason the + // SkPorterDuff::kClear_mode erase done in the RootView thinks the window is + // still opaque. So, to work around this we hide the window as soon as we can + // (now), saving off its placement so it can be properly restored once + // everything has settled down. + WINDOWPLACEMENT saved_window_placement; + saved_window_placement.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(GetNativeView(), &saved_window_placement); + Hide(); + + // Important step: restore the window first, since our hiding hack doesn't + // work for maximized windows! We tell the frame not to allow itself to be + // made visible though, which removes the brief flicker. + ++force_hidden_count_; + ::ShowWindow(GetNativeView(), SW_RESTORE); + --force_hidden_count_; + + // 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_->SetUseNativeFrame(win_util::ShouldUseVistaFrame()); + + // Now that we've updated the frame, we'll want to restore our saved placement + // since the display should have settled down and we can be properly rendered. + SetWindowPlacement(GetNativeView(), &saved_window_placement); + + // 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 WindowWin::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); +} + +void WindowWin::OnGetMinMaxInfo(MINMAXINFO* minmax_info) { + gfx::Size min_window_size(GetNonClientView()->GetMinimumSize()); + minmax_info->ptMinTrackSize.x = min_window_size.width(); + minmax_info->ptMinTrackSize.y = min_window_size.height(); + WidgetWin::OnGetMinMaxInfo(minmax_info); +} + +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 WindowWin::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_fullscreen = IsFullscreen(); + bool is_minimized = IsMinimized(); + bool is_maximized = IsMaximized(); + bool is_restored = !is_fullscreen && !is_minimized && !is_maximized; + + ScopedRedrawLock lock(this); + EnableMenuItem(menu, SC_RESTORE, is_minimized || is_maximized); + EnableMenuItem(menu, SC_MOVE, is_restored); + EnableMenuItem(menu, SC_SIZE, window_delegate_->CanResize() && is_restored); + EnableMenuItem(menu, SC_MAXIMIZE, + window_delegate_->CanMaximize() && !is_fullscreen && !is_maximized); + EnableMenuItem(menu, SC_MINIMIZE, + window_delegate_->CanMaximize() && !is_minimized); +} + +void WindowWin::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 WindowWin::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 (IsActive()) + 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 WindowWin::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_DEFAULTTONULL); + if (win_util::EdgeHasTopmostAutoHideTaskbar(ABE_LEFT, monitor)) + client_rect->left += win_util::kAutoHideTaskbarThicknessPx; + if (win_util::EdgeHasTopmostAutoHideTaskbar(ABE_TOP, monitor)) + client_rect->top += win_util::kAutoHideTaskbarThicknessPx; + if (win_util::EdgeHasTopmostAutoHideTaskbar(ABE_RIGHT, monitor)) + client_rect->right -= win_util::kAutoHideTaskbarThicknessPx; + if (win_util::EdgeHasTopmostAutoHideTaskbar(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 WindowWin::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 WindowWin::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(); + gfx::Rect old_paint_region = + root_view->GetScheduledPaintRectConstrainedToSize(); + + if (!old_paint_region.IsEmpty()) { + // The root view has a region that needs to be painted. Include it in the + // region we're going to paint. + + CRect old_paint_region_crect = old_paint_region.ToRECT(); + CRect tmp = dirty_region; + UnionRect(&dirty_region, &tmp, &old_paint_region_crect); + } + + 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 WindowWin::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()) { + // WindowWin::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 WindowWin::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 WindowWin::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 WindowWin::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 WindowWin::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 WindowWin::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 WindowWin::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 WindowWin::OnSettingChange(UINT flags, const wchar_t* section) { + if (!GetParent() && (flags == SPI_SETWORKAREA)) { + // Fire a dummy SetWindowPos() call, so we'll trip the code in + // OnWindowPosChanging() below that notices work area changes. + ::SetWindowPos(GetNativeView(), 0, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | + SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE | SWP_NOOWNERZORDER); + SetMsgHandled(TRUE); + } else { + WidgetWin::OnSettingChange(flags, section); + } +} + +void WindowWin::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 WindowWin::OnSysCommand(UINT notification_code, CPoint click) { + // 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; + // Ignore size/move/maximize in fullscreen mode. + if (IsFullscreen() && + (((notification_code & sc_mask) == SC_SIZE) || + ((notification_code & sc_mask) == SC_MOVE) || + ((notification_code & sc_mask) == SC_MAXIMIZE))) + return; + if (!non_client_view_->UseNativeFrame()) { + 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 WindowWin::OnWindowPosChanging(WINDOWPOS* window_pos) { + if (force_hidden_count_) { + // 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; + } + + if (ignore_window_pos_changes_) { + // If somebody's trying to toggle our visibility, change the nonclient area, + // change our Z-order, or activate us, we should probably let it go through. + if (!(window_pos->flags & ((IsVisible() ? SWP_HIDEWINDOW : SWP_SHOWWINDOW) | + SWP_FRAMECHANGED)) && + (window_pos->flags & (SWP_NOZORDER | SWP_NOACTIVATE))) { + // Just sizing/moving the window; ignore. + window_pos->flags |= SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW; + window_pos->flags &= ~(SWP_SHOWWINDOW | SWP_HIDEWINDOW); + } + } else if (!GetParent()) { + CRect window_rect; + HMONITOR monitor; + gfx::Rect monitor_rect, work_area; + if (GetWindowRect(&window_rect) && + GetMonitorAndRects(window_rect, &monitor, &monitor_rect, &work_area)) { + if (monitor && (monitor == last_monitor_) && + (IsFullscreen() || ((monitor_rect == last_monitor_rect_) && + (work_area != last_work_area_)))) { + // A rect for the monitor we're on changed. Normally Windows notifies + // us about this (and thus we're reaching here due to the SetWindowPos() + // call in OnSettingChange() above), but with some software (e.g. + // nVidia's nView desktop manager) the work area can change asynchronous + // to any notification, and we're just sent a SetWindowPos() call with a + // new (frequently incorrect) position/size. In either case, the best + // response is to throw away the existing position/size information in + // |window_pos| and recalculate it based on the new work rect. + gfx::Rect new_window_rect; + if (IsFullscreen()) { + new_window_rect = monitor_rect; + } else if (IsZoomed()) { + new_window_rect = work_area; + int border_thickness = GetSystemMetrics(SM_CXSIZEFRAME); + new_window_rect.Inset(-border_thickness, -border_thickness); + } else { + new_window_rect = gfx::Rect(window_rect).AdjustToFit(work_area); + } + window_pos->x = new_window_rect.x(); + window_pos->y = new_window_rect.y(); + window_pos->cx = new_window_rect.width(); + window_pos->cy = new_window_rect.height(); + // WARNING! Don't set SWP_FRAMECHANGED here, it breaks moving the child + // HWNDs for some reason. + window_pos->flags &= ~(SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW); + window_pos->flags |= SWP_NOCOPYBITS; + + // Now ignore all immediately-following SetWindowPos() changes. Windows + // likes to (incorrectly) recalculate what our position/size should be + // and send us further updates. + ignore_window_pos_changes_ = true; + DCHECK(ignore_pos_changes_factory_.empty()); + MessageLoop::current()->PostTask(FROM_HERE, + ignore_pos_changes_factory_.NewRunnableMethod( + &WindowWin::StopIgnoringPosChanges)); + } + last_monitor_ = monitor; + last_monitor_rect_ = monitor_rect; + last_work_area_ = work_area; + } + } + + WidgetWin::OnWindowPosChanging(window_pos); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowWin, private: + +void WindowWin::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 WindowWin::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 WindowWin::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 WindowWin::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 WindowWin::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 WindowWin::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 WindowWin::AlwaysOnTopChanged() { + ::SetWindowPos(GetNativeView(), + is_always_on_top_ ? HWND_TOPMOST : HWND_NOTOPMOST, + 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); +} + +DWORD WindowWin::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 WindowWin::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 WindowWin::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 WindowWin::LockUpdates() { + lock_updates_ = true; + saved_window_style_ = GetWindowLong(GWL_STYLE); + SetWindowLong(GWL_STYLE, saved_window_style_ & ~WS_VISIBLE); +} + +void WindowWin::UnlockUpdates() { + SetWindowLong(GWL_STYLE, saved_window_style_); + lock_updates_ = false; +} + +void WindowWin::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 WindowWin::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, false); +} + +LRESULT WindowWin::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 WindowWin::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 { +// static +static BOOL CALLBACK WindowCallbackProc(HWND hwnd, LPARAM lParam) { + WidgetWin* widget = reinterpret_cast<WidgetWin*>( + win_util::GetWindowUserData(hwnd)); + if (!widget) + return TRUE; + + // If the toplevel HWND is a Window, close it if it's identified as a + // secondary window. + Window* window = widget->GetWindow(); + if (window) { + if (!window->IsAppWindow()) + window->Close(); + } else { + // If it's not a Window, then close it anyway since it probably is + // secondary. + widget->Close(); + } + return TRUE; +} +} // namespace + +void Window::CloseAllSecondaryWindows() { + EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc, 0); +} + +} // namespace views diff --git a/views/window/window_win.h b/views/window/window_win.h new file mode 100644 index 0000000..76d5196 --- /dev/null +++ b/views/window/window_win.h @@ -0,0 +1,307 @@ +// 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. + +#ifndef VIEWS_WINDOW_WINDOW_WIN_H_ +#define VIEWS_WINDOW_WINDOW_WIN_H_ + +#include "views/widget/widget_win.h" +#include "views/window/client_view.h" +#include "views/window/non_client_view.h" +#include "views/window/window.h" + +namespace gfx { +class Point; +class Size; +}; + +namespace views { + +class Client; +class WindowDelegate; + +/////////////////////////////////////////////////////////////////////////////// +// +// WindowWin +// +// A WindowWin is a WidgetWin that has a caption and a border. The frame is +// rendered by the operating system. +// +/////////////////////////////////////////////////////////////////////////////// +class WindowWin : public WidgetWin, + public Window { + public: + virtual ~WindowWin(); + + // Show the window with the specified show command. + void Show(int show_state); + + // Retrieve the show state of the window. This is one of the SW_SHOW* flags + // passed into Windows' ShowWindow method. For normal windows this defaults + // to SW_SHOWNORMAL, however windows (e.g. the main window) can override this + // method to provide different values (e.g. retrieve the user's specified + // show state from the shortcut starutp info). + virtual int GetShowState() const; + + // Executes the specified SC_command. + void ExecuteSystemMenuCommand(int command); + + // Hides the window if it hasn't already been force-hidden, then increments + // |force_hidden_count_| to prevent it from being shown again until + // PopForceHidden()) is called. + void PushForceHidden(); + + // Decrements |force_hidden_count_| and, if it is now zero, shows the window. + void PopForceHidden(); + + // Accessors and setters for various properties. + HWND owning_window() const { return owning_hwnd_; } + void set_focus_on_creation(bool focus_on_creation) { + focus_on_creation_ = focus_on_creation; + } + + // Window overrides: + virtual gfx::Rect GetBounds() const; + virtual gfx::Rect GetNormalBounds() const; + virtual void SetBounds(const gfx::Rect& bounds); + virtual void SetBounds(const gfx::Rect& bounds, + gfx::NativeWindow other_window); + virtual void Show(); + virtual void Activate(); + virtual void Close(); + virtual void Maximize(); + virtual void Minimize(); + virtual void Restore(); + virtual bool IsActive() const; + virtual bool IsVisible() const; + virtual bool IsMaximized() const; + virtual bool IsMinimized() const; + virtual void SetFullscreen(bool fullscreen); + virtual bool IsFullscreen() const; + virtual void EnableClose(bool enable); + virtual void DisableInactiveRendering(); + virtual void UpdateWindowTitle(); + virtual void UpdateWindowIcon(); + virtual NonClientFrameView* CreateFrameViewForWindow(); + virtual void UpdateFrameAfterFrameChange(); + virtual WindowDelegate* GetDelegate() const; + virtual NonClientView* GetNonClientView() const; + virtual ClientView* GetClientView() const; + virtual gfx::NativeWindow GetNativeWindow() const; + + protected: + friend Window; + + // Constructs the WindowWin. |window_delegate| cannot be NULL. + explicit WindowWin(WindowDelegate* window_delegate); + + // Create the Window. + // If parent is NULL, this WindowWin is top level on the desktop. + // If |bounds| is empty, the view is queried for its preferred size and + // centered on screen. + virtual void Init(HWND parent, const gfx::Rect& bounds); + + // Sizes the window to the default size specified by its ClientView. + virtual void SizeWindowToDefault(); + + // Shows the system menu at the specified screen point. + void RunSystemMenu(const gfx::Point& point); + + // Overridden from WidgetWin: + virtual void OnActivate(UINT action, BOOL minimized, HWND window); + virtual void OnActivateApp(BOOL active, DWORD thread_id); + virtual LRESULT OnAppCommand(HWND window, short app_command, WORD device, + int keystate); + virtual void OnCommand(UINT notification_code, int command_id, HWND window); + virtual void OnDestroy(); + virtual LRESULT OnDwmCompositionChanged(UINT msg, WPARAM w_param, + LPARAM l_param); + virtual void OnFinalMessage(HWND window); + virtual void OnGetMinMaxInfo(MINMAXINFO* minmax_info); + virtual void OnInitMenu(HMENU menu); + virtual void OnMouseLeave(); + virtual LRESULT OnNCActivate(BOOL active); + virtual LRESULT OnNCCalcSize(BOOL mode, LPARAM l_param); + virtual LRESULT OnNCHitTest(const CPoint& point); + virtual void OnNCPaint(HRGN rgn); + virtual void OnNCLButtonDown(UINT ht_component, const CPoint& point); + virtual void OnNCRButtonDown(UINT ht_component, const CPoint& point); + virtual LRESULT OnNCUAHDrawCaption(UINT msg, WPARAM w_param, LPARAM l_param); + virtual LRESULT OnNCUAHDrawFrame(UINT msg, WPARAM w_param, LPARAM l_param); + virtual LRESULT OnSetCursor(HWND window, UINT hittest_code, UINT message); + virtual LRESULT OnSetIcon(UINT size_type, HICON new_icon); + virtual LRESULT OnSetText(const wchar_t* text); + virtual void OnSettingChange(UINT flags, const wchar_t* section); + virtual void OnSize(UINT size_param, const CSize& new_size); + virtual void OnSysCommand(UINT notification_code, CPoint click); + virtual void OnWindowPosChanging(WINDOWPOS* window_pos); + virtual Window* GetWindow() { return this; } + virtual const Window* GetWindow() const { return this; } + + // Accessor for disable_inactive_rendering_. + bool disable_inactive_rendering() const { + return disable_inactive_rendering_; + } + + private: + // Information saved before going into fullscreen mode, used to restore the + // window afterwards. + struct SavedWindowInfo { + bool maximized; + LONG style; + LONG ex_style; + RECT window_rect; + }; + + // Set the window as modal (by disabling all the other windows). + void BecomeModal(); + + // Sets-up the focus manager with the view that should have focus when the + // window is shown the first time. If NULL is returned, the focus goes to the + // button if there is one, otherwise the to the Cancel button. + void SetInitialFocus(); + + // Place and size the window when it is created. |create_bounds| are the + // bounds used when the window was created. + void SetInitialBounds(const gfx::Rect& create_bounds); + + // Restore saved always on stop state and add the always on top system menu + // if needed. + void InitAlwaysOnTopState(); + + // Add an item for "Always on Top" to the System Menu. + void AddAlwaysOnTopSystemMenuItem(); + + // If necessary, enables all ancestors. + void RestoreEnabledIfNecessary(); + + // Update the window style to reflect the always on top state. + void AlwaysOnTopChanged(); + + // Calculate the appropriate window styles for this window. + DWORD CalculateWindowStyle(); + DWORD CalculateWindowExStyle(); + + // Asks the delegate if any to save the window's location and size. + void SaveWindowPosition(); + + // Lock or unlock the window from being able to redraw itself in response to + // updates to its invalid region. + class ScopedRedrawLock; + void LockUpdates(); + void UnlockUpdates(); + + // Stops ignoring SetWindowPos() requests (see below). + void StopIgnoringPosChanges() { ignore_window_pos_changes_ = false; } + + // Resets the window region for the current window bounds if necessary. + // If |force| is true, the window region is reset to NULL even for native + // frame windows. + void ResetWindowRegion(bool force); + + // Converts a non-client mouse down message to a regular ChromeViews event + // and handle it. |point| is the mouse position of the message in screen + // coords. |flags| are flags that would be passed with a WM_L/M/RBUTTON* + // message and relate to things like which button was pressed. These are + // combined with flags relating to the current key state. + void ProcessNCMousePress(const CPoint& point, int flags); + + // Calls the default WM_NCACTIVATE handler with the specified activation + // value, safely wrapping the call in a ScopedRedrawLock to prevent frame + // flicker. + LRESULT CallDefaultNCActivateHandler(BOOL active); + + // Static resource initialization. + static void InitClass(); + enum ResizeCursor { + RC_NORMAL = 0, RC_VERTICAL, RC_HORIZONTAL, RC_NESW, RC_NWSE + }; + static HCURSOR resize_cursors_[6]; + + // Our window delegate (see Init method for documentation). + WindowDelegate* window_delegate_; + + // The View that provides the non-client area of the window (title bar, + // window controls, sizing borders etc). To use an implementation other than + // the default, this class must be subclassed and this value set to the + // desired implementation before calling |Init|. + NonClientView* non_client_view_; + + // Whether we should SetFocus() on a newly created window after + // Init(). Defaults to true. + bool focus_on_creation_; + + // We need to save the parent window that spawned us, since GetParent() + // returns NULL for dialogs. + HWND owning_hwnd_; + + // The smallest size the window can be. + CSize minimum_size_; + + // Whether or not the window is modal. This comes from the delegate and is + // cached at Init time to avoid calling back to the delegate from the + // destructor. + bool is_modal_; + + // Whether all ancestors have been enabled. This is only used if is_modal_ is + // true. + bool restored_enabled_; + + // Whether the window is currently always on top. + bool is_always_on_top_; + + // We need to own the text of the menu, the Windows API does not copy it. + std::wstring always_on_top_menu_text_; + + // True if we're in fullscreen mode. + bool fullscreen_; + + // Saved window information from before entering fullscreen mode. + SavedWindowInfo saved_window_info_; + + // Set to true if the window is in the process of closing . + bool window_closed_; + + // True when the window should be rendered as active, regardless of whether + // or not it actually is. + bool disable_inactive_rendering_; + + // True if this window is the active top level window. + bool is_active_; + + // True if updates to this window are currently locked. + bool lock_updates_; + + // The window styles of the window before updates were locked. + DWORD saved_window_style_; + + // The saved maximized state for this window. See note in SetInitialBounds + // that explains why we save this. + bool saved_maximized_state_; + + // When true, this flag makes us discard incoming SetWindowPos() requests that + // only change our position/size. (We still allow changes to Z-order, + // activation, etc.) + bool ignore_window_pos_changes_; + + // The following factory is used to ignore SetWindowPos() calls for short time + // periods. + ScopedRunnableMethodFactory<WindowWin> ignore_pos_changes_factory_; + + // If this is greater than zero, we should prevent attempts to make the window + // visible when we handle WM_WINDOWPOSCHANGING. Some calls like + // ShowWindow(SW_RESTORE) make the window visible in addition to restoring it, + // when all we want to do is restore it. + int force_hidden_count_; + + // The last-seen monitor containing us, and its rect and work area. These are + // used to catch updates to the rect and work area and react accordingly. + HMONITOR last_monitor_; + gfx::Rect last_monitor_rect_, last_work_area_; + + DISALLOW_COPY_AND_ASSIGN(WindowWin); +}; + +} // namespace views + +#endif // VIEWS_WINDOW_WINDOW_WIN_H_ |