| | |
| | | private JPanel floatingButtonPanel; |
| | | private JPanel floatingButtonColumn; |
| | | private Runnable endDrawingCallback; |
| | | private JButton pathPreviewReturnButton; |
| | | private boolean pathPreviewActive; |
| | | private Runnable pathPreviewReturnAction; |
| | | private String previewRestoreLandNumber; |
| | | private String previewRestoreLandName; |
| | | private boolean drawingPaused; |
| | | private ImageIcon pauseIcon; |
| | | private ImageIcon pauseActiveIcon; |
| | |
| | | private double[] circleBaseLatLon; |
| | | private Timer circleDataMonitor; |
| | | private Coordinate lastCapturedCoordinate; |
| | | private HandheldBoundaryCaptureDialog handheldCaptureDialog; |
| | | private boolean handheldCaptureActive; |
| | | private int handheldCapturedPoints; |
| | | private final List<Point2D.Double> handheldTemporaryPoints = new ArrayList<>(); |
| | |
| | | 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); |
| | | dellmessage.registerLineListener(serialLineListener); |
| | | initializeUI(); |
| | | setupEventHandlers(); |
| | | scheduleIdentifierCheck(); |
| | | } |
| | | |
| | | public static Shouye getInstance() { |
| | |
| | | refreshMapForSelectedArea(); |
| | | } |
| | | |
| | | 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(); |
| | | }); |
| | | } |
| | | } |
| | | }; |
| | | addHierarchyListener(listener); |
| | | } |
| | | |
| | | private void showInitialMowerSelfCheckDialogIfNeeded() { |
| | | zijian.showInitialPromptIfNeeded(this, this::showRemoteControlDialog); |
| | | } |
| | | |
| | | private void applyIdleTrailDurationFromSettings() { |
| | | if (mapRenderer == null) { |
| | | return; |
| | |
| | | } |
| | | if (remoteDialog != null) { |
| | | positionRemoteDialogBottomCenter(remoteDialog); |
| | | zijian.markSelfCheckCompleted(); |
| | | remoteDialog.setVisible(true); |
| | | } |
| | | remoteDialog.setVisible(true); |
| | | } |
| | | |
| | | private void positionRemoteDialogBottomCenter(RemoteControlDialog dialog) { |
| | |
| | | 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; |
| | |
| | | } |
| | | |
| | | private void toggleStartPause() { |
| | | if (handheldCaptureInlineUiActive) { |
| | | handleHandheldConfirmAction(); |
| | | return; |
| | | } |
| | | if (drawingControlModeActive) { |
| | | toggleDrawingPause(); |
| | | return; |
| | | } |
| | | if (startBtn == null) { |
| | | return; |
| | | } |
| | | if (startButtonShowingPause) { |
| | | if (!zijian.ensureBeforeMowing(this, this::showRemoteControlDialog)) { |
| | | return; |
| | | } |
| | | } |
| | | startButtonShowingPause = !startButtonShowingPause; |
| | | if (!startButtonShowingPause) { |
| | | statusLabel.setText("作业中"); |
| | |
| | | } |
| | | |
| | | private void handleStopAction() { |
| | | if (handheldCaptureInlineUiActive) { |
| | | handleHandheldFinishAction(); |
| | | return; |
| | | } |
| | | if (drawingControlModeActive) { |
| | | handleDrawingStopFromControlPanel(); |
| | | return; |
| | | } |
| | | stopButtonActive = !stopButtonActive; |
| | | updateStopButtonIcon(); |
| | | if (stopButtonActive) { |
| | |
| | | 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; |
| | |
| | | refreshMowerSpeedLabel(); |
| | | } |
| | | |
| | | public void setHandheldMowerIconActive(boolean active) { |
| | | if (mapRenderer == null) { |
| | | return; |
| | | } |
| | | mapRenderer.setHandheldMowerIconActive(active); |
| | | } |
| | | |
| | | public boolean startMowerBoundaryCapture() { |
| | | if (mapRenderer == null) { |
| | | return false; |
| | |
| | | return false; |
| | | } |
| | | |
| | | mapRenderer.clearIdleTrail(); |
| | | |
| | | activeBoundaryMode = BoundaryCaptureMode.MOWER; |
| | | mowerBoundaryCaptureActive = true; |
| | | mowerBaseLatLon = baseLatLonCandidate; |
| | |
| | | Coordinate.setStartSaveGngga(true); |
| | | |
| | | if (mapRenderer != null) { |
| | | mapRenderer.setBoundaryPreviewMarkerScale(2.0d); |
| | | mapRenderer.beginHandheldBoundaryPreview(); |
| | | } |
| | | |
| | | setHandheldMowerIconActive(false); |
| | | |
| | | startMowerBoundaryMonitor(); |
| | | return true; |
| | | } |
| | |
| | | if (mapRenderer == null) { |
| | | return false; |
| | | } |
| | | if (handheldCaptureDialog != null && handheldCaptureDialog.isShowing()) { |
| | | handheldCaptureDialog.toFront(); |
| | | return true; |
| | | } |
| | | |
| | | if (activeBoundaryMode == BoundaryCaptureMode.MOWER) { |
| | | stopMowerBoundaryCapture(); |
| | | } |
| | | |
| | | mapRenderer.clearIdleTrail(); |
| | | |
| | | activeBoundaryMode = BoundaryCaptureMode.HANDHELD; |
| | | handheldCaptureActive = true; |
| | | handheldCapturedPoints = 0; |
| | |
| | | handheldTemporaryPoints.clear(); |
| | | } |
| | | AddDikuai.recordTemporaryBoundaryPoints(Collections.emptyList()); |
| | | mapRenderer.beginHandheldBoundaryPreview(); |
| | | |
| | | Window ownerWindow = SwingUtilities.getWindowAncestor(this); |
| | | SwingUtilities.invokeLater(() -> { |
| | | Window targetOwner = ownerWindow; |
| | | if (targetOwner == null) { |
| | | targetOwner = SwingUtilities.getWindowAncestor(Shouye.this); |
| | | } |
| | | HandheldBoundaryCaptureDialog dialog = new HandheldBoundaryCaptureDialog(targetOwner, Shouye.this, visualizationPanel, THEME_COLOR); |
| | | handheldCaptureDialog = dialog; |
| | | dialog.setVisible(true); |
| | | }); |
| | | |
| | | mapRenderer.setBoundaryPreviewMarkerScale(1.0d); |
| | | mapRenderer.beginHandheldBoundaryPreview(); |
| | | setHandheldMowerIconActive(true); |
| | | enterHandheldCaptureInlineUi(); |
| | | return true; |
| | | } |
| | | |
| | |
| | | if (activeBoundaryMode == BoundaryCaptureMode.MOWER) { |
| | | activeBoundaryMode = BoundaryCaptureMode.NONE; |
| | | } |
| | | setHandheldMowerIconActive(false); |
| | | } |
| | | |
| | | private void discardLatestCoordinate(Coordinate coordinate) { |
| | |
| | | |
| | | 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; |
| | | } |
| | | |
| | | void handheldBoundaryCaptureDialogClosed(HandheldBoundaryCaptureDialog dialog) { |
| | | if (handheldCaptureDialog == dialog) { |
| | | handheldCaptureDialog = null; |
| | | } |
| | | handheldCaptureActive = false; |
| | | if (activeBoundaryMode == BoundaryCaptureMode.HANDHELD) { |
| | | activeBoundaryMode = BoundaryCaptureMode.NONE; |
| | | } |
| | | } |
| | | |
| | | int getHandheldCapturedPointCount() { |
| | | return handheldCapturedPoints; |
| | | } |
| | |
| | | if (statusLabel == null) { |
| | | return; |
| | | } |
| | | if ("作业中".equals(statusText)) { |
| | | if ("作业中".equals(statusText) || "绘制中".equals(statusText)) { |
| | | statusLabel.setForeground(THEME_COLOR); |
| | | } else if ("暂停中".equals(statusText)) { |
| | | } else if ("暂停中".equals(statusText) || "绘制暂停".equals(statusText)) { |
| | | statusLabel.setForeground(STATUS_PAUSE_COLOR); |
| | | } else { |
| | | statusLabel.setForeground(Color.GRAY); |
| | |
| | | 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 (notifyCoordinate) { |
| | | Coordinate.setStartSaveGngga(!paused); |
| | | } |
| | | if (drawingControlModeActive) { |
| | | updateDrawingControlButtonLabels(); |
| | | if (statusLabel != null) { |
| | | statusLabel.setText(paused ? "绘制暂停" : "绘制中"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void toggleDrawingPause() { |
| | |
| | | |
| | | public void showEndDrawingButton(Runnable callback, String drawingShape) { |
| | | endDrawingCallback = callback; |
| | | applyDrawingPauseState(false, false); |
| | | circleDialogMode = false; |
| | | hideCircleGuidancePanel(); |
| | | |
| | | ensureFloatingIconsLoaded(); |
| | | ensureFloatingButtonInfrastructure(); |
| | | enterDrawingControlMode(); |
| | | |
| | | boolean enableCircleGuidance = drawingShape != null |
| | | && "circle".equalsIgnoreCase(drawingShape.trim()); |
| | | if (enableCircleGuidance) { |
| | | prepareCircleGuidanceState(); |
| | | showCircleGuidanceStep(1); |
| | | endDrawingButton.setVisible(false); |
| | | 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(); |
| | | endDrawingButton.setVisible(true); |
| | | if (drawingPauseButton != null) { |
| | | drawingPauseButton.setVisible(true); |
| | | } |
| | | hideFloatingDrawingControls(); |
| | | } |
| | | |
| | | floatingButtonPanel.setVisible(true); |
| | | if (floatingButtonPanel.getParent() != visualizationPanel) { |
| | | visualizationPanel.add(floatingButtonPanel, BorderLayout.SOUTH); |
| | | } |
| | | |
| | | rebuildFloatingButtonColumn(); |
| | | visualizationPanel.revalidate(); |
| | | visualizationPanel.repaint(); |
| | | } |
| | |
| | | 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; |
| | | } |
| | | floatingButtonColumn.revalidate(); |
| | | floatingButtonColumn.repaint(); |
| | |
| | | |
| | | private double[] resolveCircleBaseLatLon() { |
| | | String coords = null; |
| | | String landNumber = Dikuaiguanli.getCurrentWorkLandNumber(); |
| | | if (isMeaningfulValue(landNumber)) { |
| | | Dikuai current = Dikuai.getDikuai(landNumber); |
| | | if (current != null) { |
| | | coords = current.getBaseStationCoordinates(); |
| | | |
| | | 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) && baseStation != null) { |
| | | coords = baseStation.getInstallationCoordinates(); |
| | | } |
| | | if (!isMeaningfulValue(coords)) { |
| | | return null; |
| | | if (!isMeaningfulValue(coords)) { |
| | | coords = addzhangaiwu.getActiveSessionBaseStation(); |
| | | } |
| | | if (!isMeaningfulValue(coords)) { |
| | | return null; |
| | | } |
| | | } |
| | | String[] parts = coords.split(","); |
| | | if (parts.length < 4) { |
| | |
| | | clearCircleGuidanceArtifacts(); |
| | | hideFloatingDrawingControls(); |
| | | circleDialogMode = false; |
| | | applyDrawingPauseState(false, false); |
| | | exitHandheldCaptureInlineUi(); |
| | | handheldCaptureActive = false; |
| | | exitDrawingControlMode(); |
| | | if (activeBoundaryMode == BoundaryCaptureMode.MOWER) { |
| | | stopMowerBoundaryCapture(); |
| | | } else if (activeBoundaryMode == BoundaryCaptureMode.HANDHELD && !handheldCaptureActive) { |
| | |
| | | endDrawingCallback = null; |
| | | 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 || !isMeaningfulValue(plannedPath)) { |
| | | 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); |
| | | mapRenderer.setCurrentPlannedPath(plannedPath); |
| | | mapRenderer.clearHandheldBoundaryPreview(); |
| | | mapRenderer.setBoundaryPointSizeScale(1.0d); |
| | | mapRenderer.setBoundaryPointsVisible(isMeaningfulValue(boundary)); |
| | | |
| | | 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(); |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | 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); |