title: Giving a color to single tab consumes too much processing power
body: <p>After lots of research, I managed to customize the <code>QTabWidget</code> in PyQt5 (Python 3.6) such that I can assign a different color to an arbitrary tab:</p>
  
  <p><a href="https://i.stack.imgur.com/nzooE.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/nzooE.png" alt="enter image description here"></a></p>
  
  <p>Yes, I know that one can manipulate certain tabs using CSS-selectors like:</p>
  
  <ul>
  <li><code>QTabBar::tab:selected</code></li>
  <li><code>QTabBar::tab:hover</code></li>
  <li><code>QTabBar::tab:selected</code></li>
  <li><code>QTabBar::tab:!selected</code></li>
  </ul>
  
  <p>But none of these selectors solves the actual problem I have. If I want to highlight the second tab - no matter if it is selected, hovered, ... - neither of these CSS-selectors help me.</p>
  
  <p>I will now explain how I got it eventually working. After that, I'll show where the computation-intensive part is, and why I can't get that out. Hopefully you can help me to improve the efficiency.</p>
  
  <hr>
  
  <h2>The code</h2>
  
  <p>Below you can find the source code of my solution. To try it out yourself, just copy-paste the code into a new file (like <code>tab_test.py</code>) and run it. Below the code you find more explanations.</p>
  
  <pre><code>import sys
  
  from PyQt5.QtWidgets import *
  from PyQt5.QtCore import *
  from PyQt5.QtGui import *
  
  
  #########################################################
  #             STYLESHEET FOR QTABWIDGET                 #
  #########################################################
  def get_QTabWidget_style():
      styleStr = str("""
          QTabWidget::pane {             
              border-width: 2px;         
              border-style: solid;       
              border-color: #0000ff;         
              border-radius: 6px;        
          }                              
          QTabWidget::tab-bar {          
              left: 5px;                 
          }                              
      """)
      return styleStr
  
  #########################################################
  #               STYLESHEET FOR QTABBAR                  #
  #########################################################
  def get_QTabBar_style():
      styleStr = str("""
          QTabBar {                                          
              background: #00ffffff;                         
              color: #ff000000;                              
              font-family: Courier;                          
              font-size: 12pt;                               
          }                                                  
          QTabBar::tab {                  
              background: #00ff00;                         
              color: #000000;                              
              border-width: 2px;                             
              border-style: solid;                           
              border-color: #0000ff;                             
              border-bottom-color: #00ffffff;                
              border-top-left-radius: 6px;                   
              border-top-right-radius: 6px;                  
              min-height: 40px;                              
              padding: 2px;                                  
          }                                                  
          QTabBar::tab:selected {                            
              border-color: #0000ff;                             
              border-bottom-color: #00ffffff;                
          }                                                  
          QTabBar::tab:!selected {                           
              margin-top: 2px;                               
          }                                                  
          QTabBar[colorToggle=true]::tab {                   
              background: #ff0000;                         
          }                                                  
      """)
  
      return styleStr
  
  
  #########################################################
  #                  SUBCLASS QTABBAR                     #
  #########################################################
  class MyTabBar(QTabBar):
      def __init__(self, *args, **kwargs):
          super(MyTabBar, self).__init__(*args, **kwargs)
          self.__coloredTabs = []
          self.setProperty("colorToggle", False)
  
      def colorTab(self, index):
          if (index &gt;= self.count()) or (index &lt; 0) or (index in self.__coloredTabs):
              return
          self.__coloredTabs.append(index)
          self.update()
  
      def uncolorTab(self, index):
          if index in self.__coloredTabs:
              self.__coloredTabs.remove(index)
              self.update()
  
      def paintEvent(self, event):
          painter = QStylePainter(self)
          opt = QStyleOptionTab()
          painter.save()
  
          for i in range(self.count()):
              self.initStyleOption(opt, i)
              if i in self.__coloredTabs:
                  self.setProperty("colorToggle", True)
                  self.style().unpolish(self)
                  self.style().polish(self)
  
                  painter.drawControl(QStyle.CE_TabBarTabShape, opt)
                  painter.drawControl(QStyle.CE_TabBarTabLabel, opt)
              else:
                  self.setProperty("colorToggle", False)
                  self.style().unpolish(self)
                  self.style().polish(self)
  
                  painter.drawControl(QStyle.CE_TabBarTabShape, opt)
                  painter.drawControl(QStyle.CE_TabBarTabLabel, opt)
  
          painter.restore()
  
  #########################################################
  #                SUBCLASS QTABWIDGET                    #
  #########################################################
  class MyTabWidget(QTabWidget):
      def __init__(self, *args, **kwargs):
          super(MyTabWidget, self).__init__(*args, **kwargs)
          self.myTabBar = MyTabBar()
          self.setTabBar(self.myTabBar)
          self.setTabsClosable(True)
  
          self.setStyleSheet(get_QTabWidget_style())
          self.tabBar().setStyleSheet(get_QTabBar_style())
  
      def colorTab(self, index):
          self.myTabBar.colorTab(index)
  
      def uncolorTab(self, index):
          self.myTabBar.uncolorTab(index)
  
  
  
  
  '''=========================================================='''
  '''|                  CUSTOM MAIN WINDOW                    |'''
  '''=========================================================='''
  class CustomMainWindow(QMainWindow):
  
      def __init__(self):
          super(CustomMainWindow, self).__init__()
  
          # -------------------------------- #
          #           Window setup           #
          # -------------------------------- #
  
          # 1. Define the geometry of the main window
          # ------------------------------------------
          self.setGeometry(100, 100, 800, 800)
          self.setWindowTitle("Custom TabBar test")
  
          # 2. Create frame and layout
          # ---------------------------
          self.__frm = QFrame(self)
          self.__frm.setStyleSheet("QWidget { background-color: #efefef }")
          self.__lyt = QVBoxLayout()
          self.__frm.setLayout(self.__lyt)
          self.setCentralWidget(self.__frm)
  
          # 3. Insert the TabMaster
          # ------------------------
          self.__tabMaster = MyTabWidget()
          self.__lyt.addWidget(self.__tabMaster)
  
          # 4. Add some dummy tabs
          # -----------------------
          self.__tabMaster.addTab(QFrame(), "first")
          self.__tabMaster.addTab(QFrame(), "second")
          self.__tabMaster.addTab(QFrame(), "third")
          self.__tabMaster.addTab(QFrame(), "fourth")
  
          # 5. Color a specific tab
          # ------------------------
          self.__tabMaster.colorTab(1)
  
  
          # 6. Show window
          # ---------------
          self.show()
  
      ''''''
  
  '''=== end Class ==='''
  
  
  if __name__ == '__main__':
      app = QApplication(sys.argv)
      QApplication.setStyle(QStyleFactory.create('Fusion'))
      myGUI = CustomMainWindow()
      sys.exit(app.exec_())
  
  ''''''
  </code></pre>
  
  <hr>
  
  <h2>The code explained</h2>
  
  <p><b>1. Dynamic stylesheets</b><br>
  I've got a stylesheet for the QTabWidget and one for the QTabBar. The magic is in the last one. The background color of the tab (denoted by the CSS-selector <code>QTabBar::tab</code>) is generally green <code>#00ff00</code>. But when the <code>colorToggle</code> property is on, the color is set to red <code>#ff0000</code>.</p>
  
  <p>&nbsp;<br>
  <b>2. class MyTabBar</b><br>
  I subclass <code>QTabBar</code> into a new class <code>MyTabBar</code>. In this way, I can do two things:</p>
  
  <ul>
  <li><p>I add a function <code>colorTab(index)</code> such that external code can call it to color an arbitrary tab.</p></li>
  <li><p>I override the <code>paintEvent(event)</code> function such that I can apply the color on the chosen tabs.</p></li>
  </ul>
  
  <p>The <code>colorTab(index)</code> function simply takes an index and adds it to a list. That's it. The list will be checked in the overridden <code>paintEvent(event)</code> function.</p>
  
  <p>After checking the list, the <code>paintEvent(event)</code> function decides whether it should set or clear the property <code>"colorToggle"</code>:</p>
  
  <pre><code>    self.setProperty("colorToggle", True)
  </code></pre>
  
  <p>After setting (or clearing) this property, the <code>paintEvent(event)</code> function proceeds to paint the actual tab:</p>
  
  <pre><code>    self.style().unpolish(self)
      self.style().polish(self)
  
      painter.drawControl(QStyle.CE_TabBarTabShape, opt)
      painter.drawControl(QStyle.CE_TabBarTabLabel, opt)
  </code></pre>
  
  <p>&nbsp;<br></p>
  
  <blockquote>
    <p>I have noticed that <code>self.style().unpolish(self)</code> and <code>self.style().polish(self)</code> consume a lot of processing power. But deleting them results in failure. I don't know any (less computational-intensive) alternative.</p>
  </blockquote>
  
  <p>&nbsp;<br>
  <b>3. class MyTabWidget</b><br>
  I've also subclassed the <code>QTabWidget</code> class. In its constructor, I replace the default <code>QTabBar</code> by my own subclassed <code>MyTabBar</code>. After that, I apply my stylesheets.</p>
  
  <p>&nbsp;<br>
  <b>4. class CustomMainWindow</b><br>
  I create a main window (subclassed from <code>QMainWindow</code>) to simply test the new Tab Widget. That's very simple. I instantiate <code>MyTabWidget()</code> and insert some dummy tabs into it.<br>
  Then I color the second one (note: tab counting starts from 0).</p>
  
  <hr>
  
  <h2>The problem explained</h2>
  
  <p>The problem is all in the lines:</p>
  
  <pre><code>    self.style().unpolish(self)
      self.style().polish(self)
  </code></pre>
  
  <p>inside the overridden <code>paintEvent(event)</code> function. They take some execution time, which is a problem because the paintEvent function gets called very regularly. My processor runs at 14% for this simple example (I have a 4Ghz watercooled i7 processor). Such processor load is simply unacceptable.</p>
  
  <hr>
  
  <h2>The platform/environment</h2>
  
  <p>I'm running on:</p>
  
  <ul>
  <li>Python 3.6.3</li>
  <li>PyQt5</li>
  <li>Windows 10 (but please feel free to post your solution if it works on Linux)</li>
  </ul>
  
  <p>Apparently the widget-style seems to be important. On the last lines of the sample code, you can see:</p>
  
  <pre><code>    QApplication.setStyle(QStyleFactory.create('Fusion'))
  </code></pre>
  
  <p>That widget-style should be consistently the same - both on Windows and Linux. But again - please feel free to post your solution if it works on another non-Fusion style.</p>
  
  <hr>
  
  <h2>First proposed solution</h2>
  
  <p>I was recommended to take a look here: <a href="https://stackoverflow.com/questions/46137500/qt-tabwidget-each-tab-title-background-color">Qt TabWidget Each tab Title Background Color</a></p>
  
  <p>A solution is proposed: Subclass <code>QTabBar</code> and override the <code>paintEvent(event)</code> function. That's quite similar to the solution I already have above, but the code inside the <code>paintEvent(event)</code> function is different. So I give it a try.</p>
  
  <p>First, I translate the given C++ code into Python:</p>
  
  <pre><code>    def paintEvent(self, event):
          painter = QStylePainter(self)
          opt = QStyleOptionTab()
  
          for i in range(self.count()):
              self.initStyleOption(opt, i)
              if i in self.__coloredTabs:
                  opt.palette.setColor(QPalette.Button, QColor("#ff0000"))
              painter.drawControl(QStyle.CE_TabBarTabShape, opt)
              painter.drawControl(QStyle.CE_TabBarTabLabel, opt)
  </code></pre>
  
  <p>Now I replace my previous <code>paintEvent(event)</code> function with this code. I run the file ... but all tabs are green :-(</p>
  
  <p>There must be something I'm doing wrong?</p>
  
  <p><strong>EDIT :</strong><br>
  Apparently the tab didn't color because I was mixing <code>stylesheets</code> with <code>QPalette</code> changes. I was suggested to comment out all calls to <code>setStyleSheet(..)</code> and try again. Indeed, the intended tab gets the new color. But I lose all my styles... So this won't really help me.</p>
  
  <hr>
  
  <h2>Second proposed solution</h2>
  
  <p>Musicamante has proposed a solution based on <code>QStyleOption</code> helper classes. Please look below to see his answer. I've inserted his solution into my own sample code:</p>
  
  <pre><code>import sys
  
  from PyQt5.QtWidgets import *
  from PyQt5.QtCore import *
  from PyQt5.QtGui import *
  
  
  #########################################################
  #             STYLESHEET FOR QTABWIDGET                 #
  #########################################################
  def get_QTabWidget_style():
      styleStr = str("""
          QTabWidget::pane {             
              border-width: 2px;         
              border-style: solid;       
              border-color: #0000ff;         
              border-radius: 6px;        
          }                              
          QTabWidget::tab-bar {          
              left: 5px;                 
          }                              
      """)
      return styleStr
  
  #########################################################
  #               STYLESHEET FOR QTABBAR                  #
  #########################################################
  def get_QTabBar_style():
      styleStr = str("""
          QTabBar {                                          
              background: #00ffffff;                         
              color: #ff000000;                              
              font-family: Courier;                          
              font-size: 12pt;                               
          }                                                  
          QTabBar::tab {                  
              background: #00ff00;                         
              color: #000000;                              
              border-width: 2px;                             
              border-style: solid;                           
              border-color: #0000ff;                             
              border-bottom-color: #00ffffff;                
              border-top-left-radius: 6px;                   
              border-top-right-radius: 6px;                  
              min-height: 40px;                              
              padding: 2px 12px;                                  
          }                                                  
          QTabBar::tab:selected {                            
              border-color: #0000ff;                             
              border-bottom-color: #00ffffff;                
          }                                                  
          QTabBar::tab:!selected {                           
              margin-top: 2px;                               
          }                                                  
          QTabBar[colorToggle=true]::tab {                   
              background: #ff0000;                         
          }                                                  
      """)
  
      return styleStr
  
  
  #########################################################
  #                  SUBCLASS QTABBAR                     #
  #########################################################
  class MyTabBar(QTabBar):
      def __init__(self, parent):
          QTabBar.__init__(self, parent)
          self.colorIndexes = parent.colorIndexes
  
      def paintEvent(self, event):
          qp = QPainter(self)
          qp.setRenderHints(qp.Antialiasing)
          option = QStyleOptionTab()
          option.features |= option.HasFrame
          palette = option.palette
          for index in range(self.count()):
              self.initStyleOption(option, index)
              palette.setColor(palette.Button, self.colorIndexes.get(index, QColor(Qt.green)))
              palette.setColor(palette.Window, QColor(Qt.blue))
              option.palette = palette
              self.style().drawControl(QStyle.CE_TabBarTab, option, qp)
  
  
  #########################################################
  #                SUBCLASS QTABWIDGET                    #
  #########################################################
  class MyTabWidget(QTabWidget):
      def __init__(self):
          QTabWidget.__init__(self)
          self.colorIndexes = {
              1: QColor(Qt.red), 
              3: QColor(Qt.blue), 
              }
          self.setTabBar(MyTabBar(self))
  
          self.tabBar().setStyleSheet(get_QTabBar_style())
          self.setStyleSheet(get_QTabWidget_style())
          self.setTabsClosable(True)
  
  
  
  '''=========================================================='''
  '''|                  CUSTOM MAIN WINDOW                    |'''
  '''=========================================================='''
  class CustomMainWindow(QMainWindow):
  
      def __init__(self):
          super(CustomMainWindow, self).__init__()
  
          # -------------------------------- #
          #           Window setup           #
          # -------------------------------- #
  
          # 1. Define the geometry of the main window
          # ------------------------------------------
          self.setGeometry(100, 100, 800, 800)
          self.setWindowTitle("Custom TabBar test")
  
          # 2. Create frame and layout
          # ---------------------------
          self.__frm = QFrame(self)
          self.__frm.setStyleSheet("QWidget { background-color: #efefef }")
          self.__lyt = QVBoxLayout()
          self.__frm.setLayout(self.__lyt)
          self.setCentralWidget(self.__frm)
  
          # 3. Insert the TabMaster
          # ------------------------
          self.__tabMaster = MyTabWidget()
          self.__lyt.addWidget(self.__tabMaster)
  
          # 4. Add some dummy tabs
          # -----------------------
          self.__tabMaster.addTab(QFrame(), "first")
          self.__tabMaster.addTab(QFrame(), "second")
          self.__tabMaster.addTab(QFrame(), "third")
          self.__tabMaster.addTab(QFrame(), "fourth")
  
          # 5. Show window
          # ---------------
          self.show()
  
      ''''''
  
  '''=== end Class ==='''
  
  
  if __name__ == '__main__':
      app = QApplication(sys.argv)
      QApplication.setStyle(QStyleFactory.create('Fusion'))
      myGUI = CustomMainWindow()
      sys.exit(app.exec_())
  
  ''''''
  </code></pre>
  
  <p>The result gets pretty close to the desired outcome:</p>
  
  <p><a href="https://i.stack.imgur.com/giTYU.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/giTYU.png" alt="enter image description here"></a></p>
  
  <p>Musicamante says:</p>
  
  <blockquote>
    <p>The only issue here is that the tab border does not use stylesheets (I wasn't able to find how QStyle draws them), so the radius is smaller and the pen width is thinner.</p>
  </blockquote>
  
  <p>Thank you very much @musicamante! There is still one issue (the borders) but the result is the closest we ever got to the solution.</p>