请哪位老师百忙中抽空帮我分析一下逻辑要怎么改,挠头四天了,没搞定!万分感谢!

AI摘要
该内容为Python代码实现的家谱吊线图生成器,包含节点类、树结构、图形界面及布局算法。用户希望优化布局算法,使节点排列更紧凑。这是一个技术性的编程问题,涉及树形数据结构的可视化布局算法优化。

现有代码为:

#!/usr/bin/env python

-- coding: utf-8 --

“””
Created on Sun Nov 19 21:07:50 2023

@author: admin
“””
import sys
import os
from PyQt5.QtWidgets import (QApplication, QMainWindow, QGraphicsScene, QGraphicsView, QGraphicsItem,
QGraphicsPixmapItem, QGraphicsTextItem, QGraphicsLineItem, QGraphicsPathItem, QGraphicsRectItem,
QGraphicsItemGroup, QComboBox, QFontComboBox, QLabel, QPushButton, QVBoxLayout, QHBoxLayout,
QGridLayout, QWidget, QSplitter, QFileDialog, QMessageBox, QScrollArea, QGroupBox,
QSpinBox, QDoubleSpinBox, QColorDialog, QFrame, QTabWidget, QListWidget,
QListWidgetItem, QInputDialog, QMenu, QAction, QCheckBox)
from PyQt5.QtGui import (QPen, QBrush, QFont, QFontMetrics, QPainter, QPainterPath, QColor,
QPixmap, QIcon, QPolygonF, QPolygon, QImage, QTextDocument, QCursor)
from PyQt5.QtCore import Qt, QPointF, QRectF, QLineF, QSize, QSizeF, QEvent
import math
import csv
from datetime import datetime
import platform
import json
import pandas as pd

定义节点类

class Node:
def init(self, id, name, note=””):
self.id = id
self.name = name
self.note = note
self.parent = None
self.children = []
self.x = 0
self.y = 0
self.width = 0
self.height = 0
self.spouse = None # 配偶节点
self.spouse_relationship = “” # 配偶关系描述
self.node_id = None # 用于布局计算的唯一标识

def add_child(self, child_node):
    """添加子节点"""
    if child_node not in self.children:
        self.children.append(child_node)
        child_node.parent = self

def remove_child(self, child_node):
    """移除子节点"""
    if child_node in self.children:
        self.children.remove(child_node)
        child_node.parent = None

def add_spouse(self, spouse_node, relationship="配偶"):
    """添加配偶节点"""
    self.spouse = spouse_node
    spouse_node.spouse = self
    self.spouse_relationship = relationship
    spouse_node.spouse_relationship = relationship

定义树类

class FamilyTree:
def init(self):
self.root = None
self.nodes = {}

def add_node(self, node):
    """添加节点到树中"""
    if node.id not in self.nodes:
        self.nodes[node.id] = node
        if self.root is None:
            self.root = node

def remove_node(self, node_id):
    """从树中移除节点"""
    if node_id in self.nodes:
        node = self.nodes[node_id]
        # 断开与父节点的连接
        if node.parent:
            node.parent.remove_child(node)
        # 断开与所有子节点的连接
        for child in node.children.copy():
            node.remove_child(child)
        # 断开与配偶的连接
        if node.spouse:
            node.spouse.spouse = None
            node.spouse.spouse_relationship = ""
        # 从字典中移除节点
        del self.nodes[node_id]
        # 如果移除的是根节点,则重新设置根节点为第一个添加的节点(如果有的话)
        if node == self.root and self.nodes:
            self.root = next(iter(self.nodes.values()))

def get_node(self, node_id):
    """通过ID获取节点"""
    return self.nodes.get(node_id)

def get_all_nodes(self):
    """获取所有节点"""
    return list(self.nodes.values())

def find_node_by_name(self, name):
    """通过名称查找节点"""
    for node in self.nodes.values():
        if node.name == name:
            return node
    return None

定义图形项类

class NodeItem(QGraphicsItemGroup):
def init(self, node, parent=None):
super().init(parent)
self.node = node
self.setFlag(QGraphicsItem.ItemIsMovable, False)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
self.is_expanded = True # 初始状态为展开

    # 创建节点背景矩形
    self.background = QGraphicsPathItem()
    self.addToGroup(self.background)

    # 创建节点文本
    self.text_item = QGraphicsTextItem()
    self.addToGroup(self.text_item)

    # 创建展开/折叠按钮(仅在有子节点时显示)
    self.toggle_button = QGraphicsPixmapItem()
    self.toggle_button.hide()
    self.addToGroup(self.toggle_button)

    # 首先存储样式信息,确保在update_node_appearance前初始化
    self.box_color = QColor(255, 255, 255)
    self.border_color = QColor(0, 0, 0)
    self.text_color = QColor(0, 0, 0)
    self.border_width = 1
    self.border_style = Qt.SolidLine
    self.font = QFont("SimHei", 12)
    self.corner_radius = 0  # 默认圆角值设为0,表示直角边框

    # 然后初始化外观
    self.update_node_appearance()
    self.update_toggle_button()

def update_node_appearance(self):
    """更新节点的外观,实现字体大小与图形尺寸的相互适应"""
    # 防御性编程:确保text_color和font属性存在
    if not hasattr(self, 'text_color'):
        self.text_color = QColor(0, 0, 0)
        print("Warning: Added missing text_color attribute to NodeItem")
    if not hasattr(self, 'font'):
        self.font = QFont("SimHei", 12)
        print("Warning: Added missing font attribute to NodeItem")

    # 获取文字方向设置
    text_direction = getattr(self, 'text_direction', '横向')

    # 根据文字方向设置文本显示方式
    if text_direction == "竖向":
        # 竖排文字:每个字符占一行
        vertical_text = '<br>'.join(list(self.node.name))
        self.text_item.setHtml(f'<div style="text-align:center; color:{self.text_color.name()};">{vertical_text}</div>')
    else:
        # 横排文字
        self.text_item.setHtml(f'<div style="text-align:center; color:{self.text_color.name()};">{self.node.name}</div>')

    self.text_item.setFont(self.font)  # 使用实际设置的字体显示

    # 获取文本尺寸(像素)
    text_rect = self.text_item.boundingRect()
    text_width_px = text_rect.width()
    text_height_px = text_rect.height()

    # 当文字方向为竖向时,互换文本的宽高值
    if text_direction == "竖向":
        text_width_px, text_height_px = text_height_px, text_width_px

    # 设置外扩边距(像素)
    padding_px = 0  # 删除谱名与节点边框之间的最小间距

    # 最小宽度和高度(像素)
    min_width_px = 0  # 移除最小宽度限制
    min_height_px = 0  # 移除最小高度限制

    # 处理自定义尺寸设置(英寸转像素)
    dpi = 96.0  # 屏幕DPI,用于转换英寸到像素
    custom_width_px = None
    custom_height_px = None

    # 检查是否有自定义尺寸设置,node.width/node.height 是英寸
    if hasattr(self.node, 'width') and self.node.width > 0:
        # 将英寸转换为像素
        custom_width_px = self.node.width * dpi

    if hasattr(self.node, 'height') and self.node.height > 0:
        # 将英寸转换为像素
        custom_height_px = self.node.height * dpi

    # 确定最终的节点尺寸
    if custom_width_px is not None and custom_height_px is not None:
        # 如果同时设置了自定义宽度和高度,当文字方向为竖向时互换
        if text_direction == "竖向":
            node_width_px = custom_height_px
            node_height_px = custom_width_px
            print(f"        文字方向为竖向,互换自定义尺寸:宽度={node_width_px}, 高度={node_height_px}")
        else:
            node_width_px = custom_width_px
            node_height_px = custom_height_px
            print(f"        文字方向为横向,使用自定义尺寸:宽度={node_width_px}, 高度={node_height_px}")
    elif custom_width_px is not None:
        # 如果只设置了自定义宽度,当文字方向为竖向时,宽度使用高度默认值
        node_width_px = custom_width_px
        node_height_px = max(text_height_px + padding_px * 2, min_height_px)
        print(f"        只设置了自定义宽度:宽度={node_width_px}, 高度={node_height_px}")
    elif custom_height_px is not None:
        # 如果只设置了自定义高度,当文字方向为竖向时,高度使用宽度默认值
        node_width_px = max(text_width_px + padding_px * 2, min_width_px)
        node_height_px = custom_height_px
        print(f"        只设置了自定义高度:宽度={node_width_px}, 高度={node_height_px}")
    else:
        # 计算默认宽度和高度
        node_width_px = max(text_width_px + padding_px * 2, min_width_px)
        node_height_px = max(text_height_px + padding_px * 2, min_height_px)
        # 当文字方向为竖向时,互换节点图形的宽高值
        if text_direction == "竖向":
            node_width_px, node_height_px = node_height_px, node_width_px
            print(f"        文字方向为竖向,互换默认尺寸:宽度={node_width_px}, 高度={node_height_px}")
        else:
            print(f"        文字方向为横向,使用默认尺寸:宽度={node_width_px}, 高度={node_height_px}")

    # 当只是改变文字方向时,文字大小不能改变
    # 因此,我们跳过字体大小调整的步骤
    font_adjusted = False

    # 创建圆角矩形路径
    path = QPainterPath()
    # 将圆角半径从毫米转换为像素
    dpi = 96.0
    corner_radius_px = self.corner_radius * dpi / 25.4

    # 直接使用计算出的节点宽高值创建路径,不考虑文字方向
    # 确保圆角半径不超过图形宽度和高度的一半
    max_radius_px = min(node_width_px, node_height_px) / 2
    corner_radius_px = min(corner_radius_px, max_radius_px)
    path.addRoundedRect(QRectF(0, 0, node_width_px, node_height_px), corner_radius_px, corner_radius_px)
    print(f"        创建圆角矩形路径:宽度={node_width_px}, 高度={node_height_px}")

    # 设置背景样式
    self.background.setPath(path)
    self.background.setBrush(QBrush(self.box_color))
    pen = QPen(self.border_color, self.border_width, self.border_style)
    self.background.setPen(pen)

    # 设置文本位置(居中)
    # 直接使用计算出的节点宽高值计算文本位置,不考虑文字方向
    # 调整垂直方向的位置,确保文本在垂直方向上也能正确居中显示
    text_x = (node_width_px - text_rect.width()) / 2

    # 由于 QGraphicsTextItem 的 boundingRect() 可能包含额外的垂直空间,特别是对于中文文本
    # 我们需要调整 y 坐标,使文本在视觉上垂直居中
    # 对于中文文本,需要微调 y 坐标,以补偿字体的基线偏移
    # 再往下移2%试一下看
    text_y = (node_height_px - text_rect.height()) / 2 + text_rect.height() * 0.02  # 向下调整 2% 的文本高度

    print(f"        设置文本位置:x={text_x}, y={text_y}")
    self.text_item.setPos(text_x, text_y)

    # 更新节点尺寸(像素转英寸,用于后续布局计算)
    # 保存原始的宽高值,以便在文字方向改变时恢复
    # 当节点的width或height属性被修改时,更新original_width和original_height
    if hasattr(self.node, 'width') and self.node.width > 0:
        self.node.original_width = self.node.width
    else:
        self.node.original_width = node_width_px / dpi
    if hasattr(self.node, 'height') and self.node.height > 0:
        self.node.original_height = self.node.height
    else:
        self.node.original_height = node_height_px / dpi

    if text_direction == "竖向":
        # 当文字方向为竖向时,互换节点的宽高值
        self.node.width = self.node.original_height
        self.node.height = self.node.original_width
        print(f"        更新节点尺寸(竖向):宽度={self.node.width}, 高度={self.node.height}")
    else:
        # 当文字方向为横向时,使用原始宽高值
        self.node.width = self.node.original_width
        self.node.height = self.node.original_height
        print(f"        更新节点尺寸(横向):宽度={self.node.width}, 高度={self.node.height}")

def set_node_style(self, box_color=None, border_color=None, text_color=None, border_width=None, 
                   border_style=None, font=None, corner_radius=None, text_direction=None):
    """设置节点样式"""
    if box_color is not None:
        self.box_color = box_color
    if border_color is not None:
        self.border_color = border_color
    if text_color is not None:
        self.text_color = text_color
    if border_width is not None:
        self.border_width = border_width
    if border_style is not None:
        self.border_style = border_style
    if font is not None:
        self.font = font
    if corner_radius is not None:
        # 验证圆角值不能大于图形宽度和图形高度中最小那个值的一半
        text_rect = self.text_item.boundingRect()
        padding = 10  # 内边距
        node_width = text_rect.width() + padding * 2
        node_height = text_rect.height() + padding * 2
        min_dimension_half = min(node_width, node_height) / 2

        # 如果圆角值超过最大允许值,则设置为最大允许值
        if corner_radius > min_dimension_half:
            self.corner_radius = min_dimension_half
        else:
            self.corner_radius = corner_radius
    if text_direction is not None:
        self.text_direction = text_direction

    self.update_node_appearance()

def update_toggle_button(self):
    """更新展开/折叠按钮"""
    # 隐藏展开/折叠按钮,避免显示三条短粗线
    self.toggle_button.hide()

def toggle_expansion(self):
    """切换展开/折叠状态"""
    self.is_expanded = not self.is_expanded
    self.update_toggle_button()
    return self.is_expanded

def mousePressEvent(self, event):
    """处理鼠标点击事件"""
    # 检查是否点击了展开/折叠按钮
    button_rect = self.toggle_button.boundingRect()
    button_rect.moveTopLeft(self.toggle_button.pos())
    if button_rect.contains(event.pos()):
        self.toggle_expansion()
        event.ignore()  # 忽略事件,不触发选择
    else:
        super().mousePressEvent(event)

class LineItem(QGraphicsPathItem):
def init(self, start_node, end_node, layout_direction=’vertical_normal’):
super().init()
self.start_node = start_node
self.end_node = end_node
self.line_color = QColor(0, 0, 0)
self.line_width = 1
self.line_style = Qt.SolidLine
self.end_shape_type = “无” # 末端图形类型
self.end_shape_size = 5.0 # 末端图形大小(像素),默认为5像素
self.end_shape_line_style = “实线” # 末端图形轮廓线形,默认为实线
self.end_shape_line_width = 1.0 # 末端图形轮廓粗细(像素),默认为1像素
self.end_shape_color = QColor(0, 0, 0) # 末端图形轮廓颜色,默认为黑色

    # 获取布局方向
    self.layout_direction = layout_direction  # 默认布局方向
    self.update_line_path()

def set_line_style(self, color=None, width=None, style=None, end_shape_type=None, end_shape_size=None, end_shape_line_style=None, end_shape_line_width=None, end_shape_color=None, layout_direction=None):
    """设置线条样式"""
    if color is not None:
        self.line_color = color
    if width is not None:
        self.line_width = width
    if style is not None:
        self.line_style = style
    if end_shape_type is not None:
        self.end_shape_type = end_shape_type
    if end_shape_size is not None:
        self.end_shape_size = end_shape_size
    if end_shape_line_style is not None:
        self.end_shape_line_style = end_shape_line_style
    if end_shape_line_width is not None:
        self.end_shape_line_width = end_shape_line_width
    if end_shape_color is not None:
        self.end_shape_color = end_shape_color
    if layout_direction is not None:
        self.layout_direction = layout_direction

    self.update_line_path()

def update_line_path(self):
    """更新线条路径,确保导线绘制为直角连接,没有斜线"""
    # 创建线条路径
    path = QPainterPath()

    # 单位转换:将英寸转换为像素
    dpi = 96.0

    # 设置线条样式
    pen = QPen(self.line_color, self.line_width, self.line_style)
    self.setPen(pen)

    # 检查是否是父子关系(如果是配偶关系,保持现有样式)
    is_parent_child = self.end_node in self.start_node.children or self.start_node in self.end_node.children

    if is_parent_child:
        # 父子关系:确保导线为直角连接

        # 确定父子关系
        parent = self.start_node if self.end_node in self.start_node.children else self.end_node
        child = self.end_node if self.end_node in self.start_node.children else self.start_node

        if self.layout_direction in ['vertical_normal', 'vertical_reverse']:
            # 竖排布局:导线从父节点的下方连接到子节点的上方
            # 计算父节点底部边缘中点
            parent_bottom_x = (parent.x + parent.width / 2) * dpi
            parent_bottom_y = (parent.y + parent.height) * dpi

            # 计算子节点顶部边缘中点
            child_top_x = (child.x + child.width / 2) * dpi
            child_top_y = child.y * dpi

            # 计算垂直间距的中间位置
            mid_y = (parent_bottom_y + child_top_y) / 2

            # 绘制直角连接线
            path.moveTo(parent_bottom_x, parent_bottom_y)

            # 如果父节点和子节点的X坐标相同,直接垂直连接
            if abs(parent_bottom_x - child_top_x) < 1.0:
                path.lineTo(parent_bottom_x, child_top_y)
            else:
                # 否则绘制直角连接,水平导线在垂直间距中间
                # 1. 从父节点底部向下画垂直线到垂直间距的中间位置
                path.lineTo(parent_bottom_x, mid_y)
                # 2. 从中间位置水平连接到子节点的正上方
                path.lineTo(child_top_x, mid_y)
                # 3. 从中间位置向下画垂直线到子节点顶部
                path.lineTo(child_top_x, child_top_y)
        else:
            # 横排布局:根据阅读方向确定导线连接方向
            if self.layout_direction == 'horizontal_normal':
                # 横排正翻:导线从父节点的右边线中部到子节点或子节点组左边中部
                # 计算父节点右边线中部
                parent_right_mid_x = (parent.x + parent.width) * dpi
                parent_right_mid_y = (parent.y + parent.height / 2) * dpi

                # 计算子节点左边线中部
                child_left_mid_x = child.x * dpi
                child_left_mid_y = (child.y + child.height / 2) * dpi

                # 计算水平间距的中间位置
                mid_x = (parent_right_mid_x + child_left_mid_x) / 2

                # 绘制直角连接线
                path.moveTo(parent_right_mid_x, parent_right_mid_y)

                # 如果父节点和子节点的Y坐标相同,直接水平连接
                if abs(parent_right_mid_y - child_left_mid_y) < 1.0:
                    path.lineTo(child_left_mid_x, child_left_mid_y)
                else:
                    # 否则绘制直角连接,垂直导线在水平间距中间
                    # 1. 从父节点右边线中部向右画水平线到水平间距的中间位置
                    path.lineTo(mid_x, parent_right_mid_y)
                    # 2. 从中间位置垂直连接到子节点左边线中部的正右方
                    path.lineTo(mid_x, child_left_mid_y)
                    # 3. 从中间位置向左画水平线到子节点左边线中部
                    path.lineTo(child_left_mid_x, child_left_mid_y)
            else:
                # 横排反翻:导线从父节点的左边线中部到子节点或子节点组右边中部
                # 计算父节点左边线中部
                parent_left_mid_x = parent.x * dpi
                parent_left_mid_y = (parent.y + parent.height / 2) * dpi

                # 计算子节点右边线中部
                child_right_mid_x = (child.x + child.width) * dpi
                child_right_mid_y = (child.y + child.height / 2) * dpi

                # 计算水平间距的中间位置
                mid_x = (parent_left_mid_x + child_right_mid_x) / 2

                # 绘制直角连接线
                path.moveTo(parent_left_mid_x, parent_left_mid_y)

                # 如果父节点和子节点的Y坐标相同,直接水平连接
                if abs(parent_left_mid_y - child_right_mid_y) < 1.0:
                    path.lineTo(child_right_mid_x, child_right_mid_y)
                else:
                    # 否则绘制直角连接,垂直导线在水平间距中间
                    # 1. 从父节点左边线中部向左画水平线到水平间距的中间位置
                    path.lineTo(mid_x, parent_left_mid_y)
                    # 2. 从中间位置垂直连接到子节点右边线中部的正左方
                    path.lineTo(mid_x, child_right_mid_y)
                    # 3. 从中间位置向右画水平线到子节点右边线中部
                    path.lineTo(child_right_mid_x, child_right_mid_y)
    else:
        # 配偶关系:保持现有样式(虚线)
        # 直接水平连接两个节点
        start_x = (self.start_node.x + self.start_node.width / 2) * dpi
        start_y = (self.start_node.y + self.start_node.height) * dpi
        end_x = (self.end_node.x + self.end_node.width / 2) * dpi
        end_y = self.end_node.y * dpi
        path.moveTo(start_x, start_y)
        path.lineTo(end_x, end_y)

    self.setPath(path)

    # 绘制末端图形
    if self.end_shape_type != "无":
        self.draw_end_shape(path)

def draw_end_shape(self, path):
    """绘制末端图形"""
    # 获取路径的最后一个点作为末端图形的位置
    path_element_count = path.elementCount()
    if path_element_count == 0:
        return

    last_element = path.elementAt(path_element_count - 1)
    end_x = last_element.x
    end_y = last_element.y

    # 图形大小(像素)
    shape_size = self.end_shape_size

    # 调整末端图形位置,确保垂直导线不会穿过末端图形最上边
    # 对于不同类型的图形,调整方式略有不同

    # 映射轮廓线形
    line_style_map = {
        "无": Qt.NoPen,
        "实线": Qt.SolidLine,
        "虚线": Qt.DashLine,
        "点线": Qt.DotLine,
        "点划线": Qt.DashDotLine
    }
    pen_style = line_style_map.get(self.end_shape_line_style, Qt.NoPen)

    # 创建轮廓画笔
    shape_pen = QPen(self.end_shape_color, self.end_shape_line_width, pen_style)

    # 创建末端图形
    if self.end_shape_type == "圆点":
        # 绘制圆点,确保垂直导线在圆点下方结束
        ellipse_path = QPainterPath()
        ellipse_path.addEllipse(
            end_x - shape_size/2, 
            end_y - shape_size,  # 向上移动一个shape_size,使底部与end_y对齐
            shape_size, 
            shape_size
        )
        # 创建图形项并添加到场景
        shape_item = QGraphicsPathItem(ellipse_path, self)
        shape_item.setBrush(QBrush(self.line_color))
        shape_item.setPen(shape_pen)  # 添加轮廓
    elif self.end_shape_type == "方块":
        # 绘制方块,确保垂直导线在方块下方结束
        rect_path = QPainterPath()
        rect_path.addRect(
            end_x - shape_size/2, 
            end_y - shape_size,  # 向上移动一个shape_size,使底部与end_y对齐
            shape_size, 
            shape_size
        )
        # 创建图形项并添加到场景
        shape_item = QGraphicsPathItem(rect_path, self)
        shape_item.setBrush(QBrush(self.line_color))
        shape_item.setPen(shape_pen)  # 添加轮廓
    elif self.end_shape_type == "箭头":
        # 绘制箭头,确保垂直导线在箭头下方结束
        arrow_path = QPainterPath()
        # 调整箭头位置,使箭头底部与end_y对齐
        arrow_path.moveTo(end_x, end_y)
        arrow_path.lineTo(end_x - shape_size, end_y - shape_size)
        arrow_path.lineTo(end_x + shape_size, end_y - shape_size)
        arrow_path.closeSubpath()
        # 创建图形项并添加到场景
        shape_item = QGraphicsPathItem(arrow_path, self)
        shape_item.setBrush(QBrush(self.line_color))
        shape_item.setPen(shape_pen)  # 添加轮廓
    elif self.end_shape_type == "圆圈":
        # 绘制圆圈(空心圆),确保垂直导线在圆圈下方结束
        circle_path = QPainterPath()
        circle_path.addEllipse(
            end_x - shape_size/2, 
            end_y - shape_size,  # 向上移动一个shape_size,使底部与end_y对齐
            shape_size, 
            shape_size
        )
        # 创建图形项并添加到场景(使用线条而不是填充)
        shape_item = QGraphicsPathItem(circle_path, self)
        shape_item.setPen(shape_pen)  # 使用轮廓画笔

class TreeView(QGraphicsView):
def init(self, parent=None):
super().init(parent)
self.scene = QGraphicsScene()
self.setScene(self.scene)
self.setRenderHint(QPainter.Antialiasing)
self.setRenderHint(QPainter.TextAntialiasing)
self.setDragMode(QGraphicsView.RubberBandDrag)
self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)

    # 支持滚轮缩放
    self.setMouseTracking(True)
    self.scale_factor = 1.0

    # 存储节点项和线条项
    self.node_items = {}
    self.line_items = []

    # 树数据
    self.family_tree = FamilyTree()

    # 设置样式参数
    self.box_color = QColor(255, 255, 255)
    self.border_color = QColor(0, 0, 0)
    self.text_color = QColor(0, 0, 0)
    self.line_color = QColor(0, 0, 0)
    self.border_width = 1
    self.line_width = 1
    self.border_style = Qt.SolidLine
    self.line_style = Qt.SolidLine
    self.font = QFont("SimHei", 12)
    self.corner_radius = 0  # 默认圆角值设为0,表示直角边框

    # 布局参数
    # 根据用户建议,初始默认间距值设置为合理的值,后续会根据节点尺寸自动调整
    self.horizontal_spacing_mm = 30.0  # 水平间距(毫米)- 改为30mm避免重叠

    # 主窗口引用,用于访问样式面板
    self.main_window = None
    self.vertical_spacing_mm = 20.0   # 垂直间距(毫米)- 改为20mm避免重叠
    self._spacing_initialized = False  # 标记间距是否已初始化
    self.layout_direction = 'vertical_normal'  # 布局方向
    self.letter_spacing = 0.0  # 字符间距(0-2.0)
    self.end_shape_type = "无"  # 末端图形类型,默认为无
    self.end_shape_size = 5.0  # 末端图形大小(像素),默认为5像素
    self.end_shape_line_style = "实线"  # 末端图形轮廓线形,默认为实线
    self.end_shape_line_width = 1.0  # 末端图形轮廓粗细(像素),默认为1像素
    self.end_shape_color = QColor(0, 0, 0)  # 末端图形轮廓颜色,默认为黑色
    self.text_direction = "横向"  # 文字方向,默认为横向

    # 自定义节点尺寸参数
    self.custom_box_width_mm = None  # 自定义节点宽度(毫米),None表示自动计算
    self.custom_box_height_mm = None  # 自定义节点高度(毫米),None表示自动计算

    # 画布大小参数
    self.canvas_width_cm = 21.0  # 画布宽度(厘米),默认A4宽度
    self.canvas_height_cm = 29.7  # 画布高度(厘米),默认A4高度
    self.canvas_size = "A4"  # 画布规格,默认为A4

    # 边距参数
    self.left_margin_cm = 1.5  # 左边距(厘米),默认1.5cm
    self.right_margin_cm = 1.5  # 右边距(厘米),默认1.5cm
    self.top_margin_cm = 1.5  # 页眉距(厘米),默认1.5cm
    self.bottom_margin_cm = 1.5  # 页脚距(厘米),默认1.5cm

    # 创建右键菜单
    self.setContextMenuPolicy(Qt.CustomContextMenu)
    self.customContextMenuRequested.connect(self.show_context_menu)
    self.context_menu = QMenu(self)
    self.add_node_action = QAction("添加子节点", self)
    self.add_node_action.triggered.connect(self.add_node_from_menu)
    self.add_spouse_action = QAction("添加配偶", self)
    self.add_spouse_action.triggered.connect(self.add_spouse_from_menu)
    self.add_elder_brother_action = QAction("添加哥哥", self)
    self.add_elder_brother_action.triggered.connect(self.add_elder_brother_from_menu)
    self.add_younger_brother_action = QAction("添加弟弟", self)
    self.add_younger_brother_action.triggered.connect(self.add_younger_brother_from_menu)
    self.edit_node_action = QAction("编辑节点", self)
    self.edit_node_action.triggered.connect(self.edit_node_from_menu)
    self.delete_node_action = QAction("删除节点", self)
    self.delete_node_action.triggered.connect(self.delete_node_from_menu)
    self.context_menu.addActions([self.add_node_action, self.add_spouse_action, self.add_elder_brother_action, self.add_younger_brother_action, self.edit_node_action, self.delete_node_action])

    # 当前选中的节点
    self.selected_node = None

def show_context_menu(self, position):
    """显示右键菜单"""
    # 将视图坐标转换为场景坐标
    scene_pos = self.mapToScene(position)

    # 查找鼠标位置下的节点项
    items = self.scene.items(scene_pos)
    node_item = None
    for item in items:
        if isinstance(item, NodeItem):
            node_item = item
            break
        elif isinstance(item.parentItem(), NodeItem):
            node_item = item.parentItem()
            break

    # 如果找到节点项,更新选中的节点
    if node_item:
        self.selected_node = node_item.node
        # 选择该节点项
        self.scene.clearSelection()
        node_item.setSelected(True)
        # 显示菜单
        self.context_menu.exec_(self.mapToGlobal(position))

