summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjohnnyg@chromium.org <johnnyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-15 21:02:01 +0000
committerjohnnyg@chromium.org <johnnyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-15 21:02:01 +0000
commit099c6c2f4118361105354eec5b36c720a3c20a00 (patch)
tree9fc8bc284354598e2e4364c0ead4cc05114040e7
parented7b2980a035eea169e02ab4c7729b0c8056cf4a (diff)
downloadchromium_src-099c6c2f4118361105354eec5b36c720a3c20a00.zip
chromium_src-099c6c2f4118361105354eec5b36c720a3c20a00.tar.gz
chromium_src-099c6c2f4118361105354eec5b36c720a3c20a00.tar.bz2
position the balloons after closing in a way that will keep the next one's close button under your mouse; don't reposition them to the normal place until the mouse has left the balloon collection
BUG=47333 TEST=make notifications of different sizes, try to close them all; the X should remain under your mouse Review URL: http://codereview.chromium.org/2915003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@52526 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/cocoa/notifications/balloon_controller.mm4
-rw-r--r--chrome/browser/gtk/notifications/balloon_view_gtk.cc4
-rw-r--r--chrome/browser/notifications/balloon.h12
-rw-r--r--chrome/browser/notifications/balloon_collection.cc91
-rw-r--r--chrome/browser/notifications/balloon_collection_impl.h51
-rw-r--r--chrome/browser/notifications/balloon_collection_linux.cc32
-rw-r--r--chrome/browser/notifications/balloon_collection_win.cc28
-rw-r--r--chrome/browser/notifications/desktop_notifications_unittest.cc2
-rw-r--r--chrome/browser/views/notifications/balloon_view.cc6
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);