package lujing; import javax.swing.*; import javax.swing.SwingUtilities; import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; import dikuai.Dikuai; import lujing.Lunjingguihua; import lujing.ObstaclePathPlanner; import org.locationtech.jts.geom.Coordinate; import gecaoji.Device; /** * 生成割草路径页面 * 独立的对话框类,用于生成和编辑割草路径 */ public class MowingPathGenerationPage extends JDialog { private static final long serialVersionUID = 1L; // 尺寸常量 private static final int SCREEN_WIDTH = 400; private static final int SCREEN_HEIGHT = 800; // 颜色常量 private static final Color PRIMARY_COLOR = new Color(46, 139, 87); private static final Color PRIMARY_DARK = new Color(30, 107, 69); private static final Color TEXT_COLOR = new Color(51, 51, 51); private static final Color WHITE = Color.WHITE; private static final Color BORDER_COLOR = new Color(200, 200, 200); private static final Color BACKGROUND_COLOR = new Color(250, 250, 250); // 数据保存回调接口 public interface PathSaveCallback { boolean saveBaseStationCoordinates(Dikuai dikuai, String value); boolean saveBoundaryCoordinates(Dikuai dikuai, String value); boolean saveObstacleCoordinates(Dikuai dikuai, String baseStationValue, String obstacleValue); boolean saveMowingWidth(Dikuai dikuai, String value); boolean savePlannedPath(Dikuai dikuai, String value); } private final Dikuai dikuai; private final PathSaveCallback saveCallback; // UI组件 private JTextField baseStationField; private JTextArea boundaryArea; private JTextArea obstacleArea; private JTextField widthField; private JTextArea pathArea; /** * 构造函数 * @param owner 父窗口 * @param dikuai 地块对象 * @param baseStationValue 基站坐标 * @param boundaryValue 地块边界 * @param obstacleValue 障碍物坐标 * @param widthValue 割草宽度 * @param modeValue 割草模式 * @param initialGeneratedPath 初始生成的路径 * @param saveCallback 保存回调接口 */ public MowingPathGenerationPage(Window owner, Dikuai dikuai, String baseStationValue, String boundaryValue, String obstacleValue, String widthValue, String modeValue, String initialGeneratedPath, PathSaveCallback saveCallback) { super(owner, "路径规划页面", Dialog.ModalityType.APPLICATION_MODAL); this.dikuai = dikuai; this.saveCallback = saveCallback; setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); getContentPane().setLayout(new BorderLayout()); getContentPane().setBackground(BACKGROUND_COLOR); initializeUI(baseStationValue, boundaryValue, obstacleValue, widthValue, modeValue, initialGeneratedPath); pack(); setSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT)); setLocationRelativeTo(owner); } /** * 初始化UI */ private void initializeUI(String baseStationValue, String boundaryValue, String obstacleValue, String widthValue, String modeValue, String initialGeneratedPath) { 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)); // 基站坐标 baseStationField = createInfoTextField(baseStationValue != null ? baseStationValue : "", true); contentPanel.add(createTextFieldSection("基站坐标", baseStationField)); // 地块边界 boundaryArea = createInfoTextArea(boundaryValue != null ? boundaryValue : "", true, 6); contentPanel.add(createTextAreaSection("地块边界", boundaryArea)); // 障碍物坐标 obstacleArea = createInfoTextArea(obstacleValue != null ? obstacleValue : "", true, 6); contentPanel.add(createTextAreaSection("障碍物坐标", obstacleArea)); // 割草宽度 widthField = createInfoTextField(widthValue != null ? widthValue : "", true); contentPanel.add(createTextFieldSection("割草宽度 (厘米)", widthField)); // 割草安全距离(只读显示) String displaySafetyDistance = "未设置"; Device device = Device.getActiveDevice(); if (device != null) { String safetyDistanceValue = device.getMowingSafetyDistance(); if (safetyDistanceValue != null && !"-1".equals(safetyDistanceValue) && !safetyDistanceValue.trim().isEmpty()) { try { double distanceMeters = Double.parseDouble(safetyDistanceValue.trim()); // 如果值大于100,认为是厘米,需要转换为米 if (distanceMeters > 100) { distanceMeters = distanceMeters / 100.0; } displaySafetyDistance = String.format("%.2f米", distanceMeters); } catch (NumberFormatException e) { displaySafetyDistance = "未设置"; } } } contentPanel.add(createInfoValueSection("割草安全距离", displaySafetyDistance)); // 割草模式(只读显示) contentPanel.add(createInfoValueSection("割草模式", formatMowingPatternForDialog(modeValue))); // 割草路径坐标 String existingPath = prepareCoordinateForEditor(dikuai.getPlannedPath()); String pathSeed = initialGeneratedPath != null ? initialGeneratedPath : existingPath; pathArea = createInfoTextArea(pathSeed != null ? pathSeed : "", true, 10); contentPanel.add(createTextAreaSection("割草路径坐标", pathArea)); JScrollPane dialogScrollPane = new JScrollPane(contentPanel); dialogScrollPane.setBorder(BorderFactory.createEmptyBorder()); dialogScrollPane.getVerticalScrollBar().setUnitIncrement(16); add(dialogScrollPane, BorderLayout.CENTER); // 按钮面板 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 12, 12)); buttonPanel.setBackground(BACKGROUND_COLOR); JButton generateBtn = createPrimaryFooterButton("生成割草路径"); JButton previewBtn = createPrimaryFooterButton("预览"); JButton saveBtn = createPrimaryFooterButton("保存路径"); JButton cancelBtn = createPrimaryFooterButton("取消"); generateBtn.addActionListener(e -> generatePath(modeValue)); previewBtn.addActionListener(e -> previewPath()); saveBtn.addActionListener(e -> savePath()); cancelBtn.addActionListener(e -> dispose()); buttonPanel.add(generateBtn); buttonPanel.add(previewBtn); buttonPanel.add(saveBtn); buttonPanel.add(cancelBtn); add(buttonPanel, BorderLayout.SOUTH); } /** * 生成路径 */ private void generatePath(String modeValue) { 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); } } String generated = attemptMowingPathPreview( boundaryArea.getText(), obstacleArea.getText(), sanitizedWidth, modeValue, this, true ); if (generated != null) { pathArea.setText(generated); pathArea.setCaretPosition(0); } } /** * 预览路径 */ private void previewPath() { // 先保存当前路径到地块(临时保存,用于预览) 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(this, "请先生成割草路径", "提示", JOptionPane.INFORMATION_MESSAGE); return; } // 临时保存路径到地块对象(不持久化) if (saveCallback != null) { saveCallback.savePlannedPath(dikuai, pathNormalized); } // 保存当前页面状态,用于返回时恢复 String currentBaseStation = baseStationField.getText(); String currentBoundary = boundaryArea.getText(); String currentObstacle = obstacleArea.getText(); String currentWidth = widthField.getText(); String currentPath = pathArea.getText(); // 获取地块信息 String landNumber = dikuai.getLandNumber(); String landName = dikuai.getLandName(); // 处理边界坐标,确保变量是 effectively final String boundaryInput = normalizeCoordinateInput(boundaryArea.getText()); final String boundary; if (!"-1".equals(boundaryInput)) { String processed = boundaryInput.replace("\r\n", ";") .replace('\r', ';') .replace('\n', ';') .replaceAll(";+", ";") .replaceAll("\\s*;\\s*", ";") .trim(); if (processed.isEmpty()) { boundary = dikuai.getBoundaryCoordinates(); } else { boundary = processed; } } else { boundary = dikuai.getBoundaryCoordinates(); } // 处理障碍物坐标,确保变量是 effectively final String obstaclesInput = normalizeCoordinateInput(obstacleArea.getText()); final String obstacles; if (!"-1".equals(obstaclesInput)) { String processed = obstaclesInput.replace("\r\n", " ") .replace('\r', ' ') .replace('\n', ' ') .replaceAll("\\s{2,}", " ") .trim(); if (processed.isEmpty()) { obstacles = null; } else { obstacles = processed; } } else { obstacles = null; } // 保存最终值到 final 变量,以便在 lambda 中使用 final String finalPathNormalized = pathNormalized; final String finalLandNumber = landNumber; final String finalLandName = landName; // 关闭路径规划页面 setVisible(false); // 打开主页面并显示路径预览 SwingUtilities.invokeLater(() -> { zhuye.Shouye shouye = zhuye.Shouye.getInstance(); if (shouye != null) { // 显示路径预览,并设置返回回调 shouye.startMowingPathPreview( finalLandNumber, finalLandName, boundary, obstacles, finalPathNormalized, () -> { // 返回回调:重新打开路径规划页面 SwingUtilities.invokeLater(() -> { setVisible(true); // 恢复之前的状态 baseStationField.setText(currentBaseStation); boundaryArea.setText(currentBoundary); obstacleArea.setText(currentObstacle); widthField.setText(currentWidth); pathArea.setText(currentPath); }); } ); } else { // 如果主页面不存在,提示用户并重新显示路径规划页面 JOptionPane.showMessageDialog(null, "无法打开主页面进行预览", "提示", JOptionPane.WARNING_MESSAGE); setVisible(true); } }); } /** * 保存路径 */ private void savePath() { 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"; } } 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"; } } String rawWidthInput = widthField.getText() != null ? widthField.getText().trim() : ""; String widthSanitized = sanitizeWidthString(widthField.getText()); if (widthSanitized == null) { String message = rawWidthInput.isEmpty() ? "请先设置割草宽度(厘米)" : "割草宽度格式不正确"; JOptionPane.showMessageDialog(this, message, "提示", JOptionPane.WARNING_MESSAGE); return; } double widthCm; try { widthCm = Double.parseDouble(widthSanitized); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, "割草宽度格式不正确", "提示", JOptionPane.WARNING_MESSAGE); return; } if (widthCm <= 0) { JOptionPane.showMessageDialog(this, "割草宽度必须大于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(this, "请先生成割草路径", "提示", JOptionPane.INFORMATION_MESSAGE); return; } // 调用回调保存数据 if (saveCallback != null) { if (!saveCallback.saveBaseStationCoordinates(dikuai, baseStationNormalized)) { JOptionPane.showMessageDialog(this, "无法保存基站坐标", "错误", JOptionPane.ERROR_MESSAGE); return; } if (!saveCallback.saveBoundaryCoordinates(dikuai, boundaryNormalized)) { JOptionPane.showMessageDialog(this, "无法保存地块边界", "错误", JOptionPane.ERROR_MESSAGE); return; } if (!saveCallback.saveObstacleCoordinates(dikuai, baseStationNormalized, obstacleNormalized)) { JOptionPane.showMessageDialog(this, "无法保存障碍物坐标", "错误", JOptionPane.ERROR_MESSAGE); return; } if (!saveCallback.saveMowingWidth(dikuai, widthNormalized)) { JOptionPane.showMessageDialog(this, "无法保存割草宽度", "错误", JOptionPane.ERROR_MESSAGE); return; } if (!saveCallback.savePlannedPath(dikuai, pathNormalized)) { JOptionPane.showMessageDialog(this, "无法保存割草路径", "错误", JOptionPane.ERROR_MESSAGE); return; } } JOptionPane.showMessageDialog(this, "割草路径已保存", "成功", JOptionPane.INFORMATION_MESSAGE); dispose(); } /** * 尝试生成路径预览 */ private String attemptMowingPathPreview(String boundaryInput, String obstacleInput, String widthCmInput, String modeInput, Component parentComponent, boolean showMessages) { String boundary = sanitizeValueOrNull(boundaryInput); if (boundary == null) { if (showMessages) { JOptionPane.showMessageDialog(parentComponent, "当前地块未设置边界坐标,无法生成路径", "提示", JOptionPane.WARNING_MESSAGE); } return null; } String rawWidth = widthCmInput != null ? widthCmInput.trim() : ""; String widthStr = sanitizeWidthString(widthCmInput); if (widthStr == null) { if (showMessages) { String message = rawWidth.isEmpty() ? "请先设置割草宽度(厘米)" : "割草宽度格式不正确"; JOptionPane.showMessageDialog(parentComponent, message, "提示", JOptionPane.WARNING_MESSAGE); } return null; } double widthCm; try { widthCm = Double.parseDouble(widthStr); } catch (NumberFormatException ex) { if (showMessages) { JOptionPane.showMessageDialog(parentComponent, "割草宽度格式不正确", "提示", JOptionPane.WARNING_MESSAGE); } return null; } if (widthCm <= 0) { if (showMessages) { JOptionPane.showMessageDialog(parentComponent, "割草宽度必须大于0", "提示", JOptionPane.WARNING_MESSAGE); } return null; } double widthMeters = widthCm / 100.0d; String plannerWidth = BigDecimal.valueOf(widthMeters) .setScale(3, RoundingMode.HALF_UP) .stripTrailingZeros() .toPlainString(); // 检查原始输入是否有障碍物(在sanitize之前检查,避免丢失信息) String rawObstacleInput = obstacleInput != null ? obstacleInput.trim() : ""; boolean hasObstacleInput = !rawObstacleInput.isEmpty() && !"-1".equals(rawObstacleInput); String obstacles = sanitizeValueOrNull(obstacleInput); if (obstacles != null) { obstacles = obstacles.replace("\r\n", " ").replace('\r', ' ').replace('\n', ' '); } String mode = normalizeExistingMowingPattern(modeInput); try { // 解析障碍物列表 List> obstacleList = Lunjingguihua.parseObstacles(obstacles); if (obstacleList == null) { obstacleList = new ArrayList<>(); } // 判断是否有有效的障碍物:只有当解析成功且列表不为空时,才认为有障碍物 boolean hasValidObstacles = !obstacleList.isEmpty(); String generated; if (!hasValidObstacles) { // 障碍物坐标不存在或为空时,使用Lunjingguihua类的方法生成路径 generated = Lunjingguihua.generatePathFromStrings( boundary, obstacles != null ? obstacles : "", plannerWidth, null, // safetyDistStr,使用默认值 mode ); } else { // 有有效障碍物时,使用ObstaclePathPlanner处理路径生成 List polygon = Lunjingguihua.parseCoordinates(boundary); if (polygon.size() < 4) { if (showMessages) { JOptionPane.showMessageDialog(parentComponent, "多边形坐标数量不足,至少需要三个点", "错误", JOptionPane.ERROR_MESSAGE); } return null; } // 有障碍物时使用割草宽度的一半 + 0.05米额外安全距离 double safetyDistance = widthMeters / 2.0 + 0.05; ObstaclePathPlanner pathPlanner = new ObstaclePathPlanner( polygon, widthMeters, mode, obstacleList, safetyDistance); List segments = pathPlanner.generate(); generated = Lunjingguihua.formatPathSegments(segments); } String trimmed = generated != null ? generated.trim() : ""; if (trimmed.isEmpty()) { if (showMessages) { JOptionPane.showMessageDialog(parentComponent, "未生成有效的割草路径,请检查地块数据", "提示", JOptionPane.INFORMATION_MESSAGE); } return null; } if (showMessages) { JOptionPane.showMessageDialog(parentComponent, "割草路径已生成", "成功", JOptionPane.INFORMATION_MESSAGE); } return trimmed; } catch (IllegalArgumentException ex) { if (showMessages) { JOptionPane.showMessageDialog(parentComponent, "生成割草路径失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); } } catch (Exception ex) { if (showMessages) { JOptionPane.showMessageDialog(parentComponent, "生成割草路径时发生异常: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); } ex.printStackTrace(); } return null; } // ========== UI辅助方法 ========== private JTextArea createInfoTextArea(String text, boolean editable, int rows) { JTextArea area = new JTextArea(text); area.setEditable(editable); area.setLineWrap(true); area.setWrapStyleWord(true); area.setFont(new Font("微软雅黑", Font.PLAIN, 13)); area.setRows(Math.max(rows, 2)); area.setCaretPosition(0); area.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6)); area.setBackground(editable ? WHITE : new Color(245, 245, 245)); return area; } private JPanel createTextAreaSection(String title, JTextArea textArea) { JPanel section = new JPanel(new BorderLayout(0, 6)); section.setBackground(BACKGROUND_COLOR); section.setAlignmentX(Component.LEFT_ALIGNMENT); // 创建标题面板,包含标题和复制图标 JPanel titlePanel = new JPanel(new BorderLayout()); titlePanel.setBackground(BACKGROUND_COLOR); titlePanel.setOpaque(false); JLabel titleLabel = new JLabel(title); titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 14)); titleLabel.setForeground(TEXT_COLOR); titlePanel.add(titleLabel, BorderLayout.WEST); // 创建复制按钮 JButton copyButton = createCopyButton(title, () -> { String text = textArea.getText(); if (text == null || text.trim().isEmpty()) { JOptionPane.showMessageDialog(this, title + " 未设置", "提示", JOptionPane.INFORMATION_MESSAGE); return; } copyToClipboard(text, title); }); titlePanel.add(copyButton, BorderLayout.EAST); section.add(titlePanel, BorderLayout.NORTH); JScrollPane scrollPane = new JScrollPane(textArea); scrollPane.setBorder(BorderFactory.createLineBorder(BORDER_COLOR)); scrollPane.getVerticalScrollBar().setUnitIncrement(12); section.add(scrollPane, BorderLayout.CENTER); section.setBorder(BorderFactory.createEmptyBorder(4, 0, 12, 0)); return section; } private JTextField createInfoTextField(String text, boolean editable) { JTextField field = new JTextField(text); field.setEditable(editable); field.setFont(new Font("微软雅黑", Font.PLAIN, 13)); field.setForeground(TEXT_COLOR); field.setBackground(editable ? WHITE : new Color(245, 245, 245)); field.setCaretPosition(0); field.setBorder(BorderFactory.createEmptyBorder(4, 6, 4, 6)); field.setFocusable(true); field.setOpaque(true); return field; } private JPanel createTextFieldSection(String title, JTextField textField) { JPanel section = new JPanel(new BorderLayout(0, 6)); section.setBackground(BACKGROUND_COLOR); section.setAlignmentX(Component.LEFT_ALIGNMENT); // 创建标题面板,包含标题和复制图标 JPanel titlePanel = new JPanel(new BorderLayout()); titlePanel.setBackground(BACKGROUND_COLOR); titlePanel.setOpaque(false); JLabel titleLabel = new JLabel(title); titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 14)); titleLabel.setForeground(TEXT_COLOR); titlePanel.add(titleLabel, BorderLayout.WEST); // 创建复制按钮 JButton copyButton = createCopyButton(title, () -> { String text = textField.getText(); if (text == null || text.trim().isEmpty()) { JOptionPane.showMessageDialog(this, title + " 未设置", "提示", JOptionPane.INFORMATION_MESSAGE); return; } copyToClipboard(text, title); }); titlePanel.add(copyButton, BorderLayout.EAST); section.add(titlePanel, BorderLayout.NORTH); JPanel fieldWrapper = new JPanel(new BorderLayout()); fieldWrapper.setBackground(textField.isEditable() ? WHITE : new Color(245, 245, 245)); fieldWrapper.setBorder(BorderFactory.createLineBorder(BORDER_COLOR)); fieldWrapper.add(textField, BorderLayout.CENTER); section.add(fieldWrapper, BorderLayout.CENTER); section.setBorder(BorderFactory.createEmptyBorder(4, 0, 12, 0)); return section; } private JPanel createInfoValueSection(String title, String value) { JPanel section = new JPanel(); section.setLayout(new BoxLayout(section, BoxLayout.X_AXIS)); section.setBackground(BACKGROUND_COLOR); section.setAlignmentX(Component.LEFT_ALIGNMENT); section.setBorder(BorderFactory.createEmptyBorder(4, 0, 4, 0)); JLabel titleLabel = new JLabel(title + ":"); titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 14)); titleLabel.setForeground(TEXT_COLOR); section.add(titleLabel); section.add(Box.createHorizontalStrut(8)); JLabel valueLabel = new JLabel(value); valueLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); valueLabel.setForeground(TEXT_COLOR); section.add(valueLabel); section.add(Box.createHorizontalGlue()); return section; } private JButton createPrimaryFooterButton(String text) { JButton button = new JButton(text); button.setFont(new Font("微软雅黑", Font.PLAIN, 12)); button.setBackground(PRIMARY_COLOR); button.setForeground(WHITE); button.setBorder(BorderFactory.createEmptyBorder(6, 12, 6, 12)); button.setFocusPainted(false); button.setCursor(new Cursor(Cursor.HAND_CURSOR)); button.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseEntered(java.awt.event.MouseEvent e) { button.setBackground(PRIMARY_DARK); } public void mouseExited(java.awt.event.MouseEvent e) { button.setBackground(PRIMARY_COLOR); } }); return button; } // ========== 数据处理辅助方法 ========== private String getDisplayValue(String value, String defaultValue) { if (value == null || value.equals("-1") || value.trim().isEmpty()) { return defaultValue; } return value; } private String prepareCoordinateForEditor(String value) { if (value == null) { return ""; } String trimmed = value.trim(); if (trimmed.isEmpty() || "-1".equals(trimmed)) { return ""; } return trimmed; } private String normalizeCoordinateInput(String input) { if (input == null) { return "-1"; } String trimmed = input.trim(); return trimmed.isEmpty() ? "-1" : trimmed; } private String sanitizeValueOrNull(String input) { if (input == null) { return null; } String trimmed = input.trim(); if (trimmed.isEmpty() || "-1".equals(trimmed)) { return null; } return trimmed; } private String sanitizeWidthString(String input) { if (input == null) { return null; } String trimmed = input.trim(); if (trimmed.isEmpty() || "-1".equals(trimmed)) { return null; } String cleaned = trimmed.replaceAll("[^0-9.+-]", ""); return cleaned.isEmpty() ? null : cleaned; } private String formatWidthForStorage(double widthCm) { return BigDecimal.valueOf(widthCm) .setScale(2, RoundingMode.HALF_UP) .stripTrailingZeros() .toPlainString(); } private String formatMowingPatternForDialog(String patternValue) { String sanitized = sanitizeValueOrNull(patternValue); if (sanitized == null) { return "未设置"; } String normalized = normalizeExistingMowingPattern(sanitized); if ("parallel".equals(normalized)) { return "平行模式 (parallel)"; } if ("spiral".equals(normalized)) { return "螺旋模式 (spiral)"; } return sanitized; } private String normalizeExistingMowingPattern(String patternValue) { if (patternValue == null) { return "parallel"; } String trimmed = patternValue.trim().toLowerCase(); if ("1".equals(trimmed) || "spiral".equals(trimmed)) { return "spiral"; } return "parallel"; } /** * 创建复制按钮 */ private JButton createCopyButton(String title, Runnable copyAction) { JButton copyButton = new JButton(); Font titleFont = new Font("微软雅黑", Font.BOLD, 14); FontMetrics metrics = getFontMetrics(titleFont); int iconSize = metrics.getHeight(); // 使用标题字体高度作为图标大小 // 加载复制图标 ImageIcon copyIcon = null; ImageIcon successIcon = null; try { ImageIcon originalCopyIcon = new ImageIcon("image/fuzhi.png"); Image scaledCopyImage = originalCopyIcon.getImage().getScaledInstance(iconSize, iconSize, Image.SCALE_SMOOTH); copyIcon = new ImageIcon(scaledCopyImage); // 加载成功图标 ImageIcon originalSuccessIcon = new ImageIcon("image/fuzhisucc.png"); Image scaledSuccessImage = originalSuccessIcon.getImage().getScaledInstance(iconSize, iconSize, Image.SCALE_SMOOTH); successIcon = new ImageIcon(scaledSuccessImage); } catch (Exception e) { // 如果图片加载失败,使用文本 copyButton.setText("复制"); copyButton.setFont(new Font("微软雅黑", Font.PLAIN, 12)); System.err.println("无法加载复制图标: " + e.getMessage()); } final ImageIcon finalCopyIcon = copyIcon; final ImageIcon finalSuccessIcon = successIcon; copyButton.setIcon(finalCopyIcon); copyButton.setContentAreaFilled(false); copyButton.setBorder(null); copyButton.setFocusPainted(false); copyButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); copyButton.setToolTipText("复制" + title); // 添加点击事件 copyButton.addActionListener(e -> { copyAction.run(); // 复制成功后切换图标 if (finalSuccessIcon != null) { copyButton.setIcon(finalSuccessIcon); // 1秒后恢复原图标 Timer timer = new Timer(1000, evt -> { copyButton.setIcon(finalCopyIcon); }); timer.setRepeats(false); timer.start(); } }); return copyButton; } /** * 复制文本到剪贴板 */ private void copyToClipboard(String text, String title) { if (text == null || text.trim().isEmpty()) { JOptionPane.showMessageDialog(this, title + " 未设置", "提示", JOptionPane.INFORMATION_MESSAGE); return; } try { StringSelection selection = new StringSelection(text); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); clipboard.setContents(selection, selection); // 去掉成功提示弹窗 } catch (Exception ex) { JOptionPane.showMessageDialog(this, "复制失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); } } }