source: trunk/Puzzlebox/Synapse/Interface.py @ 309

Last change on this file since 309 was 309, checked in by sc, 9 years ago

Interface:

  • comboBoxEEGHeadsetModel disabled when connected

Interface_Design.py:

  • Property svn:executable set to *
File size: 38.7 KB
Line 
1# -*- coding: utf-8 -*-
2
3# Copyright Puzzlebox Productions, LLC (2010-2011)
4#
5# This code is released under the GNU Pulic License (GPL) version 2
6# For more information please refer to http://www.gnu.org/copyleft/gpl.html
7
8# Old Class Names:
9#       puzzlebox_synapse_interface = QtUI
10
11__changelog__ = """\
12Last Update: 2011.12.04
13"""
14
15__todo__ = """
16- update configuration.ini file with settings entered into interface
17"""
18
19### IMPORTS ###
20import os, sys, time
21import simplejson as json
22
23
24import Configuration as configuration
25
26if configuration.ENABLE_PYSIDE:
27        try:
28                import PySide as PyQt4
29                from PySide import QtCore, QtGui, QtNetwork
30        except Exception, e:
31                print "ERROR: Exception importing PySide:",
32                print e
33                configuration.ENABLE_PYSIDE = False
34        else:
35                print "INFO: [Synapse:Interface] Using PySide module"
36
37if not configuration.ENABLE_PYSIDE:
38        print "INFO: [Synapse:Interface] Using PyQt4 module"
39        from PyQt4 import QtCore, QtGui, QtNetwork
40
41
42try:
43        from Interface_Plot import *
44        MATPLOTLIB_AVAILABLE = True
45except Exception, e:
46        print "ERROR: Exception importing Interface_Plot:",
47        print e
48        MATPLOTLIB_AVAILABLE = False
49
50
51if (sys.platform != 'win32'):
52        import bluetooth
53        DEFAULT_IMAGE_PATH = '/usr/share/puzzlebox_synapse/images'
54else:
55        import _winreg as winreg
56        import itertools
57        import re
58        import serial
59        DEFAULT_IMAGE_PATH = 'images'
60
61#try:
62        #import PySide as PyQt4
63        #from PySide import QtCore, QtGui
64#except:
65        #print "Using PyQt4 module"
66        #from PyQt4 import QtCore, QtGui
67#else:
68        #print "Using PySide module"
69
70try:
71        import cPickle as pickle
72except:
73        import pickle
74
75# from puzzlebox_synapse_interface_design import Ui_Form
76from Interface_Design import Ui_Form as Design
77
78#import Configuration as configuration
79import Server as synapse_server
80import Client as thinkgear_client
81#import puzzlebox_logger
82
83### GLOBALS ###
84
85DEBUG = configuration.DEBUG
86
87THINKGEAR_SERVER_HOST = configuration.THINKGEAR_SERVER_HOST
88THINKGEAR_SERVER_PORT = configuration.THINKGEAR_SERVER_PORT
89
90THINKGEAR_EEG_POWER_BAND_ORDER = configuration.THINKGEAR_EEG_POWER_BAND_ORDER
91
92THINKGEAR_EMULATION_MAX_ESENSE_VALUE = \
93        configuration.THINKGEAR_EMULATION_MAX_ESENSE_VALUE
94THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE = \
95        configuration.THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE
96
97PATH_TO_HCITOOL = '/usr/bin/hcitool'
98
99#UPDATE_INTERFACE_VIA_TIMER = True # Alternative is to establish a
100                                  ## ThinkGear Connect client which
101                                  ## updates the interface on demand
102                                  ## as packets are received
103
104UPDATE_INTERFACE_VIA_TIMER = False
105
106#INTERFACE_UPDATE_FREQUENCY = (1 / 512) * 1000 # ms (512 Hz)
107INTERFACE_UPDATE_FREQUENCY = 1000 # ms
108
109INTERFACE_RAW_EEG_UPDATE_FREQUENCY = 512
110
111PACKET_MINIMUM_TIME_DIFFERENCE_THRESHOLD = 0.75
112
113### CLASSES ###
114
115class QtUI(QtGui.QWidget, Design):
116       
117        def __init__(self, log, server=None, DEBUG=DEBUG, parent = None):
118               
119                self.log = log
120                self.DEBUG = DEBUG
121                self.parent=parent
122               
123                if self.parent == None:
124                        QtGui.QWidget.__init__(self, parent)
125                        self.setupUi(self)
126               
127                        self.configureSettings()
128                        self.connectWidgets()
129               
130                self.name = "Synapse Interface"
131               
132                self.thinkGearConnectServer = None
133                self.thinkgearConnectClient = None
134               
135                self.maxEEGPower = THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE
136               
137                self.debug_console_buffer = ''
138               
139                self.packets = {}
140                self.packets['rawEeg'] = []
141                self.packets['signals'] = []
142               
143                self.customDataHeaders = []
144               
145                if UPDATE_INTERFACE_VIA_TIMER:
146                        self.updateInterfaceTimer = QtCore.QTimer()
147                        QtCore.QObject.connect(self.updateInterfaceTimer, \
148                                                    QtCore.SIGNAL("timeout()"), \
149                                                    self.updateInterface)
150               
151                if (sys.platform == 'win32'):
152                        self.homepath = os.path.join( \
153                           os.environ['HOMEDRIVE'], \
154                           os.environ['HOMEPATH'], \
155                           'Desktop')
156                else:
157                        self.homepath = os.environ['HOME']
158               
159                if not os.path.exists(self.homepath):
160                        self.homepath = os.getcwd()
161       
162       
163        ##################################################################
164       
165        def configureSettings(self):
166               
167                # Synapse Interface
168                image_path = "puzzlebox.ico"
169                if not os.path.exists(image_path):
170                        image_path = os.path.join(DEFAULT_IMAGE_PATH, image_path)
171               
172                if os.path.exists(image_path):
173                        icon = QtGui.QIcon()
174                        icon.addPixmap(QtGui.QPixmap(image_path), \
175                                            QtGui.QIcon.Normal, \
176                                            QtGui.QIcon.Off)
177                        self.setWindowIcon(icon)
178               
179                image_path = "puzzlebox_logo.png"
180                if not os.path.exists(image_path):
181                        image_path = os.path.join(DEFAULT_IMAGE_PATH, image_path)
182                if os.path.exists(image_path):
183                        self.labelPuzzleboxIcon.setPixmap(QtGui.QPixmap(image_path))
184               
185               
186                if configuration.INTERFACE_TAB_POSITION == 'South':
187                        self.tabWidget.setTabPosition(QtGui.QTabWidget.South)
188                else:
189                        self.tabWidget.setTabPosition(QtGui.QTabWidget.North)
190               
191               
192                # ThinkGear Device
193                self.updateThinkGearDevices()
194               
195               
196                # ThinkGear Connect Server
197                self.textLabelBluetoothStatus.setText("Status: Disconnected")
198               
199                # Display Host for ThinkGear Connect Socket Server
200                self.lineEditThinkGearHost.setText(THINKGEAR_SERVER_HOST)
201               
202                # Display Port for ThinkGear Connect Socket Server
203                self.lineEditThinkGearPort.setText('%i' % THINKGEAR_SERVER_PORT)
204               
205               
206                # ThinkgGear Progress Bars
207                self.progressBarEEGDelta.setMaximum(THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE)
208                self.progressBarEEGTheta.setMaximum(THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE)
209                self.progressBarEEGLowAlpha.setMaximum(THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE)
210                self.progressBarEEGHighAlpha.setMaximum(THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE)
211                self.progressBarEEGLowBeta.setMaximum(THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE)
212                self.progressBarEEGHighBeta.setMaximum(THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE)
213                self.progressBarEEGLowGamma.setMaximum(THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE)
214                self.progressBarEEGMidGamma.setMaximum(THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE)
215               
216                self.progressBarAttention.setMaximum(THINKGEAR_EMULATION_MAX_ESENSE_VALUE)
217                self.progressBarMeditation.setMaximum(THINKGEAR_EMULATION_MAX_ESENSE_VALUE)
218               
219                self.progressBarSignalContactQuality.setMaximum(200)
220               
221               
222                if MATPLOTLIB_AVAILABLE:
223                        self.rawEEGMatplot = rawEEGMatplotlibCanvas( \
224                                                self.tabEEGSignals, \
225                                                width=8, \
226                                                height=4, \
227                                                dpi=100, \
228                                                title='Raw EEG Waves')
229                        self.chartEEGMatplot = chartEEGMatplotlibCanvas( \
230                                                self.tabCharts, \
231                                                width=8, \
232                                                height=4, \
233                                                dpi=100, \
234                                                title='EEG Brain Signals')
235               
236                else:
237                        self.tabWidget.removeTab(self.tabWidget.indexOf(self.tabEEGSignals))
238                        self.tabWidget.removeTab(self.tabWidget.indexOf(self.tabCharts))
239       
240       
241        ##################################################################
242       
243        def connectWidgets(self):
244               
245                self.connect(self.pushButtonBluetoothSearch, \
246                                  QtCore.SIGNAL("clicked()"), \
247                                  self.updateThinkGearDevices)
248               
249                self.connect(self.pushButtonBluetoothConnect, \
250                                  QtCore.SIGNAL("clicked()"), \
251                                  self.connectToThinkGearDevice)
252               
253                self.connect(self.pushButtonThinkGearConnect, \
254                                  QtCore.SIGNAL("clicked()"), \
255                                  self.startThinkGearConnectServer)
256               
257                self.connect(self.pushButtonSave, \
258                                  QtCore.SIGNAL("clicked()"), \
259                                  self.saveData)
260               
261                self.connect(self.pushButtonExport, \
262                                  QtCore.SIGNAL("clicked()"), \
263                                  self.exportData)
264               
265                self.connect(self.pushButtonReset, \
266                                  QtCore.SIGNAL("clicked()"), \
267                                  self.resetData)
268       
269       
270        ##################################################################
271       
272        def connectToThinkGearDevice(self):
273               
274                device_selection = self.comboBoxDeviceSelect.currentText()
275               
276                self.disconnect(self.pushButtonBluetoothConnect, \
277                                     QtCore.SIGNAL("clicked()"), \
278                                     self.connectToThinkGearDevice)
279               
280                self.connect(self.pushButtonBluetoothConnect, \
281                                  QtCore.SIGNAL("clicked()"), \
282                                  self.disconnectFromThinkGearDevice)
283               
284                self.textLabelBluetoothStatus.setText("Status: Connected")
285               
286                self.pushButtonBluetoothSearch.setEnabled(False)
287               
288                self.pushButtonBluetoothConnect.setText('Disconnect')
289                self.pushButtonBluetoothConnect.setChecked(True)
290               
291                self.comboBoxDeviceSelect.setEnabled(False)
292                self.comboBoxEEGHeadsetModel.setEnabled(False)
293       
294       
295        ##################################################################
296       
297        def disconnectFromThinkGearDevice(self):
298               
299                self.disconnect(self.pushButtonBluetoothConnect, \
300                                     QtCore.SIGNAL("clicked()"), \
301                                     self.disconnectFromThinkGearDevice)
302               
303                self.connect(self.pushButtonBluetoothConnect, \
304                                  QtCore.SIGNAL("clicked()"), \
305                                  self.connectToThinkGearDevice)
306               
307                self.textLabelBluetoothStatus.setText("Status: Disconnected")
308               
309                self.pushButtonBluetoothSearch.setEnabled(True)
310               
311                self.pushButtonBluetoothConnect.setText('Connect')
312                self.pushButtonBluetoothConnect.setChecked(False)
313               
314                self.comboBoxDeviceSelect.setEnabled(True)
315                self.comboBoxEEGHeadsetModel.setEnabled(True)
316               
317               
318                self.progressBarEEGDelta.setValue(0)
319                self.progressBarEEGTheta.setValue(0)
320                self.progressBarEEGLowAlpha.setValue(0)
321                self.progressBarEEGHighAlpha.setValue(0)
322                self.progressBarEEGLowBeta.setValue(0)
323                self.progressBarEEGHighBeta.setValue(0)
324                self.progressBarEEGLowGamma.setValue(0)
325                self.progressBarEEGMidGamma.setValue(0)
326               
327                self.progressBarAttention.setValue(0)
328                self.progressBarMeditation.setValue(0)
329               
330                self.progressBarSignalContactQuality.setValue(0)
331               
332                self.maxEEGPower = THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE
333               
334                # In case the user connects to a MindSet, then disconnects
335                # and re-connects to a MindSet Emulator,
336                # we need to reset the max power values
337                self.progressBarEEGDelta.setMaximum(self.maxEEGPower)
338                self.progressBarEEGTheta.setMaximum(self.maxEEGPower)
339                self.progressBarEEGLowAlpha.setMaximum(self.maxEEGPower)
340                self.progressBarEEGHighAlpha.setMaximum(self.maxEEGPower)
341                self.progressBarEEGLowBeta.setMaximum(self.maxEEGPower)
342                self.progressBarEEGHighBeta.setMaximum(self.maxEEGPower)
343                self.progressBarEEGLowGamma.setMaximum(self.maxEEGPower)
344                self.progressBarEEGMidGamma.setMaximum(self.maxEEGPower)
345       
346       
347        ##################################################################
348       
349        def startThinkGearConnectServer(self):
350               
351                # Ensure EEG device is connected first
352               
353                if not self.pushButtonBluetoothConnect.isChecked():
354                        self.connectToThinkGearDevice()
355               
356               
357                self.pushButtonBluetoothSearch.setEnabled(False)
358                self.pushButtonBluetoothConnect.setEnabled(False)
359               
360                server_interface = str(self.lineEditThinkGearHost.text())
361                server_port = int(self.lineEditThinkGearPort.text())
362                device_address = str(self.comboBoxDeviceSelect.currentText())
363                emulate_headset_data = (device_address == 'ThinkGear Emulator')
364               
365               
366                self.thinkGearConnectServer = \
367                        synapse_server.ThinkgearServer( \
368                                self.log, \
369                                server_interface=server_interface, \
370                                server_port=server_port, \
371                                device_address=device_address, \
372                                device_model=None, \
373                                emulate_headset_data=emulate_headset_data, \
374                                DEBUG=DEBUG, \
375                                parent=self)
376               
377                #self.connect(self.thinkGearConnectServer, \
378                             #QtCore.SIGNAL("sendPacket()"), \
379                             #self.thinkGearConnectServer.sendPacketQueue)
380               
381                self.thinkGearConnectServer.start()
382               
383               
384                if UPDATE_INTERFACE_VIA_TIMER:
385                        self.updateInterfaceTimer.start(INTERFACE_UPDATE_FREQUENCY)
386               
387                else:
388                        self.thinkgearConnectClient = \
389                                thinkgear_client.QtClient( \
390                                        self.log, \
391                                        server_host=server_interface, \
392                                        server_port=server_port, \
393                                        DEBUG=0, \
394                                        parent=self)
395                       
396                        self.thinkgearConnectClient.start()
397               
398               
399                self.disconnect(self.pushButtonThinkGearConnect, \
400                                     QtCore.SIGNAL("clicked()"), \
401                                     self.startThinkGearConnectServer)
402               
403                self.connect(self.pushButtonThinkGearConnect, \
404                                  QtCore.SIGNAL("clicked()"), \
405                                  self.stopThinkGearConnectServer)
406               
407                self.lineEditThinkGearHost.setEnabled(False)
408                self.lineEditThinkGearPort.setEnabled(False)
409               
410                self.pushButtonThinkGearConnect.setText('Stop')
411       
412       
413        ##################################################################
414       
415        def stopThinkGearConnectServer(self):
416               
417                if UPDATE_INTERFACE_VIA_TIMER:
418                        self.updateInterfaceTimer.stop()
419                else:
420                        try:
421                                self.thinkgearConnectClient.disconnectFromHost()
422                        except Exception, e:
423                                if self.DEBUG:
424                                        print "Call failed to self.thinkgearConnectClient.disconnectFromHost():",
425                                        print e
426                       
427                        try:
428                                self.thinkGearConnectServer.exitThread()
429                        except Exception, e:
430                                if self.DEBUG:
431                                        print "Call failed to self.thinkGearConnectServer.exitThread():",
432                                        print e
433               
434                self.disconnect(self.pushButtonThinkGearConnect, \
435                                QtCore.SIGNAL("clicked()"), \
436                                self.stopThinkGearConnectServer)
437               
438                self.connect(self.pushButtonThinkGearConnect, \
439                                  QtCore.SIGNAL("clicked()"), \
440                                  self.startThinkGearConnectServer)
441               
442                self.lineEditThinkGearHost.setEnabled(True)
443                self.lineEditThinkGearPort.setEnabled(True)
444               
445                self.pushButtonThinkGearConnect.setText('Start')
446               
447                #self.pushButtonBluetoothSearch.setEnabled(True)
448                self.pushButtonBluetoothConnect.setEnabled(True)
449               
450                self.pushButtonThinkGearConnect.setChecked(False)
451       
452       
453        ##################################################################
454       
455        def updateInterface(self):
456               
457                if not self.thinkGearConnectServer.emulate_headset_data:
458                        self.processPacketThinkGear( \
459                                self.thinkGearConnectServer.protocol.data_packet)
460       
461       
462        ##################################################################
463       
464        def processPacketThinkGear(self, packet):
465               
466                if self.DEBUG > 2:
467                        print packet
468               
469               
470                if ('rawEeg' in packet.keys()):
471                        self.packets['rawEeg'].append(packet['rawEeg'])
472                        value = packet['rawEeg']
473                        if MATPLOTLIB_AVAILABLE and \
474                                (self.tabWidget.currentIndex() == \
475                                 self.tabWidget.indexOf(self.tabEEGSignals)):
476                                self.rawEEGMatplot.update_figure(value)
477                        return
478                else:
479                        self.packets['signals'].append(packet)
480               
481               
482                if ('poorSignalLevel' in packet.keys()):
483                        value = 200 - packet['poorSignalLevel']
484                        self.progressBarSignalContactQuality.setValue(value)
485                        self.textEditDebugConsole.append("")
486                        try:
487                                (date, localtime) = self.parseTimeStamp(packet['timestamp'])
488                                self.textEditDebugConsole.append("Timestamp: %s %s" % (date, localtime))
489                        except:
490                                pass
491                        self.textEditDebugConsole.append("poorSignalLevel: %i" % \
492                                                         packet['poorSignalLevel'])
493               
494               
495                if ('eSense' in packet.keys()):
496                       
497                        if ('attention' in packet['eSense'].keys()):
498                                value = packet['eSense']['attention']
499                                self.progressBarAttention.setValue(value)
500                                self.textEditDebugConsole.append("eSense attention: %i" % value)
501                       
502                        if ('meditation' in packet['eSense'].keys()):
503                                value = packet['eSense']['meditation']
504                                self.progressBarMeditation.setValue(value)
505                                self.textEditDebugConsole.append("eSense meditation: %i" % value)
506                       
507                       
508                        if MATPLOTLIB_AVAILABLE:
509                                self.chartEEGMatplot.update_values('eSense', packet['eSense'])
510                                if (self.tabWidget.currentIndex() == \
511                                    self.tabWidget.indexOf(self.tabCharts)):
512                                        self.chartEEGMatplot.update_figure('eSense', packet['eSense'])
513               
514               
515                if ('eegPower' in packet.keys()):
516                       
517                        # If we are not emulating packets we'll set the maximum EEG Power value
518                        # threshold to the default (or maximum value found within this packet)
519                        if not self.thinkGearConnectServer.emulate_headset_data:
520                                self.maxEEGPower = THINKGEAR_EMULATION_MAX_EEG_POWER_VALUE
521                       
522                        for value in packet['eegPower'].keys():
523                                if packet['eegPower'][value] > self.maxEEGPower:
524                                        self.maxEEGPower = packet['eegPower'][value]
525                       
526                       
527                        if ('delta' in packet['eegPower'].keys()):
528                                value = packet['eegPower']['delta']
529                                self.progressBarEEGDelta.setMaximum(self.maxEEGPower)
530                                self.progressBarEEGDelta.setValue(value)
531                                self.textEditDebugConsole.append("delta: %i" % value)
532                       
533                        if ('theta' in packet['eegPower'].keys()):
534                                value = packet['eegPower']['theta']
535                                self.progressBarEEGTheta.setMaximum(self.maxEEGPower)
536                                self.progressBarEEGTheta.setValue(value)
537                                self.textEditDebugConsole.append("theta: %i" % value)
538                       
539                        if ('lowAlpha' in packet['eegPower'].keys()):
540                                value = packet['eegPower']['lowAlpha']
541                                self.progressBarEEGLowAlpha.setMaximum(self.maxEEGPower)
542                                self.progressBarEEGLowAlpha.setValue(value)
543                                self.textEditDebugConsole.append("lowAlpha: %i" % value)
544                       
545                        if ('highAlpha' in packet['eegPower'].keys()):
546                                value = packet['eegPower']['highAlpha']
547                                self.progressBarEEGHighAlpha.setMaximum(self.maxEEGPower)
548                                self.progressBarEEGHighAlpha.setValue(value)
549                                self.textEditDebugConsole.append("highAlpha: %i" % value)
550                       
551                        if ('lowBeta' in packet['eegPower'].keys()):
552                                value = packet['eegPower']['lowBeta']
553                                self.progressBarEEGLowBeta.setMaximum(self.maxEEGPower)
554                                self.progressBarEEGLowBeta.setValue(value)
555                                self.textEditDebugConsole.append("lowBeta: %i" % value)
556                       
557                        if ('highBeta' in packet['eegPower'].keys()):
558                                value = packet['eegPower']['highBeta']
559                                self.progressBarEEGHighBeta.setMaximum(self.maxEEGPower)
560                                self.progressBarEEGHighBeta.setValue(value)
561                                self.textEditDebugConsole.append("highBeta: %i" % value)
562                       
563                        if ('lowGamma' in packet['eegPower'].keys()):
564                                value = packet['eegPower']['lowGamma']
565                                self.progressBarEEGLowGamma.setMaximum(self.maxEEGPower)
566                                self.progressBarEEGLowGamma.setValue(value)
567                                self.textEditDebugConsole.append("lowGamma: %i" % value)
568                       
569                        if ('highGamma' in packet['eegPower'].keys()):
570                                value = packet['eegPower']['highGamma']
571                                self.progressBarEEGMidGamma.setMaximum(self.maxEEGPower)
572                                self.progressBarEEGMidGamma.setValue(value)
573                                self.textEditDebugConsole.append("highGamma: %i" % value)
574                       
575                       
576                        if MATPLOTLIB_AVAILABLE:
577                                self.chartEEGMatplot.update_values('eegPower', packet['eegPower'])
578                                if (self.tabWidget.currentIndex() == \
579                                    self.tabWidget.indexOf(self.tabCharts)):
580                                        self.chartEEGMatplot.update_figure('eegPower', packet['eegPower'])
581               
582               
583                if ((self.thinkGearConnectServer.protocol != None) and
584                    (self.tabWidget.currentIndex() == \
585                     self.tabWidget.indexOf(self.tabControlPanel))):
586                       
587                        self.updateProfileSessionStatus()
588       
589       
590        ##################################################################
591       
592        def updateProfileSessionStatus(self):
593               
594                session_time = self.calculateSessionTime()
595               
596                if self.parent == None:
597                        target = self
598                else:
599                        target = self.parent
600               
601                target.textLabelSessionTime.setText(session_time)
602               
603                target.textLabelPacketsReceived.setText( "%i" % \
604                        target.thinkGearConnectServer.protocol.packet_count)
605                target.textLabelPacketsDropped.setText( "%i" % \
606                        target.thinkGearConnectServer.protocol.bad_packets)
607       
608       
609        ##################################################################
610       
611        def calculateSessionTime(self):
612               
613                if self.parent == None:
614                        server = self.thinkGearConnectServer
615                else:
616                        server = self.parent.thinkGearConnectServer
617               
618                session_time = time.time() - \
619                        server.protocol.session_start_timestamp
620               
621                session_time = int(session_time)
622               
623                session_time = self.convert_seconds_to_datetime(session_time)
624               
625                return(session_time)
626       
627       
628        ##################################################################
629       
630        def enumerateSerialPorts(self):
631               
632                """ Uses the Win32 registry to return an
633                iterator of serial (COM) ports
634                existing on this computer.
635               
636                from http://eli.thegreenplace.net/2009/07/31/listing-all-serial-ports-on-windows-with-python/
637                """
638         
639                path = 'HARDWARE\\DEVICEMAP\\SERIALCOMM'
640                try:
641                        key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path)
642                except WindowsError:
643                        #raise IterationError
644                        return
645               
646                for i in itertools.count():
647                        try:
648                                val = winreg.EnumValue(key, i)
649                                yield str(val[1])
650                        except EnvironmentError:
651                                break
652       
653       
654        ##################################################################
655       
656        def fullPortName(self, portname):
657               
658                """ Given a port-name (of the form COM7,
659                COM12, CNCA0, etc.) returns a full
660                name suitable for opening with the
661                Serial class.
662                """
663               
664                m = re.match('^COM(\d+)$', portname)
665                if m and int(m.group(1)) < 10:
666                        return portname
667               
668                return '\\\\.\\' + portname
669       
670       
671        ##################################################################
672       
673        def searchForSerialDevices(self, devices=[]):
674               
675                if (sys.platform == 'win32'):
676                       
677                        for portname in self.enumerateSerialPorts():
678                               
679                                if portname not in devices:
680                                        #portname = self.fullPortName(portname)
681                                        devices.append(portname)
682               
683                else:
684                       
685                        if os.path.exists('/dev/ttyUSB0'):
686                                devices.append('/dev/ttyUSB0')
687                        if os.path.exists('/dev/ttyUSB1'):
688                                devices.append('/dev/ttyUSB1')
689                        if os.path.exists('/dev/ttyUSB2'):
690                                devices.append('/dev/ttyUSB2')
691                        if os.path.exists('/dev/ttyUSB3'):
692                                devices.append('/dev/ttyUSB3')
693                        if os.path.exists('/dev/ttyUSB4'):
694                                devices.append('/dev/ttyUSB4')
695                        if os.path.exists('/dev/ttyUSB5'):
696                                devices.append('/dev/ttyUSB5')
697                        if os.path.exists('/dev/ttyUSB6'):
698                                devices.append('/dev/ttyUSB6')
699                        if os.path.exists('/dev/ttyUSB7'):
700                                devices.append('/dev/ttyUSB7')
701                        if os.path.exists('/dev/ttyUSB8'):
702                                devices.append('/dev/ttyUSB8')
703                        if os.path.exists('/dev/ttyUSB9'):
704                                devices.append('/dev/ttyUSB9')
705                       
706                        if os.path.exists('/dev/ttyACM0'):
707                                devices.append('/dev/ttyACM0')
708                        if os.path.exists('/dev/ttyACM1'):
709                                devices.append('/dev/ttyACM1')
710                        if os.path.exists('/dev/ttyACM2'):
711                                devices.append('/dev/ttyACM2')
712                        if os.path.exists('/dev/ttyACM3'):
713                                devices.append('/dev/ttyACM3')
714                        if os.path.exists('/dev/ttyACM4'):
715                                devices.append('/dev/ttyACM4')
716               
717               
718                return(devices)
719       
720       
721        ##################################################################
722       
723        def hcitoolScanForRemoteDevices(self, thinkgear_devices=[]):
724               
725                bluetooth_devices = []
726               
727                #command = '%s scan 2> /dev/null' % PATH_TO_HCITOOL
728                command = '%s scan' % PATH_TO_HCITOOL
729               
730                if self.DEBUG > 1:
731                        print 'INFO: Calling "%s"' % command
732               
733                output = os.popen(command, 'r')
734               
735                for line in output.readlines():
736                        line = line.strip()
737                        if line == '' or line == 'Scanning ...':
738                                continue
739                        elif self.DEBUG > 1:
740                                print line
741                        try:
742                                address = line.split('\t')[0]
743                        except:
744                                pass
745                        else:
746                                bluetooth_devices.append(address)
747               
748               
749                for address in bluetooth_devices:
750                       
751                        command = '%s name %s' % (PATH_TO_HCITOOL, address)
752                       
753                        if self.DEBUG:
754                                print 'INFO: Calling "%s"' % command
755                       
756                        output = os.popen(command, 'r')
757                       
758                        for line in output.readlines():
759                                line = line.strip()
760                                if line == '':
761                                        continue
762                                elif self.DEBUG:
763                                        print '\t',
764                                        print line
765                               
766                                device_name = line.strip()
767                       
768                                if ((device_name == 'MindSet' or device_name == 'MindWave Mobile') and \
769                                        (address not in thinkgear_devices)):
770                                        thinkgear_devices.append(address)
771               
772               
773                return (thinkgear_devices)
774       
775       
776        ##################################################################
777       
778        def hcitoolGetActiveConnections(self, thinkgear_devices=[]):
779               
780                bluetooth_devices = []
781               
782                #command = '%s con 2> /dev/null' % PATH_TO_HCITOOL
783                command = '%s con' % PATH_TO_HCITOOL
784               
785                if self.DEBUG > 1:
786                        print 'INFO: Calling "%s"' % command
787               
788                output = os.popen(command, 'r')
789               
790                for line in output.readlines():
791                        line = line.strip()
792                        if line == '' or line == 'Connections:':
793                                continue
794                        elif self.DEBUG > 1:
795                                print line
796                        try:
797                                address = line.split(' ')[2]
798                        except:
799                                pass
800                        else:
801                                bluetooth_devices.append(address)
802               
803               
804                for address in bluetooth_devices:
805                       
806                        command = '%s name %s' % (PATH_TO_HCITOOL, address)
807                       
808                        if self.DEBUG:
809                                print 'INFO: Calling "%s":' % command
810                       
811                        output = os.popen(command, 'r')
812                       
813                        for line in output.readlines():
814                                line = line.strip()
815                                if line == '':
816                                        continue
817                                elif self.DEBUG:
818                                        print '\t',
819                                        print line
820                               
821                                device_name = line.strip()
822                       
823                                if ((device_name == 'MindSet' or device_name == 'MindWave Mobile') and \
824                                        (address not in thinkgear_devices)):
825                                        thinkgear_devices.append(address)
826               
827               
828                return (thinkgear_devices)
829       
830       
831        ##################################################################
832       
833        def searchForThinkGearDevices(self):
834               
835                enable_hcitool = configuration.ENABLE_HCITOOL
836               
837                thinkgear_devices = []
838               
839                #self.pushButtonBluetoothSearch.setText('Searching')
840               
841                if (sys.platform != 'win32'):
842                       
843                        # Bluetooth module doesn't compile properly under WindowsError
844                       
845                        bluetooth_devices = []
846                       
847                        if not enable_hcitool:
848                               
849                                try:
850                                       
851                                        if self.DEBUG:
852                                                print "INFO: Searching for Bluetooth devices using PyBluez module"
853                                       
854                                        bluetooth_devices = bluetooth.discover_devices( \
855                                                               duration=5, \
856                                                               flush_cache=True, \
857                                                               lookup_names=False)
858                                       
859                                        for address in bluetooth_devices:
860                                                device_name = bluetooth.lookup_name(address)
861                                                if ((device_name == 'MindSet' or device_name == 'MindWave Mobile') and \
862                                                        (address not in thinkgear_devices)):
863                                                        thinkgear_devices.append(address)
864                                       
865                                       
866                                        # There is an issue under recent released of Linux
867                                        # in which already-connected Bluetooth ThinkGear devices
868                                        # are not appearing in a bluetooth device scan. However,
869                                        # using "hcitool" connected devices can be listed correctly.
870                                        # There does not appear to be an equivalent PyBluez feature.
871                                        # (http://pybluez.googlecode.com/svn/www/docs-0.7/index.html)
872                                       
873                                        if thinkgear_devices == []:
874                                                if self.DEBUG:
875                                                        print "INFO: No devices found through PyBluez module. Falling back to hcitool."
876                                                thinkgear_devices = self.hcitoolGetActiveConnections(thinkgear_devices)
877                               
878                               
879                                except Exception, e:
880                                        if self.DEBUG:
881                                                print "ERROR: Exception calling Python Bluetooth module. (Is PyBluez installed?):"
882                                                print e
883                                       
884                                        enable_hcitool = True
885                       
886                       
887                        if enable_hcitool:
888                               
889                                thinkgear_devices = self.hcitoolScanForRemoteDevices(thinkgear_devices)
890                                thinkgear_devices = self.hcitoolGetActiveConnections(thinkgear_devices)
891                       
892                       
893                        if self.DEBUG > 2:
894                                print "Bluetooth ThinkGear devices found:",
895                                print thinkgear_devices
896               
897               
898                thinkgear_devices = self.searchForSerialDevices(thinkgear_devices)
899               
900               
901                if self.DEBUG:
902                        print "ThinkGear devices found:",
903                        print thinkgear_devices
904               
905               
906                return(thinkgear_devices)
907       
908       
909        ##################################################################
910       
911        def updateThinkGearDevices(self):
912               
913                devices = self.searchForThinkGearDevices()
914               
915                #if self.parent == None:
916                        #self.comboBoxDeviceSelect.clear()
917                #else:
918                        #self.parent.comboBoxDeviceSelect.clear()
919               
920                self.comboBoxDeviceSelect.clear()
921                devices.insert(0, 'ThinkGear Emulator')
922               
923                for device in devices:
924                        self.comboBoxDeviceSelect.addItem(device)
925       
926       
927        ##################################################################
928       
929        def collectData(self):
930               
931                if self == None:
932                        target = self
933                else:
934                        target = self.parent
935               
936                data = {}
937               
938                data['rawEeg'] = target.packets['rawEeg']
939                data['signals'] = target.packets['signals']
940               
941                data['sessionTime'] = self.calculateSessionTime()
942               
943                data['profileName'] = str(target.lineEditSessionProfile.text())
944               
945                return(data)
946       
947       
948        ##################################################################
949       
950        def parseTimeStamp(self, timestamp, local_version=False, truncate_time_zone=False):
951               
952                try:
953                        decimal = '%f' % timestamp
954                        decimal = decimal.split('.')[1]
955                except:
956                        decimal = '0'
957               
958                localtime = time.localtime(timestamp)
959               
960                if local_version:
961                        date = time.strftime('%x', localtime)
962                        localtime = time.strftime('%X', localtime)
963               
964                elif truncate_time_zone:
965                        date = time.strftime('%Y-%m-%d', localtime)
966                        localtime = time.strftime('%H:%M:%S', localtime)
967                        localtime = '%s.%s' % (localtime, decimal[:3])
968               
969                else:
970                        date = time.strftime('%Y-%m-%d', localtime)
971                        localtime = time.strftime('%H:%M:%S', localtime)
972                        localtime = '%s.%s %s' % (localtime, decimal, \
973                                       time.strftime('%Z', time.localtime(timestamp)))
974               
975               
976                return(date, localtime)
977       
978       
979        ##################################################################
980       
981        def saveData(self):
982               
983                if self == None:
984                        target = self
985                else:
986                        target = self.parent
987               
988                data = self.collectData()
989               
990                (date, localtime) = self.parseTimeStamp(time.time())
991               
992                default_filename = '%s %s.synapse' % (date, \
993                                      target.lineEditSessionProfile.text())
994                                     
995                default_filename = os.path.join(self.homepath, default_filename)
996               
997                output_file = QtGui.QFileDialog.getSaveFileName(parent=target, \
998                                 caption="Save Session Data to File", \
999                                 dir=default_filename, \
1000                                 filter="Puzzlebox Synapse Data File (*.synapse)")
1001               
1002                try:
1003                        output_file = output_file[0]
1004                except:
1005                        output_file = ''
1006               
1007                if output_file == '':
1008                        return
1009               
1010                file = open(str(output_file), 'w')
1011                pickle.dump(data, file)
1012                file.close()
1013       
1014       
1015        ##################################################################
1016       
1017        def exportData(self):
1018               
1019                if self.parent == None:
1020                        target = self
1021                else:
1022                        target = self.parent
1023               
1024                (date, localtime) = self.parseTimeStamp(time.time())
1025               
1026                default_filename = '%s %s.csv' % (date, \
1027                                      target.lineEditSessionProfile.text())
1028               
1029                default_filename = os.path.join(target.homepath, default_filename)
1030               
1031                output_file = QtGui.QFileDialog.getSaveFileName(parent=target, \
1032                                 caption="Export Session Data to File", \
1033                                 dir=default_filename, \
1034                                 filter="CSV File (*.csv);;Text File (*.txt)")
1035               
1036                try:
1037                        output_file = output_file[0]
1038                except:
1039                        output_file = ''
1040               
1041                if output_file == '':
1042                        return
1043               
1044                if str(output_file).endswith('.csv'):
1045                       
1046                        outputData = self.exportDataToCSV()
1047               
1048               
1049                else:
1050                       
1051                        try:
1052                                outputData = self.textEditDebugConsole.toPlainText()
1053                        except:
1054                                outputData = self.exportDataToCSV()
1055                       
1056                       
1057                file = open(str(output_file), 'w')
1058                file.write(outputData)
1059                file.close()
1060       
1061       
1062        ##################################################################
1063       
1064        def exportDataToCSV(self):
1065               
1066                if self == None:
1067                        target = self
1068                else:
1069                        target = self.parent
1070               
1071                try:
1072                        truncate_csv_timezone = target.configuration.EXPORT_CSV_TRUNCATE_TIMEZONE
1073                except:
1074                        truncate_csv_timezone = False
1075               
1076                try:
1077                        scrub_data = target.configuration.EXPORT_CSV_SCRUB_DATA
1078                except:
1079                        scrub_data = False
1080               
1081               
1082                headers = 'Date,Time,Delta,Theta,Low Alpha,High Alpha,Low Beta,High Beta,Low Gamma,Mid Gamma,Attention,Meditation,Signal Level'
1083               
1084                customDataHeaders = []
1085                for header in target.customDataHeaders:
1086                        customDataHeaders.append(header)
1087                for plugin in target.activePlugins:
1088                        for header in plugin.customDataHeaders:
1089                                customDataHeaders.append(header)
1090               
1091                for each in customDataHeaders:
1092                        headers = headers + ',%s' % each
1093               
1094                headers = headers + '\n'
1095               
1096                csv = {}
1097               
1098                for packet in target.packets['signals']:
1099                       
1100                        if 'rawEeg' in packet.keys():
1101                                continue
1102                       
1103                        if packet['timestamp'] not in csv.keys():
1104                               
1105                                #print packet
1106                                timestamp = packet['timestamp']
1107                                (date, localtime) = self.parseTimeStamp(timestamp, \
1108                                                    truncate_time_zone=truncate_csv_timezone)
1109                               
1110                                csv[timestamp] = {}
1111                                csv[timestamp]['Date'] = date
1112                                csv[timestamp]['Time'] = localtime
1113                                csv[timestamp]['Delta'] = ''
1114                                csv[timestamp]['Theta'] = ''
1115                                csv[timestamp]['Low Alpha'] = ''
1116                                csv[timestamp]['High Alpha'] = ''
1117                                csv[timestamp]['Low Beta'] = ''
1118                                csv[timestamp]['High Beta'] = ''
1119                                csv[timestamp]['Low Gamma'] = ''
1120                                csv[timestamp]['Mid Gamma'] = ''
1121                                csv[timestamp]['Attention'] = ''
1122                                csv[timestamp]['Meditation'] = ''
1123                                csv[timestamp]['Signal Level'] = ''
1124                               
1125                                for header in customDataHeaders:
1126                                        csv[timestamp][header] = ''
1127                       
1128                       
1129                        if 'eSense' in packet.keys():
1130                                if 'attention' in packet['eSense'].keys():
1131                                        csv[timestamp]['Attention'] = packet['eSense']['attention']
1132                                if 'meditation' in packet['eSense'].keys():
1133                                        csv[timestamp]['Meditation'] = packet['eSense']['meditation']
1134                       
1135                        if 'eegPower' in packet.keys():
1136                                if 'delta' in packet['eegPower'].keys():
1137                                        csv[timestamp]['Delta'] = packet['eegPower']['delta']
1138                                if 'theta' in packet['eegPower'].keys():
1139                                        csv[timestamp]['Theta'] = packet['eegPower']['theta']
1140                                if 'lowAlpha' in packet['eegPower'].keys():
1141                                        csv[timestamp]['Low Alpha'] = packet['eegPower']['lowAlpha']
1142                                if 'highAlpha' in packet['eegPower'].keys():
1143                                        csv[timestamp]['High Alpha'] = packet['eegPower']['highAlpha']
1144                                if 'lowBeta' in packet['eegPower'].keys():
1145                                        csv[timestamp]['Low Beta'] = packet['eegPower']['lowBeta']
1146                                if 'highBeta' in packet['eegPower'].keys():
1147                                        csv[timestamp]['High Beta'] = packet['eegPower']['highBeta']
1148                                if 'lowGamma' in packet['eegPower'].keys():
1149                                        csv[timestamp]['Low Gamma'] = packet['eegPower']['lowGamma']
1150                                if 'highGamma' in packet['eegPower'].keys():
1151                                        csv[timestamp]['Mid Gamma'] = packet['eegPower']['highGamma']
1152                       
1153                        if 'poorSignalLevel' in packet.keys():
1154                                csv[timestamp]['Signal Level'] = packet['poorSignalLevel']
1155                       
1156                        for header in customDataHeaders:
1157                                if 'custom' in packet.keys() and \
1158                                   header in packet['custom'].keys():
1159                                        csv[timestamp][header] = packet['custom'][header]
1160               
1161               
1162                if scrub_data:
1163                        csv = self.scrubData(csv, truncate_csv_timezone)
1164               
1165               
1166                output = headers
1167               
1168                csv_keys = csv.keys()
1169                csv_keys.sort()
1170               
1171                for key in csv_keys:
1172                       
1173                        row = '%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s' % \
1174                              (csv[key]['Date'], \
1175                               csv[key]['Time'], \
1176                               csv[key]['Delta'], \
1177                               csv[key]['Theta'], \
1178                               csv[key]['Low Alpha'], \
1179                               csv[key]['High Alpha'], \
1180                               csv[key]['Low Beta'], \
1181                               csv[key]['High Beta'], \
1182                               csv[key]['Low Gamma'], \
1183                               csv[key]['Mid Gamma'], \
1184                               csv[key]['Attention'], \
1185                               csv[key]['Meditation'], \
1186                               csv[key]['Signal Level'])
1187                       
1188                        for header in customDataHeaders:
1189                                row = row + ',%s' % csv[key][header]
1190                       
1191                        row = row + '\n'
1192                       
1193                        output = output + row
1194               
1195               
1196                return(output)
1197       
1198       
1199        ##################################################################
1200       
1201        def scrubData(self, csv, truncate_csv_timezone=False):
1202               
1203                # If there are missing packets, repeat a given packet once per missing
1204                # second until there is a gap between 1 and 2 seconds, in which case
1205                # produce a final duplicate packet at the mid-point between the packets
1206
1207                if self.DEBUG:
1208                        print "INFO: Scrubbing Data"
1209               
1210                last_time = None
1211                last_recorded_time = None
1212               
1213                output = {}
1214               
1215                csv_keys = csv.keys()
1216                csv_keys.sort()
1217               
1218                for key in csv_keys:
1219                       
1220                        timestamp = key
1221
1222                        if csv[key]['Attention'] == '':
1223                                continue
1224                       
1225                        if last_time == None:
1226                                # First entry in log
1227                                last_time = timestamp
1228                                last_recorded_time = timestamp
1229                                output[key] = csv[key]
1230                                continue
1231                       
1232                        else:
1233                               
1234                                #time_difference = timestamp - last_time
1235                                time_difference = timestamp - last_recorded_time
1236                               
1237                                if (time_difference <= 1) and \
1238                                   (time_difference >= PACKET_MINIMUM_TIME_DIFFERENCE_THRESHOLD):
1239                                        # Skip packets within the correct time threshold
1240                                        last_time = timestamp
1241                                        last_recorded_time = timestamp
1242                                        output[key] = csv[key]
1243                                        continue
1244                               
1245                                else:
1246
1247                                        if self.DEBUG > 1:
1248                                                print "time_difference:",
1249                                                print time_difference
1250                                                print "timestamp:",
1251                                                print self.parseTimeStamp(timestamp)[-1].split(' ')[0]
1252                                                print "last_time:",
1253                                                print self.parseTimeStamp(last_time)[-1].split(' ')[0]
1254                                                print "last_recorded_time:",
1255                                                print self.parseTimeStamp(last_recorded_time)[-1].split(' ')[0]
1256
1257                                       
1258                                        new_packet = csv[key].copy()
1259                                       
1260                                        if time_difference >= 2:
1261                                               
1262                                                ##new_time = last_time + 1
1263                                                #new_time = last_recorded_time + 1
1264
1265                                                count = int(time_difference)
1266                                                while count >= 1:
1267                                                        new_packet = csv[key].copy()
1268                                                        new_time = last_recorded_time + 1
1269                                                        (date, formatted_new_time) = self.parseTimeStamp(new_time, \
1270                                                         truncate_time_zone=truncate_csv_timezone)
1271                                                        new_packet['Time'] = formatted_new_time
1272                                                        last_recorded_time = new_time
1273                                                        last_time = timestamp
1274                                                        output[new_time] = new_packet
1275                                                        count = count - 1
1276                                                continue
1277                                               
1278                                        elif time_difference < PACKET_MINIMUM_TIME_DIFFERENCE_THRESHOLD:
1279                                                # Spread out "bunched up" packets
1280                                                #new_time = last_time + 1
1281                                                new_time = last_recorded_time + 1
1282                                       
1283                                       
1284                                        elif (time_difference < 2) and (time_difference > 1):
1285                                               
1286                                                #new_time = last_time + ((last_time - timestamp) / 2)
1287                                                #new_time = last_recorded_time + ((last_recorded_time - timestamp) / 2)
1288                                                #new_time = last_time + 1
1289                                                new_time = last_recorded_time + 1
1290                                       
1291                                       
1292                                        (date, formatted_new_time) = self.parseTimeStamp(new_time, \
1293                                           truncate_time_zone=truncate_csv_timezone)
1294                                       
1295                                        new_packet['Time'] = formatted_new_time
1296                                       
1297                                        #last_time = new_time
1298                                        last_recorded_time = new_time
1299                                        last_time = timestamp
1300                                        output[new_time] = new_packet
1301                                       
1302                                        if self.DEBUG > 1:
1303                                                print "WARN: Scrubbing new packet:",
1304                                                print new_packet
1305                                                print
1306               
1307               
1308                return(output)
1309       
1310       
1311        ##################################################################
1312       
1313        def resetData(self):
1314               
1315                if self == None:
1316                        target = self
1317                else:
1318                        target = self.parent
1319               
1320                target.packets['rawEeg'] = []
1321                target.packets['signals'] = []
1322               
1323                target.thinkGearConnectServer.protocol.session_start_timestamp = \
1324                        time.time()
1325               
1326                target.thinkGearConnectServer.protocol.packet_count = 0
1327                target.thinkGearConnectServer.protocol.bad_packets = 0
1328               
1329                self.updateProfileSessionStatus()
1330               
1331                try:
1332                        target.textEditDebugConsole.setText("")
1333                except:
1334                        pass
1335       
1336       
1337        #####################################################################
1338       
1339        def convert_seconds_to_datetime(self, duration):
1340               
1341                duration_hours = duration / (60 * 60)
1342                duration_minutes = (duration - (duration_hours * (60 * 60))) / 60
1343                duration_seconds = (duration - (duration_hours * (60 * 60)) - (duration_minutes * 60))
1344               
1345                duration_hours = '%i' % duration_hours
1346                if (len(duration_hours) == 1):
1347                        duration_hours = "0%s" % duration_hours
1348               
1349                duration_minutes = '%i' % duration_minutes
1350                if (len(duration_minutes) == 1):
1351                        duration_minutes = "0%s" % duration_minutes
1352               
1353                duration_seconds = '%i' % duration_seconds
1354                if (len(duration_seconds) == 1):
1355                        duration_seconds = "0%s" % duration_seconds
1356               
1357                datetime = '%s:%s:%s' % (duration_hours, duration_minutes, duration_seconds)
1358               
1359                return(datetime)
1360       
1361       
1362        ##################################################################
1363       
1364        def stop(self):
1365               
1366                if UPDATE_INTERFACE_VIA_TIMER:
1367                        self.updateInterfaceTimer.stop()
1368                else:
1369                        if self.thinkgearConnectClient != None:
1370                                self.thinkgearConnectClient.exitThread()
1371               
1372                if self.thinkGearConnectServer != None:
1373                        self.thinkGearConnectServer.exitThread()
1374       
1375       
1376        ##################################################################
1377       
1378        def closeEvent(self, event):
1379               
1380                quit_message = "Are you sure you want to exit the program?"
1381               
1382                reply = QtGui.QMessageBox.question( \
1383                           self, \
1384                          'Message', \
1385                           quit_message, \
1386                           QtGui.QMessageBox.Yes, \
1387                           QtGui.QMessageBox.No)
1388               
1389                if reply == QtGui.QMessageBox.Yes:
1390                       
1391                        self.stop()
1392                       
1393                        event.accept()
1394               
1395                else:
1396                        event.ignore()
Note: See TracBrowser for help on using the repository browser.