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