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

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

Disabled the orientation viewer SASVIEW-1132

  • Property mode set to 100644
File size: 36.7 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)
[12acdcc]433        # orientation viewer set to invisible SASVIEW-1132
434        self._workspace.actionOrientation_Viewer.setVisible(False)
[dad086f]435
[f721030]436        # File
437        self._workspace.actionLoadData.triggered.connect(self.actionLoadData)
438        self._workspace.actionLoad_Data_Folder.triggered.connect(self.actionLoad_Data_Folder)
439        self._workspace.actionOpen_Project.triggered.connect(self.actionOpen_Project)
440        self._workspace.actionOpen_Analysis.triggered.connect(self.actionOpen_Analysis)
441        self._workspace.actionSave.triggered.connect(self.actionSave)
442        self._workspace.actionSave_Analysis.triggered.connect(self.actionSave_Analysis)
443        self._workspace.actionQuit.triggered.connect(self.actionQuit)
444        # Edit
445        self._workspace.actionUndo.triggered.connect(self.actionUndo)
446        self._workspace.actionRedo.triggered.connect(self.actionRedo)
447        self._workspace.actionCopy.triggered.connect(self.actionCopy)
448        self._workspace.actionPaste.triggered.connect(self.actionPaste)
449        self._workspace.actionReport.triggered.connect(self.actionReport)
450        self._workspace.actionReset.triggered.connect(self.actionReset)
451        self._workspace.actionExcel.triggered.connect(self.actionExcel)
452        self._workspace.actionLatex.triggered.connect(self.actionLatex)
453        # View
454        self._workspace.actionShow_Grid_Window.triggered.connect(self.actionShow_Grid_Window)
455        self._workspace.actionHide_Toolbar.triggered.connect(self.actionHide_Toolbar)
456        self._workspace.actionStartup_Settings.triggered.connect(self.actionStartup_Settings)
[3d18691]457        self._workspace.actionCategory_Manager.triggered.connect(self.actionCategory_Manager)
[768387e0]458        self._workspace.actionHide_DataExplorer.triggered.connect(self.actionHide_DataExplorer)
[f721030]459        # Tools
460        self._workspace.actionData_Operation.triggered.connect(self.actionData_Operation)
461        self._workspace.actionSLD_Calculator.triggered.connect(self.actionSLD_Calculator)
462        self._workspace.actionDensity_Volume_Calculator.triggered.connect(self.actionDensity_Volume_Calculator)
[363fbfa]463        self._workspace.actionKeissig_Calculator.triggered.connect(self.actionKiessig_Calculator)
464        #self._workspace.actionKIESSING_Calculator.triggered.connect(self.actionKIESSING_Calculator)
[f721030]465        self._workspace.actionSlit_Size_Calculator.triggered.connect(self.actionSlit_Size_Calculator)
466        self._workspace.actionSAS_Resolution_Estimator.triggered.connect(self.actionSAS_Resolution_Estimator)
467        self._workspace.actionGeneric_Scattering_Calculator.triggered.connect(self.actionGeneric_Scattering_Calculator)
468        self._workspace.actionPython_Shell_Editor.triggered.connect(self.actionPython_Shell_Editor)
469        self._workspace.actionImage_Viewer.triggered.connect(self.actionImage_Viewer)
[aa1db44]470        self._workspace.actionOrientation_Viewer.triggered.connect(self.actionOrientation_Viewer)
[6b50296]471        self._workspace.actionFreeze_Theory.triggered.connect(self.actionFreeze_Theory)
[f721030]472        # Fitting
473        self._workspace.actionNew_Fit_Page.triggered.connect(self.actionNew_Fit_Page)
474        self._workspace.actionConstrained_Fit.triggered.connect(self.actionConstrained_Fit)
475        self._workspace.actionCombine_Batch_Fit.triggered.connect(self.actionCombine_Batch_Fit)
476        self._workspace.actionFit_Options.triggered.connect(self.actionFit_Options)
[06ce180]477        self._workspace.actionGPU_Options.triggered.connect(self.actionGPU_Options)
[f721030]478        self._workspace.actionFit_Results.triggered.connect(self.actionFit_Results)
[3b3b40b]479        self._workspace.actionAdd_Custom_Model.triggered.connect(self.actionAdd_Custom_Model)
[f721030]480        self._workspace.actionEdit_Custom_Model.triggered.connect(self.actionEdit_Custom_Model)
[3b3b40b]481        self._workspace.actionManage_Custom_Models.triggered.connect(self.actionManage_Custom_Models)
[01ef3f7]482        self._workspace.actionAddMult_Models.triggered.connect(self.actionAddMult_Models)
[339e22b]483        self._workspace.actionEditMask.triggered.connect(self.actionEditMask)
484
[f721030]485        # Window
486        self._workspace.actionCascade.triggered.connect(self.actionCascade)
[e540cd2]487        self._workspace.actionTile.triggered.connect(self.actionTile)
[f721030]488        self._workspace.actionArrange_Icons.triggered.connect(self.actionArrange_Icons)
489        self._workspace.actionNext.triggered.connect(self.actionNext)
490        self._workspace.actionPrevious.triggered.connect(self.actionPrevious)
491        # Analysis
492        self._workspace.actionFitting.triggered.connect(self.actionFitting)
493        self._workspace.actionInversion.triggered.connect(self.actionInversion)
494        self._workspace.actionInvariant.triggered.connect(self.actionInvariant)
[8ac3551]495        self._workspace.actionCorfunc.triggered.connect(self.actionCorfunc)
[f721030]496        # Help
497        self._workspace.actionDocumentation.triggered.connect(self.actionDocumentation)
498        self._workspace.actionTutorial.triggered.connect(self.actionTutorial)
499        self._workspace.actionAcknowledge.triggered.connect(self.actionAcknowledge)
500        self._workspace.actionAbout.triggered.connect(self.actionAbout)
[fc5d2d7f]501        self._workspace.actionWelcomeWidget.triggered.connect(self.actionWelcome)
[f721030]502        self._workspace.actionCheck_for_update.triggered.connect(self.actionCheck_for_update)
503
[d4dac80]504        self.communicate.sendDataToGridSignal.connect(self.showBatchOutput)
505
[f721030]506    #============ FILE =================
507    def actionLoadData(self):
508        """
[9e426c1]509        Menu File/Load Data File(s)
[f721030]510        """
[5032ea68]511        self.filesWidget.loadFile()
[f721030]512
513    def actionLoad_Data_Folder(self):
514        """
[9e426c1]515        Menu File/Load Data Folder
[f721030]516        """
[5032ea68]517        self.filesWidget.loadFolder()
[f721030]518
519    def actionOpen_Project(self):
520        """
[630155bd]521        Menu Open Project
[f721030]522        """
[630155bd]523        self.filesWidget.loadProject()
[f721030]524
525    def actionOpen_Analysis(self):
526        """
527        """
528        print("actionOpen_Analysis TRIGGERED")
529        pass
530
531    def actionSave(self):
532        """
[630155bd]533        Menu Save Project
[f721030]534        """
[630155bd]535        self.filesWidget.saveProject()
[f721030]536
537    def actionSave_Analysis(self):
538        """
[57be490]539        Menu File/Save Analysis
[f721030]540        """
[57be490]541        self.communicate.saveAnalysisSignal.emit()
[f721030]542
543    def actionQuit(self):
544        """
[1042dba]545        Close the reactor, exit the application.
[f721030]546        """
[9e426c1]547        self.quitApplication()
[f721030]548
549    #============ EDIT =================
550    def actionUndo(self):
551        """
552        """
553        print("actionUndo TRIGGERED")
554        pass
555
556    def actionRedo(self):
557        """
558        """
559        print("actionRedo TRIGGERED")
560        pass
561
562    def actionCopy(self):
563        """
[8e2cd79]564        Send a signal to the fitting perspective so parameters
565        can be saved to the clipboard
[f721030]566        """
[8e2cd79]567        self.communicate.copyFitParamsSignal.emit("")
[0eff615]568        self._workspace.actionPaste.setEnabled(True)
[f721030]569        pass
570
571    def actionPaste(self):
572        """
[8e2cd79]573        Send a signal to the fitting perspective so parameters
574        from the clipboard can be used to modify the fit state
[f721030]575        """
[8e2cd79]576        self.communicate.pasteFitParamsSignal.emit()
[f721030]577
578    def actionReport(self):
579        """
[57be490]580        Show the Fit Report dialog.
[f721030]581        """
[57be490]582        report_list = None
583        if getattr(self._current_perspective, "currentTab"):
584            try:
585                report_list = self._current_perspective.currentTab.getReport()
586            except Exception as ex:
587                logging.error("Report generation failed with: " + str(ex))
588
589        if report_list is not None:
590            self.report_dialog = ReportDialog(parent=self, report_list=report_list)
591            self.report_dialog.show()
[f721030]592
593    def actionReset(self):
594        """
595        """
[0cd8612]596        logging.warning(" *** actionOpen_Analysis logging *******")
597        print("actionReset print TRIGGERED")
598        sys.stderr.write("STDERR - TRIGGERED")
[f721030]599        pass
600
601    def actionExcel(self):
602        """
[8e2cd79]603        Send a signal to the fitting perspective so parameters
604        can be saved to the clipboard
[f721030]605        """
[20f4857]606        self.communicate.copyExcelFitParamsSignal.emit("Excel")
[f721030]607
608    def actionLatex(self):
609        """
[8e2cd79]610        Send a signal to the fitting perspective so parameters
611        can be saved to the clipboard
[f721030]612        """
[20f4857]613        self.communicate.copyLatexFitParamsSignal.emit("Latex")
[f721030]614
615    #============ VIEW =================
616    def actionShow_Grid_Window(self):
617        """
618        """
[d4dac80]619        self.showBatchOutput(None)
620
621    def showBatchOutput(self, output_data):
622        """
623        Display/redisplay the batch fit viewer
624        """
[fa762f4]625        self.grid_subwindow.setVisible(True)
[d4dac80]626        if output_data:
627            self.grid_window.addFitResults(output_data)
[f721030]628
629    def actionHide_Toolbar(self):
630        """
[e540cd2]631        Toggle toolbar vsibility
[f721030]632        """
[e540cd2]633        if self._workspace.toolBar.isVisible():
634            self._workspace.actionHide_Toolbar.setText("Show Toolbar")
635            self._workspace.toolBar.setVisible(False)
636        else:
637            self._workspace.actionHide_Toolbar.setText("Hide Toolbar")
638            self._workspace.toolBar.setVisible(True)
[f721030]639        pass
640
[768387e0]641    def actionHide_DataExplorer(self):
642        """
643        Toggle Data Explorer vsibility
644        """
645        if self.dockedFilesWidget.isVisible():
646            #self._workspace.actionHide_DataExplorer.setText("Show Data Explorer")
647            self.dockedFilesWidget.setVisible(False)
648        else:
649            #self._workspace.actionHide_DataExplorer.setText("Hide Data Explorer")
650            self.dockedFilesWidget.setVisible(True)
651        pass
652
[f721030]653    def actionStartup_Settings(self):
654        """
655        """
656        print("actionStartup_Settings TRIGGERED")
657        pass
658
[3d18691]659    def actionCategory_Manager(self):
[f721030]660        """
661        """
[3d18691]662        self.categoryManagerWidget.show()
[f721030]663
664    #============ TOOLS =================
665    def actionData_Operation(self):
666        """
667        """
[f0bb711]668        self.communicate.sendDataToPanelSignal.emit(self._data_manager.get_all_data())
[d5c5d3d]669
670        self.DataOperation.show()
[f721030]671
672    def actionSLD_Calculator(self):
673        """
674        """
[1d85b5e]675        self.SLDCalculator.show()
[f721030]676
677    def actionDensity_Volume_Calculator(self):
678        """
679        """
[1d85b5e]680        self.DVCalculator.show()
[f721030]681
[363fbfa]682    def actionKiessig_Calculator(self):
683        """
684        """
685        self.KIESSIGCalculator.show()
686
[f721030]687    def actionSlit_Size_Calculator(self):
688        """
689        """
[a8ec5b1]690        self.SlitSizeCalculator.show()
[f721030]691
692    def actionSAS_Resolution_Estimator(self):
693        """
694        """
[fa05c6c1]695        try:
696            self.ResolutionCalculator.show()
697        except Exception as ex:
698            logging.error(str(ex))
699            return
[f721030]700
701    def actionGeneric_Scattering_Calculator(self):
702        """
703        """
[fa05c6c1]704        try:
705            self.GENSASCalculator.show()
706        except Exception as ex:
707            logging.error(str(ex))
708            return
[f721030]709
710    def actionPython_Shell_Editor(self):
711        """
[1af348e]712        Display the Jupyter console as a docked widget.
[f721030]713        """
[fef38e8]714        # Import moved here for startup performance reasons
715        from sas.qtgui.Utilities.IPythonWidget import IPythonWidget
[1af348e]716        terminal = IPythonWidget()
717
718        # Add the console window as another docked widget
[4992ff2]719        self.ipDockWidget = QDockWidget("IPython", self._workspace)
[1af348e]720        self.ipDockWidget.setObjectName("IPythonDockWidget")
721        self.ipDockWidget.setWidget(terminal)
[fbfc488]722        self._workspace.addDockWidget(Qt.RightDockWidgetArea, self.ipDockWidget)
[f721030]723
[6b50296]724    def actionFreeze_Theory(self):
725        """
726        Convert a child index with data into a separate top level dataset
727        """
728        self.filesWidget.freezeCheckedData()
729
[aa1db44]730    def actionOrientation_Viewer(self):
731        """
732        Make sasmodels orientation & jitter viewer available
733        """
734        from sasmodels.jitter import run as orientation_run
735        try:
736            orientation_run()
737        except Exception as ex:
738            logging.error(str(ex))
739
[f721030]740    def actionImage_Viewer(self):
741        """
742        """
743        print("actionImage_Viewer TRIGGERED")
744        pass
745
746    #============ FITTING =================
747    def actionNew_Fit_Page(self):
748        """
[60af928]749        Add a new, empty Fit page in the fitting perspective.
[f721030]750        """
[60af928]751        # Make sure the perspective is correct
752        per = self.perspective()
753        if not isinstance(per, FittingWindow):
754            return
755        per.addFit(None)
[f721030]756
757    def actionConstrained_Fit(self):
758        """
[676f137]759        Add a new Constrained and Simult. Fit page in the fitting perspective.
[f721030]760        """
[676f137]761        per = self.perspective()
762        if not isinstance(per, FittingWindow):
763            return
764        per.addConstraintTab()
[f721030]765
766    def actionCombine_Batch_Fit(self):
767        """
768        """
769        print("actionCombine_Batch_Fit TRIGGERED")
770        pass
771
772    def actionFit_Options(self):
773        """
774        """
[2d0e0c1]775        if getattr(self._current_perspective, "fit_options_widget"):
776            self._current_perspective.fit_options_widget.show()
[f721030]777        pass
778
[06ce180]779    def actionGPU_Options(self):
780        """
[9863343]781        Load the OpenCL selection dialog if the fitting perspective is active
[06ce180]782        """
[9863343]783        if hasattr(self._current_perspective, "gpu_options_widget"):
[06ce180]784            self._current_perspective.gpu_options_widget.show()
785        pass
786
[f721030]787    def actionFit_Results(self):
788        """
789        """
790        print("actionFit_Results TRIGGERED")
791        pass
792
[3b3b40b]793    def actionAdd_Custom_Model(self):
794        """
795        """
796        self.model_editor = TabbedModelEditor(self)
797        self.model_editor.show()
798
[f721030]799    def actionEdit_Custom_Model(self):
800        """
801        """
[3b3b40b]802        self.model_editor = TabbedModelEditor(self, edit_only=True)
803        self.model_editor.show()
804
805    def actionManage_Custom_Models(self):
806        """
807        """
808        self.model_manager = PluginManager(self)
809        self.model_manager.show()
[f721030]810
[01ef3f7]811    def actionAddMult_Models(self):
812        """
813        """
[3b8cc00]814        # Add Simple Add/Multiply Editor
[01ef3f7]815        self.add_mult_editor = AddMultEditor(self)
816        self.add_mult_editor.show()
817
[339e22b]818    def actionEditMask(self):
819
820        self.communicate.extMaskEditorSignal.emit()
821
[f721030]822    #============ ANALYSIS =================
823    def actionFitting(self):
824        """
[9e54199]825        Change to the Fitting perspective
[f721030]826        """
[9e54199]827        self.perspectiveChanged("Fitting")
[8ac3551]828        # Notify other widgets
829        self.filesWidget.onAnalysisUpdate("Fitting")
[f721030]830
831    def actionInversion(self):
832        """
[9e54199]833        Change to the Inversion perspective
[f721030]834        """
[d4881f6a]835        self.perspectiveChanged("Inversion")
[8ac3551]836        self.filesWidget.onAnalysisUpdate("Inversion")
[f721030]837
838    def actionInvariant(self):
839        """
[9e54199]840        Change to the Invariant perspective
[f721030]841        """
[9e54199]842        self.perspectiveChanged("Invariant")
[8ac3551]843        self.filesWidget.onAnalysisUpdate("Invariant")
844
845    def actionCorfunc(self):
846        """
847        Change to the Corfunc perspective
848        """
849        self.perspectiveChanged("Corfunc")
850        self.filesWidget.onAnalysisUpdate("Corfunc")
[f721030]851
852    #============ WINDOW =================
853    def actionCascade(self):
854        """
[e540cd2]855        Arranges all the child windows in a cascade pattern.
[f721030]856        """
[2b39fea]857        self._workspace.workspace.cascadeSubWindows()
[f721030]858
[e540cd2]859    def actionTile(self):
[f721030]860        """
[e540cd2]861        Tile workspace windows
[f721030]862        """
[2b39fea]863        self._workspace.workspace.tileSubWindows()
[f721030]864
865    def actionArrange_Icons(self):
866        """
[e540cd2]867        Arranges all iconified windows at the bottom of the workspace
[f721030]868        """
[e540cd2]869        self._workspace.workspace.arrangeIcons()
[f721030]870
871    def actionNext(self):
872        """
[e540cd2]873        Gives the input focus to the next window in the list of child windows.
[f721030]874        """
[2b39fea]875        self._workspace.workspace.activateNextSubWindow()
[f721030]876
877    def actionPrevious(self):
878        """
[e540cd2]879        Gives the input focus to the previous window in the list of child windows.
[f721030]880        """
[2b39fea]881        self._workspace.workspace.activatePreviousSubWindow()
[f721030]882
883    #============ HELP =================
884    def actionDocumentation(self):
885        """
[9e426c1]886        Display the documentation
887
888        TODO: use QNetworkAccessManager to assure _helpLocation is valid
[f721030]889        """
[fe76fba]890        helpfile = "/index.html"
[aed0532]891        self.showHelp(helpfile)
[f721030]892
893    def actionTutorial(self):
894        """
[9e426c1]895        Open the tutorial PDF file with default PDF renderer
[f721030]896        """
[9e426c1]897        # Not terribly safe here. Shell injection warning.
898        # isfile() helps but this probably needs a better solution.
899        if os.path.isfile(self._tutorialLocation):
900            result = subprocess.Popen([self._tutorialLocation], shell=True)
[f721030]901
902    def actionAcknowledge(self):
903        """
[9e426c1]904        Open the Acknowledgements widget
[f721030]905        """
[9e426c1]906        self.ackWidget.show()
[f721030]907
908    def actionAbout(self):
909        """
[9e426c1]910        Open the About box
[f721030]911        """
[f82ab8c]912        # Update the about box with current version and stuff
913
914        # TODO: proper sizing
915        self.aboutWidget.show()
[f721030]916
917    def actionCheck_for_update(self):
918        """
[9e426c1]919        Menu Help/Check for Update
[f721030]920        """
[9e426c1]921        self.checkUpdate()
922
[cbcdd2c]923    def updateTheoryFromPerspective(self, index):
924        """
925        Catch the theory update signal from a perspective
926        Send the request to the DataExplorer for updating the theory model.
927        """
928        self.filesWidget.updateTheoryFromPerspective(index)
929
[fd7ef36]930    def deleteIntermediateTheoryPlotsByModelID(self, model_id):
931        """
932        Catch the signal to delete items in the Theory item model which correspond to a model ID.
933        Send the request to the DataExplorer for updating the theory model.
934        """
935        self.filesWidget.deleteIntermediateTheoryPlotsByModelID(model_id)
936
[d5c5d3d]937    def updateModelFromDataOperationPanel(self, new_item, new_datalist_item):
938        """
939        :param new_item: item to be added to list of loaded files
940        :param new_datalist_item:
941        """
[4992ff2]942        if not isinstance(new_item, QStandardItem) or \
[d5c5d3d]943                not isinstance(new_datalist_item, dict):
944            msg = "Wrong data type returned from calculations."
[b3e8629]945            raise AttributeError(msg)
[d5c5d3d]946
947        self.filesWidget.model.appendRow(new_item)
948        self._data_manager.add_data(new_datalist_item)
949
[3b3b40b]950    def showPlotFromFilename(self, filename):
951        """
952        Pass the show plot request to the data explorer
953        """
954        if hasattr(self, "filesWidget"):
955            self.filesWidget.displayFile(filename=filename, is_data=True)
956
[5b144c6]957    def showPlot(self, plot, id):
[d48cc19]958        """
959        Pass the show plot request to the data explorer
960        """
961        if hasattr(self, "filesWidget"):
[5b144c6]962            self.filesWidget.displayData(plot, id)
[9e54199]963
964    def uncheckAllMenuItems(self, menuObject):
965        """
966        Uncheck all options in a given menu
967        """
968        menuObjects = menuObject.actions()
969
970        for menuItem in menuObjects:
971            menuItem.setChecked(False)
972
973    def checkAnalysisOption(self, analysisMenuOption):
974        """
975        Unchecks all the items in the analysis menu and checks the item passed
976        """
977        self.uncheckAllMenuItems(self._workspace.menuAnalysis)
978        analysisMenuOption.setChecked(True)
979
980    def clearPerspectiveMenubarOptions(self, perspective):
981        """
982        When closing a perspective, clears the menu bar
983        """
984        for menuItem in self._workspace.menuAnalysis.actions():
985            menuItem.setChecked(False)
986
987        if isinstance(self._current_perspective, Perspectives.PERSPECTIVES["Fitting"]):
988            self._workspace.menubar.removeAction(self._workspace.menuFitting.menuAction())
989
990    def setupPerspectiveMenubarOptions(self, perspective):
991        """
992        When setting a perspective, sets up the menu bar
993        """
[dee9e5f]994        self._workspace.actionReport.setEnabled(False)
[9e54199]995        if isinstance(perspective, Perspectives.PERSPECTIVES["Fitting"]):
996            self.checkAnalysisOption(self._workspace.actionFitting)
997            # Put the fitting menu back in
998            # This is a bit involved but it is needed to preserve the menu ordering
999            self._workspace.menubar.removeAction(self._workspace.menuWindow.menuAction())
1000            self._workspace.menubar.removeAction(self._workspace.menuHelp.menuAction())
1001            self._workspace.menubar.addAction(self._workspace.menuFitting.menuAction())
1002            self._workspace.menubar.addAction(self._workspace.menuWindow.menuAction())
1003            self._workspace.menubar.addAction(self._workspace.menuHelp.menuAction())
[dee9e5f]1004            self._workspace.actionReport.setEnabled(True)
[0eff615]1005
[9e54199]1006        elif isinstance(perspective, Perspectives.PERSPECTIVES["Invariant"]):
1007            self.checkAnalysisOption(self._workspace.actionInvariant)
[8ac3551]1008        elif isinstance(perspective, Perspectives.PERSPECTIVES["Inversion"]):
1009            self.checkAnalysisOption(self._workspace.actionInversion)
1010        elif isinstance(perspective, Perspectives.PERSPECTIVES["Corfunc"]):
1011            self.checkAnalysisOption(self._workspace.actionCorfunc)
Note: See TracBrowser for help on using the repository browser.