Ctrl+Enter 로도 검증이 돌게 했다(F5 는 그대로 남는다). ■ PreviewKeyDown 대신 KeyBinding 으로 처음에는 PreviewKeyDown 에서 Keyboard.Modifiers 를 읽어 처리했는데, 그 경로는 검사할 수가 없다 — Keyboard.Modifiers 는 <b>실제 키보드 상태</b>에서 나오므로 합성 키 이벤트로는 Ctrl 이 잡히지 않는다. 진단을 쓰다 이걸 발견했고(검사 2건이 계속 실패했다), 단축키를 Window.InputBindings 선언으로 옮겼다. 이제 진단이 등록된 KeyBinding 을 찾아 그 명령을 직접 실행해 배선을 확인한다. ■ 자동완성 Enter 와 뜻이 갈린다 목록이 떠 있을 때 Enter 는 후보 확정이다. Ctrl+Enter 가 그 처리에 먼저 걸리면 검증이 안 돈다. PreviewKeyDown 의 목록 처리에서 Ctrl+Enter 만 비켜 가게 하고, 실행할 때 목록을 닫는다. --query-popup 진단에 4건 추가(Ctrl+Enter 등록 · F5 유지 · 실행됨 · 실행하며 목록 닫힘). 현재 16/16. 회귀: 테스트 230/230, 편집 스모크 실패 0, 팝업·정렬·결과표·단축키 점검 16/16, DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
331 lines
22 KiB
XML
331 lines
22 KiB
XML
<Window x:Class="SheetMe.Designer.Views.QueryEditorWindow"
|
||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||
xmlns:ctl="clr-namespace:SheetMe.Designer.Controls"
|
||
Title="쿼리 편집" Width="1180" Height="760" MinWidth="880" MinHeight="520"
|
||
WindowStartupLocation="CenterOwner" ShowInTaskbar="False"
|
||
Style="{StaticResource ThemedWindow}">
|
||
<Window.Resources>
|
||
<!-- 처음 펼쳐 둘 그룹 판단 — 가장 많이 쓰는 '환자'와 이 서식의 컨트롤만 펴 둔다 -->
|
||
<ctl:QueryGroupExpandedConverter x:Key="GroupExpanded"/>
|
||
|
||
<!-- 카드: 두 영역을 눈으로 갈라 준다 -->
|
||
<Style x:Key="Card" TargetType="Border">
|
||
<Setter Property="CornerRadius" Value="10"/>
|
||
<Setter Property="BorderThickness" Value="1"/>
|
||
<Setter Property="BorderBrush" Value="{DynamicResource B.InputBorder}"/>
|
||
<Setter Property="Background" Value="{DynamicResource B.Surface}"/>
|
||
</Style>
|
||
|
||
<Style x:Key="CardTitle" TargetType="TextBlock">
|
||
<Setter Property="FontSize" Value="13.5"/>
|
||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||
<Setter Property="Foreground" Value="{DynamicResource B.Ink}"/>
|
||
</Style>
|
||
|
||
<!-- 편집기 툴바 버튼 -->
|
||
<Style x:Key="ToolBtn" TargetType="Button" BasedOn="{StaticResource Subtle}">
|
||
<Setter Property="Padding" Value="8,3"/>
|
||
<Setter Property="FontSize" Value="11.5"/>
|
||
<Setter Property="Foreground" Value="{DynamicResource B.Ink}"/>
|
||
</Style>
|
||
|
||
<!-- 갈래 칩 -->
|
||
<Style x:Key="BucketChip" TargetType="ToggleButton">
|
||
<Setter Property="FontSize" Value="11.5"/>
|
||
<Setter Property="Padding" Value="10,3"/>
|
||
<Setter Property="Margin" Value="0,0,6,0"/>
|
||
<Setter Property="Cursor" Value="Hand"/>
|
||
<Setter Property="Foreground" Value="{DynamicResource B.Ink}"/>
|
||
<Setter Property="Template">
|
||
<Setter.Value>
|
||
<ControlTemplate TargetType="ToggleButton">
|
||
<Border x:Name="chip" CornerRadius="12" Padding="{TemplateBinding Padding}"
|
||
Background="{DynamicResource B.Input}"
|
||
BorderBrush="{DynamicResource B.InputBorder}" BorderThickness="1">
|
||
<ContentPresenter VerticalAlignment="Center"/>
|
||
</Border>
|
||
<ControlTemplate.Triggers>
|
||
<Trigger Property="IsChecked" Value="True">
|
||
<Setter TargetName="chip" Property="Background" Value="{DynamicResource B.Accent}"/>
|
||
<Setter TargetName="chip" Property="BorderBrush" Value="{DynamicResource B.Accent}"/>
|
||
<Setter Property="Foreground" Value="{DynamicResource B.OnAccent}"/>
|
||
</Trigger>
|
||
<Trigger Property="IsMouseOver" Value="True">
|
||
<Setter TargetName="chip" Property="BorderBrush" Value="{DynamicResource B.Accent}"/>
|
||
</Trigger>
|
||
</ControlTemplate.Triggers>
|
||
</ControlTemplate>
|
||
</Setter.Value>
|
||
</Setter>
|
||
</Style>
|
||
</Window.Resources>
|
||
|
||
<!-- 검증 실행 단축키 — 선언으로 두면 실제 배선을 진단이 그대로 확인할 수 있다.
|
||
PreviewKeyDown 에서 손으로 Keyboard.Modifiers 를 읽으면 그 경로를 검사할 방법이 없다. -->
|
||
<Window.InputBindings>
|
||
<KeyBinding Modifiers="Control" Key="Return" Command="{Binding TrialCommand}"/>
|
||
<KeyBinding Key="F5" Command="{Binding TrialCommand}"/>
|
||
</Window.InputBindings>
|
||
|
||
<DockPanel Margin="16,14,16,14">
|
||
<!-- ── 머리말 ── -->
|
||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,0,0,12">
|
||
<TextBlock x:Name="TitleTextBlock" Text="쿼리 편집" FontSize="17" FontWeight="Bold"
|
||
Foreground="{DynamicResource B.Ink}" VerticalAlignment="Center"/>
|
||
<Border x:Name="TargetChip" CornerRadius="6" Padding="8,2" Margin="10,0,0,0"
|
||
VerticalAlignment="Center"
|
||
Background="{DynamicResource B.Input}" BorderBrush="{DynamicResource B.InputBorder}"
|
||
BorderThickness="1">
|
||
<TextBlock x:Name="TargetChipText" FontSize="11.5" FontFamily="D2Coding, 굴림체, Consolas"
|
||
Foreground="{DynamicResource B.AccentText}"/>
|
||
</Border>
|
||
</StackPanel>
|
||
|
||
<!-- ── 아래 버튼 줄 ── -->
|
||
<DockPanel DockPanel.Dock="Bottom" Margin="0,12,0,0">
|
||
<TextBlock x:Name="ProblemText" VerticalAlignment="Center" Margin="0,0,12,0"
|
||
Foreground="{DynamicResource B.Danger}" FontSize="11.5"
|
||
TextTrimming="CharacterEllipsis"/>
|
||
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
|
||
<Button x:Name="TrialButton" Padding="14,6" Click="OnTrial"
|
||
ToolTip="DB 에서 실제로 돌려 봅니다 (Ctrl+Enter 또는 F5). 조회만 실행되며 표본 몇 행만 가져옵니다.">
|
||
<StackPanel Orientation="Horizontal">
|
||
<TextBlock Text="▶" FontSize="10" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||
<TextBlock Text="검증 실행" VerticalAlignment="Center"/>
|
||
</StackPanel>
|
||
</Button>
|
||
<Button Content="확인" Padding="24,6" Margin="8,0,0,0"
|
||
Style="{StaticResource Primary}" Click="OnConfirm" IsDefault="True"/>
|
||
<Button Content="취소" Padding="20,6" Margin="8,0,0,0" IsCancel="True"/>
|
||
</StackPanel>
|
||
</DockPanel>
|
||
|
||
<Grid>
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition x:Name="SplitterColumn" Width="12"/>
|
||
<ColumnDefinition x:Name="VariableColumn" Width="430"/>
|
||
</Grid.ColumnDefinitions>
|
||
|
||
<!-- ══ SQL 편집기 카드 ══ -->
|
||
<Border Grid.Column="0" Style="{StaticResource Card}">
|
||
<DockPanel Margin="14,12,14,12">
|
||
<TextBlock DockPanel.Dock="Top" Text="SQL 편집기" Style="{StaticResource CardTitle}"
|
||
Margin="0,0,0,10"/>
|
||
|
||
<!-- 툴바 -->
|
||
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,8">
|
||
<Button DockPanel.Dock="Right" Style="{StaticResource ToolBtn}" Click="OnClearAll"
|
||
Foreground="{DynamicResource B.Danger}" Content="전체 지우기"
|
||
ToolTip="편집 내용을 모두 지웁니다"/>
|
||
<StackPanel Orientation="Horizontal">
|
||
<Button Style="{StaticResource ToolBtn}" Content="포맷" Click="OnFormat"
|
||
ToolTip="예약어를 대문자로 바꾸고 절마다 줄을 나눕니다 (Ctrl+Shift+F). 문자열·치환 변수는 건드리지 않습니다."/>
|
||
<Button Style="{StaticResource ToolBtn}" Content="주석" Click="OnToggleComment"
|
||
ToolTip="선택한 줄에 -- 를 붙이거나 뗍니다 (Ctrl+/)"/>
|
||
<Button Style="{StaticResource ToolBtn}" Content="자동완성" Click="OnInvokeCompletion"
|
||
ToolTip="커서 위치에서 후보를 띄웁니다 (Ctrl+Space)"/>
|
||
</StackPanel>
|
||
</DockPanel>
|
||
|
||
<!-- 상태 줄 -->
|
||
<Border DockPanel.Dock="Bottom" Margin="0,8,0,0" Padding="10,5" CornerRadius="6"
|
||
Background="{DynamicResource B.Input}">
|
||
<DockPanel>
|
||
<TextBlock DockPanel.Dock="Right" x:Name="CaretText" FontSize="11"
|
||
Foreground="{DynamicResource B.Muted}"/>
|
||
<StackPanel Orientation="Horizontal">
|
||
<Ellipse x:Name="ConnDot" Width="7" Height="7" VerticalAlignment="Center"
|
||
Margin="0,0,6,0" Fill="{DynamicResource B.Muted}"/>
|
||
<TextBlock x:Name="ConnText" FontSize="11" VerticalAlignment="Center"
|
||
Foreground="{DynamicResource B.Muted}"/>
|
||
<TextBlock Text="│" FontSize="11" Margin="10,0" VerticalAlignment="Center"
|
||
Foreground="{DynamicResource B.InputBorder}"/>
|
||
<TextBlock x:Name="LengthText" FontSize="11" VerticalAlignment="Center"
|
||
Foreground="{DynamicResource B.Muted}"/>
|
||
</StackPanel>
|
||
</DockPanel>
|
||
</Border>
|
||
|
||
<!-- 검증 결과 -->
|
||
<Border x:Name="TrialPane" DockPanel.Dock="Bottom" Visibility="Collapsed" Margin="0,8,0,0"
|
||
MaxHeight="260" CornerRadius="8" BorderThickness="1"
|
||
BorderBrush="{DynamicResource B.InputBorder}" Background="{DynamicResource B.Input}">
|
||
<DockPanel Margin="10,8">
|
||
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,6">
|
||
<Button DockPanel.Dock="Right" Content="닫기" Padding="8,1"
|
||
Style="{StaticResource ToolBtn}" Click="OnCloseTrial"/>
|
||
<TextBlock x:Name="TrialSummary" FontSize="11.5" TextWrapping="Wrap"/>
|
||
</DockPanel>
|
||
<DataGrid x:Name="TrialGrid" AutoGenerateColumns="True" IsReadOnly="True"
|
||
HeadersVisibility="Column" FontSize="11.5" MaxHeight="200"
|
||
GridLinesVisibility="Horizontal" Background="Transparent"
|
||
AutomationProperties.Name="검증 결과 표본"/>
|
||
</DockPanel>
|
||
</Border>
|
||
|
||
<!-- 편집 영역: 색칠 레이어 + 투명 TextBox -->
|
||
<Border BorderThickness="1" CornerRadius="8" ClipToBounds="True"
|
||
BorderBrush="{DynamicResource B.InputBorder}" Background="{DynamicResource B.Input}">
|
||
<Grid>
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="Auto"/>
|
||
<ColumnDefinition Width="*"/>
|
||
</Grid.ColumnDefinitions>
|
||
<Border Grid.Column="0" Background="{DynamicResource B.Surface}" Padding="8,6">
|
||
<TextBlock x:Name="LineNumbers" FontFamily="D2Coding, 굴림체, Consolas" FontSize="13"
|
||
Foreground="{DynamicResource B.Muted}" TextAlignment="Right" MinWidth="24"/>
|
||
</Border>
|
||
<Grid Grid.Column="1">
|
||
<ctl:SqlHighlightLayer x:Name="Highlight" IsHitTestVisible="False"/>
|
||
<!-- StaysOpen=False 여야 바깥을 클릭했을 때 닫힌다 -->
|
||
<Popup x:Name="CompletionPopup" Placement="Relative" AllowsTransparency="True"
|
||
StaysOpen="False" PopupAnimation="None" Focusable="False">
|
||
<Border Background="{DynamicResource B.Surface}" BorderThickness="1"
|
||
BorderBrush="{DynamicResource B.InputBorder}" CornerRadius="8" Padding="3">
|
||
<ListBox x:Name="CompletionList" MaxHeight="260" MinWidth="280" FontSize="12"
|
||
BorderThickness="0" Background="Transparent"
|
||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||
MouseDoubleClick="OnCompletionPicked">
|
||
<ListBox.ItemTemplate>
|
||
<DataTemplate>
|
||
<DockPanel MinWidth="260">
|
||
<TextBlock DockPanel.Dock="Right" Text="{Binding KindLabel}" FontSize="10"
|
||
Foreground="{DynamicResource B.Muted}" Margin="10,0,0,0"
|
||
VerticalAlignment="Center"/>
|
||
<TextBlock Text="{Binding Text}" FontFamily="D2Coding, 굴림체, Consolas"
|
||
TextTrimming="CharacterEllipsis"/>
|
||
</DockPanel>
|
||
</DataTemplate>
|
||
</ListBox.ItemTemplate>
|
||
</ListBox>
|
||
</Border>
|
||
</Popup>
|
||
<TextBox x:Name="SqlBox"
|
||
FontFamily="D2Coding, 굴림체, Consolas" FontSize="13"
|
||
AcceptsReturn="True" AcceptsTab="False"
|
||
TextWrapping="NoWrap" Padding="8,6"
|
||
Background="Transparent" BorderThickness="0"
|
||
Foreground="Transparent"
|
||
CaretBrush="{DynamicResource B.Ink}"
|
||
SelectionBrush="{DynamicResource B.Accent}" SelectionOpacity="0.35"
|
||
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
|
||
TextChanged="OnSqlChanged" PreviewKeyDown="OnSqlKeyDown"
|
||
SelectionChanged="OnSqlSelectionChanged" LostFocus="OnSqlLostFocus"
|
||
AutomationProperties.Name="SQL 편집"/>
|
||
</Grid>
|
||
</Grid>
|
||
</Border>
|
||
</DockPanel>
|
||
</Border>
|
||
|
||
<GridSplitter Grid.Column="1" Style="{StaticResource ColSplitter}"/>
|
||
|
||
<!-- ══ 치환 변수 카드 ══ -->
|
||
<Border x:Name="VariablePane" Grid.Column="2" Style="{StaticResource Card}">
|
||
<DockPanel Margin="14,12,14,12">
|
||
<TextBlock DockPanel.Dock="Top" Text="치환 변수" Style="{StaticResource CardTitle}"
|
||
Margin="0,0,0,10"/>
|
||
|
||
<!-- 검색: 돋보기를 겹쳐 둔다 — 빈 상자만 있으면 무엇을 하는 칸인지 안 읽힌다 -->
|
||
<Grid DockPanel.Dock="Top" Margin="0,0,0,8">
|
||
<TextBox x:Name="VariableSearch" Padding="28,4,8,4"
|
||
VerticalContentAlignment="Center" TextChanged="OnVariableSearchChanged"
|
||
AutomationProperties.Name="치환 변수 검색"
|
||
ToolTip="변수명·설명·초성으로 찾습니다 (예: ㅊㅌㅂㅎ → 차트번호)"/>
|
||
<TextBlock Text="🔍" FontSize="11" Margin="9,0,0,0" IsHitTestVisible="False"
|
||
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||
Foreground="{DynamicResource B.Muted}"/>
|
||
<TextBlock x:Name="SearchPlaceholder" Text="변수명 또는 설명 검색" FontSize="11.5"
|
||
Margin="30,0,0,0" IsHitTestVisible="False"
|
||
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||
Foreground="{DynamicResource B.Muted}"/>
|
||
</Grid>
|
||
|
||
<!-- 갈래 칩 -->
|
||
<ItemsControl x:Name="BucketChips" DockPanel.Dock="Top" Margin="0,0,0,8">
|
||
<ItemsControl.ItemsPanel>
|
||
<ItemsPanelTemplate><WrapPanel/></ItemsPanelTemplate>
|
||
</ItemsControl.ItemsPanel>
|
||
<ItemsControl.ItemTemplate>
|
||
<DataTemplate>
|
||
<ToggleButton Style="{StaticResource BucketChip}"
|
||
IsChecked="{Binding IsActive, Mode=OneWay}"
|
||
Tag="{Binding Name}" Click="OnBucketClick">
|
||
<StackPanel Orientation="Horizontal">
|
||
<TextBlock Text="{Binding Name}"/>
|
||
<TextBlock Text="{Binding Count}" Margin="6,0,0,0" Opacity="0.75"/>
|
||
</StackPanel>
|
||
</ToggleButton>
|
||
</DataTemplate>
|
||
</ItemsControl.ItemTemplate>
|
||
</ItemsControl>
|
||
|
||
<!-- 안내 -->
|
||
|
||
<ListBox x:Name="VariableList" FontSize="11.5" MouseDoubleClick="OnInsertVariable"
|
||
KeyDown="OnVariableListKeyDown"
|
||
BorderThickness="0" Background="Transparent"
|
||
HorizontalContentAlignment="Stretch"
|
||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||
AutomationProperties.Name="치환 변수 목록">
|
||
<ListBox.ItemTemplate>
|
||
<DataTemplate>
|
||
<DockPanel Margin="0,3" ToolTip="{Binding Token}">
|
||
<!-- ⊕ 로 바로 삽입 — 더블클릭을 모르는 사람도 넣을 수 있어야 한다 -->
|
||
<Button DockPanel.Dock="Right" Width="22" Height="22" Padding="0"
|
||
Margin="8,0,0,0" Style="{StaticResource ToolBtn}"
|
||
Content="+" FontSize="13"
|
||
Foreground="{DynamicResource B.AccentText}"
|
||
Command="{Binding DataContext.InsertCommand,
|
||
RelativeSource={RelativeSource AncestorType=ListBox}}"
|
||
CommandParameter="{Binding}"
|
||
ToolTip="커서 위치에 삽입"/>
|
||
<TextBlock DockPanel.Dock="Right" Text="{Binding ShortForm}"
|
||
Foreground="{DynamicResource B.Muted}" FontSize="10.5"
|
||
FontFamily="D2Coding, 굴림체, Consolas"
|
||
VerticalAlignment="Center" Margin="8,0,0,0"/>
|
||
<TextBlock Text="{Binding Description}" FontFamily="Segoe UI, 맑은 고딕" FontSize="12"
|
||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center"/>
|
||
</DockPanel>
|
||
</DataTemplate>
|
||
</ListBox.ItemTemplate>
|
||
<ListBox.GroupStyle>
|
||
<GroupStyle>
|
||
<GroupStyle.ContainerStyle>
|
||
<Style TargetType="GroupItem">
|
||
<Setter Property="Template">
|
||
<Setter.Value>
|
||
<ControlTemplate TargetType="GroupItem">
|
||
<Expander IsExpanded="{Binding Name,
|
||
Converter={StaticResource GroupExpanded}, Mode=OneTime}"
|
||
Margin="0,4,0,0">
|
||
<Expander.Header>
|
||
<DockPanel MinWidth="330">
|
||
<Border DockPanel.Dock="Right" CornerRadius="9" Padding="6,1"
|
||
VerticalAlignment="Center" Margin="6,0,0,0"
|
||
Background="{DynamicResource B.Input}">
|
||
<TextBlock Text="{Binding ItemCount}" FontSize="10"
|
||
Foreground="{DynamicResource B.Muted}"/>
|
||
</Border>
|
||
<TextBlock Text="{Binding Name}" FontWeight="SemiBold"
|
||
FontFamily="Segoe UI, 맑은 고딕" FontSize="12"/>
|
||
</DockPanel>
|
||
</Expander.Header>
|
||
<ItemsPresenter Margin="8,2,0,6"/>
|
||
</Expander>
|
||
</ControlTemplate>
|
||
</Setter.Value>
|
||
</Setter>
|
||
</Style>
|
||
</GroupStyle.ContainerStyle>
|
||
</GroupStyle>
|
||
</ListBox.GroupStyle>
|
||
</ListBox>
|
||
</DockPanel>
|
||
</Border>
|
||
</Grid>
|
||
</DockPanel>
|
||
</Window>
|