def add_node_from_menu(self):
    """从右键菜单添加子节点"""
    if not self.selected_node:
        return

    # 获取新节点的名称
    name, ok = QInputDialog.getText(self, "添加子节点", "请输入子节点名称:")
    if ok and name:
        # 创建新节点
        new_id = f"node_{len(self.family_tree.nodes) + 1}"
        new_node = Node(new_id, name)
        # 添加到树中
        self.family_tree.add_node(new_node)
        # 设置父子关系
        self.selected_node.add_child(new_node)
        # 更新视图
        self.update_tree_display()

def add_spouse_from_menu(self):
    """从右键菜单添加配偶"""
    if not self.selected_node:
        return

    # 获取配偶节点的名称
    name, ok = QInputDialog.getText(self, "添加配偶", "请输入配偶名称:")
    if ok and name:
        # 创建配偶节点
        spouse_id = f"node_{len(self.family_tree.nodes) + 1}"
        spouse_node = Node(spouse_id, name)
        # 添加到树中
        self.family_tree.add_node(spouse_node)
        # 设置配偶关系
        self.selected_node.add_spouse(spouse_node)
        # 更新视图
        self.update_tree_display()

def edit_node_from_menu(self):
    """从右键菜单编辑节点"""
    if not self.selected_node:
        return

    # 获取新的节点名称
    name, ok = QInputDialog.getText(self, "编辑节点", "请输入新的节点名称:", text=self.selected_node.name)
    if ok and name:
        # 更新节点名称
        self.selected_node.name = name
        # 更新视图
        self.update_tree_display()

def add_elder_brother_from_menu(self):
    """从右键菜单添加哥哥(在当前节点左边)"""
    if not self.selected_node:
        return

    # 检查选中的节点是否有父节点
    if not self.selected_node.parent:
        QMessageBox.information(self, "添加哥哥", "根节点不能添加哥哥!")
        return

    # 获取新节点的名称
    name, ok = QInputDialog.getText(self, "添加哥哥", "请输入哥哥名称:")
    if ok and name:
        # 生成比当前节点ID小的ID,确保按ID排序时在左边
        current_id = self.selected_node.id
        # 尝试提取数字部分
        try:
            if current_id.startswith('node_'):
                current_num = int(current_id.split('_')[1])
                new_num = current_num - 0.5  # 使用小数确保在当前节点之前
                new_id = f"node_{new_num}"
            else:
                # 如果ID格式不是node_数字,使用基于总数的ID
                new_id = f"node_{len(self.family_tree.nodes) + 1}"
        except:
            new_id = f"node_{len(self.family_tree.nodes) + 1}"

        new_node = Node(new_id, name)
        # 添加到树中
        self.family_tree.add_node(new_node)
        # 获取选中节点在父节点子节点列表中的索引
        parent = self.selected_node.parent
        child_index = parent.children.index(self.selected_node)
        # 在选中节点前插入新节点
        parent.children.insert(child_index, new_node)
        # 设置父子关系
        new_node.parent = parent
        # 更新视图
        self.update_tree_display()

def add_younger_brother_from_menu(self):
    """从右键菜单添加弟弟(在当前节点右边)"""
    if not self.selected_node:
        return

    # 检查选中的节点是否有父节点
    if not self.selected_node.parent:
        QMessageBox.information(self, "添加弟弟", "根节点不能添加弟弟!")
        return

    # 获取新节点的名称
    name, ok = QInputDialog.getText(self, "添加弟弟", "请输入弟弟名称:")
    if ok and name:
        # 生成比当前节点ID大的ID,确保按ID排序时在右边
        current_id = self.selected_node.id
        # 尝试提取数字部分
        try:
            if current_id.startswith('node_'):
                current_num = int(current_id.split('_')[1])
                new_num = current_num + 0.5  # 使用小数确保在当前节点之后
                new_id = f"node_{new_num}"
            else:
                # 如果ID格式不是node_数字,使用基于总数的ID
                new_id = f"node_{len(self.family_tree.nodes) + 1}"
        except:
            new_id = f"node_{len(self.family_tree.nodes) + 1}"

        new_node = Node(new_id, name)
        # 添加到树中
        self.family_tree.add_node(new_node)
        # 获取选中节点在父节点子节点列表中的索引
        parent = self.selected_node.parent
        child_index = parent.children.index(self.selected_node)
        # 在选中节点后插入新节点
        parent.children.insert(child_index + 1, new_node)
        # 设置父子关系
        new_node.parent = parent
        # 更新视图
        self.update_tree_display()

