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