Search code examples
qtqtabbar

How to pin a tab in Qt


Is it possible to pin a tab with Qt?

I want a tab to always stay in place (index 0) while still able to move other tabs.

So far I tried to listen to QTabBar::tabMoved and revert the move but that's too late. I don't want it even to attempt to move.

Worst case for me would be to be forced to change the mouse handling. Let me know please if there is an other way.


Solution

  • I have never found a nice way to do that. But, I used the fact that you can store raw data in the QTabBar to pin the tabs and undo a move if it was a pinned tab. It's not perfect and I still have some ugly behavior, but I didn't want to use mouse events, neither.

    First, create a struct to store the current state of a tab:

    struct PinnedTab
    {
        bool isPinned;
        int currentIndex;
    };
    
    Q_DECLARE_METATYPE(PinnedTab); // For QVariant
    

    Then, create a custom QTabBar to handle the move and use QTabWidget to replace the current tab bar (you have to do that before inserting the tabs):

    class Bar: public QTabBar
    {
    public:
        void pin(int const index)
        {
            PinnedTab info;
            info.isPinned = true;
            info.currentIndex = index; // TODO: move the tab to the left and do not use current index
            setTabData(index, QVariant::fromValue(info));
        }
    
        Bar(QWidget* parent=nullptr): QTabBar(parent)
        {}
    
        virtual void tabLayoutChange() override
        {
            for (int i = 0; i != count(); ++i) // Check if a pinned tab has moved
            {
                if (tabData(i).isValid())
                {
                    PinnedTab const info = tabData(i).value<PinnedTab>();
                    if (info.isPinned == true && i != info.currentIndex) {
                        rollbackLayout();
                        return;
                    }
                }
            }
    
            for (int i = 0; i != count(); ++i)
            {
                if (tabData(i).isValid())
                {
                    PinnedTab info = tabData(i).value<PinnedTab>();
                    info.currentIndex = i;
                    setTabData(i, QVariant::fromValue(info));
                }
                else
                {
                    PinnedTab info;
                    info.isPinned = false;
                    info.currentIndex = i;
                    setTabData(i, QVariant::fromValue(info));
                }
            }
        }
    
        void rollbackLayout() {
            for (int i = 0; i != count(); ++i)
            {
                if (tabData(i).isValid())
                {
                    PinnedTab const info = tabData(i).value<PinnedTab>();
                    if (i != info.currentIndex) {
                        moveTab(i, info.currentIndex);
                    }
                }
            }
        }
    };
    

    tabLayoutChange is called when the layout has changed. So, it will be called when you move a tab.

    the rollbackLayout method is used to move each tab to the last position stored in the tab data.

    Call pin to pin a tab with the given index.

    I simplified my code for more clarity and you may have to redefine some behavior (for now, if you pin a tab, it will keep its current position and it will not handle the insert/remove tabs).