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

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

Initial version of the C&S widget

  • 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
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        Add a new Constrained and Simult. Fit page in the fitting perspective.
626        """
627        per = self.perspective()
628        if not isinstance(per, FittingWindow):
629            return
630        per.addConstraintTab()
631
632    def actionCombine_Batch_Fit(self):
633        """
634        """
635        print("actionCombine_Batch_Fit TRIGGERED")
636        pass
637
638    def actionFit_Options(self):
639        """
640        """
641        if getattr(self._current_perspective, "fit_options_widget"):
642            self._current_perspective.fit_options_widget.show()
643        pass
644
645    def actionGPU_Options(self):
646        """
647        Load the OpenCL selection dialog if the fitting perspective is active
648        """
649        if hasattr(self._current_perspective, "gpu_options_widget"):
650            self._current_perspective.gpu_options_widget.show()
651        pass
652
653    def actionFit_Results(self):
654        """
655        """
656        print("actionFit_Results TRIGGERED")
657        pass
658
659    def actionChain_Fitting(self):
660        """
661        """
662        print("actionChain_Fitting TRIGGERED")
663        pass
664
665    def actionEdit_Custom_Model(self):
666        """
667        """
668        print("actionEdit_Custom_Model TRIGGERED")
669        pass
670
671    #============ ANALYSIS =================
672    def actionFitting(self):
673        """
674        Change to the Fitting perspective
675        """
676        self.perspectiveChanged("Fitting")
677
678    def actionInversion(self):
679        """
680        Change to the Inversion perspective
681        """
682        # For now we'll just update the analysis menu status but when the inversion is implemented delete from here
683        self.checkAnalysisOption(self._workspace.actionInversion)
684        # to here and uncomment the following line
685        self.perspectiveChanged("Inversion")
686
687    def actionInvariant(self):
688        """
689        Change to the Invariant perspective
690        """
691        self.perspectiveChanged("Invariant")
692
693    #============ WINDOW =================
694    def actionCascade(self):
695        """
696        Arranges all the child windows in a cascade pattern.
697        """
698        self._workspace.workspace.cascade()
699
700    def actionTile(self):
701        """
702        Tile workspace windows
703        """
704        self._workspace.workspace.tile()
705
706    def actionArrange_Icons(self):
707        """
708        Arranges all iconified windows at the bottom of the workspace
709        """
710        self._workspace.workspace.arrangeIcons()
711
712    def actionNext(self):
713        """
714        Gives the input focus to the next window in the list of child windows.
715        """
716        self._workspace.workspace.activateNextWindow()
717
718    def actionPrevious(self):
719        """
720        Gives the input focus to the previous window in the list of child windows.
721        """
722        self._workspace.workspace.activatePreviousWindow()
723
724    #============ HELP =================
725    def actionDocumentation(self):
726        """
727        Display the documentation
728
729        TODO: use QNetworkAccessManager to assure _helpLocation is valid
730        """
731        self.showHelp(self._helpLocation)
732
733    def actionTutorial(self):
734        """
735        Open the tutorial PDF file with default PDF renderer
736        """
737        # Not terribly safe here. Shell injection warning.
738        # isfile() helps but this probably needs a better solution.
739        if os.path.isfile(self._tutorialLocation):
740            result = subprocess.Popen([self._tutorialLocation], shell=True)
741
742    def actionAcknowledge(self):
743        """
744        Open the Acknowledgements widget
745        """
746        self.ackWidget.show()
747
748    def actionAbout(self):
749        """
750        Open the About box
751        """
752        # Update the about box with current version and stuff
753
754        # TODO: proper sizing
755        self.aboutWidget.show()
756
757    def actionCheck_for_update(self):
758        """
759        Menu Help/Check for Update
760        """
761        self.checkUpdate()
762
763    def updateTheoryFromPerspective(self, index):
764        """
765        Catch the theory update signal from a perspective
766        Send the request to the DataExplorer for updating the theory model.
767        """
768        self.filesWidget.updateTheoryFromPerspective(index)
769
770    def updateModelFromDataOperationPanel(self, new_item, new_datalist_item):
771        """
772        :param new_item: item to be added to list of loaded files
773        :param new_datalist_item:
774        """
775        if not isinstance(new_item, QStandardItem) or \
776                not isinstance(new_datalist_item, dict):
777            msg = "Wrong data type returned from calculations."
778            raise AttributeError(msg)
779
780        self.filesWidget.model.appendRow(new_item)
781        self._data_manager.add_data(new_datalist_item)
782
783    def showPlot(self, plot):
784        """
785        Pass the show plot request to the data explorer
786        """
787        if hasattr(self, "filesWidget"):
788            self.filesWidget.displayData(plot)
789
790    def uncheckAllMenuItems(self, menuObject):
791        """
792        Uncheck all options in a given menu
793        """
794        menuObjects = menuObject.actions()
795
796        for menuItem in menuObjects:
797            menuItem.setChecked(False)
798
799    def checkAnalysisOption(self, analysisMenuOption):
800        """
801        Unchecks all the items in the analysis menu and checks the item passed
802        """
803        self.uncheckAllMenuItems(self._workspace.menuAnalysis)
804        analysisMenuOption.setChecked(True)
805
806    def clearPerspectiveMenubarOptions(self, perspective):
807        """
808        When closing a perspective, clears the menu bar
809        """
810        for menuItem in self._workspace.menuAnalysis.actions():
811            menuItem.setChecked(False)
812
813        if isinstance(self._current_perspective, Perspectives.PERSPECTIVES["Fitting"]):
814            self._workspace.menubar.removeAction(self._workspace.menuFitting.menuAction())
815
816    def setupPerspectiveMenubarOptions(self, perspective):
817        """
818        When setting a perspective, sets up the menu bar
819        """
820        if isinstance(perspective, Perspectives.PERSPECTIVES["Fitting"]):
821            self.checkAnalysisOption(self._workspace.actionFitting)
822            # Put the fitting menu back in
823            # This is a bit involved but it is needed to preserve the menu ordering
824            self._workspace.menubar.removeAction(self._workspace.menuWindow.menuAction())
825            self._workspace.menubar.removeAction(self._workspace.menuHelp.menuAction())
826            self._workspace.menubar.addAction(self._workspace.menuFitting.menuAction())
827            self._workspace.menubar.addAction(self._workspace.menuWindow.menuAction())
828            self._workspace.menubar.addAction(self._workspace.menuHelp.menuAction())
829        elif isinstance(perspective, Perspectives.PERSPECTIVES["Invariant"]):
830            self.checkAnalysisOption(self._workspace.actionInvariant)
831        # elif isinstance(perspective, Perspectives.PERSPECTIVES["Inversion"]):
832        #     self.checkAnalysisOption(self._workspace.actionInversion)
Note: See TracBrowser for help on using the repository browser.