QTabWidget's tab bar behavior?

Matic Kukovec <[email protected]>
Newsgroups gmane.comp.python.pyqt-pykde
Message-ID <VI1PR01MB5344312DF836261DBDBF43CDD7030@VI1PR01MB5344.eurprd01.prod.exchangelabs.com>
Hi guys,

In a standard QTabWidget when moving the tabs, the behavior is like this:
[cid:7d8c1ac9-04b1-4350-b2d7-fdabff771b69]

I made a QTabWidget with a custom tabBar in which I manually add a QGroupBox with QLabel
that acts as a close button with self.setTabButton(index, QTabBar.RightSide, groupbox).
This works great except that it changes the behavior of the mouse-dragging of tabs like so:
[cid:9c0ec5b9-3a3f-4f1c-a3eb-5166abe45a55]

I don't exactly know what caused this? I wish to get the standard behaviour back, can anyone help?

The code is in the attachment.

Thanks,
Matic

_______________________________________________
PyQt mailing list    [email protected]
https://www.riverbankcomputing.com/mailman/listinfo/pyqt
2019-05-25_20-05-15.gif (image/gif, 52.9 KB) - not displayed
2019-05-25_20-02-47.gif (image/gif, 83.4 KB) - not displayed
qtabwidget_test.py (text/plain, 15.1 KB)
import os
import sys
from PyQt5.Qsci import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtTest import *


def create_pixmap(pixmap_name):
    return QPixmap(pixmap_name)

class PictureButton(QLabel):
    off_image = None
    on_image = None
    hover_image = None
    function = None
    
    def __init__(self,
                 off_image,
                 on_image,
                 hover_image,
                 function,
                 size,
                 parent=None):
        super().__init__(parent)
        self.setScaledContents(True)
        self.off_image = off_image
        self.on_image = on_image
        self.hover_image = hover_image
        self.function = function
#        self.resize(size)
        self.setFixedSize(size)
        self.setPixmap(self.off_image)
    
    def mousePressEvent(self, event):
        super().mousePressEvent(event)
        self.setPixmap(self.on_image)
        event.accept()
        
    def mouseReleaseEvent(self, event):
        super().mouseReleaseEvent(event)
        self.setPixmap(self.hover_image)
        QTest.qWait(5)
        if callable(self.function) == True:
            self.function()
        event.accept()
    
    def enterEvent(self, event):
        super().enterEvent(event)
        self.setPixmap(self.hover_image)
        
    def leaveEvent(self, event):
        super().leaveEvent(event)
        self.setPixmap(self.off_image)

