| | |
| | | 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.List; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.Objects; |
| | | import java.util.Properties; |
| | | |
| | | import lujing.Lunjingguihua; |
| | | import lujing.MowingPathGenerationPage; |
| | | import publicway.Fuzhibutton; |
| | | import publicway.Lookbutton; |
| | | import publicway.buttonset; |
| | | import zhangaiwu.AddDikuai; |
| | | import zhangaiwu.Obstacledge; |
| | | import zhuye.MapRenderer; |
| | | import zhuye.Shouye; |
| | | import zhuye.Coordinate; |
| | | import gecaoji.Device; |
| | | |
| | | /** |
| | | * 地块管理面板 - 卡片式布局设计 |
| | |
| | | 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 static final int BOUNDARY_TOGGLE_ICON_SIZE = 24; |
| | | private Map<String, ObstacleSummary> obstacleSummaryCache = Collections.emptyMap(); |
| | | |
| | | public Dikuaiguanli(String landNumber) { |
| | |
| | | for (Dikuai dikuai : allDikuai.values()) { |
| | | JPanel card = createDikuaiCard(dikuai); |
| | | cardsPanel.add(card); |
| | | cardsPanel.add(Box.createRigidArea(new Dimension(0, 15))); |
| | | cardsPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | 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); |
| | | headerPanel.add(workToggleBtn, BorderLayout.EAST); |
| | | |
| | | // 将状态标签和按钮关联,以便在按钮状态变化时更新标签 |
| | | workToggleBtn.putClientProperty("statusLabel", statusLabel); |
| | | |
| | | rightPanel.add(statusLabel); |
| | | rightPanel.add(workToggleBtn); |
| | | |
| | | headerPanel.add(rightPanel, BorderLayout.EAST); |
| | | |
| | | card.add(headerPanel, BorderLayout.NORTH); |
| | | |
| | |
| | | |
| | | // 地块编号 |
| | | contentPanel.add(createCardInfoItem("地块编号:", getDisplayValue(dikuai.getLandNumber(), "未知"))); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | // 添加时间 |
| | | contentPanel.add(createCardInfoItem("添加时间:", getDisplayValue(dikuai.getCreateTime(), "未知"))); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | // 地块面积 |
| | | String landArea = dikuai.getLandArea(); |
| | | if (landArea != null && !landArea.equals("-1")) { |
| | |
| | | landArea = "未知"; |
| | | } |
| | | contentPanel.add(createCardInfoItem("地块面积:", landArea)); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | |
| | | // 返回点坐标(带修改按钮) |
| | | contentPanel.add(createCardInfoItemWithButton("返回点坐标:", |
| | | getDisplayValue(dikuai.getReturnPointCoordinates(), "未设置"), |
| | | "修改", e -> editReturnPoint(dikuai))); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | |
| | | // 地块边界坐标(带显示顶点按钮) |
| | | JPanel boundaryPanel = createBoundaryInfoItem(dikuai, |
| | | getTruncatedValue(dikuai.getBoundaryCoordinates(), 12, "未设置")); |
| | | setInfoItemTooltip(boundaryPanel, dikuai.getBoundaryCoordinates()); |
| | | configureInteractiveLabel(getInfoItemTitleLabel(boundaryPanel), |
| | | () -> editBoundaryCoordinates(dikuai), |
| | | "点击查看/编辑地块边界坐标"); |
| | | contentPanel.add(boundaryPanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | JPanel mowingPatternPanel = createCardInfoItem("割草模式:", |
| | | formatMowingPatternForDisplay(dikuai.getMowingPattern())); |
| | | configureInteractiveLabel(getInfoItemTitleLabel(mowingPatternPanel), |
| | | () -> editMowingPattern(dikuai), |
| | | "点击查看/编辑割草模式"); |
| | | contentPanel.add(mowingPatternPanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | // 割草机割刀宽度 |
| | | 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, 10))); |
| | | |
| | | 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, 10))); |
| | | |
| | | // 割草安全距离 |
| | | String displaySafetyDistance = "未设置"; |
| | | String safetyDistanceValue = dikuai.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, 10))); |
| | | |
| | | // 往返点路径(带查看图标按钮) |
| | | JPanel returnPathPanel = createCardInfoItemWithIconButton("往返点路径:", |
| | | createViewButton(e -> editReturnPath(dikuai))); |
| | | configureInteractiveLabel(getInfoItemTitleLabel(returnPathPanel), |
| | | () -> editReturnPath(dikuai), |
| | | "点击查看/编辑往返点路径"); |
| | | contentPanel.add(returnPathPanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | 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, 20))); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | // 地块边界坐标(带查看按钮) |
| | | JPanel boundaryPanel = createCardInfoItemWithIconButton("地块边界:", |
| | | createViewButton(e -> editBoundaryCoordinates(dikuai))); |
| | | configureInteractiveLabel(getInfoItemTitleLabel(boundaryPanel), |
| | | () -> editBoundaryCoordinates(dikuai), |
| | | "点击查看/编辑地块边界坐标"); |
| | | contentPanel.add(boundaryPanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | // 路径坐标(带查看按钮) |
| | | JPanel pathPanel = createCardInfoItemWithButton("路径坐标:", |
| | | getTruncatedValue(dikuai.getPlannedPath(), 12, "未设置"), |
| | | "复制", e -> copyCoordinatesAction("路径坐标", dikuai.getPlannedPath())); |
| | | setInfoItemTooltip(pathPanel, dikuai.getPlannedPath()); |
| | | JPanel pathPanel = createCardInfoItemWithIconButton("路径坐标:", |
| | | createViewButton(e -> editPlannedPath(dikuai))); |
| | | configureInteractiveLabel(getInfoItemTitleLabel(pathPanel), |
| | | () -> editPlannedPath(dikuai), |
| | | "点击查看/编辑路径坐标"); |
| | | contentPanel.add(pathPanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | JPanel baseStationPanel = createCardInfoItemWithButton("基站坐标:", |
| | | getTruncatedValue(dikuai.getBaseStationCoordinates(), 12, "未设置"), |
| | | "复制", e -> copyCoordinatesAction("基站坐标", dikuai.getBaseStationCoordinates())); |
| | | setInfoItemTooltip(baseStationPanel, dikuai.getBaseStationCoordinates()); |
| | | configureInteractiveLabel(getInfoItemTitleLabel(baseStationPanel), |
| | | () -> editBaseStationCoordinates(dikuai), |
| | | "点击查看/编辑基站坐标"); |
| | | contentPanel.add(baseStationPanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | |
| | | JPanel boundaryOriginalPanel = createCardInfoItemWithButton("边界原始坐标:", |
| | | getTruncatedValue(dikuai.getBoundaryOriginalCoordinates(), 12, "未设置"), |
| | | "复制", e -> copyCoordinatesAction("边界原始坐标", dikuai.getBoundaryOriginalCoordinates())); |
| | | setInfoItemTooltip(boundaryOriginalPanel, dikuai.getBoundaryOriginalCoordinates()); |
| | | configureInteractiveLabel(getInfoItemTitleLabel(boundaryOriginalPanel), |
| | | () -> editBoundaryOriginalCoordinates(dikuai), |
| | | "点击查看/编辑边界原始坐标"); |
| | | contentPanel.add(boundaryOriginalPanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | |
| | | JPanel mowingPatternPanel = createCardInfoItemWithButton("割草模式:", |
| | | getTruncatedValue(dikuai.getMowingPattern(), 12, "未设置"), |
| | | "复制", e -> copyCoordinatesAction("割草模式", dikuai.getMowingPattern())); |
| | | setInfoItemTooltip(mowingPatternPanel, dikuai.getMowingPattern()); |
| | | configureInteractiveLabel(getInfoItemTitleLabel(mowingPatternPanel), |
| | | () -> editMowingPattern(dikuai), |
| | | "点击查看/编辑割草模式"); |
| | | contentPanel.add(mowingPatternPanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | |
| | | String mowingWidthValue = dikuai.getMowingWidth(); |
| | | String widthSource = null; |
| | | if (mowingWidthValue != null && !"-1".equals(mowingWidthValue) && !mowingWidthValue.trim().isEmpty()) { |
| | | widthSource = mowingWidthValue + "厘米"; |
| | | } |
| | | String displayWidth = getTruncatedValue(widthSource, 12, "未设置"); |
| | | JPanel mowingWidthPanel = createCardInfoItemWithButton("割草宽度:", |
| | | displayWidth, |
| | | "编辑", e -> editMowingWidth(dikuai)); |
| | | setInfoItemTooltip(mowingWidthPanel, widthSource); |
| | | contentPanel.add(mowingWidthPanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 20))); |
| | | JPanel baseStationPanel = createCardInfoItemWithIconButton("基站坐标:", |
| | | createViewButton(e -> editBaseStationCoordinates(dikuai))); |
| | | configureInteractiveLabel(getInfoItemTitleLabel(baseStationPanel), |
| | | () -> editBaseStationCoordinates(dikuai), |
| | | "点击查看/编辑基站坐标"); |
| | | contentPanel.add(baseStationPanel); |
| | | contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | |
| | | JPanel completedTrackPanel = createCardInfoItemWithButton("已完成割草路径:", |
| | | getTruncatedValue(dikuai.getMowingTrack(), 12, "未记录"), |
| | | "查看", e -> showCompletedMowingTrackDialog(dikuai)); |
| | | createViewButton(e -> showCompletedMowingTrackDialog(dikuai))); |
| | | setInfoItemTooltip(completedTrackPanel, dikuai.getMowingTrack()); |
| | | configureInteractiveLabel(getInfoItemTitleLabel(completedTrackPanel), |
| | | () -> showCompletedMowingTrackDialog(dikuai), |
| | |
| | | JButton deleteBtn = createDeleteButton(); |
| | | deleteBtn.addActionListener(e -> deleteDikuai(dikuai)); |
| | | |
| | | JButton generatePathBtn = createPrimaryFooterButton("生成割草路径"); |
| | | generatePathBtn.addActionListener(e -> generateMowingPath(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); |
| | | |
| | |
| | | |
| | | 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); |
| | | itemPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 20)); |
| | | // 增加高度以确保按钮完整显示(按钮高度约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)); |
| | |
| | | |
| | | 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 = createSmallButton(buttonText); |
| | | button.addActionListener(listener); |
| | | JButton button = createSmallLinkButton(buttonText, listener); |
| | | |
| | | rightPanel.add(valueComp); |
| | | rightPanel.add(button); |
| | |
| | | return itemPanel; |
| | | } |
| | | |
| | | private JPanel createBoundaryInfoItem(Dikuai dikuai, String displayValue) { |
| | | private JPanel createCardInfoItemWithButton(String label, String value, JButton button) { |
| | | 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); |
| | | |
| | | 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 createCardInfoItemWithIconButton(String label, JButton button) { |
| | | 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)); |
| | | |
| | | 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); |
| | | int rowHeight = Math.max(36, BOUNDARY_TOGGLE_ICON_SIZE + 12); |
| | | // 增加高度以确保按钮下边缘完整显示(按钮高度28,加上上下边距) |
| | | int rowHeight = Math.max(30, BOUNDARY_TOGGLE_ICON_SIZE + 8); |
| | | Dimension rowDimension = new Dimension(Integer.MAX_VALUE, rowHeight); |
| | | itemPanel.setMaximumSize(rowDimension); |
| | | itemPanel.setPreferredSize(rowDimension); |
| | | itemPanel.setMinimumSize(new Dimension(0, 32)); |
| | | itemPanel.setMinimumSize(new Dimension(0, 28)); |
| | | |
| | | JLabel labelComp = new JLabel("地块边界:"); |
| | | labelComp.setFont(new Font("微软雅黑", Font.PLAIN, 14)); |
| | | labelComp.setForeground(LIGHT_TEXT); |
| | | |
| | | int verticalPadding = Math.max(0, (rowHeight - BOUNDARY_TOGGLE_ICON_SIZE) / 2); |
| | | // 确保按钮有足够的上下边距,避免下边缘被裁剪 |
| | | 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 valueComp = new JLabel(displayValue); |
| | | valueComp.setFont(new Font("微软雅黑", Font.PLAIN, 14)); |
| | | valueComp.setForeground(TEXT_COLOR); |
| | | // 状态提示文字标签 |
| | | JLabel statusLabel = new JLabel(); |
| | | statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 13)); |
| | | statusLabel.setForeground(LIGHT_TEXT); |
| | | |
| | | JButton toggleButton = createBoundaryToggleButton(dikuai); |
| | | // 将状态标签和按钮关联,以便在按钮状态变化时更新标签 |
| | | toggleButton.putClientProperty("statusLabel", statusLabel); |
| | | |
| | | rightPanel.add(valueComp); |
| | | // 初始化状态文字 |
| | | 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("valueLabel", valueComp); |
| | | itemPanel.putClientProperty("titleLabel", labelComp); |
| | | |
| | | return itemPanel; |
| | |
| | | button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
| | | button.setMargin(new Insets(0, 0, 0, 0)); |
| | | button.setIconTextGap(0); |
| | | button.setPreferredSize(new Dimension(56, 56)); |
| | | button.setPreferredSize(new Dimension(28, 28)); |
| | | |
| | | String landNumber = dikuai.getLandNumber(); |
| | | boolean isVisible = boundaryPointVisibility.getOrDefault(landNumber, false); |
| | |
| | | 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() { |
| | |
| | | boolean isCurrent = currentWorkLandNumber != null && currentWorkLandNumber.equals(landNumber); |
| | | if (isCurrent) { |
| | | renderer.setBoundaryPointsVisible(desiredState); |
| | | renderer.setBoundaryPointSizeScale(desiredState ? 0.5d : 1.0d); |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | private String promptCoordinateEditing(String title, String initialValue) { |
| | | return promptCoordinateEditing(title, initialValue, null); |
| | | } |
| | | |
| | | private String promptCoordinateEditing(String title, String initialValue, Dikuai dikuai) { |
| | | // 判断是否是往返点路径对话框 |
| | | boolean isReturnPathDialog = title != null && title.contains("往返点路径"); |
| | | |
| | | if (isReturnPathDialog) { |
| | | Window owner = SwingUtilities.getWindowAncestor(this); |
| | | Wangfanpathpage page = new Wangfanpathpage(owner, title, initialValue, dikuai); |
| | | page.setVisible(true); |
| | | return page.getResult(); |
| | | } |
| | | |
| | | JTextArea textArea = new JTextArea(prepareCoordinateForEditor(initialValue)); |
| | | textArea.setLineWrap(true); |
| | | textArea.setWrapStyleWord(true); |
| | |
| | | textArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); |
| | | |
| | | JScrollPane scrollPane = new JScrollPane(textArea); |
| | | scrollPane.setPreferredSize(new Dimension(360, 240)); |
| | | // 如果是往返点路径对话框,高度调整为适应两个文本域 |
| | | scrollPane.setPreferredSize(new Dimension(360, isReturnPathDialog ? 100 : 240)); |
| | | |
| | | int option = JOptionPane.showConfirmDialog( |
| | | this, |
| | | scrollPane, |
| | | title, |
| | | JOptionPane.OK_CANCEL_OPTION, |
| | | JOptionPane.PLAIN_MESSAGE); |
| | | |
| | | if (option == JOptionPane.OK_OPTION) { |
| | | return textArea.getText(); |
| | | 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); |
| | | } |
| | | return null; |
| | | dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); |
| | | |
| | | JPanel contentPanel = new JPanel(); |
| | | contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); |
| | | |
| | | if (isReturnPathDialog) { |
| | | contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS)); |
| | | // 减小边距以增加文本域宽度 (98%左右) |
| | | contentPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); |
| | | |
| | | // 1. 原始往返路径坐标区域 |
| | | String rawCoords = dikuai != null ? prepareCoordinateForEditor(dikuai.getReturnPathRawCoordinates()) : ""; |
| | | int rawCount = 0; |
| | | if (rawCoords != null && !rawCoords.isEmpty() && !"-1".equals(rawCoords)) { |
| | | rawCount = rawCoords.split(";").length; |
| | | } |
| | | |
| | | JPanel rawHeaderPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); |
| | | rawHeaderPanel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | rawHeaderPanel.setBackground(BACKGROUND_COLOR); |
| | | rawHeaderPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 30)); |
| | | |
| | | JLabel rawTitleLabel = new JLabel("原始往返路径坐标 (" + rawCount + "点) "); |
| | | rawTitleLabel.setFont(new Font("微软雅黑", Font.BOLD, 14)); |
| | | rawHeaderPanel.add(rawTitleLabel); |
| | | |
| | | // 原始坐标复制按钮 |
| | | final String finalRawCoords = rawCoords; |
| | | JButton rawCopyBtn = Fuzhibutton.createCopyButton( |
| | | () -> { |
| | | if (finalRawCoords == null || finalRawCoords.isEmpty() || "-1".equals(finalRawCoords)) return null; |
| | | return finalRawCoords; |
| | | }, |
| | | "复制", |
| | | new Color(230, 250, 240) |
| | | ); |
| | | rawCopyBtn.setFont(new Font("微软雅黑", Font.PLAIN, 12)); |
| | | rawCopyBtn.setPreferredSize(new Dimension(50, 24)); |
| | | rawCopyBtn.setMargin(new Insets(0,0,0,0)); |
| | | rawHeaderPanel.add(rawCopyBtn); |
| | | |
| | | contentPanel.add(rawHeaderPanel); |
| | | contentPanel.add(Box.createVerticalStrut(5)); |
| | | |
| | | JTextArea rawTextArea = new JTextArea(rawCoords); |
| | | rawTextArea.setLineWrap(true); |
| | | rawTextArea.setWrapStyleWord(true); |
| | | rawTextArea.setFont(new Font("微软雅黑", Font.PLAIN, 13)); |
| | | rawTextArea.setEditable(false); // 原始坐标通常不可编辑 |
| | | rawTextArea.setRows(4); |
| | | rawTextArea.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); |
| | | |
| | | JScrollPane rawScroll = new JScrollPane(rawTextArea); |
| | | rawScroll.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | // 设置最大宽度允许扩展,首选宽度适中 |
| | | rawScroll.setPreferredSize(new Dimension(300, 100)); |
| | | rawScroll.setMaximumSize(new Dimension(Integer.MAX_VALUE, 100)); |
| | | contentPanel.add(rawScroll); |
| | | |
| | | contentPanel.add(Box.createVerticalStrut(15)); |
| | | |
| | | // 2. 优化后往返路径坐标区域 |
| | | String optCoords = prepareCoordinateForEditor(initialValue); |
| | | int optCount = 0; |
| | | if (optCoords != null && !optCoords.isEmpty() && !"-1".equals(optCoords)) { |
| | | optCount = optCoords.split(";").length; |
| | | } |
| | | |
| | | JPanel optHeaderPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); |
| | | optHeaderPanel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | optHeaderPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 30)); |
| | | |
| | | JLabel optTitleLabel = new JLabel("优化后往返路径坐标 (" + optCount + "点) "); |
| | | optTitleLabel.setFont(new Font("微软雅黑", Font.BOLD, 14)); |
| | | optHeaderPanel.add(optTitleLabel); |
| | | |
| | | // 优化坐标复制按钮 - 动态获取文本域内容 |
| | | JButton optCopyBtn = Fuzhibutton.createCopyButton( |
| | | () -> { |
| | | String text = textArea.getText(); |
| | | if (text == null || text.trim().isEmpty() || "-1".equals(text.trim())) return null; |
| | | return text; |
| | | }, |
| | | "复制", |
| | | new Color(230, 250, 240) |
| | | ); |
| | | optCopyBtn.setFont(new Font("微软雅黑", Font.PLAIN, 12)); |
| | | optCopyBtn.setPreferredSize(new Dimension(50, 24)); |
| | | optCopyBtn.setMargin(new Insets(0,0,0,0)); |
| | | optHeaderPanel.add(optCopyBtn); |
| | | |
| | | contentPanel.add(optHeaderPanel); |
| | | contentPanel.add(Box.createVerticalStrut(5)); |
| | | |
| | | // 使用传入的 textArea (已初始化为 initialValue) |
| | | textArea.setRows(4); |
| | | scrollPane.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | scrollPane.setPreferredSize(new Dimension(300, 100)); |
| | | scrollPane.setMaximumSize(new Dimension(Integer.MAX_VALUE, 100)); |
| | | contentPanel.add(scrollPane); |
| | | |
| | | } else { |
| | | contentPanel.setLayout(new BorderLayout()); |
| | | contentPanel.add(scrollPane, BorderLayout.CENTER); |
| | | } |
| | | |
| | | JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); |
| | | |
| | | JButton okButton; |
| | | JButton cancelButton; |
| | | JButton copyButton = null; // 初始化为null |
| | | |
| | | if (isReturnPathDialog) { |
| | | // 往返点路径对话框:使用 buttonset 风格的确定按钮,图标按钮 |
| | | okButton = buttonset.createStyledButton("去绘制", new Color(70, 130, 220)); |
| | | |
| | | // 取消按钮使用 closepage.png 图标 |
| | | cancelButton = new JButton(); |
| | | ImageIcon closeIcon = loadIcon("image/closepage.png", 25, 25); |
| | | if (closeIcon != null) { |
| | | cancelButton.setIcon(closeIcon); |
| | | } else { |
| | | cancelButton.setText("关闭"); |
| | | } |
| | | cancelButton.setFont(new Font("微软雅黑", Font.PLAIN, 11)); |
| | | cancelButton.setForeground(PRIMARY_COLOR); |
| | | cancelButton.setBorder(BorderFactory.createEmptyBorder()); |
| | | cancelButton.setContentAreaFilled(false); |
| | | cancelButton.setFocusPainted(false); |
| | | cancelButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); |
| | | cancelButton.addMouseListener(new MouseAdapter() { |
| | | public void mouseEntered(MouseEvent e) { cancelButton.setOpaque(true); cancelButton.setBackground(new Color(255, 240, 240)); } |
| | | public void mouseExited(MouseEvent e) { cancelButton.setOpaque(false); } |
| | | }); |
| | | |
| | | // 使用 Fuzhibutton 创建复制按钮 (这里不再需要底部的复制按钮,因为上面已经有了) |
| | | // copyButton = ... |
| | | |
| | | } else { |
| | | // 其他对话框保持原有样式 |
| | | okButton = new JButton("确定"); |
| | | cancelButton = new JButton("取消"); |
| | | copyButton = new JButton("复制"); |
| | | |
| | | // 其他对话框的复制按钮逻辑 |
| | | 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); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | final boolean[] confirmed = new boolean[] {false}; |
| | | final String[] resultHolder = new String[1]; |
| | | |
| | | okButton.addActionListener(e -> { |
| | | if (isReturnPathDialog) { |
| | | // 往返点路径对话框:标记为打开绘制页面 |
| | | // 如果文本域中已经有坐标,表示要重新绘制 |
| | | String currentText = textArea.getText(); |
| | | if (currentText != null && !currentText.trim().isEmpty() && !"-1".equals(currentText.trim())) { |
| | | // 有坐标,表示重新绘制 |
| | | resultHolder[0] = "__OPEN_DRAW_PAGE_REFRESH__"; |
| | | } else { |
| | | // 没有坐标,正常绘制 |
| | | resultHolder[0] = "__OPEN_DRAW_PAGE__"; |
| | | } |
| | | confirmed[0] = true; |
| | | dialog.dispose(); |
| | | } else { |
| | | resultHolder[0] = textArea.getText(); |
| | | confirmed[0] = true; |
| | | dialog.dispose(); |
| | | } |
| | | }); |
| | | |
| | | cancelButton.addActionListener(e -> dialog.dispose()); |
| | | |
| | | buttonPanel.add(okButton); |
| | | buttonPanel.add(cancelButton); |
| | | if (copyButton != null) { |
| | | buttonPanel.add(copyButton); |
| | | } |
| | | |
| | | contentPanel.add(buttonPanel, isReturnPathDialog ? null : BorderLayout.SOUTH); |
| | | if (isReturnPathDialog) { |
| | | // 对于 BoxLayout,直接添加到底部 |
| | | JPanel bottomWrapper = new JPanel(new BorderLayout()); |
| | | bottomWrapper.add(buttonPanel, BorderLayout.EAST); |
| | | contentPanel.add(bottomWrapper); |
| | | } |
| | | dialog.setContentPane(contentPanel); |
| | | dialog.getRootPane().setDefaultButton(okButton); |
| | | dialog.pack(); |
| | | |
| | | // 如果是往返点路径对话框,设置宽度为首页的90%,高度保持不变 |
| | | if (isReturnPathDialog) { |
| | | Shouye shouye = Shouye.getInstance(); |
| | | if (shouye != null && shouye.getWidth() > 0) { |
| | | int homeWidth = shouye.getWidth(); |
| | | int dialogWidth = (int)(homeWidth * 0.9); |
| | | Dimension currentSize = dialog.getSize(); |
| | | dialog.setSize(dialogWidth, currentSize.height); |
| | | } |
| | | } |
| | | |
| | | dialog.setLocationRelativeTo(this); |
| | | dialog.setVisible(true); |
| | | |
| | | return confirmed[0] ? resultHolder[0] : null; |
| | | } |
| | | |
| | | private boolean saveFieldAndRefresh(Dikuai dikuai, String fieldName, String value) { |
| | |
| | | if (dikuai == null) { |
| | | return; |
| | | } |
| | | String edited = promptCoordinateEditing("查看 / 编辑地块边界坐标", dikuai.getBoundaryCoordinates()); |
| | | Window owner = SwingUtilities.getWindowAncestor(this); |
| | | |
| | | // 获取地块管理对话框,准备在打开边界编辑页面时关闭 |
| | | Window managementWindow = null; |
| | | if (owner instanceof JDialog) { |
| | | managementWindow = owner; |
| | | } |
| | | |
| | | // 打开边界编辑页面 |
| | | Dikuanbianjipage page = new Dikuanbianjipage(owner, "地块边界管理页面", dikuai.getBoundaryCoordinates(), dikuai); |
| | | page.setVisible(true); |
| | | |
| | | // 关闭地块管理页面 |
| | | if (managementWindow != null) { |
| | | managementWindow.dispose(); |
| | | } |
| | | |
| | | // 获取编辑结果并保存 |
| | | String edited = page.getResult(); |
| | | if (edited == null) { |
| | | return; |
| | | } |
| | | String normalized = normalizeCoordinateInput(edited); |
| | | if (!saveFieldAndRefresh(dikuai, "boundaryCoordinates", normalized)) { |
| | | JOptionPane.showMessageDialog(this, "无法更新地块边界坐标", "错误", JOptionPane.ERROR_MESSAGE); |
| | | JOptionPane.showMessageDialog(null, "无法更新地块边界坐标", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | String message = "-1".equals(normalized) ? "地块边界坐标已清空" : "地块边界坐标已更新"; |
| | | JOptionPane.showMessageDialog(this, message, "成功", JOptionPane.INFORMATION_MESSAGE); |
| | | JOptionPane.showMessageDialog(null, message, "成功", JOptionPane.INFORMATION_MESSAGE); |
| | | } |
| | | |
| | | private void editPlannedPath(Dikuai dikuai) { |
| | |
| | | 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) { |
| | |
| | | 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"; |
| | | } |
| | | } |
| | |
| | | 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 modeValue, |
| | | String initialGeneratedPath) { |
| | | Window owner = SwingUtilities.getWindowAncestor(this); |
| | | JDialog dialog = new JDialog(owner, "生成割草路径", Dialog.ModalityType.APPLICATION_MODAL); |
| | | dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); |
| | | dialog.getContentPane().setLayout(new BorderLayout()); |
| | | dialog.getContentPane().setBackground(BACKGROUND_COLOR); |
| | | |
| | | JPanel contentPanel = new JPanel(); |
| | | contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS)); |
| | | contentPanel.setBackground(BACKGROUND_COLOR); |
| | | contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 16, 12, 16)); |
| | | |
| | | String landName = getDisplayValue(dikuai.getLandName(), "未知地块"); |
| | | String landNumber = getDisplayValue(dikuai.getLandNumber(), "未知编号"); |
| | | JLabel headerLabel = new JLabel(landName + " / " + landNumber); |
| | | headerLabel.setFont(new Font("微软雅黑", Font.BOLD, 16)); |
| | | headerLabel.setForeground(TEXT_COLOR); |
| | | headerLabel.setAlignmentX(Component.LEFT_ALIGNMENT); |
| | | contentPanel.add(headerLabel); |
| | | contentPanel.add(Box.createVerticalStrut(12)); |
| | | |
| | | JTextField baseStationField = createInfoTextField(baseStationValue != null ? baseStationValue : "", true); |
| | | contentPanel.add(createTextFieldSection("基站坐标", baseStationField)); |
| | | |
| | | JTextArea boundaryArea = createInfoTextArea(boundaryValue != null ? boundaryValue : "", true, 6); |
| | | contentPanel.add(createTextAreaSection("地块边界", boundaryArea)); |
| | | |
| | | JTextArea obstacleArea = createInfoTextArea(obstacleValue != null ? obstacleValue : "", true, 6); |
| | | contentPanel.add(createTextAreaSection("障碍物坐标", obstacleArea)); |
| | | |
| | | JTextField widthField = createInfoTextField(widthValue != null ? widthValue : "", true); |
| | | contentPanel.add(createTextFieldSection("割草宽度 (厘米)", widthField)); |
| | | contentPanel.add(createInfoValueSection("割草模式", formatMowingPatternForDialog(modeValue))); |
| | | |
| | | String existingPath = prepareCoordinateForEditor(dikuai.getPlannedPath()); |
| | | String pathSeed = initialGeneratedPath != null ? initialGeneratedPath : existingPath; |
| | | JTextArea pathArea = createInfoTextArea(pathSeed != null ? pathSeed : "", true, 10); |
| | | contentPanel.add(createTextAreaSection("割草路径坐标", pathArea)); |
| | | |
| | | JScrollPane dialogScrollPane = new JScrollPane(contentPanel); |
| | | dialogScrollPane.setBorder(BorderFactory.createEmptyBorder()); |
| | | dialogScrollPane.getVerticalScrollBar().setUnitIncrement(16); |
| | | dialog.add(dialogScrollPane, BorderLayout.CENTER); |
| | | |
| | | JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 12, 12)); |
| | | buttonPanel.setBackground(BACKGROUND_COLOR); |
| | | |
| | | JButton generateBtn = createPrimaryFooterButton("生成割草路径"); |
| | | JButton saveBtn = createPrimaryFooterButton("保存路径"); |
| | | JButton cancelBtn = createPrimaryFooterButton("取消"); |
| | | |
| | | generateBtn.addActionListener(e -> { |
| | | String sanitizedWidth = sanitizeWidthString(widthField.getText()); |
| | | if (sanitizedWidth != null) { |
| | | try { |
| | | double widthCm = Double.parseDouble(sanitizedWidth); |
| | | widthField.setText(formatWidthForStorage(widthCm)); |
| | | sanitizedWidth = formatWidthForStorage(widthCm); |
| | | } catch (NumberFormatException ex) { |
| | | widthField.setText(sanitizedWidth); |
| | | } |
| | | |
| | | // 创建保存回调接口实现 |
| | | MowingPathGenerationPage.PathSaveCallback callback = new MowingPathGenerationPage.PathSaveCallback() { |
| | | @Override |
| | | public boolean saveBaseStationCoordinates(Dikuai dikuai, String value) { |
| | | return saveFieldAndRefresh(dikuai, "baseStationCoordinates", value); |
| | | } |
| | | String generated = attemptMowingPathPreview( |
| | | boundaryArea.getText(), |
| | | obstacleArea.getText(), |
| | | sanitizedWidth, |
| | | modeValue, |
| | | dialog, |
| | | true |
| | | ); |
| | | if (generated != null) { |
| | | pathArea.setText(generated); |
| | | pathArea.setCaretPosition(0); |
| | | |
| | | @Override |
| | | public boolean saveBoundaryCoordinates(Dikuai dikuai, String value) { |
| | | return saveFieldAndRefresh(dikuai, "boundaryCoordinates", value); |
| | | } |
| | | }); |
| | | |
| | | saveBtn.addActionListener(e -> { |
| | | String baseStationNormalized = normalizeCoordinateInput(baseStationField.getText()); |
| | | String boundaryNormalized = normalizeCoordinateInput(boundaryArea.getText()); |
| | | if (!"-1".equals(boundaryNormalized)) { |
| | | boundaryNormalized = boundaryNormalized |
| | | .replace("\r\n", ";") |
| | | .replace('\r', ';') |
| | | .replace('\n', ';') |
| | | .replaceAll(";+", ";") |
| | | .replaceAll("\\s*;\\s*", ";") |
| | | .trim(); |
| | | if (boundaryNormalized.isEmpty()) { |
| | | boundaryNormalized = "-1"; |
| | | } |
| | | |
| | | @Override |
| | | public boolean saveObstacleCoordinates(Dikuai dikuai, String baseStationValue, String obstacleValue) { |
| | | return persistObstaclesForLand(dikuai, baseStationValue, obstacleValue); |
| | | } |
| | | String obstacleNormalized = normalizeCoordinateInput(obstacleArea.getText()); |
| | | if (!"-1".equals(obstacleNormalized)) { |
| | | obstacleNormalized = obstacleNormalized |
| | | .replace("\r\n", " ") |
| | | .replace('\r', ' ') |
| | | .replace('\n', ' ') |
| | | .replaceAll("\\s{2,}", " ") |
| | | .trim(); |
| | | if (obstacleNormalized.isEmpty()) { |
| | | obstacleNormalized = "-1"; |
| | | } |
| | | |
| | | @Override |
| | | public boolean saveMowingWidth(Dikuai dikuai, String value) { |
| | | return saveFieldAndRefresh(dikuai, "mowingWidth", value); |
| | | } |
| | | String rawWidthInput = widthField.getText() != null ? widthField.getText().trim() : ""; |
| | | String widthSanitized = sanitizeWidthString(widthField.getText()); |
| | | if (widthSanitized == null) { |
| | | String message = rawWidthInput.isEmpty() ? "请先设置割草宽度(厘米)" : "割草宽度格式不正确"; |
| | | JOptionPane.showMessageDialog(dialog, message, "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | |
| | | @Override |
| | | public boolean savePlannedPath(Dikuai dikuai, String value) { |
| | | return saveFieldAndRefresh(dikuai, "plannedPath", value); |
| | | } |
| | | double widthCm; |
| | | try { |
| | | widthCm = Double.parseDouble(widthSanitized); |
| | | } catch (NumberFormatException ex) { |
| | | JOptionPane.showMessageDialog(dialog, "割草宽度格式不正确", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | if (widthCm <= 0) { |
| | | JOptionPane.showMessageDialog(dialog, "割草宽度必须大于0", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | String widthNormalized = formatWidthForStorage(widthCm); |
| | | widthField.setText(widthNormalized); |
| | | String pathNormalized = normalizeCoordinateInput(pathArea.getText()); |
| | | if (!"-1".equals(pathNormalized)) { |
| | | pathNormalized = pathNormalized |
| | | .replace("\r\n", ";") |
| | | .replace('\r', ';') |
| | | .replace('\n', ';') |
| | | .replaceAll(";+", ";") |
| | | .replaceAll("\\s*;\\s*", ";") |
| | | .trim(); |
| | | if (pathNormalized.isEmpty()) { |
| | | pathNormalized = "-1"; |
| | | } |
| | | } |
| | | if ("-1".equals(pathNormalized)) { |
| | | JOptionPane.showMessageDialog(dialog, "请先生成割草路径", "提示", JOptionPane.INFORMATION_MESSAGE); |
| | | return; |
| | | } |
| | | if (!saveFieldAndRefresh(dikuai, "baseStationCoordinates", baseStationNormalized)) { |
| | | JOptionPane.showMessageDialog(dialog, "无法保存基站坐标", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | if (!saveFieldAndRefresh(dikuai, "boundaryCoordinates", boundaryNormalized)) { |
| | | JOptionPane.showMessageDialog(dialog, "无法保存地块边界", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | if (!persistObstaclesForLand(dikuai, baseStationNormalized, obstacleNormalized)) { |
| | | JOptionPane.showMessageDialog(dialog, "无法保存障碍物坐标", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | if (!saveFieldAndRefresh(dikuai, "mowingWidth", widthNormalized)) { |
| | | JOptionPane.showMessageDialog(dialog, "无法保存割草宽度", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | if (!saveFieldAndRefresh(dikuai, "plannedPath", pathNormalized)) { |
| | | JOptionPane.showMessageDialog(dialog, "无法保存割草路径", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | JOptionPane.showMessageDialog(dialog, "割草路径已保存", "成功", JOptionPane.INFORMATION_MESSAGE); |
| | | dialog.dispose(); |
| | | }); |
| | | |
| | | cancelBtn.addActionListener(e -> dialog.dispose()); |
| | | |
| | | buttonPanel.add(generateBtn); |
| | | buttonPanel.add(saveBtn); |
| | | buttonPanel.add(cancelBtn); |
| | | dialog.add(buttonPanel, BorderLayout.SOUTH); |
| | | |
| | | dialog.pack(); |
| | | dialog.setSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT)); |
| | | dialog.setLocationRelativeTo(owner); |
| | | }; |
| | | |
| | | // 使用新的独立页面类 |
| | | MowingPathGenerationPage dialog = new MowingPathGenerationPage( |
| | | owner, |
| | | dikuai, |
| | | baseStationValue, |
| | | boundaryValue, |
| | | obstacleValue, |
| | | widthValue, |
| | | modeValue, |
| | | initialGeneratedPath, |
| | | callback |
| | | ); |
| | | |
| | | dialog.setVisible(true); |
| | | } |
| | | |
| | |
| | | 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 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 createSmallButton(text, PRIMARY_COLOR, PRIMARY_DARK); |
| | | return createSmallLinkButton(text, null); |
| | | } |
| | | |
| | | private JButton createSmallButton(String text, Color backgroundColor, Color hoverColor) { |
| | | JButton button = new JButton(text); |
| | | button.setFont(new Font("微软雅黑", Font.PLAIN, 12)); |
| | | // 对于需要不同颜色的按钮,使用实心风格 |
| | | Color baseColor = backgroundColor == null ? PRIMARY_COLOR : backgroundColor; |
| | | Color hover = hoverColor == null ? baseColor : hoverColor; |
| | | button.setBackground(baseColor); |
| | | button.setForeground(WHITE); |
| | | button.setBorder(BorderFactory.createEmptyBorder(2, 10, 2, 10)); |
| | | button.setMargin(new Insets(0, 0, 0, 0)); |
| | | button.setFocusPainted(false); |
| | | button.setCursor(new Cursor(Cursor.HAND_CURSOR)); |
| | | |
| | | button.addMouseListener(new MouseAdapter() { |
| | | public void mouseEntered(MouseEvent e) { |
| | | button.setBackground(hover); |
| | | } |
| | | public void mouseExited(MouseEvent e) { |
| | | button.setBackground(baseColor); |
| | | } |
| | | }); |
| | | |
| | | return button; |
| | | return createStyledButton(text, baseColor, true); |
| | | } |
| | | |
| | | private JButton createActionButton(String text, Color color) { |
| | | JButton button = new JButton(text); |
| | | button.setFont(new Font("微软雅黑", Font.PLAIN, 14)); |
| | | button.setBackground(color); |
| | | button.setForeground(WHITE); |
| | | button.setBorder(BorderFactory.createEmptyBorder(8, 16, 8, 16)); |
| | | button.setFocusPainted(false); |
| | | button.setCursor(new Cursor(Cursor.HAND_CURSOR)); |
| | | return createStyledButton(text, color, true); // 实心风格 |
| | | } |
| | | |
| | | // 悬停效果 |
| | | button.addMouseListener(new MouseAdapter() { |
| | | public void mouseEntered(MouseEvent e) { |
| | | if (color == RED_COLOR) { |
| | | button.setBackground(RED_DARK); |
| | | /** |
| | | * 创建现代风格按钮 (实心/轮廓) |
| | | */ |
| | | 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 { |
| | | button.setBackground(PRIMARY_DARK); |
| | | 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(); |
| | | } |
| | | public void mouseExited(MouseEvent e) { |
| | | if (color == RED_COLOR) { |
| | | button.setBackground(RED_COLOR); |
| | | } else { |
| | | button.setBackground(PRIMARY_COLOR); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | return button; |
| | | }; |
| | | 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 = new JButton("删除"); |
| | | button.setFont(new Font("微软雅黑", Font.PLAIN, 12)); |
| | | button.setBackground(RED_COLOR); |
| | | button.setForeground(WHITE); |
| | | button.setBorder(BorderFactory.createEmptyBorder(6, 12, 6, 12)); |
| | | button.setFocusPainted(false); |
| | | button.setCursor(new Cursor(Cursor.HAND_CURSOR)); |
| | | |
| | | ImageIcon deleteIcon = loadIcon("image/delete.png", 16, 16); |
| | | JButton button = new JButton(); |
| | | ImageIcon deleteIcon = loadIcon("image/delete.png", 25, 25); |
| | | if (deleteIcon != null) { |
| | | button.setIcon(deleteIcon); |
| | | button.setIconTextGap(6); |
| | | } else { |
| | | button.setText("删除"); |
| | | } |
| | | |
| | | // 悬停效果 |
| | | button.setFont(new Font("微软雅黑", Font.PLAIN, 11)); |
| | | button.setForeground(RED_COLOR); |
| | | button.setBorder(BorderFactory.createEmptyBorder()); |
| | | button.setContentAreaFilled(false); |
| | | button.setFocusPainted(false); |
| | | button.setCursor(new Cursor(Cursor.HAND_CURSOR)); |
| | | button.addMouseListener(new MouseAdapter() { |
| | | public void mouseEntered(MouseEvent e) { |
| | | button.setBackground(RED_DARK); |
| | | } |
| | | public void mouseExited(MouseEvent e) { |
| | | button.setBackground(RED_COLOR); |
| | | } |
| | | public void mouseEntered(MouseEvent e) { button.setOpaque(true); button.setBackground(new Color(255, 240, 240)); } |
| | | public void mouseExited(MouseEvent e) { button.setOpaque(false); } |
| | | }); |
| | | |
| | | return button; |
| | | } |
| | | |
| | | private JButton createViewButton(ActionListener listener) { |
| | | // 使用 Lookbutton 类创建查看按钮 |
| | | return Lookbutton.createViewButton(listener, new Color(230, 250, 240)); |
| | | } |
| | | |
| | | 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 MouseAdapter() { |
| | | public void mouseEntered(MouseEvent e) { |
| | | button.setBackground(PRIMARY_DARK); |
| | | } |
| | | |
| | | public void mouseExited(MouseEvent e) { |
| | | button.setBackground(PRIMARY_COLOR); |
| | | } |
| | | }); |
| | | |
| | | return button; |
| | | return createStyledButton(text, PRIMARY_COLOR, true); // 实心风格 |
| | | } |
| | | |
| | | private JButton createWorkToggleButton(Dikuai dikuai) { |
| | |
| | | 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() { |
| | |
| | | } |
| | | |
| | | public static void setCurrentWorkLand(String landNumber, String landName) { |
| | | currentWorkLandNumber = landNumber; |
| | | 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 (landNumber != null) { |
| | | dikuai = Dikuai.getDikuai(landNumber); |
| | | if (sanitizedLandNumber != null) { |
| | | dikuai = Dikuai.getDikuai(sanitizedLandNumber); |
| | | if (dikuai != null && (landName == null || "-1".equals(landName) || landName.trim().isEmpty())) { |
| | | landName = dikuai.getLandName(); |
| | | } |
| | |
| | | |
| | | Shouye shouye = Shouye.getInstance(); |
| | | if (shouye != null) { |
| | | if (landNumber == null) { |
| | | if (sanitizedLandNumber == null) { |
| | | shouye.updateCurrentAreaName(null); |
| | | } else { |
| | | shouye.updateCurrentAreaName(landName); |
| | |
| | | renderer.applyLandMetadata(dikuai); |
| | | String boundary = (dikuai != null) ? dikuai.getBoundaryCoordinates() : null; |
| | | String plannedPath = (dikuai != null) ? dikuai.getPlannedPath() : null; |
| | | List<Obstacledge.Obstacle> configuredObstacles = (landNumber != null) ? loadObstaclesFromConfig(landNumber) : Collections.emptyList(); |
| | | List<Obstacledge.Obstacle> configuredObstacles = (sanitizedLandNumber != null) ? loadObstaclesFromConfig(sanitizedLandNumber) : Collections.emptyList(); |
| | | if (configuredObstacles == null) { |
| | | configuredObstacles = Collections.emptyList(); |
| | | } |
| | | renderer.setCurrentBoundary(boundary, landNumber, landNumber == null ? null : landName); |
| | | renderer.setCurrentBoundary(boundary, sanitizedLandNumber, sanitizedLandNumber == null ? null : landName); |
| | | renderer.setCurrentPlannedPath(plannedPath); |
| | | renderer.setCurrentObstacles(configuredObstacles, landNumber); |
| | | boolean showBoundaryPoints = landNumber != null && boundaryPointVisibility.getOrDefault(landNumber, false); |
| | | 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); |
| | |
| | | }); |
| | | } |
| | | |
| | | private void editReturnPoint(Dikuai dikuai) { |
| | | FanhuiDialog fd = new FanhuiDialog(SwingUtilities.getWindowAncestor(this), dikuai); |
| | | fd.setVisible(true); |
| | | // 如果对话框已更新数据,刷新显示 |
| | | if (fd.isUpdated()) { |
| | | loadDikuaiData(); |
| | | private void editReturnPath(Dikuai dikuai) { |
| | | if (dikuai == null) { |
| | | return; |
| | | } |
| | | String edited = promptCoordinateEditing("查看 / 编辑往返点路径", dikuai.getReturnPathCoordinates(), dikuai); |
| | | if (edited == null) { |
| | | return; |
| | | } |
| | | // 检查是否是特殊标记,表示点击了"去绘制"按钮 |
| | | if ("__OPEN_DRAW_PAGE__".equals(edited) || "__OPEN_DRAW_PAGE_REFRESH__".equals(edited)) { |
| | | // 获取地块管理对话框 |
| | | Window owner = SwingUtilities.getWindowAncestor(this); |
| | | Window managementWindow = null; |
| | | if (owner instanceof JDialog) { |
| | | managementWindow = owner; |
| | | } |
| | | |
| | | // 获取地块管理对话框的父窗口(主窗口),作为绘制页面的父窗口 |
| | | Window drawPageOwner = null; |
| | | if (managementWindow != null) { |
| | | drawPageOwner = managementWindow.getOwner(); |
| | | } |
| | | if (drawPageOwner == null && owner != null) { |
| | | drawPageOwner = owner.getOwner(); |
| | | } |
| | | if (drawPageOwner == null) { |
| | | drawPageOwner = owner; |
| | | } |
| | | |
| | | // 先关闭地块管理页面 |
| | | if (managementWindow != null) { |
| | | managementWindow.dispose(); |
| | | } |
| | | |
| | | // 然后打开绘制页面,如果是重新绘制,传递标记 |
| | | boolean isRefresh = "__OPEN_DRAW_PAGE_REFRESH__".equals(edited); |
| | | Huizhiwanfanpath.showDrawReturnPathDialog(drawPageOwner, dikuai, isRefresh); |
| | | return; |
| | | } |
| | | String normalized = normalizeCoordinateInput(edited); |
| | | if (!saveFieldAndRefresh(dikuai, "returnPathCoordinates", normalized)) { |
| | | JOptionPane.showMessageDialog(this, "无法更新往返点路径", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | String message = "-1".equals(normalized) ? "往返点路径已清空" : "往返点路径已更新"; |
| | | JOptionPane.showMessageDialog(this, message, "成功", JOptionPane.INFORMATION_MESSAGE); |
| | | } |
| | | |
| | | /** |
| | | * 显示障碍物管理页面 |
| | | */ |
| | | 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) { |
| | |
| | | return new ArrayList<>(names); |
| | | } |
| | | } |
| | | |
| | | |
| | | } |