// Copyright 2018 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. #include "ash/app_menu/notification_menu_view.h" #include "ash/app_menu/notification_item_view.h" #include "ash/app_menu/notification_menu_header_view.h" #include "ash/app_menu/notification_overflow_view.h" #include "ash/public/cpp/app_menu_constants.h" #include "ui/base/models/menu_separator_types.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" #include "ui/message_center/public/cpp/notification.h" #include "ui/message_center/views/proportional_image_view.h" #include "ui/views/controls/menu/menu_config.h" #include "ui/views/controls/menu/menu_item_view.h" #include "ui/views/controls/menu/menu_separator.h" namespace ash { NotificationMenuView::NotificationMenuView( Delegate* notification_item_view_delegate, views::SlideOutControllerDelegate* slide_out_controller_delegate, const std::string& app_id) : app_id_(app_id), notification_item_view_delegate_(notification_item_view_delegate), slide_out_controller_delegate_(slide_out_controller_delegate), double_separator_(AddChildView(std::make_unique( ui::MenuSeparatorType::DOUBLE_SEPARATOR))), header_view_( AddChildView(std::make_unique())) { DCHECK(notification_item_view_delegate_); DCHECK(slide_out_controller_delegate_); DCHECK(!app_id_.empty()) << "Only context menus for applications can show notifications."; } NotificationMenuView::~NotificationMenuView() = default; gfx::Size NotificationMenuView::CalculatePreferredSize() const { return gfx::Size( views::MenuConfig::instance().touchable_menu_min_width, double_separator_->GetPreferredSize().height() + header_view_->GetPreferredSize().height() + kNotificationItemViewHeight + (overflow_view_ ? overflow_view_->GetPreferredSize().height() : 0)); } void NotificationMenuView::Layout() { int y = 0; double_separator_->SetBoundsRect(gfx::Rect( gfx::Point(0, y), gfx::Size(views::MenuConfig::instance().touchable_menu_min_width, double_separator_->GetPreferredSize().height()))); y += double_separator_->GetPreferredSize().height(); header_view_->SetBoundsRect( gfx::Rect(gfx::Point(0, y), header_view_->GetPreferredSize())); y += header_view_->GetPreferredSize().height(); auto* item = GetDisplayedNotificationItemView(); if (item) { item->SetBoundsRect(gfx::Rect(gfx::Point(0, y), item->GetPreferredSize())); y = item->bounds().bottom(); } if (overflow_view_) { overflow_view_->SetBoundsRect( gfx::Rect(gfx::Point(0, y), overflow_view_->GetPreferredSize())); } } bool NotificationMenuView::IsEmpty() const { return notification_item_views_.empty(); } void NotificationMenuView::AddNotificationItemView( const message_center::Notification& notification) { auto* old_displayed_item = GetDisplayedNotificationItemView(); auto notification_view = std::make_unique( notification_item_view_delegate_, slide_out_controller_delegate_, notification.title(), notification.message(), notification.icon(), notification.id()); notification_item_views_.push_front( AddChildView(std::move(notification_view))); header_view_->UpdateCounter(notification_item_views_.size()); if (!old_displayed_item) return; // Push |old_displayed_notification_item_view| to |overflow_view_|. old_displayed_item->SetVisible(false); const bool overflow_view_created = !overflow_view_; if (!overflow_view_) overflow_view_ = AddChildView(std::make_unique()); overflow_view_->AddIcon(old_displayed_item->proportional_image_view(), old_displayed_item->notification_id()); if (overflow_view_created) { PreferredSizeChanged(); // OnOverflowAddedOrRemoved must be called after PreferredSizeChange to // ensure that enough room is allocated for the overflow view. notification_item_view_delegate_->OnOverflowAddedOrRemoved(); } Layout(); } void NotificationMenuView::UpdateNotificationItemView( const message_center::Notification& notification) { // Find the view which corresponds to |notification|. const auto i = NotificationIterForId(notification.id()); if (i == notification_item_views_.end()) return; (*i)->UpdateContents(notification.title(), notification.message(), notification.icon()); } void NotificationMenuView::OnNotificationRemoved( const std::string& notification_id) { // Find the view which corresponds to |notification_id|. const auto i = NotificationIterForId(notification_id); if (i == notification_item_views_.end()) return; const bool removed_displayed_notification = *i == GetDisplayedNotificationItemView(); RemoveChildViewT(*i); notification_item_views_.erase(i); header_view_->UpdateCounter(notification_item_views_.size()); if (removed_displayed_notification) { // Display the next notification. auto* item = GetDisplayedNotificationItemView(); if (item) { item->SetVisible(true); if (overflow_view_) overflow_view_->RemoveIcon(item->notification_id()); } } else if (overflow_view_) { overflow_view_->RemoveIcon(notification_id); } if (overflow_view_ && overflow_view_->is_empty()) { // Remove and delete |overflow_view_|. RemoveChildViewT(overflow_view_); overflow_view_ = nullptr; PreferredSizeChanged(); notification_item_view_delegate_->OnOverflowAddedOrRemoved(); } } ui::Layer* NotificationMenuView::GetSlideOutLayer() { auto* item = GetDisplayedNotificationItemView(); return item ? item->layer() : nullptr; } const NotificationItemView* NotificationMenuView::GetDisplayedNotificationItemView() const { return notification_item_views_.empty() ? nullptr : notification_item_views_.front(); } const std::string& NotificationMenuView::GetDisplayedNotificationID() const { DCHECK(!notification_item_views_.empty()); return GetDisplayedNotificationItemView()->notification_id(); } NotificationMenuView::NotificationItemViews::iterator NotificationMenuView::NotificationIterForId(const std::string& id) { return std::find_if( notification_item_views_.begin(), notification_item_views_.end(), [id](const auto& item) { return item->notification_id() == id; }); } } // namespace ash