From f784463ab019c1113cf0b31a249e8802b07a57fa Mon Sep 17 00:00:00 2001
From: 张世豪 <979909237@qq.com>
Date: 星期二, 16 十二月 2025 15:26:15 +0800
Subject: [PATCH] 保存地图最后位置

---
 src/dikuai/addzhangaiwu.java |  878 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 811 insertions(+), 67 deletions(-)

diff --git a/src/dikuai/addzhangaiwu.java b/src/dikuai/addzhangaiwu.java
index c936a6b..cd6d982 100644
--- a/src/dikuai/addzhangaiwu.java
+++ b/src/dikuai/addzhangaiwu.java
@@ -36,15 +36,22 @@
 import javax.swing.JPanel;
 import javax.swing.SwingConstants;
 import javax.swing.SwingUtilities;
+import javax.swing.JTextField;
 import javax.swing.border.Border;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
 
 import baseStation.BaseStation;
 import gecaoji.Device;
+import set.Setsys;
 import ui.UIConfig;
 import zhuye.Coordinate;
+import zhuye.MapRenderer;
 import zhuye.Shouye;
 import zhangaiwu.AddDikuai;
 import zhangaiwu.Obstacledge;
+import zhangaiwu.yulanzhangaiwu;
+import zhuye.buttonset;
 
 /**
  * 闅滅鐗╂柊澧�/缂栬緫瀵硅瘽妗嗐�傝璁¤瑷�鍙傝�� {@link AddDikuai}锛屾敮鎸侀�氳繃瀹炲湴缁樺埗閲囬泦闅滅鐗╁潗鏍囥��
@@ -61,6 +68,8 @@
     private final Color MEDIUM_GRAY = new Color(233, 236, 239);
     private final Color TEXT_COLOR = new Color(33, 37, 41);
     private final Color LIGHT_TEXT = new Color(108, 117, 125);
+    private final Color DANGER_COLOR = new Color(220, 53, 69);
+    private final Color DANGER_LIGHT = new Color(255, 235, 238);
 
     private final Dikuai targetDikuai;
     private final List<ExistingObstacle> existingObstacles;
@@ -80,6 +89,9 @@
     private JPanel selectedShapePanel;
     private JButton drawButton;
     private JLabel drawingStatusLabel;
+    private JTextField obstacleNameField;
+    private JPanel existingObstacleListPanel;
+    private JPanel step1NextButtonRow;
 
     private int currentStep = 1;
     private boolean drawingInProgress;
@@ -102,8 +114,11 @@
         if (target == null) {
             throw new IllegalArgumentException("targetDikuai 涓嶈兘涓虹┖");
         }
-    this.targetDikuai = target;
-    this.existingObstacles = Collections.unmodifiableList(resolveExistingObstacles(target, obstacleNames));
+        Coordinate.coordinates.clear();
+        Coordinate.setStartSaveGngga(false);
+        Coordinate.clearActiveDeviceIdFilter();
+        this.targetDikuai = target;
+        this.existingObstacles = new ArrayList<>(resolveExistingObstacles(target, obstacleNames));
         initializeUI();
         setupEventHandlers();
         preloadData();
@@ -161,7 +176,11 @@
     infoContainer.add(createInfoRow("鍦板潡缂栧彿锛�", safeValue(targetDikuai.getLandNumber(), "鏈缃�")));
     infoContainer.add(Box.createRigidArea(new Dimension(0, 16)));
     infoContainer.add(createInfoRow("鍦板潡鍚嶇О锛�", safeValue(targetDikuai.getLandName(), "鏈懡鍚�")));
-    infoContainer.add(Box.createRigidArea(new Dimension(0, 20)));
+    infoContainer.add(Box.createRigidArea(new Dimension(0, 16)));
+    infoContainer.add(createObstacleNameRow());
+    infoContainer.add(Box.createRigidArea(new Dimension(0, 12)));
+    infoContainer.add(createStep1NextButtonRow());
+    infoContainer.add(Box.createRigidArea(new Dimension(0, 12)));
     infoContainer.add(buildExistingObstacleSection());
 
     stepPanel.add(infoContainer);
@@ -182,32 +201,64 @@
         section.add(titleLabel);
         section.add(Box.createRigidArea(new Dimension(0, 8)));
 
+        existingObstacleListPanel = new JPanel();
+        existingObstacleListPanel.setLayout(new BoxLayout(existingObstacleListPanel, BoxLayout.Y_AXIS));
+        existingObstacleListPanel.setOpaque(false);
+        existingObstacleListPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+        section.add(existingObstacleListPanel);
+        refreshExistingObstacleList();
+        return section;
+    }
+
+    private JPanel createStep1NextButtonRow() {
+        if (step1NextButtonRow == null) {
+            step1NextButtonRow = new JPanel();
+            step1NextButtonRow.setLayout(new BoxLayout(step1NextButtonRow, BoxLayout.X_AXIS));
+            step1NextButtonRow.setOpaque(false);
+            step1NextButtonRow.setAlignmentX(Component.LEFT_ALIGNMENT);
+            step1NextButtonRow.setMaximumSize(new Dimension(Integer.MAX_VALUE, 36));
+            step1NextButtonRow.add(Box.createHorizontalGlue());
+        }
+        return step1NextButtonRow;
+    }
+
+    private void attachNextButtonToStep1Row() {
+        if (step1NextButtonRow == null || nextButton == null) {
+            return;
+        }
+        step1NextButtonRow.removeAll();
+        step1NextButtonRow.add(Box.createHorizontalGlue());
+        nextButton.setAlignmentX(Component.RIGHT_ALIGNMENT);
+        step1NextButtonRow.add(nextButton);
+        step1NextButtonRow.revalidate();
+        step1NextButtonRow.repaint();
+    }
+
+    private void refreshExistingObstacleList() {
+        if (existingObstacleListPanel == null) {
+            return;
+        }
+        existingObstacleListPanel.removeAll();
         if (existingObstacles.isEmpty()) {
             JLabel emptyLabel = new JLabel("鏆傛棤");
             emptyLabel.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 13));
             emptyLabel.setForeground(LIGHT_TEXT);
             emptyLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
-            section.add(emptyLabel);
-            return section;
-        }
-
-        JPanel listPanel = new JPanel();
-        listPanel.setLayout(new BoxLayout(listPanel, BoxLayout.Y_AXIS));
-        listPanel.setOpaque(false);
-        listPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
-
-        for (int i = 0; i < existingObstacles.size(); i++) {
-            ExistingObstacle obstacle = existingObstacles.get(i);
-            JPanel row = createObstacleSummaryRow(obstacle);
-            row.setAlignmentX(Component.LEFT_ALIGNMENT);
-            listPanel.add(row);
-            if (i < existingObstacles.size() - 1) {
-                listPanel.add(Box.createRigidArea(new Dimension(0, 6)));
+            existingObstacleListPanel.add(emptyLabel);
+        } else {
+            for (int i = 0; i < existingObstacles.size(); i++) {
+                ExistingObstacle obstacle = existingObstacles.get(i);
+                JPanel row = createObstacleSummaryRow(obstacle);
+                row.setAlignmentX(Component.LEFT_ALIGNMENT);
+                existingObstacleListPanel.add(row);
+                if (i < existingObstacles.size() - 1) {
+                    existingObstacleListPanel.add(Box.createRigidArea(new Dimension(0, 6)));
+                }
             }
         }
-
-        section.add(listPanel);
-        return section;
+        existingObstacleListPanel.revalidate();
+        existingObstacleListPanel.repaint();
     }
 
     private JPanel createObstacleSummaryRow(ExistingObstacle obstacle) {
@@ -225,20 +276,22 @@
         JButton editButton = createInlineButton("淇敼");
         editButton.addActionListener(e -> populateObstacleForEditing(obstacle));
 
+        JButton deleteButton = createInlineIconButton("image/delete.png", "鍒犻櫎闅滅鐗�");
+        deleteButton.addActionListener(e -> handleDeleteExistingObstacle(obstacle));
+
         row.add(infoLabel);
         row.add(Box.createHorizontalGlue());
         row.add(editButton);
+        row.add(Box.createRigidArea(new Dimension(6, 0)));
+        row.add(deleteButton);
         return row;
     }
 
     private JButton createInlineButton(String text) {
-        JButton button = new JButton(text);
+        JButton button = buttonset.createStyledButton(text, PRIMARY_COLOR);
         button.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 12));
-        button.setForeground(WHITE);
-        button.setBackground(PRIMARY_COLOR);
         button.setBorder(BorderFactory.createEmptyBorder(6, 16, 6, 16));
         button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
-        button.setFocusPainted(false);
         Dimension size = new Dimension(72, 28);
         button.setPreferredSize(size);
         button.setMinimumSize(size);
@@ -257,10 +310,56 @@
         return button;
     }
 
+    private JButton createInlineIconButton(String iconPath, String tooltip) {
+        JButton button = new JButton();
+        button.setPreferredSize(new Dimension(32, 28));
+        button.setMinimumSize(new Dimension(32, 28));
+        button.setMaximumSize(new Dimension(32, 28));
+        button.setBackground(WHITE);
+        button.setBorder(BorderFactory.createLineBorder(DANGER_COLOR));
+        button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+        button.setFocusPainted(false);
+        button.setFocusable(false);
+        button.setOpaque(true);
+        button.setContentAreaFilled(true);
+        if (tooltip != null && !tooltip.trim().isEmpty()) {
+            button.setToolTipText(tooltip);
+        }
+
+        ImageIcon icon = null;
+        if (iconPath != null && !iconPath.trim().isEmpty()) {
+            ImageIcon rawIcon = new ImageIcon(iconPath);
+            if (rawIcon.getIconWidth() > 0 && rawIcon.getIconHeight() > 0) {
+                Image scaled = rawIcon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH);
+                icon = new ImageIcon(scaled);
+            }
+        }
+        if (icon != null) {
+            button.setIcon(icon);
+        } else {
+            button.setText("鍒�");
+            button.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 12));
+            button.setForeground(DANGER_COLOR);
+        }
+
+        button.addMouseListener(new MouseAdapter() {
+            @Override
+            public void mouseEntered(MouseEvent e) {
+                button.setBackground(DANGER_LIGHT);
+            }
+
+            @Override
+            public void mouseExited(MouseEvent e) {
+                button.setBackground(WHITE);
+            }
+        });
+        return button;
+    }
+
     private String buildObstacleSummaryText(ExistingObstacle obstacle) {
         String name = obstacle.getName();
         String shape = obstacle.getShapeDisplay();
-        String coordPreview = buildCoordinatePreview(obstacle.getCoordinates(), 5);
+    String coordPreview = buildCoordinatePreview(obstacle.getDisplayCoordinates(), 5);
         return String.format(Locale.CHINA, "%s锛�%s锛屽潗鏍�:%s", name, shape, coordPreview);
     }
 
@@ -278,16 +377,70 @@
         return sanitized.substring(0, keepLength) + "...";
     }
 
+    private boolean deleteObstacleFromConfig(String obstacleName) {
+        String landNumber = targetDikuai != null ? targetDikuai.getLandNumber() : null;
+        if (!isMeaningfulValue(landNumber) || !isMeaningfulValue(obstacleName)) {
+            return false;
+        }
+        try {
+            File configFile = new File("Obstacledge.properties");
+            if (!configFile.exists()) {
+                return false;
+            }
+            Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
+            if (!manager.loadFromFile(configFile.getAbsolutePath())) {
+                return false;
+            }
+            Obstacledge.Plot plot = manager.getPlotById(landNumber.trim());
+            if (plot == null) {
+                return false;
+            }
+            if (!plot.removeObstacleByName(obstacleName.trim())) {
+                return false;
+            }
+            if (!manager.saveToFile(configFile.getAbsolutePath())) {
+                return false;
+            }
+            Dikuai.updateField(landNumber.trim(), "updateTime", currentTime());
+            Dikuai.saveToProperties();
+            Dikuaiguanli.notifyExternalCreation(landNumber.trim());
+            return true;
+        } catch (Exception ex) {
+            System.err.println("鍒犻櫎闅滅鐗╁け璐�: " + ex.getMessage());
+            return false;
+        }
+    }
+
     private void populateObstacleForEditing(ExistingObstacle obstacle) {
         if (obstacle == null) {
             return;
         }
-        String coords = obstacle.getCoordinates();
-        if (isMeaningfulValue(coords)) {
-            formData.put("obstacleCoordinates", coords.trim());
+        String name = obstacle.getName();
+        if (isMeaningfulValue(name)) {
+            formData.put("obstacleName", name.trim());
+            formData.put("editingObstacleName", name.trim());
+        } else {
+            formData.remove("obstacleName");
+            formData.remove("editingObstacleName");
+        }
+        if (obstacleNameField != null) {
+            obstacleNameField.setText(isMeaningfulValue(name) ? name.trim() : "");
+        }
+        String xyCoords = obstacle.getXyCoordinates();
+        String displayCoords = obstacle.getDisplayCoordinates();
+        if (isMeaningfulValue(xyCoords)) {
+            formData.put("obstacleCoordinates", xyCoords.trim());
+        } else if (isMeaningfulValue(displayCoords)) {
+            formData.put("obstacleCoordinates", displayCoords.trim());
         } else {
             formData.remove("obstacleCoordinates");
         }
+        String originalCoords = obstacle.getOriginalCoordinates();
+        if (isMeaningfulValue(originalCoords)) {
+            formData.put("obstacleOriginalCoordinates", originalCoords.trim());
+        } else {
+            formData.remove("obstacleOriginalCoordinates");
+        }
         String shapeKey = obstacle.getShapeKey();
         if (shapeKey != null) {
             JPanel shapePanel = shapeOptionPanels.get(shapeKey);
@@ -297,6 +450,56 @@
         showStep(2);
     }
 
+    private void handleDeleteExistingObstacle(ExistingObstacle obstacle) {
+        if (obstacle == null) {
+            return;
+        }
+        String obstacleName = obstacle.getName();
+        if (!isMeaningfulValue(obstacleName)) {
+            JOptionPane.showMessageDialog(this, "鏃犳硶鍒犻櫎锛氶殰纰嶇墿鍚嶇О鏃犳晥", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+            return;
+        }
+        int choice = JOptionPane.showConfirmDialog(this,
+                "纭畾瑕佸垹闄ら殰纰嶇墿 \"" + obstacleName + "\" 鍚楋紵姝ゆ搷浣滄棤娉曟挙閿�銆�",
+                "鍒犻櫎纭",
+                JOptionPane.YES_NO_OPTION,
+                JOptionPane.WARNING_MESSAGE);
+        if (choice != JOptionPane.YES_OPTION) {
+            return;
+        }
+        boolean removedFromConfig = deleteObstacleFromConfig(obstacleName);
+    boolean hasPersistedData = obstacle.hasPersistedData() || obstacle.getShapeKey() != null;
+        if (!removedFromConfig && hasPersistedData) {
+            JOptionPane.showMessageDialog(this, "鍒犻櫎澶辫触锛屾湭鑳藉湪閰嶇疆涓Щ闄よ闅滅鐗┿��", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+            return;
+        }
+        if (!existingObstacles.remove(obstacle)) {
+            existingObstacles.removeIf(item -> obstacleName.equals(item != null ? item.getName() : null));
+        }
+        refreshExistingObstacleList();
+        clearEditingStateIfDeleted(obstacleName);
+        JOptionPane.showMessageDialog(this, "闅滅鐗╁凡鍒犻櫎", "鎴愬姛", JOptionPane.INFORMATION_MESSAGE);
+    }
+
+    private void clearEditingStateIfDeleted(String obstacleName) {
+        if (!isMeaningfulValue(obstacleName)) {
+            return;
+        }
+        String editingName = formData.get("editingObstacleName");
+        if (isMeaningfulValue(editingName) && obstacleName.equals(editingName)) {
+            formData.remove("editingObstacleName");
+            formData.remove("obstacleCoordinates");
+            formData.remove("obstacleOriginalCoordinates");
+            if (obstacleNameField != null) {
+                obstacleNameField.setText("");
+            } else {
+                formData.remove("obstacleName");
+            }
+            updateDrawingStatus();
+            updateSaveButtonState();
+        }
+    }
+
     private JPanel createStep2Panel() {
         JPanel stepPanel = new JPanel();
         stepPanel.setLayout(new BoxLayout(stepPanel, BoxLayout.Y_AXIS));
@@ -322,7 +525,7 @@
         stepPanel.add(methodOptionsPanel);
 
         stepPanel.add(Box.createRigidArea(new Dimension(0, 20)));
-        stepPanel.add(createSectionHeader("闅滅鐗╁舰鐘�", "澶氳竟褰㈤渶閲囬泦澶氫釜鐐癸紝鍦嗗舰鍙渶鍦嗗績涓庡渾鍛ㄤ笂涓�鐐�"));
+    stepPanel.add(createSectionHeader("闅滅鐗╁舰鐘�", "澶氳竟褰㈤渶閲囬泦澶氫釜鐐癸紝鍦嗗舰闇�鍦ㄥ渾鍛ㄤ笂閲囬泦鑷冲皯涓変釜鐐�"));
         stepPanel.add(Box.createRigidArea(new Dimension(0, 10)));
 
         shapeOptionsPanel = new JPanel();
@@ -331,7 +534,7 @@
         shapeOptionsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
     shapeOptionsPanel.add(createShapeOption("polygon", "澶氳竟褰�", "渚濇閲囬泦杞粨涓婄殑澶氫釜鐐规垨鑰呮部闅滅鐗╄竟缂樿蛋涓�鍦�"));
         shapeOptionsPanel.add(Box.createRigidArea(new Dimension(0, 10)));
-        shapeOptionsPanel.add(createShapeOption("circle", "鍦嗗舰", "鍏堥噰闆嗗渾蹇冨潗鏍囷紝鍐嶉噰闆嗗渾鍛ㄤ笂涓�鐐�"));
+    shapeOptionsPanel.add(createShapeOption("circle", "鍦嗗舰", "娌垮渾鍛ㄤ换鎰忛噰闆嗕笁涓偣鑷姩鐢熸垚鍦�"));
         stepPanel.add(shapeOptionsPanel);
 
         stepPanel.add(Box.createRigidArea(new Dimension(0, 24)));
@@ -366,10 +569,11 @@
 
         buttonPanel.add(prevButton);
         buttonPanel.add(Box.createHorizontalGlue());
-        buttonPanel.add(nextButton);
         buttonPanel.add(Box.createRigidArea(new Dimension(12, 0)));
         buttonPanel.add(saveButton);
 
+        attachNextButtonToStep1Row();
+
         return buttonPanel;
     }
 
@@ -547,6 +751,10 @@
         if (option == null) {
             return;
         }
+        if (userTriggered && "handheld".equalsIgnoreCase(type) && !hasConfiguredHandheldMarker()) {
+            JOptionPane.showMessageDialog(this, "璇峰厛娣诲姞渚挎惡鎵撶偣鍣ㄧ紪鍙�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+            return;
+        }
         if (selectedMethodPanel != null && selectedMethodPanel != option) {
             resetOptionAppearance(selectedMethodPanel);
         }
@@ -558,6 +766,11 @@
         }
     }
 
+    private boolean hasConfiguredHandheldMarker() {
+        String handheldId = Setsys.getPropertyValue("handheldMarkerId");
+        return handheldId != null && !handheldId.trim().isEmpty();
+    }
+
     private void selectShapeOption(JPanel option, String type, boolean userTriggered) {
         if (option == null) {
             return;
@@ -616,6 +829,22 @@
 
         nextButton.addActionListener(e -> {
             if (currentStep == 1) {
+                String name = obstacleNameField != null ? obstacleNameField.getText() : null;
+                if (!isMeaningfulValue(name)) {
+                    JOptionPane.showMessageDialog(this, "璇峰厛濉啓闅滅鐗╁悕绉�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+                    if (obstacleNameField != null) {
+                        obstacleNameField.requestFocusInWindow();
+                    }
+                    return;
+                }
+                if (!isObstacleNameUnique(name)) {
+                    JOptionPane.showMessageDialog(this, "闅滅鐗╁悕绉板凡瀛樺湪锛岃杈撳叆鍞竴鍚嶇О", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+                    if (obstacleNameField != null) {
+                        obstacleNameField.requestFocusInWindow();
+                    }
+                    return;
+                }
+                formData.put("obstacleName", name.trim());
                 Coordinate.coordinates.clear();
                 showStep(2);
             }
@@ -656,14 +885,37 @@
             return;
         }
 
+    String deviceId = resolveDrawingDeviceId(method);
+    if (!isMeaningfulValue(deviceId)) {
+        String idLabel = "handheld".equalsIgnoreCase(method) ? "鎵嬫寔璁惧缂栧彿" : "鍓茶崏鏈虹紪鍙�";
+        JOptionPane.showMessageDialog(this,
+            "鏈幏鍙栧埌鏈夋晥鐨�" + idLabel + "锛岃鍏堝湪绯荤粺璁剧疆涓畬鎴愰厤缃�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+            return;
+        }
+
         activeDrawingShape = shape.toLowerCase(Locale.ROOT);
 
+        Shouye shouyeInstance = Shouye.getInstance();
+        if (shouyeInstance != null) {
+            shouyeInstance.setHandheldMowerIconActive("handheld".equalsIgnoreCase(method));
+            MapRenderer renderer = shouyeInstance.getMapRenderer();
+            if (renderer != null) {
+                renderer.clearIdleTrail();
+            }
+        }
+
         Coordinate.coordinates.clear();
+        Coordinate.setActiveDeviceIdFilter(deviceId);
         Coordinate.setStartSaveGngga(true);
+        yulanzhangaiwu.startPreview(activeDrawingShape, baseStation);
         drawingInProgress = true;
         drawButton.setText("姝e湪缁樺埗...");
         drawButton.setEnabled(false);
-        drawingStatusLabel.setText("姝e湪閲囬泦闅滅鐗╁潗鏍囷紝璇峰湪涓荤晫闈㈠畬鎴愮粯鍒躲��");
+        if ("circle".equals(activeDrawingShape)) {
+            drawingStatusLabel.setText("姝e湪閲囬泦鍦嗗舰闅滅鐗╋紝璇锋部鍦嗗懆閲囬泦鑷冲皯涓変釜鐐瑰悗鍦ㄤ富鐣岄潰缁撴潫缁樺埗銆�");
+        } else {
+            drawingStatusLabel.setText("姝e湪閲囬泦闅滅鐗╁潗鏍囷紝璇峰湪涓荤晫闈㈠畬鎴愮粯鍒躲��");
+        }
 
         if (activeSession == null) {
             activeSession = new ObstacleDrawingSession();
@@ -692,6 +944,8 @@
 
     public static void finishDrawingSession() {
         Coordinate.setStartSaveGngga(false);
+        Coordinate.clearActiveDeviceIdFilter();
+        yulanzhangaiwu.stopPreview();
 
         Shouye shouye = Shouye.getInstance();
         if (shouye != null) {
@@ -710,11 +964,20 @@
         showDialog(parent, activeSession.target);
     }
 
+    public static String getActiveSessionBaseStation() {
+        if (activeSession != null && isMeaningfulValue(activeSession.baseStation)) {
+            return activeSession.baseStation.trim();
+        }
+        return null;
+    }
+
     /**
      * Stops an in-progress circle capture and reopens the wizard on step 2.
      */
     public static void abortCircleDrawingAndReturn(String message) {
         Coordinate.setStartSaveGngga(false);
+        Coordinate.clearActiveDeviceIdFilter();
+        yulanzhangaiwu.stopPreview();
         Coordinate.coordinates.clear();
 
         if (activeSession == null || activeSession.target == null) {
@@ -750,6 +1013,13 @@
             return;
         }
 
+        String originalCoordStr = buildOriginalCoordinateString(captured);
+        if (isMeaningfulValue(originalCoordStr)) {
+            session.data.put("obstacleOriginalCoordinates", originalCoordStr);
+        } else {
+            session.data.remove("obstacleOriginalCoordinates");
+        }
+
         List<double[]> xyPoints = convertToLocalXY(captured, session.baseStation);
         if (xyPoints.isEmpty()) {
             session.captureSuccessful = false;
@@ -759,18 +1029,28 @@
 
         String shape = session.data.get("obstacleShape");
         if ("circle".equals(shape)) {
-            if (xyPoints.size() < 2) {
+            if (xyPoints.size() < 3) {
                 session.captureSuccessful = false;
-                session.captureMessage = "鍦嗗舰闅滅鐗╄嚦灏戦渶瑕佷袱涓噰闆嗙偣锛堝渾蹇冨拰鍦嗗懆鐐癸級";
+                session.captureMessage = "鍦嗗舰闅滅鐗╄嚦灏戦渶瑕佷笁涓噰闆嗙偣锛堝渾鍛ㄤ笂鐨勭偣锛�";
                 return;
             }
-            double[] center = xyPoints.get(0);
-            double[] radiusPoint = xyPoints.get(xyPoints.size() - 1);
+            CircleFitResult circle = fitCircleFromPoints(xyPoints);
+            if (circle == null) {
+                session.captureSuccessful = false;
+                session.captureMessage = "鏃犳硶鏍规嵁閲囬泦鐨勭偣鐢熸垚鍦嗭紝璇风‘淇濋�夋嫨浜嗕笁涓潪鍏辩嚎鐨勫渾鍛ㄧ偣";
+                return;
+            }
+            double[] radiusPoint = pickRadiusPoint(xyPoints, circle);
+            if (radiusPoint == null) {
+                session.captureSuccessful = false;
+                session.captureMessage = "閲囬泦鐨勫渾鍛ㄧ偣寮傚父锛屾棤娉曠敓鎴愬渾";
+                return;
+            }
             String result = String.format(Locale.US, "%.2f,%.2f;%.2f,%.2f",
-                    center[0], center[1], radiusPoint[0], radiusPoint[1]);
+                    circle.centerX, circle.centerY, radiusPoint[0], radiusPoint[1]);
             session.data.put("obstacleCoordinates", result);
             session.captureSuccessful = true;
-            session.captureMessage = "宸查噰闆嗗渾褰㈤殰纰嶇墿鍧愭爣";
+            session.captureMessage = "宸查噰闆嗗渾褰㈤殰纰嶇墿锛屽叡 " + xyPoints.size() + " 涓偣";
         } else {
             if (xyPoints.size() < 3) {
                 session.captureSuccessful = false;
@@ -816,6 +1096,67 @@
         return result;
     }
 
+    private static String buildOriginalCoordinateString(List<Coordinate> coords) {
+        if (coords == null || coords.isEmpty()) {
+            return null;
+        }
+        StringBuilder sb = new StringBuilder();
+        for (Coordinate coord : coords) {
+            if (coord == null) {
+                continue;
+            }
+            String latToken = sanitizeCoordinateToken(coord.getLatitude());
+            String lonToken = sanitizeCoordinateToken(coord.getLongitude());
+            if (latToken == null || lonToken == null) {
+                continue;
+            }
+            char latDir = sanitizeDirection(coord.getLatDirection(), 'N');
+            char lonDir = sanitizeDirection(coord.getLonDirection(), 'E');
+            if (sb.length() > 0) {
+                sb.append(";");
+            }
+            sb.append(latToken)
+              .append(",")
+              .append(latDir)
+              .append(",")
+              .append(lonToken)
+              .append(",")
+              .append(lonDir);
+        }
+        return sb.length() > 0 ? sb.toString() : null;
+    }
+
+    private static String sanitizeCoordinateToken(String token) {
+        if (token == null) {
+            return null;
+        }
+        String trimmed = token.trim();
+        if (trimmed.isEmpty()) {
+            return null;
+        }
+        try {
+            Double.parseDouble(trimmed);
+            return trimmed;
+        } catch (NumberFormatException ex) {
+            return null;
+        }
+    }
+
+    private static char sanitizeDirection(String direction, char fallback) {
+        if (direction == null) {
+            return fallback;
+        }
+        String trimmed = direction.trim();
+        if (trimmed.isEmpty()) {
+            return fallback;
+        }
+        char ch = Character.toUpperCase(trimmed.charAt(0));
+        if (ch != 'N' && ch != 'S' && ch != 'E' && ch != 'W') {
+            return fallback;
+        }
+        return ch;
+    }
+
     private static double parseDMToDecimal(String dmm, String direction) {
         if (dmm == null || dmm.trim().isEmpty()) {
             return Double.NaN;
@@ -847,6 +1188,101 @@
         return new double[]{eastMeters, northMeters};
     }
 
+    private static CircleFitResult fitCircleFromPoints(List<double[]> points) {
+        if (points == null || points.size() < 3) {
+            return null;
+        }
+        CircleFitResult 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);
+                    CircleFitResult 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 static CircleFitResult circleFromThreePoints(double[] p1, double[] p2, double[] p3) {
+        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 determinant = 2.0 * (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2));
+        if (Math.abs(determinant) < 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)) / determinant;
+        double centerY = (x1Sq * (x3 - x2) + x2Sq * (x1 - x3) + x3Sq * (x2 - x1)) / determinant;
+        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 CircleFitResult(centerX, centerY, radius, new double[]{x1, y1});
+    }
+
+    private static double[] pickRadiusPoint(List<double[]> points, CircleFitResult circle) {
+        if (circle == null || points == null || points.isEmpty()) {
+            return null;
+        }
+        double[] best = null;
+        double bestDistance = 0.0;
+        for (double[] pt : points) {
+            double dist = Math.hypot(pt[0] - circle.centerX, pt[1] - circle.centerY);
+            if (dist > bestDistance) {
+                bestDistance = dist;
+                best = pt;
+            }
+        }
+        return best;
+    }
+
+    private static double distance(double[] a, double[] b) {
+        double dx = a[0] - b[0];
+        double dy = a[1] - b[1];
+        return Math.hypot(dx, dy);
+    }
+
+    private static final class CircleFitResult {
+        final double centerX;
+        final double centerY;
+        final double radius;
+        final double[] referencePoint;
+
+        CircleFitResult(double centerX, double centerY, double radius, double[] referencePoint) {
+            this.centerX = centerX;
+            this.centerY = centerY;
+            this.radius = radius;
+            this.referencePoint = referencePoint;
+        }
+    }
+
     private List<ExistingObstacle> resolveExistingObstacles(Dikuai target, List<String> providedNames) {
         String landNumber = target != null ? target.getLandNumber() : null;
         Map<String, ExistingObstacle> configMap = loadObstacleDetailsFromConfig(landNumber);
@@ -914,11 +1350,11 @@
                 }
                 String trimmedName = name.trim();
                 Obstacledge.ObstacleShape shape = obstacle.getShape();
-                String xyCoords = obstacle.getXyCoordsString();
-                String original = obstacle.getOriginalCoordsString();
-                String coords = isMeaningfulValue(xyCoords) ? xyCoords.trim()
-                        : (isMeaningfulValue(original) ? original.trim() : "");
-                details.put(trimmedName, new ExistingObstacle(trimmedName, shape, coords));
+        String xyCoords = obstacle.getXyCoordsString();
+        String original = obstacle.getOriginalCoordsString();
+        String xy = isMeaningfulValue(xyCoords) ? xyCoords.trim() : "";
+        String originalTrimmed = isMeaningfulValue(original) ? original.trim() : "";
+        details.put(trimmedName, new ExistingObstacle(trimmedName, shape, xy, originalTrimmed));
             }
         } catch (Exception ex) {
             System.err.println("鍔犺浇闅滅鐗╄鎯呭け璐�: " + ex.getMessage());
@@ -927,10 +1363,9 @@
     }
 
     private void preloadData() {
-        String existing = targetDikuai.getObstacleCoordinates();
-        if (isMeaningfulValue(existing)) {
-            formData.put("obstacleCoordinates", existing.trim());
-        }
+        formData.remove("obstacleCoordinates");
+        formData.remove("obstacleOriginalCoordinates");
+        formData.remove("editingObstacleName");
         updateDrawingStatus();
     }
 
