annalyse le comportement de mapFromSource

Le chemin INDEX_DIR est valide.

Le chemin WATCHED_DIR est valide.

2024-10-24 10:20:03,106 - DEBUG - Schema defined with fields: ['creation_time', 'ext', 'modification_time', 'name', 'path', 'size']

2024-10-24 10:20:03,107 - DEBUG - Initializing FileIndexer with index_dir: C:\index_dir, rootPath: Y:\DocSoufiane

2024-10-24 10:20:03,107 - DEBUG - Beginning of __init__ function in CustomFileSystemModelForList

2024-10-24 10:20:03,123 - DEBUG - ThumbnailCache: Initialisé avec une taille maximale de 5000 vignettes.

2024-10-24 10:20:03,133 - DEBUG - Successfully connected thumbnailReady signal to on_thumbnail_ready slot.

2024-10-24 10:20:03,134 - DEBUG - End of __init__ function in CustomFileSystemModelForList

Le chemin . est valide.

2024-10-24 10:20:03,340 - DEBUG - Beginning of __init__ function in CustomFileSystemModelForList

2024-10-24 10:20:03,343 - DEBUG - ThumbnailCache: Initialisé avec une taille maximale de 5000 vignettes.

2024-10-24 10:20:03,344 - DEBUG - Successfully connected thumbnailReady signal to on_thumbnail_ready slot.

2024-10-24 10:20:03,344 - DEBUG - End of __init__ function in CustomFileSystemModelForList

2024-10-24 10:20:03,344 - DEBUG - Start of setup_tree_dock_widget function

2024-10-24 10:20:03,345 - INFO - Connection: tree_view.clicked -> on_tree_clicked

2024-10-24 10:20:03,345 - INFO - Tree root path:

2024-10-24 10:20:03,346 - DEBUG - Starting setup of the proxy model for the tree/Function setup_proxy_tree_model

2024-10-24 10:20:03,347 - INFO - Source model assigned to the tree proxy.

2024-10-24 10:20:03,347 - INFO - Filter of the tree proxy model invalidated to reflect the changes.

2024-10-24 10:20:03,347 - DEBUG - End of the setup of the proxy model for the tree/Function setup_proxy_tree_model

2024-10-24 10:20:03,349 - DEBUG - End of setup_tree_dock_widget function

2024-10-24 10:20:03,349 - DEBUG - Starting setup of the proxy model for the tree/Function setup_proxy_tree_model

2024-10-24 10:20:03,349 - DEBUG - Proxy model for the tree is already set up.

2024-10-24 10:20:03,349 - DEBUG - SearchResultsProxyModel: setSourceModel called

2024-10-24 10:20:03,350 - DEBUG - Starting setup of the proxy model for the file list./Function setup_file_list_proxy

2024-10-24 10:20:03,350 - INFO - Source model assigned to the file list proxy.

2024-10-24 10:20:03,350 - INFO - Filters applied for the file list proxy model: *.dxf *.jpg *.jpeg *.png *.bmp

2024-10-24 10:20:03,350 - INFO - Filter of the file list proxy model invalidated to reflect the changes.

2024-10-24 10:20:03,350 - DEBUG - End of the setup of the proxy model for the file list./Function setup_file_list_proxy

2024-10-24 10:20:03,351 - DEBUG - Starting the setup_ui function in FileViewer

2024-10-24 10:20:03,351 - DEBUG - Starting the setupTreeModelAndView function

2024-10-24 10:20:03,352 - DEBUG - End of the setupTreeModelAndView function

2024-10-24 10:20:03,352 - DEBUG - Starting the setupListModelAndView function

2024-10-24 10:20:03,373 - INFO - Initial root path for the file list model: (empty)

2024-10-24 10:20:03,373 - INFO - Proxy model configured with the file list model.

2024-10-24 10:20:03,373 - INFO - File list view configured with the proxy model.

2024-10-24 10:20:03,373 - INFO - File list view configured to display empty content.

2024-10-24 10:20:03,373 - INFO - Triggered showModelInfo:

2024-10-24 10:20:03,374 - DEBUG - End of the setupListModelAndView function

2024-10-24 10:20:03,374 - DEBUG - Start of setup_central_frame function

2024-10-24 10:20:03,376 - INFO - New icon size for QListView: PyQt5.QtCore.QSize(160, 120)

2024-10-24 10:20:03,376 - DEBUG - End of setup_central_frame function

2024-10-24 10:20:03,376 - DEBUG - Start of setup_preview_dock_widget function

2024-10-24 10:20:03,378 - DEBUG - End of setup_preview_dock_widget function

2024-10-24 10:20:03,379 - INFO - Connecting signals and slots:

2024-10-24 10:20:03,379 - DEBUG - [Init] Directory label initialized

2024-10-24 10:20:03,379 - DEBUG - [Layout] Directory label added to layout

2024-10-24 10:20:03,379 - DEBUG - End of the setup_ui function in FileViewer

2024-10-24 10:20:03,379 - INFO - Start setupMenu

2024-10-24 10:20:03,379 - DEBUG - Setting up the menu bar

2024-10-24 10:20:03,381 - DEBUG - Menu bar configured with File and View menus.

2024-10-24 10:20:03,381 - INFO - End setupMenu

2024-10-24 10:20:03,381 - INFO - Start setupToolbar

2024-10-24 10:20:03,381 - DEBUG - Setting up the toolbar

2024-10-24 10:20:03,381 - DEBUG - Toolbar configured

2024-10-24 10:20:03,382 - INFO - End setupToolbar

2024-10-24 10:20:03,382 - DEBUG - SearchWorker initialized with index_dir: C:\index_dir

2024-10-24 10:20:03,383 - INFO - Function update_directory_label start

2024-10-24 10:20:03,383 - DEBUG - Attempting to update directory label with text: Current folder path:

2024-10-24 10:20:03,383 - INFO - Updated directory label: Current folder path:

2024-10-24 10:20:03,383 - INFO - Function update_directory_label end

2024-10-24 10:20:03,384 - INFO - Connection: file_list.clicked -> display_selected_file

2024-10-24 10:20:03,384 - INFO - Connection: file_list.doubleClicked -> on_folder_double_clicked

2024-10-24 10:20:03,384 - DEBUG - SearchResultsProxyModel: setSourceModel called

2024-10-24 10:20:03,439 - INFO - Start of resizeEvent in CustomGraphicsView class

2024-10-24 10:20:03,439 - DEBUG - Resize event detected.

2024-10-24 10:20:03,439 - DEBUG - Initial view adjusted to show entire scene.

2024-10-24 10:20:03,439 - DEBUG - Signal connected to adjust view dynamically.

2024-10-24 10:20:03,439 - INFO - End of resizeEvent in CustomGraphicsView class

2024-10-24 10:20:03,440 - INFO - Start of showEvent in CustomGraphicsView class

2024-10-24 10:20:03,440 - DEBUG - Show event detected.

2024-10-24 10:20:03,440 - INFO - End of showEvent in CustomGraphicsView class

2024-10-24 10:20:07,637 - INFO - Start of load_folder function

2024-10-24 10:20:17,902 - INFO - Selected folder path: Y:/Divers fichiers Dxf À TRIER/ARC

2024-10-24 10:20:17,904 - INFO - Function update_directory_label start

2024-10-24 10:20:17,904 - DEBUG - Attempting to update directory label with text: Current folder path: Y:/Divers fichiers Dxf À TRIER/ARC

2024-10-24 10:20:17,904 - INFO - Updated directory label: Current folder path: Y:/Divers fichiers Dxf À TRIER/ARC

2024-10-24 10:20:17,904 - INFO - Function update_directory_label end

2024-10-24 10:20:17,904 - DEBUG - [load_folder] Current folder updated to: Y:/Divers fichiers Dxf À TRIER/ARC

2024-10-24 10:20:17,906 - INFO - End of load_folder function

2024-10-24 10:20:17,958 - DEBUG - ThumbnailCache: __getitem__ appelé pour Y:/Divers fichiers Dxf À TRIER/ARC/REPARE LUM 4.5-6.5PO.DXF

2024-10-24 10:20:17,959 - DEBUG - Thumbnail retrieved from cache for: Y:/Divers fichiers Dxf À TRIER/ARC/REPARE LUM 4.5-6.5PO.DXF

2024-10-24 10:20:17,959 - DEBUG - SearchResultsProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x000002077CFD2C10>

2024-10-24 10:20:17,959 - DEBUG - Source File Path: y:\divers fichiers dxf à trier\arc\repare lum 4.5-6.5po.dxf

2024-10-24 10:20:17,959 - DEBUG - Mapped Row: None

2024-10-24 10:20:17,959 - WARNING - SearchResultsProxyModel.mapFromSource reçu un sourceIndex invalide.

2024-10-24 10:20:17,960 - DEBUG - SearchResultsProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x000002077CFD2AC0>

2024-10-24 10:20:17,960 - DEBUG - Source File Path: y:\divers fichiers dxf à trier\arc\repare lum 4.5-6.5po.dxf

2024-10-24 10:20:17,960 - DEBUG - Mapped Row: None

2024-10-24 10:20:17,960 - WARNING - SearchResultsProxyModel.mapFromSource reçu un sourceIndex invalide.

2024-10-24 10:20:17,960 - DEBUG - SearchResultsProxyModel: Skipping dataChanged emission, model is not active.

2024-10-24 10:20:17,961 - DEBUG - Fonction update_file_list_view: Début

2024-10-24 10:20:17,961 - ERROR - Le chemin du dossier doit être une chaîne de caractères valide.

2024-10-24 10:20:17,961 - DEBUG - ThumbnailCache: __getitem__ appelé pour Y:/Divers fichiers Dxf À TRIER/ARC/REPARE LUM 4.5-6.5PO.DXF

2024-10-24 10:20:17,961 - DEBUG - Thumbnail retrieved from cache for: Y:/Divers fichiers Dxf À TRIER/ARC/REPARE LUM 4.5-6.5PO.DXF

2024-10-24 10:20:17,961 - DEBUG - SearchResultsProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x000002077CFD2B30>

2024-10-24 10:20:17,961 - DEBUG - Source File Path: y:\divers fichiers dxf à trier\arc\repare lum 4.5-6.5po.dxf

2024-10-24 10:20:17,961 - DEBUG - Mapped Row: None

2024-10-24 10:20:17,961 - WARNING - SearchResultsProxyModel.mapFromSource reçu un sourceIndex invalide.

2024-10-24 10:20:17,962 - DEBUG - SearchResultsProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x000002077CFD2AC0>

2024-10-24 10:20:17,962 - DEBUG - Source File Path: y:\divers fichiers dxf à trier\arc\repare lum 4.5-6.5po.dxf

2024-10-24 10:20:17,962 - DEBUG - Mapped Row: None

2024-10-24 10:20:17,962 - WARNING - SearchResultsProxyModel.mapFromSource reçu un sourceIndex invalide.

2024-10-24 10:20:17,962 - DEBUG - SearchResultsProxyModel: Skipping dataChanged emission, model is not active.

2024-10-24 10:20:17,962 - DEBUG - Fonction update_file_list_view: Début

2024-10-24 10:20:17,962 - ERROR - Le chemin du dossier doit être une chaîne de caractères valide.

2024-10-24 10:20:20,475 - DEBUG - Slot activated: launch_delayed_search

2024-10-24 10:20:20,475 - INFO - Search criteria modified. Waiting before launching search...

2024-10-24 10:20:20,475 - DEBUG - End of launch_delayed_search

2024-10-24 10:20:21,467 - DEBUG - Function launchSearch start

2024-10-24 10:20:21,468 - DEBUG - Search criteria: r, criteria_type: filename

2024-10-24 10:20:21,468 - DEBUG - Current root path: Y:/Divers fichiers Dxf À TRIER/ARC

2024-10-24 10:20:21,468 - DEBUG - WATCHED_DIR: Y:\DocSoufiane

2024-10-24 10:20:21,468 - DEBUG - Search criteria set: r (type: filename)

2024-10-24 10:20:21,468 - DEBUG - Date range set: 2024-10-24 - 2024-10-24

2024-10-24 10:20:21,469 - DEBUG - Base path for search set: Y:\Divers fichiers Dxf À TRIER\ARC

2024-10-24 10:20:21,469 - DEBUG - Function launchSearch end

2024-10-24 10:20:21,469 - INFO - Starting search.

