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

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

Fit result viewer SASVIEW-274, SASVIEW-275

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