diff options
Diffstat (limited to 'layout/xul/nsSplitterFrame.cpp')
-rw-r--r-- | layout/xul/nsSplitterFrame.cpp | 944 |
1 files changed, 944 insertions, 0 deletions
diff --git a/layout/xul/nsSplitterFrame.cpp b/layout/xul/nsSplitterFrame.cpp new file mode 100644 index 0000000000..79143df15e --- /dev/null +++ b/layout/xul/nsSplitterFrame.cpp @@ -0,0 +1,944 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Eric Vaughan +// Netscape Communications +// +// See documentation in associated header file +// + +#include "gfxContext.h" +#include "nsSplitterFrame.h" +#include "nsGkAtoms.h" +#include "nsXULElement.h" +#include "nsPresContext.h" +#include "mozilla/dom/Document.h" +#include "nsNameSpaceManager.h" +#include "nsScrollbarButtonFrame.h" +#include "nsIDOMEventListener.h" +#include "nsFrameList.h" +#include "nsHTMLParts.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/CSSOrderAwareFrameIterator.h" +#include "nsBoxLayoutState.h" +#include "nsContainerFrame.h" +#include "nsContentCID.h" +#include "nsLayoutUtils.h" +#include "nsDisplayList.h" +#include "nsContentUtils.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/MouseEvent.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/PresShell.h" +#include "mozilla/UniquePtr.h" + +using namespace mozilla; + +using mozilla::dom::Element; +using mozilla::dom::Event; + +class nsSplitterInfo { + public: + nscoord min; + nscoord max; + nscoord current; + nscoord changed; + nsCOMPtr<nsIContent> childElem; + int32_t flex; + int32_t index; +}; + +class nsSplitterFrameInner final : public nsIDOMEventListener { + protected: + virtual ~nsSplitterFrameInner(); + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + + explicit nsSplitterFrameInner(nsSplitterFrame* aSplitter) + : mDidDrag(false), + mDragStart(0), + mParentBox(nullptr), + mChildInfosBeforeCount(0), + mChildInfosAfterCount(0), + mState(Open), + mSplitterPos(0), + mDragging(false) { + mOuter = aSplitter; + mPressed = false; + } + + void Disconnect() { mOuter = nullptr; } + + nsresult MouseDown(Event* aMouseEvent); + nsresult MouseUp(Event* aMouseEvent); + nsresult MouseMove(Event* aMouseEvent); + + void MouseDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent); + void MouseUp(nsPresContext* aPresContext, WidgetGUIEvent* aEvent); + + void AdjustChildren(nsPresContext* aPresContext); + void AdjustChildren(nsPresContext* aPresContext, nsSplitterInfo* aChildInfos, + int32_t aCount, bool aIsHorizontal); + + void AddRemoveSpace(nscoord aDiff, nsSplitterInfo* aChildInfos, + int32_t aCount, int32_t& aSpaceLeft); + + void ResizeChildTo(nscoord& aDiff, nsSplitterInfo* aChildrenBeforeInfos, + nsSplitterInfo* aChildrenAfterInfos, + int32_t aChildrenBeforeCount, int32_t aChildrenAfterCount, + bool aBounded); + + void UpdateState(); + + void AddListener(); + void RemoveListener(); + + enum ResizeType { Closest, Farthest, Flex, Grow }; + enum State { Open, CollapsedBefore, CollapsedAfter, Dragging }; + enum CollapseDirection { Before, After }; + + ResizeType GetResizeBefore(); + ResizeType GetResizeAfter(); + State GetState(); + + void Reverse(UniquePtr<nsSplitterInfo[]>& aIndexes, int32_t aCount); + bool SupportsCollapseDirection(CollapseDirection aDirection); + + void EnsureOrient(); + void SetPreferredSize(nsBoxLayoutState& aState, nsIFrame* aChildBox, + nscoord aOnePixel, bool aIsHorizontal, nscoord* aSize); + + nsSplitterFrame* mOuter; + bool mDidDrag; + nscoord mDragStart; + nsIFrame* mParentBox; + bool mPressed; + UniquePtr<nsSplitterInfo[]> mChildInfosBefore; + UniquePtr<nsSplitterInfo[]> mChildInfosAfter; + int32_t mChildInfosBeforeCount; + int32_t mChildInfosAfterCount; + State mState; + nscoord mSplitterPos; + bool mDragging; + + const Element* SplitterElement() const { + return mOuter->GetContent()->AsElement(); + } +}; + +NS_IMPL_ISUPPORTS(nsSplitterFrameInner, nsIDOMEventListener) + +nsSplitterFrameInner::ResizeType nsSplitterFrameInner::GetResizeBefore() { + static Element::AttrValuesArray strings[] = {nsGkAtoms::farthest, + nsGkAtoms::flex, nullptr}; + switch (SplitterElement()->FindAttrValueIn( + kNameSpaceID_None, nsGkAtoms::resizebefore, strings, eCaseMatters)) { + case 0: + return Farthest; + case 1: + return Flex; + } + return Closest; +} + +nsSplitterFrameInner::~nsSplitterFrameInner() = default; + +nsSplitterFrameInner::ResizeType nsSplitterFrameInner::GetResizeAfter() { + static Element::AttrValuesArray strings[] = { + nsGkAtoms::farthest, nsGkAtoms::flex, nsGkAtoms::grow, nullptr}; + switch (SplitterElement()->FindAttrValueIn( + kNameSpaceID_None, nsGkAtoms::resizeafter, strings, eCaseMatters)) { + case 0: + return Farthest; + case 1: + return Flex; + case 2: + return Grow; + } + return Closest; +} + +nsSplitterFrameInner::State nsSplitterFrameInner::GetState() { + static Element::AttrValuesArray strings[] = {nsGkAtoms::dragging, + nsGkAtoms::collapsed, nullptr}; + static Element::AttrValuesArray strings_substate[] = { + nsGkAtoms::before, nsGkAtoms::after, nullptr}; + switch (SplitterElement()->FindAttrValueIn( + kNameSpaceID_None, nsGkAtoms::state, strings, eCaseMatters)) { + case 0: + return Dragging; + case 1: + switch (SplitterElement()->FindAttrValueIn( + kNameSpaceID_None, nsGkAtoms::substate, strings_substate, + eCaseMatters)) { + case 0: + return CollapsedBefore; + case 1: + return CollapsedAfter; + default: + if (SupportsCollapseDirection(After)) return CollapsedAfter; + return CollapsedBefore; + } + } + return Open; +} + +// +// NS_NewSplitterFrame +// +// Creates a new Toolbar frame and returns it +// +nsIFrame* NS_NewSplitterFrame(PresShell* aPresShell, ComputedStyle* aStyle) { + return new (aPresShell) nsSplitterFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsSplitterFrame) + +nsSplitterFrame::nsSplitterFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : nsBoxFrame(aStyle, aPresContext, kClassID), mInner(0) {} + +void nsSplitterFrame::DestroyFrom(nsIFrame* aDestructRoot, + PostDestroyData& aPostDestroyData) { + if (mInner) { + mInner->RemoveListener(); + mInner->Disconnect(); + mInner->Release(); + mInner = nullptr; + } + nsBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData); +} + +nsresult nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + nsresult rv = + nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); + if (aAttribute == nsGkAtoms::state) { + mInner->UpdateState(); + } + + return rv; +} + +/** + * Initialize us. If we are in a box get our alignment so we know what direction + * we are + */ +void nsSplitterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + MOZ_ASSERT(!mInner); + mInner = new nsSplitterFrameInner(this); + + mInner->AddRef(); + + // determine orientation of parent, and if vertical, set orient to vertical + // on splitter content, then re-resolve style + // XXXbz this is pretty messed up, since this can change whether we should + // have a frame at all. This really needs a better solution. + if (aParent && aParent->IsXULBoxFrame()) { + if (!aParent->IsXULHorizontal()) { + if (!nsContentUtils::HasNonEmptyAttr(aContent, kNameSpaceID_None, + nsGkAtoms::orient)) { + aContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, + u"vertical"_ns, false); + } + } + } + + nsBoxFrame::Init(aContent, aParent, aPrevInFlow); + + mInner->mState = nsSplitterFrameInner::Open; + mInner->AddListener(); + mInner->mParentBox = nullptr; +} + +NS_IMETHODIMP +nsSplitterFrame::DoXULLayout(nsBoxLayoutState& aState) { + if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { + mInner->mParentBox = nsIFrame::GetParentXULBox(this); + mInner->UpdateState(); + } + + return nsBoxFrame::DoXULLayout(aState); +} + +void nsSplitterFrame::GetInitialOrientation(bool& aIsHorizontal) { + nsIFrame* box = nsIFrame::GetParentXULBox(this); + if (box) { + aIsHorizontal = !box->IsXULHorizontal(); + } else + nsBoxFrame::GetInitialOrientation(aIsHorizontal); +} + +NS_IMETHODIMP +nsSplitterFrame::HandlePress(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) { + return NS_OK; +} + +NS_IMETHODIMP +nsSplitterFrame::HandleMultiplePress(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus, + bool aControlHeld) { + return NS_OK; +} + +NS_IMETHODIMP +nsSplitterFrame::HandleDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) { + return NS_OK; +} + +NS_IMETHODIMP +nsSplitterFrame::HandleRelease(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) { + return NS_OK; +} + +void nsSplitterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + nsBoxFrame::BuildDisplayList(aBuilder, aLists); + + // if the mouse is captured always return us as the frame. + if (mInner->mDragging && aBuilder->IsForEventDelivery()) { + // XXX It's probably better not to check visibility here, right? + aLists.Outlines()->AppendNewToTop<nsDisplayEventReceiver>(aBuilder, this); + return; + } +} + +nsresult nsSplitterFrame::HandleEvent(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent, + nsEventStatus* aEventStatus) { + NS_ENSURE_ARG_POINTER(aEventStatus); + if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { + return NS_OK; + } + + AutoWeakFrame weakFrame(this); + RefPtr<nsSplitterFrameInner> inner(mInner); + switch (aEvent->mMessage) { + case eMouseMove: + inner->MouseDrag(aPresContext, aEvent); + break; + + case eMouseUp: + if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) { + inner->MouseUp(aPresContext, aEvent); + } + break; + + default: + break; + } + + NS_ENSURE_STATE(weakFrame.IsAlive()); + return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus); +} + +void nsSplitterFrameInner::MouseUp(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent) { + if (mDragging && mOuter) { + AdjustChildren(aPresContext); + AddListener(); + PresShell::ReleaseCapturingContent(); // XXXndeakin is this needed? + mDragging = false; + State newState = GetState(); + // if the state is dragging then make it Open. + if (newState == Dragging) { + mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None, + nsGkAtoms::state, u""_ns, true); + } + + mPressed = false; + + // if we dragged then fire a command event. + if (mDidDrag) { + RefPtr<nsXULElement> element = + nsXULElement::FromNode(mOuter->GetContent()); + element->DoCommand(); + } + + // printf("MouseUp\n"); + } + + mChildInfosBefore = nullptr; + mChildInfosAfter = nullptr; + mChildInfosBeforeCount = 0; + mChildInfosAfterCount = 0; +} + +void nsSplitterFrameInner::MouseDrag(nsPresContext* aPresContext, + WidgetGUIEvent* aEvent) { + if (mDragging && mOuter) { + // printf("Dragging\n"); + + bool isHorizontal = !mOuter->IsXULHorizontal(); + // convert coord to pixels + nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo( + aEvent, RelativeTo{mParentBox}); + nscoord pos = isHorizontal ? pt.x : pt.y; + + // mDragStart is in frame coordinates + nscoord start = mDragStart; + + // take our current position and subtract the start location + pos -= start; + + // printf("Diff=%d\n", pos); + + ResizeType resizeAfter = GetResizeAfter(); + + bool bounded; + + if (resizeAfter == nsSplitterFrameInner::Grow) + bounded = false; + else + bounded = true; + + int i; + for (i = 0; i < mChildInfosBeforeCount; i++) + mChildInfosBefore[i].changed = mChildInfosBefore[i].current; + + for (i = 0; i < mChildInfosAfterCount; i++) + mChildInfosAfter[i].changed = mChildInfosAfter[i].current; + + nscoord oldPos = pos; + + ResizeChildTo(pos, mChildInfosBefore.get(), mChildInfosAfter.get(), + mChildInfosBeforeCount, mChildInfosAfterCount, bounded); + + State currentState = GetState(); + bool supportsBefore = SupportsCollapseDirection(Before); + bool supportsAfter = SupportsCollapseDirection(After); + + const bool isRTL = + mOuter->StyleVisibility()->mDirection == StyleDirection::Rtl; + bool pastEnd = oldPos > 0 && oldPos > pos; + bool pastBegin = oldPos < 0 && oldPos < pos; + if (isRTL) { + // Swap the boundary checks in RTL mode + bool tmp = pastEnd; + pastEnd = pastBegin; + pastBegin = tmp; + } + const bool isCollapsedBefore = pastBegin && supportsBefore; + const bool isCollapsedAfter = pastEnd && supportsAfter; + + // if we are in a collapsed position + if (isCollapsedBefore || isCollapsedAfter) { + // and we are not collapsed then collapse + if (currentState == Dragging) { + if (pastEnd) { + // printf("Collapse right\n"); + if (supportsAfter) { + RefPtr<Element> outer = mOuter->mContent->AsElement(); + outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"after"_ns, + true); + outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns, + true); + } + + } else if (pastBegin) { + // printf("Collapse left\n"); + if (supportsBefore) { + RefPtr<Element> outer = mOuter->mContent->AsElement(); + outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"before"_ns, + true); + outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns, + true); + } + } + } + } else { + // if we are not in a collapsed position and we are not dragging make sure + // we are dragging. + if (currentState != Dragging) { + mOuter->mContent->AsElement()->SetAttr( + kNameSpaceID_None, nsGkAtoms::state, u"dragging"_ns, true); + } + AdjustChildren(aPresContext); + } + + mDidDrag = true; + } +} + +void nsSplitterFrameInner::AddListener() { + mOuter->GetContent()->AddEventListener(u"mouseup"_ns, this, false, false); + mOuter->GetContent()->AddEventListener(u"mousedown"_ns, this, false, false); + mOuter->GetContent()->AddEventListener(u"mousemove"_ns, this, false, false); + mOuter->GetContent()->AddEventListener(u"mouseout"_ns, this, false, false); +} + +void nsSplitterFrameInner::RemoveListener() { + NS_ENSURE_TRUE_VOID(mOuter); + mOuter->GetContent()->RemoveEventListener(u"mouseup"_ns, this, false); + mOuter->GetContent()->RemoveEventListener(u"mousedown"_ns, this, false); + mOuter->GetContent()->RemoveEventListener(u"mousemove"_ns, this, false); + mOuter->GetContent()->RemoveEventListener(u"mouseout"_ns, this, false); +} + +nsresult nsSplitterFrameInner::HandleEvent(dom::Event* aEvent) { + nsAutoString eventType; + aEvent->GetType(eventType); + if (eventType.EqualsLiteral("mouseup")) return MouseUp(aEvent); + if (eventType.EqualsLiteral("mousedown")) return MouseDown(aEvent); + if (eventType.EqualsLiteral("mousemove") || + eventType.EqualsLiteral("mouseout")) + return MouseMove(aEvent); + + MOZ_ASSERT_UNREACHABLE("Unexpected eventType"); + return NS_OK; +} + +nsresult nsSplitterFrameInner::MouseUp(Event* aMouseEvent) { + NS_ENSURE_TRUE(mOuter, NS_OK); + mPressed = false; + + PresShell::ReleaseCapturingContent(); + + return NS_OK; +} + +nsresult nsSplitterFrameInner::MouseDown(Event* aMouseEvent) { + NS_ENSURE_TRUE(mOuter, NS_OK); + dom::MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent(); + if (!mouseEvent) { + return NS_OK; + } + + // only if left button + if (mouseEvent->Button() != 0) return NS_OK; + + if (SplitterElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, + nsGkAtoms::_true, eCaseMatters)) + return NS_OK; + + mParentBox = nsIFrame::GetParentXULBox(mOuter); + if (!mParentBox) return NS_OK; + + // get our index + nsPresContext* outerPresContext = mOuter->PresContext(); + const nsFrameList& siblingList(mParentBox->PrincipalChildList()); + int32_t childIndex = siblingList.IndexOf(mOuter); + // if it's 0 (or not found) then stop right here. + // It might be not found if we're not in the parent's primary frame list. + if (childIndex <= 0) return NS_OK; + + int32_t childCount = siblingList.GetLength(); + // if it's the last index then we need to allow for resizeafter="grow" + if (childIndex == childCount - 1 && GetResizeAfter() != Grow) return NS_OK; + + RefPtr<gfxContext> rc = + outerPresContext->PresShell()->CreateReferenceRenderingContext(); + nsBoxLayoutState state(outerPresContext, rc); + mPressed = true; + + mDidDrag = false; + + EnsureOrient(); + bool isHorizontal = !mOuter->IsXULHorizontal(); + + ResizeType resizeBefore = GetResizeBefore(); + ResizeType resizeAfter = GetResizeAfter(); + + mChildInfosBefore = MakeUnique<nsSplitterInfo[]>(childCount); + mChildInfosAfter = MakeUnique<nsSplitterInfo[]>(childCount); + + // create info 2 lists. One of the children before us and one after. + int32_t count = 0; + mChildInfosBeforeCount = 0; + mChildInfosAfterCount = 0; + + CSSOrderAwareFrameIterator iter( + mParentBox, layout::kPrincipalList, + CSSOrderAwareFrameIterator::ChildFilter::IncludeAll, + CSSOrderAwareFrameIterator::OrderState::Unknown, + CSSOrderAwareFrameIterator::OrderingProperty::BoxOrdinalGroup); + for (; !iter.AtEnd(); iter.Next()) { + nsIFrame* childBox = iter.get(); + nsIContent* content = childBox->GetContent(); + + // skip over any splitters + if (content->NodeInfo()->NameAtom() != nsGkAtoms::splitter) { + nsSize prefSize = childBox->GetXULPrefSize(state); + nsSize minSize = childBox->GetXULMinSize(state); + nsSize maxSize = nsIFrame::XULBoundsCheckMinMax( + minSize, childBox->GetXULMaxSize(state)); + prefSize = nsIFrame::XULBoundsCheck(minSize, prefSize, maxSize); + + nsSplitterFrame::AddXULMargin(childBox, minSize); + nsSplitterFrame::AddXULMargin(childBox, prefSize); + nsSplitterFrame::AddXULMargin(childBox, maxSize); + + nscoord flex = childBox->GetXULFlex(); + + nsMargin margin(0, 0, 0, 0); + childBox->GetXULMargin(margin); + nsRect r(childBox->GetRect()); + r.Inflate(margin); + + // We need to check for hidden attribute too, since treecols with + // the hidden="true" attribute are not really hidden, just collapsed + if (!content->IsElement() || (!content->AsElement()->AttrValueIs( + kNameSpaceID_None, nsGkAtoms::fixed, + nsGkAtoms::_true, eCaseMatters) && + !content->AsElement()->AttrValueIs( + kNameSpaceID_None, nsGkAtoms::hidden, + nsGkAtoms::_true, eCaseMatters))) { + if (count < childIndex && (resizeBefore != Flex || flex > 0)) { + mChildInfosBefore[mChildInfosBeforeCount].childElem = content; + mChildInfosBefore[mChildInfosBeforeCount].min = + isHorizontal ? minSize.width : minSize.height; + mChildInfosBefore[mChildInfosBeforeCount].max = + isHorizontal ? maxSize.width : maxSize.height; + mChildInfosBefore[mChildInfosBeforeCount].current = + isHorizontal ? r.width : r.height; + mChildInfosBefore[mChildInfosBeforeCount].flex = flex; + mChildInfosBefore[mChildInfosBeforeCount].index = count; + mChildInfosBefore[mChildInfosBeforeCount].changed = + mChildInfosBefore[mChildInfosBeforeCount].current; + mChildInfosBeforeCount++; + } else if (count > childIndex && (resizeAfter != Flex || flex > 0)) { + mChildInfosAfter[mChildInfosAfterCount].childElem = content; + mChildInfosAfter[mChildInfosAfterCount].min = + isHorizontal ? minSize.width : minSize.height; + mChildInfosAfter[mChildInfosAfterCount].max = + isHorizontal ? maxSize.width : maxSize.height; + mChildInfosAfter[mChildInfosAfterCount].current = + isHorizontal ? r.width : r.height; + mChildInfosAfter[mChildInfosAfterCount].flex = flex; + mChildInfosAfter[mChildInfosAfterCount].index = count; + mChildInfosAfter[mChildInfosAfterCount].changed = + mChildInfosAfter[mChildInfosAfterCount].current; + mChildInfosAfterCount++; + } + } + } + count++; + } + + if (!mParentBox->IsXULNormalDirection()) { + // The before array is really the after array, and the order needs to be + // reversed. First reverse both arrays. + Reverse(mChildInfosBefore, mChildInfosBeforeCount); + Reverse(mChildInfosAfter, mChildInfosAfterCount); + + // Now swap the two arrays. + std::swap(mChildInfosBeforeCount, mChildInfosAfterCount); + std::swap(mChildInfosBefore, mChildInfosAfter); + } + + // if resizebefore is not Farthest, reverse the list because the first child + // in the list is the farthest, and we want the first child to be the closest. + if (resizeBefore != Farthest) + Reverse(mChildInfosBefore, mChildInfosBeforeCount); + + // if the resizeafter is the Farthest we must reverse the list because the + // first child in the list is the closest we want the first child to be the + // Farthest. + if (resizeAfter == Farthest) Reverse(mChildInfosAfter, mChildInfosAfterCount); + + // grow only applys to the children after. If grow is set then no space should + // be taken out of any children after us. To do this we just set the size of + // that list to be 0. + if (resizeAfter == Grow) mChildInfosAfterCount = 0; + + int32_t c; + nsPoint pt = + nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent, mParentBox); + if (isHorizontal) { + c = pt.x; + mSplitterPos = mOuter->mRect.x; + } else { + c = pt.y; + mSplitterPos = mOuter->mRect.y; + } + + mDragStart = c; + + // printf("Pressed mDragStart=%d\n",mDragStart); + + PresShell::SetCapturingContent(mOuter->GetContent(), + CaptureFlags::IgnoreAllowedState); + + return NS_OK; +} + +nsresult nsSplitterFrameInner::MouseMove(Event* aMouseEvent) { + NS_ENSURE_TRUE(mOuter, NS_OK); + if (!mPressed) return NS_OK; + + if (mDragging) return NS_OK; + + nsCOMPtr<nsIDOMEventListener> kungfuDeathGrip(this); + mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::state, + u"dragging"_ns, true); + + RemoveListener(); + mDragging = true; + + return NS_OK; +} + +void nsSplitterFrameInner::Reverse(UniquePtr<nsSplitterInfo[]>& aChildInfos, + int32_t aCount) { + UniquePtr<nsSplitterInfo[]> infos(new nsSplitterInfo[aCount]); + + for (int i = 0; i < aCount; i++) infos[i] = aChildInfos[aCount - 1 - i]; + + aChildInfos = std::move(infos); +} + +bool nsSplitterFrameInner::SupportsCollapseDirection( + nsSplitterFrameInner::CollapseDirection aDirection) { + static Element::AttrValuesArray strings[] = { + nsGkAtoms::before, nsGkAtoms::after, nsGkAtoms::both, nullptr}; + + switch (SplitterElement()->FindAttrValueIn( + kNameSpaceID_None, nsGkAtoms::collapse, strings, eCaseMatters)) { + case 0: + return (aDirection == Before); + case 1: + return (aDirection == After); + case 2: + return true; + } + + return false; +} + +void nsSplitterFrameInner::UpdateState() { + // State Transitions: + // Open -> Dragging + // Open -> CollapsedBefore + // Open -> CollapsedAfter + // CollapsedBefore -> Open + // CollapsedBefore -> Dragging + // CollapsedAfter -> Open + // CollapsedAfter -> Dragging + // Dragging -> Open + // Dragging -> CollapsedBefore (auto collapse) + // Dragging -> CollapsedAfter (auto collapse) + + State newState = GetState(); + + if (newState == mState) { + // No change. + return; + } + + if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) && + mOuter->GetParent()->IsXULBoxFrame()) { + // Find the splitter's immediate sibling. + nsIFrame* splitterSibling; + if (newState == CollapsedBefore || mState == CollapsedBefore) { + splitterSibling = mOuter->GetPrevSibling(); + } else { + splitterSibling = mOuter->GetNextSibling(); + } + + if (splitterSibling) { + nsCOMPtr<nsIContent> sibling = splitterSibling->GetContent(); + if (sibling && sibling->IsElement()) { + if (mState == CollapsedBefore || mState == CollapsedAfter) { + // CollapsedBefore -> Open + // CollapsedBefore -> Dragging + // CollapsedAfter -> Open + // CollapsedAfter -> Dragging + nsContentUtils::AddScriptRunner(new nsUnsetAttrRunnable( + sibling->AsElement(), nsGkAtoms::collapsed)); + } else if ((mState == Open || mState == Dragging) && + (newState == CollapsedBefore || + newState == CollapsedAfter)) { + // Open -> CollapsedBefore / CollapsedAfter + // Dragging -> CollapsedBefore / CollapsedAfter + nsContentUtils::AddScriptRunner(new nsSetAttrRunnable( + sibling->AsElement(), nsGkAtoms::collapsed, u"true"_ns)); + } + } + } + } + mState = newState; +} + +void nsSplitterFrameInner::EnsureOrient() { + bool isHorizontal = !mParentBox->HasAnyStateBits(NS_STATE_IS_HORIZONTAL); + if (isHorizontal) + mOuter->AddStateBits(NS_STATE_IS_HORIZONTAL); + else + mOuter->RemoveStateBits(NS_STATE_IS_HORIZONTAL); +} + +void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext) { + EnsureOrient(); + bool isHorizontal = !mOuter->IsXULHorizontal(); + + AdjustChildren(aPresContext, mChildInfosBefore.get(), mChildInfosBeforeCount, + isHorizontal); + AdjustChildren(aPresContext, mChildInfosAfter.get(), mChildInfosAfterCount, + isHorizontal); +} + +static nsIFrame* GetChildBoxForContent(nsIFrame* aParentBox, + nsIContent* aContent) { + nsIFrame* childBox = nsIFrame::GetChildXULBox(aParentBox); + + while (childBox) { + if (childBox->GetContent() == aContent) { + return childBox; + } + childBox = nsIFrame::GetNextXULBox(childBox); + } + return nullptr; +} + +void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext, + nsSplitterInfo* aChildInfos, + int32_t aCount, bool aIsHorizontal) { + /// printf("------- AdjustChildren------\n"); + + nsBoxLayoutState state(aPresContext); + + nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1); + + // first set all the widths. + nsIFrame* child = nsIFrame::GetChildXULBox(mOuter); + while (child) { + SetPreferredSize(state, child, onePixel, aIsHorizontal, nullptr); + child = nsIFrame::GetNextXULBox(child); + } + + // now set our changed widths. + for (int i = 0; i < aCount; i++) { + nscoord pref = aChildInfos[i].changed; + nsIFrame* childBox = + GetChildBoxForContent(mParentBox, aChildInfos[i].childElem); + + if (childBox) { + SetPreferredSize(state, childBox, onePixel, aIsHorizontal, &pref); + } + } +} + +void nsSplitterFrameInner::SetPreferredSize(nsBoxLayoutState& aState, + nsIFrame* aChildBox, + nscoord aOnePixel, + bool aIsHorizontal, + nscoord* aSize) { + nsRect rect(aChildBox->GetRect()); + nscoord pref = 0; + + if (!aSize) { + if (aIsHorizontal) + pref = rect.width; + else + pref = rect.height; + } else { + pref = *aSize; + } + + nsMargin margin(0, 0, 0, 0); + aChildBox->GetXULMargin(margin); + + RefPtr<nsAtom> attribute; + + if (aIsHorizontal) { + pref -= (margin.left + margin.right); + attribute = nsGkAtoms::width; + } else { + pref -= (margin.top + margin.bottom); + attribute = nsGkAtoms::height; + } + + nsIContent* content = aChildBox->GetContent(); + if (!content->IsElement()) { + return; + } + + // set its preferred size. + nsAutoString prefValue; + prefValue.AppendInt(pref / aOnePixel); + if (content->AsElement()->AttrValueIs(kNameSpaceID_None, attribute, prefValue, + eCaseMatters)) { + return; + } + + AutoWeakFrame weakBox(aChildBox); + content->AsElement()->SetAttr(kNameSpaceID_None, attribute, prefValue, true); + NS_ENSURE_TRUE_VOID(weakBox.IsAlive()); + aState.PresShell()->FrameNeedsReflow(aChildBox, IntrinsicDirty::StyleChange, + NS_FRAME_IS_DIRTY); +} + +void nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff, + nsSplitterInfo* aChildInfos, + int32_t aCount, int32_t& aSpaceLeft) { + aSpaceLeft = 0; + + for (int i = 0; i < aCount; i++) { + nscoord min = aChildInfos[i].min; + nscoord max = aChildInfos[i].max; + nscoord& c = aChildInfos[i].changed; + + // figure our how much space to add or remove + if (c + aDiff < min) { + aDiff += (c - min); + c = min; + } else if (c + aDiff > max) { + aDiff -= (max - c); + c = max; + } else { + c += aDiff; + aDiff = 0; + } + + // there is not space left? We are done + if (aDiff == 0) break; + } + + aSpaceLeft = aDiff; +} + +/** + * Ok if we want to resize a child we will know the actual size in pixels we + * want it to be. This is not the preferred size. But they only way we can + * change a child is my manipulating its preferred size. So give the actual + * pixel size this return method will return figure out the preferred size and + * set it. + */ + +void nsSplitterFrameInner::ResizeChildTo(nscoord& aDiff, + nsSplitterInfo* aChildrenBeforeInfos, + nsSplitterInfo* aChildrenAfterInfos, + int32_t aChildrenBeforeCount, + int32_t aChildrenAfterCount, + bool aBounded) { + nscoord spaceLeft; + AddRemoveSpace(aDiff, aChildrenBeforeInfos, aChildrenBeforeCount, spaceLeft); + + // if there is any space left over remove it from the dif we were originally + // given + aDiff -= spaceLeft; + AddRemoveSpace(-aDiff, aChildrenAfterInfos, aChildrenAfterCount, spaceLeft); + + if (spaceLeft != 0) { + if (aBounded) { + aDiff += spaceLeft; + AddRemoveSpace(spaceLeft, aChildrenBeforeInfos, aChildrenBeforeCount, + spaceLeft); + } + } +} |