Python WX – Threads management

Pour pouvoir s’immiscer dans la boucle principale d’une application WX, c’est à dire exécuter des fonctions sans action de la part de l’utilisateur, le plus simple est d’utiliser un thread.

Mais attention, il est impossible d’exécuter une fonction de votre code WX depuis un programme externe comme un thread.

La manipulation consiste donc à faire en sorte que le thread n’appelle aucune fonction de votre appli, mais lui envoie un événement, de la même manière qu’un bouton.

Ainsi, dans votre appli WX, vous créerez une fonction qui sera associé à cet événement ( bind ).

Voici un exemple avec un petit clavier virtuel, originalement développé pour le raspberry pi .


#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#
###################################################################
#
# Copyright (C) DDRDEV 2015
#
# Author Gonzague Defos du Rau
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
####################################################################

import threading, time, os

def getWinId(TITLE):
	cmd = "xwininfo -root -tree | grep "+TITLE+" | awk -F' ' '{print $1}'"
	print cmd
	winId = os.popen(cmd).read().strip() 
	winId =  winId.strip()	
	return winId
	
# Check dependencies : wx and xlib
from sys import exit, argv

IMPORTERRORS = ""
try: import wx
except ImportError: IMPORTERRORS += "\nCan not import wx, you shall install python-wxgtk"

try: from Xlib import X
except ImportError: IMPORTERRORS += "\nCan not import Xlib.display, you shall install python-xlib"

if IMPORTERRORS != "":
	print IMPORTERRORS
	exit()
 
from Xlib.display import Display
from Xlib.protocol import event

# Check if XEVFILE is given, and build keys datas, see BuildXkeys() below 
if len(argv) > 1: XEVFILE = argv[1]
else : XEVFILE = "xevLog"

def BuildXkeys( xevLog ):
	'''
	 First build the X keycodes dictionary using xev :
	 Type this command line "xev | grep -e state -e XLookupString > xevLog", 
	  and than press all keys you need 
	  ( all keys + Shift all keys + AltGr all keys ) .
	 After this you can run this program.
	 If you miss some keys, re-run the command line using the "append" sign
	  so you can keep your xevLog and press only the missing keys :
		xev | grep -e state -e XLookupString >> xevLog
	'''

	h = open( xevLog, "r")
	c = h.read()
	h.close()
	logLines = [ l.strip() for l in c.split("\n") if l.strip() != "" ]
	nLine = 0

	XKEYS = {}
	XKEYSBYCARAC = {}
	SUPAKEYS = []

	for line in logLines:
	
		if line.startswith("state"):
			datas = line.split(" ")
			if len(datas) > 6:
				state = datas[1].replace(",", "")
				keycode = datas[3]
				keysym = datas[5].replace(",", "")
				keyname = datas[6].replace(")", "").replace(",", "")
				tstAscii = logLines[nLine+1].split('"')
				if len(tstAscii) > 1:
					ascii = tstAscii[1]
				else: 
					ascii = keyname
					if not keyname in SUPAKEYS:
						SUPAKEYS.append(keyname)
						print keyname
				
				if ascii in "æâ€êþÿûîœôô~ø¨£äßë‘’ðüïŀö´`≤«»©↓¬¿×÷¡µ§ù":
					ascii = unicode(ascii, 'utf-8')
					
				if not keyname in XKEYS:
					d = {"state":int(state,16), "keycode":int(keycode), "keysym":keysym, "ascii":ascii, "keyname": keyname  }
					XKEYS[ keyname ] = d

					XKEYSBYCARAC[ascii] = d
					#print keyname, ascii, d, type(ascii)

		nLine += 1

	return [ XKEYS, XKEYSBYCARAC, SUPAKEYS ]

# Than build our keys dictionaries
XKEYS, XKEYSBYCARAC, SUPAKEYS = BuildXkeys( XEVFILE )

