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