When I was doing GitQlient I wanted to have a QTabWidget where I could pin some tabs. I did some research but I found that I had to tweak/hack how QTabWidget and specially the QTabBar inside they behave. At that time it was not an option since I wanted to have the work done ASAP. But now, I have had a little bit more time and I could manage to pin tabs in a QTabWidget.
I thought it was going to be easier than I expected and I’m aware that I don’t probably cover all cases. However, I think I’ve managed to created a class that inherits from QTabWidget where the tabs can actually be pinned.
In the image above I show how I pinned the first three tabs. The widget puts the pinned tabs in the beginning whereas the unpinned remain after them. Once a tab is unpinned, the widget will remove it from the left part and put the tab as the first one in the right part.
To pin/unpin (and to close a tab) you can do it through the UI using the context menu. This menu appears when you left-click in a tab and it acts over that clicked tab:
In the other hand, if you need to modify that behaviour programmatically, there are two methods you can use for that:
int addPinnedTab(QWidget *page, const QString &label); int addPinnedTab(QWidget *page, const QIcon &icon, const QString &label);
Both methods perform the same action that they do in QTabWidget but with the addition that they move the pinned tab to the beginning of the QTabBar.
Explaining the code
You can find the code in GitHub. In the next sections I’ll do an introduction to the code I wrote and why some things look like they are.
The code can be easily understood by a mid-level Qt developer. But even in that case I had to do some tweaks in order to bypass the normal functionality of the QTabBar.
The first thing I needed, of course, was to create a class that inherits from QTabWidget. In that class I’ve created two addPinnedTab methods that add the functionality to pin tabs. The main reason is that I need to keep track of what tabs are pinned and to move them to the right place when pinned.
I also had to overwrite the mousePressEvent and mouseReleaseEvent since I want to create a context menu to pin/unpin tabs. The reason for this is that I’m processing the signal tabBarClicked from the QTabWidget. This signal tells me what tab has been clicked and after getting it I need to filter the left click.
Hidding the close button
By default, the QPinnableTabWidget has the close button visible for all the tabs. This is to allow the user to close easily a tab. However this adds an extra layer of complexity since I need to remove that close button for the pinned tabs. In addition, that same button needs to come back if the user decides to unpin the tab.
I dig into the Qt code and I saw that the QTabBar is adding an overloaded QAbstractButton so I did the same thing. I’ve created two different buttons one that adds the close button, and another that doesn’t show anything. I’ve extracted the code for the real close button from the implementation of the QTabBar. The fake one is just a dummy impl, necessary so it doesn’t crash.
Another tweak I had to do is to overload the QTabBar that QTabWidget has by default since I needed to disable the moving of the pinned tabs. I’ve created a class that overloads the mousePressEvent, mouseMoveEvent and mouseReleaseEvent. Here I check what tab index the user wants to move and I filter to only accept the unpinned ones.
Conclusions and code
I’d like to notice that most of the things I needed to do, can be done in Qt official widgets in an easier and more elegant way. Actually, my code seems more complicated than it is. Basically, because I had to fight the default implementation.
I’ve uploaded the code for QPinnableTabWidget as a library in my GitHub space. Please feel free to open bugs or add comments if you liked it.
sounds useful, push upstream please 🙂
So pinning a tab just moves it to the left and hides the close button, and unpinning a tab moves it to the right and shows the close button.
Is that all or am I missing something here?
I’m afraid you’re missing a lot.
It’s not only that. If that was as easy as hiding and showing buttons I wouldn’t have needed ~200 lines. You’re missing that in order to actually pin and replicate that behaviour there are some special needs for QTabWidget.
As I explain in the article, the problem is that QTabWidget gives for granted that it has buttons when you configure it as closable tabs. Since you cannot choose to hide or show the close button, you need to reimplement that logic with custom buttons.
In addition, you need to avoid the movement of the pinned buttons between the unpinned buttons.
Basically what the article says and what you can see here.
Why can’t you just hide the close button with:
tabBar->setTabButton(0, QTabBar::RightSide, 0);
tabBar->setTabButton(0, QTabBar::LeftSide, 0);
I’ve tested and you can actually pass nullptr (better than 0) when adding a pinned tab as you’re suggesting.
I think it’s even a better idea since I wouldn’t have to allocate memory to it.
Thanks for noticing it!
So I have tested this:
QFrame *fff = new QFrame();
QBoxLayout *fffl = new QBoxLayout(QBoxLayout::Direction::LeftToRight, fff);
QPinnableTabWidget *w = new QPinnableTabWidget(fff);
w->addTab(new QFrame(), “asd33”);
w->addTab(new QFrame(), “asd223”);
w->addTab(new QFrame(), “asd44”);
w->addPinnedTab(new QFrame(), “ssssssssssssssss”);
w->addPinnedTab(new QFrame(), “vvvvvvvvvvvv”);
When I moving tabs I have visual bugs (have video record).
Please report it in GitHub and there you can paste the video.