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