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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since a0ed202 was a0ed202, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

Updates to the spec file for reporting.
Switch toolbar icons off by default

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