|
|
|
from data_structure import *
|
|
|
|
import customtkinter as ctk
|
|
|
|
|
|
|
|
class Expression:
|
|
|
|
"""manipule les expression sous forme d'arbre"""
|
|
|
|
def __init__(self, val, fils_gauche, fils_droit) -> None:
|
|
|
|
self.val = val
|
|
|
|
self.fils_gauche = fils_gauche
|
|
|
|
self.fils_droit = fils_droit
|
|
|
|
|
|
|
|
def __str__(self) -> str:
|
|
|
|
"""renvoie l'expression sous forme infixé"""
|
|
|
|
if self.est_feuille():
|
|
|
|
return str(self.val)
|
|
|
|
return '('+self.fils_gauche.__str__()+str(self.val)+self.fils_droit.__str__()+')'
|
|
|
|
|
|
|
|
def est_feuille(self) -> bool:
|
|
|
|
"""renvoie true si le noeud est une feuille"""
|
|
|
|
if self.fils_droit is None and self.fils_gauche is None:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def evalue(self, x=0) -> float:
|
|
|
|
"""renvoie le résultat de l'expression"""
|
|
|
|
if self.est_feuille():
|
|
|
|
if self.val == 'x':
|
|
|
|
return x
|
|
|
|
return float(self.val)
|
|
|
|
if self.val == '+':
|
|
|
|
return self.fils_gauche.evalue(x) + self.fils_droit.evalue(x)
|
|
|
|
if self.val == '*':
|
|
|
|
return self.fils_gauche.evalue(x) * self.fils_droit.evalue(x)
|
|
|
|
if self.val == '/':
|
|
|
|
return self.fils_gauche.evalue(x) / self.fils_droit.evalue(x)
|
|
|
|
if self.val == '^':
|
|
|
|
return self.fils_gauche.evalue(x) ** self.fils_droit.evalue(x)
|
|
|
|
if self.val == '-':
|
|
|
|
return self.fils_gauche.evalue(x) - self.fils_droit.evalue(x)
|
|
|
|
|
|
|
|
def valeurs_de_fonction(self):
|
|
|
|
"""calcul les 100 premieres valeurs"""
|
|
|
|
result = []
|
|
|
|
for i in range(-100, 100):
|
|
|
|
result.append((i, self.evalue(i)))
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class App(ctk.CTk):
|
|
|
|
def __init__(self) -> None:
|
|
|
|
super().__init__()
|
|
|
|
self.grid_columnconfigure(0, weight=0)
|
|
|
|
|
|
|
|
# Calcul Frame
|
|
|
|
|
|
|
|
self.calcul_frame = ctk.CTkFrame(self,corner_radius=0, fg_color="transparent")
|
|
|
|
|
|
|
|
self.screen = ctk.CTkTextbox(self.calcul_frame)
|
|
|
|
self.screen.pack(padx=20, pady=20, fill="both", expand=True)
|
|
|
|
|
|
|
|
self.numbers = []
|
|
|
|
|
|
|
|
for i, val in enumerate(["(", ")", "/", "^", 7,8,9,"*", 4, 5, 6, "-", 1, 2, 3, "+", "clear", 0, '.', "exe"]): # add numbers button
|
|
|
|
self.numbers.append(ctk.CTkButton(self, text=val))
|
|
|
|
if val == "clear":
|
|
|
|
self.numbers[i]._command = self.textbox_clear
|
|
|
|
elif val == "exe":
|
|
|
|
self.numbers[i]._command = self.calculate
|
|
|
|
else:
|
|
|
|
self.numbers[i]._command = lambda x=val: self.add_value(x)
|
|
|
|
self.numbers[i].grid(row=1+i//4, column=i%4+1, sticky="NSEW", padx=5, pady=5)
|
|
|
|
|
|
|
|
# Fonction Frame
|
|
|
|
|
|
|
|
self.fonction_frame = ctk.CTkFrame(self, corner_radius=0, fg_color="transparent")
|
|
|
|
|
|
|
|
self.fonction_screen = ctk.CTkCanvas(self.fonction_frame)
|
|
|
|
self.fonction_screen.pack(padx=20, pady=20, fill="both", expand=True)
|
|
|
|
|
|
|
|
|
|
|
|
# navigation menu
|
|
|
|
self.navigation_frame = ctk.CTkFrame(self, corner_radius=0)
|
|
|
|
self.navigation_frame.grid_rowconfigure(4, weight=1)
|
|
|
|
self.navigation_frame.grid(row=0, rowspan=10, column=0, sticky="nsew")
|
|
|
|
|
|
|
|
self.titre = ctk.CTkLabel(self.navigation_frame, text="Wx Calculator",
|
|
|
|
compound="left", font=ctk.CTkFont(size=15, weight="bold"))
|
|
|
|
self.titre.grid(row=0, column=0, padx=20, pady=20)
|
|
|
|
|
|
|
|
self.home_button = ctk.CTkButton(self.navigation_frame, corner_radius=0, height=40, border_spacing=10, text="Calcul",
|
|
|
|
fg_color="transparent", text_color=("gray10", "gray90"), hover_color=("gray70", "gray30"),
|
|
|
|
anchor="w", command=lambda: self.select_frame_by_name("Calcul"))
|
|
|
|
self.home_button.grid(row=1, column=0, sticky="ew")
|
|
|
|
|
|
|
|
self.function_button = ctk.CTkButton(self.navigation_frame, corner_radius=0, height=40, border_spacing=10, text="Fonction",
|
|
|
|
fg_color="transparent", text_color=("gray10", "gray90"), hover_color=("gray70", "gray30"),
|
|
|
|
anchor="w", command=lambda: self.select_frame_by_name("Fonction"))
|
|
|
|
self.function_button.grid(row=2, column=0, sticky="ew")
|
|
|
|
|
|
|
|
self.appearance_mode_menu = ctk.CTkOptionMenu(self.navigation_frame, values=["Light", "Dark", "System"],
|
|
|
|
command=self.change_appearance_mode_event)
|
|
|
|
self.appearance_mode_menu.grid(row=4, column=0, padx=20, pady=20, sticky="s")
|
|
|
|
|
|
|
|
# select default frame
|
|
|
|
self.select_frame_by_name("Calcul")
|
|
|
|
|
|
|
|
def select_frame_by_name(self, name):
|
|
|
|
# set button color for selected button
|
|
|
|
self.home_button.configure(fg_color=("gray75", "gray25") if name == "Calcul" else "transparent")
|
|
|
|
self.function_button.configure(fg_color=("gray75", "gray25") if name == "Fonction" else "transparent")
|
|
|
|
|
|
|
|
# show selected frame
|
|
|
|
if name == "Calcul":
|
|
|
|
self.calcul_frame.grid(columnspan=4, column=1, row=0, sticky="nsew")
|
|
|
|
else:
|
|
|
|
self.calcul_frame.grid_forget()
|
|
|
|
if name == "Fonction":
|
|
|
|
self.fonction_frame.grid(columnspan=4, column=1, row=0, sticky="nsew")
|
|
|
|
else:
|
|
|
|
self.fonction_frame.grid_forget()
|
|
|
|
|
|
|
|
def change_appearance_mode_event(self, new_appearance_mode):
|
|
|
|
ctk.set_appearance_mode(new_appearance_mode)
|
|
|
|
|
|
|
|
def add_value(self, value) -> None:
|
|
|
|
self.screen.insert('end', value)
|
|
|
|
|
|
|
|
def textbox_clear(self):
|
|
|
|
self.screen.delete("0.0", "end")
|
|
|
|
|
|
|
|
def draw_framing(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def calculate(self) -> None:
|
|
|
|
exp = list(self.screen.get("0.0", "end").strip())
|
|
|
|
exp = inf2npi(exp)
|
|
|
|
result = npi2tree(exp).evalue()
|
|
|
|
self.textbox_clear()
|
|
|
|
self.screen.insert("end", result)
|
|
|
|
|
|
|
|
|
|
|
|
def npi2tree(expr: list) -> Expression:
|
|
|
|
"""renvoie l'arbre formé a partir de l'expression donnée"""
|
|
|
|
pile = Pile_chaine()
|
|
|
|
for val in expr:
|
|
|
|
if not val.isdigit() and val != "x":
|
|
|
|
# on inverse pour avoir les nombres dans le bon ordre
|
|
|
|
nombre2, nombre1 = pile.depiler(), pile.depiler()
|
|
|
|
pile.empiler(Expression(val, nombre1, nombre2))
|
|
|
|
else:
|
|
|
|
pile.empiler(Expression(val, None, None))
|
|
|
|
return pile.sommet()
|
|
|
|
|
|
|
|
def inf2npi(expr: list) -> list:
|
|
|
|
"""Transforme une expression infixé en notation polonaise inversée"""
|
|
|
|
operator_stack = Pile_chaine()
|
|
|
|
operator_priority = {
|
|
|
|
'+': 1,
|
|
|
|
'-': 1,
|
|
|
|
'*': 2,
|
|
|
|
'^': 2,
|
|
|
|
'/': 2,
|
|
|
|
'(': 0,
|
|
|
|
')': 0
|
|
|
|
}
|
|
|
|
output = []
|
|
|
|
for val in expr:
|
|
|
|
if val.isdigit() or val == 'x':
|
|
|
|
output.append(val)
|
|
|
|
else:
|
|
|
|
if operator_stack.est_vide() or ( val == '(' or operator_priority[val] > operator_priority[operator_stack.sommet()]):
|
|
|
|
operator_stack.empiler(val)
|
|
|
|
else:
|
|
|
|
while not operator_stack.est_vide():
|
|
|
|
if operator_stack.sommet() == '(':
|
|
|
|
operator_stack.depiler()
|
|
|
|
if operator_stack.est_vide(): # test si il y a un astérix avant la parenthèse
|
|
|
|
output.append('*')
|
|
|
|
elif operator_stack.sommet() != '*':
|
|
|
|
output.append('*')
|
|
|
|
else:
|
|
|
|
output.append(operator_stack.depiler())
|
|
|
|
else:
|
|
|
|
output.append(operator_stack.depiler())
|
|
|
|
if val != ')':
|
|
|
|
operator_stack.empiler(val)
|
|
|
|
while not operator_stack.est_vide():
|
|
|
|
output.append(operator_stack.depiler())
|
|
|
|
return output
|
|
|
|
|
|
|
|
# [3, '-', 6, '*', 4, '+', 3]
|
|
|
|
exp = inf2npi(list('x^2'))
|
|
|
|
print(npi2tree(exp).evalue(2))
|
|
|
|
|
|
|
|
print(npi2tree(exp).valeurs_de_fonction())
|
|
|
|
|
|
|
|
gui = App()
|
|
|
|
gui.mainloop()
|