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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since dee9e5f was dee9e5f, checked in by wojciech, 23 months ago

Moved reported results handling to GuiManager?

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