blob: c817e8f58de085f5df69b8d5f0bb7e6b8fe7da1f [file] [log] [blame]
// 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/launcher/launcher_tooltip_manager.h"
#include "ash/launcher/launcher_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.h"
#include "base/time.h"
#include "base/timer.h"
#include "ui/aura/root_window.h"
#include "ui/aura/window.h"
#include "ui/base/events/event.h"
#include "ui/base/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 internal {
namespace {
const int kTooltipTopBottomMargin = 3;
const int kTooltipLeftRightMargin = 10;
const int kTooltipAppearanceDelay = 200; // 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 distance between the arrow tip and edge of the anchor view.
const int kArrowOffset = 10;
views::BubbleBorder::ArrowLocation GetArrowLocation(ShelfAlignment alignment) {
switch (alignment) {
case SHELF_ALIGNMENT_LEFT:
return views::BubbleBorder::LEFT_CENTER;
case SHELF_ALIGNMENT_RIGHT:
return views::BubbleBorder::RIGHT_CENTER;
case SHELF_ALIGNMENT_BOTTOM:
return views::BubbleBorder::BOTTOM_CENTER;
}
return views::BubbleBorder::NONE;
}
} // namespace
// The implementation of tooltip of the launcher.
class LauncherTooltipManager::LauncherTooltipBubble
: public views::BubbleDelegateView {
public:
LauncherTooltipBubble(views::View* anchor,
views::BubbleBorder::ArrowLocation arrow_location,
LauncherTooltipManager* host);
void SetText(const string16& text);
void Close();
private:
// views::WidgetDelegate overrides:
virtual void WindowClosing() OVERRIDE;
// views::View overrides:
virtual gfx::Size GetPreferredSize() OVERRIDE;
LauncherTooltipManager* host_;
views::Label* label_;
DISALLOW_COPY_AND_ASSIGN(LauncherTooltipBubble);
};
LauncherTooltipManager::LauncherTooltipBubble::LauncherTooltipBubble(
views::View* anchor,
views::BubbleBorder::ArrowLocation arrow_location,
LauncherTooltipManager* host)
: views::BubbleDelegateView(anchor, arrow_location),
host_(host) {
set_anchor_insets(gfx::Insets(kArrowOffset, kArrowOffset, kArrowOffset,
kArrowOffset));
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::RootWindow* root_window =
anchor->GetWidget()->GetNativeView()->GetRootWindow();
set_parent_window(ash::Shell::GetInstance()->GetContainer(
root_window, ash::internal::kShellWindowId_SettingBubbleContainer));
}
label_ = new views::Label;
label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
label_->SetEnabledColor(kTooltipTextColor);
label_->SetElideBehavior(views::Label::ELIDE_AT_END);
AddChildView(label_);
views::BubbleDelegateView::CreateBubble(this);
}
void LauncherTooltipManager::LauncherTooltipBubble::SetText(
const string16& text) {
label_->SetText(text);
SizeToContents();
}
void LauncherTooltipManager::LauncherTooltipBubble::Close() {
if (GetWidget()) {
host_ = NULL;
GetWidget()->Close();
}
}
void LauncherTooltipManager::LauncherTooltipBubble::WindowClosing() {
views::BubbleDelegateView::WindowClosing();
if (host_)
host_->OnBubbleClosed(this);
}
gfx::Size LauncherTooltipManager::LauncherTooltipBubble::GetPreferredSize() {
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;
}
LauncherTooltipManager::LauncherTooltipManager(
ShelfAlignment alignment,
ShelfLayoutManager* shelf_layout_manager,
LauncherView* launcher_view)
: view_(NULL),
widget_(NULL),
anchor_(NULL),
alignment_(alignment),
shelf_layout_manager_(shelf_layout_manager),
launcher_view_(launcher_view) {
if (shelf_layout_manager)
shelf_layout_manager->AddObserver(this);
if (Shell::HasInstance())
Shell::GetInstance()->AddEnvEventFilter(this);
}
LauncherTooltipManager::~LauncherTooltipManager() {
CancelHidingAnimation();
Close();
if (shelf_layout_manager_)
shelf_layout_manager_->RemoveObserver(this);
if (Shell::HasInstance())
Shell::GetInstance()->RemoveEnvEventFilter(this);
}
void LauncherTooltipManager::ShowDelayed(views::View* anchor,
const 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 LauncherTooltipManager::ShowImmediately(views::View* anchor,
const 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 LauncherTooltipManager::Close() {
StopTimer();
if (view_) {
view_->Close();
view_ = NULL;
widget_ = NULL;
}
}
void LauncherTooltipManager::OnBubbleClosed(views::BubbleDelegateView* view) {
if (view == view_) {
view_ = NULL;
widget_ = NULL;
}
}
void LauncherTooltipManager::SetArrowLocation(ShelfAlignment alignment) {
if (alignment_ == alignment)
return;
alignment_ = alignment;
if (view_) {
CancelHidingAnimation();
Close();
ShowImmediately(anchor_, text_);
}
}
void LauncherTooltipManager::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;
base::OneShotTimer<LauncherTooltipManager>* new_timer =
new base::OneShotTimer<LauncherTooltipManager>();
new_timer->Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kTooltipAppearanceDelay),
this,
&LauncherTooltipManager::ShowInternal);
timer_.reset(new_timer);
}
void LauncherTooltipManager::StopTimer() {
timer_.reset();
}
bool LauncherTooltipManager::IsVisible() {
if (timer_.get() && timer_->IsRunning())
return false;
return widget_ && widget_->IsVisible();
}
bool LauncherTooltipManager::PreHandleKeyEvent(aura::Window* target,
ui::KeyEvent* event) {
// Not handled.
return false;
}
bool LauncherTooltipManager::PreHandleMouseEvent(aura::Window* target,
ui::MouseEvent* event) {
DCHECK(target);
DCHECK(event);
if (!widget_ || !widget_->IsVisible())
return false;
DCHECK(view_);
DCHECK(launcher_view_);
if (widget_->GetNativeWindow()->GetRootWindow() != target->GetRootWindow()) {
CloseSoon();
return false;
}
gfx::Point location_in_launcher_view = event->location();
aura::Window::ConvertPointToTarget(
target, launcher_view_->GetWidget()->GetNativeWindow(),
&location_in_launcher_view);
gfx::Point location_on_screen = event->location();
aura::Window::ConvertPointToTarget(
target, target->GetRootWindow(), &location_on_screen);
gfx::Rect bubble_rect = widget_->GetWindowBoundsInScreen();
if (launcher_view_->ShouldHideTooltip(location_in_launcher_view) &&
!bubble_rect.Contains(location_on_screen)) {
// Because this mouse event may arrive to |view_|, here we just schedule
// the closing event rather than directly calling Close().
CloseSoon();
}
return false;
}
ui::TouchStatus LauncherTooltipManager::PreHandleTouchEvent(
aura::Window* target, ui::TouchEvent* event) {
if (widget_ && widget_->IsVisible() && widget_->GetNativeWindow() != target)
Close();
return ui::TOUCH_STATUS_UNKNOWN;
}
ui::EventResult LauncherTooltipManager::PreHandleGestureEvent(
aura::Window* target, 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();
}
return ui::ER_UNHANDLED;
}
void LauncherTooltipManager::WillDeleteShelf() {
shelf_layout_manager_ = NULL;
}
void LauncherTooltipManager::WillChangeVisibilityState(
ShelfLayoutManager::VisibilityState new_state) {
if (new_state == ShelfLayoutManager::HIDDEN) {
StopTimer();
Close();
}
}
void LauncherTooltipManager::OnAutoHideStateChanged(
ShelfLayoutManager::AutoHideState new_state) {
if (new_state == ShelfLayoutManager::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 LauncherTooltipManager::CancelHidingAnimation() {
if (!widget_ || !widget_->GetNativeView())
return;
gfx::NativeView native_view = widget_->GetNativeView();
SetWindowVisibilityAnimationTransition(native_view, ANIMATE_NONE);
}
void LauncherTooltipManager::CloseSoon() {
MessageLoopForUI::current()->PostTask(
FROM_HERE,
base::Bind(&LauncherTooltipManager::Close, base::Unretained(this)));
}
void LauncherTooltipManager::ShowInternal() {
if (view_)
view_->Show();
timer_.reset();
}
void LauncherTooltipManager::CreateBubble(views::View* anchor,
const string16& text) {
DCHECK(!view_);
anchor_ = anchor;
text_ = text;
view_ = new LauncherTooltipBubble(
anchor, GetArrowLocation(alignment_), this);
widget_ = view_->GetWidget();
view_->SetText(text_);
gfx::NativeView native_view = widget_->GetNativeView();
SetWindowVisibilityAnimationType(
native_view, WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL);
SetWindowVisibilityAnimationTransition(native_view, ANIMATE_HIDE);
}
} // namespace internal
} // namespace ash