source: sasview/src/sas/qtgui/GuiManager.py @ 363fbfa

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 363fbfa was 363fbfa, checked in by trnielsen, 8 years ago

Add new Kiessing Calculator

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