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

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

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