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