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 "ui/views/corewm/tooltip_controller.h" 6 7 #include <vector> 8 9 #include "base/strings/string_util.h" 10 #include "base/time/time.h" 11 #include "ui/aura/client/capture_client.h" 12 #include "ui/aura/client/cursor_client.h" 13 #include "ui/aura/client/drag_drop_client.h" 14 #include "ui/aura/client/screen_position_client.h" 15 #include "ui/aura/env.h" 16 #include "ui/aura/window.h" 17 #include "ui/events/event.h" 18 #include "ui/gfx/font.h" 19 #include "ui/gfx/rect.h" 20 #include "ui/gfx/screen.h" 21 #include "ui/views/corewm/tooltip.h" 22 #include "ui/views/widget/tooltip_manager.h" 23 24 namespace views { 25 namespace corewm { 26 namespace { 27 28 const int kTooltipTimeoutMs = 500; 29 const int kDefaultTooltipShownTimeoutMs = 10000; 30 31 // Returns true if |target| is a valid window to get the tooltip from. 32 // |event_target| is the original target from the event and |target| the window 33 // at the same location. 34 bool IsValidTarget(aura::Window* event_target, aura::Window* target) { 35 if (!target || (event_target == target)) 36 return true; 37 38 void* event_target_grouping_id = event_target->GetNativeWindowProperty( 39 TooltipManager::kGroupingPropertyKey); 40 void* target_grouping_id = target->GetNativeWindowProperty( 41 TooltipManager::kGroupingPropertyKey); 42 return event_target_grouping_id && 43 event_target_grouping_id == target_grouping_id; 44 } 45 46 // Returns the target (the Window tooltip text comes from) based on the event. 47 // If a Window other than event.target() is returned, |location| is adjusted 48 // to be in the coordinates of the returned Window. 49 aura::Window* GetTooltipTarget(const ui::MouseEvent& event, 50 gfx::Point* location) { 51 switch (event.type()) { 52 case ui::ET_MOUSE_CAPTURE_CHANGED: 53 // On windows we can get a capture changed without an exit. We need to 54 // reset state when this happens else the tooltip may incorrectly show. 55 return NULL; 56 case ui::ET_MOUSE_EXITED: 57 return NULL; 58 case ui::ET_MOUSE_MOVED: 59 case ui::ET_MOUSE_DRAGGED: { 60 aura::Window* event_target = static_cast<aura::Window*>(event.target()); 61 if (!event_target) 62 return NULL; 63 64 // If a window other than |event_target| has capture, ignore the event. 65 // This can happen when RootWindow creates events when showing/hiding, or 66 // the system generates an extra event. We have to check 67 // GetGlobalCaptureWindow() as Windows does not use a singleton 68 // CaptureClient. 69 if (!event_target->HasCapture()) { 70 aura::Window* root = event_target->GetRootWindow(); 71 if (root) { 72 aura::client::CaptureClient* capture_client = 73 aura::client::GetCaptureClient(root); 74 if (capture_client) { 75 aura::Window* capture_window = 76 capture_client->GetGlobalCaptureWindow(); 77 if (capture_window && event_target != capture_window) 78 return NULL; 79 } 80 } 81 return event_target; 82 } 83 84 // If |target| has capture all events go to it, even if the mouse is 85 // really over another window. Find the real window the mouse is over. 86 gfx::Point screen_loc(event.location()); 87 aura::client::GetScreenPositionClient(event_target->GetRootWindow())-> 88 ConvertPointToScreen(event_target, &screen_loc); 89 gfx::Screen* screen = gfx::Screen::GetScreenFor(event_target); 90 aura::Window* target = screen->GetWindowAtScreenPoint(screen_loc); 91 if (!target) 92 return NULL; 93 gfx::Point target_loc(screen_loc); 94 aura::client::GetScreenPositionClient(target->GetRootWindow())-> 95 ConvertPointFromScreen(target, &target_loc); 96 aura::Window* screen_target = target->GetEventHandlerForPoint(target_loc); 97 if (!IsValidTarget(event_target, screen_target)) 98 return NULL; 99 100 aura::Window::ConvertPointToTarget(screen_target, target, &target_loc); 101 *location = target_loc; 102 return screen_target; 103 } 104 default: 105 NOTREACHED(); 106 break; 107 } 108 return NULL; 109 } 110 111 } // namespace 112 113 //////////////////////////////////////////////////////////////////////////////// 114 // TooltipController public: 115 116 TooltipController::TooltipController(scoped_ptr<Tooltip> tooltip) 117 : tooltip_window_(NULL), 118 tooltip_window_at_mouse_press_(NULL), 119 tooltip_(tooltip.Pass()), 120 tooltips_enabled_(true) { 121 tooltip_timer_.Start(FROM_HERE, 122 base::TimeDelta::FromMilliseconds(kTooltipTimeoutMs), 123 this, &TooltipController::TooltipTimerFired); 124 } 125 126 TooltipController::~TooltipController() { 127 if (tooltip_window_) 128 tooltip_window_->RemoveObserver(this); 129 } 130 131 void TooltipController::UpdateTooltip(aura::Window* target) { 132 // If tooltip is visible, we may want to hide it. If it is not, we are ok. 133 if (tooltip_window_ == target && tooltip_->IsVisible()) 134 UpdateIfRequired(); 135 136 // If we had stopped the tooltip timer for some reason, we must restart it if 137 // there is a change in the tooltip. 138 if (!tooltip_timer_.IsRunning()) { 139 if (tooltip_window_ != target || (tooltip_window_ && 140 tooltip_text_ != aura::client::GetTooltipText(tooltip_window_))) { 141 tooltip_timer_.Start(FROM_HERE, 142 base::TimeDelta::FromMilliseconds(kTooltipTimeoutMs), 143 this, &TooltipController::TooltipTimerFired); 144 } 145 } 146 } 147 148 void TooltipController::SetTooltipShownTimeout(aura::Window* target, 149 int timeout_in_ms) { 150 tooltip_shown_timeout_map_[target] = timeout_in_ms; 151 } 152 153 void TooltipController::SetTooltipsEnabled(bool enable) { 154 if (tooltips_enabled_ == enable) 155 return; 156 tooltips_enabled_ = enable; 157 UpdateTooltip(tooltip_window_); 158 } 159 160 void TooltipController::OnKeyEvent(ui::KeyEvent* event) { 161 // On key press, we want to hide the tooltip and not show it until change. 162 // This is the same behavior as hiding tooltips on timeout. Hence, we can 163 // simply simulate a timeout. 164 if (tooltip_shown_timer_.IsRunning()) { 165 tooltip_shown_timer_.Stop(); 166 TooltipShownTimerFired(); 167 } 168 } 169 170 void TooltipController::OnMouseEvent(ui::MouseEvent* event) { 171 switch (event->type()) { 172 case ui::ET_MOUSE_CAPTURE_CHANGED: 173 case ui::ET_MOUSE_EXITED: 174 case ui::ET_MOUSE_MOVED: 175 case ui::ET_MOUSE_DRAGGED: { 176 curr_mouse_loc_ = event->location(); 177 aura::Window* target = GetTooltipTarget(*event, &curr_mouse_loc_); 178 if (tooltip_window_ != target) { 179 if (tooltip_window_) 180 tooltip_window_->RemoveObserver(this); 181 tooltip_window_ = target; 182 if (tooltip_window_) 183 tooltip_window_->AddObserver(this); 184 } 185 if (tooltip_timer_.IsRunning()) 186 tooltip_timer_.Reset(); 187 188 if (tooltip_->IsVisible()) 189 UpdateIfRequired(); 190 break; 191 } 192 case ui::ET_MOUSE_PRESSED: 193 if ((event->flags() & ui::EF_IS_NON_CLIENT) == 0) { 194 aura::Window* target = static_cast<aura::Window*>(event->target()); 195 // We don't get a release for non-client areas. 196 tooltip_window_at_mouse_press_ = target; 197 if (target) 198 tooltip_text_at_mouse_press_ = aura::client::GetTooltipText(target); 199 } 200 tooltip_->Hide(); 201 break; 202 case ui::ET_MOUSEWHEEL: 203 // Hide the tooltip for click, release, drag, wheel events. 204 if (tooltip_->IsVisible()) 205 tooltip_->Hide(); 206 break; 207 default: 208 break; 209 } 210 } 211 212 void TooltipController::OnTouchEvent(ui::TouchEvent* event) { 213 // TODO(varunjain): need to properly implement tooltips for 214 // touch events. 215 // Hide the tooltip for touch events. 216 tooltip_->Hide(); 217 if (tooltip_window_) 218 tooltip_window_->RemoveObserver(this); 219 tooltip_window_ = NULL; 220 } 221 222 void TooltipController::OnCancelMode(ui::CancelModeEvent* event) { 223 tooltip_->Hide(); 224 } 225 226 void TooltipController::OnWindowDestroyed(aura::Window* window) { 227 if (tooltip_window_ == window) { 228 tooltip_->Hide(); 229 tooltip_shown_timeout_map_.erase(tooltip_window_); 230 tooltip_window_ = NULL; 231 } 232 } 233 234 //////////////////////////////////////////////////////////////////////////////// 235 // TooltipController private: 236 237 void TooltipController::TooltipTimerFired() { 238 UpdateIfRequired(); 239 } 240 241 void TooltipController::TooltipShownTimerFired() { 242 tooltip_->Hide(); 243 244 // Since the user presumably no longer needs the tooltip, we also stop the 245 // tooltip timer so that tooltip does not pop back up. We will restart this 246 // timer if the tooltip changes (see UpdateTooltip()). 247 tooltip_timer_.Stop(); 248 } 249 250 void TooltipController::UpdateIfRequired() { 251 if (!tooltips_enabled_ || 252 aura::Env::GetInstance()->IsMouseButtonDown() || 253 IsDragDropInProgress() || !IsCursorVisible()) { 254 tooltip_->Hide(); 255 return; 256 } 257 258 string16 tooltip_text; 259 if (tooltip_window_) 260 tooltip_text = aura::client::GetTooltipText(tooltip_window_); 261 262 // If the user pressed a mouse button. We will hide the tooltip and not show 263 // it until there is a change in the tooltip. 264 if (tooltip_window_at_mouse_press_) { 265 if (tooltip_window_ == tooltip_window_at_mouse_press_ && 266 tooltip_text == tooltip_text_at_mouse_press_) { 267 tooltip_->Hide(); 268 return; 269 } 270 tooltip_window_at_mouse_press_ = NULL; 271 } 272 273 // We add the !tooltip_->IsVisible() below because when we come here from 274 // TooltipTimerFired(), the tooltip_text may not have changed but we still 275 // want to update the tooltip because the timer has fired. 276 // If we come here from UpdateTooltip(), we have already checked for tooltip 277 // visibility and this check below will have no effect. 278 if (tooltip_text_ != tooltip_text || !tooltip_->IsVisible()) { 279 tooltip_shown_timer_.Stop(); 280 tooltip_text_ = tooltip_text; 281 base::string16 trimmed_text(tooltip_text_); 282 views::TooltipManager::TrimTooltipText(&trimmed_text); 283 // If the string consists entirely of whitespace, then don't both showing it 284 // (an empty tooltip is useless). 285 base::string16 whitespace_removed_text; 286 TrimWhitespace(trimmed_text, TRIM_ALL, &whitespace_removed_text); 287 if (whitespace_removed_text.empty()) { 288 tooltip_->Hide(); 289 } else { 290 gfx::Point widget_loc = curr_mouse_loc_ + 291 tooltip_window_->GetBoundsInScreen().OffsetFromOrigin(); 292 tooltip_->SetText(tooltip_window_, trimmed_text, widget_loc); 293 tooltip_->Show(); 294 int timeout = GetTooltipShownTimeout(); 295 if (timeout > 0) { 296 tooltip_shown_timer_.Start(FROM_HERE, 297 base::TimeDelta::FromMilliseconds(timeout), 298 this, &TooltipController::TooltipShownTimerFired); 299 } 300 } 301 } 302 } 303 304 bool TooltipController::IsTooltipVisible() { 305 return tooltip_->IsVisible(); 306 } 307 308 bool TooltipController::IsDragDropInProgress() { 309 if (!tooltip_window_) 310 return false; 311 aura::client::DragDropClient* client = 312 aura::client::GetDragDropClient(tooltip_window_->GetRootWindow()); 313 return client && client->IsDragDropInProgress(); 314 } 315 316 bool TooltipController::IsCursorVisible() { 317 if (!tooltip_window_) 318 return false; 319 aura::Window* root = tooltip_window_->GetRootWindow(); 320 if (!root) 321 return false; 322 aura::client::CursorClient* cursor_client = 323 aura::client::GetCursorClient(root); 324 // |cursor_client| may be NULL in tests, treat NULL as always visible. 325 return !cursor_client || cursor_client->IsCursorVisible(); 326 } 327 328 int TooltipController::GetTooltipShownTimeout() { 329 std::map<aura::Window*, int>::const_iterator it = 330 tooltip_shown_timeout_map_.find(tooltip_window_); 331 if (it == tooltip_shown_timeout_map_.end()) 332 return kDefaultTooltipShownTimeoutMs; 333 return it->second; 334 } 335 336 } // namespace corewm 337 } // namespace views 338