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/strings/string_util.h" 11 #include "base/win/windows_version.h" 12 #include "ui/app_list/app_list_constants.h" 13 #include "ui/app_list/app_list_model.h" 14 #include "ui/app_list/app_list_view_delegate.h" 15 #include "ui/app_list/speech_ui_model.h" 16 #include "ui/app_list/views/app_list_background.h" 17 #include "ui/app_list/views/app_list_folder_view.h" 18 #include "ui/app_list/views/app_list_main_view.h" 19 #include "ui/app_list/views/app_list_view_observer.h" 20 #include "ui/app_list/views/apps_container_view.h" 21 #include "ui/app_list/views/contents_view.h" 22 #include "ui/app_list/views/search_box_view.h" 23 #include "ui/app_list/views/speech_view.h" 24 #include "ui/base/ui_base_switches.h" 25 #include "ui/compositor/layer.h" 26 #include "ui/compositor/layer_animation_observer.h" 27 #include "ui/compositor/scoped_layer_animation_settings.h" 28 #include "ui/gfx/image/image_skia.h" 29 #include "ui/gfx/insets.h" 30 #include "ui/gfx/path.h" 31 #include "ui/gfx/skia_util.h" 32 #include "ui/views/bubble/bubble_frame_view.h" 33 #include "ui/views/controls/textfield/textfield.h" 34 #include "ui/views/layout/fill_layout.h" 35 #include "ui/views/widget/widget.h" 36 37 #if defined(USE_AURA) 38 #include "ui/aura/window.h" 39 #include "ui/aura/window_tree_host.h" 40 #include "ui/views/bubble/bubble_window_targeter.h" 41 #if defined(OS_WIN) 42 #include "ui/base/win/shell.h" 43 #endif 44 #if !defined(OS_CHROMEOS) 45 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" 46 #endif 47 #endif // defined(USE_AURA) 48 49 namespace app_list { 50 51 namespace { 52 53 // The margin from the edge to the speech UI. 54 const int kSpeechUIMargin = 12; 55 56 // The vertical position for the appearing animation of the speech UI. 57 const float kSpeechUIAppearingPosition = 12; 58 59 // The distance between the arrow tip and edge of the anchor view. 60 const int kArrowOffset = 10; 61 62 // Determines whether the current environment supports shadows bubble borders. 63 bool SupportsShadow() { 64 #if defined(OS_WIN) 65 // Shadows are not supported on Windows without Aero Glass. 66 if (!ui::win::IsAeroGlassEnabled() || 67 CommandLine::ForCurrentProcess()->HasSwitch( 68 switches::kDisableDwmComposition)) { 69 return false; 70 } 71 #elif defined(OS_LINUX) && !defined(OS_CHROMEOS) 72 // Shadows are not supported on (non-ChromeOS) Linux. 73 return false; 74 #endif 75 return true; 76 } 77 78 } // namespace 79 80 // An animation observer to hide the view at the end of the animation. 81 class HideViewAnimationObserver : public ui::ImplicitAnimationObserver { 82 public: 83 HideViewAnimationObserver() 84 : frame_(NULL), 85 target_(NULL) { 86 } 87 88 virtual ~HideViewAnimationObserver() { 89 if (target_) 90 StopObservingImplicitAnimations(); 91 } 92 93 void SetTarget(views::View* target) { 94 if (!target_) 95 StopObservingImplicitAnimations(); 96 target_ = target; 97 } 98 99 void set_frame(views::BubbleFrameView* frame) { frame_ = frame; } 100 101 private: 102 // Overridden from ui::ImplicitAnimationObserver: 103 virtual void OnImplicitAnimationsCompleted() OVERRIDE { 104 if (target_) { 105 target_->SetVisible(false); 106 target_ = NULL; 107 108 // Should update the background by invoking SchedulePaint(). 109 frame_->SchedulePaint(); 110 } 111 } 112 113 views::BubbleFrameView* frame_; 114 views::View* target_; 115 116 DISALLOW_COPY_AND_ASSIGN(HideViewAnimationObserver); 117 }; 118 119 //////////////////////////////////////////////////////////////////////////////// 120 // AppListView: 121 122 AppListView::AppListView(AppListViewDelegate* delegate) 123 : delegate_(delegate), 124 app_list_main_view_(NULL), 125 speech_view_(NULL), 126 animation_observer_(new HideViewAnimationObserver()) { 127 CHECK(delegate); 128 129 delegate_->AddObserver(this); 130 delegate_->GetSpeechUI()->AddObserver(this); 131 } 132 133 AppListView::~AppListView() { 134 delegate_->GetSpeechUI()->RemoveObserver(this); 135 delegate_->RemoveObserver(this); 136 animation_observer_.reset(); 137 // Remove child views first to ensure no remaining dependencies on delegate_. 138 RemoveAllChildViews(true); 139 } 140 141 void AppListView::InitAsBubbleAttachedToAnchor( 142 gfx::NativeView parent, 143 int initial_apps_page, 144 views::View* anchor, 145 const gfx::Vector2d& anchor_offset, 146 views::BubbleBorder::Arrow arrow, 147 bool border_accepts_events) { 148 SetAnchorView(anchor); 149 InitAsBubbleInternal( 150 parent, initial_apps_page, arrow, border_accepts_events, anchor_offset); 151 } 152 153 void AppListView::InitAsBubbleAtFixedLocation( 154 gfx::NativeView parent, 155 int initial_apps_page, 156 const gfx::Point& anchor_point_in_screen, 157 views::BubbleBorder::Arrow arrow, 158 bool border_accepts_events) { 159 SetAnchorView(NULL); 160 SetAnchorRect(gfx::Rect(anchor_point_in_screen, gfx::Size())); 161 InitAsBubbleInternal( 162 parent, initial_apps_page, arrow, border_accepts_events, gfx::Vector2d()); 163 } 164 165 void AppListView::SetBubbleArrow(views::BubbleBorder::Arrow arrow) { 166 GetBubbleFrameView()->bubble_border()->set_arrow(arrow); 167 SizeToContents(); // Recalcuates with new border. 168 GetBubbleFrameView()->SchedulePaint(); 169 } 170 171 void AppListView::SetAnchorPoint(const gfx::Point& anchor_point) { 172 SetAnchorRect(gfx::Rect(anchor_point, gfx::Size())); 173 } 174 175 void AppListView::SetDragAndDropHostOfCurrentAppList( 176 ApplicationDragAndDropHost* drag_and_drop_host) { 177 app_list_main_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host); 178 } 179 180 void AppListView::ShowWhenReady() { 181 app_list_main_view_->ShowAppListWhenReady(); 182 } 183 184 void AppListView::Close() { 185 app_list_main_view_->Close(); 186 delegate_->Dismiss(); 187 } 188 189 void AppListView::UpdateBounds() { 190 SizeToContents(); 191 } 192 193 bool AppListView::ShouldCenterWindow() const { 194 return delegate_->ShouldCenterWindow(); 195 } 196 197 gfx::Size AppListView::GetPreferredSize() const { 198 return app_list_main_view_->GetPreferredSize(); 199 } 200 201 void AppListView::Paint(gfx::Canvas* canvas, const views::CullSet& cull_set) { 202 views::BubbleDelegateView::Paint(canvas, cull_set); 203 if (!next_paint_callback_.is_null()) { 204 next_paint_callback_.Run(); 205 next_paint_callback_.Reset(); 206 } 207 } 208 209 void AppListView::OnThemeChanged() { 210 #if defined(OS_WIN) 211 GetWidget()->Close(); 212 #endif 213 } 214 215 bool AppListView::ShouldHandleSystemCommands() const { 216 return true; 217 } 218 219 void AppListView::Prerender() { 220 app_list_main_view_->Prerender(); 221 } 222 223 void AppListView::OnProfilesChanged() { 224 app_list_main_view_->search_box_view()->InvalidateMenu(); 225 } 226 227 void AppListView::SetProfileByPath(const base::FilePath& profile_path) { 228 delegate_->SetProfileByPath(profile_path); 229 app_list_main_view_->ModelChanged(); 230 } 231 232 void AppListView::AddObserver(AppListViewObserver* observer) { 233 observers_.AddObserver(observer); 234 } 235 236 void AppListView::RemoveObserver(AppListViewObserver* observer) { 237 observers_.RemoveObserver(observer); 238 } 239 240 // static 241 void AppListView::SetNextPaintCallback(const base::Closure& callback) { 242 next_paint_callback_ = callback; 243 } 244 245 #if defined(OS_WIN) 246 HWND AppListView::GetHWND() const { 247 gfx::NativeWindow window = 248 GetWidget()->GetTopLevelWidget()->GetNativeWindow(); 249 return window->GetHost()->GetAcceleratedWidget(); 250 } 251 #endif 252 253 PaginationModel* AppListView::GetAppsPaginationModel() { 254 return app_list_main_view_->contents_view() 255 ->apps_container_view() 256 ->apps_grid_view() 257 ->pagination_model(); 258 } 259 260 void AppListView::InitAsBubbleInternal(gfx::NativeView parent, 261 int initial_apps_page, 262 views::BubbleBorder::Arrow arrow, 263 bool border_accepts_events, 264 const gfx::Vector2d& anchor_offset) { 265 app_list_main_view_ = 266 new AppListMainView(delegate_.get(), initial_apps_page, parent); 267 AddChildView(app_list_main_view_); 268 app_list_main_view_->SetPaintToLayer(true); 269 app_list_main_view_->SetFillsBoundsOpaquely(false); 270 app_list_main_view_->layer()->SetMasksToBounds(true); 271 272 // Speech recognition is available only when the start page exists. 273 if (delegate_ && delegate_->IsSpeechRecognitionEnabled()) { 274 speech_view_ = new SpeechView(delegate_.get()); 275 speech_view_->SetVisible(false); 276 speech_view_->SetPaintToLayer(true); 277 speech_view_->SetFillsBoundsOpaquely(false); 278 speech_view_->layer()->SetOpacity(0.0f); 279 AddChildView(speech_view_); 280 } 281 282 OnProfilesChanged(); 283 set_color(kContentsBackgroundColor); 284 set_margins(gfx::Insets()); 285 set_parent_window(parent); 286 set_close_on_deactivate(false); 287 set_close_on_esc(false); 288 set_anchor_view_insets(gfx::Insets(kArrowOffset + anchor_offset.y(), 289 kArrowOffset + anchor_offset.x(), 290 kArrowOffset - anchor_offset.y(), 291 kArrowOffset - anchor_offset.x())); 292 set_border_accepts_events(border_accepts_events); 293 set_shadow(SupportsShadow() ? views::BubbleBorder::BIG_SHADOW 294 : views::BubbleBorder::NO_SHADOW_OPAQUE_BORDER); 295 views::BubbleDelegateView::CreateBubble(this); 296 SetBubbleArrow(arrow); 297 298 #if defined(USE_AURA) 299 aura::Window* window = GetWidget()->GetNativeWindow(); 300 window->layer()->SetMasksToBounds(true); 301 GetBubbleFrameView()->set_background(new AppListBackground( 302 GetBubbleFrameView()->bubble_border()->GetBorderCornerRadius(), 303 app_list_main_view_)); 304 set_background(NULL); 305 window->SetEventTargeter(scoped_ptr<ui::EventTargeter>( 306 new views::BubbleWindowTargeter(this))); 307 #else 308 set_background(new AppListBackground( 309 GetBubbleFrameView()->bubble_border()->GetBorderCornerRadius(), 310 app_list_main_view_)); 311 312 // On non-aura the bubble has two widgets, and it's possible for the border 313 // to be shown independently in odd situations. Explicitly hide the bubble 314 // widget to ensure that any WM_WINDOWPOSCHANGED messages triggered by the 315 // window manager do not have the SWP_SHOWWINDOW flag set which would cause 316 // the border to be shown. See http://crbug.com/231687 . 317 GetWidget()->Hide(); 318 #endif 319 320 if (delegate_) 321 delegate_->ViewInitialized(); 322 } 323 324 void AppListView::OnBeforeBubbleWidgetInit( 325 views::Widget::InitParams* params, 326 views::Widget* widget) const { 327 #if defined(USE_AURA) && !defined(OS_CHROMEOS) 328 if (delegate_ && delegate_->ForceNativeDesktop()) 329 params->native_widget = new views::DesktopNativeWidgetAura(widget); 330 #endif 331 #if defined(OS_WIN) 332 // Windows 7 and higher offer pinning to the taskbar, but we need presence 333 // on the taskbar for the user to be able to pin us. So, show the window on 334 // the taskbar for these versions of Windows. 335 if (base::win::GetVersion() >= base::win::VERSION_WIN7) 336 params->force_show_in_taskbar = true; 337 #elif defined(OS_LINUX) 338 // Set up a custom WM_CLASS for the app launcher window. This allows task 339 // switchers in X11 environments to distinguish it from main browser windows. 340 params->wm_class_name = kAppListWMClass; 341 // Show the window in the taskbar, even though it is a bubble, which would not 342 // normally be shown. 343 params->force_show_in_taskbar = true; 344 #endif 345 } 346 347 views::View* AppListView::GetInitiallyFocusedView() { 348 return app_list_main_view_->search_box_view()->search_box(); 349 } 350 351 gfx::ImageSkia AppListView::GetWindowIcon() { 352 if (delegate_) 353 return delegate_->GetWindowIcon(); 354 355 return gfx::ImageSkia(); 356 } 357 358 bool AppListView::WidgetHasHitTestMask() const { 359 return true; 360 } 361 362 void AppListView::GetWidgetHitTestMask(gfx::Path* mask) const { 363 DCHECK(mask); 364 mask->addRect(gfx::RectToSkRect( 365 GetBubbleFrameView()->GetContentsBounds())); 366 } 367 368 bool AppListView::AcceleratorPressed(const ui::Accelerator& accelerator) { 369 // The accelerator is added by BubbleDelegateView. 370 if (accelerator.key_code() == ui::VKEY_ESCAPE) { 371 if (app_list_main_view_->search_box_view()->HasSearch()) { 372 app_list_main_view_->search_box_view()->ClearSearch(); 373 } else if (app_list_main_view_->contents_view() 374 ->apps_container_view() 375 ->IsInFolderView()) { 376 app_list_main_view_->contents_view() 377 ->apps_container_view() 378 ->app_list_folder_view() 379 ->CloseFolderPage(); 380 return true; 381 } else { 382 GetWidget()->Deactivate(); 383 Close(); 384 } 385 return true; 386 } 387 388 return false; 389 } 390 391 void AppListView::Layout() { 392 const gfx::Rect contents_bounds = GetContentsBounds(); 393 app_list_main_view_->SetBoundsRect(contents_bounds); 394 395 if (speech_view_) { 396 gfx::Rect speech_bounds = contents_bounds; 397 int preferred_height = speech_view_->GetPreferredSize().height(); 398 speech_bounds.Inset(kSpeechUIMargin, kSpeechUIMargin); 399 speech_bounds.set_height(std::min(speech_bounds.height(), 400 preferred_height)); 401 speech_bounds.Inset(-speech_view_->GetInsets()); 402 speech_view_->SetBoundsRect(speech_bounds); 403 } 404 } 405 406 void AppListView::SchedulePaintInRect(const gfx::Rect& rect) { 407 BubbleDelegateView::SchedulePaintInRect(rect); 408 if (GetBubbleFrameView()) 409 GetBubbleFrameView()->SchedulePaint(); 410 } 411 412 void AppListView::OnWidgetDestroying(views::Widget* widget) { 413 BubbleDelegateView::OnWidgetDestroying(widget); 414 if (delegate_ && widget == GetWidget()) 415 delegate_->ViewClosing(); 416 } 417 418 void AppListView::OnWidgetActivationChanged(views::Widget* widget, 419 bool active) { 420 // Do not called inherited function as the bubble delegate auto close 421 // functionality is not used. 422 if (widget == GetWidget()) 423 FOR_EACH_OBSERVER(AppListViewObserver, observers_, 424 OnActivationChanged(widget, active)); 425 } 426 427 void AppListView::OnWidgetVisibilityChanged(views::Widget* widget, 428 bool visible) { 429 BubbleDelegateView::OnWidgetVisibilityChanged(widget, visible); 430 431 if (widget != GetWidget()) 432 return; 433 434 if (!visible) 435 app_list_main_view_->ResetForShow(); 436 } 437 438 void AppListView::OnSpeechRecognitionStateChanged( 439 SpeechRecognitionState new_state) { 440 if (!speech_view_) 441 return; 442 443 bool will_appear = (new_state == SPEECH_RECOGNITION_RECOGNIZING || 444 new_state == SPEECH_RECOGNITION_IN_SPEECH || 445 new_state == SPEECH_RECOGNITION_NETWORK_ERROR); 446 // No change for this class. 447 if (speech_view_->visible() == will_appear) 448 return; 449 450 if (will_appear) 451 speech_view_->Reset(); 452 453 animation_observer_->set_frame(GetBubbleFrameView()); 454 gfx::Transform speech_transform; 455 speech_transform.Translate( 456 0, SkFloatToMScalar(kSpeechUIAppearingPosition)); 457 if (will_appear) 458 speech_view_->layer()->SetTransform(speech_transform); 459 460 { 461 ui::ScopedLayerAnimationSettings main_settings( 462 app_list_main_view_->layer()->GetAnimator()); 463 if (will_appear) { 464 animation_observer_->SetTarget(app_list_main_view_); 465 main_settings.AddObserver(animation_observer_.get()); 466 } 467 app_list_main_view_->layer()->SetOpacity(will_appear ? 0.0f : 1.0f); 468 } 469 470 { 471 ui::ScopedLayerAnimationSettings speech_settings( 472 speech_view_->layer()->GetAnimator()); 473 if (!will_appear) { 474 animation_observer_->SetTarget(speech_view_); 475 speech_settings.AddObserver(animation_observer_.get()); 476 } 477 478 speech_view_->layer()->SetOpacity(will_appear ? 1.0f : 0.0f); 479 if (will_appear) 480 speech_view_->layer()->SetTransform(gfx::Transform()); 481 else 482 speech_view_->layer()->SetTransform(speech_transform); 483 } 484 485 if (will_appear) 486 speech_view_->SetVisible(true); 487 else 488 app_list_main_view_->SetVisible(true); 489 } 490 491 } // namespace app_list 492