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

ESS_GUIESS_GUI_DocsESS_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 3b8cc00 was 3b8cc00, checked in by Piotr Rozyczko <rozyczko@…>, 5 years ago

Code review changes

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