Home | History | Annotate | Download | only in corewm
      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.
     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_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 
    127 TooltipController::~TooltipController() {
    128   if (tooltip_window_)
    129     tooltip_window_->RemoveObserver(this);
    130 }
    131 
    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 
    161 void TooltipController::SetTooltipShownTimeout(aura::Window* target,
    162                                                int timeout_in_ms) {
    163   tooltip_shown_timeout_map_[target] = timeout_in_ms;
    164 }
    165 
    166 void TooltipController::SetTooltipsEnabled(bool enable) {
    167   if (tooltips_enabled_ == enable)
    168     return;
    169   tooltips_enabled_ = enable;
    170   UpdateTooltip(tooltip_window_);
    171 }
    172 
    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 
    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 
    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 
    234 void TooltipController::OnCancelMode(ui::CancelModeEvent* event) {
    235   tooltip_->Hide();
    236   SetTooltipWindow(NULL);
    237 }
    238 
    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 
    250 void TooltipController::TooltipTimerFired() {
    251   UpdateIfRequired();
    252 }
    253 
    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 
    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 
    325 bool TooltipController::IsTooltipVisible() {
    326   return tooltip_->IsVisible();
    327 }
    328 
    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 
    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 
    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 
    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