def delete_node_from_menu(self):
    """从右键菜单删除节点"""
    if not self.selected_node:
        return

    # 确认删除
    reply = QMessageBox.question(self, "确认删除", f"确定要删除节点'{self.selected_node.name}'吗?\n删除节点将同时删除其所有子节点。", 
                                QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
    if reply == QMessageBox.Yes:
        # 保存要删除的节点ID
        node_id = self.selected_node.id
        # 如果删除的是根节点,且树中还有其他节点,需要先更新根节点
        if self.selected_node == self.family_tree.root and len(self.family_tree.nodes) > 1:
            # 找到下一个可能的根节点(例如,第一个子节点)
            for node in self.family_tree.nodes.values():
                if node != self.selected_node:
                    self.family_tree.root = node
                    break
        # 删除节点
        self.family_tree.remove_node(node_id)
        # 更新视图
        self.update_tree_display()
        # 重置选中的节点
        self.selected_node = None

def wheelEvent(self, event):
    """处理滚轮事件,实现缩放,无限制放大"""
    # 检查是否按住了Ctrl键
    if event.modifiers() & Qt.ControlModifier:
        # 设置缩放标志,防止resizeEvent干扰
        self._is_zooming = True

        try:
            # 处理滚轮事件
            if event.angleDelta().y() > 0:
                # 放大,无限制最大缩放因子
                scale_amount = 1.1
                print(f"准备放大 - 当前缩放因子: {self.scale_factor}, 缩放比例: {scale_amount}")
                self.scale(scale_amount, scale_amount)
                self.scale_factor *= scale_amount
                print(f"放大后缩放因子: {self.scale_factor}")
            else:
                # 缩小,但限制最小缩放因子为0.1
                if self.scale_factor > 0.1:
                    scale_amount = 0.9
                    print(f"准备缩小 - 当前缩放因子: {self.scale_factor}, 缩放比例: {scale_amount}")
                    self.scale(scale_amount, scale_amount)
                    self.scale_factor *= scale_amount
                    print(f"缩小后缩放因子: {self.scale_factor}")
        except Exception as e:
            print(f"缩放时出错: {str(e)}")
            import traceback
            traceback.print_exc()
        finally:
            # 无论成功失败,都要重置缩放标志
            self._is_zooming = False

        # 防止事件被进一步处理
        event.accept()
    else:
        # 否则,使用默认的滚动行为
        super().wheelEvent(event)

def resizeEvent(self, event):
    """处理窗口大小变化事件,确保画布大小按当前软件窗口的大小等比完整显示"""
    super().resizeEvent(event)

    # 如果场景中有项,且当前不在缩放操作中
    if self.scene.items() and not getattr(self, '_is_zooming', False):
        # 获取所有项的边界矩形
        items_rect = self.scene.itemsBoundingRect()
        # 调整场景矩形,添加一些边距
        self.scene.setSceneRect(items_rect.adjusted(-50, -50, 50, 50))
        # 不再自动适应窗口大小,保持当前缩放状态
        # 移除 fitInView 调用,避免重置缩放因子
        # 移除缩放因子重置,保持当前缩放状态
        # self.fitInView(items_rect, Qt.KeepAspectRatio)
        # self.scale_factor = 1.0

def _draw_canvas_border(self):
    """绘制画布边界,设置画布大小,并根据阅读方向设置画图起始位置"""
    # 将画布尺寸从厘米转换为像素(假设96 DPI)
    dpi = 96.0
    cm_to_inch = 0.393701
    canvas_width_px = self.canvas_width_cm * cm_to_inch * dpi
    canvas_height_px = self.canvas_height_cm * cm_to_inch * dpi

    # 创建白色画布边界,画板区域设为白色
    border_pen = QPen(QColor(200, 200, 200), 1, Qt.SolidLine)
    border_brush = QBrush(QColor(255, 255, 255), Qt.SolidPattern)
    # 设置场景背景为浅灰色,画板区域外设置为浅灰色
    self.scene.setBackgroundBrush(QBrush(QColor(245, 245, 245), Qt.SolidPattern))

    # 根据阅读方向设置画布位置和画图起始位置
    if self.layout_direction in ['vertical_normal', 'horizontal_normal']:
        # 正翻:从画布的左上角开始绘制
        canvas_rect = QRectF(0, 0, canvas_width_px, canvas_height_px)
    else:
        # 反翻:从画布的左上角开始绘制(与正翻相同)
        # 画布位置不需要调整,反翻的效果通过计算布局时的X坐标调整实现
        canvas_rect = QRectF(0, 0, canvas_width_px, canvas_height_px)

    # 创建画布边界项
    canvas_item = QGraphicsRectItem(canvas_rect)
    canvas_item.setPen(border_pen)
    canvas_item.setBrush(border_brush)
    canvas_item.setZValue(-1)  # 置于底层
    self.scene.addItem(canvas_item)

    # 设置QGraphicsScene的大小
    scene_rect = canvas_rect.adjusted(-100, -100, 100, 100)
    self.scene.setSceneRect(scene_rect)

def keyPressEvent(self, event):
    """处理键盘事件"""
    if event.key() == Qt.Key_Plus and (event.modifiers() & Qt.ControlModifier):
        # 放大,无限制最大缩放因子
        try:
            scale_amount = 1.1
            print(f"准备放大 - 当前缩放因子: {self.scale_factor}, 缩放比例: {scale_amount}")
            self.scale(scale_amount, scale_amount)
            self.scale_factor *= scale_amount
            print(f"放大后缩放因子: {self.scale_factor}")
        except Exception as e:
            print(f"缩放时出错: {str(e)}")
            import traceback
            traceback.print_exc()
    elif event.key() == Qt.Key_Minus and (event.modifiers() & Qt.ControlModifier):
        # 缩小,但限制最小缩放因子为0.1
        try:
            if self.scale_factor > 0.1:
                scale_amount = max(0.9, 0.1 / self.scale_factor)
                self.scale(scale_amount, scale_amount)
                self.scale_factor *= scale_amount

            # 确保所有节点都在视图范围内
            if self.scene.items():
                items_rect = self.scene.itemsBoundingRect()
                self.scene.setSceneRect(items_rect.adjusted(-50, -50, 50, 50))
        except Exception as e:
            print(f"缩放时出错: {str(e)}")
            # 重置缩放以防止程序崩溃
            self.resetTransform()
            self.scale_factor = 1.0
    elif event.key() == Qt.Key_F and (event.modifiers() & Qt.ControlModifier):
            # 适应窗口
            if self.scene.items():
                # 重置变换矩阵
                self.resetTransform()
                # 适应窗口
                self.fitInView(self.scene.itemsBoundingRect(), Qt.KeepAspectRatio)
                # 重置缩放因子为1.0,因为fitInView会重置变换矩阵
                self.scale_factor = 1.0
    else:
        super().keyPressEvent(event)

def mousePressEvent(self, event):
    """处理鼠标按下事件"""
    if event.button() == Qt.MiddleButton:
        # 中键拖动开始
        self.setDragMode(QGraphicsView.ScrollHandDrag)
        # 保存当前鼠标位置
        self.last_mouse_pos = event.pos()
        # 调用父类方法处理事件
        super().mousePressEvent(event)
    else:
        super().mousePressEvent(event)

        # 更新选中的节点
        items = self.scene.selectedItems()
        if items:
            for item in items:
                if isinstance(item, NodeItem):
                    self.selected_node = item.node
                    break
                elif isinstance(item.parentItem(), NodeItem):
                    self.selected_node = item.parentItem().node
                    break
        else:
            self.selected_node = None

def mouseReleaseEvent(self, event):
    """处理鼠标释放事件"""
    if event.button() == Qt.MiddleButton:
        # 中键拖动结束
        self.setDragMode(QGraphicsView.RubberBandDrag)
    # 总是调用父类方法处理事件
    super().mouseReleaseEvent(event)

def mouseMoveEvent(self, event):
    """处理鼠标移动事件,实现平滑平移"""
    try:
        # 如果正在中键拖动
        if self.dragMode() == QGraphicsView.ScrollHandDrag:
            # 计算鼠标移动的距离
            delta = event.pos() - self.last_mouse_pos

            # 使用平滑的平移速度
            # 根据缩放因子调整平移速度,确保在不同缩放级别下平移感觉一致
            scale_factor = self.scale_factor if hasattr(self, 'scale_factor') else 1.0
            smooth_delta = delta / scale_factor

            # 平移视图
            self.translate(smooth_delta.x(), smooth_delta.y())

            # 更新最后鼠标位置
            self.last_mouse_pos = event.pos()

            # 接受事件,防止其他处理
            event.accept()
        else:
            super().mouseMoveEvent(event)
    except Exception as e:
        print(f"鼠标移动事件出错: {str(e)}")
        super().mouseMoveEvent(event)

def update_tree_display(self, adjust_view=True):
    """更新树的显示"""
    # 清空场景
    self.scene.clear()
    self.node_items.clear()
    self.line_items.clear()

    if not self.family_tree or not self.family_tree.root:
        # 即使没有树数据,也绘制画布边界
        self._draw_canvas_border()
        return

    # 计算节点位置
    self.calculate_layout()

    # 绘制画布边界
    self._draw_canvas_border()

    # 创建节点项和线条项
    try:
        self._create_node_items()
        self._create_line_items()
    except Exception as e:
        error_message = str(e)
        # 检查是否是'NodeItem''Nodeitem'错误
        if ('NodeItem' in error_message or 'Nodeitem' in error_message) and 'text_color' in error_message:
            # 这种情况可能是类名拼写错误导致的
            # 我们尝试修复问题:确保TreeView类有text_color属性
            if not hasattr(self, 'text_color'):
                self.text_color = QColor(0, 0, 0)
                print("Warning: Added missing text_color attribute to TreeView")

            # 重新尝试创建节点项和线条项
            try:
                self.scene.clear()
                self.node_items.clear()
                self.line_items.clear()
                self._create_node_items()
                self._create_line_items()
            except Exception as retry_error:
                # 如果再次失败,显示详细错误信息
                error_type = type(retry_error).__name__
                detailed_message = f"更新显示时出错:\n类型: {error_type}\n消息: {str(retry_error)}"
                print(detailed_message)
                QMessageBox.critical(self, "显示错误", f"更新显示时出错:\n{str(retry_error)}\n\n这可能是由于'NodeItem'类名拼写错误导致的。\n请查看控制台获取详细信息。")
                return
        else:
            # 其他错误
            error_type = type(e).__name__
            detailed_message = f"更新显示时出错:\n类型: {error_type}\n消息: {error_message}"
            print(detailed_message)
            QMessageBox.critical(self, "显示错误", f"更新显示时出错:\n{error_message}\n\n请查看控制台获取详细信息")
            return

    # 调整场景大小
    if self.scene.items():
        # 设置场景矩形以包含所有项目,并添加足够的边距
        items_rect = self.scene.itemsBoundingRect()
        self.scene.setSceneRect(items_rect.adjusted(-100, -100, 100, 100))

        # 如果需要调整视图,并且当前视图没有包含所有项目,尝试调整视图以显示所有内容
        if adjust_view:
            current_view_rect = self.mapToScene(self.viewport().rect()).boundingRect()
            if not current_view_rect.contains(items_rect):
                # 保存当前缩放因子
                current_zoom = self.scale_factor
                # 尝试适应窗口
                self.fitInView(items_rect, Qt.KeepAspectRatio)
                # 计算新的缩放因子
                new_zoom = min(self.viewport().width() / items_rect.width(), 
                             self.viewport().height() / items_rect.height())
                # 如果新的缩放因子小于当前缩放因子,说明视图会变小,恢复原来的缩放
                if new_zoom < current_zoom * 0.9:  # 有一定的容差
                    # 恢复缩放
                    self.resetTransform()
                    self.scale(current_zoom, current_zoom)
                    # 调整视图中心到项目中心
                    self.centerOn(items_rect.center())
                else:
                    # 只有当new_zoom大于1时才更新缩放因子,否则保持当前缩放因子
                    if new_zoom > 1:
                        self.scale_factor = new_zoom
                    else:
                        # 即使new_zoom小于1,也保持当前缩放因子
                        pass

def calculate_layout(self):
    """根据绘图逻辑.txt中的逻辑重新编写的家族树布局计算方法"""
    if not self.family_tree or not self.family_tree.root:
        return

    # 重置为默认参数:12号字体
    self.font_size = 12

    print("\n===== 开始计算家族树布局 ====")

    # 1. 计算所有节点的层级(基于父子关系的严格层级)
    print("  计算所有节点的层级...")

    # 基于父子关系递归计算层级
    def calculate_levels(node, level):
        node.level = level
        print(f"        节点 {node.name} 的层级: {level}")
        for child in node.children:
            calculate_levels(child, level + 1)

    # 从根节点开始计算层级
    if self.family_tree.root:
        calculate_levels(self.family_tree.root, 0)

    # 验证层级计算
    print("  层级计算结果:")
    # 收集每个层级的节点
    level_nodes = {}
    all_nodes = []

    def collect_nodes(node):
        if node:
            if node.level not in level_nodes:
                level_nodes[node.level] = []
            level_nodes[node.level].append(node)
            all_nodes.append(node)
            for child in node.children:
                collect_nodes(child)

    collect_nodes(self.family_tree.root)

    for level, nodes in level_nodes.items():
        print(f"    层级 {level}: {len(nodes)} 个节点")
        # 打印前几个节点的名称
        node_names = [node.name for node in nodes[:5]]
        if len(nodes) > 5:
            node_names.append("...")
        print(f"      节点: {', '.join(node_names)}")

    # 2. 收集所有节点并按层级分组
    print("  收集所有节点并按层级分组...")

    # 已经在层级计算时收集了level_nodes和all_nodes
    # 这里只需要确定最大层级
    max_level = max(level_nodes.keys()) if level_nodes else 0


    print(f"  总节点数: {len(all_nodes)}, 最大层级: {max_level}")

    # 3. 计算每个节点的宽度和高度
    print("  计算每个节点的宽度和高度...")

    # 使用用户设置的节点尺寸,如果没有设置则使用默认值
    if hasattr(self, 'custom_box_width_mm') and self.custom_box_width_mm is not None and self.custom_box_width_mm > 0:
        fixed_node_width_mm = self.custom_box_width_mm
    else:
        fixed_node_width_mm = 12  # 默认宽度

    if hasattr(self, 'custom_box_height_mm') and self.custom_box_height_mm is not None and self.custom_box_height_mm > 0:
        fixed_node_height_mm = self.custom_box_height_mm
    else:
        fixed_node_height_mm = 6  # 默认高度

    # 转换为英寸单位
    fixed_node_width_inch = self.mm_to_inch(fixed_node_width_mm)
    fixed_node_height_inch = self.mm_to_inch(fixed_node_height_mm)

    print(f"  使用节点尺寸: {fixed_node_width_mm}mm x {fixed_node_height_mm}mm")

    for node in all_nodes:
        # 设置节点尺寸
        node.width = fixed_node_width_inch
        node.height = fixed_node_height_inch

    # 4. 设置间距
    print("  设置节点间距...")

    # 直接使用用户设置的间距值,如果没有设置则使用默认值
    if not hasattr(self, 'horizontal_spacing_mm') or self.horizontal_spacing_mm is None:
        self.horizontal_spacing_mm = 50
    if not hasattr(self, 'vertical_spacing_mm') or self.vertical_spacing_mm is None:
        self.vertical_spacing_mm = 100

    # 转换间距到英寸
    horizontal_spacing_inch = self.mm_to_inch(self.horizontal_spacing_mm)
    vertical_spacing_inch = self.mm_to_inch(self.vertical_spacing_mm)

    print(f"  水平间距: {self.horizontal_spacing_mm}mm, 垂直间距: {self.vertical_spacing_mm}mm")

    # 5. 计算每个节点的子树宽度(超紧凑型算法)
    print("  计算每个节点的子树宽度...")

    # 计算每个节点的子树宽度(以实际空间为单位)
    def calculate_subtree_width(node):
        """递归计算子树的宽度(超紧凑型算法)"""
        if not node:
            return 0

        if not node.children:
            # 没有子节点,子树宽度就是节点本身的宽度
            node.subtree_width = fixed_node_width_inch
            return node.subtree_width

        # 计算所有子节点的子树宽度
        child_widths = []
        total_child_width = 0
        min_spacing = self.mm_to_inch(1)  # 最小间距1mm,超紧凑

        for child in sorted(node.children, key=lambda n: n.id):
            child_width = calculate_subtree_width(child)
            child_widths.append(child_width)
            total_child_width += child_width

        # 计算子节点之间的总间距
        total_spacing = min_spacing * (len(node.children) - 1)

        # 子树宽度 = 所有子节点宽度之和 + 子节点之间的间距
        node.subtree_width = total_child_width + total_spacing

        # 确保子树宽度至少等于节点本身的宽度
        node.subtree_width = max(node.subtree_width, fixed_node_width_inch)

        return node.subtree_width

    # 从根节点开始计算子树宽度
    if self.family_tree.root:
        calculate_subtree_width(self.family_tree.root)

    # 打印子树宽度计算结果
    print("  子树宽度计算结果:")
    for node in all_nodes:
        print(f"    节点 {node.name} 的子树宽度: {node.subtree_width:.4f}英寸")

    # 6. 为每个层级分配坐标
    print("  为每个层级分配坐标...")

    # 竖排、正翻的生成方式
    # 竖排布局:同世次人员图形从左往右排列
    # 首先计算每个层级的Y坐标(统一垂直间距)
    level_y = {}
    base_y = fixed_node_height_inch / 2  # 根节点Y坐标(顶部)
    level_spacing = fixed_node_height_inch + vertical_spacing_inch  # 固定层间距

    for level in range(max_level + 1):
        level_y[level] = base_y + level * level_spacing

    # 为每个节点设置Y坐标
    for node in all_nodes:
        node.y = level_y[node.level]

    # 7. 计算每个节点的X坐标(紧凑型算法)
    print("  计算每个节点的X坐标...")

    # 递归设置节点的X坐标
    def set_node_x(node, x_offset):
        """递归设置节点的X坐标(紧凑型算法)"""
        if not node:
            return x_offset

        # 计算当前节点的X坐标,使其位于子树宽度的中心
        node.x = x_offset + node.subtree_width / 2
        print(f"        节点 {node.name} 的X坐标: {node.x:.4f}")

        if not node.children:
            return x_offset + node.subtree_width

        # 递归设置子节点的X坐标
        current_offset = x_offset
        min_spacing = self.mm_to_inch(1)  # 最小间距1mm,超紧凑

        for child in sorted(node.children, key=lambda n: n.id):  # 按ID排序子节点
            current_offset = set_node_x(child, current_offset)
            # 添加子节点之间的间距
            if child != sorted(node.children, key=lambda n: n.id)[-1]:
                current_offset += min_spacing

        return x_offset + node.subtree_width

    # 从根节点开始设置X坐标
    if self.family_tree.root:
        set_node_x(self.family_tree.root, 0)

    # 8. 调整节点位置,确保父节点位于子节点组的中心
    print("  调整节点位置,确保父节点位于子节点组的中心...")

    # 递归调整父节点位置
    def adjust_parent_position(node):
        """递归调整父节点位置,确保其位于子节点组的中心"""
        if not node or not node.children:
            return

        # 首先递归调整所有子节点的位置
        for child in node.children:
            adjust_parent_position(child)

        # 计算子节点组的左右边界
        sorted_children = sorted(node.children, key=lambda n: n.id)  # 按ID排序子节点
        min_child_x = min(child.x - child.width/2 for child in sorted_children)
        max_child_x = max(child.x + child.width/2 for child in sorted_children)

        # 计算子节点组的中心
        child_group_center = (min_child_x + max_child_x) / 2

        # 调整父节点位置使其居中于子节点组
        if abs(node.x - child_group_center) > 0.001:
            # 计算需要移动的距离
            delta_x = child_group_center - node.x
            node.x = child_group_center
            print(f"        调整父节点 {node.name} 的位置以居中于子节点组: {child_group_center:.4f}")

            # 调整父节点的所有祖先节点,保持整体布局紧凑
            current = node.parent
            while current:
                # 重新计算祖先节点的子节点组中心
                sorted_ancestor_children = sorted(current.children, key=lambda n: n.id)
                if sorted_ancestor_children:
                    ancestor_min_x = min(c.x - c.width/2 for c in sorted_ancestor_children)
                    ancestor_max_x = max(c.x + c.width/2 for c in sorted_ancestor_children)
                    ancestor_center = (ancestor_min_x + ancestor_max_x) / 2

                    if abs(current.x - ancestor_center) > 0.001:
                        current.x = ancestor_center
                        print(f"        调整祖先节点 {current.name} 的位置: {ancestor_center:.4f}")
                current = current.parent

    # 从根节点开始调整父节点位置
    if self.family_tree.root:
        adjust_parent_position(self.family_tree.root)

    # 9. 全局优化布局,减少空白空间
    print("  全局优化布局,减少空白空间...")

    # 全局优化函数:尝试向左移动节点组以减少空白空间
    def optimize_layout():
        """全局优化布局,尝试向左移动节点组以减少空白空间"""
        optimized = False
        min_spacing = self.mm_to_inch(1)  # 最小间距1mm,超紧凑

        # 计算节点及其所有子树的边界
        def get_subtree_bounds(node):
            """获取节点及其所有子节点的边界"""
            min_x = node.x - node.width / 2
            max_x = node.x + node.width / 2
            min_y = node.y - node.height / 2
            max_y = node.y + node.height / 2

            for child in node.children:
                child_min_x, child_min_y, child_max_x, child_max_y = get_subtree_bounds(child)
                min_x = min(min_x, child_min_x)
                max_x = max(max_x, child_max_x)
                min_y = min(min_y, child_min_y)
                max_y = max(max_y, child_max_y)

            return (min_x, min_y, max_x, max_y)

        # 获取所有节点的列表
        all_nodes_list = []
        def collect_all_nodes(node):
            if node:
                all_nodes_list.append(node)
                for child in node.children:
                    collect_all_nodes(child)

        if self.family_tree.root:
            collect_all_nodes(self.family_tree.root)

        # 按层级从下往上优化
        for level in range(max_level, -1, -1):
            if level not in level_nodes:
                continue

            nodes = level_nodes[level]
            # 按X坐标排序节点
            sorted_nodes = sorted(nodes, key=lambda n: n.x)

            if len(sorted_nodes) <= 1:
                continue

            # 检查每个节点组是否可以向左移动
            for i in range(1, len(sorted_nodes)):
                current_node = sorted_nodes[i]

                # 计算当前节点及其子树的左边界
                current_min_x, _, _, _ = get_subtree_bounds(current_node)

                # 计算左侧所有节点(包括不同层级)的最大右边界
                max_left_right = 0

                # 检查所有左侧节点
                for left_node in all_nodes_list:
                    # 只考虑X坐标小于当前节点的节点
                    if left_node.x >= current_node.x:
                        continue

                    # 计算左侧节点及其子树的右边界
                    _, _, left_max_x, _ = get_subtree_bounds(left_node)
                    max_left_right = max(max_left_right, left_max_x)

                # 计算可以向左移动的距离
                available_space = current_min_x - max_left_right
                if available_space > min_spacing:
                    # 可以向左移动,计算移动距离
                    move_distance = available_space - min_spacing

                    # 移动当前节点及其子树
                    def move_subtree(node, distance):
                        """递归移动节点及其所有子节点"""
                        node.x -= distance
                        for child in node.children:
                            move_subtree(child, distance)

                    # 移动当前节点及其子树
                    move_subtree(current_node, move_distance)

                    # 调整当前节点的所有祖先节点位置
                    ancestor = current_node.parent
                    while ancestor:
                        # 重新计算祖先节点的子节点组中心
                        sorted_ancestor_children = sorted(ancestor.children, key=lambda n: n.id)
                        if sorted_ancestor_children:
                            ancestor_min_x = min(c.x - c.width/2 for c in sorted_ancestor_children)
                            ancestor_max_x = max(c.x + c.width/2 for c in sorted_ancestor_children)
                            ancestor_center = (ancestor_min_x + ancestor_max_x) / 2

                            if abs(ancestor.x - ancestor_center) > 0.001:
                                ancestor.x = ancestor_center
                        ancestor = ancestor.parent

                    optimized = True
                    print(f"    优化层级 {level} 节点 {current_node.name} 向左移动: {move_distance:.4f}英寸")
                    print(f"    移动前左边界: {current_min_x:.4f}, 左侧最大右边界: {max_left_right:.4f}")
                    print(f"    可用空间: {available_space:.4f}, 移动后左边界: {current_min_x - move_distance:.4f}")

                    # 重新收集所有节点,因为位置已经改变
                    all_nodes_list.clear()
                    if self.family_tree.root:
                        collect_all_nodes(self.family_tree.root)

                    # 重新排序当前层级的节点
                    sorted_nodes = sorted(nodes, key=lambda n: n.x)

        return optimized

    # 执行全局优化
    optimization_count = 0
    max_optimizations = 5  # 最大优化次数

    while optimization_count < max_optimizations:
        if optimize_layout():
            optimization_count += 1
            print(f"  完成第 {optimization_count} 次优化")
        else:
            break

    # 10. 验证同一层级的节点之间没有重叠
    print("  验证同一层级的节点之间没有重叠...")

    # 验证同一层级的节点之间没有重叠
    overlap_found = False

    for level in range(max_level + 1):
        nodes = level_nodes[level]

        # 循环处理,直到该层级没有重叠
        while True:
            # 按X坐标排序节点
            sorted_nodes = sorted(nodes, key=lambda n: n.x)

            if len(sorted_nodes) <= 1:
                break

            # 检查是否有重叠
            overlap_detected = False

            for i in range(len(sorted_nodes) - 1):
                curr_node = sorted_nodes[i]
                next_node = sorted_nodes[i + 1]

                # 计算curr_node及其子树的右边界
                def get_subtree_right(node):
                    max_right = node.x + node.width / 2
                    for child in node.children:
                        child_right = get_subtree_right(child)
                        max_right = max(max_right, child_right)
                    return max_right

                # 计算next_node及其子树的左边界
                def get_subtree_left(node):
                    min_left = node.x - node.width / 2
                    for child in node.children:
                        child_left = get_subtree_left(child)
                        min_left = min(min_left, child_left)
                    return min_left

                curr_right = get_subtree_right(curr_node)
                next_left = get_subtree_left(next_node)

                if curr_right > next_left:
                    # 有重叠,计算需要移动的距离
                    overlap = curr_right - next_left
                    min_spacing = self.mm_to_inch(1)  # 最小间距1mm,超紧凑
                    offset = overlap + min_spacing

                    print(f"    层级 {level} 节点 {curr_node.name} 和 {next_node.name} 之间存在重叠,重叠量: {overlap:.4f}英寸")
                    print(f"    需要向右移动 {offset:.4f}英寸")

                    # 向右移动next_node及其后续节点及其所有子节点
                    for j in range(i + 1, len(sorted_nodes)):
                        move_node = sorted_nodes[j]

                        # 递归移动节点及其所有子节点
                        def move_subtree_recursive(node, distance):
                            node.x += distance
                            for child in node.children:
                                move_subtree_recursive(child, distance)

                        # 移动当前节点及其子树
                        move_subtree_recursive(move_node, offset)

                        # 调整移动节点的所有祖先节点位置
                        def adjust_ancestors(node):
                            ancestor = node.parent
                            while ancestor:
                                # 重新计算祖先节点的子节点组中心
                                sorted_ancestor_children = sorted(ancestor.children, key=lambda n: n.id)
                                if sorted_ancestor_children:
                                    ancestor_min_x = min(c.x - c.width/2 for c in sorted_ancestor_children)
                                    ancestor_max_x = max(c.x + c.width/2 for c in sorted_ancestor_children)
                                    ancestor_center = (ancestor_min_x + ancestor_max_x) / 2

                                    if abs(ancestor.x - ancestor_center) > 0.001:
                                        ancestor.x = ancestor_center
                                ancestor = ancestor.parent

                        # 调整祖先节点位置
                        adjust_ancestors(move_node)

                    overlap_detected = True
                    overlap_found = True
                    print(f"    已修复重叠,向右移动了 {i + 1} 及后续节点")

                    # 重新检查该层级,因为移动可能导致新的重叠
                    break

            if not overlap_detected:
                # 该层级没有重叠,退出循环
                break

    # 11. 再次执行全局优化,确保布局紧凑
    if not overlap_found:
        print("  再次执行全局优化...")
        optimization_count = 0
        max_optimizations = 3  # 最大优化次数

        while optimization_count < max_optimizations:
            if optimize_layout():
                optimization_count += 1
                print(f"  完成第 {optimization_count} 次优化")
            else:
                break

    # 10. 确保所有节点在画布内
    print("  调整整个家族树的位置...")

    # 将画布尺寸从厘米转换为英寸
    cm_to_inch = 0.393701
    canvas_width_inch = self.canvas_width_cm * cm_to_inch
    canvas_height_inch = self.canvas_height_cm * cm_to_inch

    # 将边距从厘米转换为英寸
    left_margin_inch = self.left_margin_cm * cm_to_inch
    right_margin_inch = self.right_margin_cm * cm_to_inch
    top_margin_inch = self.top_margin_cm * cm_to_inch
    bottom_margin_inch = self.bottom_margin_cm * cm_to_inch

    # 计算实际可绘制区域
    drawable_left_inch = left_margin_inch
    drawable_right_inch = canvas_width_inch - right_margin_inch
    drawable_top_inch = top_margin_inch
    drawable_bottom_inch = canvas_height_inch - bottom_margin_inch

    # 计算所有节点的边界
    all_lefts = [node.x - node.width / 2 for node in all_nodes]
    all_rights = [node.x + node.width / 2 for node in all_nodes]
    all_tops = [node.y - node.height / 2 for node in all_nodes]
    all_bottoms = [node.y + node.height / 2 for node in all_nodes]

    min_left = min(all_lefts)
    max_right = max(all_rights)
    min_top = min(all_tops)
    max_bottom = max(all_bottoms)

    # 首先调整垂直位置,确保所有节点在垂直方向上在可绘制区域内
    # 但保持层间距的统一性,只移动整个层级结构
    vertical_offset = drawable_top_inch - min_top
    # 应用垂直偏移(保持原有层间距)
    for node in all_nodes:
        node.y += vertical_offset

    # 竖排、正翻:从画布的左上角开始绘制,同世次人员图形从左往右排列
    # 计算水平偏移量,将整个图形移动到可绘制区域的左侧
    horizontal_offset = drawable_left_inch - min_left
    # 应用水平偏移
    for node in all_nodes:
        node.x += horizontal_offset

    # 最终检查:确保所有节点都在可绘制区域内
    # 重新计算所有节点的边界
    all_lefts = [node.x - node.width / 2 for node in all_nodes]
    all_rights = [node.x + node.width / 2 for node in all_nodes]
    all_tops = [node.y - node.height / 2 for node in all_nodes]
    all_bottoms = [node.y + node.height / 2 for node in all_nodes]

    min_left = min(all_lefts)
    max_right = max(all_rights)
    min_top = min(all_tops)
    max_bottom = max(all_bottoms)

    # 检查水平边界
    if min_left < drawable_left_inch:
        # 如果左侧超出边界,向右移动整个图形
        offset_x = drawable_left_inch - min_left
        for node in all_nodes:
            node.x += offset_x
    elif max_right > drawable_right_inch:
        # 如果右侧超出边界,向左移动整个图形
        offset_x = drawable_right_inch - max_right
        for node in all_nodes:
            node.x += offset_x

    # 检查垂直边界(保持层间距统一)
    if min_top < drawable_top_inch:
        # 如果顶部超出边界,向下移动整个图形但保持层间距
        offset_y = drawable_top_inch - min_top
        for node in all_nodes:
            node.y += offset_y
    elif max_bottom > drawable_bottom_inch:
        # 如果底部超出边界,向上移动整个图形但保持层间距
        offset_y = drawable_bottom_inch - max_bottom
        for node in all_nodes:
            node.y += offset_y

    # 11. 打印布局结果和坐标表
    print("\n===== 布局完成 ====")
    print(f"总节点数: {len(all_nodes)}")
    print(f"最大层级: {max_level}")
    print(f"节点尺寸: {fixed_node_width_mm}mm x {fixed_node_height_mm}mm")
    print(f"水平间距: {self.horizontal_spacing_mm}mm, 垂直间距: {self.vertical_spacing_mm}mm")
    print(f"最终结果: {'布局正确' if not overlap_found else '经过修复已解决重叠'}")

    # 生成坐标表
    print("\n=== 节点坐标表 ===")
    print("ID\t名称\t层级\tX(mm)\tY(mm)\t宽度(mm)\t高度(mm)\t父节点")
    for node in sorted(all_nodes, key=lambda n: (n.level, n.x)):
        parent_name = node.parent.name if node.parent else "无"
        print(f"{node.id}\t{node.name}\t{node.level}\t{self.inch_to_mm(node.x):.1f}\t"
              f"{self.inch_to_mm(node.y):.1f}\t{self.inch_to_mm(node.width):.1f}\t"
              f"{self.inch_to_mm(node.height):.1f}\t{parent_name}")

    # 反写间距值到UI面板
    self.update_ui_spacing_values()

def update_ui_spacing_values(self):
    """将实际使用的间距值反写到UI面板的控件中"""
    if hasattr(self, 'main_window') and self.main_window and hasattr(self.main_window, 'style_panel'):
        # 更新水平间距
        if hasattr(self.main_window.style_panel, 'horizontal_spacing_spin'):
            self.main_window.style_panel.horizontal_spacing_spin.setValue(self.horizontal_spacing_mm)
        # 更新垂直间距
        if hasattr(self.main_window.style_panel, 'vertical_spacing_spin'):
            self.main_window.style_panel.vertical_spacing_spin.setValue(self.vertical_spacing_mm)
        print(f"已更新UI面板间距值:水平={self.horizontal_spacing_mm}mm, 垂直={self.vertical_spacing_mm}mm")

def _calculate_subtree_width(self, node):
    """递归计算子树的宽度"""
    if not node or not node.children:
        node.subtree_width = node.width
        node.subtree_height = node.height
        return

    # 首先递归计算所有子节点的子树宽度
    for child in node.children:
        self._calculate_subtree_width(child)

    # 计算当前节点的子树宽度
    if self.layout_direction in ['vertical_normal', 'vertical_reverse']:
        # 竖式布局:子节点水平排列
        spacing_inch = self.mm_to_inch(self.horizontal_spacing_mm) * 3.0  # 增加200%的水平间距
        total_children_width = sum(child.subtree_width for child in node.children)
        total_spacing = spacing_inch * (len(node.children) - 1)
        node.subtree_width = max(node.width, total_children_width + total_spacing)

        # 计算子树高度
        max_child_height = max(child.subtree_height for child in node.children)
        vertical_spacing_inch = self.mm_to_inch(self.vertical_spacing_mm) * 3.0  # 增加200%的垂直间距
        node.subtree_height = node.height + vertical_spacing_inch + max_child_height
    else:
        # 横式布局:子节点垂直排列
        spacing_inch = self.mm_to_inch(self.vertical_spacing_mm) * 3.0  # 增加200%的垂直间距
        total_children_height = sum(child.subtree_height for child in node.children)
        total_spacing = spacing_inch * (len(node.children) - 1)
        node.subtree_height = max(node.height, total_children_height + total_spacing)

        # 计算子树宽度
        max_child_width = max(child.subtree_width for child in node.children)
        horizontal_spacing_inch = self.mm_to_inch(self.horizontal_spacing_mm) * 3.0  # 增加200%的水平间距
        node.subtree_width = node.width + horizontal_spacing_inch + max_child_width

def _set_node_positions(self, node, start_x, start_y):
    """递归设置节点位置"""
    if not node:
        return

    # 设置当前节点位置
    node.x = start_x + node.width / 2
    node.y = start_y + node.height / 2

    if not node.children:
        return

    # 对子节点按名称排序
    children = sorted(node.children, key=lambda n: n.name)

    if self.layout_direction in ['vertical_normal', 'vertical_reverse']:
        # 竖式布局:子节点在父节点下方,水平排列
        spacing_inch = self.mm_to_inch(self.horizontal_spacing_mm) * 3.0  # 增加200%的水平间距
        current_x = start_x + (node.subtree_width - sum(child.subtree_width for child in children) - spacing_inch * (len(children) - 1)) / 2
        next_y = start_y + node.height + self.mm_to_inch(self.vertical_spacing_mm) * 3.0  # 增加200%的垂直间距

        for child in children:
            self._set_node_positions(child, current_x, next_y)
            current_x += child.subtree_width + spacing_inch

        # 如果是垂直翻转布局,需要进行水平翻转
        if self.layout_direction == 'vertical_reverse':
            center_x = node.x
            for child in children:
                # 计算相对于父节点的水平翻转位置
                flipped_x = 2 * center_x - child.x
                # 计算水平偏移量
                delta_x = flipped_x - child.x
                # 调整子节点及其所有后代节点的位置
                self._adjust_descendants_position(child, delta_x, 0)
    else:
        # 横式布局:子节点在父节点右侧,垂直排列
        spacing_inch = self.mm_to_inch(self.vertical_spacing_mm) * 3.0  # 增加200%的垂直间距
        current_y = start_y + (node.subtree_height - sum(child.subtree_height for child in children) - spacing_inch * (len(children) - 1)) / 2
        next_x = start_x + node.width + self.mm_to_inch(self.horizontal_spacing_mm) * 3.0  # 增加200%的水平间距

        for child in children:
            self._set_node_positions(child, next_x, current_y)
            current_y += child.subtree_height + spacing_inch

        # 如果是水平翻转布局,需要进行垂直翻转
        if self.layout_direction == 'horizontal_reverse':
            center_y = node.y
            for child in children:
                # 计算相对于父节点的垂直翻转位置
                flipped_y = 2 * center_y - child.y
                # 计算垂直偏移量
                delta_y = flipped_y - child.y
                # 调整子节点及其所有后代节点的位置
                self._adjust_descendants_position(child, 0, delta_y)

def _adjust_parent_child_alignments(self, node):
    """调整父子节点之间的对齐关系,确保居中对齐"""
    if not node or not node.children:
        return

    # 递归调整所有子节点
    for child in node.children:
        self._adjust_parent_child_alignments(child)

    # 计算子节点组的中心点
    if self.layout_direction in ['vertical_normal', 'vertical_reverse']:
        # 竖式布局:水平居中对齐
        min_child_x = min(child.x - child.width/2 for child in node.children)
        max_child_x = max(child.x + child.width/2 for child in node.children)
        children_center_x = (min_child_x + max_child_x) / 2

        # 调整父节点位置使其居中于子节点组
        if abs(node.x - children_center_x) > 0.001:
            delta_x = children_center_x - node.x
            node.x = children_center_x
            # 调整父节点的所有祖先节点位置
            self._adjust_ancestor_positions(node, delta_x, 0)
    else:
        # 横式布局:垂直居中对齐
        min_child_y = min(child.y - child.height/2 for child in node.children)
        max_child_y = max(child.y + child.height/2 for child in node.children)
        children_center_y = (min_child_y + max_child_y) / 2

        # 调整父节点位置使其居中于子节点组
        if abs(node.y - children_center_y) > 0.001:
            delta_y = children_center_y - node.y
            node.y = children_center_y
            # 调整父节点的所有祖先节点位置
            self._adjust_ancestor_positions(node, 0, delta_y)

def _resolve_node_overlaps(self, node):
    """解决节点重叠问题"""
    if not node:
        return

    # 递归检查所有子节点
    for child in node.children:
        self._resolve_node_overlaps(child)

    # 检查兄弟节点之间的重叠
    if node.parent:
        siblings = node.parent.children
        for i in range(len(siblings)):
            for j in range(i+1, len(siblings)):
                self._check_and_fix_overlap(siblings[i], siblings[j])

def _check_all_node_overlaps(self):
    """检查并修复所有节点之间的重叠"""
    # 获取所有节点列表
    all_nodes = []

    def collect_nodes(node):
        if node:
            all_nodes.append(node)
            for child in node.children:
                collect_nodes(child)

    if self.family_tree.root:
        collect_nodes(self.family_tree.root)

    # 检查所有节点对之间的重叠
    for i in range(len(all_nodes)):
        for j in range(i+1, len(all_nodes)):
            self._check_and_fix_overlap(all_nodes[i], all_nodes[j])

def _check_and_fix_overlap(self, node1, node2):
    """检查并修复两个节点之间的重叠,增加更强的重叠修复能力"""
    # 计算节点边界
    node1_left = node1.x - node1.width/2
    node1_right = node1.x + node1.width/2
    node2_left = node2.x - node2.width/2
    node2_right = node2.x + node2.width/2
    node1_top = node1.y - node1.height/2
    node1_bottom = node1.y + node1.height/2
    node2_top = node2.y - node2.height/2
    node2_bottom = node2.y + node2.height/2

    # 检查水平重叠(适用于所有布局)
    if node1_right > node2_left and node1_left < node2_right:
        # 计算重叠量
        overlap = min(node1_right - node2_left, node2_right - node1_left)
        # 计算需要的最小间距(增加200%的间距,确保更强的分离效果)
        min_gap = self.mm_to_inch(self.horizontal_spacing_mm) * 3.0
        # 计算需要移动的距离
        required_move = (overlap + min_gap) / 2

        # 计算节点的层级深度
        def get_node_depth(node):
            depth = 0
            current = node
            while current.parent:
                depth += 1
                current = current.parent
            return depth

        # 简单的位置调整,优先移动距离根节点更远的节点
        node1_depth = get_node_depth(node1)
        node2_depth = get_node_depth(node2)

        # 同时移动两个节点组以保持整体布局的平衡
        move_distance1 = required_move * 0.7  # 主要移动距离
        move_distance2 = required_move * 0.3  # 次要移动距离

        if node1.x < node2.x:
            # node1在左边,node2在右边
            # 向右移动node2及其后代,向左移动node1及其前驱节点
            self._adjust_descendants_position(node2, move_distance1, 0)
            self._adjust_node_and_ancestors(node1, -move_distance2, 0)
        else:
            # node2在左边,node1在右边
            # 向右移动node1及其后代,向左移动node2及其前驱节点
            self._adjust_descendants_position(node1, move_distance1, 0)
            self._adjust_node_and_ancestors(node2, -move_distance2, 0)

    # 检查垂直重叠(适用于所有布局)
    if node1_bottom > node2_top and node1_top < node2_bottom:
        # 计算重叠量
        overlap = min(node1_bottom - node2_top, node2_bottom - node1_top)
        # 计算需要的最小间距(增加200%的间距,确保更强的分离效果)
        min_gap = self.mm_to_inch(self.vertical_spacing_mm) * 3.0
        # 计算需要移动的距离
        required_move = (overlap + min_gap) / 2

        # 计算节点的层级深度
        def get_node_depth(node):
            depth = 0
            current = node
            while current.parent:
                depth += 1
                current = current.parent
            return depth
        # 简单的位置调整,优先移动距离根节点更远的节点
        def get_node_depth(node):
            depth = 0
            current = node
            while current.parent:
                depth += 1
                current = current.parent
            return depth

        # 同时移动两个节点组以保持整体布局的平衡
        move_distance1 = required_move * 0.7  # 主要移动距离
        move_distance2 = required_move * 0.3  # 次要移动距离

        if node1.y < node2.y:
            # node1在上边,node2在下边
            # 向下移动node2及其后代,向上移动node1及其前驱节点
            self._adjust_descendants_position(node2, 0, move_distance1)
            self._adjust_node_and_ancestors(node1, 0, -move_distance2)
        else:
            # node2在上边,node1在下边
            # 向下移动node1及其后代,向上移动node2及其前驱节点
            self._adjust_descendants_position(node1, 0, move_distance1)
            self._adjust_node_and_ancestors(node2, 0, -move_distance2)

def _calculate_positions(self):
    """根据用户定义的吊线图生成规律计算节点位置(保持向后兼容)"""
    # 调用新的布局计算方法
    self.calculate_layout()

def _check_global_overlaps(self):
    """检查并解决整个树中所有节点之间的重叠,不仅仅是兄弟节点"""
    if not self.family_tree or not self.family_tree.nodes:
        return

    # 获取所有节点列表
    nodes = list(self.family_tree.nodes.values())

    # 检查每对节点之间的重叠
    for i in range(len(nodes)):
        for j in range(i + 1, len(nodes)):
            node1 = nodes[i]
            node2 = nodes[j]
            self._check_and_fix_overlap(node1, node2)

def _adjust_parent_positions_for_symmetry(self, node):
    """调整祖先节点的位置,确保整个树的对称性"""
    # 这是一个简单的实现,仅确保在竖式布局中父节点位置正确
    if not node.parent or self.layout_direction.startswith('horizontal'):
        return

    # 找到当前节点的所有同级节点
    siblings = node.parent.children
    if len(siblings) <= 1:
        return

    # 计算同级节点的中心点
    min_x = min(siblings, key=lambda n: n.x).x
    max_x = max(siblings, key=lambda n: n.x).x
    center_x = (min_x + max_x) / 2

    # 如果父节点的位置与子节点中心点不匹配,则调整父节点位置
    if abs(node.parent.x - center_x) > 0.001:  # 添加一个小的容差
        dx = center_x - node.parent.x
        node.parent.x = center_x

        # 递归调整所有祖先节点
        self._adjust_parent_positions_for_symmetry(node.parent)

def _get_node_group_bounds(self, node):
    """获取节点及其所有子节点的边界范围"""
    if not node:
        return (0, 0, 0, 0)  # (min_x, min_y, max_x, max_y)

    # 初始边界为当前节点的边界
    min_x = node.x - node.width / 2
    max_x = node.x + node.width / 2
    min_y = node.y - node.height / 2
    max_y = node.y + node.height / 2

    # 递归检查所有子节点的边界
    for child in node.children:
        child_min_x, child_min_y, child_max_x, child_max_y = self._get_node_group_bounds(child)
        min_x = min(min_x, child_min_x)
        max_x = max(max_x, child_max_x)
        min_y = min(min_y, child_min_y)
        max_y = max(max_y, child_max_y)

    return (min_x, min_y, max_x, max_y)



def _new_calculate_node_positions(self):
    """全新的节点位置计算方法,实现传统家族树布局"""
    if not self.family_tree or not self.family_tree.root:
        return

    # 计算每个节点的层级
    def calculate_levels(node, level):
        node.level = level
        for child in node.children:
            calculate_levels(child, level + 1)

    calculate_levels(self.family_tree.root, 0)

    # 基于层级的布局计算
    # 为每个层级分配固定的Y坐标
    level_heights = {}
    current_y = 0
    vertical_spacing = 150.0  # 层级之间的垂直间距

    # 计算每个层级的Y坐标
    def calculate_level_y(node):
        nonlocal current_y
        if node.level not in level_heights:
            level_heights[node.level] = current_y
            current_y += vertical_spacing
        for child in node.children:
            calculate_level_y(child)

    calculate_level_y(self.family_tree.root)

    # 计算每个层级内节点的X坐标
    def calculate_level_x(node):
        if not node.children:
            return

        # 计算子节点的总宽度
        child_count = len(node.children)
        if child_count == 0:
            return

        # 估算每个子节点的宽度
        estimated_child_width = 60.0  # 估算的子节点宽度
        horizontal_spacing = 120.0  # 子节点之间的水平间距

        total_width = child_count * estimated_child_width + (child_count - 1) * horizontal_spacing
        start_x = node.x - total_width / 2

        # 为每个子节点分配X坐标
        for i, child in enumerate(node.children):
            child.x = start_x + i * (estimated_child_width + horizontal_spacing) + estimated_child_width / 2
            child.y = level_heights[child.level]
            calculate_level_x(child)

    # 设置根节点位置
    self.family_tree.root.x = 0
    self.family_tree.root.y = level_heights[0]

    # 计算所有子节点的位置
    calculate_level_x(self.family_tree.root)

    # 强制修复所有重叠
    self._force_fix_all_overlaps()

    # 再次修复重叠,确保完全分离
    self._force_fix_all_overlaps()

def _bottom_up_calculate_node_positions(self):
    """自底向上的节点位置计算方法,从最低世次者开始生成"""
    if not self.family_tree or not self.family_tree.root:
        return

    # 计算每个节点的层级
    def calculate_levels(node, level):
        node.level = level
        max_level = level
        for child in node.children:
            child_level = calculate_levels(child, level + 1)
            if child_level > max_level:
                max_level = child_level
        return max_level

    max_level = calculate_levels(self.family_tree.root, 0)

    # 收集所有节点
    def get_all_nodes(node):
        nodes = [node]
        for child in node.children:
            nodes.extend(get_all_nodes(child))
        return nodes

    all_nodes = get_all_nodes(self.family_tree.root)

    # 按层级分组
    level_nodes = {}
    for node in all_nodes:
        if node.level not in level_nodes:
            level_nodes[node.level] = []
        level_nodes[node.level].append(node)

    # 计算每个层级的最小垂直间距
    # 基于节点高度和UI面板设置的间距计算
    vertical_spacing_inch = self.mm_to_inch(self.vertical_spacing_mm)

    # 计算每个层级的最大节点高度
    level_max_heights = {}
    for level in range(max_level + 1):
        if level in level_nodes:
            max_height = max(node.height for node in level_nodes[level]) if level_nodes[level] else 0
            level_max_heights[level] = max_height
        else:
            level_max_heights[level] = 0

    # 为每个层级分配Y坐标(从根节点开始向下递增)
    # Y坐标表示节点的垂直中心位置
    level_heights = {}
    current_y = 0  # 根节点的Y坐标(中心)

    for level in range(max_level + 1):
        level_heights[level] = current_y

        # 获取当前层级的最大节点高度
        current_level_height = level_max_heights.get(level, 0)

        # 计算下一层级的Y坐标
        # 总垂直空间 = 当前节点高度 + 垂直间距
        # 因为Y坐标是中心位置,所以下一层级的Y坐标 = 当前Y + 当前节点高度/2 + 垂直间距 + 下一层节点高度/2
        next_level_height = level_max_heights.get(level + 1, 0)

        # 计算垂直间距:当前节点底部到下一层节点顶部的距离
        total_vertical_gap = vertical_spacing_inch

        # 计算下一层级的中心Y坐标
        next_level_center_y = current_y + (current_level_height / 2) + total_vertical_gap + (next_level_height / 2)

        # 更新当前Y坐标为下一层级的中心Y坐标
        current_y = next_level_center_y

    # 水平间距设置
    # 从UI面板获取水平间距设置(转换为英寸)
    horizontal_spacing = self.mm_to_inch(self.horizontal_spacing_mm)

    # 从最高层级(最低世次者)开始计算位置
    for level in range(max_level, -1, -1):
        if level not in level_nodes:
            continue

        for node in level_nodes[level]:
            if not node.children:
                # 叶子节点,设置默认X坐标(暂时设为0,后续调整)
                node.x = 0
                node.y = level_heights[level]
            else:
                # 非叶子节点,根据子节点位置计算自己的位置
                # 计算子节点的总宽度
                child_count = len(node.children)
                if child_count == 1:
                    # 只有一个子节点,直接对齐
                    node.x = node.children[0].x
                else:
                    # 多个子节点,水平居中
                    min_child_x = min(child.x for child in node.children)
                    max_child_x = max(child.x for child in node.children)
                    node.x = (min_child_x + max_child_x) / 2

                # 设置Y坐标
                node.y = level_heights[level]

    # 修复同一层级内的水平间距
    for level in range(max_level + 1):
        if level not in level_nodes:
            continue

        nodes = level_nodes[level]
        if len(nodes) <= 1:
            continue

        # 按X坐标排序
        nodes.sort(key=lambda n: n.x)

        # 确保节点之间有足够的水平间距
        for i in range(len(nodes) - 1):
            current_node = nodes[i]
            next_node = nodes[i + 1]

            # 计算当前节点的右边界和下一个节点的左边界
            current_right = current_node.x + current_node.width / 2
            next_left = next_node.x - next_node.width / 2

            # 计算当前间距
            current_gap = next_left - current_right

            # 计算所需最小间距:直接使用水平间距设置值
            required_gap = horizontal_spacing

            # 如果间距不足,调整位置
            if current_gap < required_gap:
                # 计算需要调整的偏移量
                offset = required_gap - current_gap

                # 向右移动下一个节点及所有后续节点
                for j in range(i + 1, len(nodes)):
                    nodes[j].x += offset

    # 确保每个节点组(父子关系)内的间距正确
    for level in range(max_level - 1, -1, -1):
        if level not in level_nodes:
            continue

        for node in level_nodes[level]:
            if node.children:
                # 重新计算子节点的水平布局,确保每个子节点有足够空间
                children = sorted(node.children, key=lambda n: n.name)
                if len(children) > 1:
                    # 计算总宽度需求:所有子节点宽度 + 间距
                    total_width_needed = sum(child.width for child in children)
                    total_width_needed += horizontal_spacing * (len(children) - 1)

                    # 计算起始X坐标,使子节点组居中
                    start_x = node.x - total_width_needed / 2

                    # 重新分配子节点X坐标
                    current_x = start_x
                    for child in children:
                        # 设置子节点中心X坐标
                        child.x = current_x + child.width / 2
                        # 更新当前X坐标,加上子节点宽度和间距
                        current_x += child.width + horizontal_spacing

                    # 更新子节点Y坐标,确保垂直位置正确
                    for child in children:
                        child.y = level_heights[child.level]

    # 再次从下到上调整,确保父节点正确居中
    for level in range(max_level - 1, -1, -1):
        if level not in level_nodes:
            continue

        for node in level_nodes[level]:
            if node.children:
                # 重新计算父节点位置,确保居中
                if len(node.children) == 1:
                    node.x = node.children[0].x
                else:
                    min_child_x = min(child.x for child in node.children)
                    max_child_x = max(child.x for child in node.children)
                    node.x = (min_child_x + max_child_x) / 2

    # 强制修复所有重叠
    self._force_fix_all_overlaps()

    # 再次修复重叠,确保完全分离
    self._force_fix_all_overlaps()

def _force_resolve_all_overlaps(self, all_nodes):
    """强制修复所有节点重叠的最终方法"""
    print("执行强制重叠修复...")

    # 按层级分组
    level_nodes = {}
    for node in all_nodes:
        if not hasattr(node, 'level'):
            continue
        if node.level not in level_nodes:
            level_nodes[node.level] = []
        level_nodes[node.level].append(node)

    # 对每个层级的节点进行排序和分布
    for level, nodes in level_nodes.items():
        if len(nodes) <= 1:
            continue

        # 按X坐标排序
        sorted_nodes = sorted(nodes, key=lambda n: n.x)

        # 计算总宽度需求
        total_width_needed = sum(node.width for node in sorted_nodes)
        total_spacing = self.mm_to_inch(30.0) * (len(sorted_nodes) - 1)  # 30mm间距
        total_span = total_width_needed + total_spacing

        # 从画布左侧开始分布
        start_x = self.mm_to_inch(self.left_margin_cm * 10)  # cm转mm
        current_x = start_x

        for node in sorted_nodes:
            # 设置节点中心位置
            node.x = current_x + node.width / 2
            current_x += node.width + self.mm_to_inch(30.0)

    print("强制重叠修复完成")

def _force_fix_all_overlaps(self):
    """强制修复所有节点之间的重叠"""
    self._aggressive_fix_all_overlaps()

def _aggressive_fix_all_overlaps(self):
    """使用更激进的算法修复所有节点之间的重叠"""
    if not self.family_tree or not self.family_tree.root:
        return

    # 获取所有节点
    def get_all_nodes(node):
        nodes = [node]
        for child in node.children:
            nodes.extend(get_all_nodes(child))
        return nodes

    all_nodes = get_all_nodes(self.family_tree.root)

    # 修复所有节点之间的重叠,包括不同层级之间的重叠
    max_iterations = 100
    for iteration in range(max_iterations):
        overlap_found = False

        # 对所有节点对进行检查和修复
        for i in range(len(all_nodes)):
            for j in range(i + 1, len(all_nodes)):
                node1 = all_nodes[i]
                node2 = all_nodes[j]

                if self._check_nodes_overlap(node1, node2):
                    # 计算节点边界
                    node1_left = node1.x - node1.width / 2
                    node1_right = node1.x + node1.width / 2
                    node1_top = node1.y - node1.height / 2
                    node1_bottom = node1.y + node1.height / 2

                    node2_left = node2.x - node2.width / 2
                    node2_right = node2.x + node2.width / 2
                    node2_top = node2.y - node2.height / 2
                    node2_bottom = node2.y + node2.height / 2

                    # 计算水平和垂直重叠
                    horizontal_overlap = max(0, min(node1_right, node2_right) - max(node1_left, node2_left))
                    vertical_overlap = max(0, min(node1_bottom, node2_bottom) - max(node1_top, node2_top))

                    # 计算所需的分离距离
                    required_separation = self.mm_to_inch(50.0)  # 固定最小间距50mm

                    # 优先处理水平重叠
                    if horizontal_overlap > vertical_overlap:
                        # 水平分离
                        if node1.x < node2.x:
                            # 向右移动node2
                            self._move_node_right(node2, (horizontal_overlap + required_separation))
                        else:
                            # 向右移动node1
                            self._move_node_right(node1, (horizontal_overlap + required_separation))
                    else:
                        # 垂直分离
                        if node1.y < node2.y:
                            # 向下移动node2
                            self._move_node_down(node2, (vertical_overlap + required_separation))
                        else:
                            # 向下移动node1
                            self._move_node_down(node1, (vertical_overlap + required_separation))

                    overlap_found = True

        if not overlap_found:
            break

    print(f"激进修复重叠完成,迭代次数: {iteration+1}")
    print(f"总节点数: {len(all_nodes)}")

def _fix_node_overlaps(self):
    """使用改进的重叠检测和修复算法"""
    if not self.family_tree or not self.family_tree.root:
        return

    # 获取所有节点
    def get_all_nodes(node):
        nodes = [node]
        for child in node.children:
            nodes.extend(get_all_nodes(child))
        return nodes

    all_nodes = get_all_nodes(self.family_tree.root)

    print(f"开始修复重叠,总节点数: {len(all_nodes)}")

    # 使用新的智能重叠检测和修复算法
    self._smart_overlap_resolution(all_nodes)

    print("重叠修复完成")

def _smart_overlap_resolution(self, all_nodes):
    """智能重叠检测和修复算法"""
    max_iterations = 100
    for iteration in range(max_iterations):
        overlap_count = 0
        moved_nodes = set()

        # 检查所有节点对之间的重叠
        for i in range(len(all_nodes)):
            for j in range(i + 1, len(all_nodes)):
                node1 = all_nodes[i]
                node2 = all_nodes[j]

                if self._check_nodes_overlap(node1, node2):
                    overlap_count += 1

                    # 计算重叠量和修复方向
                    node1_left = node1.x - node1.width / 2
                    node1_right = node1.x + node1.width / 2
                    node1_top = node1.y - node1.height / 2
                    node1_bottom = node1.y + node1.height / 2

                    node2_left = node2.x - node2.width / 2
                    node2_right = node2.x + node2.width / 2
                    node2_top = node2.y - node2.height / 2
                    node2_bottom = node2.y + node2.height / 2

                    # 计算水平和垂直重叠
                    h_overlap = max(0, min(node1_right, node2_right) - max(node1_left, node2_left))
                    v_overlap = max(0, min(node1_bottom, node2_bottom) - max(node1_top, node2_top))

                    # 只调整水平方向的重叠,保持垂直位置不变,确保同一层级的节点在同一水平线上
                    # 水平重叠为主
                    if node1.x < node2.x:
                        # node1在左边,向右移动node2
                        move_x = h_overlap + self.mm_to_inch(10.0)  # 额外10mm安全间距
                        self._adjust_descendants_position(node2, move_x, 0)
                        moved_nodes.add(node2.id)
                    else:
                        # node2在左边,向右移动node1
                        move_x = h_overlap + self.mm_to_inch(10.0)
                        self._adjust_descendants_position(node1, move_x, 0)
                        moved_nodes.add(node1.id)

        # 如果没有发现重叠,提前结束
        if overlap_count == 0:
            print(f"智能修复完成,迭代 {iteration + 1},所有重叠已解决")
            break

        print(f"迭代 {iteration + 1}: 发现 {overlap_count} 处重叠,移动了 {len(moved_nodes)} 个节点")

        # 如果到达最大迭代次数仍有重叠,使用强制修复
        if iteration == max_iterations - 1 and overlap_count > 0:
            print(f"警告: 在 {max_iterations} 次迭代后仍有 {overlap_count} 处重叠,使用强制修复")
            self._force_resolve_all_overlaps(all_nodes)
            break

def _force_resolve_overlap(self, node1, node2):
    """强制解决两个节点之间的重叠"""
    # 计算间距需求
    min_gap = self.mm_to_inch(max(self.horizontal_spacing_mm, self.vertical_spacing_mm)) * 2.0

    # 直接移动节点,确保它们完全分离
    # 只调整水平方向,保持垂直位置不变,确保同一世次的人在同一水平线上
    if node1.x <= node2.x:
        self._move_node_right(node2, min_gap)
        self._move_node_left(node1, min_gap)
    else:
        self._move_node_right(node1, min_gap)
        self._move_node_left(node2, min_gap)

    # 不再调整垂直方向,保持同一世次的人在同一水平线上

def _check_nodes_overlap(self, node1, node2):
    """检查两个节点是否重叠"""
    # 计算节点边界
    node1_left = node1.x - node1.width / 2
    node1_right = node1.x + node1.width / 2
    node1_top = node1.y - node1.height / 2
    node1_bottom = node1.y + node1.height / 2

    node2_left = node2.x - node2.width / 2
    node2_right = node2.x + node2.width / 2
    node2_top = node2.y - node2.height / 2
    node2_bottom = node2.y + node2.height / 2

    # 检查是否重叠
    return (node1_right > node2_left and node1_left < node2_right and
            node1_bottom > node2_top and node1_top < node2_bottom)

def _resolve_overlap(self, node1, node2):
    """解决两个节点之间的重叠"""
    # 计算间距需求
    min_gap = self.mm_to_inch(max(self.horizontal_spacing_mm, self.vertical_spacing_mm))

    # 检查是否是完全相同的位置
    if node1.x == node2.x and node1.y == node2.y:
        # 如果位置完全相同,直接移动节点
        move_distance = min_gap
        self._move_node_right(node1, move_distance)
        self._move_node_left(node2, move_distance)
        return

    # 计算节点边界
    node1_left = node1.x - node1.width / 2
    node1_right = node1.x + node1.width / 2
    node1_top = node1.y - node1.height / 2
    node1_bottom = node1.y + node1.height / 2

    node2_left = node2.x - node2.width / 2
    node2_right = node2.x + node2.width / 2
    node2_top = node2.y - node2.height / 2
    node2_bottom = node2.y + node2.height / 2

    # 计算水平和垂直重叠
    horizontal_overlap = min(node1_right - node2_left, node2_right - node1_left)
    vertical_overlap = min(node1_bottom - node2_top, node2_bottom - node1_top)

    # 确保重叠值为正数
    horizontal_overlap = max(0, horizontal_overlap)
    vertical_overlap = max(0, vertical_overlap)

    # 根据重叠方向进行调整
    if horizontal_overlap > vertical_overlap:
        # 水平重叠更严重
        if node1.x <= node2.x:
            # node1在左边,node2在右边
            move_distance = (horizontal_overlap + min_gap) / 2
            self._move_node_right(node2, move_distance)
            self._move_node_left(node1, move_distance)
        else:
            # node1在右边,node2在左边
            move_distance = (horizontal_overlap + min_gap) / 2
            self._move_node_right(node1, move_distance)
            self._move_node_left(node2, move_distance)
    else:
        # 垂直重叠更严重
        if node1.y <= node2.y:
            # node1在上边,node2在下边
            move_distance = (vertical_overlap + min_gap) / 2
            self._move_node_down(node2, move_distance)
            self._move_node_up(node1, move_distance)
        else:
            # node1在下边,node2在上边
            move_distance = (vertical_overlap + min_gap) / 2
            self._move_node_down(node1, move_distance)
            self._move_node_up(node2, move_distance)

def _move_node_right(self, node, distance):
    """将节点及其所有后代向右移动"""
    if not node:
        return

    node.x += distance
    for child in node.children:
        self._move_node_right(child, distance)

def _move_node_left(self, node, distance):
    """将节点及其所有后代向左移动"""
    if not node:
        return

    node.x -= distance
    for child in node.children:
        self._move_node_left(child, distance)

def _move_node_down(self, node, distance):
    """将节点及其所有后代向下移动"""
    if not node:
        return

    node.y += distance
    for child in node.children:
        self._move_node_down(child, distance)

def _move_node_up(self, node, distance):
    """将节点及其所有后代向上移动"""
    if not node:
        return

    node.y -= distance
    for child in node.children:
        self._move_node_up(child, distance)

def _adjust_ancestor_positions(self, node, delta_x, delta_y):
    """调整节点的所有祖先节点位置,确保父子关系正确显示"""
    # 向上递归调整所有祖先节点的位置
    parent = node.parent
    while parent:
        parent.x += delta_x
        parent.y += delta_y

        # 对于每个祖先节点,需要确保它也居中于其子节点组
        # 计算祖先节点的子节点组中心点
        if self.layout_direction in ['vertical_normal', 'vertical_reverse']:
            # 竖式布局:计算水平居中
            siblings = parent.parent.children if parent.parent else []
            if len(siblings) > 1:
                min_x = min(s.x - s.width/2 for s in siblings)
                max_x = max(s.x + s.width/2 for s in siblings)
                siblings_center_x = (min_x + max_x) / 2

                # 如果祖先节点的父节点位置与子节点组中心点不匹配,则进一步调整
                if abs(parent.parent.x - siblings_center_x) > 0.001:
                    grand_delta_x = siblings_center_x - parent.parent.x
                    parent.parent.x = siblings_center_x
                    # 递归调整更高层级的祖先节点
                    self._adjust_ancestor_positions(parent.parent, grand_delta_x, 0)
        elif self.layout_direction in ['horizontal_normal', 'horizontal_reverse']:
            # 横式布局:计算垂直居中
            siblings = parent.parent.children if parent.parent else []
            if len(siblings) > 1:
                min_y = min(s.y - s.height/2 for s in siblings)
                max_y = max(s.y + s.height/2 for s in siblings)
                siblings_center_y = (min_y + max_y) / 2

                # 如果祖先节点的父节点位置与子节点组中心点不匹配,则进一步调整
                if abs(parent.parent.y - siblings_center_y) > 0.001:
                    grand_delta_y = siblings_center_y - parent.parent.y
                    parent.parent.y = siblings_center_y
                    # 递归调整更高层级的祖先节点
                    self._adjust_ancestor_positions(parent.parent, 0, grand_delta_y)

        # 继续调整父节点的父节点
        parent = parent.parent

def _adjust_descendants_position(self, node, delta_x, delta_y):
    """调整节点及其所有后代节点的位置,避免递归重新计算"""
    if not node:
        return

    # 首先调整节点本身的位置
    node.x += delta_x
    node.y += delta_y

    # 然后调整所有子节点的位置
    for child in node.children:
        # 递归调整子节点及其后代
        self._adjust_descendants_position(child, delta_x, delta_y)

def _adjust_node_and_ancestors(self, node, delta_x, delta_y):
    """调整节点及其所有祖先节点的位置,保持整体布局平衡"""
    if not node:
        return

    # 调整当前节点
    node.x += delta_x
    node.y += delta_y

    # 递归调整父节点
    if node.parent:
        self._adjust_node_and_ancestors(node.parent, delta_x, delta_y)

def _adjust_sibling_node_groups(self, parent_node):
    """当父节点层存在多个有子节点的父节点时,调整子节点组布局,避免重叠"""
    # 获取当前节点的所有兄弟节点(同层级的其他节点)
    if not parent_node.children:
        return

    siblings = parent_node.children
    if len(siblings) <= 1:
        return

    # 找出所有有子节点的兄弟节点
    siblings_with_children = [s for s in siblings if s.children]
    if len(siblings_with_children) <= 1:
        return

    # 按照子节点组的起始位置排序(确保正确的处理顺序)
    if self.layout_direction in ['vertical_normal', 'vertical_reverse']:
        siblings_with_children.sort(key=lambda s: min(c.x for c in s.children))
    else:
        siblings_with_children.sort(key=lambda s: min(c.y for c in s.children))

    # 遍历所有有子节点的兄弟节点,从第二个开始检查和调整
    for i in range(1, len(siblings_with_children)):
        prev_sibling = siblings_with_children[i-1]
        current_sibling = siblings_with_children[i]

        # 根据布局方向确定如何检测和调整重叠
        if self.layout_direction in ['vertical_normal', 'vertical_reverse']:
            # 竖式布局:检查水平方向是否重叠
            # 获取前一个兄弟节点子节点组的最右边界
            prev_rightmost = max(child.x + child.width/2 for child in prev_sibling.children)
            # 获取当前兄弟节点子节点组的最左边界
            curr_leftmost = min(child.x - child.width/2 for child in current_sibling.children)

            # 计算需要的最小间距(水平间距的1.8倍,与_check_and_fix_overlap方法保持一致)
            required_min_gap = self.mm_to_inch(self.horizontal_spacing_mm) * 1.8

            # 如果重叠或间距不足,调整当前兄弟节点及其子节点组
            if curr_leftmost - prev_rightmost < required_min_gap:
                offset = required_min_gap - (curr_leftmost - prev_rightmost)

                # 调整当前兄弟节点位置
                current_sibling.x += offset

                # 调整当前兄弟节点的所有子节点
                for child in current_sibling.children:
                    child.x += offset
                    self._adjust_descendants_position(child, offset, 0)

                # 调整所有后续兄弟节点
                for j in range(i+1, len(siblings_with_children)):
                    next_sibling = siblings_with_children[j]
                    next_sibling.x += offset
                    for child in next_sibling.children:
                        child.x += offset
                        self._adjust_descendants_position(child, offset, 0)
        else:
            # 横式布局:检查垂直方向是否重叠
            # 获取前一个兄弟节点子节点组的最下边界
            prev_bottommost = max(child.y + child.height/2 for child in prev_sibling.children)
            # 获取当前兄弟节点子节点组的最上边界
            curr_topmost = min(child.y - child.height/2 for child in current_sibling.children)

            # 计算需要的最小间距(垂直间距的1.8倍,与_check_and_fix_overlap方法保持一致)
            required_min_gap = self.mm_to_inch(self.vertical_spacing_mm) * 1.8

            # 如果重叠或间距不足,调整当前兄弟节点及其子节点组
            if curr_topmost - prev_bottommost < required_min_gap:
                offset = required_min_gap - (curr_topmost - prev_bottommost)

                # 调整当前兄弟节点位置
                current_sibling.y += offset

                # 调整当前兄弟节点的所有子节点
                for child in current_sibling.children:
                    child.y += offset
                    self._adjust_descendants_position(child, 0, offset)

                # 调整所有后续兄弟节点
                for j in range(i+1, len(siblings_with_children)):
                    next_sibling = siblings_with_children[j]
                    next_sibling.y += offset
                    for child in next_sibling.children:
                        child.y += offset
                        self._adjust_descendants_position(child, 0, offset)

        # 调整完后,确保父节点仍然居中于其子节点组
        if self.layout_direction in ['vertical_normal', 'vertical_reverse']:
            # 竖式布局:重新计算子节点组的水平中心点
            min_child_x = min(child.x - child.width/2 for child in current_sibling.children)
            max_child_x = max(child.x + child.width/2 for child in current_sibling.children)
            children_center_x = (min_child_x + max_child_x) / 2

            # 调整父节点位置使其居中于子节点组
            if abs(current_sibling.x - children_center_x) > 0.001:
                delta_x = children_center_x - current_sibling.x
                current_sibling.x = children_center_x
                # 调整当前兄弟节点的所有子节点
                for child in current_sibling.children:
                    child.x += delta_x
                    self._adjust_descendants_position(child, delta_x, 0)

def _create_node_items(self):
    """创建所有节点的图形项"""
    for node in self.family_tree.nodes.values():
        node_item = NodeItem(node)
        # 设置节点样式
        node_item.set_node_style(
            box_color=self.box_color,
            border_color=self.border_color,
            text_color=self.text_color,
            border_width=self.border_width,
            border_style=self.border_style,
            font=self.font,
            corner_radius=self.corner_radius,
            text_direction=getattr(self, 'text_direction', '横向')
        )

        # 保存布局算法计算的节点尺寸(英寸)
        layout_width_inch = node.width
        layout_height_inch = node.height

        # 强制使用布局算法计算的节点尺寸(确保单位一致)
        dpi = 96.0
        node_item.node.width = layout_width_inch
        node_item.node.height = layout_height_inch

        # 确保更新节点外观,根据新的尺寸调整字体大小
        node_item.update_node_appearance()

        # 设置节点位置(将英寸转换为像素)
        dpi = 96.0
        node_item.setPos(node.x * dpi, node.y * dpi)
        # 添加到场景和字典中
        self.scene.addItem(node_item)
        self.node_items[node.id] = node_item

def _create_line_items(self):
    """创建所有导线的图形项"""
    for node in self.family_tree.nodes.values():
        # 创建与子节点的导线
        for child in node.children:
            line_item = LineItem(node, child, self.layout_direction)
            # 设置线条样式
            line_item.set_line_style(
                color=self.line_color,
                width=self.line_width,
                style=self.line_style,
                end_shape_type=getattr(self, 'end_shape_type', '无'),
                end_shape_size=getattr(self, 'end_shape_size', 5.0),
                end_shape_line_style=getattr(self, 'end_shape_line_style', '实线'),
                end_shape_line_width=getattr(self, 'end_shape_line_width', 1.0),
                end_shape_color=getattr(self, 'end_shape_color', QColor(0, 0, 0)),
                layout_direction=self.layout_direction
            )
            # 添加到场景和列表中
            self.scene.addItem(line_item)
            self.line_items.append(line_item)

        # 创建与配偶的导线
        if node.spouse and node.id < node.spouse.id:  # 避免重复创建
            line_item = LineItem(node, node.spouse, self.layout_direction)
            # 设置线条样式(可以使用不同的样式表示配偶关系)
            line_item.set_line_style(
                color=self.line_color,
                width=self.line_width,
                style=Qt.DashLine,  # 使用虚线表示配偶关系
                end_shape_type=getattr(self, 'end_shape_type', '无'),
                end_shape_size=getattr(self, 'end_shape_size', 5.0),
                end_shape_line_style=getattr(self, 'end_shape_line_style', '实线'),
                end_shape_line_width=getattr(self, 'end_shape_line_width', 1.0),
                end_shape_color=getattr(self, 'end_shape_color', QColor(0, 0, 0))
            )
            # 添加到场景和列表中
            self.scene.addItem(line_item)
            self.line_items.append(line_item)

def calculate_box_size(self, text):
    """根据文本内容计算节点的尺寸(毫米),确保最小值足够显示名称字号"""
    # 默认最小尺寸(毫米),确保足够显示名称字号
    min_box_width_mm = 9.5
    min_box_height_mm = 5.2

    # 首先根据文本内容和字体大小自动计算所需尺寸
    # 创建临时QFont对象来测量文本尺寸
    font = QFont("SimHei", 12)  # 默认12号字体

    # 获取字体的像素尺寸
    fm = QFontMetrics(font)
    text_rect = fm.boundingRect(text)

    # 计算所需的像素尺寸,添加适当的内边距
    padding_px = 6  # 内边距(像素)
    required_width_px = text_rect.width() + padding_px * 2
    required_height_px = text_rect.height() + padding_px * 2

    # 转换为毫米单位(1英寸=25.4毫米=96像素)
    required_width_mm = (required_width_px / 96.0) * 25.4
    required_height_mm = (required_height_px / 96.0) * 25.4

    # 使用计算出的尺寸,确保不小于最小值
    box_width_mm = max(required_width_mm, min_box_width_mm)
    box_height_mm = max(required_height_mm, min_box_height_mm)

    # 检查是否有自定义参数设置,如果有则优先使用
    if hasattr(self, 'custom_box_width_mm') and self.custom_box_width_mm is not None and self.custom_box_width_mm > 0:
        box_width_mm = max(self.custom_box_width_mm, min_box_width_mm, required_width_mm)
    if hasattr(self, 'custom_box_height_mm') and self.custom_box_height_mm is not None and self.custom_box_height_mm > 0:
        box_height_mm = max(self.custom_box_height_mm, min_box_height_mm, required_height_mm)

    return box_width_mm, box_height_mm

def mm_to_inch(self, mm):
    """将毫米转换为英寸"""
    return mm / 25.4

def inch_to_mm(self, inch):
    """将英寸转换为毫米"""
    return inch * 25.4

def set_style_params(self, params, only_custom_size_params=False):
    """设置样式参数"""
    # 检查是否只更新了自定义尺寸参数,避免不必要的显示更新
    if not only_custom_size_params:
        only_custom_size_params = ('custom_box_width_mm' in params or 'custom_box_height_mm' in params) and \
                                 len(params) <= 2 and \
                                 all(key in ['custom_box_width_mm', 'custom_box_height_mm'] for key in params)

    if 'box_color' in params:
        if params['box_color'] == 'no_fill':
            # 不填色:使用透明颜色
            self.box_color = QColor(0, 0, 0, 0)  # 透明颜色
        else:
            self.box_color = params['box_color']
    if 'border_color' in params:
        self.border_color = params['border_color']
    if 'text_color' in params:
        self.text_color = params['text_color']
    if 'line_color' in params:
        self.line_color = params['line_color']
    if 'border_width' in params:
        self.border_width = params['border_width']
    if 'line_width' in params:
        self.line_width = params['line_width']
    if 'border_style' in params:
        self.border_style = params['border_style']
    if 'line_style' in params:
        self.line_style = params['line_style']
    if 'font' in params:
        self.font = params['font']
    if 'corner_radius' in params:
        self.corner_radius = params['corner_radius']
    if 'horizontal_spacing_mm' in params:
        self.horizontal_spacing_mm = params['horizontal_spacing_mm']
        # 移除硬编码的最小间距约束,允许用户自由设置
        # 重置初始化标志,确保使用新的间距值
        self._spacing_initialized = True
    if 'vertical_spacing_mm' in params:
        self.vertical_spacing_mm = params['vertical_spacing_mm']
        # 移除硬编码的最小间距约束,允许用户自由设置
        # 重置初始化标志,确保使用新的间距值
        self._spacing_initialized = True
    if 'layout_direction' in params:
        self.layout_direction = params['layout_direction']
        # 当阅读方向改变时,画板水平翻转,同时所有节点也要翻转
        # 这里的水平翻转已经在 calculate_layout 方法中通过调整节点位置实现
        # 节点内容的翻转需要在创建节点时处理
    if 'letter_spacing' in params:
        self.letter_spacing = params['letter_spacing']
    if 'text_direction' in params:
        self.text_direction = params['text_direction']
    # 添加对自定义图形宽度和高度参数的支持
    if 'custom_box_width_mm' in params:
        self.custom_box_width_mm = params['custom_box_width_mm']
    if 'custom_box_height_mm' in params:
        self.custom_box_height_mm = params['custom_box_height_mm']
    # 添加末端图形设置
    if 'end_shape_type' in params:
        self.end_shape_type = params['end_shape_type']
    if 'end_shape_size' in params:
        self.end_shape_size = params['end_shape_size']  # 末端图形大小(像素)
    if 'end_shape_line_style' in params:
        self.end_shape_line_style = params['end_shape_line_style']  # 末端图形轮廓线形
    if 'end_shape_line_width' in params:
        self.end_shape_line_width = params['end_shape_line_width']  # 末端图形轮廓粗细
    if 'end_shape_color' in params:
        self.end_shape_color = params['end_shape_color']  # 末端图形轮廓颜色

    # 添加画布大小设置
    if 'canvas_length_cm' in params:
        self.canvas_width_cm = params['canvas_length_cm']  # 画布宽度(厘米)
    if 'canvas_width_cm' in params:
        self.canvas_height_cm = params['canvas_width_cm']  # 画布高度(厘米)
    if 'canvas_size' in params:
        self.canvas_size = params['canvas_size']  # 画布规格

    # 添加边距设置
    if 'left_margin_cm' in params:
        self.left_margin_cm = params['left_margin_cm']  # 左边距(厘米)
    if 'right_margin_cm' in params:
        self.right_margin_cm = params['right_margin_cm']  # 右边距(厘米)
    if 'top_margin_cm' in params:
        self.top_margin_cm = params['top_margin_cm']  # 页眉距(厘米)
    if 'bottom_margin_cm' in params:
        self.bottom_margin_cm = params['bottom_margin_cm']  # 页脚距(厘米)

    # 确保尺寸变化能够正确触发显示更新
    if 'custom_box_width_mm' in params or 'custom_box_height_mm' in params:
        # 立即更新显示,确保尺寸变化生效
        current_update_display = getattr(self, '_updating_display', False)
        if not current_update_display:
            self._updating_display = True
            try:
                # 尺寸修改时不自动调整视图位置,让用户能看到修改效果
                self.update_tree_display(adjust_view=False)
            finally:
                self._updating_display = False
    # 非尺寸参数变化时更新显示
    elif not only_custom_size_params:
        # 检查是否正在更新显示,避免循环更新
        current_update_display = getattr(self, '_updating_display', False)
        if not current_update_display:
            self._updating_display = True
            try:
                # 非尺寸修改时自动调整视图位置
                self.update_tree_display(adjust_view=True)
            finally:
                self._updating_display = False

def load_data_from_csv(self, file_path):
    """从CSV文件加载数据"""
    try:
        # 清空现有数据
        self.family_tree = FamilyTree()

        # 读取CSV文件
        with open(file_path, 'r', encoding='utf-8') as f:
            reader = csv.DictReader(f)
            # 获取列名
            fieldnames = reader.fieldnames
            print(f"CSV文件列名: {fieldnames}")

            # 第一遍:创建所有节点
            node_id_map = {}
            name_node_map = {}
            rows = []  # 保存所有行,以便后续处理
            for index, row in enumerate(reader):
                rows.append(row)
                # 尝试获取不同格式的ID和名称列
                original_id = row.get('ID', '') or row.get('本人ID', '')
                node_id = original_id or f"node_{index+1}"
                name = row.get('Name', '') or row.get('谱名', '') or row.get('姓名', '')
                note = row.get('Note', '') or row.get('备注', '')

                if name:
                    node = Node(node_id, name, note)
                    self.family_tree.add_node(node)
                    node_id_map[node_id] = node_id
                    # 同时将原始的本人ID也映射到节点ID
                    if original_id:
                        node_id_map[original_id] = node_id
                    name_node_map[name] = node
                    print(f"创建节点: {name} (ID: {node_id}, 原始ID: {original_id})")

        # 第二遍:建立父子关系和配偶关系
        for row in rows:
            # 尝试获取不同格式的ID和名称列
            node_id = row.get('ID', '') or row.get('本人ID', '')
            parent_id = row.get('ParentID', '') or row.get('父亲ID', '')
            parent_name = row.get('父名', '') or row.get('父亲', '')
            spouse_id = row.get('SpouseID', '') or row.get('配偶ID', '')
            spouse_name = row.get('配偶姓名', '') or row.get('配偶', '')
            name = row.get('Name', '') or row.get('谱名', '') or row.get('姓名', '')

            print(f"处理行: 姓名={name}, 父名={parent_name}")

            # 获取节点
            node = None
            if node_id:
                node = self.family_tree.get_node(node_id)
            elif name:
                # 尝试通过名称获取节点
                if name in name_node_map:
                    node = name_node_map[name]

            if node:
                print(f"找到节点: {node.name}")
                # 建立父子关系
                parent_node = None
                if parent_id:
                    print(f"尝试通过父节点ID {parent_id} 获取父节点")
                    print(f"node_id_map 中的键: {list(node_id_map.keys())}")
                    # 尝试通过父节点ID获取父节点
                    if parent_id in node_id_map:
                        mapped_parent_id = node_id_map[parent_id]
                        parent_node = self.family_tree.get_node(mapped_parent_id)
                        print(f"通过父节点ID {parent_id} (映射到 {mapped_parent_id}) 获取父节点成功: {parent_node.name}")
                    else:
                        print(f"警告: 父节点ID {parent_id} 不存在于 node_id_map 中")
                        # 如果通过父节点ID找不到父节点,尝试通过父名获取
                        if parent_name:
                            print(f"尝试通过父亲名称 {parent_name} 获取父节点")
                            print(f"name_node_map 中的键: {list(name_node_map.keys())}")
                            # 尝试通过父亲名称获取父节点
                            if parent_name in name_node_map:
                                parent_node = name_node_map[parent_name]
                                print(f"通过父亲名称 {parent_name} 获取父节点成功: {parent_node.name}")
                            else:
                                print(f"警告: 父节点 {parent_name} 不存在于 name_node_map 中")
                elif parent_name:
                    print(f"尝试通过父亲名称 {parent_name} 获取父节点")
                    print(f"name_node_map 中的键: {list(name_node_map.keys())}")
                    # 尝试通过父亲名称获取父节点
                    if parent_name in name_node_map:
                        parent_node = name_node_map[parent_name]
                        print(f"通过父亲名称 {parent_name} 获取父节点成功: {parent_node.name}")
                    else:
                        print(f"警告: 父节点 {parent_name} 不存在于 name_node_map 中")

                if parent_node:
                    print(f"建立父子关系: {parent_node.name} -> {node.name}")
                    parent_node.add_child(node)
                else:
                    print(f"无法建立父子关系: 父节点不存在")

                # 建立配偶关系
                spouse_node = None
                if spouse_id:
                    spouse_node = self.family_tree.get_node(spouse_id)
                elif spouse_name:
                    # 尝试通过配偶名称获取配偶节点
                    if spouse_name in name_node_map:
                        spouse_node = name_node_map[spouse_name]

                if spouse_node:
                    print(f"建立配偶关系: {node.name} <-> {spouse_node.name}")
                    node.add_spouse(spouse_node)

        # 设置根节点:找到没有父节点的节点作为根节点
        root_nodes = []
        for node in self.family_tree.get_all_nodes():
            if node.parent is None:
                root_nodes.append(node)

        # 如果有多个根节点,选择第一个作为根节点
        if root_nodes:
            self.family_tree.root = root_nodes[0]
            print(f"设置根节点为: {self.family_tree.root.name}")
        else:
            # 如果没有找到根节点,选择第一个添加的节点
            if self.family_tree.nodes:
                self.family_tree.root = next(iter(self.family_tree.nodes.values()))
                print(f"未找到根节点,默认设置为: {self.family_tree.root.name}")

        # 检查所有节点的父子关系
        print("\n检查所有节点的父子关系:")
        for node in self.family_tree.get_all_nodes():
            if node.parent:
                print(f"节点 {node.name} 的父节点是: {node.parent.name}")
            else:
                print(f"节点 {node.name} 没有父节点")
            if node.children:
                print(f"节点 {node.name} 的子节点是: {[child.name for child in node.children]}")

        # 更新显示
        self.update_tree_display()
        return True
    except Exception as e:
        QMessageBox.critical(self, "错误", f"加载CSV文件失败: {str(e)}")
        import traceback
        traceback.print_exc()
        return False

def load_data_from_excel(self, file_path):
    """从Excel或CSV文件加载数据"""
    try:
        # 清空现有数据
        self.family_tree = FamilyTree()

        # 根据文件扩展名选择读取方法
        if file_path.endswith('.csv'):
            # 读取CSV文件
            df = pd.read_csv(file_path, encoding='utf-8')
        else:
            # 读取Excel文件
            df = pd.read_excel(file_path)

        # 检查必要的列是否存在
        valid_combination = False

        # 组合1: 谱名 + 父名
        if '谱名' in df.columns and '父名' in df.columns:
            valid_combination = True
        # 组合2: 谱名 + 父亲谱名
        elif '谱名' in df.columns and '父亲谱名' in df.columns:
            df.rename(columns={'父亲谱名': '父名'}, inplace=True)
            valid_combination = True
        # 组合3: 姓名 + 父名
        elif '姓名' in df.columns and '父名' in df.columns:
            df.rename(columns={'姓名': '谱名'}, inplace=True)
            valid_combination = True
        # 组合4: 姓名 + 父亲谱名
        elif '姓名' in df.columns and '父亲谱名' in df.columns:
            df.rename(columns={'姓名': '谱名', '父亲谱名': '父名'}, inplace=True)
            valid_combination = True

        if not valid_combination:
            QMessageBox.warning(self, "错误", "文件中必须包含以下字段组合之一:\n1. '谱名'和'父名'\n2. '谱名'和'父亲谱名'\n3. '姓名'和'父名'\n4. '姓名'和'父亲谱名'")
            return False

        # 创建所有节点,使用唯一ID作为核心
        node_map = {}
        name_to_nodes = {}

        for index, row in df.iterrows():
            # 生成唯一ID
            unique_id = f"node_{index+1}"
            name = str(row['谱名']).strip()

            if name:
                note = str(row.get('备注', '')).strip()
                node = Node(unique_id, name, note)
                self.family_tree.add_node(node)

                # 存储节点映射
                node_map[unique_id] = node

                # 按姓名存储节点(用于处理父名匹配)
                if name not in name_to_nodes:
                    name_to_nodes[name] = []
                name_to_nodes[name].append(node)

        # 建立父子关系,使用姓名匹配
        for index, row in df.iterrows():
            node_id = f"node_{index+1}"
            parent_name = str(row['父名']).strip()

            if parent_name and node_id in node_map:
                child_node = node_map[node_id]

                # 找到对应的父节点
                if parent_name in name_to_nodes:
                    # 选择第一个匹配的父节点
                    parent_node = name_to_nodes[parent_name][0]
                    parent_node.add_child(child_node)

        # 重新设置根节点:找到没有父节点的节点作为根节点
        root_nodes = []
        for node in self.family_tree.get_all_nodes():
            if node.parent is None:
                root_nodes.append(node)

        # 如果有多个根节点,选择第一个作为根节点
        if root_nodes:
            self.family_tree.root = root_nodes[0]
            print(f"设置根节点为: {self.family_tree.root.name}")
        else:
            # 如果没有找到根节点,选择第一个添加的节点
            if self.family_tree.nodes:
                self.family_tree.root = next(iter(self.family_tree.nodes.values()))
                print(f"未找到根节点,默认设置为: {self.family_tree.root.name}")

        # 更新显示
        try:
            # 确保text_color属性存在
            if not hasattr(self, 'text_color'):
                self.text_color = QColor(0, 0, 0)
                print("Warning: Added missing text_color attribute to TreeView")

            self.update_tree_display()
        except Exception as display_error:
            # 更详细的错误信息
            error_type = type(display_error).__name__
            error_message = str(display_error)
            import traceback
            error_trace = traceback.format_exc()

            detailed_message = f"加载数据成功,但更新显示时出错:\n类型: {error_type}\n消息: {error_message}\n\n详细信息:\n{error_trace}"
            print(detailed_message)
            QMessageBox.critical(self, "显示错误", f"加载数据成功,但更新显示时出错:\n{error_message}\n\n请查看控制台输出获取详细信息")

        return True
    except Exception as e:
        # 更详细的错误信息
        error_type = type(e).__name__
        error_message = str(e)
        import traceback
        error_trace = traceback.format_exc()

        detailed_message = f"加载Excel文件失败:\n类型: {error_type}\n消息: {error_message}\n\n详细信息:\n{error_trace}"
        print(detailed_message)
        QMessageBox.critical(self, "错误", f"加载Excel文件失败:\n{error_message}\n\n请查看控制台输出获取详细信息")
        return False

def export_to_image(self, file_path, width=800, height=600):
    """导出为图片"""
    try:
        # 创建图像
        image = QImage(width, height, QImage.Format_RGB32)
        image.fill(Qt.white)

        # 创建画家
        painter = QPainter(image)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setRenderHint(QPainter.TextAntialiasing)

        # 绘制场景
        self.scene.render(painter)

        # 保存图像
        image.save(file_path)
        return True
    except Exception as e:
        QMessageBox.critical(self, "错误", f"导出图片失败: {str(e)}")
        return False

def export_to_pdf(self, file_path, paper_size=None, custom_size=None, orientation=None):
    """导出为PDF"""
    try:
        # 创建打印机
        from PyQt5.QtPrintSupport import QPrinter
        printer = QPrinter(QPrinter.HighResolution)
        printer.setOutputFormat(QPrinter.PdfFormat)
        printer.setOutputFileName(file_path)

        # 设置纸张方向
        if orientation is not None:
            printer.setOrientation(orientation)

        # 获取场景的边界矩形
        if not self.scene.items():
            QMessageBox.warning(self, "警告", "没有可导出的内容")
            return False

        # 设置纸张大小
        if paper_size:
            printer.setPaperSize(paper_size)
        elif custom_size:
            printer.setPaperSize(QSizeF(custom_size[0], custom_size[1]), QPrinter.Millimeter)

        # 设置页面边距
        printer.setPageMargins(10, 10, 10, 10, QPrinter.Millimeter)

        # 调整场景大小以适应纸张
        scene_rect = self.scene.itemsBoundingRect()
        scale = min(
            (printer.width() - 20) / scene_rect.width(),
            (printer.height() - 20) / scene_rect.height()
        )

        # 创建画家并绘制
        painter = QPainter(printer)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setRenderHint(QPainter.TextAntialiasing)

        # 应用缩放
        painter.scale(scale, scale)

        # 绘制场景
        self.scene.render(painter)
        painter.end()

        return True
    except Exception as e:
        QMessageBox.critical(self, "错误", f"导出PDF失败: {str(e)}")
        return False

# TreeView类的导出方法结束位置

        # 设置纸张大小
        if paper_size is not None:
            if paper_size == QPrinter.Custom and custom_size:
                # 自定义纸张大小
                width_mm, height_mm = custom_size
                # 转换为英寸
                width_inch = width_mm / 25.4
                height_inch = height_mm / 25.4
                printer.setPaperSize(QSizeF(width_inch, height_inch), QPrinter.Inch)
            else:
                # 预设纸张大小
                printer.setPaperSize(paper_size)
        else:
            # 默认纸张大小
            printer.setPaperSize(QSizeF(8.27, 11.69), QPrinter.Inch)  # A4纸张大小(英寸)

        printer.setPageMargins(0.5, 0.5, 0.5, 0.5, QPrinter.Inch)

        # 创建画家
        painter = QPainter(printer)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setRenderHint(QPainter.TextAntialiasing)

        # 使用QGraphicsView的render方法,而不是QGraphicsScene的render方法
        # QGraphicsView已经设置了正确的视口和变换
        self.render(painter)

        painter.end()
        return True
    except Exception as e:
        QMessageBox.critical(self, "错误", f"导出PDF失败: {str(e)}")
        return False

def print_tree(self):
    """打印功能"""
    try:
        # 创建打印机
        from PyQt5.QtPrintSupport import QPrinter, QPrintDialog
        printer = QPrinter(QPrinter.HighResolution)

        # 显示打印对话框
        dialog = QPrintDialog(printer, self)
        if dialog.exec_() == QPrintDialog.Accepted:
            # 收集所有项目,包括画布背景
            items = []
            for item in self.scene.items():
                items.append(item)

            if not items:
                QMessageBox.warning(self, "警告", "没有可打印的内容")
                return False

            # 计算所有要打印项目的边界矩形
            items_rect = QRectF()
            for item in items:
                items_rect = items_rect.united(item.sceneBoundingRect())

            # 创建画家
            painter = QPainter(printer)
            painter.setRenderHint(QPainter.Antialiasing)
            painter.setRenderHint(QPainter.TextAntialiasing)

            # 获取打印机页面大小(使用像素单位)
            page_rect = printer.pageRect(QPrinter.DevicePixel)
            page_width = page_rect.width()
            page_height = page_rect.height()

            # 计算缩放比例,保持宽高比
            # 使用像素作为单位,与画布一致
            items_width = items_rect.width()
            items_height = items_rect.height()

            # 计算缩放比例,限制最大缩放,避免单个图形占满整页
            max_scale = 0.8  # 最大缩放为页面的80%
            scale_x = page_width / items_width
            scale_y = page_height / items_height
            scale = min(scale_x, scale_y, max_scale)

            # 计算打印区域的左上角位置,确保内容居中
            offset_x = (page_width - items_width * scale) / 2
            offset_y = (page_height - items_height * scale) / 2

            # 设置打印区域
            painter.translate(offset_x, offset_y)
            painter.scale(scale, scale)

            # 渲染原始场景到打印机,但只渲染指定区域
            # 这样就不会打印画布及边框
            # 使用items_rect作为源矩形,只渲染非画布项目
            self.scene.render(painter, QRectF(), items_rect)

            painter.end()
            return True
        return False
    except Exception as e:
        QMessageBox.critical(self, "错误", f"打印失败: {str(e)}")
        return False

class StylePanel(QScrollArea):
def init(self, parent=None):
super().init(parent)
self.setFrameStyle(QFrame.StyledPanel)
self.setMinimumWidth(190) # 固定宽度,与应用按钮一致
self.setMaximumWidth(190)
self.setWidgetResizable(True)

    # 确保滚动条不会覆盖内容
    self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
    # 设置垂直滚动条始终显示(保留空间),但只有在需要时才可操作
    self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)

    # 创建内容窗口
    self.content_widget = QWidget()
    self.setWidget(self.content_widget)

    # 跟踪上一次的字体大小,用于检测字体大小变化
    self._last_font_size = None

    self.setup_ui()

