[PySide] QCloseEvent from one GUI getting passed to delegate of another GUI?

We ran into a really strange bug at work. We have a GUI with a tree view and some delegates, and it works just fine. While that GUI was open, a user opened and closed another GUI. This second GUI had an unhandled exception in the close event. After that point, the first GUI started throwing all sorts of errors. It was complaining that the delegate paint function was receiving a QCloseEvent instead of a QPainter. The delegate is called once for every cell in the tree, so that is a lot of errors. The only way to make it stop was to restart Maya.

I made a minimal test case. I discovered that the error happens only when I am using QTreeView. If I use QListView, there is no problem. Here is the code:

import shiboken

from PySide import QtCore, QtGui
from maya import OpenMayaUI

#------------------------------------------------------------------------------
def getMayaMainWindow():
    parentWindow = OpenMayaUI.MQtUtil.mainWindow()
    if parentWindow:
        return shiboken.wrapInstance(long(parentWindow), QtGui.QWidget)

#------------------------------------------------------------------------------
class ErrorGUI(QtGui.QMainWindow):
    def __init__(self, parent=getMayaMainWindow()):
        super(ErrorGUI, self).__init__(parent)
        
        self.setWindowTitle('Close event error')
        self.setObjectName('closeEventErrorWindow')
        
        self.centralWidget = QtGui.QWidget()
        self.setCentralWidget(self.centralWidget)
        
        self._foo = []

    def closeEvent(self, event):
        x = self._foo[0][-1]

#------------------------------------------------------------------------------
class SelectedItemDelegate(QtGui.QStyledItemDelegate):
    """ A delegate that ensures the text color of selected items matches what is
    specified in the model's ForegroundRole.
    
    Without this delegate, the text of selected items is always white, instead
    of the color specified in the model.
    
    """
    #----- Overridden functions ------------------------------------------------
    def paint(self, qPainter, option, qModelIndex):
        if (option.state & QtGui.QStyle.State_Selected):
            textColor = qModelIndex.data(QtCore.Qt.ForegroundRole)
            if textColor is not None:
                brush = QtGui.QBrush(textColor)
                option.palette.setBrush(QtGui.QPalette.HighlightedText, brush)
        super(SelectedItemDelegate, self).paint(qPainter, option, qModelIndex)

#------------------------------------------------------------------------------
class MyListModel(QtCore.QAbstractListModel):
    def __init__(self, parent=None):
        super(MyListModel, self).__init__(parent)
        
        self._data = []
        self._fgColors = []
    
    #----- Overridden functions -----------------------------------------------
    def data(self, index, role):
        if not index.isValid():
            return None
        
        if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
            return self._data[index.row()]
        
        elif role == QtCore.Qt.ForegroundRole:
            return self._fgColors[index.row()]
    
    #--------------------------------------------------------------------------
    def rowCount(self, parentIndex):
        return len(self._data)
    
    #----- Other functions ----------------------------------------------------
    def setModelData(self, stringList, foregroundColorList):
        if len(stringList) != len(foregroundColorList):
            print 'Arrays do not match in size'
            return
        self._data = stringList
        self._fgColors = foregroundColorList
        
#------------------------------------------------------------------------------
class DelegateGUI(QtGui.QMainWindow):
    def __init__(self, parent=getMayaMainWindow()):
        super(DelegateGUI, self).__init__(parent)
        
        self.setWindowTitle('GUI with delegate')
        self.setObjectName('testDelegateWindow')
        
        # # QListView version.  DOES NOT BREAK
        # self.listView = QtGui.QListView()
        # self.listModel = MyListModel()
        # self.fillListModel()
        # self.listView.setModel(self.listModel)
        # textColorDelegate = SelectedItemDelegate()
        # self.listView.setItemDelegate(textColorDelegate)
        # self.setCentralWidget(self.listView)
        
        # QTreeView version.  THIS BREAKS!
        self.treeView = QtGui.QTreeView()
        self.listModel = MyListModel()
        self.fillListModel()
        self.treeView.setModel(self.listModel)
        textColorDelegate = SelectedItemDelegate()
        self.treeView.setItemDelegate(textColorDelegate)
        self.setCentralWidget(self.treeView)
    
    def fillListModel(self):
        items = ['alpha', 'bravo', 'charlie', 'delta']
        color = QtGui.QColor(225, 130, 0)
        colors = [None, color, color, None]
        self.listModel.setModelData(items, colors)

#------------------------------------------------------------------------------
def openErrorWin():
    '''This ensures that only one instance of the UI is open at a time.'''
    global errorWin
    try: errorWin.close()
    except: pass
    errorWin = ErrorGUI()
    errorWin.setAttribute(QtCore.Qt.WA_DeleteOnClose)
    errorWin.show()
    return errorWin

