source: sasview/src/sas/qtgui/GuiManager.py @ 28a84e9

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

Plot handler prototype + append plot functionality

  • Property mode set to 100755
File size: 21.2 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        """
538        print("actionPython_Shell_Editor TRIGGERED")
539        pass
540
541    def actionImage_Viewer(self):
542        """
543        """
544        print("actionImage_Viewer TRIGGERED")
545        pass
546
547    #============ FITTING =================
548    def actionNew_Fit_Page(self):
549        """
550        """
551        print("actionNew_Fit_Page TRIGGERED")
552        pass
553
554    def actionConstrained_Fit(self):
555        """
556        """
557        print("actionConstrained_Fit TRIGGERED")
558        pass
559
560    def actionCombine_Batch_Fit(self):
561        """
562        """
563        print("actionCombine_Batch_Fit TRIGGERED")
564        pass
565
566    def actionFit_Options(self):
567        """
568        """
569        print("actionFit_Options TRIGGERED")
570        pass
571
572    def actionFit_Results(self):
573        """
574        """
575        print("actionFit_Results TRIGGERED")
576        pass
577
578    def actionChain_Fitting(self):
579        """
580        """
581        print("actionChain_Fitting TRIGGERED")
582        pass
583
584    def actionEdit_Custom_Model(self):
585        """
586        """
587        print("actionEdit_Custom_Model TRIGGERED")
588        pass
589
590    #============ ANALYSIS =================
591    def actionFitting(self):
592        """
593        """
594        print("actionFitting TRIGGERED")
595        pass
596
597    def actionInversion(self):
598        """
599        """
600        print("actionInversion TRIGGERED")
601        pass
602
603    def actionInvariant(self):
604        """
605        """
606        print("actionInvariant TRIGGERED")
607        pass
608
609    #============ WINDOW =================
610    def actionCascade(self):
611        """
612        Arranges all the child windows in a cascade pattern.
613        """
614        self._workspace.workspace.cascade()
615
616    def actionTile(self):
617        """
618        Tile workspace windows
619        """
620        self._workspace.workspace.tile()
621
622    def actionArrange_Icons(self):
623        """
624        Arranges all iconified windows at the bottom of the workspace
625        """
626        self._workspace.workspace.arrangeIcons()
627
628    def actionNext(self):
629        """
630        Gives the input focus to the next window in the list of child windows.
631        """
632        self._workspace.workspace.activateNextWindow()
633
634    def actionPrevious(self):
635        """
636        Gives the input focus to the previous window in the list of child windows.
637        """
638        self._workspace.workspace.activatePreviousWindow()
639
640    #============ HELP =================
641    def actionDocumentation(self):
642        """
643        Display the documentation
644
645        TODO: use QNetworkAccessManager to assure _helpLocation is valid
646        """
647        self._helpView.load(QtCore.QUrl(self._helpLocation))
648        self._helpView.show()
649
650    def actionTutorial(self):
651        """
652        Open the tutorial PDF file with default PDF renderer
653        """
654        # Not terribly safe here. Shell injection warning.
655        # isfile() helps but this probably needs a better solution.
656        if os.path.isfile(self._tutorialLocation):
657            result = subprocess.Popen([self._tutorialLocation], shell=True)
658
659    def actionAcknowledge(self):
660        """
661        Open the Acknowledgements widget
662        """
663        self.ackWidget.show()
664
665    def actionAbout(self):
666        """
667        Open the About box
668        """
669        # Update the about box with current version and stuff
670
671        # TODO: proper sizing
672        self.aboutWidget.show()
673
674    def actionCheck_for_update(self):
675        """
676        Menu Help/Check for Update
677        """
678        self.checkUpdate()
679
680        pass
681
Note: See TracBrowser for help on using the repository browser.