def setup_ui(self):
    layout = QVBoxLayout(self.content_widget)
    layout.setContentsMargins(5, 5, 5, 5)        
    # 保存对主窗口的引用,用于调用apply_style方法
    self.main_window = None

    # 画布类型设置
    canvas_group = QGroupBox("画布类型")
    canvas_layout = QVBoxLayout()

    # 画布规格设置
    canvas_size_layout = QHBoxLayout()
    label_canvas_size = QLabel("画布规格:")
    label_canvas_size.setFixedWidth(60)  # 固定标签宽度
    canvas_size_layout.addWidget(label_canvas_size)
    self.canvas_size_combo = QComboBox()
    self.canvas_size_combo.addItems(["A4", "A3", "自定义大小"])
    self.canvas_size_combo.setFixedWidth(80)  # 固定选择框宽度
    canvas_size_layout.addWidget(self.canvas_size_combo)
    canvas_size_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    canvas_layout.addLayout(canvas_size_layout)

    # 尺寸参数设置

    # 宽度设置
    width_layout = QHBoxLayout()
    label_width = QLabel("画布宽度:")
    label_width.setFixedWidth(60)  # 固定标签宽度,与画布规格标签保持一致
    width_layout.addWidget(label_width)
    self.canvas_length_spin = QDoubleSpinBox()
    self.canvas_length_spin.setRange(1, 500)  # 扩大范围
    self.canvas_length_spin.setDecimals(1)    # 显示小数点后1位
    self.canvas_length_spin.setValue(21.0)    # 默认A4竖向宽度
    self.canvas_length_spin.setSingleStep(0.1)  # 减小步长
    self.canvas_length_spin.setSuffix(" cm")
    self.canvas_length_spin.setFixedWidth(80)  # 固定宽度,与画布规格下拉框保持一致
    width_layout.addWidget(self.canvas_length_spin)
    width_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    canvas_layout.addLayout(width_layout)

    # 高度设置
    height_layout = QHBoxLayout()
    label_height = QLabel("画布高度:")
    label_height.setFixedWidth(60)  # 固定标签宽度,与画布规格标签保持一致
    height_layout.addWidget(label_height)
    self.canvas_width_spin = QDoubleSpinBox()
    self.canvas_width_spin.setRange(1, 500)  # 扩大范围
    self.canvas_width_spin.setDecimals(1)    # 显示小数点后1位
    self.canvas_width_spin.setValue(29.7)    # 默认A4竖向高度
    self.canvas_width_spin.setSingleStep(0.1)  # 减小步长
    self.canvas_width_spin.setSuffix(" cm")
    self.canvas_width_spin.setFixedWidth(80)  # 固定宽度,与画布规格下拉框保持一致
    height_layout.addWidget(self.canvas_width_spin)
    height_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    canvas_layout.addLayout(height_layout)

    # 页左边距设置
    left_margin_layout = QHBoxLayout()
    label_left_margin = QLabel("页左边距:")
    label_left_margin.setFixedWidth(60)  # 固定标签宽度
    left_margin_layout.addWidget(label_left_margin)
    self.left_margin_spin = QDoubleSpinBox()
    self.left_margin_spin.setRange(0, 50)  # 边距范围
    self.left_margin_spin.setDecimals(1)    # 显示小数点后1位
    self.left_margin_spin.setValue(1.5)    # 默认1.5cm
    self.left_margin_spin.setSingleStep(0.1)  # 减小步长
    self.left_margin_spin.setSuffix(" cm")
    self.left_margin_spin.setFixedWidth(80)  # 固定宽度,与其他控件保持一致
    left_margin_layout.addWidget(self.left_margin_spin)
    left_margin_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    canvas_layout.addLayout(left_margin_layout)

    # 页右边距设置
    right_margin_layout = QHBoxLayout()
    label_right_margin = QLabel("页右边距:")
    label_right_margin.setFixedWidth(60)  # 固定标签宽度
    right_margin_layout.addWidget(label_right_margin)
    self.right_margin_spin = QDoubleSpinBox()
    self.right_margin_spin.setRange(0, 50)  # 边距范围
    self.right_margin_spin.setDecimals(1)    # 显示小数点后1位
    self.right_margin_spin.setValue(1.5)    # 默认1.5cm
    self.right_margin_spin.setSingleStep(0.1)  # 减小步长
    self.right_margin_spin.setSuffix(" cm")
    self.right_margin_spin.setFixedWidth(80)  # 固定宽度,与其他控件保持一致
    right_margin_layout.addWidget(self.right_margin_spin)
    right_margin_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    canvas_layout.addLayout(right_margin_layout)

    # 页眉高度设置
    top_margin_layout = QHBoxLayout()
    label_top_margin = QLabel("页眉高度:")
    label_top_margin.setFixedWidth(60)  # 固定标签宽度
    top_margin_layout.addWidget(label_top_margin)
    self.top_margin_spin = QDoubleSpinBox()
    self.top_margin_spin.setRange(0, 50)  # 边距范围
    self.top_margin_spin.setDecimals(1)    # 显示小数点后1位
    self.top_margin_spin.setValue(1.5)    # 默认1.5cm
    self.top_margin_spin.setSingleStep(0.1)  # 减小步长
    self.top_margin_spin.setSuffix(" cm")
    self.top_margin_spin.setFixedWidth(80)  # 固定宽度,与其他控件保持一致
    top_margin_layout.addWidget(self.top_margin_spin)
    top_margin_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    canvas_layout.addLayout(top_margin_layout)

    # 页脚高度设置
    bottom_margin_layout = QHBoxLayout()
    label_bottom_margin = QLabel("页脚高度:")
    label_bottom_margin.setFixedWidth(60)  # 固定标签宽度
    bottom_margin_layout.addWidget(label_bottom_margin)
    self.bottom_margin_spin = QDoubleSpinBox()
    self.bottom_margin_spin.setRange(0, 50)  # 边距范围
    self.bottom_margin_spin.setDecimals(1)    # 显示小数点后1位
    self.bottom_margin_spin.setValue(1.5)    # 默认1.5cm
    self.bottom_margin_spin.setSingleStep(0.1)  # 减小步长
    self.bottom_margin_spin.setSuffix(" cm")
    self.bottom_margin_spin.setFixedWidth(80)  # 固定宽度,与其他控件保持一致
    bottom_margin_layout.addWidget(self.bottom_margin_spin)
    bottom_margin_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    canvas_layout.addLayout(bottom_margin_layout)

    canvas_group.setLayout(canvas_layout)
    layout.addWidget(canvas_group)

    # 生成方式
    reading_group = QGroupBox("生成方式")
    reading_layout = QVBoxLayout()

    # 排版方向设置
    typesetting_direction_layout = QHBoxLayout()
    label_typesetting_dir = QLabel("排版方向:")
    label_typesetting_dir.setFixedWidth(60)  # 固定标签宽度
    typesetting_direction_layout.addWidget(label_typesetting_dir)
    self.typesetting_direction_combo = QComboBox()
    self.typesetting_direction_combo.addItems(["竖排", "横排"])
    self.typesetting_direction_combo.setFixedWidth(80)  # 固定选择框宽度
    typesetting_direction_layout.addWidget(self.typesetting_direction_combo)
    typesetting_direction_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    reading_layout.addLayout(typesetting_direction_layout)

    # 阅读方向设置
    layout_direction_layout = QHBoxLayout()
    label_layout_dir = QLabel("阅读方向:")
    label_layout_dir.setFixedWidth(60)  # 固定标签宽度
    layout_direction_layout.addWidget(label_layout_dir)
    self.layout_direction_combo = QComboBox()
    self.layout_direction_combo.addItems(["正翻", "反翻"])
    self.layout_direction_combo.setFixedWidth(80)  # 固定选择框宽度
    layout_direction_layout.addWidget(self.layout_direction_combo)
    layout_direction_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    reading_layout.addLayout(layout_direction_layout)

    # 文字方向设置
    text_direction_layout = QHBoxLayout()
    label_text_dir = QLabel("文字方向:")
    label_text_dir.setFixedWidth(60)  # 固定标签宽度
    text_direction_layout.addWidget(label_text_dir)
    self.text_direction_combo = QComboBox()
    self.text_direction_combo.addItems(["横向", "竖向"])
    self.text_direction_combo.setFixedWidth(80)  # 固定选择框宽度
    text_direction_layout.addWidget(self.text_direction_combo)
    text_direction_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    reading_layout.addLayout(text_direction_layout)

    reading_group.setLayout(reading_layout)
    layout.addWidget(reading_group)

    # 图形设置(合并边框设置和图形设置)
    shape_group = QGroupBox("图形设置")
    shape_layout = QVBoxLayout()

    # 图形宽度设置
    shape_width_layout = QHBoxLayout()
    label_shape_width = QLabel("图形宽度:")
    label_shape_width.setFixedWidth(60)  # 固定标签宽度
    shape_width_layout.addWidget(label_shape_width)
    self.shape_width_spin = QDoubleSpinBox()
    self.shape_width_spin.setRange(5, 200)  # 扩大范围,允许更小的值
    self.shape_width_spin.setDecimals(1)    # 显示小数点后1位
    self.shape_width_spin.setValue(12)    # 初始化图形宽度为12mm
    self.shape_width_spin.setSingleStep(0.1)  # 减小步长以支持更精确的调整
    self.shape_width_spin.setSuffix(" mm")
    self.shape_width_spin.setFixedWidth(80)  # 稍微增加宽度以显示小数位
    shape_width_layout.addWidget(self.shape_width_spin)
    shape_width_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    shape_layout.addLayout(shape_width_layout)

    # 图形高度设置
    shape_height_layout = QHBoxLayout()
    label_shape_height = QLabel("图形高度:")
    label_shape_height.setFixedWidth(60)  # 固定标签宽度
    shape_height_layout.addWidget(label_shape_height)
    self.shape_height_spin = QDoubleSpinBox()
    self.shape_height_spin.setRange(3, 200)  # 扩大范围,允许更小的值
    self.shape_height_spin.setDecimals(1)    # 显示小数点后1位
    self.shape_height_spin.setValue(6)     # 初始化图形高度为6mm
    self.shape_height_spin.setSingleStep(0.1)  # 减小步长以支持更精确的调整
    self.shape_height_spin.setSuffix(" mm")
    self.shape_height_spin.setFixedWidth(80)  # 稍微增加宽度以显示小数位
    shape_height_layout.addWidget(self.shape_height_spin)
    shape_height_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    shape_layout.addLayout(shape_height_layout)

    # 边框线型设置
    border_style_layout = QHBoxLayout()
    label_border_style = QLabel("边框线型:")
    label_border_style.setFixedWidth(60)  # 固定标签宽度
    border_style_layout.addWidget(label_border_style)
    self.border_style_combo = QComboBox()
    self.border_style_combo.addItems(["无", "实线", "虚线", "点线", "点划线"])
    self.border_style_combo.setFixedWidth(80)  # 固定选择框宽度
    border_style_layout.addWidget(self.border_style_combo)
    border_style_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    shape_layout.addLayout(border_style_layout)

    # 边框类型设置
    border_type_layout = QHBoxLayout()
    label_border_type = QLabel("边框角型:")
    label_border_type.setFixedWidth(60)  # 固定标签宽度
    border_type_layout.addWidget(label_border_type)
    self.border_type_combo = QComboBox()
    self.border_type_combo.addItems(["直角", "圆角"])
    self.border_type_combo.setFixedWidth(80)  # 固定选择框宽度
    border_type_layout.addWidget(self.border_type_combo)
    border_type_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    shape_layout.addLayout(border_type_layout)

    # 圆角半径设置
    corner_radius_layout = QHBoxLayout()
    label_corner_radius = QLabel("圆角半径:")
    label_corner_radius.setFixedWidth(60)  # 固定标签宽度
    corner_radius_layout.addWidget(label_corner_radius)
    self.corner_radius_spin = QDoubleSpinBox()
    self.corner_radius_spin.setRange(0, 50)
    self.corner_radius_spin.setValue(1)
    self.corner_radius_spin.setSingleStep(1)
    self.corner_radius_spin.setSuffix(" mm")
    self.corner_radius_spin.setFixedWidth(80)  # 固定选择框宽度
    corner_radius_layout.addWidget(self.corner_radius_spin)
    corner_radius_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    shape_layout.addLayout(corner_radius_layout)

    # 边框宽度设置
    border_width_layout = QHBoxLayout()
    label_border_width = QLabel("边框粗细:")
    label_border_width.setFixedWidth(60)  # 固定标签宽度
    border_width_layout.addWidget(label_border_width)
    self.border_width_spin = QSpinBox()
    self.border_width_spin.setRange(1, 10)
    self.border_width_spin.setValue(1)
    self.border_width_spin.setFixedWidth(80)  # 固定选择框宽度
    border_width_layout.addWidget(self.border_width_spin)
    border_width_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    shape_layout.addLayout(border_width_layout)

    # 边框颜色设置
    border_color_layout = QHBoxLayout()
    label_border_color = QLabel("边框颜色:")
    label_border_color.setFixedWidth(60)  # 固定标签宽度
    border_color_layout.addWidget(label_border_color)
    self.border_color_button = QPushButton()
    self.border_color_button.setStyleSheet("background-color: black")
    self.border_color_button.setFixedWidth(80)  # 固定按钮宽度
    self.border_color_button.clicked.connect(lambda: self.select_color(self.border_color_button))
    border_color_layout.addWidget(self.border_color_button)
    border_color_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    shape_layout.addLayout(border_color_layout)

    # 填充颜色设置
    fill_color_layout = QHBoxLayout()
    label_fill_color = QLabel("填充颜色:")
    label_fill_color.setFixedWidth(60)  # 固定标签宽度
    fill_color_layout.addWidget(label_fill_color)
    self.fill_color_button = QPushButton()
    # 默认设置为白色背景,表示无色
    self.fill_color_button.setStyleSheet("background-color: white")
    self.fill_color_button.setFixedWidth(60) 
    self.fill_color_button.clicked.connect(lambda: self._open_color_dialog(self.fill_color_button))
    fill_color_layout.addWidget(self.fill_color_button)

    # 添加不填色复选框
    self.no_fill_checkbox = QCheckBox()
    # 默认选中,表示填充颜色为无色
    self.no_fill_checkbox.setChecked(True)
    # 设置固定大小,宽度20,高度20
    self.no_fill_checkbox.setFixedSize(20, 20)
    # 设置提示信息
    self.no_fill_checkbox.setToolTip("透明色")
    # 连接信号,当状态变化时触发
    self.no_fill_checkbox.stateChanged.connect(self._toggle_fill_color)
    # 移除间距,使两个控件紧密相连
    fill_color_layout.addWidget(self.no_fill_checkbox)

    fill_color_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    # 添加填充颜色布局到形状布局,设置上下间距为0
    shape_layout.addLayout(fill_color_layout)

    # 水平间距设置
    horizontal_spacing_layout = QHBoxLayout()
    label_horizontal_spacing = QLabel("水平间距:")
    label_horizontal_spacing.setFixedWidth(60)  # 固定标签宽度
    horizontal_spacing_layout.addWidget(label_horizontal_spacing)
    self.horizontal_spacing_spin = QDoubleSpinBox()
    self.horizontal_spacing_spin.setRange(1, 200)  # 允许更小的值,生成更紧密的图形
    self.horizontal_spacing_spin.setValue(1)  # 初始化水平间距为1mm
    self.horizontal_spacing_spin.setSingleStep(1)  # 减小步长,允许更精细的调整
    self.horizontal_spacing_spin.setSuffix(" mm")
    self.horizontal_spacing_spin.setFixedWidth(80)  # 固定选择框宽度
    horizontal_spacing_layout.addWidget(self.horizontal_spacing_spin)
    horizontal_spacing_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    shape_layout.addLayout(horizontal_spacing_layout)

    # 垂直间距设置
    vertical_spacing_layout = QHBoxLayout()
    label_vertical_spacing = QLabel("垂直间距:")
    label_vertical_spacing.setFixedWidth(60)  # 固定标签宽度
    vertical_spacing_layout.addWidget(label_vertical_spacing)
    self.vertical_spacing_spin = QDoubleSpinBox()
    self.vertical_spacing_spin.setRange(1, 1000)  # 允许更小的值,生成更紧密的图形
    self.vertical_spacing_spin.setValue(3)  # 初始化垂直间距为3mm
    self.vertical_spacing_spin.setSingleStep(1)  # 减小步长,允许更精细的调整
    self.vertical_spacing_spin.setSuffix(" mm")
    self.vertical_spacing_spin.setFixedWidth(80)  # 固定选择框宽度
    vertical_spacing_layout.addWidget(self.vertical_spacing_spin)
    vertical_spacing_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    shape_layout.addLayout(vertical_spacing_layout)

    shape_group.setLayout(shape_layout)
    layout.addWidget(shape_group)

    # 导线设置
    line_group = QGroupBox("导线设置")
    line_layout = QVBoxLayout()

    # 导线颜色设置
    line_color_layout = QHBoxLayout()
    label_line_color = QLabel("导线颜色:")
    label_line_color.setFixedWidth(60)  # 固定标签宽度
    line_color_layout.addWidget(label_line_color)
    self.line_color_button = QPushButton()
    self.line_color_button.setStyleSheet("background-color: black")
    self.line_color_button.setFixedWidth(80)  # 固定按钮宽度
    self.line_color_button.clicked.connect(lambda: self.select_color(self.line_color_button))
    line_color_layout.addWidget(self.line_color_button)
    line_color_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    line_layout.addLayout(line_color_layout)

    # 导线宽度设置
    line_width_layout = QHBoxLayout()
    label_line_width = QLabel("导线宽度:")
    label_line_width.setFixedWidth(60)  # 固定标签宽度
    line_width_layout.addWidget(label_line_width)
    self.line_width_spin = QSpinBox()
    self.line_width_spin.setRange(1, 10)
    self.line_width_spin.setValue(1)
    self.line_width_spin.setFixedWidth(80)  # 固定选择框宽度
    line_width_layout.addWidget(self.line_width_spin)
    line_width_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    line_layout.addLayout(line_width_layout)

    # 导线线型设置
    line_style_layout = QHBoxLayout()
    label_line_style = QLabel("导线线型:")
    label_line_style.setFixedWidth(60)  # 固定标签宽度
    line_style_layout.addWidget(label_line_style)
    self.line_style_combo = QComboBox()
    self.line_style_combo.addItems(["实线", "虚线", "点线", "点划线"])
    self.line_style_combo.setFixedWidth(80)  # 固定选择框宽度
    line_style_layout.addWidget(self.line_style_combo)
    line_style_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    line_layout.addLayout(line_style_layout)

    line_group.setLayout(line_layout)
    layout.addWidget(line_group)

    # 末端图形设置
    end_shape_group = QGroupBox("末端图形")
    end_shape_layout = QVBoxLayout()

    # 末端图形类型设置
    end_shape_type_layout = QHBoxLayout()
    label_end_shape_type = QLabel("末端类型:")
    label_end_shape_type.setFixedWidth(60)  # 固定标签宽度,与线设置一致
    end_shape_type_layout.addWidget(label_end_shape_type)
    self.end_shape_type_combo = QComboBox()
    self.end_shape_type_combo.addItems(["无", "圆点", "方块", "箭头", "圆圈"])
    self.end_shape_type_combo.setFixedWidth(80)  # 固定选择框宽度
    end_shape_type_layout.addWidget(self.end_shape_type_combo)
    end_shape_type_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    end_shape_layout.addLayout(end_shape_type_layout)

    # 图形大小设置
    end_shape_size_layout = QHBoxLayout()
    label_end_shape_size = QLabel("图形大小:")
    label_end_shape_size.setFixedWidth(60)  # 固定标签宽度,与线设置一致
    end_shape_size_layout.addWidget(label_end_shape_size)
    self.end_shape_size_spin = QDoubleSpinBox()
    self.end_shape_size_spin.setRange(1, 20)  # 图形大小范围(像素)
    self.end_shape_size_spin.setDecimals(1)    # 显示小数点后1位
    self.end_shape_size_spin.setValue(5)    # 默认大小为5像素
    self.end_shape_size_spin.setSingleStep(0.5)  # 减小步长以支持更精确的调整
    self.end_shape_size_spin.setSuffix(" px")
    self.end_shape_size_spin.setFixedWidth(80)  # 固定输入框宽度
    end_shape_size_layout.addWidget(self.end_shape_size_spin)
    end_shape_size_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    end_shape_layout.addLayout(end_shape_size_layout)

    # 轮廓线形设置
    end_shape_line_style_layout = QHBoxLayout()
    label_end_shape_line_style = QLabel("轮廓线形:")
    label_end_shape_line_style.setFixedWidth(60)  # 固定标签宽度,与线设置一致
    end_shape_line_style_layout.addWidget(label_end_shape_line_style)
    self.end_shape_line_style_combo = QComboBox()
    self.end_shape_line_style_combo.addItems(["无", "实线", "虚线", "点线", "点划线"])
    self.end_shape_line_style_combo.setCurrentIndex(1)  # 设置"实线"为默认选项
    self.end_shape_line_style_combo.setFixedWidth(80)  # 固定选择框宽度
    end_shape_line_style_layout.addWidget(self.end_shape_line_style_combo)
    end_shape_line_style_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    end_shape_layout.addLayout(end_shape_line_style_layout)

    # 轮廓粗细设置
    end_shape_line_width_layout = QHBoxLayout()
    label_end_shape_line_width = QLabel("轮廓粗细:")
    label_end_shape_line_width.setFixedWidth(60)  # 固定标签宽度,与线设置一致
    end_shape_line_width_layout.addWidget(label_end_shape_line_width)
    self.end_shape_line_width_spin = QDoubleSpinBox()
    self.end_shape_line_width_spin.setRange(0.5, 10)  # 轮廓粗细范围(像素)
    self.end_shape_line_width_spin.setDecimals(1)    # 显示小数点后1位
    self.end_shape_line_width_spin.setValue(1)    # 默认粗细为1像素
    self.end_shape_line_width_spin.setSingleStep(0.5)  # 减小步长以支持更精确的调整
    self.end_shape_line_width_spin.setSuffix(" px")
    self.end_shape_line_width_spin.setFixedWidth(80)  # 固定输入框宽度
    end_shape_line_width_layout.addWidget(self.end_shape_line_width_spin)
    end_shape_line_width_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    end_shape_layout.addLayout(end_shape_line_width_layout)

    # 轮廓颜色设置
    end_shape_color_layout = QHBoxLayout()
    label_end_shape_color = QLabel("轮廓颜色:")
    label_end_shape_color.setFixedWidth(60)  # 固定标签宽度,与线设置一致
    end_shape_color_layout.addWidget(label_end_shape_color)
    self.end_shape_color_button = QPushButton()
    self.end_shape_color_button.setFixedWidth(80)  # 固定按钮宽度,与其他颜色按钮一致
    self.end_shape_color_button.setStyleSheet("background-color: #000000")  # 默认颜色:黑色
    self.end_shape_color_button.clicked.connect(lambda: self.select_color(self.end_shape_color_button))
    end_shape_color_layout.addWidget(self.end_shape_color_button)
    end_shape_color_layout.setAlignment(Qt.AlignLeft)  # 左对齐
    end_shape_layout.addLayout(end_shape_color_layout)

    end_shape_group.setLayout(end_shape_layout)
    layout.addWidget(end_shape_group)

    # 文字设置
    text_group = QGroupBox("文字设置")
    text_layout = QGridLayout()

    # 字体选择
    font_label = QLabel("文字字体:")
    font_label.setFixedWidth(60)  # 固定标签宽度
    self.font_combo = QFontComboBox()
    self.font_combo.setCurrentFont(QFont("SimHei"))
    self.font_combo.setFixedWidth(80)  # 固定选择框宽度,与线设置一致
    text_layout.addWidget(font_label, 0, 0)
    text_layout.addWidget(self.font_combo, 0, 1)

    # 字号选择
    size_label = QLabel("文字大小:")
    size_label.setFixedWidth(60)  # 固定标签宽度
    self.font_size_spin = QSpinBox()
    self.font_size_spin.setRange(8, 48)
    self.font_size_spin.setValue(12)
    self.font_size_spin.setFixedWidth(80)  # 固定选择框宽度
    text_layout.addWidget(size_label, 1, 0)
    text_layout.addWidget(self.font_size_spin, 1, 1)

    # 文字颜色 - 另起一行
    text_color_label = QLabel("文字颜色:")
    text_color_label.setFixedWidth(60)  # 固定标签宽度
    self.text_color_button = QPushButton()
    self.text_color_button.setFixedWidth(80)  # 固定按钮宽度
    self.text_color_button.setStyleSheet("background-color: #000000")
    self.text_color_button.clicked.connect(lambda: self.select_color(self.text_color_button))
    text_layout.addWidget(text_color_label, 2, 0)
    text_layout.addWidget(self.text_color_button, 2, 1)

    # 字符间距
    letter_spacing_label = QLabel("文字间距:")
    letter_spacing_label.setFixedWidth(60)  # 固定标签宽度
    self.letter_spacing_spin = QDoubleSpinBox()
    self.letter_spacing_spin.setRange(0.0, 2.0)
    self.letter_spacing_spin.setValue(0.0)
    self.letter_spacing_spin.setSingleStep(0.1)
    self.letter_spacing_spin.setFixedWidth(80)  # 固定选择框宽度
    text_layout.addWidget(letter_spacing_label, 3, 0)
    text_layout.addWidget(self.letter_spacing_spin, 3, 1)

    text_group.setLayout(text_layout)
    layout.addWidget(text_group)



    # 添加垂直扩展空间,确保内容不会被滚动条遮挡
    layout.addStretch()

    # 连接信号,当参数变化时自动应用样式
    self.typesetting_direction_combo.currentIndexChanged.connect(self.apply_style)
    self.layout_direction_combo.currentIndexChanged.connect(self.apply_style)
    self.text_direction_combo.currentIndexChanged.connect(self.apply_style)
    self.border_width_spin.valueChanged.connect(self.apply_style)
    self.border_style_combo.currentIndexChanged.connect(self.apply_style)
    self.border_type_combo.currentIndexChanged.connect(self.on_border_type_changed)
    self.border_type_combo.currentIndexChanged.connect(self.apply_style)
    self.shape_width_spin.valueChanged.connect(self.apply_style)
    self.shape_height_spin.valueChanged.connect(self.apply_style)
    self.corner_radius_spin.valueChanged.connect(self.apply_style)
    self.horizontal_spacing_spin.valueChanged.connect(self.apply_style)
    self.vertical_spacing_spin.valueChanged.connect(self.on_vertical_spacing_changed)
    self.vertical_spacing_spin.valueChanged.connect(self.apply_style)
    self.line_width_spin.valueChanged.connect(self.apply_style)
    self.line_style_combo.currentIndexChanged.connect(self.apply_style)
    self.end_shape_type_combo.currentIndexChanged.connect(self.apply_style)
    self.end_shape_size_spin.valueChanged.connect(self.apply_style)
    self.end_shape_line_style_combo.currentIndexChanged.connect(self.on_end_shape_line_style_changed)
    self.end_shape_line_style_combo.currentIndexChanged.connect(self.apply_style)
    self.end_shape_line_width_spin.valueChanged.connect(self.apply_style)
    self.font_combo.currentFontChanged.connect(self.apply_style)
    self.font_size_spin.valueChanged.connect(self.apply_style)
    self.letter_spacing_spin.valueChanged.connect(self.apply_style)
    # 画布类型信号连接
    self.canvas_size_combo.currentIndexChanged.connect(self.on_canvas_size_changed)
    self.canvas_length_spin.valueChanged.connect(self.apply_style)
    self.canvas_width_spin.valueChanged.connect(self.apply_style)
    # 边距信号连接
    self.left_margin_spin.valueChanged.connect(self.apply_style)
    self.right_margin_spin.valueChanged.connect(self.apply_style)
    self.top_margin_spin.valueChanged.connect(self.apply_style)
    self.bottom_margin_spin.valueChanged.connect(self.apply_style)