# The X keyboard
class Keyboard():
	'''
	This Keyboard() class is use to send the KeyPress event to X windows . 
	'''
	def __init__(self, parent):
	
		self.ready = False
		
		self.parent = parent
		self.display = Display()
		self.rootWindow = self.display.get_input_focus()._data["focus"] 
		self.lastWindow = False
		self.currentWindow = False
		
		self.kbThread = KbThread(self)
		
		self.supaKeysOn = {}
		for k in ["Shift_L", "Shift_R"]:
			self.supaKeysOn[k] = False
			
	def ResetSupaKeys(self):
		for k in self.supaKeysOn:
			self.supaKeysOn[k] = False
		
	def KeyPress(self, key):
	
		if not self.lastWindow or not self.parent.wxWindow:
			print "No Window selected"
			return
			
		#if not self.ready : print "Please wait while initialising"

		keycode	= False
		if key in XKEYSBYCARAC :
			keycode = XKEYSBYCARAC[key]["keycode"]
			state = XKEYSBYCARAC[key]["state"]
		elif key in XKEYS :
			keycode = XKEYS[key]["keycode"]
			state = XKEYS[key]["state"]
		
		if not keycode:
			print "No keycode for "+key+", "+str(type(key))+", you shall re-run xev..."
		else:
			keyevt = event.KeyPress( 	detail=keycode,
										time=X.CurrentTime,
										root=self.display.screen().root,
										window=self.lastWindow,
										child=X.NONE,
										root_x=1,
										root_y=1,
										event_x=1,
										event_y=1,
										state=state,
										same_screen=1
									)

			#print "SENDING \""+key+"\" ( "+str(keycode)+", "+str(state)+" )" 
			#, keycode, state, self.lastWindow.id
			self.lastWindow.send_event(keyevt)

			self.display.sync()
		
	def __del__(self):
		self.kbThread.stop()
	
# The X thread	
class KbThread(threading.Thread):
	''' 
	This is the main thread that detect the last selected windows.
	It first check its parent's windows to not select them : 
	 parent.rootWindow ( console ) and parent.parent.wxWindow ( wx gui ), 
	 where parent is the Keyboard() instance and parent.parent 
	 is the KbFrame() instance.
	'''
	def __init__(self, parent):

		self.parent = parent
		
		# Have a "slow" delay so we can Alt+Tab without getting the desktop 
		#  as the lastDetectedWin. Note that it could happen. 
		self.delay = 1
		
		self.tmpCheck = time.time()
		
		self.lastDetectedWin = False

		self._stopevent = threading.Event()
		threading.Thread.__init__(self, target=self.run, name="MainThread", args=() )
		
		self.winEvtType = wx.NewEventType()
		self.winEvt = MyEvent( etype=self.winEvtType, eid=9, value="Event Window Focus" )

		self.start()
 
	def run(self):
		'''
		Detect the last focused window and send it to WX as an event.
		'''
		while not self._stopevent.isSet():
			
			currentWindow = self.parent.display.get_input_focus()._data["focus"] 
			
			# TODO: Find something else, currently we're setting the WX windows as the 1rst win
			#   that is focus on if it's not the rootWindow, so we shall not focus any windows
			#   between the start of the program and the end of wx initialisation, brrr .
			if not self.parent.parent.wxWindow and self.parent.rootWindow and currentWindow.id != self.parent.rootWindow.id :
				#self.parent.parent.wxWindow = currentWindow
				self.parent.parent.wxWindow = self.parent.display.create_resource_object('window', int(getWinId("MyKb"), 16)+1)
				print "R C W", self.parent.rootWindow.id , currentWindow.id , self.parent.parent.wxWindow.id
				self.parent.ready = True
				
			'''if self.parent.parent.mainXID:
				print "mainXID", self.parent.parent.mainXID
				self.parent.parent.wxWindow.id = self.parent.parent.mainXID'''
				
			'''if not self.parent.parent.wxWindow and self.parent.rootWindow and currentWindow.id != self.parent.rootWindow.id :	
				mainXID = getWinId("MyKb")
				self.parent.parent.wxWindow = self.parent.display.create_resource_object('window', int(mainXID, 16)+1)
				print "***", mainXID, self.parent.parent.wxWindow.id'''
		
			# We don't want the last selected windows to be this program 
			if currentWindow.id != self.parent.rootWindow.id and currentWindow.id != self.parent.parent.wxWindow.id :
				self.parent.lastWindow = currentWindow
				
				# And we change lastDetectedWin only if it's a different window
				if not self.lastDetectedWin or self.lastDetectedWin.id != currentWindow.id :
					self.lastDetectedWin = currentWindow
					
					# Get the name of the window
					wName = self.lastDetectedWin.get_wm_class()
					if wName == None:
						w = self.lastDetectedWin.query_tree().parent
						wName = w.get_wm_class()
					
					# Sending the event to wx so GUI knows witch is the lastDetectedWin
					msg = "lastDetectedWin has changed to "+str(wName)+", "+str(self.lastDetectedWin.id)
					self.winEvt.SetValue( msg )
					wx.PostEvent(self.parent.parent, self.winEvt)
			
				
			time.sleep( self.delay )
	
	def stop(self):
		print "\n[INFO] KbThread : stopping thread, please wait ..."
		self._stopevent.set()
		print "[INFO] KbThread : thread is stopped."
				
		
