Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/layout/xul/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 29 kB image not shown  

Quelle  nsSplitterFrame.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


//
// Eric Vaughan
// Netscape Communications
//
// See documentation in associated header file
//

#include "LayoutConstants.h"
#include "SimpleXULLeafFrame.h"
#include "gfxContext.h"
#include "mozilla/ReflowInput.h"
#include "nsSplitterFrame.h"
#include "nsGkAtoms.h"
#include "nsXULElement.h"
#include "nsPresContext.h"
#include "mozilla/dom/Document.h"
#include "nsNameSpaceManager.h"
#include "nsScrollbarButtonFrame.h"
#include "nsIDOMEventListener.h"
#include "nsICSSDeclaration.h"
#include "nsFrameList.h"
#include "nsHTMLParts.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/CSSOrderAwareFrameIterator.h"
#include "nsContainerFrame.h"
#include "nsLayoutUtils.h"
#include "nsDisplayList.h"
#include "nsContentUtils.h"
#include "nsFlexContainerFrame.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/MouseEvent.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/PresShell.h"
#include "mozilla/UniquePtr.h"
#include "nsStyledElement.h"

using namespace mozilla;

using mozilla::dom::Element;
using mozilla::dom::Event;

class nsSplitterInfo {
 public:
  nscoord min;
  nscoord max;
  nscoord current;
  nscoord pref;
  nscoord changed;
  nsCOMPtr<nsIContent> childElem;
};

enum class ResizeType {
  // Resize the closest sibling in a given direction.
  Closest,
  // Resize the farthest sibling in a given direction.
  Farthest,
  // Resize only flexible siblings in a given direction.
  Flex,
  // No space should be taken out of any children in that direction.
  // FIXME(emilio): This is a rather odd name...
  Grow,
  // Only resize adjacent siblings.
  Sibling,
  // Don't resize anything in a given direction.
  None,
};
static ResizeType ResizeTypeFromAttribute(const Element& aElement,
                                          nsAtom* aAttribute) {
  static Element::AttrValuesArray strings[] = {
      nsGkAtoms::farthest, nsGkAtoms::flex, nsGkAtoms::grow,
      nsGkAtoms::sibling,  nsGkAtoms::none, nullptr};
  switch (aElement.FindAttrValueIn(kNameSpaceID_None, aAttribute, strings,
                                   eCaseMatters)) {
    case 0:
      return ResizeType::Farthest;
    case 1:
      return ResizeType::Flex;
    case 2:
      // Grow only applies to resizeAfter.
      if (aAttribute == nsGkAtoms::resizeafter) {
        return ResizeType::Grow;
      }
      break;
    case 3:
      return ResizeType::Sibling;
    case 4:
      return ResizeType::None;
    default:
      break;
  }
  return ResizeType::Closest;
}

class nsSplitterFrameInner final : public nsIDOMEventListener {
 protected:
  virtual ~nsSplitterFrameInner();

 public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIDOMEVENTLISTENER

  explicit nsSplitterFrameInner(nsSplitterFrame* aSplitter)
      : mOuter(aSplitter) {}

  void Disconnect() { mOuter = nullptr; }

  nsresult MouseDown(Event* aMouseEvent);
  nsresult MouseUp(Event* aMouseEvent);
  nsresult MouseMove(Event* aMouseEvent);

  void MouseDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);
  void MouseUp(nsPresContext* aPresContext, WidgetGUIEvent* aEvent);

  void AdjustChildren(nsPresContext* aPresContext);
  void AdjustChildren(nsPresContext* aPresContext,
                      nsTArray<nsSplitterInfo>& aChildInfos,
                      bool aIsHorizontal);

  void AddRemoveSpace(nscoord aDiff, nsTArray<nsSplitterInfo>& aChildInfos,
                      int32_t& aSpaceLeft);

  void ResizeChildTo(nscoord& aDiff);

  void UpdateState();

  void AddListener();
  void RemoveListener();

  enum class State { Open, CollapsedBefore, CollapsedAfter, Dragging };
  enum CollapseDirection { Before, After };

  ResizeType GetResizeBefore();
  ResizeType GetResizeAfter();
  State GetState();

  bool SupportsCollapseDirection(CollapseDirection aDirection);

  void EnsureOrient();
  void SetPreferredSize(nsIFrame* aChildBox, bool aIsHorizontal, nscoord aSize);

  nsSplitterFrame* mOuter;
  bool mDidDrag = false;
  nscoord mDragStart = 0;
  nsIFrame* mParentBox = nullptr;
  bool mPressed = false;
  nsTArray<nsSplitterInfo> mChildInfosBefore;
  nsTArray<nsSplitterInfo> mChildInfosAfter;
  State mState = State::Open;
  nscoord mSplitterPos = 0;
  bool mDragging = false;

  const Element* SplitterElement() const {
    return mOuter->GetContent()->AsElement();
  }
};