def on_canvas_size_changed(self, index):
    """处理画布规格变化事件"""
    canvas_size = self.canvas_size_combo.currentText()

    # 阻止信号循环
    self.canvas_length_spin.blockSignals(True)
    self.canvas_width_spin.blockSignals(True)

    try:
        if canvas_size == "A4":
            # 默认A4竖向
            self.canvas_length_spin.setValue(21.0)
            self.canvas_width_spin.setValue(29.7)
        elif canvas_size == "A3":
            # 默认A3竖向
            self.canvas_length_spin.setValue(29.7)
            self.canvas_width_spin.setValue(42.0)
        # 自定义大小不自动设置值
    finally:
        # 重新启用信号
        self.canvas_length_spin.blockSignals(False)
        self.canvas_width_spin.blockSignals(False)

    # 应用样式
    self.apply_style()

def on_vertical_spacing_changed(self, value):
    """处理垂直间距变化事件,确保不小于最小值"""
    min_vertical_spacing = self.calculate_min_vertical_spacing()

    if value < min_vertical_spacing:
        # 立即设置为最小值,不显示警告(避免重复警告)
        self.vertical_spacing_spin.blockSignals(True)  # 阻止信号循环
        self.vertical_spacing_spin.setValue(min_vertical_spacing)
        self.vertical_spacing_spin.blockSignals(False)  # 重新启用信号

