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 23 namespace base { 24 class RunLoop; 25 } 26 27 namespace views { 28 class Widget; 29 } 30 31 namespace ash { 32 class WebNotificationTrayTest; 33 FORWARD_DECLARE_TEST(WebNotificationTrayTest, ManyPopupNotifications); 34 } 35 36 namespace message_center { 37 namespace test { 38 class MessagePopupCollectionTest; 39 } 40 41 class MessageCenter; 42 class MessageCenterTray; 43 class ToastContentsView; 44 45 enum 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. 57 class 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