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/. */
6
7 //
8 // Eric Vaughan
9 // Netscape Communications
10 //
11 // See documentation in associated header file
12 //
13
14 #include "gfxContext.h"
15 #include "nsSplitterFrame.h"
16 #include "nsGkAtoms.h"
17 #include "nsXULElement.h"
18 #include "nsPresContext.h"
19 #include "mozilla/dom/Document.h"
20 #include "nsNameSpaceManager.h"
21 #include "nsScrollbarButtonFrame.h"
22 #include "nsIDOMEventListener.h"
23 #include "nsFrameList.h"
24 #include "nsHTMLParts.h"
25 #include "mozilla/ComputedStyle.h"
26 #include "mozilla/CSSOrderAwareFrameIterator.h"
27 #include "nsBoxLayoutState.h"
28 #include "nsContainerFrame.h"
29 #include "nsContentCID.h"
30 #include "nsLayoutUtils.h"
31 #include "nsDisplayList.h"
32 #include "nsContentUtils.h"
33 #include "mozilla/dom/Element.h"
34 #include "mozilla/dom/Event.h"
35 #include "mozilla/dom/MouseEvent.h"
36 #include "mozilla/MouseEvents.h"
37 #include "mozilla/PresShell.h"
38 #include "mozilla/UniquePtr.h"
39
40 using namespace mozilla;
41
42 using mozilla::dom::Element;
43 using mozilla::dom::Event;
44
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 };
54
55 class nsSplitterFrameInner final : public nsIDOMEventListener {
56 protected:
57 virtual ~nsSplitterFrameInner();
58
59 public:
60 NS_DECL_ISUPPORTS
61 NS_DECL_NSIDOMEVENTLISTENER
62
nsSplitterFrameInner(nsSplitterFrame * aSplitter)63 explicit nsSplitterFrameInner(nsSplitterFrame* aSplitter)
64 : mDidDrag(false),
65 mDragStart(0),
66 mParentBox(nullptr),
67 mChildInfosBeforeCount(0),
68 mChildInfosAfterCount(0),
69 mState(Open),
70 mSplitterPos(0),
71 mDragging(false) {
72 mOuter = aSplitter;
73 mPressed = false;
74 }
75
Disconnect()76 void Disconnect() { mOuter = nullptr; }
77
78 nsresult MouseDown(Event* aMouseEvent);
79 nsresult MouseUp(Event* aMouseEvent);
80 nsresult MouseMove(Event* aMouseEvent);
81
82 void MouseDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
83 void MouseUp(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
84
85 void AdjustChildren(nsPresContext* aPresContext);
86 void AdjustChildren(nsPresContext* aPresContext, nsSplitterInfo* aChildInfos,
87 int32_t aCount, bool aIsHorizontal);
88
89 void AddRemoveSpace(nscoord aDiff, nsSplitterInfo* aChildInfos,
90 int32_t aCount, int32_t& aSpaceLeft);
91
92 void ResizeChildTo(nscoord& aDiff, nsSplitterInfo* aChildrenBeforeInfos,
93 nsSplitterInfo* aChildrenAfterInfos,
94 int32_t aChildrenBeforeCount, int32_t aChildrenAfterCount,
95 bool aBounded);
96
97 void UpdateState();
98
99 void AddListener();
100 void RemoveListener();
101
102 enum ResizeType { Closest, Farthest, Flex, Grow };
103 enum State { Open, CollapsedBefore, CollapsedAfter, Dragging };
104 enum CollapseDirection { Before, After };
105
106 ResizeType GetResizeBefore();
107 ResizeType GetResizeAfter();
108 State GetState();
109
110 void Reverse(UniquePtr<nsSplitterInfo[]>& aIndexes, int32_t aCount);
111 bool SupportsCollapseDirection(CollapseDirection aDirection);
112
113 void EnsureOrient();
114 void SetPreferredSize(nsBoxLayoutState& aState, nsIFrame* aChildBox,
115 nscoord aOnePixel, bool aIsHorizontal, nscoord* aSize);
116
117 nsSplitterFrame* mOuter;
118 bool mDidDrag;
119 nscoord mDragStart;
120 nsIFrame* mParentBox;
121 bool mPressed;
122 UniquePtr<nsSplitterInfo[]> mChildInfosBefore;
123 UniquePtr<nsSplitterInfo[]> mChildInfosAfter;
124 int32_t mChildInfosBeforeCount;
125 int32_t mChildInfosAfterCount;
126 State mState;
127 nscoord mSplitterPos;
128 bool mDragging;
129
SplitterElement() const130 const Element* SplitterElement() const {
131 return mOuter->GetContent()->AsElement();
132 }
133 };
134
NS_IMPL_ISUPPORTS(nsSplitterFrameInner,nsIDOMEventListener)135 NS_IMPL_ISUPPORTS(nsSplitterFrameInner, nsIDOMEventListener)
136
137 nsSplitterFrameInner::ResizeType nsSplitterFrameInner::GetResizeBefore() {
138 static Element::AttrValuesArray strings[] = {nsGkAtoms::farthest,
139 nsGkAtoms::flex, nullptr};
140 switch (SplitterElement()->FindAttrValueIn(
141 kNameSpaceID_None, nsGkAtoms::resizebefore, strings, eCaseMatters)) {
142 case 0:
143 return Farthest;
144 case 1:
145 return Flex;
146 }
147 return Closest;
148 }
149
150 nsSplitterFrameInner::~nsSplitterFrameInner() = default;
151
GetResizeAfter()152 nsSplitterFrameInner::ResizeType nsSplitterFrameInner::GetResizeAfter() {
153 static Element::AttrValuesArray strings[] = {
154 nsGkAtoms::farthest, nsGkAtoms::flex, nsGkAtoms::grow, nullptr};
155 switch (SplitterElement()->FindAttrValueIn(
156 kNameSpaceID_None, nsGkAtoms::resizeafter, strings, eCaseMatters)) {
157 case 0:
158 return Farthest;
159 case 1:
160 return Flex;
161 case 2:
162 return Grow;
163 }
164 return Closest;
165 }
166
GetState()167 nsSplitterFrameInner::State nsSplitterFrameInner::GetState() {
168 static Element::AttrValuesArray strings[] = {nsGkAtoms::dragging,
169 nsGkAtoms::collapsed, nullptr};
170 static Element::AttrValuesArray strings_substate[] = {
171 nsGkAtoms::before, nsGkAtoms::after, nullptr};
172 switch (SplitterElement()->FindAttrValueIn(
173 kNameSpaceID_None, nsGkAtoms::state, strings, eCaseMatters)) {
174 case 0:
175 return Dragging;
176 case 1:
177 switch (SplitterElement()->FindAttrValueIn(
178 kNameSpaceID_None, nsGkAtoms::substate, strings_substate,
179 eCaseMatters)) {
180 case 0:
181 return CollapsedBefore;
182 case 1:
183 return CollapsedAfter;
184 default:
185 if (SupportsCollapseDirection(After)) return CollapsedAfter;
186 return CollapsedBefore;
187 }
188 }
189 return Open;
190 }
191
192 //
193 // NS_NewSplitterFrame
194 //
195 // Creates a new Toolbar frame and returns it
196 //
NS_NewSplitterFrame(PresShell * aPresShell,ComputedStyle * aStyle)197 nsIFrame* NS_NewSplitterFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
198 return new (aPresShell) nsSplitterFrame(aStyle, aPresShell->GetPresContext());
199 }
200
NS_IMPL_FRAMEARENA_HELPERS(nsSplitterFrame)201 NS_IMPL_FRAMEARENA_HELPERS(nsSplitterFrame)
202
203 nsSplitterFrame::nsSplitterFrame(ComputedStyle* aStyle,
204 nsPresContext* aPresContext)
205 : nsBoxFrame(aStyle, aPresContext, kClassID), mInner(0) {}
206
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)207 void nsSplitterFrame::DestroyFrom(nsIFrame* aDestructRoot,
208 PostDestroyData& aPostDestroyData) {
209 if (mInner) {
210 mInner->RemoveListener();
211 mInner->Disconnect();
212 mInner->Release();
213 mInner = nullptr;
214 }
215 nsBoxFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
216 }
217
AttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType)218 nsresult nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID,
219 nsAtom* aAttribute,
220 int32_t aModType) {
221 nsresult rv =
222 nsBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
223 if (aAttribute == nsGkAtoms::state) {
224 mInner->UpdateState();
225 }
226
227 return rv;
228 }
229
230 /**
231 * Initialize us. If we are in a box get our alignment so we know what direction
232 * we are
233 */
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)234 void nsSplitterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
235 nsIFrame* aPrevInFlow) {
236 MOZ_ASSERT(!mInner);
237 mInner = new nsSplitterFrameInner(this);
238
239 mInner->AddRef();
240
241 // determine orientation of parent, and if vertical, set orient to vertical
242 // on splitter content, then re-resolve style
243 // XXXbz this is pretty messed up, since this can change whether we should
244 // have a frame at all. This really needs a better solution.
245 if (aParent && aParent->IsXULBoxFrame()) {
246 if (!aParent->IsXULHorizontal()) {
247 if (!nsContentUtils::HasNonEmptyAttr(aContent, kNameSpaceID_None,
248 nsGkAtoms::orient)) {
249 aContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::orient,
250 u"vertical"_ns, false);
251 }
252 }
253 }
254
255 nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
256
257 mInner->mState = nsSplitterFrameInner::Open;
258 mInner->AddListener();
259 mInner->mParentBox = nullptr;
260 }
261
262 NS_IMETHODIMP
DoXULLayout(nsBoxLayoutState & aState)263 nsSplitterFrame::DoXULLayout(nsBoxLayoutState& aState) {
264 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
265 mInner->mParentBox = nsIFrame::GetParentXULBox(this);
266 mInner->UpdateState();
267 }
268
269 return nsBoxFrame::DoXULLayout(aState);
270 }
271
GetInitialOrientation(bool & aIsHorizontal)272 void nsSplitterFrame::GetInitialOrientation(bool& aIsHorizontal) {
273 nsIFrame* box = nsIFrame::GetParentXULBox(this);
274 if (box) {
275 aIsHorizontal = !box->IsXULHorizontal();
276 } else
277 nsBoxFrame::GetInitialOrientation(aIsHorizontal);
278 }
279
280 NS_IMETHODIMP
HandlePress(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)281 nsSplitterFrame::HandlePress(nsPresContext* aPresContext,
282 WidgetGUIEvent* aEvent,
283 nsEventStatus* aEventStatus) {
284 return NS_OK;
285 }
286
287 NS_IMETHODIMP
HandleMultiplePress(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus,bool aControlHeld)288 nsSplitterFrame::HandleMultiplePress(nsPresContext* aPresContext,
289 WidgetGUIEvent* aEvent,
290 nsEventStatus* aEventStatus,
291 bool aControlHeld) {
292 return NS_OK;
293 }
294
295 NS_IMETHODIMP
HandleDrag(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)296 nsSplitterFrame::HandleDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
297 nsEventStatus* aEventStatus) {
298 return NS_OK;
299 }
300
301 NS_IMETHODIMP
HandleRelease(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)302 nsSplitterFrame::HandleRelease(nsPresContext* aPresContext,
303 WidgetGUIEvent* aEvent,
304 nsEventStatus* aEventStatus) {
305 return NS_OK;
306 }
307
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)308 void nsSplitterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
309 const nsDisplayListSet& aLists) {
310 nsBoxFrame::BuildDisplayList(aBuilder, aLists);
311
312 // if the mouse is captured always return us as the frame.
313 if (mInner->mDragging && aBuilder->IsForEventDelivery()) {
314 // XXX It's probably better not to check visibility here, right?
315 aLists.Outlines()->AppendNewToTop<nsDisplayEventReceiver>(aBuilder, this);
316 return;
317 }
318 }
319
HandleEvent(nsPresContext * aPresContext,WidgetGUIEvent * aEvent,nsEventStatus * aEventStatus)320 nsresult nsSplitterFrame::HandleEvent(nsPresContext* aPresContext,
321 WidgetGUIEvent* aEvent,
322 nsEventStatus* aEventStatus) {
323 NS_ENSURE_ARG_POINTER(aEventStatus);
324 if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
325 return NS_OK;
326 }
327
328 AutoWeakFrame weakFrame(this);
329 RefPtr<nsSplitterFrameInner> inner(mInner);
330 switch (aEvent->mMessage) {
331 case eMouseMove:
332 inner->MouseDrag(aPresContext, aEvent);
333 break;
334
335 case eMouseUp:
336 if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) {
337 inner->MouseUp(aPresContext, aEvent);
338 }
339 break;
340
341 default:
342 break;
343 }
344
345 NS_ENSURE_STATE(weakFrame.IsAlive());
346 return nsBoxFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
347 }
348
MouseUp(nsPresContext * aPresContext,WidgetGUIEvent * aEvent)349 void nsSplitterFrameInner::MouseUp(nsPresContext* aPresContext,
350 WidgetGUIEvent* aEvent) {
351 if (mDragging && mOuter) {
352 AdjustChildren(aPresContext);
353 AddListener();
354 PresShell::ReleaseCapturingContent(); // XXXndeakin is this needed?
355 mDragging = false;
356 State newState = GetState();
357 // if the state is dragging then make it Open.
358 if (newState == Dragging) {
359 mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None,
360 nsGkAtoms::state, u""_ns, true);
361 }
362
363 mPressed = false;
364
365 // if we dragged then fire a command event.
366 if (mDidDrag) {
367 RefPtr<nsXULElement> element =
368 nsXULElement::FromNode(mOuter->GetContent());
369 element->DoCommand();
370 }
371
372 // printf("MouseUp\n");
373 }
374
375 mChildInfosBefore = nullptr;
376 mChildInfosAfter = nullptr;
377 mChildInfosBeforeCount = 0;
378 mChildInfosAfterCount = 0;
379 }
380
MouseDrag(nsPresContext * aPresContext,WidgetGUIEvent * aEvent)381 void nsSplitterFrameInner::MouseDrag(nsPresContext* aPresContext,
382 WidgetGUIEvent* aEvent) {
383 if (mDragging && mOuter) {
384 // printf("Dragging\n");
385
386 bool isHorizontal = !mOuter->IsXULHorizontal();
387 // convert coord to pixels
388 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
389 aEvent, RelativeTo{mParentBox});
390 nscoord pos = isHorizontal ? pt.x : pt.y;
391
392 // mDragStart is in frame coordinates
393 nscoord start = mDragStart;
394
395 // take our current position and subtract the start location
396 pos -= start;
397
398 // printf("Diff=%d\n", pos);
399
400 ResizeType resizeAfter = GetResizeAfter();
401
402 const bool bounded = resizeAfter != nsSplitterFrameInner::Grow;
403
404 int i;
405 for (i = 0; i < mChildInfosBeforeCount; i++)
406 mChildInfosBefore[i].changed = mChildInfosBefore[i].current;
407
408 for (i = 0; i < mChildInfosAfterCount; i++)
409 mChildInfosAfter[i].changed = mChildInfosAfter[i].current;
410
411 nscoord oldPos = pos;
412
413 ResizeChildTo(pos, mChildInfosBefore.get(), mChildInfosAfter.get(),
414 mChildInfosBeforeCount, mChildInfosAfterCount, bounded);
415
416 State currentState = GetState();
417 bool supportsBefore = SupportsCollapseDirection(Before);
418 bool supportsAfter = SupportsCollapseDirection(After);
419
420 const bool isRTL =
421 mOuter->StyleVisibility()->mDirection == StyleDirection::Rtl;
422 bool pastEnd = oldPos > 0 && oldPos > pos;
423 bool pastBegin = oldPos < 0 && oldPos < pos;
424 if (isRTL) {
425 // Swap the boundary checks in RTL mode
426 std::swap(pastEnd, pastBegin);
427 }
428 const bool isCollapsedBefore = pastBegin && supportsBefore;
429 const bool isCollapsedAfter = pastEnd && supportsAfter;
430
431 // if we are in a collapsed position
432 if (isCollapsedBefore || isCollapsedAfter) {
433 // and we are not collapsed then collapse
434 if (currentState == Dragging) {
435 if (pastEnd) {
436 // printf("Collapse right\n");
437 if (supportsAfter) {
438 RefPtr<Element> outer = mOuter->mContent->AsElement();
439 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"after"_ns,
440 true);
441 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns,
442 true);
443 }
444
445 } else if (pastBegin) {
446 // printf("Collapse left\n");
447 if (supportsBefore) {
448 RefPtr<Element> outer = mOuter->mContent->AsElement();
449 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"before"_ns,
450 true);
451 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns,
452 true);
453 }
454 }
455 }
456 } else {
457 // if we are not in a collapsed position and we are not dragging make sure
458 // we are dragging.
459 if (currentState != Dragging) {
460 mOuter->mContent->AsElement()->SetAttr(
461 kNameSpaceID_None, nsGkAtoms::state, u"dragging"_ns, true);
462 }
463 AdjustChildren(aPresContext);
464 }
465
466 mDidDrag = true;
467 }
468 }
469
AddListener()470 void nsSplitterFrameInner::AddListener() {
471 mOuter->GetContent()->AddEventListener(u"mouseup"_ns, this, false, false);
472 mOuter->GetContent()->AddEventListener(u"mousedown"_ns, this, false, false);
473 mOuter->GetContent()->AddEventListener(u"mousemove"_ns, this, false, false);
474 mOuter->GetContent()->AddEventListener(u"mouseout"_ns, this, false, false);
475 }
476
RemoveListener()477 void nsSplitterFrameInner::RemoveListener() {
478 NS_ENSURE_TRUE_VOID(mOuter);
479 mOuter->GetContent()->RemoveEventListener(u"mouseup"_ns, this, false);
480 mOuter->GetContent()->RemoveEventListener(u"mousedown"_ns, this, false);
481 mOuter->GetContent()->RemoveEventListener(u"mousemove"_ns, this, false);
482 mOuter->GetContent()->RemoveEventListener(u"mouseout"_ns, this, false);
483 }
484
HandleEvent(dom::Event * aEvent)485 nsresult nsSplitterFrameInner::HandleEvent(dom::Event* aEvent) {
486 nsAutoString eventType;
487 aEvent->GetType(eventType);
488 if (eventType.EqualsLiteral("mouseup")) return MouseUp(aEvent);
489 if (eventType.EqualsLiteral("mousedown")) return MouseDown(aEvent);
490 if (eventType.EqualsLiteral("mousemove") ||
491 eventType.EqualsLiteral("mouseout"))
492 return MouseMove(aEvent);
493
494 MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
495 return NS_OK;
496 }
497
MouseUp(Event * aMouseEvent)498 nsresult nsSplitterFrameInner::MouseUp(Event* aMouseEvent) {
499 NS_ENSURE_TRUE(mOuter, NS_OK);
500 mPressed = false;
501
502 PresShell::ReleaseCapturingContent();
503
504 return NS_OK;
505 }
506
MouseDown(Event * aMouseEvent)507 nsresult nsSplitterFrameInner::MouseDown(Event* aMouseEvent) {
508 NS_ENSURE_TRUE(mOuter, NS_OK);
509 dom::MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
510 if (!mouseEvent) {
511 return NS_OK;
512 }
513
514 // only if left button
515 if (mouseEvent->Button() != 0) return NS_OK;
516
517 if (SplitterElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
518 nsGkAtoms::_true, eCaseMatters))
519 return NS_OK;
520
521 mParentBox = nsIFrame::GetParentXULBox(mOuter);
522 if (!mParentBox) {
523 return NS_OK;
524 }
525
526 // get our index
527 nsPresContext* outerPresContext = mOuter->PresContext();
528
529 const int32_t childCount = mParentBox->PrincipalChildList().GetLength();
530 RefPtr<gfxContext> rc =
531 outerPresContext->PresShell()->CreateReferenceRenderingContext();
532 nsBoxLayoutState state(outerPresContext, rc);
533
534 mDidDrag = false;
535
536 EnsureOrient();
537 bool isHorizontal = !mOuter->IsXULHorizontal();
538
539 ResizeType resizeBefore = GetResizeBefore();
540 ResizeType resizeAfter = GetResizeAfter();
541
542 mChildInfosBefore = MakeUnique<nsSplitterInfo[]>(childCount);
543 mChildInfosAfter = MakeUnique<nsSplitterInfo[]>(childCount);
544
545 // create info 2 lists. One of the children before us and one after.
546 int32_t count = 0;
547 mChildInfosBeforeCount = 0;
548 mChildInfosAfterCount = 0;
549
550 bool foundOuter = false;
551 CSSOrderAwareFrameIterator iter(
552 mParentBox, layout::kPrincipalList,
553 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
554 CSSOrderAwareFrameIterator::OrderState::Unknown,
555 CSSOrderAwareFrameIterator::OrderingProperty::BoxOrdinalGroup);
556 for (; !iter.AtEnd(); iter.Next()) {
557 nsIFrame* childBox = iter.get();
558 if (childBox == mOuter) {
559 foundOuter = true;
560 if (!count) {
561 // We're at the beginning, nothing to do.
562 return NS_OK;
563 }
564 if (count == childCount - 1 && resizeAfter != Grow) {
565 // if it's the last index then we need to allow for resizeafter="grow"
566 return NS_OK;
567 }
568 }
569 count++;
570
571 nsIContent* content = childBox->GetContent();
572
573 // skip over any splitters
574 if (content->NodeInfo()->NameAtom() != nsGkAtoms::splitter) {
575 nsSize prefSize = childBox->GetXULPrefSize(state);
576 nsSize minSize = childBox->GetXULMinSize(state);
577 nsSize maxSize = nsIFrame::XULBoundsCheckMinMax(
578 minSize, childBox->GetXULMaxSize(state));
579 prefSize = nsIFrame::XULBoundsCheck(minSize, prefSize, maxSize);
580
581 nsSplitterFrame::AddXULMargin(childBox, minSize);
582 nsSplitterFrame::AddXULMargin(childBox, prefSize);
583 nsSplitterFrame::AddXULMargin(childBox, maxSize);
584
585 nscoord flex = childBox->GetXULFlex();
586
587 nsMargin margin(0, 0, 0, 0);
588 childBox->GetXULMargin(margin);
589 nsRect r(childBox->GetRect());
590 r.Inflate(margin);
591
592 // We need to check for hidden attribute too, since treecols with
593 // the hidden="true" attribute are not really hidden, just collapsed
594 if (!content->IsElement() || (!content->AsElement()->AttrValueIs(
595 kNameSpaceID_None, nsGkAtoms::fixed,
596 nsGkAtoms::_true, eCaseMatters) &&
597 !content->AsElement()->AttrValueIs(
598 kNameSpaceID_None, nsGkAtoms::hidden,
599 nsGkAtoms::_true, eCaseMatters))) {
600 if (!foundOuter && (resizeBefore != Flex || flex > 0)) {
601 mChildInfosBefore[mChildInfosBeforeCount].childElem = content;
602 mChildInfosBefore[mChildInfosBeforeCount].min =
603 isHorizontal ? minSize.width : minSize.height;
604 mChildInfosBefore[mChildInfosBeforeCount].max =
605 isHorizontal ? maxSize.width : maxSize.height;
606 mChildInfosBefore[mChildInfosBeforeCount].current =
607 isHorizontal ? r.width : r.height;
608 mChildInfosBefore[mChildInfosBeforeCount].flex = flex;
609 mChildInfosBefore[mChildInfosBeforeCount].changed =
610 mChildInfosBefore[mChildInfosBeforeCount].current;
611 mChildInfosBeforeCount++;
612 } else if (foundOuter && (resizeAfter != Flex || flex > 0)) {
613 mChildInfosAfter[mChildInfosAfterCount].childElem = content;
614 mChildInfosAfter[mChildInfosAfterCount].min =
615 isHorizontal ? minSize.width : minSize.height;
616 mChildInfosAfter[mChildInfosAfterCount].max =
617 isHorizontal ? maxSize.width : maxSize.height;
618 mChildInfosAfter[mChildInfosAfterCount].current =
619 isHorizontal ? r.width : r.height;
620 mChildInfosAfter[mChildInfosAfterCount].flex = flex;
621 mChildInfosAfter[mChildInfosAfterCount].changed =
622 mChildInfosAfter[mChildInfosAfterCount].current;
623 mChildInfosAfterCount++;
624 }
625 }
626 }
627 }
628
629 if (!foundOuter) {
630 return NS_OK;
631 }
632
633 mPressed = true;
634
635 if (!mParentBox->IsXULNormalDirection()) {
636 // The before array is really the after array, and the order needs to be
637 // reversed. First reverse both arrays.
638 Reverse(mChildInfosBefore, mChildInfosBeforeCount);
639 Reverse(mChildInfosAfter, mChildInfosAfterCount);
640
641 // Now swap the two arrays.
642 std::swap(mChildInfosBeforeCount, mChildInfosAfterCount);
643 std::swap(mChildInfosBefore, mChildInfosAfter);
644 }
645
646 // if resizebefore is not Farthest, reverse the list because the first child
647 // in the list is the farthest, and we want the first child to be the closest.
648 if (resizeBefore != Farthest)
649 Reverse(mChildInfosBefore, mChildInfosBeforeCount);
650
651 // if the resizeafter is the Farthest we must reverse the list because the
652 // first child in the list is the closest we want the first child to be the
653 // Farthest.
654 if (resizeAfter == Farthest) Reverse(mChildInfosAfter, mChildInfosAfterCount);
655
656 // grow only applys to the children after. If grow is set then no space should
657 // be taken out of any children after us. To do this we just set the size of
658 // that list to be 0.
659 if (resizeAfter == Grow) mChildInfosAfterCount = 0;
660
661 int32_t c;
662 nsPoint pt =
663 nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent, mParentBox);
664 if (isHorizontal) {
665 c = pt.x;
666 mSplitterPos = mOuter->mRect.x;
667 } else {
668 c = pt.y;
669 mSplitterPos = mOuter->mRect.y;
670 }
671
672 mDragStart = c;
673
674 // printf("Pressed mDragStart=%d\n",mDragStart);
675
676 PresShell::SetCapturingContent(mOuter->GetContent(),
677 CaptureFlags::IgnoreAllowedState);
678
679 return NS_OK;
680 }
681
MouseMove(Event * aMouseEvent)682 nsresult nsSplitterFrameInner::MouseMove(Event* aMouseEvent) {
683 NS_ENSURE_TRUE(mOuter, NS_OK);
684 if (!mPressed) return NS_OK;
685
686 if (mDragging) return NS_OK;
687
688 nsCOMPtr<nsIDOMEventListener> kungfuDeathGrip(this);
689 mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
690 u"dragging"_ns, true);
691
692 RemoveListener();
693 mDragging = true;
694
695 return NS_OK;
696 }
697
Reverse(UniquePtr<nsSplitterInfo[]> & aChildInfos,int32_t aCount)698 void nsSplitterFrameInner::Reverse(UniquePtr<nsSplitterInfo[]>& aChildInfos,
699 int32_t aCount) {
700 UniquePtr<nsSplitterInfo[]> infos(new nsSplitterInfo[aCount]);
701
702 for (int i = 0; i < aCount; i++) infos[i] = aChildInfos[aCount - 1 - i];
703
704 aChildInfos = std::move(infos);
705 }
706
SupportsCollapseDirection(nsSplitterFrameInner::CollapseDirection aDirection)707 bool nsSplitterFrameInner::SupportsCollapseDirection(
708 nsSplitterFrameInner::CollapseDirection aDirection) {
709 static Element::AttrValuesArray strings[] = {
710 nsGkAtoms::before, nsGkAtoms::after, nsGkAtoms::both, nullptr};
711
712 switch (SplitterElement()->FindAttrValueIn(
713 kNameSpaceID_None, nsGkAtoms::collapse, strings, eCaseMatters)) {
714 case 0:
715 return (aDirection == Before);
716 case 1:
717 return (aDirection == After);
718 case 2:
719 return true;
720 }
721
722 return false;
723 }
724
UpdateState()725 void nsSplitterFrameInner::UpdateState() {
726 // State Transitions:
727 // Open -> Dragging
728 // Open -> CollapsedBefore
729 // Open -> CollapsedAfter
730 // CollapsedBefore -> Open
731 // CollapsedBefore -> Dragging
732 // CollapsedAfter -> Open
733 // CollapsedAfter -> Dragging
734 // Dragging -> Open
735 // Dragging -> CollapsedBefore (auto collapse)
736 // Dragging -> CollapsedAfter (auto collapse)
737
738 State newState = GetState();
739
740 if (newState == mState) {
741 // No change.
742 return;
743 }
744
745 if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) &&
746 mOuter->GetParent()->IsXULBoxFrame()) {
747 // Find the splitter's immediate sibling.
748 const bool prev = newState == CollapsedBefore || mState == CollapsedBefore;
749 nsIFrame* splitterSibling =
750 nsBoxFrame::SlowOrdinalGroupAwareSibling(mOuter, !prev);
751 if (splitterSibling) {
752 nsCOMPtr<nsIContent> sibling = splitterSibling->GetContent();
753 if (sibling && sibling->IsElement()) {
754 if (mState == CollapsedBefore || mState == CollapsedAfter) {
755 // CollapsedBefore -> Open
756 // CollapsedBefore -> Dragging
757 // CollapsedAfter -> Open
758 // CollapsedAfter -> Dragging
759 nsContentUtils::AddScriptRunner(new nsUnsetAttrRunnable(
760 sibling->AsElement(), nsGkAtoms::collapsed));
761 } else if ((mState == Open || mState == Dragging) &&
762 (newState == CollapsedBefore ||
763 newState == CollapsedAfter)) {
764 // Open -> CollapsedBefore / CollapsedAfter
765 // Dragging -> CollapsedBefore / CollapsedAfter
766 nsContentUtils::AddScriptRunner(new nsSetAttrRunnable(
767 sibling->AsElement(), nsGkAtoms::collapsed, u"true"_ns));
768 }
769 }
770 }
771 }
772 mState = newState;
773 }
774
EnsureOrient()775 void nsSplitterFrameInner::EnsureOrient() {
776 bool isHorizontal = !mParentBox->HasAnyStateBits(NS_STATE_IS_HORIZONTAL);
777 if (isHorizontal)
778 mOuter->AddStateBits(NS_STATE_IS_HORIZONTAL);
779 else
780 mOuter->RemoveStateBits(NS_STATE_IS_HORIZONTAL);
781 }
782
AdjustChildren(nsPresContext * aPresContext)783 void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext) {
784 EnsureOrient();
785 bool isHorizontal = !mOuter->IsXULHorizontal();
786
787 AdjustChildren(aPresContext, mChildInfosBefore.get(), mChildInfosBeforeCount,
788 isHorizontal);
789 AdjustChildren(aPresContext, mChildInfosAfter.get(), mChildInfosAfterCount,
790 isHorizontal);
791 }
792
GetChildBoxForContent(nsIFrame * aParentBox,nsIContent * aContent)793 static nsIFrame* GetChildBoxForContent(nsIFrame* aParentBox,
794 nsIContent* aContent) {
795 nsIFrame* childBox = nsIFrame::GetChildXULBox(aParentBox);
796
797 while (childBox) {
798 if (childBox->GetContent() == aContent) {
799 return childBox;
800 }
801 childBox = nsIFrame::GetNextXULBox(childBox);
802 }
803 return nullptr;
804 }
805
AdjustChildren(nsPresContext * aPresContext,nsSplitterInfo * aChildInfos,int32_t aCount,bool aIsHorizontal)806 void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext,
807 nsSplitterInfo* aChildInfos,
808 int32_t aCount, bool aIsHorizontal) {
809 /// printf("------- AdjustChildren------\n");
810
811 nsBoxLayoutState state(aPresContext);
812
813 nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
814
815 // first set all the widths.
816 nsIFrame* child = nsIFrame::GetChildXULBox(mOuter);
817 while (child) {
818 SetPreferredSize(state, child, onePixel, aIsHorizontal, nullptr);
819 child = nsIFrame::GetNextXULBox(child);
820 }
821
822 // now set our changed widths.
823 for (int i = 0; i < aCount; i++) {
824 nscoord pref = aChildInfos[i].changed;
825 nsIFrame* childBox =
826 GetChildBoxForContent(mParentBox, aChildInfos[i].childElem);
827
828 if (childBox) {
829 SetPreferredSize(state, childBox, onePixel, aIsHorizontal, &pref);
830 }
831 }
832 }
833
SetPreferredSize(nsBoxLayoutState & aState,nsIFrame * aChildBox,nscoord aOnePixel,bool aIsHorizontal,nscoord * aSize)834 void nsSplitterFrameInner::SetPreferredSize(nsBoxLayoutState& aState,
835 nsIFrame* aChildBox,
836 nscoord aOnePixel,
837 bool aIsHorizontal,
838 nscoord* aSize) {
839 nsRect rect(aChildBox->GetRect());
840 nscoord pref = 0;
841
842 if (!aSize) {
843 if (aIsHorizontal)
844 pref = rect.width;
845 else
846 pref = rect.height;
847 } else {
848 pref = *aSize;
849 }
850
851 nsMargin margin(0, 0, 0, 0);
852 aChildBox->GetXULMargin(margin);
853
854 RefPtr<nsAtom> attribute;
855
856 if (aIsHorizontal) {
857 pref -= (margin.left + margin.right);
858 attribute = nsGkAtoms::width;
859 } else {
860 pref -= (margin.top + margin.bottom);
861 attribute = nsGkAtoms::height;
862 }
863
864 nsIContent* content = aChildBox->GetContent();
865 if (!content->IsElement()) {
866 return;
867 }
868
869 // set its preferred size.
870 nsAutoString prefValue;
871 prefValue.AppendInt(pref / aOnePixel);
872 if (content->AsElement()->AttrValueIs(kNameSpaceID_None, attribute, prefValue,
873 eCaseMatters)) {
874 return;
875 }
876
877 AutoWeakFrame weakBox(aChildBox);
878 content->AsElement()->SetAttr(kNameSpaceID_None, attribute, prefValue, true);
879 NS_ENSURE_TRUE_VOID(weakBox.IsAlive());
880 aState.PresShell()->FrameNeedsReflow(aChildBox, IntrinsicDirty::StyleChange,
881 NS_FRAME_IS_DIRTY);
882 }
883
AddRemoveSpace(nscoord aDiff,nsSplitterInfo * aChildInfos,int32_t aCount,int32_t & aSpaceLeft)884 void nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff,
885 nsSplitterInfo* aChildInfos,
886 int32_t aCount, int32_t& aSpaceLeft) {
887 aSpaceLeft = 0;
888
889 for (int i = 0; i < aCount; i++) {
890 nscoord min = aChildInfos[i].min;
891 nscoord max = aChildInfos[i].max;
892 nscoord& c = aChildInfos[i].changed;
893
894 // figure our how much space to add or remove
895 if (c + aDiff < min) {
896 aDiff += (c - min);
897 c = min;
898 } else if (c + aDiff > max) {
899 aDiff -= (max - c);
900 c = max;
901 } else {
902 c += aDiff;
903 aDiff = 0;
904 }
905
906 // there is not space left? We are done
907 if (aDiff == 0) break;
908 }
909
910 aSpaceLeft = aDiff;
911 }
912
913 /**
914 * Ok if we want to resize a child we will know the actual size in pixels we
915 * want it to be. This is not the preferred size. But they only way we can
916 * change a child is my manipulating its preferred size. So give the actual
917 * pixel size this return method will return figure out the preferred size and
918 * set it.
919 */
920
ResizeChildTo(nscoord & aDiff,nsSplitterInfo * aChildrenBeforeInfos,nsSplitterInfo * aChildrenAfterInfos,int32_t aChildrenBeforeCount,int32_t aChildrenAfterCount,bool aBounded)921 void nsSplitterFrameInner::ResizeChildTo(nscoord& aDiff,
922 nsSplitterInfo* aChildrenBeforeInfos,
923 nsSplitterInfo* aChildrenAfterInfos,
924 int32_t aChildrenBeforeCount,
925 int32_t aChildrenAfterCount,
926 bool aBounded) {
927 nscoord spaceLeft;
928 AddRemoveSpace(aDiff, aChildrenBeforeInfos, aChildrenBeforeCount, spaceLeft);
929
930 // if there is any space left over remove it from the dif we were originally
931 // given
932 aDiff -= spaceLeft;
933 AddRemoveSpace(-aDiff, aChildrenAfterInfos, aChildrenAfterCount, spaceLeft);
934
935 if (spaceLeft != 0) {
936 if (aBounded) {
937 aDiff += spaceLeft;
938 AddRemoveSpace(spaceLeft, aChildrenBeforeInfos, aChildrenBeforeCount,
939 spaceLeft);
940 }
941 }
942 }
943