<b>먼저 밝힐 것: 내가 "완전 중복"이라고 인용한 판정이 틀렸다.</b> 좌측 목록과 이 대화상자는 같은 조회를 쓰지만(둘 다 ListSheets, WHERE·ORDER BY·상한 동일) 같은 쓸모를 주지 않았다. 지우기 전에 구멍 넷을 좌측에 옮겼다. <b>① 저장·등록 뒤 재조회 — 가장 컸다.</b> 좌측 목록은 기동 1회와 검색 버튼으로만 채워졌고 저장 뒤 갱신이 없었다. 즉 <b>방금 저장한 서식이 목록에 안 떴다</b>. 대화상자는 열 때마다 재조회해서 그 구멍을 덮고 있었다. 그것을 안 메우고 창만 지웠으면, DB 전용으로 바꾼 직후 신규 서식을 저장한 사용자가 그 서식을 다시 찾지 못했을 것이다. <b>② 서식명 툴팁</b> — 좌측은 폭이 148px 남짓(자물쇠 붙으면 134, 패널 최소폭이면 80)이라 긴 이름이 잘리는데 전문을 볼 방법이 없었다. 대화상자는 열이 300px 에 드래그로 늘릴 수 있었다. <b>③ '디자인 없음'과 '서식생성기 사용 꺼짐'</b> 이 좌측에서는 둘 다 같은 회색+Opacity 0.6 이라 화면만 보고 구분할 수 없었다. 툴팁으로 갈랐다. <b>④ Enter 로 열기</b> — 좌측은 더블클릭 전용이라 키보드로 서식을 열 수 없었다. 대화상자에는 [열기] 버튼이 있어 그 구멍이 가려져 있었다. 검색칸에서 ↓ 로 목록에 내려가는 길도 붙였다. 그 다음 지웠다: SheetOpenDialogView(680×560), OpenFromDbCommand, 파일 메뉴의 파일 열기· 다른 이름으로 저장·DB에서 열기, 빈 화면의 '파일 열기' 버튼, Ctrl+Shift+S. Ctrl+O 는 이제 서식 목록으로 간다 — 전에는 OS 파일 대화상자였고, 운영 서식은 전부 DB 에 있다. <b>SaveMode 기본값은 바꾸지 않았다.</b> 조사해 보니 그 규칙은 실재하고 문서·코드 4곳에 있다 — docs/DEPLOYMENT.md:56 은 "이 기본값을 바꾸지 말 것"이라고 명시한다. 배포된 ini 의 CurrentServer 가 운영 병원 DB 를 가리켜서, 명시적으로 켠 단말에서만 운영 테이블(E_SdgMst/E_SctMst)에 쓰게 하려는 장치다. ini 는 SaveMode 를 담지 않으므로 이 규칙이 병원 단말의 유일한 방어선이다. UI 진입점만 없애고 규칙은 그대로 둔다. 파일 저장·열기 <b>코드</b>도 남긴다 — 접속 없이 도는 --render-smoke 와 --edit-smoke 직렬화 왕복이 그 경로를 쓴다. 대신 화면이 상태를 말하게 했다 — SaveMode=File 단말에서는 저장 메뉴·버튼이 비활성이다. 전에는 항상 활성이고 누른 뒤에야 경고가 떴다. <b>종료 가드를 고쳤다 — 앱이 안 닫히는 상태를 없앤다.</b> 전에는 저장 실패가 곧 종료 중단이었다. 파일 폴백이 있을 때는 드문 분기였지만 저장이 DB 로만 가면 흔한 경로가 된다 — SaveMode=File 단말이나 접속 불가에서 <b>수정한 탭을 가진 사용자가 앱을 닫을 수 없게</b> 된다. 이제 '버리고 종료'를 묻는다. 게이트 대체물도 함께 옮겼다: --dialog-shots 의 01-sheet-open 을 지우면 서식명 잘림을 잡을 자리가 통째로 사라지므로, 좌측 목록에 표본을 넣은 01-sheet-list 샷으로 바꿨다 (진단 모드는 Loaded 를 안 돌려 목록이 비므로 표본을 직접 주입한다). 검사한 글자 2,531 → 2,649개. 게이트: 테스트 291/291, --edit-smoke 0실패, --dialog-shots 62장 FAIL 0 대조군 4/4, --modal-check 0실패, --cleartype 11/11, --maxrect 0실패, --scale-budget 5/5, 빌드 경고 0, --db-smoke 1271건 diff 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
237 lines
10 KiB
C#
237 lines
10 KiB
C#
using System.Windows;
|
|
using SheetMe.Data.Stores;
|
|
using SheetMe.Designer.Services;
|
|
using SheetMe.Designer.ViewModels;
|
|
|
|
namespace SheetMe.Designer.Views;
|
|
|
|
/// <summary>
|
|
/// 메인 셸 윈도우 — 커스텀 타이틀바(로고+메뉴) | 좌(서식 목록/도구상자/페이지/레이어) | 문서 탭 | 인스펙터.
|
|
/// [200]SheetMe 크롬(WindowChrome CaptionHeight 40) 이식 — 창 제어/테마 토글은 뷰 전용 관심사라 코드비하인드 처리.
|
|
/// </summary>
|
|
public partial class MainView : Window
|
|
{
|
|
public MainView()
|
|
{
|
|
InitializeComponent();
|
|
// StaysOpen=False 팝업은 바깥 클릭(버튼 포함)으로 먼저 닫힌다 — 토글 버튼 재클릭이 곧바로 다시 열지 않게 닫힌 시각 기록
|
|
AddControlPopup.Closed += (_, _) => addControlPopupClosedAt = Environment.TickCount;
|
|
ZoomPopup.Closed += (_, _) => zoomPopupClosedAt = Environment.TickCount;
|
|
|
|
// 진단(게이트)은 창 크기를 스스로 정한다 — 저장된 취향이 끼어들면 샷 크기가 PC 마다 달라진다
|
|
if (StartupArguments.DiagnosticMode)
|
|
{
|
|
return;
|
|
}
|
|
// Show 전에 복원해야 한 프레임 번쩍이지 않는다
|
|
WindowPlacement.Restore(this);
|
|
LeftColumn.Width = new GridLength(WindowPlacement.LeftPanelWidth(LeftColumn.Width.Value));
|
|
RightColumn.Width = new GridLength(WindowPlacement.RightPanelWidth(RightColumn.Width.Value));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 창 핸들이 막 생긴 시점 — 최대화 보정을 건다.
|
|
///
|
|
/// Loaded 가 아니라 여기다. Loaded 는 창이 이미 보인 뒤라 창 생성 시점의 첫 WM_GETMINMAXINFO 를 놓친다.
|
|
/// 지금은 기동 시 최대화하지 않아 결과가 같지만, '마지막 창 상태 기억'이 생기면
|
|
/// 기동 순간 잘못된 크기가 한 프레임 번쩍인다.
|
|
/// </summary>
|
|
protected override void OnSourceInitialized(EventArgs e)
|
|
{
|
|
base.OnSourceInitialized(e);
|
|
MaximizeToWorkArea.Attach(this);
|
|
}
|
|
|
|
/// <summary>기동 — 테마 메뉴 체크 동기화 + 뷰모델 Loaded 커맨드 실행(구 CustomWindow.LoadedCommand 대체)</summary>
|
|
private void OnWindowLoaded(object sender, RoutedEventArgs e)
|
|
{
|
|
ThemeMenuItem.IsChecked = !ThemeManager.IsLight;
|
|
// 진단(게이트) 실행에서는 Loaded 커맨드를 돌리지 않는다 — 서식 목록 DB 조회가 붙어 있어서
|
|
// 검사 결과가 접속 상태와 조회 결과에 따라 달라진다. 게이트는 DB 없이 같은 답을 내야 한다.
|
|
if (StartupArguments.DiagnosticMode)
|
|
{
|
|
return;
|
|
}
|
|
if (DataContext is MainViewModel viewModel && viewModel.LoadedCommand?.CanExecute(null) == true)
|
|
{
|
|
viewModel.LoadedCommand.Execute(null);
|
|
}
|
|
}
|
|
|
|
/// <summary>서식 목록 더블클릭 → 탭으로 열기(뷰모델 위임 — ListBox 더블클릭은 InputBinding 미지원)</summary>
|
|
private void OnSheetListDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
|
{
|
|
if (DataContext is MainViewModel viewModel)
|
|
{
|
|
viewModel.OpenSheetFromList(SheetListBox.SelectedItem as SheetSummary);
|
|
}
|
|
}
|
|
|
|
private void OnThemeToggleClick(object sender, RoutedEventArgs e)
|
|
{
|
|
ThemeManager.Toggle();
|
|
ThemeMenuItem.IsChecked = !ThemeManager.IsLight;
|
|
}
|
|
|
|
/// <summary>플로팅 바 — 컨트롤 추가 플라이아웃 토글(닫힘 직후 재클릭 가드)</summary>
|
|
private void OnAddControlFlyoutClick(object sender, RoutedEventArgs e)
|
|
{
|
|
if (Environment.TickCount - addControlPopupClosedAt > 150)
|
|
{
|
|
AddControlPopup.IsOpen = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>플로팅 바 — 줌 플라이아웃 토글</summary>
|
|
private void OnZoomFlyoutClick(object sender, RoutedEventArgs e)
|
|
{
|
|
if (Environment.TickCount - zoomPopupClosedAt > 150)
|
|
{
|
|
ZoomPopup.IsOpen = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>플라이아웃 타일 클릭 — 활성 페이지 중앙에 컨트롤 배치(팔레트 더블클릭과 동일 경로)</summary>
|
|
private void OnFlyoutTileClick(object sender, RoutedEventArgs e)
|
|
{
|
|
AddControlPopup.IsOpen = false;
|
|
if ((sender as FrameworkElement)?.DataContext is SheetMe.Core.Catalog.ControlDescriptor item
|
|
&& DataContext is MainViewModel viewModel)
|
|
{
|
|
viewModel.CurrentDesigner?.AddPaletteItemAtCenter(item.Type);
|
|
}
|
|
}
|
|
|
|
/// <summary>줌 플라이아웃 행 클릭 — 커맨드 실행 후 팝업 닫기</summary>
|
|
private void OnZoomRowClick(object sender, RoutedEventArgs e) => ZoomPopup.IsOpen = false;
|
|
|
|
/// <summary>플로팅 바 — 선택 도구(기본)</summary>
|
|
private void OnSelectToolClick(object sender, RoutedEventArgs e)
|
|
{
|
|
if (DataContext is MainViewModel viewModel)
|
|
{
|
|
viewModel.IsHandTool = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>플로팅 바 — 손(팬) 도구</summary>
|
|
private void OnHandToolClick(object sender, RoutedEventArgs e)
|
|
{
|
|
if (DataContext is MainViewModel viewModel)
|
|
{
|
|
viewModel.IsHandTool = true;
|
|
}
|
|
}
|
|
|
|
private int addControlPopupClosedAt;
|
|
private int zoomPopupClosedAt;
|
|
|
|
private void OnMinimizeClick(object sender, RoutedEventArgs e) => WindowState = WindowState.Minimized;
|
|
|
|
private void OnMaxRestoreClick(object sender, RoutedEventArgs e) =>
|
|
WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
|
|
|
|
private void OnCloseWinClick(object sender, RoutedEventArgs e) => Close();
|
|
|
|
/// <summary>
|
|
/// 창 닫기(타이틀바 X / Alt+F4) 전 미저장 확인 — 판단은 VM 에 위임하고 여기서는 취소만 반영한다.
|
|
/// 파일▸종료는 Application.Shutdown() 이라 이 이벤트의 e.Cancel 을 무시하므로 VM 이 먼저 확인하고
|
|
/// IsShuttingDown 을 세운다(그 경로에서 여기가 다시 묻지 않도록).
|
|
/// </summary>
|
|
private void OnWindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
|
|
{
|
|
if (DataContext is MainViewModel viewModel && !viewModel.IsShuttingDown && !viewModel.ConfirmCloseAll())
|
|
{
|
|
e.Cancel = true;
|
|
return;
|
|
}
|
|
// 취소되지 않은 뒤에만 저장한다 — 닫기를 물렀는데 크기가 기억되면 조작하지 않은 값이 남는다
|
|
if (!StartupArguments.DiagnosticMode)
|
|
{
|
|
WindowPlacement.Save(this, LeftColumn.ActualWidth, RightColumn.ActualWidth);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// F4 — 인스펙터 첫 편집 칸으로 들어간다.
|
|
///
|
|
/// 전에는 인스펙터로 포커스를 보내는 키가 저장소에 <b>하나도 없었다</b>.
|
|
/// 캔버스에서 Tab 을 누르면 좌측 패널·문서 탭·플로팅 바를 다 지나야 인스펙터에 닿았다.
|
|
/// 커맨드로 만들지 않는다 — 그러면 뷰모델이 뷰의 포커스를 알아야 한다.
|
|
/// </summary>
|
|
private void OnWindowPreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
|
{
|
|
if (e.Key == System.Windows.Input.Key.F4)
|
|
{
|
|
Inspector.FocusFirstRow();
|
|
e.Handled = true;
|
|
return;
|
|
}
|
|
// Ctrl+O — 서식 목록으로. 전에는 OS 파일 대화상자로 갔다.
|
|
// 손이 먼저 가는 키인데 디스크로 가고, 운영 서식 1,271건은 전부 DB 에 있다.
|
|
if (e.Key == System.Windows.Input.Key.O
|
|
&& System.Windows.Input.Keyboard.Modifiers == System.Windows.Input.ModifierKeys.Control)
|
|
{
|
|
FocusSheetList();
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>파일 메뉴·빈 화면 버튼에서 서식 목록으로 보낸다</summary>
|
|
private void OnOpenFromListClick(object sender, RoutedEventArgs e) => FocusSheetList();
|
|
|
|
/// <summary>서식 목록 탭으로 옮기고 검색칸에 커서를 둔다</summary>
|
|
private void FocusSheetList()
|
|
{
|
|
if (DataContext is MainViewModel viewModel)
|
|
{
|
|
viewModel.SelectedLeftTabIndex = 0;
|
|
}
|
|
// 탭이 방금 바뀌어 아직 시각 트리가 없다 — 레이아웃이 한 번 돈 뒤에 잡는다
|
|
// (LayerPanelView 의 검색칸 포커스와 같은 규범).
|
|
Dispatcher.BeginInvoke(new Action(() =>
|
|
{
|
|
SheetSearchBox.Focus();
|
|
System.Windows.Input.Keyboard.Focus(SheetSearchBox);
|
|
SheetSearchBox.SelectAll();
|
|
}), System.Windows.Threading.DispatcherPriority.Input);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 서식 목록 키 처리 — Enter 로 연다.
|
|
///
|
|
/// 전에는 <b>더블클릭 전용</b>이라 키보드로는 서식을 열 수 없었다.
|
|
/// 'DB에서 열기' 대화상자에는 [열기] 버튼이 있어서 그 구멍이 가려져 있었다 — 그 창을 지우기 전에 메운다.
|
|
/// </summary>
|
|
private void OnSheetListKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
|
{
|
|
if (e.Key != System.Windows.Input.Key.Enter || DataContext is not MainViewModel viewModel)
|
|
{
|
|
return;
|
|
}
|
|
viewModel.OpenSheetFromList(SheetListBox.SelectedItem as SheetSummary);
|
|
e.Handled = true;
|
|
}
|
|
|
|
/// <summary>검색칸에서 ↓ 를 누르면 목록으로 내려간다 — 검색과 선택 사이에 마우스를 두지 않는다</summary>
|
|
private void OnSheetSearchKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
|
{
|
|
if (e.Key != System.Windows.Input.Key.Down || SheetListBox.Items.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
SheetListBox.SelectedIndex = Math.Max(0, SheetListBox.SelectedIndex);
|
|
SheetListBox.Focus();
|
|
if (SheetListBox.ItemContainerGenerator.ContainerFromIndex(SheetListBox.SelectedIndex)
|
|
is System.Windows.Controls.ListBoxItem item)
|
|
{
|
|
item.Focus();
|
|
}
|
|
e.Handled = true;
|
|
}
|
|
|
|
/// <summary>최대화/복원 글리프 전환(Segoe MDL2: E922=최대화, E923=복원)</summary>
|
|
private void OnWindowStateChanged(object sender, EventArgs e) =>
|
|
MaxGlyph.Text = WindowState == WindowState.Maximized ? "" : "";
|
|
}
|