[SyncFS] Build indexes from FileTracker entries on disk.
[chromium-blink-merge.git] / ui / views / controls / scroll_view.cc
blob51db071b71f81d025effdb67000fe404465e4a2e
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/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"
14 namespace views {
16 const char ScrollView::kViewClassName[] = "ScrollView";
18 namespace {
20 // Subclass of ScrollView that resets the border when the theme changes.
21 class ScrollViewWithBorder : public views::ScrollView {
22 public:
23 ScrollViewWithBorder() {}
25 // View overrides;
26 virtual void OnNativeThemeChanged(const ui::NativeTheme* theme) OVERRIDE {
27 SetBorder(Border::CreateSolidBorder(
29 theme->GetSystemColor(ui::NativeTheme::kColorId_UnfocusedBorderColor)));
32 private:
33 DISALLOW_COPY_AND_ASSIGN(ScrollViewWithBorder);
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;
47 // Make sure the content is not scrolled out of bounds
48 void CheckScrollBounds(View* viewport, View* view) {
49 if (!view)
50 return;
52 int x = CheckScrollBounds(viewport->width(), view->width(), -view->x());
53 int y = CheckScrollBounds(viewport->height(), view->height(), -view->y());
55 // This is no op if bounds are the same
56 view->SetBounds(-x, -y, view->width(), view->height());
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;
73 } // namespace
75 // Viewport contains the contents View of the ScrollView.
76 class ScrollView::Viewport : public View {
77 public:
78 Viewport() {}
79 virtual ~Viewport() {}
81 virtual const char* GetClassName() const OVERRIDE {
82 return "ScrollView::Viewport";
85 virtual void ScrollRectToVisible(const gfx::Rect& rect) OVERRIDE {
86 if (!has_children() || !parent())
87 return;
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);
96 virtual void ChildPreferredSizeChanged(View* child) OVERRIDE {
97 if (parent())
98 parent()->Layout();
101 private:
102 DISALLOW_COPY_AND_ASSIGN(Viewport);
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);
118 AddChildView(contents_viewport_);
119 AddChildView(header_viewport_);
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);
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_;
137 if (resize_corner_ && !resize_corner_->parent())
138 delete resize_corner_;
141 // static
142 ScrollView* ScrollView::CreateScrollViewWithBorder() {
143 return new ScrollViewWithBorder();
146 void ScrollView::SetContents(View* a_view) {
147 SetHeaderOrContents(contents_viewport_, a_view, &contents_);
150 void ScrollView::SetHeader(View* header) {
151 SetHeaderOrContents(header_viewport_, header, &header_);
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());
161 void ScrollView::ClipHeightTo(int min_height, int max_height) {
162 min_height_ = min_height;
163 max_height_ = max_height;
166 int ScrollView::GetScrollBarWidth() const {
167 return vert_sb_ ? vert_sb_->GetLayoutSize() : 0;
170 int ScrollView::GetScrollBarHeight() const {
171 return horiz_sb_ ? horiz_sb_->GetLayoutSize() : 0;
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;
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;
190 gfx::Size ScrollView::GetPreferredSize() const {
191 if (!is_bounded())
192 return View::GetPreferredSize();
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;
202 int ScrollView::GetHeightForWidth(int width) const {
203 if (!is_bounded())
204 return View::GetHeightForWidth(width);
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_);
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);
220 if (contents()->bounds().size() != gfx::Size(content_width, content_height))
221 contents()->SetBounds(0, 0, content_width, content_height);
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;
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);
256 // Give |contents_| a chance to update its bounds if it depends on the
257 // viewport.
258 if (contents_)
259 contents_->Layout();
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);
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);
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;
284 // Default.
285 if (!vert_sb_required) {
286 viewport_bounds.set_width(viewport_bounds.width() + vert_sb_width);
287 should_layout_contents = true;
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);
297 if (vert_sb_required) {
298 int width_offset = vert_sb_->GetContentOverlapSize();
299 vert_sb_->SetBounds(viewport_bounds.right() - width_offset,
301 vert_sb_width + width_offset,
302 viewport_bounds.bottom());
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);
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();
317 header_viewport_->SetBounds(contents_x, contents_y,
318 viewport_bounds.width(), header_height);
319 if (header_)
320 header_->Layout();
322 CheckScrollBounds(header_viewport_, header_);
323 CheckScrollBounds(contents_viewport_, contents_);
324 SchedulePaint();
325 UpdateScrollBarPositions();
328 bool ScrollView::OnKeyPressed(const ui::KeyEvent& event) {
329 bool processed = false;
331 // Give vertical scrollbar priority
332 if (vert_sb_->visible())
333 processed = vert_sb_->OnKeyPressed(event);
335 if (!processed && horiz_sb_->visible())
336 processed = horiz_sb_->OnKeyPressed(event);
338 return processed;
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);
347 if (!processed && horiz_sb_->visible())
348 processed = horiz_sb_->OnMouseWheel(e);
350 return processed;
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);
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);
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;
376 if (vert_sb_->visible()) {
377 if (vert_sb_->bounds().Contains(event->location()) || scroll_event)
378 vert_sb_->OnGestureEvent(event);
380 if (!event->handled() && horiz_sb_->visible()) {
381 if (horiz_sb_->bounds().Contains(event->location()) || scroll_event)
382 horiz_sb_->OnGestureEvent(event);
386 const char* ScrollView::GetClassName() const {
387 return kViewClassName;
390 void ScrollView::ScrollToPosition(ScrollBar* source, int position) {
391 if (!contents_)
392 return;
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());
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);
411 contents_->SchedulePaintInRect(contents_->GetVisibleBounds());
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);
426 if (amount > 0)
427 return amount;
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();
434 return is_horizontal ? contents_viewport_->width() / 5 :
435 contents_viewport_->height() / 5;
438 void ScrollView::SetHeaderOrContents(View* parent,
439 View* new_view,
440 View** member) {
441 if (*member == new_view)
442 return;
444 delete *member;
445 *member = new_view;
446 if (*member)
447 parent->AddChildView(*member);
448 Layout();
451 void ScrollView::ScrollContentsRegionToBeVisible(const gfx::Rect& rect) {
452 if (!contents_ || (!horiz_sb_->visible() && !vert_sb_->visible()))
453 return;
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());
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()));
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()));
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;
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());
492 contents_->SetX(-new_x);
493 if (header_)
494 header_->SetX(-new_x);
495 contents_->SetY(-new_y);
496 UpdateScrollBarPositions();
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;
520 if (hide_horizontal_scrollbar_)
521 *horiz_is_shown = false;
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);
533 } else {
534 RemoveChildView(control);
535 control->SetVisible(false);
539 void ScrollView::UpdateScrollBarPositions() {
540 if (!contents_)
541 return;
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);
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);
557 // VariableRowHeightScrollHelper ----------------------------------------------
559 VariableRowHeightScrollHelper::VariableRowHeightScrollHelper(
560 Controller* controller) : controller_(controller) {
563 VariableRowHeightScrollHelper::~VariableRowHeightScrollHelper() {
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);
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;
607 VariableRowHeightScrollHelper::RowInfo
608 VariableRowHeightScrollHelper::GetRowInfo(int y) {
609 return controller_->GetRowInfo(y);
612 // FixedRowHeightScrollHelper -----------------------------------------------
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);
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_);
630 } // namespace views