| | |
| | | private JLabel statusLabel; |
| | | private JLabel speedLabel; // 速度显示标签 |
| | | private JLabel areaNameLabel; |
| | | private JLabel drawingBoundaryLabel; // 正在绘制边界状态标签 |
| | | private JLabel navigationPreviewLabel; // 导航预览模式标签 |
| | | |
| | | // 边界警告相关 |
| | | private Timer boundaryWarningTimer; // 边界警告检查定时器 |
| | |
| | | private JButton pathPreviewReturnButton; |
| | | private boolean pathPreviewActive; |
| | | private Runnable pathPreviewReturnAction; |
| | | private JButton settingsReturnButton; // 返回系统设置页面的悬浮按钮 |
| | | private JButton saveManualBoundaryButton; // 保存手动绘制边界的按钮 |
| | | private String previewRestoreLandNumber; |
| | | private String previewRestoreLandName; |
| | | private boolean drawingPaused; |
| | |
| | | } |
| | | } |
| | | mapRenderer.setIdleTrailDurationSeconds(durationSeconds); |
| | | |
| | | // 应用边界距离显示设置和测量模式设置 |
| | | Setsys setsys = new Setsys(); |
| | | setsys.initializeFromProperties(); |
| | | mapRenderer.setBoundaryLengthVisible(setsys.isBoundaryLengthVisible()); |
| | | // 初始化测量模式 |
| | | boolean measurementEnabled = setsys.isMeasurementModeEnabled(); |
| | | mapRenderer.setMeasurementMode(measurementEnabled); |
| | | if (measurementEnabled) { |
| | | celiangmoshi.start(); |
| | | } else { |
| | | celiangmoshi.stop(); |
| | | } |
| | | // 初始化手动绘制边界模式 |
| | | boolean manualBoundaryDrawingEnabled = setsys.isManualBoundaryDrawingMode(); |
| | | if (mapRenderer != null) { |
| | | mapRenderer.setManualBoundaryDrawingMode(manualBoundaryDrawingEnabled); |
| | | } |
| | | // 更新返回设置按钮的显示状态 |
| | | updateSettingsReturnButtonVisibility(); |
| | | } |
| | | |
| | | /** |
| | | * 更新返回系统设置按钮的显示状态 |
| | | * 当手动绘制边界模式、显示边界距离或开启测量模式任一开启时显示 |
| | | */ |
| | | public void updateSettingsReturnButtonVisibility() { |
| | | Setsys setsys = new Setsys(); |
| | | setsys.initializeFromProperties(); |
| | | |
| | | boolean manualBoundaryDrawingEnabled = setsys.isManualBoundaryDrawingMode(); |
| | | boolean shouldShow = manualBoundaryDrawingEnabled |
| | | || setsys.isBoundaryLengthVisible() |
| | | || setsys.isMeasurementModeEnabled(); |
| | | |
| | | if (shouldShow) { |
| | | showSettingsReturnButton(); |
| | | // 如果手动绘制边界模式开启,显示保存按钮 |
| | | if (manualBoundaryDrawingEnabled) { |
| | | showSaveManualBoundaryButton(); |
| | | } else { |
| | | hideSaveManualBoundaryButton(); |
| | | } |
| | | } else { |
| | | hideSettingsReturnButton(); |
| | | hideSaveManualBoundaryButton(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 显示返回系统设置按钮 |
| | | */ |
| | | private void showSettingsReturnButton() { |
| | | ensureFloatingButtonInfrastructure(); |
| | | if (settingsReturnButton == null) { |
| | | settingsReturnButton = createFloatingTextButton("返回"); |
| | | settingsReturnButton.setToolTipText("返回系统设置"); |
| | | settingsReturnButton.addActionListener(e -> { |
| | | // 关闭所有相关模式 |
| | | Setsys setsys = new Setsys(); |
| | | setsys.initializeFromProperties(); |
| | | |
| | | boolean modeChanged = false; |
| | | |
| | | // 关闭手动绘制边界模式 |
| | | if (setsys.isManualBoundaryDrawingMode()) { |
| | | setsys.setManualBoundaryDrawingMode(false); |
| | | setsys.updateProperty("manualBoundaryDrawingMode", "false"); |
| | | // 清空手动绘制的边界点 |
| | | if (mapRenderer != null) { |
| | | mapRenderer.clearManualBoundaryPoints(); |
| | | } |
| | | modeChanged = true; |
| | | } |
| | | |
| | | // 关闭显示边界距离 |
| | | if (setsys.isBoundaryLengthVisible()) { |
| | | setsys.setBoundaryLengthVisible(false); |
| | | setsys.updateProperty("boundaryLengthVisible", "false"); |
| | | if (mapRenderer != null) { |
| | | mapRenderer.setBoundaryLengthVisible(false); |
| | | } |
| | | modeChanged = true; |
| | | } |
| | | |
| | | // 关闭测量模式 |
| | | if (setsys.isMeasurementModeEnabled()) { |
| | | setsys.setMeasurementModeEnabled(false); |
| | | setsys.updateProperty("measurementModeEnabled", "false"); |
| | | if (mapRenderer != null) { |
| | | mapRenderer.setMeasurementMode(false); |
| | | } |
| | | celiangmoshi.stop(); |
| | | modeChanged = true; |
| | | } |
| | | |
| | | // 如果关闭了任何模式,立即隐藏返回按钮并刷新界面 |
| | | if (modeChanged) { |
| | | // 立即隐藏返回按钮 |
| | | if (settingsReturnButton != null) { |
| | | settingsReturnButton.setVisible(false); |
| | | } |
| | | // 更新按钮列(移除返回按钮) |
| | | rebuildFloatingButtonColumn(); |
| | | // 如果所有按钮都隐藏了,隐藏悬浮按钮面板 |
| | | if (floatingButtonPanel != null && floatingButtonColumn != null |
| | | && floatingButtonColumn.getComponentCount() == 0) { |
| | | floatingButtonPanel.setVisible(false); |
| | | } |
| | | // 刷新界面 |
| | | if (visualizationPanel != null) { |
| | | visualizationPanel.revalidate(); |
| | | visualizationPanel.repaint(); |
| | | } |
| | | } |
| | | |
| | | // 更新返回按钮显示状态(确保状态同步) |
| | | updateSettingsReturnButtonVisibility(); |
| | | |
| | | // 打开系统设置页面 |
| | | showSettingsDialog(); |
| | | }); |
| | | } |
| | | settingsReturnButton.setVisible(true); |
| | | // 隐藏绘制相关的按钮(暂停、结束绘制) |
| | | if (drawingPauseButton != null) { |
| | | drawingPauseButton.setVisible(false); |
| | | } |
| | | if (endDrawingButton != null) { |
| | | endDrawingButton.setVisible(false); |
| | | } |
| | | if (floatingButtonPanel != null) { |
| | | floatingButtonPanel.setVisible(true); |
| | | if (floatingButtonPanel.getParent() != visualizationPanel) { |
| | | visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH); |
| | | } |
| | | } |
| | | rebuildFloatingButtonColumn(); |
| | | } |
| | | |
| | | /** |
| | | * 隐藏返回系统设置按钮 |
| | | */ |
| | | private void hideSettingsReturnButton() { |
| | | if (settingsReturnButton != null) { |
| | | settingsReturnButton.setVisible(false); |
| | | } |
| | | rebuildFloatingButtonColumn(); |
| | | if (floatingButtonPanel != null && floatingButtonColumn != null |
| | | && floatingButtonColumn.getComponentCount() == 0) { |
| | | floatingButtonPanel.setVisible(false); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 显示保存手动绘制边界按钮 |
| | | */ |
| | | private void showSaveManualBoundaryButton() { |
| | | ensureFloatingButtonInfrastructure(); |
| | | if (saveManualBoundaryButton == null) { |
| | | saveManualBoundaryButton = createFloatingTextButton("保存"); |
| | | saveManualBoundaryButton.setToolTipText("保存手动绘制的边界"); |
| | | saveManualBoundaryButton.addActionListener(e -> saveManualBoundary()); |
| | | } |
| | | saveManualBoundaryButton.setVisible(true); |
| | | if (floatingButtonPanel != null) { |
| | | floatingButtonPanel.setVisible(true); |
| | | if (floatingButtonPanel.getParent() != visualizationPanel) { |
| | | visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH); |
| | | } |
| | | } |
| | | rebuildFloatingButtonColumn(); |
| | | } |
| | | |
| | | /** |
| | | * 隐藏保存手动绘制边界按钮 |
| | | */ |
| | | private void hideSaveManualBoundaryButton() { |
| | | if (saveManualBoundaryButton != null) { |
| | | saveManualBoundaryButton.setVisible(false); |
| | | } |
| | | rebuildFloatingButtonColumn(); |
| | | if (floatingButtonPanel != null && floatingButtonColumn != null |
| | | && floatingButtonColumn.getComponentCount() == 0) { |
| | | floatingButtonPanel.setVisible(false); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 保存手动绘制的边界到文件 |
| | | */ |
| | | private void saveManualBoundary() { |
| | | if (mapRenderer == null) { |
| | | JOptionPane.showMessageDialog(this, "地图渲染器未初始化", "错误", JOptionPane.ERROR_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | List<Point2D.Double> points = mapRenderer.getManualBoundaryPoints(); |
| | | if (points == null || points.isEmpty()) { |
| | | JOptionPane.showMessageDialog(this, "没有可保存的边界点,请先在地图上点击绘制边界", "提示", JOptionPane.WARNING_MESSAGE); |
| | | return; |
| | | } |
| | | |
| | | // 构建坐标字符串:x1,y1;x2,y2;...;xn,yn(单位:米,精确到小数点后2位) |
| | | StringBuilder coordinates = new StringBuilder(); |
| | | for (int i = 0; i < points.size(); i++) { |
| | | Point2D.Double point = points.get(i); |
| | | if (i > 0) { |
| | | coordinates.append(";"); |
| | | } |
| | | coordinates.append(String.format(Locale.US, "%.2f,%.2f", point.x, point.y)); |
| | | } |
| | | |
| | | // 保存到 properties 文件 |
| | | try { |
| | | java.util.Properties props = new java.util.Properties(); |
| | | java.io.File file = new java.io.File("shoudongbianjie.properties"); |
| | | |
| | | // 如果文件存在,先加载现有内容 |
| | | if (file.exists()) { |
| | | try (java.io.FileInputStream input = new java.io.FileInputStream(file)) { |
| | | props.load(input); |
| | | } |
| | | } |
| | | |
| | | // 保存坐标 |
| | | props.setProperty("boundaryCoordinates", coordinates.toString()); |
| | | props.setProperty("pointCount", String.valueOf(points.size())); |
| | | |
| | | // 写回文件 |
| | | try (java.io.FileOutputStream output = new java.io.FileOutputStream(file)) { |
| | | props.store(output, "手动绘制边界坐标 - 格式: x1,y1;x2,y2;...;xn,yn (单位:米,精确到小数点后2位)"); |
| | | } |
| | | |
| | | JOptionPane.showMessageDialog(this, |
| | | String.format("边界已保存成功!\n共 %d 个点", points.size()), |
| | | "保存成功", |
| | | JOptionPane.INFORMATION_MESSAGE); |
| | | } catch (Exception ex) { |
| | | ex.printStackTrace(); |
| | | JOptionPane.showMessageDialog(this, |
| | | "保存失败: " + ex.getMessage(), |
| | | "错误", |
| | | JOptionPane.ERROR_MESSAGE); |
| | | } |
| | | } |
| | | |
| | | private void createHeaderPanel() { |
| | |
| | | |
| | | // 添加速度显示标签 |
| | | speedLabel = new JLabel(""); |
| | | speedLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); |
| | | speedLabel.setForeground(Color.GRAY); |
| | | speedLabel.setVisible(false); // 默认隐藏 |
| | | speedLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12)); |
| | | speedLabel.setForeground(Color.GRAY); |
| | | speedLabel.setVisible(false); // 默认隐藏 |
| | | |
| | | // 正在绘制边界状态标签 |
| | | drawingBoundaryLabel = new JLabel("正在绘制边界"); |
| | | drawingBoundaryLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); |
| | | drawingBoundaryLabel.setForeground(new Color(46, 139, 87)); |
| | | drawingBoundaryLabel.setVisible(false); // 默认隐藏 |
| | | |
| | | // 导航预览模式标签 |
| | | navigationPreviewLabel = new JLabel("当前导航预览模式"); |
| | | navigationPreviewLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); |
| | | navigationPreviewLabel.setForeground(new Color(46, 139, 87)); |
| | | navigationPreviewLabel.setVisible(false); // 默认隐藏 |
| | | |
| | | // 将状态与速度放在同一行,显示在地块名称下面一行 |
| | | JPanel statusRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0)); |
| | | statusRow.setOpaque(false); |
| | | statusRow.add(statusLabel); |
| | | statusRow.add(drawingBoundaryLabel); |
| | | statusRow.add(navigationPreviewLabel); |
| | | statusRow.add(speedLabel); |
| | | |
| | | // 左对齐标签与状态行,确保它们在 BoxLayout 中靠左显示 |
| | |
| | | if (parentWindow != null) { |
| | | // 使用 yaokong 包中的 RemoteControlDialog 实现 |
| | | remoteDialog = new yaokong.RemoteControlDialog(this, THEME_COLOR, speedLabel); |
| | | } else { |
| | | } else {/* */ |
| | | remoteDialog = new yaokong.RemoteControlDialog((JFrame) null, THEME_COLOR, speedLabel); |
| | | } |
| | | } |
| | |
| | | circleDialogMode = false; |
| | | hideCircleGuidancePanel(); |
| | | enterDrawingControlMode(); |
| | | |
| | | // 隐藏返回设置按钮(如果显示绘制按钮,则不应该显示返回按钮) |
| | | if (settingsReturnButton != null) { |
| | | settingsReturnButton.setVisible(false); |
| | | } |
| | | |
| | | // 显示"正在绘制边界"提示 |
| | | if (drawingBoundaryLabel != null) { |
| | | drawingBoundaryLabel.setVisible(true); |
| | | } |
| | | |
| | | boolean enableCircleGuidance = drawingShape != null |
| | | && "circle".equalsIgnoreCase(drawingShape.trim()); |
| | |
| | | floatingButtonColumn.add(pathPreviewReturnButton); |
| | | added = true; |
| | | } |
| | | if (saveManualBoundaryButton != null && saveManualBoundaryButton.isVisible()) { |
| | | if (added) { |
| | | floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | } |
| | | floatingButtonColumn.add(saveManualBoundaryButton); |
| | | added = true; |
| | | } |
| | | if (settingsReturnButton != null && settingsReturnButton.isVisible()) { |
| | | if (added) { |
| | | floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10))); |
| | | } |
| | | floatingButtonColumn.add(settingsReturnButton); |
| | | added = true; |
| | | } |
| | | floatingButtonColumn.revalidate(); |
| | | floatingButtonColumn.repaint(); |
| | | } |
| | |
| | | if (latest == null) { |
| | | return false; |
| | | } |
| | | return lastCapturedCoordinate == null || latest != lastCapturedCoordinate; |
| | | |
| | | // 检查是否有新的坐标(与上次采集的不同) |
| | | if (lastCapturedCoordinate != null && latest == lastCapturedCoordinate) { |
| | | return false; |
| | | } |
| | | |
| | | // 检查定位状态是否为4(固定解) |
| | | // 当选择割草机绘制圆形障碍物时,需要检查设备编号和定位状态 |
| | | Device device = Device.getGecaoji(); |
| | | if (device == null) { |
| | | return false; |
| | | } |
| | | |
| | | String positioningStatus = device.getPositioningStatus(); |
| | | if (positioningStatus == null || !"4".equals(positioningStatus.trim())) { |
| | | return false; |
| | | } |
| | | |
| | | // 检查设备编号是否匹配割草机编号 |
| | | String mowerId = Setsys.getPropertyValue("mowerId"); |
| | | String deviceId = device.getMowerNumber(); |
| | | if (mowerId != null && !mowerId.trim().isEmpty()) { |
| | | if (deviceId == null || !mowerId.trim().equals(deviceId.trim())) { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | private void applyCirclePrimaryButtonState(boolean enabled) { |
| | |
| | | activeBoundaryMode = BoundaryCaptureMode.NONE; |
| | | } |
| | | endDrawingCallback = null; |
| | | |
| | | // 隐藏"正在绘制边界"提示 |
| | | if (drawingBoundaryLabel != null) { |
| | | drawingBoundaryLabel.setVisible(false); |
| | | } |
| | | |
| | | visualizationPanel.revalidate(); |
| | | visualizationPanel.repaint(); |
| | | setHandheldMowerIconActive(false); |
| | |
| | | return mapRenderer; |
| | | } |
| | | |
| | | /** |
| | | * 获取控制面板(用于导航预览时替换按钮) |
| | | * @return 控制面板 |
| | | */ |
| | | public JPanel getControlPanel() { |
| | | return controlPanel; |
| | | } |
| | | |
| | | /** |
| | | * 获取开始按钮(用于导航预览时隐藏) |
| | | * @return 开始按钮 |
| | | */ |
| | | public JButton getStartButton() { |
| | | return startBtn; |
| | | } |
| | | |
| | | /** |
| | | * 获取结束按钮(用于导航预览时隐藏) |
| | | * @return 结束按钮 |
| | | */ |
| | | public JButton getStopButton() { |
| | | return stopBtn; |
| | | } |
| | | |
| | | /** |
| | | * 设置导航预览模式标签的显示状态 |
| | | * @param visible 是否显示 |
| | | */ |
| | | public void setNavigationPreviewLabelVisible(boolean visible) { |
| | | if (navigationPreviewLabel != null) { |
| | | navigationPreviewLabel.setVisible(visible); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 更新导航预览状态显示 |
| | | * @param active 是否处于导航预览模式 |
| | | */ |
| | | public void updateNavigationPreviewStatus(boolean active) { |
| | | setNavigationPreviewLabelVisible(active); |
| | | } |
| | | |
| | | /** |
| | | * 更新割草进度显示 |
| | | * @param percentage 完成百分比 |
| | | * @param completedArea 已完成面积(平方米) |
| | | * @param totalArea 总面积(平方米) |
| | | */ |
| | | public void updateMowingProgress(double percentage, double completedArea, double totalArea) { |
| | | if (mowingProgressLabel == null) { |
| | | return; |
| | | } |
| | | if (totalArea <= 0) { |
| | | mowingProgressLabel.setText("--%"); |
| | | mowingProgressLabel.setToolTipText("暂无地块面积数据"); |
| | | return; |
| | | } |
| | | double percent = Math.max(0.0, Math.min(100.0, percentage)); |
| | | mowingProgressLabel.setText(String.format(Locale.US, "%.1f%%", percent)); |
| | | mowingProgressLabel.setToolTipText(String.format(Locale.US, "%.1f㎡ / %.1f㎡", completedArea, totalArea)); |
| | | } |
| | | |
| | | /** |
| | | * 更新割草机速度显示 |
| | | * @param speed 速度值(单位:km/h) |
| | | */ |
| | | public void updateMowerSpeed(double speed) { |
| | | if (mowerSpeedValueLabel == null) { |
| | | return; |
| | | } |
| | | if (speed < 0) { |
| | | mowerSpeedValueLabel.setText("--"); |
| | | } else { |
| | | mowerSpeedValueLabel.setText(String.format(Locale.US, "%.1f", speed)); |
| | | } |
| | | if (mowerSpeedUnitLabel != null) { |
| | | mowerSpeedUnitLabel.setText("km/h"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取可视化面板实例 |
| | | */ |
| | | public JPanel getVisualizationPanel() { |
| | | return visualizationPanel; |
| | | } |
| | | |
| | | /** |
| | | * 获取主内容面板实例(用于添加浮动按钮) |
| | | */ |
| | | public JPanel getMainContentPanel() { |
| | | return mainContentPanel; |
| | | } |
| | | |
| | | |
| | | public void updateCurrentAreaName(String areaName) { |
| | | if (areaNameLabel == null) { |
| | | return; |