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.