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

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

Added data weighting prototype

  • Property mode set to 100644
File size: 24.1 KB
Line 
1import sys
2import os
3import subprocess
4import logging
5import json
6import webbrowser
7
8from PyQt4 import QtCore
9from PyQt4 import QtGui
10from PyQt4 import QtWebKit
11
12from twisted.internet import reactor
13# General SAS imports
14
15from sas.sasgui.guiframe.data_manager import DataManager
16from sas.sasgui.guiframe.proxy import Connection
17from sas.qtgui.Utilities.SasviewLogger import XStream
18from sas.qtgui.Utilities.IPythonWidget import IPythonWidget
19import sas.qtgui.Utilities.LocalConfig as LocalConfig
20import sas.qtgui.Utilities.GuiUtils as GuiUtils
21
22from sas.qtgui.MainWindow.UI.AcknowledgementsUI import Ui_Acknowledgements
23from sas.qtgui.MainWindow.AboutBox import AboutBox
24from sas.qtgui.MainWindow.WelcomePanel import WelcomePanel
25
26from sas.qtgui.Calculators.SldPanel import SldPanel
27from sas.qtgui.Calculators.DensityPanel import DensityPanel
28from sas.qtgui.Calculators.KiessigPanel import KiessigPanel
29from sas.qtgui.Calculators.SlitSizeCalculator import SlitSizeCalculator
30
31# Perspectives
32import sas.qtgui.Perspectives as Perspectives
33from sas.qtgui.Perspectives.Fitting.FittingPerspective import FittingWindow
34from sas.qtgui.MainWindow.DataExplorer import DataExplorerWindow
35
36class Acknowledgements(QtGui.QDialog, Ui_Acknowledgements):
37    def __init__(self, parent=None):
38        QtGui.QDialog.__init__(self, parent)
39        self.setupUi(self)
40
41class GuiManager(object):
42    """
43    Main SasView window functionality
44    """
45    ## TODO: CHANGE FOR SHIPPED PATH IN RELEASE
46    HELP_DIRECTORY_LOCATION = "docs/sphinx-docs/build/html"
47
48    def __init__(self, parent=None):
49        """
50        Initialize the manager as a child of MainWindow.
51        """
52        self._workspace = parent
53        self._parent = parent
54
55        # Add signal callbacks
56        self.addCallbacks()
57
58        # Create the data manager
59        # TODO: pull out all required methods from DataManager and reimplement
60        self._data_manager = DataManager()
61
62        # Create action triggers
63        self.addTriggers()
64
65        # Populate menus with dynamic data
66        #
67        # Analysis/Perspectives - potentially
68        # Window/current windows
69        #
70        # Widgets
71        #
72        # Current displayed perspective
73        self._current_perspective = None
74
75        # Invoke the initial perspective
76        self.perspectiveChanged("Fitting")
77
78        self.addWidgets()
79
80        # Fork off logging messages to the Log Window
81        XStream.stdout().messageWritten.connect(self.listWidget.insertPlainText)
82        XStream.stderr().messageWritten.connect(self.listWidget.insertPlainText)
83
84        # Log the start of the session
85        logging.info(" --- SasView session started ---")
86        # Log the python version
87        logging.info("Python: %s" % sys.version)
88
89        # Set up the status bar
90        self.statusBarSetup()
91
92        # Show the Welcome panel
93        self.welcomePanel = WelcomePanel()
94        self._workspace.workspace.addWindow(self.welcomePanel)
95
96        # Current help file
97        self._helpView = QtWebKit.QWebView()
98        # Needs URL like path, so no path.join() here
99        self._helpLocation = self.HELP_DIRECTORY_LOCATION + "/index.html"
100
101        # Current tutorial location
102        self._tutorialLocation = os.path.abspath(os.path.join(self.HELP_DIRECTORY_LOCATION,
103                                              "_downloads",
104                                              "Tutorial.pdf"))
105    def addWidgets(self):
106        """
107        Populate the main window with widgets
108
109        TODO: overwrite close() on Log and DR widgets so they can be hidden/shown
110        on request
111        """
112        # Add FileDialog widget as docked
113        self.filesWidget = DataExplorerWindow(self._parent, self, manager=self._data_manager)
114
115        self.dockedFilesWidget = QtGui.QDockWidget("Data Explorer", self._workspace)
116        self.dockedFilesWidget.setWidget(self.filesWidget)
117
118        # Disable maximize/minimize and close buttons
119        self.dockedFilesWidget.setFeatures(QtGui.QDockWidget.NoDockWidgetFeatures)
120        self._workspace.addDockWidget(QtCore.Qt.LeftDockWidgetArea,
121                                      self.dockedFilesWidget)
122
123        # Add the console window as another docked widget
124        self.logDockWidget = QtGui.QDockWidget("Log Explorer", self._workspace)
125        self.logDockWidget.setObjectName("LogDockWidget")
126        self.listWidget = QtGui.QTextBrowser()
127        self.logDockWidget.setWidget(self.listWidget)
128        self._workspace.addDockWidget(QtCore.Qt.BottomDockWidgetArea,
129                                      self.logDockWidget)
130
131        # Add other, minor widgets
132        self.ackWidget = Acknowledgements()
133        self.aboutWidget = AboutBox()
134
135        # Add calculators - floating for usability
136        self.SLDCalculator = SldPanel(self)
137        self.DVCalculator = DensityPanel(self)
138        #self.KIESSIGCalculator = DensityPanel(self)#KiessigPanel(self)
139        self.KIESSIGCalculator = KiessigPanel(self)
140        self.SlitSizeCalculator = SlitSizeCalculator(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 = QtGui.QProgressBar()
150        self._workspace.statusbar.setSizeGripEnabled(False)
151
152        self.statusLabel = QtGui.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 workspace(self):
168        """
169        Accessor for the main window workspace
170        """
171        return self._workspace.workspace
172
173    def perspectiveChanged(self, perspective_name):
174        """
175        Respond to change of the perspective signal
176        """
177        # Close the previous perspective
178        if self._current_perspective:
179            self._current_perspective.setClosable()
180            self._current_perspective.close()
181        # Default perspective
182        self._current_perspective = Perspectives.PERSPECTIVES[str(perspective_name)](parent=self)
183
184        self._workspace.workspace.addWindow(self._current_perspective)
185        # Resize to the workspace height
186        workspace_height = self._workspace.workspace.sizeHint().height()
187        perspective_size = self._current_perspective.sizeHint()
188        if workspace_height < perspective_size.height:
189            perspective_width = perspective_size.width()
190            self._current_perspective.resize(perspective_width, workspace_height-10)
191        self._current_perspective.show()
192
193    def updatePerspective(self, data):
194        """
195        Update perspective with data sent.
196        """
197        assert isinstance(data, list)
198        if self._current_perspective is not None:
199            self._current_perspective.setData(data.values())
200        else:
201            msg = "No perspective is currently active."
202            logging.info(msg)
203
204    def communicator(self):
205        """ Accessor for the communicator """
206        return self.communicate
207
208    def perspective(self):
209        """ Accessor for the perspective """
210        return self._current_perspective
211
212    def updateProgressBar(self, value):
213        """
214        Update progress bar with the required value (0-100)
215        """
216        assert -1 <= value <= 100
217        if value == -1:
218            self.progress.setVisible(False)
219            return
220        if not self.progress.isVisible():
221            self.progress.setTextVisible(True)
222            self.progress.setVisible(True)
223
224        self.progress.setValue(value)
225
226    def updateStatusBar(self, text):
227        """
228        Set the status bar text
229        """
230        self.statusLabel.setText(text)
231
232    def createGuiData(self, item, p_file=None):
233        """
234        Access the Data1D -> plottable Data1D conversion
235        """
236        return self._data_manager.create_gui_data(item, p_file)
237
238    def setData(self, data):
239        """
240        Sends data to current perspective
241        """
242        if self._current_perspective is not None:
243            self._current_perspective.setData(data.values())
244        else:
245            msg = "Guiframe does not have a current perspective"
246            logging.info(msg)
247
248    def quitApplication(self):
249        """
250        Close the reactor and exit nicely.
251        """
252        # Display confirmation messagebox
253        quit_msg = "Are you sure you want to exit the application?"
254        reply = QtGui.QMessageBox.question(
255            self._parent,
256            'Information',
257            quit_msg,
258            QtGui.QMessageBox.Yes,
259            QtGui.QMessageBox.No)
260
261        # Exit if yes
262        if reply == QtGui.QMessageBox.Yes:
263            reactor.callFromThread(reactor.stop)
264            return True
265
266        return False
267
268    def checkUpdate(self):
269        """
270        Check with the deployment server whether a new version
271        of the application is available.
272        A thread is started for the connecting with the server. The thread calls
273        a call-back method when the current version number has been obtained.
274        """
275        version_info = {"version": "0.0.0"}
276        c = Connection(LocalConfig.__update_URL__, LocalConfig.UPDATE_TIMEOUT)
277        response = c.connect()
278        if response is None:
279            return
280        try:
281            content = response.read().strip()
282            logging.info("Connected to www.sasview.org. Latest version: %s"
283                            % (content))
284            version_info = json.loads(content)
285            self.processVersion(version_info)
286        except ValueError, ex:
287            logging.info("Failed to connect to www.sasview.org:", ex)
288
289    def processVersion(self, version_info):
290        """
291        Call-back method for the process of checking for updates.
292        This methods is called by a VersionThread object once the current
293        version number has been obtained. If the check is being done in the
294        background, the user will not be notified unless there's an update.
295
296        :param version: version string
297        """
298        try:
299            version = version_info["version"]
300            if version == "0.0.0":
301                msg = "Could not connect to the application server."
302                msg += " Please try again later."
303                self.communicate.statusBarUpdateSignal.emit(msg)
304
305            elif cmp(version, LocalConfig.__version__) > 0:
306                msg = "Version %s is available! " % str(version)
307                if "download_url" in version_info:
308                    webbrowser.open(version_info["download_url"])
309                else:
310                    webbrowser.open(LocalConfig.__download_page__)
311                self.communicate.statusBarUpdateSignal.emit(msg)
312            else:
313                msg = "You have the latest version"
314                msg += " of %s" % str(LocalConfig.__appname__)
315                self.communicate.statusBarUpdateSignal.emit(msg)
316        except:
317            msg = "guiframe: could not get latest application"
318            msg += " version number\n  %s" % sys.exc_value
319            logging.error(msg)
320            msg = "Could not connect to the application server."
321            msg += " Please try again later."
322            self.communicate.statusBarUpdateSignal.emit(msg)
323
324    def addCallbacks(self):
325        """
326        Method defining all signal connections for the gui manager
327        """
328        self.communicate = GuiUtils.Communicate()
329        self.communicate.fileDataReceivedSignal.connect(self.fileWasRead)
330        self.communicate.statusBarUpdateSignal.connect(self.updateStatusBar)
331        self.communicate.updatePerspectiveWithDataSignal.connect(self.updatePerspective)
332        self.communicate.progressBarUpdateSignal.connect(self.updateProgressBar)
333        self.communicate.perspectiveChangedSignal.connect(self.perspectiveChanged)
334        self.communicate.updateTheoryFromPerspectiveSignal.connect(self.updateTheoryFromPerspective)
335
336    def addTriggers(self):
337        """
338        Trigger definitions for all menu/toolbar actions.
339        """
340        # File
341        self._workspace.actionLoadData.triggered.connect(self.actionLoadData)
342        self._workspace.actionLoad_Data_Folder.triggered.connect(self.actionLoad_Data_Folder)
343        self._workspace.actionOpen_Project.triggered.connect(self.actionOpen_Project)
344        self._workspace.actionOpen_Analysis.triggered.connect(self.actionOpen_Analysis)
345        self._workspace.actionSave.triggered.connect(self.actionSave)
346        self._workspace.actionSave_Analysis.triggered.connect(self.actionSave_Analysis)
347        self._workspace.actionQuit.triggered.connect(self.actionQuit)
348        # Edit
349        self._workspace.actionUndo.triggered.connect(self.actionUndo)
350        self._workspace.actionRedo.triggered.connect(self.actionRedo)
351        self._workspace.actionCopy.triggered.connect(self.actionCopy)
352        self._workspace.actionPaste.triggered.connect(self.actionPaste)
353        self._workspace.actionReport.triggered.connect(self.actionReport)
354        self._workspace.actionReset.triggered.connect(self.actionReset)
355        self._workspace.actionExcel.triggered.connect(self.actionExcel)
356        self._workspace.actionLatex.triggered.connect(self.actionLatex)
357
358        # View
359        self._workspace.actionShow_Grid_Window.triggered.connect(self.actionShow_Grid_Window)
360        self._workspace.actionHide_Toolbar.triggered.connect(self.actionHide_Toolbar)
361        self._workspace.actionStartup_Settings.triggered.connect(self.actionStartup_Settings)
362        self._workspace.actionCategry_Manager.triggered.connect(self.actionCategry_Manager)
363        # Tools
364        self._workspace.actionData_Operation.triggered.connect(self.actionData_Operation)
365        self._workspace.actionSLD_Calculator.triggered.connect(self.actionSLD_Calculator)
366        self._workspace.actionDensity_Volume_Calculator.triggered.connect(self.actionDensity_Volume_Calculator)
367        self._workspace.actionKeissig_Calculator.triggered.connect(self.actionKiessig_Calculator)
368        #self._workspace.actionKIESSING_Calculator.triggered.connect(self.actionKIESSING_Calculator)
369        self._workspace.actionSlit_Size_Calculator.triggered.connect(self.actionSlit_Size_Calculator)
370        self._workspace.actionSAS_Resolution_Estimator.triggered.connect(self.actionSAS_Resolution_Estimator)
371        self._workspace.actionGeneric_Scattering_Calculator.triggered.connect(self.actionGeneric_Scattering_Calculator)
372        self._workspace.actionPython_Shell_Editor.triggered.connect(self.actionPython_Shell_Editor)
373        self._workspace.actionImage_Viewer.triggered.connect(self.actionImage_Viewer)
374        # Fitting
375        self._workspace.actionNew_Fit_Page.triggered.connect(self.actionNew_Fit_Page)
376        self._workspace.actionConstrained_Fit.triggered.connect(self.actionConstrained_Fit)
377        self._workspace.actionCombine_Batch_Fit.triggered.connect(self.actionCombine_Batch_Fit)
378        self._workspace.actionFit_Options.triggered.connect(self.actionFit_Options)
379        self._workspace.actionFit_Results.triggered.connect(self.actionFit_Results)
380        self._workspace.actionChain_Fitting.triggered.connect(self.actionChain_Fitting)
381        self._workspace.actionEdit_Custom_Model.triggered.connect(self.actionEdit_Custom_Model)
382        # Window
383        self._workspace.actionCascade.triggered.connect(self.actionCascade)
384        self._workspace.actionTile.triggered.connect(self.actionTile)
385        self._workspace.actionArrange_Icons.triggered.connect(self.actionArrange_Icons)
386        self._workspace.actionNext.triggered.connect(self.actionNext)
387        self._workspace.actionPrevious.triggered.connect(self.actionPrevious)
388        # Analysis
389        self._workspace.actionFitting.triggered.connect(self.actionFitting)
390        self._workspace.actionInversion.triggered.connect(self.actionInversion)
391        self._workspace.actionInvariant.triggered.connect(self.actionInvariant)
392        # Help
393        self._workspace.actionDocumentation.triggered.connect(self.actionDocumentation)
394        self._workspace.actionTutorial.triggered.connect(self.actionTutorial)
395        self._workspace.actionAcknowledge.triggered.connect(self.actionAcknowledge)
396        self._workspace.actionAbout.triggered.connect(self.actionAbout)
397        self._workspace.actionCheck_for_update.triggered.connect(self.actionCheck_for_update)
398
399    #============ FILE =================
400    def actionLoadData(self):
401        """
402        Menu File/Load Data File(s)
403        """
404        self.filesWidget.loadFile()
405
406    def actionLoad_Data_Folder(self):
407        """
408        Menu File/Load Data Folder
409        """
410        self.filesWidget.loadFolder()
411
412    def actionOpen_Project(self):
413        """
414        Menu Open Project
415        """
416        self.filesWidget.loadProject()
417
418    def actionOpen_Analysis(self):
419        """
420        """
421        print("actionOpen_Analysis TRIGGERED")
422        pass
423
424    def actionSave(self):
425        """
426        Menu Save Project
427        """
428        self.filesWidget.saveProject()
429
430    def actionSave_Analysis(self):
431        """
432        """
433        print("actionSave_Analysis TRIGGERED")
434
435        pass
436
437    def actionQuit(self):
438        """
439        Close the reactor, exit the application.
440        """
441        self.quitApplication()
442
443    #============ EDIT =================
444    def actionUndo(self):
445        """
446        """
447        print("actionUndo TRIGGERED")
448        pass
449
450    def actionRedo(self):
451        """
452        """
453        print("actionRedo TRIGGERED")
454        pass
455
456    def actionCopy(self):
457        """
458        """
459        print("actionCopy TRIGGERED")
460        pass
461
462    def actionPaste(self):
463        """
464        """
465        print("actionPaste TRIGGERED")
466        pass
467
468    def actionReport(self):
469        """
470        """
471        print("actionReport TRIGGERED")
472        pass
473
474    def actionReset(self):
475        """
476        """
477        logging.warning(" *** actionOpen_Analysis logging *******")
478        print("actionReset print TRIGGERED")
479        sys.stderr.write("STDERR - TRIGGERED")
480        pass
481
482    def actionExcel(self):
483        """
484        """
485        print("actionExcel TRIGGERED")
486        pass
487
488    def actionLatex(self):
489        """
490        """
491        print("actionLatex TRIGGERED")
492        pass
493
494    #============ VIEW =================
495    def actionShow_Grid_Window(self):
496        """
497        """
498        print("actionShow_Grid_Window TRIGGERED")
499        pass
500
501    def actionHide_Toolbar(self):
502        """
503        Toggle toolbar vsibility
504        """
505        if self._workspace.toolBar.isVisible():
506            self._workspace.actionHide_Toolbar.setText("Show Toolbar")
507            self._workspace.toolBar.setVisible(False)
508        else:
509            self._workspace.actionHide_Toolbar.setText("Hide Toolbar")
510            self._workspace.toolBar.setVisible(True)
511        pass
512
513    def actionStartup_Settings(self):
514        """
515        """
516        print("actionStartup_Settings TRIGGERED")
517        pass
518
519    def actionCategry_Manager(self):
520        """
521        """
522        print("actionCategry_Manager TRIGGERED")
523        pass
524
525    #============ TOOLS =================
526    def actionData_Operation(self):
527        """
528        """
529        print("actionData_Operation TRIGGERED")
530        pass
531
532    def actionSLD_Calculator(self):
533        """
534        """
535        self.SLDCalculator.show()
536
537    def actionDensity_Volume_Calculator(self):
538        """
539        """
540        self.DVCalculator.show()
541
542    def actionKiessig_Calculator(self):
543        """
544        """
545        #self.DVCalculator.show()
546        self.KIESSIGCalculator.show()
547
548    def actionSlit_Size_Calculator(self):
549        """
550        """
551        self.SlitSizeCalculator.show()
552
553    def actionSAS_Resolution_Estimator(self):
554        """
555        """
556        print("actionSAS_Resolution_Estimator TRIGGERED")
557        pass
558
559    def actionGeneric_Scattering_Calculator(self):
560        """
561        """
562        print("actionGeneric_Scattering_Calculator TRIGGERED")
563        pass
564
565    def actionPython_Shell_Editor(self):
566        """
567        Display the Jupyter console as a docked widget.
568        """
569        terminal = IPythonWidget()
570
571        # Add the console window as another docked widget
572        self.ipDockWidget = QtGui.QDockWidget("IPython", self._workspace)
573        self.ipDockWidget.setObjectName("IPythonDockWidget")
574        self.ipDockWidget.setWidget(terminal)
575        self._workspace.addDockWidget(QtCore.Qt.RightDockWidgetArea,
576                                      self.ipDockWidget)
577
578    def actionImage_Viewer(self):
579        """
580        """
581        print("actionImage_Viewer TRIGGERED")
582        pass
583
584    #============ FITTING =================
585    def actionNew_Fit_Page(self):
586        """
587        Add a new, empty Fit page in the fitting perspective.
588        """
589        # Make sure the perspective is correct
590        per = self.perspective()
591        if not isinstance(per, FittingWindow):
592            return
593        per.addFit(None)
594
595    def actionConstrained_Fit(self):
596        """
597        """
598        print("actionConstrained_Fit TRIGGERED")
599        pass
600
601    def actionCombine_Batch_Fit(self):
602        """
603        """
604        print("actionCombine_Batch_Fit TRIGGERED")
605        pass
606
607    def actionFit_Options(self):
608        """
609        """
610        print("actionFit_Options TRIGGERED")
611        pass
612
613    def actionFit_Results(self):
614        """
615        """
616        print("actionFit_Results TRIGGERED")
617        pass
618
619    def actionChain_Fitting(self):
620        """
621        """
622        print("actionChain_Fitting TRIGGERED")
623        pass
624
625    def actionEdit_Custom_Model(self):
626        """
627        """
628        print("actionEdit_Custom_Model TRIGGERED")
629        pass
630
631    #============ ANALYSIS =================
632    def actionFitting(self):
633        """
634        """
635        print("actionFitting TRIGGERED")
636        pass
637
638    def actionInversion(self):
639        """
640        """
641        print("actionInversion TRIGGERED")
642        pass
643
644    def actionInvariant(self):
645        """
646        """
647        print("actionInvariant TRIGGERED")
648        pass
649
650    #============ WINDOW =================
651    def actionCascade(self):
652        """
653        Arranges all the child windows in a cascade pattern.
654        """
655        self._workspace.workspace.cascade()
656
657    def actionTile(self):
658        """
659        Tile workspace windows
660        """
661        self._workspace.workspace.tile()
662
663    def actionArrange_Icons(self):
664        """
665        Arranges all iconified windows at the bottom of the workspace
666        """
667        self._workspace.workspace.arrangeIcons()
668
669    def actionNext(self):
670        """
671        Gives the input focus to the next window in the list of child windows.
672        """
673        self._workspace.workspace.activateNextWindow()
674
675    def actionPrevious(self):
676        """
677        Gives the input focus to the previous window in the list of child windows.
678        """
679        self._workspace.workspace.activatePreviousWindow()
680
681    #============ HELP =================
682    def actionDocumentation(self):
683        """
684        Display the documentation
685
686        TODO: use QNetworkAccessManager to assure _helpLocation is valid
687        """
688        self._helpView.load(QtCore.QUrl(self._helpLocation))
689        self._helpView.show()
690
691    def actionTutorial(self):
692        """
693        Open the tutorial PDF file with default PDF renderer
694        """
695        # Not terribly safe here. Shell injection warning.
696        # isfile() helps but this probably needs a better solution.
697        if os.path.isfile(self._tutorialLocation):
698            result = subprocess.Popen([self._tutorialLocation], shell=True)
699
700    def actionAcknowledge(self):
701        """
702        Open the Acknowledgements widget
703        """
704        self.ackWidget.show()
705
706    def actionAbout(self):
707        """
708        Open the About box
709        """
710        # Update the about box with current version and stuff
711
712        # TODO: proper sizing
713        self.aboutWidget.show()
714
715    def actionCheck_for_update(self):
716        """
717        Menu Help/Check for Update
718        """
719        self.checkUpdate()
720
721    def updateTheoryFromPerspective(self, index):
722        """
723        Catch the theory update signal from a perspective
724        Send the request to the DataExplorer for updating the theory model.
725        """
726        self.filesWidget.updateTheoryFromPerspective(index)
727
728
729
Note: See TracBrowser for help on using the repository browser.