Home | History | Annotate | Download | only in views
      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