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

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

More code review related fixes

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