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/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"
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
))
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.
58 case ui::ET_MOUSE_EXITED
:
60 case ui::ET_MOUSE_MOVED
:
61 case ui::ET_MOUSE_DRAGGED
: {
62 aura::Window
* event_target
= static_cast<aura::Window
*>(event
.target());
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
71 if (!event_target
->HasCapture()) {
72 aura::Window
* root
= event_target
->GetRootWindow();
74 aura::client::CaptureClient
* capture_client
=
75 aura::client::GetCaptureClient(root
);
77 aura::Window
* capture_window
=
78 capture_client
->GetGlobalCaptureWindow();
79 if (capture_window
&& event_target
!= capture_window
)
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
);
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
))
102 aura::Window::ConvertPointToTarget(screen_target
, target
, &target_loc
);
103 *location
= target_loc
;
104 return screen_target
;
115 ////////////////////////////////////////////////////////////////////////////////
116 // TooltipController public:
118 TooltipController::TooltipController(scoped_ptr
<Tooltip
> tooltip
)
119 : tooltip_window_(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() {
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())
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
,
170 tooltip_shown_timeout_map_
[target
] = timeout_in_ms
;
173 void TooltipController::SetTooltipsEnabled(bool enable
) {
174 if (tooltips_enabled_
== enable
)
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())
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
;
219 tooltip_text_at_mouse_press_
= aura::client::GetTooltipText(target
);
223 case ui::ET_MOUSEWHEEL
:
224 // Hide the tooltip for click, release, drag, wheel events.
225 if (tooltip_
->IsVisible())
233 void TooltipController::OnTouchEvent(ui::TouchEvent
* event
) {
234 // TODO(varunjain): need to properly implement tooltips for
236 // Hide the tooltip for touch events.
238 SetTooltipWindow(NULL
);
241 void TooltipController::OnCancelMode(ui::CancelModeEvent
* event
) {
243 SetTooltipWindow(NULL
);
246 void TooltipController::OnWindowDestroyed(aura::Window
* window
) {
247 if (tooltip_window_
== window
) {
249 tooltip_shown_timeout_map_
.erase(tooltip_window_
);
250 tooltip_window_
= NULL
;
254 ////////////////////////////////////////////////////////////////////////////////
255 // TooltipController private:
257 void TooltipController::TooltipTimerFired() {
261 void TooltipController::TooltipShownTimerFired() {
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()) {
278 base::string16 tooltip_text
;
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_
) {
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()) {
318 gfx::Point widget_loc
= curr_mouse_loc_
+
319 tooltip_window_
->GetBoundsInScreen().OffsetFromOrigin();
320 tooltip_
->SetText(tooltip_window_
, whitespace_removed_text
, widget_loc
);
322 int timeout
= GetTooltipShownTimeout();
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_
)
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_
)
347 aura::Window
* root
= tooltip_window_
->GetRootWindow();
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
;
364 void TooltipController::SetTooltipWindow(aura::Window
* target
) {
365 if (tooltip_window_
== target
)
368 tooltip_window_
->RemoveObserver(this);
369 tooltip_window_
= target
;
371 tooltip_window_
->AddObserver(this);
374 } // namespace corewm