source: sasview/src/sas/qtgui/GuiManager.py @ abc5e70

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 abc5e70 was abc5e70, checked in by wojciech, 7 years ago

Fixed bug related to call for SlitSizeCalculator?

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