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
55namespace app_list {
56
57namespace {
58
59// The margin from the edge to the speech UI.
60const int kSpeechUIMargin = 12;
61
62// The vertical position for the appearing animation of the speech UI.
63const float kSpeechUIAppearingPosition = 12;
64
65// The distance between the arrow tip and edge of the anchor view.
66const int kArrowOffset = 10;
67
68// Determines whether the current environment supports shadows bubble borders.
69bool 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.
86class 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.
109class 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
151AppListView::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
164AppListView::~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
172void 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
184void 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
196void AppListView::SetBubbleArrow(views::BubbleBorder::Arrow arrow) {
197  GetBubbleFrameView()->bubble_border()->set_arrow(arrow);
198  SizeToContents();  // Recalcuates with new border.
199  GetBubbleFrameView()->SchedulePaint();
200}
201
202void AppListView::SetAnchorPoint(const gfx::Point& anchor_point) {
203  SetAnchorRect(gfx::Rect(anchor_point, gfx::Size()));
204}
205
206void AppListView::SetDragAndDropHostOfCurrentAppList(
207    ApplicationDragAndDropHost* drag_and_drop_host) {
208  app_list_main_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
209}
210
211void AppListView::ShowWhenReady() {
212  app_list_main_view_->ShowAppListWhenReady();
213}
214
215void AppListView::Close() {
216  app_list_main_view_->Close();
217  delegate_->Dismiss();
218}
219
220void AppListView::UpdateBounds() {
221  SizeToContents();
222}
223
224void 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
251bool AppListView::ShouldCenterWindow() const {
252  return delegate_->ShouldCenterWindow();
253}
254
255gfx::Size AppListView::GetPreferredSize() const {
256  return app_list_main_view_->GetPreferredSize();
257}
258
259void 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
267void AppListView::OnThemeChanged() {
268#if defined(OS_WIN)
269  GetWidget()->Close();
270#endif
271}
272
273bool AppListView::ShouldHandleSystemCommands() const {
274  return true;
275}
276
277void AppListView::Prerender() {
278  app_list_main_view_->Prerender();
279}
280
281void AppListView::OnProfilesChanged() {
282  app_list_main_view_->search_box_view()->InvalidateMenu();
283}
284
285void AppListView::OnShutdown() {
286  // Nothing to do on views - the widget will soon be closed, which will tear
287  // everything down.
288}
289
290void AppListView::SetProfileByPath(const base::FilePath& profile_path) {
291  delegate_->SetProfileByPath(profile_path);
292  app_list_main_view_->ModelChanged();
293}
294
295void AppListView::AddObserver(AppListViewObserver* observer) {
296  observers_.AddObserver(observer);
297}
298
299void AppListView::RemoveObserver(AppListViewObserver* observer) {
300  observers_.RemoveObserver(observer);
301}
302
303// static
304void AppListView::SetNextPaintCallback(const base::Closure& callback) {
305  next_paint_callback_ = callback;
306}
307
308#if defined(OS_WIN)
309HWND AppListView::GetHWND() const {
310  gfx::NativeWindow window =
311      GetWidget()->GetTopLevelWidget()->GetNativeWindow();
312  return window->GetHost()->GetAcceleratedWidget();
313}
314#endif
315
316PaginationModel* AppListView::GetAppsPaginationModel() {
317  return app_list_main_view_->contents_view()
318      ->apps_container_view()
319      ->apps_grid_view()
320      ->pagination_model();
321}
322
323void 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
423void 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
446views::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
455gfx::ImageSkia AppListView::GetWindowIcon() {
456  if (delegate_)
457    return delegate_->GetWindowIcon();
458
459  return gfx::ImageSkia();
460}
461
462bool AppListView::WidgetHasHitTestMask() const {
463  return true;
464}
465
466void AppListView::GetWidgetHitTestMask(gfx::Path* mask) const {
467  DCHECK(mask);
468  mask->addRect(gfx::RectToSkRect(
469      GetBubbleFrameView()->GetContentsBounds()));
470}
471
472bool 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
495void 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
519void AppListView::SchedulePaintInRect(const gfx::Rect& rect) {
520  BubbleDelegateView::SchedulePaintInRect(rect);
521  if (GetBubbleFrameView())
522    GetBubbleFrameView()->SchedulePaint();
523}
524
525void AppListView::OnWidgetDestroying(views::Widget* widget) {
526  BubbleDelegateView::OnWidgetDestroying(widget);
527  if (delegate_ && widget == GetWidget())
528    delegate_->ViewClosing();
529}
530
531void 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
540void 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
551void 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