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

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

Fixes to the Invariant perspective

  • Property mode set to 100644
File size: 28.9 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
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        # Populate menus with dynamic data
72        #
73        # Analysis/Perspectives - potentially
74        # Window/current windows
75        #
76        # Widgets
77        #
78        # Current displayed perspective
79        self._current_perspective = None
80
81        # Invoke the initial perspective
82        #self.perspectiveChanged("Fitting")
83        self.addWidgets()
84
85        # Fork off logging messages to the Log Window
86        XStream.stdout().messageWritten.connect(self.listWidget.insertPlainText)
87        XStream.stderr().messageWritten.connect(self.listWidget.insertPlainText)
88
89        # Log the start of the session
90        logging.info(" --- SasView session started ---")
91        # Log the python version
92        logging.info("Python: %s" % sys.version)
93
94        # Set up the status bar
95        self.statusBarSetup()
96        # Show the Welcome panel
97        self.welcomePanel = WelcomePanel()
98        self._workspace.workspace.addSubWindow(self.welcomePanel)
99
100        # Current help file
101        self._helpView = QWebView()
102        # Needs URL like path, so no path.join() here
103        self._helpLocation = GuiUtils.HELP_DIRECTORY_LOCATION + "/index.html"
104
105        # Current tutorial location
106        self._tutorialLocation = os.path.abspath(os.path.join(GuiUtils.HELP_DIRECTORY_LOCATION,
107                                              "_downloads",
108                                              "Tutorial.pdf"))
109    def addWidgets(self):
110        """
111        Populate the main window with widgets
112
113        TODO: overwrite close() on Log and DR widgets so they can be hidden/shown
114        on request
115        """
116        # Add FileDialog widget as docked
117        self.filesWidget = DataExplorerWindow(self._parent, self, manager=self._data_manager)
118        ObjectLibrary.addObject('DataExplorer', self.filesWidget)
119
120        self.dockedFilesWidget = QDockWidget("Data Explorer", self._workspace)
121        self.dockedFilesWidget.setFloating(False)
122        self.dockedFilesWidget.setWidget(self.filesWidget)
123
124        # Disable maximize/minimize and close buttons
125        self.dockedFilesWidget.setFeatures(QDockWidget.NoDockWidgetFeatures)
126
127        #self._workspace.workspace.addDockWidget(Qt.LeftDockWidgetArea, self.dockedFilesWidget)
128        self._workspace.addDockWidget(Qt.LeftDockWidgetArea, self.dockedFilesWidget)
129
130        # Add the console window as another docked widget
131        self.logDockWidget = QDockWidget("Log Explorer", self._workspace)
132        self.logDockWidget.setObjectName("LogDockWidget")
133
134        self.listWidget = QTextBrowser()
135        self.logDockWidget.setWidget(self.listWidget)
136        self._workspace.addDockWidget(Qt.BottomDockWidgetArea, self.logDockWidget)
137
138        # Add other, minor widgets
139        self.ackWidget = Acknowledgements()
140        self.aboutWidget = AboutBox()
141
142        # Add calculators - floating for usability
143        self.SLDCalculator = SldPanel(self)
144        self.DVCalculator = DensityPanel(self)
145        self.KIESSIGCalculator = KiessigPanel(self)
146        self.SlitSizeCalculator = SlitSizeCalculator(self)
147        self.GENSASCalculator = GenericScatteringCalculator(self)
148        self.ResolutionCalculator = ResolutionCalculatorPanel(self)
149        self.DataOperation = DataOperationUtilityPanel(self)
150
151    def statusBarSetup(self):
152        """
153        Define the status bar.
154        | <message label> .... | Progress Bar |
155
156        Progress bar invisible until explicitly shown
157        """
158        self.progress = QProgressBar()
159        self._workspace.statusbar.setSizeGripEnabled(False)
160
161        self.statusLabel = QLabel()
162        self.statusLabel.setText("Welcome to SasView")
163        self._workspace.statusbar.addPermanentWidget(self.statusLabel, 1)
164        self._workspace.statusbar.addPermanentWidget(self.progress, stretch=0)
165        self.progress.setRange(0, 100)
166        self.progress.setValue(0)
167        self.progress.setTextVisible(True)
168        self.progress.setVisible(False)
169
170    def fileWasRead(self, data):
171        """
172        Callback for fileDataReceivedSignal
173        """
174        pass
175
176    def workspace(self):
177        """
178        Accessor for the main window workspace
179        """
180        return self._workspace.workspace
181
182    def perspectiveChanged(self, perspective_name):
183        """
184        Respond to change of the perspective signal
185        """
186
187        # Save users from themselves...
188        if isinstance(self._current_perspective, Perspectives.PERSPECTIVES[str(perspective_name)]):
189            self.setupPerspectiveMenubarOptions(self._current_perspective)
190            return
191
192        # Close the previous perspective
193        self.clearPerspectiveMenubarOptions(self._current_perspective)
194        if self._current_perspective:
195            self._current_perspective.setClosable()
196            #self._workspace.workspace.removeSubWindow(self._current_perspective)
197            self._current_perspective.close()
198            self._workspace.workspace.removeSubWindow(self._current_perspective)
199        # Default perspective
200        self._current_perspective = Perspectives.PERSPECTIVES[str(perspective_name)](parent=self)
201
202        self._workspace.workspace.addSubWindow(self._current_perspective)
203
204        # Resize to the workspace height
205        workspace_height = self._workspace.workspace.sizeHint().height()
206        perspective_size = self._current_perspective.sizeHint()
207        #if workspace_height < perspective_size.height():
208        perspective_width = perspective_size.width()
209        self._current_perspective.resize(perspective_width, workspace_height-10)
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 addCallbacks(self):
345        """
346        Method defining all signal connections for the gui manager
347        """
348        self.communicate = GuiUtils.Communicate()
349        self.communicate.fileDataReceivedSignal.connect(self.fileWasRead)
350        self.communicate.statusBarUpdateSignal.connect(self.updateStatusBar)
351        self.communicate.updatePerspectiveWithDataSignal.connect(self.updatePerspective)
352        self.communicate.progressBarUpdateSignal.connect(self.updateProgressBar)
353        self.communicate.perspectiveChangedSignal.connect(self.perspectiveChanged)
354        self.communicate.updateTheoryFromPerspectiveSignal.connect(self.updateTheoryFromPerspective)
355        self.communicate.plotRequestedSignal.connect(self.showPlot)
356        self.communicate.updateModelFromDataOperationPanelSignal.connect(self.updateModelFromDataOperationPanel)
357
358    def addTriggers(self):
359        """
360        Trigger definitions for all menu/toolbar actions.
361        """
362        # File
363        self._workspace.actionLoadData.triggered.connect(self.actionLoadData)
364        self._workspace.actionLoad_Data_Folder.triggered.connect(self.actionLoad_Data_Folder)
365        self._workspace.actionOpen_Project.triggered.connect(self.actionOpen_Project)
366        self._workspace.actionOpen_Analysis.triggered.connect(self.actionOpen_Analysis)
367        self._workspace.actionSave.triggered.connect(self.actionSave)
368        self._workspace.actionSave_Analysis.triggered.connect(self.actionSave_Analysis)
369        self._workspace.actionQuit.triggered.connect(self.actionQuit)
370        # Edit
371        self._workspace.actionUndo.triggered.connect(self.actionUndo)
372        self._workspace.actionRedo.triggered.connect(self.actionRedo)
373        self._workspace.actionCopy.triggered.connect(self.actionCopy)
374        self._workspace.actionPaste.triggered.connect(self.actionPaste)
375        self._workspace.actionReport.triggered.connect(self.actionReport)
376        self._workspace.actionReset.triggered.connect(self.actionReset)
377        self._workspace.actionExcel.triggered.connect(self.actionExcel)
378        self._workspace.actionLatex.triggered.connect(self.actionLatex)
379
380        # View
381        self._workspace.actionShow_Grid_Window.triggered.connect(self.actionShow_Grid_Window)
382        self._workspace.actionHide_Toolbar.triggered.connect(self.actionHide_Toolbar)
383        self._workspace.actionStartup_Settings.triggered.connect(self.actionStartup_Settings)
384        self._workspace.actionCategry_Manager.triggered.connect(self.actionCategry_Manager)
385        # Tools
386        self._workspace.actionData_Operation.triggered.connect(self.actionData_Operation)
387        self._workspace.actionSLD_Calculator.triggered.connect(self.actionSLD_Calculator)
388        self._workspace.actionDensity_Volume_Calculator.triggered.connect(self.actionDensity_Volume_Calculator)
389        self._workspace.actionKeissig_Calculator.triggered.connect(self.actionKiessig_Calculator)
390        #self._workspace.actionKIESSING_Calculator.triggered.connect(self.actionKIESSING_Calculator)
391        self._workspace.actionSlit_Size_Calculator.triggered.connect(self.actionSlit_Size_Calculator)
392        self._workspace.actionSAS_Resolution_Estimator.triggered.connect(self.actionSAS_Resolution_Estimator)
393        self._workspace.actionGeneric_Scattering_Calculator.triggered.connect(self.actionGeneric_Scattering_Calculator)
394        self._workspace.actionPython_Shell_Editor.triggered.connect(self.actionPython_Shell_Editor)
395        self._workspace.actionImage_Viewer.triggered.connect(self.actionImage_Viewer)
396        # Fitting
397        self._workspace.actionNew_Fit_Page.triggered.connect(self.actionNew_Fit_Page)
398        self._workspace.actionConstrained_Fit.triggered.connect(self.actionConstrained_Fit)
399        self._workspace.actionCombine_Batch_Fit.triggered.connect(self.actionCombine_Batch_Fit)
400        self._workspace.actionFit_Options.triggered.connect(self.actionFit_Options)
401        self._workspace.actionGPU_Options.triggered.connect(self.actionGPU_Options)
402        self._workspace.actionFit_Results.triggered.connect(self.actionFit_Results)
403        self._workspace.actionChain_Fitting.triggered.connect(self.actionChain_Fitting)
404        self._workspace.actionEdit_Custom_Model.triggered.connect(self.actionEdit_Custom_Model)
405        # Window
406        self._workspace.actionCascade.triggered.connect(self.actionCascade)
407        self._workspace.actionTile.triggered.connect(self.actionTile)
408        self._workspace.actionArrange_Icons.triggered.connect(self.actionArrange_Icons)
409        self._workspace.actionNext.triggered.connect(self.actionNext)
410        self._workspace.actionPrevious.triggered.connect(self.actionPrevious)
411        # Analysis
412        self._workspace.actionFitting.triggered.connect(self.actionFitting)
413        self._workspace.actionInversion.triggered.connect(self.actionInversion)
414        self._workspace.actionInvariant.triggered.connect(self.actionInvariant)
415        # Help
416        self._workspace.actionDocumentation.triggered.connect(self.actionDocumentation)
417        self._workspace.actionTutorial.triggered.connect(self.actionTutorial)
418        self._workspace.actionAcknowledge.triggered.connect(self.actionAcknowledge)
419        self._workspace.actionAbout.triggered.connect(self.actionAbout)
420        self._workspace.actionCheck_for_update.triggered.connect(self.actionCheck_for_update)
421
422    #============ FILE =================
423    def actionLoadData(self):
424        """
425        Menu File/Load Data File(s)
426        """
427        self.filesWidget.loadFile()
428
429    def actionLoad_Data_Folder(self):
430        """
431        Menu File/Load Data Folder
432        """
433        self.filesWidget.loadFolder()
434
435    def actionOpen_Project(self):
436        """
437        Menu Open Project
438        """
439        self.filesWidget.loadProject()
440
441    def actionOpen_Analysis(self):
442        """
443        """
444        print("actionOpen_Analysis TRIGGERED")
445        pass
446
447    def actionSave(self):
448        """
449        Menu Save Project
450        """
451        self.filesWidget.saveProject()
452
453    def actionSave_Analysis(self):
454        """
455        """
456        print("actionSave_Analysis TRIGGERED")
457
458        pass
459
460    def actionQuit(self):
461        """
462        Close the reactor, exit the application.
463        """
464        self.quitApplication()
465
466    #============ EDIT =================
467    def actionUndo(self):
468        """
469        """
470        print("actionUndo TRIGGERED")
471        pass
472
473    def actionRedo(self):
474        """
475        """
476        print("actionRedo TRIGGERED")
477        pass
478
479    def actionCopy(self):
480        """
481        """
482        print("actionCopy TRIGGERED")
483        pass
484
485    def actionPaste(self):
486        """
487        """
488        print("actionPaste TRIGGERED")
489        pass
490
491    def actionReport(self):
492        """
493        """
494        print("actionReport TRIGGERED")
495        pass
496
497    def actionReset(self):
498        """
499        """
500        logging.warning(" *** actionOpen_Analysis logging *******")
501        print("actionReset print TRIGGERED")
502        sys.stderr.write("STDERR - TRIGGERED")
503        pass
504
505    def actionExcel(self):
506        """
507        """
508        print("actionExcel TRIGGERED")
509        pass
510
511    def actionLatex(self):
512        """
513        """
514        print("actionLatex TRIGGERED")
515        pass
516
517    #============ VIEW =================
518    def actionShow_Grid_Window(self):
519        """
520        """
521        print("actionShow_Grid_Window TRIGGERED")
522        pass
523
524    def actionHide_Toolbar(self):
525        """
526        Toggle toolbar vsibility
527        """
528        if self._workspace.toolBar.isVisible():
529            self._workspace.actionHide_Toolbar.setText("Show Toolbar")
530            self._workspace.toolBar.setVisible(False)
531        else:
532            self._workspace.actionHide_Toolbar.setText("Hide Toolbar")
533            self._workspace.toolBar.setVisible(True)
534        pass
535
536    def actionStartup_Settings(self):
537        """
538        """
539        print("actionStartup_Settings TRIGGERED")
540        pass
541
542    def actionCategry_Manager(self):
543        """
544        """
545        print("actionCategry_Manager TRIGGERED")
546        pass
547
548    #============ TOOLS =================
549    def actionData_Operation(self):
550        """
551        """
552        self.communicate.sendDataToPanelSignal.emit(self._data_manager.get_all_data())
553
554        self.DataOperation.show()
555
556    def actionSLD_Calculator(self):
557        """
558        """
559        self.SLDCalculator.show()
560
561    def actionDensity_Volume_Calculator(self):
562        """
563        """
564        self.DVCalculator.show()
565
566    def actionKiessig_Calculator(self):
567        """
568        """
569        #self.DVCalculator.show()
570        self.KIESSIGCalculator.show()
571
572    def actionSlit_Size_Calculator(self):
573        """
574        """
575        self.SlitSizeCalculator.show()
576
577    def actionSAS_Resolution_Estimator(self):
578        """
579        """
580        self.ResolutionCalculator.show()
581
582    def actionGeneric_Scattering_Calculator(self):
583        """
584        """
585        self.GENSASCalculator.show()
586
587    def actionPython_Shell_Editor(self):
588        """
589        Display the Jupyter console as a docked widget.
590        """
591        # Import moved here for startup performance reasons
592        from sas.qtgui.Utilities.IPythonWidget import IPythonWidget
593        terminal = IPythonWidget()
594
595        # Add the console window as another docked widget
596        self.ipDockWidget = QDockWidget("IPython", self._workspace)
597        self.ipDockWidget.setObjectName("IPythonDockWidget")
598        self.ipDockWidget.setWidget(terminal)
599        self._workspace.addDockWidget(Qt.RightDockWidgetArea, self.ipDockWidget)
600
601    def actionImage_Viewer(self):
602        """
603        """
604        print("actionImage_Viewer TRIGGERED")
605        pass
606
607    #============ FITTING =================
608    def actionNew_Fit_Page(self):
609        """
610        Add a new, empty Fit page in the fitting perspective.
611        """
612        # Make sure the perspective is correct
613        per = self.perspective()
614        if not isinstance(per, FittingWindow):
615            return
616        per.addFit(None)
617
618    def actionConstrained_Fit(self):
619        """
620        """
621        print("actionConstrained_Fit TRIGGERED")
622        pass
623
624    def actionCombine_Batch_Fit(self):
625        """
626        """
627        print("actionCombine_Batch_Fit TRIGGERED")
628        pass
629
630    def actionFit_Options(self):
631        """
632        """
633        if getattr(self._current_perspective, "fit_options_widget"):
634            self._current_perspective.fit_options_widget.show()
635        pass
636
637    def actionGPU_Options(self):
638        """
639        Load the OpenCL selection dialog if the fitting perspective is active
640        """
641        if hasattr(self._current_perspective, "gpu_options_widget"):
642            self._current_perspective.gpu_options_widget.show()
643        pass
644
645    def actionFit_Results(self):
646        """
647        """
648        print("actionFit_Results TRIGGERED")
649        pass
650
651    def actionChain_Fitting(self):
652        """
653        """
654        print("actionChain_Fitting TRIGGERED")
655        pass
656
657    def actionEdit_Custom_Model(self):
658        """
659        """
660        print("actionEdit_Custom_Model TRIGGERED")
661        pass
662
663    #============ ANALYSIS =================
664    def actionFitting(self):
665        """
666        Change to the Fitting perspective
667        """
668        self.perspectiveChanged("Fitting")
669
670    def actionInversion(self):
671        """
672        Change to the Inversion perspective
673        """
674        # For now we'll just update the analysis menu status but when the inversion is implemented delete from here
675        self.checkAnalysisOption(self._workspace.actionInversion)
676        print("actionInversion TRIGGERED")
677        # to here and uncomment the following line
678        # self.perspectiveChanged("Inversion")
679
680    def actionInvariant(self):
681        """
682        Change to the Invariant perspective
683        """
684        self.perspectiveChanged("Invariant")
685
686    #============ WINDOW =================
687    def actionCascade(self):
688        """
689        Arranges all the child windows in a cascade pattern.
690        """
691        self._workspace.workspace.cascade()
692
693    def actionTile(self):
694        """
695        Tile workspace windows
696        """
697        self._workspace.workspace.tile()
698
699    def actionArrange_Icons(self):
700        """
701        Arranges all iconified windows at the bottom of the workspace
702        """
703        self._workspace.workspace.arrangeIcons()
704
705    def actionNext(self):
706        """
707        Gives the input focus to the next window in the list of child windows.
708        """
709        self._workspace.workspace.activateNextWindow()
710
711    def actionPrevious(self):
712        """
713        Gives the input focus to the previous window in the list of child windows.
714        """
715        self._workspace.workspace.activatePreviousWindow()
716
717    #============ HELP =================
718    def actionDocumentation(self):
719        """
720        Display the documentation
721
722        TODO: use QNetworkAccessManager to assure _helpLocation is valid
723        """
724        self._helpView.load(QUrl(self._helpLocation))
725        self._helpView.show()
726
727    def actionTutorial(self):
728        """
729        Open the tutorial PDF file with default PDF renderer
730        """
731        # Not terribly safe here. Shell injection warning.
732        # isfile() helps but this probably needs a better solution.
733        if os.path.isfile(self._tutorialLocation):
734            result = subprocess.Popen([self._tutorialLocation], shell=True)
735
736    def actionAcknowledge(self):
737        """
738        Open the Acknowledgements widget
739        """
740        self.ackWidget.show()
741
742    def actionAbout(self):
743        """
744        Open the About box
745        """
746        # Update the about box with current version and stuff
747
748        # TODO: proper sizing
749        self.aboutWidget.show()
750
751    def actionCheck_for_update(self):
752        """
753        Menu Help/Check for Update
754        """
755        self.checkUpdate()
756
757    def updateTheoryFromPerspective(self, index):
758        """
759        Catch the theory update signal from a perspective
760        Send the request to the DataExplorer for updating the theory model.
761        """
762        self.filesWidget.updateTheoryFromPerspective(index)
763
764    def updateModelFromDataOperationPanel(self, new_item, new_datalist_item):
765        """
766        :param new_item: item to be added to list of loaded files
767        :param new_datalist_item:
768        """
769        if not isinstance(new_item, QStandardItem) or \
770                not isinstance(new_datalist_item, dict):
771            msg = "Wrong data type returned from calculations."
772            raise AttributeError(msg)
773
774        self.filesWidget.model.appendRow(new_item)
775        self._data_manager.add_data(new_datalist_item)
776
777    def showPlot(self, plot):
778        """
779        Pass the show plot request to the data explorer
780        """
781        if hasattr(self, "filesWidget"):
782            self.filesWidget.displayData(plot)
783
784    def uncheckAllMenuItems(self, menuObject):
785        """
786        Uncheck all options in a given menu
787        """
788        menuObjects = menuObject.actions()
789
790        for menuItem in menuObjects:
791            menuItem.setChecked(False)
792
793    def checkAnalysisOption(self, analysisMenuOption):
794        """
795        Unchecks all the items in the analysis menu and checks the item passed
796        """
797        self.uncheckAllMenuItems(self._workspace.menuAnalysis)
798        analysisMenuOption.setChecked(True)
799
800    def clearPerspectiveMenubarOptions(self, perspective):
801        """
802        When closing a perspective, clears the menu bar
803        """
804        for menuItem in self._workspace.menuAnalysis.actions():
805            menuItem.setChecked(False)
806
807        if isinstance(self._current_perspective, Perspectives.PERSPECTIVES["Fitting"]):
808            self._workspace.menubar.removeAction(self._workspace.menuFitting.menuAction())
809
810    def setupPerspectiveMenubarOptions(self, perspective):
811        """
812        When setting a perspective, sets up the menu bar
813        """
814        if isinstance(perspective, Perspectives.PERSPECTIVES["Fitting"]):
815            self.checkAnalysisOption(self._workspace.actionFitting)
816            # Put the fitting menu back in
817            # This is a bit involved but it is needed to preserve the menu ordering
818            self._workspace.menubar.removeAction(self._workspace.menuWindow.menuAction())
819            self._workspace.menubar.removeAction(self._workspace.menuHelp.menuAction())
820            self._workspace.menubar.addAction(self._workspace.menuFitting.menuAction())
821            self._workspace.menubar.addAction(self._workspace.menuWindow.menuAction())
822            self._workspace.menubar.addAction(self._workspace.menuHelp.menuAction())
823        elif isinstance(perspective, Perspectives.PERSPECTIVES["Invariant"]):
824            self.checkAnalysisOption(self._workspace.actionInvariant)
825        # elif isinstance(perspective, Perspectives.PERSPECTIVES["Inversion"]):
826        #     self.checkAnalysisOption(self._workspace.actionInversion)
Note: See TracBrowser for help on using the repository browser.