Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / ui / views / corewm / tooltip_controller.cc
blobb1ffc1c7de8e61093d83bcc5c35bbdf21c07df74
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.
5 #include "ui/views/corewm/tooltip_controller.h"
7 #include <vector>
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"
24 namespace views {
25 namespace corewm {
26 namespace {
28 const int kTooltipTimeoutMs = 500;
29 const int kDefaultTooltipShownTimeoutMs = 10000;
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;
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;
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;
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;
81 return event_target;
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;
100 aura::Window::ConvertPointToTarget(screen_target, target, &target_loc);
101 *location = target_loc;
102 return screen_target;
104 default:
105 NOTREACHED();
106 break;
108 return NULL;
111 } // namespace
113 ////////////////////////////////////////////////////////////////////////////////
114 // TooltipController public:
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);
127 TooltipController::~TooltipController() {
128 if (tooltip_window_)
129 tooltip_window_->RemoveObserver(this);
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();
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;
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);
161 void TooltipController::SetTooltipShownTimeout(aura::Window* target,
162 int timeout_in_ms) {
163 tooltip_shown_timeout_map_[target] = timeout_in_ms;
166 void TooltipController::SetTooltipsEnabled(bool enable) {
167 if (tooltips_enabled_ == enable)
168 return;
169 tooltips_enabled_ = enable;
170 UpdateTooltip(tooltip_window_);
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();
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_);
198 SetTooltipWindow(target);
199 if (tooltip_timer_.IsRunning())
200 tooltip_timer_.Reset();
202 if (tooltip_->IsVisible())
203 UpdateIfRequired();
204 break;
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);
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;
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);
234 void TooltipController::OnCancelMode(ui::CancelModeEvent* event) {
235 tooltip_->Hide();
236 SetTooltipWindow(NULL);
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;
247 ////////////////////////////////////////////////////////////////////////////////
248 // TooltipController private:
250 void TooltipController::TooltipTimerFired() {
251 UpdateIfRequired();
254 void TooltipController::TooltipShownTimerFired() {
255 tooltip_->Hide();
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();
263 void TooltipController::UpdateIfRequired() {
264 if (!tooltips_enabled_ ||
265 aura::Env::GetInstance()->IsMouseButtonDown() ||
266 IsDragDropInProgress() || !IsCursorVisible()) {
267 tooltip_->Hide();
268 return;
271 base::string16 tooltip_text;
272 if (tooltip_window_)
273 tooltip_text = aura::client::GetTooltipText(tooltip_window_);
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;
283 tooltip_window_at_mouse_press_ = NULL;
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;
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);
325 bool TooltipController::IsTooltipVisible() {
326 return tooltip_->IsVisible();
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();
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();
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;
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);
367 } // namespace corewm
368 } // namespace views