How can I add a "new tab" button next to the tabs of a QMdiArea in tabbed view mode?
Asked Answered
S

7

19

I'd like to have a "new tab" button much like Chrome or Firefox has for my QMdiArea.

I can make a button or menu item somewhere that adds a new subdocument to the MDI thing, but how can I make it a visually appealing tiny tab with a "+" as label? Alternatively, I would be happy enough with a QTabWidget with such a button.

Salpingitis answered 14/11, 2013 at 10:23 Comment(1)
I don't know about QMdiArea, but QTabWidget has a QTabBar which has a setTabButton() function. Perhaps you should check that out?Despumate
S
10

You will have to write your own class for the QTabBar. The plus button can be added by using absolute positioning.

I have some code here for PySide; it should give you the basic idea.

class TabBarPlus(QtGui.QTabBar):
    """Tab bar that has a plus button floating to the right of the tabs."""

    plusClicked = QtCore.Signal()

    def __init__(self):
        super().__init__()

        # Plus Button
        self.plusButton = QtGui.QPushButton("+")
        self.plusButton.setParent(self)
        self.plusButton.setFixedSize(20, 20)  # Small Fixed size
        self.plusButton.clicked.connect(self.plusClicked.emit)
        self.movePlusButton() # Move to the correct location
    # end Constructor

    def sizeHint(self):
        """Return the size of the TabBar with increased width for the plus button."""
        sizeHint = QtGui.QTabBar.sizeHint(self) 
        width = sizeHint.width()
        height = sizeHint.height()
        return QtCore.QSize(width+25, height)
    # end tabSizeHint

    def resizeEvent(self, event):
        """Resize the widget and make sure the plus button is in the correct location."""
        super().resizeEvent(event)

        self.movePlusButton()
    # end resizeEvent

    def tabLayoutChange(self):
        """This virtual handler is called whenever the tab layout changes.
        If anything changes make sure the plus button is in the correct location.
        """
        super().tabLayoutChange()

        self.movePlusButton()
    # end tabLayoutChange

    def movePlusButton(self):
        """Move the plus button to the correct location."""
        # Find the width of all of the tabs
        size = sum([self.tabRect(i).width() for i in range(self.count())])
        # size = 0
        # for i in range(self.count()):
        #     size += self.tabRect(i).width()

        # Set the plus button location in a visible area
        h = self.geometry().top()
        w = self.width()
        if size > w: # Show just to the left of the scroll buttons
            self.plusButton.move(w-54, h)
        else:
            self.plusButton.move(size, h)
    # end movePlusButton
# end class MyClass

class CustomTabWidget(QtGui.QTabWidget):
    """Tab Widget that that can have new tabs easily added to it."""

    def __init__(self):
        super().__init__()

        # Tab Bar
        self.tab = TabBarPlus()
        self.setTabBar(self.tab)

        # Properties
        self.setMovable(True)
        self.setTabsClosable(True)

        # Signals
        self.tab.plusClicked.connect(self.addTab)
        self.tab.tabMoved.connect(self.moveTab)
        self.tabCloseRequested.connect(self.removeTab)
    # end Constructor
# end class CustomTabWidget
Smetana answered 20/11, 2013 at 14:21 Comment(1)
This works perfectly. Can you tell me some issue that i can face with your method? Like any in resizingRosy
S
20

I know that question is outdated, but some time ago I was looking for ready-to-use implementation of feature you requested. I digged a bit and implement this for Qt 5 -- take a look at repo.

Main idea is to do:

