analyse le log  et Assure  une Gestion Cohérente des Modèles et des Proxy Models
Le chemin INDEX_DIR est valide.
Le chemin WATCHED_DIR est valide.
2024-10-23 15:41:55,225 - DEBUG - Schema defined with fields: ['creation_time', 'ext', 'modification_time', 'name', 'path', 'size']
2024-10-23 15:41:55,225 - DEBUG - Initializing FileIndexer with index_dir: C:\index_dir, rootPath: Y:\DocSoufiane
2024-10-23 15:41:55,226 - DEBUG - Beginning of __init__ function in CustomFileSystemModelForList
2024-10-23 15:41:55,242 - DEBUG - ThumbnailCache: Initialisé avec une taille maximale de 5000 vignettes.
2024-10-23 15:41:55,250 - DEBUG - Successfully connected thumbnailReady signal to on_thumbnail_ready slot.
2024-10-23 15:41:55,251 - DEBUG - End of __init__ function in CustomFileSystemModelForList
Le chemin . est valide.
2024-10-23 15:41:55,450 - DEBUG - Beginning of __init__ function in CustomFileSystemModelForList
2024-10-23 15:41:55,454 - DEBUG - ThumbnailCache: Initialisé avec une taille maximale de 5000 vignettes.
2024-10-23 15:41:55,454 - DEBUG - Successfully connected thumbnailReady signal to on_thumbnail_ready slot.
2024-10-23 15:41:55,455 - DEBUG - End of __init__ function in CustomFileSystemModelForList
2024-10-23 15:41:55,455 - DEBUG - Start of setup_tree_dock_widget function
2024-10-23 15:41:55,456 - INFO - Connection: tree_view.clicked -> on_tree_clicked
2024-10-23 15:41:55,456 - INFO - Tree root path:
2024-10-23 15:41:55,456 - DEBUG - Starting setup of the proxy model for the tree/Function setup_proxy_tree_model
2024-10-23 15:41:55,457 - INFO - Source model assigned to the tree proxy.
2024-10-23 15:41:55,457 - INFO - Filter of the tree proxy model invalidated to reflect the changes.
2024-10-23 15:41:55,457 - DEBUG - End of the setup of the proxy model for the tree/Function setup_proxy_tree_model
2024-10-23 15:41:55,459 - DEBUG - End of setup_tree_dock_widget function
2024-10-23 15:41:55,459 - DEBUG - Starting setup of the proxy model for the tree/Function setup_proxy_tree_model
2024-10-23 15:41:55,459 - DEBUG - Proxy model for the tree is already set up.
2024-10-23 15:41:55,459 - DEBUG - SearchResultsProxyModel: setSourceModel called
2024-10-23 15:41:55,459 - DEBUG - Starting setup of the proxy model for the file list./Function setup_file_list_proxy
2024-10-23 15:41:55,460 - INFO - Source model assigned to the file list proxy.
2024-10-23 15:41:55,460 - INFO - Filters applied for the file list proxy model: *.dxf *.jpg *.jpeg *.png *.bmp
2024-10-23 15:41:55,460 - INFO - Filter of the file list proxy model invalidated to reflect the changes.
2024-10-23 15:41:55,460 - DEBUG - End of the setup of the proxy model for the file list./Function setup_file_list_proxy
2024-10-23 15:41:55,460 - DEBUG - Starting the setup_ui function in FileViewer
2024-10-23 15:41:55,461 - DEBUG - Starting the setupTreeModelAndView function
2024-10-23 15:41:55,461 - DEBUG - End of the setupTreeModelAndView function
2024-10-23 15:41:55,461 - DEBUG - Starting the setupListModelAndView function
2024-10-23 15:41:55,467 - INFO - Initial root path for the file list model: (empty)
2024-10-23 15:41:55,468 - INFO - Proxy model configured with the file list model.
2024-10-23 15:41:55,468 - INFO - File list view configured with the proxy model.
2024-10-23 15:41:55,468 - INFO - File list view configured to display empty content.
2024-10-23 15:41:55,468 - INFO - Triggered showModelInfo:
2024-10-23 15:41:55,468 - DEBUG - End of the setupListModelAndView function
2024-10-23 15:41:55,468 - DEBUG - Start of setup_central_frame function
2024-10-23 15:41:55,471 - INFO - New icon size for QListView: PyQt5.QtCore.QSize(160, 120)
2024-10-23 15:41:55,471 - DEBUG - End of setup_central_frame function
2024-10-23 15:41:55,472 - DEBUG - Start of setup_preview_dock_widget function
2024-10-23 15:41:55,474 - DEBUG - End of setup_preview_dock_widget function
2024-10-23 15:41:55,474 - INFO - Connecting signals and slots:
2024-10-23 15:41:55,474 - DEBUG - [Init] Directory label initialized
2024-10-23 15:41:55,475 - DEBUG - [Layout] Directory label added to layout
2024-10-23 15:41:55,475 - DEBUG - End of the setup_ui function in FileViewer
2024-10-23 15:41:55,475 - INFO - Start setupMenu
2024-10-23 15:41:55,475 - DEBUG - Setting up the menu bar
2024-10-23 15:41:55,475 - DEBUG - Menu bar configured with File and View menus.
2024-10-23 15:41:55,475 - INFO - End setupMenu
2024-10-23 15:41:55,475 - INFO - Start setupToolbar
2024-10-23 15:41:55,476 - DEBUG - Setting up the toolbar
2024-10-23 15:41:55,476 - DEBUG - Toolbar configured
2024-10-23 15:41:55,476 - INFO - End setupToolbar
2024-10-23 15:41:55,476 - DEBUG - SearchWorker initialized with index_dir: C:\index_dir
2024-10-23 15:41:55,478 - INFO - Function update_directory_label start
2024-10-23 15:41:55,478 - DEBUG - Attempting to update directory label with text: Current folder path:
2024-10-23 15:41:55,478 - INFO - Updated directory label: Current folder path:
2024-10-23 15:41:55,478 - INFO - Function update_directory_label end
2024-10-23 15:41:55,478 - INFO - Connection: file_list.clicked -> display_selected_file
2024-10-23 15:41:55,478 - INFO - Connection: file_list.doubleClicked -> on_folder_double_clicked
2024-10-23 15:41:55,478 - DEBUG - SearchResultsProxyModel: setSourceModel called
2024-10-23 15:41:55,534 - INFO - Start of resizeEvent in CustomGraphicsView class
2024-10-23 15:41:55,534 - DEBUG - Resize event detected.
2024-10-23 15:41:55,534 - DEBUG - Initial view adjusted to show entire scene.
2024-10-23 15:41:55,534 - DEBUG - Signal connected to adjust view dynamically.
2024-10-23 15:41:55,534 - INFO - End of resizeEvent in CustomGraphicsView class
2024-10-23 15:41:55,534 - INFO - Start of showEvent in CustomGraphicsView class
2024-10-23 15:41:55,534 - DEBUG - Show event detected.
2024-10-23 15:41:55,535 - INFO - End of showEvent in CustomGraphicsView class
2024-10-23 15:42:15,993 - DEBUG - Start of on_tree_clicked
2024-10-23 15:42:15,993 - DEBUG - Start processing index: <PyQt5.QtCore.QModelIndex object at 0x00000276C54A1A90>
2024-10-23 15:42:15,994 - DEBUG - Directory path: Y:/Divers fichiers Dxf À TRIER/ARC
2024-10-23 15:42:15,994 - INFO - Updating file list model for directory: Y:/Divers fichiers Dxf À TRIER/ARC
2024-10-23 15:42:15,997 - INFO - Function update_directory_label start
2024-10-23 15:42:15,997 - DEBUG - Attempting to update directory label with text: Current folder path: Y:/Divers fichiers Dxf À TRIER/ARC
2024-10-23 15:42:15,997 - INFO - Updated directory label: Current folder path: Y:/Divers fichiers Dxf À TRIER/ARC
2024-10-23 15:42:15,998 - INFO - Function update_directory_label end
2024-10-23 15:42:15,998 - INFO - [on_tree_clicked] Current folder path updated to: Y:/Divers fichiers Dxf À TRIER/ARC
2024-10-23 15:42:15,998 - DEBUG - End of on_tree_clicked
2024-10-23 15:42:16,036 - DEBUG - ThumbnailCache: __getitem__ appelé pour Y:/Divers fichiers Dxf À TRIER/ARC/REPARE LUM 4.5-6.5PO.DXF
2024-10-23 15:42:16,036 - DEBUG - Thumbnail retrieved from cache for: Y:/Divers fichiers Dxf À TRIER/ARC/REPARE LUM 4.5-6.5PO.DXF
2024-10-23 15:42:16,037 - DEBUG - FileListProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000276C54A1B70>
2024-10-23 15:42:16,037 - WARNING - FileListProxyModel.mapFromSource reçu un sourceIndex invalide.
2024-10-23 15:42:16,037 - DEBUG - FileListProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000276C54A29E0>
2024-10-23 15:42:16,037 - WARNING - FileListProxyModel.mapFromSource reçu un sourceIndex invalide.
2024-10-23 15:42:16,037 - DEBUG - SearchResultsProxyModel: Skipping dataChanged emission, model is not active.
2024-10-23 15:42:16,037 - DEBUG - Fonction update_file_list_view: Début
2024-10-23 15:42:16,037 - ERROR - Le chemin du dossier doit être une chaîne de caractères valide.
2024-10-23 15:42:16,038 - DEBUG - ThumbnailCache: __getitem__ appelé pour Y:/Divers fichiers Dxf À TRIER/ARC/REPARE LUM 4.5-6.5PO.DXF
2024-10-23 15:42:16,038 - DEBUG - Thumbnail retrieved from cache for: Y:/Divers fichiers Dxf À TRIER/ARC/REPARE LUM 4.5-6.5PO.DXF
2024-10-23 15:42:16,039 - DEBUG - FileListProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000276C54A2B30>
2024-10-23 15:42:16,039 - WARNING - FileListProxyModel.mapFromSource reçu un sourceIndex invalide.
2024-10-23 15:42:16,039 - DEBUG - FileListProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000276C54A29E0>
2024-10-23 15:42:16,039 - WARNING - FileListProxyModel.mapFromSource reçu un sourceIndex invalide.
2024-10-23 15:42:16,039 - DEBUG - SearchResultsProxyModel: Skipping dataChanged emission, model is not active.
2024-10-23 15:42:16,039 - DEBUG - Fonction update_file_list_view: Début
2024-10-23 15:42:16,040 - ERROR - Le chemin du dossier doit être une chaîne de caractères valide.
2024-10-23 15:42:26,917 - DEBUG - Slot activated: launch_delayed_search
2024-10-23 15:42:26,918 - INFO - Search criteria modified. Waiting before launching search...
2024-10-23 15:42:26,918 - DEBUG - End of launch_delayed_search
2024-10-23 15:42:27,919 - DEBUG - Function launchSearch start
2024-10-23 15:42:27,919 - DEBUG - Search criteria: r, criteria_type: filename
2024-10-23 15:42:27,919 - DEBUG - Current root path: Y:/Divers fichiers Dxf À TRIER/ARC
2024-10-23 15:42:27,919 - DEBUG - WATCHED_DIR: Y:\DocSoufiane
2024-10-23 15:42:27,919 - DEBUG - Search criteria set: r (type: filename)
2024-10-23 15:42:27,919 - DEBUG - Date range set: 2024-10-23 - 2024-10-23
2024-10-23 15:42:27,919 - DEBUG - Base path for search set: Y:\Divers fichiers Dxf À TRIER\ARC
2024-10-23 15:42:27,920 - DEBUG - Function launchSearch end
2024-10-23 15:42:27,920 - INFO - Starting search.
2024-10-23 15:42:27,920 - DEBUG - Root path for search: Y:\Divers fichiers Dxf À TRIER\ARC
2024-10-23 15:42:27,921 - INFO - Using file system search.
2024-10-23 15:42:27,924 - INFO - Finalizing search.
2024-10-23 15:42:27,924 - INFO - Search completed with 1 results.
2024-10-23 15:42:27,924 - DEBUG - Search finished with success: True
2024-10-23 15:42:27,926 - DEBUG - Stopping search thread.
2024-10-23 15:42:27,926 - DEBUG - Search thread stopped.
2024-10-23 15:42:27,927 - DEBUG - Search thread stopped and resources cleaned up.
2024-10-23 15:42:27,927 - DEBUG - Function handle_search_completion start
2024-10-23 15:42:27,927 - INFO - Search completed successfully.
2024-10-23 15:42:27,927 - DEBUG - Beginning of displayResults function.
2024-10-23 15:42:27,927 - DEBUG - Updating proxy model with search results.
2024-10-23 15:42:27,927 - DEBUG - SearchResultsProxyModel.setSearchResults called with 1 results.
2024-10-23 15:42:27,927 - DEBUG - setSearchResults: Model reset completed.
2024-10-23 15:42:27,927 - DEBUG - SearchResultsProxyModel set as the current model for file_list.
2024-10-23 15:42:27,932 - DEBUG - FileListProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000276C54A29E0>
2024-10-23 15:42:27,934 - DEBUG - FileListProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000276C54A1B70>
2024-10-23 15:42:27,935 - DEBUG - Logging search results.
2024-10-23 15:42:27,935 - DEBUG - Beginning of logSearchResults function.
2024-10-23 15:42:27,935 - DEBUG - Search result: y:\divers fichiers dxf à trier\arc\REPARE LUM 4.5-6.5PO.DXF
2024-10-23 15:42:27,935 - DEBUG - End of logSearchResults function.
2024-10-23 15:42:27,935 - DEBUG - End of displayResults function.
2024-10-23 15:42:27,935 - DEBUG - Function handle_search_completion end
2024-10-23 15:42:27,936 - DEBUG - End of onSearchFinished function.
2024-10-23 15:42:27,951 - DEBUG - ThumbnailCache: __getitem__ appelé pour Y:/divers fichiers dxf à trier/ARC/repare lum 4.5-6.5po.dxf
2024-10-23 15:42:27,951 - DEBUG - Thumbnail retrieved from cache for: Y:/divers fichiers dxf à trier/ARC/repare lum 4.5-6.5po.dxf
2024-10-23 15:42:27,952 - DEBUG - FileListProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000276C8EB01A0>
2024-10-23 15:42:27,952 - DEBUG - FileListProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000276C8EB0130>
2024-10-23 15:42:27,953 - DEBUG - ThumbnailCache: __getitem__ appelé pour Y:/divers fichiers dxf à trier/ARC/repare lum 4.5-6.5po.dxf
2024-10-23 15:42:27,953 - DEBUG - Thumbnail retrieved from cache for: Y:/divers fichiers dxf à trier/ARC/repare lum 4.5-6.5po.dxf
2024-10-23 15:42:27,953 - DEBUG - FileListProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000276C8EB0210>
2024-10-23 15:42:27,953 - DEBUG - FileListProxyModel.mapFromSource appelé avec sourceIndex: <PyQt5.QtCore.QModelIndex object at 0x00000276C8EB0130>
import logging, os, threading, time, sys, cProfile, pstats, io, re, math   
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, QTreeView, QFileSystemModel, QListView, QDockWidget, QComboBox, QLineEdit, QDateEdit, QGraphicsView,
    QGraphicsScene, QGraphicsPixmapItem, QSplitter, QLabel, QFileDialog, QGraphicsPathItem, QGraphicsItemGroup, QGraphicsTextItem, QGraphicsLineItem, QGraphicsEllipseItem, QToolBar, QAction,QToolButton, QGraphicsPolygonItem,
      QMenu,QMessageBox, QHeaderView, QGraphicsItem, QGraphicsRectItem,  )
