Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / ui / views / corewm / tooltip_controller.cc
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/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"
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.
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.
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
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);
124 }
125
126 TooltipController::~TooltipController() {
127   if (tooltip_window_)
128     tooltip_window_->RemoveObserver(this);
129 }
130
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())
134     UpdateIfRequired();
135
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;
146   }
147
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);
156     }
157   }
158 }
159
160 void TooltipController::SetTooltipShownTimeout(aura::Window* target,
161                                                int timeout_in_ms) {
162   tooltip_shown_timeout_map_[target] = timeout_in_ms;
163 }
164
165 void TooltipController::SetTooltipsEnabled(bool enable) {
166   if (tooltips_enabled_ == enable)
167     return;
168   tooltips_enabled_ = enable;
169   UpdateTooltip(tooltip_window_);
170 }
171
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();
179   }
180 }
181
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();
193
194       if (tooltip_->IsVisible())
195         UpdateIfRequired();
196       break;
197     }
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;
203         if (target)
204           tooltip_text_at_mouse_press_ = aura::client::GetTooltipText(target);
205       }
206       tooltip_->Hide();
207       break;
208     case ui::ET_MOUSEWHEEL:
209       // Hide the tooltip for click, release, drag, wheel events.
210       if (tooltip_->IsVisible())
211         tooltip_->Hide();
212       break;
213     default:
214       break;
215   }
216 }
217
218 void TooltipController::OnTouchEvent(ui::TouchEvent* event) {
219   // TODO(varunjain): need to properly implement tooltips for
220   // touch events.
221   // Hide the tooltip for touch events.
222   tooltip_->Hide();
223   SetTooltipWindow(NULL);
224 }
225
226 void TooltipController::OnCancelMode(ui::CancelModeEvent* event) {
227   tooltip_->Hide();
228   SetTooltipWindow(NULL);
229 }
230
231 void TooltipController::OnWindowDestroyed(aura::Window* window) {
232   if (tooltip_window_ == window) {
233     tooltip_->Hide();
234     tooltip_shown_timeout_map_.erase(tooltip_window_);
235     tooltip_window_ = NULL;
236   }
237 }
238
239 ////////////////////////////////////////////////////////////////////////////////
240 // TooltipController private:
241
242 void TooltipController::TooltipTimerFired() {
243   UpdateIfRequired();
244 }
245
246 void TooltipController::TooltipShownTimerFired() {
247   tooltip_->Hide();
248
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();
253 }
254
255 void TooltipController::UpdateIfRequired() {
256   if (!tooltips_enabled_ ||
257       aura::Env::GetInstance()->IsMouseButtonDown() ||
258       IsDragDropInProgress() || !IsCursorVisible()) {
259     tooltip_->Hide();
260     return;
261   }
262
263   base::string16 tooltip_text;
264   if (tooltip_window_)
265     tooltip_text = aura::client::GetTooltipText(tooltip_window_);
266
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_) {
272       tooltip_->Hide();
273       return;
274     }
275     tooltip_window_at_mouse_press_ = NULL;
276   }
277
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()) {
293       tooltip_->Hide();
294     } else {
295       gfx::Point widget_loc = curr_mouse_loc_ +
296           tooltip_window_->GetBoundsInScreen().OffsetFromOrigin();
297       tooltip_->SetText(tooltip_window_, whitespace_removed_text, widget_loc);
298       tooltip_->Show();
299       int timeout = GetTooltipShownTimeout();
300       if (timeout > 0) {
301         tooltip_shown_timer_.Start(FROM_HERE,
302             base::TimeDelta::FromMilliseconds(timeout),
303             this, &TooltipController::TooltipShownTimerFired);
304       }
305     }
306   }
307 }
308
309 bool TooltipController::IsTooltipVisible() {
310   return tooltip_->IsVisible();
311 }
312
313 bool TooltipController::IsDragDropInProgress() {
314   if (!tooltip_window_)
315     return false;
316   aura::client::DragDropClient* client =
317       aura::client::GetDragDropClient(tooltip_window_->GetRootWindow());
318   return client && client->IsDragDropInProgress();
319 }
320
321 bool TooltipController::IsCursorVisible() {
322   if (!tooltip_window_)
323     return false;
324   aura::Window* root = tooltip_window_->GetRootWindow();
325   if (!root)
326     return false;
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();
331 }
332
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;
338   return it->second;
339 }
340
341 void TooltipController::SetTooltipWindow(aura::Window* target) {
342   if (tooltip_window_ == target)
343     return;
344   if (tooltip_window_)
345     tooltip_window_->RemoveObserver(this);
346   tooltip_window_ = target;
347   if (tooltip_window_)
348     tooltip_window_->AddObserver(this);
349 }
350
351 }  // namespace corewm
352 }  // namespace views