source: sasview/src/sas/qtgui/MainWindow/GuiManager.py @ 03e04a4

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

More CR and merge fixes.

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