1 // Copyright (c) 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_controller.h" 6 7 #include "base/strings/utf_string_conversions.h" 8 #include "ui/aura/client/cursor_client.h" 9 #include "ui/aura/client/tooltip_client.h" 10 #include "ui/aura/env.h" 11 #include "ui/aura/root_window.h" 12 #include "ui/aura/test/aura_test_base.h" 13 #include "ui/aura/test/event_generator.h" 14 #include "ui/aura/window.h" 15 #include "ui/base/resource/resource_bundle.h" 16 #include "ui/base/text/text_elider.h" 17 #include "ui/gfx/font.h" 18 #include "ui/gfx/point.h" 19 #include "ui/views/corewm/tooltip_controller_test_helper.h" 20 #include "ui/views/view.h" 21 #include "ui/views/widget/widget.h" 22 23 #if defined(OS_WIN) 24 #include "ui/base/win/scoped_ole_initializer.h" 25 #endif 26 #if !defined(OS_CHROMEOS) 27 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" 28 #endif 29 30 namespace views { 31 namespace corewm { 32 namespace test { 33 namespace { 34 35 views::Widget* CreateWidget(aura::RootWindow* root) { 36 views::Widget* widget = new views::Widget; 37 views::Widget::InitParams params; 38 params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS; 39 params.accept_events = true; 40 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; 41 #if defined(OS_CHROMEOS) 42 params.parent = root; 43 #else 44 params.native_widget = new DesktopNativeWidgetAura(widget); 45 #endif 46 params.bounds = gfx::Rect(0, 0, 200, 100); 47 widget->Init(params); 48 widget->Show(); 49 return widget; 50 } 51 52 gfx::Font GetDefaultFont() { 53 return ui::ResourceBundle::GetSharedInstance().GetFont( 54 ui::ResourceBundle::BaseFont); 55 } 56 57 TooltipController* GetController(Widget* widget) { 58 return static_cast<TooltipController*>( 59 aura::client::GetTooltipClient( 60 widget->GetNativeWindow()->GetRootWindow())); 61 } 62 63 } // namespace 64 65 class TooltipControllerTest : public aura::test::AuraTestBase { 66 public: 67 TooltipControllerTest() : view_(NULL) {} 68 virtual ~TooltipControllerTest() {} 69 70 virtual void SetUp() OVERRIDE { 71 aura::test::AuraTestBase::SetUp(); 72 #if defined(OS_CHROMEOS) 73 controller_.reset(new TooltipController(gfx::SCREEN_TYPE_ALTERNATE)); 74 root_window()->AddPreTargetHandler(controller_.get()); 75 SetTooltipClient(root_window(), controller_.get()); 76 #endif 77 widget_.reset(CreateWidget(root_window())); 78 widget_->SetContentsView(new View); 79 view_ = new TooltipTestView; 80 widget_->GetContentsView()->AddChildView(view_); 81 view_->SetBoundsRect(widget_->GetContentsView()->GetLocalBounds()); 82 helper_.reset(new TooltipControllerTestHelper( 83 GetController(widget_.get()))); 84 generator_.reset(new aura::test::EventGenerator(GetRootWindow())); 85 } 86 87 virtual void TearDown() OVERRIDE { 88 #if defined(OS_CHROMEOS) 89 root_window()->RemovePreTargetHandler(controller_.get()); 90 SetTooltipClient(root_window(), NULL); 91 controller_.reset(); 92 #endif 93 generator_.reset(); 94 helper_.reset(); 95 widget_.reset(); 96 aura::test::AuraTestBase::TearDown(); 97 } 98 99 protected: 100 aura::Window* GetWindow() { 101 return widget_->GetNativeWindow(); 102 } 103 104 aura::RootWindow* GetRootWindow() { 105 return GetWindow()->GetRootWindow(); 106 } 107 108 TooltipTestView* PrepareSecondView() { 109 TooltipTestView* view2 = new TooltipTestView; 110 widget_->GetContentsView()->AddChildView(view2); 111 view_->SetBounds(0, 0, 100, 100); 112 view2->SetBounds(100, 0, 100, 100); 113 return view2; 114 } 115 116 scoped_ptr<views::Widget> widget_; 117 TooltipTestView* view_; 118 scoped_ptr<TooltipControllerTestHelper> helper_; 119 scoped_ptr<aura::test::EventGenerator> generator_; 120 121 private: 122 scoped_ptr<TooltipController> controller_; 123 #if defined(OS_WIN) 124 ui::ScopedOleInitializer ole_initializer_; 125 #endif 126 127 DISALLOW_COPY_AND_ASSIGN(TooltipControllerTest); 128 }; 129 130 TEST_F(TooltipControllerTest, ViewTooltip) { 131 view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text")); 132 EXPECT_EQ(string16(), helper_->GetTooltipText()); 133 EXPECT_EQ(NULL, helper_->GetTooltipWindow()); 134 generator_->MoveMouseToCenterOf(GetWindow()); 135 136 EXPECT_EQ(GetWindow(), GetRootWindow()->GetEventHandlerForPoint( 137 generator_->current_location())); 138 string16 expected_tooltip = ASCIIToUTF16("Tooltip Text"); 139 EXPECT_EQ(expected_tooltip, aura::client::GetTooltipText(GetWindow())); 140 EXPECT_EQ(string16(), helper_->GetTooltipText()); 141 EXPECT_EQ(GetWindow(), helper_->GetTooltipWindow()); 142 143 // Fire tooltip timer so tooltip becomes visible. 144 helper_->FireTooltipTimer(); 145 146 EXPECT_TRUE(helper_->IsTooltipVisible()); 147 generator_->MoveMouseBy(1, 0); 148 149 EXPECT_TRUE(helper_->IsTooltipVisible()); 150 EXPECT_EQ(expected_tooltip, aura::client::GetTooltipText(GetWindow())); 151 EXPECT_EQ(expected_tooltip, helper_->GetTooltipText()); 152 EXPECT_EQ(GetWindow(), helper_->GetTooltipWindow()); 153 } 154 155 TEST_F(TooltipControllerTest, TooltipsInMultipleViews) { 156 view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text")); 157 EXPECT_EQ(string16(), helper_->GetTooltipText()); 158 EXPECT_EQ(NULL, helper_->GetTooltipWindow()); 159 160 PrepareSecondView(); 161 aura::Window* window = GetWindow(); 162 aura::RootWindow* root_window = GetRootWindow(); 163 164 // Fire tooltip timer so tooltip becomes visible. 165 generator_->MoveMouseRelativeTo(window, view_->bounds().CenterPoint()); 166 helper_->FireTooltipTimer(); 167 EXPECT_TRUE(helper_->IsTooltipVisible()); 168 for (int i = 0; i < 49; ++i) { 169 generator_->MoveMouseBy(1, 0); 170 EXPECT_TRUE(helper_->IsTooltipVisible()); 171 EXPECT_EQ(window, root_window->GetEventHandlerForPoint( 172 generator_->current_location())); 173 string16 expected_tooltip = ASCIIToUTF16("Tooltip Text"); 174 EXPECT_EQ(expected_tooltip, aura::client::GetTooltipText(window)); 175 EXPECT_EQ(expected_tooltip, helper_->GetTooltipText()); 176 EXPECT_EQ(window, helper_->GetTooltipWindow()); 177 } 178 for (int i = 0; i < 49; ++i) { 179 generator_->MoveMouseBy(1, 0); 180 EXPECT_FALSE(helper_->IsTooltipVisible()); 181 EXPECT_EQ(window, root_window->GetEventHandlerForPoint( 182 generator_->current_location())); 183 string16 expected_tooltip; // = "" 184 EXPECT_EQ(expected_tooltip, aura::client::GetTooltipText(window)); 185 EXPECT_EQ(expected_tooltip, helper_->GetTooltipText()); 186 EXPECT_EQ(window, helper_->GetTooltipWindow()); 187 } 188 } 189 190 TEST_F(TooltipControllerTest, EnableOrDisableTooltips) { 191 view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text")); 192 EXPECT_EQ(string16(), helper_->GetTooltipText()); 193 EXPECT_EQ(NULL, helper_->GetTooltipWindow()); 194 195 generator_->MoveMouseRelativeTo(GetWindow(), view_->bounds().CenterPoint()); 196 string16 expected_tooltip = ASCIIToUTF16("Tooltip Text"); 197 198 // Fire tooltip timer so tooltip becomes visible. 199 helper_->FireTooltipTimer(); 200 EXPECT_TRUE(helper_->IsTooltipVisible()); 201 202 // Diable tooltips and check again. 203 helper_->controller()->SetTooltipsEnabled(false); 204 EXPECT_FALSE(helper_->IsTooltipVisible()); 205 helper_->FireTooltipTimer(); 206 EXPECT_FALSE(helper_->IsTooltipVisible()); 207 208 // Enable tooltips back and check again. 209 helper_->controller()->SetTooltipsEnabled(true); 210 EXPECT_FALSE(helper_->IsTooltipVisible()); 211 helper_->FireTooltipTimer(); 212 EXPECT_TRUE(helper_->IsTooltipVisible()); 213 } 214 215 TEST_F(TooltipControllerTest, TrimTooltipToFitTests) { 216 const int max_width = 4000; 217 string16 tooltip; 218 int width, line_count, expect_lines; 219 int max_pixel_width = 400; // copied from constants in tooltip_controller.cc 220 int max_lines = 10; // copied from constants in tooltip_controller.cc 221 gfx::Font font = GetDefaultFont(); 222 size_t tooltip_len; 223 224 // Error in computed size vs. expected size should not be greater than the 225 // size of the longest word. 226 int error_in_pixel_width = font.GetStringWidth(ASCIIToUTF16("tooltip")); 227 228 // Long tooltips should wrap to next line 229 tooltip.clear(); 230 width = line_count = -1; 231 expect_lines = 3; 232 for (; font.GetStringWidth(tooltip) <= (expect_lines - 1) * max_pixel_width;) 233 tooltip.append(ASCIIToUTF16("This is part of the tooltip")); 234 tooltip_len = tooltip.length(); 235 TooltipControllerTestHelper::TrimTooltipToFit( 236 max_width, &tooltip, &width, &line_count); 237 EXPECT_NEAR(max_pixel_width, width, error_in_pixel_width); 238 EXPECT_EQ(expect_lines, line_count); 239 EXPECT_EQ(tooltip_len + expect_lines - 1, tooltip.length()); 240 241 // More than |max_lines| lines should get truncated at 10 lines. 242 tooltip.clear(); 243 width = line_count = -1; 244 expect_lines = 13; 245 for (; font.GetStringWidth(tooltip) <= (expect_lines - 1) * max_pixel_width;) 246 tooltip.append(ASCIIToUTF16("This is part of the tooltip")); 247 TooltipControllerTestHelper::TrimTooltipToFit( 248 max_width, &tooltip, &width, &line_count); 249 EXPECT_NEAR(max_pixel_width, width, error_in_pixel_width); 250 EXPECT_EQ(max_lines, line_count); 251 252 // Long multi line tooltips should wrap individual lines. 253 tooltip.clear(); 254 width = line_count = -1; 255 expect_lines = 4; 256 for (; font.GetStringWidth(tooltip) <= (expect_lines - 2) * max_pixel_width;) 257 tooltip.append(ASCIIToUTF16("This is part of the tooltip")); 258 tooltip.insert(tooltip.length() / 2, ASCIIToUTF16("\n")); 259 tooltip_len = tooltip.length(); 260 TooltipControllerTestHelper::TrimTooltipToFit( 261 max_width, &tooltip, &width, &line_count); 262 EXPECT_NEAR(max_pixel_width, width, error_in_pixel_width); 263 EXPECT_EQ(expect_lines, line_count); 264 // We may have inserted the line break above near a space which will get 265 // trimmed. Hence we may be off by 1 in the final tooltip length calculation. 266 EXPECT_NEAR(tooltip_len + expect_lines - 2, tooltip.length(), 1); 267 268 #if !defined(OS_WIN) 269 // Tooltip with really long word gets elided. 270 tooltip.clear(); 271 width = line_count = -1; 272 tooltip = UTF8ToUTF16(std::string('a', max_pixel_width)); 273 TooltipControllerTestHelper::TrimTooltipToFit( 274 max_width, &tooltip, &width, &line_count); 275 EXPECT_NEAR(max_pixel_width, width, 5); 276 EXPECT_EQ(1, line_count); 277 EXPECT_EQ(ui::ElideText(UTF8ToUTF16(std::string('a', max_pixel_width)), font, 278 max_pixel_width, ui::ELIDE_AT_END), tooltip); 279 #endif 280 281 // Normal small tooltip should stay as is. 282 tooltip.clear(); 283 width = line_count = -1; 284 tooltip = ASCIIToUTF16("Small Tooltip"); 285 TooltipControllerTestHelper::TrimTooltipToFit( 286 max_width, &tooltip, &width, &line_count); 287 EXPECT_EQ(font.GetStringWidth(ASCIIToUTF16("Small Tooltip")), width); 288 EXPECT_EQ(1, line_count); 289 EXPECT_EQ(ASCIIToUTF16("Small Tooltip"), tooltip); 290 291 // Normal small multi-line tooltip should stay as is. 292 tooltip.clear(); 293 width = line_count = -1; 294 tooltip = ASCIIToUTF16("Multi line\nTooltip"); 295 TooltipControllerTestHelper::TrimTooltipToFit( 296 max_width, &tooltip, &width, &line_count); 297 int expected_width = font.GetStringWidth(ASCIIToUTF16("Multi line")); 298 expected_width = std::max(expected_width, 299 font.GetStringWidth(ASCIIToUTF16("Tooltip"))); 300 EXPECT_EQ(expected_width, width); 301 EXPECT_EQ(2, line_count); 302 EXPECT_EQ(ASCIIToUTF16("Multi line\nTooltip"), tooltip); 303 304 // Whitespaces in tooltips are preserved. 305 tooltip.clear(); 306 width = line_count = -1; 307 tooltip = ASCIIToUTF16("Small Tool t\tip"); 308 TooltipControllerTestHelper::TrimTooltipToFit( 309 max_width, &tooltip, &width, &line_count); 310 EXPECT_EQ(font.GetStringWidth(ASCIIToUTF16("Small Tool t\tip")), width); 311 EXPECT_EQ(1, line_count); 312 EXPECT_EQ(ASCIIToUTF16("Small Tool t\tip"), tooltip); 313 } 314 315 TEST_F(TooltipControllerTest, TooltipHidesOnKeyPressAndStaysHiddenUntilChange) { 316 view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text for view 1")); 317 EXPECT_EQ(string16(), helper_->GetTooltipText()); 318 EXPECT_EQ(NULL, helper_->GetTooltipWindow()); 319 320 TooltipTestView* view2 = PrepareSecondView(); 321 view2->set_tooltip_text(ASCIIToUTF16("Tooltip Text for view 2")); 322 323 aura::Window* window = GetWindow(); 324 325 // Fire tooltip timer so tooltip becomes visible. 326 generator_->MoveMouseRelativeTo(window, view_->bounds().CenterPoint()); 327 helper_->FireTooltipTimer(); 328 EXPECT_TRUE(helper_->IsTooltipVisible()); 329 EXPECT_TRUE(helper_->IsTooltipShownTimerRunning()); 330 331 generator_->PressKey(ui::VKEY_1, 0); 332 EXPECT_FALSE(helper_->IsTooltipVisible()); 333 EXPECT_FALSE(helper_->IsTooltipTimerRunning()); 334 EXPECT_FALSE(helper_->IsTooltipShownTimerRunning()); 335 336 // Moving the mouse inside |view1| should not change the state of the tooltip 337 // or the timers. 338 for (int i = 0; i < 49; i++) { 339 generator_->MoveMouseBy(1, 0); 340 EXPECT_FALSE(helper_->IsTooltipVisible()); 341 EXPECT_FALSE(helper_->IsTooltipTimerRunning()); 342 EXPECT_FALSE(helper_->IsTooltipShownTimerRunning()); 343 EXPECT_EQ(window, 344 GetRootWindow()->GetEventHandlerForPoint( 345 generator_->current_location())); 346 string16 expected_tooltip = ASCIIToUTF16("Tooltip Text for view 1"); 347 EXPECT_EQ(expected_tooltip, aura::client::GetTooltipText(window)); 348 EXPECT_EQ(expected_tooltip, helper_->GetTooltipText()); 349 EXPECT_EQ(window, helper_->GetTooltipWindow()); 350 } 351 352 // Now we move the mouse on to |view2|. It should re-start the tooltip timer. 353 generator_->MoveMouseBy(1, 0); 354 EXPECT_TRUE(helper_->IsTooltipTimerRunning()); 355 helper_->FireTooltipTimer(); 356 EXPECT_TRUE(helper_->IsTooltipVisible()); 357 EXPECT_TRUE(helper_->IsTooltipShownTimerRunning()); 358 string16 expected_tooltip = ASCIIToUTF16("Tooltip Text for view 2"); 359 EXPECT_EQ(expected_tooltip, aura::client::GetTooltipText(window)); 360 EXPECT_EQ(expected_tooltip, helper_->GetTooltipText()); 361 EXPECT_EQ(window, helper_->GetTooltipWindow()); 362 } 363 364 TEST_F(TooltipControllerTest, TooltipHidesOnTimeoutAndStaysHiddenUntilChange) { 365 view_->set_tooltip_text(ASCIIToUTF16("Tooltip Text for view 1")); 366 EXPECT_EQ(string16(), helper_->GetTooltipText()); 367 EXPECT_EQ(NULL, helper_->GetTooltipWindow()); 368 369 TooltipTestView* view2 = PrepareSecondView(); 370 view2->set_tooltip_text(ASCIIToUTF16("Tooltip Text for view 2")); 371 372 aura::Window* window = GetWindow(); 373 374 // Fire tooltip timer so tooltip becomes visible. 375 generator_->MoveMouseRelativeTo(window, view_->bounds().CenterPoint()); 376 helper_->FireTooltipTimer(); 377 EXPECT_TRUE(helper_->IsTooltipVisible()); 378 EXPECT_TRUE(helper_->IsTooltipShownTimerRunning()); 379 380 helper_->FireTooltipShownTimer(); 381 EXPECT_FALSE(helper_->IsTooltipVisible()); 382 EXPECT_FALSE(helper_->IsTooltipTimerRunning()); 383 EXPECT_FALSE(helper_->IsTooltipShownTimerRunning()); 384 385 // Moving the mouse inside |view1| should not change the state of the tooltip 386 // or the timers. 387 for (int i = 0; i < 49; ++i) { 388 generator_->MoveMouseBy(1, 0); 389 EXPECT_FALSE(helper_->IsTooltipVisible()); 390 EXPECT_FALSE(helper_->IsTooltipTimerRunning()); 391 EXPECT_FALSE(helper_->IsTooltipShownTimerRunning()); 392 EXPECT_EQ(window, GetRootWindow()->GetEventHandlerForPoint( 393 generator_->current_location())); 394 string16 expected_tooltip = ASCIIToUTF16("Tooltip Text for view 1"); 395 EXPECT_EQ(expected_tooltip, aura::client::GetTooltipText(window)); 396 EXPECT_EQ(expected_tooltip, helper_->GetTooltipText()); 397 EXPECT_EQ(window, helper_->GetTooltipWindow()); 398 } 399 400 // Now we move the mouse on to |view2|. It should re-start the tooltip timer. 401 generator_->MoveMouseBy(1, 0); 402 EXPECT_TRUE(helper_->IsTooltipTimerRunning()); 403 helper_->FireTooltipTimer(); 404 EXPECT_TRUE(helper_->IsTooltipVisible()); 405 EXPECT_TRUE(helper_->IsTooltipShownTimerRunning()); 406 string16 expected_tooltip = ASCIIToUTF16("Tooltip Text for view 2"); 407 EXPECT_EQ(expected_tooltip, aura::client::GetTooltipText(window)); 408 EXPECT_EQ(expected_tooltip, helper_->GetTooltipText()); 409 EXPECT_EQ(window, helper_->GetTooltipWindow()); 410 } 411 412 } // namespace test 413 } // namespace corewm 414 } // namespace views 415