blob: 38ca2705cf17e104dab0677962a0df232e3f036a [file] [log] [blame] [edit]
// 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/tray/system_tray.h"
#include "ash/shell.h"
#include "ash/shell/panel_window.h"
#include "ash/shell_window_ids.h"
#include "ash/system/audio/tray_volume.h"
#include "ash/system/bluetooth/tray_bluetooth.h"
#include "ash/system/brightness/tray_brightness.h"
#include "ash/system/date/tray_date.h"
#include "ash/system/drive/tray_drive.h"
#include "ash/system/ime/tray_ime.h"
#include "ash/system/locale/tray_locale.h"
#include "ash/system/network/tray_network.h"
#include "ash/system/network/tray_sms.h"
#include "ash/system/power/power_status_observer.h"
#include "ash/system/power/power_supply_status.h"
#include "ash/system/power/tray_power.h"
#include "ash/system/settings/tray_settings.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/tray/system_tray_bubble.h"
#include "ash/system/tray/system_tray_delegate.h"
#include "ash/system/tray/system_tray_item.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/tray_accessibility.h"
#include "ash/system/tray_caps_lock.h"
#include "ash/system/tray_display.h"
#include "ash/system/tray_update.h"
#include "ash/system/user/login_status.h"
#include "ash/system/user/tray_user.h"
#include "ash/wm/shelf_layout_manager.h"
#include "base/logging.h"
#include "base/timer.h"
#include "base/utf_string_conversions.h"
#include "grit/ash_strings.h"
#include "ui/aura/root_window.h"
#include "ui/base/events/event_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/screen.h"
#include "ui/gfx/skia_util.h"
#include "ui/views/border.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/view.h"
namespace ash {
// SystemTray
using internal::SystemTrayBubble;
using internal::TrayBubbleView;
SystemTray::SystemTray(internal::StatusAreaWidget* status_area_widget)
: internal::TrayBackgroundView(status_area_widget),
items_(),
accessibility_observer_(NULL),
audio_observer_(NULL),
bluetooth_observer_(NULL),
brightness_observer_(NULL),
caps_lock_observer_(NULL),
clock_observer_(NULL),
drive_observer_(NULL),
ime_observer_(NULL),
locale_observer_(NULL),
network_observer_(NULL),
update_observer_(NULL),
user_observer_(NULL),
default_bubble_height_(0),
hide_notifications_(false) {
SetContentsBackground();
}
SystemTray::~SystemTray() {
bubble_.reset();
for (std::vector<SystemTrayItem*>::iterator it = items_.begin();
it != items_.end();
++it) {
(*it)->DestroyTrayView();
}
}
void SystemTray::CreateItems() {
internal::TrayVolume* tray_volume = new internal::TrayVolume();
internal::TrayBluetooth* tray_bluetooth = new internal::TrayBluetooth();
internal::TrayBrightness* tray_brightness = new internal::TrayBrightness();
internal::TrayDate* tray_date = new internal::TrayDate();
internal::TrayPower* tray_power = new internal::TrayPower();
internal::TrayNetwork* tray_network = new internal::TrayNetwork;
internal::TraySms* tray_sms = new internal::TraySms();
internal::TrayUser* tray_user = new internal::TrayUser;
internal::TrayAccessibility* tray_accessibility =
new internal::TrayAccessibility;
internal::TrayCapsLock* tray_caps_lock = new internal::TrayCapsLock;
internal::TrayDrive* tray_drive = new internal::TrayDrive;
internal::TrayIME* tray_ime = new internal::TrayIME;
internal::TrayLocale* tray_locale = new internal::TrayLocale;
internal::TrayUpdate* tray_update = new internal::TrayUpdate;
internal::TraySettings* tray_settings = new internal::TraySettings();
internal::TrayDisplay* tray_display = new internal::TrayDisplay;
accessibility_observer_ = tray_accessibility;
audio_observer_ = tray_volume;
bluetooth_observer_ = tray_bluetooth;
brightness_observer_ = tray_brightness;
caps_lock_observer_ = tray_caps_lock;
clock_observer_ = tray_date;
drive_observer_ = tray_drive;
ime_observer_ = tray_ime;
locale_observer_ = tray_locale;
network_observer_ = tray_network;
power_status_observers_.AddObserver(tray_power);
power_status_observers_.AddObserver(tray_settings);
sms_observer_ = tray_sms;
update_observer_ = tray_update;
user_observer_ = tray_user;
AddTrayItem(tray_user);
AddTrayItem(tray_power);
AddTrayItem(tray_network);
AddTrayItem(tray_bluetooth);
AddTrayItem(tray_sms);
AddTrayItem(tray_drive);
AddTrayItem(tray_ime);
AddTrayItem(tray_locale);
AddTrayItem(tray_display);
AddTrayItem(tray_volume);
AddTrayItem(tray_brightness);
AddTrayItem(tray_update);
AddTrayItem(tray_accessibility);
AddTrayItem(tray_caps_lock);
AddTrayItem(tray_settings);
AddTrayItem(tray_date);
SetVisible(ash::Shell::GetInstance()->tray_delegate()->
GetTrayVisibilityOnStartup());
}
void SystemTray::AddTrayItem(SystemTrayItem* item) {
items_.push_back(item);
SystemTrayDelegate* delegate = Shell::GetInstance()->tray_delegate();
views::View* tray_item = item->CreateTrayView(delegate->GetUserLoginStatus());
item->UpdateAfterShelfAlignmentChange(shelf_alignment());
if (tray_item) {
tray_container()->AddChildViewAt(tray_item, 0);
PreferredSizeChanged();
tray_item_map_[item] = tray_item;
}
}
void SystemTray::RemoveTrayItem(SystemTrayItem* item) {
NOTIMPLEMENTED();
}
void SystemTray::ShowDefaultView(BubbleCreationType creation_type) {
ShowDefaultViewWithOffset(creation_type,
TrayBubbleView::InitParams::kArrowDefaultOffset);
}
void SystemTray::ShowDetailedView(SystemTrayItem* item,
int close_delay,
bool activate,
BubbleCreationType creation_type) {
std::vector<SystemTrayItem*> items;
items.push_back(item);
ShowItems(items, true, activate, creation_type, GetTrayXOffset(item));
bubble_->StartAutoCloseTimer(close_delay);
}
void SystemTray::SetDetailedViewCloseDelay(int close_delay) {
if (bubble_.get() &&
bubble_->bubble_type() == SystemTrayBubble::BUBBLE_TYPE_DETAILED)
bubble_->StartAutoCloseTimer(close_delay);
}
void SystemTray::HideDetailedView(SystemTrayItem* item) {
if (item != detailed_item_)
return;
DestroyBubble();
UpdateNotificationBubble();
}
void SystemTray::ShowNotificationView(SystemTrayItem* item) {
if (std::find(notification_items_.begin(), notification_items_.end(), item)
!= notification_items_.end())
return;
notification_items_.push_back(item);
UpdateNotificationBubble();
}
void SystemTray::HideNotificationView(SystemTrayItem* item) {
std::vector<SystemTrayItem*>::iterator found_iter =
std::find(notification_items_.begin(), notification_items_.end(), item);
if (found_iter == notification_items_.end())
return;
notification_items_.erase(found_iter);
// Only update the notification bubble if visible (i.e. don't create one).
if (notification_bubble_.get())
UpdateNotificationBubble();
}
void SystemTray::UpdateAfterLoginStatusChange(user::LoginStatus login_status) {
DestroyBubble();
for (std::vector<SystemTrayItem*>::iterator it = items_.begin();
it != items_.end();
++it) {
(*it)->UpdateAfterLoginStatusChange(login_status);
}
SetVisible(true);
PreferredSizeChanged();
}
void SystemTray::UpdateAfterShelfAlignmentChange(ShelfAlignment alignment) {
for (std::vector<SystemTrayItem*>::iterator it = items_.begin();
it != items_.end();
++it) {
(*it)->UpdateAfterShelfAlignmentChange(alignment);
}
}
void SystemTray::SetHideNotifications(bool hide_notifications) {
if (notification_bubble_.get())
notification_bubble_->SetVisible(!hide_notifications);
hide_notifications_ = hide_notifications;
}
bool SystemTray::HasSystemBubble() const {
return bubble_.get() != NULL;
}
bool SystemTray::IsAnyBubbleVisible() const {
if (bubble_.get() && bubble_->IsVisible())
return true;
if (notification_bubble_.get() && notification_bubble_->IsVisible())
return true;
return false;
}
bool SystemTray::IsMouseInNotificationBubble() const {
if (!notification_bubble_.get())
return false;
return notification_bubble_->bubble_view()->GetBoundsInScreen().Contains(
gfx::Screen::GetCursorScreenPoint());
}
bool SystemTray::CloseBubbleForTest() const {
if (!bubble_.get())
return false;
bubble_->Close();
return true;
}
// Private methods.
void SystemTray::DestroyBubble() {
bubble_.reset();
detailed_item_ = NULL;
}
void SystemTray::RemoveBubble(SystemTrayBubble* bubble) {
if (bubble == bubble_.get()) {
DestroyBubble();
UpdateNotificationBubble(); // State changed, re-create notifications.
Shell::GetInstance()->shelf()->UpdateAutoHideState();
} else if (bubble == notification_bubble_) {
notification_bubble_.reset();
status_area_widget()->SetHideWebNotifications(false);
} else {
NOTREACHED();
}
}
int SystemTray::GetTrayXOffset(SystemTrayItem* item) const {
// Don't attempt to align the arrow if the shelf is on the left or right.
if (shelf_alignment() != SHELF_ALIGNMENT_BOTTOM)
return TrayBubbleView::InitParams::kArrowDefaultOffset;
std::map<SystemTrayItem*, views::View*>::const_iterator it =
tray_item_map_.find(item);
if (it == tray_item_map_.end())
return TrayBubbleView::InitParams::kArrowDefaultOffset;
const views::View* item_view = it->second;
if (item_view->bounds().IsEmpty()) {
// The bounds of item could be still empty if it does not have a visible
// tray view. In that case, use the default (minimum) offset.
return TrayBubbleView::InitParams::kArrowDefaultOffset;
}
gfx::Point point(item_view->width() / 2, 0);
ConvertPointToWidget(item_view, &point);
return point.x();
}
void SystemTray::ShowDefaultViewWithOffset(BubbleCreationType creation_type,
int arrow_offset) {
ShowItems(items_.get(), false, true, creation_type, arrow_offset);
}
void SystemTray::ShowItems(const std::vector<SystemTrayItem*>& items,
bool detailed,
bool can_activate,
BubbleCreationType creation_type,
int arrow_offset) {
// Destroy any existing bubble and create a new one.
SystemTrayBubble::BubbleType bubble_type = detailed ?
SystemTrayBubble::BUBBLE_TYPE_DETAILED :
SystemTrayBubble::BUBBLE_TYPE_DEFAULT;
// Destroy the notification bubble here so that it doesn't get rebuilt
// while we add items to the main bubble_ (e.g. in HideNotificationView).
notification_bubble_.reset();
if (bubble_.get() && creation_type == BUBBLE_USE_EXISTING) {
bubble_->UpdateView(items, bubble_type);
} else {
bubble_.reset(new SystemTrayBubble(this, items, bubble_type));
ash::SystemTrayDelegate* delegate =
ash::Shell::GetInstance()->tray_delegate();
views::View* anchor = tray_container();
TrayBubbleView::InitParams init_params(TrayBubbleView::ANCHOR_TYPE_TRAY,
shelf_alignment());
init_params.can_activate = can_activate;
if (detailed) {
// This is the case where a volume control or brightness control bubble
// is created.
init_params.max_height = default_bubble_height_;
init_params.arrow_color = kBackgroundColor;
}
init_params.arrow_offset = arrow_offset;
bubble_->InitView(anchor, init_params, delegate->GetUserLoginStatus());
}
// Save height of default view for creating detailed views directly.
if (!detailed)
default_bubble_height_ = bubble_->bubble_view()->height();
if (detailed && items.size() > 0)
detailed_item_ = items[0];
else
detailed_item_ = NULL;
UpdateNotificationBubble(); // State changed, re-create notifications.
status_area_widget()->SetHideWebNotifications(true);
Shell::GetInstance()->shelf()->UpdateAutoHideState();
}
void SystemTray::UpdateNotificationBubble() {
// Only show the notification buble if we have notifications and we are not
// showing the default bubble.
if (notification_items_.empty() ||
(bubble_.get() &&
bubble_->bubble_type() == SystemTrayBubble::BUBBLE_TYPE_DEFAULT)) {
notification_bubble_.reset();
status_area_widget()->SetHideWebNotifications(false);
return;
}
if (bubble_.get() &&
bubble_->bubble_type() == SystemTrayBubble::BUBBLE_TYPE_DETAILED) {
// Skip notifications for any currently displayed detailed item.
std::vector<SystemTrayItem*> items;
for (std::vector<SystemTrayItem*>::iterator iter =
notification_items_.begin();
iter != notification_items_.end(); ++ iter) {
if (*iter != detailed_item_)
items.push_back(*iter);
}
notification_bubble_.reset(new SystemTrayBubble(
this, items, SystemTrayBubble::BUBBLE_TYPE_NOTIFICATION));
} else {
// Show all notifications.
notification_bubble_.reset(new SystemTrayBubble(
this, notification_items_, SystemTrayBubble::BUBBLE_TYPE_NOTIFICATION));
}
views::View* anchor;
TrayBubbleView::AnchorType anchor_type;
if (bubble_.get()) {
anchor = bubble_->bubble_view();
anchor_type = TrayBubbleView::ANCHOR_TYPE_BUBBLE;
} else {
anchor = tray_container();
anchor_type = TrayBubbleView::ANCHOR_TYPE_TRAY;
}
TrayBubbleView::InitParams init_params(anchor_type, shelf_alignment());
init_params.arrow_offset = GetTrayXOffset(notification_items_[0]);
init_params.arrow_color = kBackgroundColor;
user::LoginStatus login_status =
Shell::GetInstance()->tray_delegate()->GetUserLoginStatus();
notification_bubble_->InitView(anchor, init_params, login_status);
if (hide_notifications_)
notification_bubble_->SetVisible(false);
else
status_area_widget()->SetHideWebNotifications(true);
}
void SystemTray::Initialize() {
internal::TrayBackgroundView::Initialize();
CreateItems();
}
void SystemTray::SetShelfAlignment(ShelfAlignment alignment) {
if (alignment == shelf_alignment())
return;
internal::TrayBackgroundView::SetShelfAlignment(alignment);
UpdateAfterShelfAlignmentChange(alignment);
// Destroy any existing bubble so that it is rebuilt correctly.
bubble_.reset();
// Rebuild any notification bubble.
if (notification_bubble_.get()) {
notification_bubble_.reset();
UpdateNotificationBubble();
}
}
void SystemTray::AnchorUpdated() {
if (notification_bubble_.get()) {
notification_bubble_->bubble_view()->UpdateBubble();
// Ensure that the notification buble is above the launcher/status area.
notification_bubble_->bubble_view()->GetWidget()->StackAtTop();
}
if (bubble_.get())
bubble_->bubble_view()->UpdateBubble();
}
string16 SystemTray::GetAccessibleName() {
return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBLE_NAME);
}
bool SystemTray::PerformAction(const ui::Event& event) {
// If we're already showing the default view, hide it; otherwise, show it
// (and hide any popup that's currently shown).
if (bubble_.get() &&
bubble_->bubble_type() == SystemTrayBubble::BUBBLE_TYPE_DEFAULT) {
bubble_->Close();
} else {
int arrow_offset = TrayBubbleView::InitParams::kArrowDefaultOffset;
if (event.IsMouseEvent() || event.type() == ui::ET_GESTURE_TAP) {
const ui::LocatedEvent& located_event =
static_cast<const ui::LocatedEvent&>(event);
if (shelf_alignment() == SHELF_ALIGNMENT_BOTTOM) {
gfx::Point point(located_event.x(), 0);
ConvertPointToWidget(this, &point);
arrow_offset = point.x();
}
}
ShowDefaultViewWithOffset(BUBBLE_CREATE_NEW, arrow_offset);
}
return true;
}
} // namespace ash