This commit is contained in:
Sergio Martins
2020-04-02 19:43:36 +01:00
parent 993ca6e9c6
commit 4734f06046
15 changed files with 71 additions and 612 deletions

View File

@@ -48,7 +48,7 @@ const QString MultiSplitterLayout::s_magicMarker = QStringLiteral("bac9948e-5f1b
MultiSplitterLayout::MultiSplitterLayout(MultiSplitter *parent)
: QObject(parent)
, m_multiSplitter(parent)
, m_rootItem(new Item())
, m_rootItem(new ItemContainer())
{
Q_ASSERT(parent);
@@ -141,57 +141,6 @@ bool MultiSplitterLayout::validateInputs(QWidgetOrQuick *widget,
return true;
}
std::pair<int,int> MultiSplitterLayout::boundInterval(int newPos1, Anchor* anchor1, int newPos2, Anchor *anchor2) const
{
const int bound1 = boundPositionForAnchor(anchor1, Anchor::Side1);
const int bound2 = boundPositionForAnchor(anchor2, Anchor::Side2);
if (newPos1 >= bound1 && newPos2 <= bound2) {
// Simplest case, it's bounded.
return { newPos1, newPos2 };
}
if (newPos1 < bound1) {
// the anchor1 is out of bounds
const int bythismuch = bound1 - newPos1;
newPos1 = bound1;
newPos2 = newPos2 + bythismuch;
if (newPos2 > bound2) {
qWarning() << "Adjusted interval still out of bounds. Not enough space. #1"
<< "; newPos1=" << newPos1
<< "; newPos2=" << newPos2
<< "; bounds=" << bound1 << bound2
<< "; anchor1=" << anchor1
<< "; anchor2=" << anchor2
<< "; size=" << size();
}
return { newPos1, newPos2 };
} else if (newPos2 > bound2) {
// the anchor2 is out of bounds
const int bythismuch = newPos2 - bound2;
newPos2 = bound2;
newPos1 = newPos1 - bythismuch;
if (newPos1 < bound1) {
qWarning() << "Adjusted interval still out of bounds. Not enough space. #2"
<< "; newPos1=" << newPos1
<< "; newPos2=" << newPos2
<< "; bounds=" << bound1 << bound2
<< "; anchor1=" << anchor1
<< "; anchor2=" << anchor2
<< "; size=" << size();
}
return { newPos1, newPos2 };
}
return { newPos1, newPos2 };
}
void MultiSplitterLayout::addWidget(QWidgetOrQuick *w, Location location, Frame *relativeToWidget, AddingOption option)
{
auto frame = qobject_cast<Frame*>(w);
@@ -227,9 +176,8 @@ void MultiSplitterLayout::addItems_internal(const ItemList &items, bool updateCo
updateSizeConstraints();
for (auto item : items) {
item->setLayout(this);
if (item->frame()) {
item->setVisible(true);
item->setIsVisible(true);
item->frame()->installEventFilter(this);
Q_EMIT widgetAdded(item);
}
@@ -239,192 +187,6 @@ void MultiSplitterLayout::addItems_internal(const ItemList &items, bool updateCo
Q_EMIT widgetCountChanged(m_items.size());
}
void MultiSplitterLayout::addAsPlaceholder(DockWidgetBase *dockWidget, Location location, Item *relativeTo)
{
if (!dockWidget) {
qWarning() << Q_FUNC_INFO << "null dockwidget";
return;
}
dockWidget->setParent(nullptr);
auto result = createTargetAnchorGroup(location, relativeTo);
AnchorGroup targetAnchorGroup = result.first;
auto frame = Config::self().frameworkWidgetFactory()->createFrame(m_multiSplitter);
auto item = new Item(frame, this);
targetAnchorGroup.addItem(item);
addItems_internal(ItemList{ item }, false);
dockWidget->addPlaceholderItem(item);
delete frame;
updateAnchorFollowing();
Q_ASSERT(!dockWidget->isVisible());
maybeCheckSanity();
}
void MultiSplitterLayout::ensureEnoughSize(const QWidgetOrQuick *widget,
Location location, const Item *relativeToItem)
{
const int neededAnchorThickness = isEmpty() ? 0 : Anchor::thickness(/*static=*/ false);
const QSize available = availableSize();
const QSize widgetMin = { widgetMinLength(widget, Qt::Vertical), widgetMinLength(widget, Qt::Horizontal) };
const QSize oldSize = m_size;
const int neededWidth = widgetMin.width() - available.width() + neededAnchorThickness;
const int neededHeight = widgetMin.height() - available.height() + neededAnchorThickness;
QSize newSize = m_size;
if (neededWidth > 0)
newSize.setWidth(newSize.width() + neededWidth);
if (neededHeight > 0)
newSize.setHeight(newSize.height() + neededHeight);
if (newSize != m_size)
setSize(newSize);
// Just to make sure:
if (lengthForDrop(widget, location, relativeToItem).isNull()) {
qWarning() << Q_FUNC_INFO << "failed! Please report a bug."
<< "; oldAvailable=" << available
<< "; newAvailable=" << availableSize()
<< "; newSize=" << newSize
<< "; m_size=" << m_size
<< "; oldSize=" << oldSize
<< "; widgetMin=" << widgetMin
<< "; isEmpty=" << isEmpty();
}
}
void MultiSplitterLayout::ensureAnchorsBounded()
{
//Ensures all separators are within their bounds, meaning all items obey their min size
positionStaticAnchors();
ensureItemsMinSize();
}
static Anchor::List removeSmallestPath(QVector<Anchor::List> &paths)
{
// Removes and returns the smallest list
Anchor::List smallestPath;
int indexOfSmallest = 0;
for (int i = 0, end = paths.size(); i < end; ++i) {
const Anchor::List &path = paths.at(i);
if (path.size() <= smallestPath.size() || smallestPath.isEmpty()) {
smallestPath = path;
indexOfSmallest = i;
}
}
paths.removeAt(indexOfSmallest);
return smallestPath;
}
void MultiSplitterLayout::propagateResize(int delta, Anchor *fromAnchor, Anchor::Side direction)
{
if (delta < 0)
qWarning() << Q_FUNC_INFO << "Invalid delta" << delta << fromAnchor << direction;
if (delta <= 0 || fromAnchor->isStatic())
return;
QVector<Anchor::List> paths;
collectPaths(paths, fromAnchor, direction);
for (const Anchor::List &path : qAsConst(paths)) {
qCDebug(sizing) << Q_FUNC_INFO << path;
}
Anchor::List anchorsThatAlreadyContributed;
anchorsThatAlreadyContributed.push_back(fromAnchor);
while (!paths.isEmpty()) {
// Get smallest path:
Anchor::List smallestPath = removeSmallestPath(/*by-ref*/paths);
if (smallestPath.size() <= 1) {
// Nothing to do, it has a single anchor, which was already adjusted in addWidget()
continue;
}
const bool towardsSide1 = direction == Anchor::Side1;
const bool towardsSide2 = !towardsSide1;
const int sign = towardsSide1 ? -1 : 1;
const int contributionPerAnchor = (delta / (smallestPath.size() - 1)) * sign; // n-1 because the initial anchor already contributed
if (qAbs(contributionPerAnchor) < 5) {
// Too small, don't bother
continue;
}
// Now make those anchors contribute, skipping the first
for (int i = 1, end = smallestPath.size(); i < end; ++i) {
Anchor *a = smallestPath.at(i);
if (!anchorsThatAlreadyContributed.contains(a)) {
// When moving anchors don't allow widgets to go bellow their min size
const int bound = boundPositionForAnchor(a, direction);
int newPosition = a->position() + contributionPerAnchor;
if ((towardsSide1 && newPosition < bound) ||
(towardsSide2 && newPosition > bound)) {
newPosition = bound;
}
if (a->position() != newPosition) {
a->setPosition(newPosition);
anchorsThatAlreadyContributed.push_back(a);
}
}
}
}
}
void MultiSplitterLayout::collectPaths(QVector<Anchor::List> &paths, Anchor *fromAnchor, Anchor::Side direction)
{
if (fromAnchor->isStatic()) {
// We've finally reached a border anchor, we can stop now.
return;
}
if (paths.isEmpty())
paths.push_back({});
int currentPathIndex = paths.size() - 1; // Store the index instead of using "Anchor::List &currentPath = paths.last();" as the references are stable, as the paths vector reallocates
paths[currentPathIndex].push_back(fromAnchor);
const ItemList items = fromAnchor->items(direction);
for (int i = 0, end = items.size(); i < end; ++i) {
Anchor *nextAnchor = items[i]->anchorAtSide(direction, fromAnchor->orientation());
if (i > 0) {
Anchor::List newPath = paths[currentPathIndex];
paths.push_back(newPath);
}
collectPaths(paths, nextAnchor, direction);
}
}
void MultiSplitterLayout::resizeItem(Frame *frame, int newSize, Qt::Orientation orientation)
{
// Used for unit-tests only
Item *item = itemForFrame(frame);
Q_ASSERT(item);
Anchor *a = item->anchorAtSide(Anchor::Side2, orientation);
Q_ASSERT(!a->isStatic());
const int widgLength = item->length(orientation);
const int delta = newSize - widgLength;
qCDebug(::anchors) << Q_FUNC_INFO << "Old position:" << a->position() << "; old w.geo=" << item->geometry();
a->setPosition(a->position() + delta);
qCDebug(::anchors) << Q_FUNC_INFO << "New position:" << a->position() << "; new w.geo=" << item->geometry();
}
void MultiSplitterLayout::ensureItemsMinSize()
{
for (Item *item : qAsConst(m_items)) {
item->ensureMinSize(Qt::Vertical);
item->ensureMinSize(Qt::Horizontal);
}
}
QString MultiSplitterLayout::affinityName() const
{
if (auto ms = multiSplitter()) {
@@ -448,18 +210,13 @@ void MultiSplitterLayout::addMultiSplitter(MultiSplitter *sourceMultiSplitter,
void MultiSplitterLayout::removeItem(Item *item)
{
if (!item || m_inDestructor || !m_items.contains(item))
if (!item || m_inDestructor)
return;
maybeCheckSanity();
Q_ASSERT(item != m_rootItem);
if (!item->isPlaceholder())
item->frame()->removeEventFilter(this);
AnchorGroup anchorGroup = item->anchorGroup();
anchorGroup.removeItem(item);
m_items.removeOne(item);
updateAnchorFollowing();
item->parentContainer()->removeItem(item);
Q_EMIT widgetRemoved(item);
Q_EMIT widgetCountChanged(m_items.size());
@@ -485,43 +242,18 @@ Item *MultiSplitterLayout::itemAt(QPoint p) const
return nullptr;
}
void MultiSplitterLayout::clear(bool alsoDeleteStaticAnchors)
void MultiSplitterLayout::clear()
{
const int oldCount = count();
const int oldVisibleCount = visibleCount();
const auto items = m_items;
m_items.clear(); // Clear the item list first, do avoid ~Item() triggering a removal from the list
qDeleteAll(items);
const auto anchors = m_anchors;
m_anchors.clear();
m_rootItem->clear();
for (Anchor *anchor : qAsConst(anchors)) {
anchor->clear();
if (!anchor->isStatic() || alsoDeleteStaticAnchors) {
delete anchor;
}
}
if (alsoDeleteStaticAnchors) {
m_anchors.clear();
m_topAnchor = nullptr;
m_bottomAnchor = nullptr;
m_leftAnchor = nullptr;
m_rightAnchor = nullptr;
m_staticAnchorGroup.left = nullptr;
m_staticAnchorGroup.top = nullptr;
m_staticAnchorGroup.right = nullptr;
m_staticAnchorGroup.bottom = nullptr;
} else {
m_anchors = { m_topAnchor, m_bottomAnchor, m_leftAnchor, m_rightAnchor };
}
if (oldCount > 0)
Q_EMIT widgetCountChanged(0);
if (oldVisibleCount > 0)
Q_EMIT visibleWidgetCountChanged(0);
}
int MultiSplitterLayout::visibleCount() const
@@ -538,8 +270,6 @@ int MultiSplitterLayout::placeholderCount() const
return count() - visibleCount();
}
void MultiSplitterLayout::setAnchorBeingDragged(Anchor *anchor)
{
m_anchorBeingDragged = anchor;
@@ -617,6 +347,11 @@ QVector<DockWidgetBase *> MultiSplitterLayout::dockWidgets() const
return result;
}
void MultiSplitterLayout::restorePlaceholder(Item *, int /*tabIndex*/)
{
// TODO
}
bool MultiSplitterLayout::checkSanity() const
{
return m_rootItem->checkSanity();
@@ -637,33 +372,11 @@ void MultiSplitterLayout::unrefOldPlaceholders(const Frame::List &framesBeingAdd
void MultiSplitterLayout::setSize(QSize size)
{
if (size != m_size) {
if (size != this->size()) {
m_rootItem->resize(size);
m_resizing = true;
QSize oldSize = m_size;
if (size.width() < m_minSize.width() || size.height() < m_minSize.height()) {
qWarning() << Q_FUNC_INFO << "new size is smaller than min size. Size=" << size << "; min=" << m_minSize;
return;
}
#if defined(DOCKS_DEVELOPER_MODE)
if (!m_inCtor && false) { // TODO Uncomment when it passes
QSize minSizeCalculated = QSize(availableLengthForOrientation(Qt::Vertical), availableLengthForOrientation(Qt::Horizontal));
if (size.width() < minSizeCalculated.width() || size.height() < minSizeCalculated.height()) {
qWarning() << Q_FUNC_INFO << "new size is smaller than min size calculated" << size << minSizeCalculated;
}
}
#endif
m_size = size;
Q_EMIT sizeChanged(size);
redistributeSpace(oldSize, size);
m_resizing = false;
if (!m_restoringPlaceholder) { // ensureAnchorsBounded() is run at the end of restorePlaceholder() already.
ensureAnchorsBounded();
}
m_resizing = false; // TODO: m_resizing needed ?
}
}
@@ -671,10 +384,10 @@ void MultiSplitterLayout::setContentLength(int value, Qt::Orientation o)
{
if (o == Qt::Vertical) {
// Setting the width
setSize({value, m_size.height()});
setSize({value, size().height()});
} else {
// Setting the height
setSize({m_size.width(), value});
setSize({size().width(), value});
}
}
@@ -686,9 +399,9 @@ int MultiSplitterLayout::length(Qt::Orientation o) const
void MultiSplitterLayout::setMinimumSize(QSize sz)
{
if (sz != m_minSize) {
m_minSize = sz;
setSize(m_size.expandedTo(m_minSize)); // Increase size incase we need to
if (sz != m_rootItem->minSize()) {
m_rootItem->setMinSize(sz);
setSize(size().expandedTo(m_rootItem->minSize())); // Increase size incase we need to
Q_EMIT minimumSizeChanged(sz);
}
qCDebug(sizing) << Q_FUNC_INFO << "minSize = " << m_minSize;
@@ -725,77 +438,23 @@ bool MultiSplitterLayout::eventFilter(QObject *o, QEvent *e)
bool MultiSplitterLayout::deserialize(const LayoutSaver::MultiSplitterLayout &msl)
{
clear(true);
clear();
ItemList items;
items.reserve(msl.items.size());
for (const auto &i : qAsConst(msl.items)) {
Item *item = Item::deserialize(i, this);
items.push_back(item);
Q_UNUSED(i);
//Item *item = deserialize(); TODO
//items.push_back(item);
}
m_items = items; // Set the items, so Anchor::deserialize() can set the side1 and side2 items
for (const auto &a : qAsConst(msl.anchors)) {
Anchor *anchor = Anchor::deserialize(a, this); // They auto-register into m_anchors
if (!anchor)
return false;
if (anchor->type() == Anchor::Type_LeftStatic) {
Q_ASSERT(!m_leftAnchor);
m_leftAnchor = anchor;
} else if (anchor->type() == Anchor::Type_TopStatic) {
Q_ASSERT(!m_topAnchor);
m_topAnchor = anchor;
} else if (anchor->type() == Anchor::Type_RightStatic) {
Q_ASSERT(!m_rightAnchor);
m_rightAnchor = anchor;
} else if (anchor->type() == Anchor::Type_BottomStatic) {
Q_ASSERT(!m_bottomAnchor);
m_bottomAnchor = anchor;
}
}
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
for (Anchor *anchor : qAsConst(m_anchors)) {
int indexFrom = anchor->property("indexFrom").toInt();
int indexTo = anchor->property("indexTo").toInt();
int indexFolowee = anchor->property("indexFolowee").toInt();
anchor->setProperty("indexFrom", QVariant());
anchor->setProperty("indexTo", QVariant());
anchor->setProperty("indexFolowee", QVariant());
anchor->setFrom(m_anchors.at(indexFrom));
anchor->setTo(m_anchors.at(indexTo));
if (indexFolowee != -1)
anchor->setFollowee(m_anchors.at(indexFolowee));
}
m_size = msl.size;
m_minSize = msl.minSize;
// Now that the anchors were created we can add them to the items
for (Item *item : qAsConst(m_items)) {
const int leftIndex = item->property("leftIndex").toInt();
const int topIndex = item->property("topIndex").toInt();
const int rightIndex = item->property("rightIndex").toInt();
const int bottomIndex = item->property("bottomIndex").toInt();
AnchorGroup &group = item->anchorGroup();
group.left = m_anchors.at(leftIndex);
group.top = m_anchors.at(topIndex);
group.right = m_anchors.at(rightIndex);
group.bottom = m_anchors.at(bottomIndex);
// Clear helper properties
item->setProperty("leftIndex", QVariant());
item->setProperty("topIndex", QVariant());
item->setProperty("rightIndex", QVariant());
item->setProperty("bottomIndex", QVariant());
}
setSize(msl.size);
setMinimumSize(msl.minSize);
if (!m_items.isEmpty())
Q_EMIT widgetCountChanged(m_items.size());
@@ -806,7 +465,7 @@ bool MultiSplitterLayout::deserialize(const LayoutSaver::MultiSplitterLayout &ms
// its content size if needed
Q_EMIT minimumSizeChanged(m_minSize);
if (m_size != multiSplitter()->size()) {
if (size() != multiSplitter()->size()) {
setSize(multiSplitter()->size());
}
@@ -820,11 +479,8 @@ LayoutSaver::MultiSplitterLayout MultiSplitterLayout::serialize() const
l.size = size();
l.minSize = minimumSize();
for (Item *item : m_items)
l.items.push_back(item->serialize());
for (Anchor *anchor : m_anchors)
l.anchors.push_back(anchor->serialize());
//for (Item *item : m_items) TODO
//l.items.push_back(item->serialize());
return l;
}