Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / ui / views / controls / scroll_view.cc
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/views/controls/scroll_view.h"
6
7 #include "base/logging.h"
8 #include "ui/events/event.h"
9 #include "ui/gfx/canvas.h"
10 #include "ui/native_theme/native_theme.h"
11 #include "ui/views/border.h"
12 #include "ui/views/controls/scrollbar/native_scroll_bar.h"
13 #include "ui/views/widget/root_view.h"
14
15 namespace views {
16
17 const char ScrollView::kViewClassName[] = "ScrollView";
18
19 namespace {
20
21 // Subclass of ScrollView that resets the border when the theme changes.
22 class ScrollViewWithBorder : public views::ScrollView {
23  public:
24   ScrollViewWithBorder() {}
25
26   // View overrides;
27   virtual void OnNativeThemeChanged(const ui::NativeTheme* theme) OVERRIDE {
28     SetBorder(Border::CreateSolidBorder(
29         1,
30         theme->GetSystemColor(ui::NativeTheme::kColorId_UnfocusedBorderColor)));
31   }
32
33  private:
34   DISALLOW_COPY_AND_ASSIGN(ScrollViewWithBorder);
35 };
36
37 class ScrollCornerView : public views::View {
38  public:
39   ScrollCornerView() {}
40
41   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
42     ui::NativeTheme::ExtraParams ignored;
43     GetNativeTheme()->Paint(canvas->sk_canvas(),
44                             ui::NativeTheme::kScrollbarCorner,
45                             ui::NativeTheme::kNormal,
46                             GetLocalBounds(),
47                             ignored);
48   }
49
50  private:
51   DISALLOW_COPY_AND_ASSIGN(ScrollCornerView);
52 };
53
54 // Returns the position for the view so that it isn't scrolled off the visible
55 // region.
56 int CheckScrollBounds(int viewport_size, int content_size, int current_pos) {
57   int max = std::max(content_size - viewport_size, 0);
58   if (current_pos < 0)
59     return 0;
60   if (current_pos > max)
61     return max;
62   return current_pos;
63 }
64
65 // Make sure the content is not scrolled out of bounds
66 void CheckScrollBounds(View* viewport, View* view) {
67   if (!view)
68     return;
69
70   int x = CheckScrollBounds(viewport->width(), view->width(), -view->x());
71   int y = CheckScrollBounds(viewport->height(), view->height(), -view->y());
72
73   // This is no op if bounds are the same
74   view->SetBounds(-x, -y, view->width(), view->height());
75 }
76
77 // Used by ScrollToPosition() to make sure the new position fits within the
78 // allowed scroll range.
79 int AdjustPosition(int current_position,
80                    int new_position,
81                    int content_size,
82                    int viewport_size) {
83   if (-current_position == new_position)
84     return new_position;
85   if (new_position < 0)
86     return 0;
87   const int max_position = std::max(0, content_size - viewport_size);
88   return (new_position > max_position) ? max_position : new_position;
89 }
90
91 }  // namespace
92
93 // Viewport contains the contents View of the ScrollView.
94 class ScrollView::Viewport : public View {
95  public:
96   Viewport() {}
97   virtual ~Viewport() {}
98
99   virtual const char* GetClassName() const OVERRIDE {
100     return "ScrollView::Viewport";
101   }
102
103   virtual void ScrollRectToVisible(const gfx::Rect& rect) OVERRIDE {
104     if (!has_children() || !parent())
105       return;
106
107     View* contents = child_at(0);
108     gfx::Rect scroll_rect(rect);
109     scroll_rect.Offset(-contents->x(), -contents->y());
110     static_cast<ScrollView*>(parent())->ScrollContentsRegionToBeVisible(
111         scroll_rect);
112   }
113
114   virtual void ChildPreferredSizeChanged(View* child) OVERRIDE {
115     if (parent())
116       parent()->Layout();
117   }
118
119  private:
120   DISALLOW_COPY_AND_ASSIGN(Viewport);
121 };
122
123 ScrollView::ScrollView()
124     : contents_(NULL),
125       contents_viewport_(new Viewport()),
126       header_(NULL),
127       header_viewport_(new Viewport()),
128       horiz_sb_(new NativeScrollBar(true)),
129       vert_sb_(new NativeScrollBar(false)),
130       corner_view_(new ScrollCornerView()),
131       min_height_(-1),
132       max_height_(-1),
133       hide_horizontal_scrollbar_(false) {
134   set_notify_enter_exit_on_child(true);
135
136   AddChildView(contents_viewport_);
137   AddChildView(header_viewport_);
138
139   // Don't add the scrollbars as children until we discover we need them
140   // (ShowOrHideScrollBar).
141   horiz_sb_->SetVisible(false);
142   horiz_sb_->set_controller(this);
143   vert_sb_->SetVisible(false);
144   vert_sb_->set_controller(this);
145   corner_view_->SetVisible(false);
146 }
147
148 ScrollView::~ScrollView() {
149   // The scrollbars may not have been added, delete them to ensure they get
150   // deleted.
151   delete horiz_sb_;
152   delete vert_sb_;
153   delete corner_view_;
154 }
155
156 // static
157 ScrollView* ScrollView::CreateScrollViewWithBorder() {
158   return new ScrollViewWithBorder();
159 }
160
161 void ScrollView::SetContents(View* a_view) {
162   SetHeaderOrContents(contents_viewport_, a_view, &contents_);
163 }
164
165 void ScrollView::SetHeader(View* header) {
166   SetHeaderOrContents(header_viewport_, header, &header_);
167 }
168
169 gfx::Rect ScrollView::GetVisibleRect() const {
170   if (!contents_)
171     return gfx::Rect();
172   return gfx::Rect(-contents_->x(), -contents_->y(),
173                    contents_viewport_->width(), contents_viewport_->height());
174 }
175
176 void ScrollView::ClipHeightTo(int min_height, int max_height) {
177   min_height_ = min_height;
178   max_height_ = max_height;
179 }
180
181 int ScrollView::GetScrollBarWidth() const {
182   return vert_sb_ ? vert_sb_->GetLayoutSize() : 0;
183 }
184
185 int ScrollView::GetScrollBarHeight() const {
186   return horiz_sb_ ? horiz_sb_->GetLayoutSize() : 0;
187 }
188
189 void ScrollView::SetHorizontalScrollBar(ScrollBar* horiz_sb) {
190   DCHECK(horiz_sb);
191   horiz_sb->SetVisible(horiz_sb_->visible());
192   delete horiz_sb_;
193   horiz_sb->set_controller(this);
194   horiz_sb_ = horiz_sb;
195 }
196
197 void ScrollView::SetVerticalScrollBar(ScrollBar* vert_sb) {
198   DCHECK(vert_sb);
199   vert_sb->SetVisible(vert_sb_->visible());
200   delete vert_sb_;
201   vert_sb->set_controller(this);
202   vert_sb_ = vert_sb;
203 }
204
205 gfx::Size ScrollView::GetPreferredSize() const {
206   if (!is_bounded())
207     return View::GetPreferredSize();
208
209   gfx::Size size = contents()->GetPreferredSize();
210   size.SetToMax(gfx::Size(size.width(), min_height_));
211   size.SetToMin(gfx::Size(size.width(), max_height_));
212   gfx::Insets insets = GetInsets();
213   size.Enlarge(insets.width(), insets.height());
214   return size;
215 }
216
217 int ScrollView::GetHeightForWidth(int width) const {
218   if (!is_bounded())
219     return View::GetHeightForWidth(width);
220
221   gfx::Insets insets = GetInsets();
222   width = std::max(0, width - insets.width());
223   int height = contents()->GetHeightForWidth(width) + insets.height();
224   return std::min(std::max(height, min_height_), max_height_);
225 }
226
227 void ScrollView::Layout() {
228   if (is_bounded()) {
229     int content_width = width();
230     int content_height = contents()->GetHeightForWidth(content_width);
231     if (content_height > height()) {
232       content_width = std::max(content_width - GetScrollBarWidth(), 0);
233       content_height = contents()->GetHeightForWidth(content_width);
234     }
235     if (contents()->bounds().size() != gfx::Size(content_width, content_height))
236       contents()->SetBounds(0, 0, content_width, content_height);
237   }
238
239   // Most views will want to auto-fit the available space. Most of them want to
240   // use all available width (without overflowing) and only overflow in
241   // height. Examples are HistoryView, MostVisitedView, DownloadTabView, etc.
242   // Other views want to fit in both ways. An example is PrintView. To make both
243   // happy, assume a vertical scrollbar but no horizontal scrollbar. To override
244   // this default behavior, the inner view has to calculate the available space,
245   // used ComputeScrollBarsVisibility() to use the same calculation that is done
246   // here and sets its bound to fit within.
247   gfx::Rect viewport_bounds = GetContentsBounds();
248   const int contents_x = viewport_bounds.x();
249   const int contents_y = viewport_bounds.y();
250   if (viewport_bounds.IsEmpty()) {
251     // There's nothing to layout.
252     return;
253   }
254
255   const int header_height =
256       std::min(viewport_bounds.height(),
257                header_ ? header_->GetPreferredSize().height() : 0);
258   viewport_bounds.set_height(
259       std::max(0, viewport_bounds.height() - header_height));
260   viewport_bounds.set_y(viewport_bounds.y() + header_height);
261   // viewport_size is the total client space available.
262   gfx::Size viewport_size = viewport_bounds.size();
263   // Assumes a vertical scrollbar since most of the current views are designed
264   // for this.
265   int horiz_sb_height = GetScrollBarHeight();
266   int vert_sb_width = GetScrollBarWidth();
267   viewport_bounds.set_width(viewport_bounds.width() - vert_sb_width);
268   // Update the bounds right now so the inner views can fit in it.
269   contents_viewport_->SetBoundsRect(viewport_bounds);
270
271   // Give |contents_| a chance to update its bounds if it depends on the
272   // viewport.
273   if (contents_)
274     contents_->Layout();
275
276   bool should_layout_contents = false;
277   bool horiz_sb_required = false;
278   bool vert_sb_required = false;
279   if (contents_) {
280     gfx::Size content_size = contents_->size();
281     ComputeScrollBarsVisibility(viewport_size,
282                                 content_size,
283                                 &horiz_sb_required,
284                                 &vert_sb_required);
285   }
286   bool corner_view_required = horiz_sb_required && vert_sb_required;
287   // Take action.
288   SetControlVisibility(horiz_sb_, horiz_sb_required);
289   SetControlVisibility(vert_sb_, vert_sb_required);
290   SetControlVisibility(corner_view_, corner_view_required);
291
292   // Non-default.
293   if (horiz_sb_required) {
294     viewport_bounds.set_height(
295         std::max(0, viewport_bounds.height() - horiz_sb_height));
296     should_layout_contents = true;
297   }
298   // Default.
299   if (!vert_sb_required) {
300     viewport_bounds.set_width(viewport_bounds.width() + vert_sb_width);
301     should_layout_contents = true;
302   }
303
304   if (horiz_sb_required) {
305     int height_offset = horiz_sb_->GetContentOverlapSize();
306     horiz_sb_->SetBounds(0,
307                          viewport_bounds.bottom() - height_offset,
308                          viewport_bounds.right(),
309                          horiz_sb_height + height_offset);
310   }
311   if (vert_sb_required) {
312     int width_offset = vert_sb_->GetContentOverlapSize();
313     vert_sb_->SetBounds(viewport_bounds.right() - width_offset,
314                         0,
315                         vert_sb_width + width_offset,
316                         viewport_bounds.bottom());
317   }
318   if (corner_view_required) {
319     // Show the resize corner.
320     corner_view_->SetBounds(viewport_bounds.right(),
321                             viewport_bounds.bottom(),
322                             vert_sb_width,
323                             horiz_sb_height);
324   }
325
326   // Update to the real client size with the visible scrollbars.
327   contents_viewport_->SetBoundsRect(viewport_bounds);
328   if (should_layout_contents && contents_)
329     contents_->Layout();
330
331   header_viewport_->SetBounds(contents_x, contents_y,
332                               viewport_bounds.width(), header_height);
333   if (header_)
334     header_->Layout();
335
336   CheckScrollBounds(header_viewport_, header_);
337   CheckScrollBounds(contents_viewport_, contents_);
338   SchedulePaint();
339   UpdateScrollBarPositions();
340 }
341
342 bool ScrollView::OnKeyPressed(const ui::KeyEvent& event) {
343   bool processed = false;
344
345   // Give vertical scrollbar priority
346   if (vert_sb_->visible())
347     processed = vert_sb_->OnKeyPressed(event);
348
349   if (!processed && horiz_sb_->visible())
350     processed = horiz_sb_->OnKeyPressed(event);
351
352   return processed;
353 }
354
355 bool ScrollView::OnMouseWheel(const ui::MouseWheelEvent& e) {
356   bool processed = false;
357   // Give vertical scrollbar priority
358   if (vert_sb_->visible())
359     processed = vert_sb_->OnMouseWheel(e);
360
361   if (!processed && horiz_sb_->visible())
362     processed = horiz_sb_->OnMouseWheel(e);
363
364   return processed;
365 }
366
367 void ScrollView::OnMouseEntered(const ui::MouseEvent& event) {
368   if (horiz_sb_)
369     horiz_sb_->OnMouseEnteredScrollView(event);
370   if (vert_sb_)
371     vert_sb_->OnMouseEnteredScrollView(event);
372 }
373
374 void ScrollView::OnMouseExited(const ui::MouseEvent& event) {
375   if (horiz_sb_)
376     horiz_sb_->OnMouseExitedScrollView(event);
377   if (vert_sb_)
378     vert_sb_->OnMouseExitedScrollView(event);
379 }
380
381 void ScrollView::OnGestureEvent(ui::GestureEvent* event) {
382   // If the event happened on one of the scrollbars, then those events are
383   // sent directly to the scrollbars. Otherwise, only scroll events are sent to
384   // the scrollbars.
385   bool scroll_event = event->type() == ui::ET_GESTURE_SCROLL_UPDATE ||
386                       event->type() == ui::ET_GESTURE_SCROLL_BEGIN ||
387                       event->type() == ui::ET_GESTURE_SCROLL_END ||
388                       event->type() == ui::ET_SCROLL_FLING_START;
389
390   if (vert_sb_->visible()) {
391     if (vert_sb_->bounds().Contains(event->location()) || scroll_event)
392       vert_sb_->OnGestureEvent(event);
393   }
394   if (!event->handled() && horiz_sb_->visible()) {
395     if (horiz_sb_->bounds().Contains(event->location()) || scroll_event)
396       horiz_sb_->OnGestureEvent(event);
397   }
398 }
399
400 const char* ScrollView::GetClassName() const {
401   return kViewClassName;
402 }
403
404 void ScrollView::ScrollToPosition(ScrollBar* source, int position) {
405   if (!contents_)
406     return;
407
408   if (source == horiz_sb_ && horiz_sb_->visible()) {
409     position = AdjustPosition(contents_->x(), position, contents_->width(),
410                               contents_viewport_->width());
411     if (-contents_->x() == position)
412       return;
413     contents_->SetX(-position);
414     if (header_) {
415       header_->SetX(-position);
416       header_->SchedulePaintInRect(header_->GetVisibleBounds());
417     }
418   } else if (source == vert_sb_ && vert_sb_->visible()) {
419     position = AdjustPosition(contents_->y(), position, contents_->height(),
420                               contents_viewport_->height());
421     if (-contents_->y() == position)
422       return;
423     contents_->SetY(-position);
424   }
425   contents_->SchedulePaintInRect(contents_->GetVisibleBounds());
426 }
427
428 int ScrollView::GetScrollIncrement(ScrollBar* source, bool is_page,
429                                    bool is_positive) {
430   bool is_horizontal = source->IsHorizontal();
431   int amount = 0;
432   if (contents_) {
433     if (is_page) {
434       amount = contents_->GetPageScrollIncrement(
435           this, is_horizontal, is_positive);
436     } else {
437       amount = contents_->GetLineScrollIncrement(
438           this, is_horizontal, is_positive);
439     }
440     if (amount > 0)
441       return amount;
442   }
443   // No view, or the view didn't return a valid amount.
444   if (is_page) {
445     return is_horizontal ? contents_viewport_->width() :
446                            contents_viewport_->height();
447   }
448   return is_horizontal ? contents_viewport_->width() / 5 :
449                          contents_viewport_->height() / 5;
450 }
451
452 void ScrollView::SetHeaderOrContents(View* parent,
453                                      View* new_view,
454                                      View** member) {
455   if (*member == new_view)
456     return;
457
458   delete *member;
459   *member = new_view;
460   if (*member)
461     parent->AddChildView(*member);
462   Layout();
463 }
464
465 void ScrollView::ScrollContentsRegionToBeVisible(const gfx::Rect& rect) {
466   if (!contents_ || (!horiz_sb_->visible() && !vert_sb_->visible()))
467     return;
468
469   // Figure out the maximums for this scroll view.
470   const int contents_max_x =
471       std::max(contents_viewport_->width(), contents_->width());
472   const int contents_max_y =
473       std::max(contents_viewport_->height(), contents_->height());
474
475   // Make sure x and y are within the bounds of [0,contents_max_*].
476   int x = std::max(0, std::min(contents_max_x, rect.x()));
477   int y = std::max(0, std::min(contents_max_y, rect.y()));
478
479   // Figure out how far and down the rectangle will go taking width
480   // and height into account.  This will be "clipped" by the viewport.
481   const int max_x = std::min(contents_max_x,
482       x + std::min(rect.width(), contents_viewport_->width()));
483   const int max_y = std::min(contents_max_y,
484       y + std::min(rect.height(), contents_viewport_->height()));
485
486   // See if the rect is already visible. Note the width is (max_x - x)
487   // and the height is (max_y - y) to take into account the clipping of
488   // either viewport or the content size.
489   const gfx::Rect vis_rect = GetVisibleRect();
490   if (vis_rect.Contains(gfx::Rect(x, y, max_x - x, max_y - y)))
491     return;
492
493   // Shift contents_'s X and Y so that the region is visible. If we
494   // need to shift up or left from where we currently are then we need
495   // to get it so that the content appears in the upper/left
496   // corner. This is done by setting the offset to -X or -Y.  For down
497   // or right shifts we need to make sure it appears in the
498   // lower/right corner. This is calculated by taking max_x or max_y
499   // and scaling it back by the size of the viewport.
500   const int new_x =
501       (vis_rect.x() > x) ? x : std::max(0, max_x - contents_viewport_->width());
502   const int new_y =
503       (vis_rect.y() > y) ? y : std::max(0, max_y -
504                                         contents_viewport_->height());
505
506   contents_->SetX(-new_x);
507   if (header_)
508     header_->SetX(-new_x);
509   contents_->SetY(-new_y);
510   UpdateScrollBarPositions();
511 }
512
513 void ScrollView::ComputeScrollBarsVisibility(const gfx::Size& vp_size,
514                                              const gfx::Size& content_size,
515                                              bool* horiz_is_shown,
516                                              bool* vert_is_shown) const {
517   // Try to fit both ways first, then try vertical bar only, then horizontal
518   // bar only, then defaults to both shown.
519   if (content_size.width() <= vp_size.width() &&
520       content_size.height() <= vp_size.height()) {
521     *horiz_is_shown = false;
522     *vert_is_shown = false;
523   } else if (content_size.width() <= vp_size.width() - GetScrollBarWidth()) {
524     *horiz_is_shown = false;
525     *vert_is_shown = true;
526   } else if (content_size.height() <= vp_size.height() - GetScrollBarHeight()) {
527     *horiz_is_shown = true;
528     *vert_is_shown = false;
529   } else {
530     *horiz_is_shown = true;
531     *vert_is_shown = true;
532   }
533
534   if (hide_horizontal_scrollbar_)
535     *horiz_is_shown = false;
536 }
537
538 // Make sure that a single scrollbar is created and visible as needed
539 void ScrollView::SetControlVisibility(View* control, bool should_show) {
540   if (!control)
541     return;
542   if (should_show) {
543     if (!control->visible()) {
544       AddChildView(control);
545       control->SetVisible(true);
546     }
547   } else {
548     RemoveChildView(control);
549     control->SetVisible(false);
550   }
551 }
552
553 void ScrollView::UpdateScrollBarPositions() {
554   if (!contents_)
555     return;
556
557   if (horiz_sb_->visible()) {
558     int vw = contents_viewport_->width();
559     int cw = contents_->width();
560     int origin = contents_->x();
561     horiz_sb_->Update(vw, cw, -origin);
562   }
563   if (vert_sb_->visible()) {
564     int vh = contents_viewport_->height();
565     int ch = contents_->height();
566     int origin = contents_->y();
567     vert_sb_->Update(vh, ch, -origin);
568   }
569 }
570
571 // VariableRowHeightScrollHelper ----------------------------------------------
572
573 VariableRowHeightScrollHelper::VariableRowHeightScrollHelper(
574     Controller* controller) : controller_(controller) {
575 }
576
577 VariableRowHeightScrollHelper::~VariableRowHeightScrollHelper() {
578 }
579
580 int VariableRowHeightScrollHelper::GetPageScrollIncrement(
581     ScrollView* scroll_view, bool is_horizontal, bool is_positive) {
582   if (is_horizontal)
583     return 0;
584   // y coordinate is most likely negative.
585   int y = abs(scroll_view->contents()->y());
586   int vis_height = scroll_view->contents()->parent()->height();
587   if (is_positive) {
588     // Align the bottom most row to the top of the view.
589     int bottom = std::min(scroll_view->contents()->height() - 1,
590                           y + vis_height);
591     RowInfo bottom_row_info = GetRowInfo(bottom);
592     // If 0, ScrollView will provide a default value.
593     return std::max(0, bottom_row_info.origin - y);
594   } else {
595     // Align the row on the previous page to to the top of the view.
596     int last_page_y = y - vis_height;
597     RowInfo last_page_info = GetRowInfo(std::max(0, last_page_y));
598     if (last_page_y != last_page_info.origin)
599       return std::max(0, y - last_page_info.origin - last_page_info.height);
600     return std::max(0, y - last_page_info.origin);
601   }
602 }
603
604 int VariableRowHeightScrollHelper::GetLineScrollIncrement(
605     ScrollView* scroll_view, bool is_horizontal, bool is_positive) {
606   if (is_horizontal)
607     return 0;
608   // y coordinate is most likely negative.
609   int y = abs(scroll_view->contents()->y());
610   RowInfo row = GetRowInfo(y);
611   if (is_positive) {
612     return row.height - (y - row.origin);
613   } else if (y == row.origin) {
614     row = GetRowInfo(std::max(0, row.origin - 1));
615     return y - row.origin;
616   } else {
617     return y - row.origin;
618   }
619 }
620
621 VariableRowHeightScrollHelper::RowInfo
622     VariableRowHeightScrollHelper::GetRowInfo(int y) {
623   return controller_->GetRowInfo(y);
624 }
625
626 // FixedRowHeightScrollHelper -----------------------------------------------
627
628 FixedRowHeightScrollHelper::FixedRowHeightScrollHelper(int top_margin,
629                                                        int row_height)
630     : VariableRowHeightScrollHelper(NULL),
631       top_margin_(top_margin),
632       row_height_(row_height) {
633   DCHECK_GT(row_height, 0);
634 }
635
636 VariableRowHeightScrollHelper::RowInfo
637     FixedRowHeightScrollHelper::GetRowInfo(int y) {
638   if (y < top_margin_)
639     return RowInfo(0, top_margin_);
640   return RowInfo((y - top_margin_) / row_height_ * row_height_ + top_margin_,
641                  row_height_);
642 }
643
644 }  // namespace views