root/ash/system/web_notification/web_notification_tray.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. CreateMessageCenterTray
  2. system_tray_height_
  3. SetSystemTrayHeight
  4. StartObserving
  5. StopObserving
  6. OnDisplayWorkAreaInsetsChanged
  7. OnAutoHideStateChanged
  8. UpdateShelf
  9. bubble
  10. bubble_view
  11. unread_count_
  12. SetBubbleVisible
  13. SetUnreadCount
  14. GetPreferredSize
  15. GetHeightForWidth
  16. UpdateIconVisibility
  17. should_block_shelf_auto_hide_
  18. ShowMessageCenterInternal
  19. ShowMessageCenter
  20. HideMessageCenter
  21. SetSystemTrayHeight
  22. ShowPopups
  23. HidePopups
  24. ShouldShowMessageCenter
  25. ShouldBlockShelfAutoHide
  26. IsMessageCenterBubbleVisible
  27. IsMouseInNotificationBubble
  28. ShowMessageCenterBubble
  29. UpdateAfterLoginStatusChange
  30. SetShelfAlignment
  31. AnchorUpdated
  32. GetAccessibleNameForTray
  33. HideBubbleWithView
  34. PerformAction
  35. BubbleViewDestroyed
  36. OnMouseEnteredView
  37. OnMouseExitedView
  38. GetAccessibleNameForBubble
  39. GetAnchorRect
  40. HideBubble
  41. ShowNotifierSettings
  42. IsContextMenuEnabled
  43. GetMessageCenterTray
  44. IsCommandIdChecked
  45. IsCommandIdEnabled
  46. GetAcceleratorForCommandId
  47. ExecuteCommand
  48. ButtonPressed
  49. OnMessageCenterTrayChanged
  50. UpdateTrayContent
  51. ClickedOutsideBubble
  52. IsPopupVisible
  53. GetMessageCenterBubbleForTest

// Copyright (c) 2012 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/system/web_notification/web_notification_tray.h"

#include "ash/ash_switches.h"
#include "ash/root_window_controller.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_layout_manager_observer.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/tray/system_tray.h"
#include "ash/system/tray/tray_background_view.h"
#include "ash/system/tray/tray_bubble_wrapper.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/tray/tray_utils.h"
#include "base/auto_reset.h"
#include "base/i18n/number_formatting.h"
#include "base/i18n/rtl.h"
#include "base/strings/utf_string_conversions.h"
#include "grit/ash_strings.h"
#include "grit/ui_strings.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/screen.h"
#include "ui/message_center/message_center_style.h"
#include "ui/message_center/message_center_tray_delegate.h"
#include "ui/message_center/message_center_util.h"
#include "ui/message_center/views/message_bubble_base.h"
#include "ui/message_center/views/message_center_bubble.h"
#include "ui/message_center/views/message_popup_collection.h"
#include "ui/views/bubble/tray_bubble_view.h"
#include "ui/views/controls/button/custom_button.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/layout/fill_layout.h"

#if defined(OS_CHROMEOS)

namespace message_center {

MessageCenterTrayDelegate* CreateMessageCenterTray() {
  // On Windows+Ash the Tray will not be hosted in ash::Shell.
  NOTREACHED();
  return NULL;
}

}  // namespace message_center

#endif  // defined(OS_CHROMEOS)

