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

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

More Qt5 related fixes

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