DockWidgets: Allow to have floating dockwidgets side by side

Based on what MS VisualStudio can do, allow to have. We can now
drop the QDockWidgets on floating ones so that they are next to eachother

This is activated when QMainWindow::GroupedDragging and
QMainWindow::AllowNestedDocks are both set.

[ChangeLog][QtWidgets][QDockWidget] When QMainWindow::GrouppedDragging
and QMainWindow::AllowNestedDocks are both enabled, floating dock widgets
acquired the ability to be dropped together side by side.

Change-Id: I48765c72ef80ef15aa31b91ae582cbb01dea41d6
Reviewed-by: Sérgio Martins <sergio.martins@kdab.com>
This commit is contained in:
Olivier Goffart 2017-02-22 12:21:46 +01:00 committed by Olivier Goffart (Woboq GmbH)
parent 60be2fb6a1
commit 4b0d6704d9
5 changed files with 353 additions and 160 deletions

View File

@ -2418,20 +2418,21 @@ QList<int> QDockAreaLayout::indexOf(QWidget *dockWidget) const
return QList<int>();
}
QList<int> QDockAreaLayout::gapIndex(const QPoint &pos) const
QList<int> QDockAreaLayout::gapIndex(const QPoint &pos, bool disallowTabs) const
{
QMainWindow::DockOptions opts = mainWindow->dockOptions();
bool nestingEnabled = opts & QMainWindow::AllowNestedDocks;
QDockAreaLayoutInfo::TabMode tabMode = QDockAreaLayoutInfo::NoTabs;
#ifndef QT_NO_TABBAR
if (opts & QMainWindow::AllowTabbedDocks
|| opts & QMainWindow::VerticalTabs)
tabMode = QDockAreaLayoutInfo::AllowTabs;
if (opts & QMainWindow::ForceTabbedDocks)
tabMode = QDockAreaLayoutInfo::ForceTabs;
if (!disallowTabs) {
if (opts & QMainWindow::AllowTabbedDocks || opts & QMainWindow::VerticalTabs)
tabMode = QDockAreaLayoutInfo::AllowTabs;
if (opts & QMainWindow::ForceTabbedDocks)
tabMode = QDockAreaLayoutInfo::ForceTabs;
if (tabMode == QDockAreaLayoutInfo::ForceTabs)
nestingEnabled = false;
if (tabMode == QDockAreaLayoutInfo::ForceTabs)
nestingEnabled = false;
}
#endif
@ -3303,6 +3304,19 @@ int QDockAreaLayout::separatorMove(const QList<int> &separator, const QPoint &or
return delta;
}
int QDockAreaLayoutInfo::separatorMove(const QList<int> &separator, const QPoint &origin,
const QPoint &dest)
{
int delta = 0;
int index = separator.last();
QDockAreaLayoutInfo *info = this->info(separator);
delta = pick(info->o, dest - origin);
if (delta != 0)
delta = info->separatorMove(index, delta);
info->apply(false);
return delta;
}
#ifndef QT_NO_TABBAR
// Sets the correct positions for the separator widgets
// Allocates new sepearator widgets with getSeparatorWidget

View File

@ -182,6 +182,7 @@ public:
const QPoint &mouse) const;
QRegion separatorRegion() const;
int separatorMove(int index, int delta);
int separatorMove(const QList<int> &separator, const QPoint &origin, const QPoint &dest);
QLayoutItem *itemAt(int *x, int index) const;
QLayoutItem *takeAt(int *x, int index);
@ -248,7 +249,7 @@ public:
QList<int> indexOfPlaceHolder(const QString &objectName) const;
QList<int> indexOf(QWidget *dockWidget) const;
QList<int> gapIndex(const QPoint &pos) const;
QList<int> gapIndex(const QPoint &pos, bool disallowTabs) const;
QList<int> findSeparator(const QPoint &pos) const;
QDockAreaLayoutItem &item(const QList<int> &path);

View File

@ -214,10 +214,9 @@ QDockWidgetLayout::~QDockWidgetLayout()
bool QDockWidgetLayout::nativeWindowDeco() const
{
bool floating = parentWidget()->isWindow();
if (!floating) {
if (auto groupWindow = qobject_cast<const QDockWidgetGroupWindow*>(parentWidget()->parentWidget()))
return groupWindow->hasNativeDecos();
}
if (auto groupWindow =
qobject_cast<const QDockWidgetGroupWindow *>(parentWidget()->parentWidget()))
floating = floating || groupWindow->tabLayoutInfo();
return nativeWindowDeco(floating);
}

View File