namespace ash {
namespace {

// Menu commands
const int kToggleQuietMode = 0;
const int kEnableQuietModeDay = 2;

}

namespace {

const SkColor kWebNotificationColorNoUnread = SkColorSetA(SK_ColorWHITE, 128);
const SkColor kWebNotificationColorWithUnread = SK_ColorWHITE;

}

// Observes the change of work area (including temporary change by auto-hide)
// and notifies MessagePopupCollection.
class WorkAreaObserver : public ShelfLayoutManagerObserver,
                         public ShellObserver {
 public:
  WorkAreaObserver();
  virtual ~WorkAreaObserver();

  void SetSystemTrayHeight(int height);

  // Starts observing |shelf| and shell and sends the change to |collection|.
  void StartObserving(message_center::MessagePopupCollection* collection,
                      aura::Window* root_window);

  // Stops the observing session.
  void StopObserving();

  // Overridden from ShellObserver:
  virtual void OnDisplayWorkAreaInsetsChanged() OVERRIDE;

  // Overridden from ShelfLayoutManagerObserver:
  virtual void OnAutoHideStateChanged(ShelfAutoHideState new_state) OVERRIDE;

 private:
  // Updates |shelf_| from |root_window_|.
  void UpdateShelf();

  message_center::MessagePopupCollection* collection_;
  aura::Window* root_window_;
  ShelfLayoutManager* shelf_;
  int system_tray_height_;

  DISALLOW_COPY_AND_ASSIGN(WorkAreaObserver);
};

WorkAreaObserver::WorkAreaObserver()
    : collection_(NULL),
      root_window_(NULL),
      shelf_(NULL),
      system_tray_height_(0) {
}

WorkAreaObserver::~WorkAreaObserver() {
  StopObserving();
}

void WorkAreaObserver::SetSystemTrayHeight(int height) {
  system_tray_height_ = height;

  // If the shelf is shown during auto-hide state, the distance from the edge
  // should be reduced by the height of shelf's shown height.
  if (shelf_ && shelf_->visibility_state() == SHELF_AUTO_HIDE &&
      shelf_->auto_hide_state() == SHELF_AUTO_HIDE_SHOWN) {
    system_tray_height_ -= ShelfLayoutManager::GetPreferredShelfSize() -
        ShelfLayoutManager::kAutoHideSize;
  }

  if (system_tray_height_ > 0 && ash::switches::UseAlternateShelfLayout())
    system_tray_height_ += message_center::kMarginBetweenItems;

  if (!shelf_)
    return;

  OnAutoHideStateChanged(shelf_->auto_hide_state());
}

void WorkAreaObserver::StartObserving(
    message_center::MessagePopupCollection* collection,
    aura::Window* root_window) {
  DCHECK(collection);
  collection_ = collection;
  root_window_ = root_window;
  UpdateShelf();
  Shell::GetInstance()->AddShellObserver(this);
  if (system_tray_height_ > 0)
    OnAutoHideStateChanged(shelf_->auto_hide_state());
}

void WorkAreaObserver::StopObserving() {
  Shell::GetInstance()->RemoveShellObserver(this);
  if (shelf_)
    shelf_->RemoveObserver(this);
  collection_ = NULL;
  shelf_ = NULL;
}

void WorkAreaObserver::OnDisplayWorkAreaInsetsChanged() {
  UpdateShelf();

  collection_->OnDisplayBoundsChanged(
      Shell::GetScreen()->GetDisplayNearestWindow(
          shelf_->shelf_widget()->GetNativeView()));
}

void WorkAreaObserver::OnAutoHideStateChanged(ShelfAutoHideState new_state) {
  gfx::Display display = Shell::GetScreen()->GetDisplayNearestWindow(
      shelf_->shelf_widget()->GetNativeView());
  gfx::Rect work_area = display.work_area();
  int width = 0;
  if ((shelf_->visibility_state() == SHELF_AUTO_HIDE) &&
      new_state == SHELF_AUTO_HIDE_SHOWN) {
    // Since the work_area is already reduced by kAutoHideSize, the inset width
    // should be just the difference.
    width = ShelfLayoutManager::GetPreferredShelfSize() -
        ShelfLayoutManager::kAutoHideSize;
  }
  work_area.Inset(shelf_->SelectValueForShelfAlignment(
      gfx::Insets(0, 0, width, 0),
      gfx::Insets(0, width, 0, 0),
      gfx::Insets(0, 0, 0, width),
      gfx::Insets(width, 0, 0, 0)));
  if (system_tray_height_ > 0) {
    work_area.set_height(
        std::max(0, work_area.height() - system_tray_height_));
    if (shelf_->GetAlignment() == SHELF_ALIGNMENT_TOP)
      work_area.set_y(work_area.y() + system_tray_height_);
  }
  collection_->SetDisplayInfo(work_area, display.bounds());
}

void WorkAreaObserver::UpdateShelf() {
  if (shelf_)
    return;

  shelf_ = ShelfLayoutManager::ForShelf(root_window_);
  if (shelf_)
    shelf_->AddObserver(this);
}

// Class to initialize and manage the WebNotificationBubble and
// TrayBubbleWrapper instances for a bubble.
class WebNotificationBubbleWrapper {
 public:
  // Takes ownership of |bubble| and creates |bubble_wrapper_|.
  WebNotificationBubbleWrapper(WebNotificationTray* tray,
                               message_center::MessageBubbleBase* bubble) {
    bubble_.reset(bubble);
    views::TrayBubbleView::AnchorAlignment anchor_alignment =
        tray->GetAnchorAlignment();
    views::TrayBubbleView::InitParams init_params =
        bubble->GetInitParams(anchor_alignment);
    views::View* anchor = tray->tray_container();
    if (anchor_alignment == views::TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM) {
      gfx::Point bounds(anchor->width() / 2, 0);
      views::View::ConvertPointToWidget(anchor, &bounds);
      init_params.arrow_offset = bounds.x();
    }
    views::TrayBubbleView* bubble_view = views::TrayBubbleView::Create(
        tray->GetBubbleWindowContainer(), anchor, tray, &init_params);
    if (ash::switches::UseAlternateShelfLayout())
      bubble_view->SetArrowPaintType(views::BubbleBorder::PAINT_NONE);
    bubble_wrapper_.reset(new TrayBubbleWrapper(tray, bubble_view));
    bubble->InitializeContents(bubble_view);
  }

