QTabWidget with add button right after the tabs (like Firefox or Chrome)
-
Hi,
I want to realize a QTabWidget with an "add tab" button right after the last tab (just like the tab bar from Firefox or Chrome). I've tried different approaches but each with it's different disadvantages:
-
Using a (disabled) tab with an add icon. This worked but if you have movable tabs, you could move other tabs beyond the add button.
-
Using the setCornerWidget() method of QTabWidget. This works also but I would prefer to have the button right after the last tab instead of the widget corner. With a style sheet like "QTabWidget::right-corner { left: -100px; }" I managed to move the corner button towards the tab bar. However I don't know to get the right width and it seems pretty hacky to adapt the style sheet on geometry changes.
-
So I came up with the idea of subclassing QTabWidget an modify the layout manually. But unfortunately the responsible function QTabWidget::setUpLayout() is non-virtual private. So no luck here...
My ultimatively last idea would be to implement a QTabWidget like widget completely from scratch (possibly with copy & paste code from the original). But this seems to be almost overkill.
Does anyone have a better idea?
-
-
Unfortunately it's not that simple keeping the button independent from the QTabWidget. The edge cases in particular are somewhat more complex to deal with. For example if the widget is too narrow, the tab bar scroll buttons will appear and there will be no space left for the add button:
So you have to consider the complete geometry of the QTabWidget which is defined in
void QTabWidget::setUpLayout(bool onlyCheck)
:void QTabWidget::setUpLayout(bool onlyCheck) { /* ... */ 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); QRect leftCornerRect = style()->subElementRect(QStyle::SE_TabWidgetLeftCorner, &option, this); QRect rightCornerRect = style()->subElementRect(QStyle::SE_TabWidgetRightCorner, &option, this); d->tabs->setGeometry(tabRect); d->stack->setGeometry(contentsRect); if (d->leftCornerWidget) d->leftCornerWidget->setGeometry(leftCornerRect); if (d->rightCornerWidget) d->rightCornerWidget->setGeometry(rightCornerRect); /* ... */ }
As said, this method is unfortunately non-virtual and cannot be overridden. However, on closer inspection it turns out that in fact the current QStyle is defining the concrete layout. So after I took a deeper dive into Qt styling (and stepping through the various Qt abstraction layers ^^) I came up with the solution using in fact the right corner widget and a QProxyStyle implementation to modify the position:
QRect FancyTabStyle::subElementRect(SubElement subElement, const QStyleOption *option, const QWidget *widget) const { if ( subElement == QStyle::SE_TabWidgetRightCorner) { // adapted from void QTabWidget::setUpLayout(bool onlyCheck) QRect tabRect = QProxyStyle::subElementRect(QStyle::SE_TabWidgetTabBar, option, widget); QRect rightCornerRect = QProxyStyle::subElementRect(QStyle::SE_TabWidgetRightCorner, option, widget); // place the right corner widget right after the tab bar rightCornerRect.setRect( tabRect.left() + tabRect.width() + 4, 3, rightCornerRect.width(), rightCornerRect.height()); return rightCornerRect; } return QProxyStyle::subElementRect(subElement, option, widget); }
However, to get the correct button size and alignment (especially the vertical alignment) I had to do some additional tweakings (such as overriding the paintEvent and implementing a QAbstractButton subclass for the add button). I implemented the solution with my FancyTabWidget which additionally provides editable tab names and published it on github: https://github.com/SM-nzberg/QtFancyTabWidget
-
Hi,
Just thinking out loud, you could have a QPushButton that you manually position after the last tab. Note that I haven't tested the complexity of that variant (and if it would work well in your case).
-
Unfortunately it's not that simple keeping the button independent from the QTabWidget. The edge cases in particular are somewhat more complex to deal with. For example if the widget is too narrow, the tab bar scroll buttons will appear and there will be no space left for the add button:
So you have to consider the complete geometry of the QTabWidget which is defined in
void QTabWidget::setUpLayout(bool onlyCheck)
:void QTabWidget::setUpLayout(bool onlyCheck) { /* ... */ 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); QRect leftCornerRect = style()->subElementRect(QStyle::SE_TabWidgetLeftCorner, &option, this); QRect rightCornerRect = style()->subElementRect(QStyle::SE_TabWidgetRightCorner, &option, this); d->tabs->setGeometry(tabRect); d->stack->setGeometry(contentsRect); if (d->leftCornerWidget) d->leftCornerWidget->setGeometry(leftCornerRect); if (d->rightCornerWidget) d->rightCornerWidget->setGeometry(rightCornerRect); /* ... */ }
As said, this method is unfortunately non-virtual and cannot be overridden. However, on closer inspection it turns out that in fact the current QStyle is defining the concrete layout. So after I took a deeper dive into Qt styling (and stepping through the various Qt abstraction layers ^^) I came up with the solution using in fact the right corner widget and a QProxyStyle implementation to modify the position:
QRect FancyTabStyle::subElementRect(SubElement subElement, const QStyleOption *option, const QWidget *widget) const { if ( subElement == QStyle::SE_TabWidgetRightCorner) { // adapted from void QTabWidget::setUpLayout(bool onlyCheck) QRect tabRect = QProxyStyle::subElementRect(QStyle::SE_TabWidgetTabBar, option, widget); QRect rightCornerRect = QProxyStyle::subElementRect(QStyle::SE_TabWidgetRightCorner, option, widget); // place the right corner widget right after the tab bar rightCornerRect.setRect( tabRect.left() + tabRect.width() + 4, 3, rightCornerRect.width(), rightCornerRect.height()); return rightCornerRect; } return QProxyStyle::subElementRect(subElement, option, widget); }
However, to get the correct button size and alignment (especially the vertical alignment) I had to do some additional tweakings (such as overriding the paintEvent and implementing a QAbstractButton subclass for the add button). I implemented the solution with my FancyTabWidget which additionally provides editable tab names and published it on github: https://github.com/SM-nzberg/QtFancyTabWidget
-
Thanks for the detailed deep dive !