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
Line 
1import sys
2import os
3import subprocess
4import logging
5import json
6import webbrowser
7
8from PyQt5.QtWidgets import *
9from PyQt5.QtGui import *
10from PyQt5.QtCore import Qt, QLocale, QUrl
11
12from twisted.internet import reactor
13# General SAS imports
14from sas.qtgui.Utilities.ConnectionProxy import ConnectionProxy
15from sas.qtgui.Utilities.SasviewLogger import XStream
16
17import sas.qtgui.Utilities.LocalConfig as LocalConfig
18import sas.qtgui.Utilities.GuiUtils as GuiUtils
19
20import sas.qtgui.Utilities.ObjectLibrary as ObjectLibrary
21from sas.qtgui.Utilities.TabbedModelEditor import TabbedModelEditor
22from sas.qtgui.Utilities.PluginManager import PluginManager
23from sas.qtgui.Utilities.GridPanel import BatchOutputPanel
24
25from sas.qtgui.Utilities.ReportDialog import ReportDialog
26from sas.qtgui.MainWindow.UI.AcknowledgementsUI import Ui_Acknowledgements
27from sas.qtgui.MainWindow.AboutBox import AboutBox
28from sas.qtgui.MainWindow.WelcomePanel import WelcomePanel
29
30from sas.qtgui.MainWindow.DataManager import DataManager
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
36from sas.qtgui.Calculators.GenericScatteringCalculator import GenericScatteringCalculator
37from sas.qtgui.Calculators.ResolutionCalculatorPanel import ResolutionCalculatorPanel
38from sas.qtgui.Calculators.DataOperationUtilityPanel import DataOperationUtilityPanel
39
40# Perspectives
41import sas.qtgui.Perspectives as Perspectives
42from sas.qtgui.Perspectives.Fitting.FittingPerspective import FittingWindow
43from sas.qtgui.MainWindow.DataExplorer import DataExplorerWindow, DEFAULT_PERSPECTIVE
44
45from sas.qtgui.Utilities.AddMultEditor import AddMultEditor
46
47class Acknowledgements(QDialog, Ui_Acknowledgements):
48    def __init__(self, parent=None):
49        QDialog.__init__(self, parent)
50        self.setupUi(self)
51
52class GuiManager(object):
53    """
54    Main SasView window functionality
55    """
56    def __init__(self, parent=None):
57        """
58        Initialize the manager as a child of MainWindow.
59        """
60        self._workspace = parent
61        self._parent = parent
62
63        # Decide on a locale
64        QLocale.setDefault(QLocale('en_US'))
65
66        # Add signal callbacks
67        self.addCallbacks()
68
69        # Assure model categories are available
70        self.addCategories()
71
72        # Create the data manager
73        # TODO: pull out all required methods from DataManager and reimplement
74        self._data_manager = DataManager()
75
76        # Create action triggers
77        self.addTriggers()
78
79        # Currently displayed perspective
80        self._current_perspective = None
81
82        # Populate the main window with stuff
83        self.addWidgets()
84
85        # Fork off logging messages to the Log Window
86        XStream.stdout().messageWritten.connect(self.listWidget.insertPlainText)
87        XStream.stderr().messageWritten.connect(self.listWidget.insertPlainText)
88
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)
93
94        # Set up the status bar
95        self.statusBarSetup()
96
97        # Needs URL like path, so no path.join() here
98        self._helpLocation = GuiUtils.HELP_DIRECTORY_LOCATION + "/index.html"
99
100        # Current tutorial location
101        self._tutorialLocation = os.path.abspath(os.path.join(GuiUtils.HELP_DIRECTORY_LOCATION,
102                                              "_downloads",
103                                              "Tutorial.pdf"))
104
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
113        self.filesWidget = DataExplorerWindow(self._parent, self, manager=self._data_manager)
114        ObjectLibrary.addObject('DataExplorer', self.filesWidget)
115
116        self.dockedFilesWidget = QDockWidget("Data Explorer", self._workspace)
117        self.dockedFilesWidget.setFloating(False)
118        self.dockedFilesWidget.setWidget(self.filesWidget)
119
120        # Disable maximize/minimize and close buttons
121        self.dockedFilesWidget.setFeatures(QDockWidget.NoDockWidgetFeatures)
122
123        #self._workspace.workspace.addDockWidget(Qt.LeftDockWidgetArea, self.dockedFilesWidget)
124        self._workspace.addDockWidget(Qt.LeftDockWidgetArea, self.dockedFilesWidget)
125
126        # Add the console window as another docked widget
127        self.logDockWidget = QDockWidget("Log Explorer", self._workspace)
128        self.logDockWidget.setObjectName("LogDockWidget")
129
130        self.listWidget = QTextBrowser()
131        self.logDockWidget.setWidget(self.listWidget)
132        self._workspace.addDockWidget(Qt.BottomDockWidgetArea, self.logDockWidget)
133
134        # Add other, minor widgets
135        self.ackWidget = Acknowledgements()
136        self.aboutWidget = AboutBox()
137        self.welcomePanel = WelcomePanel()
138        self.grid_window = None
139        self._workspace.toolBar.setVisible(LocalConfig.TOOLBAR_SHOW)
140        self._workspace.actionHide_Toolbar.setText("Show Toolbar")
141
142        # Add calculators - floating for usability
143        self.SLDCalculator = SldPanel(self)
144        self.DVCalculator = DensityPanel(self)
145        self.KIESSIGCalculator = KiessigPanel(self)
146        self.SlitSizeCalculator = SlitSizeCalculator(self)
147        self.GENSASCalculator = GenericScatteringCalculator(self)
148        self.ResolutionCalculator = ResolutionCalculatorPanel(self)
149        self.DataOperation = DataOperationUtilityPanel(self)
150
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
164    def statusBarSetup(self):
165        """
166        Define the status bar.
167        | <message label> .... | Progress Bar |
168
169        Progress bar invisible until explicitly shown
170        """
171        self.progress = QProgressBar()
172        self._workspace.statusbar.setSizeGripEnabled(False)
173
174        self.statusLabel = QLabel()
175        self.statusLabel.setText("Welcome to SasView")
176        self._workspace.statusbar.addPermanentWidget(self.statusLabel, 1)
177        self._workspace.statusbar.addPermanentWidget(self.progress, stretch=0)
178        self.progress.setRange(0, 100)
179        self.progress.setValue(0)
180        self.progress.setTextVisible(True)
181        self.progress.setVisible(False)
182
183    def fileWasRead(self, data):
184        """
185        Callback for fileDataReceivedSignal
186        """
187        pass
188
189    def showHelp(self, url):
190        """
191        Open a local url in the default browser
192        """
193        #location = os.path.join(GuiUtils.HELP_DIRECTORY_LOCATION, url)
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
200    def workspace(self):
201        """
202        Accessor for the main window workspace
203        """
204        return self._workspace.workspace
205
206    def perspectiveChanged(self, perspective_name):
207        """
208        Respond to change of the perspective signal
209        """
210        # Close the previous perspective
211        self.clearPerspectiveMenubarOptions(self._current_perspective)
212        if self._current_perspective:
213            self._current_perspective.setClosable()
214            #self._workspace.workspace.removeSubWindow(self._current_perspective)
215            self._current_perspective.close()
216            self._workspace.workspace.removeSubWindow(self._current_perspective)
217        # Default perspective
218        self._current_perspective = Perspectives.PERSPECTIVES[str(perspective_name)](parent=self)
219
220        self.setupPerspectiveMenubarOptions(self._current_perspective)
221
222        subwindow = self._workspace.workspace.addSubWindow(self._current_perspective)
223
224        # Resize to the workspace height
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)
229        # Resize the mdi area to match the widget within
230        subwindow.resize(subwindow.minimumSizeHint())
231
232        self._current_perspective.show()
233
234    def updatePerspective(self, data):
235        """
236        Update perspective with data sent.
237        """
238        assert isinstance(data, list)
239        if self._current_perspective is not None:
240            self._current_perspective.setData(list(data.values()))
241        else:
242            msg = "No perspective is currently active."
243            logging.info(msg)
244
245    def communicator(self):
246        """ Accessor for the communicator """
247        return self.communicate
248
249    def perspective(self):
250        """ Accessor for the perspective """
251        return self._current_perspective
252
253    def updateProgressBar(self, value):
254        """
255        Update progress bar with the required value (0-100)
256        """
257        assert -1 <= value <= 100
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
267    def updateStatusBar(self, text):
268        """
269        Set the status bar text
270        """
271        self.statusLabel.setText(text)
272
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)
278
279    def setData(self, data):
280        """
281        Sends data to current perspective
282        """
283        if self._current_perspective is not None:
284            self._current_perspective.setData(list(data.values()))
285        else:
286            msg = "Guiframe does not have a current perspective"
287            logging.info(msg)
288
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
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?"
301        reply = QMessageBox.question(
302            self._parent,
303            'Information',
304            quit_msg,
305            QMessageBox.Yes,
306            QMessageBox.No)
307
308        # Exit if yes
309        if reply == QMessageBox.Yes:
310            reactor.callFromThread(reactor.stop)
311            return True
312
313        return False
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"}
323        c = ConnectionProxy(LocalConfig.__update_URL__, LocalConfig.UPDATE_TIMEOUT)
324        response = c.connect()
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)
333        except ValueError as ex:
334            logging.info("Failed to connect to www.sasview.org:", ex)
335
336    def processVersion(self, version_info):
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
352            elif version.__gt__(LocalConfig.__version__):
353                msg = "Version %s is available! " % str(version)
354                if "download_url" in version_info:
355                    webbrowser.open(version_info["download_url"])
356                else:
357                    webbrowser.open(LocalConfig.__download_page__)
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"
365            msg += " version number\n  %s" % sys.exc_info()[1]
366            logging.error(msg)
367            msg = "Could not connect to the application server."
368            msg += " Please try again later."
369            self.communicate.statusBarUpdateSignal.emit(msg)
370
371    def showWelcomeMessage(self):
372        """ Show the Welcome panel """
373        self._workspace.workspace.addSubWindow(self.welcomePanel)
374        self.welcomePanel.show()
375
376    def addCallbacks(self):
377        """
378        Method defining all signal connections for the gui manager
379        """
380        self.communicate = GuiUtils.Communicate()
381        self.communicate.fileDataReceivedSignal.connect(self.fileWasRead)
382        self.communicate.statusBarUpdateSignal.connect(self.updateStatusBar)
383        self.communicate.updatePerspectiveWithDataSignal.connect(self.updatePerspective)
384        self.communicate.progressBarUpdateSignal.connect(self.updateProgressBar)
385        self.communicate.perspectiveChangedSignal.connect(self.perspectiveChanged)
386        self.communicate.updateTheoryFromPerspectiveSignal.connect(self.updateTheoryFromPerspective)
387        self.communicate.plotRequestedSignal.connect(self.showPlot)
388        self.communicate.plotFromFilenameSignal.connect(self.showPlotFromFilename)
389        self.communicate.updateModelFromDataOperationPanelSignal.connect(self.updateModelFromDataOperationPanel)
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)
422        self._workspace.actionKeissig_Calculator.triggered.connect(self.actionKiessig_Calculator)
423        #self._workspace.actionKIESSING_Calculator.triggered.connect(self.actionKIESSING_Calculator)
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)
434        self._workspace.actionGPU_Options.triggered.connect(self.actionGPU_Options)
435        self._workspace.actionFit_Results.triggered.connect(self.actionFit_Results)
436        self._workspace.actionAdd_Custom_Model.triggered.connect(self.actionAdd_Custom_Model)
437        self._workspace.actionEdit_Custom_Model.triggered.connect(self.actionEdit_Custom_Model)
438        self._workspace.actionManage_Custom_Models.triggered.connect(self.actionManage_Custom_Models)
439        self._workspace.actionAddMult_Models.triggered.connect(self.actionAddMult_Models)
440        # Window
441        self._workspace.actionCascade.triggered.connect(self.actionCascade)
442        self._workspace.actionTile.triggered.connect(self.actionTile)
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)
450        self._workspace.actionCorfunc.triggered.connect(self.actionCorfunc)
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
458        self.communicate.sendDataToGridSignal.connect(self.showBatchOutput)
459
460    #============ FILE =================
461    def actionLoadData(self):
462        """
463        Menu File/Load Data File(s)
464        """
465        self.filesWidget.loadFile()
466
467    def actionLoad_Data_Folder(self):
468        """
469        Menu File/Load Data Folder
470        """
471        self.filesWidget.loadFolder()
472
473    def actionOpen_Project(self):
474        """
475        Menu Open Project
476        """
477        self.filesWidget.loadProject()
478
479    def actionOpen_Analysis(self):
480        """
481        """
482        print("actionOpen_Analysis TRIGGERED")
483        pass
484
485    def actionSave(self):
486        """
487        Menu Save Project
488        """
489        self.filesWidget.saveProject()
490
491    def actionSave_Analysis(self):
492        """
493        Menu File/Save Analysis
494        """
495        self.communicate.saveAnalysisSignal.emit()
496
497    def actionQuit(self):
498        """
499        Close the reactor, exit the application.
500        """
501        self.quitApplication()
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        """
530        Show the Fit Report dialog.
531        """
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()
542
543    def actionReset(self):
544        """
545        """
546        logging.warning(" *** actionOpen_Analysis logging *******")
547        print("actionReset print TRIGGERED")
548        sys.stderr.write("STDERR - TRIGGERED")
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        """
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)
585
586    def actionHide_Toolbar(self):
587        """
588        Toggle toolbar vsibility
589        """
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)
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        """
614        self.communicate.sendDataToPanelSignal.emit(self._data_manager.get_all_data())
615
616        self.DataOperation.show()
617
618    def actionSLD_Calculator(self):
619        """
620        """
621        self.SLDCalculator.show()
622
623    def actionDensity_Volume_Calculator(self):
624        """
625        """
626        self.DVCalculator.show()
627
628    def actionKiessig_Calculator(self):
629        """
630        """
631        self.KIESSIGCalculator.show()
632
633    def actionSlit_Size_Calculator(self):
634        """
635        """
636        self.SlitSizeCalculator.show()
637
638    def actionSAS_Resolution_Estimator(self):
639        """
640        """
641        try:
642            self.ResolutionCalculator.show()
643        except Exception as ex:
644            logging.error(str(ex))
645            return
646
647    def actionGeneric_Scattering_Calculator(self):
648        """
649        """
650        try:
651            self.GENSASCalculator.show()
652        except Exception as ex:
653            logging.error(str(ex))
654            return
655
656    def actionPython_Shell_Editor(self):
657        """
658        Display the Jupyter console as a docked widget.
659        """
660        # Import moved here for startup performance reasons
661        from sas.qtgui.Utilities.IPythonWidget import IPythonWidget
662        terminal = IPythonWidget()
663
664        # Add the console window as another docked widget
665        self.ipDockWidget = QDockWidget("IPython", self._workspace)
666        self.ipDockWidget.setObjectName("IPythonDockWidget")
667        self.ipDockWidget.setWidget(terminal)
668        self._workspace.addDockWidget(Qt.RightDockWidgetArea, self.ipDockWidget)
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        """
679        Add a new, empty Fit page in the fitting perspective.
680        """
681        # Make sure the perspective is correct
682        per = self.perspective()
683        if not isinstance(per, FittingWindow):
684            return
685        per.addFit(None)
686
687    def actionConstrained_Fit(self):
688        """
689        Add a new Constrained and Simult. Fit page in the fitting perspective.
690        """
691        per = self.perspective()
692        if not isinstance(per, FittingWindow):
693            return
694        per.addConstraintTab()
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        """
705        if getattr(self._current_perspective, "fit_options_widget"):
706            self._current_perspective.fit_options_widget.show()
707        pass
708
709    def actionGPU_Options(self):
710        """
711        Load the OpenCL selection dialog if the fitting perspective is active
712        """
713        if hasattr(self._current_perspective, "gpu_options_widget"):
714            self._current_perspective.gpu_options_widget.show()
715        pass
716
717    def actionFit_Results(self):
718        """
719        """
720        print("actionFit_Results TRIGGERED")
721        pass
722
723    def actionAdd_Custom_Model(self):
724        """
725        """
726        self.model_editor = TabbedModelEditor(self)
727        self.model_editor.show()
728
729    def actionEdit_Custom_Model(self):
730        """
731        """
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()
740
741    def actionAddMult_Models(self):
742        """
743        """
744        # Add Simple Add/Multiply Editor
745        self.add_mult_editor = AddMultEditor(self)
746        self.add_mult_editor.show()
747
748    #============ ANALYSIS =================
749    def actionFitting(self):
750        """
751        Change to the Fitting perspective
752        """
753        self.perspectiveChanged("Fitting")
754        # Notify other widgets
755        self.filesWidget.onAnalysisUpdate("Fitting")
756
757    def actionInversion(self):
758        """
759        Change to the Inversion perspective
760        """
761        self.perspectiveChanged("Inversion")
762        self.filesWidget.onAnalysisUpdate("Inversion")
763
764    def actionInvariant(self):
765        """
766        Change to the Invariant perspective
767        """
768        self.perspectiveChanged("Invariant")
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")
777
778    #============ WINDOW =================
779    def actionCascade(self):
780        """
781        Arranges all the child windows in a cascade pattern.
782        """
783        self._workspace.workspace.cascade()
784
785    def actionTile(self):
786        """
787        Tile workspace windows
788        """
789        self._workspace.workspace.tile()
790
791    def actionArrange_Icons(self):
792        """
793        Arranges all iconified windows at the bottom of the workspace
794        """
795        self._workspace.workspace.arrangeIcons()
796
797    def actionNext(self):
798        """
799        Gives the input focus to the next window in the list of child windows.
800        """
801        self._workspace.workspace.activateNextWindow()
802
803    def actionPrevious(self):
804        """
805        Gives the input focus to the previous window in the list of child windows.
806        """
807        self._workspace.workspace.activatePreviousWindow()
808
809    #============ HELP =================
810    def actionDocumentation(self):
811        """
812        Display the documentation
813
814        TODO: use QNetworkAccessManager to assure _helpLocation is valid
815        """
816        helpfile = "index.html"
817        self.showHelp(helpfile)
818
819    def actionTutorial(self):
820        """
821        Open the tutorial PDF file with default PDF renderer
822        """
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)
827
828    def actionAcknowledge(self):
829        """
830        Open the Acknowledgements widget
831        """
832        self.ackWidget.show()
833
834    def actionAbout(self):
835        """
836        Open the About box
837        """
838        # Update the about box with current version and stuff
839
840        # TODO: proper sizing
841        self.aboutWidget.show()
842
843    def actionCheck_for_update(self):
844        """
845        Menu Help/Check for Update
846        """
847        self.checkUpdate()
848
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
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        """
861        if not isinstance(new_item, QStandardItem) or \
862                not isinstance(new_datalist_item, dict):
863            msg = "Wrong data type returned from calculations."
864            raise AttributeError(msg)
865
866        self.filesWidget.model.appendRow(new_item)
867        self._data_manager.add_data(new_datalist_item)
868
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
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)
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)
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.