Fixed docking into empty main dock container, fixed tab handling to properly show the right dock widget tab when removing a dock widget, fixed tab menu to only show visible tabs, tab menu is now dynamically created just befor menu is shown

This commit is contained in:
Uwe Kindler 2018-09-07 11:10:14 +02:00
parent 72ee4a53df
commit 67199a81f4
12 changed files with 316 additions and 49 deletions

View File

@ -18,6 +18,7 @@
#include "DockAreaWidget.h"
#include "DockOverlay.h"
#include "DockManager.h"
#include "DockWidget.h"
namespace ads
{
@ -166,6 +167,10 @@ void CDockAreaTabBar::startFloating(const QPoint& Pos)
CFloatingDockContainer* FloatingWidget = new CFloatingDockContainer(d->DockArea);
FloatingWidget->startFloating(Pos, Size);
d->FloatingWidget = FloatingWidget;
if (d->FloatingWidget->hasSingleDockWidget())
{
emit d->FloatingWidget->firstDockWidget()->topLevelChanged(true);
}
}
} // namespace ads

View File

@ -41,6 +41,7 @@
#include <QMenu>
#include <QSplitter>
#include <QXmlStreamWriter>
#include <QVector>
#include "DockContainerWidget.h"
@ -50,11 +51,14 @@
#include "DockOverlay.h"
#include "DockAreaTabBar.h"
#include <iostream>
namespace ads
{
static const char* const INDEX_PROPERTY = "index";
static const char* const ACTION_PROPERTY = "action";
static const char* const DOCKWIDGET_PROPERTY = "dockwidget";
static const int APPEND = -1;
@ -75,6 +79,8 @@ struct DockAreaWidgetPrivate
QPushButton* CloseButton;
int TabsLayoutInitCount;
CDockManager* DockManager = nullptr;
QVector<CDockWidget*> OpenDockWidgets;
bool MenuOutdated = true;
/**
* Private data constructor
@ -99,7 +105,7 @@ struct DockAreaWidgetPrivate
*/
CDockWidgetTab* titleWidgetAt(int index)
{
return dockWidgetAt(index)->titleBar();
return dockWidgetAt(index)->tabWidget();
}
/**
@ -130,6 +136,11 @@ struct DockAreaWidgetPrivate
* Update the tabs menu if dock widget order changed or if dock widget has
* been removed
*/
void markTabsMenuOutdated();
/**
* Updates the tabs menu if it is outdated
*/
void updateTabsMenu();
/**
@ -178,7 +189,9 @@ void DockAreaWidgetPrivate::createTabBar()
TabsMenuButton->setFlat(true);
TabsMenuButton->setIcon(_this->style()->standardIcon(QStyle::SP_TitleBarUnshadeButton));
TabsMenuButton->setMaximumWidth(TabsMenuButton->iconSize().width());
TabsMenuButton->setMenu(new QMenu(TabsMenuButton));
QMenu* TabsMenu = new QMenu(TabsMenuButton);
_this->connect(TabsMenu, SIGNAL(aboutToShow()), SLOT(onTabsMenuAboutToShow()));
TabsMenuButton->setMenu(TabsMenu);
TopLayout->addWidget(TabsMenuButton, 0);
TabsMenuButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
_this->connect(TabsMenuButton->menu(), SIGNAL(triggered(QAction*)),
@ -232,20 +245,39 @@ void DockAreaWidgetPrivate::addTabsMenuEntry(CDockWidget* DockWidget,
{
Action = menu->addAction(DockWidget->icon(), DockWidget->windowTitle());
}
Action->setProperty(DOCKWIDGET_PROPERTY, QVariant::fromValue(DockWidget));
QVariant vAction = QVariant::fromValue(Action);
DockWidget->setProperty(ACTION_PROPERTY, vAction);
}
//============================================================================
void DockAreaWidgetPrivate::markTabsMenuOutdated()
{
MenuOutdated = true;
}
//============================================================================
void DockAreaWidgetPrivate::updateTabsMenu()
{
if (!MenuOutdated)
{
return;
}
QMenu* menu = TabsMenuButton->menu();
menu->clear();
for (int i = 0; i < ContentsLayout->count(); ++i)
{
if (dockWidgetAt(i)->isClosed())
{
continue;
}
addTabsMenuEntry(dockWidgetAt(i), APPEND, menu);
}
MenuOutdated = false;
}
@ -313,17 +345,16 @@ void CDockAreaWidget::insertDockWidget(int index, CDockWidget* DockWidget,
bool Activate)
{
d->ContentsLayout->insertWidget(index, DockWidget);
DockWidget->titleBar()->setDockAreaWidget(this);
auto TitleBar = DockWidget->titleBar();
d->TabsLayout->insertWidget(index, TitleBar);
TitleBar->show();
connect(TitleBar, SIGNAL(clicked()), this, SLOT(onDockWidgetTitleClicked()));
DockWidget->tabWidget()->setDockAreaWidget(this);
auto TabWidget = DockWidget->tabWidget();
d->TabsLayout->insertWidget(index, TabWidget);
TabWidget->show();
connect(TabWidget, SIGNAL(clicked()), this, SLOT(onDockWidgetTitleClicked()));
DockWidget->setProperty(INDEX_PROPERTY, index);
if (Activate)
{
setCurrentIndex(index);
}
d->addTabsMenuEntry(DockWidget, index);
DockWidget->setDockArea(this);
}
@ -332,13 +363,18 @@ void CDockAreaWidget::insertDockWidget(int index, CDockWidget* DockWidget,
void CDockAreaWidget::removeDockWidget(CDockWidget* DockWidget)
{
qDebug() << "CDockAreaWidget::removeDockWidget";
auto NextDockWidget = nextOpenDockWidget(DockWidget);
d->ContentsLayout->removeWidget(DockWidget);
auto TitleBar = DockWidget->titleBar();
auto TitleBar = DockWidget->tabWidget();
TitleBar->hide();
d->TabsLayout->removeWidget(TitleBar);
disconnect(TitleBar, SIGNAL(clicked()), this, SLOT(onDockWidgetTitleClicked()));
setCurrentIndex(d->ContentsLayout->currentIndex());
d->updateTabsMenu();
if (NextDockWidget)
{
setCurrentDockWidget(NextDockWidget);
d->markTabsMenuOutdated();
}
CDockContainerWidget* DockContainer = dockContainer();
if (d->ContentsLayout->isEmpty())
@ -550,7 +586,6 @@ void CDockAreaWidget::reorderDockWidget(int fromIndex, int toIndex)
Menu->insertAction(Menu->actions().at(toIndex), TabsAction);
}
// now reorder contents and title bars
QLayoutItem* liFrom = nullptr;
liFrom = d->TabsLayout->takeAt(fromIndex);
@ -561,11 +596,21 @@ void CDockAreaWidget::reorderDockWidget(int fromIndex, int toIndex)
}
//============================================================================
void CDockAreaWidget::toggleDockWidgetView(CDockWidget* DockWidget, bool Open)
{
Q_UNUSED(DockWidget);
Q_UNUSED(Open);
d->markTabsMenuOutdated();
}
//============================================================================
void CDockAreaWidget::onTabsMenuActionTriggered(QAction* Action)
{
int Index = d->TabsMenuButton->menu()->actions().indexOf(Action);
setCurrentIndex(Index);
QVariant vDockWidget = Action->property(DOCKWIDGET_PROPERTY);
CDockWidget* DockWidget = vDockWidget.value<CDockWidget*>();
setCurrentDockWidget(DockWidget);
}
@ -591,6 +636,40 @@ void CDockAreaWidget::saveState(QXmlStreamWriter& s) const
s.writeEndElement();
}
//============================================================================
CDockWidget* CDockAreaWidget::nextOpenDockWidget(CDockWidget* DockWidget) const
{
auto OpenDockWidgets = openedDockWidgets();
if (OpenDockWidgets.count() > 1)
{
CDockWidget* NextDockWidget;
if (OpenDockWidgets.last() == DockWidget)
{
NextDockWidget = OpenDockWidgets[OpenDockWidgets.count() - 2];
}
else
{
int NextIndex = OpenDockWidgets.indexOf(DockWidget) + 1;
NextDockWidget = OpenDockWidgets[NextIndex];
}
return NextDockWidget;
}
else
{
return nullptr;
}
}
//============================================================================
void CDockAreaWidget::onTabsMenuAboutToShow()
{
std::cout << "CDockAreaWidget::onTabsMenuAboutToShow()" << std::endl;
d->updateTabsMenu();
}
} // namespace ads
//---------------------------------------------------------------------------

View File

@ -59,11 +59,14 @@ private:
friend class CDockContainerWidget;
friend class DockContainerWidgetPrivate;
friend class CDockWidgetTab;
friend struct DockWidgetPrivate;
friend class CDockWidget;
private slots:
void onDockWidgetTitleClicked();
void onTabsMenuActionTriggered(QAction* Action);
void onCloseButtonClicked();
void onTabsMenuAboutToShow();
protected:
/**
@ -97,6 +100,18 @@ protected:
*/
void reorderDockWidget(int fromIndex, int toIndex);
/**
* Called from dock widget if it is opened or closed
*/
void toggleDockWidgetView(CDockWidget* DockWidget, bool Open);
/**
* This is a helper function to get the next open dock widget to activate
* if the given DockWidget will be closed or removed.
* The function returns the next widget that should be activated or
* nullptr in case there are no more open widgets in this area.
*/
CDockWidget* nextOpenDockWidget(CDockWidget* DockWidget) const;
public:
/**
@ -178,7 +193,9 @@ public:
public slots:
/**
* This sets the index position of the current tab page.
* This activates the tab for the given tab index.
* If the dock widget for the given tab is not visible, the this function
* call will make it visible.
*/
void setCurrentIndex(int index);

View File

@ -54,6 +54,26 @@ namespace ads
{
static unsigned int zOrderCounter = 0;
/**
* Converts dock area ID to an index for array access
*/
static int areaIdToIndex(DockWidgetArea area)
{
switch (area)
{
case LeftDockWidgetArea: return 0;
case RightDockWidgetArea: return 1;
case TopDockWidgetArea: return 2;
case BottomDockWidgetArea: return 3;
case CenterDockWidgetArea: return 4;
default:
return 4;
}
return 4;
}
/**
* Helper function to ease insertion of dock area into splitter
*/
@ -81,6 +101,7 @@ struct DockContainerWidgetPrivate
QGridLayout* Layout = nullptr;
QSplitter* RootSplitter;
bool isFloating = false;
CDockAreaWidget* LastAddedAreaCache[5]{0, 0, 0, 0, 0};
/**
* Private data constructor
@ -172,7 +193,6 @@ void DockContainerWidgetPrivate::dropIntoContainer(CFloatingDockContainer* Float
auto InsertParam = internal::dockAreaInsertParameters(area);
auto NewDockAreas = FloatingWidget->dockContainer()->findChildren<CDockAreaWidget*>(
QString(), Qt::FindChildrenRecursively);
CDockWidget* DockWidget = FloatingWidget->dockContainer()->findChild<CDockWidget*>();
QSplitter* Splitter = RootSplitter;
if (DockAreas.count() <= 1)
@ -209,9 +229,12 @@ void DockContainerWidgetPrivate::dropIntoContainer(CFloatingDockContainer* Float
RootSplitter = Splitter;
addDockAreasToList(NewDockAreas);
FloatingWidget->deleteLater();
if (DockWidget)
// If we dropped the floating widget into the main dock container that does
// not contain any dock widgets, then splitter is invisible and we need to
// show it to display the docked widgets
if (!Splitter->isVisible())
{
DockWidget->toggleView(true);
Splitter->show();
}
_this->dumpLayout();
}
@ -503,6 +526,8 @@ bool DockContainerWidgetPrivate::restoreDockArea(QXmlStreamReader& s,
qDebug() << "Dock Widget found - parent " << DockWidget->parent();
DockArea->addDockWidget(DockWidget);
// We hide the DockArea here to prevent the short display (the flashing)
// of the dock areas during application startup
DockArea->hide();
DockWidget->setToggleViewActionChecked(!Closed);
DockWidget->setProperty("closed", Closed);
@ -565,6 +590,7 @@ CDockAreaWidget* DockContainerWidgetPrivate::dockWidgetIntoContainer(DockWidgetA
CDockAreaWidget* NewDockArea = new CDockAreaWidget(DockManager, _this);
NewDockArea->addDockWidget(Dockwidget);
addDockArea(NewDockArea, area);
LastAddedAreaCache[areaIdToIndex(area)] = NewDockArea;
return NewDockArea;
}
@ -689,7 +715,6 @@ CDockContainerWidget::CDockContainerWidget(CDockManager* DockManager, QWidget *p
{
d->isFloating = dynamic_cast<CFloatingDockContainer*>(parent) != 0;
//setStyleSheet("background: green;");
d->DockManager = DockManager;
if (DockManager != this)
{
@ -905,6 +930,8 @@ void CDockContainerWidget::dropFloatingWidget(CFloatingDockContainer* FloatingWi
CDockAreaWidget* DockArea = dockAreaAt(TargetPos);
auto dropArea = InvalidDockWidgetArea;
auto ContainerDropArea = d->DockManager->containerOverlay()->dropAreaUnderCursor();
CDockWidget* TopLevelDockWidget = FloatingWidget->hasSingleDockWidget() ?
FloatingWidget->firstDockWidget() : nullptr;
if (DockArea)
{
auto dropOverlay = d->DockManager->dockAreaOverlay();
@ -933,6 +960,13 @@ void CDockContainerWidget::dropFloatingWidget(CFloatingDockContainer* FloatingWi
d->dropIntoContainer(FloatingWidget, dropArea);
}
}
// If we drop a floating widget with only one single dock widget, then we
// drop a top level widget that changes from floating to docked now
if (TopLevelDockWidget)
{
emit TopLevelDockWidget->topLevelChanged(false);
}
}
@ -1047,6 +1081,13 @@ void CDockContainerWidget::dumpLayout()
}
//============================================================================
CDockAreaWidget* CDockContainerWidget::lastAddedDockAreaWidget(DockWidgetArea area) const
{
return d->LastAddedAreaCache[areaIdToIndex(area)];
}
} // namespace ads
//---------------------------------------------------------------------------

View File

@ -103,6 +103,13 @@ protected:
*/
bool restoreState(QXmlStreamReader& Stream, bool Testing);
/**
* This function returns the last added dock area widget for the given
* area identifier or 0 if no dock area widget has been added for the given
* area
*/
CDockAreaWidget* lastAddedDockAreaWidget(DockWidgetArea area) const;
public:
/**
* Default Constructor

View File

@ -361,7 +361,8 @@ bool CDockManager::restoreState(const QByteArray &state, int version)
{
CDockAreaWidget* DockArea = DockContainer->dockArea(i);
int CurrentIndex = DockArea->property("currentIndex").toInt();
if (CurrentIndex < DockArea->count() && DockArea->count() > 1 && CurrentIndex > -1)
int OpenDockWidgetCount = DockArea->openedDockWidgets().count();
if (CurrentIndex < OpenDockWidgetCount && OpenDockWidgetCount > 1 && CurrentIndex > -1)
{
DockArea->setCurrentIndex(CurrentIndex);
}
@ -382,6 +383,30 @@ CDockAreaWidget* CDockManager::addDockWidget(DockWidgetArea area,
}
//============================================================================
CDockAreaWidget* CDockManager::addDockWidgetTab(DockWidgetArea area,
CDockWidget* Dockwidget)
{
CDockAreaWidget* AreaWidget = lastAddedDockAreaWidget(area);
if (AreaWidget)
{
return addDockWidget(ads::CenterDockWidgetArea, Dockwidget, AreaWidget);
}
else
{
return addDockWidget(area, Dockwidget, AreaWidget);
}
}
//============================================================================
CDockAreaWidget* CDockManager::addDockWidgetTabToArea(CDockWidget* Dockwidget,
CDockAreaWidget* DockAreaWidget)
{
return addDockWidget(ads::CenterDockWidgetArea, Dockwidget, DockAreaWidget);
}
//============================================================================
CDockWidget* CDockManager::findDockWidget(const QString& ObjectName)
{

View File

@ -128,6 +128,22 @@ public:
CDockAreaWidget* addDockWidget(DockWidgetArea area, CDockWidget* Dockwidget,
CDockAreaWidget* DockAreaWidget = nullptr);
/**
* This function will add the given Dockwidget to the given dock area as
* a new tab.
* If no dock area widget exists for the given area identifier, a new
* dock area widget is created.
*/
CDockAreaWidget* addDockWidgetTab(DockWidgetArea area,
CDockWidget* Dockwidget);
/**
* This function will add the given Dockwidget to the given DockAreaWidget
* as a new tab.
*/
CDockAreaWidget* addDockWidgetTabToArea(CDockWidget* Dockwidget,
CDockAreaWidget* DockAreaWidget);
/**
* Searches for a registered doc widget with the given ObjectName
* \return Return the found dock widget or nullptr if a dock widget with the

View File

@ -59,7 +59,7 @@ struct DockWidgetPrivate
CDockWidget* _this;
QBoxLayout* Layout;
QWidget* Widget = nullptr;
CDockWidgetTab* TitleWidget;
CDockWidgetTab* TabWidget;
CDockWidget::DockWidgetFeatures Features = CDockWidget::AllDockWidgetFeatures;
CDockManager* DockManager = nullptr;
CDockAreaWidget* DockArea = nullptr;
@ -87,9 +87,11 @@ struct DockWidgetPrivate
void hideEmptyParentSplitters();
/**
* Hides a dock area if all dock widgets in the area are closed
* Hides a dock area if all dock widgets in the area are closed.
* This function updates the current selected tab and hides the parent
* dock area if it is empty
*/
void hideEmptyParentDockArea();
void updateParentDockArea();
/**
* Hides a floating widget if all dock areas are empty - that means,
@ -141,8 +143,8 @@ void DockWidgetPrivate::showDockWidget()
//============================================================================
void DockWidgetPrivate::hideDockWidget()
{
TitleWidget->hide();
hideEmptyParentDockArea();
TabWidget->hide();
updateParentDockArea();
hideEmptyParentSplitters();
hideEmptyFloatingWidget();
}
@ -164,22 +166,11 @@ void DockWidgetPrivate::hideEmptyParentSplitters()
//============================================================================
void DockWidgetPrivate::hideEmptyParentDockArea()
void DockWidgetPrivate::updateParentDockArea()
{
auto OpenDockWidgets = DockArea->openedDockWidgets();
if (OpenDockWidgets.count() > 1)
auto NextDockWidget = DockArea->nextOpenDockWidget(_this);
if (NextDockWidget)
{
CDockWidget* NextDockWidget;
if (OpenDockWidgets.last() == _this)
{
NextDockWidget = OpenDockWidgets[OpenDockWidgets.count() - 2];
}
else
{
int NextIndex = OpenDockWidgets.indexOf(_this) + 1;
NextDockWidget = OpenDockWidgets[NextIndex];
}
DockArea->setCurrentDockWidget(NextDockWidget);
}
else
@ -213,7 +204,7 @@ CDockWidget::CDockWidget(const QString &title, QWidget *parent) :
setWindowTitle(title);
setObjectName(title);
d->TitleWidget = new CDockWidgetTab(this);
d->TabWidget = new CDockWidgetTab(this);
d->ToggleViewAction = new QAction(title);
d->ToggleViewAction->setCheckable(true);
connect(d->ToggleViewAction, SIGNAL(triggered(bool)), this,
@ -262,9 +253,9 @@ QWidget* CDockWidget::widget() const
//============================================================================
CDockWidgetTab* CDockWidget::titleBar() const
CDockWidgetTab* CDockWidget::tabWidget() const
{
return d->TitleWidget;
return d->TabWidget;
}
@ -320,7 +311,39 @@ CDockAreaWidget* CDockWidget::dockAreaWidget() const
//============================================================================
bool CDockWidget::isFloating() const
{
return dockContainer() ? dockContainer()->isFloating() : false;
if (!isInFloatingContainer())
{
return false;
}
if (dockContainer()->dockAreaCount() != 1)
{
return false;
}
if (dockContainer()->dockArea(0)->count() != 1)
{
return false;
}
return true;
}
//============================================================================
bool CDockWidget::isInFloatingContainer() const
{
if (!dockContainer())
{
return false;
}
if (!dockContainer()->isFloating())
{
return false;
}
return true;
}
@ -349,7 +372,7 @@ void CDockWidget::setToggleViewActionMode(eToggleViewActionMode Mode)
else
{
d->ToggleViewAction->setCheckable(false);
d->ToggleViewAction->setIcon(d->TitleWidget->icon());
d->ToggleViewAction->setIcon(d->TabWidget->icon());
}
}
@ -375,6 +398,8 @@ void CDockWidget::toggleView(bool Open)
d->ToggleViewAction->blockSignals(true);
d->ToggleViewAction->setChecked(Open);
d->ToggleViewAction->blockSignals(false);
d->DockArea->toggleDockWidgetView(this, Open);
if (!Open)
{
emit closed();
@ -406,7 +431,7 @@ void CDockWidget::flagAsUnassigned()
{
setParent(d->DockManager);
setDockArea(nullptr);
titleBar()->setParent(this);
tabWidget()->setParent(this);
}
@ -424,7 +449,7 @@ bool CDockWidget::event(QEvent *e)
//============================================================================
void CDockWidget::setIcon(const QIcon& Icon)
{
d->TitleWidget->setIcon(Icon);
d->TabWidget->setIcon(Icon);
if (!d->ToggleViewAction->isCheckable())
{
d->ToggleViewAction->setIcon(Icon);
@ -435,7 +460,7 @@ void CDockWidget::setIcon(const QIcon& Icon)
//============================================================================
QIcon CDockWidget::icon() const
{
return d->TitleWidget->icon();
return d->TabWidget->icon();
}

View File

@ -164,7 +164,7 @@ public:
/**
* Returns the title bar widget of this dock widget
*/
CDockWidgetTab* titleBar() const;
CDockWidgetTab* tabWidget() const;
/**
* Sets, whether the dock widget is movable, closable, and floatable.
@ -193,7 +193,7 @@ public:
/**
* Returns the dock container widget this dock area widget belongs to or 0
* if this dock widget has nt been docked yet
* if this dock widget has not been docked yet
*/
CDockContainerWidget* dockContainer() const;
@ -205,9 +205,19 @@ public:
/**
* This property holds whether the dock widget is floating.
* A dock widget is only floating, if it is the one and only widget inside
* of a floating container. If there are more than one dock widget in a
* floating container, the all dock widgets are docked and not floating.
*/
bool isFloating() const;
/**
* This function returns true, if this dock widget is in a floating.
* The function returns true, if the dock widget is floating and it also
* returns true if it is docked inside of a floating container.
*/
bool isInFloatingContainer() const;
/**
* Returns true, if this dock widget is closed.
*/
@ -266,6 +276,13 @@ signals:
* changed
*/
void titleChanged(const QString& Title);
/**
* This signal is emitted when the floating property changes.
* The topLevel parameter is true if the dock widget is now floating;
* otherwise it is false.
*/
void topLevelChanged(bool topLevel);
}; // class DockWidget
}
// namespace ads

