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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 2eeda93 was 2eeda93, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 9 months ago

Working version of Save/Load? Analysis. SASVIEW-983.
Changed the default behaviour of Category/Model? combos:
Selecting a category does not pre-select the first model now.

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