mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-23 20:47:25 -08:00
feature(Histories): supports to diff selected 2 commits
This commit is contained in:
parent
b177f541be
commit
5592c652f7
5 changed files with 479 additions and 13 deletions
|
@ -282,8 +282,7 @@ namespace SourceGit.UI {
|
|||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ChangeListMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
private void ChangeListMouseDoubleClick(object sender, MouseButtonEventArgs e) {
|
||||
var row = sender as DataGridRow;
|
||||
if (row == null) return;
|
||||
|
||||
|
|
|
@ -161,13 +161,16 @@
|
|||
<!-- Detail for selected commit -->
|
||||
<Grid x:Name="commitDetailPanel">
|
||||
<!-- Selected commit detail -->
|
||||
<local:CommitViewer x:Name="commitViewer"/>
|
||||
|
||||
<local:CommitViewer x:Name="commitViewer" Visibility="Collapsed"/>
|
||||
|
||||
<!-- Diff with selected 2 commit -->
|
||||
<local:TwoCommitsDiff x:Name="twoCommitDiff" Visibility="Collapsed"/>
|
||||
|
||||
<!-- Mask for select multi rows in commit list -->
|
||||
<Border x:Name="mask4MultiSelection" Background="{StaticResource Brush.BG1}" Visibility="Collapsed">
|
||||
<Border x:Name="mask4MultiSelection" Background="{StaticResource Brush.BG1}">
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Opacity=".2">
|
||||
<Path Width="160" Height="160" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Git}"/>
|
||||
<Label x:Name="txtTotalSelected" Margin="0,16,0,0" FontSize="24" FontWeight="UltraBold" HorizontalAlignment="Center"/>
|
||||
<Label x:Name="txtTotalSelected" Content="SELECT COMMIT TO VIEW DETAIL" Margin="0,16,0,0" FontSize="24" FontWeight="UltraBold" HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
|
|
|
@ -41,10 +41,6 @@ namespace SourceGit.UI {
|
|||
/// </summary>
|
||||
public Histories() {
|
||||
InitializeComponent();
|
||||
|
||||
mask4MultiSelection.Visibility = Visibility.Visible;
|
||||
txtTotalSelected.Content = "SELECT COMMIT TO VIEW DETAIL";
|
||||
|
||||
ChangeOrientation(null, null);
|
||||
}
|
||||
|
||||
|
@ -271,14 +267,22 @@ namespace SourceGit.UI {
|
|||
|
||||
private void CommitSelectChanged(object sender, SelectionChangedEventArgs e) {
|
||||
mask4MultiSelection.Visibility = Visibility.Collapsed;
|
||||
commitViewer.Visibility = Visibility.Collapsed;
|
||||
twoCommitDiff.Visibility = Visibility.Collapsed;
|
||||
|
||||
var selected = commitList.SelectedItems;
|
||||
if (selected.Count == 1) {
|
||||
var commit = selected[0] as Git.Commit;
|
||||
if (commit != null) commitViewer.SetData(Repo, commit);
|
||||
} else if (selected.Count > 1) {
|
||||
commitViewer.Visibility = Visibility.Visible;
|
||||
commitViewer.SetData(Repo, selected[0] as Git.Commit);
|
||||
} else if (selected.Count == 2) {
|
||||
twoCommitDiff.Visibility = Visibility.Visible;
|
||||
twoCommitDiff.SetData(Repo, (selected[0] as Git.Commit).ShortSHA, (selected[1] as Git.Commit).ShortSHA);
|
||||
} else if (selected.Count > 2) {
|
||||
mask4MultiSelection.Visibility = Visibility.Visible;
|
||||
txtTotalSelected.Content = $"SELECTED {selected.Count} COMMITS";
|
||||
} else {
|
||||
mask4MultiSelection.Visibility = Visibility.Visible;
|
||||
txtTotalSelected.Content = $"SELECT COMMIT TO VIEW DETAIL";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
146
src/UI/TwoCommitsDiff.xaml
Normal file
146
src/UI/TwoCommitsDiff.xaml
Normal file
|
@ -0,0 +1,146 @@
|
|||
<UserControl x:Class="SourceGit.UI.TwoCommitsDiff"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SourceGit.UI"
|
||||
xmlns:source="clr-namespace:SourceGit"
|
||||
xmlns:converters="clr-namespace:SourceGit.Converters"
|
||||
xmlns:helpers="clr-namespace:SourceGit.Helpers"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid Margin="4,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="30"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Grid.Row="0" x:Name="txtTitle" FontFamily="Consolas" FontSize="18" FontWeight="UltraBold" Foreground="{StaticResource Brush.FG2}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200" MinWidth="200"/>
|
||||
<ColumnDefinition Width="1"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid Grid.Column="0" Margin="2,0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="24"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.Resources>
|
||||
<converters:BoolToCollapsed x:Key="BoolToCollapsed"/>
|
||||
<converters:InverseBoolToCollapsed x:Key="InverseBoolToCollapsed"/>
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid Grid.Row="0" Margin="0,0,0,4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="24"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="24"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0" Grid.ColumnSpan="2" BorderThickness="1" BorderBrush="{StaticResource Brush.Border2}" Background="{StaticResource Brush.BG3}"/>
|
||||
<Path Grid.Column="0" Width="14" Height="14" Fill="{StaticResource Brush.FG2}" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Search}"/>
|
||||
<TextBox Grid.Column="1" x:Name="txtChangeFilter" BorderThickness="0" helpers:TextBoxHelper.Placeholder="Search File ..." TextChanged="SearchChangeFileTextChanged"/>
|
||||
<ToggleButton
|
||||
Grid.Column="2"
|
||||
x:Name="toggleSwitchMode"
|
||||
Margin="4,0,0,0"
|
||||
ToolTip="SWITCH TO LIST/TREE VIEW"
|
||||
Style="{StaticResource Style.ToggleButton.ListOrTree}"
|
||||
IsChecked="{Binding Source={x:Static source:App.Preference}, Path=UIUseListInChanges, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
|
||||
<TreeView
|
||||
Grid.Row="1"
|
||||
x:Name="changeTree"
|
||||
FontFamily="Consolas"
|
||||
Visibility="{Binding ElementName=toggleSwitchMode, Path=IsChecked, Converter={StaticResource InverseBoolToCollapsed}}"
|
||||
Background="{StaticResource Brush.BG2}"
|
||||
SelectedItemChanged="ChangeTreeItemSelected"
|
||||
PreviewMouseWheel="TreeMouseWheel">
|
||||
<TreeView.Resources>
|
||||
<converters:FileStatusToColor x:Key="StatusColorConverter"/>
|
||||
<converters:FileStatusToIcon x:Key="StatusIconConverter"/>
|
||||
</TreeView.Resources>
|
||||
<TreeView.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.ItemContainerStyle}">
|
||||
<Setter Property="IsExpanded" Value="{Binding IsNodeExpanded, Mode=TwoWay}"/>
|
||||
<EventSetter Event="ContextMenuOpening" Handler="TreeContextMenuOpening"/>
|
||||
</Style>
|
||||
</TreeView.ItemContainerStyle>
|
||||
<TreeView.ItemTemplate>
|
||||
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
|
||||
<StackPanel Orientation="Horizontal" Height="24">
|
||||
<Border x:Name="status" Width="14" Height="14" Visibility="Collapsed" Background="{Binding Change, Converter={StaticResource StatusColorConverter}}" CornerRadius="2" Margin="0,0,4,0">
|
||||
<TextBlock Text="{Binding Change, Converter={StaticResource StatusIconConverter}}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" FontSize="10" RenderOptions.BitmapScalingMode="HighQuality"/>
|
||||
</Border>
|
||||
<Path x:Name="icon" Width="14" Style="{StaticResource Style.Icon}" Fill="Goldenrod" Data="{StaticResource Icon.Folder.Fill}"/>
|
||||
<TextBlock Text="{Binding Name}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" Margin="4,0,0,0" FontSize="11"/>
|
||||
</StackPanel>
|
||||
|
||||
<HierarchicalDataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding IsFile}" Value="True">
|
||||
<Setter TargetName="status" Property="Visibility" Value="Visible"/>
|
||||
<Setter TargetName="icon" Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsFile}" Value="False"/>
|
||||
<Condition Binding="{Binding IsNodeExpanded}" Value="True"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
|
||||
</MultiDataTrigger>
|
||||
</HierarchicalDataTemplate.Triggers>
|
||||
</HierarchicalDataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
</TreeView>
|
||||
|
||||
<DataGrid
|
||||
Grid.Row="1"
|
||||
x:Name="changeList2"
|
||||
Visibility="{Binding ElementName=toggleSwitchMode, Path=IsChecked, Converter={StaticResource BoolToCollapsed}}"
|
||||
RowHeight="24"
|
||||
SelectionChanged="ChangeListSelectionChanged"
|
||||
SelectionMode="Single"
|
||||
SelectionUnit="FullRow"
|
||||
Background="{StaticResource Brush.BG2}">
|
||||
<DataGrid.Resources>
|
||||
<converters:FileStatusToColor x:Key="StatusColorConverter"/>
|
||||
<converters:FileStatusToIcon x:Key="StatusIconConverter"/>
|
||||
|
||||
<Style x:Key="Style.DataGridText.VerticalCenter" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Width="22">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Border Width="14" Height="14" x:Name="status" Background="{Binding ., Converter={StaticResource StatusColorConverter}}" CornerRadius="2" Margin="2,0,4,0">
|
||||
<TextBlock Text="{Binding ., Converter={StaticResource StatusIconConverter}}" Foreground="{StaticResource Brush.FG}" TextAlignment="Center" VerticalAlignment="Center" FontSize="8"/>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTextColumn Width="*" Binding="{Binding Path}" Foreground="{StaticResource Brush.FG}" FontFamily="Consolas" ElementStyle="{StaticResource Style.DataGridText.VerticalCenter}"/>
|
||||
</DataGrid.Columns>
|
||||
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
|
||||
<EventSetter Event="ContextMenuOpening" Handler="ChangeListContextMenuOpening"/>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
|
||||
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"/>
|
||||
|
||||
<local:DiffViewer Grid.Column="2" x:Name="diffViewer" Background="{StaticResource Brush.BG3}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
314
src/UI/TwoCommitsDiff.xaml.cs
Normal file
314
src/UI/TwoCommitsDiff.xaml.cs
Normal file
|
@ -0,0 +1,314 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Diff with selected 2 commits.
|
||||
/// </summary>
|
||||
public partial class TwoCommitsDiff : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
private string sha1 = null;
|
||||
private string sha2 = null;
|
||||
private List<Git.Change> cachedChanges = new List<Git.Change>();
|
||||
private List<Git.Change> displayChanges = new List<Git.Change>();
|
||||
private string changeFilter = null;
|
||||
|
||||
/// <summary>
|
||||
/// Node for file tree.
|
||||
/// </summary>
|
||||
public class Node {
|
||||
public string FilePath { get; set; } = "";
|
||||
public string OriginalPath { get; set; } = "";
|
||||
public string Name { get; set; } = "";
|
||||
public bool IsFile { get; set; } = false;
|
||||
public bool IsNodeExpanded { get; set; } = true;
|
||||
public Git.Change Change { get; set; } = null;
|
||||
public Git.Commit.Object CommitObject { get; set; } = null;
|
||||
public List<Node> Children { get; set; } = new List<Node>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public TwoCommitsDiff() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="sha1"></param>
|
||||
/// <param name="sha2"></param>
|
||||
public void SetData(Git.Repository repo, string sha1, string sha2) {
|
||||
this.repo = repo;
|
||||
this.sha1 = sha1;
|
||||
this.sha2 = sha2;
|
||||
|
||||
txtTitle.Content = $"COMMIT: {sha1} -> {sha2}";
|
||||
Task.Run(() => LoadChanges(true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanup.
|
||||
/// </summary>
|
||||
public void Cleanup() {
|
||||
repo = null;
|
||||
cachedChanges.Clear();
|
||||
displayChanges.Clear();
|
||||
}
|
||||
|
||||
private void LoadChanges(bool reload = false) {
|
||||
if (reload) {
|
||||
cachedChanges.Clear();
|
||||
|
||||
repo.RunCommand($"diff --name-status {sha1} {sha2}", line => {
|
||||
var c = Git.Change.Parse(line, true);
|
||||
if (c != null) cachedChanges.Add(c);
|
||||
});
|
||||
}
|
||||
|
||||
displayChanges.Clear();
|
||||
|
||||
if (string.IsNullOrEmpty(changeFilter)) {
|
||||
displayChanges.AddRange(cachedChanges);
|
||||
} else {
|
||||
foreach (var c in cachedChanges) {
|
||||
if (c.Path.ToUpper().Contains(changeFilter)) displayChanges.Add(c);
|
||||
}
|
||||
}
|
||||
|
||||
List<Node> changeTreeSource = new List<Node>();
|
||||
Dictionary<string, Node> folders = new Dictionary<string, Node>();
|
||||
bool isDefaultExpanded = displayChanges.Count < 50;
|
||||
|
||||
foreach (var c in displayChanges) {
|
||||
var sepIdx = c.Path.IndexOf('/');
|
||||
if (sepIdx == -1) {
|
||||
Node node = new Node();
|
||||
node.FilePath = c.Path;
|
||||
node.IsFile = true;
|
||||
node.Name = c.Path;
|
||||
node.Change = c;
|
||||
node.IsNodeExpanded = isDefaultExpanded;
|
||||
if (c.OriginalPath != null) node.OriginalPath = c.OriginalPath;
|
||||
changeTreeSource.Add(node);
|
||||
} else {
|
||||
Node lastFolder = null;
|
||||
var start = 0;
|
||||
|
||||
while (sepIdx != -1) {
|
||||
var folder = c.Path.Substring(0, sepIdx);
|
||||
if (folders.ContainsKey(folder)) {
|
||||
lastFolder = folders[folder];
|
||||
} else if (lastFolder == null) {
|
||||
lastFolder = new Node();
|
||||
lastFolder.FilePath = folder;
|
||||
lastFolder.Name = folder.Substring(start);
|
||||
lastFolder.IsNodeExpanded = isDefaultExpanded;
|
||||
changeTreeSource.Add(lastFolder);
|
||||
folders.Add(folder, lastFolder);
|
||||
} else {
|
||||
var folderNode = new Node();
|
||||
folderNode.FilePath = folder;
|
||||
folderNode.Name = folder.Substring(start);
|
||||
folderNode.IsNodeExpanded = isDefaultExpanded;
|
||||
folders.Add(folder, folderNode);
|
||||
lastFolder.Children.Add(folderNode);
|
||||
lastFolder = folderNode;
|
||||
}
|
||||
|
||||
start = sepIdx + 1;
|
||||
sepIdx = c.Path.IndexOf('/', start);
|
||||
}
|
||||
|
||||
Node node = new Node();
|
||||
node.FilePath = c.Path;
|
||||
node.Name = c.Path.Substring(start);
|
||||
node.IsFile = true;
|
||||
node.Change = c;
|
||||
if (c.OriginalPath != null) node.OriginalPath = c.OriginalPath;
|
||||
lastFolder.Children.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
folders.Clear();
|
||||
SortTreeNodes(changeTreeSource);
|
||||
|
||||
Dispatcher.Invoke(() => {
|
||||
changeList2.ItemsSource = null;
|
||||
changeList2.ItemsSource = displayChanges;
|
||||
changeTree.ItemsSource = changeTreeSource;
|
||||
diffViewer.Reset();
|
||||
});
|
||||
}
|
||||
|
||||
private void SearchChangeFileTextChanged(object sender, TextChangedEventArgs e) {
|
||||
changeFilter = txtChangeFilter.Text.ToUpper();
|
||||
Task.Run(() => LoadChanges());
|
||||
}
|
||||
|
||||
private void ChangeTreeItemSelected(object sender, RoutedPropertyChangedEventArgs<object> e) {
|
||||
diffViewer.Reset();
|
||||
|
||||
var node = e.NewValue as Node;
|
||||
if (node == null || !node.IsFile) return;
|
||||
|
||||
diffViewer.Diff(repo, new DiffViewer.Option() {
|
||||
RevisionRange = new string[] { sha1, sha2 },
|
||||
Path = node.FilePath,
|
||||
OrgPath = node.OriginalPath
|
||||
});
|
||||
}
|
||||
|
||||
private void ChangeListSelectionChanged(object sender, SelectionChangedEventArgs e) {
|
||||
if (e.AddedItems.Count != 1) return;
|
||||
|
||||
var change = e.AddedItems[0] as Git.Change;
|
||||
if (change == null) return;
|
||||
|
||||
diffViewer.Diff(repo, new DiffViewer.Option() {
|
||||
RevisionRange = new string[] { sha1, sha2 },
|
||||
Path = change.Path,
|
||||
OrgPath = change.OriginalPath
|
||||
});
|
||||
}
|
||||
|
||||
private void ChangeListContextMenuOpening(object sender, ContextMenuEventArgs e) {
|
||||
var row = sender as DataGridRow;
|
||||
if (row == null) return;
|
||||
|
||||
var change = row.DataContext as Git.Change;
|
||||
if (change == null) return;
|
||||
|
||||
var path = change.Path;
|
||||
var menu = new ContextMenu();
|
||||
if (change.Index != Git.Change.Status.Deleted) {
|
||||
MenuItem history = new MenuItem();
|
||||
history.Header = "File History";
|
||||
history.Click += (o, ev) => {
|
||||
var viewer = new FileHistories(repo, path);
|
||||
viewer.Show();
|
||||
};
|
||||
menu.Items.Add(history);
|
||||
|
||||
MenuItem explore = new MenuItem();
|
||||
explore.Header = "Reveal in File Explorer";
|
||||
explore.Click += (o, ev) => {
|
||||
var absPath = Path.GetFullPath(repo.Path + "\\" + path);
|
||||
Process.Start("explorer", $"/select,{absPath}");
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(explore);
|
||||
}
|
||||
|
||||
MenuItem copyPath = new MenuItem();
|
||||
copyPath.Header = "Copy Path";
|
||||
copyPath.Click += (obj, ev) => {
|
||||
Clipboard.SetText(path);
|
||||
};
|
||||
menu.Items.Add(copyPath);
|
||||
menu.IsOpen = true;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ChangeListMouseDoubleClick(object sender, MouseButtonEventArgs e) {
|
||||
var row = sender as DataGridRow;
|
||||
if (row == null) return;
|
||||
|
||||
var change = row.DataContext as Git.Change;
|
||||
if (change == null) return;
|
||||
|
||||
var viewer = new FileHistories(repo, change.Path);
|
||||
viewer.Show();
|
||||
}
|
||||
|
||||
private void SortTreeNodes(List<Node> list) {
|
||||
list.Sort((l, r) => {
|
||||
if (l.IsFile) {
|
||||
return r.IsFile ? l.Name.CompareTo(r.Name) : 1;
|
||||
} else {
|
||||
return r.IsFile ? -1 : l.Name.CompareTo(r.Name);
|
||||
}
|
||||
});
|
||||
|
||||
foreach (var sub in list) {
|
||||
if (sub.Children.Count > 0) SortTreeNodes(sub.Children);
|
||||
}
|
||||
}
|
||||
|
||||
private ScrollViewer GetScrollViewer(FrameworkElement owner) {
|
||||
if (owner == null) return null;
|
||||
if (owner is ScrollViewer) return owner as ScrollViewer;
|
||||
|
||||
int n = VisualTreeHelper.GetChildrenCount(owner);
|
||||
for (int i = 0; i < n; i++) {
|
||||
var child = VisualTreeHelper.GetChild(owner, i) as FrameworkElement;
|
||||
var deep = GetScrollViewer(child);
|
||||
if (deep != null) return deep;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void TreeMouseWheel(object sender, MouseWheelEventArgs e) {
|
||||
var scroll = GetScrollViewer(sender as TreeView);
|
||||
if (scroll == null) return;
|
||||
|
||||
if (e.Delta > 0) {
|
||||
scroll.LineUp();
|
||||
} else {
|
||||
scroll.LineDown();
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void TreeContextMenuOpening(object sender, ContextMenuEventArgs e) {
|
||||
var item = sender as TreeViewItem;
|
||||
if (item == null) return;
|
||||
|
||||
var node = item.DataContext as Node;
|
||||
if (node == null || !node.IsFile) return;
|
||||
|
||||
item.IsSelected = true;
|
||||
|
||||
ContextMenu menu = new ContextMenu();
|
||||
if (node.Change == null || node.Change.Index != Git.Change.Status.Deleted) {
|
||||
MenuItem history = new MenuItem();
|
||||
history.Header = "File History";
|
||||
history.Click += (o, ev) => {
|
||||
var viewer = new FileHistories(repo, node.FilePath);
|
||||
viewer.Show();
|
||||
};
|
||||
menu.Items.Add(history);
|
||||
|
||||
MenuItem explore = new MenuItem();
|
||||
explore.Header = "Reveal in File Explorer";
|
||||
explore.Click += (o, ev) => {
|
||||
var path = Path.GetFullPath(repo.Path + "\\" + node.FilePath);
|
||||
Process.Start("explorer", $"/select,{path}");
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(explore);
|
||||
}
|
||||
|
||||
MenuItem copyPath = new MenuItem();
|
||||
copyPath.Header = "Copy Path";
|
||||
copyPath.Click += (obj, ev) => {
|
||||
Clipboard.SetText(node.FilePath);
|
||||
};
|
||||
menu.Items.Add(copyPath);
|
||||
|
||||
menu.IsOpen = true;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue