1// Copyright 2013 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/ui/views/message_center/message_center_widget_delegate.h"
6
7#include <complex>
8
9#include "chrome/browser/ui/views/message_center/message_center_frame_view.h"
10#include "chrome/browser/ui/views/message_center/web_notification_tray.h"
11#include "content/public/browser/user_metrics.h"
12#include "ui/accessibility/ax_view_state.h"
13#include "ui/gfx/screen.h"
14#include "ui/message_center/message_center_style.h"
15#include "ui/message_center/views/message_center_view.h"
16#include "ui/native_theme/native_theme.h"
17#include "ui/views/border.h"
18#include "ui/views/layout/box_layout.h"
19#include "ui/views/widget/widget.h"
20
21#if defined(OS_WIN)
22#include "ui/views/win/hwnd_util.h"
23#endif
24
25#if defined(USE_ASH)
26#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
27#endif
28
29namespace message_center {
30
31MessageCenterWidgetDelegate::MessageCenterWidgetDelegate(
32    WebNotificationTray* tray,
33    MessageCenterTray* mc_tray,
34    bool initially_settings_visible,
35    const PositionInfo& pos_info,
36    const base::string16& title)
37    : MessageCenterView(tray->message_center(),
38                        mc_tray,
39                        pos_info.max_height,
40                        initially_settings_visible,
41                        pos_info.message_center_alignment &
42                            ALIGNMENT_TOP,  // Show buttons on top if message
43                                            // center is top aligned
44                        title),
45      pos_info_(pos_info),
46      tray_(tray) {
47  // A WidgetDelegate should be deleted on DeleteDelegate.
48  set_owned_by_client();
49
50  views::BoxLayout* layout =
51      new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0);
52  layout->SetDefaultFlex(1);
53  SetLayoutManager(layout);
54
55  AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
56
57  SetPaintToLayer(true);
58  SetFillsBoundsOpaquely(true);
59
60  InitWidget();
61}
62
63MessageCenterWidgetDelegate::~MessageCenterWidgetDelegate() {
64  views::Widget* widget = GetWidget();
65  if (widget) {
66    widget->RemoveObserver(this);
67  }
68}
69
70views::View* MessageCenterWidgetDelegate::GetContentsView() {
71  return this;
72}
73
74views::NonClientFrameView*
75MessageCenterWidgetDelegate::CreateNonClientFrameView(views::Widget* widget) {
76  MessageCenterFrameView* frame_view = new MessageCenterFrameView();
77  border_insets_ = frame_view->GetInsets();
78  return frame_view;
79}
80
81void MessageCenterWidgetDelegate::DeleteDelegate() {
82  delete this;
83}
84
85views::Widget* MessageCenterWidgetDelegate::GetWidget() {
86  return View::GetWidget();
87}
88
89const views::Widget* MessageCenterWidgetDelegate::GetWidget() const {
90  return View::GetWidget();
91}
92
93void MessageCenterWidgetDelegate::OnWidgetActivationChanged(
94    views::Widget* widget,
95    bool active) {
96  // Some Linux users set 'focus-follows-mouse' where the activation is lost
97  // immediately after the mouse exists from the bubble, which is a really bad
98  // experience. Disable hiding until the bug around the focus is fixed.
99  // TODO(erg, pkotwicz): fix the activation issue and then remove this ifdef.
100#if !defined(OS_LINUX)
101  if (!active) {
102    tray_->SendHideMessageCenter();
103  }
104#endif
105}
106
107void MessageCenterWidgetDelegate::OnWidgetClosing(views::Widget* widget) {
108  SetIsClosing(true);
109  tray_->MarkMessageCenterHidden();
110}
111
112void MessageCenterWidgetDelegate::PreferredSizeChanged() {
113  GetWidget()->SetBounds(GetMessageCenterBounds());
114  views::View::PreferredSizeChanged();
115}
116
117gfx::Size MessageCenterWidgetDelegate::GetPreferredSize() const {
118  int preferred_width = kNotificationWidth + 2 * kMarginBetweenItems;
119  return gfx::Size(preferred_width, GetHeightForWidth(preferred_width));
120}
121
122gfx::Size MessageCenterWidgetDelegate::GetMaximumSize() const {
123  gfx::Size size = GetPreferredSize();
124  return size;
125}
126
127int MessageCenterWidgetDelegate::GetHeightForWidth(int width) const {
128  int height = MessageCenterView::GetHeightForWidth(width);
129  return (pos_info_.max_height != 0) ?
130    std::min(height, pos_info_.max_height - border_insets_.height()) : height;
131}
132
133bool MessageCenterWidgetDelegate::AcceleratorPressed(
134    const ui::Accelerator& accelerator) {
135  if (accelerator.key_code() != ui::VKEY_ESCAPE)
136    return false;
137  tray_->SendHideMessageCenter();
138  return true;
139}
140
141void MessageCenterWidgetDelegate::InitWidget() {
142  views::Widget* widget = new views::Widget();
143  views::Widget::InitParams params(views::Widget::InitParams::TYPE_BUBBLE);
144  params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
145  params.delegate = this;
146  params.keep_on_top = true;
147#if defined(USE_ASH)
148  // This class is not used in Ash; there is another container for the message
149  // center that's used there.  So, we must be in a Views + Ash environment.  We
150  // want the notification center to be available on both desktops.  Setting the
151  // |native_widget| variable here ensures that the widget is hosted on the
152  // native desktop.
153  params.native_widget = new views::DesktopNativeWidgetAura(widget);
154#endif
155  widget->Init(params);
156
157  widget->AddObserver(this);
158  widget->StackAtTop();
159  widget->SetAlwaysOnTop(true);
160
161  const NotificationList::Notifications& notifications =
162      tray_->message_center()->GetVisibleNotifications();
163  SetNotifications(notifications);
164
165  widget->SetBounds(GetMessageCenterBounds());
166  widget->Show();
167  widget->Activate();
168}
169
170gfx::Point MessageCenterWidgetDelegate::GetCorrectedAnchor(
171    gfx::Size calculated_size) {
172  gfx::Point corrected_anchor = pos_info_.inital_anchor_point;
173
174  // Inset the width slightly so that the click point is not exactly on the edge
175  // of the message center but somewhere within the middle 60 %.
176  int insetted_width = (calculated_size.width() * 4) / 5;
177
178  if (pos_info_.taskbar_alignment == ALIGNMENT_TOP ||
179      pos_info_.taskbar_alignment == ALIGNMENT_BOTTOM) {
180    int click_point_x = tray_->mouse_click_point().x();
181
182    if (pos_info_.message_center_alignment & ALIGNMENT_RIGHT) {
183      int opposite_x_corner =
184          pos_info_.inital_anchor_point.x() - insetted_width;
185
186      // If the click point is outside the x axis length of the message center,
187      // push the message center towards the left to align with the click point.
188      if (opposite_x_corner > click_point_x)
189        corrected_anchor.set_x(pos_info_.inital_anchor_point.x() -
190                               (opposite_x_corner - click_point_x));
191    } else {
192      int opposite_x_corner =
193          pos_info_.inital_anchor_point.x() + insetted_width;
194
195      if (opposite_x_corner < click_point_x)
196        corrected_anchor.set_x(pos_info_.inital_anchor_point.x() +
197                               (click_point_x - opposite_x_corner));
198    }
199  } else if (pos_info_.taskbar_alignment == ALIGNMENT_LEFT ||
200             pos_info_.taskbar_alignment == ALIGNMENT_RIGHT) {
201    int click_point_y = tray_->mouse_click_point().y();
202
203    if (pos_info_.message_center_alignment & ALIGNMENT_BOTTOM) {
204      int opposite_y_corner =
205          pos_info_.inital_anchor_point.y() - insetted_width;
206
207      // If the click point is outside the y axis length of the message center,
208      // push the message center upwards to align with the click point.
209      if (opposite_y_corner > click_point_y)
210        corrected_anchor.set_y(pos_info_.inital_anchor_point.y() -
211                               (opposite_y_corner - click_point_y));
212    } else {
213      int opposite_y_corner =
214          pos_info_.inital_anchor_point.y() + insetted_width;
215
216      if (opposite_y_corner < click_point_y)
217        corrected_anchor.set_y(pos_info_.inital_anchor_point.y() +
218                               (click_point_y - opposite_y_corner));
219    }
220  }
221  return corrected_anchor;
222}
223
224gfx::Rect MessageCenterWidgetDelegate::GetMessageCenterBounds() {
225  gfx::Size size = GetPreferredSize();
226
227  // Make space for borders on sides.
228  size.Enlarge(border_insets_.width(), border_insets_.height());
229  gfx::Rect bounds(size);
230
231  gfx::Point corrected_anchor = GetCorrectedAnchor(size);
232
233  if (pos_info_.message_center_alignment & ALIGNMENT_TOP)
234    bounds.set_y(corrected_anchor.y());
235  if (pos_info_.message_center_alignment & ALIGNMENT_BOTTOM)
236    bounds.set_y(corrected_anchor.y() - size.height());
237  if (pos_info_.message_center_alignment & ALIGNMENT_LEFT)
238    bounds.set_x(corrected_anchor.x());
239  if (pos_info_.message_center_alignment & ALIGNMENT_RIGHT)
240    bounds.set_x(corrected_anchor.x() - size.width());
241
242  return bounds;
243}
244
245}  // namespace message_center
246