2024-10-24 10:20:21,469 - DEBUG - Root path for search: Y:\Divers fichiers Dxf À TRIER\ARC

2024-10-24 10:20:21,470 - INFO - Using file system search.

2024-10-24 10:20:21,470 - INFO - Finalizing search.

2024-10-24 10:20:21,470 - INFO - Search completed with 1 results.

2024-10-24 10:20:21,471 - DEBUG - Search finished with success: True

2024-10-24 10:20:21,471 - DEBUG - Stopping search thread.

2024-10-24 10:20:21,471 - DEBUG - Search thread stopped.

2024-10-24 10:20:21,472 - DEBUG - Search thread stopped and resources cleaned up.

2024-10-24 10:20:21,472 - DEBUG - Function handle_search_completion start

2024-10-24 10:20:21,472 - INFO - Search completed successfully.

2024-10-24 10:20:21,472 - DEBUG - Beginning of displayResults function.

2024-10-24 10:20:21,472 - DEBUG - Updating proxy model with search results.

2024-10-24 10:20:21,472 - DEBUG - SearchResultsProxyModel.setSearchResults called with 1 results.

2024-10-24 10:20:21,472 - DEBUG - setSearchResults: Model reset completed.

2024-10-24 10:20:21,472 - DEBUG - SearchResultsProxyModel set as the current model for file_list.

2024-10-24 10:20:21,476 - DEBUG - SearchResultsProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x000002077CFD2AC0>

2024-10-24 10:20:21,476 - DEBUG - Source File Path: y:\divers fichiers dxf à trier\arc\repare lum 4.5-6.5po.dxf

2024-10-24 10:20:21,476 - DEBUG - Mapped Row: 0

2024-10-24 10:20:21,477 - DEBUG - SearchResultsProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x000002077CFD2BA0>

2024-10-24 10:20:21,478 - DEBUG - Source File Path: y:\divers fichiers dxf à trier\arc\repare lum 4.5-6.5po.dxf

2024-10-24 10:20:21,478 - DEBUG - Mapped Row: 0

2024-10-24 10:20:21,479 - DEBUG - Logging search results.

2024-10-24 10:20:21,479 - DEBUG - Beginning of logSearchResults function.

2024-10-24 10:20:21,479 - DEBUG - Search result: y:\divers fichiers dxf à trier\arc\REPARE LUM 4.5-6.5PO.DXF

2024-10-24 10:20:21,480 - DEBUG - End of logSearchResults function.

2024-10-24 10:20:21,480 - DEBUG - End of displayResults function.

2024-10-24 10:20:21,480 - DEBUG - Function handle_search_completion end

2024-10-24 10:20:21,480 - DEBUG - End of onSearchFinished function.

2024-10-24 10:20:21,491 - DEBUG - ThumbnailCache: __getitem__ appelé pour Y:/divers fichiers dxf à trier/ARC/repare lum 4.5-6.5po.dxf

2024-10-24 10:20:21,491 - DEBUG - Thumbnail retrieved from cache for: Y:/divers fichiers dxf à trier/ARC/repare lum 4.5-6.5po.dxf

2024-10-24 10:20:21,492 - DEBUG - SearchResultsProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000207043D0210>

2024-10-24 10:20:21,492 - DEBUG - Source File Path: y:\divers fichiers dxf à trier\arc\repare lum 4.5-6.5po.dxf

2024-10-24 10:20:21,492 - DEBUG - Mapped Row: 0

2024-10-24 10:20:21,492 - DEBUG - SearchResultsProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000207043D01A0>

2024-10-24 10:20:21,492 - DEBUG - Source File Path: y:\divers fichiers dxf à trier\arc\repare lum 4.5-6.5po.dxf

2024-10-24 10:20:21,492 - DEBUG - Mapped Row: 0

2024-10-24 10:20:21,493 - DEBUG - ThumbnailCache: __getitem__ appelé pour Y:/divers fichiers dxf à trier/ARC/repare lum 4.5-6.5po.dxf

2024-10-24 10:20:21,493 - DEBUG - Thumbnail retrieved from cache for: Y:/divers fichiers dxf à trier/ARC/repare lum 4.5-6.5po.dxf

2024-10-24 10:20:21,494 - DEBUG - SearchResultsProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000207043D0280>

2024-10-24 10:20:21,494 - DEBUG - Source File Path: y:\divers fichiers dxf à trier\arc\repare lum 4.5-6.5po.dxf

2024-10-24 10:20:21,494 - DEBUG - Mapped Row: 0

2024-10-24 10:20:21,494 - DEBUG - SearchResultsProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000207043D01A0>

2024-10-24 10:20:21,494 - DEBUG - Source File Path: y:\divers fichiers dxf à trier\arc\repare lum 4.5-6.5po.dxf

2024-10-24 10:20:21,494 - DEBUG - Mapped Row: 0

#...debut du script

class CustomFileSystemModelForList(QFileSystemModel):

    """Modèle de système de fichiers personnalisé pour afficher les vignettes dans une vue de liste."""

    searchFinished = pyqtSignal(list) # results: List[str]

    rootPathChanged = pyqtSignal(str) # new_root_path: str

    #dataChanged = pyqtSignal(QModelIndex, QModelIndex) # index: QModelIndex 

    modelUpdated = pyqtSignal(QModelIndex)  # index: QModelIndex # Signal to notify that the model has been updated

    directoryLoaded = pyqtSignal(str) # path: str

    #thumbnailUpdated = pyqtSignal(str, QPixmap, bool) # file_path: str, thumbnail: QPixmap, success: bool

    thumbnailRequested = pyqtSignal(str)  #



    def __init__(self, parent=None, indexer=None, max_workers=10, cache_size=5000): 

        logger.debug("Beginning of __init__ function in CustomFileSystemModelForList")

        super().__init__(parent)

        self.allowed_extensions = ALLOWED_EXTENSIONS

        self.setRootPath(os.path.normpath(WATCHED_DIR))  

        # Connexion des signaux pour le débogage

        #self.searchFinished.connect(lambda results: print(f"Search finished signal received with results: {results}"))

        #self.rootPathChanged.connect(lambda path: print(f"Root path changed signal received with path: {path}"))

        #self.dataChanged.connect(lambda top_left, bottom_right: print(f"Data changed signal received for indexes: {top_left}, {bottom_right}"))

        #self.rootPathChanged.connect(lambda path: logger.debug(f"Root path changed: {path}"))

        #self.modelUpdated.connect(lambda index: print(f"Model updated signal received for index: {index}"))



        self.thumbnailManager = ThumbnailManager(parent=self, max_workers=max_workers, cache_size=cache_size)

        # Connexion du signal thumbnailReady au slot correspondant de ThumbnailManager

        self.thumbnailManager.thumbnailReady.connect(self.on_thumbnail_ready)

        # Connexion explicite du signal thumbnailRequested

        self.thumbnailRequested.connect(self.thumbnailManager.start_worker)

        # Connexion du signal requestProcessed au slot correspondant de FileViewer

        #self.processing_thread.requestProcessed.connect(self.handle_request_processed)



        self.parent = parent

        self.file_list = QListView()

        self.file_list.setStyleSheet("QListView::item { margin: 5px; }")

        self.criteria = ""    

        self._search_timer = QTimer(self)

        self._search_timer.setSingleShot(True)

        self._last_criteria = ""

        self.indexer = indexer

        if self.indexer:

            # Connect the indexUpdated signal with an anonymous lambda function to provide rootPath

            self.indexer.indexUpdated.connect(lambda: QTimer.singleShot(1000,  # Délai de 1 seconde

                                                                self.add_files_to_watcher))

        # Cache resources

        script_directory = os.path.dirname(os.path.abspath(__file__))

        icon_path = os.path.join(script_directory, "dossier.png")

        self.folder_icon = QIcon(QPixmap(icon_path))

        if self.folder_icon.isNull():

            logger.error(f"Failed to load folder icon from {icon_path}")



        #self.thumbnailManager = ThumbnailManager(parent=self, max_workers=max_workers, cache_size=cache_size) # Créer une instance de ThumbnailManager

        self.thumbnailManager.thumbnailReady.connect(self.on_thumbnail_ready)

        logger.debug("Successfully connected thumbnailReady signal to on_thumbnail_ready slot.")

        self.file_watcher = QFileSystemWatcher(self)  # Initialize file_watcher

        self.directoryLoaded.connect(self.on_directory_loaded)

        #self.file_watcher.fileChanged.connect(self.handle_file_change) # Connect to the signal

        logger.debug("End of __init__ function in CustomFileSystemModelForList")



    def event(self, event):

        return super().event(event)  # Revenir à la méthode par défaut



    def rowCount(self, parent=QModelIndex()):

        #logger.debug(f"rowCount called with parent: {parent}")

        return super().rowCount(parent)



    def index(self, *args, **kwargs):  

        #logger.debug(f"index called with row: {row}, column: {column}, parent: {parent}")

        try:

            #result = super().index(row, column, parent)

            #logger.debug(f"CustomFileSystemModelForList.index: Returning index: {result}")

            if len(args) == 1 and isinstance(args[0], str):

                # Appel avec path  

                path = args[0]  

                column = kwargs.get('column', 0)  

                #logger.debug(f"index called with path: {path}, column: {column}")  

                return super().index(path, column)  

                #return super().index(row, column, parent)

            elif len(args) == 2 and isinstance(args[0], str) and isinstance(args[1], int):

                # Appel avec path et column  

                path = args[0]  

                column = args[1]  

                #logger.debug(f"index called with path: {path}, column: {column}")  

                return super().index(path, column)  

            else:

                # Appel avec row, column, parent  

                return super().index(*args, **kwargs)  



        except Exception as e:

            logger.error(f"CustomFileSystemModelForList.index: An error occurred: {e}")

            raise  # Remonte l'exception pour une gestion plus haut dans le code si nécessaire

    def indexFromPath(self, path):

        """Helper function to get the index from a path."""

        #logger.debug(f"Entering CustomFileSystemModelForList.indexFromPath with path: {path}")

        result = super().index(path)

        #logger.debug(f"CustomFileSystemModelForList.indexFromPath: Returning index: {result}")

        return result

    def add_files_to_watcher(self):

        """

        Ajoute les fichiers du répertoire surveillé (WATCHED_DIR) et de ses sous-répertoires 

        au `QFileSystemWatcher` pour surveiller les modifications.

        """

        logger.debug(f"Entering CustomFileSystemModelForList.add_files_to_watcher")

        try:

            if not self.file_watcher:

                logger.warning("file_watcher is not initialized.")

                return



            # Assurez-vous que le répertoire surveillé est correctement normalisé

            normalized_watched_dir = os.path.normpath(WATCHED_DIR)



            # Utiliser QDir pour lister les fichiers dans le répertoire surveillé

            dir = QDir(normalized_watched_dir)

            dir.setNameFilters([f"*{ext}" for ext in self.allowed_extensions])

            dir.setFilter(QDir.Files | QDir.NoSymLinks | QDir.NoDotAndDotDot)



            # Obtenir tous les fichiers correspondants dans le répertoire et ses sous-répertoires

            file_info_list = dir.entryInfoList(QDir.AllEntries | QDir.NoDotAndDotDot, QDir.Name)



            allowed_file_paths = []

            for file_info in file_info_list:

                file_path_str = file_info.absoluteFilePath()

                #logger.debug(f"Checking file path: {file_path_str}")



                # Vérifiez si le fichier est caché ou système

                if file_info.isHidden() or (win32api.GetFileAttributes(file_path_str) & win32con.FILE_ATTRIBUTE_SYSTEM):

                    logger.debug(f"  - Skipped: Is hidden or system file")

                    continue



                # Vérifier l'extension du fichier

                extension = os.path.splitext(file_path_str)[1].lower()

                if extension not in self.allowed_extensions:

                    logger.debug(f"  - Skipped: Extension {extension} not allowed")

                    continue



                allowed_file_paths.append(file_path_str)



            if not allowed_file_paths:

                logger.warning("No valid file paths found to add to QFileSystemWatcher.")

                logger.warning(f"  - Root path: {WATCHED_DIR}")

                logger.warning(f"  - Allowed extensions: {self.allowed_extensions}")

            else:

                self.file_watcher.addPaths(allowed_file_paths)

                logger.info(f"{len(allowed_file_paths)} files added to QFileSystemWatcher.")



        except Exception as e:

            logger.critical(f"Unhandled exception in add_files_to_watcher: {e}", exc_info=True)

        logger.debug("Exiting CustomFileSystemModelForList.add_files_to_watcher")

    

    def on_directory_loaded(self, path):

        """

        Gère les actions à effectuer après le chargement d'un répertoire.

        """

        logger.info(f"on_directory_loaded / Répertoire chargé (CustomFileSystemModelForList) : {path}")

        if self.indexer: 

            normalized_path = os.path.normpath(path)

            if normalized_path.startswith(os.path.normpath(WATCHED_DIR)):

                self.indexer.buildIndex()

                logger.debug("add_files_to_watcher.on_directory_loaded(self, path) / self.indexer.buildIndex()  called")

            #self.add_files_to_watcher(path) 

        else:

            logger.warning("CustomFileSystemModelForList/ on_directory_loaded; Indexer non disponible dans CustomFileSystemModelForList.")



    def data(self, index, role=Qt.DisplayRole):

        #logger.debug(f"CustomFileSystemModelForList.data called for index {index} with role {role}")  

        if role in (Qt.SizeHintRole, Qt.FontRole):

            return None

        try:

            if not index.isValid():

                return None

            if role not in [Qt.DisplayRole, Qt.DecorationRole, Qt.ToolTipRole]:

                return None

            return self.standard_data_method(index, role)

        except Exception as e:

            logger.exception(f"CustomFileSystemModelForList.data: Exception occurred: {e}")

            return None

        finally:

            pass

            #logger.debug(f"CustomFileSystemModelForList.data finished for index {index} with role {role}")



    def standard_data_method(self, index, role):

        """Méthode standard pour fournir les données du modèle."""



        if role in (Qt.SizeHintRole, Qt.FontRole):  # Optimisation : ignorer ces rôles

            return None

        

        file_path = self.filePath(index)

        #logger.debug(f"CustomFileSystemModelForList.standard_data_method called for {file_path}, role: {role}")

        #logger.debug(f"standard_data_method: Current thread: {QThread.currentThread()}")

        try:

            if not index.isValid():

                return None

            #logger.debug(f"Entering role check for {file_path}")

            if role not in (Qt.DisplayRole, Qt.DecorationRole, Qt.ToolTipRole):

               #logger.debug(f"Unsupported role {role} for {file_path}, returning None")

               return None 

            #logger.debug(f"Processing role {role} for {file_path}")

            if role == Qt.DisplayRole:

                filename = os.path.basename(file_path)

                #logger.debug(f"DisplayRole: Original filename {filename}")

                if file_path in QDir.drives():

                    filename = file_path

                    #logger.debug(f"DisplayRole: Filename is a drive {filename}")

                longueur_max_titre = 25

                if len(filename) > longueur_max_titre:

                    filename = filename[:longueur_max_titre - 3] + "..."

                return filename



            elif role == Qt.ToolTipRole:

                #logger.debug(f"ToolTipRole: Returning file path {file_path}")

                return file_path



            elif role == Qt.DecorationRole and index.column() == 0:

                #logger.debug(f"CustomFileSystemModelForList: Standard data method called ; Requesting thumbnail for {file_path} , role: {role}")

                if os.path.isdir(file_path):

                    #logger.debug(f"DecorationRole: {file_path} is a directory, returning folder icon")

                    return self.folder_icon

                elif file_path.lower().endswith(('.dxf', '.png', '.jpg', '.jpeg')):

                    thumbnail = self.thumbnailManager.cache.get(file_path)  # Vérifier le cache

                    if thumbnail is None:

                        with QMutexLocker(self.thumbnailManager.processing_thread.pending_requests_lock):  # Utiliser le verrou spécifique

                            # Vérifier les requêtes en attente dans ThumbnailManager

                            if file_path not in self.thumbnailManager.processing_thread.pending_requests: 

                                self.thumbnailManager.processing_thread.pending_requests.add(file_path)

                                if QThread.currentThread() == QApplication.instance().thread():

                                    self.thumbnailRequested.emit(file_path)

                                else:

                                    # Émettre le signal depuis le thread principal en utilisant un timer

                                    QTimer.singleShot(0, lambda: self.thumbnailRequested.emit(file_path))

                        return QPixmap()  # Retourner une image vide en attendant

                    else:

                        return thumbnail.scaled(160, 120, Qt.KeepAspectRatio, Qt.SmoothTransformation)  # Redimensionner la vignette

                else:

                    return None  # Aucun icône pour les autres types de fichiers

        except Exception as e:

            logger.exception(f"CustomFileSystemModelForList.data: Exception occurred: {e}")

            return None

 

    @pyqtSlot(str, QPixmap, bool)

    def on_thumbnail_ready(self, file_path, thumbnail, success):

        """Slot appelé lorsque la vignette est prête."""

        #logger.debug(f"CustomFileSystemModelForList: on_thumbnail_ready() - file_path: {file_path}, success: {success}")

        if success:

            self.update_thumbnail_cache(file_path, thumbnail)

        else:

            logger.warning(f"on_thumbnail_ready: Échec de la génération de la vignette pour {file_path}")

    @pyqtSlot(str, bool)

    def handle_request_processed(self, file_path, success):

        """Gère le signal requestProcessed du ProcessingThread."""

        if not success:

            # Gérez l'échec de la génération de la vignette (réessayez, affichez un message d'erreur, etc.)

            logger.error(f"CustomFileSystemModelForList: Failed to generate thumbnail for {file_path}")

        else:

            logger.debug(f"CustomFileSystemModelForList: Thumbnail successfully generated for {file_path}")



    @pyqtSlot(str, QPixmap)

    def update_thumbnail_cache(self, file_path, thumbnail):

        """Met à jour le cache des vignettes et émet le signal dataChanged dans le thread principal."""

        #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Entrée avec file_path: {file_path}")

        

        # Utiliser QMutexLocker pour assurer le déverrouillage automatique

        with QMutexLocker(self.thumbnailManager.lock):

            #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Verrou acquis")

            if file_path not in self.thumbnailManager.cache or self.thumbnailManager.cache[file_path] != thumbnail:

                #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Vignette absente du cache ou différente, mise à jour en cours...")

                self.thumbnailManager.cache[file_path] = thumbnail

                #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Vignette mise à jour dans le cache pour {file_path}")

            else:

                logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Vignette déjà présente dans le cache, aucune mise à jour nécessaire.")



        # Émettre le signal dataChanged pour indiquer que les données ont changé

        index = self.indexFromPath(file_path)

        if index.isValid():

            #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Émission du signal dataChanged pour {file_path}")  

            self.dataChanged.emit(index, index, [Qt.DecorationRole])  # Émettre le signal dataChanged pour l'élément spécifique

            #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Signal dataChanged émis pour {file_path}")

        else:

            logger.warning(f"CustomFileSystemModelForList: update_thumbnail_cache() - Index invalide pour {file_path}")



