- add sources.
[platform/framework/web/crosswalk.git] / src / ash / shelf / overflow_bubble.cc
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 "ash/shelf/overflow_bubble.h"
6
7 #include <algorithm>
8
9 #include "ash/launcher/launcher_types.h"
10 #include "ash/root_window_controller.h"
11 #include "ash/shelf/shelf_layout_manager.h"
12 #include "ash/shelf/shelf_view.h"
13 #include "ash/shelf/shelf_widget.h"
14 #include "ash/shell.h"
15 #include "ash/shell_window_ids.h"
16 #include "ash/system/tray/system_tray.h"
17 #include "ui/aura/client/screen_position_client.h"
18 #include "ui/aura/root_window.h"
19 #include "ui/events/event.h"
20 #include "ui/gfx/insets.h"
21 #include "ui/gfx/screen.h"
22 #include "ui/views/bubble/bubble_delegate.h"
23 #include "ui/views/bubble/bubble_frame_view.h"
24 #include "ui/views/widget/widget.h"
25
26 namespace ash {
27 namespace internal {
28
29 namespace {
30
31 // Max bubble size to screen size ratio.
32 const float kMaxBubbleSizeToScreenRatio = 0.5f;
33
34 // Inner padding in pixels for shelf view inside bubble.
35 const int kPadding = 2;
36
37 // Padding space in pixels between ShelfView's left/top edge to its contents.
38 const int kShelfViewLeadingInset = 8;
39
40 ////////////////////////////////////////////////////////////////////////////////
41 // OverflowBubbleView
42 // OverflowBubbleView hosts a ShelfView to display overflown items.
43
44 class OverflowBubbleView : public views::BubbleDelegateView {
45  public:
46   OverflowBubbleView();
47   virtual ~OverflowBubbleView();
48
49   void InitOverflowBubble(views::View* anchor, ShelfView* shelf_view);
50
51  private:
52   bool IsHorizontalAlignment() const {
53     return GetShelfLayoutManagerForLauncher()->IsHorizontalAlignment();
54   }
55
56   const gfx::Size GetContentsSize() const {
57     return static_cast<views::View*>(shelf_view_)->GetPreferredSize();
58   }
59
60   // Gets arrow location based on shelf alignment.
61   views::BubbleBorder::Arrow GetBubbleArrow() const {
62     return GetShelfLayoutManagerForLauncher()->SelectValueForShelfAlignment(
63         views::BubbleBorder::BOTTOM_LEFT,
64         views::BubbleBorder::LEFT_TOP,
65         views::BubbleBorder::RIGHT_TOP,
66         views::BubbleBorder::TOP_LEFT);
67   }
68
69   void ScrollByXOffset(int x_offset);
70   void ScrollByYOffset(int y_offset);
71
72   // views::View overrides:
73   virtual gfx::Size GetPreferredSize() OVERRIDE;
74   virtual void Layout() OVERRIDE;
75   virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
76   virtual bool OnMouseWheel(const ui::MouseWheelEvent& event) OVERRIDE;
77
78   // ui::EventHandler overrides:
79   virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE;
80
81   // views::BubbleDelegate overrides:
82   virtual gfx::Rect GetBubbleBounds() OVERRIDE;
83
84   ShelfLayoutManager* GetShelfLayoutManagerForLauncher() const {
85     return ShelfLayoutManager::ForLauncher(
86         GetAnchorView()->GetWidget()->GetNativeView());
87   }
88
89   ShelfView* shelf_view_;  // Owned by views hierarchy.
90   gfx::Vector2d scroll_offset_;
91
92   DISALLOW_COPY_AND_ASSIGN(OverflowBubbleView);
93 };
94
95 OverflowBubbleView::OverflowBubbleView()
96     : shelf_view_(NULL) {
97 }
98
99 OverflowBubbleView::~OverflowBubbleView() {
100 }
101
102 void OverflowBubbleView::InitOverflowBubble(views::View* anchor,
103                                             ShelfView* shelf_view) {
104   // set_anchor_view needs to be called before GetShelfLayoutManagerForLauncher
105   // can be called.
106   SetAnchorView(anchor);
107   set_arrow(GetBubbleArrow());
108   set_background(NULL);
109   set_color(SkColorSetARGB(kLauncherBackgroundAlpha, 0, 0, 0));
110   set_margins(gfx::Insets(kPadding, kPadding, kPadding, kPadding));
111   set_move_with_anchor(true);
112   // Overflow bubble should not get focus. If it get focus when it is shown,
113   // active state item is changed to running state.
114   set_use_focusless(true);
115
116   // Makes bubble view has a layer and clip its children layers.
117   SetPaintToLayer(true);
118   SetFillsBoundsOpaquely(false);
119   layer()->SetMasksToBounds(true);
120
121   shelf_view_ = shelf_view;
122   AddChildView(shelf_view_);
123
124   set_parent_window(Shell::GetContainer(
125         anchor->GetWidget()->GetNativeWindow()->GetRootWindow(),
126         internal::kShellWindowId_ShelfBubbleContainer));
127   views::BubbleDelegateView::CreateBubble(this);
128 }
129
130 void OverflowBubbleView::ScrollByXOffset(int x_offset) {
131   const gfx::Rect visible_bounds(GetContentsBounds());
132   const gfx::Size contents_size(GetContentsSize());
133
134   int x = std::min(contents_size.width() - visible_bounds.width(),
135                    std::max(0, scroll_offset_.x() + x_offset));
136   scroll_offset_.set_x(x);
137 }
138
139 void OverflowBubbleView::ScrollByYOffset(int y_offset) {
140   const gfx::Rect visible_bounds(GetContentsBounds());
141   const gfx::Size contents_size(GetContentsSize());
142
143   int y = std::min(contents_size.height() - visible_bounds.height(),
144                    std::max(0, scroll_offset_.y() + y_offset));
145   scroll_offset_.set_y(y);
146 }
147
148 gfx::Size OverflowBubbleView::GetPreferredSize() {
149   gfx::Size preferred_size = GetContentsSize();
150
151   const gfx::Rect monitor_rect = Shell::GetScreen()->GetDisplayNearestPoint(
152       GetAnchorRect().CenterPoint()).work_area();
153   if (!monitor_rect.IsEmpty()) {
154     if (IsHorizontalAlignment()) {
155       preferred_size.set_width(std::min(
156           preferred_size.width(),
157           static_cast<int>(monitor_rect.width() *
158                            kMaxBubbleSizeToScreenRatio)));
159     } else {
160       preferred_size.set_height(std::min(
161           preferred_size.height(),
162           static_cast<int>(monitor_rect.height() *
163                            kMaxBubbleSizeToScreenRatio)));
164     }
165   }
166
167   return preferred_size;
168 }
169
170 void OverflowBubbleView::Layout() {
171   shelf_view_->SetBoundsRect(gfx::Rect(
172       gfx::PointAtOffsetFromOrigin(-scroll_offset_), GetContentsSize()));
173 }
174
175 void OverflowBubbleView::ChildPreferredSizeChanged(views::View* child) {
176   // Ensures |launch_view_| is still visible.
177   ScrollByXOffset(0);
178   ScrollByYOffset(0);
179   Layout();
180
181   SizeToContents();
182 }
183
184 bool OverflowBubbleView::OnMouseWheel(const ui::MouseWheelEvent& event) {
185   // The MouseWheelEvent was changed to support both X and Y offsets
186   // recently, but the behavior of this function was retained to continue
187   // using Y offsets only. Might be good to simply scroll in both
188   // directions as in OverflowBubbleView::OnScrollEvent.
189   if (IsHorizontalAlignment())
190     ScrollByXOffset(-event.y_offset());
191   else
192     ScrollByYOffset(-event.y_offset());
193   Layout();
194
195   return true;
196 }
197
198 void OverflowBubbleView::OnScrollEvent(ui::ScrollEvent* event) {
199   ScrollByXOffset(-event->x_offset());
200   ScrollByYOffset(-event->y_offset());
201   Layout();
202   event->SetHandled();
203 }
204
205 gfx::Rect OverflowBubbleView::GetBubbleBounds() {
206   views::BubbleBorder* border = GetBubbleFrameView()->bubble_border();
207   gfx::Insets bubble_insets = border->GetInsets();
208
209   const int border_size =
210       views::BubbleBorder::is_arrow_on_horizontal(arrow()) ?
211       bubble_insets.left() : bubble_insets.top();
212   const int arrow_offset = border_size + kPadding + kShelfViewLeadingInset +
213       ShelfLayoutManager::GetPreferredShelfSize() / 2;
214
215   const gfx::Size content_size = GetPreferredSize();
216   border->set_arrow_offset(arrow_offset);
217
218   const gfx::Rect anchor_rect = GetAnchorRect();
219   gfx::Rect bubble_rect = GetBubbleFrameView()->GetUpdatedWindowBounds(
220       anchor_rect,
221       content_size,
222       false);
223
224   gfx::Rect monitor_rect = Shell::GetScreen()->GetDisplayNearestPoint(
225       anchor_rect.CenterPoint()).work_area();
226
227   int offset = 0;
228   if (views::BubbleBorder::is_arrow_on_horizontal(arrow())) {
229     if (bubble_rect.x() < monitor_rect.x())
230       offset = monitor_rect.x() - bubble_rect.x();
231     else if (bubble_rect.right() > monitor_rect.right())
232       offset = monitor_rect.right() - bubble_rect.right();
233
234     bubble_rect.Offset(offset, 0);
235     border->set_arrow_offset(anchor_rect.CenterPoint().x() - bubble_rect.x());
236   } else {
237     if (bubble_rect.y() < monitor_rect.y())
238       offset = monitor_rect.y() - bubble_rect.y();
239     else if (bubble_rect.bottom() > monitor_rect.bottom())
240       offset =  monitor_rect.bottom() - bubble_rect.bottom();
241
242     bubble_rect.Offset(0, offset);
243     border->set_arrow_offset(anchor_rect.CenterPoint().y() - bubble_rect.y());
244   }
245
246   GetBubbleFrameView()->SchedulePaint();
247   return bubble_rect;
248 }
249
250 }  // namespace
251
252 OverflowBubble::OverflowBubble()
253     : bubble_(NULL),
254       anchor_(NULL),
255       shelf_view_(NULL) {
256 }
257
258 OverflowBubble::~OverflowBubble() {
259   Hide();
260 }
261
262 void OverflowBubble::Show(views::View* anchor, ShelfView* shelf_view) {
263   Hide();
264
265   OverflowBubbleView* bubble_view = new OverflowBubbleView();
266   bubble_view->InitOverflowBubble(anchor, shelf_view);
267   shelf_view_ = shelf_view;
268   anchor_ = anchor;
269
270   Shell::GetInstance()->AddPreTargetHandler(this);
271
272   bubble_ = bubble_view;
273   RootWindowController::ForWindow(anchor->GetWidget()->GetNativeView())->
274       GetSystemTray()->InitializeBubbleAnimations(bubble_->GetWidget());
275   bubble_->GetWidget()->AddObserver(this);
276   bubble_->GetWidget()->Show();
277 }
278
279 void OverflowBubble::Hide() {
280   if (!IsShowing())
281     return;
282
283   Shell::GetInstance()->RemovePreTargetHandler(this);
284   bubble_->GetWidget()->RemoveObserver(this);
285   bubble_->GetWidget()->Close();
286   bubble_ = NULL;
287   anchor_ = NULL;
288   shelf_view_ = NULL;
289 }
290
291 void OverflowBubble::HideBubbleAndRefreshButton() {
292   if (!IsShowing())
293     return;
294
295   views::View* anchor = anchor_;
296   Hide();
297   // Update overflow button (|anchor|) status when overflow bubble is hidden
298   // by outside event of overflow button.
299   anchor->SchedulePaint();
300 }
301
302 void OverflowBubble::ProcessPressedEvent(ui::LocatedEvent* event) {
303   aura::Window* target = static_cast<aura::Window*>(event->target());
304   gfx::Point event_location_in_screen = event->location();
305   aura::client::GetScreenPositionClient(target->GetRootWindow())->
306       ConvertPointToScreen(target, &event_location_in_screen);
307   if (!shelf_view_->IsShowingMenu() &&
308       !bubble_->GetBoundsInScreen().Contains(event_location_in_screen) &&
309       !anchor_->GetBoundsInScreen().Contains(event_location_in_screen)) {
310     HideBubbleAndRefreshButton();
311   }
312 }
313
314 void OverflowBubble::OnMouseEvent(ui::MouseEvent* event) {
315   if (event->type() == ui::ET_MOUSE_PRESSED)
316     ProcessPressedEvent(event);
317 }
318
319 void OverflowBubble::OnTouchEvent(ui::TouchEvent* event) {
320   if (event->type() == ui::ET_TOUCH_PRESSED)
321     ProcessPressedEvent(event);
322 }
323
324 void OverflowBubble::OnWidgetDestroying(views::Widget* widget) {
325   DCHECK(widget == bubble_->GetWidget());
326   bubble_ = NULL;
327   anchor_ = NULL;
328   shelf_view_ = NULL;
329   ShelfLayoutManager::ForLauncher(
330       widget->GetNativeView())->shelf_widget()->launcher()->SchedulePaint();
331 }
332
333 }  // namespace internal
334 }  // namespace ash