app_list_linux.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
1// Copyright 2013 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 "chrome/browser/ui/views/app_list/linux/app_list_linux.h"
6
7#include "base/command_line.h"
8#include "base/location.h"
9#include "base/single_thread_task_runner.h"
10#include "base/thread_task_runner_handle.h"
11#include "chrome/browser/profiles/profile.h"
12#include "chrome/browser/ui/app_list/app_list_positioner.h"
13#include "ui/app_list/app_list_switches.h"
14#include "ui/app_list/views/app_list_view.h"
15#include "ui/gfx/screen.h"
16#include "ui/views/linux_ui/linux_ui.h"
17#include "ui/views/widget/widget.h"
18
19AppListLinux::AppListLinux(app_list::AppListView* view,
20                           const base::Closure& on_should_dismiss)
21    : view_(view),
22      window_icon_updated_(false),
23      on_should_dismiss_(on_should_dismiss) {
24  view_->AddObserver(this);
25}
26
27AppListLinux::~AppListLinux() {
28  view_->RemoveObserver(this);
29}
30
31// static
32AppListPositioner::ScreenEdge AppListLinux::ShelfLocationInDisplay(
33    const gfx::Display& display) {
34  // On Linux, it is difficult to find the shelf (due to the large variety of
35  // desktop environments). The shelf can usually be found on the edge where the
36  // display edge and work area do not match up, but there can be more than one
37  // such edge. The shelf is assumed to be on the side of the screen with the
38  // largest delta between the display edge and the work area edge. Ties are
39  // broken in the order: top, left, right, bottom.
40  const gfx::Rect work_area = display.work_area();
41  const gfx::Rect display_bounds = display.bounds();
42
43  int winning_margin = 0;
44  AppListPositioner::ScreenEdge winning_edge =
45      AppListPositioner::SCREEN_EDGE_UNKNOWN;
46
47  if (work_area.y() - display_bounds.y() > winning_margin) {
48    winning_margin = work_area.y() - display_bounds.y();
49    winning_edge = AppListPositioner::SCREEN_EDGE_TOP;
50  }
51
52  if (work_area.x() - display_bounds.x() > winning_margin) {
53    winning_margin = work_area.x() - display_bounds.x();
54    winning_edge = AppListPositioner::SCREEN_EDGE_LEFT;
55  }
56
57  if (display_bounds.right() - work_area.right() > winning_margin) {
58    winning_margin = display_bounds.right() - work_area.right();
59    winning_edge = AppListPositioner::SCREEN_EDGE_RIGHT;
60  }
61
62  if (display_bounds.bottom() - work_area.bottom() > winning_margin) {
63    winning_margin = display_bounds.bottom() - work_area.bottom();
64    winning_edge = AppListPositioner::SCREEN_EDGE_BOTTOM;
65  }
66
67  return winning_edge;
68}
69
70// static
71gfx::Point AppListLinux::FindAnchorPoint(const gfx::Size& view_size,
72                                         const gfx::Display& display,
73                                         const gfx::Point& cursor,
74                                         AppListPositioner::ScreenEdge edge) {
75  AppListPositioner positioner(display, view_size, 0);
76
77  // The experimental app list is placed in the center of the screen.
78  if (app_list::switches::IsExperimentalAppListPositionEnabled())
79    return positioner.GetAnchorPointForScreenCenter();
80
81  gfx::Point anchor;
82  // Snap to the shelf edge. If the cursor is greater than the window
83  // width/height away, anchor to the corner. Otherwise, anchor to the cursor
84  // position.
85  if (edge == AppListPositioner::SCREEN_EDGE_UNKNOWN) {
86    // If we can't find the shelf, snap to the top left.
87    return positioner.GetAnchorPointForScreenCorner(
88        AppListPositioner::SCREEN_CORNER_TOP_LEFT);
89  }
90
91  int snap_distance = edge == AppListPositioner::SCREEN_EDGE_BOTTOM ||
92                              edge == AppListPositioner::SCREEN_EDGE_TOP
93                          ? view_size.height()
94                          : view_size.width();
95  if (positioner.GetCursorDistanceFromShelf(edge, cursor) > snap_distance)
96    return positioner.GetAnchorPointForShelfCorner(edge);
97
98  return positioner.GetAnchorPointForShelfCursor(edge, cursor);
99}
100
101void AppListLinux::Show() {
102  view_->GetWidget()->Show();
103  if (!window_icon_updated_) {
104    view_->GetWidget()->GetTopLevelWidget()->UpdateWindowIcon();
105    window_icon_updated_ = true;
106  }
107  view_->GetWidget()->Activate();
108}
109
110void AppListLinux::Hide() {
111  view_->GetWidget()->Hide();
112}
113
114void AppListLinux::MoveNearCursor() {
115  gfx::Point cursor = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint();
116  gfx::Screen* screen =
117      gfx::Screen::GetScreenFor(view_->GetWidget()->GetNativeView());
118  gfx::Display display = screen->GetDisplayNearestPoint(cursor);
119
120  view_->SetBubbleArrow(views::BubbleBorder::FLOAT);
121
122  // In the Unity desktop environment, special case SCREEN_EDGE_LEFT. It is
123  // always on the left side in Unity, but ShelfLocationInDisplay will not
124  // detect this if the shelf is hidden.
125  // TODO(mgiuca): Apply this special case in Gnome Shell also. The same logic
126  // applies, but we currently have no way to detect whether Gnome Shell is
127  // running.
128  views::LinuxUI* ui = views::LinuxUI::instance();
129  AppListPositioner::ScreenEdge edge;
130  if (ui && ui->UnityIsRunning())
131    edge = AppListPositioner::SCREEN_EDGE_LEFT;
132  else
133    edge = ShelfLocationInDisplay(display);
134  view_->SetAnchorPoint(
135      FindAnchorPoint(view_->GetPreferredSize(), display, cursor, edge));
136}
137
138bool AppListLinux::IsVisible() {
139  return view_->GetWidget()->IsVisible();
140}
141
142void AppListLinux::Prerender() {
143  view_->Prerender();
144}
145
146gfx::NativeWindow AppListLinux::GetWindow() {
147  return view_->GetWidget()->GetNativeWindow();
148}
149
150void AppListLinux::SetProfile(Profile* profile) {
151  view_->SetProfileByPath(profile->GetPath());
152}
153
154void AppListLinux::OnActivationChanged(
155    views::Widget* /*widget*/, bool active) {
156  if (active)
157    return;
158
159  // Call |on_should_dismiss_| asynchronously. This must be done asynchronously
160  // or our caller will crash, as it expects the app list to remain alive.
161  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, on_should_dismiss_);
162}
163