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

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

Fixed Mdi area and widget resizing issue(s)

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