Bug 1492664 - vendor taskcluster-urls; r=gps
[gecko.git] / layout / xul / nsSplitterFrame.cpp
blobd6862c92bcee7394162a8ab6e62c5f7fa3310e87
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 //
8 // Eric Vaughan
9 // Netscape Communications
11 // See documentation in associated header file
14 #include "gfxContext.h"
15 #include "nsSplitterFrame.h"
16 #include "nsGkAtoms.h"
17 #include "nsXULElement.h"
18 #include "nsPresContext.h"
19 #include "nsIDocument.h"
20 #include "nsNameSpaceManager.h"
21 #include "nsScrollbarButtonFrame.h"
22 #include "nsIDOMEventListener.h"
23 #include "nsIPresShell.h"
24 #include "nsFrameList.h"
25 #include "nsHTMLParts.h"
26 #include "mozilla/ComputedStyle.h"
27 #include "nsBoxLayoutState.h"
28 #include "nsIServiceManager.h"
29 #include "nsContainerFrame.h"
30 #include "nsContentCID.h"
31 #include "nsLayoutUtils.h"
32 #include "nsDisplayList.h"
33 #include "nsContentUtils.h"
34 #include "mozilla/dom/Element.h"
35 #include "mozilla/dom/Event.h"
36 #include "mozilla/dom/MouseEvent.h"
37 #include "mozilla/MouseEvents.h"
38 #include "mozilla/UniquePtr.h"
39 #include "nsBindingManager.h"
41 using namespace mozilla;
43 using mozilla::dom::Event;
45 class nsSplitterInfo {
46 public:
47 nscoord min;
48 nscoord max;
49 nscoord current;
50 nscoord changed;
51 nsCOMPtr<nsIContent> childElem;
52 int32_t flex;
53 int32_t index;
56 class nsSplitterFrameInner final : public nsIDOMEventListener {
57 protected:
58 virtual ~nsSplitterFrameInner();
60 public:
61 NS_DECL_ISUPPORTS
62 NS_DECL_NSIDOMEVENTLISTENER
64 explicit nsSplitterFrameInner(nsSplitterFrame* aSplitter)
65 : mDidDrag(false),
66 mDragStart(0),
67 mParentBox(nullptr),
68 mChildInfosBeforeCount(0),
69 mChildInfosAfterCount(0),
70 mState(Open),
71 mSplitterPos(0),
72 mDragging(false) {
73 mOuter = aSplitter;
74 mPressed = false;
77 void Disconnect() { mOuter = nullptr; }
79 nsresult MouseDown(Event* aMouseEvent);
80 nsresult MouseUp(Event* aMouseEvent);
81 nsresult MouseMove(Event* aMouseEvent);
83 void MouseDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
84 void MouseUp(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
86 void AdjustChildren(nsPresContext* aPresContext);
87 void AdjustChildren(nsPresContext* aPresContext, nsSplitterInfo* aChildInfos,
88 int32_t aCount, bool aIsHorizontal);
90 void AddRemoveSpace(nscoord aDiff, nsSplitterInfo* aChildInfos,
91 int32_t aCount, int32_t& aSpaceLeft);
93 void ResizeChildTo(nscoord& aDiff, nsSplitterInfo* aChildrenBeforeInfos,
94 nsSplitterInfo* aChildrenAfterInfos,
95 int32_t aChildrenBeforeCount, int32_t aChildrenAfterCount,
96 bool aBounded);
98 void UpdateState();
100 void AddListener();
101 void RemoveListener();
103 enum ResizeType { Closest, Farthest, Flex, Grow };
104 enum State { Open, CollapsedBefore, CollapsedAfter, Dragging };
105 enum CollapseDirection { Before, After };
107 ResizeType GetResizeBefore();
108 ResizeType GetResizeAfter();
109 State GetState();
111 void Reverse(UniquePtr<nsSplitterInfo[]>& aIndexes, int32_t aCount);
112 bool SupportsCollapseDirection(CollapseDirection aDirection);
114 void EnsureOrient();
115 void SetPreferredSize(nsBoxLayoutState& aState, nsIFrame* aChildBox,
116 nscoord aOnePixel, bool aIsHorizontal, nscoord* aSize);
118 nsSplitterFrame* mOuter;
119 bool mDidDrag;
120 nscoord mDragStart;
121 nsIFrame* mParentBox;
122 bool mPressed;
123 UniquePtr<nsSplitterInfo[]> mChildInfosBefore;
124 UniquePtr<nsSplitterInfo[]> mChildInfosAfter;
125 int32_t mChildInfosBeforeCount;
126 int32_t mChildInfosAfterCount;
127 State mState;
128 nscoord mSplitterPos;
129 bool mDragging;
131 const Element* SplitterElement() const {
132 return mOuter->GetContent()->AsElement();
136 NS_IMPL_ISUPPORTS(nsSplitterFrameInner, nsIDOMEventListener)
138 nsSplitterFrameInner::ResizeType nsSplitterFrameInner::GetResizeBefore() {
139 static Element::AttrValuesArray strings[] = {nsGkAtoms::farthest,
140 nsGkAtoms::flex, nullptr};
141 switch (SplitterElement()->FindAttrValueIn(
142 kNameSpaceID_None, nsGkAtoms::resizebefore, strings, eCaseMatters)) {
143 case 0:
144 return Farthest;
145 case 1:
146 return Flex;
148 return Closest;
151 nsSplitterFrameInner::~nsSplitterFrameInner() {}
153 nsSplitterFrameInner::ResizeType nsSplitterFrameInner::GetResizeAfter() {
154 static Element::AttrValuesArray strings[] = {
155 nsGkAtoms::farthest, nsGkAtoms::flex, nsGkAtoms::grow, nullptr};
156 switch (SplitterElement()->FindAttrValueIn(
157 kNameSpaceID_None, nsGkAtoms::resizeafter, strings, eCaseMatters)) {
158 case 0:
159 return Farthest;
160 case 1:
161 return Flex;
162 case 2:
163 return Grow;
165 return Closest;
168 nsSplitterFrameInner::State nsSplitterFrameInner::GetState() {
169 static Element::AttrValuesArray strings[] = {nsGkAtoms::dragging,
170 nsGkAtoms::collapsed, nullptr};
171 static Element::AttrValuesArray strings_substate[] = {
172 nsGkAtoms::before, nsGkAtoms::after, nullptr};
173 switch (SplitterElement()->FindAttrValueIn(
174 kNameSpaceID_None, nsGkAtoms::state, strings, eCaseMatters)) {
175 case 0:
176 return Dragging;
177 case 1:
178 switch (SplitterElement()->FindAttrValueIn(
179 kNameSpaceID_None, nsGkAtoms::substate, strings_substate,
180 eCaseMatters)) {
181 case 0:
182 return CollapsedBefore;
183 case 1:
184 return CollapsedAfter;
185 default:
186 if (SupportsCollapseDirection(After)) return CollapsedAfter;
187 return CollapsedBefore;
190 return Open;
194 // NS_NewSplitterFrame
196 // Creates a new Toolbar frame and returns it
198 nsIFrame* NS_NewSplitterFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle) {
199 return new (aPresShell) nsSplitterFrame(aStyle);
202 NS_IMPL_FRAMEARENA_HELPERS(nsSplitterFrame)
204 nsSplitterFrame::nsSplitterFrame(ComputedStyle* aStyle)
205 : nsBoxFrame(aStyle, kClassID), mInner(0) {}
207 void nsSplitterFrame::DestroyFrom(nsIFrame* aDestructRoot,
208 PostDestroyData& aPostDestroyData) {
209 if (mInner) {
210 mInner->RemoveListener();
211 mInner->Disconnect();
212 mInner->Release();
213 mInner = nullptr;
215 nsBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
218 nsresult nsSplitterFrame::GetCursor(const nsPoint& aPoint,
219 nsIFrame::Cursor& aCursor) {
220 return nsBoxFrame::GetCursor(aPoint, aCursor);
223 if (IsXULHorizontal())
224 aCursor = NS_STYLE_CURSOR_N_RESIZE;
225 else
226 aCursor = NS_STYLE_CURSOR_W_RESIZE;
228 return NS_OK;
232 nsresult nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID,
233 nsAtom* aAttribute,
234 int32_t aModType) {
235 nsresult rv =
236 nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
237 if (aAttribute == nsGkAtoms::state) {
238 mInner->UpdateState();
241 return rv;
245 * Initialize us. If we are in a box get our alignment so we know what direction
246 * we are
248 void nsSplitterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
249 nsIFrame* aPrevInFlow) {
250 MOZ_ASSERT(!mInner);
251 mInner = new nsSplitterFrameInner(this);
253 mInner->AddRef();
255 // determine orientation of parent, and if vertical, set orient to vertical
256 // on splitter content, then re-resolve style
257 // XXXbz this is pretty messed up, since this can change whether we should
258 // have a frame at all. This really needs a better solution.
259 if (aParent && aParent->IsXULBoxFrame()) {
260 if (!aParent->IsXULHorizontal()) {
261 if (!nsContentUtils::HasNonEmptyAttr(aContent, kNameSpaceID_None,
262 nsGkAtoms::orient)) {
263 aContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
264 NS_LITERAL_STRING("vertical"), false);
269 nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
271 mInner->mState = nsSplitterFrameInner::Open;
272 mInner->AddListener();
273 mInner->mParentBox = nullptr;
276 NS_IMETHODIMP
277 nsSplitterFrame::DoXULLayout(nsBoxLayoutState& aState) {
278 if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
279 mInner->mParentBox = nsBox::GetParentXULBox(this);
280 mInner->UpdateState();
283 return nsBoxFrame::DoXULLayout(aState);
286 void nsSplitterFrame::GetInitialOrientation(bool& aIsHorizontal) {
287 nsIFrame* box = nsBox::GetParentXULBox(this);
288 if (box) {
289 aIsHorizontal = !box->IsXULHorizontal();
290 } else
291 nsBoxFrame::GetInitialOrientation(aIsHorizontal);
294 NS_IMETHODIMP
295 nsSplitterFrame::HandlePress(nsPresContext* aPresContext,
296 WidgetGUIEvent* aEvent,
297 nsEventStatus* aEventStatus) {
298 return NS_OK;
301 NS_IMETHODIMP
302 nsSplitterFrame::HandleMultiplePress(nsPresContext* aPresContext,
303 WidgetGUIEvent* aEvent,
304 nsEventStatus* aEventStatus,
305 bool aControlHeld) {
306 return NS_OK;
309 NS_IMETHODIMP
310 nsSplitterFrame::HandleDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
311 nsEventStatus* aEventStatus) {
312 return NS_OK;
315 NS_IMETHODIMP
316 nsSplitterFrame::HandleRelease(nsPresContext* aPresContext,
317 WidgetGUIEvent* aEvent,
318 nsEventStatus* aEventStatus) {
319 return NS_OK;
322 void nsSplitterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
323 const nsDisplayListSet& aLists) {
324 nsBoxFrame::BuildDisplayList(aBuilder, aLists);
326 // if the mouse is captured always return us as the frame.
327 if (mInner->mDragging && aBuilder->IsForEventDelivery()) {
328 // XXX It's probably better not to check visibility here, right?
329 aLists.Outlines()->AppendToTop(
330 MakeDisplayItem<nsDisplayEventReceiver>(aBuilder, this));
331 return;
335 nsresult nsSplitterFrame::HandleEvent(nsPresContext* aPresContext,
336 WidgetGUIEvent* aEvent,
337 nsEventStatus* aEventStatus) {
338 NS_ENSURE_ARG_POINTER(aEventStatus);
339 if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
340 return NS_OK;
343 AutoWeakFrame weakFrame(this);
344 RefPtr<nsSplitterFrameInner> inner(mInner);
345 switch (aEvent->mMessage) {
346 case eMouseMove:
347 inner->MouseDrag(aPresContext, aEvent);
348 break;
350 case eMouseUp:
351 if (aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) {
352 inner->MouseUp(aPresContext, aEvent);
354 break;
356 default:
357 break;
360 NS_ENSURE_STATE(weakFrame.IsAlive());
361 return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
364 void nsSplitterFrameInner::MouseUp(nsPresContext* aPresContext,
365 WidgetGUIEvent* aEvent) {
366 if (mDragging && mOuter) {
367 AdjustChildren(aPresContext);
368 AddListener();
369 nsIPresShell::SetCapturingContent(nullptr,
370 0); // XXXndeakin is this needed?
371 mDragging = false;
372 State newState = GetState();
373 // if the state is dragging then make it Open.
374 if (newState == Dragging) {
375 mOuter->mContent->AsElement()->SetAttr(
376 kNameSpaceID_None, nsGkAtoms::state, EmptyString(), true);
379 mPressed = false;
381 // if we dragged then fire a command event.
382 if (mDidDrag) {
383 RefPtr<nsXULElement> element =
384 nsXULElement::FromNode(mOuter->GetContent());
385 element->DoCommand();
388 // printf("MouseUp\n");
391 mChildInfosBefore = nullptr;
392 mChildInfosAfter = nullptr;
393 mChildInfosBeforeCount = 0;
394 mChildInfosAfterCount = 0;
397 void nsSplitterFrameInner::MouseDrag(nsPresContext* aPresContext,
398 WidgetGUIEvent* aEvent) {
399 if (mDragging && mOuter) {
400 // printf("Dragging\n");
402 bool isHorizontal = !mOuter->IsXULHorizontal();
403 // convert coord to pixels
404 nsPoint pt =
405 nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, mParentBox);
406 nscoord pos = isHorizontal ? pt.x : pt.y;
408 // mDragStart is in frame coordinates
409 nscoord start = mDragStart;
411 // take our current position and subtract the start location
412 pos -= start;
414 // printf("Diff=%d\n", pos);
416 ResizeType resizeAfter = GetResizeAfter();
418 bool bounded;
420 if (resizeAfter == nsSplitterFrameInner::Grow)
421 bounded = false;
422 else
423 bounded = true;
425 int i;
426 for (i = 0; i < mChildInfosBeforeCount; i++)
427 mChildInfosBefore[i].changed = mChildInfosBefore[i].current;
429 for (i = 0; i < mChildInfosAfterCount; i++)
430 mChildInfosAfter[i].changed = mChildInfosAfter[i].current;
432 nscoord oldPos = pos;
434 ResizeChildTo(pos, mChildInfosBefore.get(), mChildInfosAfter.get(),
435 mChildInfosBeforeCount, mChildInfosAfterCount, bounded);
437 State currentState = GetState();
438 bool supportsBefore = SupportsCollapseDirection(Before);
439 bool supportsAfter = SupportsCollapseDirection(After);
441 const bool isRTL =
442 mOuter->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
443 bool pastEnd = oldPos > 0 && oldPos > pos;
444 bool pastBegin = oldPos < 0 && oldPos < pos;
445 if (isRTL) {
446 // Swap the boundary checks in RTL mode
447 bool tmp = pastEnd;
448 pastEnd = pastBegin;
449 pastBegin = tmp;
451 const bool isCollapsedBefore = pastBegin && supportsBefore;
452 const bool isCollapsedAfter = pastEnd && supportsAfter;
454 // if we are in a collapsed position
455 if (isCollapsedBefore || isCollapsedAfter) {
456 // and we are not collapsed then collapse
457 if (currentState == Dragging) {
458 if (pastEnd) {
459 // printf("Collapse right\n");
460 if (supportsAfter) {
461 RefPtr<Element> outer = mOuter->mContent->AsElement();
462 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate,
463 NS_LITERAL_STRING("after"), true);
464 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
465 NS_LITERAL_STRING("collapsed"), true);
468 } else if (pastBegin) {
469 // printf("Collapse left\n");
470 if (supportsBefore) {
471 RefPtr<Element> outer = mOuter->mContent->AsElement();
472 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate,
473 NS_LITERAL_STRING("before"), true);
474 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
475 NS_LITERAL_STRING("collapsed"), true);
479 } else {
480 // if we are not in a collapsed position and we are not dragging make sure
481 // we are dragging.
482 if (currentState != Dragging) {
483 mOuter->mContent->AsElement()->SetAttr(
484 kNameSpaceID_None, nsGkAtoms::state, NS_LITERAL_STRING("dragging"),
485 true);
487 AdjustChildren(aPresContext);
490 mDidDrag = true;
494 void nsSplitterFrameInner::AddListener() {
495 mOuter->GetContent()->AddEventListener(NS_LITERAL_STRING("mouseup"), this,
496 false, false);
497 mOuter->GetContent()->AddEventListener(NS_LITERAL_STRING("mousedown"), this,
498 false, false);
499 mOuter->GetContent()->AddEventListener(NS_LITERAL_STRING("mousemove"), this,
500 false, false);
501 mOuter->GetContent()->AddEventListener(NS_LITERAL_STRING("mouseout"), this,
502 false, false);
505 void nsSplitterFrameInner::RemoveListener() {
506 NS_ENSURE_TRUE_VOID(mOuter);
507 mOuter->GetContent()->RemoveEventListener(NS_LITERAL_STRING("mouseup"), this,
508 false);
509 mOuter->GetContent()->RemoveEventListener(NS_LITERAL_STRING("mousedown"),
510 this, false);
511 mOuter->GetContent()->RemoveEventListener(NS_LITERAL_STRING("mousemove"),
512 this, false);
513 mOuter->GetContent()->RemoveEventListener(NS_LITERAL_STRING("mouseout"), this,
514 false);
517 nsresult nsSplitterFrameInner::HandleEvent(dom::Event* aEvent) {
518 nsAutoString eventType;
519 aEvent->GetType(eventType);
520 if (eventType.EqualsLiteral("mouseup")) return MouseUp(aEvent);
521 if (eventType.EqualsLiteral("mousedown")) return MouseDown(aEvent);
522 if (eventType.EqualsLiteral("mousemove") ||
523 eventType.EqualsLiteral("mouseout"))
524 return MouseMove(aEvent);
526 MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
527 return NS_OK;
530 nsresult nsSplitterFrameInner::MouseUp(Event* aMouseEvent) {
531 NS_ENSURE_TRUE(mOuter, NS_OK);
532 mPressed = false;
534 nsIPresShell::SetCapturingContent(nullptr, 0);
536 return NS_OK;
539 nsresult nsSplitterFrameInner::MouseDown(Event* aMouseEvent) {
540 NS_ENSURE_TRUE(mOuter, NS_OK);
541 dom::MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
542 if (!mouseEvent) {
543 return NS_OK;
546 // only if left button
547 if (mouseEvent->Button() != 0) return NS_OK;
549 if (SplitterElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
550 nsGkAtoms::_true, eCaseMatters))
551 return NS_OK;
553 mParentBox = nsBox::GetParentXULBox(mOuter);
554 if (!mParentBox) return NS_OK;
556 // get our index
557 nsPresContext* outerPresContext = mOuter->PresContext();
558 const nsFrameList& siblingList(mParentBox->PrincipalChildList());
559 int32_t childIndex = siblingList.IndexOf(mOuter);
560 // if it's 0 (or not found) then stop right here.
561 // It might be not found if we're not in the parent's primary frame list.
562 if (childIndex <= 0) return NS_OK;
564 int32_t childCount = siblingList.GetLength();
565 // if it's the last index then we need to allow for resizeafter="grow"
566 if (childIndex == childCount - 1 && GetResizeAfter() != Grow) return NS_OK;
568 RefPtr<gfxContext> rc =
569 outerPresContext->PresShell()->CreateReferenceRenderingContext();
570 nsBoxLayoutState state(outerPresContext, rc);
571 mPressed = true;
573 mDidDrag = false;
575 EnsureOrient();
576 bool isHorizontal = !mOuter->IsXULHorizontal();
578 ResizeType resizeBefore = GetResizeBefore();
579 ResizeType resizeAfter = GetResizeAfter();
581 mChildInfosBefore = MakeUnique<nsSplitterInfo[]>(childCount);
582 mChildInfosAfter = MakeUnique<nsSplitterInfo[]>(childCount);
584 // create info 2 lists. One of the children before us and one after.
585 int32_t count = 0;
586 mChildInfosBeforeCount = 0;
587 mChildInfosAfterCount = 0;
589 nsIFrame* childBox = nsBox::GetChildXULBox(mParentBox);
591 while (nullptr != childBox) {
592 nsIContent* content = childBox->GetContent();
593 nsIDocument* doc = content->OwnerDoc();
594 int32_t dummy;
595 nsAtom* atom = doc->BindingManager()->ResolveTag(content, &dummy);
597 // skip over any splitters
598 if (atom != nsGkAtoms::splitter) {
599 nsSize prefSize = childBox->GetXULPrefSize(state);
600 nsSize minSize = childBox->GetXULMinSize(state);
601 nsSize maxSize =
602 nsBox::BoundsCheckMinMax(minSize, childBox->GetXULMaxSize(state));
603 prefSize = nsBox::BoundsCheck(minSize, prefSize, maxSize);
605 nsSplitterFrame::AddMargin(childBox, minSize);
606 nsSplitterFrame::AddMargin(childBox, prefSize);
607 nsSplitterFrame::AddMargin(childBox, maxSize);
609 nscoord flex = childBox->GetXULFlex();
611 nsMargin margin(0, 0, 0, 0);
612 childBox->GetXULMargin(margin);
613 nsRect r(childBox->GetRect());
614 r.Inflate(margin);
616 // We need to check for hidden attribute too, since treecols with
617 // the hidden="true" attribute are not really hidden, just collapsed
618 if (!content->IsElement() || (!content->AsElement()->AttrValueIs(
619 kNameSpaceID_None, nsGkAtoms::fixed,
620 nsGkAtoms::_true, eCaseMatters) &&
621 !content->AsElement()->AttrValueIs(
622 kNameSpaceID_None, nsGkAtoms::hidden,
623 nsGkAtoms::_true, eCaseMatters))) {
624 if (count < childIndex && (resizeBefore != Flex || flex > 0)) {
625 mChildInfosBefore[mChildInfosBeforeCount].childElem = content;
626 mChildInfosBefore[mChildInfosBeforeCount].min =
627 isHorizontal ? minSize.width : minSize.height;
628 mChildInfosBefore[mChildInfosBeforeCount].max =
629 isHorizontal ? maxSize.width : maxSize.height;
630 mChildInfosBefore[mChildInfosBeforeCount].current =
631 isHorizontal ? r.width : r.height;
632 mChildInfosBefore[mChildInfosBeforeCount].flex = flex;
633 mChildInfosBefore[mChildInfosBeforeCount].index = count;
634 mChildInfosBefore[mChildInfosBeforeCount].changed =
635 mChildInfosBefore[mChildInfosBeforeCount].current;
636 mChildInfosBeforeCount++;
637 } else if (count > childIndex && (resizeAfter != Flex || flex > 0)) {
638 mChildInfosAfter[mChildInfosAfterCount].childElem = content;
639 mChildInfosAfter[mChildInfosAfterCount].min =
640 isHorizontal ? minSize.width : minSize.height;
641 mChildInfosAfter[mChildInfosAfterCount].max =
642 isHorizontal ? maxSize.width : maxSize.height;
643 mChildInfosAfter[mChildInfosAfterCount].current =
644 isHorizontal ? r.width : r.height;
645 mChildInfosAfter[mChildInfosAfterCount].flex = flex;
646 mChildInfosAfter[mChildInfosAfterCount].index = count;
647 mChildInfosAfter[mChildInfosAfterCount].changed =
648 mChildInfosAfter[mChildInfosAfterCount].current;
649 mChildInfosAfterCount++;
654 childBox = nsBox::GetNextXULBox(childBox);
655 count++;
658 if (!mParentBox->IsXULNormalDirection()) {
659 // The before array is really the after array, and the order needs to be
660 // reversed. First reverse both arrays.
661 Reverse(mChildInfosBefore, mChildInfosBeforeCount);
662 Reverse(mChildInfosAfter, mChildInfosAfterCount);
664 // Now swap the two arrays.
665 Swap(mChildInfosBeforeCount, mChildInfosAfterCount);
666 Swap(mChildInfosBefore, mChildInfosAfter);
669 // if resizebefore is not Farthest, reverse the list because the first child
670 // in the list is the farthest, and we want the first child to be the closest.
671 if (resizeBefore != Farthest)
672 Reverse(mChildInfosBefore, mChildInfosBeforeCount);
674 // if the resizeafter is the Farthest we must reverse the list because the
675 // first child in the list is the closest we want the first child to be the
676 // Farthest.
677 if (resizeAfter == Farthest) Reverse(mChildInfosAfter, mChildInfosAfterCount);
679 // grow only applys to the children after. If grow is set then no space should
680 // be taken out of any children after us. To do this we just set the size of
681 // that list to be 0.
682 if (resizeAfter == Grow) mChildInfosAfterCount = 0;
684 int32_t c;
685 nsPoint pt =
686 nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent, mParentBox);
687 if (isHorizontal) {
688 c = pt.x;
689 mSplitterPos = mOuter->mRect.x;
690 } else {
691 c = pt.y;
692 mSplitterPos = mOuter->mRect.y;
695 mDragStart = c;
697 // printf("Pressed mDragStart=%d\n",mDragStart);
699 nsIPresShell::SetCapturingContent(mOuter->GetContent(),
700 CAPTURE_IGNOREALLOWED);
702 return NS_OK;
705 nsresult nsSplitterFrameInner::MouseMove(Event* aMouseEvent) {
706 NS_ENSURE_TRUE(mOuter, NS_OK);
707 if (!mPressed) return NS_OK;
709 if (mDragging) return NS_OK;
711 nsCOMPtr<nsIDOMEventListener> kungfuDeathGrip(this);
712 mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
713 NS_LITERAL_STRING("dragging"), true);
715 RemoveListener();
716 mDragging = true;
718 return NS_OK;
721 void nsSplitterFrameInner::Reverse(UniquePtr<nsSplitterInfo[]>& aChildInfos,
722 int32_t aCount) {
723 UniquePtr<nsSplitterInfo[]> infos(new nsSplitterInfo[aCount]);
725 for (int i = 0; i < aCount; i++) infos[i] = aChildInfos[aCount - 1 - i];
727 aChildInfos = std::move(infos);
730 bool nsSplitterFrameInner::SupportsCollapseDirection(
731 nsSplitterFrameInner::CollapseDirection aDirection) {
732 static Element::AttrValuesArray strings[] = {
733 nsGkAtoms::before, nsGkAtoms::after, nsGkAtoms::both, nullptr};
735 switch (SplitterElement()->FindAttrValueIn(
736 kNameSpaceID_None, nsGkAtoms::collapse, strings, eCaseMatters)) {
737 case 0:
738 return (aDirection == Before);
739 case 1:
740 return (aDirection == After);
741 case 2:
742 return true;
745 return false;
748 void nsSplitterFrameInner::UpdateState() {
749 // State Transitions:
750 // Open -> Dragging
751 // Open -> CollapsedBefore
752 // Open -> CollapsedAfter
753 // CollapsedBefore -> Open
754 // CollapsedBefore -> Dragging
755 // CollapsedAfter -> Open
756 // CollapsedAfter -> Dragging
757 // Dragging -> Open
758 // Dragging -> CollapsedBefore (auto collapse)
759 // Dragging -> CollapsedAfter (auto collapse)
761 State newState = GetState();
763 if (newState == mState) {
764 // No change.
765 return;
768 if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) &&
769 mOuter->GetParent()->IsXULBoxFrame()) {
770 // Find the splitter's immediate sibling.
771 nsIFrame* splitterSibling;
772 if (newState == CollapsedBefore || mState == CollapsedBefore) {
773 splitterSibling = mOuter->GetPrevSibling();
774 } else {
775 splitterSibling = mOuter->GetNextSibling();
778 if (splitterSibling) {
779 nsCOMPtr<nsIContent> sibling = splitterSibling->GetContent();
780 if (sibling && sibling->IsElement()) {
781 if (mState == CollapsedBefore || mState == CollapsedAfter) {
782 // CollapsedBefore -> Open
783 // CollapsedBefore -> Dragging
784 // CollapsedAfter -> Open
785 // CollapsedAfter -> Dragging
786 nsContentUtils::AddScriptRunner(new nsUnsetAttrRunnable(
787 sibling->AsElement(), nsGkAtoms::collapsed));
788 } else if ((mState == Open || mState == Dragging) &&
789 (newState == CollapsedBefore ||
790 newState == CollapsedAfter)) {
791 // Open -> CollapsedBefore / CollapsedAfter
792 // Dragging -> CollapsedBefore / CollapsedAfter
793 nsContentUtils::AddScriptRunner(
794 new nsSetAttrRunnable(sibling->AsElement(), nsGkAtoms::collapsed,
795 NS_LITERAL_STRING("true")));
800 mState = newState;
803 void nsSplitterFrameInner::EnsureOrient() {
804 bool isHorizontal = !(mParentBox->GetStateBits() & NS_STATE_IS_HORIZONTAL);
805 if (isHorizontal)
806 mOuter->AddStateBits(NS_STATE_IS_HORIZONTAL);
807 else
808 mOuter->RemoveStateBits(NS_STATE_IS_HORIZONTAL);
811 void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext) {
812 EnsureOrient();
813 bool isHorizontal = !mOuter->IsXULHorizontal();
815 AdjustChildren(aPresContext, mChildInfosBefore.get(), mChildInfosBeforeCount,
816 isHorizontal);
817 AdjustChildren(aPresContext, mChildInfosAfter.get(), mChildInfosAfterCount,
818 isHorizontal);
821 static nsIFrame* GetChildBoxForContent(nsIFrame* aParentBox,
822 nsIContent* aContent) {
823 nsIFrame* childBox = nsBox::GetChildXULBox(aParentBox);
825 while (nullptr != childBox) {
826 if (childBox->GetContent() == aContent) {
827 return childBox;
829 childBox = nsBox::GetNextXULBox(childBox);
831 return nullptr;
834 void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext,
835 nsSplitterInfo* aChildInfos,
836 int32_t aCount, bool aIsHorizontal) {
837 /// printf("------- AdjustChildren------\n");
839 nsBoxLayoutState state(aPresContext);
841 nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
843 // first set all the widths.
844 nsIFrame* child = nsBox::GetChildXULBox(mOuter);
845 while (child) {
846 SetPreferredSize(state, child, onePixel, aIsHorizontal, nullptr);
847 child = nsBox::GetNextXULBox(child);
850 // now set our changed widths.
851 for (int i = 0; i < aCount; i++) {
852 nscoord pref = aChildInfos[i].changed;
853 nsIFrame* childBox =
854 GetChildBoxForContent(mParentBox, aChildInfos[i].childElem);
856 if (childBox) {
857 SetPreferredSize(state, childBox, onePixel, aIsHorizontal, &pref);
862 void nsSplitterFrameInner::SetPreferredSize(nsBoxLayoutState& aState,
863 nsIFrame* aChildBox,
864 nscoord aOnePixel,
865 bool aIsHorizontal,
866 nscoord* aSize) {
867 nsRect rect(aChildBox->GetRect());
868 nscoord pref = 0;
870 if (!aSize) {
871 if (aIsHorizontal)
872 pref = rect.width;
873 else
874 pref = rect.height;
875 } else {
876 pref = *aSize;
879 nsMargin margin(0, 0, 0, 0);
880 aChildBox->GetXULMargin(margin);
882 RefPtr<nsAtom> attribute;
884 if (aIsHorizontal) {
885 pref -= (margin.left + margin.right);
886 attribute = nsGkAtoms::width;
887 } else {
888 pref -= (margin.top + margin.bottom);
889 attribute = nsGkAtoms::height;
892 nsIContent* content = aChildBox->GetContent();
893 if (!content->IsElement()) {
894 return;
897 // set its preferred size.
898 nsAutoString prefValue;
899 prefValue.AppendInt(pref / aOnePixel);
900 if (content->AsElement()->AttrValueIs(kNameSpaceID_None, attribute, prefValue,
901 eCaseMatters)) {
902 return;
905 AutoWeakFrame weakBox(aChildBox);
906 content->AsElement()->SetAttr(kNameSpaceID_None, attribute, prefValue, true);
907 NS_ENSURE_TRUE_VOID(weakBox.IsAlive());
908 aState.PresShell()->FrameNeedsReflow(aChildBox, nsIPresShell::eStyleChange,
909 NS_FRAME_IS_DIRTY);
912 void nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff,
913 nsSplitterInfo* aChildInfos,
914 int32_t aCount, int32_t& aSpaceLeft) {
915 aSpaceLeft = 0;
917 for (int i = 0; i < aCount; i++) {
918 nscoord min = aChildInfos[i].min;
919 nscoord max = aChildInfos[i].max;
920 nscoord& c = aChildInfos[i].changed;
922 // figure our how much space to add or remove
923 if (c + aDiff < min) {
924 aDiff += (c - min);
925 c = min;
926 } else if (c + aDiff > max) {
927 aDiff -= (max - c);
928 c = max;
929 } else {
930 c += aDiff;
931 aDiff = 0;
934 // there is not space left? We are done
935 if (aDiff == 0) break;
938 aSpaceLeft = aDiff;
942 * Ok if we want to resize a child we will know the actual size in pixels we
943 * want it to be. This is not the preferred size. But they only way we can
944 * change a child is my manipulating its preferred size. So give the actual
945 * pixel size this return method will return figure out the preferred size and
946 * set it.
949 void nsSplitterFrameInner::ResizeChildTo(nscoord& aDiff,
950 nsSplitterInfo* aChildrenBeforeInfos,
951 nsSplitterInfo* aChildrenAfterInfos,
952 int32_t aChildrenBeforeCount,
953 int32_t aChildrenAfterCount,
954 bool aBounded) {
955 nscoord spaceLeft;
956 AddRemoveSpace(aDiff, aChildrenBeforeInfos, aChildrenBeforeCount, spaceLeft);
958 // if there is any space left over remove it from the dif we were originally
959 // given
960 aDiff -= spaceLeft;
961 AddRemoveSpace(-aDiff, aChildrenAfterInfos, aChildrenAfterCount, spaceLeft);
963 if (spaceLeft != 0) {
964 if (aBounded) {
965 aDiff += spaceLeft;
966 AddRemoveSpace(spaceLeft, aChildrenBeforeInfos, aChildrenBeforeCount,
967 spaceLeft);