ksj09406 55d9deb418 이진 파일·그림 비교 (Services/BinaryCompare)
"줄 단위로 비교할 수 없습니다" 한 줄로 끝내면 정작 알고 싶은 "같은지 다른지" 를
모른 채 화면을 닫게 된다. 답이 있는 화면으로 바꿨다.

- 크기 · SHA-1 · 처음 갈리는 바이트 위치를 한 줄로 알려 준다. 크기가 같아도 내용이
  다를 수 있어 해시로 본다. 앞부분이 같고 길이만 다르면 짧은 쪽이 끝나는 자리가
  갈리는 지점이다.
- 그림(.png/.jpg/.bmp/.gif/.ico/.tif)이면 나란히 띄운다 - 16진으로 보는 것보다 훨씬 빠르다.
- 그 밖에는 앞 4KB 를 16진으로 나란히 놓고, 16바이트 단위로 다른 줄에만 색을 준다.
  볼 수 있는 글자는 오른쪽 칸에 그대로 적는다 - 16진만 보면 무슨 파일인지 모른다.
  수십 MB 짜리를 전부 그리면 화면이 굳으므로 앞부분만 그리고 잘렸다고 알린다.

BinaryTest 25건 신설(같음·가운데 다름·길이 다름·16진 표기·줄 수 제한·글자 칸·
없는 파일·그림 판별). 전체 330건 통과.

실행 확인: .bin 두 개 -> "다릅니다 · 왼쪽 17 B · 오른쪽 19 B · 처음 갈리는 곳 0x10"
+ 16진 두 줄(0x10 줄만 색). .png 두 개 -> 나란히 표시.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:50:11 +09:00

1487 lines
100 KiB
XML

