diff options
author | oshima@chromium.org <oshima@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-12 21:04:39 +0000 |
---|---|---|
committer | oshima@chromium.org <oshima@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-12 21:04:39 +0000 |
commit | 3a9c26f147ab16179197bd4460f4ec6c476afcf7 (patch) | |
tree | aa850e93e76b3549550ae5d4d1e2bccbd07083b1 | |
parent | e512583b79e366db399a6566964412bb6e5af823 (diff) | |
download | chromium_src-3a9c26f147ab16179197bd4460f4ec6c476afcf7.zip chromium_src-3a9c26f147ab16179197bd4460f4ec6c476afcf7.tar.gz chromium_src-3a9c26f147ab16179197bd4460f4ec6c476afcf7.tar.bz2 |
Autoslide/hide notifications after timeout.
* Introduced panel's state to manage which notifications will be shown.'
* Added "OnPanelStateChanged" to PanelController::Delegate method to monitor the panel's state.
* Removed unnecessary SetSize in BalloonViewImpl. The size is set in BalloonViewImpl::Layout.
BUG=33306
TEST=This requires chromeos's wm and system notification to test (which does not exit yet).
None for now but I am writing unittest now.
Review URL: http://codereview.chromium.org/874004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@41484 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/chromeos/frame/panel_browser_view.h | 1 | ||||
-rw-r--r-- | chrome/browser/chromeos/frame/panel_controller.cc | 19 | ||||
-rw-r--r-- | chrome/browser/chromeos/frame/panel_controller.h | 10 | ||||
-rw-r--r-- | chrome/browser/chromeos/notifications/balloon_collection_impl.cc | 4 | ||||
-rw-r--r-- | chrome/browser/chromeos/notifications/balloon_view.cc | 7 | ||||
-rw-r--r-- | chrome/browser/chromeos/notifications/balloon_view.h | 12 | ||||
-rw-r--r-- | chrome/browser/chromeos/notifications/notification_panel.cc | 195 | ||||
-rw-r--r-- | chrome/browser/chromeos/notifications/notification_panel.h | 62 | ||||
-rw-r--r-- | chrome/browser/views/notifications/balloon_view_host.cc | 1 | ||||
-rw-r--r-- | views/view.cc | 6 | ||||
-rw-r--r-- | views/view.h | 3 |
11 files changed, 295 insertions, 25 deletions
diff --git a/chrome/browser/chromeos/frame/panel_browser_view.h b/chrome/browser/chromeos/frame/panel_browser_view.h index 0bd7c6f8..5e8b770 100644 --- a/chrome/browser/chromeos/frame/panel_browser_view.h +++ b/chrome/browser/chromeos/frame/panel_browser_view.h @@ -32,6 +32,7 @@ class PanelBrowserView : public BrowserView, virtual string16 GetPanelTitle(); virtual SkBitmap GetPanelIcon(); virtual void ClosePanel(); + virtual void OnPanelStateChanged(PanelController::State state) {} private: // Controls interactions with the window manager for popup panels. diff --git a/chrome/browser/chromeos/frame/panel_controller.cc b/chrome/browser/chromeos/frame/panel_controller.cc index 8f2d1b7..4008342 100644 --- a/chrome/browser/chromeos/frame/panel_controller.cc +++ b/chrome/browser/chromeos/frame/panel_controller.cc @@ -166,10 +166,8 @@ void PanelController::TitleMouseReleased( mouse_down_ = false; if (!dragging_) { - WmIpc::Message msg(WmIpc::Message::WM_SET_PANEL_STATE); - msg.set_param(0, panel_xid_); - msg.set_param(1, expanded_ ? 0 : 1); - WmIpc::instance()->SendMessage(msg); + SetState(expanded_ ? + PanelController::MINIMIZED : PanelController::EXPANDED); } else { WmIpc::Message msg(WmIpc::Message::WM_NOTIFY_PANEL_DRAG_COMPLETE); msg.set_param(0, panel_xid_); @@ -178,6 +176,13 @@ void PanelController::TitleMouseReleased( } } +void PanelController::SetState(State state) { + WmIpc::Message msg(WmIpc::Message::WM_SET_PANEL_STATE); + msg.set_param(0, panel_xid_); + msg.set_param(1, state == EXPANDED); + WmIpc::instance()->SendMessage(msg); +} + bool PanelController::TitleMouseDragged(const views::MouseEvent& event) { if (!mouse_down_) { return false; @@ -230,7 +235,11 @@ bool PanelController::PanelClientEvent(GdkEventClient* event) { WmIpc::Message msg; WmIpc::instance()->DecodeMessage(*event, &msg); if (msg.type() == WmIpc::Message::CHROME_NOTIFY_PANEL_STATE) { - expanded_ = msg.param(0); + bool new_state = msg.param(0); + if (expanded_ != new_state) { + expanded_ = new_state; + delegate_->OnPanelStateChanged(new_state ? EXPANDED : MINIMIZED); + } } return true; } diff --git a/chrome/browser/chromeos/frame/panel_controller.h b/chrome/browser/chromeos/frame/panel_controller.h index cac3ed0..5a78569 100644 --- a/chrome/browser/chromeos/frame/panel_controller.h +++ b/chrome/browser/chromeos/frame/panel_controller.h @@ -26,6 +26,11 @@ namespace chromeos { // Controls interactions with the WM for popups / panels. class PanelController : public views::ButtonListener { public: + enum State { + EXPANDED, + MINIMIZED, + }; + // Delegate to control panel's appearance and behavior. class Delegate { public: @@ -37,6 +42,8 @@ class PanelController : public views::ButtonListener { // Close the panel. Called when a close button is pressed. virtual void ClosePanel() = 0; + + virtual void OnPanelStateChanged(State state) = 0; }; PanelController(Delegate* delegate_window, @@ -53,6 +60,9 @@ class PanelController : public views::ButtonListener { void UpdateTitleBar(); void Close(); + + void SetState(State state); + // ButtonListener methods. virtual void ButtonPressed(views::Button* sender, const views::Event& event); diff --git a/chrome/browser/chromeos/notifications/balloon_collection_impl.cc b/chrome/browser/chromeos/notifications/balloon_collection_impl.cc index 590e430..d4e073c 100644 --- a/chrome/browser/chromeos/notifications/balloon_collection_impl.cc +++ b/chrome/browser/chromeos/notifications/balloon_collection_impl.cc @@ -36,6 +36,10 @@ BalloonCollectionImpl::BalloonCollectionImpl() } BalloonCollectionImpl::~BalloonCollectionImpl() { + // We need to remove the panel first because deleting + // views that are not owned by parent will not remove + // themselves from the parent. + panel_.reset(); STLDeleteElements(&balloons_); } diff --git a/chrome/browser/chromeos/notifications/balloon_view.cc b/chrome/browser/chromeos/notifications/balloon_view.cc index ae4c8f5..4d33e02 100644 --- a/chrome/browser/chromeos/notifications/balloon_view.cc +++ b/chrome/browser/chromeos/notifications/balloon_view.cc @@ -42,13 +42,17 @@ BalloonViewImpl::BalloonViewImpl() close_button_(NULL), options_menu_contents_(NULL), options_menu_menu_(NULL), - options_menu_button_(NULL) { + options_menu_button_(NULL), + stale_(false) { // This object is not to be deleted by the views hierarchy, // as it is owned by the balloon. set_parent_owned(false); } BalloonViewImpl::~BalloonViewImpl() { + if (html_contents_) { + html_contents_->Shutdown(); + } } //////////////////////////////////////////////////////////////////////////////// @@ -229,6 +233,7 @@ void BalloonViewImpl::CreateOptionsMenu() { void BalloonViewImpl::DelayedClose(bool by_user) { html_contents_->Shutdown(); + html_contents_ = NULL; balloon_->OnClose(by_user); } diff --git a/chrome/browser/chromeos/notifications/balloon_view.h b/chrome/browser/chromeos/notifications/balloon_view.h index ff3fcc4..9eb5378 100644 --- a/chrome/browser/chromeos/notifications/balloon_view.h +++ b/chrome/browser/chromeos/notifications/balloon_view.h @@ -55,6 +55,16 @@ class BalloonViewImpl : public BalloonView, void RepositionToBalloon(); gfx::Size GetSize() const; + // True if the notification is stale. False if the notification is new. + bool stale() { + return stale_; + } + + // Makes the notification stable. + void make_stale() { + stale_ = true; + } + private: // views::View interface. virtual gfx::Size GetPreferredSize() { @@ -104,7 +114,7 @@ class BalloonViewImpl : public BalloonView, scoped_ptr<menus::SimpleMenuModel> options_menu_contents_; scoped_ptr<views::Menu2> options_menu_menu_; views::MenuButton* options_menu_button_; - + bool stale_; NotificationRegistrar notification_registrar_; DISALLOW_COPY_AND_ASSIGN(BalloonViewImpl); diff --git a/chrome/browser/chromeos/notifications/notification_panel.cc b/chrome/browser/chromeos/notifications/notification_panel.cc index 9f980fe..f16f690 100644 --- a/chrome/browser/chromeos/notifications/notification_panel.cc +++ b/chrome/browser/chromeos/notifications/notification_panel.cc @@ -13,6 +13,7 @@ #include "grit/generated_resources.h" #include "views/background.h" #include "views/controls/scroll_view.h" +#include "views/widget/root_view.h" #include "views/widget/widget_gtk.h" namespace { @@ -26,6 +27,11 @@ const int kBalloonMaxHeight = 120; // TODO(oshima): Get this from system's metrics. const int kMaxPanelHeight = 400; +// The duration for a new notification to become stale. +const int kStaleTimeoutInSeconds = 10; + +using chromeos::BalloonViewImpl; + class BalloonSubContainer : public views::View { public: explicit BalloonSubContainer(int margin) @@ -67,6 +73,44 @@ class BalloonSubContainer : public views::View { SizeToPreferredSize(); } + // Returns the bounds that covers new notifications. + gfx::Rect GetNewBounds() { + gfx::Rect rect; + for (int i = GetChildViewCount() - 1; i >= 0; --i) { + BalloonViewImpl* view = + static_cast<BalloonViewImpl*>(GetChildViewAt(i)); + if (!view->stale()) { + if (rect.IsEmpty()) { + rect = view->bounds(); + } else { + rect = rect.Union(bounds()); + } + } + } + return gfx::Rect(x(), y(), rect.width(), rect.height()); + } + + // Returns # of new notifications. + int GetNewCount() { + int count = 0; + for (int i = GetChildViewCount() - 1; i >= 0; --i) { + BalloonViewImpl* view = + static_cast<BalloonViewImpl*>(GetChildViewAt(i)); + if (!view->stale()) + count++; + } + return count; + } + + // Make all notifications stale. + void MakeAllStale() { + for (int i = GetChildViewCount() - 1; i >= 0; --i) { + BalloonViewImpl* view = + static_cast<BalloonViewImpl*>(GetChildViewAt(i)); + view->make_stale(); + } + } + private: gfx::Size preferred_size_; int margin_; @@ -105,6 +149,17 @@ class BalloonContainer : public views::View { return preferred_size_; } + // Returns the size that covers sticky and new notifications. + gfx::Size GetStickyNewSize() { + gfx::Rect new_sticky = sticky_container_->bounds(); + gfx::Rect new_non_sticky = non_sticky_container_->GetNewBounds(); + if (new_sticky.IsEmpty()) + return new_non_sticky.size(); + if (new_non_sticky.IsEmpty()) + return new_sticky.size(); + return new_sticky.Union(new_non_sticky).size(); + } + // Add a ballon to the panel. void Add(Balloon* balloon) { BalloonViewImpl* view = @@ -125,6 +180,17 @@ class BalloonContainer : public views::View { non_sticky_container_->GetChildViewCount(); } + // Returns true if the container has sticky notification. + bool HasStickyNotifications() { + return sticky_container_->GetChildViewCount() > 0; + } + + // Returns true if the |view| is contained in the panel. + bool HasBalloonView(View* view) { + return sticky_container_->HasChildView(view) || + non_sticky_container_->HasChildView(view); + } + // Update the bounds so that all notifications are visible. void UpdateBounds() { sticky_container_->UpdateBounds(); @@ -141,6 +207,11 @@ class BalloonContainer : public views::View { SizeToPreferredSize(); } + void MakeAllStale() { + sticky_container_->MakeAllStale(); + non_sticky_container_->MakeAllStale(); + } + private: BalloonSubContainer* GetContainerFor(Balloon* balloon) const { return balloon->notification().sticky() ? @@ -158,11 +229,14 @@ class BalloonContainer : public views::View { }; NotificationPanel::NotificationPanel() - : balloon_container_(NULL) { + : balloon_container_(NULL), + state_(CLOSED), + task_factory_(this) { Init(); } NotificationPanel::~NotificationPanel() { + Hide(); } //////////////////////////////////////////////////////////////////////////////// @@ -173,18 +247,33 @@ void NotificationPanel::Show() { // TODO(oshima): Using window because Popup widget behaves weird // when resizing. This needs to be investigated. panel_widget_.reset(new views::WidgetGtk(views::WidgetGtk::TYPE_WINDOW)); - panel_widget_->Init(NULL, GetPreferredBounds()); + gfx::Rect bounds = GetPreferredBounds(); + if (bounds.width() < kBalloonMinWidth || + bounds.height() < kBalloonMinHeight) { + // Gtk uses its own default size when the size is empty. + // Use the minimum size as a default. + bounds.SetRect(0, 0, kBalloonMinWidth, kBalloonMinHeight); + } + panel_widget_->Init(NULL, bounds); + // TODO(oshima): I needed the following code in order to get sizing + // reliably. Investigate and fix it in WidgetGtk. + gtk_widget_set_size_request(GTK_WIDGET(panel_widget_->GetNativeView()), + bounds.width(), bounds.height()); panel_widget_->SetContentsView(scroll_view_.get()); panel_controller_.reset( new PanelController(this, GTK_WINDOW(panel_widget_->GetNativeView()), - gfx::Rect(0, 0, 1000, 1000))); + gfx::Rect(0, 0, kBalloonMinWidth, 1))); } panel_widget_->Show(); } void NotificationPanel::Hide() { if (panel_widget_.get()) { + // We need to remove & detach the scroll view from hierarchy to + // avoid GTK deleting child. + // TODO(oshima): handle this details in WidgetGtk. + panel_widget_->GetRootView()->RemoveChildView(scroll_view_.get()); panel_widget_.release()->Close(); panel_controller_.release()->Close(); } @@ -195,13 +284,19 @@ void NotificationPanel::Hide() { void NotificationPanel::Add(Balloon* balloon) { balloon_container_->Add(balloon); - UpdateSize(); + if (state_ == CLOSED || state_ == MINIMIZED) + state_ = STICKY_AND_NEW; Show(); + UpdatePanel(true); + StartStaleTimer(balloon); } void NotificationPanel::Remove(Balloon* balloon) { balloon_container_->Remove(balloon); - UpdateSize(); + // no change to the state + if (balloon_container_->GetNotificationCount() == 0) + state_ = CLOSED; + UpdatePanel(true); } void NotificationPanel::ResizeNotification( @@ -214,7 +309,7 @@ void NotificationPanel::ResizeNotification( std::min(kBalloonMaxHeight, size.height()))); balloon->set_content_size(real_size); static_cast<BalloonViewImpl*>(balloon->view())->Layout(); - UpdateSize(); + UpdatePanel(true); } //////////////////////////////////////////////////////////////////////////////// @@ -229,7 +324,28 @@ SkBitmap NotificationPanel::GetPanelIcon() { } void NotificationPanel::ClosePanel() { - panel_widget_.release()->Close(); + state_ = CLOSED; + UpdatePanel(false); +} + +void NotificationPanel::OnPanelStateChanged(PanelController::State state) { + switch (state) { + case PanelController::EXPANDED: + // Geting expanded in STICKY_AND_NEW state means that a new + // notification is added, so just leave the state. Otherwise, + // expand to full. + if (state_ != STICKY_AND_NEW) + state_ = FULL; + // When the panel is to be expanded, we either show all, or + // show only sticky/new, depending on the state. + UpdatePanel(false); + break; + case PanelController::MINIMIZED: + state_ = MINIMIZED; + // Make all notifications stale when a user minimize the panel. + balloon_container_->MakeAllStale(); + break; + } } //////////////////////////////////////////////////////////////////////////////// @@ -246,14 +362,33 @@ void NotificationPanel::Init() { scroll_view_->SetContents(balloon_container_); } -void NotificationPanel::UpdateSize() { - balloon_container_->UpdateBounds(); - scroll_view_->Layout(); - if (panel_widget_.get()) { - if (balloon_container_->GetNotificationCount() == 0) +void NotificationPanel::UpdatePanel(bool contents_changed) { + if (contents_changed) { + balloon_container_->UpdateBounds(); + scroll_view_->Layout(); + } + switch(state_) { + case CLOSED: + balloon_container_->MakeAllStale(); Hide(); - else - panel_widget_->SetBounds(GetPreferredBounds()); + break; + case MINIMIZED: + balloon_container_->MakeAllStale(); + if (panel_controller_.get()) + panel_controller_->SetState(PanelController::MINIMIZED); + break; + case FULL: + if (panel_widget_.get()) { + panel_widget_->SetBounds(GetPreferredBounds()); + panel_controller_->SetState(PanelController::EXPANDED); + } + break; + case STICKY_AND_NEW: + if (panel_widget_.get()) { + panel_widget_->SetBounds(GetStickyNewBounds()); + panel_controller_->SetState(PanelController::EXPANDED); + } + break; } } @@ -267,4 +402,36 @@ gfx::Rect NotificationPanel::GetPreferredBounds() { return gfx::Rect(0, 0, new_width, new_height); } +gfx::Rect NotificationPanel::GetStickyNewBounds() { + gfx::Size pref_size = balloon_container_->GetPreferredSize(); + gfx::Size sticky_size = balloon_container_->GetStickyNewSize(); + int new_height = std::min(sticky_size.height(), kMaxPanelHeight); + int new_width = pref_size.width(); + // Adjust the width to avoid showing a horizontal scroll bar. + if (new_height != pref_size.height()) + new_width += scroll_view_->GetScrollBarWidth(); + return gfx::Rect(0, 0, new_width, new_height); +} + +void NotificationPanel::StartStaleTimer(Balloon* balloon) { + BalloonViewImpl* view = static_cast<BalloonViewImpl*>(balloon->view()); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + task_factory_.NewRunnableMethod( + &NotificationPanel::StaleNotification, view), + 1000 * kStaleTimeoutInSeconds); +} + +void NotificationPanel::StaleNotification(BalloonViewImpl* view) { + if (balloon_container_->HasBalloonView(view) && !view->stale()) { + view->make_stale(); + if (balloon_container_->HasStickyNotifications()) { + state_ = STICKY_AND_NEW; + } else { + state_ = MINIMIZED; + } + UpdatePanel(false); + } +} + } // namespace chromeos diff --git a/chrome/browser/chromeos/notifications/notification_panel.h b/chrome/browser/chromeos/notifications/notification_panel.h index 1bdddc9..3036b18 100644 --- a/chrome/browser/chromeos/notifications/notification_panel.h +++ b/chrome/browser/chromeos/notifications/notification_panel.h @@ -8,6 +8,7 @@ #define CHROME_BROWSER_CHROMEOS_NOTIFICATIONS_NOTIFICATION_PANEL_H_ #include "base/gfx/rect.h" +#include "base/task.h" #include "base/scoped_ptr.h" #include "chrome/browser/chromeos/frame/panel_controller.h" #include "chrome/browser/chromeos/notifications/balloon_collection_impl.h" @@ -21,7 +22,43 @@ class ScrollView; namespace chromeos { class BalloonContainer; - +class BalloonViewImpl; + +// NotificationPanel is a panel that displays notifications. It has +// several states and displays the different portion of notifications +// depending on in which state the panel is. The following shows +// how the panel's state changes in response to various events. +// +// Event List: +// close: a user pressed close button on the title bar, +// or the system closed the panel. +// new : a new notification is added. +// stale: one of new notifications became stale. +// expand: a user pressed minimized panel to expand. +// minimize: a user pressed the panel's title bar to minimize. +// For state, see State enum's description below. +// +// CLOSE<--(event=close)-+ +--(event=stale, cond=has new|sticky) +// | | | (event=new) +// | | V | +// +--(event=new)--> STICKY_AND_NEW----------+ +// | | ^ +// | (event=stale, | +// | cond=has new, no sticy) (event=new) +// | (event=minimize) | +// | | | +// | V | +// | MINIMIZED ---(event=close)--> [CLOSE] +// | | ^ +// | | | +// | (event=expand) (event=minmize) +// | V | +// +--(event=open)----> FULL --(event=stale)(event=new) +// | | | +// (event=close) +--------------+ +// | +// [CLOSE] <------+ +// class NotificationPanel : public PanelController::Delegate, public BalloonCollectionImpl::NotificationUI { public: @@ -42,22 +79,41 @@ class NotificationPanel : public PanelController::Delegate, virtual string16 GetPanelTitle(); virtual SkBitmap GetPanelIcon(); virtual void ClosePanel(); + virtual void OnPanelStateChanged(PanelController::State state); private: + enum State { + FULL, // Show all notifications + STICKY_AND_NEW, // Show only new and sticky notifications. + MINIMIZED, // The panel is minimized. + CLOSED, // The panel is closed. + }; + void Init(); - // Update the Panel Size to the preferred size. - void UpdateSize(); + // Update the Panel Size according to its state. + void UpdatePanel(bool contents_changed); // Returns the panel's preferred bounds in the screen's coordinates. // The position will be controlled by window manager so // the origin is always (0, 0). gfx::Rect GetPreferredBounds(); + // Returns the bounds that covers sticky and new notifications. + gfx::Rect GetStickyNewBounds(); + + void StartStaleTimer(Balloon* balloon); + + // A callback function that is called when the notification + // (that the view is associated with) becomes stale after a timeout. + void StaleNotification(BalloonViewImpl* view); + BalloonContainer* balloon_container_; scoped_ptr<views::Widget> panel_widget_; scoped_ptr<PanelController> panel_controller_; scoped_ptr<views::ScrollView> scroll_view_; + State state_; + ScopedRunnableMethodFactory<NotificationPanel> task_factory_; DISALLOW_COPY_AND_ASSIGN(NotificationPanel); }; diff --git a/chrome/browser/views/notifications/balloon_view_host.cc b/chrome/browser/views/notifications/balloon_view_host.cc index 94f645a..ea8d55d 100644 --- a/chrome/browser/views/notifications/balloon_view_host.cc +++ b/chrome/browser/views/notifications/balloon_view_host.cc @@ -154,7 +154,6 @@ void BalloonViewHost::Init(gfx::NativeView parent_hwnd) { static_cast<RenderWidgetHostViewGtk*>(view); view_gtk->InitAsChild(); Attach(view_gtk->native_view()); - view->SetSize(gfx::Size(width(), height())); #else NOTIMPLEMENTED(); #endif diff --git a/views/view.cc b/views/view.cc index c04a4df7..437b3b7 100644 --- a/views/view.cc +++ b/views/view.cc @@ -556,6 +556,12 @@ int View::GetChildViewCount() const { return static_cast<int>(child_views_.size()); } +bool View::HasChildView(View* a_view) { + return find(child_views_.begin(), + child_views_.end(), + a_view) != child_views_.end(); +} + void View::RemoveChildView(View* a_view) { DoRemoveChildView(a_view, true, true, false); } diff --git a/views/view.h b/views/view.h index d493a0b..0dcc734 100644 --- a/views/view.h +++ b/views/view.h @@ -435,6 +435,9 @@ class View : public AcceleratorTarget { // Get the number of child Views. int GetChildViewCount() const; + // Tests if this view has a given view as direct child. + bool HasChildView(View* a_view); + // Returns the deepest descendant that contains the specified point. virtual View* GetViewForPoint(const gfx::Point& point); |