package dikuai;
|
|
import javax.swing.*;
|
import java.awt.*;
|
import java.awt.event.*;
|
import java.awt.datatransfer.*;
|
import java.awt.datatransfer.StringSelection;
|
import java.io.File;
|
import java.io.FileInputStream;
|
import java.io.FileOutputStream;
|
import java.io.IOException;
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import ui.UIConfig;
|
import ui.UIUtils;
|
import java.text.SimpleDateFormat;
|
import java.util.Date;
|
import java.util.Map;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.Objects;
|
import java.util.Properties;
|
|
import lujing.Lunjingguihua;
|
import lujing.MowingPathGenerationPage;
|
import zhangaiwu.AddDikuai;
|
import zhangaiwu.Obstacledge;
|
import zhuye.MapRenderer;
|
import zhuye.Shouye;
|
import zhuye.Coordinate;
|
import gecaoji.Device;
|
|
/**
|
* 地块管理面板 - 卡片式布局设计
|
* 改为JPanel以适应主界面嵌入,参考Shouye尺寸
|
*/
|
public class Dikuaiguanli extends JPanel {
|
private static final long serialVersionUID = 1L;
|
private static Dikuaiguanli latestInstance;
|
// 6.5寸竖屏尺寸
|
private final int SCREEN_WIDTH = 400;
|
private final int SCREEN_HEIGHT = 800;
|
|
// 主题颜色
|
private final Color PRIMARY_COLOR = new Color(46, 139, 87);
|
private final Color PRIMARY_DARK = new Color(30, 107, 69);
|
private final Color RED_COLOR = new Color(255, 107, 107);
|
private final Color RED_DARK = new Color(255, 82, 82);
|
private final Color TEXT_COLOR = new Color(51, 51, 51);
|
private final Color LIGHT_TEXT = new Color(119, 119, 119);
|
private final Color WHITE = Color.WHITE;
|
private final Color BORDER_COLOR = new Color(200, 200, 200);
|
private final Color BACKGROUND_COLOR = new Color(250, 250, 250);
|
private final Color CARD_BACKGROUND = new Color(255, 255, 255);
|
private final Color CARD_SHADOW = new Color(220, 220, 220);
|
|
// 组件
|
private JPanel mainPanel;
|
private JPanel cardsPanel;
|
private JScrollPane scrollPane;
|
|
// 按钮
|
private JButton addLandBtn;
|
|
private static String currentWorkLandNumber;
|
private static final String WORK_LAND_KEY = "currentWorkLandNumber";
|
private static final String PROPERTIES_FILE = "set.properties";
|
private static final Map<String, Boolean> boundaryPointVisibility = new HashMap<>();
|
private ImageIcon workSelectedIcon;
|
private ImageIcon workUnselectedIcon;
|
private ImageIcon boundaryVisibleIcon;
|
private ImageIcon boundaryHiddenIcon;
|
private static final int BOUNDARY_TOGGLE_ICON_SIZE = 48;
|
private Map<String, ObstacleSummary> obstacleSummaryCache = Collections.emptyMap();
|
|
public Dikuaiguanli(String landNumber) {
|
latestInstance = this;
|
initializeUI(landNumber);
|
setupEventHandlers();
|
}
|
|
private void copyCoordinatesAction(String title, String coordinates) {
|
if (coordinates == null || coordinates.equals("-1") || coordinates.trim().isEmpty()) {
|
JOptionPane.showMessageDialog(this, title + " 未设置", "提示", JOptionPane.INFORMATION_MESSAGE);
|
return;
|
}
|
|
try {
|
StringSelection selection = new StringSelection(coordinates);
|
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
clipboard.setContents(selection, selection);
|
JOptionPane.showMessageDialog(this, title + " 已复制到剪贴板", "提示", JOptionPane.INFORMATION_MESSAGE);
|
} catch (Exception ex) {
|
JOptionPane.showMessageDialog(this, "复制失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
|
}
|
}
|
|
private void initializeUI(String landNumber) {
|
setLayout(new BorderLayout());
|
setBackground(BACKGROUND_COLOR);
|
// 使用与Shouye相同的6.5寸竖屏尺寸
|
setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
|
|
createMainPanel();
|
|
// 添加到主面板
|
add(mainPanel, BorderLayout.CENTER);
|
|
// 加载地块数据
|
loadDikuaiData();
|
}
|
|
private void createMainPanel() {
|
mainPanel = new JPanel();
|
mainPanel.setLayout(new BorderLayout());
|
mainPanel.setBackground(BACKGROUND_COLOR);
|
mainPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
|
|
// 顶部:新增按钮
|
JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
topPanel.setBackground(BACKGROUND_COLOR);
|
|
addLandBtn = createActionButton("+ 新增地块", PRIMARY_COLOR);
|
addLandBtn.setPreferredSize(new Dimension(120, 40));
|
addLandBtn.setFont(new Font("微软雅黑", Font.BOLD, 14));
|
|
topPanel.add(addLandBtn);
|
|
mainPanel.add(topPanel, BorderLayout.NORTH);
|
|
// 中部:卡片区域
|
cardsPanel = new JPanel();
|
cardsPanel.setLayout(new BoxLayout(cardsPanel, BoxLayout.Y_AXIS));
|
cardsPanel.setBackground(BACKGROUND_COLOR);
|
|
scrollPane = new JScrollPane(cardsPanel);
|
scrollPane.setBorder(BorderFactory.createEmptyBorder());
|
scrollPane.getVerticalScrollBar().setUnitIncrement(16);
|
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
|
mainPanel.add(scrollPane, BorderLayout.CENTER);
|
}
|
|
private void loadDikuaiData() {
|
Dikuai.initFromProperties();
|
// 清空现有卡片
|
cardsPanel.removeAll();
|
|
Map<String, Dikuai> allDikuai = Dikuai.getAllDikuai();
|
|
if (allDikuai.isEmpty()) {
|
obstacleSummaryCache = Collections.emptyMap();
|
// 显示空状态
|
JPanel emptyPanel = createEmptyStatePanel();
|
cardsPanel.add(emptyPanel);
|
setCurrentWorkLand(null, null);
|
} else {
|
obstacleSummaryCache = loadObstacleSummaries();
|
if (allDikuai.size() == 1) {
|
Dikuai onlyDikuai = allDikuai.values().iterator().next();
|
setCurrentWorklandIfNeeded(onlyDikuai);
|
}
|
// 为每个地块创建卡片
|
for (Dikuai dikuai : allDikuai.values()) {
|
JPanel card = createDikuaiCard(dikuai);
|
cardsPanel.add(card);
|
cardsPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
}
|
}
|
|
cardsPanel.revalidate();
|
cardsPanel.repaint();
|
}
|
|
private JPanel createEmptyStatePanel() {
|
JPanel emptyPanel = new JPanel();
|
emptyPanel.setLayout(new BorderLayout());
|
emptyPanel.setBackground(BACKGROUND_COLOR);
|
emptyPanel.setBorder(BorderFactory.createEmptyBorder(50, 0, 0, 0));
|
|
JLabel emptyLabel = new JLabel("暂无地块数据", JLabel.CENTER);
|
emptyLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16));
|
emptyLabel.setForeground(LIGHT_TEXT);
|
|
emptyPanel.add(emptyLabel, BorderLayout.CENTER);
|
|
return emptyPanel;
|
}
|
|
private JPanel createDikuaiCard(Dikuai dikuai) {
|
JPanel card = new JPanel();
|
card.setLayout(new BorderLayout());
|
card.setBackground(CARD_BACKGROUND);
|
card.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(CARD_SHADOW, 1),
|
BorderFactory.createEmptyBorder(15, 15, 15, 15)
|
));
|
card.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
|
|
// 卡片头部:地块名称和删除按钮
|
JPanel headerPanel = new JPanel(new BorderLayout());
|
headerPanel.setBackground(CARD_BACKGROUND);
|
|
// 地块名称
|
String landName = dikuai.getLandName();
|
if (landName == null || landName.equals("-1")) {
|
landName = "未知地块";
|
}
|
JLabel nameLabel = new JLabel(landName);
|
nameLabel.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
nameLabel.setForeground(TEXT_COLOR);
|
|
headerPanel.add(nameLabel, BorderLayout.WEST);
|
|
// 右侧区域:状态文字 + 按钮
|
JPanel rightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
|
rightPanel.setBackground(CARD_BACKGROUND);
|
rightPanel.setOpaque(false);
|
|
// 状态文字标签(根据是否选中显示/隐藏)
|
JLabel statusLabel = new JLabel("已设置为当前地块");
|
statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
|
statusLabel.setForeground(PRIMARY_COLOR);
|
boolean isCurrent = dikuai.getLandNumber() != null && dikuai.getLandNumber().equals(currentWorkLandNumber);
|
statusLabel.setVisible(isCurrent);
|
|
JButton workToggleBtn = createWorkToggleButton(dikuai);
|
|
// 将状态标签和按钮关联,以便在按钮状态变化时更新标签
|
workToggleBtn.putClientProperty("statusLabel", statusLabel);
|
|
rightPanel.add(statusLabel);
|
rightPanel.add(workToggleBtn);
|
|
headerPanel.add(rightPanel, BorderLayout.EAST);
|
|
card.add(headerPanel, BorderLayout.NORTH);
|
|
// 卡片内容:地块信息
|
JPanel contentPanel = new JPanel();
|
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
|
contentPanel.setBackground(CARD_BACKGROUND);
|
contentPanel.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0));
|
|
// 地块编号
|
contentPanel.add(createCardInfoItem("地块编号:", getDisplayValue(dikuai.getLandNumber(), "未知")));
|
contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
|
// 添加时间
|
contentPanel.add(createCardInfoItem("添加时间:", getDisplayValue(dikuai.getCreateTime(), "未知")));
|
contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
|
// 地块面积
|
String landArea = dikuai.getLandArea();
|
if (landArea != null && !landArea.equals("-1")) {
|
landArea += "㎡";
|
} else {
|
landArea = "未知";
|
}
|
contentPanel.add(createCardInfoItem("地块面积:", landArea));
|
contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
|
// 返回点坐标(带修改按钮)
|
contentPanel.add(createCardInfoItemWithButton("返回点坐标:",
|
getDisplayValue(dikuai.getReturnPointCoordinates(), "未设置"),
|
"修改", e -> editReturnPoint(dikuai)));
|
contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
|
// 地块边界坐标(带显示顶点按钮)
|
JPanel boundaryPanel = createBoundaryInfoItem(dikuai);
|
configureInteractiveLabel(getInfoItemTitleLabel(boundaryPanel),
|
() -> editBoundaryCoordinates(dikuai),
|
"点击查看/编辑地块边界坐标");
|
contentPanel.add(boundaryPanel);
|
contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
|
ObstacleSummary obstacleSummary = getObstacleSummaryFromCache(dikuai.getLandNumber());
|
JPanel obstaclePanel = createCardInfoItemWithButton("障碍物:",
|
obstacleSummary.buildDisplayValue(),
|
"新增",
|
e -> addNewObstacle(dikuai));
|
setInfoItemTooltip(obstaclePanel, obstacleSummary.buildTooltip());
|
// 让障碍物标题可点击,打开障碍物管理页面
|
configureInteractiveLabel(getInfoItemTitleLabel(obstaclePanel),
|
() -> showObstacleManagementPage(dikuai),
|
"点击查看/管理障碍物");
|
contentPanel.add(obstaclePanel);
|
contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
|
// 路径坐标(带查看按钮)
|
JPanel pathPanel = createCardInfoItemWithButtonOnly("路径坐标:",
|
"查看", e -> editPlannedPath(dikuai));
|
configureInteractiveLabel(getInfoItemTitleLabel(pathPanel),
|
() -> editPlannedPath(dikuai),
|
"点击查看/编辑路径坐标");
|
contentPanel.add(pathPanel);
|
contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
|
JPanel baseStationPanel = createCardInfoItemWithButtonOnly("基站坐标:",
|
"查看", e -> editBaseStationCoordinates(dikuai));
|
configureInteractiveLabel(getInfoItemTitleLabel(baseStationPanel),
|
() -> editBaseStationCoordinates(dikuai),
|
"点击查看/编辑基站坐标");
|
contentPanel.add(baseStationPanel);
|
contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
|
JPanel boundaryOriginalPanel = createCardInfoItemWithButtonOnly("边界原始坐标:",
|
"查看", e -> editBoundaryOriginalCoordinates(dikuai));
|
configureInteractiveLabel(getInfoItemTitleLabel(boundaryOriginalPanel),
|
() -> editBoundaryOriginalCoordinates(dikuai),
|
"点击查看/编辑边界原始坐标");
|
contentPanel.add(boundaryOriginalPanel);
|
contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
|
JPanel mowingPatternPanel = createCardInfoItem("割草模式:",
|
formatMowingPatternForDisplay(dikuai.getMowingPattern()));
|
configureInteractiveLabel(getInfoItemTitleLabel(mowingPatternPanel),
|
() -> editMowingPattern(dikuai),
|
"点击查看/编辑割草模式");
|
contentPanel.add(mowingPatternPanel);
|
contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
|
// 割草机割刀宽度
|
String mowingBladeWidthValue = dikuai.getMowingBladeWidth();
|
String displayBladeWidth = "未设置";
|
if (mowingBladeWidthValue != null && !"-1".equals(mowingBladeWidthValue) && !mowingBladeWidthValue.trim().isEmpty()) {
|
try {
|
double bladeWidthMeters = Double.parseDouble(mowingBladeWidthValue.trim());
|
double bladeWidthCm = bladeWidthMeters * 100.0;
|
displayBladeWidth = String.format("%.2f厘米", bladeWidthCm);
|
} catch (NumberFormatException e) {
|
displayBladeWidth = "未设置";
|
}
|
}
|
JPanel mowingBladeWidthPanel = createCardInfoItem("割草机割刀宽度:", displayBladeWidth);
|
contentPanel.add(mowingBladeWidthPanel);
|
contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
|
String mowingWidthValue = dikuai.getMowingWidth();
|
String displayWidth = "未设置";
|
if (mowingWidthValue != null && !"-1".equals(mowingWidthValue) && !mowingWidthValue.trim().isEmpty()) {
|
displayWidth = mowingWidthValue + "厘米";
|
}
|
JPanel mowingWidthPanel = createCardInfoItem("割草宽度:", displayWidth);
|
contentPanel.add(mowingWidthPanel);
|
contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
|
// 割草安全距离
|
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 = "未设置";
|
}
|
}
|
}
|
JPanel mowingSafetyDistancePanel = createCardInfoItem("割草安全距离:", displaySafetyDistance);
|
contentPanel.add(mowingSafetyDistancePanel);
|
contentPanel.add(Box.createRigidArea(new Dimension(0, 15)));
|
|
JPanel completedTrackPanel = createCardInfoItemWithButton("已完成割草路径:",
|
getTruncatedValue(dikuai.getMowingTrack(), 12, "未记录"),
|
"查看", e -> showCompletedMowingTrackDialog(dikuai));
|
setInfoItemTooltip(completedTrackPanel, dikuai.getMowingTrack());
|
configureInteractiveLabel(getInfoItemTitleLabel(completedTrackPanel),
|
() -> showCompletedMowingTrackDialog(dikuai),
|
"点击查看完成的割草路径记录");
|
contentPanel.add(completedTrackPanel);
|
|
card.add(contentPanel, BorderLayout.CENTER);
|
|
JButton deleteBtn = createDeleteButton();
|
deleteBtn.addActionListener(e -> deleteDikuai(dikuai));
|
|
JButton generatePathBtn = createPrimaryFooterButton("路径规划");
|
generatePathBtn.addActionListener(e -> showPathPlanningPage(dikuai));
|
|
JButton navigationPreviewBtn = createPrimaryFooterButton("导航预览");
|
navigationPreviewBtn.addActionListener(e -> startNavigationPreview(dikuai));
|
|
JPanel footerPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
footerPanel.setBackground(CARD_BACKGROUND);
|
footerPanel.setBorder(BorderFactory.createEmptyBorder(15, 0, 0, 0));
|
footerPanel.add(generatePathBtn);
|
footerPanel.add(Box.createHorizontalStrut(12));
|
footerPanel.add(navigationPreviewBtn);
|
footerPanel.add(Box.createHorizontalStrut(12));
|
footerPanel.add(deleteBtn);
|
card.add(footerPanel, BorderLayout.SOUTH);
|
|
return card;
|
}
|
|
private JPanel createCardInfoItem(String label, String value) {
|
JPanel itemPanel = new JPanel(new BorderLayout());
|
itemPanel.setBackground(CARD_BACKGROUND);
|
itemPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 20));
|
|
JLabel labelComp = new JLabel(label);
|
labelComp.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
labelComp.setForeground(LIGHT_TEXT);
|
|
JLabel valueComp = new JLabel(value);
|
valueComp.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
valueComp.setForeground(TEXT_COLOR);
|
|
itemPanel.add(labelComp, BorderLayout.WEST);
|
itemPanel.add(valueComp, BorderLayout.EAST);
|
itemPanel.putClientProperty("titleLabel", labelComp);
|
|
return itemPanel;
|
}
|
|
private JPanel createCardInfoItemWithButton(String label, String value, String buttonText, ActionListener listener) {
|
JPanel itemPanel = new JPanel(new BorderLayout());
|
itemPanel.setBackground(CARD_BACKGROUND);
|
// 增加高度以确保按钮完整显示(按钮高度约24-28像素,加上上下边距)
|
itemPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 35));
|
itemPanel.setPreferredSize(new Dimension(Integer.MAX_VALUE, 30));
|
itemPanel.setMinimumSize(new Dimension(0, 28));
|
|
JLabel labelComp = new JLabel(label);
|
labelComp.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
labelComp.setForeground(LIGHT_TEXT);
|
|
JPanel rightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
|
rightPanel.setBackground(CARD_BACKGROUND);
|
// 添加垂直内边距以确保按钮不被裁剪
|
rightPanel.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0));
|
|
JLabel valueComp = new JLabel(value);
|
valueComp.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
valueComp.setForeground(TEXT_COLOR);
|
|
JButton button = createSmallLinkButton(buttonText, listener);
|
|
rightPanel.add(valueComp);
|
rightPanel.add(button);
|
|
itemPanel.add(labelComp, BorderLayout.WEST);
|
itemPanel.add(rightPanel, BorderLayout.CENTER);
|
itemPanel.putClientProperty("valueLabel", valueComp);
|
itemPanel.putClientProperty("titleLabel", labelComp);
|
|
return itemPanel;
|
}
|
|
private JPanel createCardInfoItemWithButtonOnly(String label, String buttonText, ActionListener listener) {
|
JPanel itemPanel = new JPanel(new BorderLayout());
|
itemPanel.setBackground(CARD_BACKGROUND);
|
// 增加高度以确保按钮完整显示(按钮高度约24-28像素,加上上下边距)
|
itemPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 35));
|
itemPanel.setPreferredSize(new Dimension(Integer.MAX_VALUE, 30));
|
itemPanel.setMinimumSize(new Dimension(0, 28));
|
|
JLabel labelComp = new JLabel(label);
|
labelComp.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
labelComp.setForeground(LIGHT_TEXT);
|
|
JPanel rightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
|
rightPanel.setBackground(CARD_BACKGROUND);
|
// 添加垂直内边距以确保按钮不被裁剪
|
rightPanel.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0));
|
|
JButton button = createSmallLinkButton(buttonText, listener);
|
|
rightPanel.add(button);
|
|
itemPanel.add(labelComp, BorderLayout.WEST);
|
itemPanel.add(rightPanel, BorderLayout.CENTER);
|
itemPanel.putClientProperty("titleLabel", labelComp);
|
|
return itemPanel;
|
}
|
|
private JPanel createBoundaryInfoItem(Dikuai dikuai) {
|
JPanel itemPanel = new JPanel(new BorderLayout());
|
itemPanel.setBackground(CARD_BACKGROUND);
|
// 增加高度以确保按钮下边缘完整显示(按钮高度56,加上上下边距)
|
int rowHeight = Math.max(60, BOUNDARY_TOGGLE_ICON_SIZE + 16);
|
Dimension rowDimension = new Dimension(Integer.MAX_VALUE, rowHeight);
|
itemPanel.setMaximumSize(rowDimension);
|
itemPanel.setPreferredSize(rowDimension);
|
itemPanel.setMinimumSize(new Dimension(0, 56));
|
|
JLabel labelComp = new JLabel("地块边界:");
|
labelComp.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
labelComp.setForeground(LIGHT_TEXT);
|
|
// 确保按钮有足够的上下边距,避免下边缘被裁剪
|
int verticalPadding = Math.max(2, (rowHeight - BOUNDARY_TOGGLE_ICON_SIZE) / 2);
|
JPanel rightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
|
rightPanel.setBackground(CARD_BACKGROUND);
|
rightPanel.setBorder(BorderFactory.createEmptyBorder(verticalPadding, 0, verticalPadding, 0));
|
|
// 状态提示文字标签
|
JLabel statusLabel = new JLabel();
|
statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13));
|
statusLabel.setForeground(LIGHT_TEXT);
|
|
JButton toggleButton = createBoundaryToggleButton(dikuai);
|
// 将状态标签和按钮关联,以便在按钮状态变化时更新标签
|
toggleButton.putClientProperty("statusLabel", statusLabel);
|
|
// 初始化状态文字
|
String landNumber = dikuai.getLandNumber();
|
boolean isVisible = boundaryPointVisibility.getOrDefault(landNumber, false);
|
updateBoundaryStatusLabel(statusLabel, isVisible);
|
|
rightPanel.add(statusLabel);
|
rightPanel.add(toggleButton);
|
|
itemPanel.add(labelComp, BorderLayout.WEST);
|
itemPanel.add(rightPanel, BorderLayout.CENTER);
|
itemPanel.putClientProperty("titleLabel", labelComp);
|
|
return itemPanel;
|
}
|
|
private JButton createBoundaryToggleButton(Dikuai dikuai) {
|
JButton button = new JButton();
|
button.setContentAreaFilled(false);
|
button.setBorder(BorderFactory.createEmptyBorder());
|
button.setFocusPainted(false);
|
button.setOpaque(false);
|
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
button.setMargin(new Insets(0, 0, 0, 0));
|
button.setIconTextGap(0);
|
button.setPreferredSize(new Dimension(56, 56));
|
|
String landNumber = dikuai.getLandNumber();
|
boolean isVisible = boundaryPointVisibility.getOrDefault(landNumber, false);
|
updateBoundaryToggleButton(button, isVisible);
|
|
button.addActionListener(e -> toggleBoundaryPointVisualization(dikuai, button));
|
|
return button;
|
}
|
|
private void updateBoundaryToggleButton(JButton button, boolean active) {
|
ensureBoundaryToggleIconsLoaded();
|
ImageIcon icon = active ? boundaryVisibleIcon : boundaryHiddenIcon;
|
if (icon != null) {
|
button.setIcon(icon);
|
button.setText(null);
|
button.setContentAreaFilled(false);
|
button.setOpaque(false);
|
} else {
|
button.setIcon(null);
|
button.setText(active ? "关闭" : "开启");
|
button.setContentAreaFilled(true);
|
button.setBackground(PRIMARY_COLOR);
|
button.setForeground(WHITE);
|
button.setOpaque(true);
|
}
|
button.setToolTipText(active ? "隐藏边界点序号" : "显示边界点序号");
|
|
// 更新状态提示文字
|
Object statusLabelObj = button.getClientProperty("statusLabel");
|
if (statusLabelObj instanceof JLabel) {
|
JLabel statusLabel = (JLabel) statusLabelObj;
|
updateBoundaryStatusLabel(statusLabel, active);
|
}
|
}
|
|
private void updateBoundaryStatusLabel(JLabel statusLabel, boolean active) {
|
if (statusLabel == null) {
|
return;
|
}
|
if (active) {
|
statusLabel.setText("已开启边界点显示");
|
} else {
|
statusLabel.setText("已关闭边界点显示");
|
}
|
}
|
|
private void ensureBoundaryToggleIconsLoaded() {
|
if (boundaryVisibleIcon == null) {
|
boundaryVisibleIcon = loadIcon("image/open.png", BOUNDARY_TOGGLE_ICON_SIZE, BOUNDARY_TOGGLE_ICON_SIZE);
|
}
|
if (boundaryHiddenIcon == null) {
|
boundaryHiddenIcon = loadIcon("image/close.png", BOUNDARY_TOGGLE_ICON_SIZE, BOUNDARY_TOGGLE_ICON_SIZE);
|
}
|
}
|
|
private void toggleBoundaryPointVisualization(Dikuai dikuai, JButton button) {
|
if (dikuai == null) {
|
return;
|
}
|
|
String landNumber = dikuai.getLandNumber();
|
if (landNumber == null || landNumber.trim().isEmpty()) {
|
return;
|
}
|
|
boolean currentState = boundaryPointVisibility.getOrDefault(landNumber, false);
|
boolean desiredState = !currentState;
|
|
if (desiredState) {
|
String boundary = dikuai.getBoundaryCoordinates();
|
if (boundary == null || boundary.trim().isEmpty() || "-1".equals(boundary.trim())) {
|
JOptionPane.showMessageDialog(this, "当前地块暂无边界数据", "提示", JOptionPane.INFORMATION_MESSAGE);
|
desiredState = false;
|
}
|
}
|
|
boundaryPointVisibility.put(landNumber, desiredState);
|
updateBoundaryToggleButton(button, desiredState);
|
|
Shouye shouye = Shouye.getInstance();
|
if (shouye != null) {
|
MapRenderer renderer = shouye.getMapRenderer();
|
if (renderer != null) {
|
boolean isCurrent = currentWorkLandNumber != null && currentWorkLandNumber.equals(landNumber);
|
if (isCurrent) {
|
renderer.setBoundaryPointsVisible(desiredState);
|
renderer.setBoundaryPointSizeScale(desiredState ? 0.5d : 1.0d);
|
}
|
}
|
}
|
}
|
|
private void setInfoItemTooltip(JPanel itemPanel, String rawValue) {
|
if (itemPanel == null || rawValue == null || rawValue.trim().isEmpty() || "-1".equals(rawValue.trim())) {
|
return;
|
}
|
Object valueComp = itemPanel.getClientProperty("valueLabel");
|
if (valueComp instanceof JLabel) {
|
((JLabel) valueComp).setToolTipText(rawValue);
|
}
|
}
|
|
private JLabel getInfoItemTitleLabel(JPanel itemPanel) {
|
if (itemPanel == null) {
|
return null;
|
}
|
Object titleComp = itemPanel.getClientProperty("titleLabel");
|
return titleComp instanceof JLabel ? (JLabel) titleComp : null;
|
}
|
|
private void configureInteractiveLabel(JLabel label, Runnable onClick, String tooltip) {
|
if (label == null || onClick == null) {
|
return;
|
}
|
Color originalColor = label.getForeground();
|
label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
if (tooltip != null && !tooltip.trim().isEmpty()) {
|
label.setToolTipText(tooltip);
|
}
|
label.addMouseListener(new MouseAdapter() {
|
public void mouseClicked(MouseEvent e) {
|
if (SwingUtilities.isLeftMouseButton(e)) {
|
onClick.run();
|
}
|
}
|
|
public void mouseEntered(MouseEvent e) {
|
label.setForeground(PRIMARY_COLOR);
|
}
|
|
public void mouseExited(MouseEvent e) {
|
label.setForeground(originalColor);
|
}
|
});
|
}
|
|
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 promptCoordinateEditing(String title, String initialValue) {
|
JTextArea textArea = new JTextArea(prepareCoordinateForEditor(initialValue));
|
textArea.setLineWrap(true);
|
textArea.setWrapStyleWord(true);
|
textArea.setFont(new Font("微软雅黑", Font.PLAIN, 13));
|
textArea.setCaretPosition(0);
|
textArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
|
JScrollPane scrollPane = new JScrollPane(textArea);
|
scrollPane.setPreferredSize(new Dimension(360, 240));
|
|
Window owner = SwingUtilities.getWindowAncestor(this);
|
JDialog dialog;
|
if (owner instanceof Frame) {
|
dialog = new JDialog((Frame) owner, title, true);
|
} else if (owner instanceof Dialog) {
|
dialog = new JDialog((Dialog) owner, title, true);
|
} else {
|
dialog = new JDialog((Frame) null, title, true);
|
}
|
dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
|
JPanel contentPanel = new JPanel(new BorderLayout());
|
contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
contentPanel.add(scrollPane, BorderLayout.CENTER);
|
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
JButton okButton = new JButton("确定");
|
JButton cancelButton = new JButton("取消");
|
JButton copyButton = new JButton("复制");
|
|
final boolean[] confirmed = new boolean[] {false};
|
final String[] resultHolder = new String[1];
|
|
okButton.addActionListener(e -> {
|
resultHolder[0] = textArea.getText();
|
confirmed[0] = true;
|
dialog.dispose();
|
});
|
|
cancelButton.addActionListener(e -> dialog.dispose());
|
|
copyButton.addActionListener(e -> {
|
String text = textArea.getText();
|
if (text == null) {
|
text = "";
|
}
|
String trimmed = text.trim();
|
if (trimmed.isEmpty() || "-1".equals(trimmed)) {
|
JOptionPane.showMessageDialog(dialog, title + " 未设置", "提示", JOptionPane.INFORMATION_MESSAGE);
|
return;
|
}
|
try {
|
StringSelection selection = new StringSelection(text);
|
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
clipboard.setContents(selection, selection);
|
JOptionPane.showMessageDialog(dialog, title + " 已复制到剪贴板", "提示", JOptionPane.INFORMATION_MESSAGE);
|
} catch (Exception ex) {
|
JOptionPane.showMessageDialog(dialog, "复制失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
|
}
|
});
|
|
buttonPanel.add(okButton);
|
buttonPanel.add(cancelButton);
|
buttonPanel.add(copyButton);
|
|
contentPanel.add(buttonPanel, BorderLayout.SOUTH);
|
dialog.setContentPane(contentPanel);
|
dialog.getRootPane().setDefaultButton(okButton);
|
dialog.pack();
|
dialog.setLocationRelativeTo(this);
|
dialog.setVisible(true);
|
|
return confirmed[0] ? resultHolder[0] : null;
|
}
|
|
private boolean saveFieldAndRefresh(Dikuai dikuai, String fieldName, String value) {
|
if (dikuai == null || fieldName == null || dikuai.getLandNumber() == null) {
|
return false;
|
}
|
if (!Dikuai.updateField(dikuai.getLandNumber(), fieldName, value)) {
|
return false;
|
}
|
Dikuai.updateField(dikuai.getLandNumber(), "updateTime", getCurrentTime());
|
Dikuai.saveToProperties();
|
boolean isCurrent = dikuai.getLandNumber().equals(currentWorkLandNumber);
|
loadDikuaiData();
|
if (isCurrent) {
|
setCurrentWorkLand(dikuai.getLandNumber(), dikuai.getLandName());
|
}
|
return true;
|
}
|
|
private void editBoundaryCoordinates(Dikuai dikuai) {
|
if (dikuai == null) {
|
return;
|
}
|
String edited = promptCoordinateEditing("查看 / 编辑地块边界坐标", dikuai.getBoundaryCoordinates());
|
if (edited == null) {
|
return;
|
}
|
String normalized = normalizeCoordinateInput(edited);
|
if (!saveFieldAndRefresh(dikuai, "boundaryCoordinates", normalized)) {
|
JOptionPane.showMessageDialog(this, "无法更新地块边界坐标", "错误", JOptionPane.ERROR_MESSAGE);
|
return;
|
}
|
String message = "-1".equals(normalized) ? "地块边界坐标已清空" : "地块边界坐标已更新";
|
JOptionPane.showMessageDialog(this, message, "成功", JOptionPane.INFORMATION_MESSAGE);
|
}
|
|
private void editPlannedPath(Dikuai dikuai) {
|
if (dikuai == null) {
|
return;
|
}
|
String edited = promptCoordinateEditing("查看 / 编辑路径坐标", dikuai.getPlannedPath());
|
if (edited == null) {
|
return;
|
}
|
String normalized = normalizeCoordinateInput(edited);
|
if (!saveFieldAndRefresh(dikuai, "plannedPath", normalized)) {
|
JOptionPane.showMessageDialog(this, "无法更新路径坐标", "错误", JOptionPane.ERROR_MESSAGE);
|
return;
|
}
|
String message = "-1".equals(normalized) ? "路径坐标已清空" : "路径坐标已更新";
|
JOptionPane.showMessageDialog(this, message, "成功", JOptionPane.INFORMATION_MESSAGE);
|
}
|
|
private void editBaseStationCoordinates(Dikuai dikuai) {
|
if (dikuai == null) {
|
return;
|
}
|
String edited = promptCoordinateEditing("查看 / 编辑基站坐标", dikuai.getBaseStationCoordinates());
|
if (edited == null) {
|
return;
|
}
|
String normalized = normalizeCoordinateInput(edited);
|
if (!saveFieldAndRefresh(dikuai, "baseStationCoordinates", normalized)) {
|
JOptionPane.showMessageDialog(this, "无法更新基站坐标", "错误", JOptionPane.ERROR_MESSAGE);
|
return;
|
}
|
String message = "-1".equals(normalized) ? "基站坐标已清空" : "基站坐标已更新";
|
JOptionPane.showMessageDialog(this, message, "成功", JOptionPane.INFORMATION_MESSAGE);
|
}
|
|
private void editBoundaryOriginalCoordinates(Dikuai dikuai) {
|
if (dikuai == null) {
|
return;
|
}
|
String edited = promptCoordinateEditing("查看 / 编辑边界原始坐标", dikuai.getBoundaryOriginalCoordinates());
|
if (edited == null) {
|
return;
|
}
|
String normalized = normalizeCoordinateInput(edited);
|
if (!saveFieldAndRefresh(dikuai, "boundaryOriginalCoordinates", normalized)) {
|
JOptionPane.showMessageDialog(this, "无法更新边界原始坐标", "错误", JOptionPane.ERROR_MESSAGE);
|
return;
|
}
|
String message = "-1".equals(normalized) ? "边界原始坐标已清空" : "边界原始坐标已更新";
|
JOptionPane.showMessageDialog(this, message, "成功", JOptionPane.INFORMATION_MESSAGE);
|
}
|
|
private void editMowingPattern(Dikuai dikuai) {
|
if (dikuai == null) {
|
return;
|
}
|
String current = sanitizeValueOrNull(dikuai.getMowingPattern());
|
String normalized = normalizeExistingMowingPattern(current);
|
JRadioButton parallelBtn = new JRadioButton("平行线 (parallel)");
|
JRadioButton spiralBtn = new JRadioButton("螺旋形 (spiral)");
|
|
ButtonGroup group = new ButtonGroup();
|
group.add(parallelBtn);
|
group.add(spiralBtn);
|
|
if ("spiral".equals(normalized)) {
|
spiralBtn.setSelected(true);
|
} else {
|
parallelBtn.setSelected(true);
|
}
|
|
JPanel panel = new JPanel();
|
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
panel.add(new JLabel("请选择割草模式:"));
|
panel.add(Box.createVerticalStrut(8));
|
panel.add(parallelBtn);
|
panel.add(Box.createVerticalStrut(4));
|
panel.add(spiralBtn);
|
|
int option = JOptionPane.showConfirmDialog(this, panel, "编辑割草模式", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
|
if (option != JOptionPane.OK_OPTION) {
|
return;
|
}
|
|
String selectedValue = parallelBtn.isSelected() ? "parallel" : "spiral";
|
if (!saveFieldAndRefresh(dikuai, "mowingPattern", selectedValue)) {
|
JOptionPane.showMessageDialog(this, "无法更新割草模式", "错误", JOptionPane.ERROR_MESSAGE);
|
return;
|
}
|
JOptionPane.showMessageDialog(this, "割草模式已更新", "成功", JOptionPane.INFORMATION_MESSAGE);
|
}
|
|
private String normalizeExistingMowingPattern(String value) {
|
if (value == null) {
|
return "parallel";
|
}
|
String trimmed = value.trim().toLowerCase();
|
if (trimmed.isEmpty() || "-1".equals(trimmed)) {
|
return "parallel";
|
}
|
switch (trimmed) {
|
case "1":
|
case "spiral":
|
case "螺旋":
|
case "螺旋模式":
|
return "spiral";
|
case "0":
|
case "parallel":
|
case "平行":
|
case "平行模式":
|
default:
|
if (trimmed.contains("螺旋")) {
|
return "spiral";
|
}
|
if (trimmed.contains("spiral")) {
|
return "spiral";
|
}
|
if (trimmed.contains("parallel")) {
|
return "parallel";
|
}
|
if (trimmed.contains("平行")) {
|
return "parallel";
|
}
|
return "parallel";
|
}
|
}
|
|
private String sanitizeValueOrNull(String value) {
|
if (value == null) {
|
return null;
|
}
|
String trimmed = value.trim();
|
if (trimmed.isEmpty() || "-1".equals(trimmed)) {
|
return null;
|
}
|
return trimmed;
|
}
|
|
/**
|
* 启动导航预览
|
*/
|
private void startNavigationPreview(Dikuai dikuai) {
|
if (dikuai == null) {
|
return;
|
}
|
|
Window owner = SwingUtilities.getWindowAncestor(this);
|
|
// 获取地块管理对话框,准备在打开导航预览时关闭
|
Window managementWindow = null;
|
if (owner instanceof JDialog) {
|
managementWindow = owner;
|
}
|
|
// 关闭地块管理页面
|
if (managementWindow != null) {
|
managementWindow.dispose();
|
}
|
|
// 启动导航预览
|
daohangyulan.getInstance().startNavigationPreview(dikuai);
|
}
|
|
/**
|
* 显示路径规划页面
|
*/
|
private void showPathPlanningPage(Dikuai dikuai) {
|
if (dikuai == null) {
|
return;
|
}
|
|
Window owner = SwingUtilities.getWindowAncestor(this);
|
|
// 获取地块管理对话框,准备在打开路径规划页面时关闭
|
Window managementWindow = null;
|
if (owner instanceof JDialog) {
|
managementWindow = owner;
|
}
|
|
// 获取地块基本数据
|
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
|
);
|
|
// 关闭地块管理页面
|
if (managementWindow != null) {
|
managementWindow.dispose();
|
}
|
|
dialog.setVisible(true);
|
}
|
|
|
private void generateMowingPath(Dikuai dikuai) {
|
if (dikuai == null) {
|
return;
|
}
|
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 initialGenerated = attemptMowingPathPreview(
|
boundaryValue,
|
obstacleValue,
|
widthValue,
|
modeValue,
|
this,
|
false
|
);
|
showMowingPathDialog(dikuai, baseStationValue, boundaryValue, obstacleValue, widthValue, modeValue, initialGenerated);
|
}
|
|
private void showMowingPathDialog(
|
Dikuai dikuai,
|
String baseStationValue,
|
String boundaryValue,
|
String obstacleValue,
|
String widthValue,
|
String modeValue,
|
String initialGeneratedPath) {
|
Window owner = SwingUtilities.getWindowAncestor(this);
|
|
// 创建保存回调接口实现
|
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,
|
initialGeneratedPath,
|
callback
|
);
|
|
dialog.setVisible(true);
|
}
|
|
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();
|
String obstacles = sanitizeValueOrNull(obstacleInput);
|
if (obstacles != null) {
|
obstacles = obstacles.replace("\r\n", " ").replace('\r', ' ').replace('\n', ' ');
|
}
|
String mode = normalizeExistingMowingPattern(modeInput);
|
try {
|
String generated = Lunjingguihua.generatePathFromStrings(boundary, obstacles, plannerWidth, mode);
|
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);
|
}
|
}
|
return null;
|
}
|
|
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 String formatMowingPatternForDisplay(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 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 List<Obstacledge.Obstacle> getConfiguredObstacles(Dikuai dikuai) {
|
if (dikuai == null) {
|
return Collections.emptyList();
|
}
|
List<Obstacledge.Obstacle> obstacles = loadObstaclesFromConfig(dikuai.getLandNumber());
|
if (obstacles == null) {
|
return Collections.emptyList();
|
}
|
return obstacles;
|
}
|
|
private String determineInitialObstacleValue(Dikuai dikuai, List<Obstacledge.Obstacle> configuredObstacles) {
|
if (configuredObstacles != null && !configuredObstacles.isEmpty()) {
|
String payload = Obstacledge.buildPlannerPayload(configuredObstacles);
|
if (payload != null && !payload.trim().isEmpty()) {
|
return payload;
|
}
|
}
|
return "";
|
}
|
|
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 boolean persistObstaclesForLand(Dikuai dikuai, String baseStationValue, String obstaclePayload) {
|
if (dikuai == null || dikuai.getLandNumber() == null) {
|
return false;
|
}
|
String landNumber = dikuai.getLandNumber().trim();
|
try {
|
File configFile = new File("Obstacledge.properties");
|
Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
|
if (configFile.exists()) {
|
if (!manager.loadFromFile(configFile.getAbsolutePath())) {
|
return false;
|
}
|
}
|
|
Obstacledge.Plot plot = manager.getPlotById(landNumber);
|
if (plot == null) {
|
plot = new Obstacledge.Plot(landNumber);
|
manager.addPlot(plot);
|
}
|
|
applyBaseStationValue(plot, baseStationValue, dikuai.getBaseStationCoordinates());
|
|
List<Obstacledge.Obstacle> obstacles;
|
if (obstaclePayload == null || "-1".equals(obstaclePayload.trim())) {
|
obstacles = new ArrayList<>();
|
} else {
|
obstacles = Obstacledge.parsePlannerPayload(obstaclePayload, landNumber);
|
}
|
plot.setObstacles(obstacles);
|
|
if (!manager.saveToFile(configFile.getAbsolutePath())) {
|
return false;
|
}
|
|
obstacleSummaryCache = loadObstacleSummaries();
|
boolean isCurrent = landNumber.equals(currentWorkLandNumber);
|
loadDikuaiData();
|
if (isCurrent) {
|
setCurrentWorkLand(landNumber, dikuai.getLandName());
|
}
|
return true;
|
} catch (Exception ex) {
|
System.err.println("保存障碍物配置失败: " + ex.getMessage());
|
return false;
|
}
|
}
|
|
private void applyBaseStationValue(Obstacledge.Plot plot, String baseStationValue, String fallbackValue) {
|
if (plot == null) {
|
return;
|
}
|
String sanitized = sanitizeBaseStationValue(baseStationValue);
|
if (sanitized == null) {
|
sanitized = sanitizeBaseStationValue(fallbackValue);
|
}
|
if (sanitized == null) {
|
return;
|
}
|
try {
|
plot.setBaseStationString(sanitized);
|
} catch (Exception ex) {
|
System.err.println("更新障碍物配置中的基站坐标失败: " + ex.getMessage());
|
}
|
}
|
|
private String sanitizeBaseStationValue(String value) {
|
if (value == null) {
|
return null;
|
}
|
String trimmed = value.trim();
|
if (trimmed.isEmpty() || "-1".equals(trimmed)) {
|
return null;
|
}
|
return trimmed.replaceAll("\\s+", "");
|
}
|
|
private String getDisplayValue(String value, String defaultValue) {
|
if (value == null || value.equals("-1") || value.trim().isEmpty()) {
|
return defaultValue;
|
}
|
return value;
|
}
|
|
private String getTruncatedValue(String value, int maxLength, String defaultValue) {
|
if (value == null || value.equals("-1") || value.trim().isEmpty()) {
|
return defaultValue;
|
}
|
|
if (value.length() > maxLength) {
|
return value.substring(0, maxLength) + "...";
|
}
|
return value;
|
}
|
|
/**
|
* 创建类似于链接的小按钮
|
*/
|
private JButton createSmallLinkButton(String text, ActionListener listener) {
|
JButton btn = new JButton(text);
|
btn.setFont(new Font("微软雅黑", Font.PLAIN, 11));
|
btn.setForeground(PRIMARY_COLOR);
|
btn.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(PRIMARY_COLOR, 1, true),
|
BorderFactory.createEmptyBorder(2, 6, 2, 6)
|
));
|
btn.setContentAreaFilled(false);
|
btn.setFocusPainted(false);
|
btn.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
btn.addMouseListener(new MouseAdapter() {
|
public void mouseEntered(MouseEvent e) { btn.setOpaque(true); btn.setBackground(new Color(230, 250, 240)); }
|
public void mouseExited(MouseEvent e) { btn.setOpaque(false); }
|
});
|
if (listener != null) {
|
btn.addActionListener(listener);
|
}
|
return btn;
|
}
|
|
private JButton createSmallButton(String text) {
|
return createSmallLinkButton(text, null);
|
}
|
|
private JButton createSmallButton(String text, Color backgroundColor, Color hoverColor) {
|
// 对于需要不同颜色的按钮,使用实心风格
|
Color baseColor = backgroundColor == null ? PRIMARY_COLOR : backgroundColor;
|
return createStyledButton(text, baseColor, true);
|
}
|
|
private JButton createActionButton(String text, Color color) {
|
return createStyledButton(text, color, true); // 实心风格
|
}
|
|
/**
|
* 创建现代风格按钮 (实心/轮廓)
|
*/
|
private JButton createStyledButton(String text, Color baseColor, boolean filled) {
|
JButton btn = new JButton(text) {
|
@Override
|
protected void paintComponent(Graphics g) {
|
Graphics2D g2 = (Graphics2D) g.create();
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
|
boolean isPressed = getModel().isPressed();
|
boolean isRollover = getModel().isRollover();
|
|
if (filled) {
|
if (isPressed) g2.setColor(baseColor.darker());
|
else if (isRollover) g2.setColor(baseColor.brighter());
|
else g2.setColor(baseColor);
|
g2.fillRoundRect(0, 0, getWidth(), getHeight(), 8, 8);
|
g2.setColor(Color.WHITE);
|
} else {
|
g2.setColor(CARD_BACKGROUND); // 背景
|
g2.fillRoundRect(0, 0, getWidth(), getHeight(), 8, 8);
|
|
if (isPressed) g2.setColor(baseColor.darker());
|
else if (isRollover) g2.setColor(baseColor);
|
else g2.setColor(new Color(200, 200, 200)); // 默认边框灰
|
|
g2.setStroke(new BasicStroke(1.2f));
|
g2.drawRoundRect(0, 0, getWidth()-1, getHeight()-1, 8, 8);
|
g2.setColor(isRollover ? baseColor : TEXT_COLOR);
|
}
|
|
FontMetrics fm = g2.getFontMetrics();
|
int x = (getWidth() - fm.stringWidth(getText())) / 2;
|
int y = (getHeight() - fm.getHeight()) / 2 + fm.getAscent();
|
g2.drawString(getText(), x, y);
|
|
g2.dispose();
|
}
|
};
|
btn.setFocusPainted(false);
|
btn.setContentAreaFilled(false);
|
btn.setBorderPainted(false);
|
btn.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
btn.setFont(new Font("微软雅黑", Font.BOLD, 12));
|
return btn;
|
}
|
|
private JButton createDeleteButton() {
|
JButton button = createStyledButton("删除", RED_COLOR, false); // 轮廓风格
|
ImageIcon deleteIcon = loadIcon("image/delete.png", 16, 16);
|
if (deleteIcon != null) {
|
button.setIcon(deleteIcon);
|
button.setIconTextGap(6);
|
}
|
return button;
|
}
|
|
private JButton createPrimaryFooterButton(String text) {
|
return createStyledButton(text, PRIMARY_COLOR, true); // 实心风格
|
}
|
|
private JButton createWorkToggleButton(Dikuai dikuai) {
|
JButton button = new JButton();
|
button.setContentAreaFilled(false);
|
button.setBorder(BorderFactory.createEmptyBorder());
|
button.setFocusPainted(false);
|
button.setOpaque(false);
|
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
button.setPreferredSize(new Dimension(56, 56));
|
|
boolean isCurrent = dikuai.getLandNumber() != null && dikuai.getLandNumber().equals(currentWorkLandNumber);
|
updateWorkToggleButton(button, isCurrent);
|
|
button.addActionListener(e -> handleWorkToggle(dikuai));
|
|
return button;
|
}
|
|
private void updateWorkToggleButton(JButton button, boolean isCurrent) {
|
ensureWorkIconsLoaded();
|
ImageIcon icon = isCurrent ? workSelectedIcon : workUnselectedIcon;
|
if (icon != null) {
|
button.setIcon(icon);
|
button.setText(null);
|
} else {
|
button.setText(isCurrent ? "当前地块" : "设为当前");
|
}
|
button.setToolTipText(isCurrent ? "取消当前作业地块" : "设为当前作业地块");
|
|
// 更新状态文字标签的显示/隐藏
|
Object statusLabelObj = button.getClientProperty("statusLabel");
|
if (statusLabelObj instanceof JLabel) {
|
JLabel statusLabel = (JLabel) statusLabelObj;
|
statusLabel.setVisible(isCurrent);
|
}
|
}
|
|
private void ensureWorkIconsLoaded() {
|
if (workSelectedIcon == null) {
|
workSelectedIcon = loadIcon("image/open.png", 48, 48);
|
}
|
if (workUnselectedIcon == null) {
|
workUnselectedIcon = loadIcon("image/close.png", 48, 48);
|
}
|
}
|
|
private void handleWorkToggle(Dikuai dikuai) {
|
String landNumber = dikuai.getLandNumber();
|
if (landNumber == null || landNumber.trim().isEmpty()) {
|
return;
|
}
|
boolean isCurrent = landNumber.equals(currentWorkLandNumber);
|
if (isCurrent) {
|
setCurrentWorkLand(null, null);
|
} else {
|
setCurrentWorkLand(landNumber, dikuai.getLandName());
|
}
|
loadDikuaiData();
|
}
|
|
private void setCurrentWorklandIfNeeded(Dikuai dikuai) {
|
if (dikuai == null) {
|
return;
|
}
|
String landNumber = dikuai.getLandNumber();
|
if (landNumber == null || landNumber.trim().isEmpty()) {
|
return;
|
}
|
setCurrentWorkLand(landNumber, dikuai.getLandName());
|
}
|
|
public static void setCurrentWorkLand(String landNumber, String landName) {
|
String sanitizedLandNumber = sanitizeLandNumber(landNumber);
|
boolean changed = !Objects.equals(currentWorkLandNumber, sanitizedLandNumber);
|
if (!changed) {
|
String persisted = readPersistedWorkLandNumber();
|
if (!Objects.equals(persisted, sanitizedLandNumber)) {
|
changed = true;
|
}
|
}
|
currentWorkLandNumber = sanitizedLandNumber;
|
|
Dikuai dikuai = null;
|
if (sanitizedLandNumber != null) {
|
dikuai = Dikuai.getDikuai(sanitizedLandNumber);
|
if (dikuai != null && (landName == null || "-1".equals(landName) || landName.trim().isEmpty())) {
|
landName = dikuai.getLandName();
|
}
|
}
|
if (landName != null && ("-1".equals(landName) || landName.trim().isEmpty())) {
|
landName = "未知地块";
|
}
|
|
Shouye shouye = Shouye.getInstance();
|
if (shouye != null) {
|
if (sanitizedLandNumber == null) {
|
shouye.updateCurrentAreaName(null);
|
} else {
|
shouye.updateCurrentAreaName(landName);
|
}
|
MapRenderer renderer = shouye.getMapRenderer();
|
if (renderer != null) {
|
renderer.applyLandMetadata(dikuai);
|
String boundary = (dikuai != null) ? dikuai.getBoundaryCoordinates() : null;
|
String plannedPath = (dikuai != null) ? dikuai.getPlannedPath() : null;
|
List<Obstacledge.Obstacle> configuredObstacles = (sanitizedLandNumber != null) ? loadObstaclesFromConfig(sanitizedLandNumber) : Collections.emptyList();
|
if (configuredObstacles == null) {
|
configuredObstacles = Collections.emptyList();
|
}
|
renderer.setCurrentBoundary(boundary, sanitizedLandNumber, sanitizedLandNumber == null ? null : landName);
|
renderer.setCurrentPlannedPath(plannedPath);
|
renderer.setCurrentObstacles(configuredObstacles, sanitizedLandNumber);
|
boolean showBoundaryPoints = sanitizedLandNumber != null && boundaryPointVisibility.getOrDefault(sanitizedLandNumber, false);
|
renderer.setBoundaryPointsVisible(showBoundaryPoints);
|
renderer.setBoundaryPointSizeScale(showBoundaryPoints ? 0.5d : 1.0d);
|
// 退出预览后,不显示障碍物点(障碍物点只在预览时显示)
|
renderer.setObstaclePointsVisible(false);
|
}
|
shouye.refreshMowingIndicators();
|
}
|
|
if (changed) {
|
persistCurrentWorkLand(sanitizedLandNumber);
|
}
|
}
|
|
public static String getCurrentWorkLandNumber() {
|
return currentWorkLandNumber;
|
}
|
|
public static String getPersistedWorkLandNumber() {
|
return readPersistedWorkLandNumber();
|
}
|
|
private static String sanitizeLandNumber(String landNumber) {
|
if (landNumber == null) {
|
return null;
|
}
|
String trimmed = landNumber.trim();
|
if (trimmed.isEmpty() || "-1".equals(trimmed)) {
|
return null;
|
}
|
return trimmed;
|
}
|
|
private static void persistCurrentWorkLand(String landNumber) {
|
synchronized (Dikuaiguanli.class) {
|
Properties props = new Properties();
|
try (FileInputStream in = new FileInputStream(PROPERTIES_FILE)) {
|
props.load(in);
|
} catch (IOException ignored) {
|
// Use empty defaults when the configuration file is missing.
|
}
|
|
if (landNumber == null) {
|
props.setProperty(WORK_LAND_KEY, "-1");
|
} else {
|
props.setProperty(WORK_LAND_KEY, landNumber);
|
}
|
|
try (FileOutputStream out = new FileOutputStream(PROPERTIES_FILE)) {
|
props.store(out, "Current work land selection updated");
|
} catch (IOException ex) {
|
System.err.println("无法保存当前作业地块: " + ex.getMessage());
|
}
|
}
|
}
|
|
private static String readPersistedWorkLandNumber() {
|
Properties props = new Properties();
|
try (FileInputStream in = new FileInputStream(PROPERTIES_FILE)) {
|
props.load(in);
|
String value = props.getProperty(WORK_LAND_KEY);
|
if (value == null) {
|
return null;
|
}
|
String trimmed = value.trim();
|
if (trimmed.isEmpty() || "-1".equals(trimmed)) {
|
return null;
|
}
|
return trimmed;
|
} catch (IOException ex) {
|
return null;
|
}
|
}
|
|
private ImageIcon loadIcon(String path, int width, int height) {
|
try {
|
ImageIcon rawIcon = new ImageIcon(path);
|
if (rawIcon.getIconWidth() <= 0 || rawIcon.getIconHeight() <= 0) {
|
return null;
|
}
|
Image scaled = rawIcon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH);
|
return new ImageIcon(scaled);
|
} catch (Exception ex) {
|
System.err.println("无法加载图标: " + path + " - " + ex.getMessage());
|
return null;
|
}
|
}
|
|
private void setupEventHandlers() {
|
// 新增地块按钮
|
addLandBtn.addActionListener(e -> {
|
Window ownerWindow = SwingUtilities.getWindowAncestor(this);
|
Window addDialogOwner = ownerWindow != null ? ownerWindow.getOwner() : null;
|
Component parentComponent = addDialogOwner instanceof Component ? (Component) addDialogOwner : ownerWindow;
|
if (ownerWindow instanceof JDialog) {
|
ownerWindow.dispose();
|
}
|
Coordinate.coordinates.clear();
|
latestInstance = null;
|
SwingUtilities.invokeLater(() -> AddDikuai.showAddDikuaiDialog(parentComponent));
|
});
|
}
|
|
private void editReturnPoint(Dikuai dikuai) {
|
FanhuiDialog fd = new FanhuiDialog(SwingUtilities.getWindowAncestor(this), dikuai);
|
fd.setVisible(true);
|
// 如果对话框已更新数据,刷新显示
|
if (fd.isUpdated()) {
|
loadDikuaiData();
|
}
|
}
|
|
/**
|
* 显示障碍物管理页面
|
*/
|
private void showObstacleManagementPage(Dikuai dikuai) {
|
if (dikuai == null) {
|
return;
|
}
|
Window owner = SwingUtilities.getWindowAncestor(this);
|
|
// 获取地块管理对话框,准备在打开障碍物管理页面时关闭
|
Window managementWindow = null;
|
if (owner instanceof JDialog) {
|
managementWindow = owner;
|
}
|
|
ObstacleManagementPage managementPage = new ObstacleManagementPage(owner, dikuai);
|
|
// 关闭地块管理页面
|
if (managementWindow != null) {
|
managementWindow.dispose();
|
}
|
|
managementPage.setVisible(true);
|
}
|
|
private void addNewObstacle(Dikuai dikuai) {
|
if (dikuai == null) {
|
JOptionPane.showMessageDialog(this, "未找到当前地块,无法新增障碍物", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
Window windowAncestor = SwingUtilities.getWindowAncestor(this);
|
if (windowAncestor instanceof JDialog) {
|
windowAncestor.dispose();
|
}
|
Component parent = windowAncestor != null ? windowAncestor.getOwner() instanceof Component ? (Component) windowAncestor.getOwner() : windowAncestor : null;
|
List<String> existingObstacleNames = loadObstacleNamesForLand(dikuai.getLandNumber());
|
addzhangaiwu.showDialog(parent, dikuai, existingObstacleNames);
|
loadDikuaiData();
|
}
|
|
private void showCompletedMowingTrackDialog(Dikuai dikuai) {
|
if (dikuai == null) {
|
return;
|
}
|
Window owner = SwingUtilities.getWindowAncestor(this);
|
JDialog dialog = new JDialog(owner, "已完成的割草路径坐标", Dialog.ModalityType.APPLICATION_MODAL);
|
dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
dialog.setLayout(new BorderLayout());
|
dialog.getContentPane().setBackground(WHITE);
|
|
String normalizedTrack = prepareCoordinateForEditor(dikuai.getMowingTrack());
|
boolean hasTrack = normalizedTrack != null && !normalizedTrack.isEmpty();
|
|
JTextArea textArea = new JTextArea(hasTrack ? normalizedTrack : "");
|
textArea.setEditable(false);
|
textArea.setLineWrap(true);
|
textArea.setWrapStyleWord(true);
|
textArea.setFont(new Font("微软雅黑", Font.PLAIN, 13));
|
textArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
textArea.setCaretPosition(0);
|
|
JScrollPane scrollPane = new JScrollPane(textArea);
|
scrollPane.setPreferredSize(new Dimension(342, 240));
|
scrollPane.setBorder(BorderFactory.createEmptyBorder(0, 12, 0, 12));
|
|
JLabel statusLabel = new JLabel(hasTrack ? "当前已保存完成的割草路径记录。" : "当前暂无完成的割草路径记录。");
|
statusLabel.setBorder(BorderFactory.createEmptyBorder(12, 12, 6, 12));
|
statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
|
statusLabel.setForeground(hasTrack ? TEXT_COLOR : LIGHT_TEXT);
|
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 0));
|
buttonPanel.setBackground(WHITE);
|
buttonPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
|
|
JButton deleteButton = createSmallButton("删除记录", RED_COLOR, RED_DARK);
|
deleteButton.setEnabled(hasTrack);
|
JButton closeButton = createSmallButton("关闭", PRIMARY_COLOR, PRIMARY_DARK);
|
|
deleteButton.addActionListener(e -> {
|
String latestTrack = prepareCoordinateForEditor(dikuai.getMowingTrack());
|
if (latestTrack == null || latestTrack.isEmpty()) {
|
JOptionPane.showMessageDialog(dialog, "当前没有可删除的轨迹记录。", "提示", JOptionPane.INFORMATION_MESSAGE);
|
return;
|
}
|
int choice = JOptionPane.showConfirmDialog(dialog, "确定要删除已完成的割草路径记录吗?", "确认删除", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
|
if (choice != JOptionPane.YES_OPTION) {
|
return;
|
}
|
if (saveFieldAndRefresh(dikuai, "mowingTrack", "-1")) {
|
dikuai.setMowingTrack("-1");
|
textArea.setText("");
|
statusLabel.setText("当前暂无完成的割草路径记录。");
|
statusLabel.setForeground(LIGHT_TEXT);
|
deleteButton.setEnabled(false);
|
JOptionPane.showMessageDialog(dialog, "已删除完成路径记录", "成功", JOptionPane.INFORMATION_MESSAGE);
|
} else {
|
JOptionPane.showMessageDialog(dialog, "删除失败,请稍后重试。", "错误", JOptionPane.ERROR_MESSAGE);
|
}
|
});
|
|
closeButton.addActionListener(e -> dialog.dispose());
|
|
buttonPanel.add(deleteButton);
|
buttonPanel.add(closeButton);
|
|
dialog.add(statusLabel, BorderLayout.NORTH);
|
dialog.add(scrollPane, BorderLayout.CENTER);
|
dialog.add(buttonPanel, BorderLayout.SOUTH);
|
dialog.pack();
|
dialog.setMinimumSize(new Dimension(378, 320));
|
dialog.setLocationRelativeTo(owner);
|
dialog.setVisible(true);
|
}
|
|
private Map<String, ObstacleSummary> loadObstacleSummaries() {
|
Map<String, ObstacleSummary> summaries = new HashMap<>();
|
try {
|
File configFile = new File("Obstacledge.properties");
|
if (!configFile.exists()) {
|
return summaries;
|
}
|
Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
|
if (!manager.loadFromFile(configFile.getAbsolutePath())) {
|
return summaries;
|
}
|
for (Obstacledge.Plot plot : manager.getPlots()) {
|
if (plot == null) {
|
continue;
|
}
|
String plotId = plot.getPlotId();
|
if (plotId == null || plotId.trim().isEmpty()) {
|
continue;
|
}
|
List<String> names = new ArrayList<>();
|
for (Obstacledge.Obstacle obstacle : plot.getObstacles()) {
|
if (obstacle == null) {
|
continue;
|
}
|
String name = obstacle.getObstacleName();
|
if (name == null) {
|
continue;
|
}
|
String trimmed = name.trim();
|
if (!trimmed.isEmpty()) {
|
names.add(trimmed);
|
}
|
}
|
summaries.put(plotId.trim(), ObstacleSummary.of(names));
|
}
|
} catch (Exception ex) {
|
System.err.println("读取障碍物配置失败: " + ex.getMessage());
|
}
|
return summaries;
|
}
|
|
private static List<Obstacledge.Obstacle> loadObstaclesFromConfig(String landNumber) {
|
if (landNumber == null || landNumber.trim().isEmpty()) {
|
return Collections.emptyList();
|
}
|
try {
|
File configFile = new File("Obstacledge.properties");
|
if (!configFile.exists()) {
|
return null;
|
}
|
Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
|
if (!manager.loadFromFile(configFile.getAbsolutePath())) {
|
return null;
|
}
|
Obstacledge.Plot plot = manager.getPlotById(landNumber.trim());
|
if (plot == null) {
|
return Collections.emptyList();
|
}
|
List<Obstacledge.Obstacle> obstacles = plot.getObstacles();
|
if (obstacles == null || obstacles.isEmpty()) {
|
return Collections.emptyList();
|
}
|
return new ArrayList<>(obstacles);
|
} catch (Exception ex) {
|
System.err.println("读取障碍物配置失败: " + ex.getMessage());
|
return null;
|
}
|
}
|
|
private ObstacleSummary getObstacleSummaryFromCache(String landNumber) {
|
if (landNumber == null || landNumber.trim().isEmpty()) {
|
return ObstacleSummary.empty();
|
}
|
if (obstacleSummaryCache == null || obstacleSummaryCache.isEmpty()) {
|
return ObstacleSummary.empty();
|
}
|
ObstacleSummary summary = obstacleSummaryCache.get(landNumber.trim());
|
return summary != null ? summary : ObstacleSummary.empty();
|
}
|
|
private List<String> loadObstacleNamesForLand(String landNumber) {
|
List<String> names = new ArrayList<>();
|
if (landNumber == null || landNumber.trim().isEmpty()) {
|
return names;
|
}
|
ObstacleSummary cached = getObstacleSummaryFromCache(landNumber);
|
if (!cached.isEmpty()) {
|
names.addAll(cached.copyNames());
|
return names;
|
}
|
Map<String, ObstacleSummary> latest = loadObstacleSummaries();
|
if (!latest.isEmpty()) {
|
obstacleSummaryCache = latest;
|
}
|
ObstacleSummary refreshed = getObstacleSummaryFromCache(landNumber);
|
if (!refreshed.isEmpty()) {
|
names.addAll(refreshed.copyNames());
|
}
|
return names;
|
}
|
|
private void editMowingWidth(Dikuai dikuai) {
|
String currentWidth = dikuai.getMowingWidth();
|
if (currentWidth == null || "-1".equals(currentWidth)) {
|
currentWidth = "";
|
}
|
String input = JOptionPane.showInputDialog(this, "请输入割草宽度(单位: 厘米)", currentWidth);
|
if (input == null) {
|
return;
|
}
|
String trimmed = input.trim();
|
if (trimmed.isEmpty()) {
|
JOptionPane.showMessageDialog(this, "割草宽度不能为空", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
String sanitized = trimmed;
|
if (sanitized.endsWith("厘米")) {
|
sanitized = sanitized.substring(0, sanitized.length() - 2).trim();
|
}
|
if (sanitized.endsWith("cm") || sanitized.endsWith("CM")) {
|
sanitized = sanitized.substring(0, sanitized.length() - 2).trim();
|
}
|
double widthValue;
|
try {
|
widthValue = Double.parseDouble(sanitized);
|
} catch (NumberFormatException ex) {
|
JOptionPane.showMessageDialog(this, "请输入有效的数字", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
if (widthValue <= 0) {
|
JOptionPane.showMessageDialog(this, "割草宽度必须大于0", "提示", JOptionPane.WARNING_MESSAGE);
|
return;
|
}
|
String normalized = BigDecimal.valueOf(widthValue).stripTrailingZeros().toPlainString();
|
if (!Dikuai.updateField(dikuai.getLandNumber(), "mowingWidth", normalized)) {
|
JOptionPane.showMessageDialog(this, "无法更新割草宽度", "错误", JOptionPane.ERROR_MESSAGE);
|
return;
|
}
|
Dikuai.updateField(dikuai.getLandNumber(), "updateTime", getCurrentTime());
|
Dikuai.saveToProperties();
|
loadDikuaiData();
|
JOptionPane.showMessageDialog(this, "割草宽度已更新", "成功", JOptionPane.INFORMATION_MESSAGE);
|
}
|
|
private void deleteDikuai(Dikuai dikuai) {
|
int result = JOptionPane.showConfirmDialog(this,
|
"确定要删除地块 \"" + dikuai.getLandName() + "\" 吗?此操作无法撤销。",
|
"确认删除",
|
JOptionPane.YES_NO_OPTION,
|
JOptionPane.WARNING_MESSAGE);
|
|
if (result == JOptionPane.YES_OPTION) {
|
// 从内存中移除并同步到文件
|
if (Dikuai.removeDikuai(dikuai.getLandNumber())) {
|
if (dikuai.getLandNumber() != null && dikuai.getLandNumber().equals(currentWorkLandNumber)) {
|
setCurrentWorkLand(null, null);
|
}
|
boundaryPointVisibility.remove(dikuai.getLandNumber());
|
Dikuai.saveToProperties();
|
loadDikuaiData();
|
JOptionPane.showMessageDialog(this, "地块删除成功!", "成功", JOptionPane.INFORMATION_MESSAGE);
|
} else {
|
JOptionPane.showMessageDialog(this, "未找到要删除的地块。", "提示", JOptionPane.WARNING_MESSAGE);
|
}
|
}
|
}
|
|
private String getCurrentTime() {
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
return sdf.format(new Date());
|
}
|
|
/**
|
* 获取地块管理面板实例
|
* @param landNumber 地块编号
|
*/
|
public static Dikuaiguanli createDikuaiManagementPanel(String landNumber) {
|
// 确保地块数据已初始化
|
Dikuai.initFromProperties();
|
|
return new Dikuaiguanli(landNumber);
|
}
|
|
/**
|
* 显示地块管理对话框(向后兼容)
|
* @param parent 父组件
|
* @param landNumber 地块编号
|
*/
|
public static void showDikuaiManagement(Component parent, String landNumber) {
|
// 确保地块数据已初始化
|
Dikuai.initFromProperties();
|
|
JDialog dialog = new JDialog();
|
dialog.setTitle("地块管理");
|
dialog.setModal(true);
|
dialog.setSize(400, 800);
|
dialog.setLocationRelativeTo(parent);
|
dialog.setResizable(false);
|
|
Dikuaiguanli managementPanel = new Dikuaiguanli(landNumber);
|
dialog.add(managementPanel);
|
dialog.setVisible(true);
|
}
|
|
public static void notifyExternalCreation(String landNumber) {
|
if (latestInstance == null) {
|
return;
|
}
|
SwingUtilities.invokeLater(() -> latestInstance.loadDikuaiData());
|
}
|
|
public static void updateBoundaryPointVisibility(String landNumber, boolean visible) {
|
if (landNumber == null || landNumber.trim().isEmpty()) {
|
return;
|
}
|
boundaryPointVisibility.put(landNumber, visible);
|
}
|
|
private static final class ObstacleSummary {
|
private static final ObstacleSummary EMPTY = new ObstacleSummary(Collections.emptyList());
|
private final List<String> names;
|
|
private ObstacleSummary(List<String> names) {
|
this.names = names;
|
}
|
|
static ObstacleSummary of(List<String> originalNames) {
|
if (originalNames == null || originalNames.isEmpty()) {
|
return empty();
|
}
|
List<String> cleaned = new ArrayList<>();
|
for (String name : originalNames) {
|
if (name == null) {
|
continue;
|
}
|
String trimmed = name.trim();
|
if (trimmed.isEmpty()) {
|
continue;
|
}
|
boolean duplicated = false;
|
for (String existing : cleaned) {
|
if (existing.equalsIgnoreCase(trimmed)) {
|
duplicated = true;
|
break;
|
}
|
}
|
if (!duplicated) {
|
cleaned.add(trimmed);
|
}
|
}
|
if (cleaned.isEmpty()) {
|
return empty();
|
}
|
cleaned.sort(String::compareToIgnoreCase);
|
return new ObstacleSummary(Collections.unmodifiableList(cleaned));
|
}
|
|
static ObstacleSummary empty() {
|
return EMPTY;
|
}
|
|
boolean isEmpty() {
|
return names.isEmpty();
|
}
|
|
int count() {
|
return names.size();
|
}
|
|
String buildDisplayValue() {
|
return count() > 0 ? String.format("障碍物%d个", count()) : "暂无障碍物";
|
}
|
|
String buildTooltip() {
|
return count() > 0 ? String.join(",", names) : "暂无障碍物";
|
}
|
|
List<String> copyNames() {
|
return new ArrayList<>(names);
|
}
|
}
|
}
|