class MyTabWidget(QTabWidget):          
    """
    Basic widget used for holding QScintilla/QTextEdit objects
    """
    class CustomTabBar(QTabBar):
        class TabGroupBox(QGroupBox):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                self._index = None
            
            @property
            def index(self):
                return self._index
            
            @index.setter
            def index(self, value):
                self._index = value
        
        """
        Custom tab bar used to capture tab clicks, ...
        """
        # Layout constants
        SPACING = 3
        MARGINS = (5, 0, 5, 0)
        # Reference to the parent widget
        parent      = None
        # Reference to the main form
        main_form   = None
        # Reference to the tab menu
        tab_menu    = None
        
        
        def __init__(self, parent):
            """Initialize the tab bar object"""
            # Initialize superclass
            super().__init__(parent)
            # Store the parent reference
            self.parent = parent
            # Store the main form reference
            self.main_form = self.parent.parent
            # Connect the signals
            self.tabMoved.connect(self._tab_moved_slot)
            self.currentChanged.connect(self._current_tab_changed)
            # Enable mouse tracking move events
            self.setMouseTracking(True)
            self.mouse_hover_index = None
        
        def _update_tab_indexes(self):
            # Readdress all tabs
            for i in range(self.count()):
                groupbox = self.tabButton(i, QTabBar.RightSide)
                _data = self.tabData(i)
                if _data is not None:
                    stored_groupbox, close_box = _data
                    if groupbox == None:
                        groupbox = stored_groupbox
                    if close_box:
                        close_box.index = i
                groupbox.index = i
            self.changeEvent(QEvent(QEvent.FontChange))
        
        def tabInserted(self, index):
            groupbox = self.TabGroupBox()
            layout = QHBoxLayout()
            layout.setSpacing(self.SPACING)
            layout.setContentsMargins(*self.MARGINS)
            groupbox.setLayout(layout)
            groupbox.setStyleSheet("QGroupBox{border: 0px;}")
            
            close_box = None
            widget = self.parent.widget(index)
            def close_function():
                close_index = groupbox.index
                self.parent.tabCloseRequested.emit(close_index)
            close_button = PictureButton(
                create_pixmap("alt_close.png"),
                create_pixmap("alt_close_press.png"),
                create_pixmap("alt_close_hover.png"),
                close_function,
                QSize(int(20), int(20)),
                self
            )
            close_button.closes = True
            layout.addWidget(close_button)
            
            close_box = self.TabGroupBox()
            def copy_close_function():
                close_index = close_box.index
                self.parent.tabCloseRequested.emit(close_index)
            copy_close_button = PictureButton(
                create_pixmap("alt_close.png"),
                create_pixmap("alt_close_press.png"),
                create_pixmap("alt_close_hover.png"),
                copy_close_function,
                QSize(int(20), int(20)),
                self
            )
            copy_close_button.closes = True
            copy_close_button.setParent(close_box)
            copy_layout = QHBoxLayout()
            copy_layout.setSpacing(self.SPACING)
            copy_layout.setContentsMargins(*self.MARGINS)
            copy_layout.addWidget(copy_close_button)
            close_box.setLayout(copy_layout)
            close_box.setStyleSheet("QGroupBox{border: 0px;}")
                
            # Store the groupbox
            self.setTabData(index, (groupbox, close_box))
            
            self.setTabButton(index, QTabBar.RightSide, groupbox)
            self.resize_tab_buttons(index)
            self._update_tab_indexes()
        
        def tabRemoved(self, index):
            self._update_tab_indexes()
        
        @pyqtSlot(int, int)
        def _tab_moved_slot(self, t_from, t_to):
            self._update_tab_indexes()
        
        @pyqtSlot(int)
        def _current_tab_changed(self, current_index):
            self.refresh_tab_buttons(current_index)
            
        def refresh_tab_buttons(self, index):
            for i in range(self.count()):
                groupbox = self.tabButton(i, QTabBar.RightSide)
                if i != index:
                    if groupbox == None:
                        continue
                    if isinstance(groupbox, self.TabGroupBox) == False:
                        continue
                    groupbox.hide()
                    close_box = self.tabData(i)[1]
                    self.setTabButton(i, QTabBar.RightSide, close_box)
                    if close_box:
                        close_box.show()
                else:
                    _data = self.tabData(i)
                    if _data == None:
                        continue
                    groupbox, close_box = _data
                    if isinstance(groupbox, self.TabGroupBox) == False or \
                       groupbox.layout().count() == 0:
                            continue
                    self.setTabButton(i, QTabBar.RightSide, groupbox)
                    groupbox.show()
                    
                
        
        def add_tab_button(self,
                           index,
                           icon_path_off,
                           icon_path_on,
                           icon_path_hover,
                           tooltip,
                           func,
                           visible=True):
            groupbox = self.tabButton(index, QTabBar.RightSide)
            layout = groupbox.layout()
            items = []
            for i in range(layout.count()):
                items.append(layout.itemAt(i).widget())
            
            button = PictureButton(
                create_pixmap(icon_path_off),
                create_pixmap(icon_path_on),
                create_pixmap(icon_path_hover),
                func,
                QSize(int(20), int(20)),
                self
            )
            button.setVisible(visible)
            button.setToolTip(tooltip)
            button.closes = False
            groupbox.index = index
            
            close_box = self.tabData(index)[1]
            if close_box:
                close_box.index = index
            
            layout.setSpacing(self.SPACING)
            layout.setContentsMargins(*self.MARGINS)
            groupbox.setStyleSheet("QGroupBox{border: 0px;}")
            # Clear old layout
            for i in reversed(range(layout.count())): 
                layout.itemAt(i).widget().setParent(None)
            # Add new items
            for i in items[:-1]:
                layout.addWidget(i)
            layout.addWidget(button)
            if len(items) > 0:
                layout.addWidget(items[-1])
            self.setTabButton(index, QTabBar.RightSide, groupbox)
            self.resize_tab_buttons(index)
            self._update_tab_indexes()
            groupbox.show()
        
        def _set_tab_button_visibility(self, tab_index, button_index, visible):
            groupbox = self.tabButton(tab_index, QTabBar.RightSide)
            if groupbox is None:
                return
            layout = groupbox.layout()
            button_item = layout.itemAt(button_index)
            if button_item is None:
                return
            if visible == True:
                button_item.widget().show()
            else:
                button_item.widget().hide()
            self.resize_tab_buttons(tab_index)
            groupbox.adjustSize()
            e = QEvent(QEvent.Resize)
            QCoreApplication.sendEvent(self, e)
            self.adjustSize()
            QCoreApplication.processEvents()
        
        def hide_tab_button(self, tab_index, button_index):
            self._set_tab_button_visibility(tab_index, button_index, False)
        
        def show_tab_button(self, tab_index, button_index):
            self._set_tab_button_visibility(tab_index, button_index, True)
    
        def scale_tab_buttons(self):
            for i in range(self.count()):
                groupbox = self.tabButton(i, QTabBar.RightSide)
                if groupbox == None:
                    groupbox = self.tabData(i)
                    if groupbox == None:
                        continue
                if isinstance(groupbox, self.TabGroupBox):
                    layout = groupbox.layout()
                    for j in range(layout.count()):
                        w = layout.itemAt(j).widget()
                        w.setFixedSize(
                            int(20), int(20)
                        )
                        if isinstance(w, QToolButton):
                            w.setIconSize(
                                QSize(
                                    int(20), 
                                    int(20)
                                )
                            )
                    self.resize_tab_buttons(i)
                elif isinstance(groupbox, tuple):
                    groupbox, close_box = groupbox
                    layout = groupbox.layout()
                    for j in range(layout.count()):
                        w = layout.itemAt(j).widget()
                        w.setFixedSize(
                            int(20), int(20)
                        )
                        if isinstance(w, QToolButton):
                            w.setIconSize(
                                QSize(
                                    int(20), 
                                    int(20)
                                )
                            )
                    
                    if close_box:
                        close_box.layout().itemAt(0).widget().setFixedSize(
                            int(20), int(20)
                        )
                    
                    self.resize_tab_buttons(i)
        
        def resize_tab_buttons(self, index):
            # Resize groupbox
            close_box = None
            groupbox = self.tabButton(index, QTabBar.RightSide)
            _data = self.tabData(index)
            if groupbox == None:
                if _data == None:
                    return
                groupbox = _data[0]
                close_box = _data[1]
            layout = groupbox.layout()
            count = 0
            for i in range(layout.count()):
                if layout.itemAt(i).widget().isVisible():
                    count += 1
            if count > 0:
                button_size = int(20) * count
                spacing_size = self.SPACING * (count - 1)
                margin_space_horizontal = self.MARGINS[0] + self.MARGINS[2]
                margin_space_vertical = self.MARGINS[1] + self.MARGINS[3]
                groupbox.setFixedSize(
                    QSize(
                        button_size + spacing_size + margin_space_horizontal, 
                        int(20) + margin_space_vertical
                    )
                )
            if close_box == None and _data != None:
                close_box = _data[1]
            if close_box:
                layout = close_box.layout()
                count = 0
                for i in range(layout.count()):
                    if layout.itemAt(i).widget().isVisible():
                        count += 1
                if count > 0:
                    button_size = int(20) * count
                    spacing_size = self.SPACING * (count - 1)
                    margin_space_horizontal = self.MARGINS[0] + self.MARGINS[2]
                    margin_space_vertical = self.MARGINS[1] + self.MARGINS[3]
                    close_box.setFixedSize(
                        QSize(
                            button_size + spacing_size + margin_space_horizontal, 
                            int(20) + margin_space_vertical
                        )
                    )
    
    def __init__(self, parent):
        super().__init__(parent)
        self.custom_tab_bar = self.CustomTabBar(self)
        self.setTabBar(self.custom_tab_bar)


