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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since dad086f was dad086f, checked in by rozyczko <piotrrozyczko@…>, 6 years ago

Disable not yet fully implemented main menu items

  • Property mode set to 100644
File size: 37.2 KB
Line 
1import sys
2import os
3import subprocess
4import logging
5import json
6import webbrowser
7
8from PyQt5.QtWidgets import *
9from PyQt5.QtGui import *
10from PyQt5.QtCore import Qt, QLocale, QUrl
11
12from twisted.internet import reactor
13# General SAS imports
14from sas import get_local_config, get_custom_config
15from sas.qtgui.Utilities.ConnectionProxy import ConnectionProxy
16from sas.qtgui.Utilities.SasviewLogger import setup_qt_logging
17
18import sas.qtgui.Utilities.LocalConfig as LocalConfig
19import sas.qtgui.Utilities.GuiUtils as GuiUtils
20
21import sas.qtgui.Utilities.ObjectLibrary as ObjectLibrary
22from sas.qtgui.Utilities.TabbedModelEditor import TabbedModelEditor
23from sas.qtgui.Utilities.PluginManager import PluginManager
24from sas.qtgui.Utilities.GridPanel import BatchOutputPanel
25
26from sas.qtgui.Utilities.ReportDialog import ReportDialog
27from sas.qtgui.MainWindow.UI.AcknowledgementsUI import Ui_Acknowledgements
28from sas.qtgui.MainWindow.AboutBox import AboutBox
29from sas.qtgui.MainWindow.WelcomePanel import WelcomePanel
30from sas.qtgui.MainWindow.CategoryManager import CategoryManager
31
32from sas.qtgui.MainWindow.DataManager import DataManager
33
34from sas.qtgui.Calculators.SldPanel import SldPanel
35from sas.qtgui.Calculators.DensityPanel import DensityPanel
36from sas.qtgui.Calculators.KiessigPanel import KiessigPanel
37from sas.qtgui.Calculators.SlitSizeCalculator import SlitSizeCalculator
38from sas.qtgui.Calculators.GenericScatteringCalculator import GenericScatteringCalculator
39from sas.qtgui.Calculators.ResolutionCalculatorPanel import ResolutionCalculatorPanel
40from sas.qtgui.Calculators.DataOperationUtilityPanel import DataOperationUtilityPanel
41
42# Perspectives
43import sas.qtgui.Perspectives as Perspectives
44from sas.qtgui.Perspectives.Fitting.FittingPerspective import FittingWindow
45from sas.qtgui.MainWindow.DataExplorer import DataExplorerWindow, DEFAULT_PERSPECTIVE
46
47from sas.qtgui.Utilities.AddMultEditor import AddMultEditor
48
49logger = logging.getLogger(__name__)
50
51class Acknowledgements(QDialog, Ui_Acknowledgements):
52    def __init__(self, parent=None):
53        QDialog.__init__(self, parent)
54        self.setupUi(self)
55
56class GuiManager(object):
57    """
58    Main SasView window functionality
59    """
60    def __init__(self, parent=None):
61        """
62        Initialize the manager as a child of MainWindow.
63        """
64        self._workspace = parent
65        self._parent = parent
66
67        # Decide on a locale
68        QLocale.setDefault(QLocale('en_US'))
69
70        # Add signal callbacks
71        self.addCallbacks()
72
73        # Assure model categories are available
74        self.addCategories()
75
76        # Create the data manager
77        # TODO: pull out all required methods from DataManager and reimplement
78        self._data_manager = DataManager()
79
80        # Create action triggers
81        self.addTriggers()
82
83        # Currently displayed perspective
84        self._current_perspective = None
85
86        # Populate the main window with stuff
87        self.addWidgets()
88
89        # Fork off logging messages to the Log Window
90        handler = setup_qt_logging()
91        handler.messageWritten.connect(self.appendLog)
92
93        # Log the start of the session
94        logging.info(" --- SasView session started ---")
95        # Log the python version
96        logging.info("Python: %s" % sys.version)
97
98        # Set up the status bar
99        self.statusBarSetup()
100
101        # Current tutorial location
102        self._tutorialLocation = os.path.abspath(os.path.join(GuiUtils.HELP_DIRECTORY_LOCATION,
103                                              "_downloads",
104                                              "Tutorial.pdf"))
105
106    def addWidgets(self):
107        """
108        Populate the main window with widgets
109
110        TODO: overwrite close() on Log and DR widgets so they can be hidden/shown
111        on request
112        """
113        # Add FileDialog widget as docked
114        self.filesWidget = DataExplorerWindow(self._parent, self, manager=self._data_manager)
115        ObjectLibrary.addObject('DataExplorer', self.filesWidget)
116
117        self.dockedFilesWidget = QDockWidget("Data Explorer", self._workspace)
118        self.dockedFilesWidget.setFloating(False)
119        self.dockedFilesWidget.setWidget(self.filesWidget)
120
121        # Modify menu items on widget visibility change
122        self.dockedFilesWidget.visibilityChanged.connect(self.updateContextMenus)
123
124        self._workspace.addDockWidget(Qt.LeftDockWidgetArea, self.dockedFilesWidget)
125        self._workspace.resizeDocks([self.dockedFilesWidget], [305], Qt.Horizontal)
126
127        # Add the console window as another docked widget
128        self.logDockWidget = QDockWidget("Log Explorer", self._workspace)
129        self.logDockWidget.setObjectName("LogDockWidget")
130
131        self.listWidget = QTextBrowser()
132        self.logDockWidget.setWidget(self.listWidget)
133        self._workspace.addDockWidget(Qt.BottomDockWidgetArea, self.logDockWidget)
134
135        # Add other, minor widgets
136        self.ackWidget = Acknowledgements()
137        self.aboutWidget = AboutBox()
138        self.categoryManagerWidget = CategoryManager(self._parent, manager=self)
139        self.grid_window = None
140        self._workspace.toolBar.setVisible(LocalConfig.TOOLBAR_SHOW)
141        self._workspace.actionHide_Toolbar.setText("Show Toolbar")
142
143        # Add calculators - floating for usability
144        self.SLDCalculator = SldPanel(self)
145        self.DVCalculator = DensityPanel(self)
146        self.KIESSIGCalculator = KiessigPanel(self)
147        self.SlitSizeCalculator = SlitSizeCalculator(self)
148        self.GENSASCalculator = GenericScatteringCalculator(self)
149        self.ResolutionCalculator = ResolutionCalculatorPanel(self)
150        self.DataOperation = DataOperationUtilityPanel(self)
151
152    def addCategories(self):
153        """
154        Make sure categories.json exists and if not compile it and install in ~/.sasview
155        """
156        try:
157            from sas.sascalc.fit.models import ModelManager
158            from sas.qtgui.Utilities.CategoryInstaller import CategoryInstaller
159            model_list = ModelManager().cat_model_list()
160            CategoryInstaller.check_install(model_list=model_list)
161        except Exception:
162            import traceback
163            logger.error("%s: could not load SasView models")
164            logger.error(traceback.format_exc())
165
166    def updateContextMenus(self, visible=False):
167        """
168        Modify the View/Data Explorer menu item text on widget visibility
169        """
170        if visible:
171            self._workspace.actionHide_DataExplorer.setText("Hide Data Explorer")
172        else:
173            self._workspace.actionHide_DataExplorer.setText("Show Data Explorer")
174
175    def statusBarSetup(self):
176        """
177        Define the status bar.
178        | <message label> .... | Progress Bar |
179
180        Progress bar invisible until explicitly shown
181        """
182        self.progress = QProgressBar()
183        self._workspace.statusbar.setSizeGripEnabled(False)
184
185        self.statusLabel = QLabel()
186        self.statusLabel.setText("Welcome to SasView")
187        self._workspace.statusbar.addPermanentWidget(self.statusLabel, 1)
188        self._workspace.statusbar.addPermanentWidget(self.progress, stretch=0)
189        self.progress.setRange(0, 100)
190        self.progress.setValue(0)
191        self.progress.setTextVisible(True)
192        self.progress.setVisible(False)
193
194    def fileWasRead(self, data):
195        """
196        Callback for fileDataReceivedSignal
197        """
198        pass
199
200    def showHelp(self, url):
201        """
202        Open a local url in the default browser
203        """
204        location = GuiUtils.HELP_DIRECTORY_LOCATION + url
205        #WP: Added to handle OSX bundle docs
206        if os.path.isdir(location) == False:
207            sas_path = os.path.abspath(os.path.dirname(sys.argv[0]))
208            location = sas_path+"/"+location
209        try:
210            webbrowser.open('file://' + os.path.realpath(location))
211        except webbrowser.Error as ex:
212            logging.warning("Cannot display help. %s" % ex)
213
214    def workspace(self):
215        """
216        Accessor for the main window workspace
217        """
218        return self._workspace.workspace
219
220    def perspectiveChanged(self, perspective_name):
221        """
222        Respond to change of the perspective signal
223        """
224        # Close the previous perspective
225        self.clearPerspectiveMenubarOptions(self._current_perspective)
226        if self._current_perspective:
227            self._current_perspective.setClosable()
228            #self._workspace.workspace.removeSubWindow(self._current_perspective)
229            self._current_perspective.close()
230            self._workspace.workspace.removeSubWindow(self._current_perspective)
231        # Default perspective
232        self._current_perspective = Perspectives.PERSPECTIVES[str(perspective_name)](parent=self)
233
234        self.setupPerspectiveMenubarOptions(self._current_perspective)
235
236        subwindow = self._workspace.workspace.addSubWindow(self._current_perspective)
237
238        # Resize to the workspace height
239        workspace_height = self._workspace.workspace.sizeHint().height()
240        perspective_size = self._current_perspective.sizeHint()
241        perspective_width = perspective_size.width()
242        self._current_perspective.resize(perspective_width, workspace_height-10)
243
244        self._current_perspective.show()
245
246    def updatePerspective(self, data):
247        """
248        Update perspective with data sent.
249        """
250        assert isinstance(data, list)
251        if self._current_perspective is not None:
252            self._current_perspective.setData(list(data.values()))
253        else:
254            msg = "No perspective is currently active."
255            logging.info(msg)
256
257    def communicator(self):
258        """ Accessor for the communicator """
259        return self.communicate
260
261    def perspective(self):
262        """ Accessor for the perspective """
263        return self._current_perspective
264
265    def updateProgressBar(self, value):
266        """
267        Update progress bar with the required value (0-100)
268        """
269        assert -1 <= value <= 100
270        if value == -1:
271            self.progress.setVisible(False)
272            return
273        if not self.progress.isVisible():
274            self.progress.setTextVisible(True)
275            self.progress.setVisible(True)
276
277        self.progress.setValue(value)
278
279    def updateStatusBar(self, text):
280        """
281        Set the status bar text
282        """
283        self.statusLabel.setText(text)
284
285    def appendLog(self, msg):
286        """Appends a message to the list widget in the Log Explorer. Use this
287        instead of listWidget.insertPlainText() to facilitate auto-scrolling"""
288        self.listWidget.append(msg.strip())
289
290    def createGuiData(self, item, p_file=None):
291        """
292        Access the Data1D -> plottable Data1D conversion
293        """
294        return self._data_manager.create_gui_data(item, p_file)
295
296    def setData(self, data):
297        """
298        Sends data to current perspective
299        """
300        if self._current_perspective is not None:
301            self._current_perspective.setData(list(data.values()))
302        else:
303            msg = "Guiframe does not have a current perspective"
304            logging.info(msg)
305
306    def findItemFromFilename(self, filename):
307        """
308        Queries the data explorer for the index corresponding to the filename within
309        """
310        return self.filesWidget.itemFromFilename(filename)
311
312    def quitApplication(self):
313        """
314        Close the reactor and exit nicely.
315        """
316        # Display confirmation messagebox
317        quit_msg = "Are you sure you want to exit the application?"
318        reply = QMessageBox.question(
319            self._parent,
320            'Information',
321            quit_msg,
322            QMessageBox.Yes,
323            QMessageBox.No)
324
325        # Exit if yes
326        if reply == QMessageBox.Yes:
327            reactor.callFromThread(reactor.stop)
328            return True
329
330        return False
331
332    def checkUpdate(self):
333        """
334        Check with the deployment server whether a new version
335        of the application is available.
336        A thread is started for the connecting with the server. The thread calls
337        a call-back method when the current version number has been obtained.
338        """
339        version_info = {"version": "0.0.0"}
340        c = ConnectionProxy(LocalConfig.__update_URL__, LocalConfig.UPDATE_TIMEOUT)
341        response = c.connect()
342        if response is None:
343            return
344        try:
345            content = response.read().strip()
346            logging.info("Connected to www.sasview.org. Latest version: %s"
347                            % (content))
348            version_info = json.loads(content)
349            self.processVersion(version_info)
350        except ValueError as ex:
351            logging.info("Failed to connect to www.sasview.org:", ex)
352
353    def processVersion(self, version_info):
354        """
355        Call-back method for the process of checking for updates.
356        This methods is called by a VersionThread object once the current
357        version number has been obtained. If the check is being done in the
358        background, the user will not be notified unless there's an update.
359
360        :param version: version string
361        """
362        try:
363            version = version_info["version"]
364            if version == "0.0.0":
365                msg = "Could not connect to the application server."
366                msg += " Please try again later."
367                self.communicate.statusBarUpdateSignal.emit(msg)
368
369            elif version.__gt__(LocalConfig.__version__):
370                msg = "Version %s is available! " % str(version)
371                if "download_url" in version_info:
372                    webbrowser.open(version_info["download_url"])
373                else:
374                    webbrowser.open(LocalConfig.__download_page__)
375                self.communicate.statusBarUpdateSignal.emit(msg)
376            else:
377                msg = "You have the latest version"
378                msg += " of %s" % str(LocalConfig.__appname__)
379                self.communicate.statusBarUpdateSignal.emit(msg)
380        except:
381            msg = "guiframe: could not get latest application"
382            msg += " version number\n  %s" % sys.exc_info()[1]
383            logging.error(msg)
384            msg = "Could not connect to the application server."
385            msg += " Please try again later."
386            self.communicate.statusBarUpdateSignal.emit(msg)
387
388    def actionWelcome(self):
389        """ Show the Welcome panel """
390        self.welcomePanel = WelcomePanel()
391        self._workspace.workspace.addSubWindow(self.welcomePanel)
392        self.welcomePanel.show()
393
394    def showWelcomeMessage(self):
395        """ Show the Welcome panel, when required """
396        # Assure the welcome screen is requested
397        show_welcome_widget = True
398        custom_config = get_custom_config()
399        if hasattr(custom_config, "WELCOME_PANEL_SHOW"):
400            if isinstance(custom_config.WELCOME_PANEL_SHOW, bool):
401                show_welcome_widget = custom_config.WELCOME_PANEL_SHOW
402            else:
403                logging.warning("WELCOME_PANEL_SHOW has invalid value in custom_config.py")
404        if show_welcome_widget:
405            self.actionWelcome()
406
407    def addCallbacks(self):
408        """
409        Method defining all signal connections for the gui manager
410        """
411        self.communicate = GuiUtils.Communicate()
412        self.communicate.fileDataReceivedSignal.connect(self.fileWasRead)
413        self.communicate.statusBarUpdateSignal.connect(self.updateStatusBar)
414        self.communicate.updatePerspectiveWithDataSignal.connect(self.updatePerspective)
415        self.communicate.progressBarUpdateSignal.connect(self.updateProgressBar)
416        self.communicate.perspectiveChangedSignal.connect(self.perspectiveChanged)
417        self.communicate.updateTheoryFromPerspectiveSignal.connect(self.updateTheoryFromPerspective)
418        self.communicate.deleteIntermediateTheoryPlotsSignal.connect(self.deleteIntermediateTheoryPlotsByModelID)
419        self.communicate.plotRequestedSignal.connect(self.showPlot)
420        self.communicate.plotFromFilenameSignal.connect(self.showPlotFromFilename)
421        self.communicate.updateModelFromDataOperationPanelSignal.connect(self.updateModelFromDataOperationPanel)
422
423    def addTriggers(self):
424        """
425        Trigger definitions for all menu/toolbar actions.
426        """
427        # disable not yet fully implemented actions
428        self._workspace.actionOpen_Analysis.setEnabled(False)
429        self._workspace.actionUndo.setEnabled(False)
430        self._workspace.actionRedo.setEnabled(False)
431        self._workspace.actionReset.setEnabled(False)
432        self._workspace.actionStartup_Settings.setEnabled(False)
433        self._workspace.actionImage_Viewer.setEnabled(False)
434        self._workspace.actionCombine_Batch_Fit.setEnabled(False)
435        self._workspace.actionFit_Results.setEnabled(False)
436
437        # File
438        self._workspace.actionLoadData.triggered.connect(self.actionLoadData)
439        self._workspace.actionLoad_Data_Folder.triggered.connect(self.actionLoad_Data_Folder)
440        self._workspace.actionOpen_Project.triggered.connect(self.actionOpen_Project)
441        self._workspace.actionOpen_Analysis.triggered.connect(self.actionOpen_Analysis)
442        self._workspace.actionSave.triggered.connect(self.actionSave)
443        self._workspace.actionSave_Analysis.triggered.connect(self.actionSave_Analysis)
444        self._workspace.actionQuit.triggered.connect(self.actionQuit)
445        # Edit
446        self._workspace.actionUndo.triggered.connect(self.actionUndo)
447        self._workspace.actionRedo.triggered.connect(self.actionRedo)
448        self._workspace.actionCopy.triggered.connect(self.actionCopy)
449        self._workspace.actionPaste.triggered.connect(self.actionPaste)
450        self._workspace.actionReport.triggered.connect(self.actionReport)
451        self._workspace.actionReset.triggered.connect(self.actionReset)
452        self._workspace.actionExcel.triggered.connect(self.actionExcel)
453        self._workspace.actionLatex.triggered.connect(self.actionLatex)
454        # View
455        self._workspace.actionShow_Grid_Window.triggered.connect(self.actionShow_Grid_Window)
456        self._workspace.actionHide_Toolbar.triggered.connect(self.actionHide_Toolbar)
457        self._workspace.actionStartup_Settings.triggered.connect(self.actionStartup_Settings)
458        self._workspace.actionCategory_Manager.triggered.connect(self.actionCategory_Manager)
459        self._workspace.actionHide_DataExplorer.triggered.connect(self.actionHide_DataExplorer)
460        # Tools
461        self._workspace.actionData_Operation.triggered.connect(self.actionData_Operation)
462        self._workspace.actionSLD_Calculator.triggered.connect(self.actionSLD_Calculator)
463        self._workspace.actionDensity_Volume_Calculator.triggered.connect(self.actionDensity_Volume_Calculator)
464        self._workspace.actionKeissig_Calculator.triggered.connect(self.actionKiessig_Calculator)
465        #self._workspace.actionKIESSING_Calculator.triggered.connect(self.actionKIESSING_Calculator)
466        self._workspace.actionSlit_Size_Calculator.triggered.connect(self.actionSlit_Size_Calculator)
467        self._workspace.actionSAS_Resolution_Estimator.triggered.connect(self.actionSAS_Resolution_Estimator)
468        self._workspace.actionGeneric_Scattering_Calculator.triggered.connect(self.actionGeneric_Scattering_Calculator)
469        self._workspace.actionPython_Shell_Editor.triggered.connect(self.actionPython_Shell_Editor)
470        self._workspace.actionImage_Viewer.triggered.connect(self.actionImage_Viewer)
471        self._workspace.actionOrientation_Viewer.triggered.connect(self.actionOrientation_Viewer)
472        self._workspace.actionFreeze_Theory.triggered.connect(self.actionFreeze_Theory)
473        # Fitting
474        self._workspace.actionNew_Fit_Page.triggered.connect(self.actionNew_Fit_Page)
475        self._workspace.actionConstrained_Fit.triggered.connect(self.actionConstrained_Fit)
476        self._workspace.actionCombine_Batch_Fit.triggered.connect(self.actionCombine_Batch_Fit)
477        self._workspace.actionFit_Options.triggered.connect(self.actionFit_Options)
478        self._workspace.actionGPU_Options.triggered.connect(self.actionGPU_Options)
479        self._workspace.actionFit_Results.triggered.connect(self.actionFit_Results)
480        self._workspace.actionAdd_Custom_Model.triggered.connect(self.actionAdd_Custom_Model)
481        self._workspace.actionEdit_Custom_Model.triggered.connect(self.actionEdit_Custom_Model)
482        self._workspace.actionManage_Custom_Models.triggered.connect(self.actionManage_Custom_Models)
483        self._workspace.actionAddMult_Models.triggered.connect(self.actionAddMult_Models)
484        self._workspace.actionEditMask.triggered.connect(self.actionEditMask)
485
486        # Window
487        self._workspace.actionCascade.triggered.connect(self.actionCascade)
488        self._workspace.actionTile.triggered.connect(self.actionTile)
489        self._workspace.actionArrange_Icons.triggered.connect(self.actionArrange_Icons)
490        self._workspace.actionNext.triggered.connect(self.actionNext)
491        self._workspace.actionPrevious.triggered.connect(self.actionPrevious)
492        # Analysis
493        self._workspace.actionFitting.triggered.connect(self.actionFitting)
494        self._workspace.actionInversion.triggered.connect(self.actionInversion)
495        self._workspace.actionInvariant.triggered.connect(self.actionInvariant)
496        self._workspace.actionCorfunc.triggered.connect(self.actionCorfunc)
497        # Help
498        self._workspace.actionDocumentation.triggered.connect(self.actionDocumentation)
499        self._workspace.actionTutorial.triggered.connect(self.actionTutorial)
500        self._workspace.actionAcknowledge.triggered.connect(self.actionAcknowledge)
501        self._workspace.actionAbout.triggered.connect(self.actionAbout)
502        self._workspace.actionWelcomeWidget.triggered.connect(self.actionWelcome)
503        self._workspace.actionCheck_for_update.triggered.connect(self.actionCheck_for_update)
504
505        self.communicate.sendDataToGridSignal.connect(self.showBatchOutput)
506
507    #============ FILE =================
508    def actionLoadData(self):
509        """
510        Menu File/Load Data File(s)
511        """
512        self.filesWidget.loadFile()
513
514    def actionLoad_Data_Folder(self):
515        """
516        Menu File/Load Data Folder
517        """
518        self.filesWidget.loadFolder()
519
520    def actionOpen_Project(self):
521        """
522        Menu Open Project
523        """
524        self.filesWidget.loadProject()
525
526    def actionOpen_Analysis(self):
527        """
528        """
529        print("actionOpen_Analysis TRIGGERED")
530        pass
531
532    def actionSave(self):
533        """
534        Menu Save Project
535        """
536        self.filesWidget.saveProject()
537
538    def actionSave_Analysis(self):
539        """
540        Menu File/Save Analysis
541        """
542        self.communicate.saveAnalysisSignal.emit()
543
544    def actionQuit(self):
545        """
546        Close the reactor, exit the application.
547        """
548        self.quitApplication()
549
550    #============ EDIT =================
551    def actionUndo(self):
552        """
553        """
554        print("actionUndo TRIGGERED")
555        pass
556
557    def actionRedo(self):
558        """
559        """
560        print("actionRedo TRIGGERED")
561        pass
562
563    def actionCopy(self):
564        """
565        Send a signal to the fitting perspective so parameters
566        can be saved to the clipboard
567        """
568        self.communicate.copyFitParamsSignal.emit("")
569        self._workspace.actionPaste.setEnabled(True)
570        pass
571
572    def actionPaste(self):
573        """
574        Send a signal to the fitting perspective so parameters
575        from the clipboard can be used to modify the fit state
576        """
577        self.communicate.pasteFitParamsSignal.emit()
578
579    def actionReport(self):
580        """
581        Show the Fit Report dialog.
582        """
583        report_list = None
584        if getattr(self._current_perspective, "currentTab"):
585            try:
586                report_list = self._current_perspective.currentTab.getReport()
587            except Exception as ex:
588                logging.error("Report generation failed with: " + str(ex))
589
590        if report_list is not None:
591            self.report_dialog = ReportDialog(parent=self, report_list=report_list)
592            self.report_dialog.show()
593
594    def actionReset(self):
595        """
596        """
597        logging.warning(" *** actionOpen_Analysis logging *******")
598        print("actionReset print TRIGGERED")
599        sys.stderr.write("STDERR - TRIGGERED")
600        pass
601
602    def actionExcel(self):
603        """
604        Send a signal to the fitting perspective so parameters
605        can be saved to the clipboard
606        """
607        self.communicate.copyFitParamsSignal.emit("Excel")
608
609    def actionLatex(self):
610        """
611        Send a signal to the fitting perspective so parameters
612        can be saved to the clipboard
613        """
614        self.communicate.copyFitParamsSignal.emit("Latex")
615
616    #============ VIEW =================
617    def actionShow_Grid_Window(self):
618        """
619        """
620        self.showBatchOutput(None)
621
622    def showBatchOutput(self, output_data):
623        """
624        Display/redisplay the batch fit viewer
625        """
626        if self.grid_window is None:
627            self.grid_window = BatchOutputPanel(parent=self, output_data=output_data)
628            subwindow = self._workspace.workspace.addSubWindow(self.grid_window)
629
630            #self.grid_window = BatchOutputPanel(parent=self, output_data=output_data)
631            self.grid_window.show()
632            return
633        if output_data:
634            self.grid_window.addFitResults(output_data)
635        self.grid_window.show()
636        if self.grid_window.windowState() == Qt.WindowMinimized:
637            self.grid_window.setWindowState(Qt.WindowActive)
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.