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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 57be490 was 57be490, checked in by Piotr Rozyczko <rozyczko@…>, 4 years ago

Merged ESS_GUI_reporting

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