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

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

Copy/paste fitting parameters SASVIEW-933

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