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

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

Assure the main window opens maximized and the welcome widget doesnt mess up the layout. SASVIEW-831

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