This source file includes following definitions.
- weak_factory_
 
- ClickOnNotification
 
- RemoveNotification
 
- CreateMenuModel
 
- HasClickedListener
 
- ClickOnNotificationButton
 
- MarkAllPopupsShown
 
- UpdateWidgets
 
- OnMouseEntered
 
- OnMouseExited
 
- CloseAllWidgets
 
- ForgetToast
 
- RemoveToast
 
- GetToastOriginX
 
- RepositionWidgets
 
- RepositionWidgetsWithTarget
 
- ComputePopupAlignment
 
- GetBaseLine
 
- OnNotificationAdded
 
- OnNotificationRemoved
 
- OnDeferTimerExpired
 
- OnNotificationUpdated
 
- FindToast
 
- IncrementDeferCounter
 
- DecrementDeferCounter
 
- DoUpdateIfPossible
 
- SetDisplayInfo
 
- OnDisplayBoundsChanged
 
- OnDisplayAdded
 
- OnDisplayRemoved
 
- GetWidgetForTest
 
- CreateRunLoopForTest
 
- WaitForTest
 
- GetToastRectAt
 
#include "ui/message_center/views/message_popup_collection.h"
#include <set>
#include "base/bind.h"
#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "ui/accessibility/ax_enums.h"
#include "ui/gfx/animation/animation_delegate.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/screen.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/message_center_style.h"
#include "ui/message_center/message_center_tray.h"
#include "ui/message_center/message_center_util.h"
#include "ui/message_center/notification.h"
#include "ui/message_center/notification_list.h"
#include "ui/message_center/views/message_view_context_menu_controller.h"
#include "ui/message_center/views/notification_view.h"
#include "ui/message_center/views/toast_contents_view.h"
#include "ui/views/background.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/view.h"
#include "ui/views/views_delegate.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
namespace message_center {
namespace {
const int kMouseExitedDeferTimeoutMs = 200;
const int kToastMarginY = kMarginBetweenItems;
#if defined(OS_CHROMEOS)
const int kToastMarginX = 3;
#else
const int kToastMarginX = kMarginBetweenItems;
#endif
const int kNoToastMarginBorderAndShadowOffset = 2;
}  
MessagePopupCollection::MessagePopupCollection(gfx::NativeView parent,
                                               MessageCenter* message_center,
                                               MessageCenterTray* tray,
                                               bool first_item_has_no_margin)
    : parent_(parent),
      message_center_(message_center),
      tray_(tray),
      display_id_(gfx::Display::kInvalidDisplayID),
      screen_(NULL),
      defer_counter_(0),
      latest_toast_entered_(NULL),
      user_is_closing_toasts_by_clicking_(false),
      first_item_has_no_margin_(first_item_has_no_margin),
      context_menu_controller_(new MessageViewContextMenuController(this)),
      weak_factory_(this) {
  DCHECK(message_center_);
  defer_timer_.reset(new base::OneShotTimer<MessagePopupCollection>);
  message_center_->AddObserver(this);
}
MessagePopupCollection::~MessagePopupCollection() {
  weak_factory_.InvalidateWeakPtrs();
  if (screen_)
    screen_->RemoveObserver(this);
  message_center_->RemoveObserver(this);
  CloseAllWidgets();
}
void MessagePopupCollection::ClickOnNotification(
    const std::string& notification_id) {
  message_center_->ClickOnNotification(notification_id);
}
void MessagePopupCollection::RemoveNotification(
    const std::string& notification_id,
    bool by_user) {
  message_center_->RemoveNotification(notification_id, by_user);
}
scoped_ptr<ui::MenuModel> MessagePopupCollection::CreateMenuModel(
    const NotifierId& notifier_id,
    const base::string16& display_source) {
  return tray_->CreateNotificationMenuModel(notifier_id, display_source);
}
bool MessagePopupCollection::HasClickedListener(
    const std::string& notification_id) {
  return message_center_->HasClickedListener(notification_id);
}
void MessagePopupCollection::ClickOnNotificationButton(
    const std::string& notification_id,
    int button_index) {
  message_center_->ClickOnNotificationButton(notification_id, button_index);
}
void MessagePopupCollection::MarkAllPopupsShown() {
  std::set<std::string> closed_ids = CloseAllWidgets();
  for (std::set<std::string>::iterator iter = closed_ids.begin();
       iter != closed_ids.end(); iter++) {
    message_center_->MarkSinglePopupAsShown(*iter, false);
  }
}
void MessagePopupCollection::UpdateWidgets() {
  NotificationList::PopupNotifications popups =
      message_center_->GetPopupNotifications();
  if (popups.empty()) {
    CloseAllWidgets();
    return;
  }
  bool top_down = alignment_ & POPUP_ALIGNMENT_TOP;
  int base = GetBaseLine(toasts_.empty() ? NULL : toasts_.back());
  
  
  for (NotificationList::PopupNotifications::const_reverse_iterator iter =
           popups.rbegin(); iter != popups.rend(); ++iter) {
    if (FindToast((*iter)->id()))
      continue;
    NotificationView* view =
        NotificationView::Create(NULL,
                                 *(*iter),
                                 true); 
    view->set_context_menu_controller(context_menu_controller_.get());
    int view_height = ToastContentsView::GetToastSizeForView(view).height();
    int height_available = top_down ? work_area_.bottom() - base : base;
    if (height_available - view_height - kToastMarginY < 0) {
      delete view;
      break;
    }
    ToastContentsView* toast =
        new ToastContentsView((*iter)->id(), weak_factory_.GetWeakPtr());
    
    toast->SetContents(view, false);
    toasts_.push_back(toast);
    view->set_controller(toast);
    gfx::Size preferred_size = toast->GetPreferredSize();
    gfx::Point origin(GetToastOriginX(gfx::Rect(preferred_size)), base);
    
    if (alignment_ & POPUP_ALIGNMENT_LEFT)
      origin.set_x(origin.x() - preferred_size.width());
    else
      origin.set_x(origin.x() + preferred_size.width());
    if (top_down)
      origin.set_y(origin.y() + view_height);
    toast->RevealWithAnimation(origin);
    
    
    if (top_down)
      base += view_height + kToastMarginY;
    else
      base -= view_height + kToastMarginY;
    if (views::ViewsDelegate::views_delegate) {
      views::ViewsDelegate::views_delegate->NotifyAccessibilityEvent(
          toast, ui::AX_EVENT_ALERT);
    }
    message_center_->DisplayedNotification((*iter)->id());
  }
}
void MessagePopupCollection::OnMouseEntered(ToastContentsView* toast_entered) {
  
  
  latest_toast_entered_ = toast_entered;
  message_center_->PausePopupTimers();
  if (user_is_closing_toasts_by_clicking_)
    defer_timer_->Stop();
}
void MessagePopupCollection::OnMouseExited(ToastContentsView* toast_exited) {
  
  
  if (toast_exited != latest_toast_entered_)
    return;
  latest_toast_entered_ = NULL;
  if (user_is_closing_toasts_by_clicking_) {
    defer_timer_->Start(
        FROM_HERE,
        base::TimeDelta::FromMilliseconds(kMouseExitedDeferTimeoutMs),
        this,
        &MessagePopupCollection::OnDeferTimerExpired);
  } else {
    message_center_->RestartPopupTimers();
  }
}
std::set<std::string> MessagePopupCollection::CloseAllWidgets() {
  std::set<std::string> closed_toast_ids;
  while (!toasts_.empty()) {
    ToastContentsView* toast = toasts_.front();
    toasts_.pop_front();
    closed_toast_ids.insert(toast->id());
    OnMouseExited(toast);
    
    
    toast->CloseWithAnimation();
  }
  return closed_toast_ids;
}
void MessagePopupCollection::ForgetToast(ToastContentsView* toast) {
  toasts_.remove(toast);
  OnMouseExited(toast);
}
void MessagePopupCollection::RemoveToast(ToastContentsView* toast,
                                         bool mark_as_shown) {
  ForgetToast(toast);
  toast->CloseWithAnimation();
  if (mark_as_shown)
    message_center_->MarkSinglePopupAsShown(toast->id(), false);
}
int MessagePopupCollection::GetToastOriginX(const gfx::Rect& toast_bounds)
    const {
#if defined(OS_CHROMEOS)
  
  
  if (base::i18n::IsRTL())
    return work_area_.x() + kToastMarginX;
#endif
  if (alignment_ & POPUP_ALIGNMENT_LEFT)
    return work_area_.x() + kToastMarginX;
  return work_area_.right() - kToastMarginX - toast_bounds.width();
}
void MessagePopupCollection::RepositionWidgets() {
  bool top_down = alignment_ & POPUP_ALIGNMENT_TOP;
  int base = GetBaseLine(NULL);  
                                 
  for (Toasts::const_iterator iter = toasts_.begin(); iter != toasts_.end();) {
    Toasts::const_iterator curr = iter++;
    gfx::Rect bounds((*curr)->bounds());
    bounds.set_x(GetToastOriginX(bounds));
    bounds.set_y(alignment_ & POPUP_ALIGNMENT_TOP ? base
                                                  : base - bounds.height());
    
    
    
    
    if ((top_down ? work_area_.bottom() - bounds.bottom() : bounds.y()) >= 0)
      (*curr)->SetBoundsWithAnimation(bounds);
    else
      RemoveToast(*curr, false);
    
    
    if (top_down)
      base += bounds.height() + kToastMarginY;
    else
      base -= bounds.height() + kToastMarginY;
  }
}
void MessagePopupCollection::RepositionWidgetsWithTarget() {
  if (toasts_.empty())
    return;
  bool top_down = alignment_ & POPUP_ALIGNMENT_TOP;
  
  
  if (top_down ? toasts_.back()->origin().y() < target_top_edge_
               : toasts_.back()->origin().y() > target_top_edge_)
    return;
  Toasts::reverse_iterator iter = toasts_.rbegin();
  for (; iter != toasts_.rend(); ++iter) {
    
    
    if (top_down ? (*iter)->origin().y() < target_top_edge_
                 : (*iter)->origin().y() > target_top_edge_)
      break;
  }
  --iter;
  
  
  int slide_length = std::abs(target_top_edge_ - (*iter)->origin().y());
  for (;; --iter) {
    gfx::Rect bounds((*iter)->bounds());
    
    
    if (top_down)
      bounds.set_y(bounds.y() - slide_length);
    else
      bounds.set_y(bounds.y() + slide_length);
    (*iter)->SetBoundsWithAnimation(bounds);
    if (iter == toasts_.rbegin())
      break;
  }
}
void MessagePopupCollection::ComputePopupAlignment(gfx::Rect work_area,
                                                   gfx::Rect screen_bounds) {
  
  
  
  alignment_ = work_area.y() > screen_bounds.y() ? POPUP_ALIGNMENT_TOP
                                                 : POPUP_ALIGNMENT_BOTTOM;
  
  
  
  
  
  
  alignment_ = static_cast<PopupAlignment>(
      alignment_ |
      ((work_area.x() > screen_bounds.x() && work_area.y() == screen_bounds.y())
           ? POPUP_ALIGNMENT_LEFT
           : POPUP_ALIGNMENT_RIGHT));
}
int MessagePopupCollection::GetBaseLine(ToastContentsView* last_toast) const {
  bool top_down = alignment_ & POPUP_ALIGNMENT_TOP;
  int base;
  if (top_down) {
    if (!last_toast) {
      base = work_area_.y();
      if (!first_item_has_no_margin_)
        base += kToastMarginY;
      else
        base -= kNoToastMarginBorderAndShadowOffset;
    } else {
      base = toasts_.back()->bounds().bottom() + kToastMarginY;
    }
  } else {
    if (!last_toast) {
      base = work_area_.bottom();
      if (!first_item_has_no_margin_)
        base -= kToastMarginY;
      else
        base += kNoToastMarginBorderAndShadowOffset;
    } else {
      base = toasts_.back()->origin().y() - kToastMarginY;
    }
  }
  return base;
}
void MessagePopupCollection::OnNotificationAdded(
    const std::string& notification_id) {
  DoUpdateIfPossible();
}
void MessagePopupCollection::OnNotificationRemoved(
    const std::string& notification_id,
    bool by_user) {
  
  Toasts::const_iterator iter = toasts_.begin();
  for (; iter != toasts_.end(); ++iter) {
    if ((*iter)->id() == notification_id)
      break;
  }
  if (iter == toasts_.end())
    return;
  target_top_edge_ = (*iter)->bounds().y();
  if (by_user && !user_is_closing_toasts_by_clicking_) {
    
    
    
    
    
    
    
    user_is_closing_toasts_by_clicking_ = true;
    IncrementDeferCounter();
  }
  
  
  
  
  RemoveToast(*iter, true);
  if (by_user)
    RepositionWidgetsWithTarget();
}
void MessagePopupCollection::OnDeferTimerExpired() {
  user_is_closing_toasts_by_clicking_ = false;
  DecrementDeferCounter();
  message_center_->RestartPopupTimers();
}
void MessagePopupCollection::OnNotificationUpdated(
    const std::string& notification_id) {
  
  Toasts::const_iterator toast_iter = toasts_.begin();
  for (; toast_iter != toasts_.end(); ++toast_iter) {
    if ((*toast_iter)->id() == notification_id)
      break;
  }
  if (toast_iter == toasts_.end())
    return;
  NotificationList::PopupNotifications notifications =
      message_center_->GetPopupNotifications();
  bool updated = false;
  for (NotificationList::PopupNotifications::iterator iter =
           notifications.begin(); iter != notifications.end(); ++iter) {
    if ((*iter)->id() != notification_id)
      continue;
    const RichNotificationData& optional_fields =
        (*iter)->rich_notification_data();
    bool a11y_feedback_for_updates =
        optional_fields.should_make_spoken_feedback_for_popup_updates;
    NotificationView* view =
        NotificationView::Create(*toast_iter,
                                 *(*iter),
                                 true); 
    view->set_context_menu_controller(context_menu_controller_.get());
    (*toast_iter)->SetContents(view, a11y_feedback_for_updates);
    updated = true;
  }
  
  
  
  if (!updated)
    RemoveToast(*toast_iter, true);
  if (user_is_closing_toasts_by_clicking_)
    RepositionWidgetsWithTarget();
  else
    DoUpdateIfPossible();
}
ToastContentsView* MessagePopupCollection::FindToast(
    const std::string& notification_id) const {
  for (Toasts::const_iterator iter = toasts_.begin(); iter != toasts_.end();
       ++iter) {
    if ((*iter)->id() == notification_id)
      return *iter;
  }
  return NULL;
}
void MessagePopupCollection::IncrementDeferCounter() {
  defer_counter_++;
}
void MessagePopupCollection::DecrementDeferCounter() {
  defer_counter_--;
  DCHECK(defer_counter_ >= 0);
  DoUpdateIfPossible();
}
void MessagePopupCollection::DoUpdateIfPossible() {
  if (!screen_) {
    gfx::Display display;
    if (!parent_) {
      
      
      
      screen_ = gfx::Screen::GetNativeScreen();
      display = screen_->GetPrimaryDisplay();
    } else {
      screen_ = gfx::Screen::GetScreenFor(parent_);
      display = screen_->GetDisplayNearestWindow(parent_);
    }
    screen_->AddObserver(this);
    display_id_ = display.id();
    
    if (work_area_.IsEmpty()) {
      work_area_ = display.work_area();
      ComputePopupAlignment(work_area_, display.bounds());
    }
  }
  if (defer_counter_ > 0)
    return;
  RepositionWidgets();
  if (defer_counter_ > 0)
    return;
  
  UpdateWidgets();
  if (defer_counter_ > 0)
    return;
  
  
  
  if (run_loop_for_test_.get())
    run_loop_for_test_->Quit();
}
void MessagePopupCollection::SetDisplayInfo(const gfx::Rect& work_area,
                                            const gfx::Rect& screen_bounds) {
  if (work_area_ == work_area)
    return;
  work_area_ = work_area;
  ComputePopupAlignment(work_area, screen_bounds);
  RepositionWidgets();
}
void MessagePopupCollection::OnDisplayBoundsChanged(
    const gfx::Display& display) {
  if (display.id() != display_id_)
    return;
  SetDisplayInfo(display.work_area(), display.bounds());
}
void MessagePopupCollection::OnDisplayAdded(const gfx::Display& new_display) {
}
void MessagePopupCollection::OnDisplayRemoved(const gfx::Display& old_display) {
  if (display_id_ == old_display.id() && !parent_) {
    gfx::Display display = gfx::Screen::GetNativeScreen()->GetPrimaryDisplay();
    display_id_ = display.id();
    SetDisplayInfo(display.work_area(), display.bounds());
  }
}
views::Widget* MessagePopupCollection::GetWidgetForTest(const std::string& id)
    const {
  for (Toasts::const_iterator iter = toasts_.begin(); iter != toasts_.end();
       ++iter) {
    if ((*iter)->id() == id)
      return (*iter)->GetWidget();
  }
  return NULL;
}
void MessagePopupCollection::CreateRunLoopForTest() {
  run_loop_for_test_.reset(new base::RunLoop());
}
void MessagePopupCollection::WaitForTest() {
  run_loop_for_test_->Run();
  run_loop_for_test_.reset();
}
gfx::Rect MessagePopupCollection::GetToastRectAt(size_t index) const {
  DCHECK(defer_counter_ == 0) << "Fetching the bounds with animations active.";
  size_t i = 0;
  for (Toasts::const_iterator iter = toasts_.begin(); iter != toasts_.end();
       ++iter) {
    if (i++ == index) {
      views::Widget* widget = (*iter)->GetWidget();
      if (widget)
        return widget->GetWindowBoundsInScreen();
      break;
    }
  }
  return gfx::Rect();
}
}