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