| OLD | NEW |
| (Empty) |
| 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/screen_position_client.h" | |
| 14 #include "ui/aura/env.h" | |
| 15 #include "ui/aura/window.h" | |
| 16 #include "ui/events/event.h" | |
| 17 #include "ui/gfx/font.h" | |
| 18 #include "ui/gfx/rect.h" | |
| 19 #include "ui/gfx/screen.h" | |
| 20 #include "ui/views/corewm/tooltip.h" | |
| 21 #include "ui/views/widget/tooltip_manager.h" | |
| 22 #include "ui/wm/public/drag_drop_client.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_id_(NULL), | |
| 119 tooltip_window_at_mouse_press_(NULL), | |
| 120 tooltip_(tooltip.Pass()), | |
| 121 tooltips_enabled_(true) { | |
| 122 tooltip_timer_.Start(FROM_HERE, | |
| 123 base::TimeDelta::FromMilliseconds(kTooltipTimeoutMs), | |
| 124 this, &TooltipController::TooltipTimerFired); | |
| 125 } | |
| 126 | |
| 127 TooltipController::~TooltipController() { | |
| 128 if (tooltip_window_) | |
| 129 tooltip_window_->RemoveObserver(this); | |
| 130 } | |
| 131 | |
| 132 void TooltipController::UpdateTooltip(aura::Window* target) { | |
| 133 // If tooltip is visible, we may want to hide it. If it is not, we are ok. | |
| 134 if (tooltip_window_ == target && tooltip_->IsVisible()) | |
| 135 UpdateIfRequired(); | |
| 136 | |
| 137 // Reset |tooltip_window_at_mouse_press_| if the moving within the same window | |
| 138 // but over a region that has different tooltip text. By resetting | |
| 139 // |tooltip_window_at_mouse_press_| we ensure the next time the timer fires | |
| 140 // we'll requery for the tooltip text. | |
| 141 // This handles the case of clicking on a view, moving within the same window | |
| 142 // but over a different view, than back to the original. | |
| 143 if (tooltip_window_at_mouse_press_ && | |
| 144 target == tooltip_window_at_mouse_press_ && | |
| 145 aura::client::GetTooltipText(target) != tooltip_text_at_mouse_press_) { | |
| 146 tooltip_window_at_mouse_press_ = NULL; | |
| 147 } | |
| 148 | |
| 149 // If we had stopped the tooltip timer for some reason, we must restart it if | |
| 150 // there is a change in the tooltip. | |
| 151 if (!tooltip_timer_.IsRunning()) { | |
| 152 if (tooltip_window_ != target || (tooltip_window_ && | |
| 153 tooltip_text_ != aura::client::GetTooltipText(tooltip_window_))) { | |
| 154 tooltip_timer_.Start(FROM_HERE, | |
| 155 base::TimeDelta::FromMilliseconds(kTooltipTimeoutMs), | |
| 156 this, &TooltipController::TooltipTimerFired); | |
| 157 } | |
| 158 } | |
| 159 } | |
| 160 | |
| 161 void TooltipController::SetTooltipShownTimeout(aura::Window* target, | |
| 162 int timeout_in_ms) { | |
| 163 tooltip_shown_timeout_map_[target] = timeout_in_ms; | |
| 164 } | |
| 165 | |
| 166 void TooltipController::SetTooltipsEnabled(bool enable) { | |
| 167 if (tooltips_enabled_ == enable) | |
| 168 return; | |
| 169 tooltips_enabled_ = enable; | |
| 170 UpdateTooltip(tooltip_window_); | |
| 171 } | |
| 172 | |
| 173 void TooltipController::OnKeyEvent(ui::KeyEvent* event) { | |
| 174 // On key press, we want to hide the tooltip and not show it until change. | |
| 175 // This is the same behavior as hiding tooltips on timeout. Hence, we can | |
| 176 // simply simulate a timeout. | |
| 177 if (tooltip_shown_timer_.IsRunning()) { | |
| 178 tooltip_shown_timer_.Stop(); | |
| 179 TooltipShownTimerFired(); | |
| 180 } | |
| 181 } | |
| 182 | |
| 183 void TooltipController::OnMouseEvent(ui::MouseEvent* event) { | |
| 184 switch (event->type()) { | |
| 185 case ui::ET_MOUSE_CAPTURE_CHANGED: | |
| 186 case ui::ET_MOUSE_EXITED: | |
| 187 case ui::ET_MOUSE_MOVED: | |
| 188 case ui::ET_MOUSE_DRAGGED: { | |
| 189 curr_mouse_loc_ = event->location(); | |
| 190 aura::Window* target = NULL; | |
| 191 // Avoid a call to gfx::Screen::GetWindowAtScreenPoint() since it can be | |
| 192 // very expensive on X11 in cases when the tooltip is hidden anyway. | |
| 193 if (tooltips_enabled_ && | |
| 194 !aura::Env::GetInstance()->IsMouseButtonDown() && | |
| 195 !IsDragDropInProgress()) { | |
| 196 target = GetTooltipTarget(*event, &curr_mouse_loc_); | |
| 197 } | |
| 198 SetTooltipWindow(target); | |
| 199 if (tooltip_timer_.IsRunning()) | |
| 200 tooltip_timer_.Reset(); | |
| 201 | |
| 202 if (tooltip_->IsVisible()) | |
| 203 UpdateIfRequired(); | |
| 204 break; | |
| 205 } | |
| 206 case ui::ET_MOUSE_PRESSED: | |
| 207 if ((event->flags() & ui::EF_IS_NON_CLIENT) == 0) { | |
| 208 aura::Window* target = static_cast<aura::Window*>(event->target()); | |
| 209 // We don't get a release for non-client areas. | |
| 210 tooltip_window_at_mouse_press_ = target; | |
| 211 if (target) | |
| 212 tooltip_text_at_mouse_press_ = aura::client::GetTooltipText(target); | |
| 213 } | |
| 214 tooltip_->Hide(); | |
| 215 break; | |
| 216 case ui::ET_MOUSEWHEEL: | |
| 217 // Hide the tooltip for click, release, drag, wheel events. | |
| 218 if (tooltip_->IsVisible()) | |
| 219 tooltip_->Hide(); | |
| 220 break; | |
| 221 default: | |
| 222 break; | |
| 223 } | |
| 224 } | |
| 225 | |
| 226 void TooltipController::OnTouchEvent(ui::TouchEvent* event) { | |
| 227 // TODO(varunjain): need to properly implement tooltips for | |
| 228 // touch events. | |
| 229 // Hide the tooltip for touch events. | |
| 230 tooltip_->Hide(); | |
| 231 SetTooltipWindow(NULL); | |
| 232 } | |
| 233 | |
| 234 void TooltipController::OnCancelMode(ui::CancelModeEvent* event) { | |
| 235 tooltip_->Hide(); | |
| 236 SetTooltipWindow(NULL); | |
| 237 } | |
| 238 | |
| 239 void TooltipController::OnWindowDestroyed(aura::Window* window) { | |
| 240 if (tooltip_window_ == window) { | |
| 241 tooltip_->Hide(); | |
| 242 tooltip_shown_timeout_map_.erase(tooltip_window_); | |
| 243 tooltip_window_ = NULL; | |
| 244 } | |
| 245 } | |
| 246 | |
| 247 //////////////////////////////////////////////////////////////////////////////// | |
| 248 // TooltipController private: | |
| 249 | |
| 250 void TooltipController::TooltipTimerFired() { | |
| 251 UpdateIfRequired(); | |
| 252 } | |
| 253 | |
| 254 void TooltipController::TooltipShownTimerFired() { | |
| 255 tooltip_->Hide(); | |
| 256 | |
| 257 // Since the user presumably no longer needs the tooltip, we also stop the | |
| 258 // tooltip timer so that tooltip does not pop back up. We will restart this | |
| 259 // timer if the tooltip changes (see UpdateTooltip()). | |
| 260 tooltip_timer_.Stop(); | |
| 261 } | |
| 262 | |
| 263 void TooltipController::UpdateIfRequired() { | |
| 264 if (!tooltips_enabled_ || | |
| 265 aura::Env::GetInstance()->IsMouseButtonDown() || | |
| 266 IsDragDropInProgress() || !IsCursorVisible()) { | |
| 267 tooltip_->Hide(); | |
| 268 return; | |
| 269 } | |
| 270 | |
| 271 base::string16 tooltip_text; | |
| 272 if (tooltip_window_) | |
| 273 tooltip_text = aura::client::GetTooltipText(tooltip_window_); | |
| 274 | |
| 275 // If the user pressed a mouse button. We will hide the tooltip and not show | |
| 276 // it until there is a change in the tooltip. | |
| 277 if (tooltip_window_at_mouse_press_) { | |
| 278 if (tooltip_window_ == tooltip_window_at_mouse_press_ && | |
| 279 tooltip_text == tooltip_text_at_mouse_press_) { | |
| 280 tooltip_->Hide(); | |
| 281 return; | |
| 282 } | |
| 283 tooltip_window_at_mouse_press_ = NULL; | |
| 284 } | |
| 285 | |
| 286 // If the uniqueness indicator is different from the previously encountered | |
| 287 // one, we should force tooltip update | |
| 288 const void* tooltip_id = aura::client::GetTooltipId(tooltip_window_); | |
| 289 bool ids_differ = false; | |
| 290 ids_differ = tooltip_id_ != tooltip_id; | |
| 291 tooltip_id_ = tooltip_id; | |
| 292 | |
| 293 // We add the !tooltip_->IsVisible() below because when we come here from | |
| 294 // TooltipTimerFired(), the tooltip_text may not have changed but we still | |
| 295 // want to update the tooltip because the timer has fired. | |
| 296 // If we come here from UpdateTooltip(), we have already checked for tooltip | |
| 297 // visibility and this check below will have no effect. | |
| 298 if (tooltip_text_ != tooltip_text || !tooltip_->IsVisible() || ids_differ) { | |
| 299 tooltip_shown_timer_.Stop(); | |
| 300 tooltip_text_ = tooltip_text; | |
| 301 base::string16 trimmed_text(tooltip_text_); | |
| 302 views::TooltipManager::TrimTooltipText(&trimmed_text); | |
| 303 // If the string consists entirely of whitespace, then don't both showing it | |
| 304 // (an empty tooltip is useless). | |
| 305 base::string16 whitespace_removed_text; | |
| 306 base::TrimWhitespace(trimmed_text, base::TRIM_ALL, | |
| 307 &whitespace_removed_text); | |
| 308 if (whitespace_removed_text.empty()) { | |
| 309 tooltip_->Hide(); | |
| 310 } else { | |
| 311 gfx::Point widget_loc = curr_mouse_loc_ + | |
| 312 tooltip_window_->GetBoundsInScreen().OffsetFromOrigin(); | |
| 313 tooltip_->SetText(tooltip_window_, whitespace_removed_text, widget_loc); | |
| 314 tooltip_->Show(); | |
| 315 int timeout = GetTooltipShownTimeout(); | |
| 316 if (timeout > 0) { | |
| 317 tooltip_shown_timer_.Start(FROM_HERE, | |
| 318 base::TimeDelta::FromMilliseconds(timeout), | |
| 319 this, &TooltipController::TooltipShownTimerFired); | |
| 320 } | |
| 321 } | |
| 322 } | |
| 323 } | |
| 324 | |
| 325 bool TooltipController::IsTooltipVisible() { | |
| 326 return tooltip_->IsVisible(); | |
| 327 } | |
| 328 | |
| 329 bool TooltipController::IsDragDropInProgress() { | |
| 330 if (!tooltip_window_) | |
| 331 return false; | |
| 332 aura::client::DragDropClient* client = | |
| 333 aura::client::GetDragDropClient(tooltip_window_->GetRootWindow()); | |
| 334 return client && client->IsDragDropInProgress(); | |
| 335 } | |
| 336 | |
| 337 bool TooltipController::IsCursorVisible() { | |
| 338 if (!tooltip_window_) | |
| 339 return false; | |
| 340 aura::Window* root = tooltip_window_->GetRootWindow(); | |
| 341 if (!root) | |
| 342 return false; | |
| 343 aura::client::CursorClient* cursor_client = | |
| 344 aura::client::GetCursorClient(root); | |
| 345 // |cursor_client| may be NULL in tests, treat NULL as always visible. | |
| 346 return !cursor_client || cursor_client->IsCursorVisible(); | |
| 347 } | |
| 348 | |
| 349 int TooltipController::GetTooltipShownTimeout() { | |
| 350 std::map<aura::Window*, int>::const_iterator it = | |
| 351 tooltip_shown_timeout_map_.find(tooltip_window_); | |
| 352 if (it == tooltip_shown_timeout_map_.end()) | |
| 353 return kDefaultTooltipShownTimeoutMs; | |
| 354 return it->second; | |
| 355 } | |
| 356 | |
| 357 void TooltipController::SetTooltipWindow(aura::Window* target) { | |
| 358 if (tooltip_window_ == target) | |
| 359 return; | |
| 360 if (tooltip_window_) | |
| 361 tooltip_window_->RemoveObserver(this); | |
| 362 tooltip_window_ = target; | |
| 363 if (tooltip_window_) | |
| 364 tooltip_window_->AddObserver(this); | |
| 365 } | |
| 366 | |
| 367 } // namespace corewm | |
| 368 } // namespace views | |
| OLD | NEW |