class CustomFileSystemModelForTree(QFileSystemModel):

    def __init__(self, *args, **kwargs):

        super().__init__(*args, **kwargs)

        # Cache the folder icon for performance

        script_directory = os.path.dirname(os.path.abspath(__file__))

        icon_path = os.path.join(script_directory, "dossier.png")

        self.folder_icon = QIcon(QPixmap(icon_path))

        if self.folder_icon.isNull():

            logger.error(f"Failed to load folder icon from {icon_path}")



    def data(self, index, role):

        if role == Qt.DecorationRole and index.column() == 0:

            # Get the full path of the file or directory at the index

            file_path = self.filePath(index)

            # Check if it's a directory

            if os.path.isdir(file_path):

                return self.folder_icon

        elif role == Qt.DisplayRole:

            # Get the default display text

            original_text = super().data(index, role)

            return original_text

        return None



# Background search worker class

class SearchWorker(QObject):

    searchFinished = pyqtSignal(bool, list)  # Signal to indicate search success



    def __init__(self, indexer, index_dir):

        super().__init__()

        self.running = False

        self.running_mutex = QMutex()  # Mutex to protect self.running

        self.rootPath = ""

        self.criteria = ""

        self.criteria_type = "filename"

        self.start_date = None

        self.end_date = None

        self.allowed_extensions = ALLOWED_EXTENSIONS

        self.extension_pattern = re.compile(r".*(" + "|".join([re.escape(ext) + "$" for ext in self.allowed_extensions]) + ")", re.IGNORECASE)

        self.indexer = indexer  

        self.index_dir = os.path.normpath(index_dir)

        logger.debug(f"SearchWorker initialized with index_dir: {self.index_dir}")



    def stop(self):

        """Stop the ongoing search gracefully."""

        self.running_mutex.lock()

        running = self.running

        self.running_mutex.unlock()



        if not running:

            logger.info("Search interrupted by user.")

            return  # Exit if the search was not running



        logger.info("Search stopped by user.")

        self.running = False  # This will cause the search loop to exit



    def setCriteria(self, criteria, criteria_type="filename"):

        """Sets the search criteria and its type."""

        self.criteria = criteria.lower()  # Convert criteria to lowercase

        self.criteria_type = criteria_type

        logger.debug(f"Search criteria set: {criteria} (type: {criteria_type})")



    def setBasePath(self, path):

        self.rootPath = os.path.normpath(path)

        logger.debug(f"Base path for search set: {self.rootPath}")



    def setDateRange(self, start_date, end_date):

        self.start_date = start_date.toPyDate()

        self.end_date = end_date.toPyDate()

        logger.debug(f"Date range set: {self.start_date} - {self.end_date}")



        # Pre-calculate timestamps for date comparison

        self.start_date_timestamp = datetime.combine(self.start_date, datetime.min.time()).timestamp()

        self.end_date_timestamp = datetime.combine(self.end_date, datetime.max.time()).timestamp()



    def performSearch(self, retry=False):

        logger.info("Starting search.")

        logger.debug(f"Root path for search: {self.rootPath}")



        # Verify root path

        if not self.rootPath or not os.path.exists(self.rootPath):

            logger.error("Base path not set or does not exist.")

            self.searchFinished.emit(False, [])

            return



        # Validate dates

        if self.start_date and self.end_date and self.start_date > self.end_date:

            logger.error("Start date must be before end date.")

            self.searchFinished.emit(False, [])

            return



        self.running_mutex.lock()  # Lock before modifying

        self.running = True

        self.running_mutex.unlock()  # Unlock after modification



        results = []

        criteria_lower = self.criteria.lower().strip()

        if not criteria_lower:

            logger.error("Search criteria is empty after processing. Aborting search.")

            self.searchFinished.emit(False, [])

            return



        try:

            # Check if within watched directory

            normalized_root_path = os.path.normcase(os.path.normpath(self.rootPath))

            normalized_watched_dir = os.path.normcase(os.path.normpath(WATCHED_DIR))



            if os.path.commonpath([normalized_root_path, normalized_watched_dir]) == normalized_watched_dir:

                logger.info("Using index-based search.")

                self.index_based_search(results, criteria_lower, retry)

            else:

                logger.info("Using file system search.")

                self.scan_directory(normalized_root_path, results, criteria_lower)



            #logger.debug(f"Final search results: {results}")

        except Exception as e:

            logger.error(f"Error during search: {e}")

            self.searchFinished.emit(False, [])

            return

        finally:

            logger.info("Finalizing search.")

            self.running_mutex.lock()

            self.running = False

            self.running_mutex.unlock()

            self.searchFinished.emit(True, results)

            logger.info(f"Search completed with {len(results)} results.")



    def index_based_search(self, results, criteria_lower, retry):

        try:

            logger.info("Performing index-based search.")

            ix = open_dir(self.index_dir)

            with ix.searcher() as searcher:

                query_parts = []



                # Build query based on criteria type

                if self.criteria_type == "filename":

                    if not criteria_lower.strip():

                        raise ValueError("Search criteria for filename is empty.")

                    query_part = Prefix("name", criteria_lower)

                    query_parts.append(query_part)

                elif self.criteria_type in ("creation_date", "modification_date"):

                    date_field = "creation_time" if self.criteria_type == "creation_date" else "modification_time"

                    query_parts.append(DateRange(date_field, self.start_date, self.end_date))



                # Filter by extensions using regex

                if self.allowed_extensions:  # Check if extensions are specified

                    query_parts.append(self.extension_pattern)



                # Filter by path

                query_parts.append(Prefix("path", os.path.normcase(os.path.normpath(self.rootPath))))

                query = And(query_parts)



                # Execute the search

                hits = searcher.search(query, limit=None)

                for hit in hits:

                    self.running_mutex.lock()  # Protect access to self.running

                    running = self.running

                    self.running_mutex.unlock()

                    if not running:

                        break

                    results.append(hit["path"])

        except EmptyIndexError:

            if not retry:

                logger.warning("Index directory is empty, creating index...")

                self.indexer.buildIndex()

                return self.performSearch(retry=True)  # Retry after rebuilding index

            else:

                logger.error("Index is still empty after rebuilding.")

                self.searchFinished.emit(False, [])

        except Exception as e:

            logger.error(f"Error during Whoosh search: {e}")

            self.searchFinished.emit(False, [])



    def scan_directory(self, directory, results, criteria_lower):

        """Recursively scan the directory for files matching the criteria."""

        for entry in os.scandir(directory):

            if entry.is_dir():

                self.scan_directory(entry.path, results, criteria_lower)  # Recursive call for subdirectories

            elif entry.is_file():

                self.process_file(entry.path, entry.name, results, criteria_lower)



    def process_file(self, fullPath, file, results, criteria_lower):

        try:

            file_lower = file.lower()

            _, extension = os.path.splitext(fullPath)



            # Check extension using regex

            if not self.extension_pattern.match(fullPath):

                return



            # Check criteria

            if self.criteria_type == "filename" and criteria_lower in file_lower:

                results.append(fullPath)

            elif self.criteria_type in ("creation_date", "modification_date"):

                date_to_check = os.path.getctime(fullPath) if self.criteria_type == "creation_date" else os.path.getmtime(fullPath)

                if self.compare_dates(date_to_check) :

                    if criteria_lower in file_lower:

                        results.append(fullPath)

        except Exception as e:

            logger.error(f"Error processing file {fullPath}: {e}")



    def compare_dates(self, file_timestamp):

        return self.start_date_timestamp <= file_timestamp <= self.end_date_timestamp