#------------------------------------------------------------------------------
def openDelegateWin():
    '''This ensures that only one instance of the UI is open at a time.'''
    global delegateWin
    try: delegateWin.close()
    except: pass
    delegateWin = DelegateGUI()
    delegateWin.setAttribute(QtCore.Qt.WA_DeleteOnClose)
    delegateWin.show()
    return delegateWin

Then in Maya, I run the following:

import closeEventError
dWin = closeEventError.openDelegateWin()
eWin = closeEventError.openErrorWin()

[ol]
[li]At this point there are two windows open, and everything is fine.
[/li][li]Close the window titled “Close event error”.
[/li][li]Put the focus back on the window titled “GUI with delegate”.
[/li][li]ERRORS!
[/li][/ol]

The first error, from the close event of one GUI is as follows:

# Traceback (most recent call last):
#   File "U:/maya/2016/scripts\closeEventError.py", line 26, in closeEvent
#     x = self._foo[0][-1]
# IndexError: list index out of range

The following errors are from the delegate of the other GUI, one error per cell:


# Traceback (most recent call last):
#   File "U:/maya/2016/scripts\closeEventError.py", line 44, in paint
#     super(SelectedItemDelegate, self).paint(qPainter, option, qModelIndex)
# TypeError: # 'PySide.QtGui.QStyledItemDelegate.paint' called with wrong argument types:
#   PySide.QtGui.QStyledItemDelegate.paint(PySide.QtGui.QCloseEvent, PySide.QtGui.QStyleOptionViewItem, PySide.QtCore.QModelIndex)
# Supported signatures:
  PySide.QtGui.QStyledItemDelegate.paint(PySide.QtGui.QPainter, PySide.QtGui.QStyleOptionViewItem, PySide.QtCore.QModelIndex)
# Traceback (most recent call last):
#   File "U:/maya/2016/scripts\closeEventError.py", line 44, in paint
#     super(SelectedItemDelegate, self).paint(qPainter, option, qModelIndex)
# TypeError: # 'PySide.QtGui.QStyledItemDelegate.paint' called with wrong argument types:
#   PySide.QtGui.QStyledItemDelegate.paint(PySide.QtGui.QCloseEvent, PySide.QtGui.QStyleOptionViewItem, PySide.QtCore.QModelIndex)
# Supported signatures:
  PySide.QtGui.QStyledItemDelegate.paint(PySide.QtGui.QPainter, PySide.QtGui.QStyleOptionViewItem, PySide.QtCore.QModelIndex)
# Traceback (most recent call last):
#   File "U:/maya/2016/scripts\closeEventError.py", line 44, in paint
#     super(SelectedItemDelegate, self).paint(qPainter, option, qModelIndex)
# TypeError: # 'PySide.QtGui.QStyledItemDelegate.paint' called with wrong argument types:
#   PySide.QtGui.QStyledItemDelegate.paint(PySide.QtGui.QCloseEvent, PySide.QtGui.QStyleOptionViewItem, PySide.QtCore.QModelIndex)
# Supported signatures:
  PySide.QtGui.QStyledItemDelegate.paint(PySide.QtGui.QPainter, PySide.QtGui.QStyleOptionViewItem, PySide.QtCore.QModelIndex)
# Traceback (most recent call last):
#   File "U:/maya/2016/scripts\closeEventError.py", line 44, in paint
#     super(SelectedItemDelegate, self).paint(qPainter, option, qModelIndex)
# TypeError: # 'PySide.QtGui.QStyledItemDelegate.paint' called with wrong argument types:
#   PySide.QtGui.QStyledItemDelegate.paint(PySide.QtGui.QCloseEvent, PySide.QtGui.QStyleOptionViewItem, PySide.QtCore.QModelIndex)
# Supported signatures:
  PySide.QtGui.QStyledItemDelegate.paint(PySide.QtGui.QPainter, PySide.QtGui.QStyleOptionViewItem, PySide.QtCore.QModelIndex)

Okay, I found a way to make the errors stop, but it makes no sense. I made a dummy tree view class that does nothing but override the paint event. Even then, it just calls the superclass paint event. Essentially it is doing nothing, but using it makes the problem go away.

class CustomTreeView(QtGui.QTreeView):
    def paintEvent(self, qPaintEvent):
        super(CustomTreeView, self).paintEvent(qPaintEvent)

Then, replace

self.treeView = QtGui.QTreeView()

with

self.treeView = CustomTreeView()

I don’t get the paint() error when using your code.

Is your delegate being used in two places by chance? Are you importing the module with the delegate into other modules? Are you reloading python modules (I have fond that reloading modules with Qt code in it can cause issues)

I guess I should have mentioned I am using Maya 2016 with Extension 1 (not Extension 2) and Service Pack 6.

In my real code, the delegate is being used in two places. But for the test case, the code shown above is the complete code. And I’m not really reloading modules because once the error happens, I have to restart Maya to get it to stop.