NS_IMPL_ISUPPORTS(nsSplitterFrameInner, nsIDOMEventListener)

ResizeType nsSplitterFrameInner::GetResizeBefore() {
  return ResizeTypeFromAttribute(*SplitterElement(), nsGkAtoms::resizebefore);
}

ResizeType nsSplitterFrameInner::GetResizeAfter() {
  return ResizeTypeFromAttribute(*SplitterElement(), nsGkAtoms::resizeafter);
}

nsSplitterFrameInner::~nsSplitterFrameInner() = default;

nsSplitterFrameInner::State nsSplitterFrameInner::GetState() {
  static Element::AttrValuesArray strings[] = {nsGkAtoms::dragging,
                                               nsGkAtoms::collapsed, nullptr};
  static Element::AttrValuesArray strings_substate[] = {
      nsGkAtoms::before, nsGkAtoms::after, nullptr};
  switch (SplitterElement()->FindAttrValueIn(
      kNameSpaceID_None, nsGkAtoms::state, strings, eCaseMatters)) {
    case 0:
      return State::Dragging;
    case 1:
      switch (SplitterElement()->FindAttrValueIn(
          kNameSpaceID_None, nsGkAtoms::substate, strings_substate,
          eCaseMatters)) {
        case 0:
          return State::CollapsedBefore;
        case 1:
          return State::CollapsedAfter;
        default:
          if (SupportsCollapseDirection(After)) {
            return State::CollapsedAfter;
          }
          return State::CollapsedBefore;
      }
  }
  return State::Open;
}

//
// NS_NewSplitterFrame
//
// Creates a new Toolbar frame and returns it
//
nsIFrame* NS_NewSplitterFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
  return new (aPresShell) nsSplitterFrame(aStyle, aPresShell->GetPresContext());
}

NS_IMPL_FRAMEARENA_HELPERS(nsSplitterFrame)

nsSplitterFrame::nsSplitterFrame(ComputedStyle* aStyle,
                                 nsPresContext* aPresContext)
    : SimpleXULLeafFrame(aStyle, aPresContext, kClassID) {}

void nsSplitterFrame::Destroy(DestroyContext& aContext) {
  if (mInner) {
    mInner->RemoveListener();
    mInner->Disconnect();
    mInner = nullptr;
  }
  SimpleXULLeafFrame::Destroy(aContext);
}

nsresult nsSplitterFrame::AttributeChanged(int32_t aNameSpaceID,
                                           nsAtom* aAttribute,
                                           int32_t aModType) {
  nsresult rv =
      SimpleXULLeafFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
  if (aAttribute == nsGkAtoms::state) {
    mInner->UpdateState();
  }

  return rv;
}

/**
 * Initialize us. If we are in a box get our alignment so we know what direction
 * we are
 */

void nsSplitterFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
                           nsIFrame* aPrevInFlow) {
  MOZ_ASSERT(!mInner);
  mInner = new nsSplitterFrameInner(this);

  SimpleXULLeafFrame::Init(aContent, aParent, aPrevInFlow);

  mInner->AddListener();
  mInner->mParentBox = nullptr;
}

static bool IsValidParentBox(nsIFrame* aFrame) {
  return aFrame->IsFlexContainerFrame();
}

