package lujing;
|
|
import javax.swing.*;
|
import javax.swing.SwingUtilities;
|
import java.awt.*;
|
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;
|
|
/**
|
* 生成割草路径页面
|
* 独立的对话框类,用于生成和编辑割草路径
|
*/
|
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));
|
|
// 割草模式(只读显示)
|
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<List<Coordinate>> obstacleList = Lunjingguihua.parseObstacles(obstacles);
|
if (obstacleList == null) {
|
obstacleList = new ArrayList<>();
|
}
|
|
// 判断是否有障碍物:只要原始输入有障碍物内容,就使用ObstaclePathPlanner
|
// 即使解析后列表为空,也尝试使用ObstaclePathPlanner(它会处理空障碍物列表的情况)
|
boolean hasObstacles = hasObstacleInput && !obstacleList.isEmpty();
|
|
// 如果原始输入有障碍物但解析失败,给出提示
|
if (hasObstacleInput && obstacleList.isEmpty()) {
|
if (showMessages) {
|
JOptionPane.showMessageDialog(parentComponent,
|
"障碍物坐标格式可能不正确,将尝试生成路径。如果路径不正确,请检查障碍物坐标格式。",
|
"提示", JOptionPane.WARNING_MESSAGE);
|
}
|
// 仍然尝试使用ObstaclePathPlanner,即使障碍物列表为空
|
// 这样至少可以确保使用正确的路径规划器
|
}
|
|
String generated;
|
|
if (!hasObstacles && !hasObstacleInput) {
|
// 完全没有障碍物输入时,使用Lunjingguihua类的方法生成路径
|
generated = Lunjingguihua.generatePathFromStrings(
|
boundary,
|
obstacles != null ? obstacles : "",
|
plannerWidth,
|
mode
|
);
|
} else {
|
// 有障碍物输入时(即使解析失败),使用ObstaclePathPlanner处理路径生成
|
List<Coordinate> polygon = Lunjingguihua.parseCoordinates(boundary);
|
if (polygon.size() < 4) {
|
if (showMessages) {
|
JOptionPane.showMessageDialog(parentComponent, "多边形坐标数量不足,至少需要三个点",
|
"错误", JOptionPane.ERROR_MESSAGE);
|
}
|
return null;
|
}
|
|
// 根据是否有障碍物设置不同的安全距离
|
double safetyDistance;
|
if (!obstacleList.isEmpty()) {
|
// 有障碍物时使用割草宽度的一半 + 0.05米额外安全距离
|
safetyDistance = widthMeters / 2.0 + 0.05;
|
} else {
|
// 障碍物解析失败但输入存在,使用较小的安全距离
|
safetyDistance = 0.01;
|
}
|
|
ObstaclePathPlanner pathPlanner = new ObstaclePathPlanner(
|
polygon, widthMeters, mode, obstacleList, safetyDistance);
|
List<Lunjingguihua.PathSegment> 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);
|
|
JLabel titleLabel = new JLabel(title);
|
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
|
titleLabel.setForeground(TEXT_COLOR);
|
section.add(titleLabel, 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);
|
|
JLabel titleLabel = new JLabel(title);
|
titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
|
titleLabel.setForeground(TEXT_COLOR);
|
section.add(titleLabel, 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";
|
}
|
}
|