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 44namespace app_list { 45 46namespace { 47 48void (*g_next_paint_callback)(); 49 50// The margin from the edge to the speech UI. 51const int kSpeechUIMargin = 12; 52 53// The vertical position for the appearing animation of the speech UI. 54const float kSpeechUIApearingPosition =12; 55 56// The distance between the arrow tip and edge of the anchor view. 57const int kArrowOffset = 10; 58 59#if defined(OS_LINUX) 60// The WM_CLASS name for the app launcher window on Linux. 61const char* kAppListWMClass = "chrome_app_list"; 62#endif 63 64// Determines whether the current environment supports shadows bubble borders. 65bool 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. 83class 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 116AppListView::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 128AppListView::~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 136void 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 148void 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 160void AppListView::SetBubbleArrow(views::BubbleBorder::Arrow arrow) { 161 GetBubbleFrameView()->bubble_border()->set_arrow(arrow); 162 SizeToContents(); // Recalcuates with new border. 163 GetBubbleFrameView()->SchedulePaint(); 164} 165 166void AppListView::SetAnchorPoint(const gfx::Point& anchor_point) { 167 SetAnchorRect(gfx::Rect(anchor_point, gfx::Size())); 168} 169 170void AppListView::SetDragAndDropHostOfCurrentAppList( 171 ApplicationDragAndDropHost* drag_and_drop_host) { 172 app_list_main_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host); 173} 174 175void AppListView::ShowWhenReady() { 176 app_list_main_view_->ShowAppListWhenReady(); 177} 178 179void AppListView::Close() { 180 app_list_main_view_->Close(); 181 delegate_->Dismiss(); 182} 183 184void AppListView::UpdateBounds() { 185 SizeToContents(); 186} 187 188gfx::Size AppListView::GetPreferredSize() { 189 return app_list_main_view_->GetPreferredSize(); 190} 191 192void 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 200bool AppListView::ShouldHandleSystemCommands() const { 201 return true; 202} 203 204void AppListView::Prerender() { 205 app_list_main_view_->Prerender(); 206} 207 208void 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 218void AppListView::SetProfileByPath(const base::FilePath& profile_path) { 219 delegate_->SetProfileByPath(profile_path); 220 app_list_main_view_->ModelChanged(); 221} 222 223void AppListView::AddObserver(AppListViewObserver* observer) { 224 observers_.AddObserver(observer); 225} 226 227void AppListView::RemoveObserver(AppListViewObserver* observer) { 228 observers_.RemoveObserver(observer); 229} 230 231// static 232void AppListView::SetNextPaintCallback(void (*callback)()) { 233 g_next_paint_callback = callback; 234} 235 236#if defined(OS_WIN) 237HWND 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 248void 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 317void 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 331views::View* AppListView::GetInitiallyFocusedView() { 332 return app_list_main_view_->search_box_view()->search_box(); 333} 334 335gfx::ImageSkia AppListView::GetWindowIcon() { 336 if (delegate_) 337 return delegate_->GetWindowIcon(); 338 339 return gfx::ImageSkia(); 340} 341 342bool AppListView::WidgetHasHitTestMask() const { 343 return true; 344} 345 346void AppListView::GetWidgetHitTestMask(gfx::Path* mask) const { 347 DCHECK(mask); 348 mask->addRect(gfx::RectToSkRect( 349 GetBubbleFrameView()->GetContentsBounds())); 350} 351 352bool 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 367void 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 383void AppListView::OnWidgetDestroying(views::Widget* widget) { 384 BubbleDelegateView::OnWidgetDestroying(widget); 385 if (delegate_ && widget == GetWidget()) 386 delegate_->ViewClosing(); 387} 388 389void 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 398void 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 415void 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