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