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