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 27 namespace ash { 28 namespace internal { 29 namespace { 30 const int kTooltipTopBottomMargin = 3; 31 const int kTooltipLeftRightMargin = 10; 32 const int kTooltipAppearanceDelay = 1000; // msec 33 const int kTooltipMinHeight = 29 - 2 * kTooltipTopBottomMargin; 34 const 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 38 const 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. 43 const int kArrowOffsetLeftRight = 11; 44 const int kArrowOffsetTopBottom = 7; 45 46 } // namespace 47 48 // The implementation of tooltip of the launcher. 49 class 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 72 ShelfTooltipManager::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 111 void ShelfTooltipManager::ShelfTooltipBubble::SetText( 112 const base::string16& text) { 113 label_->SetText(text); 114 SizeToContents(); 115 } 116 117 void ShelfTooltipManager::ShelfTooltipBubble::Close() { 118 if (GetWidget()) { 119 host_ = NULL; 120 GetWidget()->Close(); 121 } 122 } 123 124 void ShelfTooltipManager::ShelfTooltipBubble::WindowClosing() { 125 views::BubbleDelegateView::WindowClosing(); 126 if (host_) 127 host_->OnBubbleClosed(this); 128 } 129 130 gfx::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 139 ShelfTooltipManager::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 154 ShelfTooltipManager::~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 163 void 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 181 void 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 197 void ShelfTooltipManager::Close() { 198 StopTimer(); 199 if (view_) { 200 view_->Close(); 201 view_ = NULL; 202 widget_ = NULL; 203 } 204 } 205 206 void ShelfTooltipManager::OnBubbleClosed(views::BubbleDelegateView* view) { 207 if (view == view_) { 208 view_ = NULL; 209 widget_ = NULL; 210 } 211 } 212 213 void ShelfTooltipManager::UpdateArrow() { 214 if (view_) { 215 CancelHidingAnimation(); 216 Close(); 217 ShowImmediately(anchor_, text_); 218 } 219 } 220 221 void 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 234 void ShelfTooltipManager::StopTimer() { 235 timer_.reset(); 236 } 237 238 bool ShelfTooltipManager::IsVisible() { 239 if (timer_.get() && timer_->IsRunning()) 240 return false; 241 242 return widget_ && widget_->IsVisible(); 243 } 244 245 void ShelfTooltipManager::CreateZeroDelayTimerForTest() { 246 CreateTimer(0); 247 } 248 249 void 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 282 void 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 288 void 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 296 void ShelfTooltipManager::OnCancelMode(ui::CancelModeEvent* event) { 297 Close(); 298 } 299 300 void ShelfTooltipManager::WillDeleteShelf() { 301 shelf_layout_manager_ = NULL; 302 } 303 304 void ShelfTooltipManager::WillChangeVisibilityState( 305 ShelfVisibilityState new_state) { 306 if (new_state == SHELF_HIDDEN) { 307 StopTimer(); 308 Close(); 309 } 310 } 311 312 void 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 322 void 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 331 void ShelfTooltipManager::CloseSoon() { 332 base::MessageLoopForUI::current()->PostTask( 333 FROM_HERE, 334 base::Bind(&ShelfTooltipManager::Close, weak_factory_.GetWeakPtr())); 335 } 336 337 void ShelfTooltipManager::ShowInternal() { 338 if (view_) 339 view_->GetWidget()->Show(); 340 341 timer_.reset(); 342 } 343 344 void 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 368 void 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