Refactor QTabBarPrivate::Tab and much of its usage in QTabBar

The type was broken in that it pretends to be a value, but didn't
implement the necessary operators for save copy, move, or comparison.
Tabs are supposed to be managed by Tab instance, as the crazy
implementation of operator== demonstrated.

Refactor the code to use it as a pointer consistently, even though this
means additional allocations for each tab. This is however acceptable
since there are not millions of tabs, and there is only a single place
where tabs are removed from the list.

Also use ranged for where possible, and never access the tabList using
operator[] to avoid detaches.

Use a std::unique_ptr for the animation in the tab, which implicitly
protects against attempts to copy a Tab, and allows us to use the
compiler generated default destructor. Add Q_DISABLE_COPY_MOVE for good
measure, the compiler-generated move semantics would not work either
due to the back-pointer from animation to the tab.

Change-Id: I8e8c071472f8f1f401b0f4f1dde074d800842934
Fixes: QTBUG-85394
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
bb10
Volker Hilsheimer 2020-07-02 15:53:34 +02:00
parent 652459afde
commit 359616066e
2 changed files with 187 additions and 171 deletions

View File

@ -174,7 +174,7 @@ void QTabBarPrivate::initBasicStyleOption(QStyleOptionTab *option, int tabIndex)
if (!option || (tabIndex < 0 || tabIndex >= totalTabs))
return;
const QTabBarPrivate::Tab &tab = tabList.at(tabIndex);
const QTabBarPrivate::Tab &tab = *tabList.at(tabIndex);
option->initFrom(q);
option->state &= ~(QStyle::State_HasFocus | QStyle::State_MouseOver);
option->rect = q->tabRect(tabIndex);
@ -433,23 +433,13 @@ void QTabBarPrivate::init()
useScrollButtons = !q->style()->styleHint(QStyle::SH_TabBar_PreferNoArrows, nullptr, q);
}
QTabBarPrivate::Tab *QTabBarPrivate::at(int index)
{
return validIndex(index)?&tabList[index]:nullptr;
}
const QTabBarPrivate::Tab *QTabBarPrivate::at(int index) const
{
return validIndex(index)?&tabList[index]:nullptr;
}
int QTabBarPrivate::indexAtPos(const QPoint &p) const
{
Q_Q(const QTabBar);
if (q->tabRect(currentIndex).contains(p))
return currentIndex;
for (int i = 0; i < tabList.count(); ++i)
if (tabList.at(i).enabled && q->tabRect(i).contains(p))
if (tabList.at(i)->enabled && q->tabRect(i).contains(p))
return i;
return -1;
}
@ -461,7 +451,6 @@ void QTabBarPrivate::layoutTabs()
QSize size = q->size();
int last, available;
int maxExtent;
int i;
bool vertTabs = verticalTabs(shape);
int tabChainIndex = 0;
int hiddenTabs = 0;
@ -490,20 +479,21 @@ void QTabBarPrivate::layoutTabs()
int minx = 0;
int x = 0;
int maxHeight = 0;
for (i = 0; i < tabList.count(); ++i) {
if (!tabList.at(i).visible) {
for (int i = 0; i < tabList.count(); ++i) {
const auto tab = tabList.at(i);
if (!tab->visible) {
++hiddenTabs;
continue;
}
QSize sz = q->tabSizeHint(i);
tabList[i].maxRect = QRect(x, 0, sz.width(), sz.height());
tab->maxRect = QRect(x, 0, sz.width(), sz.height());
x += sz.width();
maxHeight = qMax(maxHeight, sz.height());
sz = q->minimumTabSizeHint(i);
tabList[i].minRect = QRect(minx, 0, sz.width(), sz.height());
tab->minRect = QRect(minx, 0, sz.width(), sz.height());
minx += sz.width();
tabChain[tabChainIndex].init();
tabChain[tabChainIndex].sizeHint = tabList.at(i).maxRect.width();
tabChain[tabChainIndex].sizeHint = tab->maxRect.width();
tabChain[tabChainIndex].minimumSize = sz.width();
tabChain[tabChainIndex].empty = false;
tabChain[tabChainIndex].expansive = true;
@ -520,20 +510,21 @@ void QTabBarPrivate::layoutTabs()
int miny = 0;
int y = 0;
int maxWidth = 0;
for (i = 0; i < tabList.count(); ++i) {
if (!tabList.at(i).visible) {
for (int i = 0; i < tabList.count(); ++i) {
auto tab = tabList.at(i);
if (!tab->visible) {
++hiddenTabs;
continue;
}
QSize sz = q->tabSizeHint(i);
tabList[i].maxRect = QRect(0, y, sz.width(), sz.height());
tab->maxRect = QRect(0, y, sz.width(), sz.height());
y += sz.height();
maxWidth = qMax(maxWidth, sz.width());
sz = q->minimumTabSizeHint(i);
tabList[i].minRect = QRect(0, miny, sz.width(), sz.height());
tab->data = QRect(0, miny, sz.width(), sz.height());
miny += sz.height();
tabChain[tabChainIndex].init();
tabChain[tabChainIndex].sizeHint = tabList.at(i).maxRect.height();
tabChain[tabChainIndex].sizeHint = tab->maxRect.height();
tabChain[tabChainIndex].minimumSize = sz.height();
tabChain[tabChainIndex].empty = false;
tabChain[tabChainIndex].expansive = true;
@ -561,17 +552,18 @@ void QTabBarPrivate::layoutTabs()
// Use the results
hiddenTabs = 0;
for (i = 0; i < tabList.count(); ++i) {
if (!tabList.at(i).visible) {
tabList[i].rect = QRect();
for (int i = 0; i < tabList.count(); ++i) {
auto tab = tabList.at(i);
if (!tab->visible) {
tab->rect = QRect();
++hiddenTabs;
continue;
}
const QLayoutStruct &lstruct = tabChain.at(i + 1 - hiddenTabs);
if (!vertTabs)
tabList[i].rect.setRect(lstruct.pos, 0, lstruct.size, maxExtent);
tab->rect.setRect(lstruct.pos, 0, lstruct.size, maxExtent);
else
tabList[i].rect.setRect(0, lstruct.pos, maxExtent, lstruct.size);
tab->rect.setRect(0, lstruct.pos, maxExtent, lstruct.size);
}
if (useScrollButtons && tabList.count() && last > available) {
@ -659,7 +651,7 @@ QRect QTabBarPrivate::normalizedScrollRect(int index)
}
bool tearTopVisible = index != 0 && topEdge != -scrollOffset;
bool tearBottomVisible = index != tabList.size() - 1 && bottomEdge != tabList.constLast().rect.bottom() + 1 - scrollOffset;
bool tearBottomVisible = index != tabList.size() - 1 && bottomEdge != tabList.constLast()->rect.bottom() + 1 - scrollOffset;
if (tearTopVisible && !tearLeftRect.isNull())
topEdge = tearLeftRect.bottom() + 1;
if (tearBottomVisible && !tearRightRect.isNull())
@ -690,7 +682,7 @@ QRect QTabBarPrivate::normalizedScrollRect(int index)
}
bool tearLeftVisible = index != 0 && leftEdge != -scrollOffset;
bool tearRightVisible = index != tabList.size() - 1 && rightEdge != tabList.constLast().rect.right() + 1 - scrollOffset;
bool tearRightVisible = index != tabList.size() - 1 && rightEdge != tabList.constLast()->rect.right() + 1 - scrollOffset;
if (tearLeftVisible && !tearLeftRect.isNull())
leftEdge = tearLeftRect.right() + 1;
if (tearRightVisible && !tearRightRect.isNull())
@ -715,12 +707,12 @@ void QTabBarPrivate::makeVisible(int index)
if (!validIndex(index) || leftB->isHidden())
return;
const QRect tabRect = tabList.at(index).rect;
const QRect tabRect = tabList.at(index)->rect;
const int oldScrollOffset = scrollOffset;
const bool horiz = !verticalTabs(shape);
const int tabStart = horiz ? tabRect.left() : tabRect.top();
const int tabEnd = horiz ? tabRect.right() : tabRect.bottom();
const int lastTabEnd = horiz ? tabList.constLast().rect.right() : tabList.constLast().rect.bottom();
const int lastTabEnd = horiz ? tabList.constLast()->rect.right() : tabList.constLast()->rect.bottom();
const QRect scrollRect = normalizedScrollRect(index);
const int scrolledTabBarStart = qMax(1, scrollRect.left() + scrollOffset);
const int scrolledTabBarEnd = qMin(lastTabEnd - 1, scrollRect.right() + scrollOffset);
@ -757,34 +749,34 @@ void QTabBarPrivate::layoutTab(int index)
Q_Q(QTabBar);
Q_ASSERT(index >= 0);
Tab &tab = tabList[index];
const Tab *tab = tabList.at(index);
bool vertical = verticalTabs(shape);
if (!(tab.leftWidget || tab.rightWidget))
if (!(tab->leftWidget || tab->rightWidget))
return;
QStyleOptionTab opt;
q->initStyleOption(&opt, index);
if (tab.leftWidget) {
if (tab->leftWidget) {
QRect rect = q->style()->subElementRect(QStyle::SE_TabBarTabLeftButton, &opt, q);
QPoint p = rect.topLeft();
if ((index == pressedIndex) || paintWithOffsets) {
if (vertical)
p.setY(p.y() + tabList[index].dragOffset);
p.setY(p.y() + tab->dragOffset);
else
p.setX(p.x() + tabList[index].dragOffset);
p.setX(p.x() + tab->dragOffset);
}
tab.leftWidget->move(p);
tab->leftWidget->move(p);
}
if (tab.rightWidget) {
if (tab->rightWidget) {
QRect rect = q->style()->subElementRect(QStyle::SE_TabBarTabRightButton, &opt, q);
QPoint p = rect.topLeft();
if ((index == pressedIndex) || paintWithOffsets) {
if (vertical)
p.setY(p.y() + tab.dragOffset);
p.setY(p.y() + tab->dragOffset);
else
p.setX(p.x() + tab.dragOffset);
p.setX(p.x() + tab->dragOffset);
}
tab.rightWidget->move(p);
tab->rightWidget->move(p);
}
}
@ -812,12 +804,12 @@ void QTabBarPrivate::_q_closeTab()
QTabBar::ButtonPosition closeSide = (QTabBar::ButtonPosition)q->style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, nullptr, q);
for (int i = 0; i < tabList.count(); ++i) {
if (closeSide == QTabBar::LeftSide) {
if (tabList.at(i).leftWidget == object) {
if (tabList.at(i)->leftWidget == object) {
tabToClose = i;
break;
}
} else {
if (tabList.at(i).rightWidget == object) {
if (tabList.at(i)->rightWidget == object) {
tabToClose = i;
break;
}
@ -837,7 +829,7 @@ void QTabBarPrivate::_q_scrollTabs()
if (sender == leftB) {
for (i = tabList.count() - 1; i >= 0; --i) {
int start = horizontal ? tabList.at(i).rect.left() : tabList.at(i).rect.top();
int start = horizontal ? tabList.at(i)->rect.left() : tabList.at(i)->rect.top();
if (start < scrollRect.left() + scrollOffset) {
makeVisible(i);
return;
@ -845,7 +837,7 @@ void QTabBarPrivate::_q_scrollTabs()
}
} else if (sender == rightB) {
for (i = 0; i < tabList.count(); ++i) {
int end = horizontal ? tabList.at(i).rect.right() : tabList.at(i).rect.bottom();
int end = horizontal ? tabList.at(i)->rect.right() : tabList.at(i)->rect.bottom();
if (end > scrollRect.right() + scrollOffset) {
makeVisible(i);
return;
@ -990,12 +982,12 @@ int QTabBar::insertTab(int index, const QIcon& icon, const QString &text)
Q_D(QTabBar);
if (!d->validIndex(index)) {
index = d->tabList.count();
d->tabList.append(QTabBarPrivate::Tab(icon, text));
d->tabList.append(new QTabBarPrivate::Tab(icon, text));
} else {
d->tabList.insert(index, QTabBarPrivate::Tab(icon, text));
d->tabList.insert(index, new QTabBarPrivate::Tab(icon, text));
}
#ifndef QT_NO_SHORTCUT
d->tabList[index].shortcutId = grabShortcut(QKeySequence::mnemonic(text));
d->tabList.at(index)->shortcutId = grabShortcut(QKeySequence::mnemonic(text));
#endif
d->firstVisible = qMax(qMin(index, d->firstVisible), 0);
d->lastVisible = qMax(index, d->lastVisible);
@ -1016,9 +1008,9 @@ int QTabBar::insertTab(int index, const QIcon& icon, const QString &text)
setTabButton(index, closeSide, closeButton);
}
for (int i = 0; i < d->tabList.count(); ++i) {
if (d->tabList[i].lastTab >= index)
++d->tabList[i].lastTab;
for (const auto tab : qAsConst(d->tabList)) {
if (tab->lastTab >= index)
++tab->lastTab;
}
tabInserted(index);
@ -1036,30 +1028,32 @@ void QTabBar::removeTab(int index)
{
Q_D(QTabBar);
if (d->validIndex(index)) {
auto removedTab = d->tabList.at(index);
if (d->dragInProgress)
d->moveTabFinished(d->pressedIndex);
#ifndef QT_NO_SHORTCUT
releaseShortcut(d->tabList.at(index).shortcutId);
releaseShortcut(d->tabList.at(index)->shortcutId);
#endif
if (d->tabList[index].leftWidget) {
d->tabList[index].leftWidget->hide();
d->tabList[index].leftWidget->deleteLater();
d->tabList[index].leftWidget = nullptr;
if (removedTab->leftWidget) {
removedTab->leftWidget->hide();
removedTab->leftWidget->deleteLater();
removedTab->leftWidget = nullptr;
}
if (d->tabList[index].rightWidget) {
d->tabList[index].rightWidget->hide();
d->tabList[index].rightWidget->deleteLater();
d->tabList[index].rightWidget = nullptr;
if (removedTab->rightWidget) {
removedTab->rightWidget->hide();
removedTab->rightWidget->deleteLater();
removedTab->rightWidget = nullptr;
}
int newIndex = d->tabList[index].lastTab;
int newIndex = removedTab->lastTab;
d->tabList.removeAt(index);
for (int i = 0; i < d->tabList.count(); ++i) {
if (d->tabList[i].lastTab == index)
d->tabList[i].lastTab = -1;
if (d->tabList[i].lastTab > index)
--d->tabList[i].lastTab;
delete removedTab;
for (auto tab : qAsConst(d->tabList)) {
if (tab->lastTab == index)
tab->lastTab = -1;
if (tab->lastTab > index)
--tab->lastTab;
}
d->calculateFirstLastVisible(index, false, true);
@ -1074,7 +1068,7 @@ void QTabBar::removeTab(int index)
case SelectPreviousTab:
if (newIndex > index)
newIndex--;
if (d->validIndex(newIndex) && d->tabList.at(newIndex).visible)
if (d->validIndex(newIndex) && d->tabList.at(newIndex)->visible)
break;
Q_FALLTHROUGH();
case SelectRightTab:
@ -1091,9 +1085,9 @@ void QTabBar::removeTab(int index)
if (d->validIndex(newIndex)) {
// don't loose newIndex's old through setCurrentIndex
int bump = d->tabList[newIndex].lastTab;
int bump = d->tabList.at(newIndex)->lastTab;
setCurrentIndex(newIndex);
d->tabList[newIndex].lastTab = bump;
d->tabList.at(newIndex)->lastTab = bump;
}
} else {
emit currentChanged(-1);
@ -1161,7 +1155,7 @@ bool QTabBar::isTabVisible(int index) const
{
Q_D(const QTabBar);
if (d->validIndex(index))
return d->tabList.at(index).visible;
return d->tabList.at(index)->visible;
return false;
}
@ -1426,13 +1420,15 @@ void QTabBar::setCurrentIndex(int index)
Q_D(QTabBar);
if (d->dragInProgress && d->pressedIndex != -1)
return;
if (d->currentIndex == index)
return;
int oldIndex = d->currentIndex;
if (d->validIndex(index) && d->currentIndex != index) {
if (auto tab = d->at(index)) {
d->currentIndex = index;
update();
d->makeVisible(index);
d->tabList[index].lastTab = oldIndex;
tab->lastTab = oldIndex;
if (oldIndex >= 0 && oldIndex < count())
d->layoutTab(oldIndex);
d->layoutTab(index);
@ -1501,9 +1497,9 @@ QSize QTabBar::sizeHint() const
if (d->layoutDirty)
const_cast<QTabBarPrivate*>(d)->layoutTabs();
QRect r;
for (int i = 0; i < d->tabList.count(); ++i) {
if (d->tabList.at(i).visible)
r = r.united(d->tabList.at(i).maxRect);
for (const auto tab : d->tabList) {
if (tab->visible)
r = r.united(tab->maxRect);
}
return r.size();
}
@ -1517,9 +1513,9 @@ QSize QTabBar::minimumSizeHint() const
const_cast<QTabBarPrivate*>(d)->layoutTabs();
if (!d->useScrollButtons) {
QRect r;
for (int i = 0; i < d->tabList.count(); ++i) {
if (d->tabList.at(i).visible)
r = r.united(d->tabList.at(i).minRect);
for (const auto tab : d->tabList) {
if (tab->visible)
r = r.united(tab->minRect);
}
return r.size();
}
@ -1562,11 +1558,11 @@ static QString computeElidedText(Qt::TextElideMode mode, const QString &text)
QSize QTabBar::minimumTabSizeHint(int index) const
{
Q_D(const QTabBar);
QTabBarPrivate::Tab &tab = const_cast<QTabBarPrivate::Tab&>(d->tabList[index]);
QString oldText = tab.text;
tab.text = computeElidedText(d->elideMode, oldText);
QTabBarPrivate::Tab *tab = d->tabList.at(index);
QString oldText = tab->text;
tab->text = computeElidedText(d->elideMode, oldText);
QSize size = tabSizeHint(index);
tab.text = oldText;
tab->text = oldText;
return size;
}
@ -1580,7 +1576,7 @@ QSize QTabBar::tabSizeHint(int index) const
if (const QTabBarPrivate::Tab *tab = d->at(index)) {
QStyleOptionTab opt;
d->initBasicStyleOption(&opt, index);
opt.text = d->tabList.at(index).text;
opt.text = tab->text;
QSize iconSize = tab->icon.isNull() ? QSize(0, 0) : opt.iconSize;
int hframe = style()->pixelMetric(QStyle::PM_TabBarTabHSpace, &opt, this);
int vframe = style()->pixelMetric(QStyle::PM_TabBarTabVSpace, &opt, this);
@ -1746,7 +1742,7 @@ bool QTabBar::event(QEvent *event)
case QEvent::Shortcut: {
QShortcutEvent *se = static_cast<QShortcutEvent *>(event);
for (int i = 0; i < d->tabList.count(); ++i) {
const QTabBarPrivate::Tab *tab = &d->tabList.at(i);
const QTabBarPrivate::Tab *tab = d->tabList.at(i);
if (tab->shortcutId == se->shortcutId()) {
setCurrentIndex(i);
return true;
@ -1836,64 +1832,66 @@ void QTabBar::paintEvent(QPaintEvent *)
p.drawPrimitive(QStyle::PE_FrameTabBarBase, optTabBase);
for (int i = 0; i < d->tabList.count(); ++i) {
if (!d->at(i)->visible)
const auto tab = d->tabList.at(i);
if (!tab->visible)
continue;
QStyleOptionTab tab;
initStyleOption(&tab, i);
if (d->paintWithOffsets && d->tabList[i].dragOffset != 0) {
QStyleOptionTab tabOption;
initStyleOption(&tabOption, i);
if (d->paintWithOffsets && tab->dragOffset != 0) {
if (vertical) {
tab.rect.moveTop(tab.rect.y() + d->tabList[i].dragOffset);
tabOption.rect.moveTop(tabOption.rect.y() + tab->dragOffset);
} else {
tab.rect.moveLeft(tab.rect.x() + d->tabList[i].dragOffset);
tabOption.rect.moveLeft(tabOption.rect.x() + tab->dragOffset);
}
}
if (!(tab.state & QStyle::State_Enabled)) {
tab.palette.setCurrentColorGroup(QPalette::Disabled);
if (!(tabOption.state & QStyle::State_Enabled)) {
tabOption.palette.setCurrentColorGroup(QPalette::Disabled);
}
// If this tab is partially obscured, make a note of it so that we can pass the information
// along when we draw the tear.
QRect tabRect = d->tabList[i].rect;
QRect tabRect = tab->rect;
int tabStart = vertical ? tabRect.top() : tabRect.left();
int tabEnd = vertical ? tabRect.bottom() : tabRect.right();
if (tabStart < scrollRect.left() + d->scrollOffset) {
cutLeft = i;
cutTabLeft = tab;
cutTabLeft = tabOption;
} else if (tabEnd > scrollRect.right() + d->scrollOffset) {
cutRight = i;
cutTabRight = tab;
cutTabRight = tabOption;
}
// Don't bother drawing a tab if the entire tab is outside of the visible tab bar.
if ((!vertical && (tab.rect.right() < 0 || tab.rect.left() > width()))
|| (vertical && (tab.rect.bottom() < 0 || tab.rect.top() > height())))
if ((!vertical && (tabOption.rect.right() < 0 || tabOption.rect.left() > width()))
|| (vertical && (tabOption.rect.bottom() < 0 || tabOption.rect.top() > height())))
continue;
optTabBase.tabBarRect |= tab.rect;
optTabBase.tabBarRect |= tabOption.rect;
if (i == selected)
continue;
p.drawControl(QStyle::CE_TabBarTab, tab);
p.drawControl(QStyle::CE_TabBarTab, tabOption);
}
// Draw the selected tab last to get it "on top"
if (selected >= 0) {
QStyleOptionTab tab;
initStyleOption(&tab, selected);
if (d->paintWithOffsets && d->tabList[selected].dragOffset != 0) {
QStyleOptionTab tabOption;
const auto tab = d->tabList.at(selected);
initStyleOption(&tabOption, selected);
if (d->paintWithOffsets && tab->dragOffset != 0) {
if (vertical)
tab.rect.moveTop(tab.rect.y() + d->tabList[selected].dragOffset);
tabOption.rect.moveTop(tabOption.rect.y() + tab->dragOffset);
else
tab.rect.moveLeft(tab.rect.x() + d->tabList[selected].dragOffset);
tabOption.rect.moveLeft(tabOption.rect.x() + tab->dragOffset);
}
if (!d->dragInProgress)
p.drawControl(QStyle::CE_TabBarTab, tab);
p.drawControl(QStyle::CE_TabBarTab, tabOption);
else {
int taboverlap = style()->pixelMetric(QStyle::PM_TabBarTabOverlap, nullptr, this);
if (verticalTabs(d->shape))
d->movingTab->setGeometry(tab.rect.adjusted(0, -taboverlap, 0, taboverlap));
d->movingTab->setGeometry(tabOption.rect.adjusted(0, -taboverlap, 0, taboverlap));
else
d->movingTab->setGeometry(tab.rect.adjusted(-taboverlap, 0, taboverlap, 0));
d->movingTab->setGeometry(tabOption.rect.adjusted(-taboverlap, 0, taboverlap, 0));
}
}
@ -1924,7 +1922,7 @@ void QTabBarPrivate::calculateFirstLastVisible(int index, bool visible, bool rem
if (remove || (index == firstVisible)) {
firstVisible = -1;
for (int i = 0; i < tabList.count(); ++i) {
if (tabList.at(i).visible) {
if (tabList.at(i)->visible) {
firstVisible = i;
break;
}
@ -1935,7 +1933,7 @@ void QTabBarPrivate::calculateFirstLastVisible(int index, bool visible, bool rem
if (remove || (index == lastVisible)) {
lastVisible = -1;
for (int i = tabList.count() - 1; i >= 0; --i) {
if (tabList.at(i).visible) {
if (tabList.at(i)->visible) {
lastVisible = i;
break;
}
@ -1999,53 +1997,57 @@ void QTabBar::moveTab(int from, int to)
|| !d->validIndex(to))
return;
auto &fromTab = *d->tabList.at(from);
auto &toTab = *d->tabList.at(to);
bool vertical = verticalTabs(d->shape);
int oldPressedPosition = 0;
if (d->pressedIndex != -1) {
// Record the position of the pressed tab before reordering the tabs.
oldPressedPosition = vertical ? d->tabList[d->pressedIndex].rect.y()
: d->tabList[d->pressedIndex].rect.x();
oldPressedPosition = vertical ? d->tabList.at(d->pressedIndex)->rect.y()
: d->tabList.at(d->pressedIndex)->rect.x();
}
// Update the locations of the tabs first
int start = qMin(from, to);
int end = qMax(from, to);
int width = vertical ? d->tabList[from].rect.height() : d->tabList[from].rect.width();
int width = vertical ? fromTab.rect.height() : fromTab.rect.width();
if (from < to)
width *= -1;
bool rtl = isRightToLeft();
for (int i = start; i <= end; ++i) {
if (i == from)
continue;
auto &tab = *d->tabList.at(i);
if (vertical)
d->tabList[i].rect.moveTop(d->tabList[i].rect.y() + width);
tab.rect.moveTop(tab.rect.y() + width);
else
d->tabList[i].rect.moveLeft(d->tabList[i].rect.x() + width);
tab.rect.moveLeft(tab.rect.x() + width);
int direction = -1;
if (rtl && !vertical)
direction *= -1;
if (d->tabList[i].dragOffset != 0)
d->tabList[i].dragOffset += (direction * width);
if (tab.dragOffset != 0)
tab.dragOffset += (direction * width);
}
if (vertical) {
if (from < to)
d->tabList[from].rect.moveTop(d->tabList[to].rect.bottom() + 1);
fromTab.rect.moveTop(toTab.rect.bottom() + 1);
else
d->tabList[from].rect.moveTop(d->tabList[to].rect.top() - width);
fromTab.rect.moveTop(toTab.rect.top() - width);
} else {
if (from < to)
d->tabList[from].rect.moveLeft(d->tabList[to].rect.right() + 1);
fromTab.rect.moveLeft(toTab.rect.right() + 1);
else
d->tabList[from].rect.moveLeft(d->tabList[to].rect.left() - width);
fromTab.rect.moveLeft(toTab.rect.left() - width);
}
// Move the actual data structures
d->tabList.move(from, to);
// update lastTab locations
for (int i = 0; i < d->tabList.count(); ++i)
d->tabList[i].lastTab = d->calculateNewPosition(from, to, d->tabList[i].lastTab);
for (const auto tab : qAsConst(d->tabList))
tab->lastTab = d->calculateNewPosition(from, to, tab->lastTab);
// update external variables
int previousIndex = d->currentIndex;
@ -2054,7 +2056,8 @@ void QTabBar::moveTab(int from, int to)
// If we are in the middle of a drag update the dragStartPosition
if (d->pressedIndex != -1) {
d->pressedIndex = d->calculateNewPosition(from, to, d->pressedIndex);
int newPressedPosition = vertical ? d->tabList[d->pressedIndex].rect.top() : d->tabList[d->pressedIndex].rect.left();
const auto pressedTab = d->tabList.at(d->pressedIndex);
int newPressedPosition = vertical ? pressedTab->rect.top() : pressedTab->rect.left();
int diff = oldPressedPosition - newPressedPosition;
if (isRightToLeft() && !vertical)
diff *= -1;
@ -2086,15 +2089,15 @@ void QTabBarPrivate::slide(int from, int to)
q->setUpdatesEnabled(true);
int postLocation = vertical ? q->tabRect(to).y() : q->tabRect(to).x();
int length = postLocation - preLocation;
tabList[to].dragOffset -= length;
tabList[to].startAnimation(this, ANIMATION_DURATION);
tabList.at(to)->dragOffset -= length;
tabList.at(to)->startAnimation(this, ANIMATION_DURATION);
}
void QTabBarPrivate::moveTab(int index, int offset)
{
if (!validIndex(index))
return;
tabList[index].dragOffset = offset;
tabList.at(index)->dragOffset = offset;
layoutTab(index); // Make buttons follow tab
q_func()->update();
}
@ -2166,7 +2169,7 @@ void QTabBar::mouseMoveEvent(QMouseEvent *event)
} else {
dragDistance = (event->position().toPoint().x() - d->dragStartPosition.x());
}
d->tabList[d->pressedIndex].dragOffset = dragDistance;
d->tabList.at(d->pressedIndex)->dragOffset = dragDistance;
QRect startingRect = tabRect(d->pressedIndex);
if (vertical)
@ -2243,10 +2246,11 @@ void QTabBarPrivate::setupMovableTab()
movingTab->raise();
// Re-arrange widget order to avoid overlaps
if (tabList[pressedIndex].leftWidget)
tabList[pressedIndex].leftWidget->raise();
if (tabList[pressedIndex].rightWidget)
tabList[pressedIndex].rightWidget->raise();
const auto &pressedTab = *tabList.at(pressedIndex);
if (pressedTab.leftWidget)
pressedTab.leftWidget->raise();
if (pressedTab.rightWidget)
pressedTab.rightWidget->raise();
if (leftB)
leftB->raise();
if (rightB)
@ -2260,17 +2264,18 @@ void QTabBarPrivate::moveTabFinished(int index)
bool cleanup = (pressedIndex == index) || (pressedIndex == -1) || !validIndex(index);
bool allAnimationsFinished = true;
#if QT_CONFIG(animation)
for(int i = 0; allAnimationsFinished && i < tabList.count(); ++i) {
const Tab &t = tabList.at(i);
if (t.animation && t.animation->state() == QAbstractAnimation::Running)
for (const auto tab : qAsConst(tabList)) {
if (tab->animation && tab->animation->state() == QAbstractAnimation::Running) {
allAnimationsFinished = false;
break;
}
}
#endif // animation
if (allAnimationsFinished && cleanup) {
if(movingTab)
movingTab->setVisible(false); // We might not get a mouse release
for (int i = 0; i < tabList.count(); ++i) {
tabList[i].dragOffset = 0;
for (auto tab : qAsConst(tabList)) {
tab->dragOffset = 0;
}
if (pressedIndex != -1 && movable) {
pressedIndex = -1;
@ -2281,7 +2286,7 @@ void QTabBarPrivate::moveTabFinished(int index)
} else {
if (!validIndex(index))
return;
tabList[index].dragOffset = 0;
tabList.at(index)->dragOffset = 0;
}
q->update();
}
@ -2297,13 +2302,13 @@ void QTabBar::mouseReleaseEvent(QMouseEvent *event)
}
if (d->movable && d->dragInProgress && d->validIndex(d->pressedIndex)) {
int length = d->tabList[d->pressedIndex].dragOffset;
int length = d->tabList.at(d->pressedIndex)->dragOffset;
int width = verticalTabs(d->shape)
? tabRect(d->pressedIndex).height()
: tabRect(d->pressedIndex).width();
int duration = qMin(ANIMATION_DURATION,
(qAbs(length) * ANIMATION_DURATION) / width);
d->tabList[d->pressedIndex].startAnimation(d, duration);
d->tabList.at(d->pressedIndex)->startAnimation(d, duration);
d->dragInProgress = false;
d->movingTab->setVisible(false);
d->dragStartPosition = QPoint();
@ -2373,7 +2378,7 @@ void QTabBarPrivate::setCurrentNextEnabledIndex(int offset)
{
Q_Q(QTabBar);
for (int index = currentIndex + offset; validIndex(index); index += offset) {
if (tabList.at(index).enabled) {
if (tabList.at(index)->enabled) {
q->setCurrentIndex(index);
break;
}
@ -2502,14 +2507,14 @@ void QTabBar::setTabsClosable(bool closable)
d->closeButtonOnTabs = closable;
ButtonPosition closeSide = (ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, nullptr, this);
if (!closable) {
for (int i = 0; i < d->tabList.count(); ++i) {
if (closeSide == LeftSide && d->tabList[i].leftWidget) {
d->tabList[i].leftWidget->deleteLater();
d->tabList[i].leftWidget = nullptr;
for (auto tab : qAsConst(d->tabList)) {
if (closeSide == LeftSide && tab->leftWidget) {
tab->leftWidget->deleteLater();
tab->leftWidget = nullptr;
}
if (closeSide == RightSide && d->tabList[i].rightWidget) {
d->tabList[i].rightWidget->deleteLater();
d->tabList[i].rightWidget = nullptr;
if (closeSide == RightSide && tab->rightWidget) {
tab->rightWidget->deleteLater();
tab->rightWidget = nullptr;
}
}
} else {
@ -2731,14 +2736,15 @@ void QTabBar::setTabButton(int index, ButtonPosition position, QWidget *widget)
widget->lower();
widget->show();
}
auto &tab = *d->tabList.at(index);
if (position == LeftSide) {
if (d->tabList[index].leftWidget)
d->tabList[index].leftWidget->hide();
d->tabList[index].leftWidget = widget;
if (tab.leftWidget)
tab.leftWidget->hide();
tab.leftWidget = widget;
} else {
if (d->tabList[index].rightWidget)
d->tabList[index].rightWidget->hide();
d->tabList[index].rightWidget = widget;
if (tab.rightWidget)
tab.rightWidget->hide();
tab.rightWidget = widget;
}
d->layoutTabs();
d->refresh();
@ -2752,12 +2758,11 @@ void QTabBar::setTabButton(int index, ButtonPosition position, QWidget *widget)
QWidget *QTabBar::tabButton(int index, ButtonPosition position) const
{
Q_D(const QTabBar);
if (index < 0 || index >= d->tabList.count())
return nullptr;
if (position == LeftSide)
return d->tabList.at(index).leftWidget;
else
return d->tabList.at(index).rightWidget;
if (const auto tab = d->at(index)) {
return position == LeftSide ? tab->leftWidget
: tab->rightWidget;
}
return nullptr;
}
#ifndef QT_NO_ACCESSIBILITY
@ -2849,12 +2854,12 @@ void CloseButton::paintEvent(QPaintEvent *)
#if QT_CONFIG(animation)
void QTabBarPrivate::Tab::TabBarAnimation::updateCurrentValue(const QVariant &current)
{
priv->moveTab(priv->tabList.indexOf(*tab), current.toInt());
priv->moveTab(priv->tabList.indexOf(tab), current.toInt());
}
void QTabBarPrivate::Tab::TabBarAnimation::updateState(QAbstractAnimation::State newState, QAbstractAnimation::State)
{
if (newState == Stopped) priv->moveTabFinished(priv->tabList.indexOf(*tab));
if (newState == Stopped) priv->moveTabFinished(priv->tabList.indexOf(tab));
}
#endif

View File

@ -65,6 +65,7 @@
#define ANIMATION_DURATION 250
#include <qstyleoption.h>
#include <utility>
QT_REQUIRE_CONFIG(tabbar);
@ -93,6 +94,10 @@ public:
paintWithOffsets(true), movable(false), dragInProgress(false), documentMode(false),
autoHide(false), changeCurrentOnDrag(false)
{}
~QTabBarPrivate()
{
qDeleteAll(tabList);
}
QRect hoverRect;
QPoint dragStartPosition;
@ -131,8 +136,14 @@ public:
struct Tab {
inline Tab(const QIcon &ico, const QString &txt)
: text(txt), icon(ico), enabled(true), visible(true)
{}
bool operator==(const Tab &other) const { return &other == this; }
{
}
/*
Tabs are managed by instance; they are not the same even
if all properties are the same.
*/
Q_DISABLE_COPY_MOVE(Tab);
QString text;
#if QT_CONFIG(tooltip)
QString toolTip;
@ -159,7 +170,6 @@ public:
uint visible : 1;
#if QT_CONFIG(animation)
~Tab() { delete animation; }
struct TabBarAnimation : public QVariantAnimation {
TabBarAnimation(Tab *t, QTabBarPrivate *_priv) : tab(t), priv(_priv)
{ setEasingCurve(QEasingCurve::InOutQuad); }
@ -171,15 +181,16 @@ public:
//these are needed for the callbacks
Tab *tab;
QTabBarPrivate *priv;
} *animation = nullptr;
};
std::unique_ptr<TabBarAnimation> animation;
void startAnimation(QTabBarPrivate *priv, int duration) {
if (!priv->isAnimated()) {
priv->moveTabFinished(priv->tabList.indexOf(*this));
priv->moveTabFinished(priv->tabList.indexOf(this));
return;
}
if (!animation)
animation = new TabBarAnimation(this, priv);
animation = std::make_unique<TabBarAnimation>(this, priv);
animation->setStartValue(dragOffset);
animation->setEndValue(0);
animation->setDuration(duration);
@ -190,7 +201,7 @@ public:
{ Q_UNUSED(duration); priv->moveTabFinished(priv->tabList.indexOf(*this)); }
#endif // animation
};
QList<Tab> tabList;
QList<Tab*> tabList;
mutable QHash<QString, QSize> textSizes;
void calculateFirstLastVisible(int index, bool visible, bool remove);
@ -199,8 +210,8 @@ public:
void slide(int from, int to);
void init();
Tab *at(int index);
const Tab *at(int index) const;
inline Tab *at(int index) { return tabList.value(index, nullptr); }
inline const Tab *at(int index) const { return tabList.value(index, nullptr); }
int indexAtPos(const QPoint &p) const;