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

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

Working version of Save/Load? Analysis. SASVIEW-983.
Changed the default behaviour of Category/Model? combos:
Selecting a category does not pre-select the first model now.

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