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