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

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

Added "Close All Plots" to main window/Window menu

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