diff options
Diffstat (limited to 'chrome/browser/views/info_bubble.cc')
-rw-r--r-- | chrome/browser/views/info_bubble.cc | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/chrome/browser/views/info_bubble.cc b/chrome/browser/views/info_bubble.cc new file mode 100644 index 0000000..f239684 --- /dev/null +++ b/chrome/browser/views/info_bubble.cc @@ -0,0 +1,445 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/views/info_bubble.h" + +#include "base/win_util.h" +#include "chrome/app/theme/theme_resources.h" +#include "chrome/browser/chrome_frame.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/path.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/win_util.h" +#include "chrome/views/focus_manager.h" + +using ChromeViews::View; + +// All sizes are in pixels. + +// Size of the border, along each edge. +static const int kBorderSize = 1; + +// Size of the arrow. +static const int kArrowSize = 5; + +// Number of pixels to the start of the arrow from the edge of the window. +static const int kArrowXOffset = 13; + +// Number of pixels between the tip of the arrow and the region we're +// pointing to. +static const int kArrowToContentPadding = -4; + +// Background color of the bubble. +static const SkColor kBackgroundColor = SK_ColorWHITE; + +// Color of the border and arrow. +static const SkColor kBorderColor1 = SkColorSetRGB(99, 99, 99); +// Border shadow color. +static const SkColor kBorderColor2 = SkColorSetRGB(160, 160, 160); + +// Intended dimensions of the bubble's corner images. If you update these, +// make sure that the OnSize code works. +static const int kInfoBubbleCornerWidth = 3; +static const int kInfoBubbleCornerHeight = 3; + +// Bubble corner images. +static const SkBitmap* kInfoBubbleCornerTopLeft = NULL; +static const SkBitmap* kInfoBubbleCornerTopRight = NULL; +static const SkBitmap* kInfoBubbleCornerBottomLeft = NULL; +static const SkBitmap* kInfoBubbleCornerBottomRight = NULL; + +// Margins around the content. +static const int kInfoBubbleViewTopMargin = 6; +static const int kInfoBubbleViewBottomMargin = 9; +static const int kInfoBubbleViewLeftMargin = 6; +static const int kInfoBubbleViewRightMargin = 6; + +// The minimum alpha the bubble can be - because we're using a simple layered +// window (in order to get window-level alpha at the same time as using native +// controls), the window's drop shadow doesn't fade; this means if we went +// to zero alpha, you'd see a drop shadow outline against nothing. +static const int kMinimumAlpha = 72; + +// InfoBubble ----------------------------------------------------------------- + +// static +InfoBubble* InfoBubble::Show(HWND parent_hwnd, + const gfx::Rect& position_relative_to, + ChromeViews::View* content, + InfoBubbleDelegate* delegate) { + InfoBubble* window = new InfoBubble(); + window->Init(parent_hwnd, position_relative_to, content); + ChromeFrame* frame = window->GetHostingFrame(); + if (frame) + frame->InfoBubbleShowing(); + window->ShowWindow(SW_SHOW); + window->delegate_ = delegate; + return window; +} + +InfoBubble::InfoBubble() : content_view_(NULL) { +} + +InfoBubble::~InfoBubble() { +} + +void InfoBubble::Init(HWND parent_hwnd, + const gfx::Rect& position_relative_to, + ChromeViews::View* content) { + if (kInfoBubbleCornerTopLeft == NULL) { + kInfoBubbleCornerTopLeft = ResourceBundle::GetSharedInstance() + .GetBitmapNamed(IDR_INFO_BUBBLE_CORNER_TOP_LEFT); + kInfoBubbleCornerTopRight = ResourceBundle::GetSharedInstance() + .GetBitmapNamed(IDR_INFO_BUBBLE_CORNER_TOP_RIGHT); + kInfoBubbleCornerBottomLeft = ResourceBundle::GetSharedInstance() + .GetBitmapNamed(IDR_INFO_BUBBLE_CORNER_BOTTOM_LEFT); + kInfoBubbleCornerBottomRight = ResourceBundle::GetSharedInstance() + .GetBitmapNamed(IDR_INFO_BUBBLE_CORNER_BOTTOM_RIGHT); + } + set_window_style(WS_POPUP | WS_CLIPCHILDREN); + set_window_ex_style(WS_EX_LAYERED | WS_EX_TOOLWINDOW); + content_view_ = CreateContentView(content); + gfx::Rect bounds = content_view_-> + CalculateWindowBounds(parent_hwnd, position_relative_to); + set_initial_class_style( + (win_util::GetWinVersion() < win_util::WINVERSION_XP) ? + 0 : CS_DROPSHADOW); + + HWNDViewContainer::Init(parent_hwnd, bounds, content_view_, true); + // The preferred size may differ when parented. Ask for the bounds again + // and if they differ reset the bounds. + gfx::Rect parented_bounds = content_view_-> + CalculateWindowBounds(parent_hwnd, position_relative_to); + + // Set our initial alpha to zero so we don't flicker at the user. This + // doesn't trigger UpdateLayeredWindow, which would explode our native + // controls. + SetLayeredAlpha(kMinimumAlpha); + + if (bounds != parented_bounds) { + SetWindowPos(NULL, parented_bounds.x(), parented_bounds.y(), + parented_bounds.width(), parented_bounds.height(), + SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOZORDER); + // Invoke ChangeSize, otherwise layered window isn't updated correctly. + ChangeSize(0, CSize(parented_bounds.width(), parented_bounds.height())); + } + + // Register the Escape accelerator for closing. + ChromeViews::FocusManager* focus_manager = + ChromeViews::FocusManager::GetFocusManager(GetHWND()); + focus_manager->RegisterAccelerator(ChromeViews::Accelerator(VK_ESCAPE, + false, false, + false), + this); + + fade_animation_.reset(new SlideAnimation(this)); + fade_animation_->Show(); +} + +void InfoBubble::Close() { + // We don't fade out because it looks terrible. + ChromeFrame* frame = GetHostingFrame(); + if (delegate_) + delegate_->InfoBubbleClosing(this); + if (frame) + frame->InfoBubbleClosing(); + HWNDViewContainer::Close(); +} + +void InfoBubble::AnimationProgressed(const Animation* animation) { + int alpha = static_cast<int>(static_cast<double> + (fade_animation_->GetCurrentValue() * (255.0 - kMinimumAlpha) + + kMinimumAlpha)); + + SetLayeredWindowAttributes(GetHWND(), + RGB(0xFF, 0xFF, 0xFF), + alpha, + LWA_ALPHA); + content_view_->SchedulePaint(); +} + +bool InfoBubble::AcceleratorPressed( + const ChromeViews::Accelerator& accelerator) { + DCHECK(accelerator.GetKeyCode() == VK_ESCAPE); + if (!delegate_ || delegate_->CloseOnEscape()) { + Close(); + return true; + } + return false; +} + +void InfoBubble::OnSize(UINT param, const CSize& size) { + SetWindowRgn(content_view_->GetMask(size), TRUE); +} + +void InfoBubble::OnActivate(UINT action, BOOL minimized, HWND window) { + // The popup should close when it is deactivated. + if (action == WA_INACTIVE) { + Close(); + } else if (action == WA_ACTIVE) { + DCHECK(GetRootView()->GetChildViewCount() > 0); + GetRootView()->GetChildViewAt(0)->RequestFocus(); + } +} + +InfoBubble::ContentView* InfoBubble::CreateContentView(View* content) { + return new ContentView(content, this); +} + +ChromeFrame* InfoBubble::GetHostingFrame() { + HWND owning_frame_hwnd = GetAncestor(GetHWND(), GA_ROOTOWNER); + ChromeFrame* frame = ChromeFrame::GetChromeFrameForWindow(owning_frame_hwnd); + if (!frame) { + // We should always have a frame, but there was a bug else where that + // made it possible for the frame to be NULL, so we have the check. If + // you hit this, file a bug. + NOTREACHED(); + } + return frame; +} + +// ContentView ---------------------------------------------------------------- + +InfoBubble::ContentView::ContentView(ChromeViews::View* content, + InfoBubble* host) + : host_(host) { + if (UILayoutIsRightToLeft()) { + arrow_edge_ = TOP_RIGHT; + } else { + arrow_edge_ = TOP_LEFT; + } + AddChildView(content); +} + +gfx::Rect InfoBubble::ContentView::CalculateWindowBounds( + HWND parent_hwnd, + const gfx::Rect& position_relative_to) { + gfx::Rect monitor_bounds = win_util::GetMonitorBoundsForRect( + position_relative_to); + // Calculate the bounds using TOP_LEFT (the default). + gfx::Rect window_bounds = CalculateWindowBounds(position_relative_to); + if (monitor_bounds.IsEmpty() || monitor_bounds.Contains(window_bounds)) + return window_bounds; + // Didn't fit, adjust the edge to fit as much as we can. + if (window_bounds.bottom() > monitor_bounds.bottom()) + SetArrowEdge(BOTTOM_LEFT); + if (window_bounds.right() > monitor_bounds.right()) { + if (IsTop()) + SetArrowEdge(TOP_RIGHT); + else + SetArrowEdge(BOTTOM_RIGHT); + } + // And return new bounds. + return CalculateWindowBounds(position_relative_to); +} + +void InfoBubble::ContentView::GetPreferredSize(CSize* pref) { + DCHECK(GetChildViewCount() == 1); + View* content = GetChildViewAt(0); + content->GetPreferredSize(pref); + pref->cx += kBorderSize + kBorderSize + kInfoBubbleViewLeftMargin + + kInfoBubbleViewRightMargin; + pref->cy += kBorderSize + kBorderSize + kArrowSize + + kInfoBubbleViewTopMargin + kInfoBubbleViewBottomMargin; +} + +void InfoBubble::ContentView::Layout() { + DCHECK(GetChildViewCount() == 1); + View* content = GetChildViewAt(0); + int x = kBorderSize; + int y = kBorderSize; + int width = GetWidth() - kBorderSize - kBorderSize - + kInfoBubbleViewLeftMargin - kInfoBubbleViewRightMargin; + int height = GetHeight() - kBorderSize - kBorderSize - kArrowSize - + kInfoBubbleViewTopMargin - kInfoBubbleViewBottomMargin; + if (IsTop()) + y += kArrowSize; + x += kInfoBubbleViewLeftMargin; + y += kInfoBubbleViewTopMargin; + content->SetBounds(x, y, width, height); +} + +HRGN InfoBubble::ContentView::GetMask(const CSize &size) { + gfx::Path mask; + + // Redefine the window visible region so that our dropshadows look right. + SkScalar width = SkIntToScalar(size.cx); + SkScalar height = SkIntToScalar(size.cy); + SkScalar arrow_size = SkIntToScalar(kArrowSize); + SkScalar arrow_x = SkIntToScalar( + (IsLeft() ? kArrowXOffset : width - kArrowXOffset) - 1); + SkScalar corner_size = SkIntToScalar(kInfoBubbleCornerHeight); + + if (IsTop()) { + // Top left corner. + mask.moveTo(0, arrow_size + corner_size - 1); + mask.lineTo(corner_size - 1, arrow_size); + + // Draw the arrow and the notch of the arrow. + mask.lineTo(arrow_x - arrow_size, arrow_size); + mask.lineTo(arrow_x, 0); + mask.lineTo(arrow_x + 3, 0); + mask.lineTo(arrow_x + arrow_size + 3, arrow_size); + + // Top right corner. + mask.lineTo(width - corner_size + 1, arrow_size); + mask.lineTo(width, arrow_size + corner_size - 1); + + // Bottom right corner. + mask.lineTo(width, height - corner_size); + mask.lineTo(width - corner_size, height); + + // Bottom left corner. + mask.lineTo(corner_size, height); + mask.lineTo(0, height - corner_size); + } else { + // Top left corner. + mask.moveTo(0, corner_size - 1); + mask.lineTo(corner_size - 1, 0); + + // Top right corner. + mask.lineTo(width - corner_size + 1, 0); + mask.lineTo(width, corner_size - 1); + + // Bottom right corner. + mask.lineTo(width, height - corner_size - arrow_size); + mask.lineTo(width - corner_size, height - arrow_size); + + // Draw the arrow and the notch of the arrow. + mask.lineTo(arrow_x + arrow_size + 2, height - arrow_size); + mask.lineTo(arrow_x + 2, height); + mask.lineTo(arrow_x + 1, height); + mask.lineTo(arrow_x - arrow_size + 1, height - arrow_size); + + // Bottom left corner. + mask.lineTo(corner_size, height - arrow_size); + mask.lineTo(0, height - corner_size - arrow_size); + } + + mask.close(); + + return mask.CreateHRGN(); +} + +void InfoBubble::ContentView::Paint(ChromeCanvas* canvas) { + int bubble_x = 0; + int bubble_y = 0; + int bubble_w = GetWidth(); + int bubble_h = GetHeight() - kArrowSize; + + int border_w = bubble_w - 2 * kInfoBubbleCornerWidth; + int border_h = bubble_h - 2 * kInfoBubbleCornerHeight; + + if (IsTop()) + bubble_y += kArrowSize; + + // Fill in the background. + // Left side. + canvas->FillRectInt(kBackgroundColor, + bubble_x, bubble_y + kInfoBubbleCornerHeight, + kInfoBubbleCornerWidth, border_h); + // Center Column. + canvas->FillRectInt(kBackgroundColor, + kInfoBubbleCornerWidth, bubble_y, + border_w, bubble_h); + // Right Column. + canvas->FillRectInt(kBackgroundColor, + bubble_w - kInfoBubbleCornerWidth, + bubble_y + kInfoBubbleCornerHeight, + kInfoBubbleCornerWidth, border_h); + + // Draw the border. + // Top border. + canvas->DrawRectInt(kBorderColor1, + kInfoBubbleCornerWidth, bubble_y, + border_w, + 0); + // Bottom border. + canvas->DrawRectInt(kBorderColor1, + kInfoBubbleCornerWidth, bubble_y + bubble_h - 1, + border_w, 0); + // Left border. + canvas->DrawRectInt(kBorderColor1, + bubble_x, bubble_y + kInfoBubbleCornerHeight, + 0, border_h); + + // Right border. + canvas->DrawRectInt(kBorderColor1, + GetWidth() - 1, bubble_y + kInfoBubbleCornerHeight, + 0, border_h); + + // Draw the corners. + canvas->DrawBitmapInt(*kInfoBubbleCornerTopLeft, 0, bubble_y); + canvas->DrawBitmapInt(*kInfoBubbleCornerTopRight, + bubble_w - kInfoBubbleCornerWidth, bubble_y); + canvas->DrawBitmapInt(*kInfoBubbleCornerBottomLeft, 0, + bubble_y + bubble_h - kInfoBubbleCornerHeight); + canvas->DrawBitmapInt(*kInfoBubbleCornerBottomRight, + bubble_w - kInfoBubbleCornerWidth, + bubble_y + bubble_h - kInfoBubbleCornerHeight); + + // Draw the arrow and the notch of the arrow. + int arrow_x = IsLeft() ? kArrowXOffset : GetWidth() - kArrowXOffset; + int arrow_y = IsTop() ? bubble_y : bubble_y + bubble_h - 1; + const int arrow_delta = IsTop() ? -1 : 1; + for (int i = 0, y = arrow_y; i <= kArrowSize; ++i, y += arrow_delta) { + if (kArrowSize != i) { + // Draw the notch formed by the arrow. + canvas->FillRectInt(kBackgroundColor, arrow_x - (kArrowSize - i) + 1, + y, (kArrowSize - i) * 2 - 1, 1); + } + // Draw the sides of the arrow. + canvas->FillRectInt(kBorderColor1, arrow_x - (kArrowSize - i), y, 1, 1); + canvas->FillRectInt(kBorderColor1, arrow_x + (kArrowSize - i), y, 1, 1); + if (i != 0) { + canvas->FillRectInt(kBorderColor2, arrow_x - (kArrowSize - i) - 1, y, 1, + 1); + canvas->FillRectInt(kBorderColor2, arrow_x + (kArrowSize - i) + 1, y, 1, + 1); + } + } +} + +gfx::Rect InfoBubble::ContentView::CalculateWindowBounds( + const gfx::Rect& position_relative_to) { + CSize pref; + GetPreferredSize(&pref); + int x = position_relative_to.x() + position_relative_to.width() / 2; + int y; + if (IsLeft()) + x -= kArrowXOffset; + else + x = x + kArrowXOffset - pref.cx; + if (IsTop()) { + y = position_relative_to.bottom() + kArrowToContentPadding; + } else { + y = position_relative_to.y() - kArrowToContentPadding - pref.cy; + } + return gfx::Rect(x, y, pref.cx, pref.cy); +} |