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

ESS_GUIESS_GUI_Pr_fixesESS_GUI_iss879ESS_GUI_project_save
Last change on this file since e4335ae was e4335ae, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 4 months ago

SASVIEW-957 logging changes (#158)

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