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

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 9863343 was 9863343, checked in by krzywon, 6 years ago

Address issues outlined in SVCC-79.

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