# A WX event	
class MyEvent(wx.PyCommandEvent):
	""" A WX event we can send from anywhere """
	def __init__(self, etype=wx.NewEventType(), eid=wx.ID_ANY, value=None):
		"""Creates the event object"""
		wx.PyCommandEvent.__init__(self, etype, eid)
		self._value = value
		self._eid = eid
		
	def GetValue(self):
		"""
		@return: the value of this event
		"""
		return self._value
		
	def SetValue(self, value):
		"""
		Set the value of this event
		"""
		self._value = value
		return 
		
	def GetId(self):
		"""
		@return: the id of this event
		"""
		return self._eid
		
# The main WX frame	
class KbFrame(wx.Frame):
	'''
	KbFrame() is the main wx 
	'''
	def __init__(self, parent, id, title):
		
		self.wxWindow = False

		self.kb = Keyboard(self)

		self.specialValues = { "CR": "Return", "TAB" : "Tab", "ESC" : "Escape",  
								"\"":"quotedbl", "BS":"BackSpace", 
								}
								
		self.specialFunc = {"SW": self.SwitchPanel, 
							"SH": self.OnShift,
							"AG": self.OnAltGr,
							 }
		
		self.currentPanel = 0
		self.panelsName = ["Basic", "Upper case", "Alt Gr", "Misc"]
		
		self.lastPanel = 0
		self.isShift = False
		self.isAltGr = False
		self.isCtrl = False

		# Initialisation
		displays = (wx.Display(i) for i in range(wx.Display.GetCount()))
		sizes = [display.GetGeometry().GetSize() for display in displays]
		szx, szy = sizes[0]
		
		width, height = 550, 115
		if width > szx:
			width = szx
			
		if height > ( szy / 2):
			height = ( szy / 2)
		
		x = 0 #szx-width
		y = szy-height
		print szx, szy, width, height, x,y
		
		wx.Frame.__init__(self, parent, id, title, (x,y), wx.Size(width, height) )
		
		
		# Bind event from thread to the WindowEvt function
		EVT_TYPE = wx.PyEventBinder( self.kb.kbThread.winEvtType , 1)
		self.Bind(EVT_TYPE, self.WindowEvt)
		
		self.SetBackgroundColour( wx.Colour(55, 55, 66) )

		# Store keys by event id
		self.keysByEvtId = {}
		
		# Build default panel
		self.SwitchPanel(False)
		self.SetPosition((x, y))
		#print "GetId", self.GetId()
		
		#mainXID = getWinId("MyKb")
		#self.wxWindow = self.kb.display.create_resource_object('window', mainXID)
		#print "***", mainXID, self.wxWindow.id
		

	def OnShift(self, event):
		'''
		Switch to panel 1 or 0 
		'''
		if not self.isShift:
			self.isShift = True
			self.currentPanel = 0
			self.SwitchPanel( True ) # To panel 1
		else:
			self.isShift = False
			self.currentPanel = 3 
			self.SwitchPanel( True ) # To panel 0
	
	def OnAltGr(self, event):
		'''
		Switch to panel 2 or 0 
		'''
		if not self.isAltGr:
			self.isAltGr = True
			self.currentPanel = 1
			self.SwitchPanel( True ) # To panel 2

		else:
			self.isAltGr = False
			self.currentPanel = 3 
			self.SwitchPanel( True ) # To panel 0
			
	def SwitchPanel(self, event):
		'''
		Should have been call SwitchToNextPanel
		'''
		if event:
			self.lastPanel = self.currentPanel
			sx,sy = self.GetClientSize()
			px, py = self.GetPosition()
			
			self.currentPanel += 1
			if self.currentPanel > 3:
				self.currentPanel = 0

			for child in self.gs.GetChildren():
				child.GetWindow().Destroy()

		# Build the layout
		self.buttons = self.BuildLayout( self.currentPanel )	
		
		# On laisse WX placer nos bouton par ligne de 14 
		self.sizer = wx.BoxSizer(wx.VERTICAL)
		
		# La grille principale, sur 5 lignes, 14 colonnes 
		self.gs = wx.GridSizer(5, 14, 0, 0)
		
		self.gs.AddMany( self.buttons )

		self.sizer.Add(self.gs, 1, wx.EXPAND)

		self.SetSizer(self.sizer)
		self.Centre()

		if event:
			self.SetPosition((px, py))
			print (px, py)
		
		self.SetTitle('MyKb - '+self.panelsName[self.currentPanel] )
		
		# Refresh the layout
		self.Layout()

	def BuildLayout(self, layout):
		'''
		Ordering and coloring buttons' label, then binding to function
		'''
		if layout == 0:
			# 1rst line
			self.buttonsLabels = ["SW"]
			for i in range(1,10):
				self.buttonsLabels.append( str(i) )
			self.buttonsLabels += ["0", "°", "+", "BS"]
			
			# 2nd line
			self.buttonsLabels += ["TAB"]
			for c in "azertyuiop^$":
				self.buttonsLabels.append(c)
			self.buttonsLabels.append("CR")
			
			# 3rd line
			self.buttonsLabels.append("SH")
			for c in "qsdfghjklm"+u"ù"+"*":
				self.buttonsLabels.append(c)
			self.buttonsLabels.append("Left")
			
			# 4rth line
			self.buttonsLabels.append("AG")
			for c in "WXCVBN?./"+u"§"+"  ":
				self.buttonsLabels.append(c)
				
		elif layout == 2:
			# 1rst line
			self.buttonsLabels = ["SW"]
			self.buttonsLabels += [ "²", "~", "#", "{", "[", "|", "`", "\\", "^", "@", "]", "}" ]
			self.buttonsLabels += ["BS"]
			
			# 2nd line
			self.buttonsLabels += ["TAB"]
			for c in u"æâ€êþÿûîœô~ø":
				self.buttonsLabels.append(c)
			self.buttonsLabels.append("CR")
			
			# 3rd line
			self.buttonsLabels.append("SH")
			for c in u"äßë‘’ðüïŀö´` ": 
				self.buttonsLabels.append(c)
			
			# 4rth line	
			self.buttonsLabels.append("AG")
			for c in u"≤«»© ↓¬¿×÷¡  ": 
				self.buttonsLabels.append(c)
		
		elif layout == 3:

			kDone = ["Left", "Down", "Right", "Up"]
			
			self.buttonsLabels = ["SW"]

			n = 1
			for sk in SUPAKEYS:
				if not sk in kDone :
					kDone.append(sk)
					self.buttonsLabels += [ sk ]
				
				if n == 12: # end 1rst, start 2nd line at TAB
					self.buttonsLabels += ["BS", "TAB"]
					
				if n == 24:	# end 2nd line, start 3rd at SH
					self.buttonsLabels += ["CR", "SH", "", "Up", ""]
					
				if n == 32:	# end 3rd, start 4rth line at AG
					self.buttonsLabels += ["", " ", " ", "AG", "Left", "Down", "Right"]
					
				n += 1

		
		# Une petite touche de couleurs
		defaultColor = wx.Colour(240, 240, 255)
		numColor = 	wx.Colour(200, 222, 200)
		buttonsColor = {}
		
		for i in range(10):
			buttonsColor[str(i)] = numColor
			
		buttonsColor["SW"] = wx.Colour(253, 202, 200)

		# Identifiant WX	
		buttonId = 10
		
		# Liste des boutons
		buttons = []
		
		# Loop into labels
		for buttonLabel in self.buttonsLabels :

			# If there's a label
			if buttonLabel not in [ "" ]:
				
				#print "Building "+buttonLabel

				# Création du bouton
				button = wx.Button(self, buttonId, " "+buttonLabel+" ", style=wx.BU_EXACTFIT )
				
				# Assignation à la fonction
				if buttonLabel in self.specialFunc :
					self.Bind(wx.EVT_BUTTON, self.specialFunc[ buttonLabel ], id=buttonId)
				else:
					self.Bind(wx.EVT_BUTTON, self.OnPress, id=buttonId)
				
				self.keysByEvtId[buttonId] = buttonLabel
			
			else:
				button = wx.StaticText(self, -1, buttonLabel)
				
			# Assignation des couleurs
			if buttonLabel in buttonsColor : 
				button.SetBackgroundColour( buttonsColor[ buttonLabel ] )
			else:
				button.SetBackgroundColour( defaultColor )
				
			# Ajout du bouton dans la liste
			buttons.append( button )
			
			# Incrémenter l'id du boutton
			buttonId += 1
		
		return buttons

	def OnPress(self, event):
		'''
		Call the Keyboard's KeyPress method
		'''
		key = self.keysByEvtId[ event.GetId() ]
		
		self.SetTitle('MyKb - '+self.panelsName[self.currentPanel]+' - '+key)
		
		#print "\nPress "+key+"  "

		if key in self.specialValues :
			key = self.specialValues[ key ]

		self.kb.KeyPress(key)
		
		if self.isShift:
			self.OnShift(False)

		if self.isAltGr:
			self.OnAltGr(False)

	def WindowEvt(self, event):
		'''
		Receive event from the thread
		'''
		print "\nEVT from thread :", event.GetId(), event.GetValue(), "\n"
		self.SetTitle('MyKb - '+self.panelsName[self.currentPanel]+' - '+str(event.GetValue()))
		

	def __del__(self):
		self.kb.__del__()

