ameliore la gestion d'affichage des mesure et la gestion d'exeception :
class MeasureMode(Enum):
    NONE = 0
    DISTANCE = 1
    ANGLE = 2
    AREA = 3

class CustomGraphicsView(QGraphicsView):
    resized = pyqtSignal()

    def __init__(self, parent=None, zoom_in_factor=1.15, unit_scale=1.0, unit_name="unités"):
        super().__init__(parent)
        self.setDragMode(QGraphicsView.ScrollHandDrag)
        self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        self.zoom_in_factor = zoom_in_factor
        self.zoom_out_factor = 1 / zoom_in_factor
        self._initial_view_fitted = False
        self.start_point = None
        self.temp_line = None
        self.measurement_label = QLabel(self)
        self.measurement_label.setStyleSheet("background-color: rgba(255, 255, 255, 200);")
        self.measurement_label.hide()
        self.setMouseTracking(True)
        self.selected_line1 = None  # Pour la mesure d'angle
        self.spatial_index = {}
        self.previously_highlighted = None
        self._adjustment_in_progress = False
        self._signal_connected = False
        self.measure_mode = MeasureMode.NONE
        self.unit_scale = unit_scale
        self.unit_name = unit_name
        self.measure_items = []  # Liste pour stocker les éléments de mesure
        self.original_pens = {}

    def set_measure_mode(self, mode):
        self.measure_mode = mode
        #self.measurement_label.hide()  # Cacher l'étiquette lors du changement de mode
        self.update_drag_mode()
        self.clear_measurements()
    def clear_measurements(self):
        """Supprime tous les éléments de mesure de la scène."""
        try:
            logger.debug("Clearing all measurements")
            for item in list(self.measure_items):
                if self.scene() and item in self.scene().items():# Vérifier si la scène existe avant de supprimer l'élément
                    self.scene().removeItem(item)
                    # Supprimer les flèches associées
                    if isinstance(item, MeasuredLine):
                        if item.start_control_point:
                            self.scene().removeItem(item.start_control_point)
                        if item.end_control_point:
                            self.scene().removeItem(item.end_control_point)
            self.measure_items.clear()
            self.measurement_label.hide()
        except Exception as e:
            logger.error(f"Error in clear_measurements: {e}", exc_info=True)
    
    def update_drag_mode(self):
        if self.measure_mode == MeasureMode.NONE:
            self.setDragMode(QGraphicsView.ScrollHandDrag)
        else:
            self.setDragMode(QGraphicsView.NoDrag)

    def is_circle(self, item):
        """Vérifie si un QGraphicsEllipseItem est un cercle."""
        try:
            rect = item.rect()
            return rect.width() == rect.height()
        except Exception as e:
            logger.error(f"Error in is_circle: {e}", exc_info=True)
            return False

    def measure_circle_radius(self, circle_item, scene_pos):
        """Mesure le rayon du cercle et affiche la mesure."""
        try:
            rect = circle_item.rect()
            center = rect.center()
            radius = rect.width() / 2

            real_radius = radius * self.unit_scale
            self.display_radius(real_radius, scene_pos)
        except Exception as e:
            logger.error(f"Error in measure_circle_radius: {e}", exc_info=True)

    def display_radius(self, radius, position):
        """Affiche le rayon à la position spécifiée."""
        try:
            self.measurement_label.setText(f"Rayon: {radius:.2f} {self.unit_name}")
            self.measurement_label.adjustSize()
            # Convertir la position de la scène en position de la vue
            screen_position = self.mapFromScene(position)
            # Assurez-vous que l'étiquette reste dans les limites de la vue
            view_rect = self.viewport().rect()
            label_width = self.measurement_label.width()
            label_height = self.measurement_label.height()
            # Ajustez la position si l'étiquette dépasse les limites de la vue
            if screen_position.x() + label_width > view_rect.right():
                screen_position.setX(view_rect.right() - label_width)
            if screen_position.y() + label_height > view_rect.bottom():
                screen_position.setY(view_rect.bottom() - label_height)
            if screen_position.x() < view_rect.left():
                screen_position.setX(view_rect.left())
            if screen_position.y() < view_rect.top():
                screen_position.setY(view_rect.top())

            # Déplacer l'étiquette à la position ajustée
            adjusted_position = self.adjust_label_position(screen_position)
            self.measurement_label.move(adjusted_position)
            self.measurement_label.show()
        except Exception as e:
            logger.error(f"Error in display_radius: {e}", exc_info=True)

    def display_measurement(self, text, item):
        """Affiche la mesure à côté de l'élément."""
        try:
            bounding_rect = item.boundingRect()
            center = bounding_rect.center()
            screen_position = self.mapFromScene(center)
            self.measurement_label.setText(text)
            self.measurement_label.adjustSize()
            self.measurement_label.move(screen_position)
            self.measurement_label.show()
        except Exception as e:
            logger.error(f"Error in display_measurement: {e}", exc_info=True)

    def add_control_point(self, position, connected_line, is_start_point):
        """Ajoute un point de contrôle interactif à la position donnée."""
        try:
            logger.debug(f"Adding control point at {position} to line {connected_line}")
            control_point = ControlPoint(position, connected_line)
            if self.scene():
                self.scene().addItem(control_point)
            if is_start_point:
                connected_line.start_control_point = control_point
                logger.debug("Assigned as start_control_point")
            else:
                connected_line.end_control_point = control_point
                logger.debug("Assigned as end_control_point")

        except Exception as e:
            logger.error(f"Error in add_control_point: {e}", exc_info=True)

    def add_arrow(self, start, end):
        """Ajoute une flèche à chaque extrémité d'une ligne."""
        try:
            line_vector = end - start
            length = math.sqrt(line_vector.x() ** 2 + line_vector.y() ** 2)
            if length == 0:
                return
            # Normaliser le vecteur de ligne
            line_vector /= length
            # Obtenir le facteur d'échelle actuel
            current_scale = self.transform().m11()
            desired_arrow_size = 10  # Taille en pixels à l'écran
            arrow_size = desired_arrow_size / current_scale
            perp_vector = QPointF(-line_vector.y(), line_vector.x()) * arrow_size
            # Flèche au début (pointant vers l'intérieur)
            arrow_p1 = start + line_vector * arrow_size + perp_vector
            arrow_p2 = start + line_vector * arrow_size - perp_vector
            arrow_head = QPolygonF([start, arrow_p1, arrow_p2])
            arrow_item = QGraphicsPolygonItem(arrow_head)

            # Définir la plume cosmétique pour la flèche
            arrow_pen = QPen(QColor("blue"))
            arrow_pen.setCosmetic(True)
            arrow_item.setPen(arrow_pen)
            arrow_item.setBrush(QBrush(QColor("blue")))
            self.scene().addItem(arrow_item)
            self.measure_items.append(arrow_item)
            # Flèche à la fin (pointant vers l'extérieur)
            arrow_p1 = end - line_vector * arrow_size + perp_vector
            arrow_p2 = end - line_vector * arrow_size - perp_vector
            arrow_head = QPolygonF([end, arrow_p1, arrow_p2])
            arrow_item = QGraphicsPolygonItem(arrow_head)

            # Définir la plume cosmétique pour la flèche
            arrow_pen = QPen(QColor("blue"))
            arrow_pen.setCosmetic(True)
            arrow_item.setPen(arrow_pen)
            arrow_item.setBrush(QBrush(QColor("blue")))
            self.scene().addItem(arrow_item)
            self.measure_items.append(arrow_item)
        except Exception as e:
            logger.error(f"Error in add_arrow: {e}", exc_info=True)

    def mousePressEvent(self, event):
        try:
            if self.scene() is None:
                return
            scene_pos = self.mapToScene(event.pos())
            closest_entity, closest_point = self.find_closest_entity(scene_pos)
            self.clear_measurements()
            if event.button() == Qt.LeftButton:
                if self.measure_mode == MeasureMode.DISTANCE:
                    self.start_point = closest_point if closest_entity else scene_pos
                    self.measurement_label.hide()
                    self.temp_line = QGraphicsLineItem(QLineF(self.start_point, self.start_point))
                    pen = QPen(QColor("blue"), 2, Qt.DashLine)
                    pen.setCosmetic(True) 
                    self.temp_line.setPen(pen)
                    self.scene().addItem(self.temp_line)
                    return
                elif self.measure_mode == MeasureMode.AREA and isinstance(closest_entity, QGraphicsPolygonItem):
                    self.measure_polygon(closest_entity)
                    return
            elif event.button() == Qt.RightButton and isinstance(closest_entity, QGraphicsLineItem):
                self.selected_line1 = closest_entity
                return
        except Exception as e:
            logger.error(f"Error in mousePressEvent: {e}", exc_info=True)
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        try:
            scene_pos = self.mapToScene(event.pos())
            closest_entity, _ = self.find_closest_entity(scene_pos)

            if self.previously_highlighted:
                if self.scene() and self.previously_highlighted in self.scene().items():
                    self.reset_item_appearance(self.previously_highlighted)
                self.previously_highlighted = None

            if closest_entity:
                self.highlight_item(closest_entity)
                self.previously_highlighted = closest_entity

            if self.measure_mode == MeasureMode.DISTANCE and self.start_point and self.temp_line:
                current_point = self.mapToScene(event.pos())
                snap_point = self.find_closest_snap_point(current_point)
                self.temp_line.setLine(QLineF(self.start_point, snap_point))
                dx = snap_point.x() - self.start_point.x()
                dy = snap_point.y() - self.start_point.y()
                distance = math.sqrt(dx * dx + dy * dy)
                self.update_measurement_label(distance, self.start_point, snap_point)
                return
            super().mouseMoveEvent(event)
        except Exception as e:
            logger.error(f"Error in mouseMoveEvent: {e}", exc_info=True)

    def mouseReleaseEvent(self, event):
        try:
            if self.measure_mode == MeasureMode.DISTANCE and self.start_point and self.temp_line:
                end_point = self.mapToScene(event.pos())
                closest_entity, closest_point = self.find_closest_entity(end_point)
                end_point = closest_point if closest_entity else end_point

                self.scene().removeItem(self.temp_line)
                pen = QPen(QColor("blue"), 2)
                pen.setCosmetic(True)
                final_line = MeasuredLine(QLineF(self.start_point, end_point))
                final_line.setPen(pen) 
                self.scene().addItem(final_line)
                self.add_arrow(self.start_point, end_point)

                logger.debug(f"Avant add_control_point (start): {final_line}") # Vérifier l'état de final_line
                self.add_control_point(self.start_point, final_line, is_start_point=True)
                logger.debug(f"Après add_control_point (start): {final_line}")  # Vérifier l'état de final_line
                logger.debug(f"Avant add_control_point (end): {final_line}")  # Vérifier l'état de final_line
                self.add_control_point(end_point, final_line, is_start_point=False)
                logger.debug(f"Après add_control_point (end): {final_line}")  # Vérifier l'état de final_line

                dx = end_point.x() - self.start_point.x()
                dy = end_point.y() - self.start_point.y()
                distance = math.sqrt(dx * dx + dy * dy)
                self.update_measurement_label(distance, self.start_point, end_point)

                # Ajouter les éléments à la liste de mesure
                self.measure_items.extend([final_line, final_line.start_control_point, final_line.end_control_point])

                self.start_point = None
                self.temp_line = None
            elif event.button() == Qt.RightButton and self.selected_line1:
                scene_pos = self.mapToScene(event.pos())
                closest_line, _ = self.find_closest_entity(scene_pos)
                if closest_line and closest_line != self.selected_line1:
                    angle = self.calculate_angle_between_lines(self.selected_line1.line(), closest_line.line())
                    self.display_angle(angle, event.pos())
                self.selected_line1 = None
            super().mouseReleaseEvent(event)
        except Exception as e:
            logger.error(f"Error in mouseReleaseEvent: {e}", exc_info=True)

    

    def highlight_item(self, item):
        try:
            if item not in self.original_pens:
                self.original_pens[item] = item.pen()
            pen = QPen(QColor("orange"), 2)
            pen.setCosmetic(True)  # Stylet cosmétique pour épaisseur constante à l'écran
            item.setPen(pen)
        except Exception as e:
            logger.error(f"Error in highlight_item: {e}", exc_info=True)

    def reset_item_appearance(self, item):
        try:
            if item in self.original_pens:
                item.setPen(self.original_pens.pop(item))
        except Exception as e:
            logger.error(f"Error in reset_item_appearance: {e}", exc_info=True)

    def build_spatial_index(self):
        try:
            self.spatial_index = {}
            if self.scene():
                for item in self.scene().items():
                    bounding_box = item.mapToScene(item.boundingRect()).boundingRect()
                    key = (int(math.floor(bounding_box.x() / 100)), int(math.floor(bounding_box.y() / 100)))
                    self.spatial_index.setdefault(key, []).append(item)
        except Exception as e:
            logger.error(f"Error in build_spatial_index: {e}", exc_info=True)

    def find_items_in_area(self, area):
        try:
            # Calculer les clés pour les coins supérieur gauche et inférieur droit
            top_left_key = (int(area.left() // 100), int(area.top() // 100))
            bottom_right_key = (int(area.right() // 100), int(area.bottom() // 100))
            # Assurer que les plages de clés sont correctes
            x_start = min(top_left_key[0], bottom_right_key[0])
            x_end = max(top_left_key[0], bottom_right_key[0])
            y_start = min(top_left_key[1], bottom_right_key[1])
            y_end = max(top_left_key[1], bottom_right_key[1])
            # Collecter toutes les clés dans la plage
            keys = []
            for x in range(x_start, x_end + 1):
                for y in range(y_start, y_end + 1):
                    keys.append((x, y))
            # Récupérer les éléments pour chaque clé
            items = []
            for key in keys:
                items.extend(self.spatial_index.get(key, []))
            # Filtrer les éléments qui intersectent réellement la zone
            return [item for item in items if item.sceneBoundingRect().intersects(area)]
        except Exception as e:
            logger.error(f"Error in find_items_in_area: {e}", exc_info=True)
            return []

    def find_closest_entity(self, scene_pos, search_radius=500):
        closest_entity = None
        closest_point = None
        min_distance = float('inf')
        try:
            search_area = QRectF(scene_pos.x() - search_radius, scene_pos.y() - search_radius,
                                 2 * search_radius, 2 * search_radius)
            items_in_area = self.find_items_in_area(search_area)
            for item in items_in_area:
                if isinstance(item, QGraphicsLineItem):
                    line = item.line()
                    point_on_line = self.closest_point_on_line(line, scene_pos)
                    distance = self.euclidean_distance(point_on_line, scene_pos)
                    if distance < min_distance:
                        min_distance = distance
                        closest_entity = item
                        closest_point = point_on_line

                elif isinstance(item, QGraphicsEllipseItem):
                    ellipse = item.rect()
                    if ellipse.width() == ellipse.height():
                        center = ellipse.center()
                        radius = ellipse.width() / 2
                        point_on_circle = self.closest_point_on_circle(center, radius, scene_pos)
                        distance = self.euclidean_distance(point_on_circle, scene_pos)
                        if distance < min_distance:
                            min_distance = distance
                            closest_entity = item
                            closest_point = point_on_circle
                    else:
                        center = ellipse.center()
                        distance = self.euclidean_distance(center, scene_pos)
                        if distance < min_distance:
                            min_distance = distance
                            closest_entity = item
                            closest_point = center
                elif isinstance(item, QGraphicsPolygonItem):
                    polygon = item.polygon()
                    # Utilisez un point moyen ou une méthode plus précise
                    center = polygon.boundingRect().center()
                    distance = self.euclidean_distance(center, scene_pos)
                    if distance < min_distance:
                        min_distance = distance
                        closest_entity = item
                        closest_point = center

                elif isinstance(item, QGraphicsRectItem):
                    rect = item.rect()
                    point_on_rect = self.closest_point_on_rect(rect, scene_pos)
                    distance = self.euclidean_distance(point_on_rect, scene_pos)
                    if distance < min_distance:
                        min_distance = distance
                        closest_entity = item
                        closest_point = point_on_rect

                elif isinstance(item, QGraphicsPathItem):
                    path = item.path()
                    point_on_path = self.closest_point_on_path(path, scene_pos)
                    if point_on_path is not None:
                        distance = self.euclidean_distance(point_on_path, scene_pos)
                        if distance < min_distance:
                            min_distance = distance
                            closest_entity = item
                            closest_point = point_on_path
                # Ajoutez d'autres types d'entités et leurs calculs ici
                else:
                    continue
        except Exception as e:
            logger.error(f"Error in find_closest_entity: {e}", exc_info=True)
        return closest_entity, closest_point

    def find_closest_snap_point(self, scene_pos, search_radius=15):
        closest_point = None
        min_distance = float('inf')
        try:
            search_area = QRectF(scene_pos.x() - search_radius, scene_pos.y() - search_radius,
                                 2 * search_radius, 2 * search_radius)
            items_in_area = self.find_items_in_area(search_area)
            for item in items_in_area:
                if isinstance(item, QGraphicsLineItem):
                    for point in [item.line().p1(), item.line().p2()]:
                        distance = self.euclidean_distance(point, scene_pos)
                        if distance < min_distance:
                            min_distance = distance
                            closest_point = point
                elif isinstance(item, QGraphicsEllipseItem):
                    center = item.rect().center()
                    distance = self.euclidean_distance(center, scene_pos)
                    if distance < min_distance:
                        min_distance = distance
                        closest_point = center
                # Ajouter d'autres types d'entités si nécessaire
            return closest_point if min_distance <= search_radius else scene_pos
        except Exception as e:
            logger.error(f"Error in find_closest_snap_point: {e}", exc_info=True)
        return scene_pos

    def closest_point_on_line(self, line, point):
        try:
            line_vector = line.p2() - line.p1()
            point_vector = point - line.p1()
            line_length_squared = line_vector.x() ** 2 + line_vector.y() ** 2
            if line_length_squared == 0:
                return line.p1()
            t = QPointF.dotProduct(point_vector, line_vector) / line_length_squared
            t = max(0, min(1, t))
            closest = line.p1() + line_vector * t
            return closest
        except Exception as e:
            logger.error(f"Error in closest_point_on_line: {e}", exc_info=True)
            return point  # Retourner le point original en cas d'erreur

    def closest_point_on_circle(self, center, radius, point):
        try:
            direction = point - center
            distance = math.sqrt(direction.x() ** 2 + direction.y() ** 2)
            if distance == 0:
                return center
            return center + direction * (radius / distance)
        except Exception as e:
            logger.error(f"Error in closest_point_on_circle: {e}", exc_info=True)
            return point

    def closest_point_on_rect(self, rect, point):
        try:
            left = rect.left()
            right = rect.right()
            top = rect.top()
            bottom = rect.bottom()
            x = min(max(point.x(), left), right)
            y = min(max(point.y(), top), bottom)
            return QPointF(x, y)
        except Exception as e:
            logger.error(f"Error in closest_point_on_rect: {e}", exc_info=True)
            return point

    def closest_point_on_path(self, path, point):
        try:
            if path.isEmpty():
                return None
            min_distance_sq = float('inf')
            closest_point = None
            last_point = None
            i = 0
            element_count = path.elementCount()
            while i < element_count:
                element = path.elementAt(i)
                if element.type == QPainterPath.ElementType.CurveToElement:
                    if i + 2 < element_count and path.elementAt(i + 2).type == QPainterPath.ElementType.CurveToDataElement:
                        # Courbe cubique
                        element2 = path.elementAt(i + 1)
                        element3 = path.elementAt(i + 2)
                        cp1 = last_point
                        cp2 = QPointF(element.x, element.y)
                        cp3 = QPointF(element2.x, element2.y)
                        end_point = QPointF(element3.x, element3.y)
                        p_on_curve = self.closest_point_on_bezier_curve(cp1, cp2, cp3, end_point, point)
                        dist_sq = (p_on_curve.x() - point.x()) ** 2 + (p_on_curve.y() - point.y()) ** 2
                        if dist_sq < min_distance_sq:
                            min_distance_sq = dist_sq
                            closest_point = p_on_curve
                        i += 3
                    else:
                        # Courbe quadratique
                        if i + 1 < element_count:
                            element2 = path.elementAt(i + 1)
                            cp1 = last_point
                            cp2 = QPointF(element.x, element.y)
                            end_point = QPointF(element2.x, element2.y)
                            p_on_curve = self.closest_point_on_bezier_curve(cp1, cp2, end_point, point, segments=100)
                            dist_sq = (p_on_curve.x() - point.x()) ** 2 + (p_on_curve.y() - point.y()) ** 2
                            if dist_sq < min_distance_sq:
                                min_distance_sq = dist_sq
                                closest_point = p_on_curve
                            i += 2
                        else:
                            i += 1
                elif element.type == QPainterPath.ElementType.MoveToElement:
                    last_point = QPointF(element.x, element.y)
                    i += 1
                else:
                    if last_point is not None:
                        line = QLineF(last_point, QPointF(element.x, element.y))
                        p_on_line = self.closest_point_on_line(line, point)
                        dist_sq = (p_on_line.x() - point.x()) ** 2 + (p_on_line.y() - point.y()) ** 2
                        if dist_sq < min_distance_sq:
                            min_distance_sq = dist_sq
                            closest_point = p_on_line
                    last_point = QPointF(element.x, element.y)
                    i += 1
            return closest_point
        except Exception as e:
            logger.error(f"Error in closest_point_on_path: {e}", exc_info=True)
            return point

    def closest_point_on_bezier_curve(self, cp1, cp2, cp3, end_point, point, segments=100):
        min_distance_sq = float('inf')
        closest_point = None
        try:
            for i in range(segments + 1):
                t = i / segments
                x = (1 - t)**3 * cp1.x() + 3 * (1 - t)**2 * t * cp2.x() + 3 * (1 - t) * t**2 * cp3.x() + t**3 * end_point.x()
                y = (1 - t)**3 * cp1.y() + 3 * (1 - t)**2 * t * cp2.y() + 3 * (1 - t) * t**2 * cp3.y() + t**3 * end_point.y()
                p_on_curve = QPointF(x, y)
                dist_sq = (p_on_curve.x() - point.x())**2 + (p_on_curve.y() - point.y())**2
                if dist_sq < min_distance_sq:
                    min_distance_sq = dist_sq
                    closest_point = p_on_curve
            return closest_point
        except Exception as e:
            logger.error(f"Error in closest_point_on_bezier_curve: {e}", exc_info=True)
            return point

    def euclidean_distance(self, p1, p2):
        """Calcule la distance euclidienne entre deux points."""
        try:
            return math.sqrt((p1.x() - p2.x()) ** 2 + (p1.y() - p2.y()) ** 2)
        except Exception as e:
            logger.error(f"Error in euclidean_distance: {e}", exc_info=True)
            return 0

    def calculate_angle_between_lines(self, line1, line2):
        try:
            vector1 = QPointF(line1.dx(), line1.dy())
            vector2 = QPointF(line2.dx(), line2.dy())
            dot_product = QPointF.dotProduct(vector1, vector2)
            length1 = math.sqrt(QPointF.dotProduct(vector1, vector1))
            length2 = math.sqrt(QPointF.dotProduct(vector2, vector2))
            if length1 == 0 or length2 == 0:
                return 0
            cos_theta = dot_product / (length1 * length2)
            # Clamp cos_theta to the valid range to avoid math domain error
            cos_theta = max(-1.0, min(1.0, cos_theta))
            angle = math.acos(cos_theta)
            return angle
        except Exception as e:
            logger.error(f"Error in calculate_angle_between_lines: {e}", exc_info=True)
            return 0

    def measure_polygon(self, polygon_item):
        try:
            polygon = polygon_item.polygon()
            perimeter = 0
            area = 0
            n = polygon.count()
            for i in range(n):
                p1 = polygon.at(i)
                p2 = polygon.at((i + 1) % n)
                perimeter += self.euclidean_distance(p1, p2)
                area += (p1.x() * p2.y()) - (p2.x() * p1.y())
            area = abs(area) / 2
            real_perimeter = perimeter * self.unit_scale
            real_area = area * (self.unit_scale ** 2)
            self.display_measurement(
                f"Périmètre: {real_perimeter:.2f} {self.unit_name}, "
                f"Aire: {real_area:.2f} {self.unit_name}²",
                polygon_item
            )
        except Exception as e:
            logger.error(f"Error in measure_polygon: {e}", exc_info=True)

    def adjust_label_position(self, screen_position):
        try:
            view_rect = self.viewport().rect()
            label_width = self.measurement_label.width()
            label_height = self.measurement_label.height()

            x = screen_position.x()
            y = screen_position.y()
            # Vérifier si l'étiquette dépasse les bords droit et inférieur de la vue
            if x + label_width > view_rect.right():
                x = view_rect.right() - label_width
            if y + label_height > view_rect.bottom():
                y = view_rect.bottom() - label_height
            # Vérifier si l'étiquette dépasse les bords gauche et supérieur de la vue
            if x < view_rect.left():
                x = view_rect.left()
            if y < view_rect.top():
                y = view_rect.top()

            return QPoint(int(x), int(y))
        except Exception as e:
            logger.error(f"Error in adjust_label_position: {e}", exc_info=True)
            return screen_position  # Retourner la position originale en cas d'erreur

    def display_angle(self, angle, position):
        try:
            angle_degrees = math.degrees(angle)
            self.measurement_label.setText(f"Angle: {angle_degrees:.2f}°")
            self.measurement_label.adjustSize()
            adjusted_position = self.adjust_label_position(position)
            self.measurement_label.move(adjusted_position)
            self.measurement_label.show()
        except Exception as e:
            logger.error(f"Error in display_angle: {e}", exc_info=True)

    def update_measurement_label(self, distance, start_point, end_point):
        try:
            # Obtenez le facteur d'échelle depuis l'instance de FileViewer
            # Assurez-vous que le parent de ce CustomGraphicsView est bien FileViewer
            file_viewer = self.window()  # Utiliser self.window() pour accéder à la fenêtre principale
            if hasattr(file_viewer, 'scale_factor'):
                scale_factor = file_viewer.scale_factor
                real_distance = distance / scale_factor  # Appliquer le facteur d'échelle
            else:
                real_distance = distance  # Si le facteur d'échelle n'est pas disponible, utilisez la distance brute
            # real_distance = distance * self.unit_scale
            self.measurement_label.setText(f"Distance: {real_distance:.2f} {self.unit_name}")
            self.measurement_label.adjustSize()
            mid_x = (start_point.x() + end_point.x()) / 2
            mid_y = (start_point.y() + end_point.y()) / 2
            screen_position = self.mapFromScene(QPointF(mid_x, mid_y))
            label_position = QPoint(int(screen_position.x() - self.measurement_label.width() / 2),
                                    int(screen_position.y() - self.measurement_label.height() / 2))
            adjusted_position = self.adjust_label_position(label_position)
            self.measurement_label.move(adjusted_position)
            self.measurement_label.show()
        except Exception as e:
            logger.error(f"Error in update_measurement_label: {e}", exc_info=True)

    def wheelEvent(self, event):
        try:
            # Définir les limites de zoom
            zoomOutScale = 0.07  # Échelle minimale autorisée
            zoomInScale = 100    # Échelle maximale autorisée
            # Obtenir l'échelle actuelle
            old_scale = self.transform().m11()
            oldPos = self.mapToScene(event.pos())  # Position avant le zoom
            if event.angleDelta().y() > 0 and old_scale < zoomInScale:
                factor = self.zoom_in_factor
            elif event.angleDelta().y() < 0 and old_scale > zoomOutScale:
                factor = self.zoom_out_factor
            else:
                factor = 1  # Pas de zoom si les limites sont atteintes
            self.scale(factor, factor)   
            # Recentrer la vue pour que le point sous la souris reste fixe
            newPos = self.mapToScene(event.pos())  # Position après le zoom
            delta = newPos - oldPos
            self.translate(delta.x(), delta.y())
        except Exception as e:
            logger.error(f"Error handling wheel event: {e}", exc_info=True)

    def resizeEvent(self, event):
        logger.info("Start of resizeEvent in CustomGraphicsView class")
        try:
            super().resizeEvent(event)
            self.resized.emit()
            logger.debug("Resize event detected.")
            self.adjust_view()
        except Exception as e:
            logger.error(f"Error in resizeEvent: {e}", exc_info=True)
        logger.info("End of resizeEvent in CustomGraphicsView class")

    def showEvent(self, event):
        logger.info("Start of showEvent in CustomGraphicsView class")
        super().showEvent(event)
        logger.debug("Show event detected.")
        QTimer.singleShot(0, self.adjust_view)
        logger.info("End of showEvent in CustomGraphicsView class")

    def adjust_view(self):
        if self._adjustment_in_progress:
            logger.warning("Adjustment already in progress, skipping.")
            return
        self._adjustment_in_progress = True
        try:
            if self.scene():
                rect_scene = self.scene().itemsBoundingRect()
                if not self._initial_view_fitted:
                    self.fitInView(rect_scene, Qt.KeepAspectRatio)
                    self._initial_view_fitted = True
                    logger.debug("Initial view adjusted to show entire scene.")
                if not self._signal_connected:
                    self.scene().changed.connect(self.adjust_view)
                    self._signal_connected = True
                    logger.debug("Signal connected to adjust view dynamically.")
        except Exception as e:
            logger.error(f"Error in adjust_view: {e}", exc_info=True)
        finally:
            self._adjustment_in_progress = False
            self.build_spatial_index()  # Construire l'index spatial

    def zoom_in(self):
        logger.info("Zooming in")
        try:
            self.scale(self.zoom_in_factor, self.zoom_in_factor)
        except Exception as e:
            logger.error(f"Error in zoom_in: {e}", exc_info=True)

    def zoom_out(self):
        logger.info("Zooming out")
        try:
            self.scale(self.zoom_out_factor, self.zoom_out_factor)
        except Exception as e:
            logger.error(f"Error in zoom_out: {e}", exc_info=True)

class ControlPoint(QGraphicsItem):
    def __init__(self, position, connected_line=None, parent=None):
        super().__init__(parent)
        self.connected_line = connected_line
        self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemSendsGeometryChanges)
        self.setPos(position)
        self.setAcceptHoverEvents(True)
        self.size = 10  # Taille en pixels souhaitée

    def boundingRect(self):
        s = self.size / 2
        return QRectF(-s, -s, self.size, self.size)

    def paint(self, painter, option, widget):
        # Obtenir le facteur d'échelle courant
        views = self.scene().views()
        if views:
            current_scale = views[0].transform().m11()
        else:
            current_scale = 1.0
        adjusted_size = self.size / current_scale
        s = adjusted_size / 2
        rect = QRectF(-s, -s, adjusted_size, adjusted_size)
        painter.setBrush(QBrush(QColor("red")))
        painter.setPen(Qt.NoPen)
        painter.drawEllipse(rect)

    def itemChange(self, change, value):
        try:
            if change == QGraphicsItem.ItemPositionChange and self.connected_line:
                new_pos = value
                line = self.connected_line.line()
                if self == self.connected_line.start_control_point:
                    self.connected_line.setLine(QLineF(new_pos, line.p2()))
                elif self == self.connected_line.end_control_point:
                    self.connected_line.setLine(QLineF(line.p1(), new_pos))
                return new_pos
        except Exception as e:
            logger.error(f"Error in itemChange: {e}", exc_info=True)
        return super().itemChange(change, value)
class MeasuredLine(QGraphicsLineItem):
    def __init__(self, line, parent=None):
        super().__init__(line, parent)
        self.start_control_point = None
        self.end_control_point = None

def main():
    #profiler = cProfile.Profile()
    #profiler.enable()

    app = QApplication([])
    window = FileViewer()
    window.show()

    app.exec_()