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

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

Modified the open tutorial action - it now opens a webpage with tutorial
links. Just like in 4.2. SASVIEW-1255.
Also, modified copyright year in yet another place.

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