QTabWidget: Don't build expensive parts of the style option when hidden

We end up calling setUpLayout() quite a few times and, in
particular, every time we add a new tab. Even if the tab
widget is hidden, we set the layout item margins to ensure
that whatever layout will contain the tab widget can get
the proper sizing.

For all practical purposes, layout item margins don't depend
on the contents itself, but are rather a simple constant
returned by the style. This means that QStyleOptionTabWidgetFrame
::tabBarSize, among a few other properties, is not needed
right away. This property in particular is quite expensive
to compute because it requires measuring the text size of each
tab. This can lead to a quadratic behavior: the size of each
tab's text will be computed for each tab we add. Besides, text
size computing has become a relatively expensive function in
itself (see QTBUG-53151, for example).

The current solution just uses a partially initialized style
option object for the sole purpose of getting the tab widget's
layout item margins from the style. The performance improvements
detailed show the creation time for QTabWidget with the specified
amount of tabs (times in ms):

        Tabs  Before  After
        -------------------
          1      6      5
          5      6      6
         10      8      6
         50     57     17
        100    178     21
        200    673     33

Task-number: QTBUG-55126
Change-Id: I79505dbd0014f6ed185da28047d8b68f9462ba94
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
bb10
Gabriel de Dietrich 2017-08-29 11:44:27 +07:00
parent d253464172
commit c84b4ff495
1 changed files with 49 additions and 34 deletions

View File

@ -195,6 +195,8 @@ public:
void _q_tabMoved(int from, int to);
void init();
void initBasicStyleOption(QStyleOptionTabWidgetFrame *option) const;
QTabBar *tabs;
QStackedWidget *stack;
QRect panelRect;
@ -257,6 +259,43 @@ bool QTabWidget::hasHeightForWidth() const
return has;
}
/*!
\internal
Initialize only time inexpensive parts of the style option
for QTabWidget::setUpLayout()'s non-visible code path.
*/
void QTabWidgetPrivate::initBasicStyleOption(QStyleOptionTabWidgetFrame *option) const
{
Q_Q(const QTabWidget);
option->initFrom(q);
if (q->documentMode())
option->lineWidth = 0;
else
option->lineWidth = q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, q);
switch (pos) {
case QTabWidget::North:
option->shape = shape == QTabWidget::Rounded ? QTabBar::RoundedNorth
: QTabBar::TriangularNorth;
break;
case QTabWidget::South:
option->shape = shape == QTabWidget::Rounded ? QTabBar::RoundedSouth
: QTabBar::TriangularSouth;
break;
case QTabWidget::West:
option->shape = shape == QTabWidget::Rounded ? QTabBar::RoundedWest
: QTabBar::TriangularWest;
break;
case QTabWidget::East:
option->shape = shape == QTabWidget::Rounded ? QTabBar::RoundedEast
: QTabBar::TriangularEast;
break;
}
option->tabBarRect = q->tabBar()->geometry();
}
/*!
Initialize \a option with the values from this QTabWidget. This method is useful
@ -271,12 +310,7 @@ void QTabWidget::initStyleOption(QStyleOptionTabWidgetFrame *option) const
return;
Q_D(const QTabWidget);
option->initFrom(this);
if (documentMode())
option->lineWidth = 0;
else
option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, this);
d->initBasicStyleOption(option);
int exth = style()->pixelMetric(QStyle::PM_TabBarBaseHeight, 0, this);
QSize t(0, d->stack->frameWidth());
@ -307,31 +341,10 @@ void QTabWidget::initStyleOption(QStyleOptionTabWidgetFrame *option) const
option->leftCornerWidgetSize = QSize(0, 0);
}
switch (d->pos) {
case QTabWidget::North:
option->shape = d->shape == QTabWidget::Rounded ? QTabBar::RoundedNorth
: QTabBar::TriangularNorth;
break;
case QTabWidget::South:
option->shape = d->shape == QTabWidget::Rounded ? QTabBar::RoundedSouth
: QTabBar::TriangularSouth;
break;
case QTabWidget::West:
option->shape = d->shape == QTabWidget::Rounded ? QTabBar::RoundedWest
: QTabBar::TriangularWest;
break;
case QTabWidget::East:
option->shape = d->shape == QTabWidget::Rounded ? QTabBar::RoundedEast
: QTabBar::TriangularEast;
break;
}
option->tabBarSize = t;
QRect tbRect = tabBar()->geometry();
QRect selectedTabRect = tabBar()->tabRect(tabBar()->currentIndex());
option->tabBarRect = tbRect;
selectedTabRect.moveTopLeft(selectedTabRect.topLeft() + tbRect.topLeft());
selectedTabRect.moveTopLeft(selectedTabRect.topLeft() + option->tabBarRect.topLeft());
option->selectedTabRect = selectedTabRect;
}
@ -763,17 +776,19 @@ void QTabWidget::setUpLayout(bool onlyCheck)
if (onlyCheck && !d->dirty)
return; // nothing to do
QStyleOptionTabWidgetFrame option;
initStyleOption(&option);
// this must be done immediately, because QWidgetItem relies on it (even if !isVisible())
d->setLayoutItemMargins(QStyle::SE_TabWidgetLayoutItem, &option);
if (!isVisible()) {
// this must be done immediately, because QWidgetItem relies on it (even if !isVisible())
QStyleOptionTabWidgetFrame basicOption;
d->initBasicStyleOption(&basicOption);
d->setLayoutItemMargins(QStyle::SE_TabWidgetLayoutItem, &basicOption);
d->dirty = true;
return; // we'll do it later
}
QStyleOptionTabWidgetFrame option;
initStyleOption(&option);
d->setLayoutItemMargins(QStyle::SE_TabWidgetLayoutItem, &option);
QRect tabRect = style()->subElementRect(QStyle::SE_TabWidgetTabBar, &option, this);
d->panelRect = style()->subElementRect(QStyle::SE_TabWidgetTabPane, &option, this);
QRect contentsRect = style()->subElementRect(QStyle::SE_TabWidgetTabContents, &option, this);