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

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

reduce initial width of Data Explorer dock

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