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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 768387e0 was 768387e0, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

Added Data Explorer visibility toggle for more flexibility. SASVIEW-998

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