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

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

output console + logging

  • Property mode set to 100755
File size: 21.1 KB
Line 
1import sys
2import os
3import subprocess
4import logging
5import json
6import webbrowser
7
8from PyQt4 import QtCore
9from PyQt4 import QtGui
10from PyQt4 import QtWebKit
11
12from twisted.internet import reactor
13
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 updatePerspective(self, data):
154        """
155        """
156        assert isinstance(data, list)
157        if self._current_perspective is not None:
158            self._current_perspective.setData(data.values())
159        else:
160            msg = "No perspective is currently active."
161            logging.info(msg)
162
163
164    def communicator(self):
165        """
166        """
167        return self.communicate
168
169    def reactor(self):
170        """
171        """
172        return self._reactor
173
174    def setReactor(self, reactor):
175        """
176        """
177        self._reactor = reactor
178
179    def perspective(self):
180        """
181        """
182        return self._current_perspective
183
184    def updateProgressBar(self, value):
185        """
186        Update progress bar with the required value (0-100)
187        """
188        assert(-1 <= value <= 100)
189        if value == -1:
190            self.progress.setVisible(False)
191            return
192        if not self.progress.isVisible():
193            self.progress.setTextVisible(True)
194            self.progress.setVisible(True)
195
196        self.progress.setValue(value)
197
198    def updateStatusBar(self, text):
199        """
200        """
201        #self._workspace.statusbar.showMessage(text)
202        self.statusLabel.setText(text)
203
204    def createGuiData(self, item, p_file=None):
205        """
206        Access the Data1D -> plottable Data1D conversion
207        """
208        return self._data_manager.create_gui_data(item, p_file)
209
210    def setData(self, data):
211        """
212        Sends data to current perspective
213        """
214        if self._current_perspective is not None:
215            self._current_perspective.setData(data.values())
216        else:
217            msg = "Guiframe does not have a current perspective"
218            logging.info(msg)
219
220    def quitApplication(self):
221        """
222        Close the reactor and exit nicely.
223        """
224        # Display confirmation messagebox
225        quit_msg = "Are you sure you want to exit the application?"
226        reply = QtGui.QMessageBox.question(
227            self._parent,
228            'Warning',
229            quit_msg,
230            QtGui.QMessageBox.Yes,
231            QtGui.QMessageBox.No)
232
233        if reply == QtGui.QMessageBox.No:
234            return
235
236        # Exit if yes
237        reactor.callFromThread(reactor.stop)
238
239    def checkUpdate(self):
240        """
241        Check with the deployment server whether a new version
242        of the application is available.
243        A thread is started for the connecting with the server. The thread calls
244        a call-back method when the current version number has been obtained.
245        """
246        version_info = {"version": "0.0.0"}
247        c = Connection(LocalConfig.__update_URL__, LocalConfig.UPDATE_TIMEOUT)
248        response = c.connect()
249        if response is not None:
250            try:
251                content = response.read().strip()
252                logging.info("Connected to www.sasview.org. Latest version: %s"
253                             % (content))
254                version_info = json.loads(content)
255            except:
256                logging.info("Failed to connect to www.sasview.org")
257        self.processVersion(version_info)
258
259    def processVersion(self, version_info):
260        """
261        Call-back method for the process of checking for updates.
262        This methods is called by a VersionThread object once the current
263        version number has been obtained. If the check is being done in the
264        background, the user will not be notified unless there's an update.
265
266        :param version: version string
267        """
268        try:
269            version = version_info["version"]
270            if version == "0.0.0":
271                msg = "Could not connect to the application server."
272                msg += " Please try again later."
273                #self.SetStatusText(msg)
274                self.communicate.statusBarUpdateSignal.emit(msg)
275
276            elif cmp(version, LocalConfig.__version__) > 0:
277                msg = "Version %s is available! " % str(version)
278                if "download_url" in version_info:
279                    webbrowser.open(version_info["download_url"])
280                else:
281                    webbrowser.open(LocalConfig.__download_page__)
282                self.communicate.statusBarUpdateSignal.emit(msg)
283            else:
284                msg = "You have the latest version"
285                msg += " of %s" % str(LocalConfig.__appname__)
286                self.communicate.statusBarUpdateSignal.emit(msg)
287        except:
288            msg = "guiframe: could not get latest application"
289            msg += " version number\n  %s" % sys.exc_value
290            logging.error(msg)
291            msg = "Could not connect to the application server."
292            msg += " Please try again later."
293            self.communicate.statusBarUpdateSignal.emit(msg)
294
295    def addCallbacks(self):
296        """
297        Method defining all signal connections for the gui manager
298        """
299        self.communicate = GuiUtils.Communicate()
300        self.communicate.fileDataReceivedSignal.connect(self.fileRead)
301        self.communicate.statusBarUpdateSignal.connect(self.updateStatusBar)
302        self.communicate.updatePerspectiveWithDataSignal.connect(self.updatePerspective)
303        self.communicate.progressBarUpdateSignal.connect(self.updateProgressBar)
304
305    def addTriggers(self):
306        """
307        Trigger definitions for all menu/toolbar actions.
308        """
309        # File
310        self._workspace.actionLoadData.triggered.connect(self.actionLoadData)
311        self._workspace.actionLoad_Data_Folder.triggered.connect(self.actionLoad_Data_Folder)
312        self._workspace.actionOpen_Project.triggered.connect(self.actionOpen_Project)
313        self._workspace.actionOpen_Analysis.triggered.connect(self.actionOpen_Analysis)
314        self._workspace.actionSave.triggered.connect(self.actionSave)
315        self._workspace.actionSave_Analysis.triggered.connect(self.actionSave_Analysis)
316        self._workspace.actionQuit.triggered.connect(self.actionQuit)
317        # Edit
318        self._workspace.actionUndo.triggered.connect(self.actionUndo)
319        self._workspace.actionRedo.triggered.connect(self.actionRedo)
320        self._workspace.actionCopy.triggered.connect(self.actionCopy)
321        self._workspace.actionPaste.triggered.connect(self.actionPaste)
322        self._workspace.actionReport.triggered.connect(self.actionReport)
323        self._workspace.actionReset.triggered.connect(self.actionReset)
324        self._workspace.actionExcel.triggered.connect(self.actionExcel)
325        self._workspace.actionLatex.triggered.connect(self.actionLatex)
326
327        # View
328        self._workspace.actionShow_Grid_Window.triggered.connect(self.actionShow_Grid_Window)
329        self._workspace.actionHide_Toolbar.triggered.connect(self.actionHide_Toolbar)
330        self._workspace.actionStartup_Settings.triggered.connect(self.actionStartup_Settings)
331        self._workspace.actionCategry_Manager.triggered.connect(self.actionCategry_Manager)
332        # Tools
333        self._workspace.actionData_Operation.triggered.connect(self.actionData_Operation)
334        self._workspace.actionSLD_Calculator.triggered.connect(self.actionSLD_Calculator)
335        self._workspace.actionDensity_Volume_Calculator.triggered.connect(self.actionDensity_Volume_Calculator)
336        self._workspace.actionSlit_Size_Calculator.triggered.connect(self.actionSlit_Size_Calculator)
337        self._workspace.actionSAS_Resolution_Estimator.triggered.connect(self.actionSAS_Resolution_Estimator)
338        self._workspace.actionGeneric_Scattering_Calculator.triggered.connect(self.actionGeneric_Scattering_Calculator)
339        self._workspace.actionPython_Shell_Editor.triggered.connect(self.actionPython_Shell_Editor)
340        self._workspace.actionImage_Viewer.triggered.connect(self.actionImage_Viewer)
341        # Fitting
342        self._workspace.actionNew_Fit_Page.triggered.connect(self.actionNew_Fit_Page)
343        self._workspace.actionConstrained_Fit.triggered.connect(self.actionConstrained_Fit)
344        self._workspace.actionCombine_Batch_Fit.triggered.connect(self.actionCombine_Batch_Fit)
345        self._workspace.actionFit_Options.triggered.connect(self.actionFit_Options)
346        self._workspace.actionFit_Results.triggered.connect(self.actionFit_Results)
347        self._workspace.actionChain_Fitting.triggered.connect(self.actionChain_Fitting)
348        self._workspace.actionEdit_Custom_Model.triggered.connect(self.actionEdit_Custom_Model)
349        # Window
350        self._workspace.actionCascade.triggered.connect(self.actionCascade)
351        self._workspace.actionTile.triggered.connect(self.actionTile)
352        self._workspace.actionArrange_Icons.triggered.connect(self.actionArrange_Icons)
353        self._workspace.actionNext.triggered.connect(self.actionNext)
354        self._workspace.actionPrevious.triggered.connect(self.actionPrevious)
355        # Analysis
356        self._workspace.actionFitting.triggered.connect(self.actionFitting)
357        self._workspace.actionInversion.triggered.connect(self.actionInversion)
358        self._workspace.actionInvariant.triggered.connect(self.actionInvariant)
359        # Help
360        self._workspace.actionDocumentation.triggered.connect(self.actionDocumentation)
361        self._workspace.actionTutorial.triggered.connect(self.actionTutorial)
362        self._workspace.actionAcknowledge.triggered.connect(self.actionAcknowledge)
363        self._workspace.actionAbout.triggered.connect(self.actionAbout)
364        self._workspace.actionCheck_for_update.triggered.connect(self.actionCheck_for_update)
365
366    #============ FILE =================
367    def actionLoadData(self):
368        """
369        Menu File/Load Data File(s)
370        """
371        self.filesWidget.loadFile()
372
373    def actionLoad_Data_Folder(self):
374        """
375        Menu File/Load Data Folder
376        """
377        self.filesWidget.loadFolder()
378
379    def actionOpen_Project(self):
380        """
381        """
382        print("actionOpen_Project TRIGGERED")
383        pass
384
385    def actionOpen_Analysis(self):
386        """
387        """
388        print("actionOpen_Analysis TRIGGERED")
389        pass
390
391    def actionSave(self):
392        """
393        """
394        print("actionSave TRIGGERED")
395        pass
396
397    def actionSave_Analysis(self):
398        """
399        """
400        print("actionSave_Analysis TRIGGERED")
401
402        pass
403
404    def actionQuit(self):
405        """
406        Close the reactor, exit the application.
407        """
408        self.quitApplication()
409
410    #============ EDIT =================
411    def actionUndo(self):
412        """
413        """
414        print("actionUndo TRIGGERED")
415        pass
416
417    def actionRedo(self):
418        """
419        """
420        print("actionRedo TRIGGERED")
421        pass
422
423    def actionCopy(self):
424        """
425        """
426        print("actionCopy TRIGGERED")
427        pass
428
429    def actionPaste(self):
430        """
431        """
432        print("actionPaste TRIGGERED")
433        pass
434
435    def actionReport(self):
436        """
437        """
438        print("actionReport TRIGGERED")
439        pass
440
441    def actionReset(self):
442        """
443        """
444        logging.warning(" *** actionOpen_Analysis logging *******")
445        print("actionReset print TRIGGERED")
446        sys.stderr.write("STDERR - TRIGGERED")
447        pass
448
449    def actionExcel(self):
450        """
451        """
452        print("actionExcel TRIGGERED")
453        pass
454
455    def actionLatex(self):
456        """
457        """
458        print("actionLatex TRIGGERED")
459        pass
460
461    #============ VIEW =================
462    def actionShow_Grid_Window(self):
463        """
464        """
465        print("actionShow_Grid_Window TRIGGERED")
466        pass
467
468    def actionHide_Toolbar(self):
469        """
470        Toggle toolbar vsibility
471        """
472        if self._workspace.toolBar.isVisible():
473            self._workspace.actionHide_Toolbar.setText("Show Toolbar")
474            self._workspace.toolBar.setVisible(False)
475        else:
476            self._workspace.actionHide_Toolbar.setText("Hide Toolbar")
477            self._workspace.toolBar.setVisible(True)
478        pass
479
480    def actionStartup_Settings(self):
481        """
482        """
483        print("actionStartup_Settings TRIGGERED")
484        pass
485
486    def actionCategry_Manager(self):
487        """
488        """
489        print("actionCategry_Manager TRIGGERED")
490        pass
491
492    #============ TOOLS =================
493    def actionData_Operation(self):
494        """
495        """
496        print("actionData_Operation TRIGGERED")
497        pass
498
499    def actionSLD_Calculator(self):
500        """
501        """
502        print("actionSLD_Calculator TRIGGERED")
503        pass
504
505    def actionDensity_Volume_Calculator(self):
506        """
507        """
508        print("actionDensity_Volume_Calculator TRIGGERED")
509        pass
510
511    def actionSlit_Size_Calculator(self):
512        """
513        """
514        print("actionSlit_Size_Calculator TRIGGERED")
515        pass
516
517    def actionSAS_Resolution_Estimator(self):
518        """
519        """
520        print("actionSAS_Resolution_Estimator TRIGGERED")
521        pass
522
523    def actionGeneric_Scattering_Calculator(self):
524        """
525        """
526        print("actionGeneric_Scattering_Calculator TRIGGERED")
527        pass
528
529    def actionPython_Shell_Editor(self):
530        """
531        """
532        print("actionPython_Shell_Editor TRIGGERED")
533        pass
534
535    def actionImage_Viewer(self):
536        """
537        """
538        print("actionImage_Viewer TRIGGERED")
539        pass
540
541    #============ FITTING =================
542    def actionNew_Fit_Page(self):
543        """
544        """
545        print("actionNew_Fit_Page TRIGGERED")
546        pass
547
548    def actionConstrained_Fit(self):
549        """
550        """
551        print("actionConstrained_Fit TRIGGERED")
552        pass
553
554    def actionCombine_Batch_Fit(self):
555        """
556        """
557        print("actionCombine_Batch_Fit TRIGGERED")
558        pass
559
560    def actionFit_Options(self):
561        """
562        """
563        print("actionFit_Options TRIGGERED")
564        pass
565
566    def actionFit_Results(self):
567        """
568        """
569        print("actionFit_Results TRIGGERED")
570        pass
571
572    def actionChain_Fitting(self):
573        """
574        """
575        print("actionChain_Fitting TRIGGERED")
576        pass
577
578    def actionEdit_Custom_Model(self):
579        """
580        """
581        print("actionEdit_Custom_Model TRIGGERED")
582        pass
583
584    #============ ANALYSIS =================
585    def actionFitting(self):
586        """
587        """
588        print("actionFitting TRIGGERED")
589        pass
590
591    def actionInversion(self):
592        """
593        """
594        print("actionInversion TRIGGERED")
595        pass
596
597    def actionInvariant(self):
598        """
599        """
600        print("actionInvariant TRIGGERED")
601        pass
602
603    #============ WINDOW =================
604    def actionCascade(self):
605        """
606        Arranges all the child windows in a cascade pattern.
607        """
608        self._workspace.workspace.cascade()
609
610    def actionTile(self):
611        """
612        Tile workspace windows
613        """
614        self._workspace.workspace.tile()
615
616    def actionArrange_Icons(self):
617        """
618        Arranges all iconified windows at the bottom of the workspace
619        """
620        self._workspace.workspace.arrangeIcons()
621
622    def actionNext(self):
623        """
624        Gives the input focus to the next window in the list of child windows.
625        """
626        self._workspace.workspace.activateNextWindow()
627
628    def actionPrevious(self):
629        """
630        Gives the input focus to the previous window in the list of child windows.
631        """
632        self._workspace.workspace.activatePreviousWindow()
633
634    #============ HELP =================
635    def actionDocumentation(self):
636        """
637        Display the documentation
638
639        TODO: use QNetworkAccessManager to assure _helpLocation is valid
640        """
641        self._helpView.load(QtCore.QUrl(self._helpLocation))
642        self._helpView.show()
643
644    def actionTutorial(self):
645        """
646        Open the tutorial PDF file with default PDF renderer
647        """
648        # Not terribly safe here. Shell injection warning.
649        # isfile() helps but this probably needs a better solution.
650        if os.path.isfile(self._tutorialLocation):
651            result = subprocess.Popen([self._tutorialLocation], shell=True)
652
653    def actionAcknowledge(self):
654        """
655        Open the Acknowledgements widget
656        """
657        self.ackWidget.show()
658
659    def actionAbout(self):
660        """
661        Open the About box
662        """
663        # Update the about box with current version and stuff
664
665        # TODO: proper sizing
666        self.aboutWidget.show()
667
668    def actionCheck_for_update(self):
669        """
670        Menu Help/Check for Update
671        """
672        self.checkUpdate()
673
674        pass
675
Note: See TracBrowser for help on using the repository browser.