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

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

New category manager design

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