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"
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"
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
))
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.
56 case ui::ET_MOUSE_EXITED
:
58 case ui::ET_MOUSE_MOVED
:
59 case ui::ET_MOUSE_DRAGGED
: {
60 aura::Window
* event_target
= static_cast<aura::Window
*>(event
.target());
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
69 if (!event_target
->HasCapture()) {
70 aura::Window
* root
= event_target
->GetRootWindow();
72 aura::client::CaptureClient
* capture_client
=
73 aura::client::GetCaptureClient(root
);
75 aura::Window
* capture_window
=
76 capture_client
->GetGlobalCaptureWindow();
77 if (capture_window
&& event_target
!= capture_window
)
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
);
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
))
100 aura::Window::ConvertPointToTarget(screen_target
, target
, &target_loc
);
101 *location
= target_loc
;
102 return screen_target
;
113 ////////////////////////////////////////////////////////////////////////////////
114 // TooltipController public:
116 TooltipController::TooltipController(scoped_ptr
<Tooltip
> tooltip
)
117 : tooltip_window_(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() {
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())
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
,
163 tooltip_shown_timeout_map_
[target
] = timeout_in_ms
;
166 void TooltipController::SetTooltipsEnabled(bool enable
) {
167 if (tooltips_enabled_
== enable
)
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())
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
;
212 tooltip_text_at_mouse_press_
= aura::client::GetTooltipText(target
);
216 case ui::ET_MOUSEWHEEL
:
217 // Hide the tooltip for click, release, drag, wheel events.
218 if (tooltip_
->IsVisible())
226 void TooltipController::OnTouchEvent(ui::TouchEvent
* event
) {
227 // TODO(varunjain): need to properly implement tooltips for
229 // Hide the tooltip for touch events.
231 SetTooltipWindow(NULL
);
234 void TooltipController::OnCancelMode(ui::CancelModeEvent
* event
) {
236 SetTooltipWindow(NULL
);
239 void TooltipController::OnWindowDestroyed(aura::Window
* window
) {
240 if (tooltip_window_
== window
) {
242 tooltip_shown_timeout_map_
.erase(tooltip_window_
);
243 tooltip_window_
= NULL
;
247 ////////////////////////////////////////////////////////////////////////////////
248 // TooltipController private:
250 void TooltipController::TooltipTimerFired() {
254 void TooltipController::TooltipShownTimerFired() {
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()) {
271 base::string16 tooltip_text
;
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_
) {
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()) {
311 gfx::Point widget_loc
= curr_mouse_loc_
+
312 tooltip_window_
->GetBoundsInScreen().OffsetFromOrigin();
313 tooltip_
->SetText(tooltip_window_
, whitespace_removed_text
, widget_loc
);
315 int timeout
= GetTooltipShownTimeout();
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_
)
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_
)
340 aura::Window
* root
= tooltip_window_
->GetRootWindow();
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
;
357 void TooltipController::SetTooltipWindow(aura::Window
* target
) {
358 if (tooltip_window_
== target
)
361 tooltip_window_
->RemoveObserver(this);
362 tooltip_window_
= target
;
364 tooltip_window_
->AddObserver(this);
367 } // namespace corewm