From 42aa62a1b267d2efcf706332e47764d07cd0f3a3 Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Wed, 12 Nov 2025 14:28:33 +0100 Subject: [PATCH] =?UTF-8?q?aggiunta=20la=20possibilit=C3=A0=20di=20gestire?= =?UTF-8?q?=20repository=20che=20hanno=20nomi=20diversi=20rispetto=20la=20?= =?UTF-8?q?cartella=20del=20sorgente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyinstallerguiwrapper/gui/project_selector.py | 190 ++++++++++++++++-- 1 file changed, 177 insertions(+), 13 deletions(-) diff --git a/pyinstallerguiwrapper/gui/project_selector.py b/pyinstallerguiwrapper/gui/project_selector.py index 060a507..2aef95d 100644 --- a/pyinstallerguiwrapper/gui/project_selector.py +++ b/pyinstallerguiwrapper/gui/project_selector.py @@ -69,7 +69,7 @@ class ProjectManager: self.derived_main_script_path: Optional[str] = None self.derived_spec_path: Optional[str] = None self.derived_icon_path: Optional[str] = None - self.derived_source_dir_path: Optional[path.Path] = None # pathlib.Path object + self.derived_source_dir_path: Optional[pathlib.Path] = None # pathlib.Path object self.project_root_name: Optional[str] = None def _log(self, message: str, level: str = "INFO") -> None: @@ -79,10 +79,128 @@ class ProjectManager: except TypeError: self.logger(f"[{level}][ProjectManager] {message}") + def _find_source_folders_with_main(self, project_root: pathlib.Path) -> list[pathlib.Path]: + """ + Finds all direct subdirectories of the project root that contain a __main__.py file. + + Args: + project_root: The root directory of the project. + + Returns: + A list of Path objects representing subdirectories containing __main__.py. + """ + candidates = [] + try: + for item in project_root.iterdir(): + if item.is_dir() and not item.name.startswith('.') and not item.name.startswith('_'): + main_script = item / "__main__.py" + if main_script.is_file(): + candidates.append(item) + self._log(f"Found candidate source folder: {item.name}", level="DEBUG") + except Exception as e: + self._log(f"Error scanning directory: {e}", level="ERROR") + + return candidates + + def _ask_user_for_source_folder(self, project_root: pathlib.Path, candidates: list[pathlib.Path]) -> Optional[pathlib.Path]: + """ + Asks the user to select the correct source folder either from candidates or by manual selection. + + Args: + project_root: The root directory of the project. + candidates: List of candidate folders found automatically. + + Returns: + The selected source folder Path, or None if cancelled. + """ + dialog = tk.Toplevel(self.parent_gui) + dialog.title("Select Source Folder") + dialog.geometry("600x400") + dialog.transient(self.parent_gui) + dialog.grab_set() + + selected_folder = None + + def on_select(): + nonlocal selected_folder + selection = listbox.curselection() + if selection: + idx = selection[0] + if idx < len(candidates): + selected_folder = candidates[idx] + dialog.destroy() + + def on_browse(): + nonlocal selected_folder + folder = filedialog.askdirectory( + title="Select the source folder containing __main__.py", + initialdir=str(project_root), + parent=dialog + ) + if folder: + folder_path = pathlib.Path(folder) + # Verify it contains __main__.py + if (folder_path / "__main__.py").is_file(): + selected_folder = folder_path + dialog.destroy() + else: + messagebox.showerror( + "Invalid Selection", + f"The selected folder does not contain a __main__.py file.", + parent=dialog + ) + + def on_cancel(): + dialog.destroy() + + # Message label + msg_frame = ttk.Frame(dialog, padding=10) + msg_frame.pack(fill=tk.X) + + if candidates: + msg_text = "Multiple source folders found. Please select the correct one:" + else: + msg_text = "No source folder found automatically. Please browse to select the folder containing __main__.py:" + + ttk.Label(msg_frame, text=msg_text, wraplength=560).pack() + + # Listbox with candidates + if candidates: + list_frame = ttk.Frame(dialog, padding=10) + list_frame.pack(fill=tk.BOTH, expand=True) + + scrollbar = ttk.Scrollbar(list_frame) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + listbox = tk.Listbox(list_frame, yscrollcommand=scrollbar.set) + listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + scrollbar.config(command=listbox.yview) + + for candidate in candidates: + listbox.insert(tk.END, candidate.name) + + listbox.bind('', lambda e: on_select()) + else: + listbox = None + + # Buttons + btn_frame = ttk.Frame(dialog, padding=10) + btn_frame.pack(fill=tk.X) + + if candidates: + ttk.Button(btn_frame, text="Select", command=on_select).pack(side=tk.LEFT, padx=5) + + ttk.Button(btn_frame, text="Browse...", command=on_browse).pack(side=tk.LEFT, padx=5) + ttk.Button(btn_frame, text="Cancel", command=on_cancel).pack(side=tk.RIGHT, padx=5) + + dialog.wait_window() + return selected_folder + def select_project_directory(self) -> None: """ Opens a file dialog for the user to select the main project directory. Derives and updates paths for the main script, .spec file, and icon. + If the expected source folder is not found, searches for alternatives and asks the user. """ self._log( "=" * 20 + " PROJECT DIRECTORY SELECTION STARTED " + "=" * 20, level="INFO" @@ -101,19 +219,61 @@ class ProjectManager: self.project_root_name = project_root.name project_name_lower = self.project_root_name.lower() - # Derived paths as strings for direct assignment to Tkinter variables/attributes - self.derived_source_dir_path = project_root / project_name_lower + # Try to find the source directory - first check the expected location + expected_source_dir = project_root / project_name_lower + source_folder_name = project_name_lower # Default to expected name + + if (expected_source_dir / "__main__.py").is_file(): + # Expected location found - use it + self.derived_source_dir_path = expected_source_dir + self._log(f"Using expected source folder: {source_folder_name}", level="INFO") + else: + # Expected location not found - search for alternatives + self._log(f"Expected source folder '{project_name_lower}' not found or does not contain __main__.py", level="WARNING") + self._log("Searching for alternative source folders...", level="INFO") + + candidates = self._find_source_folders_with_main(project_root) + + if len(candidates) == 1: + # Only one candidate found - use it automatically + self.derived_source_dir_path = candidates[0] + source_folder_name = candidates[0].name + self._log(f"Automatically selected single candidate: {source_folder_name}", level="INFO") + elif len(candidates) > 1: + # Multiple candidates - ask user to choose + self._log(f"Found {len(candidates)} candidate folders, asking user to select...", level="INFO") + selected = self._ask_user_for_source_folder(project_root, candidates) + if selected: + self.derived_source_dir_path = selected + source_folder_name = selected.name + self._log(f"User selected source folder: {source_folder_name}", level="INFO") + else: + self._log("User cancelled source folder selection.", level="INFO") + return + else: + # No candidates found - ask user to browse + self._log("No source folders with __main__.py found, asking user to browse...", level="WARNING") + selected = self._ask_user_for_source_folder(project_root, []) + if selected: + self.derived_source_dir_path = selected + source_folder_name = selected.name + self._log(f"User selected source folder: {source_folder_name}", level="INFO") + else: + self._log("User cancelled source folder selection.", level="INFO") + return + + # Now derive paths using the determined source folder name self.derived_main_script_path = str( self.derived_source_dir_path / "__main__.py" ) - self.derived_spec_path = str(project_root / f"{project_name_lower}.spec") + self.derived_spec_path = str(project_root / f"{source_folder_name}.spec") # Default icon filename based on platform - default_icon_filename = f"{project_name_lower}.ico" + default_icon_filename = f"{source_folder_name}.ico" if sys.platform == "darwin": - default_icon_filename = f"{project_name_lower}.icns" + default_icon_filename = f"{source_folder_name}.icns" elif sys.platform != "win32": - default_icon_filename = f"{project_name_lower}.png" + default_icon_filename = f"{source_folder_name}.png" self.derived_icon_path = str(project_root / default_icon_filename) self._log(f"Derived project name: {self.project_root_name}", level="DEBUG") @@ -134,21 +294,25 @@ class ProjectManager: spec_path_obj = pathlib.Path(self.derived_spec_path) icon_path_obj = pathlib.Path(self.derived_icon_path) + # At this point, main script should always exist since we verified it during folder selection if main_script_path_obj.is_file(): self.derived_script_label_val.config( text=str(main_script_path_obj), foreground="black" ) + self._log(f"Main script confirmed: {main_script_path_obj}", level="INFO") else: + # This shouldn't happen anymore, but keep as safeguard self.derived_script_label_val.config(text="NOT FOUND!", foreground="red") self._log( - f"Error: Main script not found in the expected location: {main_script_path_obj}", + f"Error: Main script not found: {main_script_path_obj}", level="ERROR", ) messagebox.showerror( "Invalid Project Structure", - f"Main script not found in expected location:\n{main_script_path_obj}", + f"Main script not found:\n{main_script_path_obj}", parent=self.parent_gui, ) + return if spec_path_obj.is_file(): self.derived_spec_label_val.config( @@ -172,18 +336,18 @@ class ProjectManager: ) self.icon_path_var.set("") # Clear icon path if default not found - # Check if derived source directory actually exists + # Source directory should always exist at this point if ( isinstance(self.derived_source_dir_path, pathlib.Path) and not self.derived_source_dir_path.is_dir() ): self._log( - f"Warning: Expected source directory '{self.derived_source_dir_path.name}' does not exist. This might be an issue if it's essential for the project structure.", + f"Warning: Source directory '{self.derived_source_dir_path.name}' does not exist.", level="WARNING", ) - # Call the callback if project selection and initial path derivation are successful - if main_script_path_obj.is_file() and self.on_project_selected_callback: + # Call the callback - main script existence is guaranteed at this point + if self.on_project_selected_callback: # Prepare a dictionary of derived paths to pass to the callback derived_paths_info: Dict[str, Any] = { "project_root_path": dir_path,