static nsIFrame* GetValidParentBox(nsIFrame* aChild) {
  return aChild->GetParent() && IsValidParentBox(aChild->GetParent())
             ? aChild->GetParent()
             : nullptr;
}

void nsSplitterFrame::Reflow(nsPresContext* aPresContext,
                             ReflowOutput& aDesiredSize,
                             const ReflowInput& aReflowInput,
                             nsReflowStatus& aStatus) {
  if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
    mInner->mParentBox = GetValidParentBox(this);
    mInner->UpdateState();
  }
  return SimpleXULLeafFrame::Reflow(aPresContext, aDesiredSize, aReflowInput,
                                    aStatus);
}

static bool SplitterIsHorizontal(const nsIFrame* aParentBox) {
  // If our parent is horizontal, the splitter is vertical and vice-versa.
  MOZ_ASSERT(aParentBox->IsFlexContainerFrame());
  const FlexboxAxisInfo info(aParentBox);
  return !info.mIsRowOriented;
}

NS_IMETHODIMP
nsSplitterFrame::HandlePress(nsPresContext* aPresContext,
                             WidgetGUIEvent* aEvent,
                             nsEventStatus* aEventStatus) {
  return NS_OK;
}

NS_IMETHODIMP
nsSplitterFrame::HandleMultiplePress(nsPresContext* aPresContext,
                                     WidgetGUIEvent* aEvent,
                                     nsEventStatus* aEventStatus,
                                     bool aControlHeld) {
  return NS_OK;
}

NS_IMETHODIMP
nsSplitterFrame::HandleDrag(nsPresContext* aPresContext, WidgetGUIEvent* aEvent,
                            nsEventStatus* aEventStatus) {
  return NS_OK;
}

NS_IMETHODIMP
nsSplitterFrame::HandleRelease(nsPresContext* aPresContext,
                               WidgetGUIEvent* aEvent,
                               nsEventStatus* aEventStatus) {
  return NS_OK;
}

void nsSplitterFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                       const nsDisplayListSet& aLists) {
  SimpleXULLeafFrame::BuildDisplayList(aBuilder, aLists);

  // if the mouse is captured always return us as the frame.
  if (mInner->mDragging && aBuilder->IsForEventDelivery()) {
    // XXX It's probably better not to check visibility here, right?
    aLists.Outlines()->AppendNewToTop<nsDisplayEventReceiver>(aBuilder, this);
    return;
  }
}

nsresult nsSplitterFrame::HandleEvent(nsPresContext* aPresContext,
                                      WidgetGUIEvent* aEvent,
                                      nsEventStatus* aEventStatus) {
  NS_ENSURE_ARG_POINTER(aEventStatus);
  if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
    return NS_OK;
  }

  AutoWeakFrame weakFrame(this);
  RefPtr<nsSplitterFrameInner> inner(mInner);
  switch (aEvent->mMessage) {
    case eMouseMove:
      inner->MouseDrag(aPresContext, aEvent);
      break;

    case eMouseUp:
      if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) {
        inner->MouseUp(aPresContext, aEvent);
      }
      break;

    default:
      break;
  }

  NS_ENSURE_STATE(weakFrame.IsAlive());
  return SimpleXULLeafFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
}

void nsSplitterFrameInner::MouseUp(nsPresContext* aPresContext,
                                   WidgetGUIEvent* aEvent) {
  if (mDragging && mOuter) {
    AdjustChildren(aPresContext);
    AddListener();
    PresShell::ReleaseCapturingContent();  // XXXndeakin is this needed?
    mDragging = false;
    State newState = GetState();
    // if the state is dragging then make it Open.
    if (newState == State::Dragging) {
      mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None,
                                             nsGkAtoms::state, u""_ns, true);
    }

    mPressed = false;

    // if we dragged then fire a command event.
    if (mDidDrag) {
      RefPtr<nsXULElement> element =
          nsXULElement::FromNode(mOuter->GetContent());
      element->DoCommand();
    }

    // printf("MouseUp\n");
  }

  mChildInfosBefore.Clear();
  mChildInfosAfter.Clear();
}

