1

I am trying to have FigureCanvasQTAgg inside QMdiSubWindow such that the user can create his/her own plots on the fly. I have made this very small self contained code:

from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import sys

class ExampleApp(QtGui.QMainWindow):
    def __init__(self):
        super(self.__class__, self).__init__()
        self.mdiarea = QtGui.QMdiArea()
        self.setCentralWidget(self.mdiarea)
        sub = QtGui.QMdiSubWindow(self.mdiarea)
        fig = Figure()
        p = FigureCanvas(fig)
        sub.layout().addWidget(p)
        sub.show()

def main():
    app = QtGui.QApplication(sys.argv)
    form = ExampleApp()
    form.show()
    app.exec_()

if __name__ == '__main__':
    main()

The problem occurs when i run the program and try to minimize the QtGui.QMdiSubWindow object. When I do that the program segfaults and exits with no error description. This could be an error in qt, in the python bindings or in the FigureCanvasQTAgg object. Of course it could also be me who just use these objects wrongly. Please help me understand why the segfault occurs when i minimize the subwindow and help me figure out how i can solve this problem. Thank you.

My environment is ubuntu 14.04 and using Qt version: 4.8.7 SIP version: 4.16.9 PyQt version: 4.11.4 MatplotLib version: 1.5.0

Here is an example of drag and drop properties set. It seems that there are issues with that as well.

from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import sys

class QtZListView(QtGui.QListView):
    def __init__(self, *args, **kwargs):
        QtGui.QListView.__init__(self, *args, **kwargs)
        self.model = QtGui.QStringListModel(['a','b','c'])
        self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
        self.setModel(self.model)
        self.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
        self.setDragEnabled(True)

    def setStringList(self, *args, **kwargs):
        return self.model.setStringList(*args, **kwargs)

class mplsubwindow(QtGui.QMdiSubWindow):

    def __init__(self, *args, **kwargs):
        QtGui.QMdiSubWindow.__init__(self, *args, **kwargs)
        self.setWindowTitle("testing")
        self.setAcceptDrops(True)
        self.resize(400,400)
        self.show()

    def dragEnterEvent(self, event):
        print('entering')
        super(mplsubwindow, self).dragEnterEvent(event)

    def dragMoveEvent(self, event):
        print('drag moving')
        super(mplsubwindow, self).dragMoveEvent(event)

    def dropEvent(self, event):
        print('dropped')
        super(mplsubwindow, self).dropEvent(event)

class ExampleApp(QtGui.QMainWindow):
    def __init__(self):
        super(self.__class__, self).__init__()
        mainwid = QtGui.QWidget()
        self.mdiarea = QtGui.QMdiArea()

        layout = QtGui.QGridLayout(mainwid)
        layout.addWidget(self.mdiarea)
        sub = mplsubwindow(self.mdiarea)
        sub.show()
        layout.addWidget(QtZListView())
        self.setCentralWidget(mainwid)
        #self.setWidget(mainwid)

def main():
    app = QtGui.QApplication(sys.argv)
    form = ExampleApp()
    form.show()
    app.exec_()

if __name__ == '__main__':
    main()
plaindev
  • 131
  • 1
  • 1
  • 9
  • On Windows 10, matplotlib v1.3.1, PyQt v4.11.1, I don't get a segfault, but I do get warnings about invalid width/height for the matplotlib renderer. These go away if I add the widget via `sub.setWidget(p)` rather than through the layout. Let me know if that solves your problem, so I can post it as an answer! – three_pineapples Mar 14 '16 at 00:10
  • Can you report this as a bug to mpl? The problem seems to be that when the window is minimized the requested height of the canvas is negative which Agg does not like. – tacaswell Mar 14 '16 at 03:03
  • See fix here https://github.com/matplotlib/matplotlib/pull/6152 – tacaswell Mar 14 '16 at 03:20
  • three_pineapples, that actually seems to solve the problem. Thank you very much. – plaindev Mar 14 '16 at 06:59
  • three_pineapples, however that means i can only add a single widget to the canvas i think...because if i want to add 2 or more and call setwidget on each it will replace the previous. – plaindev Mar 14 '16 at 07:18
  • I've added an answer that demonstrates creating and using your own layout. P.S. Welcome to stack overflow! You should prefix peoples user name with a @ symbol if you want them to get a notification of your comment. Like this: @plaindev – three_pineapples Mar 14 '16 at 09:14
  • @three_pineapples thank you for that. Still learning yeah. I have updated the above question with an additional code snippet - which displays the problem with drag and drop also. It seems even with setwidget as you proposed, and completely removing matplotlib dependency it still cannot accept drop events. – plaindev Mar 14 '16 at 10:25
  • @plaindev you shouldn't really be adding more to this question. It's better to ask a new question (as you have already done anyway) and leave this one as it was originally so that it can help other people in the future. – three_pineapples Mar 14 '16 at 20:34
  • @plaindev Can you revert the edits that ask a different question (and link to where ever the new question is)? – tacaswell Mar 16 '16 at 13:47
  • Yes definitly. I will do that – plaindev Mar 16 '16 at 18:38

2 Answers2

1

The issue seems to be that when minimized the widget has a negative height (I guess that makes sense, but I can not find any documentation of this fact; I noticed this by adding some print statements). The solution is to just not draw in those cases. I have submitted a PR to fix this upstream, but you might need to monky patch matplotlib.backends.backend_qt5agg.FigureCanvasQTAggBase.__draw_idle_agg with:

def __draw_idle_agg(self, *args):
    if self.height() < 0 or self.width() < 0:
        self._agg_draw_pending = False
        return
    try:
        FigureCanvasAgg.draw(self)
        self.update()
    finally:
        self._agg_draw_pending = False

Note that the qt5 in the module is not a typo, the Qt4 functionality is derived from the Qt5 support.

tacaswell
  • 84,579
  • 22
  • 210
  • 199
  • Thank you very much for your quick answer. It seems you have already reported this bug upstream, so I don't need to take any action? Do you have any idea more or less how long it will be before this patch is released? – plaindev Mar 14 '16 at 06:55
1

The issue seems to revolve around an incorrect size being reported for the matplotlib widget. As @tcaswell points out, matplotlib should be fixed to ensure this doesn't cause a segfault.

I'm going to attack the problem from the other side and try to stop Qt from reporting bogus dimensions. It seems that using the "inbuilt" layout causes the issue. This is likely because the existence of the layout is inherited by QMdiSubWindow from QWidget, but the implementation of QMdiSubWindow likely does not use it correctly. As long as you use the QMdiSubWindow.setWidget() method, and create your own layouts, the segfault is avoided.

Here is some example code with a layout that you manage yourself:

p = FigureCanvas(fig)
container = QtGui.QWidget()
layout = QtGui.QVBoxLayout(container)
layout.addWidget(p)        
sub.setWidget(container)

EDIT

If you look at the underlying C++ implementation you can see that a call to QMdiSubWindow.setWidget() is a lot more complicated than just adding the widget to the layout!

Community
  • 1
  • 1
three_pineapples
  • 11,579
  • 5
  • 38
  • 75