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