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 "nsBoxLayoutState.h"
27 #include "nsContainerFrame.h"
28 #include "nsContentCID.h"
29 #include "nsLayoutUtils.h"
30 #include "nsDisplayList.h"
31 #include "nsContentUtils.h"
32 #include "mozilla/dom/Element.h"
33 #include "mozilla/dom/Event.h"
34 #include "mozilla/dom/MouseEvent.h"
35 #include "mozilla/MouseEvents.h"
36 #include "mozilla/PresShell.h"
37 #include "mozilla/UniquePtr.h"
38
39 using namespace mozilla;
40
41 using mozilla::dom::Element;
42 using mozilla::dom::Event;
43
44 class nsSplitterInfo {
45 public:
46 nscoord min;
47 nscoord max;
48 nscoord current;
49 nscoord changed;
50 nsCOMPtr<nsIContent> childElem;
51 int32_t flex;
52 int32_t index;
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 NS_LITERAL_STRING("vertical"), 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 (GetStateBits() & 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::eLeft) {
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(
360 kNameSpaceID_None, nsGkAtoms::state, EmptyString(), 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 bool bounded;
403
404 if (resizeAfter == nsSplitterFrameInner::Grow)
405 bounded = false;
406 else
407 bounded = true;
408
409 int i;
410 for (i = 0; i < mChildInfosBeforeCount; i++)
411 mChildInfosBefore[i].changed = mChildInfosBefore[i].current;
412
413 for (i = 0; i < mChildInfosAfterCount; i++)
414 mChildInfosAfter[i].changed = mChildInfosAfter[i].current;
415
416 nscoord oldPos = pos;
417
418 ResizeChildTo(pos, mChildInfosBefore.get(), mChildInfosAfter.get(),
419 mChildInfosBeforeCount, mChildInfosAfterCount, bounded);
420
421 State currentState = GetState();
422 bool supportsBefore = SupportsCollapseDirection(Before);
423 bool supportsAfter = SupportsCollapseDirection(After);
424
425 const bool isRTL =
426 mOuter->StyleVisibility()->mDirection == StyleDirection::Rtl;
427 bool pastEnd = oldPos > 0 && oldPos > pos;
428 bool pastBegin = oldPos < 0 && oldPos < pos;
429 if (isRTL) {
430 // Swap the boundary checks in RTL mode
431 bool tmp = pastEnd;
432 pastEnd = pastBegin;
433 pastBegin = tmp;
434 }
435 const bool isCollapsedBefore = pastBegin && supportsBefore;
436 const bool isCollapsedAfter = pastEnd && supportsAfter;
437
438 // if we are in a collapsed position
439 if (isCollapsedBefore || isCollapsedAfter) {
440 // and we are not collapsed then collapse
441 if (currentState == Dragging) {
442 if (pastEnd) {
443 // printf("Collapse right\n");
444 if (supportsAfter) {
445 RefPtr<Element> outer = mOuter->mContent->AsElement();
446 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate,
447 NS_LITERAL_STRING("after"), true);
448 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
449 NS_LITERAL_STRING("collapsed"), true);
450 }
451
452 } else if (pastBegin) {
453 // printf("Collapse left\n");
454 if (supportsBefore) {
455 RefPtr<Element> outer = mOuter->mContent->AsElement();
456 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate,
457 NS_LITERAL_STRING("before"), true);
458 outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
459 NS_LITERAL_STRING("collapsed"), true);
460 }
461 }
462 }
463 } else {
464 // if we are not in a collapsed position and we are not dragging make sure
465 // we are dragging.
466 if (currentState != Dragging) {
467 mOuter->mContent->AsElement()->SetAttr(
468 kNameSpaceID_None, nsGkAtoms::state, NS_LITERAL_STRING("dragging"),
469 true);
470 }
471 AdjustChildren(aPresContext);
472 }
473
474 mDidDrag = true;
475 }
476 }
477
AddListener()478 void nsSplitterFrameInner::AddListener() {
479 mOuter->GetContent()->AddEventListener(NS_LITERAL_STRING("mouseup"), this,
480 false, false);
481 mOuter->GetContent()->AddEventListener(NS_LITERAL_STRING("mousedown"), this,
482 false, false);
483 mOuter->GetContent()->AddEventListener(NS_LITERAL_STRING("mousemove"), this,
484 false, false);
485 mOuter->GetContent()->AddEventListener(NS_LITERAL_STRING("mouseout"), this,
486 false, false);
487 }
488
RemoveListener()489 void nsSplitterFrameInner::RemoveListener() {
490 NS_ENSURE_TRUE_VOID(mOuter);
491 mOuter->GetContent()->RemoveEventListener(NS_LITERAL_STRING("mouseup"), this,
492 false);
493 mOuter->GetContent()->RemoveEventListener(NS_LITERAL_STRING("mousedown"),
494 this, false);
495 mOuter->GetContent()->RemoveEventListener(NS_LITERAL_STRING("mousemove"),
496 this, false);
497 mOuter->GetContent()->RemoveEventListener(NS_LITERAL_STRING("mouseout"), this,
498 false);
499 }
500
HandleEvent(dom::Event * aEvent)501 nsresult nsSplitterFrameInner::HandleEvent(dom::Event* aEvent) {
502 nsAutoString eventType;
503 aEvent->GetType(eventType);
504 if (eventType.EqualsLiteral("mouseup")) return MouseUp(aEvent);
505 if (eventType.EqualsLiteral("mousedown")) return MouseDown(aEvent);
506 if (eventType.EqualsLiteral("mousemove") ||
507 eventType.EqualsLiteral("mouseout"))
508 return MouseMove(aEvent);
509
510 MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
511 return NS_OK;
512 }
513
MouseUp(Event * aMouseEvent)514 nsresult nsSplitterFrameInner::MouseUp(Event* aMouseEvent) {
515 NS_ENSURE_TRUE(mOuter, NS_OK);
516 mPressed = false;
517
518 PresShell::ReleaseCapturingContent();
519
520 return NS_OK;
521 }
522
MouseDown(Event * aMouseEvent)523 nsresult nsSplitterFrameInner::MouseDown(Event* aMouseEvent) {
524 NS_ENSURE_TRUE(mOuter, NS_OK);
525 dom::MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
526 if (!mouseEvent) {
527 return NS_OK;
528 }
529
530 // only if left button
531 if (mouseEvent->Button() != 0) return NS_OK;
532
533 if (SplitterElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
534 nsGkAtoms::_true, eCaseMatters))
535 return NS_OK;
536
537 mParentBox = nsIFrame::GetParentXULBox(mOuter);
538 if (!mParentBox) return NS_OK;
539
540 // get our index
541 nsPresContext* outerPresContext = mOuter->PresContext();
542 const nsFrameList& siblingList(mParentBox->PrincipalChildList());
543 int32_t childIndex = siblingList.IndexOf(mOuter);
544 // if it's 0 (or not found) then stop right here.
545 // It might be not found if we're not in the parent's primary frame list.
546 if (childIndex <= 0) return NS_OK;
547
548 int32_t childCount = siblingList.GetLength();
549 // if it's the last index then we need to allow for resizeafter="grow"
550 if (childIndex == childCount - 1 && GetResizeAfter() != Grow) return NS_OK;
551
552 RefPtr<gfxContext> rc =
553 outerPresContext->PresShell()->CreateReferenceRenderingContext();
554 nsBoxLayoutState state(outerPresContext, rc);
555 mPressed = true;
556
557 mDidDrag = false;
558
559 EnsureOrient();
560 bool isHorizontal = !mOuter->IsXULHorizontal();
561
562 ResizeType resizeBefore = GetResizeBefore();
563 ResizeType resizeAfter = GetResizeAfter();
564
565 mChildInfosBefore = MakeUnique<nsSplitterInfo[]>(childCount);
566 mChildInfosAfter = MakeUnique<nsSplitterInfo[]>(childCount);
567
568 // create info 2 lists. One of the children before us and one after.
569 int32_t count = 0;
570 mChildInfosBeforeCount = 0;
571 mChildInfosAfterCount = 0;
572
573 nsIFrame* childBox = nsIFrame::GetChildXULBox(mParentBox);
574
575 while (childBox) {
576 nsIContent* content = childBox->GetContent();
577
578 // skip over any splitters
579 if (content->NodeInfo()->NameAtom() != nsGkAtoms::splitter) {
580 nsSize prefSize = childBox->GetXULPrefSize(state);
581 nsSize minSize = childBox->GetXULMinSize(state);
582 nsSize maxSize = nsIFrame::XULBoundsCheckMinMax(
583 minSize, childBox->GetXULMaxSize(state));
584 prefSize = nsIFrame::XULBoundsCheck(minSize, prefSize, maxSize);
585
586 nsSplitterFrame::AddXULMargin(childBox, minSize);
587 nsSplitterFrame::AddXULMargin(childBox, prefSize);
588 nsSplitterFrame::AddXULMargin(childBox, maxSize);
589
590 nscoord flex = childBox->GetXULFlex();
591
592 nsMargin margin(0, 0, 0, 0);
593 childBox->GetXULMargin(margin);
594 nsRect r(childBox->GetRect());
595 r.Inflate(margin);
596
597 // We need to check for hidden attribute too, since treecols with
598 // the hidden="true" attribute are not really hidden, just collapsed
599 if (!content->IsElement() || (!content->AsElement()->AttrValueIs(
600 kNameSpaceID_None, nsGkAtoms::fixed,
601 nsGkAtoms::_true, eCaseMatters) &&
602 !content->AsElement()->AttrValueIs(
603 kNameSpaceID_None, nsGkAtoms::hidden,
604 nsGkAtoms::_true, eCaseMatters))) {
605 if (count < childIndex && (resizeBefore != Flex || flex > 0)) {
606 mChildInfosBefore[mChildInfosBeforeCount].childElem = content;
607 mChildInfosBefore[mChildInfosBeforeCount].min =
608 isHorizontal ? minSize.width : minSize.height;
609 mChildInfosBefore[mChildInfosBeforeCount].max =
610 isHorizontal ? maxSize.width : maxSize.height;
611 mChildInfosBefore[mChildInfosBeforeCount].current =
612 isHorizontal ? r.width : r.height;
613 mChildInfosBefore[mChildInfosBeforeCount].flex = flex;
614 mChildInfosBefore[mChildInfosBeforeCount].index = count;
615 mChildInfosBefore[mChildInfosBeforeCount].changed =
616 mChildInfosBefore[mChildInfosBeforeCount].current;
617 mChildInfosBeforeCount++;
618 } else if (count > childIndex && (resizeAfter != Flex || flex > 0)) {
619 mChildInfosAfter[mChildInfosAfterCount].childElem = content;
620 mChildInfosAfter[mChildInfosAfterCount].min =
621 isHorizontal ? minSize.width : minSize.height;
622 mChildInfosAfter[mChildInfosAfterCount].max =
623 isHorizontal ? maxSize.width : maxSize.height;
624 mChildInfosAfter[mChildInfosAfterCount].current =
625 isHorizontal ? r.width : r.height;
626 mChildInfosAfter[mChildInfosAfterCount].flex = flex;
627 mChildInfosAfter[mChildInfosAfterCount].index = count;
628 mChildInfosAfter[mChildInfosAfterCount].changed =
629 mChildInfosAfter[mChildInfosAfterCount].current;
630 mChildInfosAfterCount++;
631 }
632 }
633 }
634
635 childBox = nsIFrame::GetNextXULBox(childBox);
636 count++;
637 }
638
639 if (!mParentBox->IsXULNormalDirection()) {
640 // The before array is really the after array, and the order needs to be
641 // reversed. First reverse both arrays.
642 Reverse(mChildInfosBefore, mChildInfosBeforeCount);
643 Reverse(mChildInfosAfter, mChildInfosAfterCount);
644
645 // Now swap the two arrays.
646 std::swap(mChildInfosBeforeCount, mChildInfosAfterCount);
647 std::swap(mChildInfosBefore, mChildInfosAfter);
648 }
649
650 // if resizebefore is not Farthest, reverse the list because the first child
651 // in the list is the farthest, and we want the first child to be the closest.
652 if (resizeBefore != Farthest)
653 Reverse(mChildInfosBefore, mChildInfosBeforeCount);
654
655 // if the resizeafter is the Farthest we must reverse the list because the
656 // first child in the list is the closest we want the first child to be the
657 // Farthest.
658 if (resizeAfter == Farthest) Reverse(mChildInfosAfter, mChildInfosAfterCount);
659
660 // grow only applys to the children after. If grow is set then no space should
661 // be taken out of any children after us. To do this we just set the size of
662 // that list to be 0.
663 if (resizeAfter == Grow) mChildInfosAfterCount = 0;
664
665 int32_t c;
666 nsPoint pt =
667 nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent, mParentBox);
668 if (isHorizontal) {
669 c = pt.x;
670 mSplitterPos = mOuter->mRect.x;
671 } else {
672 c = pt.y;
673 mSplitterPos = mOuter->mRect.y;
674 }
675
676 mDragStart = c;
677
678 // printf("Pressed mDragStart=%d\n",mDragStart);
679
680 PresShell::SetCapturingContent(mOuter->GetContent(),
681 CaptureFlags::IgnoreAllowedState);
682
683 return NS_OK;
684 }
685
MouseMove(Event * aMouseEvent)686 nsresult nsSplitterFrameInner::MouseMove(Event* aMouseEvent) {
687 NS_ENSURE_TRUE(mOuter, NS_OK);
688 if (!mPressed) return NS_OK;
689
690 if (mDragging) return NS_OK;
691
692 nsCOMPtr<nsIDOMEventListener> kungfuDeathGrip(this);
693 mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
694 NS_LITERAL_STRING("dragging"), true);
695
696 RemoveListener();
697 mDragging = true;
698
699 return NS_OK;
700 }
701
Reverse(UniquePtr<nsSplitterInfo[]> & aChildInfos,int32_t aCount)702 void nsSplitterFrameInner::Reverse(UniquePtr<nsSplitterInfo[]>& aChildInfos,
703 int32_t aCount) {
704 UniquePtr<nsSplitterInfo[]> infos(new nsSplitterInfo[aCount]);
705
706 for (int i = 0; i < aCount; i++) infos[i] = aChildInfos[aCount - 1 - i];
707
708 aChildInfos = std::move(infos);
709 }
710
SupportsCollapseDirection(nsSplitterFrameInner::CollapseDirection aDirection)711 bool nsSplitterFrameInner::SupportsCollapseDirection(
712 nsSplitterFrameInner::CollapseDirection aDirection) {
713 static Element::AttrValuesArray strings[] = {
714 nsGkAtoms::before, nsGkAtoms::after, nsGkAtoms::both, nullptr};
715
716 switch (SplitterElement()->FindAttrValueIn(
717 kNameSpaceID_None, nsGkAtoms::collapse, strings, eCaseMatters)) {
718 case 0:
719 return (aDirection == Before);
720 case 1:
721 return (aDirection == After);
722 case 2:
723 return true;
724 }
725
726 return false;
727 }
728
UpdateState()729 void nsSplitterFrameInner::UpdateState() {
730 // State Transitions:
731 // Open -> Dragging
732 // Open -> CollapsedBefore
733 // Open -> CollapsedAfter
734 // CollapsedBefore -> Open
735 // CollapsedBefore -> Dragging
736 // CollapsedAfter -> Open
737 // CollapsedAfter -> Dragging
738 // Dragging -> Open
739 // Dragging -> CollapsedBefore (auto collapse)
740 // Dragging -> CollapsedAfter (auto collapse)
741
742 State newState = GetState();
743
744 if (newState == mState) {
745 // No change.
746 return;
747 }
748
749 if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) &&
750 mOuter->GetParent()->IsXULBoxFrame()) {
751 // Find the splitter's immediate sibling.
752 nsIFrame* splitterSibling;
753 if (newState == CollapsedBefore || mState == CollapsedBefore) {
754 splitterSibling = mOuter->GetPrevSibling();
755 } else {
756 splitterSibling = mOuter->GetNextSibling();
757 }
758
759 if (splitterSibling) {
760 nsCOMPtr<nsIContent> sibling = splitterSibling->GetContent();
761 if (sibling && sibling->IsElement()) {
762 if (mState == CollapsedBefore || mState == CollapsedAfter) {
763 // CollapsedBefore -> Open
764 // CollapsedBefore -> Dragging
765 // CollapsedAfter -> Open
766 // CollapsedAfter -> Dragging
767 nsContentUtils::AddScriptRunner(new nsUnsetAttrRunnable(
768 sibling->AsElement(), nsGkAtoms::collapsed));
769 } else if ((mState == Open || mState == Dragging) &&
770 (newState == CollapsedBefore ||
771 newState == CollapsedAfter)) {
772 // Open -> CollapsedBefore / CollapsedAfter
773 // Dragging -> CollapsedBefore / CollapsedAfter
774 nsContentUtils::AddScriptRunner(
775 new nsSetAttrRunnable(sibling->AsElement(), nsGkAtoms::collapsed,
776 NS_LITERAL_STRING("true")));
777 }
778 }
779 }
780 }
781 mState = newState;
782 }
783
EnsureOrient()784 void nsSplitterFrameInner::EnsureOrient() {
785 bool isHorizontal = !(mParentBox->GetStateBits() & NS_STATE_IS_HORIZONTAL);
786 if (isHorizontal)
787 mOuter->AddStateBits(NS_STATE_IS_HORIZONTAL);
788 else
789 mOuter->RemoveStateBits(NS_STATE_IS_HORIZONTAL);
790 }
791
AdjustChildren(nsPresContext * aPresContext)792 void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext) {
793 EnsureOrient();
794 bool isHorizontal = !mOuter->IsXULHorizontal();
795
796 AdjustChildren(aPresContext, mChildInfosBefore.get(), mChildInfosBeforeCount,
797 isHorizontal);
798 AdjustChildren(aPresContext, mChildInfosAfter.get(), mChildInfosAfterCount,
799 isHorizontal);
800 }
801
GetChildBoxForContent(nsIFrame * aParentBox,nsIContent * aContent)802 static nsIFrame* GetChildBoxForContent(nsIFrame* aParentBox,
803 nsIContent* aContent) {
804 nsIFrame* childBox = nsIFrame::GetChildXULBox(aParentBox);
805
806 while (nullptr != childBox) {
807 if (childBox->GetContent() == aContent) {
808 return childBox;
809 }
810 childBox = nsIFrame::GetNextXULBox(childBox);
811 }
812 return nullptr;
813 }
814
AdjustChildren(nsPresContext * aPresContext,nsSplitterInfo * aChildInfos,int32_t aCount,bool aIsHorizontal)815 void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext,
816 nsSplitterInfo* aChildInfos,
817 int32_t aCount, bool aIsHorizontal) {
818 /// printf("------- AdjustChildren------\n");
819
820 nsBoxLayoutState state(aPresContext);
821
822 nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
823
824 // first set all the widths.
825 nsIFrame* child = nsIFrame::GetChildXULBox(mOuter);
826 while (child) {
827 SetPreferredSize(state, child, onePixel, aIsHorizontal, nullptr);
828 child = nsIFrame::GetNextXULBox(child);
829 }
830
831 // now set our changed widths.
832 for (int i = 0; i < aCount; i++) {
833 nscoord pref = aChildInfos[i].changed;
834 nsIFrame* childBox =
835 GetChildBoxForContent(mParentBox, aChildInfos[i].childElem);
836
837 if (childBox) {
838 SetPreferredSize(state, childBox, onePixel, aIsHorizontal, &pref);
839 }
840 }
841 }
842
SetPreferredSize(nsBoxLayoutState & aState,nsIFrame * aChildBox,nscoord aOnePixel,bool aIsHorizontal,nscoord * aSize)843 void nsSplitterFrameInner::SetPreferredSize(nsBoxLayoutState& aState,
844 nsIFrame* aChildBox,
845 nscoord aOnePixel,
846 bool aIsHorizontal,
847 nscoord* aSize) {
848 nsRect rect(aChildBox->GetRect());
849 nscoord pref = 0;
850
851 if (!aSize) {
852 if (aIsHorizontal)
853 pref = rect.width;
854 else
855 pref = rect.height;
856 } else {
857 pref = *aSize;
858 }
859
860 nsMargin margin(0, 0, 0, 0);
861 aChildBox->GetXULMargin(margin);
862
863 RefPtr<nsAtom> attribute;
864
865 if (aIsHorizontal) {
866 pref -= (margin.left + margin.right);
867 attribute = nsGkAtoms::width;
868 } else {
869 pref -= (margin.top + margin.bottom);
870 attribute = nsGkAtoms::height;
871 }
872
873 nsIContent* content = aChildBox->GetContent();
874 if (!content->IsElement()) {
875 return;
876 }
877
878 // set its preferred size.
879 nsAutoString prefValue;
880 prefValue.AppendInt(pref / aOnePixel);
881 if (content->AsElement()->AttrValueIs(kNameSpaceID_None, attribute, prefValue,
882 eCaseMatters)) {
883 return;
884 }
885
886 AutoWeakFrame weakBox(aChildBox);
887 content->AsElement()->SetAttr(kNameSpaceID_None, attribute, prefValue, true);
888 NS_ENSURE_TRUE_VOID(weakBox.IsAlive());
889 aState.PresShell()->FrameNeedsReflow(aChildBox, IntrinsicDirty::StyleChange,
890 NS_FRAME_IS_DIRTY);
891 }
892
AddRemoveSpace(nscoord aDiff,nsSplitterInfo * aChildInfos,int32_t aCount,int32_t & aSpaceLeft)893 void nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff,
894 nsSplitterInfo* aChildInfos,
895 int32_t aCount, int32_t& aSpaceLeft) {
896 aSpaceLeft = 0;
897
898 for (int i = 0; i < aCount; i++) {
899 nscoord min = aChildInfos[i].min;
900 nscoord max = aChildInfos[i].max;
901 nscoord& c = aChildInfos[i].changed;
902
903 // figure our how much space to add or remove
904 if (c + aDiff < min) {
905 aDiff += (c - min);
906 c = min;
907 } else if (c + aDiff > max) {
908 aDiff -= (max - c);
909 c = max;
910 } else {
911 c += aDiff;
912 aDiff = 0;
913 }
914
915 // there is not space left? We are done
916 if (aDiff == 0) break;
917 }
918
919 aSpaceLeft = aDiff;
920 }
921
922 /**
923 * Ok if we want to resize a child we will know the actual size in pixels we
924 * want it to be. This is not the preferred size. But they only way we can
925 * change a child is my manipulating its preferred size. So give the actual
926 * pixel size this return method will return figure out the preferred size and
927 * set it.
928 */
929
ResizeChildTo(nscoord & aDiff,nsSplitterInfo * aChildrenBeforeInfos,nsSplitterInfo * aChildrenAfterInfos,int32_t aChildrenBeforeCount,int32_t aChildrenAfterCount,bool aBounded)930 void nsSplitterFrameInner::ResizeChildTo(nscoord& aDiff,
931 nsSplitterInfo* aChildrenBeforeInfos,
932 nsSplitterInfo* aChildrenAfterInfos,
933 int32_t aChildrenBeforeCount,
934 int32_t aChildrenAfterCount,
935 bool aBounded) {
936 nscoord spaceLeft;
937 AddRemoveSpace(aDiff, aChildrenBeforeInfos, aChildrenBeforeCount, spaceLeft);
938
939 // if there is any space left over remove it from the dif we were originally
940 // given
941 aDiff -= spaceLeft;
942 AddRemoveSpace(-aDiff, aChildrenAfterInfos, aChildrenAfterCount, spaceLeft);
943
944 if (spaceLeft != 0) {
945 if (aBounded) {
946 aDiff += spaceLeft;
947 AddRemoveSpace(spaceLeft, aChildrenBeforeInfos, aChildrenBeforeCount,
948 spaceLeft);
949 }
950 }
951 }
952