using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Windows.Forms; using AGVMapEditor.Models; using AGVMapEditor.Controls; using Newtonsoft.Json; namespace AGVMapEditor.Forms { /// /// AGV 맵 에디터 메인 폼 /// public partial class MainForm : Form { #region Fields private NodeResolver _nodeResolver; private List _mapNodes; private List _rfidMappings; private MapCanvas _mapCanvas; // 현재 선택된 노드 private MapNode _selectedNode; // 파일 경로 private string _currentMapFile = string.Empty; private bool _hasChanges = false; #endregion #region Constructor public MainForm() { InitializeComponent(); InitializeData(); InitializeMapCanvas(); UpdateTitle(); } #endregion #region Initialization private void InitializeData() { _mapNodes = new List(); _rfidMappings = new List(); _nodeResolver = new NodeResolver(_rfidMappings, _mapNodes); } private void InitializeMapCanvas() { _mapCanvas = new MapCanvas(_mapNodes); _mapCanvas.Dock = DockStyle.Fill; _mapCanvas.NodeSelected += OnNodeSelected; _mapCanvas.NodeMoved += OnNodeMoved; _mapCanvas.BackgroundClicked += OnBackgroundClicked; // 스플리터 패널에 맵 캔버스 추가 splitContainer1.Panel2.Controls.Add(_mapCanvas); } #endregion #region Event Handlers private void MainForm_Load(object sender, EventArgs e) { RefreshNodeList(); RefreshRfidMappingList(); } private void OnNodeSelected(object sender, MapNode node) { _selectedNode = node; UpdateNodeProperties(); } private void OnNodeMoved(object sender, MapNode node) { _hasChanges = true; UpdateTitle(); RefreshNodeList(); } private void OnBackgroundClicked(object sender, Point location) { _selectedNode = null; ClearNodeProperties(); } #endregion #region Menu Event Handlers private void newToolStripMenuItem_Click(object sender, EventArgs e) { if (CheckSaveChanges()) { NewMap(); } } private void openToolStripMenuItem_Click(object sender, EventArgs e) { if (CheckSaveChanges()) { OpenMap(); } } private void saveToolStripMenuItem_Click(object sender, EventArgs e) { SaveMap(); } private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) { SaveAsMap(); } private void exitToolStripMenuItem_Click(object sender, EventArgs e) { this.Close(); } #endregion #region Button Event Handlers private void btnAddNode_Click(object sender, EventArgs e) { AddNewNode(); } private void btnDeleteNode_Click(object sender, EventArgs e) { DeleteSelectedNode(); } private void btnAddConnection_Click(object sender, EventArgs e) { AddConnectionToSelectedNode(); } private void btnRemoveConnection_Click(object sender, EventArgs e) { RemoveConnectionFromSelectedNode(); } private void btnAddRfidMapping_Click(object sender, EventArgs e) { AddNewRfidMapping(); } private void btnDeleteRfidMapping_Click(object sender, EventArgs e) { DeleteSelectedRfidMapping(); } #endregion #region Node Management private void AddNewNode() { var nodeId = GenerateNodeId(); var nodeName = $"노드{_mapNodes.Count + 1}"; var position = new Point(100 + _mapNodes.Count * 50, 100 + _mapNodes.Count * 50); var node = new MapNode(nodeId, nodeName, position, NodeType.Normal); _mapNodes.Add(node); _hasChanges = true; RefreshNodeList(); RefreshMapCanvas(); UpdateTitle(); } private void DeleteSelectedNode() { if (_selectedNode == null) { MessageBox.Show("삭제할 노드를 선택하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } var result = MessageBox.Show($"노드 '{_selectedNode.Name}'를 삭제하시겠습니까?\n연결된 RFID 매핑도 함께 삭제됩니다.", "삭제 확인", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (result == DialogResult.Yes) { _nodeResolver.RemoveMapNode(_selectedNode.NodeId); _selectedNode = null; _hasChanges = true; RefreshNodeList(); RefreshRfidMappingList(); RefreshMapCanvas(); ClearNodeProperties(); UpdateTitle(); } } private void AddConnectionToSelectedNode() { if (_selectedNode == null) { MessageBox.Show("연결을 추가할 노드를 선택하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } // 다른 노드들 중에서 선택 var availableNodes = _mapNodes.Where(n => n.NodeId != _selectedNode.NodeId && !_selectedNode.ConnectedNodes.Contains(n.NodeId)).ToList(); if (availableNodes.Count == 0) { MessageBox.Show("연결 가능한 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } // 간단한 선택 다이얼로그 (실제로는 별도 폼을 만들어야 함) var nodeNames = availableNodes.Select(n => $"{n.NodeId}: {n.Name}").ToArray(); var input = Microsoft.VisualBasic.Interaction.InputBox("연결할 노드를 선택하세요:", "노드 연결", nodeNames[0]); var targetNode = availableNodes.FirstOrDefault(n => input.StartsWith(n.NodeId)); if (targetNode != null) { _selectedNode.AddConnection(targetNode.NodeId); _hasChanges = true; RefreshMapCanvas(); UpdateNodeProperties(); UpdateTitle(); } } private void RemoveConnectionFromSelectedNode() { if (_selectedNode == null || _selectedNode.ConnectedNodes.Count == 0) { MessageBox.Show("연결을 제거할 노드를 선택하거나 연결된 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } // 연결된 노드들 중에서 선택 var connectedNodeNames = _selectedNode.ConnectedNodes.Select(connectedNodeId => { var node = _mapNodes.FirstOrDefault(n => n.NodeId == connectedNodeId); return node != null ? $"{node.NodeId}: {node.Name}" : connectedNodeId; }).ToArray(); var input = Microsoft.VisualBasic.Interaction.InputBox("제거할 연결을 선택하세요:", "연결 제거", connectedNodeNames[0]); var targetNodeId = input.Split(':')[0]; if (_selectedNode.ConnectedNodes.Contains(targetNodeId)) { _selectedNode.RemoveConnection(targetNodeId); _hasChanges = true; RefreshMapCanvas(); UpdateNodeProperties(); UpdateTitle(); } } private string GenerateNodeId() { int counter = 1; string nodeId; do { nodeId = $"N{counter:D3}"; counter++; } while (_mapNodes.Any(n => n.NodeId == nodeId)); return nodeId; } #endregion #region RFID Mapping Management private void AddNewRfidMapping() { if (_mapNodes.Count == 0) { MessageBox.Show("매핑할 노드가 없습니다. 먼저 노드를 추가하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } var unmappedNodes = _nodeResolver.GetUnmappedNodes(); if (unmappedNodes.Count == 0) { MessageBox.Show("모든 노드가 이미 RFID에 매핑되어 있습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } // RFID 값 입력 var rfidValue = Microsoft.VisualBasic.Interaction.InputBox("RFID 값을 입력하세요:", "RFID 매핑 추가"); if (string.IsNullOrEmpty(rfidValue)) return; // 노드 선택 var nodeNames = unmappedNodes.Select(n => $"{n.NodeId}: {n.Name}").ToArray(); var selectedNode = Microsoft.VisualBasic.Interaction.InputBox("매핑할 노드를 선택하세요:", "노드 선택", nodeNames[0]); var nodeId = selectedNode.Split(':')[0]; var description = Microsoft.VisualBasic.Interaction.InputBox("설명을 입력하세요 (선택사항):", "설명"); if (_nodeResolver.AddRfidMapping(rfidValue, nodeId, description)) { _hasChanges = true; RefreshRfidMappingList(); UpdateTitle(); MessageBox.Show("RFID 매핑이 추가되었습니다.", "성공", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { MessageBox.Show("RFID 매핑 추가에 실패했습니다. 중복된 RFID이거나 노드가 존재하지 않습니다.", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void DeleteSelectedRfidMapping() { if (listBoxRfidMappings.SelectedItem == null) { MessageBox.Show("삭제할 RFID 매핑을 선택하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } var mapping = listBoxRfidMappings.SelectedItem as RfidMapping; var result = MessageBox.Show($"RFID 매핑 '{mapping.RfidId} → {mapping.LogicalNodeId}'를 삭제하시겠습니까?", "삭제 확인", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (result == DialogResult.Yes) { if (_nodeResolver.RemoveRfidMapping(mapping.RfidId)) { _hasChanges = true; RefreshRfidMappingList(); UpdateTitle(); MessageBox.Show("RFID 매핑이 삭제되었습니다.", "성공", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { MessageBox.Show("RFID 매핑 삭제에 실패했습니다.", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } #endregion #region File Operations private void NewMap() { _mapNodes.Clear(); _rfidMappings.Clear(); _nodeResolver = new NodeResolver(_rfidMappings, _mapNodes); _selectedNode = null; _currentMapFile = string.Empty; _hasChanges = false; RefreshAll(); UpdateTitle(); } private void OpenMap() { var openFileDialog = new OpenFileDialog { Filter = "AGV Map Files (*.agvmap)|*.agvmap|JSON Files (*.json)|*.json|All Files (*.*)|*.*", DefaultExt = "agvmap" }; if (openFileDialog.ShowDialog() == DialogResult.OK) { try { LoadMapFromFile(openFileDialog.FileName); _currentMapFile = openFileDialog.FileName; _hasChanges = false; RefreshAll(); UpdateTitle(); MessageBox.Show("맵이 성공적으로 로드되었습니다.", "성공", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show($"맵 로드 중 오류가 발생했습니다: {ex.Message}", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } private void SaveMap() { if (string.IsNullOrEmpty(_currentMapFile)) { SaveAsMap(); } else { try { SaveMapToFile(_currentMapFile); _hasChanges = false; UpdateTitle(); MessageBox.Show("맵이 성공적으로 저장되었습니다.", "성공", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show($"맵 저장 중 오류가 발생했습니다: {ex.Message}", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } private void SaveAsMap() { var saveFileDialog = new SaveFileDialog { Filter = "AGV Map Files (*.agvmap)|*.agvmap|JSON Files (*.json)|*.json", DefaultExt = "agvmap", FileName = "NewMap.agvmap" }; if (saveFileDialog.ShowDialog() == DialogResult.OK) { try { SaveMapToFile(saveFileDialog.FileName); _currentMapFile = saveFileDialog.FileName; _hasChanges = false; UpdateTitle(); MessageBox.Show("맵이 성공적으로 저장되었습니다.", "성공", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show($"맵 저장 중 오류가 발생했습니다: {ex.Message}", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } private void LoadMapFromFile(string filePath) { var json = File.ReadAllText(filePath); var mapData = JsonConvert.DeserializeObject(json); _mapNodes = mapData.Nodes ?? new List(); _rfidMappings = mapData.RfidMappings ?? new List(); _nodeResolver = new NodeResolver(_rfidMappings, _mapNodes); } private void SaveMapToFile(string filePath) { var mapData = new MapData { Nodes = _mapNodes, RfidMappings = _rfidMappings, CreatedDate = DateTime.Now, Version = "1.0" }; var json = JsonConvert.SerializeObject(mapData, Formatting.Indented); File.WriteAllText(filePath, json); } private bool CheckSaveChanges() { if (_hasChanges) { var result = MessageBox.Show("변경사항이 있습니다. 저장하시겠습니까?", "변경사항 저장", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question); if (result == DialogResult.Yes) { SaveMap(); return !_hasChanges; // 저장이 성공했으면 true } else if (result == DialogResult.Cancel) { return false; } } return true; } #endregion #region UI Updates private void RefreshAll() { RefreshNodeList(); RefreshRfidMappingList(); RefreshMapCanvas(); ClearNodeProperties(); } private void RefreshNodeList() { listBoxNodes.DataSource = null; listBoxNodes.DataSource = _mapNodes; listBoxNodes.DisplayMember = "Name"; listBoxNodes.ValueMember = "NodeId"; } private void RefreshRfidMappingList() { listBoxRfidMappings.DataSource = null; listBoxRfidMappings.DataSource = _rfidMappings; listBoxRfidMappings.DisplayMember = "ToString"; } private void RefreshMapCanvas() { _mapCanvas?.Invalidate(); } private void UpdateNodeProperties() { if (_selectedNode == null) { ClearNodeProperties(); return; } // 선택된 노드의 속성을 프로퍼티 패널에 표시 // (실제로는 PropertyGrid나 별도 컨트롤 사용) labelSelectedNode.Text = $"선택된 노드: {_selectedNode.Name} ({_selectedNode.NodeId})"; } private void ClearNodeProperties() { labelSelectedNode.Text = "선택된 노드: 없음"; } private void UpdateTitle() { var title = "AGV Map Editor"; if (!string.IsNullOrEmpty(_currentMapFile)) { title += $" - {Path.GetFileName(_currentMapFile)}"; } if (_hasChanges) { title += " *"; } this.Text = title; } #endregion #region Form Events private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { if (!CheckSaveChanges()) { e.Cancel = true; } } #endregion #region Data Model for Serialization private class MapData { public List Nodes { get; set; } = new List(); public List RfidMappings { get; set; } = new List(); public DateTime CreatedDate { get; set; } public string Version { get; set; } = "1.0"; } #endregion } }