From 379dd660b3423c0aac562aceb81922fbaf3a65c8 Mon Sep 17 00:00:00 2001 From: arthur Date: Mon, 6 Mar 2023 17:27:55 +0100 Subject: [PATCH] patch --- package/{app.py => ctk_app.py} | 227 ---------------------- package/tk_app.py | 331 +++++++++++++++++++++++++++++++++ 2 files changed, 331 insertions(+), 227 deletions(-) rename package/{app.py => ctk_app.py} (58%) create mode 100644 package/tk_app.py diff --git a/package/app.py b/package/ctk_app.py similarity index 58% rename from package/app.py rename to package/ctk_app.py index c9f8ac8..6ee51c6 100644 --- a/package/app.py +++ b/package/ctk_app.py @@ -2,7 +2,6 @@ import math import customtkinter as ctk -import tkinter as tk from package.data_structure import Pile_chaine from package.expression import Expression @@ -342,229 +341,3 @@ class Ctk_app(ctk.CTk): 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) - - -class Tk_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) - for i, (x, y) in enumerate(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) \ No newline at end of file diff --git a/package/tk_app.py b/package/tk_app.py new file mode 100644 index 0000000..45fe8ac --- /dev/null +++ b/package/tk_app.py @@ -0,0 +1,331 @@ +"""Classe d'application tk""" + +import math +import tkinter as tk +from package.data_structure import Pile_chaine +from package.expression import Expression + + +def change_appearance_mode_event(new_appearance_mode: str) -> None: + """Change le thème de sombre à Claire""" + ctk.set_appearance_mode(new_appearance_mode) + +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 Tk_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) + for i, (x, y) in enumerate(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) \ No newline at end of file