Search code examples
c++qtqtabwidgetqtabbar

Inline renaming of a tab in QTabBar/QTabWidget


I really want to have a QTabWidget that can be renamed by double-click on tab caption. I have googled and found this solution, but it's more like an outline for a developer who knows his way through Qt and subclassing it's widgets. I'm kind of stuck how to implement all that. I have used an example given further down that thread (with IndependentLineEdit and so on) and it worked, but it's not what i wanted. I do not want to have any kind of InputDialog. I do not want to have any floating widget or something. What i basically want is to use QTabWidget (subclass), that behaves the similar way as spreadsheet tabs do in modern office suites - like, tab label is substituted with seamless line edit, which resets tab label on Enter or leaves it intact on Esc. I have not been able to find such a solution so far. I do understand that what I actually need is very close to this:

provide a temporary QLineEdit at QTabBar::tabRect() filled with QTabBar::tabText()

but i do not understand how to do that. Moreover, since QTabBar is kind of bare tabbar i would also prefer to have in enclosed into QTabWidget (subclass).


Solution

  • The following is an implementation of such behavior in Python in PyQt4. It should be easy to convert that to C++. Please note that Qt5 has a nice signal tabBarDoubleClicked which is missing in Qt4. This piece of code creates such signal as well.

    from PyQt4 import QtGui
    from PyQt4.QtCore import pyqtSignal, pyqtSlot
    
    
    class QTabBar(QtGui.QTabBar):
    
        """QTabBar with double click signal and tab rename behavior."""
    
        def __init__(self, parent=None):
            super().__init__(parent)
    
        tabDoubleClicked = pyqtSignal(int)
    
        def mouseDoubleClickEvent(self, event):
            tab_index = self.tabAt(event.pos())
            self.tabDoubleClicked.emit(tab_index)
            self.start_rename(tab_index)
    
        def start_rename(self, tab_index):
            self.__edited_tab = tab_index
            rect = self.tabRect(tab_index)
            top_margin = 3
            left_margin = 6
            self.__edit = QtGui.QLineEdit(self)
            self.__edit.show()
            self.__edit.move(rect.left() + left_margin, rect.top() + top_margin)
            self.__edit.resize(rect.width() - 2 * left_margin, rect.height() - 2 * top_margin)
            self.__edit.setText(self.tabText(tab_index))
            self.__edit.selectAll()
            self.__edit.setFocus()
            self.__edit.editingFinished.connect(self.finish_rename)
    
        @pyqtSlot()
        def finish_rename(self):
            self.setTabText(self.__edited_tab, self.__edit.text())
            self.__edit.deleteLater()
    
    
    class QTabWidget(QtGui.QTabWidget):
    
        """QTabWidget with double click on tab signal and tab rename behavior."""
    
        def __init__(self, parent):
            super().__init__(parent)
            self.setTabBar(QTabBar())
            self.tabBar().tabDoubleClicked.connect(self.tabBarDoubleClicked)
    
        tabBarDoubleClicked = pyqtSignal(int)
    

    Please note that there is a rather hacky solution to aligning the qlineedit within the tab header. It is achieved by fixing the margin to a certain value. I'm sure it is possible to look that value up in the style if necessary.