@@ -979,11 +1414,40 @@
 
     private void updateSaveButtonState() {
         if (saveButton != null) {
-            saveButton.setEnabled(isMeaningfulValue(formData.get("obstacleCoordinates")));
+            boolean hasCoords = isMeaningfulValue(formData.get("obstacleCoordinates"));
+            boolean hasName = isMeaningfulValue(formData.get("obstacleName"));
+            boolean enabled = hasCoords && hasName;
+            saveButton.setEnabled(enabled);
+            if (enabled) {
+                saveButton.setBackground(PRIMARY_COLOR);
+                saveButton.setForeground(WHITE);
+                saveButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+            } else {
+                saveButton.setBackground(MEDIUM_GRAY);
+                saveButton.setForeground(TEXT_COLOR);
+                saveButton.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+            }
         }
     }
 
     private boolean validateStep2() {
+        String name = formData.get("obstacleName");
+        if (!isMeaningfulValue(name)) {
+            JOptionPane.showMessageDialog(this, "璇峰~鍐欓殰纰嶇墿鍚嶇О", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+            showStep(1);
+            if (obstacleNameField != null) {
+                obstacleNameField.requestFocusInWindow();
+            }
+            return false;
+        }
+        if (!isObstacleNameUnique(name)) {
+            JOptionPane.showMessageDialog(this, "闅滅鐗╁悕绉板凡瀛樺湪锛岃杈撳叆鍞竴鍚嶇О", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+            showStep(1);
+            if (obstacleNameField != null) {
+                obstacleNameField.requestFocusInWindow();
+            }
+            return false;
+        }
         if (!isMeaningfulValue(formData.get("obstacleCoordinates"))) {
             JOptionPane.showMessageDialog(this, "璇峰厛瀹屾垚闅滅鐗╃粯鍒�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
             return false;
@@ -995,34 +1459,184 @@
         if (!validateStep2()) {
             return;
         }
-        String coords = formData.get("obstacleCoordinates").trim();
         String landNumber = targetDikuai.getLandNumber();
-        if (!Dikuai.updateField(landNumber, "obstacleCoordinates", coords)) {
-            JOptionPane.showMessageDialog(this, "鏃犳硶鏇存柊闅滅鐗╁潗鏍�", "閿欒", JOptionPane.ERROR_MESSAGE);
+        String obstacleName = formData.get("obstacleName");
+        String coordsValue = formData.get("obstacleCoordinates");
+        String originalValue = formData.get("obstacleOriginalCoordinates");
+        String shapeKey = formData.get("obstacleShape");
+        String previousName = formData.get("editingObstacleName");
+
+        if (!isMeaningfulValue(coordsValue)) {
+            JOptionPane.showMessageDialog(this, "闅滅鐗╁潗鏍囨棤鏁�", "閿欒", JOptionPane.ERROR_MESSAGE);
+            return;
+        }
+
+        String coords = coordsValue.trim();
+        String originalCoords = isMeaningfulValue(originalValue) ? originalValue.trim() : null;
+        String trimmedName = isMeaningfulValue(obstacleName) ? obstacleName.trim() : null;
+        if (!isMeaningfulValue(trimmedName)) {
+            JOptionPane.showMessageDialog(this, "闅滅鐗╁悕绉版棤鏁�", "閿欒", JOptionPane.ERROR_MESSAGE);
+            return;
+        }
+
+        formData.put("obstacleName", trimmedName);
+        if (isMeaningfulValue(previousName)) {
+            formData.put("editingObstacleName", trimmedName);
+        }
+
+        if (!persistObstacleToConfig(landNumber, previousName, trimmedName, shapeKey, coords, originalCoords)) {
+            JOptionPane.showMessageDialog(this, "鍐欏叆闅滅鐗╅厤缃け璐ワ紝璇烽噸璇�", "閿欒", JOptionPane.ERROR_MESSAGE);
             return;
         }
         Dikuai.updateField(landNumber, "updateTime", currentTime());
         Dikuai.saveToProperties();
         Dikuaiguanli.notifyExternalCreation(landNumber);
 
-        JOptionPane.showMessageDialog(this, "闅滅鐗╁潗鏍囧凡淇濆瓨", "鎴愬姛", JOptionPane.INFORMATION_MESSAGE);
+    JOptionPane.showMessageDialog(this, "闅滅鐗╂暟鎹凡淇濆瓨", "鎴愬姛", JOptionPane.INFORMATION_MESSAGE);
         activeSession = null;
         dispose();
     }
 
+    private boolean persistObstacleToConfig(String landNumber, String previousName, String obstacleName,
+                                            String shapeKey, String xyCoords, String originalCoords) {
+        if (!isMeaningfulValue(landNumber) || !isMeaningfulValue(obstacleName) || !isMeaningfulValue(xyCoords)) {
+            return false;
+        }
+
+        String normalizedLandNumber = landNumber.trim();
+        String normalizedNewName = obstacleName.trim();
+        String normalizedPrevious = isMeaningfulValue(previousName) ? previousName.trim() : null;
+
+        String normalizedXy = xyCoords.trim();
+
+        try {
+            File configFile = new File("Obstacledge.properties");
+            Obstacledge.ConfigManager manager = new Obstacledge.ConfigManager();
+            if (configFile.exists()) {
+                if (!manager.loadFromFile(configFile.getAbsolutePath())) {
+                    return false;
+                }
+            }
+
+            Obstacledge.Plot plot = manager.getPlotById(normalizedLandNumber);
+            if (plot == null) {
+                plot = new Obstacledge.Plot(normalizedLandNumber);
+                manager.addPlot(plot);
+            }
+
+            ensurePlotBaseStation(plot);
+
+            if (normalizedPrevious != null && !normalizedPrevious.equals(normalizedNewName)) {
+                plot.removeObstacleByName(normalizedPrevious);
+            }
+
+            Obstacledge.Obstacle obstacle = plot.getObstacleByName(normalizedNewName);
+            if (obstacle == null) {
+                obstacle = new Obstacledge.Obstacle(normalizedLandNumber, normalizedNewName,
+                        determineObstacleShape(shapeKey, normalizedXy));
+                plot.addObstacle(obstacle);
+            }
+
+            obstacle.setPlotId(normalizedLandNumber);
+            obstacle.setObstacleName(normalizedNewName);
+            obstacle.setShape(determineObstacleShape(shapeKey, normalizedXy));
+
+            obstacle.setXyCoordinates(new ArrayList<Obstacledge.XYCoordinate>());
+            obstacle.setOriginalCoordinates(new ArrayList<Obstacledge.DMCoordinate>());
+
+            obstacle.setXyCoordsString(normalizedXy);
+
+            if (isMeaningfulValue(originalCoords)) {
+                try {
+                    obstacle.setOriginalCoordsString(originalCoords);
+                } catch (Exception parseEx) {
+                    System.err.println("瑙f瀽闅滅鐗╁師濮嬪潗鏍囧け璐ワ紝灏嗕娇鐢ㄥ崰浣嶅��: " + parseEx.getMessage());
+                    obstacle.setOriginalCoordinates(new ArrayList<Obstacledge.DMCoordinate>());
+                }
+            }
+
+            ensurePlaceholderOriginalCoords(obstacle);
+
+            return manager.saveToFile(configFile.getAbsolutePath());
+        } catch (Exception ex) {
+            System.err.println("淇濆瓨闅滅鐗╅厤缃け璐�: " + ex.getMessage());
+            return false;
+        }
+    }
+
+    private Obstacledge.ObstacleShape determineObstacleShape(String shapeKey, String xyCoords) {
+        if (isMeaningfulValue(shapeKey)) {
+            String normalized = shapeKey.trim().toLowerCase(Locale.ROOT);
+            if ("circle".equals(normalized) || "鍦嗗舰".equals(normalized) || "0".equals(normalized)) {
+                return Obstacledge.ObstacleShape.CIRCLE;
+            }
+            if ("polygon".equals(normalized) || "澶氳竟褰�".equals(normalized) || "1".equals(normalized)) {
+                return Obstacledge.ObstacleShape.POLYGON;
+            }
+        }
+
+        int pairCount = countCoordinatePairs(xyCoords);
+        if (pairCount <= 0) {
+            return Obstacledge.ObstacleShape.POLYGON;
+        }
+        if (pairCount <= 2) {
+            return Obstacledge.ObstacleShape.CIRCLE;
+        }
+        return Obstacledge.ObstacleShape.POLYGON;
+    }
+
+    private void ensurePlaceholderOriginalCoords(Obstacledge.Obstacle obstacle) {
+        if (obstacle == null) {
+            return;
+        }
+        List<Obstacledge.DMCoordinate> originals = obstacle.getOriginalCoordinates();
+        if (originals != null && !originals.isEmpty()) {
+            return;
+        }
+        List<Obstacledge.XYCoordinate> xyCoords = obstacle.getXyCoordinates();
+        int pointCount = (xyCoords != null && !xyCoords.isEmpty()) ? xyCoords.size() : 1;
+        List<Obstacledge.DMCoordinate> placeholders = new ArrayList<>(pointCount * 2);
+        for (int i = 0; i < pointCount; i++) {
+            placeholders.add(new Obstacledge.DMCoordinate(0.0, 'N'));
+            placeholders.add(new Obstacledge.DMCoordinate(0.0, 'E'));
+        }
+        obstacle.setOriginalCoordinates(placeholders);
+    }
+
+    private void ensurePlotBaseStation(Obstacledge.Plot plot) {
+        if (plot == null) {
+            return;
+        }
+        String existing = plot.getBaseStationString();
+        if (isMeaningfulValue(existing)) {
+            return;
+        }
+        String baseStation = resolveBaseStationCoordinates();
+        if (!isMeaningfulValue(baseStation)) {
+            return;
+        }
+        try {
+            plot.setBaseStationString(baseStation.trim());
+        } catch (Exception ex) {
+            System.err.println("璁剧疆鍩哄噯绔欏潗鏍囧け璐�: " + ex.getMessage());
+        }
+    }
+
     private static final class ExistingObstacle {
         private final String name;
         private final Obstacledge.ObstacleShape shape;
-        private final String coordinates;
+        private final String xyCoordinates;
+        private final String originalCoordinates;
 
-        ExistingObstacle(String name, Obstacledge.ObstacleShape shape, String coordinates) {
+        ExistingObstacle(String name, Obstacledge.ObstacleShape shape, String xyCoordinates, String originalCoordinates) {
             this.name = name != null ? name : "";
             this.shape = shape;
-            this.coordinates = coordinates != null ? coordinates : "";
+            this.xyCoordinates = safeCoordString(xyCoordinates);
+            this.originalCoordinates = safeCoordString(originalCoordinates);
         }
 
         static ExistingObstacle placeholder(String name) {
-            return new ExistingObstacle(name, null, "");
+            return new ExistingObstacle(name, null, "", "");
         }
 
         String getName() {
@@ -1047,7 +1661,38 @@
         }
 
         String getCoordinates() {
-            return coordinates;
+            return getDisplayCoordinates();
+        }
+
+        String getDisplayCoordinates() {
+            return hasText(xyCoordinates) ? xyCoordinates : originalCoordinates;
+        }
+
+        String getXyCoordinates() {
+            return xyCoordinates;
+        }
+
+        String getOriginalCoordinates() {
+            return originalCoordinates;
+        }
+
+        boolean hasPersistedData() {
+            return hasText(xyCoordinates) || hasText(originalCoordinates);
+        }
+
+        private static String safeCoordString(String value) {
+            if (value == null) {
+                return "";
+            }
+            return value.trim();
+        }
+
+        private static boolean hasText(String value) {
+            if (value == null) {
+                return false;
+            }
+            String trimmed = value.trim();
+            return !trimmed.isEmpty() && !"-1".equals(trimmed);
         }
     }
 
@@ -1076,6 +1721,11 @@
         formData.clear();
         formData.putAll(session.data);
 
+        String sessionName = session.data.get("obstacleName");
+        if (obstacleNameField != null) {
+            obstacleNameField.setText(sessionName != null ? sessionName : "");
+        }
+
         String method = session.data.get("drawingMethod");
         if (method != null) {
             JPanel panel = methodOptionPanels.get(method);
@@ -1092,7 +1742,7 @@
             }
         }
 
-        if (session.captureMessage != null) {
+    if (session.captureMessage != null && !session.captureSuccessful) {
             JOptionPane.showMessageDialog(this,
                     session.captureMessage,
                     session.captureSuccessful ? "鎴愬姛" : "鎻愮ず",
@@ -1108,6 +1758,93 @@
         return createInfoRow(label, value, null);
     }
 
+    private JPanel createObstacleNameRow() {
+        JPanel row = new JPanel();
+        row.setLayout(new BoxLayout(row, BoxLayout.X_AXIS));
+        row.setOpaque(false);
+        row.setAlignmentX(Component.LEFT_ALIGNMENT);
+        row.setMaximumSize(new Dimension(Integer.MAX_VALUE, 36));
+
+        JLabel label = new JLabel("闅滅鐗╁悕绉帮細");
+        label.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 14));
+        label.setForeground(TEXT_COLOR);
+        label.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+        if (obstacleNameField == null) {
+            obstacleNameField = new JTextField();
+            obstacleNameField.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 14));
+            obstacleNameField.setColumns(16);
+            Dimension fieldSize = new Dimension(240, 30);
+            obstacleNameField.setPreferredSize(fieldSize);
+            obstacleNameField.setMinimumSize(fieldSize);
+            obstacleNameField.setMaximumSize(new Dimension(Integer.MAX_VALUE, 30));
+            obstacleNameField.setBorder(BorderFactory.createCompoundBorder(
+                    BorderFactory.createLineBorder(BORDER_COLOR, 1),
+                    BorderFactory.createEmptyBorder(4, 8, 4, 8)));
+            obstacleNameField.getDocument().addDocumentListener(new DocumentListener() {
+                @Override
+                public void insertUpdate(DocumentEvent e) {
+                    handleObstacleNameChanged();
+                }
+
+                @Override
+                public void removeUpdate(DocumentEvent e) {
+                    handleObstacleNameChanged();
+                }
+
+                @Override
+                public void changedUpdate(DocumentEvent e) {
+                    handleObstacleNameChanged();
+                }
+            });
+        }
+
+        String existing = formData.get("obstacleName");
+        if (existing != null && !existing.equals(obstacleNameField.getText())) {
+            obstacleNameField.setText(existing);
+        }
+
+        row.add(label);
+        row.add(Box.createRigidArea(new Dimension(10, 0)));
+        row.add(obstacleNameField);
+        row.add(Box.createHorizontalGlue());
+        return row;
+    }
+
+    private void handleObstacleNameChanged() {
+        if (obstacleNameField == null) {
+            return;
+        }
+        String text = obstacleNameField.getText();
+        if (isMeaningfulValue(text)) {
+            formData.put("obstacleName", text.trim());
+        } else {
+            formData.remove("obstacleName");
+        }
+        updateSaveButtonState();
+    }
+
+    private boolean isObstacleNameUnique(String candidate) {
+        if (!isMeaningfulValue(candidate)) {
+            return false;
+        }
+        String trimmed = candidate.trim().toLowerCase(Locale.ROOT);
+        String original = formData.get("editingObstacleName");
+        if (isMeaningfulValue(original) && trimmed.equals(original.trim().toLowerCase(Locale.ROOT))) {
+            return true;
+        }
+        for (ExistingObstacle obstacle : existingObstacles) {
+            if (obstacle == null) {
+                continue;
+            }
+            String existingName = obstacle.getName();
+            if (isMeaningfulValue(existingName) && trimmed.equals(existingName.trim().toLowerCase(Locale.ROOT))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     private JPanel createInfoRow(String label, String value, String tooltip) {
         JPanel row = new JPanel();
         row.setLayout(new BoxLayout(row, BoxLayout.X_AXIS));
@@ -1136,15 +1873,12 @@
     }
 
     private JButton createPrimaryButton(String text, int fontSize) {
-        JButton button = new JButton(text);
+        JButton button = buttonset.createStyledButton(text, PRIMARY_COLOR);
         button.setFont(new Font("寰蒋闆呴粦", Font.BOLD, fontSize));
-        button.setBackground(PRIMARY_COLOR);
-        button.setForeground(WHITE);
         button.setBorder(BorderFactory.createCompoundBorder(
                 BorderFactory.createLineBorder(PRIMARY_DARK, 2),
                 BorderFactory.createEmptyBorder(10, 22, 10, 22)));
         button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
-        button.setFocusPainted(false);
         button.addMouseListener(new MouseAdapter() {
             @Override
             public void mouseEntered(MouseEvent e) {
@@ -1164,15 +1898,13 @@
     }
 
     private JButton createSecondaryButton(String text) {
-        JButton button = new JButton(text);
+        JButton button = buttonset.createStyledButton(text, MEDIUM_GRAY);
         button.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 16));
-        button.setBackground(MEDIUM_GRAY);
         button.setForeground(TEXT_COLOR);
         button.setBorder(BorderFactory.createCompoundBorder(
                 BorderFactory.createLineBorder(BORDER_COLOR, 2),
                 BorderFactory.createEmptyBorder(10, 22, 10, 22)));
         button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
-        button.setFocusPainted(false);
         return button;
     }
 
@@ -1206,6 +1938,18 @@
         return null;
     }
 
+    private String resolveDrawingDeviceId(String method) {
+        if (!isMeaningfulValue(method)) {
+            return null;
+        }
+        String key = "handheld".equalsIgnoreCase(method) ? "handheldMarkerId" : "mowerId";
+        String value = Setsys.getPropertyValue(key);
+        if (!isMeaningfulValue(value)) {
+            return null;
+        }
+        return value.trim();
+    }
+
     private String safeValue(String value, String fallback) {
         if (!isMeaningfulValue(value)) {
             return fallback;

--
Gitblit v1.10.0