"feat:Enable-hover-highlight-and-refactor"

This commit is contained in:
2025-12-14 17:20:50 +09:00
parent 34b038c4be
commit 764fbbd204
48 changed files with 3980 additions and 2750 deletions

View File

@@ -13,9 +13,9 @@ namespace AGVMapEditor.Forms
/// </summary>
public partial class ImageEditorForm : Form
{
private MapNode _targetNode;
private MapImage _targetNode;
public ImageEditorForm(MapNode imageNode = null)
public ImageEditorForm(MapImage imageNode = null)
{
InitializeComponent();
_targetNode = imageNode;
@@ -25,7 +25,7 @@ namespace AGVMapEditor.Forms
{
LoadImageFromNode(imageNode);
}
this.KeyPreview = true;
this.KeyDown += (s1, e1) => {
if (e1.KeyCode == Keys.Escape) this.Close();
@@ -38,7 +38,7 @@ namespace AGVMapEditor.Forms
imageCanvas.BrushSize = trackBrush.Value;
imageCanvas.BrushModeEnabled = chkBrushMode.Checked;
imageCanvas.BackColor = Color.FromArgb(32,32,32);
// 이벤트 연결
chkBrushMode.CheckedChanged += (s, e) => imageCanvas.BrushModeEnabled = chkBrushMode.Checked;
}
@@ -48,7 +48,7 @@ namespace AGVMapEditor.Forms
imageCanvas.BrushSize = trackBrush.Value;
}
private void LoadImageFromNode(MapNode node)
private void LoadImageFromNode(MapImage node)
{
if (node.LoadedImage != null)
{
@@ -66,7 +66,7 @@ namespace AGVMapEditor.Forms
}
}
}
private void LoadImageFromFile(string filePath)
{
try
@@ -160,7 +160,7 @@ namespace AGVMapEditor.Forms
return;
}
if (_targetNode != null && _targetNode.Type == NodeType.Image)
if (_targetNode != null)
{
// 표시 크기로 리사이즈된 이미지 가져오기
var finalImage = imageCanvas.GetResizedImage();

View File

@@ -7,6 +7,8 @@ using System.Windows.Forms;
using AGVMapEditor.Models;
using AGVNavigationCore.Controls;
using AGVNavigationCore.Models;
using MapImage = AGVNavigationCore.Models.MapImage;
using MapLabel = AGVNavigationCore.Models.MapLabel;
using Newtonsoft.Json;
namespace AGVMapEditor.Forms
@@ -18,11 +20,11 @@ namespace AGVMapEditor.Forms
{
#region Fields
private List<MapNode> _mapNodes;
// private List<MapNode> this._mapCanvas.Nodes;
private UnifiedAGVCanvas _mapCanvas;
// 현재 선택된 노드
private MapNode _selectedNode;
private NodeBase _selectedNode;
// 파일 경로
private string _currentMapFile = string.Empty;
@@ -93,20 +95,18 @@ namespace AGVMapEditor.Forms
#endregion
#region Initialization
private void InitializeData()
{
_mapNodes = new List<MapNode>();
// this._mapCanvas.Nodes = new List<MapNode>();
}
private void InitializeMapCanvas()
{
_mapCanvas = new UnifiedAGVCanvas();
_mapCanvas.Dock = DockStyle.Fill;
_mapCanvas.Nodes = _mapNodes;
// RfidMappings 제거 - MapNode에 통합됨
// 이벤트 연결
_mapCanvas.NodeAdded += OnNodeAdded;
@@ -115,12 +115,14 @@ namespace AGVMapEditor.Forms
_mapCanvas.NodeMoved += OnNodeMoved;
_mapCanvas.NodeDeleted += OnNodeDeleted;
_mapCanvas.ConnectionDeleted += OnConnectionDeleted;
_mapCanvas.ImageNodeDoubleClicked += OnImageNodeDoubleClicked;
_mapCanvas.ImageDoubleClicked += OnImageDoubleClicked;
_mapCanvas.MapChanged += OnMapChanged;
// 스플리터 패널에 맵 캔버스 추가
panel1.Controls.Add(_mapCanvas);
// ...
// 툴바 버튼 이벤트 연결
WireToolbarButtonEvents();
}
@@ -160,7 +162,7 @@ namespace AGVMapEditor.Forms
private void MainForm_Load(object sender, EventArgs e)
{
RefreshNodeList();
// 속성 변경 시 이벤트 연결
_propertyGrid.PropertyValueChanged += PropertyGrid_PropertyValueChanged;
@@ -174,7 +176,7 @@ namespace AGVMapEditor.Forms
}
}
private void OnNodeAdded(object sender, MapNode node)
private void OnNodeAdded(object sender, NodeBase node)
{
_hasChanges = true;
UpdateTitle();
@@ -199,7 +201,7 @@ namespace AGVMapEditor.Forms
// }
//}
private void OnNodesSelected(object sender, List<MapNode> nodes)
private void OnNodesSelected(object sender, List<NodeBase> nodes)
{
// 다중 선택 시 처리
if (nodes == null || nodes.Count == 0)
@@ -227,14 +229,14 @@ namespace AGVMapEditor.Forms
}
}
private void OnNodeMoved(object sender, MapNode node)
private void OnNodeMoved(object sender, NodeBase node)
{
_hasChanges = true;
UpdateTitle();
RefreshNodeList();
}
private void OnNodeDeleted(object sender, MapNode node)
private void OnNodeDeleted(object sender, NodeBase node)
{
_hasChanges = true;
UpdateTitle();
@@ -258,20 +260,17 @@ namespace AGVMapEditor.Forms
UpdateNodeProperties(); // 연결 정보 업데이트
}
private void OnImageNodeDoubleClicked(object sender, MapNode node)
private void OnImageDoubleClicked(object sender, MapImage image)
{
// 이미지 노드 더블클릭 시 이미지 편집창 표시
if (node != null && node.Type == NodeType.Image)
using (var editor = new ImageEditorForm(image))
{
using (var editor = new ImageEditorForm(node))
if (editor.ShowDialog(this) == DialogResult.OK)
{
if (editor.ShowDialog(this) == DialogResult.OK)
{
_hasChanges = true;
UpdateTitle();
_mapCanvas.Invalidate(); // 캔버스 다시 그리기
UpdateNodeProperties(); // 속성 업데이트
}
_hasChanges = true;
UpdateTitle();
_mapCanvas.Invalidate(); // 캔버스 다시 그리기
UpdateNodeProperties(); // 속성 업데이트
}
}
}
@@ -401,12 +400,11 @@ namespace AGVMapEditor.Forms
private void AddNewNode()
{
var nodeId = GenerateNodeId();
var nodeName = $"노드{_mapNodes.Count + 1}";
var position = new Point(100 + _mapNodes.Count * 50, 100 + _mapNodes.Count * 50);
var position = new Point(100 + this._mapCanvas.Nodes.Count * 50, 100 + this._mapCanvas.Nodes.Count * 50);
var node = new MapNode(nodeId, nodeName, position, NodeType.Normal);
var node = new MapNode(nodeId, position, StationType.Normal);
_mapNodes.Add(node);
this._mapCanvas.Nodes.Add(node);
_hasChanges = true;
RefreshNodeList();
@@ -422,13 +420,14 @@ namespace AGVMapEditor.Forms
return;
}
var result = MessageBox.Show($"노드 '{_selectedNode.Name}'를 삭제하시겠습니까?\n연결된 RFID 매핑도 함께 삭제됩니다.",
var rfidDisplay = (_selectedNode as MapNode)?.RfidId ?? "";
var result = MessageBox.Show($"노드 {rfidDisplay}[{_selectedNode.Id}] 를 삭제하시겠습니까?\n연결된 RFID 매핑도 함께 삭제됩니다.",
"삭제 확인", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
// 노드 제거
_mapNodes.Remove(_selectedNode);
_mapCanvas.RemoveItem(_selectedNode);
_selectedNode = null;
_hasChanges = true;
@@ -441,15 +440,15 @@ namespace AGVMapEditor.Forms
private void AddConnectionToSelectedNode()
{
if (_selectedNode == null)
if (!(_selectedNode is MapNode selectedMapNode))
{
MessageBox.Show("연결을 추가할 노드를 선택하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
MessageBox.Show("연결을 추가할 노드(MapNode)를 선택하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
// 다른 노드들 중에서 선택
var availableNodes = _mapNodes.Where(n => n.NodeId != _selectedNode.NodeId &&
!_selectedNode.ConnectedNodes.Contains(n.NodeId)).ToList();
var availableNodes = this._mapCanvas.Nodes.Where(n => n.Id != selectedMapNode.Id &&
!selectedMapNode.ConnectedNodes.Contains(n.Id)).ToList();
if (availableNodes.Count == 0)
{
@@ -458,13 +457,13 @@ namespace AGVMapEditor.Forms
}
// 간단한 선택 다이얼로그 (실제로는 별도 폼을 만들어야 함)
var nodeNames = availableNodes.Select(n => $"{n.NodeId}: {n.Name}").ToArray();
var nodeNames = availableNodes.Select(n => $"{n.Id}: {n.RfidId}").ToArray();
var input = Microsoft.VisualBasic.Interaction.InputBox("연결할 노드를 선택하세요:", "노드 연결", nodeNames[0]);
var targetNode = availableNodes.FirstOrDefault(n => input.StartsWith(n.NodeId));
var targetNode = availableNodes.FirstOrDefault(n => input.StartsWith(n.Id));
if (targetNode != null)
{
_selectedNode.AddConnection(targetNode.NodeId);
selectedMapNode.AddConnection(targetNode.Id);
_hasChanges = true;
RefreshMapCanvas();
UpdateNodeProperties();
@@ -474,25 +473,25 @@ namespace AGVMapEditor.Forms
private void RemoveConnectionFromSelectedNode()
{
if (_selectedNode == null || _selectedNode.ConnectedNodes.Count == 0)
if (!(_selectedNode is MapNode selectedMapNode) || selectedMapNode.ConnectedNodes.Count == 0)
{
MessageBox.Show("연결을 제거할 노드를 선택하거나 연결된 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
// 연결된 노드들 중에서 선택
var connectedNodeNames = _selectedNode.ConnectedNodes.Select(connectedNodeId =>
var connectedNodeNames = selectedMapNode.ConnectedNodes.Select(connectedNodeId =>
{
var node = _mapNodes.FirstOrDefault(n => n.NodeId == connectedNodeId);
return node != null ? $"{node.NodeId}: {node.Name}" : connectedNodeId;
var node = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectedNodeId);
return node != null ? $"{node.Id}: {node.RfidId}" : connectedNodeId;
}).ToArray();
var input = Microsoft.VisualBasic.Interaction.InputBox("제거할 연결을 선택하세요:", "연결 제거", connectedNodeNames[0]);
var targetNodeId = input.Split(':')[0];
if (_selectedNode.ConnectedNodes.Contains(targetNodeId))
if (selectedMapNode.ConnectedNodes.Contains(targetNodeId))
{
_selectedNode.RemoveConnection(targetNodeId);
selectedMapNode.RemoveConnection(targetNodeId);
_hasChanges = true;
RefreshMapCanvas();
UpdateNodeProperties();
@@ -509,7 +508,7 @@ namespace AGVMapEditor.Forms
{
nodeId = $"N{counter:D3}";
counter++;
} while (_mapNodes.Any(n => n.NodeId == nodeId));
} while (this._mapCanvas.Nodes.Any(n => n.Id == nodeId));
return nodeId;
}
@@ -520,7 +519,7 @@ namespace AGVMapEditor.Forms
private void NewMap()
{
_mapNodes.Clear();
this._mapCanvas.Nodes.Clear();
_selectedNode = null;
_currentMapFile = string.Empty;
_hasChanges = false;
@@ -533,7 +532,7 @@ namespace AGVMapEditor.Forms
{
if (CheckSaveChanges())
{
_mapNodes.Clear();
this._mapCanvas.Nodes.Clear();
_selectedNode = null;
_currentMapFile = string.Empty;
_hasChanges = false;
@@ -547,7 +546,7 @@ namespace AGVMapEditor.Forms
{
var openFileDialog = new OpenFileDialog
{
Filter = "AGV Map Files (*.agvmap)|*.agvmap|All Files (*.*)|*.*",
Filter = "AGV Map Files (*.agvmap;*.json)|*.agvmap;*.json|All Files (*.*)|*.*",
DefaultExt = "agvmap",
};
@@ -624,10 +623,14 @@ namespace AGVMapEditor.Forms
if (result.Success)
{
_mapNodes = result.Nodes;
this._mapCanvas.Nodes = result.Nodes;
// 맵 캔버스에 데이터 설정
_mapCanvas.Nodes = _mapNodes;
_mapCanvas.Nodes = this._mapCanvas.Nodes;
_mapCanvas.Labels = result.Labels; // 추가
_mapCanvas.Images = result.Images; // 추가
_mapCanvas.Marks = result.Marks;
_mapCanvas.Magnets = result.Magnets;
// RfidMappings 제거됨 - MapNode에 통합
// 🔥 맵 설정 적용 (배경색, 그리드 표시)
@@ -693,7 +696,7 @@ namespace AGVMapEditor.Forms
ShowGrid = _mapCanvas.ShowGrid
};
if (MapLoader.SaveMapToFile(filePath, _mapNodes, settings))
if (MapLoader.SaveMapToFile(filePath, this._mapCanvas.Nodes, _mapCanvas.Labels, _mapCanvas.Images, _mapCanvas.Marks, _mapCanvas.Magnets, settings))
{
// 현재 파일 경로 업데이트
_currentMapFile = filePath;
@@ -718,7 +721,7 @@ namespace AGVMapEditor.Forms
private void UpdateRfidMappings()
{
// RFID 자동 할당 제거 - 사용자가 직접 입력한 값 유지
// MapLoader.AssignAutoRfidIds(_mapNodes);
// MapLoader.AssignAutoRfidIds(this._mapCanvas.Nodes);
}
private bool CheckSaveChanges()
@@ -780,14 +783,14 @@ namespace AGVMapEditor.Forms
private void RefreshNodeList()
{
listBoxNodes.DataSource = null;
listBoxNodes.DataSource = _mapNodes;
listBoxNodes.DataSource = this._mapCanvas.Nodes;
listBoxNodes.DisplayMember = "DisplayText";
listBoxNodes.ValueMember = "NodeId";
listBoxNodes.ValueMember = "Id";
// 노드 목록 클릭 이벤트 연결
listBoxNodes.SelectedIndexChanged -= ListBoxNodes_SelectedIndexChanged;
listBoxNodes.SelectedIndexChanged += ListBoxNodes_SelectedIndexChanged;
// 노드 타입별 색상 적용
listBoxNodes.DrawMode = DrawMode.OwnerDrawFixed;
listBoxNodes.DrawItem -= ListBoxNodes_DrawItem;
@@ -812,14 +815,14 @@ namespace AGVMapEditor.Forms
{
e.DrawBackground();
if (e.Index >= 0 && e.Index < _mapNodes.Count)
if (e.Index >= 0 && e.Index < this._mapCanvas.Items.Count)
{
var node = _mapNodes[e.Index];
var node = this._mapCanvas.Items[e.Index];
// 노드 타입에 따른 색상 설정
Color foreColor = Color.Black;
Color backColor = e.BackColor;
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
{
backColor = SystemColors.Highlight;
@@ -827,22 +830,30 @@ namespace AGVMapEditor.Forms
}
else
{
backColor = Color.White;
switch (node.Type)
{
case NodeType.Normal:
foreColor = Color.Black;
backColor = Color.White;
var item = node as MapNode;
if (item.StationType == StationType.Normal)
foreColor = Color.DimGray;
else if (item.StationType == StationType.Charger)
foreColor = Color.Red;
else
foreColor = Color.DarkGreen;
break;
case NodeType.Loader:
case NodeType.UnLoader:
case NodeType.Clearner:
case NodeType.Buffer:
foreColor = Color.DarkGreen;
backColor = Color.LightGreen;
case NodeType.Label:
case NodeType.Mark:
case NodeType.Image:
foreColor = Color.DarkBlue;
break;
case NodeType.Charging:
case NodeType.Magnet:
foreColor = Color.DarkMagenta;
break;
default:
foreColor = Color.DarkRed;
backColor = Color.LightPink;
break;
}
}
@@ -854,17 +865,21 @@ namespace AGVMapEditor.Forms
}
// 텍스트 그리기 (노드ID - 노드명 - RFID 순서)
var displayText = node.NodeId;
if (!string.IsNullOrEmpty(node.Name))
string displayText;
if (node.Type == NodeType.Normal)
{
displayText += $" - {node.Name}";
var item = node as MapNode;
if (item.HasRfid())
displayText = $"[{node.Id}-{item.RfidId}] {item.StationType}";
else
displayText = $"[{node.Id}] {item.StationType}";
}
if (!string.IsNullOrEmpty(node.RfidId))
else if(node.Type == NodeType.Label)
{
displayText += $" - [{node.RfidId}]";
var item = node as MapLabel;
displayText = $"{item.Type.ToString().ToUpper()} - {item.Text}";
}
else displayText = $"{node.Type.ToString().ToUpper()}";
using (var brush = new SolidBrush(foreColor))
{
@@ -881,31 +896,31 @@ namespace AGVMapEditor.Forms
var processedPairs = new HashSet<string>();
// 모든 노드의 연결 정보를 수집 (중복 방지)
foreach (var fromNode in _mapNodes)
foreach (var fromNode in this._mapCanvas.Nodes)
{
foreach (var toNodeId in fromNode.ConnectedNodes)
{
var toNode = _mapNodes.FirstOrDefault(n => n.NodeId == toNodeId);
var toNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == toNodeId);
if (toNode != null)
{
// 중복 체크 (단일 연결만 표시)
string pairKey1 = $"{fromNode.NodeId}-{toNode.NodeId}";
string pairKey2 = $"{toNode.NodeId}-{fromNode.NodeId}";
string pairKey1 = $"{fromNode.Id}-{toNode.Id}";
string pairKey2 = $"{toNode.Id}-{fromNode.Id}";
if (!processedPairs.Contains(pairKey1) && !processedPairs.Contains(pairKey2))
{
// 사전 순으로 정렬하여 일관성 있게 표시
var (firstNode, secondNode) = string.Compare(fromNode.NodeId, toNode.NodeId) < 0
var (firstNode, secondNode) = string.Compare(fromNode.Id, toNode.Id) < 0
? (fromNode, toNode)
: (toNode, fromNode);
connections.Add(new NodeConnectionInfo
{
FromNodeId = firstNode.NodeId,
FromNodeName = firstNode.Name,
FromNodeId = firstNode.Id,
FromNodeName = "",
FromRfidId = firstNode.RfidId,
ToNodeId = secondNode.NodeId,
ToNodeName = secondNode.Name,
ToNodeId = secondNode.Id,
ToNodeName = "",
ToRfidId = secondNode.RfidId,
ConnectionType = "양방향" // 모든 연결이 양방향
});
@@ -940,7 +955,7 @@ namespace AGVMapEditor.Forms
_mapCanvas?.HighlightConnection(connectionInfo.FromNodeId, connectionInfo.ToNodeId);
// 연결된 노드들을 맵에서 하이라이트 표시 (선택적)
var fromNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectionInfo.FromNodeId);
var fromNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectionInfo.FromNodeId);
if (fromNode != null)
{
_selectedNode = fromNode;
@@ -1002,7 +1017,7 @@ namespace AGVMapEditor.Forms
private void UpdateImageEditButton()
{
// ToolStripButton으로 변경됨
btnEditImage.Enabled = (_selectedNode != null && _selectedNode.Type == NodeType.Image);
btnEditImage.Enabled = (_mapCanvas.SelectedImage != null);
}
/// <summary>
@@ -1019,9 +1034,10 @@ namespace AGVMapEditor.Forms
/// </summary>
private void BtnToolbarEditImage_Click(object sender, EventArgs e)
{
if (_selectedNode != null && _selectedNode.Type == NodeType.Image)
var selectedImage = _mapCanvas.SelectedImage;
if (selectedImage != null)
{
using (var editor = new ImageEditorForm(_selectedNode))
using (var editor = new ImageEditorForm(selectedImage))
{
if (editor.ShowDialog(this) == DialogResult.OK)
{
@@ -1059,7 +1075,7 @@ namespace AGVMapEditor.Forms
if (listBoxNodes != null)
{
listBoxNodes.DataSource = null;
listBoxNodes.DataSource = _mapNodes;
listBoxNodes.DataSource = this._mapCanvas.Items;
listBoxNodes.DisplayMember = "DisplayText";
}
}
@@ -1121,13 +1137,13 @@ namespace AGVMapEditor.Forms
// 🔥 다중 선택 여부 확인 및 선택된 노드들 저장
bool isMultiSelect = _propertyGrid.SelectedObjects is MapNode[]; // _propertyGrid.SelectedObject is MapNode[];
//var a = _propertyGrid.SelectedObject;
List<MapNode> selectedNodes = null;
List<NodeBase> selectedNodes = null;
if (isMultiSelect)
{
// 캔버스에서 현재 선택된 노드들 가져오기
selectedNodes = new List<MapNode>(_mapCanvas.SelectedNodes);
selectedNodes = new List<NodeBase>(_mapCanvas.SelectedNodes);
System.Diagnostics.Debug.WriteLine($"[PropertyGrid] 다중 선택 노드 수: {selectedNodes.Count}");
}
@@ -1144,7 +1160,7 @@ namespace AGVMapEditor.Forms
// 🔥 다중 선택인 경우 MultiNodePropertyWrapper를 다시 생성하여 바인딩
if (isMultiSelect && selectedNodes != null && selectedNodes.Count > 0)
{
// System.Diagnostics.Debug.WriteLine($"[PropertyGrid] MultiNodePropertyWrapper 재생성: {selectedNodes.Count}개");
// System.Diagnostics.Debug.WriteLine($"[PropertyGrid] MultiNodePropertyWrapper 재생성: {selectedNodes.Count}개");
//var multiWrapper = new MultiNodePropertyWrapper(selectedNodes);
_propertyGrid.SelectedObjects = selectedNodes.ToArray();// multiWrapper;
}
@@ -1153,9 +1169,9 @@ namespace AGVMapEditor.Forms
_propertyGrid.Refresh();
// 🔥 단일 선택인 경우에만 노드를 다시 선택 (다중 선택은 캔버스에서 유지)
if (!isMultiSelect && currentSelectedNode != null)
if (!isMultiSelect && currentSelectedNode is MapNode mapNode)
{
var nodeIndex = _mapNodes.IndexOf(currentSelectedNode);
var nodeIndex = this._mapCanvas.Nodes.IndexOf(mapNode);
if (nodeIndex >= 0)
{
listBoxNodes.SelectedIndex = nodeIndex;
@@ -1170,7 +1186,7 @@ namespace AGVMapEditor.Forms
/// <returns>중복되면 true, 아니면 false</returns>
private bool CheckRfidDuplicate(string rfidValue)
{
if (string.IsNullOrEmpty(rfidValue) || _mapNodes == null)
if (string.IsNullOrEmpty(rfidValue) || this._mapCanvas.Nodes == null)
return false;
// 현재 편집 중인 노드 제외하고 중복 검사
@@ -1192,10 +1208,10 @@ namespace AGVMapEditor.Forms
//}
int duplicateCount = 0;
foreach (var node in _mapNodes)
foreach (var node in this._mapCanvas.Nodes)
{
// 현재 편집 중인 노드는 제외
if (node.NodeId == currentNodeId)
if (node.Id == currentNodeId)
continue;
// 같은 RFID 값을 가진 노드가 있는지 확인
@@ -1234,23 +1250,23 @@ namespace AGVMapEditor.Forms
if (result == DialogResult.Yes)
{
// 단일 연결 삭제
var fromNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectionInfo.FromNodeId);
var toNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectionInfo.ToNodeId);
var fromNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectionInfo.FromNodeId);
var toNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectionInfo.ToNodeId);
if (fromNode != null && toNode != null)
{
// 양방향 연결 삭제 (양쪽 방향 모두 제거)
bool removed = false;
if (fromNode.ConnectedNodes.Contains(toNode.NodeId))
if (fromNode.ConnectedNodes.Contains(toNode.Id))
{
fromNode.RemoveConnection(toNode.NodeId);
fromNode.RemoveConnection(toNode.Id);
removed = true;
}
if (toNode.ConnectedNodes.Contains(fromNode.NodeId))
if (toNode.ConnectedNodes.Contains(fromNode.Id))
{
toNode.RemoveConnection(fromNode.NodeId);
toNode.RemoveConnection(fromNode.Id);
removed = true;
}
@@ -1277,108 +1293,18 @@ namespace AGVMapEditor.Forms
}
}
#region Multi-Node Selection
private void ShowMultiNodeContextMenu(List<MapNode> nodes)
{
// 다중 선택 시 간단한 다이얼로그 표시
var result = MessageBox.Show(
$"{nodes.Count}개의 노드가 선택되었습니다.\n\n" +
"일괄 속성 변경을 하시겠습니까?\n\n" +
"예: 글자색 변경\n" +
"아니오: 배경색 변경\n" +
"취소: 닫기",
"다중 노드 속성 변경",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
BatchChangeForeColor();
}
else if (result == DialogResult.No)
{
BatchChangeBackColor();
}
}
/// <summary>
/// 선택된 노드들의 글자색 일괄 변경
/// </summary>
public void BatchChangeForeColor()
{
var selectedNodes = _mapCanvas.SelectedNodes;
if (selectedNodes == null || selectedNodes.Count == 0)
{
MessageBox.Show("선택된 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
using (var colorDialog = new ColorDialog())
{
colorDialog.Color = selectedNodes[0].ForeColor;
if (colorDialog.ShowDialog() == DialogResult.OK)
{
foreach (var node in selectedNodes)
{
node.ForeColor = colorDialog.Color;
node.ModifiedDate = DateTime.Now;
}
_hasChanges = true;
UpdateTitle();
RefreshMapCanvas();
MessageBox.Show($"{selectedNodes.Count}개 노드의 글자색이 변경되었습니다.", "완료", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
/// <summary>
/// 선택된 노드들의 배경색 일괄 변경
/// </summary>
public void BatchChangeBackColor()
{
var selectedNodes = _mapCanvas.SelectedNodes;
if (selectedNodes == null || selectedNodes.Count == 0)
{
MessageBox.Show("선택된 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
using (var colorDialog = new ColorDialog())
{
colorDialog.Color = selectedNodes[0].DisplayColor;
if (colorDialog.ShowDialog() == DialogResult.OK)
{
foreach (var node in selectedNodes)
{
node.DisplayColor = colorDialog.Color;
node.ModifiedDate = DateTime.Now;
}
_hasChanges = true;
UpdateTitle();
RefreshMapCanvas();
MessageBox.Show($"{selectedNodes.Count}개 노드의 배경색이 변경되었습니다.", "완료", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
#endregion
private void allTurnLeftRightCrossOnToolStripMenuItem_Click(object sender, EventArgs e)
{
//모든노드의 trun left/right/ cross 속성을 true로 변경합니다
if (_mapNodes == null || _mapNodes.Count == 0)
if (this._mapCanvas.Nodes == null || this._mapCanvas.Nodes.Count == 0)
{
MessageBox.Show("맵에 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
var result = MessageBox.Show(
$"모든 노드({_mapNodes.Count}개)의 회전/교차 속성을 활성화하시겠습니까?\n\n" +
$"모든 노드({this._mapCanvas.Nodes.Count}개)의 회전/교차 속성을 활성화하시겠습니까?\n\n" +
"• CanTurnLeft = true\n" +
"• CanTurnRight = true\n" +
"• DisableCross = false",
@@ -1388,20 +1314,20 @@ namespace AGVMapEditor.Forms
if (result == DialogResult.Yes)
{
foreach (var node in _mapNodes)
foreach (var node in this._mapCanvas.Nodes)
{
node.CanTurnLeft = true;
node.CanTurnRight = true;
node.DisableCross =false;
node.DisableCross = false;
node.ModifiedDate = DateTime.Now;
}
_hasChanges = true;
UpdateTitle();
RefreshMapCanvas();
MessageBox.Show(
$"{_mapNodes.Count}개 노드의 회전/교차 속성이 모두 활성화되었습니다.",
$"{this._mapCanvas.Nodes.Count}개 노드의 회전/교차 속성이 모두 활성화되었습니다.",
"완료",
MessageBoxButtons.OK,
MessageBoxIcon.Information);