| Index: chrome/browser/views/notifications/balloon_view.cc
|
| ===================================================================
|
| --- chrome/browser/views/notifications/balloon_view.cc (revision 0)
|
| +++ chrome/browser/views/notifications/balloon_view.cc (revision 0)
|
| @@ -0,0 +1,354 @@
|
| +// 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 "chrome/browser/views/notifications/balloon_view.h"
|
| +
|
| +#include <vector>
|
| +
|
| +#include "app/gfx/canvas.h"
|
| +#include "app/gfx/gdi_util.h"
|
| +#include "app/gfx/insets.h"
|
| +#include "app/gfx/native_widget_types.h"
|
| +#include "app/l10n_util.h"
|
| +#include "app/resource_bundle.h"
|
| +#include "base/message_loop.h"
|
| +#include "base/string_util.h"
|
| +#include "chrome/browser/browser_theme_provider.h"
|
| +#include "chrome/browser/notifications/balloon.h"
|
| +#include "chrome/browser/views/notifications/balloon_view_host.h"
|
| +#include "chrome/common/notification_details.h"
|
| +#include "chrome/common/notification_source.h"
|
| +#include "chrome/common/notification_type.h"
|
| +#include "grit/generated_resources.h"
|
| +#include "grit/theme_resources.h"
|
| +#include "views/controls/button/button.h"
|
| +#include "views/controls/button/text_button.h"
|
| +#include "views/controls/label.h"
|
| +#include "views/controls/native/native_view_host.h"
|
| +#include "views/painter.h"
|
| +#include "views/widget/widget_win.h"
|
| +
|
| +using views::Widget;
|
| +
|
| +namespace {
|
| +
|
| +// How many pixels of overlap there is between the shelf top and the
|
| +// balloon bottom.
|
| +const int kTopMargin = 1;
|
| +const int kBottomMargin = 1;
|
| +const int kLeftMargin = 1;
|
| +const int kRightMargin = 1;
|
| +const int kShelfBorderTopOverlap = 2;
|
| +
|
| +// Properties of the dismiss button.
|
| +const int kDismissButtonWidth = 46;
|
| +const int kDismissButtonHeight = 20;
|
| +
|
| +// Properties of the origin label.
|
| +const int kLeftLabelMargin = 5;
|
| +
|
| +// TODO(johnnyg): Add a shadow for the frame.
|
| +const int kLeftShadowWidth = 0;
|
| +const int kRightShadowWidth = 0;
|
| +const int kTopShadowWidth = 0;
|
| +const int kBottomShadowWidth = 0;
|
| +
|
| +// Optional animation.
|
| +const bool kAnimateEnabled = true;
|
| +
|
| +// The shelf height for the system default font size. It is scaled
|
| +// with changes in the default font size.
|
| +const int kDefaultShelfHeight = 22;
|
| +
|
| +} // namespace
|
| +
|
| +class BalloonCloseButtonListener : public views::ButtonListener {
|
| + public:
|
| + explicit BalloonCloseButtonListener(BalloonView* view)
|
| + : view_(view) {}
|
| + virtual ~BalloonCloseButtonListener() {}
|
| +
|
| + // The only button currently is the close button.
|
| + virtual void ButtonPressed(views::Button* sender, const views::Event&) {
|
| + view_->Close();
|
| + }
|
| +
|
| + private:
|
| + // Non-owned pointer to the view which owns this object.
|
| + BalloonView* view_;
|
| +};
|
| +
|
| +BalloonViewImpl::BalloonViewImpl()
|
| + : balloon_(NULL),
|
| + frame_container_(NULL),
|
| + html_container_(NULL),
|
| + html_contents_(NULL),
|
| + shelf_background_(NULL),
|
| + balloon_background_(NULL),
|
| + close_button_(NULL),
|
| + close_button_listener_(NULL),
|
| + animation_(NULL),
|
| + method_factory_(this) {
|
| + // This object is not to be deleted by the views hierarchy,
|
| + // as it is owned by the balloon.
|
| + SetParentOwned(false);
|
| +
|
| + // Load the sprites for the frames.
|
| + ResourceBundle& rb = ResourceBundle::GetSharedInstance();
|
| + SkBitmap* shelf_bitmap = rb.GetBitmapNamed(IDR_BALLOON_SHELF);
|
| + SkBitmap* border_bitmap = rb.GetBitmapNamed(IDR_BALLOON_BORDER);
|
| +
|
| + // Insets are such because the sprites have 3x3 corners.
|
| + gfx::Insets insets(3, 3, 3, 3);
|
| + shelf_background_.reset(
|
| + views::Painter::CreateImagePainter(*shelf_bitmap, insets, true));
|
| + balloon_background_.reset(
|
| + views::Painter::CreateImagePainter(*border_bitmap, insets, false));
|
| +}
|
| +
|
| +BalloonViewImpl::~BalloonViewImpl() {
|
| +}
|
| +
|
| +void BalloonViewImpl::Close() {
|
| + MessageLoop::current()->PostTask(FROM_HERE,
|
| + method_factory_.NewRunnableMethod(&BalloonViewImpl::DelayedClose));
|
| +}
|
| +
|
| +void BalloonViewImpl::DelayedClose() {
|
| + html_contents_->Shutdown();
|
| + html_container_->CloseNow();
|
| + frame_container_->CloseNow();
|
| + balloon_->Close(true);
|
| +}
|
| +
|
| +void BalloonViewImpl::DidChangeBounds(const gfx::Rect& previous,
|
| + const gfx::Rect& current) {
|
| + SizeContentsWindow();
|
| +}
|
| +
|
| +void BalloonViewImpl::SizeContentsWindow() {
|
| + if (!html_container_ || !frame_container_)
|
| + return;
|
| +
|
| + gfx::Rect contents_rect = GetContentsRectangle();
|
| + html_container_->SetBounds(contents_rect);
|
| + html_container_->MoveAbove(frame_container_);
|
| +
|
| + gfx::Path path;
|
| + GetContentsMask(contents_rect, &path);
|
| + html_container_->SetShape(path);
|
| +}
|
| +
|
| +void BalloonViewImpl::RepositionToBalloon() {
|
| + DCHECK(frame_container_);
|
| + DCHECK(html_container_);
|
| + DCHECK(balloon_);
|
| +
|
| + if (!kAnimateEnabled) {
|
| + frame_container_->SetBounds(
|
| + gfx::Rect(balloon_->position(), balloon_->size()));
|
| + gfx::Rect contents_rect = GetContentsRectangle();
|
| + html_container_->SetBounds(contents_rect);
|
| + return;
|
| + }
|
| +
|
| + anim_frame_end_ = gfx::Rect(balloon_->position().x(),
|
| + balloon_->position().y(),
|
| + balloon_->size().width(),
|
| + balloon_->size().height());
|
| + frame_container_->GetBounds(&anim_frame_start_, false);
|
| + animation_.reset(new SlideAnimation(this));
|
| + animation_->Show();
|
| +}
|
| +
|
| +void BalloonViewImpl::AnimationProgressed(const Animation* animation) {
|
| + DCHECK(animation == animation_.get());
|
| +
|
| + // Linear interpolation from start to end position.
|
| + double e = animation->GetCurrentValue();
|
| + double s = (1.0 - e);
|
| +
|
| + gfx::Rect frame_position(
|
| + static_cast<int>(s * anim_frame_start_.x() +
|
| + e * anim_frame_end_.x()),
|
| + static_cast<int>(s * anim_frame_start_.y() +
|
| + e * anim_frame_end_.y()),
|
| + static_cast<int>(s * anim_frame_start_.width() +
|
| + e * anim_frame_end_.width()),
|
| + static_cast<int>(s * anim_frame_start_.height() +
|
| + e * anim_frame_end_.height()));
|
| +
|
| + frame_container_->SetBounds(frame_position);
|
| + html_container_->SetBounds(GetContentsRectangle());
|
| +}
|
| +
|
| +void BalloonViewImpl::Show(Balloon* balloon) {
|
| + balloon_ = balloon;
|
| + close_button_listener_.reset(new BalloonCloseButtonListener(this));
|
| +
|
| + SetBounds(balloon_->position().x(), balloon_->position().y(),
|
| + balloon_->size().width(), balloon_->size().height());
|
| +
|
| + // We have to create two windows: one for the contents and one for the
|
| + // frame. Why?
|
| + // * The contents is an html window which cannot be a
|
| + // layered window (because it may have child windows for instance).
|
| + // * The frame is a layered window so that we can have nicely rounded
|
| + // corners using alpha blending (and we may do other alpha blending
|
| + // effects).
|
| + // Unfortunately, layered windows cannot have child windows. (Well, they can
|
| + // but the child windows don't render).
|
| + //
|
| + // We carefully keep these two windows in sync to present the illusion of
|
| + // one window to the user.
|
| + gfx::Rect contents_rect = GetContentsRectangle();
|
| + html_contents_ = new BalloonViewHost(balloon);
|
| + html_contents_->SetPreferredSize(gfx::Size(10000, 10000));
|
| +
|
| + html_container_ = Widget::CreatePopupWidget(Widget::NotTransparent,
|
| + Widget::AcceptEvents,
|
| + Widget::DeleteOnDestroy);
|
| + html_container_->SetAlwaysOnTop(true);
|
| + html_container_->Init(NULL, contents_rect);
|
| + html_container_->SetContentsView(html_contents_);
|
| +
|
| + gfx::Rect balloon_rect(x(), y(), width(), height());
|
| + frame_container_ = Widget::CreatePopupWidget(Widget::Transparent,
|
| + Widget::AcceptEvents,
|
| + Widget::DeleteOnDestroy);
|
| + frame_container_->SetAlwaysOnTop(true);
|
| + frame_container_->Init(NULL, balloon_rect);
|
| + frame_container_->SetContentsView(this);
|
| +
|
| + const std::wstring dismiss_text =
|
| + l10n_util::GetString(IDS_NOTIFICATION_BALLOON_DISMISS_LABEL);
|
| + close_button_ = new views::TextButton(close_button_listener_.get(),
|
| + dismiss_text);
|
| + close_button_->SetFont(
|
| + ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::SmallFont));
|
| + close_button_->SetEnabledColor(BrowserThemeProvider::kDefaultColorTabText);
|
| + close_button_->set_alignment(views::TextButton::ALIGN_CENTER);
|
| + close_button_->SetBounds(width() - kDismissButtonWidth
|
| + - kRightMargin,
|
| + height() - kDismissButtonHeight
|
| + - kShelfBorderTopOverlap
|
| + - kBottomMargin,
|
| + kDismissButtonWidth,
|
| + kDismissButtonHeight);
|
| + AddChildView(close_button_);
|
| +
|
| + const std::wstring source_label_text = l10n_util::GetStringF(
|
| + IDS_NOTIFICATION_BALLOON_SOURCE_LABEL,
|
| + ASCIIToWide(this->balloon_->notification().origin_url().spec()));
|
| +
|
| + views::Label* source_label = new views::Label(source_label_text);
|
| + source_label->SetFont(ResourceBundle::GetSharedInstance().GetFont(
|
| + ResourceBundle::SmallFont));
|
| + source_label->SetColor(BrowserThemeProvider::kDefaultColorTabText);
|
| + source_label->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
|
| + source_label->SetBounds(kLeftLabelMargin,
|
| + height() - kDismissButtonHeight
|
| + - kShelfBorderTopOverlap
|
| + - kBottomMargin,
|
| + width() - kDismissButtonWidth
|
| + - kRightMargin,
|
| + kDismissButtonHeight);
|
| + AddChildView(source_label);
|
| +
|
| + SizeContentsWindow();
|
| + html_container_->Show();
|
| + frame_container_->Show();
|
| +
|
| + notification_registrar_.Add(this,
|
| + NotificationType::NOTIFY_BALLOON_DISCONNECTED, Source<Balloon>(balloon));
|
| +}
|
| +
|
| +
|
| +void BalloonViewImpl::GetContentsMask(const gfx::Rect& rect,
|
| + gfx::Path* path) const {
|
| + // This needs to remove areas that look like the following from each corner:
|
| + //
|
| + // xx
|
| + // x
|
| + path->moveTo(SkScalar(1), SkScalar(0));
|
| + // Upper right corner
|
| + path->arcTo(rect.width() - SkScalar(2), SkScalar(0),
|
| + rect.width() - SkScalar(1), SkScalar(2),
|
| + SkScalar(1));
|
| + // Lower right corner
|
| + path->arcTo(rect.width() - SkScalar(1), rect.height() - SkScalar(2),
|
| + rect.width() - SkScalar(2), rect.height() - SkScalar(1),
|
| + SkScalar(1));
|
| + // Lower left corner
|
| + path->arcTo(SkScalar(1), rect.height() - SkScalar(1),
|
| + 0, rect.height() - SkScalar(2),
|
| + SkScalar(1));
|
| + // Upper left corner
|
| + path->arcTo(0, SkScalar(1), SkScalar(1), 0, SkScalar(1));
|
| +}
|
| +
|
| +gfx::Point BalloonViewImpl::GetContentsOffset() const {
|
| + return gfx::Point(kTopShadowWidth + kTopMargin,
|
| + kLeftShadowWidth + kLeftMargin);
|
| +}
|
| +
|
| +int BalloonViewImpl::GetShelfHeight() const {
|
| + // TODO(johnnyg): add scaling here.
|
| + return kDefaultShelfHeight;
|
| +}
|
| +
|
| +int BalloonViewImpl::GetFrameWidth() const {
|
| + return size().width() - kLeftShadowWidth - kRightShadowWidth;
|
| +}
|
| +
|
| +int BalloonViewImpl::GetTotalFrameHeight() const {
|
| + return size().height() - kTopShadowWidth - kBottomShadowWidth;
|
| +}
|
| +
|
| +int BalloonViewImpl::GetBalloonFrameHeight() const {
|
| + return GetTotalFrameHeight() - GetShelfHeight();
|
| +}
|
| +
|
| +gfx::Rect BalloonViewImpl::GetContentsRectangle() const {
|
| + if (!frame_container_)
|
| + return gfx::Rect();
|
| +
|
| + int contents_width = GetFrameWidth() - kLeftMargin - kRightMargin;
|
| + int contents_height = GetBalloonFrameHeight() - kTopMargin - kBottomMargin;
|
| + gfx::Point offset = GetContentsOffset();
|
| + gfx::Rect frame_rect;
|
| + frame_container_->GetBounds(&frame_rect, true);
|
| + return gfx::Rect(frame_rect.x() + offset.x(), frame_rect.y() + offset.y(),
|
| + contents_width, contents_height);
|
| +}
|
| +
|
| +void BalloonViewImpl::Paint(gfx::Canvas* canvas) {
|
| + DCHECK(canvas);
|
| + int background_width = GetFrameWidth();
|
| + int background_height = GetBalloonFrameHeight();
|
| + balloon_background_->Paint(background_width, background_height, canvas);
|
| + canvas->save();
|
| + SkScalar y_offset =
|
| + static_cast<SkScalar>(background_height - kShelfBorderTopOverlap);
|
| + canvas->translate(0, y_offset);
|
| + shelf_background_->Paint(background_width, GetShelfHeight(), canvas);
|
| + canvas->restore();
|
| +
|
| + View::Paint(canvas);
|
| +}
|
| +
|
| +void BalloonViewImpl::Observe(NotificationType type,
|
| + const NotificationSource& source,
|
| + const NotificationDetails& details) {
|
| + if (type != NotificationType::NOTIFY_BALLOON_DISCONNECTED) {
|
| + NOTREACHED();
|
| + return;
|
| + }
|
| +
|
| + // If the renderer process attached to this balloon is disconnected
|
| + // (e.g., because of a crash), we want to close the balloon.
|
| + notification_registrar_.Remove(this,
|
| + NotificationType::NOTIFY_BALLOON_DISCONNECTED, Source<Balloon>(balloon_));
|
| + Close();
|
| +}
|
|
|
| Property changes on: chrome\browser\views\notifications\balloon_view.cc
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|