2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2020 Uwe Kindler
|
2023-01-04 08:19:47 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later
|
2020-01-24 17:13:32 +01:00
|
|
|
|
|
|
|
|
#include "dockareatabbar.h"
|
2023-02-22 14:29:18 +01:00
|
|
|
#include "ads_globals_p.h"
|
2020-01-24 17:13:32 +01:00
|
|
|
|
|
|
|
|
#include "dockareawidget.h"
|
|
|
|
|
#include "dockwidget.h"
|
|
|
|
|
#include "dockwidgettab.h"
|
|
|
|
|
|
|
|
|
|
#include <QApplication>
|
|
|
|
|
#include <QBoxLayout>
|
|
|
|
|
#include <QLoggingCategory>
|
|
|
|
|
#include <QMouseEvent>
|
|
|
|
|
#include <QScrollBar>
|
2023-06-23 19:58:25 +02:00
|
|
|
#include <QTimer>
|
2020-01-24 17:13:32 +01:00
|
|
|
#include <QtGlobal>
|
|
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
namespace ADS {
|
|
|
|
|
/**
|
|
|
|
|
* Private data class of DockAreaTabBar class (pimpl)
|
|
|
|
|
*/
|
|
|
|
|
class DockAreaTabBarPrivate
|
2020-01-24 17:13:32 +01:00
|
|
|
{
|
2023-06-23 19:58:25 +02:00
|
|
|
public:
|
|
|
|
|
DockAreaTabBar *q;
|
|
|
|
|
DockAreaWidget *m_dockArea = nullptr;
|
|
|
|
|
QWidget *m_tabsContainerWidget = nullptr;
|
|
|
|
|
QBoxLayout *m_tabsLayout = nullptr;
|
|
|
|
|
int m_currentIndex = -1;
|
|
|
|
|
|
2020-01-24 17:13:32 +01:00
|
|
|
/**
|
2023-06-23 19:58:25 +02:00
|
|
|
* Private data constructor
|
2020-01-24 17:13:32 +01:00
|
|
|
*/
|
2023-06-23 19:58:25 +02:00
|
|
|
DockAreaTabBarPrivate(DockAreaTabBar *parent);
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
/**
|
|
|
|
|
* Update tabs after current index changed or when tabs are removed.
|
|
|
|
|
* The function reassigns the stylesheet to update the tabs.
|
|
|
|
|
*/
|
|
|
|
|
void updateTabs();
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
/**
|
|
|
|
|
* Convenience function to access first tab.
|
|
|
|
|
*/
|
|
|
|
|
DockWidgetTab *firstTab() const { return q->tab(0); }
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
/**
|
|
|
|
|
* Convenience function to access last tab.
|
|
|
|
|
*/
|
|
|
|
|
DockWidgetTab *lastTab() const { return q->tab(q->count() - 1); }
|
|
|
|
|
}; // class DockAreaTabBarPrivate
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
DockAreaTabBarPrivate::DockAreaTabBarPrivate(DockAreaTabBar *parent)
|
|
|
|
|
: q(parent)
|
|
|
|
|
{}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
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);
|
|
|
|
|
// Sometimes the synchronous calculation of the rectangular area fails. Therefore we
|
|
|
|
|
// use QTimer::singleShot here to execute the call within the event loop - see #520.
|
|
|
|
|
QTimer::singleShot(0, q, [&, tabWidget] { q->ensureWidgetVisible(tabWidget); });
|
|
|
|
|
} else {
|
|
|
|
|
tabWidget->setActiveTab(false);
|
2020-01-24 17:13:32 +01:00
|
|
|
}
|
|
|
|
|
}
|
2023-06-23 19:58:25 +02:00
|
|
|
}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
void DockAreaTabBar::wheelEvent(QWheelEvent *event)
|
|
|
|
|
{
|
|
|
|
|
event->accept();
|
|
|
|
|
const int direction = event->angleDelta().y();
|
|
|
|
|
if (direction < 0)
|
|
|
|
|
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 20);
|
|
|
|
|
else
|
|
|
|
|
horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 20);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DockAreaTabBar::setCurrentIndex(int index)
|
|
|
|
|
{
|
|
|
|
|
if (index == d->m_currentIndex)
|
|
|
|
|
return;
|
2020-06-22 16:46:25 +02:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
if (index < -1 || index > (count() - 1)) {
|
|
|
|
|
qWarning() << Q_FUNC_INFO << "Invalid index" << index;
|
|
|
|
|
return;
|
2020-01-24 17:13:32 +01:00
|
|
|
}
|
|
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
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);
|
|
|
|
|
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);
|
|
|
|
|
dockWidgetTab->installEventFilter(this);
|
|
|
|
|
emit tabInserted(index);
|
|
|
|
|
if (index <= d->m_currentIndex)
|
|
|
|
|
setCurrentIndex(d->m_currentIndex + 1);
|
|
|
|
|
else if (d->m_currentIndex == -1)
|
|
|
|
|
setCurrentIndex(index);
|
|
|
|
|
|
|
|
|
|
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;
|
2020-01-24 17:13:32 +01:00
|
|
|
}
|
2023-06-23 19:58:25 +02:00
|
|
|
}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
// 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;
|
2020-01-24 17:13:32 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-06-23 19:58:25 +02:00
|
|
|
}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
emit removingTab(removeIndex);
|
|
|
|
|
d->m_tabsLayout->removeWidget(dockWidgetTab);
|
|
|
|
|
dockWidgetTab->disconnect(this);
|
|
|
|
|
dockWidgetTab->removeEventFilter(this);
|
|
|
|
|
qCInfo(adsLog) << "NewCurrentIndex" << newCurrentIndex;
|
|
|
|
|
if (newCurrentIndex != d->m_currentIndex)
|
|
|
|
|
setCurrentIndex(newCurrentIndex);
|
|
|
|
|
else
|
|
|
|
|
d->updateTabs();
|
2020-06-22 16:46:25 +02:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
updateGeometry();
|
|
|
|
|
}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
int DockAreaTabBar::currentIndex() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_currentIndex;
|
|
|
|
|
}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
DockWidgetTab *DockAreaTabBar::currentTab() const
|
|
|
|
|
{
|
|
|
|
|
if (d->m_currentIndex < 0)
|
|
|
|
|
return nullptr;
|
|
|
|
|
else
|
|
|
|
|
return qobject_cast<DockWidgetTab *>(d->m_tabsLayout->itemAt(d->m_currentIndex)->widget());
|
|
|
|
|
}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
void DockAreaTabBar::onTabClicked(DockWidgetTab *sourceTab)
|
|
|
|
|
{
|
|
|
|
|
const int index = d->m_tabsLayout->indexOf(sourceTab);
|
|
|
|
|
if (index < 0)
|
|
|
|
|
return;
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
setCurrentIndex(index);
|
|
|
|
|
emit tabBarClicked(index);
|
|
|
|
|
}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
void DockAreaTabBar::onTabCloseRequested(DockWidgetTab *sourceTab)
|
|
|
|
|
{
|
|
|
|
|
const int index = d->m_tabsLayout->indexOf(sourceTab);
|
|
|
|
|
closeTab(index);
|
|
|
|
|
}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
void DockAreaTabBar::onCloseOtherTabsRequested(DockWidgetTab *sourceTab)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < count(); ++i) {
|
|
|
|
|
auto currentTab = tab(i);
|
|
|
|
|
if (currentTab->isClosable() && !currentTab->isHidden() && currentTab != sourceTab) {
|
|
|
|
|
// 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.
|
|
|
|
|
if (currentTab->dockWidget()->isClosed())
|
|
|
|
|
i -= offset;
|
2020-01-24 17:13:32 +01:00
|
|
|
}
|
|
|
|
|
}
|
2023-06-23 19:58:25 +02:00
|
|
|
}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
DockWidgetTab *DockAreaTabBar::tab(int index) const
|
|
|
|
|
{
|
|
|
|
|
if (index >= count() || index < 0)
|
|
|
|
|
return nullptr;
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
return qobject_cast<DockWidgetTab *>(d->m_tabsLayout->itemAt(index)->widget());
|
|
|
|
|
}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
void DockAreaTabBar::onTabWidgetMoved(DockWidgetTab *sourceTab, const QPoint &globalPosition)
|
|
|
|
|
{
|
|
|
|
|
const int fromIndex = d->m_tabsLayout->indexOf(sourceTab);
|
|
|
|
|
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);
|
|
|
|
|
if (dropTab == sourceTab || !dropTab->isVisibleTo(this)
|
|
|
|
|
|| !dropTab->geometry().contains(mousePos))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
toIndex = d->m_tabsLayout->indexOf(dropTab);
|
|
|
|
|
if (toIndex == fromIndex)
|
|
|
|
|
toIndex = -1;
|
|
|
|
|
|
|
|
|
|
break;
|
2020-01-24 17:13:32 +01:00
|
|
|
}
|
|
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
if (toIndex > -1) {
|
|
|
|
|
d->m_tabsLayout->removeWidget(sourceTab);
|
|
|
|
|
d->m_tabsLayout->insertWidget(toIndex, sourceTab);
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
void DockAreaTabBar::closeTab(int index)
|
|
|
|
|
{
|
|
|
|
|
if (index < 0 || index >= count())
|
|
|
|
|
return;
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
auto dockWidgetTab = tab(index);
|
|
|
|
|
if (dockWidgetTab->isHidden())
|
|
|
|
|
return;
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
emit tabCloseRequested(index);
|
|
|
|
|
}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
bool DockAreaTabBar::eventFilter(QObject *watched, QEvent *event)
|
|
|
|
|
{
|
|
|
|
|
bool result = Super::eventFilter(watched, event);
|
|
|
|
|
DockWidgetTab *dockWidgetTab = qobject_cast<DockWidgetTab *>(watched);
|
|
|
|
|
if (!dockWidgetTab)
|
2020-01-24 17:13:32 +01:00
|
|
|
return result;
|
2023-06-23 19:58:25 +02:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
// Setting the text of a tab will cause a LayoutRequest event
|
|
|
|
|
case QEvent::LayoutRequest:
|
|
|
|
|
updateGeometry();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
2020-01-24 17:13:32 +01:00
|
|
|
}
|
|
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
return result;
|
|
|
|
|
}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
bool DockAreaTabBar::isTabOpen(int index) const
|
|
|
|
|
{
|
|
|
|
|
if (index < 0 || index >= count())
|
|
|
|
|
return false;
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
return !tab(index)->isHidden();
|
|
|
|
|
}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
2023-06-23 19:58:25 +02:00
|
|
|
QSize DockAreaTabBar::minimumSizeHint() const
|
|
|
|
|
{
|
|
|
|
|
QSize size = sizeHint();
|
|
|
|
|
size.setWidth(10);
|
|
|
|
|
return size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QSize DockAreaTabBar::sizeHint() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_tabsContainerWidget->sizeHint();
|
|
|
|
|
}
|
2020-01-24 17:13:32 +01:00
|
|
|
|
|
|
|
|
} // namespace ADS
|