# pyhasher/gui/gui.py import tkinter as tk from tkinter import filedialog, messagebox import os # Import the core logic function from pyhasher.core.core import calculate_hashes_for_file # Defines the order in which hashes will be displayed in the GUI. # This must match the keys returned by the core function. HASH_DISPLAY_ORDER = ( "CRC32", "Adler-32", "MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512", "SHA3-256", "BLAKE2b", ) # --- Import Version Info FOR THE WRAPPER ITSELF --- try: # Use absolute import based on package name from pyhasher import _version as wrapper_version WRAPPER_APP_VERSION_STRING = f"{wrapper_version.__version__} ({wrapper_version.GIT_BRANCH}/{wrapper_version.GIT_COMMIT_HASH[:7]})" WRAPPER_BUILD_INFO = f"Wrapper Built: {wrapper_version.BUILD_TIMESTAMP}" except ImportError: # This might happen if you run the wrapper directly from source # without generating its _version.py first (if you use that approach for the wrapper itself) WRAPPER_APP_VERSION_STRING = "(Dev Wrapper)" WRAPPER_BUILD_INFO = "Wrapper build time unknown" # --- End Import Version Info --- # --- Constants for Version Generation --- DEFAULT_VERSION = "0.0.0+unknown" DEFAULT_COMMIT = "Unknown" DEFAULT_BRANCH = "Unknown" # --- End Constants --- class PyHasherApp: """ The main GUI application class for PyHasher. It handles the user interface and interactions. """ def __init__(self, root_window): """ Initialize the application. Args: root_window: The main tkinter window. """ self.root = root_window self.root.title( f"PyHasher - {WRAPPER_APP_VERSION_STRING}" ) self.root.geometry("1024x400") # Increased height for more hashes self.file_path = tk.StringVar() # --- Main frame --- main_frame = tk.Frame(self.root, padx=10, pady=10) main_frame.pack(fill=tk.BOTH, expand=True) # --- File selection widgets --- self._create_file_selection_widgets(main_frame) # --- Hash results widgets --- self._create_hash_display_widgets(main_frame) # --- Action buttons --- self._create_action_buttons(main_frame) def _create_file_selection_widgets(self, parent_frame): """Creates widgets for file selection.""" file_frame = tk.Frame(parent_frame) file_frame.pack(fill=tk.X, pady=(0, 10)) file_label = tk.Label(file_frame, text="File:") file_label.pack(side=tk.LEFT, padx=(0, 5)) file_entry = tk.Entry(file_frame, textvariable=self.file_path, state="readonly") file_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) browse_button = tk.Button(file_frame, text="Browse...", command=self.select_file) browse_button.pack(side=tk.LEFT, padx=(5, 0)) def _create_hash_display_widgets(self, parent_frame): """Creates labels and entry boxes for displaying hash results.""" results_frame = tk.Frame(parent_frame) results_frame.pack(fill=tk.BOTH, expand=True) # Dictionary to hold hash names and their corresponding tk.StringVar self.hash_vars = {name: tk.StringVar() for name in HASH_DISPLAY_ORDER} # Create labels and read-only entry widgets for each hash type for i, name in enumerate(HASH_DISPLAY_ORDER): label = tk.Label(results_frame, text=f"{name}:") label.grid(row=i, column=0, sticky="w", pady=2) entry = tk.Entry(results_frame, textvariable=self.hash_vars[name], state="readonly") entry.grid(row=i, column=1, sticky="ew", pady=2, padx=(5, 0)) # Configure the grid to expand the entry column results_frame.columnconfigure(1, weight=1) def _create_action_buttons(self, parent_frame): """Creates the main action buttons.""" button_frame = tk.Frame(parent_frame) button_frame.pack(fill=tk.X, pady=(10, 0), side=tk.BOTTOM) calculate_button = tk.Button( button_frame, text="Calculate Hashes", command=self.calculate_hashes ) calculate_button.pack(side=tk.RIGHT) def select_file(self): """Opens a file dialog to select a file and clears old results.""" path = filedialog.askopenfilename() if path: self.file_path.set(path) # Clear previous results when a new file is selected for var in self.hash_vars.values(): var.set("") def calculate_hashes(self): """ Gets the file path, calls the core function to calculate hashes, and displays the results in the GUI. """ current_file_path = self.file_path.get() if not current_file_path or not os.path.exists(current_file_path): messagebox.showerror("Error", "Please select a valid file first.") return try: # Call the decoupled core function to do the heavy lifting all_hashes = calculate_hashes_for_file(current_file_path) # Update the GUI with the results from the core function for name, value in all_hashes.items(): if name in self.hash_vars: self.hash_vars[name].set(value) except IOError as e: messagebox.showerror("Error", f"Could not read file: {e}") except Exception as e: messagebox.showerror("Error", f"An unexpected error occurred: {e}")