feature<*>: use DataGrid instead of RichTextBox to improve performance

This commit is contained in:
leo 2020-11-24 17:14:44 +08:00
parent 544d819c96
commit cbdebee4c2
9 changed files with 594 additions and 848 deletions

View file

@ -1,12 +1,11 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
namespace SourceGit.UI {
@ -14,8 +13,11 @@ namespace SourceGit.UI {
/// Viewer for git diff
/// </summary>
public partial class DiffViewer : UserControl {
private double minWidth = 0;
private Git.Diff.TextChange textChangeData = null;
private List<Git.Diff.LineChange> lineChanges = null;
private Brush bgEmpty = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0));
private Brush bgAdded = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0));
private Brush bgDeleted = new SolidColorBrush(Color.FromArgb(60, 255, 0, 0));
private Brush bgNormal = Brushes.Transparent;
/// <summary>
/// Diff options.
@ -27,6 +29,19 @@ namespace SourceGit.UI {
public string ExtraArgs = "";
}
/// <summary>
/// Change block.
/// </summary>
public class ChangeBlock {
public string Content { get; set; }
public Git.Diff.LineMode Mode { get; set; }
public Brush BG { get; set; }
public Brush FG { get; set; }
public FontStyle Style { get; set; }
public string OldLine { get; set; }
public string NewLine { get; set; }
}
/// <summary>
/// Constructor
/// </summary>
@ -50,11 +65,12 @@ namespace SourceGit.UI {
public void Diff(Git.Repository repo, Option opts) {
SetTitle(opts.Path, opts.OrgPath);
textChangeData = null;
lineChanges = null;
loading.Visibility = Visibility.Visible;
mask.Visibility = Visibility.Collapsed;
textChange.Visibility = Visibility.Collapsed;
textChangeOneSide.Visibility = Visibility.Collapsed;
textChangeTwoSides.Visibility = Visibility.Collapsed;
sizeChange.Visibility = Visibility.Collapsed;
noChange.Visibility = Visibility.Collapsed;
@ -82,8 +98,8 @@ namespace SourceGit.UI {
var rs = Git.Diff.GetTextChange(repo, args);
if (rs.IsBinary) {
SetBinaryChange(Git.Diff.GetSizeChange(repo, opts.RevisionRange, opts.Path, opts.OrgPath));
} else if (rs.Blocks.Count > 0) {
textChangeData = rs;
} else if (rs.Lines.Count > 0) {
lineChanges = rs.Lines;
SetTextChange();
} else {
SetSame();
@ -112,42 +128,100 @@ namespace SourceGit.UI {
/// </summary>
/// <param name="rs"></param>
private void SetTextChange() {
if (textChangeData == null) return;
if (lineChanges == null) return;
Dispatcher.Invoke(() => {
loading.Visibility = Visibility.Collapsed;
textChange.Visibility = Visibility.Visible;
textChangeOptions.Visibility = Visibility.Visible;
var fgCommon = FindResource("Brush.FG") as Brush;
var fgIndicator = FindResource("Brush.FG2") as Brush;
if (App.Preference.UIUseOneSideDiff) {
twoSideLeft.Width = new GridLength(0);
twoSideLeft.MinWidth = 0;
twoSideSplittter.Width = new GridLength(0);
} else {
twoSideLeft.Width = new GridLength(1, GridUnitType.Star);
twoSideLeft.MinWidth = 100;
twoSideSplittter.Width = new GridLength(2);
if (App.Preference.UIUseOneSideDiff) {
var blocks = new List<ChangeBlock>();
foreach (var line in lineChanges) {
var block = new ChangeBlock();
block.Content = line.Content;
block.Mode = line.Mode;
block.BG = GetLineBackground(line);
block.FG = line.Mode == Git.Diff.LineMode.Indicator ? fgIndicator : fgCommon;
block.Style = line.Mode == Git.Diff.LineMode.Indicator ? FontStyles.Italic : FontStyles.Normal;
block.OldLine = line.OldLine;
block.NewLine = line.NewLine;
blocks.Add(block);
}
minWidth = Math.Max(leftText.ActualWidth, rightText.ActualWidth) - 16;
Dispatcher.Invoke(() => {
loading.Visibility = Visibility.Collapsed;
textChangeOptions.Visibility = Visibility.Visible;
textChangeOneSide.Visibility = Visibility.Visible;
textChangeTwoSides.Visibility = Visibility.Collapsed;
leftLineNumber.ItemsSource = null;
rightLineNumber.ItemsSource = null;
ResetDataGrid(textChangeOneSide);
textChangeOneSide.ItemsSource = blocks;
OnSizeChanged(null, null);
});
} else {
var oldSideBlocks = new List<ChangeBlock>();
var newSideBlocks = new List<ChangeBlock>();
leftText.Document.Blocks.Clear();
rightText.Document.Blocks.Clear();
foreach (var line in lineChanges) {
var block = new ChangeBlock();
block.Content = line.Content;
block.Mode = line.Mode;
block.BG = GetLineBackground(line);
block.FG = line.Mode == Git.Diff.LineMode.Indicator ? fgIndicator : fgCommon;
block.Style = line.Mode == Git.Diff.LineMode.Indicator ? FontStyles.Italic : FontStyles.Normal;
block.OldLine = line.OldLine;
block.NewLine = line.NewLine;
var lLineNumbers = new List<string>();
var rLineNumbers = new List<string>();
switch (line.Mode) {
case Git.Diff.LineMode.Added:
newSideBlocks.Add(block);
foreach (var b in textChangeData.Blocks) ShowBlock(b, lLineNumbers, rLineNumbers);
var oldEmpty = new ChangeBlock();
oldEmpty.Content = "";
oldEmpty.Mode = Git.Diff.LineMode.None;
oldEmpty.BG = bgEmpty;
oldEmpty.FG = fgCommon;
oldEmpty.Style = FontStyles.Normal;
oldEmpty.OldLine = block.OldLine;
oldEmpty.NewLine = block.NewLine;
oldSideBlocks.Add(oldEmpty);
break;
case Git.Diff.LineMode.Deleted:
oldSideBlocks.Add(block);
if (!App.Preference.UIUseOneSideDiff) leftText.Document.PageWidth = minWidth + 16;
rightText.Document.PageWidth = minWidth + 16;
leftLineNumber.ItemsSource = lLineNumbers;
rightLineNumber.ItemsSource = rLineNumbers;
leftText.ScrollToHome();
});
var newEmpty = new ChangeBlock();
newEmpty.Content = "";
newEmpty.Mode = Git.Diff.LineMode.None;
newEmpty.BG = bgEmpty;
newEmpty.FG = fgCommon;
newEmpty.Style = FontStyles.Normal;
newEmpty.OldLine = block.OldLine;
newEmpty.NewLine = block.NewLine;
newSideBlocks.Add(newEmpty);
break;
default:
oldSideBlocks.Add(block);
newSideBlocks.Add(block);
break;
}
}
Dispatcher.Invoke(() => {
loading.Visibility = Visibility.Collapsed;
textChangeOptions.Visibility = Visibility.Visible;
textChangeOneSide.Visibility = Visibility.Collapsed;
textChangeTwoSides.Visibility = Visibility.Visible;
ResetDataGrid(textChangeOldSide);
ResetDataGrid(textChangeNewSide);
textChangeOldSide.ItemsSource = oldSideBlocks;
textChangeNewSide.ItemsSource = newSideBlocks;
OnSizeChanged(null, null);
});
}
}
/// <summary>
@ -195,277 +269,145 @@ namespace SourceGit.UI {
}
/// <summary>
/// Make paragraph for two-sides diff
/// Get background color of line.
/// </summary>
/// <param name="b"></param>
/// <param name="leftNumber"></param>
/// <param name="rightNumber"></param>
private void ShowBlock(Git.Diff.Block b, List<string> leftNumber, List<string> rightNumber) {
bool useOneSide = App.Preference.UIUseOneSideDiff;
if (useOneSide && b.Mode == Git.Diff.LineMode.Empty) return;
var content = b.Builder.ToString();
// Make paragraph element
Paragraph p = new Paragraph(new Run(content));
p.Margin = new Thickness(0);
p.Padding = new Thickness(0);
p.LineHeight = 1;
p.Background = GetBlockBackground(b);
p.Foreground = b.Mode == Git.Diff.LineMode.Indicator ? Brushes.Gray : FindResource("Brush.FG") as SolidColorBrush;
p.FontStyle = b.Mode == Git.Diff.LineMode.Indicator ? FontStyles.Italic : FontStyles.Normal;
p.DataContext = b;
p.ContextMenuOpening += OnParagraphContextMenuOpening;
// Calculate with
var formatter = new FormattedText(
content,
CultureInfo.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(leftText.FontFamily, p.FontStyle, p.FontWeight, p.FontStretch),
leftText.FontSize,
Brushes.Black,
new NumberSubstitution(),
TextFormattingMode.Ideal);
if (minWidth < formatter.Width) minWidth = formatter.Width;
// Line numbers
switch (b.Side) {
case Git.Diff.Side.Left:
for (int i = 0; i < b.Count; i++) {
if (b.CanShowNumber) leftNumber.Add($"{i + b.LeftStart}");
else leftNumber.Add("");
if (useOneSide) rightNumber.Add("");
}
break;
case Git.Diff.Side.Right:
for (int i = 0; i < b.Count; i++) {
if (b.CanShowNumber) rightNumber.Add($"{i + b.RightStart}");
else rightNumber.Add("");
if (useOneSide) leftNumber.Add("");
}
break;
/// <returns></returns>
private Brush GetLineBackground(Git.Diff.LineChange line) {
switch (line.Mode) {
case Git.Diff.LineMode.Added:
return bgAdded;
case Git.Diff.LineMode.Deleted:
return bgDeleted;
default:
for (int i = 0; i < b.Count; i++) {
if (b.CanShowNumber) {
leftNumber.Add($"{i + b.LeftStart}");
rightNumber.Add($"{i + b.RightStart}");
} else {
leftNumber.Add("");
rightNumber.Add("");
}
}
break;
}
// Add this paragraph to document.
if (App.Preference.UIUseOneSideDiff) {
rightText.Document.Blocks.Add(p);
} else {
switch (b.Side) {
case Git.Diff.Side.Left:
leftText.Document.Blocks.Add(p);
break;
case Git.Diff.Side.Right:
rightText.Document.Blocks.Add(p);
break;
default:
leftText.Document.Blocks.Add(p);
var cp = new Paragraph(new Run(content));
cp.Margin = new Thickness(0);
cp.Padding = new Thickness();
cp.LineHeight = 1;
cp.Background = p.Background;
cp.Foreground = p.Foreground;
cp.FontStyle = p.FontStyle;
cp.DataContext = b;
cp.ContextMenuOpening += OnParagraphContextMenuOpening;
rightText.Document.Blocks.Add(cp);
break;
}
return bgNormal;
}
}
/// <summary>
/// Get background color of block.
/// Find child element of type.
/// </summary>
/// <param name="b"></param>
/// <typeparam name="T"></typeparam>
/// <param name="parent"></param>
/// <returns></returns>
private Brush GetBlockBackground(Git.Diff.Block b) {
Border border = new Border();
border.BorderThickness = new Thickness(0);
border.BorderBrush = Brushes.LightBlue;
border.Height = b.Count * 16 - 1;
border.Width = minWidth - 1;
private T GetVisualChild<T>(DependencyObject parent) where T : Visual {
T child = null;
switch (b.Mode) {
case Git.Diff.LineMode.Empty:
border.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0));
break;
case Git.Diff.LineMode.Added:
border.Background = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0));
break;
case Git.Diff.LineMode.Deleted:
border.Background = new SolidColorBrush(Color.FromArgb(60, 255, 0, 0));
break;
default:
border.Background = Brushes.Transparent;
break;
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++) {
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null) {
child = GetVisualChild<T>(v);
}
if (child != null) {
break;
}
}
VisualBrush highlight = new VisualBrush();
highlight.TileMode = TileMode.None;
highlight.Stretch = Stretch.Fill;
highlight.Visual = border;
return highlight;
return child;
}
private void ResetDataGrid(DataGrid dg) {
dg.ItemsSource = null;
dg.Items.Clear();
foreach (var col in dg.Columns) {
col.MinWidth = 0;
col.Width = 0;
}
}
#endregion
#region EVENTS
/// <summary>
/// Context menu for text-change paragraph
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnParagraphContextMenuOpening(object sender, ContextMenuEventArgs ev) {
var paragraph = sender as Paragraph;
var doc = (paragraph.Parent as FlowDocument);
if (doc != null) {
var textBox = doc.Parent as RichTextBox;
if (textBox != null && !textBox.Selection.IsEmpty) {
var copyItem = new MenuItem();
copyItem.Header = "Copy";
copyItem.Click += (o, e) => {
Clipboard.SetText(textBox.Selection.Text);
e.Handled = true;
};
var copyMenu = new ContextMenu();
copyMenu.Items.Add(copyItem);
copyMenu.IsOpen = true;
ev.Handled = true;
return;
}
}
var block = paragraph.DataContext as Git.Diff.Block;
if (block.Mode == Git.Diff.LineMode.Empty || block.Mode == Git.Diff.LineMode.Indicator) {
ev.Handled = true;
return;
}
var highlight = paragraph.Background as VisualBrush;
if (highlight != null) {
(highlight.Visual as Border).BorderThickness = new Thickness(.5);
}
paragraph.ContextMenu = new ContextMenu();
paragraph.ContextMenu.Closed += (o, e) => {
if (paragraph.ContextMenu == (o as ContextMenu)) {
if (highlight != null) {
(highlight.Visual as Border).BorderThickness = new Thickness(0);
}
paragraph.ContextMenu = null;
}
};
var copy = new MenuItem();
copy.Header = "Copy";
copy.Click += (o, e) => {
Clipboard.SetText(block.Builder.ToString());
e.Handled = true;
};
paragraph.ContextMenu.Items.Add(copy);
paragraph.ContextMenu.IsOpen = true;
ev.Handled = true;
}
/// <summary>
/// Fix document size.
/// Auto fit text change diff size.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnSizeChanged(object sender, SizeChangedEventArgs e) {
var text = sender as RichTextBox;
if (text.Document.PageWidth < text.ActualWidth) {
text.Document.PageWidth = text.ActualWidth;
var total = area.ActualWidth;
if (App.Preference.UIUseOneSideDiff) {
textChangeOneSide.Columns[0].Width = DataGridLength.Auto;
textChangeOneSide.Columns[1].Width = DataGridLength.Auto;
textChangeOneSide.Columns[2].MinWidth = 1;
textChangeOneSide.Columns[2].Width = 1;
textChangeOneSide.UpdateLayout();
var offset = textChangeOneSide.NonFrozenColumnsViewportHorizontalOffset;
var minWidth = total - offset;
var scroller = GetVisualChild<ScrollViewer>(textChangeOneSide);
if (scroller.ComputedVerticalScrollBarVisibility == Visibility.Visible) minWidth -= 8;
textChangeOneSide.Columns[2].MinWidth = minWidth;
textChangeOneSide.Columns[2].Width = DataGridLength.Auto;
textChangeOneSide.UpdateLayout();
} else {
textChangeOldSide.Columns[0].Width = DataGridLength.Auto;
textChangeOldSide.Columns[1].MinWidth = 1;
textChangeOldSide.Columns[1].Width = 1;
textChangeOldSide.UpdateLayout();
textChangeNewSide.Columns[0].Width = DataGridLength.Auto;
textChangeNewSide.Columns[1].MinWidth = 1;
textChangeNewSide.Columns[1].Width = 1;
textChangeNewSide.UpdateLayout();
var oldOffset = textChangeOldSide.NonFrozenColumnsViewportHorizontalOffset;
var newOffset = textChangeNewSide.NonFrozenColumnsViewportHorizontalOffset;
var minWidth = total - Math.Min(oldOffset, newOffset);
var scroller = GetVisualChild<ScrollViewer>(textChangeNewSide);
if (scroller.ComputedVerticalScrollBarVisibility == Visibility.Visible) minWidth -= 8;
textChangeOldSide.Columns[1].MinWidth = minWidth;
textChangeOldSide.Columns[1].Width = DataGridLength.Auto;
textChangeOldSide.UpdateLayout();
textChangeNewSide.Columns[1].MinWidth = minWidth;
textChangeNewSide.Columns[1].Width = DataGridLength.Auto;
textChangeNewSide.UpdateLayout();
}
}
/// <summary>
/// Scroll using mouse wheel.
/// Prevent default auto-scrolling when click row in DataGrid.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnViewerMouseWheel(object sender, MouseWheelEventArgs e) {
var text = sender as RichTextBox;
if (text == null) return;
if (e.Delta > 0) {
text.LineUp();
} else {
text.LineDown();
}
private void OnLineRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) {
e.Handled = true;
}
/// <summary>
/// Sync scroll both sides.
/// Sync scroll on two sides diff.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnViewerScroll(object sender, ScrollChangedEventArgs e) {
private void OnTwoSidesScroll(object sender, ScrollChangedEventArgs e) {
var oldSideScroller = GetVisualChild<ScrollViewer>(textChangeOldSide);
var newSideScroller = GetVisualChild<ScrollViewer>(textChangeNewSide);
if (e.VerticalChange != 0) {
if (leftText.VerticalOffset != e.VerticalOffset) {
leftText.ScrollToVerticalOffset(e.VerticalOffset);
if (oldSideScroller.VerticalOffset != e.VerticalOffset) {
oldSideScroller.ScrollToVerticalOffset(e.VerticalOffset);
}
if (rightText.VerticalOffset != e.VerticalOffset) {
rightText.ScrollToVerticalOffset(e.VerticalOffset);
if (newSideScroller.VerticalOffset != e.VerticalOffset) {
newSideScroller.ScrollToVerticalOffset(e.VerticalOffset);
}
leftLineNumber.Margin = new Thickness(0, -e.VerticalOffset, 0, 0);
rightLineNumber.Margin = new Thickness(0, -e.VerticalOffset, 0, 0);
} else {
if (leftText.HorizontalOffset != e.HorizontalOffset) {
leftText.ScrollToHorizontalOffset(e.HorizontalOffset);
if (oldSideScroller.HorizontalOffset != e.HorizontalOffset) {
oldSideScroller.ScrollToHorizontalOffset(e.HorizontalOffset);
}
if (rightText.HorizontalOffset != e.HorizontalOffset) {
rightText.ScrollToHorizontalOffset(e.HorizontalOffset);
}
}
}
/// <summary>
/// Auto scroll when selection changed.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnViewerSelectionChanged(object sender, RoutedEventArgs e) {
var doc = sender as RichTextBox;
if (doc == null || doc.IsFocused == false) return;
if (Mouse.LeftButton == MouseButtonState.Pressed && !doc.Selection.IsEmpty) {
var p = Mouse.GetPosition(doc);
if (p.X <= 8) {
doc.LineLeft();
} else if (p.X >= doc.ActualWidth - 8) {
doc.LineRight();
}
if (p.Y <= 8) {
doc.LineUp();
} else if (p.Y >= doc.ActualHeight - 8) {
doc.LineDown();
if (newSideScroller.HorizontalOffset != e.HorizontalOffset) {
newSideScroller.ScrollToHorizontalOffset(e.HorizontalOffset);
}
}
}
@ -476,46 +418,23 @@ namespace SourceGit.UI {
/// <param name="sender"></param>
/// <param name="e"></param>
private void Go2Next(object sender, RoutedEventArgs e) {
double minTop = 0;
var grid = textChangeOneSide;
if (!App.Preference.UIUseOneSideDiff) grid = textChangeNewSide;
if (App.Preference.UIUseOneSideDiff) {
foreach (var p in rightText.Document.Blocks) {
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
var block = p.DataContext as Git.Diff.Block;
if (rect.Top > 17 && (block.IsLeftDelete || block.IsRightAdded)) {
minTop = rect.Top;
var scroller = GetVisualChild<ScrollViewer>(grid);
var firstVisible = (int)scroller.VerticalOffset;
var firstModeEnded = false;
var first = grid.Items[firstVisible] as ChangeBlock;
for (int i = firstVisible + 1; i < grid.Items.Count; i++) {
var next = grid.Items[i] as ChangeBlock;
if (next.Mode != Git.Diff.LineMode.Normal && next.Mode != Git.Diff.LineMode.Indicator) {
if (firstModeEnded || next.Mode != first.Mode) {
scroller.ScrollToVerticalOffset(i);
break;
}
} else {
firstModeEnded = true;
}
} else {
Paragraph next = null;
foreach (var p in leftText.Document.Blocks) {
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
var block = p.DataContext as Git.Diff.Block;
if (rect.Top > 17 && block.IsLeftDelete) {
next = p as Paragraph;
minTop = rect.Top;
break;
}
}
foreach (var p in rightText.Document.Blocks) {
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
var block = p.DataContext as Git.Diff.Block;
if (rect.Top > 17 && block.IsRightAdded) {
if (next == null || minTop > rect.Top) {
next = p as Paragraph;
minTop = rect.Top;
}
break;
}
}
}
if (minTop > 0) {
rightText.ScrollToVerticalOffset(rightText.VerticalOffset + minTop - 16);
}
}
@ -525,51 +444,23 @@ namespace SourceGit.UI {
/// <param name="sender"></param>
/// <param name="e"></param>
private void Go2Prev(object sender, RoutedEventArgs e) {
double maxTop = double.MaxValue;
var grid = textChangeOneSide;
if (!App.Preference.UIUseOneSideDiff) grid = textChangeNewSide;
if (App.Preference.UIUseOneSideDiff) {
var p = rightText.Document.Blocks.LastBlock as Paragraph;
do {
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
var block = p.DataContext as Git.Diff.Block;
if (rect.Top < 15 && (block.IsLeftDelete || block.IsRightAdded)) {
maxTop = rect.Top;
var scroller = GetVisualChild<ScrollViewer>(grid);
var firstVisible = (int)scroller.VerticalOffset;
var firstModeEnded = false;
var first = grid.Items[firstVisible] as ChangeBlock;
for (int i = firstVisible - 1; i >= 0; i--) {
var next = grid.Items[i] as ChangeBlock;
if (next.Mode != Git.Diff.LineMode.Normal && next.Mode != Git.Diff.LineMode.Indicator) {
if (firstModeEnded || next.Mode != first.Mode) {
scroller.ScrollToVerticalOffset(i);
break;
}
p = p.PreviousBlock as Paragraph;
} while (p != null);
} else {
Paragraph next = null;
var p = leftText.Document.Blocks.LastBlock as Paragraph;
do {
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
var block = p.DataContext as Git.Diff.Block;
if (rect.Top < 15 && block.IsLeftDelete) {
next = p;
maxTop = rect.Top;
break;
}
p = p.PreviousBlock as Paragraph;
} while (p != null);
p = rightText.Document.Blocks.LastBlock as Paragraph;
do {
var rect = p.ContentStart.GetCharacterRect(LogicalDirection.Forward);
var block = p.DataContext as Git.Diff.Block;
if (rect.Top < 15 && block.IsRightAdded) {
if (next == null || maxTop < rect.Top) maxTop = rect.Top;
break;
}
p = p.PreviousBlock as Paragraph;
} while (p != null);
}
if (maxTop != double.MaxValue) {
rightText.ScrollToVerticalOffset(rightText.VerticalOffset + maxTop - 16);
} else {
firstModeEnded = true;
}
}
}
@ -581,6 +472,39 @@ namespace SourceGit.UI {
private void ChangeDiffMode(object sender, RoutedEventArgs e) {
SetTextChange();
}
/// <summary>
/// Text change context menu opening.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnTextChangeContextMenuOpening(object sender, ContextMenuEventArgs e) {
var grid = sender as DataGrid;
if (grid == null) return;
var menu = new ContextMenu();
var copy = new MenuItem();
copy.Header = "Copy Selected Lines";
copy.Click += (o, ev) => {
var items = grid.SelectedItems;
if (items.Count == 0) return;
var builder = new StringBuilder();
foreach (var item in items) {
var block = item as ChangeBlock;
if (block == null) continue;
if (block.Mode == Git.Diff.LineMode.None || block.Mode == Git.Diff.LineMode.Indicator) continue;
builder.Append(block.Content);
builder.AppendLine();
}
Clipboard.SetText(builder.ToString());
};
menu.Items.Add(copy);
menu.IsOpen = true;
e.Handled = true;
}
#endregion
}
}