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