Bumping manifests a=b2g-bump
[gecko.git] / layout / xul / nsSplitterFrame.cpp
blob994a4c79be303b1dd154a5b82c16e19b4011dd63
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 //
7 // Eric Vaughan
8 // Netscape Communications
9 //
10 // See documentation in associated header file
13 #include "nsSplitterFrame.h"
14 #include "nsGkAtoms.h"
15 #include "nsIDOMElement.h"
16 #include "nsIDOMXULElement.h"
17 #include "nsPresContext.h"
18 #include "nsRenderingContext.h"
19 #include "nsIDocument.h"
20 #include "nsNameSpaceManager.h"
21 #include "nsScrollbarButtonFrame.h"
22 #include "nsIDOMEventListener.h"
23 #include "nsIDOMMouseEvent.h"
24 #include "nsIPresShell.h"
25 #include "nsFrameList.h"
26 #include "nsHTMLParts.h"
27 #include "nsStyleContext.h"
28 #include "nsBoxLayoutState.h"
29 #include "nsIServiceManager.h"
30 #include "nsContainerFrame.h"
31 #include "nsAutoPtr.h"
32 #include "nsContentCID.h"
33 #include "nsStyleSet.h"
34 #include "nsLayoutUtils.h"
35 #include "nsDisplayList.h"
36 #include "nsContentUtils.h"
37 #include "mozilla/dom/Element.h"
38 #include "mozilla/MouseEvents.h"
40 using namespace mozilla;
42 class nsSplitterInfo {
43 public:
44 nscoord min;
45 nscoord max;
46 nscoord current;
47 nscoord changed;
48 nsCOMPtr<nsIContent> childElem;
49 int32_t flex;
50 int32_t index;
53 class nsSplitterFrameInner MOZ_FINAL : public nsIDOMEventListener
55 protected:
56 virtual ~nsSplitterFrameInner();
58 public:
60 NS_DECL_ISUPPORTS
61 NS_DECL_NSIDOMEVENTLISTENER
63 explicit nsSplitterFrameInner(nsSplitterFrame* aSplitter)
65 mOuter = aSplitter;
66 mPressed = false;
69 void Disconnect() { mOuter = nullptr; }
71 nsresult MouseDown(nsIDOMEvent* aMouseEvent);
72 nsresult MouseUp(nsIDOMEvent* aMouseEvent);
73 nsresult MouseMove(nsIDOMEvent* aMouseEvent);
75 void MouseDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
76 void MouseUp(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
78 void AdjustChildren(nsPresContext* aPresContext);
79 void AdjustChildren(nsPresContext* aPresContext, nsSplitterInfo* aChildInfos, int32_t aCount, bool aIsHorizontal);
81 void AddRemoveSpace(nscoord aDiff,
82 nsSplitterInfo* aChildInfos,
83 int32_t aCount,
84 int32_t& aSpaceLeft);
86 void ResizeChildTo(nsPresContext* aPresContext,
87 nscoord& aDiff,
88 nsSplitterInfo* aChildrenBeforeInfos,
89 nsSplitterInfo* aChildrenAfterInfos,
90 int32_t aChildrenBeforeCount,
91 int32_t aChildrenAfterCount,
92 bool aBounded);
94 void UpdateState();
96 void AddListener(nsPresContext* aPresContext);
97 void RemoveListener();
99 enum ResizeType { Closest, Farthest, Flex, Grow };
100 enum State { Open, CollapsedBefore, CollapsedAfter, Dragging };
101 enum CollapseDirection { Before, After };
103 ResizeType GetResizeBefore();
104 ResizeType GetResizeAfter();
105 State GetState();
107 void Reverse(nsSplitterInfo*& aIndexes, int32_t aCount);
108 bool SupportsCollapseDirection(CollapseDirection aDirection);
110 void EnsureOrient();
111 void SetPreferredSize(nsBoxLayoutState& aState, nsIFrame* aChildBox, nscoord aOnePixel, bool aIsHorizontal, nscoord* aSize);
113 nsSplitterFrame* mOuter;
114 bool mDidDrag;
115 nscoord mDragStart;
116 nscoord mCurrentPos;
117 nsIFrame* mParentBox;
118 bool mPressed;
119 nsSplitterInfo* mChildInfosBefore;
120 nsSplitterInfo* mChildInfosAfter;
121 int32_t mChildInfosBeforeCount;
122 int32_t mChildInfosAfterCount;
123 State mState;
124 nscoord mSplitterPos;
125 bool mDragging;
129 NS_IMPL_ISUPPORTS(nsSplitterFrameInner, nsIDOMEventListener)
131 nsSplitterFrameInner::ResizeType
132 nsSplitterFrameInner::GetResizeBefore()
134 static nsIContent::AttrValuesArray strings[] =
135 {&nsGkAtoms::farthest, &nsGkAtoms::flex, nullptr};
136 switch (mOuter->GetContent()->FindAttrValueIn(kNameSpaceID_None,
137 nsGkAtoms::resizebefore,
138 strings, eCaseMatters)) {
139 case 0: return Farthest;
140 case 1: return Flex;
142 return Closest;
145 nsSplitterFrameInner::~nsSplitterFrameInner()
147 delete[] mChildInfosBefore;
148 delete[] mChildInfosAfter;
151 nsSplitterFrameInner::ResizeType
152 nsSplitterFrameInner::GetResizeAfter()
154 static nsIContent::AttrValuesArray strings[] =
155 {&nsGkAtoms::farthest, &nsGkAtoms::flex, &nsGkAtoms::grow, nullptr};
156 switch (mOuter->GetContent()->FindAttrValueIn(kNameSpaceID_None,
157 nsGkAtoms::resizeafter,
158 strings, eCaseMatters)) {
159 case 0: return Farthest;
160 case 1: return Flex;
161 case 2: return Grow;
163 return Closest;
166 nsSplitterFrameInner::State
167 nsSplitterFrameInner::GetState()
169 static nsIContent::AttrValuesArray strings[] =
170 {&nsGkAtoms::dragging, &nsGkAtoms::collapsed, nullptr};
171 static nsIContent::AttrValuesArray strings_substate[] =
172 {&nsGkAtoms::before, &nsGkAtoms::after, nullptr};
173 switch (mOuter->GetContent()->FindAttrValueIn(kNameSpaceID_None,
174 nsGkAtoms::state,
175 strings, eCaseMatters)) {
176 case 0: return Dragging;
177 case 1:
178 switch (mOuter->GetContent()->FindAttrValueIn(kNameSpaceID_None,
179 nsGkAtoms::substate,
180 strings_substate,
181 eCaseMatters)) {
182 case 0: return CollapsedBefore;
183 case 1: return CollapsedAfter;
184 default:
185 if (SupportsCollapseDirection(After))
186 return CollapsedAfter;
187 return CollapsedBefore;
190 return Open;
194 // NS_NewSplitterFrame
196 // Creates a new Toolbar frame and returns it
198 nsIFrame*
199 NS_NewSplitterFrame (nsIPresShell* aPresShell, nsStyleContext* aContext)
201 return new (aPresShell) nsSplitterFrame(aPresShell, aContext);
204 NS_IMPL_FRAMEARENA_HELPERS(nsSplitterFrame)
206 nsSplitterFrame::nsSplitterFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
207 : nsBoxFrame(aPresShell, aContext),
208 mInner(0)
212 void
213 nsSplitterFrame::DestroyFrom(nsIFrame* aDestructRoot)
215 if (mInner) {
216 mInner->RemoveListener();
217 mInner->Disconnect();
218 mInner->Release();
219 mInner = nullptr;
221 nsBoxFrame::DestroyFrom(aDestructRoot);
225 nsresult
226 nsSplitterFrame::GetCursor(const nsPoint& aPoint,
227 nsIFrame::Cursor& aCursor)
229 return nsBoxFrame::GetCursor(aPoint, aCursor);
232 if (IsHorizontal())
233 aCursor = NS_STYLE_CURSOR_N_RESIZE;
234 else
235 aCursor = NS_STYLE_CURSOR_W_RESIZE;
237 return NS_OK;
241 nsresult
242 nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID,
243 nsIAtom* aAttribute,
244 int32_t aModType)
246 nsresult rv = nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute,
247 aModType);
248 // if the alignment changed. Let the grippy know
249 if (aAttribute == nsGkAtoms::align) {
250 // tell the slider its attribute changed so it can
251 // update itself
252 nsIFrame* grippy = nullptr;
253 nsScrollbarButtonFrame::GetChildWithTag(PresContext(), nsGkAtoms::grippy, this, grippy);
254 if (grippy)
255 grippy->AttributeChanged(aNameSpaceID, aAttribute, aModType);
256 } else if (aAttribute == nsGkAtoms::state) {
257 mInner->UpdateState();
260 return rv;
264 * Initialize us. If we are in a box get our alignment so we know what direction we are
266 void
267 nsSplitterFrame::Init(nsIContent* aContent,
268 nsContainerFrame* aParent,
269 nsIFrame* aPrevInFlow)
271 MOZ_ASSERT(!mInner);
272 mInner = new nsSplitterFrameInner(this);
274 mInner->AddRef();
275 mInner->mChildInfosAfter = nullptr;
276 mInner->mChildInfosBefore = nullptr;
277 mInner->mState = nsSplitterFrameInner::Open;
278 mInner->mDragging = false;
280 // determine orientation of parent, and if vertical, set orient to vertical
281 // on splitter content, then re-resolve style
282 // XXXbz this is pretty messed up, since this can change whether we should
283 // have a frame at all. This really needs a better solution.
284 if (aParent && aParent->IsBoxFrame()) {
285 if (!aParent->IsHorizontal()) {
286 if (!nsContentUtils::HasNonEmptyAttr(aContent, kNameSpaceID_None,
287 nsGkAtoms::orient)) {
288 aContent->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
289 NS_LITERAL_STRING("vertical"), false);
290 nsStyleContext* parentStyleContext = StyleContext()->GetParent();
291 nsRefPtr<nsStyleContext> newContext = PresContext()->StyleSet()->
292 ResolveStyleFor(aContent->AsElement(), parentStyleContext);
293 SetStyleContextWithoutNotification(newContext);
298 nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
300 mInner->mState = nsSplitterFrameInner::Open;
301 mInner->AddListener(PresContext());
302 mInner->mParentBox = nullptr;
305 NS_IMETHODIMP
306 nsSplitterFrame::DoLayout(nsBoxLayoutState& aState)
308 if (GetStateBits() & NS_FRAME_FIRST_REFLOW)
310 mInner->mParentBox = nsBox::GetParentBox(this);
311 mInner->UpdateState();
314 return nsBoxFrame::DoLayout(aState);
318 void
319 nsSplitterFrame::GetInitialOrientation(bool& aIsHorizontal)
321 nsIFrame* box = nsBox::GetParentBox(this);
322 if (box) {
323 aIsHorizontal = !box->IsHorizontal();
325 else
326 nsBoxFrame::GetInitialOrientation(aIsHorizontal);
329 NS_IMETHODIMP
330 nsSplitterFrame::HandlePress(nsPresContext* aPresContext,
331 WidgetGUIEvent* aEvent,
332 nsEventStatus* aEventStatus)
334 return NS_OK;
337 NS_IMETHODIMP
338 nsSplitterFrame::HandleMultiplePress(nsPresContext* aPresContext,
339 WidgetGUIEvent* aEvent,
340 nsEventStatus* aEventStatus,
341 bool aControlHeld)
343 return NS_OK;
346 NS_IMETHODIMP
347 nsSplitterFrame::HandleDrag(nsPresContext* aPresContext,
348 WidgetGUIEvent* aEvent,
349 nsEventStatus* aEventStatus)
351 return NS_OK;
354 NS_IMETHODIMP
355 nsSplitterFrame::HandleRelease(nsPresContext* aPresContext,
356 WidgetGUIEvent* aEvent,
357 nsEventStatus* aEventStatus)
359 return NS_OK;
362 void
363 nsSplitterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
364 const nsRect& aDirtyRect,
365 const nsDisplayListSet& aLists)
367 nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
369 // if the mouse is captured always return us as the frame.
370 if (mInner->mDragging)
372 // XXX It's probably better not to check visibility here, right?
373 aLists.Outlines()->AppendNewToTop(new (aBuilder)
374 nsDisplayEventReceiver(aBuilder, this));
375 return;
379 nsresult
380 nsSplitterFrame::HandleEvent(nsPresContext* aPresContext,
381 WidgetGUIEvent* aEvent,
382 nsEventStatus* aEventStatus)
384 NS_ENSURE_ARG_POINTER(aEventStatus);
385 if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
386 return NS_OK;
389 nsWeakFrame weakFrame(this);
390 nsRefPtr<nsSplitterFrameInner> kungFuDeathGrip(mInner);
391 switch (aEvent->message) {
392 case NS_MOUSE_MOVE:
393 mInner->MouseDrag(aPresContext, aEvent);
394 break;
396 case NS_MOUSE_BUTTON_UP:
397 if (aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) {
398 mInner->MouseUp(aPresContext, aEvent);
400 break;
403 NS_ENSURE_STATE(weakFrame.IsAlive());
404 return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
407 void
408 nsSplitterFrameInner::MouseUp(nsPresContext* aPresContext,
409 WidgetGUIEvent* aEvent)
411 if (mDragging && mOuter) {
412 AdjustChildren(aPresContext);
413 AddListener(aPresContext);
414 nsIPresShell::SetCapturingContent(nullptr, 0); // XXXndeakin is this needed?
415 mDragging = false;
416 State newState = GetState();
417 // if the state is dragging then make it Open.
418 if (newState == Dragging)
419 mOuter->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::state, EmptyString(), true);
421 mPressed = false;
423 // if we dragged then fire a command event.
424 if (mDidDrag) {
425 nsCOMPtr<nsIDOMXULElement> element = do_QueryInterface(mOuter->GetContent());
426 element->DoCommand();
429 //printf("MouseUp\n");
432 delete[] mChildInfosBefore;
433 delete[] mChildInfosAfter;
434 mChildInfosBefore = nullptr;
435 mChildInfosAfter = nullptr;
436 mChildInfosBeforeCount = 0;
437 mChildInfosAfterCount = 0;
440 void
441 nsSplitterFrameInner::MouseDrag(nsPresContext* aPresContext,
442 WidgetGUIEvent* aEvent)
444 if (mDragging && mOuter) {
446 //printf("Dragging\n");
448 bool isHorizontal = !mOuter->IsHorizontal();
449 // convert coord to pixels
450 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
451 mParentBox);
452 nscoord pos = isHorizontal ? pt.x : pt.y;
454 // mDragStart is in frame coordinates
455 nscoord start = mDragStart;
457 // take our current position and subtract the start location
458 pos -= start;
460 //printf("Diff=%d\n", pos);
462 ResizeType resizeAfter = GetResizeAfter();
464 bool bounded;
466 if (resizeAfter == nsSplitterFrameInner::Grow)
467 bounded = false;
468 else
469 bounded = true;
471 int i;
472 for (i=0; i < mChildInfosBeforeCount; i++)
473 mChildInfosBefore[i].changed = mChildInfosBefore[i].current;
475 for (i=0; i < mChildInfosAfterCount; i++)
476 mChildInfosAfter[i].changed = mChildInfosAfter[i].current;
478 nscoord oldPos = pos;
480 ResizeChildTo(aPresContext, pos, mChildInfosBefore, mChildInfosAfter, mChildInfosBeforeCount, mChildInfosAfterCount, bounded);
482 State currentState = GetState();
483 bool supportsBefore = SupportsCollapseDirection(Before);
484 bool supportsAfter = SupportsCollapseDirection(After);
486 const bool isRTL = mOuter->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
487 bool pastEnd = oldPos > 0 && oldPos > pos;
488 bool pastBegin = oldPos < 0 && oldPos < pos;
489 if (isRTL) {
490 // Swap the boundary checks in RTL mode
491 bool tmp = pastEnd;
492 pastEnd = pastBegin;
493 pastBegin = tmp;
495 const bool isCollapsedBefore = pastBegin && supportsBefore;
496 const bool isCollapsedAfter = pastEnd && supportsAfter;
498 // if we are in a collapsed position
499 if (isCollapsedBefore || isCollapsedAfter)
501 // and we are not collapsed then collapse
502 if (currentState == Dragging) {
503 if (pastEnd)
505 //printf("Collapse right\n");
506 if (supportsAfter)
508 nsCOMPtr<nsIContent> outer = mOuter->mContent;
509 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate,
510 NS_LITERAL_STRING("after"),
511 true);
512 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
513 NS_LITERAL_STRING("collapsed"),
514 true);
517 } else if (pastBegin)
519 //printf("Collapse left\n");
520 if (supportsBefore)
522 nsCOMPtr<nsIContent> outer = mOuter->mContent;
523 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate,
524 NS_LITERAL_STRING("before"),
525 true);
526 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
527 NS_LITERAL_STRING("collapsed"),
528 true);
532 } else {
533 // if we are not in a collapsed position and we are not dragging make sure
534 // we are dragging.
535 if (currentState != Dragging)
536 mOuter->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::state, NS_LITERAL_STRING("dragging"), true);
537 AdjustChildren(aPresContext);
540 mDidDrag = true;
544 void
545 nsSplitterFrameInner::AddListener(nsPresContext* aPresContext)
547 mOuter->GetContent()->
548 AddEventListener(NS_LITERAL_STRING("mouseup"), this, false, false);
549 mOuter->GetContent()->
550 AddEventListener(NS_LITERAL_STRING("mousedown"), this, false, false);
551 mOuter->GetContent()->
552 AddEventListener(NS_LITERAL_STRING("mousemove"), this, false, false);
553 mOuter->GetContent()->
554 AddEventListener(NS_LITERAL_STRING("mouseout"), this, false, false);
557 void
558 nsSplitterFrameInner::RemoveListener()
560 ENSURE_TRUE(mOuter);
561 mOuter->GetContent()->
562 RemoveEventListener(NS_LITERAL_STRING("mouseup"), this, false);
563 mOuter->GetContent()->
564 RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, false);
565 mOuter->GetContent()->
566 RemoveEventListener(NS_LITERAL_STRING("mousemove"), this, false);
567 mOuter->GetContent()->
568 RemoveEventListener(NS_LITERAL_STRING("mouseout"), this, false);
571 nsresult
572 nsSplitterFrameInner::HandleEvent(nsIDOMEvent* aEvent)
574 nsAutoString eventType;
575 aEvent->GetType(eventType);
576 if (eventType.EqualsLiteral("mouseup"))
577 return MouseUp(aEvent);
578 if (eventType.EqualsLiteral("mousedown"))
579 return MouseDown(aEvent);
580 if (eventType.EqualsLiteral("mousemove") ||
581 eventType.EqualsLiteral("mouseout"))
582 return MouseMove(aEvent);
584 NS_ABORT();
585 return NS_OK;
588 nsresult
589 nsSplitterFrameInner::MouseUp(nsIDOMEvent* aMouseEvent)
591 NS_ENSURE_TRUE(mOuter, NS_OK);
592 mPressed = false;
594 nsIPresShell::SetCapturingContent(nullptr, 0);
596 return NS_OK;
599 nsresult
600 nsSplitterFrameInner::MouseDown(nsIDOMEvent* aMouseEvent)
602 NS_ENSURE_TRUE(mOuter, NS_OK);
603 nsCOMPtr<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(aMouseEvent));
604 if (!mouseEvent)
605 return NS_OK;
607 int16_t button = 0;
608 mouseEvent->GetButton(&button);
610 // only if left button
611 if (button != 0)
612 return NS_OK;
614 if (mOuter->GetContent()->
615 AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
616 nsGkAtoms::_true, eCaseMatters))
617 return NS_OK;
619 mParentBox = nsBox::GetParentBox(mOuter);
620 if (!mParentBox)
621 return NS_OK;
623 // get our index
624 nsPresContext* outerPresContext = mOuter->PresContext();
625 const nsFrameList& siblingList(mParentBox->PrincipalChildList());
626 int32_t childIndex = siblingList.IndexOf(mOuter);
627 // if it's 0 (or not found) then stop right here.
628 // It might be not found if we're not in the parent's primary frame list.
629 if (childIndex <= 0)
630 return NS_OK;
632 int32_t childCount = siblingList.GetLength();
633 // if it's the last index then we need to allow for resizeafter="grow"
634 if (childIndex == childCount - 1 && GetResizeAfter() != Grow)
635 return NS_OK;
637 nsRefPtr<nsRenderingContext> rc =
638 outerPresContext->PresShell()->CreateReferenceRenderingContext();
639 nsBoxLayoutState state(outerPresContext, rc);
640 mCurrentPos = 0;
641 mPressed = true;
643 mDidDrag = false;
645 EnsureOrient();
646 bool isHorizontal = !mOuter->IsHorizontal();
648 ResizeType resizeBefore = GetResizeBefore();
649 ResizeType resizeAfter = GetResizeAfter();
651 delete[] mChildInfosBefore;
652 delete[] mChildInfosAfter;
653 mChildInfosBefore = new nsSplitterInfo[childCount];
654 mChildInfosAfter = new nsSplitterInfo[childCount];
656 // create info 2 lists. One of the children before us and one after.
657 int32_t count = 0;
658 mChildInfosBeforeCount = 0;
659 mChildInfosAfterCount = 0;
661 nsIFrame* childBox = nsBox::GetChildBox(mParentBox);
663 while (nullptr != childBox)
665 nsIContent* content = childBox->GetContent();
666 nsIDocument* doc = content->OwnerDoc();
667 int32_t dummy;
668 nsIAtom* atom = doc->BindingManager()->ResolveTag(content, &dummy);
670 // skip over any splitters
671 if (atom != nsGkAtoms::splitter) {
672 nsSize prefSize = childBox->GetPrefSize(state);
673 nsSize minSize = childBox->GetMinSize(state);
674 nsSize maxSize = nsBox::BoundsCheckMinMax(minSize, childBox->GetMaxSize(state));
675 prefSize = nsBox::BoundsCheck(minSize, prefSize, maxSize);
677 mOuter->AddMargin(childBox, minSize);
678 mOuter->AddMargin(childBox, prefSize);
679 mOuter->AddMargin(childBox, maxSize);
681 nscoord flex = childBox->GetFlex(state);
683 nsMargin margin(0,0,0,0);
684 childBox->GetMargin(margin);
685 nsRect r(childBox->GetRect());
686 r.Inflate(margin);
688 // We need to check for hidden attribute too, since treecols with
689 // the hidden="true" attribute are not really hidden, just collapsed
690 if (!content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::fixed,
691 nsGkAtoms::_true, eCaseMatters) &&
692 !content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
693 nsGkAtoms::_true, eCaseMatters)) {
694 if (count < childIndex && (resizeBefore != Flex || flex > 0)) {
695 mChildInfosBefore[mChildInfosBeforeCount].childElem = content;
696 mChildInfosBefore[mChildInfosBeforeCount].min = isHorizontal ? minSize.width : minSize.height;
697 mChildInfosBefore[mChildInfosBeforeCount].max = isHorizontal ? maxSize.width : maxSize.height;
698 mChildInfosBefore[mChildInfosBeforeCount].current = isHorizontal ? r.width : r.height;
699 mChildInfosBefore[mChildInfosBeforeCount].flex = flex;
700 mChildInfosBefore[mChildInfosBeforeCount].index = count;
701 mChildInfosBefore[mChildInfosBeforeCount].changed = mChildInfosBefore[mChildInfosBeforeCount].current;
702 mChildInfosBeforeCount++;
703 } else if (count > childIndex && (resizeAfter != Flex || flex > 0)) {
704 mChildInfosAfter[mChildInfosAfterCount].childElem = content;
705 mChildInfosAfter[mChildInfosAfterCount].min = isHorizontal ? minSize.width : minSize.height;
706 mChildInfosAfter[mChildInfosAfterCount].max = isHorizontal ? maxSize.width : maxSize.height;
707 mChildInfosAfter[mChildInfosAfterCount].current = isHorizontal ? r.width : r.height;
708 mChildInfosAfter[mChildInfosAfterCount].flex = flex;
709 mChildInfosAfter[mChildInfosAfterCount].index = count;
710 mChildInfosAfter[mChildInfosAfterCount].changed = mChildInfosAfter[mChildInfosAfterCount].current;
711 mChildInfosAfterCount++;
716 childBox = nsBox::GetNextBox(childBox);
717 count++;
720 if (!mParentBox->IsNormalDirection()) {
721 // The before array is really the after array, and the order needs to be reversed.
722 // First reverse both arrays.
723 Reverse(mChildInfosBefore, mChildInfosBeforeCount);
724 Reverse(mChildInfosAfter, mChildInfosAfterCount);
726 // Now swap the two arrays.
727 nscoord newAfterCount = mChildInfosBeforeCount;
728 mChildInfosBeforeCount = mChildInfosAfterCount;
729 mChildInfosAfterCount = newAfterCount;
730 nsSplitterInfo* temp = mChildInfosAfter;
731 mChildInfosAfter = mChildInfosBefore;
732 mChildInfosBefore = temp;
735 // if resizebefore is not Farthest, reverse the list because the first child
736 // in the list is the farthest, and we want the first child to be the closest.
737 if (resizeBefore != Farthest)
738 Reverse(mChildInfosBefore, mChildInfosBeforeCount);
740 // if the resizeafter is the Farthest we must reverse the list because the first child in the list
741 // is the closest we want the first child to be the Farthest.
742 if (resizeAfter == Farthest)
743 Reverse(mChildInfosAfter, mChildInfosAfterCount);
745 // grow only applys to the children after. If grow is set then no space should be taken out of any children after
746 // us. To do this we just set the size of that list to be 0.
747 if (resizeAfter == Grow)
748 mChildInfosAfterCount = 0;
750 int32_t c;
751 nsPoint pt = nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent,
752 mParentBox);
753 if (isHorizontal) {
754 c = pt.x;
755 mSplitterPos = mOuter->mRect.x;
756 } else {
757 c = pt.y;
758 mSplitterPos = mOuter->mRect.y;
761 mDragStart = c;
763 //printf("Pressed mDragStart=%d\n",mDragStart);
765 nsIPresShell::SetCapturingContent(mOuter->GetContent(), CAPTURE_IGNOREALLOWED);
767 return NS_OK;
770 nsresult
771 nsSplitterFrameInner::MouseMove(nsIDOMEvent* aMouseEvent)
773 NS_ENSURE_TRUE(mOuter, NS_OK);
774 if (!mPressed)
775 return NS_OK;
777 if (mDragging)
778 return NS_OK;
780 nsCOMPtr<nsIDOMEventListener> kungfuDeathGrip(this);
781 mOuter->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
782 NS_LITERAL_STRING("dragging"), true);
784 RemoveListener();
785 mDragging = true;
787 return NS_OK;
790 void
791 nsSplitterFrameInner::Reverse(nsSplitterInfo*& aChildInfos, int32_t aCount)
793 nsSplitterInfo* infos = new nsSplitterInfo[aCount];
795 for (int i=0; i < aCount; i++)
796 infos[i] = aChildInfos[aCount - 1 - i];
798 delete[] aChildInfos;
799 aChildInfos = infos;
802 bool
803 nsSplitterFrameInner::SupportsCollapseDirection
805 nsSplitterFrameInner::CollapseDirection aDirection
808 static nsIContent::AttrValuesArray strings[] =
809 {&nsGkAtoms::before, &nsGkAtoms::after, &nsGkAtoms::both, nullptr};
811 switch (mOuter->mContent->FindAttrValueIn(kNameSpaceID_None,
812 nsGkAtoms::collapse,
813 strings, eCaseMatters)) {
814 case 0:
815 return (aDirection == Before);
816 case 1:
817 return (aDirection == After);
818 case 2:
819 return true;
822 return false;
825 void
826 nsSplitterFrameInner::UpdateState()
828 // State Transitions:
829 // Open -> Dragging
830 // Open -> CollapsedBefore
831 // Open -> CollapsedAfter
832 // CollapsedBefore -> Open
833 // CollapsedBefore -> Dragging
834 // CollapsedAfter -> Open
835 // CollapsedAfter -> Dragging
836 // Dragging -> Open
837 // Dragging -> CollapsedBefore (auto collapse)
838 // Dragging -> CollapsedAfter (auto collapse)
840 State newState = GetState();
842 if (newState == mState) {
843 // No change.
844 return;
847 if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) &&
848 mOuter->GetParent()->IsBoxFrame()) {
849 // Find the splitter's immediate sibling.
850 nsIFrame* splitterSibling;
851 if (newState == CollapsedBefore || mState == CollapsedBefore) {
852 splitterSibling = mOuter->GetPrevSibling();
853 } else {
854 splitterSibling = mOuter->GetNextSibling();
857 if (splitterSibling) {
858 nsCOMPtr<nsIContent> sibling = splitterSibling->GetContent();
859 if (sibling) {
860 if (mState == CollapsedBefore || mState == CollapsedAfter) {
861 // CollapsedBefore -> Open
862 // CollapsedBefore -> Dragging
863 // CollapsedAfter -> Open
864 // CollapsedAfter -> Dragging
865 nsContentUtils::AddScriptRunner(
866 new nsUnsetAttrRunnable(sibling, nsGkAtoms::collapsed));
867 } else if ((mState == Open || mState == Dragging)
868 && (newState == CollapsedBefore ||
869 newState == CollapsedAfter)) {
870 // Open -> CollapsedBefore / CollapsedAfter
871 // Dragging -> CollapsedBefore / CollapsedAfter
872 nsContentUtils::AddScriptRunner(
873 new nsSetAttrRunnable(sibling, nsGkAtoms::collapsed,
874 NS_LITERAL_STRING("true")));
879 mState = newState;
882 void
883 nsSplitterFrameInner::EnsureOrient()
885 bool isHorizontal = !(mParentBox->GetStateBits() & NS_STATE_IS_HORIZONTAL);
886 if (isHorizontal)
887 mOuter->mState |= NS_STATE_IS_HORIZONTAL;
888 else
889 mOuter->mState &= ~NS_STATE_IS_HORIZONTAL;
892 void
893 nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext)
895 EnsureOrient();
896 bool isHorizontal = !mOuter->IsHorizontal();
898 AdjustChildren(aPresContext, mChildInfosBefore, mChildInfosBeforeCount, isHorizontal);
899 AdjustChildren(aPresContext, mChildInfosAfter, mChildInfosAfterCount, isHorizontal);
902 static nsIFrame* GetChildBoxForContent(nsIFrame* aParentBox, nsIContent* aContent)
904 nsIFrame* childBox = nsBox::GetChildBox(aParentBox);
906 while (nullptr != childBox) {
907 if (childBox->GetContent() == aContent) {
908 return childBox;
910 childBox = nsBox::GetNextBox(childBox);
912 return nullptr;
915 void
916 nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext, nsSplitterInfo* aChildInfos, int32_t aCount, bool aIsHorizontal)
918 ///printf("------- AdjustChildren------\n");
920 nsBoxLayoutState state(aPresContext);
922 nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
924 // first set all the widths.
925 nsIFrame* child = nsBox::GetChildBox(mOuter);
926 while(child)
928 SetPreferredSize(state, child, onePixel, aIsHorizontal, nullptr);
929 child = nsBox::GetNextBox(child);
932 // now set our changed widths.
933 for (int i=0; i < aCount; i++)
935 nscoord pref = aChildInfos[i].changed;
936 nsIFrame* childBox = GetChildBoxForContent(mParentBox, aChildInfos[i].childElem);
938 if (childBox) {
939 SetPreferredSize(state, childBox, onePixel, aIsHorizontal, &pref);
944 void
945 nsSplitterFrameInner::SetPreferredSize(nsBoxLayoutState& aState, nsIFrame* aChildBox, nscoord aOnePixel, bool aIsHorizontal, nscoord* aSize)
947 nsRect rect(aChildBox->GetRect());
948 nscoord pref = 0;
950 if (!aSize)
952 if (aIsHorizontal)
953 pref = rect.width;
954 else
955 pref = rect.height;
956 } else {
957 pref = *aSize;
960 nsMargin margin(0,0,0,0);
961 aChildBox->GetMargin(margin);
963 nsCOMPtr<nsIAtom> attribute;
965 if (aIsHorizontal) {
966 pref -= (margin.left + margin.right);
967 attribute = nsGkAtoms::width;
968 } else {
969 pref -= (margin.top + margin.bottom);
970 attribute = nsGkAtoms::height;
973 nsIContent* content = aChildBox->GetContent();
975 // set its preferred size.
976 nsAutoString prefValue;
977 prefValue.AppendInt(pref/aOnePixel);
978 if (content->AttrValueIs(kNameSpaceID_None, attribute,
979 prefValue, eCaseMatters))
980 return;
982 nsWeakFrame weakBox(aChildBox);
983 content->SetAttr(kNameSpaceID_None, attribute, prefValue, true);
984 ENSURE_TRUE(weakBox.IsAlive());
985 aState.PresShell()->FrameNeedsReflow(aChildBox, nsIPresShell::eStyleChange,
986 NS_FRAME_IS_DIRTY);
990 void
991 nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff,
992 nsSplitterInfo* aChildInfos,
993 int32_t aCount,
994 int32_t& aSpaceLeft)
996 aSpaceLeft = 0;
998 for (int i=0; i < aCount; i++) {
999 nscoord min = aChildInfos[i].min;
1000 nscoord max = aChildInfos[i].max;
1001 nscoord& c = aChildInfos[i].changed;
1003 // figure our how much space to add or remove
1004 if (c + aDiff < min) {
1005 aDiff += (c - min);
1006 c = min;
1007 } else if (c + aDiff > max) {
1008 aDiff -= (max - c);
1009 c = max;
1010 } else {
1011 c += aDiff;
1012 aDiff = 0;
1015 // there is not space left? We are done
1016 if (aDiff == 0)
1017 break;
1020 aSpaceLeft = aDiff;
1024 * Ok if we want to resize a child we will know the actual size in pixels we want it to be.
1025 * This is not the preferred size. But they only way we can change a child is my manipulating its
1026 * preferred size. So give the actual pixel size this return method will return figure out the preferred
1027 * size and set it.
1030 void
1031 nsSplitterFrameInner::ResizeChildTo(nsPresContext* aPresContext,
1032 nscoord& aDiff,
1033 nsSplitterInfo* aChildrenBeforeInfos,
1034 nsSplitterInfo* aChildrenAfterInfos,
1035 int32_t aChildrenBeforeCount,
1036 int32_t aChildrenAfterCount,
1037 bool aBounded)
1039 nscoord spaceLeft;
1040 AddRemoveSpace(aDiff, aChildrenBeforeInfos,aChildrenBeforeCount,spaceLeft);
1042 // if there is any space left over remove it from the dif we were originally given
1043 aDiff -= spaceLeft;
1044 AddRemoveSpace(-aDiff, aChildrenAfterInfos,aChildrenAfterCount,spaceLeft);
1046 if (spaceLeft != 0) {
1047 if (aBounded) {
1048 aDiff += spaceLeft;
1049 AddRemoveSpace(spaceLeft, aChildrenBeforeInfos,aChildrenBeforeCount,spaceLeft);
1050 } else {
1051 spaceLeft = 0;