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

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

Merged ESS_GUI_image_viewer

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