• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "ui/views/corewm/tooltip_controller.h"
6 
7 #include <vector>
8 
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"
23 
24 namespace views {
25 namespace corewm {
26 namespace {
27 
28 const int kTooltipTimeoutMs = 500;
29 const int kDefaultTooltipShownTimeoutMs = 10000;
30 
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.
IsValidTarget(aura::Window * event_target,aura::Window * target)34 bool IsValidTarget(aura::Window* event_target, aura::Window* target) {
35   if (!target || (event_target == target))
36     return true;
37 
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;
44 }
45 
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.
GetTooltipTarget(const ui::MouseEvent & event,gfx::Point * location)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;
63 
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;
79           }
80         }
81         return event_target;
82       }
83 
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;
99 
100       aura::Window::ConvertPointToTarget(screen_target, target, &target_loc);
101       *location = target_loc;
102       return screen_target;
103     }
104     default:
105       NOTREACHED();
106       break;
107   }
108   return NULL;
109 }
110 
111 }  // namespace
112 
113 ////////////////////////////////////////////////////////////////////////////////
114 // TooltipController public:
115 
TooltipController(scoped_ptr<Tooltip> tooltip)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);
125 }
126 
~TooltipController()127 TooltipController::~TooltipController() {
128   if (tooltip_window_)
129     tooltip_window_->RemoveObserver(this);
130 }
131 
UpdateTooltip(aura::Window * target)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();
136 
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;
147   }
148 
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);
157     }
158   }
159 }
160 
SetTooltipShownTimeout(aura::Window * target,int timeout_in_ms)161 void TooltipController::SetTooltipShownTimeout(aura::Window* target,
162                                                int timeout_in_ms) {
163   tooltip_shown_timeout_map_[target] = timeout_in_ms;
164 }
165 
SetTooltipsEnabled(bool enable)166 void TooltipController::SetTooltipsEnabled(bool enable) {
167   if (tooltips_enabled_ == enable)
168     return;
169   tooltips_enabled_ = enable;
170   UpdateTooltip(tooltip_window_);
171 }
172 
OnKeyEvent(ui::KeyEvent * event)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();
180   }
181 }
182 
OnMouseEvent(ui::MouseEvent * event)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_);
197       }
198       SetTooltipWindow(target);
199       if (tooltip_timer_.IsRunning())
200         tooltip_timer_.Reset();
201 
202       if (tooltip_->IsVisible())
203         UpdateIfRequired();
204       break;
205     }
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);
213       }
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;
223   }
224 }
225 
OnTouchEvent(ui::TouchEvent * event)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);
232 }
233 
OnCancelMode(ui::CancelModeEvent * event)234 void TooltipController::OnCancelMode(ui::CancelModeEvent* event) {
235   tooltip_->Hide();
236   SetTooltipWindow(NULL);
237 }
238 
OnWindowDestroyed(aura::Window * window)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;
244   }
245 }
246 
247 ////////////////////////////////////////////////////////////////////////////////
248 // TooltipController private:
249 
TooltipTimerFired()250 void TooltipController::TooltipTimerFired() {
251   UpdateIfRequired();
252 }
253 
TooltipShownTimerFired()254 void TooltipController::TooltipShownTimerFired() {
255   tooltip_->Hide();
256 
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();
261 }
262 
UpdateIfRequired()263 void TooltipController::UpdateIfRequired() {
264   if (!tooltips_enabled_ ||
265       aura::Env::GetInstance()->IsMouseButtonDown() ||
266       IsDragDropInProgress() || !IsCursorVisible()) {
267     tooltip_->Hide();
268     return;
269   }
270 
271   base::string16 tooltip_text;
272   if (tooltip_window_)
273     tooltip_text = aura::client::GetTooltipText(tooltip_window_);
274 
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;
282     }
283     tooltip_window_at_mouse_press_ = NULL;
284   }
285 
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;
292 
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);
320       }
321     }
322   }
323 }
324 
IsTooltipVisible()325 bool TooltipController::IsTooltipVisible() {
326   return tooltip_->IsVisible();
327 }
328 
IsDragDropInProgress()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();
335 }
336 
IsCursorVisible()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();
347 }
348 
GetTooltipShownTimeout()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;
355 }
356 
SetTooltipWindow(aura::Window * target)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);
365 }
366 
367 }  // namespace corewm
368 }  // namespace views
369