Bumping manifests a=b2g-bump
[gecko.git] / layout / xul / nsSliderFrame.cpp
blob4093da5c80612823663fd46c8f97018d3496266e
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 //
7 // Eric Vaughan
8 // Netscape Communications
9 //
10 // See documentation in associated header file
13 #include "nsSliderFrame.h"
14 #include "nsStyleContext.h"
15 #include "nsPresContext.h"
16 #include "nsIContent.h"
17 #include "nsCOMPtr.h"
18 #include "nsNameSpaceManager.h"
19 #include "nsGkAtoms.h"
20 #include "nsHTMLParts.h"
21 #include "nsIPresShell.h"
22 #include "nsCSSRendering.h"
23 #include "nsIDOMMouseEvent.h"
24 #include "nsScrollbarButtonFrame.h"
25 #include "nsISliderListener.h"
26 #include "nsIScrollbarMediator.h"
27 #include "nsScrollbarFrame.h"
28 #include "nsRepeatService.h"
29 #include "nsBoxLayoutState.h"
30 #include "nsSprocketLayout.h"
31 #include "nsIServiceManager.h"
32 #include "nsContentUtils.h"
33 #include "nsLayoutUtils.h"
34 #include "nsDisplayList.h"
35 #include "mozilla/Preferences.h"
36 #include "mozilla/LookAndFeel.h"
37 #include "mozilla/MouseEvents.h"
38 #include <algorithm>
40 using namespace mozilla;
42 bool nsSliderFrame::gMiddlePref = false;
43 int32_t nsSliderFrame::gSnapMultiplier;
45 // Turn this on if you want to debug slider frames.
46 #undef DEBUG_SLIDER
48 static already_AddRefed<nsIContent>
49 GetContentOfBox(nsIFrame *aBox)
51 nsCOMPtr<nsIContent> content = aBox->GetContent();
52 return content.forget();
55 nsIFrame*
56 NS_NewSliderFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
58 return new (aPresShell) nsSliderFrame(aContext);
61 NS_IMPL_FRAMEARENA_HELPERS(nsSliderFrame)
63 NS_QUERYFRAME_HEAD(nsSliderFrame)
64 NS_QUERYFRAME_ENTRY(nsSliderFrame)
65 NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
67 nsSliderFrame::nsSliderFrame(nsStyleContext* aContext):
68 nsBoxFrame(aContext),
69 mCurPos(0),
70 mChange(0),
71 mDragFinished(true),
72 mUserChanged(false)
76 // stop timer
77 nsSliderFrame::~nsSliderFrame()
81 void
82 nsSliderFrame::Init(nsIContent* aContent,
83 nsContainerFrame* aParent,
84 nsIFrame* aPrevInFlow)
86 nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
88 static bool gotPrefs = false;
89 if (!gotPrefs) {
90 gotPrefs = true;
92 gMiddlePref = Preferences::GetBool("middlemouse.scrollbarPosition");
93 gSnapMultiplier = Preferences::GetInt("slider.snapMultiplier");
96 mCurPos = GetCurrentPosition(aContent);
99 void
100 nsSliderFrame::RemoveFrame(ChildListID aListID,
101 nsIFrame* aOldFrame)
103 nsBoxFrame::RemoveFrame(aListID, aOldFrame);
104 if (mFrames.IsEmpty())
105 RemoveListener();
108 void
109 nsSliderFrame::InsertFrames(ChildListID aListID,
110 nsIFrame* aPrevFrame,
111 nsFrameList& aFrameList)
113 bool wasEmpty = mFrames.IsEmpty();
114 nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList);
115 if (wasEmpty)
116 AddListener();
119 void
120 nsSliderFrame::AppendFrames(ChildListID aListID,
121 nsFrameList& aFrameList)
123 // if we have no children and on was added then make sure we add the
124 // listener
125 bool wasEmpty = mFrames.IsEmpty();
126 nsBoxFrame::AppendFrames(aListID, aFrameList);
127 if (wasEmpty)
128 AddListener();
131 int32_t
132 nsSliderFrame::GetCurrentPosition(nsIContent* content)
134 return GetIntegerAttribute(content, nsGkAtoms::curpos, 0);
137 int32_t
138 nsSliderFrame::GetMinPosition(nsIContent* content)
140 return GetIntegerAttribute(content, nsGkAtoms::minpos, 0);
143 int32_t
144 nsSliderFrame::GetMaxPosition(nsIContent* content)
146 return GetIntegerAttribute(content, nsGkAtoms::maxpos, 100);
149 int32_t
150 nsSliderFrame::GetIncrement(nsIContent* content)
152 return GetIntegerAttribute(content, nsGkAtoms::increment, 1);
156 int32_t
157 nsSliderFrame::GetPageIncrement(nsIContent* content)
159 return GetIntegerAttribute(content, nsGkAtoms::pageincrement, 10);
162 int32_t
163 nsSliderFrame::GetIntegerAttribute(nsIContent* content, nsIAtom* atom, int32_t defaultValue)
165 nsAutoString value;
166 content->GetAttr(kNameSpaceID_None, atom, value);
167 if (!value.IsEmpty()) {
168 nsresult error;
170 // convert it to an integer
171 defaultValue = value.ToInteger(&error);
174 return defaultValue;
177 class nsValueChangedRunnable : public nsRunnable
179 public:
180 nsValueChangedRunnable(nsISliderListener* aListener,
181 nsIAtom* aWhich,
182 int32_t aValue,
183 bool aUserChanged)
184 : mListener(aListener), mWhich(aWhich),
185 mValue(aValue), mUserChanged(aUserChanged)
188 NS_IMETHODIMP Run()
190 return mListener->ValueChanged(nsDependentAtomString(mWhich),
191 mValue, mUserChanged);
194 nsCOMPtr<nsISliderListener> mListener;
195 nsCOMPtr<nsIAtom> mWhich;
196 int32_t mValue;
197 bool mUserChanged;
200 class nsDragStateChangedRunnable : public nsRunnable
202 public:
203 nsDragStateChangedRunnable(nsISliderListener* aListener,
204 bool aDragBeginning)
205 : mListener(aListener),
206 mDragBeginning(aDragBeginning)
209 NS_IMETHODIMP Run()
211 return mListener->DragStateChanged(mDragBeginning);
214 nsCOMPtr<nsISliderListener> mListener;
215 bool mDragBeginning;
218 nsresult
219 nsSliderFrame::AttributeChanged(int32_t aNameSpaceID,
220 nsIAtom* aAttribute,
221 int32_t aModType)
223 nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
224 aModType);
225 // if the current position changes
226 if (aAttribute == nsGkAtoms::curpos) {
227 CurrentPositionChanged();
228 } else if (aAttribute == nsGkAtoms::minpos ||
229 aAttribute == nsGkAtoms::maxpos) {
230 // bounds check it.
232 nsIFrame* scrollbarBox = GetScrollbar();
233 nsCOMPtr<nsIContent> scrollbar;
234 scrollbar = GetContentOfBox(scrollbarBox);
235 int32_t current = GetCurrentPosition(scrollbar);
236 int32_t min = GetMinPosition(scrollbar);
237 int32_t max = GetMaxPosition(scrollbar);
239 // inform the parent <scale> that the minimum or maximum changed
240 nsIFrame* parent = GetParent();
241 if (parent) {
242 nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent());
243 if (sliderListener) {
244 nsContentUtils::AddScriptRunner(
245 new nsValueChangedRunnable(sliderListener, aAttribute,
246 aAttribute == nsGkAtoms::minpos ? min : max, false));
250 if (current < min || current > max)
252 int32_t direction = 0;
253 if (current < min || max < min) {
254 current = min;
255 direction = -1;
256 } else if (current > max) {
257 current = max;
258 direction = 1;
261 // set the new position and notify observers
262 nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
263 if (scrollbarFrame) {
264 nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator();
265 scrollbarFrame->SetIncrementToWhole(direction);
266 if (mediator) {
267 mediator->ScrollByWhole(scrollbarFrame, direction);
270 // 'this' might be destroyed here
272 nsContentUtils::AddScriptRunner(
273 new nsSetAttrRunnable(scrollbar, nsGkAtoms::curpos, current));
277 if (aAttribute == nsGkAtoms::minpos ||
278 aAttribute == nsGkAtoms::maxpos ||
279 aAttribute == nsGkAtoms::pageincrement ||
280 aAttribute == nsGkAtoms::increment) {
282 PresContext()->PresShell()->
283 FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
286 return rv;
289 void
290 nsSliderFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
291 const nsRect& aDirtyRect,
292 const nsDisplayListSet& aLists)
294 if (aBuilder->IsForEventDelivery() && isDraggingThumb()) {
295 // This is EVIL, we shouldn't be messing with event delivery just to get
296 // thumb mouse drag events to arrive at the slider!
297 aLists.Outlines()->AppendNewToTop(new (aBuilder)
298 nsDisplayEventReceiver(aBuilder, this));
299 return;
302 nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
305 void
306 nsSliderFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
307 const nsRect& aDirtyRect,
308 const nsDisplayListSet& aLists)
310 // if we are too small to have a thumb don't paint it.
311 nsIFrame* thumb = nsBox::GetChildBox(this);
313 if (thumb) {
314 nsRect thumbRect(thumb->GetRect());
315 nsMargin m;
316 thumb->GetMargin(m);
317 thumbRect.Inflate(m);
319 nsRect crect;
320 GetClientRect(crect);
322 if (crect.width < thumbRect.width || crect.height < thumbRect.height)
323 return;
326 nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists);
329 NS_IMETHODIMP
330 nsSliderFrame::DoLayout(nsBoxLayoutState& aState)
332 // get the thumb should be our only child
333 nsIFrame* thumbBox = nsBox::GetChildBox(this);
335 if (!thumbBox) {
336 SyncLayout(aState);
337 return NS_OK;
340 EnsureOrient();
342 #ifdef DEBUG_LAYOUT
343 if (mState & NS_STATE_DEBUG_WAS_SET) {
344 if (mState & NS_STATE_SET_TO_DEBUG)
345 SetDebug(aState, true);
346 else
347 SetDebug(aState, false);
349 #endif
351 // get the content area inside our borders
352 nsRect clientRect;
353 GetClientRect(clientRect);
355 // get the scrollbar
356 nsIFrame* scrollbarBox = GetScrollbar();
357 nsCOMPtr<nsIContent> scrollbar;
358 scrollbar = GetContentOfBox(scrollbarBox);
360 // get the thumb's pref size
361 nsSize thumbSize = thumbBox->GetPrefSize(aState);
363 if (IsHorizontal())
364 thumbSize.height = clientRect.height;
365 else
366 thumbSize.width = clientRect.width;
368 int32_t curPos = GetCurrentPosition(scrollbar);
369 int32_t minPos = GetMinPosition(scrollbar);
370 int32_t maxPos = GetMaxPosition(scrollbar);
371 int32_t pageIncrement = GetPageIncrement(scrollbar);
373 maxPos = std::max(minPos, maxPos);
374 curPos = clamped(curPos, minPos, maxPos);
376 nscoord& availableLength = IsHorizontal() ? clientRect.width : clientRect.height;
377 nscoord& thumbLength = IsHorizontal() ? thumbSize.width : thumbSize.height;
379 if ((pageIncrement + maxPos - minPos) > 0 && thumbBox->GetFlex(aState) > 0) {
380 float ratio = float(pageIncrement) / float(maxPos - minPos + pageIncrement);
381 thumbLength = std::max(thumbLength, NSToCoordRound(availableLength * ratio));
384 // Round the thumb's length to device pixels.
385 nsPresContext* presContext = PresContext();
386 thumbLength = presContext->DevPixelsToAppUnits(
387 presContext->AppUnitsToDevPixels(thumbLength));
389 // mRatio translates the thumb position in app units to the value.
390 mRatio = (minPos != maxPos) ? float(availableLength - thumbLength) / float(maxPos - minPos) : 1;
392 // in reverse mode, curpos is reversed such that lower values are to the
393 // right or bottom and increase leftwards or upwards. In this case, use the
394 // offset from the end instead of the beginning.
395 bool reverse = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
396 nsGkAtoms::reverse, eCaseMatters);
397 nscoord pos = reverse ? (maxPos - curPos) : (curPos - minPos);
399 // set the thumb's coord to be the current pos * the ratio.
400 nsRect thumbRect(clientRect.x, clientRect.y, thumbSize.width, thumbSize.height);
401 int32_t& thumbPos = (IsHorizontal() ? thumbRect.x : thumbRect.y);
402 thumbPos += NSToCoordRound(pos * mRatio);
404 nsRect oldThumbRect(thumbBox->GetRect());
405 LayoutChildAt(aState, thumbBox, thumbRect);
407 SyncLayout(aState);
409 // Redraw only if thumb changed size.
410 if (!oldThumbRect.IsEqualInterior(thumbRect))
411 Redraw(aState);
413 return NS_OK;
417 nsresult
418 nsSliderFrame::HandleEvent(nsPresContext* aPresContext,
419 WidgetGUIEvent* aEvent,
420 nsEventStatus* aEventStatus)
422 NS_ENSURE_ARG_POINTER(aEventStatus);
424 // If a web page calls event.preventDefault() we still want to
425 // scroll when scroll arrow is clicked. See bug 511075.
426 if (!mContent->IsInNativeAnonymousSubtree() &&
427 nsEventStatus_eConsumeNoDefault == *aEventStatus) {
428 return NS_OK;
431 if (!mDragFinished && !isDraggingThumb()) {
432 StopDrag();
433 return NS_OK;
436 nsIFrame* scrollbarBox = GetScrollbar();
437 nsCOMPtr<nsIContent> scrollbar;
438 scrollbar = GetContentOfBox(scrollbarBox);
439 bool isHorizontal = IsHorizontal();
441 if (isDraggingThumb())
443 switch (aEvent->message) {
444 case NS_TOUCH_MOVE:
445 case NS_MOUSE_MOVE: {
446 nsPoint eventPoint;
447 if (!GetEventPoint(aEvent, eventPoint)) {
448 break;
450 if (mChange) {
451 // On Linux the destination point is determined by the initial click
452 // on the scrollbar track and doesn't change until the mouse button
453 // is released.
454 #ifndef MOZ_WIDGET_GTK
455 // On the other platforms we need to update the destination point now.
456 mDestinationPoint = eventPoint;
457 StopRepeat();
458 StartRepeat();
459 #endif
460 break;
463 nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y;
465 nsIFrame* thumbFrame = mFrames.FirstChild();
466 if (!thumbFrame) {
467 return NS_OK;
470 // take our current position and subtract the start location
471 pos -= mDragStart;
472 bool isMouseOutsideThumb = false;
473 if (gSnapMultiplier) {
474 nsSize thumbSize = thumbFrame->GetSize();
475 if (isHorizontal) {
476 // horizontal scrollbar - check if mouse is above or below thumb
477 // XXXbz what about looking at the .y of the thumb's rect? Is that
478 // always zero here?
479 if (eventPoint.y < -gSnapMultiplier * thumbSize.height ||
480 eventPoint.y > thumbSize.height +
481 gSnapMultiplier * thumbSize.height)
482 isMouseOutsideThumb = true;
484 else {
485 // vertical scrollbar - check if mouse is left or right of thumb
486 if (eventPoint.x < -gSnapMultiplier * thumbSize.width ||
487 eventPoint.x > thumbSize.width +
488 gSnapMultiplier * thumbSize.width)
489 isMouseOutsideThumb = true;
492 if (aEvent->mClass == eTouchEventClass) {
493 *aEventStatus = nsEventStatus_eConsumeNoDefault;
495 if (isMouseOutsideThumb)
497 SetCurrentThumbPosition(scrollbar, mThumbStart, false, false);
498 return NS_OK;
501 // set it
502 SetCurrentThumbPosition(scrollbar, pos, false, true); // with snapping
504 break;
506 case NS_TOUCH_END:
507 case NS_MOUSE_BUTTON_UP:
508 if (ShouldScrollForEvent(aEvent)) {
509 StopDrag();
510 //we MUST call nsFrame HandleEvent for mouse ups to maintain the selection state and capture state.
511 return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
515 //return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
516 return NS_OK;
517 } else if (ShouldScrollToClickForEvent(aEvent)) {
518 nsPoint eventPoint;
519 if (!GetEventPoint(aEvent, eventPoint)) {
520 return NS_OK;
522 nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y;
524 // adjust so that the middle of the thumb is placed under the click
525 nsIFrame* thumbFrame = mFrames.FirstChild();
526 if (!thumbFrame) {
527 return NS_OK;
529 nsSize thumbSize = thumbFrame->GetSize();
530 nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height;
532 // set it
533 nsWeakFrame weakFrame(this);
534 // should aMaySnap be true here?
535 SetCurrentThumbPosition(scrollbar, pos - thumbLength/2, false, false);
536 NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK);
538 DragThumb(true);
540 #ifdef MOZ_WIDGET_GTK
541 nsCOMPtr<nsIContent> thumb = thumbFrame->GetContent();
542 thumb->SetAttr(kNameSpaceID_None, nsGkAtoms::active, NS_LITERAL_STRING("true"), true);
543 #endif
545 if (aEvent->mClass == eTouchEventClass) {
546 *aEventStatus = nsEventStatus_eConsumeNoDefault;
549 if (isHorizontal)
550 mThumbStart = thumbFrame->GetPosition().x;
551 else
552 mThumbStart = thumbFrame->GetPosition().y;
554 mDragStart = pos - mThumbStart;
556 #ifdef MOZ_WIDGET_GTK
557 else if (ShouldScrollForEvent(aEvent) &&
558 aEvent->mClass == eMouseEventClass &&
559 aEvent->AsMouseEvent()->button == WidgetMouseEvent::eRightButton) {
560 // HandlePress and HandleRelease are usually called via
561 // nsFrame::HandleEvent, but only for the left mouse button.
562 if (aEvent->message == NS_MOUSE_BUTTON_DOWN) {
563 HandlePress(aPresContext, aEvent, aEventStatus);
564 } else if (aEvent->message == NS_MOUSE_BUTTON_UP) {
565 HandleRelease(aPresContext, aEvent, aEventStatus);
568 return NS_OK;
570 #endif
572 // XXX hack until handle release is actually called in nsframe.
573 // if (aEvent->message == NS_MOUSE_EXIT_SYNTH || aEvent->message == NS_MOUSE_RIGHT_BUTTON_UP || aEvent->message == NS_MOUSE_LEFT_BUTTON_UP)
574 // HandleRelease(aPresContext, aEvent, aEventStatus);
576 if (aEvent->message == NS_MOUSE_EXIT_SYNTH && mChange)
577 HandleRelease(aPresContext, aEvent, aEventStatus);
579 return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
582 // Helper function to collect the "scroll to click" metric. Beware of
583 // caching this, users expect to be able to change the system preference
584 // and see the browser change its behavior immediately.
585 bool
586 nsSliderFrame::GetScrollToClick()
588 if (GetScrollbar() != this) {
589 return LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollToClick, false);
592 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::movetoclick,
593 nsGkAtoms::_true, eCaseMatters)) {
594 return true;
596 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::movetoclick,
597 nsGkAtoms::_false, eCaseMatters)) {
598 return false;
601 #ifdef XP_MACOSX
602 return true;
603 #else
604 return false;
605 #endif
608 nsIFrame*
609 nsSliderFrame::GetScrollbar()
611 // if we are in a scrollbar then return the scrollbar's content node
612 // if we are not then return ours.
613 nsIFrame* scrollbar;
614 nsScrollbarButtonFrame::GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar);
616 if (scrollbar == nullptr)
617 return this;
619 return scrollbar->IsBoxFrame() ? scrollbar : this;
622 void
623 nsSliderFrame::PageUpDown(nscoord change)
625 // on a page up or down get our page increment. We get this by getting the scrollbar we are in and
626 // asking it for the current position and the page increment. If we are not in a scrollbar we will
627 // get the values from our own node.
628 nsIFrame* scrollbarBox = GetScrollbar();
629 nsCOMPtr<nsIContent> scrollbar;
630 scrollbar = GetContentOfBox(scrollbarBox);
632 nscoord pageIncrement = GetPageIncrement(scrollbar);
633 int32_t curpos = GetCurrentPosition(scrollbar);
634 int32_t minpos = GetMinPosition(scrollbar);
635 int32_t maxpos = GetMaxPosition(scrollbar);
637 // get the new position and make sure it is in bounds
638 int32_t newpos = curpos + change * pageIncrement;
639 if (newpos < minpos || maxpos < minpos)
640 newpos = minpos;
641 else if (newpos > maxpos)
642 newpos = maxpos;
644 SetCurrentPositionInternal(scrollbar, newpos, true);
647 // called when the current position changed and we need to update the thumb's location
648 void
649 nsSliderFrame::CurrentPositionChanged()
651 nsIFrame* scrollbarBox = GetScrollbar();
652 nsCOMPtr<nsIContent> scrollbar;
653 scrollbar = GetContentOfBox(scrollbarBox);
655 // get the current position
656 int32_t curPos = GetCurrentPosition(scrollbar);
658 // do nothing if the position did not change
659 if (mCurPos == curPos)
660 return;
662 // get our current min and max position from our content node
663 int32_t minPos = GetMinPosition(scrollbar);
664 int32_t maxPos = GetMaxPosition(scrollbar);
666 maxPos = std::max(minPos, maxPos);
667 curPos = clamped(curPos, minPos, maxPos);
669 // get the thumb's rect
670 nsIFrame* thumbFrame = mFrames.FirstChild();
671 if (!thumbFrame)
672 return; // The thumb may stream in asynchronously via XBL.
674 nsRect thumbRect = thumbFrame->GetRect();
676 nsRect clientRect;
677 GetClientRect(clientRect);
679 // figure out the new rect
680 nsRect newThumbRect(thumbRect);
682 bool reverse = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
683 nsGkAtoms::reverse, eCaseMatters);
684 nscoord pos = reverse ? (maxPos - curPos) : (curPos - minPos);
686 if (IsHorizontal())
687 newThumbRect.x = clientRect.x + NSToCoordRound(pos * mRatio);
688 else
689 newThumbRect.y = clientRect.y + NSToCoordRound(pos * mRatio);
691 // avoid putting the scroll thumb at subpixel positions which cause needless invalidations
692 nscoord appUnitsPerPixel = PresContext()->AppUnitsPerDevPixel();
693 nsRect snappedThumbRect = newThumbRect.ToNearestPixels(appUnitsPerPixel).ToAppUnits(appUnitsPerPixel);
694 if (IsHorizontal()) {
695 newThumbRect.x = snappedThumbRect.x;
696 newThumbRect.width = snappedThumbRect.width;
697 } else {
698 newThumbRect.y = snappedThumbRect.y;
699 newThumbRect.height = snappedThumbRect.height;
701 newThumbRect = newThumbRect.Intersect(clientRect);
703 // set the rect
704 thumbFrame->SetRect(newThumbRect);
706 // Request a repaint of the scrollbar
707 SchedulePaint();
709 mCurPos = curPos;
711 // inform the parent <scale> if it exists that the value changed
712 nsIFrame* parent = GetParent();
713 if (parent) {
714 nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent());
715 if (sliderListener) {
716 nsContentUtils::AddScriptRunner(
717 new nsValueChangedRunnable(sliderListener, nsGkAtoms::curpos, mCurPos, mUserChanged));
722 static void UpdateAttribute(nsIContent* aScrollbar, nscoord aNewPos, bool aNotify, bool aIsSmooth) {
723 nsAutoString str;
724 str.AppendInt(aNewPos);
726 if (aIsSmooth) {
727 aScrollbar->SetAttr(kNameSpaceID_None, nsGkAtoms::smooth, NS_LITERAL_STRING("true"), false);
729 aScrollbar->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, str, aNotify);
730 if (aIsSmooth) {
731 aScrollbar->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false);
735 // Use this function when you want to set the scroll position via the position
736 // of the scrollbar thumb, e.g. when dragging the slider. This function scrolls
737 // the content in such a way that thumbRect.x/.y becomes aNewThumbPos.
738 void
739 nsSliderFrame::SetCurrentThumbPosition(nsIContent* aScrollbar, nscoord aNewThumbPos,
740 bool aIsSmooth, bool aMaySnap)
742 nsRect crect;
743 GetClientRect(crect);
744 nscoord offset = IsHorizontal() ? crect.x : crect.y;
745 int32_t newPos = NSToIntRound((aNewThumbPos - offset) / mRatio);
747 if (aMaySnap && mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::snap,
748 nsGkAtoms::_true, eCaseMatters)) {
749 // If snap="true", then the slider may only be set to min + (increment * x).
750 // Otherwise, the slider may be set to any positive integer.
751 int32_t increment = GetIncrement(aScrollbar);
752 newPos = NSToIntRound(newPos / float(increment)) * increment;
755 SetCurrentPosition(aScrollbar, newPos, aIsSmooth);
758 // Use this function when you know the target scroll position of the scrolled content.
759 // aNewPos should be passed to this function as a position as if the minpos is 0.
760 // That is, the minpos will be added to the position by this function. In a reverse
761 // direction slider, the newpos should be the distance from the end.
762 void
763 nsSliderFrame::SetCurrentPosition(nsIContent* aScrollbar, int32_t aNewPos,
764 bool aIsSmooth)
766 // get min and max position from our content node
767 int32_t minpos = GetMinPosition(aScrollbar);
768 int32_t maxpos = GetMaxPosition(aScrollbar);
770 // in reverse direction sliders, flip the value so that it goes from
771 // right to left, or bottom to top.
772 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
773 nsGkAtoms::reverse, eCaseMatters))
774 aNewPos = maxpos - aNewPos;
775 else
776 aNewPos += minpos;
778 // get the new position and make sure it is in bounds
779 if (aNewPos < minpos || maxpos < minpos)
780 aNewPos = minpos;
781 else if (aNewPos > maxpos)
782 aNewPos = maxpos;
784 SetCurrentPositionInternal(aScrollbar, aNewPos, aIsSmooth);
787 void
788 nsSliderFrame::SetCurrentPositionInternal(nsIContent* aScrollbar, int32_t aNewPos,
789 bool aIsSmooth)
791 nsCOMPtr<nsIContent> scrollbar = aScrollbar;
792 nsIFrame* scrollbarBox = GetScrollbar();
793 nsWeakFrame weakFrame(this);
795 mUserChanged = true;
797 nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox);
798 if (scrollbarFrame) {
799 // See if we have a mediator.
800 nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator();
801 if (mediator) {
802 nsCOMPtr<nsIContent> content = GetContent();
803 nscoord oldPos = nsPresContext::CSSPixelsToAppUnits(GetCurrentPosition(scrollbar));
804 nscoord newPos = nsPresContext::CSSPixelsToAppUnits(aNewPos);
805 mediator->ThumbMoved(scrollbarFrame, oldPos, newPos);
806 if (!weakFrame.IsAlive()) {
807 return;
809 CurrentPositionChanged();
810 mUserChanged = false;
811 return;
815 UpdateAttribute(scrollbar, aNewPos, true, aIsSmooth);
816 if (!weakFrame.IsAlive()) {
817 return;
819 mUserChanged = false;
821 #ifdef DEBUG_SLIDER
822 printf("Current Pos=%d\n",aNewPos);
823 #endif
827 nsIAtom*
828 nsSliderFrame::GetType() const
830 return nsGkAtoms::sliderFrame;
833 void
834 nsSliderFrame::SetInitialChildList(ChildListID aListID,
835 nsFrameList& aChildList)
837 nsBoxFrame::SetInitialChildList(aListID, aChildList);
838 AddListener();
841 nsresult
842 nsSliderMediator::HandleEvent(nsIDOMEvent* aEvent)
844 // Only process the event if the thumb is not being dragged.
845 if (mSlider && !mSlider->isDraggingThumb())
846 return mSlider->StartDrag(aEvent);
848 return NS_OK;
851 nsresult
852 nsSliderFrame::StartDrag(nsIDOMEvent* aEvent)
854 #ifdef DEBUG_SLIDER
855 printf("Begin dragging\n");
856 #endif
857 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
858 nsGkAtoms::_true, eCaseMatters))
859 return NS_OK;
861 WidgetGUIEvent* event = aEvent->GetInternalNSEvent()->AsGUIEvent();
863 if (!ShouldScrollForEvent(event)) {
864 return NS_OK;
867 nsPoint pt;
868 if (!GetEventPoint(event, pt)) {
869 return NS_OK;
871 bool isHorizontal = IsHorizontal();
872 nscoord pos = isHorizontal ? pt.x : pt.y;
874 // If we should scroll-to-click, first place the middle of the slider thumb
875 // under the mouse.
876 nsCOMPtr<nsIContent> scrollbar;
877 nscoord newpos = pos;
878 bool scrollToClick = ShouldScrollToClickForEvent(event);
879 if (scrollToClick) {
880 // adjust so that the middle of the thumb is placed under the click
881 nsIFrame* thumbFrame = mFrames.FirstChild();
882 if (!thumbFrame) {
883 return NS_OK;
885 nsSize thumbSize = thumbFrame->GetSize();
886 nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height;
888 newpos -= (thumbLength/2);
890 nsIFrame* scrollbarBox = GetScrollbar();
891 scrollbar = GetContentOfBox(scrollbarBox);
894 DragThumb(true);
896 if (scrollToClick) {
897 // should aMaySnap be true here?
898 SetCurrentThumbPosition(scrollbar, newpos, false, false);
901 nsIFrame* thumbFrame = mFrames.FirstChild();
902 if (!thumbFrame) {
903 return NS_OK;
906 #ifdef MOZ_WIDGET_GTK
907 nsCOMPtr<nsIContent> thumb = thumbFrame->GetContent();
908 thumb->SetAttr(kNameSpaceID_None, nsGkAtoms::active, NS_LITERAL_STRING("true"), true);
909 #endif
911 if (isHorizontal)
912 mThumbStart = thumbFrame->GetPosition().x;
913 else
914 mThumbStart = thumbFrame->GetPosition().y;
916 mDragStart = pos - mThumbStart;
918 #ifdef DEBUG_SLIDER
919 printf("Pressed mDragStart=%d\n",mDragStart);
920 #endif
922 return NS_OK;
925 nsresult
926 nsSliderFrame::StopDrag()
928 AddListener();
929 DragThumb(false);
931 #ifdef MOZ_WIDGET_GTK
932 nsIFrame* thumbFrame = mFrames.FirstChild();
933 if (thumbFrame) {
934 nsCOMPtr<nsIContent> thumb = thumbFrame->GetContent();
935 thumb->UnsetAttr(kNameSpaceID_None, nsGkAtoms::active, true);
937 #endif
939 if (mChange) {
940 StopRepeat();
941 mChange = 0;
943 return NS_OK;
946 void
947 nsSliderFrame::DragThumb(bool aGrabMouseEvents)
949 mDragFinished = !aGrabMouseEvents;
951 // inform the parent <scale> that a drag is beginning or ending
952 nsIFrame* parent = GetParent();
953 if (parent) {
954 nsCOMPtr<nsISliderListener> sliderListener = do_QueryInterface(parent->GetContent());
955 if (sliderListener) {
956 nsContentUtils::AddScriptRunner(
957 new nsDragStateChangedRunnable(sliderListener, aGrabMouseEvents));
961 nsIPresShell::SetCapturingContent(aGrabMouseEvents ? GetContent() : nullptr,
962 aGrabMouseEvents ? CAPTURE_IGNOREALLOWED : 0);
965 bool
966 nsSliderFrame::isDraggingThumb()
968 return (nsIPresShell::GetCapturingContent() == GetContent());
971 void
972 nsSliderFrame::AddListener()
974 if (!mMediator) {
975 mMediator = new nsSliderMediator(this);
978 nsIFrame* thumbFrame = mFrames.FirstChild();
979 if (!thumbFrame) {
980 return;
982 thumbFrame->GetContent()->
983 AddSystemEventListener(NS_LITERAL_STRING("mousedown"), mMediator,
984 false, false);
985 thumbFrame->GetContent()->
986 AddSystemEventListener(NS_LITERAL_STRING("touchstart"), mMediator,
987 false, false);
990 void
991 nsSliderFrame::RemoveListener()
993 NS_ASSERTION(mMediator, "No listener was ever added!!");
995 nsIFrame* thumbFrame = mFrames.FirstChild();
996 if (!thumbFrame)
997 return;
999 thumbFrame->GetContent()->
1000 RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"), mMediator, false);
1003 bool
1004 nsSliderFrame::ShouldScrollForEvent(WidgetGUIEvent* aEvent)
1006 switch (aEvent->message) {
1007 case NS_TOUCH_START:
1008 case NS_TOUCH_END:
1009 return true;
1010 case NS_MOUSE_BUTTON_DOWN:
1011 case NS_MOUSE_BUTTON_UP: {
1012 uint16_t button = aEvent->AsMouseEvent()->button;
1013 #ifdef MOZ_WIDGET_GTK
1014 return (button == WidgetMouseEvent::eLeftButton) ||
1015 (button == WidgetMouseEvent::eRightButton && GetScrollToClick()) ||
1016 (button == WidgetMouseEvent::eMiddleButton && gMiddlePref && !GetScrollToClick());
1017 #else
1018 return (button == WidgetMouseEvent::eLeftButton) ||
1019 (button == WidgetMouseEvent::eMiddleButton && gMiddlePref);
1020 #endif
1022 default:
1023 return false;
1027 bool
1028 nsSliderFrame::ShouldScrollToClickForEvent(WidgetGUIEvent* aEvent)
1030 if (!ShouldScrollForEvent(aEvent)) {
1031 return false;
1034 if (aEvent->message == NS_TOUCH_START) {
1035 return GetScrollToClick();
1038 if (aEvent->message != NS_MOUSE_BUTTON_DOWN) {
1039 return false;
1042 #if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
1043 // On Mac and Linux, clicking the scrollbar thumb should never scroll to click.
1044 if (IsEventOverThumb(aEvent)) {
1045 return false;
1047 #endif
1049 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
1050 if (mouseEvent->button == WidgetMouseEvent::eLeftButton) {
1051 #ifdef XP_MACOSX
1052 bool invertPref = mouseEvent->IsAlt();
1053 #else
1054 bool invertPref = mouseEvent->IsShift();
1055 #endif
1056 return GetScrollToClick() != invertPref;
1059 #ifdef MOZ_WIDGET_GTK
1060 if (mouseEvent->button == WidgetMouseEvent::eRightButton) {
1061 return !GetScrollToClick();
1063 #endif
1065 return true;
1068 bool
1069 nsSliderFrame::IsEventOverThumb(WidgetGUIEvent* aEvent)
1071 nsIFrame* thumbFrame = mFrames.FirstChild();
1072 if (!thumbFrame) {
1073 return false;
1076 nsPoint eventPoint;
1077 if (!GetEventPoint(aEvent, eventPoint)) {
1078 return false;
1081 bool isHorizontal = IsHorizontal();
1082 nsRect thumbRect = thumbFrame->GetRect();
1083 nscoord eventPos = isHorizontal ? eventPoint.x : eventPoint.y;
1084 nscoord thumbStart = isHorizontal ? thumbRect.x : thumbRect.y;
1085 nscoord thumbEnd = isHorizontal ? thumbRect.XMost() : thumbRect.YMost();
1087 return eventPos >= thumbStart && eventPos < thumbEnd;
1090 NS_IMETHODIMP
1091 nsSliderFrame::HandlePress(nsPresContext* aPresContext,
1092 WidgetGUIEvent* aEvent,
1093 nsEventStatus* aEventStatus)
1095 if (!ShouldScrollForEvent(aEvent) || ShouldScrollToClickForEvent(aEvent)) {
1096 return NS_OK;
1099 if (IsEventOverThumb(aEvent)) {
1100 return NS_OK;
1103 nsIFrame* thumbFrame = mFrames.FirstChild();
1104 if (!thumbFrame) // display:none?
1105 return NS_OK;
1107 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
1108 nsGkAtoms::_true, eCaseMatters))
1109 return NS_OK;
1111 nsRect thumbRect = thumbFrame->GetRect();
1113 nscoord change = 1;
1114 nsPoint eventPoint;
1115 if (!GetEventPoint(aEvent, eventPoint)) {
1116 return NS_OK;
1118 if (IsHorizontal() ? eventPoint.x < thumbRect.x
1119 : eventPoint.y < thumbRect.y)
1120 change = -1;
1122 mChange = change;
1123 DragThumb(true);
1124 // On Linux we want to keep scrolling in the direction indicated by |change|
1125 // until the mouse is released. On the other platforms we want to stop
1126 // scrolling as soon as the scrollbar thumb has reached the current mouse
1127 // position.
1128 #ifdef MOZ_WIDGET_GTK
1129 nsRect clientRect;
1130 GetClientRect(clientRect);
1132 // Set the destination point to the very end of the scrollbar so that
1133 // scrolling doesn't stop halfway through.
1134 if (change > 0) {
1135 mDestinationPoint = nsPoint(clientRect.width, clientRect.height);
1137 else {
1138 mDestinationPoint = nsPoint(0, 0);
1140 #else
1141 mDestinationPoint = eventPoint;
1142 #endif
1143 StartRepeat();
1144 PageScroll(change);
1146 return NS_OK;
1149 NS_IMETHODIMP
1150 nsSliderFrame::HandleRelease(nsPresContext* aPresContext,
1151 WidgetGUIEvent* aEvent,
1152 nsEventStatus* aEventStatus)
1154 StopRepeat();
1156 return NS_OK;
1159 void
1160 nsSliderFrame::DestroyFrom(nsIFrame* aDestructRoot)
1162 // tell our mediator if we have one we are gone.
1163 if (mMediator) {
1164 mMediator->SetSlider(nullptr);
1165 mMediator = nullptr;
1167 StopRepeat();
1169 // call base class Destroy()
1170 nsBoxFrame::DestroyFrom(aDestructRoot);
1173 nsSize
1174 nsSliderFrame::GetPrefSize(nsBoxLayoutState& aState)
1176 EnsureOrient();
1177 return nsBoxFrame::GetPrefSize(aState);
1180 nsSize
1181 nsSliderFrame::GetMinSize(nsBoxLayoutState& aState)
1183 EnsureOrient();
1185 // our min size is just our borders and padding
1186 return nsBox::GetMinSize(aState);
1189 nsSize
1190 nsSliderFrame::GetMaxSize(nsBoxLayoutState& aState)
1192 EnsureOrient();
1193 return nsBoxFrame::GetMaxSize(aState);
1196 void
1197 nsSliderFrame::EnsureOrient()
1199 nsIFrame* scrollbarBox = GetScrollbar();
1201 bool isHorizontal = (scrollbarBox->GetStateBits() & NS_STATE_IS_HORIZONTAL) != 0;
1202 if (isHorizontal)
1203 mState |= NS_STATE_IS_HORIZONTAL;
1204 else
1205 mState &= ~NS_STATE_IS_HORIZONTAL;
1209 void
1210 nsSliderFrame::Notify(void)
1212 bool stop = false;
1214 nsIFrame* thumbFrame = mFrames.FirstChild();
1215 if (!thumbFrame) {
1216 StopRepeat();
1217 return;
1219 nsRect thumbRect = thumbFrame->GetRect();
1221 bool isHorizontal = IsHorizontal();
1223 // See if the thumb has moved past our destination point.
1224 // if it has we want to stop.
1225 if (isHorizontal) {
1226 if (mChange < 0) {
1227 if (thumbRect.x < mDestinationPoint.x)
1228 stop = true;
1229 } else {
1230 if (thumbRect.x + thumbRect.width > mDestinationPoint.x)
1231 stop = true;
1233 } else {
1234 if (mChange < 0) {
1235 if (thumbRect.y < mDestinationPoint.y)
1236 stop = true;
1237 } else {
1238 if (thumbRect.y + thumbRect.height > mDestinationPoint.y)
1239 stop = true;
1244 if (stop) {
1245 StopRepeat();
1246 } else {
1247 PageScroll(mChange);
1251 void
1252 nsSliderFrame::PageScroll(nscoord aChange)
1254 if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
1255 nsGkAtoms::reverse, eCaseMatters)) {
1256 aChange = -aChange;
1258 nsIFrame* scrollbar = GetScrollbar();
1259 nsScrollbarFrame* sb = do_QueryFrame(scrollbar);
1260 if (sb) {
1261 nsIScrollbarMediator* m = sb->GetScrollbarMediator();
1262 sb->SetIncrementToPage(aChange);
1263 if (m) {
1264 m->ScrollByPage(sb, aChange);
1265 return;
1268 PageUpDown(aChange);
1271 NS_IMPL_ISUPPORTS(nsSliderMediator,
1272 nsIDOMEventListener)