source: sasview/src/sas/qtgui/GuiManager.py @ 1af348e

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 1af348e was 1af348e, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 8 years ago

Added python console as a docked widget

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