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

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 28a84e9 was 28a84e9, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 8 years ago

More context menu functionality in Data Explorer

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