3232from .views .large_files_page import LargeFilesPage
3333from .views .about_page import AboutPage
3434
35+
3536class SplashScreen (ctk .CTkToplevel ):
37+ """ Écran de chargement. """
3638 def __init__ (self , master , logo_path ):
3739 super ().__init__ (master )
3840 self .overrideredirect (True )
3941 self .attributes ("-topmost" , True )
4042 self .configure (fg_color = DR_BG )
43+
4144 width , height = 340 , 380
4245 x = (self .winfo_screenwidth () // 2 ) - (width // 2 )
4346 y = (self .winfo_screenheight () // 2 ) - (height // 2 )
4447 self .geometry (f"{ width } x{ height } +{ x } +{ y } " )
48+
4549 self .grid_columnconfigure (0 , weight = 1 )
4650 self .grid_rowconfigure (0 , weight = 1 )
51+
4752 inner = ctk .CTkFrame (self , fg_color = "transparent" )
4853 inner .grid (row = 0 , column = 0 )
54+
4955 if logo_path and logo_path .exists ():
50- img = Image .open (logo_path )
51- self .photo = ctk .CTkImage (light_image = img , dark_image = img , size = (120 , 120 ))
52- ctk .CTkLabel (inner , text = "" , image = self .photo ).pack (pady = (0 , 20 ))
56+ try :
57+ img = Image .open (logo_path )
58+ self .photo = ctk .CTkImage (light_image = img , dark_image = img , size = (120 , 120 ))
59+ ctk .CTkLabel (inner , text = "" , image = self .photo ).pack (pady = (0 , 20 ))
60+ except Exception : pass
61+
5362 ctk .CTkLabel (inner , text = "BulkFolder" , font = ctk .CTkFont (size = 26 , weight = "bold" ), text_color = DR_TEXT ).pack ()
5463 ctk .CTkLabel (inner , text = "Organize & Rename safely" , font = ctk .CTkFont (size = 13 ), text_color = DR_MUTED ).pack (pady = (0 , 20 ))
64+
5565 self .pb = ctk .CTkProgressBar (inner , width = 220 , progress_color = DR_PURPLE , fg_color = DR_BORDER )
5666 self .pb .pack (pady = (5 , 0 ))
5767 self .pb .set (0 )
5868 self .pb .start ()
5969
70+
6071class App (ctk .CTk ):
6172 def __init__ (self ):
73+ # OPTIMISATION : DPI Awareness pour la fluidité (multi-écran)
6274 try :
6375 if sys .platform .startswith ("win" ):
6476 import ctypes
6577 ctypes .windll .shcore .SetProcessDpiAwareness (1 )
6678 except Exception : pass
79+
6780 super ().__init__ ()
6881 self .withdraw ()
82+
83+ # Bloqueur de saccades pour le redimensionnement (resize)
6984 self ._resize_after_id = None
85+
7086 self .settings : AppSettings = load_settings ()
87+
7188 ctk .set_appearance_mode ("dark" )
89+ ctk .set_default_color_theme ("blue" )
90+
91+ try :
92+ scale_val = float (self .settings .ui_scaling .replace ("%" , "" )) / 100.0
93+ ctk .set_widget_scaling (scale_val )
94+ ctk .set_window_scaling (scale_val )
95+ except Exception : pass
96+
7297 self .title ("BulkFolder" )
7398 self .geometry ("1240x800" )
7499 self .minsize (1020 , 700 )
75100 self .configure (fg_color = DR_BG )
101+
102+ # Liaison du redimensionnement intelligent (Smart Resize)
76103 self .bind ("<Configure>" , self ._smart_resize )
104+
77105 self .logo_path = Path (__file__ ).resolve ().parent .parent .parent / "assets" / "logo.png"
78106 self ._ensure_logo_exists (self .logo_path )
107+
79108 self .ui_state = UIState ()
80109 self .last_plan = None
81110 self .last_scan = None
111+ self .renamer_plan = []
112+ self .chunker_plan = []
113+
114+ # Configuration de la grille
82115 self .grid_columnconfigure (0 , weight = 0 )
83116 self .grid_columnconfigure (1 , weight = 1 )
84117 self .grid_rowconfigure (0 , weight = 1 )
118+
85119 self .splash = SplashScreen (self , self .logo_path )
86120 self .splash .update ()
121+
87122 info = get_project_info ()
88123 self .sidebar_view = SidebarView (info , self , on_page = self .switch_page , logo_path = self .logo_path )
89124 self .sidebar_view .grid (row = 0 , column = 0 , rowspan = 2 , sticky = "nsw" )
125+
90126 self .content_area = ctk .CTkFrame (self , corner_radius = 0 , fg_color = DR_BG )
91127 self .content_area .grid (row = 0 , column = 1 , sticky = "nsew" )
92128 self .content_area .grid_columnconfigure (0 , weight = 1 )
93129 self .content_area .grid_rowconfigure (1 , weight = 1 )
94130 self .content_area .grid_rowconfigure (2 , weight = 0 )
131+
95132 self .topbar_view = TopbarView (self .content_area , on_toggle_sidebar = self .toggle_terminal )
96133 self .topbar_view .grid (row = 0 , column = 0 , sticky = "ew" , padx = 18 , pady = (18 , 8 ))
134+
97135 self .page_container = ctk .CTkFrame (self .content_area , corner_radius = 0 , fg_color = DR_BG )
98136 self .page_container .grid (row = 1 , column = 0 , sticky = "nsew" )
99137 self .page_container .grid_columnconfigure (0 , weight = 1 )
100138 self .page_container .grid_rowconfigure (0 , weight = 1 )
139+
101140 self .logs_view = LogsView (self .content_area , on_close = self .toggle_terminal )
102141 self .logs_view .grid (row = 2 , column = 0 , sticky = "ew" , padx = 18 , pady = (0 , 18 ))
103142 self ._terminal_visible = True
104143 self .ui_state .log_view = self .logs_view
144+
145+ # Initialisation de toutes les pages (UI Originale)
105146 self .pages : dict [str , ctk .CTkFrame ] = {}
106147 self .pages ["Organizer" ] = self ._build_page_organizer (self .page_container )
107148 self .pages ["Renamer" ] = self ._build_page_renamer (self .page_container )
@@ -114,29 +155,35 @@ def __init__(self):
114155 self .pages ["LargeFiles" ] = self ._build_page_large_files (self .page_container )
115156 self .pages ["Settings" ] = self ._build_page_settings (self .page_container )
116157 self .pages ["About" ] = self ._build_page_about (self .page_container )
158+
117159 self ._current_page = ""
118160 self .switch_page ("Organizer" )
161+
162+ self .log ("System initialized." , level = "DEBUG" )
119163 self .after (2000 , self ._show_main_window )
120164
121165 def _smart_resize (self , event ):
166+ """ Évite le rendu saccadé en attendant 50ms de stabilité. """
122167 if event .widget == self :
123- if self ._resize_after_id : self .after_cancel (self ._resize_after_id )
168+ if self ._resize_after_id :
169+ self .after_cancel (self ._resize_after_id )
124170 self ._resize_after_id = self .after (50 , self ._do_refresh )
125171
126172 def _do_refresh (self ):
127173 self .update_idletasks ()
128174 self ._resize_after_id = None
129175
130- @staticmethod
131- def _ensure_logo_exists (png_path : Path ):
132- ico_path = png_path .with_suffix (".ico" )
176+ def _ensure_logo_exists (self , png_path : Path ):
177+ """ Génère et charge l'icône .ico. """
133178 if not png_path .exists (): return
179+ ico_path = png_path .with_suffix (".ico" )
134180 try :
135181 img = Image .open (png_path ).convert ("RGBA" )
136182 bbox = img .getbbox ()
137183 if bbox : img = img .crop (bbox )
138184 layers = [img .resize ((s , s ), Image .Resampling .LANCZOS ) for s in [256 , 128 , 64 , 48 , 32 , 16 ]]
139185 layers [0 ].save (ico_path , format = "ICO" , append_images = layers [1 :])
186+ self .iconbitmap (str (ico_path ))
140187 except Exception : pass
141188
142189 def _show_main_window (self ):
@@ -145,27 +192,41 @@ def _show_main_window(self):
145192 self .deiconify ()
146193
147194 def _build_page_organizer (self , parent ) -> ctk .CTkFrame :
195+ """ Conserve l'UI originale avec le Panel à gauche. """
148196 frame = ctk .CTkFrame (parent , corner_radius = 0 , fg_color = DR_BG )
149- frame .grid_columnconfigure (0 , weight = 0 ); frame .grid_columnconfigure (1 , weight = 1 ); frame .grid_rowconfigure (0 , weight = 1 )
150- self .organizer_panel = OrganizerPanel (frame ,
197+ frame .grid_columnconfigure (0 , weight = 0 )
198+ frame .grid_columnconfigure (1 , weight = 1 )
199+ frame .grid_rowconfigure (0 , weight = 1 )
200+
201+ # Panel de contrôle à gauche
202+ self .organizer_panel = OrganizerPanel (
203+ frame ,
151204 on_choose_folder = lambda : actions .choose_folder (self ),
152205 on_scan = lambda : actions .scan_and_plan (self ),
153206 on_apply = lambda : actions .apply_plan (self ),
154207 on_undo = lambda : actions .undo_last_ops (self ),
155208 on_toggle_subfolders = lambda enabled : actions .toggle_subfolders (self , enabled ),
156209 )
157210 self .organizer_panel .grid (row = 0 , column = 0 , sticky = "ns" , padx = (18 , 10 ), pady = (0 , 18 ))
211+
212+ # Zone de droite (Cards + Tabs)
158213 right = ctk .CTkFrame (frame , corner_radius = 0 , fg_color = DR_BG )
159214 right .grid (row = 0 , column = 1 , sticky = "nsew" , padx = (10 , 18 ), pady = (0 , 18 ))
160- right .grid_columnconfigure (0 , weight = 1 ); right .grid_rowconfigure (2 , weight = 1 )
215+ right .grid_columnconfigure (0 , weight = 1 )
216+ right .grid_rowconfigure (2 , weight = 1 )
217+
161218 self .cards_view = CardsRow (right )
162219 self .cards_view .grid (row = 0 , column = 0 , sticky = "ew" , pady = (0 , 8 ))
220+
163221 self .tabs = ctk .CTkTabview (right , fg_color = DR_BG )
164222 self .tabs .grid (row = 2 , column = 0 , sticky = "nsew" , pady = (8 , 0 ))
223+
165224 self .dashboard_view = DashboardView (self .tabs .add ("Dashboard" ))
166225 self .dashboard_view .pack (fill = "both" , expand = True )
226+
167227 self .preview_view = PreviewView (self .tabs .add ("Preview" ))
168228 self .preview_view .pack (fill = "both" , expand = True )
229+
169230 return frame
170231
171232 def _build_page_renamer (self , p ): return RenamerPage (p , on_choose_folder = lambda : actions .renamer_choose_folder (self ), on_preview = lambda : actions .renamer_preview (self ), on_apply = lambda : actions .renamer_apply (self ))
@@ -189,9 +250,11 @@ def switch_page(self, page_name: str) -> None:
189250
190251 def toggle_terminal (self ) -> None :
191252 if self ._terminal_visible :
192- self .logs_view .grid_forget (); self ._terminal_visible = False
253+ self .logs_view .grid_forget ()
254+ self ._terminal_visible = False
193255 else :
194- self .logs_view .grid (row = 2 , column = 0 , sticky = "ew" , padx = 18 , pady = (0 , 18 )); self ._terminal_visible = True
256+ self .logs_view .grid (row = 2 , column = 0 , sticky = "ew" , padx = 18 , pady = (0 , 18 ))
257+ self ._terminal_visible = True
195258
196259 def set_status (self , s : str ) -> None : self .topbar_view .set_status (s )
197260 def log (self , s : str , level : str = "INFO" ) -> None : self .ui_state .log (s , level = level )
0 commit comments