Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / views / corewm / tooltip_controller.cc
blobbb28c54535c7a3ae057c31678d2e175e4667369b
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/geometry/rect.h"
19 #include "ui/gfx/screen.h"
20 #include "ui/gfx/text_elider.h"
21 #include "ui/views/corewm/tooltip.h"
22 #include "ui/views/widget/tooltip_manager.h"
23 #include "ui/wm/public/drag_drop_client.h"
25 namespace views {
26 namespace corewm {
27 namespace {
29 const int kTooltipTimeoutMs = 500;
30 const int kDefaultTooltipShownTimeoutMs = 10000;
31 const size_t kMaxTooltipLength = 2048;
33 // Returns true if |target| is a valid window to get the tooltip from.
34 // |event_target| is the original target from the event and |target| the window
35 // at the same location.
36 bool IsValidTarget(aura::Window* event_target, aura::Window* target) {
37 if (!target || (event_target == target))
38 return true;
40 void* event_target_grouping_id = event_target->GetNativeWindowProperty(
41 TooltipManager::kGroupingPropertyKey);
42 void* target_grouping_id = target->GetNativeWindowProperty(
43 TooltipManager::kGroupingPropertyKey);
44 return event_target_grouping_id &&
45 event_target_grouping_id == target_grouping_id;
48 // Returns the target (the Window tooltip text comes from) based on the event.
49 // If a Window other than event.target() is returned, |location| is adjusted
50 // to be in the coordinates of the returned Window.
51 aura::Window* GetTooltipTarget(const ui::MouseEvent& event,
52 gfx::Point* location) {
53 switch (event.type()) {
54 case ui::ET_MOUSE_CAPTURE_CHANGED:
55 // On windows we can get a capture changed without an exit. We need to
56 // reset state when this happens else the tooltip may incorrectly show.
57 return NULL;
58 case ui::ET_MOUSE_EXITED:
59 return NULL;
60 case ui::ET_MOUSE_MOVED:
61 case ui::ET_MOUSE_DRAGGED: {
62 aura::Window* event_target = static_cast<aura::Window*>(event.target());
63 if (!event_target)
64 return NULL;
66 // If a window other than |event_target| has capture, ignore the event.
67 // This can happen when RootWindow creates events when showing/hiding, or
68 // the system generates an extra event. We have to check
69 // GetGlobalCaptureWindow() as Windows does not use a singleton
70 // CaptureClient.
71 if (!event_target->HasCapture()) {
72 aura::Window* root = event_target->GetRootWindow();
73 if (root) {
74 aura::client::CaptureClient* capture_client =
75 aura::client::GetCaptureClient(root);
76 if (capture_client) {
77 aura::Window* capture_window =
78 capture_client->GetGlobalCaptureWindow();
79 if (capture_window && event_target != capture_window)
80 return NULL;
83 return event_target;
86 // If |target| has capture all events go to it, even if the mouse is
87 // really over another window. Find the real window the mouse is over.
88 gfx::Point screen_loc(event.location());
89 aura::client::GetScreenPositionClient(event_target->GetRootWindow())->
90 ConvertPointToScreen(event_target, &screen_loc);
91 gfx::Screen* screen = gfx::Screen::GetScreenFor(event_target);
92 aura::Window* target = screen->GetWindowAtScreenPoint(screen_loc);
93 if (!target)
94 return NULL;
95 gfx::Point target_loc(screen_loc);
96 aura::client::GetScreenPositionClient(target->GetRootWindow())->
97 ConvertPointFromScreen(target, &target_loc);
98 aura::Window* screen_target = target->GetEventHandlerForPoint(target_loc);
99 if (!IsValidTarget(event_target, screen_target))
100 return NULL;
102 aura::Window::ConvertPointToTarget(screen_target, target, &target_loc);
103 *location = target_loc;
104 return screen_target;
106 default:
107 NOTREACHED();
108 break;
110 return NULL;
113 } // namespace
115 ////////////////////////////////////////////////////////////////////////////////
116 // TooltipController public:
118 TooltipController::TooltipController(scoped_ptr<Tooltip> tooltip)
119 : tooltip_window_(NULL),
120 tooltip_id_(NULL),
121 tooltip_window_at_mouse_press_(NULL),
122 tooltip_(tooltip.Pass()),
123 tooltips_enabled_(true) {
124 tooltip_timer_.Start(FROM_HERE,
125 base::TimeDelta::FromMilliseconds(kTooltipTimeoutMs),
126 this, &TooltipController::TooltipTimerFired);
129 TooltipController::~TooltipController() {
130 if (tooltip_window_)
131 tooltip_window_->RemoveObserver(this);
134 int TooltipController::GetMaxWidth(const gfx::Point& location,
135 gfx::NativeView context) const {
136 return tooltip_->GetMaxWidth(location, context);
139 void TooltipController::UpdateTooltip(aura::Window* target) {
140 // If tooltip is visible, we may want to hide it. If it is not, we are ok.
141 if (tooltip_window_ == target && tooltip_->IsVisible())
142 UpdateIfRequired();
144 // Reset |tooltip_window_at_mouse_press_| if the moving within the same window
145 // but over a region that has different tooltip text. By resetting
146 // |tooltip_window_at_mouse_press_| we ensure the next time the timer fires
147 // we'll requery for the tooltip text.
148 // This handles the case of clicking on a view, moving within the same window
149 // but over a different view, than back to the original.
150 if (tooltip_window_at_mouse_press_ &&
151 target == tooltip_window_at_mouse_press_ &&
152 aura::client::GetTooltipText(target) != tooltip_text_at_mouse_press_) {
153 tooltip_window_at_mouse_press_ = NULL;
156 // If we had stopped the tooltip timer for some reason, we must restart it if
157 // there is a change in the tooltip.
158 if (!tooltip_timer_.IsRunning()) {
159 if (tooltip_window_ != target || (tooltip_window_ &&
160 tooltip_text_ != aura::client::GetTooltipText(tooltip_window_))) {
161 tooltip_timer_.Start(FROM_HERE,
162 base::TimeDelta::FromMilliseconds(kTooltipTimeoutMs),
163 this, &TooltipController::TooltipTimerFired);
168 void TooltipController::SetTooltipShownTimeout(aura::Window* target,
169 int timeout_in_ms) {
170 tooltip_shown_timeout_map_[target] = timeout_in_ms;
173 void TooltipController::SetTooltipsEnabled(bool enable) {
174 if (tooltips_enabled_ == enable)
175 return;
176 tooltips_enabled_ = enable;
177 UpdateTooltip(tooltip_window_);
180 void TooltipController::OnKeyEvent(ui::KeyEvent* event) {
181 // On key press, we want to hide the tooltip and not show it until change.
182 // This is the same behavior as hiding tooltips on timeout. Hence, we can
183 // simply simulate a timeout.
184 if (tooltip_shown_timer_.IsRunning()) {
185 tooltip_shown_timer_.Stop();
186 TooltipShownTimerFired();
190 void TooltipController::OnMouseEvent(ui::MouseEvent* event) {
191 switch (event->type()) {
192 case ui::ET_MOUSE_CAPTURE_CHANGED:
193 case ui::ET_MOUSE_EXITED:
194 case ui::ET_MOUSE_MOVED:
195 case ui::ET_MOUSE_DRAGGED: {
196 curr_mouse_loc_ = event->location();
197 aura::Window* target = NULL;
198 // Avoid a call to gfx::Screen::GetWindowAtScreenPoint() since it can be
199 // very expensive on X11 in cases when the tooltip is hidden anyway.
200 if (tooltips_enabled_ &&
201 !aura::Env::GetInstance()->IsMouseButtonDown() &&
202 !IsDragDropInProgress()) {
203 target = GetTooltipTarget(*event, &curr_mouse_loc_);
205 SetTooltipWindow(target);
206 if (tooltip_timer_.IsRunning())
207 tooltip_timer_.Reset();
209 if (tooltip_->IsVisible())
210 UpdateIfRequired();
211 break;
213 case ui::ET_MOUSE_PRESSED:
214 if ((event->flags() & ui::EF_IS_NON_CLIENT) == 0) {
215 aura::Window* target = static_cast<aura::Window*>(event->target());
216 // We don't get a release for non-client areas.
217 tooltip_window_at_mouse_press_ = target;
218 if (target)
219 tooltip_text_at_mouse_press_ = aura::client::GetTooltipText(target);
221 tooltip_->Hide();
222 break;
223 case ui::ET_MOUSEWHEEL:
224 // Hide the tooltip for click, release, drag, wheel events.
225 if (tooltip_->IsVisible())
226 tooltip_->Hide();
227 break;
228 default:
229 break;
233 void TooltipController::OnTouchEvent(ui::TouchEvent* event) {
234 // TODO(varunjain): need to properly implement tooltips for
235 // touch events.
236 // Hide the tooltip for touch events.
237 tooltip_->Hide();
238 SetTooltipWindow(NULL);
241 void TooltipController::OnCancelMode(ui::CancelModeEvent* event) {
242 tooltip_->Hide();
243 SetTooltipWindow(NULL);
246 void TooltipController::OnWindowDestroyed(aura::Window* window) {
247 if (tooltip_window_ == window) {
248 tooltip_->Hide();
249 tooltip_shown_timeout_map_.erase(tooltip_window_);
250 tooltip_window_ = NULL;
254 ////////////////////////////////////////////////////////////////////////////////
255 // TooltipController private:
257 void TooltipController::TooltipTimerFired() {
258 UpdateIfRequired();
261 void TooltipController::TooltipShownTimerFired() {
262 tooltip_->Hide();
264 // Since the user presumably no longer needs the tooltip, we also stop the
265 // tooltip timer so that tooltip does not pop back up. We will restart this
266 // timer if the tooltip changes (see UpdateTooltip()).
267 tooltip_timer_.Stop();
270 void TooltipController::UpdateIfRequired() {
271 if (!tooltips_enabled_ ||
272 aura::Env::GetInstance()->IsMouseButtonDown() ||
273 IsDragDropInProgress() || !IsCursorVisible()) {
274 tooltip_->Hide();
275 return;
278 base::string16 tooltip_text;
279 if (tooltip_window_)
280 tooltip_text = aura::client::GetTooltipText(tooltip_window_);
282 // If the user pressed a mouse button. We will hide the tooltip and not show
283 // it until there is a change in the tooltip.
284 if (tooltip_window_at_mouse_press_) {
285 if (tooltip_window_ == tooltip_window_at_mouse_press_ &&
286 tooltip_text == tooltip_text_at_mouse_press_) {
287 tooltip_->Hide();
288 return;
290 tooltip_window_at_mouse_press_ = NULL;
293 // If the uniqueness indicator is different from the previously encountered
294 // one, we should force tooltip update
295 const void* tooltip_id = aura::client::GetTooltipId(tooltip_window_);
296 bool ids_differ = false;
297 ids_differ = tooltip_id_ != tooltip_id;
298 tooltip_id_ = tooltip_id;
300 // We add the !tooltip_->IsVisible() below because when we come here from
301 // TooltipTimerFired(), the tooltip_text may not have changed but we still
302 // want to update the tooltip because the timer has fired.
303 // If we come here from UpdateTooltip(), we have already checked for tooltip
304 // visibility and this check below will have no effect.
305 if (tooltip_text_ != tooltip_text || !tooltip_->IsVisible() || ids_differ) {
306 tooltip_shown_timer_.Stop();
307 tooltip_text_ = tooltip_text;
308 base::string16 trimmed_text =
309 gfx::TruncateString(tooltip_text_, kMaxTooltipLength, gfx::WORD_BREAK);
310 // If the string consists entirely of whitespace, then don't both showing it
311 // (an empty tooltip is useless).
312 base::string16 whitespace_removed_text;
313 base::TrimWhitespace(trimmed_text, base::TRIM_ALL,
314 &whitespace_removed_text);
315 if (whitespace_removed_text.empty()) {
316 tooltip_->Hide();
317 } else {
318 gfx::Point widget_loc = curr_mouse_loc_ +
319 tooltip_window_->GetBoundsInScreen().OffsetFromOrigin();
320 tooltip_->SetText(tooltip_window_, whitespace_removed_text, widget_loc);
321 tooltip_->Show();
322 int timeout = GetTooltipShownTimeout();
323 if (timeout > 0) {
324 tooltip_shown_timer_.Start(FROM_HERE,
325 base::TimeDelta::FromMilliseconds(timeout),
326 this, &TooltipController::TooltipShownTimerFired);
332 bool TooltipController::IsTooltipVisible() {
333 return tooltip_->IsVisible();
336 bool TooltipController::IsDragDropInProgress() {
337 if (!tooltip_window_)
338 return false;
339 aura::client::DragDropClient* client =
340 aura::client::GetDragDropClient(tooltip_window_->GetRootWindow());
341 return client && client->IsDragDropInProgress();
344 bool TooltipController::IsCursorVisible() {
345 if (!tooltip_window_)
346 return false;
347 aura::Window* root = tooltip_window_->GetRootWindow();
348 if (!root)
349 return false;
350 aura::client::CursorClient* cursor_client =
351 aura::client::GetCursorClient(root);
352 // |cursor_client| may be NULL in tests, treat NULL as always visible.
353 return !cursor_client || cursor_client->IsCursorVisible();
356 int TooltipController::GetTooltipShownTimeout() {
357 std::map<aura::Window*, int>::const_iterator it =
358 tooltip_shown_timeout_map_.find(tooltip_window_);
359 if (it == tooltip_shown_timeout_map_.end())
360 return kDefaultTooltipShownTimeoutMs;
361 return it->second;
364 void TooltipController::SetTooltipWindow(aura::Window* target) {
365 if (tooltip_window_ == target)
366 return;
367 if (tooltip_window_)
368 tooltip_window_->RemoveObserver(this);
369 tooltip_window_ = target;
370 if (tooltip_window_)
371 tooltip_window_->AddObserver(this);
374 } // namespace corewm
375 } // namespace views