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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 6040cd7 was 6040cd7, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

Refactored getFitParameters slightly to allow use for both parameter
copy, analysis save and project save

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