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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since fd7ef36 was fd7ef36, checked in by Torin Cooper-Bennun <torin.cooper-bennun@…>, 6 years ago

delete intermediate theory-only plots after model evaluation, before adding current ones

this applies only to beta approximation, whereby plots such as beta(Q)
and S_eff(Q) may be removed between calculations. however, since it does
not affect behaviour otherwise, I am pushing to ESS_GUI to ensure no
later conflicts occur

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