package zhuye;
|
|
import javax.swing.*;
|
import javax.swing.Timer;
|
|
import baseStation.BaseStation;
|
import set.Setsys;
|
import baseStation.BaseStationDialog;
|
|
import java.awt.*;
|
import java.awt.event.*;
|
|
import chuankou.dellmessage;
|
import chuankou.sendmessage;
|
import chuankou.SerialPortService;
|
import dikuai.Dikuai;
|
import dikuai.Dikuaiguanli;
|
import dikuai.addzhangaiwu;
|
import gecaoji.Device;
|
import gecaoji.Gecaoji;
|
import gecaoji.MowerBoundaryChecker;
|
import set.Sets;
|
import set.debug;
|
import udpdell.UDPServer;
|
import zhangaiwu.AddDikuai;
|
import yaokong.Control04;
|
import yaokong.RemoteControlDialog;
|
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Locale;
|
import java.util.Objects;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.function.Consumer;
|
import java.awt.geom.Point2D;
|
|
/**
|
* 首页界面 - 适配6.5寸竖屏,使用独立的MapRenderer进行绘制
|
*/
|
public class Shouye extends JPanel {
|
private static final long serialVersionUID = 1L;
|
private static Shouye instance;
|
|
// 主题颜色
|
private final Color THEME_COLOR = new Color(46, 139, 87);
|
private final Color THEME_HOVER_COLOR = new Color(30, 107, 69);
|
private final Color BACKGROUND_COLOR = new Color(250, 250, 250);
|
private final Color PANEL_BACKGROUND = new Color(255, 255, 255);
|
private final Color STATUS_PAUSE_COLOR = new Color(255, 165, 0);
|
|
// 组件
|
private JPanel headerPanel;
|
private JPanel mainContentPanel;
|
private JPanel controlPanel;
|
private JPanel navigationPanel;
|
private JPanel visualizationPanel;
|
|
// 按钮
|
private JButton startBtn;
|
private JButton stopBtn;
|
private JButton legendBtn;
|
private JButton remoteBtn;
|
private JButton areaSelectBtn;
|
private JButton baseStationBtn;
|
private JButton bluetoothBtn;
|
private JLabel dataPacketCountLabel;
|
private JLabel mowerSpeedValueLabel;
|
private JLabel mowerSpeedUnitLabel;
|
private JLabel mowingProgressLabel;
|
private gpszhuangtai fixQualityIndicator;
|
|
// 导航按钮
|
private JButton homeNavBtn;
|
private JButton areasNavBtn;
|
private JButton settingsNavBtn;
|
|
// 状态显示
|
private JLabel statusLabel;
|
private JLabel speedLabel; // 速度显示标签
|
private JLabel areaNameLabel;
|
private JLabel drawingBoundaryLabel; // 正在绘制边界状态标签
|
private JLabel navigationPreviewLabel; // 导航预览模式标签
|
|
// 边界警告相关
|
private Timer boundaryWarningTimer; // 边界警告检查定时器
|
private Timer warningBlinkTimer; // 警告图标闪烁定时器
|
private boolean isMowerOutsideBoundary = false; // 割草机是否在边界外
|
private boolean warningIconVisible = true; // 警告图标显示状态(用于闪烁)
|
|
// 以割草机为中心视图模式
|
private boolean centerOnMowerMode = false; // 是否处于以割草机为中心的模式
|
|
// 当前选中的导航按钮
|
private JButton currentNavButton;
|
|
// 对话框引用
|
private LegendDialog legendDialog;
|
// 使用完全限定名以避免与同包的 RemoteControlDialog 冲突,确保使用 yaokong 包下的实现
|
private yaokong.RemoteControlDialog remoteDialog;
|
private AreaSelectionDialog areaDialog;
|
private BaseStationDialog baseStationDialog;
|
private Sets settingsDialog;
|
private BaseStation baseStation;
|
|
// 地图渲染器
|
private MapRenderer mapRenderer;
|
|
private final Consumer<String> serialLineListener = line -> {
|
SwingUtilities.invokeLater(() -> {
|
updateDataPacketCountLabel();
|
// 如果收到$GNGGA数据,立即更新拖尾
|
if (line != null && line.trim().startsWith("$GNGGA")) {
|
if (mapRenderer != null) {
|
mapRenderer.forceUpdateIdleMowerTrail();
|
}
|
}
|
});
|
};
|
private static final int FLOAT_ICON_SIZE = 32;
|
private JButton endDrawingButton;
|
private JButton drawingPauseButton;
|
private JPanel floatingButtonPanel;
|
private JPanel floatingButtonColumn;
|
private Runnable endDrawingCallback;
|
private JButton pathPreviewReturnButton;
|
private boolean pathPreviewActive;
|
private Runnable pathPreviewReturnAction;
|
private JButton settingsReturnButton; // 返回系统设置页面的悬浮按钮
|
private JButton saveManualBoundaryButton; // 保存手动绘制边界的按钮
|
private String previewRestoreLandNumber;
|
private String previewRestoreLandName;
|
private boolean drawingPaused;
|
private ImageIcon pauseIcon;
|
private ImageIcon pauseActiveIcon;
|
private ImageIcon endIcon;
|
private ImageIcon bluetoothIcon;
|
private ImageIcon bluetoothLinkedIcon;
|
private JPanel circleGuidancePanel;
|
private JLabel circleGuidanceLabel;
|
private JButton circleGuidancePrimaryButton;
|
private JButton circleGuidanceSecondaryButton;
|
private int circleGuidanceStep;
|
private JDialog circleGuidanceDialog;
|
private boolean circleDialogMode;
|
private ComponentAdapter circleDialogOwnerAdapter;
|
private static final double METERS_PER_DEGREE_LAT = 111320.0d;
|
private static final double HANDHELD_DUPLICATE_THRESHOLD_METERS = 0.01d;
|
private final List<double[]> circleCapturedPoints = new ArrayList<>();
|
private double[] circleBaseLatLon;
|
private Timer circleDataMonitor;
|
private Coordinate lastCapturedCoordinate;
|
private boolean handheldCaptureActive;
|
private int handheldCapturedPoints;
|
private final List<Point2D.Double> handheldTemporaryPoints = new ArrayList<>();
|
private final List<Point2D.Double> mowerTemporaryPoints = new ArrayList<>();
|
private enum BoundaryCaptureMode { NONE, HANDHELD, MOWER }
|
private BoundaryCaptureMode activeBoundaryMode = BoundaryCaptureMode.NONE;
|
private boolean mowerBoundaryCaptureActive;
|
private Timer mowerBoundaryMonitor;
|
private Coordinate lastMowerCoordinate;
|
private double[] mowerBaseLatLon;
|
private boolean startButtonShowingPause = true;
|
private boolean stopButtonActive = false;
|
private boolean bluetoothConnected = false;
|
private Timer mowerSpeedRefreshTimer;
|
private boolean drawingControlModeActive;
|
private boolean storedStartButtonShowingPause;
|
private boolean storedStopButtonActive;
|
private String storedStatusBeforeDrawing;
|
private boolean handheldCaptureInlineUiActive;
|
private Timer handheldCaptureStatusTimer;
|
private String handheldCaptureStoredStatusText;
|
private Color handheldStartButtonOriginalBackground;
|
private Color handheldStartButtonOriginalForeground;
|
private Color handheldStopButtonOriginalBackground;
|
private Color handheldStopButtonOriginalForeground;
|
|
public Shouye() {
|
instance = this;
|
baseStation = new BaseStation();
|
baseStation.load();
|
dellmessage.registerLineListener(serialLineListener);
|
initializeUI();
|
setupEventHandlers();
|
scheduleIdentifierCheck();
|
}
|
|
public static Shouye getInstance() {
|
return instance;
|
}
|
|
private void initializeUI() {
|
setLayout(new BorderLayout());
|
setBackground(BACKGROUND_COLOR);
|
|
// 创建各个面板
|
createHeaderPanel();
|
createMainContentPanel();
|
createControlPanel();
|
createNavigationPanel();
|
|
// 添加到主面板
|
add(headerPanel, BorderLayout.NORTH);
|
add(mainContentPanel, BorderLayout.CENTER);
|
add(controlPanel, BorderLayout.SOUTH);
|
|
// 初始化地图渲染器
|
mapRenderer = new MapRenderer(visualizationPanel);
|
applyIdleTrailDurationFromSettings();
|
|
// 初始化对话框引用为null,延迟创建
|
legendDialog = null;
|
remoteDialog = null;
|
areaDialog = null;
|
baseStationDialog = null;
|
settingsDialog = null;
|
|
// 设置默认状态
|
setNavigationActive(homeNavBtn);
|
|
initializeDefaultAreaSelection();
|
refreshMapForSelectedArea();
|
|
// 启动边界警告检查定时器
|
startBoundaryWarningTimer();
|
}
|
|
private void scheduleIdentifierCheck() {
|
HierarchyListener listener = new HierarchyListener() {
|
@Override
|
public void hierarchyChanged(HierarchyEvent e) {
|
if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 && Shouye.this.isShowing()) {
|
Shouye.this.removeHierarchyListener(this);
|
SwingUtilities.invokeLater(() -> {
|
Shouye.this.checkIdentifiersAndPromptIfNeeded();
|
Shouye.this.showInitialMowerSelfCheckDialogIfNeeded();
|
// 设置窗口关闭监听器,在关闭时保存缩放比例
|
setupWindowCloseListener();
|
});
|
}
|
}
|
};
|
addHierarchyListener(listener);
|
}
|
|
/**
|
* 设置窗口关闭监听器,在窗口关闭时保存当前缩放比例
|
*/
|
private void setupWindowCloseListener() {
|
Window window = SwingUtilities.getWindowAncestor(this);
|
if (window != null && window instanceof JFrame) {
|
JFrame frame = (JFrame) window;
|
frame.addWindowListener(new WindowAdapter() {
|
@Override
|
public void windowClosing(WindowEvent e) {
|
// 保存当前缩放比例
|
saveCurrentScale();
|
// 停止边界警告定时器
|
if (boundaryWarningTimer != null && boundaryWarningTimer.isRunning()) {
|
boundaryWarningTimer.stop();
|
}
|
// 停止闪烁定时器
|
if (warningBlinkTimer != null && warningBlinkTimer.isRunning()) {
|
warningBlinkTimer.stop();
|
}
|
}
|
});
|
}
|
}
|
|
/**
|
* 保存当前地图缩放比例和视图中心坐标到配置文件
|
*/
|
public void saveCurrentScale() {
|
if (mapRenderer != null) {
|
double currentScale = mapRenderer.getScale();
|
double translateX = mapRenderer.getTranslateX();
|
double translateY = mapRenderer.getTranslateY();
|
Setsys setsys = new Setsys();
|
// 保留2位小数
|
setsys.updateProperty("mapScale", String.format("%.2f", currentScale));
|
setsys.updateProperty("viewCenterX", String.format("%.2f", translateX));
|
setsys.updateProperty("viewCenterY", String.format("%.2f", translateY));
|
}
|
}
|
|
/**
|
* 启动边界警告检查定时器
|
*/
|
private void startBoundaryWarningTimer() {
|
// 边界检查定时器:每500ms检查一次割草机是否在边界内
|
boundaryWarningTimer = new Timer(500, e -> {
|
checkMowerBoundaryStatus();
|
// 同时更新蓝牙图标状态
|
updateBluetoothButtonIcon();
|
});
|
boundaryWarningTimer.setInitialDelay(0);
|
boundaryWarningTimer.start();
|
|
// 闪烁定时器:每1秒切换一次警告图标显示状态
|
warningBlinkTimer = new Timer(1000, e -> {
|
if (isMowerOutsideBoundary) {
|
warningIconVisible = !warningIconVisible;
|
if (visualizationPanel != null) {
|
visualizationPanel.repaint();
|
}
|
}
|
});
|
warningBlinkTimer.setInitialDelay(0);
|
warningBlinkTimer.start();
|
}
|
|
/**
|
* 切换以割草机为中心的模式
|
*/
|
private void toggleCenterOnMowerMode() {
|
centerOnMowerMode = !centerOnMowerMode;
|
|
if (centerOnMowerMode) {
|
// 开启模式:立即将视图中心移动到割草机位置
|
updateViewToCenterOnMower();
|
}
|
// 关闭模式时不需要做任何操作,用户已经可以自由移动地图
|
|
// 更新图标显示(重绘以切换图标)
|
if (visualizationPanel != null) {
|
visualizationPanel.repaint();
|
}
|
}
|
|
/**
|
* 更新视图中心到割草机位置
|
*/
|
private void updateViewToCenterOnMower() {
|
if (mapRenderer == null) {
|
return;
|
}
|
|
Gecaoji mower = mapRenderer.getMower();
|
if (mower != null && mower.hasValidPosition()) {
|
Point2D.Double mowerPosition = mower.getPosition();
|
if (mowerPosition != null) {
|
// 获取当前缩放比例
|
double currentScale = mapRenderer.getScale();
|
// 设置视图变换,使割草机位置对应到屏幕中心
|
// translateX = -mowerX, translateY = -mowerY 可以让割草机在屏幕中心
|
mapRenderer.setViewTransform(currentScale, -mowerPosition.x, -mowerPosition.y);
|
}
|
}
|
}
|
|
/**
|
* 检查割草机边界状态
|
*/
|
private void checkMowerBoundaryStatus() {
|
// 如果处于以割草机为中心的模式,实时更新视图中心
|
if (centerOnMowerMode) {
|
updateViewToCenterOnMower();
|
}
|
|
// 检查是否在作业中
|
if (statusLabel == null || !"作业中".equals(statusLabel.getText())) {
|
// 不在作业中,重置状态
|
if (isMowerOutsideBoundary) {
|
isMowerOutsideBoundary = false;
|
warningIconVisible = true;
|
if (visualizationPanel != null) {
|
visualizationPanel.repaint();
|
}
|
}
|
return;
|
}
|
|
// 在作业中,检查是否在边界内
|
if (mapRenderer == null) {
|
return;
|
}
|
|
// 获取当前边界
|
List<Point2D.Double> boundary = mapRenderer.getCurrentBoundary();
|
if (boundary == null || boundary.size() < 3) {
|
// 没有边界,重置状态
|
if (isMowerOutsideBoundary) {
|
isMowerOutsideBoundary = false;
|
warningIconVisible = true;
|
if (visualizationPanel != null) {
|
visualizationPanel.repaint();
|
}
|
}
|
return;
|
}
|
|
// 获取割草机位置
|
Gecaoji mower = mapRenderer.getMower();
|
if (mower == null || !mower.hasValidPosition()) {
|
// 无法获取位置,重置状态
|
if (isMowerOutsideBoundary) {
|
isMowerOutsideBoundary = false;
|
warningIconVisible = true;
|
if (visualizationPanel != null) {
|
visualizationPanel.repaint();
|
}
|
}
|
return;
|
}
|
|
Point2D.Double mowerPosition = mower.getPosition();
|
if (mowerPosition == null) {
|
return;
|
}
|
|
// 使用 MowerBoundaryChecker 检查是否在边界内
|
boolean isInside = MowerBoundaryChecker.isInsideBoundaryPoints(
|
boundary,
|
mowerPosition.x,
|
mowerPosition.y
|
);
|
|
// 更新状态
|
boolean wasOutside = isMowerOutsideBoundary;
|
isMowerOutsideBoundary = !isInside;
|
|
// 如果状态改变,立即重绘
|
if (wasOutside != isMowerOutsideBoundary) {
|
warningIconVisible = true;
|
if (visualizationPanel != null) {
|
visualizationPanel.repaint();
|
}
|
}
|
}
|
|
private void showInitialMowerSelfCheckDialogIfNeeded() {
|
// 已移除进入主页时的自检提示(按用户要求删除)
|
// 以前这里会调用 zijian.showInitialPromptIfNeeded(...) 展示自检对话框,现已禁用。
|
}
|
|
private void applyIdleTrailDurationFromSettings() {
|
if (mapRenderer == null) {
|
return;
|
}
|
int durationSeconds = MapRenderer.DEFAULT_IDLE_TRAIL_DURATION_SECONDS;
|
String configuredValue = Setsys.getPropertyValue("idleTrailDurationSeconds");
|
if (configuredValue != null) {
|
String trimmed = configuredValue.trim();
|
if (!trimmed.isEmpty()) {
|
try {
|
int parsed = Integer.parseInt(trimmed);
|
if (parsed >= 5 && parsed <= 600) {
|
durationSeconds = parsed;
|
}
|
} catch (NumberFormatException ignored) {
|
durationSeconds = MapRenderer.DEFAULT_IDLE_TRAIL_DURATION_SECONDS;
|
}
|
}
|
}
|
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() {
|
headerPanel = new JPanel(new BorderLayout());
|
headerPanel.setBackground(PANEL_BACKGROUND);
|
headerPanel.setBorder(BorderFactory.createEmptyBorder(15, 20, 15, 20));
|
headerPanel.setPreferredSize(new Dimension(0, 80));
|
|
// 左侧信息区域(垂直排列:地块名称在上,状态行在下)
|
JPanel leftInfoPanel = new JPanel();
|
leftInfoPanel.setBackground(PANEL_BACKGROUND);
|
leftInfoPanel.setLayout(new BoxLayout(leftInfoPanel, BoxLayout.Y_AXIS));
|
// 保证子组件左对齐
|
leftInfoPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
areaNameLabel = new JLabel("未选择地块");
|
areaNameLabel.setFont(new Font("微软雅黑", Font.BOLD, 18));
|
areaNameLabel.setForeground(Color.BLACK);
|
areaNameLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
areaNameLabel.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseClicked(MouseEvent e) {
|
Dikuaiguanli.showDikuaiManagement(Shouye.this, null);
|
}
|
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
areaNameLabel.setForeground(THEME_COLOR);
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
areaNameLabel.setForeground(Color.BLACK);
|
}
|
});
|
|
statusLabel = new JLabel("待机");
|
statusLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
statusLabel.setForeground(Color.GRAY);
|
statusLabel.addPropertyChangeListener("text", evt -> {
|
Object newValue = evt.getNewValue();
|
applyStatusLabelColor(newValue instanceof String ? (String) newValue : null);
|
});
|
applyStatusLabelColor(statusLabel.getText());
|
|
// 添加速度显示标签
|
speedLabel = new JLabel("");
|
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 中靠左显示
|
areaNameLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
statusRow.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
leftInfoPanel.add(areaNameLabel);
|
leftInfoPanel.add(statusRow);
|
|
// 右侧操作区域
|
JPanel rightActionPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
rightActionPanel.setBackground(PANEL_BACKGROUND);
|
|
bluetoothBtn = createBluetoothButton();
|
|
// 修改设置按钮:使用图片替代Unicode图标
|
JButton settingsBtn = new JButton();
|
try {
|
ImageIcon settingsIcon = new ImageIcon("image/sets.png");
|
// 调整图片大小以适应按钮
|
Image scaledImage = settingsIcon.getImage().getScaledInstance(30, 30, Image.SCALE_SMOOTH);
|
settingsBtn.setIcon(new ImageIcon(scaledImage));
|
} catch (Exception e) {
|
// 如果图片加载失败,使用默认文本
|
settingsBtn.setText("设置");
|
System.err.println("无法加载设置图标: " + e.getMessage());
|
}
|
settingsBtn.setPreferredSize(new Dimension(40, 40));
|
settingsBtn.setBackground(PANEL_BACKGROUND);
|
settingsBtn.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
settingsBtn.setFocusPainted(false);
|
|
// 添加悬停效果
|
settingsBtn.addMouseListener(new MouseAdapter() {
|
public void mouseEntered(MouseEvent e) {
|
settingsBtn.setBackground(new Color(240, 240, 240));
|
}
|
public void mouseExited(MouseEvent e) {
|
settingsBtn.setBackground(PANEL_BACKGROUND);
|
}
|
});
|
|
// 添加设置按钮事件
|
settingsBtn.addActionListener(e -> showSettingsDialog());
|
|
rightActionPanel.add(bluetoothBtn);
|
rightActionPanel.add(settingsBtn);
|
|
headerPanel.add(leftInfoPanel, BorderLayout.WEST);
|
headerPanel.add(rightActionPanel, BorderLayout.EAST);
|
}
|
|
private void createMainContentPanel() {
|
mainContentPanel = new JPanel(new BorderLayout());
|
mainContentPanel.setBackground(BACKGROUND_COLOR);
|
|
// 可视化区域 - 使用MapRenderer进行绘制
|
visualizationPanel = new JPanel() {
|
private ImageIcon gecaojiIcon1 = null; // 默认图标
|
private ImageIcon gecaojiIcon2 = null; // 以割草机为中心模式图标
|
private static final int GECAOJI_ICON_X = 37;
|
private static final int GECAOJI_ICON_Y = 10;
|
private static final int GECAOJI_ICON_SIZE = 20;
|
|
{
|
// 加载割草机图标,大小20x20像素
|
gecaojiIcon1 = loadScaledIcon("image/gecaojishijiao1.png", GECAOJI_ICON_SIZE, GECAOJI_ICON_SIZE);
|
gecaojiIcon2 = loadScaledIcon("image/gecaojishijiao2.png", GECAOJI_ICON_SIZE, GECAOJI_ICON_SIZE);
|
}
|
|
/**
|
* 检查鼠标位置是否在割草机图标区域内
|
*/
|
private boolean isMouseOnGecaojiIcon(Point mousePoint) {
|
return mousePoint.x >= GECAOJI_ICON_X &&
|
mousePoint.x <= GECAOJI_ICON_X + GECAOJI_ICON_SIZE &&
|
mousePoint.y >= GECAOJI_ICON_Y &&
|
mousePoint.y <= GECAOJI_ICON_Y + GECAOJI_ICON_SIZE;
|
}
|
|
@Override
|
public String getToolTipText(MouseEvent event) {
|
// 如果鼠标在割草机图标区域内,显示提示文字
|
if (isMouseOnGecaojiIcon(event.getPoint())) {
|
// 根据当前模式显示不同的提示文字
|
return centerOnMowerMode ? "取消以割草机为中心" : "以割草机为中心";
|
}
|
// 不在图标上时返回null,不显示工具提示框
|
return null;
|
}
|
|
@Override
|
protected void paintComponent(Graphics g) {
|
super.paintComponent(g);
|
// 委托给MapRenderer进行绘制
|
if (mapRenderer != null) {
|
mapRenderer.renderMap(g);
|
}
|
|
// 检查是否需要显示警告图标
|
if (isMowerOutsideBoundary && warningIconVisible) {
|
// 绘制红色三角形警告图标(带叹号)
|
drawWarningIcon(g, GECAOJI_ICON_X, GECAOJI_ICON_Y, GECAOJI_ICON_SIZE);
|
} else {
|
// 根据模式选择不同的图标
|
ImageIcon iconToDraw = centerOnMowerMode ? gecaojiIcon2 : gecaojiIcon1;
|
if (iconToDraw != null) {
|
// 绘制割草机图标
|
// 水平方向与速度指示器对齐(x=37)
|
// 垂直方向与卫星状态图标对齐(y=10,速度指示器面板顶部边距10像素,使图标中心对齐)
|
g.drawImage(iconToDraw.getImage(), GECAOJI_ICON_X, GECAOJI_ICON_Y, null);
|
}
|
}
|
}
|
|
/**
|
* 绘制红色三角形警告图标(带叹号)
|
*/
|
private void drawWarningIcon(Graphics g, int x, int y, int size) {
|
Graphics2D g2d = (Graphics2D) g.create();
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
|
// 绘制红色三角形
|
int[] xPoints = {x + size / 2, x, x + size};
|
int[] yPoints = {y, y + size, y + size};
|
g2d.setColor(Color.RED);
|
g2d.fillPolygon(xPoints, yPoints, 3);
|
|
// 绘制白色边框
|
g2d.setColor(Color.WHITE);
|
g2d.setStroke(new BasicStroke(1.5f));
|
g2d.drawPolygon(xPoints, yPoints, 3);
|
|
// 绘制白色叹号
|
g2d.setColor(Color.WHITE);
|
g2d.setFont(new Font("Arial", Font.BOLD, size * 3 / 4));
|
FontMetrics fm = g2d.getFontMetrics();
|
String exclamation = "!";
|
int textWidth = fm.stringWidth(exclamation);
|
int textHeight = fm.getAscent();
|
g2d.drawString(exclamation, x + (size - textWidth) / 2, y + (size + textHeight) / 2 - 2);
|
|
g2d.dispose();
|
}
|
};
|
visualizationPanel.setLayout(new BorderLayout());
|
|
// 添加鼠标点击监听器,检测是否点击了割草机图标
|
visualizationPanel.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseClicked(MouseEvent e) {
|
if (SwingUtilities.isLeftMouseButton(e)) {
|
Point clickPoint = e.getPoint();
|
// 检查是否点击了割草机图标区域(37, 10, 20, 20)
|
if (clickPoint.x >= 37 && clickPoint.x <= 57 &&
|
clickPoint.y >= 10 && clickPoint.y <= 30) {
|
// 切换以割草机为中心的模式
|
toggleCenterOnMowerMode();
|
}
|
}
|
}
|
});
|
|
JPanel speedIndicatorPanel = createSpeedIndicatorPanel();
|
visualizationPanel.add(speedIndicatorPanel, BorderLayout.NORTH);
|
|
// 创建功能按钮面板
|
JPanel functionButtonsPanel = new JPanel();
|
functionButtonsPanel.setLayout(new BoxLayout(functionButtonsPanel, BoxLayout.Y_AXIS));
|
functionButtonsPanel.setOpaque(false);
|
functionButtonsPanel.setBorder(BorderFactory.createEmptyBorder(20, 5, 0, 0)); // 左边距改为5像素
|
|
legendBtn = createFunctionButton("图例", "📖");
|
baseStationBtn = createFunctionButton("基准站", "📡"); // 调整到第二位
|
areaSelectBtn = createFunctionButton("地块", "🌿"); // 调整到第三位
|
remoteBtn = createFunctionButton("遥控", "🎮"); // 调整到最后
|
|
functionButtonsPanel.add(legendBtn);
|
functionButtonsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
functionButtonsPanel.add(baseStationBtn);
|
functionButtonsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
functionButtonsPanel.add(areaSelectBtn);
|
functionButtonsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
functionButtonsPanel.add(remoteBtn);
|
|
visualizationPanel.add(functionButtonsPanel, BorderLayout.WEST);
|
|
JPanel zoomControlPanel = createZoomControlPanel();
|
visualizationPanel.add(zoomControlPanel, BorderLayout.EAST);
|
|
mainContentPanel.add(visualizationPanel, BorderLayout.CENTER);
|
|
startMowerSpeedUpdates();
|
}
|
|
private JPanel createZoomControlPanel() {
|
JPanel container = new JPanel(new BorderLayout());
|
container.setOpaque(false);
|
container.setBorder(BorderFactory.createEmptyBorder(0, 0, 20, 20));
|
|
JPanel alignmentPanel = new JPanel();
|
alignmentPanel.setOpaque(false);
|
alignmentPanel.setLayout(new BoxLayout(alignmentPanel, BoxLayout.Y_AXIS));
|
alignmentPanel.add(Box.createVerticalGlue());
|
|
JPanel buttonStack = new JPanel();
|
buttonStack.setOpaque(false);
|
buttonStack.setLayout(new BoxLayout(buttonStack, BoxLayout.Y_AXIS));
|
|
JButton zoomInButton = createZoomButton("+");
|
JButton zoomOutButton = createZoomButton("-");
|
|
AtomicBoolean skipZoomInClick = new AtomicBoolean(false);
|
AtomicBoolean skipZoomOutClick = new AtomicBoolean(false);
|
|
Timer zoomInHoldTimer = new Timer(120, event -> {
|
if (mapRenderer == null || !zoomInButton.getModel().isPressed()) {
|
((Timer) event.getSource()).stop();
|
return;
|
}
|
if (!mapRenderer.canZoomIn()) {
|
((Timer) event.getSource()).stop();
|
return;
|
}
|
skipZoomInClick.set(true);
|
mapRenderer.zoomInFromCenter();
|
if (!mapRenderer.canZoomIn()) {
|
((Timer) event.getSource()).stop();
|
}
|
});
|
zoomInHoldTimer.setInitialDelay(180);
|
zoomInHoldTimer.setDelay(120);
|
|
Timer zoomOutHoldTimer = new Timer(120, event -> {
|
if (mapRenderer == null || !zoomOutButton.getModel().isPressed()) {
|
((Timer) event.getSource()).stop();
|
return;
|
}
|
if (!mapRenderer.canZoomOut()) {
|
((Timer) event.getSource()).stop();
|
return;
|
}
|
skipZoomOutClick.set(true);
|
mapRenderer.zoomOutFromCenter();
|
if (!mapRenderer.canZoomOut()) {
|
((Timer) event.getSource()).stop();
|
}
|
});
|
zoomOutHoldTimer.setInitialDelay(180);
|
zoomOutHoldTimer.setDelay(120);
|
|
zoomInButton.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mousePressed(MouseEvent e) {
|
if (!SwingUtilities.isLeftMouseButton(e) || mapRenderer == null || !zoomInButton.isEnabled()) {
|
return;
|
}
|
if (mapRenderer.canZoomIn()) {
|
skipZoomInClick.set(true);
|
mapRenderer.zoomInFromCenter();
|
if (mapRenderer.canZoomIn()) {
|
zoomInHoldTimer.restart();
|
} else {
|
zoomInHoldTimer.stop();
|
}
|
} else {
|
skipZoomInClick.set(false);
|
zoomInHoldTimer.stop();
|
}
|
}
|
|
@Override
|
public void mouseReleased(MouseEvent e) {
|
zoomInHoldTimer.stop();
|
if (!zoomInButton.contains(e.getPoint())) {
|
skipZoomInClick.set(false);
|
}
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
if (!zoomInButton.getModel().isPressed()) {
|
zoomInHoldTimer.stop();
|
}
|
}
|
});
|
|
zoomOutButton.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mousePressed(MouseEvent e) {
|
if (!SwingUtilities.isLeftMouseButton(e) || mapRenderer == null || !zoomOutButton.isEnabled()) {
|
return;
|
}
|
if (mapRenderer.canZoomOut()) {
|
skipZoomOutClick.set(true);
|
mapRenderer.zoomOutFromCenter();
|
if (mapRenderer.canZoomOut()) {
|
zoomOutHoldTimer.restart();
|
} else {
|
zoomOutHoldTimer.stop();
|
}
|
} else {
|
skipZoomOutClick.set(false);
|
zoomOutHoldTimer.stop();
|
}
|
}
|
|
@Override
|
public void mouseReleased(MouseEvent e) {
|
zoomOutHoldTimer.stop();
|
if (!zoomOutButton.contains(e.getPoint())) {
|
skipZoomOutClick.set(false);
|
}
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
if (!zoomOutButton.getModel().isPressed()) {
|
zoomOutHoldTimer.stop();
|
}
|
}
|
});
|
|
zoomInButton.addActionListener(e -> {
|
if (skipZoomInClick.getAndSet(false)) {
|
return;
|
}
|
if (mapRenderer != null) {
|
mapRenderer.zoomInFromCenter();
|
}
|
});
|
|
zoomOutButton.addActionListener(e -> {
|
if (skipZoomOutClick.getAndSet(false)) {
|
return;
|
}
|
if (mapRenderer != null) {
|
mapRenderer.zoomOutFromCenter();
|
}
|
});
|
|
buttonStack.add(zoomInButton);
|
buttonStack.add(Box.createRigidArea(new Dimension(0, 8)));
|
buttonStack.add(zoomOutButton);
|
|
buttonStack.setAlignmentX(Component.CENTER_ALIGNMENT);
|
alignmentPanel.add(buttonStack);
|
|
container.add(alignmentPanel, BorderLayout.CENTER);
|
return container;
|
}
|
|
private JButton createZoomButton(String symbol) {
|
JButton button = new JButton(symbol);
|
Dimension size = new Dimension(44, 44);
|
button.setPreferredSize(size);
|
button.setMinimumSize(size);
|
button.setMaximumSize(size);
|
button.setFont(new Font("微软雅黑", Font.BOLD, 22));
|
button.setMargin(new Insets(0, 0, 0, 0));
|
button.setFocusPainted(false);
|
button.setBackground(Color.WHITE);
|
button.setForeground(Color.DARK_GRAY);
|
button.setBorder(BorderFactory.createLineBorder(new Color(210, 210, 210), 1, true));
|
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
button.setOpaque(true);
|
|
button.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
button.setBackground(new Color(245, 245, 245));
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
button.setBackground(Color.WHITE);
|
}
|
});
|
|
return button;
|
}
|
|
private void createControlPanel() {
|
controlPanel = new JPanel(new BorderLayout());
|
controlPanel.setBackground(PANEL_BACKGROUND);
|
controlPanel.setBorder(BorderFactory.createEmptyBorder(15, 20, 15, 20));
|
controlPanel.setPreferredSize(new Dimension(0, 100));
|
|
JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 20, 0));
|
buttonPanel.setBackground(PANEL_BACKGROUND);
|
|
startBtn = createControlButton("暂停", THEME_COLOR);
|
updateStartButtonAppearance();
|
|
stopBtn = createControlButton("结束", Color.ORANGE);
|
updateStopButtonIcon();
|
|
buttonPanel.add(startBtn);
|
buttonPanel.add(stopBtn);
|
|
controlPanel.add(buttonPanel, BorderLayout.CENTER);
|
}
|
|
private void createNavigationPanel() {
|
navigationPanel = new JPanel(new GridLayout(1, 3));
|
navigationPanel.setBackground(PANEL_BACKGROUND);
|
navigationPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
|
navigationPanel.setPreferredSize(new Dimension(0, 70));
|
|
homeNavBtn = createNavButton("首页", "🏠");
|
areasNavBtn = createNavButton("地块", "🌿");
|
settingsNavBtn = createNavButton("设置", "⚙️");
|
|
navigationPanel.add(homeNavBtn);
|
navigationPanel.add(areasNavBtn);
|
navigationPanel.add(settingsNavBtn);
|
|
// 添加到主界面底部
|
add(navigationPanel, BorderLayout.SOUTH);
|
}
|
|
private JButton createIconButton(String icon, int size) {
|
JButton button = new JButton(icon);
|
button.setFont(new Font("Segoe UI Emoji", Font.PLAIN, 20));
|
button.setPreferredSize(new Dimension(size, size));
|
button.setBackground(PANEL_BACKGROUND);
|
button.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
button.setFocusPainted(false);
|
|
// 悬停效果
|
button.addMouseListener(new MouseAdapter() {
|
public void mouseEntered(MouseEvent e) {
|
button.setBackground(new Color(240, 240, 240));
|
}
|
public void mouseExited(MouseEvent e) {
|
button.setBackground(PANEL_BACKGROUND);
|
}
|
});
|
|
return button;
|
}
|
|
private JButton createBluetoothButton() {
|
JButton button = new JButton();
|
button.setPreferredSize(new Dimension(40, 40));
|
button.setBackground(PANEL_BACKGROUND);
|
button.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
button.setFocusPainted(false);
|
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
button.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
button.setBackground(new Color(240, 240, 240));
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
button.setBackground(PANEL_BACKGROUND);
|
}
|
});
|
ensureBluetoothIconsLoaded();
|
// 根据串口连接状态显示图标
|
SerialPortService service = sendmessage.getActiveService();
|
boolean serialConnected = (service != null && service.isOpen());
|
ImageIcon initialIcon = serialConnected ? bluetoothLinkedIcon : bluetoothIcon;
|
if (initialIcon != null) {
|
button.setIcon(initialIcon);
|
} else {
|
button.setText(serialConnected ? "已连" : "蓝牙");
|
}
|
return button;
|
}
|
|
private JButton createFunctionButton(String text, String icon) {
|
JButton button = new JButton("<html><center>" + icon + "<br>" + text + "</center></html>");
|
button.setFont(new Font("微软雅黑", Font.PLAIN, 12));
|
button.setPreferredSize(new Dimension(80, 70));
|
button.setBackground(new Color(0, 0, 0, 0)); // 完全透明背景
|
button.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); // 只保留内边距
|
button.setFocusPainted(false);
|
button.setOpaque(false); // 设置为不透明,确保背景透明
|
|
// 去掉悬停效果
|
button.addMouseListener(new MouseAdapter() {
|
public void mouseEntered(MouseEvent e) {
|
// 悬停时不改变任何样式
|
}
|
public void mouseExited(MouseEvent e) {
|
// 悬停时不改变任何样式
|
}
|
});
|
|
return button;
|
}
|
|
private JButton createControlButton(String text, Color color) {
|
JButton button = new JButton(text);
|
button.setFont(new Font("微软雅黑", Font.BOLD, 16));
|
button.setBackground(color);
|
button.setForeground(Color.WHITE);
|
button.setBorder(BorderFactory.createEmptyBorder(15, 0, 15, 0));
|
button.setFocusPainted(false);
|
|
// 悬停效果
|
button.addMouseListener(new MouseAdapter() {
|
public void mouseEntered(MouseEvent e) {
|
if (button.isEnabled()) {
|
if (color == THEME_COLOR) {
|
button.setBackground(THEME_HOVER_COLOR);
|
} else {
|
button.setBackground(new Color(255, 165, 0));
|
}
|
}
|
}
|
public void mouseExited(MouseEvent e) {
|
if (button.isEnabled()) {
|
button.setBackground(color);
|
}
|
}
|
});
|
|
return button;
|
}
|
|
private void applyButtonIcon(JButton button, String imagePath) {
|
try {
|
ImageIcon icon = new ImageIcon(imagePath);
|
Image scaledImage = icon.getImage().getScaledInstance(28, 28, Image.SCALE_SMOOTH);
|
button.setIcon(new ImageIcon(scaledImage));
|
button.setHorizontalAlignment(SwingConstants.CENTER);
|
button.setIconTextGap(10);
|
button.setHorizontalTextPosition(SwingConstants.RIGHT);
|
button.setVerticalTextPosition(SwingConstants.CENTER);
|
} catch (Exception e) {
|
System.err.println("无法加载按钮图标: " + imagePath + " -> " + e.getMessage());
|
}
|
}
|
|
private JButton createNavButton(String text, String icon) {
|
JButton button = new JButton("<html><center>" + icon + "<br>" + text + "</center></html>");
|
button.setFont(new Font("微软雅黑", Font.PLAIN, 12));
|
button.setBackground(PANEL_BACKGROUND);
|
button.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
|
button.setFocusPainted(false);
|
|
// 悬停效果
|
button.addMouseListener(new MouseAdapter() {
|
public void mouseEntered(MouseEvent e) {
|
if (button != currentNavButton) {
|
button.setBackground(new Color(240, 240, 240));
|
}
|
}
|
public void mouseExited(MouseEvent e) {
|
if (button != currentNavButton) {
|
button.setBackground(PANEL_BACKGROUND);
|
}
|
}
|
});
|
|
return button;
|
}
|
|
private void setNavigationActive(JButton navButton) {
|
// 重置所有导航按钮样式
|
for (Component comp : navigationPanel.getComponents()) {
|
if (comp instanceof JButton) {
|
JButton btn = (JButton) comp;
|
btn.setBackground(PANEL_BACKGROUND);
|
btn.setForeground(Color.BLACK);
|
}
|
}
|
|
// 设置当前选中按钮样式
|
navButton.setBackground(THEME_COLOR);
|
navButton.setForeground(Color.WHITE);
|
currentNavButton = navButton;
|
}
|
|
private void setupEventHandlers() {
|
// 导航按钮事件
|
homeNavBtn.addActionListener(e -> setNavigationActive(homeNavBtn));
|
areasNavBtn.addActionListener(e -> setNavigationActive(areasNavBtn));
|
settingsNavBtn.addActionListener(e -> setNavigationActive(settingsNavBtn));
|
|
// 功能按钮事件
|
legendBtn.addActionListener(e -> showLegendDialog());
|
if (bluetoothBtn != null) {
|
bluetoothBtn.addActionListener(e -> toggleBluetoothConnection());
|
}
|
remoteBtn.addActionListener(e -> showRemoteControlDialog());
|
areaSelectBtn.addActionListener(e -> {
|
// 点击“地块”直接打开地块管理对话框(若需要可传入特定地块编号)
|
Dikuaiguanli.showDikuaiManagement(this, null);
|
});
|
baseStationBtn.addActionListener(e -> showBaseStationDialog());
|
|
// 控制按钮事件
|
startBtn.addActionListener(e -> toggleStartPause());
|
stopBtn.addActionListener(e -> handleStopAction());
|
}
|
|
private void showSettingsDialog() {
|
if (settingsDialog == null) {
|
Window parentWindow = SwingUtilities.getWindowAncestor(this);
|
if (parentWindow instanceof JFrame) {
|
settingsDialog = new Sets((JFrame) parentWindow, THEME_COLOR);
|
} else if (parentWindow instanceof JDialog) {
|
settingsDialog = new Sets((JDialog) parentWindow, THEME_COLOR);
|
} else {
|
// Fallback to a frameless dialog when no parent is available
|
settingsDialog = new Sets((JFrame) null, THEME_COLOR);
|
}
|
}
|
settingsDialog.setVisible(true);
|
}
|
|
private void showLegendDialog() {
|
if (legendDialog == null) {
|
Window parentWindow = SwingUtilities.getWindowAncestor(this);
|
if (parentWindow != null) {
|
legendDialog = new LegendDialog(this, THEME_COLOR);
|
} else {
|
// 如果没有父窗口,创建无父窗口的对话框
|
legendDialog = new LegendDialog((JFrame) null, THEME_COLOR);
|
}
|
}
|
legendDialog.setVisible(true);
|
}
|
|
private void showRemoteControlDialog() {
|
if (remoteDialog == null) {
|
Window parentWindow = SwingUtilities.getWindowAncestor(this);
|
if (parentWindow != null) {
|
// 使用 yaokong 包中的 RemoteControlDialog 实现
|
remoteDialog = new yaokong.RemoteControlDialog(this, THEME_COLOR, speedLabel);
|
} else {/* */
|
remoteDialog = new yaokong.RemoteControlDialog((JFrame) null, THEME_COLOR, speedLabel);
|
}
|
}
|
if (remoteDialog != null) {
|
positionRemoteDialogBottomCenter(remoteDialog);
|
zijian.markSelfCheckCompleted();
|
remoteDialog.setVisible(true);
|
}
|
}
|
|
private void positionRemoteDialogBottomCenter(RemoteControlDialog dialog) {
|
if (dialog == null) {
|
return;
|
}
|
// 将对话框底部与整个首页(Shouye 面板)的下边框对齐,
|
// 而不是与 visualizationPanel 对齐,以满足设计要求。
|
Dimension dialogSize = dialog.getSize();
|
|
// 获取当前 Shouye 面板在屏幕上的位置和尺寸
|
Point parentOnScreen = null;
|
try {
|
parentOnScreen = this.getLocationOnScreen();
|
} catch (IllegalComponentStateException ex) {
|
// 如果组件尚未显示或无法获取屏幕位置,回退到窗口层级位置获取
|
Window owner = SwingUtilities.getWindowAncestor(this);
|
if (owner != null) {
|
parentOnScreen = owner.getLocationOnScreen();
|
}
|
}
|
|
int x = 0, y = 0;
|
if (parentOnScreen != null) {
|
int parentWidth = this.getWidth();
|
int parentHeight = this.getHeight();
|
x = parentOnScreen.x + (parentWidth - dialogSize.width) / 2;
|
y = parentOnScreen.y + parentHeight - dialogSize.height;
|
} else {
|
// 作为最后的回退,使用屏幕中心对齐底部
|
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
|
x = (screen.width - dialogSize.width) / 2;
|
y = screen.height - dialogSize.height;
|
}
|
|
dialog.setLocation(Math.max(x, 0), Math.max(y, 0));
|
}
|
|
private Rectangle computeVisualizationBoundsOnScreen() {
|
if (visualizationPanel == null || !visualizationPanel.isShowing()) {
|
return null;
|
}
|
Point location = visualizationPanel.getLocationOnScreen();
|
Dimension size = visualizationPanel.getSize();
|
return new Rectangle(location.x, location.y, size.width, size.height);
|
}
|
|
private void showAreaSelectionDialog() {
|
if (areaDialog == null) {
|
Window parentWindow = SwingUtilities.getWindowAncestor(this);
|
if (parentWindow != null) {
|
areaDialog = new AreaSelectionDialog(this, THEME_COLOR, areaNameLabel, statusLabel);
|
} else {
|
areaDialog = new AreaSelectionDialog((JFrame) null, THEME_COLOR, areaNameLabel, statusLabel);
|
}
|
}
|
areaDialog.setVisible(true);
|
}
|
|
private void showBaseStationDialog() {
|
if (baseStation == null) {
|
baseStation = new BaseStation();
|
}
|
baseStation.load();
|
|
Component dialogParent = this;
|
|
if (!hasValidBaseStationId()) {
|
boolean recorded = promptForBaseStationId(dialogParent);
|
if (!recorded) {
|
return;
|
}
|
}
|
|
Device device = Device.getGecaoji();
|
if (device == null) {
|
device = new Device();
|
device.initFromProperties();
|
Device.setGecaoji(device);
|
}
|
|
if (baseStationDialog == null) {
|
baseStationDialog = new BaseStationDialog(dialogParent, THEME_COLOR, device, baseStation);
|
} else {
|
baseStationDialog.refreshData();
|
}
|
baseStationDialog.setVisible(true);
|
}
|
|
private void checkIdentifiersAndPromptIfNeeded() {
|
if (baseStation == null) {
|
baseStation = new BaseStation();
|
}
|
baseStation.load();
|
|
String currentMowerId = Setsys.getPropertyValue("mowerId");
|
String currentBaseStationId = baseStation.getDeviceId();
|
|
if (!isIdentifierMissing(currentMowerId) && !isIdentifierMissing(currentBaseStationId)) {
|
return;
|
}
|
|
Window owner = SwingUtilities.getWindowAncestor(this);
|
promptForMissingIdentifiers(owner, currentMowerId, currentBaseStationId);
|
}
|
|
private void promptForMissingIdentifiers(Window owner, String currentMowerId, String currentBaseStationId) {
|
while (true) {
|
JTextField mowerField = new JTextField(10);
|
JTextField baseField = new JTextField(10);
|
|
if (!isIdentifierMissing(currentMowerId)) {
|
mowerField.setText(currentMowerId.trim());
|
}
|
if (!isIdentifierMissing(currentBaseStationId)) {
|
baseField.setText(currentBaseStationId.trim());
|
}
|
|
JPanel panel = new JPanel();
|
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
panel.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
|
|
JLabel mowerLabel = new JLabel("割草机编号");
|
JLabel baseLabel = new JLabel("差分基准站编号");
|
|
mowerField.setMaximumSize(new Dimension(Integer.MAX_VALUE, mowerField.getPreferredSize().height));
|
baseField.setMaximumSize(new Dimension(Integer.MAX_VALUE, baseField.getPreferredSize().height));
|
|
panel.add(mowerLabel);
|
panel.add(Box.createVerticalStrut(4));
|
panel.add(mowerField);
|
panel.add(Box.createVerticalStrut(10));
|
panel.add(baseLabel);
|
panel.add(Box.createVerticalStrut(4));
|
panel.add(baseField);
|
|
Object[] options = {"保存", "取消"};
|
int result = JOptionPane.showOptionDialog(owner, panel, "完善设备信息",
|
JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
|
|
if (result != 0) {
|
break;
|
}
|
|
String mowerInput = mowerField.getText().trim();
|
String baseInput = baseField.getText().trim();
|
|
if (mowerInput.isEmpty()) {
|
JOptionPane.showMessageDialog(owner, "割草机编号不能为空。", "提示", JOptionPane.WARNING_MESSAGE);
|
continue;
|
}
|
|
if (baseInput.isEmpty()) {
|
JOptionPane.showMessageDialog(owner, "差分基准站编号不能为空。", "提示", JOptionPane.WARNING_MESSAGE);
|
continue;
|
}
|
|
boolean mowerSaved = persistMowerIdentifier(mowerInput);
|
boolean baseSaved = persistBaseStationIdentifier(baseInput);
|
|
if (mowerSaved && baseSaved) {
|
JOptionPane.showMessageDialog(owner, "编号已保存。", "成功", JOptionPane.INFORMATION_MESSAGE);
|
break;
|
}
|
|
StringBuilder errorBuilder = new StringBuilder();
|
if (!mowerSaved) {
|
errorBuilder.append("割草机编号保存失败。");
|
}
|
if (!baseSaved) {
|
if (errorBuilder.length() > 0) {
|
errorBuilder.append('\n');
|
}
|
errorBuilder.append("差分基准站编号保存失败。");
|
}
|
|
JOptionPane.showMessageDialog(owner, errorBuilder.toString(), "保存失败", JOptionPane.ERROR_MESSAGE);
|
|
currentMowerId = Setsys.getPropertyValue("mowerId");
|
baseStation.load();
|
currentBaseStationId = baseStation.getDeviceId();
|
}
|
}
|
|
private boolean isIdentifierMissing(String value) {
|
if (value == null) {
|
return true;
|
}
|
String trimmed = value.trim();
|
return trimmed.isEmpty() || "-1".equals(trimmed);
|
}
|
|
private boolean persistMowerIdentifier(String mowerId) {
|
try {
|
Setsys setsys = new Setsys();
|
setsys.initializeFromProperties();
|
boolean updated = setsys.updateProperty("mowerId", mowerId);
|
if (updated) {
|
Device.initializeActiveDevice(mowerId);
|
}
|
return updated;
|
} catch (Exception ex) {
|
ex.printStackTrace();
|
return false;
|
}
|
}
|
|
private boolean persistBaseStationIdentifier(String baseStationId) {
|
if (baseStation == null) {
|
baseStation = new BaseStation();
|
}
|
try {
|
baseStation.updateByDeviceId(baseStationId,
|
baseStation.getInstallationCoordinates(),
|
baseStation.getIotSimCardNumber(),
|
baseStation.getDeviceActivationTime(),
|
baseStation.getDataUpdateTime());
|
baseStation.load();
|
return true;
|
} catch (Exception ex) {
|
ex.printStackTrace();
|
return false;
|
}
|
}
|
|
private boolean hasValidBaseStationId() {
|
if (baseStation == null) {
|
return false;
|
}
|
String deviceId = baseStation.getDeviceId();
|
if (deviceId == null) {
|
return false;
|
}
|
String trimmed = deviceId.trim();
|
return !trimmed.isEmpty() && !"-1".equals(trimmed);
|
}
|
|
private boolean promptForBaseStationId(Component parentComponent) {
|
if (baseStation == null) {
|
baseStation = new BaseStation();
|
}
|
|
while (true) {
|
String input = JOptionPane.showInputDialog(parentComponent,
|
"请输入基准站编号", "录入基准站编号", JOptionPane.PLAIN_MESSAGE);
|
|
if (input == null) {
|
return false;
|
}
|
|
String trimmed = input.trim();
|
if (trimmed.isEmpty()) {
|
JOptionPane.showMessageDialog(parentComponent,
|
"基准站编号不能为空,请重新输入。", "提示", JOptionPane.WARNING_MESSAGE);
|
continue;
|
}
|
|
try {
|
baseStation.updateByDeviceId(trimmed,
|
baseStation.getInstallationCoordinates(),
|
baseStation.getIotSimCardNumber(),
|
baseStation.getDeviceActivationTime(),
|
baseStation.getDataUpdateTime());
|
baseStation.load();
|
JOptionPane.showMessageDialog(parentComponent,
|
"基准站编号已保存。", "操作成功", JOptionPane.INFORMATION_MESSAGE);
|
return true;
|
} catch (IllegalArgumentException ex) {
|
JOptionPane.showMessageDialog(parentComponent,
|
ex.getMessage(), "输入错误", JOptionPane.ERROR_MESSAGE);
|
}
|
}
|
}
|
|
private void toggleStartPause() {
|
if (handheldCaptureInlineUiActive) {
|
handleHandheldConfirmAction();
|
return;
|
}
|
if (drawingControlModeActive) {
|
toggleDrawingPause();
|
return;
|
}
|
if (startBtn == null) {
|
return;
|
}
|
if (startButtonShowingPause) {
|
// 点击开始按钮时不再弹出自检提示(按用户要求删除)
|
// 旧逻辑:调用 zijian.ensureBeforeMowing(...) 并在未确认自检时阻止开始
|
// 新逻辑:直接允许开始作业
|
}
|
startButtonShowingPause = !startButtonShowingPause;
|
if (!startButtonShowingPause) {
|
// 检查割草机是否在作业地块边界范围内
|
if (!checkMowerInBoundary()) {
|
startButtonShowingPause = true;
|
statusLabel.setText("待机");
|
updateStartButtonAppearance();
|
return;
|
}
|
|
statusLabel.setText("作业中");
|
if (stopButtonActive) {
|
stopButtonActive = false;
|
updateStopButtonIcon();
|
}
|
if (!beginMowingSession()) {
|
startButtonShowingPause = true;
|
statusLabel.setText("待机");
|
updateStartButtonAppearance();
|
return;
|
}
|
} else {
|
statusLabel.setText("暂停中");
|
pauseMowingSession();
|
}
|
updateStartButtonAppearance();
|
}
|
|
/**
|
* 检查割草机是否在当前选中的作业地块边界范围内
|
* @return 如果割草机在边界内返回true,否则返回false并显示提示
|
*/
|
private boolean checkMowerInBoundary() {
|
if (mapRenderer == null) {
|
return true; // 如果没有地图渲染器,跳过检查
|
}
|
|
// 获取当前边界
|
List<Point2D.Double> boundary = mapRenderer.getCurrentBoundary();
|
if (boundary == null || boundary.size() < 3) {
|
return true; // 如果没有边界或边界点不足,跳过检查
|
}
|
|
// 获取割草机位置
|
Gecaoji mower = mapRenderer.getMower();
|
if (mower == null || !mower.hasValidPosition()) {
|
showCustomMessageDialog("无法获取割草机位置,请检查设备连接", "提示");
|
return false;
|
}
|
|
Point2D.Double mowerPosition = mower.getPosition();
|
if (mowerPosition == null) {
|
showCustomMessageDialog("无法获取割草机位置,请检查设备连接", "提示");
|
return false;
|
}
|
|
// 使用 MowerBoundaryChecker 检查是否在边界内
|
boolean isInside = MowerBoundaryChecker.isInsideBoundaryPoints(
|
boundary,
|
mowerPosition.x,
|
mowerPosition.y
|
);
|
|
if (!isInside) {
|
showCustomMessageDialog("请将割草机开到作业地块内然后点击开始作业", "提示");
|
return false;
|
}
|
|
return true;
|
}
|
|
/**
|
* 显示自定义消息对话框,使用 buttonset 创建确定按钮
|
* @param message 消息内容
|
* @param title 对话框标题
|
*/
|
private void showCustomMessageDialog(String message, String title) {
|
Window parentWindow = SwingUtilities.getWindowAncestor(this);
|
JDialog dialog = new JDialog(parentWindow, title, Dialog.ModalityType.APPLICATION_MODAL);
|
dialog.setLayout(new BorderLayout(20, 20));
|
dialog.setResizable(false);
|
|
// 内容面板
|
JPanel contentPanel = new JPanel(new BorderLayout(0, 15));
|
contentPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 10, 20));
|
contentPanel.setBackground(Color.WHITE);
|
|
// 消息标签
|
JLabel messageLabel = new JLabel("<html><div style='text-align: center;'>" + message + "</div></html>");
|
messageLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14));
|
messageLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
contentPanel.add(messageLabel, BorderLayout.CENTER);
|
|
// 按钮面板
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 0));
|
buttonPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
|
buttonPanel.setOpaque(false);
|
|
// 使用 buttonset 创建确定按钮
|
JButton okButton = buttonset.createStyledButton("确定", THEME_COLOR);
|
okButton.addActionListener(e -> dialog.dispose());
|
buttonPanel.add(okButton);
|
|
contentPanel.add(buttonPanel, BorderLayout.SOUTH);
|
dialog.add(contentPanel, BorderLayout.CENTER);
|
|
dialog.pack();
|
dialog.setLocationRelativeTo(this);
|
dialog.setVisible(true);
|
}
|
|
private void handleStopAction() {
|
if (handheldCaptureInlineUiActive) {
|
handleHandheldFinishAction();
|
return;
|
}
|
if (drawingControlModeActive) {
|
handleDrawingStopFromControlPanel();
|
return;
|
}
|
stopButtonActive = !stopButtonActive;
|
updateStopButtonIcon();
|
if (stopButtonActive) {
|
statusLabel.setText("已结束");
|
startButtonShowingPause = false;
|
stopMowingSession();
|
} else {
|
statusLabel.setText("待机");
|
startButtonShowingPause = true;
|
pauseMowingSession();
|
}
|
updateStartButtonAppearance();
|
}
|
|
private void handleDrawingStopFromControlPanel() {
|
if (endDrawingCallback != null) {
|
endDrawingCallback.run();
|
} else {
|
addzhangaiwu.finishDrawingSession();
|
}
|
}
|
|
private void handleHandheldConfirmAction() {
|
if (!handheldCaptureInlineUiActive) {
|
return;
|
}
|
if (!canConfirmHandheldPoint()) {
|
refreshHandheldCaptureUiState();
|
return;
|
}
|
int count = captureHandheldBoundaryPoint();
|
if (count <= 0) {
|
refreshHandheldCaptureUiState();
|
return;
|
}
|
refreshHandheldCaptureUiState();
|
}
|
|
private void handleHandheldFinishAction() {
|
if (!handheldCaptureInlineUiActive) {
|
return;
|
}
|
if (stopBtn != null && !stopBtn.isEnabled()) {
|
refreshHandheldCaptureUiState();
|
return;
|
}
|
if (!finishHandheldBoundaryCapture()) {
|
refreshHandheldCaptureUiState();
|
}
|
}
|
|
private void enterHandheldCaptureInlineUi() {
|
if (handheldCaptureInlineUiActive) {
|
refreshHandheldCaptureUiState();
|
return;
|
}
|
handheldCaptureInlineUiActive = true;
|
handheldCaptureStoredStatusText = statusLabel != null ? statusLabel.getText() : null;
|
if (statusLabel != null) {
|
statusLabel.setText("手持采集中");
|
}
|
if (startBtn != null) {
|
handheldStartButtonOriginalBackground = startBtn.getBackground();
|
handheldStartButtonOriginalForeground = startBtn.getForeground();
|
startBtn.setIcon(null);
|
startBtn.setIconTextGap(0);
|
startBtn.setHorizontalAlignment(SwingConstants.CENTER);
|
startBtn.setHorizontalTextPosition(SwingConstants.CENTER);
|
startBtn.setVerticalTextPosition(SwingConstants.CENTER);
|
}
|
if (stopBtn != null) {
|
handheldStopButtonOriginalBackground = stopBtn.getBackground();
|
handheldStopButtonOriginalForeground = stopBtn.getForeground();
|
stopBtn.setIcon(null);
|
stopBtn.setIconTextGap(0);
|
stopBtn.setHorizontalAlignment(SwingConstants.CENTER);
|
stopBtn.setHorizontalTextPosition(SwingConstants.CENTER);
|
stopBtn.setVerticalTextPosition(SwingConstants.CENTER);
|
stopBtn.setText("结束");
|
}
|
startHandheldCaptureStatusTimer();
|
refreshHandheldCaptureUiState();
|
}
|
|
private void exitHandheldCaptureInlineUi() {
|
if (!handheldCaptureInlineUiActive) {
|
return;
|
}
|
handheldCaptureInlineUiActive = false;
|
stopHandheldCaptureStatusTimer();
|
if (statusLabel != null) {
|
statusLabel.setText(handheldCaptureStoredStatusText != null ? handheldCaptureStoredStatusText : "待机");
|
}
|
if (startBtn != null) {
|
startBtn.setToolTipText(null);
|
if (handheldStartButtonOriginalBackground != null) {
|
startBtn.setBackground(handheldStartButtonOriginalBackground);
|
}
|
if (handheldStartButtonOriginalForeground != null) {
|
startBtn.setForeground(handheldStartButtonOriginalForeground);
|
}
|
startBtn.setEnabled(true);
|
updateStartButtonAppearance();
|
}
|
if (stopBtn != null) {
|
stopBtn.setToolTipText(null);
|
if (handheldStopButtonOriginalBackground != null) {
|
stopBtn.setBackground(handheldStopButtonOriginalBackground);
|
}
|
if (handheldStopButtonOriginalForeground != null) {
|
stopBtn.setForeground(handheldStopButtonOriginalForeground);
|
}
|
stopBtn.setEnabled(true);
|
stopBtn.setText("结束");
|
updateStopButtonIcon();
|
}
|
handheldCaptureStoredStatusText = null;
|
handheldStartButtonOriginalBackground = null;
|
handheldStartButtonOriginalForeground = null;
|
handheldStopButtonOriginalBackground = null;
|
handheldStopButtonOriginalForeground = null;
|
}
|
|
private void startHandheldCaptureStatusTimer() {
|
if (handheldCaptureStatusTimer == null) {
|
handheldCaptureStatusTimer = new Timer(400, e -> refreshHandheldCaptureUiState());
|
handheldCaptureStatusTimer.setRepeats(true);
|
}
|
if (!handheldCaptureStatusTimer.isRunning()) {
|
handheldCaptureStatusTimer.start();
|
}
|
}
|
|
private void stopHandheldCaptureStatusTimer() {
|
if (handheldCaptureStatusTimer != null && handheldCaptureStatusTimer.isRunning()) {
|
handheldCaptureStatusTimer.stop();
|
}
|
}
|
|
// Update inline handheld capture buttons based on the current device reading.
|
private void refreshHandheldCaptureUiState() {
|
if (!handheldCaptureInlineUiActive) {
|
return;
|
}
|
int nextIndex = handheldCapturedPoints + 1;
|
boolean hasFix = hasHighPrecisionFix();
|
boolean hasValid = hasValidRealtimeHandheldPosition();
|
boolean duplicate = hasValid && isCurrentHandheldPointDuplicate();
|
boolean canConfirm = handheldCaptureActive && hasFix && hasValid && !duplicate;
|
|
if (startBtn != null) {
|
String prompt = "<html><center>采集点" + nextIndex + "<br>确定</center></html>";
|
startBtn.setText(prompt);
|
startBtn.setEnabled(canConfirm);
|
if (canConfirm) {
|
if (handheldStartButtonOriginalBackground != null) {
|
startBtn.setBackground(handheldStartButtonOriginalBackground);
|
}
|
if (handheldStartButtonOriginalForeground != null) {
|
startBtn.setForeground(handheldStartButtonOriginalForeground);
|
}
|
startBtn.setToolTipText(null);
|
} else {
|
startBtn.setBackground(new Color(200, 200, 200));
|
startBtn.setForeground(new Color(130, 130, 130));
|
startBtn.setToolTipText(resolveHandheldConfirmTooltip(hasFix, hasValid, duplicate));
|
}
|
}
|
|
if (stopBtn != null) {
|
boolean canFinish = handheldCapturedPoints >= 3;
|
stopBtn.setText("结束");
|
stopBtn.setEnabled(canFinish);
|
if (canFinish) {
|
if (handheldStopButtonOriginalBackground != null) {
|
stopBtn.setBackground(handheldStopButtonOriginalBackground);
|
}
|
if (handheldStopButtonOriginalForeground != null) {
|
stopBtn.setForeground(handheldStopButtonOriginalForeground);
|
}
|
stopBtn.setToolTipText("结束采集并返回新增地块");
|
} else {
|
stopBtn.setBackground(new Color(220, 220, 220));
|
stopBtn.setForeground(new Color(130, 130, 130));
|
stopBtn.setToolTipText("至少采集三个点才能结束");
|
}
|
}
|
}
|
|
private String resolveHandheldConfirmTooltip(boolean hasFix, boolean hasValidPosition, boolean duplicate) {
|
if (!hasFix) {
|
return "当前定位质量不足,无法采集";
|
}
|
if (!hasValidPosition) {
|
return "当前定位数据无效,请稍后再试";
|
}
|
if (duplicate) {
|
return "当前坐标已采集,请移动到新的位置";
|
}
|
return null;
|
}
|
|
private boolean hasHighPrecisionFix() {
|
Device device = Device.getGecaoji();
|
if (device == null) {
|
return false;
|
}
|
String status = device.getPositioningStatus();
|
return status != null && "4".equals(status.trim());
|
}
|
|
private boolean canConfirmHandheldPoint() {
|
return handheldCaptureActive
|
&& hasHighPrecisionFix()
|
&& hasValidRealtimeHandheldPosition()
|
&& !isCurrentHandheldPointDuplicate();
|
}
|
|
private void enterDrawingControlMode() {
|
if (drawingControlModeActive) {
|
return;
|
}
|
storedStartButtonShowingPause = startButtonShowingPause;
|
storedStopButtonActive = stopButtonActive;
|
storedStatusBeforeDrawing = statusLabel != null ? statusLabel.getText() : null;
|
drawingControlModeActive = true;
|
applyDrawingPauseState(false, false);
|
updateDrawingControlButtonLabels();
|
}
|
|
private void exitDrawingControlMode() {
|
if (!drawingControlModeActive) {
|
return;
|
}
|
drawingControlModeActive = false;
|
applyDrawingPauseState(false, false);
|
drawingPaused = false;
|
stopButtonActive = storedStopButtonActive;
|
startButtonShowingPause = storedStartButtonShowingPause;
|
if (startBtn != null) {
|
updateStartButtonAppearance();
|
}
|
if (stopBtn != null) {
|
stopBtn.setText("结束");
|
updateStopButtonIcon();
|
}
|
if (statusLabel != null) {
|
statusLabel.setText(storedStatusBeforeDrawing != null ? storedStatusBeforeDrawing : "待机");
|
}
|
storedStatusBeforeDrawing = null;
|
}
|
|
private void updateDrawingControlButtonLabels() {
|
if (!drawingControlModeActive) {
|
return;
|
}
|
configureButtonForDrawingMode(startBtn);
|
configureButtonForDrawingMode(stopBtn);
|
if (startBtn != null) {
|
startBtn.setText(drawingPaused ? "开始绘制" : "暂停绘制");
|
}
|
if (stopBtn != null) {
|
stopBtn.setText("结束绘制");
|
}
|
}
|
|
private void configureButtonForDrawingMode(JButton button) {
|
if (button == null) {
|
return;
|
}
|
button.setIcon(null);
|
button.setIconTextGap(0);
|
button.setHorizontalAlignment(SwingConstants.CENTER);
|
button.setHorizontalTextPosition(SwingConstants.CENTER);
|
}
|
|
private void updateStartButtonAppearance() {
|
if (startBtn == null) {
|
return;
|
}
|
String iconPath = startButtonShowingPause ? "image/start0.png" : "image/start1.png";
|
startBtn.setText(startButtonShowingPause ? "暂停" : "开始");
|
applyButtonIcon(startBtn, iconPath);
|
}
|
|
private void updateStopButtonIcon() {
|
if (stopBtn == null) {
|
return;
|
}
|
String iconPath = stopButtonActive ? "image/stop1.png" : "image/stop0.png";
|
applyButtonIcon(stopBtn, iconPath);
|
}
|
|
private void toggleBluetoothConnection() {
|
if (bluetoothBtn == null) {
|
return;
|
}
|
// 弹出系统调试页面
|
showDebugDialog();
|
}
|
|
private void showDebugDialog() {
|
Window parentWindow = SwingUtilities.getWindowAncestor(this);
|
debug debugDialog = new debug(parentWindow, THEME_COLOR);
|
debugDialog.setLocationRelativeTo(this); // 居中显示在首页
|
debugDialog.setVisible(true);
|
}
|
|
private void updateBluetoothButtonIcon() {
|
if (bluetoothBtn == null) {
|
return;
|
}
|
ensureBluetoothIconsLoaded();
|
// 根据串口连接状态显示图标
|
SerialPortService service = sendmessage.getActiveService();
|
boolean serialConnected = (service != null && service.isOpen());
|
ImageIcon icon = serialConnected ? bluetoothLinkedIcon : bluetoothIcon;
|
if (icon != null) {
|
bluetoothBtn.setIcon(icon);
|
bluetoothBtn.setText(null);
|
} else {
|
bluetoothBtn.setText(serialConnected ? "已连" : "蓝牙");
|
}
|
}
|
|
private JPanel createSpeedIndicatorPanel() {
|
JPanel panel = new JPanel(new BorderLayout());
|
panel.setOpaque(false);
|
panel.setBorder(BorderFactory.createEmptyBorder(10, 20, 5, 20));
|
|
JPanel rightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0));
|
rightPanel.setOpaque(false);
|
|
fixQualityIndicator = new gpszhuangtai(THEME_COLOR);
|
fixQualityIndicator.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
fixQualityIndicator.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseClicked(MouseEvent e) {
|
if (SwingUtilities.isLeftMouseButton(e) && mapRenderer != null) {
|
mapRenderer.showMowerInfo();
|
}
|
}
|
});
|
mowingProgressLabel = new JLabel("--%");
|
mowingProgressLabel.setFont(new Font("微软雅黑", Font.BOLD, 12));
|
mowingProgressLabel.setForeground(THEME_COLOR);
|
|
mowerSpeedValueLabel = new JLabel("--");
|
mowerSpeedValueLabel.setFont(new Font("微软雅黑", Font.BOLD, 14));
|
mowerSpeedValueLabel.setForeground(THEME_COLOR);
|
|
mowerSpeedUnitLabel = new JLabel("km/h");
|
mowerSpeedUnitLabel.setFont(new Font("微软雅黑", Font.BOLD, 9));
|
mowerSpeedUnitLabel.setForeground(THEME_COLOR);
|
|
dataPacketCountLabel = new JLabel("--");
|
dataPacketCountLabel.setFont(new Font("微软雅黑", Font.BOLD, 12));
|
dataPacketCountLabel.setForeground(THEME_COLOR);
|
|
rightPanel.add(fixQualityIndicator);
|
|
JSeparator areaSeparator = new JSeparator(SwingConstants.VERTICAL);
|
areaSeparator.setPreferredSize(new Dimension(1, 16));
|
rightPanel.add(areaSeparator);
|
|
rightPanel.add(mowingProgressLabel);
|
JSeparator speedSeparator = new JSeparator(SwingConstants.VERTICAL);
|
speedSeparator.setPreferredSize(new Dimension(1, 16));
|
rightPanel.add(speedSeparator);
|
rightPanel.add(mowerSpeedValueLabel);
|
rightPanel.add(mowerSpeedUnitLabel);
|
|
JSeparator separator = new JSeparator(SwingConstants.VERTICAL);
|
separator.setPreferredSize(new Dimension(1, 16));
|
rightPanel.add(separator);
|
|
rightPanel.add(dataPacketCountLabel);
|
|
panel.add(rightPanel, BorderLayout.EAST);
|
updateFixQualityIndicator();
|
updateDataPacketCountLabel();
|
return panel;
|
}
|
|
private void startMowerSpeedUpdates() {
|
if (mowerSpeedRefreshTimer == null) {
|
mowerSpeedRefreshTimer = new Timer(1000, e -> refreshMowerSpeedLabel());
|
mowerSpeedRefreshTimer.setRepeats(true);
|
}
|
if (!mowerSpeedRefreshTimer.isRunning()) {
|
mowerSpeedRefreshTimer.start();
|
}
|
refreshMowerSpeedLabel();
|
}
|
|
private void refreshMowerSpeedLabel() {
|
if (mowerSpeedValueLabel == null) {
|
return;
|
}
|
String display = "--";
|
Device device = Device.getGecaoji();
|
if (device != null) {
|
String sanitized = sanitizeSpeedValue(device.getRealtimeSpeed());
|
if (sanitized != null) {
|
display = sanitized;
|
}
|
}
|
mowerSpeedValueLabel.setText(display);
|
if (mowerSpeedUnitLabel != null) {
|
mowerSpeedUnitLabel.setText("km/h");
|
}
|
updateMowingProgressLabel();
|
updateFixQualityIndicator();
|
updateDataPacketCountLabel();
|
}
|
|
private void updateDataPacketCountLabel() {
|
if (dataPacketCountLabel == null) {
|
return;
|
}
|
int udpCount = UDPServer.getReceivedPacketCount();
|
int serialCount = dellmessage.getProcessedLineCount();
|
int displayCount = Math.max(udpCount, serialCount);
|
|
if (displayCount <= 0) {
|
dataPacketCountLabel.setText("--");
|
dataPacketCountLabel.setToolTipText(null);
|
} else {
|
dataPacketCountLabel.setText(String.valueOf(displayCount));
|
dataPacketCountLabel.setToolTipText(String.format("串口: %d UDP: %d", serialCount, udpCount));
|
}
|
}
|
|
private void updateFixQualityIndicator() {
|
if (fixQualityIndicator == null) {
|
return;
|
}
|
Device device = Device.getGecaoji();
|
String code = null;
|
if (device != null) {
|
code = sanitizeDeviceValue(device.getPositioningStatus());
|
}
|
fixQualityIndicator.setQuality(code);
|
}
|
|
private Color resolveFixQualityColor(String code) {
|
if (code == null) {
|
return new Color(160, 160, 160);
|
}
|
switch (code) {
|
case "0":
|
return new Color(160, 160, 160);
|
case "1":
|
return new Color(52, 152, 219);
|
case "2":
|
return new Color(26, 188, 156);
|
case "3":
|
return new Color(155, 89, 182);
|
case "4":
|
return THEME_COLOR;
|
case "5":
|
return new Color(241, 196, 15);
|
case "6":
|
return new Color(231, 76, 60);
|
case "7":
|
return new Color(230, 126, 34);
|
default:
|
return new Color(95, 95, 95);
|
}
|
}
|
|
private String resolveFixQualityDescription(String code) {
|
if (code == null) {
|
return "未知";
|
}
|
switch (code) {
|
case "0":
|
return "未定位";
|
case "1":
|
return "单点定位";
|
case "2":
|
return "码差分";
|
case "3":
|
return "无效PPS";
|
case "4":
|
return "固定解";
|
case "5":
|
return "浮点解";
|
case "6":
|
return "正在估算";
|
case "7":
|
return "人工输入固定值";
|
default:
|
return "其他";
|
}
|
}
|
|
private String sanitizeSpeedValue(String raw) {
|
if (raw == null) {
|
return null;
|
}
|
String trimmed = raw.trim();
|
if (trimmed.isEmpty() || "-1".equals(trimmed)) {
|
return null;
|
}
|
if (trimmed.toLowerCase().endsWith("km/h")) {
|
trimmed = trimmed.substring(0, trimmed.length() - 4).trim();
|
}
|
return trimmed;
|
}
|
|
private String sanitizeDeviceValue(String raw) {
|
if (raw == null) {
|
return null;
|
}
|
String trimmed = raw.trim();
|
if (trimmed.isEmpty() || "-1".equals(trimmed) || "null".equalsIgnoreCase(trimmed)) {
|
return null;
|
}
|
return trimmed;
|
}
|
|
private void updateMowingProgressLabel() {
|
if (mowingProgressLabel == null) {
|
return;
|
}
|
if (mapRenderer == null) {
|
mowingProgressLabel.setText("--%");
|
mowingProgressLabel.setToolTipText(null);
|
return;
|
}
|
|
double totalArea = mapRenderer.getTotalLandAreaSqMeters();
|
double completedArea = mapRenderer.getCompletedMowingAreaSqMeters();
|
double ratio = mapRenderer.getMowingCompletionRatio();
|
|
if (totalArea <= 0) {
|
mowingProgressLabel.setText("--%");
|
mowingProgressLabel.setToolTipText("暂无地块面积数据");
|
return;
|
}
|
|
double percent = Math.max(0.0, Math.min(1.0, ratio)) * 100.0;
|
mowingProgressLabel.setText(String.format(Locale.US, "%.1f%%", percent));
|
mowingProgressLabel.setToolTipText(String.format(Locale.US, "%.1f㎡ / %.1f㎡", completedArea, totalArea));
|
}
|
|
public void refreshMowingIndicators() {
|
refreshMowerSpeedLabel();
|
}
|
|
public void setHandheldMowerIconActive(boolean active) {
|
if (mapRenderer == null) {
|
return;
|
}
|
mapRenderer.setHandheldMowerIconActive(active);
|
}
|
|
public boolean startMowerBoundaryCapture() {
|
if (mapRenderer == null) {
|
return false;
|
}
|
double[] baseLatLonCandidate = resolveCircleBaseLatLon();
|
if (baseLatLonCandidate == null) {
|
return false;
|
}
|
|
mapRenderer.clearIdleTrail();
|
|
activeBoundaryMode = BoundaryCaptureMode.MOWER;
|
mowerBoundaryCaptureActive = true;
|
mowerBaseLatLon = baseLatLonCandidate;
|
lastMowerCoordinate = null;
|
|
synchronized (Coordinate.coordinates) {
|
Coordinate.coordinates.clear();
|
}
|
synchronized (mowerTemporaryPoints) {
|
mowerTemporaryPoints.clear();
|
}
|
|
AddDikuai.recordTemporaryBoundaryPoints(Collections.emptyList());
|
Coordinate.setStartSaveGngga(true);
|
|
if (mapRenderer != null) {
|
mapRenderer.setBoundaryPreviewMarkerScale(2.0d);
|
mapRenderer.beginHandheldBoundaryPreview();
|
}
|
|
setHandheldMowerIconActive(false);
|
|
startMowerBoundaryMonitor();
|
return true;
|
}
|
|
public boolean startHandheldBoundaryCapture() {
|
if (mapRenderer == null) {
|
return false;
|
}
|
if (activeBoundaryMode == BoundaryCaptureMode.MOWER) {
|
stopMowerBoundaryCapture();
|
}
|
|
mapRenderer.clearIdleTrail();
|
|
activeBoundaryMode = BoundaryCaptureMode.HANDHELD;
|
handheldCaptureActive = true;
|
handheldCapturedPoints = 0;
|
Coordinate.setStartSaveGngga(false);
|
synchronized (Coordinate.coordinates) {
|
Coordinate.coordinates.clear();
|
}
|
synchronized (handheldTemporaryPoints) {
|
handheldTemporaryPoints.clear();
|
}
|
AddDikuai.recordTemporaryBoundaryPoints(Collections.emptyList());
|
mapRenderer.setBoundaryPreviewMarkerScale(1.0d);
|
mapRenderer.beginHandheldBoundaryPreview();
|
setHandheldMowerIconActive(true);
|
enterHandheldCaptureInlineUi();
|
return true;
|
}
|
|
private void startMowerBoundaryMonitor() {
|
if (mowerBoundaryMonitor == null) {
|
mowerBoundaryMonitor = new Timer(600, e -> pollMowerBoundaryCoordinate());
|
mowerBoundaryMonitor.setRepeats(true);
|
}
|
if (!mowerBoundaryMonitor.isRunning()) {
|
mowerBoundaryMonitor.start();
|
}
|
pollMowerBoundaryCoordinate();
|
}
|
|
private void stopMowerBoundaryMonitor() {
|
if (mowerBoundaryMonitor != null && mowerBoundaryMonitor.isRunning()) {
|
mowerBoundaryMonitor.stop();
|
}
|
}
|
|
private void pollMowerBoundaryCoordinate() {
|
if (!mowerBoundaryCaptureActive) {
|
return;
|
}
|
|
Coordinate latest = getLatestCoordinate();
|
if (latest == null || latest == lastMowerCoordinate) {
|
return;
|
}
|
|
double[] base = mowerBaseLatLon;
|
if (base == null || base.length < 2) {
|
discardLatestCoordinate(latest);
|
lastMowerCoordinate = latest;
|
return;
|
}
|
|
double lat = parseDMToDecimal(latest.getLatitude(), latest.getLatDirection());
|
double lon = parseDMToDecimal(latest.getLongitude(), latest.getLonDirection());
|
if (!Double.isFinite(lat) || !Double.isFinite(lon)) {
|
discardLatestCoordinate(latest);
|
lastMowerCoordinate = latest;
|
return;
|
}
|
|
double[] local = convertLatLonToLocal(lat, lon, base[0], base[1]);
|
Point2D.Double candidate = new Point2D.Double(local[0], local[1]);
|
if (!Double.isFinite(candidate.x) || !Double.isFinite(candidate.y)) {
|
discardLatestCoordinate(latest);
|
lastMowerCoordinate = latest;
|
return;
|
}
|
|
List<Point2D.Double> snapshot;
|
synchronized (mowerTemporaryPoints) {
|
for (Point2D.Double existing : mowerTemporaryPoints) {
|
if (existing != null && arePointsClose(existing, candidate)) {
|
discardLatestCoordinate(latest);
|
lastMowerCoordinate = latest;
|
return;
|
}
|
}
|
mowerTemporaryPoints.add(candidate);
|
snapshot = new ArrayList<>(mowerTemporaryPoints.size() + 1);
|
for (Point2D.Double point : mowerTemporaryPoints) {
|
if (point != null) {
|
snapshot.add(new Point2D.Double(point.x, point.y));
|
}
|
}
|
}
|
|
ensureClosed(snapshot);
|
AddDikuai.recordTemporaryBoundaryPoints(snapshot);
|
if (mapRenderer != null) {
|
mapRenderer.addHandheldBoundaryPoint(candidate.x, candidate.y);
|
}
|
lastMowerCoordinate = latest;
|
}
|
|
private void stopMowerBoundaryCapture() {
|
stopMowerBoundaryMonitor();
|
mowerBoundaryCaptureActive = false;
|
lastMowerCoordinate = null;
|
mowerBaseLatLon = null;
|
if (mapRenderer != null) {
|
mapRenderer.clearHandheldBoundaryPreview();
|
}
|
Coordinate.setStartSaveGngga(false);
|
if (activeBoundaryMode == BoundaryCaptureMode.MOWER) {
|
activeBoundaryMode = BoundaryCaptureMode.NONE;
|
}
|
setHandheldMowerIconActive(false);
|
}
|
|
private void discardLatestCoordinate(Coordinate coordinate) {
|
if (coordinate == null) {
|
return;
|
}
|
synchronized (Coordinate.coordinates) {
|
int size = Coordinate.coordinates.size();
|
if (size == 0) {
|
return;
|
}
|
int lastIndex = size - 1;
|
if (Coordinate.coordinates.get(lastIndex) == coordinate) {
|
Coordinate.coordinates.remove(lastIndex);
|
} else {
|
Coordinate.coordinates.remove(coordinate);
|
}
|
}
|
}
|
|
private void ensureClosed(List<Point2D.Double> points) {
|
if (points == null || points.size() < 3) {
|
return;
|
}
|
Point2D.Double first = points.get(0);
|
Point2D.Double last = points.get(points.size() - 1);
|
if (first == null || last == null) {
|
return;
|
}
|
if (!arePointsClose(first, last)) {
|
points.add(new Point2D.Double(first.x, first.y));
|
}
|
}
|
|
int captureHandheldBoundaryPoint() {
|
if (!handheldCaptureActive) {
|
return -1;
|
}
|
Device device = Device.getGecaoji();
|
if (device == null) {
|
JOptionPane.showMessageDialog(this, "未检测到采集设备,请检查连接。", "提示", JOptionPane.WARNING_MESSAGE);
|
return -1;
|
}
|
|
String[] latParts = splitCoordinateComponents(device.getRealtimeLatitude(), true);
|
String[] lonParts = splitCoordinateComponents(device.getRealtimeLongitude(), false);
|
if (latParts == null || lonParts == null) {
|
JOptionPane.showMessageDialog(this, "当前定位无效,请在定位稳定后再试。", "提示", JOptionPane.WARNING_MESSAGE);
|
return -1;
|
}
|
|
double x = parseMetersValue(device.getRealtimeX());
|
double y = parseMetersValue(device.getRealtimeY());
|
if (!Double.isFinite(x) || !Double.isFinite(y)) {
|
JOptionPane.showMessageDialog(this, "当前定位数据无效,请稍后再试。", "提示", JOptionPane.WARNING_MESSAGE);
|
return -1;
|
}
|
if (isDuplicateHandheldPoint(x, y)) {
|
JOptionPane.showMessageDialog(this, "当前坐标已采集,请移动到新的位置后再试。", "提示", JOptionPane.WARNING_MESSAGE);
|
return -1;
|
}
|
|
double altitude = parseAltitudeValue(device.getRealtimeAltitude());
|
Coordinate coordinate = new Coordinate(latParts[0], latParts[1], lonParts[0], lonParts[1], altitude);
|
synchronized (Coordinate.coordinates) {
|
Coordinate.coordinates.add(coordinate);
|
}
|
|
if (mapRenderer != null) {
|
mapRenderer.addHandheldBoundaryPoint(x, y);
|
}
|
|
List<Point2D.Double> snapshot;
|
synchronized (handheldTemporaryPoints) {
|
handheldTemporaryPoints.add(new Point2D.Double(x, y));
|
snapshot = new ArrayList<>(handheldTemporaryPoints);
|
}
|
AddDikuai.recordTemporaryBoundaryPoints(snapshot);
|
|
handheldCapturedPoints++;
|
return handheldCapturedPoints;
|
}
|
|
boolean finishHandheldBoundaryCapture() {
|
if (!handheldCaptureActive) {
|
return false;
|
}
|
if (handheldCapturedPoints < 3) {
|
JOptionPane.showMessageDialog(this, "至少采集三个点才能生成边界。", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
|
List<Point2D.Double> closedSnapshot = createClosedHandheldPointSnapshot();
|
handheldCaptureActive = false;
|
activeBoundaryMode = BoundaryCaptureMode.NONE;
|
Coordinate.setStartSaveGngga(false);
|
if (mapRenderer != null) {
|
mapRenderer.clearHandheldBoundaryPreview();
|
}
|
|
AddDikuai.recordTemporaryBoundaryPoints(closedSnapshot);
|
|
exitHandheldCaptureInlineUi();
|
|
SwingUtilities.invokeLater(AddDikuai::finishDrawingSession);
|
return true;
|
}
|
|
int getHandheldCapturedPointCount() {
|
return handheldCapturedPoints;
|
}
|
|
public List<Point2D.Double> getHandheldTemporaryPointsSnapshot() {
|
if (activeBoundaryMode == BoundaryCaptureMode.MOWER) {
|
return createClosedMowerPointSnapshot();
|
}
|
if (!handheldCaptureActive) {
|
return createClosedHandheldPointSnapshot();
|
}
|
synchronized (handheldTemporaryPoints) {
|
return new ArrayList<>(handheldTemporaryPoints);
|
}
|
}
|
|
public boolean isCurrentHandheldPointDuplicate() {
|
Device device = Device.getGecaoji();
|
if (device == null) {
|
return false;
|
}
|
double x = parseMetersValue(device.getRealtimeX());
|
double y = parseMetersValue(device.getRealtimeY());
|
if (!Double.isFinite(x) || !Double.isFinite(y)) {
|
return false;
|
}
|
return isDuplicateHandheldPoint(x, y);
|
}
|
|
public boolean hasValidRealtimeHandheldPosition() {
|
Device device = Device.getGecaoji();
|
if (device == null) {
|
return false;
|
}
|
double x = parseMetersValue(device.getRealtimeX());
|
double y = parseMetersValue(device.getRealtimeY());
|
return Double.isFinite(x) && Double.isFinite(y);
|
}
|
|
private boolean isDuplicateHandheldPoint(double x, double y) {
|
Point2D.Double candidate = new Point2D.Double(x, y);
|
synchronized (handheldTemporaryPoints) {
|
for (Point2D.Double existing : handheldTemporaryPoints) {
|
if (existing == null) {
|
continue;
|
}
|
if (arePointsClose(existing, candidate)) {
|
return true;
|
}
|
}
|
}
|
return false;
|
}
|
|
private boolean arePointsClose(Point2D.Double a, Point2D.Double b) {
|
if (a == null || b == null) {
|
return false;
|
}
|
double dx = a.x - b.x;
|
double dy = a.y - b.y;
|
return Math.hypot(dx, dy) < HANDHELD_DUPLICATE_THRESHOLD_METERS;
|
}
|
|
private List<Point2D.Double> createClosedHandheldPointSnapshot() {
|
List<Point2D.Double> copy = new ArrayList<>();
|
synchronized (handheldTemporaryPoints) {
|
for (Point2D.Double point : handheldTemporaryPoints) {
|
if (point != null) {
|
copy.add(new Point2D.Double(point.x, point.y));
|
}
|
}
|
}
|
ensureClosed(copy);
|
return copy;
|
}
|
|
private List<Point2D.Double> createClosedMowerPointSnapshot() {
|
List<Point2D.Double> copy = new ArrayList<>();
|
synchronized (mowerTemporaryPoints) {
|
for (Point2D.Double point : mowerTemporaryPoints) {
|
if (point != null) {
|
copy.add(new Point2D.Double(point.x, point.y));
|
}
|
}
|
}
|
ensureClosed(copy);
|
return copy;
|
}
|
|
private String[] splitCoordinateComponents(String combined, boolean latitude) {
|
if (combined == null) {
|
return null;
|
}
|
String trimmed = combined.trim();
|
if (trimmed.isEmpty() || "-1".equals(trimmed)) {
|
return null;
|
}
|
|
String valuePart;
|
String directionPart = null;
|
String[] parts = trimmed.split(",");
|
if (parts.length >= 2) {
|
valuePart = parts[0].trim();
|
directionPart = parts[1].trim();
|
} else {
|
valuePart = trimmed;
|
}
|
|
if (valuePart.isEmpty()) {
|
return null;
|
}
|
|
if (directionPart == null || directionPart.isEmpty()) {
|
char lastChar = valuePart.charAt(valuePart.length() - 1);
|
if (Character.isLetter(lastChar)) {
|
directionPart = String.valueOf(lastChar);
|
valuePart = valuePart.substring(0, valuePart.length() - 1).trim();
|
}
|
}
|
|
if (valuePart.isEmpty()) {
|
return null;
|
}
|
|
directionPart = normalizeHemisphere(directionPart, latitude);
|
return new String[]{valuePart, directionPart};
|
}
|
|
private String normalizeHemisphere(String direction, boolean latitude) {
|
if (direction == null || direction.trim().isEmpty()) {
|
return latitude ? "N" : "E";
|
}
|
String normalized = direction.trim().toUpperCase(Locale.ROOT);
|
if (latitude) {
|
if (!"N".equals(normalized) && !"S".equals(normalized)) {
|
return "N";
|
}
|
} else {
|
if (!"E".equals(normalized) && !"W".equals(normalized)) {
|
return "E";
|
}
|
}
|
return normalized;
|
}
|
|
private double parseMetersValue(String raw) {
|
if (raw == null) {
|
return Double.NaN;
|
}
|
String trimmed = raw.trim();
|
if (trimmed.isEmpty() || "-1".equals(trimmed)) {
|
return Double.NaN;
|
}
|
try {
|
return Double.parseDouble(trimmed);
|
} catch (NumberFormatException ex) {
|
return Double.NaN;
|
}
|
}
|
|
private double parseAltitudeValue(String raw) {
|
if (raw == null) {
|
return 0.0;
|
}
|
String trimmed = raw.trim();
|
if (trimmed.isEmpty() || "-1".equals(trimmed)) {
|
return 0.0;
|
}
|
try {
|
return Double.parseDouble(trimmed);
|
} catch (NumberFormatException ex) {
|
return 0.0;
|
}
|
}
|
|
private boolean beginMowingSession() {
|
if (mapRenderer == null) {
|
return false;
|
}
|
String landNumber = Dikuaiguanli.getCurrentWorkLandNumber();
|
if (!isMeaningfulValue(landNumber)) {
|
JOptionPane.showMessageDialog(this, "请先选择地块后再开始作业", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
|
double widthMeters = resolveMowerWidthMeters(landNumber);
|
if (widthMeters <= 0) {
|
JOptionPane.showMessageDialog(this, "未配置割草宽度,将无法计算作业面积", "提示", JOptionPane.WARNING_MESSAGE);
|
}
|
|
mapRenderer.startRealtimeTrackRecording(landNumber, widthMeters);
|
refreshMowerSpeedLabel();
|
return true;
|
}
|
|
private void pauseMowingSession() {
|
if (mapRenderer == null) {
|
return;
|
}
|
mapRenderer.pauseRealtimeTrackRecording();
|
refreshMowerSpeedLabel();
|
}
|
|
private void stopMowingSession() {
|
if (mapRenderer == null) {
|
return;
|
}
|
mapRenderer.stopRealtimeTrackRecording();
|
refreshMowerSpeedLabel();
|
}
|
|
private double resolveMowerWidthMeters(String landNumber) {
|
double width = 0.0;
|
if (isMeaningfulValue(landNumber)) {
|
Dikuai current = Dikuai.getDikuai(landNumber);
|
if (current != null) {
|
width = parseMowerWidthMeters(current.getMowingWidth());
|
}
|
}
|
if (width > 0) {
|
return width;
|
}
|
return parseMowerWidthFromDevice();
|
}
|
|
private double parseMowerWidthFromDevice() {
|
Device device = Device.getGecaoji();
|
if (device == null) {
|
return 0.0;
|
}
|
return parseMowerWidthMeters(device.getMowingWidth());
|
}
|
|
private double parseMowerWidthMeters(String raw) {
|
if (raw == null) {
|
return 0.0;
|
}
|
String sanitized = raw.trim().toLowerCase(Locale.ROOT);
|
if (sanitized.isEmpty() || "-1".equals(sanitized)) {
|
return 0.0;
|
}
|
sanitized = sanitized.replace("厘米", "cm");
|
sanitized = sanitized.replace("公分", "cm");
|
sanitized = sanitized.replace("米", "m");
|
sanitized = sanitized.replace("cm", "");
|
sanitized = sanitized.replace("m", "");
|
sanitized = sanitized.trim();
|
if (sanitized.isEmpty()) {
|
return 0.0;
|
}
|
try {
|
double value = Double.parseDouble(sanitized);
|
if (value <= 0) {
|
return 0.0;
|
}
|
if (value > 10) {
|
return value / 100.0;
|
}
|
return value;
|
} catch (NumberFormatException ex) {
|
return 0.0;
|
}
|
}
|
|
private void applyStatusLabelColor(String statusText) {
|
if (statusLabel == null) {
|
return;
|
}
|
if ("作业中".equals(statusText) || "绘制中".equals(statusText)) {
|
statusLabel.setForeground(THEME_COLOR);
|
} else if ("暂停中".equals(statusText) || "绘制暂停".equals(statusText)) {
|
statusLabel.setForeground(STATUS_PAUSE_COLOR);
|
} else {
|
statusLabel.setForeground(Color.GRAY);
|
}
|
}
|
|
private void ensureBluetoothIconsLoaded() {
|
if (bluetoothIcon == null) {
|
bluetoothIcon = loadScaledIcon("image/blue.png", 28, 28);
|
}
|
if (bluetoothLinkedIcon == null) {
|
bluetoothLinkedIcon = loadScaledIcon("image/bluelink.png", 28, 28);
|
}
|
}
|
|
private JButton createFloatingIconButton() {
|
JButton button = new JButton();
|
button.setContentAreaFilled(false);
|
button.setBorder(BorderFactory.createEmptyBorder());
|
button.setFocusPainted(false);
|
button.setOpaque(false);
|
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
int size = FLOAT_ICON_SIZE + 8;
|
button.setPreferredSize(new Dimension(size, size));
|
return button;
|
}
|
|
private JButton createFloatingTextButton(String text) {
|
JButton button = new JButton(text);
|
button.setFont(new Font("微软雅黑", Font.BOLD, 15));
|
button.setForeground(Color.WHITE);
|
button.setBackground(THEME_COLOR);
|
button.setBorder(BorderFactory.createEmptyBorder(10, 18, 10, 18));
|
button.setFocusPainted(false);
|
button.setOpaque(true);
|
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
button.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
button.setBackground(THEME_HOVER_COLOR);
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
button.setBackground(THEME_COLOR);
|
}
|
});
|
return button;
|
}
|
|
private ImageIcon loadScaledIcon(String path, int width, int height) {
|
try {
|
ImageIcon icon = new ImageIcon(path);
|
if (icon.getIconWidth() <= 0 || icon.getIconHeight() <= 0) {
|
return null;
|
}
|
Image scaled = icon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH);
|
return new ImageIcon(scaled);
|
} catch (Exception ex) {
|
System.err.println("加载图标失败: " + path + " - " + ex.getMessage());
|
return null;
|
}
|
}
|
|
private void ensureFloatingIconsLoaded() {
|
if (pauseIcon == null) {
|
pauseIcon = loadScaledIcon("image/zanting.png", FLOAT_ICON_SIZE, FLOAT_ICON_SIZE);
|
}
|
if (pauseActiveIcon == null) {
|
pauseActiveIcon = loadScaledIcon("image/zantingzhong.png", FLOAT_ICON_SIZE, FLOAT_ICON_SIZE);
|
}
|
if (endIcon == null) {
|
endIcon = loadScaledIcon("image/end.png", FLOAT_ICON_SIZE, FLOAT_ICON_SIZE);
|
}
|
}
|
|
private void updatePauseButtonVisual() {
|
if (drawingPauseButton == null) {
|
return;
|
}
|
if (drawingPaused) {
|
if (pauseActiveIcon != null) {
|
drawingPauseButton.setIcon(pauseActiveIcon);
|
drawingPauseButton.setText(null);
|
} else {
|
drawingPauseButton.setText("暂停中");
|
drawingPauseButton.setIcon(null);
|
}
|
drawingPauseButton.setToolTipText("点击恢复绘制");
|
} else {
|
if (pauseIcon != null) {
|
drawingPauseButton.setIcon(pauseIcon);
|
drawingPauseButton.setText(null);
|
} else {
|
drawingPauseButton.setText("暂停");
|
drawingPauseButton.setIcon(null);
|
}
|
drawingPauseButton.setToolTipText("点击暂停绘制");
|
}
|
}
|
|
private void applyDrawingPauseState(boolean paused, boolean notifyCoordinate) {
|
drawingPaused = paused;
|
updatePauseButtonVisual();
|
if (notifyCoordinate) {
|
Coordinate.setStartSaveGngga(!paused);
|
}
|
if (drawingControlModeActive) {
|
updateDrawingControlButtonLabels();
|
if (statusLabel != null) {
|
statusLabel.setText(paused ? "绘制暂停" : "绘制中");
|
}
|
}
|
}
|
|
private void toggleDrawingPause() {
|
applyDrawingPauseState(!drawingPaused, true);
|
}
|
|
public void showEndDrawingButton(Runnable callback) {
|
showEndDrawingButton(callback, null);
|
}
|
|
public void showEndDrawingButton(Runnable callback, String drawingShape) {
|
endDrawingCallback = callback;
|
circleDialogMode = false;
|
hideCircleGuidancePanel();
|
enterDrawingControlMode();
|
|
// 隐藏返回设置按钮(如果显示绘制按钮,则不应该显示返回按钮)
|
if (settingsReturnButton != null) {
|
settingsReturnButton.setVisible(false);
|
}
|
|
// 显示"正在绘制边界"提示
|
if (drawingBoundaryLabel != null) {
|
drawingBoundaryLabel.setVisible(true);
|
}
|
|
boolean enableCircleGuidance = drawingShape != null
|
&& "circle".equalsIgnoreCase(drawingShape.trim());
|
if (enableCircleGuidance) {
|
ensureFloatingIconsLoaded();
|
ensureFloatingButtonInfrastructure();
|
if (drawingPauseButton != null) {
|
drawingPauseButton.setVisible(false);
|
}
|
if (endDrawingButton != null) {
|
endDrawingButton.setVisible(false);
|
}
|
prepareCircleGuidanceState();
|
showCircleGuidanceStep(1);
|
floatingButtonPanel.setVisible(true);
|
if (floatingButtonPanel.getParent() != visualizationPanel) {
|
visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
|
}
|
rebuildFloatingButtonColumn();
|
} else {
|
clearCircleGuidanceArtifacts();
|
hideFloatingDrawingControls();
|
}
|
|
visualizationPanel.revalidate();
|
visualizationPanel.repaint();
|
}
|
|
private void ensureFloatingButtonInfrastructure() {
|
if (endDrawingButton == null) {
|
endDrawingButton = createFloatingIconButton();
|
endDrawingButton.addActionListener(e -> {
|
if (endDrawingCallback != null) {
|
endDrawingCallback.run();
|
}
|
});
|
}
|
if (endIcon != null) {
|
endDrawingButton.setIcon(endIcon);
|
endDrawingButton.setText(null);
|
} else {
|
endDrawingButton.setText("结束绘制");
|
}
|
endDrawingButton.setToolTipText("结束绘制");
|
|
if (drawingPauseButton == null) {
|
drawingPauseButton = createFloatingIconButton();
|
drawingPauseButton.addActionListener(e -> toggleDrawingPause());
|
}
|
updatePauseButtonVisual();
|
|
if (floatingButtonPanel == null) {
|
floatingButtonPanel = new JPanel(new BorderLayout());
|
floatingButtonPanel.setOpaque(false);
|
floatingButtonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 20, 20));
|
|
floatingButtonColumn = new JPanel();
|
floatingButtonColumn.setOpaque(false);
|
floatingButtonColumn.setLayout(new BoxLayout(floatingButtonColumn, BoxLayout.Y_AXIS));
|
floatingButtonPanel.add(floatingButtonColumn, BorderLayout.EAST);
|
}
|
}
|
|
private void hideFloatingDrawingControls() {
|
if (drawingPauseButton != null) {
|
drawingPauseButton.setVisible(false);
|
}
|
if (endDrawingButton != null) {
|
endDrawingButton.setVisible(false);
|
}
|
if (floatingButtonPanel != null) {
|
floatingButtonPanel.setVisible(false);
|
}
|
if (!circleDialogMode) {
|
rebuildFloatingButtonColumn();
|
}
|
}
|
|
private void rebuildFloatingButtonColumn() {
|
if (floatingButtonColumn == null) {
|
return;
|
}
|
floatingButtonColumn.removeAll();
|
boolean added = false;
|
if (!circleDialogMode && circleGuidancePanel != null && circleGuidancePanel.isVisible()) {
|
floatingButtonColumn.add(circleGuidancePanel);
|
added = true;
|
}
|
if (!circleDialogMode && drawingPauseButton != null && drawingPauseButton.isVisible()) {
|
if (added) {
|
floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
|
}
|
floatingButtonColumn.add(drawingPauseButton);
|
added = true;
|
}
|
if (!circleDialogMode && endDrawingButton != null && endDrawingButton.isVisible()) {
|
if (added) {
|
floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
|
}
|
floatingButtonColumn.add(endDrawingButton);
|
added = true;
|
}
|
if (pathPreviewReturnButton != null && pathPreviewReturnButton.isVisible()) {
|
if (added) {
|
floatingButtonColumn.add(Box.createRigidArea(new Dimension(0, 10)));
|
}
|
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();
|
}
|
|
private void showCircleGuidanceStep(int step) {
|
ensureCircleGuidancePanel();
|
if (circleGuidancePanel == null) {
|
return;
|
}
|
circleGuidanceStep = step;
|
|
if (step == 1) {
|
circleGuidanceLabel.setText("采集第1个点");
|
circleGuidancePrimaryButton.setText("确认第1点");
|
circleGuidanceSecondaryButton.setText("返回");
|
circleGuidanceSecondaryButton.setVisible(true);
|
} else if (step == 2) {
|
circleGuidanceLabel.setText("采集第2个点");
|
circleGuidancePrimaryButton.setText("确认第2点");
|
circleGuidanceSecondaryButton.setText("返回");
|
circleGuidanceSecondaryButton.setVisible(true);
|
} else if (step == 3) {
|
circleGuidanceLabel.setText("采集第3个点");
|
circleGuidancePrimaryButton.setText("确认第3点");
|
circleGuidanceSecondaryButton.setText("返回");
|
circleGuidanceSecondaryButton.setVisible(true);
|
} else if (step == 4) {
|
circleGuidanceLabel.setText("已采集三个点");
|
circleGuidancePrimaryButton.setText("结束绘制");
|
circleGuidanceSecondaryButton.setText("重新采集");
|
circleGuidanceSecondaryButton.setVisible(true);
|
} else {
|
hideCircleGuidancePanel();
|
return;
|
}
|
circleGuidancePanel.setVisible(true);
|
|
refreshCircleGuidanceButtonAvailability();
|
|
if (circleDialogMode) {
|
ensureCircleGuidanceDialog();
|
if (circleGuidanceDialog != null) {
|
circleGuidanceDialog.pack();
|
positionCircleGuidanceDialog();
|
circleGuidanceDialog.setVisible(true);
|
circleGuidanceDialog.toFront();
|
}
|
} else {
|
rebuildFloatingButtonColumn();
|
}
|
}
|
|
private void ensureCircleGuidancePanel() {
|
if (circleGuidancePanel != null) {
|
return;
|
}
|
circleGuidancePanel = new JPanel();
|
circleGuidancePanel.setLayout(new BoxLayout(circleGuidancePanel, BoxLayout.Y_AXIS));
|
circleGuidancePanel.setOpaque(true);
|
circleGuidancePanel.setBackground(new Color(255, 255, 255, 235));
|
circleGuidancePanel.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(THEME_COLOR, 1),
|
BorderFactory.createEmptyBorder(10, 12, 10, 12)));
|
circleGuidancePanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
circleGuidanceLabel = new JLabel();
|
circleGuidanceLabel.setFont(new Font("微软雅黑", Font.BOLD, 13));
|
circleGuidanceLabel.setForeground(new Color(33, 37, 41));
|
circleGuidanceLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
JPanel buttonRow = new JPanel();
|
buttonRow.setLayout(new BoxLayout(buttonRow, BoxLayout.X_AXIS));
|
buttonRow.setOpaque(false);
|
buttonRow.setAlignmentX(Component.LEFT_ALIGNMENT);
|
|
circleGuidancePrimaryButton = createGuidanceButton("是", THEME_COLOR, Color.WHITE, true);
|
circleGuidancePrimaryButton.addActionListener(e -> onCircleGuidancePrimary());
|
|
circleGuidanceSecondaryButton = createGuidanceButton("否", Color.WHITE, THEME_COLOR, false);
|
circleGuidanceSecondaryButton.addActionListener(e -> onCircleGuidanceSecondary());
|
|
buttonRow.add(circleGuidancePrimaryButton);
|
buttonRow.add(Box.createRigidArea(new Dimension(8, 0)));
|
buttonRow.add(circleGuidanceSecondaryButton);
|
|
circleGuidancePanel.add(circleGuidanceLabel);
|
circleGuidancePanel.add(Box.createRigidArea(new Dimension(0, 8)));
|
circleGuidancePanel.add(buttonRow);
|
circleGuidancePanel.setVisible(false);
|
}
|
|
private void ensureCircleGuidanceDialog() {
|
if (circleGuidancePanel == null) {
|
return;
|
}
|
if (circleGuidanceDialog != null) {
|
if (circleGuidancePanel.getParent() != circleGuidanceDialog.getContentPane()) {
|
detachCircleGuidancePanel();
|
circleGuidanceDialog.getContentPane().removeAll();
|
circleGuidanceDialog.getContentPane().add(circleGuidancePanel, BorderLayout.CENTER);
|
circleGuidanceDialog.pack();
|
}
|
return;
|
}
|
|
Window owner = SwingUtilities.getWindowAncestor(this);
|
circleGuidanceDialog = new JDialog(owner, "绘制提示", Dialog.ModalityType.MODELESS);
|
circleGuidanceDialog.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
|
circleGuidanceDialog.setResizable(false);
|
circleGuidanceDialog.setAlwaysOnTop(true);
|
|
if (owner != null && circleDialogOwnerAdapter == null) {
|
circleDialogOwnerAdapter = new ComponentAdapter() {
|
@Override
|
public void componentMoved(ComponentEvent e) {
|
positionCircleGuidanceDialog();
|
}
|
|
@Override
|
public void componentResized(ComponentEvent e) {
|
positionCircleGuidanceDialog();
|
}
|
};
|
owner.addComponentListener(circleDialogOwnerAdapter);
|
}
|
|
detachCircleGuidancePanel();
|
circleGuidanceDialog.getContentPane().setLayout(new BorderLayout());
|
circleGuidanceDialog.getContentPane().add(circleGuidancePanel, BorderLayout.CENTER);
|
circleGuidanceDialog.pack();
|
}
|
|
private void detachCircleGuidancePanel() {
|
if (circleGuidancePanel == null) {
|
return;
|
}
|
Container parent = circleGuidancePanel.getParent();
|
if (parent != null) {
|
parent.remove(circleGuidancePanel);
|
parent.revalidate();
|
parent.repaint();
|
}
|
}
|
|
private void positionCircleGuidanceDialog() {
|
if (circleGuidanceDialog == null) {
|
return;
|
}
|
Window owner = SwingUtilities.getWindowAncestor(this);
|
if (owner == null || !owner.isShowing()) {
|
return;
|
}
|
try {
|
Point ownerLocation = owner.getLocationOnScreen();
|
int x = ownerLocation.x + owner.getWidth() - circleGuidanceDialog.getWidth() - 30;
|
int y = ownerLocation.y + owner.getHeight() - circleGuidanceDialog.getHeight() - 40;
|
x = Math.max(ownerLocation.x, x);
|
y = Math.max(ownerLocation.y, y);
|
circleGuidanceDialog.setLocation(x, y);
|
} catch (IllegalComponentStateException ex) {
|
// Owner not yet displayable; skip positioning
|
}
|
}
|
|
private JButton createGuidanceButton(String text, Color bg, Color fg, boolean filled) {
|
JButton button = new JButton(text);
|
button.setFont(new Font("微软雅黑", Font.BOLD, 12));
|
button.setForeground(fg);
|
button.setBackground(bg);
|
button.setOpaque(true);
|
button.setFocusPainted(false);
|
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
button.setAlignmentX(Component.LEFT_ALIGNMENT);
|
if (filled) {
|
button.setBorder(BorderFactory.createEmptyBorder(6, 14, 6, 14));
|
} else {
|
button.setBackground(Color.WHITE);
|
button.setOpaque(true);
|
button.setBorder(BorderFactory.createCompoundBorder(
|
BorderFactory.createLineBorder(THEME_COLOR, 1),
|
BorderFactory.createEmptyBorder(5, 12, 5, 12)));
|
}
|
button.addMouseListener(new MouseAdapter() {
|
@Override
|
public void mouseEntered(MouseEvent e) {
|
if (!button.isEnabled()) {
|
return;
|
}
|
if (filled) {
|
button.setBackground(THEME_HOVER_COLOR);
|
} else {
|
button.setBackground(new Color(245, 245, 245));
|
}
|
}
|
|
@Override
|
public void mouseExited(MouseEvent e) {
|
if (!button.isEnabled()) {
|
return;
|
}
|
if (filled) {
|
button.setBackground(bg);
|
} else {
|
button.setBackground(Color.WHITE);
|
}
|
}
|
});
|
return button;
|
}
|
|
private void onCircleGuidancePrimary() {
|
if (circleGuidanceStep >= 1 && circleGuidanceStep <= 3) {
|
if (!recordCircleSamplePoint()) {
|
return;
|
}
|
if (circleGuidanceStep == 3) {
|
showCircleGuidanceStep(4);
|
} else {
|
showCircleGuidanceStep(circleGuidanceStep + 1);
|
}
|
} else if (circleGuidanceStep == 4) {
|
handleCircleCompletion();
|
}
|
}
|
|
private void onCircleGuidanceSecondary() {
|
if (circleGuidanceStep == 1 || circleGuidanceStep == 2 || circleGuidanceStep == 3) {
|
handleCircleAbort(null);
|
} else if (circleGuidanceStep == 4) {
|
restartCircleCapture();
|
}
|
}
|
|
private void hideCircleGuidancePanel() {
|
circleGuidanceStep = 0;
|
if (circleGuidancePanel != null) {
|
circleGuidancePanel.setVisible(false);
|
}
|
if (circleGuidanceDialog != null) {
|
circleGuidanceDialog.setVisible(false);
|
}
|
if (!circleDialogMode) {
|
rebuildFloatingButtonColumn();
|
}
|
}
|
|
private void handleCircleAbort(String message) {
|
// Return the wizard to step 2 without committing any captured points
|
hideEndDrawingButton();
|
addzhangaiwu.abortCircleDrawingAndReturn(message);
|
}
|
|
private void handleCircleCompletion() {
|
hideCircleGuidancePanel();
|
clearCircleGuidanceArtifacts();
|
if (endDrawingCallback != null) {
|
endDrawingCallback.run();
|
} else {
|
addzhangaiwu.finishDrawingSession();
|
}
|
}
|
|
private void prepareCircleGuidanceState() {
|
clearCircleGuidanceArtifacts();
|
circleBaseLatLon = resolveCircleBaseLatLon();
|
startCircleDataMonitor();
|
}
|
|
private void clearCircleGuidanceArtifacts() {
|
stopCircleDataMonitor();
|
circleCapturedPoints.clear();
|
circleBaseLatLon = null;
|
lastCapturedCoordinate = null;
|
if (mapRenderer != null) {
|
mapRenderer.clearCircleCaptureOverlay();
|
mapRenderer.clearCircleSampleMarkers();
|
}
|
}
|
|
private void restartCircleCapture() {
|
clearCircleGuidanceArtifacts();
|
synchronized (Coordinate.coordinates) {
|
Coordinate.coordinates.clear();
|
}
|
Coordinate.setStartSaveGngga(true);
|
circleBaseLatLon = resolveCircleBaseLatLon();
|
showCircleGuidanceStep(1);
|
startCircleDataMonitor();
|
}
|
|
private boolean recordCircleSamplePoint() {
|
Coordinate latest = getLatestCoordinate();
|
if (latest == null) {
|
JOptionPane.showMessageDialog(this, "未获取到当前位置坐标,请稍后重试。", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
|
double[] base = ensureCircleBaseLatLon();
|
if (base == null) {
|
JOptionPane.showMessageDialog(this, "基准站坐标无效,请先在基准站管理中完成设置。", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
|
double lat = parseDMToDecimal(latest.getLatitude(), latest.getLatDirection());
|
double lon = parseDMToDecimal(latest.getLongitude(), latest.getLonDirection());
|
if (!Double.isFinite(lat) || !Double.isFinite(lon)) {
|
JOptionPane.showMessageDialog(this, "采集点坐标无效,请重新采集。", "提示", JOptionPane.WARNING_MESSAGE);
|
return false;
|
}
|
|
double[] local = convertLatLonToLocal(lat, lon, base[0], base[1]);
|
circleCapturedPoints.add(local);
|
if (mapRenderer != null) {
|
mapRenderer.updateCircleSampleMarkers(circleCapturedPoints);
|
}
|
lastCapturedCoordinate = latest;
|
refreshCircleGuidanceButtonAvailability();
|
|
if (circleCapturedPoints.size() >= 3) {
|
CircleSolution solution = fitCircleFromPoints(circleCapturedPoints);
|
if (solution == null) {
|
circleCapturedPoints.remove(circleCapturedPoints.size() - 1);
|
JOptionPane.showMessageDialog(this, "无法根据当前三个点生成圆,请重新采集点。", "提示", JOptionPane.WARNING_MESSAGE);
|
restartCircleCapture();
|
return false;
|
}
|
if (mapRenderer != null) {
|
mapRenderer.showCircleCaptureOverlay(solution.centerX, solution.centerY, solution.radius, circleCapturedPoints);
|
}
|
}
|
|
return true;
|
}
|
|
private void startCircleDataMonitor() {
|
if (circleDataMonitor == null) {
|
circleDataMonitor = new Timer(600, e -> refreshCircleGuidanceButtonAvailability());
|
circleDataMonitor.setRepeats(true);
|
}
|
if (!circleDataMonitor.isRunning()) {
|
circleDataMonitor.start();
|
}
|
refreshCircleGuidanceButtonAvailability();
|
}
|
|
private void stopCircleDataMonitor() {
|
if (circleDataMonitor != null) {
|
circleDataMonitor.stop();
|
}
|
}
|
|
private void refreshCircleGuidanceButtonAvailability() {
|
if (circleGuidancePrimaryButton == null) {
|
return;
|
}
|
boolean shouldEnable = circleGuidanceStep >= 1 && circleGuidanceStep <= 3
|
? isCircleDataAvailable()
|
: true;
|
applyCirclePrimaryButtonState(shouldEnable);
|
}
|
|
private boolean isCircleDataAvailable() {
|
Coordinate latest = getLatestCoordinate();
|
if (latest == null) {
|
return false;
|
}
|
|
// 检查是否有新的坐标(与上次采集的不同)
|
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) {
|
if (circleGuidancePrimaryButton == null) {
|
return;
|
}
|
if (circleGuidanceStep >= 4) {
|
enabled = true;
|
}
|
circleGuidancePrimaryButton.setEnabled(enabled);
|
if (enabled) {
|
circleGuidancePrimaryButton.setBackground(THEME_COLOR);
|
circleGuidancePrimaryButton.setForeground(Color.WHITE);
|
circleGuidancePrimaryButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
} else {
|
circleGuidancePrimaryButton.setBackground(new Color(200, 200, 200));
|
circleGuidancePrimaryButton.setForeground(new Color(120, 120, 120));
|
circleGuidancePrimaryButton.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
}
|
}
|
|
private Coordinate getLatestCoordinate() {
|
synchronized (Coordinate.coordinates) {
|
int size = Coordinate.coordinates.size();
|
if (size == 0) {
|
return null;
|
}
|
return Coordinate.coordinates.get(size - 1);
|
}
|
}
|
|
private double[] ensureCircleBaseLatLon() {
|
if (circleBaseLatLon == null) {
|
circleBaseLatLon = resolveCircleBaseLatLon();
|
}
|
return circleBaseLatLon;
|
}
|
|
private double[] resolveCircleBaseLatLon() {
|
String coords = null;
|
|
if (baseStation == null) {
|
baseStation = new BaseStation();
|
}
|
baseStation.load();
|
coords = baseStation.getInstallationCoordinates();
|
|
if (!isMeaningfulValue(coords)) {
|
String landNumber = Dikuaiguanli.getCurrentWorkLandNumber();
|
if (isMeaningfulValue(landNumber)) {
|
Dikuai current = Dikuai.getDikuai(landNumber);
|
if (current != null) {
|
coords = current.getBaseStationCoordinates();
|
}
|
}
|
if (!isMeaningfulValue(coords)) {
|
coords = addzhangaiwu.getActiveSessionBaseStation();
|
}
|
if (!isMeaningfulValue(coords)) {
|
return null;
|
}
|
}
|
String[] parts = coords.split(",");
|
if (parts.length < 4) {
|
return null;
|
}
|
double baseLat = parseDMToDecimal(parts[0], parts[1]);
|
double baseLon = parseDMToDecimal(parts[2], parts[3]);
|
if (!Double.isFinite(baseLat) || !Double.isFinite(baseLon)) {
|
return null;
|
}
|
return new double[]{baseLat, baseLon};
|
}
|
|
private double parseDMToDecimal(String dmm, String direction) {
|
if (dmm == null || dmm.trim().isEmpty()) {
|
return Double.NaN;
|
}
|
try {
|
String trimmed = dmm.trim();
|
int dotIndex = trimmed.indexOf('.');
|
if (dotIndex < 2) {
|
return Double.NaN;
|
}
|
int degrees = Integer.parseInt(trimmed.substring(0, dotIndex - 2));
|
double minutes = Double.parseDouble(trimmed.substring(dotIndex - 2));
|
double decimal = degrees + minutes / 60.0;
|
if ("S".equalsIgnoreCase(direction) || "W".equalsIgnoreCase(direction)) {
|
decimal = -decimal;
|
}
|
return decimal;
|
} catch (NumberFormatException ex) {
|
return Double.NaN;
|
}
|
}
|
|
private double[] convertLatLonToLocal(double lat, double lon, double baseLat, double baseLon) {
|
double deltaLat = lat - baseLat;
|
double deltaLon = lon - baseLon;
|
double meanLatRad = Math.toRadians((baseLat + lat) / 2.0);
|
double eastMeters = deltaLon * METERS_PER_DEGREE_LAT * Math.cos(meanLatRad);
|
double northMeters = deltaLat * METERS_PER_DEGREE_LAT;
|
return new double[]{eastMeters, northMeters};
|
}
|
|
private CircleSolution fitCircleFromPoints(List<double[]> points) {
|
if (points == null || points.size() < 3) {
|
return null;
|
}
|
CircleSolution best = null;
|
double bestScore = 0.0;
|
int n = points.size();
|
for (int i = 0; i < n - 2; i++) {
|
double[] p1 = points.get(i);
|
for (int j = i + 1; j < n - 1; j++) {
|
double[] p2 = points.get(j);
|
for (int k = j + 1; k < n; k++) {
|
double[] p3 = points.get(k);
|
CircleSolution candidate = circleFromThreePoints(p1, p2, p3);
|
if (candidate == null || candidate.radius <= 0) {
|
continue;
|
}
|
double minEdge = Math.min(distance(p1, p2), Math.min(distance(p2, p3), distance(p1, p3)));
|
if (minEdge > bestScore) {
|
bestScore = minEdge;
|
best = candidate;
|
}
|
}
|
}
|
}
|
return best;
|
}
|
|
private CircleSolution circleFromThreePoints(double[] p1, double[] p2, double[] p3) {
|
if (p1 == null || p2 == null || p3 == null) {
|
return null;
|
}
|
double x1 = p1[0];
|
double y1 = p1[1];
|
double x2 = p2[0];
|
double y2 = p2[1];
|
double x3 = p3[0];
|
double y3 = p3[1];
|
|
double a = x1 * (y2 - y3) - y1 * (x2 - x3) + x2 * y3 - x3 * y2;
|
double d = 2.0 * a;
|
if (Math.abs(d) < 1e-6) {
|
return null;
|
}
|
|
double x1Sq = x1 * x1 + y1 * y1;
|
double x2Sq = x2 * x2 + y2 * y2;
|
double x3Sq = x3 * x3 + y3 * y3;
|
|
double centerX = (x1Sq * (y2 - y3) + x2Sq * (y3 - y1) + x3Sq * (y1 - y2)) / d;
|
double centerY = (x1Sq * (x3 - x2) + x2Sq * (x1 - x3) + x3Sq * (x2 - x1)) / d;
|
double radius = Math.hypot(centerX - x1, centerY - y1);
|
if (!Double.isFinite(centerX) || !Double.isFinite(centerY) || !Double.isFinite(radius)) {
|
return null;
|
}
|
if (radius < 0.05) {
|
return null;
|
}
|
return new CircleSolution(centerX, centerY, radius);
|
}
|
|
private double distance(double[] a, double[] b) {
|
if (a == null || b == null) {
|
return 0.0;
|
}
|
double dx = a[0] - b[0];
|
double dy = a[1] - b[1];
|
return Math.hypot(dx, dy);
|
}
|
|
private static final class CircleSolution {
|
final double centerX;
|
final double centerY;
|
final double radius;
|
|
CircleSolution(double centerX, double centerY, double radius) {
|
this.centerX = centerX;
|
this.centerY = centerY;
|
this.radius = radius;
|
}
|
}
|
|
public void hideEndDrawingButton() {
|
hideCircleGuidancePanel();
|
clearCircleGuidanceArtifacts();
|
hideFloatingDrawingControls();
|
circleDialogMode = false;
|
exitHandheldCaptureInlineUi();
|
handheldCaptureActive = false;
|
exitDrawingControlMode();
|
if (activeBoundaryMode == BoundaryCaptureMode.MOWER) {
|
stopMowerBoundaryCapture();
|
} else if (activeBoundaryMode == BoundaryCaptureMode.HANDHELD && !handheldCaptureActive) {
|
activeBoundaryMode = BoundaryCaptureMode.NONE;
|
}
|
endDrawingCallback = null;
|
|
// 隐藏"正在绘制边界"提示
|
if (drawingBoundaryLabel != null) {
|
drawingBoundaryLabel.setVisible(false);
|
}
|
|
visualizationPanel.revalidate();
|
visualizationPanel.repaint();
|
setHandheldMowerIconActive(false);
|
}
|
|
private void showPathPreviewReturnControls() {
|
ensureFloatingButtonInfrastructure();
|
if (drawingPauseButton != null) {
|
drawingPauseButton.setVisible(false);
|
}
|
if (endDrawingButton != null) {
|
endDrawingButton.setVisible(false);
|
}
|
if (pathPreviewReturnButton == null) {
|
pathPreviewReturnButton = createFloatingTextButton("返回");
|
pathPreviewReturnButton.setToolTipText("返回新增地块步骤");
|
pathPreviewReturnButton.addActionListener(e -> handlePathPreviewReturn());
|
}
|
pathPreviewReturnButton.setVisible(true);
|
if (floatingButtonPanel != null) {
|
floatingButtonPanel.setVisible(true);
|
if (floatingButtonPanel.getParent() != visualizationPanel) {
|
visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH);
|
}
|
}
|
rebuildFloatingButtonColumn();
|
}
|
|
private void hidePathPreviewReturnControls() {
|
if (pathPreviewReturnButton != null) {
|
pathPreviewReturnButton.setVisible(false);
|
}
|
rebuildFloatingButtonColumn();
|
if (floatingButtonPanel != null && floatingButtonColumn != null
|
&& floatingButtonColumn.getComponentCount() == 0) {
|
floatingButtonPanel.setVisible(false);
|
}
|
}
|
|
private void handlePathPreviewReturn() {
|
Runnable callback = pathPreviewReturnAction;
|
exitMowingPathPreview();
|
if (callback != null) {
|
callback.run();
|
}
|
}
|
|
public boolean startMowingPathPreview(String landNumber,
|
String landName,
|
String boundary,
|
String obstacles,
|
String plannedPath,
|
Runnable returnAction) {
|
if (mapRenderer == null) {
|
return false;
|
}
|
// 允许没有路径的预览(例如障碍物预览),只要有返回回调即可
|
if (!isMeaningfulValue(plannedPath) && returnAction == null) {
|
return false;
|
}
|
|
if (pathPreviewActive) {
|
exitMowingPathPreview();
|
}
|
|
exitDrawingControlMode();
|
hideCircleGuidancePanel();
|
clearCircleGuidanceArtifacts();
|
|
pathPreviewReturnAction = returnAction;
|
pathPreviewActive = true;
|
mapRenderer.setPathPreviewSizingEnabled(true);
|
|
previewRestoreLandNumber = Dikuaiguanli.getCurrentWorkLandNumber();
|
previewRestoreLandName = null;
|
if (isMeaningfulValue(previewRestoreLandNumber)) {
|
Dikuai existing = Dikuai.getDikuai(previewRestoreLandNumber);
|
if (existing != null) {
|
previewRestoreLandName = existing.getLandName();
|
}
|
}
|
|
mapRenderer.setCurrentBoundary(boundary, landNumber, landName);
|
mapRenderer.setCurrentObstacles(obstacles, landNumber);
|
// 只有在有路径时才设置路径
|
if (isMeaningfulValue(plannedPath)) {
|
mapRenderer.setCurrentPlannedPath(plannedPath);
|
} else {
|
mapRenderer.setCurrentPlannedPath(null);
|
}
|
mapRenderer.clearHandheldBoundaryPreview();
|
mapRenderer.setBoundaryPointSizeScale(1.0d);
|
mapRenderer.setBoundaryPointsVisible(isMeaningfulValue(boundary));
|
// 启用障碍物边界点显示
|
mapRenderer.setObstaclePointsVisible(isMeaningfulValue(obstacles));
|
|
String displayName = isMeaningfulValue(landName) ? landName : landNumber;
|
updateCurrentAreaName(displayName);
|
|
showPathPreviewReturnControls();
|
visualizationPanel.revalidate();
|
visualizationPanel.repaint();
|
return true;
|
}
|
|
public void exitMowingPathPreview() {
|
if (!pathPreviewActive) {
|
return;
|
}
|
pathPreviewActive = false;
|
if (mapRenderer != null) {
|
mapRenderer.setPathPreviewSizingEnabled(false);
|
}
|
hidePathPreviewReturnControls();
|
|
String restoreNumber = previewRestoreLandNumber;
|
String restoreName = previewRestoreLandName;
|
previewRestoreLandNumber = null;
|
previewRestoreLandName = null;
|
pathPreviewReturnAction = null;
|
|
if (restoreNumber != null) {
|
Dikuaiguanli.setCurrentWorkLand(restoreNumber, restoreName);
|
} else if (mapRenderer != null) {
|
mapRenderer.setCurrentBoundary(null, null, null);
|
mapRenderer.setCurrentObstacles((String) null, null);
|
mapRenderer.setCurrentPlannedPath(null);
|
mapRenderer.setBoundaryPointsVisible(false);
|
mapRenderer.setBoundaryPointSizeScale(1.0d);
|
mapRenderer.clearHandheldBoundaryPreview();
|
mapRenderer.resetView();
|
updateCurrentAreaName(null);
|
}
|
|
visualizationPanel.revalidate();
|
visualizationPanel.repaint();
|
}
|
|
/**
|
* 获取地图渲染器实例
|
*/
|
public MapRenderer getMapRenderer() {
|
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);
|
}
|
}
|
|
/**
|
* 获取可视化面板实例
|
*/
|
public JPanel getVisualizationPanel() {
|
return visualizationPanel;
|
}
|
|
/**
|
* 获取主内容面板实例(用于添加浮动按钮)
|
*/
|
public JPanel getMainContentPanel() {
|
return mainContentPanel;
|
}
|
|
|
public void updateCurrentAreaName(String areaName) {
|
if (areaNameLabel == null) {
|
return;
|
}
|
if (areaName == null || areaName.trim().isEmpty()) {
|
areaNameLabel.setText("未选择地块");
|
} else {
|
areaNameLabel.setText(areaName);
|
}
|
}
|
|
/**
|
* 重置地图视图
|
*/
|
public void resetMapView() {
|
if (mapRenderer != null) {
|
mapRenderer.resetView();
|
}
|
}
|
|
private void initializeDefaultAreaSelection() {
|
Dikuai.initFromProperties();
|
String persistedLandNumber = Dikuaiguanli.getPersistedWorkLandNumber();
|
if (persistedLandNumber != null) {
|
Dikuai stored = Dikuai.getDikuai(persistedLandNumber);
|
if (stored != null) {
|
Dikuaiguanli.setCurrentWorkLand(persistedLandNumber, stored.getLandName());
|
return;
|
}
|
Dikuaiguanli.setCurrentWorkLand(null, null);
|
}
|
|
Map<String, Dikuai> all = Dikuai.getAllDikuai();
|
if (all.isEmpty()) {
|
Dikuaiguanli.setCurrentWorkLand(null, null);
|
} else if (all.size() == 1) {
|
Dikuai only = all.values().iterator().next();
|
if (only != null) {
|
Dikuaiguanli.setCurrentWorkLand(only.getLandNumber(), only.getLandName());
|
}
|
}
|
}
|
|
private void refreshMapForSelectedArea() {
|
if (mapRenderer == null) {
|
return;
|
}
|
|
String currentLandNumber = Dikuaiguanli.getCurrentWorkLandNumber();
|
if (isMeaningfulValue(currentLandNumber)) {
|
Dikuai current = Dikuai.getDikuai(currentLandNumber);
|
String landName = current != null ? current.getLandName() : null;
|
Dikuaiguanli.setCurrentWorkLand(currentLandNumber, landName);
|
return;
|
}
|
|
String labelName = areaNameLabel != null ? areaNameLabel.getText() : null;
|
if (!isMeaningfulAreaName(labelName)) {
|
Dikuaiguanli.setCurrentWorkLand(null, null);
|
return;
|
}
|
|
Map<String, Dikuai> all = Dikuai.getAllDikuai();
|
for (Dikuai dikuai : all.values()) {
|
if (dikuai == null) {
|
continue;
|
}
|
String candidateName = dikuai.getLandName();
|
if (candidateName != null && candidateName.trim().equals(labelName.trim())) {
|
Dikuaiguanli.setCurrentWorkLand(dikuai.getLandNumber(), candidateName);
|
return;
|
}
|
}
|
|
Dikuaiguanli.setCurrentWorkLand(null, null);
|
}
|
|
private boolean isMeaningfulValue(String value) {
|
if (value == null) {
|
return false;
|
}
|
String trimmed = value.trim();
|
return !trimmed.isEmpty() && !"-1".equals(trimmed);
|
}
|
|
private boolean isMeaningfulAreaName(String value) {
|
if (value == null) {
|
return false;
|
}
|
String trimmed = value.trim();
|
if (trimmed.isEmpty()) {
|
return false;
|
}
|
return !"未选择地块".equals(trimmed);
|
}
|
|
|
|
// 测试方法
|
public static void main(String[] args) {
|
JFrame frame = new JFrame("AutoMow - 首页");
|
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
frame.setSize(400, 800);
|
frame.setLocationRelativeTo(null);
|
|
Shouye shouye = new Shouye();
|
frame.add(shouye);
|
|
frame.setVisible(true);
|
UDPServer.startAsync();//启动数据接收线程
|
}
|
}
|