<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"
mc:Ignorable="d"
PreviewKeyDown="View_PreviewKeyDown">
<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">20</system:Double>
<!-- BASE / LOCAL 하단 요약 칸 공통 높이. 두 패널의 코드 영역이 같은 크기여야 나란히 읽힌다. -->
<system:Double x:Key="PaneFooterHeight">54</system:Double>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<ctl:LineHighlightBrushConverter x:Key="HighlightBrush"/>
<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>
<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="12"/>
</Style>
<!--
코드 줄 컨테이너. 평소에는 높이를 고정해 두 패널의 행이 1:1로 맞물리게 한다.
높이를 풀어 두면 컨테이너 자체 여백 때문에 20px 를 조금씩 넘어 아래로 갈수록 어긋난다.
워드랩일 때만 풀어 준다 - 그때는 줄마다 높이가 달라야 하기 때문이다.
-->
<Style x:Key="CodeLineItem" TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="Height" Value="{StaticResource CodeLineHeight}"/>
<Setter Property="MinHeight" Value="{StaticResource 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# 문법. -->
<DataTemplate x:Key="BaseLineTemplate">
<!-- 여백 폭(54)을 LOCAL 과 맞춘다. 접히는 자리가 같아야 워드랩에서도 행이 어긋나지 않는다. -->
<Grid MinHeight="{Binding SourceRowHeight}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="54"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding BaseNumberText}" Style="{StaticResource Mono}"
Foreground="{DynamicResource Ubisam.Brush.Muted}" TextAlignment="Right"
VerticalAlignment="Top" Margin="0,0,8,0"/>
<Border Grid.Column="1" 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="12"
LineHeight="{StaticResource CodeLineHeight}"
LineStackingStrategy="BlockLineHeight"
SizeChanged="BaseText_SizeChanged"
ToolTip="{Binding BaseText}">
<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 SourceRowHeight}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="34"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding OriginalLocalNumberText}" Style="{StaticResource Mono}"
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="14" MinHeight="14" FontSize="9" 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="12"
LineHeight="{StaticResource CodeLineHeight}"
LineStackingStrategy="BlockLineHeight"
SizeChanged="OriginalLocalText_SizeChanged"
ToolTip="{Binding OriginalLocalText}">
<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 SourceRowHeight}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="34"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding RemoteNumberText}" Style="{StaticResource Mono}"
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="14" MinHeight="14" FontSize="9" 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="12"
LineHeight="{StaticResource CodeLineHeight}"
LineStackingStrategy="BlockLineHeight"
SizeChanged="RemoteText_SizeChanged"
ToolTip="{Binding RemoteText}">
<ctl:CodeTextBlock.RenderTransform>
<TranslateTransform X="{Binding SourceShiftX,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</ctl:CodeTextBlock.RenderTransform>
</ctl:CodeTextBlock>
</Grid>
</Border>
</Grid>
</DataTemplate>
<!--
LOCAL 한 줄 = 여백(북마크·줄번호·[>]) + 본문(색 배경 + 문법 색).
여백과 본문을 한 행에 두는 것이 요점이다. 따로 두면 워드랩을 켰을 때
줄마다 높이가 달라져 여백과 본문이 어긋난다. 한 행이면 높이가 저절로 맞는다.
대신 가로로 밀 때 여백까지 밀리면 안 되므로, 본문 글자만
TextShiftX(편집기의 가로 스크롤 값)만큼 밀고 열 안에서 잘라낸다.
글자를 고치는 일은 이 위에 겹쳐 놓은 편집기(LocalEditor)가 맡는다.
-->
<DataTemplate x:Key="LocalLineTemplate">
<Grid Background="Transparent"
MinHeight="{Binding RowHeight}"
MouseLeftButtonDown="Gutter_MouseLeftButtonDown">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 북마크 표식. 켜진 줄만 색 막대가 보인다. -->
<Border Grid.Column="0" Width="4" HorizontalAlignment="Left" CornerRadius="1"
Margin="1,3" VerticalAlignment="Top" Height="14"
Background="{DynamicResource Ubisam.Brush.Accent}"
Visibility="{Binding IsBookmarked, Converter={StaticResource BoolToVis}}"/>
<TextBlock Grid.Column="1" Text="{Binding LocalNumberText}" Style="{StaticResource Mono}"
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="14" MinHeight="14" FontSize="9" VerticalAlignment="Top"
Visibility="{Binding CanTakeBase, Converter={StaticResource BoolToVis}}"
Command="{Binding DataContext.TakeLineCommand,
RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
ToolTip="이 줄만 BASE 내용으로 가져오기"/>
<!--
색 배경은 칸 전체를 채우고 제자리에 있는다. 미는 것은 글자뿐이다.
Padding 을 0 으로 두는 것이 중요하다. 문제 줄 표시용 왼쪽 테두리(2)도
자리를 차지하므로, BASE·REMOTE 의 Padding 2 와 합이 같아야 세 패널의 글자가
같은 x 에서 시작한다. 여기가 2 더 밀려 있으면 그 위에 겹친 편집기의 캐럿이
글자 가운데에 박힌 것처럼 보인다.
-->
<Border Grid.Column="3" 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="12"
LineHeight="{StaticResource CodeLineHeight}"
LineStackingStrategy="BlockLineHeight"
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>
<Grid Margin="2" TextElement.Foreground="{DynamicResource Ubisam.Brush.ContentForeground}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</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 로
오른쪽에 붙박아 어느 모드에서든 같은 자리에 있게 했다.
-->
<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)"/>
<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 있는 폴더 ↔ 없는 폴더를 한 번에 오간다. 설정에 두 경로가
모두 적혀 있을 때만 보인다. -->
<Button Content="{Binding DemoSwitchLabel}" Command="{Binding SwitchDemoCommand}"
Height="26" MinHeight="26" Padding="8,2" FontSize="11" Margin="0,0,10,0"
Visibility="{Binding CanSwitchDemo, Converter={StaticResource BoolToVis}}"
ToolTip="Git 저장소 화면과 파일 비교 화면을 오갑니다"/>
<!-- Git 이 아닐 때: 비교할 파일 두 개를 직접 고른다 -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
Visibility="{Binding IsPlainMode, Converter={StaticResource BoolToVis}}">
<Button Content="기준 파일..." Command="{Binding PickLeftFileCommand}"
Height="26" MinHeight="26" Padding="8,2" FontSize="11"
ToolTip="왼쪽(BASE) 에 놓을 파일"/>
<TextBlock Text="{Binding LeftFile}" Style="{StaticResource Muted}" FontSize="11"
MaxWidth="260" TextTrimming="CharacterEllipsis" Margin="4,0,10,0"
ToolTip="{Binding LeftFile}"/>
<Button Content="내 파일..." Command="{Binding PickRightFileCommand}"
Height="26" MinHeight="26" Padding="8,2" FontSize="11"
ToolTip="오른쪽(LOCAL) 에 놓을 파일. 저장하면 이 파일에 씁니다"/>
<TextBlock Text="{Binding RightFile}" Style="{StaticResource Muted}" FontSize="11"
MaxWidth="260" TextTrimming="CharacterEllipsis" Margin="4,0,0,0"
ToolTip="{Binding RightFile}"/>
</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}"
MinWidth="180" Height="26" MinHeight="26" FontSize="11"
ToolTip="고른 브랜치를 REMOTE(Theirs) 로 놓고 merge base 기준 3-Way 로 봅니다"/>
</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 한 곳에서 오므로 코드 패널과 항상 같은 색이다. -->
<Border Grid.Row="1" 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">
<TextBlock Text="색 구분" Style="{StaticResource Muted}" FontWeight="Bold"
FontSize="11" Margin="0,0,12,0"/>
<!-- 각 항목 뒤의 숫자는 지금 화면에 있는 줄 수. 한 줄만 반영해도 즉시 바뀐다. -->
<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}"/>
<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}"/>
<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}"/>
<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}"/>
<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}"/>
<!-- 무시 규칙에 걸린 줄. 차이로 세지 않는다. -->
<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}"/>
<!-- 자리만 옮긴 줄. 삭제+추가로 두 번 세지 않도록 따로 표시한다. -->
<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}"/>
<!-- 3-Way 일 때만 뜻이 있는 두 가지. 2-Way 에서는 늘 0 이라 자리만 차지한다. -->
<Border Style="{StaticResource LegendChip}"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
Background="{Binding Source=RemoteChange, Converter={StaticResource HighlightByName}, ConverterParameter=RemoteChange}"/>
<TextBlock Text="Theirs only" Style="{StaticResource LegendText}"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"/>
<TextBlock Text="{Binding RemoteChangeCount}" Style="{StaticResource LegendCount}"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"/>
<Border Style="{StaticResource LegendChip}"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
Background="{Binding Source=Conflict, Converter={StaticResource HighlightByName}, ConverterParameter=Conflict}"/>
<TextBlock Text="Conflict" Style="{StaticResource LegendText}"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"/>
<TextBlock Text="{Binding ConflictCount}" Style="{StaticResource LegendCount}"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"/>
<Border Style="{StaticResource LegendChip}" Background="#66E0A020"/>
<TextBlock Text="줄 안에서 바뀐 부분" Style="{StaticResource LegendText}" Margin="0,0,14,0"/>
</WrapPanel>
</Border>
<!--
이진 파일을 열었을 때. 코드 패널 위를 덮는다.
"줄 단위로 비교할 수 없습니다" 한 줄로 끝내면 정작 알고 싶은 <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.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 Width="*" MinWidth="200"/>
<ColumnDefinition Width="4"/>
<ColumnDefinition 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="4"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
<ColumnDefinition 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 Width="4"/>
<ColumnDefinition 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"
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}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Margin="8,6" Height="{Binding SourceHeaderHeight}">
<TextBlock Text="LOCAL (Mine)" Style="{StaticResource PanelTitle}"/>
<TextBlock Text="{Binding LocalHeader}" Style="{StaticResource Muted}" FontSize="11"
TextTrimming="CharacterEllipsis"/>
</StackPanel>
<ListBox Grid.Row="1" x:Name="OriginalLocalLines" BorderThickness="0" Background="Transparent"
ItemsSource="{Binding Lines}" SelectedItem="{Binding SelectedLine}"
ItemTemplate="{StaticResource OriginalLocalLineTemplate}"
ItemContainerStyle="{StaticResource CodeLineItem}"
VirtualizingPanel.ScrollUnit="Pixel"
HorizontalContentAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
ScrollViewer.HorizontalScrollBarVisibility="{Binding SourceBars,
RelativeSource={RelativeSource AncestorType=UserControl}}"
ScrollViewer.ScrollChanged="Lines_ScrollChanged"/>
<!-- 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>
</Grid>
</Border>
<!-- ② BASE -->
<Border Grid.Row="0" BorderThickness="1"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
<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>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- BASE / LOCAL 의 머리 영역 높이를 같게 고정해야 두 패널의 01행이 같은 눈높이에 온다 -->
<StackPanel Grid.Row="0" Margin="8,6" Height="{Binding SourceHeaderHeight}">
<TextBlock Text="{Binding BaseTitle}" Style="{StaticResource PanelTitle}"/>
<TextBlock Text="{Binding BaseHeader}" Style="{StaticResource Muted}" FontSize="11"
TextTrimming="CharacterEllipsis"/>
<!-- 커밋 안의 파일 목록은 위 툴바로 옮겼다.
여기 있으면 BASE 머리에만 한 줄이 더 생겨 경로 줄이 LOCAL 과 어긋난다. -->
<!-- 파일 경로. 읽기전용 TextBox 라 드래그해서 복사할 수 있다.
3-Way 에서는 자리가 빠듯해 접는다 - 같은 값이 제목 아래 줄에 이미 있다. -->
<Grid Margin="0,4,0,0"
Visibility="{Binding IsTwoWay, Converter={StaticResource BoolToVis}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="경로" Style="{StaticResource Muted}"
FontSize="11" Margin="0,0,6,0"/>
<TextBox Grid.Column="1" Text="{Binding BaseFileLocation, Mode=OneWay}"
IsReadOnly="True" FontSize="11" FontFamily="Consolas"
BorderThickness="0" Background="Transparent" Padding="0"
ToolTip="{Binding BaseFileLocation}"/>
</Grid>
</StackPanel>
<ListBox Grid.Row="1" x:Name="BaseLines" BorderThickness="0" Background="Transparent"
ItemsSource="{Binding Lines}" SelectedItem="{Binding SelectedLine}"
ItemTemplate="{StaticResource BaseLineTemplate}"
ItemContainerStyle="{StaticResource CodeLineItem}"
VirtualizingPanel.ScrollUnit="Pixel"
HorizontalContentAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
ScrollViewer.HorizontalScrollBarVisibility="{Binding SourceBars,
RelativeSource={RelativeSource AncestorType=UserControl}}"
ScrollViewer.ScrollChanged="Lines_ScrollChanged"/>
<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="Diff 요약" Style="{StaticResource Muted}" FontSize="11"/>
<TextBlock Text="{Binding DiffSummary}" FontSize="12"/>
</StackPanel>
</Border>
</Grid>
</Border>
<GridSplitter Grid.Column="3" Grid.Row="0" Width="4" HorizontalAlignment="Stretch"
Background="{DynamicResource Ubisam.Brush.Table.GridLine}"/>
<!--
③ LOCAL(2-Way) = MERGED(3-Way).
실제로 고치고 저장하는 칸은 언제나 여기 하나다. 3-Way 에서는 아래 줄로 내려가
폭 전체를 쓰고, 위 세 칸(LOCAL 원본 · BASE · REMOTE)은 읽기 전용 참고가 된다.
-->
<Border BorderThickness="1"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
<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>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!--
BASE 와 같은 높이. 두 패널의 줄이 정확히 같은 눈높이에 오도록 맞춘다.
"전체 반영 / 전체 되돌리기" 는 여기 있다가 ACTIONS 로 옮겼다.
이쪽에만 버튼 줄이 하나 더 있으면 그만큼 경로 줄이 아래로 밀려
BASE 의 경로와 높이가 어긋난다. 어차피 줄 단위 반영·되돌리기 버튼이
ACTIONS 에 있으니 같은 성격끼리 한자리에 모이는 편이 낫기도 하다.
-->
<StackPanel Grid.Row="0" Margin="8,6" Height="{StaticResource PaneHeaderHeight}">
<TextBlock Text="{Binding LocalTitle}" Style="{StaticResource PanelTitle}"/>
<TextBlock Text="{Binding LocalHeader}" Style="{StaticResource Muted}" FontSize="11"
TextTrimming="CharacterEllipsis"/>
<!-- BASE 와 같은 자리에 경로를 둔다. 이쪽은 워킹트리의 실제 파일. -->
<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="0,0,6,0"/>
<TextBox Grid.Column="1" Text="{Binding SelectedFileFullPath, Mode=OneWay}"
IsReadOnly="True" FontSize="11" FontFamily="Consolas"
BorderThickness="0" Background="Transparent" Padding="0"
ToolTip="{Binding SelectedFileFullPath}"/>
</Grid>
</StackPanel>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!--
아래층은 여백과 색·문법을 그리는 표시용, 위층은 실제로 글자를 고치는 편집기다.
편집기 글자는 투명이라 아래층 색이 그대로 보이고, 캐럿·선택 영역만 편집기가 그린다.
둘의 글꼴·줄 높이가 정확히 같아야 어긋나지 않는다.
편집기는 여백 폭(54)만큼 왼쪽을 비워 두어, 그 자리의 [>] 버튼과
북마크 더블클릭이 아래층까지 닿게 한다.
-->
<Grid Grid.Column="0">
<ListBox x:Name="LocalLines" BorderThickness="0" Background="Transparent"
ItemsSource="{Binding Lines}" SelectedItem="{Binding SelectedLine}"
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" 가 무시되고, 아래층 표시가 덮여 안 보인다.
세로 스크롤 막대를 늘 띄워 두는 이유 - 나타났다 사라지면 글 너비가 바뀌어
워드랩 접히는 자리가 아래층과 어긋난다.
-->
<TextBox x:Name="LocalEditor" Style="{x:Null}" Margin="54,0,0,0"
FontFamily="Consolas" FontSize="12"
TextBlock.LineHeight="{StaticResource 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>
<Border Grid.Row="2" Padding="8,5" BorderThickness="0,1,0,0"
Height="{StaticResource PaneFooterHeight}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
<StackPanel VerticalAlignment="Center">
<!--
상태바. 지금 어디에 있고(행·열, 몇 번째 차이),
저장하면 어떤 형식으로 나가는지(인코딩·줄바꿈)를 한 줄에 모아 둔다.
-->
<StackPanel Orientation="Horizontal" Margin="0,0,0,4">
<TextBlock Text="{Binding CaretPosition}" Style="{StaticResource Mono}" FontSize="11"/>
<Border Width="1" Margin="8,1" Background="{DynamicResource Ubisam.Brush.Input.Border}"/>
<TextBlock Text="{Binding DiffPosition}" FontSize="11" FontWeight="Bold"
Foreground="{DynamicResource Ubisam.Brush.Accent}"/>
<Border Width="1" Margin="8,1" Background="{DynamicResource Ubisam.Brush.Input.Border}"/>
<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="8,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}}"/>
</StackPanel>
<!--
편집 상태와 코드 검사를 한 줄에 둔다. BASE 쪽 요약과 높이를 맞춰야
두 패널의 코드 영역이 같은 크기가 되고 나란히 읽힌다.
검사 결과는 걸린 게 있을 때만 눌러서 목록을 펼친다 - 늘 펼쳐 두면
이 칸이 커져 코드가 가린다.
-->
<StackPanel Orientation="Horizontal" Margin="0,2,0,0">
<TextBlock Text="{Binding EditSummary}" FontSize="12"
VerticalAlignment="Center"/>
<Border Width="1" Margin="8,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>
</StackPanel>
</Border>
</Grid>
</Border>
<GridSplitter Grid.Column="5" Grid.Row="0" Width="4" HorizontalAlignment="Stretch"
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
Background="{DynamicResource Ubisam.Brush.Table.GridLine}"/>
<!--
③-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}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- BASE/LOCAL 과 같은 머리 높이. 세 패널의 01행이 같은 눈높이에 와야 한다.
RowDefinition 이 아니라 StackPanel 에 거는 이유 - 리소스가 Double 이라
GridLength 자리에 넣으면 화면을 만들 때 그대로 터진다. -->
<StackPanel Grid.Row="0" Margin="8,6" Height="{Binding SourceHeaderHeight}">
<TextBlock Text="REMOTE (Theirs)" Style="{StaticResource PanelTitle}"/>
<TextBlock Text="{Binding RemoteHeader}" Style="{StaticResource Muted}"
FontSize="11" TextTrimming="CharacterEllipsis"/>
<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>
<ListBox Grid.Row="1" x:Name="RemoteLines" BorderThickness="0" Background="Transparent"
ItemsSource="{Binding Lines}" SelectedItem="{Binding SelectedLine}"
ItemTemplate="{StaticResource RemoteLineTemplate}"
ItemContainerStyle="{StaticResource CodeLineItem}"
VirtualizingPanel.ScrollUnit="Pixel"
HorizontalContentAlignment="Stretch"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
ScrollViewer.HorizontalScrollBarVisibility="{Binding SourceBars,
RelativeSource={RelativeSource AncestorType=UserControl}}"
ScrollViewer.ScrollChanged="Lines_ScrollChanged"/>
<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>
</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}"/>
<GridSplitter Grid.Column="7" Grid.RowSpan="3" Width="4" HorizontalAlignment="Stretch"
Background="{DynamicResource Ubisam.Brush.Table.GridLine}"/>
<!-- ④ ACTIONS -->
<Border Grid.Column="8" Grid.RowSpan="3" BorderThickness="1" Padding="8"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
<DockPanel>
<!-- 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 내용을 파일에 쓰고 스테이징 후 커밋"/>
<!-- 창이 낮거나 커밋 메시지가 길어지면 아래가 잘리므로 스크롤을 둔다. -->
<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" ToolTip="차이나는 모든 줄을 BASE 내용으로"/>
<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="이 덩어리 반영" Command="{Binding AcceptBlockCommand}"
MinHeight="30" Margin="2,4,2,0" FontSize="11"
ToolTip="차이가 이어지는 구간을 한 번에 반영합니다 (Alt+Shift+Right)"/>
<!--
저장 형식. 기본은 "원본 유지"라 지금까지와 똑같이 나간다.
사내 소스에 CP949 가 섞여 있어, 새로 만드는 파일과 형식이 갈릴 때 여기서 맞춘다.
-->
<TextBlock Text="저장 형식" Style="{StaticResource Muted}" FontSize="11" Margin="0,12,0,4"/>
<UniformGrid Columns="2">
<ComboBox ItemsSource="{Binding SaveEncodings}" SelectedItem="{Binding SaveEncoding}"
MinHeight="26" FontSize="11" Margin="2,0"
ToolTip="저장할 때 쓸 인코딩"/>
<ComboBox ItemsSource="{Binding SaveNewLines}" SelectedItem="{Binding SaveNewLine}"
MinHeight="26" FontSize="11" Margin="2,0"
ToolTip="저장할 때 쓸 줄바꿈"/>
</UniformGrid>
<!--
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>
<TextBlock Text="비교 규칙" Style="{StaticResource Muted}" FontSize="11" Margin="0,14,0,4"/>
<UniformGrid Columns="2">
<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>
<Button Content="{Binding ReadOnlyLabel}" Click="ToggleReadOnly_Click"
MinHeight="28" FontSize="11" Margin="2,4,2,0"
ToolTip="실수로 고치는 것을 막습니다"/>
</StackPanel>
</ScrollViewer>
</DockPanel>
</Border>
</Grid>
</Grid>
</UserControl>