Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / views / controls / scrollbar / base_scroll_bar.cc
blob117fe345dffea247bef217e0c73e1210d7ea7b24
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/scrollbar/base_scroll_bar.h"
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/callback.h"
10 #include "base/compiler_specific.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/strings/string16.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "build/build_config.h"
15 #include "ui/base/l10n/l10n_util.h"
16 #include "ui/events/event.h"
17 #include "ui/events/keycodes/keyboard_codes.h"
18 #include "ui/gfx/canvas.h"
19 #include "ui/gfx/geometry/safe_integer_conversions.h"
20 #include "ui/strings/grit/ui_strings.h"
21 #include "ui/views/controls/menu/menu_item_view.h"
22 #include "ui/views/controls/menu/menu_runner.h"
23 #include "ui/views/controls/scroll_view.h"
24 #include "ui/views/controls/scrollbar/base_scroll_bar_thumb.h"
25 #include "ui/views/widget/widget.h"
27 #if defined(OS_LINUX)
28 #include "ui/gfx/screen.h"
29 #endif
31 #undef min
32 #undef max
34 namespace views {
36 ///////////////////////////////////////////////////////////////////////////////
37 // BaseScrollBar, public:
39 BaseScrollBar::BaseScrollBar(bool horizontal, BaseScrollBarThumb* thumb)
40 : ScrollBar(horizontal),
41 thumb_(thumb),
42 contents_size_(0),
43 contents_scroll_offset_(0),
44 viewport_size_(0),
45 thumb_track_state_(CustomButton::STATE_NORMAL),
46 last_scroll_amount_(SCROLL_NONE),
47 repeater_(base::Bind(&BaseScrollBar::TrackClicked,
48 base::Unretained(this))),
49 context_menu_mouse_position_(0) {
50 AddChildView(thumb_);
52 set_context_menu_controller(this);
53 thumb_->set_context_menu_controller(this);
56 void BaseScrollBar::ScrollByAmount(ScrollAmount amount) {
57 int offset = contents_scroll_offset_;
58 switch (amount) {
59 case SCROLL_START:
60 offset = GetMinPosition();
61 break;
62 case SCROLL_END:
63 offset = GetMaxPosition();
64 break;
65 case SCROLL_PREV_LINE:
66 offset -= GetScrollIncrement(false, false);
67 offset = std::max(GetMinPosition(), offset);
68 break;
69 case SCROLL_NEXT_LINE:
70 offset += GetScrollIncrement(false, true);
71 offset = std::min(GetMaxPosition(), offset);
72 break;
73 case SCROLL_PREV_PAGE:
74 offset -= GetScrollIncrement(true, false);
75 offset = std::max(GetMinPosition(), offset);
76 break;
77 case SCROLL_NEXT_PAGE:
78 offset += GetScrollIncrement(true, true);
79 offset = std::min(GetMaxPosition(), offset);
80 break;
81 default:
82 break;
84 contents_scroll_offset_ = offset;
85 ScrollContentsToOffset();
88 BaseScrollBar::~BaseScrollBar() {
91 void BaseScrollBar::ScrollToThumbPosition(int thumb_position,
92 bool scroll_to_middle) {
93 contents_scroll_offset_ =
94 CalculateContentsOffset(thumb_position, scroll_to_middle);
95 if (contents_scroll_offset_ < GetMinPosition()) {
96 contents_scroll_offset_ = GetMinPosition();
97 } else if (contents_scroll_offset_ > GetMaxPosition()) {
98 contents_scroll_offset_ = GetMaxPosition();
100 ScrollContentsToOffset();
101 SchedulePaint();
104 bool BaseScrollBar::ScrollByContentsOffset(int contents_offset) {
105 int old_offset = contents_scroll_offset_;
106 contents_scroll_offset_ -= contents_offset;
107 if (contents_scroll_offset_ < GetMinPosition()) {
108 contents_scroll_offset_ = GetMinPosition();
109 } else if (contents_scroll_offset_ > GetMaxPosition()) {
110 contents_scroll_offset_ = GetMaxPosition();
112 if (old_offset == contents_scroll_offset_)
113 return false;
115 ScrollContentsToOffset();
116 return true;
119 void BaseScrollBar::OnThumbStateChanged(CustomButton::ButtonState old_state,
120 CustomButton::ButtonState new_state) {
121 if (old_state == CustomButton::STATE_PRESSED &&
122 new_state == CustomButton::STATE_NORMAL &&
123 GetThumbTrackState() == CustomButton::STATE_HOVERED) {
124 SetThumbTrackState(CustomButton::STATE_NORMAL);
128 ///////////////////////////////////////////////////////////////////////////////
129 // BaseScrollBar, View implementation:
131 bool BaseScrollBar::OnMousePressed(const ui::MouseEvent& event) {
132 if (event.IsOnlyLeftMouseButton())
133 ProcessPressEvent(event);
134 return true;
137 void BaseScrollBar::OnMouseReleased(const ui::MouseEvent& event) {
138 SetState(HitTestPoint(event.location()) ?
139 CustomButton::STATE_HOVERED : CustomButton::STATE_NORMAL);
142 void BaseScrollBar::OnMouseCaptureLost() {
143 SetState(CustomButton::STATE_NORMAL);
146 void BaseScrollBar::OnMouseEntered(const ui::MouseEvent& event) {
147 SetThumbTrackState(CustomButton::STATE_HOVERED);
150 void BaseScrollBar::OnMouseExited(const ui::MouseEvent& event) {
151 if (GetThumbTrackState() == CustomButton::STATE_HOVERED)
152 SetState(CustomButton::STATE_NORMAL);
155 bool BaseScrollBar::OnKeyPressed(const ui::KeyEvent& event) {
156 ScrollAmount amount = SCROLL_NONE;
157 switch (event.key_code()) {
158 case ui::VKEY_UP:
159 if (!IsHorizontal())
160 amount = SCROLL_PREV_LINE;
161 break;
162 case ui::VKEY_DOWN:
163 if (!IsHorizontal())
164 amount = SCROLL_NEXT_LINE;
165 break;
166 case ui::VKEY_LEFT:
167 if (IsHorizontal())
168 amount = SCROLL_PREV_LINE;
169 break;
170 case ui::VKEY_RIGHT:
171 if (IsHorizontal())
172 amount = SCROLL_NEXT_LINE;
173 break;
174 case ui::VKEY_PRIOR:
175 amount = SCROLL_PREV_PAGE;
176 break;
177 case ui::VKEY_NEXT:
178 amount = SCROLL_NEXT_PAGE;
179 break;
180 case ui::VKEY_HOME:
181 amount = SCROLL_START;
182 break;
183 case ui::VKEY_END:
184 amount = SCROLL_END;
185 break;
186 default:
187 break;
189 if (amount != SCROLL_NONE) {
190 ScrollByAmount(amount);
191 return true;
193 return false;
196 bool BaseScrollBar::OnMouseWheel(const ui::MouseWheelEvent& event) {
197 OnScroll(event.x_offset(), event.y_offset());
198 return true;
201 void BaseScrollBar::OnGestureEvent(ui::GestureEvent* event) {
202 // If a fling is in progress, then stop the fling for any incoming gesture
203 // event (except for the GESTURE_END event that is generated at the end of the
204 // fling).
205 if (scroll_animator_.get() && scroll_animator_->is_scrolling() &&
206 (event->type() != ui::ET_GESTURE_END ||
207 event->details().touch_points() > 1)) {
208 scroll_animator_->Stop();
211 if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
212 ProcessPressEvent(*event);
213 event->SetHandled();
214 return;
217 if (event->type() == ui::ET_GESTURE_LONG_PRESS) {
218 // For a long-press, the repeater started in tap-down should continue. So
219 // return early.
220 return;
223 SetState(CustomButton::STATE_NORMAL);
225 if (event->type() == ui::ET_GESTURE_TAP) {
226 // TAP_DOWN would have already scrolled some amount. So scrolling again on
227 // TAP is not necessary.
228 event->SetHandled();
229 return;
232 if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN ||
233 event->type() == ui::ET_GESTURE_SCROLL_END) {
234 event->SetHandled();
235 return;
238 if (event->type() == ui::ET_GESTURE_SCROLL_UPDATE) {
239 float scroll_amount_f;
240 int scroll_amount;
241 if (IsHorizontal()) {
242 scroll_amount_f = event->details().scroll_x() - roundoff_error_.x();
243 scroll_amount = gfx::ToRoundedInt(scroll_amount_f);
244 roundoff_error_.set_x(scroll_amount - scroll_amount_f);
245 } else {
246 scroll_amount_f = event->details().scroll_y() - roundoff_error_.y();
247 scroll_amount = gfx::ToRoundedInt(scroll_amount_f);
248 roundoff_error_.set_y(scroll_amount - scroll_amount_f);
250 if (ScrollByContentsOffset(scroll_amount))
251 event->SetHandled();
252 return;
255 if (event->type() == ui::ET_SCROLL_FLING_START) {
256 if (!scroll_animator_.get())
257 scroll_animator_.reset(new ScrollAnimator(this));
258 scroll_animator_->Start(
259 IsHorizontal() ? event->details().velocity_x() : 0.f,
260 IsHorizontal() ? 0.f : event->details().velocity_y());
261 event->SetHandled();
265 ///////////////////////////////////////////////////////////////////////////////
266 // BaseScrollBar, ScrollDelegate implementation:
268 bool BaseScrollBar::OnScroll(float dx, float dy) {
269 return IsHorizontal() ? ScrollByContentsOffset(dx) :
270 ScrollByContentsOffset(dy);
273 ///////////////////////////////////////////////////////////////////////////////
274 // BaseScrollBar, ContextMenuController implementation:
276 enum ScrollBarContextMenuCommands {
277 ScrollBarContextMenuCommand_ScrollHere = 1,
278 ScrollBarContextMenuCommand_ScrollStart,
279 ScrollBarContextMenuCommand_ScrollEnd,
280 ScrollBarContextMenuCommand_ScrollPageUp,
281 ScrollBarContextMenuCommand_ScrollPageDown,
282 ScrollBarContextMenuCommand_ScrollPrev,
283 ScrollBarContextMenuCommand_ScrollNext
286 void BaseScrollBar::ShowContextMenuForView(View* source,
287 const gfx::Point& p,
288 ui::MenuSourceType source_type) {
289 Widget* widget = GetWidget();
290 gfx::Rect widget_bounds = widget->GetWindowBoundsInScreen();
291 gfx::Point temp_pt(p.x() - widget_bounds.x(), p.y() - widget_bounds.y());
292 View::ConvertPointFromWidget(this, &temp_pt);
293 context_menu_mouse_position_ = IsHorizontal() ? temp_pt.x() : temp_pt.y();
295 views::MenuItemView* menu = new views::MenuItemView(this);
296 // MenuRunner takes ownership of |menu|.
297 menu_runner_.reset(new MenuRunner(
298 menu, MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
299 menu->AppendDelegateMenuItem(ScrollBarContextMenuCommand_ScrollHere);
300 menu->AppendSeparator();
301 menu->AppendDelegateMenuItem(ScrollBarContextMenuCommand_ScrollStart);
302 menu->AppendDelegateMenuItem(ScrollBarContextMenuCommand_ScrollEnd);
303 menu->AppendSeparator();
304 menu->AppendDelegateMenuItem(ScrollBarContextMenuCommand_ScrollPageUp);
305 menu->AppendDelegateMenuItem(ScrollBarContextMenuCommand_ScrollPageDown);
306 menu->AppendSeparator();
307 menu->AppendDelegateMenuItem(ScrollBarContextMenuCommand_ScrollPrev);
308 menu->AppendDelegateMenuItem(ScrollBarContextMenuCommand_ScrollNext);
309 if (menu_runner_->RunMenuAt(GetWidget(),
310 NULL,
311 gfx::Rect(p, gfx::Size()),
312 MENU_ANCHOR_TOPLEFT,
313 source_type) == MenuRunner::MENU_DELETED) {
314 return;
318 ///////////////////////////////////////////////////////////////////////////////
319 // BaseScrollBar, Menu::Delegate implementation:
321 base::string16 BaseScrollBar::GetLabel(int id) const {
322 int ids_value = 0;
323 switch (id) {
324 case ScrollBarContextMenuCommand_ScrollHere:
325 ids_value = IDS_APP_SCROLLBAR_CXMENU_SCROLLHERE;
326 break;
327 case ScrollBarContextMenuCommand_ScrollStart:
328 ids_value = IsHorizontal() ? IDS_APP_SCROLLBAR_CXMENU_SCROLLLEFTEDGE
329 : IDS_APP_SCROLLBAR_CXMENU_SCROLLHOME;
330 break;
331 case ScrollBarContextMenuCommand_ScrollEnd:
332 ids_value = IsHorizontal() ? IDS_APP_SCROLLBAR_CXMENU_SCROLLRIGHTEDGE
333 : IDS_APP_SCROLLBAR_CXMENU_SCROLLEND;
334 break;
335 case ScrollBarContextMenuCommand_ScrollPageUp:
336 ids_value = IDS_APP_SCROLLBAR_CXMENU_SCROLLPAGEUP;
337 break;
338 case ScrollBarContextMenuCommand_ScrollPageDown:
339 ids_value = IDS_APP_SCROLLBAR_CXMENU_SCROLLPAGEDOWN;
340 break;
341 case ScrollBarContextMenuCommand_ScrollPrev:
342 ids_value = IsHorizontal() ? IDS_APP_SCROLLBAR_CXMENU_SCROLLLEFT
343 : IDS_APP_SCROLLBAR_CXMENU_SCROLLUP;
344 break;
345 case ScrollBarContextMenuCommand_ScrollNext:
346 ids_value = IsHorizontal() ? IDS_APP_SCROLLBAR_CXMENU_SCROLLRIGHT
347 : IDS_APP_SCROLLBAR_CXMENU_SCROLLDOWN;
348 break;
349 default:
350 NOTREACHED() << "Invalid BaseScrollBar Context Menu command!";
353 return ids_value ? l10n_util::GetStringUTF16(ids_value) : base::string16();
356 bool BaseScrollBar::IsCommandEnabled(int id) const {
357 switch (id) {
358 case ScrollBarContextMenuCommand_ScrollPageUp:
359 case ScrollBarContextMenuCommand_ScrollPageDown:
360 return !IsHorizontal();
362 return true;
365 void BaseScrollBar::ExecuteCommand(int id) {
366 switch (id) {
367 case ScrollBarContextMenuCommand_ScrollHere:
368 ScrollToThumbPosition(context_menu_mouse_position_, true);
369 break;
370 case ScrollBarContextMenuCommand_ScrollStart:
371 ScrollByAmount(SCROLL_START);
372 break;
373 case ScrollBarContextMenuCommand_ScrollEnd:
374 ScrollByAmount(SCROLL_END);
375 break;
376 case ScrollBarContextMenuCommand_ScrollPageUp:
377 ScrollByAmount(SCROLL_PREV_PAGE);
378 break;
379 case ScrollBarContextMenuCommand_ScrollPageDown:
380 ScrollByAmount(SCROLL_NEXT_PAGE);
381 break;
382 case ScrollBarContextMenuCommand_ScrollPrev:
383 ScrollByAmount(SCROLL_PREV_LINE);
384 break;
385 case ScrollBarContextMenuCommand_ScrollNext:
386 ScrollByAmount(SCROLL_NEXT_LINE);
387 break;
391 ///////////////////////////////////////////////////////////////////////////////
392 // BaseScrollBar, ScrollBar implementation:
394 void BaseScrollBar::Update(int viewport_size,
395 int content_size,
396 int contents_scroll_offset) {
397 ScrollBar::Update(viewport_size, content_size, contents_scroll_offset);
399 // Make sure contents_size is always > 0 to avoid divide by zero errors in
400 // calculations throughout this code.
401 contents_size_ = std::max(1, content_size);
403 viewport_size_ = std::max(1, viewport_size);
405 if (content_size < 0)
406 content_size = 0;
407 if (contents_scroll_offset < 0)
408 contents_scroll_offset = 0;
409 if (contents_scroll_offset > content_size)
410 contents_scroll_offset = content_size;
411 contents_scroll_offset_ = contents_scroll_offset;
413 // Thumb Height and Thumb Pos.
414 // The height of the thumb is the ratio of the Viewport height to the
415 // content size multiplied by the height of the thumb track.
416 double ratio = static_cast<double>(viewport_size) / contents_size_;
417 int thumb_size = static_cast<int>(ratio * GetTrackSize());
418 thumb_->SetSize(thumb_size);
420 int thumb_position = CalculateThumbPosition(contents_scroll_offset);
421 thumb_->SetPosition(thumb_position);
424 int BaseScrollBar::GetPosition() const {
425 return thumb_->GetPosition();
428 ///////////////////////////////////////////////////////////////////////////////
429 // BaseScrollBar, protected:
431 BaseScrollBarThumb* BaseScrollBar::GetThumb() const {
432 return thumb_;
435 CustomButton::ButtonState BaseScrollBar::GetThumbTrackState() const {
436 return thumb_track_state_;
439 void BaseScrollBar::ScrollToPosition(int position) {
440 controller()->ScrollToPosition(this, position);
443 int BaseScrollBar::GetScrollIncrement(bool is_page, bool is_positive) {
444 return controller()->GetScrollIncrement(this, is_page, is_positive);
447 ///////////////////////////////////////////////////////////////////////////////
448 // BaseScrollBar, private:
450 int BaseScrollBar::GetThumbSizeForTest() {
451 return thumb_->GetSize();
454 void BaseScrollBar::ProcessPressEvent(const ui::LocatedEvent& event) {
455 SetThumbTrackState(CustomButton::STATE_PRESSED);
456 gfx::Rect thumb_bounds = thumb_->bounds();
457 if (IsHorizontal()) {
458 if (GetMirroredXInView(event.x()) < thumb_bounds.x()) {
459 last_scroll_amount_ = SCROLL_PREV_PAGE;
460 } else if (GetMirroredXInView(event.x()) > thumb_bounds.right()) {
461 last_scroll_amount_ = SCROLL_NEXT_PAGE;
463 } else {
464 if (event.y() < thumb_bounds.y()) {
465 last_scroll_amount_ = SCROLL_PREV_PAGE;
466 } else if (event.y() > thumb_bounds.bottom()) {
467 last_scroll_amount_ = SCROLL_NEXT_PAGE;
470 TrackClicked();
471 repeater_.Start();
474 void BaseScrollBar::SetState(CustomButton::ButtonState state) {
475 SetThumbTrackState(state);
476 repeater_.Stop();
479 void BaseScrollBar::TrackClicked() {
480 if (last_scroll_amount_ != SCROLL_NONE)
481 ScrollByAmount(last_scroll_amount_);
484 void BaseScrollBar::ScrollContentsToOffset() {
485 ScrollToPosition(contents_scroll_offset_);
486 thumb_->SetPosition(CalculateThumbPosition(contents_scroll_offset_));
489 int BaseScrollBar::GetTrackSize() const {
490 gfx::Rect track_bounds = GetTrackBounds();
491 return IsHorizontal() ? track_bounds.width() : track_bounds.height();
494 int BaseScrollBar::CalculateThumbPosition(int contents_scroll_offset) const {
495 // In some combination of viewport_size and contents_size_, the result of
496 // simple division can be rounded and there could be 1 pixel gap even when the
497 // contents scroll down to the bottom. See crbug.com/244671
498 if (contents_scroll_offset + viewport_size_ == contents_size_) {
499 int track_size = GetTrackSize();
500 return track_size - (viewport_size_ * GetTrackSize() / contents_size_);
502 return (contents_scroll_offset * GetTrackSize()) / contents_size_;
505 int BaseScrollBar::CalculateContentsOffset(int thumb_position,
506 bool scroll_to_middle) const {
507 if (scroll_to_middle)
508 thumb_position = thumb_position - (thumb_->GetSize() / 2);
509 return (thumb_position * contents_size_) / GetTrackSize();
512 void BaseScrollBar::SetThumbTrackState(CustomButton::ButtonState state) {
513 thumb_track_state_ = state;
514 SchedulePaint();
517 } // namespace views