// 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