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