explore toute les piste possible lié à l'erreur  WARNING - mapToSource: Invalid proxyIndex received qui peut empêcher de sélectionner correctement certaine resultats de recherche
2024-11-15 08:43:44,689 - DEBUG - ThumbnailCache: __getitem__ appelé pour Y:/divers fichiers dxf à trier/ARC/test/A0.DXF
2024-11-15 08:43:44,689 - DEBUG - Thumbnail retrieved from cache for: Y:/divers fichiers dxf à trier/ARC/test/A0.DXF
2024-11-15 08:43:44,689 - DEBUG - Entering CustomFileSystemModelForList.indexFromPath with path: Y:/divers fichiers dxf à trier/ARC/test/A0.DXF
2024-11-15 08:43:44,690 - DEBUG - CustomFileSystemModelForList.indexFromPath: Returning index: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1A740>
2024-11-15 08:43:44,690 - DEBUG - Start SearchResultsProxyModel.sourceDataChanged
2024-11-15 08:43:44,690 - DEBUG - SearchResultsProxyModel.mapFromSource called with sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1A820>
2024-11-15 08:43:44,690 - DEBUG - SearchResultsProxyModel.mapFromSource called with sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1A7B0>
2024-11-15 08:43:44,690 - DEBUG - SearchResultsProxyModel.mapToSource called with proxyIndex: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1AB30>
2024-11-15 08:43:44,690 - DEBUG - Entering CustomFileSystemModelForList.indexFromPath with path: y:\divers fichiers dxf à trier\arc\test\a0.dxf
2024-11-15 08:43:44,692 - DEBUG - CustomFileSystemModelForList.indexFromPath: Returning index: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1A9E0>
2024-11-15 08:43:44,692 - DEBUG - SearchResultsProxyModel.mapToSource called with proxyIndex: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1AB30>
2024-11-15 08:43:44,692 - DEBUG - Entering CustomFileSystemModelForList.indexFromPath with path: y:\divers fichiers dxf à trier\arc\test\a0.dxf
2024-11-15 08:43:44,693 - DEBUG - CustomFileSystemModelForList.indexFromPath: Returning index: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1A9E0>
2024-11-15 08:43:44,693 - DEBUG - SearchResultsProxyModel.mapToSource called with proxyIndex: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1AB30>
2024-11-15 08:43:44,693 - DEBUG - Entering CustomFileSystemModelForList.indexFromPath with path: y:\divers fichiers dxf à trier\arc\test\a0.dxf
2024-11-15 08:43:44,693 - DEBUG - CustomFileSystemModelForList.indexFromPath: Returning index: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1A9E0>
2024-11-15 08:43:44,694 - DEBUG - SearchResultsProxyModel.mapToSource called with proxyIndex: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1AB30>
2024-11-15 08:43:44,694 - DEBUG - Entering CustomFileSystemModelForList.indexFromPath with path: y:\divers fichiers dxf à trier\arc\test\a0.dxf
2024-11-15 08:43:44,694 - DEBUG - CustomFileSystemModelForList.indexFromPath: Returning index: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1A9E0>
2024-11-15 08:43:44,694 - DEBUG - SearchResultsProxyModel.mapToSource called with proxyIndex: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1AB30>
2024-11-15 08:43:44,695 - DEBUG - Entering CustomFileSystemModelForList.indexFromPath with path: y:\divers fichiers dxf à trier\arc\test\a0.dxf
2024-11-15 08:43:44,695 - DEBUG - CustomFileSystemModelForList.indexFromPath: Returning index: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1A9E0>
2024-11-15 08:43:44,695 - DEBUG - SearchResultsProxyModel.mapToSource called with proxyIndex: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1AB30>
2024-11-15 08:43:44,696 - DEBUG - Entering CustomFileSystemModelForList.indexFromPath with path: y:\divers fichiers dxf à trier\arc\test\a0.dxf
2024-11-15 08:43:44,696 - DEBUG - CustomFileSystemModelForList.indexFromPath: Returning index: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1A9E0>
2024-11-15 08:43:44,696 - DEBUG - SearchResultsProxyModel.mapToSource called with proxyIndex: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1AC10>
2024-11-15 08:43:44,696 - DEBUG - Entering CustomFileSystemModelForList.indexFromPath with path: y:\divers fichiers dxf à trier\arc\test\a0.dxf
2024-11-15 08:43:44,697 - DEBUG - CustomFileSystemModelForList.indexFromPath: Returning index: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1AB30>
2024-11-15 08:43:44,697 - DEBUG - SearchResultsProxyModel.mapToSource called with proxyIndex: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1AC10>
2024-11-15 08:43:44,697 - DEBUG - Entering CustomFileSystemModelForList.indexFromPath with path: y:\divers fichiers dxf à trier\arc\test\a0.dxf
2024-11-15 08:43:44,698 - DEBUG - CustomFileSystemModelForList.indexFromPath: Returning index: <PyQt5.QtCore.QModelIndex object at 0x0000015230E1AB30>
2024-11-15 08:43:44,698 - DEBUG - SearchResultsProxyModel.mapToSource called with proxyIndex: <PyQt5.QtCore.QModelIndex object at 0x000001522FB83F50>
2024-11-15 08:43:44,698 - WARNING - mapToSource: Invalid proxyIndex received.
2024-11-15 08:43:44,698 - DEBUG - SearchResultsProxyModel.mapToSource called with proxyIndex: <PyQt5.QtCore.QModelIndex object at 0x000001522FB83F50>
2024-11-15 08:43:44,698 - WARNING - mapToSource: Invalid proxyIndex received.
# Définition des constantes
MAX_RETRIES = 3  # Nombre maximum de tentatives pour traiter un fichier
INDEX_DIR = os.path.normpath(r"C:\index_dir") # Chemin d'accès centralisé à l'index Whoosh
if os.path.exists(INDEX_DIR):
    print("Le chemin INDEX_DIR est valide.")
    # Ajoutez votre logique pour surveiller le dossier ici
