source: sasview/src/sas/qtgui/MainWindow/GuiManager.py @ fef38e8

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 fef38e8 was fef38e8, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Startup time improvements - hiding expensive imports and such

  • Property mode set to 100644
File size: 24.7 KB
Line 
1import sys
2import os
3import subprocess
4import logging
5import json
6import webbrowser
7
8from PyQt4 import QtCore
9from PyQt4 import QtGui
10from PyQt4 import QtWebKit
11
12from twisted.internet import reactor
13# General SAS imports
14from sas.qtgui.Utilities.ConnectionProxy import ConnectionProxy
15from sas.qtgui.Utilities.SasviewLogger import XStream
16
17import sas.qtgui.Utilities.LocalConfig as LocalConfig
18import sas.qtgui.Utilities.GuiUtils as GuiUtils
19
20import sas.qtgui.Utilities.ObjectLibrary as ObjectLibrary
21from sas.qtgui.MainWindow.UI.AcknowledgementsUI import Ui_Acknowledgements
22from sas.qtgui.MainWindow.AboutBox import AboutBox
23from sas.qtgui.MainWindow.WelcomePanel import WelcomePanel
24
25from sas.qtgui.MainWindow.DataManager import DataManager
26
27from sas.qtgui.Calculators.SldPanel import SldPanel
28from sas.qtgui.Calculators.DensityPanel import DensityPanel
29from sas.qtgui.Calculators.KiessigPanel import KiessigPanel
30from sas.qtgui.Calculators.SlitSizeCalculator import SlitSizeCalculator
31from sas.qtgui.Calculators.GenericScatteringCalculator import GenericScatteringCalculator
32
33# Perspectives
34import sas.qtgui.Perspectives as Perspectives
35from sas.qtgui.Perspectives.Fitting.FittingPerspective import FittingWindow
36from sas.qtgui.MainWindow.DataExplorer import DataExplorerWindow
37
38class Acknowledgements(QtGui.QDialog, Ui_Acknowledgements):
39    def __init__(self, parent=None):
40        QtGui.QDialog.__init__(self, parent)
41        self.setupUi(self)
42
43class GuiManager(object):
44    """
45    Main SasView window functionality
46    """
47    def __init__(self, parent=None):
48        """
49        Initialize the manager as a child of MainWindow.
50        """
51        self._workspace = parent
52        self._parent = parent
53
54        # Add signal callbacks
55        self.addCallbacks()
56
57        # Create the data manager
58        # TODO: pull out all required methods from DataManager and reimplement
59        self._data_manager = DataManager()
60
61        # Create action triggers
62        self.addTriggers()
63
64        # Populate menus with dynamic data
65        #
66        # Analysis/Perspectives - potentially
67        # Window/current windows
68        #
69        # Widgets
70        #
71        # Current displayed perspective
72        self._current_perspective = None
73
74        # Invoke the initial perspective
75        self.perspectiveChanged("Fitting")
76
77        self.addWidgets()
78
79        # Fork off logging messages to the Log Window
80        XStream.stdout().messageWritten.connect(self.listWidget.insertPlainText)
81        XStream.stderr().messageWritten.connect(self.listWidget.insertPlainText)
82
83        # Log the start of the session
84        logging.info(" --- SasView session started ---")
85        # Log the python version
86        logging.info("Python: %s" % sys.version)
87
88        # Set up the status bar
89        self.statusBarSetup()
90
91        # Show the Welcome panel
92        self.welcomePanel = WelcomePanel()
93        self._workspace.workspace.addWindow(self.welcomePanel)
94
95        # Current help file
96        self._helpView = QtWebKit.QWebView()
97        # Needs URL like path, so no path.join() here
98        GuiUtils._init()
99        self._helpLocation = GuiUtils.HELP_DIRECTORY_LOCATION + "/index.html"
100
101        # Current tutorial location
102        self._tutorialLocation = os.path.abspath(os.path.join(GuiUtils.HELP_DIRECTORY_LOCATION,
103                                              "_downloads",
104                                              "Tutorial.pdf"))
105    def addWidgets(self):
106        """
107        Populate the main window with widgets
108
109        TODO: overwrite close() on Log and DR widgets so they can be hidden/shown
110        on request
111        """
112        # Add FileDialog widget as docked
113        self.filesWidget = DataExplorerWindow(self._parent, self, manager=self._data_manager)
114        ObjectLibrary.addObject('DataExplorer', self.filesWidget)
115
116        self.dockedFilesWidget = QtGui.QDockWidget("Data Explorer", self._workspace)
117        self.dockedFilesWidget.setWidget(self.filesWidget)
118
119        # Disable maximize/minimize and close buttons
120        self.dockedFilesWidget.setFeatures(QtGui.QDockWidget.NoDockWidgetFeatures)
121        self._workspace.addDockWidget(QtCore.Qt.LeftDockWidgetArea,
122                                      self.dockedFilesWidget)
123
124        # Add the console window as another docked widget
125        self.logDockWidget = QtGui.QDockWidget("Log Explorer", self._workspace)
126        self.logDockWidget.setObjectName("LogDockWidget")
127        self.listWidget = QtGui.QTextBrowser()
128        self.logDockWidget.setWidget(self.listWidget)
129        self._workspace.addDockWidget(QtCore.Qt.BottomDockWidgetArea,
130                                      self.logDockWidget)
131
132        # Add other, minor widgets
133        self.ackWidget = Acknowledgements()
134        self.aboutWidget = AboutBox()
135
136        # Add calculators - floating for usability
137        self.SLDCalculator = SldPanel(self)
138        self.DVCalculator = DensityPanel(self)
139        #self.KIESSIGCalculator = DensityPanel(self)#KiessigPanel(self)
140        self.KIESSIGCalculator = KiessigPanel(self)
141        self.SlitSizeCalculator = SlitSizeCalculator(self)
142        self.GENSASCalculator = GenericScatteringCalculator(self)
143
144    def statusBarSetup(self):
145        """
146        Define the status bar.
147        | <message label> .... | Progress Bar |
148
149        Progress bar invisible until explicitly shown
150        """
151        self.progress = QtGui.QProgressBar()
152        self._workspace.statusbar.setSizeGripEnabled(False)
153
154        self.statusLabel = QtGui.QLabel()
155        self.statusLabel.setText("Welcome to SasView")
156        self._workspace.statusbar.addPermanentWidget(self.statusLabel, 1)
157        self._workspace.statusbar.addPermanentWidget(self.progress, stretch=0)
158        self.progress.setRange(0, 100)
159        self.progress.setValue(0)
160        self.progress.setTextVisible(True)
161        self.progress.setVisible(False)
162
163    def fileWasRead(self, data):
164        """
165        Callback for fileDataReceivedSignal
166        """
167        pass
168
169    def workspace(self):
170        """
171        Accessor for the main window workspace
172        """
173        return self._workspace.workspace
174
175    def perspectiveChanged(self, perspective_name):
176        """
177        Respond to change of the perspective signal
178        """
179        # Close the previous perspective
180        if self._current_perspective:
181            self._current_perspective.setClosable()
182            self._current_perspective.close()
183        # Default perspective
184        self._current_perspective = Perspectives.PERSPECTIVES[str(perspective_name)](parent=self)
185
186        self._workspace.workspace.addWindow(self._current_perspective)
187        # Resize to the workspace height
188        workspace_height = self._workspace.workspace.sizeHint().height()
189        perspective_size = self._current_perspective.sizeHint()
190        if workspace_height < perspective_size.height:
191            perspective_width = perspective_size.width()
192            self._current_perspective.resize(perspective_width, workspace_height-10)
193        self._current_perspective.show()
194
195    def updatePerspective(self, data):
196        """
197        Update perspective with data sent.
198        """
199        assert isinstance(data, list)
200        if self._current_perspective is not None:
201            self._current_perspective.setData(data.values())
202        else:
203            msg = "No perspective is currently active."
204            logging.info(msg)
205
206    def communicator(self):
207        """ Accessor for the communicator """
208        return self.communicate
209
210    def perspective(self):
211        """ Accessor for the perspective """
212        return self._current_perspective
213
214    def updateProgressBar(self, value):
215        """
216        Update progress bar with the required value (0-100)
217        """
218        assert -1 <= value <= 100
219        if value == -1:
220            self.progress.setVisible(False)
221            return
222        if not self.progress.isVisible():
223            self.progress.setTextVisible(True)
224            self.progress.setVisible(True)
225
226        self.progress.setValue(value)
227
228    def updateStatusBar(self, text):
229        """
230        Set the status bar text
231        """
232        self.statusLabel.setText(text)
233
234    def createGuiData(self, item, p_file=None):
235        """
236        Access the Data1D -> plottable Data1D conversion
237        """
238        return self._data_manager.create_gui_data(item, p_file)
239
240    def setData(self, data):
241        """
242        Sends data to current perspective
243        """
244        if self._current_perspective is not None:
245            self._current_perspective.setData(data.values())
246        else:
247            msg = "Guiframe does not have a current perspective"
248            logging.info(msg)
249
250    def quitApplication(self):
251        """
252        Close the reactor and exit nicely.
253        """
254        # Display confirmation messagebox
255        quit_msg = "Are you sure you want to exit the application?"
256        reply = QtGui.QMessageBox.question(
257            self._parent,
258            'Information',
259            quit_msg,
260            QtGui.QMessageBox.Yes,
261            QtGui.QMessageBox.No)
262
263        # Exit if yes
264        if reply == QtGui.QMessageBox.Yes:
265            reactor.callFromThread(reactor.stop)
266            return True
267
268        return False
269
270    def checkUpdate(self):
271        """
272        Check with the deployment server whether a new version
273        of the application is available.
274        A thread is started for the connecting with the server. The thread calls
275        a call-back method when the current version number has been obtained.
276        """
277        version_info = {"version": "0.0.0"}
278        c = ConnectionProxy(LocalConfig.__update_URL__, LocalConfig.UPDATE_TIMEOUT)
279        response = c.connect()
280        if response is None:
281            return
282        try:
283            content = response.read().strip()
284            logging.info("Connected to www.sasview.org. Latest version: %s"
285                            % (content))
286            version_info = json.loads(content)
287            self.processVersion(version_info)
288        except ValueError, ex:
289            logging.info("Failed to connect to www.sasview.org:", ex)
290
291    def processVersion(self, version_info):
292        """
293        Call-back method for the process of checking for updates.
294        This methods is called by a VersionThread object once the current
295        version number has been obtained. If the check is being done in the
296        background, the user will not be notified unless there's an update.
297
298        :param version: version string
299        """
300        try:
301            version = version_info["version"]
302            if version == "0.0.0":
303                msg = "Could not connect to the application server."
304                msg += " Please try again later."
305                self.communicate.statusBarUpdateSignal.emit(msg)
306
307            elif cmp(version, LocalConfig.__version__) > 0:
308                msg = "Version %s is available! " % str(version)
309                if "download_url" in version_info:
310                    webbrowser.open(version_info["download_url"])
311                else:
312                    webbrowser.open(LocalConfig.__download_page__)
313                self.communicate.statusBarUpdateSignal.emit(msg)
314            else:
315                msg = "You have the latest version"
316                msg += " of %s" % str(LocalConfig.__appname__)
317                self.communicate.statusBarUpdateSignal.emit(msg)
318        except:
319            msg = "guiframe: could not get latest application"
320            msg += " version number\n  %s" % sys.exc_value
321            logging.error(msg)
322            msg = "Could not connect to the application server."
323            msg += " Please try again later."
324            self.communicate.statusBarUpdateSignal.emit(msg)
325
326    def addCallbacks(self):
327        """
328        Method defining all signal connections for the gui manager
329        """
330        self.communicate = GuiUtils.Communicate()
331        self.communicate.fileDataReceivedSignal.connect(self.fileWasRead)
332        self.communicate.statusBarUpdateSignal.connect(self.updateStatusBar)
333        self.communicate.updatePerspectiveWithDataSignal.connect(self.updatePerspective)
334        self.communicate.progressBarUpdateSignal.connect(self.updateProgressBar)
335        self.communicate.perspectiveChangedSignal.connect(self.perspectiveChanged)
336        self.communicate.updateTheoryFromPerspectiveSignal.connect(self.updateTheoryFromPerspective)
337        self.communicate.plotRequestedSignal.connect(self.showPlot)
338
339    def addTriggers(self):
340        """
341        Trigger definitions for all menu/toolbar actions.
342        """
343        # File
344        self._workspace.actionLoadData.triggered.connect(self.actionLoadData)
345        self._workspace.actionLoad_Data_Folder.triggered.connect(self.actionLoad_Data_Folder)
346        self._workspace.actionOpen_Project.triggered.connect(self.actionOpen_Project)
347        self._workspace.actionOpen_Analysis.triggered.connect(self.actionOpen_Analysis)
348        self._workspace.actionSave.triggered.connect(self.actionSave)
349        self._workspace.actionSave_Analysis.triggered.connect(self.actionSave_Analysis)
350        self._workspace.actionQuit.triggered.connect(self.actionQuit)
351        # Edit
352        self._workspace.actionUndo.triggered.connect(self.actionUndo)
353        self._workspace.actionRedo.triggered.connect(self.actionRedo)
354        self._workspace.actionCopy.triggered.connect(self.actionCopy)
355        self._workspace.actionPaste.triggered.connect(self.actionPaste)
356        self._workspace.actionReport.triggered.connect(self.actionReport)
357        self._workspace.actionReset.triggered.connect(self.actionReset)
358        self._workspace.actionExcel.triggered.connect(self.actionExcel)
359        self._workspace.actionLatex.triggered.connect(self.actionLatex)
360
361        # View
362        self._workspace.actionShow_Grid_Window.triggered.connect(self.actionShow_Grid_Window)
363        self._workspace.actionHide_Toolbar.triggered.connect(self.actionHide_Toolbar)
364        self._workspace.actionStartup_Settings.triggered.connect(self.actionStartup_Settings)
365        self._workspace.actionCategry_Manager.triggered.connect(self.actionCategry_Manager)
366        # Tools
367        self._workspace.actionData_Operation.triggered.connect(self.actionData_Operation)
368        self._workspace.actionSLD_Calculator.triggered.connect(self.actionSLD_Calculator)
369        self._workspace.actionDensity_Volume_Calculator.triggered.connect(self.actionDensity_Volume_Calculator)
370        self._workspace.actionKeissig_Calculator.triggered.connect(self.actionKiessig_Calculator)
371        #self._workspace.actionKIESSING_Calculator.triggered.connect(self.actionKIESSING_Calculator)
372        self._workspace.actionSlit_Size_Calculator.triggered.connect(self.actionSlit_Size_Calculator)
373        self._workspace.actionSAS_Resolution_Estimator.triggered.connect(self.actionSAS_Resolution_Estimator)
374        self._workspace.actionGeneric_Scattering_Calculator.triggered.connect(self.actionGeneric_Scattering_Calculator)
375        self._workspace.actionPython_Shell_Editor.triggered.connect(self.actionPython_Shell_Editor)
376        self._workspace.actionImage_Viewer.triggered.connect(self.actionImage_Viewer)
377        # Fitting
378        self._workspace.actionNew_Fit_Page.triggered.connect(self.actionNew_Fit_Page)
379        self._workspace.actionConstrained_Fit.triggered.connect(self.actionConstrained_Fit)
380        self._workspace.actionCombine_Batch_Fit.triggered.connect(self.actionCombine_Batch_Fit)
381        self._workspace.actionFit_Options.triggered.connect(self.actionFit_Options)
382        self._workspace.actionFit_Results.triggered.connect(self.actionFit_Results)
383        self._workspace.actionChain_Fitting.triggered.connect(self.actionChain_Fitting)
384        self._workspace.actionEdit_Custom_Model.triggered.connect(self.actionEdit_Custom_Model)
385        # Window
386        self._workspace.actionCascade.triggered.connect(self.actionCascade)
387        self._workspace.actionTile.triggered.connect(self.actionTile)
388        self._workspace.actionArrange_Icons.triggered.connect(self.actionArrange_Icons)
389        self._workspace.actionNext.triggered.connect(self.actionNext)
390        self._workspace.actionPrevious.triggered.connect(self.actionPrevious)
391        # Analysis
392        self._workspace.actionFitting.triggered.connect(self.actionFitting)
393        self._workspace.actionInversion.triggered.connect(self.actionInversion)
394        self._workspace.actionInvariant.triggered.connect(self.actionInvariant)
395        # Help
396        self._workspace.actionDocumentation.triggered.connect(self.actionDocumentation)
397        self._workspace.actionTutorial.triggered.connect(self.actionTutorial)
398        self._workspace.actionAcknowledge.triggered.connect(self.actionAcknowledge)
399        self._workspace.actionAbout.triggered.connect(self.actionAbout)
400        self._workspace.actionCheck_for_update.triggered.connect(self.actionCheck_for_update)
401
402    #============ FILE =================
403    def actionLoadData(self):
404        """
405        Menu File/Load Data File(s)
406        """
407        self.filesWidget.loadFile()
408
409    def actionLoad_Data_Folder(self):
410        """
411        Menu File/Load Data Folder
412        """
413        self.filesWidget.loadFolder()
414
415    def actionOpen_Project(self):
416        """
417        Menu Open Project
418        """
419        self.filesWidget.loadProject()
420
421    def actionOpen_Analysis(self):
422        """
423        """
424        print("actionOpen_Analysis TRIGGERED")
425        pass
426
427    def actionSave(self):
428        """
429        Menu Save Project
430        """
431        self.filesWidget.saveProject()
432
433    def actionSave_Analysis(self):
434        """
435        """
436        print("actionSave_Analysis TRIGGERED")
437
438        pass
439
440    def actionQuit(self):
441        """
442        Close the reactor, exit the application.
443        """
444        self.quitApplication()
445
446    #============ EDIT =================
447    def actionUndo(self):
448        """
449        """
450        print("actionUndo TRIGGERED")
451        pass
452
453    def actionRedo(self):
454        """
455        """
456        print("actionRedo TRIGGERED")
457        pass
458
459    def actionCopy(self):
460        """
461        """
462        print("actionCopy TRIGGERED")
463        pass
464
465    def actionPaste(self):
466        """
467        """
468        print("actionPaste TRIGGERED")
469        pass
470
471    def actionReport(self):
472        """
473        """
474        print("actionReport TRIGGERED")
475        pass
476
477    def actionReset(self):
478        """
479        """
480        logging.warning(" *** actionOpen_Analysis logging *******")
481        print("actionReset print TRIGGERED")
482        sys.stderr.write("STDERR - TRIGGERED")
483        pass
484
485    def actionExcel(self):
486        """
487        """
488        print("actionExcel TRIGGERED")
489        pass
490
491    def actionLatex(self):
492        """
493        """
494        print("actionLatex TRIGGERED")
495        pass
496
497    #============ VIEW =================
498    def actionShow_Grid_Window(self):
499        """
500        """
501        print("actionShow_Grid_Window TRIGGERED")
502        pass
503
504    def actionHide_Toolbar(self):
505        """
506        Toggle toolbar vsibility
507        """
508        if self._workspace.toolBar.isVisible():
509            self._workspace.actionHide_Toolbar.setText("Show Toolbar")
510            self._workspace.toolBar.setVisible(False)
511        else:
512            self._workspace.actionHide_Toolbar.setText("Hide Toolbar")
513            self._workspace.toolBar.setVisible(True)
514        pass
515
516    def actionStartup_Settings(self):
517        """
518        """
519        print("actionStartup_Settings TRIGGERED")
520        pass
521
522    def actionCategry_Manager(self):
523        """
524        """
525        print("actionCategry_Manager TRIGGERED")
526        pass
527
528    #============ TOOLS =================
529    def actionData_Operation(self):
530        """
531        """
532        print("actionData_Operation TRIGGERED")
533        pass
534
535    def actionSLD_Calculator(self):
536        """
537        """
538        self.SLDCalculator.show()
539
540    def actionDensity_Volume_Calculator(self):
541        """
542        """
543        self.DVCalculator.show()
544
545    def actionKiessig_Calculator(self):
546        """
547        """
548        #self.DVCalculator.show()
549        self.KIESSIGCalculator.show()
550
551    def actionSlit_Size_Calculator(self):
552        """
553        """
554        self.SlitSizeCalculator.show()
555
556    def actionSAS_Resolution_Estimator(self):
557        """
558        """
559        print("actionSAS_Resolution_Estimator TRIGGERED")
560        pass
561
562    def actionGeneric_Scattering_Calculator(self):
563        """
564        """
565        self.GENSASCalculator.show()
566
567    def actionPython_Shell_Editor(self):
568        """
569        Display the Jupyter console as a docked widget.
570        """
571        # Import moved here for startup performance reasons
572        from sas.qtgui.Utilities.IPythonWidget import IPythonWidget
573        terminal = IPythonWidget()
574
575        # Add the console window as another docked widget
576        self.ipDockWidget = QtGui.QDockWidget("IPython", self._workspace)
577        self.ipDockWidget.setObjectName("IPythonDockWidget")
578        self.ipDockWidget.setWidget(terminal)
579        self._workspace.addDockWidget(QtCore.Qt.RightDockWidgetArea,
580                                      self.ipDockWidget)
581
582    def actionImage_Viewer(self):
583        """
584        """
585        print("actionImage_Viewer TRIGGERED")
586        pass
587
588    #============ FITTING =================
589    def actionNew_Fit_Page(self):
590        """
591        Add a new, empty Fit page in the fitting perspective.
592        """
593        # Make sure the perspective is correct
594        per = self.perspective()
595        if not isinstance(per, FittingWindow):
596            return
597        per.addFit(None)
598
599    def actionConstrained_Fit(self):
600        """
601        """
602        print("actionConstrained_Fit TRIGGERED")
603        pass
604
605    def actionCombine_Batch_Fit(self):
606        """
607        """
608        print("actionCombine_Batch_Fit TRIGGERED")
609        pass
610
611    def actionFit_Options(self):
612        """
613        """
614        if getattr(self._current_perspective, "fit_options_widget"):
615            self._current_perspective.fit_options_widget.show()
616        pass
617
618    def actionFit_Results(self):
619        """
620        """
621        print("actionFit_Results TRIGGERED")
622        pass
623
624    def actionChain_Fitting(self):
625        """
626        """
627        print("actionChain_Fitting TRIGGERED")
628        pass
629
630    def actionEdit_Custom_Model(self):
631        """
632        """
633        print("actionEdit_Custom_Model TRIGGERED")
634        pass
635
636    #============ ANALYSIS =================
637    def actionFitting(self):
638        """
639        """
640        print("actionFitting TRIGGERED")
641        pass
642
643    def actionInversion(self):
644        """
645        """
646        print("actionInversion TRIGGERED")
647        pass
648
649    def actionInvariant(self):
650        """
651        """
652        print("actionInvariant TRIGGERED")
653        pass
654
655    #============ WINDOW =================
656    def actionCascade(self):
657        """
658        Arranges all the child windows in a cascade pattern.
659        """
660        self._workspace.workspace.cascade()
661
662    def actionTile(self):
663        """
664        Tile workspace windows
665        """
666        self._workspace.workspace.tile()
667
668    def actionArrange_Icons(self):
669        """
670        Arranges all iconified windows at the bottom of the workspace
671        """
672        self._workspace.workspace.arrangeIcons()
673
674    def actionNext(self):
675        """
676        Gives the input focus to the next window in the list of child windows.
677        """
678        self._workspace.workspace.activateNextWindow()
679
680    def actionPrevious(self):
681        """
682        Gives the input focus to the previous window in the list of child windows.
683        """
684        self._workspace.workspace.activatePreviousWindow()
685
686    #============ HELP =================
687    def actionDocumentation(self):
688        """
689        Display the documentation
690
691        TODO: use QNetworkAccessManager to assure _helpLocation is valid
692        """
693        self._helpView.load(QtCore.QUrl(self._helpLocation))
694        self._helpView.show()
695
696    def actionTutorial(self):
697        """
698        Open the tutorial PDF file with default PDF renderer
699        """
700        # Not terribly safe here. Shell injection warning.
701        # isfile() helps but this probably needs a better solution.
702        if os.path.isfile(self._tutorialLocation):
703            result = subprocess.Popen([self._tutorialLocation], shell=True)
704
705    def actionAcknowledge(self):
706        """
707        Open the Acknowledgements widget
708        """
709        self.ackWidget.show()
710
711    def actionAbout(self):
712        """
713        Open the About box
714        """
715        # Update the about box with current version and stuff
716
717        # TODO: proper sizing
718        self.aboutWidget.show()
719
720    def actionCheck_for_update(self):
721        """
722        Menu Help/Check for Update
723        """
724        self.checkUpdate()
725
726    def updateTheoryFromPerspective(self, index):
727        """
728        Catch the theory update signal from a perspective
729        Send the request to the DataExplorer for updating the theory model.
730        """
731        self.filesWidget.updateTheoryFromPerspective(index)
732
733    def showPlot(self, plot):
734        """
735        Pass the show plot request to the data explorer
736        """
737        if hasattr(self, "filesWidget"):
738            self.filesWidget.displayData(plot)
739
Note: See TracBrowser for help on using the repository browser.