def on_border_type_changed(self):
    """处理边框类型变化事件"""
    border_type = self.border_type_combo.currentText()
    # 当边框类型为圆角时启用圆角半径设置
    if border_type == "圆角":
        self.corner_radius_spin.setEnabled(True)
        # 设置圆角半径为默认值1
        self.corner_radius_spin.setValue(1)
    else:
        self.corner_radius_spin.setEnabled(False)
        self.corner_radius_spin.setValue(0)

def on_end_shape_line_style_changed(self):
    """处理末端图形轮廓线形变化事件"""
    line_style = self.end_shape_line_style_combo.currentText()
    # 当轮廓线形为"无"时禁用轮廓粗细和轮廓颜色控件
    if line_style == "无":
        self.end_shape_line_width_spin.setEnabled(False)
        self.end_shape_color_button.setEnabled(False)
    else:
        self.end_shape_line_width_spin.setEnabled(True)
        self.end_shape_color_button.setEnabled(True)

def select_color(self, button):
    """选择颜色"""
    try:
        # 创建一个菜单,包含颜色选择和不填色选项
        menu = QMenu()

        # 添加颜色选择动作
        color_action = QAction("选择颜色...")
        color_action.triggered.connect(lambda: self._open_color_dialog(button))
        menu.addAction(color_action)

        # 添加不填色动作
        no_fill_action = QAction("不填色")
        no_fill_action.triggered.connect(lambda: self._set_no_fill(button))
        menu.addAction(no_fill_action)

        # 在按钮位置显示菜单
        # 尝试使用不同的方式显示菜单
        if hasattr(button, 'parentWidget'):
            parent_widget = button.parentWidget()
            if parent_widget:
                # 在父窗口上显示菜单
                global_pos = button.mapToGlobal(QPoint(0, button.height()))
                menu.exec_(global_pos)
            else:
                # 如果没有父窗口,使用全局位置
                menu.exec_(QCursor.pos())
        else:
            # 如果无法获取父窗口,使用全局位置
            menu.exec_(QCursor.pos())
    except Exception as e:
        # 捕获所有异常,确保程序不会崩溃
        print(f"Error in select_color: {str(e)}")
        # 直接打开颜色选择对话框作为备选方案
        self._open_color_dialog(button)

def _open_color_dialog(self, button):
    """打开颜色选择对话框"""
    # 获取当前按钮的颜色
    try:
        current_color = button.styleSheet().split(':')[1].strip()
        current_qcolor = QColor(current_color)
    except (IndexError, ValueError):
        # 如果样式表格式不正确,使用默认颜色
        current_qcolor = QColor(255, 255, 255)  # 默认白色

    # 打开颜色选择对话框
    color = QColorDialog.getColor(current_qcolor, self, "选择颜色")
    if color.isValid():
        # 清空按钮文本,只显示颜色
        button.setText("")
        # 更新按钮的背景颜色,与边框颜色按钮样式一致
        button.setStyleSheet(f"background-color: {color.name()}")
        # 同步更新复选框状态为未选中
        if hasattr(self, 'no_fill_checkbox') and button == self.fill_color_button:
            self.no_fill_checkbox.setChecked(False)
        # 应用样式
        self.apply_style()

def _set_no_fill(self, button):
    """设置不填色"""
    try:
        # 使用白色表示不填色
        button.setStyleSheet("background-color: white; border: 1px solid black")
        # 同步更新复选框状态为选中
        if hasattr(self, 'no_fill_checkbox'):
            self.no_fill_checkbox.setChecked(True)
        # 应用样式
        self.apply_style()
    except Exception as e:
        # 捕获所有异常,确保程序不会崩溃
        print(f"Error in _set_no_fill: {str(e)}")

def _toggle_fill_color(self, state):
    """切换填充颜色状态"""
    try:
        if state == Qt.Checked:  # 选中,设置为白色表示无色
            self.fill_color_button.setText("")
            self.fill_color_button.setStyleSheet("background-color: white")
        # 未选中时,保持当前颜色不变
        # 应用样式
        self.apply_style()
    except Exception as e:
        # 捕获所有异常,确保程序不会崩溃
        print(f"Error in _toggle_fill_color: {str(e)}")

def get_settings(self):
    """获取样式设置,实现当字体大小或文字方向变化时自动调整图形尺寸"""
    settings = {}

    # 检查字体大小或文字方向是否发生变化,如果是则自动调整图形尺寸
    current_font_size = self.font_size_spin.value()
    current_text_direction = self.text_direction_combo.currentText()

    # 检查是否需要更新图形尺寸
    update_shape_size = False
    if self._last_font_size is not None and current_font_size != self._last_font_size:
        update_shape_size = True
    elif hasattr(self, '_last_text_direction') and self._last_text_direction != current_text_direction:
        # 文字方向改变时,只需要交换当前的宽度和高度值
        current_width = self.shape_width_spin.value()
        current_height = self.shape_height_spin.value()
        # 交换宽度和高度值
        self.shape_width_spin.setValue(current_height)
        self.shape_height_spin.setValue(current_width)
    elif not hasattr(self, '_last_text_direction'):
        # 首次运行时需要计算初始尺寸
        update_shape_size = True

    if update_shape_size:
        # 使用QFontMetrics实际测量字体宽度和高度
        from PyQt5.QtGui import QFontMetrics, QFont
        from PyQt5.QtWidgets import QApplication

        # 创建与当前设置匹配的字体
        current_font = QFont(self.font_combo.currentFont().family(), current_font_size)

        # 使用QFontMetrics测量字体尺寸
        font_metrics = QFontMetrics(current_font)

        # 获取应用程序的DPI,用于像素到毫米的转换
        dpi = QApplication.desktop().logicalDpiX()

        if current_text_direction == "竖向":
            # 竖向文字:每个字符占一行,宽度为单个字符宽度,高度为字符数*单个字符高度
            # 测量单个汉字的宽度和高度
            single_char_width_px = font_metrics.horizontalAdvance("测")
            single_char_height_px = font_metrics.height()

            # 假设节点中平均有2个汉字
            avg_char_count = 2
            text_width_px = single_char_width_px
            text_height_px = single_char_height_px * avg_char_count
        else:
            # 横向文字:测量两个汉字("测试")的宽度,这是节点中常见的文本长度
            text_width_px = font_metrics.horizontalAdvance("测试")
            text_height_px = font_metrics.height()

        # 将像素转换为毫米(1英寸=25.4毫米)
        text_width_mm = (text_width_px / dpi) * 25.4
        text_height_mm = (text_height_px / dpi) * 25.4

        # 外框与文字间距:文字宽度+1mm,文字高度+1mm
        padding = 1  # 每个方向各增加1mm
        ideal_width = text_width_mm + padding
        ideal_height = text_height_mm + padding

        # 设置最小尺寸限制(确保可读性)
        min_width = 8  # 最小宽度(mm)
        min_height = 4  # 最小高度(mm)
        ideal_width = max(ideal_width, min_width)
        ideal_height = max(ideal_height, min_height)

        # 更新UI控件中的图形尺寸(保留小数点后1位精度)
        self.shape_width_spin.setValue(round(ideal_width, 1))
        self.shape_height_spin.setValue(round(ideal_height, 1))

    # 保存当前字体大小和文字方向作为下一次比较的基准
    self._last_font_size = current_font_size
    self._last_text_direction = current_text_direction

    # 排版方向设置
    typesetting_direction = self.typesetting_direction_combo.currentText()

    # 阅读方向设置
    layout_direction = self.layout_direction_combo.currentText()

    # 根据排版方向设置布局方向
    if typesetting_direction == "竖排":
        # 竖排:第一世在最上边第一行,第二世在第二行……
        if layout_direction == "正翻":
            layout_direction_value = "vertical_normal"
        else:
            layout_direction_value = "vertical_reverse"
    else:
        # 横排:第一世在最左边第一列,第二世在第二列……
        if layout_direction == "正翻":
            layout_direction_value = "horizontal_normal"
        else:
            layout_direction_value = "horizontal_reverse"

    settings['layout_direction'] = layout_direction_value

    # 文字方向设置
    text_direction = self.text_direction_combo.currentText()
    settings['text_direction'] = text_direction

    # 边框设置映射
    border_style_map = {
        "无": Qt.NoPen,
        "实线": Qt.SolidLine,
        "虚线": Qt.DashLine,
        "点线": Qt.DotLine,
        "点划线": Qt.DashDotLine
    }
    settings['border_style'] = border_style_map.get(self.border_style_combo.currentText(), Qt.SolidLine)

    # 导线样式映射
    line_style_map = {
        "实线": Qt.SolidLine,
        "虚线": Qt.DashLine,
        "点线": Qt.DotLine,
        "点划线": Qt.DashDotLine
    }
    settings['line_style'] = line_style_map.get(self.line_style_combo.currentText(), Qt.SolidLine)

    # 边框类型映射
    border_type_map = {
        "直角": 0,
        "圆角": 5
    }
    # 如果是圆角,则使用自定义的圆角半径
    if self.border_type_combo.currentText() == "圆角":
        # 确保圆角半径不超过图形宽度的一半
        max_radius = min(self.shape_width_spin.value() / 2, self.shape_height_spin.value() / 2)
        corner_radius = min(self.corner_radius_spin.value(), max_radius)
        settings['corner_radius'] = corner_radius
    else:
        settings['corner_radius'] = border_type_map.get(self.border_type_combo.currentText(), 0)

    # 其他设置
    # 处理填充颜色,支持不填色选项
    if self.no_fill_checkbox.isChecked():
        # 使用特殊值表示不填色
        settings['box_color'] = 'no_fill'
    else:
        # 只有当填充颜色有了真正选择的颜色时,才读取填充颜色真正的色值
        fill_color_style = self.fill_color_button.styleSheet()
        if 'background-color:' in fill_color_style:
            # 找到background-color属性的起始位置
            start_idx = fill_color_style.find('background-color:') + len('background-color:')
            # 找到下一个分号或字符串结束
            end_idx = fill_color_style.find(';', start_idx)
            if end_idx == -1:
                end_idx = len(fill_color_style)
            fill_color_value = fill_color_style[start_idx:end_idx].strip()
            # 只有当填充颜色不是透明时,才使用该颜色
            if fill_color_value != 'transparent':
                settings['box_color'] = QColor(fill_color_value)
            else:
                # 默认使用白色
                settings['box_color'] = QColor('white')
        else:
            # 默认使用白色
            settings['box_color'] = QColor('white')

    settings['border_color'] = QColor(self.border_color_button.styleSheet().split(':')[1].strip())
    settings['line_color'] = QColor(self.line_color_button.styleSheet().split(':')[1].strip())
    settings['text_color'] = QColor(self.text_color_button.styleSheet().split(':')[1].strip())
    settings['border_width'] = self.border_width_spin.value()
    settings['line_width'] = self.line_width_spin.value()
    settings['horizontal_spacing_mm'] = self.horizontal_spacing_spin.value()
    settings['vertical_spacing_mm'] = self.vertical_spacing_spin.value()
    settings['letter_spacing'] = self.letter_spacing_spin.value()

    # 设置字体
    font = self.font_combo.currentFont()
    font.setPointSize(self.font_size_spin.value())
    settings['font'] = font

    # 添加图形宽度和高度参数
    settings['custom_box_width_mm'] = self.shape_width_spin.value()
    settings['custom_box_height_mm'] = self.shape_height_spin.value()

    # 添加末端图形设置
    settings['end_shape_type'] = self.end_shape_type_combo.currentText()
    settings['end_shape_size'] = self.end_shape_size_spin.value()
    settings['end_shape_line_style'] = self.end_shape_line_style_combo.currentText()
    settings['end_shape_line_width'] = self.end_shape_line_width_spin.value()
    settings['end_shape_color'] = QColor(self.end_shape_color_button.styleSheet().split(':')[1].strip())

    # 添加画布类型设置
    settings['canvas_size'] = self.canvas_size_combo.currentText()
    settings['canvas_length_cm'] = self.canvas_length_spin.value()
    settings['canvas_width_cm'] = self.canvas_width_spin.value()

    # 添加边距设置
    settings['left_margin_cm'] = self.left_margin_spin.value()
    settings['right_margin_cm'] = self.right_margin_spin.value()
    settings['top_margin_cm'] = self.top_margin_spin.value()
    settings['bottom_margin_cm'] = self.bottom_margin_spin.value()

    return settings

