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/app_list/views/app_list_view.h" 6 7 #include <algorithm> 8 9 #include "base/command_line.h" 10 #include "base/metrics/histogram.h" 11 #include "base/strings/string_util.h" 12 #include "base/win/windows_version.h" 13 #include "ui/app_list/app_list_constants.h" 14 #include "ui/app_list/app_list_model.h" 15 #include "ui/app_list/app_list_switches.h" 16 #include "ui/app_list/app_list_view_delegate.h" 17 #include "ui/app_list/speech_ui_model.h" 18 #include "ui/app_list/views/app_list_background.h" 19 #include "ui/app_list/views/app_list_folder_view.h" 20 #include "ui/app_list/views/app_list_main_view.h" 21 #include "ui/app_list/views/app_list_view_observer.h" 22 #include "ui/app_list/views/apps_container_view.h" 23 #include "ui/app_list/views/contents_view.h" 24 #include "ui/app_list/views/search_box_view.h" 25 #include "ui/app_list/views/speech_view.h" 26 #include "ui/app_list/views/start_page_view.h" 27 #include "ui/base/ui_base_switches.h" 28 #include "ui/compositor/layer.h" 29 #include "ui/compositor/layer_animation_observer.h" 30 #include "ui/compositor/scoped_layer_animation_settings.h" 31 #include "ui/gfx/canvas.h" 32 #include "ui/gfx/image/image_skia.h" 33 #include "ui/gfx/insets.h" 34 #include "ui/gfx/path.h" 35 #include "ui/gfx/skia_util.h" 36 #include "ui/resources/grit/ui_resources.h" 37 #include "ui/views/bubble/bubble_frame_view.h" 38 #include "ui/views/controls/image_view.h" 39 #include "ui/views/controls/textfield/textfield.h" 40 #include "ui/views/layout/fill_layout.h" 41 #include "ui/views/widget/widget.h" 42 43 #if defined(USE_AURA) 44 #include "ui/aura/window.h" 45 #include "ui/aura/window_tree_host.h" 46 #include "ui/views/bubble/bubble_window_targeter.h" 47 #if defined(OS_WIN) 48 #include "ui/base/win/shell.h" 49 #endif 50 #if !defined(OS_CHROMEOS) 51 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" 52 #endif 53 #endif // defined(USE_AURA) 54 55 namespace app_list { 56 57 namespace { 58 59 // The margin from the edge to the speech UI. 60 const int kSpeechUIMargin = 12; 61 62 // The vertical position for the appearing animation of the speech UI. 63 const float kSpeechUIAppearingPosition = 12; 64 65 // The distance between the arrow tip and edge of the anchor view. 66 const int kArrowOffset = 10; 67 68 // Determines whether the current environment supports shadows bubble borders. 69 bool SupportsShadow() { 70 #if defined(OS_WIN) 71 // Shadows are not supported on Windows without Aero Glass. 72 if (!ui::win::IsAeroGlassEnabled() || 73 CommandLine::ForCurrentProcess()->HasSwitch( 74 ::switches::kDisableDwmComposition)) { 75 return false; 76 } 77 #elif defined(OS_LINUX) && !defined(OS_CHROMEOS) 78 // Shadows are not supported on (non-ChromeOS) Linux. 79 return false; 80 #endif 81 return true; 82 } 83 84 // The background for the App List overlay, which appears as a white rounded 85 // rectangle with the given radius and the same size as the target view. 86 class AppListOverlayBackground : public views::Background { 87 public: 88 AppListOverlayBackground(int corner_radius) 89 : corner_radius_(corner_radius) {}; 90 virtual ~AppListOverlayBackground() {}; 91 92 // Overridden from views::Background: 93 virtual void Paint(gfx::Canvas* canvas, views::View* view) const OVERRIDE { 94 SkPaint paint; 95 paint.setStyle(SkPaint::kFill_Style); 96 paint.setColor(SK_ColorWHITE); 97 canvas->DrawRoundRect(view->GetContentsBounds(), corner_radius_, paint); 98 } 99 100 private: 101 const int corner_radius_; 102 103 DISALLOW_COPY_AND_ASSIGN(AppListOverlayBackground); 104 }; 105 106 } // namespace 107 108 // An animation observer to hide the view at the end of the animation. 109 class HideViewAnimationObserver : public ui::ImplicitAnimationObserver { 110 public: 111 HideViewAnimationObserver() 112 : frame_(NULL), 113 target_(NULL) { 114 } 115 116 virtual ~HideViewAnimationObserver() { 117 if (target_) 118 StopObservingImplicitAnimations(); 119 } 120 121 void SetTarget(views::View* target) { 122 if (target_) 123 StopObservingImplicitAnimations(); 124 target_ = target; 125 } 126 127 void set_frame(views::BubbleFrameView* frame) { frame_ = frame; } 128 129 private: 130 // Overridden from ui::ImplicitAnimationObserver: 131 virtual void OnImplicitAnimationsCompleted() OVERRIDE { 132 if (target_) { 133 target_->SetVisible(false); 134 target_ = NULL; 135 136 // Should update the background by invoking SchedulePaint(). 137 if (frame_) 138 frame_->SchedulePaint(); 139 } 140 } 141 142 views::BubbleFrameView* frame_; 143 views::View* target_; 144 145 DISALLOW_COPY_AND_ASSIGN(HideViewAnimationObserver); 146 }; 147 148 //////////////////////////////////////////////////////////////////////////////// 149 // AppListView: 150 151 AppListView::AppListView(AppListViewDelegate* delegate) 152 : delegate_(delegate), 153 app_list_main_view_(NULL), 154 speech_view_(NULL), 155 experimental_banner_view_(NULL), 156 overlay_view_(NULL), 157 animation_observer_(new HideViewAnimationObserver()) { 158 CHECK(delegate); 159 160 delegate_->AddObserver(this); 161 delegate_->GetSpeechUI()->AddObserver(this); 162 } 163 164 AppListView::~AppListView() { 165 delegate_->GetSpeechUI()->RemoveObserver(this); 166 delegate_->RemoveObserver(this); 167 animation_observer_.reset(); 168 // Remove child views first to ensure no remaining dependencies on delegate_. 169 RemoveAllChildViews(true); 170 } 171 172 void AppListView::InitAsBubbleAttachedToAnchor( 173 gfx::NativeView parent, 174 int initial_apps_page, 175 views::View* anchor, 176 const gfx::Vector2d& anchor_offset, 177 views::BubbleBorder::Arrow arrow, 178 bool border_accepts_events) { 179 SetAnchorView(anchor); 180 InitAsBubbleInternal( 181 parent, initial_apps_page, arrow, border_accepts_events, anchor_offset); 182 } 183 184 void AppListView::InitAsBubbleAtFixedLocation( 185 gfx::NativeView parent, 186 int initial_apps_page, 187 const gfx::Point& anchor_point_in_screen, 188 views::BubbleBorder::Arrow arrow, 189 bool border_accepts_events) { 190 SetAnchorView(NULL); 191 SetAnchorRect(gfx::Rect(anchor_point_in_screen, gfx::Size())); 192 InitAsBubbleInternal( 193 parent, initial_apps_page, arrow, border_accepts_events, gfx::Vector2d()); 194 } 195 196 void AppListView::SetBubbleArrow(views::BubbleBorder::Arrow arrow) { 197 GetBubbleFrameView()->bubble_border()->set_arrow(arrow); 198 SizeToContents(); // Recalcuates with new border. 199 GetBubbleFrameView()->SchedulePaint(); 200 } 201 202 void AppListView::SetAnchorPoint(const gfx::Point& anchor_point) { 203 SetAnchorRect(gfx::Rect(anchor_point, gfx::Size())); 204 } 205 206 void AppListView::SetDragAndDropHostOfCurrentAppList( 207 ApplicationDragAndDropHost* drag_and_drop_host) { 208 app_list_main_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host); 209 } 210 211 void AppListView::ShowWhenReady() { 212 app_list_main_view_->ShowAppListWhenReady(); 213 } 214 215 void AppListView::Close() { 216 app_list_main_view_->Close(); 217 delegate_->Dismiss(); 218 } 219 220 void AppListView::UpdateBounds() { 221 SizeToContents(); 222 } 223 224 void AppListView::SetAppListOverlayVisible(bool visible) { 225 DCHECK(overlay_view_); 226 227 // Display the overlay immediately so we can begin the animation. 228 overlay_view_->SetVisible(true); 229 230 ui::ScopedLayerAnimationSettings settings( 231 overlay_view_->layer()->GetAnimator()); 232 settings.SetTweenType(gfx::Tween::LINEAR); 233 234 // If we're dismissing the overlay, hide the view at the end of the animation. 235 if (!visible) { 236 // Since only one animation is visible at a time, it's safe to re-use 237 // animation_observer_ here. 238 animation_observer_->set_frame(NULL); 239 animation_observer_->SetTarget(overlay_view_); 240 settings.AddObserver(animation_observer_.get()); 241 } 242 243 const float kOverlayFadeInMilliseconds = 125; 244 settings.SetTransitionDuration( 245 base::TimeDelta::FromMilliseconds(kOverlayFadeInMilliseconds)); 246 247 const float kOverlayOpacity = 0.75f; 248 overlay_view_->layer()->SetOpacity(visible ? kOverlayOpacity : 0.0f); 249 } 250 251 bool AppListView::ShouldCenterWindow() const { 252 return delegate_->ShouldCenterWindow(); 253 } 254 255 gfx::Size AppListView::GetPreferredSize() const { 256 return app_list_main_view_->GetPreferredSize(); 257 } 258 259 void AppListView::Paint(gfx::Canvas* canvas, const views::CullSet& cull_set) { 260 views::BubbleDelegateView::Paint(canvas, cull_set); 261 if (!next_paint_callback_.is_null()) { 262 next_paint_callback_.Run(); 263 next_paint_callback_.Reset(); 264 } 265 } 266 267 void AppListView::OnThemeChanged() { 268 #if defined(OS_WIN) 269 GetWidget()->Close(); 270 #endif 271 } 272 273 bool AppListView::ShouldHandleSystemCommands() const { 274 return true; 275 } 276 277 void AppListView::Prerender() { 278 app_list_main_view_->Prerender(); 279 } 280 281 void AppListView::OnProfilesChanged() { 282 app_list_main_view_->search_box_view()->InvalidateMenu(); 283 } 284 285 void AppListView::OnShutdown() { 286 // Nothing to do on views - the widget will soon be closed, which will tear 287 // everything down. 288 } 289 290 void AppListView::SetProfileByPath(const base::FilePath& profile_path) { 291 delegate_->SetProfileByPath(profile_path); 292 app_list_main_view_->ModelChanged(); 293 } 294 295 void AppListView::AddObserver(AppListViewObserver* observer) { 296 observers_.AddObserver(observer); 297 } 298 299 void AppListView::RemoveObserver(AppListViewObserver* observer) { 300 observers_.RemoveObserver(observer); 301 } 302 303 // static 304 void AppListView::SetNextPaintCallback(const base::Closure& callback) { 305 next_paint_callback_ = callback; 306 } 307 308 #if defined(OS_WIN) 309 HWND AppListView::GetHWND() const { 310 gfx::NativeWindow window = 311 GetWidget()->GetTopLevelWidget()->GetNativeWindow(); 312 return window->GetHost()->GetAcceleratedWidget(); 313 } 314 #endif 315 316 PaginationModel* AppListView::GetAppsPaginationModel() { 317 return app_list_main_view_->contents_view() 318 ->apps_container_view() 319 ->apps_grid_view() 320 ->pagination_model(); 321 } 322 323 void AppListView::InitAsBubbleInternal(gfx::NativeView parent, 324 int initial_apps_page, 325 views::BubbleBorder::Arrow arrow, 326 bool border_accepts_events, 327 const gfx::Vector2d& anchor_offset) { 328 base::Time start_time = base::Time::Now(); 329 330 app_list_main_view_ = 331 new AppListMainView(delegate_, initial_apps_page, parent); 332 AddChildView(app_list_main_view_); 333 app_list_main_view_->SetPaintToLayer(true); 334 app_list_main_view_->SetFillsBoundsOpaquely(false); 335 app_list_main_view_->layer()->SetMasksToBounds(true); 336 337 // Speech recognition is available only when the start page exists. 338 if (delegate_ && delegate_->IsSpeechRecognitionEnabled()) { 339 speech_view_ = new SpeechView(delegate_); 340 speech_view_->SetVisible(false); 341 speech_view_->SetPaintToLayer(true); 342 speech_view_->SetFillsBoundsOpaquely(false); 343 speech_view_->layer()->SetOpacity(0.0f); 344 AddChildView(speech_view_); 345 } 346 347 if (app_list::switches::IsExperimentalAppListEnabled()) { 348 // Draw a banner in the corner of the experimental app list. 349 experimental_banner_view_ = new views::ImageView; 350 const gfx::ImageSkia& experimental_icon = 351 *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( 352 IDR_APP_LIST_EXPERIMENTAL_ICON); 353 experimental_banner_view_->SetImage(experimental_icon); 354 experimental_banner_view_->SetPaintToLayer(true); 355 experimental_banner_view_->SetFillsBoundsOpaquely(false); 356 AddChildView(experimental_banner_view_); 357 } 358 359 OnProfilesChanged(); 360 set_color(kContentsBackgroundColor); 361 set_margins(gfx::Insets()); 362 set_parent_window(parent); 363 set_close_on_deactivate(false); 364 set_close_on_esc(false); 365 set_anchor_view_insets(gfx::Insets(kArrowOffset + anchor_offset.y(), 366 kArrowOffset + anchor_offset.x(), 367 kArrowOffset - anchor_offset.y(), 368 kArrowOffset - anchor_offset.x())); 369 set_border_accepts_events(border_accepts_events); 370 set_shadow(SupportsShadow() ? views::BubbleBorder::BIG_SHADOW 371 : views::BubbleBorder::NO_SHADOW_OPAQUE_BORDER); 372 views::BubbleDelegateView::CreateBubble(this); 373 SetBubbleArrow(arrow); 374 375 #if defined(USE_AURA) 376 aura::Window* window = GetWidget()->GetNativeWindow(); 377 window->layer()->SetMasksToBounds(true); 378 GetBubbleFrameView()->set_background(new AppListBackground( 379 GetBubbleFrameView()->bubble_border()->GetBorderCornerRadius(), 380 app_list_main_view_)); 381 set_background(NULL); 382 window->SetEventTargeter(scoped_ptr<ui::EventTargeter>( 383 new views::BubbleWindowTargeter(this))); 384 #else 385 set_background(new AppListBackground( 386 GetBubbleFrameView()->bubble_border()->GetBorderCornerRadius(), 387 app_list_main_view_)); 388 389 // On non-aura the bubble has two widgets, and it's possible for the border 390 // to be shown independently in odd situations. Explicitly hide the bubble 391 // widget to ensure that any WM_WINDOWPOSCHANGED messages triggered by the 392 // window manager do not have the SWP_SHOWWINDOW flag set which would cause 393 // the border to be shown. See http://crbug.com/231687 . 394 GetWidget()->Hide(); 395 #endif 396 397 // To make the overlay view, construct a view with a white background, rather 398 // than a white rectangle in it. This is because we need overlay_view_ to be 399 // drawn to its own layer (so it appears correctly in the foreground). 400 overlay_view_ = new views::View(); 401 overlay_view_->SetPaintToLayer(true); 402 overlay_view_->SetBoundsRect(GetContentsBounds()); 403 overlay_view_->SetVisible(false); 404 overlay_view_->layer()->SetOpacity(0.0f); 405 // On platforms that don't support a shadow, the rounded border of the app 406 // list is constructed _inside_ the view, so a rectangular background goes 407 // over the border in the rounded corners. To fix this, give the background a 408 // corner radius 1px smaller than the outer border, so it just reaches but 409 // doesn't cover it. 410 const int kOverlayCornerRadius = 411 GetBubbleFrameView()->bubble_border()->GetBorderCornerRadius(); 412 overlay_view_->set_background(new AppListOverlayBackground( 413 kOverlayCornerRadius - (SupportsShadow() ? 0 : 1))); 414 AddChildView(overlay_view_); 415 416 if (delegate_) 417 delegate_->ViewInitialized(); 418 419 UMA_HISTOGRAM_TIMES("Apps.AppListCreationTime", 420 base::Time::Now() - start_time); 421 } 422 423 void AppListView::OnBeforeBubbleWidgetInit( 424 views::Widget::InitParams* params, 425 views::Widget* widget) const { 426 #if defined(USE_AURA) && !defined(OS_CHROMEOS) 427 if (delegate_ && delegate_->ForceNativeDesktop()) 428 params->native_widget = new views::DesktopNativeWidgetAura(widget); 429 #endif 430 #if defined(OS_WIN) 431 // Windows 7 and higher offer pinning to the taskbar, but we need presence 432 // on the taskbar for the user to be able to pin us. So, show the window on 433 // the taskbar for these versions of Windows. 434 if (base::win::GetVersion() >= base::win::VERSION_WIN7) 435 params->force_show_in_taskbar = true; 436 #elif defined(OS_LINUX) 437 // Set up a custom WM_CLASS for the app launcher window. This allows task 438 // switchers in X11 environments to distinguish it from main browser windows. 439 params->wm_class_name = kAppListWMClass; 440 // Show the window in the taskbar, even though it is a bubble, which would not 441 // normally be shown. 442 params->force_show_in_taskbar = true; 443 #endif 444 } 445 446 views::View* AppListView::GetInitiallyFocusedView() { 447 return app_list::switches::IsExperimentalAppListEnabled() 448 ? app_list_main_view_->contents_view() 449 ->start_page_view() 450 ->dummy_search_box_view() 451 ->search_box() 452 : app_list_main_view_->search_box_view()->search_box(); 453 } 454 455 gfx::ImageSkia AppListView::GetWindowIcon() { 456 if (delegate_) 457 return delegate_->GetWindowIcon(); 458 459 return gfx::ImageSkia(); 460 } 461 462 bool AppListView::WidgetHasHitTestMask() const { 463 return true; 464 } 465 466 void AppListView::GetWidgetHitTestMask(gfx::Path* mask) const { 467 DCHECK(mask); 468 mask->addRect(gfx::RectToSkRect( 469 GetBubbleFrameView()->GetContentsBounds())); 470 } 471 472 bool AppListView::AcceleratorPressed(const ui::Accelerator& accelerator) { 473 // The accelerator is added by BubbleDelegateView. 474 if (accelerator.key_code() == ui::VKEY_ESCAPE) { 475 if (app_list_main_view_->search_box_view()->HasSearch()) { 476 app_list_main_view_->search_box_view()->ClearSearch(); 477 } else if (app_list_main_view_->contents_view() 478 ->apps_container_view() 479 ->IsInFolderView()) { 480 app_list_main_view_->contents_view() 481 ->apps_container_view() 482 ->app_list_folder_view() 483 ->CloseFolderPage(); 484 return true; 485 } else { 486 GetWidget()->Deactivate(); 487 Close(); 488 } 489 return true; 490 } 491 492 return false; 493 } 494 495 void AppListView::Layout() { 496 const gfx::Rect contents_bounds = GetContentsBounds(); 497 app_list_main_view_->SetBoundsRect(contents_bounds); 498 499 if (speech_view_) { 500 gfx::Rect speech_bounds = contents_bounds; 501 int preferred_height = speech_view_->GetPreferredSize().height(); 502 speech_bounds.Inset(kSpeechUIMargin, kSpeechUIMargin); 503 speech_bounds.set_height(std::min(speech_bounds.height(), 504 preferred_height)); 505 speech_bounds.Inset(-speech_view_->GetInsets()); 506 speech_view_->SetBoundsRect(speech_bounds); 507 } 508 509 if (experimental_banner_view_) { 510 // Position the experimental banner in the lower right corner. 511 gfx::Rect image_bounds = experimental_banner_view_->GetImageBounds(); 512 image_bounds.set_origin( 513 gfx::Point(contents_bounds.width() - image_bounds.width(), 514 contents_bounds.height() - image_bounds.height())); 515 experimental_banner_view_->SetBoundsRect(image_bounds); 516 } 517 } 518 519 void AppListView::SchedulePaintInRect(const gfx::Rect& rect) { 520 BubbleDelegateView::SchedulePaintInRect(rect); 521 if (GetBubbleFrameView()) 522 GetBubbleFrameView()->SchedulePaint(); 523 } 524 525 void AppListView::OnWidgetDestroying(views::Widget* widget) { 526 BubbleDelegateView::OnWidgetDestroying(widget); 527 if (delegate_ && widget == GetWidget()) 528 delegate_->ViewClosing(); 529 } 530 531 void AppListView::OnWidgetActivationChanged(views::Widget* widget, 532 bool active) { 533 // Do not called inherited function as the bubble delegate auto close 534 // functionality is not used. 535 if (widget == GetWidget()) 536 FOR_EACH_OBSERVER(AppListViewObserver, observers_, 537 OnActivationChanged(widget, active)); 538 } 539 540 void AppListView::OnWidgetVisibilityChanged(views::Widget* widget, 541 bool visible) { 542 BubbleDelegateView::OnWidgetVisibilityChanged(widget, visible); 543 544 if (widget != GetWidget()) 545 return; 546 547 if (!visible) 548 app_list_main_view_->ResetForShow(); 549 } 550 551 void AppListView::OnSpeechRecognitionStateChanged( 552 SpeechRecognitionState new_state) { 553 if (!speech_view_) 554 return; 555 556 bool will_appear = (new_state == SPEECH_RECOGNITION_RECOGNIZING || 557 new_state == SPEECH_RECOGNITION_IN_SPEECH || 558 new_state == SPEECH_RECOGNITION_NETWORK_ERROR); 559 // No change for this class. 560 if (speech_view_->visible() == will_appear) 561 return; 562 563 if (will_appear) 564 speech_view_->Reset(); 565 566 animation_observer_->set_frame(GetBubbleFrameView()); 567 gfx::Transform speech_transform; 568 speech_transform.Translate( 569 0, SkFloatToMScalar(kSpeechUIAppearingPosition)); 570 if (will_appear) 571 speech_view_->layer()->SetTransform(speech_transform); 572 573 { 574 ui::ScopedLayerAnimationSettings main_settings( 575 app_list_main_view_->layer()->GetAnimator()); 576 if (will_appear) { 577 animation_observer_->SetTarget(app_list_main_view_); 578 main_settings.AddObserver(animation_observer_.get()); 579 } 580 app_list_main_view_->layer()->SetOpacity(will_appear ? 0.0f : 1.0f); 581 } 582 583 { 584 ui::ScopedLayerAnimationSettings speech_settings( 585 speech_view_->layer()->GetAnimator()); 586 if (!will_appear) { 587 animation_observer_->SetTarget(speech_view_); 588 speech_settings.AddObserver(animation_observer_.get()); 589 } 590 591 speech_view_->layer()->SetOpacity(will_appear ? 1.0f : 0.0f); 592 if (will_appear) 593 speech_view_->layer()->SetTransform(gfx::Transform()); 594 else 595 speech_view_->layer()->SetTransform(speech_transform); 596 } 597 598 if (will_appear) 599 speech_view_->SetVisible(true); 600 else 601 app_list_main_view_->SetVisible(true); 602 } 603 604 } // namespace app_list 605