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