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 "nsSplitterFrame.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIDOMXULElement.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsScrollbarButtonFrame.h" michael@0: #include "nsIDOMEventListener.h" michael@0: #include "nsIDOMMouseEvent.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsFrameList.h" michael@0: #include "nsHTMLParts.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsBoxLayoutState.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsContainerFrame.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsContentCID.h" michael@0: #include "nsStyleSet.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsDisplayList.h" michael@0: #include "nsContentUtils.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/MouseEvents.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: class nsSplitterInfo { michael@0: public: michael@0: nscoord min; michael@0: nscoord max; michael@0: nscoord current; michael@0: nscoord changed; michael@0: nsCOMPtr childElem; michael@0: int32_t flex; michael@0: int32_t index; michael@0: }; michael@0: michael@0: class nsSplitterFrameInner : public nsIDOMEventListener michael@0: { michael@0: public: michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIDOMEVENTLISTENER michael@0: michael@0: nsSplitterFrameInner(nsSplitterFrame* aSplitter) michael@0: { michael@0: mOuter = aSplitter; michael@0: mPressed = false; michael@0: } michael@0: virtual ~nsSplitterFrameInner(); michael@0: michael@0: void Disconnect() { mOuter = nullptr; } michael@0: michael@0: nsresult MouseDown(nsIDOMEvent* aMouseEvent); michael@0: nsresult MouseUp(nsIDOMEvent* aMouseEvent); michael@0: nsresult MouseMove(nsIDOMEvent* aMouseEvent); michael@0: michael@0: void MouseDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent); michael@0: void MouseUp(nsPresContext* aPresContext, WidgetGUIEvent* aEvent); michael@0: michael@0: void AdjustChildren(nsPresContext* aPresContext); michael@0: void AdjustChildren(nsPresContext* aPresContext, nsSplitterInfo* aChildInfos, int32_t aCount, bool aIsHorizontal); michael@0: michael@0: void AddRemoveSpace(nscoord aDiff, michael@0: nsSplitterInfo* aChildInfos, michael@0: int32_t aCount, michael@0: int32_t& aSpaceLeft); michael@0: michael@0: void ResizeChildTo(nsPresContext* aPresContext, michael@0: nscoord& aDiff, michael@0: nsSplitterInfo* aChildrenBeforeInfos, michael@0: nsSplitterInfo* aChildrenAfterInfos, michael@0: int32_t aChildrenBeforeCount, michael@0: int32_t aChildrenAfterCount, michael@0: bool aBounded); michael@0: michael@0: void UpdateState(); michael@0: michael@0: void AddListener(nsPresContext* aPresContext); michael@0: void RemoveListener(); michael@0: michael@0: enum ResizeType { Closest, Farthest, Flex, Grow }; michael@0: enum State { Open, CollapsedBefore, CollapsedAfter, Dragging }; michael@0: enum CollapseDirection { Before, After }; michael@0: michael@0: ResizeType GetResizeBefore(); michael@0: ResizeType GetResizeAfter(); michael@0: State GetState(); michael@0: michael@0: void Reverse(nsSplitterInfo*& aIndexes, int32_t aCount); michael@0: bool SupportsCollapseDirection(CollapseDirection aDirection); michael@0: michael@0: void EnsureOrient(); michael@0: void SetPreferredSize(nsBoxLayoutState& aState, nsIFrame* aChildBox, nscoord aOnePixel, bool aIsHorizontal, nscoord* aSize); michael@0: michael@0: nsSplitterFrame* mOuter; michael@0: bool mDidDrag; michael@0: nscoord mDragStart; michael@0: nscoord mCurrentPos; michael@0: nsIFrame* mParentBox; michael@0: bool mPressed; michael@0: nsSplitterInfo* mChildInfosBefore; michael@0: nsSplitterInfo* mChildInfosAfter; michael@0: int32_t mChildInfosBeforeCount; michael@0: int32_t mChildInfosAfterCount; michael@0: State mState; michael@0: nscoord mSplitterPos; michael@0: bool mDragging; michael@0: michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsSplitterFrameInner, nsIDOMEventListener) michael@0: michael@0: nsSplitterFrameInner::ResizeType michael@0: nsSplitterFrameInner::GetResizeBefore() michael@0: { michael@0: static nsIContent::AttrValuesArray strings[] = michael@0: {&nsGkAtoms::farthest, &nsGkAtoms::flex, nullptr}; michael@0: switch (mOuter->GetContent()->FindAttrValueIn(kNameSpaceID_None, michael@0: nsGkAtoms::resizebefore, michael@0: strings, eCaseMatters)) { michael@0: case 0: return Farthest; michael@0: case 1: return Flex; michael@0: } michael@0: return Closest; michael@0: } michael@0: michael@0: nsSplitterFrameInner::~nsSplitterFrameInner() michael@0: { michael@0: delete[] mChildInfosBefore; michael@0: delete[] mChildInfosAfter; michael@0: } michael@0: michael@0: nsSplitterFrameInner::ResizeType michael@0: nsSplitterFrameInner::GetResizeAfter() michael@0: { michael@0: static nsIContent::AttrValuesArray strings[] = michael@0: {&nsGkAtoms::farthest, &nsGkAtoms::flex, &nsGkAtoms::grow, nullptr}; michael@0: switch (mOuter->GetContent()->FindAttrValueIn(kNameSpaceID_None, michael@0: nsGkAtoms::resizeafter, michael@0: strings, eCaseMatters)) { michael@0: case 0: return Farthest; michael@0: case 1: return Flex; michael@0: case 2: return Grow; michael@0: } michael@0: return Closest; michael@0: } michael@0: michael@0: nsSplitterFrameInner::State michael@0: nsSplitterFrameInner::GetState() michael@0: { michael@0: static nsIContent::AttrValuesArray strings[] = michael@0: {&nsGkAtoms::dragging, &nsGkAtoms::collapsed, nullptr}; michael@0: static nsIContent::AttrValuesArray strings_substate[] = michael@0: {&nsGkAtoms::before, &nsGkAtoms::after, nullptr}; michael@0: switch (mOuter->GetContent()->FindAttrValueIn(kNameSpaceID_None, michael@0: nsGkAtoms::state, michael@0: strings, eCaseMatters)) { michael@0: case 0: return Dragging; michael@0: case 1: michael@0: switch (mOuter->GetContent()->FindAttrValueIn(kNameSpaceID_None, michael@0: nsGkAtoms::substate, michael@0: strings_substate, michael@0: eCaseMatters)) { michael@0: case 0: return CollapsedBefore; michael@0: case 1: return CollapsedAfter; michael@0: default: michael@0: if (SupportsCollapseDirection(After)) michael@0: return CollapsedAfter; michael@0: return CollapsedBefore; michael@0: } michael@0: } michael@0: return Open; michael@0: } michael@0: michael@0: // michael@0: // NS_NewSplitterFrame michael@0: // michael@0: // Creates a new Toolbar frame and returns it michael@0: // michael@0: nsIFrame* michael@0: NS_NewSplitterFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsSplitterFrame(aPresShell, aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsSplitterFrame) michael@0: michael@0: nsSplitterFrame::nsSplitterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: : nsBoxFrame(aPresShell, aContext), michael@0: mInner(0) michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsSplitterFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: if (mInner) { michael@0: mInner->RemoveListener(); michael@0: mInner->Disconnect(); michael@0: mInner->Release(); michael@0: mInner = nullptr; michael@0: } michael@0: nsBoxFrame::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsSplitterFrame::GetCursor(const nsPoint& aPoint, michael@0: nsIFrame::Cursor& aCursor) michael@0: { michael@0: return nsBoxFrame::GetCursor(aPoint, aCursor); michael@0: michael@0: /* michael@0: if (IsHorizontal()) michael@0: aCursor = NS_STYLE_CURSOR_N_RESIZE; michael@0: else michael@0: aCursor = NS_STYLE_CURSOR_W_RESIZE; michael@0: michael@0: return NS_OK; michael@0: */ michael@0: } michael@0: michael@0: nsresult michael@0: nsSplitterFrame::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 alignment changed. Let the grippy know michael@0: if (aAttribute == nsGkAtoms::align) { michael@0: // tell the slider its attribute changed so it can michael@0: // update itself michael@0: nsIFrame* grippy = nullptr; michael@0: nsScrollbarButtonFrame::GetChildWithTag(PresContext(), nsGkAtoms::grippy, this, grippy); michael@0: if (grippy) michael@0: grippy->AttributeChanged(aNameSpaceID, aAttribute, aModType); michael@0: } else if (aAttribute == nsGkAtoms::state) { michael@0: mInner->UpdateState(); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: /** michael@0: * Initialize us. If we are in a box get our alignment so we know what direction we are michael@0: */ michael@0: void michael@0: nsSplitterFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: MOZ_ASSERT(!mInner); michael@0: mInner = new nsSplitterFrameInner(this); michael@0: michael@0: mInner->AddRef(); michael@0: mInner->mChildInfosAfter = nullptr; michael@0: mInner->mChildInfosBefore = nullptr; michael@0: mInner->mState = nsSplitterFrameInner::Open; michael@0: mInner->mDragging = false; michael@0: michael@0: // determine orientation of parent, and if vertical, set orient to vertical michael@0: // on splitter content, then re-resolve style michael@0: // XXXbz this is pretty messed up, since this can change whether we should michael@0: // have a frame at all. This really needs a better solution. michael@0: if (aParent && aParent->IsBoxFrame()) { michael@0: if (!aParent->IsHorizontal()) { michael@0: if (!nsContentUtils::HasNonEmptyAttr(aContent, kNameSpaceID_None, michael@0: nsGkAtoms::orient)) { michael@0: aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient, michael@0: NS_LITERAL_STRING("vertical"), false); michael@0: nsStyleContext* parentStyleContext = StyleContext()->GetParent(); michael@0: nsRefPtr newContext = PresContext()->StyleSet()-> michael@0: ResolveStyleFor(aContent->AsElement(), parentStyleContext); michael@0: SetStyleContextWithoutNotification(newContext); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsBoxFrame::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: mInner->mState = nsSplitterFrameInner::Open; michael@0: mInner->AddListener(PresContext()); michael@0: mInner->mParentBox = nullptr; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSplitterFrame::DoLayout(nsBoxLayoutState& aState) michael@0: { michael@0: if (GetStateBits() & NS_FRAME_FIRST_REFLOW) michael@0: { michael@0: mInner->mParentBox = GetParentBox(); michael@0: mInner->UpdateState(); michael@0: } michael@0: michael@0: return nsBoxFrame::DoLayout(aState); michael@0: } michael@0: michael@0: michael@0: void michael@0: nsSplitterFrame::GetInitialOrientation(bool& aIsHorizontal) michael@0: { michael@0: nsIFrame* box = GetParentBox(); michael@0: if (box) { michael@0: aIsHorizontal = !box->IsHorizontal(); michael@0: } michael@0: else michael@0: nsBoxFrame::GetInitialOrientation(aIsHorizontal); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSplitterFrame::HandlePress(nsPresContext* aPresContext, michael@0: WidgetGUIEvent* aEvent, michael@0: nsEventStatus* aEventStatus) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSplitterFrame::HandleMultiplePress(nsPresContext* aPresContext, michael@0: WidgetGUIEvent* aEvent, michael@0: nsEventStatus* aEventStatus, michael@0: bool aControlHeld) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSplitterFrame::HandleDrag(nsPresContext* aPresContext, michael@0: WidgetGUIEvent* aEvent, michael@0: nsEventStatus* aEventStatus) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSplitterFrame::HandleRelease(nsPresContext* aPresContext, michael@0: WidgetGUIEvent* aEvent, michael@0: nsEventStatus* aEventStatus) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsSplitterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists); michael@0: michael@0: // if the mouse is captured always return us as the frame. michael@0: if (mInner->mDragging) michael@0: { michael@0: // XXX It's probably better not to check visibility here, right? michael@0: aLists.Outlines()->AppendNewToTop(new (aBuilder) michael@0: nsDisplayEventReceiver(aBuilder, this)); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsSplitterFrame::HandleEvent(nsPresContext* aPresContext, michael@0: WidgetGUIEvent* aEvent, michael@0: nsEventStatus* aEventStatus) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aEventStatus); michael@0: if (nsEventStatus_eConsumeNoDefault == *aEventStatus) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsWeakFrame weakFrame(this); michael@0: nsRefPtr kungFuDeathGrip(mInner); michael@0: switch (aEvent->message) { michael@0: case NS_MOUSE_MOVE: michael@0: mInner->MouseDrag(aPresContext, aEvent); michael@0: break; michael@0: michael@0: case NS_MOUSE_BUTTON_UP: michael@0: if (aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) { michael@0: mInner->MouseUp(aPresContext, aEvent); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: NS_ENSURE_STATE(weakFrame.IsAlive()); michael@0: return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus); michael@0: } michael@0: michael@0: void michael@0: nsSplitterFrameInner::MouseUp(nsPresContext* aPresContext, michael@0: WidgetGUIEvent* aEvent) michael@0: { michael@0: if (mDragging && mOuter) { michael@0: AdjustChildren(aPresContext); michael@0: AddListener(aPresContext); michael@0: nsIPresShell::SetCapturingContent(nullptr, 0); // XXXndeakin is this needed? michael@0: mDragging = false; michael@0: State newState = GetState(); michael@0: // if the state is dragging then make it Open. michael@0: if (newState == Dragging) michael@0: mOuter->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::state, EmptyString(), true); michael@0: michael@0: mPressed = false; michael@0: michael@0: // if we dragged then fire a command event. michael@0: if (mDidDrag) { michael@0: nsCOMPtr element = do_QueryInterface(mOuter->GetContent()); michael@0: element->DoCommand(); michael@0: } michael@0: michael@0: //printf("MouseUp\n"); michael@0: } michael@0: michael@0: delete[] mChildInfosBefore; michael@0: delete[] mChildInfosAfter; michael@0: mChildInfosBefore = nullptr; michael@0: mChildInfosAfter = nullptr; michael@0: mChildInfosBeforeCount = 0; michael@0: mChildInfosAfterCount = 0; michael@0: } michael@0: michael@0: void michael@0: nsSplitterFrameInner::MouseDrag(nsPresContext* aPresContext, michael@0: WidgetGUIEvent* aEvent) michael@0: { michael@0: if (mDragging && mOuter) { michael@0: michael@0: //printf("Dragging\n"); michael@0: michael@0: bool isHorizontal = !mOuter->IsHorizontal(); michael@0: // convert coord to pixels michael@0: nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, michael@0: mParentBox); michael@0: nscoord pos = isHorizontal ? pt.x : pt.y; michael@0: michael@0: // mDragStart is in frame coordinates michael@0: nscoord start = mDragStart; michael@0: michael@0: // take our current position and subtract the start location michael@0: pos -= start; michael@0: michael@0: //printf("Diff=%d\n", pos); michael@0: michael@0: ResizeType resizeAfter = GetResizeAfter(); michael@0: michael@0: bool bounded; michael@0: michael@0: if (resizeAfter == nsSplitterFrameInner::Grow) michael@0: bounded = false; michael@0: else michael@0: bounded = true; michael@0: michael@0: int i; michael@0: for (i=0; i < mChildInfosBeforeCount; i++) michael@0: mChildInfosBefore[i].changed = mChildInfosBefore[i].current; michael@0: michael@0: for (i=0; i < mChildInfosAfterCount; i++) michael@0: mChildInfosAfter[i].changed = mChildInfosAfter[i].current; michael@0: michael@0: nscoord oldPos = pos; michael@0: michael@0: ResizeChildTo(aPresContext, pos, mChildInfosBefore, mChildInfosAfter, mChildInfosBeforeCount, mChildInfosAfterCount, bounded); michael@0: michael@0: State currentState = GetState(); michael@0: bool supportsBefore = SupportsCollapseDirection(Before); michael@0: bool supportsAfter = SupportsCollapseDirection(After); michael@0: michael@0: const bool isRTL = mOuter->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; michael@0: bool pastEnd = oldPos > 0 && oldPos > pos; michael@0: bool pastBegin = oldPos < 0 && oldPos < pos; michael@0: if (isRTL) { michael@0: // Swap the boundary checks in RTL mode michael@0: bool tmp = pastEnd; michael@0: pastEnd = pastBegin; michael@0: pastBegin = tmp; michael@0: } michael@0: const bool isCollapsedBefore = pastBegin && supportsBefore; michael@0: const bool isCollapsedAfter = pastEnd && supportsAfter; michael@0: michael@0: // if we are in a collapsed position michael@0: if (isCollapsedBefore || isCollapsedAfter) michael@0: { michael@0: // and we are not collapsed then collapse michael@0: if (currentState == Dragging) { michael@0: if (pastEnd) michael@0: { michael@0: //printf("Collapse right\n"); michael@0: if (supportsAfter) michael@0: { michael@0: nsCOMPtr outer = mOuter->mContent; michael@0: outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, michael@0: NS_LITERAL_STRING("after"), michael@0: true); michael@0: outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, michael@0: NS_LITERAL_STRING("collapsed"), michael@0: true); michael@0: } michael@0: michael@0: } else if (pastBegin) michael@0: { michael@0: //printf("Collapse left\n"); michael@0: if (supportsBefore) michael@0: { michael@0: nsCOMPtr outer = mOuter->mContent; michael@0: outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, michael@0: NS_LITERAL_STRING("before"), michael@0: true); michael@0: outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, michael@0: NS_LITERAL_STRING("collapsed"), michael@0: true); michael@0: } michael@0: } michael@0: } michael@0: } else { michael@0: // if we are not in a collapsed position and we are not dragging make sure michael@0: // we are dragging. michael@0: if (currentState != Dragging) michael@0: mOuter->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::state, NS_LITERAL_STRING("dragging"), true); michael@0: AdjustChildren(aPresContext); michael@0: } michael@0: michael@0: mDidDrag = true; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSplitterFrameInner::AddListener(nsPresContext* aPresContext) michael@0: { michael@0: mOuter->GetContent()-> michael@0: AddEventListener(NS_LITERAL_STRING("mouseup"), this, false, false); michael@0: mOuter->GetContent()-> michael@0: AddEventListener(NS_LITERAL_STRING("mousedown"), this, false, false); michael@0: mOuter->GetContent()-> michael@0: AddEventListener(NS_LITERAL_STRING("mousemove"), this, false, false); michael@0: mOuter->GetContent()-> michael@0: AddEventListener(NS_LITERAL_STRING("mouseout"), this, false, false); michael@0: } michael@0: michael@0: void michael@0: nsSplitterFrameInner::RemoveListener() michael@0: { michael@0: ENSURE_TRUE(mOuter); michael@0: mOuter->GetContent()-> michael@0: RemoveEventListener(NS_LITERAL_STRING("mouseup"), this, false); michael@0: mOuter->GetContent()-> michael@0: RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, false); michael@0: mOuter->GetContent()-> michael@0: RemoveEventListener(NS_LITERAL_STRING("mousemove"), this, false); michael@0: mOuter->GetContent()-> michael@0: RemoveEventListener(NS_LITERAL_STRING("mouseout"), this, false); michael@0: } michael@0: michael@0: nsresult michael@0: nsSplitterFrameInner::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: nsAutoString eventType; michael@0: aEvent->GetType(eventType); michael@0: if (eventType.EqualsLiteral("mouseup")) michael@0: return MouseUp(aEvent); michael@0: if (eventType.EqualsLiteral("mousedown")) michael@0: return MouseDown(aEvent); michael@0: if (eventType.EqualsLiteral("mousemove") || michael@0: eventType.EqualsLiteral("mouseout")) michael@0: return MouseMove(aEvent); michael@0: michael@0: NS_ABORT(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSplitterFrameInner::MouseUp(nsIDOMEvent* aMouseEvent) michael@0: { michael@0: NS_ENSURE_TRUE(mOuter, NS_OK); michael@0: mPressed = false; michael@0: michael@0: nsIPresShell::SetCapturingContent(nullptr, 0); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSplitterFrameInner::MouseDown(nsIDOMEvent* aMouseEvent) michael@0: { michael@0: NS_ENSURE_TRUE(mOuter, NS_OK); michael@0: nsCOMPtr mouseEvent(do_QueryInterface(aMouseEvent)); michael@0: if (!mouseEvent) michael@0: return NS_OK; michael@0: michael@0: int16_t button = 0; michael@0: mouseEvent->GetButton(&button); michael@0: michael@0: // only if left button michael@0: if (button != 0) michael@0: return NS_OK; michael@0: michael@0: if (mOuter->GetContent()-> michael@0: AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, michael@0: nsGkAtoms::_true, eCaseMatters)) michael@0: return NS_OK; michael@0: michael@0: mParentBox = mOuter->GetParentBox(); michael@0: if (!mParentBox) michael@0: return NS_OK; michael@0: michael@0: // get our index michael@0: nsPresContext* outerPresContext = mOuter->PresContext(); michael@0: const nsFrameList& siblingList(mParentBox->PrincipalChildList()); michael@0: int32_t childIndex = siblingList.IndexOf(mOuter); michael@0: // if it's 0 (or not found) then stop right here. michael@0: // It might be not found if we're not in the parent's primary frame list. michael@0: if (childIndex <= 0) michael@0: return NS_OK; michael@0: michael@0: int32_t childCount = siblingList.GetLength(); michael@0: // if it's the last index then we need to allow for resizeafter="grow" michael@0: if (childIndex == childCount - 1 && GetResizeAfter() != Grow) michael@0: return NS_OK; michael@0: michael@0: nsRefPtr rc = michael@0: outerPresContext->PresShell()->CreateReferenceRenderingContext(); michael@0: nsBoxLayoutState state(outerPresContext, rc); michael@0: mCurrentPos = 0; michael@0: mPressed = true; michael@0: michael@0: mDidDrag = false; michael@0: michael@0: EnsureOrient(); michael@0: bool isHorizontal = !mOuter->IsHorizontal(); michael@0: michael@0: ResizeType resizeBefore = GetResizeBefore(); michael@0: ResizeType resizeAfter = GetResizeAfter(); michael@0: michael@0: delete[] mChildInfosBefore; michael@0: delete[] mChildInfosAfter; michael@0: mChildInfosBefore = new nsSplitterInfo[childCount]; michael@0: mChildInfosAfter = new nsSplitterInfo[childCount]; michael@0: michael@0: // create info 2 lists. One of the children before us and one after. michael@0: int32_t count = 0; michael@0: mChildInfosBeforeCount = 0; michael@0: mChildInfosAfterCount = 0; michael@0: michael@0: nsIFrame* childBox = mParentBox->GetChildBox(); michael@0: michael@0: while (nullptr != childBox) michael@0: { michael@0: nsIContent* content = childBox->GetContent(); michael@0: nsIDocument* doc = content->OwnerDoc(); michael@0: int32_t dummy; michael@0: nsIAtom* atom = doc->BindingManager()->ResolveTag(content, &dummy); michael@0: michael@0: // skip over any splitters michael@0: if (atom != nsGkAtoms::splitter) { michael@0: nsSize prefSize = childBox->GetPrefSize(state); michael@0: nsSize minSize = childBox->GetMinSize(state); michael@0: nsSize maxSize = nsBox::BoundsCheckMinMax(minSize, childBox->GetMaxSize(state)); michael@0: prefSize = nsBox::BoundsCheck(minSize, prefSize, maxSize); michael@0: michael@0: mOuter->AddMargin(childBox, minSize); michael@0: mOuter->AddMargin(childBox, prefSize); michael@0: mOuter->AddMargin(childBox, maxSize); michael@0: michael@0: nscoord flex = childBox->GetFlex(state); michael@0: michael@0: nsMargin margin(0,0,0,0); michael@0: childBox->GetMargin(margin); michael@0: nsRect r(childBox->GetRect()); michael@0: r.Inflate(margin); michael@0: michael@0: // We need to check for hidden attribute too, since treecols with michael@0: // the hidden="true" attribute are not really hidden, just collapsed michael@0: if (!content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::fixed, michael@0: nsGkAtoms::_true, eCaseMatters) && michael@0: !content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, michael@0: nsGkAtoms::_true, eCaseMatters)) { michael@0: if (count < childIndex && (resizeBefore != Flex || flex > 0)) { michael@0: mChildInfosBefore[mChildInfosBeforeCount].childElem = content; michael@0: mChildInfosBefore[mChildInfosBeforeCount].min = isHorizontal ? minSize.width : minSize.height; michael@0: mChildInfosBefore[mChildInfosBeforeCount].max = isHorizontal ? maxSize.width : maxSize.height; michael@0: mChildInfosBefore[mChildInfosBeforeCount].current = isHorizontal ? r.width : r.height; michael@0: mChildInfosBefore[mChildInfosBeforeCount].flex = flex; michael@0: mChildInfosBefore[mChildInfosBeforeCount].index = count; michael@0: mChildInfosBefore[mChildInfosBeforeCount].changed = mChildInfosBefore[mChildInfosBeforeCount].current; michael@0: mChildInfosBeforeCount++; michael@0: } else if (count > childIndex && (resizeAfter != Flex || flex > 0)) { michael@0: mChildInfosAfter[mChildInfosAfterCount].childElem = content; michael@0: mChildInfosAfter[mChildInfosAfterCount].min = isHorizontal ? minSize.width : minSize.height; michael@0: mChildInfosAfter[mChildInfosAfterCount].max = isHorizontal ? maxSize.width : maxSize.height; michael@0: mChildInfosAfter[mChildInfosAfterCount].current = isHorizontal ? r.width : r.height; michael@0: mChildInfosAfter[mChildInfosAfterCount].flex = flex; michael@0: mChildInfosAfter[mChildInfosAfterCount].index = count; michael@0: mChildInfosAfter[mChildInfosAfterCount].changed = mChildInfosAfter[mChildInfosAfterCount].current; michael@0: mChildInfosAfterCount++; michael@0: } michael@0: } michael@0: } michael@0: michael@0: childBox = childBox->GetNextBox(); michael@0: count++; michael@0: } michael@0: michael@0: if (!mParentBox->IsNormalDirection()) { michael@0: // The before array is really the after array, and the order needs to be reversed. michael@0: // First reverse both arrays. michael@0: Reverse(mChildInfosBefore, mChildInfosBeforeCount); michael@0: Reverse(mChildInfosAfter, mChildInfosAfterCount); michael@0: michael@0: // Now swap the two arrays. michael@0: nscoord newAfterCount = mChildInfosBeforeCount; michael@0: mChildInfosBeforeCount = mChildInfosAfterCount; michael@0: mChildInfosAfterCount = newAfterCount; michael@0: nsSplitterInfo* temp = mChildInfosAfter; michael@0: mChildInfosAfter = mChildInfosBefore; michael@0: mChildInfosBefore = temp; michael@0: } michael@0: michael@0: // if resizebefore is not Farthest, reverse the list because the first child michael@0: // in the list is the farthest, and we want the first child to be the closest. michael@0: if (resizeBefore != Farthest) michael@0: Reverse(mChildInfosBefore, mChildInfosBeforeCount); michael@0: michael@0: // if the resizeafter is the Farthest we must reverse the list because the first child in the list michael@0: // is the closest we want the first child to be the Farthest. michael@0: if (resizeAfter == Farthest) michael@0: Reverse(mChildInfosAfter, mChildInfosAfterCount); michael@0: michael@0: // grow only applys to the children after. If grow is set then no space should be taken out of any children after michael@0: // us. To do this we just set the size of that list to be 0. michael@0: if (resizeAfter == Grow) michael@0: mChildInfosAfterCount = 0; michael@0: michael@0: int32_t c; michael@0: nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent, michael@0: mParentBox); michael@0: if (isHorizontal) { michael@0: c = pt.x; michael@0: mSplitterPos = mOuter->mRect.x; michael@0: } else { michael@0: c = pt.y; michael@0: mSplitterPos = mOuter->mRect.y; michael@0: } michael@0: michael@0: mDragStart = c; michael@0: michael@0: //printf("Pressed mDragStart=%d\n",mDragStart); michael@0: michael@0: nsIPresShell::SetCapturingContent(mOuter->GetContent(), CAPTURE_IGNOREALLOWED); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSplitterFrameInner::MouseMove(nsIDOMEvent* aMouseEvent) michael@0: { michael@0: NS_ENSURE_TRUE(mOuter, NS_OK); michael@0: if (!mPressed) michael@0: return NS_OK; michael@0: michael@0: if (mDragging) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr kungfuDeathGrip(this); michael@0: mOuter->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::state, michael@0: NS_LITERAL_STRING("dragging"), true); michael@0: michael@0: RemoveListener(); michael@0: mDragging = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsSplitterFrameInner::Reverse(nsSplitterInfo*& aChildInfos, int32_t aCount) michael@0: { michael@0: nsSplitterInfo* infos = new nsSplitterInfo[aCount]; michael@0: michael@0: for (int i=0; i < aCount; i++) michael@0: infos[i] = aChildInfos[aCount - 1 - i]; michael@0: michael@0: delete[] aChildInfos; michael@0: aChildInfos = infos; michael@0: } michael@0: michael@0: bool michael@0: nsSplitterFrameInner::SupportsCollapseDirection michael@0: ( michael@0: nsSplitterFrameInner::CollapseDirection aDirection michael@0: ) michael@0: { michael@0: static nsIContent::AttrValuesArray strings[] = michael@0: {&nsGkAtoms::before, &nsGkAtoms::after, &nsGkAtoms::both, nullptr}; michael@0: michael@0: switch (mOuter->mContent->FindAttrValueIn(kNameSpaceID_None, michael@0: nsGkAtoms::collapse, michael@0: strings, eCaseMatters)) { michael@0: case 0: michael@0: return (aDirection == Before); michael@0: case 1: michael@0: return (aDirection == After); michael@0: case 2: michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsSplitterFrameInner::UpdateState() michael@0: { michael@0: // State Transitions: michael@0: // Open -> Dragging michael@0: // Open -> CollapsedBefore michael@0: // Open -> CollapsedAfter michael@0: // CollapsedBefore -> Open michael@0: // CollapsedBefore -> Dragging michael@0: // CollapsedAfter -> Open michael@0: // CollapsedAfter -> Dragging michael@0: // Dragging -> Open michael@0: // Dragging -> CollapsedBefore (auto collapse) michael@0: // Dragging -> CollapsedAfter (auto collapse) michael@0: michael@0: State newState = GetState(); michael@0: michael@0: if (newState == mState) { michael@0: // No change. michael@0: return; michael@0: } michael@0: michael@0: if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) && michael@0: mOuter->GetParent()->IsBoxFrame()) { michael@0: // Find the splitter's immediate sibling. michael@0: nsIFrame* splitterSibling; michael@0: if (newState == CollapsedBefore || mState == CollapsedBefore) { michael@0: splitterSibling = mOuter->GetPrevSibling(); michael@0: } else { michael@0: splitterSibling = mOuter->GetNextSibling(); michael@0: } michael@0: michael@0: if (splitterSibling) { michael@0: nsCOMPtr sibling = splitterSibling->GetContent(); michael@0: if (sibling) { michael@0: if (mState == CollapsedBefore || mState == CollapsedAfter) { michael@0: // CollapsedBefore -> Open michael@0: // CollapsedBefore -> Dragging michael@0: // CollapsedAfter -> Open michael@0: // CollapsedAfter -> Dragging michael@0: nsContentUtils::AddScriptRunner( michael@0: new nsUnsetAttrRunnable(sibling, nsGkAtoms::collapsed)); michael@0: } else if ((mState == Open || mState == Dragging) michael@0: && (newState == CollapsedBefore || michael@0: newState == CollapsedAfter)) { michael@0: // Open -> CollapsedBefore / CollapsedAfter michael@0: // Dragging -> CollapsedBefore / CollapsedAfter michael@0: nsContentUtils::AddScriptRunner( michael@0: new nsSetAttrRunnable(sibling, nsGkAtoms::collapsed, michael@0: NS_LITERAL_STRING("true"))); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: mState = newState; michael@0: } michael@0: michael@0: void michael@0: nsSplitterFrameInner::EnsureOrient() michael@0: { michael@0: bool isHorizontal = !(mParentBox->GetStateBits() & NS_STATE_IS_HORIZONTAL); michael@0: if (isHorizontal) michael@0: mOuter->mState |= NS_STATE_IS_HORIZONTAL; michael@0: else michael@0: mOuter->mState &= ~NS_STATE_IS_HORIZONTAL; michael@0: } michael@0: michael@0: void michael@0: nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext) michael@0: { michael@0: EnsureOrient(); michael@0: bool isHorizontal = !mOuter->IsHorizontal(); michael@0: michael@0: AdjustChildren(aPresContext, mChildInfosBefore, mChildInfosBeforeCount, isHorizontal); michael@0: AdjustChildren(aPresContext, mChildInfosAfter, mChildInfosAfterCount, isHorizontal); michael@0: } michael@0: michael@0: static nsIFrame* GetChildBoxForContent(nsIFrame* aParentBox, nsIContent* aContent) michael@0: { michael@0: nsIFrame* childBox = aParentBox->GetChildBox(); michael@0: michael@0: while (nullptr != childBox) { michael@0: if (childBox->GetContent() == aContent) { michael@0: return childBox; michael@0: } michael@0: childBox = childBox->GetNextBox(); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext, nsSplitterInfo* aChildInfos, int32_t aCount, bool aIsHorizontal) michael@0: { michael@0: ///printf("------- AdjustChildren------\n"); michael@0: michael@0: nsBoxLayoutState state(aPresContext); michael@0: michael@0: nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1); michael@0: michael@0: // first set all the widths. michael@0: nsIFrame* child = mOuter->GetChildBox(); michael@0: while(child) michael@0: { michael@0: SetPreferredSize(state, child, onePixel, aIsHorizontal, nullptr); michael@0: child = child->GetNextBox(); michael@0: } michael@0: michael@0: // now set our changed widths. michael@0: for (int i=0; i < aCount; i++) michael@0: { michael@0: nscoord pref = aChildInfos[i].changed; michael@0: nsIFrame* childBox = GetChildBoxForContent(mParentBox, aChildInfos[i].childElem); michael@0: michael@0: if (childBox) { michael@0: SetPreferredSize(state, childBox, onePixel, aIsHorizontal, &pref); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSplitterFrameInner::SetPreferredSize(nsBoxLayoutState& aState, nsIFrame* aChildBox, nscoord aOnePixel, bool aIsHorizontal, nscoord* aSize) michael@0: { michael@0: nsRect rect(aChildBox->GetRect()); michael@0: nscoord pref = 0; michael@0: michael@0: if (!aSize) michael@0: { michael@0: if (aIsHorizontal) michael@0: pref = rect.width; michael@0: else michael@0: pref = rect.height; michael@0: } else { michael@0: pref = *aSize; michael@0: } michael@0: michael@0: nsMargin margin(0,0,0,0); michael@0: aChildBox->GetMargin(margin); michael@0: michael@0: nsCOMPtr attribute; michael@0: michael@0: if (aIsHorizontal) { michael@0: pref -= (margin.left + margin.right); michael@0: attribute = nsGkAtoms::width; michael@0: } else { michael@0: pref -= (margin.top + margin.bottom); michael@0: attribute = nsGkAtoms::height; michael@0: } michael@0: michael@0: nsIContent* content = aChildBox->GetContent(); michael@0: michael@0: // set its preferred size. michael@0: nsAutoString prefValue; michael@0: prefValue.AppendInt(pref/aOnePixel); michael@0: if (content->AttrValueIs(kNameSpaceID_None, attribute, michael@0: prefValue, eCaseMatters)) michael@0: return; michael@0: michael@0: nsWeakFrame weakBox(aChildBox); michael@0: content->SetAttr(kNameSpaceID_None, attribute, prefValue, true); michael@0: ENSURE_TRUE(weakBox.IsAlive()); michael@0: aState.PresShell()->FrameNeedsReflow(aChildBox, nsIPresShell::eStyleChange, michael@0: NS_FRAME_IS_DIRTY); michael@0: } michael@0: michael@0: michael@0: void michael@0: nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff, michael@0: nsSplitterInfo* aChildInfos, michael@0: int32_t aCount, michael@0: int32_t& aSpaceLeft) michael@0: { michael@0: aSpaceLeft = 0; michael@0: michael@0: for (int i=0; i < aCount; i++) { michael@0: nscoord min = aChildInfos[i].min; michael@0: nscoord max = aChildInfos[i].max; michael@0: nscoord& c = aChildInfos[i].changed; michael@0: michael@0: // figure our how much space to add or remove michael@0: if (c + aDiff < min) { michael@0: aDiff += (c - min); michael@0: c = min; michael@0: } else if (c + aDiff > max) { michael@0: aDiff -= (max - c); michael@0: c = max; michael@0: } else { michael@0: c += aDiff; michael@0: aDiff = 0; michael@0: } michael@0: michael@0: // there is not space left? We are done michael@0: if (aDiff == 0) michael@0: break; michael@0: } michael@0: michael@0: aSpaceLeft = aDiff; michael@0: } michael@0: michael@0: /** michael@0: * Ok if we want to resize a child we will know the actual size in pixels we want it to be. michael@0: * This is not the preferred size. But they only way we can change a child is my manipulating its michael@0: * preferred size. So give the actual pixel size this return method will return figure out the preferred michael@0: * size and set it. michael@0: */ michael@0: michael@0: void michael@0: nsSplitterFrameInner::ResizeChildTo(nsPresContext* aPresContext, michael@0: nscoord& aDiff, michael@0: nsSplitterInfo* aChildrenBeforeInfos, michael@0: nsSplitterInfo* aChildrenAfterInfos, michael@0: int32_t aChildrenBeforeCount, michael@0: int32_t aChildrenAfterCount, michael@0: bool aBounded) michael@0: { michael@0: nscoord spaceLeft; michael@0: AddRemoveSpace(aDiff, aChildrenBeforeInfos,aChildrenBeforeCount,spaceLeft); michael@0: michael@0: // if there is any space left over remove it from the dif we were originally given michael@0: aDiff -= spaceLeft; michael@0: AddRemoveSpace(-aDiff, aChildrenAfterInfos,aChildrenAfterCount,spaceLeft); michael@0: michael@0: if (spaceLeft != 0) { michael@0: if (aBounded) { michael@0: aDiff += spaceLeft; michael@0: AddRemoveSpace(spaceLeft, aChildrenBeforeInfos,aChildrenBeforeCount,spaceLeft); michael@0: } else { michael@0: spaceLeft = 0; michael@0: } michael@0: } michael@0: }