from PyQt5 import QtGui
from PyQt5.QtGui import QVector2D, QPixmap, QImageReader, QImage, QPainter, QIcon, QPainterPath, QTransform, QPolygonF, QPen, QBrush, QColor, QFont, QFontMetrics 
from unittest.mock import MagicMock
from PyQt5.QtCore import (
    QDir, Qt, QSize, QRectF, pyqtSignal, QLineF, QTimer, QPointF, QPoint, QModelIndex, QSortFilterProxyModel, QAbstractProxyModel,QItemSelectionModel, QRegularExpression, QAbstractListModel, QFileInfo,QObject,
    QThread, QDate, QMetaObject, QSemaphore, QThreadPool, QRunnable, QFileSystemWatcher,Q_ARG, pyqtSlot, QEvent, QMutex, QMutexLocker,QRecursiveMutex, QVariant)
from PyQt5.QtChart import QChart, QSplineSeries, QChartView
from PIL import Image, ImageQt
import ezdxf
import ezdxf.query
from ezdxf.entities import DXFEntity 
from math import sin, cos, radians
from uuid import uuid4
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from collections import OrderedDict, deque
import matplotlib.pyplot as plt
from enum import Enum
import whoosh
from whoosh.index import create_in, open_dir, exists_in, EmptyIndexError
from whoosh.fields import Schema, TEXT, ID, DATETIME, STORED
from whoosh.qparser import QueryParser
from whoosh.query import Term, Or, DateRange, And, Every, FuzzyTerm, Prefix
from whoosh.writing import AsyncWriter  
from datetime import datetime
import win32api,win32con
import shutil 

# 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é
    
class CustomFileSystemModelForList(QFileSystemModel):
    """Modèle de système de fichiers personnalisé pour afficher les vignettes dans une vue de liste."""
    searchFinished = pyqtSignal(list) # results: List[str]
    rootPathChanged = pyqtSignal(str) # new_root_path: str
    #dataChanged = pyqtSignal(QModelIndex, QModelIndex) # index: QModelIndex 
    modelUpdated = pyqtSignal(QModelIndex)  # index: QModelIndex # Signal to notify that the model has been updated
    directoryLoaded = pyqtSignal(str) # path: str
    #thumbnailUpdated = pyqtSignal(str, QPixmap, bool) # file_path: str, thumbnail: QPixmap, success: bool
    thumbnailRequested = pyqtSignal(str)  #

    def __init__(self, parent=None, indexer=None, max_workers=10, cache_size=5000): 
        logger.debug("Beginning of __init__ function in CustomFileSystemModelForList")
        super().__init__(parent)
        self.allowed_extensions = ALLOWED_EXTENSIONS
        self.setRootPath(os.path.normpath(WATCHED_DIR))  
        # Connexion des signaux pour le débogage
        #self.searchFinished.connect(lambda results: print(f"Search finished signal received with results: {results}"))
        #self.rootPathChanged.connect(lambda path: print(f"Root path changed signal received with path: {path}"))
        #self.dataChanged.connect(lambda top_left, bottom_right: print(f"Data changed signal received for indexes: {top_left}, {bottom_right}"))
        #self.rootPathChanged.connect(lambda path: logger.debug(f"Root path changed: {path}"))
        #self.modelUpdated.connect(lambda index: print(f"Model updated signal received for index: {index}"))

        self.thumbnailManager = ThumbnailManager(parent=self, max_workers=max_workers, cache_size=cache_size)
        # Connexion du signal thumbnailReady au slot correspondant de ThumbnailManager
        self.thumbnailManager.thumbnailReady.connect(self.on_thumbnail_ready)
        # Connexion explicite du signal thumbnailRequested
        self.thumbnailRequested.connect(self.thumbnailManager.start_worker)
        # Connexion du signal requestProcessed au slot correspondant de FileViewer
        #self.processing_thread.requestProcessed.connect(self.handle_request_processed)

        self.parent = parent
        self.file_list = QListView()
        self.file_list.setStyleSheet("QListView::item { margin: 5px; }")
        self.criteria = ""    
        self._search_timer = QTimer(self)
        self._search_timer.setSingleShot(True)
        self._last_criteria = ""
        self.indexer = indexer
        if self.indexer:
            # Connect the indexUpdated signal with an anonymous lambda function to provide rootPath
            self.indexer.indexUpdated.connect(lambda: QTimer.singleShot(1000,  # Délai de 1 seconde
                                                                self.add_files_to_watcher))
        # Cache resources
        script_directory = os.path.dirname(os.path.abspath(__file__))
        icon_path = os.path.join(script_directory, "dossier.png")
        self.folder_icon = QIcon(QPixmap(icon_path))
        if self.folder_icon.isNull():
            logger.error(f"Failed to load folder icon from {icon_path}")

        #self.thumbnailManager = ThumbnailManager(parent=self, max_workers=max_workers, cache_size=cache_size) # Créer une instance de ThumbnailManager
        self.thumbnailManager.thumbnailReady.connect(self.on_thumbnail_ready)
        logger.debug("Successfully connected thumbnailReady signal to on_thumbnail_ready slot.")
        self.file_watcher = QFileSystemWatcher(self)  # Initialize file_watcher
        self.directoryLoaded.connect(self.on_directory_loaded)
        #self.file_watcher.fileChanged.connect(self.handle_file_change) # Connect to the signal
        logger.debug("End of __init__ function in CustomFileSystemModelForList")

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

    def rowCount(self, parent=QModelIndex()):
        #logger.debug(f"rowCount called with parent: {parent}")
        return super().rowCount(parent)

    def index(self, *args, **kwargs):  
        #logger.debug(f"index called with row: {row}, column: {column}, parent: {parent}")
        try:
            #result = super().index(row, column, parent)
            #logger.debug(f"CustomFileSystemModelForList.index: Returning index: {result}")
            if len(args) == 1 and isinstance(args[0], str):
                # Appel avec path  
                path = args[0]  
                column = kwargs.get('column', 0)  
                #logger.debug(f"index called with path: {path}, column: {column}")  
                return super().index(path, column)  
                #return super().index(row, column, parent)
            elif len(args) == 2 and isinstance(args[0], str) and isinstance(args[1], int):
                # Appel avec path et column  
                path = args[0]  
                column = args[1]  
                #logger.debug(f"index called with path: {path}, column: {column}")  
                return super().index(path, column)  
            else:
                # Appel avec row, column, parent  
                return super().index(*args, **kwargs)  

        except Exception as e:
            logger.error(f"CustomFileSystemModelForList.index: An error occurred: {e}")
            raise  # Remonte l'exception pour une gestion plus haut dans le code si nécessaire
    def indexFromPath(self, path):
        """Helper function to get the index from a path."""
        #logger.debug(f"Entering CustomFileSystemModelForList.indexFromPath with path: {path}")
        result = super().index(path)
        #logger.debug(f"CustomFileSystemModelForList.indexFromPath: Returning index: {result}")
        return result
    def add_files_to_watcher(self):
        """
        Ajoute les fichiers du répertoire surveillé (WATCHED_DIR) et de ses sous-répertoires 
        au `QFileSystemWatcher` pour surveiller les modifications.
        """
        logger.debug(f"Entering CustomFileSystemModelForList.add_files_to_watcher")
        try:
            if not self.file_watcher:
                logger.warning("file_watcher is not initialized.")
                return

            # Assurez-vous que le répertoire surveillé est correctement normalisé
            normalized_watched_dir = os.path.normpath(WATCHED_DIR)

            # Utiliser QDir pour lister les fichiers dans le répertoire surveillé
            dir = QDir(normalized_watched_dir)
            dir.setNameFilters([f"*{ext}" for ext in self.allowed_extensions])
            dir.setFilter(QDir.Files | QDir.NoSymLinks | QDir.NoDotAndDotDot)

            # Obtenir tous les fichiers correspondants dans le répertoire et ses sous-répertoires
            file_info_list = dir.entryInfoList(QDir.AllEntries | QDir.NoDotAndDotDot, QDir.Name)

            allowed_file_paths = []
            for file_info in file_info_list:
                file_path_str = file_info.absoluteFilePath()
                #logger.debug(f"Checking file path: {file_path_str}")

                # Vérifiez si le fichier est caché ou système
                if file_info.isHidden() or (win32api.GetFileAttributes(file_path_str) & win32con.FILE_ATTRIBUTE_SYSTEM):
                    logger.debug(f"  - Skipped: Is hidden or system file")
                    continue

                # Vérifier l'extension du fichier
                extension = os.path.splitext(file_path_str)[1].lower()
                if extension not in self.allowed_extensions:
                    logger.debug(f"  - Skipped: Extension {extension} not allowed")
                    continue

                allowed_file_paths.append(file_path_str)

            if not allowed_file_paths:
                logger.warning("No valid file paths found to add to QFileSystemWatcher.")
                logger.warning(f"  - Root path: {WATCHED_DIR}")
                logger.warning(f"  - Allowed extensions: {self.allowed_extensions}")
            else:
                self.file_watcher.addPaths(allowed_file_paths)
                logger.info(f"{len(allowed_file_paths)} files added to QFileSystemWatcher.")

        except Exception as e:
            logger.critical(f"Unhandled exception in add_files_to_watcher: {e}", exc_info=True)
        logger.debug("Exiting CustomFileSystemModelForList.add_files_to_watcher")
    
    def on_directory_loaded(self, path):
        """
        Gère les actions à effectuer après le chargement d'un répertoire.
        """
        logger.info(f"on_directory_loaded / Répertoire chargé (CustomFileSystemModelForList) : {path}")
        if self.indexer: 
            normalized_path = os.path.normpath(path)
            if normalized_path.startswith(os.path.normpath(WATCHED_DIR)):
                self.indexer.buildIndex()
                logger.debug("add_files_to_watcher.on_directory_loaded(self, path) / self.indexer.buildIndex()  called")
            #self.add_files_to_watcher(path) 
        else:
            logger.warning("CustomFileSystemModelForList/ on_directory_loaded; Indexer non disponible dans CustomFileSystemModelForList.")

    def data(self, index, role=Qt.DisplayRole):
        #logger.debug(f"CustomFileSystemModelForList.data called for index {index} with role {role}")  
        if role in (Qt.SizeHintRole, Qt.FontRole):
            return None
        try:
            if not index.isValid():
                return None
            if role not in [Qt.DisplayRole, Qt.DecorationRole, Qt.ToolTipRole]:
                return None
            return self.standard_data_method(index, role)
        except Exception as e:
            logger.exception(f"CustomFileSystemModelForList.data: Exception occurred: {e}")
            return None
        finally:
            pass
            #logger.debug(f"CustomFileSystemModelForList.data finished for index {index} with role {role}")

    def standard_data_method(self, index, role):
        """Méthode standard pour fournir les données du modèle."""

        if role in (Qt.SizeHintRole, Qt.FontRole):  # Optimisation : ignorer ces rôles
            return None
        
        file_path = self.filePath(index)
        #logger.debug(f"CustomFileSystemModelForList.standard_data_method called for {file_path}, role: {role}")
        #logger.debug(f"standard_data_method: Current thread: {QThread.currentThread()}")
        try:
            if not index.isValid():
                return None
            #logger.debug(f"Entering role check for {file_path}")
            if role not in (Qt.DisplayRole, Qt.DecorationRole, Qt.ToolTipRole):
               #logger.debug(f"Unsupported role {role} for {file_path}, returning None")
               return None 
            #logger.debug(f"Processing role {role} for {file_path}")
            if role == Qt.DisplayRole:
                filename = os.path.basename(file_path)
                #logger.debug(f"DisplayRole: Original filename {filename}")
                if file_path in QDir.drives():
                    filename = file_path
                    #logger.debug(f"DisplayRole: Filename is a drive {filename}")
                longueur_max_titre = 25
                if len(filename) > longueur_max_titre:
                    filename = filename[:longueur_max_titre - 3] + "..."
                return filename

            elif role == Qt.ToolTipRole:
                #logger.debug(f"ToolTipRole: Returning file path {file_path}")
                return file_path

            elif role == Qt.DecorationRole and index.column() == 0:
                #logger.debug(f"CustomFileSystemModelForList: Standard data method called ; Requesting thumbnail for {file_path} , role: {role}")
                if os.path.isdir(file_path):
                    #logger.debug(f"DecorationRole: {file_path} is a directory, returning folder icon")
                    return self.folder_icon
                elif file_path.lower().endswith(('.dxf', '.png', '.jpg', '.jpeg')):
                    thumbnail = self.thumbnailManager.cache.get(file_path)  # Vérifier le cache
                    if thumbnail is None:
                        with QMutexLocker(self.thumbnailManager.processing_thread.pending_requests_lock):  # Utiliser le verrou spécifique
                            # Vérifier les requêtes en attente dans ThumbnailManager
                            if file_path not in self.thumbnailManager.processing_thread.pending_requests: 
                                self.thumbnailManager.processing_thread.pending_requests.add(file_path)
                                if QThread.currentThread() == QApplication.instance().thread():
                                    self.thumbnailRequested.emit(file_path)
                                else:
                                    # Émettre le signal depuis le thread principal en utilisant un timer
                                    QTimer.singleShot(0, lambda: self.thumbnailRequested.emit(file_path))
                        return QPixmap()  # Retourner une image vide en attendant
                    else:
                        return thumbnail.scaled(160, 120, Qt.KeepAspectRatio, Qt.SmoothTransformation)  # Redimensionner la vignette
                else:
                    return None  # Aucun icône pour les autres types de fichiers
        except Exception as e:
            logger.exception(f"CustomFileSystemModelForList.data: Exception occurred: {e}")
            return None
 
    @pyqtSlot(str, QPixmap, bool)
    def on_thumbnail_ready(self, file_path, thumbnail, success):
        """Slot appelé lorsque la vignette est prête."""
        #logger.debug(f"CustomFileSystemModelForList: on_thumbnail_ready() - file_path: {file_path}, success: {success}")
        if success:
            self.update_thumbnail_cache(file_path, thumbnail)
        else:
            logger.warning(f"on_thumbnail_ready: Échec de la génération de la vignette pour {file_path}")
    @pyqtSlot(str, bool)
    def handle_request_processed(self, file_path, success):
        """Gère le signal requestProcessed du ProcessingThread."""
        if not success:
            # Gérez l'échec de la génération de la vignette (réessayez, affichez un message d'erreur, etc.)
            logger.error(f"CustomFileSystemModelForList: Failed to generate thumbnail for {file_path}")
        else:
            logger.debug(f"CustomFileSystemModelForList: Thumbnail successfully generated for {file_path}")

    @pyqtSlot(str, QPixmap)
    def update_thumbnail_cache(self, file_path, thumbnail):
        """Met à jour le cache des vignettes et émet le signal dataChanged dans le thread principal."""
        #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Entrée avec file_path: {file_path}")
        
        # Utiliser QMutexLocker pour assurer le déverrouillage automatique
        with QMutexLocker(self.thumbnailManager.lock):
            #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Verrou acquis")
            if file_path not in self.thumbnailManager.cache or self.thumbnailManager.cache[file_path] != thumbnail:
                #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Vignette absente du cache ou différente, mise à jour en cours...")
                self.thumbnailManager.cache[file_path] = thumbnail
                #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Vignette mise à jour dans le cache pour {file_path}")
            else:
                logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Vignette déjà présente dans le cache, aucune mise à jour nécessaire.")

        # Émettre le signal dataChanged pour indiquer que les données ont changé
        index = self.indexFromPath(file_path)
        if index.isValid():
            #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Émission du signal dataChanged pour {file_path}")  
            self.dataChanged.emit(index, index, [Qt.DecorationRole])  # Émettre le signal dataChanged pour l'élément spécifique
            #logger.debug(f"CustomFileSystemModelForList: update_thumbnail_cache() - Signal dataChanged émis pour {file_path}")
        else:
            logger.warning(f"CustomFileSystemModelForList: update_thumbnail_cache() - Index invalide pour {file_path}")

