| Index: chrome/browser/notifications/balloon_view_win.cc
|
| diff --git a/chrome/browser/notifications/balloon_view_win.cc b/chrome/browser/notifications/balloon_view_win.cc
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..3f34901978007fb228eb2f36328ad2304f9ccad0
|
| --- /dev/null
|
| +++ b/chrome/browser/notifications/balloon_view_win.cc
|
| @@ -0,0 +1,386 @@
|
| +// 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/notifications/balloon_view_win.h"
|
| +
|
| +#include "base/message_loop.h"
|
| +#include "base/string_util.h"
|
| +#include "base/gfx/gdi_util.h"
|
| +
|
| +#include "app/gfx/canvas.h"
|
| +#include "app/l10n_util.h"
|
| +#include "chrome/browser/notifications/balloon_contents_win.h"
|
| +#include "chrome/browser/notifications/balloons.h"
|
| +#include "chrome/common/notification_details.h"
|
| +#include "chrome/common/notification_source.h"
|
| +#include "chrome/common/notification_type.h"
|
| +#include "views/widget/widget_win.h"
|
| +#include "views/painter.h"
|
| +#include "views/controls/button/button.h"
|
| +#include "views/controls/button/text_button.h"
|
| +#include "views/controls/label.h"
|
| +
|
| +#include "grit/generated_resources.h"
|
| +#include "grit/theme_resources.h"
|
| +
|
| +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 kShelfBoarderTopOverlap = 2;
|
| +
|
| +// Properties of the dismiss button.
|
| +const int kDismissButtonWidth = 46;
|
| +const int kDismissButtonHeight = 20;
|
| +const wchar_t kDismissButtonText[] = L"Dismiss";
|
| +
|
| +// Properties of the origin label.
|
| +const int kLeftLabelMargin = 5;
|
| +
|
| +// TODO(levin): 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:
|
| + BalloonCloseButtonListener(BalloonView* view) : view_(view) {}
|
| + virtual ~BalloonCloseButtonListener() {}
|
| +
|
| + virtual void ButtonPressed(views::Button* sender, const views::Event&) {
|
| + view_->Close();
|
| + }
|
| +
|
| + private:
|
| + BalloonView* view_;
|
| +};
|
| +
|
| +BalloonView::BalloonView()
|
| + : balloon_(NULL),
|
| + frame_container_(NULL),
|
| + html_container_(NULL),
|
| + method_factory_(this) {
|
| + int shelf_images[9];
|
| + shelf_images[views::ImagePainter::BORDER_TOP_LEFT] =
|
| + IDR_BALLOON_SHELF_TOP_LEFT;
|
| + shelf_images[views::ImagePainter::BORDER_TOP] = IDR_BALLOON_SHELF_TOP_CENTER;
|
| + shelf_images[views::ImagePainter::BORDER_TOP_RIGHT] =
|
| + IDR_BALLOON_SHELF_TOP_RIGHT;
|
| + shelf_images[views::ImagePainter::BORDER_RIGHT] = IDR_BALLOON_SHELF_RIGHT;
|
| + shelf_images[views::ImagePainter::BORDER_BOTTOM_RIGHT] =
|
| + IDR_BALLOON_SHELF_BOTTOM_RIGHT;
|
| + shelf_images[views::ImagePainter::BORDER_BOTTOM] =
|
| + IDR_BALLOON_SHELF_BOTTOM_CENTER;
|
| + shelf_images[views::ImagePainter::BORDER_BOTTOM_LEFT] =
|
| + IDR_BALLOON_SHELF_BOTTOM_LEFT;
|
| + shelf_images[views::ImagePainter::BORDER_LEFT] = IDR_BALLOON_SHELF_LEFT;
|
| + shelf_images[views::ImagePainter::BORDER_CENTER] = IDR_BALLOON_SHELF_CENTER;
|
| + shelf_background_.reset(new views::ImagePainter(shelf_images, true));
|
| +
|
| + int balloon_images[8];
|
| + balloon_images[views::ImagePainter::BORDER_TOP_LEFT] = IDR_BALLOON_TOP_LEFT;
|
| + balloon_images[views::ImagePainter::BORDER_TOP] = IDR_BALLOON_TOP_CENTER;
|
| + balloon_images[views::ImagePainter::BORDER_TOP_RIGHT] = IDR_BALLOON_TOP_RIGHT;
|
| + balloon_images[views::ImagePainter::BORDER_RIGHT] = IDR_BALLOON_RIGHT;
|
| + balloon_images[views::ImagePainter::BORDER_BOTTOM_RIGHT] =
|
| + IDR_BALLOON_BOTTOM_RIGHT;
|
| + balloon_images[views::ImagePainter::BORDER_BOTTOM] =
|
| + IDR_BALLOON_BOTTOM_CENTER;
|
| + balloon_images[views::ImagePainter::BORDER_BOTTOM_LEFT] =
|
| + IDR_BALLOON_BOTTOM_LEFT;
|
| + balloon_images[views::ImagePainter::BORDER_LEFT] = IDR_BALLOON_LEFT;
|
| + balloon_background_.reset(new views::ImagePainter(balloon_images, false));
|
| +}
|
| +
|
| +BalloonView::~BalloonView() {
|
| +}
|
| +
|
| +void BalloonView::Close() {
|
| + MessageLoop::current()->PostTask(
|
| + FROM_HERE,
|
| + method_factory_.NewRunnableMethod(&BalloonView::DelayedClose));
|
| +}
|
| +
|
| +void BalloonView::DelayedClose() {
|
| + balloon_->Close();
|
| + html_contents_->Shutdown();
|
| + html_container_->CloseNow();
|
| + frame_container_->CloseNow();
|
| +}
|
| +
|
| +void BalloonView::DidChangeBounds(const gfx::Rect& previous,
|
| + const gfx::Rect& current) {
|
| + SizeContentsWindow();
|
| +}
|
| +
|
| +void BalloonView::SizeContentsWindow() {
|
| + if (!html_container_ || !frame_container_) {
|
| + return;
|
| + }
|
| + gfx::Rect contents_rect = contents_rectangle();
|
| + html_container_->SetWindowPos(frame_container_->GetNativeView(),
|
| + contents_rect.x(),
|
| + contents_rect.y(),
|
| + contents_rect.width(),
|
| + contents_rect.height(),
|
| + SWP_NOACTIVATE);
|
| +
|
| + // Note: System will own the hrgn after we call SetWindowRgn,
|
| + // so we don't need to call DeleteObject() for the mask.
|
| + ::SetWindowRgn(html_container_->GetNativeView(),
|
| + GetContentsMask(contents_rect),
|
| + false);
|
| +}
|
| +
|
| +void BalloonView::RepositionToBalloon() {
|
| + DCHECK(frame_container_);
|
| + DCHECK(html_container_);
|
| + DCHECK(balloon_);
|
| +
|
| + if (!kAnimateEnabled) {
|
| + frame_container_->MoveWindow(balloon_->position().x(),
|
| + balloon_->position().y(),
|
| + balloon_->size().width(),
|
| + balloon_->size().height());
|
| + gfx::Rect contents_rect = contents_rectangle();
|
| + html_container_->MoveWindow(contents_rect.x(),
|
| + contents_rect.y(),
|
| + contents_rect.width(),
|
| + contents_rect.height());
|
| + 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 BalloonView::AnimationProgressed(const Animation* animation) {
|
| + DCHECK(animation == animation_.get());
|
| +
|
| + double e = animation->GetCurrentValue();
|
| + double s = (1.0 - e);
|
| +
|
| + gfx::Rect frame_position(
|
| + (int) (s * anim_frame_start_.x() + e * anim_frame_end_.x()),
|
| + (int) (s * anim_frame_start_.y() + e * anim_frame_end_.y()),
|
| + (int) (s * anim_frame_start_.width() + e * anim_frame_end_.width()),
|
| + (int) (s * anim_frame_start_.height() + e * anim_frame_end_.height()));
|
| +
|
| + frame_container_->MoveWindow(frame_position.x(),
|
| + frame_position.y(),
|
| + frame_position.width(),
|
| + frame_position.height());
|
| + gfx::Rect contents_rect = contents_rectangle();
|
| + html_container_->MoveWindow(contents_rect.x(),
|
| + contents_rect.y(),
|
| + contents_rect.width(),
|
| + contents_rect.height());
|
| +}
|
| +
|
| +void BalloonView::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.
|
| +
|
| + html_container_ = new views::WidgetWin();
|
| + html_container_->set_window_style(WS_POPUP);
|
| + html_container_->set_window_ex_style(WS_EX_TOPMOST | WS_EX_TOOLWINDOW);
|
| + gfx::Rect contents_rect = contents_rectangle();
|
| + html_container_->Init(NULL, contents_rect);
|
| + html_contents_ = new BalloonContents(balloon);
|
| + html_contents_->SetPreferredSize(gfx::Size(10000, 10000));
|
| + html_container_->SetContentsView(html_contents_);
|
| +
|
| + 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_->set_alignment(views::TextButton::ALIGN_CENTER);
|
| + close_button_->SetBounds(width() - kDismissButtonWidth
|
| + - kRightMargin,
|
| + height() - kDismissButtonHeight
|
| + - kShelfBoarderTopOverlap
|
| + - 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().GetOrigin().spec()));
|
| +
|
| + views::Label* source_label = new views::Label(source_label_text);
|
| + source_label->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
|
| + source_label->SetBounds(kLeftLabelMargin,
|
| + height() - kDismissButtonHeight
|
| + - kShelfBoarderTopOverlap
|
| + - kBottomMargin,
|
| + width() - kDismissButtonWidth
|
| + - kRightMargin,
|
| + kDismissButtonHeight);
|
| + AddChildView(source_label);
|
| +
|
| + frame_container_ = new views::WidgetWin();
|
| + frame_container_->set_window_style(WS_POPUP);
|
| + frame_container_->set_window_ex_style(
|
| + WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW);
|
| + gfx::Rect balloon_rect(x(), y(), width(), height());
|
| + frame_container_->Init(NULL, balloon_rect);
|
| + frame_container_->SetContentsView(this);
|
| +
|
| + SizeContentsWindow();
|
| + html_container_->ShowWindow(SW_SHOWNOACTIVATE);
|
| + frame_container_->ShowWindow(SW_SHOWNOACTIVATE);
|
| +
|
| + notification_registrar_.Add(this,
|
| + NotificationType::NOTIFY_BALLOON_DISCONNECTED, Source<Balloon>(balloon));
|
| +}
|
| +
|
| +
|
| +HRGN BalloonView::GetContentsMask(gfx::Rect& contents_rect) const {
|
| + // This needs to remove areas that look like the following from each corner:
|
| + //
|
| + // xx
|
| + // x
|
| +
|
| + // Ideally, the frame would alpha blend these pixels on to the balloon
|
| + // contents. However, that is a problem because the frame and contents are
|
| + // separate windows, and the OS may swap in z order (so the contents goes on
|
| + // top of the frame and covers the alpha blended pixels).
|
| +
|
| + std::vector<gfx::Rect> cutouts;
|
| + // Upper left.
|
| + cutouts.push_back(gfx::Rect(0, 0, 2, 1));
|
| + cutouts.push_back(gfx::Rect(0, 1, 1, 1));
|
| +
|
| + // Upper right.
|
| + cutouts.push_back(gfx::Rect(contents_rect.width() - 2, 0, 2, 1));
|
| + cutouts.push_back(gfx::Rect(contents_rect.width() - 1, 1, 1, 1));
|
| +
|
| + // Lower left.
|
| + cutouts.push_back(gfx::Rect(0, contents_rect.height() - 1, 2, 1));
|
| + cutouts.push_back(gfx::Rect(0, contents_rect.height() - 2, 1, 1));
|
| +
|
| + // Lower right.
|
| + cutouts.push_back(gfx::Rect(contents_rect.width() - 2,
|
| + contents_rect.height() - 1,
|
| + 2,
|
| + 1));
|
| + cutouts.push_back(gfx::Rect(contents_rect.width() - 1,
|
| + contents_rect.height() - 2,
|
| + 1,
|
| + 1));
|
| +
|
| + HRGN hrgn = ::CreateRectRgn(0,
|
| + 0,
|
| + contents_rect.width(),
|
| + contents_rect.height());
|
| + gfx::SubtractRectanglesFromRegion(hrgn, cutouts);
|
| + return hrgn;
|
| +}
|
| +
|
| +gfx::Point BalloonView::contents_offset() const {
|
| + return gfx::Point(kTopShadowWidth + kTopMargin,
|
| + kLeftShadowWidth + kLeftMargin);
|
| +}
|
| +
|
| +int BalloonView::shelf_height() const {
|
| + // TODO(levin): add scaling here.
|
| + return kDefaultShelfHeight;
|
| +}
|
| +
|
| +int BalloonView::frame_width() const {
|
| + return size().width() - kLeftShadowWidth - kRightShadowWidth;
|
| +}
|
| +
|
| +int BalloonView::total_frame_height() const {
|
| + return size().height() - kTopShadowWidth - kBottomShadowWidth;
|
| +}
|
| +
|
| +int BalloonView::balloon_frame_height() const {
|
| + return total_frame_height() - shelf_height();
|
| +}
|
| +
|
| +gfx::Rect BalloonView::contents_rectangle() const {
|
| + if (!frame_container_) {
|
| + return gfx::Rect();
|
| + }
|
| + int contents_width = frame_width() - kLeftMargin - kRightMargin;
|
| + int contents_height = balloon_frame_height() - kTopMargin - kBottomMargin;
|
| + gfx::Point offset = contents_offset();
|
| + 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 BalloonView::Paint(gfx::Canvas* canvas) {
|
| + DCHECK(canvas);
|
| +
|
| + int background_width = frame_width();
|
| + int background_height = balloon_frame_height();
|
| +
|
| + balloon_background_->Paint(background_width, background_height, canvas);
|
| +
|
| + canvas->save();
|
| + SkScalar y_offset =
|
| + static_cast<SkScalar>(background_height - kShelfBoarderTopOverlap);
|
| + canvas->translate(0, y_offset);
|
| + shelf_background_->Paint(background_width, shelf_height(), canvas);
|
| + canvas->restore();
|
| +
|
| + View::Paint(canvas);
|
| +}
|
| +
|
| +void BalloonView::Observe(NotificationType type,
|
| + const NotificationSource& source,
|
| + const NotificationDetails& details) {
|
| + if (type != NotificationType::NOTIFY_BALLOON_DISCONNECTED) {
|
| + NOTREACHED();
|
| + return;
|
| + }
|
| +
|
| + notification_registrar_.Remove(this,
|
| + NotificationType::NOTIFY_BALLOON_DISCONNECTED, Source<Balloon>(balloon_));
|
| +
|
| + Close();
|
| +}
|
|
|