else:
    print("Le chemin INDEX_DIR n'est pas valide.")
WATCHED_DIR = os.path.normpath(r"Y:\DocSoufiane")# Constante pour le répertoire spécifique à surveiller
if os.path.exists(WATCHED_DIR):
    print("Le chemin WATCHED_DIR est valide.")
    # Ajoutez votre logique pour surveiller le dossier ici
else:
    print("Le chemin WATCHED_DIR n'est pas valide.")
ALLOWED_EXTENSIONS = ['.dxf', '.png', '.jpg', '.jpeg', '.bmp']

# Setup logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

def handle_uncaught_exception(exc_type, exc_value, exc_traceback):
    """Global exception handler."""
    logger.critical("Unhandled exception!", exc_info=(exc_type, exc_value, exc_traceback))
    sys.__excepthook__(exc_type, exc_value, exc_traceback)  # Call the default exception handler

# Install the global exception handler
sys.excepthook = handle_uncaught_exception
class CancelledError(Exception):
    pass
class RequestState(Enum):
    PENDING = 'pending'
    IN_PROGRESS = 'in_progress'
    PROCESSED = 'processed'
class RequestThread(QThread):
    """Thread dédié à la gestion de la file d'attente des requêtes de vignettes."""
    thumbnailRequested = pyqtSignal(str)  # Signal pour transmettre les requêtes au ProcessingThread

    def __init__(self, parent=None):
        super().__init__(parent)
        self.request_queue = deque()
        self.lock = QMutex()
    def run(self):
        """Boucle principale du thread.
        Attend les demandes de vignettes et les transmet au thread de traitement.
        """
        while True:
            file_path = None
            with QMutexLocker(self.lock):  # Verrouiller l'accès à la file d'attente
                if self.request_queue:
                    file_path = self.request_queue.popleft()

            if file_path:
                logger.debug(f"RequestThread: Sending thumbnail request for {file_path}")
                self.thumbnailRequested.emit(file_path)  # Émettre le signal pour demander la vignette
            else:
                time.sleep(0.1)  # Pause de 100 ms

    @pyqtSlot(str)
    def enqueue_request(self, file_path):
        """Ajoute une requête de vignette à la file d'attente.
        Args:
            file_path (str): Chemin du fichier pour lequel une vignette est demandée.
        """
        with QMutexLocker(self.lock):  # Verrouiller l'accès à la file d'attente
            if file_path not in self.request_queue:
                self.request_queue.append(file_path)
                logger.debug(f"RequestThread: Request queued for {file_path}. Queue: {list(self.request_queue)}")
