张世豪
6 天以前 1cf1ecbc75c6d14b40efb3161e7db0b8b64f7de2
src/dikuai/Dikuaiguanli.java
@@ -24,6 +24,7 @@
import java.util.Properties;
import lujing.Lunjingguihua;
import lujing.MowingPathGenerationPage;
import zhangaiwu.AddDikuai;
import zhangaiwu.Obstacledge;
import zhuye.MapRenderer;
@@ -334,8 +335,8 @@
      JButton deleteBtn = createDeleteButton();
      deleteBtn.addActionListener(e -> deleteDikuai(dikuai));
      JButton generatePathBtn = createPrimaryFooterButton("生成割草路径");
      generatePathBtn.addActionListener(e -> generateMowingPath(dikuai));
      JButton generatePathBtn = createPrimaryFooterButton("路径规划");
      generatePathBtn.addActionListener(e -> showPathPlanningPage(dikuai));
      JPanel footerPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
      footerPanel.setBackground(CARD_BACKGROUND);
@@ -828,6 +829,77 @@
      return trimmed;
   }
   /**
    * 显示路径规划页面
    */
   private void showPathPlanningPage(Dikuai dikuai) {
      if (dikuai == null) {
         return;
      }
      Window owner = SwingUtilities.getWindowAncestor(this);
      // 获取地块基本数据
      String baseStationValue = prepareCoordinateForEditor(dikuai.getBaseStationCoordinates());
      String boundaryValue = prepareCoordinateForEditor(dikuai.getBoundaryCoordinates());
      List<Obstacledge.Obstacle> configuredObstacles = getConfiguredObstacles(dikuai);
      String obstacleValue = determineInitialObstacleValue(dikuai, configuredObstacles);
      String widthValue = sanitizeWidthString(dikuai.getMowingWidth());
      if (widthValue != null) {
         try {
            double widthCm = Double.parseDouble(widthValue);
            widthValue = formatWidthForStorage(widthCm);
         } catch (NumberFormatException ignored) {
            // 保持原始字符串,稍后校验提示
         }
      }
      String modeValue = sanitizeValueOrNull(dikuai.getMowingPattern());
      String existingPath = prepareCoordinateForEditor(dikuai.getPlannedPath());
      // 创建保存回调接口实现
      MowingPathGenerationPage.PathSaveCallback callback = new MowingPathGenerationPage.PathSaveCallback() {
         @Override
         public boolean saveBaseStationCoordinates(Dikuai dikuai, String value) {
            return saveFieldAndRefresh(dikuai, "baseStationCoordinates", value);
         }
         @Override
         public boolean saveBoundaryCoordinates(Dikuai dikuai, String value) {
            return saveFieldAndRefresh(dikuai, "boundaryCoordinates", value);
         }
         @Override
         public boolean saveObstacleCoordinates(Dikuai dikuai, String baseStationValue, String obstacleValue) {
            return persistObstaclesForLand(dikuai, baseStationValue, obstacleValue);
         }
         @Override
         public boolean saveMowingWidth(Dikuai dikuai, String value) {
            return saveFieldAndRefresh(dikuai, "mowingWidth", value);
         }
         @Override
         public boolean savePlannedPath(Dikuai dikuai, String value) {
            return saveFieldAndRefresh(dikuai, "plannedPath", value);
         }
      };
      // 显示路径规划页面
      MowingPathGenerationPage dialog = new MowingPathGenerationPage(
         owner,
         dikuai,
         baseStationValue,
         boundaryValue,
         obstacleValue,
         widthValue,
         modeValue,
         existingPath,
         callback
      );
      dialog.setVisible(true);
   }
   private void generateMowingPath(Dikuai dikuai) {
      if (dikuai == null) {
         return;
@@ -866,178 +938,48 @@
      String modeValue,
      String initialGeneratedPath) {
      Window owner = SwingUtilities.getWindowAncestor(this);
      JDialog dialog = new JDialog(owner, "生成割草路径", Dialog.ModalityType.APPLICATION_MODAL);
      dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
      dialog.getContentPane().setLayout(new BorderLayout());
      dialog.getContentPane().setBackground(BACKGROUND_COLOR);
      JPanel contentPanel = new JPanel();
      contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
      contentPanel.setBackground(BACKGROUND_COLOR);
      contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 16, 12, 16));
      String landName = getDisplayValue(dikuai.getLandName(), "未知地块");
      String landNumber = getDisplayValue(dikuai.getLandNumber(), "未知编号");
      JLabel headerLabel = new JLabel(landName + " / " + landNumber);
      headerLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
      headerLabel.setForeground(TEXT_COLOR);
      headerLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
      contentPanel.add(headerLabel);
      contentPanel.add(Box.createVerticalStrut(12));
      JTextField baseStationField = createInfoTextField(baseStationValue != null ? baseStationValue : "", true);
      contentPanel.add(createTextFieldSection("基站坐标", baseStationField));
      JTextArea boundaryArea = createInfoTextArea(boundaryValue != null ? boundaryValue : "", true, 6);
      contentPanel.add(createTextAreaSection("地块边界", boundaryArea));
      JTextArea obstacleArea = createInfoTextArea(obstacleValue != null ? obstacleValue : "", true, 6);
      contentPanel.add(createTextAreaSection("障碍物坐标", obstacleArea));
      JTextField widthField = createInfoTextField(widthValue != null ? widthValue : "", true);
      contentPanel.add(createTextFieldSection("割草宽度 (厘米)", widthField));
      contentPanel.add(createInfoValueSection("割草模式", formatMowingPatternForDialog(modeValue)));
      String existingPath = prepareCoordinateForEditor(dikuai.getPlannedPath());
      String pathSeed = initialGeneratedPath != null ? initialGeneratedPath : existingPath;
      JTextArea pathArea = createInfoTextArea(pathSeed != null ? pathSeed : "", true, 10);
      contentPanel.add(createTextAreaSection("割草路径坐标", pathArea));
      JScrollPane dialogScrollPane = new JScrollPane(contentPanel);
      dialogScrollPane.setBorder(BorderFactory.createEmptyBorder());
      dialogScrollPane.getVerticalScrollBar().setUnitIncrement(16);
      dialog.add(dialogScrollPane, BorderLayout.CENTER);
      JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 12, 12));
      buttonPanel.setBackground(BACKGROUND_COLOR);
      JButton generateBtn = createPrimaryFooterButton("生成割草路径");
      JButton saveBtn = createPrimaryFooterButton("保存路径");
      JButton cancelBtn = createPrimaryFooterButton("取消");
      generateBtn.addActionListener(e -> {
         String sanitizedWidth = sanitizeWidthString(widthField.getText());
         if (sanitizedWidth != null) {
            try {
               double widthCm = Double.parseDouble(sanitizedWidth);
               widthField.setText(formatWidthForStorage(widthCm));
               sanitizedWidth = formatWidthForStorage(widthCm);
            } catch (NumberFormatException ex) {
               widthField.setText(sanitizedWidth);
            }
      // 创建保存回调接口实现
      MowingPathGenerationPage.PathSaveCallback callback = new MowingPathGenerationPage.PathSaveCallback() {
         @Override
         public boolean saveBaseStationCoordinates(Dikuai dikuai, String value) {
            return saveFieldAndRefresh(dikuai, "baseStationCoordinates", value);
         }
         String generated = attemptMowingPathPreview(
            boundaryArea.getText(),
            obstacleArea.getText(),
            sanitizedWidth,
            modeValue,
            dialog,
            true
         );
         if (generated != null) {
            pathArea.setText(generated);
            pathArea.setCaretPosition(0);
         @Override
         public boolean saveBoundaryCoordinates(Dikuai dikuai, String value) {
            return saveFieldAndRefresh(dikuai, "boundaryCoordinates", value);
         }
      });
      saveBtn.addActionListener(e -> {
         String baseStationNormalized = normalizeCoordinateInput(baseStationField.getText());
         String boundaryNormalized = normalizeCoordinateInput(boundaryArea.getText());
         if (!"-1".equals(boundaryNormalized)) {
            boundaryNormalized = boundaryNormalized
               .replace("\r\n", ";")
               .replace('\r', ';')
               .replace('\n', ';')
               .replaceAll(";+", ";")
               .replaceAll("\\s*;\\s*", ";")
               .trim();
            if (boundaryNormalized.isEmpty()) {
               boundaryNormalized = "-1";
            }
         @Override
         public boolean saveObstacleCoordinates(Dikuai dikuai, String baseStationValue, String obstacleValue) {
            return persistObstaclesForLand(dikuai, baseStationValue, obstacleValue);
         }
         String obstacleNormalized = normalizeCoordinateInput(obstacleArea.getText());
         if (!"-1".equals(obstacleNormalized)) {
            obstacleNormalized = obstacleNormalized
               .replace("\r\n", " ")
               .replace('\r', ' ')
               .replace('\n', ' ')
               .replaceAll("\\s{2,}", " ")
               .trim();
            if (obstacleNormalized.isEmpty()) {
               obstacleNormalized = "-1";
            }
         @Override
         public boolean saveMowingWidth(Dikuai dikuai, String value) {
            return saveFieldAndRefresh(dikuai, "mowingWidth", value);
         }
         String rawWidthInput = widthField.getText() != null ? widthField.getText().trim() : "";
         String widthSanitized = sanitizeWidthString(widthField.getText());
         if (widthSanitized == null) {
            String message = rawWidthInput.isEmpty() ? "请先设置割草宽度(厘米)" : "割草宽度格式不正确";
            JOptionPane.showMessageDialog(dialog, message, "提示", JOptionPane.WARNING_MESSAGE);
            return;
         @Override
         public boolean savePlannedPath(Dikuai dikuai, String value) {
            return saveFieldAndRefresh(dikuai, "plannedPath", value);
         }
         double widthCm;
         try {
            widthCm = Double.parseDouble(widthSanitized);
         } catch (NumberFormatException ex) {
            JOptionPane.showMessageDialog(dialog, "割草宽度格式不正确", "提示", JOptionPane.WARNING_MESSAGE);
            return;
         }
         if (widthCm <= 0) {
            JOptionPane.showMessageDialog(dialog, "割草宽度必须大于0", "提示", JOptionPane.WARNING_MESSAGE);
            return;
         }
         String widthNormalized = formatWidthForStorage(widthCm);
         widthField.setText(widthNormalized);
         String pathNormalized = normalizeCoordinateInput(pathArea.getText());
         if (!"-1".equals(pathNormalized)) {
            pathNormalized = pathNormalized
               .replace("\r\n", ";")
               .replace('\r', ';')
               .replace('\n', ';')
               .replaceAll(";+", ";")
               .replaceAll("\\s*;\\s*", ";")
               .trim();
            if (pathNormalized.isEmpty()) {
               pathNormalized = "-1";
            }
         }
         if ("-1".equals(pathNormalized)) {
            JOptionPane.showMessageDialog(dialog, "请先生成割草路径", "提示", JOptionPane.INFORMATION_MESSAGE);
            return;
         }
         if (!saveFieldAndRefresh(dikuai, "baseStationCoordinates", baseStationNormalized)) {
            JOptionPane.showMessageDialog(dialog, "无法保存基站坐标", "错误", JOptionPane.ERROR_MESSAGE);
            return;
         }
         if (!saveFieldAndRefresh(dikuai, "boundaryCoordinates", boundaryNormalized)) {
            JOptionPane.showMessageDialog(dialog, "无法保存地块边界", "错误", JOptionPane.ERROR_MESSAGE);
            return;
         }
         if (!persistObstaclesForLand(dikuai, baseStationNormalized, obstacleNormalized)) {
            JOptionPane.showMessageDialog(dialog, "无法保存障碍物坐标", "错误", JOptionPane.ERROR_MESSAGE);
            return;
         }
         if (!saveFieldAndRefresh(dikuai, "mowingWidth", widthNormalized)) {
            JOptionPane.showMessageDialog(dialog, "无法保存割草宽度", "错误", JOptionPane.ERROR_MESSAGE);
            return;
         }
         if (!saveFieldAndRefresh(dikuai, "plannedPath", pathNormalized)) {
            JOptionPane.showMessageDialog(dialog, "无法保存割草路径", "错误", JOptionPane.ERROR_MESSAGE);
            return;
         }
         JOptionPane.showMessageDialog(dialog, "割草路径已保存", "成功", JOptionPane.INFORMATION_MESSAGE);
         dialog.dispose();
      });
      cancelBtn.addActionListener(e -> dialog.dispose());
      buttonPanel.add(generateBtn);
      buttonPanel.add(saveBtn);
      buttonPanel.add(cancelBtn);
      dialog.add(buttonPanel, BorderLayout.SOUTH);
      dialog.pack();
      dialog.setSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
      dialog.setLocationRelativeTo(owner);
      };
      // 使用新的独立页面类
      MowingPathGenerationPage dialog = new MowingPathGenerationPage(
         owner,
         dikuai,
         baseStationValue,
         boundaryValue,
         obstacleValue,
         widthValue,
         modeValue,
         initialGeneratedPath,
         callback
      );
      dialog.setVisible(true);
   }