diff options
author | jianli@chromium.org <jianli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-11 04:49:23 +0000 |
---|---|---|
committer | jianli@chromium.org <jianli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-11 04:49:23 +0000 |
commit | 1311a0786ef2de3cb44ca9684b8d66ed1013e01b (patch) | |
tree | 95b4ad8e1d25da74c5db3677d3480dda0dbb14b3 | |
parent | 61df7d1f919ab65a45ffca7b031b962a95474537 (diff) | |
download | chromium_src-1311a0786ef2de3cb44ca9684b8d66ed1013e01b.zip chromium_src-1311a0786ef2de3cb44ca9684b8d66ed1013e01b.tar.gz chromium_src-1311a0786ef2de3cb44ca9684b8d66ed1013e01b.tar.bz2 |
Fix the problem that notifications and panels overlap.
The workaround is to move all notifications on top of the panels that could overlap with.
BUG=103052
TEST=new test
Review URL: http://codereview.chromium.org/8503037
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@109588 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/notifications/balloon_collection_gtk.cc | 9 | ||||
-rw-r--r-- | chrome/browser/notifications/balloon_collection_impl.cc | 138 | ||||
-rw-r--r-- | chrome/browser/notifications/balloon_collection_impl.h | 25 | ||||
-rw-r--r-- | chrome/browser/notifications/balloon_collection_mac.mm | 11 | ||||
-rw-r--r-- | chrome/browser/notifications/balloon_collection_views.cc | 9 | ||||
-rw-r--r-- | chrome/browser/ui/panels/base_panel_browser_test.cc | 30 | ||||
-rw-r--r-- | chrome/browser/ui/panels/base_panel_browser_test.h | 5 | ||||
-rw-r--r-- | chrome/browser/ui/panels/panel.cc | 5 | ||||
-rw-r--r-- | chrome/browser/ui/panels/panel_browsertest.cc | 170 | ||||
-rw-r--r-- | chrome/browser/ui/panels/panel_manager.cc | 14 | ||||
-rw-r--r-- | chrome/browser/ui/panels/panel_manager.h | 2 | ||||
-rw-r--r-- | chrome/common/chrome_notification_types.h | 15 |
12 files changed, 408 insertions, 25 deletions
diff --git a/chrome/browser/notifications/balloon_collection_gtk.cc b/chrome/browser/notifications/balloon_collection_gtk.cc index 2420735..5b8a784 100644 --- a/chrome/browser/notifications/balloon_collection_gtk.cc +++ b/chrome/browser/notifications/balloon_collection_gtk.cc @@ -35,6 +35,14 @@ int BalloonCollectionImpl::Layout::VerticalEdgeMargin() const { return 5; } +bool BalloonCollectionImpl::Layout::NeedToMoveAboveLeftSidePanels() const { + return placement_ == VERTICALLY_FROM_BOTTOM_LEFT; +} + +bool BalloonCollectionImpl::Layout::NeedToMoveAboveRightSidePanels() const { + return placement_ == VERTICALLY_FROM_BOTTOM_RIGHT; +} + void BalloonCollectionImpl::PositionBalloons(bool reposition) { PositionBalloonsInternal(reposition); } @@ -92,6 +100,7 @@ void BalloonCollectionImpl::SetPositionPreference( else NOTREACHED(); + layout_.ComputeOffsetToMoveAbovePanels(gfx::Rect()); PositionBalloons(true); } diff --git a/chrome/browser/notifications/balloon_collection_impl.cc b/chrome/browser/notifications/balloon_collection_impl.cc index 9d234c2..173c78c 100644 --- a/chrome/browser/notifications/balloon_collection_impl.cc +++ b/chrome/browser/notifications/balloon_collection_impl.cc @@ -10,7 +10,13 @@ #include "chrome/browser/notifications/balloon.h" #include "chrome/browser/notifications/balloon_host.h" #include "chrome/browser/notifications/notification.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/panels/panel_manager.h" +#include "chrome/browser/ui/panels/panel.h" #include "chrome/browser/ui/window_sizer.h" +#include "chrome/common/chrome_notification_types.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/notification_service.h" #include "ui/gfx/rect.h" #include "ui/gfx/size.h" @@ -27,6 +33,9 @@ const int kMinAllowedBalloonCount = 2; // there is a relayout, in milliseconds. const int kRepositionDelay = 300; +// The spacing between the balloon and the panel. +const int kVerticalSpacingBetweenBalloonAndPanel = 5; + } // namespace BalloonCollectionImpl::BalloonCollectionImpl() @@ -35,6 +44,12 @@ BalloonCollectionImpl::BalloonCollectionImpl() added_as_message_loop_observer_(false) #endif { + registrar_.Add(this, chrome::NOTIFICATION_PANEL_ADDED, + content::NotificationService::AllSources()); + registrar_.Add(this, chrome::NOTIFICATION_PANEL_REMOVED, + content::NotificationService::AllSources()); + registrar_.Add(this, chrome::NOTIFICATION_PANEL_CHANGED_BOUNDS, + content::NotificationService::AllSources()); SetPositionPreference(BalloonCollection::DEFAULT_POSITION); } @@ -153,6 +168,26 @@ const BalloonCollection::Balloons& BalloonCollectionImpl::GetActiveBalloons() { return base_.balloons(); } +void BalloonCollectionImpl::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + gfx::Rect bounds; + switch (type) { + case chrome::NOTIFICATION_PANEL_CHANGED_BOUNDS: + bounds = content::Source<Panel>(source).ptr()->GetBounds(); + // Fall through. + case chrome::NOTIFICATION_PANEL_ADDED: + case chrome::NOTIFICATION_PANEL_REMOVED: + if (layout_.ComputeOffsetToMoveAbovePanels(bounds)) + PositionBalloons(true); + break; + default: + NOTREACHED(); + break; + } +} + void BalloonCollectionImpl::PositionBalloonsInternal(bool reposition) { const Balloons& balloons = base_.balloons(); @@ -231,7 +266,9 @@ void BalloonCollectionImpl::HandleMouseMoveEvent() { } #endif -BalloonCollectionImpl::Layout::Layout() : placement_(INVALID) { +BalloonCollectionImpl::Layout::Layout() + : placement_(INVALID), + offset_to_move_above_panels_(0) { RefreshSystemMetrics(); } @@ -245,24 +282,30 @@ void BalloonCollectionImpl::Layout::GetMaxLinearSize(int* max_balloon_size, } gfx::Point BalloonCollectionImpl::Layout::GetLayoutOrigin() const { + // For lower-left and lower-right positioning, we need to add an offset + // to ensure balloons to stay on top of panels to avoid overlapping. int x = 0; int y = 0; switch (placement_) { - case VERTICALLY_FROM_TOP_LEFT: + case VERTICALLY_FROM_TOP_LEFT: { x = work_area_.x() + HorizontalEdgeMargin(); - y = work_area_.y() + VerticalEdgeMargin(); + y = work_area_.y() + VerticalEdgeMargin() + offset_to_move_above_panels_; break; - case VERTICALLY_FROM_TOP_RIGHT: + } + case VERTICALLY_FROM_TOP_RIGHT: { x = work_area_.right() - HorizontalEdgeMargin(); - y = work_area_.y() + VerticalEdgeMargin(); + y = work_area_.y() + VerticalEdgeMargin() + offset_to_move_above_panels_; break; + } case VERTICALLY_FROM_BOTTOM_LEFT: x = work_area_.x() + HorizontalEdgeMargin(); - y = work_area_.bottom() - VerticalEdgeMargin(); + y = work_area_.bottom() - VerticalEdgeMargin() - + offset_to_move_above_panels_; break; case VERTICALLY_FROM_BOTTOM_RIGHT: x = work_area_.right() - HorizontalEdgeMargin(); - y = work_area_.bottom() - VerticalEdgeMargin(); + y = work_area_.bottom() - VerticalEdgeMargin() - + offset_to_move_above_panels_; break; default: NOTREACHED(); @@ -311,30 +354,21 @@ gfx::Point BalloonCollectionImpl::Layout::NextPosition( } gfx::Point BalloonCollectionImpl::Layout::OffScreenLocation() const { - int x = 0; - int y = 0; + gfx::Point location = GetLayoutOrigin(); switch (placement_) { case VERTICALLY_FROM_TOP_LEFT: - x = work_area_.x() + HorizontalEdgeMargin(); - y = work_area_.y() + kBalloonMaxHeight + VerticalEdgeMargin(); - break; - case VERTICALLY_FROM_TOP_RIGHT: - x = work_area_.right() - kBalloonMaxWidth - HorizontalEdgeMargin(); - y = work_area_.y() + kBalloonMaxHeight + VerticalEdgeMargin(); - break; case VERTICALLY_FROM_BOTTOM_LEFT: - x = work_area_.x() + HorizontalEdgeMargin(); - y = work_area_.bottom() + kBalloonMaxHeight + VerticalEdgeMargin(); + location.Offset(0, kBalloonMaxHeight); break; + case VERTICALLY_FROM_TOP_RIGHT: case VERTICALLY_FROM_BOTTOM_RIGHT: - x = work_area_.right() - kBalloonMaxWidth - HorizontalEdgeMargin(); - y = work_area_.bottom() + kBalloonMaxHeight + VerticalEdgeMargin(); + location.Offset(-kBalloonMaxWidth, kBalloonMaxHeight); break; default: NOTREACHED(); break; } - return gfx::Point(x, y); + return location; } bool BalloonCollectionImpl::Layout::RequiresOffsets() const { @@ -363,6 +397,68 @@ gfx::Size BalloonCollectionImpl::Layout::ConstrainToSizeLimits( std::min(max_balloon_height(), size.height()))); } +bool BalloonCollectionImpl::Layout::ComputeOffsetToMoveAbovePanels( + const gfx::Rect& panel_bounds) { + const PanelManager::Panels& panels = PanelManager::GetInstance()->panels(); + int offset_to_move_above_panels = 0; + + // The offset is the maximum height of panels that could overlap with the + // balloons. + if (NeedToMoveAboveLeftSidePanels()) { + // If the affecting panel does not lie in the balloon area, no need to + // update the offset. + if (!panel_bounds.IsEmpty() && + (panel_bounds.right() <= work_area_.x() || + panel_bounds.x() >= work_area_.x() + max_balloon_width())) { + return false; + } + + for (PanelManager::Panels::const_reverse_iterator iter = panels.rbegin(); + iter != panels.rend(); ++iter) { + // No need to check panels beyond the area occupied by the balloons. + if ((*iter)->GetBounds().x() >= work_area_.x() + max_balloon_width()) + break; + + int current_height = (*iter)->GetBounds().height(); + if (current_height > offset_to_move_above_panels) + offset_to_move_above_panels = current_height; + } + } else if (NeedToMoveAboveRightSidePanels()) { + // If the affecting panel does not lie in the balloon area, no need to + // update the offset. + if (!panel_bounds.IsEmpty() && + (panel_bounds.right() <= work_area_.right() - max_balloon_width() || + panel_bounds.x() >= work_area_.right())) { + return false; + } + + for (PanelManager::Panels::const_iterator iter = panels.begin(); + iter != panels.end(); ++iter) { + // No need to check panels beyond the area occupied by the balloons. + if ((*iter)->GetBounds().right() <= + work_area_.right() - max_balloon_width()) + break; + + int current_height = (*iter)->GetBounds().height(); + if (current_height > offset_to_move_above_panels) + offset_to_move_above_panels = current_height; + } + } + + // Ensure that we have some sort of margin between the 1st balloon and the + // panel beneath it even the vertical edge margin is 0 as on Mac. + if (offset_to_move_above_panels && !VerticalEdgeMargin()) + offset_to_move_above_panels += kVerticalSpacingBetweenBalloonAndPanel; + + // If no change is detected, return false to indicate that we do not need to + // reposition balloons. + if (offset_to_move_above_panels_ == offset_to_move_above_panels) + return false; + + offset_to_move_above_panels_ = offset_to_move_above_panels; + return true; +} + bool BalloonCollectionImpl::Layout::RefreshSystemMetrics() { bool changed = false; diff --git a/chrome/browser/notifications/balloon_collection_impl.h b/chrome/browser/notifications/balloon_collection_impl.h index 887d1f2..424351c 100644 --- a/chrome/browser/notifications/balloon_collection_impl.h +++ b/chrome/browser/notifications/balloon_collection_impl.h @@ -16,6 +16,8 @@ #include "base/message_loop.h" #include "chrome/browser/notifications/balloon_collection.h" #include "chrome/browser/notifications/balloon_collection_base.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" #include "ui/gfx/point.h" #include "ui/gfx/rect.h" @@ -32,7 +34,8 @@ // 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, + public content::NotificationObserver #if USE_OFFSETS , public MessageLoopForUI::Observer #endif @@ -54,6 +57,11 @@ class BalloonCollectionImpl : public BalloonCollection virtual void OnBalloonClosed(Balloon* source); virtual const Balloons& GetActiveBalloons(); + // content::NotificationObserver interface. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + // MessageLoopForUI::Observer interface. #if defined(OS_WIN) || defined(TOUCH_UI) || defined(USE_AURA) virtual base::EventStatus WillProcessEvent( @@ -127,12 +135,19 @@ class BalloonCollectionImpl : public BalloonCollection // buttons under the cursor during rapid-close interaction. bool RequiresOffsets() const; + // Returns true if there is change to the offset that requires the balloons + // to be repositioned. + bool ComputeOffsetToMoveAbovePanels(const gfx::Rect& panel_bounds); + private: // Layout parameters int VerticalEdgeMargin() const; int HorizontalEdgeMargin() const; int InterBalloonMargin() const; + bool NeedToMoveAboveLeftSidePanels() const; + bool NeedToMoveAboveRightSidePanels() const; + // Minimum and maximum size of balloon content. static const int kBalloonMinWidth = 300; static const int kBalloonMaxWidth = 300; @@ -141,6 +156,12 @@ class BalloonCollectionImpl : public BalloonCollection Placement placement_; gfx::Rect work_area_; + + // The offset that guarantees that the notificaitions shown in the + // lower-right or lower-left corner of the screen will go above currently + // shown panels and will not be obscured by them. + int offset_to_move_above_panels_; + DISALLOW_COPY_AND_ASSIGN(Layout); }; @@ -171,6 +192,8 @@ class BalloonCollectionImpl : public BalloonCollection // The layout parameters for balloons in this collection. Layout layout_; + content::NotificationRegistrar registrar_; + #if USE_OFFSETS // Start and stop observing all UI events. void AddMessageLoopObserver(); diff --git a/chrome/browser/notifications/balloon_collection_mac.mm b/chrome/browser/notifications/balloon_collection_mac.mm index d31bc39..a30b689 100644 --- a/chrome/browser/notifications/balloon_collection_mac.mm +++ b/chrome/browser/notifications/balloon_collection_mac.mm @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -35,6 +35,14 @@ int BalloonCollectionImpl::Layout::VerticalEdgeMargin() const { return 0; } +bool BalloonCollectionImpl::Layout::NeedToMoveAboveLeftSidePanels() const { + return placement_ == VERTICALLY_FROM_TOP_LEFT; +} + +bool BalloonCollectionImpl::Layout::NeedToMoveAboveRightSidePanels() const { + return placement_ == VERTICALLY_FROM_TOP_RIGHT; +} + void BalloonCollectionImpl::PositionBalloons(bool reposition) { // Use an animation context so that all the balloons animate together. [NSAnimationContext beginGrouping]; @@ -61,6 +69,7 @@ void BalloonCollectionImpl::SetPositionPreference( else NOTREACHED(); + layout_.ComputeOffsetToMoveAbovePanels(gfx::Rect()); PositionBalloons(true); } diff --git a/chrome/browser/notifications/balloon_collection_views.cc b/chrome/browser/notifications/balloon_collection_views.cc index c436df88..f5ac0e4 100644 --- a/chrome/browser/notifications/balloon_collection_views.cc +++ b/chrome/browser/notifications/balloon_collection_views.cc @@ -31,6 +31,14 @@ int BalloonCollectionImpl::Layout::VerticalEdgeMargin() const { return 0; } +bool BalloonCollectionImpl::Layout::NeedToMoveAboveLeftSidePanels() const { + return placement_ == VERTICALLY_FROM_BOTTOM_LEFT; +} + +bool BalloonCollectionImpl::Layout::NeedToMoveAboveRightSidePanels() const { + return placement_ == VERTICALLY_FROM_BOTTOM_RIGHT; +} + void BalloonCollectionImpl::PositionBalloons(bool reposition) { PositionBalloonsInternal(reposition); } @@ -96,6 +104,7 @@ void BalloonCollectionImpl::SetPositionPreference( else NOTREACHED(); + layout_.ComputeOffsetToMoveAbovePanels(gfx::Rect()); PositionBalloons(true); } diff --git a/chrome/browser/ui/panels/base_panel_browser_test.cc b/chrome/browser/ui/panels/base_panel_browser_test.cc index bcfbee0..992557b 100644 --- a/chrome/browser/ui/panels/base_panel_browser_test.cc +++ b/chrome/browser/ui/panels/base_panel_browser_test.cc @@ -148,6 +148,11 @@ void MockAutoHidingDesktopBarImpl::NotifyThicknessChange() { observer_->OnAutoHidingDesktopBarThicknessChanged(); } +bool ExistsPanel(Panel* panel) { + const PanelManager::Panels& panels = PanelManager::GetInstance()->panels(); + return find(panels.begin(), panels.end(), panel) != panels.end(); +} + } // namespace BasePanelBrowserTest::BasePanelBrowserTest() @@ -175,7 +180,10 @@ void BasePanelBrowserTest::SetUpOnMainThread() { mock_auto_hiding_desktop_bar_ = new MockAutoHidingDesktopBarImpl( panel_manager); panel_manager->set_auto_hiding_desktop_bar(mock_auto_hiding_desktop_bar_); - panel_manager->SetWorkAreaForTesting(testing_work_area_); + // Do not use the testing work area if it is empty since we're going to + // use the actual work area in some testing scenarios. + if (!testing_work_area_.IsEmpty()) + panel_manager->SetWorkAreaForTesting(testing_work_area_); panel_manager->enable_auto_sizing(false); panel_manager->remove_delays_for_testing(); // This is needed so the subsequently created panels can be activated. @@ -183,6 +191,26 @@ void BasePanelBrowserTest::SetUpOnMainThread() { EXPECT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser())); } +void BasePanelBrowserTest::WaitForPanelAdded(Panel* panel) { + if (ExistsPanel(panel)) + return; + ui_test_utils::WindowedNotificationObserver signal( + chrome::NOTIFICATION_PANEL_ADDED, + content::Source<Panel>(panel)); + signal.Wait(); + EXPECT_TRUE(ExistsPanel(panel)); +} + +void BasePanelBrowserTest::WaitForPanelRemoved(Panel* panel) { + if (!ExistsPanel(panel)) + return; + ui_test_utils::WindowedNotificationObserver signal( + chrome::NOTIFICATION_PANEL_REMOVED, + content::Source<Panel>(panel)); + signal.Wait(); + EXPECT_FALSE(ExistsPanel(panel)); +} + void BasePanelBrowserTest::WaitForPanelActiveState( Panel* panel, ActiveState expected_state) { DCHECK(expected_state == SHOW_AS_ACTIVE || diff --git a/chrome/browser/ui/panels/base_panel_browser_test.h b/chrome/browser/ui/panels/base_panel_browser_test.h index 42cf7e0..a833706 100644 --- a/chrome/browser/ui/panels/base_panel_browser_test.h +++ b/chrome/browser/ui/panels/base_panel_browser_test.h @@ -58,6 +58,8 @@ class BasePanelBrowserTest : public InProcessBrowserTest { const gfx::Rect& bounds); Panel* CreatePanel(const std::string& panel_name); + void WaitForPanelAdded(Panel* panel); + void WaitForPanelRemoved(Panel* panel); void WaitForPanelActiveState(Panel* panel, ActiveState state); void WaitForWindowSizeAvailable(Panel* panel); void WaitForBoundsAnimationFinished(Panel* panel); @@ -69,6 +71,9 @@ class BasePanelBrowserTest : public InProcessBrowserTest { const DictionaryValue& extra_value); gfx::Rect testing_work_area() const { return testing_work_area_; } + void set_testing_work_area(const gfx::Rect& work_area) { + testing_work_area_ = work_area; + } MockAutoHidingDesktopBar* mock_auto_hiding_desktop_bar() const { return mock_auto_hiding_desktop_bar_.get(); diff --git a/chrome/browser/ui/panels/panel.cc b/chrome/browser/ui/panels/panel.cc index b231136..976ede4 100644 --- a/chrome/browser/ui/panels/panel.cc +++ b/chrome/browser/ui/panels/panel.cc @@ -63,6 +63,11 @@ void Panel::SetPanelBounds(const gfx::Rect& bounds) { restored_height_ = bounds.height(); native_panel_->SetPanelBounds(bounds); + + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_PANEL_CHANGED_BOUNDS, + content::Source<Panel>(this), + content::NotificationService::NoDetails()); } void Panel::SetMaxSize(const gfx::Size& max_size) { diff --git a/chrome/browser/ui/panels/panel_browsertest.cc b/chrome/browser/ui/panels/panel_browsertest.cc index 382642c..8b146be 100644 --- a/chrome/browser/ui/panels/panel_browsertest.cc +++ b/chrome/browser/ui/panels/panel_browsertest.cc @@ -4,9 +4,16 @@ #include "base/bind.h" #include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/browser_process.h" #include "chrome/browser/download/download_service.h" #include "chrome/browser/download/download_service_factory.h" #include "chrome/browser/net/url_request_mock_util.h" +#include "chrome/browser/notifications/balloon_collection_impl.h" +#include "chrome/browser/notifications/desktop_notification_service.h" +#include "chrome/browser/notifications/notification.h" +#include "chrome/browser/notifications/notification_ui_manager.h" +#include "chrome/browser/prefs/browser_prefs.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser_list.h" @@ -26,9 +33,11 @@ #include "content/browser/download/download_manager.h" #include "content/browser/net/url_request_mock_http_job.h" #include "content/browser/tab_contents/tab_contents.h" +#include "content/common/desktop_notification_messages.h" #include "content/public/browser/notification_service.h" #include "content/public/common/url_constants.h" #include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/screen.h" using content::BrowserThread; @@ -1565,3 +1574,164 @@ IN_PROC_BROWSER_TEST_F(PanelDownloadTest, DownloadNoTabbedBrowser) { panel_browser->CloseWindow(); } + +class PanelAndNotificationTest : public PanelBrowserTest { + public: + PanelAndNotificationTest() : PanelBrowserTest() { + // Do not use our own testing work area since desktop notification code + // does not have the hook up for testing work area. + set_testing_work_area(gfx::Rect()); + } + + virtual ~PanelAndNotificationTest() { + } + + virtual void SetUpOnMainThread() OVERRIDE { + g_browser_process->local_state()->SetInteger( + prefs::kDesktopNotificationPosition, BalloonCollection::LOWER_RIGHT); + balloons_ = new BalloonCollectionImpl(); + ui_manager_.reset(NotificationUIManager::Create( + g_browser_process->local_state(), balloons_)); + service_.reset(new DesktopNotificationService(browser()->profile(), + ui_manager_.get())); + + PanelBrowserTest::SetUpOnMainThread(); + } + + virtual void CleanUpOnMainThread() OVERRIDE { + balloons_->RemoveAll(); + MessageLoopForUI::current()->RunAllPending(); + + service_.reset(); + ui_manager_.reset(); + + PanelBrowserTest::CleanUpOnMainThread(); + } + + DesktopNotificationHostMsg_Show_Params StandardTestNotification() { + DesktopNotificationHostMsg_Show_Params params; + params.notification_id = 0; + params.origin = GURL("http://www.google.com"); + params.is_html = false; + params.icon_url = GURL("/icon.png"); + params.title = ASCIIToUTF16("Title"); + params.body = ASCIIToUTF16("Text"); + params.direction = WebKit::WebTextDirectionDefault; + return params; + } + + int GetBalloonBottomPosition(Balloon* balloon) const { +#if defined(OS_MACOSX) + // The position returned by the notification balloon is based on Mac's + // vertically inverted orientation. We need to flip it so that it can + // be compared against the position returned by the panel. + gfx::Size screen_size = gfx::Screen::GetPrimaryMonitorSize(); + return screen_size.height() - balloon->GetPosition().y(); +#else + return balloon->GetPosition().y() + balloon->GetViewSize().height(); +#endif + } + + DesktopNotificationService* service() const { return service_.get(); } + const BalloonCollection::Balloons& balloons() const { + return balloons_->GetActiveBalloons(); + } + + private: + BalloonCollectionImpl* balloons_; // Owned by NotificationUIManager. + scoped_ptr<NotificationUIManager> ui_manager_; + scoped_ptr<DesktopNotificationService> service_; +}; + +IN_PROC_BROWSER_TEST_F(PanelAndNotificationTest, NoOverlapping) { + const int kPanelWidth = 200; + const int kShortPanelHeight = 150; + const int kTallPanelHeight = 200; + + DesktopNotificationHostMsg_Show_Params params = StandardTestNotification(); + EXPECT_TRUE(service()->ShowDesktopNotification( + params, 0, 0, DesktopNotificationService::PageNotification)); + MessageLoopForUI::current()->RunAllPending(); + Balloon* balloon = balloons().front(); + int original_balloon_bottom = GetBalloonBottomPosition(balloon); + // Ensure that balloon width is greater than the panel width. + EXPECT_GT(balloon->GetViewSize().width(), kPanelWidth); + + // Creating a short panel should move the notification balloon up. + Panel* panel1 = CreatePanelWithBounds( + "Panel1", gfx::Rect(0, 0, kPanelWidth, kShortPanelHeight)); + WaitForPanelAdded(panel1); + int balloon_bottom_after_short_panel_created = + GetBalloonBottomPosition(balloon); + EXPECT_LT(balloon_bottom_after_short_panel_created, panel1->GetBounds().y()); + EXPECT_LT(balloon_bottom_after_short_panel_created, original_balloon_bottom); + + // Creating another tall panel should move the notification balloon further + // up. + Panel* panel2 = CreatePanelWithBounds( + "Panel2", gfx::Rect(0, 0, kPanelWidth, kTallPanelHeight)); + WaitForPanelAdded(panel2); + int balloon_bottom_after_tall_panel_created = + GetBalloonBottomPosition(balloon); + EXPECT_LT(balloon_bottom_after_tall_panel_created, panel2->GetBounds().y()); + EXPECT_LT(balloon_bottom_after_tall_panel_created, + balloon_bottom_after_short_panel_created); + + // Minimizing tall panel should move the notification balloon down to the same + // position when short panel is first created. + panel2->SetExpansionState(Panel::MINIMIZED); + WaitForBoundsAnimationFinished(panel2); + int balloon_bottom_after_tall_panel_minimized = + GetBalloonBottomPosition(balloon); + EXPECT_EQ(balloon_bottom_after_short_panel_created, + balloon_bottom_after_tall_panel_minimized); + + // Minimizing short panel should move the notification balloon further down. + panel1->SetExpansionState(Panel::MINIMIZED); + WaitForBoundsAnimationFinished(panel1); + int balloon_bottom_after_both_panels_minimized = + GetBalloonBottomPosition(balloon); + EXPECT_LT(balloon_bottom_after_both_panels_minimized, + panel1->GetBounds().y()); + EXPECT_LT(balloon_bottom_after_both_panels_minimized, + panel2->GetBounds().y()); + EXPECT_LT(balloon_bottom_after_short_panel_created, + balloon_bottom_after_both_panels_minimized); + EXPECT_LT(balloon_bottom_after_both_panels_minimized, + original_balloon_bottom); + + // Bringing up the titlebar for tall panel should move the notification + // balloon up a little bit. + panel2->SetExpansionState(Panel::TITLE_ONLY); + WaitForBoundsAnimationFinished(panel2); + int balloon_bottom_after_tall_panel_titlebar_up = + GetBalloonBottomPosition(balloon); + EXPECT_LT(balloon_bottom_after_tall_panel_titlebar_up, + panel2->GetBounds().y()); + EXPECT_LT(balloon_bottom_after_tall_panel_titlebar_up, + balloon_bottom_after_both_panels_minimized); + EXPECT_LT(balloon_bottom_after_short_panel_created, + balloon_bottom_after_tall_panel_titlebar_up); + + // Expanding short panel should move the notification balloon further up to + // the same position when short panel is first created. + panel1->SetExpansionState(Panel::EXPANDED); + WaitForBoundsAnimationFinished(panel1); + int balloon_bottom_after_short_panel_expanded = + GetBalloonBottomPosition(balloon); + EXPECT_EQ(balloon_bottom_after_short_panel_created, + balloon_bottom_after_short_panel_expanded); + + // Closing short panel should move the notification balloon down to the same + // position when tall panel brings up its titlebar. + panel1->Close(); + WaitForBoundsAnimationFinished(panel1); + EXPECT_EQ(balloon_bottom_after_tall_panel_titlebar_up, + GetBalloonBottomPosition(balloon)); + + // Closing the remaining tall panel should move the notification balloon back + // to its original position. + panel2->Close(); + WaitForBoundsAnimationFinished(panel2); + EXPECT_EQ(original_balloon_bottom, GetBalloonBottomPosition(balloon)); +} diff --git a/chrome/browser/ui/panels/panel_manager.cc b/chrome/browser/ui/panels/panel_manager.cc index 0f4c0d2..e37eb43 100644 --- a/chrome/browser/ui/panels/panel_manager.cc +++ b/chrome/browser/ui/panels/panel_manager.cc @@ -13,6 +13,9 @@ #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/window_sizer.h" +#include "chrome/common/chrome_notification_types.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_source.h" namespace { // Invalid panel index. @@ -120,6 +123,12 @@ Panel* PanelManager::CreatePanel(Browser* browser) { Panel* panel = new Panel(browser, gfx::Rect(x, y, width, height)); panel->SetMaxSize(gfx::Size(max_panel_width, max_panel_height)); panels_.push_back(panel); + + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_PANEL_ADDED, + content::Source<Panel>(panel), + content::NotificationService::NoDetails()); + return panel; } @@ -166,6 +175,11 @@ void PanelManager::DoRemove(Panel* panel) { gfx::Rect bounds = (*iter)->GetBounds(); Rearrange(panels_.erase(iter), bounds.right()); + + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_PANEL_REMOVED, + content::Source<Panel>(panel), + content::NotificationService::NoDetails()); } void PanelManager::StartDragging(Panel* panel) { diff --git a/chrome/browser/ui/panels/panel_manager.h b/chrome/browser/ui/panels/panel_manager.h index 7b272ab6c..0e0d3cb 100644 --- a/chrome/browser/ui/panels/panel_manager.h +++ b/chrome/browser/ui/panels/panel_manager.h @@ -73,6 +73,7 @@ class PanelManager : public PanelMouseWatcher::Observer, int num_panels() const { return panels_.size(); } bool is_dragging_panel() const; + const Panels& panels() const { return panels_; } int GetMaxPanelWidth() const; int GetMaxPanelHeight() const; @@ -82,7 +83,6 @@ class PanelManager : public PanelMouseWatcher::Observer, virtual void OnMouseMove(const gfx::Point& mouse_position) OVERRIDE; #ifdef UNIT_TEST - const Panels& panels() const { return panels_; } static int horizontal_spacing() { return kPanelsHorizontalSpacing; } const gfx::Rect& work_area() const { diff --git a/chrome/common/chrome_notification_types.h b/chrome/common/chrome_notification_types.h index 12477c3..e2467d8 100644 --- a/chrome/common/chrome_notification_types.h +++ b/chrome/common/chrome_notification_types.h @@ -947,6 +947,21 @@ enum NotificationType { // Used only in unit testing. NOTIFICATION_PANEL_WINDOW_SIZE_KNOWN, + // Sent when panel's bounds get changed. + // The source is the Panel, no details. + // Used only in coordination with notification balloons. + NOTIFICATION_PANEL_CHANGED_BOUNDS, + + // Sent when panel is added into the panel manager. + // The source is the Panel, no details. + // Used only in coordination with notification balloons. + NOTIFICATION_PANEL_ADDED, + + // Sent when panel is removed from the panel manager. + // The source is the Panel, no details. + // Used only in coordination with notification balloons. + NOTIFICATION_PANEL_REMOVED, + // Sent when a global error has changed and the error UI should update it // self. The source is a Source<Profile> containing the profile for the // error. The detail is a GlobalError object that has changed or NULL if |