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

ESS_GUIESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_sync_sascalc
Last change on this file since a8e6394 was d9e7792, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

Show Plot now restores minimized plots. SASVIEW-1221 == trac#13
Added "Minimize all plots" to Window menu
Changed draw → draw_idle to avoid weird numpy linalg errors

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