| | |
| | | import java.util.Properties; |
| | | |
| | | import lujing.Lunjingguihua; |
| | | import lujing.MowingPathGenerationPage; |
| | | import zhangaiwu.AddDikuai; |
| | | import zhangaiwu.Obstacledge; |
| | | import zhuye.MapRenderer; |
| | |
| | | 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); |
| | |
| | | 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; |
| | |
| | | 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); |
| | | } |
| | | |