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/. */
8 // Netscape Communications
10 // See documentation in associated header file
13 #include "nsSliderFrame.h"
14 #include "nsStyleContext.h"
15 #include "nsPresContext.h"
16 #include "nsIContent.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"
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.
48 static already_AddRefed
<nsIContent
>
49 GetContentOfBox(nsIFrame
*aBox
)
51 nsCOMPtr
<nsIContent
> content
= aBox
->GetContent();
52 return content
.forget();
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
):
77 nsSliderFrame::~nsSliderFrame()
82 nsSliderFrame::Init(nsIContent
* aContent
,
83 nsContainerFrame
* aParent
,
84 nsIFrame
* aPrevInFlow
)
86 nsBoxFrame::Init(aContent
, aParent
, aPrevInFlow
);
88 static bool gotPrefs
= false;
92 gMiddlePref
= Preferences::GetBool("middlemouse.scrollbarPosition");
93 gSnapMultiplier
= Preferences::GetInt("slider.snapMultiplier");
96 mCurPos
= GetCurrentPosition(aContent
);
100 nsSliderFrame::RemoveFrame(ChildListID aListID
,
103 nsBoxFrame::RemoveFrame(aListID
, aOldFrame
);
104 if (mFrames
.IsEmpty())
109 nsSliderFrame::InsertFrames(ChildListID aListID
,
110 nsIFrame
* aPrevFrame
,
111 nsFrameList
& aFrameList
)
113 bool wasEmpty
= mFrames
.IsEmpty();
114 nsBoxFrame::InsertFrames(aListID
, aPrevFrame
, aFrameList
);
120 nsSliderFrame::AppendFrames(ChildListID aListID
,
121 nsFrameList
& aFrameList
)
123 // if we have no children and on was added then make sure we add the
125 bool wasEmpty
= mFrames
.IsEmpty();
126 nsBoxFrame::AppendFrames(aListID
, aFrameList
);
132 nsSliderFrame::GetCurrentPosition(nsIContent
* content
)
134 return GetIntegerAttribute(content
, nsGkAtoms::curpos
, 0);
138 nsSliderFrame::GetMinPosition(nsIContent
* content
)
140 return GetIntegerAttribute(content
, nsGkAtoms::minpos
, 0);
144 nsSliderFrame::GetMaxPosition(nsIContent
* content
)
146 return GetIntegerAttribute(content
, nsGkAtoms::maxpos
, 100);
150 nsSliderFrame::GetIncrement(nsIContent
* content
)
152 return GetIntegerAttribute(content
, nsGkAtoms::increment
, 1);
157 nsSliderFrame::GetPageIncrement(nsIContent
* content
)
159 return GetIntegerAttribute(content
, nsGkAtoms::pageincrement
, 10);
163 nsSliderFrame::GetIntegerAttribute(nsIContent
* content
, nsIAtom
* atom
, int32_t defaultValue
)
166 content
->GetAttr(kNameSpaceID_None
, atom
, value
);
167 if (!value
.IsEmpty()) {
170 // convert it to an integer
171 defaultValue
= value
.ToInteger(&error
);
177 class nsValueChangedRunnable
: public nsRunnable
180 nsValueChangedRunnable(nsISliderListener
* aListener
,
184 : mListener(aListener
), mWhich(aWhich
),
185 mValue(aValue
), mUserChanged(aUserChanged
)
190 return mListener
->ValueChanged(nsDependentAtomString(mWhich
),
191 mValue
, mUserChanged
);
194 nsCOMPtr
<nsISliderListener
> mListener
;
195 nsCOMPtr
<nsIAtom
> mWhich
;
200 class nsDragStateChangedRunnable
: public nsRunnable
203 nsDragStateChangedRunnable(nsISliderListener
* aListener
,
205 : mListener(aListener
),
206 mDragBeginning(aDragBeginning
)
211 return mListener
->DragStateChanged(mDragBeginning
);
214 nsCOMPtr
<nsISliderListener
> mListener
;
219 nsSliderFrame::AttributeChanged(int32_t aNameSpaceID
,
223 nsresult rv
= nsBoxFrame::AttributeChanged(aNameSpaceID
, aAttribute
,
225 // if the current position changes
226 if (aAttribute
== nsGkAtoms::curpos
) {
227 CurrentPositionChanged();
228 } else if (aAttribute
== nsGkAtoms::minpos
||
229 aAttribute
== nsGkAtoms::maxpos
) {
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();
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
) {
256 } else if (current
> max
) {
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
);
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
);
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));
302 nsBoxFrame::BuildDisplayList(aBuilder
, aDirtyRect
, aLists
);
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);
314 nsRect
thumbRect(thumb
->GetRect());
317 thumbRect
.Inflate(m
);
320 GetClientRect(crect
);
322 if (crect
.width
< thumbRect
.width
|| crect
.height
< thumbRect
.height
)
326 nsBoxFrame::BuildDisplayListForChildren(aBuilder
, aDirtyRect
, aLists
);
330 nsSliderFrame::DoLayout(nsBoxLayoutState
& aState
)
332 // get the thumb should be our only child
333 nsIFrame
* thumbBox
= nsBox::GetChildBox(this);
343 if (mState
& NS_STATE_DEBUG_WAS_SET
) {
344 if (mState
& NS_STATE_SET_TO_DEBUG
)
345 SetDebug(aState
, true);
347 SetDebug(aState
, false);
351 // get the content area inside our borders
353 GetClientRect(clientRect
);
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
);
364 thumbSize
.height
= clientRect
.height
;
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
);
409 // Redraw only if thumb changed size.
410 if (!oldThumbRect
.IsEqualInterior(thumbRect
))
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
) {
431 if (!mDragFinished
&& !isDraggingThumb()) {
436 nsIFrame
* scrollbarBox
= GetScrollbar();
437 nsCOMPtr
<nsIContent
> scrollbar
;
438 scrollbar
= GetContentOfBox(scrollbarBox
);
439 bool isHorizontal
= IsHorizontal();
441 if (isDraggingThumb())
443 switch (aEvent
->message
) {
445 case NS_MOUSE_MOVE
: {
447 if (!GetEventPoint(aEvent
, eventPoint
)) {
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
454 #ifndef MOZ_WIDGET_GTK
455 // On the other platforms we need to update the destination point now.
456 mDestinationPoint
= eventPoint
;
463 nscoord pos
= isHorizontal
? eventPoint
.x
: eventPoint
.y
;
465 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
470 // take our current position and subtract the start location
472 bool isMouseOutsideThumb
= false;
473 if (gSnapMultiplier
) {
474 nsSize thumbSize
= thumbFrame
->GetSize();
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
479 if (eventPoint
.y
< -gSnapMultiplier
* thumbSize
.height
||
480 eventPoint
.y
> thumbSize
.height
+
481 gSnapMultiplier
* thumbSize
.height
)
482 isMouseOutsideThumb
= true;
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);
502 SetCurrentThumbPosition(scrollbar
, pos
, false, true); // with snapping
507 case NS_MOUSE_BUTTON_UP
:
508 if (ShouldScrollForEvent(aEvent
)) {
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);
517 } else if (ShouldScrollToClickForEvent(aEvent
)) {
519 if (!GetEventPoint(aEvent
, eventPoint
)) {
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();
529 nsSize thumbSize
= thumbFrame
->GetSize();
530 nscoord thumbLength
= isHorizontal
? thumbSize
.width
: thumbSize
.height
;
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
);
540 #ifdef MOZ_WIDGET_GTK
541 nsCOMPtr
<nsIContent
> thumb
= thumbFrame
->GetContent();
542 thumb
->SetAttr(kNameSpaceID_None
, nsGkAtoms::active
, NS_LITERAL_STRING("true"), true);
545 if (aEvent
->mClass
== eTouchEventClass
) {
546 *aEventStatus
= nsEventStatus_eConsumeNoDefault
;
550 mThumbStart
= thumbFrame
->GetPosition().x
;
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
);
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.
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
)) {
596 if (mContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::movetoclick
,
597 nsGkAtoms::_false
, eCaseMatters
)) {
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.
614 nsScrollbarButtonFrame::GetParentWithTag(nsGkAtoms::scrollbar
, this, scrollbar
);
616 if (scrollbar
== nullptr)
619 return scrollbar
->IsBoxFrame() ? scrollbar
: this;
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
)
641 else if (newpos
> maxpos
)
644 SetCurrentPositionInternal(scrollbar
, newpos
, true);
647 // called when the current position changed and we need to update the thumb's location
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
)
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();
672 return; // The thumb may stream in asynchronously via XBL.
674 nsRect thumbRect
= thumbFrame
->GetRect();
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
);
687 newThumbRect
.x
= clientRect
.x
+ NSToCoordRound(pos
* mRatio
);
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
;
698 newThumbRect
.y
= snappedThumbRect
.y
;
699 newThumbRect
.height
= snappedThumbRect
.height
;
701 newThumbRect
= newThumbRect
.Intersect(clientRect
);
704 thumbFrame
->SetRect(newThumbRect
);
706 // Request a repaint of the scrollbar
711 // inform the parent <scale> if it exists that the value changed
712 nsIFrame
* parent
= GetParent();
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
) {
724 str
.AppendInt(aNewPos
);
727 aScrollbar
->SetAttr(kNameSpaceID_None
, nsGkAtoms::smooth
, NS_LITERAL_STRING("true"), false);
729 aScrollbar
->SetAttr(kNameSpaceID_None
, nsGkAtoms::curpos
, str
, aNotify
);
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.
739 nsSliderFrame::SetCurrentThumbPosition(nsIContent
* aScrollbar
, nscoord aNewThumbPos
,
740 bool aIsSmooth
, bool aMaySnap
)
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.
763 nsSliderFrame::SetCurrentPosition(nsIContent
* aScrollbar
, int32_t aNewPos
,
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
;
778 // get the new position and make sure it is in bounds
779 if (aNewPos
< minpos
|| maxpos
< minpos
)
781 else if (aNewPos
> maxpos
)
784 SetCurrentPositionInternal(aScrollbar
, aNewPos
, aIsSmooth
);
788 nsSliderFrame::SetCurrentPositionInternal(nsIContent
* aScrollbar
, int32_t aNewPos
,
791 nsCOMPtr
<nsIContent
> scrollbar
= aScrollbar
;
792 nsIFrame
* scrollbarBox
= GetScrollbar();
793 nsWeakFrame
weakFrame(this);
797 nsScrollbarFrame
* scrollbarFrame
= do_QueryFrame(scrollbarBox
);
798 if (scrollbarFrame
) {
799 // See if we have a mediator.
800 nsIScrollbarMediator
* mediator
= scrollbarFrame
->GetScrollbarMediator();
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()) {
809 CurrentPositionChanged();
810 mUserChanged
= false;
815 UpdateAttribute(scrollbar
, aNewPos
, true, aIsSmooth
);
816 if (!weakFrame
.IsAlive()) {
819 mUserChanged
= false;
822 printf("Current Pos=%d\n",aNewPos
);
828 nsSliderFrame::GetType() const
830 return nsGkAtoms::sliderFrame
;
834 nsSliderFrame::SetInitialChildList(ChildListID aListID
,
835 nsFrameList
& aChildList
)
837 nsBoxFrame::SetInitialChildList(aListID
, aChildList
);
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
);
852 nsSliderFrame::StartDrag(nsIDOMEvent
* aEvent
)
855 printf("Begin dragging\n");
857 if (mContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::disabled
,
858 nsGkAtoms::_true
, eCaseMatters
))
861 WidgetGUIEvent
* event
= aEvent
->GetInternalNSEvent()->AsGUIEvent();
863 if (!ShouldScrollForEvent(event
)) {
868 if (!GetEventPoint(event
, pt
)) {
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
876 nsCOMPtr
<nsIContent
> scrollbar
;
877 nscoord newpos
= pos
;
878 bool scrollToClick
= ShouldScrollToClickForEvent(event
);
880 // adjust so that the middle of the thumb is placed under the click
881 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
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
);
897 // should aMaySnap be true here?
898 SetCurrentThumbPosition(scrollbar
, newpos
, false, false);
901 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
906 #ifdef MOZ_WIDGET_GTK
907 nsCOMPtr
<nsIContent
> thumb
= thumbFrame
->GetContent();
908 thumb
->SetAttr(kNameSpaceID_None
, nsGkAtoms::active
, NS_LITERAL_STRING("true"), true);
912 mThumbStart
= thumbFrame
->GetPosition().x
;
914 mThumbStart
= thumbFrame
->GetPosition().y
;
916 mDragStart
= pos
- mThumbStart
;
919 printf("Pressed mDragStart=%d\n",mDragStart
);
926 nsSliderFrame::StopDrag()
931 #ifdef MOZ_WIDGET_GTK
932 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
934 nsCOMPtr
<nsIContent
> thumb
= thumbFrame
->GetContent();
935 thumb
->UnsetAttr(kNameSpaceID_None
, nsGkAtoms::active
, true);
947 nsSliderFrame::DragThumb(bool aGrabMouseEvents
)
949 mDragFinished
= !aGrabMouseEvents
;
951 // inform the parent <scale> that a drag is beginning or ending
952 nsIFrame
* parent
= GetParent();
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);
966 nsSliderFrame::isDraggingThumb()
968 return (nsIPresShell::GetCapturingContent() == GetContent());
972 nsSliderFrame::AddListener()
975 mMediator
= new nsSliderMediator(this);
978 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
982 thumbFrame
->GetContent()->
983 AddSystemEventListener(NS_LITERAL_STRING("mousedown"), mMediator
,
985 thumbFrame
->GetContent()->
986 AddSystemEventListener(NS_LITERAL_STRING("touchstart"), mMediator
,
991 nsSliderFrame::RemoveListener()
993 NS_ASSERTION(mMediator
, "No listener was ever added!!");
995 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
999 thumbFrame
->GetContent()->
1000 RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"), mMediator
, false);
1004 nsSliderFrame::ShouldScrollForEvent(WidgetGUIEvent
* aEvent
)
1006 switch (aEvent
->message
) {
1007 case NS_TOUCH_START
:
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());
1018 return (button
== WidgetMouseEvent::eLeftButton
) ||
1019 (button
== WidgetMouseEvent::eMiddleButton
&& gMiddlePref
);
1028 nsSliderFrame::ShouldScrollToClickForEvent(WidgetGUIEvent
* aEvent
)
1030 if (!ShouldScrollForEvent(aEvent
)) {
1034 if (aEvent
->message
== NS_TOUCH_START
) {
1035 return GetScrollToClick();
1038 if (aEvent
->message
!= NS_MOUSE_BUTTON_DOWN
) {
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
)) {
1049 WidgetMouseEvent
* mouseEvent
= aEvent
->AsMouseEvent();
1050 if (mouseEvent
->button
== WidgetMouseEvent::eLeftButton
) {
1052 bool invertPref
= mouseEvent
->IsAlt();
1054 bool invertPref
= mouseEvent
->IsShift();
1056 return GetScrollToClick() != invertPref
;
1059 #ifdef MOZ_WIDGET_GTK
1060 if (mouseEvent
->button
== WidgetMouseEvent::eRightButton
) {
1061 return !GetScrollToClick();
1069 nsSliderFrame::IsEventOverThumb(WidgetGUIEvent
* aEvent
)
1071 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
1077 if (!GetEventPoint(aEvent
, eventPoint
)) {
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
;
1091 nsSliderFrame::HandlePress(nsPresContext
* aPresContext
,
1092 WidgetGUIEvent
* aEvent
,
1093 nsEventStatus
* aEventStatus
)
1095 if (!ShouldScrollForEvent(aEvent
) || ShouldScrollToClickForEvent(aEvent
)) {
1099 if (IsEventOverThumb(aEvent
)) {
1103 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
1104 if (!thumbFrame
) // display:none?
1107 if (mContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::disabled
,
1108 nsGkAtoms::_true
, eCaseMatters
))
1111 nsRect thumbRect
= thumbFrame
->GetRect();
1115 if (!GetEventPoint(aEvent
, eventPoint
)) {
1118 if (IsHorizontal() ? eventPoint
.x
< thumbRect
.x
1119 : eventPoint
.y
< thumbRect
.y
)
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
1128 #ifdef MOZ_WIDGET_GTK
1130 GetClientRect(clientRect
);
1132 // Set the destination point to the very end of the scrollbar so that
1133 // scrolling doesn't stop halfway through.
1135 mDestinationPoint
= nsPoint(clientRect
.width
, clientRect
.height
);
1138 mDestinationPoint
= nsPoint(0, 0);
1141 mDestinationPoint
= eventPoint
;
1150 nsSliderFrame::HandleRelease(nsPresContext
* aPresContext
,
1151 WidgetGUIEvent
* aEvent
,
1152 nsEventStatus
* aEventStatus
)
1160 nsSliderFrame::DestroyFrom(nsIFrame
* aDestructRoot
)
1162 // tell our mediator if we have one we are gone.
1164 mMediator
->SetSlider(nullptr);
1165 mMediator
= nullptr;
1169 // call base class Destroy()
1170 nsBoxFrame::DestroyFrom(aDestructRoot
);
1174 nsSliderFrame::GetPrefSize(nsBoxLayoutState
& aState
)
1177 return nsBoxFrame::GetPrefSize(aState
);
1181 nsSliderFrame::GetMinSize(nsBoxLayoutState
& aState
)
1185 // our min size is just our borders and padding
1186 return nsBox::GetMinSize(aState
);
1190 nsSliderFrame::GetMaxSize(nsBoxLayoutState
& aState
)
1193 return nsBoxFrame::GetMaxSize(aState
);
1197 nsSliderFrame::EnsureOrient()
1199 nsIFrame
* scrollbarBox
= GetScrollbar();
1201 bool isHorizontal
= (scrollbarBox
->GetStateBits() & NS_STATE_IS_HORIZONTAL
) != 0;
1203 mState
|= NS_STATE_IS_HORIZONTAL
;
1205 mState
&= ~NS_STATE_IS_HORIZONTAL
;
1210 nsSliderFrame::Notify(void)
1214 nsIFrame
* thumbFrame
= mFrames
.FirstChild();
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.
1227 if (thumbRect
.x
< mDestinationPoint
.x
)
1230 if (thumbRect
.x
+ thumbRect
.width
> mDestinationPoint
.x
)
1235 if (thumbRect
.y
< mDestinationPoint
.y
)
1238 if (thumbRect
.y
+ thumbRect
.height
> mDestinationPoint
.y
)
1247 PageScroll(mChange
);
1252 nsSliderFrame::PageScroll(nscoord aChange
)
1254 if (mContent
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::dir
,
1255 nsGkAtoms::reverse
, eCaseMatters
)) {
1258 nsIFrame
* scrollbar
= GetScrollbar();
1259 nsScrollbarFrame
* sb
= do_QueryFrame(scrollbar
);
1261 nsIScrollbarMediator
* m
= sb
->GetScrollbarMediator();
1262 sb
->SetIncrementToPage(aChange
);
1264 m
->ScrollByPage(sb
, aChange
);
1268 PageUpDown(aChange
);
1271 NS_IMPL_ISUPPORTS(nsSliderMediator
,
1272 nsIDOMEventListener
)