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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since d00475d was d00475d, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 11 months ago

Merge branch 'ESS_GUI' into ESS_GUI_project_save

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