class ProcessingThread(QThread):
    """Thread dédié au traitement des requêtes de vignettes et à la gestion du cache."""
    thumbnailReady = pyqtSignal(str, QPixmap, bool)  # Signal pour notifier la disponibilité des vignettes
    requestProcessed = pyqtSignal(str, bool)  # Signal pour signaler le traitement d'une requête

    def __init__(self, thumbnail_manager, max_workers=10, parent=None):
        super().__init__(parent)
        self.thumbnail_manager = thumbnail_manager  # Référence au ThumbnailManager
        self.max_workers = max_workers
        self.request_queue = deque()
        self.lock = QMutex()
        self.pending_requests_lock = QMutex()  # Verrou spécifique pour pending_requests
        self.semaphore = QSemaphore(max_workers)  # Sémaphore pour limiter le nombre de workers
        self.thumbnail_manager.requestThumbnail.connect(self.handle_thumbnail_request)
        self.pending_requests = set() 

    def run(self):
        """Boucle principale du thread.
        
        Traite les requêtes de vignettes en lançant des ThumbnailWorker.
        """
        while True:
            file_path = None
            with QMutexLocker(self.lock):  # Verrouiller l'accès à la file d'attente
                if self.request_queue:
                    file_path = self.request_queue.popleft()

            if file_path:
                with QMutexLocker(self.pending_requests_lock):  # Protéger l'accès à pending_requests
                    if file_path not in self.pending_requests:
                        self.pending_requests.add(file_path)
                        if self.semaphore.tryAcquire():  # Acquérir un sémaphore si disponible
                            logger.debug(f"ProcessingThread: Processing request for {file_path}")
                            worker = ThumbnailWorker(file_path, self.cache)  # Passer le cache ici
                            worker.signals.thumbnailReady.connect(self.thumbnailReady.emit)  # Connecter le signal du worker
                            worker.signals.finished.connect(self.worker_finished)
                            worker.signals.errorOccurred.connect(self.handle_worker_error)
                            self.thumbnail_manager.threadpool.start(worker)
                        else:
                            # Si aucun sémaphore n'est disponible, remettre la requête en file d'attente
                            with QMutexLocker(self.lock):
                                self.request_queue.appendleft(file_path)
                            logger.debug(f"ProcessingThread: No semaphore available, request for {file_path} requeued")
            else:
                # Pause pour éviter la surcharge CPU lorsque la file d'attente est vide
                time.sleep(0.1)

    @pyqtSlot(str)
    def handle_thumbnail_request(self, file_path):
        """Ajoute une requête de vignette à la file d'attente.     
        Args:
            file_path (str): Chemin du fichier pour lequel une vignette est demandée.
        """
        with QMutexLocker(self.lock):  # Verrouiller l'accès à la file d'attente
            # Vérifier si le fichier est déjà dans la file d'attente ou en cours de traitement
            with QMutexLocker(self.pending_requests_lock):  # Utiliser un verrou pour pending_requests
                if file_path not in self.request_queue and file_path not in self.pending_requests:
                    self.request_queue.append(file_path)
                    logger.debug(f"ProcessingThread: Request queued for {file_path}. Queue: {list(self.request_queue)}")

    @pyqtSlot()
    def worker_finished(self, file_path):
        """Appelée lorsque le travailleur a terminé, pour libérer le sémaphore et mettre à jour les requêtes en attente."""
        with QMutexLocker(self.pending_requests_lock):
            self.pending_requests.discard(file_path)
        self.semaphore.release()
    @pyqtSlot(str, Exception)
    def handle_worker_error(self, file_path, error):
        """Gérer les erreurs survenues dans le travailleur."""
        logger.error(f"Error processing {file_path}: {error}")
        # Retirer le fichier des requêtes en attente même en cas d'erreur
        with QMutexLocker(self.pending_requests_lock):
            self.pending_requests.discard(file_path)
        self.semaphore.release()

