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

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

Fixed lifecycle/visibility of the Grid Viewer SASVIEW-1122

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