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

Last change on this file since 5dba493 was f0a8f74, checked in by Torin Cooper-Bennun <torin.cooper-bennun@…>, 6 years ago

fixed missing import statement + decl. of 'logger' in GuiManager?

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