def calculate_min_vertical_spacing(self):
    """计算最小垂直间距值,确保导线与图形连线不重合,保证可视间距"""
    # 获取当前节点高度(毫米)
    node_height_mm = self.shape_height_spin.value()

    # 获取当前线条宽度(像素转换为毫米,假设96 DPI)
    line_width_px = self.line_width_spin.value()
    line_width_mm = (line_width_px / 96.0) * 25.4

    # 最小可视间距(毫米),确保导线和节点之间有足够的可视空间
    min_visual_gap_mm = 0.5

    # 计算最小垂直间距:至少要大于线条宽度,并保证可视间距
    # 考虑到导线绘制逻辑,需要确保父节点底部到子节点顶部的距离足够
    min_vertical_spacing = max(line_width_mm * 2, min_visual_gap_mm)

    # 确保最小值至少为1mm,避免过小的值
    min_vertical_spacing = max(min_vertical_spacing, 1.0)

    return min_vertical_spacing

def apply_style(self):
    """应用样式"""
    if self.main_window:
        # 获取当前节点高度和线条宽度
        node_height_mm = self.shape_height_spin.value()
        line_width_px = self.line_width_spin.value()

        # 检查垂直间距是否小于最小值
        current_vertical_spacing = self.vertical_spacing_spin.value()
        min_vertical_spacing = self.calculate_min_vertical_spacing()

        if current_vertical_spacing < min_vertical_spacing:
            # 显示警告消息
            QMessageBox.warning(
                self,
                "警告",
                f"垂直间距不能小于最小可视间距!\n当前节点高度:{node_height_mm:.1f}mm\n当前线条宽度:{line_width_px:.1f}px\n最小允许垂直间距:{min_vertical_spacing:.1f}mm\n已自动将垂直间距设置为最小值。"
            )
            # 自动设置为最小值
            self.vertical_spacing_spin.setValue(min_vertical_spacing)

        settings = self.get_settings()
        self.main_window.tree_view.set_style_params(settings)

def apply_settings(self, settings):
    """应用设置"""
    try:
        # 应用排版方向设置
        if 'layout_direction' in settings:
            layout_direction = settings['layout_direction']
            if layout_direction == 'vertical_normal' or layout_direction == 'vertical_reverse':
                # 竖式
                self.typesetting_direction_combo.setCurrentText('竖排')
                self.layout_direction_combo.setCurrentText('正翻' if layout_direction == 'vertical_normal' else '反翻')
            elif layout_direction == 'horizontal_normal' or layout_direction == 'horizontal_reverse':
                # 横式
                self.typesetting_direction_combo.setCurrentText('横排')
                self.layout_direction_combo.setCurrentText('正翻' if layout_direction == 'horizontal_normal' else '反翻')

        # 应用边框设置设置
        if 'border_style' in settings:
            border_style_map = {
                Qt.NoPen: "无",
                Qt.SolidLine: "实线",
                Qt.DashLine: "虚线",
                Qt.DotLine: "点线",
                Qt.DashDotLine: "点划线"
            }
            self.border_style_combo.setCurrentText(border_style_map.get(settings['border_style'], "实线"))

        # 应用导线样式设置
        if 'line_style' in settings:
            line_style_map = {
                Qt.SolidLine: "实线",
                Qt.DashLine: "虚线",
                Qt.DotLine: "点线",
                Qt.DashDotLine: "点划线"
            }
            self.line_style_combo.setCurrentText(line_style_map.get(settings['line_style'], "实线"))

        # 应用边框类型设置
        if 'corner_radius' in settings:
            radius = settings['corner_radius']
            if radius == 0:
                self.border_type_combo.setCurrentText("直角")
            else:
                self.border_type_combo.setCurrentText("圆角")
                self.corner_radius_spin.setValue(radius)

        # 应用颜色设置
        if 'box_color' in settings:
            color = settings['box_color']
            if isinstance(color, dict) and 'name' in color:
                color = QColor(color['name'])
            elif isinstance(color, str):
                # 如果 color 是字符串,转换为 QColor 对象
                color = QColor(color)
            if color.alpha() == 0:
                # 透明颜色:不填色
                self.fill_color_button.setStyleSheet("background-color: transparent")
            else:
                self.fill_color_button.setStyleSheet(f"background-color: {color.name()}")

        if 'border_color' in settings:
            color = settings['border_color']
            if isinstance(color, dict) and 'name' in color:
                color = QColor(color['name'])
            elif isinstance(color, str):
                # 如果 color 是字符串,转换为 QColor 对象
                color = QColor(color)
            self.border_color_button.setStyleSheet(f"background-color: {color.name()}")

        if 'line_color' in settings:
            color = settings['line_color']
            if isinstance(color, dict) and 'name' in color:
                color = QColor(color['name'])
            elif isinstance(color, str):
                # 如果 color 是字符串,转换为 QColor 对象
                color = QColor(color)
            self.line_color_button.setStyleSheet(f"background-color: {color.name()}")

        # 应用其他数值设置
        if 'border_width' in settings:
            self.border_width_spin.setValue(settings['border_width'])

        if 'line_width' in settings:
            self.line_width_spin.setValue(settings['line_width'])

        if 'horizontal_spacing_mm' in settings:
            # 加载水平间距,允许用户自由设置
            horizontal = settings['horizontal_spacing_mm']
            self.horizontal_spacing_spin.setValue(horizontal)

        if 'vertical_spacing_mm' in settings:
            # 加载垂直间距,允许用户自由设置
            vertical = settings['vertical_spacing_mm']
            self.vertical_spacing_spin.setValue(vertical)

        if 'letter_spacing' in settings:
            self.letter_spacing_spin.setValue(settings['letter_spacing'])

        # 应用文字设置
        if 'font' in settings:
            font_data = settings['font']
            # 确保font变量是QFont对象
            if isinstance(font_data, dict):
                # 如果是字典形式的字体数据(反序列化后),重新创建QFont对象
                font = QFont()
                font.setFamily(font_data.get('family', ''))
                font.setPointSize(font_data.get('pointSize', 12))
                font.setBold(font_data.get('bold', False))
                font.setItalic(font_data.get('italic', False))
                font.setUnderline(font_data.get('underline', False))
                font.setStrikeOut(font_data.get('strikeOut', False))
            elif isinstance(font_data, QFont):
                # 如果已经是QFont对象,则直接使用
                font = font_data
            else:
                # 如果是其他类型,创建默认的QFont对象
                font = QFont()
                font.setPointSize(12)

            # 再次检查font是否为QFont对象
            if isinstance(font, QFont):
                self.font_combo.setCurrentFont(font)
                self.font_size_spin.setValue(font.pointSize())
            else:
                # 如果不是QFont对象,创建默认字体并应用
                default_font = QFont()
                default_font.setPointSize(12)
                self.font_combo.setCurrentFont(default_font)
                self.font_size_spin.setValue(12)

        if 'text_color' in settings:
            color = settings['text_color']
            if isinstance(color, dict) and 'name' in color:
                color = QColor(color['name'])
            self.text_color_button.setStyleSheet(f"background-color: {color.name()}")

        # 添加画布大小设置
        if 'canvas_size' in settings:
            self.canvas_size_combo.setCurrentText(settings['canvas_size'])
        if 'canvas_length_cm' in settings:
            self.canvas_length_spin.setValue(settings['canvas_length_cm'])
        if 'canvas_width_cm' in settings:
            self.canvas_width_spin.setValue(settings['canvas_width_cm'])

        # 添加边距设置
        if 'left_margin_cm' in settings:
            self.left_margin_spin.setValue(settings['left_margin_cm'])
        if 'right_margin_cm' in settings:
            self.right_margin_spin.setValue(settings['right_margin_cm'])
        if 'top_margin_cm' in settings:
            self.top_margin_spin.setValue(settings['top_margin_cm'])
        if 'bottom_margin_cm' in settings:
            self.bottom_margin_spin.setValue(settings['bottom_margin_cm'])

        # 应用样式到树视图
        self.apply_style()
    except Exception as e:
        QMessageBox.warning(self, "警告", f"应用设置时出错: {str(e)}")

def reset_settings(self):
    """重置设置为默认值"""
    # 设置默认排版方向和布局方向
    self.typesetting_direction_combo.setCurrentText('竖排')  # 排版方向:竖排
    self.layout_direction_combo.setCurrentText('正翻')     # 阅读方向:正翻
    self.text_direction_combo.setCurrentText('横向')       # 文字方向:横向

    # 设置默认边框设置
    self.border_style_combo.setCurrentText('实线')         # 边框线型:实线
    self.border_width_spin.setValue(1)                    # 边框粗细:1

    # 设置默认形状和大小
    self.border_type_combo.setCurrentText('直角')          # 边框类型:直角

    # 直接设置默认图形尺寸
    default_width = 12  # 默认图形宽度:12mm
    default_height = 6  # 默认图形高度:6mm

    # 设置UI控件的值
    self.shape_width_spin.setValue(default_width)
    self.shape_height_spin.setValue(default_height)

    self.corner_radius_spin.setValue(0)                   # 圆角半径:0mm
    self.corner_radius_spin.setEnabled(False)

    # 设置默认导线样式
    self.line_style_combo.setCurrentText('实线')          # 导线线型:实线
    self.line_width_spin.setValue(1)                      # 导线宽度:1
    self.end_shape_type_combo.setCurrentText('无')         # 末端类型:无
    self.end_shape_size_spin.setValue(5.0)                # 图形大小:5像素
    self.end_shape_line_style_combo.setCurrentText('无')    # 轮廓线形:无(默认)
    self.end_shape_line_width_spin.setValue(1.0)          # 轮廓粗细:1像素
    self.end_shape_color_button.setStyleSheet("background-color: #000000")  # 轮廓颜色:黑色
    # 更新末端图形控件的启用/禁用状态
    self.on_end_shape_line_style_changed()

    # 设置默认间距
    self.horizontal_spacing_spin.setValue(1)              # 水平间距:1mm
    self.vertical_spacing_spin.setValue(3)                # 垂直间距:3mm

    # 设置默认字体间距
    self.letter_spacing_spin.setValue(0)                  # 文字间距:0

    # 设置默认字体
    self.font_combo.setCurrentFont(QFont("SimHei"))       # 文字字体:黑体
    self.font_size_spin.setValue(12)                      # 文字大小:12pt

    # 设置默认颜色
    self.fill_color_button.setStyleSheet("background-color: white")  # 填充颜色:白色(默认不填色)
    self.border_color_button.setStyleSheet("background-color: #000000")  # 边框颜色:黑色
    self.line_color_button.setStyleSheet("background-color: #000000")    # 导线颜色:黑色
    self.text_color_button.setStyleSheet("background-color: #000000")     # 文字颜色:黑色

    # 设置默认画布大小
    self.canvas_size_combo.setCurrentText('A4')            # 画布规格:A4
    self.canvas_length_spin.setValue(21.0)                # 画布宽度:21cm
    self.canvas_width_spin.setValue(29.7)                # 画布高度:29.7cm

    # 设置默认边距
    self.left_margin_spin.setValue(1.5)                  # 页左边距:1.5cm
    self.right_margin_spin.setValue(1.5)                 # 页右边距:1.5cm
    self.top_margin_spin.setValue(1.5)                  # 页眉高度:1.5cm
    self.bottom_margin_spin.setValue(1.5)               # 页脚高度:1.5cm

    # 应用样式
    self.apply_style()

    # 重置比较基准值
    self._last_font_size = 12  # 默认字体大小
    self._last_text_direction = '横向'  # 默认文字方向

def export_coordinates_to_excel(self, file_path=None):
    """导出节点坐标到EXCEL文件"""
    try:
        import pandas as pd
        from datetime import datetime

        # 收集所有节点数据
        nodes_data = []

        def collect_nodes(node, level=0):
            if not node:
                return

            # 获取父节点名称
            parent_name = node.parent.name if node.parent else ""

            # 获取子节点名称列表
            children_names = ",".join([c.name for c in node.children])

            # 转换为毫米单位 (使用正确的转换因子)
            x_mm = node.x * 25.4
            y_mm = node.y * 25.4
            width_mm = node.width * 25.4
            height_mm = node.height * 25.4

            # 计算边界
            left_mm = x_mm - width_mm / 2
            right_mm = x_mm + width_mm / 2
            top_mm = y_mm - height_mm / 2
            bottom_mm = y_mm + height_mm / 2

            nodes_data.append({
                "ID": node.id,
                "名称": node.name,
                "层级": level,
                "父节点": parent_name,
                "子节点": children_names,
                "X坐标(mm)": round(x_mm, 2),
                "Y坐标(mm)": round(y_mm, 2),
                "宽度(mm)": round(width_mm, 2),
                "高度(mm)": round(height_mm, 2),
                "左边界(mm)": round(left_mm, 2),
                "右边界(mm)": round(right_mm, 2),
                "上边界(mm)": round(top_mm, 2),
                "下边界(mm)": round(bottom_mm, 2)
            })

            # 递归收集子节点
            for child in node.children:
                collect_nodes(child, level + 1)

        # 收集所有节点
        if self.main_window and hasattr(self.main_window, 'tree_view') and self.main_window.tree_view.family_tree and self.main_window.tree_view.family_tree.root:
            collect_nodes(self.main_window.tree_view.family_tree.root)

        if not nodes_data:
            QMessageBox.warning(self, "警告", "没有可导出的节点数据")
            return False

        # 创建DataFrame
        df = pd.DataFrame(nodes_data)

        # 按层级和X坐标排序
        df = df.sort_values(["层级", "X坐标(mm)"])

        # 如果没有指定文件名,使用默认文件名
        if not file_path:
            default_path = f"节点坐标表_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
            file_path, _ = QFileDialog.getSaveFileName(
                self, "导出节点坐标", default_path, "Excel文件 (*.xlsx);;所有文件 (*)"
            )

        if not file_path:
            return False

        # 导出到EXCEL
        df.to_excel(file_path, index=False, sheet_name="节点坐标")

        QMessageBox.information(self, "成功", f"节点坐标已成功导出到:\n{file_path}")
        return True

    except Exception as e:
        QMessageBox.critical(self, "错误", f"导出节点坐标失败: {str(e)}")
        return False

def analyze_spacing_issues(self):
    """分析并显示间距问题"""
    try:
        if not self.main_window or not hasattr(self.main_window, 'tree_view') or not self.main_window.tree_view.family_tree or not self.main_window.tree_view.family_tree.nodes:
            QMessageBox.warning(self, "警告", "没有可分析的节点数据")
            return

        import pandas as pd

        # 收集节点数据
        nodes_data = []

        def collect_nodes(node, level=0):
            if not node:
                return

            x_mm = node.x * 25.4
            y_mm = node.y * 25.4
            width_mm = node.width * 25.4
            height_mm = node.height * 25.4

            parent_name = node.parent.name if node.parent else ""

            nodes_data.append({
                "ID": node.id,
                "名称": node.name,
                "层级": level,
                "父节点": parent_name,
                "X坐标(mm)": round(x_mm, 2),
                "Y坐标(mm)": round(y_mm, 2),
                "宽度(mm)": round(width_mm, 2),
                "高度(mm)": round(height_mm, 2)
            })

            for child in node.children:
                collect_nodes(child, level + 1)

        if self.main_window.tree_view.family_tree.root:
            collect_nodes(self.main_window.tree_view.family_tree.root)

        df = pd.DataFrame(nodes_data)

        # 获取设置的间距值
        h_spacing = self.main_window.tree_view.horizontal_spacing_mm
        v_spacing = self.main_window.tree_view.vertical_spacing_mm

        # 分析水平间距
        h_report = []
        for level in sorted(df["层级"].unique()):
            level_df = df[df["层级"] == level].sort_values("X坐标(mm)")

            if len(level_df) <= 1:
                continue

            for i in range(len(level_df) - 1):
                curr = level_df.iloc[i]
                next_node = level_df.iloc[i + 1]

                curr_right = curr["X坐标(mm)"] + curr["宽度(mm)"] / 2
                next_left = next_node["X坐标(mm)"] - next_node["宽度(mm)"] / 2
                spacing = next_left - curr_right

                if abs(spacing - h_spacing) > 2.0:
                    h_report.append(
                        f"层级{level}: {curr['名称']} <-> {next_node['名称']}: "
                        f"{spacing:.1f}mm (期望: {h_spacing}mm)"
                    )

        # 分析垂直间距
        v_report = []
        max_level = df["层级"].max()
        for level in range(max_level):
            level_df = df[df["层级"] == level]
            next_level_df = df[df["层级"] == level + 1]

            if len(level_df) == 0 or len(next_level_df) == 0:
                continue

            avg_y_curr = level_df["Y坐标(mm)"].mean()
            avg_y_next = next_level_df["Y坐标(mm)"].mean()
            expected_spacing = v_spacing + 5.2  # 加上节点高度
            actual_spacing = avg_y_next - avg_y_curr

            if abs(actual_spacing - expected_spacing) > 2.0:
                v_report.append(
                    f"层级{level}->{level+1}: {actual_spacing:.1f}mm "
                    f"(期望: {expected_spacing:.1f}mm)"
                )

        # 生成报告
        report = "=== 间距分析报告 ===\n\n"
        report += f"设置水平间距: {h_spacing}mm\n"
        report += f"设置垂直间距: {v_spacing}mm\n\n"

        report += "--- 水平间距问题 ---\n"
        if h_report:
            report += "\n".join(h_report)
        else:
            report += "无明显问题\n"

        report += "\n\n--- 垂直间距问题 ---\n"
        if v_report:
            report += "\n".join(v_report)
        else:
            report += "无明显问题\n"

        # 显示报告
        from PyQt5.QtWidgets import QDialog, QVBoxLayout, QTextEdit, QPushButton, QLabel
        dialog = QDialog(self)
        dialog.setWindowTitle("间距分析报告")
        dialog.setMinimumSize(600, 400)

        layout = QVBoxLayout()
        layout.addWidget(QLabel("间距分析结果:"))

        text_edit = QTextEdit()
        text_edit.setPlainText(report)
        text_edit.setReadOnly(True)
        layout.addWidget(text_edit)

        btn_layout = QVBoxLayout()
        export_btn = QPushButton("导出坐标到EXCEL")
        export_btn.clicked.connect(lambda: self.export_coordinates_to_excel())
        btn_layout.addWidget(export_btn)

        close_btn = QPushButton("关闭")
        close_btn.clicked.connect(dialog.accept)
        btn_layout.addWidget(close_btn)

        layout.addLayout(btn_layout)
        dialog.setLayout(layout)
        dialog.exec_()

    except Exception as e:
        QMessageBox.critical(self, "错误", f"分析间距失败: {str(e)}")

class MainWindow(QMainWindow):
def init(self):
super().init()
self.setWindowTitle(“家谱吊线图生成器”)
self.resize(1000, 800)

    # 跟踪当前打开的文件路径
    self.current_file_path = None

    # 创建中央部件
    central_widget = QWidget()
    self.setCentralWidget(central_widget)

    # 创建水平分割器
    splitter = QSplitter(Qt.Horizontal)

    # 创建样式面板
    self.style_panel = StylePanel()

    # 创建树视图
    self.tree_view = TreeView()

    # 将样式面板和树视图添加到分割器
    splitter.addWidget(self.style_panel)
    splitter.addWidget(self.tree_view)

    # 设置分割器的初始大小
    splitter.setSizes([200, 800])

    # 创建布局并添加分割器
    layout = QHBoxLayout(central_widget)
    layout.addWidget(splitter)

    # 设置样式面板的主窗口引用
    self.style_panel.main_window = self

    # 设置树视图的主窗口引用
    self.tree_view.main_window = self

    # 创建菜单栏
    self.create_menu_bar()

    # 重置设置到默认值
    self.style_panel.reset_settings()

    # 应用初始样式
    self.style_panel.apply_style()

    # 为了确保JSON序列化正常工作,需要为QColor和QFont类型添加默认的序列化支持
    def json_default(obj):
        if isinstance(obj, QColor):
            return {'name': obj.name()}
        elif isinstance(obj, QFont):
            return {
                'family': obj.family(),
                'pointSize': obj.pointSize(),
                'bold': obj.bold(),
                'italic': obj.italic(),
                'underline': obj.underline(),
                'strikeOut': obj.strikeOut()
            }
        raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable")

    # 保存原始的json.dump函数
    original_json_dump = json.dump

    # 替换json.dump函数,添加default参数
    def patched_json_dump(*args, **kwargs):
        kwargs.setdefault('default', json_default)
        return original_json_dump(*args, **kwargs)

    # 应用补丁
    json.dump = patched_json_dump

def export_params_to_txt(self):
    """将参数区的参数值导出到TXT文件"""
    try:
        # 获取所有参数
        settings = self.style_panel.get_settings()

        # 格式化参数为文本
        params_text = "=== 家谱吊线图生成器参数 ===\n\n"

        # 添加布局相关参数
        params_text += "【布局设置】\n"
        params_text += f"排列方向: {self.style_panel.layout_direction_combo.currentText()}\n"
        params_text += f"文字方向: {self.style_panel.text_direction_combo.currentText()}\n\n"

        # 添加图形相关参数
        params_text += "【图形设置】\n"
        params_text += f"图形宽度: {settings.get('custom_box_width_mm', 0)} mm\n"
        params_text += f"图形高度: {settings.get('custom_box_height_mm', 0)} mm\n"
        params_text += f"边框线型: {self.style_panel.border_style_combo.currentText()}\n"
        params_text += f"边框类型: {self.style_panel.border_type_combo.currentText()}\n"
        params_text += f"圆角半径: {settings.get('corner_radius', 0)} mm\n"
        params_text += f"边框粗细: {settings.get('border_width', 0)}\n"
        params_text += f"填充颜色: {settings.get('box_color', QColor()).name()}\n"
        params_text += f"边框颜色: {settings.get('border_color', QColor()).name()}\n"
        params_text += f"文本颜色: {settings.get('text_color', QColor()).name()}\n\n"

        # 添加间距相关参数
        params_text += "【间距设置】\n"
        params_text += f"水平间距: {settings.get('horizontal_spacing_mm', 0)} mm\n"
        params_text += f"垂直间距: {settings.get('vertical_spacing_mm', 0)} mm\n"
        params_text += f"字间距: {settings.get('letter_spacing', 0)}\n\n"

        # 添加导线相关参数
        params_text += "【导线设置】\n"
        params_text += f"导线颜色: {settings.get('line_color', QColor()).name()}\n"
        params_text += f"导线宽度: {settings.get('line_width', 0)}\n"
        params_text += f"导线线型: {self.style_panel.line_style_combo.currentText()}\n"
        params_text += f"端点形状: {self.style_panel.end_shape_type_combo.currentText()}\n\n"

        # 添加字体相关参数
        params_text += "【字体设置】\n"
        font = settings.get('font', QFont())
        params_text += f"字体名称: {font.family()}\n"
        params_text += f"字体大小: {font.pointSize()} 磅\n"

        # 确定默认的文件名
        default_path = "."
        if self.current_file_path:
            # 从当前文件路径中提取基本名称,去掉扩展名
            base_name = os.path.splitext(os.path.basename(self.current_file_path))[0]
            # 默认保存目录为当前文件所在目录
            default_dir = os.path.dirname(self.current_file_path)
            # 构建默认的参数文件名
            default_path = os.path.join(default_dir, f"{base_name}_params.txt")

        # 使用文件对话框让用户选择保存位置
        file_path, _ = QFileDialog.getSaveFileName(
            self, "导出参数到TXT文件", default_path, "文本文件 (*.txt);;所有文件 (*)"
        )

        if file_path:
            # 将参数写入到TXT文件中
            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(params_text)

            QMessageBox.information(self, "成功", f"参数已成功导出到:\n{file_path}")
    except Exception as e:
        QMessageBox.critical(self, "错误", f"导出参数失败: {str(e)}")

def create_menu_bar(self):
    """创建菜单栏"""
    menu_bar = self.menuBar()

    # 文件菜单
    file_menu = menu_bar.addMenu("文件")

    # 新建菜单项
    new_action = QAction("新建", self)
    new_action.setShortcut("Ctrl+N")
    new_action.triggered.connect(self.new_tree)
    file_menu.addAction(new_action)

    # 打开菜单项
    open_action = QAction("打开", self)
    open_action.setShortcut("Ctrl+O")
    open_action.triggered.connect(self.open_tree)
    file_menu.addAction(open_action)

    # 从Excel导入菜单项
    import_excel_action = QAction("从Excel导入", self)
    import_excel_action.setShortcut("Ctrl+I")
    import_excel_action.triggered.connect(self.import_from_excel)
    file_menu.addAction(import_excel_action)

    # 保存菜单项
    save_action = QAction("保存", self)
    save_action.setShortcut("Ctrl+S")
    save_action.triggered.connect(self.save_tree)
    file_menu.addAction(save_action)

    # 打印菜单项
    print_action = QAction("打印", self)
    print_action.setShortcut("Ctrl+P")
    print_action.triggered.connect(self.print_tree)
    file_menu.addAction(print_action)

    # 导出菜单项
    export_menu = file_menu.addMenu("导出")

    # 导出为图片
    export_image_action = QAction("导出为图片", self)
    export_image_action.triggered.connect(self.export_to_image)
    export_menu.addAction(export_image_action)

    # 导出为PDF
    export_pdf_action = QAction("导出为PDF", self)
    export_pdf_action.triggered.connect(self.export_to_pdf)
    export_menu.addAction(export_pdf_action)

    # 导出参数到TXT
    export_params_action = QAction("导出参数到TXT", self)
    export_params_action.triggered.connect(self.export_params_to_txt)
    export_menu.addAction(export_params_action)

    # 导出节点坐标到EXCEL
    export_coords_action = QAction("导出节点坐标", self)
    def export_coords():
        if hasattr(self, 'style_panel') and hasattr(self.style_panel, 'export_coordinates_to_excel'):
            self.style_panel.export_coordinates_to_excel()
    export_coords_action.triggered.connect(export_coords)
    export_menu.addAction(export_coords_action)

    # 分析间距问题
    analyze_spacing_action = QAction("分析间距问题", self)
    def analyze_spacing():
        if hasattr(self, 'style_panel') and hasattr(self.style_panel, 'analyze_spacing_issues'):
            self.style_panel.analyze_spacing_issues()
    analyze_spacing_action.triggered.connect(analyze_spacing)
    export_menu.addAction(analyze_spacing_action)

    # 退出菜单项
    exit_action = QAction("退出", self)
    exit_action.setShortcut("Ctrl+Q")
    exit_action.triggered.connect(self.close)
    file_menu.addAction(exit_action)

    # 视图菜单
    view_menu = menu_bar.addMenu("视图")

    # 适应窗口
    fit_window_action = QAction("适应窗口", self)
    fit_window_action.setShortcut("Ctrl+F")
    fit_window_action.triggered.connect(self.fit_to_window)
    view_menu.addAction(fit_window_action)

    # 缩放菜单
    zoom_menu = view_menu.addMenu("缩放")

    # 放大
    zoom_in_action = QAction("放大", self)
    zoom_in_action.setShortcut("Ctrl++")
    zoom_in_action.triggered.connect(lambda: self.zoom(1.1))
    zoom_menu.addAction(zoom_in_action)

    # 缩小
    zoom_out_action = QAction("缩小", self)
    zoom_out_action.setShortcut("Ctrl+-")
    zoom_out_action.triggered.connect(lambda: self.zoom(0.9))
    zoom_menu.addAction(zoom_out_action)

    # 重置缩放
    reset_zoom_action = QAction("重置缩放", self)
    reset_zoom_action.setShortcut("Ctrl+0")
    reset_zoom_action.triggered.connect(self.reset_zoom)
    zoom_menu.addAction(reset_zoom_action)

    # 编辑菜单
    edit_menu = menu_bar.addMenu("编辑")

    # 添加节点
    add_node_action = QAction("添加节点", self)
    add_node_action.setShortcut("Ctrl+A")
    add_node_action.triggered.connect(self.add_node)
    edit_menu.addAction(add_node_action)

    # 删除节点
    delete_node_action = QAction("删除节点", self)
    delete_node_action.setShortcut("Delete")
    delete_node_action.triggered.connect(self.delete_node)
    edit_menu.addAction(delete_node_action)

    # 帮助菜单
    help_menu = menu_bar.addMenu("帮助")

    # 关于
    about_action = QAction("关于", self)
    about_action.triggered.connect(self.show_about)
    help_menu.addAction(about_action)

    # 创建工具栏
    self.create_toolbar()

