11from __future__ import annotations
22import sys
33import customtkinter as ctk
4-
54from pathlib import Path
65from PIL import Image , ImageTk
76
3332from .views .large_files_page import LargeFilesPage
3433from .views .about_page import AboutPage
3534
36-
3735class SplashScreen (ctk .CTkToplevel ):
3836 def __init__ (self , master , logo_path ):
3937 super ().__init__ (master )
4038 self .overrideredirect (True )
4139 self .attributes ("-topmost" , True )
4240 self .configure (fg_color = DR_BG )
43-
4441 width , height = 340 , 380
4542 x = (self .winfo_screenwidth () // 2 ) - (width // 2 )
4643 y = (self .winfo_screenheight () // 2 ) - (height // 2 )
4744 self .geometry (f"{ width } x{ height } +{ x } +{ y } " )
48-
4945 self .grid_columnconfigure (0 , weight = 1 )
5046 self .grid_rowconfigure (0 , weight = 1 )
51-
5247 inner = ctk .CTkFrame (self , fg_color = "transparent" )
5348 inner .grid (row = 0 , column = 0 )
54-
5549 if logo_path and logo_path .exists ():
5650 img = Image .open (logo_path )
5751 self .photo = ctk .CTkImage (light_image = img , dark_image = img , size = (120 , 120 ))
5852 ctk .CTkLabel (inner , text = "" , image = self .photo ).pack (pady = (0 , 20 ))
59-
6053 ctk .CTkLabel (inner , text = "BulkFolder" , font = ctk .CTkFont (size = 26 , weight = "bold" ), text_color = DR_TEXT ).pack ()
6154 ctk .CTkLabel (inner , text = "Organize & Rename safely" , font = ctk .CTkFont (size = 13 ), text_color = DR_MUTED ).pack (pady = (0 , 20 ))
62- ctk .CTkLabel (inner , text = "Loading..." , font = ctk .CTkFont (size = 12 ), text_color = DR_MUTED ).pack (pady = (0 , 5 ))
63-
6455 self .pb = ctk .CTkProgressBar (inner , width = 220 , progress_color = DR_PURPLE , fg_color = DR_BORDER )
6556 self .pb .pack (pady = (5 , 0 ))
6657 self .pb .set (0 )
6758 self .pb .start ()
6859
69-
7060class App (ctk .CTk ):
7161 def __init__ (self ):
72- # OPTIMISATION 1: Force le mode de rendu haute performance sur Windows
7362 try :
7463 if sys .platform .startswith ("win" ):
7564 import ctypes
7665 ctypes .windll .shcore .SetProcessDpiAwareness (1 )
77- except Exception :
78- pass
79-
66+ except Exception : pass
8067 super ().__init__ ()
8168 self .withdraw ()
82-
83- # Variable pour bloquer le rendu intensif durant le mouvement
8469 self ._resize_after_id = None
85-
8670 self .settings : AppSettings = load_settings ()
87-
8871 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 :
96- pass
97-
9872 self .title ("BulkFolder" )
9973 self .geometry ("1240x800" )
10074 self .minsize (1020 , 700 )
10175 self .configure (fg_color = DR_BG )
102-
103- # OPTIMISATION 2: Capture l'événement de redimensionnement
10476 self .bind ("<Configure>" , self ._smart_resize )
105-
10677 self .logo_path = Path (__file__ ).resolve ().parent .parent .parent / "assets" / "logo.png"
10778 self ._ensure_logo_exists (self .logo_path )
108-
109- if self .logo_path .exists ():
110- img = Image .open (self .logo_path )
111- photo = ImageTk .PhotoImage (img )
112- self .wm_iconphoto (True , photo )
113-
114- if sys .platform .startswith ("win" ):
115- try :
116- import ctypes
117- myappid = 'zyloscore.bulkfolder.app.1.1'
118- ctypes .windll .shell32 .SetCurrentProcessExplicitAppUserModelID (myappid )
119- except Exception : pass
120- ico_path = self .logo_path .with_suffix (".ico" )
121- if ico_path .exists (): self .iconbitmap (str (ico_path ))
122-
123- self .splash = SplashScreen (self , self .logo_path )
124- self .splash .update ()
125-
12679 self .ui_state = UIState ()
12780 self .last_plan = None
12881 self .last_scan = None
129- self .renamer_plan = []
130- self .chunker_plan = []
131-
132- # Grid config
13382 self .grid_columnconfigure (0 , weight = 0 )
13483 self .grid_columnconfigure (1 , weight = 1 )
13584 self .grid_rowconfigure (0 , weight = 1 )
136-
85+ self .splash = SplashScreen (self , self .logo_path )
86+ self .splash .update ()
13787 info = get_project_info ()
13888 self .sidebar_view = SidebarView (info , self , on_page = self .switch_page , logo_path = self .logo_path )
13989 self .sidebar_view .grid (row = 0 , column = 0 , rowspan = 2 , sticky = "nsw" )
140-
141- # Right side container
14290 self .content_area = ctk .CTkFrame (self , corner_radius = 0 , fg_color = DR_BG )
14391 self .content_area .grid (row = 0 , column = 1 , sticky = "nsew" )
14492 self .content_area .grid_columnconfigure (0 , weight = 1 )
14593 self .content_area .grid_rowconfigure (1 , weight = 1 )
14694 self .content_area .grid_rowconfigure (2 , weight = 0 )
147-
14895 self .topbar_view = TopbarView (self .content_area , on_toggle_sidebar = self .toggle_terminal )
14996 self .topbar_view .grid (row = 0 , column = 0 , sticky = "ew" , padx = 18 , pady = (18 , 8 ))
150-
15197 self .page_container = ctk .CTkFrame (self .content_area , corner_radius = 0 , fg_color = DR_BG )
15298 self .page_container .grid (row = 1 , column = 0 , sticky = "nsew" )
15399 self .page_container .grid_columnconfigure (0 , weight = 1 )
154100 self .page_container .grid_rowconfigure (0 , weight = 1 )
155-
156101 self .logs_view = LogsView (self .content_area , on_close = self .toggle_terminal )
157102 self .logs_view .grid (row = 2 , column = 0 , sticky = "ew" , padx = 18 , pady = (0 , 18 ))
158103 self ._terminal_visible = True
159-
160104 self .ui_state .log_view = self .logs_view
161-
162105 self .pages : dict [str , ctk .CTkFrame ] = {}
163106 self .pages ["Organizer" ] = self ._build_page_organizer (self .page_container )
164107 self .pages ["Renamer" ] = self ._build_page_renamer (self .page_container )
@@ -171,26 +114,16 @@ def __init__(self):
171114 self .pages ["LargeFiles" ] = self ._build_page_large_files (self .page_container )
172115 self .pages ["Settings" ] = self ._build_page_settings (self .page_container )
173116 self .pages ["About" ] = self ._build_page_about (self .page_container )
174-
175117 self ._current_page = ""
176- self .switch_page (self .settings .default_page if self .settings .default_page in self .pages else "Organizer" )
177-
178- self .log ("System initialized." , level = "DEBUG" )
179- self .log ("Terminal functional." , level = "SUCCESS" )
180- self .set_status ("Ready." )
181-
118+ self .switch_page ("Organizer" )
182119 self .after (2000 , self ._show_main_window )
183120
184121 def _smart_resize (self , event ):
185- """Évite le recalcul saccadé en temps réel."""
186122 if event .widget == self :
187- if self ._resize_after_id :
188- self .after_cancel (self ._resize_after_id )
189- # Attend 50ms de stabilité avant de rafraîchir
123+ if self ._resize_after_id : self .after_cancel (self ._resize_after_id )
190124 self ._resize_after_id = self .after (50 , self ._do_refresh )
191125
192126 def _do_refresh (self ):
193- """Effectue le rendu final de manière fluide."""
194127 self .update_idletasks ()
195128 self ._resize_after_id = None
196129
@@ -202,11 +135,7 @@ def _ensure_logo_exists(png_path: Path):
202135 img = Image .open (png_path ).convert ("RGBA" )
203136 bbox = img .getbbox ()
204137 if bbox : img = img .crop (bbox )
205- sizes = [256 , 128 , 64 , 48 , 32 , 16 ]
206- layers = []
207- for s in sizes :
208- resized = img .resize ((s , s ), Image .Resampling .LANCZOS )
209- layers .append (resized )
138+ layers = [img .resize ((s , s ), Image .Resampling .LANCZOS ) for s in [256 , 128 , 64 , 48 , 32 , 16 ]]
210139 layers [0 ].save (ico_path , format = "ICO" , append_images = layers [1 :])
211140 except Exception : pass
212141
@@ -217,28 +146,20 @@ def _show_main_window(self):
217146
218147 def _build_page_organizer (self , parent ) -> ctk .CTkFrame :
219148 frame = ctk .CTkFrame (parent , corner_radius = 0 , fg_color = DR_BG )
220- frame .grid_columnconfigure (0 , weight = 0 )
221- frame .grid_columnconfigure (1 , weight = 1 )
222- frame .grid_rowconfigure (0 , weight = 1 )
223-
224- self .organizer_panel = OrganizerPanel (
225- frame ,
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 ,
226151 on_choose_folder = lambda : actions .choose_folder (self ),
227152 on_scan = lambda : actions .scan_and_plan (self ),
228153 on_apply = lambda : actions .apply_plan (self ),
229154 on_undo = lambda : actions .undo_last_ops (self ),
230155 on_toggle_subfolders = lambda enabled : actions .toggle_subfolders (self , enabled ),
231156 )
232157 self .organizer_panel .grid (row = 0 , column = 0 , sticky = "ns" , padx = (18 , 10 ), pady = (0 , 18 ))
233-
234158 right = ctk .CTkFrame (frame , corner_radius = 0 , fg_color = DR_BG )
235159 right .grid (row = 0 , column = 1 , sticky = "nsew" , padx = (10 , 18 ), pady = (0 , 18 ))
236- right .grid_columnconfigure (0 , weight = 1 )
237- right .grid_rowconfigure (2 , weight = 1 )
238-
160+ right .grid_columnconfigure (0 , weight = 1 ); right .grid_rowconfigure (2 , weight = 1 )
239161 self .cards_view = CardsRow (right )
240162 self .cards_view .grid (row = 0 , column = 0 , sticky = "ew" , pady = (0 , 8 ))
241-
242163 self .tabs = ctk .CTkTabview (right , fg_color = DR_BG )
243164 self .tabs .grid (row = 2 , column = 0 , sticky = "nsew" , pady = (8 , 0 ))
244165 self .dashboard_view = DashboardView (self .tabs .add ("Dashboard" ))
@@ -247,69 +168,16 @@ def _build_page_organizer(self, parent) -> ctk.CTkFrame:
247168 self .preview_view .pack (fill = "both" , expand = True )
248169 return frame
249170
250- def _build_page_renamer (self , parent ) -> ctk .CTkFrame :
251- self .renamer_page = RenamerPage (parent ,
252- on_choose_folder = lambda : actions .renamer_choose_folder (self ),
253- on_preview = lambda : actions .renamer_preview (self ),
254- on_apply = lambda : actions .renamer_apply (self ))
255- return self .renamer_page
256-
257- def _build_page_chunker (self , parent ) -> ctk .CTkFrame :
258- self .chunker_page = ChunkerPage (parent ,
259- on_choose_folder = lambda : actions .chunker_choose_folder (self ),
260- on_preview = lambda : actions .chunker_preview (self ),
261- on_apply = lambda : actions .chunker_apply (self ))
262- return self .chunker_page
263-
264- def _build_page_flattener (self , parent ) -> ctk .CTkFrame :
265- self .flattener_page = FlattenerPage (parent ,
266- on_choose_folder = lambda : actions .flattener_choose_folder (self ),
267- on_preview = lambda : actions .flattener_preview (self ),
268- on_apply = lambda : actions .flattener_apply (self ))
269- return self .flattener_page
270-
271- def _build_page_unzipper (self , parent ) -> ctk .CTkFrame :
272- self .unzipper_page = UnzipperPage (parent ,
273- on_choose_folder = lambda : actions .unzipper_choose_folder (self ),
274- on_refresh = lambda : actions .unzipper_refresh (self ),
275- on_extract_selected = lambda paths : actions .unzipper_extract_selected (self , paths ))
276- return self .unzipper_page
277-
278- def _build_page_pdf (self , parent ) -> ctk .CTkFrame :
279- self .pdf_page = PdfPage (parent ,
280- on_choose_folder = lambda : actions .pdf_choose_folder (self ),
281- on_refresh = lambda : actions .pdf_refresh (self ),
282- on_convert = lambda paths : actions .pdf_convert_selected (self , paths ))
283- return self .pdf_page
284-
285- def _build_page_dateorg (self , parent ) -> ctk .CTkFrame :
286- self .dateorg_page = DateOrgPage (parent ,
287- on_choose_folder = lambda : actions .dateorg_choose_folder (self ),
288- on_preview = lambda : actions .dateorg_preview (self ),
289- on_apply = lambda : actions .dateorg_apply (self ))
290- return self .dateorg_page
291-
292- def _build_page_empty_folders (self , parent ) -> ctk .CTkFrame :
293- self .empty_folders_page = EmptyFoldersPage (parent ,
294- on_choose_folder = lambda : actions .empty_folders_choose_folder (self ),
295- on_refresh = lambda : actions .empty_folders_refresh (self ),
296- on_delete_selected = lambda paths : actions .empty_folders_delete_selected (self , paths ))
297- return self .empty_folders_page
298-
299- def _build_page_large_files (self , parent ) -> ctk .CTkFrame :
300- self .large_files_page = LargeFilesPage (parent ,
301- on_choose_folder = lambda : actions .large_files_choose_folder (self ),
302- on_refresh = lambda min_mb : actions .large_files_refresh (self , min_mb ),
303- on_delete_selected = lambda paths : actions .large_files_delete_selected (self , paths ))
304- return self .large_files_page
305-
306- def _build_page_settings (self , parent ) -> ctk .CTkFrame :
307- self .settings_page = SettingsPage (parent , settings = self .settings , on_save = lambda : actions .settings_save (self ))
308- return self .settings_page
309-
310- def _build_page_about (self , parent ) -> ctk .CTkFrame :
311- self .about_page = AboutPage (parent , project_info = get_project_info (), on_open_github = lambda url : actions .open_github (self , url ))
312- return self .about_page
171+ 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 ))
172+ def _build_page_chunker (self , p ): return ChunkerPage (p , on_choose_folder = lambda : actions .chunker_choose_folder (self ), on_preview = lambda : actions .chunker_preview (self ), on_apply = lambda : actions .chunker_apply (self ))
173+ def _build_page_flattener (self , p ): return FlattenerPage (p , on_choose_folder = lambda : actions .flattener_choose_folder (self ), on_preview = lambda : actions .flattener_preview (self ), on_apply = lambda : actions .flattener_apply (self ))
174+ def _build_page_unzipper (self , p ): return UnzipperPage (p , on_choose_folder = lambda : actions .unzipper_choose_folder (self ), on_refresh = lambda : actions .unzipper_refresh (self ), on_extract_selected = lambda paths : actions .unzipper_extract_selected (self , paths ))
175+ def _build_page_pdf (self , p ): return PdfPage (p , on_choose_folder = lambda : actions .pdf_choose_folder (self ), on_refresh = lambda : actions .pdf_refresh (self ), on_convert = lambda paths : actions .pdf_convert_selected (self , paths ))
176+ def _build_page_dateorg (self , p ): return DateOrgPage (p , on_choose_folder = lambda : actions .dateorg_choose_folder (self ), on_preview = lambda : actions .dateorg_preview (self ), on_apply = lambda : actions .dateorg_apply (self ))
177+ def _build_page_empty_folders (self , p ): return EmptyFoldersPage (p , on_choose_folder = lambda : actions .empty_folders_choose_folder (self ), on_refresh = lambda : actions .empty_folders_refresh (self ), on_delete_selected = lambda paths : actions .empty_folders_delete_selected (self , paths ))
178+ def _build_page_large_files (self , p ): return LargeFilesPage (p , on_choose_folder = lambda : actions .large_files_choose_folder (self ), on_refresh = lambda m : actions .large_files_refresh (self , m ), on_delete_selected = lambda paths : actions .large_files_delete_selected (self , paths ))
179+ def _build_page_settings (self , p ): return SettingsPage (p , settings = self .settings , on_save = lambda : actions .settings_save (self ))
180+ def _build_page_about (self , p ): return AboutPage (p , project_info = get_project_info (), on_open_github = lambda url : actions .open_github (self , url ))
313181
314182 def switch_page (self , page_name : str ) -> None :
315183 if page_name not in self .pages : return
@@ -321,22 +189,17 @@ def switch_page(self, page_name: str) -> None:
321189
322190 def toggle_terminal (self ) -> None :
323191 if self ._terminal_visible :
324- self .logs_view .grid_forget ()
325- self ._terminal_visible = False
192+ self .logs_view .grid_forget (); self ._terminal_visible = False
326193 else :
327- self .logs_view .grid (row = 2 , column = 0 , sticky = "ew" , padx = 18 , pady = (0 , 18 ))
328- self ._terminal_visible = True
329-
330- def set_status (self , s : str ) -> None :
331- self .topbar_view .set_status (s )
194+ self .logs_view .grid (row = 2 , column = 0 , sticky = "ew" , padx = 18 , pady = (0 , 18 )); self ._terminal_visible = True
332195
333- def log (self , s : str , level : str = "INFO" ) -> None :
334- self .ui_state .log (s , level = level )
196+ def set_status (self , s : str ) -> None : self . topbar_view . set_status ( s )
197+ def log ( self , s : str , level : str = "INFO" ) -> None : self .ui_state .log (s , level = level )
335198
336199 @staticmethod
337200 def human_bytes (n : int ) -> str :
338201 step = 1024.0
339202 for u in ["B" , "KB" , "MB" , "GB" , "TB" ]:
340203 if n < step : return f"{ n :.1f} { u } "
341204 n /= step
342- return f"{ n :.1f} PB"
205+ return f"{ n :.1f} PB"
0 commit comments