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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 9b9ec10 was ee22241, checked in by rozyczko <piotr.rozyczko@…>, 3 years ago

Refactored onHelp a bit to allow more encapsulation. SASVIEW-1112

  • Property mode set to 100644
File size: 36.8 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        GuiUtils.showHelp(url)
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
236        self._current_perspective.show()
237
238    def updatePerspective(self, data):
239        """
240        Update perspective with data sent.
241        """
242        assert isinstance(data, list)
243        if self._current_perspective is not None:
244            self._current_perspective.setData(list(data.values()))
245        else:
246            msg = "No perspective is currently active."
247            logging.info(msg)
248
249    def communicator(self):
250        """ Accessor for the communicator """
251        return self.communicate
252
253    def perspective(self):
254        """ Accessor for the perspective """
255        return self._current_perspective
256
257    def updateProgressBar(self, value):
258        """
259        Update progress bar with the required value (0-100)
260        """
261        assert -1 <= value <= 100
262        if value == -1:
263            self.progress.setVisible(False)
264            return
265        if not self.progress.isVisible():
266            self.progress.setTextVisible(True)
267            self.progress.setVisible(True)
268
269        self.progress.setValue(value)
270
271    def updateStatusBar(self, text):
272        """
273        Set the status bar text
274        """
275        self.statusLabel.setText(text)
276
277    def appendLog(self, msg):
278        """Appends a message to the list widget in the Log Explorer. Use this
279        instead of listWidget.insertPlainText() to facilitate auto-scrolling"""
280        self.listWidget.append(msg.strip())
281
282    def createGuiData(self, item, p_file=None):
283        """
284        Access the Data1D -> plottable Data1D conversion
285        """
286        return self._data_manager.create_gui_data(item, p_file)
287
288    def setData(self, data):
289        """
290        Sends data to current perspective
291        """
292        if self._current_perspective is not None:
293            self._current_perspective.setData(list(data.values()))
294        else:
295            msg = "Guiframe does not have a current perspective"
296            logging.info(msg)
297
298    def findItemFromFilename(self, filename):
299        """
300        Queries the data explorer for the index corresponding to the filename within
301        """
302        return self.filesWidget.itemFromFilename(filename)
303
304    def quitApplication(self):
305        """
306        Close the reactor and exit nicely.
307        """
308        # Display confirmation messagebox
309        quit_msg = "Are you sure you want to exit the application?"
310        reply = QMessageBox.question(
311            self._parent,
312            'Information',
313            quit_msg,
314            QMessageBox.Yes,
315            QMessageBox.No)
316
317        # Exit if yes
318        if reply == QMessageBox.Yes:
319            reactor.callFromThread(reactor.stop)
320            return True
321
322        return False
323
324    def checkUpdate(self):
325        """
326        Check with the deployment server whether a new version
327        of the application is available.
328        A thread is started for the connecting with the server. The thread calls
329        a call-back method when the current version number has been obtained.
330        """
331        version_info = {"version": "0.0.0"}
332        c = ConnectionProxy(LocalConfig.__update_URL__, LocalConfig.UPDATE_TIMEOUT)
333        response = c.connect()
334        if response is None:
335            return
336        try:
337            content = response.read().strip()
338            logging.info("Connected to www.sasview.org. Latest version: %s"
339                            % (content))
340            version_info = json.loads(content)
341            self.processVersion(version_info)
342        except ValueError as ex:
343            logging.info("Failed to connect to www.sasview.org:", ex)
344
345    def processVersion(self, version_info):
346        """
347        Call-back method for the process of checking for updates.
348        This methods is called by a VersionThread object once the current
349        version number has been obtained. If the check is being done in the
350        background, the user will not be notified unless there's an update.
351
352        :param version: version string
353        """
354        try:
355            version = version_info["version"]
356            if version == "0.0.0":
357                msg = "Could not connect to the application server."
358                msg += " Please try again later."
359                self.communicate.statusBarUpdateSignal.emit(msg)
360
361            elif version.__gt__(LocalConfig.__version__):
362                msg = "Version %s is available! " % str(version)
363                if "download_url" in version_info:
364                    webbrowser.open(version_info["download_url"])
365                else:
366                    webbrowser.open(LocalConfig.__download_page__)
367                self.communicate.statusBarUpdateSignal.emit(msg)
368            else:
369                msg = "You have the latest version"
370                msg += " of %s" % str(LocalConfig.__appname__)
371                self.communicate.statusBarUpdateSignal.emit(msg)
372        except:
373            msg = "guiframe: could not get latest application"
374            msg += " version number\n  %s" % sys.exc_info()[1]
375            logging.error(msg)
376            msg = "Could not connect to the application server."
377            msg += " Please try again later."
378            self.communicate.statusBarUpdateSignal.emit(msg)
379
380    def actionWelcome(self):
381        """ Show the Welcome panel """
382        self.welcomePanel = WelcomePanel()
383        self._workspace.workspace.addSubWindow(self.welcomePanel)
384        self.welcomePanel.show()
385
386    def showWelcomeMessage(self):
387        """ Show the Welcome panel, when required """
388        # Assure the welcome screen is requested
389        show_welcome_widget = True
390        custom_config = get_custom_config()
391        if hasattr(custom_config, "WELCOME_PANEL_SHOW"):
392            if isinstance(custom_config.WELCOME_PANEL_SHOW, bool):
393                show_welcome_widget = custom_config.WELCOME_PANEL_SHOW
394            else:
395                logging.warning("WELCOME_PANEL_SHOW has invalid value in custom_config.py")
396        if show_welcome_widget:
397            self.actionWelcome()
398
399    def addCallbacks(self):
400        """
401        Method defining all signal connections for the gui manager
402        """
403        self.communicate = GuiUtils.Communicate()
404        self.communicate.fileDataReceivedSignal.connect(self.fileWasRead)
405        self.communicate.statusBarUpdateSignal.connect(self.updateStatusBar)
406        self.communicate.updatePerspectiveWithDataSignal.connect(self.updatePerspective)
407        self.communicate.progressBarUpdateSignal.connect(self.updateProgressBar)
408        self.communicate.perspectiveChangedSignal.connect(self.perspectiveChanged)
409        self.communicate.updateTheoryFromPerspectiveSignal.connect(self.updateTheoryFromPerspective)
410        self.communicate.deleteIntermediateTheoryPlotsSignal.connect(self.deleteIntermediateTheoryPlotsByModelID)
411        self.communicate.plotRequestedSignal.connect(self.showPlot)
412        self.communicate.plotFromFilenameSignal.connect(self.showPlotFromFilename)
413        self.communicate.updateModelFromDataOperationPanelSignal.connect(self.updateModelFromDataOperationPanel)
414
415    def addTriggers(self):
416        """
417        Trigger definitions for all menu/toolbar actions.
418        """
419        # disable not yet fully implemented actions
420        self._workspace.actionOpen_Analysis.setEnabled(False)
421        self._workspace.actionUndo.setEnabled(False)
422        self._workspace.actionRedo.setEnabled(False)
423        self._workspace.actionReset.setEnabled(False)
424        self._workspace.actionStartup_Settings.setEnabled(False)
425        self._workspace.actionImage_Viewer.setEnabled(False)
426        self._workspace.actionCombine_Batch_Fit.setEnabled(False)
427        self._workspace.actionFit_Results.setEnabled(False)
428
429        # File
430        self._workspace.actionLoadData.triggered.connect(self.actionLoadData)
431        self._workspace.actionLoad_Data_Folder.triggered.connect(self.actionLoad_Data_Folder)
432        self._workspace.actionOpen_Project.triggered.connect(self.actionOpen_Project)
433        self._workspace.actionOpen_Analysis.triggered.connect(self.actionOpen_Analysis)
434        self._workspace.actionSave.triggered.connect(self.actionSave)
435        self._workspace.actionSave_Analysis.triggered.connect(self.actionSave_Analysis)
436        self._workspace.actionQuit.triggered.connect(self.actionQuit)
437        # Edit
438        self._workspace.actionUndo.triggered.connect(self.actionUndo)
439        self._workspace.actionRedo.triggered.connect(self.actionRedo)
440        self._workspace.actionCopy.triggered.connect(self.actionCopy)
441        self._workspace.actionPaste.triggered.connect(self.actionPaste)
442        self._workspace.actionReport.triggered.connect(self.actionReport)
443        self._workspace.actionReset.triggered.connect(self.actionReset)
444        self._workspace.actionExcel.triggered.connect(self.actionExcel)
445        self._workspace.actionLatex.triggered.connect(self.actionLatex)
446        # View
447        self._workspace.actionShow_Grid_Window.triggered.connect(self.actionShow_Grid_Window)
448        self._workspace.actionHide_Toolbar.triggered.connect(self.actionHide_Toolbar)
449        self._workspace.actionStartup_Settings.triggered.connect(self.actionStartup_Settings)
450        self._workspace.actionCategory_Manager.triggered.connect(self.actionCategory_Manager)
451        self._workspace.actionHide_DataExplorer.triggered.connect(self.actionHide_DataExplorer)
452        # Tools
453        self._workspace.actionData_Operation.triggered.connect(self.actionData_Operation)
454        self._workspace.actionSLD_Calculator.triggered.connect(self.actionSLD_Calculator)
455        self._workspace.actionDensity_Volume_Calculator.triggered.connect(self.actionDensity_Volume_Calculator)
456        self._workspace.actionKeissig_Calculator.triggered.connect(self.actionKiessig_Calculator)
457        #self._workspace.actionKIESSING_Calculator.triggered.connect(self.actionKIESSING_Calculator)
458        self._workspace.actionSlit_Size_Calculator.triggered.connect(self.actionSlit_Size_Calculator)
459        self._workspace.actionSAS_Resolution_Estimator.triggered.connect(self.actionSAS_Resolution_Estimator)
460        self._workspace.actionGeneric_Scattering_Calculator.triggered.connect(self.actionGeneric_Scattering_Calculator)
461        self._workspace.actionPython_Shell_Editor.triggered.connect(self.actionPython_Shell_Editor)
462        self._workspace.actionImage_Viewer.triggered.connect(self.actionImage_Viewer)
463        self._workspace.actionOrientation_Viewer.triggered.connect(self.actionOrientation_Viewer)
464        self._workspace.actionFreeze_Theory.triggered.connect(self.actionFreeze_Theory)
465        # Fitting
466        self._workspace.actionNew_Fit_Page.triggered.connect(self.actionNew_Fit_Page)
467        self._workspace.actionConstrained_Fit.triggered.connect(self.actionConstrained_Fit)
468        self._workspace.actionCombine_Batch_Fit.triggered.connect(self.actionCombine_Batch_Fit)
469        self._workspace.actionFit_Options.triggered.connect(self.actionFit_Options)
470        self._workspace.actionGPU_Options.triggered.connect(self.actionGPU_Options)
471        self._workspace.actionFit_Results.triggered.connect(self.actionFit_Results)
472        self._workspace.actionAdd_Custom_Model.triggered.connect(self.actionAdd_Custom_Model)
473        self._workspace.actionEdit_Custom_Model.triggered.connect(self.actionEdit_Custom_Model)
474        self._workspace.actionManage_Custom_Models.triggered.connect(self.actionManage_Custom_Models)
475        self._workspace.actionAddMult_Models.triggered.connect(self.actionAddMult_Models)
476        self._workspace.actionEditMask.triggered.connect(self.actionEditMask)
477
478        # Window
479        self._workspace.actionCascade.triggered.connect(self.actionCascade)
480        self._workspace.actionTile.triggered.connect(self.actionTile)
481        self._workspace.actionArrange_Icons.triggered.connect(self.actionArrange_Icons)
482        self._workspace.actionNext.triggered.connect(self.actionNext)
483        self._workspace.actionPrevious.triggered.connect(self.actionPrevious)
484        # Analysis
485        self._workspace.actionFitting.triggered.connect(self.actionFitting)
486        self._workspace.actionInversion.triggered.connect(self.actionInversion)
487        self._workspace.actionInvariant.triggered.connect(self.actionInvariant)
488        self._workspace.actionCorfunc.triggered.connect(self.actionCorfunc)
489        # Help
490        self._workspace.actionDocumentation.triggered.connect(self.actionDocumentation)
491        self._workspace.actionTutorial.triggered.connect(self.actionTutorial)
492        self._workspace.actionAcknowledge.triggered.connect(self.actionAcknowledge)
493        self._workspace.actionAbout.triggered.connect(self.actionAbout)
494        self._workspace.actionWelcomeWidget.triggered.connect(self.actionWelcome)
495        self._workspace.actionCheck_for_update.triggered.connect(self.actionCheck_for_update)
496
497        self.communicate.sendDataToGridSignal.connect(self.showBatchOutput)
498
499    #============ FILE =================
500    def actionLoadData(self):
501        """
502        Menu File/Load Data File(s)
503        """
504        self.filesWidget.loadFile()
505
506    def actionLoad_Data_Folder(self):
507        """
508        Menu File/Load Data Folder
509        """
510        self.filesWidget.loadFolder()
511
512    def actionOpen_Project(self):
513        """
514        Menu Open Project
515        """
516        self.filesWidget.loadProject()
517
518    def actionOpen_Analysis(self):
519        """
520        """
521        print("actionOpen_Analysis TRIGGERED")
522        pass
523
524    def actionSave(self):
525        """
526        Menu Save Project
527        """
528        self.filesWidget.saveProject()
529
530    def actionSave_Analysis(self):
531        """
532        Menu File/Save Analysis
533        """
534        self.communicate.saveAnalysisSignal.emit()
535
536    def actionQuit(self):
537        """
538        Close the reactor, exit the application.
539        """
540        self.quitApplication()
541
542    #============ EDIT =================
543    def actionUndo(self):
544        """
545        """
546        print("actionUndo TRIGGERED")
547        pass
548
549    def actionRedo(self):
550        """
551        """
552        print("actionRedo TRIGGERED")
553        pass
554
555    def actionCopy(self):
556        """
557        Send a signal to the fitting perspective so parameters
558        can be saved to the clipboard
559        """
560        self.communicate.copyFitParamsSignal.emit("")
561        self._workspace.actionPaste.setEnabled(True)
562        pass
563
564    def actionPaste(self):
565        """
566        Send a signal to the fitting perspective so parameters
567        from the clipboard can be used to modify the fit state
568        """
569        self.communicate.pasteFitParamsSignal.emit()
570
571    def actionReport(self):
572        """
573        Show the Fit Report dialog.
574        """
575        report_list = None
576        if getattr(self._current_perspective, "currentTab"):
577            try:
578                report_list = self._current_perspective.currentTab.getReport()
579            except Exception as ex:
580                logging.error("Report generation failed with: " + str(ex))
581
582        if report_list is not None:
583            self.report_dialog = ReportDialog(parent=self, report_list=report_list)
584            self.report_dialog.show()
585
586    def actionReset(self):
587        """
588        """
589        logging.warning(" *** actionOpen_Analysis logging *******")
590        print("actionReset print TRIGGERED")
591        sys.stderr.write("STDERR - TRIGGERED")
592        pass
593
594    def actionExcel(self):
595        """
596        Send a signal to the fitting perspective so parameters
597        can be saved to the clipboard
598        """
599        self.communicate.copyFitParamsSignal.emit("Excel")
600
601    def actionLatex(self):
602        """
603        Send a signal to the fitting perspective so parameters
604        can be saved to the clipboard
605        """
606        self.communicate.copyFitParamsSignal.emit("Latex")
607
608    #============ VIEW =================
609    def actionShow_Grid_Window(self):
610        """
611        """
612        self.showBatchOutput(None)
613
614    def showBatchOutput(self, output_data):
615        """
616        Display/redisplay the batch fit viewer
617        """
618        if self.grid_window is None:
619            self.grid_window = BatchOutputPanel(parent=self, output_data=output_data)
620            subwindow = self._workspace.workspace.addSubWindow(self.grid_window)
621
622            #self.grid_window = BatchOutputPanel(parent=self, output_data=output_data)
623            self.grid_window.show()
624            return
625        if output_data:
626            self.grid_window.addFitResults(output_data)
627        self.grid_window.show()
628        if self.grid_window.windowState() == Qt.WindowMinimized:
629            self.grid_window.setWindowState(Qt.WindowActive)
630
631    def actionHide_Toolbar(self):
632        """
633        Toggle toolbar vsibility
634        """
635        if self._workspace.toolBar.isVisible():
636            self._workspace.actionHide_Toolbar.setText("Show Toolbar")
637            self._workspace.toolBar.setVisible(False)
638        else:
639            self._workspace.actionHide_Toolbar.setText("Hide Toolbar")
640            self._workspace.toolBar.setVisible(True)
641        pass
642
643    def actionHide_DataExplorer(self):
644        """
645        Toggle Data Explorer vsibility
646        """
647        if self.dockedFilesWidget.isVisible():
648            #self._workspace.actionHide_DataExplorer.setText("Show Data Explorer")
649            self.dockedFilesWidget.setVisible(False)
650        else:
651            #self._workspace.actionHide_DataExplorer.setText("Hide Data Explorer")
652            self.dockedFilesWidget.setVisible(True)
653        pass
654
655    def actionStartup_Settings(self):
656        """
657        """
658        print("actionStartup_Settings TRIGGERED")
659        pass
660
661    def actionCategory_Manager(self):
662        """
663        """
664        self.categoryManagerWidget.show()
665
666    #============ TOOLS =================
667    def actionData_Operation(self):
668        """
669        """
670        self.communicate.sendDataToPanelSignal.emit(self._data_manager.get_all_data())
671
672        self.DataOperation.show()
673
674    def actionSLD_Calculator(self):
675        """
676        """
677        self.SLDCalculator.show()
678
679    def actionDensity_Volume_Calculator(self):
680        """
681        """
682        self.DVCalculator.show()
683
684    def actionKiessig_Calculator(self):
685        """
686        """
687        self.KIESSIGCalculator.show()
688
689    def actionSlit_Size_Calculator(self):
690        """
691        """
692        self.SlitSizeCalculator.show()
693
694    def actionSAS_Resolution_Estimator(self):
695        """
696        """
697        try:
698            self.ResolutionCalculator.show()
699        except Exception as ex:
700            logging.error(str(ex))
701            return
702
703    def actionGeneric_Scattering_Calculator(self):
704        """
705        """
706        try:
707            self.GENSASCalculator.show()
708        except Exception as ex:
709            logging.error(str(ex))
710            return
711
712    def actionPython_Shell_Editor(self):
713        """
714        Display the Jupyter console as a docked widget.
715        """
716        # Import moved here for startup performance reasons
717        from sas.qtgui.Utilities.IPythonWidget import IPythonWidget
718        terminal = IPythonWidget()
719
720        # Add the console window as another docked widget
721        self.ipDockWidget = QDockWidget("IPython", self._workspace)
722        self.ipDockWidget.setObjectName("IPythonDockWidget")
723        self.ipDockWidget.setWidget(terminal)
724        self._workspace.addDockWidget(Qt.RightDockWidgetArea, self.ipDockWidget)
725
726    def actionFreeze_Theory(self):
727        """
728        Convert a child index with data into a separate top level dataset
729        """
730        self.filesWidget.freezeCheckedData()
731
732    def actionOrientation_Viewer(self):
733        """
734        Make sasmodels orientation & jitter viewer available
735        """
736        from sasmodels.jitter import run as orientation_run
737        try:
738            orientation_run()
739        except Exception as ex:
740            logging.error(str(ex))
741
742    def actionImage_Viewer(self):
743        """
744        """
745        print("actionImage_Viewer TRIGGERED")
746        pass
747
748    #============ FITTING =================
749    def actionNew_Fit_Page(self):
750        """
751        Add a new, empty Fit page in the fitting perspective.
752        """
753        # Make sure the perspective is correct
754        per = self.perspective()
755        if not isinstance(per, FittingWindow):
756            return
757        per.addFit(None)
758
759    def actionConstrained_Fit(self):
760        """
761        Add a new Constrained and Simult. Fit page in the fitting perspective.
762        """
763        per = self.perspective()
764        if not isinstance(per, FittingWindow):
765            return
766        per.addConstraintTab()
767
768    def actionCombine_Batch_Fit(self):
769        """
770        """
771        print("actionCombine_Batch_Fit TRIGGERED")
772        pass
773
774    def actionFit_Options(self):
775        """
776        """
777        if getattr(self._current_perspective, "fit_options_widget"):
778            self._current_perspective.fit_options_widget.show()
779        pass
780
781    def actionGPU_Options(self):
782        """
783        Load the OpenCL selection dialog if the fitting perspective is active
784        """
785        if hasattr(self._current_perspective, "gpu_options_widget"):
786            self._current_perspective.gpu_options_widget.show()
787        pass
788
789    def actionFit_Results(self):
790        """
791        """
792        print("actionFit_Results TRIGGERED")
793        pass
794
795    def actionAdd_Custom_Model(self):
796        """
797        """
798        self.model_editor = TabbedModelEditor(self)
799        self.model_editor.show()
800
801    def actionEdit_Custom_Model(self):
802        """
803        """
804        self.model_editor = TabbedModelEditor(self, edit_only=True)
805        self.model_editor.show()
806
807    def actionManage_Custom_Models(self):
808        """
809        """
810        self.model_manager = PluginManager(self)
811        self.model_manager.show()
812
813    def actionAddMult_Models(self):
814        """
815        """
816        # Add Simple Add/Multiply Editor
817        self.add_mult_editor = AddMultEditor(self)
818        self.add_mult_editor.show()
819
820    def actionEditMask(self):
821
822        self.communicate.extMaskEditorSignal.emit()
823
824    #============ ANALYSIS =================
825    def actionFitting(self):
826        """
827        Change to the Fitting perspective
828        """
829        self.perspectiveChanged("Fitting")
830        # Notify other widgets
831        self.filesWidget.onAnalysisUpdate("Fitting")
832
833    def actionInversion(self):
834        """
835        Change to the Inversion perspective
836        """
837        self.perspectiveChanged("Inversion")
838        self.filesWidget.onAnalysisUpdate("Inversion")
839
840    def actionInvariant(self):
841        """
842        Change to the Invariant perspective
843        """
844        self.perspectiveChanged("Invariant")
845        self.filesWidget.onAnalysisUpdate("Invariant")
846
847    def actionCorfunc(self):
848        """
849        Change to the Corfunc perspective
850        """
851        self.perspectiveChanged("Corfunc")
852        self.filesWidget.onAnalysisUpdate("Corfunc")
853
854    #============ WINDOW =================
855    def actionCascade(self):
856        """
857        Arranges all the child windows in a cascade pattern.
858        """
859        self._workspace.workspace.cascadeSubWindows()
860
861    def actionTile(self):
862        """
863        Tile workspace windows
864        """
865        self._workspace.workspace.tileSubWindows()
866
867    def actionArrange_Icons(self):
868        """
869        Arranges all iconified windows at the bottom of the workspace
870        """
871        self._workspace.workspace.arrangeIcons()
872
873    def actionNext(self):
874        """
875        Gives the input focus to the next window in the list of child windows.
876        """
877        self._workspace.workspace.activateNextSubWindow()
878
879    def actionPrevious(self):
880        """
881        Gives the input focus to the previous window in the list of child windows.
882        """
883        self._workspace.workspace.activatePreviousSubWindow()
884
885    #============ HELP =================
886    def actionDocumentation(self):
887        """
888        Display the documentation
889
890        TODO: use QNetworkAccessManager to assure _helpLocation is valid
891        """
892        helpfile = "/index.html"
893        self.showHelp(helpfile)
894
895    def actionTutorial(self):
896        """
897        Open the tutorial PDF file with default PDF renderer
898        """
899        # Not terribly safe here. Shell injection warning.
900        # isfile() helps but this probably needs a better solution.
901        if os.path.isfile(self._tutorialLocation):
902            result = subprocess.Popen([self._tutorialLocation], shell=True)
903
904    def actionAcknowledge(self):
905        """
906        Open the Acknowledgements widget
907        """
908        self.ackWidget.show()
909
910    def actionAbout(self):
911        """
912        Open the About box
913        """
914        # Update the about box with current version and stuff
915
916        # TODO: proper sizing
917        self.aboutWidget.show()
918
919    def actionCheck_for_update(self):
920        """
921        Menu Help/Check for Update
922        """
923        self.checkUpdate()
924
925    def updateTheoryFromPerspective(self, index):
926        """
927        Catch the theory update signal from a perspective
928        Send the request to the DataExplorer for updating the theory model.
929        """
930        self.filesWidget.updateTheoryFromPerspective(index)
931
932    def deleteIntermediateTheoryPlotsByModelID(self, model_id):
933        """
934        Catch the signal to delete items in the Theory item model which correspond to a model ID.
935        Send the request to the DataExplorer for updating the theory model.
936        """
937        self.filesWidget.deleteIntermediateTheoryPlotsByModelID(model_id)
938
939    def updateModelFromDataOperationPanel(self, new_item, new_datalist_item):
940        """
941        :param new_item: item to be added to list of loaded files
942        :param new_datalist_item:
943        """
944        if not isinstance(new_item, QStandardItem) or \
945                not isinstance(new_datalist_item, dict):
946            msg = "Wrong data type returned from calculations."
947            raise AttributeError(msg)
948
949        self.filesWidget.model.appendRow(new_item)
950        self._data_manager.add_data(new_datalist_item)
951
952    def showPlotFromFilename(self, filename):
953        """
954        Pass the show plot request to the data explorer
955        """
956        if hasattr(self, "filesWidget"):
957            self.filesWidget.displayFile(filename=filename, is_data=True)
958
959    def showPlot(self, plot, id):
960        """
961        Pass the show plot request to the data explorer
962        """
963        if hasattr(self, "filesWidget"):
964            self.filesWidget.displayData(plot, id)
965
966    def uncheckAllMenuItems(self, menuObject):
967        """
968        Uncheck all options in a given menu
969        """
970        menuObjects = menuObject.actions()
971
972        for menuItem in menuObjects:
973            menuItem.setChecked(False)
974
975    def checkAnalysisOption(self, analysisMenuOption):
976        """
977        Unchecks all the items in the analysis menu and checks the item passed
978        """
979        self.uncheckAllMenuItems(self._workspace.menuAnalysis)
980        analysisMenuOption.setChecked(True)
981
982    def clearPerspectiveMenubarOptions(self, perspective):
983        """
984        When closing a perspective, clears the menu bar
985        """
986        for menuItem in self._workspace.menuAnalysis.actions():
987            menuItem.setChecked(False)
988
989        if isinstance(self._current_perspective, Perspectives.PERSPECTIVES["Fitting"]):
990            self._workspace.menubar.removeAction(self._workspace.menuFitting.menuAction())
991
992    def setupPerspectiveMenubarOptions(self, perspective):
993        """
994        When setting a perspective, sets up the menu bar
995        """
996        self._workspace.actionReport.setEnabled(False)
997        if isinstance(perspective, Perspectives.PERSPECTIVES["Fitting"]):
998            self.checkAnalysisOption(self._workspace.actionFitting)
999            # Put the fitting menu back in
1000            # This is a bit involved but it is needed to preserve the menu ordering
1001            self._workspace.menubar.removeAction(self._workspace.menuWindow.menuAction())
1002            self._workspace.menubar.removeAction(self._workspace.menuHelp.menuAction())
1003            self._workspace.menubar.addAction(self._workspace.menuFitting.menuAction())
1004            self._workspace.menubar.addAction(self._workspace.menuWindow.menuAction())
1005            self._workspace.menubar.addAction(self._workspace.menuHelp.menuAction())
1006            self._workspace.actionReport.setEnabled(True)
1007
1008        elif isinstance(perspective, Perspectives.PERSPECTIVES["Invariant"]):
1009            self.checkAnalysisOption(self._workspace.actionInvariant)
1010        elif isinstance(perspective, Perspectives.PERSPECTIVES["Inversion"]):
1011            self.checkAnalysisOption(self._workspace.actionInversion)
1012        elif isinstance(perspective, Perspectives.PERSPECTIVES["Corfunc"]):
1013            self.checkAnalysisOption(self._workspace.actionCorfunc)
Note: See TracBrowser for help on using the repository browser.