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 39namespace { 40// Menu commands 41const int kNoopCommand = 0; 42const int kRevokePermissionCommand = 1; 43 44// Vertical margin between close button and menu button. 45const int kControlButtonsMargin = 6; 46 47// Top, Right margin for notification control view. 48const int kControlViewTopMargin = 4; 49const int kControlViewRightMargin = 6; 50} // namespace 51 52namespace chromeos { 53 54// NotificationControlView has close and menu buttons and 55// overlays on top of renderer view. 56class 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 187BalloonViewImpl::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 201BalloonViewImpl::~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 213void 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 223void 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 230void 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 238gfx::Size BalloonViewImpl::GetSize() const { 239 // Not used. The layout is managed by the Panel. 240 return gfx::Size(0, 0); 241} 242 243BalloonHost* BalloonViewImpl::GetHost() const { 244 return html_contents_; 245} 246 247void BalloonViewImpl::RepositionToBalloon() { 248 // Not used. The layout is managed by the Panel. 249} 250 251//////////////////////////////////////////////////////////////////////////////// 252// views::View interface overrides. 253 254void 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 267void 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 288void 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 306bool BalloonViewImpl::IsFor(const Notification& notification) const { 307 return balloon_->notification().notification_id() == 308 notification.notification_id(); 309} 310 311void 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 325void BalloonViewImpl::Deactivated() { 326 if (control_view_host_.get()) { 327 control_view_host_->Hide(); 328 } 329} 330 331//////////////////////////////////////////////////////////////////////////////// 332// BalloonViewImpl private. 333 334void BalloonViewImpl::DelayedClose(bool by_user) { 335 html_contents_->Shutdown(); 336 html_contents_ = NULL; 337 balloon_->OnClose(by_user); 338} 339 340void BalloonViewImpl::DenyPermission() { 341 DesktopNotificationService* service = 342 DesktopNotificationServiceFactory::GetForProfile(balloon_->profile()); 343 service->DenyPermission(balloon_->notification().origin_url()); 344} 345 346gfx::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