@ -173,13 +173,17 @@ QDebug operator<<(QDebug debug, const QMainWindowLayout *layout)
/******************************************************************************
** QDockWidgetGroupWindow
*/
// QDockWidgetGroupWindow is the floating window containing the tabbed dockwidgets in case several
// dockwidgets are dragged together (QMainWindow::GroupedDragging feature).
// QDockWidgetGroupWindow is the floating window containing several QDockWidgets floating together.
// (QMainWindow::GroupedDragging feature)
// QDockWidgetGroupLayout is the layout of that window and use a QDockAreaLayoutInfo to layout
// the tabs inside it.
// the QDockWidgets inside it.
// If there is only one QDockWidgets, or all QDockWidgets are tabbed together, it is equivalent
// of a floating QDockWidget (the title of the QDockWidget is the title of the window). But if there
// are nested QDockWidget, an additional title bar is there.
#ifndef QT_NO_DOCKWIDGET
class QDockWidgetGroupLayout : public QLayout {
QDockAreaLayoutInfo info;
class QDockWidgetGroupLayout : public QLayout,
public QMainWindowLayoutSeparatorHelper<QDockWidgetGroupLayout>
{
QWidgetResizeHandler *resizer;
public:
QDockWidgetGroupLayout(QDockWidgetGroupWindow* parent) : QLayout(parent) {
@ -188,7 +192,7 @@ public:
resizer->setMovingEnabled(false);
}
~QDockWidgetGroupLayout() {
info.deleteAllLayoutItems();
layoutState.deleteAllLayoutItems();
}
void addItem(QLayoutItem*) Q_DECL_OVERRIDE { Q_UNREACHABLE(); }
@ -196,32 +200,43 @@ public:
QLayoutItem* itemAt(int index) const Q_DECL_OVERRIDE
{
int x = 0;
return info.itemAt(&x, index);
return layoutState.itemAt(&x, index);
}
QLayoutItem* takeAt(int index) Q_DECL_OVERRIDE
{
int x = 0;
return info.takeAt(&x, index);
QLayoutItem *ret = layoutState.takeAt(&x, index);
if (savedState.rect.isValid() && ret->widget()) {
// we need to remove the item also from the saved state to prevent crash
QList<int> path = savedState.indexOf(ret->widget());
if (!path.isEmpty())
savedState.remove(path);
// Also, the item may be contained several times as a gap item.
path = layoutState.indexOf(ret->widget());
if (!path.isEmpty())
layoutState.remove(path);
}
return ret;
}
QSize sizeHint() const Q_DECL_OVERRIDE
{
int fw = frameWidth();
return info.sizeHint() + QSize(fw, fw);
return layoutState.sizeHint() + QSize(fw, fw);
}
QSize minimumSize() const Q_DECL_OVERRIDE
{
int fw = frameWidth();
return info.minimumSize() + QSize(fw, fw);
return layoutState.minimumSize() + QSize(fw, fw);
}
QSize maximumSize() const Q_DECL_OVERRIDE
{
int fw = frameWidth();
return info.maximumSize() + QSize(fw, fw);
return layoutState.maximumSize() + QSize(fw, fw);
}
void setGeometry(const QRect&r) Q_DECL_OVERRIDE
{
groupWindow()->destroyOrHideIfEmpty();
QDockAreaLayoutInfo *li = layoutInfo();
QDockAreaLayoutInfo *li = dockAreaLayoutInfo();
if (li->isEmpty())
return;
int fw = frameWidth();
@ -231,12 +246,12 @@ public:
li->rect = r.adjusted(fw, fw, -fw, -fw);
li->fitItems();
li->apply(false);
if (savedState.rect.isValid())
savedState.rect = li->rect;
resizer->setActive(QWidgetResizeHandler::Resize, !nativeWindowDeco());
}
QDockAreaLayoutInfo *layoutInfo() {
return &info;
}
QDockAreaLayoutInfo *dockAreaLayoutInfo() { return &layoutState; }
bool nativeWindowDeco() const
{
@ -253,14 +268,21 @@ public:
{
return static_cast<QDockWidgetGroupWindow *>(parent());
}
QDockAreaLayoutInfo layoutState;
QDockAreaLayoutInfo savedState;
};
bool QDockWidgetGroupWindow::event(QEvent *e)
{
auto lay = static_cast<QDockWidgetGroupLayout *>(layout());
if (lay && lay->windowEvent(e))
return true;
switch (e->type()) {
case QEvent::Close:
// Forward the close to the QDockWidget just as if its close button was pressed
if (QDockWidget *dw = topDockWidget()) {
if (QDockWidget *dw = activeTabbedDockWidget()) {
e->ignore();
dw->close();
adjustFlags();
@ -268,7 +290,7 @@ bool QDockWidgetGroupWindow::event(QEvent *e)
return true;
case QEvent::Move:
// Let QDockWidgetPrivate::moseEvent handle the dragging
if (QDockWidget *dw = topDockWidget())
if (QDockWidget *dw = activeTabbedDockWidget())
static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(dw))->moveEvent(static_cast<QMoveEvent*>(e));
return true;
case QEvent::NonClientAreaMouseMove:
@ -276,7 +298,7 @@ bool QDockWidgetGroupWindow::event(QEvent *e)
case QEvent::NonClientAreaMouseButtonRelease:
case QEvent::NonClientAreaMouseButtonDblClick:
// Let the QDockWidgetPrivate of the currently visible dock widget handle the drag and drop
if (QDockWidget *dw = topDockWidget())
if (QDockWidget *dw = activeTabbedDockWidget())
static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(dw))->nonClientAreaMouseEvent(static_cast<QMouseEvent*>(e));
return true;
case QEvent::ChildAdded:
@ -308,17 +330,49 @@ void QDockWidgetGroupWindow::paintEvent(QPaintEvent *)
QDockAreaLayoutInfo *QDockWidgetGroupWindow::layoutInfo() const
{
return static_cast<QDockWidgetGroupLayout*>(layout())->layoutInfo();
return static_cast<QDockWidgetGroupLayout *>(layout())->dockAreaLayoutInfo();
}
/*! \internal
Returns the currently active QDockWidget.
If this is a floating tab bar returns the currently the QDockWidgetGroupWindow that contains
tab, otherwise, return nullptr;
\note: if there is only one QDockWidget, it's still considered as a floating tab
*/
QDockWidget *QDockWidgetGroupWindow::topDockWidget() const
const QDockAreaLayoutInfo *QDockWidgetGroupWindow::tabLayoutInfo() const
{
QDockAreaLayoutInfo *info = layoutInfo();
QDockWidget *dw = 0;
const QDockAreaLayoutInfo *info = layoutInfo();
while (info && !info->tabbed) {
// There should be only one tabbed subinfo otherwise we are not a floating tab but a real
// window
const QDockAreaLayoutInfo *next = nullptr;
bool isSingle = false;
for (const auto &item : info->item_list) {
if (item.skip() || (item.flags & QDockAreaLayoutItem::GapItem))
continue;
if (next || isSingle) // Two visible things
return nullptr;
if (item.subinfo)
next = item.subinfo;
else if (item.widgetItem)
isSingle = true;
}
if (isSingle)
return info;
info = next;
}
return info;
}
/*! \internal
If this is a floating tab bar returns the currently active QDockWidget, otherwise nullptr
*/
QDockWidget *QDockWidgetGroupWindow::activeTabbedDockWidget() const
{
QDockWidget *dw = nullptr;
#if QT_CONFIG(tabbar)
const QDockAreaLayoutInfo *info = tabLayoutInfo();
if (!info)
return nullptr;
if (info->tabBar && info->tabBar->currentIndex() >= 0) {
int i = info->tabIndexToListIndex(info->tabBar->currentIndex());
if (i >= 0) {
@ -327,7 +381,6 @@ QDockWidget *QDockWidgetGroupWindow::topDockWidget() const
dw = qobject_cast<QDockWidget *>(item.widgetItem->widget());
}
}
#endif
if (!dw) {
for (int i = 0; !dw && i < info->item_list.count(); ++i) {
const QDockAreaLayoutItem &item = info->item_list.at(i);
@ -338,6 +391,7 @@ QDockWidget *QDockWidgetGroupWindow::topDockWidget() const
dw = qobject_cast<QDockWidget *>(item.widgetItem->widget());
}
}
#endif
return dw;
}
@ -384,18 +438,18 @@ void QDockWidgetGroupWindow::destroyOrHideIfEmpty()
}
/*! \internal
Sets the flags of this window in accordence to the capabilities of the dock widgets
Sets the flags of this window in accordance to the capabilities of the dock widgets
*/
void QDockWidgetGroupWindow::adjustFlags()
{
QDockWidget *top = topDockWidget();
if (!top)
return;
const bool nativeDeco = static_cast<QDockWidgetGroupLayout *>(layout())->nativeWindowDeco();
Qt::WindowFlags oldFlags = windowFlags();
Qt::WindowFlags flags = oldFlags;
if (nativeDeco) {
QDockWidget *top = activeTabbedDockWidget();
if (!top) { // nested tabs, show window decoration
flags =
((oldFlags & ~Qt::FramelessWindowHint) | Qt::CustomizeWindowHint | Qt::WindowTitleHint);
} else if (static_cast<QDockWidgetGroupLayout *>(layout())->nativeWindowDeco()) {
flags |= Qt::CustomizeWindowHint | Qt::WindowTitleHint;
flags.setFlag(Qt::WindowCloseButtonHint, top->features() & QDockWidget::DockWidgetClosable);
flags &= ~Qt::FramelessWindowHint;
@ -428,21 +482,99 @@ void QDockWidgetGroupWindow::adjustFlags()
show(); // setWindowFlags hides the window
}
setWindowTitle(top->windowTitle());
setWindowIcon(top->windowIcon());
QWidget *titleBarOf = top ? top : parentWidget();
setWindowTitle(titleBarOf->windowTitle());
setWindowIcon(titleBarOf->windowIcon());
}
bool QDockWidgetGroupWindow::hasNativeDecos() const
{
QDockWidget *dw = activeTabbedDockWidget();
if (!dw) // We have a group of nested QDockWidgets (not just floating tabs)
return true;
if (!QDockWidgetLayout::wmSupportsNativeWindowDeco())
return false;
if (QDockWidget *dw = topDockWidget())
return dw->titleBarWidget() == nullptr;
return dw->titleBarWidget() == nullptr;
}
/*
The given widget is hovered over this floating group.
This function will save the state and create a gap in the actual state.
currentGapRect and currentGapPos will be set.
One must call restore() or apply() after this function.
Returns true if there was any change in the currentGapPos
*/
bool QDockWidgetGroupWindow::hover(QLayoutItem *widgetItem, const QPoint &mousePos)
{
QDockAreaLayoutInfo &savedState = static_cast<QDockWidgetGroupLayout *>(layout())->savedState;
if (savedState.isEmpty())
savedState = *layoutInfo();
QMainWindow::DockOptions opts = static_cast<QMainWindow *>(parentWidget())->dockOptions();
bool nestingEnabled =
(opts & QMainWindow::AllowNestedDocks) && !(opts & QMainWindow::ForceTabbedDocks);
QDockAreaLayoutInfo::TabMode tabMode =
nestingEnabled ? QDockAreaLayoutInfo::AllowTabs : QDockAreaLayoutInfo::ForceTabs;
if (auto group = qobject_cast<QDockWidgetGroupWindow *>(widgetItem->widget())) {
if (!group->tabLayoutInfo())
tabMode = QDockAreaLayoutInfo::NoTabs;
}
QDockAreaLayoutInfo newState = savedState;
if (newState.tabbed) {
// insertion into a top-level tab
newState.item_list = { QDockAreaLayoutItem(new QDockAreaLayoutInfo(newState)) };
newState.item_list.first().size = pick(savedState.o, savedState.rect.size());
newState.tabbed = false;
newState.tabBar = nullptr;
}
auto newGapPos = newState.gapIndex(mousePos, nestingEnabled, tabMode);
Q_ASSERT(!newGapPos.isEmpty());
if (newGapPos == currentGapPos)
return false; // gap is already there
currentGapPos = newGapPos;
newState.insertGap(currentGapPos, widgetItem);
newState.fitItems();
currentGapRect = newState.info(currentGapPos)->itemRect(currentGapPos.last(), true);
*layoutInfo() = std::move(newState);
layoutInfo()->apply(opts & QMainWindow::AnimatedDocks);
return true;
}
/*
Remove the gap that was created by hover()
*/
void QDockWidgetGroupWindow::restore()
{
QDockAreaLayoutInfo &savedState = static_cast<QDockWidgetGroupLayout *>(layout())->savedState;
if (!savedState.isEmpty()) {
*layoutInfo() = savedState;
savedState = QDockAreaLayoutInfo();
}
currentGapRect = QRect();
currentGapPos.clear();
adjustFlags();
layoutInfo()->fitItems();
layoutInfo()->apply(static_cast<QMainWindow *>(parentWidget())->dockOptions()
& QMainWindow::AnimatedDocks);
}
/*
Apply the state that was created by hover
*/
void QDockWidgetGroupWindow::apply()
{
static_cast<QDockWidgetGroupLayout *>(layout())->savedState.clear();
currentGapRect = QRect();
layoutInfo()->plug(currentGapPos);
currentGapPos.clear();
adjustFlags();
layoutInfo()->apply(false);
}
#endif
/******************************************************************************
@ -696,7 +828,14 @@ QList<int> QMainWindowLayoutState::gapIndex(QWidget *widget,
// is it a dock widget?
if (qobject_cast<QDockWidget *>(widget) != 0
|| qobject_cast<QDockWidgetGroupWindow *>(widget)) {
result = dockAreaLayout.gapIndex(pos);
bool disallowTabs = false;
#if QT_CONFIG(tabbar)
if (auto *group = qobject_cast<QDockWidgetGroupWindow *>(widget)) {
if (!group->tabLayoutInfo()) // Disallow to drop nested docks as a tab
disallowTabs = true;
}
#endif
result = dockAreaLayout.gapIndex(pos, disallowTabs);
if (!result.isEmpty())
result.prepend(1);
return result;
@ -1824,12 +1963,16 @@ void QMainWindowLayout::invalidate()
}
#ifndef QT_NO_DOCKWIDGET
void QMainWindowLayout::setCurrentHoveredFloat(QWidget *w)
void QMainWindowLayout::setCurrentHoveredFloat(QDockWidgetGroupWindow *w)
{
if (currentHoveredFloat != w) {
if (currentHoveredFloat) {
disconnect(currentHoveredFloat.data(), &QObject::destroyed,
this, &QMainWindowLayout::updateGapIndicator);
if (currentHoveredFloat)
currentHoveredFloat->restore();
} else if (w) {
restore(true);
}
currentHoveredFloat = w;
@ -1900,52 +2043,30 @@ void QMainWindowLayout::revert(QLayoutItem *widgetItem)
bool QMainWindowLayout::plug(QLayoutItem *widgetItem)
{
#ifndef QT_NO_DOCKWIDGET
#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget) && QT_CONFIG(tabbar)
if (currentHoveredFloat) {
QWidget *widget = widgetItem->widget();
QList<int> previousPath = layoutState.indexOf(widget);
if (!previousPath.isEmpty())
layoutState.remove(previousPath);
previousPath = currentHoveredFloat->layoutInfo()->indexOf(widget);
// Let's remove the widget from any possible group window
foreach (QDockWidgetGroupWindow *dwgw,
parent()->findChildren<QDockWidgetGroupWindow*>(QString(), Qt::FindDirectChildrenOnly)) {
if (dwgw == currentHoveredFloat)
continue;
QList<int> path = dwgw->layoutInfo()->indexOf(widget);
if (!path.isEmpty())
dwgw->layoutInfo()->remove(path);
}
currentGapRect = QRect();
#if QT_CONFIG(tabwidget)
if (QDockWidget *dropTo = qobject_cast<QDockWidget*>(currentHoveredFloat)) {
//dropping to a normal widget, we mutate it in a QDockWidgetGroupWindow with two tabs
QDockWidgetGroupWindow *floatingTabs = createTabbedDockWindow();
floatingTabs->setGeometry(dropTo->geometry());
QDockAreaLayoutInfo *info = floatingTabs->layoutInfo();
*info = QDockAreaLayoutInfo(&layoutState.dockAreaLayout.sep, QInternal::LeftDock,
Qt::Horizontal, QTabBar::RoundedSouth,
static_cast<QMainWindow*>(parentWidget()));
info->tabbed = true;
QLayout *parentLayout = currentHoveredFloat->parentWidget()->layout();
info->item_list.append(QDockAreaLayoutItem(parentLayout->takeAt(parentLayout->indexOf(currentHoveredFloat))));
dropTo->setParent(floatingTabs);
dropTo->show();
dropTo->d_func()->plug(QRect());
setCurrentHoveredFloat(floatingTabs);
}
#endif // QT_CONFIG(tabwidget)
#if QT_CONFIG(tabbar)
QDockWidgetGroupWindow *dwgw = qobject_cast<QDockWidgetGroupWindow *>(currentHoveredFloat);
Q_ASSERT(dwgw);
Q_ASSERT(dwgw->layoutInfo()->tabbed); // because floating group should always be tabbed
previousPath = dwgw->layoutInfo()->indexOf(widget);
currentHoveredFloat->apply();
if (!previousPath.isEmpty())
dwgw->layoutInfo()->remove(previousPath);
dwgw->layoutInfo()->tab(0, widgetItem);
QRect globalRect = dwgw->layoutInfo()->tabContentRect();
globalRect.moveTopLeft(dwgw->mapToGlobal(globalRect.topLeft()));
currentHoveredFloat->layoutInfo()->remove(previousPath);
QRect globalRect = currentHoveredFloat->currentGapRect;
globalRect.moveTopLeft(currentHoveredFloat->mapToGlobal(globalRect.topLeft()));
pluggingWidget = widget;
widgetAnimator.animate(widget, globalRect, dockOptions & QMainWindow::AnimatedDocks);
#endif // QT_CONFIG(tabbar)
return true;
}
#endif
@ -2014,57 +2135,55 @@ void QMainWindowLayout::animationFinished(QWidget *widget)
if (widget == pluggingWidget) {
#ifndef QT_NO_DOCKWIDGET
#if QT_CONFIG(tabbar)
if (QDockWidgetGroupWindow *dwgw = qobject_cast<QDockWidgetGroupWindow *>(widget)) {
// When the animated widget was a QDockWidgetGroupWindow, it means each of the
// embedded QDockWidget needs to be plugged back into the QMainWindow layout.
savedState.clear();
QDockAreaLayoutInfo* info = dwgw->layoutInfo();
QDockAreaLayoutInfo* parentInfo;
QList<int> path;
QDockAreaLayoutInfo *srcInfo = dwgw->layoutInfo();
const QDockAreaLayoutInfo *srcTabInfo = dwgw->tabLayoutInfo();
QDockAreaLayoutInfo *dstParentInfo;
QList<int> dstPath;
if (QDockWidgetGroupWindow *dropTo = qobject_cast<QDockWidgetGroupWindow *>(currentHoveredFloat)) {
parentInfo = dropTo->layoutInfo();
#if QT_CONFIG(tabbar)
Q_ASSERT(parentInfo->tabbed);
#endif
path = parentInfo->indexOf(widget);
Q_ASSERT(path.size() == 1);
if (currentHoveredFloat) {
dstPath = currentHoveredFloat->layoutInfo()->indexOf(widget);
Q_ASSERT(dstPath.size() >= 1);
dstParentInfo = currentHoveredFloat->layoutInfo()->info(dstPath);
} else {
path = layoutState.dockAreaLayout.indexOf(widget);
Q_ASSERT(path.size() >= 2);
parentInfo = layoutState.dockAreaLayout.info(path);
Q_ASSERT(parentInfo);
dstPath = layoutState.dockAreaLayout.indexOf(widget);
Q_ASSERT(dstPath.size() >= 2);
dstParentInfo = layoutState.dockAreaLayout.info(dstPath);
}
#if QT_CONFIG(tabbar)
if (parentInfo->tabbed) {
Q_ASSERT(dstParentInfo);
int idx = dstPath.constLast();
Q_ASSERT(dstParentInfo->item_list[idx].widgetItem->widget() == dwgw);
if (dstParentInfo->tabbed && srcTabInfo) {
// merge the two tab widgets
int idx = path.constLast();
Q_ASSERT(parentInfo->item_list[idx].widgetItem->widget() == dwgw);
delete parentInfo->item_list[idx].widgetItem;
parentInfo->item_list.removeAt(idx);
std::copy(info->item_list.cbegin(), info->item_list.cend(),
std::inserter(parentInfo->item_list, parentInfo->item_list.begin() + idx));
quintptr currentId = info->currentTabId();
*info = QDockAreaLayoutInfo();
parentInfo->reparentWidgets(currentHoveredFloat ? currentHoveredFloat.data() : parentWidget());
parentInfo->updateTabBar();
parentInfo->setCurrentTabId(currentId);
} else
#endif // QT_CONFIG(tabbar)
{
QDockAreaLayoutItem &item = layoutState.dockAreaLayout.item(path);
delete dstParentInfo->item_list[idx].widgetItem;
dstParentInfo->item_list.removeAt(idx);
std::copy(srcTabInfo->item_list.cbegin(), srcTabInfo->item_list.cend(),
std::inserter(dstParentInfo->item_list,
dstParentInfo->item_list.begin() + idx));
quintptr currentId = srcTabInfo->currentTabId();
*srcInfo = QDockAreaLayoutInfo();
dstParentInfo->reparentWidgets(currentHoveredFloat ? currentHoveredFloat.data()
: parentWidget());
dstParentInfo->updateTabBar();
dstParentInfo->setCurrentTabId(currentId);
} else {
QDockAreaLayoutItem &item = dstParentInfo->item_list[idx];
Q_ASSERT(item.widgetItem->widget() == dwgw);
delete item.widgetItem;
item.widgetItem = 0;
item.subinfo = new QDockAreaLayoutInfo(qMove(*info));
*info = QDockAreaLayoutInfo();
#if QT_CONFIG(tabbar)
item.subinfo->reparentWidgets(parentWidget());
item.subinfo->setTabBarShape(parentInfo->tabBarShape);
#endif
item.subinfo = new QDockAreaLayoutInfo(std::move(*srcInfo));
*srcInfo = QDockAreaLayoutInfo();
item.subinfo->reparentWidgets(currentHoveredFloat ? currentHoveredFloat.data()
: parentWidget());
item.subinfo->setTabBarShape(dstParentInfo->tabBarShape);
}
dwgw->destroyOrHideIfEmpty();
}
#endif
if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) {
dw->setParent(currentHoveredFloat ? currentHoveredFloat.data() : parentWidget());
@ -2220,6 +2339,36 @@ void QMainWindowLayout::setCentralWidget(QWidget *widget)
invalidate();
}
#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
/*! \internal
This helper function is called by QMainWindowLayout::unplug if QMainWindow::GroupedDragging is
set and we are dragging the title bar of a non-floating QDockWidget.
If one should unplug the whole group, do so and return true, otherwise return false.
\a item is pointing to the QLayoutItem that holds the QDockWidget, but will be updated to the
QLayoutItem that holds the new QDockWidgetGroupWindow if the group is unplugged.
*/
static bool unplugGroup(QMainWindowLayout *layout, QLayoutItem **item,
QDockAreaLayoutItem &parentItem)
{
if (!parentItem.subinfo || !parentItem.subinfo->tabbed)
return false;
// The QDockWidget is part of a group of tab and we need to unplug them all.
QDockWidgetGroupWindow *floatingTabs = layout->createTabbedDockWindow();
QDockAreaLayoutInfo *info = floatingTabs->layoutInfo();
*info = std::move(*parentItem.subinfo);
delete parentItem.subinfo;
parentItem.subinfo = nullptr;
floatingTabs->setGeometry(info->rect.translated(layout->parentWidget()->pos()));
floatingTabs->show();
floatingTabs->raise();
*item = new QDockWidgetGroupWindowItem(floatingTabs);
parentItem.widgetItem = *item;
return true;
}
#endif
/*! \internal
Unplug \a widget (QDockWidget or QToolBar) from it's parent container.
@ -2233,19 +2382,31 @@ void QMainWindowLayout::setCentralWidget(QWidget *widget)
QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group)
{
#if !defined(QT_NO_DOCKWIDGET) && !defined(QT_NO_TABBAR)
if (!widget->isWindow() && qobject_cast<const QDockWidgetGroupWindow *>(widget->parentWidget())) {
if (group) {
auto *groupWindow = qobject_cast<const QDockWidgetGroupWindow *>(widget->parentWidget());
if (!widget->isWindow() && groupWindow) {
if (group && groupWindow->tabLayoutInfo()) {
// We are just dragging a floating window as it, not need to do anything, we just have to
// look up the corresponding QWidgetItem* if it exists
QList<int> tabbedWindowPath = layoutState.indexOf(widget->parentWidget());
return tabbedWindowPath.isEmpty() ? 0 : layoutState.item(tabbedWindowPath);
if (QDockAreaLayoutInfo *info = dockInfo(widget->parentWidget())) {
QList<int> groupWindowPath = info->indexOf(widget->parentWidget());
return groupWindowPath.isEmpty() ? nullptr : info->item(groupWindowPath).widgetItem;
}
return nullptr;
}
QList<int> path = groupWindow->layoutInfo()->indexOf(widget);
QLayoutItem *item = groupWindow->layoutInfo()->item(path).widgetItem;
if (group && path.size() > 1
&& unplugGroup(this, &item,
groupWindow->layoutInfo()->item(path.mid(0, path.size() - 1)))) {
return item;
} else {
// We are unplugging a dock widget from a floating window.
if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) {
dw->d_func()->unplug(widget->geometry());
int index = widget->parentWidget()->layout()->indexOf(widget);
return widget->parentWidget()->layout()->itemAt(index);
}
QDockWidget *dw = qobject_cast<QDockWidget *>(widget);
Q_ASSERT(dw); // cannot be a QDockWidgetGroupWindow because it's not floating.
dw->d_func()->unplug(widget->geometry());
groupWindow->layoutInfo()->fitItems();
groupWindow->layoutInfo()->apply(dockOptions & QMainWindow::AnimatedDocks);
return item;
}
}
#endif
@ -2263,30 +2424,15 @@ QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group)
#ifndef QT_NO_DOCKWIDGET
if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) {
Q_ASSERT(path.constFirst() == 1);
bool actualGroup = false;
#if QT_CONFIG(tabwidget)
if (group && (dockOptions & QMainWindow::GroupedDragging) && path.size() > 3) {
QDockAreaLayoutItem &parentItem = layoutState.dockAreaLayout.item(path.mid(1, path.size() - 2));
if (parentItem.subinfo && parentItem.subinfo->tabbed) {
// The QDockWidget is part of a group of tab and we need to unplug them all.
actualGroup = true;
path.removeLast();
QDockWidgetGroupWindow* floatingTabs = createTabbedDockWindow();
QDockAreaLayoutInfo* info = floatingTabs->layoutInfo();
*info = qMove(*parentItem.subinfo);
delete parentItem.subinfo;
parentItem.subinfo = 0;
floatingTabs->setGeometry(info->rect.translated(parentWidget()->pos()));
floatingTabs->show();
floatingTabs->raise();
item = new QDockWidgetGroupWindowItem(floatingTabs);
parentItem.widgetItem = item;
savedState = layoutState;
}
}
if (group && (dockOptions & QMainWindow::GroupedDragging) && path.size() > 3
&& unplugGroup(this, &item,
layoutState.dockAreaLayout.item(path.mid(1, path.size() - 2)))) {
path.removeLast();
savedState = layoutState;
} else
#endif // QT_CONFIG(tabwidget)
if (!actualGroup) {
{
dw->d_func()->unplug(r);
}
}
@ -2315,11 +2461,11 @@ QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group)
void QMainWindowLayout::updateGapIndicator()
{
#ifndef QT_NO_RUBBERBAND
if ((!widgetAnimator.animating() && !currentGapPos.isEmpty())
if (!widgetAnimator.animating() && (!currentGapPos.isEmpty()
#if QT_CONFIG(dockwidget)
|| currentHoveredFloat
|| currentHoveredFloat
#endif
) {
)) {
QWidget *expectedParent =
#if QT_CONFIG(dockwidget)
currentHoveredFloat ? currentHoveredFloat.data() :
@ -2332,11 +2478,13 @@ void QMainWindowLayout::updateGapIndicator()
} else if (gapIndicator->parent() != expectedParent) {
gapIndicator->setParent(expectedParent);
}
gapIndicator->setGeometry(
#if QT_CONFIG(dockwidget)
currentHoveredFloat ? currentHoveredFloat->rect() :
if (currentHoveredFloat)
gapIndicator->setGeometry(currentHoveredFloat->currentGapRect);
else
#endif
currentGapRect);
gapIndicator->setGeometry(currentGapRect);
gapIndicator->show();
gapIndicator->raise();
} else if (gapIndicator) {
@ -2385,8 +2533,31 @@ void QMainWindowLayout::hover(QLayoutItem *widgetItem, const QPoint &mousePos)
if (!w->geometry().contains(mousePos))
continue;
setCurrentHoveredFloat(w);
restore(true);
if (auto dropTo = qobject_cast<QDockWidget *>(w)) {
// dropping to a normal widget, we mutate it in a QDockWidgetGroupWindow with two
// tabs
QDockWidgetGroupWindow *floatingTabs = createTabbedDockWindow(); // FIXME
floatingTabs->setGeometry(dropTo->geometry());
QDockAreaLayoutInfo *info = floatingTabs->layoutInfo();
*info = QDockAreaLayoutInfo(&layoutState.dockAreaLayout.sep, QInternal::LeftDock,
Qt::Horizontal, QTabBar::RoundedSouth,
static_cast<QMainWindow *>(parentWidget()));
info->tabbed = true;
QLayout *parentLayout = dropTo->parentWidget()->layout();
info->item_list.append(
QDockAreaLayoutItem(parentLayout->takeAt(parentLayout->indexOf(dropTo))));
dropTo->setParent(floatingTabs);
dropTo->show();
dropTo->d_func()->plug(QRect());
w = floatingTabs;
}
Q_ASSERT(qobject_cast<QDockWidgetGroupWindow *>(w));
auto group = static_cast<QDockWidgetGroupWindow *>(w);
if (group->hover(widgetItem, group->mapFromGlobal(mousePos))) {
setCurrentHoveredFloat(group);
applyState(layoutState); // update the tabbars
}
return;
}
}
@ -2454,7 +2625,7 @@ void QMainWindowLayout::hover(QLayoutItem *widgetItem, const QPoint &mousePos)
#ifndef QT_NO_DOCKWIDGET
parentWidget()->update(layoutState.dockAreaLayout.separatorRegion());
#endif
layoutState = newState;
layoutState = std::move(newState);
applyState(layoutState);
updateGapIndicator();

View File

@ -327,11 +327,19 @@ public:
explicit QDockWidgetGroupWindow(QWidget* parent = 0, Qt::WindowFlags f = 0)
: QWidget(parent, f) {}
QDockAreaLayoutInfo *layoutInfo() const;
QDockWidget *topDockWidget() const;
const QDockAreaLayoutInfo *tabLayoutInfo() const;
QDockWidget *activeTabbedDockWidget() const;
void destroyOrHideIfEmpty();
void adjustFlags();
bool hasNativeDecos() const;
bool hover(QLayoutItem *widgetItem, const QPoint &mousePos);
void restore();
void apply();
QRect currentGapRect;
QList<int> currentGapPos;
protected:
bool event(QEvent *) Q_DECL_OVERRIDE;
void paintEvent(QPaintEvent*) Q_DECL_OVERRIDE;
@ -543,8 +551,8 @@ public:
QPointer<QRubberBand> gapIndicator;
#endif
#ifndef QT_NO_DOCKWIDGET
QPointer<QWidget> currentHoveredFloat; // set when dragging over a floating dock widget
void setCurrentHoveredFloat(QWidget *w);
QPointer<QDockWidgetGroupWindow> currentHoveredFloat; // set when dragging over a floating dock widget
void setCurrentHoveredFloat(QDockWidgetGroupWindow *w);
#endif
void hover(QLayoutItem *widgetItem, const QPoint &mousePos);