void nsSplitterFrameInner::MouseDrag(nsPresContext* aPresContext,
                                     WidgetGUIEvent* aEvent) {
  if (!mDragging || !mOuter) {
    return;
  }

  const bool isHorizontal = !mOuter->IsHorizontal();
  nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(
      aEvent, RelativeTo{mParentBox});
  nscoord pos = isHorizontal ? pt.x : pt.y;

  // take our current position and subtract the start location,
  // mDragStart is in parent-box relative coordinates already.
  pos -= mDragStart;

  for (auto& info : mChildInfosBefore) {
    info.changed = info.current;
  }

  for (auto& info : mChildInfosAfter) {
    info.changed = info.current;
  }
  nscoord oldPos = pos;

  ResizeChildTo(pos);

  State currentState = GetState();
  bool supportsBefore = SupportsCollapseDirection(Before);
  bool supportsAfter = SupportsCollapseDirection(After);

  const bool isRTL =
      mOuter->StyleVisibility()->mDirection == StyleDirection::Rtl;
  bool pastEnd = oldPos > 0 && oldPos > pos;
  bool pastBegin = oldPos < 0 && oldPos < pos;
  if (isRTL) {
    // Swap the boundary checks in RTL mode
    std::swap(pastEnd, pastBegin);
  }
  const bool isCollapsedBefore = pastBegin && supportsBefore;
  const bool isCollapsedAfter = pastEnd && supportsAfter;

  // if we are in a collapsed position
  if (isCollapsedBefore || isCollapsedAfter) {
    // and we are not collapsed then collapse
    if (currentState == State::Dragging) {
      if (pastEnd) {
        // printf("Collapse right\n");
        if (supportsAfter) {
          RefPtr<Element> outer = mOuter->mContent->AsElement();
          outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"after"_ns,
                         true);
          outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns,
                         true);
        }

      } else if (pastBegin) {
        // printf("Collapse left\n");
        if (supportsBefore) {
          RefPtr<Element> outer = mOuter->mContent->AsElement();
          outer->SetAttr(kNameSpaceID_None, nsGkAtoms::substate, u"before"_ns,
                         true);
          outer->SetAttr(kNameSpaceID_None, nsGkAtoms::state, u"collapsed"_ns,
                         true);
        }
      }
    }
  } else {
    // if we are not in a collapsed position and we are not dragging make sure
    // we are dragging.
    if (currentState != State::Dragging) {
      mOuter->mContent->AsElement()->SetAttr(
          kNameSpaceID_None, nsGkAtoms::state, u"dragging"_ns, true);
    }
    AdjustChildren(aPresContext);
  }

  mDidDrag = true;
}

void nsSplitterFrameInner::AddListener() {
  mOuter->GetContent()->AddEventListener(u"mouseup"_ns, thisfalsefalse);
  mOuter->GetContent()->AddEventListener(u"mousedown"_ns, thisfalsefalse);
  mOuter->GetContent()->AddEventListener(u"mousemove"_ns, thisfalsefalse);
  mOuter->GetContent()->AddEventListener(u"mouseout"_ns, thisfalsefalse);
}

void nsSplitterFrameInner::RemoveListener() {
  NS_ENSURE_TRUE_VOID(mOuter);
  mOuter->GetContent()->RemoveEventListener(u"mouseup"_ns, thisfalse);
  mOuter->GetContent()->RemoveEventListener(u"mousedown"_ns, thisfalse);
  mOuter->GetContent()->RemoveEventListener(u"mousemove"_ns, thisfalse);
  mOuter->GetContent()->RemoveEventListener(u"mouseout"_ns, thisfalse);
}

nsresult nsSplitterFrameInner::HandleEvent(dom::Event* aEvent) {
  nsAutoString eventType;
  aEvent->GetType(eventType);
  if (eventType.EqualsLiteral("mouseup")) {
    return MouseUp(aEvent);
  }
  if (eventType.EqualsLiteral("mousedown")) {
    return MouseDown(aEvent);
  }
  if (eventType.EqualsLiteral("mousemove") ||
      eventType.EqualsLiteral("mouseout")) {
    return MouseMove(aEvent);
  }

  MOZ_ASSERT_UNREACHABLE("Unexpected eventType");
  return NS_OK;
}

