张世豪
4 天以前 a3b05960fe629e9006b45d61618b01f724e757fd
src/zhuye/Shouye.java
@@ -11,6 +11,8 @@
import java.awt.event.*;
import chuankou.dellmessage;
import chuankou.sendmessage;
import chuankou.SerialPortService;
import dikuai.Dikuai;
import dikuai.Dikuaiguanli;
import dikuai.addzhangaiwu;
@@ -78,6 +80,8 @@
   private JLabel statusLabel;
   private JLabel speedLabel;  // 速度显示标签
   private JLabel areaNameLabel;
   private JLabel drawingBoundaryLabel;  // 正在绘制边界状态标签
   private JLabel navigationPreviewLabel;  // 导航预览模式标签
   
   // 边界警告相关
   private Timer boundaryWarningTimer;  // 边界警告检查定时器
@@ -123,6 +127,8 @@
   private JButton pathPreviewReturnButton;
   private boolean pathPreviewActive;
   private Runnable pathPreviewReturnAction;
   private JButton settingsReturnButton;  // 返回系统设置页面的悬浮按钮
   private JButton saveManualBoundaryButton;  // 保存手动绘制边界的按钮
   private String previewRestoreLandNumber;
   private String previewRestoreLandName;
   private boolean drawingPaused;
@@ -287,6 +293,8 @@
      // 边界检查定时器:每500ms检查一次割草机是否在边界内
      boundaryWarningTimer = new Timer(500, e -> {
         checkMowerBoundaryStatus();
         // 同时更新蓝牙图标状态
         updateBluetoothButtonIcon();
      });
      boundaryWarningTimer.setInitialDelay(0);
      boundaryWarningTimer.start();
@@ -316,7 +324,7 @@
      }
      // 关闭模式时不需要做任何操作,用户已经可以自由移动地图
      
      // 更新工具提示
      // 更新图标显示(重绘以切换图标)
      if (visualizationPanel != null) {
         visualizationPanel.repaint();
      }
