WIP: Anchor.cpp now builds

This commit is contained in:
Sergio Martins
2020-04-01 15:19:22 +01:00
parent 1643f23612
commit 76eb54e86f
5 changed files with 71 additions and 1371 deletions

View File

@@ -538,276 +538,13 @@ int MultiSplitterLayout::placeholderCount() const
return count() - visibleCount();
}
void MultiSplitterLayout::removeAnchor(Anchor *anchor)
{
if (!m_inDestructor)
m_anchors.removeOne(anchor);
}
QPair<int, int> MultiSplitterLayout::boundPositionsForAnchor(Anchor *anchor) const
{
if (anchor->isStatic()) {
if (anchor == m_leftAnchor || anchor == m_topAnchor) {
return {0, 0};
} else if (anchor == m_rightAnchor || anchor == m_bottomAnchor) {
const int max = length(anchor->orientation()) - Anchor::thickness(true);
return {max, max};
}
}
if (anchor->isFollowing())
anchor = anchor->endFollowee();
const int minSide1Length = anchor->cumulativeMinLength(Anchor::Side1);
const int minSide2Length = anchor->cumulativeMinLength(Anchor::Side2);
const int length = anchor->isVertical() ? width() : height();
const int bound1 = qMax(0, minSide1Length - anchor->thickness());
const int bound2 = qMax(0, length - minSide2Length);
if (bound2 < bound1) {
qWarning() << Q_FUNC_INFO << "Invalid bounds"
<< "; bound1=" << bound1
<< "; bound2=" << bound2
<< "; layout.size=" << size()
<< "; layout.min=" << minimumSize()
<< "; anchor=" << anchor
<< "; orientation=" << anchor->orientation()
<< "; minSide1Length=" << minSide1Length
<< "; minSide2Length=" << minSide2Length
<< "; side1=" << anchor->side1Items()
<< "; side2=" << anchor->side2Items()
<< "; followee=" << anchor->followee()
<< "; thickness=" << anchor->thickness();
}
return { bound1, bound2 };
}
QHash<Anchor *, QPair<int, int> > MultiSplitterLayout::boundPositionsForAllAnchors() const
{
QHash<Anchor *, QPair<int, int> > result;
for (Anchor *anchor : m_anchors)
result.insert(anchor, boundPositionsForAnchor(anchor));
return result;
}
int MultiSplitterLayout::boundPositionForAnchor(Anchor *anchor, Anchor::Side direction) const
{
auto bounds = boundPositionsForAnchor(anchor);
return direction == Anchor::Side1 ? bounds.first
: bounds.second;
}
MultiSplitterLayout::Length MultiSplitterLayout::availableLengthForDrop(Location location, const Item *relativeTo) const
{
Length result;
const bool relativeToThis = relativeTo == nullptr;
AnchorGroup anchors = relativeToThis ? staticAnchorGroup()
: relativeTo->anchorGroup();
Anchor *anchor = nullptr;
int thisLength = 0;
switch (location) {
case KDDockWidgets::Location_None:
qWarning() << "MultiSplitterLayout::availableLengthForDrop invalid location for dropping";
return result;
case KDDockWidgets::Location_OnLeft:
anchor = anchors.left;
thisLength = width();
break;
case KDDockWidgets::Location_OnTop:
anchor = anchors.top;
thisLength = height();
break;
case KDDockWidgets::Location_OnRight:
anchor = anchors.right;
thisLength = width();
break;
case KDDockWidgets::Location_OnBottom:
anchor = anchors.bottom;
thisLength = height();
break;
}
anchor = anchor->isFollowing() ? anchor->endFollowee() : anchor;
const int minForAlreadyOccupied1 = anchor->cumulativeMinLength(Anchor::Side1) - anchor->thickness(); // TODO: Check if this is correct, we're discounting the anchor twice
const int minForAlreadyOccupied2 = anchor->cumulativeMinLength(Anchor::Side2) - anchor->thickness();
const int side1AvailableLength = anchor->position() - minForAlreadyOccupied1;
const int side2AvailableLength = thisLength - (anchor->position() + anchor->thickness()) - minForAlreadyOccupied2;
const bool needsNewAnchor = hasVisibleItems(); // If a new anchor is needed then we need space for the drag handle and such.
const int newAnchorThickness = needsNewAnchor ? Anchor::thickness(/*static=*/false) : 0;
// This useless space doesn't belong to side1 or side2 specifically. So account for it separately.
const int unusableSpace = newAnchorThickness;
const int usableLength = qMax(0, side1AvailableLength + side2AvailableLength - unusableSpace);
if (usableLength > 0) {
qreal factor = (side1AvailableLength * 1.0) / (side1AvailableLength + side2AvailableLength);
result.side1Length = int(qRound(usableLength * factor)); // rounding not really needed, but makes things more fair probably
result.side2Length = usableLength - result.side1Length;
}
qCDebug(sizing) << Q_FUNC_INFO
<< "; available=" << result.length() << result.side1Length << result.side2Length
<< "; side1AvailableLength=" << side1AvailableLength
<< "; side2AvailableLength=" << side2AvailableLength
<< "; minForAlreadyOccupied1=" << minForAlreadyOccupied1
<< "; minForAlreadyOccupied2=" << minForAlreadyOccupied2
<< "; thisLength=" << thisLength
<< "; anchorPos=" << anchor->position()
<< "; unusableSpace=" << unusableSpace;
return result;
}
int MultiSplitterLayout::availableLengthForOrientation(Qt::Orientation orientation) const
{
Length l = availableLengthForDrop(orientation == Qt::Vertical ? Location_OnLeft
: Location_OnTop, nullptr);
return l.length();
}
QSize MultiSplitterLayout::availableSize() const
{
return { availableLengthForOrientation(Qt::Vertical), availableLengthForOrientation(Qt::Horizontal) };
}
/*
* Returns the width or height the widget will get when dropped.
*/
MultiSplitterLayout::Length MultiSplitterLayout::lengthForDrop(const QWidgetOrQuick *widget, Location location,
const Item *relativeTo) const
{
Q_ASSERT(location != Location_None);
const Qt::Orientation anchorOrientation = anchorOrientationForLocation(location);
const int widgetCurrentLength = widgetLength(widget, anchorOrientation);
Length available = availableLengthForDrop(location, relativeTo);
const int requiredAtLeast = widgetMinLength(widget, anchorOrientation);
if (available.length() < requiredAtLeast) {
qCDebug(sizing) << Q_FUNC_INFO
<< "\n Not enough space. available=" << available.length()
<< "; required=" << requiredAtLeast
<< "; m_size=" << m_size;
return {};
}
const int suggestedLength = qMin(widgetCurrentLength, int(0.4 * length(anchorOrientation)));
available.setLength(qBound(requiredAtLeast, suggestedLength, available.length()));
qCDebug(sizing) << "MultiSplitterLayout::lengthForDrop length=" << available.length()
<< "; s1=" << available.side1Length << "; s2="<< available.side2Length
<< "; relativeTo=" << relativeTo
<< "; relativeTo.geo=" << (relativeTo ? relativeTo->geometry() : QRect())
<< "; widgetCurrentLength=" << widgetCurrentLength;
return available;
}
QRect MultiSplitterLayout::rectForDrop(MultiSplitterLayout::Length lfd, Location location, QRect relativeToRect) const
{
QRect result;
const int widgetLength = lfd.length();
const int newAnchorThickness = isEmpty() ? 0 : Anchor::thickness(/*static=*/false);
const int side1Length = lfd.side1Length;
const int staticAnchorThickness = Anchor::thickness(/**static=*/true);
switch (location) {
case Location_OnLeft:
result = QRect(qMax(0, relativeToRect.x() - side1Length), relativeToRect.y(),
widgetLength, relativeToRect.height());
break;
case Location_OnTop:
result = QRect(relativeToRect.x(), qMax(0, relativeToRect.y() - side1Length),
relativeToRect.width(), widgetLength);
break;
case Location_OnRight:
result = QRect(qMin(relativeToRect.right() + 1 - side1Length + newAnchorThickness,
width() - widgetLength - staticAnchorThickness), relativeToRect.y(), widgetLength, relativeToRect.height());
break;
case Location_OnBottom:
result = QRect(relativeToRect.x(), qMin(relativeToRect.bottom() + 1 - side1Length + newAnchorThickness,
height() - widgetLength - staticAnchorThickness),
relativeToRect.width(), widgetLength);
break;
default:
break;
}
qCDebug(sizing) << "MultiSplitterLayout::rectForDrop rect=" << result
<< "; result.bottomRight=" << result.bottomRight()
<< "; location=" << location
<< "; s1=" << side1Length
<< "; relativeToRect.bottomRight=" << relativeToRect.bottomRight();
return result;
}
QRect MultiSplitterLayout::rectForDrop(const QWidgetOrQuick *widgetBeingDropped, Location location,
const Item *relativeTo) const
{
Q_ASSERT(widgetBeingDropped);
Length lfd = lengthForDrop(widgetBeingDropped, location, relativeTo);
const bool needsMoreSpace = lfd.isNull();
if (needsMoreSpace) {
// This is the case with the drop indicators. If there's not enough space let's still
// draw some indicator drop. The window will resize to accommodate the drop.
lfd.side1Length = INDICATOR_MINIMUM_LENGTH / 2;
lfd.side2Length = INDICATOR_MINIMUM_LENGTH - lfd.side1Length;
}
const int staticAnchorThickness = Anchor::thickness(/**static=*/true);
const bool relativeToThis = relativeTo == nullptr;
const QRect relativeToRect = relativeToThis ? m_multiSplitter->rect().adjusted(staticAnchorThickness, staticAnchorThickness,
-staticAnchorThickness, -staticAnchorThickness)
: relativeTo->geometry();
// This function is split in two just so we can unit-test the math in the second one, which is more involved
QRect result = rectForDrop(lfd, location, relativeToRect);
return result;
}
void MultiSplitterLayout::setAnchorBeingDragged(Anchor *anchor)
{
m_anchorBeingDragged = anchor;
}
Anchor::List MultiSplitterLayout::anchorsFollowing(Anchor *followee) const
{
if (!followee)
return {};
Anchor::List followers;
for (Anchor *a : m_anchors) {
if (a->followee() == followee)
followers.push_back(a);
}
return followers;
}
int MultiSplitterLayout::numAchorsFollowing() const
{
int count = 0;
for (Anchor *a : m_anchors) {
if (a->isFollowing())
count++;
}
return count;
}
int MultiSplitterLayout::numVisibleAnchors() const
{
int count = 0;
@@ -819,193 +556,15 @@ int MultiSplitterLayout::numVisibleAnchors() const
return count;
}
Anchor *MultiSplitterLayout::staticAnchor(Anchor::Type type) const
{
if (type == Anchor::Type_TopStatic)
return m_topAnchor;
if (type == Anchor::Type_BottomStatic)
return m_bottomAnchor;
if (type == Anchor::Type_LeftStatic)
return m_leftAnchor;
if (type == Anchor::Type_RightStatic)
return m_rightAnchor;
return nullptr;
}
void MultiSplitterLayout::dumpDebug() const
{
Q_EMIT aboutToDumpDebug();
qDebug() << Q_FUNC_INFO << "m_size=" << m_size
<< "; minimumSize=" << minimumSize()
<< "; parentWidget.size=" << multiSplitter()->size()
<< "; window=" << multiSplitter()->window()
<< "; window.size=" << multiSplitter()->window()->size();
qDebug() << "Items:";
for (auto item : items()) {
qDebug() <<" " << item
<< "; min.width=" << item->minLength(Qt::Vertical)
<< "; min.height=" << item->minLength(Qt::Horizontal)
<< "; geometry=" << item->geometry()
<< "; isPlaceholder=" << item->isPlaceholder()
<< "; refCount=" << item->refCount();
if (Frame *frame = item->frame())
frame->dumpDebug();
}
qDebug() << "Anchors:";
for (Anchor *anchor : m_anchors) {
auto side1Widgets = anchor->items(Anchor::Side1);
auto side2Widgets = anchor->items(Anchor::Side2);
auto bounds = anchor->isStatic() ? QPair<int, int>() : boundPositionsForAnchor(anchor);
qDebug() << "\n " << anchor
<< "; side1=" << side1Widgets
<< "; side2=" << side2Widgets
<< "; pos=" << anchor->position()
<< "; sepWidget.pos=" << (anchor->isVertical() ? anchor->separatorWidget()->x()
: anchor->separatorWidget()->y())
<< "; sepWidget.visible=" << anchor->separatorWidget()->isVisible()
<< "; geo=" << anchor->geometry()
<< "; sep.geo=" << anchor->separatorWidget()->geometry()
<< "; bounds=" << bounds
<< "; orientation=" << anchor->orientation()
<< "; isFollowing=" << anchor->isFollowing()
<< "; followee=" << anchor->followee()
<< "; from=" << ((void*)anchor->from())
<< "; to=" << ((void*)anchor->to())
<< "; positionPercentage=" << anchor->positionPercentage();
}
qDebug() << "Num Frame:" << Frame::dbg_numFrames();
qDebug() << "Num FloatingWindow:" << FloatingWindow::dbg_numFrames();
}
void MultiSplitterLayout::positionStaticAnchors()
{
qCDebug(sizing) << Q_FUNC_INFO;
m_leftAnchor->setPosition(0);
m_topAnchor->setPosition(0);
m_bottomAnchor->setPosition(height() - m_bottomAnchor->thickness());
m_rightAnchor->setPosition(width() - m_rightAnchor->thickness());
}
void MultiSplitterLayout::redistributeSpace()
{
positionStaticAnchors();
redistributeSpace_recursive(m_leftAnchor, 0);
redistributeSpace_recursive(m_topAnchor, 0);
}
void MultiSplitterLayout::redistributeSpace(QSize oldSize, QSize newSize)
{
positionStaticAnchors();
if (oldSize == newSize || !oldSize.isValid() || !newSize.isValid())
return;
qCDebug(sizing) << Q_FUNC_INFO << "old=" << oldSize << "; new=" << newSize;
const bool widthChanged = oldSize.width() != newSize.width();
const bool heightChanged = oldSize.height() != newSize.height();
if (widthChanged)
redistributeSpace_recursive(m_leftAnchor, 0);
if (heightChanged)
redistributeSpace_recursive(m_topAnchor, 0);
}
void MultiSplitterLayout::redistributeSpace_recursive(Anchor *fromAnchor, int minAnchorPos)
{
for (Item *item : fromAnchor->items(Anchor::Side2)) {
Anchor *nextAnchor = item->anchorAtSide(Anchor::Side2, fromAnchor->orientation());
if (nextAnchor->isStatic())
continue;
// We use the minPos of the Anchor that had non-placeholder items on its side1.
if (nextAnchor->hasNonPlaceholderItems(Anchor::Side1))
minAnchorPos = nextAnchor->minPosition();
if (nextAnchor->hasNonPlaceholderItems(Anchor::Side2) && !nextAnchor->isFollowing()) {
const int newPosition = int(nextAnchor->positionPercentage() * length(nextAnchor->orientation()));
// But don't let the anchor go out of bounds, it must respect its widgets min sizes
auto bounds = boundPositionsForAnchor(nextAnchor);
// For the bounding, use Anchor::minPosition, as we're not making the anchors on the left/top shift, which boundsPositionsForAnchor() assumes.
const int newPositionBounded = qMax(bounds.first, qBound(minAnchorPos, newPosition, bounds.second));
qCDebug(sizing) << Q_FUNC_INFO << nextAnchor << "bounds.first=" << bounds.first
<< "; newPosition=" << newPosition
<< "; bounds.first=" << bounds.first
<< "; bounds.second=" << bounds.second
<< "; newPositionBounded=" << newPositionBounded
<< "; oldPosition=" << nextAnchor->position()
<< "; size=" << m_size
<< "; nextAnchor.minPosition=" << minAnchorPos;
nextAnchor->setPosition(newPositionBounded, Anchor::SetPositionOption_DontRecalculatePercentage);
}
redistributeSpace_recursive(nextAnchor, minAnchorPos);
}
}
void MultiSplitterLayout::updateSizeConstraints()
{
const int minH = m_topAnchor->cumulativeMinLength(Anchor::Side2);
const int minW = m_leftAnchor->cumulativeMinLength(Anchor::Side2);
const QSize newMinSize = QSize(minW, minH);
const QSize newMinSize = m_rootItem->minSize();
qCDebug(sizing) << Q_FUNC_INFO << "Updating size constraints from" << m_minSize
<< "to" << newMinSize;
setMinimumSize(newMinSize);
}
int MultiSplitterLayout::wastedSpacing(Qt::Orientation orientation) const
{
// Wasted spacing due to using splitters:
int numAnchors = 0;
for (Anchor *anchor : m_anchors) {
if (anchor->orientation() == orientation)
numAnchors++;
}
return (2 * Anchor::thickness(/*static=*/ true)) +
((numAnchors - 2) * Anchor::thickness(/*static=*/ false)); // 2 of the anchors are always static
}
AnchorGroup MultiSplitterLayout::staticAnchorGroup() const
{
return m_staticAnchorGroup;
}
Anchor::List MultiSplitterLayout::anchors(Qt::Orientation orientation, bool includeStatic,
bool includePlaceholders) const
{
Anchor::List result;
for (Anchor *anchor : m_anchors) {
if ((includeStatic || !anchor->isStatic()) && (includePlaceholders || !anchor->isFollowing()) && anchor->orientation() == orientation)
result << anchor;
}
return result;
}
void MultiSplitterLayout::blockItemPropagateGeo(bool block)
{
for (Item *item : m_items) {
if (block)
item->beginBlockPropagateGeo();
else
item->endBlockPropagateGeo();
}
}
void MultiSplitterLayout::emitVisibleWidgetCountChanged()
{
if (!m_inDestructor)
@@ -1040,7 +599,7 @@ Frame::List MultiSplitterLayout::frames() const
Frame::List result;
for (Item *item : m_items) {
if (Frame *f = item->frame())
if (auto f = static_cast<Frame*>(item->frame()))
result.push_back(f);
}
@@ -1063,22 +622,6 @@ bool MultiSplitterLayout::checkSanity() const
return m_rootItem->checkSanity();
}
void MultiSplitterLayout::ensureHasAvailableSize(QSize needed)
{
const QSize availableSize = this->availableSize();
qCDebug(placeholder) << Q_FUNC_INFO << "; needed=" << needed << availableSize;
const int deltaWidth = needed.width() > availableSize.width() ? (needed.width() - availableSize.width())
: 0;
const int deltaHeight = needed.height() > availableSize.height() ? (needed.height() - availableSize.height())
: 0;
const QSize newSize = size() + QSize(deltaWidth, deltaHeight);
setSize(newSize);
}
void MultiSplitterLayout::unrefOldPlaceholders(const Frame::List &framesBeingAdded) const
{
for (Frame *frame : framesBeingAdded) {
@@ -1151,107 +694,6 @@ void MultiSplitterLayout::setMinimumSize(QSize sz)
qCDebug(sizing) << Q_FUNC_INFO << "minSize = " << m_minSize;
}
void MultiSplitterLayout::clearAnchorsFollowing()
{
for (Anchor *anchor : qAsConst(m_anchors))
anchor->setFollowee(nullptr);
}
void MultiSplitterLayout::updateAnchorFollowing(const AnchorGroup &groupBeingRemoved)
{
clearAnchorsFollowing();
QHash<Anchor *, int> newPositionsWhenGroupRemoved;
for (Anchor *anchor : qAsConst(m_anchors)) {
if (anchor->isStatic())
continue;
if (anchor->onlyHasPlaceholderItems(Anchor::Side2)) {
Anchor *toFollow = anchor->findNearestAnchorWithItems(Anchor::Side2);
if (toFollow->followee() != anchor) {
if (!toFollow->isStatic() && groupBeingRemoved.containsAnchor(anchor, Anchor::Side1)) {
// A group is being removed, instead of simply shifting the left/top anchor all the way, let's make it use half the space
if (toFollow->onlyHasPlaceholderItems(Anchor::Side1)) { // Means it can move!
const int delta = toFollow->position() - anchor->position() - anchor->thickness();
const int halfDelta = int(delta / 2.0);
if (halfDelta > 0) {
newPositionsWhenGroupRemoved.insert(toFollow, toFollow->position() - halfDelta);
}
}
}
anchor->setFollowee(toFollow);
}
} else if (anchor->onlyHasPlaceholderItems(Anchor::Side1)) {
Anchor *toFollow = anchor->findNearestAnchorWithItems(Anchor::Side1);
if (toFollow->followee() != anchor) {
if (!toFollow->isStatic() && groupBeingRemoved.containsAnchor(anchor, Anchor::Side2)) {
// A group is being removed, instead of simply shifting the right/bottom anchor all the way, let's make it use half the space
if (toFollow->onlyHasPlaceholderItems(Anchor::Side2)) { // Means it can move!
const int delta = anchor->position() - toFollow->position() - toFollow->thickness();
const int halfDelta = int(delta / 2.0);
if (halfDelta > 0) {
newPositionsWhenGroupRemoved.insert(toFollow, toFollow->position() + halfDelta);
}
}
}
anchor->setFollowee(toFollow);
}
}
}
for (auto it = newPositionsWhenGroupRemoved.begin(), end = newPositionsWhenGroupRemoved.end(); it != end; ++it) {
Anchor *anchorToShift = it.key();
const int newPosition = it.value();
const Anchor::Side sideToShiftTo = newPosition < anchorToShift->position() ? Anchor::Side1
: Anchor::Side2;
bool doShift = true;
for (Anchor *follower : anchorToShift->followers()) {
if (follower->hasNonPlaceholderItems(sideToShiftTo) && !groupBeingRemoved.containsAnchor(follower, sideToShiftTo)) {
doShift = false;
break;
}
}
if (doShift && !anchorToShift->isFollowing())
anchorToShift->setPosition(newPosition);
}
updateSizeConstraints();
ensureAnchorsBounded();
}
QHash<Anchor*, Anchor*> MultiSplitterLayout::anchorsShouldFollow() const
{
QHash<Anchor*, Anchor*> followers;
for (Anchor *anchor : m_anchors) {
if (anchor->isStatic())
continue;
if (anchor->onlyHasPlaceholderItems(Anchor::Side2)) {
Anchor *toFollow = anchor->findNearestAnchorWithItems(Anchor::Side2);
if (followers.value(toFollow) != anchor)
followers.insert(anchor, toFollow);
} else if (anchor->onlyHasPlaceholderItems(Anchor::Side1)) {
Anchor *toFollow = anchor->findNearestAnchorWithItems(Anchor::Side1);
if (followers.value(toFollow) != anchor)
followers.insert(anchor, toFollow);
}
}
return followers;
}
void MultiSplitterLayout::insertAnchor(Anchor *anchor)
{
m_anchors.append(anchor);
}
const ItemList MultiSplitterLayout::items() const
{
return m_items;
@@ -1314,10 +756,6 @@ bool MultiSplitterLayout::deserialize(const LayoutSaver::MultiSplitterLayout &ms
}
}
m_staticAnchorGroup.left = m_leftAnchor;
m_staticAnchorGroup.top = m_topAnchor;
m_staticAnchorGroup.right = m_rightAnchor;
m_staticAnchorGroup.bottom = m_bottomAnchor;
m_items.clear(); // Now properly set the items, which installs needed event filters, etc.
addItems_internal(items, false, false); // Add the items only after we have the static anchors set