1// Copyright (c) 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#ifndef UI_MESSAGE_CENTER_VIEWS_MESSAGE_POPUP_COLLECTION_H_
6#define UI_MESSAGE_CENTER_VIEWS_MESSAGE_POPUP_COLLECTION_H_
7
8#include <list>
9#include <map>
10
11#include "base/compiler_specific.h"
12#include "base/gtest_prod_util.h"
13#include "base/memory/weak_ptr.h"
14#include "base/timer/timer.h"
15#include "ui/gfx/display.h"
16#include "ui/gfx/display_observer.h"
17#include "ui/gfx/native_widget_types.h"
18#include "ui/gfx/rect.h"
19#include "ui/message_center/message_center_export.h"
20#include "ui/message_center/message_center_observer.h"
21#include "ui/views/widget/widget_observer.h"
22
23namespace base {
24class RunLoop;
25}
26
27namespace views {
28class Widget;
29}
30
31namespace ash {
32class WebNotificationTrayTest;
33FORWARD_DECLARE_TEST(WebNotificationTrayTest, ManyPopupNotifications);
34}
35
36namespace message_center {
37namespace test {
38class MessagePopupCollectionTest;
39}
40
41class MessageCenter;
42class MessageCenterTray;
43class ToastContentsView;
44
45enum PopupAlignment {
46  POPUP_ALIGNMENT_TOP = 1 << 0,
47  POPUP_ALIGNMENT_LEFT = 1 << 1,
48  POPUP_ALIGNMENT_BOTTOM = 1 << 2,
49  POPUP_ALIGNMENT_RIGHT = 1 << 3,
50};
51
52// Container for popup toasts. Because each toast is a frameless window rather
53// than a view in a bubble, now the container just manages all of those toasts.
54// This is similar to chrome/browser/notifications/balloon_collection, but the
55// contents of each toast are for the message center and layout strategy would
56// be slightly different.
57class MESSAGE_CENTER_EXPORT MessagePopupCollection
58    : public MessageCenterObserver,
59      public gfx::DisplayObserver,
60      public base::SupportsWeakPtr<MessagePopupCollection> {
61 public:
62  // |parent| specifies the parent widget of the toast windows. The default
63  // parent will be used for NULL. Usually each icon is spacing against its
64  // predecessor. If |first_item_has_no_margin| is set however the first item
65  // does not space against the tray.
66  MessagePopupCollection(gfx::NativeView parent,
67                         MessageCenter* message_center,
68                         MessageCenterTray* tray,
69                         bool first_item_has_no_margin);
70  virtual ~MessagePopupCollection();
71
72  // Called by ToastContentsView when its window is closed.
73  void RemoveToast(ToastContentsView* toast);
74
75  // Since these events are really coming from individual toast widgets,
76  // it helps to be able to keep track of the sender.
77  void OnMouseEntered(ToastContentsView* toast_entered);
78  void OnMouseExited(ToastContentsView* toast_exited);
79
80  // Invoked by toasts when they start/finish their animations.
81  // While "defer counter" is greater then zero, the popup collection does
82  // not perform updates. It is used to wait for various animations and user
83  // actions like serial closing of the toasts, when the remaining toasts "flow
84  // under the mouse".
85  void IncrementDeferCounter();
86  void DecrementDeferCounter();
87
88  // Runs the next step in update/animate sequence, if the defer counter is not
89  // zero. Otherwise, simply waits when it becomes zero.
90  void DoUpdateIfPossible();
91
92  // Updates |work_area_| and re-calculates the alignment of notification toasts
93  // rearranging them if necessary.
94  // This is separated from methods from OnDisplayBoundsChanged(), since
95  // sometimes the display info has to be specified directly. One example is
96  // shelf's auto-hide change. When the shelf in ChromeOS is temporarily shown
97  // from auto hide status, it doesn't change the display's work area but the
98  // actual work area for toasts should be resized.
99  void SetDisplayInfo(const gfx::Rect& work_area,
100                      const gfx::Rect& screen_bounds);
101
102  // Overridden from gfx::DislayObserver:
103  virtual void OnDisplayBoundsChanged(const gfx::Display& display) OVERRIDE;
104  virtual void OnDisplayAdded(const gfx::Display& new_display) OVERRIDE;
105  virtual void OnDisplayRemoved(const gfx::Display& old_display) OVERRIDE;
106
107 private:
108  FRIEND_TEST_ALL_PREFIXES(ash::WebNotificationTrayTest,
109                           ManyPopupNotifications);
110  friend class test::MessagePopupCollectionTest;
111  friend class ash::WebNotificationTrayTest;
112  typedef std::list<ToastContentsView*> Toasts;
113
114  void CloseAllWidgets();
115
116  // Returns the x-origin for the given toast bounds in the current work area.
117  int GetToastOriginX(const gfx::Rect& toast_bounds);
118
119  // Iterates toasts and starts closing the expired ones.
120  void CloseToasts();
121
122  // Creates new widgets for new toast notifications, and updates |toasts_| and
123  // |widgets_| correctly.
124  void UpdateWidgets();
125
126  // Repositions all of the widgets based on the current work area.
127  void RepositionWidgets();
128
129  // Repositions widgets to the top edge of the notification toast that was
130  // just removed, so that the user can click close button without mouse moves.
131  // See crbug.com/224089
132  void RepositionWidgetsWithTarget();
133
134  void ComputePopupAlignment(gfx::Rect work_area, gfx::Rect screen_bounds);
135
136  // The base line is an (imaginary) line that would touch the bottom of the
137  // next created notification if bottom-aligned or its top if top-aligned.
138  int GetBaseLine(ToastContentsView* last_toast);
139
140  // Overridden from MessageCenterObserver:
141  virtual void OnNotificationAdded(const std::string& notification_id) OVERRIDE;
142  virtual void OnNotificationRemoved(const std::string& notification_id,
143                                     bool by_user) OVERRIDE;
144  virtual void OnNotificationUpdated(
145      const std::string& notification_id) OVERRIDE;
146
147  ToastContentsView* FindToast(const std::string& notification_id);
148
149  // While the toasts are animated, avoid updating the collection, to reduce
150  // user confusion. Instead, update the collection when all animations are
151  // done. This method is run when defer counter is zero, may initiate next
152  // update/animation step.
153  void PerformDeferredTasks();
154  void OnDeferTimerExpired();
155
156  // "ForTest" methods.
157  views::Widget* GetWidgetForTest(const std::string& id);
158  void RunLoopForTest();
159  gfx::Rect GetToastRectAt(size_t index);
160
161  gfx::NativeView parent_;
162  MessageCenter* message_center_;
163  MessageCenterTray* tray_;
164  Toasts toasts_;
165  gfx::Rect work_area_;
166  int64 display_id_;
167
168  // Specifies which corner of the screen popups should show up. This should
169  // ideally be the same corner the notification area (systray) is at.
170  PopupAlignment alignment_;
171
172  int defer_counter_;
173
174  // This is only used to compare with incoming events, do not assume that
175  // the toast will be valid if this pointer is non-NULL.
176  ToastContentsView* latest_toast_entered_;
177
178  // Denotes a mode when user is clicking the Close button of toasts in a
179  // sequence, w/o moving the mouse. We reposition the toasts so the next one
180  // happens to be right under the mouse, and the user can just dispose of
181  // multipel toasts by clicking. The mode ends when defer_timer_ expires.
182  bool user_is_closing_toasts_by_clicking_;
183  scoped_ptr<base::OneShotTimer<MessagePopupCollection> > defer_timer_;
184  // The top edge to align the position of the next toast during 'close by
185  // clicking" mode.
186  // Only to be used when user_is_closing_toasts_by_clicking_ is true.
187  int target_top_edge_;
188
189  // Weak, only exists temporarily in tests.
190  scoped_ptr<base::RunLoop> run_loop_for_test_;
191
192  // True if the first item should not have spacing against the tray.
193  bool first_item_has_no_margin_;
194
195  DISALLOW_COPY_AND_ASSIGN(MessagePopupCollection);
196};
197
198}  // namespace message_center
199
200#endif // UI_MESSAGE_CENTER_VIEWS_MESSAGE_POPUP_COLLECTION_H_
201