  message_center::MessageBubbleBase* bubble() const { return bubble_.get(); }

  // Convenience accessors.
  views::TrayBubbleView* bubble_view() const { return bubble_->bubble_view(); }

 private:
  scoped_ptr<message_center::MessageBubbleBase> bubble_;
  scoped_ptr<TrayBubbleWrapper> bubble_wrapper_;

  DISALLOW_COPY_AND_ASSIGN(WebNotificationBubbleWrapper);
};

class WebNotificationButton : public views::CustomButton {
 public:
  WebNotificationButton(views::ButtonListener* listener)
      : views::CustomButton(listener),
        is_bubble_visible_(false),
        unread_count_(0) {
    SetLayoutManager(new views::FillLayout);
    unread_label_ = new views::Label();
    SetupLabelForTray(unread_label_);
    AddChildView(unread_label_);
  }

  void SetBubbleVisible(bool visible) {
    if (visible == is_bubble_visible_)
      return;

    is_bubble_visible_ = visible;
    UpdateIconVisibility();
  }

  void SetUnreadCount(int unread_count) {
    // base::FormatNumber doesn't convert to arabic numeric characters.
    // TODO(mukai): use ICU to support conversion for such locales.
    unread_count_ = unread_count;
    // TODO(mukai): move NINE_PLUS message to ui_strings, it doesn't need to be
    // in ash_strings.
    unread_label_->SetText((unread_count > 9) ?
        l10n_util::GetStringUTF16(IDS_ASH_NOTIFICATION_UNREAD_COUNT_NINE_PLUS) :
        base::FormatNumber(unread_count));
    UpdateIconVisibility();
  }

 protected:
  // Overridden from views::ImageButton:
  virtual gfx::Size GetPreferredSize() OVERRIDE {
    const int notification_item_size = GetShelfItemHeight();
    return gfx::Size(notification_item_size, notification_item_size);
  }

  virtual int GetHeightForWidth(int width) OVERRIDE {
    return GetPreferredSize().height();
  }