View File

@ -196,6 +196,7 @@ bool DockWidgetTabPrivate::startFloating()
auto Overlay = DockWidget->dockManager()->containerOverlay();
Overlay->setAllowedAreas(OuterDockAreas);
this->FloatingWidget = FloatingWidget;
emit DockWidget->topLevelChanged(true);
return true;
}

View File

@ -481,6 +481,25 @@ bool CFloatingDockContainer::restoreState(QXmlStreamReader& Stream, bool Testing
}
//============================================================================
bool CFloatingDockContainer::hasSingleDockWidget() const
{
if (d->DockContainer->dockAreaCount() != 1)
{
return false;
}
return d->DockContainer->dockArea(0)->count() == 1;
}
//============================================================================
CDockWidget* CFloatingDockContainer::firstDockWidget() const
{
return d->DockContainer->dockArea(0)->dockWidget(0);
}
} // namespace ads
//---------------------------------------------------------------------------

View File

@ -132,6 +132,21 @@ public:
* It can be closed, if all dock widgets in all dock areas can be closed
*/
bool isClosable() const;
/**
* This function returns true, if this floating widget has only one single
* dock widget in a single dock area.
* The single dock widget is a real top level floating widget because no
* other widgets are docked.
*/
bool hasSingleDockWidget() const;
/**
* This function returns the first dock widget in the first dock area.
* If the function hasSingleDockWidget() returns true, then this function
* returns this single dock widget.
*/
CDockWidget* firstDockWidget() const;
}; // class FloatingDockContainer
}
// namespace ads