66from PIL import Image , ImageTk , ImageDraw
77
88# --- Imports: Theme, State, and Logic ---
9- # Importing colors to ensure the app respects our custom UI theme.
109from .theme import DR_BG , DR_TEXT , DR_MUTED , DR_SURFACE , DR_BORDER , DR_PURPLE
11- # UIState acts as the "memory" of the application (e.g., storing the selected folder paths).
1210from .state import UIState
13- # actions contains all the logical functions (the "Controller" in MVC).
1411from . import actions
1512
1613# --- Imports: Configuration and Project Info ---
1714from ..config import load_settings , AppSettings
1815from ..info import get_project_info
1916
2017# --- Imports: Views (UI Components) ---
21- # Importing all the visual blocks (the "View" in MVC) that make up the user interface.
2218from .views .sidebar import SidebarView
2319from .views .topbar import TopbarView
2420from .views .cards import CardsRow
4238class SplashScreen (ctk .CTkToplevel ):
4339 """
4440 A temporary loading screen that appears before the main application window is ready.
45- It provides visual feedback to the user while settings and heavy components are loading.
4641 """
4742 def __init__ (self , master , logo_path ):
4843 super ().__init__ (master )
49- # Removes the standard window borders and close/minimize buttons.
5044 self .overrideredirect (True )
51- # Keeps the splash screen always on top of other windows.
5245 self .attributes ("-topmost" , True )
5346 self .configure (fg_color = DR_BG )
5447
55- # Calculate coordinates to center the splash screen on the user's monitor.
5648 width , height = 340 , 380
5749 x = (self .winfo_screenwidth () // 2 ) - (width // 2 )
5850 y = (self .winfo_screenheight () // 2 ) - (height // 2 )
5951 self .geometry (f"{ width } x{ height } +{ x } +{ y } " )
6052
61- # Configure the grid to center the inner frame.
6253 self .grid_columnconfigure (0 , weight = 1 )
6354 self .grid_rowconfigure (0 , weight = 1 )
6455
6556 inner = ctk .CTkFrame (self , fg_color = "transparent" )
6657 inner .grid (row = 0 , column = 0 )
6758
68- # Load and display the application logo if it exists.
6959 if logo_path and logo_path .exists ():
7060 img = Image .open (logo_path )
7161 self .photo = ctk .CTkImage (light_image = img , dark_image = img , size = (120 , 120 ))
7262 ctk .CTkLabel (inner , text = "" , image = self .photo ).pack (pady = (0 , 20 ))
7363
74- # Title and subtitles
7564 ctk .CTkLabel (inner , text = "BulkFolder" , font = ctk .CTkFont (size = 26 , weight = "bold" ), text_color = DR_TEXT ).pack ()
7665 ctk .CTkLabel (inner , text = "Organize & Rename safely" , font = ctk .CTkFont (size = 13 ), text_color = DR_MUTED ).pack (pady = (0 , 20 ))
7766 ctk .CTkLabel (inner , text = "Loading..." , font = ctk .CTkFont (size = 12 ), text_color = DR_MUTED ).pack (pady = (0 , 5 ))
7867
79- # Start an animated progress bar to indicate loading is happening.
8068 self .pb = ctk .CTkProgressBar (inner , width = 220 , progress_color = DR_PURPLE , fg_color = DR_BORDER )
8169 self .pb .pack (pady = (5 , 0 ))
8270 self .pb .set (0 )
@@ -85,77 +73,59 @@ def __init__(self, master, logo_path):
8573
8674class App (ctk .CTk ):
8775 """
88- The main application window. It acts as the "orchestrator", connecting
89- the UI (Views) with the logic (Actions) and holding the shared memory (State).
76+ The main application window.
9077 """
9178 def __init__ (self ):
9279 super ().__init__ ()
93- # Hide the main window immediately so only the splash screen is visible at startup.
9480 self .withdraw ()
9581
96- # Load user preferences from the config file.
9782 self .settings : AppSettings = load_settings ()
9883
99- # Force dark mode. The dynamic custom colors are handled by theme.py.
10084 ctk .set_appearance_mode ("dark" )
10185 ctk .set_default_color_theme ("blue" )
10286
103- # Apply UI scaling based on user settings (e.g., 125%).
10487 try :
10588 scale_val = float (self .settings .ui_scaling .replace ("%" , "" )) / 100.0
10689 ctk .set_widget_scaling (scale_val )
10790 ctk .set_window_scaling (scale_val )
10891 except Exception :
10992 pass
11093
111- # Set main window properties.
11294 self .title ("BulkFolder" )
11395 self .geometry ("1200x720" )
11496 self .minsize (1020 , 640 )
11597 self .configure (fg_color = DR_BG )
11698
117- # Locate the logo and generate a default one if it doesn't exist.
11899 self .logo_path = Path (__file__ ).resolve ().parent .parent .parent / "assets" / "logo.png"
119100 self ._ensure_logo_exists (self .logo_path )
120101
121- # Set the window icon (top left corner and taskbar).
122102 if self .logo_path .exists ():
123103 img = Image .open (self .logo_path )
124104 photo = ImageTk .PhotoImage (img )
125105 self .wm_iconphoto (True , photo )
126106 self .iconphoto (True , photo )
127107
128- # --- WINDOWS TASKBAR MAGIC FIX ---
129108 if sys .platform .startswith ("win" ):
130- # 1. Tell Windows this is a standalone app (AppUserModelID)
131109 try :
132110 import ctypes
133- # Changement ici : on passe à 1.1 pour vider le cache de Windows !
134111 myappid = 'monprojet.bulkfolder.app.1.1'
135112 ctypes .windll .shell32 .SetCurrentProcessExplicitAppUserModelID (myappid )
136113 except Exception :
137114 pass
138115
139116 ico_path = self .logo_path .with_suffix (".ico" )
140-
141- # 2. Force the creation of a proper .ICO file with ALL necessary sizes
142117 try :
143118 img .save (ico_path , format = "ICO" , sizes = [(16 ,16 ), (24 ,24 ), (32 ,32 ), (48 ,48 ), (64 ,64 ), (128 ,128 ), (256 ,256 )])
144119 except Exception :
145120 pass
146121
147- # 3. Apply the icon
148122 if ico_path .exists ():
149123 self .iconbitmap (str (ico_path ))
150124
151- # Initialize and update the splash screen so it renders before the heavy lifting starts.
152125 self .splash = SplashScreen (self , self .logo_path )
153126 self .splash .update ()
154127
155- # Initialize the global application state (the shared memory).
156128 self .ui_state = UIState ()
157-
158- # Initialize variables to store active plans and scan results.
159129 self .last_plan = None
160130 self .last_scan = None
161131 self .large_files_scan = None
@@ -164,36 +134,28 @@ def __init__(self):
164134 self .flattener_plan = []
165135 self .dateorg_plan = []
166136
167- # Configure the main window grid layout.
168- # Column 0 (Sidebar) has weight 0 (fixed width). Column 1 (Main content) has weight 1 (expandable).
169137 self .grid_columnconfigure (0 , weight = 0 )
170138 self .grid_columnconfigure (1 , weight = 1 )
171139 self .grid_rowconfigure (0 , weight = 1 )
172140
173- # Build the Sidebar and pass the project_info (version, etc.) to it.
174141 info = get_project_info ()
175142 self .sidebar_view = SidebarView (info , self , on_page = self .switch_page , logo_path = self .logo_path )
176143 self .sidebar_view .grid (row = 0 , column = 0 , sticky = "nsw" )
177144
178- # Create the main right-side container that will hold the topbar and the pages.
179145 self .main = ctk .CTkFrame (self , corner_radius = 0 , fg_color = DR_BG )
180146 self .main .grid (row = 0 , column = 1 , sticky = "nsew" )
181147 self .main .grid_columnconfigure (0 , weight = 1 )
182- self .main .grid_rowconfigure (1 , weight = 1 ) # Row 1 is for the page container, it expands.
148+ self .main .grid_rowconfigure (1 , weight = 1 )
183149
184- # Build the Topbar (Status and title).
185- self .topbar_view = TopbarView (self .main , on_toggle_sidebar = self . toggle_sidebar )
150+ # Removed on_toggle_sidebar callback as it's no longer needed
151+ self .topbar_view = TopbarView (self .main , on_toggle_sidebar = None )
186152 self .topbar_view .grid (row = 0 , column = 0 , sticky = "ew" , padx = 18 , pady = (18 , 8 ))
187153
188- # Create the specific container where different pages will be swapped in and out.
189154 self .page_container = ctk .CTkFrame (self .main , corner_radius = 0 , fg_color = DR_BG )
190155 self .page_container .grid (row = 1 , column = 0 , sticky = "nsew" )
191156 self .page_container .grid_columnconfigure (0 , weight = 1 )
192157 self .page_container .grid_rowconfigure (0 , weight = 1 )
193158
194- # --- Pre-building all pages ---
195- # We build ALL pages at startup and store them in a dictionary.
196- # This allows instant switching between pages without losing the user's data or state on that page.
197159 self .pages : dict [str , ctk .CTkFrame ] = {}
198160 self .pages ["Organizer" ] = self ._build_page_organizer (self .page_container )
199161 self .pages ["Renamer" ] = self ._build_page_renamer (self .page_container )
@@ -207,7 +169,6 @@ def __init__(self):
207169 self .pages ["Settings" ] = self ._build_page_settings (self .page_container )
208170 self .pages ["About" ] = self ._build_page_about (self .page_container )
209171
210- # Determine which page to show first based on user settings.
211172 self ._current_page = ""
212173 start_page = self .settings .default_page
213174 if start_page not in self .pages :
@@ -217,7 +178,6 @@ def __init__(self):
217178 self .log ("App started." , level = "DEBUG" )
218179 self .set_status ("Ready." )
219180
220- # Wait 2.5 seconds before destroying the splash screen and showing the main UI.
221181 self .after (2500 , self ._show_main_window )
222182
223183 @staticmethod
@@ -240,26 +200,18 @@ def _ensure_logo_exists(png_path: Path):
240200 except Exception : pass
241201
242202 def _show_main_window (self ):
243- """Transitions from the splash screen to the main application window."""
244203 try :
245204 self .splash .pb .stop ()
246205 self .splash .destroy ()
247206 except : pass
248- self .deiconify () # Unhides the main window.
249-
250- # -------------------------------------------------------------------------
251- # Page Builder Methods
252- # These methods create the specific UI frames for each feature and
253- # link their buttons to the corresponding logic in actions.py using lambdas.
254- # -------------------------------------------------------------------------
207+ self .deiconify ()
255208
256209 def _build_page_organizer (self , parent ) -> ctk .CTkFrame :
257210 frame = ctk .CTkFrame (parent , corner_radius = 0 , fg_color = DR_BG )
258211 frame .grid_columnconfigure (0 , weight = 0 )
259212 frame .grid_columnconfigure (1 , weight = 1 )
260213 frame .grid_rowconfigure (0 , weight = 1 )
261214
262- # Left panel: Controls for the Organizer
263215 self .organizer_panel = OrganizerPanel (
264216 frame ,
265217 on_choose_folder = lambda : actions .choose_folder (self ),
@@ -273,7 +225,6 @@ def _build_page_organizer(self, parent) -> ctk.CTkFrame:
273225 self .organizer_panel .grid (row = 0 , column = 0 , sticky = "ns" , padx = (18 , 10 ), pady = (0 , 18 ))
274226 self .organizer_panel .configure (width = 320 )
275227
276- # Right panel: Data visualization (Cards and Tabs)
277228 right = ctk .CTkFrame (frame , corner_radius = 0 , fg_color = DR_BG )
278229 right .grid (row = 0 , column = 1 , sticky = "nsew" , padx = (10 , 18 ), pady = (0 , 18 ))
279230 right .grid_columnconfigure (0 , weight = 1 )
@@ -380,45 +331,27 @@ def _build_page_about(self, parent) -> ctk.CTkFrame:
380331 )
381332 return self .about_page
382333
383- # -------------------------------------------------------------------------
384- # Utility Methods
385- # -------------------------------------------------------------------------
386-
387334 def switch_page (self , page_name : str ) -> None :
388- """
389- Hides the current page and displays the requested page.
390- Instead of destroying and recreating the UI, it uses grid_forget() to hide it,
391- which preserves all user inputs (text fields, checkboxes, etc.).
392- """
393335 if page_name not in self .pages : return
394-
395- # Hide the currently active page.
396336 if self ._current_page : self .pages [self ._current_page ].grid_forget ()
397-
398- # Show the new page.
399337 self .pages [page_name ].grid (row = 0 , column = 0 , sticky = "nsew" )
400338 self ._current_page = page_name
401-
402- # Update the topbar title.
403339 self .topbar_view .set_title (page_name if page_name != "Organizer" else "Organizer" )
404340
405341 def toggle_sidebar (self ) -> None :
406- """Expands or collapses the left sidebar ."""
407- self . sidebar_view . toggle ()
342+ """Disabled ."""
343+ pass
408344
409345 def set_status (self , s : str ) -> None :
410- """Updates the status message in the top navigation bar."""
411346 self .topbar_view .set_status (s )
412347
413348 def log (self , s : str , level : str = "INFO" ) -> None :
414- """Sends a message to the Logs tab in the Organizer."""
415349 if hasattr (self , "logs_view" ): self .logs_view .log (s , level = level )
416350
417351 @staticmethod
418352 def human_bytes (n : int ) -> str :
419- """Converts raw bytes into a readable format (KB, MB, GB, etc.)."""
420353 step = 1024.0
421354 for u in ["B" , "KB" , "MB" , "GB" , "TB" ]:
422355 if n < step : return f"{ n :.1f} { u } "
423356 n /= step
424- return f"{ n :.1f} PB"
357+ return f"{ n :.1f} PB"
0 commit comments