def create_toolbar(self):
    """创建工具栏,添加常用功能的快捷图标"""
    toolbar = self.addToolBar("常用功能")
    toolbar.setMovable(False)

    # 导入
    from PyQt5.QtGui import QIcon
    import_action = QAction(QIcon("./Icons/Input.png"), "导入", self)
    import_action.setShortcut("Ctrl+I")
    import_action.setToolTip("从EXCEL文件中导入数据")
    import_action.triggered.connect(self.import_from_excel)
    toolbar.addAction(import_action)

    # 打开
    open_action = QAction(QIcon("./Icons/Open.png"), "打开", self)
    open_action.setShortcut("Ctrl+O")
    open_action.setToolTip("打开已保存的工作")
    open_action.triggered.connect(self.open_tree)
    toolbar.addAction(open_action)

    # 保存
    save_action = QAction(QIcon("./Icons/Save.png"), "保存", self)
    save_action.setShortcut("Ctrl+S")
    save_action.setToolTip("保存当前工作")
    save_action.triggered.connect(self.save_tree)
    toolbar.addAction(save_action)

    # 打印
    print_action = QAction(QIcon("./Icons/Print.png"), "打印", self)
    print_action.setShortcut("Ctrl+P")
    print_action.setToolTip("打印")
    print_action.triggered.connect(self.print_tree)
    toolbar.addAction(print_action)

    # 分隔线
    toolbar.addSeparator()

    # 导出为PNG
    export_png_action = QAction(QIcon("./Icons/PNG.png"), "导出为PNG", self)
    export_png_action.setToolTip("导出为PNG图片")
    export_png_action.triggered.connect(self.export_to_image)
    toolbar.addAction(export_png_action)

    # 导出为JPEG
    export_jpeg_action = QAction(QIcon("./Icons/JPEG.png"), "导出为JPEG", self)
    export_jpeg_action.setToolTip("导出为JPEG图片")
    export_jpeg_action.triggered.connect(self.export_to_image)
    toolbar.addAction(export_jpeg_action)

    # 导出为PDF
    export_pdf_action = QAction(QIcon("./Icons/PDF.png"), "导出为PDF", self)
    export_pdf_action.setToolTip("导出为PDF文件")
    def export_to_pdf():
        # 调用现有的PDF导出功能
        self.export_to_pdf()
    export_pdf_action.triggered.connect(export_to_pdf)
    toolbar.addAction(export_pdf_action)

    # 适应窗口
    fit_window_action = QAction(QIcon("./Icons/Refresh.png"), "适应窗口", self)
    fit_window_action.setShortcut("Ctrl+F")
    fit_window_action.setToolTip("重置显示大小")
    fit_window_action.triggered.connect(self.fit_to_window)
    toolbar.addAction(fit_window_action)

    # 正翻
    leftp_action = QAction(QIcon("./Icons/leftP.png"), "正翻", self)
    leftp_action.setToolTip("正翻(往左边翻书)")
    def set_normal_layout():
        # 设置为正翻模式
        try:
            if hasattr(self, 'style_panel') and hasattr(self.style_panel, 'layout_direction_combo'):
                # 找到"正翻"选项的索引
                index = self.style_panel.layout_direction_combo.findText("正翻")
                if index >= 0:
                    self.style_panel.layout_direction_combo.setCurrentIndex(index)
                    # 获取当前设置并应用
                    settings = self.style_panel.get_settings()
                    self.style_panel.apply_settings(settings)
        except Exception as e:
            print(f"设置正翻模式时出错: {str(e)}")
    leftp_action.triggered.connect(set_normal_layout)
    toolbar.addAction(leftp_action)

    # 反翻
    rightp_action = QAction(QIcon("./Icons/RightP.png"), "反翻", self)
    rightp_action.setToolTip("反翻(往右边翻书)")
    def set_reverse_layout():
        # 设置为反翻模式
        try:
            if hasattr(self, 'style_panel') and hasattr(self.style_panel, 'layout_direction_combo'):
                # 找到"反翻"选项的索引
                index = self.style_panel.layout_direction_combo.findText("反翻")
                if index >= 0:
                    self.style_panel.layout_direction_combo.setCurrentIndex(index)
                    # 获取当前设置并应用
                    settings = self.style_panel.get_settings()
                    self.style_panel.apply_settings(settings)
        except Exception as e:
            print(f"设置反翻模式时出错: {str(e)}")
    rightp_action.triggered.connect(set_reverse_layout)
    toolbar.addAction(rightp_action)

    # 分隔线
    toolbar.addSeparator()

    # 添加节点
    add_node_action = QAction(QIcon("./Icons/Add+.png"), "添加节点", self)
    add_node_action.setShortcut("Ctrl+A")
    add_node_action.triggered.connect(self.add_node)
    toolbar.addAction(add_node_action)

    # 删除节点
    delete_node_action = QAction(QIcon("./Icons/Add-.png"), "删除节点", self)
    delete_node_action.setShortcut("Delete")
    delete_node_action.triggered.connect(self.delete_node)
    toolbar.addAction(delete_node_action)

def new_tree(self):
    """新建树"""
    # 确认新建
    reply = QMessageBox.question(self, "确认新建", "确定要新建一个家谱树吗?当前未保存的内容将会丢失。", 
                                QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
    if reply == QMessageBox.Yes:
        # 重置当前文件路径
        self.current_file_path = None

        # 创建新的树
        self.tree_view.family_tree = FamilyTree()
        self.tree_view.update_tree_display()
        # 重置样式面板设置
        if hasattr(self, 'style_panel'):
            self.style_panel.reset_settings()

def open_tree(self):
    """打开树"""
    # 打开文件对话框,支持工程文件和CSV文件
    file_path, _ = QFileDialog.getOpenFileName(self, "打开文件", "", "工程文件 (*.ftree);;CSV文件 (*.csv)")
    if file_path:
        self.current_file_path = file_path

        if file_path.endswith('.ftree'):
            # 加载工程文件
            success = self.load_project_file(file_path)
        elif file_path.endswith('.csv'):
            # 加载CSV文件
            success = self.tree_view.load_data_from_csv(file_path)
        else:
            success = False
            QMessageBox.warning(self, "错误", "不支持的文件格式!")

        if success:
            QMessageBox.information(self, "成功", "数据加载成功!")

def save_tree(self):
    """保存树"""
    # 确定默认的文件名
    default_path = ""
    if self.current_file_path:
        # 从当前文件路径中提取基本名称,去掉扩展名
        base_name = os.path.splitext(os.path.basename(self.current_file_path))[0]
        # 默认保存目录为当前文件所在目录
        default_dir = os.path.dirname(self.current_file_path)
        # 构建默认的工程文件名,不带原文件后缀
        default_path = os.path.join(default_dir, base_name)

    # 打开文件对话框,默认保存为工程文件
    file_path, _ = QFileDialog.getSaveFileName(self, "保存文件", default_path, "工程文件 (*.ftree);;CSV文件 (*.csv)")
    if file_path:
        self.current_file_path = file_path

        if file_path.endswith('.ftree'):
            # 保存工程文件
            success = self.save_project_file(file_path)
        elif file_path.endswith('.csv'):
            # 保存CSV文件(原有功能)
            success = self.save_as_csv(file_path)
        else:
            # 默认保存为工程文件
            if not file_path.endswith('.ftree'):
                file_path += '.ftree'
            success = self.save_project_file(file_path)

        if success:
            QMessageBox.information(self, "成功", "文件保存成功!")
        else:
            QMessageBox.critical(self, "错误", "文件保存失败!")

def export_to_image(self):
    """导出为图片"""
    # 确定默认的文件名
    default_path = ""
    if self.current_file_path:
        # 从当前文件路径中提取基本名称,去掉扩展名
        base_name = os.path.splitext(os.path.basename(self.current_file_path))[0]
        # 默认保存目录为当前文件所在目录
        default_dir = os.path.dirname(self.current_file_path)
        # 构建默认的图片文件名,不要加_image后缀
        default_path = os.path.join(default_dir, f"{base_name}.png")

    # 打开文件对话框,支持PNGJPEG格式
    file_path, selected_filter = QFileDialog.getSaveFileName(self, "导出图片", default_path, "PNG图片 (*.png);;JPEG图片 (*.jpg);;所有文件 (*)")
    if file_path:
        # 确保文件路径有正确的扩展名
        if selected_filter == "PNG图片 (*.png)" and not file_path.lower().endswith('.png'):
            file_path += '.png'
        elif selected_filter == "JPEG图片 (*.jpg)" and not file_path.lower().endswith('.jpg'):
            file_path += '.jpg'

        # 导出图片
        success = self.tree_view.export_to_image(file_path)
        if success:
            QMessageBox.information(self, "成功", "图片导出成功!")

def import_from_excel(self):
    """从Excel导入数据"""
    # 打开文件对话框
    file_path, _ = QFileDialog.getOpenFileName(self, "打开Excel文件", "", "Excel文件 (*.xlsx *.xls)")
    if file_path:
        self.current_file_path = file_path
        # 加载Excel数据
        success = self.tree_view.load_data_from_excel(file_path)
        if success:
            QMessageBox.information(self, "成功", "Excel数据导入成功!")

def save_project_file(self, file_path):
    """保存工程文件"""
    try:
        # 准备要保存的数据
        project_data = {
            'version': '1.0',
            'nodes': [],
            'style_settings': {}
        }

        # 保存节点数据
        for node in self.tree_view.family_tree.get_all_nodes():
            node_data = {
                'id': node.id,
                'name': node.name,
                'note': node.note,
                'parent_id': node.parent.id if node.parent else '',
                'spouse_id': node.spouse.id if node.spouse and hasattr(node, 'spouse') else '',
                'children_ids': [child.id for child in node.children]
            }
            project_data['nodes'].append(node_data)

        # 保存样式设置
        if hasattr(self, 'style_panel'):
            project_data['style_settings'] = self.style_panel.get_settings()

        # 保存为JSON文件
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(project_data, f, ensure_ascii=False, indent=2)

        return True
    except Exception as e:
        QMessageBox.critical(self, "错误", f"保存工程文件失败: {str(e)}")
        return False

def load_project_file(self, file_path):
    """加载工程文件"""
    try:
        # 读取JSON文件
        with open(file_path, 'r', encoding='utf-8') as f:
            project_data = json.load(f)

        # 创建新的树
        self.tree_view.family_tree = FamilyTree()

        # 第一遍:创建所有节点
        node_map = {}
        for node_data in project_data.get('nodes', []):
            node = Node(node_data['id'], node_data['name'], node_data['note'])
            self.tree_view.family_tree.add_node(node)
            node_map[node_data['id']] = node

        # 第二遍:建立关系
        for node_data in project_data.get('nodes', []):
            node = node_map.get(node_data['id'])
            if node:
                # 设置父节点
                if node_data['parent_id'] and node_data['parent_id'] in node_map:
                    parent_node = node_map[node_data['parent_id']]
                    parent_node.add_child(node)

                # 设置配偶
                if node_data['spouse_id'] and node_data['spouse_id'] in node_map:
                    spouse_node = node_map[node_data['spouse_id']]
                    node.add_spouse(spouse_node)

        # 应用样式设置
        if hasattr(self, 'style_panel') and 'style_settings' in project_data:
            self.style_panel.apply_settings(project_data['style_settings'])

        # 更新显示
        self.tree_view.update_tree_display()
        return True
    except Exception as e:
        QMessageBox.critical(self, "错误", f"加载工程文件失败: {str(e)}")
        return False

def save_as_csv(self, file_path):
    """保存为CSV文件(兼容原有功能)"""
    try:
        # 保存数据
        with open(file_path, 'w', newline='', encoding='utf-8') as f:
            writer = csv.writer(f)
            # 写入表头
            writer.writerow(['ID', 'Name', 'Note', 'ParentID', 'SpouseID'])
            # 写入节点数据
            for node in self.tree_view.family_tree.get_all_nodes():
                parent_id = node.parent.id if node.parent else ''
                spouse_id = node.spouse.id if node.spouse and hasattr(node, 'spouse') else ''
                writer.writerow([node.id, node.name, node.note, parent_id, spouse_id])

        return True
    except Exception as e:
        QMessageBox.critical(self, "错误", f"保存CSV文件失败: {str(e)}")
        return False

def export_to_pdf(self):
    """导出为PDF"""
    # 确定默认的文件名
    default_path = ""
    if self.current_file_path:
        # 从当前文件路径中提取基本名称,去掉扩展名
        base_name = os.path.splitext(os.path.basename(self.current_file_path))[0]
        # 默认保存目录为当前文件所在目录
        default_dir = os.path.dirname(self.current_file_path)
        # 构建默认的PDF文件名
        default_path = os.path.join(default_dir, f"{base_name}.pdf")

    # 打开文件对话框
    file_path, _ = QFileDialog.getSaveFileName(self, "导出PDF", default_path, "PDF文件 (*.pdf)")
    if file_path:
        # 创建纸张大小选择对话框
        from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QPushButton, QSpinBox, QDoubleSpinBox
        from PyQt5.QtPrintSupport import QPrinter

        class PaperSizeDialog(QDialog):
            def __init__(self, parent=None):
                super().__init__(parent)
                self.setWindowTitle("选择纸张大小")

                layout = QVBoxLayout()

                # 纸张类型选择
                paper_type_layout = QHBoxLayout()
                paper_type_label = QLabel("纸张类型:")
                self.paper_type_combo = QComboBox()
                self.paper_type_combo.addItems(["A4", "A3", "A2", "A1", "A0", "B4", "B3", "B2", "B1", "B0", "自定义"])
                self.paper_type_combo.setCurrentText("A4")
                paper_type_layout.addWidget(paper_type_label)
                paper_type_layout.addWidget(self.paper_type_combo)
                layout.addLayout(paper_type_layout)

                # 页面方向选择
                orientation_layout = QHBoxLayout()
                orientation_label = QLabel("页面方向:")
                self.orientation_combo = QComboBox()
                self.orientation_combo.addItems(["竖向", "横向"])
                self.orientation_combo.setCurrentText("竖向")
                orientation_layout.addWidget(orientation_label)
                orientation_layout.addWidget(self.orientation_combo)
                layout.addLayout(orientation_layout)

                # 自定义纸张大小
                custom_size_layout = QVBoxLayout()
                width_layout = QHBoxLayout()
                width_label = QLabel("宽度 (mm):")
                self.width_spin = QDoubleSpinBox()
                self.width_spin.setRange(1, 10000)
                self.width_spin.setValue(210)  # A4宽度
                width_layout.addWidget(width_label)
                width_layout.addWidget(self.width_spin)

                height_layout = QHBoxLayout()
                height_label = QLabel("高度 (mm):")
                self.height_spin = QDoubleSpinBox()
                self.height_spin.setRange(1, 10000)
                self.height_spin.setValue(297)  # A4高度
                height_layout.addWidget(height_label)
                height_layout.addWidget(self.height_spin)

                custom_size_layout.addLayout(width_layout)
                custom_size_layout.addLayout(height_layout)
                layout.addLayout(custom_size_layout)

                # 按钮
                button_layout = QHBoxLayout()
                ok_button = QPushButton("确定")
                cancel_button = QPushButton("取消")
                button_layout.addStretch()
                button_layout.addWidget(ok_button)
                button_layout.addWidget(cancel_button)
                layout.addLayout(button_layout)

                self.setLayout(layout)

                # 连接信号
                ok_button.clicked.connect(self.accept)
                cancel_button.clicked.connect(self.reject)
                self.paper_type_combo.currentTextChanged.connect(self.on_paper_type_changed)

                # 初始化自定义纸张大小
                self.on_paper_type_changed("A4")

            def on_paper_type_changed(self, paper_type):
                # 根据选择的纸张类型设置默认尺寸
                paper_sizes = {
                    "A4": (210, 297),
                    "A3": (297, 420),
                    "A2": (420, 594),
                    "A1": (594, 841),
                    "A0": (841, 1189),
                    "B4": (250, 353),
                    "B3": (353, 500),
                    "B2": (500, 707),
                    "B1": (707, 1000),
                    "B0": (1000, 1414)
                }

                if paper_type in paper_sizes:
                    width, height = paper_sizes[paper_type]
                    self.width_spin.setValue(width)
                    self.height_spin.setValue(height)
                    self.width_spin.setEnabled(False)
                    self.height_spin.setEnabled(False)
                else:
                    self.width_spin.setEnabled(True)
                    self.height_spin.setEnabled(True)

            def get_paper_settings(self):
                paper_type = self.paper_type_combo.currentText()
                orientation = self.orientation_combo.currentText()

                if orientation == "横向":
                    orientation = QPrinter.Landscape
                else:
                    orientation = QPrinter.Portrait

                if paper_type != "自定义":
                    paper_sizes = {
                        "A4": QPrinter.A4,
                        "A3": QPrinter.A3,
                        "A2": QPrinter.A2,
                        "A1": QPrinter.A1,
                        "A0": QPrinter.A0,
                        "B4": QPrinter.B4,
                        "B3": QPrinter.B3,
                        "B2": QPrinter.B2,
                        "B1": QPrinter.B1,
                        "B0": QPrinter.B0
                    }
                    return paper_sizes.get(paper_type, QPrinter.A4), None, orientation
                else:
                    # 自定义纸张大小
                    width_mm = self.width_spin.value()
                    height_mm = self.height_spin.value()
                    return QPrinter.Custom, (width_mm, height_mm), orientation

        # 显示纸张大小选择对话框
        dialog = PaperSizeDialog(self)
        if dialog.exec_() == QDialog.Accepted:
            paper_size, custom_size, orientation = dialog.get_paper_settings()
            # 导出PDF
            success = self.tree_view.export_to_pdf(file_path, paper_size, custom_size, orientation)
            if success:
                QMessageBox.information(self, "成功", "PDF导出成功!")

def print_tree(self):
    """打印功能"""
    try:
        # 调用TreeView的打印方法
        success = self.tree_view.print_tree()
        if success:
            QMessageBox.information(self, "成功", "打印成功!")
    except Exception as e:
        QMessageBox.critical(self, "错误", f"打印失败: {str(e)}")

def fit_to_window(self):
    """适应窗口"""
    if self.tree_view.scene.items():
        self.tree_view.fitInView(self.tree_view.scene.itemsBoundingRect(), Qt.KeepAspectRatio)

def zoom(self, factor):
    """缩放"""
    if factor > 1.0:
        # 放大,但限制最大缩放因子为10.0
        if self.tree_view.scale_factor < 10.0:
            self.tree_view.scale(factor, factor)
            self.tree_view.scale_factor *= factor
    else:
        # 缩小,但限制最小缩放因子为0.1
        if self.tree_view.scale_factor > 0.1:
            self.tree_view.scale(factor, factor)
            self.tree_view.scale_factor *= factor

def reset_zoom(self):
    """重置缩放"""
    self.tree_view.resetTransform()

def add_node(self):
    """添加节点"""
    if self.tree_view.selected_node:
        # 获取新节点的名称
        name, ok = QInputDialog.getText(self, "添加子节点", "请输入子节点名称:")
        if ok and name:
            # 创建新节点
            new_id = f"node_{len(self.tree_view.family_tree.nodes) + 1}"
            new_node = Node(new_id, name)
            # 添加到树中
            self.tree_view.family_tree.add_node(new_node)
            # 设置父子关系
            self.tree_view.selected_node.add_child(new_node)
            # 更新视图
            self.tree_view.update_tree_display()
    else:
        QMessageBox.warning(self, "警告", "请先选择一个节点作为父节点!")

def delete_node(self):
    """删除节点"""
    if self.tree_view.selected_node:
        # 确认删除
        reply = QMessageBox.question(self, "确认删除", f"确定要删除节点'{self.tree_view.selected_node.name}'吗?\n删除节点将同时删除其所有子节点。", 
                                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            # 保存要删除的节点ID
            node_id = self.tree_view.selected_node.id
            # 如果删除的是根节点,且树中还有其他节点,需要先更新根节点
            if self.tree_view.selected_node == self.tree_view.family_tree.root and len(self.tree_view.family_tree.nodes) > 1:
                # 找到下一个可能的根节点(例如,第一个子节点)
                for node in self.tree_view.family_tree.nodes.values():
                    if node != self.tree_view.selected_node:
                        self.tree_view.family_tree.root = node
                        break
            # 删除节点
            self.tree_view.family_tree.remove_node(node_id)
            # 更新视图
            self.tree_view.update_tree_display()
            # 重置选中的节点
            self.tree_view.selected_node = None
    else:
        QMessageBox.warning(self, "警告", "请先选择一个要删除的节点!")

def show_about(self):
    """显示关于对话框"""
    about_text = "家谱吊线图生成器\n\n版本: 1.0\n\n一个用于生成家族谱系吊线图的工具,可以帮助用户可视化家族关系。\n\n支持的功能:\n- 加载和保存家谱数据(CSV格式)\n- 自定义图形样式(颜色、边框、导线等)\n- 调整布局方向和间距\n- 导出为图片或PDF\n\n使用方法:\n1. 从CSV文件加载数据或通过右键菜单添加节点\n2. 在右侧面板调整样式参数\n3. 使用菜单栏导出图形\n\n版权所有 © 2023"
    QMessageBox.about(self, "关于", about_text)

def closeEvent(self, event):
    """关闭窗口时的处理"""
    reply = QMessageBox.question(self, "确认退出", "确定要退出吗?当前未保存的内容将会丢失。", 
                                QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
    if reply == QMessageBox.Yes:
        event.accept()
    else:
        event.ignore()

主函数

if name == ‘main‘:

# 确保中文正常显示
import sys
import os
import traceback
import time

# 创建一个标记文件,证明脚本开始运行
with open('program_started.txt', 'w', encoding='utf-8') as f:
    f.write(f'Program started at: {time.strftime("%Y-%m-%d %H:%M:%S")}\n')
    f.write(f'Python version: {sys.version}\n')
    f.write(f'Current directory: {os.getcwd()}\n')

try:
    print("开始运行家谱吊线图生成器...")
    print(f"Python版本: {sys.version}")
    print(f"当前目录: {os.getcwd()}")
    print(f"时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")

    print("\n创建QApplication实例...")
    app = QApplication(sys.argv)
    print("QApplication实例创建成功")

    # 设置应用程序样式
    print("\n设置应用程序样式...")
    app.setStyle('Fusion')
    print("应用程序样式设置成功")

    # 创建主窗口
    print("\n创建MainWindow实例...")
    window = MainWindow()
    print("MainWindow实例创建成功")

    print("\n显示主窗口...")
    window.show()
    print("主窗口显示成功")

    # 运行应用程序
    print("\n开始运行应用程序事件循环...")
    exit_code = app.exec_()
    print(f"应用程序事件循环结束,退出码: {exit_code}")

    # 创建结束标记文件
    with open('program_ended.txt', 'w', encoding='utf-8') as f:
        f.write(f'Program ended at: {time.strftime("%Y-%m-%d %H:%M:%S")}\n')
        f.write(f'Exit code: {exit_code}\n')

    sys.exit(exit_code)
except Exception as e:
    print(f"\n错误: {str(e)}")
    print("详细错误信息:")
    traceback.print_exc()

    # 创建错误标记文件
    with open('program_error.txt', 'w', encoding='utf-8') as f:
        f.write(f'Program error at: {time.strftime("%Y-%m-%d %H:%M:%S")}\n')
        f.write(f'Error: {str(e)}\n')
        f.write(f'Traceback: {traceback.format_exc()}\n')

    print("\n按回车键退出...")
    sys.exit(1)
    数据文件:

谱名 性别 父名 *世次 *ID 父亲ID
书熙 男 *22 *220001
登琼 男 书熙 *23 *230002 220001
登珍 男 书熙 *23 *230003 220001
登瑞 女 书熙 *23 *230004 220001
科渭 男 登琼 *24 *240005 230002
科滨 男 登琼 *24 *240006 230002
科汉 男 登琼 *24 *240007 230002
科淋 男 登琼 *24 *240008 230002
科洲 男 登珍 *24 *240009 230003
科渊 男 登珍 *24 *240010 230003
科海 男 登珍 *24 *240011 230003
科源 男 登珍 *24 *240012 230003
科荛 男 登瑞 *24 *240013 230004
科蕣 男 登瑞 *24 *240014 230004
第识 男 科滨 *25 *250015 240006
第训 男 科滨 *25 *250016 240006
第详 男 科海 *25 *250017 240011
第邦 男 科海 *25 *250018 240011
第諠 男 科荛 *25 *250019 240013
第谋 男 科荛 *25 *250020 240013
第譁 男 科荛 *25 *250021 240013
第谟 男 科荛 *25 *250022 240013
第诏 男 科荛 *25 *250023 240013
第诠 男 科荛 *25 *250024 240013
第诲 男 科荛 *25 *250025 240013
第谦 男 科荛 *25 *250026 240013
仁茂 男 第训 *26 *260027 250016
仁菎 男 第训 *26 *260028 250016
仁菖 男 第训 *26 *260029 250016
仁盛 男 第训 *26 *260030 250016
仁耀 男 第详 *26 *260031 250017
仁辉 男 第详 *26 *260032 250017
仁標 男 第谋 *26 *260033 250020
义禄 男 仁菖 *27 *270034 260029
义财 男 仁菖 *27 *270035 260029
义洪 男 仁菖 *27 *270036 260029
义音 男 仁菖 *27 *270037 260029
义福 男 仁盛 *27 *270038 260030
义禧 男 仁盛 *27 *270039 260030
义祥 男 仁盛 *27 *270040 260030
义发 男 仁盛 *27 *270041 260030
义泰 男 仁標 *27 *270042 260033
义参 男 仁標 *27 *270043 260033
义拳 男 仁標 *27 *270044 260033
葆冯 男 义禄 *28 *280045 270034
葆鸟 女 义禄 *28 *280046 270034
葆鸣 男 义财 *28 *280047 270035
葆凤 男 义洪 *28 *280048 270036
葆鸿 男 义发 *28 *280049 270041
葆逊 男 义发 *28 *280050 270041
葆迎 男 义泰 *28 *280051 270042
葆进 男 义泰 *28 *280052 270042
葆逑 男 义泰 *28 *280053 270042
葆遲 男 义泰 *28 *280054 270042
坚龙 男 葆逊 *29 *290055 280050
坚锴 男 葆逊 *29 *290056 280050
坚忠 男 葆进 *29 *290057 280052
贞娣 男 坚龙 *30 *300058 290055
张静 男 坚龙 *30 *300059 290055
张清 男 坚龙 *30 *300060 290055
张婷 男 坚锴 *30 *300061 290056
张伟 男 坚锴 *30 *300062 290056
贞龙 男 坚忠 *30 *300063 290057
贞海 男 坚忠 *30 *300064 290057
贞华 男 坚忠 *30 *300065 290057
贞东 男 坚忠 *30 *300066 290057
这是程序代码生成的结果
而我想要的结果是节点排列紧凑一点:
目标结果
请哪位老师百忙中抽空帮我分析一下逻辑要怎么改,挠头四天了,没搞定!万分感谢!

讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!