Allow hiding tabs in QTabWidget / QTabBar

Add 2 methods to set/ask if a tab is hidden.

[ChangeLog][QtWidgets][QTabWidget/QTabBar] Tabs can now be
hidden with setTabVisible

Fixes: QTBUG-63038
Change-Id: I7b07ecdb485e1f6c085d03515ef2b73baae889de
Reviewed-by: Christian Ehrlicher <ch.ehrlicher@gmx.de>
bb10
Jordi Pujol Foyo 2019-03-06 16:46:21 +01:00
parent 23d4c0c34b
commit 556712f511
7 changed files with 308 additions and 25 deletions

View File

@ -207,8 +207,8 @@ void QTabBarPrivate::initBasicStyleOption(QStyleOptionTab *option, int tabIndex)
else
option->selectedPosition = QStyleOptionTab::NotAdjacent;
const bool paintBeginning = (tabIndex == 0) || (dragInProgress && tabIndex == pressedIndex + 1);
const bool paintEnd = (tabIndex == totalTabs - 1) || (dragInProgress && tabIndex == pressedIndex - 1);
const bool paintBeginning = (tabIndex == firstVisible) || (dragInProgress && tabIndex == pressedIndex + 1);
const bool paintEnd = (tabIndex == lastVisible - 1) || (dragInProgress && tabIndex == pressedIndex - 1);
if (paintBeginning) {
if (paintEnd)
option->position = QStyleOptionTab::OnlyOneTab;
@ -459,6 +459,7 @@ void QTabBarPrivate::layoutTabs()
int i;
bool vertTabs = verticalTabs(shape);
int tabChainIndex = 0;
int hiddenTabs = 0;
Qt::Alignment tabAlignment = Qt::Alignment(q->style()->styleHint(QStyle::SH_TabBar_Alignment, nullptr, q));
QVector<QLayoutStruct> tabChain(tabList.count() + 2);
@ -484,7 +485,11 @@ void QTabBarPrivate::layoutTabs()
int minx = 0;
int x = 0;
int maxHeight = 0;
for (i = 0; i < tabList.count(); ++i, ++tabChainIndex) {
for (i = 0; i < tabList.count(); ++i) {
if (!tabList.at(i).visible) {
++hiddenTabs;
continue;
}
QSize sz = q->tabSizeHint(i);
tabList[i].maxRect = QRect(x, 0, sz.width(), sz.height());
x += sz.width();
@ -500,6 +505,7 @@ void QTabBarPrivate::layoutTabs()
if (!expanding)
tabChain[tabChainIndex].maximumSize = tabChain[tabChainIndex].sizeHint;
++tabChainIndex;
}
last = minx;
@ -509,7 +515,11 @@ void QTabBarPrivate::layoutTabs()
int miny = 0;
int y = 0;
int maxWidth = 0;
for (i = 0; i < tabList.count(); ++i, ++tabChainIndex) {
for (i = 0; i < tabList.count(); ++i) {
if (!tabList.at(i).visible) {
++hiddenTabs;
continue;
}
QSize sz = q->tabSizeHint(i);
tabList[i].maxRect = QRect(0, y, sz.width(), sz.height());
y += sz.height();
@ -525,6 +535,7 @@ void QTabBarPrivate::layoutTabs()
if (!expanding)
tabChain[tabChainIndex].maximumSize = tabChain[tabChainIndex].sizeHint;
++tabChainIndex;
}
last = miny;
@ -538,14 +549,20 @@ void QTabBarPrivate::layoutTabs()
&& (tabAlignment != Qt::AlignRight)
&& (tabAlignment != Qt::AlignJustify);
tabChain[tabChainIndex].empty = true;
Q_ASSERT(tabChainIndex == tabChain.count() - 1); // add an assert just to make sure.
Q_ASSERT(tabChainIndex == tabChain.count() - 1 - hiddenTabs); // add an assert just to make sure.
// Do the calculation
qGeomCalc(tabChain, 0, tabChain.count(), 0, qMax(available, last), 0);
// Use the results
hiddenTabs = 0;
for (i = 0; i < tabList.count(); ++i) {
const QLayoutStruct &lstruct = tabChain.at(i + 1);
if (!tabList.at(i).visible) {
tabList[i].rect = QRect();
++hiddenTabs;
continue;
}
const QLayoutStruct &lstruct = tabChain.at(i + 1 - hiddenTabs);
if (!vertTabs)
tabList[i].rect.setRect(lstruct.pos, 0, lstruct.size, maxExtent);
else
@ -975,11 +992,15 @@ int QTabBar::insertTab(int index, const QIcon& icon, const QString &text)
#ifndef QT_NO_SHORTCUT
d->tabList[index].shortcutId = grabShortcut(QKeySequence::mnemonic(text));
#endif
d->firstVisible = qMax(qMin(index, d->firstVisible), 0);
d->lastVisible = qMax(index, d->lastVisible);
d->refresh();
if (d->tabList.count() == 1)
setCurrentIndex(index);
else if (index <= d->currentIndex)
else if (index <= d->currentIndex) {
++d->currentIndex;
++d->lastVisible;
}
if (d->closeButtonOnTabs) {
QStyleOptionTab opt;
@ -1035,6 +1056,9 @@ void QTabBar::removeTab(int index)
if (d->tabList[i].lastTab > index)
--d->tabList[i].lastTab;
}
d->calculateFirstLastVisible(index, false, true);
if (index == d->currentIndex) {
// The current tab is going away, in order to make sure
// we emit that "current has changed", we need to reset this
@ -1045,16 +1069,14 @@ void QTabBar::removeTab(int index)
case SelectPreviousTab:
if (newIndex > index)
newIndex--;
if (d->validIndex(newIndex))
if (d->validIndex(newIndex) && d->tabList.at(newIndex).visible)
break;
Q_FALLTHROUGH();
case SelectRightTab:
newIndex = index;
if (newIndex >= d->tabList.size())
newIndex = d->tabList.size() - 1;
newIndex = qBound(d->firstVisible, index, d->lastVisible);
break;
case SelectLeftTab:
newIndex = index - 1;
newIndex = qBound(d->firstVisible, index-1, d->lastVisible);
if (newIndex < 0)
newIndex = 0;
break;
@ -1118,9 +1140,52 @@ void QTabBar::setTabEnabled(int index, bool enabled)
#endif
update();
if (!enabled && index == d->currentIndex)
setCurrentIndex(d->validIndex(index+1)?index+1:0);
else if (enabled && !d->validIndex(d->currentIndex))
setCurrentIndex(index);
setCurrentIndex(d->selectNewCurrentIndexFrom(index+1));
else if (enabled && !isTabVisible(d->currentIndex))
setCurrentIndex(d->selectNewCurrentIndexFrom(index));
}
}
/*!
Returns true if the tab at position \a index is visible; otherwise
returns false.
\since 5.15
*/
bool QTabBar::isTabVisible(int index) const
{
Q_D(const QTabBar);
if (d->validIndex(index))
return d->tabList.at(index).visible;
return false;
}
/*!
If \a visible is true, make the tab at position \a index visible,
otherwise make it hidden.
\since 5.15
*/
void QTabBar::setTabVisible(int index, bool visible)
{
Q_D(QTabBar);
if (QTabBarPrivate::Tab *tab = d->at(index)) {
d->layoutDirty = (visible != tab->visible);
if (!d->layoutDirty)
return;
tab->visible = visible;
if (tab->leftWidget)
tab->leftWidget->setVisible(visible);
if (tab->rightWidget)
tab->rightWidget->setVisible(visible);
#ifndef QT_NO_SHORTCUT
setShortcutEnabled(tab->shortcutId, visible);
#endif
d->calculateFirstLastVisible(index, visible, false);
if (!visible && index == d->currentIndex) {
const int newindex = d->selectNewCurrentIndexFrom(index+1);
setCurrentIndex(newindex);
}
update();
}
}
@ -1291,7 +1356,7 @@ QVariant QTabBar::tabData(int index) const
/*!
Returns the visual rectangle of the tab at position \a
index, or a null rectangle if \a index is out of range.
index, or a null rectangle if \a index is hidden, or out of range.
*/
QRect QTabBar::tabRect(int index) const
{
@ -1299,6 +1364,8 @@ QRect QTabBar::tabRect(int index) const
if (const QTabBarPrivate::Tab *tab = d->at(index)) {
if (d->layoutDirty)
const_cast<QTabBarPrivate*>(d)->layoutTabs();
if (!tab->visible)
return QRect();
QRect r = tab->rect;
if (verticalTabs(d->shape))
r.translate(0, -d->scrollOffset);
@ -1429,8 +1496,10 @@ QSize QTabBar::sizeHint() const
if (d->layoutDirty)
const_cast<QTabBarPrivate*>(d)->layoutTabs();
QRect r;
for (int i = 0; i < d->tabList.count(); ++i)
r = r.united(d->tabList.at(i).maxRect);
for (int i = 0; i < d->tabList.count(); ++i) {
if (d->tabList.at(i).visible)
r = r.united(d->tabList.at(i).maxRect);
}
QSize sz = QApplication::globalStrut();
return r.size().expandedTo(sz);
}
@ -1444,8 +1513,10 @@ QSize QTabBar::minimumSizeHint() const
const_cast<QTabBarPrivate*>(d)->layoutTabs();
if (!d->useScrollButtons) {
QRect r;
for (int i = 0; i < d->tabList.count(); ++i)
r = r.united(d->tabList.at(i).minRect);
for (int i = 0; i < d->tabList.count(); ++i) {
if (d->tabList.at(i).visible)
r = r.united(d->tabList.at(i).minRect);
}
return r.size().expandedTo(QApplication::globalStrut());
}
if (verticalTabs(d->shape))
@ -1746,6 +1817,8 @@ void QTabBar::paintEvent(QPaintEvent *)
p.drawPrimitive(QStyle::PE_FrameTabBarBase, optTabBase);
for (int i = 0; i < d->tabList.count(); ++i) {
if (!d->at(i)->visible)
continue;
QStyleOptionTab tab;
initStyleOption(&tab, i);
if (d->paintWithOffsets && d->tabList[i].dragOffset != 0) {
@ -1819,6 +1892,65 @@ void QTabBar::paintEvent(QPaintEvent *)
}
}
/*
When index changes visibility, we have to find first & last visible indexes.
If remove is set, we force both
*/
void QTabBarPrivate::calculateFirstLastVisible(int index, bool visible, bool remove)
{
if (visible) {
firstVisible = qMin(index, firstVisible);
lastVisible = qMax(index, lastVisible);
} else {
if (remove || (index == firstVisible)) {
firstVisible = -1;
for (int i = 0; i < tabList.count(); ++i) {
if (tabList.at(i).visible) {
firstVisible = i;
break;
}
}
if (firstVisible < 0)
firstVisible = 0;
}
if (remove || (index == lastVisible)) {
lastVisible = -1;
for (int i = tabList.count() - 1; i >= 0; --i) {
if (tabList.at(i).visible) {
lastVisible = i;
break;
}
}
}
}
}
/*
Selects the new current index starting at "fromIndex". If "fromIndex" is visible we're done.
Else it tries any index AFTER fromIndex, then any BEFORE fromIndex and, if everything fails,
it returns -1 indicating that no index is available
*/
int QTabBarPrivate::selectNewCurrentIndexFrom(int fromIndex)
{
int newindex = -1;
for (int i = fromIndex; i < tabList.count(); ++i) {
if (at(i)->visible && at(i)->enabled) {
newindex = i;
break;
}
}
if (newindex < 0) {
for (int i = fromIndex-1; i > -1; --i) {
if (at(i)->visible && at(i)->enabled) {
newindex = i;
break;
}
}
}
return newindex;
}
/*
Given that index at position from moved to position to where return where index goes.
*/

View File

@ -105,6 +105,9 @@ public:
bool isTabEnabled(int index) const;
void setTabEnabled(int index, bool);
bool isTabVisible(int index) const;
void setTabVisible(int index, bool);
QString tabText(int index) const;
void setTabText(int index, const QString &text);

View File

@ -88,7 +88,7 @@ class Q_WIDGETS_EXPORT QTabBarPrivate : public QWidgetPrivate
Q_DECLARE_PUBLIC(QTabBar)
public:
QTabBarPrivate()
:currentIndex(-1), pressedIndex(-1), shape(QTabBar::RoundedNorth), layoutDirty(false),
:currentIndex(-1), pressedIndex(-1), firstVisible(0), lastVisible(-1), shape(QTabBar::RoundedNorth), layoutDirty(false),
drawBase(true), scrollOffset(0), hoverIndex(-1), elideModeSetByUser(false), useScrollButtonsSetByUser(false), expanding(true), closeButtonOnTabs(false),
selectionBehaviorOnRemove(QTabBar::SelectRightTab), paintWithOffsets(true), movable(false),
dragInProgress(false), documentMode(false), autoHide(false), changeCurrentOnDrag(false),
@ -97,6 +97,8 @@ public:
int currentIndex;
int pressedIndex;
int firstVisible;
int lastVisible;
QTabBar::Shape shape;
bool layoutDirty;
bool drawBase;
@ -104,7 +106,7 @@ public:
struct Tab {
inline Tab(const QIcon &ico, const QString &txt)
: enabled(true) , shortcutId(0), text(txt), icon(ico),
: enabled(true) , visible(true), shortcutId(0), text(txt), icon(ico),
leftWidget(nullptr), rightWidget(nullptr), lastTab(-1), dragOffset(0)
#if QT_CONFIG(animation)
, animation(nullptr)
@ -112,6 +114,7 @@ public:
{}
bool operator==(const Tab &other) const { return &other == this; }
bool enabled;
bool visible;
int shortcutId;
QString text;
#ifndef QT_NO_TOOLTIP
@ -170,6 +173,8 @@ public:
QList<Tab> tabList;
mutable QHash<QString, QSize> textSizes;
void calculateFirstLastVisible(int index, bool visible, bool remove);
int selectNewCurrentIndexFrom(int currentIndex);
int calculateNewPosition(int from, int to, int index) const;
void slide(int from, int to);
void init();

View File

@ -544,8 +544,8 @@ bool QTabWidget::isTabEnabled(int index) const
}
/*!
If \a enable is true, the page at position \a index is enabled; otherwise the page at position \a index is
disabled. The page's tab is redrawn appropriately.
If \a enable is true, the page at position \a index is enabled; otherwise the page at
position \a index is disabled. The page's tab is redrawn appropriately.
QTabWidget uses QWidget::setEnabled() internally, rather than
keeping a separate flag.
@ -565,6 +565,44 @@ void QTabWidget::setTabEnabled(int index, bool enable)
widget->setEnabled(enable);
}
/*!
Returns true if the page at position \a index is visible; otherwise returns false.
\sa setTabVisible()
\since 5.15
*/
bool QTabWidget::isTabVisible(int index) const
{
Q_D(const QTabWidget);
return d->tabs->isTabVisible(index);
}
/*!
If \a enable is true, the page at position \a index is visible; otherwise the page at
position \a index is hidden. The page's tab is redrawn appropriately.
\sa isTabVisible()
\since 5.15
*/
void QTabWidget::setTabVisible(int index, bool visible)
{
Q_D(QTabWidget);
QWidget *widget = d->stack->widget(index);
bool currentVisible = d->tabs->isTabVisible(d->tabs->currentIndex());
d->tabs->setTabVisible(index, visible);
if (!visible) {
if (widget)
widget->setVisible(false);
} else if (!currentVisible) {
setCurrentIndex(index);
if (widget)
widget->setVisible(true);
}
setUpLayout();
}
/*!
\fn void QTabWidget::setCornerWidget(QWidget *widget, Qt::Corner corner)
@ -848,7 +886,13 @@ QSize QTabWidget::sizeHint() const
QTabWidget *that = const_cast<QTabWidget*>(this);
that->setUpLayout(true);
}
QSize s(d->stack->sizeHint());
QSize s;
for (int i=0; i< d->stack->count(); ++i) {
if (const QWidget* w = d->stack->widget(i)) {
if (d->tabs->isTabVisible(i))
s = s.expandedTo(w->sizeHint());
}
}
QSize t;
if (!d->isAutoHidden()) {
t = d->tabs->sizeHint();

View File

@ -82,6 +82,9 @@ public:
bool isTabEnabled(int index) const;
void setTabEnabled(int index, bool);
bool isTabVisible(int index) const;
void setTabVisible(int index, bool);
QString tabText(int index) const;
void setTabText(int index, const QString &);

View File

@ -60,6 +60,10 @@ private slots:
void removeTab_data();
void removeTab();
void hideTab_data();
void hideTab();
void hideAllTabs();
void setElideMode_data();
void setElideMode();
void sizeHints();
@ -244,6 +248,73 @@ void tst_QTabBar::removeTab()
QTEST(tabbar.currentIndex(), "finalIndex");
}
void tst_QTabBar::hideTab_data()
{
QTest::addColumn<int>("currentIndex");
QTest::addColumn<int>("hideIndex");
QTest::addColumn<int>("spyCount");
QTest::addColumn<int>("finalIndex");
QTest::newRow("hideEnd") << 0 << 2 << 0 << 0;
QTest::newRow("hideEndWithIndexOnEnd") << 2 << 2 << 1 << 1;
QTest::newRow("hideMiddle") << 2 << 1 << 0 << 2;
QTest::newRow("hideMiddleOnMiddle") << 1 << 1 << 1 << 2;
QTest::newRow("hideFirst") << 2 << 0 << 0 << 2;
QTest::newRow("hideFirstOnFirst") << 0 << 0 << 1 << 1;
}
void tst_QTabBar::hideTab()
{
QTabBar tabbar;
QFETCH(int, currentIndex);
QFETCH(int, hideIndex);
tabbar.addTab("foo");
tabbar.addTab("bar");
tabbar.addTab("baz");
tabbar.setCurrentIndex(currentIndex);
QSignalSpy spy(&tabbar, &QTabBar::currentChanged);
tabbar.setTabVisible(hideIndex, false);
QTEST(spy.count(), "spyCount");
QTEST(tabbar.currentIndex(), "finalIndex");
}
void tst_QTabBar::hideAllTabs()
{
QTabBar tabbar;
tabbar.addTab("foo");
tabbar.addTab("bar");
tabbar.addTab("baz");
tabbar.setCurrentIndex(0);
// Check we don't crash trying to hide an unexistant tab
QSize prevSizeHint = tabbar.sizeHint();
tabbar.setTabVisible(3, false);
QCOMPARE(tabbar.currentIndex(), 0);
QSize sizeHint = tabbar.sizeHint();
QVERIFY(sizeHint.width() == prevSizeHint.width());
prevSizeHint = sizeHint;
tabbar.setTabVisible(1, false);
QCOMPARE(tabbar.currentIndex(), 0);
sizeHint = tabbar.sizeHint();
QVERIFY(sizeHint.width() < prevSizeHint.width());
prevSizeHint = sizeHint;
tabbar.setTabVisible(2, false);
QCOMPARE(tabbar.currentIndex(), 0);
sizeHint = tabbar.sizeHint();
QVERIFY(sizeHint.width() < prevSizeHint.width());
prevSizeHint = sizeHint;
tabbar.setTabVisible(0, false);
// We cannot set currentIndex < 0
QCOMPARE(tabbar.currentIndex(), 0);
sizeHint = tabbar.sizeHint();
QVERIFY(sizeHint.width() < prevSizeHint.width());
}
void tst_QTabBar::setElideMode_data()
{
QTest::addColumn<int>("tabElideMode");

View File

@ -81,6 +81,7 @@ private slots:
void addRemoveTab();
void tabPosition();
void tabEnabled();
void tabHidden();
void tabText();
void tabShape();
void tabTooltip();
@ -246,6 +247,30 @@ void tst_QTabWidget::tabEnabled()
removePage(index);
}
void tst_QTabWidget::tabHidden()
{
// Test bad arguments
QVERIFY(!tw->isTabVisible(-1));
tw->setTabVisible(-1, false);
QVERIFY(!tw->isTabVisible(tw->count()));
tw->setTabVisible(tw->count(), false);
const int index = addPage();
tw->setTabVisible(index, true);
QVERIFY(tw->isTabVisible(index));
tw->setTabVisible(index, false);
QVERIFY(!tw->isTabVisible(index));
tw->setTabVisible(index, true);
QVERIFY(tw->isTabVisible(index));
removePage(index);
for (int i = 0; i < tw->count(); ++i) {
QVERIFY(tw->isTabVisible(i));
}
}
void tst_QTabWidget::tabText()
{
// Test bad arguments