// 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* new_timer = new base::OneShotTimer(); 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