"""Classe d'application tk""" import math import tkinter as tk import tkinter.font from package.data_structure import Pile_chaine from package.expression import Expression def parse_string_to_list(text: str) -> list: """ Transforme le text en liste d'éléments intelligible par le programme """ text = list(text) result = [] buffer_function = "" buffer_number = "" number_first = False # Savoir dans quel ordre sont les chiffres et les lettres for char in text: if char == 'e': result.append(math.e) elif char == '𝜋': result.append(math.pi) elif char.isdigit() or char == ".": buffer_number += char if len(buffer_function) == 0: number_first = True elif char.isalpha(): buffer_function += char else: if number_first: result.append(float(buffer_number)) buffer_number = "" number_first = False if len(buffer_function) != 0: result.append('*') result.append(buffer_function) buffer_function = "" elif char == '(': result.append('*') else: if len(buffer_function) != 0: result.append(buffer_function) buffer_function = "" if len(buffer_number) != 0: result.append(float(buffer_number)) buffer_number = "" number_first = False result.append(char) if len(buffer_number) != 0: result.append(float(buffer_number)) if len(buffer_function) != 0: result.append('*') result.append(buffer_function) elif len(buffer_function) != 0: result.append(buffer_function) return result def npi2tree(expr: list) -> Expression: """ Renvoie l'arbre formé à partir de l'expression donnée""" pile = Pile_chaine() for val in expr: if not type(val) is float and val != "x": # on inverse pour avoir les nombres dans le bon ordre nombre2 = pile.depiler() if not pile.est_vide(): pile.empiler(Expression(val, pile.depiler(), nombre2)) else: pile.empiler(Expression(val, nombre2)) else: pile.empiler(Expression(val)) 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, '^': 3, '/': 2, '(': 0, ')': 0 } output = [] for val in expr: if type(val) is float 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() 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 class App(tk.Tk): """Classe d'application graphique si python < 3.7""" def __init__(self) -> None: super().__init__() self.fonction_screen_height = None self.fonction_screen_width = None # touches de la calculette self.keys = [] for i, val in enumerate(["←", "cos", "sin", "tan", "→", "x", "e", "√", "^", "𝜋", 7, 8, 9, "(", ")", 4, 5, 6, "*", "/", 1, 2, 3, "+", "-", "clear", 0, ".", "ln", "exe"]): # add numbers button if type(val) == int: self.keys.append(tk.Button(self, text=val, command=lambda x=val: self.add_value(x), background="blue", fg="gray90")) elif val == "clear": self.keys.append(tk.Button(self, text=val, command=self.clear_screen)) elif val == "←": self.keys.append(tk.Button(self, text=val, command=self.move_cursor_left)) elif val == "→": self.keys.append(tk.Button(self, text=val, command=self.move_cursor_right)) else: self.keys.append(tk.Button(self, text=val, command=lambda x=val: self.add_value(x))) self.keys[i].grid(row=i//5+1, column=i % 5+1, sticky="NSEW", padx=5, pady=5) # Calcul Frame self.calcul_frame = tk.Frame(self, bg="gray30") self.calcul_screen = tk.Label(self.calcul_frame, text="hello") self.calcul_screen.pack(fill="both", expand=True) self.calcul_entry = tk.Entry(self.calcul_frame, width=50) self.calcul_entry.pack(fill="both", expand=True) # Fonction Frame self.fonction_frame = tk.Frame(self, bg="gray30") self.fonction_bornes_frame = tk.Frame(self.fonction_frame) self.fonction_bornes_entry = tk.Entry(self.fonction_bornes_frame) self.fonction_bornes_entry.insert(0, "-100,100") self.fonction_bornes_entry.grid(sticky="ew", row=1, padx=10) self.fonction_bornes_text = tk.Label(self.fonction_bornes_frame, text="Entrez les bornes de tracé: (min, max)") self.fonction_bornes_text.grid(sticky="ew", row=0) self.fonction_bornes_frame.grid(sticky="ns", column=0, rowspan=2) self.fonction_screen = tk.Canvas(self.fonction_frame) self.fonction_screen.grid(sticky="nsew", column=1, row=0) self.fonction_entry = tk.Entry(self.fonction_frame) self.fonction_entry.grid(sticky='ew', column=1, row=1) # navigation menu self.navigation_frame = tk.Frame(self, bg="gray50") self.navigation_frame.grid(row=0, rowspan=10, column=0, sticky="nsew") self.titre = tk.Label(self.navigation_frame, text="Wx Calculator", compound="left" , font=tk.font.Font(size=15, weight="bold")) self.titre.grid(row=0, column=0, padx=20, pady=20) self.home_button = tk.Button(self.navigation_frame, height=3 , text="Calcul", bg="gray30", fg="gray10", anchor="w" , command=lambda: self.select_frame_by_name("Calcul")) self.home_button.grid(row=1, column=0, sticky="ew") self.function_button = tk.Button(self.navigation_frame, height=3 , text="Fonction", bg="gray30", fg="gray10", anchor="w" , command=lambda: self.select_frame_by_name("Fonction")) self.function_button.grid(row=2, column=0, sticky="ew") self.mode = 'Calcul' # select default frame self.select_frame_by_name("Calcul") def select_frame_by_name(self, name): """Change de mode : passe de calcul à fonction""" # set button color for selected button self.home_button.configure(bg="gray75" if name == "Calcul" else "gray30") self.function_button.configure(bg="gray75" if name == "Fonction" else "gray30") # show selected frame if name == "Calcul": self.mode = "Calcul" self.calcul_frame.grid(padx=20, pady=20, columnspan=5, column=1, row=0, sticky="nsew") self.keys[-1].configure(command=self.calculate) else: self.calcul_frame.grid_forget() if name == "Fonction": self.mode = "Fonction" self.fonction_frame.grid(columnspan=5, column=1, row=0, padx=20, pady=20, sticky="ns") self.fonction_frame.update() self.fonction_screen_width = self.fonction_screen.winfo_width() self.keys[-1].configure(command=self.draw_graph) self.fonction_screen_height = self.fonction_screen.winfo_height() else: self.fonction_frame.grid_forget() def add_value(self, value) -> None: """Ajoute le charactère à la suite de l'expression""" parenthesis = False if value == "e": value += "^" if value == "(": value += ")" parenthesis = True elif value in ["ln", "cos", "sin", "tan", "√"]: value += "()" parenthesis = True if self.mode == 'Fonction': self.fonction_entry.insert(tk.INSERT, value) else: self.calcul_entry.insert(tk.INSERT, value) if parenthesis: self.move_cursor_left() def clear_screen(self) -> None: """Enlève l'affichage actuel""" if self.mode == "Calcul": self.calcul_screen.configure(text="") self.calcul_entry.delete('0', 'end') else: self.fonction_screen.delete('all') self.fonction_entry.delete(0, 'end') def move_cursor_left(self): """Déplace le curseur à gauche""" if self.mode == "Calcul": pos = self.calcul_entry.index(tk.INSERT) if pos > 0: self.calcul_entry.icursor(pos -1) else: pos = self.fonction_entry.index(tk.INSERT) if pos > 0: self.fonction_entry.icursor(pos-1) def move_cursor_right(self): """Déplace le curseur à droite""" if self.mode == "Calcul": pos = self.calcul_entry.index(tk.INSERT) if pos < len(self.calcul_entry.get()): self.calcul_entry.icursor(pos + 1) else: pos = self.fonction_entry.index(tk.INSERT) if pos < len(self.fonction_entry.get()): self.fonction_entry.icursor(pos + 1) def draw_framing(self, min_x, max_x, min_y, max_y): """Dessine le cadrillage du graphique""" ratio = self.fonction_screen_height / self.fonction_screen_width # longueur par largeur du canvas # position du max dans l'interval abscisse = max_y / (abs(min_y)+abs(max_y)) ordonnee = max_x / (abs(min_x)+abs(max_x)) for i in range(20): # dessin des lignes verticales if i == int(20 - 20*ordonnee): self.fonction_screen.create_line(0 + i * self.fonction_screen_width/20, 0 , 0 + i * self.fonction_screen_width/20, self.fonction_screen_height , fill='red', width=4) text = self.fonction_screen.create_text(0 + i * self.fonction_screen_width/20, 10, text=max_y) rect = self.fonction_screen.create_rectangle(self.fonction_screen.bbox(text), fill="white") self.fonction_screen.tag_lower(rect, text) text = self.fonction_screen.create_text(0 + i * self.fonction_screen_width/20, self.fonction_screen_height - 10, text=min_y) rect = self.fonction_screen.create_rectangle(self.fonction_screen.bbox(text), fill="white") self.fonction_screen.tag_lower(rect, text) else: self.fonction_screen.create_line(0 + i * self.fonction_screen_width/20, 0 , 0 + i * self.fonction_screen_width/20, self.fonction_screen_height , fill='red') nb_abscisse_lines = int(20*ratio) # nombre de lignes horizontales à tracer for i in range(nb_abscisse_lines): # dessin des lignes horizontales if i == math.ceil(nb_abscisse_lines * abscisse): # tracer de l'axe des abscisses self.fonction_screen.create_line(0, 0 + i * self.fonction_screen_width/20 , self.fonction_screen_width , 0 + i * self.fonction_screen_width/20, fill='red' , width=4) text = self.fonction_screen.create_text(15, 0 + i * self.fonction_screen_width/20 , text=min_x) rect = self.fonction_screen.create_rectangle(self.fonction_screen.bbox(text) , fill="white") self.fonction_screen.tag_lower(rect, text) text = self.fonction_screen.create_text(self.fonction_screen_width - 15 , 0 + i * self.fonction_screen_width/20 , text=max_x) rect = self.fonction_screen.create_rectangle(self.fonction_screen.bbox(text), fill="white") self.fonction_screen.tag_lower(rect, text) else: self.fonction_screen.create_line(0, 0 + i * self.fonction_screen_width/20 , self.fonction_screen_width , 0 + i * self.fonction_screen_width/20, fill='red') def calculate(self) -> None: """Calcule dans la partie graphique""" exp = parse_string_to_list(self.calcul_entry.get()) exp = npi2tree(inf2npi(exp)) exp.print_tree() try: result = exp.evalue() except ZeroDivisionError: result = "ERREUR" self.calcul_screen.configure(text=result) def draw_graph(self): """Dessine les points du graphique""" self.fonction_screen.delete('all') min_x, max_x = map(int, self.fonction_bornes_entry.get().split(',')) fonction_points = npi2tree(inf2npi(parse_string_to_list(self.fonction_entry.get()))).valeurs_de_fonction(min_x, max_x) max_y = max(fonction_points, key=lambda item: item[1])[1] min_y = min(fonction_points, key=lambda item: item[1])[1] self.draw_framing(min_x, max_x, min_y, max_y) ratio = (fonction_points[0][0] - min_x) / (abs(max_x)+abs(min_x)) i = len(fonction_points) * ratio for (x, y) in fonction_points: image_x = i * (self.fonction_screen_width/len(fonction_points)) image_y = self.fonction_screen_height - (y - min_y) * self.fonction_screen_height / (abs(max_y)+abs(min_y)) self.fonction_screen.create_rectangle(image_x, image_y, image_x, image_y) i += 1