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