You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

316 lines
14 KiB

7 months ago
# C'est très bien, sauf que ta classe Interface fait beaucoup plus de choses
# que simplement être une interface. Normalement, toute la partie calcul devrait
# être faite par la classe Expression, que tu n'utilises pas !!!
# En POO, il y a un principe que s'appelle SRP (Single Responsability Principle)
# qui dit que chaque objet doit avoir un unique rôle.
# Note : 10 / 10
from math import *
import tkinter as tk
from tkinter import messagebox
from Pile import Pile_chaine as Pile
from expression import Expression, toTree
from infixe import infixe2npi, estUnNombre
class ValeurSaisie:
"""classe d'une valeur saise par l'utilisateur"""
def __init__(self, valeur, master):
"""initialisation de la valeur"""
self.valeur = valeur
self.label_valeur = tk.Label(master, text=valeur)
self.label_valeur.pack(side="bottom")
def __del__(self):
"""suppression du label"""
self.label_valeur.destroy()
class Interface(tk.Frame):
"""interface de la calculatrice"""
def __init__(self, master):
"""initialisation de l'interface"""
self.master = master
tk.Frame.__init__(self, master)
self.etat = tk.Label(self.master, text="notation postfixée (polonaise inversée) -- mode interactif", fg="gray42")
self.etat.pack()
self.etat_affichage = "post-int"
self.stack = Pile()
self.create_menu_bar()
self.frame_stack = tk.Frame(self.master)
self.frame_stack.pack(padx=10, pady=10)
self.affichage_expression = tk.Label(self.master, text="")
self.affichage_expression.pack()
frame_saisie = tk.Frame(self.master, padx=5, pady=5)
frame_saisie.pack(fill="both")
self.saisie_expression = tk.Entry(frame_saisie)
self.saisie_expression.pack(fill="both",expand=True,side="left")
self.saisie_expression.bind("<Return>", lambda e : self.entrer())
self.saisie_expression.bind("<KP_Enter>", lambda e : self.entrer())
btn_entrer = tk.Button(frame_saisie, text='entrer', command=self.entrer)
btn_entrer.pack()
commandes = tk.LabelFrame(self.master, text="commandes", padx=5, pady=5)
commandes.pack(fill="both", padx=5)
self.frame_pile = tk.LabelFrame(commandes, text="pile", padx=5, pady=5)
self.frame_pile.pack(fill="both")
tk.Button(self.frame_pile, text='tout supprimer', command=self.effacer_tt).pack(side="left")
tk.Button(self.frame_pile, text='supprimer sommet', command=self.effacer_dernier).pack(side="left")
tk.Button(self.frame_pile, text='dupliquer sommet', command=self.dupliquer).pack(side="left")
fonctions = tk.LabelFrame(commandes, text="fonctions", padx=5, pady=5)
fonctions.pack(fill="both")
for i, text in enumerate(("+", "-", "/","*","^")):
tk.Button(fonctions, text=text, command=lambda x=text: self.ecrire_fx(x)).grid(row=0, column=i, sticky="ew")
for i, text in enumerate(("sin","cos","tan","factorielle")):
tk.Button(fonctions, text=text, command=lambda x=text: self.ecrire_fx(x)).grid(row=1, column=i, sticky="ew")
for i, text in enumerate(("asin","acos","atan")):
tk.Button(fonctions, text=text, command=lambda x=text: self.ecrire_fx(x)).grid(row=2, column=i, sticky="ew")
def ecrire_fx(self, nom):
"""insère le nom de la fonction dans la barre de saisie"""
self.saisie_expression.insert(tk.END, " "+nom+" ")
if self.etat_affichage == "post-int":
self.entrer()
def dupliquer(self):
"""duplique la dernière valeur entrée dans le stack"""
if not self.stack.est_vide():
self.stack.empiler(ValeurSaisie(self.stack.sommet().valeur, self.frame_stack))
def create_menu_bar(self):
"""crée la barre de menu de la calculatrice"""
menu_bar = tk.Menu(self)
menu_file = tk.Menu(menu_bar, tearoff=0)
menu_file.add_command(label="Quitter", command=self.master.destroy)
menu_bar.add_cascade(label="Fichier", menu=menu_file)
menu_affichage = tk.Menu(menu_bar, tearoff=0)
menu_mode = tk.Menu(menu_bar, tearoff=0)
menu_affichage.add_command(label="préfixe (polonaise)", command=lambda : self.change_affichage("pre"))
menu_affichage.add_command(label="infixe", command=lambda : self.change_affichage("inf"))
menu_mode.add_command(label="mode interactif", command=lambda : self.change_affichage("post-int"))
menu_mode.add_command(label="mode expression", command=lambda : self.change_affichage("post-expr"))
menu_affichage.add_cascade(label="postfixe (polonaise inversée)", menu=menu_mode)
menu_bar.add_cascade(label="Notation", menu=menu_affichage)
menu_help = tk.Menu(menu_bar, tearoff=0)
menu_help.add_command(label="Aide", command=self.aide)
menu_help.add_command(label="À propos", command=self.about)
menu_bar.add_cascade(label="Aide", menu=menu_help)
self.master['menu']=menu_bar
def change_affichage(self, affichage):
"""change le mode d'affichage,
désactive les opérations de pile si l'on est pas dans le mode interactif
et efface les calculs en cours"""
self.etat_affichage = affichage
etat_text = {"pre":"notation préfixée (polonaise)",
"inf":"notation infixée",
"post-int":"notation postfixée (polonaise inversée) -- mode interactif",
"post-expr": "notation postfixée (polonaise inversée) -- mode expression"}
self.etat.config(text=etat_text[affichage])
if self.etat_affichage != "post-int":
for child in self.frame_pile.winfo_children():
child['state']='disable'
else:
for child in self.frame_pile.winfo_children():
child['state']='normal'
self.affichage_expression.config(text="")
self.effacer_tt()
self.saisie_expression.delete(0 ,'end')
def aide(self):
"""affiche l'aide"""
messagebox.showinfo("Aide", "42")
def about(self):
"""à propos de cette application"""
messagebox.showinfo("À propos", "Ceci est une calculatrice.")
def entrer(self):
"""selon le mode, on évalue l'expression ou on entre la valeur dans le stack"""
if len(self.saisie_expression.get()) > 0:
try:
if self.etat_affichage == "post-int":
try: self.entrer_terme_dans_stack(self.saisie_expression.get().replace(" ",""))
except ValueError:
messagebox.showerror("Saisie invalide", "Le résultat de l'opération n'est pas un nombre réel.")
except TypeError:
messagebox.showerror("Saisie invalide", "L'opérande n'est pas un nombre réel.")
else:
self.evaluer_expr()
except OverflowError:
messagebox.showerror("OverflowError", "Le résultat est trop élevé.")
def entrer_terme_dans_stack(self, terme):
"""si c'est un nombre ou une constante: on ajoute une nouvelle valeur dans le stack
si c'est une fonction : on l'évalue"""
if estUnNombre(terme):
if "." in terme:
terme = float(terme)
else:
terme = int(terme)
self.stack.empiler(ValeurSaisie(terme, self.frame_stack))
elif terme in ["asin","acos","atan","sin","cos","tan","sqrt","!","factorielle","opp"]: self.evaluer_stack(terme,1)
elif terme in ["+","-","/","*","^"]: self.evaluer_stack(terme,2)
elif terme in ["moy3"]: self.evaluer_stack(terme,3)
elif terme in ["somme","produit"]: self.evaluer_stack(terme,-1)
elif terme == "pi": self.stack.empiler(ValeurSaisie(pi, self.frame_stack))
elif terme == "e": self.stack.empiler(ValeurSaisie(exp(1), self.frame_stack))
else:
messagebox.showerror("Saisie invalide", "La fonction « "+terme+" » est inconnue.")
return
self.saisie_expression.delete(0 ,'end')
def evaluer_stack(self, op, nb_arg):
"""application de l'operation aux nb_arg dernières valeurs du stack ;
si nb_arg == -1 alors toutes les valeurs du stack sont utilisées"""
if nb_arg == -1:
if self.stack.est_vide():
messagebox.showerror("Pas assez d'arguments", "Il n'y a pas d'arguments")
return
lst_val = []
while not self.stack.est_vide():
lst_val.append(self.stack.depiler().valeur)
if op == "somme":
res = 0
for val in lst_val:
res += val
if op == "produit":
res = 1
for val in lst_val:
res *= val
if self.stack.taille() < nb_arg:
if nb_arg - self.stack.taille() == 1:
messagebox.showerror("Pas assez d'arguments", "Il manque 1 argument")
else:
messagebox.showerror("Pas assez d'arguments", "Il manque "+str(nb_arg - self.stack.taille())+" arguments")
return
if nb_arg == 1:
val = self.stack.depiler().valeur
if op == "sin": res = sin(val)
if op == "cos": res = cos(val)
if op == "tan": res = tan(val)
if op == "asin": res = asin(val)
if op == "acos": res = acos(val)
if op == "atan": res = atan(val)
if op == "sqrt": res = sqrt(val)
if op == "!" or op == "factorielle": res = gamma(val + 1)
if op == "opp": res = - val
elif nb_arg == 2:
droite = self.stack.depiler().valeur
gauche = self.stack.depiler().valeur
if op == "+": res = gauche + droite
if op == "-": res = gauche - droite
if op == "*": res = gauche * droite
if op == "/": res = gauche / droite
if op == "^": res = gauche ** droite
elif nb_arg == 3:
terme3 = self.stack.depiler().valeur
terme2 = self.stack.depiler().valeur
terme1 = self.stack.depiler().valeur
if op == "moy3": res = (terme1 + terme2 + terme3)/3
self.stack.empiler(ValeurSaisie(res, self.frame_stack))
def evaluer_expr(self):
"""evalue la chaine de caractère dans le champ de saisie
selon l'affichage / la notation choisie (prefixe/infixe/postfixe)"""
lst_expr = self.saisie_expression.get().split()
if self.etat_affichage == "pre":
lst_expr.reverse()
if self.etat_affichage == "inf":
if "opp" in lst_expr:
messagebox.showerror("Erreur", "Vous ne pouvez pas utiliser la fonction 'opp' ici ; veuillez utiliser le signe '-' à la place.\n(Vous pouvez par contre utiliser 'opp' quand vous êtes en notation préfixée ou infixée.)")
return
try:
lst_expr = infixe2npi(lst_expr)
except SyntaxError as s:
explication = s.args[0]
messagebox.showerror("Erreur de syntaxe", explication)
return
except ValueError as v:
message, explication, i_char = v.args
messagebox.showerror(message,"Le terme « " + lst_expr[i_char] + " » "
+ explication + "\n\n"
+ "Dans l'expression, entre 【 crochet 】 :\n" + " ".join(lst_expr[:i_char])
+ "" + str(lst_expr[i_char]) + "" + " ".join(lst_expr[i_char+1:]))
return
try:
arbre_expr = toTree(lst_expr, self.etat_affichage)
self.affichage_expression.config(text = str(arbre_expr) + "=" + str(arbre_expr.evalue()))
except IndexError:
messagebox.showerror("Erreur","Erreur de syntaxe ici")
except ValueError as v:
explication = v.args[0]
if v.args[0] == "math domain error":
messagebox.showerror("Saisie invalide", "Le résultat n'est pas un nombre réel.")
else:
message, explication, i_char = v.args
if self.etat_affichage == "pre":
lst_expr.reverse()
i_char = len(lst_expr) - i_char - 1
messagebox.showerror(message,"Le terme « " + lst_expr[i_char] + " » "
+ explication + "\n\n"
+ "Dans l'expression, entre 【 crochet 】 :\n" + " ".join(lst_expr[:i_char])
+ "" + str(lst_expr[i_char]) + "" + " ".join(lst_expr[i_char+1:]))
except SyntaxError as s:
explication = s.args[0]
messagebox.showerror("Erreur de syntaxe", explication)
def effacer_tt(self):
"""supprime tout le contenu de la pile (et les labels associés)"""
while not self.stack.est_vide():
self.stack.depiler()
self.frame_stack.config(height = 1)
def effacer_dernier(self):
"""supprime la dernière valeur entrée (et le label associés)"""
if not self.stack.est_vide():
self.stack.depiler()
if __name__ == "__main__":
root = tk.Tk()
root.title("Calculatrice")
root.geometry("380x500")
app = Interface(root)
app.mainloop()