# This Python file uses the following encoding: utf-8
import sys
from PyQt5.QtCore import QPointF, QPoint, Qt, QRectF,  QLineF
from PyQt5.QtGui import QPolygonF, QPen, QFont, QPainterPath
from PyQt5.QtWidgets import QApplication, QGraphicsView, QGraphicsScene
from PyQt5.QtWidgets import QGraphicsObject, QGraphicsItem
from enum import Enum, unique
import math

@unique
class DrawState(Enum):
    DrawState_Default = 0
    DrawState_Draw = 1
    DrawState_Move = 2
    DrawState_Size = 3
    DrawState_Normal = 4


@unique
class PenSelect(Enum):
    # 绘制颜色
    PenSelect_Draw = 0
    # 移动颜色
    PenSelect_Move = 1
    # 移动颜色
    PenSelect_Size = 2
    # 正常颜色
    PenSelect_Normal = 3


# 定义支持的绘图类型
class ShapeType(Enum):
    ShapeType_Null = 0
    ShapeType_Rectangle = 1
    ShapeType_Ellipse = 2
    ShapeType_Line = 3
    ShapeType_Angle = 4
    ShapeType_Polygon = 5
    ShapeType_PolygonLine = 6


# 继承QGraphicsObject进行自定义绘图
class CustomItem(QGraphicsObject):
    def __init__(self, **kwargs):
        print("Init CustomItem")
        w = kwargs.pop('w', None)
        h = kwargs.pop('h', None)
        s = kwargs.pop('shape', None)
        super(CustomItem, self).__init__()
        self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable)
        self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable)
        self.setAcceptHoverEvents(True)

        # Item宽高
        self.w = w
        self.h = h
        # 图形类型
        self.m_shape_type = s
        # 当前移动的端点
        self.m_current_index = 0
        # 当前point list大小由shape决定
        # 端点QPointF数组，默认为空，存放点位列表信息
        self.m_points_list = []
        # 端点的椭圆检测范围，同上面的points列表保持一致
        self.m_points_rect = []
        # drag状态标记，可以要加粗， 变色
        self.m_move_rect = QRectF()
        self.m_drag_flag = False
        # STATE_FLAG
        self.m_draw_state = DrawState.DrawState_Default
        self.m_draw_changed = False
        # 绘制使能
        self.m_draw_enable = False
        # 当前点位和上一个点位
        self.m_current_pos = QPointF()
        self.m_previous_pos = QPointF()
        # 画笔选择
        self.m_pen_select = PenSelect.PenSelect_Normal
        # 存放画笔列表
        # 存放画笔，针对多状态个性化显示
        self.m_pen_list = [QPen(Qt.GlobalColor.red), QPen(Qt.GlobalColor.red),
                           QPen(Qt.GlobalColor.green), QPen(Qt.GlobalColor.blue)]
        # 存放字体列表
        self.m_font_list = [QFont("宋体", 35, 1), QFont("宋体", 35, 1), QFont("宋体", 35, 1), QFont("宋体", 35, 1)]

        # 按照探测板17英寸 = 431.8mm = 3072像素
        # 每个像素表示的实际大小， 比例尺
        self.m_scale_value = 431.8/3072
        # 绘制探测区
        self.m_draw_senor = False

    def boundingRect(self):
        """
        boundingRect 函数非常重要，鼠标响应和绘制都跟该Rect相关
        可以利用点位生成多边形对象，返回对变形对象的boundingRect即可
        QRectF(start, end).normalized() 可以返回正常矩形区域，w/h为正数
        ！！！！！这个是该画图模块的核心所在！！！！！！
        :return:
        """
        polygon = QPolygonF()
        for point in self.m_points_list:
            polygon.append(point)

        # 缩小boundingRect()区域20像素
        self.m_move_rect = QRectF(polygon.boundingRect().topLeft().x(), polygon.boundingRect().topLeft().y(),
                      polygon.boundingRect().width(), polygon.boundingRect().height())

        # 扩大polygon.boundingRect()区域20像素
        return QRectF(polygon.boundingRect().topLeft().x()-5, polygon.boundingRect().topLeft().y()-5,
                      polygon.boundingRect().width() + 10, polygon.boundingRect().height() + 10)

    def update_point_list(self, pos):
        """
        根据index修改对应的坐标点， 可以通过点为圆心的检测范围确定是哪个点坐标发生了改变
        这个检测范围在
        :param pos:
        :return:
        """
        # 直接更新对应的点位
        self.m_points_list[self.m_current_index] = pos

        # 清除所有感知矩形
        self.m_points_rect.clear()

        # 设置矩形
        for point in self.m_points_list:
            # 以point为中心的正方形区域 20x20
            self.m_points_rect.append(QRectF(point.x() - 4, point.y() - 4, 8, 8))

        self.m_draw_changed = True

    def update_point_list(self, index, pos):
        """
        根据index修改对应的坐标点， 可以通过点为圆心的检测范围确定是哪个点坐标发生了改变
        这个检测范围在
        :param index:
        :param pos:
        :return:
        """
        # 直接更新对应的点位
        if index < len(self.m_points_list):
            self.m_points_list[index] = pos
        else:
            self.m_points_list.append(pos)

        # 清除所有感知矩形
        self.m_points_rect.clear()

        # 设置矩形
        for point in self.m_points_list:
            # 以point为中心的正方形区域 8x8
            self.m_points_rect.append(QRectF(point.x() - 4, point.y() - 4, 8, 8))

        self.m_draw_changed = True

    def update_font_size(self):
        rect = self.boundingRect()
        for font in self.m_font_list:
            font.setWeight(10 + int(20*rect.width()*rect.height()/(2000*2000)))

    def update_resize_flag(self, event):
        # 检测当前移动的是哪个端点
        for point in self.m_points_list:
            line = QLineF(point, event.pos())
            if line.length() < 10:
                self.m_current_index = self.m_points_list.index(point)
                self.setCursor(Qt.CursorShape.SizeFDiagCursor)
                self.m_draw_state = DrawState.DrawState_Size
                self.m_pen_select = PenSelect.PenSelect_Size
                break

    def update_move_flag(self, event):
        if len(self.m_points_list) < 2:
            return

        # 利用boundingRect
        if self.m_move_rect.contains(event.pos()):
            self.m_draw_state = DrawState.DrawState_Move
            self.m_pen_select = PenSelect.PenSelect_Move
            self.m_drag_flag = True

    def draw_shape(self, painter, option, widget):
        if self.m_shape_type == ShapeType.ShapeType_Line:
            line = QLineF(self.m_points_list[0], self.m_points_list[1])
            painter.drawLine(line)
            pixels = line.length()
            length = self.m_scale_value * pixels
            # 文本区域
            text_rect = QRectF(line.x2(), line.y2(), 500, 500)
            painter.drawText(text_rect, Qt.TextFlag.TextWordWrap, "Pixels:%d\nLength:%.2fmm" % (pixels, length))
        elif self.m_shape_type == ShapeType.ShapeType_Rectangle:
            # 两点坐标，将其normalized为正常矩形
            rect = QRectF(self.m_points_list[0], self.m_points_list[1]).normalized()
            painter.drawRect(rect)
            pixels = rect.width() * rect.height()
            # 面积单位cm2
            area = self.m_scale_value * pixels / 100.0
            # 文本区域
            text_rect = QRectF(rect.topRight().x(), rect.topRight().y(), 500, 500)
            painter.drawText(text_rect, Qt.TextFlag.TextWordWrap, "Pixels:%d\nArea:%.2fcm2" % (pixels, area))
        elif self.m_shape_type == ShapeType.ShapeType_Ellipse:
            # 两点坐标，将其normalized为正常矩形
            rect = QRectF(self.m_points_list[0], self.m_points_list[1]).normalized()
            painter.drawEllipse(rect)
            pixels = math.pi * rect.width() * rect.height() / 4
            # 面积单位cm2
            area = self.m_scale_value * pixels / 100.0
            # 文本区域
            text_rect = QRectF(rect.topRight().x(), rect.topRight().y(), 500, 500)
            painter.drawText(text_rect, Qt.TextFlag.TextWordWrap, "Pixels:%d\nArea:%.2fcm2" % (pixels, area))
        elif self.m_shape_type == ShapeType.ShapeType_Polygon:
            if len(self.m_points_list) == 2:
                painter.drawLine(QLineF(self.m_points_list[0], self.m_points_list[1]))
            else:
                # 两点坐标，将其normalized为正常矩形
                polygon = QPolygonF(self.m_points_list)
                rect = polygon.boundingRect()
                painter.drawPolygon(polygon)

                pixels = 0
                start = QPoint(round(rect.topLeft().x()), round(rect.topLeft().y()))
                w = round(rect.width())
                h = round(rect.height())

                # 计算面积优化一把， step按照图形大小增加
                step_x = 1+round(w / 10)
                step_y = 1+round(h / 10)
                for x in range(start.x(), start.x() + w, step_x):
                    for y in range(start.y(), start.y() + h, step_y):
                        if polygon.containsPoint(QPoint(x, y), Qt.FillRule.OddEvenFill):
                            pixels = pixels + 1
                # 面积单位cm2
                area = self.m_scale_value * pixels * step_x * step_y / 100.0
                # 文本区域
                text_rect = QRectF(rect.topRight().x(), rect.topRight().y(), 500, 500)
                painter.drawText(text_rect, Qt.TextFlag.TextWordWrap, "Pixels:%d\nArea:%.2fcm2" %
                                 (pixels*step_x*step_y, area))

        elif self.m_shape_type == ShapeType.ShapeType_Angle:
            if len(self.m_points_list) > 1:
                line1 = QLineF(self.m_points_list[0], self.m_points_list[1])
                painter.drawLine(line1)
                pixels1 = line1.length()
                length1 = self.m_scale_value * pixels1
            if len(self.m_points_list) > 2:
                line2 = QLineF(self.m_points_list[0], self.m_points_list[2])
                painter.drawLine(line2)
                pixels2 = line2.length()
                length2 = self.m_scale_value * pixels2
                # 文本区域
                angle = line1.angleTo(line2)
                if angle > 180:
                    angle = 360 - angle
                length = length1 + length2
                pixels = pixels1 + pixels2
                text_rect = QRectF(line2.x2(), line2.y2(), 500, 500)
                painter.drawText(text_rect, Qt.TextFlag.TextWordWrap, "Pixels:%d\n Length:%.2fmm\nAngle:%.2f degree"
                                 % (pixels, length, angle))
        elif self.m_shape_type == ShapeType.ShapeType_PolygonLine:
            pixels = 0
            pre_point = self.m_points_list[0]
            for point in self.m_points_list:
                if point == self.m_points_list[0]:
                    path = QPainterPath(self.m_points_list[0])
                else:
                    pixels = pixels + QLineF(pre_point, point).length()
                    path.lineTo(point)
                pre_point = point
            length = self.m_scale_value*pixels
            text_rect = QRectF(pre_point.x(), pre_point.y(), 500, 500)
            painter.drawText(text_rect, Qt.TextFlag.TextWordWrap, "Pixels:%d\n Length:%.2fmm"
                             % (pixels, length))

            painter.drawPath(path)

    def paint(self, painter, option, widget):
        """
        图形绘制函数，将图形一次绘制
        :param painter:
        :param option:
        :param widget:
        """
        if self.m_draw_enable:
            painter.setPen(self.m_pen_list[self.m_pen_select.value])
            painter.setFont(self.m_font_list[self.m_pen_select.value])

            # 超过两个点即可绘制
            if self.m_draw_changed and len(self.m_points_list) > 1:
                self.draw_shape(painter, option, widget)

                if self.m_drag_flag:
                    if self.m_draw_state == DrawState.DrawState_Move:
                        self.setCursor(Qt.CursorShape.DragMoveCursor)
                    elif self.m_draw_state == DrawState.DrawState_Size:
                        self.setCursor(Qt.CursorShape.SizeFDiagCursor)
                    else:
                        self.setCursor(Qt.CursorShape.ArrowCursor)

                    # 绘制感知区
                    for rect in self.m_points_rect:
                        painter.drawRect(rect)

                    if self.m_draw_senor:
                        # 绘制boundingRect
                        painter.drawRect(self.boundingRect())
                        painter.drawRect(self.m_move_rect)

    def mousePressEvent(self, event):
        """
        mousePressEvent
        :param event:
        :return:
        """
        if event.button() == Qt.MouseButton.LeftButton:
            # 记录按下的位置
            self.m_current_pos = event.pos()
            print("mousePressEvent")
            print(self.m_current_pos)
            self.update_move_flag(event)
            self.update_resize_flag(event)
        else:
            self.m_drag_flag = False
            self.m_draw_state = DrawState.DrawState_Normal
            self.m_pen_select = PenSelect.PenSelect_Normal

        event.accept()
        super(CustomItem, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        print("mouseMoveEvent")
        self.prepareGeometryChange()
        self.m_drag_flag = True
        self.m_previous_pos = event.pos()
        if self.m_draw_state == DrawState.DrawState_Move:
            # 得到偏移值
            point = self.m_previous_pos - self.m_current_pos
            self.moveBy(point.x(), point.y())
        elif self.m_draw_state == DrawState.DrawState_Size:
            self.update_point_list(self.m_current_index, event.pos())
            self.update_font_size()

        # 触发绘制
        self.m_draw_changed = True
        self.scene().update()

    def mouseReleaseEvent(self, event):
        """
        鼠标释放， 修改图形样式
        :param event:
        :return:
        """
        self.setCursor(Qt.CursorShape.ArrowCursor)
        self.m_draw_state = DrawState.DrawState_Normal
        self.m_pen_select = PenSelect.PenSelect_Normal

        # 触发绘制
        self.m_draw_changed = True
        self.scene().update()

        event.accept()
        super(CustomItem, self).mouseReleaseEvent(event)

    def hoverMoveEvent(self, event):
        self.update_move_flag(event)
        self.scene().update()
        event.accept()
        super(CustomItem, self).hoverMoveEvent(event)

    def hoverLeaveEvent(self, event):
        # 离开清除标记
        self.m_drag_flag = False
        self.m_draw_state = DrawState.DrawState_Normal
        self.m_pen_select = PenSelect.PenSelect_Normal
        self.scene().update()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    scene = QGraphicsScene()
    view = QGraphicsView()
    view.setMinimumSize(800, 600)
    view.setScene(scene)
    shape = ShapeType.ShapeType_Polygon
    item = CustomItem(w=view.width(), h=view.height(), shape=shape)

    if shape == ShapeType.ShapeType_Angle:
        item.m_points_list.append(QPointF(100, 100))
        item.m_points_list.append(QPointF(200, 200))
        item.m_points_list.append(QPointF(100, 200))
        item.m_current_index = 2
        item.update_point_list(QPointF(100, 200))
        item.m_draw_enable = True
    elif shape == ShapeType.ShapeType_Polygon or shape == ShapeType.ShapeType_PolygonLine:
        item.m_points_list.append(QPointF(100, 100))
        item.m_points_list.append(QPointF(200, 200))
        item.m_points_list.append(QPointF(150, 300))
        item.m_points_list.append(QPointF(400, 250))
        item.m_points_list.append(QPointF(750, 60))
        item.m_points_list.append(QPointF(400, 150))
        item.m_current_index = 5
        item.update_point_list(QPointF(400, 150))
        item.m_draw_enable = True
    else:
        item.m_points_list.append(QPointF(100, 100))
        item.m_points_list.append(QPointF(200, 200))
        item.m_current_index = 1
        item.update_point_list(QPointF(200, 200))
        item.m_draw_enable = True
    item.m_draw_changed = True

    scene.addItem(item)

    view.show()
    sys.exit(app.exec_())