nsresult nsSplitterFrameInner::MouseUp(Event* aMouseEvent) {
  NS_ENSURE_TRUE(mOuter, NS_OK);
  mPressed = false;

  PresShell::ReleaseCapturingContent();

  return NS_OK;
}

template <typename LengthLike>
static nscoord ToLengthWithFallback(const LengthLike& aLengthLike,
                                    nscoord aFallback) {
  if (aLengthLike.ConvertsToLength()) {
    return aLengthLike.ToLength();
  }
  return aFallback;
}

template <typename LengthLike>
static nsSize ToLengthWithFallback(const LengthLike& aWidth,
                                   const LengthLike& aHeight,
                                   nscoord aFallback = 0) {
  return {ToLengthWithFallback(aWidth, aFallback),
          ToLengthWithFallback(aHeight, aFallback)};
}

static void ApplyMargin(nsSize& aSize, const nsMargin& aMargin) {
  if (aSize.width != NS_UNCONSTRAINEDSIZE) {
    aSize.width += aMargin.LeftRight();
  }
  if (aSize.height != NS_UNCONSTRAINEDSIZE) {
    aSize.height += aMargin.TopBottom();
  }
}

nsresult nsSplitterFrameInner::MouseDown(Event* aMouseEvent) {
  NS_ENSURE_TRUE(mOuter, NS_OK);
  dom::MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
  if (!mouseEvent) {
    return NS_OK;
  }

  // only if left button
  if (mouseEvent->Button() != 0) {
    return NS_OK;
  }

  if (SplitterElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
                                     nsGkAtoms::_true, eCaseMatters)) {
    return NS_OK;
  }

  mParentBox = GetValidParentBox(mOuter);
  if (!mParentBox) {
    return NS_OK;
  }

  // get our index
  mDidDrag = false;

  EnsureOrient();
  const bool isHorizontal = !mOuter->IsHorizontal();

  const nsIContent* outerContent = mOuter->GetContent();

  const ResizeType resizeBefore = GetResizeBefore();
  const ResizeType resizeAfter = GetResizeAfter();
  const int32_t childCount = mParentBox->PrincipalChildList().GetLength();

  mChildInfosBefore.Clear();
  mChildInfosAfter.Clear();
  int32_t count = 0;

  bool foundOuter = false;
  CSSOrderAwareFrameIterator iter(
      mParentBox, FrameChildListID::Principal,
      CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
      CSSOrderAwareFrameIterator::OrderState::Unknown,
      CSSOrderAwareFrameIterator::OrderingProperty::Order);
  for (; !iter.AtEnd(); iter.Next()) {
    nsIFrame* childBox = iter.get();
    if (childBox == mOuter) {
      foundOuter = true;
      if (!count) {
        // We're at the beginning, nothing to do.
        return NS_OK;
      }
      if (count == childCount - 1 && resizeAfter != ResizeType::Grow) {
        // If it's the last index then we need to allow for resizeafter="grow"
        return NS_OK;
      }
    }
    count++;

    nsIContent* content = childBox->GetContent();
    // XXX flex seems untested, as it uses mBoxFlex rather than actual flexbox
    // flex.
    const nscoord flex = childBox->StyleXUL()->mBoxFlex;
    const bool isBefore = !foundOuter;
    const bool isResizable = [&] {
      if (auto* element = nsXULElement::FromNode(content)) {
        if (element->NodeInfo()->NameAtom() == nsGkAtoms::splitter) {
          // skip over any splitters
          return false;
        }

        // We need to check for hidden attribute too, since treecols with
        // the hidden="true" attribute are not really hidden, just collapsed
        if (element->GetXULBoolAttr(nsGkAtoms::fixed) ||
            element->GetXULBoolAttr(nsGkAtoms::hidden)) {
          return false;
        }
      }

      // We need to check this here rather than in the switch before because we
      // want `sibling` to work in the DOM order, not frame tree order.
      if (resizeBefore == ResizeType::Sibling &&
          content->GetNextElementSibling() == outerContent) {
        return true;
      }
      if (resizeAfter == ResizeType::Sibling &&
          content->GetPreviousElementSibling() == outerContent) {
        return true;
      }

      const ResizeType resizeType = isBefore ? resizeBefore : resizeAfter;
      switch (resizeType) {
        case ResizeType::Grow:
        case ResizeType::None:
        case ResizeType::Sibling:
          return false;
        case ResizeType::Flex:
          return flex > 0;
        case ResizeType::Closest:
        case ResizeType::Farthest:
          break;
      }
      return true;
    }();

    if (!isResizable) {
      continue;
    }

    nsSize curSize = childBox->GetSize();
    const auto& pos = *childBox->StylePosition();
    nsSize minSize =
        ToLengthWithFallback(pos.GetMinWidth(), pos.GetMinHeight());
    nsSize maxSize = ToLengthWithFallback(pos.GetMaxWidth(), pos.GetMaxHeight(),
                                          NS_UNCONSTRAINEDSIZE);
    nsSize prefSize(ToLengthWithFallback(pos.GetWidth(), curSize.width),
                    ToLengthWithFallback(pos.GetHeight(), curSize.height));

    maxSize.width = std::max(maxSize.width, minSize.width);
    maxSize.height = std::max(maxSize.height, minSize.height);
    prefSize.width = CSSMinMax(prefSize.width, minSize.width, maxSize.width);
    prefSize.height =
        CSSMinMax(prefSize.height, minSize.height, maxSize.height);

    nsMargin m;
    childBox->StyleMargin()->GetMargin(m);

    ApplyMargin(curSize, m);
    ApplyMargin(minSize, m);
    ApplyMargin(maxSize, m);
    ApplyMargin(prefSize, m);

    auto& list = isBefore ? mChildInfosBefore : mChildInfosAfter;
    nsSplitterInfo& info = *list.AppendElement();
    info.childElem = content;
    info.min = isHorizontal ? minSize.width : minSize.height;
    info.max = isHorizontal ? maxSize.width : maxSize.height;
    info.pref = isHorizontal ? prefSize.width : prefSize.height;
    info.current = info.changed = isHorizontal ? curSize.width : curSize.height;
  }

  if (!foundOuter) {
    return NS_OK;
  }

  mPressed = true;

  const bool reverseDirection = [&] {
    MOZ_ASSERT(mParentBox->IsFlexContainerFrame());
    const FlexboxAxisInfo info(mParentBox);
    if (!info.mIsRowOriented) {
      return info.mIsMainAxisReversed;
    }
    const bool rtl =
        mParentBox->StyleVisibility()->mDirection == StyleDirection::Rtl;
    return info.mIsMainAxisReversed != rtl;
  }();

  if (reverseDirection) {
    // The before array is really the after array, and the order needs to be
    // reversed. First reverse both arrays.
    mChildInfosBefore.Reverse();
    mChildInfosAfter.Reverse();

    // Now swap the two arrays.
    std::swap(mChildInfosBefore, mChildInfosAfter);
  }

  // if resizebefore is not Farthest, reverse the list because the first child
  // in the list is the farthest, and we want the first child to be the closest.
  if (resizeBefore != ResizeType::Farthest) {
    mChildInfosBefore.Reverse();
  }

  // if the resizeafter is the Farthest we must reverse the list because the
  // first child in the list is the closest we want the first child to be the
  // Farthest.
  if (resizeAfter == ResizeType::Farthest) {
    mChildInfosAfter.Reverse();
  }

  int32_t c;
  nsPoint pt =
      nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(mouseEvent, mParentBox);
  if (isHorizontal) {
    c = pt.x;
    mSplitterPos = mOuter->mRect.x;
  } else {
    c = pt.y;
    mSplitterPos = mOuter->mRect.y;
  }

  mDragStart = c;

  // printf("Pressed mDragStart=%d\n",mDragStart);

  PresShell::SetCapturingContent(mOuter->GetContent(),
                                 CaptureFlags::IgnoreAllowedState);

  return NS_OK;
}