@@ -448,6 +456,251 @@
         }
      }
      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();
      }
      // 初始化手动绘制边界模式
      boolean manualBoundaryDrawingEnabled = setsys.isManualBoundaryDrawingMode();
      if (mapRenderer != null) {
         mapRenderer.setManualBoundaryDrawingMode(manualBoundaryDrawingEnabled);
      }
      // 更新返回设置按钮的显示状态
      updateSettingsReturnButtonVisibility();
   }
   /**
    * 更新返回系统设置按钮的显示状态
    * 当手动绘制边界模式、显示边界距离或开启测量模式任一开启时显示
    */
   public void updateSettingsReturnButtonVisibility() {
      Setsys setsys = new Setsys();
      setsys.initializeFromProperties();
      boolean manualBoundaryDrawingEnabled = setsys.isManualBoundaryDrawingMode();
      boolean shouldShow = manualBoundaryDrawingEnabled
         || setsys.isBoundaryLengthVisible()
         || setsys.isMeasurementModeEnabled();
      if (shouldShow) {
         showSettingsReturnButton();
         // 如果手动绘制边界模式开启,显示保存按钮
         if (manualBoundaryDrawingEnabled) {
            showSaveManualBoundaryButton();
         } else {
            hideSaveManualBoundaryButton();
         }
      } else {
         hideSettingsReturnButton();
         hideSaveManualBoundaryButton();
      }
   }
   /**
    * 显示返回系统设置按钮
    */
   private void showSettingsReturnButton() {
      ensureFloatingButtonInfrastructure();
      if (settingsReturnButton == null) {
         settingsReturnButton = createFloatingTextButton("返回");
         settingsReturnButton.setToolTipText("返回系统设置");
         settingsReturnButton.addActionListener(e -> {
            // 关闭所有相关模式
            Setsys setsys = new Setsys();
            setsys.initializeFromProperties();
            boolean modeChanged = false;
            // 关闭手动绘制边界模式
            if (setsys.isManualBoundaryDrawingMode()) {
               setsys.setManualBoundaryDrawingMode(false);
               setsys.updateProperty("manualBoundaryDrawingMode", "false");
               // 清空手动绘制的边界点
               if (mapRenderer != null) {
                  mapRenderer.clearManualBoundaryPoints();
               }
               modeChanged = true;
            }
            // 关闭显示边界距离
            if (setsys.isBoundaryLengthVisible()) {
               setsys.setBoundaryLengthVisible(false);
               setsys.updateProperty("boundaryLengthVisible", "false");
               if (mapRenderer != null) {
                  mapRenderer.setBoundaryLengthVisible(false);
               }
               modeChanged = true;
            }
            // 关闭测量模式
            if (setsys.isMeasurementModeEnabled()) {
               setsys.setMeasurementModeEnabled(false);
               setsys.updateProperty("measurementModeEnabled", "false");
               if (mapRenderer != null) {
                  mapRenderer.setMeasurementMode(false);
               }
               celiangmoshi.stop();
               modeChanged = true;
            }
            // 如果关闭了任何模式,立即隐藏返回按钮并刷新界面
            if (modeChanged) {
               // 立即隐藏返回按钮
               if (settingsReturnButton != null) {
                  settingsReturnButton.setVisible(false);
               }
               // 更新按钮列(移除返回按钮)
               rebuildFloatingButtonColumn();
               // 如果所有按钮都隐藏了,隐藏悬浮按钮面板
               if (floatingButtonPanel != null && floatingButtonColumn != null
                     && floatingButtonColumn.getComponentCount() == 0) {
                  floatingButtonPanel.setVisible(false);
               }
               // 刷新界面
               if (visualizationPanel != null) {
                  visualizationPanel.revalidate();
                  visualizationPanel.repaint();
               }
            }
            // 更新返回按钮显示状态(确保状态同步)
            updateSettingsReturnButtonVisibility();
            // 打开系统设置页面
            showSettingsDialog();
         });
      }
      settingsReturnButton.setVisible(true);
      // 隐藏绘制相关的按钮(暂停、结束绘制)
      if (drawingPauseButton != null) {
         drawingPauseButton.setVisible(false);
      }
      if (endDrawingButton != null) {
         endDrawingButton.setVisible(false);
      }
      if (floatingButtonPanel != null) {
         floatingButtonPanel.setVisible(true);
         if (floatingButtonPanel.getParent() != visualizationPanel) {
            visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
         }
      }
      rebuildFloatingButtonColumn();
   }
   /**
    * 隐藏返回系统设置按钮
    */
   private void hideSettingsReturnButton() {
      if (settingsReturnButton != null) {
         settingsReturnButton.setVisible(false);
      }
      rebuildFloatingButtonColumn();
      if (floatingButtonPanel != null && floatingButtonColumn != null
            && floatingButtonColumn.getComponentCount() == 0) {
         floatingButtonPanel.setVisible(false);
      }
   }
   /**
    * 显示保存手动绘制边界按钮
    */
   private void showSaveManualBoundaryButton() {
      ensureFloatingButtonInfrastructure();
      if (saveManualBoundaryButton == null) {
         saveManualBoundaryButton = createFloatingTextButton("保存");
         saveManualBoundaryButton.setToolTipText("保存手动绘制的边界");
         saveManualBoundaryButton.addActionListener(e -> saveManualBoundary());
      }
      saveManualBoundaryButton.setVisible(true);
      if (floatingButtonPanel != null) {
         floatingButtonPanel.setVisible(true);
         if (floatingButtonPanel.getParent() != visualizationPanel) {
            visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
         }
      }
      rebuildFloatingButtonColumn();
   }
   /**
    * 隐藏保存手动绘制边界按钮
    */
   private void hideSaveManualBoundaryButton() {
      if (saveManualBoundaryButton != null) {
         saveManualBoundaryButton.setVisible(false);
      }
      rebuildFloatingButtonColumn();
      if (floatingButtonPanel != null && floatingButtonColumn != null
            && floatingButtonColumn.getComponentCount() == 0) {
         floatingButtonPanel.setVisible(false);
      }
   }
   /**
    * 保存手动绘制的边界到文件
    */
   private void saveManualBoundary() {
      if (mapRenderer == null) {
         JOptionPane.showMessageDialog(this, "地图渲染器未初始化", "错误", JOptionPane.ERROR_MESSAGE);
         return;
      }
      List<Point2D.Double> points = mapRenderer.getManualBoundaryPoints();
      if (points == null || points.isEmpty()) {
         JOptionPane.showMessageDialog(this, "没有可保存的边界点,请先在地图上点击绘制边界", "提示", JOptionPane.WARNING_MESSAGE);
         return;
      }
      // 构建坐标字符串:x1,y1;x2,y2;...;xn,yn(单位:米,精确到小数点后2位)
      StringBuilder coordinates = new StringBuilder();
      for (int i = 0; i < points.size(); i++) {
         Point2D.Double point = points.get(i);
         if (i > 0) {
            coordinates.append(";");
         }
         coordinates.append(String.format(Locale.US, "%.2f,%.2f", point.x, point.y));
      }
      // 保存到 properties 文件
      try {
         java.util.Properties props = new java.util.Properties();
         java.io.File file = new java.io.File("shoudongbianjie.properties");
         // 如果文件存在,先加载现有内容
         if (file.exists()) {
            try (java.io.FileInputStream input = new java.io.FileInputStream(file)) {
               props.load(input);
            }
         }
         // 保存坐标
         props.setProperty("boundaryCoordinates", coordinates.toString());
         props.setProperty("pointCount", String.valueOf(points.size()));
         // 写回文件
         try (java.io.FileOutputStream output = new java.io.FileOutputStream(file)) {
            props.store(output, "手动绘制边界坐标 - 格式: x1,y1;x2,y2;...;xn,yn (单位:米,精确到小数点后2位)");
         }
         JOptionPane.showMessageDialog(this,
            String.format("边界已保存成功!\n共 %d 个点", points.size()),
            "保存成功",
            JOptionPane.INFORMATION_MESSAGE);
      } catch (Exception ex) {
         ex.printStackTrace();
         JOptionPane.showMessageDialog(this,
            "保存失败: " + ex.getMessage(),
            "错误",
            JOptionPane.ERROR_MESSAGE);
      }
   }
   private void createHeaderPanel() {
@@ -494,14 +747,28 @@
      // 添加速度显示标签
      speedLabel = new JLabel("");
      speedLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
      speedLabel.setForeground(Color.GRAY);
      speedLabel.setVisible(false);  // 默认隐藏
   speedLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
   speedLabel.setForeground(Color.GRAY);
   speedLabel.setVisible(false);  // 默认隐藏
   // 正在绘制边界状态标签
   drawingBoundaryLabel = new JLabel("正在绘制边界");
   drawingBoundaryLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
   drawingBoundaryLabel.setForeground(new Color(46, 139, 87));
   drawingBoundaryLabel.setVisible(false);  // 默认隐藏
   // 导航预览模式标签
   navigationPreviewLabel = new JLabel("当前导航预览模式");
   navigationPreviewLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
   navigationPreviewLabel.setForeground(new Color(46, 139, 87));
   navigationPreviewLabel.setVisible(false);  // 默认隐藏
   // 将状态与速度放在同一行,显示在地块名称下面一行
   JPanel statusRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
   statusRow.setOpaque(false);
   statusRow.add(statusLabel);
   statusRow.add(drawingBoundaryLabel);
   statusRow.add(navigationPreviewLabel);
   statusRow.add(speedLabel);
   // 左对齐标签与状态行,确保它们在 BoxLayout 中靠左显示
@@ -560,14 +827,16 @@
      // 可视化区域 - 使用MapRenderer进行绘制
      visualizationPanel = new JPanel() {
         private ImageIcon gecaojiIcon = null;
         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像素
            gecaojiIcon = loadScaledIcon("image/gecaoji.png", GECAOJI_ICON_SIZE, GECAOJI_ICON_SIZE);
            gecaojiIcon1 = loadScaledIcon("image/gecaojishijiao1.png", GECAOJI_ICON_SIZE, GECAOJI_ICON_SIZE);
            gecaojiIcon2 = loadScaledIcon("image/gecaojishijiao2.png", GECAOJI_ICON_SIZE, GECAOJI_ICON_SIZE);
         }
         
         /**
@@ -603,11 +872,15 @@
            if (isMowerOutsideBoundary && warningIconVisible) {
               // 绘制红色三角形警告图标(带叹号)
               drawWarningIcon(g, GECAOJI_ICON_X, GECAOJI_ICON_Y, GECAOJI_ICON_SIZE);
            } else if (gecaojiIcon != null) {
               // 绘制正常的割草机图标
               // 水平方向与速度指示器对齐(x=37)
               // 垂直方向与卫星状态图标对齐(y=10,速度指示器面板顶部边距10像素,使图标中心对齐)
               g.drawImage(gecaojiIcon.getImage(), GECAOJI_ICON_X, GECAOJI_ICON_Y, null);
            } 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);
               }
            }
         }
         
@@ -957,12 +1230,14 @@
         }
      });
      ensureBluetoothIconsLoaded();
      bluetoothConnected = Bluelink.isConnected();
      ImageIcon initialIcon = bluetoothConnected ? bluetoothLinkedIcon : bluetoothIcon;
      // 根据串口连接状态显示图标
      SerialPortService service = sendmessage.getActiveService();
      boolean serialConnected = (service != null && service.isOpen());
      ImageIcon initialIcon = serialConnected ? bluetoothLinkedIcon : bluetoothIcon;
      if (initialIcon != null) {
         button.setIcon(initialIcon);
      } else {
         button.setText(bluetoothConnected ? "已连" : "蓝牙");
         button.setText(serialConnected ? "已连" : "蓝牙");
      }
      return button;
   }
@@ -1129,7 +1404,7 @@
         if (parentWindow != null) {
            // 使用 yaokong 包中的 RemoteControlDialog 实现
            remoteDialog = new yaokong.RemoteControlDialog(this, THEME_COLOR, speedLabel);
         } else {
         } else {/*  */
            remoteDialog = new yaokong.RemoteControlDialog((JFrame) null, THEME_COLOR, speedLabel);
         }
      }
