source: sasview/src/sas/qtgui/GuiManager.py @ 7451b88

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 7451b88 was 7451b88, checked in by davidm, 8 years ago

cancelling the Quit dialog will now prevent the MainWindow? from closing

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