class ThumbnailManager(QObject):
    """Gère la coordination des vignettes, le cache et la communication entre les threads."""

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

    def __init__(self, parent=None, max_workers=10, cache_size=5000):
        super().__init__(parent)
        self.cache = ThumbnailCache(cache_size)
        self.request_thread = RequestThread()
        self.processing_thread = ProcessingThread(self)
        self.request_thread.thumbnailRequested.connect(self.processing_thread.handle_thumbnail_request)
        self.request_thread.start()
        self.processing_thread.start()

        self.threadpool = QThreadPool()
        self.threadpool.setMaxThreadCount(max_workers)
        self.lock = QMutex()  # Verrou pour protéger l'accès au cache et aux workers
        self.workers = {}  # Dictionnaire pour suivre les workers actifs

    @pyqtSlot(str, QPixmap, bool)
    def handle_thumbnail_ready(self, file_path, thumbnail, success):
        """Gère la réception des vignettes générées par les workers."""
        with QMutexLocker(self.lock):
            if success:
                #logger.debug(f"handle_thumbnail_ready: Mise en cache de la vignette pour {file_path}")
                self.cache[file_path] = thumbnail  # Mettre en cache la vignette
            if file_path in self.workers:
                del self.workers[file_path]  # Supprimer le worker terminé
                #logger.debug(f"handle_thumbnail_ready: Worker supprimé pour {file_path}")

        #logger.debug(f"handle_thumbnail_ready: Émission du signal thumbnailReady pour {file_path}")
        self.thumbnailReady.emit(file_path, thumbnail, success)  # Émettre le signal pour mettre à jour la vue

    def start_worker(self, file_path):
        """Lance un worker pour générer une vignette pour le fichier donné."""
        with QMutexLocker(self.lock):
            if file_path in self.cache:  # Vérifier si la vignette est déjà dans le cache
                #logger.debug(f"ThumbnailManager: Thumbnail found in cache for {file_path}")
                self.thumbnailReady.emit(file_path, self.cache[file_path], True)
                return

            if file_path in self.workers:  # Vérifier si un worker est déjà en cours pour ce fichier
                logger.debug(f"ThumbnailManager: Thumbnail generation already in progress for {file_path}")
                return

            #logger.debug(f"ThumbnailManager: Starting thumbnail worker for {file_path}")
            worker = ThumbnailWorker(file_path, self.cache)
            worker.signals.thumbnailReady.connect(self.handle_thumbnail_ready)
            worker.signals.finished.connect(self.worker_finished)
            self.workers[file_path] = worker  # Ajouter le worker au dictionnaire
            self.threadpool.start(worker)

    @pyqtSlot()
    def worker_finished(self):
        """Slot appelé lorsqu'un worker a terminé."""
        worker = self.sender()
        if worker is not None and hasattr(worker, 'file_path'):  # Check if worker is valid and has file_path
            file_path = worker.file_path
            logger.debug(f"ThumbnailManager: Worker finished for {file_path}")
            with QMutexLocker(self.lock):
                if file_path in self.workers:
                    del self.workers[file_path]
                    logger.debug(f"worker_finished: Worker supprimé pour {file_path}")
                else:
                    logger.warning("ThumbnailManager: Worker finished but sender is None or doesn't have file_path")
class ThumbnailCache(OrderedDict):
    """Cache pour les vignettes générées."""
    def __init__(self, cache_size=5000):
        super().__init__()
        self.cache_size = cache_size
        logger.debug(f"ThumbnailCache: Initialisé avec une taille maximale de {cache_size} vignettes.")

    def __getitem__(self, key):
        logger.debug(f"ThumbnailCache: __getitem__ appelé pour {key}")
        try:
            value = super().__getitem__(key)
            self.move_to_end(key)
            logger.debug(f"Thumbnail retrieved from cache for: {key}")
            return value
        except KeyError:
            logger.warning(f"ThumbnailCache: Vignette non trouvée dans le cache pour {key}")
            raise  # Relève l'exception pour que le code appelant puisse la gérer

    def get(self, key):
        #logger.debug(f"ThumbnailCache: get appelé pour {key}")
        value = super().pop(key, None)
        if value is not None:
            self[key] = value  # Remettre à la fin pour indiquer un accès récent
            #logger.debug(f"(ThumbnailCache get): Thumbnail found in cache for: {key}")
        #else:
            #logger.debug(f"(ThumbnailCache get): Thumbnail not found in cache for: {key}")
        return value

    def __setitem__(self, key, value):
        #logger.debug(f"ThumbnailCache: __setitem__ appelé pour {key}")
        super().__setitem__(key, value)
        self.move_to_end(key)  # Déplacer l'élément à la fin (le plus récemment utilisé)
        if len(self) > self.cache_size:
            oldest = next(iter(self))
            logger.debug(f"ThumbnailCache: Suppression de l'élément le plus ancien {oldest}")
            self.pop(oldest)  # Supprimer l'élément le moins récemment utilisé
    