# The WX App
class KbGui(wx.App):
	''' The wx class to create the main frame '''
	def OnInit(self):
		frame = KbFrame(None, -1, 'MyKb')
		frame.Show(True)
		self.SetTopWindow(frame)
		return True

	
zkbd = KbGui(0) 
zkbd.MainLoop()   


Python WX – Buttons management

Cet exemple illustre comment gérer les boutons et les fonctions associées, basé sur http://wiki.wxpython.org/AnotherTutorial

calculator

#!/usr/bin/python
# -*- coding: utf-8 -*- 
#
# calculator.py
#
# Based on the wonderfull tutorial 
#  http://wiki.wxpython.org/AnotherTutorial
#
# tested with python 2.7
#
#################################################


# Pour prendre en compte la division d'entiers comme un flotant
from __future__ import division

# Quelques fonctions mathématiques
from math import cos, sin, tan, pi, sqrt, radians

# WX
import wx

# La fenêtre principale		
class MyApp(wx.App):
	def OnInit(self):
		frame = MyFrame(None, -1, 'CalculatOr')
		frame.Show(True)
		self.SetTopWindow(frame)
		return True
		
# Le cadre principal	
class MyFrame(wx.Frame):

	# Constructeur
	def __init__(self, parent, id, title):

		# Initialisation
		wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size(400, 400))

		self.SetBackgroundColour( wx.Colour(55, 55, 66) )
		
		self.formula = False

		sizer = wx.BoxSizer(wx.VERTICAL)
		
		# Affichage du texte 
		self.display = wx.TextCtrl(self, -1, '',  style=wx.TE_RIGHT)
		self.display.SetBackgroundColour( wx.Colour(240, 250, 230) )
		
		sizer.Add(self.display, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 4)

		# La grille principale, sur 4 colonnes
		gs = wx.GridSizer(4, 4, 3, 3)
		
		# Mise en place des labels
		buttonsLabels = [   'Cls', 'Bck', '', 'Close', 
							'7', '8', '9', '/', 
							'4', '5', '6', '*', 
							'1', '2', '3', '-', 
							'0', '.', '=', '+', 
							'Pi', '', '(',  ')',
							'Carré', 'Racine', '', '', 
							'Rad', 'Cos', 'Sin', 'Tan', 
							]
							
		# Définition des signes					
		self.signs = ['/', '*', '-', '.', '+', '(',  ')']
		self.signsByEvtId = {}
		
		# Définition des chiffres
		self.numbers = [ str(i) for i in range(10) ] + ["Pi"]
		self.numbersByEvtId = {}
				
		# Assignation des fonctions	particulières				
		specialFunc = { 'Cls' : self.OnClear, 'Bck' : self.OnBackspace, 
						'Close' : self.OnClose, 
						'=' : self.OnEqual, 
						'Rad' : self.OnRad, 'Cos' : self.OnCos, 
						'Sin' : self.OnSin, 'Tan' : self.OnTan, 
						'Carré' : self.OnSq, 'Racine' : self.OnSqrt, 
						}

			
		# Une petite touche de couleurs
		defaultColor = wx.Colour(240, 240, 255)
		numColor = 	wx.Colour(200, 222, 200)
		buttonsColor = {}
		
		for i in range(10):
			buttonsColor[str(i)] = numColor
		buttonsColor['Pi'] = wx.Colour(200, 220, 255)
		
		for b in ['(',  ')']:
			buttonsColor[b] = wx.Colour(250, 250, 200)
		buttonsColor['='] = wx.Colour(250, 200, 200)
		
		# Création des boutons et correspondances d'avec les fonctions	
		
		# Identifiant WX	
		buttonId = 10
		
		# Liste des boutons
		buttons = []
		
		# Boucle sur les labels
		for buttonLabel in buttonsLabels :

			# Si le label correspond à un chiffre, fonction OnNumber(),
			if buttonLabel in self.numbers:
			
				# Attribution d'une valeur à l'id du bouton ( id de l'évènement )
				if buttonLabel == "Pi" : 
					val = str(pi)
				else : 
					val = buttonLabel
				self.numbersByEvtId[buttonId] = val
				
				# Création du bouton
				button = wx.Button(self, buttonId, buttonLabel)
				
				# Assignation à la fonction
				self.Bind(wx.EVT_BUTTON, self.OnNumber, id=buttonId)
				
			# si non, si le label correspond à un signe, fonction OnSign(),
			elif buttonLabel in self.signs:
				self.signsByEvtId[buttonId] = buttonLabel
				button = wx.Button(self, buttonId, buttonLabel)
				self.Bind(wx.EVT_BUTTON, self.OnSign, id=buttonId)
				
			# si non, si le label correspond à une fonction particulière,
			elif buttonLabel in specialFunc:  
				button = wx.Button(self, buttonId, buttonLabel)
				self.Bind(wx.EVT_BUTTON, specialFunc[ buttonLabel ], id=buttonId)
			
			# si non, on affiche le texte .
			else:
				button = wx.StaticText(self, -1, buttonLabel)
				
			# Assignation des couleurs
			if buttonLabel in buttonsColor : 
				button.SetBackgroundColour( buttonsColor[ buttonLabel ] )
			else:
				button.SetBackgroundColour( defaultColor )
				
			# Ajout du bouton dans la liste
			buttons.append( button )
			
			# Incrémenter l'id du boutton
			buttonId += 1
		
		# On laisse WX placer nos bouton par ligne de 4 suivant notre liste
		gs.AddMany( buttons )

		sizer.Add(gs, 1, wx.EXPAND)

		self.SetSizer(sizer)
		self.Centre()
		
		# Fin Constructeur
	 
	# Traitement des signes
	def OnSign(self, event):
		if self.formula: 
			return
		sign = self.signsByEvtId[ event.GetId() ]
		self.display.AppendText( sign )

	# Traitement des chiffres
	def OnNumber(self, event):
		if self.formula:
			self.display.Clear()
			self.formula = False
		 
		num =  self.numbersByEvtId[ event.GetId() ]  
		self.display.AppendText( num )

	# Traitement particuliers: fonctions mathématiques standards

	# Tangente		
	def OnTan(self, event):
		if self.formula:
			return
		formula = self.display.GetValue()
		self.formula = False
		try:
			self.display.Clear()
			output = tan( float(eval(formula)) )
			self.display.AppendText(str(output))
		except StandardError, e:
			self.display.AppendText("Error : "+str(e))
	
	# Sinus	
	def OnSin(self, event):
		if self.formula:
			return
		formula = self.display.GetValue()
		self.formula = False
		try:
			self.display.Clear()
			output = sin( float(eval(formula)) )
			self.display.AppendText(str(output))
		except StandardError, e:
			self.display.AppendText("Error : "+str(e))
		
	# Cosinus		
	def OnCos(self, event):
		if self.formula:
			return
		formula = self.display.GetValue()
		self.formula = False
		try:
			self.display.Clear()
			output = cos( float(eval(formula)) )
			self.display.AppendText(str(output))
		except StandardError, e:
			self.display.AppendText("Error : "+str(e))
			
	# Racinne carré	  
	def OnSqrt(self, event):
		if self.formula:
			return
		formula = self.display.GetValue()
		self.formula = False
		try:
			self.display.Clear()
			output = sqrt( float(eval(formula)) )
			self.display.AppendText(str(output))
		except StandardError, e:
			self.display.AppendText("Error : "+str(e))
		
	# Carré	
	def OnSq(self, event):
		if self.formula:
			return
		formula = float(eval(self.display.GetValue() ) )
		self.formula = False
		try:
			self.display.Clear()
			output = formula ** 2 
			self.display.AppendText(str(output))
		except StandardError, e:
			self.display.AppendText("Error : "+str(e))

	# Traitements particuliers: fonctions de la calculette 
	
	# Convertir en radians
	def OnRad(self, event):
		if self.formula:
			return
		formula = self.display.GetValue()
		self.formula = False
		try:
			self.display.Clear()
			output = radians( float(eval(formula)) )
			self.display.AppendText(str(output))
		except StandardError, e:
			self.display.AppendText("Error : "+str(e))
			
			
	# Tout effacer
	def OnClear(self, event):
		self.display.Clear()

	# Effacer le dernier caractère
	def OnBackspace(self, event):
		formula = self.display.GetValue()
		self.display.Clear()
		self.display.SetValue(formula[:-1])

	# Calcul du résultat
	def OnEqual(self, event):
		if self.formula:
			return
		formula = self.display.GetValue()
		self.formula = False
		try:
			self.display.Clear()
			output = eval(formula)
			self.display.AppendText(str(output))
			#self.formula = True
		except StandardError, e:
			self.display.AppendText("Error : "+str(e))

	# Fermeture de la fenêtre
	def OnClose(self, event):
		self.Close()
		
# Construction
app = MyApp(0) 
app.MainLoop()