class TreeProxyModel(QSortFilterProxyModel):

    def filterAcceptsRow(self, source_row, source_parent):

        index = self.sourceModel().index(source_row, 0, source_parent)

        if self.sourceModel().isDir(index):

            #logger.debug(f"Folder accepted for display: {index.data()}")

            return True  # Accept the entry as it is a folder

        #logger.debug(f"Entry rejected (not a folder): {index.data()}")

        return False  # Reject the entry as it is not a folder



class FileListProxyModel(QSortFilterProxyModel):

    def __init__(self, parent=None):

        super(FileListProxyModel, self).__init__(parent)

        self.filter_text = ""

        self.filters = ['.dxf', '.png', '.jpg', '.jpeg']

        self.recursiveSearchEnabled = True  # Enabled by default   

        self.searchPaths = []  # Paths to use for filtering results     



    def setFilterText(self, text):

        """Set the filter text and invalidate the current filter."""

        self.filter_text = text.lower()

        self.invalidateFilter()

        logger.debug(f"Filter text set: {self.filter_text}")   



    def setSearchPaths(self, paths):

        """Set the search paths for filtering."""

        self.searchPaths = paths

        self.invalidateFilter()

        logger.debug(f"Search paths set: {self.searchPaths}") 



    def setRecursiveSearchEnabled(self, enabled):

        """Enable or disable recursive search."""

        self.recursiveSearchEnabled = enabled

        self.invalidateFilter()

        logger.debug(f"Recursive search {'enabled' if enabled else 'disabled'}.")

    

    def filterAcceptsRow(self, sourceRow, sourceParent):

        sourceModel = self.sourceModel()

        if not sourceModel:

            return False



        sourceIndex = sourceModel.index(sourceRow, 0, sourceParent)

        if not sourceIndex.isValid():

            return False



        # Standard filtering logic

        filepath = sourceModel.filePath(sourceIndex).lower()

        isDirectory = sourceModel.isDir(sourceIndex)

        isAcceptableFile = isDirectory or any(filepath.endswith(ext) for ext in self.filters)

        filename = sourceModel.fileName(sourceIndex).lower()



        if self.searchPaths:

            if not any(filepath.startswith(path.lower()) for path in self.searchPaths):

                #logger.debug(f"Path {filepath} rejected by search paths.")

                return False



        if self.filter_text:

            filename = self.sourceModel().data(sourceIndex, Qt.DisplayRole).lower()

            textMatch = self.filter_text in filename or self.filter_text in filepath

        else:

            textMatch = True



        # Final decision for row acceptance

        if isAcceptableFile and textMatch:

            return True



        # If recursive search is enabled and the index is a directory, check subdirectories

        if self.recursiveSearchEnabled and isDirectory:

            rowCount = sourceModel.rowCount(sourceIndex)

            logger.debug(f"Recursive search enabled, number of subdirectories for {filepath}: {rowCount}")

            for childRow in range(rowCount):

                if self.filterAcceptsRow(childRow, sourceIndex):

                    logger.debug(f"Recursive acceptance for {filepath} due to subdirectory at row {childRow}")

                    return True



        #logger.debug(f"Rejected {filepath}")

        return False



class SearchResultsProxyModel(QAbstractProxyModel):

    def __init__(self, parent=None):

        super().__init__(parent)

        self._search_results = []  # Stocke les chemins des fichiers trouvés

        self._path_to_row = {}     # Mapping from path to row index

        self._cached_source_indexes = {}  # Cache pour les index source



    def setSearchResults(self, results):

        """Met à jour les résultats de la recherche et signale les changements au modèle."""

        logger.debug(f"SearchResultsProxyModel.setSearchResults called with {len(results)} results.")

        self.beginResetModel()

        self._search_results = [os.path.normcase(r) for r in results]

        self._path_to_row = {path: idx for idx, path in enumerate(self._search_results)}

        self._cached_source_indexes.clear()  # Vider le cache car les résultats ont changé

        self.endResetModel()

        logger.debug("setSearchResults: Model reset completed.") 



    def rowCount(self, parent=QModelIndex()):

        """Retourne le nombre de résultats de recherche."""

        if parent.isValid():  # Si l'index parent est valide, on est dans un noeud enfant, donc 0 enfant

            return 0

        return len(self._search_results)



    def filePath(self, index):

        """Retourne le chemin du fichier pour l'index donné, en déléguant au modèle source."""

        source_index = self.mapToSource(index)

        return self.sourceModel().filePath(source_index)



    def columnCount(self, parent=QModelIndex()):

        """Retourne le nombre de colonnes du modèle source."""

        return self.sourceModel().columnCount(parent) if self.sourceModel() else 0



    def index(self, row, column, parent=QModelIndex()):

        """Retourne l'index du proxy pour la ligne, la colonne et le parent donnés."""

        if not parent.isValid() and 0 <= row < len(self._search_results):

            source_index = self.sourceModel().indexFromPath(self._search_results[row])

            if source_index.isValid():

                return self.createIndex(row, column, source_index)

            else:

                logger.error(f"Invalid source index for path: {self._search_results[row]}")                     

        return QModelIndex()



    def parent(self, index):

        """Retourne l'index parent, toujours invalide pour ce modèle."""

        return QModelIndex()



    def mapFromSource(self, sourceIndex):

        """Retourne l'index du proxy correspondant à l'index source."""

        logger.debug(f"SearchResultsProxyModel.mapFromSource appelé avec sourceIndex: {sourceIndex}")

        if sourceIndex.isValid():

            source_file_path = os.path.normcase(os.path.normpath(self.sourceModel().filePath(sourceIndex)))

            logger.debug(f"Source File Path: {source_file_path}")

            row = self._path_to_row.get(source_file_path)

            logger.debug(f"Mapped Row: {row}")

            if row is not None:

                return self.createIndex(row, sourceIndex.column())

            else:

                logger.warning("SearchResultsProxyModel.mapFromSource reçu un sourceIndex invalide.")

                return QModelIndex()

        return QModelIndex()



    def mapToSource(self, proxyIndex):

        """Retourne l'index source correspondant à l'index du proxy."""

        #logger.debug(f"FileListProxyModel.mapToSource appelé avec proxyIndex: {proxyIndex}")

        if proxyIndex.isValid() and 0 <= proxyIndex.row() < len(self._search_results):

            source_path = self._search_results[proxyIndex.row()]

            if source_path in self._cached_source_indexes:

                return self._cached_source_indexes[source_path]

            source_index = self.sourceModel().indexFromPath(source_path)

            if source_index.isValid():

                self._cached_source_indexes[source_path] = source_index

            return source_index

        return QModelIndex()

    

    def data(self, index, role=Qt.DisplayRole):

        """Retourne les données pour l'index donné et le rôle."""

        if not index.isValid():

            logger.debug("Invalid index in data method.")

            return None



        if not self.sourceModel():

            logger.error("Source model is not set.")

            return None



        source_index = self.mapToSource(index)  # Obtenir un nouvel index source valide

        if not source_index.isValid():

            logger.debug("Invalid source index in data method.")

            return None



        return self.sourceModel().data(source_index, role)

    def setSourceModel(self, source_model):

        logger.debug("SearchResultsProxyModel: setSourceModel called")

        super().setSourceModel(source_model)

        if source_model is not None:

            source_model.dataChanged.connect(self.sourceDataChanged)



    def sourceDataChanged(self, sourceTopLeft, sourceBottomRight, roles):

        #logger.debug(f"SearchResultsProxyModel: sourceDataChanged called with sourceTopLeft={sourceTopLeft}, sourceBottomRight={sourceBottomRight}, roles={roles}")

        # Mapper les index source vers les index du proxy

        proxyTopLeft = self.mapFromSource(sourceTopLeft)

        proxyBottomRight = self.mapFromSource(sourceBottomRight)

        #logger.debug(f"SearchResultsProxyModel: Mapped proxyTopLeft={proxyTopLeft}, proxyBottomRight={proxyBottomRight}")



        if proxyTopLeft.isValid() and proxyBottomRight.isValid():

            #logger.debug("SearchResultsProxyModel: Emitting dataChanged from proxy model")

            # Émettre le signal dataChanged du proxy avec les rôles appropriés

            self.dataChanged.emit(proxyTopLeft, proxyBottomRight, roles)

        else:

            logger.debug("SearchResultsProxyModel: Skipping dataChanged emission, model is not active.")

    

class ThumbnailWorkerSignals(QObject):

    """Classe pour regrouper les signaux du ThumbnailWorker."""

    thumbnailReady = pyqtSignal(str, QPixmap, bool)  # Chemin du fichier, vignette, succès

    finished = pyqtSignal()

    errorOccurred = pyqtSignal(str, Exception)  # Chemin du fichier, exception

 

