diff options
9 files changed, 215 insertions, 15 deletions
diff --git a/chrome/browser/cocoa/notifications/balloon_controller.mm b/chrome/browser/cocoa/notifications/balloon_controller.mm index 01a753c..f50adac 100644 --- a/chrome/browser/cocoa/notifications/balloon_controller.mm +++ b/chrome/browser/cocoa/notifications/balloon_controller.mm @@ -94,8 +94,8 @@ const int kRightMargin = 1; - (void)repositionToBalloon { DCHECK(balloon_); - int x = balloon_->position().x(); - int y = balloon_->position().y(); + int x = balloon_->GetPosition().x(); + int y = balloon_->GetPosition().y(); int w = [self desiredTotalWidth]; int h = [self desiredTotalHeight]; diff --git a/chrome/browser/gtk/notifications/balloon_view_gtk.cc b/chrome/browser/gtk/notifications/balloon_view_gtk.cc index dfea155..754684f 100644 --- a/chrome/browser/gtk/notifications/balloon_view_gtk.cc +++ b/chrome/browser/gtk/notifications/balloon_view_gtk.cc @@ -160,8 +160,8 @@ void BalloonViewImpl::RepositionToBalloon() { gtk_window_get_position(GTK_WINDOW(frame_container_), &start_x, &start_y); gtk_window_get_size(GTK_WINDOW(frame_container_), &start_w, &start_h); - int end_x = balloon_->position().x(); - int end_y = balloon_->position().y(); + int end_x = balloon_->GetPosition().x(); + int end_y = balloon_->GetPosition().y(); int end_w = GetDesiredTotalWidth(); int end_h = GetDesiredTotalHeight(); diff --git a/chrome/browser/notifications/balloon.h b/chrome/browser/notifications/balloon.h index 87440c9..7f3b604 100644 --- a/chrome/browser/notifications/balloon.h +++ b/chrome/browser/notifications/balloon.h @@ -57,9 +57,15 @@ class Balloon { const Notification& notification() const { return *notification_.get(); } Profile* profile() const { return profile_; } - const gfx::Point& position() const { return position_; } + gfx::Point GetPosition() const { + return position_.Add(offset_); + } void SetPosition(const gfx::Point& upper_left, bool reposition); + const gfx::Point& offset() { return offset_;} + void set_offset(const gfx::Point& offset) { offset_ = offset; } + void add_offset(const gfx::Point& offset) { offset_ = offset_.Add(offset); } + const gfx::Size& content_size() const { return content_size_; } void set_content_size(const gfx::Size& size) { content_size_ = size; } @@ -115,6 +121,10 @@ class Balloon { gfx::Point position_; gfx::Size content_size_; + // Temporary offset for balloons that need to be positioned in a non-standard + // position for keeping the close buttons under the mouse cursor. + gfx::Point offset_; + // Smallest size for this balloon where scrollbars will be shown. gfx::Size min_scrollbar_size_; diff --git a/chrome/browser/notifications/balloon_collection.cc b/chrome/browser/notifications/balloon_collection.cc index 09bfb56d..91311d1 100644 --- a/chrome/browser/notifications/balloon_collection.cc +++ b/chrome/browser/notifications/balloon_collection.cc @@ -22,6 +22,10 @@ const double kPercentBalloonFillFactor = 0.7; // Allow at least this number of balloons on the screen. const int kMinAllowedBalloonCount = 2; +// Delay from the mouse leaving the balloon collection before +// there is a relayout, in milliseconds. +const int kRepositionDelay = 300; + } // namespace // static @@ -32,7 +36,12 @@ BalloonCollectionImpl::Layout::Placement BalloonCollectionImpl::Layout::placement_ = Layout::VERTICALLY_FROM_BOTTOM_RIGHT; -BalloonCollectionImpl::BalloonCollectionImpl() { +BalloonCollectionImpl::BalloonCollectionImpl() +#if USE_OFFSETS + : ALLOW_THIS_IN_INITIALIZER_LIST(reposition_factory_(this)), + added_as_message_loop_observer_(false) +#endif +{ } BalloonCollectionImpl::~BalloonCollectionImpl() { @@ -49,6 +58,10 @@ void BalloonCollectionImpl::Add(const Notification& notification, layout_.max_balloon_height())); new_balloon->SetPosition(layout_.OffScreenLocation(), false); new_balloon->Show(); +#if USE_OFFSETS + if (balloons_.size() > 0) + new_balloon->set_offset(balloons_[balloons_.size() - 1]->offset()); +#endif balloons_.push_back(new_balloon); PositionBalloons(false); @@ -99,12 +112,38 @@ void BalloonCollectionImpl::DisplayChanged() { void BalloonCollectionImpl::OnBalloonClosed(Balloon* source) { // We want to free the balloon when finished. scoped_ptr<Balloon> closed(source); - for (Balloons::iterator it = balloons_.begin(); it != balloons_.end(); ++it) { + Balloons::iterator it = balloons_.begin(); + +#if USE_OFFSETS + gfx::Point offset; + bool apply_offset = false; + while (it != balloons_.end()) { + if (*it == source) { + it = balloons_.erase(it); + if (it != balloons_.end()) { + apply_offset = true; + offset.set_y((source)->offset().y() - (*it)->offset().y() + + (*it)->content_size().height() - source->content_size().height()); + } + } else { + if (apply_offset) + (*it)->add_offset(offset); + ++it; + } + } + // Start listening for UI events so we cancel the offset when the mouse + // leaves the balloon area. + if (apply_offset) + AddMessageLoopObserver(); +#else + for (; it != balloons_.end(); ++it) { if (*it == source) { balloons_.erase(it); break; } } +#endif + PositionBalloons(true); // There may be no listener in a unit test. @@ -115,12 +154,56 @@ void BalloonCollectionImpl::OnBalloonClosed(Balloon* source) { void BalloonCollectionImpl::PositionBalloons(bool reposition) { gfx::Point origin = layout_.GetLayoutOrigin(); for (Balloons::iterator it = balloons_.begin(); it != balloons_.end(); ++it) { - gfx::Point upper_left = layout_.NextPosition( - Layout::ConstrainToSizeLimits((*it)->GetViewSize()), &origin); + gfx::Point upper_left = layout_.NextPosition((*it)->GetViewSize(), &origin); (*it)->SetPosition(upper_left, reposition); } } +#if USE_OFFSETS +void BalloonCollectionImpl::AddMessageLoopObserver() { + if (!added_as_message_loop_observer_) { + MessageLoopForUI::current()->AddObserver(this); + added_as_message_loop_observer_ = true; + } +} + +void BalloonCollectionImpl::RemoveMessageLoopObserver() { + if (added_as_message_loop_observer_) { + MessageLoopForUI::current()->RemoveObserver(this); + added_as_message_loop_observer_ = false; + } +} + +void BalloonCollectionImpl::CancelOffsets() { + reposition_factory_.RevokeAll(); + + // Unhook from listening to all UI events. + RemoveMessageLoopObserver(); + + for (Balloons::iterator it = balloons_.begin(); it != balloons_.end(); ++it) + (*it)->set_offset(gfx::Point(0, 0)); + + PositionBalloons(true); +} + +void BalloonCollectionImpl::HandleMouseMoveEvent() { + if (!IsCursorInBalloonCollection()) { + // Mouse has left the region. Schedule a reposition after + // a short delay. + if (reposition_factory_.empty()) { + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + reposition_factory_.NewRunnableMethod( + &BalloonCollectionImpl::CancelOffsets), + kRepositionDelay); + } + } else { + // Mouse moved back into the region. Cancel the reposition. + reposition_factory_.RevokeAll(); + } +} +#endif + BalloonCollectionImpl::Layout::Layout() { RefreshSystemMetrics(); } diff --git a/chrome/browser/notifications/balloon_collection_impl.h b/chrome/browser/notifications/balloon_collection_impl.h index cec0476..cb06230 100644 --- a/chrome/browser/notifications/balloon_collection_impl.h +++ b/chrome/browser/notifications/balloon_collection_impl.h @@ -10,20 +10,34 @@ #include <deque> #include "base/basictypes.h" +#include "base/message_loop.h" #include "chrome/browser/notifications/balloon_collection.h" #include "gfx/point.h" #include "gfx/rect.h" +// Mac balloons grow from the top down and have close buttons on top, so +// offsetting is not necessary for easy multiple-closing. Other platforms grow +// from the bottom up and have close buttons on top, so it is necessary. +#if defined(OS_MACOSX) +#define USE_OFFSETS 0 +#else +#define USE_OFFSETS 1 +#endif + // A balloon collection represents a set of notification balloons being // shown on the screen. It positions new notifications according to // a layout, and monitors for balloons being closed, which it reports // up to its parent, the notification UI manager. -class BalloonCollectionImpl : public BalloonCollection { +class BalloonCollectionImpl : public BalloonCollection +#if USE_OFFSETS + , public MessageLoopForUI::Observer +#endif +{ public: BalloonCollectionImpl(); virtual ~BalloonCollectionImpl(); - // BalloonCollectionInterface overrides + // BalloonCollection interface. virtual void Add(const Notification& notification, Profile* profile); virtual bool Remove(const Notification& notification); @@ -35,6 +49,16 @@ class BalloonCollectionImpl : public BalloonCollection { return balloons_; } + // MessageLoopForUI::Observer interface. +#if defined(OS_WIN) + virtual void WillProcessMessage(const MSG& event) {} + virtual void DidProcessMessage(const MSG& event); +#endif +#if defined(OS_LINUX) + virtual void WillProcessEvent(GdkEvent* event) {} + virtual void DidProcessEvent(GdkEvent* event); +#endif + protected: // Calculates layout values for the balloons including // the scaling, the max/min sizes, and the upper left corner of each. @@ -121,6 +145,21 @@ class BalloonCollectionImpl : public BalloonCollection { static gfx::Rect GetMacWorkArea(); #endif +#if USE_OFFSETS + // Start and stop observing all UI events. + void AddMessageLoopObserver(); + void RemoveMessageLoopObserver(); + + // Cancel all offset and reposition the balloons normally. + void CancelOffsets(); + + // Handles a mouse motion while the balloons are temporarily offset. + void HandleMouseMoveEvent(); + + // Is the current cursor in the balloon area? + bool IsCursorInBalloonCollection() const; +#endif + // Queue of active balloons. typedef std::deque<Balloon*> Balloons; Balloons balloons_; @@ -128,6 +167,14 @@ class BalloonCollectionImpl : public BalloonCollection { // The layout parameters for balloons in this collection. Layout layout_; +#if USE_OFFSETS + // Factory for generating delayed reposition tasks on mouse motion. + ScopedRunnableMethodFactory<BalloonCollectionImpl> reposition_factory_; + + // Is the balloon collection currently listening for UI events? + bool added_as_message_loop_observer_; +#endif + DISALLOW_COPY_AND_ASSIGN(BalloonCollectionImpl); }; diff --git a/chrome/browser/notifications/balloon_collection_linux.cc b/chrome/browser/notifications/balloon_collection_linux.cc index c82b3c1..a499292 100644 --- a/chrome/browser/notifications/balloon_collection_linux.cc +++ b/chrome/browser/notifications/balloon_collection_linux.cc @@ -30,6 +30,38 @@ int BalloonCollectionImpl::Layout::VerticalEdgeMargin() const { return 5; } +void BalloonCollectionImpl::DidProcessEvent(GdkEvent* event) { + switch (event->type) { + case GDK_MOTION_NOTIFY: + case GDK_LEAVE_NOTIFY: + HandleMouseMoveEvent(); + break; + default: + break; + } +} + +bool BalloonCollectionImpl::IsCursorInBalloonCollection() const { + if (balloons_.empty()) + return false; + + gfx::Point upper_left = balloons_[balloons_.size() - 1]->GetPosition(); + gfx::Point lower_right = layout_.GetLayoutOrigin(); + + gfx::Rect bounds = gfx::Rect(upper_left.x(), + upper_left.y(), + lower_right.x() - upper_left.x(), + lower_right.y() - upper_left.y()); + + GdkScreen* screen = gdk_screen_get_default(); + GdkDisplay* display = gdk_screen_get_display(screen); + gint x, y; + gdk_display_get_pointer(display, NULL, &x, &y, NULL); + gfx::Point cursor(x, y); + + return bounds.Contains(cursor); +} + // static BalloonCollection* BalloonCollection::Create() { return new BalloonCollectionImpl(); diff --git a/chrome/browser/notifications/balloon_collection_win.cc b/chrome/browser/notifications/balloon_collection_win.cc index a913f25..b5c2351 100644 --- a/chrome/browser/notifications/balloon_collection_win.cc +++ b/chrome/browser/notifications/balloon_collection_win.cc @@ -29,6 +29,34 @@ int BalloonCollectionImpl::Layout::VerticalEdgeMargin() const { return 0; } +void BalloonCollectionImpl::DidProcessMessage(const MSG& msg) { + switch (msg.message) { + case WM_MOUSEMOVE: + case WM_MOUSELEAVE: + case WM_NCMOUSELEAVE: + HandleMouseMoveEvent(); + break; + } +} + +bool BalloonCollectionImpl::IsCursorInBalloonCollection() const { + if (balloons_.empty()) + return false; + + gfx::Point upper_left = balloons_[balloons_.size() - 1]->GetPosition(); + gfx::Point lower_right = layout_.GetLayoutOrigin(); + + gfx::Rect bounds = gfx::Rect(upper_left.x(), + upper_left.y(), + lower_right.x() - upper_left.x(), + lower_right.y() - upper_left.y()); + + DWORD pos = GetMessagePos(); + gfx::Point cursor(pos); + + return bounds.Contains(cursor); +} + // static BalloonCollection* BalloonCollection::Create() { return new BalloonCollectionImpl(); diff --git a/chrome/browser/notifications/desktop_notifications_unittest.cc b/chrome/browser/notifications/desktop_notifications_unittest.cc index 4cbe647..34746d1 100644 --- a/chrome/browser/notifications/desktop_notifications_unittest.cc +++ b/chrome/browser/notifications/desktop_notifications_unittest.cc @@ -59,7 +59,7 @@ int MockBalloonCollection::UppermostVerticalPosition() { int min = 0; std::deque<Balloon*>::iterator iter; for (iter = balloons_.begin(); iter != balloons_.end(); ++iter) { - int pos = (*iter)->position().y(); + int pos = (*iter)->GetPosition().y(); if (iter == balloons_.begin() || pos < min) min = pos; } diff --git a/chrome/browser/views/notifications/balloon_view.cc b/chrome/browser/views/notifications/balloon_view.cc index 68c6570..ec9aa95 100644 --- a/chrome/browser/views/notifications/balloon_view.cc +++ b/chrome/browser/views/notifications/balloon_view.cc @@ -186,7 +186,7 @@ void BalloonViewImpl::RepositionToBalloon() { if (!kAnimateEnabled) { frame_container_->SetBounds( - gfx::Rect(balloon_->position().x(), balloon_->position().y(), + gfx::Rect(balloon_->GetPosition().x(), balloon_->GetPosition().y(), GetTotalWidth(), GetTotalHeight())); gfx::Rect contents_rect = GetContentsRectangle(); html_container_->SetBounds(contents_rect); @@ -198,7 +198,7 @@ void BalloonViewImpl::RepositionToBalloon() { } anim_frame_end_ = gfx::Rect( - balloon_->position().x(), balloon_->position().y(), + balloon_->GetPosition().x(), balloon_->GetPosition().y(), GetTotalWidth(), GetTotalHeight()); frame_container_->GetBounds(&anim_frame_start_, false); animation_.reset(new SlideAnimation(this)); @@ -274,7 +274,7 @@ void BalloonViewImpl::Show(Balloon* balloon) { balloon_ = balloon; - SetBounds(balloon_->position().x(), balloon_->position().y(), + SetBounds(balloon_->GetPosition().x(), balloon_->GetPosition().y(), GetTotalWidth(), GetTotalHeight()); source_label_ = new views::Label(source_label_text); |