1912 lines
68 KiB
C#
1912 lines
68 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Linq;
|
|
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;
|
|
using System.ComponentModel;
|
|
|
|
namespace AGVMapEditor.Forms
|
|
{
|
|
/// <summary>
|
|
/// AGV 맵 에디터 메인 폼
|
|
/// </summary>
|
|
public partial class MainForm : Form
|
|
{
|
|
#region Fields
|
|
|
|
// private List<MapNode> this._mapCanvas.Nodes;
|
|
private UnifiedAGVCanvas _mapCanvas;
|
|
|
|
// 현재 선택된 노드
|
|
private NodeBase _selectedNode;
|
|
|
|
// 파일 경로
|
|
private string _currentMapFile = string.Empty;
|
|
private bool _hasChanges = false;
|
|
private bool _hasCommandLineArgs = false;
|
|
|
|
// 노드 연결 정보를 표현하는 클래스
|
|
public class NodeConnectionInfo
|
|
{
|
|
public string FromNodeId { get; set; }
|
|
public ushort FromRfidId { get; set; }
|
|
public string ToNodeId { get; set; }
|
|
public ushort ToRfidId { get; set; }
|
|
public string ConnectionType { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
// RFID가 있으면 RFID(노드이름), 없으면 NodeID(노드이름) 형태로 표시
|
|
string fromDisplay = FromRfidId > 0
|
|
? $"{FromRfidId:0000}(*{FromNodeId.PadLeft(4, '0')})"
|
|
: $"(*{FromNodeId})";
|
|
|
|
string toDisplay = ToRfidId > 0
|
|
? $"{ToRfidId:0000}(*{ToNodeId.PadLeft(4, '0')})"
|
|
: $"(*{ToNodeId})";
|
|
|
|
// 양방향 연결은 ↔ 기호 사용
|
|
string arrow = ConnectionType == "양방향" ? "↔" : "→";
|
|
return $"{fromDisplay} {arrow} {toDisplay}";
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
public MainForm() : this(null)
|
|
{
|
|
}
|
|
|
|
public MainForm(string[] args)
|
|
{
|
|
InitializeComponent();
|
|
InitializeData();
|
|
InitializeMapCanvas();
|
|
UpdateTitle();
|
|
|
|
// 명령줄 인수로 파일이 전달되었으면 자동으로 열기
|
|
if (args != null && args.Length > 0)
|
|
{
|
|
_hasCommandLineArgs = true;
|
|
string filePath = args[0];
|
|
if (System.IO.File.Exists(filePath))
|
|
{
|
|
LoadMapFromFile(filePath);
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show($"지정된 파일을 찾을 수 없습니다: {filePath}", "파일 오류",
|
|
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
}
|
|
}
|
|
// 명령줄 인수가 없는 경우는 Form_Load에서 마지막 맵 파일 자동 로드 확인
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region Initialization
|
|
|
|
private void InitializeData()
|
|
{
|
|
// this._mapCanvas.Nodes = new List<MapNode>();
|
|
}
|
|
|
|
private void InitializeMapCanvas()
|
|
{
|
|
_mapCanvas = new UnifiedAGVCanvas();
|
|
_mapCanvas.Dock = DockStyle.Fill;
|
|
_mapCanvas.Mode = UnifiedAGVCanvas.CanvasMode.Edit;
|
|
|
|
// 이벤트 연결
|
|
_mapCanvas.NodeAdded += OnNodeAdded;
|
|
//_mapCanvas.NodeSelected += OnNodeSelected;
|
|
_mapCanvas.NodesSelected += OnNodesSelected; // 다중 선택 이벤트
|
|
_mapCanvas.NodeMoved += OnNodeMoved;
|
|
_mapCanvas.NodeDeleted += OnNodeDeleted;
|
|
_mapCanvas.ConnectionCreated += OnConnectionCreated;
|
|
_mapCanvas.ConnectionDeleted += OnConnectionDeleted;
|
|
_mapCanvas.ImageDoubleClicked += OnImageDoubleClicked;
|
|
_mapCanvas.MapChanged += OnMapChanged;
|
|
|
|
// 스플리터 패널에 맵 캔버스 추가
|
|
panel1.Controls.Add(_mapCanvas);
|
|
|
|
// ...
|
|
|
|
// 툴바 버튼 이벤트 연결
|
|
WireToolbarButtonEvents();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 툴바 버튼 이벤트 핸들러 연결
|
|
/// </summary>
|
|
private void WireToolbarButtonEvents()
|
|
{
|
|
// 편집 모드 버튼들
|
|
btnSelect.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.Select;
|
|
btnMove.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.Move;
|
|
|
|
// btnAddNode는 이제 SplitButton이므로 ButtonClick 사용
|
|
btnAddNode.ButtonClick += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.AddNode;
|
|
|
|
// 드롭다운 메뉴 항목들 (btnAddLabel, btnAddImage)
|
|
btnAddLabel.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.AddLabel;
|
|
btnAddImage.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.AddImage;
|
|
|
|
btnConnNode.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.Connect;
|
|
btnDelete.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.Delete;
|
|
|
|
// 그리드 토글 버튼
|
|
btnToggleGrid.Click += (s, e) => _mapCanvas.ShowGrid = !_mapCanvas.ShowGrid;
|
|
|
|
// 맵 맞춤 버튼
|
|
btnFitMap.Click += (s, e) => _mapCanvas.FitToNodes();
|
|
|
|
// 이미지 편집 버튼은 이미 Designer.cs에서 연결됨 (BtnToolbarEditImage_Click)
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event Handlers
|
|
|
|
private void MainForm_Load(object sender, EventArgs e)
|
|
{
|
|
|
|
RefreshNodeList();
|
|
// 속성 변경 시 이벤트 연결
|
|
_propertyGrid.PropertyValueChanged += PropertyGrid_PropertyValueChanged;
|
|
|
|
// 명령줄 인수가 없는 경우에만 마지막 맵 파일 자동 로드 확인
|
|
if (!_hasCommandLineArgs)
|
|
{
|
|
this.Show();
|
|
Application.DoEvents();
|
|
CheckAndLoadLastMapFile();
|
|
}
|
|
}
|
|
|
|
private void OnNodeAdded(object sender, NodeBase node)
|
|
{
|
|
_hasChanges = true;
|
|
UpdateTitle();
|
|
RefreshNodeList();
|
|
RefreshMagnetList(); // 추가
|
|
// RFID 자동 할당
|
|
}
|
|
|
|
//private void OnNodeSelected(object sender, MapNode node)
|
|
//{
|
|
// _selectedNode = node;
|
|
|
|
// if (node == null)
|
|
// {
|
|
// // 빈 공간 클릭 시 캔버스 속성 표시
|
|
// ShowCanvasProperties();
|
|
// }
|
|
// else
|
|
// {
|
|
// // 노드 클릭 시 노드 속성 표시
|
|
// UpdateNodeProperties();
|
|
// UpdateImageEditButton(); // 이미지 노드 선택 시 이미지 편집 버튼 활성화
|
|
// }
|
|
//}
|
|
|
|
private void OnNodesSelected(object sender, List<NodeBase> nodes)
|
|
{
|
|
// 다중 선택 시 처리
|
|
if (nodes == null || nodes.Count == 0)
|
|
{
|
|
ShowCanvasProperties();
|
|
return;
|
|
}
|
|
|
|
if (nodes.Count == 1)
|
|
{
|
|
// 단일 선택은 기존 방식 사용
|
|
_selectedNode = nodes[0];
|
|
|
|
// Sync with lstMagnet
|
|
if (_selectedNode is MapMagnet magnet)
|
|
{
|
|
lstMagnet.SelectedItem = magnet;
|
|
}
|
|
else
|
|
{
|
|
lstMagnet.SelectedItem = null;
|
|
}
|
|
|
|
//this._mapCanvas.SelectedNode = nodes;
|
|
//this._mapCanvas.Invalidate();
|
|
|
|
UpdateNodeProperties();
|
|
UpdateImageEditButton();
|
|
}
|
|
else
|
|
{
|
|
// 다중 선택: 상태바에 선택 개수 표시
|
|
toolStripStatusLabel1.Text = $"{nodes.Count}개 노드 선택됨 - PropertyGrid에서 공통 속성 일괄 변경 가능";
|
|
|
|
// 다중 선택 PropertyWrapper 표시
|
|
//var multiWrapper = new MultiNodePropertyWrapper(nodes);
|
|
_propertyGrid.SelectedObjects = nodes.ToArray();// multiWrapper;
|
|
_propertyGrid.Focus();
|
|
}
|
|
}
|
|
|
|
private void OnNodeMoved(object sender, NodeBase node)
|
|
{
|
|
_hasChanges = true;
|
|
UpdateTitle();
|
|
RefreshNodeList();
|
|
}
|
|
|
|
private void OnNodeDeleted(object sender, NodeBase node)
|
|
{
|
|
_hasChanges = true;
|
|
UpdateTitle();
|
|
RefreshNodeList();
|
|
RefreshMagnetList(); // 추가
|
|
ClearNodeProperties();
|
|
// RFID 자동 할당
|
|
}
|
|
private void OnConnectionCreated(object sender, (MapNode From, MapNode To) connection)
|
|
{
|
|
_hasChanges = true;
|
|
UpdateTitle();
|
|
RefreshNodeConnectionList();
|
|
UpdateNodeProperties(); // 연결 정보 업데이트
|
|
}
|
|
|
|
|
|
|
|
private void OnConnectionDeleted(object sender, (MapNode From, MapNode To) connection)
|
|
{
|
|
_hasChanges = true;
|
|
UpdateTitle();
|
|
RefreshNodeConnectionList();
|
|
UpdateNodeProperties(); // 연결 정보 업데이트
|
|
}
|
|
|
|
|
|
|
|
private void OnImageDoubleClicked(object sender, MapImage image)
|
|
{
|
|
// 이미지 노드 더블클릭 시 이미지 편집창 표시
|
|
using (var editor = new ImageEditorForm(image))
|
|
{
|
|
if (editor.ShowDialog(this) == DialogResult.OK)
|
|
{
|
|
_hasChanges = true;
|
|
UpdateTitle();
|
|
_mapCanvas.Invalidate(); // 캔버스 다시 그리기
|
|
UpdateNodeProperties(); // 속성 업데이트
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnMapChanged(object sender, EventArgs e)
|
|
{
|
|
_hasChanges = true;
|
|
UpdateTitle();
|
|
RefreshMagnetDirectionList(); // 방향 정보 업데이트
|
|
}
|
|
|
|
private void OnBackgroundClicked(object sender, Point location)
|
|
{
|
|
_selectedNode = null;
|
|
ClearNodeProperties();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ToolStrip Button Event Handlers
|
|
|
|
private void btnNew_Click(object sender, EventArgs e)
|
|
{
|
|
if (CheckSaveChanges())
|
|
{
|
|
NewMap();
|
|
}
|
|
}
|
|
|
|
private void btnOpen_Click(object sender, EventArgs e)
|
|
{
|
|
if (CheckSaveChanges())
|
|
{
|
|
OpenMap();
|
|
}
|
|
}
|
|
|
|
private void btnReopen_Click(object sender, EventArgs e)
|
|
{
|
|
if (string.IsNullOrEmpty(_currentMapFile))
|
|
{
|
|
MessageBox.Show("다시 열 파일이 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
return;
|
|
}
|
|
|
|
if (!File.Exists(_currentMapFile))
|
|
{
|
|
MessageBox.Show($"파일을 찾을 수 없습니다: {_currentMapFile}", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
return;
|
|
}
|
|
|
|
if (CheckSaveChanges())
|
|
{
|
|
LoadMapFromFile(_currentMapFile);
|
|
UpdateStatusBar($"파일을 다시 열었습니다: {Path.GetFileName(_currentMapFile)}");
|
|
}
|
|
}
|
|
|
|
private void btnClose_Click(object sender, EventArgs e)
|
|
{
|
|
CloseMap();
|
|
}
|
|
|
|
private void btnSave_Click(object sender, EventArgs e)
|
|
{
|
|
SaveMap();
|
|
}
|
|
|
|
private void btnSaveAs_Click(object sender, EventArgs e)
|
|
{
|
|
SaveAsMap();
|
|
}
|
|
|
|
private void btnExit_Click(object sender, EventArgs e)
|
|
{
|
|
this.Close();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Keyboard Shortcuts
|
|
|
|
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
|
|
{
|
|
switch (keyData)
|
|
{
|
|
case Keys.Control | Keys.N:
|
|
btnNew_Click(null, null);
|
|
return true;
|
|
case Keys.Control | Keys.O:
|
|
btnOpen_Click(null, null);
|
|
return true;
|
|
case Keys.Control | Keys.S:
|
|
btnSave_Click(null, null);
|
|
return true;
|
|
}
|
|
return base.ProcessCmdKey(ref msg, keyData);
|
|
}
|
|
|
|
#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();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Node Management
|
|
|
|
private void AddNewNode()
|
|
{
|
|
var nodeId = GenerateNodeId();
|
|
var position = new Point(100 + this._mapCanvas.Nodes.Count * 50, 100 + this._mapCanvas.Nodes.Count * 50);
|
|
|
|
var node = new MapNode(nodeId, position, StationType.Normal);
|
|
|
|
this._mapCanvas.Nodes.Add(node);
|
|
_hasChanges = true;
|
|
|
|
RefreshNodeList();
|
|
RefreshMapCanvas();
|
|
UpdateTitle();
|
|
}
|
|
|
|
private void DeleteSelectedNode()
|
|
{
|
|
if (_selectedNode == null)
|
|
{
|
|
MessageBox.Show("삭제할 노드를 선택하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
return;
|
|
}
|
|
|
|
var rfidDisplay = (_selectedNode as MapNode)?.RfidId ?? 0;
|
|
var result = MessageBox.Show($"노드 {rfidDisplay}[{_selectedNode.Id}] 를 삭제하시겠습니까?\n연결된 RFID 매핑도 함께 삭제됩니다.",
|
|
"삭제 확인", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
|
|
|
|
if (result == DialogResult.Yes)
|
|
{
|
|
// 노드 제거
|
|
_mapCanvas.RemoveItem(_selectedNode);
|
|
_selectedNode = null;
|
|
_hasChanges = true;
|
|
|
|
RefreshNodeList();
|
|
RefreshMapCanvas();
|
|
ClearNodeProperties();
|
|
UpdateTitle();
|
|
}
|
|
}
|
|
|
|
private void AddConnectionToSelectedNode()
|
|
{
|
|
if (!(_selectedNode is MapNode selectedMapNode))
|
|
{
|
|
MessageBox.Show("연결을 추가할 노드(MapNode)를 선택하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
return;
|
|
}
|
|
|
|
// 다른 노드들 중에서 선택
|
|
var availableNodes = this._mapCanvas.Nodes.Where(n => n.Id != selectedMapNode.Id &&
|
|
!selectedMapNode.ConnectedNodes.Contains(n.Id)).ToList();
|
|
|
|
if (availableNodes.Count == 0)
|
|
{
|
|
MessageBox.Show("연결 가능한 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
return;
|
|
}
|
|
|
|
// 간단한 선택 다이얼로그 (실제로는 별도 폼을 만들어야 함)
|
|
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.Id));
|
|
if (targetNode != null)
|
|
{
|
|
selectedMapNode.AddConnection(targetNode.Id);
|
|
_hasChanges = true;
|
|
RefreshMapCanvas();
|
|
UpdateNodeProperties();
|
|
UpdateTitle();
|
|
}
|
|
}
|
|
|
|
private void RemoveConnectionFromSelectedNode()
|
|
{
|
|
if (!(_selectedNode is MapNode selectedMapNode) || selectedMapNode.ConnectedNodes.Count == 0)
|
|
{
|
|
MessageBox.Show("연결을 제거할 노드를 선택하거나 연결된 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
return;
|
|
}
|
|
|
|
// 연결된 노드들 중에서 선택
|
|
var connectedNodeNames = selectedMapNode.ConnectedNodes.Select(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 (selectedMapNode.ConnectedNodes.Contains(targetNodeId))
|
|
{
|
|
selectedMapNode.RemoveConnection(targetNodeId);
|
|
_hasChanges = true;
|
|
RefreshMapCanvas();
|
|
UpdateNodeProperties();
|
|
UpdateTitle();
|
|
}
|
|
}
|
|
|
|
private string GenerateNodeId()
|
|
{
|
|
int counter = 1;
|
|
string nodeId;
|
|
|
|
do
|
|
{
|
|
nodeId = $"N{counter:D3}";
|
|
counter++;
|
|
} while (this._mapCanvas.Nodes.Any(n => n.Id == nodeId));
|
|
|
|
return nodeId;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region File Operations
|
|
|
|
private void NewMap()
|
|
{
|
|
this._mapCanvas.Nodes.Clear();
|
|
_selectedNode = null;
|
|
_currentMapFile = string.Empty;
|
|
_hasChanges = false;
|
|
|
|
RefreshAll();
|
|
UpdateTitle();
|
|
}
|
|
|
|
private void CloseMap()
|
|
{
|
|
if (CheckSaveChanges())
|
|
{
|
|
this._mapCanvas.Nodes.Clear();
|
|
_selectedNode = null;
|
|
_currentMapFile = string.Empty;
|
|
_hasChanges = false;
|
|
|
|
RefreshAll();
|
|
UpdateTitle();
|
|
}
|
|
}
|
|
|
|
private void OpenMap()
|
|
{
|
|
var openFileDialog = new OpenFileDialog
|
|
{
|
|
Filter = "AGV Map Files (*.json)|*.json|All Files (*.*)|*.*",
|
|
DefaultExt = "json",
|
|
};
|
|
|
|
|
|
|
|
if (openFileDialog.ShowDialog() == DialogResult.OK)
|
|
{
|
|
try
|
|
{
|
|
LoadMapFromFile(openFileDialog.FileName);
|
|
_currentMapFile = openFileDialog.FileName;
|
|
_hasChanges = false;
|
|
RefreshAll();
|
|
UpdateTitle();
|
|
}
|
|
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 (*.json)|*.json",
|
|
DefaultExt = "json",
|
|
FileName = "NewMap.json"
|
|
};
|
|
|
|
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 result = MapLoader.LoadMapFromFile(filePath);
|
|
sbFile.Text = filePath;
|
|
if (result.Success)
|
|
{
|
|
// 맵 캔버스에 데이터 설정
|
|
_mapCanvas.SetMapLoadResult(result);
|
|
|
|
// 현재 파일 경로 업데이트
|
|
_currentMapFile = filePath;
|
|
_hasChanges = false;
|
|
|
|
// 설정에 마지막 맵 파일 경로 저장
|
|
EditorSettings.Instance.UpdateLastMapFile(filePath);
|
|
|
|
UpdateTitle();
|
|
UpdateNodeList();
|
|
RefreshNodeConnectionList();
|
|
RefreshMagnetList(); // 추가
|
|
|
|
|
|
|
|
|
|
UpdateStatusBar($"맵 파일을 성공적으로 로드했습니다: {Path.GetFileName(filePath)}");
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show($"맵 파일 로딩 실패: {result.ErrorMessage}", "오류",
|
|
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
}
|
|
}
|
|
|
|
private void SaveMapToFile(string filePath)
|
|
{
|
|
// 🔥 백업 파일 생성 (기존 파일이 있을 경우)
|
|
if (File.Exists(filePath))
|
|
{
|
|
try
|
|
{
|
|
// 날짜시간 포함 백업 파일명 생성
|
|
var directory = Path.GetDirectoryName(filePath);
|
|
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(filePath);
|
|
var extension = Path.GetExtension(filePath);
|
|
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
|
var backupFileName = $"{fileNameWithoutExt}_{timestamp}{extension}.bak";
|
|
var backupFilePath = Path.Combine(directory, backupFileName);
|
|
|
|
// 기존 파일을 백업 파일로 복사
|
|
File.Copy(filePath, backupFilePath, true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// 백업 파일 생성 실패 시 경고만 표시하고 계속 진행
|
|
MessageBox.Show($"백업 파일 생성 실패: {ex.Message}\n원본 파일은 계속 저장됩니다.", "백업 경고",
|
|
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
}
|
|
}
|
|
|
|
// 🔥 현재 캔버스 설정을 맵 파일에 저장
|
|
var settings = new MapLoader.MapSettings
|
|
{
|
|
BackgroundColorArgb = _mapCanvas.BackColor.ToArgb(),
|
|
ShowGrid = _mapCanvas.ShowGrid
|
|
};
|
|
|
|
if (MapLoader.SaveMapToFile(filePath,
|
|
_mapCanvas.Nodes, _mapCanvas.Labels,
|
|
_mapCanvas.Images, _mapCanvas.Marks,
|
|
_mapCanvas.Magnets,
|
|
settings))
|
|
{
|
|
// 현재 파일 경로 업데이트
|
|
_currentMapFile = filePath;
|
|
_hasChanges = false;
|
|
|
|
// 설정에 마지막 맵 파일 경로 저장
|
|
EditorSettings.Instance.UpdateLastMapFile(filePath);
|
|
|
|
UpdateTitle();
|
|
UpdateStatusBar($"맵 파일을 성공적으로 저장했습니다: {Path.GetFileName(filePath)}");
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show("맵 파일 저장 실패", "오류",
|
|
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// RFID 매핑 업데이트 (공용 MapLoader 사용)
|
|
/// </summary>
|
|
private void UpdateRfidMappings()
|
|
{
|
|
// RFID 자동 할당 제거 - 사용자가 직접 입력한 값 유지
|
|
// MapLoader.AssignAutoRfidIds(this._mapCanvas.Nodes);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 마지막 맵 파일이 있는지 확인하고 사용자에게 로드할지 물어봄
|
|
/// </summary>
|
|
private void CheckAndLoadLastMapFile()
|
|
{
|
|
var settings = EditorSettings.Instance;
|
|
|
|
if (settings.AutoLoadLastMapFile && settings.HasValidLastMapFile())
|
|
{
|
|
string fileName = Path.GetFileName(settings.LastMapFilePath);
|
|
var result = MessageBox.Show(
|
|
$"마지막으로 사용한 맵 파일을 찾았습니다:\n\n{fileName}\n\n이 파일을 열까요?",
|
|
"마지막 맵 파일 로드",
|
|
MessageBoxButtons.YesNo,
|
|
MessageBoxIcon.Question);
|
|
|
|
if (result == DialogResult.Yes)
|
|
{
|
|
LoadMapFromFile(settings.LastMapFilePath);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region UI Updates
|
|
|
|
private void RefreshAll()
|
|
{
|
|
RefreshNodeList();
|
|
RefreshNodeConnectionList();
|
|
RefreshMagnetList(); // 추가
|
|
RefreshMapCanvas();
|
|
ClearNodeProperties();
|
|
}
|
|
|
|
private void RefreshNodeList()
|
|
{
|
|
listBoxNodes.DataSource = null;
|
|
listBoxNodes.DataSource = this._mapCanvas.Items;
|
|
listBoxNodes.DisplayMember = "DisplayText";
|
|
listBoxNodes.ValueMember = "Id";
|
|
|
|
// 노드 목록 클릭 이벤트 연결
|
|
listBoxNodes.SelectedIndexChanged -= ListBoxNodes_SelectedIndexChanged;
|
|
listBoxNodes.SelectedIndexChanged += ListBoxNodes_SelectedIndexChanged;
|
|
|
|
// 노드 타입별 색상 적용
|
|
listBoxNodes.DrawMode = DrawMode.OwnerDrawFixed;
|
|
listBoxNodes.DrawItem -= ListBoxNodes_DrawItem;
|
|
listBoxNodes.DrawItem += ListBoxNodes_DrawItem;
|
|
}
|
|
|
|
private void ListBoxNodes_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
if (listBoxNodes.SelectedItem is NodeBase selectedNode)
|
|
{
|
|
_selectedNode = selectedNode;
|
|
UpdateNodeProperties();
|
|
// 맵 캔버스에서도 선택된 노드 표시
|
|
if (_mapCanvas != null)
|
|
{
|
|
_mapCanvas.Invalidate();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ListBoxNodes_DrawItem(object sender, DrawItemEventArgs e)
|
|
{
|
|
e.DrawBackground();
|
|
|
|
if (e.Index >= 0 && e.Index < this._mapCanvas.Items.Count)
|
|
{
|
|
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;
|
|
foreColor = SystemColors.HighlightText;
|
|
}
|
|
else
|
|
{
|
|
backColor = Color.White;
|
|
switch (node.Type)
|
|
{
|
|
case NodeType.Normal:
|
|
|
|
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.Label:
|
|
case NodeType.Mark:
|
|
case NodeType.Image:
|
|
foreColor = Color.DarkBlue;
|
|
break;
|
|
case NodeType.Magnet:
|
|
foreColor = Color.DarkMagenta;
|
|
break;
|
|
default:
|
|
foreColor = Color.DarkRed;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 배경 그리기
|
|
using (var brush = new SolidBrush(backColor))
|
|
{
|
|
e.Graphics.FillRectangle(brush, e.Bounds);
|
|
}
|
|
|
|
// 텍스트 그리기 (노드ID - 노드명 - RFID 순서)
|
|
string displayText;
|
|
if (node.Type == NodeType.Normal)
|
|
{
|
|
var item = node as MapNode;
|
|
if (item.HasRfid())
|
|
displayText = $"[{node.Id}-{item.RfidId}] {item.StationType}";
|
|
else
|
|
displayText = $"[{node.Id}] {item.StationType}";
|
|
}
|
|
else if (node.Type == NodeType.Label)
|
|
{
|
|
var item = node as MapLabel;
|
|
displayText = $"{item.Type.ToString().ToUpper()} - {item.Text}";
|
|
}
|
|
else displayText = $"{node.Type.ToString().ToUpper()}";
|
|
|
|
using (var brush = new SolidBrush(foreColor))
|
|
{
|
|
e.Graphics.DrawString(displayText, e.Font, brush, e.Bounds.X + 2, e.Bounds.Y + 2);
|
|
}
|
|
}
|
|
|
|
e.DrawFocusRectangle();
|
|
}
|
|
|
|
private void RefreshNodeConnectionList()
|
|
{
|
|
var connections = new List<NodeConnectionInfo>();
|
|
var processedPairs = new HashSet<string>();
|
|
|
|
// 모든 노드의 연결 정보를 수집 (중복 방지)
|
|
foreach (var fromNode in this._mapCanvas.Nodes)
|
|
{
|
|
foreach (var toNodeId in fromNode.ConnectedNodes)
|
|
{
|
|
var toNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == toNodeId);
|
|
if (toNode != null)
|
|
{
|
|
// 중복 체크 (단일 연결만 표시)
|
|
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.Id, toNode.Id) < 0
|
|
? (fromNode, toNode)
|
|
: (toNode, fromNode);
|
|
|
|
connections.Add(new NodeConnectionInfo
|
|
{
|
|
FromNodeId = firstNode.Id,
|
|
FromRfidId = firstNode.RfidId,
|
|
ToNodeId = secondNode.Id,
|
|
ToRfidId = secondNode.RfidId,
|
|
ConnectionType = "양방향" // 모든 연결이 양방향
|
|
});
|
|
|
|
processedPairs.Add(pairKey1);
|
|
processedPairs.Add(pairKey2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 리스트박스에 표시
|
|
lstNodeConnection.Font = new Font("돋움체", 10);
|
|
lstNodeConnection.DataSource = null;
|
|
lstNodeConnection.DataSource = connections;
|
|
lstNodeConnection.DisplayMember = "ToString";
|
|
|
|
// 리스트박스 클릭 이벤트 연결
|
|
lstNodeConnection.SelectedIndexChanged -= LstNodeConnection_SelectedIndexChanged;
|
|
lstNodeConnection.SelectedIndexChanged += LstNodeConnection_SelectedIndexChanged;
|
|
|
|
// 더블클릭 이벤트 연결 (연결 삭제)
|
|
lstNodeConnection.DoubleClick -= LstNodeConnection_DoubleClick;
|
|
lstNodeConnection.DoubleClick += LstNodeConnection_DoubleClick;
|
|
}
|
|
|
|
|
|
private void LstNodeConnection_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
if (lstNodeConnection.SelectedItem is NodeConnectionInfo connectionInfo)
|
|
{
|
|
// 캔버스에서 해당 연결선 강조 표시
|
|
_mapCanvas?.HighlightConnection(connectionInfo.FromNodeId, connectionInfo.ToNodeId);
|
|
|
|
// 연결된 노드들을 맵에서 하이라이트 표시 (선택적)
|
|
//var fromNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectionInfo.FromNodeId);
|
|
//if (fromNode != null)
|
|
//{
|
|
// if (_selectedNode != fromNode)
|
|
// {
|
|
// _selectedNode = fromNode;
|
|
// _mapCanvas.SelectedNode = fromNode; // 캔버스 선택 상태 동기화
|
|
|
|
// // 속성창 업데이트 (리스트 리프레시 포함)
|
|
// // 주의: RefreshMagnetDirectionList()가 호출되어도 lstNodeConnection에는 영향이 없으므로 안전함
|
|
// UpdateNodeProperties();
|
|
|
|
// _mapCanvas?.Invalidate();
|
|
// }
|
|
//}
|
|
}
|
|
else
|
|
{
|
|
// 선택 해제 시 강조 표시 제거
|
|
_mapCanvas?.ClearHighlightedConnection();
|
|
}
|
|
}
|
|
|
|
private void LstNodeConnection_DoubleClick(object sender, EventArgs e)
|
|
{
|
|
// 더블클릭으로 연결 삭제
|
|
DeleteSelectedConnection();
|
|
}
|
|
|
|
private void RefreshMapCanvas()
|
|
{
|
|
_mapCanvas?.Invalidate();
|
|
}
|
|
|
|
private void UpdateNodeProperties()
|
|
{
|
|
if (_selectedNode == null)
|
|
{
|
|
ShowCanvasProperties();
|
|
return;
|
|
}
|
|
|
|
_propertyGrid.SelectedObject = _selectedNode;
|
|
_propertyGrid.Focus();
|
|
|
|
// 이미지 노드인 경우 편집 버튼 활성화
|
|
UpdateImageEditButton();
|
|
|
|
// 마그넷 방향 리스트 업데이트
|
|
RefreshMagnetDirectionList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 캔버스 속성 표시 (배경색 등)
|
|
/// </summary>
|
|
private void ShowCanvasProperties()
|
|
{
|
|
_propertyGrid.SelectedObject = _mapCanvas;
|
|
DisableImageEditButton();
|
|
}
|
|
|
|
private void ClearNodeProperties()
|
|
{
|
|
_propertyGrid.SelectedObject = null;
|
|
DisableImageEditButton();
|
|
lstMagnetDirection.DataSource = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 선택된 노드가 이미지 노드이면 편집 버튼 활성화
|
|
/// </summary>
|
|
private void UpdateImageEditButton()
|
|
{
|
|
// ToolStripButton으로 변경됨
|
|
btnEditImage.Enabled = (_mapCanvas.SelectedImage != null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 이미지 편집 버튼 비활성화
|
|
/// </summary>
|
|
private void DisableImageEditButton()
|
|
{
|
|
// ToolStripButton으로 변경됨
|
|
btnEditImage.Enabled = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 상단 툴바의 이미지 편집 버튼 클릭 이벤트
|
|
/// </summary>
|
|
private void BtnToolbarEditImage_Click(object sender, EventArgs e)
|
|
{
|
|
var selectedImage = _mapCanvas.SelectedImage;
|
|
if (selectedImage != null)
|
|
{
|
|
using (var editor = new ImageEditorForm(selectedImage))
|
|
{
|
|
if (editor.ShowDialog(this) == DialogResult.OK)
|
|
{
|
|
_hasChanges = true;
|
|
UpdateTitle();
|
|
_mapCanvas.Invalidate(); // 캔버스 다시 그리기
|
|
UpdateNodeProperties(); // 속성 업데이트
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateTitle()
|
|
{
|
|
var title = "AGV Map Editor";
|
|
|
|
if (!string.IsNullOrEmpty(_currentMapFile))
|
|
{
|
|
title += $" - {Path.GetFileName(_currentMapFile)}";
|
|
}
|
|
|
|
if (_hasChanges)
|
|
{
|
|
title += " *";
|
|
}
|
|
|
|
this.Text = title;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 노드 목록을 업데이트
|
|
/// </summary>
|
|
private void UpdateNodeList()
|
|
{
|
|
if (listBoxNodes != null)
|
|
{
|
|
listBoxNodes.DataSource = null;
|
|
listBoxNodes.DataSource = this._mapCanvas.Items;
|
|
listBoxNodes.DisplayMember = "DisplayText";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 상태바에 메시지 표시
|
|
/// </summary>
|
|
/// <param name="message">표시할 메시지</param>
|
|
private void UpdateStatusBar(string message)
|
|
{
|
|
if (toolStripStatusLabel1 != null)
|
|
{
|
|
toolStripStatusLabel1.Text = message;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Form Events
|
|
|
|
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
|
|
{
|
|
if (!CheckSaveChanges())
|
|
{
|
|
e.Cancel = true;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PropertyGrid
|
|
|
|
|
|
private void PropertyGrid_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
|
|
{
|
|
// 변경된 속성명 디버그 출력
|
|
System.Diagnostics.Debug.WriteLine($"[PropertyGrid] 속성 변경됨: {e.ChangedItem.PropertyDescriptor.Name}");
|
|
|
|
// 🔥 MagnetDirectionInfo 변경 처리
|
|
if (_propertyGrid.SelectedObject is MagnetDirectionInfo magInfo)
|
|
{
|
|
ApplyDirectionChange(magInfo);
|
|
return;
|
|
}
|
|
|
|
// RFID 값 변경시 중복 검사
|
|
if (e.ChangedItem.PropertyDescriptor.Name == "RFID")
|
|
{
|
|
var newRfidValue = ushort.Parse(e.ChangedItem.Value?.ToString());
|
|
if (newRfidValue != 0 && CheckRfidDuplicate(newRfidValue))
|
|
{
|
|
// 중복된 RFID 값 발견
|
|
MessageBox.Show($"RFID 값 '{newRfidValue}'이(가) 이미 다른 노드에서 사용 중입니다.\n입력값을 되돌립니다.",
|
|
"RFID 중복 오류", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
|
|
// 원래 값으로 되돌리기 - PropertyGrid의 SelectedObject 사용
|
|
e.ChangedItem.PropertyDescriptor.SetValue(_propertyGrid.SelectedObject, e.OldValue);
|
|
_propertyGrid.Refresh();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 속성이 변경되었을 때 자동으로 변경사항 표시
|
|
_hasChanges = true;
|
|
UpdateTitle();
|
|
|
|
// 🔥 다중 선택 여부 확인 및 선택된 노드들 저장
|
|
bool isMultiSelect = _propertyGrid.SelectedObjects is MapNode[]; // _propertyGrid.SelectedObject is MapNode[];
|
|
|
|
//var a = _propertyGrid.SelectedObject;
|
|
List<NodeBase> selectedNodes = null;
|
|
if (isMultiSelect)
|
|
{
|
|
// 캔버스에서 현재 선택된 노드들 가져오기
|
|
selectedNodes = new List<NodeBase>(_mapCanvas.SelectedNodes);
|
|
System.Diagnostics.Debug.WriteLine($"[PropertyGrid] 다중 선택 노드 수: {selectedNodes.Count}");
|
|
}
|
|
|
|
// 현재 선택된 노드를 기억 (단일 선택인 경우만)
|
|
var currentSelectedNode = _selectedNode;
|
|
|
|
RefreshNodeList();
|
|
RefreshMapCanvas();
|
|
|
|
// 🔥 캔버스 강제 갱신 (bool 타입 속성 변경 시 특히 필요)
|
|
_mapCanvas.Invalidate();
|
|
_mapCanvas.Update();
|
|
|
|
// 🔥 다중 선택인 경우 MultiNodePropertyWrapper를 다시 생성하여 바인딩
|
|
if (isMultiSelect && selectedNodes != null && selectedNodes.Count > 0)
|
|
{
|
|
// System.Diagnostics.Debug.WriteLine($"[PropertyGrid] MultiNodePropertyWrapper 재생성: {selectedNodes.Count}개");
|
|
//var multiWrapper = new MultiNodePropertyWrapper(selectedNodes);
|
|
_propertyGrid.SelectedObjects = selectedNodes.ToArray();// multiWrapper;
|
|
}
|
|
|
|
// PropertyGrid 새로고침
|
|
_propertyGrid.Refresh();
|
|
|
|
// 🔥 단일 선택인 경우에만 노드를 다시 선택 (다중 선택은 캔버스에서 유지)
|
|
if (!isMultiSelect && currentSelectedNode is MapNode mapNode)
|
|
{
|
|
var nodeIndex = this._mapCanvas.Nodes.IndexOf(mapNode);
|
|
if (nodeIndex >= 0)
|
|
{
|
|
listBoxNodes.SelectedIndex = nodeIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// RFID 값 중복 검사
|
|
/// </summary>
|
|
/// <param name="rfidValue">검사할 RFID 값</param>
|
|
/// <returns>중복되면 true, 아니면 false</returns>
|
|
private bool CheckRfidDuplicate(ushort rfidValue)
|
|
{
|
|
if (rfidValue == 0 || this._mapCanvas.Nodes == null)
|
|
return false;
|
|
|
|
// 현재 편집 중인 노드 제외하고 중복 검사
|
|
string currentNodeId = null;
|
|
var selectedObject = _propertyGrid.SelectedObject;
|
|
|
|
int duplicateCount = 0;
|
|
foreach (var node in this._mapCanvas.Nodes)
|
|
{
|
|
// 현재 편집 중인 노드는 제외
|
|
if (node.Id == currentNodeId)
|
|
continue;
|
|
|
|
// 같은 RFID 값을 가진 노드가 있는지 확인
|
|
if (node.RfidId != 0 && node.RfidId == rfidValue)
|
|
{
|
|
duplicateCount++;
|
|
break; // 하나라도 발견되면 중복
|
|
}
|
|
}
|
|
|
|
return duplicateCount > 0;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Data Model for Serialization
|
|
|
|
|
|
#endregion
|
|
|
|
private void btNodeRemove_Click(object sender, EventArgs e)
|
|
{
|
|
DeleteSelectedConnection();
|
|
}
|
|
|
|
private void DeleteSelectedConnection()
|
|
{
|
|
if (lstNodeConnection.SelectedItem is NodeConnectionInfo connectionInfo)
|
|
{
|
|
var result = MessageBox.Show(
|
|
$"다음 연결을 삭제하시겠습니까?\n{connectionInfo}",
|
|
"연결 삭제 확인",
|
|
MessageBoxButtons.YesNo,
|
|
MessageBoxIcon.Question);
|
|
|
|
if (result == DialogResult.Yes)
|
|
{
|
|
// 단일 연결 삭제
|
|
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.Id))
|
|
{
|
|
fromNode.RemoveConnection(toNode.Id);
|
|
removed = true;
|
|
}
|
|
|
|
if (toNode.ConnectedNodes.Contains(fromNode.Id))
|
|
{
|
|
toNode.RemoveConnection(fromNode.Id);
|
|
removed = true;
|
|
}
|
|
|
|
if (!removed)
|
|
{
|
|
MessageBox.Show("연결을 찾을 수 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
return;
|
|
}
|
|
|
|
_hasChanges = true;
|
|
|
|
RefreshNodeConnectionList();
|
|
RefreshMapCanvas();
|
|
UpdateNodeProperties();
|
|
UpdateTitle();
|
|
|
|
toolStripStatusLabel1.Text = $"연결 삭제됨: {connectionInfo.FromNodeId} ↔ {connectionInfo.ToNodeId}";
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show("삭제할 연결을 선택하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
}
|
|
}
|
|
|
|
|
|
private void allTurnLeftRightCrossOnToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
//모든노드의 trun left/right/ cross 속성을 true로 변경합니다
|
|
if (this._mapCanvas.Nodes == null || this._mapCanvas.Nodes.Count == 0)
|
|
{
|
|
MessageBox.Show("맵에 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
return;
|
|
}
|
|
|
|
var result = MessageBox.Show(
|
|
$"모든 노드({this._mapCanvas.Nodes.Count}개)의 회전/교차 속성을 활성화하시겠습니까?\n\n" +
|
|
"• CanTurnLeft = true\n" +
|
|
"• CanTurnRight = true\n" +
|
|
"• DisableCross = false",
|
|
"일괄 속성 변경",
|
|
MessageBoxButtons.YesNo,
|
|
MessageBoxIcon.Question);
|
|
|
|
if (result == DialogResult.Yes)
|
|
{
|
|
foreach (var node in this._mapCanvas.Nodes)
|
|
{
|
|
node.CanTurnLeft = false;
|
|
node.CanTurnRight = false;
|
|
node.DisableCross = true;
|
|
node.ModifiedDate = DateTime.Now;
|
|
}
|
|
|
|
_hasChanges = true;
|
|
UpdateTitle();
|
|
RefreshMapCanvas();
|
|
|
|
MessageBox.Show(
|
|
$"{this._mapCanvas.Nodes.Count}개 노드의 회전/교차 속성이 모두 활성화되었습니다.",
|
|
"완료",
|
|
MessageBoxButtons.OK,
|
|
MessageBoxIcon.Information);
|
|
|
|
UpdateStatusBar($"모든 노드의 회전/교차 속성 활성화 완료");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 마그넷 방향 정보를 표현하는 클래스
|
|
/// </summary>
|
|
public class MagnetDirectionInfo
|
|
{
|
|
[JsonIgnore, Browsable(false)]
|
|
public MapNode FromNode { get; set; }
|
|
|
|
[JsonIgnore, Browsable(false)]
|
|
public MapNode ToNode { get; set; }
|
|
|
|
[Category("연결 정보")]
|
|
[DisplayName("출발 노드")]
|
|
[Description("출발 노드의 ID입니다.")]
|
|
[ReadOnly(true)]
|
|
public string FromNodeId => FromNode?.ID2 ?? "Unknown";
|
|
|
|
[Category("연결 정보")]
|
|
[DisplayName("도착 노드")]
|
|
[Description("도착 노드의 ID입니다.")]
|
|
[ReadOnly(true)]
|
|
public string ToNodeId => ToNode?.ID2 ?? "Unknown";
|
|
|
|
[Category("설정")]
|
|
[DisplayName("방향")]
|
|
[Description("이동할 마그넷 방향입니다.")]
|
|
public MagnetPosition? Direction { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
string dirStr = Direction.HasValue ? Direction.Value.ToString() : "None";
|
|
|
|
string fromStr = FromNode != null ? FromNode.ID2 : "Unknown";
|
|
string toStr = ToNode != null ? ToNode.ID2 : "Unknown";
|
|
|
|
return $"{fromStr} -> {toStr} : {dirStr}";
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RefreshMagnetDirectionList()
|
|
{
|
|
// 이벤트 임시 제거 (DataSource 변경 시 불필요한 이벤트 발생 방지)
|
|
lstMagnetDirection.SelectedIndexChanged -= LstMagnetDirection_SelectedIndexChanged;
|
|
|
|
// 현재 선택된 항목 기억
|
|
int selectedIndex = lstMagnetDirection.SelectedIndex;
|
|
|
|
// 데이터 소스 초기화 (UI 갱신 강제)
|
|
lstMagnetDirection.DataSource = null;
|
|
lstMagnetDirection.Items.Clear();
|
|
|
|
if (this._mapCanvas.Nodes == null)
|
|
{
|
|
// 이벤트 다시 연결 (빠른 리턴 시에도 연결 필요)
|
|
lstMagnetDirection.SelectedIndexChanged += LstMagnetDirection_SelectedIndexChanged;
|
|
return;
|
|
}
|
|
|
|
var directions = new List<MagnetDirectionInfo>();
|
|
|
|
// 모든 노드 검색
|
|
foreach (var nodeItem in this._mapCanvas.Nodes)
|
|
{
|
|
if (nodeItem is MapNode node)
|
|
{
|
|
if (node.MagnetDirections != null && node.MagnetDirections.Count > 0)
|
|
{
|
|
foreach (var kvp in node.MagnetDirections)
|
|
{
|
|
var neighborId = kvp.Key;
|
|
var dir = kvp.Value;
|
|
var neighbor = this._mapCanvas.Nodes.FirstOrDefault(t => t.Id == neighborId);
|
|
|
|
directions.Add(new MagnetDirectionInfo
|
|
{
|
|
FromNode = node,
|
|
ToNode = neighbor,
|
|
Direction = dir
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 보기 좋게 정렬 (FromNode ID 순)
|
|
directions.Sort((a, b) => string.Compare(a.FromNode.Id, b.FromNode.Id));
|
|
|
|
if (directions.Count > 0)
|
|
{
|
|
lstMagnetDirection.DataSource = directions;
|
|
}
|
|
|
|
// 이벤트 다시 연결
|
|
lstMagnetDirection.SelectedIndexChanged += LstMagnetDirection_SelectedIndexChanged;
|
|
|
|
lstMagnetDirection.DoubleClick -= LstMagnetDirection_DoubleClick;
|
|
lstMagnetDirection.DoubleClick += LstMagnetDirection_DoubleClick;
|
|
|
|
// 선택 항목 복원 (가능한 경우) -> 선택된 객체가 다르게 생성되므로 인덱스로 복원 시도
|
|
if (selectedIndex >= 0 && selectedIndex < lstMagnetDirection.Items.Count)
|
|
{
|
|
try
|
|
{
|
|
lstMagnetDirection.SelectedIndex = selectedIndex;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
private void LstMagnetDirection_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
if (lstMagnetDirection.SelectedItem is MagnetDirectionInfo info)
|
|
{
|
|
// 버튼 상태 업데이트
|
|
UpdateDirectionButtons(info);
|
|
|
|
// 캔버스에서 해당 연결선 강조 표시
|
|
if (info.FromNode != null && info.ToNode != null)
|
|
{
|
|
_mapCanvas.HighlightConnection(info.FromNode.Id, info.ToNode.Id);
|
|
|
|
// FromNode 선택 (속성창 갱신 루프 방지 위해 _propertyGrid 직접 설정 고려)
|
|
// 하지만 _selectedNode 변경 시 RefreshMagnetDirectionList()가 호출되어 리스트가 재생성되면 선택이 풀릴 수 있음
|
|
// 따라서 여기서는 캔버스 상의 선택 표시만 변경하고, 전체 속성 업데이트(리스트 리프레시 포함)는 건너뛰거나
|
|
// 리스트 리프레시 로직에서 선택 상태 유지를 보완해야 함.
|
|
|
|
// 일단 _selectedNode를 변경하되, RefreshMagnetDirectionList에서 선택 인덱스 복원을 하므로 괜찮을 것으로 예상됨.
|
|
// 만약 깜빡임이나 끊김이 심하면 UpdateNodeProperties 내의 RefreshMagnetDirectionList 호출을 조건부로 변경해야 함.
|
|
|
|
if (_selectedNode != info.FromNode)
|
|
{
|
|
var prevSelected = _selectedNode;
|
|
_selectedNode = info.FromNode;
|
|
|
|
// _mapCanvas.SelectedNode 설정 (이것만으로는 PropertyGrid 갱신 안됨)
|
|
_mapCanvas.SelectedNode = info.FromNode;
|
|
|
|
// PropertyGrid 갱신 (리스트 리프레시 포함)
|
|
// 주의: 여기서 UpdateNodeProperties()를 부르면 리스트가 다시 그려지면서 선택 이벤트가 다시 발생할 수 있음.
|
|
// 하지만 인덱스 복원 로직이 있으므로 무한루프는 아닐 수 있으나, 비효율적임.
|
|
|
|
// 해결책: 리스트 리프레시 없이 속성창만 갱신
|
|
_propertyGrid.SelectedObject = _selectedNode;
|
|
UpdateImageEditButton();
|
|
|
|
// 캔버스 다시 그리기
|
|
_mapCanvas.Invalidate();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_mapCanvas.ClearHighlightedConnection();
|
|
}
|
|
}
|
|
|
|
private void LstMagnetDirection_DoubleClick(object sender, EventArgs e)
|
|
{
|
|
if (lstMagnetDirection.SelectedItem is MagnetDirectionInfo info)
|
|
{
|
|
var node = info.FromNode;
|
|
if (node != null)
|
|
{
|
|
// 방향 순환
|
|
if (info.Direction == MagnetPosition.S) info.Direction = MagnetPosition.L;
|
|
else if (info.Direction == MagnetPosition.L) info.Direction = MagnetPosition.R;
|
|
else if (info.Direction == MagnetPosition.R) info.Direction = MagnetPosition.S;
|
|
|
|
ApplyDirectionChange(info);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ApplyDirectionChange(MagnetDirectionInfo info)
|
|
{
|
|
var node = info.FromNode;
|
|
if (node == null) return;
|
|
|
|
// 딕셔너리 업데이트
|
|
if (node.MagnetDirections == null)
|
|
node.MagnetDirections = new Dictionary<string, MagnetPosition>();
|
|
|
|
if (info.ToNode != null)
|
|
{
|
|
if (info.Direction == null)
|
|
{
|
|
if (node.MagnetDirections.ContainsKey(info.ToNode.Id))
|
|
node.MagnetDirections.Remove(info.ToNode.Id);
|
|
}
|
|
else
|
|
{
|
|
node.MagnetDirections[info.ToNode.Id] = info.Direction.Value;
|
|
}
|
|
|
|
_hasChanges = true;
|
|
UpdateTitle();
|
|
|
|
// 리스트 갱신
|
|
RefreshMagnetDirectionList();
|
|
|
|
// 캔버스 등 갱신
|
|
_mapCanvas.Invalidate();
|
|
|
|
// 속성창 갱신 (선택된 객체가 바뀌었을 수 있으므로 다시 설정은 RefreshMagnetDirectionList의 선택 복원에서 처리됨)
|
|
_propertyGrid.Refresh();
|
|
}
|
|
}
|
|
|
|
private void btMakeDirdata_Click(object sender, EventArgs e)
|
|
{
|
|
|
|
}
|
|
|
|
private void toolStripButton3_Click(object sender, EventArgs e)
|
|
{
|
|
if (this._mapCanvas.Nodes == null || this._mapCanvas.Nodes.Count == 0)
|
|
return;
|
|
|
|
// 기존 목록을 모두 지울지 물어보고
|
|
var result = MessageBox.Show(
|
|
"마그넷방향을 자동 생성할까요? 없는 부분만 추가됩니다.",
|
|
"마그넷 방향 자동 생성",
|
|
MessageBoxButtons.YesNo,
|
|
MessageBoxIcon.Question);
|
|
|
|
if (result != DialogResult.Yes)
|
|
return;
|
|
|
|
bool clearAll = false;// (result == DialogResult.Yes);
|
|
|
|
int updateCount = 0;
|
|
|
|
foreach (var node in this._mapCanvas.Nodes)
|
|
{
|
|
// 연결 노드가 3개 이상인 노드들을 찾아서
|
|
if (node.Type == NodeType.Normal && node is MapNode mapNode)
|
|
{
|
|
if (clearAll)
|
|
{
|
|
if (mapNode.MagnetDirections != null)
|
|
mapNode.MagnetDirections.Clear();
|
|
else mapNode.MagnetDirections = new Dictionary<string, MagnetPosition>();
|
|
}
|
|
|
|
if (mapNode.ConnectedNodes.Count >= 3)
|
|
{
|
|
// 마그넷 방향 딕셔너리가 없으면 생성
|
|
if (mapNode.MagnetDirections == null)
|
|
mapNode.MagnetDirections = new Dictionary<string, MagnetPosition>();
|
|
|
|
foreach (var connectedId in mapNode.ConnectedNodes)
|
|
{
|
|
// 이미 설정된 경우 건너뜀 (모두 초기화 안 한 경우)
|
|
if (!clearAll && mapNode.MagnetDirections.ContainsKey(connectedId))
|
|
continue;
|
|
|
|
// 모두 자동 생성을 해준다 (기본은 직진으로)
|
|
mapNode.MagnetDirections[connectedId] = MagnetPosition.S;
|
|
updateCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (updateCount > 0)
|
|
{
|
|
_hasChanges = true;
|
|
UpdateTitle();
|
|
|
|
// 현재 선택된 노드의 속성창 및 리스트 갱신
|
|
UpdateNodeProperties();
|
|
|
|
MessageBox.Show($"총 {updateCount}개의 연결에 대해 마그넷 방향(직진)이 설정되었습니다.", "완료", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show("변경된 사항이 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
}
|
|
}
|
|
|
|
private void btDirDelete_Click(object sender, EventArgs e)
|
|
{
|
|
if (lstMagnetDirection.SelectedItem is MagnetDirectionInfo info)
|
|
{
|
|
// 선택된 방향정보를 삭제한다.
|
|
var result = MessageBox.Show(
|
|
$"선택한 마그넷 방향 정보를 삭제하시겠습니까?\n{info.FromNodeId} -> {info.ToNodeId} : {info.Direction}",
|
|
"삭제 확인",
|
|
MessageBoxButtons.YesNo,
|
|
MessageBoxIcon.Question);
|
|
|
|
if (result == DialogResult.Yes)
|
|
{
|
|
if (info.FromNode != null && info.FromNode.MagnetDirections != null)
|
|
{
|
|
if (info.ToNode != null && info.FromNode.MagnetDirections.ContainsKey(info.ToNode.Id))
|
|
{
|
|
info.FromNode.MagnetDirections.Remove(info.ToNode.Id);
|
|
|
|
_hasChanges = true;
|
|
UpdateTitle();
|
|
|
|
// 리스트 및 UI 갱신
|
|
RefreshMagnetDirectionList();
|
|
|
|
// 캔버스 갱신
|
|
_mapCanvas.Invalidate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show("삭제할 항목을 선택해주세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
}
|
|
}
|
|
|
|
private void button1_Click(object sender, EventArgs e)
|
|
{
|
|
//set left
|
|
if (lstMagnetDirection.SelectedItem is MagnetDirectionInfo info)
|
|
{
|
|
info.Direction = MagnetPosition.L;
|
|
ApplyDirectionChange(info);
|
|
|
|
UpdateDirectionButtons(info);
|
|
}
|
|
}
|
|
|
|
private void button2_Click(object sender, EventArgs e)
|
|
{
|
|
//set straight
|
|
if (lstMagnetDirection.SelectedItem is MagnetDirectionInfo info)
|
|
{
|
|
info.Direction = MagnetPosition.S;
|
|
ApplyDirectionChange(info);
|
|
|
|
UpdateDirectionButtons(info);
|
|
}
|
|
}
|
|
|
|
private void button3_Click(object sender, EventArgs e)
|
|
{
|
|
//set right
|
|
if (lstMagnetDirection.SelectedItem is MagnetDirectionInfo info)
|
|
{
|
|
info.Direction = MagnetPosition.R;
|
|
ApplyDirectionChange(info);
|
|
|
|
UpdateDirectionButtons(info);
|
|
}
|
|
}
|
|
|
|
private void UpdateDirectionButtons(MagnetDirectionInfo info)
|
|
{
|
|
button1.BackColor = SystemColors.Control;
|
|
button2.BackColor = SystemColors.Control;
|
|
button3.BackColor = SystemColors.Control;
|
|
|
|
if (info.Direction == MagnetPosition.L) button1.BackColor = Color.Lime;
|
|
else if (info.Direction == MagnetPosition.S) button2.BackColor = Color.Lime;
|
|
else if (info.Direction == MagnetPosition.R) button3.BackColor = Color.Lime;
|
|
}
|
|
|
|
private void toolStripButton2_Click(object sender, EventArgs e)
|
|
{
|
|
var result = MessageBox.Show(
|
|
"기존 설정된 마그넷 방향 정보를 모두 초기화하시겠습니까?",
|
|
"마그넷 방향 일괄 삭제",
|
|
MessageBoxButtons.YesNoCancel,
|
|
MessageBoxIcon.Question);
|
|
|
|
if (result == DialogResult.Cancel)
|
|
return;
|
|
|
|
bool clearAll = (result == DialogResult.Yes);
|
|
|
|
int updateCount = 0;
|
|
|
|
foreach (var node in this._mapCanvas.Nodes)
|
|
{
|
|
// 연결 노드가 3개 이상인 노드들을 찾아서
|
|
if (node.Type == NodeType.Normal && node is MapNode mapNode)
|
|
{
|
|
if (clearAll)
|
|
{
|
|
if (mapNode.MagnetDirections != null)
|
|
mapNode.MagnetDirections.Clear();
|
|
else mapNode.MagnetDirections = new Dictionary<string, MagnetPosition>();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 현재 선택된 노드의 속성창 및 리스트 갱신
|
|
UpdateNodeProperties();
|
|
}
|
|
|
|
private void btAddMagnet_Click(object sender, EventArgs e)
|
|
{
|
|
// 마그넷 추가
|
|
var result = MessageBox.Show("곡선 마그넷(Bezier)을 추가하시겠습니까?\n(예: 베지어 곡선, 아니오: 직선, 취소: 중단)",
|
|
"마그넷 타입 선택", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
|
|
|
|
if (result == DialogResult.Cancel) return;
|
|
|
|
bool isBezier = (result == DialogResult.Yes);
|
|
|
|
// 화면 중앙 좌표 계산 (World Coordinate)
|
|
float zoom = _mapCanvas.ZoomFactor;
|
|
PointF pan = _mapCanvas.PanOffset;
|
|
|
|
float worldCX = (_mapCanvas.Width / 2f) / zoom - pan.X;
|
|
float worldCY = (_mapCanvas.Height / 2f) / zoom - pan.Y;
|
|
|
|
// 고유 ID 생성
|
|
string id = _mapCanvas.GenerateUniqueNodeId();
|
|
|
|
var magnet = new MapMagnet { Id = id };
|
|
|
|
// 점 생성 시 정규화(Snap) 처리
|
|
int cx = (int)worldCX;
|
|
int cy = (int)worldCY;
|
|
|
|
magnet.StartPoint = new Point(cx - 50, cy);
|
|
magnet.EndPoint = new Point(cx + 50, cy);
|
|
|
|
if (isBezier)
|
|
{
|
|
magnet.ControlPoint = new MapMagnet.MagnetPoint { X = cx, Y = cy - 50 };
|
|
}
|
|
|
|
// 캔버스에 추가
|
|
_mapCanvas.Magnets.Add(magnet);
|
|
_hasChanges = true;
|
|
|
|
UpdateTitle();
|
|
RefreshMapCanvas();
|
|
RefreshNodeList();
|
|
RefreshMagnetList(); // 추가
|
|
|
|
// 추가된 마그넷 선택
|
|
//_mapCanvas.SelectedNode = magnet;
|
|
UpdateNodeProperties();
|
|
}
|
|
|
|
private void btDelMagnet_Click(object sender, EventArgs e)
|
|
{
|
|
//선택한 마그넷라인을 삭제 (삭제 후 맵에 바로 반영되도록 업데이트필요)
|
|
if (lstMagnet.SelectedItem is MapMagnet magnet)
|
|
{
|
|
_mapCanvas.RemoveMagnet(magnet);
|
|
RefreshMagnetList();
|
|
}
|
|
}
|
|
|
|
private void RefreshMagnetList()
|
|
{
|
|
lstMagnet.DataSource = null;
|
|
lstMagnet.Items.Clear();
|
|
|
|
if (_mapCanvas.Magnets != null && _mapCanvas.Magnets.Count > 0)
|
|
{
|
|
lstMagnet.DataSource = _mapCanvas.Magnets;
|
|
}
|
|
|
|
// 이벤트 연결
|
|
lstMagnet.SelectedIndexChanged -= LstMagnet_SelectedIndexChanged;
|
|
lstMagnet.SelectedIndexChanged += LstMagnet_SelectedIndexChanged;
|
|
|
|
lstMagnet.DoubleClick -= LstMagnet_DoubleClick;
|
|
lstMagnet.DoubleClick += LstMagnet_DoubleClick;
|
|
|
|
lstMagnet.DrawMode = DrawMode.OwnerDrawFixed;
|
|
lstMagnet.DrawItem -= LstMagnet_DrawItem;
|
|
lstMagnet.DrawItem += LstMagnet_DrawItem;
|
|
}
|
|
|
|
private void LstMagnet_DrawItem(object sender, DrawItemEventArgs e)
|
|
{
|
|
e.DrawBackground();
|
|
|
|
if (e.Index >= 0 && e.Index < lstMagnet.Items.Count)
|
|
{
|
|
var magnet = lstMagnet.Items[e.Index] as MapMagnet;
|
|
if (magnet != null)
|
|
{
|
|
Brush brush = Brushes.Black;
|
|
if (magnet.ControlPoint != null) // Curve
|
|
{
|
|
brush = Brushes.Blue; // Curve는 파란색
|
|
}
|
|
|
|
// 선택된 항목은 흰색 글씨 (배경이 파란색이므로)
|
|
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
|
|
{
|
|
brush = Brushes.White;
|
|
}
|
|
|
|
e.Graphics.DrawString(magnet.ToString(), e.Font, brush, e.Bounds);
|
|
}
|
|
}
|
|
|
|
e.DrawFocusRectangle();
|
|
}
|
|
|
|
private void LstMagnet_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
if (lstMagnet.SelectedItem is MapMagnet magnet)
|
|
{
|
|
_mapCanvas.SelectedNode = magnet;
|
|
//UpdateNodeProperties(); // SelectedNode setter에서 Invalidate 호출됨
|
|
}
|
|
}
|
|
|
|
private void LstMagnet_DoubleClick(object sender, EventArgs e)
|
|
{
|
|
if (lstMagnet.SelectedItem is MapMagnet magnet)
|
|
{
|
|
_mapCanvas.PanTo(magnet.Position);
|
|
}
|
|
}
|
|
|
|
private void btnAddNode_ButtonClick(object sender, EventArgs e)
|
|
{
|
|
|
|
}
|
|
|
|
private void btnAddNode_BackColorChanged(object sender, EventArgs e)
|
|
{
|
|
|
|
}
|
|
|
|
private void btnDeleteConnection_Click(object sender, EventArgs e)
|
|
{
|
|
_mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.DeleteConnection;
|
|
}
|
|
|
|
private void btnConnDir_Click(object sender, EventArgs e)
|
|
{
|
|
//방향연결(노드연결과 유사), 두 노드간의 방향을 생성한다 기본값 straight
|
|
_mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.ConnectDirection;
|
|
}
|
|
|
|
private void toolStripButton3_Click_1(object sender, EventArgs e)
|
|
{
|
|
RefreshMagnetList();
|
|
}
|
|
}
|
|
} |