blob: 0e6009c64bde998e6e377f4388e9474f09a17cec [file] [log] [blame]
// Copyright (c) 2011 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/chromeos/notifications/balloon_view.h"
#include <algorithm>
#include <vector>
#include "base/bind.h"
#include "base/message_loop.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/chromeos/notifications/balloon_view_host.h"
#include "chrome/browser/chromeos/notifications/notification_panel.h"
#include "chrome/browser/notifications/balloon.h"
#include "chrome/browser/notifications/desktop_notification_service.h"
#include "chrome/browser/notifications/desktop_notification_service_factory.h"
#include "chrome/browser/notifications/notification.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/views/notifications/balloon_view_host.h"
#include "chrome/common/chrome_notification_types.h"
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/renderer_host/render_widget_host_view.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "grit/theme_resources_standard.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/simple_menu_model.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/menu_model_adapter.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/menu/view_menu_delegate.h"
#include "ui/views/widget/widget.h"
namespace {
// Menu commands
const int kNoopCommand = 0;
const int kRevokePermissionCommand = 1;
// Vertical margin between close button and menu button.
const int kControlButtonsMargin = 6;
// Top, Right margin for notification control view.
const int kControlViewTopMargin = 4;
const int kControlViewRightMargin = 6;
} // namespace
namespace chromeos {
// NotificationControlView has close and menu buttons and
// overlays on top of renderer view.
class NotificationControlView : public views::View,
public views::ViewMenuDelegate,
public ui::SimpleMenuModel::Delegate,
public views::ButtonListener {
public:
explicit NotificationControlView(BalloonViewImpl* view)
: balloon_view_(view),
close_button_(NULL),
options_menu_contents_(NULL),
options_menu_button_(NULL) {
// TODO(oshima): make background transparent.
set_background(views::Background::CreateSolidBackground(SK_ColorWHITE));
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
SkBitmap* close_button_n = rb.GetBitmapNamed(IDR_TAB_CLOSE);
SkBitmap* close_button_m = rb.GetBitmapNamed(IDR_TAB_CLOSE_MASK);
SkBitmap* close_button_h = rb.GetBitmapNamed(IDR_TAB_CLOSE_H);
SkBitmap* close_button_p = rb.GetBitmapNamed(IDR_TAB_CLOSE_P);
close_button_ = new views::ImageButton(this);
close_button_->SetImage(views::CustomButton::BS_NORMAL, close_button_n);
close_button_->SetImage(views::CustomButton::BS_HOT, close_button_h);
close_button_->SetImage(views::CustomButton::BS_PUSHED, close_button_p);
close_button_->SetBackground(
SK_ColorBLACK, close_button_n, close_button_m);
AddChildView(close_button_);
options_menu_button_ = new views::MenuButton(
NULL, string16(), this, false);
options_menu_button_->SetFont(rb.GetFont(ResourceBundle::SmallFont));
options_menu_button_->SetIcon(*rb.GetBitmapNamed(IDR_NOTIFICATION_MENU));
options_menu_button_->set_border(NULL);
options_menu_button_->set_icon_placement(views::TextButton::ICON_ON_RIGHT);
AddChildView(options_menu_button_);
// The control view will never be resized, so just layout once.
gfx::Size options_size = options_menu_button_->GetPreferredSize();
gfx::Size button_size = close_button_->GetPreferredSize();
int height = std::max(options_size.height(), button_size.height());
options_menu_button_->SetBounds(
0, (height - options_size.height()) / 2,
options_size.width(), options_size.height());
close_button_->SetBounds(
options_size.width() + kControlButtonsMargin,
(height - button_size.height()) / 2,
button_size.width(), button_size.height());
SizeToPreferredSize();
}
virtual gfx::Size GetPreferredSize() {
gfx::Rect total_bounds =
close_button_->bounds().Union(options_menu_button_->bounds());
return total_bounds.size();
}
// views::ViewMenuDelegate implements.
virtual void RunMenu(views::View* source, const gfx::Point& pt) {
CreateOptionsMenu();
views::MenuModelAdapter menu_model_adapter(options_menu_contents_.get());
menu_runner_.reset(new views::MenuRunner(menu_model_adapter.CreateMenu()));
gfx::Point screen_location;
views::View::ConvertPointToScreen(options_menu_button_, &screen_location);
if (menu_runner_->RunMenuAt(
source->GetWidget()->GetTopLevelWidget(), options_menu_button_,
gfx::Rect(screen_location, options_menu_button_->size()),
views::MenuItemView::TOPRIGHT, views::MenuRunner::HAS_MNEMONICS) ==
views::MenuRunner::MENU_DELETED)
return;
}
// views::ButtonListener implements.
virtual void ButtonPressed(views::Button* sender, const views::Event&) {
balloon_view_->Close(true);
}
// ui::SimpleMenuModel::Delegate impglements.
virtual bool IsCommandIdChecked(int /* command_id */) const {
// Nothing in the menu is checked.
return false;
}
virtual bool IsCommandIdEnabled(int /* command_id */) const {
// All the menu options are always enabled.
return true;
}
virtual bool GetAcceleratorForCommandId(
int /* command_id */, ui::Accelerator* /* accelerator */) {
// Currently no accelerators.
return false;
}
virtual void ExecuteCommand(int command_id) {
switch (command_id) {
case kRevokePermissionCommand:
balloon_view_->DenyPermission();
default:
NOTIMPLEMENTED();
}
}
private:
void CreateOptionsMenu() {
if (options_menu_contents_.get())
return;
const string16 source_label_text = l10n_util::GetStringFUTF16(
IDS_NOTIFICATION_BALLOON_SOURCE_LABEL,
balloon_view_->balloon_->notification().display_source());
const string16 label_text = l10n_util::GetStringFUTF16(
IDS_NOTIFICATION_BALLOON_REVOKE_MESSAGE,
balloon_view_->balloon_->notification().display_source());
options_menu_contents_.reset(new ui::SimpleMenuModel(this));
// TODO(oshima): Showing the source info in the menu for now.
// Figure out where to show the source info.
options_menu_contents_->AddItem(kNoopCommand, source_label_text);
options_menu_contents_->AddItem(kRevokePermissionCommand, label_text);
}
BalloonViewImpl* balloon_view_;
views::ImageButton* close_button_;
// The options menu.
scoped_ptr<ui::SimpleMenuModel> options_menu_contents_;
scoped_ptr<views::MenuRunner> menu_runner_;
views::MenuButton* options_menu_button_;
DISALLOW_COPY_AND_ASSIGN(NotificationControlView);
};
BalloonViewImpl::BalloonViewImpl(bool sticky, bool controls, bool web_ui)
: balloon_(NULL),
html_contents_(NULL),
stale_(false),
sticky_(sticky),
controls_(controls),
closed_(false),
web_ui_(web_ui) {
// This object is not to be deleted by the views hierarchy,
// as it is owned by the balloon.
set_parent_owned(false);
}
BalloonViewImpl::~BalloonViewImpl() {
if (control_view_host_.get()) {
control_view_host_->CloseNow();
}
if (html_contents_.get()) {
html_contents_->Shutdown();
}
}
////////////////////////////////////////////////////////////////////////////////
// BallonViewImpl, BalloonView implementation.
void BalloonViewImpl::Show(Balloon* balloon) {
balloon_ = balloon;
html_contents_.reset(new BalloonViewHost(balloon));
if (web_ui_)
html_contents_->EnableWebUI();
AddChildView(html_contents_->view());
notification_registrar_.Add(this,
chrome::NOTIFICATION_NOTIFY_BALLOON_DISCONNECTED,
content::Source<Balloon>(balloon));
}
void BalloonViewImpl::Update() {
stale_ = false;
if (!html_contents_->tab_contents())
return;
html_contents_->tab_contents()->GetController().LoadURL(
balloon_->notification().content_url(), content::Referrer(),
content::PAGE_TRANSITION_LINK, std::string());
}
void BalloonViewImpl::Close(bool by_user) {
closed_ = true;
MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&BalloonViewImpl::DelayedClose, AsWeakPtr(), by_user));
}
gfx::Size BalloonViewImpl::GetSize() const {
// Not used. The layout is managed by the Panel.
return gfx::Size(0, 0);
}
BalloonHost* BalloonViewImpl::GetHost() const {
return html_contents_.get();
}
void BalloonViewImpl::RepositionToBalloon() {
// Not used. The layout is managed by the Panel.
}
////////////////////////////////////////////////////////////////////////////////
// views::View interface overrides.
void BalloonViewImpl::Layout() {
gfx::Size size = balloon_->content_size();
SetBounds(x(), y(), size.width(), size.height());
html_contents_->view()->SetBounds(0, 0, size.width(), size.height());
if (html_contents_->tab_contents()) {
RenderWidgetHostView* view =
html_contents_->tab_contents()->GetRenderViewHost()->view();
if (view)
view->SetSize(size);
}
}
void BalloonViewImpl::ViewHierarchyChanged(
bool is_add, View* parent, View* child) {
if (is_add && GetWidget() && !control_view_host_.get() && controls_) {
control_view_host_.reset(new views::Widget);
views::Widget::InitParams params(
views::Widget::InitParams::TYPE_CONTROL);
params.double_buffer = true;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.parent = GetParentNativeView();
control_view_host_->Init(params);
NotificationControlView* control = new NotificationControlView(this);
control_view_host_->SetContentsView(control);
}
if (!is_add && this == child && control_view_host_.get() && controls_)
control_view_host_.release()->CloseNow();
}
gfx::Size BalloonViewImpl::GetPreferredSize() {
return gfx::Size(1000, 1000);
}
////////////////////////////////////////////////////////////////////////////////
// content::NotificationObserver overrides.
void BalloonViewImpl::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
if (type != chrome::NOTIFICATION_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,
chrome::NOTIFICATION_NOTIFY_BALLOON_DISCONNECTED,
content::Source<Balloon>(balloon_));
Close(false);
}
////////////////////////////////////////////////////////////////////////////////
// BalloonViewImpl public.
bool BalloonViewImpl::IsFor(const Notification& notification) const {
return balloon_->notification().notification_id() ==
notification.notification_id();
}
void BalloonViewImpl::Activated() {
if (!control_view_host_.get())
return;
// Get the size of Control View.
gfx::Size size =
control_view_host_->GetRootView()->child_at(0)->GetPreferredSize();
control_view_host_->Show();
control_view_host_->SetBounds(
gfx::Rect(width() - size.width() - kControlViewRightMargin,
kControlViewTopMargin,
size.width(), size.height()));
}
void BalloonViewImpl::Deactivated() {
if (control_view_host_.get()) {
control_view_host_->Hide();
}
}
////////////////////////////////////////////////////////////////////////////////
// BalloonViewImpl private.
void BalloonViewImpl::DelayedClose(bool by_user) {
html_contents_->Shutdown();
html_contents_.reset();
balloon_->OnClose(by_user);
}
void BalloonViewImpl::DenyPermission() {
DesktopNotificationService* service =
DesktopNotificationServiceFactory::GetForProfile(balloon_->profile());
service->DenyPermission(balloon_->notification().origin_url());
}
gfx::NativeView BalloonViewImpl::GetParentNativeView() {
RenderWidgetHostView* view =
html_contents_->tab_contents()->GetRenderViewHost()->view();
DCHECK(view);
return view->GetNativeView();
}
} // namespace chromeos