source: sasview/src/sas/qtgui/GuiManager.py @ 257bd57

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

unit tests for graph range and add text

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