nsresult nsSplitterFrameInner::MouseMove(Event* aMouseEvent) {
  NS_ENSURE_TRUE(mOuter, NS_OK);
  if (!mPressed) {
    return NS_OK;
  }

  if (mDragging) {
    return NS_OK;
  }

  nsCOMPtr<nsIDOMEventListener> kungfuDeathGrip(this);
  mOuter->mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::state,
                                         u"dragging"_ns, true);

  RemoveListener();
  mDragging = true;

  return NS_OK;
}

bool nsSplitterFrameInner::SupportsCollapseDirection(
    nsSplitterFrameInner::CollapseDirection aDirection) {
  static Element::AttrValuesArray strings[] = {
      nsGkAtoms::before, nsGkAtoms::after, nsGkAtoms::both, nullptr};

  switch (SplitterElement()->FindAttrValueIn(
      kNameSpaceID_None, nsGkAtoms::collapse, strings, eCaseMatters)) {
    case 0:
      return (aDirection == Before);
    case 1:
      return (aDirection == After);
    case 2:
      return true;
  }

  return false;
}

static nsIFrame* SlowOrderAwareSibling(nsIFrame* aBox, bool aNext) {
  nsIFrame* parent = aBox->GetParent();
  if (!parent) {
    return nullptr;
  }
  CSSOrderAwareFrameIterator iter(
      parent, FrameChildListID::Principal,
      CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
      CSSOrderAwareFrameIterator::OrderState::Unknown,
      CSSOrderAwareFrameIterator::OrderingProperty::Order);

  nsIFrame* prevSibling = nullptr;
  for (; !iter.AtEnd(); iter.Next()) {
    nsIFrame* current = iter.get();
    if (!aNext && current == aBox) {
      return prevSibling;
    }
    if (aNext && prevSibling == aBox) {
      return current;
    }
    prevSibling = current;
  }
  return nullptr;
}