class CustomFileSystemModelForTree(QFileSystemModel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Cache the folder icon for performance
        script_directory = os.path.dirname(os.path.abspath(__file__))
        icon_path = os.path.join(script_directory, "dossier.png")
        self.folder_icon = QIcon(QPixmap(icon_path))
        if self.folder_icon.isNull():
            logger.error(f"Failed to load folder icon from {icon_path}")

    def data(self, index, role):
        if role == Qt.DecorationRole and index.column() == 0:
            # Get the full path of the file or directory at the index
            file_path = self.filePath(index)
            # Check if it's a directory
            if os.path.isdir(file_path):
                return self.folder_icon
        elif role == Qt.DisplayRole:
            # Get the default display text
            original_text = super().data(index, role)
            return original_text
        return None

# Background search worker class
class SearchWorker(QObject):
    searchFinished = pyqtSignal(bool, list)  # Signal to indicate search success

    def __init__(self, indexer, index_dir):
        super().__init__()
        self.running = False
        self.running_mutex = QMutex()  # Mutex to protect self.running
        self.rootPath = ""
        self.criteria = ""
        self.criteria_type = "filename"
        self.start_date = None
        self.end_date = None
        self.allowed_extensions = ALLOWED_EXTENSIONS
        self.extension_pattern = re.compile(r".*(" + "|".join([re.escape(ext) + "$" for ext in self.allowed_extensions]) + ")", re.IGNORECASE)
        self.indexer = indexer  
        self.index_dir = os.path.normpath(index_dir)
        logger.debug(f"SearchWorker initialized with index_dir: {self.index_dir}")

    def stop(self):
        """Stop the ongoing search gracefully."""
        self.running_mutex.lock()
        running = self.running
        self.running_mutex.unlock()

        if not running:
            logger.info("Search interrupted by user.")
            return  # Exit if the search was not running

        logger.info("Search stopped by user.")
        self.running = False  # This will cause the search loop to exit

    def setCriteria(self, criteria, criteria_type="filename"):
        """Sets the search criteria and its type."""
        self.criteria = criteria.lower()  # Convert criteria to lowercase
        self.criteria_type = criteria_type
        logger.debug(f"Search criteria set: {criteria} (type: {criteria_type})")

    def setBasePath(self, path):
        self.rootPath = os.path.normpath(path)
        logger.debug(f"Base path for search set: {self.rootPath}")

    def setDateRange(self, start_date, end_date):
        self.start_date = start_date.toPyDate()
        self.end_date = end_date.toPyDate()
        logger.debug(f"Date range set: {self.start_date} - {self.end_date}")

        # Pre-calculate timestamps for date comparison
        self.start_date_timestamp = datetime.combine(self.start_date, datetime.min.time()).timestamp()
        self.end_date_timestamp = datetime.combine(self.end_date, datetime.max.time()).timestamp()

    def performSearch(self, retry=False):
        logger.info("Starting search.")
        logger.debug(f"Root path for search: {self.rootPath}")

        # Verify root path
        if not self.rootPath or not os.path.exists(self.rootPath):
            logger.error("Base path not set or does not exist.")
            self.searchFinished.emit(False, [])
            return

        # Validate dates
        if self.start_date and self.end_date and self.start_date > self.end_date:
            logger.error("Start date must be before end date.")
            self.searchFinished.emit(False, [])
            return

        self.running_mutex.lock()  # Lock before modifying
        self.running = True
        self.running_mutex.unlock()  # Unlock after modification

        results = []
        criteria_lower = self.criteria.lower().strip()
        if not criteria_lower:
            logger.error("Search criteria is empty after processing. Aborting search.")
            self.searchFinished.emit(False, [])
            return

        try:
            # Check if within watched directory
            normalized_root_path = os.path.normcase(os.path.normpath(self.rootPath))
            normalized_watched_dir = os.path.normcase(os.path.normpath(WATCHED_DIR))

            if os.path.commonpath([normalized_root_path, normalized_watched_dir]) == normalized_watched_dir:
                logger.info("Using index-based search.")
                self.index_based_search(results, criteria_lower, retry)
            else:
                logger.info("Using file system search.")
                self.scan_directory(normalized_root_path, results, criteria_lower)

            #logger.debug(f"Final search results: {results}")
        except Exception as e:
            logger.error(f"Error during search: {e}")
            self.searchFinished.emit(False, [])
            return
        finally:
            logger.info("Finalizing search.")
            self.running_mutex.lock()
            self.running = False
            self.running_mutex.unlock()
            self.searchFinished.emit(True, results)
            logger.info(f"Search completed with {len(results)} results.")

    def index_based_search(self, results, criteria_lower, retry):
        try:
            logger.info("Performing index-based search.")
            ix = open_dir(self.index_dir)
            with ix.searcher() as searcher:
                query_parts = []

                # Build query based on criteria type
                if self.criteria_type == "filename":
                    if not criteria_lower.strip():
                        raise ValueError("Search criteria for filename is empty.")
                    query_part = Prefix("name", criteria_lower)
                    query_parts.append(query_part)
                elif self.criteria_type in ("creation_date", "modification_date"):
                    date_field = "creation_time" if self.criteria_type == "creation_date" else "modification_time"
                    query_parts.append(DateRange(date_field, self.start_date, self.end_date))

                # Filter by extensions using regex
                if self.allowed_extensions:  # Check if extensions are specified
                    query_parts.append(self.extension_pattern)

                # Filter by path
                query_parts.append(Prefix("path", os.path.normcase(os.path.normpath(self.rootPath))))
                query = And(query_parts)

                # Execute the search
                hits = searcher.search(query, limit=None)
                for hit in hits:
                    self.running_mutex.lock()  # Protect access to self.running
                    running = self.running
                    self.running_mutex.unlock()
                    if not running:
                        break
                    results.append(hit["path"])
        except EmptyIndexError:
            if not retry:
                logger.warning("Index directory is empty, creating index...")
                self.indexer.buildIndex()
                return self.performSearch(retry=True)  # Retry after rebuilding index
            else:
                logger.error("Index is still empty after rebuilding.")
                self.searchFinished.emit(False, [])
        except Exception as e:
            logger.error(f"Error during Whoosh search: {e}")
            self.searchFinished.emit(False, [])

    def scan_directory(self, directory, results, criteria_lower):
        """Recursively scan the directory for files matching the criteria."""
        for entry in os.scandir(directory):
            if entry.is_dir():
                self.scan_directory(entry.path, results, criteria_lower)  # Recursive call for subdirectories
            elif entry.is_file():
                self.process_file(entry.path, entry.name, results, criteria_lower)

    def process_file(self, fullPath, file, results, criteria_lower):
        try:
            file_lower = file.lower()
            _, extension = os.path.splitext(fullPath)

            # Check extension using regex
            if not self.extension_pattern.match(fullPath):
                return

            # Check criteria
            if self.criteria_type == "filename" and criteria_lower in file_lower:
                results.append(fullPath)
            elif self.criteria_type in ("creation_date", "modification_date"):
                date_to_check = os.path.getctime(fullPath) if self.criteria_type == "creation_date" else os.path.getmtime(fullPath)
                if self.compare_dates(date_to_check) :
                    if criteria_lower in file_lower:
                        results.append(fullPath)
        except Exception as e:
            logger.error(f"Error processing file {fullPath}: {e}")

    def compare_dates(self, file_timestamp):
        return self.start_date_timestamp <= file_timestamp <= self.end_date_timestamp
class TreeProxyModel(QSortFilterProxyModel):
    def filterAcceptsRow(self, source_row, source_parent):
        index = self.sourceModel().index(source_row, 0, source_parent)
        if self.sourceModel().isDir(index):
            #logger.debug(f"Folder accepted for display: {index.data()}")
            return True  # Accept the entry as it is a folder
        #logger.debug(f"Entry rejected (not a folder): {index.data()}")
        return False  # Reject the entry as it is not a folder

class FileListProxyModel(QSortFilterProxyModel):
    def __init__(self, parent=None):
        super(FileListProxyModel, self).__init__(parent)
        self.filter_text = ""
        self.filters = ['.dxf', '.png', '.jpg', '.jpeg']
        self.recursiveSearchEnabled = True  # Enabled by default   
        self.searchPaths = []  # Paths to use for filtering results     

    def setFilterText(self, text):
        """Set the filter text and invalidate the current filter."""
        self.filter_text = text.lower()
        self.invalidateFilter()
        logger.debug(f"Filter text set: {self.filter_text}")   

    def setSearchPaths(self, paths):
        """Set the search paths for filtering."""
        self.searchPaths = paths
        self.invalidateFilter()
        logger.debug(f"Search paths set: {self.searchPaths}") 

    def setRecursiveSearchEnabled(self, enabled):
        """Enable or disable recursive search."""
        self.recursiveSearchEnabled = enabled
        self.invalidateFilter()
        logger.debug(f"Recursive search {'enabled' if enabled else 'disabled'}.")
    
    def filterAcceptsRow(self, sourceRow, sourceParent):
        sourceModel = self.sourceModel()
        if not sourceModel:
            return False

        sourceIndex = sourceModel.index(sourceRow, 0, sourceParent)
        if not sourceIndex.isValid():
            return False

        # Standard filtering logic
        filepath = sourceModel.filePath(sourceIndex).lower()
        isDirectory = sourceModel.isDir(sourceIndex)
        isAcceptableFile = isDirectory or any(filepath.endswith(ext) for ext in self.filters)
        filename = sourceModel.fileName(sourceIndex).lower()

        if self.searchPaths:
            if not any(filepath.startswith(path.lower()) for path in self.searchPaths):
                #logger.debug(f"Path {filepath} rejected by search paths.")
                return False

        if self.filter_text:
            filename = self.sourceModel().data(sourceIndex, Qt.DisplayRole).lower()
            textMatch = self.filter_text in filename or self.filter_text in filepath
        else:
            textMatch = True

        # Final decision for row acceptance
        if isAcceptableFile and textMatch:
            return True

        # If recursive search is enabled and the index is a directory, check subdirectories
        if self.recursiveSearchEnabled and isDirectory:
            rowCount = sourceModel.rowCount(sourceIndex)
            logger.debug(f"Recursive search enabled, number of subdirectories for {filepath}: {rowCount}")
            for childRow in range(rowCount):
                if self.filterAcceptsRow(childRow, sourceIndex):
                    logger.debug(f"Recursive acceptance for {filepath} due to subdirectory at row {childRow}")
                    return True

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

class SearchResultsProxyModel(QAbstractProxyModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._search_results = []  # Stocke les chemins des fichiers trouvés
        self._path_to_row = {}     # Mapping from path to row index
        self._cached_source_indexes = {}  # Cache pour les index source

    def setSearchResults(self, results):
        """Met à jour les résultats de la recherche et signale les changements au modèle."""
        logger.debug(f"SearchResultsProxyModel.setSearchResults called with {len(results)} results.")
        self.beginResetModel()
        self._search_results = [os.path.normcase(r) for r in results]
        self._path_to_row = {path: idx for idx, path in enumerate(self._search_results)}
        self._cached_source_indexes.clear()  # Vider le cache car les résultats ont changé
        self.endResetModel()
        logger.debug("setSearchResults: Model reset completed.") 

    def rowCount(self, parent=QModelIndex()):
        """Retourne le nombre de résultats de recherche."""
        if parent.isValid():  # Si l'index parent est valide, on est dans un noeud enfant, donc 0 enfant
            return 0
        return len(self._search_results)

    def filePath(self, index):
        """Retourne le chemin du fichier pour l'index donné, en déléguant au modèle source."""
        source_index = self.mapToSource(index)
        return self.sourceModel().filePath(source_index)

    def columnCount(self, parent=QModelIndex()):
        """Retourne le nombre de colonnes du modèle source."""
        return self.sourceModel().columnCount(parent) if self.sourceModel() else 0

    def index(self, row, column, parent=QModelIndex()):
        """Retourne l'index du proxy pour la ligne, la colonne et le parent donnés."""
        if not parent.isValid() and 0 <= row < len(self._search_results):
            source_index = self.sourceModel().indexFromPath(self._search_results[row])
            if source_index.isValid():
                return self.createIndex(row, column, source_index)
            else:
                logger.error(f"Invalid source index for path: {self._search_results[row]}")                     
        return QModelIndex()

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

    def mapFromSource(self, sourceIndex):
        """Retourne l'index du proxy correspondant à l'index source."""
        logger.debug(f"FileListProxyModel.mapFromSource appelé avec sourceIndex: {sourceIndex}")
        if sourceIndex.isValid():
            source_file_path = os.path.normcase(os.path.normpath(self.sourceModel().filePath(sourceIndex)))
            row = self._path_to_row.get(source_file_path)
            if row is not None:
                return self.createIndex(row, sourceIndex.column())
            else:
                logger.warning("FileListProxyModel.mapFromSource reçu un sourceIndex invalide.")
                return QModelIndex()
        return QModelIndex()

    def mapToSource(self, proxyIndex):
        """Retourne l'index source correspondant à l'index du proxy."""
        #logger.debug(f"FileListProxyModel.mapToSource appelé avec proxyIndex: {proxyIndex}")
        if proxyIndex.isValid() and 0 <= proxyIndex.row() < len(self._search_results):
            source_path = self._search_results[proxyIndex.row()]
            if source_path in self._cached_source_indexes:
                return self._cached_source_indexes[source_path]
            source_index = self.sourceModel().indexFromPath(source_path)
            if source_index.isValid():
                self._cached_source_indexes[source_path] = source_index
            return source_index
        return QModelIndex()
    
    def data(self, index, role=Qt.DisplayRole):
        """Retourne les données pour l'index donné et le rôle."""
        if not index.isValid():
            logger.debug("Invalid index in data method.")
            return None

        if not self.sourceModel():
            logger.error("Source model is not set.")
            return None

        source_index = self.mapToSource(index)  # Obtenir un nouvel index source valide
        if not source_index.isValid():
            logger.debug("Invalid source index in data method.")
            return None

        return self.sourceModel().data(source_index, role)
    def setSourceModel(self, source_model):
        logger.debug("SearchResultsProxyModel: setSourceModel called")
        super().setSourceModel(source_model)
        if source_model is not None:
            source_model.dataChanged.connect(self.sourceDataChanged)

    def sourceDataChanged(self, sourceTopLeft, sourceBottomRight, roles):
        #logger.debug(f"SearchResultsProxyModel: sourceDataChanged called with sourceTopLeft={sourceTopLeft}, sourceBottomRight={sourceBottomRight}, roles={roles}")
        # Mapper les index source vers les index du proxy
        proxyTopLeft = self.mapFromSource(sourceTopLeft)
        proxyBottomRight = self.mapFromSource(sourceBottomRight)
        #logger.debug(f"SearchResultsProxyModel: Mapped proxyTopLeft={proxyTopLeft}, proxyBottomRight={proxyBottomRight}")

        if proxyTopLeft.isValid() and proxyBottomRight.isValid():
            #logger.debug("SearchResultsProxyModel: Emitting dataChanged from proxy model")
            # Émettre le signal dataChanged du proxy avec les rôles appropriés
            self.dataChanged.emit(proxyTopLeft, proxyBottomRight, roles)
        else:
            logger.debug("SearchResultsProxyModel: Skipping dataChanged emission, model is not active.")
    
class ThumbnailWorkerSignals(QObject):
    """Classe pour regrouper les signaux du ThumbnailWorker."""
    thumbnailReady = pyqtSignal(str, QPixmap, bool)  # Chemin du fichier, vignette, succès
    finished = pyqtSignal()
    errorOccurred = pyqtSignal(str, Exception)  # Chemin du fichier, exception
 
class ThumbnailWorker(QRunnable):
    """Worker pour générer les vignettes de manière asynchrone."""

    def __init__(self, file_path, cache):
        super().__init__()
        self.signals = ThumbnailWorkerSignals()  # Instance pour gérer les signaux
        self.file_path = file_path
        self.cache = cache  # Référence au cache partagé
        self._cancel_requested = False

    @pyqtSlot()
    def cancel(self):
        """Annule la génération de la vignette si elle est en cours."""
        self._cancel_requested = True

    @pyqtSlot()  # Supprimer l'argument file_path
    def run(self):  # Méthode exécutée dans un thread séparé pour générer la vignette
        #logger.debug(f"ThumbnailWorker: run() Démarrage pour {self.file_path}")
        if self._cancel_requested:
            logger.warning(f"ThumbnailWorker: run() cancelled for {self.file_path}")
            return

        # Vérifier si la vignette est déjà dans le cache
        if self.file_path in self.cache:
            #logger.debug(f"ThumbnailWorker: Thumbnail found in cache for {self.file_path}")
            self.signals.thumbnailReady.emit(self.file_path, self.cache[self.file_path], True)
            self.signals.finished.emit()
            return
        
        try:
            #logger.debug(f"ThumbnailWorker: Generating thumbnail for {self.file_path}")
            thumbnail = self.generate_thumbnail(self.file_path)
            success = thumbnail is not None and not thumbnail.isNull()
            #logger.debug(f"ThumbnailWorker: Thumbnail generated for {self.file_path}, success: {success}")

            if success:
                self.signals.thumbnailReady.emit(self.file_path, thumbnail, success)
        except (FileNotFoundError, ValueError, CancelledError) as e:
            logger.error(f"ThumbnailWorker: Error generating thumbnail for {self.file_path}: {e}")
            self.signals.errorOccurred.emit(self.file_path, e)
        finally:
            self.signals.finished.emit()

    def generate_thumbnail(self, file_path):
        """Génère une vignette pour le fichier donné."""
        #logger.debug(f"ThumbnailWorker: generate_thumbnail called for {file_path}")
        try:
            if file_path.lower().endswith(('.png', '.jpg', '.jpeg')):
                thumbnail = self.generate_image_thumbnail(file_path)
            elif file_path.lower().endswith('.dxf'):
                thumbnail = self.generate_dxf_thumbnail(file_path)
            else:
                logger.warning(f"ThumbnailWorker: generate_thumbnail() - Unsupported file format for {file_path}")
                raise ValueError(f"Unsupported file format: {file_path}")
            return thumbnail
        except Exception as e:
            logger.error(f"ThumbnailWorker: Error generating thumbnail for {file_path}: {e}", exc_info=True)
            raise

    def generate_image_thumbnail(self, image_path):
        """Génère une miniature à partir d'un fichier image."""
        #logger.debug(f"ThumbnailWorker: generate_image_thumbnail for image: {image_path}")
        try:
            if self._cancel_requested:
                logger.warning(f"ThumbnailWorker: generate_image_thumbnail - Annulé pour {image_path}")
                return None  # Return None if cancelle
            size = QSize(160, 120)
            reader = QImageReader(image_path)
            reader.setAutoTransform(True)
            image = reader.read()
            if image.isNull():
                logger.error(f"Impossible de lire l'image: {image_path}")
                return QPixmap()
            thumbnail = QPixmap.fromImage(image).scaled(size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
            return thumbnail
        except ValueError as ve:
            logger.error(f"ValueError creating thumbnail for {image_path}: {ve}", exc_info=True)
            return QPixmap()
        except Exception as e:
            logger.error(f"Error creating thumbnail for {image_path}: {e}", exc_info=True)
            return QPixmap()  # Return an empty QPixmap on failure
    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()
    # SUITE DE LA CLASS .... 
class FileViewer(QMainWindow):
    folderChanged  = pyqtSignal(str)
    fileSelected = pyqtSignal(str)
    rechercheChanged = pyqtSignal(str)
    def __init__(self):
        super().__init__()
        self.scale_factor = 1  # Initialiser avec une valeur par défaut
        self.indexeur = FileIndexer()
        self.setWindowTitle("Souf Dxf Explorer")
        self.setGeometry(100, 100, 800, 600)

        self.model = CustomFileSystemModelForList(self)
        self.isSearchThreadRunning = False
        self._in_search_mode = False

        # Search indicator
        self.search_indicator = QLabel("Recherche en cours...", self)
        self.statusBar().addWidget(self.search_indicator)
        self.search_indicator.hide()

        self.current_folder = os.path.normpath(os.path.expanduser("~")) 
        self.previous_folder = None
        #self.current_folders = [QDir.homePath()]  # Initialize the current_folders attribute
        #self.rootPaths = ["Y:/"]
        #logger.debug(f"[Init] Current folders initialized to: {self.current_folders}")
        self.setupModelsAndView()
        self.setupUIComponents()
        self.setupObservers()
        self.connect_signals()
        self.indexeur.indexUpdated.connect(self.on_indexing_finished) 
        self.indexeur.indexerError.connect(self.on_indexing_error) # connecter au slot
        self.indexeur.indexingProgress.connect(self.update_indexing_progress)  # Connecter le signal
        # Démarrer l'indexation asynchrone après l'initialisation de l'interface
        #QTimer.singleShot(3000, lambda: (self.indexeur.buildIndexAsync(), logger.info(" Fileviewer.__init__(self) (self.indexeur.buildIndexAsync())Lambda function in QTimer.singleShot executed")))
        #logger.info("FileViewer / Asynchronous indexing initiated.") 
    
    def connect_signals(self):
        if not hasattr(self, 'signals_connected'):
            # Connexions pour la mise à jour de la vue de la liste de fichiers
            self.file_list.model().dataChanged.connect(self.update_file_list_view)
            self.model.modelUpdated.connect(self.update_file_list_view)
            logger.info("Connection: file_list.clicked -> display_selected_file")
            self.file_list.clicked.connect(self.display_selected_file)
            logger.info("Connection: file_list.doubleClicked -> on_folder_double_clicked")
            self.file_list.doubleClicked.connect(self.on_folder_double_clicked)

            # Connexions pour la recherche
            self.SearchWorker.searchFinished.connect(self.onSearchFinished)
            self.searchThread.started.connect(self.SearchWorker.performSearch)

            # Connexions pour les temporisateurs et les champs de texte
            self.search_timer.timeout.connect(self.launchSearch)
            self.search_box.textChanged.connect(self.launch_delayed_search)

            # Configuration du proxy de recherche
            self.search_results_proxy_model = SearchResultsProxyModel(self)
            self.search_results_proxy_model.setSourceModel(self.model)  # Définir le modèle source du proxy

            self.signals_connected = True

    def setupUIComponents(self):
        self.pathLabel = QLabel("Path:")
        self.setup_ui()
        self.setupMenu()
        self.setupToolbar()

    def setupModelsAndView(self):
        #self.indexeur = FileIndexer()
        self.rootPaths = [os.path.normpath("")]
        for path in self.rootPaths:
            if os.path.exists(path):
                print(f"Le chemin {path} est valide.")
            else:
                #print(f"Le chemin {path} n'est pas valide.")
                print(f"Le chemin path est:{path} ")
        self.tree_model = CustomFileSystemModelForTree()
        self.file_list_model = CustomFileSystemModelForList(self, self.indexeur)
        self.file_list = QListView()
        self.setup_tree_dock_widget()
        self.setup_proxy_tree_model()

        # Add SearchResultsProxyModel
        self.search_results_proxy_model = SearchResultsProxyModel(self)
        self.search_results_proxy_model.setSourceModel(self.file_list_model)

        # Connect signal modelReset
        self.search_results_proxy_model.modelReset.connect(self.file_list.reset)
        # Connect signals layoutChanged and dataChanged
        self.search_results_proxy_model.layoutChanged.connect(self.file_list.reset)
        self.search_results_proxy_model.dataChanged.connect(self.file_list.reset)

        # Configure FileListProxyModel to use SearchResultsProxyModel as source
        self.file_list_proxy = FileListProxyModel()
        self.setup_file_list_proxy()
        self.file_list_proxy.setSourceModel(self.search_results_proxy_model)

        # Configure list view to use the proxy file_list_proxy
        self.file_list.setModel(self.file_list_proxy)

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

    def setupObservers(self):
        self.SearchWorker = SearchWorker(self.indexeur, self.indexeur.index_dir)
        self.searchThread = QThread()
        self.SearchWorker.moveToThread(self.searchThread)

        self.FileMonitor = FileMonitor(self.indexeur, self.SearchWorker.allowed_extensions) 
        self.observer = Observer()
        self.observer.schedule(self.FileMonitor, path=os.path.normpath(WATCHED_DIR), recursive=True)   # Surveiller WATCHED_DIR
        self.observer.start()

        self.search_timer = QTimer(self)
        self.search_timer.setSingleShot(True)

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

    def displayResults(self, success, results):
        logger.debug("Beginning of displayResults function.")

        # Arrêter le profilage et afficher les résultats
        #if hasattr(self, 'profiler'):  # Vérifier si le profilage a été activé
            #self.profiler.disable()
            #s = io.StringIO()
            #ps = pstats.Stats(self.profiler, stream=s).sort_stats('cumulative')
            #ps.print_stats()
           # logger.debug(f"Profiling results:\n{s.getvalue()}")

        # Eviter les appels récursifs ou redondants
        if hasattr(self, 'display_results_called') and self.display_results_called:
            logger.debug("displayResults already called, skipping.")
            return

        self.display_results_called = True
        self.isSearchThreadRunning = False
        self.search_indicator.hide()

        # Gérer les différents cas de figure
        if not success:
            logger.debug("Handling search failure.")
            self.handleSearchFailure()
            self._in_search_mode = False  # Assurez de quitter le mode recherche en cas d'échec
        elif not results:
            logger.debug("Handling no results.")
            self.handleNoResults()
            self._in_search_mode = False
        elif not self.search_box.text().strip():
            logger.debug("Search box is empty, resetting list view.")
            self.resetListView()
            self._in_search_mode = False
        else:
            # Afficher les résultats de la recherche
            self._in_search_mode = True
            logger.debug("Updating proxy model with search results.")
            self.search_results_proxy_model.setSearchResults(results)  # Mettre à jour le proxy
            # Si le modèle actuel de la vue n'est pas le modèle de résultats, le définir
            if self.file_list.model() != self.search_results_proxy_model:
                self.file_list.setModel(self.search_results_proxy_model)
            logger.debug("SearchResultsProxyModel set as the current model for file_list.")
            # Rafraîchir la vue
            #self.file_list.reset()
            #logger.debug(f"displayResults: ListView reset. Number of items in view: {self.file_list.model().rowCount()} ")
            # Forcer le focus et mettre à jour la vue
            #self.file_list.setFocus()
            #self.file_list.update()
            #self.file_list.repaint()
            # (Facultatif) Sélectionner le premier élément si souhaité
            if results:
                first_result_index = self.search_results_proxy_model.index(0, 0)
                if first_result_index.isValid():
                    self.file_list.setCurrentIndex(first_result_index)
            logger.debug("Logging search results.")
            self.logSearchResults(results)

        self.display_results_called = False
        logger.debug("End of displayResults function.")

    def launch_delayed_search(self):
        logger.debug("Slot activated: launch_delayed_search")
        search_criteria = self.search_box.text().strip()
        if search_criteria:
            logger.info("Search criteria modified. Waiting before launching search...")
            if not self.previous_folder: 
                self.previous_folder = self.current_folder  # Sauvegarder le dossier actuel 
            self.search_timer.stop()  # Stop the existing timer before restarting
            if self.search_timer.isActive():
                self.search_timer.stop()
            self.search_timer.start(1000)  # Delay of 1000 ms
        else:
            logger.info("Search bar empty. Displaying current folder.")
            self.exit_search_mode()
        logger.debug("End of launch_delayed_search")
    def launchSearch(self):
        logger.debug("Function launchSearch start")
        criteria = self.search_box.text().strip()
        criteria_type = self.search_criteria_type.currentText().lower().replace(" ", "_")
        logger.debug(f"Search criteria: {criteria}, criteria_type: {criteria_type}")
        logger.debug(f"Current root path: {self.file_list_model.rootPath()}")
        logger.debug(f"WATCHED_DIR: {WATCHED_DIR}")
        if not criteria and criteria_type == "filename":
            logger.debug("Search criteria is empty, exiting search.")
            #QMessageBox.warning(self, "Warning", "Search criteria cannot be empty for filename search.")
            self.exit_search_mode()
            return

        if self.isSearchThreadRunning:
            logger.info("Search already running, stopping current search.")
            self.SearchWorker.running = False  # Stop the thread gracefully
            self.searchThread.quit()
            self.searchThread.wait()
        # Sauvegarder le dossier actuel avant de lancer la recherche  
        if not self.previous_folder:  
            self.previous_folder = self.current_folder  
            logger.debug(f"Saved current folder to previous_folder: {self.previous_folder}") 

        self.isSearchThreadRunning = True
        self.SearchWorker.setCriteria(criteria, criteria_type)
        self.SearchWorker.setDateRange(self.start_date.date(), self.end_date.date())

        # Assurez-vous que rootPath est bien défini
        current_root_path = self.file_list_model.rootPath()
        if current_root_path:
            self.SearchWorker.setBasePath(current_root_path)
            #self.profiler = cProfile.Profile()
            #self.profiler.enable()
            #self.searchThread.started.connect(self.SearchWorker.performSearch)
            self.searchThread.start()
            self.search_indicator.show()
            logger.debug("Function launchSearch end")
        else:
            logger.error("Current root path is not set. Cannot start search.")
            self.isSearchThreadRunning = False
    def onSearchFinished(self, success, results):
        logger.debug("Search finished with success: {}".format(success))
        self.isSearchThreadRunning = False
        if self.searchThread.isRunning():
            logger.debug("Stopping search thread.")
            self.searchThread.quit()
            self.searchThread.wait()  # Ensure the thread is fully stopped
            logger.debug("Search thread stopped.")
        logger.debug("Search thread stopped and resources cleaned up.")
        self.handle_search_completion(success, results)
        logger.debug("End of onSearchFinished function.")

    def handle_search_completion(self, success, results):
        logger.debug("Function handle_search_completion start")
        if success:
            logger.info("Search completed successfully.")
            self.displayResults(success, results)
            # Connecter le proxy de recherche à la vue SI en mode recherche
            if self._in_search_mode:  
                self.file_list.setModel(self.search_results_proxy_model)
            else:
                # Revenir au proxy normal si on n'est plus en recherche
                self.file_list.setModel(self.file_list_proxy) 
        else:
            logger.error("Search failed, attempting to restart.")
            logger.debug(f"Search results (should be empty on failure): {results}")
            # Resetting the thread to allow for new searches
            self.searchThread.quit()
            self.searchThread.wait()  # Wait until the thread cleanly finishes
            self.searchThread = QThread()  # Recreate the thread for future use
            self.SearchWorker = SearchWorker(self.indexeur, self.indexeur.index_dir)
            self.SearchWorker.moveToThread(self.searchThread)
            self.searchThread.started.connect(self.SearchWorker.performSearch)
            self.SearchWorker.searchFinished.connect(self.onSearchFinished)

        logger.debug("Function handle_search_completion end")

    def handleSearchFailure(self):
        logger.debug("Beginning of handleSearchFailure function.")
        QMessageBox.warning(self, "Search Failed", "The search failed or was stopped without results.")
        self.resetListView()
        logger.debug("End of handleSearchFailure function.")

    def handleNoResults(self):
        logger.debug("Beginning of handleNoResults function.")
        QMessageBox.information(self, "No Results", "No results to display.")
        self.resetListView()
        logger.debug("End of handleNoResults function.")

    def logSearchResults(self, results):
        logger.debug("Beginning of logSearchResults function.")
        for result in results[:min(5, len(results))]:
            logger.debug(f"Search result: {result}")
        logger.debug("End of logSearchResults function.")
    
    def exit_search_mode(self):
        logger.debug("Function exit_search_mode start")
        self._in_search_mode = False
        self.file_list_proxy.invalidateFilter()
        if self.previous_folder:  
            logger.debug(f"[exit_search_mode] Previous folder: {self.previous_folder}") 
            self.current_folder = self.previous_folder  # Restaurer le dossier précédent  
            self.previous_folder = None  # Réinitialiser previous_folder  
            self.file_list_model.setRootPath(self.current_folder)
            root_index = self.file_list_model.indexFromPath(self.current_folder)
            if root_index.isValid():
                if hasattr(self, 'file_list_proxy'):
                    self.file_list_proxy.setSourceModel(self.file_list_model)
                    self.file_list.setModel(self.file_list_proxy)  
                    proxy_root_index = self.file_list_proxy.mapFromSource(root_index)
                    if proxy_root_index.isValid():
                       self.file_list.setRootIndex(proxy_root_index)
                    else:
                        logger.error(f"Invalid proxy root index: {proxy_root_index}")
                else:
                    self.file_list.setModel(self.file_list_model) 
                    self.file_list.setRootIndex(root_index)
            else:
                logger.error(f"Invalid root index: {root_index}")
            self.update_directory_label(f"Current folder: {self.current_folder}")  
            logger.info(f"[exit_search_mode] Current folder updated to: {self.current_folder}") 
        else:
            logger.warning("[exit_search_mode] No previous folder stored. Using default path.")  
            default_folder = QDir.homePath()
            self.file_list_model.setRootPath(default_folder)  
            root_index = self.file_list_model.index(default_folder)

            if hasattr(self, 'file_list_proxy'):
                self.file_list_proxy.setSourceModel(self.file_list_model)
                proxy_root_index = self.file_list_proxy.mapFromSource(root_index)
                self.file_list.setRootIndex(proxy_root_index)
            else:
                self.file_list.setRootIndex(root_index)

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

        logger.debug("Function exit_search_mode end")

    def resetListView(self):
        """Réinitialise la vue de liste pour afficher le contenu du répertoire courant."""
        try:
            self._in_search_mode = False  # Sortir du mode recherche
            self.file_list.setModel(self.file_list_proxy)  # Rétablir le proxy principal

            # Vérifier si current_folder est valide avant de continuer
            if self.current_folder and os.path.exists(self.current_folder):
                logger.debug(f"[resetListView] Current folder: {self.current_folder}")
                self.updateFileListModel(self.current_folder)  
                self.update_file_list_view()
                self.checkDisplayedFiles(self.file_list_model)
                self.updateDirectoryLabel()
            else:
                self.handleNoCurrentFolder()

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

        except Exception as e:
            self.handleResetListViewError(e)
    def updateFileListModel(self, folder_path):
        """Met à jour le chemin racine du modèle de liste de fichiers.

        Args:
            folder_path (str): Le nouveau chemin racine pour le modèle.
        """
        logger.debug("Fonction updateFileListModel: Début")
        if not isinstance(folder_path, str) or not folder_path:
            logger.error("Le chemin du dossier doit être une chaîne de caractères valide.")
            QMessageBox.warning(self, "Erreur", "Le chemin du dossier doit être une chaîne de caractères valide.")
            return
        if not os.path.isdir(folder_path):
            logger.error(f"Le chemin fourni n'est pas valide ou n'est pas un dossier: {folder_path}")
            QMessageBox.warning(self, "Erreur", "Le chemin fourni n'est pas un dossier valide.")
            return
        self.file_list_model.setRootPath(folder_path)
        logger.info(f"Chemin racine du modèle de liste de fichiers mis à jour: {folder_path}")
        logger.debug("Fonction updateFileListModel: Fin")

    def update_file_list_view(self, folder_path=None, index=None):
        logger.debug("Fonction update_file_list_view: Début")
        try:
            root_index = None
            if folder_path is not None:
                if isinstance(folder_path, str):
                    logger.debug(f"Chemin du dossier fourni: {folder_path}")
                    self.updateFileListModel(folder_path)
                    root_index = self.file_list_model.indexFromPath(folder_path)
                    if not root_index.isValid():
                        logger.error("Index racine obtenu à partir du chemin est invalide.")
                        return
                else:
                    logger.error("Le chemin du dossier doit être une chaîne de caractères valide.")
                    return
            elif index is not None:
                logger.debug(f"Index fourni: {index}")
                if not index.isValid():
                    logger.error("Index non valide reçu pour la mise à jour.")
                    return
                root_index = index if not isinstance(index.model(), QSortFilterProxyModel) else index.model().mapToSource(index)
                if not root_index.isValid():
                    logger.error("Index racine obtenu à partir de l'index fourni est invalide.")
                    return
            else:
                if self.current_folder:  
                    folder_path = self.current_folder  
                    logger.debug(f"Utilisation du dossier actuel: {folder_path}")  
                    self.updateFileListModel(folder_path)  
                    root_index = self.file_list_model.indexFromPath(folder_path)
                    if not root_index.isValid():
                        logger.error("Index racine obtenu à partir du dossier actuel est invalide.")
                        return
                else:
                    logger.error("Aucun dossier courant défini.")  
                    return  
 
            if hasattr(self, 'file_list_proxy'):
                self.file_list_proxy.setSourceModel(self.file_list_model)
                proxy_root_index = self.file_list_proxy.mapFromSource(root_index)
                if proxy_root_index.isValid():
                    self.file_list.setRootIndex(proxy_root_index)
                    logger.info(f"Vue de la liste mise à jour avec proxy. Index source: {root_index}, Index proxy: {proxy_root_index}")
                else:
                    logger.error("Erreur lors de la mise à jour de la vue avec proxy: L'index proxy n'est pas valide.")
            else:
                if root_index.isValid():
                    self.file_list.setRootIndex(root_index)
                    logger.info(f"Vue de la liste mise à jour sans proxy. Index source: {root_index}")
                else:
                    logger.error("Erreur lors de la mise à jour de la vue sans proxy: L'index racine n'est pas valide.")
        except Exception as e:
            logger.error(f"Erreur dans update_file_list_view: {e}")
            QMessageBox.warning(self, "Erreur", f"Une erreur s'est produite: {e}")

        logger.debug("Fonction update_file_list_view: Fin")

    def showModelInfo(self):
        logger.debug("Beginning of showModelInfo function.")
        try:
            self.logModelContents(self.file_list_model)
        except Exception as e:
            logger.error(f"An error occurred while displaying model information: {str(e)}")
            QMessageBox.critical(self, "Error", f"An error occurred: {str(e)}")
        logger.debug("End of showModelInfo function.")

    def logModelContents(self, model):
        logger.debug("Beginning of logModelContents function.")
        root_path = model.rootPath() 
        root_index = model.indexFromPath(root_path)
        for row in range(min(5, model.rowCount(root_index))):
            child_index = model.index(row, 0, root_index)
            if child_index.isValid():
                file_path = model.filePath(child_index)
                logger.debug(f"Element at index {row}: {file_path}")
            else:
                logger.warning(f"Invalid index at row {row}")
        logger.debug("End of logModelContents function.")

    def handleNoCurrentFolder(self):
        logger.info("No current folder set, not updating the file list.")
        self.current_folder = os.path.expanduser("~")  # Chemin par défaut vers le dossier utilisateur  
        self.updateFileListModel(self.current_folder)  
        self.updateDirectoryLabel()  
        self.update_file_list_view() 
        # You can add additional logic here, such as displaying a message to the user

    def setCurrentFolderPath(self):
        default_folder = os.path.expanduser("~")  # Ou un autre chemin par défaut
        self.current_folder = default_folder
        logger.debug(f"Current folder path reset to default: {self.current_folder}")

    def updateDirectoryLabel(self):
        self.update_directory_label(f"Current folder: {self.current_folder}")
        logger.debug("Directory label updated.")

    def handleResetListViewError(self, error):
        logger.error(f"Error resetting list view: {error}")
        QMessageBox.warning(self, "Error", "Problem resetting list view.")
        logger.warning("Reset list view error handled.")

    def checkDisplayedFiles(self, model):
        actual_files = [model.filePath(model.index(row, 0)) for row in range(model.rowCount())]
        logger.info("Files currently displayed in the view: " + str(actual_files))
    
    @pyqtSlot(int, int)
    def update_indexing_progress(self, processed_files, total_files):
        # Mettre à jour la barre de progression ou afficher un message de statut
        if total_files > 0:
            progress_percent = int(processed_files / total_files * 100)
            QMetaObject.invokeMethod(self, "update_status_bar", Qt.QueuedConnection,
                             Q_ARG(str, f"Indexing in progress... {progress_percent}%"))
        else:
            progress_percent = 0
    @pyqtSlot(str)
    def update_status_bar(self, message):
        self.statusBar().showMessage(message)
    @pyqtSlot()
    def on_indexing_finished(self):
        """Slot appelé lorsque l'indexation est terminée."""
        logger.info("FileViewer / Indexing finished successfully.")
        # Ajoutez ici le code que vous souhaitez exécuter à la fin de l'indexation
        #self.update_file_list_view(WATCHED_DIR)  # Mettre à jour la vue de liste après l'indexation
        # ... (autres actions)
        # Activez la recherche lorsque l'indexation est terminée
        self.search_button.setEnabled(True)
    @pyqtSlot(str)
    def on_indexing_error(self, error_message):
        """Slot appelé en cas d'erreur lors de l'indexation."""
        logger.error(f"FileViewer / Indexing error: {error_message}")
        # Gérez l'erreur ici (par exemple, affichez un message à l'utilisateur)
        self.is_indexing = False  # Reset the flag in case of an error
        QMessageBox.critical(self, "Erreur d'indexation", f"Une erreur est survenue lors de l'indexation : {error_message}")

    def get_proxy_model_for_source_model(self, source_model):
        logger.debug("Function get_proxy_model_for_source_model start")
        if source_model == self.file_list_model:
            logger.info("Found matching proxy model for file list model.")
            return self.file_list_proxy
        elif source_model == self.tree_model:
            logger.info("Found matching proxy model for tree model.")
            return self.tree_proxy
        logger.warning("No matching proxy model found for given source model.")
        logger.debug("Function get_proxy_model_for_source_model end")
        return None

    def get_view_for_model(self, model):
        logger.debug("Function get_view_for_model start")
        if model == self.file_list_model:
            logger.info("Found matching view for file list model.")
            return self.file_list
        elif model == self.tree_model:
            logger.info("Found matching view for tree model.")
            return self.tree_view
        logger.debug("Function get_view_for_model end")
        return None

    def on_root_path_changed(self, new_path):
        #self.current_folder = new_path  
        logger.debug("Function on_root_path_changed start with new path: {}".format(new_path))
        self.file_list_proxy.setSourceModel(self.file_list_model)  # Re-attach source model if needed
        self.update_file_list_view()
        logger.debug("Function on_root_path_changed end")

    def update_directory_label(self, text=None):
        logger.info("Function update_directory_label start")
        if text is None:  
            text = f"Current folder: {self.current_folder}" 
        logger.debug(f"Attempting to update directory label with text: {text}") 
        if hasattr(self, 'directory_label'):
            self.directory_label.setText(text)
            logger.info(f"Updated directory label: {text}")
        else:
            logger.error("Error: Directory label is not initialized.")
            # Optionally initialize the label here if needed
            self.directory_label = QLabel(text)
            logger.info(f"Initialized and updated directory label: {text}")
        logger.info("Function update_directory_label end")

    def onRecursiveSearchToggled(self, checked):
        logger.debug("Function onRecursiveSearchToggled start")
        self.file_list_proxy.setRecursiveSearchEnabled(checked)
        logger.info(f"Recursive search {'enabled' if checked else 'disabled'}.")
        logger.debug("Function onRecursiveSearchToggled end")
    
    def setup_proxy_tree_model(self):
        logger.debug("Starting setup of the proxy model for the tree/Function setup_proxy_tree_model")
        if self.tree_model is None:
            logger.error("Error: The source model (tree_model) is not initialized.")
            return
        if hasattr(self, 'tree_proxy_initialized') and self.tree_proxy_initialized:
            logger.debug("Proxy model for the tree is already set up.")
            return
        self.tree_proxy = TreeProxyModel() 
        self.tree_proxy.setSourceModel(self.tree_model)
        logger.info("Source model assigned to the tree proxy.")
        tree_filters = ['*']  # Specific filters for the tree
        self.tree_proxy.setFilterWildcard(' '.join(tree_filters))
        self.tree_proxy.invalidateFilter()
        logger.info("Filter of the tree proxy model invalidated to reflect the changes.")
        self.tree_view.setModel(self.tree_proxy)
        self.tree_proxy_initialized = True
        logger.debug("End of the setup of the proxy model for the tree/Function setup_proxy_tree_model")

    def setup_file_list_proxy(self):
        logger.debug("Starting setup of the proxy model for the file list./Function setup_file_list_proxy")
        if self.file_list_model is None:
            logger.error("Error: The source model (file_list_model) is not initialized.")
            return
        try:
            self.file_list_proxy = FileListProxyModel() 
            self.file_list_proxy.setSourceModel(self.file_list_model)
            logger.info("Source model assigned to the file list proxy.")
            # Configuring filters
            file_list_filters = ['*.dxf', '*.jpg', '*.jpeg', '*.png', '*.bmp']  # Filters for the file list view
            self.file_list_proxy.setFilterWildcard(' '.join(file_list_filters))
            logger.info(f"Filters applied for the file list proxy model: {' '.join(file_list_filters)}")
            # Applying filters and updating the view
            self.file_list_proxy.invalidateFilter()
            logger.info("Filter of the file list proxy model invalidated to reflect the changes.")
            self.file_list.setModel(self.file_list_proxy)
        except Exception as e:
            logger.error(f"Error during the configuration of the proxy model: {str(e)}")
        logger.debug("End of the setup of the proxy model for the file list./Function setup_file_list_proxy")

    def setup_ui(self):
        logger.debug("Starting the setup_ui function in FileViewer")
        self.setupTreeModelAndView()
        self.setupListModelAndView()
        self.central_layout = QVBoxLayout()
        self.main_splitter = QSplitter(Qt.Horizontal)
        self.setCentralWidget(self.main_splitter)
        self.tree_dock_widget.setFeatures(QDockWidget.NoDockWidgetFeatures)
        self.setup_central_frame()  
        self.setup_preview_dock_widget()
        self.main_splitter.setSizes([445])

        logger.info("Connecting signals and slots:")
        self.central_layout.addWidget(self.file_list)  
        self.directory_label = QLabel("Current folder path:") 
        logger.debug("[Init] Directory label initialized") 
        self.central_layout.addWidget(self.directory_label) 
        logger.debug("[Layout] Directory label added to layout")
        self.file_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.file_list.customContextMenuRequested.connect(self.open_context_menu)   
        logger.debug("End of the setup_ui function in FileViewer")

    def handle_root_path_changed(self, new_root_path):
        logger.debug("Starting the handle_root_path_changed function")
        logger.info(f"New root path: {new_root_path}")
        current_root_path = self.file_list_model.rootPath()
        if new_root_path != current_root_path:
            self.current_folder = new_root_path  # Mettre à jour current_folder 
            self.update_file_list_view(new_root_path) 
        logger.debug("End of the handle_root_path_changed function")

    def setupTreeModelAndView(self):
        logger.debug("Starting the setupTreeModelAndView function")
        self.tree_model = CustomFileSystemModelForTree()
        self.tree_model.setRootPath("")
        self.tree_proxy = TreeProxyModel()
        self.tree_proxy.setSourceModel(self.tree_model)
        self.tree_view.setModel(self.tree_proxy)
        logger.debug("End of the setupTreeModelAndView function")

    def setupListModelAndView(self):
        logger.debug("Starting the setupListModelAndView function")
        self.file_list_model.setRootPath(self.current_folder)
        logger.info(f"Initial root path for the file list model: (empty)")
        # Configuring the proxy model
        self.file_list_proxy = FileListProxyModel()
        self.file_list_proxy.setSourceModel(self.file_list_model)
        logger.info("Proxy model configured with the file list model.")
        self.file_list_proxy.setFilterKeyColumn(0)
        self.file_list_proxy.setFilterWildcard("*.dxf *.jpg *.jpeg *.png *.bmp")
        # Associating the proxy model with the view
        self.file_list.setModel(self.file_list_proxy)
        logger.info("File list view configured with the proxy model.")
        self.file_list.setRootIndex(QModelIndex())
        logger.info("File list view configured to display empty content.")
        #self.showModelInfo()
        logger.info("Triggered showModelInfo:")
        logger.debug("End of the setupListModelAndView function")

    def setup_tree_dock_widget(self):
        logger.debug("Start of setup_tree_dock_widget function")
        self.tree_dock_widget = QDockWidget("Tree")
        self.tree_view = QTreeView(self.tree_dock_widget)
        self.tree_dock_widget.setWidget(self.tree_view)
        self.tree_dock_widget.setMinimumWidth(455)
        self.tree_view.clicked.connect(self.on_tree_clicked)
        logger.info("Connection: tree_view.clicked -> on_tree_clicked")
        #self.tree_model = CustomFileSystemModelForTree() 
        self.tree_model.setRootPath("")
        root_path = self.tree_model.rootPath()
        logger.info(f"Tree root path: {root_path}")

        self.setup_proxy_tree_model()
        header = self.tree_view.header()    
        header.setSectionHidden(1, True) 
        header.setSectionHidden(2, True) 
        header.setSectionHidden(3, True) 
        self.tree_view.setHeaderHidden(False)  # Hide header if desired
        self.addDockWidget(Qt.LeftDockWidgetArea, self.tree_dock_widget)
        logger.debug("End of setup_tree_dock_widget function")

    def setup_preview_dock_widget(self):
        logger.debug("Start of setup_preview_dock_widget function")
        self.preview_dock_widget = QDockWidget("Preview")
        self.preview_dock_widget.setFixedHeight(455)
        self.preview_dock_widget.setMinimumWidth(455)

        self.preview_scene = QGraphicsScene()
        self.preview_view = CustomGraphicsView(self.preview_scene)  
        self.preview_dock_widget.setWidget(self.preview_view)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.preview_dock_widget)

        # Barre d'outils pour le zoom et les modes de mesure
        self.toolbar = QToolBar("Zoom and Measure Tools", self)
        self.addToolBar(Qt.TopToolBarArea, self.toolbar)
        
        zoom_in_action = QAction("Zoom In", self)
        zoom_in_action.triggered.connect(self.preview_view.zoom_in)
        self.toolbar.addAction(zoom_in_action)

        zoom_out_action = QAction("Zoom Out", self)
        zoom_out_action.triggered.connect(self.preview_view.zoom_out)
        self.toolbar.addAction(zoom_out_action)
        # Séparateur visuel
        self.toolbar.addSeparator()
        # Actions pour les modes de mesure
        action_measure_distance = QAction(QIcon("iconDistance.png"), "Distance Mode", self)
        action_measure_distance.triggered.connect(lambda: self.preview_view.set_measure_mode(MeasureMode.DISTANCE))
        self.toolbar.addAction(action_measure_distance)

        action_measure_angle = QAction(QIcon("iconAngle.png"), "Angle Mode", self)
        action_measure_angle.triggered.connect(lambda: self.preview_view.set_measure_mode(MeasureMode.ANGLE))
        self.toolbar.addAction(action_measure_angle)
        # Configuration de la vue et de la scène
        self.canvas = CustomGraphicsView()
        self.preview_view.setScene(self.preview_scene)
        self.preview_view.fitInView(self.preview_scene.sceneRect(), Qt.KeepAspectRatio)

        self.scene = QGraphicsScene()
        self.canvas.setScene(self.scene)

        logger.debug("End of setup_preview_dock_widget function")
    def setup_central_frame(self):
        logger.debug("Start of setup_central_frame function")
        self.central_frame = QWidget()
        self.central_layout = QVBoxLayout(self.central_frame)

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

        self.search_box = QLineEdit()
        self.search_box.setPlaceholderText("Search files...")
        self.central_layout.addWidget(self.search_box)

        self.search_criteria_type = QComboBox(self)
        self.search_criteria_type.addItems(["Filename", "Creation Date", "Modification Date"])
        self.central_layout.addWidget(self.search_criteria_type)

        self.start_date = QDateEdit(self)
        self.start_date.setCalendarPopup(True)
        self.start_date.setDate(QDate.currentDate())
        self.central_layout.addWidget(self.start_date)

        self.end_date = QDateEdit(self)
        self.end_date.setCalendarPopup(True)
        self.end_date.setDate(QDate.currentDate())
        self.central_layout.addWidget(self.end_date)

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

        #self.file_list = QListView()
        self.file_list.setIconSize(QSize(160, 120))
        logger.info(f"New icon size for QListView: {self.file_list.iconSize()}")
        self.file_list.setGridSize(QSize(175, 135))
        self.file_list.setModel(self.file_list_proxy )  # Use the file list proxy model here
        self.file_list.setViewMode(QListView.IconMode)
        #self.file_list_model.setRootPath(QDir.rootPath()) 
        root_index_model_files = self.file_list_proxy.sourceModel().index(0, 0) 
        proxy_root_index = self.file_list_proxy.mapFromSource(root_index_model_files)
        self.file_list.setRootIndex(proxy_root_index)
        
        self.central_layout.addWidget(self.file_list)
        self.main_splitter.addWidget(self.central_frame)

        logger.debug("End of setup_central_frame function")
    def on_signal_emitted(self, index):
        logger.debug(f"Start on_signal_emitted (self.update_file_list(index))")
        logger.debug(f"Signal emitted. Index: {index}")
        if index.isValid():
            folder_path = self.file_list_model.filePath(index)
            self.current_folder = folder_path  
            self.update_file_list_view(folder_path)
        else:
            logger.error("Invalid index received in on_signal_emitted")
        logger.debug(f"End on_signal_emitted")
    def on_folder_double_clicked(self, index):
        logger.debug("Start on_folder_double_clicked")
        if not index.isValid():
            logger.error("The index is not valid.")
            QMessageBox.warning(self, "Error", "Invalid index selected.")
            return
        # Détermination du modèle de base et de l'indice source
        if isinstance(index.model(), QSortFilterProxyModel):
            source_index = index.model().mapToSource(index)
            if index.model() == self.file_list_proxy:
                base_model = self.file_list_model
                logger.info("Index from file list proxy")
            elif index.model() == self.tree_proxy:
                base_model = self.tree_model
                logger.info("Index from tree proxy")
            else:
                QMessageBox.warning(self, "Error", "Unknown model type")
                return
        else:
            source_index = index
            base_model = index.model()
        folder_path = base_model.filePath(source_index)
        if os.path.isdir(folder_path) and os.access(folder_path, os.R_OK):
            logger.info(f"Selected folder path: {folder_path}")
            self.current_folder = folder_path  
            logger.debug(f"[on_folder_double_clicked] Current folder updated to: {self.current_folder}")  
            self.file_list_model.setRootPath(folder_path)
            logger.info(f"Root path of the file list model updated: {folder_path}")
            # Mise à jour de l'indice root de la vue
            updated_root_index = self.file_list_model.indexFromPath(folder_path)
            if hasattr(self, 'file_list_proxy'):
                proxy_root_index = self.file_list_proxy.mapFromSource(updated_root_index)
                if proxy_root_index.isValid():
                   self.file_list.setRootIndex(proxy_root_index)
                else:
                    logger.error("Invalid proxy root index after mapping.")
                    QMessageBox.warning(None, "Mapping Error", "Invalid proxy root index after mapping.")
            else:
                self.file_list.setRootIndex(updated_root_index)
            logger.info("List view updated to display the content of the selected folder.")
            self.update_directory_label(f"Current folder path: {self.current_folder}")  
        else:
            msg = "The selected path is not a directory." if not os.path.isdir(folder_path) else "The directory is not accessible."
            logger.error(f"{msg}: {folder_path}")
            QMessageBox.warning(self, "Error", msg)
        logger.debug("End on_folder_double_clicked")

    def on_tree_clicked(self, index):
        logger.debug("Start of on_tree_clicked")
        
        if self._in_search_mode:
            logger.info("Exiting search mode after tree click.")
            self._in_search_mode = False
            self.file_list.setModel(self.file_list_proxy)
            self.search_box.clear()
        logger.debug(f"Start processing index: {index}")
        if not index.isValid():
            logger.error("The provided index is invalid.")
            return

        # Map the proxy index to the source index
        source_index = self.tree_proxy.mapToSource(index) if isinstance(index.model(), QSortFilterProxyModel) else index
        if not source_index.isValid():
            logger.error("Index mapping failed. Attempting reset.")
            return

        # Get the directory path
        try:
            directory_path = self.tree_model.filePath(source_index)
            self.current_folder = directory_path  
        except IndexError:
            logger.error(f"Error getting directory path from index: {source_index}")
            return
        assert os.path.isdir(directory_path), f"Invalid directory path: {directory_path}"
        logger.debug(f"Directory path: {directory_path}")

        #Only update if the directory is different
        if directory_path == self.file_list_model.rootPath():
            logger.debug("Selected directory is already the active directory. Skipping update.")
            return

        # Update the file list model 
        logger.info(f"Updating file list model for directory: {directory_path}")
        self.file_list_model.setRootPath(directory_path)

        # Directly update the list view after setting the root path
        list_root_index = self.file_list_model.indexFromPath(directory_path)
        self.update_view_index(self.file_list_model, self.file_list, list_root_index)

        self.update_directory_label(f"Current folder path: {self.current_folder}")  
        logger.info(f"[on_tree_clicked] Current folder path updated to: {directory_path}")
        logger.debug("End of on_tree_clicked")

    def update_view_index(self, model, view, root_index):
        """Updates the view's index based on the model and root index."""
        if hasattr(self, 'file_list_proxy') and model is self.file_list_model:
            index_proxy = self.file_list_proxy.mapFromSource(root_index)
            if index_proxy.isValid():
                view.setRootIndex(index_proxy)
            else:
                logger.error("Root proxy index is not valid after mapping.")
        else:
            view.setRootIndex(root_index)
    def load_folder(self):
        """Loads a folder selected by the user."""
        logger.info("Start of load_folder function")
        folder_path = QFileDialog.getExistingDirectory(self, "Choose a folder")
        if folder_path:
            try:
                # Check if the path is accessible (read and write)
                if not os.access(folder_path, os.R_OK | os.W_OK):
                    raise OSError("The selected folder is not accessible for read or write.")

                logger.info(f"Selected folder path: {folder_path}")
                self.current_folder = folder_path
                self.update_directory_label(f"Current folder path: {self.current_folder}")
                logger.debug(f"[load_folder] Current folder updated to: {self.current_folder}")

                # Update path in the list model
                self.file_list_model.setRootPath(self.current_folder)

                # Update list view index
                list_root_index = self.file_list_model.indexFromPath(self.current_folder)
                self.update_view_index(self.file_list_model, self.file_list, list_root_index)

                self.folderChanged.emit(folder_path)

            except OSError as e:
                QMessageBox.warning(self, "Error", str(e))
                logger.error(f"Error accessing folder: {e}")
        else:
            logger.warning("No folder selected.")
        logger.info("End of load_folder function")

    def navigate_up(self):
        logger.info("Start navigate_up")
        logger.debug("Attempting to navigate up one level in the file tree.")
        current_index = self.file_list.rootIndex()
        index_parent = self.file_list_proxy.mapToSource(current_index).parent()  # Use the correct proxy model here
        if index_parent.isValid():
            self.file_list.setRootIndex(self.file_list_proxy.mapFromSource(index_parent))
            folder_path = self.file_list_model.filePath(index_parent)
            self.current_folder = folder_path  
            self.update_directory_label(f"Current folder path: {self.current_folder}") 
            logger.info(f"Current directory updated to: {self.current_folder}")  
        else:
            logger.warning("Unable to navigate up, no valid parent found.")
        logger.info("End navigate_up")

    def on_tree_selection_changed(self, selected, deselected):
        logger.debug("Function on_tree_selection_changed start")
        index = self.tree_view.currentIndex()
        logger.debug(f"Current index in the tree: {index}")
        self.navigate_folder(index)
        logger.debug("Function on_tree_selection_changed end")

    def on_list_selection_changed(self, selected, deselected):
        logger.debug("Function on_list_selection_changed start")
        index_source = self.file_list.currentIndex()
        logger.debug(f"Current source index in the list: {index_source}")
        self.navigate_folder(index_source)
        logger.debug("Function on_list_selection_changed end")

    def navigate_folder(self, index):
        logger.debug("Function navigate_folder start")
        if not index.isValid():
            logger.error("Error: Invalid index provided for navigation.")
            return
        folder_path = self.file_list_model.filePath(index)
        if not os.path.exists(folder_path):
            logger.error(f"Error: Folder path does not exist - {folder_path}")
            return
        self.current_folder = folder_path  # Mise à jour du dossier courant  
        self.file_list_model.setRootPath(self.current_folder)
        logger.info(f"Updated root path of the file list model to: {self.current_folder}")
        self.update_file_list_view(folder_path)
        self.update_directory_label(f"Current folder: {self.current_folder}")
        logger.debug("Function navigate_folder end")

    def change_list_view(self):
        logger.info("Start change_list_view")
        logger.info("Switching view to List View")
        self.file_list.setViewMode(QListView.ListMode)
        logger.debug("File view switched to list.")
        logger.info("End change_list_view")

    def change_thumbnail_view(self):
        logger.info("Start change_thumbnail_view")
        logger.info("Switching view to Thumbnail View")
        self.file_list.setViewMode(QListView.IconMode)
        logger.debug("File view switched to thumbnails.")
        logger.info("End change_thumbnail_view")

    def setupMenu(self):
        logger.info("Start setupMenu")
        logger.debug("Setting up the menu bar")
        menuBar = self.menuBar()
        fileMenu = menuBar.addMenu("&File")
        openAction = QAction("&Open", self)
        openAction.setShortcut("Ctrl+O")
        openAction.triggered.connect(self.load_folder)  # Assuming `load_folder` is your method to open a folder
        exitAction = QAction("&Exit", self)
        exitAction.setShortcut("Ctrl+Q")
        exitAction.triggered.connect(self.close)
        fileMenu.addAction(openAction)
        fileMenu.addSeparator()
        fileMenu.addAction(exitAction)
        viewMenu = menuBar.addMenu("&View")
        listViewAction = QAction("&List View", self)
        listViewAction.triggered.connect(self.change_list_view)
        viewMenu.addAction(listViewAction)
        thumbnailViewAction = QAction("&Thumbnail View", self)
        thumbnailViewAction.triggered.connect(self.change_thumbnail_view)
        viewMenu.addAction(thumbnailViewAction)
        logger.debug("Menu bar configured with File and View menus.")
        logger.info("End setupMenu")

    def setupToolbar(self):
        logger.info("Start setupToolbar")
        logger.debug("Setting up the toolbar")
        toolbar = self.addToolBar("Main Toolbar")
        returnAction = QAction(QIcon("iconReturn.png"), "Return", self) 
        toolbar.setIconSize(QSize(24, 24))
        returnAction.triggered.connect(self.navigate_up)
        toolbar.addAction(returnAction)
        logger.debug("Toolbar configured")
        logger.info("End setupToolbar")

    def closeEvent(self, event):
        logger.info("Function closeEvent start")
        logger.info("Closing application, stopping threads and observer.")
        try:
            if hasattr(self, 'observer') and self.observer:
                self.observer.stop()
                self.observer.join()
            if hasattr(self, 'searchThread') and self.searchThread.isRunning():
                self.SearchWorker.running = False  # Assurez-vous que SearchWorker peut être arrêté proprement
                #self.SearchWorker.stop() 
                self.searchThread.quit()
                self.searchThread.wait()  # Wait up to 0 seconds for the thread to cleanly finish
        except Exception as e:
            logger.error(f"Error during closing: {e}")
        finally:
            super().closeEvent(event)
        logger.info("Function closeEvent end")

    def display_file(self, file):
        logger.debug(f"Start of display_file function with file: {file}")
        try:
            if os.path.isdir(file):
                self.display_folder(file)
            elif file.lower().endswith(('.png', '.jpg', '.jpeg')):
                self.display_image(file)
            elif file.lower().endswith('.dxf'):
                self.display_dxf(file)
            else:
                self.display_default_message()
        except Exception as e:
            logger.error(f"Error displaying file {file}: {e}")
            self.display_default_message()
        logger.info(f"End of display_file function")

    def display_folder(self, folder):
        logger.info(f" Function display_folder / Displaying folder: {folder}")
        self.current_folder = folder  # Mise à jour du dossier courant  
        self.update_directory_label(f"Current folder: {self.current_folder}")  
        # Ajoutez le code nécessaire pour afficher le contenu du dossier si nécessaire  

    def display_selected_file(self, index):
        """Affiche le fichier sélectionné dans le dock de prévisualisation."""
        logger.info("Start of display_selected_file")
        logger.debug(f"Function display_selected_file with index: {index}")
        logger.debug(f"État du thread avant la sélection: {QThread.currentThread()}") 
        try:

            # Déterminer le modèle à utiliser en fonction du mode de recherche
            if self._in_search_mode:
                model = self.search_results_proxy_model
                logger.debug("Mode recherche activé. Utilisation de search_results_proxy_model.")
            else:
                model = self.file_list_proxy 
                logger.debug("Mode recherche désactivé. Utilisation de file_list_proxy.")
            
            if not model:
                logger.error("Model is not initialized")
                return

            # Convertir l'index du proxy en index du modèle source
            source_index = model.mapToSource(index)

            if not source_index.isValid():
                logger.error("Invalid index for file selection")
                return

            file_path = self.file_list_model.filePath(source_index)
            if not file_path:
                logger.error("File path could not be retrieved")
                return

            logger.debug(f"Ready to open: {file_path}")
            logger.info(f"Displaying file for index {index}: {file_path}")
            logger.debug(f"FileViewer.display_selected_file() - Appel de display_file pour {file_path}")
            self.display_file(file_path)
            self.fileSelected.emit(file_path)
            logger.info(f"Emitted fileSelected signal for: {file_path}")
        except Exception as e:
            logger.error(f"Error in display_selected_file: {e}")
        finally:
            logger.info("End of display_selected_file")

    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")
            #SUITE DU SCRIPT ...