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/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   // If we had stopped the tooltip timer for some reason, we must restart it if
    137   // there is a change in the tooltip.
    138   if (!tooltip_timer_.IsRunning()) {
    139     if (tooltip_window_ != target || (tooltip_window_ &&
    140         tooltip_text_ != aura::client::GetTooltipText(tooltip_window_))) {
    141       tooltip_timer_.Start(FROM_HERE,
    142           base::TimeDelta::FromMilliseconds(kTooltipTimeoutMs),
    143           this, &TooltipController::TooltipTimerFired);
    144     }
    145   }
    146 }
    147 
    148 void TooltipController::SetTooltipShownTimeout(aura::Window* target,
    149                                                int timeout_in_ms) {
    150   tooltip_shown_timeout_map_[target] = timeout_in_ms;
    151 }
    152 
    153 void TooltipController::SetTooltipsEnabled(bool enable) {
    154   if (tooltips_enabled_ == enable)
    155     return;
    156   tooltips_enabled_ = enable;
    157   UpdateTooltip(tooltip_window_);
    158 }
    159 
    160 void TooltipController::OnKeyEvent(ui::KeyEvent* event) {
    161   // On key press, we want to hide the tooltip and not show it until change.
    162   // This is the same behavior as hiding tooltips on timeout. Hence, we can
    163   // simply simulate a timeout.
    164   if (tooltip_shown_timer_.IsRunning()) {
    165     tooltip_shown_timer_.Stop();
    166     TooltipShownTimerFired();
    167   }
    168 }
    169 
    170 void TooltipController::OnMouseEvent(ui::MouseEvent* event) {
    171   switch (event->type()) {
    172     case ui::ET_MOUSE_CAPTURE_CHANGED:
    173     case ui::ET_MOUSE_EXITED:
    174     case ui::ET_MOUSE_MOVED:
    175     case ui::ET_MOUSE_DRAGGED: {
    176       curr_mouse_loc_ = event->location();
    177       aura::Window* target = GetTooltipTarget(*event, &curr_mouse_loc_);
    178       if (tooltip_window_ != target) {
    179         if (tooltip_window_)
    180           tooltip_window_->RemoveObserver(this);
    181         tooltip_window_ = target;
    182         if (tooltip_window_)
    183           tooltip_window_->AddObserver(this);
    184       }
    185       if (tooltip_timer_.IsRunning())
    186         tooltip_timer_.Reset();
    187 
    188       if (tooltip_->IsVisible())
    189         UpdateIfRequired();
    190       break;
    191     }
    192     case ui::ET_MOUSE_PRESSED:
    193       if ((event->flags() & ui::EF_IS_NON_CLIENT) == 0) {
    194         aura::Window* target = static_cast<aura::Window*>(event->target());
    195         // We don't get a release for non-client areas.
    196         tooltip_window_at_mouse_press_ = target;
    197         if (target)
    198           tooltip_text_at_mouse_press_ = aura::client::GetTooltipText(target);
    199       }
    200       tooltip_->Hide();
    201       break;
    202     case ui::ET_MOUSEWHEEL:
    203       // Hide the tooltip for click, release, drag, wheel events.
    204       if (tooltip_->IsVisible())
    205         tooltip_->Hide();
    206       break;
    207     default:
    208       break;
    209   }
    210 }
    211 
    212 void TooltipController::OnTouchEvent(ui::TouchEvent* event) {
    213   // TODO(varunjain): need to properly implement tooltips for
    214   // touch events.
    215   // Hide the tooltip for touch events.
    216   tooltip_->Hide();
    217   if (tooltip_window_)
    218     tooltip_window_->RemoveObserver(this);
    219   tooltip_window_ = NULL;
    220 }
    221 
    222 void TooltipController::OnCancelMode(ui::CancelModeEvent* event) {
    223   tooltip_->Hide();
    224 }
    225 
    226 void TooltipController::OnWindowDestroyed(aura::Window* window) {
    227   if (tooltip_window_ == window) {
    228     tooltip_->Hide();
    229     tooltip_shown_timeout_map_.erase(tooltip_window_);
    230     tooltip_window_ = NULL;
    231   }
    232 }
    233 
    234 ////////////////////////////////////////////////////////////////////////////////
    235 // TooltipController private:
    236 
    237 void TooltipController::TooltipTimerFired() {
    238   UpdateIfRequired();
    239 }
    240 
    241 void TooltipController::TooltipShownTimerFired() {
    242   tooltip_->Hide();
    243 
    244   // Since the user presumably no longer needs the tooltip, we also stop the
    245   // tooltip timer so that tooltip does not pop back up. We will restart this
    246   // timer if the tooltip changes (see UpdateTooltip()).
    247   tooltip_timer_.Stop();
    248 }
    249 
    250 void TooltipController::UpdateIfRequired() {
    251   if (!tooltips_enabled_ ||
    252       aura::Env::GetInstance()->IsMouseButtonDown() ||
    253       IsDragDropInProgress() || !IsCursorVisible()) {
    254     tooltip_->Hide();
    255     return;
    256   }
    257 
    258   string16 tooltip_text;
    259   if (tooltip_window_)
    260     tooltip_text = aura::client::GetTooltipText(tooltip_window_);
    261 
    262   // If the user pressed a mouse button. We will hide the tooltip and not show
    263   // it until there is a change in the tooltip.
    264   if (tooltip_window_at_mouse_press_) {
    265     if (tooltip_window_ == tooltip_window_at_mouse_press_ &&
    266         tooltip_text == tooltip_text_at_mouse_press_) {
    267       tooltip_->Hide();
    268       return;
    269     }
    270     tooltip_window_at_mouse_press_ = NULL;
    271   }
    272 
    273   // We add the !tooltip_->IsVisible() below because when we come here from
    274   // TooltipTimerFired(), the tooltip_text may not have changed but we still
    275   // want to update the tooltip because the timer has fired.
    276   // If we come here from UpdateTooltip(), we have already checked for tooltip
    277   // visibility and this check below will have no effect.
    278   if (tooltip_text_ != tooltip_text || !tooltip_->IsVisible()) {
    279     tooltip_shown_timer_.Stop();
    280     tooltip_text_ = tooltip_text;
    281     base::string16 trimmed_text(tooltip_text_);
    282     views::TooltipManager::TrimTooltipText(&trimmed_text);
    283     // If the string consists entirely of whitespace, then don't both showing it
    284     // (an empty tooltip is useless).
    285     base::string16 whitespace_removed_text;
    286     TrimWhitespace(trimmed_text, TRIM_ALL, &whitespace_removed_text);
    287     if (whitespace_removed_text.empty()) {
    288       tooltip_->Hide();
    289     } else {
    290       gfx::Point widget_loc = curr_mouse_loc_ +
    291           tooltip_window_->GetBoundsInScreen().OffsetFromOrigin();
    292       tooltip_->SetText(tooltip_window_, trimmed_text, widget_loc);
    293       tooltip_->Show();
    294       int timeout = GetTooltipShownTimeout();
    295       if (timeout > 0) {
    296         tooltip_shown_timer_.Start(FROM_HERE,
    297             base::TimeDelta::FromMilliseconds(timeout),
    298             this, &TooltipController::TooltipShownTimerFired);
    299       }
    300     }
    301   }
    302 }
    303 
    304 bool TooltipController::IsTooltipVisible() {
    305   return tooltip_->IsVisible();
    306 }
    307 
    308 bool TooltipController::IsDragDropInProgress() {
    309   if (!tooltip_window_)
    310     return false;
    311   aura::client::DragDropClient* client =
    312       aura::client::GetDragDropClient(tooltip_window_->GetRootWindow());
    313   return client && client->IsDragDropInProgress();
    314 }
    315 
    316 bool TooltipController::IsCursorVisible() {
    317   if (!tooltip_window_)
    318     return false;
    319   aura::Window* root = tooltip_window_->GetRootWindow();
    320   if (!root)
    321     return false;
    322   aura::client::CursorClient* cursor_client =
    323       aura::client::GetCursorClient(root);
    324   // |cursor_client| may be NULL in tests, treat NULL as always visible.
    325   return !cursor_client || cursor_client->IsCursorVisible();
    326 }
    327 
    328 int TooltipController::GetTooltipShownTimeout() {
    329   std::map<aura::Window*, int>::const_iterator it =
    330       tooltip_shown_timeout_map_.find(tooltip_window_);
    331   if (it == tooltip_shown_timeout_map_.end())
    332     return kDefaultTooltipShownTimeoutMs;
    333   return it->second;
    334 }
    335 
    336 }  // namespace corewm
    337 }  // namespace views
    338