@@ -1862,13 +2137,15 @@
         return;
      }
      ensureBluetoothIconsLoaded();
      bluetoothConnected = Bluelink.isConnected();
      ImageIcon icon = bluetoothConnected ? bluetoothLinkedIcon : bluetoothIcon;
      // 根据串口连接状态显示图标
      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(bluetoothConnected ? "已连" : "蓝牙");
         bluetoothBtn.setText(serialConnected ? "已连" : "蓝牙");
      }
   }
@@ -2766,6 +3043,16 @@
      circleDialogMode = false;
      hideCircleGuidancePanel();
      enterDrawingControlMode();
      // 隐藏返回设置按钮(如果显示绘制按钮,则不应该显示返回按钮)
      if (settingsReturnButton != null) {
         settingsReturnButton.setVisible(false);
      }
      // 显示"正在绘制边界"提示
      if (drawingBoundaryLabel != null) {
         drawingBoundaryLabel.setVisible(true);
      }
      boolean enableCircleGuidance = drawingShape != null
            && "circle".equalsIgnoreCase(drawingShape.trim());
@@ -2875,6 +3162,20 @@
         floatingButtonColumn.add(pathPreviewReturnButton);
         added = true;
      }
      if (saveManualBoundaryButton != null && saveManualBoundaryButton.isVisible()) {
         if (added) {
            floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
         }
         floatingButtonColumn.add(saveManualBoundaryButton);
         added = true;
      }
      if (settingsReturnButton != null && settingsReturnButton.isVisible()) {
         if (added) {
            floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
         }
         floatingButtonColumn.add(settingsReturnButton);
         added = true;
      }
      floatingButtonColumn.revalidate();
      floatingButtonColumn.repaint();
   }
