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