Home | History | Annotate | Download | only in corewm
      1 // Copyright 2013 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_aura.h"
      6 
      7 #include "base/strings/string_split.h"
      8 #include "ui/aura/window.h"
      9 #include "ui/aura/window_tree_host.h"
     10 #include "ui/gfx/screen.h"
     11 #include "ui/gfx/text_elider.h"
     12 #include "ui/gfx/text_utils.h"
     13 #include "ui/native_theme/native_theme.h"
     14 #include "ui/views/background.h"
     15 #include "ui/views/border.h"
     16 #include "ui/views/widget/widget.h"
     17 
     18 namespace {
     19 
     20 // Max visual tooltip width. If a tooltip is greater than this width, it will
     21 // be wrapped.
     22 const int kTooltipMaxWidthPixels = 400;
     23 
     24 const size_t kMaxLines = 10;
     25 
     26 // FIXME: get cursor offset from actual cursor size.
     27 const int kCursorOffsetX = 10;
     28 const int kCursorOffsetY = 15;
     29 
     30 // Creates a widget of type TYPE_TOOLTIP
     31 views::Widget* CreateTooltipWidget(aura::Window* tooltip_window) {
     32   views::Widget* widget = new views::Widget;
     33   views::Widget::InitParams params;
     34   // For aura, since we set the type to TYPE_TOOLTIP, the widget will get
     35   // auto-parented to the right container.
     36   params.type = views::Widget::InitParams::TYPE_TOOLTIP;
     37   params.context = tooltip_window;
     38   DCHECK(params.context);
     39   params.keep_on_top = true;
     40   params.accept_events = false;
     41   widget->Init(params);
     42   return widget;
     43 }
     44 
     45 }  // namespace
     46 
     47 namespace views {
     48 namespace corewm {
     49 
     50 TooltipAura::TooltipAura(gfx::ScreenType screen_type)
     51     : screen_type_(screen_type),
     52       widget_(NULL),
     53       tooltip_window_(NULL) {
     54   label_.set_owned_by_client();
     55   label_.SetMultiLine(true);
     56 
     57   const int kHorizontalPadding = 3;
     58   const int kVerticalPadding = 2;
     59   label_.SetBorder(Border::CreateEmptyBorder(
     60       kVerticalPadding, kHorizontalPadding,
     61       kVerticalPadding, kHorizontalPadding));
     62 }
     63 
     64 TooltipAura::~TooltipAura() {
     65   DestroyWidget();
     66 }
     67 
     68 // static
     69 void TooltipAura::TrimTooltipToFit(const gfx::FontList& font_list,
     70                                    int max_width,
     71                                    base::string16* text,
     72                                    int* width,
     73                                    int* line_count) {
     74   *width = 0;
     75   *line_count = 0;
     76 
     77   // Determine the available width for the tooltip.
     78   int available_width = std::min(kTooltipMaxWidthPixels, max_width);
     79 
     80   std::vector<base::string16> lines;
     81   base::SplitString(*text, '\n', &lines);
     82   std::vector<base::string16> result_lines;
     83 
     84   // Format each line to fit.
     85   for (std::vector<base::string16>::iterator l = lines.begin();
     86        l != lines.end(); ++l) {
     87     // We break the line at word boundaries, then stuff as many words as we can
     88     // in the available width to the current line, and move the remaining words
     89     // to a new line.
     90     std::vector<base::string16> words;
     91     base::SplitStringDontTrim(*l, ' ', &words);
     92     int current_width = 0;
     93     base::string16 line;
     94     for (std::vector<base::string16>::iterator w = words.begin();
     95          w != words.end(); ++w) {
     96       base::string16 word = *w;
     97       if (w + 1 != words.end())
     98         word.push_back(' ');
     99       int word_width = gfx::GetStringWidth(word, font_list);
    100       if (current_width + word_width > available_width) {
    101         // Current width will exceed the available width. Must start a new line.
    102         if (!line.empty())
    103           result_lines.push_back(line);
    104         current_width = 0;
    105         line.clear();
    106       }
    107       current_width += word_width;
    108       line.append(word);
    109     }
    110     result_lines.push_back(line);
    111   }
    112 
    113   // Clamp number of lines to |kMaxLines|.
    114   if (result_lines.size() > kMaxLines) {
    115     result_lines.resize(kMaxLines);
    116     // Add ellipses character to last line.
    117     result_lines[kMaxLines - 1] = gfx::TruncateString(
    118         result_lines.back(), result_lines.back().length() - 1, gfx::WORD_BREAK);
    119   }
    120   *line_count = result_lines.size();
    121 
    122   // Flatten the result.
    123   base::string16 result;
    124   for (std::vector<base::string16>::iterator l = result_lines.begin();
    125       l != result_lines.end(); ++l) {
    126     if (!result.empty())
    127       result.push_back('\n');
    128     int line_width = gfx::GetStringWidth(*l, font_list);
    129     // Since we only break at word boundaries, it could happen that due to some
    130     // very long word, line_width is greater than the available_width. In such
    131     // case, we simply truncate at available_width and add ellipses at the end.
    132     if (line_width > available_width) {
    133       *width = available_width;
    134       result.append(gfx::ElideText(*l, font_list, available_width,
    135                                    gfx::ELIDE_TAIL));
    136     } else {
    137       *width = std::max(*width, line_width);
    138       result.append(*l);
    139     }
    140   }
    141   *text = result;
    142 }
    143 
    144 int TooltipAura::GetMaxWidth(const gfx::Point& location) const {
    145   // TODO(varunjain): implementation duplicated in tooltip_manager_aura. Figure
    146   // out a way to merge.
    147   gfx::Screen* screen = gfx::Screen::GetScreenByType(screen_type_);
    148   gfx::Rect display_bounds(screen->GetDisplayNearestPoint(location).bounds());
    149   return (display_bounds.width() + 1) / 2;
    150 }
    151 
    152 void TooltipAura::SetTooltipBounds(const gfx::Point& mouse_pos,
    153                                    const gfx::Size& tooltip_size) {
    154   gfx::Rect tooltip_rect(mouse_pos, tooltip_size);
    155   tooltip_rect.Offset(kCursorOffsetX, kCursorOffsetY);
    156   gfx::Screen* screen = gfx::Screen::GetScreenByType(screen_type_);
    157   gfx::Rect display_bounds(screen->GetDisplayNearestPoint(mouse_pos).bounds());
    158 
    159   // If tooltip is out of bounds on the x axis, we simply shift it
    160   // horizontally by the offset.
    161   if (tooltip_rect.right() > display_bounds.right()) {
    162     int h_offset = tooltip_rect.right() - display_bounds.right();
    163     tooltip_rect.Offset(-h_offset, 0);
    164   }
    165 
    166   // If tooltip is out of bounds on the y axis, we flip it to appear above the
    167   // mouse cursor instead of below.
    168   if (tooltip_rect.bottom() > display_bounds.bottom())
    169     tooltip_rect.set_y(mouse_pos.y() - tooltip_size.height());
    170 
    171   tooltip_rect.AdjustToFit(display_bounds);
    172   widget_->SetBounds(tooltip_rect);
    173 }
    174 
    175 void TooltipAura::DestroyWidget() {
    176   if (widget_) {
    177     widget_->RemoveObserver(this);
    178     widget_->Close();
    179     widget_ = NULL;
    180   }
    181 }
    182 
    183 void TooltipAura::SetText(aura::Window* window,
    184                           const base::string16& tooltip_text,
    185                           const gfx::Point& location) {
    186   tooltip_window_ = window;
    187   int max_width = 0;
    188   int line_count = 0;
    189   base::string16 trimmed_text(tooltip_text);
    190   TrimTooltipToFit(label_.font_list(), GetMaxWidth(location), &trimmed_text,
    191                    &max_width, &line_count);
    192   label_.SetText(trimmed_text);
    193 
    194   if (!widget_) {
    195     widget_ = CreateTooltipWidget(tooltip_window_);
    196     widget_->SetContentsView(&label_);
    197     widget_->AddObserver(this);
    198   }
    199 
    200   label_.SizeToFit(max_width + label_.GetInsets().width());
    201   SetTooltipBounds(location, label_.size());
    202 
    203   ui::NativeTheme* native_theme = widget_->GetNativeTheme();
    204   label_.set_background(
    205       views::Background::CreateSolidBackground(
    206           native_theme->GetSystemColor(
    207               ui::NativeTheme::kColorId_TooltipBackground)));
    208 
    209   label_.SetAutoColorReadabilityEnabled(false);
    210   label_.SetEnabledColor(native_theme->GetSystemColor(
    211       ui::NativeTheme::kColorId_TooltipText));
    212 }
    213 
    214 void TooltipAura::Show() {
    215   if (widget_) {
    216     widget_->Show();
    217     widget_->StackAtTop();
    218   }
    219 }
    220 
    221 void TooltipAura::Hide() {
    222   tooltip_window_ = NULL;
    223   if (widget_)
    224     widget_->Hide();
    225 }
    226 
    227 bool TooltipAura::IsVisible() {
    228   return widget_ && widget_->IsVisible();
    229 }
    230 
    231 void TooltipAura::OnWidgetDestroying(views::Widget* widget) {
    232   DCHECK_EQ(widget_, widget);
    233   widget_ = NULL;
    234   tooltip_window_ = NULL;
    235 }
    236 
    237 }  // namespace corewm
    238 }  // namespace views
    239