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