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

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

Merging feature branches

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