source: sasview/src/sas/qtgui/Perspectives/Invariant/InvariantPerspective.py @ f82ab8c

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since f82ab8c was 1042dba, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 8 years ago

Plottables support in the data object. Additional menu items.

  • Property mode set to 100755
File size: 23.3 KB
Line 
1# global
2import sys
3import os
4from PyQt4 import QtCore
5from PyQt4 import QtGui
6from PyQt4 import QtWebKit
7
8from twisted.internet import threads
9from twisted.internet import reactor
10
11# sas-global
12from sas.sascalc.invariant import invariant
13from sas.sasgui.guiframe.dataFitting import Data1D
14# from sas.qtgui.GuiUtils import *
15from GuiUtils import *
16
17# local
18from UI.TabbedInvariantUI import tabbedInvariantUI
19from InvariantDetails import DetailsDialog
20from Plotter import Plotter
21from InvariantUtils import WIDGETS
22
23# The minimum q-value to be used when extrapolating
24Q_MINIMUM = 1e-5
25# The maximum q-value to be used when extrapolating
26Q_MAXIMUM = 10
27# the ratio of maximum q value/(qmax of data) to plot the theory data
28Q_MAXIMUM_PLOT = 3
29
30
31class MyModel(object):
32    def __init__(self):
33        self._model = QtGui.QStandardItemModel(self)
34
35    def addItem(self, item):
36        item = QtGui.QStandardItem(str(item))
37        self._model.appendRow(item)
38
39class InvariantWindow(tabbedInvariantUI):
40    # The controller which is responsible for managing signal slots connections
41    # for the gui and providing an interface to the data model.
42    def __init__(self, manager=None, parent=None):
43        super(InvariantWindow, self).__init__(parent)
44        self.setWindowTitle("Invariant Perspective")
45        # initial input params
46        self._background = 0.0
47        self._scale = 1.0
48        self._contrast = 1.0
49        self._porod = 1.0
50        self._npoints_low = 10
51        self._npoints_high = 10
52        self._power_low = 4
53
54        self._manager = manager
55        self._reactor = self._manager.reactor()
56        self._model_item = QtGui.QStandardItem()
57
58        self._helpView = QtWebKit.QWebView()
59        self.detailsDialog = DetailsDialog(self)
60        self._plotter = Plotter(self)
61
62        self._low_extrapolate = False
63        self._low_guinier  = True
64        self._low_fit  = True
65        self._high_extrapolate = False
66        self._high_power_value  = False
67
68        self.communicate = Communicate()
69
70        # Mask file selector
71        ###################################################
72        self._path = "cyl_400_20.txt"
73        from sas.sascalc.dataloader.loader import  Loader
74        loader = Loader()
75        try:
76            self._data = loader.load(self._path)
77        except:
78            raise
79        ###################################################
80
81        self.lineEdit_8.setText(str(Q_MINIMUM))
82        self.lineEdit_9.setText(str(Q_MAXIMUM))
83
84        # Let's choose the Standard Item Model.
85        self.model = QtGui.QStandardItemModel(self)
86
87        # Connect buttons to slots.
88        # Needs to be done early so default values propagate properly.
89        self.setupSlots()
90
91        # Set up the model.
92        self.setupModel()
93
94        # Set up the mapper
95        self.setupMapper()
96
97    def communicator(self):
98        """
99        """
100        return self.communicate
101
102    def updateFromModel(self):
103        """
104        update the globals based on the data in the model
105        """
106        self._background = float(self.model.item(WIDGETS.W_BACKGROUND).text())
107        self._contrast   = float(self.model.item(WIDGETS.W_CONTRAST).text())
108        self._scale      = float(self.model.item(WIDGETS.W_SCALE).text())
109
110        # High extrapolate
111        self._low_extrapolate = ( str(self.model.item(WIDGETS.W_ENABLE_LOWQ).text()) == 'true')
112        self._low_points = float(self.model.item(WIDGETS.W_NPTS_LOWQ).text())
113        self._low_guinier  = ( str(self.model.item(WIDGETS.W_LOWQ_GUINIER).text()) == 'true')
114        self._low_fit  = ( str(self.model.item(WIDGETS.W_LOWQ_FIT).text()) == 'true')
115        self._low_power_value  = float(self.model.item(WIDGETS.W_LOWQ_POWER_VALUE).text())
116
117        # High extrapolating
118        self._high_extrapolate = ( str(self.model.item(WIDGETS.W_ENABLE_HIGHQ).text()) == 'true')
119        self._high_points  = float(self.model.item(WIDGETS.W_NPTS_HIGHQ).text())
120        self._high_fit  = ( str(self.model.item(WIDGETS.W_HIGHQ_FIT).text()) == 'true')
121        self._high_power_value  = float(self.model.item(WIDGETS.W_HIGHQ_POWER_VALUE).text())
122
123    def calculateInvariant(self):
124        """
125        Use twisted to thread the calculations away.
126        """
127        # Find out if extrapolation needs to be used.
128        extrapolation = None
129        if self._low_extrapolate  and not self._high_extrapolate:
130            extrapolation = "low"
131        elif not self._low_extrapolate  and self._high_extrapolate:
132            extrapolation = "high"
133        elif self._low_extrapolate and self._high_extrapolate:
134            extrapolation = "both"
135        try:
136            # modify the Calculate button to indicate background process
137            self.pushButton.setText("Calculating...")
138            self.pushButton.setEnabled(False)
139            self.style = self.pushButton.styleSheet()
140            self.pushButton.setStyleSheet("background-color: rgb(255, 255, 0); color: rgb(0, 0, 0)")
141            # Send the calculations to separate thread.
142            d = threads.deferToThread(self.calculateThread, extrapolation)
143            # Add deferred callback for call return
144            d.addCallback(self.plotResult)
145        except Exception as ex:
146            # Set the button back to available
147            self.pushButton.setEnabled(True)
148            self.pushButton.setText("Calculate")
149            self.pushButton.setStyleSheet(self.style)
150
151
152    def plotResult(self, model):
153        """
154        """
155        if self._low_extrapolate or self._high_extrapolate:
156            self._plotter.show()
157        self.model = model
158        self.mapper.toFirst()
159
160        # Set the button back to available
161        self.pushButton.setEnabled(True)
162        self.pushButton.setText("Calculate")
163        self.pushButton.setStyleSheet(self.style)
164
165        # Send the modified model item to DE for keeping in the model
166        self.communicate.updateModelFromPerspectiveSignal.emit(self._model_item)
167
168
169    def calculateThread(self, extrapolation):
170        """
171        Perform Invariant calculations.
172
173        TODO: Create a dictionary of results to be sent to DE on completion.
174        """
175        self.updateFromModel()
176
177        qstar_low = 0.0
178        qstar_low_err = 0.0
179        qstar_high = 0.0
180        qstar_high_err = 0.0
181        qstar_total = 0.0
182        qstar_total_low_err = 0.0
183
184        # Prepare the invariant object
185        inv = invariant.InvariantCalculator(data=self._data,
186                                            background = self._background,
187                                            scale = self._scale)
188
189        if self._low_extrapolate:
190            function_low = "power_law"
191            if self._low_guinier:
192                function_low = "guinier"
193            if self._low_fit:
194                self._low_power_value = None
195            inv.set_extrapolation(range="low",
196                                  npts=self._low_points,
197                                  function=function_low,
198                                  power=self._low_power_value)
199
200        if self._high_extrapolate:
201            function_low = "power_law"
202            inv.set_extrapolation(range="high",
203                                  npts=self._high_points,
204                                  function=function_low,
205                                  power=self._low_power_value)
206
207        #Compute invariant
208        # TODO: proper exception handling and logic -
209        # display info, update lineedits, don't run extrapolations etc.
210        calculation_failed = False
211        try:
212            qstar_total, qstar_total_error         = inv.get_qstar_with_error()
213        except Exception as ex:
214            calculation_failed = True
215            # Display relevant information
216            item = QtGui.QStandardItem("ERROR")
217            self.model.setItem(WIDGETS.W_INVARIANT, item)
218            item = QtGui.QStandardItem("ERROR")
219            self.model.setItem(WIDGETS.W_INVARIANT_ERR, item)
220        try:
221            volume_fraction, volume_fraction_error = \
222                inv.get_volume_fraction_with_error(self._contrast)
223        except Exception as ex:
224            calculation_failed = True
225            # Display relevant information
226            item = QtGui.QStandardItem("ERROR")
227            self.model.setItem(WIDGETS.W_VOLUME_FRACTION, item)
228            item = QtGui.QStandardItem("ERROR")
229            self.model.setItem(WIDGETS.W_VOLUME_FRACTION_ERR, item)
230        try:
231            surface, surface_error                 = \
232                inv.get_surface_with_error(self._contrast, self._porod)
233        except Exception as ex:
234            calculation_failed = True
235            # Display relevant information
236            item = QtGui.QStandardItem("ERROR")
237            self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE, item)
238            item = QtGui.QStandardItem("ERROR")
239            self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE_ERR, item)
240
241        if(calculation_failed):
242            # TODO: NOTIFY THE GUI MANAGER!!
243            self.mapper.toFirst()
244            return self.model
245
246        self._plotter.clean()
247        self._plotter.x_label("Q(A$^{-1}$)")
248        self._plotter.y_label("Intensity(cm$^{-1}$)")
249
250        if self._low_extrapolate:
251            # for presentation in InvariantDetails
252            qstar_low, qstar_low_err = inv.get_qstar_low()
253            extrapolated_data = inv.get_extra_data_low(self._low_points)
254            power_low = inv.get_extrapolation_power(range='low')
255
256            #inv.data = extrapolated_data
257            #qstar_total, qstar_total_error = inv.get_qstar_with_error()
258
259            # Plot the chart
260            title = "Low-Q extrapolation"
261            self._plotter.data(extrapolated_data)
262            self._plotter.title(title)
263            self._plotter.plot()
264
265            # Convert the data into plottable
266            extrapolated_data = self._manager.createGuiData(extrapolated_data)
267
268            # Add the plot to the model item
269            # variant_item = QtCore.QVariant(self._plotter)
270            variant_item = QtCore.QVariant(extrapolated_data)
271
272            # This needs to run in the main thread
273            reactor.callFromThread(updateModelItem, self._model_item, variant_item, title)
274
275        if self._high_extrapolate:
276            # for presentation in InvariantDetails
277            qmax_plot = Q_MAXIMUM_PLOT * max(self._data.x)
278            if qmax_plot > Q_MAXIMUM:
279                qmax_plot = Q_MAXIMUM
280            qstar_high, qstar_high_err = inv.get_qstar_high()
281            power_high = inv.get_extrapolation_power(range='high')
282            high_out_data = inv.get_extra_data_high(q_end=qmax_plot, npts=500)
283
284            # Convert the data into plottable
285            high_out_data = self._manager.createGuiData(high_out_data)
286
287            # find how to add this plot to the existing plot for low_extrapolate
288            # Plot the chart
289            title = "High-Q extrapolation"
290            self._plotter.data(high_out_data)
291            self._plotter.title(title)
292            self._plotter.plot()
293
294            # Add the plot to the model item
295            # variant_item = QtCore.QVariant(self._plotter)
296            variant_item = QtCore.QVariant(high_out_data)
297            # This needs to run in the main thread
298            reactor.callFromThread(updateModelItem, self._model_item, variant_item, title)
299
300        item = QtGui.QStandardItem(str(float('%.5g'% volume_fraction)))
301        self.model.setItem(WIDGETS.W_VOLUME_FRACTION, item)
302        item = QtGui.QStandardItem(str(float('%.5g'% volume_fraction_error)))
303        self.model.setItem(WIDGETS.W_VOLUME_FRACTION_ERR, item)
304        item = QtGui.QStandardItem(str(float('%.5g'% surface)))
305        self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE, item)
306        item = QtGui.QStandardItem(str(float('%.5g'% surface_error)))
307        self.model.setItem(WIDGETS.W_SPECIFIC_SURFACE_ERR, item)
308        item = QtGui.QStandardItem(str(float('%.5g'% qstar_total)))
309        self.model.setItem(WIDGETS.W_INVARIANT, item)
310        item = QtGui.QStandardItem(str(float('%.5g'% qstar_total_error)))
311        self.model.setItem(WIDGETS.W_INVARIANT_ERR, item)
312
313        #item = QtGui.QStandardItem(str(float('%.5g'% qstar_total)))
314        #self.model.setItem(WIDGETS.D_TOTAL_QSTAR, item)
315        #item = QtGui.QStandardItem(str(float('%.5g'% qstar_total_err)))
316        #self.model.setItem(WIDGETS.D_TOTAL_QSTAR_ERR, item)
317        item = QtGui.QStandardItem(str(float('%.5g'% qstar_low)))
318        self.model.setItem(WIDGETS.D_LOW_QSTAR, item)
319        item = QtGui.QStandardItem(str(float('%.5g'% qstar_low_err)))
320        self.model.setItem(WIDGETS.D_LOW_QSTAR_ERR, item)
321        item = QtGui.QStandardItem(str(float('%.5g'% qstar_high)))
322        self.model.setItem(WIDGETS.D_HIGH_QSTAR, item)
323        item = QtGui.QStandardItem(str(float('%.5g'% qstar_high_err)))
324        self.model.setItem(WIDGETS.D_HIGH_QSTAR_ERR, item)
325
326        self.mapper.toFirst()
327
328        return self.model
329               
330    def title(self):
331        """
332        Perspective name
333        """
334        return "Invariant panel"
335
336    def status(self):
337        """
338        """
339        self.detailsDialog.setModel(self.model)
340        self.detailsDialog.showDialog()
341
342    def help(self):
343        """
344        """
345        _TreeLocation = "html/user/sasgui/perspectives/invariant/invariant_help.html"
346        self._helpView.load(QtCore.QUrl(_TreeLocation))
347        self._helpView.show()
348
349    def setupSlots(self):
350        self.pushButton.clicked.connect(self.calculateInvariant)
351        self.pushButton_2.clicked.connect(self.status)
352        self.pushButton_3.clicked.connect(self.help)
353
354        self.radioButton.toggled.connect(self.lowGuinierAndPowerToggle)
355        self.radioButton_8.toggled.connect(self.hiFitAndFixToggle)
356
357        self.radioButton_3.toggled.connect(self.lowFitAndFixToggle)
358
359        # Bug workaround for QDataWidgetMapper() not reacting to checkbox clicks.
360        # https://bugreports.qt.io/browse/QTBUG-1818
361        self.checkBox.clicked.connect(self.setFocus)
362        self.checkBox_2.clicked.connect(self.setFocus)
363
364        self.model.itemChanged.connect(self.modelChanged)
365
366    def modelChanged(self, item):
367        """
368        """
369        if item.row() == WIDGETS.W_ENABLE_LOWQ:
370            toggle = (str(item.text()) == 'true')
371            self._low_extrapolate = toggle
372            self.lowQToggle(toggle)
373        elif item.row() == WIDGETS.W_ENABLE_HIGHQ:
374            toggle = (str(item.text()) == 'true')
375            self._high_extrapolate = toggle
376            self.highQToggle(toggle)
377       
378    def lowGuinierAndPowerToggle(self, toggle):
379        """
380        """
381        self._low_guinier = toggle
382        toggle = not toggle
383        self.lineEdit_11.setEnabled(toggle)
384        self.radioButton_3.setEnabled(toggle)
385        self.radioButton_4.setEnabled(toggle)
386        self.lineEdit_11.setEnabled(toggle and (not self._low_fit))
387
388    def lowFitAndFixToggle(self, toggle):
389        """
390        """
391        self._low_fit = toggle
392        toggle = not toggle
393        self.lineEdit_11.setEnabled(toggle)
394
395    def hiFitAndFixToggle(self, toggle):
396        """
397        """
398        self.lineEdit_13.setEnabled(toggle)
399
400    def highQToggle(self, clicked):
401        """
402        Disable/enable High Q extrapolation
403        """
404        self.radioButton_7.setEnabled(clicked)
405        self.radioButton_8.setEnabled(clicked)
406        self.lineEdit_12.setEnabled(clicked)
407        self.lineEdit_13.setEnabled(clicked)
408
409    def lowQToggle(self, clicked):
410        """
411        Disable/enable Low Q extrapolation
412        """
413        self.radioButton.setEnabled(clicked)
414        self.radioButton_2.setEnabled(clicked)
415        self.lineEdit_11.setEnabled(clicked and not self._low_fit)
416
417        # Enable subelements
418        self.radioButton_3.setEnabled(clicked and not self._low_guinier)
419        self.radioButton_4.setEnabled(clicked and not self._low_guinier)
420        self.lineEdit_10.setEnabled(clicked and not self._low_guinier)
421
422    def setupModel(self):
423
424        # filename
425        item = QtGui.QStandardItem(self._path)
426        self.model.setItem(WIDGETS.W_FILENAME, item)
427
428        # add Q parameters to the model
429        qmin = min(self._data.x)
430        item = QtGui.QStandardItem(str(qmin))
431        self.model.setItem(WIDGETS.W_QMIN, item)
432        item = QtGui.QStandardItem(str(max(self._data.x)))
433        self.model.setItem(WIDGETS.W_QMAX, item)
434
435        # add custom input params
436        item = QtGui.QStandardItem(str(self._background))
437        self.model.setItem(WIDGETS.W_BACKGROUND, item)
438        item = QtGui.QStandardItem(str(self._contrast))
439        self.model.setItem(WIDGETS.W_CONTRAST, item)
440        item = QtGui.QStandardItem(str(self._scale))
441        self.model.setItem(WIDGETS.W_SCALE, item)
442       
443        # Dialog elements
444        itemf = QtGui.QStandardItem("false")
445        self.model.setItem(WIDGETS.W_ENABLE_HIGHQ, itemf)
446        itemf = QtGui.QStandardItem("false")
447        self.model.setItem(WIDGETS.W_ENABLE_LOWQ, itemf)
448
449        item = QtGui.QStandardItem(str(self._npoints_low))
450        self.model.setItem(WIDGETS.W_NPTS_LOWQ, item)
451        item = QtGui.QStandardItem(str(self._npoints_high))
452        self.model.setItem(WIDGETS.W_NPTS_HIGHQ, item)
453
454        itemt = QtGui.QStandardItem("true")
455        self.model.setItem(WIDGETS.W_LOWQ_GUINIER, itemt)
456
457        itemt = QtGui.QStandardItem("true")
458        self.model.setItem(WIDGETS.W_LOWQ_FIT, itemt)
459        item = QtGui.QStandardItem(str(self._power_low))
460        self.model.setItem(WIDGETS.W_LOWQ_POWER_VALUE, item)
461
462        itemt = QtGui.QStandardItem("true")
463        self.model.setItem(WIDGETS.W_HIGHQ_FIT, itemt)
464        item = QtGui.QStandardItem(str(self._power_low))
465        self.model.setItem(WIDGETS.W_HIGHQ_POWER_VALUE, item)
466
467
468    def setupMapper(self):
469        # Set up the mapper.
470        self.mapper = QtGui.QDataWidgetMapper(self)
471        self.mapper.setOrientation(QtCore.Qt.Vertical)
472        self.mapper.setModel(self.model)
473
474        # Set up the view on the model for testing
475        # self.tableView.setModel(self.model)
476
477        # Filename
478        self.mapper.addMapping(self.lineEdit, WIDGETS.W_FILENAME)
479        # Qmin/Qmax
480        self.mapper.addMapping(self.lineEdit_2, WIDGETS.W_QMIN)
481        self.mapper.addMapping(self.lineEdit_3, WIDGETS.W_QMAX)
482
483        # Background
484        self.mapper.addMapping(self.lineEdit_4, WIDGETS.W_BACKGROUND)
485        # Scale
486        self.mapper.addMapping(self.lineEdit_5, WIDGETS.W_SCALE)
487        # Contrast
488        self.mapper.addMapping(self.lineEdit_6, WIDGETS.W_CONTRAST)
489
490        # Lowq/Highq items
491        self.mapper.addMapping(self.checkBox, WIDGETS.W_ENABLE_LOWQ)
492        self.mapper.addMapping(self.checkBox_2, WIDGETS.W_ENABLE_HIGHQ)
493
494        self.mapper.addMapping(self.lineEdit_10, WIDGETS.W_NPTS_LOWQ)
495        self.mapper.addMapping(self.radioButton, WIDGETS.W_LOWQ_GUINIER)
496
497        self.mapper.addMapping(self.radioButton_3, WIDGETS.W_LOWQ_FIT)
498        self.mapper.addMapping(self.lineEdit_11, WIDGETS.W_LOWQ_POWER_VALUE)
499
500        self.mapper.addMapping(self.radioButton_7, WIDGETS.W_HIGHQ_FIT)
501        self.mapper.addMapping(self.lineEdit_13, WIDGETS.W_HIGHQ_POWER_VALUE)
502   
503        # Output
504        self.mapper.addMapping(self.lineEdit_14, WIDGETS.W_VOLUME_FRACTION)
505        self.mapper.addMapping(self.lineEdit_15, WIDGETS.W_VOLUME_FRACTION_ERR)
506        self.mapper.addMapping(self.lineEdit_16, WIDGETS.W_SPECIFIC_SURFACE)
507        self.mapper.addMapping(self.lineEdit_17, WIDGETS.W_SPECIFIC_SURFACE_ERR)
508        self.mapper.addMapping(self.lineEdit_19, WIDGETS.W_INVARIANT)
509        self.mapper.addMapping(self.lineEdit_18, WIDGETS.W_INVARIANT_ERR)
510
511        self.mapper.toFirst()
512
513    def setData(self, data_item):
514        """
515        Obtain a QStandardItem object and dissect it to get Data1D/2D
516        Pass it over to the calculator
517        """
518        if not isinstance(data_item, list):
519            msg = "Incorrect type passed to the Invariant Perspective"
520            raise AttributeError, msg
521
522        if not isinstance(data_item[0], QtGui.QStandardItem):
523            msg = "Incorrect type passed to the Invariant Perspective"
524            raise AttributeError, msg
525
526        self._model_item = data_item[0]
527
528        # Extract data on 1st child - this is the Data1D/2D component
529        data = self._model_item.child(0).data().toPyObject()
530
531        self.calculate(data_list=[data])
532       
533    def calculate(self, data_list=None):
534        """
535        receive a list of data and compute invariant
536
537        TODO: pass warnings/messages to log
538        """
539        msg = ""
540        data = None
541        if data_list is None:
542            data_list = []
543        if len(data_list) >= 1:
544            if len(data_list) == 1:
545                data = data_list[0]
546            else:
547                data_1d_list = []
548                data_2d_list = []
549                error_msg = ""
550                # separate data into data1d and data2d list
551                for data in data_list:
552                    if data is not None:
553                        if issubclass(data.__class__, Data1D):
554                            data_1d_list.append(data)
555                        else:
556                            error_msg += " %s  type %s \n" % (str(data.name),
557                                                              str(data.__class__.__name__))
558                            data_2d_list.append(data)
559                if len(data_2d_list) > 0:
560                    msg = "Invariant does not support the following data types:\n"
561                    msg += error_msg
562                if len(data_1d_list) == 0:
563                    # remake this as a qt event
564                    #wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
565                    return
566
567                # TODO: add msgbox for data choice
568                #msg += "Invariant panel does not allow multiple data!\n"
569                #msg += "Please select one.\n"
570                #if len(data_list) > 1:
571                    #from invariant_widgets import DataDialog
572                    #dlg = DataDialog(data_list=data_1d_list, text=msg)
573                    #if dlg.ShowModal() == wx.ID_OK:
574                    #    data = dlg.get_data()
575                    #else:
576                    #    data = None
577                    #dlg.Destroy()
578
579            if data is None:
580                msg += "invariant receives no data. \n"
581                #wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
582                return
583            if not issubclass(data.__class__, Data1D):
584                msg += "invariant cannot be computed for data of "
585                msg += "type %s\n" % (data.__class__.__name__)
586                #wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
587                return
588            else:
589                #wx.PostEvent(self.parent, NewPlotEvent(plot=data, title=data.title))
590                try:
591                    self._data = data
592                    self._path = "unique path"
593                    self.calculateInvariant()
594                except:
595                    msg = "Invariant Set_data: " + str(sys.exc_value)
596                    #wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
597        else:
598            msg = "invariant cannot be computed for data of "
599            msg += "type %s" % (data.__class__.__name__)
600            #wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
601
602    def allowBatch(self):
603        """
604        Tell the caller that we don't accept multiple data instances
605        """
606        return False
607
608if __name__ == "__main__":
609    app = QtGui.QApplication([])
610    import qt4reactor
611    qt4reactor.install()
612    # DO NOT move the following import to the top!
613    # (unless you know what you're doing)
614    from twisted.internet import reactor
615    dlg = InvariantWindow(reactor)
616    dlg.show()
617    reactor.run()
Note: See TracBrowser for help on using the repository browser.