void nsSplitterFrameInner::UpdateState() {
  // State Transitions:
  //   Open            -> Dragging
  //   Open            -> CollapsedBefore
  //   Open            -> CollapsedAfter
  //   CollapsedBefore -> Open
  //   CollapsedBefore -> Dragging
  //   CollapsedAfter  -> Open
  //   CollapsedAfter  -> Dragging
  //   Dragging        -> Open
  //   Dragging        -> CollapsedBefore (auto collapse)
  //   Dragging        -> CollapsedAfter (auto collapse)

  State newState = GetState();

  if (newState == mState) {
    // No change.
    return;
  }

  if ((SupportsCollapseDirection(Before) || SupportsCollapseDirection(After)) &&
      IsValidParentBox(mOuter->GetParent())) {
    // Find the splitter's immediate sibling.
    const bool prev =
        newState == State::CollapsedBefore || mState == State::CollapsedBefore;
    nsIFrame* splitterSibling = SlowOrderAwareSibling(mOuter, !prev);
    if (splitterSibling) {
      nsCOMPtr<nsIContent> sibling = splitterSibling->GetContent();
      if (sibling && sibling->IsElement()) {
        if (mState == State::CollapsedBefore ||
            mState == State::CollapsedAfter) {
          // CollapsedBefore -> Open
          // CollapsedBefore -> Dragging
          // CollapsedAfter -> Open
          // CollapsedAfter -> Dragging
          nsContentUtils::AddScriptRunner(new nsUnsetAttrRunnable(
              sibling->AsElement(), nsGkAtoms::collapsed));
        } else if ((mState == State::Open || mState == State::Dragging) &&
                   (newState == State::CollapsedBefore ||
                    newState == State::CollapsedAfter)) {
          // Open -> CollapsedBefore / CollapsedAfter
          // Dragging -> CollapsedBefore / CollapsedAfter
          nsContentUtils::AddScriptRunner(new nsSetAttrRunnable(
              sibling->AsElement(), nsGkAtoms::collapsed, u"true"_ns));
        }
      }
    }
  }
  mState = newState;
}

void nsSplitterFrameInner::EnsureOrient() {
  mOuter->mIsHorizontal = SplitterIsHorizontal(mParentBox);
}

