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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since e258c53 was e258c53, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

+1 to user experience. Exceptions don't crash SV - the just show in the
log explorer (and console, if any).

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