Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Set vertical tab close button issue



  • Hello!

    I want to change the default tab widget close button and set my icon instead. The problem is that it draws the icon on the text.

    Code:

    void AppTabBar::paintEvent(QPaintEvent *event)
    {
        QStylePainter painter(this);
        QStyleOptionTab opt;
    
        for (int i = 0; i < this->count(); i++) {
            initStyleOption(&opt, i);
            opt.text = painter.fontMetrics().elidedText(opt.text, Qt::ElideRight, 70);
            painter.drawControl(QStyle::CE_TabBarTabShape, opt);
            painter.save();
            QSize s = opt.rect.size();
    
            if (tabPos != AppTabPosition::Top && tabPos != AppTabPosition::Bottom) {
                s.transpose();
            }
    
            QRect r(QPoint(), s);
            r.moveCenter(opt.rect.center());
            opt.rect = r;
    
            QPoint c = tabRect(i).center();
            painter.translate(c);
    
            if (tabPos == AppTabPosition::Left) {
                painter.rotate(90);
            } else if (tabPos == AppTabPosition::Right) {
                painter.rotate(270); //90 - left pos, 270 - right pos
            }
    
            painter.translate(-c);
            painter.drawControl(QStyle::CE_TabBarTabLabel, opt);
            painter.restore();
        }
    
        QWidget::paintEvent(event);
    }
    
    
    void AppTabStyle::drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
    {
        if (element == PE_IndicatorTabClose) {
            int size = proxy()->pixelMetric(QStyle::PM_SmallIconSize);
            QIcon::Mode mode = option->state & State_Enabled ? (option->state & State_Raised ? QIcon::Active : QIcon::Normal) : QIcon::Disabled;
    
            if (!(option->state & State_Raised) && !(option->state & State_Sunken) && !(option->state & QStyle::State_Selected)) {
                mode = QIcon::Disabled;
            }
    
            QIcon::State state = option->state & State_Sunken ? QIcon::On : QIcon::Off;
            QPixmap pixmap = QIcon(":/Icons/cross_icon.png").pixmap(size, mode, state);
            proxy()->drawItemPixmap(painter, option->rect, Qt::AlignRight, pixmap);
        } else {
            QProxyStyle::drawPrimitive(element, option, painter, widget);
        }
    }
    

    Screenshot:
    2021-05-13_200839.png

    Any ideas how to draw it to the right? Thank you.



  • Hello!

    Finally! I have fixed the issue with vertical tab close button position.

    Code:

    if (tabPos != AppTabPosition::Top && tabPos != AppTabPosition::Bottom) {
        s.transpose();
    
        if (this->tabsClosable()) { // check if tab is closable
            QRect optRect = opt.rect;
            optRect.setX(90); // set X pos of close button
            optRect.setY(optRect.y() + 8); // calcs the Y pos of close button
            optRect.setSize(QSize(12, 12));
            this->tabButton(i, QTabBar::RightSide)->setGeometry(optRect);
       }
    }
    

    Here I do not change the default opt.rect but instead I copy it to new QRect. Then I change the size and position of optRect and finally set geometry to tabButtton .

    Screenshot:

    alt text

    The issue is resolved.



  • I think I know what the problem is. I will reply soon.



  • Nope, my solution does not work. It draws 2 close buttons, then I hide the first one and display mine. Now, the problem is when clicking on X it does nothing.

    if (tabPos != AppTabPosition::Top && tabPos != AppTabPosition::Bottom) {
        s.transpose();
    
        if (this->tabsClosable()) { // check if tab is closable
            opt.rect.setX(90); // set X pos of close button
            this->tabButton(i, QTabBar::RightSide)->hide(); // hide the default close button
            painter.drawPrimitive(QStyle::PE_IndicatorTabClose, opt); //draw new close button
            opt.rect.setX(0); // set X pos to default
        }
    }
    

    How to connect the signal to a primitive type? Or is there any better solution to display the custom close tab icon. Thank you.



  • I think, the easiest way is to change the QRect in the drawPrimitive method but when I set for example X coordinate to 90, then my close icon becomes invisible.

    Code:

    void AppTabBarStyle::drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
    {
        if (element == PE_IndicatorTabClose) {
            int size = proxy()->pixelMetric(QStyle::PM_SmallIconSize);
            QIcon::Mode mode = option->state & State_Enabled ? (option->state & State_Raised ? QIcon::Active : QIcon::Normal) : QIcon::Disabled;
    
            if (!(option->state & State_Raised) && !(option->state & State_Sunken) && !(option->state & QStyle::State_Selected)) {
                mode = QIcon::Disabled;
            }
    
            QIcon::State state = option->state & State_Sunken ? QIcon::On : QIcon::Off;
            QPixmap pixmap = QIcon(":/Icon/red_cross_icon.png").pixmap(size, mode, state);
            qDebug() << "Size: " << option->rect.size() << " | x:" << option->rect.x() << " | y:" << option->rect.y();
            QRect testRect;
            testRect.setX(90);
            testRect.setY(5);
            testRect.setSize(option->rect.size());
            proxy()->drawItemPixmap(painter, testRect, Qt::AlignRight, pixmap);
        } else {
            QProxyStyle::drawPrimitive(element, option, painter, widget);
        }
    }
    

    Any ideas how to override the default tab bar button? Thank you.



  • I have set setTabsClosable(false);, then created the TabBarLabel class and set it to setTabButton method. It closes the tabs but the issue with overlapping the tab text still exists:

    Screenshot:
    vertical_tabs_issue_example.gif

    It is a lot of code, so I have created and uploaded to Mega the test example to illustrate this issue. Here is my test example: TabExample.zip (6kb)

    Any ideas how to change position of tab bar close button? Thank you.


  • Lifetime Qt Champion

    Hi,
    @Cobra91151 said in Set vertical tab close button issue:

    testRect.setX(90);
    testRect.setY(5);
    testRect.setSize(option->rect.size());

    What are the rect values ?
    I would say that you are currently drawing in the wrong coordinate system.

    To get started, I would either get the code from the base model or one of the style examples in order to go from a working state.



  • @SGaist

    Hello!

    These are just test values. I no longer use void AppTabBarStyle::drawPrimitive to draw the X icon. Now, I use the TabBarLabel which inherits from QLabel. I will post the code for TabBarLabel soon.



  • @SGaist

    So, here is my code below:

    tabbarlable.h

    #ifndef TABBARLABEL_H
    #define TABBARLABEL_H
    
    #include <QLabel>
    #include <QObject>
    #include <QPaintEvent>
    #include <QMouseEvent>
    #include "apptabbar.h"
    #include <QDebug>
    
    class TabBarLabel : public QLabel
    {
        Q_OBJECT
    public:
        TabBarLabel(AppTabBar *parent, QPixmap image);
        ~TabBarLabel();
    
    private:
        int tabIndex() const;
    
    protected:
        void mousePressEvent(QMouseEvent *event);
    
    private:
        AppTabBar *tabBar;
    };
    
    #endif // TABBARLABEL_H
    

    tabbarlabel.cpp

    #include "tabbarlabel.h"
    
    TabBarLabel::TabBarLabel(AppTabBar *parent, QPixmap image) : QLabel(parent),
        tabBar(parent)
    {
        this->setPixmap(image);
        this->setScaledContents(true);
        this->setStyleSheet("background: transparent;");
        this->setToolTip(QObject::tr("Close"));
    }
    
    void TabBarLabel::mousePressEvent(QMouseEvent *event)
    {
        qDebug() << "Clicked...";
        QMetaObject::invokeMethod(tabBar, "tabCloseRequested", Qt::DirectConnection, Q_ARG(int, this->tabIndex()));
        QLabel::mousePressEvent(event);
    }
    
    int TabBarLabel::tabIndex() const
    {
        for (int i = 0; i < tabBar->count(); i++) {
            if (tabBar->tabButton(i, QTabBar::RightSide) == this) {
                return i;
            }
        }
    
        return -1;
    }
    
    TabBarLabel::~TabBarLabel()
    {
    
    }
    

    testwidget.cpp

    TestWidget::TestWidget(QWidget *parent)
        : QWidget(parent)
    {
        setMinimumSize(900, 560);
        AppTabControl *tabControl = new AppTabControl(this, 30, 115);
        tabControl->setTabsClosable(false);
        connect(tabControl->tabBar(), &AppTabBar::tabCloseRequested, this, [this, tabControl](int tabIndex) {
            tabControl->removeTab(tabIndex);
        });
        this->setStyleSheet("QTabBar::tab {color: #000000; font-weight: bold; font-size: 10px; font-family: Gotham, Helvetica Neue, Helvetica, Arial, sans-serif;} "
                            "QTabBar::tab:selected {background-color: #FA9944; color: #000000; border-top: 1px solid #FA9944;} "
                            "QTabBar::tab:hover {color: #000000; border-top: 1px solid #FA9944; background-color: #FFFFFF;}");
        AppTabBar *tabBar1 = new AppTabBar(tabControl);
        TabBarLabel *closeTab2Label = new TabBarLabel(reinterpret_cast<AppTabBar*>(tabControl->tabBar()), QIcon(":/Icons/close_tab_icon.png").pixmap(12, 12));
        TabBarLabel *closeTab3Label = new TabBarLabel(reinterpret_cast<AppTabBar*>(tabControl->tabBar()), QIcon(":/Icons/close_tab_icon.png").pixmap(12, 12));
        AppTabBar *tabBar2 = new AppTabBar(tabControl);
        AppTabBar *tabBar3 = new AppTabBar(tabControl);
        tabControl->addTab(tabBar1, "Tab1");
        int tab2Index = tabControl->addTab(tabBar2, "Tab2");
        int tab3Index = tabControl->addTab(tabBar3, "Tab3");
        tabControl->tabBar()->setTabButton(tab2Index, QTabBar::RightSide, closeTab2Label);
        tabControl->tabBar()->setTabButton(tab3Index, QTabBar::RightSide, closeTab3Label);
        QHBoxLayout *mainLayout = new QHBoxLayout();
        mainLayout->setContentsMargins(QMargins());
        mainLayout->addWidget(tabControl);
        setLayout(mainLayout);
    }
    

    The actual vertical tab bar draws here:

    void AppTabBar::paintEvent(QPaintEvent *event)
    {
        QStylePainter painter(this);
        QStyleOptionTab opt;
    
        for (int i = 0; i < this->count(); i++) {
            initStyleOption(&opt, i);
            painter.drawControl(QStyle::CE_TabBarTabShape, opt);
            painter.save();
    
            QSize s = opt.rect.size();
            s.transpose();
            QRect r(QPoint(), s);
            r.moveCenter(opt.rect.center());
            opt.rect = r;
    
            QPoint c = tabRect(i).center();
            painter.translate(c);
            painter.rotate(90);
    
            painter.translate(-c);
            painter.drawControl(QStyle::CE_TabBarTabLabel, opt);
            painter.restore();
        }
    
        QWidget::paintEvent(event);
    }
    

    I have checked the X and Y for TabBarLabel:
    qDebug() << "x: " << tabControl->tabBar()->tabButton(tab2Index, QTabBar::RightSide)->pos().x() << " | y:" << tabControl->tabBar()->tabButton(tab2Index, QTabBar::RightSide)->pos().y(); it returns x: 52 | y: 39

    This issue only happens for tab position - QTabWidget::West and QTabWidget::East. Any ideas how to fix it? Thank you.



  • I think the following: s.transpose(); swaps the width and height around in order to display the vertical tabs correctly. But the tab bar close button does not know about it and wrongly calculates the position. I need to reimplement the method where tab bar close button makes the calculation/drawing on a tab to fix this issue. Now, the question is what is the name of such method. I think it could be one of this two methods:

    1. AppTabBar::paintEvent
    2. TabBarLabel::paintEvent
    


  • Hello!

    Finally! I have fixed the issue with vertical tab close button position.

    Code:

    if (tabPos != AppTabPosition::Top && tabPos != AppTabPosition::Bottom) {
        s.transpose();
    
        if (this->tabsClosable()) { // check if tab is closable
            QRect optRect = opt.rect;
            optRect.setX(90); // set X pos of close button
            optRect.setY(optRect.y() + 8); // calcs the Y pos of close button
            optRect.setSize(QSize(12, 12));
            this->tabButton(i, QTabBar::RightSide)->setGeometry(optRect);
       }
    }
    

    Here I do not change the default opt.rect but instead I copy it to new QRect. Then I change the size and position of optRect and finally set geometry to tabButtton .

    Screenshot:

    alt text

    The issue is resolved.


Log in to reply