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

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

More dialogs, drag and drop onto File Load

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