// Copyright 2013 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/shelf/shelf_tooltip_manager.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/window_animations.h"
#include "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/gfx/insets.h"
#include "ui/views/bubble/bubble_delegate.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
const int kTooltipTopBottomMargin = 3;
const int kTooltipLeftRightMargin = 10;
const int kTooltipAppearanceDelay = 1000; // msec
const int kTooltipMinHeight = 29 - 2 * kTooltipTopBottomMargin;
const SkColor kTooltipTextColor = SkColorSetRGB(0x22, 0x22, 0x22);
// The maximum width of the tooltip bubble. Borrowed the value from
// ash/tooltip/tooltip_controller.cc
const int kTooltipMaxWidth = 250;
// The offset for the tooltip bubble - making sure that the bubble is flush
// with the shelf. The offset includes the arrow size in pixels as well as
// the activation bar and other spacing elements.
const int kArrowOffsetLeftRight = 11;
const int kArrowOffsetTopBottom = 7;
} // namespace
// The implementation of tooltip of the launcher.
class ShelfTooltipManager::ShelfTooltipBubble
: public views::BubbleDelegateView {
public:
ShelfTooltipBubble(views::View* anchor,
views::BubbleBorder::Arrow arrow,
ShelfTooltipManager* host);
void SetText(const base::string16& text);
void Close();
private:
// views::WidgetDelegate overrides:
virtual void WindowClosing() OVERRIDE;
// views::View overrides:
virtual gfx::Size GetPreferredSize() const OVERRIDE;
ShelfTooltipManager* host_;
views::Label* label_;
DISALLOW_COPY_AND_ASSIGN(ShelfTooltipBubble);
};
ShelfTooltipManager::ShelfTooltipBubble::ShelfTooltipBubble(
views::View* anchor,
views::BubbleBorder::Arrow arrow,
ShelfTooltipManager* host)
: views::BubbleDelegateView(anchor, arrow), host_(host) {
gfx::Insets insets = gfx::Insets(kArrowOffsetTopBottom,
kArrowOffsetLeftRight,
kArrowOffsetTopBottom,
kArrowOffsetLeftRight);
// Shelf items can have an asymmetrical border for spacing reasons.
// Adjust anchor location for this.
if (anchor->border())
insets += anchor->border()->GetInsets();
set_anchor_view_insets(insets);
set_close_on_esc(false);
set_close_on_deactivate(false);
set_use_focusless(true);
set_accept_events(false);
set_margins(gfx::Insets(kTooltipTopBottomMargin, kTooltipLeftRightMargin,
kTooltipTopBottomMargin, kTooltipLeftRightMargin));
set_shadow(views::BubbleBorder::SMALL_SHADOW);
SetLayoutManager(new views::FillLayout());
// The anchor may not have the widget in tests.
if (anchor->GetWidget() && anchor->GetWidget()->GetNativeView()) {
aura::Window* root_window =
anchor->GetWidget()->GetNativeView()->GetRootWindow();
set_parent_window(ash::Shell::GetInstance()->GetContainer(
root_window, ash::kShellWindowId_SettingBubbleContainer));
}
label_ = new views::Label;
label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
label_->SetEnabledColor(kTooltipTextColor);
AddChildView(label_);
views::BubbleDelegateView::CreateBubble(this);
}
void ShelfTooltipManager::ShelfTooltipBubble::SetText(
const base::string16& text) {
label_->SetText(text);
SizeToContents();
}
void ShelfTooltipManager::ShelfTooltipBubble::Close() {
if (GetWidget()) {
host_ = NULL;
GetWidget()->Close();
}
}
void ShelfTooltipManager::ShelfTooltipBubble::WindowClosing() {
views::BubbleDelegateView::WindowClosing();
if (host_)
host_->OnBubbleClosed(this);
}
gfx::Size ShelfTooltipManager::ShelfTooltipBubble::GetPreferredSize() const {
gfx::Size pref_size = views::BubbleDelegateView::GetPreferredSize();
if (pref_size.height() < kTooltipMinHeight)
pref_size.set_height(kTooltipMinHeight);
if (pref_size.width() > kTooltipMaxWidth)
pref_size.set_width(kTooltipMaxWidth);
return pref_size;
}
ShelfTooltipManager::ShelfTooltipManager(
ShelfLayoutManager* shelf_layout_manager,
ShelfView* shelf_view)
: view_(NULL),
widget_(NULL),
anchor_(NULL),
shelf_layout_manager_(shelf_layout_manager),
shelf_view_(shelf_view),
weak_factory_(this) {
if (shelf_layout_manager)
shelf_layout_manager->AddObserver(this);
if (Shell::HasInstance())
Shell::GetInstance()->AddPreTargetHandler(this);
}
ShelfTooltipManager::~ShelfTooltipManager() {
CancelHidingAnimation();
Close();
if (shelf_layout_manager_)
shelf_layout_manager_->RemoveObserver(this);
if (Shell::HasInstance())
Shell::GetInstance()->RemovePreTargetHandler(this);
}
void ShelfTooltipManager::ShowDelayed(views::View* anchor,
const base::string16& text) {
if (view_) {
if (timer_.get() && timer_->IsRunning()) {
return;
} else {
CancelHidingAnimation();
Close();
}
}
if (shelf_layout_manager_ && !shelf_layout_manager_->IsVisible())
return;
CreateBubble(anchor, text);
ResetTimer();
}
void ShelfTooltipManager::ShowImmediately(views::View* anchor,
const base::string16& text) {
if (view_) {
if (timer_.get() && timer_->IsRunning())
StopTimer();
CancelHidingAnimation();
Close();
}
if (shelf_layout_manager_ && !shelf_layout_manager_->IsVisible())
return;
CreateBubble(anchor, text);
ShowInternal();
}
void ShelfTooltipManager::Close() {
StopTimer();
if (view_) {
view_->Close();
view_ = NULL;
widget_ = NULL;
}
}
void ShelfTooltipManager::OnBubbleClosed(views::BubbleDelegateView* view) {
if (view == view_) {
view_ = NULL;
widget_ = NULL;
}
}
void ShelfTooltipManager::UpdateArrow() {
if (view_) {
CancelHidingAnimation();
Close();
ShowImmediately(anchor_, text_);
}
}
void ShelfTooltipManager::ResetTimer() {
if (timer_.get() && timer_->IsRunning()) {
timer_->Reset();
return;
}
// We don't start the timer if the shelf isn't visible.
if (shelf_layout_manager_ && !shelf_layout_manager_->IsVisible())
return;
CreateTimer(kTooltipAppearanceDelay);
}
void ShelfTooltipManager::StopTimer() {
timer_.reset();
}
bool ShelfTooltipManager::IsVisible() {
if (timer_.get() && timer_->IsRunning())
return false;
return widget_ && widget_->IsVisible();
}
void ShelfTooltipManager::CreateZeroDelayTimerForTest() {
CreateTimer(0);
}
void ShelfTooltipManager::OnMouseEvent(ui::MouseEvent* event) {
DCHECK(event);
DCHECK(event->target());
if (!widget_ || !widget_->IsVisible())
return;
DCHECK(view_);
DCHECK(shelf_view_);
// Pressing the mouse button anywhere should close the tooltip.
if (event->type() == ui::ET_MOUSE_PRESSED) {
CloseSoon();
return;
}
aura::Window* target = static_cast<aura::Window*>(event->target());
if (widget_->GetNativeWindow()->GetRootWindow() != target->GetRootWindow()) {
CloseSoon();
return;
}
gfx::Point location_in_shelf_view = event->location();
aura::Window::ConvertPointToTarget(
target, shelf_view_->GetWidget()->GetNativeWindow(),
&location_in_shelf_view);
if (shelf_view_->ShouldHideTooltip(location_in_shelf_view)) {
// Because this mouse event may arrive to |view_|, here we just schedule
// the closing event rather than directly calling Close().
CloseSoon();
}
}
void ShelfTooltipManager::OnTouchEvent(ui::TouchEvent* event) {
aura::Window* target = static_cast<aura::Window*>(event->target());
if (widget_ && widget_->IsVisible() && widget_->GetNativeWindow() != target)
Close();
}
void ShelfTooltipManager::OnGestureEvent(ui::GestureEvent* event) {
if (widget_ && widget_->IsVisible()) {
// Because this mouse event may arrive to |view_|, here we just schedule
// the closing event rather than directly calling Close().
CloseSoon();
}
}
void ShelfTooltipManager::OnCancelMode(ui::CancelModeEvent* event) {
Close();
}
void ShelfTooltipManager::WillDeleteShelf() {
shelf_layout_manager_ = NULL;
}
void ShelfTooltipManager::WillChangeVisibilityState(
ShelfVisibilityState new_state) {
if (new_state == SHELF_HIDDEN) {
StopTimer();
Close();
}
}
void ShelfTooltipManager::OnAutoHideStateChanged(ShelfAutoHideState new_state) {
if (new_state == SHELF_AUTO_HIDE_HIDDEN) {
StopTimer();
// AutoHide state change happens during an event filter, so immediate close
// may cause a crash in the HandleMouseEvent() after the filter. So we just
// schedule the Close here.
CloseSoon();
}
}
void ShelfTooltipManager::CancelHidingAnimation() {
if (!widget_ || !widget_->GetNativeView())
return;
gfx::NativeView native_view = widget_->GetNativeView();
wm::SetWindowVisibilityAnimationTransition(
native_view, wm::ANIMATE_NONE);
}
void ShelfTooltipManager::CloseSoon() {
base::MessageLoopForUI::current()->PostTask(
FROM_HERE,
base::Bind(&ShelfTooltipManager::Close, weak_factory_.GetWeakPtr()));
}
void ShelfTooltipManager::ShowInternal() {
if (view_)
view_->GetWidget()->Show();
timer_.reset();
}
void ShelfTooltipManager::CreateBubble(views::View* anchor,
const base::string16& text) {
DCHECK(!view_);
anchor_ = anchor;
text_ = text;
views::BubbleBorder::Arrow arrow =
shelf_layout_manager_->SelectValueForShelfAlignment(
views::BubbleBorder::BOTTOM_CENTER,
views::BubbleBorder::LEFT_CENTER,
views::BubbleBorder::RIGHT_CENTER,
views::BubbleBorder::TOP_CENTER);
view_ = new ShelfTooltipBubble(anchor, arrow, this);
widget_ = view_->GetWidget();
view_->SetText(text_);
gfx::NativeView native_view = widget_->GetNativeView();
wm::SetWindowVisibilityAnimationType(
native_view, wm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL);
wm::SetWindowVisibilityAnimationTransition(
native_view, wm::ANIMATE_HIDE);
}
void ShelfTooltipManager::CreateTimer(int delay_in_ms) {
base::OneShotTimer<ShelfTooltipManager>* new_timer =
new base::OneShotTimer<ShelfTooltipManager>();
new_timer->Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(delay_in_ms),
this,
&ShelfTooltipManager::ShowInternal);
timer_.reset(new_timer);
}
} // namespace ash