// Create button what must be placed in tabs row
QToolButton *tb = new QToolButton();
tb->setText("+");
// Add empty, not enabled tab to tabWidget
tabWidget->addTab(new QLabel("Add tabs by pressing \"+\""), QString());
tabWidget->setTabEnabled(0, false);
// Add tab button to current tab. Button will be enabled, but tab -- not
tabWidget->tabBar()->setTabButton(0, QTabBar::RightSide, tb);
Strap answered 25/11, 2014 at 12:44 Comment(3)
This works great in Qt 5, but for Qt 4 this gives me an error: error: 'QTabBar* QTabWidget::tabBar() const' is protected, so I guess I'll just need to forget about Qt 4 for this then! Simple and elegant though. And I could in principle use any image for the QLabel, which is a nice plus.Salpingitis
I've checked and saw that really tabBar is protected in Qt 4.8. So I added "Qt 5" to my answer. Thanks for your comment.Strap
If the tabs are movable (setMovable(true)), it is possible to drag-and-drop tabs after the "new-tab" special bar, even if it is disabled :(.Chaumont
F
11

Why not make a button out of the last tab of your QTabWidget ? Just create the last tab with a '+' symbol on it and use the currentChanged event.

class Trace_Tabs(QTabWidget):

    def __init__(self):
        QTabWidget.__init__(self)       
        self._build_tabs()

    def _build_tabs(self):
        self.setUpdatesEnabled(True)

        self.insertTab(0,QWidget(), "Trace" )
        self.insertTab(1,QWidget(),'  +  ') 

        self.currentChanged.connect(self._add_trace) 

    def _add_trace(self, index):    

        if index == self.count()-1 :    
            '''last tab was clicked. add tab'''
            self.insertTab(index, QWidget(), "Trace %d" %(index+1)) 
            self.setCurrentIndex(index)

if __name__ == '__main__':    
    app = QApplication([])    
    tabs = Trace_Tabs()
    tabs.show()    
    app.exec_()
Flagship answered 23/10, 2015 at 11:58 Comment(1)
A solution for that problem is then to use: self.tabBar().setSelectionBehaviorOnRemove(QTabBar.SelectLeftTab)Kiker
S
10

You will have to write your own class for the QTabBar. The plus button can be added by using absolute positioning.

I have some code here for PySide; it should give you the basic idea.

class TabBarPlus(QtGui.QTabBar):
    """Tab bar that has a plus button floating to the right of the tabs."""

    plusClicked = QtCore.Signal()

    def __init__(self):
        super().__init__()

        # Plus Button
        self.plusButton = QtGui.QPushButton("+")
        self.plusButton.setParent(self)
        self.plusButton.setFixedSize(20, 20)  # Small Fixed size
        self.plusButton.clicked.connect(self.plusClicked.emit)
        self.movePlusButton() # Move to the correct location
    # end Constructor

    def sizeHint(self):
        """Return the size of the TabBar with increased width for the plus button."""
        sizeHint = QtGui.QTabBar.sizeHint(self) 
        width = sizeHint.width()
        height = sizeHint.height()
        return QtCore.QSize(width+25, height)
    # end tabSizeHint

    def resizeEvent(self, event):
        """Resize the widget and make sure the plus button is in the correct location."""
        super().resizeEvent(event)

        self.movePlusButton()
    # end resizeEvent

    def tabLayoutChange(self):
        """This virtual handler is called whenever the tab layout changes.
        If anything changes make sure the plus button is in the correct location.
        """
        super().tabLayoutChange()

        self.movePlusButton()
    # end tabLayoutChange

    def movePlusButton(self):
        """Move the plus button to the correct location."""
        # Find the width of all of the tabs
        size = sum([self.tabRect(i).width() for i in range(self.count())])
        # size = 0
        # for i in range(self.count()):
        #     size += self.tabRect(i).width()

        # Set the plus button location in a visible area
        h = self.geometry().top()
        w = self.width()
        if size > w: # Show just to the left of the scroll buttons
            self.plusButton.move(w-54, h)
        else:
            self.plusButton.move(size, h)
    # end movePlusButton
# end class MyClass

class CustomTabWidget(QtGui.QTabWidget):
    """Tab Widget that that can have new tabs easily added to it."""

    def __init__(self):
        super().__init__()

        # Tab Bar
        self.tab = TabBarPlus()
        self.setTabBar(self.tab)

        # Properties
        self.setMovable(True)
        self.setTabsClosable(True)

        # Signals
        self.tab.plusClicked.connect(self.addTab)
        self.tab.tabMoved.connect(self.moveTab)
        self.tabCloseRequested.connect(self.removeTab)
    # end Constructor
# end class CustomTabWidget
Smetana answered 20/11, 2013 at 14:21 Comment(1)
This works perfectly. Can you tell me some issue that i can face with your method? Like any in resizingRosy
Q
4

There's a special method for this:

void QTabWidget::setCornerWidget(QWidget *widget, Qt::Corner corner = Qt::TopRightCorner)

I use it like this:

QIcon icon = QIcon::fromTheme(QLatin1String("window-new"));
QToolButton *btn = new QToolButton();
btn->setIcon(icon);
connect(btn, &QAbstractButton::clicked, this, &App::OpenNewTab);
tab_widget_->setCornerWidget(btn, Qt::TopRightCorner);
Quoth answered 20/11, 2021 at 22:38 Comment(0)
C
1

First, add an empty tab to your widget, and connect the currentChanged signal:

TabsView::TabsView(QWidget *parent) :
  QWidget(parent),
  ui(new Ui::TabsView)
{
    ui->setupUi(this);
    ui->tabWidget->clear();
    ui->tabWidget->addTab(new QLabel("+"), QString("+"));
    connect(ui->tabWidget, &QTabWidget::currentChanged, this, &TabsView::onChangeTab);
    newTab();
}

Then, on your onChangeTab slot, check if the user is clicking on the last tab, and call newTab:

void TabsView::onChangeTab(int index)
{
    if (index == this->ui->tabWidget->count() - 1) {
        newTab();
    }
}

Finally, on your newTab method, create the new tab and select it:

void TabsView::newTab()
{
    int position = ui->tabWidget->count() - 1;
    ui->tabWidget->insertTab(position, new QLabel("Your new tab here"), QString("New tab"));
    ui->tabWidget->setCurrentIndex(position);
    auto tabBar = ui->tabWidget->tabBar();
    tabBar->scroll(tabBar->width(), 0);
}
Centum answered 7/2, 2019 at 18:51 Comment(0)
S
0

Similar concept to @Garjy's answer:

You could use a "blank" tab and add a button to that tab. This will also replace the "close" button if you are using TabWidget.setTabsCloseable(True). It is possible to make it to the "blank" tab, so I suggest combining with @Garjy's answer or adding some text/ another new button.

import sys
from qtpy.QtWidgets import QTabWidget, QWidget, QToolButton, QTabBar, QApplication

class Trace_Tabs(QTabWidget):

    def __init__(self):
        QTabWidget.__init__(self)
        self.setTabsClosable(True)
        self._build_tabs()

    def _build_tabs(self):

        self.insertTab(0, QWidget(), "Trace 0" )

        # create the "new tab" tab with button
        self.insertTab(1, QWidget(),'')
        nb = self.new_btn = QToolButton()
        nb.setText('+') # you could set an icon instead of text
        nb.setAutoRaise(True)
        nb.clicked.connect(self.new_tab)
        self.tabBar().setTabButton(1, QTabBar.RightSide, nb)

    def new_tab(self):
        index = self.count() - 1
        self.insertTab(index, QWidget(), "Trace %d" % index)
        self.setCurrentIndex(index)

if __name__ == '__main__':

    app = QApplication(sys.argv)

    tabs = Trace_Tabs()
    tabs.show()

    app.exec_()
Slew answered 19/6, 2017 at 16:36 Comment(0)
R
0

I tried all the answers here and none of them looked the way I wanted, so here's the solution I came up with:

QTabWidget tabs(&mainwin);
QToolButton newTabButton(&tabs);
newTabButton.setText("+");

auto update_newtab_button_position = [&newTabButton, &tabs](int index){
    int c = tabs.tabBar()->count();
    int x = 0;
    for(int i = 0; i < c; i++){
        x += tabs.tabBar()->tabRect(i).width();
    }
    newTabButton.move(x+1, 2);
    newTabButton.resize(tabs.tabBar()->rect().height() - 4, tabs.tabBar()->rect().height() - 4);
};

QObject::connect(tabs.tabBar(), &QTabBar::tabBarClicked, update_newtab_button_position);
// ...
mainwin.show();
update_newtab_button_position(0);
Rory answered 3/3, 2023 at 10:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.