class ThumbnailWorker(QRunnable):

    """Worker pour générer les vignettes de manière asynchrone."""



    def __init__(self, file_path, cache):

        super().__init__()

        self.signals = ThumbnailWorkerSignals()  # Instance pour gérer les signaux

        self.file_path = file_path

        self.cache = cache  # Référence au cache partagé

        self._cancel_requested = False



    @pyqtSlot()

    def cancel(self):

        """Annule la génération de la vignette si elle est en cours."""

        self._cancel_requested = True



    @pyqtSlot()  # Supprimer l'argument file_path

    def run(self):  # Méthode exécutée dans un thread séparé pour générer la vignette

        #logger.debug(f"ThumbnailWorker: run() Démarrage pour {self.file_path}")

        if self._cancel_requested:

            logger.warning(f"ThumbnailWorker: run() cancelled for {self.file_path}")

            return



        # Vérifier si la vignette est déjà dans le cache

        if self.file_path in self.cache:

            #logger.debug(f"ThumbnailWorker: Thumbnail found in cache for {self.file_path}")

            self.signals.thumbnailReady.emit(self.file_path, self.cache[self.file_path], True)

            self.signals.finished.emit()

            return

        

        try:

            #logger.debug(f"ThumbnailWorker: Generating thumbnail for {self.file_path}")

            thumbnail = self.generate_thumbnail(self.file_path)

            success = thumbnail is not None and not thumbnail.isNull()

            #logger.debug(f"ThumbnailWorker: Thumbnail generated for {self.file_path}, success: {success}")



            if success:

                self.signals.thumbnailReady.emit(self.file_path, thumbnail, success)

        except (FileNotFoundError, ValueError, CancelledError) as e:

            logger.error(f"ThumbnailWorker: Error generating thumbnail for {self.file_path}: {e}")

            self.signals.errorOccurred.emit(self.file_path, e)

        finally:

            self.signals.finished.emit()



    def generate_thumbnail(self, file_path):

        """Génère une vignette pour le fichier donné."""

        #logger.debug(f"ThumbnailWorker: generate_thumbnail called for {file_path}")

        try:

            if file_path.lower().endswith(('.png', '.jpg', '.jpeg')):

                thumbnail = self.generate_image_thumbnail(file_path)

            elif file_path.lower().endswith('.dxf'):

                thumbnail = self.generate_dxf_thumbnail(file_path)

            else:

                logger.warning(f"ThumbnailWorker: generate_thumbnail() - Unsupported file format for {file_path}")

                raise ValueError(f"Unsupported file format: {file_path}")

            return thumbnail

        except Exception as e:

            logger.error(f"ThumbnailWorker: Error generating thumbnail for {file_path}: {e}", exc_info=True)

            raise



    def generate_image_thumbnail(self, image_path):

        """Génère une miniature à partir d'un fichier image."""

        #logger.debug(f"ThumbnailWorker: generate_image_thumbnail for image: {image_path}")

        try:

            if self._cancel_requested:

                logger.warning(f"ThumbnailWorker: generate_image_thumbnail - Annulé pour {image_path}")

                return None  # Return None if cancelle

            size = QSize(160, 120)

            reader = QImageReader(image_path)

            reader.setAutoTransform(True)

            image = reader.read()

            if image.isNull():

                logger.error(f"Impossible de lire l'image: {image_path}")

                return QPixmap()

            thumbnail = QPixmap.fromImage(image).scaled(size, Qt.KeepAspectRatio, Qt.SmoothTransformation)

            return thumbnail

        except ValueError as ve:

            logger.error(f"ValueError creating thumbnail for {image_path}: {ve}", exc_info=True)

            return QPixmap()

        except Exception as e:

            logger.error(f"Error creating thumbnail for {image_path}: {e}", exc_info=True)

            return QPixmap()  # Return an empty QPixmap on failure

 #Suite de la class...



