source: sasview/src/sas/qtgui/GuiManager.py @ 9e426c1

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

More main window items, system close, update checker, doc viewer etc.

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