张世豪
6 天以前 c498385fb7e372d13e2ee76d7b54ae2381728082
src/zhuye/Shouye.java
@@ -1,744 +1,3746 @@
package zhuye;
import javax.swing.*;
import javax.swing.Timer;
import baseStation.BaseStation;
import set.Setsys;
import baseStation.BaseStationDialog;
import java.awt.*;
import java.awt.event.*;
import chuankou.dellmessage;
import chuankou.sendmessage;
import chuankou.SerialPortService;
import dikuai.Dikuai;
import dikuai.Dikuaiguanli;
import dikuai.addzhangaiwu;
import gecaoji.Device;
import gecaoji.Gecaoji;
import gecaoji.MowerBoundaryChecker;
import set.Sets;
import set.debug;
import udpdell.UDPServer;
import zhangaiwu.AddDikuai;
import yaokong.Control04;
import yaokong.RemoteControlDialog;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.awt.geom.Point2D;
/**
 * 首页界面 - 适配6.5寸竖屏,使用独立的MapRenderer进行绘制
 */
public class Shouye extends JPanel {
    private static final long serialVersionUID = 1L;
    private static Shouye instance;
   private static final long serialVersionUID = 1L;
   private static Shouye instance;
   // 主题颜色
   private final Color THEME_COLOR = new Color(46, 139, 87);
   private final Color THEME_HOVER_COLOR = new Color(30, 107, 69);
   private final Color BACKGROUND_COLOR = new Color(250, 250, 250);
   private final Color PANEL_BACKGROUND = new Color(255, 255, 255);
   private final Color STATUS_PAUSE_COLOR = new Color(255, 165, 0);
   // 组件
   private JPanel headerPanel;
   private JPanel mainContentPanel;
   private JPanel controlPanel;
   private JPanel navigationPanel;
   private JPanel visualizationPanel;
   // 按钮
   private JButton startBtn;
   private JButton stopBtn;
   private JButton legendBtn;
   private JButton remoteBtn;
   private JButton areaSelectBtn;
   private JButton baseStationBtn;
   private JButton bluetoothBtn;
   private JLabel dataPacketCountLabel;
   private JLabel mowerSpeedValueLabel;
   private JLabel mowerSpeedUnitLabel;
   private JLabel mowingProgressLabel;
   private gpszhuangtai fixQualityIndicator;
   // 导航按钮
   private JButton homeNavBtn;
   private JButton areasNavBtn;
   private JButton settingsNavBtn;
   // 状态显示
   private JLabel statusLabel;
   private JLabel speedLabel;  // 速度显示标签
   private JLabel areaNameLabel;
   // 边界警告相关
   private Timer boundaryWarningTimer;  // 边界警告检查定时器
   private Timer warningBlinkTimer;  // 警告图标闪烁定时器
   private boolean isMowerOutsideBoundary = false;  // 割草机是否在边界外
   private boolean warningIconVisible = true;  // 警告图标显示状态(用于闪烁)
   // 以割草机为中心视图模式
   private boolean centerOnMowerMode = false;  // 是否处于以割草机为中心的模式
   // 当前选中的导航按钮
   private JButton currentNavButton;
   // 对话框引用
   private LegendDialog legendDialog;
   // 使用完全限定名以避免与同包的 RemoteControlDialog 冲突,确保使用 yaokong 包下的实现
   private yaokong.RemoteControlDialog remoteDialog;
   private AreaSelectionDialog areaDialog;
   private BaseStationDialog baseStationDialog;
   private Sets settingsDialog;
   private BaseStation baseStation;
   // 地图渲染器
   private MapRenderer mapRenderer;
   private final Consumer<String> serialLineListener = line -> {
      SwingUtilities.invokeLater(() -> {
         updateDataPacketCountLabel();
         // 如果收到$GNGGA数据,立即更新拖尾
         if (line != null && line.trim().startsWith("$GNGGA")) {
            if (mapRenderer != null) {
               mapRenderer.forceUpdateIdleMowerTrail();
            }
         }
      });
   };
   private static final int FLOAT_ICON_SIZE = 32;
   private JButton endDrawingButton;
   private JButton drawingPauseButton;
   private JPanel floatingButtonPanel;
   private JPanel floatingButtonColumn;
   private Runnable endDrawingCallback;
   private JButton pathPreviewReturnButton;
   private boolean pathPreviewActive;
   private Runnable pathPreviewReturnAction;
   private String previewRestoreLandNumber;
   private String previewRestoreLandName;
   private boolean drawingPaused;
   private ImageIcon pauseIcon;
   private ImageIcon pauseActiveIcon;
   private ImageIcon endIcon;
   private ImageIcon bluetoothIcon;
   private ImageIcon bluetoothLinkedIcon;
   private JPanel circleGuidancePanel;
   private JLabel circleGuidanceLabel;
   private JButton circleGuidancePrimaryButton;
   private JButton circleGuidanceSecondaryButton;
   private int circleGuidanceStep;
   private JDialog circleGuidanceDialog;
   private boolean circleDialogMode;
   private ComponentAdapter circleDialogOwnerAdapter;
   private static final double METERS_PER_DEGREE_LAT = 111320.0d;
   private static final double HANDHELD_DUPLICATE_THRESHOLD_METERS = 0.01d;
   private final List<double[]> circleCapturedPoints = new ArrayList<>();
   private double[] circleBaseLatLon;
   private Timer circleDataMonitor;
   private Coordinate lastCapturedCoordinate;
   private boolean handheldCaptureActive;
   private int handheldCapturedPoints;
   private final List<Point2D.Double> handheldTemporaryPoints = new ArrayList<>();
   private final List<Point2D.Double> mowerTemporaryPoints = new ArrayList<>();
   private enum BoundaryCaptureMode { NONE, HANDHELD, MOWER }
   private BoundaryCaptureMode activeBoundaryMode = BoundaryCaptureMode.NONE;
   private boolean mowerBoundaryCaptureActive;
   private Timer mowerBoundaryMonitor;
   private Coordinate lastMowerCoordinate;
   private double[] mowerBaseLatLon;
   private boolean startButtonShowingPause = true;
   private boolean stopButtonActive = false;
   private boolean bluetoothConnected = false;
   private Timer mowerSpeedRefreshTimer;
   private boolean drawingControlModeActive;
   private boolean storedStartButtonShowingPause;
   private boolean storedStopButtonActive;
   private String storedStatusBeforeDrawing;
   private boolean handheldCaptureInlineUiActive;
   private Timer handheldCaptureStatusTimer;
   private String handheldCaptureStoredStatusText;
   private Color handheldStartButtonOriginalBackground;
   private Color handheldStartButtonOriginalForeground;
   private Color handheldStopButtonOriginalBackground;
   private Color handheldStopButtonOriginalForeground;
   public Shouye() {
      instance = this;
      baseStation = new BaseStation();
      baseStation.load();
      dellmessage.registerLineListener(serialLineListener);
      initializeUI();
      setupEventHandlers();
      scheduleIdentifierCheck();
   }
   public static Shouye getInstance() {
      return instance;
   }
   private void initializeUI() {
      setLayout(new BorderLayout());
      setBackground(BACKGROUND_COLOR);
      // 创建各个面板
      createHeaderPanel();
      createMainContentPanel();
      createControlPanel();
      createNavigationPanel();
      // 添加到主面板
      add(headerPanel, BorderLayout.NORTH);
      add(mainContentPanel, BorderLayout.CENTER);
      add(controlPanel, BorderLayout.SOUTH);
      // 初始化地图渲染器
      mapRenderer = new MapRenderer(visualizationPanel);
      applyIdleTrailDurationFromSettings();
      // 初始化对话框引用为null,延迟创建
      legendDialog = null;
      remoteDialog = null;
      areaDialog = null;
      baseStationDialog = null;
      settingsDialog = null;
      // 设置默认状态
      setNavigationActive(homeNavBtn);
      initializeDefaultAreaSelection();
      refreshMapForSelectedArea();
      // 启动边界警告检查定时器
      startBoundaryWarningTimer();
   }
   private void scheduleIdentifierCheck() {
      HierarchyListener listener = new HierarchyListener() {
         @Override
         public void hierarchyChanged(HierarchyEvent e) {
            if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 && Shouye.this.isShowing()) {
               Shouye.this.removeHierarchyListener(this);
               SwingUtilities.invokeLater(() -> {
                  Shouye.this.checkIdentifiersAndPromptIfNeeded();
                  Shouye.this.showInitialMowerSelfCheckDialogIfNeeded();
                  // 设置窗口关闭监听器,在关闭时保存缩放比例
                  setupWindowCloseListener();
               });
            }
         }
      };
      addHierarchyListener(listener);
   }
   /**
    * 设置窗口关闭监听器,在窗口关闭时保存当前缩放比例
    */
   private void setupWindowCloseListener() {
      Window window = SwingUtilities.getWindowAncestor(this);
      if (window != null && window instanceof JFrame) {
         JFrame frame = (JFrame) window;
         frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
               // 保存当前缩放比例
               saveCurrentScale();
               // 停止边界警告定时器
               if (boundaryWarningTimer != null && boundaryWarningTimer.isRunning()) {
                  boundaryWarningTimer.stop();
               }
               // 停止闪烁定时器
               if (warningBlinkTimer != null && warningBlinkTimer.isRunning()) {
                  warningBlinkTimer.stop();
               }
            }
         });
      }
   }
   /**
    * 保存当前地图缩放比例和视图中心坐标到配置文件
    */
   public void saveCurrentScale() {
      if (mapRenderer != null) {
         double currentScale = mapRenderer.getScale();
         double translateX = mapRenderer.getTranslateX();
         double translateY = mapRenderer.getTranslateY();
         Setsys setsys = new Setsys();
         // 保留2位小数
         setsys.updateProperty("mapScale", String.format("%.2f", currentScale));
         setsys.updateProperty("viewCenterX", String.format("%.2f", translateX));
         setsys.updateProperty("viewCenterY", String.format("%.2f", translateY));
      }
   }
   /**
    * 启动边界警告检查定时器
    */
   private void startBoundaryWarningTimer() {
      // 边界检查定时器:每500ms检查一次割草机是否在边界内
      boundaryWarningTimer = new Timer(500, e -> {
         checkMowerBoundaryStatus();
         // 同时更新蓝牙图标状态
         updateBluetoothButtonIcon();
      });
      boundaryWarningTimer.setInitialDelay(0);
      boundaryWarningTimer.start();
      // 闪烁定时器:每1秒切换一次警告图标显示状态
      warningBlinkTimer = new Timer(1000, e -> {
         if (isMowerOutsideBoundary) {
            warningIconVisible = !warningIconVisible;
            if (visualizationPanel != null) {
               visualizationPanel.repaint();
            }
         }
      });
      warningBlinkTimer.setInitialDelay(0);
      warningBlinkTimer.start();
   }
   /**
    * 切换以割草机为中心的模式
    */
   private void toggleCenterOnMowerMode() {
      centerOnMowerMode = !centerOnMowerMode;
      if (centerOnMowerMode) {
         // 开启模式:立即将视图中心移动到割草机位置
         updateViewToCenterOnMower();
      }
      // 关闭模式时不需要做任何操作,用户已经可以自由移动地图
      // 更新图标显示(重绘以切换图标)
      if (visualizationPanel != null) {
         visualizationPanel.repaint();
      }
   }
   /**
    * 更新视图中心到割草机位置
    */
   private void updateViewToCenterOnMower() {
      if (mapRenderer == null) {
         return;
      }
      Gecaoji mower = mapRenderer.getMower();
      if (mower != null && mower.hasValidPosition()) {
         Point2D.Double mowerPosition = mower.getPosition();
         if (mowerPosition != null) {
            // 获取当前缩放比例
            double currentScale = mapRenderer.getScale();
            // 设置视图变换,使割草机位置对应到屏幕中心
            // translateX = -mowerX, translateY = -mowerY 可以让割草机在屏幕中心
            mapRenderer.setViewTransform(currentScale, -mowerPosition.x, -mowerPosition.y);
         }
      }
   }
   /**
    * 检查割草机边界状态
    */
   private void checkMowerBoundaryStatus() {
      // 如果处于以割草机为中心的模式,实时更新视图中心
      if (centerOnMowerMode) {
         updateViewToCenterOnMower();
      }
      // 检查是否在作业中
      if (statusLabel == null || !"作业中".equals(statusLabel.getText())) {
         // 不在作业中,重置状态
         if (isMowerOutsideBoundary) {
            isMowerOutsideBoundary = false;
            warningIconVisible = true;
            if (visualizationPanel != null) {
               visualizationPanel.repaint();
            }
         }
         return;
      }
      // 在作业中,检查是否在边界内
      if (mapRenderer == null) {
         return;
      }
      // 获取当前边界
      List<Point2D.Double> boundary = mapRenderer.getCurrentBoundary();
      if (boundary == null || boundary.size() < 3) {
         // 没有边界,重置状态
         if (isMowerOutsideBoundary) {
            isMowerOutsideBoundary = false;
            warningIconVisible = true;
            if (visualizationPanel != null) {
               visualizationPanel.repaint();
            }
         }
         return;
      }
      // 获取割草机位置
      Gecaoji mower = mapRenderer.getMower();
      if (mower == null || !mower.hasValidPosition()) {
         // 无法获取位置,重置状态
         if (isMowerOutsideBoundary) {
            isMowerOutsideBoundary = false;
            warningIconVisible = true;
            if (visualizationPanel != null) {
               visualizationPanel.repaint();
            }
         }
         return;
      }
      Point2D.Double mowerPosition = mower.getPosition();
      if (mowerPosition == null) {
         return;
      }
      // 使用 MowerBoundaryChecker 检查是否在边界内
      boolean isInside = MowerBoundaryChecker.isInsideBoundaryPoints(
         boundary,
         mowerPosition.x,
         mowerPosition.y
      );
      // 更新状态
      boolean wasOutside = isMowerOutsideBoundary;
      isMowerOutsideBoundary = !isInside;
      // 如果状态改变,立即重绘
      if (wasOutside != isMowerOutsideBoundary) {
         warningIconVisible = true;
         if (visualizationPanel != null) {
            visualizationPanel.repaint();
         }
      }
   }
   private void showInitialMowerSelfCheckDialogIfNeeded() {
      // 已移除进入主页时的自检提示(按用户要求删除)
      // 以前这里会调用 zijian.showInitialPromptIfNeeded(...) 展示自检对话框,现已禁用。
   }
   private void applyIdleTrailDurationFromSettings() {
      if (mapRenderer == null) {
         return;
      }
      int durationSeconds = MapRenderer.DEFAULT_IDLE_TRAIL_DURATION_SECONDS;
      String configuredValue = Setsys.getPropertyValue("idleTrailDurationSeconds");
      if (configuredValue != null) {
         String trimmed = configuredValue.trim();
         if (!trimmed.isEmpty()) {
            try {
               int parsed = Integer.parseInt(trimmed);
               if (parsed >= 5 && parsed <= 600) {
                  durationSeconds = parsed;
               }
            } catch (NumberFormatException ignored) {
               durationSeconds = MapRenderer.DEFAULT_IDLE_TRAIL_DURATION_SECONDS;
            }
         }
      }
      mapRenderer.setIdleTrailDurationSeconds(durationSeconds);
      // 应用边界距离显示设置和测量模式设置
      Setsys setsys = new Setsys();
      setsys.initializeFromProperties();
      mapRenderer.setBoundaryLengthVisible(setsys.isBoundaryLengthVisible());
      // 初始化测量模式
      boolean measurementEnabled = setsys.isMeasurementModeEnabled();
      mapRenderer.setMeasurementMode(measurementEnabled);
      if (measurementEnabled) {
         celiangmoshi.start();
      } else {
         celiangmoshi.stop();
      }
   }
   private void createHeaderPanel() {
      headerPanel = new JPanel(new BorderLayout());
      headerPanel.setBackground(PANEL_BACKGROUND);
      headerPanel.setBorder(BorderFactory.createEmptyBorder(15, 20, 15, 20));
      headerPanel.setPreferredSize(new Dimension(0, 80));
   // 左侧信息区域(垂直排列:地块名称在上,状态行在下)
   JPanel leftInfoPanel = new JPanel();
   leftInfoPanel.setBackground(PANEL_BACKGROUND);
   leftInfoPanel.setLayout(new BoxLayout(leftInfoPanel, BoxLayout.Y_AXIS));
   // 保证子组件左对齐
   leftInfoPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
   areaNameLabel = new JLabel("未选择地块");
      areaNameLabel.setFont(new Font("微软雅黑", Font.BOLD, 18));
      areaNameLabel.setForeground(Color.BLACK);
      areaNameLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      areaNameLabel.addMouseListener(new MouseAdapter() {
         @Override
         public void mouseClicked(MouseEvent e) {
            Dikuaiguanli.showDikuaiManagement(Shouye.this, null);
         }
         @Override
         public void mouseEntered(MouseEvent e) {
            areaNameLabel.setForeground(THEME_COLOR);
         }
         @Override
         public void mouseExited(MouseEvent e) {
            areaNameLabel.setForeground(Color.BLACK);
         }
      });
      statusLabel = new JLabel("待机");
      statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
      statusLabel.setForeground(Color.GRAY);
      statusLabel.addPropertyChangeListener("text", evt -> {
         Object newValue = evt.getNewValue();
         applyStatusLabelColor(newValue instanceof String ? (String) newValue : null);
      });
      applyStatusLabelColor(statusLabel.getText());
      // 添加速度显示标签
      speedLabel = new JLabel("");
      speedLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
      speedLabel.setForeground(Color.GRAY);
      speedLabel.setVisible(false);  // 默认隐藏
   // 将状态与速度放在同一行,显示在地块名称下面一行
   JPanel statusRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
   statusRow.setOpaque(false);
   statusRow.add(statusLabel);
   statusRow.add(speedLabel);
   // 左对齐标签与状态行,确保它们在 BoxLayout 中靠左显示
   areaNameLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
   statusRow.setAlignmentX(Component.LEFT_ALIGNMENT);
   leftInfoPanel.add(areaNameLabel);
   leftInfoPanel.add(statusRow);
      // 右侧操作区域
      JPanel rightActionPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
      rightActionPanel.setBackground(PANEL_BACKGROUND);
      bluetoothBtn = createBluetoothButton();
      // 修改设置按钮:使用图片替代Unicode图标
      JButton settingsBtn = new JButton();
      try {
         ImageIcon settingsIcon = new ImageIcon("image/sets.png");
         // 调整图片大小以适应按钮
         Image scaledImage = settingsIcon.getImage().getScaledInstance(30, 30, Image.SCALE_SMOOTH);
         settingsBtn.setIcon(new ImageIcon(scaledImage));
      } catch (Exception e) {
         // 如果图片加载失败,使用默认文本
         settingsBtn.setText("设置");
         System.err.println("无法加载设置图标: " + e.getMessage());
      }
      settingsBtn.setPreferredSize(new Dimension(40, 40));
      settingsBtn.setBackground(PANEL_BACKGROUND);
      settingsBtn.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
      settingsBtn.setFocusPainted(false);
      // 添加悬停效果
      settingsBtn.addMouseListener(new MouseAdapter() {
         public void mouseEntered(MouseEvent e) {
            settingsBtn.setBackground(new Color(240, 240, 240));
         }
         public void mouseExited(MouseEvent e) {
            settingsBtn.setBackground(PANEL_BACKGROUND);
         }
      });
      // 添加设置按钮事件
      settingsBtn.addActionListener(e -> showSettingsDialog());
      rightActionPanel.add(bluetoothBtn);
      rightActionPanel.add(settingsBtn);
      headerPanel.add(leftInfoPanel, BorderLayout.WEST);
      headerPanel.add(rightActionPanel, BorderLayout.EAST);
   }
   private void createMainContentPanel() {
      mainContentPanel = new JPanel(new BorderLayout());
      mainContentPanel.setBackground(BACKGROUND_COLOR);
      // 可视化区域 - 使用MapRenderer进行绘制
      visualizationPanel = new JPanel() {
         private ImageIcon gecaojiIcon1 = null;  // 默认图标
         private ImageIcon gecaojiIcon2 = null;  // 以割草机为中心模式图标
         private static final int GECAOJI_ICON_X = 37;
         private static final int GECAOJI_ICON_Y = 10;
         private static final int GECAOJI_ICON_SIZE = 20;
         {
            // 加载割草机图标,大小20x20像素
            gecaojiIcon1 = loadScaledIcon("image/gecaojishijiao1.png", GECAOJI_ICON_SIZE, GECAOJI_ICON_SIZE);
            gecaojiIcon2 = loadScaledIcon("image/gecaojishijiao2.png", GECAOJI_ICON_SIZE, GECAOJI_ICON_SIZE);
         }
         /**
          * 检查鼠标位置是否在割草机图标区域内
          */
         private boolean isMouseOnGecaojiIcon(Point mousePoint) {
            return mousePoint.x >= GECAOJI_ICON_X &&
                   mousePoint.x <= GECAOJI_ICON_X + GECAOJI_ICON_SIZE &&
                   mousePoint.y >= GECAOJI_ICON_Y &&
                   mousePoint.y <= GECAOJI_ICON_Y + GECAOJI_ICON_SIZE;
         }
         @Override
         public String getToolTipText(MouseEvent event) {
            // 如果鼠标在割草机图标区域内,显示提示文字
            if (isMouseOnGecaojiIcon(event.getPoint())) {
               // 根据当前模式显示不同的提示文字
               return centerOnMowerMode ? "取消以割草机为中心" : "以割草机为中心";
            }
            // 不在图标上时返回null,不显示工具提示框
            return null;
         }
         @Override
         protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            // 委托给MapRenderer进行绘制
            if (mapRenderer != null) {
               mapRenderer.renderMap(g);
            }
            // 检查是否需要显示警告图标
            if (isMowerOutsideBoundary && warningIconVisible) {
               // 绘制红色三角形警告图标(带叹号)
               drawWarningIcon(g, GECAOJI_ICON_X, GECAOJI_ICON_Y, GECAOJI_ICON_SIZE);
            } else {
               // 根据模式选择不同的图标
               ImageIcon iconToDraw = centerOnMowerMode ? gecaojiIcon2 : gecaojiIcon1;
               if (iconToDraw != null) {
                  // 绘制割草机图标
                  // 水平方向与速度指示器对齐(x=37)
                  // 垂直方向与卫星状态图标对齐(y=10,速度指示器面板顶部边距10像素,使图标中心对齐)
                  g.drawImage(iconToDraw.getImage(), GECAOJI_ICON_X, GECAOJI_ICON_Y, null);
               }
            }
         }
         /**
          * 绘制红色三角形警告图标(带叹号)
          */
         private void drawWarningIcon(Graphics g, int x, int y, int size) {
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            // 绘制红色三角形
            int[] xPoints = {x + size / 2, x, x + size};
            int[] yPoints = {y, y + size, y + size};
            g2d.setColor(Color.RED);
            g2d.fillPolygon(xPoints, yPoints, 3);
            // 绘制白色边框
            g2d.setColor(Color.WHITE);
            g2d.setStroke(new BasicStroke(1.5f));
            g2d.drawPolygon(xPoints, yPoints, 3);
            // 绘制白色叹号
            g2d.setColor(Color.WHITE);
            g2d.setFont(new Font("Arial", Font.BOLD, size * 3 / 4));
            FontMetrics fm = g2d.getFontMetrics();
            String exclamation = "!";
            int textWidth = fm.stringWidth(exclamation);
            int textHeight = fm.getAscent();
            g2d.drawString(exclamation, x + (size - textWidth) / 2, y + (size + textHeight) / 2 - 2);
            g2d.dispose();
         }
      };
      visualizationPanel.setLayout(new BorderLayout());
      // 添加鼠标点击监听器,检测是否点击了割草机图标
      visualizationPanel.addMouseListener(new MouseAdapter() {
         @Override
         public void mouseClicked(MouseEvent e) {
            if (SwingUtilities.isLeftMouseButton(e)) {
               Point clickPoint = e.getPoint();
               // 检查是否点击了割草机图标区域(37, 10, 20, 20)
               if (clickPoint.x >= 37 && clickPoint.x <= 57 &&
                   clickPoint.y >= 10 && clickPoint.y <= 30) {
                  // 切换以割草机为中心的模式
                  toggleCenterOnMowerMode();
               }
            }
         }
      });
      JPanel speedIndicatorPanel = createSpeedIndicatorPanel();
      visualizationPanel.add(speedIndicatorPanel, BorderLayout.NORTH);
      // 创建功能按钮面板
      JPanel functionButtonsPanel = new JPanel();
      functionButtonsPanel.setLayout(new BoxLayout(functionButtonsPanel, BoxLayout.Y_AXIS));
      functionButtonsPanel.setOpaque(false);
      functionButtonsPanel.setBorder(BorderFactory.createEmptyBorder(20, 5, 0, 0)); // 左边距改为5像素
      legendBtn = createFunctionButton("图例", "📖");
      baseStationBtn = createFunctionButton("基准站", "📡"); // 调整到第二位
      areaSelectBtn = createFunctionButton("地块", "🌿");    // 调整到第三位
      remoteBtn = createFunctionButton("遥控", "🎮");        // 调整到最后
      functionButtonsPanel.add(legendBtn);
      functionButtonsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
      functionButtonsPanel.add(baseStationBtn);
      functionButtonsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
      functionButtonsPanel.add(areaSelectBtn);
      functionButtonsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
      functionButtonsPanel.add(remoteBtn);
      visualizationPanel.add(functionButtonsPanel, BorderLayout.WEST);
      JPanel zoomControlPanel = createZoomControlPanel();
      visualizationPanel.add(zoomControlPanel, BorderLayout.EAST);
      mainContentPanel.add(visualizationPanel, BorderLayout.CENTER);
      startMowerSpeedUpdates();
   }
   private JPanel createZoomControlPanel() {
      JPanel container = new JPanel(new BorderLayout());
      container.setOpaque(false);
      container.setBorder(BorderFactory.createEmptyBorder(0, 0, 20, 20));
      JPanel alignmentPanel = new JPanel();
      alignmentPanel.setOpaque(false);
      alignmentPanel.setLayout(new BoxLayout(alignmentPanel, BoxLayout.Y_AXIS));
      alignmentPanel.add(Box.createVerticalGlue());
      JPanel buttonStack = new JPanel();
      buttonStack.setOpaque(false);
      buttonStack.setLayout(new BoxLayout(buttonStack, BoxLayout.Y_AXIS));
      JButton zoomInButton = createZoomButton("+");
      JButton zoomOutButton = createZoomButton("-");
      AtomicBoolean skipZoomInClick = new AtomicBoolean(false);
      AtomicBoolean skipZoomOutClick = new AtomicBoolean(false);
      Timer zoomInHoldTimer = new Timer(120, event -> {
         if (mapRenderer == null || !zoomInButton.getModel().isPressed()) {
            ((Timer) event.getSource()).stop();
            return;
         }
         if (!mapRenderer.canZoomIn()) {
            ((Timer) event.getSource()).stop();
            return;
         }
         skipZoomInClick.set(true);
         mapRenderer.zoomInFromCenter();
         if (!mapRenderer.canZoomIn()) {
            ((Timer) event.getSource()).stop();
         }
      });
      zoomInHoldTimer.setInitialDelay(180);
      zoomInHoldTimer.setDelay(120);
      Timer zoomOutHoldTimer = new Timer(120, event -> {
         if (mapRenderer == null || !zoomOutButton.getModel().isPressed()) {
            ((Timer) event.getSource()).stop();
            return;
         }
         if (!mapRenderer.canZoomOut()) {
            ((Timer) event.getSource()).stop();
            return;
         }
         skipZoomOutClick.set(true);
         mapRenderer.zoomOutFromCenter();
         if (!mapRenderer.canZoomOut()) {
            ((Timer) event.getSource()).stop();
         }
      });
      zoomOutHoldTimer.setInitialDelay(180);
      zoomOutHoldTimer.setDelay(120);
      zoomInButton.addMouseListener(new MouseAdapter() {
         @Override
         public void mousePressed(MouseEvent e) {
            if (!SwingUtilities.isLeftMouseButton(e) || mapRenderer == null || !zoomInButton.isEnabled()) {
               return;
            }
            if (mapRenderer.canZoomIn()) {
               skipZoomInClick.set(true);
               mapRenderer.zoomInFromCenter();
               if (mapRenderer.canZoomIn()) {
                  zoomInHoldTimer.restart();
               } else {
                  zoomInHoldTimer.stop();
               }
            } else {
               skipZoomInClick.set(false);
               zoomInHoldTimer.stop();
            }
         }
         @Override
         public void mouseReleased(MouseEvent e) {
            zoomInHoldTimer.stop();
            if (!zoomInButton.contains(e.getPoint())) {
               skipZoomInClick.set(false);
            }
         }
         @Override
         public void mouseExited(MouseEvent e) {
            if (!zoomInButton.getModel().isPressed()) {
               zoomInHoldTimer.stop();
            }
         }
      });
      zoomOutButton.addMouseListener(new MouseAdapter() {
         @Override
         public void mousePressed(MouseEvent e) {
            if (!SwingUtilities.isLeftMouseButton(e) || mapRenderer == null || !zoomOutButton.isEnabled()) {
               return;
            }
            if (mapRenderer.canZoomOut()) {
               skipZoomOutClick.set(true);
               mapRenderer.zoomOutFromCenter();
               if (mapRenderer.canZoomOut()) {
                  zoomOutHoldTimer.restart();
               } else {
                  zoomOutHoldTimer.stop();
               }
            } else {
               skipZoomOutClick.set(false);
               zoomOutHoldTimer.stop();
            }
         }
         @Override
         public void mouseReleased(MouseEvent e) {
            zoomOutHoldTimer.stop();
            if (!zoomOutButton.contains(e.getPoint())) {
               skipZoomOutClick.set(false);
            }
         }
         @Override
         public void mouseExited(MouseEvent e) {
            if (!zoomOutButton.getModel().isPressed()) {
               zoomOutHoldTimer.stop();
            }
         }
      });
      zoomInButton.addActionListener(e -> {
         if (skipZoomInClick.getAndSet(false)) {
            return;
         }
         if (mapRenderer != null) {
            mapRenderer.zoomInFromCenter();
         }
      });
      zoomOutButton.addActionListener(e -> {
         if (skipZoomOutClick.getAndSet(false)) {
            return;
         }
         if (mapRenderer != null) {
            mapRenderer.zoomOutFromCenter();
         }
      });
      buttonStack.add(zoomInButton);
      buttonStack.add(Box.createRigidArea(new Dimension(0, 8)));
      buttonStack.add(zoomOutButton);
      buttonStack.setAlignmentX(Component.CENTER_ALIGNMENT);
      alignmentPanel.add(buttonStack);
      container.add(alignmentPanel, BorderLayout.CENTER);
      return container;
   }
   private JButton createZoomButton(String symbol) {
      JButton button = new JButton(symbol);
      Dimension size = new Dimension(44, 44);
      button.setPreferredSize(size);
      button.setMinimumSize(size);
      button.setMaximumSize(size);
      button.setFont(new Font("微软雅黑", Font.BOLD, 22));
      button.setMargin(new Insets(0, 0, 0, 0));
      button.setFocusPainted(false);
      button.setBackground(Color.WHITE);
      button.setForeground(Color.DARK_GRAY);
      button.setBorder(BorderFactory.createLineBorder(new Color(210, 210, 210), 1, true));
      button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      button.setOpaque(true);
      button.addMouseListener(new MouseAdapter() {
         @Override
         public void mouseEntered(MouseEvent e) {
            button.setBackground(new Color(245, 245, 245));
         }
         @Override
         public void mouseExited(MouseEvent e) {
            button.setBackground(Color.WHITE);
         }
      });
      return button;
   }
   private void createControlPanel() {
      controlPanel = new JPanel(new BorderLayout());
      controlPanel.setBackground(PANEL_BACKGROUND);
      controlPanel.setBorder(BorderFactory.createEmptyBorder(15, 20, 15, 20));
      controlPanel.setPreferredSize(new Dimension(0, 100));
      JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 20, 0));
      buttonPanel.setBackground(PANEL_BACKGROUND);
      startBtn = createControlButton("暂停", THEME_COLOR);
      updateStartButtonAppearance();
      stopBtn = createControlButton("结束", Color.ORANGE);
      updateStopButtonIcon();
      buttonPanel.add(startBtn);
      buttonPanel.add(stopBtn);
      controlPanel.add(buttonPanel, BorderLayout.CENTER);
   }
   private void createNavigationPanel() {
      navigationPanel = new JPanel(new GridLayout(1, 3));
      navigationPanel.setBackground(PANEL_BACKGROUND);
      navigationPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
      navigationPanel.setPreferredSize(new Dimension(0, 70));
      homeNavBtn = createNavButton("首页", "🏠");
      areasNavBtn = createNavButton("地块", "🌿");
      settingsNavBtn = createNavButton("设置", "⚙️");
      navigationPanel.add(homeNavBtn);
      navigationPanel.add(areasNavBtn);
      navigationPanel.add(settingsNavBtn);
      // 添加到主界面底部
      add(navigationPanel, BorderLayout.SOUTH);
   }
   private JButton createIconButton(String icon, int size) {
      JButton button = new JButton(icon);
      button.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 20));
      button.setPreferredSize(new Dimension(size, size));
      button.setBackground(PANEL_BACKGROUND);
      button.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
      button.setFocusPainted(false);
      // 悬停效果
      button.addMouseListener(new MouseAdapter() {
         public void mouseEntered(MouseEvent e) {
            button.setBackground(new Color(240, 240, 240));
         }
         public void mouseExited(MouseEvent e) {
            button.setBackground(PANEL_BACKGROUND);
         }
      });
      return button;
   }
   private JButton createBluetoothButton() {
      JButton button = new JButton();
      button.setPreferredSize(new Dimension(40, 40));
      button.setBackground(PANEL_BACKGROUND);
      button.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
      button.setFocusPainted(false);
      button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      button.addMouseListener(new MouseAdapter() {
         @Override
         public void mouseEntered(MouseEvent e) {
            button.setBackground(new Color(240, 240, 240));
         }
         @Override
         public void mouseExited(MouseEvent e) {
            button.setBackground(PANEL_BACKGROUND);
         }
      });
      ensureBluetoothIconsLoaded();
      // 根据串口连接状态显示图标
      SerialPortService service = sendmessage.getActiveService();
      boolean serialConnected = (service != null && service.isOpen());
      ImageIcon initialIcon = serialConnected ? bluetoothLinkedIcon : bluetoothIcon;
      if (initialIcon != null) {
         button.setIcon(initialIcon);
      } else {
         button.setText(serialConnected ? "已连" : "蓝牙");
      }
      return button;
   }
   private JButton createFunctionButton(String text, String icon) {
      JButton button = new JButton("<html><center>" + icon + "<br>" + text + "</center></html>");
      button.setFont(new Font("微软雅黑", Font.PLAIN, 12));
      button.setPreferredSize(new Dimension(80, 70));
      button.setBackground(new Color(0, 0, 0, 0)); // 完全透明背景
      button.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); // 只保留内边距
      button.setFocusPainted(false);
      button.setOpaque(false); // 设置为不透明,确保背景透明
      // 去掉悬停效果
      button.addMouseListener(new MouseAdapter() {
         public void mouseEntered(MouseEvent e) {
            // 悬停时不改变任何样式
         }
         public void mouseExited(MouseEvent e) {
            // 悬停时不改变任何样式
         }
      });
      return button;
   }
   private JButton createControlButton(String text, Color color) {
      JButton button = new JButton(text);
      button.setFont(new Font("微软雅黑", Font.BOLD, 16));
      button.setBackground(color);
      button.setForeground(Color.WHITE);
      button.setBorder(BorderFactory.createEmptyBorder(15, 0, 15, 0));
      button.setFocusPainted(false);
      // 悬停效果
      button.addMouseListener(new MouseAdapter() {
         public void mouseEntered(MouseEvent e) {
            if (button.isEnabled()) {
               if (color == THEME_COLOR) {
                  button.setBackground(THEME_HOVER_COLOR);
               } else {
                  button.setBackground(new Color(255, 165, 0));
               }
            }
         }
         public void mouseExited(MouseEvent e) {
            if (button.isEnabled()) {
               button.setBackground(color);
            }
         }
      });
      return button;
   }
   private void applyButtonIcon(JButton button, String imagePath) {
      try {
         ImageIcon icon = new ImageIcon(imagePath);
         Image scaledImage = icon.getImage().getScaledInstance(28, 28, Image.SCALE_SMOOTH);
         button.setIcon(new ImageIcon(scaledImage));
         button.setHorizontalAlignment(SwingConstants.CENTER);
         button.setIconTextGap(10);
         button.setHorizontalTextPosition(SwingConstants.RIGHT);
         button.setVerticalTextPosition(SwingConstants.CENTER);
      } catch (Exception e) {
         System.err.println("无法加载按钮图标: " + imagePath + " -> " + e.getMessage());
      }
   }
   private JButton createNavButton(String text, String icon) {
      JButton button = new JButton("<html><center>" + icon + "<br>" + text + "</center></html>");
      button.setFont(new Font("微软雅黑", Font.PLAIN, 12));
      button.setBackground(PANEL_BACKGROUND);
      button.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
      button.setFocusPainted(false);
      // 悬停效果
      button.addMouseListener(new MouseAdapter() {
         public void mouseEntered(MouseEvent e) {
            if (button != currentNavButton) {
               button.setBackground(new Color(240, 240, 240));
            }
         }
         public void mouseExited(MouseEvent e) {
            if (button != currentNavButton) {
               button.setBackground(PANEL_BACKGROUND);
            }
         }
      });
      return button;
   }
   private void setNavigationActive(JButton navButton) {
      // 重置所有导航按钮样式
      for (Component comp : navigationPanel.getComponents()) {
         if (comp instanceof JButton) {
            JButton btn = (JButton) comp;
            btn.setBackground(PANEL_BACKGROUND);
            btn.setForeground(Color.BLACK);
         }
      }
      // 设置当前选中按钮样式
      navButton.setBackground(THEME_COLOR);
      navButton.setForeground(Color.WHITE);
      currentNavButton = navButton;
   }
   private void setupEventHandlers() {
      // 导航按钮事件
      homeNavBtn.addActionListener(e -> setNavigationActive(homeNavBtn));
      areasNavBtn.addActionListener(e -> setNavigationActive(areasNavBtn));
      settingsNavBtn.addActionListener(e -> setNavigationActive(settingsNavBtn));
      // 功能按钮事件
      legendBtn.addActionListener(e -> showLegendDialog());
      if (bluetoothBtn != null) {
         bluetoothBtn.addActionListener(e -> toggleBluetoothConnection());
      }
      remoteBtn.addActionListener(e -> showRemoteControlDialog());
      areaSelectBtn.addActionListener(e -> {
         // 点击“地块”直接打开地块管理对话框(若需要可传入特定地块编号)
         Dikuaiguanli.showDikuaiManagement(this, null);
      });
      baseStationBtn.addActionListener(e -> showBaseStationDialog());
      // 控制按钮事件
      startBtn.addActionListener(e -> toggleStartPause());
      stopBtn.addActionListener(e -> handleStopAction());
   }
   private void showSettingsDialog() {
      if (settingsDialog == null) {
         Window parentWindow = SwingUtilities.getWindowAncestor(this);
         if (parentWindow instanceof JFrame) {
            settingsDialog = new Sets((JFrame) parentWindow, THEME_COLOR);
         } else if (parentWindow instanceof JDialog) {
            settingsDialog = new Sets((JDialog) parentWindow, THEME_COLOR);
         } else {
            // Fallback to a frameless dialog when no parent is available
            settingsDialog = new Sets((JFrame) null, THEME_COLOR);
         }
      }
      settingsDialog.setVisible(true);
   }
   private void showLegendDialog() {
      if (legendDialog == null) {
         Window parentWindow = SwingUtilities.getWindowAncestor(this);
         if (parentWindow != null) {
            legendDialog = new LegendDialog(this, THEME_COLOR);
         } else {
            // 如果没有父窗口,创建无父窗口的对话框
            legendDialog = new LegendDialog((JFrame) null, THEME_COLOR);
         }
      }
      legendDialog.setVisible(true);
   }
   private void showRemoteControlDialog() {
      if (remoteDialog == null) {
         Window parentWindow = SwingUtilities.getWindowAncestor(this);
         if (parentWindow != null) {
            // 使用 yaokong 包中的 RemoteControlDialog 实现
            remoteDialog = new yaokong.RemoteControlDialog(this, THEME_COLOR, speedLabel);
         } else {
            remoteDialog = new yaokong.RemoteControlDialog((JFrame) null, THEME_COLOR, speedLabel);
         }
      }
      if (remoteDialog != null) {
         positionRemoteDialogBottomCenter(remoteDialog);
         zijian.markSelfCheckCompleted();
         remoteDialog.setVisible(true);
      }
   }
   private void positionRemoteDialogBottomCenter(RemoteControlDialog dialog) {
      if (dialog == null) {
         return;
      }
      // 将对话框底部与整个首页(Shouye 面板)的下边框对齐,
      // 而不是与 visualizationPanel 对齐,以满足设计要求。
      Dimension dialogSize = dialog.getSize();
      // 获取当前 Shouye 面板在屏幕上的位置和尺寸
      Point parentOnScreen = null;
      try {
         parentOnScreen = this.getLocationOnScreen();
      } catch (IllegalComponentStateException ex) {
         // 如果组件尚未显示或无法获取屏幕位置,回退到窗口层级位置获取
         Window owner = SwingUtilities.getWindowAncestor(this);
         if (owner != null) {
            parentOnScreen = owner.getLocationOnScreen();
         }
      }
      int x = 0, y = 0;
      if (parentOnScreen != null) {
         int parentWidth = this.getWidth();
         int parentHeight = this.getHeight();
         x = parentOnScreen.x + (parentWidth - dialogSize.width) / 2;
         y = parentOnScreen.y + parentHeight - dialogSize.height;
      } else {
         // 作为最后的回退,使用屏幕中心对齐底部
         Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
         x = (screen.width - dialogSize.width) / 2;
         y = screen.height - dialogSize.height;
      }
      dialog.setLocation(Math.max(x, 0), Math.max(y, 0));
   }
   private Rectangle computeVisualizationBoundsOnScreen() {
      if (visualizationPanel == null || !visualizationPanel.isShowing()) {
         return null;
      }
      Point location = visualizationPanel.getLocationOnScreen();
      Dimension size = visualizationPanel.getSize();
      return new Rectangle(location.x, location.y, size.width, size.height);
   }
   private void showAreaSelectionDialog() {
      if (areaDialog == null) {
         Window parentWindow = SwingUtilities.getWindowAncestor(this);
         if (parentWindow != null) {
            areaDialog = new AreaSelectionDialog(this, THEME_COLOR, areaNameLabel, statusLabel);
         } else {
            areaDialog = new AreaSelectionDialog((JFrame) null, THEME_COLOR, areaNameLabel, statusLabel);
         }
      }
      areaDialog.setVisible(true);
   }
   private void showBaseStationDialog() {
      if (baseStation == null) {
         baseStation = new BaseStation();
      }
      baseStation.load();
      Component dialogParent = this;
      if (!hasValidBaseStationId()) {
         boolean recorded = promptForBaseStationId(dialogParent);
         if (!recorded) {
            return;
         }
      }
      Device device = Device.getGecaoji();
      if (device == null) {
         device = new Device();
         device.initFromProperties();
         Device.setGecaoji(device);
      }
      if (baseStationDialog == null) {
         baseStationDialog = new BaseStationDialog(dialogParent, THEME_COLOR, device, baseStation);
      } else {
         baseStationDialog.refreshData();
      }
      baseStationDialog.setVisible(true);
   }
   private void checkIdentifiersAndPromptIfNeeded() {
      if (baseStation == null) {
         baseStation = new BaseStation();
      }
      baseStation.load();
      String currentMowerId = Setsys.getPropertyValue("mowerId");
      String currentBaseStationId = baseStation.getDeviceId();
      if (!isIdentifierMissing(currentMowerId) && !isIdentifierMissing(currentBaseStationId)) {
         return;
      }
      Window owner = SwingUtilities.getWindowAncestor(this);
      promptForMissingIdentifiers(owner, currentMowerId, currentBaseStationId);
   }
   private void promptForMissingIdentifiers(Window owner, String currentMowerId, String currentBaseStationId) {
      while (true) {
         JTextField mowerField = new JTextField(10);
         JTextField baseField = new JTextField(10);
         if (!isIdentifierMissing(currentMowerId)) {
            mowerField.setText(currentMowerId.trim());
         }
         if (!isIdentifierMissing(currentBaseStationId)) {
            baseField.setText(currentBaseStationId.trim());
         }
         JPanel panel = new JPanel();
         panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
         panel.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
         JLabel mowerLabel = new JLabel("割草机编号");
         JLabel baseLabel = new JLabel("差分基准站编号");
         mowerField.setMaximumSize(new Dimension(Integer.MAX_VALUE, mowerField.getPreferredSize().height));
         baseField.setMaximumSize(new Dimension(Integer.MAX_VALUE, baseField.getPreferredSize().height));
         panel.add(mowerLabel);
         panel.add(Box.createVerticalStrut(4));
         panel.add(mowerField);
         panel.add(Box.createVerticalStrut(10));
         panel.add(baseLabel);
         panel.add(Box.createVerticalStrut(4));
         panel.add(baseField);
         Object[] options = {"保存", "取消"};
         int result = JOptionPane.showOptionDialog(owner, panel, "完善设备信息",
               JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
         if (result != 0) {
            break;
         }
         String mowerInput = mowerField.getText().trim();
         String baseInput = baseField.getText().trim();
         if (mowerInput.isEmpty()) {
            JOptionPane.showMessageDialog(owner, "割草机编号不能为空。", "提示", JOptionPane.WARNING_MESSAGE);
            continue;
         }
         if (baseInput.isEmpty()) {
            JOptionPane.showMessageDialog(owner, "差分基准站编号不能为空。", "提示", JOptionPane.WARNING_MESSAGE);
            continue;
         }
         boolean mowerSaved = persistMowerIdentifier(mowerInput);
         boolean baseSaved = persistBaseStationIdentifier(baseInput);
         if (mowerSaved && baseSaved) {
            JOptionPane.showMessageDialog(owner, "编号已保存。", "成功", JOptionPane.INFORMATION_MESSAGE);
            break;
         }
         StringBuilder errorBuilder = new StringBuilder();
         if (!mowerSaved) {
            errorBuilder.append("割草机编号保存失败。");
         }
         if (!baseSaved) {
            if (errorBuilder.length() > 0) {
               errorBuilder.append('\n');
            }
            errorBuilder.append("差分基准站编号保存失败。");
         }
         JOptionPane.showMessageDialog(owner, errorBuilder.toString(), "保存失败", JOptionPane.ERROR_MESSAGE);
         currentMowerId = Setsys.getPropertyValue("mowerId");
         baseStation.load();
         currentBaseStationId = baseStation.getDeviceId();
      }
   }
   private boolean isIdentifierMissing(String value) {
      if (value == null) {
         return true;
      }
      String trimmed = value.trim();
      return trimmed.isEmpty() || "-1".equals(trimmed);
   }
   private boolean persistMowerIdentifier(String mowerId) {
      try {
         Setsys setsys = new Setsys();
         setsys.initializeFromProperties();
         boolean updated = setsys.updateProperty("mowerId", mowerId);
         if (updated) {
            Device.initializeActiveDevice(mowerId);
         }
         return updated;
      } catch (Exception ex) {
         ex.printStackTrace();
         return false;
      }
   }
   private boolean persistBaseStationIdentifier(String baseStationId) {
      if (baseStation == null) {
         baseStation = new BaseStation();
      }
      try {
         baseStation.updateByDeviceId(baseStationId,
               baseStation.getInstallationCoordinates(),
               baseStation.getIotSimCardNumber(),
               baseStation.getDeviceActivationTime(),
               baseStation.getDataUpdateTime());
         baseStation.load();
         return true;
      } catch (Exception ex) {
         ex.printStackTrace();
         return false;
      }
   }
   private boolean hasValidBaseStationId() {
      if (baseStation == null) {
         return false;
      }
      String deviceId = baseStation.getDeviceId();
      if (deviceId == null) {
         return false;
      }
      String trimmed = deviceId.trim();
      return !trimmed.isEmpty() && !"-1".equals(trimmed);
   }
   private boolean promptForBaseStationId(Component parentComponent) {
      if (baseStation == null) {
         baseStation = new BaseStation();
      }
      while (true) {
         String input = JOptionPane.showInputDialog(parentComponent,
               "请输入基准站编号", "录入基准站编号", JOptionPane.PLAIN_MESSAGE);
         if (input == null) {
            return false;
         }
         String trimmed = input.trim();
         if (trimmed.isEmpty()) {
            JOptionPane.showMessageDialog(parentComponent,
                  "基准站编号不能为空,请重新输入。", "提示", JOptionPane.WARNING_MESSAGE);
            continue;
         }
         try {
            baseStation.updateByDeviceId(trimmed,
                  baseStation.getInstallationCoordinates(),
                  baseStation.getIotSimCardNumber(),
                  baseStation.getDeviceActivationTime(),
                  baseStation.getDataUpdateTime());
            baseStation.load();
            JOptionPane.showMessageDialog(parentComponent,
                  "基准站编号已保存。", "操作成功", JOptionPane.INFORMATION_MESSAGE);
            return true;
         } catch (IllegalArgumentException ex) {
            JOptionPane.showMessageDialog(parentComponent,
                  ex.getMessage(), "输入错误", JOptionPane.ERROR_MESSAGE);
         }
      }
   }
   private void toggleStartPause() {
      if (handheldCaptureInlineUiActive) {
         handleHandheldConfirmAction();
         return;
      }
      if (drawingControlModeActive) {
         toggleDrawingPause();
         return;
      }
      if (startBtn == null) {
         return;
      }
      if (startButtonShowingPause) {
         // 点击开始按钮时不再弹出自检提示(按用户要求删除)
         // 旧逻辑:调用 zijian.ensureBeforeMowing(...) 并在未确认自检时阻止开始
         // 新逻辑:直接允许开始作业
      }
      startButtonShowingPause = !startButtonShowingPause;
      if (!startButtonShowingPause) {
         // 检查割草机是否在作业地块边界范围内
         if (!checkMowerInBoundary()) {
            startButtonShowingPause = true;
            statusLabel.setText("待机");
            updateStartButtonAppearance();
            return;
         }
         statusLabel.setText("作业中");
         if (stopButtonActive) {
            stopButtonActive = false;
            updateStopButtonIcon();
         }
      if (!beginMowingSession()) {
         startButtonShowingPause = true;
         statusLabel.setText("待机");
         updateStartButtonAppearance();
         return;
      }
      } else {
         statusLabel.setText("暂停中");
         pauseMowingSession();
      }
      updateStartButtonAppearance();
   }
   /**
    * 检查割草机是否在当前选中的作业地块边界范围内
    * @return 如果割草机在边界内返回true,否则返回false并显示提示
    */
   private boolean checkMowerInBoundary() {
      if (mapRenderer == null) {
         return true; // 如果没有地图渲染器,跳过检查
      }
      // 获取当前边界
      List<Point2D.Double> boundary = mapRenderer.getCurrentBoundary();
      if (boundary == null || boundary.size() < 3) {
         return true; // 如果没有边界或边界点不足,跳过检查
      }
      // 获取割草机位置
      Gecaoji mower = mapRenderer.getMower();
      if (mower == null || !mower.hasValidPosition()) {
         showCustomMessageDialog("无法获取割草机位置,请检查设备连接", "提示");
         return false;
      }
      Point2D.Double mowerPosition = mower.getPosition();
      if (mowerPosition == null) {
         showCustomMessageDialog("无法获取割草机位置,请检查设备连接", "提示");
         return false;
      }
      // 使用 MowerBoundaryChecker 检查是否在边界内
      boolean isInside = MowerBoundaryChecker.isInsideBoundaryPoints(
         boundary,
         mowerPosition.x,
         mowerPosition.y
      );
      if (!isInside) {
         showCustomMessageDialog("请将割草机开到作业地块内然后点击开始作业", "提示");
         return false;
      }
      return true;
   }
   /**
    * 显示自定义消息对话框,使用 buttonset 创建确定按钮
    * @param message 消息内容
    * @param title 对话框标题
    */
   private void showCustomMessageDialog(String message, String title) {
      Window parentWindow = SwingUtilities.getWindowAncestor(this);
      JDialog dialog = new JDialog(parentWindow, title, Dialog.ModalityType.APPLICATION_MODAL);
      dialog.setLayout(new BorderLayout(20, 20));
      dialog.setResizable(false);
      // 内容面板
      JPanel contentPanel = new JPanel(new BorderLayout(0, 15));
      contentPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 10, 20));
      contentPanel.setBackground(Color.WHITE);
      // 消息标签
      JLabel messageLabel = new JLabel("<html><div style='text-align: center;'>" + message + "</div></html>");
      messageLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
      messageLabel.setHorizontalAlignment(SwingConstants.CENTER);
      contentPanel.add(messageLabel, BorderLayout.CENTER);
      // 按钮面板
      JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 0));
      buttonPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
      buttonPanel.setOpaque(false);
      // 使用 buttonset 创建确定按钮
      JButton okButton = buttonset.createStyledButton("确定", THEME_COLOR);
      okButton.addActionListener(e -> dialog.dispose());
      buttonPanel.add(okButton);
      contentPanel.add(buttonPanel, BorderLayout.SOUTH);
      dialog.add(contentPanel, BorderLayout.CENTER);
      dialog.pack();
      dialog.setLocationRelativeTo(this);
      dialog.setVisible(true);
   }
   private void handleStopAction() {
      if (handheldCaptureInlineUiActive) {
         handleHandheldFinishAction();
         return;
      }
      if (drawingControlModeActive) {
         handleDrawingStopFromControlPanel();
         return;
      }
      stopButtonActive = !stopButtonActive;
      updateStopButtonIcon();
      if (stopButtonActive) {
         statusLabel.setText("已结束");
         startButtonShowingPause = false;
         stopMowingSession();
      } else {
         statusLabel.setText("待机");
         startButtonShowingPause = true;
         pauseMowingSession();
      }
      updateStartButtonAppearance();
   }
   private void handleDrawingStopFromControlPanel() {
      if (endDrawingCallback != null) {
         endDrawingCallback.run();
      } else {
         addzhangaiwu.finishDrawingSession();
      }
   }
   private void handleHandheldConfirmAction() {
      if (!handheldCaptureInlineUiActive) {
         return;
      }
      if (!canConfirmHandheldPoint()) {
         refreshHandheldCaptureUiState();
         return;
      }
      int count = captureHandheldBoundaryPoint();
      if (count <= 0) {
         refreshHandheldCaptureUiState();
         return;
      }
      refreshHandheldCaptureUiState();
   }
   private void handleHandheldFinishAction() {
      if (!handheldCaptureInlineUiActive) {
         return;
      }
      if (stopBtn != null && !stopBtn.isEnabled()) {
         refreshHandheldCaptureUiState();
         return;
      }
      if (!finishHandheldBoundaryCapture()) {
         refreshHandheldCaptureUiState();
      }
   }
   private void enterHandheldCaptureInlineUi() {
      if (handheldCaptureInlineUiActive) {
         refreshHandheldCaptureUiState();
         return;
      }
      handheldCaptureInlineUiActive = true;
      handheldCaptureStoredStatusText = statusLabel != null ? statusLabel.getText() : null;
      if (statusLabel != null) {
         statusLabel.setText("手持采集中");
      }
      if (startBtn != null) {
         handheldStartButtonOriginalBackground = startBtn.getBackground();
         handheldStartButtonOriginalForeground = startBtn.getForeground();
         startBtn.setIcon(null);
         startBtn.setIconTextGap(0);
         startBtn.setHorizontalAlignment(SwingConstants.CENTER);
         startBtn.setHorizontalTextPosition(SwingConstants.CENTER);
         startBtn.setVerticalTextPosition(SwingConstants.CENTER);
      }
      if (stopBtn != null) {
         handheldStopButtonOriginalBackground = stopBtn.getBackground();
         handheldStopButtonOriginalForeground = stopBtn.getForeground();
         stopBtn.setIcon(null);
         stopBtn.setIconTextGap(0);
         stopBtn.setHorizontalAlignment(SwingConstants.CENTER);
         stopBtn.setHorizontalTextPosition(SwingConstants.CENTER);
         stopBtn.setVerticalTextPosition(SwingConstants.CENTER);
         stopBtn.setText("结束");
      }
      startHandheldCaptureStatusTimer();
      refreshHandheldCaptureUiState();
   }
   private void exitHandheldCaptureInlineUi() {
      if (!handheldCaptureInlineUiActive) {
         return;
      }
      handheldCaptureInlineUiActive = false;
      stopHandheldCaptureStatusTimer();
      if (statusLabel != null) {
         statusLabel.setText(handheldCaptureStoredStatusText != null ? handheldCaptureStoredStatusText : "待机");
      }
      if (startBtn != null) {
         startBtn.setToolTipText(null);
         if (handheldStartButtonOriginalBackground != null) {
            startBtn.setBackground(handheldStartButtonOriginalBackground);
         }
         if (handheldStartButtonOriginalForeground != null) {
            startBtn.setForeground(handheldStartButtonOriginalForeground);
         }
         startBtn.setEnabled(true);
         updateStartButtonAppearance();
      }
      if (stopBtn != null) {
         stopBtn.setToolTipText(null);
         if (handheldStopButtonOriginalBackground != null) {
            stopBtn.setBackground(handheldStopButtonOriginalBackground);
         }
         if (handheldStopButtonOriginalForeground != null) {
            stopBtn.setForeground(handheldStopButtonOriginalForeground);
         }
         stopBtn.setEnabled(true);
         stopBtn.setText("结束");
         updateStopButtonIcon();
      }
      handheldCaptureStoredStatusText = null;
      handheldStartButtonOriginalBackground = null;
      handheldStartButtonOriginalForeground = null;
      handheldStopButtonOriginalBackground = null;
      handheldStopButtonOriginalForeground = null;
   }
   private void startHandheldCaptureStatusTimer() {
      if (handheldCaptureStatusTimer == null) {
         handheldCaptureStatusTimer = new Timer(400, e -> refreshHandheldCaptureUiState());
         handheldCaptureStatusTimer.setRepeats(true);
      }
      if (!handheldCaptureStatusTimer.isRunning()) {
         handheldCaptureStatusTimer.start();
      }
   }
   private void stopHandheldCaptureStatusTimer() {
      if (handheldCaptureStatusTimer != null && handheldCaptureStatusTimer.isRunning()) {
         handheldCaptureStatusTimer.stop();
      }
   }
   // Update inline handheld capture buttons based on the current device reading.
   private void refreshHandheldCaptureUiState() {
      if (!handheldCaptureInlineUiActive) {
         return;
      }
      int nextIndex = handheldCapturedPoints + 1;
      boolean hasFix = hasHighPrecisionFix();
      boolean hasValid = hasValidRealtimeHandheldPosition();
      boolean duplicate = hasValid && isCurrentHandheldPointDuplicate();
      boolean canConfirm = handheldCaptureActive && hasFix && hasValid && !duplicate;
      if (startBtn != null) {
         String prompt = "<html><center>采集点" + nextIndex + "<br>确定</center></html>";
         startBtn.setText(prompt);
         startBtn.setEnabled(canConfirm);
         if (canConfirm) {
            if (handheldStartButtonOriginalBackground != null) {
               startBtn.setBackground(handheldStartButtonOriginalBackground);
            }
            if (handheldStartButtonOriginalForeground != null) {
               startBtn.setForeground(handheldStartButtonOriginalForeground);
            }
            startBtn.setToolTipText(null);
         } else {
            startBtn.setBackground(new Color(200, 200, 200));
            startBtn.setForeground(new Color(130, 130, 130));
            startBtn.setToolTipText(resolveHandheldConfirmTooltip(hasFix, hasValid, duplicate));
         }
      }
      if (stopBtn != null) {
         boolean canFinish = handheldCapturedPoints >= 3;
         stopBtn.setText("结束");
         stopBtn.setEnabled(canFinish);
         if (canFinish) {
            if (handheldStopButtonOriginalBackground != null) {
               stopBtn.setBackground(handheldStopButtonOriginalBackground);
            }
            if (handheldStopButtonOriginalForeground != null) {
               stopBtn.setForeground(handheldStopButtonOriginalForeground);
            }
            stopBtn.setToolTipText("结束采集并返回新增地块");
         } else {
            stopBtn.setBackground(new Color(220, 220, 220));
            stopBtn.setForeground(new Color(130, 130, 130));
            stopBtn.setToolTipText("至少采集三个点才能结束");
         }
      }
   }
   private String resolveHandheldConfirmTooltip(boolean hasFix, boolean hasValidPosition, boolean duplicate) {
      if (!hasFix) {
         return "当前定位质量不足,无法采集";
      }
      if (!hasValidPosition) {
         return "当前定位数据无效,请稍后再试";
      }
      if (duplicate) {
         return "当前坐标已采集,请移动到新的位置";
      }
      return null;
   }
   private boolean hasHighPrecisionFix() {
      Device device = Device.getGecaoji();
      if (device == null) {
         return false;
      }
      String status = device.getPositioningStatus();
      return status != null && "4".equals(status.trim());
   }
   private boolean canConfirmHandheldPoint() {
      return handheldCaptureActive
            && hasHighPrecisionFix()
            && hasValidRealtimeHandheldPosition()
            && !isCurrentHandheldPointDuplicate();
   }
   private void enterDrawingControlMode() {
      if (drawingControlModeActive) {
         return;
      }
      storedStartButtonShowingPause = startButtonShowingPause;
      storedStopButtonActive = stopButtonActive;
      storedStatusBeforeDrawing = statusLabel != null ? statusLabel.getText() : null;
      drawingControlModeActive = true;
      applyDrawingPauseState(false, false);
      updateDrawingControlButtonLabels();
   }
   private void exitDrawingControlMode() {
      if (!drawingControlModeActive) {
         return;
      }
      drawingControlModeActive = false;
      applyDrawingPauseState(false, false);
      drawingPaused = false;
      stopButtonActive = storedStopButtonActive;
      startButtonShowingPause = storedStartButtonShowingPause;
      if (startBtn != null) {
         updateStartButtonAppearance();
      }
      if (stopBtn != null) {
         stopBtn.setText("结束");
         updateStopButtonIcon();
      }
      if (statusLabel != null) {
         statusLabel.setText(storedStatusBeforeDrawing != null ? storedStatusBeforeDrawing : "待机");
      }
      storedStatusBeforeDrawing = null;
   }
   private void updateDrawingControlButtonLabels() {
      if (!drawingControlModeActive) {
         return;
      }
      configureButtonForDrawingMode(startBtn);
      configureButtonForDrawingMode(stopBtn);
      if (startBtn != null) {
         startBtn.setText(drawingPaused ? "开始绘制" : "暂停绘制");
      }
      if (stopBtn != null) {
         stopBtn.setText("结束绘制");
      }
   }
   private void configureButtonForDrawingMode(JButton button) {
      if (button == null) {
         return;
      }
      button.setIcon(null);
      button.setIconTextGap(0);
      button.setHorizontalAlignment(SwingConstants.CENTER);
      button.setHorizontalTextPosition(SwingConstants.CENTER);
   }
   private void updateStartButtonAppearance() {
      if (startBtn == null) {
         return;
      }
      String iconPath = startButtonShowingPause ? "image/start0.png" : "image/start1.png";
      startBtn.setText(startButtonShowingPause ? "暂停" : "开始");
      applyButtonIcon(startBtn, iconPath);
   }
   private void updateStopButtonIcon() {
      if (stopBtn == null) {
         return;
      }
      String iconPath = stopButtonActive ? "image/stop1.png" : "image/stop0.png";
      applyButtonIcon(stopBtn, iconPath);
   }
   private void toggleBluetoothConnection() {
      if (bluetoothBtn == null) {
         return;
      }
      // 弹出系统调试页面
      showDebugDialog();
   }
   private void showDebugDialog() {
      Window parentWindow = SwingUtilities.getWindowAncestor(this);
      debug debugDialog = new debug(parentWindow, THEME_COLOR);
      debugDialog.setLocationRelativeTo(this); // 居中显示在首页
      debugDialog.setVisible(true);
   }
   private void updateBluetoothButtonIcon() {
      if (bluetoothBtn == null) {
         return;
      }
      ensureBluetoothIconsLoaded();
      // 根据串口连接状态显示图标
      SerialPortService service = sendmessage.getActiveService();
      boolean serialConnected = (service != null && service.isOpen());
      ImageIcon icon = serialConnected ? bluetoothLinkedIcon : bluetoothIcon;
      if (icon != null) {
         bluetoothBtn.setIcon(icon);
         bluetoothBtn.setText(null);
      } else {
         bluetoothBtn.setText(serialConnected ? "已连" : "蓝牙");
      }
   }
   private JPanel createSpeedIndicatorPanel() {
      JPanel panel = new JPanel(new BorderLayout());
      panel.setOpaque(false);
      panel.setBorder(BorderFactory.createEmptyBorder(10, 20, 5, 20));
      JPanel rightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0));
      rightPanel.setOpaque(false);
   fixQualityIndicator = new gpszhuangtai(THEME_COLOR);
      fixQualityIndicator.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      fixQualityIndicator.addMouseListener(new MouseAdapter() {
         @Override
         public void mouseClicked(MouseEvent e) {
            if (SwingUtilities.isLeftMouseButton(e) && mapRenderer != null) {
               mapRenderer.showMowerInfo();
            }
         }
      });
      mowingProgressLabel = new JLabel("--%");
      mowingProgressLabel.setFont(new Font("微软雅黑", Font.BOLD, 12));
      mowingProgressLabel.setForeground(THEME_COLOR);
      mowerSpeedValueLabel = new JLabel("--");
      mowerSpeedValueLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
      mowerSpeedValueLabel.setForeground(THEME_COLOR);
      mowerSpeedUnitLabel = new JLabel("km/h");
      mowerSpeedUnitLabel.setFont(new Font("微软雅黑", Font.BOLD, 9));
      mowerSpeedUnitLabel.setForeground(THEME_COLOR);
      dataPacketCountLabel = new JLabel("--");
      dataPacketCountLabel.setFont(new Font("微软雅黑", Font.BOLD, 12));
      dataPacketCountLabel.setForeground(THEME_COLOR);
      rightPanel.add(fixQualityIndicator);
      JSeparator areaSeparator = new JSeparator(SwingConstants.VERTICAL);
      areaSeparator.setPreferredSize(new Dimension(1, 16));
      rightPanel.add(areaSeparator);
      rightPanel.add(mowingProgressLabel);
      JSeparator speedSeparator = new JSeparator(SwingConstants.VERTICAL);
      speedSeparator.setPreferredSize(new Dimension(1, 16));
      rightPanel.add(speedSeparator);
      rightPanel.add(mowerSpeedValueLabel);
      rightPanel.add(mowerSpeedUnitLabel);
      JSeparator separator = new JSeparator(SwingConstants.VERTICAL);
      separator.setPreferredSize(new Dimension(1, 16));
      rightPanel.add(separator);
      rightPanel.add(dataPacketCountLabel);
      panel.add(rightPanel, BorderLayout.EAST);
      updateFixQualityIndicator();
      updateDataPacketCountLabel();
      return panel;
   }
   private void startMowerSpeedUpdates() {
      if (mowerSpeedRefreshTimer == null) {
         mowerSpeedRefreshTimer = new Timer(1000, e -> refreshMowerSpeedLabel());
         mowerSpeedRefreshTimer.setRepeats(true);
      }
      if (!mowerSpeedRefreshTimer.isRunning()) {
         mowerSpeedRefreshTimer.start();
      }
      refreshMowerSpeedLabel();
   }
   private void refreshMowerSpeedLabel() {
      if (mowerSpeedValueLabel == null) {
         return;
      }
      String display = "--";
      Device device = Device.getGecaoji();
      if (device != null) {
         String sanitized = sanitizeSpeedValue(device.getRealtimeSpeed());
         if (sanitized != null) {
            display = sanitized;
         }
      }
      mowerSpeedValueLabel.setText(display);
      if (mowerSpeedUnitLabel != null) {
         mowerSpeedUnitLabel.setText("km/h");
      }
      updateMowingProgressLabel();
      updateFixQualityIndicator();
      updateDataPacketCountLabel();
   }
   private void updateDataPacketCountLabel() {
      if (dataPacketCountLabel == null) {
         return;
      }
      int udpCount = UDPServer.getReceivedPacketCount();
      int serialCount = dellmessage.getProcessedLineCount();
      int displayCount = Math.max(udpCount, serialCount);
      if (displayCount <= 0) {
         dataPacketCountLabel.setText("--");
         dataPacketCountLabel.setToolTipText(null);
      } else {
         dataPacketCountLabel.setText(String.valueOf(displayCount));
         dataPacketCountLabel.setToolTipText(String.format("串口: %d  UDP: %d", serialCount, udpCount));
      }
   }
   private void updateFixQualityIndicator() {
      if (fixQualityIndicator == null) {
         return;
      }
      Device device = Device.getGecaoji();
      String code = null;
      if (device != null) {
         code = sanitizeDeviceValue(device.getPositioningStatus());
      }
      fixQualityIndicator.setQuality(code);
   }
   private Color resolveFixQualityColor(String code) {
      if (code == null) {
         return new Color(160, 160, 160);
      }
      switch (code) {
      case "0":
         return new Color(160, 160, 160);
      case "1":
         return new Color(52, 152, 219);
      case "2":
         return new Color(26, 188, 156);
      case "3":
         return new Color(155, 89, 182);
      case "4":
         return THEME_COLOR;
      case "5":
         return new Color(241, 196, 15);
      case "6":
         return new Color(231, 76, 60);
      case "7":
         return new Color(230, 126, 34);
      default:
         return new Color(95, 95, 95);
      }
   }
   private String resolveFixQualityDescription(String code) {
      if (code == null) {
         return "未知";
      }
      switch (code) {
      case "0":
         return "未定位";
      case "1":
         return "单点定位";
      case "2":
         return "码差分";
      case "3":
         return "无效PPS";
      case "4":
         return "固定解";
      case "5":
         return "浮点解";
      case "6":
         return "正在估算";
      case "7":
         return "人工输入固定值";
      default:
         return "其他";
      }
   }
   private String sanitizeSpeedValue(String raw) {
      if (raw == null) {
         return null;
      }
      String trimmed = raw.trim();
      if (trimmed.isEmpty() || "-1".equals(trimmed)) {
         return null;
      }
      if (trimmed.toLowerCase().endsWith("km/h")) {
         trimmed = trimmed.substring(0, trimmed.length() - 4).trim();
      }
      return trimmed;
   }
   private String sanitizeDeviceValue(String raw) {
      if (raw == null) {
         return null;
      }
      String trimmed = raw.trim();
      if (trimmed.isEmpty() || "-1".equals(trimmed) || "null".equalsIgnoreCase(trimmed)) {
         return null;
      }
      return trimmed;
   }
   private void updateMowingProgressLabel() {
      if (mowingProgressLabel == null) {
         return;
      }
      if (mapRenderer == null) {
         mowingProgressLabel.setText("--%");
         mowingProgressLabel.setToolTipText(null);
         return;
      }
      double totalArea = mapRenderer.getTotalLandAreaSqMeters();
      double completedArea = mapRenderer.getCompletedMowingAreaSqMeters();
      double ratio = mapRenderer.getMowingCompletionRatio();
      if (totalArea <= 0) {
         mowingProgressLabel.setText("--%");
         mowingProgressLabel.setToolTipText("暂无地块面积数据");
         return;
      }
      double percent = Math.max(0.0, Math.min(1.0, ratio)) * 100.0;
      mowingProgressLabel.setText(String.format(Locale.US, "%.1f%%", percent));
      mowingProgressLabel.setToolTipText(String.format(Locale.US, "%.1f㎡ / %.1f㎡", completedArea, totalArea));
   }
   public void refreshMowingIndicators() {
      refreshMowerSpeedLabel();
   }
   public void setHandheldMowerIconActive(boolean active) {
      if (mapRenderer == null) {
         return;
      }
      mapRenderer.setHandheldMowerIconActive(active);
   }
   public boolean startMowerBoundaryCapture() {
      if (mapRenderer == null) {
         return false;
      }
      double[] baseLatLonCandidate = resolveCircleBaseLatLon();
      if (baseLatLonCandidate == null) {
         return false;
      }
      mapRenderer.clearIdleTrail();
      activeBoundaryMode = BoundaryCaptureMode.MOWER;
      mowerBoundaryCaptureActive = true;
      mowerBaseLatLon = baseLatLonCandidate;
      lastMowerCoordinate = null;
      synchronized (Coordinate.coordinates) {
         Coordinate.coordinates.clear();
      }
      synchronized (mowerTemporaryPoints) {
         mowerTemporaryPoints.clear();
      }
      AddDikuai.recordTemporaryBoundaryPoints(Collections.emptyList());
      Coordinate.setStartSaveGngga(true);
      if (mapRenderer != null) {
         mapRenderer.setBoundaryPreviewMarkerScale(2.0d);
         mapRenderer.beginHandheldBoundaryPreview();
      }
      setHandheldMowerIconActive(false);
      startMowerBoundaryMonitor();
      return true;
   }
   public boolean startHandheldBoundaryCapture() {
      if (mapRenderer == null) {
         return false;
      }
      if (activeBoundaryMode == BoundaryCaptureMode.MOWER) {
         stopMowerBoundaryCapture();
      }
      mapRenderer.clearIdleTrail();
      activeBoundaryMode = BoundaryCaptureMode.HANDHELD;
      handheldCaptureActive = true;
      handheldCapturedPoints = 0;
      Coordinate.setStartSaveGngga(false);
      synchronized (Coordinate.coordinates) {
         Coordinate.coordinates.clear();
      }
      synchronized (handheldTemporaryPoints) {
         handheldTemporaryPoints.clear();
      }
      AddDikuai.recordTemporaryBoundaryPoints(Collections.emptyList());
      mapRenderer.setBoundaryPreviewMarkerScale(1.0d);
      mapRenderer.beginHandheldBoundaryPreview();
      setHandheldMowerIconActive(true);
      enterHandheldCaptureInlineUi();
      return true;
   }
   private void startMowerBoundaryMonitor() {
      if (mowerBoundaryMonitor == null) {
         mowerBoundaryMonitor = new Timer(600, e -> pollMowerBoundaryCoordinate());
         mowerBoundaryMonitor.setRepeats(true);
      }
      if (!mowerBoundaryMonitor.isRunning()) {
         mowerBoundaryMonitor.start();
      }
      pollMowerBoundaryCoordinate();
   }
   private void stopMowerBoundaryMonitor() {
      if (mowerBoundaryMonitor != null && mowerBoundaryMonitor.isRunning()) {
         mowerBoundaryMonitor.stop();
      }
   }
   private void pollMowerBoundaryCoordinate() {
      if (!mowerBoundaryCaptureActive) {
         return;
      }
      Coordinate latest = getLatestCoordinate();
      if (latest == null || latest == lastMowerCoordinate) {
         return;
      }
      double[] base = mowerBaseLatLon;
      if (base == null || base.length < 2) {
         discardLatestCoordinate(latest);
         lastMowerCoordinate = latest;
         return;
      }
      double lat = parseDMToDecimal(latest.getLatitude(), latest.getLatDirection());
      double lon = parseDMToDecimal(latest.getLongitude(), latest.getLonDirection());
      if (!Double.isFinite(lat) || !Double.isFinite(lon)) {
         discardLatestCoordinate(latest);
         lastMowerCoordinate = latest;
         return;
      }
      double[] local = convertLatLonToLocal(lat, lon, base[0], base[1]);
      Point2D.Double candidate = new Point2D.Double(local[0], local[1]);
      if (!Double.isFinite(candidate.x) || !Double.isFinite(candidate.y)) {
         discardLatestCoordinate(latest);
         lastMowerCoordinate = latest;
         return;
      }
      List<Point2D.Double> snapshot;
      synchronized (mowerTemporaryPoints) {
         for (Point2D.Double existing : mowerTemporaryPoints) {
            if (existing != null && arePointsClose(existing, candidate)) {
               discardLatestCoordinate(latest);
               lastMowerCoordinate = latest;
               return;
            }
         }
         mowerTemporaryPoints.add(candidate);
         snapshot = new ArrayList<>(mowerTemporaryPoints.size() + 1);
         for (Point2D.Double point : mowerTemporaryPoints) {
            if (point != null) {
               snapshot.add(new Point2D.Double(point.x, point.y));
            }
         }
      }
      ensureClosed(snapshot);
      AddDikuai.recordTemporaryBoundaryPoints(snapshot);
      if (mapRenderer != null) {
         mapRenderer.addHandheldBoundaryPoint(candidate.x, candidate.y);
      }
      lastMowerCoordinate = latest;
   }
   private void stopMowerBoundaryCapture() {
      stopMowerBoundaryMonitor();
      mowerBoundaryCaptureActive = false;
      lastMowerCoordinate = null;
      mowerBaseLatLon = null;
      if (mapRenderer != null) {
         mapRenderer.clearHandheldBoundaryPreview();
      }
      Coordinate.setStartSaveGngga(false);
      if (activeBoundaryMode == BoundaryCaptureMode.MOWER) {
         activeBoundaryMode = BoundaryCaptureMode.NONE;
      }
      setHandheldMowerIconActive(false);
   }
   private void discardLatestCoordinate(Coordinate coordinate) {
      if (coordinate == null) {
         return;
      }
      synchronized (Coordinate.coordinates) {
         int size = Coordinate.coordinates.size();
         if (size == 0) {
            return;
         }
         int lastIndex = size - 1;
         if (Coordinate.coordinates.get(lastIndex) == coordinate) {
            Coordinate.coordinates.remove(lastIndex);
         } else {
            Coordinate.coordinates.remove(coordinate);
         }
      }
   }
   private void ensureClosed(List<Point2D.Double> points) {
      if (points == null || points.size() < 3) {
         return;
      }
      Point2D.Double first = points.get(0);
      Point2D.Double last = points.get(points.size() - 1);
      if (first == null || last == null) {
         return;
      }
      if (!arePointsClose(first, last)) {
         points.add(new Point2D.Double(first.x, first.y));
      }
   }
   int captureHandheldBoundaryPoint() {
      if (!handheldCaptureActive) {
         return -1;
      }
      Device device = Device.getGecaoji();
      if (device == null) {
         JOptionPane.showMessageDialog(this, "未检测到采集设备,请检查连接。", "提示", JOptionPane.WARNING_MESSAGE);
         return -1;
      }
      String[] latParts = splitCoordinateComponents(device.getRealtimeLatitude(), true);
      String[] lonParts = splitCoordinateComponents(device.getRealtimeLongitude(), false);
      if (latParts == null || lonParts == null) {
         JOptionPane.showMessageDialog(this, "当前定位无效,请在定位稳定后再试。", "提示", JOptionPane.WARNING_MESSAGE);
         return -1;
      }
      double x = parseMetersValue(device.getRealtimeX());
      double y = parseMetersValue(device.getRealtimeY());
      if (!Double.isFinite(x) || !Double.isFinite(y)) {
         JOptionPane.showMessageDialog(this, "当前定位数据无效,请稍后再试。", "提示", JOptionPane.WARNING_MESSAGE);
         return -1;
      }
      if (isDuplicateHandheldPoint(x, y)) {
         JOptionPane.showMessageDialog(this, "当前坐标已采集,请移动到新的位置后再试。", "提示", JOptionPane.WARNING_MESSAGE);
         return -1;
      }
      double altitude = parseAltitudeValue(device.getRealtimeAltitude());
      Coordinate coordinate = new Coordinate(latParts[0], latParts[1], lonParts[0], lonParts[1], altitude);
      synchronized (Coordinate.coordinates) {
         Coordinate.coordinates.add(coordinate);
      }
      if (mapRenderer != null) {
         mapRenderer.addHandheldBoundaryPoint(x, y);
      }
      List<Point2D.Double> snapshot;
      synchronized (handheldTemporaryPoints) {
         handheldTemporaryPoints.add(new Point2D.Double(x, y));
         snapshot = new ArrayList<>(handheldTemporaryPoints);
      }
      AddDikuai.recordTemporaryBoundaryPoints(snapshot);
      handheldCapturedPoints++;
      return handheldCapturedPoints;
   }
   boolean finishHandheldBoundaryCapture() {
      if (!handheldCaptureActive) {
         return false;
      }
      if (handheldCapturedPoints < 3) {
         JOptionPane.showMessageDialog(this, "至少采集三个点才能生成边界。", "提示", JOptionPane.WARNING_MESSAGE);
         return false;
      }
      List<Point2D.Double> closedSnapshot = createClosedHandheldPointSnapshot();
      handheldCaptureActive = false;
      activeBoundaryMode = BoundaryCaptureMode.NONE;
      Coordinate.setStartSaveGngga(false);
      if (mapRenderer != null) {
         mapRenderer.clearHandheldBoundaryPreview();
      }
      AddDikuai.recordTemporaryBoundaryPoints(closedSnapshot);
      exitHandheldCaptureInlineUi();
      SwingUtilities.invokeLater(AddDikuai::finishDrawingSession);
      return true;
   }
   int getHandheldCapturedPointCount() {
      return handheldCapturedPoints;
   }
   public List<Point2D.Double> getHandheldTemporaryPointsSnapshot() {
      if (activeBoundaryMode == BoundaryCaptureMode.MOWER) {
         return createClosedMowerPointSnapshot();
      }
      if (!handheldCaptureActive) {
         return createClosedHandheldPointSnapshot();
      }
      synchronized (handheldTemporaryPoints) {
         return new ArrayList<>(handheldTemporaryPoints);
      }
   }
   public boolean isCurrentHandheldPointDuplicate() {
      Device device = Device.getGecaoji();
      if (device == null) {
         return false;
      }
      double x = parseMetersValue(device.getRealtimeX());
      double y = parseMetersValue(device.getRealtimeY());
      if (!Double.isFinite(x) || !Double.isFinite(y)) {
         return false;
      }
      return isDuplicateHandheldPoint(x, y);
   }
   public boolean hasValidRealtimeHandheldPosition() {
      Device device = Device.getGecaoji();
      if (device == null) {
         return false;
      }
      double x = parseMetersValue(device.getRealtimeX());
      double y = parseMetersValue(device.getRealtimeY());
      return Double.isFinite(x) && Double.isFinite(y);
   }
   private boolean isDuplicateHandheldPoint(double x, double y) {
      Point2D.Double candidate = new Point2D.Double(x, y);
      synchronized (handheldTemporaryPoints) {
         for (Point2D.Double existing : handheldTemporaryPoints) {
            if (existing == null) {
               continue;
            }
            if (arePointsClose(existing, candidate)) {
               return true;
            }
         }
      }
      return false;
   }
   private boolean arePointsClose(Point2D.Double a, Point2D.Double b) {
      if (a == null || b == null) {
         return false;
      }
      double dx = a.x - b.x;
      double dy = a.y - b.y;
      return Math.hypot(dx, dy) < HANDHELD_DUPLICATE_THRESHOLD_METERS;
   }
   private List<Point2D.Double> createClosedHandheldPointSnapshot() {
      List<Point2D.Double> copy = new ArrayList<>();
      synchronized (handheldTemporaryPoints) {
         for (Point2D.Double point : handheldTemporaryPoints) {
            if (point != null) {
               copy.add(new Point2D.Double(point.x, point.y));
            }
         }
      }
      ensureClosed(copy);
      return copy;
   }
   private List<Point2D.Double> createClosedMowerPointSnapshot() {
      List<Point2D.Double> copy = new ArrayList<>();
      synchronized (mowerTemporaryPoints) {
         for (Point2D.Double point : mowerTemporaryPoints) {
            if (point != null) {
               copy.add(new Point2D.Double(point.x, point.y));
            }
         }
      }
      ensureClosed(copy);
      return copy;
   }
   private String[] splitCoordinateComponents(String combined, boolean latitude) {
      if (combined == null) {
         return null;
      }
      String trimmed = combined.trim();
      if (trimmed.isEmpty() || "-1".equals(trimmed)) {
         return null;
      }
      String valuePart;
      String directionPart = null;
      String[] parts = trimmed.split(",");
      if (parts.length >= 2) {
         valuePart = parts[0].trim();
         directionPart = parts[1].trim();
      } else {
         valuePart = trimmed;
      }
      if (valuePart.isEmpty()) {
         return null;
      }
      if (directionPart == null || directionPart.isEmpty()) {
         char lastChar = valuePart.charAt(valuePart.length() - 1);
         if (Character.isLetter(lastChar)) {
            directionPart = String.valueOf(lastChar);
            valuePart = valuePart.substring(0, valuePart.length() - 1).trim();
         }
      }
      if (valuePart.isEmpty()) {
         return null;
      }
      directionPart = normalizeHemisphere(directionPart, latitude);
      return new String[]{valuePart, directionPart};
   }
   private String normalizeHemisphere(String direction, boolean latitude) {
      if (direction == null || direction.trim().isEmpty()) {
         return latitude ? "N" : "E";
      }
      String normalized = direction.trim().toUpperCase(Locale.ROOT);
      if (latitude) {
         if (!"N".equals(normalized) && !"S".equals(normalized)) {
            return "N";
         }
      } else {
         if (!"E".equals(normalized) && !"W".equals(normalized)) {
            return "E";
         }
      }
      return normalized;
   }
   private double parseMetersValue(String raw) {
      if (raw == null) {
         return Double.NaN;
      }
      String trimmed = raw.trim();
      if (trimmed.isEmpty() || "-1".equals(trimmed)) {
         return Double.NaN;
      }
      try {
         return Double.parseDouble(trimmed);
      } catch (NumberFormatException ex) {
         return Double.NaN;
      }
   }
   private double parseAltitudeValue(String raw) {
      if (raw == null) {
         return 0.0;
      }
      String trimmed = raw.trim();
      if (trimmed.isEmpty() || "-1".equals(trimmed)) {
         return 0.0;
      }
      try {
         return Double.parseDouble(trimmed);
      } catch (NumberFormatException ex) {
         return 0.0;
      }
   }
   private boolean beginMowingSession() {
      if (mapRenderer == null) {
         return false;
      }
      String landNumber = Dikuaiguanli.getCurrentWorkLandNumber();
      if (!isMeaningfulValue(landNumber)) {
         JOptionPane.showMessageDialog(this, "请先选择地块后再开始作业", "提示", JOptionPane.WARNING_MESSAGE);
         return false;
      }
      double widthMeters = resolveMowerWidthMeters(landNumber);
      if (widthMeters <= 0) {
         JOptionPane.showMessageDialog(this, "未配置割草宽度,将无法计算作业面积", "提示", JOptionPane.WARNING_MESSAGE);
      }
      mapRenderer.startRealtimeTrackRecording(landNumber, widthMeters);
      refreshMowerSpeedLabel();
      return true;
   }
   private void pauseMowingSession() {
      if (mapRenderer == null) {
         return;
      }
      mapRenderer.pauseRealtimeTrackRecording();
      refreshMowerSpeedLabel();
   }
   private void stopMowingSession() {
      if (mapRenderer == null) {
         return;
      }
      mapRenderer.stopRealtimeTrackRecording();
      refreshMowerSpeedLabel();
   }
   private double resolveMowerWidthMeters(String landNumber) {
      double width = 0.0;
      if (isMeaningfulValue(landNumber)) {
         Dikuai current = Dikuai.getDikuai(landNumber);
         if (current != null) {
            width = parseMowerWidthMeters(current.getMowingWidth());
         }
      }
      if (width > 0) {
         return width;
      }
      return parseMowerWidthFromDevice();
   }
   private double parseMowerWidthFromDevice() {
      Device device = Device.getGecaoji();
      if (device == null) {
         return 0.0;
      }
      return parseMowerWidthMeters(device.getMowingWidth());
   }
   private double parseMowerWidthMeters(String raw) {
      if (raw == null) {
         return 0.0;
      }
      String sanitized = raw.trim().toLowerCase(Locale.ROOT);
      if (sanitized.isEmpty() || "-1".equals(sanitized)) {
         return 0.0;
      }
      sanitized = sanitized.replace("厘米", "cm");
      sanitized = sanitized.replace("公分", "cm");
      sanitized = sanitized.replace("米", "m");
      sanitized = sanitized.replace("cm", "");
      sanitized = sanitized.replace("m", "");
      sanitized = sanitized.trim();
      if (sanitized.isEmpty()) {
         return 0.0;
      }
      try {
         double value = Double.parseDouble(sanitized);
         if (value <= 0) {
            return 0.0;
         }
         if (value > 10) {
            return value / 100.0;
         }
         return value;
      } catch (NumberFormatException ex) {
         return 0.0;
      }
   }
   private void applyStatusLabelColor(String statusText) {
      if (statusLabel == null) {
         return;
      }
      if ("作业中".equals(statusText) || "绘制中".equals(statusText)) {
         statusLabel.setForeground(THEME_COLOR);
      } else if ("暂停中".equals(statusText) || "绘制暂停".equals(statusText)) {
         statusLabel.setForeground(STATUS_PAUSE_COLOR);
      } else {
         statusLabel.setForeground(Color.GRAY);
      }
   }
   private void ensureBluetoothIconsLoaded() {
      if (bluetoothIcon == null) {
         bluetoothIcon = loadScaledIcon("image/blue.png", 28, 28);
      }
      if (bluetoothLinkedIcon == null) {
         bluetoothLinkedIcon = loadScaledIcon("image/bluelink.png", 28, 28);
      }
   }
   private JButton createFloatingIconButton() {
      JButton button = new JButton();
      button.setContentAreaFilled(false);
      button.setBorder(BorderFactory.createEmptyBorder());
      button.setFocusPainted(false);
      button.setOpaque(false);
      button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      int size = FLOAT_ICON_SIZE + 8;
      button.setPreferredSize(new Dimension(size, size));
      return button;
   }
   private JButton createFloatingTextButton(String text) {
      JButton button = new JButton(text);
      button.setFont(new Font("微软雅黑", Font.BOLD, 15));
      button.setForeground(Color.WHITE);
      button.setBackground(THEME_COLOR);
      button.setBorder(BorderFactory.createEmptyBorder(10, 18, 10, 18));
      button.setFocusPainted(false);
      button.setOpaque(true);
      button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      button.addMouseListener(new MouseAdapter() {
         @Override
         public void mouseEntered(MouseEvent e) {
            button.setBackground(THEME_HOVER_COLOR);
         }
         @Override
         public void mouseExited(MouseEvent e) {
            button.setBackground(THEME_COLOR);
         }
      });
      return button;
   }
   private ImageIcon loadScaledIcon(String path, int width, int height) {
      try {
         ImageIcon icon = new ImageIcon(path);
         if (icon.getIconWidth() <= 0 || icon.getIconHeight() <= 0) {
            return null;
         }
         Image scaled = icon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH);
         return new ImageIcon(scaled);
      } catch (Exception ex) {
         System.err.println("加载图标失败: " + path + " - " + ex.getMessage());
         return null;
      }
   }
   private void ensureFloatingIconsLoaded() {
      if (pauseIcon == null) {
         pauseIcon = loadScaledIcon("image/zanting.png", FLOAT_ICON_SIZE, FLOAT_ICON_SIZE);
      }
      if (pauseActiveIcon == null) {
         pauseActiveIcon = loadScaledIcon("image/zantingzhong.png", FLOAT_ICON_SIZE, FLOAT_ICON_SIZE);
      }
      if (endIcon == null) {
         endIcon = loadScaledIcon("image/end.png", FLOAT_ICON_SIZE, FLOAT_ICON_SIZE);
      }
   }
   private void updatePauseButtonVisual() {
      if (drawingPauseButton == null) {
         return;
      }
      if (drawingPaused) {
         if (pauseActiveIcon != null) {
            drawingPauseButton.setIcon(pauseActiveIcon);
            drawingPauseButton.setText(null);
         } else {
            drawingPauseButton.setText("暂停中");
            drawingPauseButton.setIcon(null);
         }
         drawingPauseButton.setToolTipText("点击恢复绘制");
      } else {
         if (pauseIcon != null) {
            drawingPauseButton.setIcon(pauseIcon);
            drawingPauseButton.setText(null);
         } else {
            drawingPauseButton.setText("暂停");
            drawingPauseButton.setIcon(null);
         }
         drawingPauseButton.setToolTipText("点击暂停绘制");
      }
   }
   private void applyDrawingPauseState(boolean paused, boolean notifyCoordinate) {
      drawingPaused = paused;
      updatePauseButtonVisual();
      if (notifyCoordinate) {
         Coordinate.setStartSaveGngga(!paused);
      }
      if (drawingControlModeActive) {
         updateDrawingControlButtonLabels();
         if (statusLabel != null) {
            statusLabel.setText(paused ? "绘制暂停" : "绘制中");
         }
      }
   }
   private void toggleDrawingPause() {
      applyDrawingPauseState(!drawingPaused, true);
   }
   public void showEndDrawingButton(Runnable callback) {
      showEndDrawingButton(callback, null);
   }
   public void showEndDrawingButton(Runnable callback, String drawingShape) {
      endDrawingCallback = callback;
      circleDialogMode = false;
      hideCircleGuidancePanel();
      enterDrawingControlMode();
      boolean enableCircleGuidance = drawingShape != null
            && "circle".equalsIgnoreCase(drawingShape.trim());
      if (enableCircleGuidance) {
         ensureFloatingIconsLoaded();
         ensureFloatingButtonInfrastructure();
         if (drawingPauseButton != null) {
            drawingPauseButton.setVisible(false);
         }
         if (endDrawingButton != null) {
            endDrawingButton.setVisible(false);
         }
         prepareCircleGuidanceState();
         showCircleGuidanceStep(1);
         floatingButtonPanel.setVisible(true);
         if (floatingButtonPanel.getParent() != visualizationPanel) {
            visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
         }
         rebuildFloatingButtonColumn();
      } else {
         clearCircleGuidanceArtifacts();
         hideFloatingDrawingControls();
      }
      visualizationPanel.revalidate();
      visualizationPanel.repaint();
   }
   private void ensureFloatingButtonInfrastructure() {
      if (endDrawingButton == null) {
         endDrawingButton = createFloatingIconButton();
         endDrawingButton.addActionListener(e -> {
            if (endDrawingCallback != null) {
               endDrawingCallback.run();
            }
         });
      }
      if (endIcon != null) {
         endDrawingButton.setIcon(endIcon);
         endDrawingButton.setText(null);
      } else {
         endDrawingButton.setText("结束绘制");
      }
      endDrawingButton.setToolTipText("结束绘制");
      if (drawingPauseButton == null) {
         drawingPauseButton = createFloatingIconButton();
         drawingPauseButton.addActionListener(e -> toggleDrawingPause());
      }
      updatePauseButtonVisual();
      if (floatingButtonPanel == null) {
         floatingButtonPanel = new JPanel(new BorderLayout());
         floatingButtonPanel.setOpaque(false);
         floatingButtonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 20, 20));
         floatingButtonColumn = new JPanel();
         floatingButtonColumn.setOpaque(false);
         floatingButtonColumn.setLayout(new BoxLayout(floatingButtonColumn, BoxLayout.Y_AXIS));
         floatingButtonPanel.add(floatingButtonColumn, BorderLayout.EAST);
      }
   }
   private void hideFloatingDrawingControls() {
      if (drawingPauseButton != null) {
         drawingPauseButton.setVisible(false);
      }
      if (endDrawingButton != null) {
         endDrawingButton.setVisible(false);
      }
      if (floatingButtonPanel != null) {
         floatingButtonPanel.setVisible(false);
      }
      if (!circleDialogMode) {
         rebuildFloatingButtonColumn();
      }
   }
   private void rebuildFloatingButtonColumn() {
      if (floatingButtonColumn == null) {
         return;
      }
      floatingButtonColumn.removeAll();
      boolean added = false;
      if (!circleDialogMode && circleGuidancePanel != null && circleGuidancePanel.isVisible()) {
         floatingButtonColumn.add(circleGuidancePanel);
         added = true;
      }
      if (!circleDialogMode && drawingPauseButton != null && drawingPauseButton.isVisible()) {
         if (added) {
            floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
         }
         floatingButtonColumn.add(drawingPauseButton);
         added = true;
      }
      if (!circleDialogMode && endDrawingButton != null && endDrawingButton.isVisible()) {
         if (added) {
            floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
         }
         floatingButtonColumn.add(endDrawingButton);
         added = true;
      }
      if (pathPreviewReturnButton != null && pathPreviewReturnButton.isVisible()) {
         if (added) {
            floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
         }
         floatingButtonColumn.add(pathPreviewReturnButton);
         added = true;
      }
      floatingButtonColumn.revalidate();
      floatingButtonColumn.repaint();
   }
   private void showCircleGuidanceStep(int step) {
      ensureCircleGuidancePanel();
      if (circleGuidancePanel == null) {
         return;
      }
      circleGuidanceStep = step;
      if (step == 1) {
         circleGuidanceLabel.setText("采集第1个点");
         circleGuidancePrimaryButton.setText("确认第1点");
         circleGuidanceSecondaryButton.setText("返回");
         circleGuidanceSecondaryButton.setVisible(true);
      } else if (step == 2) {
         circleGuidanceLabel.setText("采集第2个点");
         circleGuidancePrimaryButton.setText("确认第2点");
         circleGuidanceSecondaryButton.setText("返回");
         circleGuidanceSecondaryButton.setVisible(true);
      } else if (step == 3) {
         circleGuidanceLabel.setText("采集第3个点");
         circleGuidancePrimaryButton.setText("确认第3点");
         circleGuidanceSecondaryButton.setText("返回");
         circleGuidanceSecondaryButton.setVisible(true);
      } else if (step == 4) {
         circleGuidanceLabel.setText("已采集三个点");
         circleGuidancePrimaryButton.setText("结束绘制");
         circleGuidanceSecondaryButton.setText("重新采集");
         circleGuidanceSecondaryButton.setVisible(true);
      } else {
         hideCircleGuidancePanel();
         return;
      }
      circleGuidancePanel.setVisible(true);
      refreshCircleGuidanceButtonAvailability();
      if (circleDialogMode) {
         ensureCircleGuidanceDialog();
         if (circleGuidanceDialog != null) {
            circleGuidanceDialog.pack();
            positionCircleGuidanceDialog();
            circleGuidanceDialog.setVisible(true);
            circleGuidanceDialog.toFront();
         }
      } else {
         rebuildFloatingButtonColumn();
      }
   }
   private void ensureCircleGuidancePanel() {
      if (circleGuidancePanel != null) {
         return;
      }
      circleGuidancePanel = new JPanel();
      circleGuidancePanel.setLayout(new BoxLayout(circleGuidancePanel, BoxLayout.Y_AXIS));
      circleGuidancePanel.setOpaque(true);
      circleGuidancePanel.setBackground(new Color(255, 255, 255, 235));
      circleGuidancePanel.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(THEME_COLOR, 1),
            BorderFactory.createEmptyBorder(10, 12, 10, 12)));
      circleGuidancePanel.setAlignmentX(Component.LEFT_ALIGNMENT);
      circleGuidanceLabel = new JLabel();
      circleGuidanceLabel.setFont(new Font("微软雅黑", Font.BOLD, 13));
      circleGuidanceLabel.setForeground(new Color(33, 37, 41));
      circleGuidanceLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
      JPanel buttonRow = new JPanel();
      buttonRow.setLayout(new BoxLayout(buttonRow, BoxLayout.X_AXIS));
      buttonRow.setOpaque(false);
      buttonRow.setAlignmentX(Component.LEFT_ALIGNMENT);
      circleGuidancePrimaryButton = createGuidanceButton("是", THEME_COLOR, Color.WHITE, true);
      circleGuidancePrimaryButton.addActionListener(e -> onCircleGuidancePrimary());
      circleGuidanceSecondaryButton = createGuidanceButton("否", Color.WHITE, THEME_COLOR, false);
      circleGuidanceSecondaryButton.addActionListener(e -> onCircleGuidanceSecondary());
      buttonRow.add(circleGuidancePrimaryButton);
      buttonRow.add(Box.createRigidArea(new Dimension(8, 0)));
      buttonRow.add(circleGuidanceSecondaryButton);
      circleGuidancePanel.add(circleGuidanceLabel);
      circleGuidancePanel.add(Box.createRigidArea(new Dimension(0, 8)));
      circleGuidancePanel.add(buttonRow);
      circleGuidancePanel.setVisible(false);
   }
   private void ensureCircleGuidanceDialog() {
      if (circleGuidancePanel == null) {
         return;
      }
      if (circleGuidanceDialog != null) {
         if (circleGuidancePanel.getParent() != circleGuidanceDialog.getContentPane()) {
            detachCircleGuidancePanel();
            circleGuidanceDialog.getContentPane().removeAll();
            circleGuidanceDialog.getContentPane().add(circleGuidancePanel, BorderLayout.CENTER);
            circleGuidanceDialog.pack();
         }
         return;
      }
      Window owner = SwingUtilities.getWindowAncestor(this);
      circleGuidanceDialog = new JDialog(owner, "绘制提示", Dialog.ModalityType.MODELESS);
      circleGuidanceDialog.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
      circleGuidanceDialog.setResizable(false);
      circleGuidanceDialog.setAlwaysOnTop(true);
      if (owner != null && circleDialogOwnerAdapter == null) {
         circleDialogOwnerAdapter = new ComponentAdapter() {
            @Override
            public void componentMoved(ComponentEvent e) {
               positionCircleGuidanceDialog();
            }
            @Override
            public void componentResized(ComponentEvent e) {
               positionCircleGuidanceDialog();
            }
         };
         owner.addComponentListener(circleDialogOwnerAdapter);
      }
      detachCircleGuidancePanel();
      circleGuidanceDialog.getContentPane().setLayout(new BorderLayout());
      circleGuidanceDialog.getContentPane().add(circleGuidancePanel, BorderLayout.CENTER);
      circleGuidanceDialog.pack();
   }
   private void detachCircleGuidancePanel() {
      if (circleGuidancePanel == null) {
         return;
      }
      Container parent = circleGuidancePanel.getParent();
      if (parent != null) {
         parent.remove(circleGuidancePanel);
         parent.revalidate();
         parent.repaint();
      }
   }
   private void positionCircleGuidanceDialog() {
      if (circleGuidanceDialog == null) {
         return;
      }
      Window owner = SwingUtilities.getWindowAncestor(this);
      if (owner == null || !owner.isShowing()) {
         return;
      }
      try {
         Point ownerLocation = owner.getLocationOnScreen();
         int x = ownerLocation.x + owner.getWidth() - circleGuidanceDialog.getWidth() - 30;
         int y = ownerLocation.y + owner.getHeight() - circleGuidanceDialog.getHeight() - 40;
         x = Math.max(ownerLocation.x, x);
         y = Math.max(ownerLocation.y, y);
         circleGuidanceDialog.setLocation(x, y);
      } catch (IllegalComponentStateException ex) {
         // Owner not yet displayable; skip positioning
      }
   }
   private JButton createGuidanceButton(String text, Color bg, Color fg, boolean filled) {
      JButton button = new JButton(text);
      button.setFont(new Font("微软雅黑", Font.BOLD, 12));
      button.setForeground(fg);
      button.setBackground(bg);
      button.setOpaque(true);
      button.setFocusPainted(false);
      button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      button.setAlignmentX(Component.LEFT_ALIGNMENT);
      if (filled) {
         button.setBorder(BorderFactory.createEmptyBorder(6, 14, 6, 14));
      } else {
         button.setBackground(Color.WHITE);
         button.setOpaque(true);
         button.setBorder(BorderFactory.createCompoundBorder(
               BorderFactory.createLineBorder(THEME_COLOR, 1),
               BorderFactory.createEmptyBorder(5, 12, 5, 12)));
      }
      button.addMouseListener(new MouseAdapter() {
         @Override
         public void mouseEntered(MouseEvent e) {
            if (!button.isEnabled()) {
               return;
            }
            if (filled) {
               button.setBackground(THEME_HOVER_COLOR);
            } else {
               button.setBackground(new Color(245, 245, 245));
            }
         }
         @Override
         public void mouseExited(MouseEvent e) {
            if (!button.isEnabled()) {
               return;
            }
            if (filled) {
               button.setBackground(bg);
            } else {
               button.setBackground(Color.WHITE);
            }
         }
      });
      return button;
   }
   private void onCircleGuidancePrimary() {
      if (circleGuidanceStep >= 1 && circleGuidanceStep <= 3) {
         if (!recordCircleSamplePoint()) {
            return;
         }
         if (circleGuidanceStep == 3) {
            showCircleGuidanceStep(4);
         } else {
            showCircleGuidanceStep(circleGuidanceStep + 1);
         }
      } else if (circleGuidanceStep == 4) {
         handleCircleCompletion();
      }
   }
   private void onCircleGuidanceSecondary() {
      if (circleGuidanceStep == 1 || circleGuidanceStep == 2 || circleGuidanceStep == 3) {
         handleCircleAbort(null);
      } else if (circleGuidanceStep == 4) {
         restartCircleCapture();
      }
   }
   private void hideCircleGuidancePanel() {
      circleGuidanceStep = 0;
      if (circleGuidancePanel != null) {
         circleGuidancePanel.setVisible(false);
      }
      if (circleGuidanceDialog != null) {
         circleGuidanceDialog.setVisible(false);
      }
      if (!circleDialogMode) {
         rebuildFloatingButtonColumn();
      }
   }
   private void handleCircleAbort(String message) {
      // Return the wizard to step 2 without committing any captured points
      hideEndDrawingButton();
      addzhangaiwu.abortCircleDrawingAndReturn(message);
   }
   private void handleCircleCompletion() {
      hideCircleGuidancePanel();
      clearCircleGuidanceArtifacts();
      if (endDrawingCallback != null) {
         endDrawingCallback.run();
      } else {
         addzhangaiwu.finishDrawingSession();
      }
   }
   private void prepareCircleGuidanceState() {
      clearCircleGuidanceArtifacts();
      circleBaseLatLon = resolveCircleBaseLatLon();
      startCircleDataMonitor();
   }
   private void clearCircleGuidanceArtifacts() {
      stopCircleDataMonitor();
      circleCapturedPoints.clear();
      circleBaseLatLon = null;
      lastCapturedCoordinate = null;
      if (mapRenderer != null) {
         mapRenderer.clearCircleCaptureOverlay();
         mapRenderer.clearCircleSampleMarkers();
      }
   }
   private void restartCircleCapture() {
      clearCircleGuidanceArtifacts();
      synchronized (Coordinate.coordinates) {
         Coordinate.coordinates.clear();
      }
      Coordinate.setStartSaveGngga(true);
      circleBaseLatLon = resolveCircleBaseLatLon();
      showCircleGuidanceStep(1);
      startCircleDataMonitor();
   }
   private boolean recordCircleSamplePoint() {
      Coordinate latest = getLatestCoordinate();
      if (latest == null) {
         JOptionPane.showMessageDialog(this, "未获取到当前位置坐标,请稍后重试。", "提示", JOptionPane.WARNING_MESSAGE);
         return false;
      }
      double[] base = ensureCircleBaseLatLon();
      if (base == null) {
         JOptionPane.showMessageDialog(this, "基准站坐标无效,请先在基准站管理中完成设置。", "提示", JOptionPane.WARNING_MESSAGE);
         return false;
      }
      double lat = parseDMToDecimal(latest.getLatitude(), latest.getLatDirection());
      double lon = parseDMToDecimal(latest.getLongitude(), latest.getLonDirection());
      if (!Double.isFinite(lat) || !Double.isFinite(lon)) {
         JOptionPane.showMessageDialog(this, "采集点坐标无效,请重新采集。", "提示", JOptionPane.WARNING_MESSAGE);
         return false;
      }
      double[] local = convertLatLonToLocal(lat, lon, base[0], base[1]);
      circleCapturedPoints.add(local);
      if (mapRenderer != null) {
         mapRenderer.updateCircleSampleMarkers(circleCapturedPoints);
      }
      lastCapturedCoordinate = latest;
      refreshCircleGuidanceButtonAvailability();
      if (circleCapturedPoints.size() >= 3) {
         CircleSolution solution = fitCircleFromPoints(circleCapturedPoints);
         if (solution == null) {
            circleCapturedPoints.remove(circleCapturedPoints.size() - 1);
            JOptionPane.showMessageDialog(this, "无法根据当前三个点生成圆,请重新采集点。", "提示", JOptionPane.WARNING_MESSAGE);
            restartCircleCapture();
            return false;
         }
         if (mapRenderer != null) {
            mapRenderer.showCircleCaptureOverlay(solution.centerX, solution.centerY, solution.radius, circleCapturedPoints);
         }
      }
      return true;
   }
   private void startCircleDataMonitor() {
      if (circleDataMonitor == null) {
         circleDataMonitor = new Timer(600, e -> refreshCircleGuidanceButtonAvailability());
         circleDataMonitor.setRepeats(true);
      }
      if (!circleDataMonitor.isRunning()) {
         circleDataMonitor.start();
      }
      refreshCircleGuidanceButtonAvailability();
   }
   private void stopCircleDataMonitor() {
      if (circleDataMonitor != null) {
         circleDataMonitor.stop();
      }
   }
   private void refreshCircleGuidanceButtonAvailability() {
      if (circleGuidancePrimaryButton == null) {
         return;
      }
      boolean shouldEnable = circleGuidanceStep >= 1 && circleGuidanceStep <= 3
            ? isCircleDataAvailable()
                  : true;
      applyCirclePrimaryButtonState(shouldEnable);
   }
   private boolean isCircleDataAvailable() {
      Coordinate latest = getLatestCoordinate();
      if (latest == null) {
         return false;
      }
      // 检查是否有新的坐标(与上次采集的不同)
      if (lastCapturedCoordinate != null && latest == lastCapturedCoordinate) {
         return false;
      }
      // 检查定位状态是否为4(固定解)
      // 当选择割草机绘制圆形障碍物时,需要检查设备编号和定位状态
      Device device = Device.getGecaoji();
      if (device == null) {
         return false;
      }
      String positioningStatus = device.getPositioningStatus();
      if (positioningStatus == null || !"4".equals(positioningStatus.trim())) {
         return false;
      }
      // 检查设备编号是否匹配割草机编号
      String mowerId = Setsys.getPropertyValue("mowerId");
      String deviceId = device.getMowerNumber();
      if (mowerId != null && !mowerId.trim().isEmpty()) {
         if (deviceId == null || !mowerId.trim().equals(deviceId.trim())) {
            return false;
         }
      }
      return true;
   }
   private void applyCirclePrimaryButtonState(boolean enabled) {
      if (circleGuidancePrimaryButton == null) {
         return;
      }
      if (circleGuidanceStep >= 4) {
         enabled = true;
      }
      circleGuidancePrimaryButton.setEnabled(enabled);
      if (enabled) {
         circleGuidancePrimaryButton.setBackground(THEME_COLOR);
         circleGuidancePrimaryButton.setForeground(Color.WHITE);
         circleGuidancePrimaryButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
      } else {
         circleGuidancePrimaryButton.setBackground(new Color(200, 200, 200));
         circleGuidancePrimaryButton.setForeground(new Color(120, 120, 120));
         circleGuidancePrimaryButton.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
      }
   }
   private Coordinate getLatestCoordinate() {
      synchronized (Coordinate.coordinates) {
         int size = Coordinate.coordinates.size();
         if (size == 0) {
            return null;
         }
         return Coordinate.coordinates.get(size - 1);
      }
   }
   private double[] ensureCircleBaseLatLon() {
      if (circleBaseLatLon == null) {
         circleBaseLatLon = resolveCircleBaseLatLon();
      }
      return circleBaseLatLon;
   }
   private double[] resolveCircleBaseLatLon() {
      String coords = null;
      if (baseStation == null) {
         baseStation = new BaseStation();
      }
      baseStation.load();
      coords = baseStation.getInstallationCoordinates();
      if (!isMeaningfulValue(coords)) {
         String landNumber = Dikuaiguanli.getCurrentWorkLandNumber();
         if (isMeaningfulValue(landNumber)) {
            Dikuai current = Dikuai.getDikuai(landNumber);
            if (current != null) {
               coords = current.getBaseStationCoordinates();
            }
         }
         if (!isMeaningfulValue(coords)) {
            coords = addzhangaiwu.getActiveSessionBaseStation();
         }
         if (!isMeaningfulValue(coords)) {
            return null;
         }
      }
      String[] parts = coords.split(",");
      if (parts.length < 4) {
         return null;
      }
      double baseLat = parseDMToDecimal(parts[0], parts[1]);
      double baseLon = parseDMToDecimal(parts[2], parts[3]);
      if (!Double.isFinite(baseLat) || !Double.isFinite(baseLon)) {
         return null;
      }
      return new double[]{baseLat, baseLon};
   }
   private double parseDMToDecimal(String dmm, String direction) {
      if (dmm == null || dmm.trim().isEmpty()) {
         return Double.NaN;
      }
      try {
         String trimmed = dmm.trim();
         int dotIndex = trimmed.indexOf('.');
         if (dotIndex < 2) {
            return Double.NaN;
         }
         int degrees = Integer.parseInt(trimmed.substring(0, dotIndex - 2));
         double minutes = Double.parseDouble(trimmed.substring(dotIndex - 2));
         double decimal = degrees + minutes / 60.0;
         if ("S".equalsIgnoreCase(direction) || "W".equalsIgnoreCase(direction)) {
            decimal = -decimal;
         }
         return decimal;
      } catch (NumberFormatException ex) {
         return Double.NaN;
      }
   }
   private double[] convertLatLonToLocal(double lat, double lon, double baseLat, double baseLon) {
      double deltaLat = lat - baseLat;
      double deltaLon = lon - baseLon;
      double meanLatRad = Math.toRadians((baseLat + lat) / 2.0);
      double eastMeters = deltaLon * METERS_PER_DEGREE_LAT * Math.cos(meanLatRad);
      double northMeters = deltaLat * METERS_PER_DEGREE_LAT;
      return new double[]{eastMeters, northMeters};
   }
   private CircleSolution fitCircleFromPoints(List<double[]> points) {
      if (points == null || points.size() < 3) {
         return null;
      }
      CircleSolution best = null;
      double bestScore = 0.0;
      int n = points.size();
      for (int i = 0; i < n - 2; i++) {
         double[] p1 = points.get(i);
         for (int j = i + 1; j < n - 1; j++) {
            double[] p2 = points.get(j);
            for (int k = j + 1; k < n; k++) {
               double[] p3 = points.get(k);
               CircleSolution candidate = circleFromThreePoints(p1, p2, p3);
               if (candidate == null || candidate.radius <= 0) {
                  continue;
               }
               double minEdge = Math.min(distance(p1, p2), Math.min(distance(p2, p3), distance(p1, p3)));
               if (minEdge > bestScore) {
                  bestScore = minEdge;
                  best = candidate;
               }
            }
         }
      }
      return best;
   }
   private CircleSolution circleFromThreePoints(double[] p1, double[] p2, double[] p3) {
      if (p1 == null || p2 == null || p3 == null) {
         return null;
      }
      double x1 = p1[0];
      double y1 = p1[1];
      double x2 = p2[0];
      double y2 = p2[1];
      double x3 = p3[0];
      double y3 = p3[1];
      double a = x1 * (y2 - y3) - y1 * (x2 - x3) + x2 * y3 - x3 * y2;
      double d = 2.0 * a;
      if (Math.abs(d) < 1e-6) {
         return null;
      }
      double x1Sq = x1 * x1 + y1 * y1;
      double x2Sq = x2 * x2 + y2 * y2;
      double x3Sq = x3 * x3 + y3 * y3;
      double centerX = (x1Sq * (y2 - y3) + x2Sq * (y3 - y1) + x3Sq * (y1 - y2)) / d;
      double centerY = (x1Sq * (x3 - x2) + x2Sq * (x1 - x3) + x3Sq * (x2 - x1)) / d;
      double radius = Math.hypot(centerX - x1, centerY - y1);
      if (!Double.isFinite(centerX) || !Double.isFinite(centerY) || !Double.isFinite(radius)) {
         return null;
      }
      if (radius < 0.05) {
         return null;
      }
      return new CircleSolution(centerX, centerY, radius);
   }
   private double distance(double[] a, double[] b) {
      if (a == null || b == null) {
         return 0.0;
      }
      double dx = a[0] - b[0];
      double dy = a[1] - b[1];
      return Math.hypot(dx, dy);
   }
   private static final class CircleSolution {
      final double centerX;
      final double centerY;
      final double radius;
      CircleSolution(double centerX, double centerY, double radius) {
         this.centerX = centerX;
         this.centerY = centerY;
         this.radius = radius;
      }
   }
   public void hideEndDrawingButton() {
      hideCircleGuidancePanel();
      clearCircleGuidanceArtifacts();
      hideFloatingDrawingControls();
      circleDialogMode = false;
      exitHandheldCaptureInlineUi();
      handheldCaptureActive = false;
      exitDrawingControlMode();
      if (activeBoundaryMode == BoundaryCaptureMode.MOWER) {
         stopMowerBoundaryCapture();
      } else if (activeBoundaryMode == BoundaryCaptureMode.HANDHELD && !handheldCaptureActive) {
         activeBoundaryMode = BoundaryCaptureMode.NONE;
      }
      endDrawingCallback = null;
      visualizationPanel.revalidate();
      visualizationPanel.repaint();
      setHandheldMowerIconActive(false);
   }
   private void showPathPreviewReturnControls() {
      ensureFloatingButtonInfrastructure();
      if (drawingPauseButton != null) {
         drawingPauseButton.setVisible(false);
      }
      if (endDrawingButton != null) {
         endDrawingButton.setVisible(false);
      }
      if (pathPreviewReturnButton == null) {
         pathPreviewReturnButton = createFloatingTextButton("返回");
         pathPreviewReturnButton.setToolTipText("返回新增地块步骤");
         pathPreviewReturnButton.addActionListener(e -> handlePathPreviewReturn());
      }
      pathPreviewReturnButton.setVisible(true);
      if (floatingButtonPanel != null) {
         floatingButtonPanel.setVisible(true);
         if (floatingButtonPanel.getParent() != visualizationPanel) {
            visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
         }
      }
      rebuildFloatingButtonColumn();
   }
   private void hidePathPreviewReturnControls() {
      if (pathPreviewReturnButton != null) {
         pathPreviewReturnButton.setVisible(false);
      }
      rebuildFloatingButtonColumn();
      if (floatingButtonPanel != null && floatingButtonColumn != null
            && floatingButtonColumn.getComponentCount() == 0) {
         floatingButtonPanel.setVisible(false);
      }
   }
   private void handlePathPreviewReturn() {
      Runnable callback = pathPreviewReturnAction;
      exitMowingPathPreview();
      if (callback != null) {
         callback.run();
      }
   }
   public boolean startMowingPathPreview(String landNumber,
         String landName,
         String boundary,
         String obstacles,
         String plannedPath,
         Runnable returnAction) {
      if (mapRenderer == null) {
         return false;
      }
      // 允许没有路径的预览(例如障碍物预览),只要有返回回调即可
      if (!isMeaningfulValue(plannedPath) && returnAction == null) {
         return false;
      }
      if (pathPreviewActive) {
         exitMowingPathPreview();
      }
      exitDrawingControlMode();
      hideCircleGuidancePanel();
      clearCircleGuidanceArtifacts();
      pathPreviewReturnAction = returnAction;
      pathPreviewActive = true;
      mapRenderer.setPathPreviewSizingEnabled(true);
      previewRestoreLandNumber = Dikuaiguanli.getCurrentWorkLandNumber();
      previewRestoreLandName = null;
      if (isMeaningfulValue(previewRestoreLandNumber)) {
         Dikuai existing = Dikuai.getDikuai(previewRestoreLandNumber);
         if (existing != null) {
            previewRestoreLandName = existing.getLandName();
         }
      }
      mapRenderer.setCurrentBoundary(boundary, landNumber, landName);
      mapRenderer.setCurrentObstacles(obstacles, landNumber);
      // 只有在有路径时才设置路径
      if (isMeaningfulValue(plannedPath)) {
         mapRenderer.setCurrentPlannedPath(plannedPath);
      } else {
         mapRenderer.setCurrentPlannedPath(null);
      }
      mapRenderer.clearHandheldBoundaryPreview();
      mapRenderer.setBoundaryPointSizeScale(1.0d);
      mapRenderer.setBoundaryPointsVisible(isMeaningfulValue(boundary));
      // 启用障碍物边界点显示
      mapRenderer.setObstaclePointsVisible(isMeaningfulValue(obstacles));
      String displayName = isMeaningfulValue(landName) ? landName : landNumber;
      updateCurrentAreaName(displayName);
      showPathPreviewReturnControls();
      visualizationPanel.revalidate();
      visualizationPanel.repaint();
      return true;
   }
   public void exitMowingPathPreview() {
      if (!pathPreviewActive) {
         return;
      }
      pathPreviewActive = false;
      if (mapRenderer != null) {
         mapRenderer.setPathPreviewSizingEnabled(false);
      }
      hidePathPreviewReturnControls();
      String restoreNumber = previewRestoreLandNumber;
      String restoreName = previewRestoreLandName;
      previewRestoreLandNumber = null;
      previewRestoreLandName = null;
      pathPreviewReturnAction = null;
      if (restoreNumber != null) {
         Dikuaiguanli.setCurrentWorkLand(restoreNumber, restoreName);
      } else if (mapRenderer != null) {
         mapRenderer.setCurrentBoundary(null, null, null);
         mapRenderer.setCurrentObstacles((String) null, null);
         mapRenderer.setCurrentPlannedPath(null);
         mapRenderer.setBoundaryPointsVisible(false);
         mapRenderer.setBoundaryPointSizeScale(1.0d);
         mapRenderer.clearHandheldBoundaryPreview();
         mapRenderer.resetView();
         updateCurrentAreaName(null);
      }
      visualizationPanel.revalidate();
      visualizationPanel.repaint();
   }
   /**
    * 获取地图渲染器实例
    */
   public MapRenderer getMapRenderer() {
      return mapRenderer;
   }
   public void updateCurrentAreaName(String areaName) {
      if (areaNameLabel == null) {
         return;
      }
      if (areaName == null || areaName.trim().isEmpty()) {
         areaNameLabel.setText("未选择地块");
      } else {
         areaNameLabel.setText(areaName);
      }
   }
   /**
    * 重置地图视图
    */
   public void resetMapView() {
      if (mapRenderer != null) {
         mapRenderer.resetView();
      }
   }
   private void initializeDefaultAreaSelection() {
      Dikuai.initFromProperties();
      String persistedLandNumber = Dikuaiguanli.getPersistedWorkLandNumber();
      if (persistedLandNumber != null) {
         Dikuai stored = Dikuai.getDikuai(persistedLandNumber);
         if (stored != null) {
            Dikuaiguanli.setCurrentWorkLand(persistedLandNumber, stored.getLandName());
            return;
         }
         Dikuaiguanli.setCurrentWorkLand(null, null);
      }
      Map<String, Dikuai> all = Dikuai.getAllDikuai();
      if (all.isEmpty()) {
         Dikuaiguanli.setCurrentWorkLand(null, null);
      } else if (all.size() == 1) {
         Dikuai only = all.values().iterator().next();
         if (only != null) {
            Dikuaiguanli.setCurrentWorkLand(only.getLandNumber(), only.getLandName());
         }
      }
   }
   private void refreshMapForSelectedArea() {
      if (mapRenderer == null) {
         return;
      }
      String currentLandNumber = Dikuaiguanli.getCurrentWorkLandNumber();
      if (isMeaningfulValue(currentLandNumber)) {
         Dikuai current = Dikuai.getDikuai(currentLandNumber);
         String landName = current != null ? current.getLandName() : null;
         Dikuaiguanli.setCurrentWorkLand(currentLandNumber, landName);
         return;
      }
      String labelName = areaNameLabel != null ? areaNameLabel.getText() : null;
      if (!isMeaningfulAreaName(labelName)) {
         Dikuaiguanli.setCurrentWorkLand(null, null);
         return;
      }
      Map<String, Dikuai> all = Dikuai.getAllDikuai();
      for (Dikuai dikuai : all.values()) {
         if (dikuai == null) {
            continue;
         }
         String candidateName = dikuai.getLandName();
         if (candidateName != null && candidateName.trim().equals(labelName.trim())) {
            Dikuaiguanli.setCurrentWorkLand(dikuai.getLandNumber(), candidateName);
            return;
         }
      }
      Dikuaiguanli.setCurrentWorkLand(null, null);
   }
   private boolean isMeaningfulValue(String value) {
      if (value == null) {
         return false;
      }
      String trimmed = value.trim();
      return !trimmed.isEmpty() && !"-1".equals(trimmed);
   }
   private boolean isMeaningfulAreaName(String value) {
      if (value == null) {
         return false;
      }
      String trimmed = value.trim();
      if (trimmed.isEmpty()) {
         return false;
      }
      return !"未选择地块".equals(trimmed);
   }
    
    // 主题颜色
    private final Color THEME_COLOR = new Color(46, 139, 87);
    private final Color THEME_HOVER_COLOR = new Color(30, 107, 69);
    private final Color BACKGROUND_COLOR = new Color(250, 250, 250);
    private final Color PANEL_BACKGROUND = new Color(255, 255, 255);
    // 组件
    private JPanel headerPanel;
    private JPanel mainContentPanel;
    private JPanel controlPanel;
    private JPanel navigationPanel;
    private JPanel visualizationPanel;
    // 按钮
    private JButton startBtn;
    private JButton pauseBtn;
    private JButton legendBtn;
    private JButton remoteBtn;
    private JButton areaSelectBtn;
    private JButton baseStationBtn;
    // 导航按钮
    private JButton homeNavBtn;
    private JButton areasNavBtn;
    private JButton settingsNavBtn;
    // 状态显示
    private JLabel statusLabel;
    private JLabel areaNameLabel;
    // 当前选中的导航按钮
    private JButton currentNavButton;
    // 对话框引用
    private LegendDialog legendDialog;
    private RemoteControlDialog remoteDialog;
    private AreaSelectionDialog areaDialog;
    private BaseStationDialog baseStationDialog;
    private Sets settingsDialog;
    private BaseStation baseStation;
    // 地图渲染器
    private MapRenderer mapRenderer;
    private static final int FLOAT_ICON_SIZE = 32;
    private JButton endDrawingButton;
    private JButton drawingPauseButton;
    private JPanel floatingButtonPanel;
    private JPanel floatingButtonColumn;
    private Runnable endDrawingCallback;
    private boolean drawingPaused;
    private ImageIcon pauseIcon;
    private ImageIcon pauseActiveIcon;
    private ImageIcon endIcon;
    public Shouye() {
        instance = this;
        baseStation = new BaseStation();
        baseStation.load();
        initializeUI();
        setupEventHandlers();
    }
    public static Shouye getInstance() {
        return instance;
    }
    private void initializeUI() {
        setLayout(new BorderLayout());
        setBackground(BACKGROUND_COLOR);
        // 创建各个面板
        createHeaderPanel();
        createMainContentPanel();
        createControlPanel();
        createNavigationPanel();
        // 添加到主面板
        add(headerPanel, BorderLayout.NORTH);
        add(mainContentPanel, BorderLayout.CENTER);
        add(controlPanel, BorderLayout.SOUTH);
        // 初始化地图渲染器
        mapRenderer = new MapRenderer(visualizationPanel);
        // 初始化对话框引用为null,延迟创建
        legendDialog = null;
        remoteDialog = null;
        areaDialog = null;
        baseStationDialog = null;
        settingsDialog = null;
        // 设置默认状态
        setNavigationActive(homeNavBtn);
        initializeDefaultAreaSelection();
    }
    private void createHeaderPanel() {
        headerPanel = new JPanel(new BorderLayout());
        headerPanel.setBackground(PANEL_BACKGROUND);
        headerPanel.setBorder(BorderFactory.createEmptyBorder(15, 20, 15, 20));
        headerPanel.setPreferredSize(new Dimension(0, 80));
        // 左侧信息区域
        JPanel leftInfoPanel = new JPanel(new GridLayout(2, 1));
        leftInfoPanel.setBackground(PANEL_BACKGROUND);
        areaNameLabel = new JLabel("未选择地块");
        areaNameLabel.setFont(new Font("微软雅黑", Font.BOLD, 18));
        areaNameLabel.setForeground(Color.BLACK);
        areaNameLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        areaNameLabel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                Dikuaiguanli.showDikuaiManagement(Shouye.this, null);
            }
            @Override
            public void mouseEntered(MouseEvent e) {
                areaNameLabel.setForeground(THEME_COLOR);
            }
            @Override
            public void mouseExited(MouseEvent e) {
                areaNameLabel.setForeground(Color.BLACK);
            }
        });
        statusLabel = new JLabel("待机");
        statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        statusLabel.setForeground(Color.GRAY);
        leftInfoPanel.add(areaNameLabel);
        leftInfoPanel.add(statusLabel);
        // 右侧操作区域
        JPanel rightActionPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
        rightActionPanel.setBackground(PANEL_BACKGROUND);
        JButton notificationBtn = createIconButton("🔔", 40);
        // 修改设置按钮:使用图片替代Unicode图标
        JButton settingsBtn = new JButton();
        try {
            ImageIcon settingsIcon = new ImageIcon("image/sets.png");
            // 调整图片大小以适应按钮
            Image scaledImage = settingsIcon.getImage().getScaledInstance(30, 30, Image.SCALE_SMOOTH);
            settingsBtn.setIcon(new ImageIcon(scaledImage));
        } catch (Exception e) {
            // 如果图片加载失败,使用默认文本
            settingsBtn.setText("设置");
            System.err.println("无法加载设置图标: " + e.getMessage());
        }
        settingsBtn.setPreferredSize(new Dimension(40, 40));
        settingsBtn.setBackground(PANEL_BACKGROUND);
        settingsBtn.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        settingsBtn.setFocusPainted(false);
        // 添加悬停效果
        settingsBtn.addMouseListener(new MouseAdapter() {
            public void mouseEntered(MouseEvent e) {
                settingsBtn.setBackground(new Color(240, 240, 240));
            }
            public void mouseExited(MouseEvent e) {
                settingsBtn.setBackground(PANEL_BACKGROUND);
            }
        });
        // 添加设置按钮事件
        settingsBtn.addActionListener(e -> showSettingsDialog());
        rightActionPanel.add(notificationBtn);
        rightActionPanel.add(settingsBtn);
        headerPanel.add(leftInfoPanel, BorderLayout.WEST);
        headerPanel.add(rightActionPanel, BorderLayout.EAST);
    }
    private void createMainContentPanel() {
        mainContentPanel = new JPanel(new BorderLayout());
        mainContentPanel.setBackground(BACKGROUND_COLOR);
        // 可视化区域 - 使用MapRenderer进行绘制
        visualizationPanel = new JPanel() {
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                // 委托给MapRenderer进行绘制
                if (mapRenderer != null) {
                    mapRenderer.renderMap(g);
                }
            }
        };
        visualizationPanel.setLayout(new BorderLayout());
        // 创建功能按钮面板(放在左上角)
        JPanel functionButtonsPanel = new JPanel();
        functionButtonsPanel.setLayout(new BoxLayout(functionButtonsPanel, BoxLayout.Y_AXIS));
        functionButtonsPanel.setOpaque(false);
        functionButtonsPanel.setBorder(BorderFactory.createEmptyBorder(20, 5, 0, 0)); // 左边距改为5像素
        legendBtn = createFunctionButton("图例", "📖");
        baseStationBtn = createFunctionButton("基准站", "📡"); // 调整到第二位
        areaSelectBtn = createFunctionButton("地块", "🌿");    // 调整到第三位
        remoteBtn = createFunctionButton("遥控", "🎮");        // 调整到最后
        functionButtonsPanel.add(legendBtn);
        functionButtonsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
        functionButtonsPanel.add(baseStationBtn);
        functionButtonsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
        functionButtonsPanel.add(areaSelectBtn);
        functionButtonsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
        functionButtonsPanel.add(remoteBtn);
        visualizationPanel.add(functionButtonsPanel, BorderLayout.WEST);
        mainContentPanel.add(visualizationPanel, BorderLayout.CENTER);
    }
    private void createControlPanel() {
        controlPanel = new JPanel(new BorderLayout());
        controlPanel.setBackground(PANEL_BACKGROUND);
        controlPanel.setBorder(BorderFactory.createEmptyBorder(15, 20, 15, 20));
        controlPanel.setPreferredSize(new Dimension(0, 100));
        JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 20, 0));
        buttonPanel.setBackground(PANEL_BACKGROUND);
        startBtn = createControlButton("开始", THEME_COLOR);
        pauseBtn = createControlButton("暂停", Color.ORANGE);
        pauseBtn.setEnabled(false);
        buttonPanel.add(startBtn);
        buttonPanel.add(pauseBtn);
        controlPanel.add(buttonPanel, BorderLayout.CENTER);
    }
    private void createNavigationPanel() {
        navigationPanel = new JPanel(new GridLayout(1, 3));
        navigationPanel.setBackground(PANEL_BACKGROUND);
        navigationPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
        navigationPanel.setPreferredSize(new Dimension(0, 70));
        homeNavBtn = createNavButton("首页", "🏠");
        areasNavBtn = createNavButton("地块", "🌿");
        settingsNavBtn = createNavButton("设置", "⚙️");
        navigationPanel.add(homeNavBtn);
        navigationPanel.add(areasNavBtn);
        navigationPanel.add(settingsNavBtn);
        // 添加到主界面底部
        add(navigationPanel, BorderLayout.SOUTH);
    }
    private JButton createIconButton(String icon, int size) {
        JButton button = new JButton(icon);
        button.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 20));
        button.setPreferredSize(new Dimension(size, size));
        button.setBackground(PANEL_BACKGROUND);
        button.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        button.setFocusPainted(false);
        // 悬停效果
        button.addMouseListener(new MouseAdapter() {
            public void mouseEntered(MouseEvent e) {
                button.setBackground(new Color(240, 240, 240));
            }
            public void mouseExited(MouseEvent e) {
                button.setBackground(PANEL_BACKGROUND);
            }
        });
        return button;
    }
    private JButton createFunctionButton(String text, String icon) {
        JButton button = new JButton("<html><center>" + icon + "<br>" + text + "</center></html>");
        button.setFont(new Font("微软雅黑", Font.PLAIN, 12));
        button.setPreferredSize(new Dimension(80, 70));
        button.setBackground(new Color(0, 0, 0, 0)); // 完全透明背景
        button.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); // 只保留内边距
        button.setFocusPainted(false);
        button.setOpaque(false); // 设置为不透明,确保背景透明
        // 去掉悬停效果
        button.addMouseListener(new MouseAdapter() {
            public void mouseEntered(MouseEvent e) {
                // 悬停时不改变任何样式
            }
            public void mouseExited(MouseEvent e) {
                // 悬停时不改变任何样式
            }
        });
        return button;
    }
    private JButton createControlButton(String text, Color color) {
        JButton button = new JButton(text);
        button.setFont(new Font("微软雅黑", Font.BOLD, 16));
        button.setBackground(color);
        button.setForeground(Color.WHITE);
        button.setBorder(BorderFactory.createEmptyBorder(15, 0, 15, 0));
        button.setFocusPainted(false);
        // 悬停效果
        button.addMouseListener(new MouseAdapter() {
            public void mouseEntered(MouseEvent e) {
                if (button.isEnabled()) {
                    if (color == THEME_COLOR) {
                        button.setBackground(THEME_HOVER_COLOR);
                    } else {
                        button.setBackground(new Color(255, 165, 0));
                    }
                }
            }
            public void mouseExited(MouseEvent e) {
                if (button.isEnabled()) {
                    button.setBackground(color);
                }
            }
        });
        return button;
    }
    private JButton createNavButton(String text, String icon) {
        JButton button = new JButton("<html><center>" + icon + "<br>" + text + "</center></html>");
        button.setFont(new Font("微软雅黑", Font.PLAIN, 12));
        button.setBackground(PANEL_BACKGROUND);
        button.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
        button.setFocusPainted(false);
        // 悬停效果
        button.addMouseListener(new MouseAdapter() {
            public void mouseEntered(MouseEvent e) {
                if (button != currentNavButton) {
                    button.setBackground(new Color(240, 240, 240));
                }
            }
            public void mouseExited(MouseEvent e) {
                if (button != currentNavButton) {
                    button.setBackground(PANEL_BACKGROUND);
                }
            }
        });
        return button;
    }
    private void setNavigationActive(JButton navButton) {
        // 重置所有导航按钮样式
        for (Component comp : navigationPanel.getComponents()) {
            if (comp instanceof JButton) {
                JButton btn = (JButton) comp;
                btn.setBackground(PANEL_BACKGROUND);
                btn.setForeground(Color.BLACK);
            }
        }
        // 设置当前选中按钮样式
        navButton.setBackground(THEME_COLOR);
        navButton.setForeground(Color.WHITE);
        currentNavButton = navButton;
    }
    private void setupEventHandlers() {
        // 导航按钮事件
        homeNavBtn.addActionListener(e -> setNavigationActive(homeNavBtn));
        areasNavBtn.addActionListener(e -> setNavigationActive(areasNavBtn));
        settingsNavBtn.addActionListener(e -> setNavigationActive(settingsNavBtn));
        // 功能按钮事件
        legendBtn.addActionListener(e -> showLegendDialog());
        remoteBtn.addActionListener(e -> showRemoteControlDialog());
        areaSelectBtn.addActionListener(e -> {
            // 点击“地块”直接打开地块管理对话框(若需要可传入特定地块编号)
            Dikuaiguanli.showDikuaiManagement(this, null);
        });
        baseStationBtn.addActionListener(e -> showBaseStationDialog());
        // 控制按钮事件
        startBtn.addActionListener(e -> startMowing());
        pauseBtn.addActionListener(e -> pauseMowing());
    }
    private void showSettingsDialog() {
        if (settingsDialog == null) {
            Window parentWindow = SwingUtilities.getWindowAncestor(this);
            if (parentWindow instanceof JFrame) {
                settingsDialog = new Sets((JFrame) parentWindow, THEME_COLOR);
            } else if (parentWindow instanceof JDialog) {
                settingsDialog = new Sets((JDialog) parentWindow, THEME_COLOR);
            } else {
                // Fallback to a frameless dialog when no parent is available
                settingsDialog = new Sets((JFrame) null, THEME_COLOR);
            }
        }
        settingsDialog.setVisible(true);
    }
    private void showLegendDialog() {
        if (legendDialog == null) {
            Window parentWindow = SwingUtilities.getWindowAncestor(this);
            if (parentWindow != null) {
                legendDialog = new LegendDialog(this, THEME_COLOR);
            } else {
                // 如果没有父窗口,创建无父窗口的对话框
                legendDialog = new LegendDialog((JFrame) null, THEME_COLOR);
            }
        }
        legendDialog.setVisible(true);
    }
    private void showRemoteControlDialog() {
        if (remoteDialog == null) {
            Window parentWindow = SwingUtilities.getWindowAncestor(this);
            if (parentWindow != null) {
                remoteDialog = new RemoteControlDialog(this, THEME_COLOR);
            } else {
                remoteDialog = new RemoteControlDialog((JFrame) null, THEME_COLOR);
            }
        }
        remoteDialog.setVisible(true);
    }
    private void showAreaSelectionDialog() {
        if (areaDialog == null) {
            Window parentWindow = SwingUtilities.getWindowAncestor(this);
            if (parentWindow != null) {
                areaDialog = new AreaSelectionDialog(this, THEME_COLOR, areaNameLabel, statusLabel);
            } else {
                areaDialog = new AreaSelectionDialog((JFrame) null, THEME_COLOR, areaNameLabel, statusLabel);
            }
        }
        areaDialog.setVisible(true);
    }
    private void showBaseStationDialog() {
        if (baseStation == null) {
            baseStation = new BaseStation();
        }
        baseStation.load();
        Component dialogParent = this;
        if (!hasValidBaseStationId()) {
            boolean recorded = promptForBaseStationId(dialogParent);
            if (!recorded) {
                return;
            }
        }
        Device device = new Device();
        device.initFromProperties();
        if (baseStationDialog == null) {
            baseStationDialog = new BaseStationDialog(dialogParent, THEME_COLOR, device, baseStation);
        } else {
            baseStationDialog.refreshData();
        }
        baseStationDialog.setVisible(true);
    }
    private boolean hasValidBaseStationId() {
        if (baseStation == null) {
            return false;
        }
        String deviceId = baseStation.getDeviceId();
        if (deviceId == null) {
            return false;
        }
        String trimmed = deviceId.trim();
        return !trimmed.isEmpty() && !"-1".equals(trimmed);
    }
    private boolean promptForBaseStationId(Component parentComponent) {
        if (baseStation == null) {
            baseStation = new BaseStation();
        }
        while (true) {
            String input = JOptionPane.showInputDialog(parentComponent,
                    "请输入基准站编号", "录入基准站编号", JOptionPane.PLAIN_MESSAGE);
            if (input == null) {
                return false;
            }
            String trimmed = input.trim();
            if (trimmed.isEmpty()) {
                JOptionPane.showMessageDialog(parentComponent,
                        "基准站编号不能为空,请重新输入。", "提示", JOptionPane.WARNING_MESSAGE);
                continue;
            }
            try {
                baseStation.updateByDeviceId(trimmed,
                        baseStation.getInstallationCoordinates(),
                        baseStation.getIotSimCardNumber(),
                        baseStation.getDeviceActivationTime(),
                        baseStation.getDataUpdateTime());
                baseStation.load();
                JOptionPane.showMessageDialog(parentComponent,
                        "基准站编号已保存。", "操作成功", JOptionPane.INFORMATION_MESSAGE);
                return true;
            } catch (IllegalArgumentException ex) {
                JOptionPane.showMessageDialog(parentComponent,
                        ex.getMessage(), "输入错误", JOptionPane.ERROR_MESSAGE);
            }
        }
    }
    private void startMowing() {
        statusLabel.setText("作业中");
        startBtn.setEnabled(false);
        pauseBtn.setEnabled(true);
    }
    private void pauseMowing() {
        statusLabel.setText("暂停中");
        startBtn.setEnabled(true);
        pauseBtn.setEnabled(false);
    }
    private JButton createFloatingIconButton() {
        JButton button = new JButton();
        button.setContentAreaFilled(false);
        button.setBorder(BorderFactory.createEmptyBorder());
        button.setFocusPainted(false);
        button.setOpaque(false);
        button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        int size = FLOAT_ICON_SIZE + 8;
        button.setPreferredSize(new Dimension(size, size));
        return button;
    }
    private ImageIcon loadScaledIcon(String path, int width, int height) {
        try {
            ImageIcon icon = new ImageIcon(path);
            if (icon.getIconWidth() <= 0 || icon.getIconHeight() <= 0) {
                return null;
            }
            Image scaled = icon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH);
            return new ImageIcon(scaled);
        } catch (Exception ex) {
            System.err.println("加载图标失败: " + path + " - " + ex.getMessage());
            return null;
        }
    }
    private void ensureFloatingIconsLoaded() {
        if (pauseIcon == null) {
            pauseIcon = loadScaledIcon("image/zanting.png", FLOAT_ICON_SIZE, FLOAT_ICON_SIZE);
        }
        if (pauseActiveIcon == null) {
            pauseActiveIcon = loadScaledIcon("image/zantingzhong.png", FLOAT_ICON_SIZE, FLOAT_ICON_SIZE);
        }
        if (endIcon == null) {
            endIcon = loadScaledIcon("image/end.png", FLOAT_ICON_SIZE, FLOAT_ICON_SIZE);
        }
    }
    private void updatePauseButtonVisual() {
        if (drawingPauseButton == null) {
            return;
        }
        if (drawingPaused) {
            if (pauseActiveIcon != null) {
                drawingPauseButton.setIcon(pauseActiveIcon);
                drawingPauseButton.setText(null);
            } else {
                drawingPauseButton.setText("暂停中");
                drawingPauseButton.setIcon(null);
            }
            drawingPauseButton.setToolTipText("点击恢复绘制");
        } else {
            if (pauseIcon != null) {
                drawingPauseButton.setIcon(pauseIcon);
                drawingPauseButton.setText(null);
            } else {
                drawingPauseButton.setText("暂停");
                drawingPauseButton.setIcon(null);
            }
            drawingPauseButton.setToolTipText("点击暂停绘制");
        }
    }
    private void applyDrawingPauseState(boolean paused, boolean notifyCoordinate) {
        drawingPaused = paused;
        updatePauseButtonVisual();
        if (notifyCoordinate) {
            Coordinate.setStartSaveGngga(!paused);
        }
    }
    private void toggleDrawingPause() {
        applyDrawingPauseState(!drawingPaused, true);
    }
    public void showEndDrawingButton(Runnable callback) {
        ensureFloatingIconsLoaded();
        if (endDrawingButton == null) {
            endDrawingButton = createFloatingIconButton();
            endDrawingButton.addActionListener(e -> {
                if (endDrawingCallback != null) {
                    endDrawingCallback.run();
                }
            });
        }
        if (endIcon != null) {
            endDrawingButton.setIcon(endIcon);
            endDrawingButton.setText(null);
        } else {
            endDrawingButton.setText("结束绘制");
        }
        endDrawingButton.setToolTipText("结束绘制");
        if (drawingPauseButton == null) {
            drawingPauseButton = createFloatingIconButton();
            drawingPauseButton.addActionListener(e -> toggleDrawingPause());
        }
        updatePauseButtonVisual();
        if (floatingButtonPanel == null) {
            floatingButtonPanel = new JPanel(new BorderLayout());
            floatingButtonPanel.setOpaque(false);
            floatingButtonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 20, 20));
            floatingButtonColumn = new JPanel();
            floatingButtonColumn.setOpaque(false);
            floatingButtonColumn.setLayout(new BoxLayout(floatingButtonColumn, BoxLayout.Y_AXIS));
            floatingButtonColumn.add(drawingPauseButton);
            floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
            floatingButtonColumn.add(endDrawingButton);
            floatingButtonPanel.add(floatingButtonColumn, BorderLayout.EAST);
        } else if (floatingButtonColumn != null) {
            floatingButtonColumn.removeAll();
            floatingButtonColumn.add(drawingPauseButton);
            floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
            floatingButtonColumn.add(endDrawingButton);
        }
        endDrawingCallback = callback;
        endDrawingButton.setVisible(true);
        if (drawingPauseButton != null) {
            drawingPauseButton.setVisible(true);
        }
        applyDrawingPauseState(false, false);
        floatingButtonPanel.setVisible(true);
        if (floatingButtonPanel.getParent() != visualizationPanel) {
            visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
        }
        visualizationPanel.revalidate();
        visualizationPanel.repaint();
    }
    public void hideEndDrawingButton() {
        if (endDrawingButton != null) {
            endDrawingButton.setVisible(false);
        }
        if (drawingPauseButton != null) {
            drawingPauseButton.setVisible(false);
        }
        if (floatingButtonPanel != null) {
            floatingButtonPanel.setVisible(false);
        }
        applyDrawingPauseState(false, false);
        endDrawingCallback = null;
        visualizationPanel.revalidate();
        visualizationPanel.repaint();
    }
    /**
     * 获取地图渲染器实例
     */
    public MapRenderer getMapRenderer() {
        return mapRenderer;
    }
    public void updateCurrentAreaName(String areaName) {
        if (areaNameLabel == null) {
            return;
        }
        if (areaName == null || areaName.trim().isEmpty()) {
            areaNameLabel.setText("未选择地块");
        } else {
            areaNameLabel.setText(areaName);
        }
    }
    /**
     * 重置地图视图
     */
    public void resetMapView() {
        if (mapRenderer != null) {
            mapRenderer.resetView();
        }
    }
    private void initializeDefaultAreaSelection() {
        Dikuai.initFromProperties();
        Map<String, Dikuai> all = Dikuai.getAllDikuai();
        if (all.isEmpty()) {
            Dikuaiguanli.setCurrentWorkLand(null, null);
        } else if (all.size() == 1) {
            Dikuai only = all.values().iterator().next();
            if (only != null) {
                Dikuaiguanli.setCurrentWorkLand(only.getLandNumber(), only.getLandName());
            }
        }
    }
    // 测试方法
   // 测试方法
    public static void main(String[] args) {
        JFrame frame = new JFrame("AutoMow - 首页");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);