using System.Windows; using SheetMe.Data.Stores; using SheetMe.Designer.Services; using SheetMe.Designer.ViewModels; namespace SheetMe.Designer.Views; /// /// 메인 셸 윈도우 — 커스텀 타이틀바(로고+메뉴) | 좌(서식 목록/도구상자/페이지/레이어) | 문서 탭 | 인스펙터. /// [200]SheetMe 크롬(WindowChrome CaptionHeight 40) 이식 — 창 제어/테마 토글은 뷰 전용 관심사라 코드비하인드 처리. /// 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)); } /// /// 창 핸들이 막 생긴 시점 — 최대화 보정을 건다. /// /// Loaded 가 아니라 여기다. Loaded 는 창이 이미 보인 뒤라 창 생성 시점의 첫 WM_GETMINMAXINFO 를 놓친다. /// 지금은 기동 시 최대화하지 않아 결과가 같지만, '마지막 창 상태 기억'이 생기면 /// 기동 순간 잘못된 크기가 한 프레임 번쩍인다. /// protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); MaximizeToWorkArea.Attach(this); } /// 기동 — 테마 메뉴 체크 동기화 + 뷰모델 Loaded 커맨드 실행(구 CustomWindow.LoadedCommand 대체) 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); } } /// 서식 목록 더블클릭 → 탭으로 열기(뷰모델 위임 — ListBox 더블클릭은 InputBinding 미지원) 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; } /// 플로팅 바 — 컨트롤 추가 플라이아웃 토글(닫힘 직후 재클릭 가드) private void OnAddControlFlyoutClick(object sender, RoutedEventArgs e) { if (Environment.TickCount - addControlPopupClosedAt > 150) { AddControlPopup.IsOpen = true; } } /// 플로팅 바 — 줌 플라이아웃 토글 private void OnZoomFlyoutClick(object sender, RoutedEventArgs e) { if (Environment.TickCount - zoomPopupClosedAt > 150) { ZoomPopup.IsOpen = true; } } /// 플라이아웃 타일 클릭 — 활성 페이지 중앙에 컨트롤 배치(팔레트 더블클릭과 동일 경로) 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); } } /// 줌 플라이아웃 행 클릭 — 커맨드 실행 후 팝업 닫기 private void OnZoomRowClick(object sender, RoutedEventArgs e) => ZoomPopup.IsOpen = false; /// 플로팅 바 — 선택 도구(기본) private void OnSelectToolClick(object sender, RoutedEventArgs e) { if (DataContext is MainViewModel viewModel) { viewModel.IsHandTool = false; } } /// 플로팅 바 — 손(팬) 도구 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(); /// /// 창 닫기(타이틀바 X / Alt+F4) 전 미저장 확인 — 판단은 VM 에 위임하고 여기서는 취소만 반영한다. /// 파일▸종료는 Application.Shutdown() 이라 이 이벤트의 e.Cancel 을 무시하므로 VM 이 먼저 확인하고 /// IsShuttingDown 을 세운다(그 경로에서 여기가 다시 묻지 않도록). /// 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); } } /// /// F4 — 인스펙터 첫 편집 칸으로 들어간다. /// /// 전에는 인스펙터로 포커스를 보내는 키가 저장소에 하나도 없었다. /// 캔버스에서 Tab 을 누르면 좌측 패널·문서 탭·플로팅 바를 다 지나야 인스펙터에 닿았다. /// 커맨드로 만들지 않는다 — 그러면 뷰모델이 뷰의 포커스를 알아야 한다. /// 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; } } /// 파일 메뉴·빈 화면 버튼에서 서식 목록으로 보낸다 private void OnOpenFromListClick(object sender, RoutedEventArgs e) => FocusSheetList(); /// 서식 목록 탭으로 옮기고 검색칸에 커서를 둔다 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); } /// /// 서식 목록 키 처리 — Enter 로 연다. /// /// 전에는 더블클릭 전용이라 키보드로는 서식을 열 수 없었다. /// 'DB에서 열기' 대화상자에는 [열기] 버튼이 있어서 그 구멍이 가려져 있었다 — 그 창을 지우기 전에 메운다. /// 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; } /// 검색칸에서 ↓ 를 누르면 목록으로 내려간다 — 검색과 선택 사이에 마우스를 두지 않는다 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; } /// 최대화/복원 글리프 전환(Segoe MDL2: E922=최대화, E923=복원) private void OnWindowStateChanged(object sender, EventArgs e) => MaxGlyph.Text = WindowState == WindowState.Maximized ? "" : ""; }