Home | History | Annotate | Download | only in notifications
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/chromeos/notifications/balloon_view.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/message_loop.h"
     10 #include "base/utf_string_conversions.h"
     11 #include "chrome/browser/chromeos/notifications/balloon_view_host.h"
     12 #include "chrome/browser/chromeos/notifications/notification_panel.h"
     13 #include "chrome/browser/notifications/balloon.h"
     14 #include "chrome/browser/notifications/desktop_notification_service.h"
     15 #include "chrome/browser/notifications/desktop_notification_service_factory.h"
     16 #include "chrome/browser/notifications/notification.h"
     17 #include "chrome/browser/profiles/profile.h"
     18 #include "chrome/browser/ui/views/notifications/balloon_view_host.h"
     19 #include "content/browser/renderer_host/render_view_host.h"
     20 #include "content/browser/renderer_host/render_widget_host_view.h"
     21 #include "content/common/notification_details.h"
     22 #include "content/common/notification_source.h"
     23 #include "content/common/notification_type.h"
     24 #include "grit/generated_resources.h"
     25 #include "grit/theme_resources.h"
     26 #include "ui/base/l10n/l10n_util.h"
     27 #include "ui/base/models/simple_menu_model.h"
     28 #include "ui/base/resource/resource_bundle.h"
     29 #include "views/background.h"
     30 #include "views/controls/button/button.h"
     31 #include "views/controls/button/image_button.h"
     32 #include "views/controls/button/menu_button.h"
     33 #include "views/controls/label.h"
     34 #include "views/controls/menu/menu_2.h"
     35 #include "views/controls/menu/view_menu_delegate.h"
     36 #include "views/widget/root_view.h"
     37 #include "views/widget/widget_gtk.h"
     38 
     39 namespace {
     40 // Menu commands
     41 const int kNoopCommand = 0;
     42 const int kRevokePermissionCommand = 1;
     43 
     44 // Vertical margin between close button and menu button.
     45 const int kControlButtonsMargin = 6;
     46 
     47 // Top, Right margin for notification control view.
     48 const int kControlViewTopMargin = 4;
     49 const int kControlViewRightMargin = 6;
     50 }  // namespace
     51 
     52 namespace chromeos {
     53 
     54 // NotificationControlView has close and menu buttons and
     55 // overlays on top of renderer view.
     56 class NotificationControlView : public views::View,
     57                                 public views::ViewMenuDelegate,
     58                                 public ui::SimpleMenuModel::Delegate,
     59                                 public views::ButtonListener {
     60  public:
     61   explicit NotificationControlView(BalloonViewImpl* view)
     62       : balloon_view_(view),
     63         close_button_(NULL),
     64         options_menu_contents_(NULL),
     65         options_menu_menu_(NULL),
     66         options_menu_button_(NULL) {
     67     // TODO(oshima): make background transparent.
     68     set_background(views::Background::CreateSolidBackground(SK_ColorWHITE));
     69 
     70     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
     71 
     72     SkBitmap* close_button_n = rb.GetBitmapNamed(IDR_TAB_CLOSE);
     73     SkBitmap* close_button_m = rb.GetBitmapNamed(IDR_TAB_CLOSE_MASK);
     74     SkBitmap* close_button_h = rb.GetBitmapNamed(IDR_TAB_CLOSE_H);
     75     SkBitmap* close_button_p = rb.GetBitmapNamed(IDR_TAB_CLOSE_P);
     76 
     77     close_button_ = new views::ImageButton(this);
     78     close_button_->SetImage(views::CustomButton::BS_NORMAL, close_button_n);
     79     close_button_->SetImage(views::CustomButton::BS_HOT, close_button_h);
     80     close_button_->SetImage(views::CustomButton::BS_PUSHED, close_button_p);
     81     close_button_->SetBackground(
     82         SK_ColorBLACK, close_button_n, close_button_m);
     83 
     84     AddChildView(close_button_);
     85 
     86     options_menu_button_
     87         = new views::MenuButton(NULL, std::wstring(), this, false);
     88     options_menu_button_->SetFont(rb.GetFont(ResourceBundle::SmallFont));
     89     options_menu_button_->SetIcon(*rb.GetBitmapNamed(IDR_NOTIFICATION_MENU));
     90     options_menu_button_->set_border(NULL);
     91 
     92     options_menu_button_->set_icon_placement(views::TextButton::ICON_ON_RIGHT);
     93     AddChildView(options_menu_button_);
     94 
     95     // The control view will never be resized, so just layout once.
     96     gfx::Size options_size = options_menu_button_->GetPreferredSize();
     97     gfx::Size button_size = close_button_->GetPreferredSize();
     98 
     99     int height = std::max(options_size.height(), button_size.height());
    100     options_menu_button_->SetBounds(
    101         0, (height - options_size.height()) / 2,
    102         options_size.width(), options_size.height());
    103 
    104     close_button_->SetBounds(
    105         options_size.width() + kControlButtonsMargin,
    106         (height - button_size.height()) / 2,
    107         button_size.width(), button_size.height());
    108 
    109     SizeToPreferredSize();
    110   }
    111 
    112   virtual gfx::Size GetPreferredSize() {
    113     gfx::Rect total_bounds =
    114         close_button_->bounds().Union(options_menu_button_->bounds());
    115     return total_bounds.size();
    116   }
    117 
    118   // views::ViewMenuDelegate implements.
    119   virtual void RunMenu(views::View* source, const gfx::Point& pt) {
    120     CreateOptionsMenu();
    121     options_menu_menu_->RunMenuAt(pt, views::Menu2::ALIGN_TOPRIGHT);
    122   }
    123 
    124   // views::ButtonListener implements.
    125   virtual void ButtonPressed(views::Button* sender, const views::Event&) {
    126     balloon_view_->Close(true);
    127   }
    128 
    129   // ui::SimpleMenuModel::Delegate impglements.
    130   virtual bool IsCommandIdChecked(int /* command_id */) const {
    131     // Nothing in the menu is checked.
    132     return false;
    133   }
    134 
    135   virtual bool IsCommandIdEnabled(int /* command_id */) const {
    136     // All the menu options are always enabled.
    137     return true;
    138   }
    139 
    140   virtual bool GetAcceleratorForCommandId(
    141       int /* command_id */, ui::Accelerator* /* accelerator */) {
    142     // Currently no accelerators.
    143     return false;
    144   }
    145 
    146   virtual void ExecuteCommand(int command_id) {
    147     switch (command_id) {
    148       case kRevokePermissionCommand:
    149         balloon_view_->DenyPermission();
    150       default:
    151         NOTIMPLEMENTED();
    152     }
    153   }
    154 
    155  private:
    156   void CreateOptionsMenu() {
    157     if (options_menu_contents_.get())
    158       return;
    159     const string16 source_label_text = l10n_util::GetStringFUTF16(
    160         IDS_NOTIFICATION_BALLOON_SOURCE_LABEL,
    161         balloon_view_->balloon_->notification().display_source());
    162     const string16 label_text = l10n_util::GetStringFUTF16(
    163         IDS_NOTIFICATION_BALLOON_REVOKE_MESSAGE,
    164         balloon_view_->balloon_->notification().display_source());
    165 
    166     options_menu_contents_.reset(new ui::SimpleMenuModel(this));
    167     // TODO(oshima): Showing the source info in the menu for now.
    168     // Figure out where to show the source info.
    169     options_menu_contents_->AddItem(kNoopCommand, source_label_text);
    170     options_menu_contents_->AddItem(kRevokePermissionCommand, label_text);
    171 
    172     options_menu_menu_.reset(new views::Menu2(options_menu_contents_.get()));
    173   }
    174 
    175   BalloonViewImpl* balloon_view_;
    176 
    177   views::ImageButton* close_button_;
    178 
    179   // The options menu.
    180   scoped_ptr<ui::SimpleMenuModel> options_menu_contents_;
    181   scoped_ptr<views::Menu2> options_menu_menu_;
    182   views::MenuButton* options_menu_button_;
    183 
    184   DISALLOW_COPY_AND_ASSIGN(NotificationControlView);
    185 };
    186 
    187 BalloonViewImpl::BalloonViewImpl(bool sticky, bool controls, bool web_ui)
    188     : balloon_(NULL),
    189       html_contents_(NULL),
    190       method_factory_(this),
    191       stale_(false),
    192       sticky_(sticky),
    193       controls_(controls),
    194       closed_(false),
    195       web_ui_(web_ui) {
    196   // This object is not to be deleted by the views hierarchy,
    197   // as it is owned by the balloon.
    198   set_parent_owned(false);
    199 }
    200 
    201 BalloonViewImpl::~BalloonViewImpl() {
    202   if (control_view_host_.get()) {
    203     control_view_host_->CloseNow();
    204   }
    205   if (html_contents_) {
    206     html_contents_->Shutdown();
    207   }
    208 }
    209 
    210 ////////////////////////////////////////////////////////////////////////////////
    211 // BallonViewImpl, BalloonView implementation.
    212 
    213 void BalloonViewImpl::Show(Balloon* balloon) {
    214   balloon_ = balloon;
    215   html_contents_ = new BalloonViewHost(balloon);
    216   if (web_ui_)
    217     html_contents_->EnableWebUI();
    218   AddChildView(html_contents_->view());
    219   notification_registrar_.Add(this,
    220     NotificationType::NOTIFY_BALLOON_DISCONNECTED, Source<Balloon>(balloon));
    221 }
    222 
    223 void BalloonViewImpl::Update() {
    224   stale_ = false;
    225   if (html_contents_->render_view_host())
    226     html_contents_->render_view_host()->NavigateToURL(
    227         balloon_->notification().content_url());
    228 }
    229 
    230 void BalloonViewImpl::Close(bool by_user) {
    231   closed_ = true;
    232   MessageLoop::current()->PostTask(
    233       FROM_HERE,
    234       method_factory_.NewRunnableMethod(
    235           &BalloonViewImpl::DelayedClose, by_user));
    236 }
    237 
    238 gfx::Size BalloonViewImpl::GetSize() const {
    239   // Not used. The layout is managed by the Panel.
    240   return gfx::Size(0, 0);
    241 }
    242 
    243 BalloonHost* BalloonViewImpl::GetHost() const {
    244   return html_contents_;
    245 }
    246 
    247 void BalloonViewImpl::RepositionToBalloon() {
    248   // Not used. The layout is managed by the Panel.
    249 }
    250 
    251 ////////////////////////////////////////////////////////////////////////////////
    252 // views::View interface overrides.
    253 
    254 void BalloonViewImpl::Layout() {
    255   gfx::Size size = balloon_->content_size();
    256 
    257   SetBounds(x(), y(), size.width(), size.height());
    258 
    259   html_contents_->view()->SetBounds(0, 0, size.width(), size.height());
    260   if (html_contents_->render_view_host()) {
    261     RenderWidgetHostView* view = html_contents_->render_view_host()->view();
    262     if (view)
    263       view->SetSize(size);
    264   }
    265 }
    266 
    267 void BalloonViewImpl::ViewHierarchyChanged(
    268     bool is_add, View* parent, View* child) {
    269   if (is_add && GetWidget() && !control_view_host_.get() && controls_) {
    270     views::Widget::CreateParams params(
    271         views::Widget::CreateParams::TYPE_CONTROL);
    272     params.delete_on_destroy = false;
    273     control_view_host_.reset(views::Widget::CreateWidget(params));
    274     static_cast<views::WidgetGtk*>(control_view_host_.get())->
    275         EnableDoubleBuffer(true);
    276     control_view_host_->Init(GetParentNativeView(), gfx::Rect());
    277     NotificationControlView* control = new NotificationControlView(this);
    278     control_view_host_->SetContentsView(control);
    279   }
    280   if (!is_add && this == child && control_view_host_.get() && controls_) {
    281     control_view_host_.release()->CloseNow();
    282   }
    283 }
    284 
    285 ////////////////////////////////////////////////////////////////////////////////
    286 // NotificationObserver overrides.
    287 
    288 void BalloonViewImpl::Observe(NotificationType type,
    289                               const NotificationSource& source,
    290                               const NotificationDetails& details) {
    291   if (type != NotificationType::NOTIFY_BALLOON_DISCONNECTED) {
    292     NOTREACHED();
    293     return;
    294   }
    295 
    296   // If the renderer process attached to this balloon is disconnected
    297   // (e.g., because of a crash), we want to close the balloon.
    298   notification_registrar_.Remove(this,
    299       NotificationType::NOTIFY_BALLOON_DISCONNECTED, Source<Balloon>(balloon_));
    300   Close(false);
    301 }
    302 
    303 ////////////////////////////////////////////////////////////////////////////////
    304 // BalloonViewImpl public.
    305 
    306 bool BalloonViewImpl::IsFor(const Notification& notification) const {
    307   return balloon_->notification().notification_id() ==
    308       notification.notification_id();
    309 }
    310 
    311 void BalloonViewImpl::Activated() {
    312   if (!control_view_host_.get())
    313     return;
    314 
    315   // Get the size of Control View.
    316   gfx::Size size =
    317       control_view_host_->GetRootView()->GetChildViewAt(0)->GetPreferredSize();
    318   control_view_host_->Show();
    319   control_view_host_->SetBounds(
    320       gfx::Rect(width() - size.width() - kControlViewRightMargin,
    321                 kControlViewTopMargin,
    322                 size.width(), size.height()));
    323 }
    324 
    325 void BalloonViewImpl::Deactivated() {
    326   if (control_view_host_.get()) {
    327     control_view_host_->Hide();
    328   }
    329 }
    330 
    331 ////////////////////////////////////////////////////////////////////////////////
    332 // BalloonViewImpl private.
    333 
    334 void BalloonViewImpl::DelayedClose(bool by_user) {
    335   html_contents_->Shutdown();
    336   html_contents_ = NULL;
    337   balloon_->OnClose(by_user);
    338 }
    339 
    340 void BalloonViewImpl::DenyPermission() {
    341   DesktopNotificationService* service =
    342       DesktopNotificationServiceFactory::GetForProfile(balloon_->profile());
    343   service->DenyPermission(balloon_->notification().origin_url());
    344 }
    345 
    346 gfx::NativeView BalloonViewImpl::GetParentNativeView() {
    347   RenderWidgetHostView* view = html_contents_->render_view_host()->view();
    348   DCHECK(view);
    349   return view->GetNativeView();
    350 }
    351 
    352 }  // namespace chromeos
    353