class Window(QWidget):
    def __init__(self):

        super().__init__()
        
        # Custom tab widget
        tab_widget = MyTabWidget(self)

        # Standard tab widget
#        tab_widget = QTabWidget(self)
#        tab_widget.setTabsClosable(True)
        
        tab_widget.setMovable(True)
        tab_widget.addTab(QWidget(tab_widget), 'Tab One')
        tab_widget.addTab(QWidget(tab_widget), 'Tab Two')
        tab_widget.addTab(QWidget(tab_widget), 'Tab Three')
        tab_widget.addTab(QWidget(tab_widget), 'Tab Four')
        layout = QHBoxLayout()
        layout.addWidget(tab_widget)
        
        def button_func(state):
            tab_widget.addTab(QWidget(tab_widget), 'Tab Five')
            tab_widget.tabBar().add_tab_button(
                index=4,
                icon_path_off="alt_settings.png",
                icon_path_on="alt_settings_press.png",
                icon_path_hover="alt_settings_hover.png",
                tooltip="settings",
                func=None
            )
            tab_widget.setCurrentIndex(tab_widget.count()-1)
        button = QPushButton("TEST")
        button.clicked.connect(button_func)
        layout.addWidget(button)
        
        self.setLayout(layout)

if __name__ == '__main__':

    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.