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 ? "" : "";
}