diff --git a/src/SheetMe.Designer/Diagnostics/DialogShots.cs b/src/SheetMe.Designer/Diagnostics/DialogShots.cs index c2fcf7d..0ff70f4 100644 --- a/src/SheetMe.Designer/Diagnostics/DialogShots.cs +++ b/src/SheetMe.Designer/Diagnostics/DialogShots.cs @@ -127,7 +127,9 @@ public static class DialogShots ("00b-main-1920", () => NewMainShell(1920, 1032)), // 도구 상자 탭은 타일 라벨이 좁은 2열에 들어가므로 잘림이 가장 잘 나는 자리다 ("00c-main-toolbox", () => NewMainShell(1440, 920, leftTab: 2)), - ("01-sheet-open", () => new Views.SheetOpenDialogView(_ => SampleSheets())), + // 'DB에서 열기' 대화상자를 지웠다. 그 자리를 좌측 서식 목록 샷이 대신한다 — + // 안 그러면 서식명 잘림을 잡을 자리가 게이트에서 통째로 사라진다. + ("01-sheet-list", () => NewMainShell(1440, 920, leftTab: 0, sheets: SampleSheets())), ("02-sheet-history", () => new Views.SheetHistoryDialogView("S999", "표본 서식", SampleVersions())), ("03-query-editor", () => NewQueryEditor(designer, "SELECT ChtNum, PatNam FROM P_PatMst\nWHERE ChtNum = <>")), @@ -217,7 +219,8 @@ public static class DialogShots /// 접속 없이도 같은 결과가 나온다. 문서 한 장을 넣어 캔버스·레이어·인스펙터가 빈 상태가 아니게 한다 — /// 빈 껍데기를 찍으면 잘림을 볼 수 없다. /// - private static Window NewMainShell(double width, double height, int leftTab = 1) + private static Window NewMainShell(double width, double height, int leftTab = 1, + List? sheets = null) { var window = new Views.MainView { @@ -233,6 +236,16 @@ public static class DialogShots } shell.NewFileCommand?.Execute(null); shell.SelectedLeftTabIndex = leftTab; + // 진단 모드는 Loaded 커맨드를 안 돌리므로 목록이 비어 있다 — + // 빈 목록을 찍으면 서식명 잘림을 볼 수 없다. 표본을 직접 넣는다. + if (sheets is not null) + { + shell.SheetList.Clear(); + foreach (var sheet in sheets) + { + shell.SheetList.Add(sheet); + } + } // 컨트롤을 놓고 하나를 고른다 — 이걸 안 하면 인스펙터가 비어 있고 레이어에 행이 없어서 // 검사할 글자가 31개밖에 안 나온다(빈 껍데기를 찍으면 잘림을 볼 수 없다). diff --git a/src/SheetMe.Designer/ViewModels/MainViewModel.cs b/src/SheetMe.Designer/ViewModels/MainViewModel.cs index 308e61d..6f6b5bc 100644 --- a/src/SheetMe.Designer/ViewModels/MainViewModel.cs +++ b/src/SheetMe.Designer/ViewModels/MainViewModel.cs @@ -168,6 +168,17 @@ internal sealed class MainViewModel : ViewModelBase /// DB 사용 가능 여부(서식 목록 패널 안내용) public bool CanUseDb => dataBusiness.CanUseDb; + /// + /// DB 저장이 가능한 단말인가 — 저장 메뉴·버튼의 활성 조건. + /// + /// 전에는 항상 활성이었고 누른 뒤에야 경고가 떴다. 이제 저장이 DB 로만 가므로 + /// 못 쓰는 상태를 누르기 전에 보여 줘야 한다. + /// SaveMode 기본값이 File 인 데는 이유가 있다 — 배포된 ini 의 CurrentServer 가 운영 병원 DB 를 + /// 가리켜서, 명시적으로 켠 단말에서만 운영 테이블에 쓰게 하려는 장치다(docs/DEPLOYMENT.md:54-56). + /// 그 규칙은 그대로 두고 화면이 상태를 말하게 한다. + /// + public bool CanSaveToDb => dataBusiness.CanSaveToDb; + /// 좌측 패널 탭(0=서식 목록, 1=레이어, 2=도구 상자) — 서식 열면 레이어로 자동 전환([200] 관행) public int SelectedLeftTabIndex { @@ -203,7 +214,6 @@ internal sealed class MainViewModel : ViewModelBase OnSaveFile(saveAs: false); }); SaveAsFileCommand = new Command((sender, e) => OnSaveFile(saveAs: true)); - OpenFromDbCommand = new Command((sender, e) => OnOpenFromDb()); SaveToDbCommand = new Command((sender, e) => OnSaveToDb()); ExportJsonCommand = new Command((sender, e) => OnExportJson()); ImportJsonCommand = new Command((sender, e) => OnImportJson()); @@ -252,7 +262,6 @@ internal sealed class MainViewModel : ViewModelBase public ICustomCommand? SaveAsFileCommand { get; set; } /// DB에서 서식 열기(검색 대화상자) - public ICustomCommand? OpenFromDbCommand { get; set; } /// DB에 저장(E_SdgMst 버저닝 + E_SctMst 재생성) — SaveMode 게이트 public ICustomCommand? SaveToDbCommand { get; set; } @@ -374,9 +383,20 @@ internal sealed class MainViewModel : ViewModelBase { continue; } - if (!(designer.IsFromDb ? OnSaveToDb() : OnSaveFile(saveAs: false))) + if (OnSaveToDb()) { - return false; // 저장 대화상자 취소 또는 오류 — 종료를 중단한다 + continue; + } + // 저장이 실패했다 — 여기서 무조건 종료를 막으면 앱을 닫을 수 없는 상태가 된다. + // 저장은 DB 로만 가고, SaveMode=File 단말이나 접속 불가 상태에서는 갈 곳이 없기 때문이다. + // 전에는 파일 저장 폴백이 있어서 이 분기가 드물었지만 이제는 흔한 경로다. + // 그러니 막지 말고 버리고 닫을지를 묻는다 — 사용자가 갇히는 것이 더 나쁘다. + if (!DialogService.Confirm("종료", + $"'{designer.DisplayName}' 을(를) 저장하지 못했습니다.", + "변경을 버리고 종료할까요? 저장하려면 '취소'를 누르고 원인을 확인하세요.", + yes: "버리고 종료", no: "취소", destructive: true)) + { + return false; } } return true; @@ -571,29 +591,15 @@ internal sealed class MainViewModel : ViewModelBase } } - private void OnOpenFromDb() - { - try - { - if (!EnsureDb()) - { - return; - } - var dialog = new Views.SheetOpenDialogView(keyword => dataBusiness.ListSheets(keyword)) - { - Owner = Application.Current.MainWindow, - }; - if (dialog.ShowDialog() != true || dialog.SelectedSheet is null) - { - return; - } - OpenDbSheet(dialog.SelectedSheet.ShtCod); - } - catch (Exception ex) - { - DialogService.Notify(DialogKind.Error, "오류", "DB에서 서식을 여는 중 오류가 발생했습니다.", ex.Message); - } - } + // 'DB에서 열기' 대화상자(680×560)를 지웠다. + // + // 좌측 '서식 목록' 탭이 같은 조회를 상시로 한다(둘 다 ListSheets, WHERE·ORDER BY·상한 동일). + // 다만 같은 조회가 곧 같은 쓸모는 아니라, 그 창이 덮고 있던 구멍 넷을 먼저 좌측에 옮겼다: + // ① 저장·신규 등록 뒤 재조회 — 이게 없어서 방금 저장한 서식이 목록에 안 떴다(가장 컸다) + // ② 서식명 툴팁 — 좌측은 폭이 148px 남짓이라 긴 이름을 볼 방법이 없었다 + // ③ '디자인 없음'과 '서식생성기 사용 꺼짐'이 같은 회색이던 것을 툴팁으로 갈랐다 + // ④ Enter 로 열기 — 좌측은 더블클릭 전용이라 키보드로는 열 수 없었다 + // Ctrl+O 도 여기로 온다(전에는 OS 파일 대화상자였다). /// /// DB 서식 열기 공통 — 이미 열려 있으면 탭 활성화. @@ -727,6 +733,13 @@ internal sealed class MainViewModel : ViewModelBase // 그때마다 화면 전체가 어두워졌다(스크림). 실패는 계속 모달로 알린다 — // 그건 진행이 정의되지 않는 상태이고, 조용히 넘기면 저장된 줄 알고 넘어간다. StatusText = $"DB 저장 완료: {document.FormId} → SdgKey {sdgKey}"; + // 좌측 목록을 다시 읽는다. + // + // 목록은 기동 때 한 번과 검색 버튼으로만 채워졌고, 저장·신규 등록 뒤에 갱신되지 않았다. + // 그래서 방금 저장한 서식이 목록에 안 떴다 — 지금까지 그 구멍을 + // 'DB에서 열기' 대화상자가 덮고 있었다(그 창은 열 때마다 재조회했다). + // 그 창을 지우려면 여기가 먼저 메워져야 한다. + _ = LoadSheetListAsync(); return true; } catch (Exception ex) diff --git a/src/SheetMe.Designer/Views/MainView.xaml b/src/SheetMe.Designer/Views/MainView.xaml index 2deacad..44950ce 100644 --- a/src/SheetMe.Designer/Views/MainView.xaml +++ b/src/SheetMe.Designer/Views/MainView.xaml @@ -26,9 +26,8 @@ --> - + - - + - - - - - - - - + @@ -348,6 +350,7 @@ @@ -384,7 +387,8 @@ VirtualizingPanel.VirtualizationMode="Recycling" ScrollViewer.HorizontalScrollBarVisibility="Disabled" MouseDoubleClick="OnSheetListDoubleClick" - ToolTip="분류 머리글을 눌러 펼치고, 서식을 더블클릭해 엽니다"> + PreviewKeyDown="OnSheetListKeyDown" + ToolTip="분류 머리글을 눌러 펼치고, 서식을 더블클릭하거나 Enter 로 엽니다"> @@ -464,17 +468,28 @@ - + + @@ -729,9 +744,8 @@ Text="새 서식을 만들거나, 왼쪽 서식 목록에서 기존 서식을 두 번 눌러 엽니다."/> private void OnWindowPreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) { - if (e.Key != System.Windows.Input.Key.F4) + 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; } - Inspector.FocusFirstRow(); + 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; } diff --git a/src/SheetMe.Designer/Views/SheetOpenDialogView.xaml b/src/SheetMe.Designer/Views/SheetOpenDialogView.xaml deleted file mode 100644 index 6824433..0000000 --- a/src/SheetMe.Designer/Views/SheetOpenDialogView.xaml +++ /dev/null @@ -1,38 +0,0 @@ - - - -