@@ -3241,7 +3542,34 @@
      if (latest == null) {
         return false;
      }
      return lastCapturedCoordinate == null || latest != lastCapturedCoordinate;
      // 检查是否有新的坐标(与上次采集的不同)
      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) {
@@ -3443,6 +3771,12 @@
         activeBoundaryMode = BoundaryCaptureMode.NONE;
      }
      endDrawingCallback = null;
      // 隐藏"正在绘制边界"提示
      if (drawingBoundaryLabel != null) {
         drawingBoundaryLabel.setVisible(false);
      }
      visualizationPanel.revalidate();
      visualizationPanel.repaint();
      setHandheldMowerIconActive(false);
@@ -3496,7 +3830,11 @@
         String obstacles,
         String plannedPath,
         Runnable returnAction) {
      if (mapRenderer == null || !isMeaningfulValue(plannedPath)) {
      if (mapRenderer == null) {
         return false;
      }
      // 允许没有路径的预览(例如障碍物预览),只要有返回回调即可
      if (!isMeaningfulValue(plannedPath) && returnAction == null) {
         return false;
      }
@@ -3523,10 +3861,17 @@
      mapRenderer.setCurrentBoundary(boundary, landNumber, landName);
      mapRenderer.setCurrentObstacles(obstacles, landNumber);
      mapRenderer.setCurrentPlannedPath(plannedPath);
      // 只有在有路径时才设置路径
      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);
@@ -3577,6 +3922,101 @@
      return mapRenderer;
   }
   /**
    * 获取控制面板(用于导航预览时替换按钮)
    * @return 控制面板
    */
   public JPanel getControlPanel() {
      return controlPanel;
   }
   /**
    * 获取开始按钮(用于导航预览时隐藏)
    * @return 开始按钮
    */
   public JButton getStartButton() {
      return startBtn;
   }
   /**
    * 获取结束按钮(用于导航预览时隐藏)
    * @return 结束按钮
    */
   public JButton getStopButton() {
      return stopBtn;
   }
   /**
    * 设置导航预览模式标签的显示状态
    * @param visible 是否显示
    */
   public void setNavigationPreviewLabelVisible(boolean visible) {
      if (navigationPreviewLabel != null) {
         navigationPreviewLabel.setVisible(visible);
      }
   }
   /**
    * 更新导航预览状态显示
    * @param active 是否处于导航预览模式
    */
   public void updateNavigationPreviewStatus(boolean active) {
      setNavigationPreviewLabelVisible(active);
   }
   /**
    * 更新割草进度显示
    * @param percentage 完成百分比
    * @param completedArea 已完成面积(平方米)
    * @param totalArea 总面积(平方米)
    */
   public void updateMowingProgress(double percentage, double completedArea, double totalArea) {
      if (mowingProgressLabel == null) {
         return;
      }
      if (totalArea <= 0) {
         mowingProgressLabel.setText("--%");
         mowingProgressLabel.setToolTipText("暂无地块面积数据");
         return;
      }
      double percent = Math.max(0.0, Math.min(100.0, percentage));
      mowingProgressLabel.setText(String.format(Locale.US, "%.1f%%", percent));
      mowingProgressLabel.setToolTipText(String.format(Locale.US, "%.1f㎡ / %.1f㎡", completedArea, totalArea));
   }
   /**
    * 更新割草机速度显示
    * @param speed 速度值(单位:km/h)
    */
   public void updateMowerSpeed(double speed) {
      if (mowerSpeedValueLabel == null) {
         return;
      }
      if (speed < 0) {
         mowerSpeedValueLabel.setText("--");
      } else {
         mowerSpeedValueLabel.setText(String.format(Locale.US, "%.1f", speed));
      }
      if (mowerSpeedUnitLabel != null) {
         mowerSpeedUnitLabel.setText("km/h");
      }
   }
   /**
    * 获取可视化面板实例
    */
   public JPanel getVisualizationPanel() {
      return visualizationPanel;
   }
   /**
    * 获取主内容面板实例(用于添加浮动按钮)
    */
   public JPanel getMainContentPanel() {
      return mainContentPanel;
   }
   public void updateCurrentAreaName(String areaName) {
      if (areaNameLabel == null) {
         return;