void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext) {
  EnsureOrient();
  const bool isHorizontal = !mOuter->IsHorizontal();

  AdjustChildren(aPresContext, mChildInfosBefore, isHorizontal);
  AdjustChildren(aPresContext, mChildInfosAfter, isHorizontal);
}

static nsIFrame* GetChildBoxForContent(nsIFrame* aParentBox,
                                       nsIContent* aContent) {
  // XXX Can this use GetPrimaryFrame?
  for (nsIFrame* f : aParentBox->PrincipalChildList()) {
    if (f->GetContent() == aContent) {
      return f;
    }
  }
  return nullptr;
}

void nsSplitterFrameInner::AdjustChildren(nsPresContext* aPresContext,
                                          nsTArray<nsSplitterInfo>& aChildInfos,
                                          bool aIsHorizontal) {
  /// printf("------- AdjustChildren------\n");

  for (auto& info : aChildInfos) {
    nscoord newPref = info.pref + (info.changed - info.current);
    if (nsIFrame* childBox =
            GetChildBoxForContent(mParentBox, info.childElem)) {
      SetPreferredSize(childBox, aIsHorizontal, newPref);
    }
  }
}

void nsSplitterFrameInner::SetPreferredSize(nsIFrame* aChildBox,
                                            bool aIsHorizontal, nscoord aSize) {
  nsMargin margin;
  aChildBox->StyleMargin()->GetMargin(margin);
  if (aIsHorizontal) {
    aSize -= (margin.left + margin.right);
  } else {
    aSize -= (margin.top + margin.bottom);
  }

  RefPtr element = nsStyledElement::FromNode(aChildBox->GetContent());
  if (!element) {
    return;
  }

  // We set both the attribute and the CSS value, so that XUL persist="" keeps
  // working, see bug 1790712.

  int32_t pixels = aSize / AppUnitsPerCSSPixel();
  nsAutoString attrValue;
  attrValue.AppendInt(pixels);
  element->SetAttr(aIsHorizontal ? nsGkAtoms::width : nsGkAtoms::height,
                   attrValue, IgnoreErrors());

  nsCOMPtr<nsICSSDeclaration> decl = element->Style();

  nsAutoCString cssValue;
  cssValue.AppendInt(pixels);
  cssValue.AppendLiteral("px");
  decl->SetProperty(aIsHorizontal ? "width"_ns : "height"_ns, cssValue, ""_ns,
                    IgnoreErrors());
}

void nsSplitterFrameInner::AddRemoveSpace(nscoord aDiff,
                                          nsTArray<nsSplitterInfo>& aChildInfos,
                                          int32_t& aSpaceLeft) {
  aSpaceLeft = 0;

  for (auto& info : aChildInfos) {
    nscoord min = info.min;
    nscoord max = info.max;
    nscoord& c = info.changed;

    // figure our how much space to add or remove
    if (c + aDiff < min) {
      aDiff += (c - min);
      c = min;
    } else if (c + aDiff > max) {
      aDiff -= (max - c);
      c = max;
    } else {
      c += aDiff;
      aDiff = 0;
    }

    // there is not space left? We are done
    if (aDiff == 0) {
      break;
    }
  }

  aSpaceLeft = aDiff;
}

/**
 * Ok if we want to resize a child we will know the actual size in pixels we
 * want it to be. This is not the preferred size. But the only way we can change
 * a child is by manipulating its preferred size. So give the actual pixel size
 * this method will figure out the preferred size and set it.
 */


void nsSplitterFrameInner::ResizeChildTo(nscoord& aDiff) {
  nscoord spaceLeft = 0;

  if (!mChildInfosBefore.IsEmpty()) {
    AddRemoveSpace(aDiff, mChildInfosBefore, spaceLeft);
    // If there is any space left over remove it from the diff we were
    // originally given.
    aDiff -= spaceLeft;
  }

  AddRemoveSpace(-aDiff, mChildInfosAfter, spaceLeft);

  if (spaceLeft != 0 && !mChildInfosAfter.IsEmpty()) {
    aDiff += spaceLeft;
    AddRemoveSpace(spaceLeft, mChildInfosBefore, spaceLeft);
  }
}

97%


¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.