 private:
  void UpdateIconVisibility() {
    unread_label_->SetEnabledColor(
        (!is_bubble_visible_ && unread_count_ > 0) ?
        kWebNotificationColorWithUnread : kWebNotificationColorNoUnread);
    SchedulePaint();
  }

  bool is_bubble_visible_;
  int unread_count_;

  views::Label* unread_label_;

  DISALLOW_COPY_AND_ASSIGN(WebNotificationButton);
};

WebNotificationTray::WebNotificationTray(StatusAreaWidget* status_area_widget)
    : TrayBackgroundView(status_area_widget),
      button_(NULL),
      show_message_center_on_unlock_(false),
      should_update_tray_content_(false),
      should_block_shelf_auto_hide_(false) {
  button_ = new WebNotificationButton(this);
  button_->set_triggerable_event_flags(
      ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON);
  tray_container()->AddChildView(button_);
  SetContentsBackground();
  tray_container()->SetBorder(views::Border::NullBorder());
  SetVisible(false);
  message_center_tray_.reset(new message_center::MessageCenterTray(
      this,
      message_center::MessageCenter::Get()));
  popup_collection_.reset(new message_center::MessagePopupCollection(
      ash::Shell::GetContainer(
          status_area_widget->GetNativeView()->GetRootWindow(),
          kShellWindowId_StatusContainer),
      message_center(),
      message_center_tray_.get(),
      ash::switches::UseAlternateShelfLayout()));
  work_area_observer_.reset(new WorkAreaObserver());
  work_area_observer_->StartObserving(
      popup_collection_.get(),
      status_area_widget->GetNativeView()->GetRootWindow());
  OnMessageCenterTrayChanged();
}

WebNotificationTray::~WebNotificationTray() {
  // Release any child views that might have back pointers before ~View().
  message_center_bubble_.reset();
  popup_collection_.reset();
  work_area_observer_.reset();
}

// Public methods.

bool WebNotificationTray::ShowMessageCenterInternal(bool show_settings) {
  if (!ShouldShowMessageCenter())
    return false;

  should_block_shelf_auto_hide_ = true;
  message_center::MessageCenterBubble* message_center_bubble =
      new message_center::MessageCenterBubble(
          message_center(),
          message_center_tray_.get(),
          ash::switches::UseAlternateShelfLayout());

  int max_height = 0;
  aura::Window* status_area_window = status_area_widget()->GetNativeView();
  switch (GetShelfLayoutManager()->GetAlignment()) {
    case SHELF_ALIGNMENT_BOTTOM: {
      gfx::Rect shelf_bounds = GetShelfLayoutManager()->GetIdealBounds();
      max_height = shelf_bounds.y();
      break;
    }
    case SHELF_ALIGNMENT_TOP: {
      aura::Window* root = status_area_window->GetRootWindow();
      max_height =
          root->bounds().height() - status_area_window->bounds().height();
      break;
    }
    case SHELF_ALIGNMENT_LEFT:
    case SHELF_ALIGNMENT_RIGHT: {
      // Assume that the bottom line of the status area widget and the bubble
      // are aligned.
      max_height = status_area_window->GetBoundsInRootWindow().bottom();
      break;
    }
    default:
      NOTREACHED();
  }

  message_center_bubble->SetMaxHeight(std::max(0,
                                               max_height - GetTraySpacing()));
  if (show_settings)
    message_center_bubble->SetSettingsVisible();
  message_center_bubble_.reset(
      new WebNotificationBubbleWrapper(this, message_center_bubble));

  status_area_widget()->SetHideSystemNotifications(true);
  GetShelfLayoutManager()->UpdateAutoHideState();
  button_->SetBubbleVisible(true);
  SetDrawBackgroundAsActive(true);
  return true;
}

bool WebNotificationTray::ShowMessageCenter() {
  return ShowMessageCenterInternal(false /* show_settings */);
}

void WebNotificationTray::HideMessageCenter() {
  if (!message_center_bubble())
    return;
  SetDrawBackgroundAsActive(false);
  message_center_bubble_.reset();
  should_block_shelf_auto_hide_ = false;
  show_message_center_on_unlock_ = false;
  status_area_widget()->SetHideSystemNotifications(false);
  GetShelfLayoutManager()->UpdateAutoHideState();
  button_->SetBubbleVisible(false);
}

void WebNotificationTray::SetSystemTrayHeight(int height) {
  work_area_observer_->SetSystemTrayHeight(height);
}

bool WebNotificationTray::ShowPopups() {
  if (message_center_bubble())
    return false;

  popup_collection_->DoUpdateIfPossible();
  return true;
}

void WebNotificationTray::HidePopups() {
  DCHECK(popup_collection_.get());
  popup_collection_->MarkAllPopupsShown();
}

// Private methods.

bool WebNotificationTray::ShouldShowMessageCenter() {
  return status_area_widget()->login_status() != user::LOGGED_IN_LOCKED &&
      !(status_area_widget()->system_tray() &&
        status_area_widget()->system_tray()->HasNotificationBubble());
}

bool WebNotificationTray::ShouldBlockShelfAutoHide() const {
  return should_block_shelf_auto_hide_;
}

bool WebNotificationTray::IsMessageCenterBubbleVisible() const {
  return (message_center_bubble() &&
          message_center_bubble()->bubble()->IsVisible());
}

bool WebNotificationTray::IsMouseInNotificationBubble() const {
  return false;
}

void WebNotificationTray::ShowMessageCenterBubble() {
  if (!IsMessageCenterBubbleVisible())
    message_center_tray_->ShowMessageCenterBubble();
}

void WebNotificationTray::UpdateAfterLoginStatusChange(
    user::LoginStatus login_status) {
  OnMessageCenterTrayChanged();
}

void WebNotificationTray::SetShelfAlignment(ShelfAlignment alignment) {
  if (alignment == shelf_alignment())
    return;
  TrayBackgroundView::SetShelfAlignment(alignment);
  tray_container()->SetBorder(views::Border::NullBorder());
  // Destroy any existing bubble so that it will be rebuilt correctly.
  message_center_tray_->HideMessageCenterBubble();
  message_center_tray_->HidePopupBubble();
}

void WebNotificationTray::AnchorUpdated() {
  if (message_center_bubble()) {
    message_center_bubble()->bubble_view()->UpdateBubble();
    UpdateBubbleViewArrow(message_center_bubble()->bubble_view());
  }
}

base::string16 WebNotificationTray::GetAccessibleNameForTray() {
  return l10n_util::GetStringUTF16(
      IDS_MESSAGE_CENTER_ACCESSIBLE_NAME);
}

void WebNotificationTray::HideBubbleWithView(
    const views::TrayBubbleView* bubble_view) {
  if (message_center_bubble() &&
      bubble_view == message_center_bubble()->bubble_view()) {
    message_center_tray_->HideMessageCenterBubble();
  } else if (popup_collection_.get()) {
    message_center_tray_->HidePopupBubble();
  }
}

bool WebNotificationTray::PerformAction(const ui::Event& event) {
  if (message_center_bubble())
    message_center_tray_->HideMessageCenterBubble();
  else
    message_center_tray_->ShowMessageCenterBubble();
  return true;
}

void WebNotificationTray::BubbleViewDestroyed() {
  if (message_center_bubble())
    message_center_bubble()->bubble()->BubbleViewDestroyed();
}

void WebNotificationTray::OnMouseEnteredView() {}

void WebNotificationTray::OnMouseExitedView() {}

base::string16 WebNotificationTray::GetAccessibleNameForBubble() {
  return GetAccessibleNameForTray();
}

gfx::Rect WebNotificationTray::GetAnchorRect(
    views::Widget* anchor_widget,
    views::TrayBubbleView::AnchorType anchor_type,
    views::TrayBubbleView::AnchorAlignment anchor_alignment) {
  return GetBubbleAnchorRect(anchor_widget, anchor_type, anchor_alignment);
}

void WebNotificationTray::HideBubble(const views::TrayBubbleView* bubble_view) {
  HideBubbleWithView(bubble_view);
}

bool WebNotificationTray::ShowNotifierSettings() {
  if (message_center_bubble()) {
    static_cast<message_center::MessageCenterBubble*>(
        message_center_bubble()->bubble())->SetSettingsVisible();
    return true;
  }
  return ShowMessageCenterInternal(true /* show_settings */);
}

bool WebNotificationTray::IsContextMenuEnabled() const {
  user::LoginStatus login_status = status_area_widget()->login_status();
  return login_status != user::LOGGED_IN_NONE
      && login_status != user::LOGGED_IN_LOCKED;
}

message_center::MessageCenterTray* WebNotificationTray::GetMessageCenterTray() {
  return message_center_tray_.get();
}

bool WebNotificationTray::IsCommandIdChecked(int command_id) const {
  if (command_id != kToggleQuietMode)
    return false;
  return message_center()->IsQuietMode();
}

bool WebNotificationTray::IsCommandIdEnabled(int command_id) const {
  return true;
}

bool WebNotificationTray::GetAcceleratorForCommandId(
    int command_id,
    ui::Accelerator* accelerator) {
  return false;
}

void WebNotificationTray::ExecuteCommand(int command_id, int event_flags) {
  if (command_id == kToggleQuietMode) {
    bool in_quiet_mode = message_center()->IsQuietMode();
    message_center()->SetQuietMode(!in_quiet_mode);
    return;
  }
  base::TimeDelta expires_in = command_id == kEnableQuietModeDay ?
      base::TimeDelta::FromDays(1):
      base::TimeDelta::FromHours(1);
  message_center()->EnterQuietModeWithExpire(expires_in);
}

void WebNotificationTray::ButtonPressed(views::Button* sender,
                                        const ui::Event& event) {
  DCHECK_EQ(button_, sender);
  PerformAction(event);
}

void WebNotificationTray::OnMessageCenterTrayChanged() {
  // Do not update the tray contents directly. Multiple change events can happen
  // consecutively, and calling Update in the middle of those events will show
  // intermediate unread counts for a moment.
  should_update_tray_content_ = true;
  base::MessageLoop::current()->PostTask(
      FROM_HERE,
      base::Bind(&WebNotificationTray::UpdateTrayContent, AsWeakPtr()));
}

void WebNotificationTray::UpdateTrayContent() {
  if (!should_update_tray_content_)
    return;
  should_update_tray_content_ = false;

  message_center::MessageCenter* message_center =
      message_center_tray_->message_center();
  button_->SetUnreadCount(message_center->UnreadNotificationCount());
  if (IsMessageCenterBubbleVisible())
    button_->SetState(views::CustomButton::STATE_PRESSED);
  else
    button_->SetState(views::CustomButton::STATE_NORMAL);
  SetVisible((status_area_widget()->login_status() != user::LOGGED_IN_NONE) &&
             (status_area_widget()->login_status() != user::LOGGED_IN_LOCKED) &&
             (message_center->NotificationCount() > 0));
  Layout();
  SchedulePaint();
}

bool WebNotificationTray::ClickedOutsideBubble() {
  // Only hide the message center
  if (!message_center_bubble())
    return false;

  message_center_tray_->HideMessageCenterBubble();
  return true;
}

message_center::MessageCenter* WebNotificationTray::message_center() const {
  return message_center_tray_->message_center();
}

// Methods for testing

bool WebNotificationTray::IsPopupVisible() const {
  return message_center_tray_->popups_visible();
}

message_center::MessageCenterBubble*
WebNotificationTray::GetMessageCenterBubbleForTest() {
  if (!message_center_bubble())
    return NULL;
  return static_cast<message_center::MessageCenterBubble*>(
      message_center_bubble()->bubble());
}

}  // namespace ash

/* [<][>][^][v][top][bottom][index][help] */