source: sasview/src/sas/qtgui/MainWindow/GuiManager.py @ 35e36fd

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 35e36fd was 339e22b, checked in by Laura Forster <Awork@…>, 6 years ago

Mask Edit menu item added to Fitting

Mask edit was previously only available via right clicking data before fit, now available on fitting drop down menu option.

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