diff options
author | oshima@chromium.org <oshima@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-16 23:59:55 +0000 |
---|---|---|
committer | oshima@chromium.org <oshima@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-16 23:59:55 +0000 |
commit | b253e636b63e9383271fdeb2858f416d59922b57 (patch) | |
tree | bdb8de9fcf6ffa25e2b4070e2e2d065be3cf4af4 /chrome/browser/chromeos | |
parent | 45a613edf3b4cd4566cf631424217b59c75c146d (diff) | |
download | chromium_src-b253e636b63e9383271fdeb2858f416d59922b57.zip chromium_src-b253e636b63e9383271fdeb2858f416d59922b57.tar.gz chromium_src-b253e636b63e9383271fdeb2858f416d59922b57.tar.bz2 |
Hover buttons for notification.
* Chagned WidgetGtk so that any GtkFixed can be parent of WidgetGtk.
* Close button and Options menu is moved to separate TYPE_CHILD WidgetGtk
which is shown/hidden as mouse moves.
TODO: host Widget is not transparent right now. I'll address this in separate CL.
BUG=41011
TEST=none
Review URL: http://codereview.chromium.org/1654006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@44861 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/chromeos')
7 files changed, 326 insertions, 156 deletions
diff --git a/chrome/browser/chromeos/notifications/balloon_collection_impl.h b/chrome/browser/chromeos/notifications/balloon_collection_impl.h index a7c2051..013cfcd 100644 --- a/chrome/browser/chromeos/notifications/balloon_collection_impl.h +++ b/chrome/browser/chromeos/notifications/balloon_collection_impl.h @@ -20,6 +20,8 @@ class Size; namespace chromeos { +class BalloonViewImpl; + // A balloon collection represents a set of notification balloons being // shown in the chromeos notification panel. Unlike other platforms, // chromeos shows the all notifications in the notification panel, and @@ -38,8 +40,13 @@ class BalloonCollectionImpl : public BalloonCollection, virtual void Add(Balloon* balloon) = 0; virtual bool Update(Balloon* balloon) = 0; virtual void Remove(Balloon* balloon) = 0; + + // Resize notification from webkit. virtual void ResizeNotification(Balloon* balloon, const gfx::Size& size) = 0; + + // Sets the active view. + virtual void SetActiveView(BalloonViewImpl* view) = 0; private: DISALLOW_COPY_AND_ASSIGN(NotificationUI); }; diff --git a/chrome/browser/chromeos/notifications/balloon_view.cc b/chrome/browser/chromeos/notifications/balloon_view.cc index af03ad3..a59219f 100644 --- a/chrome/browser/chromeos/notifications/balloon_view.cc +++ b/chrome/browser/chromeos/notifications/balloon_view.cc @@ -7,6 +7,7 @@ #include <vector> #include "app/l10n_util.h" +#include "app/menus/simple_menu_model.h" #include "app/resource_bundle.h" #include "base/message_loop.h" #include "base/utf_string_conversions.h" @@ -22,36 +23,171 @@ #include "chrome/common/notification_type.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" +#include "views/background.h" +#include "views/controls/button/button.h" +#include "views/controls/button/image_button.h" #include "views/controls/button/menu_button.h" -#include "views/controls/button/text_button.h" +#include "views/controls/label.h" #include "views/controls/menu/menu_2.h" +#include "views/controls/menu/view_menu_delegate.h" #include "views/widget/root_view.h" +#include "views/widget/widget_gtk.h" namespace { // Menu commands const int kRevokePermissionCommand = 0; +// Vertical margin between close button and menu button. +const int kControlButtonsMargin = 4; } // namespace namespace chromeos { +// NotificationControlView has close and menu buttons and +// overlays on top of renderer view. +class NotificationControlView : public views::View, + public views::ViewMenuDelegate, + public menus::SimpleMenuModel::Delegate, + public views::ButtonListener { + public: + explicit NotificationControlView(BalloonViewImpl* view) + : balloon_view_(view), + close_button_(NULL), + options_menu_contents_(NULL), + options_menu_menu_(NULL), + options_menu_button_(NULL) { + // TODO(oshima): make background transparent. + set_background(views::Background::CreateSolidBackground(SK_ColorWHITE)); + + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + + SkBitmap* close_button_n = rb.GetBitmapNamed(IDR_TAB_CLOSE); + SkBitmap* close_button_m = rb.GetBitmapNamed(IDR_TAB_CLOSE_MASK); + SkBitmap* close_button_h = rb.GetBitmapNamed(IDR_TAB_CLOSE_H); + SkBitmap* close_button_p = rb.GetBitmapNamed(IDR_TAB_CLOSE_P); + + close_button_ = new views::ImageButton(this); + close_button_->SetImage(views::CustomButton::BS_NORMAL, close_button_n); + close_button_->SetImage(views::CustomButton::BS_HOT, close_button_h); + close_button_->SetImage(views::CustomButton::BS_PUSHED, close_button_p); + close_button_->SetBackground( + SK_ColorBLACK, close_button_n, close_button_m); + + AddChildView(close_button_); + + options_menu_button_ + = new views::MenuButton(NULL, std::wstring(), this, false); + options_menu_button_->SetFont(rb.GetFont(ResourceBundle::SmallFont)); + options_menu_button_->SetIcon(*rb.GetBitmapNamed(IDR_NOTIFICATION_MENU)); + options_menu_button_->set_border(NULL); + + options_menu_button_->set_icon_placement(views::TextButton::ICON_ON_RIGHT); + AddChildView(options_menu_button_); + + // The control view will never be resized, so just layout once. + gfx::Size button_size = close_button_->GetPreferredSize(); + close_button_->SetBounds( + 0, 0, button_size.width(), button_size.height()); + gfx::Size options_size = options_menu_button_->GetPreferredSize(); + options_menu_button_->SetBounds( + 0, button_size.height() + kControlButtonsMargin, + options_size.width(), options_size.height()); + + SizeToPreferredSize(); + } + + virtual gfx::Size GetPreferredSize() { + gfx::Rect total_bounds = + close_button_->bounds().Union(options_menu_button_->bounds()); + return total_bounds.size(); + } + + // views::ViewMenuDelegate implements. + virtual void RunMenu(views::View* source, const gfx::Point& pt) { + CreateOptionsMenu(); + options_menu_menu_->RunMenuAt(pt, views::Menu2::ALIGN_TOPRIGHT); + } + + // views::ButtonListener implements. + virtual void ButtonPressed(views::Button* sender, const views::Event&) { + balloon_view_->Close(true); + } + + // menus::SimpleMenuModel::Delegate impglements. + virtual bool IsCommandIdChecked(int /* command_id */) const { + // Nothing in the menu is checked. + return false; + } + + virtual bool IsCommandIdEnabled(int /* command_id */) const { + // All the menu options are always enabled. + return true; + } + + virtual bool GetAcceleratorForCommandId( + int /* command_id */, menus::Accelerator* /* accelerator */) { + // Currently no accelerators. + return false; + } + + virtual void ExecuteCommand(int command_id) { + switch (command_id) { + case kRevokePermissionCommand: + balloon_view_->DenyPermission(); + default: + NOTIMPLEMENTED(); + } + } + + private: + void CreateOptionsMenu() { + if (options_menu_contents_.get()) + return; + const string16 source_label_text = WideToUTF16Hack(l10n_util::GetStringF( + IDS_NOTIFICATION_BALLOON_SOURCE_LABEL, + balloon_view_->balloon_->notification().display_source())); + const string16 label_text = WideToUTF16Hack(l10n_util::GetStringF( + IDS_NOTIFICATION_BALLOON_REVOKE_MESSAGE, + balloon_view_->balloon_->notification().display_source())); + + options_menu_contents_.reset(new menus::SimpleMenuModel(this)); + // TODO(oshima): Showing the source info in the menu for now. + // Figure out where to show the source info. + options_menu_contents_->AddItem(-1, source_label_text); + options_menu_contents_->AddItem(kRevokePermissionCommand, label_text); + + options_menu_menu_.reset(new views::Menu2(options_menu_contents_.get())); + } + + BalloonViewImpl* balloon_view_; + + views::ImageButton* close_button_; + + // The options menu. + scoped_ptr<menus::SimpleMenuModel> options_menu_contents_; + scoped_ptr<views::Menu2> options_menu_menu_; + views::MenuButton* options_menu_button_; + + DISALLOW_COPY_AND_ASSIGN(NotificationControlView); +}; + BalloonViewImpl::BalloonViewImpl(bool sticky, bool controls) : balloon_(NULL), html_contents_(NULL), method_factory_(this), - close_button_(NULL), - options_menu_contents_(NULL), - options_menu_menu_(NULL), - options_menu_button_(NULL), stale_(false), sticky_(sticky), - controls_(controls) { + controls_(controls), + closed_(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 (control_view_host_.get()) { + control_view_host_->CloseNow(); + } if (html_contents_) { html_contents_->Shutdown(); } @@ -61,48 +197,10 @@ BalloonViewImpl::~BalloonViewImpl() { // BallonViewImpl, BalloonView implementation. void BalloonViewImpl::Show(Balloon* balloon) { - ResourceBundle& rb = ResourceBundle::GetSharedInstance(); - const std::wstring source_label_text = l10n_util::GetStringF( - IDS_NOTIFICATION_BALLOON_SOURCE_LABEL, - balloon->notification().display_source()); - const std::wstring options_text = - l10n_util::GetString(IDS_NOTIFICATION_OPTIONS_MENU_LABEL); - const std::wstring dismiss_text = - l10n_util::GetString(IDS_NOTIFICATION_BALLOON_DISMISS_LABEL); balloon_ = balloon; html_contents_ = new BalloonViewHost(balloon); AddChildView(html_contents_->view()); - if (controls_) { - close_button_ = new views::TextButton(this, dismiss_text); - close_button_->SetIcon(*rb.GetBitmapNamed(IDR_BALLOON_CLOSE)); - close_button_->SetHoverIcon(*rb.GetBitmapNamed(IDR_BALLOON_CLOSE_HOVER)); - close_button_->SetFont(rb.GetFont(ResourceBundle::SmallFont)); - close_button_->SetEnabledColor(SK_ColorWHITE); - close_button_->SetHoverColor(SK_ColorDKGRAY); - close_button_->set_alignment(views::TextButton::ALIGN_CENTER); - close_button_->set_icon_placement(views::TextButton::ICON_ON_RIGHT); - AddChildView(close_button_); - - options_menu_button_ - = new views::MenuButton(NULL, options_text, this, false); - options_menu_button_->SetFont(rb.GetFont(ResourceBundle::SmallFont)); - options_menu_button_->SetIcon( - *rb.GetBitmapNamed(IDR_BALLOON_OPTIONS_ARROW)); - options_menu_button_->SetHoverIcon( - *rb.GetBitmapNamed(IDR_BALLOON_OPTIONS_ARROW_HOVER)); - options_menu_button_->set_alignment(views::TextButton::ALIGN_CENTER); - options_menu_button_->set_icon_placement(views::TextButton::ICON_ON_RIGHT); - options_menu_button_->SetEnabledColor(SK_ColorWHITE); - options_menu_button_->SetHoverColor(SK_ColorDKGRAY); - AddChildView(options_menu_button_); - - source_label_ = new views::Label(source_label_text); - source_label_->SetFont(rb.GetFont(ResourceBundle::SmallFont)); - source_label_->SetColor(SK_ColorWHITE); - source_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); - AddChildView(source_label_); - } notification_registrar_.Add(this, NotificationType::NOTIFY_BALLOON_DISCONNECTED, Source<Balloon>(balloon)); } @@ -115,6 +213,7 @@ void BalloonViewImpl::Update() { } void BalloonViewImpl::Close(bool by_user) { + closed_ = true; MessageLoop::current()->PostTask( FROM_HERE, method_factory_.NewRunnableMethod( @@ -138,77 +237,30 @@ void BalloonViewImpl::RepositionToBalloon() { // views::View interface overrides. void BalloonViewImpl::Layout() { - gfx::Size button_size; - if (close_button_) { - button_size = close_button_->GetPreferredSize(); - } + gfx::Size size = balloon_->content_size(); - SetBounds(x(), y(), - balloon_->content_size().width(), - balloon_->content_size().height() + - button_size.height()); - int x = width() - button_size.width(); - int y = height() - button_size.height(); + SetBounds(x(), y(), size.width(), size.height()); - html_contents_->view()->SetBounds(0, 0, width(), y); + html_contents_->view()->SetBounds(0, 0, size.width(), size.height()); if (html_contents_->render_view_host()) { RenderWidgetHostView* view = html_contents_->render_view_host()->view(); if (view) - view->SetSize(gfx::Size(width(), y)); + view->SetSize(size); } - if (controls_) { - close_button_->SetBounds(x, y, button_size.width(), button_size.height()); - x -= close_button_->GetPreferredSize().width(); - options_menu_button_->SetBounds( - x, y, button_size.width(), button_size.height()); - source_label_->SetBounds(0, y, x, button_size.height()); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// views::ViewMenuDelegate implementation. - -void BalloonViewImpl::RunMenu(views::View* source, const gfx::Point& pt) { - CreateOptionsMenu(); - options_menu_menu_->RunMenuAt(pt, views::Menu2::ALIGN_TOPRIGHT); -} - -//////////////////////////////////////////////////////////////////////////////// -// views::Button implementation. - -void BalloonViewImpl::ButtonPressed(views::Button* sender, - const views::Event&) { - Close(true); -} - -//////////////////////////////////////////////////////////////////////////////// -// menus::SimpleMenuModel::Delegate implementation. - -bool BalloonViewImpl::IsCommandIdChecked(int /* command_id */) const { - // Nothing in the menu is checked. - return false; -} - -bool BalloonViewImpl::IsCommandIdEnabled(int /* command_id */) const { - // All the menu options are always enabled. - return true; -} - -bool BalloonViewImpl::GetAcceleratorForCommandId( - int /* command_id */, menus::Accelerator* /* accelerator */) { - // Currently no accelerators. - return false; } -void BalloonViewImpl::ExecuteCommand(int command_id) { - DesktopNotificationService* service = - balloon_->profile()->GetDesktopNotificationService(); - switch (command_id) { - case kRevokePermissionCommand: - service->DenyPermission(balloon_->notification().origin_url()); - break; - default: - NOTIMPLEMENTED(); +void BalloonViewImpl::ViewHierarchyChanged( + bool is_add, View* parent, View* child) { + if (is_add && GetWidget() && !control_view_host_.get() && controls_) { + control_view_host_.reset( + new views::WidgetGtk(views::WidgetGtk::TYPE_CHILD)); + control_view_host_->Init(GetParentNativeView(), gfx::Rect()); + NotificationControlView* control = new NotificationControlView(this); + control_view_host_->set_delete_on_destroy(false); + control_view_host_->SetContentsView(control); + } + if (!is_add && GetWidget() && control_view_host_.get() && controls_) { + control_view_host_.release()->CloseNow(); } } @@ -231,30 +283,47 @@ void BalloonViewImpl::Observe(NotificationType type, } //////////////////////////////////////////////////////////////////////////////// -// BalloonViewImpl private. - -void BalloonViewImpl::CreateOptionsMenu() { - if (options_menu_contents_.get()) - return; +// BalloonViewImpl public. - const string16 label_text = WideToUTF16Hack(l10n_util::GetStringF( - IDS_NOTIFICATION_BALLOON_REVOKE_MESSAGE, - this->balloon_->notification().display_source())); +bool BalloonViewImpl::IsFor(const Notification& notification) const { + return balloon_->notification().IsSame(notification); +} - options_menu_contents_.reset(new menus::SimpleMenuModel(this)); - options_menu_contents_->AddItem(kRevokePermissionCommand, label_text); +void BalloonViewImpl::Activated() { + DCHECK(control_view_host_.get()); + // Get the size of Control View. + gfx::Size size = + control_view_host_->GetRootView()->GetChildViewAt(0)->GetPreferredSize(); + control_view_host_->Show(); + control_view_host_->SetBounds( + gfx::Rect(width() - size.width(), 0, size.width(), size.height())); +} - options_menu_menu_.reset(new views::Menu2(options_menu_contents_.get())); +void BalloonViewImpl::Deactivated() { + if (control_view_host_.get()) { + control_view_host_->Hide(); + } } +//////////////////////////////////////////////////////////////////////////////// +// BalloonViewImpl private. + void BalloonViewImpl::DelayedClose(bool by_user) { html_contents_->Shutdown(); html_contents_ = NULL; balloon_->OnClose(by_user); } -bool BalloonViewImpl::IsFor(const Notification& notification) const { - return balloon_->notification().IsSame(notification); +void BalloonViewImpl::DenyPermission() { + DesktopNotificationService* service = + balloon_->profile()->GetDesktopNotificationService(); + service->DenyPermission(balloon_->notification().origin_url()); +} + +gfx::NativeView BalloonViewImpl::GetParentNativeView() { + RenderWidgetHostView* view = html_contents_->render_view_host()->view(); + DCHECK(view); + return view->GetNativeView(); } } // namespace chromeos diff --git a/chrome/browser/chromeos/notifications/balloon_view.h b/chrome/browser/chromeos/notifications/balloon_view.h index 0ed77a8..c6032e6 100644 --- a/chrome/browser/chromeos/notifications/balloon_view.h +++ b/chrome/browser/chromeos/notifications/balloon_view.h @@ -7,7 +7,6 @@ #ifndef CHROME_BROWSER_CHROMEOS_NOTIFICATIONS_BALLOON_VIEW_H_ #define CHROME_BROWSER_CHROMEOS_NOTIFICATIONS_BALLOON_VIEW_H_ -#include "app/menus/simple_menu_model.h" #include "base/basictypes.h" #include "base/scoped_ptr.h" #include "base/task.h" @@ -18,15 +17,14 @@ #include "gfx/point.h" #include "gfx/rect.h" #include "gfx/size.h" -#include "views/controls/button/button.h" -#include "views/controls/label.h" -#include "views/controls/menu/view_menu_delegate.h" #include "views/view.h" namespace views { class Menu2; class MenuButton; +class MouseEvent; class TextButton; +class WidgetGtk; } // namespace views class BalloonViewHost; @@ -36,19 +34,19 @@ class NotificationSource; namespace chromeos { +class NotificationControlView; + // A balloon view is the UI component for a notification panel. class BalloonViewImpl : public BalloonView, public views::View, - public views::ViewMenuDelegate, - public menus::SimpleMenuModel::Delegate, - public NotificationObserver, - public views::ButtonListener { + public NotificationObserver { public: BalloonViewImpl(bool sticky, bool controls); ~BalloonViewImpl(); // views::View interface. virtual void Layout(); + virtual void ViewHierarchyChanged(bool is_add, View* parent, View* child); // BalloonView interface. virtual void Show(Balloon* balloon); @@ -67,22 +65,25 @@ class BalloonViewImpl : public BalloonView, // True if the notification is sticky. bool sticky() const { return sticky_; } + // True if the notification is being closed. + bool closed() const { return closed_; } + // True if the balloon is for the given |notification|. bool IsFor(const Notification& notification) const; - private: - // views::ViewMenuDelegate interface. - virtual void RunMenu(views::View* source, const gfx::Point& pt); + // Called when the notification becomes active (mouse is on). + void Activated(); - // views::ButtonListener interface. - virtual void ButtonPressed(views::Button* sender, const views::Event&); + // Called when the notification becomes inactive. + void Deactivated(); - // menus::SimpleMenuModel::Delegate interface. - virtual bool IsCommandIdChecked(int command_id) const; - virtual bool IsCommandIdEnabled(int command_id) const; - virtual bool GetAcceleratorForCommandId(int command_id, - menus::Accelerator* accelerator); - virtual void ExecuteCommand(int command_id); + private: + friend class NotificationControlView; + + // views::View interface. + virtual gfx::Size GetPreferredSize() { + return gfx::Size(1000, 1000); + } // NotificationObserver interface. virtual void Observe(NotificationType type, @@ -95,6 +96,12 @@ class BalloonViewImpl : public BalloonView, // Do the delayed close work. void DelayedClose(bool by_user); + // Denies the permission to show the ballooon from its source. + void DenyPermission(); + + // Returns the renderer's native view. + gfx::NativeView GetParentNativeView(); + // Non-owned pointer to the balloon which owns this object. Balloon* balloon_; @@ -104,22 +111,17 @@ class BalloonViewImpl : public BalloonView, // The following factory is used to call methods at a later time. ScopedRunnableMethodFactory<BalloonViewImpl> method_factory_; - // Pointer to sub-view is owned by the View sub-class. - views::TextButton* close_button_; - - // Pointer to sub-view is owned by View class. - views::Label* source_label_; + // A widget for ControlView. + scoped_ptr<views::WidgetGtk> control_view_host_; - // The options menu. - scoped_ptr<menus::SimpleMenuModel> options_menu_contents_; - scoped_ptr<views::Menu2> options_menu_menu_; - views::MenuButton* options_menu_button_; bool stale_; NotificationRegistrar notification_registrar_; // A sticky flag. A sticky notification cannot be dismissed by a user. bool sticky_; // True if a notification should have info/option/dismiss label/buttons. bool controls_; + // True if the notification is being closed. + bool closed_; DISALLOW_COPY_AND_ASSIGN(BalloonViewImpl); }; diff --git a/chrome/browser/chromeos/notifications/desktop_notifications_unittest.cc b/chrome/browser/chromeos/notifications/desktop_notifications_unittest.cc index f1018ca..424ea0e 100644 --- a/chrome/browser/chromeos/notifications/desktop_notifications_unittest.cc +++ b/chrome/browser/chromeos/notifications/desktop_notifications_unittest.cc @@ -16,6 +16,7 @@ class MockNotificationUI : public BalloonCollectionImpl::NotificationUI { virtual void Remove(Balloon* balloon) {} virtual void ResizeNotification(Balloon* balloon, const gfx::Size& size) {} + virtual void SetActiveView(BalloonViewImpl* view) {} }; MockBalloonCollection::MockBalloonCollection() diff --git a/chrome/browser/chromeos/notifications/notification_browsertest.cc b/chrome/browser/chromeos/notifications/notification_browsertest.cc index d90d1fd..916dabe 100644 --- a/chrome/browser/chromeos/notifications/notification_browsertest.cc +++ b/chrome/browser/chromeos/notifications/notification_browsertest.cc @@ -176,7 +176,7 @@ IN_PROC_BROWSER_TEST_F(NotificationTest, TestKeepSizeState) { EXPECT_EQ(NotificationPanel::STICKY_AND_NEW, tester->state()); - panel->OnMouseMotion(); + panel->OnMouseMotion(gfx::Point(10, 10)); EXPECT_EQ(NotificationPanel::KEEP_SIZE, tester->state()); collection->Remove(NewMockNotification("1")); diff --git a/chrome/browser/chromeos/notifications/notification_panel.cc b/chrome/browser/chromeos/notifications/notification_panel.cc index a673396..fbd56c5 100644 --- a/chrome/browser/chromeos/notifications/notification_panel.cc +++ b/chrome/browser/chromeos/notifications/notification_panel.cc @@ -8,6 +8,7 @@ #include "app/l10n_util.h" #include "app/resource_bundle.h" +#include "chrome/browser/chromeos/notifications/balloon_collection_impl.h" #include "chrome/browser/chromeos/notifications/balloon_view.h" #include "gfx/canvas.h" #include "grit/generated_resources.h" @@ -62,10 +63,24 @@ class PanelWidget : public views::WidgetGtk { panel_(panel) { } + void UpdateControl() { + if (last_point_.get()) + panel_->OnMouseMotion(*last_point_.get()); + } + // views::WidgetGtk overrides. virtual gboolean OnMotionNotify(GtkWidget* widget, GdkEventMotion* event) { gboolean result = WidgetGtk::OnMotionNotify(widget, event); - panel_->OnMouseMotion(); + + int x = 0, y = 0; + GetContainedWidgetEventCoordinates(event, &x, &y); + if (!last_point_.get()) { + last_point_.reset(new gfx::Point(x, y)); + } else { + last_point_->set_x(x); + last_point_->set_y(y); + } + panel_->OnMouseMotion(*last_point_.get()); return result; } @@ -78,12 +93,14 @@ class PanelWidget : public views::WidgetGtk { GetBounds(&bounds, true); if (!bounds.Contains(p)) { panel_->OnMouseLeave(); + last_point_.reset(); } return result; } private: chromeos::NotificationPanel* panel_; + scoped_ptr<gfx::Point> last_point_; DISALLOW_COPY_AND_ASSIGN(PanelWidget); }; @@ -176,6 +193,18 @@ class BalloonSubContainer : public views::View { return NULL; } + BalloonViewImpl* FindBalloonView(const gfx::Point point) { + gfx::Point copy(point); + ConvertPointToView(GetRootView(), this, ©); + for (int i = GetChildViewCount() - 1; i >= 0; --i) { + views::View* view = GetChildViewAt(i); + if (view->bounds().Contains(copy)) { + return static_cast<BalloonViewImpl*>(view); + } + } + return NULL; + } + private: gfx::Size preferred_size_; int margin_; @@ -252,10 +281,11 @@ class BalloonContainer : public views::View { } // Removes a ballon from the panel. - void Remove(Balloon* balloon) { + BalloonViewImpl* Remove(Balloon* balloon) { BalloonViewImpl* view = static_cast<BalloonViewImpl*>(balloon->view()); GetContainerFor(balloon)->RemoveChildView(view); + return view; } // Returns the number of notifications added to the panel. @@ -313,6 +343,11 @@ class BalloonContainer : public views::View { return view ? view : non_sticky_container_->FindBalloonView(notification); } + BalloonViewImpl* FindBalloonView(const gfx::Point& point) { + BalloonViewImpl* view = sticky_container_->FindBalloonView(point); + return view ? view : non_sticky_container_->FindBalloonView(point); + } + private: BalloonSubContainer* GetContainerFor(Balloon* balloon) const { BalloonViewImpl* view = @@ -336,7 +371,8 @@ NotificationPanel::NotificationPanel() state_(CLOSED), task_factory_(this), min_bounds_(0, 0, kBalloonMinWidth, kBalloonMinHeight), - stale_timeout_(1000 * kStaleTimeoutInSeconds) { + stale_timeout_(1000 * kStaleTimeoutInSeconds), + active_(NULL) { Init(); } @@ -394,6 +430,7 @@ void NotificationPanel::Add(Balloon* balloon) { // Don't resize the panel yet. The panel will be resized when WebKit tells // the size in ResizeNotification. UpdatePanel(false); + UpdateControl(); StartStaleTimer(balloon); } @@ -411,7 +448,11 @@ bool NotificationPanel::Update(Balloon* balloon) { } void NotificationPanel::Remove(Balloon* balloon) { - balloon_container_->Remove(balloon); + BalloonViewImpl* view = balloon_container_->Remove(balloon); + if (view == active_) { + active_ = NULL; + } + // TODO(oshima): May be we shouldn't close // if the mouse pointer is still on the panel. if (balloon_container_->GetNotificationCount() == 0) @@ -427,6 +468,7 @@ void NotificationPanel::Remove(Balloon* balloon) { SET_STATE(MINIMIZED); UpdatePanel(true); } + UpdateControl(); } void NotificationPanel::ResizeNotification( @@ -442,6 +484,19 @@ void NotificationPanel::ResizeNotification( UpdatePanel(true); } +void NotificationPanel::SetActiveView(BalloonViewImpl* view) { + // Don't change the active view if it's same notification, + // or the notification is being closed. + if (active_ == view || (view && view->closed())) { + return; + } + if (active_) + active_->Deactivated(); + active_ = view; + if (active_) + active_->Activated(); +} + //////////////////////////////////////////////////////////////////////////////// // PanelController overrides. @@ -492,12 +547,15 @@ void NotificationPanel::Observe(NotificationType type, // PanelController public. void NotificationPanel::OnMouseLeave() { + SetActiveView(NULL); if (balloon_container_->GetNotificationCount() == 0) SET_STATE(CLOSED); UpdatePanel(true); } -void NotificationPanel::OnMouseMotion() { +void NotificationPanel::OnMouseMotion(const gfx::Point& point) { + SetActiveView(balloon_container_->FindBalloonView(point)); + SET_STATE(KEEP_SIZE); } @@ -568,6 +626,11 @@ void NotificationPanel::UpdatePanel(bool contents_changed) { } } +void NotificationPanel::UpdateControl() { + if (panel_widget_.get()) + static_cast<PanelWidget*>(panel_widget_.get())->UpdateControl(); +} + gfx::Rect NotificationPanel::GetPreferredBounds() { gfx::Size pref_size = balloon_container_->GetPreferredSize(); int new_height = std::min(pref_size.height(), kMaxPanelHeight); diff --git a/chrome/browser/chromeos/notifications/notification_panel.h b/chrome/browser/chromeos/notifications/notification_panel.h index c739880..dfc76a5 100644 --- a/chrome/browser/chromeos/notifications/notification_panel.h +++ b/chrome/browser/chromeos/notifications/notification_panel.h @@ -93,6 +93,7 @@ class NotificationPanel : public PanelController::Delegate, virtual void Remove(Balloon* balloon); virtual void ResizeNotification(Balloon* balloon, const gfx::Size& size); + virtual void SetActiveView(BalloonViewImpl* view); // PanelController overrides. virtual string16 GetPanelTitle(); @@ -106,7 +107,7 @@ class NotificationPanel : public PanelController::Delegate, // Called when a mouse left the panel window. void OnMouseLeave(); - void OnMouseMotion(); + void OnMouseMotion(const gfx::Point& point); NotificationPanelTester* GetTester(); @@ -121,6 +122,9 @@ class NotificationPanel : public PanelController::Delegate, // Update the Panel Size according to its state. void UpdatePanel(bool contents_changed); + // Update the notification's control view state. + void UpdateControl(); + // 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). @@ -141,17 +145,41 @@ class NotificationPanel : public PanelController::Delegate, // Mark the given notification as stale. void MarkStale(const Notification& notification); + // Contains all notifications. BalloonContainer* balloon_container_; + + // The notification panel's widget. scoped_ptr<views::Widget> panel_widget_; + + // Panel controller for the notification panel. scoped_ptr<PanelController> panel_controller_; + + // A scrollable parent of the BalloonContainer. + // This is owned by the panel so that we can re-attache to the widget + // when closing and opening the panel. scoped_ptr<views::ScrollView> scroll_view_; + + // Panel's state. State state_; + ScopedRunnableMethodFactory<NotificationPanel> task_factory_; + + // The minimum size of a notification. gfx::Rect min_bounds_; - scoped_ptr<NotificationPanelTester> tester_; + + // Stale timeout. int stale_timeout_; + + // A registrar to subscribe PANEL_STATE_CHANGED event. NotificationRegistrar registrar_; + // The notification a mouse pointer is currently on. NULL if the mouse + // is out of the panel. + BalloonViewImpl* active_; + + // An object that provides interfacce for tests. + scoped_ptr<NotificationPanelTester> tester_; + DISALLOW_COPY_AND_ASSIGN(NotificationPanel); }; |