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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 4cbd87f was 4cbd87f, checked in by wojciech, 6 years ago

Removing additional File menu for Show Grid subwidow

  • 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.grid_window = BatchOutputPanel(parent=self)
141        if sys.platform == "darwin":
142            self.grid_window.menubar.setNativeMenuBar(False)
143        self.grid_subwindow = self._workspace.workspace.addSubWindow(self.grid_window)
144        self.grid_subwindow.setVisible(False)
145        self.grid_window.windowClosedSignal.connect(lambda: self.grid_subwindow.setVisible(False))
146
147        self._workspace.toolBar.setVisible(LocalConfig.TOOLBAR_SHOW)
148        self._workspace.actionHide_Toolbar.setText("Show Toolbar")
149
150        # Add calculators - floating for usability
151        self.SLDCalculator = SldPanel(self)
152        self.DVCalculator = DensityPanel(self)
153        self.KIESSIGCalculator = KiessigPanel(self)
154        self.SlitSizeCalculator = SlitSizeCalculator(self)
155        self.GENSASCalculator = GenericScatteringCalculator(self)
156        self.ResolutionCalculator = ResolutionCalculatorPanel(self)
157        self.DataOperation = DataOperationUtilityPanel(self)
158
159    def addCategories(self):
160        """
161        Make sure categories.json exists and if not compile it and install in ~/.sasview
162        """
163        try:
164            from sas.sascalc.fit.models import ModelManager
165            from sas.qtgui.Utilities.CategoryInstaller import CategoryInstaller
166            model_list = ModelManager().cat_model_list()
167            CategoryInstaller.check_install(model_list=model_list)
168        except Exception:
169            import traceback
170            logger.error("%s: could not load SasView models")
171            logger.error(traceback.format_exc())
172
173    def updateContextMenus(self, visible=False):
174        """
175        Modify the View/Data Explorer menu item text on widget visibility
176        """
177        if visible:
178            self._workspace.actionHide_DataExplorer.setText("Hide Data Explorer")
179        else:
180            self._workspace.actionHide_DataExplorer.setText("Show Data Explorer")
181
182    def statusBarSetup(self):
183        """
184        Define the status bar.
185        | <message label> .... | Progress Bar |
186
187        Progress bar invisible until explicitly shown
188        """
189        self.progress = QProgressBar()
190        self._workspace.statusbar.setSizeGripEnabled(False)
191
192        self.statusLabel = QLabel()
193        self.statusLabel.setText("Welcome to SasView")
194        self._workspace.statusbar.addPermanentWidget(self.statusLabel, 1)
195        self._workspace.statusbar.addPermanentWidget(self.progress, stretch=0)
196        self.progress.setRange(0, 100)
197        self.progress.setValue(0)
198        self.progress.setTextVisible(True)
199        self.progress.setVisible(False)
200
201    def fileWasRead(self, data):
202        """
203        Callback for fileDataReceivedSignal
204        """
205        pass
206
207    def showHelp(self, url):
208        """
209        Open a local url in the default browser
210        """
211        GuiUtils.showHelp(url)
212
213    def workspace(self):
214        """
215        Accessor for the main window workspace
216        """
217        return self._workspace.workspace
218
219    def perspectiveChanged(self, perspective_name):
220        """
221        Respond to change of the perspective signal
222        """
223        # Close the previous perspective
224        self.clearPerspectiveMenubarOptions(self._current_perspective)
225        if self._current_perspective:
226            self._current_perspective.setClosable()
227            #self._workspace.workspace.removeSubWindow(self._current_perspective)
228            self._current_perspective.close()
229            self._workspace.workspace.removeSubWindow(self._current_perspective)
230        # Default perspective
231        self._current_perspective = Perspectives.PERSPECTIVES[str(perspective_name)](parent=self)
232
233        self.setupPerspectiveMenubarOptions(self._current_perspective)
234
235        subwindow = self._workspace.workspace.addSubWindow(self._current_perspective)
236
237        # Resize to the workspace height
238        workspace_height = self._workspace.workspace.sizeHint().height()
239        perspective_size = self._current_perspective.sizeHint()
240        perspective_width = perspective_size.width()
241        self._current_perspective.resize(perspective_width, workspace_height-10)
242
243        self._current_perspective.show()
244
245    def updatePerspective(self, data):
246        """
247        Update perspective with data sent.
248        """
249        assert isinstance(data, list)
250        if self._current_perspective is not None:
251            self._current_perspective.setData(list(data.values()))
252        else:
253            msg = "No perspective is currently active."
254            logging.info(msg)
255
256    def communicator(self):
257        """ Accessor for the communicator """
258        return self.communicate
259
260    def perspective(self):
261        """ Accessor for the perspective """
262        return self._current_perspective
263
264    def updateProgressBar(self, value):
265        """
266        Update progress bar with the required value (0-100)
267        """
268        assert -1 <= value <= 100
269        if value == -1:
270            self.progress.setVisible(False)
271            return
272        if not self.progress.isVisible():
273            self.progress.setTextVisible(True)
274            self.progress.setVisible(True)
275
276        self.progress.setValue(value)
277
278    def updateStatusBar(self, text):
279        """
280        Set the status bar text
281        """
282        self.statusLabel.setText(text)
283
284    def appendLog(self, msg):
285        """Appends a message to the list widget in the Log Explorer. Use this
286        instead of listWidget.insertPlainText() to facilitate auto-scrolling"""
287        self.listWidget.append(msg.strip())
288
289    def createGuiData(self, item, p_file=None):
290        """
291        Access the Data1D -> plottable Data1D conversion
292        """
293        return self._data_manager.create_gui_data(item, p_file)
294
295    def setData(self, data):
296        """
297        Sends data to current perspective
298        """
299        if self._current_perspective is not None:
300            self._current_perspective.setData(list(data.values()))
301        else:
302            msg = "Guiframe does not have a current perspective"
303            logging.info(msg)
304
305    def findItemFromFilename(self, filename):
306        """
307        Queries the data explorer for the index corresponding to the filename within
308        """
309        return self.filesWidget.itemFromFilename(filename)
310
311    def quitApplication(self):
312        """
313        Close the reactor and exit nicely.
314        """
315        # Display confirmation messagebox
316        quit_msg = "Are you sure you want to exit the application?"
317        reply = QMessageBox.question(
318            self._parent,
319            'Information',
320            quit_msg,
321            QMessageBox.Yes,
322            QMessageBox.No)
323
324        # Exit if yes
325        if reply == QMessageBox.Yes:
326            reactor.callFromThread(reactor.stop)
327            return True
328
329        return False
330
331    def checkUpdate(self):
332        """
333        Check with the deployment server whether a new version
334        of the application is available.
335        A thread is started for the connecting with the server. The thread calls
336        a call-back method when the current version number has been obtained.
337        """
338        version_info = {"version": "0.0.0"}
339        c = ConnectionProxy(LocalConfig.__update_URL__, LocalConfig.UPDATE_TIMEOUT)
340        response = c.connect()
341        if response is None:
342            return
343        try:
344            content = response.read().strip()
345            logging.info("Connected to www.sasview.org. Latest version: %s"
346                            % (content))
347            version_info = json.loads(content)
348            self.processVersion(version_info)
349        except ValueError as ex:
350            logging.info("Failed to connect to www.sasview.org:", ex)
351
352    def processVersion(self, version_info):
353        """
354        Call-back method for the process of checking for updates.
355        This methods is called by a VersionThread object once the current
356        version number has been obtained. If the check is being done in the
357        background, the user will not be notified unless there's an update.
358
359        :param version: version string
360        """
361        try:
362            version = version_info["version"]
363            if version == "0.0.0":
364                msg = "Could not connect to the application server."
365                msg += " Please try again later."
366                self.communicate.statusBarUpdateSignal.emit(msg)
367
368            elif version.__gt__(LocalConfig.__version__):
369                msg = "Version %s is available! " % str(version)
370                if "download_url" in version_info:
371                    webbrowser.open(version_info["download_url"])
372                else:
373                    webbrowser.open(LocalConfig.__download_page__)
374                self.communicate.statusBarUpdateSignal.emit(msg)
375            else:
376                msg = "You have the latest version"
377                msg += " of %s" % str(LocalConfig.__appname__)
378                self.communicate.statusBarUpdateSignal.emit(msg)
379        except:
380            msg = "guiframe: could not get latest application"
381            msg += " version number\n  %s" % sys.exc_info()[1]
382            logging.error(msg)
383            msg = "Could not connect to the application server."
384            msg += " Please try again later."
385            self.communicate.statusBarUpdateSignal.emit(msg)
386
387    def actionWelcome(self):
388        """ Show the Welcome panel """
389        self.welcomePanel = WelcomePanel()
390        self._workspace.workspace.addSubWindow(self.welcomePanel)
391        self.welcomePanel.show()
392
393    def showWelcomeMessage(self):
394        """ Show the Welcome panel, when required """
395        # Assure the welcome screen is requested
396        show_welcome_widget = True
397        custom_config = get_custom_config()
398        if hasattr(custom_config, "WELCOME_PANEL_SHOW"):
399            if isinstance(custom_config.WELCOME_PANEL_SHOW, bool):
400                show_welcome_widget = custom_config.WELCOME_PANEL_SHOW
401            else:
402                logging.warning("WELCOME_PANEL_SHOW has invalid value in custom_config.py")
403        if show_welcome_widget:
404            self.actionWelcome()
405
406    def addCallbacks(self):
407        """
408        Method defining all signal connections for the gui manager
409        """
410        self.communicate = GuiUtils.Communicate()
411        self.communicate.fileDataReceivedSignal.connect(self.fileWasRead)
412        self.communicate.statusBarUpdateSignal.connect(self.updateStatusBar)
413        self.communicate.updatePerspectiveWithDataSignal.connect(self.updatePerspective)
414        self.communicate.progressBarUpdateSignal.connect(self.updateProgressBar)
415        self.communicate.perspectiveChangedSignal.connect(self.perspectiveChanged)
416        self.communicate.updateTheoryFromPerspectiveSignal.connect(self.updateTheoryFromPerspective)
417        self.communicate.deleteIntermediateTheoryPlotsSignal.connect(self.deleteIntermediateTheoryPlotsByModelID)
418        self.communicate.plotRequestedSignal.connect(self.showPlot)
419        self.communicate.plotFromFilenameSignal.connect(self.showPlotFromFilename)
420        self.communicate.updateModelFromDataOperationPanelSignal.connect(self.updateModelFromDataOperationPanel)
421
422    def addTriggers(self):
423        """
424        Trigger definitions for all menu/toolbar actions.
425        """
426        # disable not yet fully implemented actions
427        self._workspace.actionOpen_Analysis.setEnabled(False)
428        self._workspace.actionUndo.setEnabled(False)
429        self._workspace.actionRedo.setEnabled(False)
430        self._workspace.actionReset.setEnabled(False)
431        self._workspace.actionStartup_Settings.setEnabled(False)
432        self._workspace.actionImage_Viewer.setEnabled(False)
433        self._workspace.actionCombine_Batch_Fit.setEnabled(False)
434        self._workspace.actionFit_Results.setEnabled(False)
435        # orientation viewer set to invisible SASVIEW-1132
436        self._workspace.actionOrientation_Viewer.setVisible(False)
437
438        # File
439        self._workspace.actionLoadData.triggered.connect(self.actionLoadData)
440        self._workspace.actionLoad_Data_Folder.triggered.connect(self.actionLoad_Data_Folder)
441        self._workspace.actionOpen_Project.triggered.connect(self.actionOpen_Project)
442        self._workspace.actionOpen_Analysis.triggered.connect(self.actionOpen_Analysis)
443        self._workspace.actionSave.triggered.connect(self.actionSave)
444        self._workspace.actionSave_Analysis.triggered.connect(self.actionSave_Analysis)
445        self._workspace.actionQuit.triggered.connect(self.actionQuit)
446        # Edit
447        self._workspace.actionUndo.triggered.connect(self.actionUndo)
448        self._workspace.actionRedo.triggered.connect(self.actionRedo)
449        self._workspace.actionCopy.triggered.connect(self.actionCopy)
450        self._workspace.actionPaste.triggered.connect(self.actionPaste)
451        self._workspace.actionReport.triggered.connect(self.actionReport)
452        self._workspace.actionReset.triggered.connect(self.actionReset)
453        self._workspace.actionExcel.triggered.connect(self.actionExcel)
454        self._workspace.actionLatex.triggered.connect(self.actionLatex)
455        # View
456        self._workspace.actionShow_Grid_Window.triggered.connect(self.actionShow_Grid_Window)
457        self._workspace.actionHide_Toolbar.triggered.connect(self.actionHide_Toolbar)
458        self._workspace.actionStartup_Settings.triggered.connect(self.actionStartup_Settings)
459        self._workspace.actionCategory_Manager.triggered.connect(self.actionCategory_Manager)
460        self._workspace.actionHide_DataExplorer.triggered.connect(self.actionHide_DataExplorer)
461        # Tools
462        self._workspace.actionData_Operation.triggered.connect(self.actionData_Operation)
463        self._workspace.actionSLD_Calculator.triggered.connect(self.actionSLD_Calculator)
464        self._workspace.actionDensity_Volume_Calculator.triggered.connect(self.actionDensity_Volume_Calculator)
465        self._workspace.actionKeissig_Calculator.triggered.connect(self.actionKiessig_Calculator)
466        #self._workspace.actionKIESSING_Calculator.triggered.connect(self.actionKIESSING_Calculator)
467        self._workspace.actionSlit_Size_Calculator.triggered.connect(self.actionSlit_Size_Calculator)
468        self._workspace.actionSAS_Resolution_Estimator.triggered.connect(self.actionSAS_Resolution_Estimator)
469        self._workspace.actionGeneric_Scattering_Calculator.triggered.connect(self.actionGeneric_Scattering_Calculator)
470        self._workspace.actionPython_Shell_Editor.triggered.connect(self.actionPython_Shell_Editor)
471        self._workspace.actionImage_Viewer.triggered.connect(self.actionImage_Viewer)
472        self._workspace.actionOrientation_Viewer.triggered.connect(self.actionOrientation_Viewer)
473        self._workspace.actionFreeze_Theory.triggered.connect(self.actionFreeze_Theory)
474        # Fitting
475        self._workspace.actionNew_Fit_Page.triggered.connect(self.actionNew_Fit_Page)
476        self._workspace.actionConstrained_Fit.triggered.connect(self.actionConstrained_Fit)
477        self._workspace.actionCombine_Batch_Fit.triggered.connect(self.actionCombine_Batch_Fit)
478        self._workspace.actionFit_Options.triggered.connect(self.actionFit_Options)
479        self._workspace.actionGPU_Options.triggered.connect(self.actionGPU_Options)
480        self._workspace.actionFit_Results.triggered.connect(self.actionFit_Results)
481        self._workspace.actionAdd_Custom_Model.triggered.connect(self.actionAdd_Custom_Model)
482        self._workspace.actionEdit_Custom_Model.triggered.connect(self.actionEdit_Custom_Model)
483        self._workspace.actionManage_Custom_Models.triggered.connect(self.actionManage_Custom_Models)
484        self._workspace.actionAddMult_Models.triggered.connect(self.actionAddMult_Models)
485        self._workspace.actionEditMask.triggered.connect(self.actionEditMask)
486
487        # Window
488        self._workspace.actionCascade.triggered.connect(self.actionCascade)
489        self._workspace.actionTile.triggered.connect(self.actionTile)
490        self._workspace.actionArrange_Icons.triggered.connect(self.actionArrange_Icons)
491        self._workspace.actionNext.triggered.connect(self.actionNext)
492        self._workspace.actionPrevious.triggered.connect(self.actionPrevious)
493        # Analysis
494        self._workspace.actionFitting.triggered.connect(self.actionFitting)
495        self._workspace.actionInversion.triggered.connect(self.actionInversion)
496        self._workspace.actionInvariant.triggered.connect(self.actionInvariant)
497        self._workspace.actionCorfunc.triggered.connect(self.actionCorfunc)
498        # Help
499        self._workspace.actionDocumentation.triggered.connect(self.actionDocumentation)
500        self._workspace.actionTutorial.triggered.connect(self.actionTutorial)
501        self._workspace.actionAcknowledge.triggered.connect(self.actionAcknowledge)
502        self._workspace.actionAbout.triggered.connect(self.actionAbout)
503        self._workspace.actionWelcomeWidget.triggered.connect(self.actionWelcome)
504        self._workspace.actionCheck_for_update.triggered.connect(self.actionCheck_for_update)
505
506        self.communicate.sendDataToGridSignal.connect(self.showBatchOutput)
507
508    #============ FILE =================
509    def actionLoadData(self):
510        """
511        Menu File/Load Data File(s)
512        """
513        self.filesWidget.loadFile()
514
515    def actionLoad_Data_Folder(self):
516        """
517        Menu File/Load Data Folder
518        """
519        self.filesWidget.loadFolder()
520
521    def actionOpen_Project(self):
522        """
523        Menu Open Project
524        """
525        self.filesWidget.loadProject()
526
527    def actionOpen_Analysis(self):
528        """
529        """
530        print("actionOpen_Analysis TRIGGERED")
531        pass
532
533    def actionSave(self):
534        """
535        Menu Save Project
536        """
537        self.filesWidget.saveProject()
538
539    def actionSave_Analysis(self):
540        """
541        Menu File/Save Analysis
542        """
543        self.communicate.saveAnalysisSignal.emit()
544
545    def actionQuit(self):
546        """
547        Close the reactor, exit the application.
548        """
549        self.quitApplication()
550
551    #============ EDIT =================
552    def actionUndo(self):
553        """
554        """
555        print("actionUndo TRIGGERED")
556        pass
557
558    def actionRedo(self):
559        """
560        """
561        print("actionRedo TRIGGERED")
562        pass
563
564    def actionCopy(self):
565        """
566        Send a signal to the fitting perspective so parameters
567        can be saved to the clipboard
568        """
569        self.communicate.copyFitParamsSignal.emit("")
570        self._workspace.actionPaste.setEnabled(True)
571        pass
572
573    def actionPaste(self):
574        """
575        Send a signal to the fitting perspective so parameters
576        from the clipboard can be used to modify the fit state
577        """
578        self.communicate.pasteFitParamsSignal.emit()
579
580    def actionReport(self):
581        """
582        Show the Fit Report dialog.
583        """
584        report_list = None
585        if getattr(self._current_perspective, "currentTab"):
586            try:
587                report_list = self._current_perspective.currentTab.getReport()
588            except Exception as ex:
589                logging.error("Report generation failed with: " + str(ex))
590
591        if report_list is not None:
592            self.report_dialog = ReportDialog(parent=self, report_list=report_list)
593            self.report_dialog.show()
594
595    def actionReset(self):
596        """
597        """
598        logging.warning(" *** actionOpen_Analysis logging *******")
599        print("actionReset print TRIGGERED")
600        sys.stderr.write("STDERR - TRIGGERED")
601        pass
602
603    def actionExcel(self):
604        """
605        Send a signal to the fitting perspective so parameters
606        can be saved to the clipboard
607        """
608        self.communicate.copyExcelFitParamsSignal.emit("Excel")
609
610    def actionLatex(self):
611        """
612        Send a signal to the fitting perspective so parameters
613        can be saved to the clipboard
614        """
615        self.communicate.copyLatexFitParamsSignal.emit("Latex")
616
617    #============ VIEW =================
618    def actionShow_Grid_Window(self):
619        """
620        """
621        self.showBatchOutput(None)
622
623    def showBatchOutput(self, output_data):
624        """
625        Display/redisplay the batch fit viewer
626        """
627        self.grid_subwindow.setVisible(True)
628        if output_data:
629            self.grid_window.addFitResults(output_data)
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.