mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-05-25 14:15:00 +00:00
Upgrade project style
This commit is contained in:
parent
afd22ca85d
commit
baa6c1445a
136 changed files with 29 additions and 770 deletions
274
SourceGit/Helpers/CommitGraph.cs
Normal file
274
SourceGit/Helpers/CommitGraph.cs
Normal file
|
@ -0,0 +1,274 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Helpers {
|
||||
|
||||
/// <summary>
|
||||
/// Tools to parse commit graph.
|
||||
/// </summary>
|
||||
public class CommitGraphMaker {
|
||||
/// <summary>
|
||||
/// Sizes
|
||||
/// </summary>
|
||||
public static readonly double UNIT_WIDTH = 12;
|
||||
public static readonly double HALF_WIDTH = 6;
|
||||
public static readonly double DOUBLE_WIDTH = 24;
|
||||
public static readonly double UNIT_HEIGHT = 24;
|
||||
public static readonly double HALF_HEIGHT = 12;
|
||||
|
||||
/// <summary>
|
||||
/// Colors
|
||||
/// </summary>
|
||||
public static Brush[] Colors = new Brush[] {
|
||||
Brushes.Orange,
|
||||
Brushes.ForestGreen,
|
||||
Brushes.Gold,
|
||||
Brushes.Magenta,
|
||||
Brushes.Red,
|
||||
Brushes.Gray,
|
||||
Brushes.Turquoise,
|
||||
Brushes.Olive,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Helpers to draw lines.
|
||||
/// </summary>
|
||||
public class LineHelper {
|
||||
private double lastX = 0;
|
||||
private double lastY = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Parent commit id.
|
||||
/// </summary>
|
||||
public string Next { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is merged into this tree.
|
||||
/// </summary>
|
||||
public bool IsMerged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Points in line
|
||||
/// </summary>
|
||||
public List<Point> Points { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Brush to draw line
|
||||
/// </summary>
|
||||
public Brush Brush { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current horizontal offset.
|
||||
/// </summary>
|
||||
public double HorizontalOffset => lastX;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="nextCommitId">Parent commit id</param>
|
||||
/// <param name="isMerged">Is merged in tree</param>
|
||||
/// <param name="colorIdx">Color index</param>
|
||||
/// <param name="startPoint">Start point</param>
|
||||
public LineHelper(string nextCommitId, bool isMerged, int colorIdx, Point startPoint) {
|
||||
Next = nextCommitId;
|
||||
IsMerged = isMerged;
|
||||
Points = new List<Point>() { startPoint };
|
||||
Brush = Colors[colorIdx % Colors.Length];
|
||||
|
||||
lastX = startPoint.X;
|
||||
lastY = startPoint.Y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Line to.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <param name="isEnd"></param>
|
||||
public void AddPoint(double x, double y, bool isEnd = false) {
|
||||
if (x > lastX) {
|
||||
Points.Add(new Point(lastX, lastY));
|
||||
Points.Add(new Point(x, y - HALF_HEIGHT));
|
||||
} else if (x < lastX) {
|
||||
Points.Add(new Point(lastX, lastY + HALF_HEIGHT));
|
||||
Points.Add(new Point(x, y));
|
||||
}
|
||||
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
|
||||
if (isEnd) {
|
||||
var last = Points.Last();
|
||||
if (last.X != lastX || last.Y != lastY) Points.Add(new Point(lastX, lastY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Short link between two commits.
|
||||
/// </summary>
|
||||
public struct ShortLink {
|
||||
public Point Start;
|
||||
public Point Control;
|
||||
public Point End;
|
||||
public Brush Brush;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dot
|
||||
/// </summary>
|
||||
public struct Dot {
|
||||
public double X;
|
||||
public double Y;
|
||||
public Brush Color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Independent lines in graph
|
||||
/// </summary>
|
||||
public List<LineHelper> Lines { get; set; } = new List<LineHelper>();
|
||||
|
||||
/// <summary>
|
||||
/// Short links.
|
||||
/// </summary>
|
||||
public List<ShortLink> Links { get; set; } = new List<ShortLink>();
|
||||
|
||||
/// <summary>
|
||||
/// All dots.
|
||||
/// </summary>
|
||||
public List<Dot> Dots { get; set; } = new List<Dot>();
|
||||
|
||||
/// <summary>
|
||||
/// Highlight commit id.
|
||||
/// </summary>
|
||||
public string Highlight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parse commits.
|
||||
/// </summary>
|
||||
/// <param name="commits"></param>
|
||||
/// <returns></returns>
|
||||
public static CommitGraphMaker Parse(List<Git.Commit> commits) {
|
||||
CommitGraphMaker maker = new CommitGraphMaker();
|
||||
|
||||
List<LineHelper> unsolved = new List<LineHelper>();
|
||||
List<LineHelper> ended = new List<LineHelper>();
|
||||
Dictionary<string, LineHelper> currentMap = new Dictionary<string, LineHelper>();
|
||||
double offsetY = -HALF_HEIGHT;
|
||||
int colorIdx = 0;
|
||||
|
||||
for (int i = 0; i < commits.Count; i++) {
|
||||
Git.Commit commit = commits[i];
|
||||
LineHelper major = null;
|
||||
bool isMerged = commit.IsHEAD || commit.IsMerged;
|
||||
int oldCount = unsolved.Count;
|
||||
|
||||
// 更新Y坐标
|
||||
offsetY += UNIT_HEIGHT;
|
||||
|
||||
// 找到当前的分支的HEAD,用于默认选中
|
||||
if (maker.Highlight == null && commit.IsHEAD) {
|
||||
maker.Highlight = commit.SHA;
|
||||
}
|
||||
|
||||
// 找到第一个依赖于本提交的树,将其他依赖于本提交的树标记为终止,并对已存在的线路调整(防止线重合)
|
||||
double offsetX = -HALF_WIDTH;
|
||||
foreach (var l in unsolved) {
|
||||
if (l.Next == commit.SHA) {
|
||||
if (major == null) {
|
||||
offsetX += UNIT_WIDTH;
|
||||
major = l;
|
||||
|
||||
if (commit.Parents.Count > 0) {
|
||||
major.Next = commit.Parents[0];
|
||||
if (!currentMap.ContainsKey(major.Next)) currentMap.Add(major.Next, major);
|
||||
} else {
|
||||
major.Next = "ENDED";
|
||||
}
|
||||
|
||||
major.AddPoint(offsetX, offsetY);
|
||||
} else {
|
||||
ended.Add(l);
|
||||
}
|
||||
|
||||
isMerged = isMerged || l.IsMerged;
|
||||
} else {
|
||||
if (!currentMap.ContainsKey(l.Next)) currentMap.Add(l.Next, l);
|
||||
offsetX += UNIT_WIDTH;
|
||||
l.AddPoint(offsetX, offsetY);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理本提交为非当前分支HEAD的情况(创建新依赖线路)
|
||||
if (major == null && commit.Parents.Count > 0) {
|
||||
offsetX += UNIT_WIDTH;
|
||||
major = new LineHelper(commit.Parents[0], isMerged, colorIdx, new Point(offsetX, offsetY));
|
||||
unsolved.Add(major);
|
||||
colorIdx++;
|
||||
}
|
||||
|
||||
// 确定本提交的点的位置
|
||||
Point position = new Point(offsetX, offsetY);
|
||||
if (major != null) {
|
||||
major.IsMerged = isMerged;
|
||||
position.X = major.HorizontalOffset;
|
||||
position.Y = offsetY;
|
||||
maker.Dots.Add(new Dot() { X = position.X - 3, Y = position.Y - 3, Color = major.Brush });
|
||||
} else {
|
||||
maker.Dots.Add(new Dot() { X = position.X - 3, Y = position.Y - 3, Color = Brushes.Orange });
|
||||
}
|
||||
|
||||
// 处理本提交的其他依赖
|
||||
for (int j = 1; j < commit.Parents.Count; j++) {
|
||||
var parent = commit.Parents[j];
|
||||
if (currentMap.ContainsKey(parent)) {
|
||||
var l = currentMap[parent];
|
||||
var link = new ShortLink();
|
||||
|
||||
link.Start = position;
|
||||
link.End = new Point(l.HorizontalOffset, offsetY + HALF_HEIGHT);
|
||||
link.Control = new Point(link.End.X, link.Start.Y);
|
||||
link.Brush = l.Brush;
|
||||
maker.Links.Add(link);
|
||||
} else {
|
||||
offsetX += UNIT_WIDTH;
|
||||
unsolved.Add(new LineHelper(commit.Parents[j], isMerged, colorIdx, position));
|
||||
colorIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理已终止的线
|
||||
foreach (var l in ended) {
|
||||
l.AddPoint(position.X, position.Y, true);
|
||||
maker.Lines.Add(l);
|
||||
unsolved.Remove(l);
|
||||
}
|
||||
|
||||
// 加入本次提交
|
||||
commit.IsMerged = isMerged;
|
||||
commit.GraphOffset = System.Math.Max(offsetX + HALF_WIDTH, oldCount * UNIT_WIDTH);
|
||||
|
||||
// 清理临时数据
|
||||
ended.Clear();
|
||||
currentMap.Clear();
|
||||
}
|
||||
|
||||
// 处理尚未终结的线
|
||||
for (int i = 0; i < unsolved.Count; i++) {
|
||||
var path = unsolved[i];
|
||||
path.AddPoint((i + 0.5) * UNIT_WIDTH, (commits.Count - 0.5) * UNIT_HEIGHT, true);
|
||||
maker.Lines.Add(path);
|
||||
}
|
||||
unsolved.Clear();
|
||||
|
||||
// 处理默认选中异常
|
||||
if (maker.Highlight == null && commits.Count > 0) {
|
||||
maker.Highlight = commits[0].SHA;
|
||||
}
|
||||
|
||||
return maker;
|
||||
}
|
||||
}
|
||||
}
|
143
SourceGit/Helpers/TextBoxHelper.cs
Normal file
143
SourceGit/Helpers/TextBoxHelper.cs
Normal file
|
@ -0,0 +1,143 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Helpers {
|
||||
|
||||
/// <summary>
|
||||
/// Attached properties to TextBox.
|
||||
/// </summary>
|
||||
public static class TextBoxHelper {
|
||||
|
||||
/// <summary>
|
||||
/// Placeholder property
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.RegisterAttached(
|
||||
"Placeholder",
|
||||
typeof(string),
|
||||
typeof(TextBoxHelper),
|
||||
new PropertyMetadata(string.Empty, OnPlaceholderChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Vertical alignment for placeholder.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PlaceholderBaselineProperty = DependencyProperty.RegisterAttached(
|
||||
"PlaceholderBaseline",
|
||||
typeof(AlignmentY),
|
||||
typeof(TextBoxHelper),
|
||||
new PropertyMetadata(AlignmentY.Center));
|
||||
|
||||
/// <summary>
|
||||
/// Property to store generated placeholder brush.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PlaceholderBrushProperty = DependencyProperty.RegisterAttached(
|
||||
"PlaceholderBrush",
|
||||
typeof(Brush),
|
||||
typeof(TextBoxHelper),
|
||||
new PropertyMetadata(Brushes.Transparent));
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when placeholder changed.
|
||||
/// </summary>
|
||||
/// <param name="d"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void OnPlaceholderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var textBox = d as TextBox;
|
||||
if (textBox != null) textBox.Loaded += OnTextLoaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setter for Placeholder property
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <param name="value"></param>
|
||||
public static void SetPlaceholder(UIElement element, string value) {
|
||||
element.SetValue(PlaceholderProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Getter for Placeholder property
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetPlaceholder(UIElement element) {
|
||||
return (string)element.GetValue(PlaceholderProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setter for PlaceholderBaseline property
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <param name="align"></param>
|
||||
public static void SetPlaceholderBaseline(UIElement element, AlignmentY align) {
|
||||
element.SetValue(PlaceholderBaselineProperty, align);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setter for PlaceholderBaseline property.
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <returns></returns>
|
||||
public static AlignmentY GetPlaceholderBaseline(UIElement element) {
|
||||
return (AlignmentY)element.GetValue(PlaceholderBaselineProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setter for PlaceholderBrush property.
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <param name="value"></param>
|
||||
public static void SetPlaceholderBrush(UIElement element, Brush value) {
|
||||
element.SetValue(PlaceholderBrushProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Getter for PlaceholderBrush property.
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <returns></returns>
|
||||
public static Brush GetPlaceholderBrush(UIElement element) {
|
||||
return (Brush)element.GetValue(PlaceholderBrushProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set placeholder as background when TextBox was loaded.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void OnTextLoaded(object sender, RoutedEventArgs e) {
|
||||
var textBox = sender as TextBox;
|
||||
if (textBox == null) return;
|
||||
|
||||
Label placeholder = new Label();
|
||||
placeholder.Content = textBox.GetValue(PlaceholderProperty);
|
||||
|
||||
VisualBrush brush = new VisualBrush();
|
||||
brush.AlignmentX = AlignmentX.Left;
|
||||
brush.AlignmentY = GetPlaceholderBaseline(textBox);
|
||||
brush.TileMode = TileMode.None;
|
||||
brush.Stretch = Stretch.None;
|
||||
brush.Opacity = 0.3;
|
||||
brush.Visual = placeholder;
|
||||
|
||||
textBox.SetValue(PlaceholderBrushProperty, brush);
|
||||
textBox.Background = brush;
|
||||
textBox.TextChanged += OnTextChanged;
|
||||
OnTextChanged(textBox, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dynamically hide/show placeholder.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void OnTextChanged(object sender, RoutedEventArgs e) {
|
||||
var textBox = sender as TextBox;
|
||||
if (string.IsNullOrEmpty(textBox.Text)) {
|
||||
textBox.Background = textBox.GetValue(PlaceholderBrushProperty) as Brush;
|
||||
} else {
|
||||
textBox.Background = Brushes.Transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
329
SourceGit/Helpers/TreeViewHelper.cs
Normal file
329
SourceGit/Helpers/TreeViewHelper.cs
Normal file
|
@ -0,0 +1,329 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Helpers {
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to enable multi-selection of TreeView
|
||||
/// </summary>
|
||||
public static class TreeViewHelper {
|
||||
|
||||
/// <summary>
|
||||
/// Definition of EnableMultiSelection property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty EnableMultiSelectionProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"EnableMultiSelection",
|
||||
typeof(bool),
|
||||
typeof(TreeViewHelper),
|
||||
new FrameworkPropertyMetadata(false, OnEnableMultiSelectionChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Getter of EnableMultiSelection
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static bool GetEnableMultiSelection(DependencyObject obj) {
|
||||
return (bool)obj.GetValue(EnableMultiSelectionProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setter of EnableMultiSelection
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="value"></param>
|
||||
public static void SetEnableMultiSelection(DependencyObject obj, bool value) {
|
||||
obj.SetValue(EnableMultiSelectionProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Definition of SelectedItems
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty SelectedItemsProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"SelectedItems",
|
||||
typeof(ObservableCollection<TreeViewItem>),
|
||||
typeof(TreeViewHelper),
|
||||
new FrameworkPropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Getter of SelectedItems
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static ObservableCollection<TreeViewItem> GetSelectedItems(DependencyObject obj) {
|
||||
return (ObservableCollection<TreeViewItem>)obj.GetValue(SelectedItemsProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setter of SelectedItems
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="value"></param>
|
||||
public static void SetSelectedItems(DependencyObject obj, ObservableCollection<TreeViewItem> value) {
|
||||
obj.SetValue(SelectedItemsProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Definition of IsChecked property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsCheckedProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"IsChecked",
|
||||
typeof(bool),
|
||||
typeof(TreeViewHelper),
|
||||
new FrameworkPropertyMetadata(false));
|
||||
|
||||
/// <summary>
|
||||
/// Getter of IsChecked Property.
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static bool GetIsChecked(DependencyObject obj) {
|
||||
return (bool)obj.GetValue(IsCheckedProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setter of IsChecked property
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="value"></param>
|
||||
public static void SetIsChecked(DependencyObject obj, bool value) {
|
||||
obj.SetValue(IsCheckedProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Definition of MultiSelectionChangedEvent
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent MultiSelectionChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("MultiSelectionChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TreeViewHelper));
|
||||
|
||||
/// <summary>
|
||||
/// Add handler for MultiSelectionChanged event.
|
||||
/// </summary>
|
||||
/// <param name="d"></param>
|
||||
/// <param name="handler"></param>
|
||||
public static void AddMultiSelectionChangedHandler(DependencyObject d, RoutedEventHandler handler) {
|
||||
var tree = d as TreeView;
|
||||
if (tree != null) tree.AddHandler(MultiSelectionChangedEvent, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove handler for MultiSelectionChanged event.
|
||||
/// </summary>
|
||||
/// <param name="d"></param>
|
||||
/// <param name="handler"></param>
|
||||
public static void RemoveMultiSelectionChangedHandler(DependencyObject d, RoutedEventHandler handler) {
|
||||
var tree = d as TreeView;
|
||||
if (tree != null) tree.RemoveHandler(MultiSelectionChangedEvent, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select all items in tree.
|
||||
/// </summary>
|
||||
/// <param name="tree"></param>
|
||||
public static void SelectWholeTree(TreeView tree) {
|
||||
var selected = GetSelectedItems(tree);
|
||||
selected.Clear();
|
||||
SelectAll(selected, tree);
|
||||
tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selected one item by DataContext
|
||||
/// </summary>
|
||||
/// <param name="tree"></param>
|
||||
/// <param name="obj"></param>
|
||||
public static void SelectOneByContext(TreeView tree, object obj) {
|
||||
var item = FindTreeViewItemByDataContext(tree, obj);
|
||||
if (item != null) {
|
||||
var selected = GetSelectedItems(tree);
|
||||
selected.Add(item);
|
||||
item.SetValue(IsCheckedProperty, true);
|
||||
tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unselect the whole tree.
|
||||
/// </summary>
|
||||
/// <param name="tree"></param>
|
||||
public static void UnselectTree(TreeView tree) {
|
||||
var selected = GetSelectedItems(tree);
|
||||
if (selected.Count == 0) return;
|
||||
|
||||
foreach (var old in selected) old.SetValue(IsCheckedProperty, false);
|
||||
selected.Clear();
|
||||
tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hooks when EnableMultiSelection changed.
|
||||
/// </summary>
|
||||
/// <param name="d"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void OnEnableMultiSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var tree = d as TreeView;
|
||||
if (tree != null && (bool)e.NewValue) {
|
||||
tree.SetValue(SelectedItemsProperty, new ObservableCollection<TreeViewItem>());
|
||||
tree.PreviewMouseDown += OnTreeMouseDown;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Preview mouse button select.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void OnTreeMouseDown(object sender, MouseButtonEventArgs e) {
|
||||
var tree = sender as TreeView;
|
||||
if (tree == null) return;
|
||||
|
||||
var hit = VisualTreeHelper.HitTest(tree, e.GetPosition(tree));
|
||||
if (hit == null || hit.VisualHit is null) return;
|
||||
|
||||
var item = FindTreeViewItem(hit.VisualHit as UIElement);
|
||||
if (item == null) return;
|
||||
|
||||
var selected = GetSelectedItems(tree);
|
||||
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) {
|
||||
if (GetIsChecked(item)) {
|
||||
selected.Remove(item);
|
||||
item.SetValue(IsCheckedProperty, false);
|
||||
} else {
|
||||
selected.Add(item);
|
||||
item.SetValue(IsCheckedProperty, true);
|
||||
}
|
||||
} else if ((Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) && selected.Count > 0) {
|
||||
var last = selected.Last();
|
||||
if (last == item) return;
|
||||
|
||||
var lastPos = last.PointToScreen(new Point(0, 0));
|
||||
var curPos = item.PointToScreen(new Point(0, 0));
|
||||
if (lastPos.Y > curPos.Y) {
|
||||
SelectRange(selected, tree, item, last);
|
||||
} else {
|
||||
SelectRange(selected, tree, last, item);
|
||||
}
|
||||
|
||||
selected.Add(item);
|
||||
item.SetValue(IsCheckedProperty, true);
|
||||
} else if (e.RightButton == MouseButtonState.Pressed) {
|
||||
if (GetIsChecked(item)) return;
|
||||
|
||||
foreach (var old in selected) old.SetValue(IsCheckedProperty, false);
|
||||
selected.Clear();
|
||||
selected.Add(item);
|
||||
item.SetValue(IsCheckedProperty, true);
|
||||
} else {
|
||||
foreach (var old in selected) old.SetValue(IsCheckedProperty, false);
|
||||
selected.Clear();
|
||||
selected.Add(item);
|
||||
item.SetValue(IsCheckedProperty, true);
|
||||
}
|
||||
|
||||
tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find TreeViewItem by child element.
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="child"></param>
|
||||
/// <returns></returns>
|
||||
private static TreeViewItem FindTreeViewItem(DependencyObject child) {
|
||||
if (child == null) return null;
|
||||
if (child is TreeViewItem) return child as TreeViewItem;
|
||||
if (child is TreeView) return null;
|
||||
return FindTreeViewItem(VisualTreeHelper.GetParent(child));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find TreeViewItem by DataContext
|
||||
/// </summary>
|
||||
/// <param name="control"></param>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
private static TreeViewItem FindTreeViewItemByDataContext(ItemsControl control, object obj) {
|
||||
if (control == null) return null;
|
||||
if (control.DataContext == obj) return control as TreeViewItem;
|
||||
|
||||
for (int i = 0; i < control.Items.Count; i++) {
|
||||
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as ItemsControl;
|
||||
var found = FindTreeViewItemByDataContext(child, obj);
|
||||
if (found != null) return found;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select all items.
|
||||
/// </summary>
|
||||
/// <param name="selected"></param>
|
||||
/// <param name="control"></param>
|
||||
private static void SelectAll(ObservableCollection<TreeViewItem> selected, ItemsControl control) {
|
||||
for (int i = 0; i < control.Items.Count; i++) {
|
||||
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
|
||||
if (child == null) continue;
|
||||
|
||||
selected.Add(child);
|
||||
child.SetValue(IsCheckedProperty, true);
|
||||
SelectAll(selected, child);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select range items between given.
|
||||
/// </summary>
|
||||
/// <param name="selected"></param>
|
||||
/// <param name="control"></param>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="to"></param>
|
||||
/// <param name="started"></param>
|
||||
private static int SelectRange(ObservableCollection<TreeViewItem> selected, ItemsControl control, TreeViewItem from, TreeViewItem to, int matches = 0) {
|
||||
for (int i = 0; i < control.Items.Count; i++) {
|
||||
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
|
||||
if (child == null) continue;
|
||||
|
||||
if (matches == 1) {
|
||||
if (child == to) return 2;
|
||||
selected.Add(child);
|
||||
child.SetValue(IsCheckedProperty, true);
|
||||
if (TryEndRangeSelection(selected, child, to)) return 2;
|
||||
} else if (child == from) {
|
||||
matches = 1;
|
||||
if (TryEndRangeSelection(selected, child, to)) return 2;
|
||||
} else {
|
||||
matches = SelectRange(selected, child, from, to, matches);
|
||||
if (matches == 2) return 2;
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
private static bool TryEndRangeSelection(ObservableCollection<TreeViewItem> selected, TreeViewItem control, TreeViewItem end) {
|
||||
for (int i = 0; i < control.Items.Count; i++) {
|
||||
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
|
||||
if (child == null) continue;
|
||||
|
||||
if (child == end) {
|
||||
return true;
|
||||
} else {
|
||||
selected.Add(child);
|
||||
child.SetValue(IsCheckedProperty, true);
|
||||
|
||||
var ended = TryEndRangeSelection(selected, child, end);
|
||||
if (ended) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
124
SourceGit/Helpers/Validations.cs
Normal file
124
SourceGit/Helpers/Validations.cs
Normal file
|
@ -0,0 +1,124 @@
|
|||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.Helpers {
|
||||
|
||||
/// <summary>
|
||||
/// Validate clone folder.
|
||||
/// </summary>
|
||||
public class CloneFolderRule : ValidationRule {
|
||||
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
|
||||
var badPath = "EXISTS and FULL ACCESS CONTROL needed";
|
||||
var path = value as string;
|
||||
return Directory.Exists(path) ? ValidationResult.ValidResult : new ValidationResult(false, badPath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate git remote URL
|
||||
/// </summary>
|
||||
public class RemoteUriRule : ValidationRule {
|
||||
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
|
||||
var badUrl = "Remote git URL not supported";
|
||||
return Git.Repository.IsValidUrl(value as string) ? ValidationResult.ValidResult : new ValidationResult(false, badUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate tag name.
|
||||
/// </summary>
|
||||
public class RemoteNameRule : ValidationRule {
|
||||
public Git.Repository Repo { get; set; }
|
||||
|
||||
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
|
||||
var regex = new Regex(@"^[\w\-\.]+$");
|
||||
var name = value as string;
|
||||
var remotes = Repo.Remotes();
|
||||
|
||||
if (string.IsNullOrEmpty(name)) return new ValidationResult(false, "Remote name can NOT be null");
|
||||
if (!regex.IsMatch(name)) return new ValidationResult(false, $"Bad name for remote. Regex: ^[\\w\\-\\.]+$");
|
||||
|
||||
foreach (var t in remotes) {
|
||||
if (t.Name == name) {
|
||||
return new ValidationResult(false, $"Remote '{name}' already exists");
|
||||
}
|
||||
}
|
||||
|
||||
return ValidationResult.ValidResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate branch name.
|
||||
/// </summary>
|
||||
public class BranchNameRule : ValidationRule {
|
||||
public Git.Repository Repo { get; set; }
|
||||
public string Prefix { get; set; } = "";
|
||||
|
||||
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
|
||||
var regex = new Regex(@"^[\w\-/\.]+$");
|
||||
var name = value as string;
|
||||
var branches = Repo.Branches();
|
||||
|
||||
if (string.IsNullOrEmpty(name)) return new ValidationResult(false, "Branch name can NOT be null");
|
||||
if (!regex.IsMatch(name)) return new ValidationResult(false, $"Bad name for branch. Regex: ^[\\w\\-/\\.]+$");
|
||||
|
||||
name = Prefix + name;
|
||||
|
||||
foreach (var b in branches) {
|
||||
if (b.Name == name) {
|
||||
return new ValidationResult(false, $"Branch '{name}' already exists");
|
||||
}
|
||||
}
|
||||
|
||||
return ValidationResult.ValidResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate tag name.
|
||||
/// </summary>
|
||||
public class TagNameRule : ValidationRule {
|
||||
public Git.Repository Repo { get; set; }
|
||||
|
||||
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
|
||||
var regex = new Regex(@"^[\w\-\.]+$");
|
||||
var name = value as string;
|
||||
var tags = Repo.Tags();
|
||||
|
||||
if (string.IsNullOrEmpty(name)) return new ValidationResult(false, "Tag name can NOT be null");
|
||||
if (!regex.IsMatch(name)) return new ValidationResult(false, $"Bad name for tag. Regex: ^[\\w\\-\\.]+$");
|
||||
|
||||
foreach (var t in tags) {
|
||||
if (t.Name == name) {
|
||||
return new ValidationResult(false, $"Tag '{name}' already exists");
|
||||
}
|
||||
}
|
||||
|
||||
return ValidationResult.ValidResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Required for commit subject.
|
||||
/// </summary>
|
||||
public class CommitSubjectRequiredRule : ValidationRule {
|
||||
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
|
||||
var subject = value as string;
|
||||
return string.IsNullOrWhiteSpace(subject) ? new ValidationResult(false, "Commit subject can NOT be empty") : ValidationResult.ValidResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Required for patch file.
|
||||
/// </summary>
|
||||
public class PatchFileRequiredRule : ValidationRule {
|
||||
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
|
||||
var path = value as string;
|
||||
var succ = !string.IsNullOrEmpty(path) && File.Exists(path);
|
||||
return !succ ? new ValidationResult(false, "Invalid path for patch file") : ValidationResult.ValidResult;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue