mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-05-27 23:25:00 +00:00
refactor(*): re-arrange project
This commit is contained in:
parent
7558bbd4f2
commit
5ce9cffcee
140 changed files with 4980 additions and 5003 deletions
6
src/App.config
Normal file
6
src/App.config
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<AppContextSwitchOverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false" />
|
||||
</runtime>
|
||||
</configuration>
|
BIN
src/App.ico
Normal file
BIN
src/App.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
14
src/App.manifest
Normal file
14
src/App.manifest
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
15
src/App.xaml
Normal file
15
src/App.xaml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<Application x:Class="SourceGit.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Startup="OnAppStartup"
|
||||
Deactivated="OnAppDeactivated">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Icons.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Controls.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Themes/Dark.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
102
src/App.xaml.cs
Normal file
102
src/App.xaml.cs
Normal file
|
@ -0,0 +1,102 @@
|
|||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
|
||||
namespace SourceGit {
|
||||
|
||||
/// <summary>
|
||||
/// Application.
|
||||
/// </summary>
|
||||
public partial class App : Application {
|
||||
|
||||
/// <summary>
|
||||
/// Getter/Setter for Git preference.
|
||||
/// </summary>
|
||||
public static Git.Preference Preference {
|
||||
get { return Git.Preference.Instance; }
|
||||
set { Git.Preference.Instance = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if GIT has been configured.
|
||||
/// </summary>
|
||||
public static bool IsGitConfigured {
|
||||
get {
|
||||
return !string.IsNullOrEmpty(Preference.GitExecutable)
|
||||
&& File.Exists(Preference.GitExecutable);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raise error message.
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
public static void RaiseError(string msg) {
|
||||
var main = Current.MainWindow as UI.Launcher;
|
||||
main.Dispatcher.Invoke(() => main.Errors.Add(msg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Startup event.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnAppStartup(object sender, StartupEventArgs e) {
|
||||
// Use this app as a sequence editor?
|
||||
var args = e.Args;
|
||||
if (args.Length > 1) {
|
||||
if (args[0] == "--interactive-rebase") {
|
||||
if (args.Length < 3) {
|
||||
Environment.Exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
File.WriteAllText(args[2], File.ReadAllText(args[1]));
|
||||
}
|
||||
|
||||
Environment.Exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try auto configure git via registry.
|
||||
if (!IsGitConfigured) {
|
||||
var root = RegistryKey.OpenBaseKey(
|
||||
RegistryHive.LocalMachine,
|
||||
Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32);
|
||||
|
||||
var git = root.OpenSubKey("SOFTWARE\\GitForWindows");
|
||||
if (git != null) {
|
||||
Preference.GitExecutable = Path.Combine(
|
||||
git.GetValue("InstallPath") as string,
|
||||
"bin",
|
||||
"git.exe");
|
||||
}
|
||||
}
|
||||
|
||||
// Apply themes
|
||||
if (Preference.UIUseLightTheme) {
|
||||
foreach (var rs in Current.Resources.MergedDictionaries) {
|
||||
if (rs.Source != null && rs.Source.OriginalString.StartsWith("pack://application:,,,/Resources/Themes/")) {
|
||||
rs.Source = new Uri("pack://application:,,,/Resources/Themes/Light.xaml", UriKind.Absolute);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show main window
|
||||
Current.MainWindow = new UI.Launcher();
|
||||
Current.MainWindow.Show();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivated event.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnAppDeactivated(object sender, EventArgs e) {
|
||||
Git.Preference.Save();
|
||||
GC.Collect();
|
||||
}
|
||||
}
|
||||
}
|
37
src/Converters/BoolToCollapsed.cs
Normal file
37
src/Converters/BoolToCollapsed.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace SourceGit.Converters {
|
||||
|
||||
/// <summary>
|
||||
/// Same as BoolToVisibilityConverter.
|
||||
/// </summary>
|
||||
public class BoolToCollapsed : IValueConverter {
|
||||
|
||||
/// <summary>
|
||||
/// Implement IValueConverter.Convert
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="parameter"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implement IValueConverter.ConvertBack
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="parameter"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
59
src/Converters/FileStatusToColor.cs
Normal file
59
src/Converters/FileStatusToColor.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Converters {
|
||||
|
||||
/// <summary>
|
||||
/// Convert file status to brush
|
||||
/// </summary>
|
||||
public class FileStatusToColor : IValueConverter {
|
||||
|
||||
/// <summary>
|
||||
/// Is only test local changes.
|
||||
/// </summary>
|
||||
public bool OnlyWorkTree { get; set; } = false;
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
var change = value as Git.Change;
|
||||
if (change == null) return Brushes.Transparent;
|
||||
|
||||
var status = Git.Change.Status.None;
|
||||
if (OnlyWorkTree) {
|
||||
if (change.IsConflit) return Brushes.Yellow;
|
||||
status = change.WorkTree;
|
||||
} else {
|
||||
status = change.Index;
|
||||
}
|
||||
|
||||
if (App.Preference.UIUseLightTheme) {
|
||||
switch (status) {
|
||||
case Git.Change.Status.Modified: return Brushes.Goldenrod;
|
||||
case Git.Change.Status.Added: return Brushes.Green;
|
||||
case Git.Change.Status.Deleted: return Brushes.Red;
|
||||
case Git.Change.Status.Renamed: return Brushes.Magenta;
|
||||
case Git.Change.Status.Copied: return Brushes.Goldenrod;
|
||||
case Git.Change.Status.Unmerged: return Brushes.Goldenrod;
|
||||
case Git.Change.Status.Untracked: return Brushes.Green;
|
||||
default: return Brushes.Transparent;
|
||||
}
|
||||
} else {
|
||||
switch (status) {
|
||||
case Git.Change.Status.Modified: return Brushes.DarkGoldenrod;
|
||||
case Git.Change.Status.Added: return Brushes.DarkGreen;
|
||||
case Git.Change.Status.Deleted: return Brushes.DarkRed;
|
||||
case Git.Change.Status.Renamed: return Brushes.DarkMagenta;
|
||||
case Git.Change.Status.Copied: return Brushes.DarkGoldenrod;
|
||||
case Git.Change.Status.Unmerged: return Brushes.DarkGoldenrod;
|
||||
case Git.Change.Status.Untracked: return Brushes.DarkGreen;
|
||||
default: return Brushes.Transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
45
src/Converters/FileStatusToIcon.cs
Normal file
45
src/Converters/FileStatusToIcon.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace SourceGit.Converters {
|
||||
|
||||
/// <summary>
|
||||
/// Convert file status to icon.
|
||||
/// </summary>
|
||||
public class FileStatusToIcon : IValueConverter {
|
||||
|
||||
/// <summary>
|
||||
/// Is only test local changes.
|
||||
/// </summary>
|
||||
public bool OnlyWorkTree { get; set; } = false;
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
var change = value as Git.Change;
|
||||
if (change == null) return "";
|
||||
|
||||
var status = Git.Change.Status.None;
|
||||
if (OnlyWorkTree) {
|
||||
if (change.IsConflit) return "X";
|
||||
status = change.WorkTree;
|
||||
} else {
|
||||
status = change.Index;
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case Git.Change.Status.Modified: return "M";
|
||||
case Git.Change.Status.Added: return "A";
|
||||
case Git.Change.Status.Deleted: return "D";
|
||||
case Git.Change.Status.Renamed: return "R";
|
||||
case Git.Change.Status.Copied: return "C";
|
||||
case Git.Change.Status.Unmerged: return "U";
|
||||
case Git.Change.Status.Untracked: return "?";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
37
src/Converters/IndentToMargin.cs
Normal file
37
src/Converters/IndentToMargin.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace SourceGit.Converters {
|
||||
|
||||
/// <summary>
|
||||
/// Convert indent(horizontal offset) to Margin property
|
||||
/// </summary>
|
||||
public class IndentToMargin : IValueConverter {
|
||||
|
||||
/// <summary>
|
||||
/// Implement IValueConverter.Convert
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="parameter"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
return new Thickness((double)value, 0, 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implement IValueConverter.ConvertBack
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="parameter"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
return ((Thickness)value).Left;
|
||||
}
|
||||
}
|
||||
}
|
19
src/Converters/InverseBool.cs
Normal file
19
src/Converters/InverseBool.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace SourceGit.Converters {
|
||||
|
||||
/// <summary>
|
||||
/// Inverse bool converter.
|
||||
/// </summary>
|
||||
public class InverseBool : IValueConverter {
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
return !((bool)value);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
37
src/Converters/InverseBoolToCollapsed.cs
Normal file
37
src/Converters/InverseBoolToCollapsed.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace SourceGit.Converters {
|
||||
|
||||
/// <summary>
|
||||
/// Inverse BoolToCollapsed.
|
||||
/// </summary>
|
||||
public class InverseBoolToCollapsed : IValueConverter {
|
||||
|
||||
/// <summary>
|
||||
/// Implement IValueConverter.Convert
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="parameter"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
return (bool)value ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implement IValueConverter.ConvertBack
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="parameter"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
41
src/Converters/PercentToDouble.cs
Normal file
41
src/Converters/PercentToDouble.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace SourceGit.Converters {
|
||||
|
||||
/// <summary>
|
||||
/// Convert percent to double.
|
||||
/// </summary>
|
||||
public class PercentToDouble : IValueConverter {
|
||||
|
||||
/// <summary>
|
||||
/// Percentage.
|
||||
/// </summary>
|
||||
public double Percent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Implement IValueConverter.Convert
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="parameter"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
return (double)value * Percent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implement IValueConverter.ConvertBack
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="parameter"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
69
src/Converters/TreeViewItemDepthToMargin.cs
Normal file
69
src/Converters/TreeViewItemDepthToMargin.cs
Normal file
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Converters {
|
||||
|
||||
/// <summary>
|
||||
/// Convert depth of a TreeViewItem to Margin property.
|
||||
/// </summary>
|
||||
public class TreeViewItemDepthToMargin : IValueConverter {
|
||||
|
||||
/// <summary>
|
||||
/// Indent length
|
||||
/// </summary>
|
||||
public double Indent { get; set; } = 19;
|
||||
|
||||
/// <summary>
|
||||
/// Implement IValueConverter.Convert
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="parameter"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
TreeViewItem item = value as TreeViewItem;
|
||||
if (item == null) return new Thickness(0);
|
||||
|
||||
TreeViewItem iterator = GetParent(item);
|
||||
int depth = 0;
|
||||
while (iterator != null) {
|
||||
depth++;
|
||||
iterator = GetParent(iterator);
|
||||
}
|
||||
|
||||
return new Thickness(Indent * depth, 0, 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implement IValueConvert.ConvertBack
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="targetType"></param>
|
||||
/// <param name="parameter"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get parent item.
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
private TreeViewItem GetParent(TreeViewItem item) {
|
||||
var parent = VisualTreeHelper.GetParent(item);
|
||||
|
||||
while (parent != null && !(parent is TreeView) && !(parent is TreeViewItem)) {
|
||||
parent = VisualTreeHelper.GetParent(parent);
|
||||
}
|
||||
|
||||
return parent as TreeViewItem;
|
||||
}
|
||||
}
|
||||
}
|
35
src/Git/Blame.cs
Normal file
35
src/Git/Blame.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Git {
|
||||
|
||||
/// <summary>
|
||||
/// Blame
|
||||
/// </summary>
|
||||
public class Blame {
|
||||
|
||||
/// <summary>
|
||||
/// Block content.
|
||||
/// </summary>
|
||||
public class Block {
|
||||
public string CommitSHA { get; set; }
|
||||
public string Author { get; set; }
|
||||
public string Time { get; set; }
|
||||
public string Content { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blocks
|
||||
/// </summary>
|
||||
public List<Block> Blocks { get; set; } = new List<Block>();
|
||||
|
||||
/// <summary>
|
||||
/// Is binary file?
|
||||
/// </summary>
|
||||
public bool IsBinary { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Line count.
|
||||
/// </summary>
|
||||
public int LineCount { get; set; } = 0;
|
||||
}
|
||||
}
|
190
src/Git/Branch.cs
Normal file
190
src/Git/Branch.cs
Normal file
|
@ -0,0 +1,190 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Git {
|
||||
|
||||
/// <summary>
|
||||
/// Git branch
|
||||
/// </summary>
|
||||
public class Branch {
|
||||
private static readonly string PRETTY_FORMAT = @"$%(refname)$%(objectname)$%(HEAD)$%(upstream)$%(upstream:track)$%(contents:subject)";
|
||||
private static readonly Regex PARSE = new Regex(@"\$(.*)\$(.*)\$([\* ])\$(.*)\$(.*?)\$(.*)");
|
||||
private static readonly Regex AHEAD = new Regex(@"ahead (\d+)");
|
||||
private static readonly Regex BEHIND = new Regex(@"behind (\d+)");
|
||||
|
||||
/// <summary>
|
||||
/// Branch type.
|
||||
/// </summary>
|
||||
public enum Type {
|
||||
Normal,
|
||||
Feature,
|
||||
Release,
|
||||
Hotfix,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Branch name
|
||||
/// </summary>
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Full name.
|
||||
/// </summary>
|
||||
public string FullName { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Head ref
|
||||
/// </summary>
|
||||
public string Head { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Subject for head ref.
|
||||
/// </summary>
|
||||
public string HeadSubject { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Is local branch
|
||||
/// </summary>
|
||||
public bool IsLocal { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Branch type.
|
||||
/// </summary>
|
||||
public Type Kind { get; set; } = Type.Normal;
|
||||
|
||||
/// <summary>
|
||||
/// Remote name. Only used for remote branch
|
||||
/// </summary>
|
||||
public string Remote { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Upstream. Only used for local branches.
|
||||
/// </summary>
|
||||
public string Upstream { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Track information for upstream. Only used for local branches.
|
||||
/// </summary>
|
||||
public string UpstreamTrack { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is current branch. Only used for local branches.
|
||||
/// </summary>
|
||||
public bool IsCurrent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is this branch's HEAD same with upstream?
|
||||
/// </summary>
|
||||
public bool IsSameWithUpstream => string.IsNullOrEmpty(UpstreamTrack);
|
||||
|
||||
/// <summary>
|
||||
/// Enable filter in log histories.
|
||||
/// </summary>
|
||||
public bool IsFiltered { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Load branches.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
public static List<Branch> Load(Repository repo) {
|
||||
var localPrefix = "refs/heads/";
|
||||
var remotePrefix = "refs/remotes/";
|
||||
var branches = new List<Branch>();
|
||||
var remoteBranches = new List<string>();
|
||||
|
||||
repo.RunCommand("branch -l --all -v --format=\"" + PRETTY_FORMAT + "\"", line => {
|
||||
var match = PARSE.Match(line);
|
||||
if (!match.Success) return;
|
||||
|
||||
var branch = new Branch();
|
||||
var refname = match.Groups[1].Value;
|
||||
if (refname.EndsWith("/HEAD")) return;
|
||||
|
||||
if (refname.StartsWith(localPrefix, StringComparison.Ordinal)) {
|
||||
branch.Name = refname.Substring(localPrefix.Length);
|
||||
branch.IsLocal = true;
|
||||
} else if (refname.StartsWith(remotePrefix, StringComparison.Ordinal)) {
|
||||
var name = refname.Substring(remotePrefix.Length);
|
||||
branch.Remote = name.Substring(0, name.IndexOf('/'));
|
||||
branch.Name = name;
|
||||
branch.IsLocal = false;
|
||||
remoteBranches.Add(refname);
|
||||
}
|
||||
|
||||
branch.FullName = refname;
|
||||
branch.Head = match.Groups[2].Value;
|
||||
branch.IsCurrent = match.Groups[3].Value == "*";
|
||||
branch.Upstream = match.Groups[4].Value;
|
||||
branch.UpstreamTrack = ParseTrack(match.Groups[5].Value);
|
||||
branch.HeadSubject = match.Groups[6].Value;
|
||||
|
||||
branches.Add(branch);
|
||||
});
|
||||
|
||||
// Fixed deleted remote branch
|
||||
foreach (var b in branches) {
|
||||
if (!string.IsNullOrEmpty(b.Upstream) && !remoteBranches.Contains(b.Upstream)) {
|
||||
b.Upstream = null;
|
||||
}
|
||||
}
|
||||
|
||||
return branches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create new branch.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="startPoint"></param>
|
||||
public static void Create(Repository repo, string name, string startPoint) {
|
||||
var errs = repo.RunCommand($"branch {name} {startPoint}", null);
|
||||
if (errs != null) App.RaiseError(errs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rename branch
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="name"></param>
|
||||
public void Rename(Repository repo, string name) {
|
||||
var errs = repo.RunCommand($"branch -M {Name} {name}", null);
|
||||
if (errs != null) App.RaiseError(errs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete branch.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
public void Delete(Repository repo) {
|
||||
string errs = null;
|
||||
|
||||
if (!IsLocal) {
|
||||
errs = repo.RunCommand($"-c credential.helper=manager push {Remote} --delete {Name.Substring(Name.IndexOf('/')+1)}", null);
|
||||
} else {
|
||||
errs = repo.RunCommand($"branch -D {Name}", null);
|
||||
}
|
||||
|
||||
if (errs != null) App.RaiseError(errs);
|
||||
}
|
||||
|
||||
private static string ParseTrack(string data) {
|
||||
if (string.IsNullOrEmpty(data)) return "";
|
||||
|
||||
string track = "";
|
||||
|
||||
var ahead = AHEAD.Match(data);
|
||||
if (ahead.Success) {
|
||||
track += ahead.Groups[1].Value + "↑ ";
|
||||
}
|
||||
|
||||
var behind = BEHIND.Match(data);
|
||||
if (behind.Success) {
|
||||
track += behind.Groups[1].Value + "↓";
|
||||
}
|
||||
|
||||
return track.Trim();
|
||||
}
|
||||
}
|
||||
}
|
147
src/Git/Change.cs
Normal file
147
src/Git/Change.cs
Normal file
|
@ -0,0 +1,147 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Git {
|
||||
|
||||
/// <summary>
|
||||
/// Changed file status.
|
||||
/// </summary>
|
||||
public class Change {
|
||||
private static readonly Regex FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
|
||||
|
||||
/// <summary>
|
||||
/// Status Code
|
||||
/// </summary>
|
||||
public enum Status {
|
||||
None,
|
||||
Modified,
|
||||
Added,
|
||||
Deleted,
|
||||
Renamed,
|
||||
Copied,
|
||||
Unmerged,
|
||||
Untracked,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Index status
|
||||
/// </summary>
|
||||
public Status Index { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Work tree status.
|
||||
/// </summary>
|
||||
public Status WorkTree { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current file path.
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Original file path before this revision.
|
||||
/// </summary>
|
||||
public string OriginalPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Staged(added) in index?
|
||||
/// </summary>
|
||||
public bool IsAddedToIndex {
|
||||
get {
|
||||
if (Index == Status.None || Index == Status.Untracked) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is conflict?
|
||||
/// </summary>
|
||||
public bool IsConflit {
|
||||
get {
|
||||
if (Index == Status.Unmerged || WorkTree == Status.Unmerged) return true;
|
||||
if (Index == Status.Added && WorkTree == Status.Added) return true;
|
||||
if (Index == Status.Deleted && WorkTree == Status.Deleted) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse change for `--name-status` data.
|
||||
/// </summary>
|
||||
/// <param name="data">Raw data.</param>
|
||||
/// <param name="fromCommit">Read from commit?</param>
|
||||
/// <returns>Parsed change instance.</returns>
|
||||
public static Change Parse(string data, bool fromCommit = false) {
|
||||
var match = FORMAT.Match(data);
|
||||
if (!match.Success) return null;
|
||||
|
||||
var change = new Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
||||
if (fromCommit) {
|
||||
switch (status[0]) {
|
||||
case 'M': change.Set(Status.Modified); break;
|
||||
case 'A': change.Set(Status.Added); break;
|
||||
case 'D': change.Set(Status.Deleted); break;
|
||||
case 'R': change.Set(Status.Renamed); break;
|
||||
case 'C': change.Set(Status.Copied); break;
|
||||
default: return null;
|
||||
}
|
||||
} else {
|
||||
switch (status) {
|
||||
case " M": change.Set(Status.None, Status.Modified); break;
|
||||
case " A": change.Set(Status.None, Status.Added); break;
|
||||
case " D": change.Set(Status.None, Status.Deleted); break;
|
||||
case " R": change.Set(Status.None, Status.Renamed); break;
|
||||
case " C": change.Set(Status.None, Status.Copied); break;
|
||||
case "M": change.Set(Status.Modified, Status.None); break;
|
||||
case "MM": change.Set(Status.Modified, Status.Modified); break;
|
||||
case "MD": change.Set(Status.Modified, Status.Deleted); break;
|
||||
case "A": change.Set(Status.Added, Status.None); break;
|
||||
case "AM": change.Set(Status.Added, Status.Modified); break;
|
||||
case "AD": change.Set(Status.Added, Status.Deleted); break;
|
||||
case "D": change.Set(Status.Deleted, Status.None); break;
|
||||
case "R": change.Set(Status.Renamed, Status.None); break;
|
||||
case "RM": change.Set(Status.Renamed, Status.Modified); break;
|
||||
case "RD": change.Set(Status.Renamed, Status.Deleted); break;
|
||||
case "C": change.Set(Status.Copied, Status.None); break;
|
||||
case "CM": change.Set(Status.Copied, Status.Modified); break;
|
||||
case "CD": change.Set(Status.Copied, Status.Deleted); break;
|
||||
case "DR": change.Set(Status.Deleted, Status.Renamed); break;
|
||||
case "DC": change.Set(Status.Deleted, Status.Copied); break;
|
||||
case "DD": change.Set(Status.Deleted, Status.Deleted); break;
|
||||
case "AU": change.Set(Status.Added, Status.Unmerged); break;
|
||||
case "UD": change.Set(Status.Unmerged, Status.Deleted); break;
|
||||
case "UA": change.Set(Status.Unmerged, Status.Added); break;
|
||||
case "DU": change.Set(Status.Deleted, Status.Unmerged); break;
|
||||
case "AA": change.Set(Status.Added, Status.Added); break;
|
||||
case "UU": change.Set(Status.Unmerged, Status.Unmerged); break;
|
||||
case "??": change.Set(Status.Untracked, Status.Untracked); break;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (change.Path[0] == '"') change.Path = change.Path.Substring(1, change.Path.Length - 2);
|
||||
if (!string.IsNullOrEmpty(change.OriginalPath) && change.OriginalPath[0] == '"') change.OriginalPath = change.OriginalPath.Substring(1, change.OriginalPath.Length - 2);
|
||||
return change;
|
||||
}
|
||||
|
||||
private void Set(Status index, Status workTree = Status.None) {
|
||||
Index = index;
|
||||
WorkTree = workTree;
|
||||
|
||||
if (index == Status.Renamed || workTree == Status.Renamed) {
|
||||
var idx = Path.IndexOf('\t');
|
||||
if (idx >= 0) {
|
||||
OriginalPath = Path.Substring(0, idx);
|
||||
Path = Path.Substring(idx + 1);
|
||||
} else {
|
||||
idx = Path.IndexOf(" -> ");
|
||||
if (idx > 0) {
|
||||
OriginalPath = Path.Substring(0, idx);
|
||||
Path = Path.Substring(idx + 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
296
src/Git/Commit.cs
Normal file
296
src/Git/Commit.cs
Normal file
|
@ -0,0 +1,296 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Git {
|
||||
|
||||
/// <summary>
|
||||
/// Git commit information.
|
||||
/// </summary>
|
||||
public class Commit {
|
||||
private static readonly string GPGSIG_START = "gpgsig -----BEGIN PGP SIGNATURE-----";
|
||||
private static readonly string GPGSIG_END = " -----END PGP SIGNATURE-----";
|
||||
|
||||
/// <summary>
|
||||
/// Object in commit.
|
||||
/// </summary>
|
||||
public class Object {
|
||||
public enum Type {
|
||||
Tag,
|
||||
Blob,
|
||||
Tree,
|
||||
Commit,
|
||||
}
|
||||
|
||||
public string Path { get; set; }
|
||||
public Type Kind { get; set; }
|
||||
public string SHA { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SHA
|
||||
/// </summary>
|
||||
public string SHA { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Short SHA.
|
||||
/// </summary>
|
||||
public string ShortSHA => SHA.Substring(0, 8);
|
||||
|
||||
/// <summary>
|
||||
/// Parent commit SHAs.
|
||||
/// </summary>
|
||||
public List<string> Parents { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Author
|
||||
/// </summary>
|
||||
public User Author { get; set; } = new User();
|
||||
|
||||
/// <summary>
|
||||
/// Committer.
|
||||
/// </summary>
|
||||
public User Committer { get; set; } = new User();
|
||||
|
||||
/// <summary>
|
||||
/// Subject
|
||||
/// </summary>
|
||||
public string Subject { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Extra message.
|
||||
/// </summary>
|
||||
public string Message { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// HEAD commit?
|
||||
/// </summary>
|
||||
public bool IsHEAD { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Merged in current branch?
|
||||
/// </summary>
|
||||
public bool IsMerged { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// X offset in graph
|
||||
/// </summary>
|
||||
public double GraphOffset { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Has decorators.
|
||||
/// </summary>
|
||||
public bool HasDecorators => Decorators.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Decorators.
|
||||
/// </summary>
|
||||
public List<Decorator> Decorators { get; set; } = new List<Decorator>();
|
||||
|
||||
/// <summary>
|
||||
/// Read commits.
|
||||
/// </summary>
|
||||
/// <param name="repo">Repository</param>
|
||||
/// <param name="limit">Limitations</param>
|
||||
/// <returns>Parsed commits.</returns>
|
||||
public static List<Commit> Load(Repository repo, string limit) {
|
||||
List<Commit> commits = new List<Commit>();
|
||||
Commit current = null;
|
||||
bool bSkippingGpgsig = false;
|
||||
bool findHead = false;
|
||||
|
||||
repo.RunCommand("log --date-order --decorate=full --pretty=raw " + limit, line => {
|
||||
if (bSkippingGpgsig) {
|
||||
if (line.StartsWith(GPGSIG_END, StringComparison.Ordinal)) bSkippingGpgsig = false;
|
||||
return;
|
||||
} else if (line.StartsWith(GPGSIG_START, StringComparison.Ordinal)) {
|
||||
bSkippingGpgsig = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.StartsWith("commit ", StringComparison.Ordinal)) {
|
||||
if (current != null) {
|
||||
current.Message = current.Message.TrimEnd();
|
||||
commits.Add(current);
|
||||
}
|
||||
|
||||
current = new Commit();
|
||||
ParseSHA(current, line.Substring("commit ".Length));
|
||||
if (!findHead) findHead = current.IsHEAD;
|
||||
return;
|
||||
}
|
||||
|
||||
if (current == null) return;
|
||||
|
||||
if (line.StartsWith("tree ", StringComparison.Ordinal)) {
|
||||
return;
|
||||
} else if (line.StartsWith("parent ", StringComparison.Ordinal)) {
|
||||
current.Parents.Add(line.Substring("parent ".Length));
|
||||
} else if (line.StartsWith("author ", StringComparison.Ordinal)) {
|
||||
current.Author.Parse(line);
|
||||
} else if (line.StartsWith("committer ", StringComparison.Ordinal)) {
|
||||
current.Committer.Parse(line);
|
||||
} else if (string.IsNullOrEmpty(current.Subject)) {
|
||||
current.Subject = line.Trim();
|
||||
} else {
|
||||
current.Message += (line.Trim() + "\n");
|
||||
}
|
||||
});
|
||||
|
||||
if (current != null) {
|
||||
current.Message = current.Message.TrimEnd();
|
||||
commits.Add(current);
|
||||
}
|
||||
|
||||
if (!findHead && commits.Count > 0) {
|
||||
var startInfo = new ProcessStartInfo();
|
||||
startInfo.FileName = Preference.Instance.GitExecutable;
|
||||
startInfo.Arguments = $"merge-base --is-ancestor {commits[0].SHA} HEAD";
|
||||
startInfo.WorkingDirectory = repo.Path;
|
||||
startInfo.UseShellExecute = false;
|
||||
startInfo.CreateNoWindow = true;
|
||||
startInfo.RedirectStandardOutput = false;
|
||||
startInfo.RedirectStandardError = false;
|
||||
|
||||
var proc = new Process() { StartInfo = startInfo };
|
||||
proc.Start();
|
||||
proc.WaitForExit();
|
||||
|
||||
commits[0].IsMerged = proc.ExitCode == 0;
|
||||
proc.Close();
|
||||
}
|
||||
|
||||
return commits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get changed file list.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <returns></returns>
|
||||
public List<Change> GetChanges(Repository repo) {
|
||||
var changes = new List<Change>();
|
||||
var regex = new Regex(@"^[MADRC]\d*\s*.*$");
|
||||
|
||||
var errs = repo.RunCommand($"show --name-status {SHA}", line => {
|
||||
if (!regex.IsMatch(line)) return;
|
||||
|
||||
var change = Change.Parse(line, true);
|
||||
if (change != null) changes.Add(change);
|
||||
});
|
||||
|
||||
if (errs != null) App.RaiseError(errs);
|
||||
return changes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get revision files.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <returns></returns>
|
||||
public List<Object> GetFiles(Repository repo) {
|
||||
var files = new List<Object>();
|
||||
var test = new Regex(@"^\d+\s+(\w+)\s+([0-9a-f]+)\s+(.*)$");
|
||||
|
||||
var errs = repo.RunCommand($"ls-tree -r {SHA}", line => {
|
||||
var match = test.Match(line);
|
||||
if (!match.Success) return;
|
||||
|
||||
var obj = new Object();
|
||||
obj.Path = match.Groups[3].Value;
|
||||
obj.Kind = Object.Type.Blob;
|
||||
obj.SHA = match.Groups[2].Value;
|
||||
|
||||
switch (match.Groups[1].Value) {
|
||||
case "tag": obj.Kind = Object.Type.Tag; break;
|
||||
case "blob": obj.Kind = Object.Type.Blob; break;
|
||||
case "tree": obj.Kind = Object.Type.Tree; break;
|
||||
case "commit": obj.Kind = Object.Type.Commit; break;
|
||||
}
|
||||
|
||||
files.Add(obj);
|
||||
});
|
||||
|
||||
if (errs != null) App.RaiseError(errs);
|
||||
return files;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get file content.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
public string GetTextFileContent(Repository repo, string file, out bool isBinary) {
|
||||
var data = new List<string>();
|
||||
var count = 0;
|
||||
var binary = false;
|
||||
|
||||
var errs = repo.RunCommand($"show {SHA}:\"{file}\"", line => {
|
||||
if (binary) return;
|
||||
|
||||
count++;
|
||||
if (data.Count >= 1000) return;
|
||||
|
||||
if (line.IndexOf('\0') >= 0) {
|
||||
binary = true;
|
||||
data.Clear();
|
||||
data.Add("BINARY FILE PREVIEW NOT SUPPORTED!");
|
||||
return;
|
||||
}
|
||||
|
||||
data.Add(line);
|
||||
});
|
||||
|
||||
if (!binary && count > 1000) {
|
||||
data.Add("...");
|
||||
data.Add($"Total {count} lines. Hide {count-1000} lines.");
|
||||
}
|
||||
|
||||
isBinary = binary;
|
||||
|
||||
if (errs != null) App.RaiseError(errs);
|
||||
return string.Join("\n", data);
|
||||
}
|
||||
|
||||
private static void ParseSHA(Commit commit, string data) {
|
||||
var decoratorStart = data.IndexOf('(');
|
||||
if (decoratorStart < 0) {
|
||||
commit.SHA = data.Trim();
|
||||
return;
|
||||
}
|
||||
|
||||
commit.SHA = data.Substring(0, decoratorStart).Trim();
|
||||
|
||||
var subs = data.Substring(decoratorStart + 1).Split(new char[] { ',', ')', '(' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var sub in subs) {
|
||||
var d = sub.Trim();
|
||||
if (d.StartsWith("tag: refs/tags/", StringComparison.Ordinal)) {
|
||||
commit.Decorators.Add(new Decorator() {
|
||||
Type = DecoratorType.Tag,
|
||||
Name = d.Substring(15).Trim()
|
||||
});
|
||||
} else if (d.EndsWith("/HEAD")) {
|
||||
continue;
|
||||
} else if (d.StartsWith("HEAD -> refs/heads/", StringComparison.Ordinal)) {
|
||||
commit.IsHEAD = true;
|
||||
commit.Decorators.Add(new Decorator() {
|
||||
Type = DecoratorType.CurrentBranchHead,
|
||||
Name = d.Substring(19).Trim()
|
||||
});
|
||||
} else if (d.StartsWith("refs/heads/", StringComparison.Ordinal)) {
|
||||
commit.Decorators.Add(new Decorator() {
|
||||
Type = DecoratorType.LocalBranchHead,
|
||||
Name = d.Substring(11).Trim()
|
||||
});
|
||||
} else if (d.StartsWith("refs/remotes/", StringComparison.Ordinal)) {
|
||||
commit.Decorators.Add(new Decorator() {
|
||||
Type = DecoratorType.RemoteBranchHead,
|
||||
Name = d.Substring(13).Trim()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
src/Git/Decorator.cs
Normal file
21
src/Git/Decorator.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
namespace SourceGit.Git {
|
||||
|
||||
/// <summary>
|
||||
/// Decorator type.
|
||||
/// </summary>
|
||||
public enum DecoratorType {
|
||||
None,
|
||||
CurrentBranchHead,
|
||||
LocalBranchHead,
|
||||
RemoteBranchHead,
|
||||
Tag,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Commit decorator.
|
||||
/// </summary>
|
||||
public class Decorator {
|
||||
public DecoratorType Type { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
263
src/Git/Diff.cs
Normal file
263
src/Git/Diff.cs
Normal file
|
@ -0,0 +1,263 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Git {
|
||||
|
||||
/// <summary>
|
||||
/// Diff helper.
|
||||
/// </summary>
|
||||
public class Diff {
|
||||
private static readonly Regex REG_INDICATOR = new Regex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@", RegexOptions.None);
|
||||
|
||||
/// <summary>
|
||||
/// Line mode.
|
||||
/// </summary>
|
||||
public enum LineMode {
|
||||
Normal,
|
||||
Indicator,
|
||||
Empty,
|
||||
Added,
|
||||
Deleted,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Side
|
||||
/// </summary>
|
||||
public enum Side {
|
||||
Left,
|
||||
Right,
|
||||
Both,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binary change.
|
||||
/// </summary>
|
||||
public class BinaryChange {
|
||||
public long Size = 0;
|
||||
public long PreSize = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Block
|
||||
/// </summary>
|
||||
public class Block {
|
||||
public Side Side = Side.Both;
|
||||
public LineMode Mode = LineMode.Normal;
|
||||
public int LeftStart = 0;
|
||||
public int RightStart = 0;
|
||||
public int Count = 0;
|
||||
public StringBuilder Builder = new StringBuilder();
|
||||
|
||||
public bool IsLeftDelete => Side == Side.Left && Mode == LineMode.Deleted;
|
||||
public bool IsRightAdded => Side == Side.Right && Mode == LineMode.Added;
|
||||
public bool IsBothSideNormal => Side == Side.Both && Mode == LineMode.Normal;
|
||||
public bool CanShowNumber => Mode != LineMode.Indicator && Mode != LineMode.Empty;
|
||||
|
||||
public void Append(string data) {
|
||||
if (Count > 0) Builder.AppendLine();
|
||||
Builder.Append(data);
|
||||
Count++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Diff result.
|
||||
/// </summary>
|
||||
public class Result {
|
||||
public bool IsValid = false;
|
||||
public bool IsBinary = false;
|
||||
public List<Block> Blocks = new List<Block>();
|
||||
public int LeftLineCount = 0;
|
||||
public int RightLineCount = 0;
|
||||
|
||||
public void SetBinary() {
|
||||
IsValid = true;
|
||||
IsBinary = true;
|
||||
}
|
||||
|
||||
public void Add(Block b) {
|
||||
if (b.Count == 0) return;
|
||||
|
||||
switch (b.Side) {
|
||||
case Side.Left:
|
||||
LeftLineCount += b.Count;
|
||||
break;
|
||||
case Side.Right:
|
||||
RightLineCount += b.Count;
|
||||
break;
|
||||
default:
|
||||
LeftLineCount += b.Count;
|
||||
RightLineCount += b.Count;
|
||||
break;
|
||||
}
|
||||
|
||||
Blocks.Add(b);
|
||||
}
|
||||
|
||||
public void Fit() {
|
||||
if (LeftLineCount > RightLineCount) {
|
||||
var b = new Block();
|
||||
b.Side = Side.Right;
|
||||
b.Mode = LineMode.Empty;
|
||||
|
||||
var delta = LeftLineCount - RightLineCount;
|
||||
for (int i = 0; i < delta; i++) b.Append("");
|
||||
|
||||
Add(b);
|
||||
} else if (LeftLineCount < RightLineCount) {
|
||||
var b = new Block();
|
||||
b.Side = Side.Left;
|
||||
b.Mode = LineMode.Empty;
|
||||
|
||||
var delta = RightLineCount - LeftLineCount;
|
||||
for (int i = 0; i < delta; i++) b.Append("");
|
||||
|
||||
Add(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run diff process.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <returns></returns>
|
||||
public static Result Run(Repository repo, string args) {
|
||||
var rs = new Result();
|
||||
var current = new Block();
|
||||
var left = 0;
|
||||
var right = 0;
|
||||
|
||||
repo.RunCommand($"diff --ignore-cr-at-eol {args}", line => {
|
||||
if (rs.IsBinary) return;
|
||||
|
||||
if (!rs.IsValid) {
|
||||
var match = REG_INDICATOR.Match(line);
|
||||
if (!match.Success) {
|
||||
if (line.StartsWith("Binary ")) rs.SetBinary();
|
||||
return;
|
||||
}
|
||||
|
||||
rs.IsValid = true;
|
||||
left = int.Parse(match.Groups[1].Value);
|
||||
right = int.Parse(match.Groups[2].Value);
|
||||
current.Mode = LineMode.Indicator;
|
||||
current.Append(line);
|
||||
} else {
|
||||
if (line[0] == '-') {
|
||||
if (current.IsLeftDelete) {
|
||||
current.Append(line.Substring(1));
|
||||
} else {
|
||||
rs.Add(current);
|
||||
|
||||
current = new Block();
|
||||
current.Side = Side.Left;
|
||||
current.Mode = LineMode.Deleted;
|
||||
current.LeftStart = left;
|
||||
current.Append(line.Substring(1));
|
||||
}
|
||||
|
||||
left++;
|
||||
} else if (line[0] == '+') {
|
||||
if (current.IsRightAdded) {
|
||||
current.Append(line.Substring(1));
|
||||
} else {
|
||||
rs.Add(current);
|
||||
|
||||
current = new Block();
|
||||
current.Side = Side.Right;
|
||||
current.Mode = LineMode.Added;
|
||||
current.RightStart = right;
|
||||
current.Append(line.Substring(1));
|
||||
}
|
||||
|
||||
right++;
|
||||
} else if (line[0] == '\\') {
|
||||
var tmp = new Block();
|
||||
tmp.Side = current.Side;
|
||||
tmp.Mode = LineMode.Indicator;
|
||||
tmp.Append(line.Substring(1));
|
||||
|
||||
rs.Add(current);
|
||||
rs.Add(tmp);
|
||||
rs.Fit();
|
||||
|
||||
current = new Block();
|
||||
current.LeftStart = left;
|
||||
current.RightStart = right;
|
||||
} else {
|
||||
var match = REG_INDICATOR.Match(line);
|
||||
if (match.Success) {
|
||||
rs.Add(current);
|
||||
rs.Fit();
|
||||
|
||||
left = int.Parse(match.Groups[1].Value);
|
||||
right = int.Parse(match.Groups[2].Value);
|
||||
|
||||
current = new Block();
|
||||
current.Mode = LineMode.Indicator;
|
||||
current.Append(line);
|
||||
} else {
|
||||
if (current.IsBothSideNormal) {
|
||||
current.Append(line.Substring(1));
|
||||
} else {
|
||||
rs.Add(current);
|
||||
rs.Fit();
|
||||
|
||||
current = new Block();
|
||||
current.LeftStart = left;
|
||||
current.RightStart = right;
|
||||
current.Append(line.Substring(1));
|
||||
}
|
||||
|
||||
left++;
|
||||
right++;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
rs.Add(current);
|
||||
rs.Fit();
|
||||
|
||||
if (rs.IsBinary) rs.Blocks.Clear();
|
||||
return rs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get file size changes for binary file.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="revisions"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="orgPath"></param>
|
||||
/// <returns></returns>
|
||||
public static BinaryChange GetSizeChange(Repository repo, string[] revisions, string path, string orgPath = null) {
|
||||
var change = new BinaryChange();
|
||||
|
||||
if (revisions.Length == 0) { // Compare working copy with HEAD
|
||||
change.Size = new FileInfo(Path.Combine(repo.Path, path)).Length;
|
||||
change.PreSize = repo.GetFileSize("HEAD", path);
|
||||
} else if (revisions.Length == 1) { // Compare HEAD with given revision.
|
||||
change.Size = repo.GetFileSize("HEAD", path);
|
||||
if (!string.IsNullOrEmpty(orgPath)) {
|
||||
change.PreSize = repo.GetFileSize(revisions[0], orgPath);
|
||||
} else {
|
||||
change.PreSize = repo.GetFileSize(revisions[0], path);
|
||||
}
|
||||
} else {
|
||||
change.Size = repo.GetFileSize(revisions[1], path);
|
||||
if (!string.IsNullOrEmpty(orgPath)) {
|
||||
change.PreSize = repo.GetFileSize(revisions[0], orgPath);
|
||||
} else {
|
||||
change.PreSize = repo.GetFileSize(revisions[0], path);
|
||||
}
|
||||
}
|
||||
|
||||
return change;
|
||||
}
|
||||
}
|
||||
}
|
202
src/Git/MergeTool.cs
Normal file
202
src/Git/MergeTool.cs
Normal file
|
@ -0,0 +1,202 @@
|
|||
using Microsoft.Win32;
|
||||
using SourceGit.UI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SourceGit.Git {
|
||||
|
||||
/// <summary>
|
||||
/// External merge tool
|
||||
/// </summary>
|
||||
public class MergeTool {
|
||||
|
||||
/// <summary>
|
||||
/// Display name
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Executable file name.
|
||||
/// </summary>
|
||||
public string ExecutableName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Command line parameter.
|
||||
/// </summary>
|
||||
public string Parameter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Auto finder.
|
||||
/// </summary>
|
||||
public Func<string> Finder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is this merge tool configured.
|
||||
/// </summary>
|
||||
public bool IsConfigured => !string.IsNullOrEmpty(ExecutableName);
|
||||
|
||||
/// <summary>
|
||||
/// Supported merge tools.
|
||||
/// </summary>
|
||||
public static List<MergeTool> Supported = new List<MergeTool>() {
|
||||
new MergeTool("--", "", "", FindInvalid),
|
||||
new MergeTool("Araxis Merge", "Compare.exe", "/wait /merge /3 /a1 \"$BASE\" \"$REMOTE\" \"$LOCAL\" \"$MERGED\"", FindAraxisMerge),
|
||||
new MergeTool("Beyond Compare 4", "BComp.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", FindBCompare),
|
||||
new MergeTool("KDiff3", "kdiff3.exe", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", FindKDiff3),
|
||||
new MergeTool("P4Merge", "p4merge.exe", "\"$BASE\" \"$REMOTE\" \"$LOCAL\" \"$MERGED\"", FindP4Merge),
|
||||
new MergeTool("Tortoise Merge", "TortoiseMerge.exe", "-base:\"$BASE\" -theirs:\"$REMOTE\" -mine:\"$LOCAL\" -merged:\"$MERGED\"", FindTortoiseMerge),
|
||||
new MergeTool("Visual Studio 2017/2019", "vsDiffMerge.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\" //m", FindVSMerge),
|
||||
new MergeTool("Visual Studio Code", "Code.exe", "-n --wait \"$MERGED\"", FindVSCode),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Finder for invalid merge tool.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string FindInvalid() {
|
||||
return "--";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find araxis merge tool install path.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string FindAraxisMerge() {
|
||||
var path = @"C:\Program Files\Araxis\Araxis Merge\Compare.exe";
|
||||
if (File.Exists(path)) return path;
|
||||
return "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find kdiff3.exe by registry.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string FindKDiff3() {
|
||||
var root = RegistryKey.OpenBaseKey(
|
||||
RegistryHive.LocalMachine,
|
||||
Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32);
|
||||
|
||||
var kdiff = root.OpenSubKey(@"SOFTWARE\KDiff3\diff-ext");
|
||||
if (kdiff == null) return "";
|
||||
return kdiff.GetValue("diffcommand") as string;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finder for p4merge
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string FindP4Merge() {
|
||||
var path = @"C:\Program Files\Perforce\p4merge.exe";
|
||||
if (File.Exists(path)) return path;
|
||||
return "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find BComp.exe by registry.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string FindBCompare() {
|
||||
var root = RegistryKey.OpenBaseKey(
|
||||
RegistryHive.LocalMachine,
|
||||
Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32);
|
||||
|
||||
var bc = root.OpenSubKey(@"SOFTWARE\Scooter Software\Beyond Compare");
|
||||
if (bc == null) return "";
|
||||
|
||||
var exec = bc.GetValue("ExePath") as string;
|
||||
var dir = Path.GetDirectoryName(exec);
|
||||
return $"{dir}\\BComp.exe";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find TortoiseMerge.exe by registry.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string FindTortoiseMerge() {
|
||||
var root = RegistryKey.OpenBaseKey(
|
||||
RegistryHive.LocalMachine,
|
||||
Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32);
|
||||
|
||||
var tortoiseSVN = root.OpenSubKey("SOFTWARE\\TortoiseSVN");
|
||||
if (tortoiseSVN == null) return "";
|
||||
return tortoiseSVN.GetValue("TMergePath") as string;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find vsDiffMerge.exe.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string FindVSMerge() {
|
||||
var dir = @"C:\Program Files (x86)\Microsoft Visual Studio";
|
||||
if (Directory.Exists($"{dir}\\2019")) {
|
||||
dir += "\\2019";
|
||||
} else if (Directory.Exists($"{dir}\\2017")) {
|
||||
dir += "\\2017";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (Directory.Exists($"{dir}\\Community")) {
|
||||
dir += "\\Community";
|
||||
} else if (Directory.Exists($"{dir}\\Enterprise")) {
|
||||
dir += "\\Enterprise";
|
||||
} else if (Directory.Exists($"{dir}\\Professional")) {
|
||||
dir += "\\Professional";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
||||
return $"{dir}\\Common7\\IDE\\CommonExtensions\\Microsoft\\TeamFoundation\\Team Explorer\\vsDiffMerge.exe";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find VSCode executable file path.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string FindVSCode() {
|
||||
var root = RegistryKey.OpenBaseKey(
|
||||
RegistryHive.LocalMachine,
|
||||
Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32);
|
||||
|
||||
var vscode = root.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{C26E74D1-022E-4238-8B9D-1E7564A36CC9}_is1");
|
||||
if (vscode != null) {
|
||||
return vscode.GetValue("DisplayIcon") as string;
|
||||
}
|
||||
|
||||
vscode = root.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{1287CAD5-7C8D-410D-88B9-0D1EE4A83FF2}_is1");
|
||||
if (vscode != null) {
|
||||
return vscode.GetValue("DisplayIcon") as string;
|
||||
}
|
||||
|
||||
vscode = root.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{F8A2A208-72B3-4D61-95FC-8A65D340689B}_is1");
|
||||
if (vscode != null) {
|
||||
return vscode.GetValue("DisplayIcon") as string;
|
||||
}
|
||||
|
||||
vscode = root.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{EA457B21-F73E-494C-ACAB-524FDE069978}_is1");
|
||||
if (vscode != null) {
|
||||
return vscode.GetValue("DisplayIcon") as string;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="exe"></param>
|
||||
/// <param name="param"></param>
|
||||
/// <param name="finder"></param>
|
||||
public MergeTool(string name, string exe, string param, Func<string> finder) {
|
||||
Name = name;
|
||||
ExecutableName = exe;
|
||||
Parameter = param;
|
||||
Finder = finder;
|
||||
}
|
||||
}
|
||||
}
|
300
src/Git/Preference.cs
Normal file
300
src/Git/Preference.cs
Normal file
|
@ -0,0 +1,300 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace SourceGit.Git {
|
||||
|
||||
/// <summary>
|
||||
/// User's preference settings. Serialized to
|
||||
/// </summary>
|
||||
public class Preference {
|
||||
|
||||
/// <summary>
|
||||
/// Group(Virtual folder) for watched repositories.
|
||||
/// </summary>
|
||||
public class Group {
|
||||
/// <summary>
|
||||
/// Unique ID of this group.
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
/// <summary>
|
||||
/// Display name.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Parent ID.
|
||||
/// </summary>
|
||||
public string ParentId { get; set; }
|
||||
/// <summary>
|
||||
/// Cache UI IsExpended status.
|
||||
/// </summary>
|
||||
public bool IsExpended { get; set; }
|
||||
}
|
||||
|
||||
#region STATICS
|
||||
/// <summary>
|
||||
/// Storage path for Preference.
|
||||
/// </summary>
|
||||
private static readonly string SAVE_PATH = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"SourceGit",
|
||||
"preference.xml");
|
||||
/// <summary>
|
||||
/// Runtime singleton instance.
|
||||
/// </summary>
|
||||
private static Preference instance = null;
|
||||
public static Preference Instance {
|
||||
get {
|
||||
if (instance == null) Load();
|
||||
return instance;
|
||||
}
|
||||
set {
|
||||
instance = value;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SETTING_GIT
|
||||
/// <summary>
|
||||
/// Git executable file path.
|
||||
/// </summary>
|
||||
public string GitExecutable { get; set; }
|
||||
/// <summary>
|
||||
/// Default clone directory.
|
||||
/// </summary>
|
||||
public string GitDefaultCloneDir { get; set; }
|
||||
#endregion
|
||||
|
||||
#region SETTING_MERGE_TOOL
|
||||
/// <summary>
|
||||
/// Selected merge tool.
|
||||
/// </summary>
|
||||
public int MergeTool { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// Executable file path for merge tool.
|
||||
/// </summary>
|
||||
public string MergeExecutable { get; set; } = "--";
|
||||
#endregion
|
||||
|
||||
#region SETTING_UI
|
||||
/// <summary>
|
||||
/// Main window's width
|
||||
/// </summary>
|
||||
public double UIMainWindowWidth { get; set; }
|
||||
/// <summary>
|
||||
/// Main window's height
|
||||
/// </summary>
|
||||
public double UIMainWindowHeight { get; set; }
|
||||
/// <summary>
|
||||
/// Use light color theme.
|
||||
/// </summary>
|
||||
public bool UIUseLightTheme { get; set; }
|
||||
/// <summary>
|
||||
/// Show/Hide tags' list view.
|
||||
/// </summary>
|
||||
public bool UIShowTags { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Use horizontal layout for histories.
|
||||
/// </summary>
|
||||
public bool UIUseHorizontalLayout { get; set; }
|
||||
/// <summary>
|
||||
/// Use list instead of tree in unstaged view
|
||||
/// </summary>
|
||||
public bool UIUseListInUnstaged { get; set; }
|
||||
/// <summary>
|
||||
/// Use list instead of tree in staged view.
|
||||
/// </summary>
|
||||
public bool UIUseListInStaged { get; set; }
|
||||
/// <summary>
|
||||
/// Use list instead of tree in change view.
|
||||
/// </summary>
|
||||
public bool UIUseListInChanges { get; set; }
|
||||
#endregion
|
||||
|
||||
#region SETTING_REPOS
|
||||
/// <summary>
|
||||
/// Groups for repositories.
|
||||
/// </summary>
|
||||
public List<Group> Groups { get; set; } = new List<Group>();
|
||||
/// <summary>
|
||||
/// Watched repositories.
|
||||
/// </summary>
|
||||
public List<Repository> Repositories { get; set; } = new List<Git.Repository>();
|
||||
#endregion
|
||||
|
||||
#region METHODS_LOAD_SAVE
|
||||
/// <summary>
|
||||
/// Load preference from disk.
|
||||
/// </summary>
|
||||
/// <returns>Loaded preference instance.</returns>
|
||||
public static void Load() {
|
||||
if (!File.Exists(SAVE_PATH)) {
|
||||
instance = new Preference();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var stream = new FileStream(SAVE_PATH, FileMode.Open);
|
||||
var reader = new XmlSerializer(typeof(Preference));
|
||||
instance = (Preference)reader.Deserialize(stream);
|
||||
stream.Close();
|
||||
} catch {
|
||||
instance = new Preference();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save current preference into disk.
|
||||
/// </summary>
|
||||
public static void Save() {
|
||||
if (instance == null) return;
|
||||
|
||||
var dir = Path.GetDirectoryName(SAVE_PATH);
|
||||
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
|
||||
|
||||
var stream = new FileStream(SAVE_PATH, FileMode.Create);
|
||||
var writer = new XmlSerializer(typeof(Preference));
|
||||
writer.Serialize(stream, instance);
|
||||
stream.Flush();
|
||||
stream.Close();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region METHODS_ON_GROUP
|
||||
/// <summary>
|
||||
/// Add new group(virtual folder).
|
||||
/// </summary>
|
||||
/// <param name="name">Display name.</param>
|
||||
/// <param name="parentId">Parent group ID.</param>
|
||||
/// <returns>Added group instance.</returns>
|
||||
public Group AddGroup(string name, string parentId) {
|
||||
var group = new Group() {
|
||||
Name = name,
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
ParentId = parentId,
|
||||
IsExpended = false,
|
||||
};
|
||||
|
||||
Groups.Add(group);
|
||||
Groups.Sort((l, r) => l.Name.CompareTo(r.Name));
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find group by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">Unique ID</param>
|
||||
/// <returns>Founded group's instance.</returns>
|
||||
public Group FindGroup(string id) {
|
||||
foreach (var group in Groups) {
|
||||
if (group.Id == id) return group;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rename group.
|
||||
/// </summary>
|
||||
/// <param name="id">Unique ID</param>
|
||||
/// <param name="newName">New name.</param>
|
||||
public void RenameGroup(string id, string newName) {
|
||||
foreach (var group in Groups) {
|
||||
if (group.Id == id) {
|
||||
group.Name = newName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Groups.Sort((l, r) => l.Name.CompareTo(r.Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a group.
|
||||
/// </summary>
|
||||
/// <param name="id">Unique ID</param>
|
||||
public void RemoveGroup(string id) {
|
||||
int removedIdx = -1;
|
||||
|
||||
for (int i = 0; i < Groups.Count; i++) {
|
||||
if (Groups[i].Id == id) {
|
||||
removedIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (removedIdx >= 0) Groups.RemoveAt(removedIdx);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region METHODS_ON_REPOS
|
||||
/// <summary>
|
||||
/// Add repository.
|
||||
/// </summary>
|
||||
/// <param name="path">Local storage path.</param>
|
||||
/// <param name="groupId">Group's ID</param>
|
||||
/// <returns>Added repository instance.</returns>
|
||||
public Repository AddRepository(string path, string groupId) {
|
||||
var repo = FindRepository(path);
|
||||
if (repo != null) return repo;
|
||||
|
||||
var dir = new DirectoryInfo(path);
|
||||
repo = new Repository() {
|
||||
Path = dir.FullName,
|
||||
Name = dir.Name,
|
||||
GroupId = groupId,
|
||||
LastOpenTime = 0,
|
||||
};
|
||||
|
||||
Repositories.Add(repo);
|
||||
Repositories.Sort((l, r) => l.Name.CompareTo(r.Name));
|
||||
return repo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find repository by path.
|
||||
/// </summary>
|
||||
/// <param name="path">Local storage path.</param>
|
||||
/// <returns>Founded repository instance.</returns>
|
||||
public Repository FindRepository(string path) {
|
||||
var dir = new DirectoryInfo(path);
|
||||
foreach (var repo in Repositories) {
|
||||
if (repo.Path == dir.FullName) return repo;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change a repository's display name in RepositoryManager.
|
||||
/// </summary>
|
||||
/// <param name="path">Local storage path.</param>
|
||||
/// <param name="newName">New name</param>
|
||||
public void RenameRepository(string path, string newName) {
|
||||
var repo = FindRepository(path);
|
||||
if (repo == null) return;
|
||||
|
||||
repo.Name = newName;
|
||||
Repositories.Sort((l, r) => l.Name.CompareTo(r.Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a repository in RepositoryManager.
|
||||
/// </summary>
|
||||
/// <param name="path">Local storage path.</param>
|
||||
public void RemoveRepository(string path) {
|
||||
var dir = new DirectoryInfo(path);
|
||||
var removedIdx = -1;
|
||||
|
||||
for (int i = 0; i < Repositories.Count; i++) {
|
||||
if (Repositories[i].Path == dir.FullName) {
|
||||
removedIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (removedIdx >= 0) Repositories.RemoveAt(removedIdx);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
97
src/Git/Remote.cs
Normal file
97
src/Git/Remote.cs
Normal file
|
@ -0,0 +1,97 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Git {
|
||||
|
||||
/// <summary>
|
||||
/// Git remote
|
||||
/// </summary>
|
||||
public class Remote {
|
||||
private static readonly Regex FORMAT = new Regex(@"^([\w\.\-]+)\s*(\S+).*$");
|
||||
|
||||
/// <summary>
|
||||
/// Name of this remote
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// URL
|
||||
/// </summary>
|
||||
public string URL { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parsing remote
|
||||
/// </summary>
|
||||
/// <param name="repo">Repository</param>
|
||||
/// <returns></returns>
|
||||
public static List<Remote> Load(Repository repo) {
|
||||
var remotes = new List<Remote>();
|
||||
var added = new List<string>();
|
||||
|
||||
repo.RunCommand("remote -v", data => {
|
||||
var match = FORMAT.Match(data);
|
||||
if (!match.Success) return;
|
||||
|
||||
var remote = new Remote() {
|
||||
Name = match.Groups[1].Value,
|
||||
URL = match.Groups[2].Value,
|
||||
};
|
||||
|
||||
if (added.Contains(remote.Name)) return;
|
||||
|
||||
added.Add(remote.Name);
|
||||
remotes.Add(remote);
|
||||
});
|
||||
|
||||
return remotes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add new remote
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="url"></param>
|
||||
public static void Add(Repository repo, string name, string url) {
|
||||
var errs = repo.RunCommand($"remote add {name} {url}", null);
|
||||
if (errs != null) {
|
||||
App.RaiseError(errs);
|
||||
} else {
|
||||
repo.Fetch(new Remote() { Name = name }, true, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete remote.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="remote"></param>
|
||||
public static void Delete(Repository repo, string remote) {
|
||||
var errs = repo.RunCommand($"remote remove {remote}", null);
|
||||
if (errs != null) App.RaiseError(errs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Edit remote.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="url"></param>
|
||||
public void Edit(Repository repo, string name, string url) {
|
||||
string errs = null;
|
||||
|
||||
if (name != Name) {
|
||||
errs = repo.RunCommand($"remote rename {Name} {name}", null);
|
||||
if (errs != null) {
|
||||
App.RaiseError(errs);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (url != URL) {
|
||||
errs = repo.RunCommand($"remote set-url {name} {url}", null);
|
||||
if (errs != null) App.RaiseError(errs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1163
src/Git/Repository.cs
Normal file
1163
src/Git/Repository.cs
Normal file
File diff suppressed because it is too large
Load diff
101
src/Git/Stash.cs
Normal file
101
src/Git/Stash.cs
Normal file
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SourceGit.Git {
|
||||
|
||||
/// <summary>
|
||||
/// Git stash
|
||||
/// </summary>
|
||||
public class Stash {
|
||||
|
||||
/// <summary>
|
||||
/// SHA for this stash
|
||||
/// </summary>
|
||||
public string SHA { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Author
|
||||
/// </summary>
|
||||
public User Author { get; set; } = new User();
|
||||
|
||||
/// <summary>
|
||||
/// Message
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stash push.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="includeUntracked"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="files"></param>
|
||||
public static void Push(Repository repo, bool includeUntracked, string message, List<string> files) {
|
||||
string specialFiles = "";
|
||||
|
||||
if (files.Count > 0) {
|
||||
specialFiles = " --";
|
||||
foreach (var f in files) specialFiles += $" \"{f}\"";
|
||||
}
|
||||
|
||||
string args = "stash push ";
|
||||
if (includeUntracked) args += "-u ";
|
||||
if (!string.IsNullOrEmpty(message)) args += $"-m \"{message}\" ";
|
||||
|
||||
var errs = repo.RunCommand(args + specialFiles, null);
|
||||
if (errs != null) App.RaiseError(errs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get changed file list in this stash.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <returns></returns>
|
||||
public List<Change> GetChanges(Repository repo) {
|
||||
List<Change> changes = new List<Change>();
|
||||
|
||||
var errs = repo.RunCommand($"diff --name-status --pretty=format: {SHA}^ {SHA}", line => {
|
||||
var change = Change.Parse(line);
|
||||
if (change != null) changes.Add(change);
|
||||
});
|
||||
|
||||
if (errs != null) App.RaiseError(errs);
|
||||
return changes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply stash.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
public void Apply(Repository repo) {
|
||||
var errs = repo.RunCommand($"stash apply -q {Name}", null);
|
||||
if (errs != null) App.RaiseError(errs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pop stash
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
public void Pop(Repository repo) {
|
||||
var errs = repo.RunCommand($"stash pop -q {Name}", null);
|
||||
if (errs != null) App.RaiseError(errs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drop stash
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
public void Drop(Repository repo) {
|
||||
var errs = repo.RunCommand($"stash drop -q {Name}", null);
|
||||
if (errs != null) App.RaiseError(errs);
|
||||
}
|
||||
}
|
||||
}
|
118
src/Git/Tag.cs
Normal file
118
src/Git/Tag.cs
Normal file
|
@ -0,0 +1,118 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Git {
|
||||
|
||||
/// <summary>
|
||||
/// Git tag.
|
||||
/// </summary>
|
||||
public class Tag {
|
||||
private static readonly Regex FORMAT = new Regex(@"\$(.*)\$(.*)\$(.*)");
|
||||
|
||||
/// <summary>
|
||||
/// SHA
|
||||
/// </summary>
|
||||
public string SHA { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Display name.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable filter in log histories.
|
||||
/// </summary>
|
||||
public bool IsFiltered { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Load all tags
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <returns></returns>
|
||||
public static List<Tag> Load(Repository repo) {
|
||||
var args = "for-each-ref --sort=-creatordate --format=\"$%(refname:short)$%(objectname)$%(*objectname)\" refs/tags";
|
||||
var tags = new List<Tag>();
|
||||
|
||||
repo.RunCommand(args, line => {
|
||||
var match = FORMAT.Match(line);
|
||||
if (!match.Success) return;
|
||||
|
||||
var name = match.Groups[1].Value;
|
||||
var commit = match.Groups[2].Value;
|
||||
var dereference = match.Groups[3].Value;
|
||||
|
||||
if (string.IsNullOrEmpty(dereference)) {
|
||||
tags.Add(new Tag() {
|
||||
Name = name,
|
||||
SHA = commit,
|
||||
});
|
||||
} else {
|
||||
tags.Add(new Tag() {
|
||||
Name = name,
|
||||
SHA = dereference,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add new tag.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="startPoint"></param>
|
||||
/// <param name="message"></param>
|
||||
public static void Add(Repository repo, string name, string startPoint, string message) {
|
||||
var args = $"tag -a {name} {startPoint} ";
|
||||
|
||||
if (!string.IsNullOrEmpty(message)) {
|
||||
string temp = Path.GetTempFileName();
|
||||
File.WriteAllText(temp, message);
|
||||
args += $"-F \"{temp}\"";
|
||||
} else {
|
||||
args += $"-m {name}";
|
||||
}
|
||||
|
||||
var errs = repo.RunCommand(args, null);
|
||||
if (errs != null) App.RaiseError(errs);
|
||||
else repo.OnCommitsChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete tag.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="push"></param>
|
||||
public static void Delete(Repository repo, string name, bool push) {
|
||||
var errs = repo.RunCommand($"tag --delete {name}", null);
|
||||
if (errs != null) {
|
||||
App.RaiseError(errs);
|
||||
return;
|
||||
}
|
||||
|
||||
if (push) {
|
||||
var remotes = repo.Remotes();
|
||||
foreach (var r in remotes) {
|
||||
repo.RunCommand($"-c credential.helper=manager push --delete {r.Name} refs/tags/{name}", null);
|
||||
}
|
||||
}
|
||||
|
||||
repo.OnCommitsChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Push tag to remote.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="remote"></param>
|
||||
public static void Push(Repository repo, string name, string remote) {
|
||||
var errs = repo.RunCommand($"-c credential.helper=manager push {remote} refs/tags/{name}", null);
|
||||
if (errs != null) App.RaiseError(errs);
|
||||
}
|
||||
}
|
||||
}
|
42
src/Git/User.cs
Normal file
42
src/Git/User.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Git {
|
||||
|
||||
/// <summary>
|
||||
/// Git user.
|
||||
/// </summary>
|
||||
public class User {
|
||||
private static readonly Regex FORMAT = new Regex(@"\w+ (.*) <([\w\.\-_]+@[\w\.\-_]+)> (\d{10}) [\+\-]\d+");
|
||||
|
||||
/// <summary>
|
||||
/// Name.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Email.
|
||||
/// </summary>
|
||||
public string Email { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Operation time.
|
||||
/// </summary>
|
||||
public string Time { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Parse user from raw string.
|
||||
/// </summary>
|
||||
/// <param name="data">Raw string</param>
|
||||
public void Parse(string data) {
|
||||
var match = FORMAT.Match(data);
|
||||
if (!match.Success) return;
|
||||
|
||||
var time = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(int.Parse(match.Groups[3].Value));
|
||||
|
||||
Name = match.Groups[1].Value;
|
||||
Email = match.Groups[2].Value;
|
||||
Time = time.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
}
|
||||
}
|
275
src/Helpers/CommitGraph.cs
Normal file
275
src/Helpers/CommitGraph.cs
Normal file
|
@ -0,0 +1,275 @@
|
|||
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";
|
||||
ended.Add(l);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
224
src/Helpers/TextBoxHelper.cs
Normal file
224
src/Helpers/TextBoxHelper.cs
Normal file
|
@ -0,0 +1,224 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Helpers {
|
||||
|
||||
/// <summary>
|
||||
/// Attached properties to TextBox.
|
||||
/// </summary>
|
||||
public static class TextBoxHelper {
|
||||
|
||||
/// <summary>
|
||||
/// Auto scroll on text changed or selection changed.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty AutoScrollProperty = DependencyProperty.RegisterAttached(
|
||||
"AutoScroll",
|
||||
typeof(bool),
|
||||
typeof(TextBoxHelper),
|
||||
new PropertyMetadata(false, OnAutoScrollChanged));
|
||||
|
||||
/// <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>
|
||||
/// Setter for AutoScrollProperty
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <param name="enabled"></param>
|
||||
public static void SetAutoScroll(UIElement element, bool enabled) {
|
||||
element.SetValue(AutoScrollProperty, enabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Getter for AutoScrollProperty
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <returns></returns>
|
||||
public static bool GetAutoScroll(UIElement element) {
|
||||
return (bool)element.GetValue(AutoScrollProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when AutoScroll property changed.
|
||||
/// </summary>
|
||||
/// <param name="d"></param>
|
||||
/// <param name="e"></param>
|
||||
public static void OnAutoScrollChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var textBox = d as TextBox;
|
||||
if (textBox == null) return;
|
||||
|
||||
textBox.SelectionChanged -= UpdateScrollOnSelectionChanged;
|
||||
if ((bool)e.NewValue == true) {
|
||||
textBox.SelectionChanged += UpdateScrollOnSelectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 += UpdatePlaceholder;
|
||||
UpdatePlaceholder(textBox, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dynamically hide/show placeholder.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void UpdatePlaceholder(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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void UpdateScrollOnSelectionChanged(object sender, RoutedEventArgs e) {
|
||||
var textBox = sender as TextBox;
|
||||
if (textBox != null && textBox.IsFocused) {
|
||||
if (Mouse.LeftButton == MouseButtonState.Pressed && textBox.SelectionLength > 0) {
|
||||
var p = Mouse.GetPosition(textBox);
|
||||
if (p.X <= 8) {
|
||||
textBox.LineLeft();
|
||||
} else if (p.X >= textBox.ActualWidth - 8) {
|
||||
textBox.LineRight();
|
||||
}
|
||||
|
||||
if (p.Y <= 8) {
|
||||
textBox.LineUp();
|
||||
} else if (p.Y >= textBox.ActualHeight - 8) {
|
||||
textBox.LineDown();
|
||||
}
|
||||
} else {
|
||||
var rect = textBox.GetRectFromCharacterIndex(textBox.CaretIndex);
|
||||
if (rect.Left <= 0) {
|
||||
textBox.ScrollToHorizontalOffset(textBox.HorizontalOffset + rect.Left);
|
||||
} else if (rect.Right >= textBox.ActualWidth) {
|
||||
textBox.ScrollToHorizontalOffset(textBox.HorizontalOffset + rect.Right);
|
||||
}
|
||||
|
||||
if (rect.Top <= 0) {
|
||||
textBox.ScrollToVerticalOffset(textBox.VerticalOffset + rect.Top);
|
||||
} else if (rect.Bottom >= textBox.ActualHeight) {
|
||||
textBox.ScrollToVerticalOffset(textBox.VerticalOffset + rect.Bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
329
src/Helpers/TreeViewHelper.cs
Normal file
329
src/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;
|
||||
}
|
||||
}
|
||||
}
|
136
src/Helpers/Validations.cs
Normal file
136
src/Helpers/Validations.cs
Normal file
|
@ -0,0 +1,136 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Required for submodule path.
|
||||
/// </summary>
|
||||
public class SubmodulePathRequiredRule : ValidationRule {
|
||||
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
|
||||
var regex = new Regex(@"^[\w\-\._/]+$");
|
||||
var path = value as string;
|
||||
var succ = !string.IsNullOrEmpty(path) && regex.IsMatch(path.Trim());
|
||||
return !succ ? new ValidationResult(false, "Invalid path for submodules") : ValidationResult.ValidResult;
|
||||
}
|
||||
}
|
||||
}
|
22
src/Resources/Controls.xaml
Normal file
22
src/Resources/Controls.xaml
Normal file
|
@ -0,0 +1,22 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/Border.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/Button.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/CheckBox.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/ComboBox.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/ContextMenu.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/DataGrid.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/HyperLink.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/Label.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/ListView.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/Path.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/RadioButton.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/ScrollBar.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/ScrollViewer.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/TabControl.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/TextBox.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/ToggleButton.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/Tooltip.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/Resources/Styles/TreeView.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
61
src/Resources/Icons.xaml
Normal file
61
src/Resources/Icons.xaml
Normal file
|
@ -0,0 +1,61 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Geometry x:Key="Icon.Git">M1004.824 466.4L557.72 19.328c-25.728-25.76-67.488-25.76-93.28 0L360.568 123.2l78.176 78.176c12.544-5.984 26.56-9.376 41.376-9.376 53.024 0 96 42.976 96 96 0 14.816-3.36 28.864-9.376 41.376l127.968 127.968c12.544-5.984 26.56-9.376 41.376-9.376 53.024 0 96 42.976 96 96s-42.976 96-96 96-96-42.976-96-96c0-14.816 3.36-28.864 9.376-41.376L521.496 374.624a88.837 88.837 0 0 1-9.376 3.872v266.976c37.28 13.184 64 48.704 64 90.528 0 53.024-42.976 96-96 96s-96-42.976-96-96c0-41.792 26.72-77.344 64-90.528V378.496c-37.28-13.184-64-48.704-64-90.528 0-14.816 3.36-28.864 9.376-41.376l-78.176-78.176L19.416 464.288c-25.76 25.792-25.76 67.52 0 93.28l447.136 447.072c25.728 25.76 67.488 25.76 93.28 0l444.992-444.992c25.76-25.76 25.76-67.552 0-93.28z</Geometry>
|
||||
<Geometry x:Key="Icon.Submodule">M557.696 545.347L789.873 402.66c23.998-14.999 31.297-46.496 16.398-70.493-14.798-23.798-45.995-31.197-69.993-16.699L506.501 456.555 277.123 315.37c-24.098-14.798-55.595-7.3-70.493 16.799-14.799 24.097-7.3 55.594 16.798 70.493l231.778 142.586V819.12c0 28.297 22.897 51.195 51.195 51.195 28.297 0 51.195-22.898 51.195-51.195V545.347h0.1zM506.5 0l443.356 255.975v511.95L506.501 1023.9 63.144 767.925v-511.95L506.5 0z</Geometry>
|
||||
|
||||
<Geometry x:Key="Icon.ScrollLeft">M753.613 996.727L269.38 511.505 754.602 27.272z</Geometry>
|
||||
<Geometry x:Key="Icon.ScrollRight">M270.387 27.273L754.62 512.495 269.398 996.728z</Geometry>
|
||||
|
||||
<Geometry x:Key="Icon.Minimize">F1M0,6L0,9 9,9 9,6 0,6z</Geometry>
|
||||
<Geometry x:Key="Icon.Maximize">F1M0,0L0,9 9,9 9,0 0,0 0,3 8,3 8,8 1,8 1,3z</Geometry>
|
||||
<Geometry x:Key="Icon.Restore">F1M0,10L0,3 3,3 3,0 10,0 10,2 4,2 4,3 7,3 7,6 6,6 6,5 1,5 1,10z M1,10L7,10 7,7 10,7 10,2 9,2 9,6 6,6 6,9 1,9z</Geometry>
|
||||
<Geometry x:Key="Icon.Close">M810.666667 273.493333L750.506667 213.333333 512 451.84 273.493333 213.333333 213.333333 273.493333 451.84 512 213.333333 750.506667 273.493333 810.666667 512 572.16 750.506667 810.666667 810.666667 750.506667 572.16 512z</Geometry>
|
||||
<Geometry x:Key="Icon.Check">M512 597.33333332m-1.26648097 0a1.26648097 1.26648097 0 1 0 2.53296194 0 1.26648097 1.26648097 0 1 0-2.53296194 0ZM809.691429 392.777143L732.16 314.514286 447.634286 599.771429 292.571429 443.977143 214.308571 521.508571l155.794286 155.794286 77.531429 77.531429 362.057143-362.057143z</Geometry>
|
||||
<Geometry x:Key="Icon.Loading">M511.680999 0C233.071131 0 6.524722 222.580887 0.12872 499.655715 6.013042 257.886821 189.834154 63.960025 415.740962 63.960025c229.61649 0 415.740162 200.450718 415.740162 447.720175 0 52.958901 42.981137 95.940037 95.940038 95.940037s95.940037-42.981137 95.940037-95.940037c0-282.57539-229.104809-511.6802-511.6802-511.6802z m0 1023.3604c278.609869 0 505.156277-222.580887 511.55228-499.655715-5.884322 241.768894-189.705434 435.69569-415.612242 435.69569-229.61649 0-415.740162-200.450718-415.740163-447.720175 0-52.958901-42.981137-95.940037-95.940037-95.940038s-95.940037 42.981137-95.940037 95.940038c0 282.57539 229.104809 511.6802 511.680199 511.6802z</Geometry>
|
||||
<Geometry x:Key="Icon.Search">M701.9062029 677.41589899L589.90712068 565.41681675a148.33953321 148.33953321 0 1 0-24.97646381 26.55648342L676.07895931 703.12160261z m-346.38891409-199.50786053a114.97681148 114.97681148 0 1 1 114.85527151 114.97681148A115.09835147 115.09835147 0 0 1 355.45651882 477.90803846z</Geometry>
|
||||
<Geometry x:Key="Icon.Conflict">M352 64h320L960 352v320L672 960h-320L64 672v-320L352 64z m161.28 362.688L344.128 256 259.584 341.312 428.736 512l-169.152 170.688L344.128 768 513.28 597.312 682.432 768l84.544-85.312L597.824 512l169.152-170.688L682.432 256 513.28 426.688z</Geometry>
|
||||
|
||||
<Geometry x:Key="Icon.List">M51.2 204.8h102.4v102.4H51.2V204.8z m204.8 0h716.8v102.4H256V204.8zM51.2 460.8h102.4v102.4H51.2V460.8z m204.8 0h716.8v102.4H256V460.8z m-204.8 256h102.4v102.4H51.2v-102.4z m204.8 0h716.8v102.4H256v-102.4z</Geometry>
|
||||
<Geometry x:Key="Icon.Tree">M912 737l0 150L362 887l0-100 0-50 0-150 0-150 0-150L112 287l0-150 450 0 0 150L412 287l0 150L912 437l0 150L412 587l0 150L912 737z</Geometry>
|
||||
|
||||
<Geometry x:Key="Icon.MoveUp">M868 545.5L536.1 163c-12.7-14.7-35.5-14.7-48.3 0L156 545.5c-4.5 5.2-0.8 13.2 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z</Geometry>
|
||||
<Geometry x:Key="Icon.MoveDown">M862 465.3h-81c-4.6 0-9 2-12.1 5.5L550 723.1V160c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v563.1L255.1 470.8c-3-3.5-7.4-5.5-12.1-5.5h-81c-6.8 0-10.5 8.1-6 13.2L487.9 861c12.7 14.7 35.5 14.7 48.3 0L868 478.5c4.5-5.2 0.8-13.2-6-13.2z</Geometry>
|
||||
|
||||
<Geometry x:Key="Icon.StageSelected">M509.44 546.304l270.848-270.912 90.56 90.56-347.52 349.056-0.832-0.768-13.056 13.056-362.624-361.28 91.136-91.264z</Geometry>
|
||||
<Geometry x:Key="Icon.StageAll">M256 224l1e-8 115.2L512 544l255.99999999-204.8 1e-8-115.2-256 204.80000001L256 224zM512 684.8l-256-204.8L256 595.2 512 800 768 595.2l0-115.2L512 684.8z</Geometry>
|
||||
<Geometry x:Key="Icon.UnstageSelected">M169.5 831l342.8-341.9L855.1 831l105.3-105.3-448.1-448.1L64.2 725.7 169.5 831z</Geometry>
|
||||
<Geometry x:Key="Icon.UnstageAll">M768 800V684.8L512 480 256 684.8V800l256-204.8L768 800zM512 339.2L768 544V428.8L512 224 256 428.8V544l256-204.8z</Geometry>
|
||||
|
||||
<Geometry x:Key="Icon.Preference">M64.2 180.3h418.2v120.6H64.2zM64.2 461.7h358.5v120.6H64.2zM64.2 723.1h418.2v120.6H64.2zM601.9 180.3h358.5v120.6H601.9zM482.4 119.9h179.2v241.3H482.4zM303.2 401.4h179.2v241.3H303.2zM482.4 662.8h179.2v241.3H482.4zM540.3 461.7h420.1v120.6H540.3zM601.9 723.1h358.5v120.6H601.9z</Geometry>
|
||||
<Geometry x:Key="Icon.Setting">M887 576.8v-129.4L796.6 418c-4.6-14-10.2-27.4-16.8-40.4l43.2-84.8-91.6-91.6-84.8 43.2c-13-6.6-26.6-12.2-40.4-16.8l-29.4-90.4h-129.4L418 227.6c-13.8 4.6-27.4 10.2-40.4 16.8l-84.8-43.2-91.6 91.6 43.2 84.8c-6.6 13-12.2 26.6-16.8 40.4l-90.4 29.4v129.4l90.4 29.4c4.6 13.8 10.2 27.4 16.8 40.4l-43.2 84.8 91.6 91.6 84.8-43.2c13 6.6 26.6 12.2 40.4 16.8l29.4 90.4h129.4l29.4-90.4c14-4.6 27.4-10.2 40.4-16.8l84.8 43.2 91.6-91.6-43.2-84.8c6.6-13 12.2-26.6 16.8-40.4l90.4-29.4zM512 662c-82.8 0-150-67.2-150-150s67.2-150 150-150 150 67.2 150 150-67.2 150-150 150z</Geometry>
|
||||
<Geometry x:Key="Icon.Info">M 38,19C 48.4934,19 57,27.5066 57,38C 57,48.4934 48.4934,57 38,57C 27.5066,57 19,48.4934 19,38C 19,27.5066 27.5066,19 38,19 Z M 33.25,33.25L 33.25,36.4167L 36.4166,36.4167L 36.4166,47.5L 33.25,47.5L 33.25,50.6667L 44.3333,50.6667L 44.3333,47.5L 41.1666,47.5L 41.1666,36.4167L 41.1666,33.25L 33.25,33.25 Z M 38.7917,25.3333C 37.48,25.3333 36.4167,26.3967 36.4167,27.7083C 36.4167,29.02 37.48,30.0833 38.7917,30.0833C 40.1033,30.0833 41.1667,29.02 41.1667,27.7083C 41.1667,26.3967 40.1033,25.3333 38.7917,25.3333 Z</Geometry>
|
||||
<Geometry x:Key="Icon.Folder">M64 864h896V288h-396.224a64 64 0 0 1-57.242667-35.376L460.224 160H64v704z m-64 32V128a32 32 0 0 1 32-32h448a32 32 0 0 1 28.624 17.690667L563.776 224H992a32 32 0 0 1 32 32v640a32 32 0 0 1-32 32H32a32 32 0 0 1-32-32z</Geometry>
|
||||
<Geometry x:Key="Icon.Folder.Fill">M448 64l128 128h448v768H0V64z</Geometry>
|
||||
<Geometry x:Key="Icon.Folder.Open">M832 960l192-512H192L0 960zM128 384L0 960V128h288l128 128h416v128z</Geometry>
|
||||
<Geometry x:Key="Icon.File">M958.656 320H960v639.936A64 64 0 0 1 896.128 1024H191.936A63.872 63.872 0 0 1 128 959.936V64.064A64 64 0 0 1 191.936 0H640v320.96h319.616L958.656 320zM320 544c0 17.152 14.464 32 32.192 32h383.552A32.384 32.384 0 0 0 768 544c0-17.152-14.464-32-32.256-32H352.192A32.448 32.448 0 0 0 320 544z m0 128c0 17.152 14.464 32 32.192 32h383.552a32.384 32.384 0 0 0 32.256-32c0-17.152-14.464-32-32.256-32H352.192a32.448 32.448 0 0 0-32.192 32z m0 128c0 17.152 14.464 32 32.192 32h383.552a32.384 32.384 0 0 0 32.256-32c0-17.152-14.464-32-32.256-32H352.192a32.448 32.448 0 0 0-32.192 32z</Geometry>
|
||||
<Geometry x:Key="Icon.Diff">M854.2 306.6L611.3 72.9c-6-5.7-13.9-8.9-22.2-8.9H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h277l219 210.6V824c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V329.6c0-8.7-3.5-17-9.8-23zM553.4 201.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v704c0 17.7 14.3 32 32 32h512c17.7 0 32-14.3 32-32V397.3c0-8.5-3.4-16.6-9.4-22.6L553.4 201.4zM568 753c0 3.8-3.4 7-7.5 7h-225c-4.1 0-7.5-3.2-7.5-7v-42c0-3.8 3.4-7 7.5-7h225c4.1 0 7.5 3.2 7.5 7v42z m0-220c0 3.8-3.4 7-7.5 7H476v84.9c0 3.9-3.1 7.1-7 7.1h-42c-3.8 0-7-3.2-7-7.1V540h-84.5c-4.1 0-7.5-3.2-7.5-7v-42c0-3.9 3.4-7 7.5-7H420v-84.9c0-3.9 3.2-7.1 7-7.1h42c3.9 0 7 3.2 7 7.1V484h84.5c4.1 0 7.5 3.1 7.5 7v42z</Geometry>
|
||||
<Geometry x:Key="Icon.Filter">M599.22969 424.769286 599.22969 657.383158 424.769286 831.844585 424.769286 424.769286 192.155415 192.155415 831.844585 192.155415Z</Geometry>
|
||||
<Geometry x:Key="Icon.Binary">M71.111111 1024V0h661.333333L952.888889 219.420444V1024H71.111111z m808.305778-731.420444l-220.444445-219.448889H144.583111V950.897778h734.833778V292.579556zM438.528 512h-220.444444V219.420444h220.444444V512z m-73.500444-219.420444H291.555556v146.289777h73.472v-146.289777z m0 512h73.500444v73.130666h-220.444444v-73.130666H291.555556v-146.289778H218.083556V585.102222h146.944v219.448889z m293.944888-365.710223h73.472V512H512v-73.130667h73.472v-146.289777H512V219.420444h146.972444v219.448889z m73.472 438.840889H512V585.130667h220.444444v292.579555z m-73.472-219.420444h-73.500444v146.289778h73.500444v-146.289778z</Geometry>
|
||||
|
||||
<Geometry x:Key="Icon.Vertical">M1024 1024H0V0h1024v1024z m-64-64V320H320V256h640V64H64v896h192V64h64v896z</Geometry>
|
||||
<Geometry x:Key="Icon.Horizontal">M81.92 81.92v860.16h860.16V81.92H81.92z m802.304 57.856V322.56H139.776V139.776h744.448z m-744.448 240.64H322.56v503.808H139.776V380.416z m240.128 503.808V380.416h504.32v503.808H379.904z</Geometry>
|
||||
|
||||
<Geometry x:Key="Icon.Fetch">M1024 896v128H0V704h128v192h768V704h128v192zM576 554.688L810.688 320 896 405.312l-384 384-384-384L213.312 320 448 554.688V0h128v554.688z</Geometry>
|
||||
<Geometry x:Key="Icon.Pull">M432 0h160c26.6 0 48 21.4 48 48v336h175.4c35.6 0 53.4 43 28.2 68.2L539.4 756.6c-15 15-39.6 15-54.6 0L180.2 452.2c-25.2-25.2-7.4-68.2 28.2-68.2H384V48c0-26.6 21.4-48 48-48z m592 752v224c0 26.6-21.4 48-48 48H48c-26.6 0-48-21.4-48-48V752c0-26.6 21.4-48 48-48h293.4l98 98c40.2 40.2 105 40.2 145.2 0l98-98H976c26.6 0 48 21.4 48 48z m-248 176c0-22-18-40-40-40s-40 18-40 40 18 40 40 40 40-18 40-40z m128 0c0-22-18-40-40-40s-40 18-40 40 18 40 40 40 40-18 40-40z</Geometry>
|
||||
<Geometry x:Key="Icon.Push">M592 768h-160c-26.6 0-48-21.4-48-48V384h-175.4c-35.6 0-53.4-43-28.2-68.2L484.6 11.4c15-15 39.6-15 54.6 0l304.4 304.4c25.2 25.2 7.4 68.2-28.2 68.2H640v336c0 26.6-21.4 48-48 48z m432-16v224c0 26.6-21.4 48-48 48H48c-26.6 0-48-21.4-48-48V752c0-26.6 21.4-48 48-48h272v16c0 61.8 50.2 112 112 112h160c61.8 0 112-50.2 112-112v-16h272c26.6 0 48 21.4 48 48z m-248 176c0-22-18-40-40-40s-40 18-40 40 18 40 40 40 40-18 40-40z m128 0c0-22-18-40-40-40s-40 18-40 40 18 40 40 40 40-18 40-40z</Geometry>
|
||||
<Geometry x:Key="Icon.SaveStash">M961.3 319.6L512 577.3 62.7 319.6 512 62l449.3 257.6zM512 628.4L185.4 441.6 62.7 512 512 769.6 961.3 512l-122.7-70.4L512 628.4zM512 820.8L185.4 634 62.7 704.3 512 962l449.3-257.7L838.6 634 512 820.8z</Geometry>
|
||||
<Geometry x:Key="Icon.Apply">M295.328 472l143.184 276.032S671.184 186.992 1038.096 0c-8.944 133.568-44.8 249.328 17.904 391.792C894.912 427.408 563.792 828.112 456.4 1024 304.272 837.008 125.296 694.544 0 650.016z</Geometry>
|
||||
<Geometry x:Key="Icon.Terminal">M89.6 806.4h844.8V217.6H89.6v588.8zM0 128h1024v768H0V128z m242.816 577.536L192 654.72l154.304-154.368L192 346.048l50.816-50.816L448 500.352 242.816 705.536z m584.32 13.248H512V640h315.072v78.72z</Geometry>
|
||||
<Geometry x:Key="Icon.Flow">M508.928 556.125091l92.904727 148.759273h124.462546l-79.639273-79.173819 49.245091-49.524363 164.584727 163.700363-164.631273 163.002182-49.152-49.617454 79.36-78.568728h-162.955636l-95.650909-153.227636 41.472-65.349818z m186.973091-394.705455l164.584727 163.700364-164.631273 163.002182-49.152-49.617455L726.109091 359.936H529.687273l-135.540364 223.976727H139.636364v-69.818182h215.133091l135.586909-223.976727h235.938909l-79.639273-79.173818 49.245091-49.524364z</Geometry>
|
||||
|
||||
<Geometry x:Key="Icon.Commit">M795.968 471.04A291.584 291.584 0 0 0 512 256a293.376 293.376 0 0 0-283.968 215.04H0v144h228.032A292.864 292.864 0 0 0 512 832a291.136 291.136 0 0 0 283.968-216.96H1024V471.04h-228.032M512 688A145.856 145.856 0 0 1 366.016 544 144.576 144.576 0 0 1 512 400c80 0 145.984 63.104 145.984 144A145.856 145.856 0 0 1 512 688</Geometry>
|
||||
<Geometry x:Key="Icon.WorkingCopy">M0 586.459429l403.968 118.784 497.517714-409.892572-385.536 441.490286-1.609143 250.587428 154.916572-204.580571 278.601143 83.456L1170.285714 36.571429z</Geometry>
|
||||
<Geometry x:Key="Icon.Histories">M24.356571 512A488.155429 488.155429 0 0 1 512 24.356571 488.155429 488.155429 0 0 1 999.643429 512 488.155429 488.155429 0 0 1 512 999.643429 488.155429 488.155429 0 0 1 24.356571 512z m446.976-325.046857v326.656L242.614857 619.227429l51.126857 110.665142 299.52-138.24V186.953143H471.332571z</Geometry>
|
||||
<Geometry x:Key="Icon.Stashes">M714.624 253.648h-404.8l-57.808 57.328h520.48z m-491.568 85.984v200.624h578.336V339.632z m404.8 143.296h-28.88v-28.64H425.472v28.64h-28.912v-57.312h231.328v57.312z m-404.8 295.12h578.336V559.36H223.056z m173.504-132.704h231.328v57.328h-28.912v-28.656H425.472v28.656h-28.912v-57.328z</Geometry>
|
||||
<Geometry x:Key="Icon.Branch">M868.736 144.96a144.64 144.64 0 1 0-289.408 0c0 56.064 32.64 107.008 83.456 130.624-4.928 95.552-76.608 128-201.088 174.592-52.48 19.712-110.336 41.6-159.744 74.432V276.16A144.448 144.448 0 0 0 241.664 0.192a144.64 144.64 0 0 0-144.64 144.768c0 58.24 34.688 108.288 84.352 131.2v461.184a144.32 144.32 0 0 0-84.416 131.2 144.704 144.704 0 1 0 289.472 0 144.32 144.32 0 0 0-83.52-130.688c4.992-95.488 76.672-127.936 201.152-174.592 122.368-45.952 273.792-103.168 279.744-286.784a144.64 144.64 0 0 0 84.928-131.52zM241.664 61.44a83.456 83.456 0 1 1 0 166.912 83.456 83.456 0 0 1 0-166.912z m0 890.56a83.52 83.52 0 1 1 0-167.04 83.52 83.52 0 0 1 0 167.04zM724.032 228.416a83.52 83.52 0 1 1 0-167.04 83.52 83.52 0 0 1 0 167.04z</Geometry>
|
||||
<Geometry x:Key="Icon.Branch.Add">M896 128h-64V64c0-35.2-28.8-64-64-64s-64 28.8-64 64v64h-64c-35.2 0-64 28.8-64 64s28.8 64 64 64h64v64c0 35.2 28.8 64 64 64s64-28.8 64-64V256h64c35.2 0 64-28.8 64-64s-28.8-64-64-64z m-203.52 307.2C672.64 480.64 628.48 512 576 512H448c-46.72 0-90.24 12.8-128 35.2V372.48C394.24 345.6 448 275.2 448 192c0-106.24-85.76-192-192-192S64 85.76 64 192c0 83.2 53.76 153.6 128 180.48v279.68c-74.24 25.6-128 96.64-128 179.84 0 106.24 85.76 192 192 192s192-85.76 192-192c0-66.56-33.92-124.8-84.48-159.36 22.4-19.84 51.84-32.64 84.48-32.64h128c121.6 0 223.36-85.12 248.96-199.04-18.56 4.48-37.12 7.04-56.96 7.04-26.24 0-51.2-5.12-75.52-12.8zM256 128c35.2 0 64 28.8 64 64s-28.8 64-64 64-64-28.8-64-64 28.8-64 64-64z m0 768c-35.2 0-64-28.8-64-64s28.8-64 64-64 64 28.8 64 64-28.8 64-64 64z</Geometry>
|
||||
<Geometry x:Key="Icon.Remote">M901.802667 479.232v-1.024c0-133.461333-111.616-241.664-249.514667-241.664-105.813333 0-195.925333 63.829333-232.448 153.941333-27.989333-20.138667-62.464-32.426667-100.010667-32.426666-75.776 0-139.605333 49.152-159.744 116.053333-51.882667 36.522667-86.016 96.938667-86.016 165.205333 0 111.616 90.453333 201.728 201.728 201.728h503.466667c111.616 0 201.728-90.453333 201.728-201.728 0-65.194667-31.061333-123.221333-79.189333-160.085333z</Geometry>
|
||||
<Geometry x:Key="Icon.Remote.Add">M363.789474 512h67.368421v107.789474h107.789473v67.368421h-107.789473v107.789473h-67.368421v-107.789473h-107.789474v-67.368421h107.789474v-107.789474z m297.539368-64A106.671158 106.671158 0 0 1 768 554.671158C768 613.578105 719.548632 660.210526 660.210526 660.210526h-107.789473v-53.894737h-107.789474v-107.789473h-94.31579v107.789473h-94.315789c4.311579-21.194105 22.231579-46.807579 43.560421-50.755368l-0.889263-11.560421a74.671158 74.671158 0 0 1 71.248842-74.590316 128.053895 128.053895 0 0 1 238.605474-7.437473 106.172632 106.172632 0 0 1 52.816842-13.972211z</Geometry>
|
||||
<Geometry x:Key="Icon.Tag">M177.311335 156.116617c-22.478967 4.729721-32.774451 17.336854-36.251645 36.893258-10.080589 56.697303-33.399691 257.604032-13.234419 277.769304l445.342858 445.341834c23.177885 23.177885 60.757782 23.178909 83.935668 0l246.019183-246.019183c23.177885-23.177885 23.177885-60.757782 0-83.935668l-445.341834-445.341834C437.419398 120.463606 231.004211 144.82034 177.311335 156.116617zM331.22375 344.221786c-26.195615 26.195615-68.667939 26.195615-94.863555 0-26.195615-26.195615-26.195615-68.666916 0-94.863555s68.667939-26.195615 94.862531 0C357.418342 275.55487 357.419366 318.02617 331.22375 344.221786z</Geometry>
|
||||
<Geometry x:Key="Icon.Tag.Add">M682.666667 536.576h-143.701334v-142.336h-142.336V283.306667H238.933333a44.032 44.032 0 0 0-40.96 40.96v170.666666a55.978667 55.978667 0 0 0 14.336 34.133334l320.512 320.512a40.96 40.96 0 0 0 57.685334 0l173.738666-173.738667a40.96 40.96 0 0 0 0-57.685333z m-341.333334-108.544a40.96 40.96 0 1 1 0-57.685333 40.96 40.96 0 0 1 0 57.685333zM649.216 284.330667V141.994667h-68.608v142.336h-142.336v68.266666h142.336v142.336h68.608v-142.336h142.336v-68.266666h-142.336z</Geometry>
|
||||
</ResourceDictionary>
|
12
src/Resources/Styles/Border.xaml
Normal file
12
src/Resources/Styles/Border.xaml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style x:Key="Style.Border.Badge" TargetType="{x:Type Border}">
|
||||
<Setter Property="CornerRadius" Value="9"/>
|
||||
<Setter Property="Margin" Value="4,0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Height" Value="18"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.Badge}"/>
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
55
src/Resources/Styles/Button.xaml
Normal file
55
src/Resources/Styles/Button.xaml
Normal file
|
@ -0,0 +1,55 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<!-- 无边框按钮(也是默认样式) -->
|
||||
<Style x:Key="Style.Button" TargetType="{x:Type Button}">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource Brush.FG}"/>
|
||||
<Setter Property="Opacity" Value="0.9"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Opacity" Value="1"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<!-- 修改默认样式 -->
|
||||
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource Style.Button}"/>
|
||||
|
||||
<!-- 无边框但显示Hover -->
|
||||
<Style x:Key="Style.Button.HighlightHover" BasedOn="{StaticResource Style.Button}" TargetType="{x:Type Button}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#40000000"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<!-- 有边框 -->
|
||||
<Style x:Key="Style.Button.Bordered" BasedOn="{StaticResource Style.Button}" TargetType="{x:Type Button}">
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Border1}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="8,0"/>
|
||||
</Style>
|
||||
<Style x:Key="Style.Button.AccentBordered" BasedOn="{StaticResource Style.Button}" TargetType="{x:Type Button}">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent1}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource Brush.FG}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="8,0"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
38
src/Resources/Styles/CheckBox.xaml
Normal file
38
src/Resources/Styles/CheckBox.xaml
Normal file
|
@ -0,0 +1,38 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<Style TargetType="{x:Type CheckBox}">
|
||||
<Setter Property="Foreground" Value="{DynamicResource Brush.FG}"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type CheckBox}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border x:Name="Border" Grid.Column="0" Width="16" Height="16" VerticalAlignment="Center" BorderBrush="{DynamicResource Brush.Border1}" BorderThickness="1" Background="Transparent">
|
||||
<Path x:Name="Checked" Height="12" Width="12" Style="{DynamicResource Style.Icon}" Data="{DynamicResource Icon.Check}" Fill="{DynamicResource Brush.Accent1}"/>
|
||||
</Border>
|
||||
|
||||
<ContentPresenter Grid.Column="1" VerticalAlignment="Center" Margin="8,0,0,0" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||
</Grid>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="False">
|
||||
<Setter TargetName="Checked" Property="Visibility" Value="Hidden"/>
|
||||
</Trigger>
|
||||
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource Brush.Accent1}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
83
src/Resources/Styles/ComboBox.xaml
Normal file
83
src/Resources/Styles/ComboBox.xaml
Normal file
|
@ -0,0 +1,83 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<ControlTemplate x:Key="Template.ComboBox.ToggleButton" TargetType="{x:Type ToggleButton}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="20" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border x:Name="Border" Grid.ColumnSpan="2" CornerRadius="0" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{DynamicResource Brush.Border1}" Background="Transparent"/>
|
||||
<Border Grid.Column="0" CornerRadius="0" Margin="1" Background="Transparent"/>
|
||||
<Path x:Name="Arrow" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" Data="M 0 0 L 4 4 L 8 0 Z" Fill="{DynamicResource Brush.Border1}"/>
|
||||
</Grid>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource Brush.Accent1}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="Template.ComboBox.TextBox" TargetType="{x:Type TextBox}">
|
||||
<Border x:Name="PART_ContentHost" Focusable="False" Background="{TemplateBinding Background}" />
|
||||
</ControlTemplate>
|
||||
|
||||
<Style TargetType="{x:Type ComboBox}">
|
||||
<Setter Property="SnapsToDevicePixels" Value="true" />
|
||||
<Setter Property="OverridesDefaultStyle" Value="true" />
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
|
||||
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
|
||||
<Setter Property="ScrollViewer.CanContentScroll" Value="true" />
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="MinWidth" Value="120" />
|
||||
<Setter Property="MinHeight" Value="20" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ComboBox}">
|
||||
<Grid>
|
||||
<ToggleButton x:Name="ToggleButton" BorderThickness="{TemplateBinding BorderThickness}" Template="{StaticResource Template.ComboBox.ToggleButton}" Grid.Column="2" Focusable="false" ClickMode="Press" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"/>
|
||||
<ContentPresenter x:Name="ContentSite" IsHitTestVisible="False" Content="{TemplateBinding SelectionBoxItem}" ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" Margin="3,3,23,3" VerticalAlignment="Center" HorizontalAlignment="Left" TextElement.Foreground="{DynamicResource Brush.FG}"/>
|
||||
<TextBox x:Name="PART_EditableTextBox" Style="{x:Null}" Template="{StaticResource Template.ComboBox.TextBox}" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="3,3,23,3" Focusable="True" Background="Transparent" Visibility="Hidden" IsReadOnly="{TemplateBinding IsReadOnly}" />
|
||||
<Popup x:Name="Popup" Placement="Bottom" IsOpen="{TemplateBinding IsDropDownOpen}" AllowsTransparency="True" Focusable="False" PopupAnimation="Slide">
|
||||
<Grid x:Name="DropDown" SnapsToDevicePixels="True" MinWidth="{TemplateBinding ActualWidth}" MaxHeight="{TemplateBinding MaxDropDownHeight}">
|
||||
<Border x:Name="DropDownBorder" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}" Background="{DynamicResource Brush.BG2}"/>
|
||||
<ScrollViewer Margin="4,6,4,6" SnapsToDevicePixels="True">
|
||||
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" TextElement.Foreground="{DynamicResource Brush.FG}"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Popup>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsGrouping" Value="true">
|
||||
<Setter Property="ScrollViewer.CanContentScroll" Value="false" />
|
||||
</Trigger>
|
||||
<Trigger SourceName="Popup" Property="AllowsTransparency" Value="true">
|
||||
<Setter TargetName="DropDownBorder" Property="CornerRadius" Value="0" />
|
||||
<Setter TargetName="DropDownBorder" Property="Margin" Value="0,2,0,0" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ComboBoxItem}">
|
||||
<Setter Property="SnapsToDevicePixels" Value="true" />
|
||||
<Setter Property="OverridesDefaultStyle" Value="true" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ComboBoxItem}">
|
||||
<Border x:Name="Border" Padding="2" SnapsToDevicePixels="true" Background="Transparent">
|
||||
<ContentPresenter/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Brush.Accent1}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
91
src/Resources/Styles/ContextMenu.xaml
Normal file
91
src/Resources/Styles/ContextMenu.xaml
Normal file
|
@ -0,0 +1,91 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<Style TargetType="{x:Type MenuItem}">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource Brush.FG}"/>
|
||||
<Setter Property="MinHeight" Value="24"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type ContextMenu}">
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="Grid.IsSharedSizeScope" Value="False" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ContextMenu}">
|
||||
<Border Background="{DynamicResource Brush.BG1}" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}">
|
||||
<StackPanel IsItemsHost="True" Margin="1" KeyboardNavigation.DirectionalNavigation="Cycle"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<ControlTemplate x:Key="{x:Static MenuItem.SubmenuItemTemplateKey}" TargetType="{x:Type MenuItem}">
|
||||
<Border Name="Border" Background="Transparent">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="32"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ContentPresenter Name="Icon" Grid.Column="0" HorizontalAlignment="Center" ContentSource="Icon"/>
|
||||
<ContentPresenter Name="HeadHost" Grid.Column="1" ContentSource="Header" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Brush.Accent2}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value=".5"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="{x:Static MenuItem.SubmenuHeaderTemplateKey}" TargetType="{x:Type MenuItem}">
|
||||
<Border Name="Border" Background="Transparent">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="32"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ContentPresenter Name="Icon" Grid.Column="0" Margin="6,0" VerticalAlignment="Center" ContentSource="Icon"/>
|
||||
<ContentPresenter Name="HeadHost" Grid.Column="1" ContentSource="Header" VerticalAlignment="Center"/>
|
||||
<Path Grid.Column="2" Width="8" Height="8" Style="{DynamicResource Style.Icon}" Data="M 0 0 L 0 7 L 4 3.5 Z"/>
|
||||
<Popup Name="Popup" Placement="Right" HorizontalOffset="-2" IsOpen="{TemplateBinding IsSubmenuOpen}" AllowsTransparency="True" Focusable="False" PopupAnimation="Fade">
|
||||
<Border Name="SubmenuBorder" SnapsToDevicePixels="True" Background="{DynamicResource Brush.BG1}" BorderBrush="{DynamicResource Brush.Border1}" BorderThickness="1">
|
||||
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Cycle"/>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsHighlighted" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Brush.Accent2}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value=".5"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style x:Key="{x:Static MenuItem.SeparatorStyleKey}" TargetType="{x:Type Separator}">
|
||||
<Setter Property="Margin" Value="30,2,0,2"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Separator}">
|
||||
<Rectangle Height=".8" VerticalAlignment="Center" SnapsToDevicePixels="True" Fill="{DynamicResource Brush.Border1}"/>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
44
src/Resources/Styles/DataGrid.xaml
Normal file
44
src/Resources/Styles/DataGrid.xaml
Normal file
|
@ -0,0 +1,44 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<Style x:Key="Style.DataGridCell" TargetType="{x:Type DataGridCell}">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="Style.DataGridRow" TargetType="{x:Type DataGridRow}">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.Accent2}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type DataGrid}">
|
||||
<Style.Resources>
|
||||
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="{x:Static SystemColors.HighlightColor}"/>
|
||||
</Style.Resources>
|
||||
|
||||
<Setter Property="IsReadOnly" Value="True"/>
|
||||
<Setter Property="AutoGenerateColumns" Value="False"/>
|
||||
<Setter Property="CanUserAddRows" Value="False"/>
|
||||
<Setter Property="CanUserDeleteRows" Value="False"/>
|
||||
<Setter Property="CanUserResizeRows" Value="False"/>
|
||||
<Setter Property="CanUserReorderColumns" Value="False"/>
|
||||
<Setter Property="CanUserResizeColumns" Value="False" />
|
||||
<Setter Property="CanUserSortColumns" Value="False"/>
|
||||
<Setter Property="AllowDrop" Value="False"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource Brush.FG}"/>
|
||||
<Setter Property="TextElement.FontFamily" Value="Consolas"/>
|
||||
<Setter Property="EnableColumnVirtualization" Value="True"/>
|
||||
<Setter Property="EnableRowVirtualization" Value="True"/>
|
||||
<Setter Property="RowBackground" Value="Transparent"/>
|
||||
<Setter Property="HeadersVisibility" Value="None"/>
|
||||
<Setter Property="GridLinesVisibility" Value="None"/>
|
||||
<Setter Property="CellStyle" Value="{StaticResource Style.DataGridCell}"/>
|
||||
<Setter Property="RowStyle" Value="{StaticResource Style.DataGridRow}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
11
src/Resources/Styles/HyperLink.xaml
Normal file
11
src/Resources/Styles/HyperLink.xaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style TargetType="{x:Type Hyperlink}">
|
||||
<Setter Property="TextDecorations" Value="{x:Null}"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Foreground" Value="{DynamicResource Brush.Accent1}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
15
src/Resources/Styles/Label.xaml
Normal file
15
src/Resources/Styles/Label.xaml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style x:Key="Style.Label" TargetType="{x:Type Label}">
|
||||
<Setter Property="Foreground" Value="{DynamicResource Brush.FG}"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource Style.Label}"/>
|
||||
|
||||
<Style x:Key="Style.Label.GroupHeader" BasedOn="{StaticResource Style.Label}" TargetType="{x:Type Label}">
|
||||
<Setter Property="FontWeight" Value="DemiBold"/>
|
||||
<Setter Property="Opacity" Value=".5"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
66
src/Resources/Styles/ListView.xaml
Normal file
66
src/Resources/Styles/ListView.xaml
Normal file
|
@ -0,0 +1,66 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<Style x:Key="Style.ListViewItem.Borderless" TargetType="{x:Type ListViewItem}">
|
||||
<Setter Property="Padding" Value="2"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="true"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ListViewItem}">
|
||||
<Border x:Name="Border" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true" Background="Transparent">
|
||||
<ContentPresenter VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
|
||||
</Border>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Brush.Accent1}"/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="True"/>
|
||||
<Condition Property="IsSelected" Value="False"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter TargetName="Border" Property="Background" Value="{DynamicResource Brush.Accent2}"/>
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="Style.ListView.Borderless" TargetType="{x:Type ListView}">
|
||||
<Setter Property="SnapsToDevicePixels" Value="true" />
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
|
||||
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
|
||||
<Setter Property="ScrollViewer.CanContentScroll" Value="True" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource Style.ListViewItem.Borderless}"/>
|
||||
<Setter Property="VirtualizingPanel.IsVirtualizing" Value="True"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="ItemsPanel">
|
||||
<Setter.Value>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel/>
|
||||
</ItemsPanelTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ListView}">
|
||||
<Border Name="Border" BorderThickness="0" Background="{TemplateBinding Background}">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
|
||||
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
|
||||
CanContentScroll="{TemplateBinding ScrollViewer.CanContentScroll}">
|
||||
<ItemsPresenter/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsGrouping" Value="true">
|
||||
<Setter Property="ScrollViewer.CanContentScroll" Value="false" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
21
src/Resources/Styles/Path.xaml
Normal file
21
src/Resources/Styles/Path.xaml
Normal file
|
@ -0,0 +1,21 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style x:Key="Style.Icon" TargetType="{x:Type Path}">
|
||||
<Setter Property="Width" Value="16"/>
|
||||
<Setter Property="Height" Value="16"/>
|
||||
<Setter Property="Stretch" Value="Uniform"/>
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.FG}"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
<Setter Property="RenderOptions.BitmapScalingMode" Value="HighQuality"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="Style.WindowControlIcon" TargetType="{x:Type Path}">
|
||||
<Setter Property="Stretch" Value="None"/>
|
||||
<Setter Property="Fill" Value="{DynamicResource Brush.FG}"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
<Setter Property="RenderOptions.BitmapScalingMode" Value="HighQuality"/>
|
||||
<Setter Property="RenderOptions.EdgeMode" Value="Aliased"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
52
src/Resources/Styles/RadioButton.xaml
Normal file
52
src/Resources/Styles/RadioButton.xaml
Normal file
|
@ -0,0 +1,52 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style TargetType="{x:Type RadioButton}">
|
||||
<Setter Property="Foreground" Value="{DynamicResource Brush.FG}"/>
|
||||
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type RadioButton}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid Grid.Column="0">
|
||||
<Path
|
||||
x:Name="Border"
|
||||
Width="14" Height="14"
|
||||
Stretch="Uniform"
|
||||
Fill="Transparent"
|
||||
Stroke="{DynamicResource Brush.Border1}" StrokeThickness="1"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Data="M 0,0 A 180,180 180 1 1 1,1 Z"/>
|
||||
<Path
|
||||
x:Name="Dot"
|
||||
Width="10" Height="10"
|
||||
Stretch="Uniform"
|
||||
Fill="{DynamicResource Brush.Accent1}"
|
||||
Stroke="Transparent" StrokeThickness="1"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Data="M 0,0 A 180,180 180 1 1 1,1 Z"
|
||||
Visibility="Collapsed"/>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Column="1" Margin="4,0">
|
||||
<ContentPresenter HorizontalAlignment="Left" VerticalAlignment="Center" RecognizesAccessKey="True"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="Dot" Property="Visibility" Value="Visible"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Stroke" Value="{DynamicResource Brush.Accent1}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
98
src/Resources/Styles/ScrollBar.xaml
Normal file
98
src/Resources/Styles/ScrollBar.xaml
Normal file
|
@ -0,0 +1,98 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<Style x:Key="Style.ScrollBar.RepeatPage" TargetType="{x:Type RepeatButton}">
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type RepeatButton}">
|
||||
<Border x:Name="area" Background="Transparent" />
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="area" Property="Background" Value="{DynamicResource Brush.FG}" />
|
||||
<Setter TargetName="area" Property="Opacity" Value=".08"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="Style.ScrollBar.Thumb" TargetType="{x:Type Thumb}">
|
||||
<Setter Property="Background" Value="{DynamicResource Brush.Border1}" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Focusable" Value="false" />
|
||||
<Setter Property="IsTabStop" Value="false" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="true" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Thumb}">
|
||||
<Border x:Name="Border" Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Opacity=".6"/>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Border" Property="Opacity" Value="1" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<ControlTemplate x:Key="Template.ScrollBar.Horizontal" TargetType="{x:Type ScrollBar}">
|
||||
<Grid>
|
||||
<Track Name="PART_Track" Grid.Column="1">
|
||||
<Track.DecreaseRepeatButton>
|
||||
<RepeatButton Command="ScrollBar.PageLeftCommand" Style="{StaticResource Style.ScrollBar.RepeatPage}" />
|
||||
</Track.DecreaseRepeatButton>
|
||||
<Track.Thumb>
|
||||
<Thumb Style="{StaticResource Style.ScrollBar.Thumb}" />
|
||||
</Track.Thumb>
|
||||
<Track.IncreaseRepeatButton>
|
||||
<RepeatButton Command="ScrollBar.PageRightCommand" Style="{StaticResource Style.ScrollBar.RepeatPage}" />
|
||||
</Track.IncreaseRepeatButton>
|
||||
</Track>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="Template.ScrollBar.Vertical" TargetType="{x:Type ScrollBar}">
|
||||
<Grid>
|
||||
<Track Name="PART_Track"
|
||||
Grid.Row="1"
|
||||
IsDirectionReversed="true">
|
||||
<Track.DecreaseRepeatButton>
|
||||
<RepeatButton Command="ScrollBar.PageUpCommand" Style="{StaticResource Style.ScrollBar.RepeatPage}" />
|
||||
</Track.DecreaseRepeatButton>
|
||||
<Track.Thumb>
|
||||
<Thumb Style="{StaticResource Style.ScrollBar.Thumb}"/>
|
||||
</Track.Thumb>
|
||||
<Track.IncreaseRepeatButton>
|
||||
<RepeatButton Command="ScrollBar.PageDownCommand" Style="{StaticResource Style.ScrollBar.RepeatPage}" />
|
||||
</Track.IncreaseRepeatButton>
|
||||
</Track>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style TargetType="{x:Type ScrollBar}">
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Orientation" Value="Vertical">
|
||||
<Setter Property="Height" Value="Auto" />
|
||||
<Setter Property="Template" Value="{StaticResource Template.ScrollBar.Vertical}" />
|
||||
<Setter Property="Width" Value="8" />
|
||||
</Trigger>
|
||||
<Trigger Property="Orientation" Value="Horizontal">
|
||||
<Setter Property="Height" Value="8" />
|
||||
<Setter Property="Template" Value="{StaticResource Template.ScrollBar.Horizontal}" />
|
||||
<Setter Property="Width" Value="Auto" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
54
src/Resources/Styles/ScrollViewer.xaml
Normal file
54
src/Resources/Styles/ScrollViewer.xaml
Normal file
|
@ -0,0 +1,54 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style TargetType="{x:Type ScrollViewer}">
|
||||
<Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
|
||||
<Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Border1}"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ScrollViewer}">
|
||||
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<Grid Background="{TemplateBinding Background}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ScrollContentPresenter
|
||||
Cursor="{TemplateBinding Cursor}"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"/>
|
||||
|
||||
<ScrollBar x:Name="PART_VerticalScrollBar"
|
||||
IsTabStop="False"
|
||||
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
|
||||
Grid.Column="1" Grid.Row="0" Orientation="Vertical"
|
||||
ViewportSize="{TemplateBinding ViewportHeight}"
|
||||
Maximum="{TemplateBinding ScrollableHeight}"
|
||||
Minimum="0"
|
||||
Value="{TemplateBinding VerticalOffset}"
|
||||
Margin="0,-1,-1,-1"/>
|
||||
|
||||
<ScrollBar x:Name="PART_HorizontalScrollBar"
|
||||
IsTabStop="False"
|
||||
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
|
||||
Grid.Column="0" Grid.Row="1" Orientation="Horizontal"
|
||||
ViewportSize="{TemplateBinding ViewportWidth}"
|
||||
Maximum="{TemplateBinding ScrollableWidth}"
|
||||
Minimum="0"
|
||||
Value="{TemplateBinding HorizontalOffset}"
|
||||
Margin="-1,0,-1,-1"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
62
src/Resources/Styles/TabControl.xaml
Normal file
62
src/Resources/Styles/TabControl.xaml
Normal file
|
@ -0,0 +1,62 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style TargetType="{x:Type TabControl}">
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TabControl}">
|
||||
<Grid KeyboardNavigation.TabNavigation="Local">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TabPanel x:Name="HeaderPanel" Grid.Row="0" IsItemsHost="True" KeyboardNavigation.TabIndex="1" Background="Transparent" />
|
||||
<Border
|
||||
x:Name="Border"
|
||||
Grid.Row="1"
|
||||
Background="Transparent"
|
||||
KeyboardNavigation.TabNavigation="Local"
|
||||
KeyboardNavigation.DirectionalNavigation="Contained"
|
||||
KeyboardNavigation.TabIndex="2">
|
||||
<ContentPresenter x:Name="PART_SelectedContentHost" Margin="4" ContentSource="SelectedContent" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type TabItem}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TabItem}">
|
||||
<Border x:Name="Border" Margin="0" BorderThickness="0,0,0,1.1" BorderBrush="Transparent" Opacity=".7">
|
||||
<ContentPresenter
|
||||
x:Name="ContentSite"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
TextElement.Foreground="{DynamicResource Brush.FG}"
|
||||
TextElement.FontWeight="Bold"
|
||||
ContentSource="Header"
|
||||
Margin="8,6"
|
||||
RecognizesAccessKey="True" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource Brush.Accent1}"/>
|
||||
<Setter TargetName="Border" Property="Opacity" Value="1"/>
|
||||
<Setter TargetName="ContentSite" Property="TextElement.Foreground" Value="{DynamicResource Brush.Accent1}"/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsSelected" Value="False"/>
|
||||
<Condition Property="IsMouseOver" Value="True"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource Brush.Accent2}"/>
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
126
src/Resources/Styles/TextBox.xaml
Normal file
126
src/Resources/Styles/TextBox.xaml
Normal file
|
@ -0,0 +1,126 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:helpers="clr-namespace:SourceGit.Helpers">
|
||||
|
||||
<!-- 错误Tooltip -->
|
||||
<ControlTemplate x:Key="Template.Validation.Tooltip" TargetType="{x:Type ToolTip}">
|
||||
<Border x:Name="Root" Margin="5,0,0,0" Opacity="0" Padding="0,0,20,20" RenderTransformOrigin="0,0">
|
||||
<Border.RenderTransform>
|
||||
<TranslateTransform x:Name="xform" X="-25" />
|
||||
</Border.RenderTransform>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="OpenStates">
|
||||
<VisualStateGroup.Transitions>
|
||||
<VisualTransition GeneratedDuration="0" />
|
||||
<VisualTransition GeneratedDuration="0:0:0.2" To="Open">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Duration="0:0:0.2" To="0" Storyboard.TargetProperty="X" Storyboard.TargetName="xform">
|
||||
<DoubleAnimation.EasingFunction>
|
||||
<BackEase Amplitude=".3" EasingMode="EaseOut" />
|
||||
</DoubleAnimation.EasingFunction>
|
||||
</DoubleAnimation>
|
||||
<DoubleAnimation Duration="0:0:0.2" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root" />
|
||||
</Storyboard>
|
||||
</VisualTransition>
|
||||
</VisualStateGroup.Transitions>
|
||||
<VisualState x:Name="Closed">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root" />
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Open">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="X" Storyboard.TargetName="xform" />
|
||||
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root" />
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<FrameworkElement.Effect>
|
||||
<DropShadowEffect BlurRadius="11" ShadowDepth="6" Opacity="0.4" />
|
||||
</FrameworkElement.Effect>
|
||||
<Border Background="#FFDC000C" BorderThickness="1" BorderBrush="#FFBC000C">
|
||||
<TextBlock Foreground="White" MaxWidth="250" Margin="8,4,8,4" TextWrapping="Wrap" Text="{Binding [0].ErrorContent}" UseLayoutRounding="false" />
|
||||
</Border>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
|
||||
<!-- 验证错误模板 -->
|
||||
<ControlTemplate x:Key="Template.Validation.Error">
|
||||
<AdornedElementPlaceholder x:Name="Target">
|
||||
<Border BorderBrush="#FFDB000C" BorderThickness="1" x:Name="root">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="validationTooltip"
|
||||
Placement="Right"
|
||||
PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Template="{StaticResource Template.Validation.Tooltip}"
|
||||
Style="{x:Null}"/>
|
||||
</ToolTipService.ToolTip>
|
||||
<Grid Background="Transparent" HorizontalAlignment="Right" Height="12" Width="12" Margin="1,-4,-4,0" VerticalAlignment="Top">
|
||||
<Path Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z" Fill="#FFDC000C" Margin="1,3,0,0" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</AdornedElementPlaceholder>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding ElementName=Target, Path=AdornedElement.IsKeyboardFocusWithin, Mode=OneWay}" Value="True" />
|
||||
<Condition Binding="{Binding ElementName=Target, Path=AdornedElement.(Validation.HasError), Mode=OneWay}" Value="True" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter TargetName="validationTooltip" Property="IsOpen" Value="True"/>
|
||||
</MultiDataTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
|
||||
<!-- 修改默认 -->
|
||||
<Style TargetType="{x:Type TextBox}">
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource Brush.FG}"/>
|
||||
<Setter Property="CaretBrush" Value="{DynamicResource Brush.FG}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Border1}"/>
|
||||
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource Template.Validation.Error}"/>
|
||||
<Setter Property="helpers:TextBoxHelper.AutoScroll" Value="True"/>
|
||||
<Setter Property="ContextMenu">
|
||||
<Setter.Value>
|
||||
<ContextMenu>
|
||||
<MenuItem Command="ApplicationCommands.Copy" />
|
||||
<MenuItem Command="ApplicationCommands.Cut" />
|
||||
<MenuItem Command="ApplicationCommands.Paste" />
|
||||
</ContextMenu>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TextBox}">
|
||||
<Border x:Name="Border"
|
||||
Background="{TemplateBinding Background}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}">
|
||||
<ScrollViewer x:Name="PART_ContentHost"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
VerticalAlignment="Center"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
IsTabStop="False"
|
||||
CanContentScroll="False"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||
</Border>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="true">
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{DynamicResource Brush.Accent1}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="AcceptsReturn" Value="True">
|
||||
<Setter TargetName="PART_ContentHost" Property="VerticalAlignment" Value="Top"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
111
src/Resources/Styles/ToggleButton.xaml
Normal file
111
src/Resources/Styles/ToggleButton.xaml
Normal file
|
@ -0,0 +1,111 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style x:Key="Style.ToggleButton.Expender" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Grid Background="Transparent">
|
||||
<ContentPresenter/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="Style.ToggleButton.Filter" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Grid>
|
||||
<Path
|
||||
x:Name="Icon"
|
||||
Height="12"
|
||||
Style="{DynamicResource Style.Icon}"
|
||||
Fill="Transparent"
|
||||
Stroke="{DynamicResource Brush.FG2}"
|
||||
StrokeThickness="1"
|
||||
Data="{DynamicResource Icon.Filter}"/>
|
||||
</Grid>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="Icon" Property="Fill" Value="{DynamicResource Brush.FG2}"/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsChecked" Value="False"/>
|
||||
<Condition Property="IsMouseOver" Value="True"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter TargetName="Icon" Property="Fill" Value="{DynamicResource Brush.FG2}"/>
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="Style.ToggleButton.Orientation" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Grid Background="Transparent">
|
||||
<Path
|
||||
x:Name="Icon"
|
||||
Width="18"
|
||||
Height="18"
|
||||
Style="{DynamicResource Style.Icon}"
|
||||
Fill="{DynamicResource Brush.Border1}"
|
||||
Data="{DynamicResource Icon.Horizontal}"/>
|
||||
</Grid>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="Icon" Property="Data" Value="{DynamicResource Icon.Vertical}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Icon" Property="Fill" Value="{DynamicResource Brush.Accent1}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="Style.ToggleButton.ListOrTree" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Grid Background="Transparent">
|
||||
<Path
|
||||
x:Name="Icon"
|
||||
Height="12"
|
||||
Width="12"
|
||||
Style="{DynamicResource Style.Icon}"
|
||||
Fill="Transparent"
|
||||
Stroke="{DynamicResource Brush.FG}"
|
||||
StrokeThickness=".4"
|
||||
Data="{DynamicResource Icon.Tree}"/>
|
||||
</Grid>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="Icon" Property="Data" Value="{DynamicResource Icon.List}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
21
src/Resources/Styles/Tooltip.xaml
Normal file
21
src/Resources/Styles/Tooltip.xaml
Normal file
|
@ -0,0 +1,21 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Style TargetType="{x:Type ToolTip}">
|
||||
<Setter Property="OverridesDefaultStyle" Value="True"/>
|
||||
<Setter Property="HasDropShadow" Value="False"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToolTip}">
|
||||
<Border
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource Brush.Border1}"
|
||||
Background="{DynamicResource Brush.BG1}"
|
||||
Width="Auto"
|
||||
Height="Auto">
|
||||
<ContentPresenter Margin="6,4" TextElement.Foreground="{DynamicResource Brush.FG}" HorizontalAlignment="Left" VerticalAlignment="Top" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
201
src/Resources/Styles/TreeView.xaml
Normal file
201
src/Resources/Styles/TreeView.xaml
Normal file
|
@ -0,0 +1,201 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="clr-namespace:SourceGit.Converters"
|
||||
xmlns:helpers="clr-namespace:SourceGit.Helpers">
|
||||
|
||||
<converters:TreeViewItemDepthToMargin x:Key="Converter.TreeViewItemIndent" Indent="19"/>
|
||||
|
||||
<Style x:Key="Style.TreeView.ToggleButton" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="Width" Value="16" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Grid Width="16" Height="16" Margin="1" Background="Transparent">
|
||||
<Path x:Name="ExpandPath" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="1,1,1,1" Fill="{DynamicResource Brush.FG}" Data="M 4 0 L 8 4 L 4 8 Z"/>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter Property="Data" TargetName="ExpandPath" Value="M 0 4 L 8 4 L 4 8 Z"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="Style.TreeView.ItemContainerStyle" TargetType="{x:Type TreeViewItem}">
|
||||
<Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" />
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, Mode=OneWay, FallbackValue=Stretch, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
|
||||
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, Mode=OneWay, FallbackValue=Center, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TreeViewItem}">
|
||||
<StackPanel>
|
||||
<Border
|
||||
x:Name="BG"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
|
||||
<Grid
|
||||
Margin="{Binding Converter={StaticResource Converter.TreeViewItemIndent}, RelativeSource={x:Static RelativeSource.TemplatedParent}}"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="Transparent">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ToggleButton
|
||||
Grid.Column="0"
|
||||
x:Name="Expander"
|
||||
Style="{StaticResource Style.TreeView.ToggleButton}"
|
||||
IsChecked="{Binding Path=IsExpanded, RelativeSource={x:Static RelativeSource.TemplatedParent}, Mode=TwoWay}"
|
||||
ClickMode="Press"/>
|
||||
<ContentPresenter
|
||||
x:Name="PART_Header"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
ContentSource="Header"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ItemsPresenter x:Name="ItemsHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||
</StackPanel>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsExpanded" Value="False">
|
||||
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
|
||||
</Trigger>
|
||||
<Trigger Property="HasItems" Value="False">
|
||||
<Setter TargetName="Expander" Property="Visibility" Value="Hidden"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="BG" Property="Background" Value="{DynamicResource Brush.Accent1}"/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition SourceName="BG" Property="IsMouseOver" Value="True"/>
|
||||
<Condition Property="IsSelected" Value="False"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter TargetName="BG" Property="Background" Value="{DynamicResource Brush.Accent2}"/>
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="Style.TreeView.MultiSelectionItemContainerStyle" TargetType="{x:Type TreeViewItem}">
|
||||
<Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" />
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, Mode=OneWay, FallbackValue=Stretch, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
|
||||
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, Mode=OneWay, FallbackValue=Center, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TreeViewItem}">
|
||||
<StackPanel>
|
||||
<Border
|
||||
x:Name="BG"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
|
||||
<Grid
|
||||
Margin="{Binding Converter={StaticResource Converter.TreeViewItemIndent}, RelativeSource={x:Static RelativeSource.TemplatedParent}}"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="Transparent">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ToggleButton
|
||||
Grid.Column="0"
|
||||
x:Name="Expander"
|
||||
Style="{StaticResource Style.TreeView.ToggleButton}"
|
||||
IsChecked="{Binding Path=IsExpanded, RelativeSource={x:Static RelativeSource.TemplatedParent}, Mode=TwoWay}"
|
||||
ClickMode="Press"/>
|
||||
<ContentPresenter
|
||||
x:Name="PART_Header"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
ContentSource="Header"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ItemsPresenter x:Name="ItemsHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||
</StackPanel>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsExpanded" Value="False">
|
||||
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed"/>
|
||||
</Trigger>
|
||||
<Trigger Property="HasItems" Value="False">
|
||||
<Setter TargetName="Expander" Property="Visibility" Value="Hidden"/>
|
||||
</Trigger>
|
||||
<Trigger Property="helpers:TreeViewHelper.IsChecked" Value="True">
|
||||
<Setter TargetName="BG" Property="Background" Value="{DynamicResource Brush.Accent1}"/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition SourceName="BG" Property="IsMouseOver" Value="True"/>
|
||||
<Condition Property="helpers:TreeViewHelper.IsChecked" Value="False"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter TargetName="BG" Property="Background" Value="{DynamicResource Brush.Accent2}"/>
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type TreeView}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="ItemContainerStyle" Value="{StaticResource Style.TreeView.ItemContainerStyle}"/>
|
||||
<Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True" />
|
||||
<Setter Property="VirtualizingStackPanel.VirtualizationMode" Value="Standard" />
|
||||
<Setter Property="ScrollViewer.CanContentScroll" Value="True" />
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="ItemsPanel">
|
||||
<Setter.Value>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel IsItemsHost="True"/>
|
||||
</ItemsPanelTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TreeView}">
|
||||
<Border Name="Border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
|
||||
<ScrollViewer Padding="{TemplateBinding Padding}"
|
||||
CanContentScroll="{TemplateBinding ScrollViewer.CanContentScroll}"
|
||||
Focusable="False"
|
||||
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
|
||||
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
|
||||
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" Value=".5"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
18
src/Resources/Themes/Dark.xaml
Normal file
18
src/Resources/Themes/Dark.xaml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="Brush.BG1" Color="#FF252525"/>
|
||||
<SolidColorBrush x:Key="Brush.BG2" Color="#FF1B1B1B"/>
|
||||
<SolidColorBrush x:Key="Brush.BG3" Color="#FF202020"/>
|
||||
<SolidColorBrush x:Key="Brush.BG4" Color="#FF303030"/>
|
||||
<SolidColorBrush x:Key="Brush.BG5" Color="#FF505050"/>
|
||||
<SolidColorBrush x:Key="Brush.BG6" Color="#FF404040"/>
|
||||
<SolidColorBrush x:Key="Brush.BG7" Color="#FFFAFAD2"/>
|
||||
<SolidColorBrush x:Key="Brush.Border1" Color="#FF7C7C7C"/>
|
||||
<SolidColorBrush x:Key="Brush.Border2" Color="#FF404040"/>
|
||||
<SolidColorBrush x:Key="Brush.FG" Color="#FFF1F1F1"/>
|
||||
<SolidColorBrush x:Key="Brush.FG2" Color="#40F1F1F1"/>
|
||||
<SolidColorBrush x:Key="Brush.Badge" Color="#FF8F8F8F"/>
|
||||
<SolidColorBrush x:Key="Brush.Accent1" Color="#FF007ACC"/>
|
||||
<SolidColorBrush x:Key="Brush.Accent2" Color="#4C007ACC"/>
|
||||
</ResourceDictionary>
|
17
src/Resources/Themes/Light.xaml
Normal file
17
src/Resources/Themes/Light.xaml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="Brush.BG1" Color="#FFEEEEF2"/>
|
||||
<SolidColorBrush x:Key="Brush.BG2" Color="White"/>
|
||||
<SolidColorBrush x:Key="Brush.BG3" Color="WhiteSmoke"/>
|
||||
<SolidColorBrush x:Key="Brush.BG4" Color="#FFE6E7E8"/>
|
||||
<SolidColorBrush x:Key="Brush.BG5" Color="#FFBDBDBD"/>
|
||||
<SolidColorBrush x:Key="Brush.BG6" Color="#FFCFCFCF"/>
|
||||
<SolidColorBrush x:Key="Brush.BG7" Color="#FF836C2E"/>
|
||||
<SolidColorBrush x:Key="Brush.Border1" Color="#FF898989"/>
|
||||
<SolidColorBrush x:Key="Brush.Border2" Color="#FFCFCFCF"/>
|
||||
<SolidColorBrush x:Key="Brush.FG" Color="#FF1F1F1F"/>
|
||||
<SolidColorBrush x:Key="Brush.FG2" Color="DarkGray"/>
|
||||
<SolidColorBrush x:Key="Brush.Badge" Color="#FF8F8F8F"/>
|
||||
<SolidColorBrush x:Key="Brush.Accent1" Color="#FF4295FF"/>
|
||||
<SolidColorBrush x:Key="Brush.Accent2" Color="#4C007ACC"/>
|
||||
</ResourceDictionary>
|
27
src/SourceGit.csproj
Normal file
27
src/SourceGit.csproj
Normal file
|
@ -0,0 +1,27 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net46</TargetFramework>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ApplicationIcon>App.ico</ApplicationIcon>
|
||||
<Company>sourcegit</Company>
|
||||
<Description>OpenSource GIT client for Windows</Description>
|
||||
<Copyright>Copyright © sourcegit 2020. All rights reserved.</Copyright>
|
||||
<ApplicationManifest>App.manifest</ApplicationManifest>
|
||||
<Version>1.5</Version>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<StartupObject>SourceGit.App</StartupObject>
|
||||
<PackageProjectUrl>https://gitee.com/sourcegit/SourceGit.git</PackageProjectUrl>
|
||||
<RepositoryUrl>https://gitee.com/sourcegit/SourceGit.git</RepositoryUrl>
|
||||
<RepositoryType>Public</RepositoryType>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="App.ico" />
|
||||
</ItemGroup>
|
||||
</Project>
|
82
src/UI/About.xaml
Normal file
82
src/UI/About.xaml
Normal file
|
@ -0,0 +1,82 @@
|
|||
<Window x:Class="SourceGit.UI.About"
|
||||
x:Name="me"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
Height="280" Width="400"
|
||||
Title="About"
|
||||
WindowStartupLocation="CenterOwner" ResizeMode="NoResize">
|
||||
|
||||
<!-- Enable WindowChrome Feature -->
|
||||
<WindowChrome.WindowChrome>
|
||||
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="32"/>
|
||||
</WindowChrome.WindowChrome>
|
||||
|
||||
<!-- Window Layout -->
|
||||
<Border Background="{StaticResource Brush.BG1}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Titlebar -->
|
||||
<Grid Grid.Row="0" Background="{StaticResource Brush.BG4}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- LOGO -->
|
||||
<Path Width="20" Height="20" Margin="6,-1,2,0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Info}"/>
|
||||
|
||||
<!-- Title -->
|
||||
<Label Grid.Column="1" Content="ABOUT" FontWeight="Light"/>
|
||||
|
||||
<!-- Close Button -->
|
||||
<Button Click="Quit" Width="32" Grid.Column="3" WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
<Button.Style>
|
||||
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource Style.Button.HighlightHover}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="Red"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
|
||||
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Close}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="90"/>
|
||||
<RowDefinition Height="40"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="24"/>
|
||||
<RowDefinition Height="24"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,6,0,0">
|
||||
<Path Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Git}" Fill="#FFF05133"/>
|
||||
</StackPanel>
|
||||
|
||||
<Label Grid.Row="1" Content="SourceGit - OPEN SOURCE GIT CLIENT" HorizontalContentAlignment="Center" VerticalContentAlignment="Bottom" FontSize="18" FontWeight="Bold"/>
|
||||
<Label Grid.Row="2" Content="{Binding ElementName=me, Path=Version}" HorizontalContentAlignment="Center" FontSize="11"/>
|
||||
|
||||
<Label Grid.Row="3" HorizontalContentAlignment="Center" FontSize="11">
|
||||
<Hyperlink RequestNavigate="OpenSource" NavigateUri="https://gitee.com/sourcegit/SourceGit.git">
|
||||
<Run Text="https://gitee.com/sourcegit/SourceGit.git"/>
|
||||
</Hyperlink>
|
||||
</Label>
|
||||
|
||||
<Label Grid.Row="4" Content="Copyright © sourcegit 2020. All rights reserved." HorizontalContentAlignment="Center" FontSize="11"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
46
src/UI/About.xaml.cs
Normal file
46
src/UI/About.xaml.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Navigation;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// About dialog
|
||||
/// </summary>
|
||||
public partial class About : Window {
|
||||
|
||||
/// <summary>
|
||||
/// Current app version
|
||||
/// </summary>
|
||||
public string Version {
|
||||
get {
|
||||
return "VERSION : " + FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public About() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open source code link
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OpenSource(object sender, RequestNavigateEventArgs e) {
|
||||
Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close this dialog
|
||||
/// </summary>
|
||||
private void Quit(object sender, RoutedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
72
src/UI/AddSubmodule.xaml
Normal file
72
src/UI/AddSubmodule.xaml
Normal file
|
@ -0,0 +1,72 @@
|
|||
<UserControl x:Class="SourceGit.UI.AddSubmodule"
|
||||
x:Name="me"
|
||||
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:helpers="clr-namespace:SourceGit.Helpers"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="192" d:DesignWidth="500" Height="192" Width="500">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Add Submodule"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="URL :"/>
|
||||
<TextBox x:Name="txtRepoUrl" Grid.Row="2" Grid.Column="1"
|
||||
Height="24"
|
||||
helpers:TextBoxHelper.Placeholder="Git Repository URL">
|
||||
<TextBox.Text>
|
||||
<Binding Path="RepoURL" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
|
||||
<Binding.ValidationRules>
|
||||
<helpers:RemoteUriRule/>
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
|
||||
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="Parent Folder :"/>
|
||||
<TextBox Grid.Row="3" Grid.Column="1"
|
||||
x:Name="txtPath"
|
||||
Height="24"
|
||||
helpers:TextBoxHelper.Placeholder="Relative foler to store this module. Optional.">
|
||||
<TextBox.Text>
|
||||
<Binding Path="LocalPath" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
|
||||
<Binding.ValidationRules>
|
||||
<helpers:SubmodulePathRequiredRule/>
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
|
||||
<CheckBox Grid.Row="4" Grid.Column="1"
|
||||
x:Name="chkRecursive"
|
||||
IsChecked="True"
|
||||
Content="Fetch nested submodules"/>
|
||||
|
||||
<Grid Grid.Row="6" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
74
src/UI/AddSubmodule.xaml.cs
Normal file
74
src/UI/AddSubmodule.xaml.cs
Normal file
|
@ -0,0 +1,74 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Dialog to add new submodule.
|
||||
/// </summary>
|
||||
public partial class AddSubmodule : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
|
||||
/// <summary>
|
||||
/// Submodule's repository URL.
|
||||
/// </summary>
|
||||
public string RepoURL { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Submodule's relative path.
|
||||
/// </summary>
|
||||
public string LocalPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="opened"></param>
|
||||
public AddSubmodule(Git.Repository opened) {
|
||||
repo = opened;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show this dialog.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
public static void Show(Git.Repository repo) {
|
||||
repo.GetPopupManager()?.Show(new AddSubmodule(repo));
|
||||
}
|
||||
|
||||
#region EVENTS
|
||||
private void SelectFolder(object sender, RoutedEventArgs e) {
|
||||
var dialog = new System.Windows.Forms.FolderBrowserDialog();
|
||||
dialog.Description = "Select Folder To Clone Repository";
|
||||
dialog.SelectedPath = repo.Path;
|
||||
dialog.ShowNewFolderButton = true;
|
||||
|
||||
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
|
||||
txtPath.Text = dialog.SelectedPath;
|
||||
}
|
||||
}
|
||||
|
||||
private async void Sure(object sender, RoutedEventArgs e) {
|
||||
txtRepoUrl.GetBindingExpression(TextBox.TextProperty).UpdateSource();
|
||||
if (Validation.GetHasError(txtRepoUrl)) return;
|
||||
|
||||
txtPath.GetBindingExpression(TextBox.TextProperty).UpdateSource();
|
||||
if (Validation.GetHasError(txtPath)) return;
|
||||
|
||||
var recursive = chkRecursive.IsChecked == true;
|
||||
var popup = repo.GetPopupManager();
|
||||
|
||||
popup?.Lock();
|
||||
await Task.Run(() => repo.AddSubmodule(RepoURL, LocalPath, recursive, msg => {
|
||||
popup?.UpdateStatus(msg);
|
||||
}));
|
||||
popup?.Close(true);
|
||||
}
|
||||
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
repo.GetPopupManager()?.Close();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
88
src/UI/Apply.xaml
Normal file
88
src/UI/Apply.xaml
Normal file
|
@ -0,0 +1,88 @@
|
|||
<UserControl x:Class="SourceGit.UI.Apply"
|
||||
x:Name="me"
|
||||
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:helpers="clr-namespace:SourceGit.Helpers"
|
||||
xmlns:converters="clr-namespace:SourceGit.Converters"
|
||||
mc:Ignorable="d"
|
||||
Height="192" Width="500">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.Resources>
|
||||
<converters:InverseBool x:Key="InverseBool"/>
|
||||
</Grid.Resources>
|
||||
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Apply Patch"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Patch File :"/>
|
||||
<Grid Grid.Row="2" Grid.Column="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="28"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBox
|
||||
Grid.Column="0"
|
||||
x:Name="txtPatchFile"
|
||||
Height="24"
|
||||
helpers:TextBoxHelper.Placeholder="Select .patch file to apply">
|
||||
<TextBox.Text>
|
||||
<Binding Path="PatchFile" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
|
||||
<Binding.ValidationRules>
|
||||
<helpers:PatchFileRequiredRule/>
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
|
||||
<Button Grid.Column="1" Width="24" Height="24" Click="FindPatchFile" Padding="0" BorderThickness="1" Style="{StaticResource Style.Button.Bordered}">
|
||||
<Path Width="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="Whitespace :"/>
|
||||
<ComboBox x:Name="combWhitespaceOptions" Grid.Row="3" Grid.Column="1" VerticalAlignment="Center" IsEnabled="{Binding ElementName=chkIgnoreWhitespace, Path=IsChecked, Converter={StaticResource InverseBool}}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" Height="20">
|
||||
<Label Content="{Binding Name}" Padding="4,0"/>
|
||||
<Label Content="{Binding Desc}" Foreground="{StaticResource Brush.FG2}" FontSize="11" Padding="4,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<CheckBox Grid.Row="4" Grid.Column="1"
|
||||
x:Name="chkIgnoreWhitespace"
|
||||
IsChecked="True"
|
||||
Content="Ignore whitespace changes"/>
|
||||
|
||||
<Grid Grid.Row="6" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
104
src/UI/Apply.xaml.cs
Normal file
104
src/UI/Apply.xaml.cs
Normal file
|
@ -0,0 +1,104 @@
|
|||
using Microsoft.Win32;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Apply patch dialog
|
||||
/// </summary>
|
||||
public partial class Apply : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
|
||||
/// <summary>
|
||||
/// Whitespace option.
|
||||
/// </summary>
|
||||
public class WhitespaceOption {
|
||||
public string Name { get; set; }
|
||||
public string Desc { get; set; }
|
||||
public string Arg { get; set; }
|
||||
|
||||
public WhitespaceOption(string n, string d, string a) {
|
||||
Name = n;
|
||||
Desc = d;
|
||||
Arg = a;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Path of file to be patched.
|
||||
/// </summary>
|
||||
public string PatchFile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public Apply(Git.Repository opened) {
|
||||
repo = opened;
|
||||
InitializeComponent();
|
||||
|
||||
combWhitespaceOptions.ItemsSource = new WhitespaceOption[] {
|
||||
new WhitespaceOption("No Warn", "Turns off the trailing whitespace warning", "nowarn"),
|
||||
new WhitespaceOption("Warn", "Outputs warnings for a few such errors, but applies", "warn"),
|
||||
new WhitespaceOption("Error", "Raise errors and refuses to apply the patch", "error"),
|
||||
new WhitespaceOption("Error All", "Similar to 'error', but shows more", "error-all"),
|
||||
};
|
||||
combWhitespaceOptions.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show this dialog.
|
||||
/// </summary>
|
||||
/// <param name="opened"></param>
|
||||
public static void Show(Git.Repository opened) {
|
||||
opened.GetPopupManager()?.Show(new Apply(opened));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open file browser dialog for select a file to patch.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void FindPatchFile(object sender, RoutedEventArgs e) {
|
||||
var dialog = new OpenFileDialog();
|
||||
dialog.Filter = "Patch File|*.patch";
|
||||
dialog.Title = "Select Patch File";
|
||||
dialog.InitialDirectory = repo.Path;
|
||||
dialog.CheckFileExists = true;
|
||||
|
||||
if (dialog.ShowDialog() == true) {
|
||||
PatchFile = dialog.FileName;
|
||||
txtPatchFile.Text = dialog.FileName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start apply selected path.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private async void Start(object sender, RoutedEventArgs e) {
|
||||
txtPatchFile.GetBindingExpression(TextBox.TextProperty).UpdateSource();
|
||||
if (Validation.GetHasError(txtPatchFile)) return;
|
||||
|
||||
var popup = repo.GetPopupManager();
|
||||
popup?.Lock();
|
||||
|
||||
var mode = combWhitespaceOptions.SelectedItem as WhitespaceOption;
|
||||
var ignoreSpaceChanges = chkIgnoreWhitespace.IsChecked == true;
|
||||
await Task.Run(() => repo.Apply(PatchFile, ignoreSpaceChanges, mode.Arg));
|
||||
|
||||
popup?.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel options.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
repo.GetPopupManager()?.Close();
|
||||
}
|
||||
}
|
||||
}
|
208
src/UI/Blame.xaml
Normal file
208
src/UI/Blame.xaml
Normal file
|
@ -0,0 +1,208 @@
|
|||
<Window x:Class="SourceGit.UI.Blame"
|
||||
x:Name="me"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
Title="Blame"
|
||||
Height="600" Width="800">
|
||||
|
||||
<!-- Enable WindowChrome -->
|
||||
<WindowChrome.WindowChrome>
|
||||
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="32"/>
|
||||
</WindowChrome.WindowChrome>
|
||||
|
||||
<!-- Window Content -->
|
||||
<Border Background="{StaticResource Brush.BG1}">
|
||||
<!-- Fix Maximize BUG -->
|
||||
<Border.Style>
|
||||
<Style TargetType="{x:Type Border}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Maximized">
|
||||
<Setter Property="Margin" Value="6"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Normal">
|
||||
<Setter Property="Margin" Value="0"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="24"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title bar -->
|
||||
<Grid Grid.Row="0" Background="{StaticResource Brush.BG4}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Logo & TITLE -->
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<Path
|
||||
Width="20" Height="20" Margin="6,-1,2,0"
|
||||
Style="{StaticResource Style.Icon}"
|
||||
Data="{StaticResource Icon.Git}"
|
||||
Fill="#FFF05133"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True"
|
||||
MouseLeftButtonDown="LogoMouseButtonDown"/>
|
||||
<Label Content="SOURCE GIT - BLAME" FontWeight="Light"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Options -->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
<Button Click="Minimize" Width="32" Style="{StaticResource Style.Button.HighlightHover}">
|
||||
<Path Style="{StaticResource Style.WindowControlIcon}" Data="{StaticResource Icon.Minimize}"/>
|
||||
</Button>
|
||||
<Button Click="MaximizeOrRestore" Width="32" Style="{StaticResource Style.Button.HighlightHover}">
|
||||
<Path>
|
||||
<Path.Style>
|
||||
<Style TargetType="{x:Type Path}" BasedOn="{StaticResource Style.WindowControlIcon}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Maximized">
|
||||
<Setter Property="Data" Value="{StaticResource Icon.Restore}"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Normal">
|
||||
<Setter Property="Data" Value="{StaticResource Icon.Maximize}"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Path.Style>
|
||||
</Path>
|
||||
</Button>
|
||||
<Button Click="Quit" Width="32">
|
||||
<Button.Style>
|
||||
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource Style.Button.HighlightHover}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="Red"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
|
||||
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Close}"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Blame file -->
|
||||
<Border Grid.Row="1" Padding="2,0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Column="0" x:Name="blameFile" HorizontalAlignment="Left" FontSize="11" Foreground="{StaticResource Brush.FG2}" FontFamily="Consolas"/>
|
||||
<Label Grid.Column="1" HorizontalAlignment="Right" Foreground="{StaticResource Brush.FG2}" FontSize="11" Content="Use right mouse button to view commit information."/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Content -->
|
||||
<Border Grid.Row="2" BorderThickness="1" BorderBrush="{StaticResource Brush.Border2}" ClipToBounds="True">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="1"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBox
|
||||
x:Name="lineNumber"
|
||||
Grid.Column="0"
|
||||
AcceptsReturn="True"
|
||||
AcceptsTab="True"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
IsReadOnly="True"
|
||||
Margin="4,0,4,0"
|
||||
FontSize="13"
|
||||
HorizontalContentAlignment="Right"
|
||||
VerticalAlignment="Stretch"
|
||||
FontFamily="Consolas"/>
|
||||
|
||||
<Rectangle Grid.Column="1" Width="1" Fill="{StaticResource Brush.Border2}"/>
|
||||
|
||||
<RichTextBox
|
||||
x:Name="content"
|
||||
Grid.Column="2"
|
||||
AcceptsReturn="True"
|
||||
AcceptsTab="True"
|
||||
IsReadOnly="True"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
Foreground="{StaticResource Brush.FG}"
|
||||
Height="Auto"
|
||||
FontSize="13"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
RenderOptions.ClearTypeHint="Enabled"
|
||||
ScrollViewer.ScrollChanged="SyncScrollChanged"
|
||||
PreviewMouseWheel="MouseWheelOnContent"
|
||||
SizeChanged="ContentSizeChanged"
|
||||
SelectionChanged="ContentSelectionChanged"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
FontFamily="Consolas">
|
||||
<RichTextBox.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Command="ApplicationCommands.Copy"/>
|
||||
</ContextMenu>
|
||||
</RichTextBox.ContextMenu>
|
||||
<FlowDocument PageWidth="0"/>
|
||||
</RichTextBox>
|
||||
|
||||
<!-- Loading tip -->
|
||||
<Path x:Name="loading" Grid.ColumnSpan="5" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="0"/>
|
||||
</Path.RenderTransform>
|
||||
|
||||
<Path.Style>
|
||||
<Style BasedOn="{StaticResource Style.Icon}" TargetType="{x:Type Path}">
|
||||
<Setter Property="Width" Value="48"/>
|
||||
<Setter Property="Height" Value="48"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Fill" Value="{StaticResource Brush.FG2}"/>
|
||||
</Style>
|
||||
</Path.Style>
|
||||
</Path>
|
||||
|
||||
<!-- Popup to show commit info -->
|
||||
<Popup x:Name="popup" Grid.ColumnSpan="5" Placement="MousePoint" IsOpen="False" StaysOpen="False" Focusable="True">
|
||||
<Border BorderBrush="{StaticResource Brush.Accent1}" BorderThickness="1" Background="{StaticResource Brush.BG1}">
|
||||
<Grid Margin="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="24"/>
|
||||
<RowDefinition Height="24"/>
|
||||
<RowDefinition Height="24"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="COMMIT SHA" Foreground="{StaticResource Brush.FG2}"/>
|
||||
<Label Grid.Row="0" Grid.Column="1" x:Name="commitID"/>
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="AUTHOR" Foreground="{StaticResource Brush.FG2}"/>
|
||||
<Label Grid.Row="1" Grid.Column="1" x:Name="authorName"/>
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="MODIFY TIME" Foreground="{StaticResource Brush.FG2}"/>
|
||||
<Label Grid.Row="2" Grid.Column="1" x:Name="authorTime"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
240
src/UI/Blame.xaml.cs
Normal file
240
src/UI/Blame.xaml.cs
Normal file
|
@ -0,0 +1,240 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
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.Media.Animation;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Viewer to show git-blame
|
||||
/// </summary>
|
||||
public partial class Blame : Window {
|
||||
|
||||
/// <summary>
|
||||
/// Background color for blocks.
|
||||
/// </summary>
|
||||
public static Brush[] BG = new Brush[] {
|
||||
Brushes.Transparent,
|
||||
new SolidColorBrush(Color.FromArgb(128, 0, 0, 0))
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="file"></param>
|
||||
/// <param name="revision"></param>
|
||||
public Blame(Git.Repository repo, string file, string revision) {
|
||||
InitializeComponent();
|
||||
|
||||
double minWidth = content.ActualWidth;
|
||||
|
||||
// Move to center.
|
||||
var parent = App.Current.MainWindow;
|
||||
Left = parent.Left + (parent.Width - Width) * 0.5;
|
||||
Top = parent.Top + (parent.Height - Height) * 0.5;
|
||||
|
||||
// Show loading.
|
||||
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
|
||||
anim.RepeatBehavior = RepeatBehavior.Forever;
|
||||
loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
|
||||
loading.Visibility = Visibility.Visible;
|
||||
|
||||
// Layout content
|
||||
blameFile.Content = $"{file}@{revision.Substring(0, 8)}";
|
||||
Task.Run(() => {
|
||||
var blame = repo.BlameFile(file, revision);
|
||||
|
||||
Dispatcher.Invoke(() => {
|
||||
content.Document.Blocks.Clear();
|
||||
|
||||
if (blame.IsBinary) {
|
||||
lineNumber.Text = "0";
|
||||
|
||||
Paragraph p = new Paragraph(new Run("BINARY FILE BLAME NOT SUPPORTED!!!"));
|
||||
p.Margin = new Thickness(0);
|
||||
p.Padding = new Thickness(0);
|
||||
p.LineHeight = 1;
|
||||
p.Background = Brushes.Transparent;
|
||||
p.Foreground = FindResource("Brush.FG") as SolidColorBrush;
|
||||
p.FontStyle = FontStyles.Normal;
|
||||
|
||||
content.Document.Blocks.Add(p);
|
||||
} else {
|
||||
List<string> numbers = new List<string>();
|
||||
for (int i = 0; i < blame.LineCount; i++) numbers.Add(i.ToString());
|
||||
lineNumber.Text = string.Join("\n", numbers);
|
||||
numbers.Clear();
|
||||
|
||||
for (int i = 0; i < blame.Blocks.Count; i++) {
|
||||
var frag = blame.Blocks[i];
|
||||
var idx = i;
|
||||
|
||||
Paragraph p = new Paragraph(new Run(frag.Content));
|
||||
p.DataContext = frag;
|
||||
p.Margin = new Thickness(0);
|
||||
p.Padding = new Thickness(0);
|
||||
p.LineHeight = 1;
|
||||
p.Background = BG[i % 2];
|
||||
p.Foreground = FindResource("Brush.FG") as SolidColorBrush;
|
||||
p.FontStyle = FontStyles.Normal;
|
||||
p.ContextMenuOpening += (sender, ev) => {
|
||||
if (!content.Selection.IsEmpty) return;
|
||||
|
||||
Hyperlink link = new Hyperlink(new Run(frag.CommitSHA));
|
||||
link.ToolTip = "CLICK TO GO";
|
||||
link.Click += (o, e) => {
|
||||
repo.OnNavigateCommit?.Invoke(frag.CommitSHA);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
foreach (var block in content.Document.Blocks) {
|
||||
var paragraph = block as Paragraph;
|
||||
if ((paragraph.DataContext as Git.Blame.Block).CommitSHA == frag.CommitSHA) {
|
||||
paragraph.Background = Brushes.Green;
|
||||
} else {
|
||||
paragraph.Background = BG[i % 2];
|
||||
}
|
||||
}
|
||||
|
||||
commitID.Content = link;
|
||||
authorName.Content = frag.Author;
|
||||
authorTime.Content = frag.Time;
|
||||
popup.IsOpen = true;
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var formatter = new FormattedText(
|
||||
frag.Content,
|
||||
CultureInfo.CurrentUICulture,
|
||||
FlowDirection.LeftToRight,
|
||||
new Typeface(content.FontFamily, p.FontStyle, p.FontWeight, p.FontStretch),
|
||||
content.FontSize,
|
||||
Brushes.Black,
|
||||
new NumberSubstitution(),
|
||||
TextFormattingMode.Ideal);
|
||||
if (minWidth < formatter.Width) {
|
||||
content.Document.PageWidth = formatter.Width + 16;
|
||||
minWidth = formatter.Width;
|
||||
}
|
||||
|
||||
content.Document.Blocks.Add(p);
|
||||
}
|
||||
}
|
||||
|
||||
// Hide loading.
|
||||
loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
||||
loading.Visibility = Visibility.Collapsed;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Click logo
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void LogoMouseButtonDown(object sender, MouseButtonEventArgs e) {
|
||||
var element = e.OriginalSource as FrameworkElement;
|
||||
if (element == null) return;
|
||||
|
||||
var pos = PointToScreen(new Point(0, 33));
|
||||
SystemCommands.ShowSystemMenu(this, pos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Minimize
|
||||
/// </summary>
|
||||
private void Minimize(object sender, RoutedEventArgs e) {
|
||||
SystemCommands.MinimizeWindow(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maximize/Restore
|
||||
/// </summary>
|
||||
private void MaximizeOrRestore(object sender, RoutedEventArgs e) {
|
||||
if (WindowState == WindowState.Normal) {
|
||||
SystemCommands.MaximizeWindow(this);
|
||||
} else {
|
||||
SystemCommands.RestoreWindow(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quit
|
||||
/// </summary>
|
||||
private void Quit(object sender, RoutedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sync scroll
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void SyncScrollChanged(object sender, ScrollChangedEventArgs e) {
|
||||
if (e.VerticalChange != 0) {
|
||||
var margin = new Thickness(4, -e.VerticalOffset, 4, 0);
|
||||
lineNumber.Margin = margin;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mouse wheel
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void MouseWheelOnContent(object sender, MouseWheelEventArgs e) {
|
||||
if (e.Delta > 0) {
|
||||
content.LineUp();
|
||||
} else {
|
||||
content.LineDown();
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Content size changed.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void ContentSizeChanged(object sender, SizeChangedEventArgs e) {
|
||||
if (content.Document.PageWidth < content.ActualWidth) {
|
||||
content.Document.PageWidth = content.ActualWidth;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto scroll when selection changed.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void ContentSelectionChanged(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
45
src/UI/CherryPick.xaml
Normal file
45
src/UI/CherryPick.xaml
Normal file
|
@ -0,0 +1,45 @@
|
|||
<UserControl x:Class="SourceGit.UI.CherryPick"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="160" d:DesignWidth="500" Height="160" Width="500">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Cherry Pick"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Commit :"/>
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Commit}" Margin="4,0"/>
|
||||
<Label x:Name="desc"/>
|
||||
</StackPanel>
|
||||
|
||||
<CheckBox Grid.Row="3" Grid.Column="1" x:Name="chkCommitChanges" IsChecked="True" Content="Commit the changes"/>
|
||||
|
||||
<Grid Grid.Row="5" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
54
src/UI/CherryPick.xaml.cs
Normal file
54
src/UI/CherryPick.xaml.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Cherry pick commit dialog.
|
||||
/// </summary>
|
||||
public partial class CherryPick : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
private string commitSHA = null;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="opened"></param>
|
||||
/// <param name="commit"></param>
|
||||
public CherryPick(Git.Repository opened, Git.Commit commit) {
|
||||
InitializeComponent();
|
||||
|
||||
repo = opened;
|
||||
commitSHA = commit.SHA;
|
||||
desc.Content = $"{commit.ShortSHA} {commit.Subject}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display this dialog.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="commit"></param>
|
||||
public static void Show(Git.Repository repo, Git.Commit commit) {
|
||||
repo.GetPopupManager()?.Show(new CherryPick(repo, commit));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start pick.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Start(object sender, RoutedEventArgs e) {
|
||||
repo.CherryPick(commitSHA, chkCommitChanges.IsChecked != true);
|
||||
repo.GetPopupManager()?.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
repo.GetPopupManager()?.Close();
|
||||
}
|
||||
}
|
||||
}
|
95
src/UI/Clone.xaml
Normal file
95
src/UI/Clone.xaml
Normal file
|
@ -0,0 +1,95 @@
|
|||
<UserControl x:Class="SourceGit.UI.Clone"
|
||||
x:Name="me"
|
||||
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:helpers="clr-namespace:SourceGit.Helpers"
|
||||
mc:Ignorable="d"
|
||||
Width="500" Height="224">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Clone Remote Repository"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Repository URL :"/>
|
||||
<TextBox x:Name="txtUrl" Grid.Row="2" Grid.Column="1"
|
||||
Height="24"
|
||||
helpers:TextBoxHelper.Placeholder="Git Repository URL">
|
||||
<TextBox.Text>
|
||||
<Binding Path="RemoteUri" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
|
||||
<Binding.ValidationRules>
|
||||
<helpers:RemoteUriRule/>
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
|
||||
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="Parent Folder :"/>
|
||||
<Grid Grid.Row="3" Grid.Column="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="28"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBox Grid.Column="0"
|
||||
x:Name="txtParentFolder"
|
||||
Height="24"
|
||||
helpers:TextBoxHelper.Placeholder="Folder to contain this repository">
|
||||
<TextBox.Text>
|
||||
<Binding Path="ParentFolder" ElementName="me" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay">
|
||||
<Binding.ValidationRules>
|
||||
<helpers:CloneFolderRule/>
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
<Button Grid.Column="1" Width="24" Height="24" Padding="0" BorderThickness="1" Click="SelectParentFolder" Style="{StaticResource Style.Button.Bordered}">
|
||||
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Label Grid.Row="4" Grid.Column="0" HorizontalAlignment="Right" Content="Local Name :"/>
|
||||
<TextBox Grid.Row="4" Grid.Column="1"
|
||||
VerticalContentAlignment="Center"
|
||||
Height="24"
|
||||
helpers:TextBoxHelper.Placeholder="Repository name. Optional."
|
||||
Text="{Binding LocalName, ElementName=me, Mode=TwoWay}">
|
||||
</TextBox>
|
||||
|
||||
<Label Grid.Row="5" Grid.Column="0" HorizontalAlignment="Right" Content="Remote Name :"/>
|
||||
<TextBox Grid.Row="5" Grid.Column="1"
|
||||
VerticalContentAlignment="Center"
|
||||
Height="24"
|
||||
helpers:TextBoxHelper.Placeholder="Remote name. Optional."
|
||||
Text="{Binding RemoteName, ElementName=me, Mode=TwoWay}">
|
||||
</TextBox>
|
||||
|
||||
<Grid Grid.Row="7" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
112
src/UI/Clone.xaml.cs
Normal file
112
src/UI/Clone.xaml.cs
Normal file
|
@ -0,0 +1,112 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Clone dialog.
|
||||
/// </summary>
|
||||
public partial class Clone : UserControl {
|
||||
private PopupManager popup = null;
|
||||
|
||||
/// <summary>
|
||||
/// Remote repository
|
||||
/// </summary>
|
||||
public string RemoteUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parent folder.
|
||||
/// </summary>
|
||||
public string ParentFolder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Local name.
|
||||
/// </summary>
|
||||
public string LocalName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Remote name.
|
||||
/// </summary>
|
||||
public string RemoteName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public Clone(PopupManager mgr) {
|
||||
ParentFolder = App.Preference.GitDefaultCloneDir;
|
||||
popup = mgr;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select parent folder.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void SelectParentFolder(object sender, RoutedEventArgs e) {
|
||||
var dialog = new System.Windows.Forms.FolderBrowserDialog();
|
||||
dialog.Description = "Git Repository URL";
|
||||
dialog.RootFolder = Environment.SpecialFolder.MyComputer;
|
||||
dialog.ShowNewFolderButton = true;
|
||||
|
||||
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
|
||||
txtParentFolder.Text = dialog.SelectedPath;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start clone
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private async void Start(object sender, RoutedEventArgs e) {
|
||||
txtUrl.GetBindingExpression(TextBox.TextProperty).UpdateSource();
|
||||
if (Validation.GetHasError(txtUrl)) return;
|
||||
|
||||
txtParentFolder.GetBindingExpression(TextBox.TextProperty).UpdateSource();
|
||||
if (Validation.GetHasError(txtParentFolder)) return;
|
||||
|
||||
string repoName;
|
||||
if (string.IsNullOrWhiteSpace(LocalName)) {
|
||||
var from = RemoteUri.LastIndexOfAny(new char[] { '\\', '/' });
|
||||
if (from <= 0) return;
|
||||
|
||||
var name = RemoteUri.Substring(from + 1);
|
||||
repoName = name.Replace(".git", "");
|
||||
} else {
|
||||
repoName = LocalName;
|
||||
}
|
||||
|
||||
string rName;
|
||||
if (string.IsNullOrWhiteSpace(RemoteName)){
|
||||
rName = null;
|
||||
} else {
|
||||
rName = RemoteName;
|
||||
}
|
||||
|
||||
popup.Lock();
|
||||
|
||||
var repo = await Task.Run(() => {
|
||||
return Git.Repository.Clone(RemoteUri, ParentFolder, rName, repoName, popup.UpdateStatus);
|
||||
});
|
||||
|
||||
if (repo == null) {
|
||||
popup.Unlock();
|
||||
} else {
|
||||
popup.Close(true);
|
||||
repo.Open();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
popup.Close();
|
||||
}
|
||||
}
|
||||
}
|
420
src/UI/CommitViewer.xaml
Normal file
420
src/UI/CommitViewer.xaml
Normal file
|
@ -0,0 +1,420 @@
|
|||
<UserControl x:Class="SourceGit.UI.CommitViewer"
|
||||
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:source="clr-namespace:SourceGit"
|
||||
xmlns:local="clr-namespace:SourceGit.UI"
|
||||
xmlns:git="clr-namespace:SourceGit.Git"
|
||||
xmlns:converters="clr-namespace:SourceGit.Converters"
|
||||
xmlns:helpers="clr-namespace:SourceGit.Helpers"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Unloaded="Cleanup">
|
||||
<TabControl>
|
||||
<TabItem Header="INFORMATION">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition x:Name="committerRow" Height="Auto"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="96"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="96"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- SHA -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="SHA" HorizontalAlignment="Right" Opacity=".6"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1"
|
||||
x:Name="SHA"
|
||||
IsReadOnly="True"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Margin="11,0,0,0"/>
|
||||
|
||||
<!-- Refs -->
|
||||
<Label x:Name="lblRefs" Grid.Row="0" Grid.Column="2" Content="REFS" HorizontalAlignment="Right" Opacity=".6"/>
|
||||
<ItemsControl Grid.Row="0" Grid.Column="3" x:Name="refs" Margin="8,0,0,0">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border x:Name="BG" Height="16" Margin="2">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="18"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0" Background="{StaticResource Brush.BG5}">
|
||||
<Path x:Name="Icon" Width="8" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
|
||||
</Border>
|
||||
|
||||
<Label x:Name="Name" Grid.Column="1" Content="{Binding Name}" FontSize="11" Padding="4,0" Foreground="Black"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.Tag}">
|
||||
<Setter TargetName="BG" Property="Background" Value="#FF02C302"/>
|
||||
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Tag}"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.LocalBranchHead}">
|
||||
<Setter TargetName="BG" Property="Background" Value="#FFFFB835"/>
|
||||
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Branch}"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.RemoteBranchHead}">
|
||||
<Setter TargetName="BG" Property="Background" Value="#FFFFB835"/>
|
||||
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Remote}"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.CurrentBranchHead}">
|
||||
<Setter TargetName="BG" Property="Background" Value="#FFFFB835"/>
|
||||
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Check}"/>
|
||||
<Setter TargetName="Icon" Property="Fill" Value="Orange"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- PARENTS -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="PARENTS" HorizontalAlignment="Right" Opacity=".6"/>
|
||||
<ItemsControl Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" x:Name="parents" Margin="8,0,0,0">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Label Margin="0,0,8,0">
|
||||
<Hyperlink
|
||||
RequestNavigate="NavigateParent"
|
||||
NavigateUri="{Binding .}"
|
||||
ToolTip="NAVIGATE TO COMMIT">
|
||||
<Run Text="{Binding .}"/>
|
||||
</Hyperlink>
|
||||
</Label>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- AUTHOR -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="AUTHOR" HorizontalAlignment="Right" Opacity=".6"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="1" x:Name="author"
|
||||
IsReadOnly="True"
|
||||
Background="Transparent"
|
||||
AcceptsReturn="True"
|
||||
BorderThickness="0"
|
||||
Margin="11,0,0,0"/>
|
||||
|
||||
<!-- AUTHOR TIME -->
|
||||
<Label Grid.Row="2" Grid.Column="2" Content="AUTHOR TIME" HorizontalAlignment="Right" Opacity=".6"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="3" Grid.ColumnSpan="3" x:Name="authorTime"
|
||||
IsReadOnly="True"
|
||||
Background="Transparent"
|
||||
AcceptsReturn="True"
|
||||
BorderThickness="0"
|
||||
Margin="8,0,0,0"/>
|
||||
|
||||
<!-- COMMITTER -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="COMMITTER" HorizontalAlignment="Right" Opacity=".6"/>
|
||||
<TextBox Grid.Row="3" Grid.Column="1" x:Name="committer"
|
||||
IsReadOnly="True"
|
||||
Background="Transparent"
|
||||
AcceptsReturn="True"
|
||||
BorderThickness="0"
|
||||
Margin="11,0,0,0"/>
|
||||
|
||||
<!-- COMMIT TIME -->
|
||||
<Label Grid.Row="3" Grid.Column="2" Content="COMMIT TIME" HorizontalAlignment="Right" Opacity=".6"/>
|
||||
<TextBox Grid.Row="3" Grid.Column="3" Grid.ColumnSpan="3" x:Name="committerTime"
|
||||
IsReadOnly="True"
|
||||
Background="Transparent"
|
||||
AcceptsReturn="True"
|
||||
BorderThickness="0"
|
||||
Margin="8,0,0,0"/>
|
||||
|
||||
<Rectangle Grid.Row="4" Grid.ColumnSpan="4" Height="1" Margin="8,0" Fill="{StaticResource Brush.Border2}"/>
|
||||
|
||||
<!-- SUBJECT -->
|
||||
<Label Grid.Row="5" Grid.Column="0" Content="SUBJECT" HorizontalAlignment="Right" Opacity=".6"/>
|
||||
<TextBox Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="3"
|
||||
x:Name="subject"
|
||||
IsReadOnly="True"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Margin="8,0,16,0"/>
|
||||
|
||||
<!-- MESSAGE -->
|
||||
<Label Grid.Row="6" Grid.Column="0" Content="DESCRIPTION" HorizontalAlignment="Right" VerticalAlignment="Top" Opacity=".6"/>
|
||||
<TextBox Grid.Row="6" Grid.Column="1" Grid.ColumnSpan="3"
|
||||
x:Name="message"
|
||||
IsReadOnly="True"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="11"
|
||||
Margin="11,8,0,0"/>
|
||||
|
||||
<Rectangle Grid.Row="7" Grid.ColumnSpan="4" Height="1" Margin="8,0" Fill="{StaticResource Brush.Border2}"/>
|
||||
|
||||
<!-- CHANGELIST -->
|
||||
<Label Grid.Row="8" Grid.Column="0" Content="CHANGED" HorizontalAlignment="Right" VerticalAlignment="Top" Opacity=".6"/>
|
||||
<DataGrid
|
||||
Grid.Row="8"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="3"
|
||||
x:Name="changeList1"
|
||||
RowHeight="20"
|
||||
Margin="11,2,0,2">
|
||||
<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"/>
|
||||
<EventSetter Event="MouseDoubleClick" Handler="ChangeListMouseDoubleClick"/>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<!-- CHANGES -->
|
||||
<TabItem Header="CHANGES">
|
||||
<Grid>
|
||||
<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>
|
||||
</TabItem>
|
||||
|
||||
<!-- FILE TREE -->
|
||||
<TabItem Header="FILES">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200" MinWidth="200" MaxWidth="400"/>
|
||||
<ColumnDefinition Width="1"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0" Margin="2" Background="{StaticResource Brush.BG2}">
|
||||
<TreeView x:Name="fileTree" SelectedItemChanged="FileTreeItemSelected" FontFamily="Consolas" PreviewMouseWheel="TreeMouseWheel">
|
||||
<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">
|
||||
<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="6,0,0,0" FontSize="11"/>
|
||||
</StackPanel>
|
||||
|
||||
<HierarchicalDataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding IsFile}" Value="True">
|
||||
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.File}"/>
|
||||
<Setter TargetName="icon" Property="Fill" Value="{StaticResource Brush.FG}"/>
|
||||
<Setter TargetName="icon" Property="Opacity" Value=".75"/>
|
||||
</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>
|
||||
</Border>
|
||||
|
||||
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"/>
|
||||
|
||||
<Border Grid.Column="2" BorderThickness="1" Margin="2,0" BorderBrush="{StaticResource Brush.Border2}">
|
||||
<Grid>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
||||
<TextBlock
|
||||
FontSize="10pt"
|
||||
FontFamily="Consolas"
|
||||
Padding="8"
|
||||
Opacity="0.8"
|
||||
Background="{StaticResource Brush.BG2}"
|
||||
Foreground="{StaticResource Brush.FG}"
|
||||
x:Name="filePreview"/>
|
||||
</ScrollViewer>
|
||||
|
||||
<StackPanel x:Name="maskRevision" Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Collapsed">
|
||||
<Path x:Name="iconPreviewRevision" Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Submodule}" Fill="{StaticResource Brush.FG2}"/>
|
||||
<Label x:Name="txtPreviewRevision" Margin="0,16,0,0" FontFamily="Consolas" FontSize="18" FontWeight="UltraBold" HorizontalAlignment="Center" Foreground="{StaticResource Brush.FG2}"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel x:Name="maskPreviewNotSupported" Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center" Visibility="Collapsed">
|
||||
<Path Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Info}" Fill="{StaticResource Brush.FG2}"/>
|
||||
<Label Margin="0,16,0,0" Content="BINARY FILE DETECTED" FontFamily="Consolas" FontSize="18" FontWeight="UltraBold" HorizontalAlignment="Center" Foreground="{StaticResource Brush.FG2}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</UserControl>
|
504
src/UI/CommitViewer.xaml.cs
Normal file
504
src/UI/CommitViewer.xaml.cs
Normal file
|
@ -0,0 +1,504 @@
|
|||
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;
|
||||
using System.Windows.Navigation;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Commit detail viewer
|
||||
/// </summary>
|
||||
public partial class CommitViewer : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
private Git.Commit commit = 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 CommitViewer() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
#region DATA
|
||||
public void SetData(Git.Repository opened, Git.Commit selected) {
|
||||
repo = opened;
|
||||
commit = selected;
|
||||
|
||||
SetBaseInfo(commit);
|
||||
|
||||
Task.Run(() => {
|
||||
cachedChanges.Clear();
|
||||
cachedChanges = commit.GetChanges(repo);
|
||||
|
||||
Dispatcher.Invoke(() => {
|
||||
changeList1.ItemsSource = null;
|
||||
changeList1.ItemsSource = cachedChanges;
|
||||
});
|
||||
|
||||
LayoutChanges();
|
||||
SetRevisionFiles(commit.GetFiles(repo));
|
||||
});
|
||||
}
|
||||
|
||||
private void Cleanup(object sender, RoutedEventArgs e) {
|
||||
fileTree.ItemsSource = null;
|
||||
changeList1.ItemsSource = null;
|
||||
changeList2.ItemsSource = null;
|
||||
displayChanges.Clear();
|
||||
cachedChanges.Clear();
|
||||
diffViewer.Reset();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BASE_INFO
|
||||
private void SetBaseInfo(Git.Commit commit) {
|
||||
var parentIds = new List<string>();
|
||||
foreach (var p in commit.Parents) parentIds.Add(p.Substring(0, 8));
|
||||
|
||||
SHA.Text = commit.SHA;
|
||||
refs.ItemsSource = commit.Decorators;
|
||||
parents.ItemsSource = parentIds;
|
||||
author.Text = $"{commit.Author.Name} <{commit.Author.Email}>";
|
||||
authorTime.Text = commit.Author.Time;
|
||||
committer.Text = $"{commit.Committer.Name} <{commit.Committer.Email}>";
|
||||
committerTime.Text = commit.Committer.Time;
|
||||
subject.Text = commit.Subject;
|
||||
message.Text = commit.Message.Trim();
|
||||
|
||||
if (commit.Decorators.Count == 0) lblRefs.Visibility = Visibility.Collapsed;
|
||||
else lblRefs.Visibility = Visibility.Visible;
|
||||
|
||||
if (commit.Committer.Email == commit.Author.Email && commit.Committer.Time == commit.Author.Time) {
|
||||
committerRow.Height = new GridLength(0);
|
||||
} else {
|
||||
committerRow.Height = GridLength.Auto;
|
||||
}
|
||||
}
|
||||
|
||||
private void NavigateParent(object sender, RequestNavigateEventArgs e) {
|
||||
repo.OnNavigateCommit?.Invoke(e.Uri.OriginalString);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region CHANGES
|
||||
private void LayoutChanges() {
|
||||
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(() => LayoutChanges());
|
||||
}
|
||||
|
||||
private void ChangeTreeItemSelected(object sender, RoutedPropertyChangedEventArgs<object> e) {
|
||||
diffViewer.Reset();
|
||||
|
||||
var node = e.NewValue as Node;
|
||||
if (node == null || !node.IsFile) return;
|
||||
|
||||
var start = $"{commit.SHA}^";
|
||||
if (commit.Parents.Count == 0) {
|
||||
start = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
||||
}
|
||||
|
||||
diffViewer.Diff(repo, new DiffViewer.Option() {
|
||||
RevisionRange = new string[] { start, commit.SHA },
|
||||
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;
|
||||
|
||||
var start = $"{commit.SHA}^";
|
||||
if (commit.Parents.Count == 0) {
|
||||
start = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
||||
}
|
||||
|
||||
diffViewer.Diff(repo, new DiffViewer.Option() {
|
||||
RevisionRange = new string[] { start, commit.SHA },
|
||||
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 blame = new MenuItem();
|
||||
blame.Header = "Blame";
|
||||
blame.Click += (obj, ev) => {
|
||||
Blame viewer = new Blame(repo, path, commit.SHA);
|
||||
viewer.Show();
|
||||
};
|
||||
menu.Items.Add(blame);
|
||||
|
||||
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 saveAs = new MenuItem();
|
||||
saveAs.Header = "Save As ...";
|
||||
saveAs.Click += (obj, ev) => {
|
||||
var dialog = new System.Windows.Forms.FolderBrowserDialog();
|
||||
dialog.Description = change.Path;
|
||||
dialog.ShowNewFolderButton = true;
|
||||
|
||||
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
|
||||
var savePath = Path.Combine(dialog.SelectedPath, Path.GetFileName(path));
|
||||
repo.RunAndRedirect($"show {commit.SHA}:\"{path}\"", savePath);
|
||||
}
|
||||
};
|
||||
menu.Items.Add(saveAs);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region FILES
|
||||
private void SetRevisionFiles(List<Git.Commit.Object> files) {
|
||||
List<Node> fileTreeSource = new List<Node>();
|
||||
Dictionary<string, Node> folders = new Dictionary<string, Node>();
|
||||
|
||||
foreach (var obj in files) {
|
||||
var sepIdx = obj.Path.IndexOf("/");
|
||||
if (sepIdx == -1) {
|
||||
Node node = new Node();
|
||||
node.FilePath = obj.Path;
|
||||
node.Name = obj.Path;
|
||||
node.IsFile = true;
|
||||
node.IsNodeExpanded = false;
|
||||
node.CommitObject = obj;
|
||||
fileTreeSource.Add(node);
|
||||
} else {
|
||||
Node lastFolder = null;
|
||||
var start = 0;
|
||||
|
||||
while (sepIdx != -1) {
|
||||
var folder = obj.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 = false;
|
||||
fileTreeSource.Add(lastFolder);
|
||||
folders.Add(folder, lastFolder);
|
||||
} else {
|
||||
var folderNode = new Node();
|
||||
folderNode.FilePath = folder;
|
||||
folderNode.Name = folder.Substring(start);
|
||||
folderNode.IsNodeExpanded = false;
|
||||
folders.Add(folder, folderNode);
|
||||
lastFolder.Children.Add(folderNode);
|
||||
lastFolder = folderNode;
|
||||
}
|
||||
|
||||
start = sepIdx + 1;
|
||||
sepIdx = obj.Path.IndexOf('/', start);
|
||||
}
|
||||
|
||||
Node node = new Node();
|
||||
node.FilePath = obj.Path;
|
||||
node.Name = obj.Path.Substring(start);
|
||||
node.IsFile = true;
|
||||
node.IsNodeExpanded = false;
|
||||
node.CommitObject = obj;
|
||||
lastFolder.Children.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
folders.Clear();
|
||||
SortTreeNodes(fileTreeSource);
|
||||
|
||||
Dispatcher.Invoke(() => {
|
||||
fileTree.ItemsSource = fileTreeSource;
|
||||
filePreview.Text = "";
|
||||
});
|
||||
}
|
||||
|
||||
private async void FileTreeItemSelected(object sender, RoutedPropertyChangedEventArgs<object> e) {
|
||||
filePreview.Text = "";
|
||||
maskPreviewNotSupported.Visibility = Visibility.Collapsed;
|
||||
maskRevision.Visibility = Visibility.Collapsed;
|
||||
|
||||
var node = e.NewValue as Node;
|
||||
if (node == null || !node.IsFile || node.CommitObject == null) return;
|
||||
|
||||
switch (node.CommitObject.Kind) {
|
||||
case Git.Commit.Object.Type.Blob:
|
||||
await Task.Run(() => {
|
||||
var isBinary = false;
|
||||
var data = commit.GetTextFileContent(repo, node.FilePath, out isBinary);
|
||||
|
||||
if (isBinary) {
|
||||
Dispatcher.Invoke(() => maskPreviewNotSupported.Visibility = Visibility.Visible);
|
||||
} else {
|
||||
Dispatcher.Invoke(() => filePreview.Text = data);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case Git.Commit.Object.Type.Tag:
|
||||
maskRevision.Visibility = Visibility.Visible;
|
||||
iconPreviewRevision.Data = FindResource("Icon.Tag") as Geometry;
|
||||
txtPreviewRevision.Content = "TAG: " + node.CommitObject.SHA;
|
||||
break;
|
||||
case Git.Commit.Object.Type.Commit:
|
||||
maskRevision.Visibility = Visibility.Visible;
|
||||
iconPreviewRevision.Data = FindResource("Icon.Submodule") as Geometry;
|
||||
txtPreviewRevision.Content = "SUBMODULE: " + node.CommitObject.SHA;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region TREE_COMMON
|
||||
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 blame = new MenuItem();
|
||||
blame.Header = "Blame";
|
||||
blame.Click += (obj, ev) => {
|
||||
Blame viewer = new Blame(repo, node.FilePath, commit.SHA);
|
||||
viewer.Show();
|
||||
};
|
||||
menu.Items.Add(blame);
|
||||
|
||||
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 saveAs = new MenuItem();
|
||||
saveAs.Header = "Save As ...";
|
||||
saveAs.IsEnabled = node.CommitObject == null || node.CommitObject.Kind == Git.Commit.Object.Type.Blob;
|
||||
saveAs.Click += (obj, ev) => {
|
||||
var dialog = new System.Windows.Forms.FolderBrowserDialog();
|
||||
dialog.Description = node.FilePath;
|
||||
dialog.ShowNewFolderButton = true;
|
||||
|
||||
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
|
||||
var path = Path.Combine(dialog.SelectedPath, node.Name);
|
||||
repo.RunAndRedirect($"show {commit.SHA}:\"{node.FilePath}\"", path);
|
||||
}
|
||||
};
|
||||
menu.Items.Add(saveAs);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
71
src/UI/Configure.xaml
Normal file
71
src/UI/Configure.xaml
Normal file
|
@ -0,0 +1,71 @@
|
|||
<UserControl x:Class="SourceGit.UI.Configure"
|
||||
x:Name="me"
|
||||
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:helpers="clr-namespace:SourceGit.Helpers"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="500" Width="500" Height="314">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="36"/>
|
||||
<RowDefinition Height="8"/>
|
||||
<RowDefinition Height="28"/>
|
||||
<RowDefinition Height="28"/>
|
||||
<RowDefinition Height="18"/>
|
||||
<RowDefinition Height="36"/>
|
||||
<RowDefinition Height="8"/>
|
||||
<RowDefinition Height="102"/>
|
||||
<RowDefinition Height="18"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 仓库帐号 -->
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="2" Content="CREDENTIAL" FontSize="16" FontWeight="DemiBold" Opacity=".85"/>
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="User : " HorizontalAlignment="Right"/>
|
||||
<TextBox
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Height="24"
|
||||
Text="{Binding ElementName=me, Path=UserName, Mode=TwoWay}"
|
||||
helpers:TextBoxHelper.Placeholder="User name for this repository"/>
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="Email : " HorizontalAlignment="Right"/>
|
||||
<TextBox
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Height="24"
|
||||
Text="{Binding ElementName=me, Path=UserEmail, Mode=TwoWay}"
|
||||
helpers:TextBoxHelper.Placeholder="Email address"/>
|
||||
|
||||
<!-- 提交模板 -->
|
||||
<Label Grid.Row="5" Grid.ColumnSpan="2" Content="COMMIT TEMPLATE" FontSize="16" FontWeight="DemiBold" Opacity=".85"/>
|
||||
<Label Grid.Row="7" Grid.Column="0" Content="Template : " HorizontalAlignment="Right" VerticalAlignment="Top"/>
|
||||
<TextBox
|
||||
Grid.Row="7"
|
||||
Grid.Column="1"
|
||||
Height="100"
|
||||
AcceptsReturn="True"
|
||||
AcceptsTab="True"
|
||||
Padding="2"
|
||||
Text="{Binding ElementName=me, Path=CommitTemplate, Mode=TwoWay}"/>
|
||||
|
||||
<!-- 操作 -->
|
||||
<Grid Grid.Row="9" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1" Click="Save" Content="SAVE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||
<Button Grid.Column="3" Click="Close" Content="CLOSE" Style="{StaticResource Style.Button.Bordered}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
70
src/UI/Configure.xaml.cs
Normal file
70
src/UI/Configure.xaml.cs
Normal file
|
@ -0,0 +1,70 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Repository configuration dialog
|
||||
/// </summary>
|
||||
public partial class Configure : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
|
||||
/// <summary>
|
||||
/// User name for this repository.
|
||||
/// </summary>
|
||||
public string UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User email for this repository.
|
||||
/// </summary>
|
||||
public string UserEmail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Commit template for this repository.
|
||||
/// </summary>
|
||||
public string CommitTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
public Configure(Git.Repository repo) {
|
||||
this.repo = repo;
|
||||
|
||||
UserName = repo.GetConfig("user.name");
|
||||
UserEmail = repo.GetConfig("user.email");
|
||||
CommitTemplate = repo.CommitTemplate;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show this dialog.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
public static void Show(Git.Repository repo) {
|
||||
repo.GetPopupManager()?.Show(new Configure(repo));
|
||||
}
|
||||
|
||||
#region EVENTS
|
||||
private void Save(object sender, RoutedEventArgs e) {
|
||||
var oldUser = repo.GetConfig("user.name");
|
||||
if (oldUser != UserName) repo.SetConfig("user.name", UserName);
|
||||
|
||||
var oldEmail = repo.GetConfig("user.email");
|
||||
if (oldEmail != UserEmail) repo.SetConfig("user.email", UserEmail);
|
||||
|
||||
if (CommitTemplate != repo.CommitTemplate) {
|
||||
repo.CommitTemplate = CommitTemplate;
|
||||
Git.Preference.Save();
|
||||
}
|
||||
|
||||
Close(sender, e);
|
||||
}
|
||||
|
||||
private void Close(object sender, RoutedEventArgs e) {
|
||||
repo.GetPopupManager()?.Close();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
80
src/UI/CreateBranch.xaml
Normal file
80
src/UI/CreateBranch.xaml
Normal file
|
@ -0,0 +1,80 @@
|
|||
<UserControl x:Class="SourceGit.UI.CreateBranch"
|
||||
x:Name="me"
|
||||
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:helpers="clr-namespace:SourceGit.Helpers"
|
||||
xmlns:converters="clr-namespace:SourceGit.Converters"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="192" d:DesignWidth="500" Height="224" Width="500">
|
||||
<UserControl.Resources>
|
||||
<converters:InverseBool x:Key="InverseBool"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Create Local Branch"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Based On :"/>
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||
<Path x:Name="basedOnType" Width="12" Style="{StaticResource Style.Icon}"/>
|
||||
<Label x:Name="basedOnDesc" VerticalAlignment="Center" Content="master"/>
|
||||
</StackPanel>
|
||||
|
||||
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="New Branch Name :"/>
|
||||
<TextBox Grid.Row="3" Grid.Column="1"
|
||||
x:Name="txtName"
|
||||
VerticalContentAlignment="Center"
|
||||
Height="24"
|
||||
helpers:TextBoxHelper.Placeholder="Enter branch name.">
|
||||
<TextBox.Text>
|
||||
<Binding ElementName="me" Path="BranchName" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
|
||||
<Binding.ValidationRules>
|
||||
<helpers:BranchNameRule x:Name="nameValidator"/>
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
|
||||
<Label Grid.Row="4" Grid.Column="0" HorizontalAlignment="Right" Content="Local Changes :"/>
|
||||
<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal">
|
||||
<RadioButton Content="Stash & Reapply" GroupName="LocalChanges" IsChecked="{Binding AutoStash, ElementName=me}"/>
|
||||
<RadioButton Content="Discard" Margin="8,0,0,0" GroupName="LocalChanges" IsChecked="{Binding AutoStash, ElementName=me, Mode=OneWay, Converter={StaticResource InverseBool}}"/>
|
||||
</StackPanel>
|
||||
|
||||
<CheckBox Grid.Row="5" Grid.Column="1"
|
||||
x:Name="chkCheckout"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="True"
|
||||
Content="Check out after created"/>
|
||||
|
||||
<Grid Grid.Row="7" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
137
src/UI/CreateBranch.xaml.cs
Normal file
137
src/UI/CreateBranch.xaml.cs
Normal file
|
@ -0,0 +1,137 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Create branch dialog
|
||||
/// </summary>
|
||||
public partial class CreateBranch : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
private string based = null;
|
||||
|
||||
/// <summary>
|
||||
/// New branch name.
|
||||
/// </summary>
|
||||
public string BranchName {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto Stash
|
||||
/// </summary>
|
||||
public bool AutoStash { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="opened">Opened repository</param>
|
||||
public CreateBranch(Git.Repository opened) {
|
||||
InitializeComponent();
|
||||
|
||||
repo = opened;
|
||||
nameValidator.Repo = opened;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create branch based on current head.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
public static void Show(Git.Repository repo) {
|
||||
var current = repo.CurrentBranch();
|
||||
if (current != null) Show(repo, current);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create branch base on existed one.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="branch"></param>
|
||||
public static void Show(Git.Repository repo, Git.Branch branch) {
|
||||
var dialog = new CreateBranch(repo);
|
||||
dialog.based = branch.Name;
|
||||
dialog.basedOnType.Data = dialog.FindResource("Icon.Branch") as Geometry;
|
||||
dialog.basedOnDesc.Content = branch.Name;
|
||||
|
||||
if (!branch.IsLocal) dialog.txtName.Text = branch.Name.Substring(branch.Remote.Length + 1);
|
||||
repo.GetPopupManager()?.Show(dialog);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create branch based on tag.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="tag"></param>
|
||||
public static void Show(Git.Repository repo, Git.Tag tag) {
|
||||
var dialog = new CreateBranch(repo);
|
||||
dialog.based = tag.Name;
|
||||
dialog.basedOnType.Data = dialog.FindResource("Icon.Tag") as Geometry;
|
||||
dialog.basedOnDesc.Content = tag.Name;
|
||||
|
||||
repo.GetPopupManager()?.Show(dialog);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create branch based on commit.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="commit"></param>
|
||||
public static void Show(Git.Repository repo, Git.Commit commit) {
|
||||
var dialog = new CreateBranch(repo);
|
||||
dialog.based = commit.SHA;
|
||||
dialog.basedOnType.Data = dialog.FindResource("Icon.Commit") as Geometry;
|
||||
dialog.basedOnDesc.Content = $"{commit.ShortSHA} {commit.Subject}";
|
||||
|
||||
repo.GetPopupManager()?.Show(dialog);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start create branch.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private async void Start(object sender, RoutedEventArgs e) {
|
||||
txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
|
||||
if (Validation.GetHasError(txtName)) return;
|
||||
|
||||
var popup = repo.GetPopupManager();
|
||||
popup?.Lock();
|
||||
|
||||
bool checkout = chkCheckout.IsChecked == true;
|
||||
await Task.Run(() => {
|
||||
if (checkout) {
|
||||
bool stashed = false;
|
||||
|
||||
if (repo.LocalChanges().Count > 0 && AutoStash) {
|
||||
Git.Stash.Push(repo, true, "CREATE BRANCH AUTO STASH", new List<string>());
|
||||
stashed = true;
|
||||
}
|
||||
|
||||
repo.Checkout($"-b {BranchName} {based}");
|
||||
|
||||
if (stashed) {
|
||||
var stashes = repo.Stashes();
|
||||
if (stashes.Count > 0) stashes[0].Pop(repo);
|
||||
}
|
||||
} else {
|
||||
Git.Branch.Create(repo, BranchName, based);
|
||||
}
|
||||
});
|
||||
|
||||
popup?.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
repo.GetPopupManager()?.Close();
|
||||
}
|
||||
}
|
||||
}
|
70
src/UI/CreateTag.xaml
Normal file
70
src/UI/CreateTag.xaml
Normal file
|
@ -0,0 +1,70 @@
|
|||
<UserControl x:Class="SourceGit.UI.CreateTag"
|
||||
x:Name="me"
|
||||
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:helpers="clr-namespace:SourceGit.Helpers"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="224" d:DesignWidth="500" Width="500" Height="224">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="64"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Create Tag"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="New Tag At :"/>
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||
<Path Width="12" x:Name="basedOnType" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Commit}"/>
|
||||
<Label x:Name="basedOnDesc" VerticalAlignment="Center" Content="xxx"/>
|
||||
</StackPanel>
|
||||
|
||||
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" Content="Tag Name :"/>
|
||||
<TextBox Grid.Row="3" Grid.Column="1"
|
||||
x:Name="tagName"
|
||||
Height="24"
|
||||
helpers:TextBoxHelper.Placeholder="Recommanded format :v1.0.0-alpha">
|
||||
<TextBox.Text>
|
||||
<Binding ElementName="me" Path="TagName" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
|
||||
<Binding.ValidationRules>
|
||||
<helpers:TagNameRule x:Name="nameValidator"/>
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
|
||||
<Label Grid.Row="4" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,4" Content="Tag Message :"/>
|
||||
<TextBox Grid.Row="4" Grid.Column="1"
|
||||
x:Name="tagMessage"
|
||||
Height="56"
|
||||
Padding="2"
|
||||
AcceptsReturn="True"
|
||||
helpers:TextBoxHelper.Placeholder="Optional"
|
||||
helpers:TextBoxHelper.PlaceholderBaseline="Top"/>
|
||||
|
||||
<Grid Grid.Row="6" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
95
src/UI/CreateTag.xaml.cs
Normal file
95
src/UI/CreateTag.xaml.cs
Normal file
|
@ -0,0 +1,95 @@
|
|||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Create tag dialog
|
||||
/// </summary>
|
||||
public partial class CreateTag : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
private string based = null;
|
||||
|
||||
/// <summary>
|
||||
/// Tag name
|
||||
/// </summary>
|
||||
public string TagName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
public CreateTag(Git.Repository opened) {
|
||||
InitializeComponent();
|
||||
|
||||
repo = opened;
|
||||
nameValidator.Repo = opened;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create tag using current branch.
|
||||
/// </summary>
|
||||
/// <param name="repo">Opened repository.</param>
|
||||
public static void Show(Git.Repository repo) {
|
||||
Show(repo, repo.Branches().First(b => b.IsCurrent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create tag using branch
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="branch"></param>
|
||||
public static void Show(Git.Repository repo, Git.Branch branch) {
|
||||
if (branch == null) {
|
||||
App.RaiseError("Empty repository!");
|
||||
return;
|
||||
}
|
||||
|
||||
var dialog = new CreateTag(repo);
|
||||
dialog.based = branch.Head;
|
||||
dialog.basedOnType.Data = dialog.FindResource("Icon.Branch") as Geometry;
|
||||
dialog.basedOnDesc.Content = branch.Name;
|
||||
|
||||
repo.GetPopupManager()?.Show(dialog);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create tag using commit.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="commit"></param>
|
||||
public static void Show(Git.Repository repo, Git.Commit commit) {
|
||||
var dialog = new CreateTag(repo);
|
||||
dialog.based = commit.SHA;
|
||||
dialog.basedOnType.Data = dialog.FindResource("Icon.Commit") as Geometry;
|
||||
dialog.basedOnDesc.Content = $"{commit.ShortSHA} {commit.Subject}";
|
||||
|
||||
repo.GetPopupManager()?.Show(dialog);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start to create tag.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Start(object sender, RoutedEventArgs e) {
|
||||
tagName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
|
||||
if (Validation.GetHasError(tagName)) return;
|
||||
|
||||
Git.Tag.Add(repo, TagName, based, tagMessage.Text);
|
||||
|
||||
repo.GetPopupManager()?.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
repo.GetPopupManager()?.Close();
|
||||
}
|
||||
}
|
||||
}
|
528
src/UI/Dashboard.xaml
Normal file
528
src/UI/Dashboard.xaml
Normal file
|
@ -0,0 +1,528 @@
|
|||
<UserControl x:Class="SourceGit.UI.Dashboard"
|
||||
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:source="clr-namespace:SourceGit"
|
||||
xmlns:local="clr-namespace:SourceGit.UI"
|
||||
xmlns:converters="clr-namespace:SourceGit.Converters"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Unloaded="Cleanup">
|
||||
<UserControl.Resources>
|
||||
<RoutedUICommand x:Key="OpenSearchBarCommand" Text="OpenSearchBar"/>
|
||||
<RoutedUICommand x:Key="HideSearchBarCommand" Text="HideSearchBar"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<UserControl.InputBindings>
|
||||
<KeyBinding Key="F" Modifiers="Ctrl" Command="{StaticResource OpenSearchBarCommand}"/>
|
||||
<KeyBinding Key="ESC" Command="{StaticResource HideSearchBarCommand}"/>
|
||||
</UserControl.InputBindings>
|
||||
|
||||
<UserControl.CommandBindings>
|
||||
<CommandBinding Command="{StaticResource OpenSearchBarCommand}" Executed="OpenSearchBar"/>
|
||||
<CommandBinding Command="{StaticResource HideSearchBarCommand}" Executed="HideSearchBar"/>
|
||||
</UserControl.CommandBindings>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- TitleBar -->
|
||||
<Grid Grid.Row="0" Panel.ZIndex="100">
|
||||
<Border Background="{StaticResource Brush.BG1}" Margin="0,2,0,0">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect ShadowDepth="2" Direction="270" Opacity=".5" Color="Black"/>
|
||||
</Border.Effect>
|
||||
</Border>
|
||||
|
||||
<Grid Background="{StaticResource Brush.BG1}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Browser -->
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="6,0">
|
||||
<Button Click="OpenExplorer" Margin="4,0,0,0" ToolTip="Open In File Browser">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder.Open}"/>
|
||||
<Label Content="Explore"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Click="OpenConfigure" Margin="4,0,0,0" ToolTip="Configure This Repository">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Setting}"/>
|
||||
<Label Content="Configure"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Common Git Options -->
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="6,0">
|
||||
<Button Click="OpenFetch" Margin="4,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Fetch}"/>
|
||||
<Label Content="Fetch"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Click="OpenPull" Margin="4,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Pull}"/>
|
||||
<Label Content="Pull"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Click="OpenPush" Margin="4,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Push}"/>
|
||||
<Label Content="Push"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Click="OpenStash" Margin="4,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.SaveStash}"/>
|
||||
<Label Content="Stash"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Click="OpenApply">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Apply}"/>
|
||||
<Label Content="Apply"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<!-- External Options -->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Click="OpenSearch" Margin="4,0" ToolTip="Search Commit">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Search}"/>
|
||||
<Label Content="Search"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Click="OpenTerminal" Margin="4,0" ToolTip="Open Git Bash">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Terminal}"/>
|
||||
<Label Content="Terminal"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- Main body -->
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200" MinWidth="200" MaxWidth="300"/>
|
||||
<ColumnDefinition Width="1"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Left panel -->
|
||||
<Grid Grid.Column="0" x:Name="main" Background="{StaticResource Brush.BG4}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="24"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="24"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="24"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="24"/>
|
||||
<RowDefinition Height="1"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="24"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.Resources>
|
||||
<converters:BoolToCollapsed x:Key="Bool2Collapsed"/>
|
||||
</Grid.Resources>
|
||||
|
||||
<!-- WORKSPACE -->
|
||||
<Label Grid.Row="0" Margin="4,0,0,0" Content="WORKSPACE" Style="{StaticResource Style.Label.GroupHeader}" />
|
||||
<ListView
|
||||
Grid.Row="1"
|
||||
x:Name="workspace"
|
||||
Background="{StaticResource Brush.BG3}"
|
||||
Style="{StaticResource Style.ListView.Borderless}"
|
||||
SelectionMode="Single">
|
||||
<ListViewItem x:Name="historiesSwitch" Selected="SwitchHistories" IsSelected="True">
|
||||
<StackPanel Margin="16,0,0,0" Orientation="Horizontal">
|
||||
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Histories}"/>
|
||||
<Label Margin="4,0,0,0" Content="Histories"/>
|
||||
</StackPanel>
|
||||
</ListViewItem>
|
||||
|
||||
<ListViewItem x:Name="workingCopySwitch" Selected="SwitchWorkingCopy">
|
||||
<Grid Margin="16,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Path Grid.Column="0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.WorkingCopy}"/>
|
||||
<Label Grid.Column="1" Margin="4,0,0,0" Content="Commit"/>
|
||||
<Border Grid.Column="2" x:Name="localChangesBadge" Style="{StaticResource Style.Border.Badge}">
|
||||
<Label x:Name="localChangesCount" Margin="4,-2,4,-2" Content="999" FontSize="10"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
|
||||
<ListViewItem x:Name="stashesSwitch" Selected="SwitchStashes">
|
||||
<Grid Margin="16,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Path Grid.Column="0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Stashes}"/>
|
||||
<Label Grid.Column="1" Margin="4,0,0,0" Content="Stashes"/>
|
||||
<Border Grid.Column="2" x:Name="stashBadge" Style="{StaticResource Style.Border.Badge}">
|
||||
<Label x:Name="stashCount" Margin="4,-2,4,-2" Content="999" FontSize="10"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</ListView>
|
||||
|
||||
<!-- LOCAL BRANCHES -->
|
||||
<Grid Grid.Row="2" Margin="4,0,2,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Column="0" Content="LOCAL BRANCHES" Style="{StaticResource Style.Label.GroupHeader}"/>
|
||||
<Button Grid.Column="1" Click="OpenGitFlow" Background="Transparent" ToolTip="GIT FLOW">
|
||||
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{DynamicResource Icon.Flow}"/>
|
||||
</Button>
|
||||
<Button Grid.Column="3" Click="OpenNewBranch" Background="Transparent" ToolTip="NEW BRANCH">
|
||||
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{DynamicResource Icon.Branch.Add}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
<TreeView
|
||||
Grid.Row="3"
|
||||
x:Name="localBranchTree"
|
||||
Padding="0"
|
||||
Background="{StaticResource Brush.BG3}"
|
||||
FontFamily="Consolas"
|
||||
LostFocus="TreeLostFocus"
|
||||
SelectedItemChanged="LocalBranchSelected"
|
||||
PreviewMouseWheel="TreeMouseWheel">
|
||||
<TreeView.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.ItemContainerStyle}">
|
||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
||||
<EventSetter Event="ContextMenuOpening" Handler="LocalBranchContextMenuOpening"/>
|
||||
<EventSetter Event="MouseDoubleClick" Handler="LocalBranchMouseDoubleClick"/>
|
||||
</Style>
|
||||
</TreeView.ItemContainerStyle>
|
||||
<TreeView.ItemTemplate>
|
||||
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
|
||||
<Grid Height="24">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Path Grid.Column="0" Width="10" x:Name="icon" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
|
||||
<Label Grid.Column="1" x:Name="name" Content="{Binding Name}" Padding="4,0,0,0"/>
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal">
|
||||
<Border Style="{StaticResource Style.Border.Badge}" Visibility="{Binding TrackVisibility}">
|
||||
<Label Margin="4,-2,4,-2" Content="{Binding Branch.UpstreamTrack}" FontSize="10"/>
|
||||
</Border>
|
||||
<ToggleButton
|
||||
Visibility="{Binding FilterVisibility}"
|
||||
IsChecked="{Binding IsFiltered, Mode=OneWay}"
|
||||
Checked="FilterChanged"
|
||||
Unchecked="FilterChanged"
|
||||
Style="{StaticResource Style.ToggleButton.Filter}"
|
||||
ToolTip="FILTER"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<HierarchicalDataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding IsCurrent}" Value="True">
|
||||
<Setter TargetName="name" Property="FontWeight" Value="ExtraBold"/>
|
||||
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Check}"/>
|
||||
</DataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding Branch}" Value="{x:Null}"/>
|
||||
<Condition Binding="{Binding IsExpanded}" Value="False"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Fill}"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding Branch}" Value="{x:Null}"/>
|
||||
<Condition Binding="{Binding IsExpanded}" Value="True"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
|
||||
</MultiDataTrigger>
|
||||
</HierarchicalDataTemplate.Triggers>
|
||||
</HierarchicalDataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
</TreeView>
|
||||
|
||||
<!-- REMOTES -->
|
||||
<Grid Grid.Row="4" Margin="4,0,2,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Column="0" Content="REMOTES" Style="{StaticResource Style.Label.GroupHeader}"/>
|
||||
<Button Grid.Column="1" Click="OpenRemote" ToolTip="ADD REMOTE">
|
||||
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{DynamicResource Icon.Remote.Add}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
<TreeView
|
||||
Grid.Row="5"
|
||||
x:Name="remoteBranchTree"
|
||||
Padding="0"
|
||||
Background="{StaticResource Brush.BG3}"
|
||||
FontFamily="Consolas"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
SelectedItemChanged="RemoteBranchSelected"
|
||||
LostFocus="TreeLostFocus"
|
||||
PreviewMouseWheel="TreeMouseWheel">
|
||||
<TreeView.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource Style.TreeView.ItemContainerStyle}">
|
||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
||||
<EventSetter Event="ContextMenuOpening" Handler="RemoteContextMenuOpening"/>
|
||||
</Style>
|
||||
</TreeView.ItemContainerStyle>
|
||||
|
||||
<TreeView.Resources>
|
||||
<HierarchicalDataTemplate DataType="{x:Type local:RemoteNode}" ItemsSource="{Binding Children}">
|
||||
<Grid Height="24">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Path Grid.Column="0" Width="10" x:Name="icon" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Remote}"/>
|
||||
<TextBlock Grid.Column="1" x:Name="name" Text="{Binding Name}" Padding="4,0,0,0" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}" ClipToBounds="True"/>
|
||||
</Grid>
|
||||
</HierarchicalDataTemplate>
|
||||
|
||||
<HierarchicalDataTemplate DataType="{x:Type local:BranchNode}" ItemsSource="{Binding Children}">
|
||||
<Grid Height="24">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Path Grid.Column="0" Width="10" x:Name="icon" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
|
||||
<TextBlock Grid.Column="1" x:Name="name" Text="{Binding Name}" Padding="4,0,0,0" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}" ClipToBounds="True"/>
|
||||
<ToggleButton
|
||||
Grid.Column="2"
|
||||
Visibility="{Binding FilterVisibility}"
|
||||
IsChecked="{Binding IsFiltered, Mode=OneWay}"
|
||||
Checked="FilterChanged"
|
||||
Unchecked="FilterChanged"
|
||||
Style="{StaticResource Style.ToggleButton.Filter}"
|
||||
ToolTip="FILTER"/>
|
||||
</Grid>
|
||||
|
||||
<HierarchicalDataTemplate.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding Branch}" Value="{x:Null}"/>
|
||||
<Condition Binding="{Binding IsExpanded}" Value="False"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Fill}"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding Branch}" Value="{x:Null}"/>
|
||||
<Condition Binding="{Binding IsExpanded}" Value="True"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter TargetName="icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
|
||||
</MultiDataTrigger>
|
||||
</HierarchicalDataTemplate.Triggers>
|
||||
</HierarchicalDataTemplate>
|
||||
</TreeView.Resources>
|
||||
</TreeView>
|
||||
|
||||
<!-- TAGS -->
|
||||
<ToggleButton
|
||||
x:Name="tagListToggle"
|
||||
Grid.Row="6"
|
||||
Style="{StaticResource Style.ToggleButton.Expender}"
|
||||
IsChecked="{Binding Source={x:Static source:App.Preference}, Path=UIShowTags, Mode=TwoWay}">
|
||||
<Grid Margin="4,0,2,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Column="0" x:Name="tagCount" Content="TAGS" Style="{StaticResource Style.Label.GroupHeader}"/>
|
||||
<Button Grid.Column="1" Click="OpenNewTag" ToolTip="NEW TAG">
|
||||
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Tag.Add}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</ToggleButton>
|
||||
<Rectangle Grid.Row="7" Height="1" Fill="{StaticResource Brush.BG3}"/>
|
||||
<DataGrid
|
||||
Grid.Row="8"
|
||||
x:Name="tagList"
|
||||
Visibility="{Binding ElementName=tagListToggle, Path=IsChecked, Converter={StaticResource Bool2Collapsed}}"
|
||||
Background="{StaticResource Brush.BG3}"
|
||||
RowHeight="24"
|
||||
Height="200"
|
||||
LostFocus="TagLostFocus"
|
||||
SelectionChanged="TagSelectionChanged"
|
||||
ContextMenuOpening="TagContextMenuOpening"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
SelectionMode="Single"
|
||||
SelectionUnit="FullRow">
|
||||
<DataGrid.Resources>
|
||||
<Style x:Key="Style.DataGridText.TagName" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource Brush.FG}"/>
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Width="26">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Tag}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Width="*" IsReadOnly="True" Binding="{Binding Name}" ElementStyle="{StaticResource Style.DataGridText.TagName}"/>
|
||||
|
||||
<DataGridTemplateColumn Width="16">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<ToggleButton
|
||||
Grid.Column="2"
|
||||
IsChecked="{Binding IsFiltered, Mode=TwoWay}"
|
||||
Checked="FilterChanged"
|
||||
Unchecked="FilterChanged"
|
||||
Style="{StaticResource Style.ToggleButton.Filter}"
|
||||
ToolTip="FILTER"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- SUBMODULES -->
|
||||
<ToggleButton
|
||||
x:Name="submoduleListToggle"
|
||||
Grid.Row="9"
|
||||
Style="{StaticResource Style.ToggleButton.Expender}"
|
||||
IsChecked="False">
|
||||
<Grid Margin="4,0,2,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="16"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Column="0" x:Name="submoduleCount" Content="SUBMODULES" Style="{StaticResource Style.Label.GroupHeader}"/>
|
||||
<Button Grid.Column="1" Click="OpenAddSubmodule" ToolTip="ADD SUBMODULE">
|
||||
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Submodule}"/>
|
||||
</Button>
|
||||
<Button Grid.Column="3" Click="UpdateSubmodule" ToolTip="UPDATE SUBMODULE">
|
||||
<Path Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</ToggleButton>
|
||||
<DataGrid
|
||||
Grid.Row="11"
|
||||
x:Name="submoduleList"
|
||||
Visibility="{Binding ElementName=submoduleListToggle, Path=IsChecked, Converter={StaticResource Bool2Collapsed}}"
|
||||
Background="{StaticResource Brush.BG3}"
|
||||
RowHeight="24"
|
||||
Height="100"
|
||||
LostFocus="SubmoduleLostFocus"
|
||||
ContextMenuOpening="SubmoduleContextMenuOpening"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
SelectionMode="Single"
|
||||
SelectionUnit="FullRow">
|
||||
<DataGrid.Resources>
|
||||
<Style x:Key="Style.DataGridText.SubmodulePath" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource Brush.FG}"/>
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
|
||||
<EventSetter Event="MouseDoubleClick" Handler="SubmoduleMouseDoubleClick"/>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Width="26">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Submodule}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Width="*" IsReadOnly="True" Binding="{Binding}" ElementStyle="{StaticResource Style.DataGridText.SubmodulePath}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
|
||||
<!-- Splitter -->
|
||||
<GridSplitter Grid.Column="1" Width="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{StaticResource Brush.BG3}"/>
|
||||
|
||||
<!-- Right -->
|
||||
<Grid Grid.Column="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Abort panel -->
|
||||
<Grid x:Name="abortPanel" Grid.Row="0" Background="{StaticResource Brush.BG7}" Visibility="Collapsed">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Column="0" x:Name="txtMergeProcessing" FontWeight="DemiBold" Foreground="{StaticResource Brush.BG4}"/>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<Button x:Name="btnResolve" Click="Resolve" Content="RESOLVE" Margin="4">
|
||||
<Button.Style>
|
||||
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource Style.Button.Bordered}">
|
||||
<Setter Property="Background" Value="{StaticResource Brush.BG1}"/>
|
||||
<Setter Property="Margin" Value="2"/>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
<Button x:Name="btnContinue" Click="Continue" Content="CONTINUE" Style="{StaticResource Style.Button.AccentBordered}" Margin="4"/>
|
||||
<Button Grid.Column="3" Click="Abort" Content="ABORT" Style="{StaticResource Style.Button.Bordered}" Foreground="{StaticResource Brush.BG1}" Margin="4"/>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Others -->
|
||||
<local:Histories Grid.Row="1" x:Name="histories" Visibility="Visible"/>
|
||||
<local:WorkingCopy Grid.Row="1" x:Name="commits" Visibility="Collapsed"/>
|
||||
<local:Stashes Grid.Row="1" x:Name="stashes" Visibility="Collapsed"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- Popups -->
|
||||
<local:PopupManager x:Name="popupManager" Grid.Row="1"/>
|
||||
</Grid>
|
||||
</UserControl>
|
1068
src/UI/Dashboard.xaml.cs
Normal file
1068
src/UI/Dashboard.xaml.cs
Normal file
File diff suppressed because it is too large
Load diff
43
src/UI/DeleteBranch.xaml
Normal file
43
src/UI/DeleteBranch.xaml
Normal file
|
@ -0,0 +1,43 @@
|
|||
<UserControl x:Class="SourceGit.UI.DeleteBranch"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="160" d:DesignWidth="500" Height="128" Width="500">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Confirm To Delete Branch"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Branch :"/>
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}" Margin="4,0"/>
|
||||
<Label x:Name="branchName"/>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="4" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
55
src/UI/DeleteBranch.xaml.cs
Normal file
55
src/UI/DeleteBranch.xaml.cs
Normal file
|
@ -0,0 +1,55 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
/// <summary>
|
||||
/// Confirm to delete branch
|
||||
/// </summary>
|
||||
public partial class DeleteBranch : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
private Git.Branch branch = null;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="opened">Opened repository.</param>
|
||||
/// <param name="target">Branch to be deleted.</param>
|
||||
public DeleteBranch(Git.Repository opened, Git.Branch target) {
|
||||
InitializeComponent();
|
||||
repo = opened;
|
||||
branch = target;
|
||||
branchName.Content = target.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show this dialog.
|
||||
/// </summary>
|
||||
/// <param name="opened"></param>
|
||||
/// <param name="branch"></param>
|
||||
public static void Show(Git.Repository opened, Git.Branch branch) {
|
||||
opened.GetPopupManager()?.Show(new DeleteBranch(opened, branch));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private async void Sure(object sender, RoutedEventArgs e) {
|
||||
var popup = repo.GetPopupManager();
|
||||
popup?.Lock();
|
||||
await Task.Run(() => branch.Delete(repo));
|
||||
popup?.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
repo.GetPopupManager()?.Close();
|
||||
}
|
||||
}
|
||||
}
|
42
src/UI/DeleteRemote.xaml
Normal file
42
src/UI/DeleteRemote.xaml
Normal file
|
@ -0,0 +1,42 @@
|
|||
<UserControl x:Class="SourceGit.UI.DeleteRemote"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="160" d:DesignWidth="500" Height="128" Width="500">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Confirm To Delete Remote"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Remote :"/>
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Remote}" Margin="4,0"/>
|
||||
<Label x:Name="remoteName"/>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="4" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
56
src/UI/DeleteRemote.xaml.cs
Normal file
56
src/UI/DeleteRemote.xaml.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Confirm to delete a remote
|
||||
/// </summary>
|
||||
public partial class DeleteRemote : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
private string remote = null;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="opened">Opened repository</param>
|
||||
/// <param name="target">Remote to be deleted</param>
|
||||
public DeleteRemote(Git.Repository opened, string target) {
|
||||
InitializeComponent();
|
||||
repo = opened;
|
||||
remote = target;
|
||||
remoteName.Content = target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show this dialog
|
||||
/// </summary>
|
||||
/// <param name="opened"></param>
|
||||
/// <param name="remote"></param>
|
||||
public static void Show(Git.Repository opened, string remote) {
|
||||
opened.GetPopupManager()?.Show(new DeleteRemote(opened, remote));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private async void Sure(object sender, RoutedEventArgs e) {
|
||||
var popup = repo.GetPopupManager();
|
||||
popup?.Lock();
|
||||
await Task.Run(() => Git.Remote.Delete(repo, remote));
|
||||
popup?.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
repo.GetPopupManager()?.Close();
|
||||
}
|
||||
}
|
||||
}
|
45
src/UI/DeleteTag.xaml
Normal file
45
src/UI/DeleteTag.xaml
Normal file
|
@ -0,0 +1,45 @@
|
|||
<UserControl x:Class="SourceGit.UI.DeleteTag"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="160" d:DesignWidth="500" Height="160" Width="500">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Confirm To Delete Tag"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Tag :"/>
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Tag}" Margin="4,0"/>
|
||||
<Label x:Name="tagName"/>
|
||||
</StackPanel>
|
||||
|
||||
<CheckBox Grid.Row="3" Grid.Column="1" x:Name="chkWithRemote" Content="Delete from remote repositories"/>
|
||||
|
||||
<Grid Grid.Row="5" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
60
src/UI/DeleteTag.xaml.cs
Normal file
60
src/UI/DeleteTag.xaml.cs
Normal file
|
@ -0,0 +1,60 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Delete tag dialog.
|
||||
/// </summary>
|
||||
public partial class DeleteTag : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
private Git.Tag tag = null;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="repo">Opened repo</param>
|
||||
/// <param name="tag">Delete tag</param>
|
||||
public DeleteTag(Git.Repository repo, Git.Tag tag) {
|
||||
this.repo = repo;
|
||||
this.tag = tag;
|
||||
|
||||
InitializeComponent();
|
||||
tagName.Content = tag.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display this dialog.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="tag"></param>
|
||||
public static void Show(Git.Repository repo, Git.Tag tag) {
|
||||
repo.GetPopupManager()?.Show(new DeleteTag(repo, tag));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start request.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private async void Start(object sender, RoutedEventArgs e) {
|
||||
var popup = repo.GetPopupManager();
|
||||
popup?.Lock();
|
||||
|
||||
var push = chkWithRemote.IsChecked == true;
|
||||
await Task.Run(() => Git.Tag.Delete(repo, tag.Name, push));
|
||||
|
||||
popup?.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
repo.GetPopupManager()?.Close();
|
||||
}
|
||||
}
|
||||
}
|
203
src/UI/DiffViewer.xaml
Normal file
203
src/UI/DiffViewer.xaml
Normal file
|
@ -0,0 +1,203 @@
|
|||
<UserControl x:Class="SourceGit.UI.DiffViewer"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
FontFamily="Consolas">
|
||||
<Border BorderThickness="1" BorderBrush="{StaticResource Brush.Border2}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="26"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0" BorderBrush="{StaticResource Brush.Border2}" BorderThickness="0,0,0,1">
|
||||
<Grid Margin="8,4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0" x:Name="orgFileNamePanel" Orientation="Horizontal">
|
||||
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.File}"/>
|
||||
<TextBlock x:Name="orgFileName" Margin="4,0,0,0" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}"/>
|
||||
<TextBlock Margin="8,0" VerticalAlignment="Center" Text="→" Foreground="{StaticResource Brush.FG}"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.File}"/>
|
||||
<TextBlock x:Name="fileName" Margin="4,0" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}"/>
|
||||
<Path x:Name="loading" Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="2" x:Name="diffNavigation" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Width="26" Click="Go2Next" ToolTip="Next Difference" Background="Transparent">
|
||||
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.MoveDown}"/>
|
||||
</Button>
|
||||
|
||||
<Button Click="Go2Prev" ToolTip="Previous Difference" Background="Transparent">
|
||||
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.MoveUp}"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Grid x:Name="textChange" Grid.Row="1" ClipToBounds="True">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" MinWidth="100"/>
|
||||
<ColumnDefinition Width="2"/>
|
||||
<ColumnDefinition Width="*" MinWidth="100"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid Grid.Column="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="1"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBox
|
||||
x:Name="leftLineNumber"
|
||||
Grid.Column="0"
|
||||
AcceptsReturn="True"
|
||||
AcceptsTab="True"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
IsReadOnly="True"
|
||||
Padding="2,0"
|
||||
Margin="0"
|
||||
FontSize="13"
|
||||
HorizontalContentAlignment="Right"
|
||||
VerticalAlignment="Stretch"/>
|
||||
|
||||
<Rectangle Grid.Column="1" Width="1" Fill="{StaticResource Brush.Border2}"/>
|
||||
|
||||
<RichTextBox
|
||||
x:Name="leftText"
|
||||
Grid.Column="2"
|
||||
AcceptsReturn="True"
|
||||
AcceptsTab="True"
|
||||
IsReadOnly="True"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
Foreground="{StaticResource Brush.FG}"
|
||||
Height="Auto"
|
||||
FontSize="13"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
RenderOptions.ClearTypeHint="Enabled"
|
||||
ScrollViewer.ScrollChanged="OnViewerScroll"
|
||||
PreviewMouseWheel="OnViewerMouseWheel"
|
||||
SizeChanged="LeftSizeChanged"
|
||||
SelectionChanged="OnViewerSelectionChanged"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<RichTextBox.Document>
|
||||
<FlowDocument PageWidth="0"/>
|
||||
</RichTextBox.Document>
|
||||
<RichTextBox.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Command="ApplicationCommands.Copy"/>
|
||||
</ContextMenu>
|
||||
</RichTextBox.ContextMenu>
|
||||
</RichTextBox>
|
||||
</Grid>
|
||||
|
||||
<GridSplitter Grid.Column="1" Width="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{StaticResource Brush.Border2}"/>
|
||||
|
||||
<Grid Grid.Column="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="1"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBox
|
||||
x:Name="rightLineNumber"
|
||||
Grid.Column="0"
|
||||
AcceptsReturn="True"
|
||||
AcceptsTab="True"
|
||||
IsReadOnly="True"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
Padding="2,0"
|
||||
Margin="0"
|
||||
FontSize="13"
|
||||
HorizontalContentAlignment="Right"
|
||||
VerticalAlignment="Stretch"/>
|
||||
|
||||
<Rectangle Grid.Column="1" Width="1" Fill="{StaticResource Brush.Border2}"/>
|
||||
|
||||
<RichTextBox
|
||||
x:Name="rightText"
|
||||
Grid.Column="2"
|
||||
AcceptsReturn="True"
|
||||
AcceptsTab="True"
|
||||
IsReadOnly="True"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
Foreground="{StaticResource Brush.FG}"
|
||||
Height="Auto"
|
||||
FontSize="13"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
RenderOptions.ClearTypeHint="Enabled"
|
||||
ScrollViewer.ScrollChanged="OnViewerScroll"
|
||||
PreviewMouseWheel="OnViewerMouseWheel"
|
||||
SizeChanged="RightSizeChanged"
|
||||
SelectionChanged="OnViewerSelectionChanged"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<RichTextBox.Document>
|
||||
<FlowDocument PageWidth="0"/>
|
||||
</RichTextBox.Document>
|
||||
<RichTextBox.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Command="ApplicationCommands.Copy"/>
|
||||
</ContextMenu>
|
||||
</RichTextBox.ContextMenu>
|
||||
</RichTextBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Border x:Name="sizeChange" Grid.Row="1" ClipToBounds="True" Background="{StaticResource Brush.BG3}" Visibility="Collapsed">
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
|
||||
<Label Content="BINARY DIFF" Margin="0,0,0,32" FontSize="18" FontWeight="UltraBold" Foreground="{StaticResource Brush.FG2}" HorizontalAlignment="Center"/>
|
||||
<Path Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Binary}" Fill="{StaticResource Brush.FG2}"/>
|
||||
<Grid Margin="0,16,0,0" HorizontalAlignment="Center" TextElement.FontSize="18" TextElement.FontWeight="UltraBold">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="64"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="OLD :" Foreground="{StaticResource Brush.FG2}"/>
|
||||
<Label Grid.Row="0" Grid.Column="1" x:Name="txtOldSize" Foreground="{StaticResource Brush.FG2}" HorizontalAlignment="Right"/>
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="NEW :" Foreground="{StaticResource Brush.FG2}"/>
|
||||
<Label Grid.Row="1" Grid.Column="1" x:Name="txtNewSize" Foreground="{StaticResource Brush.FG2}" HorizontalAlignment="Right"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border x:Name="noChange" Grid.Row="1" Background="{StaticResource Brush.BG3}" Visibility="Collapsed">
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Opacity=".2">
|
||||
<Path Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Check}"/>
|
||||
<Label Margin="0,8,0,0" Content="NO CHANGES OR ONLY EOL CHANGES" FontSize="18" FontWeight="UltraBold" HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border x:Name="mask" Grid.RowSpan="2" Background="{StaticResource Brush.BG3}" Visibility="Collapsed">
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Opacity=".2">
|
||||
<Path Width="64" Height="64" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Diff}"/>
|
||||
<Label Margin="0,8,0,0" Content="SELECT FILE TO VIEW CHANGES" FontSize="18" FontWeight="UltraBold" HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
405
src/UI/DiffViewer.xaml.cs
Normal file
405
src/UI/DiffViewer.xaml.cs
Normal file
|
@ -0,0 +1,405 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Viewer for git diff
|
||||
/// </summary>
|
||||
public partial class DiffViewer : UserControl {
|
||||
private double minWidth = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Diff options.
|
||||
/// </summary>
|
||||
public class Option {
|
||||
public string[] RevisionRange = new string[] { };
|
||||
public string Path = "";
|
||||
public string OrgPath = null;
|
||||
public string ExtraArgs = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public DiffViewer() {
|
||||
InitializeComponent();
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset data.
|
||||
/// </summary>
|
||||
public void Reset() {
|
||||
mask.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Diff with options.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="opts"></param>
|
||||
public void Diff(Git.Repository repo, Option opts) {
|
||||
SetTitle(opts.Path, opts.OrgPath);
|
||||
|
||||
loading.Visibility = Visibility.Visible;
|
||||
mask.Visibility = Visibility.Collapsed;
|
||||
textChange.Visibility = Visibility.Collapsed;
|
||||
sizeChange.Visibility = Visibility.Collapsed;
|
||||
noChange.Visibility = Visibility.Collapsed;
|
||||
|
||||
Task.Run(() => {
|
||||
var args = $"{opts.ExtraArgs} ";
|
||||
if (opts.RevisionRange.Length > 0) args += $"{opts.RevisionRange[0]} ";
|
||||
if (opts.RevisionRange.Length > 1) args += $"{opts.RevisionRange[1]} -- ";
|
||||
if (!string.IsNullOrEmpty(opts.OrgPath)) args += $"\"{opts.OrgPath}\" ";
|
||||
args += $"\"{opts.Path}\"";
|
||||
|
||||
var rs = Git.Diff.Run(repo, args);
|
||||
if (rs.IsBinary) {
|
||||
SetSizeChangeData(Git.Diff.GetSizeChange(repo, opts.RevisionRange, opts.Path, opts.OrgPath));
|
||||
} else if (rs.Blocks.Count > 0) {
|
||||
SetData(rs);
|
||||
} else {
|
||||
SetSame();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#region LAYOUT
|
||||
/// <summary>
|
||||
/// Show diff title
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
/// <param name="orgFile"></param>
|
||||
private void SetTitle(string file, string orgFile) {
|
||||
fileName.Text = file;
|
||||
if (!string.IsNullOrEmpty(orgFile) && orgFile != "/dev/null") {
|
||||
orgFileNamePanel.Visibility = Visibility.Visible;
|
||||
orgFileName.Text = orgFile;
|
||||
} else {
|
||||
orgFileNamePanel.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show size changes.
|
||||
/// </summary>
|
||||
/// <param name="bc"></param>
|
||||
private void SetSizeChangeData(Git.Diff.BinaryChange bc) {
|
||||
Dispatcher.Invoke(() => {
|
||||
loading.Visibility = Visibility.Collapsed;
|
||||
sizeChange.Visibility = Visibility.Visible;
|
||||
diffNavigation.Visibility = Visibility.Collapsed;
|
||||
txtNewSize.Content = $"{bc.Size} Bytes";
|
||||
txtOldSize.Content = $"{bc.PreSize} Bytes";
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show no changes or only EOL changes.
|
||||
/// </summary>
|
||||
private void SetSame() {
|
||||
Dispatcher.Invoke(() => {
|
||||
loading.Visibility = Visibility.Collapsed;
|
||||
noChange.Visibility = Visibility.Visible;
|
||||
diffNavigation.Visibility = Visibility.Collapsed;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show diff content.
|
||||
/// </summary>
|
||||
/// <param name="rs"></param>
|
||||
private void SetData(Git.Diff.Result rs) {
|
||||
Dispatcher.Invoke(() => {
|
||||
loading.Visibility = Visibility.Collapsed;
|
||||
textChange.Visibility = Visibility.Visible;
|
||||
diffNavigation.Visibility = Visibility.Visible;
|
||||
|
||||
minWidth = Math.Max(leftText.ActualWidth, rightText.ActualWidth) - 16;
|
||||
|
||||
leftLineNumber.Text = "";
|
||||
rightLineNumber.Text = "";
|
||||
leftText.Document.Blocks.Clear();
|
||||
rightText.Document.Blocks.Clear();
|
||||
|
||||
foreach (var b in rs.Blocks) ShowBlock(b);
|
||||
|
||||
leftText.Document.PageWidth = minWidth + 16;
|
||||
rightText.Document.PageWidth = minWidth + 16;
|
||||
leftText.ScrollToHome();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make paragraph.
|
||||
/// </summary>
|
||||
/// <param name="b"></param>
|
||||
private void ShowBlock(Git.Diff.Block b) {
|
||||
var content = b.Builder.ToString();
|
||||
|
||||
Paragraph p = new Paragraph(new Run(content));
|
||||
p.Margin = new Thickness(0);
|
||||
p.Padding = new Thickness();
|
||||
p.LineHeight = 1;
|
||||
p.Background = Brushes.Transparent;
|
||||
p.Foreground = FindResource("Brush.FG") as SolidColorBrush;
|
||||
p.FontStyle = FontStyles.Normal;
|
||||
p.DataContext = b;
|
||||
|
||||
switch (b.Mode) {
|
||||
case Git.Diff.LineMode.Normal:
|
||||
break;
|
||||
case Git.Diff.LineMode.Indicator:
|
||||
p.Foreground = Brushes.Gray;
|
||||
p.FontStyle = FontStyles.Italic;
|
||||
break;
|
||||
case Git.Diff.LineMode.Empty:
|
||||
p.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0));
|
||||
break;
|
||||
case Git.Diff.LineMode.Added:
|
||||
p.Background = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0));
|
||||
break;
|
||||
case Git.Diff.LineMode.Deleted:
|
||||
p.Background = new SolidColorBrush(Color.FromArgb(60, 255, 0, 0));
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
switch (b.Side) {
|
||||
case Git.Diff.Side.Left:
|
||||
leftText.Document.Blocks.Add(p);
|
||||
for (int i = 0; i < b.Count; i++) {
|
||||
if (b.CanShowNumber) leftLineNumber.AppendText($"{i + b.LeftStart}\n");
|
||||
else leftLineNumber.AppendText("\n");
|
||||
}
|
||||
break;
|
||||
case Git.Diff.Side.Right:
|
||||
rightText.Document.Blocks.Add(p);
|
||||
for (int i = 0; i < b.Count; i++) {
|
||||
if (b.CanShowNumber) rightLineNumber.AppendText($"{i + b.RightStart}\n");
|
||||
else rightLineNumber.AppendText("\n");
|
||||
}
|
||||
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;
|
||||
rightText.Document.Blocks.Add(cp);
|
||||
|
||||
for (int i = 0; i < b.Count; i++) {
|
||||
if (b.Mode != Git.Diff.LineMode.Indicator) {
|
||||
leftLineNumber.AppendText($"{i + b.LeftStart}\n");
|
||||
rightLineNumber.AppendText($"{i + b.RightStart}\n");
|
||||
} else {
|
||||
leftLineNumber.AppendText("\n");
|
||||
rightLineNumber.AppendText("\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region EVENTS
|
||||
/// <summary>
|
||||
/// Sync scroll both sides.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnViewerScroll(object sender, ScrollChangedEventArgs e) {
|
||||
if (e.VerticalChange != 0) {
|
||||
if (leftText.VerticalOffset != e.VerticalOffset) {
|
||||
leftText.ScrollToVerticalOffset(e.VerticalOffset);
|
||||
}
|
||||
|
||||
if (rightText.VerticalOffset != e.VerticalOffset) {
|
||||
rightText.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 (rightText.HorizontalOffset != e.HorizontalOffset) {
|
||||
rightText.ScrollToHorizontalOffset(e.HorizontalOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scroll using mouse wheel.
|
||||
/// </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();
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fix document size for left side.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void LeftSizeChanged(object sender, SizeChangedEventArgs e) {
|
||||
if (leftText.Document.PageWidth < leftText.ActualWidth) {
|
||||
leftText.Document.PageWidth = leftText.ActualWidth;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fix document size for right side.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void RightSizeChanged(object sender, SizeChangedEventArgs e) {
|
||||
if (rightText.Document.PageWidth < rightText.ActualWidth) {
|
||||
rightText.Document.PageWidth = rightText.ActualWidth;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Go to next difference.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Go2Next(object sender, RoutedEventArgs e) {
|
||||
Paragraph next = null;
|
||||
double minTop = 0;
|
||||
|
||||
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 (next != null) {
|
||||
rightText.ScrollToVerticalOffset(rightText.VerticalOffset + minTop - 16);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Go to previous difference.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Go2Prev(object sender, RoutedEventArgs e) {
|
||||
Paragraph next = null;
|
||||
double maxTop = 0;
|
||||
|
||||
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) {
|
||||
next = p;
|
||||
maxTop = rect.Top;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
p = p.PreviousBlock as Paragraph;
|
||||
} while (p != null);
|
||||
|
||||
if (next != null) {
|
||||
rightText.ScrollToVerticalOffset(rightText.VerticalOffset + maxTop - 16);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
47
src/UI/Discard.xaml
Normal file
47
src/UI/Discard.xaml
Normal file
|
@ -0,0 +1,47 @@
|
|||
<UserControl x:Class="SourceGit.UI.Discard"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="160" d:DesignWidth="500" Height="160" Width="500">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Confirm To Discard Changes"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Changes :"/>
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||
<Path x:Name="icon" Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.File}" Fill="{StaticResource Brush.FG2}" Margin="4,0"/>
|
||||
<Label x:Name="txtPath"/>
|
||||
</StackPanel>
|
||||
|
||||
<Label Grid.Row="3" Grid.Column="1" Content="You can't undo this action!!!" Foreground="{StaticResource Brush.FG2}"/>
|
||||
|
||||
<Grid Grid.Row="5" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
67
src/UI/Discard.xaml.cs
Normal file
67
src/UI/Discard.xaml.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Confirm to discard changes dialog.
|
||||
/// </summary>
|
||||
public partial class Discard : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
private List<Git.Change> changes = null;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="opened"></param>
|
||||
/// <param name="targets"></param>
|
||||
public Discard(Git.Repository opened, List<Git.Change> targets) {
|
||||
repo = opened;
|
||||
changes = targets;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
if (changes == null || changes.Count == 0) {
|
||||
txtPath.Content = "All local changes in working copy.";
|
||||
icon.Data = FindResource("Icon.Folder") as Geometry;
|
||||
} else if (changes.Count == 1) {
|
||||
txtPath.Content = changes[0].Path;
|
||||
} else {
|
||||
txtPath.Content = $"Total {changes.Count} changes ...";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show this dialog
|
||||
/// </summary>
|
||||
/// <param name="opened"></param>
|
||||
/// <param name="targets"></param>
|
||||
public static void Show(Git.Repository opened, List<Git.Change> targets) {
|
||||
opened.GetPopupManager()?.Show(new Discard(opened, targets));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start to discard changes
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private async void Sure(object sender, RoutedEventArgs e) {
|
||||
var popup = repo.GetPopupManager();
|
||||
popup?.Lock();
|
||||
await Task.Run(() => repo.Discard(changes));
|
||||
popup?.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
repo.GetPopupManager()?.Close();
|
||||
}
|
||||
}
|
||||
}
|
66
src/UI/Fetch.xaml
Normal file
66
src/UI/Fetch.xaml
Normal file
|
@ -0,0 +1,66 @@
|
|||
<UserControl x:Class="SourceGit.UI.Fetch"
|
||||
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:git="clr-namespace:SourceGit.Git"
|
||||
xmlns:converters="clr-namespace:SourceGit.Converters"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="192" d:DesignWidth="500" Height="192" Width="500">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.Resources>
|
||||
<converters:InverseBool x:Key="InverseBool"/>
|
||||
</Grid.Resources>
|
||||
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Fetch Remote Changes"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Remote :"/>
|
||||
<ComboBox x:Name="combRemotes" Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" IsEnabled="{Binding ElementName=chkFetchAll, Path=IsChecked, Converter={StaticResource InverseBool}}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type git:Remote}">
|
||||
<StackPanel Orientation="Horizontal" Height="20">
|
||||
<Path Margin="4,0,0,0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Remote}"/>
|
||||
<Label Content="{Binding Name}" Padding="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<CheckBox Grid.Row="3" Grid.Column="1"
|
||||
x:Name="chkFetchAll"
|
||||
IsChecked="True"
|
||||
Content="Fetch all remotes"/>
|
||||
|
||||
<CheckBox Grid.Row="4" Grid.Column="1"
|
||||
x:Name="chkPrune"
|
||||
IsChecked="True"
|
||||
Content="Prune remote dead branches"/>
|
||||
|
||||
<Grid Grid.Row="6" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1" Click="Start" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
76
src/UI/Fetch.xaml.cs
Normal file
76
src/UI/Fetch.xaml.cs
Normal file
|
@ -0,0 +1,76 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Fetch dialog.
|
||||
/// </summary>
|
||||
public partial class Fetch : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="opened">Opened repository</param>
|
||||
/// <param name="preferRemote">Prefer selected remote.</param>
|
||||
public Fetch(Git.Repository opened, string preferRemote) {
|
||||
repo = opened;
|
||||
InitializeComponent();
|
||||
|
||||
Task.Run(() => {
|
||||
var remotes = repo.Remotes();
|
||||
Dispatcher.Invoke(() => {
|
||||
combRemotes.ItemsSource = remotes;
|
||||
if (preferRemote != null) {
|
||||
combRemotes.SelectedIndex = remotes.FindIndex(r => r.Name == preferRemote);
|
||||
chkFetchAll.IsChecked = false;
|
||||
} else {
|
||||
combRemotes.SelectedIndex = 0;
|
||||
chkFetchAll.IsChecked = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show fetch dialog.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="preferRemote"></param>
|
||||
public static void Show(Git.Repository repo, string preferRemote = null) {
|
||||
repo.GetPopupManager()?.Show(new Fetch(repo, preferRemote));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start fetch
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private async void Start(object sender, RoutedEventArgs e) {
|
||||
bool prune = chkPrune.IsChecked == true;
|
||||
|
||||
var popup = repo.GetPopupManager();
|
||||
popup?.Lock();
|
||||
|
||||
if (chkFetchAll.IsChecked == true) {
|
||||
await Task.Run(() => repo.Fetch(null, prune, msg => popup?.UpdateStatus(msg)));
|
||||
} else {
|
||||
var remote = combRemotes.SelectedItem as Git.Remote;
|
||||
await Task.Run(() => repo.Fetch(remote, prune, msg => popup?.UpdateStatus(msg)));
|
||||
}
|
||||
|
||||
popup?.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
repo.GetPopupManager()?.Close();
|
||||
}
|
||||
}
|
||||
}
|
170
src/UI/FileHistories.xaml
Normal file
170
src/UI/FileHistories.xaml
Normal file
|
@ -0,0 +1,170 @@
|
|||
<Window x:Class="SourceGit.UI.FileHistories"
|
||||
x:Name="me"
|
||||
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:SourceGit.UI"
|
||||
xmlns:git="clr-namespace:SourceGit.Git"
|
||||
mc:Ignorable="d"
|
||||
Title="File Histories"
|
||||
Height="600" Width="800">
|
||||
|
||||
<!-- Enable WindowChrome -->
|
||||
<WindowChrome.WindowChrome>
|
||||
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="32"/>
|
||||
</WindowChrome.WindowChrome>
|
||||
|
||||
<!-- Layout Window -->
|
||||
<Border Background="{StaticResource Brush.BG1}">
|
||||
<!-- Fix Maximize BUG -->
|
||||
<Border.Style>
|
||||
<Style TargetType="{x:Type Border}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Maximized">
|
||||
<Setter Property="Margin" Value="6"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Normal">
|
||||
<Setter Property="Margin" Value="0"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Titlebar -->
|
||||
<Grid Grid.Row="0" Background="{StaticResource Brush.BG4}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- LOGO & TITLE -->
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<Path
|
||||
Width="20" Height="20" Margin="6,-1,2,0"
|
||||
Style="{StaticResource Style.Icon}"
|
||||
Data="{StaticResource Icon.Git}"
|
||||
Fill="#FFF05133"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True"
|
||||
MouseLeftButtonDown="LogoMouseButtonDown"/>
|
||||
<Label Content="SOURCE GIT - FILE HISTORIES" FontWeight="Light"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Options -->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
<Button Click="Minimize" Width="32" Style="{StaticResource Style.Button.HighlightHover}">
|
||||
<Path Style="{StaticResource Style.WindowControlIcon}" Data="{StaticResource Icon.Minimize}"/>
|
||||
</Button>
|
||||
<Button Click="MaximizeOrRestore" Width="32" Style="{StaticResource Style.Button.HighlightHover}">
|
||||
<Path>
|
||||
<Path.Style>
|
||||
<Style TargetType="{x:Type Path}" BasedOn="{StaticResource Style.WindowControlIcon}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Maximized">
|
||||
<Setter Property="Data" Value="{StaticResource Icon.Restore}"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Normal">
|
||||
<Setter Property="Data" Value="{StaticResource Icon.Maximize}"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Path.Style>
|
||||
</Path>
|
||||
</Button>
|
||||
<Button Click="Quit" Width="32">
|
||||
<Button.Style>
|
||||
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource Style.Button.HighlightHover}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="Red"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
|
||||
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Close}"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Body -->
|
||||
<Border Grid.Row="1" ClipToBounds="True">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="300"/>
|
||||
<ColumnDefinition Width="1"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<DataGrid
|
||||
x:Name="commitList"
|
||||
Margin="2,0,0,0"
|
||||
Grid.Column="0"
|
||||
Background="{StaticResource Brush.BG2}"
|
||||
BorderThickness="0"
|
||||
SelectionMode="Single"
|
||||
SelectionChanged="CommitSelectionChanged">
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Width="*">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Border BorderBrush="{StaticResource Brush.BG4}" BorderThickness="0,0,0,1" Padding="4">
|
||||
<StackPanel Orientation="Vertical" Margin="2" MaxWidth="290">
|
||||
<Grid TextBlock.FontSize="11" TextBlock.Foreground="{StaticResource Brush.FG2}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="72"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Grid.Column="0" Padding="0">
|
||||
<Hyperlink RequestNavigate="NavigateToCommit" NavigateUri="{Binding SHA}" Foreground="DarkOrange" ToolTip="GOTO COMMIT">
|
||||
<Run Text="{Binding ShortSHA, Mode=OneWay}"/>
|
||||
</Hyperlink>
|
||||
</Label>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Author.Name}"/>
|
||||
<TextBlock Grid.Column="2" Text="{Binding Author.Time}"/>
|
||||
</Grid>
|
||||
|
||||
<TextBlock MaxWidth="280" Foreground="{StaticResource Brush.FG}" Text="{Binding Subject}" TextAlignment="Left" Padding="0" Margin="0,8,2,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- Loading tip -->
|
||||
<Path x:Name="loading" Grid.Column="0" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="0"/>
|
||||
</Path.RenderTransform>
|
||||
|
||||
<Path.Style>
|
||||
<Style BasedOn="{StaticResource Style.Icon}" TargetType="{x:Type Path}">
|
||||
<Setter Property="Width" Value="48"/>
|
||||
<Setter Property="Height" Value="48"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Fill" Value="{StaticResource Brush.FG2}"/>
|
||||
</Style>
|
||||
</Path.Style>
|
||||
</Path>
|
||||
|
||||
<Rectangle Grid.Column="1" Width="1" Fill="{StaticResource Brush.BG4}"/>
|
||||
|
||||
<local:DiffViewer x:Name="diff" Grid.Column="2" Margin="2,0"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
|
120
src/UI/FileHistories.xaml.cs
Normal file
120
src/UI/FileHistories.xaml.cs
Normal file
|
@ -0,0 +1,120 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Navigation;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// File histories panel.
|
||||
/// </summary>
|
||||
public partial class FileHistories : Window {
|
||||
private Git.Repository repo = null;
|
||||
private string file = null;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="file"></param>
|
||||
public FileHistories(Git.Repository repo, string file) {
|
||||
this.repo = repo;
|
||||
this.file = file;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
// Move to center
|
||||
var parent = App.Current.MainWindow;
|
||||
Left = parent.Left + (parent.Width - Width) * 0.5;
|
||||
Top = parent.Top + (parent.Height - Height) * 0.5;
|
||||
|
||||
// Show loading
|
||||
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
|
||||
anim.RepeatBehavior = RepeatBehavior.Forever;
|
||||
loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
|
||||
loading.Visibility = Visibility.Visible;
|
||||
|
||||
// Load commits
|
||||
Task.Run(() => {
|
||||
var commits = repo.Commits($"-n 10000 -- \"{file}\"");
|
||||
Dispatcher.Invoke(() => {
|
||||
commitList.ItemsSource = commits;
|
||||
commitList.SelectedIndex = 0;
|
||||
|
||||
loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
||||
loading.Visibility = Visibility.Collapsed;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logo click
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void LogoMouseButtonDown(object sender, MouseButtonEventArgs e) {
|
||||
var element = e.OriginalSource as FrameworkElement;
|
||||
if (element == null) return;
|
||||
|
||||
var pos = PointToScreen(new Point(0, 33));
|
||||
SystemCommands.ShowSystemMenu(this, pos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Minimize
|
||||
/// </summary>
|
||||
private void Minimize(object sender, RoutedEventArgs e) {
|
||||
SystemCommands.MinimizeWindow(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maximize/Restore
|
||||
/// </summary>
|
||||
private void MaximizeOrRestore(object sender, RoutedEventArgs e) {
|
||||
if (WindowState == WindowState.Normal) {
|
||||
SystemCommands.MaximizeWindow(this);
|
||||
} else {
|
||||
SystemCommands.RestoreWindow(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quit
|
||||
/// </summary>
|
||||
private void Quit(object sender, RoutedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Commit selection change event.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void CommitSelectionChanged(object sender, SelectionChangedEventArgs e) {
|
||||
if (e.AddedItems.Count != 1) return;
|
||||
|
||||
var commit = e.AddedItems[0] as Git.Commit;
|
||||
var start = $"{commit.SHA}^";
|
||||
if (commit.Parents.Count == 0) start = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
||||
|
||||
diff.Diff(repo, new DiffViewer.Option() {
|
||||
RevisionRange = new string[] { start, commit.SHA },
|
||||
Path = file
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigate to given string
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void NavigateToCommit(object sender, RequestNavigateEventArgs e) {
|
||||
repo.OnNavigateCommit?.Invoke(e.Uri.OriginalString);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
42
src/UI/GitFlowFinishBranch.xaml
Normal file
42
src/UI/GitFlowFinishBranch.xaml
Normal file
|
@ -0,0 +1,42 @@
|
|||
<UserControl x:Class="SourceGit.UI.GitFlowFinishBranch"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="160" d:DesignWidth="500" Height="128" Width="500">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="2" x:Name="txtTitle" FontWeight="DemiBold" FontSize="18"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" x:Name="txtBranchType" HorizontalAlignment="Right"/>
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}" Margin="4,0"/>
|
||||
<Label x:Name="txtBranchName"/>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="4" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
76
src/UI/GitFlowFinishBranch.xaml.cs
Normal file
76
src/UI/GitFlowFinishBranch.xaml.cs
Normal file
|
@ -0,0 +1,76 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Confirm finish git-flow branch dialog
|
||||
/// </summary>
|
||||
public partial class GitFlowFinishBranch : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
private Git.Branch branch = null;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="branch"></param>
|
||||
public GitFlowFinishBranch(Git.Repository repo, Git.Branch branch) {
|
||||
this.repo = repo;
|
||||
this.branch = branch;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
switch (branch.Kind) {
|
||||
case Git.Branch.Type.Feature:
|
||||
txtTitle.Content = "Git Flow - Finish Feature";
|
||||
txtBranchType.Content = "Feature :";
|
||||
break;
|
||||
case Git.Branch.Type.Release:
|
||||
txtTitle.Content = "Git Flow - Finish Release";
|
||||
txtBranchType.Content = "Release :";
|
||||
break;
|
||||
case Git.Branch.Type.Hotfix:
|
||||
txtTitle.Content = "Git Flow - Finish Hotfix";
|
||||
txtBranchType.Content = "Hotfix :";
|
||||
break;
|
||||
default:
|
||||
repo.GetPopupManager()?.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
txtBranchName.Content = branch.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show this dialog.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="branch"></param>
|
||||
public static void Show(Git.Repository repo, Git.Branch branch) {
|
||||
repo.GetPopupManager()?.Show(new GitFlowFinishBranch(repo, branch));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do finish
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private async void Sure(object sender, RoutedEventArgs e) {
|
||||
var popup = repo.GetPopupManager();
|
||||
popup?.Lock();
|
||||
await Task.Run(() => repo.FinishGitFlowBranch(branch));
|
||||
popup?.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel finish
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
repo.GetPopupManager()?.Close();
|
||||
}
|
||||
}
|
||||
}
|
60
src/UI/GitFlowSetup.xaml
Normal file
60
src/UI/GitFlowSetup.xaml
Normal file
|
@ -0,0 +1,60 @@
|
|||
<UserControl x:Class="SourceGit.UI.GitFlowSetup"
|
||||
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:helpers="clr-namespace:SourceGit.Helpers"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="160" d:DesignWidth="500" Height="304" Width="500">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Git Flow - Initialize"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="Production Branch :"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="1" x:Name="txtMaster" Padding="2,0" TextChanged="ValidateNames" VerticalContentAlignment="Center" helpers:TextBoxHelper.Placeholder="master" Height="24" Text="master"/>
|
||||
<Label Grid.Row="3" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="Development Branch :"/>
|
||||
<TextBox Grid.Row="3" Grid.Column="1" x:Name="txtDevelop" Padding="2,0" TextChanged="ValidateNames" VerticalContentAlignment="Center" helpers:TextBoxHelper.Placeholder="develop" Height="24" Text="develop"/>
|
||||
|
||||
<Label Grid.Row="5" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="Feature Prefix :"/>
|
||||
<TextBox Grid.Row="5" Grid.Column="1" x:Name="txtFeature" Padding="2,0" TextChanged="ValidateNames" VerticalContentAlignment="Center" helpers:TextBoxHelper.Placeholder="feature/" Height="24" Text="feature/"/>
|
||||
<Label Grid.Row="6" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="Release Prefix :"/>
|
||||
<TextBox Grid.Row="6" Grid.Column="1" x:Name="txtRelease" Padding="2,0" TextChanged="ValidateNames" VerticalContentAlignment="Center" helpers:TextBoxHelper.Placeholder="release/" Height="24" Text="release/"/>
|
||||
<Label Grid.Row="7" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="Hotfix Prefix :"/>
|
||||
<TextBox Grid.Row="7" Grid.Column="1" x:Name="txtHotfix" Padding="2,0" TextChanged="ValidateNames" VerticalContentAlignment="Center" helpers:TextBoxHelper.Placeholder="hotfix/" Height="24" Text="hotfix/"/>
|
||||
<Label Grid.Row="8" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Center" Content="Version Tag Prefix :"/>
|
||||
<TextBox Grid.Row="8" Grid.Column="1" x:Name="txtVersion" Padding="2,0" TextChanged="ValidateNames" VerticalContentAlignment="Center" helpers:TextBoxHelper.Placeholder="Optional." Height="24" Text=""/>
|
||||
|
||||
<Grid Grid.Row="10" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Column="0" x:Name="txtValidation" Foreground="Red"/>
|
||||
|
||||
<Button Grid.Column="1" x:Name="btnSure" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
124
src/UI/GitFlowSetup.xaml.cs
Normal file
124
src/UI/GitFlowSetup.xaml.cs
Normal file
|
@ -0,0 +1,124 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Dialog to initialize git flow.
|
||||
/// </summary>
|
||||
public partial class GitFlowSetup : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
private Regex regex = new Regex(@"^[\w\-/\.]+$");
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="opened"></param>
|
||||
public GitFlowSetup(Git.Repository opened) {
|
||||
repo = opened;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open this dialog.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
public static void Show(Git.Repository repo) {
|
||||
repo.GetPopupManager()?.Show(new GitFlowSetup(repo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start to initialize git-flow.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private async void Sure(object sender, RoutedEventArgs e) {
|
||||
var popup = repo.GetPopupManager();
|
||||
popup?.Lock();
|
||||
|
||||
var master = txtMaster.Text;
|
||||
var dev = txtDevelop.Text;
|
||||
var feature = txtFeature.Text;
|
||||
var release = txtRelease.Text;
|
||||
var hotfix = txtHotfix.Text;
|
||||
var version = txtVersion.Text;
|
||||
|
||||
await Task.Run(() => repo.EnableGitFlow(master, dev, feature, release, hotfix, version));
|
||||
popup?.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
repo.GetPopupManager()?.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate input names.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void ValidateNames(object sender, TextChangedEventArgs e) {
|
||||
if (!IsLoaded) return;
|
||||
|
||||
var master = txtMaster.Text;
|
||||
var dev = txtDevelop.Text;
|
||||
var feature = txtFeature.Text;
|
||||
var release = txtRelease.Text;
|
||||
var hotfix = txtHotfix.Text;
|
||||
|
||||
if (!ValidateBranch("Production", master)) return;
|
||||
if (!ValidateBranch("Development", dev)) return;
|
||||
|
||||
if (dev == master) {
|
||||
txtValidation.Content = "Development branch is same with production!";
|
||||
btnSure.IsEnabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ValidatePrefix("Feature", feature)) return;
|
||||
if (!ValidatePrefix("Release", release)) return;
|
||||
if (!ValidatePrefix("Hotfix", hotfix)) return;
|
||||
|
||||
txtValidation.Content = "";
|
||||
btnSure.IsEnabled = true;
|
||||
}
|
||||
|
||||
private bool ValidateBranch(string type, string name) {
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
txtValidation.Content = $"{type} branch name can't be empty";
|
||||
btnSure.IsEnabled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!regex.IsMatch(name)) {
|
||||
txtValidation.Content = $"{type} branch name contains invalid characters.";
|
||||
btnSure.IsEnabled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ValidatePrefix(string type, string prefix) {
|
||||
if (string.IsNullOrEmpty(prefix)) {
|
||||
txtValidation.Content = $"{type} prefix is required!";
|
||||
btnSure.IsEnabled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!regex.IsMatch(prefix)) {
|
||||
txtValidation.Content = $"{type} prefix contains invalid characters.";
|
||||
btnSure.IsEnabled = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
49
src/UI/GitFlowStartBranch.xaml
Normal file
49
src/UI/GitFlowStartBranch.xaml
Normal file
|
@ -0,0 +1,49 @@
|
|||
<UserControl x:Class="SourceGit.UI.GitFlowStartBranch"
|
||||
x:Name="me"
|
||||
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:helpers="clr-namespace:SourceGit.Helpers"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="160" d:DesignWidth="500" Height="128" Width="500">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="2" x:Name="txtTitle" FontWeight="DemiBold" FontSize="18"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" x:Name="txtPrefix" HorizontalAlignment="Right" FontSize="16" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="1" x:Name="txtName" VerticalContentAlignment="Center" helpers:TextBoxHelper.Placeholder="Enter name" Height="24">
|
||||
<TextBox.Text>
|
||||
<Binding ElementName="me" Path="SubName" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
|
||||
<Binding.ValidationRules>
|
||||
<helpers:BranchNameRule x:Name="nameValidator"/>
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
|
||||
<Grid Grid.Row="4" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
92
src/UI/GitFlowStartBranch.xaml.cs
Normal file
92
src/UI/GitFlowStartBranch.xaml.cs
Normal file
|
@ -0,0 +1,92 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Start git-flow branch dialog.
|
||||
/// </summary>
|
||||
public partial class GitFlowStartBranch : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
private Git.Branch.Type type = Git.Branch.Type.Feature;
|
||||
|
||||
/// <summary>
|
||||
/// Sub-name for this git-flow branch.
|
||||
/// </summary>
|
||||
public string SubName {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="type"></param>
|
||||
public GitFlowStartBranch(Git.Repository repo, Git.Branch.Type type) {
|
||||
this.repo = repo;
|
||||
this.type = type;
|
||||
|
||||
InitializeComponent();
|
||||
nameValidator.Repo = repo;
|
||||
|
||||
switch (type) {
|
||||
case Git.Branch.Type.Feature:
|
||||
var featurePrefix = repo.GetFeaturePrefix();
|
||||
txtTitle.Content = "Git Flow - Start Feature";
|
||||
txtPrefix.Content = featurePrefix;
|
||||
nameValidator.Prefix = featurePrefix;
|
||||
break;
|
||||
case Git.Branch.Type.Release:
|
||||
var releasePrefix = repo.GetReleasePrefix();
|
||||
txtTitle.Content = "Git Flow - Start Release";
|
||||
txtPrefix.Content = releasePrefix;
|
||||
nameValidator.Prefix = releasePrefix;
|
||||
break;
|
||||
case Git.Branch.Type.Hotfix:
|
||||
var hotfixPrefix = repo.GetHotfixPrefix();
|
||||
txtTitle.Content = "Git Flow - Start Hotfix";
|
||||
txtPrefix.Content = hotfixPrefix;
|
||||
nameValidator.Prefix = hotfixPrefix;
|
||||
break;
|
||||
default:
|
||||
repo.GetPopupManager()?.Close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display this dialog
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="type"></param>
|
||||
public static void Show(Git.Repository repo, Git.Branch.Type type) {
|
||||
repo.GetPopupManager()?.Show(new GitFlowStartBranch(repo, type));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start git-flow branch
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private async void Sure(object sender, RoutedEventArgs e) {
|
||||
txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
|
||||
if (Validation.GetHasError(txtName)) return;
|
||||
|
||||
var popup = repo.GetPopupManager();
|
||||
popup?.Lock();
|
||||
await Task.Run(() => repo.StartGitFlowBranch(type, SubName));
|
||||
popup?.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
repo.GetPopupManager()?.Close();
|
||||
}
|
||||
}
|
||||
}
|
185
src/UI/Histories.xaml
Normal file
185
src/UI/Histories.xaml
Normal file
|
@ -0,0 +1,185 @@
|
|||
<UserControl x:Class="SourceGit.UI.Histories"
|
||||
x:Name="me"
|
||||
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:git="clr-namespace:SourceGit.Git"
|
||||
xmlns:helpers="clr-namespace:SourceGit.Helpers"
|
||||
xmlns:sourcegit="clr-namespace:SourceGit"
|
||||
xmlns:converters="clr-namespace:SourceGit.Converters"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Unloaded="Cleanup">
|
||||
<Grid x:Name="layout" Background="{StaticResource Brush.BG1}">
|
||||
<!-- List Panel (SearchBar + DataGrid) -->
|
||||
<Grid x:Name="commitListPanel" Background="{StaticResource Brush.BG2}" ClipToBounds="True">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Loading Tip -->
|
||||
<Path x:Name="loading" Grid.RowSpan="2" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="0"/>
|
||||
</Path.RenderTransform>
|
||||
|
||||
<Path.Style>
|
||||
<Style BasedOn="{StaticResource Style.Icon}" TargetType="{x:Type Path}">
|
||||
<Setter Property="Width" Value="48"/>
|
||||
<Setter Property="Height" Value="48"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Fill" Value="{StaticResource Brush.FG2}"/>
|
||||
</Style>
|
||||
</Path.Style>
|
||||
</Path>
|
||||
|
||||
<!-- SearchBar -->
|
||||
<Grid x:Name="searchBar" Margin="0,-32,0,0" Grid.Row="0">
|
||||
<TextBox x:Name="txtSearch" Margin="4" Height="24" Padding="0,0,16,0" helpers:TextBoxHelper.Placeholder="SEARCH SHA/SUBJECT/AUTHOR. PRESS ENTER TO SEARCH, ESC TO QUIT" PreviewKeyDown="PreviewSearchKeyDown"/>
|
||||
<Button HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,8,0" Style="{StaticResource Style.Button.HighlightHover}" ToolTip="CLEAR" Click="ClearSearch">
|
||||
<Path Width="10" Height="10" Fill="{StaticResource Brush.Border1}" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Close}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- Commit DataGrid -->
|
||||
<DataGrid
|
||||
Grid.Row="1"
|
||||
x:Name="commitList"
|
||||
RowHeight="{x:Static helpers:CommitGraphMaker.UNIT_HEIGHT}"
|
||||
ScrollViewer.ScrollChanged="CommitListScrolled"
|
||||
SelectionChanged="CommitSelectChanged"
|
||||
SelectionUnit="FullRow">
|
||||
<DataGrid.Resources>
|
||||
<converters:IndentToMargin x:Key="CommitTitleMargin"/>
|
||||
|
||||
<Style x:Key="Style.DataGridText" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontFamily" Value="Consolas"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource Brush.FG}"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Padding" Value="16,0"/>
|
||||
</Style>
|
||||
<Style x:Key="Style.DataGridText.NoPadding" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontFamily" Value="Consolas"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource Brush.FG}"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTemplateColumn Width="*" IsReadOnly="True">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" Margin="{Binding GraphOffset, Converter={StaticResource CommitTitleMargin}}">
|
||||
<ItemsControl x:Name="Decorator" ItemsSource="{Binding Decorators}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" VerticalAlignment="Center"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type git:Decorator}">
|
||||
<Border x:Name="BG" Height="16" Margin="2,0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="18"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0" Background="{StaticResource Brush.BG5}">
|
||||
<Path x:Name="Icon" Width="8" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
|
||||
</Border>
|
||||
|
||||
<TextBlock x:Name="Name" Grid.Column="1" Text="{Binding Name}" FontSize="11" Padding="4,0" Foreground="Black" VerticalAlignment="Center" TextWrapping="NoWrap"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.Tag}">
|
||||
<Setter TargetName="BG" Property="Background" Value="#FF02C302"/>
|
||||
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Tag}"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.LocalBranchHead}">
|
||||
<Setter TargetName="BG" Property="Background" Value="#FFFFB835"/>
|
||||
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Branch}"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.RemoteBranchHead}">
|
||||
<Setter TargetName="BG" Property="Background" Value="#FFFFB835"/>
|
||||
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Remote}"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Type}" Value="{x:Static git:DecoratorType.CurrentBranchHead}">
|
||||
<Setter TargetName="BG" Property="Background" Value="#FFFFB835"/>
|
||||
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Check}"/>
|
||||
<Setter TargetName="Icon" Property="Fill" Value="Orange"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<TextBlock Text="{Binding Subject}" VerticalAlignment="Center" Margin="2,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding HasDecorators}" Value="False">
|
||||
<Setter TargetName="Decorator" Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Width="100" IsReadOnly="True" Binding="{Binding Committer.Name}" ElementStyle="{StaticResource Style.DataGridText}"/>
|
||||
<DataGridTextColumn Width="84" IsReadOnly="True" Binding="{Binding ShortSHA}" ElementStyle="{StaticResource Style.DataGridText}"/>
|
||||
<DataGridTextColumn Width="128" IsReadOnly="True" Binding="{Binding Committer.Time}" ElementStyle="{StaticResource Style.DataGridText.NoPadding}"/>
|
||||
</DataGrid.Columns>
|
||||
|
||||
<DataGrid.RowStyle>
|
||||
<Style TargetType="{x:Type DataGridRow}" BasedOn="{StaticResource Style.DataGridRow}">
|
||||
<EventSetter Event="ContextMenuOpening" Handler="CommitContextMenuOpening"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsMerged}" Value="False">
|
||||
<Setter Property="Opacity" Value=".4"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</DataGrid.RowStyle>
|
||||
</DataGrid>
|
||||
|
||||
<!-- Commit Graph -->
|
||||
<Border Grid.Row="1" Margin="0,0,310,0" ClipToBounds="True" IsHitTestVisible="False">
|
||||
<Canvas x:Name="commitGraph"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Split -->
|
||||
<GridSplitter x:Name="splitter" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"/>
|
||||
|
||||
<!-- Detail for selected commit -->
|
||||
<Grid x:Name="commitDetailPanel">
|
||||
<!-- Selected commit detail -->
|
||||
<local:CommitViewer x:Name="commitViewer"/>
|
||||
|
||||
<!-- Mask for select multi rows in commit list -->
|
||||
<Border x:Name="mask4MultiSelection" Background="{StaticResource Brush.BG1}" Visibility="Collapsed">
|
||||
<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"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- SWITCH LAYOUT -->
|
||||
<ToggleButton
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0,4,8,0"
|
||||
Style="{StaticResource Style.ToggleButton.Orientation}"
|
||||
ToolTip="Toggle Horizontal/Vertical Layout"
|
||||
IsChecked="{Binding Source={x:Static sourcegit:App.Preference}, Path=UIUseHorizontalLayout, Mode=TwoWay}"
|
||||
Checked="ChangeOrientation" Unchecked="ChangeOrientation"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
653
src/UI/Histories.xaml.cs
Normal file
653
src/UI/Histories.xaml.cs
Normal file
|
@ -0,0 +1,653 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Commit histories viewer
|
||||
/// </summary>
|
||||
public partial class Histories : UserControl {
|
||||
|
||||
/// <summary>
|
||||
/// Current opened repository.
|
||||
/// </summary>
|
||||
public Git.Repository Repo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cached commits.
|
||||
/// </summary>
|
||||
private List<Git.Commit> cachedCommits = new List<Git.Commit>();
|
||||
|
||||
/// <summary>
|
||||
/// Is in search mode?
|
||||
/// </summary>
|
||||
private bool isSearchMode = false;
|
||||
|
||||
/// <summary>
|
||||
/// Regex to test search input.
|
||||
/// </summary>
|
||||
private Regex commitRegex = new Regex(@"^[0-9a-f]{6,40}$", RegexOptions.None);
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public Histories() {
|
||||
InitializeComponent();
|
||||
|
||||
mask4MultiSelection.Visibility = Visibility.Visible;
|
||||
txtTotalSelected.Content = "SELECT COMMIT TO VIEW DETAIL";
|
||||
|
||||
ChangeOrientation(null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigate to given commit.
|
||||
/// </summary>
|
||||
/// <param name="commit"></param>
|
||||
public void Navigate(string commit) {
|
||||
if (string.IsNullOrEmpty(commit)) return;
|
||||
|
||||
foreach (var item in commitList.ItemsSource) {
|
||||
var c = item as Git.Commit;
|
||||
if (c.SHA.StartsWith(commit)) {
|
||||
commitList.SelectedItem = c;
|
||||
commitList.ScrollIntoView(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loading tips.
|
||||
/// </summary>
|
||||
/// <param name="enabled"></param>
|
||||
public void SetLoadingEnabled(bool enabled) {
|
||||
if (enabled) {
|
||||
loading.Visibility = Visibility.Visible;
|
||||
|
||||
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
|
||||
anim.RepeatBehavior = RepeatBehavior.Forever;
|
||||
loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
|
||||
} else {
|
||||
loading.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
||||
loading.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
#region DATA
|
||||
public void SetCommits(List<Git.Commit> commits) {
|
||||
cachedCommits = commits;
|
||||
if (isSearchMode) return;
|
||||
|
||||
var maker = Helpers.CommitGraphMaker.Parse(commits);
|
||||
|
||||
Dispatcher.Invoke(() => {
|
||||
commitGraph.Children.Clear();
|
||||
isSearchMode = false;
|
||||
txtSearch.Text = "";
|
||||
|
||||
// Draw all lines.
|
||||
foreach (var path in maker.Lines) {
|
||||
var size = path.Points.Count;
|
||||
var geo = new StreamGeometry();
|
||||
var last = path.Points[0];
|
||||
|
||||
using (var ctx = geo.Open()) {
|
||||
ctx.BeginFigure(last, false, false);
|
||||
|
||||
for (int i = 1; i < size; i++) {
|
||||
var cur = path.Points[i];
|
||||
|
||||
if (cur.X > last.X) {
|
||||
ctx.QuadraticBezierTo(new Point(cur.X, last.Y), cur, true, false);
|
||||
} else if (cur.X < last.X) {
|
||||
if (i < size - 1) {
|
||||
cur.Y += Helpers.CommitGraphMaker.HALF_HEIGHT;
|
||||
|
||||
var midY = (last.Y + cur.Y) / 2;
|
||||
var midX = (last.X + cur.X) / 2;
|
||||
ctx.PolyQuadraticBezierTo(new Point[] {
|
||||
new Point(last.X, midY),
|
||||
new Point(midX, midY),
|
||||
new Point(cur.X, midY),
|
||||
cur
|
||||
}, true, false);
|
||||
} else {
|
||||
ctx.QuadraticBezierTo(new Point(last.X, cur.Y), cur, true, false);
|
||||
}
|
||||
} else {
|
||||
ctx.LineTo(cur, true, false);
|
||||
}
|
||||
|
||||
last = cur;
|
||||
}
|
||||
}
|
||||
|
||||
geo.Freeze();
|
||||
|
||||
var p = new Path();
|
||||
p.Data = geo;
|
||||
p.Stroke = path.Brush;
|
||||
p.StrokeThickness = 2;
|
||||
commitGraph.Children.Add(p);
|
||||
}
|
||||
maker.Lines.Clear();
|
||||
|
||||
// Draw short links
|
||||
foreach (var link in maker.Links) {
|
||||
var geo = new StreamGeometry();
|
||||
|
||||
using (var ctx = geo.Open()) {
|
||||
ctx.BeginFigure(link.Start, false, false);
|
||||
ctx.QuadraticBezierTo(link.Control, link.End, true, false);
|
||||
}
|
||||
|
||||
geo.Freeze();
|
||||
|
||||
var p = new Path();
|
||||
p.Data = geo;
|
||||
p.Stroke = link.Brush;
|
||||
p.StrokeThickness = 2;
|
||||
commitGraph.Children.Add(p);
|
||||
}
|
||||
maker.Links.Clear();
|
||||
|
||||
// Draw points.
|
||||
foreach (var dot in maker.Dots) {
|
||||
var ellipse = new Ellipse();
|
||||
ellipse.Height = 6;
|
||||
ellipse.Width = 6;
|
||||
ellipse.Fill = dot.Color;
|
||||
ellipse.SetValue(Canvas.LeftProperty, dot.X);
|
||||
ellipse.SetValue(Canvas.TopProperty, dot.Y);
|
||||
commitGraph.Children.Add(ellipse);
|
||||
}
|
||||
maker.Dots.Clear();
|
||||
|
||||
commitList.ItemsSource = new List<Git.Commit>(cachedCommits);
|
||||
// Navigate(maker.Highlight);
|
||||
SetLoadingEnabled(false);
|
||||
});
|
||||
}
|
||||
|
||||
public void SetSearchResult(List<Git.Commit> commits) {
|
||||
isSearchMode = true;
|
||||
|
||||
foreach (var c in commits) c.GraphOffset = 0;
|
||||
|
||||
Dispatcher.Invoke(() => {
|
||||
commitGraph.Children.Clear();
|
||||
commitList.ItemsSource = new List<Git.Commit>(commits);
|
||||
SetLoadingEnabled(false);
|
||||
});
|
||||
}
|
||||
|
||||
private void Cleanup(object sender, RoutedEventArgs e) {
|
||||
commitGraph.Children.Clear();
|
||||
commitList.ItemsSource = null;
|
||||
cachedCommits.Clear();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SEARCH_BAR
|
||||
public void OpenSearchBar() {
|
||||
if (searchBar.Margin.Top == 0) return;
|
||||
|
||||
ThicknessAnimation anim = new ThicknessAnimation();
|
||||
anim.From = new Thickness(0, -32, 0, 0);
|
||||
anim.To = new Thickness(0);
|
||||
anim.Duration = TimeSpan.FromSeconds(.3);
|
||||
searchBar.BeginAnimation(Grid.MarginProperty, anim);
|
||||
|
||||
txtSearch.Focus();
|
||||
}
|
||||
|
||||
public void HideSearchBar() {
|
||||
if (searchBar.Margin.Top != 0) return;
|
||||
|
||||
ClearSearch(null, null);
|
||||
|
||||
ThicknessAnimation anim = new ThicknessAnimation();
|
||||
anim.From = new Thickness(0);
|
||||
anim.To = new Thickness(0, -32, 0, 0);
|
||||
anim.Duration = TimeSpan.FromSeconds(.3);
|
||||
searchBar.BeginAnimation(Grid.MarginProperty, anim);
|
||||
}
|
||||
|
||||
private void ClearSearch(object sender, RoutedEventArgs e) {
|
||||
txtSearch.Text = "";
|
||||
if (isSearchMode) {
|
||||
isSearchMode = false;
|
||||
SetLoadingEnabled(true);
|
||||
Task.Run(() => SetCommits(cachedCommits));
|
||||
}
|
||||
}
|
||||
|
||||
private void PreviewSearchKeyDown(object sender, KeyEventArgs e) {
|
||||
if (e.Key == Key.Enter) {
|
||||
string search = txtSearch.Text;
|
||||
if (string.IsNullOrEmpty(search)) {
|
||||
ClearSearch(sender, e);
|
||||
} else if (commitRegex.IsMatch(search)) {
|
||||
SetLoadingEnabled(true);
|
||||
Task.Run(() => {
|
||||
var commits = Repo.Commits($"search -n 1");
|
||||
SetSearchResult(commits);
|
||||
});
|
||||
} else {
|
||||
SetLoadingEnabled(true);
|
||||
|
||||
Task.Run(() => {
|
||||
List<Git.Commit> found = new List<Git.Commit>();
|
||||
|
||||
foreach (var commit in cachedCommits) {
|
||||
if (commit.Subject.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
(commit.Author != null && commit.Author.Name.Equals(search, StringComparison.OrdinalIgnoreCase)) ||
|
||||
(commit.Committer != null && commit.Committer.Name.Equals(search, StringComparison.OrdinalIgnoreCase)) ||
|
||||
commit.Message.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0) {
|
||||
found.Add(commit);
|
||||
}
|
||||
}
|
||||
|
||||
SetSearchResult(found);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region COMMIT_DATAGRID_AND_GRAPH
|
||||
private void CommitListScrolled(object sender, ScrollChangedEventArgs e) {
|
||||
commitGraph.Margin = new Thickness(0, -e.VerticalOffset * Helpers.CommitGraphMaker.UNIT_HEIGHT, 0, 0);
|
||||
}
|
||||
|
||||
private void CommitSelectChanged(object sender, SelectionChangedEventArgs e) {
|
||||
mask4MultiSelection.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) {
|
||||
mask4MultiSelection.Visibility = Visibility.Visible;
|
||||
txtTotalSelected.Content = $"SELECTED {selected.Count} COMMITS";
|
||||
}
|
||||
}
|
||||
|
||||
private MenuItem GetCurrentBranchContextMenu(Git.Branch branch) {
|
||||
var icon = new Path();
|
||||
icon.Style = FindResource("Style.Icon") as Style;
|
||||
icon.Data = FindResource("Icon.Branch") as Geometry;
|
||||
icon.VerticalAlignment = VerticalAlignment.Bottom;
|
||||
icon.Width = 10;
|
||||
|
||||
var submenu = new MenuItem();
|
||||
submenu.Header = branch.Name;
|
||||
submenu.Icon = icon;
|
||||
|
||||
if (!string.IsNullOrEmpty(branch.Upstream)) {
|
||||
var upstream = branch.Upstream.Substring(13);
|
||||
var fastForward = new MenuItem();
|
||||
fastForward.Header = $"Fast-Forward to '{upstream}'";
|
||||
fastForward.Click += (o, e) => {
|
||||
Merge.StartDirectly(Repo, upstream, branch.Name);
|
||||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(fastForward);
|
||||
|
||||
var pull = new MenuItem();
|
||||
pull.Header = $"Pull '{upstream}' ...";
|
||||
pull.Click += (o, e) => {
|
||||
Pull.Show(Repo);
|
||||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(pull);
|
||||
}
|
||||
|
||||
var push = new MenuItem();
|
||||
push.Header = $"Push '{branch.Name}' ...";
|
||||
push.Click += (o, e) => {
|
||||
Push.Show(Repo, branch);
|
||||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(push);
|
||||
submenu.Items.Add(new Separator());
|
||||
|
||||
if (branch.Kind != Git.Branch.Type.Normal) {
|
||||
var flowIcon = new Path();
|
||||
flowIcon.Style = FindResource("Style.Icon") as Style;
|
||||
flowIcon.Data = FindResource("Icon.Flow") as Geometry;
|
||||
flowIcon.Width = 10;
|
||||
|
||||
var finish = new MenuItem();
|
||||
finish.Header = $"Git Flow - Finish '{branch.Name}'";
|
||||
finish.Icon = flowIcon;
|
||||
finish.Click += (o, e) => {
|
||||
GitFlowFinishBranch.Show(Repo, branch);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
submenu.Items.Add(finish);
|
||||
submenu.Items.Add(new Separator());
|
||||
}
|
||||
|
||||
var rename = new MenuItem();
|
||||
rename.Header = "Rename ...";
|
||||
rename.Click += (o, e) => {
|
||||
RenameBranch.Show(Repo, branch);
|
||||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(rename);
|
||||
|
||||
return submenu;
|
||||
}
|
||||
|
||||
private MenuItem GetOtherBranchContextMenu(Git.Branch current, Git.Branch branch, bool merged) {
|
||||
var icon = new Path();
|
||||
icon.Style = FindResource("Style.Icon") as Style;
|
||||
icon.Data = FindResource("Icon.Branch") as Geometry;
|
||||
icon.VerticalAlignment = VerticalAlignment.Bottom;
|
||||
icon.Width = 10;
|
||||
|
||||
var submenu = new MenuItem();
|
||||
submenu.Header = branch.Name;
|
||||
submenu.Icon = icon;
|
||||
|
||||
var checkout = new MenuItem();
|
||||
checkout.Header = $"Checkout '{branch.Name}'";
|
||||
checkout.Click += (o, e) => {
|
||||
if (branch.IsLocal) {
|
||||
Task.Run(() => Repo.Checkout(branch.Name));
|
||||
} else {
|
||||
var upstream = $"refs/remotes/{branch.Name}";
|
||||
var tracked = Repo.Branches().Find(b => b.IsLocal && b.Upstream == upstream);
|
||||
|
||||
if (tracked == null) {
|
||||
CreateBranch.Show(Repo, branch);
|
||||
} else if (!tracked.IsCurrent) {
|
||||
Task.Run(() => Repo.Checkout(tracked.Name));
|
||||
}
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(checkout);
|
||||
|
||||
var merge = new MenuItem();
|
||||
merge.Header = $"Merge into '{current.Name}' ...";
|
||||
merge.IsEnabled = !merged;
|
||||
merge.Click += (o, e) => {
|
||||
Merge.Show(Repo, branch.Name, current.Name);
|
||||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(merge);
|
||||
submenu.Items.Add(new Separator());
|
||||
|
||||
if (branch.Kind != Git.Branch.Type.Normal) {
|
||||
var flowIcon = new Path();
|
||||
flowIcon.Style = FindResource("Style.Icon") as Style;
|
||||
flowIcon.Data = FindResource("Icon.Flow") as Geometry;
|
||||
flowIcon.Width = 10;
|
||||
|
||||
var finish = new MenuItem();
|
||||
finish.Header = $"Git Flow - Finish '{branch.Name}'";
|
||||
finish.Icon = flowIcon;
|
||||
finish.Click += (o, e) => {
|
||||
GitFlowFinishBranch.Show(Repo, branch);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
submenu.Items.Add(finish);
|
||||
submenu.Items.Add(new Separator());
|
||||
}
|
||||
|
||||
var rename = new MenuItem();
|
||||
rename.Header = "Rename ...";
|
||||
rename.Visibility = branch.IsLocal ? Visibility.Visible : Visibility.Collapsed;
|
||||
rename.Click += (o, e) => {
|
||||
RenameBranch.Show(Repo, current);
|
||||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(rename);
|
||||
|
||||
var delete = new MenuItem();
|
||||
delete.Header = "Delete ...";
|
||||
delete.Click += (o, e) => {
|
||||
DeleteBranch.Show(Repo, branch);
|
||||
};
|
||||
submenu.Items.Add(delete);
|
||||
|
||||
return submenu;
|
||||
}
|
||||
|
||||
private MenuItem GetTagContextMenu(Git.Tag tag) {
|
||||
var icon = new Path();
|
||||
icon.Style = FindResource("Style.Icon") as Style;
|
||||
icon.Data = FindResource("Icon.Tag") as Geometry;
|
||||
icon.Width = 10;
|
||||
|
||||
var submenu = new MenuItem();
|
||||
submenu.Header = tag.Name;
|
||||
submenu.Icon = icon;
|
||||
submenu.MinWidth = 200;
|
||||
|
||||
var push = new MenuItem();
|
||||
push.Header = "Push ...";
|
||||
push.Click += (o, e) => {
|
||||
PushTag.Show(Repo, tag);
|
||||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(push);
|
||||
|
||||
var delete = new MenuItem();
|
||||
delete.Header = "Delete ...";
|
||||
delete.Click += (o, e) => {
|
||||
DeleteTag.Show(Repo, tag);
|
||||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(delete);
|
||||
|
||||
return submenu;
|
||||
}
|
||||
|
||||
private void CommitContextMenuOpening(object sender, ContextMenuEventArgs ev) {
|
||||
var row = sender as DataGridRow;
|
||||
if (row == null) return;
|
||||
|
||||
var commit = row.DataContext as Git.Commit;
|
||||
if (commit == null) return;
|
||||
commitList.SelectedItem = commit;
|
||||
|
||||
var current = Repo.CurrentBranch();
|
||||
if (current == null) return;
|
||||
|
||||
var menu = new ContextMenu();
|
||||
menu.MinWidth = 200;
|
||||
|
||||
// Decorators.
|
||||
{
|
||||
var localBranchContextMenus = new List<MenuItem>();
|
||||
var remoteBranchContextMenus = new List<MenuItem>();
|
||||
var tagContextMenus = new List<MenuItem>();
|
||||
|
||||
foreach (var d in commit.Decorators) {
|
||||
if (d.Type == Git.DecoratorType.CurrentBranchHead) {
|
||||
menu.Items.Add(GetCurrentBranchContextMenu(current));
|
||||
} else if (d.Type == Git.DecoratorType.LocalBranchHead) {
|
||||
var branch = Repo.Branches().Find(b => b.Name == d.Name);
|
||||
if (branch != null) {
|
||||
localBranchContextMenus.Add(GetOtherBranchContextMenu(current, branch, commit.IsMerged));
|
||||
}
|
||||
} else if (d.Type == Git.DecoratorType.RemoteBranchHead) {
|
||||
var branch = Repo.Branches().Find(b => b.Name == d.Name);
|
||||
if (branch != null) {
|
||||
remoteBranchContextMenus.Add(GetOtherBranchContextMenu(current, branch, commit.IsMerged));
|
||||
}
|
||||
} else if (d.Type == Git.DecoratorType.Tag) {
|
||||
var tag = Repo.Tags().Find(t => t.Name == d.Name);
|
||||
if (tag != null) tagContextMenus.Add(GetTagContextMenu(tag));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var m in localBranchContextMenus) menu.Items.Add(m);
|
||||
foreach (var m in remoteBranchContextMenus) menu.Items.Add(m);
|
||||
if (menu.Items.Count > 0) menu.Items.Add(new Separator());
|
||||
|
||||
if (tagContextMenus.Count > 0) {
|
||||
foreach (var m in tagContextMenus) menu.Items.Add(m);
|
||||
menu.Items.Add(new Separator());
|
||||
}
|
||||
}
|
||||
|
||||
// Reset
|
||||
var reset = new MenuItem();
|
||||
reset.Header = $"Reset '{current.Name}' To Here";
|
||||
reset.Visibility = commit.IsHEAD ? Visibility.Collapsed : Visibility.Visible;
|
||||
reset.Click += (o, e) => {
|
||||
Reset.Show(Repo, commit);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(reset);
|
||||
|
||||
// Rebase or interactive rebase
|
||||
var rebase = new MenuItem();
|
||||
rebase.Header = commit.IsMerged ? $"Interactive Rebase '{current.Name}' From Here" : $"Rebase '{current.Name}' To Here";
|
||||
rebase.Visibility = commit.IsHEAD ? Visibility.Collapsed : Visibility.Visible;
|
||||
rebase.Click += (o, e) => {
|
||||
if (commit.IsMerged) {
|
||||
if (Repo.LocalChanges().Count > 0) {
|
||||
App.RaiseError("You have local changes!!!");
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var dialog = new InteractiveRebase(Repo, commit);
|
||||
dialog.Owner = App.Current.MainWindow;
|
||||
dialog.ShowDialog();
|
||||
} else {
|
||||
Rebase.Show(Repo, commit);
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(rebase);
|
||||
|
||||
// Cherry-Pick
|
||||
var cherryPick = new MenuItem();
|
||||
cherryPick.Header = "Cherry-Pick This Commit";
|
||||
cherryPick.Visibility = commit.IsMerged ? Visibility.Collapsed : Visibility.Visible;
|
||||
cherryPick.Click += (o, e) => {
|
||||
CherryPick.Show(Repo, commit);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(cherryPick);
|
||||
|
||||
// Revert commit
|
||||
var revert = new MenuItem();
|
||||
revert.Header = "Revert commit";
|
||||
revert.Visibility = !commit.IsMerged ? Visibility.Collapsed : Visibility.Visible;
|
||||
revert.Click += (o, e) => {
|
||||
Revert.Show(Repo, commit);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(revert);
|
||||
menu.Items.Add(new Separator());
|
||||
|
||||
// Common
|
||||
var createBranch = new MenuItem();
|
||||
createBranch.Header = "Create Branch";
|
||||
createBranch.Click += (o, e) => {
|
||||
CreateBranch.Show(Repo, commit);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(createBranch);
|
||||
var createTag = new MenuItem();
|
||||
createTag.Header = "Create Tag";
|
||||
createTag.Click += (o, e) => {
|
||||
CreateTag.Show(Repo, commit);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(createTag);
|
||||
menu.Items.Add(new Separator());
|
||||
|
||||
// Save as patch
|
||||
var patch = new MenuItem();
|
||||
patch.Header = "Save As Patch";
|
||||
patch.Click += (o, e) => {
|
||||
var dialog = new System.Windows.Forms.FolderBrowserDialog();
|
||||
dialog.ShowNewFolderButton = true;
|
||||
|
||||
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
|
||||
Repo.RunCommand($"format-patch {commit.SHA} -1 -o \"{dialog.SelectedPath}\"", null);
|
||||
}
|
||||
};
|
||||
menu.Items.Add(patch);
|
||||
menu.Items.Add(new Separator());
|
||||
|
||||
// Copy SHA
|
||||
var copySHA = new MenuItem();
|
||||
copySHA.Header = "Copy Commit SHA";
|
||||
copySHA.Click += (o, e) => {
|
||||
Clipboard.SetText(commit.SHA);
|
||||
};
|
||||
menu.Items.Add(copySHA);
|
||||
|
||||
// Copy info
|
||||
var copyInfo = new MenuItem();
|
||||
copyInfo.Header = "Copy Commit Info";
|
||||
copyInfo.Click += (o, e) => {
|
||||
Clipboard.SetText(string.Format(
|
||||
"SHA: {0}\nTITLE: {1}\nAUTHOR: {2} <{3}>\nTIME: {4}",
|
||||
commit.SHA, commit.Subject, commit.Committer.Name, commit.Committer.Email, commit.Committer.Time));
|
||||
};
|
||||
menu.Items.Add(copyInfo);
|
||||
|
||||
menu.IsOpen = true;
|
||||
ev.Handled = true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region LAYOUT
|
||||
private void ChangeOrientation(object sender, RoutedEventArgs e) {
|
||||
if (commitDetailPanel == null || splitter == null || commitListPanel == null) return;
|
||||
|
||||
layout.RowDefinitions.Clear();
|
||||
layout.ColumnDefinitions.Clear();
|
||||
|
||||
if (App.Preference.UIUseHorizontalLayout) {
|
||||
layout.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star), MinWidth = 200 });
|
||||
layout.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(2) });
|
||||
layout.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star), MinWidth = 200 });
|
||||
|
||||
Grid.SetRow(commitListPanel, 0);
|
||||
Grid.SetRow(splitter, 0);
|
||||
Grid.SetRow(commitDetailPanel, 0);
|
||||
Grid.SetColumn(commitListPanel, 0);
|
||||
Grid.SetColumn(splitter, 1);
|
||||
Grid.SetColumn(commitDetailPanel, 2);
|
||||
} else {
|
||||
layout.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star), MinHeight = 100 });
|
||||
layout.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(2) });
|
||||
layout.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star), MinHeight = 100 });
|
||||
|
||||
Grid.SetRow(commitListPanel, 0);
|
||||
Grid.SetRow(splitter, 1);
|
||||
Grid.SetRow(commitDetailPanel, 2);
|
||||
Grid.SetColumn(commitListPanel, 0);
|
||||
Grid.SetColumn(splitter, 0);
|
||||
Grid.SetColumn(commitDetailPanel, 0);
|
||||
}
|
||||
|
||||
layout.InvalidateVisual();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
45
src/UI/Init.xaml
Normal file
45
src/UI/Init.xaml
Normal file
|
@ -0,0 +1,45 @@
|
|||
<UserControl x:Class="SourceGit.UI.Init"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="160" d:DesignWidth="500" Height="160" Width="500">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="16"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="150"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="2" FontWeight="DemiBold" FontSize="18" Content="Initialize Repository"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Content="Path :"/>
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
|
||||
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Folder}" Margin="4,0"/>
|
||||
<Label x:Name="txtPath"/>
|
||||
</StackPanel>
|
||||
|
||||
<Label Grid.Row="3" Grid.Column="1" Content="Invalid repository detected. Run `git init` under this path?" Foreground="{StaticResource Brush.FG2}"/>
|
||||
|
||||
<Grid Grid.Row="5" Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1" Click="Sure" Content="SURE" Style="{StaticResource Style.Button.AccentBordered}"/>
|
||||
<Button Grid.Column="3" Click="Cancel" Content="CANCEL" Style="{StaticResource Style.Button.Bordered}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
58
src/UI/Init.xaml.cs
Normal file
58
src/UI/Init.xaml.cs
Normal file
|
@ -0,0 +1,58 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// `git init` confirm panel.
|
||||
/// </summary>
|
||||
public partial class Init : UserControl {
|
||||
private PopupManager popup = null;
|
||||
private string workingDir = null;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="mgr"></param>
|
||||
/// <param name="path"></param>
|
||||
public Init(PopupManager mgr, string path) {
|
||||
popup = mgr;
|
||||
workingDir = path;
|
||||
InitializeComponent();
|
||||
txtPath.Content = path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do `git init`
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private async void Sure(object sender, RoutedEventArgs e) {
|
||||
popup.Lock();
|
||||
|
||||
await Task.Run(() => {
|
||||
var errs = Git.Repository.RunCommand(workingDir, "init -q", null);
|
||||
if (errs != null) {
|
||||
App.RaiseError(errs);
|
||||
} else {
|
||||
App.Preference.AddRepository(workingDir, "");
|
||||
}
|
||||
});
|
||||
|
||||
popup.Close(true);
|
||||
|
||||
var repo = App.Preference.FindRepository(workingDir);
|
||||
if (repo != null) repo.Open();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
popup.Close();
|
||||
}
|
||||
}
|
||||
}
|
257
src/UI/InteractiveRebase.xaml
Normal file
257
src/UI/InteractiveRebase.xaml
Normal file
|
@ -0,0 +1,257 @@
|
|||
<Window x:Class="SourceGit.UI.InteractiveRebase"
|
||||
x:Name="me"
|
||||
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:SourceGit.UI"
|
||||
xmlns:helpers="clr-namespace:SourceGit.Helpers"
|
||||
mc:Ignorable="d"
|
||||
Title="Interactive Rebase"
|
||||
Height="600" Width="800">
|
||||
|
||||
<!-- Enable WindowChrome -->
|
||||
<WindowChrome.WindowChrome>
|
||||
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="32"/>
|
||||
</WindowChrome.WindowChrome>
|
||||
|
||||
<!-- Window Content -->
|
||||
<Border Background="{StaticResource Brush.BG1}">
|
||||
<!-- Fix Maximize BUG -->
|
||||
<Border.Style>
|
||||
<Style TargetType="{x:Type Border}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Maximized">
|
||||
<Setter Property="Margin" Value="6"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Normal">
|
||||
<Setter Property="Margin" Value="0"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="1"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="40"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title bar -->
|
||||
<Grid Grid.Row="0" Background="{StaticResource Brush.BG4}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Logo & TITLE -->
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal">
|
||||
<Path
|
||||
Width="20" Height="20" Margin="6,-1,2,0"
|
||||
Style="{StaticResource Style.Icon}"
|
||||
Data="{StaticResource Icon.Git}"
|
||||
Fill="#FFF05133"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True"
|
||||
MouseLeftButtonDown="LogoMouseButtonDown"/>
|
||||
<Label Content="SOURCE GIT - INTERACTIVE REBASE" FontWeight="Light"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Options -->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
<Button Click="Minimize" Width="32" Style="{StaticResource Style.Button.HighlightHover}">
|
||||
<Path Style="{StaticResource Style.WindowControlIcon}" Data="{StaticResource Icon.Minimize}"/>
|
||||
</Button>
|
||||
<Button Click="MaximizeOrRestore" Width="32" Style="{StaticResource Style.Button.HighlightHover}">
|
||||
<Path>
|
||||
<Path.Style>
|
||||
<Style TargetType="{x:Type Path}" BasedOn="{StaticResource Style.WindowControlIcon}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Maximized">
|
||||
<Setter Property="Data" Value="{StaticResource Icon.Restore}"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding WindowState, ElementName=me}" Value="Normal">
|
||||
<Setter Property="Data" Value="{StaticResource Icon.Maximize}"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Path.Style>
|
||||
</Path>
|
||||
</Button>
|
||||
<Button Click="Quit" Width="32">
|
||||
<Button.Style>
|
||||
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource Style.Button.HighlightHover}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="Red"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
|
||||
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Close}"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Commit List -->
|
||||
<ListView Grid.Row="1"
|
||||
x:Name="commitList"
|
||||
Background="Transparent"
|
||||
Style="{StaticResource Style.ListView.Borderless}"
|
||||
ItemsSource="{Binding ElementName=me, Path=Items}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
SelectionChanged="CommitSelectionChanged"
|
||||
BorderThickness="0">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:InteractiveRebaseItem}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="128"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ComboBox
|
||||
Grid.Column="0"
|
||||
ItemsSource="{Binding Source={x:Static local:InteractiveRebaseModeInfo.Supported}}"
|
||||
SelectedIndex="{Binding Path=Mode, Mode=TwoWay}"
|
||||
BorderThickness="0">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:InteractiveRebaseModeInfo}">
|
||||
<Grid Height="20">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="20"/>
|
||||
<ColumnDefinition Width="60"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Path Grid.Column="0" Width="12" Height="12" Margin="4,0,0,0" Fill="{Binding Theme}" Style="{StaticResource Style.Icon}" Data="M 0,0 A 180,180 180 1 1 1,1 Z"/>
|
||||
<Label Grid.Column="1" Content="{Binding Title}" Padding="4,0"/>
|
||||
<Label Grid.Column="2" Content="{Binding Desc}" Foreground="{StaticResource Brush.FG2}" FontSize="11" Padding="4,0"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<Grid Grid.Column="1">
|
||||
<ContentControl x:Name="MessageEditorAnchor" MouseDoubleClick="PopupMessageEditor">
|
||||
<TextBlock Text="{Binding Subject, Mode=TwoWay}" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}"/>
|
||||
</ContentControl>
|
||||
|
||||
<Popup x:Name="MessageEditor" Placement="Bottom" IsOpen="{Binding IsEditorOpened}" VerticalOffset="1" Height="150" Width="400" PlacementTarget="{Binding ElementName=MessageEditorAnchor}">
|
||||
<Border BorderBrush="{StaticResource Brush.Accent1}" BorderThickness="1" Background="{StaticResource Brush.BG4}">
|
||||
<Grid Margin="8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="32"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Border Grid.Row="0" BorderBrush="{StaticResource Brush.Border1}" BorderThickness="1">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="24"/>
|
||||
<RowDefinition Height="1"/>
|
||||
<RowDefinition Height="79"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBox Grid.Row="0" Text="{Binding EditSubject, Mode=TwoWay}" Height="32" Padding="2,0" helpers:TextBoxHelper.Placeholder="Enter commit subject" BorderThickness="0"/>
|
||||
<Rectangle Grid.Row="1" Height="1" Fill="{StaticResource Brush.FG}" Opacity="0.1"/>
|
||||
<TextBox Grid.Row="2" Text="{Binding EditMessage, Mode=TwoWay}" TextChanged="CommitMessageChanged" Height="79" ScrollViewer.VerticalScrollBarVisibility="Auto" Padding="2" helpers:TextBoxHelper.Placeholder="Enter commit description. Optional" helpers:TextBoxHelper.PlaceholderBaseline="Top" BorderThickness="0" AcceptsReturn="True" AcceptsTab="True" TextWrapping="Wrap"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,8,0,0">
|
||||
<Button Click="ApplyMessageEdit" Height="24" Style="{StaticResource Style.Button.AccentBordered}" BorderThickness="1" Content="APPLY" Margin="8,0"/>
|
||||
<Button Click="HideMessageEditor" Height="24" Style="{StaticResource Style.Button.Bordered}" BorderThickness="1" Content="CANCEL"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Foreground="{StaticResource Brush.FG}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Commit.Committer.Name}"/>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="3"
|
||||
Foreground="{StaticResource Brush.FG}"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Commit.Committer.Time}"/>
|
||||
|
||||
<StackPanel
|
||||
Grid.Column="4"
|
||||
Orientation="Horizontal"
|
||||
Margin="4,0">
|
||||
<Button Click="MoveUp" ToolTip="MOVE UP">
|
||||
<Path Width="12" Height="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.MoveUp}"/>
|
||||
</Button>
|
||||
|
||||
<Button Click="MoveDown" ToolTip="MOVE DOWN" Margin="4,0,0,0">
|
||||
<Path Width="12" Height="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.MoveDown}"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<!-- Loading Tip -->
|
||||
<Grid Grid.Row="1" IsHitTestVisible="False">
|
||||
<!-- Loading tip -->
|
||||
<Path x:Name="loading" Grid.ColumnSpan="5" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5" Visibility="Hidden">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="0"/>
|
||||
</Path.RenderTransform>
|
||||
|
||||
<Path.Style>
|
||||
<Style BasedOn="{StaticResource Style.Icon}" TargetType="{x:Type Path}">
|
||||
<Setter Property="Width" Value="48"/>
|
||||
<Setter Property="Height" Value="48"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Fill" Value="{StaticResource Brush.FG2}"/>
|
||||
</Style>
|
||||
</Path.Style>
|
||||
</Path>
|
||||
</Grid>
|
||||
|
||||
<!-- Splitter -->
|
||||
<GridSplitter Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"/>
|
||||
|
||||
<!-- Commit Detail -->
|
||||
<local:CommitViewer x:Name="commitViewer" Grid.Row="3" Background="{StaticResource Brush.BG4}"/>
|
||||
|
||||
<!-- Options Bar -->
|
||||
<Grid Grid.Row="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="4"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="4,0,24,0">
|
||||
<Label Grid.Column="0" Content="Rebase :"/>
|
||||
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Branch}"/>
|
||||
<Label x:Name="branch"/>
|
||||
|
||||
<Label Grid.Column="2" Content="On :" Margin="8,0,0,0"/>
|
||||
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Commit}"/>
|
||||
<Label x:Name="on"/>
|
||||
</StackPanel>
|
||||
|
||||
<Button Grid.Column="1" Height="26" Click="Start" Style="{StaticResource Style.Button.AccentBordered}" Content="REBASE"/>
|
||||
<Button Grid.Column="3" Height="26" Click="Cancel" Style="{StaticResource Style.Button.Bordered}" Content="CANCEL"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue