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

ESS_GUIESS_GUI_Pr_fixesESS_GUI_iss879ESS_GUI_iss959ESS_GUI_project_save
Last change on this file since f0a8f74 was f0a8f74, checked in by Torin Cooper-Bennun <torin.cooper-bennun@…>, 4 months ago

fixed missing import statement + decl. of 'logger' in GuiManager?

  • 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
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        XStream.stdout().messageWritten.connect(self.listWidget.insertPlainText)
89        XStream.stderr().messageWritten.connect(self.listWidget.insertPlainText)
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 createGuiData(self, item, p_file=None):
273        """
274        Access the Data1D -> plottable Data1D conversion
275        """
276        return self._data_manager.create_gui_data(item, p_file)
277
278    def setData(self, data):
279        """
280        Sends data to current perspective
281        """
282        if self._current_perspective is not None:
283            self._current_perspective.setData(list(data.values()))
284        else:
285            msg = "Guiframe does not have a current perspective"
286            logging.info(msg)
287
288    def findItemFromFilename(self, filename):
289        """
290        Queries the data explorer for the index corresponding to the filename within
291        """
292        return self.filesWidget.itemFromFilename(filename)
293
294    def quitApplication(self):
295        """
296        Close the reactor and exit nicely.
297        """
298        # Display confirmation messagebox
299        quit_msg = "Are you sure you want to exit the application?"
300        reply = QMessageBox.question(
301            self._parent,
302            'Information',
303            quit_msg,
304            QMessageBox.Yes,
305            QMessageBox.No)
306
307        # Exit if yes
308        if reply == QMessageBox.Yes:
309            reactor.callFromThread(reactor.stop)
310            return True
311
312        return False
313
314    def checkUpdate(self):
315        """
316        Check with the deployment server whether a new version
317        of the application is available.
318        A thread is started for the connecting with the server. The thread calls
319        a call-back method when the current version number has been obtained.
320        """
321        version_info = {"version": "0.0.0"}
322        c = ConnectionProxy(LocalConfig.__update_URL__, LocalConfig.UPDATE_TIMEOUT)
323        response = c.connect()
324        if response is None:
325            return
326        try:
327            content = response.read().strip()
328            logging.info("Connected to www.sasview.org. Latest version: %s"
329                            % (content))
330            version_info = json.loads(content)
331            self.processVersion(version_info)
332        except ValueError as ex:
333            logging.info("Failed to connect to www.sasview.org:", ex)
334
335    def processVersion(self, version_info):
336        """
337        Call-back method for the process of checking for updates.
338        This methods is called by a VersionThread object once the current
339        version number has been obtained. If the check is being done in the
340        background, the user will not be notified unless there's an update.
341
342        :param version: version string
343        """
344        try:
345            version = version_info["version"]
346            if version == "0.0.0":
347                msg = "Could not connect to the application server."
348                msg += " Please try again later."
349                self.communicate.statusBarUpdateSignal.emit(msg)
350
351            elif version.__gt__(LocalConfig.__version__):
352                msg = "Version %s is available! " % str(version)
353                if "download_url" in version_info:
354                    webbrowser.open(version_info["download_url"])
355                else:
356                    webbrowser.open(LocalConfig.__download_page__)
357                self.communicate.statusBarUpdateSignal.emit(msg)
358            else:
359                msg = "You have the latest version"
360                msg += " of %s" % str(LocalConfig.__appname__)
361                self.communicate.statusBarUpdateSignal.emit(msg)
362        except:
363            msg = "guiframe: could not get latest application"
364            msg += " version number\n  %s" % sys.exc_info()[1]
365            logging.error(msg)
366            msg = "Could not connect to the application server."
367            msg += " Please try again later."
368            self.communicate.statusBarUpdateSignal.emit(msg)
369
370    def showWelcomeMessage(self):
371        """ Show the Welcome panel """
372        self._workspace.workspace.addSubWindow(self.welcomePanel)
373        self.welcomePanel.show()
374
375    def addCallbacks(self):
376        """
377        Method defining all signal connections for the gui manager
378        """
379        self.communicate = GuiUtils.Communicate()
380        self.communicate.fileDataReceivedSignal.connect(self.fileWasRead)
381        self.communicate.statusBarUpdateSignal.connect(self.updateStatusBar)
382        self.communicate.updatePerspectiveWithDataSignal.connect(self.updatePerspective)
383        self.communicate.progressBarUpdateSignal.connect(self.updateProgressBar)
384        self.communicate.perspectiveChangedSignal.connect(self.perspectiveChanged)
385        self.communicate.updateTheoryFromPerspectiveSignal.connect(self.updateTheoryFromPerspective)
386        self.communicate.plotRequestedSignal.connect(self.showPlot)
387        self.communicate.plotFromFilenameSignal.connect(self.showPlotFromFilename)
388        self.communicate.updateModelFromDataOperationPanelSignal.connect(self.updateModelFromDataOperationPanel)
389
390    def addTriggers(self):
391        """
392        Trigger definitions for all menu/toolbar actions.
393        """
394        # File
395        self._workspace.actionLoadData.triggered.connect(self.actionLoadData)
396        self._workspace.actionLoad_Data_Folder.triggered.connect(self.actionLoad_Data_Folder)
397        self._workspace.actionOpen_Project.triggered.connect(self.actionOpen_Project)
398        self._workspace.actionOpen_Analysis.triggered.connect(self.actionOpen_Analysis)
399        self._workspace.actionSave.triggered.connect(self.actionSave)
400        self._workspace.actionSave_Analysis.triggered.connect(self.actionSave_Analysis)
401        self._workspace.actionQuit.triggered.connect(self.actionQuit)
402        # Edit
403        self._workspace.actionUndo.triggered.connect(self.actionUndo)
404        self._workspace.actionRedo.triggered.connect(self.actionRedo)
405        self._workspace.actionCopy.triggered.connect(self.actionCopy)
406        self._workspace.actionPaste.triggered.connect(self.actionPaste)
407        self._workspace.actionReport.triggered.connect(self.actionReport)
408        self._workspace.actionReset.triggered.connect(self.actionReset)
409        self._workspace.actionExcel.triggered.connect(self.actionExcel)
410        self._workspace.actionLatex.triggered.connect(self.actionLatex)
411
412        # View
413        self._workspace.actionShow_Grid_Window.triggered.connect(self.actionShow_Grid_Window)
414        self._workspace.actionHide_Toolbar.triggered.connect(self.actionHide_Toolbar)
415        self._workspace.actionStartup_Settings.triggered.connect(self.actionStartup_Settings)
416        self._workspace.actionCategry_Manager.triggered.connect(self.actionCategry_Manager)
417        # Tools
418        self._workspace.actionData_Operation.triggered.connect(self.actionData_Operation)
419        self._workspace.actionSLD_Calculator.triggered.connect(self.actionSLD_Calculator)
420        self._workspace.actionDensity_Volume_Calculator.triggered.connect(self.actionDensity_Volume_Calculator)
421        self._workspace.actionKeissig_Calculator.triggered.connect(self.actionKiessig_Calculator)
422        #self._workspace.actionKIESSING_Calculator.triggered.connect(self.actionKIESSING_Calculator)
423        self._workspace.actionSlit_Size_Calculator.triggered.connect(self.actionSlit_Size_Calculator)
424        self._workspace.actionSAS_Resolution_Estimator.triggered.connect(self.actionSAS_Resolution_Estimator)
425        self._workspace.actionGeneric_Scattering_Calculator.triggered.connect(self.actionGeneric_Scattering_Calculator)
426        self._workspace.actionPython_Shell_Editor.triggered.connect(self.actionPython_Shell_Editor)
427        self._workspace.actionImage_Viewer.triggered.connect(self.actionImage_Viewer)
428        # Fitting
429        self._workspace.actionNew_Fit_Page.triggered.connect(self.actionNew_Fit_Page)
430        self._workspace.actionConstrained_Fit.triggered.connect(self.actionConstrained_Fit)
431        self._workspace.actionCombine_Batch_Fit.triggered.connect(self.actionCombine_Batch_Fit)
432        self._workspace.actionFit_Options.triggered.connect(self.actionFit_Options)
433        self._workspace.actionGPU_Options.triggered.connect(self.actionGPU_Options)
434        self._workspace.actionFit_Results.triggered.connect(self.actionFit_Results)
435        self._workspace.actionAdd_Custom_Model.triggered.connect(self.actionAdd_Custom_Model)
436        self._workspace.actionEdit_Custom_Model.triggered.connect(self.actionEdit_Custom_Model)
437        self._workspace.actionManage_Custom_Models.triggered.connect(self.actionManage_Custom_Models)
438        self._workspace.actionAddMult_Models.triggered.connect(self.actionAddMult_Models)
439        # Window
440        self._workspace.actionCascade.triggered.connect(self.actionCascade)
441        self._workspace.actionTile.triggered.connect(self.actionTile)
442        self._workspace.actionArrange_Icons.triggered.connect(self.actionArrange_Icons)
443        self._workspace.actionNext.triggered.connect(self.actionNext)
444        self._workspace.actionPrevious.triggered.connect(self.actionPrevious)
445        # Analysis
446        self._workspace.actionFitting.triggered.connect(self.actionFitting)
447        self._workspace.actionInversion.triggered.connect(self.actionInversion)
448        self._workspace.actionInvariant.triggered.connect(self.actionInvariant)
449        self._workspace.actionCorfunc.triggered.connect(self.actionCorfunc)
450        # Help
451        self._workspace.actionDocumentation.triggered.connect(self.actionDocumentation)
452        self._workspace.actionTutorial.triggered.connect(self.actionTutorial)
453        self._workspace.actionAcknowledge.triggered.connect(self.actionAcknowledge)
454        self._workspace.actionAbout.triggered.connect(self.actionAbout)
455        self._workspace.actionCheck_for_update.triggered.connect(self.actionCheck_for_update)
456
457        self.communicate.sendDataToGridSignal.connect(self.showBatchOutput)
458
459    #============ FILE =================
460    def actionLoadData(self):
461        """
462        Menu File/Load Data File(s)
463        """
464        self.filesWidget.loadFile()
465
466    def actionLoad_Data_Folder(self):
467        """
468        Menu File/Load Data Folder
469        """
470        self.filesWidget.loadFolder()
471
472    def actionOpen_Project(self):
473        """
474        Menu Open Project
475        """
476        self.filesWidget.loadProject()
477
478    def actionOpen_Analysis(self):
479        """
480        """
481        print("actionOpen_Analysis TRIGGERED")
482        pass
483
484    def actionSave(self):
485        """
486        Menu Save Project
487        """
488        self.filesWidget.saveProject()
489
490    def actionSave_Analysis(self):
491        """
492        Menu File/Save Analysis
493        """
494        self.communicate.saveAnalysisSignal.emit()
495
496    def actionQuit(self):
497        """
498        Close the reactor, exit the application.
499        """
500        self.quitApplication()
501
502    #============ EDIT =================
503    def actionUndo(self):
504        """
505        """
506        print("actionUndo TRIGGERED")
507        pass
508
509    def actionRedo(self):
510        """
511        """
512        print("actionRedo TRIGGERED")
513        pass
514
515    def actionCopy(self):
516        """
517        """
518        print("actionCopy TRIGGERED")
519        pass
520
521    def actionPaste(self):
522        """
523        """
524        print("actionPaste TRIGGERED")
525        pass
526
527    def actionReport(self):
528        """
529        Show the Fit Report dialog.
530        """
531        report_list = None
532        if getattr(self._current_perspective, "currentTab"):
533            try:
534                report_list = self._current_perspective.currentTab.getReport()
535            except Exception as ex:
536                logging.error("Report generation failed with: " + str(ex))
537
538        if report_list is not None:
539            self.report_dialog = ReportDialog(parent=self, report_list=report_list)
540            self.report_dialog.show()
541
542    def actionReset(self):
543        """
544        """
545        logging.warning(" *** actionOpen_Analysis logging *******")
546        print("actionReset print TRIGGERED")
547        sys.stderr.write("STDERR - TRIGGERED")
548        pass
549
550    def actionExcel(self):
551        """
552        """
553        print("actionExcel TRIGGERED")
554        pass
555
556    def actionLatex(self):
557        """
558        """
559        print("actionLatex TRIGGERED")
560        pass
561
562    #============ VIEW =================
563    def actionShow_Grid_Window(self):
564        """
565        """
566        self.showBatchOutput(None)
567
568    def showBatchOutput(self, output_data):
569        """
570        Display/redisplay the batch fit viewer
571        """
572        if self.grid_window is None:
573            self.grid_window = BatchOutputPanel(parent=self, output_data=output_data)
574            subwindow = self._workspace.workspace.addSubWindow(self.grid_window)
575
576            #self.grid_window = BatchOutputPanel(parent=self, output_data=output_data)
577            self.grid_window.show()
578            return
579        if output_data:
580            self.grid_window.addFitResults(output_data)
581        self.grid_window.show()
582        if self.grid_window.windowState() == Qt.WindowMinimized:
583            self.grid_window.setWindowState(Qt.WindowActive)
584
585    def actionHide_Toolbar(self):
586        """
587        Toggle toolbar vsibility
588        """
589        if self._workspace.toolBar.isVisible():
590            self._workspace.actionHide_Toolbar.setText("Show Toolbar")
591            self._workspace.toolBar.setVisible(False)
592        else:
593            self._workspace.actionHide_Toolbar.setText("Hide Toolbar")
594            self._workspace.toolBar.setVisible(True)
595        pass
596
597    def actionStartup_Settings(self):
598        """
599        """
600        print("actionStartup_Settings TRIGGERED")
601        pass
602
603    def actionCategry_Manager(self):
604        """
605        """
606        print("actionCategry_Manager TRIGGERED")
607        pass
608
609    #============ TOOLS =================
610    def actionData_Operation(self):
611        """
612        """
613        self.communicate.sendDataToPanelSignal.emit(self._data_manager.get_all_data())
614
615        self.DataOperation.show()
616
617    def actionSLD_Calculator(self):
618        """
619        """
620        self.SLDCalculator.show()
621
622    def actionDensity_Volume_Calculator(self):
623        """
624        """
625        self.DVCalculator.show()
626
627    def actionKiessig_Calculator(self):
628        """
629        """
630        self.KIESSIGCalculator.show()
631
632    def actionSlit_Size_Calculator(self):
633        """
634        """
635        self.SlitSizeCalculator.show()
636
637    def actionSAS_Resolution_Estimator(self):
638        """
639        """
640        try:
641            self.ResolutionCalculator.show()
642        except Exception as ex:
643            logging.error(str(ex))
644            return
645
646    def actionGeneric_Scattering_Calculator(self):
647        """
648        """
649        try:
650            self.GENSASCalculator.show()
651        except Exception as ex:
652            logging.error(str(ex))
653            return
654
655    def actionPython_Shell_Editor(self):
656        """
657        Display the Jupyter console as a docked widget.
658        """
659        # Import moved here for startup performance reasons
660        from sas.qtgui.Utilities.IPythonWidget import IPythonWidget
661        terminal = IPythonWidget()
662
663        # Add the console window as another docked widget
664        self.ipDockWidget = QDockWidget("IPython", self._workspace)
665        self.ipDockWidget.setObjectName("IPythonDockWidget")
666        self.ipDockWidget.setWidget(terminal)
667        self._workspace.addDockWidget(Qt.RightDockWidgetArea, self.ipDockWidget)
668
669    def actionImage_Viewer(self):
670        """
671        """
672        print("actionImage_Viewer TRIGGERED")
673        pass
674
675    #============ FITTING =================
676    def actionNew_Fit_Page(self):
677        """
678        Add a new, empty Fit page in the fitting perspective.
679        """
680        # Make sure the perspective is correct
681        per = self.perspective()
682        if not isinstance(per, FittingWindow):
683            return
684        per.addFit(None)
685
686    def actionConstrained_Fit(self):
687        """
688        Add a new Constrained and Simult. Fit page in the fitting perspective.
689        """
690        per = self.perspective()
691        if not isinstance(per, FittingWindow):
692            return
693        per.addConstraintTab()
694
695    def actionCombine_Batch_Fit(self):
696        """
697        """
698        print("actionCombine_Batch_Fit TRIGGERED")
699        pass
700
701    def actionFit_Options(self):
702        """
703        """
704        if getattr(self._current_perspective, "fit_options_widget"):
705            self._current_perspective.fit_options_widget.show()
706        pass
707
708    def actionGPU_Options(self):
709        """
710        Load the OpenCL selection dialog if the fitting perspective is active
711        """
712        if hasattr(self._current_perspective, "gpu_options_widget"):
713            self._current_perspective.gpu_options_widget.show()
714        pass
715
716    def actionFit_Results(self):
717        """
718        """
719        print("actionFit_Results TRIGGERED")
720        pass
721
722    def actionAdd_Custom_Model(self):
723        """
724        """
725        self.model_editor = TabbedModelEditor(self)
726        self.model_editor.show()
727
728    def actionEdit_Custom_Model(self):
729        """
730        """
731        self.model_editor = TabbedModelEditor(self, edit_only=True)
732        self.model_editor.show()
733
734    def actionManage_Custom_Models(self):
735        """
736        """
737        self.model_manager = PluginManager(self)
738        self.model_manager.show()
739
740    def actionAddMult_Models(self):
741        """
742        """
743        # Add Simple Add/Multiply Editor
744        self.add_mult_editor = AddMultEditor(self)
745        self.add_mult_editor.show()
746
747    #============ ANALYSIS =================
748    def actionFitting(self):
749        """
750        Change to the Fitting perspective
751        """
752        self.perspectiveChanged("Fitting")
753        # Notify other widgets
754        self.filesWidget.onAnalysisUpdate("Fitting")
755
756    def actionInversion(self):
757        """
758        Change to the Inversion perspective
759        """
760        self.perspectiveChanged("Inversion")
761        self.filesWidget.onAnalysisUpdate("Inversion")
762
763    def actionInvariant(self):
764        """
765        Change to the Invariant perspective
766        """
767        self.perspectiveChanged("Invariant")
768        self.filesWidget.onAnalysisUpdate("Invariant")
769
770    def actionCorfunc(self):
771        """
772        Change to the Corfunc perspective
773        """
774        self.perspectiveChanged("Corfunc")
775        self.filesWidget.onAnalysisUpdate("Corfunc")
776
777    #============ WINDOW =================
778    def actionCascade(self):
779        """
780        Arranges all the child windows in a cascade pattern.
781        """
782        self._workspace.workspace.cascadeSubWindows()
783
784    def actionTile(self):
785        """
786        Tile workspace windows
787        """
788        self._workspace.workspace.tileSubWindows()
789
790    def actionArrange_Icons(self):
791        """
792        Arranges all iconified windows at the bottom of the workspace
793        """
794        self._workspace.workspace.arrangeIcons()
795
796    def actionNext(self):
797        """
798        Gives the input focus to the next window in the list of child windows.
799        """
800        self._workspace.workspace.activateNextSubWindow()
801
802    def actionPrevious(self):
803        """
804        Gives the input focus to the previous window in the list of child windows.
805        """
806        self._workspace.workspace.activatePreviousSubWindow()
807
808    #============ HELP =================
809    def actionDocumentation(self):
810        """
811        Display the documentation
812
813        TODO: use QNetworkAccessManager to assure _helpLocation is valid
814        """
815        helpfile = "/index.html"
816        self.showHelp(helpfile)
817
818    def actionTutorial(self):
819        """
820        Open the tutorial PDF file with default PDF renderer
821        """
822        # Not terribly safe here. Shell injection warning.
823        # isfile() helps but this probably needs a better solution.
824        if os.path.isfile(self._tutorialLocation):
825            result = subprocess.Popen([self._tutorialLocation], shell=True)
826
827    def actionAcknowledge(self):
828        """
829        Open the Acknowledgements widget
830        """
831        self.ackWidget.show()
832
833    def actionAbout(self):
834        """
835        Open the About box
836        """
837        # Update the about box with current version and stuff
838
839        # TODO: proper sizing
840        self.aboutWidget.show()
841
842    def actionCheck_for_update(self):
843        """
844        Menu Help/Check for Update
845        """
846        self.checkUpdate()
847
848    def updateTheoryFromPerspective(self, index):
849        """
850        Catch the theory update signal from a perspective
851        Send the request to the DataExplorer for updating the theory model.
852        """
853        self.filesWidget.updateTheoryFromPerspective(index)
854
855    def updateModelFromDataOperationPanel(self, new_item, new_datalist_item):
856        """
857        :param new_item: item to be added to list of loaded files
858        :param new_datalist_item:
859        """
860        if not isinstance(new_item, QStandardItem) or \
861                not isinstance(new_datalist_item, dict):
862            msg = "Wrong data type returned from calculations."
863            raise AttributeError(msg)
864
865        self.filesWidget.model.appendRow(new_item)
866        self._data_manager.add_data(new_datalist_item)
867
868    def showPlotFromFilename(self, filename):
869        """
870        Pass the show plot request to the data explorer
871        """
872        if hasattr(self, "filesWidget"):
873            self.filesWidget.displayFile(filename=filename, is_data=True)
874
875    def showPlot(self, plot):
876        """
877        Pass the show plot request to the data explorer
878        """
879        if hasattr(self, "filesWidget"):
880            self.filesWidget.displayData(plot)
881
882    def uncheckAllMenuItems(self, menuObject):
883        """
884        Uncheck all options in a given menu
885        """
886        menuObjects = menuObject.actions()
887
888        for menuItem in menuObjects:
889            menuItem.setChecked(False)
890
891    def checkAnalysisOption(self, analysisMenuOption):
892        """
893        Unchecks all the items in the analysis menu and checks the item passed
894        """
895        self.uncheckAllMenuItems(self._workspace.menuAnalysis)
896        analysisMenuOption.setChecked(True)
897
898    def clearPerspectiveMenubarOptions(self, perspective):
899        """
900        When closing a perspective, clears the menu bar
901        """
902        for menuItem in self._workspace.menuAnalysis.actions():
903            menuItem.setChecked(False)
904
905        if isinstance(self._current_perspective, Perspectives.PERSPECTIVES["Fitting"]):
906            self._workspace.menubar.removeAction(self._workspace.menuFitting.menuAction())
907
908    def setupPerspectiveMenubarOptions(self, perspective):
909        """
910        When setting a perspective, sets up the menu bar
911        """
912        if isinstance(perspective, Perspectives.PERSPECTIVES["Fitting"]):
913            self.checkAnalysisOption(self._workspace.actionFitting)
914            # Put the fitting menu back in
915            # This is a bit involved but it is needed to preserve the menu ordering
916            self._workspace.menubar.removeAction(self._workspace.menuWindow.menuAction())
917            self._workspace.menubar.removeAction(self._workspace.menuHelp.menuAction())
918            self._workspace.menubar.addAction(self._workspace.menuFitting.menuAction())
919            self._workspace.menubar.addAction(self._workspace.menuWindow.menuAction())
920            self._workspace.menubar.addAction(self._workspace.menuHelp.menuAction())
921        elif isinstance(perspective, Perspectives.PERSPECTIVES["Invariant"]):
922            self.checkAnalysisOption(self._workspace.actionInvariant)
923        elif isinstance(perspective, Perspectives.PERSPECTIVES["Inversion"]):
924            self.checkAnalysisOption(self._workspace.actionInversion)
925        elif isinstance(perspective, Perspectives.PERSPECTIVES["Corfunc"]):
926            self.checkAnalysisOption(self._workspace.actionCorfunc)
Note: See TracBrowser for help on using the repository browser.