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/drag_drop_client.h"
14 #include "ui/aura/client/screen_position_client.h"
15 #include "ui/aura/env.h"
16 #include "ui/aura/window.h"
17 #include "ui/events/event.h"
18 #include "ui/gfx/font.h"
19 #include "ui/gfx/rect.h"
20 #include "ui/gfx/screen.h"
21 #include "ui/views/corewm/tooltip.h"
22 #include "ui/views/widget/tooltip_manager.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),
118 tooltip_window_at_mouse_press_(NULL),
119 tooltip_(tooltip.Pass()),
120 tooltips_enabled_(true) {
121 tooltip_timer_.Start(FROM_HERE,
122 base::TimeDelta::FromMilliseconds(kTooltipTimeoutMs),
123 this, &TooltipController::TooltipTimerFired);
126 TooltipController::~TooltipController() {
128 tooltip_window_->RemoveObserver(this);
131 void TooltipController::UpdateTooltip(aura::Window* target) {
132 // If tooltip is visible, we may want to hide it. If it is not, we are ok.
133 if (tooltip_window_ == target && tooltip_->IsVisible())
136 // Reset |tooltip_window_at_mouse_press_| if the moving within the same window
137 // but over a region that has different tooltip text. By resetting
138 // |tooltip_window_at_mouse_press_| we ensure the next time the timer fires
139 // we'll requery for the tooltip text.
140 // This handles the case of clicking on a view, moving within the same window
141 // but over a different view, than back to the original.
142 if (tooltip_window_at_mouse_press_ &&
143 target == tooltip_window_at_mouse_press_ &&
144 aura::client::GetTooltipText(target) != tooltip_text_at_mouse_press_) {
145 tooltip_window_at_mouse_press_ = NULL;
148 // If we had stopped the tooltip timer for some reason, we must restart it if
149 // there is a change in the tooltip.
150 if (!tooltip_timer_.IsRunning()) {
151 if (tooltip_window_ != target || (tooltip_window_ &&
152 tooltip_text_ != aura::client::GetTooltipText(tooltip_window_))) {
153 tooltip_timer_.Start(FROM_HERE,
154 base::TimeDelta::FromMilliseconds(kTooltipTimeoutMs),
155 this, &TooltipController::TooltipTimerFired);
160 void TooltipController::SetTooltipShownTimeout(aura::Window* target,
162 tooltip_shown_timeout_map_[target] = timeout_in_ms;
165 void TooltipController::SetTooltipsEnabled(bool enable) {
166 if (tooltips_enabled_ == enable)
168 tooltips_enabled_ = enable;
169 UpdateTooltip(tooltip_window_);
172 void TooltipController::OnKeyEvent(ui::KeyEvent* event) {
173 // On key press, we want to hide the tooltip and not show it until change.
174 // This is the same behavior as hiding tooltips on timeout. Hence, we can
175 // simply simulate a timeout.
176 if (tooltip_shown_timer_.IsRunning()) {
177 tooltip_shown_timer_.Stop();
178 TooltipShownTimerFired();
182 void TooltipController::OnMouseEvent(ui::MouseEvent* event) {
183 switch (event->type()) {
184 case ui::ET_MOUSE_CAPTURE_CHANGED:
185 case ui::ET_MOUSE_EXITED:
186 case ui::ET_MOUSE_MOVED:
187 case ui::ET_MOUSE_DRAGGED: {
188 curr_mouse_loc_ = event->location();
189 aura::Window* target = GetTooltipTarget(*event, &curr_mouse_loc_);
190 SetTooltipWindow(target);
191 if (tooltip_timer_.IsRunning())
192 tooltip_timer_.Reset();
194 if (tooltip_->IsVisible())
198 case ui::ET_MOUSE_PRESSED:
199 if ((event->flags() & ui::EF_IS_NON_CLIENT) == 0) {
200 aura::Window* target = static_cast<aura::Window*>(event->target());
201 // We don't get a release for non-client areas.
202 tooltip_window_at_mouse_press_ = target;
204 tooltip_text_at_mouse_press_ = aura::client::GetTooltipText(target);
208 case ui::ET_MOUSEWHEEL:
209 // Hide the tooltip for click, release, drag, wheel events.
210 if (tooltip_->IsVisible())
218 void TooltipController::OnTouchEvent(ui::TouchEvent* event) {
219 // TODO(varunjain): need to properly implement tooltips for
221 // Hide the tooltip for touch events.
223 SetTooltipWindow(NULL);
226 void TooltipController::OnCancelMode(ui::CancelModeEvent* event) {
228 SetTooltipWindow(NULL);
231 void TooltipController::OnWindowDestroyed(aura::Window* window) {
232 if (tooltip_window_ == window) {
234 tooltip_shown_timeout_map_.erase(tooltip_window_);
235 tooltip_window_ = NULL;
239 ////////////////////////////////////////////////////////////////////////////////
240 // TooltipController private:
242 void TooltipController::TooltipTimerFired() {
246 void TooltipController::TooltipShownTimerFired() {
249 // Since the user presumably no longer needs the tooltip, we also stop the
250 // tooltip timer so that tooltip does not pop back up. We will restart this
251 // timer if the tooltip changes (see UpdateTooltip()).
252 tooltip_timer_.Stop();
255 void TooltipController::UpdateIfRequired() {
256 if (!tooltips_enabled_ ||
257 aura::Env::GetInstance()->IsMouseButtonDown() ||
258 IsDragDropInProgress() || !IsCursorVisible()) {
263 base::string16 tooltip_text;
265 tooltip_text = aura::client::GetTooltipText(tooltip_window_);
267 // If the user pressed a mouse button. We will hide the tooltip and not show
268 // it until there is a change in the tooltip.
269 if (tooltip_window_at_mouse_press_) {
270 if (tooltip_window_ == tooltip_window_at_mouse_press_ &&
271 tooltip_text == tooltip_text_at_mouse_press_) {
275 tooltip_window_at_mouse_press_ = NULL;
278 // We add the !tooltip_->IsVisible() below because when we come here from
279 // TooltipTimerFired(), the tooltip_text may not have changed but we still
280 // want to update the tooltip because the timer has fired.
281 // If we come here from UpdateTooltip(), we have already checked for tooltip
282 // visibility and this check below will have no effect.
283 if (tooltip_text_ != tooltip_text || !tooltip_->IsVisible()) {
284 tooltip_shown_timer_.Stop();
285 tooltip_text_ = tooltip_text;
286 base::string16 trimmed_text(tooltip_text_);
287 views::TooltipManager::TrimTooltipText(&trimmed_text);
288 // If the string consists entirely of whitespace, then don't both showing it
289 // (an empty tooltip is useless).
290 base::string16 whitespace_removed_text;
291 TrimWhitespace(trimmed_text, TRIM_ALL, &whitespace_removed_text);
292 if (whitespace_removed_text.empty()) {
295 gfx::Point widget_loc = curr_mouse_loc_ +
296 tooltip_window_->GetBoundsInScreen().OffsetFromOrigin();
297 tooltip_->SetText(tooltip_window_, whitespace_removed_text, widget_loc);
299 int timeout = GetTooltipShownTimeout();
301 tooltip_shown_timer_.Start(FROM_HERE,
302 base::TimeDelta::FromMilliseconds(timeout),
303 this, &TooltipController::TooltipShownTimerFired);
309 bool TooltipController::IsTooltipVisible() {
310 return tooltip_->IsVisible();
313 bool TooltipController::IsDragDropInProgress() {
314 if (!tooltip_window_)
316 aura::client::DragDropClient* client =
317 aura::client::GetDragDropClient(tooltip_window_->GetRootWindow());
318 return client && client->IsDragDropInProgress();
321 bool TooltipController::IsCursorVisible() {
322 if (!tooltip_window_)
324 aura::Window* root = tooltip_window_->GetRootWindow();
327 aura::client::CursorClient* cursor_client =
328 aura::client::GetCursorClient(root);
329 // |cursor_client| may be NULL in tests, treat NULL as always visible.
330 return !cursor_client || cursor_client->IsCursorVisible();
333 int TooltipController::GetTooltipShownTimeout() {
334 std::map<aura::Window*, int>::const_iterator it =
335 tooltip_shown_timeout_map_.find(tooltip_window_);
336 if (it == tooltip_shown_timeout_map_.end())
337 return kDefaultTooltipShownTimeoutMs;
341 void TooltipController::SetTooltipWindow(aura::Window* target) {
342 if (tooltip_window_ == target)
345 tooltip_window_->RemoveObserver(this);
346 tooltip_window_ = target;
348 tooltip_window_->AddObserver(this);
351 } // namespace corewm