# 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 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))  

        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)
        logger.debug("Successfully connected thumbnailReady signal to on_thumbnail_ready slot.")
        # 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.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 QFileSystemModel.index(self, 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 QFileSystemModel.index(self, path, column)
            else:
                # Appel avec row, column, parent  
                return QFileSystemModel.index(self, *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 = QFileSystemModel.index(self, 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):
        """
        Retourne les données pour un index donné et un rôle spécifié.

        Args:
            index (QModelIndex): L'index pour lequel les données sont demandées.
            role (int): Le rôle des données à retourner (par défaut Qt.DisplayRole).

        Returns:
            QVariant: Les données correspondant à l'index et au rôle spécifié, ou None si 
                      l'index est invalide ou si le rôle n'est pas pris en charge.
        """
        #logger.debug(f"CustomFileSystemModelForList.data called for index {index} with role {role}") 

        # Vérification de la validité de l'index
        if not index.isValid():
            logger.warning(f"CustomFileSystemModelForList.data: Index invalide reçu: {index}")
            return None
        # Vérification des rôles non pris en charge
        if role in (Qt.SizeHintRole, Qt.FontRole):
            return None
        
        # Vérification des rôles pris en charge
        if role in [Qt.DisplayRole, Qt.DecorationRole, Qt.ToolTipRole]:
            try:
                # Appel à la méthode standard pour obtenir les données
                return self.standard_data_method(index, role)
            except Exception as e:
                logger.exception(f"CustomFileSystemModelForList.data: Erreur: {e}")
                return None
        else:
            #logger.debug(f"CustomFileSystemModelForList.data: Rôle non pris en charge: {role}")
            return None

    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


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 = []  # Contient les chemins des fichiers
        self._path_to_row = {}     # Mapping du chemin vers l'index de la ligne

    def setSearchResults(self, results):
        """Met à jour les résultats de la recherche et notifie le modèle."""
        logger.debug(f"SearchResultsProxyModel.setSearchResults called with {len(results)} results.")
        self.beginResetModel()
        if not results:
            logger.warning("setSearchResults: Empty results list received.")
            self._search_results.clear()
            self._path_to_row.clear()
        else:
            self._search_results = [os.path.normcase(os.path.normpath(r)) for r in results]
            # Reconstruction de _path_to_row pour garantir la synchronisation avec le modèle source
            self._path_to_row = {path: idx for idx, path in enumerate(self._search_results)}
        self.endResetModel()
        logger.debug("setSearchResults: Model reset completed.")

    def clearSearchResults(self):
        """Nettoie les résultats de recherche et réinitialise le modèle."""
        logger.debug("SearchResultsProxyModel.clearSearchResults called.")
        self.setSearchResults([])  # Réutiliser setSearchResults pour éviter la duplication
        logger.debug("clearSearchResults: Model reset completed.")

    def rowCount(self, parent=QModelIndex()):
        """Retourne le nombre de résultats de recherche."""
        if parent.isValid():
            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):
            file_path = self._search_results[row]
            return self.createIndex(row, column, file_path)
        return QModelIndex()

    def parent(self, index):
        """Retourne l'index parent, toujours invalide pour ce modèle."""
        return QModelIndex()

    def mapToSource(self, proxyIndex):
        """Retourne l'index source correspondant à l'index du proxy."""
        logger.debug(f"SearchResultsProxyModel.mapToSource called with proxyIndex: {proxyIndex}")

        if not proxyIndex.isValid():
            logger.warning("mapToSource: Invalid proxyIndex received.")
            return QModelIndex()

        file_path = proxyIndex.internalPointer()
        if not isinstance(file_path, str):
            logger.warning("mapToSource: Internal pointer is not a valid file path string.")
            return QModelIndex()

        source_index = self.sourceModel().indexFromPath(file_path)
        if not source_index.isValid():
            logger.warning(f"mapToSource: Invalid source index for path {file_path}.")
            return QModelIndex()

        return source_index

    def mapFromSource(self, sourceIndex):
        """Retourne l'index du proxy correspondant à l'index source."""
        logger.debug(f"SearchResultsProxyModel.mapFromSource called with sourceIndex: {sourceIndex}")

        if not sourceIndex.isValid():
            logger.warning("mapFromSource: Invalid sourceIndex received.")
            return QModelIndex()

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

        row = self._path_to_row.get(file_path)
        if row is not None:
            return self.index(row, sourceIndex.column())
        else:
            logger.warning(f"mapFromSource: Source index for path {file_path} not found in search results.")
            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)
        if not source_index.isValid():
            logger.debug("Invalid source index in data method.")
            return None

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

    def flags(self, index):
        if not index.isValid():
            logger.debug("flags: Invalid index")
            return Qt.NoItemFlags
        source_index = self.mapToSource(index)
        if source_index.isValid():
            return self.sourceModel().flags(source_index)
        return Qt.NoItemFlags
    
    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("Start SearchResultsProxyModel.sourceDataChanged")
        if not self._search_results:  # Vérifie si le modèle proxy est actif
            logger.debug("SearchResultsProxyModel.sourceDataChanged: Skipping dataChanged emission, model is not active.")
            return

        # Mapper les index source vers les index du proxy
        proxyTopLeft = self.mapFromSource(sourceTopLeft)
        proxyBottomRight = self.mapFromSource(sourceBottomRight)

        if proxyTopLeft.isValid() and proxyBottomRight.isValid():
            # Émettre le signal dataChanged du proxy avec les rôles appropriés
            self.dataChanged.emit(proxyTopLeft, proxyBottomRight, roles)
        else:
            logger.debug("SearchResultsProxyModel.sourceDataChanged: Skipping dataChanged emission, invalid mapping.")
    
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
    def generate_dxf_thumbnail(self, dxf_file):
        """Génère une miniature à partir d'un fichier DXF."""
        #logger.debug(f"ThumbnailWorker: generate_dxf_thumbnail Début de la génération de la miniature DXF pour {self.file_path}")
        painter = None  # Initialisation
        try:
            # Vérification d'annulation avant de commencer le traitement
            if self._cancel_requested:
                logger.warning(f"ThumbnailWorker: generate_dxf_thumbnail - Annulé pour {self.file_path}")
                return None
            doc = ezdxf.readfile(dxf_file)
            msp = doc.modelspace()
            # Log the entity types
            #entity_types = set(entity.dxftype() for entity in msp)
            #logger.info(f"Entity types in DXF file: {entity_types}")
            width_thumbnail, height_thumbnail = 120, 160
            thumbnail = QPixmap(width_thumbnail, height_thumbnail)
            thumbnail.fill(Qt.white)
            painter = QPainter(thumbnail)
            painter.setRenderHint(QPainter.Antialiasing)
            bounding_rect = self.calculate_bounding_rect(msp)
            #logger.debug(f"Bounding rect: {bounding_rect}")
            
            scale_factor, offset_x, offset_y = self.calculate_scale_factor_and_offset(bounding_rect, width_thumbnail, height_thumbnail)
            #logger.debug(f"Scale factor: {scale_factor}, Offset X: {offset_x}, Offset Y: {offset_y}")
            # Vérification d'annulation pendant le traitement
            if self._cancel_requested:
                logger.warning(f"ThumbnailWorker: generate_dxf_thumbnail - Annulé pendant le traitement de {self.file_path}")
                return None
            for ent in msp:
                if ent.dxftype() == 'LINE':
                    self.add_line(painter, ent.dxf.start, ent.dxf.end, scale_factor, offset_x, offset_y)
                elif ent.dxftype() == 'POINT':
                    # Handle points if needed
                    pass
                elif ent.dxftype() == 'CIRCLE':
                    self.add_circle(painter, ent.dxf.center, ent.dxf.radius, scale_factor, offset_x, offset_y)
                elif ent.dxftype() == 'ARC':
                    self.add_arc(painter, ent.dxf.center, ent.dxf.radius, ent.dxf.start_angle, ent.dxf.end_angle, scale_factor, offset_x, offset_y)
                elif ent.dxftype() == 'SPLINE':
                    self.add_spline(painter, ent, scale_factor, offset_x, offset_y)
                elif ent.dxftype() == 'ELLIPSE':
                    self.add_ellipse(painter, ent, scale_factor, offset_x, offset_y)
                elif ent.dxftype() == 'TEXT':
                    self.add_text(painter, ent, scale_factor, offset_x, offset_y)
                elif ent.dxftype() == 'LWPOLYLINE':
                    self.add_wpolyline(painter, ent, scale_factor, offset_x, offset_y)
        
            painter.end()
            thumbnail = thumbnail.transformed(QTransform().rotate(-90))
            final_thumbnail = thumbnail.scaled(QSize(160, 120), Qt.KeepAspectRatio, Qt.SmoothTransformation)
            #logger.debug(f"ThumbnailWorker: Fin de la génération de la miniature DXF pour {self.file_path}")
            return final_thumbnail
            #return thumbnail
        except (ezdxf.DXFError, RecursionError) as e:
            # Gérer les erreurs spécifiques au format DXF et les erreurs de récursion
            logger.error(f"Erreur DXF ou RecursionError: {e}")
        except Exception as e: # Catch-all pour les autres exceptions
            logger.error(f"Unexpected error: {type(e).__name__} - {e}", exc_info=True)  # Logging avec traceback complet
        finally: 
            if painter and painter.isActive():
                painter.end()  # Ensure QPainter is ended in case of error
        return QPixmap()
    
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.search_results_proxy_model.modelReset.connect(self.on_proxy_model_reset)

        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 on_proxy_model_reset(self):
        """Slot appelé lorsque le modèle proxy est réinitialisé."""
        logger.debug("Réinitialisation du modèle proxy détectée. Effacement de la sélection et rafraîchissement de la vue.")
        self.file_list.clearSelection()
        self.file_list.reset()

    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.")
            # Bloquer les signaux pendant la mise à jour du modèle

            self.file_list.blockSignals(True)
            self.search_results_proxy_model.setSearchResults(results) # Mettre à jour le proxy
            self.file_list.setModel(self.search_results_proxy_model)
            self.file_list.blockSignals(False)
            logger.debug("SearchResultsProxyModel set as the current model for file_list.")

            # Effacer la sélection et réinitialiser la vue
            self.file_list.clearSelection()
            self.file_list.reset() # Rafraîchir la vue
            self.file_list.update()
            #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:
                QTimer.singleShot(100, lambda: self.select_first_result(results))
            logger.debug("Logging search results.")
            self.logSearchResults(results)

        self.display_results_called = False
        logger.debug("End of displayResults function.")
    def select_first_result(self, results):
        first_result_index = self.search_results_proxy_model.index(0, 0)
        if first_result_index.isValid():
            self.file_list.setCurrentIndex(first_result_index)
    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):
        """Gère la fin d'une recherche, en affichant les résultats ou en gérant les erreurs."""
        logger.debug("Function handle_search_completion start")
        if success:
            logger.info("Search completed successfully.")
            self.displayResults(success, results)
            # Changer le modèle de la vue en fonction du mode de recherche
            if self._in_search_mode:  
                self.file_list.setModel(self.search_results_proxy_model)
                self.file_list.setRootIndex(QModelIndex())  # Set root index to invalid index
                logger.debug("Set root index to QModelIndex() in search mode.")
            else:
                # Revenir au proxy normal si on n'est plus en recherche
                self.file_list.setModel(self.file_list_proxy) 
                root_index = self.file_list_model.indexFromPath(self.current_folder)
                if root_index.isValid():
                    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("Invalid proxy root index after mapping.")
                        self.file_list.setRootIndex(QModelIndex())
                else:
                    logger.error("Invalid root index for current folder.")
                    self.file_list.setRootIndex(QModelIndex())
        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
        # Déconnecter le SearchResultsProxyModel de la vue
        if self.file_list.model() == self.search_results_proxy_model:
            self.file_list.setModel(self.file_list_proxy)
            logger.debug("SearchResultsProxyModel disconnected from file_list view.")
        self.search_results_proxy_model.clearSearchResults()
        # Restaurer le modèle principal
        self.file_list.setModel(self.file_list_proxy)
        logger.debug("File_list view set to FileListProxyModel.")
        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:
            if self._in_search_mode:
               logger.debug("In search mode, skipping update_file_list_view.")
               return  # Ne pas mettre à jour la vue en mode recherche
            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  
            # Bloquer les signaux pendant le changement de modèle
            self.file_list.blockSignals(True)
            if self._in_search_mode:
                # Mode recherche : le modèle est déjà SearchResultsProxyModel
                if self.file_list.model() != self.search_results_proxy_model:
                    self.file_list.setModel(self.search_results_proxy_model)
                    logger.debug("Switching to SearchResultsProxyModel in update_file_list_view.")
            else:
                # Mode normal
                if self.file_list.model() != self.file_list_proxy:
                    self.file_list.setModel(self.file_list_proxy)
                    logger.debug("Switching to FileListProxyModel in update_file_list_view.")
            self.file_list.blockSignals(False)

            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."""
        logger.info("Start of FileViewer.update_view_index function")
        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)
        logger.info("end of FileViewer.update_view_index function")
    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  

    @pyqtSlot(QModelIndex)
    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()}") 
        if QThread.currentThread() != QApplication.instance().thread():
            logger.debug("display_selected_file appelé depuis un thread différent, ré-invoquant dans le thread principal.")
            QMetaObject.invokeMethod(self, "display_selected_file", Qt.QueuedConnection, Q_ARG(QModelIndex, index))
            return
        if not index.isValid():
            logger.error("display_selected_file: Invalid index received.")
            return
        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
            if not index.isValid():
                logger.error("display_selected_file: Index invalide reçu.")
                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 source index after mapping from proxy")
                return
            
            # Récupérer le chemin du fichier
            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")

    def display_image(self, image_path):
        logger.info(f"Start display_image")
        pixmap = QPixmap(image_path)
        self.preview_scene.clear()
        item = QGraphicsPixmapItem(pixmap)
        item.setTransform(QTransform()) 
        self.preview_scene.addItem(item)
        rectF = QRectF(pixmap.rect())
        self.preview_scene.setSceneRect(rectF)
        self.preview_view.setTransform(QTransform())
        self.preview_view.fitInView(rectF, Qt.KeepAspectRatio)
        self.preview_view.viewport().update()
        #self.preview_view.update() 
        logger.info(f"Image displayed in preview: {image_path}")
        logger.info(f"End display_image")

    def display_dxf(self, file):
        logger.debug("Starting function display_dxf")
        try:
            self.preview_scene.clear()
            doc = ezdxf.readfile(file)
            msp = doc.modelspace()
            window_size = self.preview_view.size()
            scale_factor = self.calculate_scale_factor(msp, window_size)
            self.scale_factor = scale_factor  # Stocker le facteur d'échelle
            entities_to_add = []  # Use a list to batch add if possible
            for ent in msp:
                if ent.dxftype() == 'LINE':
                    entities_to_add.append(self.add_line(ent, scale_factor))
                elif ent.dxftype() == 'CIRCLE':
                    entities_to_add.append(self.add_circle(ent, scale_factor))
                elif ent.dxftype() == 'SPLINE':
                    entities_to_add.append(self.add_spline(ent, scale_factor))
                elif ent.dxftype() == 'POINT':
                    entities_to_add.append(self.add_point(ent, scale_factor))
                elif ent.dxftype() == 'ARC':
                    entities_to_add.append(self.add_arc(ent, scale_factor))
                elif ent.dxftype() == 'ELLIPSE':
                    entities_to_add.append(self.add_ellipse(ent, scale_factor))
                elif ent.dxftype() == 'TEXT':
                    entities_to_add.append(self.add_text(ent, scale_factor)) 
                #elif ent.dxftype() == 'MTEXT':
                    #entities_to_add.append(self.add_mtext(ent, scale_factor))
                elif ent.dxftype() == '3DFACE':
                    entities_to_add.append(self.add_3dface(ent, scale_factor))
                elif ent.dxftype() == 'SOLID':
                    entities_to_add.append(self.add_solid(ent, scale_factor))
                elif ent.dxftype() == 'POLYLINE':
                    entities_to_add.extend(self.add_polyline(ent, scale_factor))
                elif ent.dxftype() == 'INSERT':
                    entities_to_add.extend(self.add_insertion(ent, doc.blocks, scale_factor))
                elif ent.dxftype() == 'LWPOLYLINE':
                    entities_to_add.append(self.add_lwpolyline(ent, scale_factor))
            
            # Adjust the view to fit the scene
            self.preview_view.setTransform(QtGui.QTransform().scale(1, -1).rotate(0))
            self.preview_scene.setSceneRect(self.preview_scene.itemsBoundingRect())
            self.preview_view.fitInView(self.preview_scene.sceneRect(), Qt.KeepAspectRatio)
            #self.preview_view.resetCachedContent()
            self.preview_view.viewport().update()
            #self.preview_view.update()
            logger.debug("Successfully displayed DXF.")
        except ezdxf.DXFError as dxf_error:
            logger.error(f"DXF Error: {dxf_error}")
            self.preview_scene.clear()
            self.preview_scene.addText("Error reading DXF file")
            #self.preview_scene.addText("Error reading DXF file")
        except Exception as e:
            logger.error(f"Unexpected Error: {type(e).__name__} - {e}")
            self.preview_scene.clear()
            self.preview_scene.addText("Unexpected error while displaying DXF file")
            #self.preview_scene.addText("Unexpected error while displaying DXF file")