ksj09406 b717f4ed06 전체 반영 · 자동 병합의 주석을 구간마다 한 줄로 (사용자 요청 · 결정)
[전체 반영] 은 줄마다 Apply 를 따로 불러 바뀐 줄마다 머지 주석이 붙고 Undo 도
줄마다 한 걸음이었다. [구간 반영] 처럼 구간마다 주석 한 줄로 바꾼다.

- 두 파일 비교 [전체 반영]: 반영할 수 있는 줄이 이어진 구간(BlockAround 와 같은
  기준)을 먼저 나눠 구간마다 ApplyGroup. 지우는 반영은 "삭제 N줄". 모든 구간을 한
  묶음 번호로 넘겨 Undo 한 번에 전체가 돌아간다. ApplyGroup 에 group · finish 를 더함.
- 3-Way [전체 반영] 은 끈다(사용자 결정) - 차이 나는 모든 줄을 공통 조상(BASE)으로
  바꾸던 동작이라 병합에서 쓸 데가 없고 양쪽 작업을 지운다. 꺼져도 툴팁으로 까닭.
- 3-Way [자동 병합] 도 저쪽만 고친 줄 구간마다 주석 한 줄 · Undo 한 번. 사이에 낀
  충돌 · 내가 고친 줄은 구간을 끊고 그대로 둔다.

시험: 빌드 경고 0 · 오류 0. 화면 g22-accept-all(새) 주석 7 -> 3 · 삭제 2줄 ·
BASE 와 같음 · Undo 한 번, g23-three-way(새, --merge) 전체 반영 꺼짐 · 주석 5 -> 2 ·
내 줄 · 충돌 줄 그대로 · Undo 한 번. 실패 0. 기존 회귀는 사용자 지시로 돌리지 않았다.

Agent: main (claude-opus-5-5)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:24:07 +09:00

