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

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

Show help pages in default browser. Fixed some help links and modified unit tests. SASVIEW-800

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