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

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

Clumsy fix to the single-data, multi-fitpage plotting issue SASVIEW-1018.
Fixed tests after replacing plot_dict indexing from .id to .name

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