11import sys
22import os
3+ import time
34
45# Ensure the 'src' directory is on sys.path when executing as a script
56SRC_DIR = os .path .dirname (__file__ )
6-
77if SRC_DIR and SRC_DIR not in sys .path :
88 sys .path .insert (0 , SRC_DIR )
99
1616 # If we can't initialize pyexiv2, log the error but don't prevent app startup
1717 print (f"Warning: Failed to initialize pyexiv2: { e } " )
1818
19- import logging # noqa: E402 # Must be after pyexiv2 initialization
20- import time # noqa: E402
19+ import logging # noqa: E402
2120import argparse # noqa: E402
2221import traceback # noqa: E402 # For global exception handler
23- from PyQt6 .QtWidgets import ( # noqa: E402
24- QApplication ,
25- QMessageBox ,
26- ) # QMessageBox for global exception handler
27- from PyQt6 .QtGui import QIcon # noqa: E402
2822
29- from ui . main_window import MainWindow # noqa: E402
30- from ui . app_controller import AppController # noqa: E402
31- from pillow_heif import register_heif_opener # noqa: E402
23+ from PyQt6 . QtCore import Qt , QTimer # noqa: E402
24+ from PyQt6 . QtWidgets import QApplication , QMessageBox , QSplashScreen # noqa: E402
25+ from PyQt6 . QtGui import QIcon , QPixmap # noqa: E402
3226
3327
3428def load_stylesheet (filename : str = "src/ui/dark_theme.qss" ) -> str :
@@ -53,13 +47,9 @@ def load_stylesheet(filename: str = "src/ui/dark_theme.qss") -> str:
5347 candidates = [
5448 os .path .join (
5549 base_dir , "dark_theme.qss"
56- ), # we bundle at top-level in frozen builds
57- os .path .join (
58- base_dir , filename
59- ), # e.g., src/ui/dark_theme.qss inside frozen or source
60- os .path .abspath (
61- filename
62- ), # direct path from CWD when running from repo root
50+ ), # bundled at top-level in frozen builds
51+ os .path .join (base_dir , filename ), # e.g., src/ui/dark_theme.qss
52+ os .path .abspath (filename ), # direct path from CWD
6353 ]
6454
6555 for path in candidates :
@@ -119,9 +109,7 @@ def global_exception_handler(exc_type, exc_value, exc_traceback):
119109 error_box .setIcon (QMessageBox .Icon .Critical )
120110 error_box .setWindowTitle ("Application Error" )
121111 error_box .setText (main_error_text )
122- error_box .setDetailedText (
123- error_message_details
124- ) # Full traceback for expert users/reporting
112+ error_box .setDetailedText (error_message_details ) # Full traceback
125113 error_box .setStandardButtons (QMessageBox .StandardButton .Ok )
126114 error_box .exec ()
127115 except Exception as e_msgbox :
@@ -196,6 +184,31 @@ def apply_app_identity(app: QApplication, main_window=None) -> None:
196184def main ():
197185 """Main application entry point."""
198186
187+ # Create QApplication early for splash screen
188+ app = QApplication (sys .argv )
189+
190+ # --- Splash: show immediately (no text), then set message after it’s visible ---
191+ splash_total_start = time .perf_counter ()
192+
193+ splash_path = os .path .join (
194+ os .path .dirname (os .path .dirname (__file__ )), "assets" , "app_icon.png"
195+ )
196+ splash_pix = QPixmap (splash_path ).scaled (
197+ 400 ,
198+ 300 ,
199+ Qt .AspectRatioMode .KeepAspectRatio ,
200+ Qt .TransformationMode .FastTransformation ,
201+ )
202+ splash = QSplashScreen (splash_pix )
203+
204+ # Show the splash immediately (no text yet)
205+ splash .show ()
206+ app .processEvents () # should be fast; no text/layout yet
207+
208+ from pillow_heif import register_heif_opener # noqa: E402
209+
210+ register_heif_opener ()
211+
199212 # --- Enable Faulthandler for crash analysis ---
200213 import faulthandler
201214
@@ -220,8 +233,6 @@ def main():
220233 except Exception :
221234 pass
222235
223- register_heif_opener ()
224-
225236 # Parse command-line arguments
226237 parser = argparse .ArgumentParser (description = "PhotoSort" )
227238 parser .add_argument ("--folder" , type = str , help = "Open specified folder at startup" )
@@ -283,11 +294,10 @@ def main():
283294 "File logging disabled. To enable, set PHOTOSORT_ENABLE_FILE_LOGGING=true."
284295 )
285296
286- # --- Suppress verbose third-party loggers ---
297+ # --- Suppress verbose third-party loggers ---
287298 logging .getLogger ("PIL" ).setLevel (logging .INFO )
288299 logging .getLogger ("PIL.PngImagePlugin" ).setLevel (logging .INFO )
289300 logging .getLogger ("PIL.TiffImagePlugin" ).setLevel (logging .INFO )
290- # You might also want to set it for the more general Image module if logs still appear
291301 logging .getLogger ("PIL.Image" ).setLevel (logging .INFO )
292302 # --- End Suppress verbose third-party loggers ---
293303
@@ -299,6 +309,9 @@ def main():
299309 main_start_time = time .perf_counter ()
300310 logging .info ("Application starting..." )
301311
312+ from ui .main_window import MainWindow
313+ from ui .app_controller import AppController
314+
302315 # Handle clear-cache argument
303316 if args .clear_cache :
304317 clear_application_caches_start_time = time .perf_counter ()
@@ -307,23 +320,6 @@ def main():
307320 f"Caches cleared via command line in { time .perf_counter () - clear_application_caches_start_time :.4f} s"
308321 )
309322
310- app_instantiation_start_time = time .perf_counter ()
311- app = QApplication (sys .argv )
312- logging .debug (
313- f"QApplication instantiated in { time .perf_counter () - app_instantiation_start_time :.4f} s"
314- )
315-
316- # Load and apply the stylesheet
317- stylesheet_load_start_time = time .perf_counter ()
318- stylesheet = load_stylesheet ()
319- if stylesheet :
320- app .setStyleSheet (stylesheet )
321- logging .debug (
322- f"Stylesheet loaded and applied in { time .perf_counter () - stylesheet_load_start_time :.4f} s"
323- )
324- else :
325- logging .warning ("Stylesheet not found. Using default style." )
326-
327323 mainwindow_instantiation_start_time = time .perf_counter ()
328324 window = MainWindow (initial_folder = args .folder )
329325 apply_app_identity (app , window )
@@ -336,6 +332,26 @@ def main():
336332 logging .debug (
337333 f"MainWindow shown in { time .perf_counter () - window_show_start_time :.4f} s"
338334 )
335+ splash .finish (window )
336+ logging .debug (
337+ f"Splashscreen finished in { time .perf_counter () - splash_total_start :.4f} s"
338+ )
339+
340+ # Defer stylesheet loading to after splash finish to avoid blocking startup
341+ stylesheet_load_start = time .perf_counter ()
342+
343+ def apply_stylesheet ():
344+ stylesheet = load_stylesheet ()
345+ if stylesheet :
346+ app .setStyleSheet (stylesheet )
347+ logging .debug (
348+ f"Stylesheet applied in { time .perf_counter () - stylesheet_load_start :.4f} s"
349+ )
350+
351+ QTimer .singleShot (
352+ 0 ,
353+ apply_stylesheet ,
354+ )
339355
340356 logging .info (
341357 f"Application setup complete in { time .perf_counter () - main_start_time :.4f} s. Entering event loop."
0 commit comments