shelf_tooltip_manager.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "ash/shelf/shelf_tooltip_manager.h" 6 7#include "ash/shelf/shelf_layout_manager.h" 8#include "ash/shelf/shelf_view.h" 9#include "ash/shell.h" 10#include "ash/shell_window_ids.h" 11#include "ash/wm/window_animations.h" 12#include "base/bind.h" 13#include "base/message_loop/message_loop.h" 14#include "base/time/time.h" 15#include "base/timer/timer.h" 16#include "ui/aura/root_window.h" 17#include "ui/aura/window.h" 18#include "ui/events/event.h" 19#include "ui/events/event_constants.h" 20#include "ui/gfx/insets.h" 21#include "ui/views/bubble/bubble_delegate.h" 22#include "ui/views/bubble/bubble_frame_view.h" 23#include "ui/views/controls/label.h" 24#include "ui/views/layout/fill_layout.h" 25#include "ui/views/widget/widget.h" 26 27namespace ash { 28namespace internal { 29namespace { 30const int kTooltipTopBottomMargin = 3; 31const int kTooltipLeftRightMargin = 10; 32const int kTooltipAppearanceDelay = 1000; // msec 33const int kTooltipMinHeight = 29 - 2 * kTooltipTopBottomMargin; 34const SkColor kTooltipTextColor = SkColorSetRGB(0x22, 0x22, 0x22); 35 36// The maximum width of the tooltip bubble. Borrowed the value from 37// ash/tooltip/tooltip_controller.cc 38const int kTooltipMaxWidth = 250; 39 40// The offset for the tooltip bubble - making sure that the bubble is flush 41// with the shelf. The offset includes the arrow size in pixels as well as 42// the activation bar and other spacing elements. 43const int kArrowOffsetLeftRight = 11; 44const int kArrowOffsetTopBottom = 7; 45 46} // namespace 47 48// The implementation of tooltip of the launcher. 49class ShelfTooltipManager::ShelfTooltipBubble 50 : public views::BubbleDelegateView { 51 public: 52 ShelfTooltipBubble(views::View* anchor, 53 views::BubbleBorder::Arrow arrow, 54 ShelfTooltipManager* host); 55 56 void SetText(const base::string16& text); 57 void Close(); 58 59 private: 60 // views::WidgetDelegate overrides: 61 virtual void WindowClosing() OVERRIDE; 62 63 // views::View overrides: 64 virtual gfx::Size GetPreferredSize() OVERRIDE; 65 66 ShelfTooltipManager* host_; 67 views::Label* label_; 68 69 DISALLOW_COPY_AND_ASSIGN(ShelfTooltipBubble); 70}; 71 72ShelfTooltipManager::ShelfTooltipBubble::ShelfTooltipBubble( 73 views::View* anchor, 74 views::BubbleBorder::Arrow arrow, 75 ShelfTooltipManager* host) 76 : views::BubbleDelegateView(anchor, arrow), host_(host) { 77 // Make sure that the bubble follows the animation of the shelf. 78 set_move_with_anchor(true); 79 gfx::Insets insets = gfx::Insets(kArrowOffsetTopBottom, 80 kArrowOffsetLeftRight, 81 kArrowOffsetTopBottom, 82 kArrowOffsetLeftRight); 83 // Shelf items can have an asymmetrical border for spacing reasons. 84 // Adjust anchor location for this. 85 if (anchor->border()) 86 insets += anchor->border()->GetInsets(); 87 88 set_anchor_view_insets(insets); 89 set_close_on_esc(false); 90 set_close_on_deactivate(false); 91 set_use_focusless(true); 92 set_accept_events(false); 93 set_margins(gfx::Insets(kTooltipTopBottomMargin, kTooltipLeftRightMargin, 94 kTooltipTopBottomMargin, kTooltipLeftRightMargin)); 95 set_shadow(views::BubbleBorder::SMALL_SHADOW); 96 SetLayoutManager(new views::FillLayout()); 97 // The anchor may not have the widget in tests. 98 if (anchor->GetWidget() && anchor->GetWidget()->GetNativeView()) { 99 aura::Window* root_window = 100 anchor->GetWidget()->GetNativeView()->GetRootWindow(); 101 set_parent_window(ash::Shell::GetInstance()->GetContainer( 102 root_window, ash::internal::kShellWindowId_SettingBubbleContainer)); 103 } 104 label_ = new views::Label; 105 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 106 label_->SetEnabledColor(kTooltipTextColor); 107 AddChildView(label_); 108 views::BubbleDelegateView::CreateBubble(this); 109} 110 111void ShelfTooltipManager::ShelfTooltipBubble::SetText( 112 const base::string16& text) { 113 label_->SetText(text); 114 SizeToContents(); 115} 116 117void ShelfTooltipManager::ShelfTooltipBubble::Close() { 118 if (GetWidget()) { 119 host_ = NULL; 120 GetWidget()->Close(); 121 } 122} 123 124void ShelfTooltipManager::ShelfTooltipBubble::WindowClosing() { 125 views::BubbleDelegateView::WindowClosing(); 126 if (host_) 127 host_->OnBubbleClosed(this); 128} 129 130gfx::Size ShelfTooltipManager::ShelfTooltipBubble::GetPreferredSize() { 131 gfx::Size pref_size = views::BubbleDelegateView::GetPreferredSize(); 132 if (pref_size.height() < kTooltipMinHeight) 133 pref_size.set_height(kTooltipMinHeight); 134 if (pref_size.width() > kTooltipMaxWidth) 135 pref_size.set_width(kTooltipMaxWidth); 136 return pref_size; 137} 138 139ShelfTooltipManager::ShelfTooltipManager( 140 ShelfLayoutManager* shelf_layout_manager, 141 ShelfView* shelf_view) 142 : view_(NULL), 143 widget_(NULL), 144 anchor_(NULL), 145 shelf_layout_manager_(shelf_layout_manager), 146 shelf_view_(shelf_view), 147 weak_factory_(this) { 148 if (shelf_layout_manager) 149 shelf_layout_manager->AddObserver(this); 150 if (Shell::HasInstance()) 151 Shell::GetInstance()->AddPreTargetHandler(this); 152} 153 154ShelfTooltipManager::~ShelfTooltipManager() { 155 CancelHidingAnimation(); 156 Close(); 157 if (shelf_layout_manager_) 158 shelf_layout_manager_->RemoveObserver(this); 159 if (Shell::HasInstance()) 160 Shell::GetInstance()->RemovePreTargetHandler(this); 161} 162 163void ShelfTooltipManager::ShowDelayed(views::View* anchor, 164 const base::string16& text) { 165 if (view_) { 166 if (timer_.get() && timer_->IsRunning()) { 167 return; 168 } else { 169 CancelHidingAnimation(); 170 Close(); 171 } 172 } 173 174 if (shelf_layout_manager_ && !shelf_layout_manager_->IsVisible()) 175 return; 176 177 CreateBubble(anchor, text); 178 ResetTimer(); 179} 180 181void ShelfTooltipManager::ShowImmediately(views::View* anchor, 182 const base::string16& text) { 183 if (view_) { 184 if (timer_.get() && timer_->IsRunning()) 185 StopTimer(); 186 CancelHidingAnimation(); 187 Close(); 188 } 189 190 if (shelf_layout_manager_ && !shelf_layout_manager_->IsVisible()) 191 return; 192 193 CreateBubble(anchor, text); 194 ShowInternal(); 195} 196 197void ShelfTooltipManager::Close() { 198 StopTimer(); 199 if (view_) { 200 view_->Close(); 201 view_ = NULL; 202 widget_ = NULL; 203 } 204} 205 206void ShelfTooltipManager::OnBubbleClosed(views::BubbleDelegateView* view) { 207 if (view == view_) { 208 view_ = NULL; 209 widget_ = NULL; 210 } 211} 212 213void ShelfTooltipManager::UpdateArrow() { 214 if (view_) { 215 CancelHidingAnimation(); 216 Close(); 217 ShowImmediately(anchor_, text_); 218 } 219} 220 221void ShelfTooltipManager::ResetTimer() { 222 if (timer_.get() && timer_->IsRunning()) { 223 timer_->Reset(); 224 return; 225 } 226 227 // We don't start the timer if the shelf isn't visible. 228 if (shelf_layout_manager_ && !shelf_layout_manager_->IsVisible()) 229 return; 230 231 CreateTimer(kTooltipAppearanceDelay); 232} 233 234void ShelfTooltipManager::StopTimer() { 235 timer_.reset(); 236} 237 238bool ShelfTooltipManager::IsVisible() { 239 if (timer_.get() && timer_->IsRunning()) 240 return false; 241 242 return widget_ && widget_->IsVisible(); 243} 244 245void ShelfTooltipManager::CreateZeroDelayTimerForTest() { 246 CreateTimer(0); 247} 248 249void ShelfTooltipManager::OnMouseEvent(ui::MouseEvent* event) { 250 DCHECK(event); 251 DCHECK(event->target()); 252 if (!widget_ || !widget_->IsVisible()) 253 return; 254 255 DCHECK(view_); 256 DCHECK(shelf_view_); 257 258 // Pressing the mouse button anywhere should close the tooltip. 259 if (event->type() == ui::ET_MOUSE_PRESSED) { 260 CloseSoon(); 261 return; 262 } 263 264 aura::Window* target = static_cast<aura::Window*>(event->target()); 265 if (widget_->GetNativeWindow()->GetRootWindow() != target->GetRootWindow()) { 266 CloseSoon(); 267 return; 268 } 269 270 gfx::Point location_in_shelf_view = event->location(); 271 aura::Window::ConvertPointToTarget( 272 target, shelf_view_->GetWidget()->GetNativeWindow(), 273 &location_in_shelf_view); 274 275 if (shelf_view_->ShouldHideTooltip(location_in_shelf_view)) { 276 // Because this mouse event may arrive to |view_|, here we just schedule 277 // the closing event rather than directly calling Close(). 278 CloseSoon(); 279 } 280} 281 282void ShelfTooltipManager::OnTouchEvent(ui::TouchEvent* event) { 283 aura::Window* target = static_cast<aura::Window*>(event->target()); 284 if (widget_ && widget_->IsVisible() && widget_->GetNativeWindow() != target) 285 Close(); 286} 287 288void ShelfTooltipManager::OnGestureEvent(ui::GestureEvent* event) { 289 if (widget_ && widget_->IsVisible()) { 290 // Because this mouse event may arrive to |view_|, here we just schedule 291 // the closing event rather than directly calling Close(). 292 CloseSoon(); 293 } 294} 295 296void ShelfTooltipManager::OnCancelMode(ui::CancelModeEvent* event) { 297 Close(); 298} 299 300void ShelfTooltipManager::WillDeleteShelf() { 301 shelf_layout_manager_ = NULL; 302} 303 304void ShelfTooltipManager::WillChangeVisibilityState( 305 ShelfVisibilityState new_state) { 306 if (new_state == SHELF_HIDDEN) { 307 StopTimer(); 308 Close(); 309 } 310} 311 312void ShelfTooltipManager::OnAutoHideStateChanged(ShelfAutoHideState new_state) { 313 if (new_state == SHELF_AUTO_HIDE_HIDDEN) { 314 StopTimer(); 315 // AutoHide state change happens during an event filter, so immediate close 316 // may cause a crash in the HandleMouseEvent() after the filter. So we just 317 // schedule the Close here. 318 CloseSoon(); 319 } 320} 321 322void ShelfTooltipManager::CancelHidingAnimation() { 323 if (!widget_ || !widget_->GetNativeView()) 324 return; 325 326 gfx::NativeView native_view = widget_->GetNativeView(); 327 views::corewm::SetWindowVisibilityAnimationTransition( 328 native_view, views::corewm::ANIMATE_NONE); 329} 330 331void ShelfTooltipManager::CloseSoon() { 332 base::MessageLoopForUI::current()->PostTask( 333 FROM_HERE, 334 base::Bind(&ShelfTooltipManager::Close, weak_factory_.GetWeakPtr())); 335} 336 337void ShelfTooltipManager::ShowInternal() { 338 if (view_) 339 view_->GetWidget()->Show(); 340 341 timer_.reset(); 342} 343 344void ShelfTooltipManager::CreateBubble(views::View* anchor, 345 const base::string16& text) { 346 DCHECK(!view_); 347 348 anchor_ = anchor; 349 text_ = text; 350 views::BubbleBorder::Arrow arrow = 351 shelf_layout_manager_->SelectValueForShelfAlignment( 352 views::BubbleBorder::BOTTOM_CENTER, 353 views::BubbleBorder::LEFT_CENTER, 354 views::BubbleBorder::RIGHT_CENTER, 355 views::BubbleBorder::TOP_CENTER); 356 357 view_ = new ShelfTooltipBubble(anchor, arrow, this); 358 widget_ = view_->GetWidget(); 359 view_->SetText(text_); 360 361 gfx::NativeView native_view = widget_->GetNativeView(); 362 views::corewm::SetWindowVisibilityAnimationType( 363 native_view, views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_VERTICAL); 364 views::corewm::SetWindowVisibilityAnimationTransition( 365 native_view, views::corewm::ANIMATE_HIDE); 366} 367 368void ShelfTooltipManager::CreateTimer(int delay_in_ms) { 369 base::OneShotTimer<ShelfTooltipManager>* new_timer = 370 new base::OneShotTimer<ShelfTooltipManager>(); 371 new_timer->Start(FROM_HERE, 372 base::TimeDelta::FromMilliseconds(delay_in_ms), 373 this, 374 &ShelfTooltipManager::ShowInternal); 375 timer_.reset(new_timer); 376} 377 378} // namespace internal 379} // namespace ash 380