레이어 패널에 [200]SheetMe 그룹 기능 이식 — 폴더 행·계층·타입 필터·다중선택
[200] 의 그룹도 컨테이너가 아니라 GroupId 태그 + 이름 사전이었다. SheetMe 가 이미 같은 구조(ControlElement.GroupId, JsonIgnore)를 갖고 있어 저장 포맷은 그대로 두고 표시·조작 계층만 얹는다 — 왕복 XML 에는 아무 흔적도 남지 않는다. 레이어 목록을 평탄화 결과(LayerRows)로 바꿨다. 그룹 폴더 행과 컨트롤 행이 한 시퀀스로 섞이고, 계층은 들여쓰기 여백으로만 표현한다(TreeView 를 쓰면 가상화와 기존 행 템플릿을 다 버려야 한다 — 인스펙터 섹션이 쓰는 방식과 같게 맞췄다). 새로 되는 것 - 그룹 폴더 행: 접기/펼치기, 멤버 수, 이름 인라인 편집(더블클릭), 그룹 전체 눈·자물쇠 - 그룹 이름 — FormDocument.GroupNames(편집 전용). 비우면 기본 그룹 으로 복귀 - 컨테이너(패널/그룹박스) 자식이 레이어에 보인다. 지금까지는 아예 접근할 수 없었다 - 목록 다중선택(Shift/Ctrl) — 캔버스 선택과 양방향 동기 - 타입 필터: 그 페이지에 실제로 있는 타입만 후보로 띄운다 - 검색 0건·빈 페이지 안내 문구 함께 고친 결함 - 붙여넣기가 원본 그룹에 끼어들었다(Clone 이 GroupId 를 그대로 복사) → 복제본끼리 새 그룹 - 그룹 해제가 선택된 멤버만 풀어 1개짜리 반쪽 그룹이 남았다 → 그룹 전 멤버를 푼다 - 멤버가 1개 이하로 줄어든 그룹이 계속 남았다 → CleanupGroups 자가치유 - 셰브론이 늘 눌린 것처럼 보였다(IsChecked 채움) → MiniChevron 스타일 분리 --edit-smoke 에 레이어 검사 19건 추가(그룹 위생·평탄화·필터·XML 무영향). 회귀: 테스트 124/124, edit-smoke 실패 0, db-smoke 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d09bd90c8e
commit
a30421f9b5
@@ -261,6 +261,118 @@ public static class EditSmoke
|
||||
Check("폰트 일괄 적용: Undo 1스텝 원복",
|
||||
Find("Label").Model.Props.GetText("Font") != "돋움, 12pt, style=Bold");
|
||||
|
||||
// 20-2-1) 레이어 목록 — 그룹 폴더/계층 평탄화/검색·타입 필터/그룹 위생.
|
||||
// 뒤 단계들이 designer 의 컨트롤 개수를 세므로 별도 문서에서 검사한다.
|
||||
{
|
||||
var lay = new DesignerViewModel(business.CreateNew());
|
||||
lay.AddControlAt("TextBox", new Point(100, 100));
|
||||
lay.AddControlAt("Label", new Point(100, 200));
|
||||
lay.AddControlAt("CheckBox", new Point(100, 300));
|
||||
ControlViewModel Pick(string type) => lay.Pages[0].Controls.First(c => c.Type == type);
|
||||
|
||||
lay.Selection.Set(new[] { Pick("TextBox"), Pick("Label") });
|
||||
lay.GroupSelection();
|
||||
|
||||
var groupRow = lay.LayerRows.OfType<LayerGroupViewModel>().FirstOrDefault();
|
||||
Check("레이어: 그룹 폴더 행 1개", lay.LayerRows.OfType<LayerGroupViewModel>().Count() == 1);
|
||||
Check("레이어: 폴더 멤버 2 + 기본 이름",
|
||||
groupRow is { Members.Count: 2 } && groupRow.Name.StartsWith("그룹"),
|
||||
$"실제 {groupRow?.Members.Count} / {groupRow?.Name}");
|
||||
|
||||
// 폴더 바로 뒤에 멤버가 들여쓰기된 채 이어져야 한다(평탄화 시퀀스)
|
||||
var folderIndex = lay.LayerRows.IndexOf(groupRow!);
|
||||
Check("레이어: 폴더 뒤에 멤버가 들여쓰여 이어짐",
|
||||
folderIndex >= 0
|
||||
&& lay.LayerRows.Skip(folderIndex + 1).Take(2)
|
||||
.OfType<ControlViewModel>().Count(c => c.LayerIndent == 20) == 2);
|
||||
|
||||
// 접으면 멤버 행이 빠지고, 펴면 되돌아온다
|
||||
var expandedCount = lay.LayerRows.Count;
|
||||
groupRow!.IsExpanded = false;
|
||||
lay.RebuildLayerRows();
|
||||
Check("레이어: 접으면 멤버 행 제거", lay.LayerRows.Count == expandedCount - 2,
|
||||
$"실제 {lay.LayerRows.Count} 기대 {expandedCount - 2}");
|
||||
groupRow = lay.LayerRows.OfType<LayerGroupViewModel>().First();
|
||||
groupRow.IsExpanded = true;
|
||||
lay.RebuildLayerRows();
|
||||
Check("레이어: 펴면 멤버 행 복귀", lay.LayerRows.Count == expandedCount);
|
||||
|
||||
// 그룹 일괄 숨김이 전 멤버에 퍼진다
|
||||
groupRow = lay.LayerRows.OfType<LayerGroupViewModel>().First();
|
||||
groupRow.IsHiddenFlag = true;
|
||||
Check("레이어: 그룹 숨김 → 전 멤버", groupRow.Members.All(m => m.IsHiddenFlag));
|
||||
groupRow.IsHiddenFlag = false;
|
||||
Check("레이어: 그룹 표시 복귀", groupRow.Members.All(m => !m.IsHiddenFlag));
|
||||
|
||||
// 그룹 이름은 문서가 소유 — 비우면 기본값으로 되돌아간다
|
||||
groupRow.Name = "머리글 묶음";
|
||||
Check("레이어: 그룹 개명", lay.GroupNameOf(groupRow.GroupId) == "머리글 묶음");
|
||||
groupRow.Name = string.Empty;
|
||||
Check("레이어: 이름 비우면 기본값", lay.GroupNameOf(groupRow.GroupId) == "그룹");
|
||||
|
||||
// 컨테이너 자식이 계층으로 보이고, 접으면 목록에서 빠진다
|
||||
lay.AddControlAt("Panel", new Point(300, 600));
|
||||
var layerPanel = lay.Pages[0].Controls.OfType<PanelViewModel>().First();
|
||||
lay.AddControlAt("Label", new Point(layerPanel.X + 20, layerPanel.Y + 20));
|
||||
lay.RebuildLayerRows();
|
||||
Check("레이어: 컨테이너 자식이 목록에 나옴",
|
||||
lay.LayerRows.OfType<ControlViewModel>().Any(c => ReferenceEquals(c.Parent, layerPanel)));
|
||||
Check("레이어: 자식 들여쓰기 = 부모+16",
|
||||
lay.LayerRows.OfType<ControlViewModel>()
|
||||
.Where(c => ReferenceEquals(c.Parent, layerPanel))
|
||||
.All(c => c.LayerIndent == layerPanel.LayerIndent + 16));
|
||||
var withChild = lay.LayerRows.Count;
|
||||
layerPanel.IsLayerExpanded = false;
|
||||
lay.RebuildLayerRows();
|
||||
Check("레이어: 컨테이너 접으면 자식 제거", lay.LayerRows.Count == withChild - 1);
|
||||
layerPanel.IsLayerExpanded = true;
|
||||
lay.RebuildLayerRows();
|
||||
|
||||
// 검색은 자손이 걸리면 조상도 남긴다 — 부모가 빠지면 자식이 통째로 사라진다
|
||||
var childId = layerPanel.Children[0].Id;
|
||||
lay.LayerFilter = childId;
|
||||
Check("레이어: 검색 시 조상 보존",
|
||||
lay.LayerRows.Contains(layerPanel)
|
||||
&& lay.LayerRows.OfType<ControlViewModel>().Any(c => c.Id == childId));
|
||||
lay.LayerFilter = "존재하지않는이름____";
|
||||
Check("레이어: 0건이면 빈 목록 안내", lay.IsLayerListEmpty && lay.LayerEmptyText.Contains("검색"));
|
||||
lay.LayerFilter = string.Empty;
|
||||
|
||||
// 타입 필터
|
||||
lay.SetLayerTypeFilter(new[] { "CheckBox" });
|
||||
Check("레이어: 타입 필터 적용",
|
||||
lay.HasLayerTypeFilter
|
||||
&& lay.LayerRows.OfType<ControlViewModel>().Any()
|
||||
&& lay.LayerRows.OfType<ControlViewModel>().All(c => c.Type == "CheckBox"));
|
||||
lay.SetLayerTypeFilter(Array.Empty<string>());
|
||||
Check("레이어: 타입 필터 해제", !lay.HasLayerTypeFilter);
|
||||
|
||||
// 붙여넣은 복제본은 원본 그룹에 끼지 않는다(GroupId 재매핑)
|
||||
groupRow = lay.LayerRows.OfType<LayerGroupViewModel>().First();
|
||||
var sourceGroupId = groupRow.Members[0].Model.GroupId;
|
||||
lay.Selection.SetSingle(groupRow.Members[0]);
|
||||
lay.CopySelection();
|
||||
lay.Paste();
|
||||
Check("레이어: 붙여넣기가 원본 그룹에 끼지 않음",
|
||||
lay.Selection.Items[0].Model.GroupId != sourceGroupId,
|
||||
$"원본 {sourceGroupId} 복제 {lay.Selection.Items[0].Model.GroupId}");
|
||||
|
||||
// 멤버가 1개로 줄면 그룹은 자동 해제되고 이름도 정리된다
|
||||
groupRow = lay.LayerRows.OfType<LayerGroupViewModel>().First();
|
||||
lay.Selection.SetSingle(groupRow.Members[1]);
|
||||
lay.DeleteSelection();
|
||||
Check("레이어: 멤버 1개 남으면 그룹 자동 해제",
|
||||
!lay.LayerRows.OfType<LayerGroupViewModel>().Any()
|
||||
&& lay.Pages[0].Controls.All(c => c.Model.GroupId is null)
|
||||
&& lay.Document.GroupNames.Count == 0);
|
||||
|
||||
// 그룹은 편집 전용 상태다 — 저장 XML 에 흔적이 남으면 왕복 불변식이 깨진다
|
||||
lay.Selection.Set(lay.Pages[0].Controls.Take(2).ToList());
|
||||
lay.GroupSelection();
|
||||
Check("레이어: 그룹은 XML 에 나가지 않음",
|
||||
!serializer.Write(lay.Document).Contains("GroupId", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
// 20-3) 문서 간 복사/붙여넣기 — A 서식에서 복사 → B 서식에 붙여넣기(클립보드 공유)
|
||||
var designerB = new DesignerViewModel(business.CreateNew());
|
||||
designer.Selection.SetSingle(Find("TextBox"));
|
||||
|
||||
Reference in New Issue
Block a user