Changeset 9e426c1 in sasview for src/sas/qtgui


Ignore:
Timestamp:
Jun 21, 2016 8:07:33 AM (9 years ago)
Author:
Piotr Rozyczko <piotr.rozyczko@…>
Branches:
ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc
Children:
f82ab8c
Parents:
1042dba
Message:

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

Location:
src/sas/qtgui
Files:
1 added
1 deleted
6 edited

Legend:

Unmodified
Added
Removed
  • src/sas/qtgui/DataExplorer.py

    r1042dba r9e426c1  
    8585        """ 
    8686        dir = QtGui.QFileDialog.getExistingDirectory(self, "Choose a directory", "", 
    87               QtGui.QFileDialog.ShowDirsOnly) 
     87              QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog) 
    8888        if dir is None: 
    8989            return 
     
    171171        """ 
    172172        Create a new matplotlib chart from selected data 
     173 
     174        TODO: Add 2D-functionality 
    173175        """ 
    174176 
     
    192194        # Location is automatically saved - no need to keep track of the last dir 
    193195        # But only with Qt built-in dialog (non-platform native) 
    194         paths = QtGui.QFileDialog.getOpenFileName(self, "Choose a file", "", 
     196        paths = QtGui.QFileDialog.getOpenFileNames(self, "Choose a file", "", 
    195197                wlist, None, QtGui.QFileDialog.DontUseNativeDialog) 
    196198        if paths is None: 
    197199            return 
    198200 
     201        if type(paths) == QtCore.QStringList: 
     202            paths = [str(f) for f in paths] 
     203 
    199204        if paths.__class__.__name__ != "list": 
    200205            paths = [paths] 
    201206 
    202         path_str=[] 
    203         for path in paths: 
    204             if str(path): 
    205                 path_str.append(str(path)) 
    206  
    207         return path_str 
     207        return paths 
    208208 
    209209    def readData(self, path): 
     
    402402        Post message to status bar and update the data manager 
    403403        """ 
     404        # Don't show "empty" rows with data objects 
     405        self.proxy.setFilterRegExp(r"[^()]") 
     406 
     407        # Reset the model so the view gets updated. 
    404408        self.model.reset() 
    405         assert(type(output), tuple) 
     409        assert type(output)== tuple 
    406410 
    407411        output_data = output[0] 
     
    433437        checkbox_item.setText(os.path.basename(p_file)) 
    434438 
    435         # Add "Info" item 
    436         # info_item = QtGui.QStandardItem("Info") 
    437  
    438439        # Add the actual Data1D/Data2D object 
    439440        object_item = QtGui.QStandardItem() 
     
    443444 
    444445        # Add rows for display in the view 
    445         # self.addExtraRows(info_item, data) 
    446446        info_item = infoFromData(data) 
    447447 
     
    452452        self.model.appendRow(checkbox_item) 
    453453         
    454         # Don't show "empty" rows with data objects 
    455         self.proxy.setFilterRegExp(r"[^()]") 
    456  
    457454    def updateModelFromPerspective(self, model_item): 
    458455        """ 
  • src/sas/qtgui/GuiManager.py

    r1042dba r9e426c1  
    11import sys 
     2import subprocess 
     3import logging 
     4import json 
     5import webbrowser 
    26 
    37from PyQt4 import QtCore 
     
    913# General SAS imports 
    1014from sas.sasgui.guiframe.data_manager import DataManager 
     15from sas.sasgui.guiframe.proxy import Connection 
     16 
    1117import LocalConfig 
    1218from GuiUtils import * 
     19from UI.AcknowledgementsUI import Acknowledgements 
    1320 
    1421# Perspectives 
     
    2128    Main SasView window functionality 
    2229    """ 
     30    HELP_DIRECTORY_LOCATION="html" 
     31 
    2332    def __init__(self, mainWindow=None, reactor=None, parent=None): 
    2433        """ 
     
    5665        self.dockedFilesWidget.setWidget(self.filesWidget) 
    5766        self._workspace.addDockWidget(QtCore.Qt.DockWidgetArea(1), self.dockedFilesWidget) 
     67 
     68        self.ackWidget = Acknowledgements() 
     69 
    5870        # Disable the close button (?) 
     71 
    5972        # Show the Welcome panel 
    6073        self.welcomePanel = WelcomePanel() 
     
    6376        # Current help file 
    6477        self._helpView = QtWebKit.QWebView() 
    65         self._helpLocation = "html/index.html" 
     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") 
    6685 
    6786        #========================================================== 
     
    7796        """ 
    7897        """ 
    79         print("FILE %s "%data) 
    8098        pass 
    8199     
     
    161179            logging.info(msg) 
    162180 
     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 
    163265    def addCallbacks(self): 
    164266        """ 
     267        Method defining all signal connections for the gui manager 
    165268        """ 
    166269        self.communicate = Communicate() 
     
    234337    def actionLoadData(self): 
    235338        """ 
    236         Load file from Data Explorer 
     339        Menu File/Load Data File(s) 
    237340        """ 
    238341        self.filesWidget.loadFile() 
     
    240343    def actionLoad_Data_Folder(self): 
    241344        """ 
     345        Menu File/Load Data Folder 
    242346        """ 
    243347        self.filesWidget.loadFolder() 
     
    272376        Close the reactor, exit the application. 
    273377        """ 
    274         # display messagebox 
    275         quit_msg = "Are you sure you want to exit the application?" 
    276         reply = QtGui.QMessageBox.question(self._parent, 'Warning', quit_msg, 
    277                 QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) 
    278  
    279         if reply == QtGui.QMessageBox.No: 
    280             return 
    281  
    282         # exit if yes 
    283         reactor.callFromThread(reactor.stop) 
    284         sys.exit() 
     378        self.quitApplication() 
    285379 
    286380    #============ EDIT ================= 
     
    509603    def actionDocumentation(self): 
    510604        """ 
     605        Display the documentation 
     606 
     607        TODO: use QNetworkAccessManager to assure _helpLocation is valid 
    511608        """ 
    512609        self._helpView.load(QtCore.QUrl(self._helpLocation)) 
     
    515612    def actionTutorial(self): 
    516613        """ 
    517         """ 
    518         print("actionTutorial TRIGGERED") 
    519         pass 
     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) 
    520620 
    521621    def actionAcknowledge(self): 
    522622        """ 
    523         """ 
    524         print("actionAcknowledge TRIGGERED") 
    525         pass 
     623        Open the Acknowledgements widget 
     624        """ 
     625        self.ackWidget.show() 
    526626 
    527627    def actionAbout(self): 
    528628        """ 
    529         """ 
     629        Open the About box 
     630        """ 
     631         
    530632        print("actionAbout TRIGGERED") 
    531633        pass 
     
    533635    def actionCheck_for_update(self): 
    534636        """ 
    535         """ 
    536         print("actionCheck_for_update TRIGGERED") 
    537         pass 
    538  
     637        Menu Help/Check for Update 
     638        """ 
     639        self.checkUpdate() 
     640 
     641        pass 
     642 
  • src/sas/qtgui/MainWindow.py

    rf721030 r9e426c1  
    2121        # Create the gui manager 
    2222        from GuiManager import GuiManager 
    23         guiManager = GuiManager(self, reactor, self) 
     23        self.guiManager = GuiManager(self, reactor, self) 
    2424 
     25    def closeEvent(self, event): 
     26        from twisted.internet import reactor 
     27        reactor.stop 
     28        event.accept() 
     29        sys.exit() 
    2530         
    2631def SplashScreen(): 
     
    6065    reactor.run() 
    6166 
    62     # TODO : in the VS debugger, the Qt loop doesn't seem to end - investigate 
  • src/sas/qtgui/UnitTesting/DataExplorerTest.py

    r1042dba r9e426c1  
    7272        loadButton = self.form.cmdLoad 
    7373 
    74         # Mock the system file open method 
    75         QtGui.QFileDialog.getOpenFileName = MagicMock(return_value=None) 
     74        filename = "cyl_400_20.txt" 
     75        # Initialize signal spy instances 
     76        spy_file_read = QtSignalSpy(self.form, self.form.communicate.fileReadSignal) 
     77 
     78        # Return no files. 
     79        QtGui.QFileDialog.getOpenFileNames = MagicMock(return_value=None) 
    7680 
    7781        # Click on the Load button 
     
    7983 
    8084        # Test the getOpenFileName() dialog called once 
    81         self.assertTrue(QtGui.QFileDialog.getOpenFileName.called) 
    82         QtGui.QFileDialog.getOpenFileName.assert_called_once() 
     85        self.assertTrue(QtGui.QFileDialog.getOpenFileNames.called) 
     86        QtGui.QFileDialog.getOpenFileNames.assert_called_once() 
     87 
     88        # Make sure the signal has not been emitted 
     89        self.assertEqual(spy_file_read.count(), 0) 
     90 
     91        # Now, return a single file 
     92        QtGui.QFileDialog.getOpenFileNames = MagicMock(return_value=filename) 
     93         
     94        # Click on the Load button 
     95        QTest.mouseClick(loadButton, Qt.LeftButton) 
     96 
     97        # Test the getOpenFileName() dialog called once 
     98        self.assertTrue(QtGui.QFileDialog.getOpenFileNames.called) 
     99        QtGui.QFileDialog.getOpenFileNames.assert_called_once() 
     100 
     101        # Expected one spy instance 
     102        self.assertEqual(spy_file_read.count(), 1) 
     103        self.assertIn(filename, str(spy_file_read.called()[0]['args'][0])) 
    83104 
    84105    def testDeleteButton(self): 
     
    131152        QTest.mouseClick(deleteButton, Qt.LeftButton) 
    132153 
    133  
    134154    def testSendToButton(self): 
    135155        """ 
     
    166186        # Assure the message box popped up 
    167187        QtGui.QMessageBox.assert_called_once() 
    168  
    169188 
    170189    def testDataSelection(self): 
     
    272291        Test the callback method updating the data object 
    273292        """ 
    274  
    275293        message="Loading Data Complete" 
    276294        data_dict = {"a1":Data1D()} 
  • src/sas/qtgui/UnitTesting/GuiManagerTest.py

    r5032ea68 r9e426c1  
    11import sys 
     2import subprocess 
    23import unittest 
     4import webbrowser 
     5import logging 
    36 
    47from PyQt4.QtGui import * 
    58from PyQt4.QtTest import QTest 
    69from PyQt4.QtCore import * 
     10from PyQt4.QtWebKit import * 
    711from mock import MagicMock 
    812 
     
    1014from GuiManager import GuiManager 
    1115from UI.MainWindowUI import MainWindow 
     16from UnitTesting.TestUtils import QtSignalSpy 
    1217 
    1318app = QApplication(sys.argv) 
     
    5661        pass 
    5762 
     63    def testQuitApplication(self): 
     64        """ 
     65        Test that the custom exit method is called on shutdown 
     66        """ 
     67        # Must mask sys.exit, otherwise the whole testing process stops. 
     68        sys.exit = MagicMock() 
     69 
     70        # Say No to the close dialog 
     71        QMessageBox.question = MagicMock(return_value=QMessageBox.No) 
     72 
     73        # Open, then close the manager 
     74        self.manager.quitApplication() 
     75 
     76        # See that the MessageBox method got called 
     77        self.assertTrue(QMessageBox.question.called) 
     78        # sys.exit() not called this time 
     79        self.assertFalse(sys.exit.called) 
     80 
     81        # Say Yes to the close dialog 
     82        QMessageBox.question = MagicMock(return_value=QMessageBox.Yes) 
     83 
     84        # Open, then close the manager 
     85        self.manager.quitApplication() 
     86 
     87        # See that the MessageBox method got called 
     88        self.assertTrue(QMessageBox.question.called) 
     89        # Also, sys.exit() called 
     90        self.assertTrue(sys.exit.called) 
     91 
     92    def testCheckUpdate(self): 
     93        """ 
     94        Tests the SasView website version polling 
     95        """ 
     96        self.manager.processVersion = MagicMock() 
     97        version = {'update_url'  : 'http://www.sasview.org/sasview.latestversion',  
     98                   'version'     : '3.1.2', 
     99                   'download_url': 'https://github.com/SasView/sasview/releases'} 
     100        self.manager.checkUpdate() 
     101 
     102        self.manager.processVersion.assert_called_with(version) 
     103 
     104        pass 
     105 
     106    def testProcessVersion(self): 
     107        """ 
     108        Tests the version checker logic 
     109        """ 
     110        # 1. version = 0.0.0 
     111        version_info = {u'version' : u'0.0.0'} 
     112        spy_status_update = QtSignalSpy(self.manager, self.manager.communicate.statusBarUpdateSignal) 
     113 
     114        self.manager.processVersion(version_info) 
     115 
     116        self.assertEqual(spy_status_update.count(), 1) 
     117        message = 'Could not connect to the application server. Please try again later.' 
     118        self.assertIn(message, str(spy_status_update.signal(index=0))) 
     119 
     120        # 2. version < LocalConfig.__version__ 
     121        version_info = {u'version' : u'0.0.1'} 
     122        spy_status_update = QtSignalSpy(self.manager, self.manager.communicate.statusBarUpdateSignal) 
     123 
     124        self.manager.processVersion(version_info) 
     125 
     126        self.assertEqual(spy_status_update.count(), 1) 
     127        message = 'You have the latest version of SasView' 
     128        self.assertIn(message, str(spy_status_update.signal(index=0))) 
     129 
     130        # 3. version > LocalConfig.__version__ 
     131        version_info = {u'version' : u'999.0.0'} 
     132        spy_status_update = QtSignalSpy(self.manager, self.manager.communicate.statusBarUpdateSignal) 
     133        webbrowser.open = MagicMock() 
     134 
     135        self.manager.processVersion(version_info) 
     136 
     137        self.assertEqual(spy_status_update.count(), 1) 
     138        message = 'Version 999.0.0 is available!' 
     139        self.assertIn(message, str(spy_status_update.signal(index=0))) 
     140 
     141        webbrowser.open.assert_called_with("https://github.com/SasView/sasview/releases") 
     142 
     143        # 4. version > LocalConfig.__version__ and standalone 
     144        version_info = {u'version' : u'999.0.0'} 
     145        spy_status_update = QtSignalSpy(self.manager, self.manager.communicate.statusBarUpdateSignal) 
     146        webbrowser.open = MagicMock() 
     147 
     148        self.manager.processVersion(version_info, standalone=True) 
     149 
     150        self.assertEqual(spy_status_update.count(), 1) 
     151        message = 'See the help menu to download it' 
     152        self.assertIn(message, str(spy_status_update.signal(index=0))) 
     153 
     154        self.assertFalse(webbrowser.open.called) 
     155 
     156        # 5. couldn't load version 
     157        version_info = {} 
     158        logging.error = MagicMock() 
     159        spy_status_update = QtSignalSpy(self.manager, self.manager.communicate.statusBarUpdateSignal) 
     160 
     161        self.manager.processVersion(version_info) 
     162 
     163        # Retrieve and compare arguments of the mocked call 
     164        message = "guiframe: could not get latest application version number" 
     165        args, _ = logging.error.call_args 
     166        self.assertIn(message, args[0]) 
     167 
     168        # Check the signal message 
     169        message = 'Could not connect to the application server.' 
     170        self.assertIn(message, str(spy_status_update.signal(index=0))) 
     171 
    58172    def testActions(self): 
    59173        """ 
     
    66180        """ 
    67181        # Mock the system file open method 
    68         QFileDialog.getOpenFileName = MagicMock(return_value=None) 
     182        QFileDialog.getOpenFileNames = MagicMock(return_value=None) 
    69183 
    70184        # invoke the action 
     185        self.manager.actionLoadData() 
    71186 
    72187        # Test the getOpenFileName() dialog called once 
    73         #self.assertTrue(QtGui.QFileDialog.getOpenFileName.called) 
    74         #QtGui.QFileDialog.getOpenFileName.assert_called_once() 
     188        self.assertTrue(QFileDialog.getOpenFileNames.called) 
    75189         
    76  
    77     # test each action separately 
     190    def testActionDocumentation(self): 
     191        """ 
     192        Menu Help/Documentation 
     193        """ 
     194        #Mock the QWebView method 
     195        QWebView.show = MagicMock() 
     196 
     197        # Assure the filename is correct 
     198        self.assertIn("index.html", self.manager._helpLocation) 
     199 
     200        # Invoke the action 
     201        self.manager.actionDocumentation() 
     202 
     203        # Check if show() got called 
     204        self.assertTrue(QWebView.show.called) 
     205 
     206    def testActionTutorial(self): 
     207        """ 
     208        Menu Help/Tutorial 
     209        """ 
     210        # Mock subprocess.Popen 
     211        subprocess.Popen = MagicMock() 
     212 
     213        tested_location = self.manager._tutorialLocation 
     214 
     215        # Assure the filename is correct 
     216        self.assertIn("Tutorial.pdf", tested_location) 
     217 
     218        # Invoke the action 
     219        self.manager.actionTutorial() 
     220 
     221        # Check if popen() got called 
     222        self.assertTrue(subprocess.Popen.called) 
     223 
     224        #Check the popen() call arguments 
     225        subprocess.Popen.assert_called_with([tested_location], shell=True) 
     226 
     227    def testActionAcknowledge(self): 
     228        """ 
     229        Menu Help/Acknowledge 
     230        """ 
     231        pass 
     232 
     233    def testActionCheck_for_update(self): 
     234        """ 
     235        Menu Help/Check for update 
     236        """ 
     237        # Just make sure checkUpdate is called. 
     238        self.manager.checkUpdate = MagicMock() 
     239 
     240        self.manager.actionCheck_for_update() 
     241 
     242        self.assertTrue(self.manager.checkUpdate.called) 
     243              
    78244        
    79245if __name__ == "__main__": 
  • src/sas/qtgui/UnitTesting/MainWindowTest.py

    rf721030 r9e426c1  
    22import unittest 
    33 
    4 from PyQt4.QtGui import * 
    5 from PyQt4.QtTest import QTest 
    6 from PyQt4.QtCore import * 
     4from PyQt4 import QtGui 
     5from PyQt4 import QtTest 
     6from PyQt4 import QtCore 
    77from mock import MagicMock 
    88 
     
    1111from MainWindow import SplashScreen 
    1212 
    13 app = QApplication(sys.argv) 
     13app = QtGui.QApplication(sys.argv) 
    1414 
    1515class MainWindowTest(unittest.TestCase): 
    16     '''Test the Main Window GUI''' 
     16    """Test the Main Window GUI""" 
    1717    def setUp(self): 
    18         '''Create the GUI''' 
     18        """Create the GUI""" 
    1919 
    2020        self.widget = MainSasViewWindow(None) 
    2121 
    2222    def tearDown(self): 
    23         '''Destroy the GUI''' 
    24         self.widget.close() 
     23        """Destroy the GUI""" 
    2524        self.widget = None 
    2625 
    2726    def testDefaults(self): 
    28         '''Test the GUI in its default state''' 
    29         self.assertIsInstance(self.widget, QMainWindow) 
    30         self.assertIsInstance(self.widget.centralWidget(), QWorkspace) 
     27        """Test the GUI in its default state""" 
     28        self.assertIsInstance(self.widget, QtGui.QMainWindow) 
     29        self.assertIsInstance(self.widget.centralWidget(), QtGui.QWorkspace) 
    3130         
    3231    def testSplashScreen(self): 
     32        """ Test the splash screen """ 
     33        splash = SplashScreen() 
     34        self.assertIsInstance(splash, QtGui.QSplashScreen) 
     35 
     36    def testExit(self): 
    3337        """ 
     38        Test that the custom exit method is called on shutdown 
    3439        """ 
    35         splash = SplashScreen() 
    36         self.assertIsInstance(splash, QSplashScreen) 
     40        # Must mask sys.exit, otherwise the whole testing process stops. 
     41        sys.exit = MagicMock() 
     42        QtGui.QMessageBox.question = MagicMock(return_value=QtGui.QMessageBox.Yes) 
     43 
     44        # Open, then close the main window 
     45        tmp_main = MainSasViewWindow(None) 
     46        tmp_main.close() 
     47 
     48        # See that the MessageBox method got called 
     49        self.assertTrue(QtGui.QMessageBox.question.called) 
    3750        
    3851if __name__ == "__main__": 
Note: See TracChangeset for help on using the changeset viewer.