class FileViewer(QMainWindow):

    folderChanged  = pyqtSignal(str)

    fileSelected = pyqtSignal(str)

    rechercheChanged = pyqtSignal(str)

    def __init__(self):

        super().__init__()

        self.scale_factor = 1  # Initialiser avec une valeur par défaut

        self.indexeur = FileIndexer()

        self.setWindowTitle("Souf Dxf Explorer")

        self.setGeometry(100, 100, 800, 600)



        self.model = CustomFileSystemModelForList(self)

        self.isSearchThreadRunning = False

        self._in_search_mode = False



        # Search indicator

        self.search_indicator = QLabel("Recherche en cours...", self)

        self.statusBar().addWidget(self.search_indicator)

        self.search_indicator.hide()



        self.current_folder = os.path.normpath(os.path.expanduser("~")) 

        self.previous_folder = None

        #self.current_folders = [QDir.homePath()]  # Initialize the current_folders attribute

        #self.rootPaths = ["Y:/"]

        #logger.debug(f"[Init] Current folders initialized to: {self.current_folders}")

        self.setupModelsAndView()

        self.setupUIComponents()

        self.setupObservers()

        self.connect_signals()

        self.indexeur.indexUpdated.connect(self.on_indexing_finished) 

        self.indexeur.indexerError.connect(self.on_indexing_error) # connecter au slot

        self.indexeur.indexingProgress.connect(self.update_indexing_progress)  # Connecter le signal

        # Démarrer l'indexation asynchrone après l'initialisation de l'interface

        #QTimer.singleShot(3000, lambda: (self.indexeur.buildIndexAsync(), logger.info(" Fileviewer.__init__(self) (self.indexeur.buildIndexAsync())Lambda function in QTimer.singleShot executed")))

        #logger.info("FileViewer / Asynchronous indexing initiated.") 

    

    def connect_signals(self):

        if not hasattr(self, 'signals_connected'):

            # Connexions pour la mise à jour de la vue de la liste de fichiers

            self.file_list.model().dataChanged.connect(self.update_file_list_view)

            self.model.modelUpdated.connect(self.update_file_list_view)

            logger.info("Connection: file_list.clicked -> display_selected_file")

            self.file_list.clicked.connect(self.display_selected_file)

            logger.info("Connection: file_list.doubleClicked -> on_folder_double_clicked")

            self.file_list.doubleClicked.connect(self.on_folder_double_clicked)



            # Connexions pour la recherche

            self.SearchWorker.searchFinished.connect(self.onSearchFinished)

            self.searchThread.started.connect(self.SearchWorker.performSearch)



            # Connexions pour les temporisateurs et les champs de texte

            self.search_timer.timeout.connect(self.launchSearch)

            self.search_box.textChanged.connect(self.launch_delayed_search)



            # Configuration du proxy de recherche

            self.search_results_proxy_model = SearchResultsProxyModel(self)

            self.search_results_proxy_model.setSourceModel(self.model)  # Définir le modèle source du proxy



            self.signals_connected = True



    def setupUIComponents(self):

        self.pathLabel = QLabel("Path:")

        self.setup_ui()

        self.setupMenu()

        self.setupToolbar()



    def setupModelsAndView(self):

        #self.indexeur = FileIndexer()

        self.rootPaths = [os.path.normpath("")]

        for path in self.rootPaths:

            if os.path.exists(path):

                print(f"Le chemin {path} est valide.")

            else:

                #print(f"Le chemin {path} n'est pas valide.")

                print(f"Le chemin path est:{path} ")

        self.tree_model = CustomFileSystemModelForTree()

        self.file_list_model = CustomFileSystemModelForList(self, self.indexeur)

        self.file_list = QListView()

        self.setup_tree_dock_widget()

        self.setup_proxy_tree_model()



        # Add SearchResultsProxyModel

        self.search_results_proxy_model = SearchResultsProxyModel(self)

        self.search_results_proxy_model.setSourceModel(self.file_list_model)



        # Connect signal modelReset

        self.search_results_proxy_model.modelReset.connect(self.file_list.reset)

        # Connect signals layoutChanged and dataChanged

        self.search_results_proxy_model.layoutChanged.connect(self.file_list.reset)

        self.search_results_proxy_model.dataChanged.connect(self.file_list.reset)



        # Configure FileListProxyModel to use SearchResultsProxyModel as source

        self.file_list_proxy = FileListProxyModel()

        self.setup_file_list_proxy()

        self.file_list_proxy.setSourceModel(self.file_list_model)



        # Configure list view to use the proxy file_list_proxy

        self.file_list.setModel(self.file_list_proxy)



        self.file_list_model.directoryLoaded.connect(self.file_list_model.on_directory_loaded)



    def setupObservers(self):

        self.SearchWorker = SearchWorker(self.indexeur, self.indexeur.index_dir)

        self.searchThread = QThread()

        self.SearchWorker.moveToThread(self.searchThread)



        self.FileMonitor = FileMonitor(self.indexeur, self.SearchWorker.allowed_extensions) 

        self.observer = Observer()

        self.observer.schedule(self.FileMonitor, path=os.path.normpath(WATCHED_DIR), recursive=True)   # Surveiller WATCHED_DIR

        self.observer.start()



        self.search_timer = QTimer(self)

        self.search_timer.setSingleShot(True)



        self.update_directory_label(f"Current folder path: {self.tree_model.rootPath()}")



    def displayResults(self, success, results):

        logger.debug("Beginning of displayResults function.")



        # Arrêter le profilage et afficher les résultats

        #if hasattr(self, 'profiler'):  # Vérifier si le profilage a été activé

            #self.profiler.disable()

            #s = io.StringIO()

            #ps = pstats.Stats(self.profiler, stream=s).sort_stats('cumulative')

            #ps.print_stats()

           # logger.debug(f"Profiling results:\n{s.getvalue()}")



        # Eviter les appels récursifs ou redondants

        if hasattr(self, 'display_results_called') and self.display_results_called:

            logger.debug("displayResults already called, skipping.")

            return



        self.display_results_called = True

        self.isSearchThreadRunning = False

        self.search_indicator.hide()



        # Gérer les différents cas de figure

        if not success:

            logger.debug("Handling search failure.")

            self.handleSearchFailure()

            self._in_search_mode = False  # Assurez de quitter le mode recherche en cas d'échec

        elif not results:

            logger.debug("Handling no results.")

            self.handleNoResults()

            self._in_search_mode = False

        elif not self.search_box.text().strip():

            logger.debug("Search box is empty, resetting list view.")

            self.resetListView()

            self._in_search_mode = False

        else:

            # Afficher les résultats de la recherche

            self._in_search_mode = True

            logger.debug("Updating proxy model with search results.")

            self.search_results_proxy_model.setSearchResults(results)  # Mettre à jour le proxy

            # Si le modèle actuel de la vue n'est pas le modèle de résultats, le définir

            if self.file_list.model() != self.search_results_proxy_model:

                self.file_list.setModel(self.search_results_proxy_model)

            logger.debug("SearchResultsProxyModel set as the current model for file_list.")

            # Rafraîchir la vue

            #self.file_list.reset()

            #logger.debug(f"displayResults: ListView reset. Number of items in view: {self.file_list.model().rowCount()} ")

            # Forcer le focus et mettre à jour la vue

            #self.file_list.setFocus()

            #self.file_list.update()

            #self.file_list.repaint()

            # (Facultatif) Sélectionner le premier élément si souhaité

            if results:

                first_result_index = self.search_results_proxy_model.index(0, 0)

                if first_result_index.isValid():

                    self.file_list.setCurrentIndex(first_result_index)

            logger.debug("Logging search results.")

            self.logSearchResults(results)



        self.display_results_called = False

        logger.debug("End of displayResults function.")



    def launch_delayed_search(self):

        logger.debug("Slot activated: launch_delayed_search")

        search_criteria = self.search_box.text().strip()

        if search_criteria:

            logger.info("Search criteria modified. Waiting before launching search...")

            if not self.previous_folder: 

                self.previous_folder = self.current_folder  # Sauvegarder le dossier actuel 

            self.search_timer.stop()  # Stop the existing timer before restarting

            if self.search_timer.isActive():

                self.search_timer.stop()

            self.search_timer.start(1000)  # Delay of 1000 ms

        else:

            logger.info("Search bar empty. Displaying current folder.")

            self.exit_search_mode()

        logger.debug("End of launch_delayed_search")

    def launchSearch(self):

        logger.debug("Function launchSearch start")

        criteria = self.search_box.text().strip()

        criteria_type = self.search_criteria_type.currentText().lower().replace(" ", "_")

        logger.debug(f"Search criteria: {criteria}, criteria_type: {criteria_type}")

        logger.debug(f"Current root path: {self.file_list_model.rootPath()}")

        logger.debug(f"WATCHED_DIR: {WATCHED_DIR}")

        if not criteria and criteria_type == "filename":

            logger.debug("Search criteria is empty, exiting search.")

            #QMessageBox.warning(self, "Warning", "Search criteria cannot be empty for filename search.")

            self.exit_search_mode()

            return



        if self.isSearchThreadRunning:

            logger.info("Search already running, stopping current search.")

            self.SearchWorker.running = False  # Stop the thread gracefully

            self.searchThread.quit()

            self.searchThread.wait()

        # Sauvegarder le dossier actuel avant de lancer la recherche  

        if not self.previous_folder:  

            self.previous_folder = self.current_folder  

            logger.debug(f"Saved current folder to previous_folder: {self.previous_folder}") 



        self.isSearchThreadRunning = True

        self.SearchWorker.setCriteria(criteria, criteria_type)

        self.SearchWorker.setDateRange(self.start_date.date(), self.end_date.date())



        # Assurez-vous que rootPath est bien défini

        current_root_path = self.file_list_model.rootPath()

        if current_root_path:

            self.SearchWorker.setBasePath(current_root_path)

            #self.profiler = cProfile.Profile()

            #self.profiler.enable()

            #self.searchThread.started.connect(self.SearchWorker.performSearch)

            self.searchThread.start()

            self.search_indicator.show()

            logger.debug("Function launchSearch end")

        else:

            logger.error("Current root path is not set. Cannot start search.")

            self.isSearchThreadRunning = False

    def onSearchFinished(self, success, results):

        logger.debug("Search finished with success: {}".format(success))

        self.isSearchThreadRunning = False

        if self.searchThread.isRunning():

            logger.debug("Stopping search thread.")

            self.searchThread.quit()

            self.searchThread.wait()  # Ensure the thread is fully stopped

            logger.debug("Search thread stopped.")

        logger.debug("Search thread stopped and resources cleaned up.")

        self.handle_search_completion(success, results)

        logger.debug("End of onSearchFinished function.")



    def handle_search_completion(self, success, results):

        logger.debug("Function handle_search_completion start")

        if success:

            logger.info("Search completed successfully.")

            self.displayResults(success, results)

            # Connecter le proxy de recherche à la vue SI en mode recherche

            if self._in_search_mode:  

                self.file_list.setModel(self.search_results_proxy_model)

            else:

                # Revenir au proxy normal si on n'est plus en recherche

                self.file_list.setModel(self.file_list_proxy) 

        else:

            logger.error("Search failed, attempting to restart.")

            logger.debug(f"Search results (should be empty on failure): {results}")

            # Resetting the thread to allow for new searches

            self.searchThread.quit()

            self.searchThread.wait()  # Wait until the thread cleanly finishes

            self.searchThread = QThread()  # Recreate the thread for future use

            self.SearchWorker = SearchWorker(self.indexeur, self.indexeur.index_dir)

            self.SearchWorker.moveToThread(self.searchThread)

            self.searchThread.started.connect(self.SearchWorker.performSearch)

            self.SearchWorker.searchFinished.connect(self.onSearchFinished)



        logger.debug("Function handle_search_completion end")



    def handleSearchFailure(self):

        logger.debug("Beginning of handleSearchFailure function.")

        QMessageBox.warning(self, "Search Failed", "The search failed or was stopped without results.")

        self.resetListView()

        logger.debug("End of handleSearchFailure function.")



    def handleNoResults(self):

        logger.debug("Beginning of handleNoResults function.")

        QMessageBox.information(self, "No Results", "No results to display.")

        self.resetListView()

        logger.debug("End of handleNoResults function.")



    def logSearchResults(self, results):

        logger.debug("Beginning of logSearchResults function.")

        for result in results[:min(5, len(results))]:

            logger.debug(f"Search result: {result}")

        logger.debug("End of logSearchResults function.")

    

    def exit_search_mode(self):

        logger.debug("Function exit_search_mode start")

        self._in_search_mode = False

        self.file_list_proxy.invalidateFilter()

        if self.previous_folder:  

            logger.debug(f"[exit_search_mode] Previous folder: {self.previous_folder}") 

            self.current_folder = self.previous_folder  # Restaurer le dossier précédent  

            self.previous_folder = None  # Réinitialiser previous_folder  

            self.file_list_model.setRootPath(self.current_folder)

            root_index = self.file_list_model.indexFromPath(self.current_folder)

            if root_index.isValid():

                if hasattr(self, 'file_list_proxy'):

                    self.file_list_proxy.setSourceModel(self.file_list_model)

                    self.file_list.setModel(self.file_list_proxy)  

                    proxy_root_index = self.file_list_proxy.mapFromSource(root_index)

                    if proxy_root_index.isValid():

                       self.file_list.setRootIndex(proxy_root_index)

                    else:

                        logger.error(f"Invalid proxy root index: {proxy_root_index}")

                else:

                    self.file_list.setModel(self.file_list_model) 

                    self.file_list.setRootIndex(root_index)

            else:

                logger.error(f"Invalid root index: {root_index}")

            self.update_directory_label(f"Current folder: {self.current_folder}")  

            logger.info(f"[exit_search_mode] Current folder updated to: {self.current_folder}") 

        else:

            logger.warning("[exit_search_mode] No previous folder stored. Using default path.")  

            default_folder = QDir.homePath()

            self.file_list_model.setRootPath(default_folder)  

            root_index = self.file_list_model.index(default_folder)



            if hasattr(self, 'file_list_proxy'):

                self.file_list_proxy.setSourceModel(self.file_list_model)

                proxy_root_index = self.file_list_proxy.mapFromSource(root_index)

                self.file_list.setRootIndex(proxy_root_index)

            else:

                self.file_list.setRootIndex(root_index)



            self.update_directory_label(f"Current folder: {default_folder}")

            logger.info(f"[Update] Current folder updated to: {default_folder}")



        logger.debug("Function exit_search_mode end")



    def resetListView(self):

        """Réinitialise la vue de liste pour afficher le contenu du répertoire courant."""

        try:

            self._in_search_mode = False  # Sortir du mode recherche

            self.file_list.setModel(self.file_list_proxy)  # Rétablir le proxy principal



            # Vérifier si current_folder est valide avant de continuer

            if self.current_folder and os.path.exists(self.current_folder):

                logger.debug(f"[resetListView] Current folder: {self.current_folder}")

                self.updateFileListModel(self.current_folder)  

                self.update_file_list_view()

                self.checkDisplayedFiles(self.file_list_model)

                self.updateDirectoryLabel()

            else:

                self.handleNoCurrentFolder()



            self.file_list.reset()  # Rafraîchir la vue après les modifications



        except Exception as e:

            self.handleResetListViewError(e)

    def updateFileListModel(self, folder_path):

        """Met à jour le chemin racine du modèle de liste de fichiers.



        Args:

            folder_path (str): Le nouveau chemin racine pour le modèle.

        """

        logger.debug("Fonction updateFileListModel: Début")

        if not isinstance(folder_path, str) or not folder_path:

            logger.error("Le chemin du dossier doit être une chaîne de caractères valide.")

            QMessageBox.warning(self, "Erreur", "Le chemin du dossier doit être une chaîne de caractères valide.")

            return

        if not os.path.isdir(folder_path):

            logger.error(f"Le chemin fourni n'est pas valide ou n'est pas un dossier: {folder_path}")

            QMessageBox.warning(self, "Erreur", "Le chemin fourni n'est pas un dossier valide.")

            return

        self.file_list_model.setRootPath(folder_path)

        logger.info(f"Chemin racine du modèle de liste de fichiers mis à jour: {folder_path}")

        logger.debug("Fonction updateFileListModel: Fin")



    def update_file_list_view(self, folder_path=None, index=None):

        logger.debug("Fonction update_file_list_view: Début")

        try:

            root_index = None

            if folder_path is not None:

                if isinstance(folder_path, str):

                    logger.debug(f"Chemin du dossier fourni: {folder_path}")

                    self.updateFileListModel(folder_path)

                    root_index = self.file_list_model.indexFromPath(folder_path)

                    if not root_index.isValid():

                        logger.error("Index racine obtenu à partir du chemin est invalide.")

                        return

                else:

                    logger.error("Le chemin du dossier doit être une chaîne de caractères valide.")

                    return

            elif index is not None:

                logger.debug(f"Index fourni: {index}")

                if not index.isValid():

                    logger.error("Index non valide reçu pour la mise à jour.")

                    return

                root_index = index if not isinstance(index.model(), QSortFilterProxyModel) else index.model().mapToSource(index)

                if not root_index.isValid():

                    logger.error("Index racine obtenu à partir de l'index fourni est invalide.")

                    return

            else:

                if self.current_folder:  

                    folder_path = self.current_folder  

                    logger.debug(f"Utilisation du dossier actuel: {folder_path}")  

                    self.updateFileListModel(folder_path)  

                    root_index = self.file_list_model.indexFromPath(folder_path)

                    if not root_index.isValid():

                        logger.error("Index racine obtenu à partir du dossier actuel est invalide.")

                        return

                else:

                    logger.error("Aucun dossier courant défini.")  

                    return  

 

            if hasattr(self, 'file_list_proxy'):

                self.file_list_proxy.setSourceModel(self.file_list_model)

                proxy_root_index = self.file_list_proxy.mapFromSource(root_index)

                if proxy_root_index.isValid():

                    self.file_list.setRootIndex(proxy_root_index)

                    logger.info(f"Vue de la liste mise à jour avec proxy. Index source: {root_index}, Index proxy: {proxy_root_index}")

                else:

                    logger.error("Erreur lors de la mise à jour de la vue avec proxy: L'index proxy n'est pas valide.")

            else:

                if root_index.isValid():

                    self.file_list.setRootIndex(root_index)

                    logger.info(f"Vue de la liste mise à jour sans proxy. Index source: {root_index}")

                else:

                    logger.error("Erreur lors de la mise à jour de la vue sans proxy: L'index racine n'est pas valide.")

        except Exception as e:

            logger.error(f"Erreur dans update_file_list_view: {e}")

            QMessageBox.warning(self, "Erreur", f"Une erreur s'est produite: {e}")



        logger.debug("Fonction update_file_list_view: Fin")



    def showModelInfo(self):

        logger.debug("Beginning of showModelInfo function.")

        try:

            self.logModelContents(self.file_list_model)

        except Exception as e:

            logger.error(f"An error occurred while displaying model information: {str(e)}")

            QMessageBox.critical(self, "Error", f"An error occurred: {str(e)}")

        logger.debug("End of showModelInfo function.")



    def logModelContents(self, model):

        logger.debug("Beginning of logModelContents function.")

        root_path = model.rootPath() 

        root_index = model.indexFromPath(root_path)

        for row in range(min(5, model.rowCount(root_index))):

            child_index = model.index(row, 0, root_index)

            if child_index.isValid():

                file_path = model.filePath(child_index)

                logger.debug(f"Element at index {row}: {file_path}")

            else:

                logger.warning(f"Invalid index at row {row}")

        logger.debug("End of logModelContents function.")



    def handleNoCurrentFolder(self):

        logger.info("No current folder set, not updating the file list.")

        self.current_folder = os.path.expanduser("~")  # Chemin par défaut vers le dossier utilisateur  

        self.updateFileListModel(self.current_folder)  

        self.updateDirectoryLabel()  

        self.update_file_list_view() 

        # You can add additional logic here, such as displaying a message to the user



    def setCurrentFolderPath(self):

        default_folder = os.path.expanduser("~")  # Ou un autre chemin par défaut

        self.current_folder = default_folder

        logger.debug(f"Current folder path reset to default: {self.current_folder}")



    def updateDirectoryLabel(self):

        self.update_directory_label(f"Current folder: {self.current_folder}")

        logger.debug("Directory label updated.")



    def handleResetListViewError(self, error):

        logger.error(f"Error resetting list view: {error}")

        QMessageBox.warning(self, "Error", "Problem resetting list view.")

        logger.warning("Reset list view error handled.")



    def checkDisplayedFiles(self, model):

        actual_files = [model.filePath(model.index(row, 0)) for row in range(model.rowCount())]

        logger.info("Files currently displayed in the view: " + str(actual_files))

    

    @pyqtSlot(int, int)

    def update_indexing_progress(self, processed_files, total_files):

        # Mettre à jour la barre de progression ou afficher un message de statut

        if total_files > 0:

            progress_percent = int(processed_files / total_files * 100)

            QMetaObject.invokeMethod(self, "update_status_bar", Qt.QueuedConnection,

                             Q_ARG(str, f"Indexing in progress... {progress_percent}%"))

        else:

            progress_percent = 0

    @pyqtSlot(str)

    def update_status_bar(self, message):

        self.statusBar().showMessage(message)

    @pyqtSlot()

    def on_indexing_finished(self):

        """Slot appelé lorsque l'indexation est terminée."""

        logger.info("FileViewer / Indexing finished successfully.")

        # Ajoutez ici le code que vous souhaitez exécuter à la fin de l'indexation

        #self.update_file_list_view(WATCHED_DIR)  # Mettre à jour la vue de liste après l'indexation

        # ... (autres actions)

        # Activez la recherche lorsque l'indexation est terminée

        self.search_button.setEnabled(True)

    @pyqtSlot(str)

    def on_indexing_error(self, error_message):

        """Slot appelé en cas d'erreur lors de l'indexation."""

        logger.error(f"FileViewer / Indexing error: {error_message}")

        # Gérez l'erreur ici (par exemple, affichez un message à l'utilisateur)

        self.is_indexing = False  # Reset the flag in case of an error

        QMessageBox.critical(self, "Erreur d'indexation", f"Une erreur est survenue lors de l'indexation : {error_message}")



    def get_proxy_model_for_source_model(self, source_model):

        logger.debug("Function get_proxy_model_for_source_model start")

        if source_model == self.file_list_model:

            logger.info("Found matching proxy model for file list model.")

            return self.file_list_proxy

        elif source_model == self.tree_model:

            logger.info("Found matching proxy model for tree model.")

            return self.tree_proxy

        logger.warning("No matching proxy model found for given source model.")

        logger.debug("Function get_proxy_model_for_source_model end")

        return None



    def get_view_for_model(self, model):

        logger.debug("Function get_view_for_model start")

        if model == self.file_list_model:

            logger.info("Found matching view for file list model.")

            return self.file_list

        elif model == self.tree_model:

            logger.info("Found matching view for tree model.")

            return self.tree_view

        logger.debug("Function get_view_for_model end")

        return None



    def on_root_path_changed(self, new_path):

        #self.current_folder = new_path  

        logger.debug("Function on_root_path_changed start with new path: {}".format(new_path))

        self.file_list_proxy.setSourceModel(self.file_list_model)  # Re-attach source model if needed

        self.update_file_list_view()

        logger.debug("Function on_root_path_changed end")



    def update_directory_label(self, text=None):

        logger.info("Function update_directory_label start")

        if text is None:  

            text = f"Current folder: {self.current_folder}" 

        logger.debug(f"Attempting to update directory label with text: {text}") 

        if hasattr(self, 'directory_label'):

            self.directory_label.setText(text)

            logger.info(f"Updated directory label: {text}")

        else:

            logger.error("Error: Directory label is not initialized.")

            # Optionally initialize the label here if needed

            self.directory_label = QLabel(text)

            logger.info(f"Initialized and updated directory label: {text}")

        logger.info("Function update_directory_label end")



    def onRecursiveSearchToggled(self, checked):

        logger.debug("Function onRecursiveSearchToggled start")

        self.file_list_proxy.setRecursiveSearchEnabled(checked)

        logger.info(f"Recursive search {'enabled' if checked else 'disabled'}.")

        logger.debug("Function onRecursiveSearchToggled end")

    

    def setup_proxy_tree_model(self):

        logger.debug("Starting setup of the proxy model for the tree/Function setup_proxy_tree_model")

        if self.tree_model is None:

            logger.error("Error: The source model (tree_model) is not initialized.")

            return

        if hasattr(self, 'tree_proxy_initialized') and self.tree_proxy_initialized:

            logger.debug("Proxy model for the tree is already set up.")

            return

        self.tree_proxy = TreeProxyModel() 

        self.tree_proxy.setSourceModel(self.tree_model)

        logger.info("Source model assigned to the tree proxy.")

        tree_filters = ['*']  # Specific filters for the tree

        self.tree_proxy.setFilterWildcard(' '.join(tree_filters))

        self.tree_proxy.invalidateFilter()

        logger.info("Filter of the tree proxy model invalidated to reflect the changes.")

        self.tree_view.setModel(self.tree_proxy)

        self.tree_proxy_initialized = True

        logger.debug("End of the setup of the proxy model for the tree/Function setup_proxy_tree_model")



    def setup_file_list_proxy(self):

        logger.debug("Starting setup of the proxy model for the file list./Function setup_file_list_proxy")

        if self.file_list_model is None:

            logger.error("Error: The source model (file_list_model) is not initialized.")

            return

        try:

            self.file_list_proxy = FileListProxyModel() 

            self.file_list_proxy.setSourceModel(self.file_list_model)

            logger.info("Source model assigned to the file list proxy.")

            # Configuring filters

            file_list_filters = ['*.dxf', '*.jpg', '*.jpeg', '*.png', '*.bmp']  # Filters for the file list view

            self.file_list_proxy.setFilterWildcard(' '.join(file_list_filters))

            logger.info(f"Filters applied for the file list proxy model: {' '.join(file_list_filters)}")

            # Applying filters and updating the view

            self.file_list_proxy.invalidateFilter()

            logger.info("Filter of the file list proxy model invalidated to reflect the changes.")

            self.file_list.setModel(self.file_list_proxy)

        except Exception as e:

            logger.error(f"Error during the configuration of the proxy model: {str(e)}")

        logger.debug("End of the setup of the proxy model for the file list./Function setup_file_list_proxy")



    def setup_ui(self):

        logger.debug("Starting the setup_ui function in FileViewer")

        self.setupTreeModelAndView()

        self.setupListModelAndView()

        self.central_layout = QVBoxLayout()

        self.main_splitter = QSplitter(Qt.Horizontal)

        self.setCentralWidget(self.main_splitter)

        self.tree_dock_widget.setFeatures(QDockWidget.NoDockWidgetFeatures)

        self.setup_central_frame()  

        self.setup_preview_dock_widget()

        self.main_splitter.setSizes([445])



        logger.info("Connecting signals and slots:")

        self.central_layout.addWidget(self.file_list)  

        self.directory_label = QLabel("Current folder path:") 

        logger.debug("[Init] Directory label initialized") 

        self.central_layout.addWidget(self.directory_label) 

        logger.debug("[Layout] Directory label added to layout")

        self.file_list.setContextMenuPolicy(Qt.CustomContextMenu)

        self.file_list.customContextMenuRequested.connect(self.open_context_menu)   

        logger.debug("End of the setup_ui function in FileViewer")



    def handle_root_path_changed(self, new_root_path):

        logger.debug("Starting the handle_root_path_changed function")

        logger.info(f"New root path: {new_root_path}")

        current_root_path = self.file_list_model.rootPath()

        if new_root_path != current_root_path:

            self.current_folder = new_root_path  # Mettre à jour current_folder 

            self.update_file_list_view(new_root_path) 

        logger.debug("End of the handle_root_path_changed function")



    def setupTreeModelAndView(self):

        logger.debug("Starting the setupTreeModelAndView function")

        self.tree_model = CustomFileSystemModelForTree()

        self.tree_model.setRootPath("")

        self.tree_proxy = TreeProxyModel()

        self.tree_proxy.setSourceModel(self.tree_model)

        self.tree_view.setModel(self.tree_proxy)

        logger.debug("End of the setupTreeModelAndView function")



    def setupListModelAndView(self):

        logger.debug("Starting the setupListModelAndView function")

        self.file_list_model.setRootPath(self.current_folder)

        logger.info(f"Initial root path for the file list model: (empty)")

        # Configuring the proxy model

        self.file_list_proxy = FileListProxyModel()

        self.file_list_proxy.setSourceModel(self.file_list_model)

        logger.info("Proxy model configured with the file list model.")

        self.file_list_proxy.setFilterKeyColumn(0)

        self.file_list_proxy.setFilterWildcard("*.dxf *.jpg *.jpeg *.png *.bmp")

        # Associating the proxy model with the view

        self.file_list.setModel(self.file_list_proxy)

        logger.info("File list view configured with the proxy model.")

        self.file_list.setRootIndex(QModelIndex())

        logger.info("File list view configured to display empty content.")

        #self.showModelInfo()

        logger.info("Triggered showModelInfo:")

        logger.debug("End of the setupListModelAndView function")



    def setup_tree_dock_widget(self):

        logger.debug("Start of setup_tree_dock_widget function")

        self.tree_dock_widget = QDockWidget("Tree")

        self.tree_view = QTreeView(self.tree_dock_widget)

        self.tree_dock_widget.setWidget(self.tree_view)

        self.tree_dock_widget.setMinimumWidth(455)

        self.tree_view.clicked.connect(self.on_tree_clicked)

        logger.info("Connection: tree_view.clicked -> on_tree_clicked")

        #self.tree_model = CustomFileSystemModelForTree() 

        self.tree_model.setRootPath("")

        root_path = self.tree_model.rootPath()

        logger.info(f"Tree root path: {root_path}")



        self.setup_proxy_tree_model()

        header = self.tree_view.header()    

        header.setSectionHidden(1, True) 

        header.setSectionHidden(2, True) 

        header.setSectionHidden(3, True) 

        self.tree_view.setHeaderHidden(False)  # Hide header if desired

        self.addDockWidget(Qt.LeftDockWidgetArea, self.tree_dock_widget)

        logger.debug("End of setup_tree_dock_widget function")



    def setup_preview_dock_widget(self):

        logger.debug("Start of setup_preview_dock_widget function")

        self.preview_dock_widget = QDockWidget("Preview")

        self.preview_dock_widget.setFixedHeight(455)

        self.preview_dock_widget.setMinimumWidth(455)



        self.preview_scene = QGraphicsScene()

        self.preview_view = CustomGraphicsView(self.preview_scene)  

        self.preview_dock_widget.setWidget(self.preview_view)

        self.addDockWidget(Qt.LeftDockWidgetArea, self.preview_dock_widget)



        # Barre d'outils pour le zoom et les modes de mesure

        self.toolbar = QToolBar("Zoom and Measure Tools", self)

        self.addToolBar(Qt.TopToolBarArea, self.toolbar)

        

        zoom_in_action = QAction("Zoom In", self)

        zoom_in_action.triggered.connect(self.preview_view.zoom_in)

        self.toolbar.addAction(zoom_in_action)



        zoom_out_action = QAction("Zoom Out", self)

        zoom_out_action.triggered.connect(self.preview_view.zoom_out)

        self.toolbar.addAction(zoom_out_action)

        # Séparateur visuel

        self.toolbar.addSeparator()

        # Actions pour les modes de mesure

        action_measure_distance = QAction(QIcon("iconDistance.png"), "Distance Mode", self)

        action_measure_distance.triggered.connect(lambda: self.preview_view.set_measure_mode(MeasureMode.DISTANCE))

        self.toolbar.addAction(action_measure_distance)



        action_measure_angle = QAction(QIcon("iconAngle.png"), "Angle Mode", self)

        action_measure_angle.triggered.connect(lambda: self.preview_view.set_measure_mode(MeasureMode.ANGLE))

        self.toolbar.addAction(action_measure_angle)

        # Configuration de la vue et de la scène

        self.canvas = CustomGraphicsView()

        self.preview_view.setScene(self.preview_scene)

        self.preview_view.fitInView(self.preview_scene.sceneRect(), Qt.KeepAspectRatio)



        self.scene = QGraphicsScene()

        self.canvas.setScene(self.scene)



        logger.debug("End of setup_preview_dock_widget function")

    def setup_central_frame(self):

        logger.debug("Start of setup_central_frame function")

        self.central_frame = QWidget()

        self.central_layout = QVBoxLayout(self.central_frame)



        self.label_files = QLabel("File List:")

        self.central_layout.addWidget(self.label_files)



        self.search_box = QLineEdit()

        self.search_box.setPlaceholderText("Search files...")

        self.central_layout.addWidget(self.search_box)



        self.search_criteria_type = QComboBox(self)

        self.search_criteria_type.addItems(["Filename", "Creation Date", "Modification Date"])

        self.central_layout.addWidget(self.search_criteria_type)



        self.start_date = QDateEdit(self)

        self.start_date.setCalendarPopup(True)

        self.start_date.setDate(QDate.currentDate())

        self.central_layout.addWidget(self.start_date)



        self.end_date = QDateEdit(self)

        self.end_date.setCalendarPopup(True)

        self.end_date.setDate(QDate.currentDate())

        self.central_layout.addWidget(self.end_date)



        self.search_button = QPushButton("Search", self)

        self.central_layout.addWidget(self.search_button)



        #self.file_list = QListView()

        self.file_list.setIconSize(QSize(160, 120))

        logger.info(f"New icon size for QListView: {self.file_list.iconSize()}")

        self.file_list.setGridSize(QSize(175, 135))

        self.file_list.setModel(self.file_list_proxy )  # Use the file list proxy model here

        self.file_list.setViewMode(QListView.IconMode)

        #self.file_list_model.setRootPath(QDir.rootPath()) 

        root_index_model_files = self.file_list_proxy.sourceModel().index(0, 0) 

        proxy_root_index = self.file_list_proxy.mapFromSource(root_index_model_files)

        self.file_list.setRootIndex(proxy_root_index)

        

        self.central_layout.addWidget(self.file_list)

        self.main_splitter.addWidget(self.central_frame)



        logger.debug("End of setup_central_frame function")

    def on_signal_emitted(self, index):

        logger.debug(f"Start on_signal_emitted (self.update_file_list(index))")

        logger.debug(f"Signal emitted. Index: {index}")

        if index.isValid():

            folder_path = self.file_list_model.filePath(index)

            self.current_folder = folder_path  

            self.update_file_list_view(folder_path)

        else:

            logger.error("Invalid index received in on_signal_emitted")

        logger.debug(f"End on_signal_emitted")

    def on_folder_double_clicked(self, index):

        logger.debug("Start on_folder_double_clicked")

        if not index.isValid():

            logger.error("The index is not valid.")

            QMessageBox.warning(self, "Error", "Invalid index selected.")

            return

        # Détermination du modèle de base et de l'indice source

        if isinstance(index.model(), QSortFilterProxyModel):

            source_index = index.model().mapToSource(index)

            if index.model() == self.file_list_proxy:

                base_model = self.file_list_model

                logger.info("Index from file list proxy")

            elif index.model() == self.tree_proxy:

                base_model = self.tree_model

                logger.info("Index from tree proxy")

            else:

                QMessageBox.warning(self, "Error", "Unknown model type")

                return

        else:

            source_index = index

            base_model = index.model()

        folder_path = base_model.filePath(source_index)

        if os.path.isdir(folder_path) and os.access(folder_path, os.R_OK):

            logger.info(f"Selected folder path: {folder_path}")

            self.current_folder = folder_path  

            logger.debug(f"[on_folder_double_clicked] Current folder updated to: {self.current_folder}")  

            self.file_list_model.setRootPath(folder_path)

            logger.info(f"Root path of the file list model updated: {folder_path}")

            # Mise à jour de l'indice root de la vue

            updated_root_index = self.file_list_model.indexFromPath(folder_path)

            if hasattr(self, 'file_list_proxy'):

                proxy_root_index = self.file_list_proxy.mapFromSource(updated_root_index)

                if proxy_root_index.isValid():

                   self.file_list.setRootIndex(proxy_root_index)

                else:

                    logger.error("Invalid proxy root index after mapping.")

                    QMessageBox.warning(None, "Mapping Error", "Invalid proxy root index after mapping.")

            else:

                self.file_list.setRootIndex(updated_root_index)

            logger.info("List view updated to display the content of the selected folder.")

            self.update_directory_label(f"Current folder path: {self.current_folder}")  

        else:

            msg = "The selected path is not a directory." if not os.path.isdir(folder_path) else "The directory is not accessible."

            logger.error(f"{msg}: {folder_path}")

            QMessageBox.warning(self, "Error", msg)

        logger.debug("End on_folder_double_clicked")



    def on_tree_clicked(self, index):

        logger.debug("Start of on_tree_clicked")

        

        if self._in_search_mode:

            logger.info("Exiting search mode after tree click.")

            self._in_search_mode = False

            self.file_list.setModel(self.file_list_proxy)

            self.search_box.clear()

        logger.debug(f"Start processing index: {index}")

        if not index.isValid():

            logger.error("The provided index is invalid.")

            return



        # Map the proxy index to the source index

        source_index = self.tree_proxy.mapToSource(index) if isinstance(index.model(), QSortFilterProxyModel) else index

        if not source_index.isValid():

            logger.error("Index mapping failed. Attempting reset.")

            return



        # Get the directory path

        try:

            directory_path = self.tree_model.filePath(source_index)

            self.current_folder = directory_path  

        except IndexError:

            logger.error(f"Error getting directory path from index: {source_index}")

            return

        assert os.path.isdir(directory_path), f"Invalid directory path: {directory_path}"

        logger.debug(f"Directory path: {directory_path}")



        #Only update if the directory is different

        if directory_path == self.file_list_model.rootPath():

            logger.debug("Selected directory is already the active directory. Skipping update.")

            return



        # Update the file list model 

        logger.info(f"Updating file list model for directory: {directory_path}")

        self.file_list_model.setRootPath(directory_path)



        # Directly update the list view after setting the root path

        list_root_index = self.file_list_model.indexFromPath(directory_path)

        self.update_view_index(self.file_list_model, self.file_list, list_root_index)



        self.update_directory_label(f"Current folder path: {self.current_folder}")  

        logger.info(f"[on_tree_clicked] Current folder path updated to: {directory_path}")

        logger.debug("End of on_tree_clicked")



    def update_view_index(self, model, view, root_index):

        """Updates the view's index based on the model and root index."""

        if hasattr(self, 'file_list_proxy') and model is self.file_list_model:

            index_proxy = self.file_list_proxy.mapFromSource(root_index)

            if index_proxy.isValid():

                view.setRootIndex(index_proxy)

            else:

                logger.error("Root proxy index is not valid after mapping.")

        else:

            view.setRootIndex(root_index)

    def load_folder(self):

        """Loads a folder selected by the user."""

        logger.info("Start of load_folder function")

        folder_path = QFileDialog.getExistingDirectory(self, "Choose a folder")

        if folder_path:

            try:

                # Check if the path is accessible (read and write)

                if not os.access(folder_path, os.R_OK | os.W_OK):

                    raise OSError("The selected folder is not accessible for read or write.")



                logger.info(f"Selected folder path: {folder_path}")

                self.current_folder = folder_path

                self.update_directory_label(f"Current folder path: {self.current_folder}")

                logger.debug(f"[load_folder] Current folder updated to: {self.current_folder}")



                # Update path in the list model

                self.file_list_model.setRootPath(self.current_folder)



                # Update list view index

                list_root_index = self.file_list_model.indexFromPath(self.current_folder)

                self.update_view_index(self.file_list_model, self.file_list, list_root_index)



                self.folderChanged.emit(folder_path)



            except OSError as e:

                QMessageBox.warning(self, "Error", str(e))

                logger.error(f"Error accessing folder: {e}")

        else:

            logger.warning("No folder selected.")

        logger.info("End of load_folder function")



    def navigate_up(self):

        logger.info("Start navigate_up")

        logger.debug("Attempting to navigate up one level in the file tree.")

        current_index = self.file_list.rootIndex()

        index_parent = self.file_list_proxy.mapToSource(current_index).parent()  # Use the correct proxy model here

        if index_parent.isValid():

            self.file_list.setRootIndex(self.file_list_proxy.mapFromSource(index_parent))

            folder_path = self.file_list_model.filePath(index_parent)

            self.current_folder = folder_path  

            self.update_directory_label(f"Current folder path: {self.current_folder}") 

            logger.info(f"Current directory updated to: {self.current_folder}")  

        else:

            logger.warning("Unable to navigate up, no valid parent found.")

        logger.info("End navigate_up")



    def on_tree_selection_changed(self, selected, deselected):

        logger.debug("Function on_tree_selection_changed start")

        index = self.tree_view.currentIndex()

        logger.debug(f"Current index in the tree: {index}")

        self.navigate_folder(index)

        logger.debug("Function on_tree_selection_changed end")



    def on_list_selection_changed(self, selected, deselected):

        logger.debug("Function on_list_selection_changed start")

        index_source = self.file_list.currentIndex()

        logger.debug(f"Current source index in the list: {index_source}")

        self.navigate_folder(index_source)

        logger.debug("Function on_list_selection_changed end")



    def navigate_folder(self, index):

        logger.debug("Function navigate_folder start")

        if not index.isValid():

            logger.error("Error: Invalid index provided for navigation.")

            return

        folder_path = self.file_list_model.filePath(index)

        if not os.path.exists(folder_path):

            logger.error(f"Error: Folder path does not exist - {folder_path}")

            return

        self.current_folder = folder_path  # Mise à jour du dossier courant  

        self.file_list_model.setRootPath(self.current_folder)

        logger.info(f"Updated root path of the file list model to: {self.current_folder}")

        self.update_file_list_view(folder_path)

        self.update_directory_label(f"Current folder: {self.current_folder}")

        logger.debug("Function navigate_folder end")



    def change_list_view(self):

        logger.info("Start change_list_view")

        logger.info("Switching view to List View")

        self.file_list.setViewMode(QListView.ListMode)

        logger.debug("File view switched to list.")

        logger.info("End change_list_view")



    def change_thumbnail_view(self):

        logger.info("Start change_thumbnail_view")

        logger.info("Switching view to Thumbnail View")

        self.file_list.setViewMode(QListView.IconMode)

        logger.debug("File view switched to thumbnails.")

        logger.info("End change_thumbnail_view")



    def setupMenu(self):

        logger.info("Start setupMenu")

        logger.debug("Setting up the menu bar")

        menuBar = self.menuBar()

        fileMenu = menuBar.addMenu("&File")

        openAction = QAction("&Open", self)

        openAction.setShortcut("Ctrl+O")

        openAction.triggered.connect(self.load_folder)  # Assuming `load_folder` is your method to open a folder

        exitAction = QAction("&Exit", self)

        exitAction.setShortcut("Ctrl+Q")

        exitAction.triggered.connect(self.close)

        fileMenu.addAction(openAction)

        fileMenu.addSeparator()

        fileMenu.addAction(exitAction)

        viewMenu = menuBar.addMenu("&View")

        listViewAction = QAction("&List View", self)

        listViewAction.triggered.connect(self.change_list_view)

        viewMenu.addAction(listViewAction)

        thumbnailViewAction = QAction("&Thumbnail View", self)

        thumbnailViewAction.triggered.connect(self.change_thumbnail_view)

        viewMenu.addAction(thumbnailViewAction)

        logger.debug("Menu bar configured with File and View menus.")

        logger.info("End setupMenu")



    def setupToolbar(self):

        logger.info("Start setupToolbar")

        logger.debug("Setting up the toolbar")

        toolbar = self.addToolBar("Main Toolbar")

        returnAction = QAction(QIcon("iconReturn.png"), "Return", self) 

        toolbar.setIconSize(QSize(24, 24))

        returnAction.triggered.connect(self.navigate_up)

        toolbar.addAction(returnAction)

        logger.debug("Toolbar configured")

        logger.info("End setupToolbar")



    def closeEvent(self, event):

        logger.info("Function closeEvent start")

        logger.info("Closing application, stopping threads and observer.")

        try:

            if hasattr(self, 'observer') and self.observer:

                self.observer.stop()

                self.observer.join()

            if hasattr(self, 'searchThread') and self.searchThread.isRunning():

                self.SearchWorker.running = False  # Assurez-vous que SearchWorker peut être arrêté proprement

                #self.SearchWorker.stop() 

                self.searchThread.quit()

                self.searchThread.wait()  # Wait up to 0 seconds for the thread to cleanly finish

        except Exception as e:

            logger.error(f"Error during closing: {e}")

        finally:

            super().closeEvent(event)

        logger.info("Function closeEvent end")



    def display_file(self, file):

        logger.debug(f"Start of display_file function with file: {file}")

        try:

            if os.path.isdir(file):

                self.display_folder(file)

            elif file.lower().endswith(('.png', '.jpg', '.jpeg')):

                self.display_image(file)

            elif file.lower().endswith('.dxf'):

                self.display_dxf(file)

            else:

                self.display_default_message()

        except Exception as e:

            logger.error(f"Error displaying file {file}: {e}")

            self.display_default_message()

        logger.info(f"End of display_file function")



    def display_folder(self, folder):

        logger.info(f" Function display_folder / Displaying folder: {folder}")

        self.current_folder = folder  # Mise à jour du dossier courant  

        self.update_directory_label(f"Current folder: {self.current_folder}")  

        # Ajoutez le code nécessaire pour afficher le contenu du dossier si nécessaire  



    def display_selected_file(self, index):

        """Affiche le fichier sélectionné dans le dock de prévisualisation."""

        logger.info("Start of display_selected_file")

        logger.debug(f"Function display_selected_file with index: {index}")

        logger.debug(f"État du thread avant la sélection: {QThread.currentThread()}") 

        try:



            # Déterminer le modèle à utiliser en fonction du mode de recherche

            if self._in_search_mode:

                model = self.search_results_proxy_model

                logger.debug("Mode recherche activé. Utilisation de search_results_proxy_model.")

            else:

                model = self.file_list_proxy 

                logger.debug("Mode recherche désactivé. Utilisation de file_list_proxy.")

            

            if not model:

                logger.error("Model is not initialized")

                return



            # Convertir l'index du proxy en index du modèle source

            source_index = model.mapToSource(index)



            if not source_index.isValid():

                logger.error("Invalid index for file selection")

                return



            file_path = self.file_list_model.filePath(source_index)

            if not file_path:

                logger.error("File path could not be retrieved")

                return



            logger.debug(f"Ready to open: {file_path}")

            logger.info(f"Displaying file for index {index}: {file_path}")

            logger.debug(f"FileViewer.display_selected_file() - Appel de display_file pour {file_path}")

            self.display_file(file_path)

            self.fileSelected.emit(file_path)

            logger.info(f"Emitted fileSelected signal for: {file_path}")

        except Exception as e:

            logger.error(f"Error in display_selected_file: {e}")

        finally:

            logger.info("End of display_selected_file")



#SUITE DU SCRIPT ..