michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // michael@0: // Eric Vaughan michael@0: // Netscape Communications michael@0: // michael@0: // See documentation in associated header file michael@0: // michael@0: michael@0: #include "nsSliderFrame.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsIContent.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsHTMLParts.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsCSSRendering.h" michael@0: #include "nsIDOMMouseEvent.h" michael@0: #include "nsScrollbarButtonFrame.h" michael@0: #include "nsISliderListener.h" michael@0: #include "nsIScrollbarMediator.h" michael@0: #include "nsScrollbarFrame.h" michael@0: #include "nsRepeatService.h" michael@0: #include "nsBoxLayoutState.h" michael@0: #include "nsSprocketLayout.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsDisplayList.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/LookAndFeel.h" michael@0: #include "mozilla/MouseEvents.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: michael@0: bool nsSliderFrame::gMiddlePref = false; michael@0: int32_t nsSliderFrame::gSnapMultiplier; michael@0: michael@0: // Turn this on if you want to debug slider frames. michael@0: #undef DEBUG_SLIDER michael@0: michael@0: static already_AddRefed michael@0: GetContentOfBox(nsIFrame *aBox) michael@0: { michael@0: nsCOMPtr content = aBox->GetContent(); michael@0: return content.forget(); michael@0: } michael@0: michael@0: nsIFrame* michael@0: NS_NewSliderFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsSliderFrame(aPresShell, aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsSliderFrame) michael@0: michael@0: nsSliderFrame::nsSliderFrame(nsIPresShell* aPresShell, nsStyleContext* aContext): michael@0: nsBoxFrame(aPresShell, aContext), michael@0: mCurPos(0), michael@0: mChange(0), michael@0: mUserChanged(false) michael@0: { michael@0: } michael@0: michael@0: // stop timer michael@0: nsSliderFrame::~nsSliderFrame() michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsSliderFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: nsBoxFrame::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: static bool gotPrefs = false; michael@0: if (!gotPrefs) { michael@0: gotPrefs = true; michael@0: michael@0: gMiddlePref = Preferences::GetBool("middlemouse.scrollbarPosition"); michael@0: gSnapMultiplier = Preferences::GetInt("slider.snapMultiplier"); michael@0: } michael@0: michael@0: mCurPos = GetCurrentPosition(aContent); michael@0: } michael@0: michael@0: nsresult michael@0: nsSliderFrame::RemoveFrame(ChildListID aListID, michael@0: nsIFrame* aOldFrame) michael@0: { michael@0: nsresult rv = nsBoxFrame::RemoveFrame(aListID, aOldFrame); michael@0: if (mFrames.IsEmpty()) michael@0: RemoveListener(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsSliderFrame::InsertFrames(ChildListID aListID, michael@0: nsIFrame* aPrevFrame, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: bool wasEmpty = mFrames.IsEmpty(); michael@0: nsresult rv = nsBoxFrame::InsertFrames(aListID, aPrevFrame, aFrameList); michael@0: if (wasEmpty) michael@0: AddListener(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsSliderFrame::AppendFrames(ChildListID aListID, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: // if we have no children and on was added then make sure we add the michael@0: // listener michael@0: bool wasEmpty = mFrames.IsEmpty(); michael@0: nsresult rv = nsBoxFrame::AppendFrames(aListID, aFrameList); michael@0: if (wasEmpty) michael@0: AddListener(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: int32_t michael@0: nsSliderFrame::GetCurrentPosition(nsIContent* content) michael@0: { michael@0: return GetIntegerAttribute(content, nsGkAtoms::curpos, 0); michael@0: } michael@0: michael@0: int32_t michael@0: nsSliderFrame::GetMinPosition(nsIContent* content) michael@0: { michael@0: return GetIntegerAttribute(content, nsGkAtoms::minpos, 0); michael@0: } michael@0: michael@0: int32_t michael@0: nsSliderFrame::GetMaxPosition(nsIContent* content) michael@0: { michael@0: return GetIntegerAttribute(content, nsGkAtoms::maxpos, 100); michael@0: } michael@0: michael@0: int32_t michael@0: nsSliderFrame::GetIncrement(nsIContent* content) michael@0: { michael@0: return GetIntegerAttribute(content, nsGkAtoms::increment, 1); michael@0: } michael@0: michael@0: michael@0: int32_t michael@0: nsSliderFrame::GetPageIncrement(nsIContent* content) michael@0: { michael@0: return GetIntegerAttribute(content, nsGkAtoms::pageincrement, 10); michael@0: } michael@0: michael@0: int32_t michael@0: nsSliderFrame::GetIntegerAttribute(nsIContent* content, nsIAtom* atom, int32_t defaultValue) michael@0: { michael@0: nsAutoString value; michael@0: content->GetAttr(kNameSpaceID_None, atom, value); michael@0: if (!value.IsEmpty()) { michael@0: nsresult error; michael@0: michael@0: // convert it to an integer michael@0: defaultValue = value.ToInteger(&error); michael@0: } michael@0: michael@0: return defaultValue; michael@0: } michael@0: michael@0: class nsValueChangedRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: nsValueChangedRunnable(nsISliderListener* aListener, michael@0: nsIAtom* aWhich, michael@0: int32_t aValue, michael@0: bool aUserChanged) michael@0: : mListener(aListener), mWhich(aWhich), michael@0: mValue(aValue), mUserChanged(aUserChanged) michael@0: {} michael@0: michael@0: NS_IMETHODIMP Run() michael@0: { michael@0: return mListener->ValueChanged(nsDependentAtomString(mWhich), michael@0: mValue, mUserChanged); michael@0: } michael@0: michael@0: nsCOMPtr mListener; michael@0: nsCOMPtr mWhich; michael@0: int32_t mValue; michael@0: bool mUserChanged; michael@0: }; michael@0: michael@0: class nsDragStateChangedRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: nsDragStateChangedRunnable(nsISliderListener* aListener, michael@0: bool aDragBeginning) michael@0: : mListener(aListener), michael@0: mDragBeginning(aDragBeginning) michael@0: {} michael@0: michael@0: NS_IMETHODIMP Run() michael@0: { michael@0: return mListener->DragStateChanged(mDragBeginning); michael@0: } michael@0: michael@0: nsCOMPtr mListener; michael@0: bool mDragBeginning; michael@0: }; michael@0: michael@0: nsresult michael@0: nsSliderFrame::AttributeChanged(int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, michael@0: aModType); michael@0: // if the current position changes michael@0: if (aAttribute == nsGkAtoms::curpos) { michael@0: CurrentPositionChanged(); michael@0: } else if (aAttribute == nsGkAtoms::minpos || michael@0: aAttribute == nsGkAtoms::maxpos) { michael@0: // bounds check it. michael@0: michael@0: nsIFrame* scrollbarBox = GetScrollbar(); michael@0: nsCOMPtr scrollbar; michael@0: scrollbar = GetContentOfBox(scrollbarBox); michael@0: int32_t current = GetCurrentPosition(scrollbar); michael@0: int32_t min = GetMinPosition(scrollbar); michael@0: int32_t max = GetMaxPosition(scrollbar); michael@0: michael@0: // inform the parent that the minimum or maximum changed michael@0: nsIFrame* parent = GetParent(); michael@0: if (parent) { michael@0: nsCOMPtr sliderListener = do_QueryInterface(parent->GetContent()); michael@0: if (sliderListener) { michael@0: nsContentUtils::AddScriptRunner( michael@0: new nsValueChangedRunnable(sliderListener, aAttribute, michael@0: aAttribute == nsGkAtoms::minpos ? min : max, false)); michael@0: } michael@0: } michael@0: michael@0: if (current < min || current > max) michael@0: { michael@0: if (current < min || max < min) michael@0: current = min; michael@0: else if (current > max) michael@0: current = max; michael@0: michael@0: // set the new position and notify observers michael@0: nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox); michael@0: if (scrollbarFrame) { michael@0: nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator(); michael@0: if (mediator) { michael@0: mediator->PositionChanged(scrollbarFrame, GetCurrentPosition(scrollbar), current); michael@0: } michael@0: } michael@0: michael@0: nsContentUtils::AddScriptRunner( michael@0: new nsSetAttrRunnable(scrollbar, nsGkAtoms::curpos, current)); michael@0: } michael@0: } michael@0: michael@0: if (aAttribute == nsGkAtoms::minpos || michael@0: aAttribute == nsGkAtoms::maxpos || michael@0: aAttribute == nsGkAtoms::pageincrement || michael@0: aAttribute == nsGkAtoms::increment) { michael@0: michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsSliderFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: if (aBuilder->IsForEventDelivery() && isDraggingThumb()) { michael@0: // This is EVIL, we shouldn't be messing with event delivery just to get michael@0: // thumb mouse drag events to arrive at the slider! michael@0: aLists.Outlines()->AppendNewToTop(new (aBuilder) michael@0: nsDisplayEventReceiver(aBuilder, this)); michael@0: return; michael@0: } michael@0: michael@0: nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists); michael@0: } michael@0: michael@0: void michael@0: nsSliderFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: // if we are too small to have a thumb don't paint it. michael@0: nsIFrame* thumb = GetChildBox(); michael@0: michael@0: if (thumb) { michael@0: nsRect thumbRect(thumb->GetRect()); michael@0: nsMargin m; michael@0: thumb->GetMargin(m); michael@0: thumbRect.Inflate(m); michael@0: michael@0: nsRect crect; michael@0: GetClientRect(crect); michael@0: michael@0: if (crect.width < thumbRect.width || crect.height < thumbRect.height) michael@0: return; michael@0: } michael@0: michael@0: nsBoxFrame::BuildDisplayListForChildren(aBuilder, aDirtyRect, aLists); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSliderFrame::DoLayout(nsBoxLayoutState& aState) michael@0: { michael@0: // get the thumb should be our only child michael@0: nsIFrame* thumbBox = GetChildBox(); michael@0: michael@0: if (!thumbBox) { michael@0: SyncLayout(aState); michael@0: return NS_OK; michael@0: } michael@0: michael@0: EnsureOrient(); michael@0: michael@0: #ifdef DEBUG_LAYOUT michael@0: if (mState & NS_STATE_DEBUG_WAS_SET) { michael@0: if (mState & NS_STATE_SET_TO_DEBUG) michael@0: SetDebug(aState, true); michael@0: else michael@0: SetDebug(aState, false); michael@0: } michael@0: #endif michael@0: michael@0: // get the content area inside our borders michael@0: nsRect clientRect; michael@0: GetClientRect(clientRect); michael@0: michael@0: // get the scrollbar michael@0: nsIFrame* scrollbarBox = GetScrollbar(); michael@0: nsCOMPtr scrollbar; michael@0: scrollbar = GetContentOfBox(scrollbarBox); michael@0: michael@0: // get the thumb's pref size michael@0: nsSize thumbSize = thumbBox->GetPrefSize(aState); michael@0: michael@0: if (IsHorizontal()) michael@0: thumbSize.height = clientRect.height; michael@0: else michael@0: thumbSize.width = clientRect.width; michael@0: michael@0: int32_t curPos = GetCurrentPosition(scrollbar); michael@0: int32_t minPos = GetMinPosition(scrollbar); michael@0: int32_t maxPos = GetMaxPosition(scrollbar); michael@0: int32_t pageIncrement = GetPageIncrement(scrollbar); michael@0: michael@0: maxPos = std::max(minPos, maxPos); michael@0: curPos = clamped(curPos, minPos, maxPos); michael@0: michael@0: nscoord& availableLength = IsHorizontal() ? clientRect.width : clientRect.height; michael@0: nscoord& thumbLength = IsHorizontal() ? thumbSize.width : thumbSize.height; michael@0: michael@0: if ((pageIncrement + maxPos - minPos) > 0 && thumbBox->GetFlex(aState) > 0) { michael@0: float ratio = float(pageIncrement) / float(maxPos - minPos + pageIncrement); michael@0: thumbLength = std::max(thumbLength, NSToCoordRound(availableLength * ratio)); michael@0: } michael@0: michael@0: // Round the thumb's length to device pixels. michael@0: nsPresContext* presContext = PresContext(); michael@0: thumbLength = presContext->DevPixelsToAppUnits( michael@0: presContext->AppUnitsToDevPixels(thumbLength)); michael@0: michael@0: // mRatio translates the thumb position in app units to the value. michael@0: mRatio = (minPos != maxPos) ? float(availableLength - thumbLength) / float(maxPos - minPos) : 1; michael@0: michael@0: // in reverse mode, curpos is reversed such that lower values are to the michael@0: // right or bottom and increase leftwards or upwards. In this case, use the michael@0: // offset from the end instead of the beginning. michael@0: bool reverse = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir, michael@0: nsGkAtoms::reverse, eCaseMatters); michael@0: nscoord pos = reverse ? (maxPos - curPos) : (curPos - minPos); michael@0: michael@0: // set the thumb's coord to be the current pos * the ratio. michael@0: nsRect thumbRect(clientRect.x, clientRect.y, thumbSize.width, thumbSize.height); michael@0: int32_t& thumbPos = (IsHorizontal() ? thumbRect.x : thumbRect.y); michael@0: thumbPos += NSToCoordRound(pos * mRatio); michael@0: michael@0: nsRect oldThumbRect(thumbBox->GetRect()); michael@0: LayoutChildAt(aState, thumbBox, thumbRect); michael@0: michael@0: SyncLayout(aState); michael@0: michael@0: // Redraw only if thumb changed size. michael@0: if (!oldThumbRect.IsEqualInterior(thumbRect)) michael@0: Redraw(aState); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsSliderFrame::HandleEvent(nsPresContext* aPresContext, michael@0: WidgetGUIEvent* aEvent, michael@0: nsEventStatus* aEventStatus) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aEventStatus); michael@0: michael@0: // If a web page calls event.preventDefault() we still want to michael@0: // scroll when scroll arrow is clicked. See bug 511075. michael@0: if (!mContent->IsInNativeAnonymousSubtree() && michael@0: nsEventStatus_eConsumeNoDefault == *aEventStatus) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIFrame* scrollbarBox = GetScrollbar(); michael@0: nsCOMPtr scrollbar; michael@0: scrollbar = GetContentOfBox(scrollbarBox); michael@0: bool isHorizontal = IsHorizontal(); michael@0: michael@0: if (isDraggingThumb()) michael@0: { michael@0: switch (aEvent->message) { michael@0: case NS_TOUCH_MOVE: michael@0: case NS_MOUSE_MOVE: { michael@0: nsPoint eventPoint; michael@0: if (!GetEventPoint(aEvent, eventPoint)) { michael@0: break; michael@0: } michael@0: if (mChange) { michael@0: // We're in the process of moving the thumb to the mouse, michael@0: // but the mouse just moved. Make sure to update our michael@0: // destination point. michael@0: mDestinationPoint = eventPoint; michael@0: StopRepeat(); michael@0: StartRepeat(); michael@0: break; michael@0: } michael@0: michael@0: nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y; michael@0: michael@0: nsIFrame* thumbFrame = mFrames.FirstChild(); michael@0: if (!thumbFrame) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // take our current position and subtract the start location michael@0: pos -= mDragStart; michael@0: bool isMouseOutsideThumb = false; michael@0: if (gSnapMultiplier) { michael@0: nsSize thumbSize = thumbFrame->GetSize(); michael@0: if (isHorizontal) { michael@0: // horizontal scrollbar - check if mouse is above or below thumb michael@0: // XXXbz what about looking at the .y of the thumb's rect? Is that michael@0: // always zero here? michael@0: if (eventPoint.y < -gSnapMultiplier * thumbSize.height || michael@0: eventPoint.y > thumbSize.height + michael@0: gSnapMultiplier * thumbSize.height) michael@0: isMouseOutsideThumb = true; michael@0: } michael@0: else { michael@0: // vertical scrollbar - check if mouse is left or right of thumb michael@0: if (eventPoint.x < -gSnapMultiplier * thumbSize.width || michael@0: eventPoint.x > thumbSize.width + michael@0: gSnapMultiplier * thumbSize.width) michael@0: isMouseOutsideThumb = true; michael@0: } michael@0: } michael@0: if (aEvent->eventStructType == NS_TOUCH_EVENT) { michael@0: *aEventStatus = nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: if (isMouseOutsideThumb) michael@0: { michael@0: SetCurrentThumbPosition(scrollbar, mThumbStart, false, false); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // set it michael@0: SetCurrentThumbPosition(scrollbar, pos, false, true); // with snapping michael@0: } michael@0: break; michael@0: michael@0: case NS_TOUCH_END: michael@0: case NS_MOUSE_BUTTON_UP: michael@0: if (ShouldScrollForEvent(aEvent)) { michael@0: // stop capturing michael@0: AddListener(); michael@0: DragThumb(false); michael@0: if (mChange) { michael@0: StopRepeat(); michael@0: mChange = 0; michael@0: } michael@0: //we MUST call nsFrame HandleEvent for mouse ups to maintain the selection state and capture state. michael@0: return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus); michael@0: } michael@0: } michael@0: michael@0: //return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus); michael@0: return NS_OK; michael@0: } else if (ShouldScrollToClickForEvent(aEvent)) { michael@0: nsPoint eventPoint; michael@0: if (!GetEventPoint(aEvent, eventPoint)) { michael@0: return NS_OK; michael@0: } michael@0: nscoord pos = isHorizontal ? eventPoint.x : eventPoint.y; michael@0: michael@0: // adjust so that the middle of the thumb is placed under the click michael@0: nsIFrame* thumbFrame = mFrames.FirstChild(); michael@0: if (!thumbFrame) { michael@0: return NS_OK; michael@0: } michael@0: nsSize thumbSize = thumbFrame->GetSize(); michael@0: nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height; michael@0: michael@0: // set it michael@0: nsWeakFrame weakFrame(this); michael@0: // should aMaySnap be true here? michael@0: SetCurrentThumbPosition(scrollbar, pos - thumbLength/2, false, false); michael@0: NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_OK); michael@0: michael@0: DragThumb(true); michael@0: if (aEvent->eventStructType == NS_TOUCH_EVENT) { michael@0: *aEventStatus = nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: michael@0: if (isHorizontal) michael@0: mThumbStart = thumbFrame->GetPosition().x; michael@0: else michael@0: mThumbStart = thumbFrame->GetPosition().y; michael@0: michael@0: mDragStart = pos - mThumbStart; michael@0: } michael@0: michael@0: // XXX hack until handle release is actually called in nsframe. michael@0: // if (aEvent->message == NS_MOUSE_EXIT_SYNTH || aEvent->message == NS_MOUSE_RIGHT_BUTTON_UP || aEvent->message == NS_MOUSE_LEFT_BUTTON_UP) michael@0: // HandleRelease(aPresContext, aEvent, aEventStatus); michael@0: michael@0: if (aEvent->message == NS_MOUSE_EXIT_SYNTH && mChange) michael@0: HandleRelease(aPresContext, aEvent, aEventStatus); michael@0: michael@0: return nsFrame::HandleEvent(aPresContext, aEvent, aEventStatus); michael@0: } michael@0: michael@0: // Helper function to collect the "scroll to click" metric. Beware of michael@0: // caching this, users expect to be able to change the system preference michael@0: // and see the browser change its behavior immediately. michael@0: bool michael@0: nsSliderFrame::GetScrollToClick() michael@0: { michael@0: if (GetScrollbar() != this) { michael@0: return LookAndFeel::GetInt(LookAndFeel::eIntID_ScrollToClick, false); michael@0: } michael@0: michael@0: if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::movetoclick, michael@0: nsGkAtoms::_true, eCaseMatters)) { michael@0: return true; michael@0: } michael@0: if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::movetoclick, michael@0: nsGkAtoms::_false, eCaseMatters)) { michael@0: return false; michael@0: } michael@0: michael@0: #ifdef XP_MACOSX michael@0: return true; michael@0: #else michael@0: return false; michael@0: #endif michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsSliderFrame::GetScrollbar() michael@0: { michael@0: // if we are in a scrollbar then return the scrollbar's content node michael@0: // if we are not then return ours. michael@0: nsIFrame* scrollbar; michael@0: nsScrollbarButtonFrame::GetParentWithTag(nsGkAtoms::scrollbar, this, scrollbar); michael@0: michael@0: if (scrollbar == nullptr) michael@0: return this; michael@0: michael@0: return scrollbar->IsBoxFrame() ? scrollbar : this; michael@0: } michael@0: michael@0: void michael@0: nsSliderFrame::PageUpDown(nscoord change) michael@0: { michael@0: // on a page up or down get our page increment. We get this by getting the scrollbar we are in and michael@0: // asking it for the current position and the page increment. If we are not in a scrollbar we will michael@0: // get the values from our own node. michael@0: nsIFrame* scrollbarBox = GetScrollbar(); michael@0: nsCOMPtr scrollbar; michael@0: scrollbar = GetContentOfBox(scrollbarBox); michael@0: michael@0: if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir, michael@0: nsGkAtoms::reverse, eCaseMatters)) michael@0: change = -change; michael@0: michael@0: nscoord pageIncrement = GetPageIncrement(scrollbar); michael@0: int32_t curpos = GetCurrentPosition(scrollbar); michael@0: int32_t minpos = GetMinPosition(scrollbar); michael@0: int32_t maxpos = GetMaxPosition(scrollbar); michael@0: michael@0: // get the new position and make sure it is in bounds michael@0: int32_t newpos = curpos + change * pageIncrement; michael@0: if (newpos < minpos || maxpos < minpos) michael@0: newpos = minpos; michael@0: else if (newpos > maxpos) michael@0: newpos = maxpos; michael@0: michael@0: SetCurrentPositionInternal(scrollbar, newpos, true); michael@0: } michael@0: michael@0: // called when the current position changed and we need to update the thumb's location michael@0: void michael@0: nsSliderFrame::CurrentPositionChanged() michael@0: { michael@0: nsIFrame* scrollbarBox = GetScrollbar(); michael@0: nsCOMPtr scrollbar; michael@0: scrollbar = GetContentOfBox(scrollbarBox); michael@0: michael@0: // get the current position michael@0: int32_t curPos = GetCurrentPosition(scrollbar); michael@0: michael@0: // do nothing if the position did not change michael@0: if (mCurPos == curPos) michael@0: return; michael@0: michael@0: // get our current min and max position from our content node michael@0: int32_t minPos = GetMinPosition(scrollbar); michael@0: int32_t maxPos = GetMaxPosition(scrollbar); michael@0: michael@0: maxPos = std::max(minPos, maxPos); michael@0: curPos = clamped(curPos, minPos, maxPos); michael@0: michael@0: // get the thumb's rect michael@0: nsIFrame* thumbFrame = mFrames.FirstChild(); michael@0: if (!thumbFrame) michael@0: return; // The thumb may stream in asynchronously via XBL. michael@0: michael@0: nsRect thumbRect = thumbFrame->GetRect(); michael@0: michael@0: nsRect clientRect; michael@0: GetClientRect(clientRect); michael@0: michael@0: // figure out the new rect michael@0: nsRect newThumbRect(thumbRect); michael@0: michael@0: bool reverse = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir, michael@0: nsGkAtoms::reverse, eCaseMatters); michael@0: nscoord pos = reverse ? (maxPos - curPos) : (curPos - minPos); michael@0: michael@0: if (IsHorizontal()) michael@0: newThumbRect.x = clientRect.x + NSToCoordRound(pos * mRatio); michael@0: else michael@0: newThumbRect.y = clientRect.y + NSToCoordRound(pos * mRatio); michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: // avoid putting the scroll thumb at subpixel positions which cause needless invalidations michael@0: nscoord appUnitsPerPixel = PresContext()->AppUnitsPerDevPixel(); michael@0: newThumbRect = newThumbRect.ToNearestPixels(appUnitsPerPixel).ToAppUnits(appUnitsPerPixel); michael@0: #endif michael@0: // set the rect michael@0: thumbFrame->SetRect(newThumbRect); michael@0: michael@0: // Request a repaint of the scrollbar michael@0: SchedulePaint(); michael@0: michael@0: mCurPos = curPos; michael@0: michael@0: // inform the parent if it exists that the value changed michael@0: nsIFrame* parent = GetParent(); michael@0: if (parent) { michael@0: nsCOMPtr sliderListener = do_QueryInterface(parent->GetContent()); michael@0: if (sliderListener) { michael@0: nsContentUtils::AddScriptRunner( michael@0: new nsValueChangedRunnable(sliderListener, nsGkAtoms::curpos, mCurPos, mUserChanged)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static void UpdateAttribute(nsIContent* aScrollbar, nscoord aNewPos, bool aNotify, bool aIsSmooth) { michael@0: nsAutoString str; michael@0: str.AppendInt(aNewPos); michael@0: michael@0: if (aIsSmooth) { michael@0: aScrollbar->SetAttr(kNameSpaceID_None, nsGkAtoms::smooth, NS_LITERAL_STRING("true"), false); michael@0: } michael@0: aScrollbar->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, str, aNotify); michael@0: if (aIsSmooth) { michael@0: aScrollbar->UnsetAttr(kNameSpaceID_None, nsGkAtoms::smooth, false); michael@0: } michael@0: } michael@0: michael@0: // Use this function when you want to set the scroll position via the position michael@0: // of the scrollbar thumb, e.g. when dragging the slider. This function scrolls michael@0: // the content in such a way that thumbRect.x/.y becomes aNewThumbPos. michael@0: void michael@0: nsSliderFrame::SetCurrentThumbPosition(nsIContent* aScrollbar, nscoord aNewThumbPos, michael@0: bool aIsSmooth, bool aMaySnap) michael@0: { michael@0: nsRect crect; michael@0: GetClientRect(crect); michael@0: nscoord offset = IsHorizontal() ? crect.x : crect.y; michael@0: int32_t newPos = NSToIntRound((aNewThumbPos - offset) / mRatio); michael@0: michael@0: if (aMaySnap && mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::snap, michael@0: nsGkAtoms::_true, eCaseMatters)) { michael@0: // If snap="true", then the slider may only be set to min + (increment * x). michael@0: // Otherwise, the slider may be set to any positive integer. michael@0: int32_t increment = GetIncrement(aScrollbar); michael@0: newPos = NSToIntRound(newPos / float(increment)) * increment; michael@0: } michael@0: michael@0: SetCurrentPosition(aScrollbar, newPos, aIsSmooth); michael@0: } michael@0: michael@0: // Use this function when you know the target scroll position of the scrolled content. michael@0: // aNewPos should be passed to this function as a position as if the minpos is 0. michael@0: // That is, the minpos will be added to the position by this function. In a reverse michael@0: // direction slider, the newpos should be the distance from the end. michael@0: void michael@0: nsSliderFrame::SetCurrentPosition(nsIContent* aScrollbar, int32_t aNewPos, michael@0: bool aIsSmooth) michael@0: { michael@0: // get min and max position from our content node michael@0: int32_t minpos = GetMinPosition(aScrollbar); michael@0: int32_t maxpos = GetMaxPosition(aScrollbar); michael@0: michael@0: // in reverse direction sliders, flip the value so that it goes from michael@0: // right to left, or bottom to top. michael@0: if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir, michael@0: nsGkAtoms::reverse, eCaseMatters)) michael@0: aNewPos = maxpos - aNewPos; michael@0: else michael@0: aNewPos += minpos; michael@0: michael@0: // get the new position and make sure it is in bounds michael@0: if (aNewPos < minpos || maxpos < minpos) michael@0: aNewPos = minpos; michael@0: else if (aNewPos > maxpos) michael@0: aNewPos = maxpos; michael@0: michael@0: SetCurrentPositionInternal(aScrollbar, aNewPos, aIsSmooth); michael@0: } michael@0: michael@0: void michael@0: nsSliderFrame::SetCurrentPositionInternal(nsIContent* aScrollbar, int32_t aNewPos, michael@0: bool aIsSmooth) michael@0: { michael@0: nsCOMPtr scrollbar = aScrollbar; michael@0: nsIFrame* scrollbarBox = GetScrollbar(); michael@0: michael@0: mUserChanged = true; michael@0: michael@0: nsScrollbarFrame* scrollbarFrame = do_QueryFrame(scrollbarBox); michael@0: if (scrollbarFrame) { michael@0: // See if we have a mediator. michael@0: nsIScrollbarMediator* mediator = scrollbarFrame->GetScrollbarMediator(); michael@0: if (mediator) { michael@0: nsRefPtr context = PresContext(); michael@0: nsCOMPtr content = GetContent(); michael@0: mediator->PositionChanged(scrollbarFrame, GetCurrentPosition(scrollbar), aNewPos); michael@0: // 'mediator' might be dangling now... michael@0: UpdateAttribute(scrollbar, aNewPos, false, aIsSmooth); michael@0: nsIFrame* frame = content->GetPrimaryFrame(); michael@0: if (frame && frame->GetType() == nsGkAtoms::sliderFrame) { michael@0: static_cast(frame)->CurrentPositionChanged(); michael@0: } michael@0: mUserChanged = false; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: UpdateAttribute(scrollbar, aNewPos, true, aIsSmooth); michael@0: mUserChanged = false; michael@0: michael@0: #ifdef DEBUG_SLIDER michael@0: printf("Current Pos=%d\n",aNewPos); michael@0: #endif michael@0: michael@0: } michael@0: michael@0: nsIAtom* michael@0: nsSliderFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::sliderFrame; michael@0: } michael@0: michael@0: nsresult michael@0: nsSliderFrame::SetInitialChildList(ChildListID aListID, michael@0: nsFrameList& aChildList) michael@0: { michael@0: nsresult r = nsBoxFrame::SetInitialChildList(aListID, aChildList); michael@0: michael@0: AddListener(); michael@0: michael@0: return r; michael@0: } michael@0: michael@0: nsresult michael@0: nsSliderMediator::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: // Only process the event if the thumb is not being dragged. michael@0: if (mSlider && !mSlider->isDraggingThumb()) michael@0: return mSlider->StartDrag(aEvent); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSliderFrame::StartDrag(nsIDOMEvent* aEvent) michael@0: { michael@0: #ifdef DEBUG_SLIDER michael@0: printf("Begin dragging\n"); michael@0: #endif michael@0: if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, michael@0: nsGkAtoms::_true, eCaseMatters)) michael@0: return NS_OK; michael@0: michael@0: WidgetGUIEvent* event = aEvent->GetInternalNSEvent()->AsGUIEvent(); michael@0: michael@0: if (!ShouldScrollForEvent(event)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsPoint pt; michael@0: if (!GetEventPoint(event, pt)) { michael@0: return NS_OK; michael@0: } michael@0: bool isHorizontal = IsHorizontal(); michael@0: nscoord pos = isHorizontal ? pt.x : pt.y; michael@0: michael@0: // If we should scroll-to-click, first place the middle of the slider thumb michael@0: // under the mouse. michael@0: nsCOMPtr scrollbar; michael@0: nscoord newpos = pos; michael@0: bool scrollToClick = ShouldScrollToClickForEvent(event); michael@0: if (scrollToClick) { michael@0: // adjust so that the middle of the thumb is placed under the click michael@0: nsIFrame* thumbFrame = mFrames.FirstChild(); michael@0: if (!thumbFrame) { michael@0: return NS_OK; michael@0: } michael@0: nsSize thumbSize = thumbFrame->GetSize(); michael@0: nscoord thumbLength = isHorizontal ? thumbSize.width : thumbSize.height; michael@0: michael@0: newpos -= (thumbLength/2); michael@0: michael@0: nsIFrame* scrollbarBox = GetScrollbar(); michael@0: scrollbar = GetContentOfBox(scrollbarBox); michael@0: } michael@0: michael@0: DragThumb(true); michael@0: michael@0: if (scrollToClick) { michael@0: // should aMaySnap be true here? michael@0: SetCurrentThumbPosition(scrollbar, newpos, false, false); michael@0: } michael@0: michael@0: nsIFrame* thumbFrame = mFrames.FirstChild(); michael@0: if (!thumbFrame) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (isHorizontal) michael@0: mThumbStart = thumbFrame->GetPosition().x; michael@0: else michael@0: mThumbStart = thumbFrame->GetPosition().y; michael@0: michael@0: mDragStart = pos - mThumbStart; michael@0: michael@0: #ifdef DEBUG_SLIDER michael@0: printf("Pressed mDragStart=%d\n",mDragStart); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsSliderFrame::DragThumb(bool aGrabMouseEvents) michael@0: { michael@0: // inform the parent that a drag is beginning or ending michael@0: nsIFrame* parent = GetParent(); michael@0: if (parent) { michael@0: nsCOMPtr sliderListener = do_QueryInterface(parent->GetContent()); michael@0: if (sliderListener) { michael@0: nsContentUtils::AddScriptRunner( michael@0: new nsDragStateChangedRunnable(sliderListener, aGrabMouseEvents)); michael@0: } michael@0: } michael@0: michael@0: nsIPresShell::SetCapturingContent(aGrabMouseEvents ? GetContent() : nullptr, michael@0: aGrabMouseEvents ? CAPTURE_IGNOREALLOWED : 0); michael@0: } michael@0: michael@0: bool michael@0: nsSliderFrame::isDraggingThumb() michael@0: { michael@0: return (nsIPresShell::GetCapturingContent() == GetContent()); michael@0: } michael@0: michael@0: void michael@0: nsSliderFrame::AddListener() michael@0: { michael@0: if (!mMediator) { michael@0: mMediator = new nsSliderMediator(this); michael@0: } michael@0: michael@0: nsIFrame* thumbFrame = mFrames.FirstChild(); michael@0: if (!thumbFrame) { michael@0: return; michael@0: } michael@0: thumbFrame->GetContent()-> michael@0: AddSystemEventListener(NS_LITERAL_STRING("mousedown"), mMediator, michael@0: false, false); michael@0: thumbFrame->GetContent()-> michael@0: AddSystemEventListener(NS_LITERAL_STRING("touchstart"), mMediator, michael@0: false, false); michael@0: } michael@0: michael@0: void michael@0: nsSliderFrame::RemoveListener() michael@0: { michael@0: NS_ASSERTION(mMediator, "No listener was ever added!!"); michael@0: michael@0: nsIFrame* thumbFrame = mFrames.FirstChild(); michael@0: if (!thumbFrame) michael@0: return; michael@0: michael@0: thumbFrame->GetContent()-> michael@0: RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"), mMediator, false); michael@0: } michael@0: michael@0: bool michael@0: nsSliderFrame::ShouldScrollForEvent(WidgetGUIEvent* aEvent) michael@0: { michael@0: switch (aEvent->message) { michael@0: case NS_TOUCH_START: michael@0: case NS_TOUCH_END: michael@0: return true; michael@0: case NS_MOUSE_BUTTON_DOWN: michael@0: case NS_MOUSE_BUTTON_UP: { michael@0: uint16_t button = aEvent->AsMouseEvent()->button; michael@0: return (button == WidgetMouseEvent::eLeftButton) || michael@0: (button == WidgetMouseEvent::eMiddleButton && gMiddlePref); michael@0: } michael@0: default: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsSliderFrame::ShouldScrollToClickForEvent(WidgetGUIEvent* aEvent) michael@0: { michael@0: if (!ShouldScrollForEvent(aEvent)) { michael@0: return false; michael@0: } michael@0: michael@0: if (aEvent->message == NS_TOUCH_START) { michael@0: return GetScrollToClick(); michael@0: } michael@0: michael@0: if (aEvent->message != NS_MOUSE_BUTTON_DOWN) { michael@0: return false; michael@0: } michael@0: michael@0: #ifdef XP_MACOSX michael@0: // On Mac, clicking the scrollbar thumb should never scroll to click. michael@0: if (IsEventOverThumb(aEvent)) { michael@0: return false; michael@0: } michael@0: #endif michael@0: michael@0: WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); michael@0: if (mouseEvent->button == WidgetMouseEvent::eLeftButton) { michael@0: #ifdef XP_MACOSX michael@0: bool invertPref = mouseEvent->IsAlt(); michael@0: #else michael@0: bool invertPref = mouseEvent->IsShift(); michael@0: #endif michael@0: return GetScrollToClick() != invertPref; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsSliderFrame::IsEventOverThumb(WidgetGUIEvent* aEvent) michael@0: { michael@0: nsIFrame* thumbFrame = mFrames.FirstChild(); michael@0: if (!thumbFrame) { michael@0: return false; michael@0: } michael@0: michael@0: nsPoint eventPoint; michael@0: if (!GetEventPoint(aEvent, eventPoint)) { michael@0: return false; michael@0: } michael@0: michael@0: bool isHorizontal = IsHorizontal(); michael@0: nsRect thumbRect = thumbFrame->GetRect(); michael@0: nscoord eventPos = isHorizontal ? eventPoint.x : eventPoint.y; michael@0: nscoord thumbStart = isHorizontal ? thumbRect.x : thumbRect.y; michael@0: nscoord thumbEnd = isHorizontal ? thumbRect.XMost() : thumbRect.YMost(); michael@0: michael@0: return eventPos >= thumbStart && eventPos < thumbEnd; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSliderFrame::HandlePress(nsPresContext* aPresContext, michael@0: WidgetGUIEvent* aEvent, michael@0: nsEventStatus* aEventStatus) michael@0: { michael@0: if (!ShouldScrollForEvent(aEvent) || ShouldScrollToClickForEvent(aEvent)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (IsEventOverThumb(aEvent)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIFrame* thumbFrame = mFrames.FirstChild(); michael@0: if (!thumbFrame) // display:none? michael@0: return NS_OK; michael@0: michael@0: if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, michael@0: nsGkAtoms::_true, eCaseMatters)) michael@0: return NS_OK; michael@0: michael@0: nsRect thumbRect = thumbFrame->GetRect(); michael@0: michael@0: nscoord change = 1; michael@0: nsPoint eventPoint; michael@0: if (!GetEventPoint(aEvent, eventPoint)) { michael@0: return NS_OK; michael@0: } michael@0: if (IsHorizontal() ? eventPoint.x < thumbRect.x michael@0: : eventPoint.y < thumbRect.y) michael@0: change = -1; michael@0: michael@0: mChange = change; michael@0: DragThumb(true); michael@0: mDestinationPoint = eventPoint; michael@0: StartRepeat(); michael@0: PageUpDown(change); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSliderFrame::HandleRelease(nsPresContext* aPresContext, michael@0: WidgetGUIEvent* aEvent, michael@0: nsEventStatus* aEventStatus) michael@0: { michael@0: StopRepeat(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsSliderFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: // tell our mediator if we have one we are gone. michael@0: if (mMediator) { michael@0: mMediator->SetSlider(nullptr); michael@0: mMediator = nullptr; michael@0: } michael@0: StopRepeat(); michael@0: michael@0: // call base class Destroy() michael@0: nsBoxFrame::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: nsSize michael@0: nsSliderFrame::GetPrefSize(nsBoxLayoutState& aState) michael@0: { michael@0: EnsureOrient(); michael@0: return nsBoxFrame::GetPrefSize(aState); michael@0: } michael@0: michael@0: nsSize michael@0: nsSliderFrame::GetMinSize(nsBoxLayoutState& aState) michael@0: { michael@0: EnsureOrient(); michael@0: michael@0: // our min size is just our borders and padding michael@0: return nsBox::GetMinSize(aState); michael@0: } michael@0: michael@0: nsSize michael@0: nsSliderFrame::GetMaxSize(nsBoxLayoutState& aState) michael@0: { michael@0: EnsureOrient(); michael@0: return nsBoxFrame::GetMaxSize(aState); michael@0: } michael@0: michael@0: void michael@0: nsSliderFrame::EnsureOrient() michael@0: { michael@0: nsIFrame* scrollbarBox = GetScrollbar(); michael@0: michael@0: bool isHorizontal = (scrollbarBox->GetStateBits() & NS_STATE_IS_HORIZONTAL) != 0; michael@0: if (isHorizontal) michael@0: mState |= NS_STATE_IS_HORIZONTAL; michael@0: else michael@0: mState &= ~NS_STATE_IS_HORIZONTAL; michael@0: } michael@0: michael@0: michael@0: void nsSliderFrame::Notify(void) michael@0: { michael@0: bool stop = false; michael@0: michael@0: nsIFrame* thumbFrame = mFrames.FirstChild(); michael@0: if (!thumbFrame) { michael@0: StopRepeat(); michael@0: return; michael@0: } michael@0: nsRect thumbRect = thumbFrame->GetRect(); michael@0: michael@0: bool isHorizontal = IsHorizontal(); michael@0: michael@0: // See if the thumb has moved past our destination point. michael@0: // if it has we want to stop. michael@0: if (isHorizontal) { michael@0: if (mChange < 0) { michael@0: if (thumbRect.x < mDestinationPoint.x) michael@0: stop = true; michael@0: } else { michael@0: if (thumbRect.x + thumbRect.width > mDestinationPoint.x) michael@0: stop = true; michael@0: } michael@0: } else { michael@0: if (mChange < 0) { michael@0: if (thumbRect.y < mDestinationPoint.y) michael@0: stop = true; michael@0: } else { michael@0: if (thumbRect.y + thumbRect.height > mDestinationPoint.y) michael@0: stop = true; michael@0: } michael@0: } michael@0: michael@0: michael@0: if (stop) { michael@0: StopRepeat(); michael@0: } else { michael@0: PageUpDown(mChange); michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsSliderMediator, michael@0: nsIDOMEventListener)