Add ICU message format support
[chromium-blink-merge.git] / ui / views / controls / scroll_view.cc
blob8a04a02ddf63059324e5245c1748e65fbf49f338
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.
5 #include "ui/views/controls/scroll_view.h"
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"
15 namespace views {
17 const char ScrollView::kViewClassName[] = "ScrollView";
19 namespace {
21 // Subclass of ScrollView that resets the border when the theme changes.
22 class ScrollViewWithBorder : public views::ScrollView {
23 public:
24 ScrollViewWithBorder() {}
26 // View overrides;
27 void OnNativeThemeChanged(const ui::NativeTheme* theme) override {
28 SetBorder(Border::CreateSolidBorder(
30 theme->GetSystemColor(ui::NativeTheme::kColorId_UnfocusedBorderColor)));
33 private:
34 DISALLOW_COPY_AND_ASSIGN(ScrollViewWithBorder);
37 class ScrollCornerView : public views::View {
38 public:
39 ScrollCornerView() {}
41 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);
50 private:
51 DISALLOW_COPY_AND_ASSIGN(ScrollCornerView);
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;
65 // Make sure the content is not scrolled out of bounds
66 void CheckScrollBounds(View* viewport, View* view) {
67 if (!view)
68 return;
70 int x = CheckScrollBounds(viewport->width(), view->width(), -view->x());
71 int y = CheckScrollBounds(viewport->height(), view->height(), -view->y());
73 // This is no op if bounds are the same
74 view->SetBounds(-x, -y, view->width(), view->height());
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;
91 } // namespace
93 // Viewport contains the contents View of the ScrollView.
94 class ScrollView::Viewport : public View {
95 public:
96 Viewport() {}
97 ~Viewport() override {}
99 const char* GetClassName() const override { return "ScrollView::Viewport"; }
101 void ScrollRectToVisible(const gfx::Rect& rect) override {
102 if (!has_children() || !parent())
103 return;
105 View* contents = child_at(0);
106 gfx::Rect scroll_rect(rect);
107 scroll_rect.Offset(-contents->x(), -contents->y());
108 static_cast<ScrollView*>(parent())->ScrollContentsRegionToBeVisible(
109 scroll_rect);
112 void ChildPreferredSizeChanged(View* child) override {
113 if (parent())
114 parent()->Layout();
117 private:
118 DISALLOW_COPY_AND_ASSIGN(Viewport);
121 ScrollView::ScrollView()
122 : contents_(NULL),
123 contents_viewport_(new Viewport()),
124 header_(NULL),
125 header_viewport_(new Viewport()),
126 horiz_sb_(new NativeScrollBar(true)),
127 vert_sb_(new NativeScrollBar(false)),
128 corner_view_(new ScrollCornerView()),
129 min_height_(-1),
130 max_height_(-1),
131 hide_horizontal_scrollbar_(false) {
132 set_notify_enter_exit_on_child(true);
134 AddChildView(contents_viewport_);
135 AddChildView(header_viewport_);
137 // Don't add the scrollbars as children until we discover we need them
138 // (ShowOrHideScrollBar).
139 horiz_sb_->SetVisible(false);
140 horiz_sb_->set_controller(this);
141 vert_sb_->SetVisible(false);
142 vert_sb_->set_controller(this);
143 corner_view_->SetVisible(false);
146 ScrollView::~ScrollView() {
147 // The scrollbars may not have been added, delete them to ensure they get
148 // deleted.
149 delete horiz_sb_;
150 delete vert_sb_;
151 delete corner_view_;
154 // static
155 ScrollView* ScrollView::CreateScrollViewWithBorder() {
156 return new ScrollViewWithBorder();
159 void ScrollView::SetContents(View* a_view) {
160 SetHeaderOrContents(contents_viewport_, a_view, &contents_);
163 void ScrollView::SetHeader(View* header) {
164 SetHeaderOrContents(header_viewport_, header, &header_);
167 gfx::Rect ScrollView::GetVisibleRect() const {
168 if (!contents_)
169 return gfx::Rect();
170 return gfx::Rect(-contents_->x(), -contents_->y(),
171 contents_viewport_->width(), contents_viewport_->height());
174 void ScrollView::ClipHeightTo(int min_height, int max_height) {
175 min_height_ = min_height;
176 max_height_ = max_height;
179 int ScrollView::GetScrollBarWidth() const {
180 return vert_sb_ ? vert_sb_->GetLayoutSize() : 0;
183 int ScrollView::GetScrollBarHeight() const {
184 return horiz_sb_ ? horiz_sb_->GetLayoutSize() : 0;
187 void ScrollView::SetHorizontalScrollBar(ScrollBar* horiz_sb) {
188 DCHECK(horiz_sb);
189 horiz_sb->SetVisible(horiz_sb_->visible());
190 delete horiz_sb_;
191 horiz_sb->set_controller(this);
192 horiz_sb_ = horiz_sb;
195 void ScrollView::SetVerticalScrollBar(ScrollBar* vert_sb) {
196 DCHECK(vert_sb);
197 vert_sb->SetVisible(vert_sb_->visible());
198 delete vert_sb_;
199 vert_sb->set_controller(this);
200 vert_sb_ = vert_sb;
203 gfx::Size ScrollView::GetPreferredSize() const {
204 if (!is_bounded())
205 return View::GetPreferredSize();
207 gfx::Size size = contents()->GetPreferredSize();
208 size.SetToMax(gfx::Size(size.width(), min_height_));
209 size.SetToMin(gfx::Size(size.width(), max_height_));
210 gfx::Insets insets = GetInsets();
211 size.Enlarge(insets.width(), insets.height());
212 return size;
215 int ScrollView::GetHeightForWidth(int width) const {
216 if (!is_bounded())
217 return View::GetHeightForWidth(width);
219 gfx::Insets insets = GetInsets();
220 width = std::max(0, width - insets.width());
221 int height = contents()->GetHeightForWidth(width) + insets.height();
222 return std::min(std::max(height, min_height_), max_height_);
225 void ScrollView::Layout() {
226 if (is_bounded()) {
227 int content_width = width();
228 int content_height = contents()->GetHeightForWidth(content_width);
229 if (content_height > height()) {
230 content_width = std::max(content_width - GetScrollBarWidth(), 0);
231 content_height = contents()->GetHeightForWidth(content_width);
233 if (contents()->bounds().size() != gfx::Size(content_width, content_height))
234 contents()->SetBounds(0, 0, content_width, content_height);
237 // Most views will want to auto-fit the available space. Most of them want to
238 // use all available width (without overflowing) and only overflow in
239 // height. Examples are HistoryView, MostVisitedView, DownloadTabView, etc.
240 // Other views want to fit in both ways. An example is PrintView. To make both
241 // happy, assume a vertical scrollbar but no horizontal scrollbar. To override
242 // this default behavior, the inner view has to calculate the available space,
243 // used ComputeScrollBarsVisibility() to use the same calculation that is done
244 // here and sets its bound to fit within.
245 gfx::Rect viewport_bounds = GetContentsBounds();
246 const int contents_x = viewport_bounds.x();
247 const int contents_y = viewport_bounds.y();
248 if (viewport_bounds.IsEmpty()) {
249 // There's nothing to layout.
250 return;
253 const int header_height =
254 std::min(viewport_bounds.height(),
255 header_ ? header_->GetPreferredSize().height() : 0);
256 viewport_bounds.set_height(
257 std::max(0, viewport_bounds.height() - header_height));
258 viewport_bounds.set_y(viewport_bounds.y() + header_height);
259 // viewport_size is the total client space available.
260 gfx::Size viewport_size = viewport_bounds.size();
261 // Assumes a vertical scrollbar since most of the current views are designed
262 // for this.
263 int horiz_sb_height = GetScrollBarHeight();
264 int vert_sb_width = GetScrollBarWidth();
265 viewport_bounds.set_width(viewport_bounds.width() - vert_sb_width);
266 // Update the bounds right now so the inner views can fit in it.
267 contents_viewport_->SetBoundsRect(viewport_bounds);
269 // Give |contents_| a chance to update its bounds if it depends on the
270 // viewport.
271 if (contents_)
272 contents_->Layout();
274 bool should_layout_contents = false;
275 bool horiz_sb_required = false;
276 bool vert_sb_required = false;
277 if (contents_) {
278 gfx::Size content_size = contents_->size();
279 ComputeScrollBarsVisibility(viewport_size,
280 content_size,
281 &horiz_sb_required,
282 &vert_sb_required);
284 bool corner_view_required = horiz_sb_required && vert_sb_required;
285 // Take action.
286 SetControlVisibility(horiz_sb_, horiz_sb_required);
287 SetControlVisibility(vert_sb_, vert_sb_required);
288 SetControlVisibility(corner_view_, corner_view_required);
290 // Non-default.
291 if (horiz_sb_required) {
292 viewport_bounds.set_height(
293 std::max(0, viewport_bounds.height() - horiz_sb_height));
294 should_layout_contents = true;
296 // Default.
297 if (!vert_sb_required) {
298 viewport_bounds.set_width(viewport_bounds.width() + vert_sb_width);
299 should_layout_contents = true;
302 if (horiz_sb_required) {
303 int height_offset = horiz_sb_->GetContentOverlapSize();
304 horiz_sb_->SetBounds(0,
305 viewport_bounds.bottom() - height_offset,
306 viewport_bounds.right(),
307 horiz_sb_height + height_offset);
309 if (vert_sb_required) {
310 int width_offset = vert_sb_->GetContentOverlapSize();
311 vert_sb_->SetBounds(viewport_bounds.right() - width_offset,
313 vert_sb_width + width_offset,
314 viewport_bounds.bottom());
316 if (corner_view_required) {
317 // Show the resize corner.
318 corner_view_->SetBounds(viewport_bounds.right(),
319 viewport_bounds.bottom(),
320 vert_sb_width,
321 horiz_sb_height);
324 // Update to the real client size with the visible scrollbars.
325 contents_viewport_->SetBoundsRect(viewport_bounds);
326 if (should_layout_contents && contents_)
327 contents_->Layout();
329 header_viewport_->SetBounds(contents_x, contents_y,
330 viewport_bounds.width(), header_height);
331 if (header_)
332 header_->Layout();
334 CheckScrollBounds(header_viewport_, header_);
335 CheckScrollBounds(contents_viewport_, contents_);
336 SchedulePaint();
337 UpdateScrollBarPositions();
340 bool ScrollView::OnKeyPressed(const ui::KeyEvent& event) {
341 bool processed = false;
343 // Give vertical scrollbar priority
344 if (vert_sb_->visible())
345 processed = vert_sb_->OnKeyPressed(event);
347 if (!processed && horiz_sb_->visible())
348 processed = horiz_sb_->OnKeyPressed(event);
350 return processed;
353 bool ScrollView::OnMouseWheel(const ui::MouseWheelEvent& e) {
354 bool processed = false;
356 if (vert_sb_->visible())
357 processed = vert_sb_->OnMouseWheel(e);
359 if (horiz_sb_->visible())
360 processed = horiz_sb_->OnMouseWheel(e) || processed;
362 return processed;
365 void ScrollView::OnMouseEntered(const ui::MouseEvent& event) {
366 if (horiz_sb_)
367 horiz_sb_->OnMouseEnteredScrollView(event);
368 if (vert_sb_)
369 vert_sb_->OnMouseEnteredScrollView(event);
372 void ScrollView::OnMouseExited(const ui::MouseEvent& event) {
373 if (horiz_sb_)
374 horiz_sb_->OnMouseExitedScrollView(event);
375 if (vert_sb_)
376 vert_sb_->OnMouseExitedScrollView(event);
379 void ScrollView::OnGestureEvent(ui::GestureEvent* event) {
380 // If the event happened on one of the scrollbars, then those events are
381 // sent directly to the scrollbars. Otherwise, only scroll events are sent to
382 // the scrollbars.
383 bool scroll_event = event->type() == ui::ET_GESTURE_SCROLL_UPDATE ||
384 event->type() == ui::ET_GESTURE_SCROLL_BEGIN ||
385 event->type() == ui::ET_GESTURE_SCROLL_END ||
386 event->type() == ui::ET_SCROLL_FLING_START;
388 if (vert_sb_->visible()) {
389 if (vert_sb_->bounds().Contains(event->location()) || scroll_event)
390 vert_sb_->OnGestureEvent(event);
392 if (!event->handled() && horiz_sb_->visible()) {
393 if (horiz_sb_->bounds().Contains(event->location()) || scroll_event)
394 horiz_sb_->OnGestureEvent(event);
398 const char* ScrollView::GetClassName() const {
399 return kViewClassName;
402 void ScrollView::ScrollToPosition(ScrollBar* source, int position) {
403 if (!contents_)
404 return;
406 if (source == horiz_sb_ && horiz_sb_->visible()) {
407 position = AdjustPosition(contents_->x(), position, contents_->width(),
408 contents_viewport_->width());
409 if (-contents_->x() == position)
410 return;
411 contents_->SetX(-position);
412 if (header_) {
413 header_->SetX(-position);
414 header_->SchedulePaintInRect(header_->GetVisibleBounds());
416 } else if (source == vert_sb_ && vert_sb_->visible()) {
417 position = AdjustPosition(contents_->y(), position, contents_->height(),
418 contents_viewport_->height());
419 if (-contents_->y() == position)
420 return;
421 contents_->SetY(-position);
423 contents_->SchedulePaintInRect(contents_->GetVisibleBounds());
426 int ScrollView::GetScrollIncrement(ScrollBar* source, bool is_page,
427 bool is_positive) {
428 bool is_horizontal = source->IsHorizontal();
429 int amount = 0;
430 if (contents_) {
431 if (is_page) {
432 amount = contents_->GetPageScrollIncrement(
433 this, is_horizontal, is_positive);
434 } else {
435 amount = contents_->GetLineScrollIncrement(
436 this, is_horizontal, is_positive);
438 if (amount > 0)
439 return amount;
441 // No view, or the view didn't return a valid amount.
442 if (is_page) {
443 return is_horizontal ? contents_viewport_->width() :
444 contents_viewport_->height();
446 return is_horizontal ? contents_viewport_->width() / 5 :
447 contents_viewport_->height() / 5;
450 void ScrollView::SetHeaderOrContents(View* parent,
451 View* new_view,
452 View** member) {
453 if (*member == new_view)
454 return;
456 delete *member;
457 *member = new_view;
458 if (*member)
459 parent->AddChildView(*member);
460 Layout();
463 void ScrollView::ScrollContentsRegionToBeVisible(const gfx::Rect& rect) {
464 if (!contents_ || (!horiz_sb_->visible() && !vert_sb_->visible()))
465 return;
467 // Figure out the maximums for this scroll view.
468 const int contents_max_x =
469 std::max(contents_viewport_->width(), contents_->width());
470 const int contents_max_y =
471 std::max(contents_viewport_->height(), contents_->height());
473 // Make sure x and y are within the bounds of [0,contents_max_*].
474 int x = std::max(0, std::min(contents_max_x, rect.x()));
475 int y = std::max(0, std::min(contents_max_y, rect.y()));
477 // Figure out how far and down the rectangle will go taking width
478 // and height into account. This will be "clipped" by the viewport.
479 const int max_x = std::min(contents_max_x,
480 x + std::min(rect.width(), contents_viewport_->width()));
481 const int max_y = std::min(contents_max_y,
482 y + std::min(rect.height(), contents_viewport_->height()));
484 // See if the rect is already visible. Note the width is (max_x - x)
485 // and the height is (max_y - y) to take into account the clipping of
486 // either viewport or the content size.
487 const gfx::Rect vis_rect = GetVisibleRect();
488 if (vis_rect.Contains(gfx::Rect(x, y, max_x - x, max_y - y)))
489 return;
491 // Shift contents_'s X and Y so that the region is visible. If we
492 // need to shift up or left from where we currently are then we need
493 // to get it so that the content appears in the upper/left
494 // corner. This is done by setting the offset to -X or -Y. For down
495 // or right shifts we need to make sure it appears in the
496 // lower/right corner. This is calculated by taking max_x or max_y
497 // and scaling it back by the size of the viewport.
498 const int new_x =
499 (vis_rect.x() > x) ? x : std::max(0, max_x - contents_viewport_->width());
500 const int new_y =
501 (vis_rect.y() > y) ? y : std::max(0, max_y -
502 contents_viewport_->height());
504 contents_->SetX(-new_x);
505 if (header_)
506 header_->SetX(-new_x);
507 contents_->SetY(-new_y);
508 UpdateScrollBarPositions();
511 void ScrollView::ComputeScrollBarsVisibility(const gfx::Size& vp_size,
512 const gfx::Size& content_size,
513 bool* horiz_is_shown,
514 bool* vert_is_shown) const {
515 // Try to fit both ways first, then try vertical bar only, then horizontal
516 // bar only, then defaults to both shown.
517 if (content_size.width() <= vp_size.width() &&
518 content_size.height() <= vp_size.height()) {
519 *horiz_is_shown = false;
520 *vert_is_shown = false;
521 } else if (content_size.width() <= vp_size.width() - GetScrollBarWidth()) {
522 *horiz_is_shown = false;
523 *vert_is_shown = true;
524 } else if (content_size.height() <= vp_size.height() - GetScrollBarHeight()) {
525 *horiz_is_shown = true;
526 *vert_is_shown = false;
527 } else {
528 *horiz_is_shown = true;
529 *vert_is_shown = true;
532 if (hide_horizontal_scrollbar_)
533 *horiz_is_shown = false;
536 // Make sure that a single scrollbar is created and visible as needed
537 void ScrollView::SetControlVisibility(View* control, bool should_show) {
538 if (!control)
539 return;
540 if (should_show) {
541 if (!control->visible()) {
542 AddChildView(control);
543 control->SetVisible(true);
545 } else {
546 RemoveChildView(control);
547 control->SetVisible(false);
551 void ScrollView::UpdateScrollBarPositions() {
552 if (!contents_)
553 return;
555 if (horiz_sb_->visible()) {
556 int vw = contents_viewport_->width();
557 int cw = contents_->width();
558 int origin = contents_->x();
559 horiz_sb_->Update(vw, cw, -origin);
561 if (vert_sb_->visible()) {
562 int vh = contents_viewport_->height();
563 int ch = contents_->height();
564 int origin = contents_->y();
565 vert_sb_->Update(vh, ch, -origin);
569 // VariableRowHeightScrollHelper ----------------------------------------------
571 VariableRowHeightScrollHelper::VariableRowHeightScrollHelper(
572 Controller* controller) : controller_(controller) {
575 VariableRowHeightScrollHelper::~VariableRowHeightScrollHelper() {
578 int VariableRowHeightScrollHelper::GetPageScrollIncrement(
579 ScrollView* scroll_view, bool is_horizontal, bool is_positive) {
580 if (is_horizontal)
581 return 0;
582 // y coordinate is most likely negative.
583 int y = abs(scroll_view->contents()->y());
584 int vis_height = scroll_view->contents()->parent()->height();
585 if (is_positive) {
586 // Align the bottom most row to the top of the view.
587 int bottom = std::min(scroll_view->contents()->height() - 1,
588 y + vis_height);
589 RowInfo bottom_row_info = GetRowInfo(bottom);
590 // If 0, ScrollView will provide a default value.
591 return std::max(0, bottom_row_info.origin - y);
592 } else {
593 // Align the row on the previous page to to the top of the view.
594 int last_page_y = y - vis_height;
595 RowInfo last_page_info = GetRowInfo(std::max(0, last_page_y));
596 if (last_page_y != last_page_info.origin)
597 return std::max(0, y - last_page_info.origin - last_page_info.height);
598 return std::max(0, y - last_page_info.origin);
602 int VariableRowHeightScrollHelper::GetLineScrollIncrement(
603 ScrollView* scroll_view, bool is_horizontal, bool is_positive) {
604 if (is_horizontal)
605 return 0;
606 // y coordinate is most likely negative.
607 int y = abs(scroll_view->contents()->y());
608 RowInfo row = GetRowInfo(y);
609 if (is_positive) {
610 return row.height - (y - row.origin);
611 } else if (y == row.origin) {
612 row = GetRowInfo(std::max(0, row.origin - 1));
613 return y - row.origin;
614 } else {
615 return y - row.origin;
619 VariableRowHeightScrollHelper::RowInfo
620 VariableRowHeightScrollHelper::GetRowInfo(int y) {
621 return controller_->GetRowInfo(y);
624 // FixedRowHeightScrollHelper -----------------------------------------------
626 FixedRowHeightScrollHelper::FixedRowHeightScrollHelper(int top_margin,
627 int row_height)
628 : VariableRowHeightScrollHelper(NULL),
629 top_margin_(top_margin),
630 row_height_(row_height) {
631 DCHECK_GT(row_height, 0);
634 VariableRowHeightScrollHelper::RowInfo
635 FixedRowHeightScrollHelper::GetRowInfo(int y) {
636 if (y < top_margin_)
637 return RowInfo(0, top_margin_);
638 return RowInfo((y - top_margin_) / row_height_ * row_height_ + top_margin_,
639 row_height_);
642 } // namespace views