2126 lines
151 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<UserControl x:Class="MergeHub.Views.HomeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MergeHub.Views"
xmlns:ctl="clr-namespace:MergeHub.Views.Controls"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:models="clr-namespace:MergeHub.Models"
xmlns:shell="clr-namespace:UbisamBase.Core.Shell;assembly=UbisamBase.Core"
shell:PlatformChrome.FullBleed="True"
mc:Ignorable="d"
PreviewKeyDown="View_PreviewKeyDown"
AllowDrop="True" DragOver="View_DragOver" Drop="View_Drop">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/MergeHub;component/Views/Controls/AppStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!-- BASE / LOCAL 머리 영역 공통 높이. 두 패널의 줄이 같은 눈높이에 오게 하는 기준값. -->
<system:Double x:Key="PaneHeaderHeight">124</system:Double>
<!-- 코드 한 줄의 높이. BASE/LOCAL 이 이 값을 똑같이 써야 두 패널의 같은 행이 같은 눈높이에 온다. -->
<system:Double x:Key="CodeLineHeight">16</system:Double>
<!-- 코드 글자 크기와 그에 따르는 여백(11차 F-5). 기본 12 의 값이다 - 화면(ApplyCodeFont)이
CodeFontMetrics 로 함께 바꾼다. DynamicResource 로 읽어야 바뀐 값이 바로 그려진다. -->
<system:Double x:Key="CodeFontSize">12</system:Double>
<!-- 칸 사이 잇는 선 자리 폭(11차 F-10) -->
<GridLength x:Key="LinkGapWidth">48</GridLength>
<GridLength x:Key="GutterWidth">66</GridLength>
<GridLength x:Key="GutterNumberWidth">46</GridLength>
<GridLength x:Key="GutterNarrowNumberWidth">40</GridLength>
<GridLength x:Key="GutterMarkWidth">6</GridLength>
<GridLength x:Key="GutterButtonWidth">20</GridLength>
<!-- 두 파일 비교에서만 나오는 [&lt;] 단추 자리(12차 B-1). Auto 로 두면 단추가 있는 줄만
글이 오른쪽으로 밀려, 같은 칸 안에서도 줄마다 글 시작 자리가 달랐다(2026-09-28 사용자 보고).
모드가 바뀔 때 화면이 0 ↔ 단추 폭으로 갈아 끼운다(ApplyGutterReserve). -->
<GridLength x:Key="GutterBothWaysWidth">0</GridLength>
<system:Double x:Key="GutterButtonHeight">14</system:Double>
<system:Double x:Key="GutterButtonFontSize">9</system:Double>
<system:Double x:Key="BookmarkHeight">10</system:Double>
<Thickness x:Key="EditorGutterMargin">66,0,0,0</Thickness>
<!-- BASE / LOCAL 하단 요약 칸 공통 높이. 두 패널의 코드 영역이 같은 크기여야 나란히 읽힌다. -->
<system:Double x:Key="PaneFooterHeight">54</system:Double>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<ctl:LineHighlightBrushConverter x:Key="HighlightBrush"/>
<!-- 편집 칸 바탕: 밝음 #FAFAFA · 어두움 #252830(2026-09-30 사용자 선택). PaneBackgroundConverter 참고. -->
<ctl:PaneBackgroundConverter x:Key="PaneBackground"/>
<ctl:HighlightNameToBrushConverter x:Key="HighlightByName"/>
<ctl:TextToVisibilityConverter x:Key="TextToVis"/>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Margin" Value="2,0"/>
</Style>
<!-- 범례의 색 칩. 코드 패널의 줄 배경과 정확히 같은 브러시를 쓴다. -->
<Style x:Key="LegendChip" TargetType="Border">
<Setter Property="Width" Value="14"/>
<Setter Property="Height" Value="14"/>
<Setter Property="Margin" Value="0,0,4,0"/>
<Setter Property="CornerRadius" Value="2"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="{DynamicResource Ubisam.Brush.Input.Border}"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<ctl:ZeroToDimConverter x:Key="ZeroToDim"/>
<!--
범례 한 칸(4차 C-4). 읽기만 하던 설명을 <b>누르면 그 갈래로 가는 도구</b>로 바꿨다.
단추처럼 보이지 않게 테두리와 배경을 지운다 - 줄이 단추 열 개로 보이면 시끄럽다.
대신 손 모양 커서와 마우스를 올렸을 때의 옅은 배경으로 "누르는 것"임을 알린다.
-->
<Style x:Key="LegendButton" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="3,1"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="MinWidth" Value="0"/>
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="Back" Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}" CornerRadius="3">
<ContentPresenter VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Back" Property="Background"
Value="{DynamicResource Ubisam.Brush.Sidebar.Background}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="LegendText" TargetType="TextBlock">
<Setter Property="FontSize" Value="11"/>
<Setter Property="Margin" Value="0,0,3,0"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<!-- 범례에 붙는 실시간 개수. 줄 하나만 반영해도 바로 바뀐다. -->
<Style x:Key="LegendCount" TargetType="TextBlock">
<Setter Property="FontSize" Value="11"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Margin" Value="0,0,14,0"/>
<Setter Property="MinWidth" Value="14"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<!-- 범례의 문법 예시. 실제 코드 패널과 같은 컨트롤이라 색이 어긋날 수 없다. -->
<Style x:Key="LegendCode" TargetType="ctl:CodeTextBlock">
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="FontSize" Value="11"/>
<Setter Property="Margin" Value="0,0,10,0"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="PanelTitle" TargetType="TextBlock">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="{DynamicResource Ubisam.Brush.Accent}"/>
</Style>
<Style x:Key="Muted" TargetType="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource Ubisam.Brush.Muted}"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="Mono" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="FontSize" Value="{DynamicResource CodeFontSize}"/>
</Style>
<!--
코드 줄 컨테이너. 평소에는 높이를 고정해 두 패널의 행이 1:1로 맞물리게 한다.
높이를 풀어 두면 컨테이너 자체 여백 때문에 20px 를 조금씩 넘어 아래로 갈수록 어긋난다.
워드랩일 때만 풀어 준다 - 그때는 줄마다 높이가 달라야 하기 때문이다.
-->
<Style x:Key="CodeLineItem" TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="Height" Value="{DynamicResource CodeLineHeight}"/>
<Setter Property="MinHeight" Value="{DynamicResource CodeLineHeight}"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.WordWrap,
RelativeSource={RelativeSource AncestorType=ListBox}}" Value="True">
<Setter Property="Height" Value="{x:Static system:Double.NaN}"/>
</DataTrigger>
</Style.Triggers>
</Style>
<!-- BASE 한 줄. 줄 배경 = 색 범례, 글자색 = C# 문법.
코드 본문에는 <b>툴팁을 달지 않는다</b> - 글이 이미 보이는데 그 위에 같은 글이 상자로
겹쳐 뜨면 가리기만 한다. 잘려서 안 보이는 부분은 가로 스크롤로 본다. -->
<DataTemplate x:Key="BaseLineTemplate">
<!-- 여백 폭(66)을 LOCAL 과 맞춘다. 접히는 자리가 같아야 워드랩에서도 행이 어긋나지 않는다.
66 인 까닭은 LocalLineTemplate 에 적었다(6차 B-4). -->
<Grid MinHeight="{Binding BaseRowHeight}">
<Grid.ColumnDefinitions>
<!-- [<] 자리. 단추가 없는 줄에서도 자리를 잡아 둔다 - Auto 로 두면 이 줄만 글이 밀린다. -->
<ColumnDefinition Width="{DynamicResource GutterBothWaysWidth}"/>
<ColumnDefinition Width="{DynamicResource GutterWidth}"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- [<] 이 줄을 왼쪽 파일로 반영(12차 B-1). 두 파일 비교에서만 보인다 -
Git 모드의 왼쪽은 지난 커밋이라 쓸 파일이 없다. -->
<Button Grid.Column="0" Content="&lt;" Padding="0" Margin="2,0,2,0" MinWidth="16"
Height="{DynamicResource GutterButtonHeight}" MinHeight="{DynamicResource GutterButtonHeight}"
FontSize="{DynamicResource GutterButtonFontSize}" VerticalAlignment="Top"
Command="{Binding DataContext.TakeLineToBaseCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
ToolTip="이 줄을 오른쪽 내용으로 왼쪽 파일에 반영">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding CanTakeBase}" Value="True"/>
<Condition Binding="{Binding DataContext.BothWays,
RelativeSource={RelativeSource AncestorType=UserControl}}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<!-- LOCAL 에서 지운 줄을 되살리는 [>] (11차 F-10). 예전에는 LOCAL 의 빈 줄(필러)에 달려 있었는데
이제 LOCAL 칸에는 그 줄이 없으므로 BASE 쪽 줄 끝에 단다(Araxis 처럼). LOCAL 에 있는 줄은 LOCAL 여백의 [>] 그대로. -->
<Button Grid.Column="3" Content="&gt;" Padding="0" Margin="2,0,2,0" MinWidth="16"
Height="{DynamicResource GutterButtonHeight}" MinHeight="{DynamicResource GutterButtonHeight}"
FontSize="{DynamicResource GutterButtonFontSize}" VerticalAlignment="Top"
Command="{Binding DataContext.TakeLineCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
ToolTip="LOCAL 에서 지운 이 줄을 되살리기 (BASE 내용으로 가져오기)">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding CanTakeBase}" Value="True"/>
<Condition Binding="{Binding HasLocal}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<TextBlock Grid.Column="1" Text="{Binding BaseNumberText}" Style="{StaticResource Mono}" LineHeight="{DynamicResource CodeLineHeight}" LineStackingStrategy="BlockLineHeight"
Foreground="{DynamicResource Ubisam.Brush.Muted}" TextAlignment="Right"
VerticalAlignment="Top" Margin="0,0,8,0"/>
<Border Grid.Column="2" Padding="2,0"
Background="{Binding Highlight, Converter={StaticResource HighlightBrush}}">
<Grid ClipToBounds="True">
<ctl:CodeTextBlock Code="{Binding BaseText}" Words="{Binding BaseWords}"
InBlockComment="{Binding BaseInComment}"
CodeLanguage="{Binding DataContext.Language,
RelativeSource={RelativeSource AncestorType=UserControl}}"
HighlightSyntax="{Binding DataContext.SyntaxHighlight,
RelativeSource={RelativeSource AncestorType=UserControl}}"
TextWrapping="{Binding WrapMode,
RelativeSource={RelativeSource AncestorType=UserControl}}"
FontFamily="Consolas" FontSize="{DynamicResource CodeFontSize}"
LineHeight="{DynamicResource CodeLineHeight}"
LineStackingStrategy="BlockLineHeight"
VerticalAlignment="Top"
SizeChanged="BaseText_SizeChanged">
<ctl:CodeTextBlock.RenderTransform>
<TranslateTransform X="{Binding SourceShiftX,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</ctl:CodeTextBlock.RenderTransform>
</ctl:CodeTextBlock>
</Grid>
</Border>
</Grid>
</DataTemplate>
<!--
LOCAL 원본 한 줄 (3-Way). 파일을 연 시점의 내 줄이라 편집하지 않는다.
여백의 [&lt;] 를 누르면 그 줄을 결과(MERGED)로 되돌린다 - "Accept Mine".
-->
<DataTemplate x:Key="OriginalLocalLineTemplate">
<Grid MinHeight="{Binding OriginalLocalRowHeight}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{DynamicResource GutterNumberWidth}"/>
<ColumnDefinition Width="{DynamicResource GutterButtonWidth}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding OriginalLocalNumberText}" Style="{StaticResource Mono}" LineHeight="{DynamicResource CodeLineHeight}" LineStackingStrategy="BlockLineHeight"
Foreground="{DynamicResource Ubisam.Brush.Muted}" TextAlignment="Right"
VerticalAlignment="Top" Margin="0,0,4,0"/>
<Button Grid.Column="1" Content="&lt;" Padding="0" Margin="0" MinWidth="16"
Height="{DynamicResource GutterButtonHeight}" MinHeight="{DynamicResource GutterButtonHeight}" FontSize="{DynamicResource GutterButtonFontSize}" VerticalAlignment="Top"
Visibility="{Binding CanTakeLocal, Converter={StaticResource BoolToVis}}"
Command="{Binding DataContext.TakeLocalLineCommand,
RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
ToolTip="이 줄을 내 원본으로 되돌리기"/>
<Border Grid.Column="2" Padding="2,0"
Background="{Binding Highlight, Converter={StaticResource HighlightBrush}}">
<Grid ClipToBounds="True">
<ctl:CodeTextBlock Code="{Binding OriginalLocalText}"
InBlockComment="{Binding LocalInComment}"
CodeLanguage="{Binding DataContext.Language,
RelativeSource={RelativeSource AncestorType=UserControl}}"
HighlightSyntax="{Binding DataContext.SyntaxHighlight,
RelativeSource={RelativeSource AncestorType=UserControl}}"
TextWrapping="{Binding WrapMode,
RelativeSource={RelativeSource AncestorType=UserControl}}"
FontFamily="Consolas" FontSize="{DynamicResource CodeFontSize}"
LineHeight="{DynamicResource CodeLineHeight}"
LineStackingStrategy="BlockLineHeight"
VerticalAlignment="Top"
SizeChanged="OriginalLocalText_SizeChanged">
<ctl:CodeTextBlock.RenderTransform>
<TranslateTransform X="{Binding SourceShiftX,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</ctl:CodeTextBlock.RenderTransform>
</ctl:CodeTextBlock>
</Grid>
</Border>
</Grid>
</DataTemplate>
<!--
REMOTE 한 줄 (3-Way). BASE 와 같은 모양이되, 여백에 [&lt;] 버튼이 붙는다 -
누르면 그 줄을 LOCAL 로 가져온다. 방향을 헷갈리지 않게 LOCAL 쪽 [&gt;] 와 화살표를 반대로 했다.
-->
<DataTemplate x:Key="RemoteLineTemplate">
<Grid MinHeight="{Binding RemoteRowHeight}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{DynamicResource GutterNumberWidth}"/>
<ColumnDefinition Width="{DynamicResource GutterButtonWidth}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding RemoteNumberText}" Style="{StaticResource Mono}" LineHeight="{DynamicResource CodeLineHeight}" LineStackingStrategy="BlockLineHeight"
Foreground="{DynamicResource Ubisam.Brush.Muted}" TextAlignment="Right"
VerticalAlignment="Top" Margin="0,0,4,0"/>
<Button Grid.Column="1" Content="&lt;" Padding="0" Margin="0" MinWidth="16"
Height="{DynamicResource GutterButtonHeight}" MinHeight="{DynamicResource GutterButtonHeight}" FontSize="{DynamicResource GutterButtonFontSize}" VerticalAlignment="Top"
Visibility="{Binding CanTakeRemote, Converter={StaticResource BoolToVis}}"
Command="{Binding DataContext.TakeRemoteLineCommand,
RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
ToolTip="이 줄을 내 작업으로 가져오기"/>
<Border Grid.Column="2" Padding="2,0"
Background="{Binding Highlight, Converter={StaticResource HighlightBrush}}">
<Grid ClipToBounds="True">
<ctl:CodeTextBlock Code="{Binding RemoteText}" Words="{Binding RemoteWords}"
InBlockComment="{Binding RemoteInComment}"
CodeLanguage="{Binding DataContext.Language,
RelativeSource={RelativeSource AncestorType=UserControl}}"
HighlightSyntax="{Binding DataContext.SyntaxHighlight,
RelativeSource={RelativeSource AncestorType=UserControl}}"
TextWrapping="{Binding WrapMode,
RelativeSource={RelativeSource AncestorType=UserControl}}"
FontFamily="Consolas" FontSize="{DynamicResource CodeFontSize}"
LineHeight="{DynamicResource CodeLineHeight}"
LineStackingStrategy="BlockLineHeight"
VerticalAlignment="Top"
SizeChanged="RemoteText_SizeChanged">
<ctl:CodeTextBlock.RenderTransform>
<TranslateTransform X="{Binding SourceShiftX,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</ctl:CodeTextBlock.RenderTransform>
</ctl:CodeTextBlock>
</Grid>
</Border>
</Grid>
</DataTemplate>
<!--
LOCAL 한 줄 = 여백(북마크·줄번호·[>]) + 본문(색 배경 + 문법 색).
여백과 본문을 한 행에 두는 것이 요점이다. 따로 두면 워드랩을 켰을 때
줄마다 높이가 달라져 여백과 본문이 어긋난다. 한 행이면 높이가 저절로 맞는다.
대신 가로로 밀 때 여백까지 밀리면 안 되므로, 본문 글자만
TextShiftX(편집기의 가로 스크롤 값)만큼 밀고 열 안에서 잘라낸다.
글자를 고치는 일은 이 위에 겹쳐 놓은 편집기(LocalEditor)가 맡는다.
여백 폭은 66 = 북마크 6 + 줄 번호 40 + [>] 20. 네 칸이 모두 66 이다(6차 B-4).
예전에는 54 = 10 + 24 + 20 이라 줄 번호 글자 자리가 20px(Consolas 12 로 세 자리)뿐이어서
2949 가 "294" 로 잘렸다. 위의 세 칸은 번호 자리가 30px 이상이라 멀쩡했다.
지금은 번호 자리 36px 로 다섯 자리(99,999줄)까지 들어간다.
-->
<DataTemplate x:Key="LocalLineTemplate">
<Grid Background="Transparent"
MinHeight="{Binding RowHeight}"
MouseLeftButtonDown="Gutter_MouseLeftButtonDown">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{DynamicResource GutterMarkWidth}"/>
<ColumnDefinition Width="{DynamicResource GutterNarrowNumberWidth}"/>
<ColumnDefinition Width="{DynamicResource GutterButtonWidth}"/>
<!-- [<] 자리(12차 B-1). BASE 와 같은 값이라 두 칸의 글 시작 자리가 늘 같다. -->
<ColumnDefinition Width="{DynamicResource GutterBothWaysWidth}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 북마크 표식. 켜진 줄만 색 막대가 보인다. -->
<Border Grid.Column="0" Width="4" HorizontalAlignment="Left" CornerRadius="1"
Margin="1,3" VerticalAlignment="Top" Height="{DynamicResource BookmarkHeight}"
Background="{DynamicResource Ubisam.Brush.Accent}"
Visibility="{Binding IsBookmarked, Converter={StaticResource BoolToVis}}"/>
<TextBlock Grid.Column="1" Text="{Binding LocalNumberText}" Style="{StaticResource Mono}" LineHeight="{DynamicResource CodeLineHeight}" LineStackingStrategy="BlockLineHeight"
Foreground="{DynamicResource Ubisam.Brush.Muted}" TextAlignment="Right"
VerticalAlignment="Top" Margin="0,0,4,0"
ToolTip="더블클릭하면 북마크를 켜고 끕니다 (F2 로 이동)"/>
<Button Grid.Column="2" Content="&gt;" Padding="0" Margin="0" MinWidth="16"
Height="{DynamicResource GutterButtonHeight}" MinHeight="{DynamicResource GutterButtonHeight}" FontSize="{DynamicResource GutterButtonFontSize}" VerticalAlignment="Top"
Visibility="{Binding CanTakeBase, Converter={StaticResource BoolToVis}}"
Command="{Binding DataContext.TakeLineCommand,
RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
ToolTip="이 줄만 BASE 내용으로 가져오기"/>
<!-- [<] 왼쪽에 없는 줄(오른쪽에서 새로 생긴 줄)을 왼쪽 파일에 넣는다(12차 B-1).
왼쪽에 있는 줄은 BASE 칸의 [<] 를 쓴다 - 거기 줄이 있으니 그 자리에 다는 것이 맞다. -->
<Button Grid.Column="3" Content="&lt;" Padding="0" Margin="0" MinWidth="16"
Height="{DynamicResource GutterButtonHeight}" MinHeight="{DynamicResource GutterButtonHeight}"
FontSize="{DynamicResource GutterButtonFontSize}" VerticalAlignment="Top"
Command="{Binding DataContext.TakeLineToBaseCommand,
RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
ToolTip="이 줄을 왼쪽 파일에 넣기">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding CanTakeBase}" Value="True"/>
<Condition Binding="{Binding HasBase}" Value="False"/>
<Condition Binding="{Binding DataContext.BothWays,
RelativeSource={RelativeSource AncestorType=UserControl}}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<!--
색 배경은 칸 전체를 채우고 제자리에 있는다. 미는 것은 글자뿐이다.
Padding 을 0 으로 두는 것이 중요하다. 문제 줄 표시용 왼쪽 테두리(2)도
자리를 차지하므로, BASE·REMOTE 의 Padding 2 와 합이 같아야 세 패널의 글자가
같은 x 에서 시작한다. 여기가 2 더 밀려 있으면 그 위에 겹친 편집기의 캐럿이
글자 가운데에 박힌 것처럼 보인다.
-->
<Border Grid.Column="4" Padding="0"
Background="{Binding Highlight, Converter={StaticResource HighlightBrush}}"
BorderThickness="2,0,0,0">
<Border.ToolTip>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="Problem"/>
<Binding Path="MoveHint"/>
</MultiBinding>
</Border.ToolTip>
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding HasProblem}" Value="True">
<Setter Property="BorderBrush" Value="{DynamicResource Ubisam.Brush.Danger}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid ClipToBounds="True">
<ctl:CodeTextBlock Code="{Binding LocalText}" Words="{Binding LocalWords}"
InBlockComment="{Binding LocalInComment}"
CodeLanguage="{Binding DataContext.Language,
RelativeSource={RelativeSource AncestorType=UserControl}}"
HighlightSyntax="{Binding DataContext.SyntaxHighlight,
RelativeSource={RelativeSource AncestorType=UserControl}}"
TextWrapping="{Binding WrapMode,
RelativeSource={RelativeSource AncestorType=UserControl}}"
FontFamily="Consolas" FontSize="{DynamicResource CodeFontSize}"
LineHeight="{DynamicResource CodeLineHeight}"
LineStackingStrategy="BlockLineHeight"
VerticalAlignment="Top"
SizeChanged="LocalText_SizeChanged">
<ctl:CodeTextBlock.RenderTransform>
<TranslateTransform X="{Binding TextShiftX,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</ctl:CodeTextBlock.RenderTransform>
</ctl:CodeTextBlock>
</Grid>
</Border>
</Grid>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<DockPanel Margin="2" TextElement.Foreground="{DynamicResource Ubisam.Brush.ContentForeground}">
<!-- ===== 문서 탭 줄 (12차 E-2) =====
짝 여러 개를 동시에 든다. 짝이 하나뿐일 때는 줄을 내주지 않는다(ShowTabStrip).
탭 이름은 파일 이름이고, 양쪽 이름이 다르면 "왼 ↔ 오". 저장하지 않은 탭에는 앞에 가운뎃점.
가운데 단추로도 닫힌다 - 브라우저와 같은 손버릇. -->
<!-- ItemsControl 이 아니라 ListBox 다 - 그쪽은 UIA 에 "컨트롤" 로 안 보여서 화면 시험이 탭 줄을
아예 못 읽고, 키로 탭을 옮기는 것도 공짜로 얻을 수 없다. 고르기가 곧 탭 옮기기(CurrentTab) 다. -->
<ListBox DockPanel.Dock="Top" x:Name="DocTabStrip"
ItemsSource="{Binding TabItems}"
SelectedItem="{Binding CurrentTab, Mode=TwoWay}"
Visibility="{Binding ShowTabStrip, Converter={StaticResource BoolToVis}}"
Margin="0,0,0,3" Padding="0" BorderThickness="0" Background="Transparent"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<!-- 고른 항목의 파란 바탕을 없앤다 - 탭 모양은 아래 DataTemplate 이 그린다.
이 스타일이 플랫폼 ListBoxItem 스타일을 대신하므로 글자색도 여기서 준다 - 없으면 WPF 기본(검정)이라
어두운 테마에서 탭 이름이 바탕에 묻혔다(12차 G-8 곁, 잉크 차이 157 · 116 으로 확인). -->
<Style TargetType="ListBoxItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Foreground" Value="{DynamicResource Ubisam.Brush.ContentForeground}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Border x:Name="TabShell" Margin="0,0,3,0" CornerRadius="3,3,0,0" BorderThickness="1,1,1,0"
Background="{DynamicResource Ubisam.Brush.Input.Background}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}"
MouseDown="DocTab_MouseDown"
ToolTip="{Binding Tip}">
<StackPanel Orientation="Horizontal" Height="24">
<!-- 저장하지 않은 표시. 이름이 움직이지 않게 자리를 늘 차지한다. -->
<TextBlock Text="•" Width="10" FontSize="13" FontWeight="Bold"
VerticalAlignment="Center" TextAlignment="Center" Margin="4,0,0,0"
Foreground="{DynamicResource Ubisam.Brush.Accent}"
Visibility="{Binding IsDirty, Converter={StaticResource BoolToVis}}"/>
<TextBlock Text="{Binding Title}" FontSize="11" VerticalAlignment="Center"
Margin="2,0,2,0" MaxWidth="240" TextTrimming="CharacterEllipsis"/>
<Button Content="×" FontSize="12" Width="18" Height="18" Padding="0" Margin="2,0,3,0"
VerticalAlignment="Center" Focusable="False"
Command="{Binding DataContext.CloseTabCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
ToolTip="이 탭을 닫습니다 (저장하지 않은 편집은 그대로 보관됩니다)"/>
</StackPanel>
</Border>
<DataTemplate.Triggers>
<!-- 떠 있는 탭은 본문과 같은 바탕 · 진한 테두리로 이어 보이게 한다. -->
<DataTrigger Binding="{Binding IsCurrent}" Value="True">
<Setter TargetName="TabShell" Property="Background"
Value="{DynamicResource Ubisam.Brush.ContentBackground}"/>
<Setter TargetName="TabShell" Property="BorderBrush"
Value="{DynamicResource Ubisam.Brush.Accent}"/>
<Setter TargetName="TabShell" Property="BorderThickness" Value="1,2,1,0"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<!-- 상태바. 패널 안이 아니라 창 아래 한 줄로 둔다(4차 C-2). -->
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- ===== 상단 상태 바 ===== -->
<Border Grid.Row="0" Padding="10,4" Margin="0,0,0,4"
Background="{DynamicResource Ubisam.Brush.Input.Background}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}" BorderThickness="1">
<DockPanel>
<!--
단축키 안내는 화면 오른쪽 위 끝에 둔다. 위 상태 바는 내용에 따라 줄이 늘었다
줄었다 하는 WrapPanel 이라 그 안에 넣으면 자리가 떠다닌다 - DockPanel 로
오른쪽에 붙박아 어느 모드에서든 같은 자리에 있게 했다.
-->
<!-- 12차 B-2: 프로그램 정보(관리 목적). 단축키 바로 옆 - 둘 다 "이 프로그램에 대한 안내" 다. -->
<Button DockPanel.Dock="Right" Content="정보" Click="About_Click"
Height="26" MinHeight="26" Padding="10,2" FontSize="11" Margin="10,0,0,0"
VerticalAlignment="Top"
ToolTip="이 프로그램의 버전 · 빌드 날짜 · 만든 사람 · 사내 문의처를 봅니다"/>
<Button DockPanel.Dock="Right" Content="단축키" Click="ShortcutHelp_Click"
Height="26" MinHeight="26" Padding="10,2" FontSize="11" Margin="10,0,0,0"
VerticalAlignment="Top"
ToolTip="단축키 목록을 봅니다 (F1)"/>
<!-- 11차 F-4: 오른쪽 ACTIONS 칸을 감춰 코드 칸을 넓힌다. 감춘 칸에 있으면 되살릴 길이 없으니 늘 보이는 이 줄에 둔다. -->
<Button DockPanel.Dock="Right" Content="{Binding ActionsToggleLabel}" Command="{Binding ToggleActionsCommand}"
Height="26" MinHeight="26" Padding="10,2" FontSize="11" Margin="10,0,0,0"
VerticalAlignment="Top"
ToolTip="오른쪽 ACTIONS 칸을 감추거나 다시 보입니다 (F9). 감춰도 Ctrl+S · Alt+←/→ 는 그대로 듣습니다"/>
<Button DockPanel.Dock="Right" Content="결과 내보내기" Command="{Binding ExportReportCommand}"
Height="26" MinHeight="26" Padding="10,2" FontSize="11" Margin="10,0,0,0"
VerticalAlignment="Top"
ToolTip="비교 결과를 글로 뽑아 파일에 저장합니다. 메일이나 결재에 그대로 붙일 수 있습니다"/>
<WrapPanel Orientation="Horizontal">
<!-- 지금 어느 모드인지 늘 보이게 한다 - 커밋 이력이 없는 이유를 알 수 있어야 한다. -->
<Border Padding="6,1" CornerRadius="2" Margin="0,0,10,0" VerticalAlignment="Center"
Background="{DynamicResource Ubisam.Brush.Input.Background}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}" BorderThickness="1">
<TextBlock Text="{Binding ModeLabel}" FontSize="11" FontWeight="Bold"
Foreground="{DynamicResource Ubisam.Brush.Accent}"/>
</Border>
<!-- Git 이 아닐 때: 비교할 파일 두 개를 직접 고른다 -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
Visibility="{Binding IsPlainMode, Converter={StaticResource BoolToVis}}">
<!--
잘린 경로 두 줄을 뺐다(4차 C-1). 260px 안에서 잘린 경로는 읽을 수 없어
자리만 먹었고, 같은 값이 각 패널 머리글에 이미 있다.
<b>놓는 자리는 단추로 옮겼다</b> - 끌어다 놓기(3차 D-7)의 자리가 사라지면 안 되고,
잘린 글자보다 단추가 겨냥하기 쉽다.
-->
<Button Content="기준 파일..." Command="{Binding PickLeftFileCommand}"
Height="26" MinHeight="26" Padding="8,2" FontSize="11" Margin="0,0,4,0"
AllowDrop="True" DragOver="View_DragOver" Drop="LeftFile_Drop"
ToolTip="{Binding LeftFile}"/>
<Button Content="내 파일..." Command="{Binding PickRightFileCommand}"
Height="26" MinHeight="26" Padding="8,2" FontSize="11"
AllowDrop="True" DragOver="View_DragOver" Drop="RightFile_Drop"
ToolTip="{Binding RightFile}"/>
<!-- 폴더 비교에서 열었을 때만 켜진다. 목록 없이 연 파일에는 딛고 갈 자리가 없다. -->
<Button Content="◀ 이전 파일" Command="{Binding PrevFileCommand}"
Height="26" MinHeight="26" Padding="8,2" FontSize="11" Margin="14,0,4,0"
ToolTip="폴더 비교 목록에서 앞 파일로 (Ctrl+PageUp)"/>
<TextBlock Text="{Binding FileQueuePosition}" VerticalAlignment="Center"
Style="{StaticResource Muted}" FontSize="11" MinWidth="42"
TextAlignment="Center"/>
<Button Content="다음 파일 ▶" Command="{Binding NextFileCommand}"
Height="26" MinHeight="26" Padding="8,2" FontSize="11" Margin="4,0,0,0"
ToolTip="폴더 비교 목록에서 다음 파일로 (Ctrl+PageDown)"/>
<TextBlock Text="최근" VerticalAlignment="Center" Style="{StaticResource Muted}"
FontSize="11" Margin="14,0,4,0"/>
<ComboBox x:Name="RecentBox" ItemsSource="{Binding RecentFiles}"
SelectionChanged="Recent_SelectionChanged"
Width="200" Height="26" MinHeight="26" FontSize="11"
VerticalContentAlignment="Center"
ToolTip="최근에 견준 파일 짝. 고르면 바로 엽니다">
<ComboBox.ItemTemplate>
<DataTemplate>
<!-- 자리를 옮겼거나 지워진 짝은 흐리게. 목록에서 지우지는 않는다 -
네트워크 드라이브나 USB 는 잠깐 안 보일 뿐이다. -->
<TextBlock Text="{Binding Label}" ToolTip="{Binding Tip}" FontSize="11">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Exists}" Value="False">
<Setter Property="Opacity" Value="0.45"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
<!-- Git 일 때: 비교할 파일과 3-Way 상대 브랜치를 여기서 고른다.
파일 목록은 BASE 머리에 있었는데, 그러면 BASE 쪽에만 줄이 하나 더 생겨
LOCAL 과 경로 줄 높이가 어긋난다. -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
Visibility="{Binding IsGitMode, Converter={StaticResource BoolToVis}}">
<TextBlock Text="파일" Style="{StaticResource Muted}" FontSize="11"
VerticalAlignment="Center" Margin="0,0,6,0"/>
<ComboBox ItemsSource="{Binding Files}" SelectedItem="{Binding SelectedFile}"
MinWidth="260" Height="26" MinHeight="26" FontSize="11" Margin="0,0,14,0"
ToolTip="비교할 파일"/>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
Visibility="{Binding IsGitMode, Converter={StaticResource BoolToVis}}">
<TextBlock Text="3-Way" Style="{StaticResource Muted}" FontSize="11"
VerticalAlignment="Center" Margin="0,0,6,0"/>
<ComboBox ItemsSource="{Binding RemoteBranches}"
SelectedItem="{Binding RemoteBranch, Mode=TwoWay}"
Visibility="{Binding ShowBranchCombo, Converter={StaticResource BoolToVis}}"
MinWidth="180" Height="26" MinHeight="26" FontSize="11"
ToolTip="고른 브랜치를 REMOTE(Theirs) 로 놓고 merge base 기준 3-Way 로 봅니다"/>
<!-- 병합 충돌 파일은 콤보를 안 건드리고 네 칸이 뜬다(6차 A-3). 콤보는 그때 "(3-Way 안 씀)" 이라
화면과 다른 말을 했다(7차 A-2). 대신 무엇과 견주는지 글로 보인다. -->
<TextBlock Text="{Binding ConflictSideText}" FontSize="11" FontWeight="SemiBold"
VerticalAlignment="Center" MinWidth="180"
Visibility="{Binding IsConflictThreeWay, Converter={StaticResource BoolToVis}}"
ToolTip="병합 중인 충돌 파일이라 REMOTE 는 병합 상대 쪽입니다. 브랜치를 고르지 않아도 됩니다"/>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
Visibility="{Binding IsGitMode, Converter={StaticResource BoolToVis}}">
<TextBlock Text="Repo" Style="{StaticResource Muted}" Margin="0,0,6,0"/>
<TextBlock Text="{Binding Workspace.RepositoryName}" FontWeight="Bold" Margin="0,0,16,0"
VerticalAlignment="Center"
ToolTip="{Binding Workspace.RepositoryPath}"/>
<TextBlock Text="Branch" Style="{StaticResource Muted}" Margin="0,0,6,0"/>
<TextBlock Text="{Binding Workspace.CurrentBranch}" FontWeight="Bold" Margin="0,0,16,0"
Foreground="{DynamicResource Ubisam.Brush.Accent}" VerticalAlignment="Center"/>
<!-- Git 주소 + 최신 여부 -->
<TextBlock Text="Git" Style="{StaticResource Muted}" Margin="0,0,6,0"/>
<TextBlock Text="{Binding Workspace.RemoteUrlText}" Margin="0,0,10,0" MaxWidth="300"
TextTrimming="CharacterEllipsis" VerticalAlignment="Center"
ToolTip="{Binding Workspace.RemoteUrlText}"/>
<Border CornerRadius="3" Padding="6,1" Margin="0,0,6,0" VerticalAlignment="Center">
<Border.Style>
<Style TargetType="Border">
<!-- 최신이면 초록, 아니면 주황. 범례의 반영/미반영 색과 같은 뜻으로 맞췄다. -->
<Setter Property="Background"
Value="{Binding Source=Changed, Converter={StaticResource HighlightByName}, ConverterParameter=Changed}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Workspace.IsUpToDate}" Value="True">
<Setter Property="Background"
Value="{Binding Source=Applied, Converter={StaticResource HighlightByName}, ConverterParameter=Applied}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{Binding Workspace.SyncStatusText}" FontSize="11" FontWeight="Bold"
VerticalAlignment="Center"/>
</Border>
<TextBlock Text="{Binding Workspace.LastFetchText}" Style="{StaticResource Muted}"
FontSize="11" Margin="0,0,16,0"
ToolTip="앞섬/뒤처짐 수치는 마지막 fetch 시점 기준입니다. Git 탭에서 Fetch 하면 갱신됩니다."/>
<TextBlock Text="Selected Commit" Style="{StaticResource Muted}" Margin="0,0,6,0"/>
<TextBlock Text="{Binding CommitHash}" FontFamily="Consolas" Margin="0,0,4,0"
Foreground="{DynamicResource Ubisam.Brush.Warning}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding CommitSubject}" Margin="0,0,16,0" MaxWidth="240"
TextTrimming="CharacterEllipsis" VerticalAlignment="Center"/>
<TextBlock Text="Working Tree" Style="{StaticResource Muted}" Margin="0,0,6,0"/>
<TextBlock Text="{Binding Workspace.WorkingTreeSummary}" FontWeight="Bold" Margin="0,0,16,0"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding Workspace.PendingOperationText}" FontWeight="Bold" Margin="0,0,16,0"
Foreground="{DynamicResource Ubisam.Brush.Danger}" VerticalAlignment="Center"/>
</StackPanel>
<Button Content="새로고침" Command="{Binding ReloadCommand}"
Height="26" MinHeight="26" Padding="10,2" FontSize="12"
Visibility="{Binding IsGitMode, Converter={StaticResource BoolToVis}}"/>
</WrapPanel>
</DockPanel>
</Border>
<!-- ===== 색 구분 안내(범례) =====
칩 색은 LineHighlightBrushes 한 곳에서 오므로 코드 패널과 항상 같은 색이다. -->
<StackPanel Grid.Row="1">
<!-- ===== 한쪽이 지운 충돌 (6차 A-4) =====
이런 충돌에서 정할 것은 "이 파일을 남길까 지울까" 하나다. 줄 단위로 끌고 가면 그 물음이 안 보였다.
범례 바로 위, 코드보다 먼저 눈에 들어오는 자리에 둔다. -->
<Border Padding="10,6" Margin="0,0,0,5" BorderThickness="1"
Visibility="{Binding IsDeletionConflict, Converter={StaticResource BoolToVis}}"
Background="{DynamicResource Ubisam.Brush.Input.Background}"
BorderBrush="{DynamicResource Ubisam.Brush.Danger}">
<DockPanel LastChildFill="True">
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" VerticalAlignment="Center">
<Button Content="파일 남기기" MinWidth="110" Margin="8,0,0,0"
Command="{Binding KeepFileCommand}"
Style="{StaticResource FilledButton}" FontWeight="Bold"
ToolTip="파일이 있는 쪽을 골라 남기고 스테이징합니다. 고친 것이 있으면 고친 대로 남깁니다"/>
<Button Content="파일 지우기" MinWidth="110" Margin="8,0,0,0"
Command="{Binding DeleteFileCommand}"
ToolTip="지운 쪽을 따라 파일을 지우고 스테이징합니다"/>
</StackPanel>
<TextBlock Text="{Binding DeletionConflictText}" TextWrapping="Wrap" VerticalAlignment="Center"
FontWeight="Bold" Foreground="{DynamicResource Ubisam.Brush.Danger}"/>
</DockPanel>
</Border>
<Border Padding="10,3" Margin="0,0,0,5"
Background="{DynamicResource Ubisam.Brush.Input.Background}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}" BorderThickness="1">
<WrapPanel Orientation="Horizontal">
<!--
줄 왼쪽 안내(4차 C-4). 항목을 눌러 이동할 수 있게 만들었는데
<b>눌러진다는 사실 자체가 안 보였다</b> - 테두리를 지운 값이다.
항목마다 테두리를 주면 줄이 단추 여덟 개로 보여 시끄럽다.
대신 줄 머리에서 한 번만 말해 준다.
-->
<TextBlock Text="색 구분" Style="{StaticResource Muted}" FontWeight="Bold"
FontSize="11" Margin="0,0,4,0"/>
<TextBlock Text="(눌러서 이동)" Style="{StaticResource Muted}" FontSize="11" Margin="0,0,12,0"
ToolTip="개수를 누르면 그 갈래의 줄로 갑니다. 다시 누르면 다음 줄로 갑니다"/>
<!-- 각 항목 뒤의 숫자는 지금 화면에 있는 줄 수. 한 줄만 반영해도 즉시 바뀐다. -->
<Button Style="{StaticResource LegendButton}" Command="{Binding GoToKindCommand}"
CommandParameter="{x:Static models:LineHighlight.Changed}"
Opacity="{Binding ChangedCount, Converter={StaticResource ZeroToDim}}"
ToolTip="누르면 'BASE와 다름' 줄로 갑니다. 다시 누르면 다음 줄">
<StackPanel Orientation="Horizontal">
<Border Style="{StaticResource LegendChip}"
Background="{Binding Source=Changed, Converter={StaticResource HighlightByName}, ConverterParameter=Changed}"/>
<TextBlock Text="BASE와 다름" Style="{StaticResource LegendText}"/>
<TextBlock Text="{Binding ChangedCount}" Style="{StaticResource LegendCount}"/>
</StackPanel>
</Button>
<Button Style="{StaticResource LegendButton}" Command="{Binding GoToKindCommand}"
CommandParameter="{x:Static models:LineHighlight.Applied}"
Opacity="{Binding AppliedCount, Converter={StaticResource ZeroToDim}}"
ToolTip="누르면 '머지 반영함' 줄로 갑니다. 다시 누르면 다음 줄">
<StackPanel Orientation="Horizontal">
<Border Style="{StaticResource LegendChip}"
Background="{Binding Source=Applied, Converter={StaticResource HighlightByName}, ConverterParameter=Applied}"/>
<TextBlock Text="머지 반영함" Style="{StaticResource LegendText}"/>
<TextBlock Text="{Binding AppliedCount}" Style="{StaticResource LegendCount}"
Foreground="{DynamicResource Ubisam.Brush.Success}"/>
</StackPanel>
</Button>
<Button Style="{StaticResource LegendButton}" Command="{Binding GoToKindCommand}"
CommandParameter="{x:Static models:LineHighlight.Edited}"
Opacity="{Binding EditedCount, Converter={StaticResource ZeroToDim}}"
ToolTip="누르면 '직접 수정' 줄로 갑니다. 다시 누르면 다음 줄">
<StackPanel Orientation="Horizontal">
<Border Style="{StaticResource LegendChip}"
Background="{Binding Source=Edited, Converter={StaticResource HighlightByName}, ConverterParameter=Edited}"/>
<TextBlock Text="직접 수정" Style="{StaticResource LegendText}"/>
<TextBlock Text="{Binding EditedCount}" Style="{StaticResource LegendCount}"/>
</StackPanel>
</Button>
<Button Style="{StaticResource LegendButton}" Command="{Binding GoToKindCommand}"
CommandParameter="{x:Static models:LineHighlight.Added}"
Opacity="{Binding AddedCount, Converter={StaticResource ZeroToDim}}"
ToolTip="누르면 '추가' 줄로 갑니다. 다시 누르면 다음 줄">
<StackPanel Orientation="Horizontal">
<Border Style="{StaticResource LegendChip}"
Background="{Binding Source=Added, Converter={StaticResource HighlightByName}, ConverterParameter=Added}"/>
<TextBlock Text="추가" Style="{StaticResource LegendText}"/>
<TextBlock Text="{Binding AddedCount}" Style="{StaticResource LegendCount}"/>
</StackPanel>
</Button>
<Button Style="{StaticResource LegendButton}" Command="{Binding GoToKindCommand}"
CommandParameter="{x:Static models:LineHighlight.Removed}"
Opacity="{Binding RemovedCount, Converter={StaticResource ZeroToDim}}"
ToolTip="누르면 '삭제' 줄로 갑니다. 다시 누르면 다음 줄">
<StackPanel Orientation="Horizontal">
<Border Style="{StaticResource LegendChip}"
Background="{Binding Source=Removed, Converter={StaticResource HighlightByName}, ConverterParameter=Removed}"/>
<TextBlock Text="삭제" Style="{StaticResource LegendText}"/>
<TextBlock Text="{Binding RemovedCount}" Style="{StaticResource LegendCount}"/>
</StackPanel>
</Button>
<!-- 무시 규칙에 걸린 줄. 차이로 세지 않는다. -->
<Button Style="{StaticResource LegendButton}" Command="{Binding GoToKindCommand}"
CommandParameter="{x:Static models:LineHighlight.Ignored}"
Opacity="{Binding IgnoredCount, Converter={StaticResource ZeroToDim}}"
ToolTip="누르면 '무시' 줄로 갑니다. 다시 누르면 다음 줄">
<StackPanel Orientation="Horizontal">
<Border Style="{StaticResource LegendChip}"
Background="{Binding Source=Ignored, Converter={StaticResource HighlightByName}, ConverterParameter=Ignored}"/>
<TextBlock Text="무시" Style="{StaticResource LegendText}"/>
<TextBlock Text="{Binding IgnoredCount}" Style="{StaticResource LegendCount}"/>
</StackPanel>
</Button>
<!-- 자리만 옮긴 줄. 삭제+추가로 두 번 세지 않도록 따로 표시한다. -->
<Button Style="{StaticResource LegendButton}" Command="{Binding GoToKindCommand}"
CommandParameter="{x:Static models:LineHighlight.Moved}"
Opacity="{Binding MovedCount, Converter={StaticResource ZeroToDim}}"
ToolTip="누르면 '자리 옮김' 줄로 갑니다. 다시 누르면 다음 줄">
<StackPanel Orientation="Horizontal">
<Border Style="{StaticResource LegendChip}"
Background="{Binding Source=Moved, Converter={StaticResource HighlightByName}, ConverterParameter=Moved}"/>
<TextBlock Text="자리 옮김" Style="{StaticResource LegendText}"/>
<TextBlock Text="{Binding MovedCount}" Style="{StaticResource LegendCount}"/>
</StackPanel>
</Button>
<!-- 3-Way 일 때만 뜻이 있는 두 가지. 2-Way 에서는 늘 0 이라 자리만 차지한다. -->
<Button Style="{StaticResource LegendButton}" Command="{Binding GoToKindCommand}"
CommandParameter="{x:Static models:LineHighlight.RemoteChange}"
Opacity="{Binding RemoteChangeCount, Converter={StaticResource ZeroToDim}}"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
ToolTip="누르면 '저쪽만 고친 줄'로 갑니다. 다시 누르면 다음 줄">
<StackPanel Orientation="Horizontal">
<Border Style="{StaticResource LegendChip}"
Background="{Binding Source=RemoteChange, Converter={StaticResource HighlightByName}, ConverterParameter=RemoteChange}"/>
<TextBlock Text="Theirs only" Style="{StaticResource LegendText}"/>
<TextBlock Text="{Binding RemoteChangeCount}" Style="{StaticResource LegendCount}"/>
</StackPanel>
</Button>
<Button Style="{StaticResource LegendButton}" Command="{Binding GoToKindCommand}"
CommandParameter="{x:Static models:LineHighlight.Conflict}"
Opacity="{Binding ConflictCount, Converter={StaticResource ZeroToDim}}"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
ToolTip="누르면 충돌 줄로 갑니다. 다시 누르면 다음 줄">
<StackPanel Orientation="Horizontal">
<Border Style="{StaticResource LegendChip}"
Background="{Binding Source=Conflict, Converter={StaticResource HighlightByName}, ConverterParameter=Conflict}"/>
<TextBlock Text="Conflict" Style="{StaticResource LegendText}"/>
<TextBlock Text="{Binding ConflictCount}" Style="{StaticResource LegendCount}"/>
</StackPanel>
</Button>
<Border Style="{StaticResource LegendChip}" Background="#66E0A020"/>
<TextBlock Text="줄 안에서 바뀐 부분" Style="{StaticResource LegendText}"/>
<!-- 2026-09-30 사용자 요청: 이 칸만 개수가 없었다. 단어 단위로 달라진 곳이 있는 "BASE와 다름" 줄 수. -->
<TextBlock x:Name="InlineChangeCountText" Text="{Binding InlineChangeCount}" Style="{StaticResource LegendCount}" Margin="0,0,14,0"/>
</WrapPanel>
</Border>
</StackPanel>
<!--
이진 파일을 열었을 때. 코드 패널 위를 덮는다.
"줄 단위로 비교할 수 없습니다" 한 줄로 끝내면 정작 알고 싶은 <b>같은지 다른지</b>를
모른 채 화면을 닫게 된다. 그림이면 나란히 띄우고, 아니면 앞부분을 16진으로 늘어놓는다.
-->
<Border Grid.Row="2" Padding="10" Panel.ZIndex="10"
Visibility="{Binding IsBinaryFile, Converter={StaticResource BoolToVis}}"
Background="{DynamicResource Ubisam.Brush.ContentBackground}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}" BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Margin="0,0,0,8">
<TextBlock Text="이진 파일" FontWeight="Bold" FontSize="13"
Foreground="{DynamicResource Ubisam.Brush.Accent}"/>
<TextBlock Text="{Binding BinarySummary}" FontSize="12" Margin="0,2,0,0"
TextWrapping="Wrap"/>
</StackPanel>
<!-- 그림: 나란히 띄운다. 16진으로 보는 것보다 훨씬 빠르다. -->
<Grid Grid.Row="1" Visibility="{Binding IsImageFile, Converter={StaticResource BoolToVis}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" BorderThickness="1" Padding="6"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
<DockPanel>
<TextBlock DockPanel.Dock="Top" Text="{Binding BaseHeader}" Style="{StaticResource Muted}"
FontSize="11" TextTrimming="CharacterEllipsis" Margin="0,0,0,6"/>
<Image Source="{Binding LeftImagePath}" Stretch="Uniform"
RenderOptions.BitmapScalingMode="HighQuality"/>
</DockPanel>
</Border>
<Border Grid.Column="2" BorderThickness="1" Padding="6"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
<DockPanel>
<TextBlock DockPanel.Dock="Top" Text="{Binding LocalHeader}" Style="{StaticResource Muted}"
FontSize="11" TextTrimming="CharacterEllipsis" Margin="0,0,0,6"/>
<Image Source="{Binding RightImagePath}" Stretch="Uniform"
RenderOptions.BitmapScalingMode="HighQuality"/>
</DockPanel>
</Border>
</Grid>
<!-- 그림이 아니면 16진 나란히 보기. 다른 줄만 색을 준다. -->
<DataGrid Grid.Row="1" ItemsSource="{Binding HexRows}"
Visibility="{Binding ShowHexView, Converter={StaticResource BoolToVis}}"
AutoGenerateColumns="False" IsReadOnly="True"
HeadersVisibility="Column" GridLinesVisibility="None"
FontFamily="Consolas" FontSize="11"
Background="Transparent" BorderThickness="1"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding IsDifferent}" Value="True">
<Setter Property="Background"
Value="{Binding Source=Changed, Converter={StaticResource HighlightByName}, ConverterParameter=Changed}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="위치" Binding="{Binding OffsetText}" Width="Auto"/>
<DataGridTextColumn Header="왼쪽 (16진)" Binding="{Binding LeftHex}" Width="Auto"/>
<DataGridTextColumn Header="왼쪽" Binding="{Binding LeftText}" Width="Auto"/>
<DataGridTextColumn Header="오른쪽 (16진)" Binding="{Binding RightHex}" Width="Auto"/>
<DataGridTextColumn Header="오른쪽" Binding="{Binding RightText}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Border>
<!-- ===== 본문 4분할 =====
ScrollViewer 로 감싸면 내부 Grid 가 "무한 폭"으로 측정되어 star 열이 콘텐츠 크기로
부풀고 오른쪽 패널이 창 밖으로 밀려난다. 셸이 이미 정확한 폭을 주므로 그대로 채운다. -->
<Grid Grid.Row="2" x:Name="BodyGrid" Grid.IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<!-- Git 저장소가 아니면 커밋 이력 칸을 접는다. 보여 줄 이력이 없기 때문이다. -->
<ColumnDefinition MinWidth="0">
<ColumnDefinition.Style>
<Style TargetType="ColumnDefinition">
<Setter Property="Width" Value="228"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsGitMode}" Value="False">
<Setter Property="Width" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
<ColumnDefinition Width="4"/>
<ColumnDefinition x:Name="LocalColumn" Width="*" MinWidth="200"/>
<!-- 칸 사이 틈(11차 F-10). 바뀐 덩어리를 잇는 선을 그리는 자리라 구분선보다 넓다. 끌면 구분선처럼 폭이 바뀐다. -->
<ColumnDefinition Width="{StaticResource LinkGapWidth}"/>
<ColumnDefinition x:Name="BaseColumn" Width="*" MinWidth="200"/>
<!-- 3-Way 를 안 쓰면 REMOTE 칸과 그 앞 구분선을 폭 0 으로 접는다.
Visibility 만 숨기면 star 열이 자리를 그대로 차지해 빈 칸이 남는다. -->
<ColumnDefinition>
<ColumnDefinition.Style>
<Style TargetType="ColumnDefinition">
<Setter Property="Width" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsThreeWay}" Value="True">
<Setter Property="Width" Value="{StaticResource LinkGapWidth}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
<ColumnDefinition x:Name="RemoteColumn" MinWidth="0">
<ColumnDefinition.Style>
<Style TargetType="ColumnDefinition">
<Setter Property="Width" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsThreeWay}" Value="True">
<Setter Property="Width" Value="*"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
<ColumnDefinition x:Name="ActionsSplitColumn" Width="4"/>
<!--
ACTIONS 칸. 접어도 폭은 그대로 둔다(4차 C-5). 칸 전체를 감추는 것은 11차 F-4(단추 · F9).
<b>접는다고 폭이 줄지 않는다</b> - 폭을 정하는 것은 두 칸짜리 단추(Undo/Redo ·
이전 차이/다음 차이)와 Accept 인데, 그것들은 접어도 남는 쪽이다.
실제로 176 으로 좁혀 봤더니 "Undo" 가 "Undc" 로, "이전 차이" 가 "이전 차" 로 잘렸다.
C-5 가 얻는 것은 폭이 아니라 <b>세로 자리와 덜 어지러운 칸</b>이다.
-->
<ColumnDefinition x:Name="ActionsColumn" Width="214" MinWidth="170"/>
</Grid.ColumnDefinitions>
<!--
3-Way 는 위에 원본 세 개(LOCAL · BASE · REMOTE)를 나란히 놓고,
아래에 결과(MERGED)를 폭 전체로 둔다. 원본을 넷째 칸으로 붙이면 한 칸이
40자도 안 돼서 코드를 읽을 수 없다 - 세로를 쓰는 편이 훨씬 넓다.
2-Way 에서는 아래 두 줄이 높이 0 이라 지금까지의 좌우 배치 그대로다.
-->
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition>
<RowDefinition.Style>
<Style TargetType="RowDefinition">
<Setter Property="Height" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsThreeWay}" Value="True">
<Setter Property="Height" Value="4"/>
</DataTrigger>
</Style.Triggers>
</Style>
</RowDefinition.Style>
</RowDefinition>
<RowDefinition MinHeight="0">
<RowDefinition.Style>
<Style TargetType="RowDefinition">
<Setter Property="Height" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsThreeWay}" Value="True">
<Setter Property="Height" Value="1*"/>
</DataTrigger>
</Style.Triggers>
</Style>
</RowDefinition.Style>
</RowDefinition>
</Grid.RowDefinitions>
<!-- ① 수정 이력 -->
<Border Grid.Column="0" Grid.RowSpan="3" BorderThickness="1"
Visibility="{Binding IsGitMode, Converter={StaticResource BoolToVis}}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Margin="8,6">
<TextBlock Text="1. 수정 이력 (Commit)" Style="{StaticResource PanelTitle}"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="브랜치 및 커밋 히스토리 · " Style="{StaticResource Muted}" FontSize="11"/>
<TextBlock Text="{Binding Workspace.Commits.Count}" Style="{StaticResource Muted}" FontSize="11"/>
<TextBlock Text=" commits" Style="{StaticResource Muted}" FontSize="11"/>
</StackPanel>
</StackPanel>
<ListBox Grid.Row="1" BorderThickness="0" Background="Transparent"
ItemsSource="{Binding Workspace.CommitNodes}"
SelectedItem="{Binding SelectedNode}"
HorizontalContentAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Auto">
<ListBox.ItemContainerStyle>
<!-- 그래프 선이 행 경계에서 정확히 이어지려면 여백이 없어야 한다 -->
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="MinHeight" Value="44"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid MinHeight="44">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 브랜치 그래프: 행 높이 전체를 채워야 위아래 선이 이어진다 -->
<ctl:CommitGraphCell Grid.Column="0" Row="{Binding Graph}"
VerticalAlignment="Stretch"/>
<StackPanel Grid.Column="1" Margin="4,3" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding Commit.Refs}" Margin="0,0,4,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><StackPanel Orientation="Horizontal"/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="{DynamicResource Ubisam.Brush.Tag.AccentBackground}"
CornerRadius="3" Padding="4,0" Margin="0,0,3,0">
<TextBlock Text="{Binding}" FontSize="10"
Foreground="{DynamicResource Ubisam.Brush.Tag.AccentForeground}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="{Binding Commit.MessageShort}" TextTrimming="CharacterEllipsis"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Commit.ShortSha}" FontFamily="Consolas" FontSize="11"
Foreground="{DynamicResource Ubisam.Brush.Warning}"/>
<TextBlock Text=" · " FontSize="11" Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
<TextBlock Text="{Binding Commit.AuthorName}" FontSize="11"
Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
<TextBlock Text=" · " FontSize="11" Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
<TextBlock Text="{Binding Commit.When, StringFormat=yyyy-MM-dd}" FontSize="11"
Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Border Grid.Row="2" Padding="8,6" BorderThickness="0,1,0,0"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
<StackPanel>
<TextBlock Text="선택된 커밋 정보" Style="{StaticResource Muted}" FontSize="11"/>
<TextBlock Text="{Binding CommitSubject}" TextTrimming="CharacterEllipsis"/>
</StackPanel>
</Border>
</Grid>
</Border>
<GridSplitter Grid.Column="1" Grid.RowSpan="3" Width="4" HorizontalAlignment="Stretch" DragCompleted="PaneSplitter_DragCompleted"
Background="{DynamicResource Ubisam.Brush.Table.GridLine}"/>
<!--
②-0 LOCAL 원본 (3-Way 전용, 읽기 전용).
파일을 연 시점의 내 줄을 그대로 붙들어 보여 준다. 이게 없으면 REMOTE 를
한 번 가져오는 순간 내 원래 내용이 화면에서 사라져 판단 근거를 잃는다.
-->
<Border Grid.Column="2" Grid.Row="0" BorderThickness="1"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}"
Background="{Binding (TextElement.Foreground), RelativeSource={RelativeSource Self}, Converter={StaticResource PaneBackground}}">
<!-- 칸 밖으로 그려지지 않게 자른다. 표시층은 RenderTransform 으로 살짝 밀어 놓는데,
그건 배치가 아니라 그리기라 자르지 않으면 <b>아래 상태바 위에 코드가 겹쳐 그려진다.</b>
차이로 이동(Alt+↑↓)할 때 실제로 그렇게 보였다. -->
<Grid ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" SharedSizeGroup="PaneHeader"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<!-- 12차 B-3: 칸 아래 한 줄 요약 -->
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Margin="8,6">
<TextBlock Text="LOCAL(Mine)" Style="{StaticResource PanelTitle}"/>
<!-- 내 쪽 · 파일(11차 F-1). 예전에는 MERGED 칸과 같은 값(브랜치 이름)이라 git mergetool 에서 실제로 읽은 LOCAL 임시 파일과 달랐다.
네 칸 머리를 모두 같은 모양(읽으라고 쓰는 글씨 · 한 줄 · 가운데 줄이기 · 툴팁 전체)으로 둔다. -->
<TextBlock ctl:MiddleTrim.Text="{Binding MineHeader}" ToolTip="{Binding MineHeader}"
FontFamily="Consolas" FontSize="12" Margin="0,1,0,0"
TextTrimming="CharacterEllipsis"
Foreground="{DynamicResource Ubisam.Brush.ContentForeground}"/>
</StackPanel>
<!-- 코드 칸을 제 자리 밖으로 못 나가게 자른다. 표시층의 미세 보정은 배치가 아니라
그리기라 자르지 않으면 아래 바닥 글 위에 코드가 겹쳐 그려진다. -->
<Grid Grid.Row="1" ClipToBounds="True">
<ListBox x:Name="OriginalLocalLines" BorderThickness="0" Background="Transparent"
SelectedItem="{Binding SelectedLine, Mode=OneWay}" SelectionChanged="PaneLines_SelectionChanged"
ItemTemplate="{StaticResource OriginalLocalLineTemplate}"
ItemContainerStyle="{StaticResource CodeLineItem}"
VirtualizingPanel.ScrollUnit="Pixel"
HorizontalContentAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="{Binding SourceBars,
RelativeSource={RelativeSource AncestorType=UserControl}}"
ScrollViewer.ScrollChanged="Lines_ScrollChanged"/>
</Grid>
<!--
원본 칸의 가로 스크롤 막대. 3-Way 에서 워드랩을 끄고 긴 줄이 있을 때만 보인다.
ScrollViewer 가 만들어 주는 막대를 쓰지 못하는 이유가 있다 - 이 칸들은
ScrollViewer 를 가로로 굴리지 않고(줄 번호까지 밀려 나가고 세 칸이 어긋난다)
글자만 SourceShiftX 로 민다. 그래서 그 막대는 <b>움직이지 않는 막대</b>가 된다.
세 칸은 늘 같이 움직이므로 세 막대의 손잡이도 같은 자리에 있다.
-->
<ScrollBar Grid.Row="2" Orientation="Horizontal" Height="14"
Minimum="0" SmallChange="24"
LargeChange="{Binding SourceViewport, RelativeSource={RelativeSource AncestorType=UserControl}}"
Maximum="{Binding SourceScrollMax, RelativeSource={RelativeSource AncestorType=UserControl}}"
ViewportSize="{Binding SourceViewport, RelativeSource={RelativeSource AncestorType=UserControl}}"
Value="{Binding SourceScrollValue, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
Visibility="{Binding SourceBarVisibility, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<!-- 3-Way 에서는 세로가 빠듯하다. 아래 MERGED 의 상태바에 같은 정보가 있어 여기는 접는다. -->
<Border Grid.Row="2" Padding="8,5" BorderThickness="0,1,0,0"
Height="{StaticResource PaneFooterHeight}"
Visibility="{Binding IsTwoWay, Converter={StaticResource BoolToVis}}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
<StackPanel VerticalAlignment="Center">
<TextBlock Text="내 변경" Style="{StaticResource Muted}" FontSize="11"/>
<TextBlock Text="[&lt;] 를 누르면 그 줄을 결과로 되돌립니다" FontSize="11"
Style="{StaticResource Muted}"/>
</StackPanel>
</Border>
<!-- 12차 B-3: 이 칸이 저쪽과 무엇이 다른가. 맨 아래 상태 줄의 "파일 전체" 와 다른 값이다. -->
<Border Grid.Row="3" Padding="8,3" BorderThickness="0,1,0,0"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}"
Background="{DynamicResource Ubisam.Brush.Sidebar.Background}">
<TextBlock x:Name="MineSummaryText" Text="{Binding MineSummary}" FontSize="11"
TextTrimming="CharacterEllipsis"
Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
</Border>
</Grid>
</Border>
<!-- ② BASE -->
<Border Grid.Row="0" BorderThickness="1"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}"
Background="{Binding (TextElement.Foreground), RelativeSource={RelativeSource Self}, Converter={StaticResource PaneBackground}}">
<Border.Style>
<Style TargetType="Border">
<!-- 2-Way: 왼쪽 칸. 3-Way: 가운데 칸(LOCAL 과 REMOTE 사이가 공통 조상의 자리다). -->
<Setter Property="Grid.Column" Value="2"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsThreeWay}" Value="True">
<Setter Property="Grid.Column" Value="4"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<!-- 칸 밖으로 그려지지 않게 자른다. 표시층은 RenderTransform 으로 살짝 밀어 놓는데,
그건 배치가 아니라 그리기라 자르지 않으면 <b>아래 상태바 위에 코드가 겹쳐 그려진다.</b>
차이로 이동(Alt+↑↓)할 때 실제로 그렇게 보였다. -->
<Grid ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" SharedSizeGroup="PaneHeader"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<!-- 12차 B-3: 칸 아래 한 줄 요약 -->
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- BASE / LOCAL 의 머리 영역 높이를 같게 고정해야 두 패널의 01행이 같은 눈높이에 온다 -->
<StackPanel Grid.Row="0" Margin="8,6">
<TextBlock Text="{Binding BaseTitle}" Style="{StaticResource PanelTitle}"/>
<!--
경로 줄을 따로 두지 않는다(4차 C-1).
같은 값이 위 툴바 · 이 머리글 · 아래 경로 상자 셋에 있었고, 그중 경로 상자만
패널마다 세로 40px 남짓을 먹었다. 전체 경로는 툴팁으로 옮기고
<b>복사는 우클릭 메뉴로</b> 지켰다 - 읽기전용 TextBox 를 뒀던 까닭이 복사였다.
-->
<!--
경로가 이 칸의 정체다(4차 C-1). 늘 똑같은 "BASE" 는 위 제목이 이미 말하므로
여기서는 경로만, 그것도 <b>회색 작은 글씨가 아니라 읽으라고</b> 적는다.
갈리는 첫 칸만 굵게 칠해 어디가 다른지 눈이 바로 가게 한다.
-->
<TextBlock FontFamily="Consolas" FontSize="12" Margin="0,1,0,0"
TextTrimming="CharacterEllipsis"
Foreground="{DynamicResource Ubisam.Brush.ContentForeground}"
ToolTip="{Binding BaseFileLocation}"
ctl:MiddleTrim.Lead="{Binding BaseHeaderLead}" ctl:MiddleTrim.Text="{Binding BaseHeader}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="전체 경로 복사" Click="CopyBasePath_Click"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</StackPanel>
<!-- 코드 칸을 제 자리 밖으로 못 나가게 자른다. 표시층의 미세 보정은 배치가 아니라
그리기라 자르지 않으면 아래 바닥 글 위에 코드가 겹쳐 그려진다. -->
<Grid Grid.Row="1" ClipToBounds="True">
<ListBox x:Name="BaseLines" BorderThickness="0" Background="Transparent"
SelectedItem="{Binding SelectedLine, Mode=OneWay}" SelectionChanged="PaneLines_SelectionChanged"
ItemTemplate="{StaticResource BaseLineTemplate}"
ItemContainerStyle="{StaticResource CodeLineItem}"
VirtualizingPanel.ScrollUnit="Pixel"
HorizontalContentAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="{Binding SourceBars,
RelativeSource={RelativeSource AncestorType=UserControl}}"
ScrollViewer.ScrollChanged="Lines_ScrollChanged"/>
</Grid>
<!--
원본 칸의 가로 스크롤 막대. 3-Way 에서 워드랩을 끄고 긴 줄이 있을 때만 보인다.
ScrollViewer 가 만들어 주는 막대를 쓰지 못하는 이유가 있다 - 이 칸들은
ScrollViewer 를 가로로 굴리지 않고(줄 번호까지 밀려 나가고 세 칸이 어긋난다)
글자만 SourceShiftX 로 민다. 그래서 그 막대는 <b>움직이지 않는 막대</b>가 된다.
세 칸은 늘 같이 움직이므로 세 막대의 손잡이도 같은 자리에 있다.
-->
<ScrollBar Grid.Row="2" Orientation="Horizontal" Height="14"
Minimum="0" SmallChange="24"
LargeChange="{Binding SourceViewport, RelativeSource={RelativeSource AncestorType=UserControl}}"
Maximum="{Binding SourceScrollMax, RelativeSource={RelativeSource AncestorType=UserControl}}"
ViewportSize="{Binding SourceViewport, RelativeSource={RelativeSource AncestorType=UserControl}}"
Value="{Binding SourceScrollValue, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
Visibility="{Binding SourceBarVisibility, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<!-- Diff 요약도 아래 한 줄로 갔다(4차 C-2). 같은 숫자가 세 군데 있었다.
칸마다의 요약은 아래 한 줄에 따로 적는다(12차 B-3) - 파일 전체 요약과 다른 값이다. -->
<!-- 12차 B-3: 이 칸이 저쪽과 무엇이 다른가. 맨 아래 상태 줄의 "파일 전체" 와 다른 값이다. -->
<Border Grid.Row="3" Padding="8,3" BorderThickness="0,1,0,0"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}"
Background="{DynamicResource Ubisam.Brush.Sidebar.Background}">
<TextBlock x:Name="BaseSummaryText" Text="{Binding BaseSummary}" FontSize="11"
TextTrimming="CharacterEllipsis"
Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
</Border>
</Grid>
</Border>
<!-- 칸 사이 틈 전체가 구분선이다(11차 F-10). 바탕은 비워 두고 잇는 선은 LinkOverlay 가 그 위에 그린다. -->
<GridSplitter Grid.Column="3" Grid.Row="0" HorizontalAlignment="Stretch" DragCompleted="PaneSplitter_DragCompleted"
Background="Transparent"/>
<!--
③ LOCAL(2-Way) = MERGED(3-Way).
실제로 고치고 저장하는 칸은 언제나 여기 하나다. 3-Way 에서는 아래 줄로 내려가
폭 전체를 쓰고, 위 세 칸(LOCAL 원본 · BASE · REMOTE)은 읽기 전용 참고가 된다.
-->
<Border BorderThickness="1"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}"
Background="{Binding (TextElement.Foreground), RelativeSource={RelativeSource Self}, Converter={StaticResource PaneBackground}}">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Grid.Row" Value="0"/>
<Setter Property="Grid.Column" Value="4"/>
<Setter Property="Grid.ColumnSpan" Value="1"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsThreeWay}" Value="True">
<Setter Property="Grid.Row" Value="2"/>
<Setter Property="Grid.Column" Value="2"/>
<Setter Property="Grid.ColumnSpan" Value="5"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<!-- 칸 밖으로 그려지지 않게 자른다. 표시층은 RenderTransform 으로 살짝 밀어 놓는데,
그건 배치가 아니라 그리기라 자르지 않으면 <b>아래 상태바 위에 코드가 겹쳐 그려진다.</b>
차이로 이동(Alt+↑↓)할 때 실제로 그렇게 보였다. -->
<Grid ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition x:Name="MergedHeaderRow" Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!--
BASE 와 같은 높이. 두 패널의 줄이 정확히 같은 눈높이에 오도록 맞춘다.
"전체 반영 / 전체 되돌리기" 는 여기 있다가 ACTIONS 로 옮겼다.
이쪽에만 버튼 줄이 하나 더 있으면 그만큼 경로 줄이 아래로 밀려
BASE 의 경로와 높이가 어긋난다. 어차피 줄 단위 반영·되돌리기 버튼이
ACTIONS 에 있으니 같은 성격끼리 한자리에 모이는 편이 낫기도 하다.
-->
<StackPanel Grid.Row="0" Margin="8,6">
<TextBlock Text="{Binding LocalTitle}" Style="{StaticResource PanelTitle}"/>
<!-- BASE 와 같은 처리(4차 C-1). 전체 경로는 툴팁, 복사는 우클릭 메뉴. -->
<TextBlock FontFamily="Consolas" FontSize="12" Margin="0,1,0,0"
TextTrimming="CharacterEllipsis"
Foreground="{DynamicResource Ubisam.Brush.ContentForeground}"
ToolTip="{Binding SelectedFileFullPath}"
ctl:MiddleTrim.Lead="{Binding LocalHeaderLead}" ctl:MiddleTrim.Text="{Binding LocalHeader}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="전체 경로 복사" Click="CopyLocalPath_Click"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</StackPanel>
<!-- 코드 칸을 제 자리 밖으로 못 나가게 자른다 - 표시층 보정은 그리기라 자르지 않으면 바닥 글 위에 겹친다. -->
<Grid Grid.Row="1" ClipToBounds="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!--
아래층은 여백과 색·문법을 그리는 표시용, 위층은 실제로 글자를 고치는 편집기다.
편집기 글자는 투명이라 아래층 색이 그대로 보이고, 캐럿·선택 영역만 편집기가 그린다.
둘의 글꼴·줄 높이가 정확히 같아야 어긋나지 않는다.
편집기는 여백 폭(66)만큼 왼쪽을 비워 두어, 그 자리의 [>] 버튼과
북마크 더블클릭이 아래층까지 닿게 한다.
-->
<Grid Grid.Column="0">
<ListBox x:Name="LocalLines" BorderThickness="0" Background="Transparent"
SelectedItem="{Binding SelectedLine, Mode=OneWay}" SelectionChanged="PaneLines_SelectionChanged"
ItemTemplate="{StaticResource LocalLineTemplate}"
ItemContainerStyle="{StaticResource CodeLineItem}"
VirtualizingPanel.ScrollUnit="Pixel"
HorizontalContentAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.ScrollChanged="Lines_ScrollChanged"/>
<!--
Style="{x:Null}" 이 중요하다. 플랫폼의 암시적 TextBox 스타일은
템플릿 안에서 배경을 직접 칠해서(TemplateBinding 아님) 여기서 준
Background="Transparent" 가 무시되고, 아래층 표시가 덮여 안 보인다.
세로 스크롤 막대를 늘 띄워 두는 이유 - 나타났다 사라지면 글 너비가 바뀌어
워드랩 접히는 자리가 아래층과 어긋난다.
-->
<!--
IsUndoEnabled="False" - 12차 G-1 "undo 가 계속 이어진다".
Ctrl+Z 는 우리 목록(_undo · _textUndo)이 비면 <b>키를 먹지 않는다</b>
(RunShortcut - 그래야 글자 되돌리기가 살아 있다는 뜻이었다). 그러면 그 키가
TextBox 자체 되돌리기로 넘어가는데, WPF 의 그것은 한도가 없고 <b>프로그램이
코드로 넣은 글까지</b> 기록한다. 그래서 짝을 바꿔 새 파일을 실으면 그 글도 기록에
쌓이고, Ctrl+Z 가 <b>앞 파일 글을 편집기에 되살렸다.</b>
거기서 끝이 아니다. 되살아난 앞 파일 글을 Reflow 가 "사용자가 친 것"으로 보고
모델을 그쪽으로 되돌리며 되돌리기에 다시 쌓고 다시하기를 지운다. 다음 Ctrl+Z 는
우리 목록으로 되돌리고, 그러면 또 TextBox 것이 되살리고 - <b>두 상태를 끝없이
오간다.</b> 실측: 짝 A(234자) ↔ 짝 B(228자) 를 40번을 눌러도 안 끝났다.
자체 되돌리기를 끄면 그 고리가 통째로 사라진다. 되돌리기는 우리 목록만 쓴다 -
줄 단위(_undo)와 글 단위(_textUndo, 타이핑이 멈출 때마다 한 걸음).
-->
<TextBox x:Name="LocalEditor" Style="{x:Null}" Margin="{DynamicResource EditorGutterMargin}"
IsUndoEnabled="False"
FontFamily="Consolas" FontSize="{DynamicResource CodeFontSize}"
TextBlock.LineHeight="{DynamicResource CodeLineHeight}"
TextBlock.LineStackingStrategy="BlockLineHeight"
AcceptsReturn="True" AcceptsTab="True"
TextWrapping="{Binding WrapMode,
RelativeSource={RelativeSource AncestorType=UserControl}}"
Background="Transparent" BorderThickness="0" Padding="2,0,0,0"
Foreground="Transparent" SelectionTextBrush="Transparent"
CaretBrush="{DynamicResource Ubisam.Brush.ContentForeground}"
SelectionBrush="{DynamicResource Ubisam.Brush.Accent}"
SelectionOpacity="0.35"
VerticalContentAlignment="Top"
VerticalScrollBarVisibility="Visible"
HorizontalScrollBarVisibility="{Binding HorizontalBars,
RelativeSource={RelativeSource AncestorType=UserControl}}"
IsReadOnly="{Binding IsReadOnly}"
SelectionChanged="LocalEditor_SelectionChanged"
TextChanged="LocalEditor_TextChanged"/>
<!--
찾기 칸. 편집기 위에 떠 있게 둔다 - 패널 머리나 바닥에 끼워 넣으면
열 때마다 BASE 와 높이가 어긋나 두 패널의 같은 행이 다른 눈높이로 간다.
-->
<Border x:Name="FindBar" Visibility="Collapsed"
HorizontalAlignment="Right" VerticalAlignment="Top"
Margin="0,4,20,0" Padding="6,4" CornerRadius="3"
Background="{DynamicResource Ubisam.Brush.Input.Background}"
BorderBrush="{DynamicResource Ubisam.Brush.Accent}" BorderThickness="1"
Effect="{DynamicResource Ubisam.Effect.Popup}">
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBox x:Name="FindBox" Width="180" Height="24" MinHeight="24"
FontSize="11" VerticalContentAlignment="Center"
KeyDown="FindBox_KeyDown"
ToolTip="찾을 말 (Enter 다음 · Shift+Enter 이전 · Esc 닫기)"/>
<TextBlock x:Name="FindStatus" Style="{StaticResource Muted}" FontSize="11"
VerticalAlignment="Center" Margin="6,0,2,0" MinWidth="42"/>
<Button Content="&#x25B2;" Click="FindPrev_Click" Padding="6,0" MinHeight="24"
FontSize="10" ToolTip="이전 찾기 (Shift+F3)"/>
<Button Content="&#x25BC;" Click="FindNext_Click" Padding="6,0" MinHeight="24"
FontSize="10" Margin="2,0,6,0" ToolTip="다음 찾기 (F3)"/>
<CheckBox x:Name="FindMatchCase" Content="Aa" FontSize="11"
VerticalAlignment="Center" Margin="0,0,6,0"
ToolTip="대소문자를 가려서 찾습니다"/>
<Button Content="&#x2715;" Click="CloseFind_Click" Padding="6,0" MinHeight="24"
FontSize="10" Background="Transparent" BorderThickness="0"
ToolTip="닫기 (Esc)"/>
</StackPanel>
<!-- 바꾸기 줄은 Ctrl+H 로 열 때만 뜬다. 찾기만 할 때 자리를 뺏지 않는다. -->
<StackPanel x:Name="ReplaceRow" Orientation="Horizontal" Margin="0,4,0,0"
Visibility="Collapsed">
<TextBox x:Name="ReplaceBox" Width="180" Height="24" MinHeight="24"
FontSize="11" VerticalContentAlignment="Center"
ToolTip="바꿀 말"/>
<Button Content="바꾸기" Click="Replace_Click" Padding="8,0" MinHeight="24"
FontSize="11" Margin="6,0,2,0"
ToolTip="고른 자리를 바꾸고 다음으로 넘어갑니다"/>
<Button Content="모두" Click="ReplaceAll_Click" Padding="8,0" MinHeight="24"
FontSize="11"
ToolTip="글 전체에서 한 번에 바꿉니다 (Ctrl+Z 로 되돌립니다)"/>
</StackPanel>
</StackPanel>
</Border>
</Grid>
<!-- 목업 ③ 코드 미니맵 -->
<ctl:ChangeMiniMap Grid.Column="1" x:Name="MiniMap"
Source="{Binding Lines}"
Version="{Binding DiffVersion}"
SelectedLine="{Binding SelectedLine}"
LineRequested="MiniMap_LineRequested"/>
</Grid>
<!-- 상태바는 창 아래 한 줄로 옮겼다(4차 C-2). 두 패널이 같은 높이를 갖게 된다.
칸마다의 요약만 여기 한 줄로 적는다(12차 B-3). -->
<!-- 12차 B-3: 이 칸이 저쪽과 무엇이 다른가. 맨 아래 상태 줄의 "파일 전체" 와 다른 값이다. -->
<Border Grid.Row="2" Padding="8,3" BorderThickness="0,1,0,0"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}"
Background="{DynamicResource Ubisam.Brush.Sidebar.Background}">
<TextBlock x:Name="LocalSummaryText" Text="{Binding LocalSummary}" FontSize="11"
TextTrimming="CharacterEllipsis"
Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
</Border>
</Grid>
</Border>
<GridSplitter Grid.Column="5" Grid.Row="0" HorizontalAlignment="Stretch" DragCompleted="PaneSplitter_DragCompleted"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
Background="Transparent"/>
<!--
③-2 REMOTE (3-Way). 머리·바닥 높이를 BASE/LOCAL 과 똑같이 맞춰야
세 패널의 같은 행이 같은 눈높이에 온다.
-->
<Border Grid.Column="6" Grid.Row="0" BorderThickness="1"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}"
Background="{Binding (TextElement.Foreground), RelativeSource={RelativeSource Self}, Converter={StaticResource PaneBackground}}">
<!-- 칸 밖으로 그려지지 않게 자른다. 표시층은 RenderTransform 으로 살짝 밀어 놓는데,
그건 배치가 아니라 그리기라 자르지 않으면 <b>아래 상태바 위에 코드가 겹쳐 그려진다.</b>
차이로 이동(Alt+↑↓)할 때 실제로 그렇게 보였다. -->
<Grid ClipToBounds="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" SharedSizeGroup="PaneHeader"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<!-- 12차 B-3: 칸 아래 한 줄 요약 -->
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- BASE/LOCAL 과 같은 머리 높이. 세 패널의 01행이 같은 눈높이에 와야 한다.
RowDefinition 이 아니라 StackPanel 에 거는 이유 - 리소스가 Double 이라
GridLength 자리에 넣으면 화면을 만들 때 그대로 터진다. -->
<StackPanel Grid.Row="0" Margin="8,6">
<TextBlock Text="REMOTE(Theirs)" Style="{StaticResource PanelTitle}"/>
<!-- 상대 쪽 · 파일(11차 F-1). 큰 제목이 REMOTE 라고 말하므로 "REMOTE · " 를 되풀이하지 않는다. -->
<TextBlock ctl:MiddleTrim.Text="{Binding RemoteHeader}" ToolTip="{Binding RemoteHeader}"
FontFamily="Consolas" FontSize="12" Margin="0,1,0,0"
TextTrimming="CharacterEllipsis"
Foreground="{DynamicResource Ubisam.Brush.ContentForeground}"/>
<StackPanel Orientation="Horizontal" Margin="0,4,0,0">
<TextBlock Text="{Binding ConflictCount, StringFormat=Conflict {0}}"
FontSize="11" FontWeight="Bold"
Foreground="{DynamicResource Ubisam.Brush.Danger}"/>
<TextBlock Text=" · " Style="{StaticResource Muted}" FontSize="11"/>
<TextBlock Text="{Binding RemoteChangeCount, StringFormat=Theirs only {0}}"
Style="{StaticResource Muted}" FontSize="11"/>
</StackPanel>
</StackPanel>
<!-- 코드 칸을 제 자리 밖으로 못 나가게 자른다. 표시층의 미세 보정은 배치가 아니라
그리기라 자르지 않으면 아래 바닥 글 위에 코드가 겹쳐 그려진다. -->
<Grid Grid.Row="1" ClipToBounds="True">
<ListBox x:Name="RemoteLines" BorderThickness="0" Background="Transparent"
SelectedItem="{Binding SelectedLine, Mode=OneWay}" SelectionChanged="PaneLines_SelectionChanged"
ItemTemplate="{StaticResource RemoteLineTemplate}"
ItemContainerStyle="{StaticResource CodeLineItem}"
VirtualizingPanel.ScrollUnit="Pixel"
HorizontalContentAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="{Binding SourceBars,
RelativeSource={RelativeSource AncestorType=UserControl}}"
ScrollViewer.ScrollChanged="Lines_ScrollChanged"/>
</Grid>
<!--
원본 칸의 가로 스크롤 막대. 3-Way 에서 워드랩을 끄고 긴 줄이 있을 때만 보인다.
ScrollViewer 가 만들어 주는 막대를 쓰지 못하는 이유가 있다 - 이 칸들은
ScrollViewer 를 가로로 굴리지 않고(줄 번호까지 밀려 나가고 세 칸이 어긋난다)
글자만 SourceShiftX 로 민다. 그래서 그 막대는 <b>움직이지 않는 막대</b>가 된다.
세 칸은 늘 같이 움직이므로 세 막대의 손잡이도 같은 자리에 있다.
-->
<ScrollBar Grid.Row="2" Orientation="Horizontal" Height="14"
Minimum="0" SmallChange="24"
LargeChange="{Binding SourceViewport, RelativeSource={RelativeSource AncestorType=UserControl}}"
Maximum="{Binding SourceScrollMax, RelativeSource={RelativeSource AncestorType=UserControl}}"
ViewportSize="{Binding SourceViewport, RelativeSource={RelativeSource AncestorType=UserControl}}"
Value="{Binding SourceScrollValue, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"
Visibility="{Binding SourceBarVisibility, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<Border Grid.Row="2" Padding="8,5" BorderThickness="0,1,0,0"
Height="{StaticResource PaneFooterHeight}"
Visibility="{Binding IsTwoWay, Converter={StaticResource BoolToVis}}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
<StackPanel VerticalAlignment="Center">
<TextBlock Text="저쪽 변경" Style="{StaticResource Muted}" FontSize="11"/>
<TextBlock FontSize="11">
<Run Text="{Binding RemoteChangeCount, Mode=OneWay}"/>
<Run Text="줄은 받아들이기만 하면 됩니다"/>
</TextBlock>
</StackPanel>
</Border>
<!-- 12차 B-3: 이 칸이 저쪽과 무엇이 다른가. 맨 아래 상태 줄의 "파일 전체" 와 다른 값이다. -->
<Border Grid.Row="3" Padding="8,3" BorderThickness="0,1,0,0"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}"
Background="{DynamicResource Ubisam.Brush.Sidebar.Background}">
<TextBlock x:Name="RemoteSummaryText" Text="{Binding RemoteSummary}" FontSize="11"
TextTrimming="CharacterEllipsis"
Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
</Border>
</Grid>
</Border>
<!-- 원본 세 칸과 결과 칸 사이. 3-Way 일 때만 자리를 차지한다. -->
<GridSplitter Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="5"
Height="4" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
Background="{DynamicResource Ubisam.Brush.Table.GridLine}"/>
<!--
12차 B-3: 가운데(칸 사이 틈) 아래에 진행을 적는다.
틈이 48 DIP 뿐이라 두 줄로 짧게 - "3 / 7"(몇 번째 차이) 과 "남은 2"(아직 다른 줄 수).
잇는 선 그림판보다 앞에 두어 선에 가리지 않게 한다.
-->
<StackPanel Grid.Row="0" Grid.Column="3" VerticalAlignment="Bottom" HorizontalAlignment="Center"
Margin="0,0,0,6" Panel.ZIndex="6" IsHitTestVisible="False">
<TextBlock x:Name="DiffProgressText" Text="{Binding DiffProgress}" FontSize="11" FontWeight="Bold"
HorizontalAlignment="Center"
Foreground="{DynamicResource Ubisam.Brush.ContentForeground}"/>
<TextBlock x:Name="DiffRemainingText" Text="{Binding DiffRemaining}" FontSize="10"
HorizontalAlignment="Center"
Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
</StackPanel>
<!-- 잇는 선(11차 F-10). 위 칸들 위에 겹친 투명한 그림판 - 마우스는 통과한다. -->
<ctl:PaneLinkOverlay x:Name="LinkOverlay" Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="5" Panel.ZIndex="5"/>
<GridSplitter x:Name="ActionsSplitter" Grid.Column="7" Grid.RowSpan="3" Width="4" HorizontalAlignment="Stretch" DragCompleted="PaneSplitter_DragCompleted"
Background="{DynamicResource Ubisam.Brush.Table.GridLine}"/>
<!-- ④ ACTIONS -->
<Border x:Name="ActionsPanel" Grid.Column="8" Grid.RowSpan="3" BorderThickness="1" Padding="8"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
<DockPanel>
<!-- 설정 "저장할 때 백업" 을 켰을 때만(11차 F-9). 맨 아래 - 저장 단추들 밑. -->
<Button DockPanel.Dock="Bottom" Content="백업 폴더 열기" Margin="0,6,0,0" FontSize="11"
Command="{Binding OpenBackupFolderCommand}"
Visibility="{Binding IsBackupOn, Converter={StaticResource BoolToVis}}"
ToolTip="저장할 때 남긴 백업을 모아 둔 폴더를 엽니다. 날짜 폴더마다 목록.txt 에 원본과 백업이 적혀 있습니다"/>
<!-- 왼쪽 파일에 쓰는 단추(12차 B-1). 두 파일 비교에서만 보이고, 왼쪽에 고친 것이 없으면 꺼진다. -->
<Button DockPanel.Dock="Bottom" Content="◀ 왼쪽 저장" MinHeight="32" Margin="0,8,0,0" FontSize="11"
Command="{Binding SaveLeftPlainCommand}"
Visibility="{Binding BothWays, Converter={StaticResource BoolToVis}}"
ToolTip="왼쪽으로 반영한 내용을 왼쪽 파일에 씁니다"/>
<!-- Git 이 아니면 커밋할 곳이 없다. 파일에 쓰기만 한다. -->
<Button DockPanel.Dock="Bottom" Content="오른쪽 저장 ▶" MinHeight="38" Margin="0,8,0,0"
Command="{Binding SavePlainCommand}"
Visibility="{Binding IsPlainMode, Converter={StaticResource BoolToVis}}"
Style="{StaticResource FilledButton}"
Background="{DynamicResource Ubisam.Brush.Success}"
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"
FontWeight="Bold"
ToolTip="고친 내용을 오른쪽 파일에 씁니다"/>
<Button DockPanel.Dock="Bottom" Content="Save &amp; Commit" MinHeight="38" Margin="0,8,0,0"
Command="{Binding SaveAndCommitCommand}"
Visibility="{Binding IsGitMode, Converter={StaticResource BoolToVis}}"
Style="{StaticResource FilledButton}"
Background="{DynamicResource Ubisam.Brush.Success}"
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"
FontWeight="Bold"
ToolTip="LOCAL 내용을 파일에 쓰고 스테이징 후 커밋"/>
<!-- 병합 중에만 보인다. 이 파일만 풀어서 저장하고 커밋은 하지 않는다(6차 A-2).
Git 모드가 아니면 병합도 없으므로 바깥에서 한 번 더 가린다. -->
<StackPanel DockPanel.Dock="Bottom"
Visibility="{Binding IsGitMode, Converter={StaticResource BoolToVis}}">
<Button Content="Save" MinHeight="34" Margin="0,8,0,0"
Command="{Binding SaveResolvedCommand}"
Visibility="{Binding Workspace.IsMergeInProgress, Converter={StaticResource BoolToVis}}"
Style="{StaticResource FilledButton}"
FontWeight="Bold"
ToolTip="이 파일만 풀어서 저장하고 스테이징합니다. 커밋은 하지 않습니다 (Ctrl+S)"/>
</StackPanel>
<!-- 창이 낮거나 커밋 메시지가 길어지면 아래가 잘리므로 스크롤을 둔다. -->
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel>
<TextBlock Text="ACTIONS" Style="{StaticResource Muted}" FontSize="11" Margin="0,0,0,4"/>
<UniformGrid Columns="2">
<Button Content="Undo" Command="{Binding UndoCommand}"/>
<Button Content="Redo" Command="{Binding RedoCommand}"/>
</UniformGrid>
<!-- LOCAL 머리에 있던 것을 옮겨 왔다. 줄 단위 반영과 성격이 같아 여기가 맞고,
옮긴 덕에 BASE 와 LOCAL 의 경로 줄 높이가 같아졌다. -->
<TextBlock Text="파일 전체" Style="{StaticResource Muted}" FontSize="11" Margin="0,12,0,4"/>
<!-- 두 칸으로 나누면 "전체 되돌리기" 글자가 잘린다. 한 줄에 하나씩 둔다. -->
<Button Content="전체 반영" Command="{Binding AcceptAllCommand}" MinHeight="28"
FontSize="11" ToolTipService.ShowOnDisabled="True"
ToolTip="차이나는 모든 줄을 BASE 내용으로 - 구간마다 주석 한 줄, Undo 한 번에 전체가 되돌아갑니다. 3-Way 에서는 쓰지 않습니다(저쪽만 고친 줄은 [자동 병합], 충돌은 한 줄씩)"/>
<Button Content="전체 되돌리기" Command="{Binding RevertAllCommand}" MinHeight="28"
FontSize="11" Margin="0,4,0,0" ToolTip="파일을 연 시점으로 되돌립니다"/>
<TextBlock Text="선택 라인 반영" Style="{StaticResource Muted}" FontSize="11" Margin="0,12,0,4"/>
<Button Content="Accept" Command="{Binding AcceptCommand}" MinHeight="30"
Style="{StaticResource FilledButton}"
Background="{DynamicResource Ubisam.Brush.Accent.Fill}"
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"
ToolTip="선택한 줄을 BASE 내용으로"/>
<!-- 3-Way 일 때만 뜬다. -->
<Button Content="Take Mine" Command="{Binding AcceptLocalCommand}"
MinHeight="30" Margin="2,4,2,0"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
Style="{StaticResource FilledButton}"
Background="{DynamicResource Ubisam.Brush.Accent.Fill}"
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"
ToolTip="선택한 줄을 LOCAL(Mine) 내용으로"/>
<Button Content="Take Theirs" Command="{Binding AcceptRemoteCommand}"
MinHeight="30" Margin="2,4,2,0"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
Style="{StaticResource FilledButton}"
Background="{DynamicResource Ubisam.Brush.Accent.Fill}"
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"
ToolTip="선택한 줄을 REMOTE(Theirs) 내용으로"/>
<Button Content="구간 반영" x:Name="AcceptBlockButton" Command="{Binding AcceptBlockCommand}"
MinHeight="30" Margin="2,4,2,0" FontSize="11"
ToolTip="차이가 이어지는 구간을 한 번에 반영합니다 (Alt+Shift+Right)"/>
<!-- 12차 G-2: 위 단추의 거울. 두 파일 비교(BothWays)에서만 뜬다 -
Git 모드의 왼쪽은 지난 커밋이라 쓸 파일이 없다. -->
<Button Content="구간 왼쪽으로" x:Name="AcceptBlockToBaseButton"
Command="{Binding AcceptBlockToBaseCommand}"
MinHeight="30" Margin="2,4,2,0" FontSize="11"
Visibility="{Binding BothWays, Converter={StaticResource BoolToVis}}"
ToolTip="차이가 이어지는 구간을 한 번에 왼쪽 파일로 보냅니다 (Alt+Shift+Left)"/>
<!-- 11차 F-2: 캐럿이 놓인 클래스 멤버를 통째로. 못 잡으면 꺼지고 아래 한 줄이 까닭을 말한다. -->
<Button Content="이 메서드 반영" Command="{Binding AcceptMemberCommand}"
MinHeight="30" Margin="2,4,2,0" FontSize="11"
ToolTipService.ShowOnDisabled="True"
ToolTip="{Binding MemberHint}"/>
<TextBlock Text="{Binding MemberHint}" Style="{StaticResource Muted}" FontSize="10"
Margin="4,2,2,0" TextWrapping="Wrap"/>
<!--
저장 형식. 기본은 "원본 유지"라 지금까지와 똑같이 나간다.
사내 소스에 CP949 가 섞여 있어, 새로 만드는 파일과 형식이 갈릴 때 여기서 맞춘다.
-->
<!--
한 번 정하면 그대로인 것들은 접어 둔다(4차 C-5).
매번 쓰는 것(반영 · 되돌리기 · 차이 이동 · 저장)과 섞여 있어 오른쪽 칸이 길었다.
단추 글자가 무엇이 숨어 있는지 말한다 - 화살표만으로는 전해지지 않는다(C-3 · C-4).
-->
<!--
3-Way 흐름. "안 겹치는 변경은 자동으로, 사람은 충돌만" 이 순서다.
남은 충돌이 0 이 되면 그 자리에서 다 됐다고 알려 준다.
-->
<Border Margin="2,10,2,0" Padding="6,4" CornerRadius="3"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
Background="{DynamicResource Ubisam.Brush.Input.Background}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}" BorderThickness="1">
<TextBlock Text="{Binding MergeStatusText}" FontWeight="Bold" FontSize="11.5"
HorizontalAlignment="Center"
ToolTip="아직 사람이 골라야 하는 줄 수입니다">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource Ubisam.Brush.Danger}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsMergeComplete}" Value="True">
<Setter Property="Foreground" Value="{DynamicResource Ubisam.Brush.Success}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
<Button Content="자동 병합" Command="{Binding AutoMergeCommand}"
MinHeight="30" Margin="2,4,2,0"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
ToolTip="저쪽만 고친 줄을 한 번에 가져옵니다. 충돌과 내가 고친 줄은 건드리지 않습니다"/>
<UniformGrid Columns="2" Margin="0,4,0,0"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}">
<Button Content="이전 충돌" Command="{Binding PrevConflictCommand}"
MinHeight="28" FontSize="11"
ToolTip="아직 안 정한 충돌만 딛습니다 (Alt+Shift+Up)"/>
<Button Content="다음 충돌" Command="{Binding NextConflictCommand}"
MinHeight="28" FontSize="11"
ToolTip="아직 안 정한 충돌만 딛습니다 (Alt+Shift+Down)"/>
</UniformGrid>
<Button Content="Revert" Command="{Binding RevertCommand}" MinHeight="30" Margin="2,4,2,0"
Style="{StaticResource FilledButton}"
Background="{DynamicResource Ubisam.Brush.Warning}"
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"
ToolTip="선택한 줄의 마지막 편집만 취소"/>
<!-- 머지 주석: 설명은 선택, 작성자·날짜는 항상 들어간다 -->
<TextBlock Text="머지 설명 (선택)" Style="{StaticResource Muted}" FontSize="11" Margin="0,12,0,4"/>
<TextBox Text="{Binding MergeComment, UpdateSourceTrigger=PropertyChanged}"
AcceptsReturn="False" MinHeight="24"
ToolTip="반영한 줄 끝에 주석으로 붙습니다. 비워두면 작성자·날짜만 들어갑니다."/>
<TextBlock Text="{Binding MergeNotePreview}" Style="{StaticResource Muted}"
FontSize="10" FontFamily="Consolas" TextWrapping="Wrap" Margin="0,3,0,0"
ToolTip="{Binding MergeNotePreview}"/>
<TextBlock Text="커밋 상세" Style="{StaticResource Muted}" FontSize="11" Margin="0,12,0,4"
Visibility="{Binding IsGitMode, Converter={StaticResource BoolToVis}}"/>
<Grid Visibility="{Binding IsGitMode, Converter={StaticResource BoolToVis}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Hash" Style="{StaticResource Muted}" FontSize="11"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding CommitHash}" FontFamily="Consolas"
FontSize="11" TextAlignment="Right"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Date" Style="{StaticResource Muted}" FontSize="11"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding CommitDate}" FontSize="11" TextAlignment="Right"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Author" Style="{StaticResource Muted}" FontSize="11"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding CommitAuthor}" FontSize="11"
TextAlignment="Right" TextTrimming="CharacterEllipsis"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Branch" Style="{StaticResource Muted}" FontSize="11"/>
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding CommitBranch}" FontSize="11"
TextAlignment="Right" TextTrimming="CharacterEllipsis"/>
</Grid>
<TextBlock Text="커밋 메시지" Style="{StaticResource Muted}" FontSize="11" Margin="0,12,0,4"
Visibility="{Binding IsGitMode, Converter={StaticResource BoolToVis}}"/>
<TextBox Text="{Binding CommitMessage, UpdateSourceTrigger=PropertyChanged}"
AcceptsReturn="True" TextWrapping="Wrap" MinHeight="60"
Visibility="{Binding IsGitMode, Converter={StaticResource BoolToVis}}"
VerticalScrollBarVisibility="Auto"/>
<!--
차이 탐색과 비교 규칙. 코드 패널 위가 아니라 여기 둔 이유는,
읽는 자리(코드)와 조작하는 자리(오른쪽 패널)를 섞지 않기 위해서다.
-->
<TextBlock Text="차이 이동" Style="{StaticResource Muted}" FontSize="11" Margin="0,14,0,4"/>
<!-- 화살표 글리프는 이 서체에 없어 빈 칸으로 나온다. 글자로 적는다. -->
<UniformGrid Columns="2">
<Button Content="이전 차이" Command="{Binding PrevDiffCommand}" MinHeight="28" FontSize="11"
ToolTip="이전 차이 (Alt+Up)"/>
<Button Content="다음 차이" Command="{Binding NextDiffCommand}" MinHeight="28" FontSize="11"
ToolTip="다음 차이 (Alt+Down)"/>
<Button Content="처음" Command="{Binding FirstDiffCommand}" MinHeight="28" FontSize="11"
Margin="2,4,2,0" ToolTip="첫 차이 (Ctrl+Home)"/>
<Button Content="마지막" Command="{Binding LastDiffCommand}" MinHeight="28" FontSize="11"
Margin="2,4,2,0" ToolTip="마지막 차이 (Ctrl+End)"/>
</UniformGrid>
<Grid Margin="0,4,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="이동" Style="{StaticResource Muted}"
FontSize="11" Margin="2,0,6,0"/>
<TextBox Grid.Column="1" x:Name="GoToBox" MinHeight="26" FontSize="11"
VerticalContentAlignment="Center"
Text="{Binding GoToText, UpdateSourceTrigger=PropertyChanged}"
KeyDown="GoToBox_KeyDown"
ToolTip="줄 번호로 이동 (Ctrl+G)"/>
</Grid>
<!--
여기서부터가 <b>한 번 정하면 그대로인 것들</b>이다(4차 C-5).
위쪽(반영 · 되돌리기 · 차이 이동 · 저장)과 성격이 달라 한자리에 모으고 접었다.
단추 글자가 무엇이 숨어 있는지 말한다 - 화살표만으로는 전해지지 않는다(C-3 · C-4).
-->
<Button Content="{Binding RulesToggleLabel}" Command="{Binding ToggleRulesCommand}"
MinHeight="26" FontSize="11" FontWeight="Bold" Margin="2,14,2,0"
Cursor="Hand"
Foreground="{DynamicResource Ubisam.Brush.Accent}"
ToolTip="저장 형식 · 비교 규칙 · 편집 잠금을 접거나 폅니다. 접힌 상태는 다음에 켤 때도 그대로입니다"/>
<TextBlock Text="저장 형식" Style="{StaticResource Muted}" FontSize="11" Margin="0,10,0,4"
Visibility="{Binding IsRulesOpen, Converter={StaticResource BoolToVis}}"/>
<UniformGrid Columns="2" Visibility="{Binding IsRulesOpen, Converter={StaticResource BoolToVis}}">
<!-- 12차 G-4: 보이는 글(Label)과 저장되는 값(Value)을 가른다. 값은 세션 보관본에 그대로 적힌다. -->
<ComboBox ItemsSource="{Binding SaveEncodings}" x:Name="SaveEncodingBox"
DisplayMemberPath="Label" SelectedValuePath="Value" SelectedValue="{Binding SaveEncoding}"
MinHeight="26" FontSize="11" Margin="2,0"
ToolTip="저장할 때 쓸 인코딩"/>
<ComboBox ItemsSource="{Binding SaveNewLines}" x:Name="SaveNewLineBox"
DisplayMemberPath="Label" SelectedValuePath="Value" SelectedValue="{Binding SaveNewLine}"
MinHeight="26" FontSize="11" Margin="2,0"
ToolTip="저장할 때 쓸 줄바꿈"/>
</UniformGrid>
<TextBlock Text="비교 규칙" Style="{StaticResource Muted}" FontSize="11" Margin="0,10,0,4"
Visibility="{Binding IsRulesOpen, Converter={StaticResource BoolToVis}}"/>
<UniformGrid Columns="2" Visibility="{Binding IsRulesOpen, Converter={StaticResource BoolToVis}}">
<Button Content="{Binding WhitespaceLabel}" Command="{Binding CycleWhitespaceCommand}"
MinHeight="28" FontSize="11"
ToolTip="공백을 얼마나 무시할지 - 전부 비교 → 개수만 무시 → 전부 무시"/>
<Button Content="{Binding IgnoreCaseLabel}" Command="{Binding ToggleIgnoreCaseCommand}"
MinHeight="28" FontSize="11"
ToolTip="대소문자만 다른 줄을 같게 볼지"/>
<Button Content="{Binding LineFilterLabel}" Command="{Binding ToggleLineFilterCommand}"
MinHeight="28" FontSize="11" Margin="2,4,2,0"
ToolTip="설정에 적어 둔 정규식에 걸리는 줄을 차이에서 뺍니다"/>
<Button Content="{Binding WrapLabel}" Click="ToggleWrap_Click"
MinHeight="28" FontSize="11" Margin="2,4,2,0"
ToolTip="긴 줄을 접어서 보여줍니다 (가로 스크롤 대신)"/>
</UniformGrid>
<!-- C# 코드 스타일 맞춤(6차 추가). 중괄호 줄바꿈만 달라 차이가 많이 뜨는 것을 막는다. -->
<ComboBox ItemsSource="{Binding CSharpStyleNames}" SelectedIndex="{Binding CSharpStyleIndex}"
MinHeight="26" FontSize="11" Margin="2,4,2,0"
Visibility="{Binding IsRulesOpen, Converter={StaticResource BoolToVis}}"
ToolTip="C# 파일의 여는 중괄호 · else/catch/finally 줄바꿈을 이 스타일로 맞춘 뒤 비교합니다.&#x0a;AURORA: 형식 · 메서드 중괄호만 새 줄, 나머지는 같은 줄, } else {&#x0a;Allman: 모두 새 줄 (Visual Studio 기본값)&#x0a;편집 칸 글이 바뀌면 저장할 때 그 스타일로 저장됩니다."/>
<Button Content="{Binding ReadOnlyLabel}" Click="ToggleReadOnly_Click"
MinHeight="28" FontSize="11" Margin="2,4,2,0"
Visibility="{Binding IsRulesOpen, Converter={StaticResource BoolToVis}}"
ToolTip="실수로 고치는 것을 막습니다"/>
</StackPanel>
</ScrollViewer>
</DockPanel>
</Border>
</Grid>
<!--
④ 상태바 — 창 아래 한 줄.
<b>왜 옮겼나</b> - 예전에는 세 군데에 흩어져 있었다.
BASE 바닥의 "총 43 라인 · 차이 10 라인", MERGED 바닥 첫 줄의 "1행 1열 | 차이 -/5 | UTF-8",
그 아래 줄의 "편집 0건 · 되돌리기 0건 · 남은 차이 10 라인". 같은 뜻의 숫자가 세 벌이라
어느 것을 봐야 할지 헷갈렸고, 패널마다 54px 를 먹었다.
<b>자리 규칙</b> - 왼쪽은 지금 어디에 있는지, 가운데는 얼마나 남았는지, 오른쪽은 이 파일이 무엇인지.
패널 밖으로 나오면서 두 패널의 코드 영역 높이가 저절로 같아진다.
-->
<Border Grid.Row="3" Padding="8,4" BorderThickness="1,1,1,1" Margin="0,2,0,0"
Background="{DynamicResource Ubisam.Brush.Sidebar.Background}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- 왼쪽: 지금 어디 -->
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{Binding CaretPosition}" Style="{StaticResource Mono}" FontSize="11"/>
<!-- 11차 F-5: 기본(12)이 아닐 때만 보인다. 누르면 기본으로(Ctrl+0 과 같음). -->
<TextBlock Text="{Binding CodeFontLabel}" FontSize="11" Margin="10,0,0,0" Cursor="Hand"
Style="{StaticResource Muted}" ToolTip="코드 글자 크기 - Ctrl+휠 · Ctrl++/- 로 바꾸고, 누르거나 Ctrl+0 이면 기본(12)">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftClick" Command="{Binding FontResetCommand}"/>
</TextBlock.InputBindings>
</TextBlock>
<Border Width="1" Margin="10,1" Background="{DynamicResource Ubisam.Brush.Input.Border}"/>
<!--
차이 이동을 개수 바로 옆에 둔다(4차 C-5).
가장 자주 누르는 것인데 오른쪽 칸 끝에 있었고, 정작 "몇 번째인지"는 여기 있었다.
보는 자리와 누르는 자리가 갈려 있으면 눈과 손이 따로 움직인다.
오른쪽 칸의 큰 단추는 그대로 둔다 - 쓰던 사람 손을 빼앗지 않는다.
-->
<Button Content="◀" Command="{Binding PrevDiffCommand}"
Style="{StaticResource LegendButton}" FontSize="11" FontWeight="Bold" Margin="6,0,0,0"
ToolTip="이전 차이 (Alt+Up)"/>
<TextBlock Text="{Binding DiffPosition}" FontSize="11" FontWeight="Bold" Margin="2,0"
Foreground="{DynamicResource Ubisam.Brush.Accent}"/>
<Button Content="▶" Command="{Binding NextDiffCommand}"
Style="{StaticResource LegendButton}" FontSize="11" FontWeight="Bold"
ToolTip="다음 차이 (Alt+Down)"/>
</StackPanel>
<!-- 가운데: 얼마나 남았나 -->
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center"
Margin="14,0,14,0">
<TextBlock Text="{Binding DiffSummary}" FontSize="12" VerticalAlignment="Center"/>
<TextBlock Text="·" Style="{StaticResource Muted}" FontSize="11" Margin="6,0"
Visibility="{Binding EditSummary, Converter={StaticResource TextToVis}}"/>
<TextBlock Text="{Binding EditSummary}" FontSize="12" VerticalAlignment="Center"/>
</StackPanel>
<!-- 오른쪽: 이 파일이 무엇인가 -->
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{Binding FileEncoding}" Style="{StaticResource Muted}" FontSize="11"/>
<TextBlock Text="·" Style="{StaticResource Muted}" FontSize="11" Margin="5,0"/>
<TextBlock Text="{Binding FileNewLine}" Style="{StaticResource Muted}" FontSize="11"/>
<TextBlock Text="·" Style="{StaticResource Muted}" FontSize="11" Margin="5,0"/>
<TextBlock Text="{Binding LanguageName}" Style="{StaticResource Muted}" FontSize="11"
ToolTip="확장자로 고른 언어입니다. 색칠과 코드 검사가 이 값을 따릅니다"/>
<Border Width="1" Margin="10,1" Background="{DynamicResource Ubisam.Brush.Input.Border}"
Visibility="{Binding RulesSummary, Converter={StaticResource TextToVis}}"/>
<TextBlock Text="{Binding RulesSummary}" FontSize="11"
Foreground="{DynamicResource Ubisam.Brush.Muted}"
Visibility="{Binding RulesSummary, Converter={StaticResource TextToVis}}"/>
<Border Width="1" Margin="10,1" Background="{DynamicResource Ubisam.Brush.Input.Border}"/>
<TextBlock Text="코드 검사" Style="{StaticResource Muted}" FontSize="11" Margin="0,0,6,0"/>
<ToggleButton x:Name="ProblemToggle" Padding="6,1" FontSize="11" FontWeight="Bold"
MinHeight="22" BorderThickness="0" Background="Transparent"
IsEnabled="{Binding HasProblems}"
ToolTip="눌러서 확인이 필요한 줄을 봅니다">
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Setter Property="Foreground" Value="{DynamicResource Ubisam.Brush.Success}"/>
<Setter Property="Cursor" Value="Arrow"/>
<Style.Triggers>
<DataTrigger Binding="{Binding HasProblems}" Value="True">
<Setter Property="Foreground" Value="{DynamicResource Ubisam.Brush.Danger}"/>
<Setter Property="Cursor" Value="Hand"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
<TextBlock Text="{Binding ProblemSummary}"/>
</ToggleButton>
<Popup IsOpen="{Binding IsChecked, ElementName=ProblemToggle}"
PlacementTarget="{Binding ElementName=ProblemToggle}"
Placement="Top" StaysOpen="False" AllowsTransparency="True"
HorizontalOffset="-8" VerticalOffset="-4">
<Border Background="{DynamicResource Ubisam.Brush.Input.Background}"
BorderBrush="{DynamicResource Ubisam.Brush.Danger}"
BorderThickness="1" Padding="4" MaxWidth="620" MaxHeight="240"
Effect="{DynamicResource Ubisam.Effect.Popup}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Problems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding DataContext.GoToProblemCommand,
RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
HorizontalContentAlignment="Left"
Background="Transparent" BorderThickness="0"
Padding="6,3" Margin="0"
ToolTip="누르면 그 줄로 이동합니다">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Where}" FontFamily="Consolas"
FontSize="11" FontWeight="Bold" MinWidth="46"
Foreground="{DynamicResource Ubisam.Brush.Danger}"/>
<TextBlock Text="{Binding Message}" FontSize="11"
TextWrapping="Wrap" Margin="6,0,0,0"/>
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Border>
</Popup>
</StackPanel>
</Grid>
</Border>
</Grid>
</DockPanel>
</UserControl>