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

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

Partially revert last commit. Went too far.

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