diff --git a/Assets/Icons/clock.png b/Assets/Icons/clock.png index 3c01ef4..1dc7adf 100644 Binary files a/Assets/Icons/clock.png and b/Assets/Icons/clock.png differ diff --git a/arduino_logique.py b/arduino_logique.py index 9219388..957ff94 100644 --- a/arduino_logique.py +++ b/arduino_logique.py @@ -4,10 +4,9 @@ simulating logic circuits using Tkinter. It includes functionality to initialize a canvas, draw a breadboard, etc. """ - +import os from pathlib import Path import tkinter as tk -from tkinter import font from breadboard import Breadboard from component_sketch import ComponentSketcher from menus import Menus @@ -15,6 +14,11 @@ from toolbar import Toolbar from utils import resource_path +if os.name == "darwin" or os.name == "posix": + from tkinter import font + from tkmacosx import Button # type: ignore +else: + from tkinter import Button, font def main(): """ @@ -24,7 +28,7 @@ def main(): # Creating main window win = tk.Tk() win.title("Laboratoire virtuel de circuit logique - GIF-1002") - win.geometry("1700x800") # Initial window size + win.geometry("1500x800") # Initial window size win.resizable(False, False) # Disabling window resizing win.configure(bg="#333333") # Setting consistent background color @@ -50,7 +54,7 @@ def main(): # Creating the toolbar instance toolbar = Toolbar(parent=win, canvas=canvas, sketcher=sketcher, current_dict_circuit=sketcher.current_dict_circuit) # Placing the secondary top bar in row=1, column=1 (spanning only the canvas area) - toolbar.topbar_frame.grid(row=1, column=1, sticky="ew", padx=(0, 10), pady=(0, 0)) + toolbar.topbar_frame.grid(row=1, column=1, sticky="ew", padx=(0, 0), pady=(0, 0)) # Set initial scale factor initial_scale = 1.0 # Equivalent to 10.0 / 10.0 @@ -69,6 +73,22 @@ def main(): toolbar=toolbar, ) + def toggle_sidebar(): + sidebar.toggle_sidebar() + if sidebar.is_sidebar_visible: + toggle_sidebar_btn.config(text="<<") + else: + toggle_sidebar_btn.config(text=">>") + + toggle_sidebar_btn = Button( + win, + text="<<", + command=toggle_sidebar, + bg="#333333", + fg="#FFFFFF", + ) + toggle_sidebar_btn.grid(row=1, column=0, sticky="w") + def refresh_sidebar(): sidebar.refresh() win.after(5000, refresh_sidebar) diff --git a/breadboard.py b/breadboard.py index e3395d9..1eeb973 100644 --- a/breadboard.py +++ b/breadboard.py @@ -225,7 +225,7 @@ def draw_blank_board_model(self, x_origin: int = 50, y_origin: int = 10, battery ] self.sketcher.circuit(x_origin, y_origin, scale=self.sketcher.scale_factor, model=blank_board_model) - battery_x = x_origin + 1200 # Adjust as needed for proper positioning + battery_x = x_origin + 1050 # Adjust as needed for proper positioning battery_y = y_origin + 300 # Adjust as needed for proper positioning # Reset all matrix elements' states to FREE diff --git a/component_sketch.py b/component_sketch.py index e8cc9fc..872b65e 100644 --- a/component_sketch.py +++ b/component_sketch.py @@ -2764,8 +2764,8 @@ def draw_battery( original_width = battery_photo.width() original_height = battery_photo.height() - new_width = int(original_width * scale * 0.7) - new_height = int(original_height * scale * 0.7) + new_width = int(original_width * scale * 0.4) + new_height = int(original_height * scale * 0.4) scale_x = new_width / original_width scale_y = new_height / original_height @@ -2817,8 +2817,8 @@ def draw_battery( if neg_wire_end: neg_wire_end_x, neg_wire_end_y = neg_wire_end else: - neg_wire_end_x = neg_wire_start_x - 100 * scale # Wires go to the left - neg_wire_end_y = neg_wire_start_y + neg_wire_end_x = neg_wire_start_x - 50 * scale # Wires go to the left + neg_wire_end_y = neg_wire_start_y - 50 * scale # Wires go up self.draw_battery_wire( wire_id=neg_wire_id, @@ -2834,8 +2834,8 @@ def draw_battery( if pos_wire_end: pos_wire_end_x, pos_wire_end_y = pos_wire_end else: - pos_wire_end_x = pos_wire_start_x - 100 * scale - pos_wire_end_y = pos_wire_start_y + pos_wire_end_x = pos_wire_start_x - 50 * scale + pos_wire_end_y = pos_wire_start_y + 50 * scale # Wires go down self.draw_battery_wire( wire_id=pos_wire_id, diff --git a/menus.py b/menus.py index fdded35..fc1a0c6 100644 --- a/menus.py +++ b/menus.py @@ -121,9 +121,12 @@ def __init__( self.serial_port = SerialPort(None, 115200, 1, None) """The serial port configuration.""" + self.open_file_path: str | None = None + """The file path for the current open file.""" + # Define menu items and their corresponding dropdown options menus = { - "Fichier": ["Nouveau", "Ouvrir", "Enregistrer", "Quitter"], + "Fichier": ["Nouveau", "Ouvrir", "Enregistrer", "Enregistrer sous", "Quitter"], "Microcontrôleur": ["Choisir un microcontrôleur", "Table de correspondance", "Configurer le port série"], "Exporter": ["Vérifier", "Téléverser"], "Aide": ["Documentation", "À propos"], @@ -132,7 +135,8 @@ def __init__( menu_commands = { "Nouveau": self.new_file, "Ouvrir": self.open_file, - "Enregistrer": self.save_file, + "Enregistrer": lambda: self.save_file(False), + "Enregistrer sous": self.save_file, "Quitter": self.parent.quit, "Configurer le port série": self.configure_ports, "Table de correspondance": self.show_correspondence_table, @@ -152,12 +156,14 @@ def __init__( fg="white", font=("FiraCode-Bold", 12), ) - self.microcontroller_label.pack(side="right", padx=10) + self.microcontroller_label.pack(side="right", fill="y", padx=175) # Bind to parent to close dropdowns when clicking outside self.parent.bind("", self.close_dropdown, add="+") self.canvas.bind("", self.close_dropdown, add="+") + self.parent.bind("", lambda _: self.save_file(False), add="+") + def select_microcontroller(self): """Handler for microcontroller selection.""" # Create a new top-level window for the dialog @@ -405,6 +411,7 @@ def close_dropdown(self, event): def new_file(self): """Handler for the 'New' menu item.""" # Clear the canvas and reset the circuit + self.open_file_path = None self.board.sketcher.clear_board() self.board.fill_matrix_1260_pts() self.board.draw_blank_board_model() @@ -456,6 +463,7 @@ def open_file(self): print(f"Unspecified component: {key}") messagebox.showinfo("Ouvrir un fichier", f"Circuit chargé depuis {file_path}") + self.open_file_path = file_path except Exception as e: print(f"Error loading file: {e}") messagebox.showerror( @@ -521,12 +529,15 @@ def load_io(self, io_data): ] self.board.sketcher.circuit(x_o, y_o, model=model_io) - def save_file(self): + def save_file(self, prompt_for_path: bool = True): """Handler for the 'Save' menu item.""" print("Save File") - file_path = filedialog.asksaveasfilename( - defaultextension=".json", filetypes=[("JSON files", "*.json"), ("All files", "*.*")] - ) + if prompt_for_path or not self.open_file_path: + file_path = filedialog.asksaveasfilename( + defaultextension=".json", filetypes=[("JSON files", "*.json"), ("All files", "*.*")] + ) + else: + file_path = self.open_file_path if file_path: try: circuit_data = deepcopy(self.current_dict_circuit) @@ -546,6 +557,7 @@ def save_file(self): json.dump(circuit_data, file, indent=4) print(f"Circuit saved to {file_path}") messagebox.showinfo("Sauvegarde réussie", f"Circuit sauvegardé dans {file_path}") + self.open_file_path = file_path except (TypeError, KeyError) as e: print(f"Error saving file: {e}") messagebox.showerror( diff --git a/sidebar.py b/sidebar.py index 37498f3..bf65543 100644 --- a/sidebar.py +++ b/sidebar.py @@ -78,26 +78,35 @@ def __init__( self.saved_bindings: dict[str, Callable] = {} # Creating the sidebar frame - sidebar_frame = tk.Frame(parent, bg="#333333", width=275, bd=0, highlightthickness=0) - sidebar_frame.grid(row=2, column=0, sticky="nsew", padx=0, pady=0) - sidebar_frame.grid_propagate(False) # Preventing frame from resizing + self.sidebar_frame = tk.Frame(parent, bg="#333333", width=275, bd=0, highlightthickness=0) + self.sidebar_frame.grid(row=2, column=0, sticky="nsew", padx=0, pady=0) + self.sidebar_frame.grid_propagate(False) # Preventing frame from resizing + + self.is_sidebar_visible = True # Configuring grid weights for the sidebar - sidebar_frame.grid_rowconfigure(0, weight=0) # Search bar - sidebar_frame.grid_rowconfigure(1, weight=0) # Chips label - sidebar_frame.grid_rowconfigure(2, weight=8) # Chips area (80%) - sidebar_frame.grid_rowconfigure(3, weight=0) # Manage button - sidebar_frame.grid_columnconfigure(0, weight=1) + self.sidebar_frame.grid_rowconfigure(0, weight=0) # Search bar + self.sidebar_frame.grid_rowconfigure(1, weight=0) # Chips label + self.sidebar_frame.grid_rowconfigure(2, weight=8) # Chips area (80%) + self.sidebar_frame.grid_rowconfigure(3, weight=0) # Manage button + self.sidebar_frame.grid_columnconfigure(0, weight=1) self.sidebar_grid = SidebarGrid(columns=2, visible_rows=12, grid_capacity=24) # Creating sidebar components - self.create_search_bar(sidebar_frame) - self.create_chips_area(sidebar_frame) - self.create_manage_button(sidebar_frame) + self.create_search_bar(self.sidebar_frame) + self.create_chips_area(self.sidebar_frame) + self.create_manage_button(self.sidebar_frame) self.chip_files_mtimes = get_chip_modification_times() + def toggle_sidebar(self): + if self.is_sidebar_visible: + self.sidebar_frame.grid_remove() + else: + self.sidebar_frame.grid() + self.is_sidebar_visible = not self.is_sidebar_visible + def initialize_chip_data(self, current_dict_circuit, chip_images_path) -> None: """ Initializes the chip data for the sidebar. diff --git a/toolbar.py b/toolbar.py index 450ad45..7927779 100644 --- a/toolbar.py +++ b/toolbar.py @@ -9,6 +9,7 @@ from dataclasses import dataclass from pathlib import Path import tkinter as tk +from idlelib.tooltip import Hovertip # type: ignore from component_sketch import ComponentSketcher from dataCDLT import INPUT, OUTPUT, FREE, CLOCK @@ -67,20 +68,20 @@ def create_topbar(self, parent: tk.Tk): # Create left and right subframes left_frame = tk.Frame(self.topbar_frame, bg="#505050") - left_frame.pack(side=tk.LEFT, padx=5, pady=5) + left_frame.pack(side=tk.LEFT, padx=50, pady=5) right_frame = tk.Frame(self.topbar_frame, bg="#505050") - right_frame.pack(side=tk.RIGHT, padx=5, pady=5) + right_frame.pack(after=left_frame, side=tk.LEFT, padx=100, pady=5) # Load images images = self.load_images() # Create buttons in the left frame - self.create_button("Connection", left_frame, images) + self.create_button("Connection", left_frame, images, "Ajouter une connexion") # self.create_button("Power", left_frame, images) # à ajouter après si besoin - self.create_button("Input", left_frame, images) - self.create_button("Output", left_frame, images) - self.create_button("Clock", left_frame, images) + self.create_button("Input", left_frame, images, "Ajouter une entrée") + self.create_button("Output", left_frame, images, "Ajouter une sortie") + self.create_button("Clock", left_frame, images, "Ajouter une horloge") # Create the color chooser and Delete button in the right frame self.color_button = Button( @@ -94,8 +95,9 @@ def create_topbar(self, parent: tk.Tk): borderwidth=0, highlightthickness=0, ) + Hovertip(self.color_button, "Choisir une couleur pour les composantes", 500) self.color_button.pack(side=tk.LEFT, padx=2, pady=2) - self.create_button("Delete", right_frame, images) + self.create_button("Delete", right_frame, images, "Supprimer un composant") def load_images(self) -> dict[str, tk.PhotoImage | None]: """ @@ -123,7 +125,7 @@ def load_images(self) -> dict[str, tk.PhotoImage | None]: return images - def create_button(self, action: str, parent_frame: tk.Frame, images: dict[str, tk.PhotoImage | None]) -> None: + def create_button(self, action: str, parent_frame: tk.Frame, images: dict[str, tk.PhotoImage | None], hovertext: str) -> None: """ Helper method to create a button in the specified frame with an icon. @@ -164,6 +166,7 @@ def create_button(self, action: str, parent_frame: tk.Frame, images: dict[str, t borderwidth=0, highlightthickness=0, ) + Hovertip(btn, hovertext, 500) btn.pack(side=tk.LEFT, padx=10, pady=2) # Minimal spacing between buttons self.buttons[action] = btn # Store button reference