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

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 6b50296 was 6b50296, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

Added freeze action for inner main model datasets. SASVIEW-1002

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