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

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 d3b57a0 was c889a3e, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

Create categories.json if file not present. SASVIEW-937

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