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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 0c83303 was 0c83303, checked in by piotr, 5 years ago

Minor improvements after RH's review. SASVIEW-275

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