2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2020 Uwe Kindler
|
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
|
2020-01-24 17:13:32 +01:00
|
|
|
|
|
|
|
|
#include "dockareatabbar.h"
|
|
|
|
|
|
|
|
|
|
#include "dockareawidget.h"
|
|
|
|
|
#include "dockwidget.h"
|
|
|
|
|
#include "dockwidgettab.h"
|
|
|
|
|
|
|
|
|
|
#include <QApplication>
|
|
|
|
|
#include <QBoxLayout>
|
|
|
|
|
#include <QLoggingCategory>
|
|
|
|
|
#include <QMouseEvent>
|
|
|
|
|
#include <QScrollBar>
|
|
|
|
|
#include <QtGlobal>
|
|
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
2020-02-21 09:27:28 +01:00
|
|
|
static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg)
|
2020-01-24 17:13:32 +01:00
|
|
|
|
|
|
|
|
namespace ADS
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Private data class of DockAreaTabBar class (pimpl)
|
|
|
|
|
*/
|
2020-03-02 10:46:55 +01:00
|
|
|
class DockAreaTabBarPrivate
|
2020-01-24 17:13:32 +01:00
|
|
|
{
|
2020-03-02 10:46:55 +01:00
|
|
|
public:
|
2020-01-24 17:13:32 +01:00
|
|
|
DockAreaTabBar *q;
|
2020-03-02 10:48:51 +01:00
|
|
|
DockAreaWidget *m_dockArea = nullptr;
|
|
|
|
|
QWidget *m_tabsContainerWidget = nullptr;
|
|
|
|
|
QBoxLayout *m_tabsLayout = nullptr;
|
2020-01-24 17:13:32 +01:00
|
|
|
int m_currentIndex = -1;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Private data constructor
|
|
|
|
|
*/
|
|
|
|
|
DockAreaTabBarPrivate(DockAreaTabBar *parent);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update tabs after current index changed or when tabs are removed.
|
|
|
|
|
* The function reassigns the stylesheet to update the tabs
|
|
|
|
|
*/
|
|
|
|
|
void updateTabs();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convenience function to access first tab
|
|
|
|
|
*/
|
2020-06-22 16:46:25 +02:00
|
|
|
DockWidgetTab *firstTab() const { return q->tab(0); }
|
2020-01-24 17:13:32 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convenience function to access last tab
|
|
|
|
|
*/
|
2020-06-22 16:46:25 +02:00
|
|
|
DockWidgetTab *lastTab() const { return q->tab(q->count() - 1); }
|
2020-03-02 10:46:55 +01:00
|
|
|
}; // class DockAreaTabBarPrivate
|
2020-01-24 17:13:32 +01:00
|
|
|
|
|
|
|
|
DockAreaTabBarPrivate::DockAreaTabBarPrivate(DockAreaTabBar *parent)
|
|
|
|
|
: q(parent)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
void DockAreaTabBarPrivate::updateTabs()
|
|
|
|
|
{
|
|
|
|
|
// Set active TAB and update all other tabs to be inactive
|
|
|
|
|
for (int i = 0; i < q->count(); ++i) {
|
|
|
|
|
auto tabWidget = q->tab(i);
|
|
|
|
|
if (!tabWidget)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (i == m_currentIndex) {
|
|
|
|
|
tabWidget->show();
|
|
|
|
|
tabWidget->setActiveTab(true);
|
|
|
|
|
q->ensureWidgetVisible(tabWidget);
|
|
|
|
|
} else {
|
|
|
|
|
tabWidget->setActiveTab(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DockAreaTabBar::DockAreaTabBar(DockAreaWidget *parent)
|
|
|
|
|
: QScrollArea(parent)
|
|
|
|
|
, d(new DockAreaTabBarPrivate(this))
|
|
|
|
|
{
|
|
|
|
|
d->m_dockArea = parent;
|
|
|
|
|
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
|
|
|
|
setFrameStyle(QFrame::NoFrame);
|
|
|
|
|
setWidgetResizable(true);
|
|
|
|
|
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
|
|
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
|
|
|
|
|
|
|
|
d->m_tabsContainerWidget = new QWidget();
|
|
|
|
|
d->m_tabsContainerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
|
|
|
|
d->m_tabsContainerWidget->setObjectName("tabsContainerWidget");
|
|
|
|
|
d->m_tabsLayout = new QBoxLayout(QBoxLayout::LeftToRight);
|
|
|
|
|
d->m_tabsLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
d->m_tabsLayout->setSpacing(0);
|
|
|
|
|
d->m_tabsLayout->addStretch(1);
|
|
|
|
|
d->m_tabsContainerWidget->setLayout(d->m_tabsLayout);
|
|
|
|
|
setWidget(d->m_tabsContainerWidget);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DockAreaTabBar::~DockAreaTabBar() { delete d; }
|
|
|
|
|
|
|
|
|
|
void DockAreaTabBar::wheelEvent(QWheelEvent *event)
|
|
|
|
|
{
|
|
|
|
|
event->accept();
|
|
|
|
|
const int direction = event->angleDelta().y();
|
2020-06-22 16:46:25 +02:00
|
|
|
if (direction < 0)
|
2020-01-24 17:13:32 +01:00
|
|
|
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 20);
|
2020-06-22 16:46:25 +02:00
|
|
|
else
|
2020-01-24 17:13:32 +01:00
|
|
|
horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 20);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DockAreaTabBar::setCurrentIndex(int index)
|
|
|
|
|
{
|
|
|
|
|
if (index == d->m_currentIndex)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (index < -1 || index > (count() - 1)) {
|
|
|
|
|
qWarning() << Q_FUNC_INFO << "Invalid index" << index;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit currentChanging(index);
|
|
|
|
|
d->m_currentIndex = index;
|
|
|
|
|
d->updateTabs();
|
|
|
|
|
updateGeometry();
|
|
|
|
|
emit currentChanged(index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int DockAreaTabBar::count() const
|
|
|
|
|
{
|
|
|
|
|
// The tab bar contains a stretch item as last item
|
|
|
|
|
return d->m_tabsLayout->count() - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DockAreaTabBar::insertTab(int index, DockWidgetTab *dockWidgetTab)
|
|
|
|
|
{
|
|
|
|
|
d->m_tabsLayout->insertWidget(index, dockWidgetTab);
|
2022-07-20 18:18:44 +02:00
|
|
|
connect(dockWidgetTab, &DockWidgetTab::clicked,
|
|
|
|
|
this, [this, dockWidgetTab] { onTabClicked(dockWidgetTab); });
|
|
|
|
|
connect(dockWidgetTab, &DockWidgetTab::closeRequested,
|
|
|
|
|
this, [this, dockWidgetTab] { onTabCloseRequested(dockWidgetTab); });
|
|
|
|
|
connect(dockWidgetTab, &DockWidgetTab::closeOtherTabsRequested,
|
|
|
|
|
this, [this, dockWidgetTab] { onCloseOtherTabsRequested(dockWidgetTab); });
|
|
|
|
|
connect(dockWidgetTab, &DockWidgetTab::moved,
|
|
|
|
|
this, [this, dockWidgetTab](const QPoint &globalPosition) {
|
|
|
|
|
onTabWidgetMoved(dockWidgetTab, globalPosition);
|
|
|
|
|
});
|
|
|
|
|
connect(dockWidgetTab, &DockWidgetTab::elidedChanged,
|
|
|
|
|
this, &DockAreaTabBar::elidedChanged);
|
2020-01-24 17:13:32 +01:00
|
|
|
dockWidgetTab->installEventFilter(this);
|
|
|
|
|
emit tabInserted(index);
|
2020-06-22 16:46:25 +02:00
|
|
|
if (index <= d->m_currentIndex)
|
2020-01-24 17:13:32 +01:00
|
|
|
setCurrentIndex(d->m_currentIndex + 1);
|
2020-06-22 16:46:25 +02:00
|
|
|
else if (d->m_currentIndex == -1)
|
|
|
|
|
setCurrentIndex(index);
|
|
|
|
|
|
2020-01-24 17:13:32 +01:00
|
|
|
updateGeometry();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DockAreaTabBar::removeTab(DockWidgetTab *dockWidgetTab)
|
|
|
|
|
{
|
|
|
|
|
if (!count())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
qCInfo(adsLog) << Q_FUNC_INFO;
|
|
|
|
|
int newCurrentIndex = currentIndex();
|
|
|
|
|
int removeIndex = d->m_tabsLayout->indexOf(dockWidgetTab);
|
|
|
|
|
if (count() == 1)
|
|
|
|
|
newCurrentIndex = -1;
|
|
|
|
|
|
|
|
|
|
if (newCurrentIndex > removeIndex) {
|
|
|
|
|
newCurrentIndex--;
|
|
|
|
|
} else if (newCurrentIndex == removeIndex) {
|
|
|
|
|
newCurrentIndex = -1;
|
|
|
|
|
// First we walk to the right to search for the next visible tab
|
|
|
|
|
for (int i = (removeIndex + 1); i < count(); ++i) {
|
|
|
|
|
if (tab(i)->isVisibleTo(this)) {
|
|
|
|
|
newCurrentIndex = i - 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If there is no visible tab right to this tab then we walk to
|
|
|
|
|
// the left to find a visible tab
|
|
|
|
|
if (newCurrentIndex < 0) {
|
|
|
|
|
for (int i = (removeIndex - 1); i >= 0; --i) {
|
|
|
|
|
if (tab(i)->isVisibleTo(this)) {
|
|
|
|
|
newCurrentIndex = i;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit removingTab(removeIndex);
|
|
|
|
|
d->m_tabsLayout->removeWidget(dockWidgetTab);
|
|
|
|
|
dockWidgetTab->disconnect(this);
|
|
|
|
|
dockWidgetTab->removeEventFilter(this);
|
|
|
|
|
qCInfo(adsLog) << "NewCurrentIndex " << newCurrentIndex;
|
2020-06-22 16:46:25 +02:00
|
|
|
if (newCurrentIndex != d->m_currentIndex)
|
2020-01-24 17:13:32 +01:00
|
|
|
setCurrentIndex(newCurrentIndex);
|
2020-06-22 16:46:25 +02:00
|
|
|
else
|
2020-01-24 17:13:32 +01:00
|
|
|
d->updateTabs();
|
2020-06-22 16:46:25 +02:00
|
|
|
|
2020-01-24 17:13:32 +01:00
|
|
|
updateGeometry();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int DockAreaTabBar::currentIndex() const { return d->m_currentIndex; }
|
|
|
|
|
|
|
|
|
|
DockWidgetTab *DockAreaTabBar::currentTab() const
|
|
|
|
|
{
|
2020-06-22 16:46:25 +02:00
|
|
|
if (d->m_currentIndex < 0)
|
2020-01-24 17:13:32 +01:00
|
|
|
return nullptr;
|
2020-06-22 16:46:25 +02:00
|
|
|
else
|
2020-01-24 17:13:32 +01:00
|
|
|
return qobject_cast<DockWidgetTab *>(
|
|
|
|
|
d->m_tabsLayout->itemAt(d->m_currentIndex)->widget());
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-20 18:18:44 +02:00
|
|
|
void DockAreaTabBar::onTabClicked(DockWidgetTab *sourceTab)
|
2020-01-24 17:13:32 +01:00
|
|
|
{
|
2022-07-20 18:18:44 +02:00
|
|
|
const int index = d->m_tabsLayout->indexOf(sourceTab);
|
2020-01-24 17:13:32 +01:00
|
|
|
if (index < 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
setCurrentIndex(index);
|
|
|
|
|
emit tabBarClicked(index);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-20 18:18:44 +02:00
|
|
|
void DockAreaTabBar::onTabCloseRequested(DockWidgetTab *sourceTab)
|
2020-01-24 17:13:32 +01:00
|
|
|
{
|
2022-07-20 18:18:44 +02:00
|
|
|
const int index = d->m_tabsLayout->indexOf(sourceTab);
|
2020-01-24 17:13:32 +01:00
|
|
|
closeTab(index);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-20 18:18:44 +02:00
|
|
|
void DockAreaTabBar::onCloseOtherTabsRequested(DockWidgetTab *sourceTab)
|
2020-01-24 17:13:32 +01:00
|
|
|
{
|
|
|
|
|
for (int i = 0; i < count(); ++i) {
|
|
|
|
|
auto currentTab = tab(i);
|
2022-07-20 18:18:44 +02:00
|
|
|
if (currentTab->isClosable() && !currentTab->isHidden() && currentTab != sourceTab) {
|
2020-01-24 17:13:32 +01:00
|
|
|
// If the dock widget is deleted with the closeTab() call, its tab it will no longer
|
|
|
|
|
// be in the layout, and thus the index needs to be updated to not skip any tabs
|
|
|
|
|
int offset = currentTab->dockWidget()->features().testFlag(
|
|
|
|
|
DockWidget::DockWidgetDeleteOnClose)
|
|
|
|
|
? 1
|
|
|
|
|
: 0;
|
|
|
|
|
closeTab(i);
|
|
|
|
|
// If the the dock widget blocks closing, i.e. if the flag
|
|
|
|
|
// CustomCloseHandling is set, and the dock widget is still open,
|
|
|
|
|
// then we do not need to correct the index
|
2020-06-22 16:46:25 +02:00
|
|
|
if (currentTab->dockWidget()->isClosed())
|
2020-01-24 17:13:32 +01:00
|
|
|
i -= offset;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DockWidgetTab *DockAreaTabBar::tab(int index) const
|
|
|
|
|
{
|
|
|
|
|
if (index >= count() || index < 0)
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
|
|
return qobject_cast<DockWidgetTab *>(d->m_tabsLayout->itemAt(index)->widget());
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-20 18:18:44 +02:00
|
|
|
void DockAreaTabBar::onTabWidgetMoved(DockWidgetTab *sourceTab, const QPoint &globalPosition)
|
2020-01-24 17:13:32 +01:00
|
|
|
{
|
2022-07-20 18:18:44 +02:00
|
|
|
const int fromIndex = d->m_tabsLayout->indexOf(sourceTab);
|
2020-01-24 17:13:32 +01:00
|
|
|
auto mousePos = mapFromGlobal(globalPosition);
|
|
|
|
|
mousePos.rx() = qMax(d->firstTab()->geometry().left(), mousePos.x());
|
|
|
|
|
mousePos.rx() = qMin(d->lastTab()->geometry().right(), mousePos.x());
|
|
|
|
|
int toIndex = -1;
|
|
|
|
|
// Find tab under mouse
|
|
|
|
|
for (int i = 0; i < count(); ++i) {
|
|
|
|
|
DockWidgetTab *dropTab = tab(i);
|
2022-07-20 18:18:44 +02:00
|
|
|
if (dropTab == sourceTab || !dropTab->isVisibleTo(this)
|
2020-01-24 17:13:32 +01:00
|
|
|
|| !dropTab->geometry().contains(mousePos))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
toIndex = d->m_tabsLayout->indexOf(dropTab);
|
|
|
|
|
if (toIndex == fromIndex)
|
|
|
|
|
toIndex = -1;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (toIndex > -1) {
|
2022-07-20 18:18:44 +02:00
|
|
|
d->m_tabsLayout->removeWidget(sourceTab);
|
|
|
|
|
d->m_tabsLayout->insertWidget(toIndex, sourceTab);
|
2020-01-24 17:13:32 +01:00
|
|
|
qCInfo(adsLog) << "tabMoved from" << fromIndex << "to" << toIndex;
|
|
|
|
|
emit tabMoved(fromIndex, toIndex);
|
|
|
|
|
setCurrentIndex(toIndex);
|
|
|
|
|
} else {
|
|
|
|
|
// Ensure that the moved tab is reset to its start position
|
|
|
|
|
d->m_tabsLayout->update();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DockAreaTabBar::closeTab(int index)
|
|
|
|
|
{
|
|
|
|
|
if (index < 0 || index >= count())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto dockWidgetTab = tab(index);
|
|
|
|
|
if (dockWidgetTab->isHidden())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
emit tabCloseRequested(index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DockAreaTabBar::eventFilter(QObject *watched, QEvent *event)
|
|
|
|
|
{
|
|
|
|
|
bool result = Super::eventFilter(watched, event);
|
|
|
|
|
DockWidgetTab *dockWidgetTab = qobject_cast<DockWidgetTab *>(watched);
|
|
|
|
|
if (!dockWidgetTab)
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
|
|
switch (event->type()) {
|
|
|
|
|
case QEvent::Hide:
|
|
|
|
|
emit tabClosed(d->m_tabsLayout->indexOf(dockWidgetTab));
|
|
|
|
|
updateGeometry();
|
|
|
|
|
break;
|
|
|
|
|
case QEvent::Show:
|
|
|
|
|
emit tabOpened(d->m_tabsLayout->indexOf(dockWidgetTab));
|
|
|
|
|
updateGeometry();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DockAreaTabBar::isTabOpen(int index) const
|
|
|
|
|
{
|
|
|
|
|
if (index < 0 || index >= count())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return !tab(index)->isHidden();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QSize DockAreaTabBar::minimumSizeHint() const
|
|
|
|
|
{
|
|
|
|
|
QSize size = sizeHint();
|
|
|
|
|
size.setWidth(10);
|
|
|
|
|
return size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QSize DockAreaTabBar::sizeHint() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_tabsContainerWidget->sizeHint();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace ADS
|