mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-22 20:37:19 -08:00
v1.0
This commit is contained in:
commit
38227b1d57
138 changed files with 17935 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
.vs/
|
||||
bin/
|
||||
obj/
|
6
App.config
Normal file
6
App.config
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
|
||||
</startup>
|
||||
</configuration>
|
BIN
App.ico
Normal file
BIN
App.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
16
App.xaml
Normal file
16
App.xaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<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"
|
||||
Exit="OnAppExit">
|
||||
<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>
|
134
App.xaml.cs
Normal file
134
App.xaml.cs
Normal file
|
@ -0,0 +1,134 @@
|
|||
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>
|
||||
/// Interactive rebase sequence file.
|
||||
/// </summary>
|
||||
public static string InteractiveRebaseScript {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TODO file for interactive rebase.
|
||||
/// </summary>
|
||||
public static string InteractiveRebaseTodo {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Error handler.
|
||||
/// </summary>
|
||||
public static Action<string> OnError {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raise error message.
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
public static void RaiseError(string message) {
|
||||
OnError?.Invoke(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Startup event.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnAppStartup(object sender, StartupEventArgs e) {
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
// Files for interactive rebase.
|
||||
InteractiveRebaseScript = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"SourceGit",
|
||||
"rebase.bat");
|
||||
InteractiveRebaseTodo = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"SourceGit",
|
||||
"REBASE_TODO");
|
||||
if (!File.Exists(InteractiveRebaseScript)) {
|
||||
var folder = Path.GetDirectoryName(InteractiveRebaseScript);
|
||||
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
|
||||
|
||||
File.WriteAllText(InteractiveRebaseScript, $"@echo off\ntype \"{InteractiveRebaseTodo}\" > .git\\rebase-merge\\git-rebase-todo");
|
||||
File.WriteAllText(InteractiveRebaseTodo, "");
|
||||
}
|
||||
|
||||
// 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
|
||||
var launcher = new UI.Launcher();
|
||||
launcher.Show();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivated event.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnAppDeactivated(object sender, EventArgs e) {
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quit event.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnAppExit(object sender, ExitEventArgs e) {
|
||||
Git.Preference.Save();
|
||||
}
|
||||
}
|
||||
}
|
37
Converters/BoolToCollapsed.cs
Normal file
37
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
Converters/FileStatusToColor.cs
Normal file
59
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
Converters/FileStatusToIcon.cs
Normal file
45
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
Converters/IndentToMargin.cs
Normal file
37
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
Converters/InverseBool.cs
Normal file
19
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
Converters/InverseBoolToCollapsed.cs
Normal file
37
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
Converters/PercentToDouble.cs
Normal file
41
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
Converters/TreeViewItemDepthToMargin.cs
Normal file
69
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
Git/Blame.cs
Normal file
35
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
Git/Branch.cs
Normal file
190
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; }
|
||||
|
||||
/// <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
Git/Change.cs
Normal file
147
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
262
Git/Commit.cs
Normal file
262
Git/Commit.cs
Normal file
|
@ -0,0 +1,262 @@
|
|||
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>
|
||||
/// 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<string> GetFiles(Repository repo) {
|
||||
var files = new List<string>();
|
||||
|
||||
var errs = repo.RunCommand($"ls-tree --name-only -r {SHA}", line => {
|
||||
files.Add(line);
|
||||
});
|
||||
|
||||
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) {
|
||||
var data = new List<string>();
|
||||
var isBinary = false;
|
||||
var count = 0;
|
||||
|
||||
var errs = repo.RunCommand($"show {SHA}:\"{file}\"", line => {
|
||||
if (isBinary) return;
|
||||
|
||||
count++;
|
||||
if (data.Count >= 1000) return;
|
||||
|
||||
if (line.IndexOf('\0') >= 0) {
|
||||
isBinary = true;
|
||||
data.Clear();
|
||||
data.Add("BINARY FILE PREVIEW NOT SUPPORTED!");
|
||||
return;
|
||||
}
|
||||
|
||||
data.Add(line);
|
||||
});
|
||||
|
||||
if (!isBinary && count > 1000) {
|
||||
data.Add("...");
|
||||
data.Add($"Total {count} lines. Hide {count-1000} lines.");
|
||||
}
|
||||
|
||||
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
Git/Decorator.cs
Normal file
21
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; }
|
||||
}
|
||||
}
|
202
Git/MergeTool.cs
Normal file
202
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;
|
||||
}
|
||||
}
|
||||
}
|
296
Git/Preference.cs
Normal file
296
Git/Preference.cs
Normal file
|
@ -0,0 +1,296 @@
|
|||
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;
|
||||
}
|
||||
|
||||
var stream = new FileStream(SAVE_PATH, FileMode.Open);
|
||||
var reader = new XmlSerializer(typeof(Preference));
|
||||
instance = (Preference)reader.Deserialize(stream);
|
||||
stream.Close();
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
}
|
93
Git/Remote.cs
Normal file
93
Git/Remote.cs
Normal file
|
@ -0,0 +1,93 @@
|
|||
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);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1059
Git/Repository.cs
Normal file
1059
Git/Repository.cs
Normal file
File diff suppressed because it is too large
Load diff
101
Git/Stash.cs
Normal file
101
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
Git/Tag.cs
Normal file
118
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
Git/User.cs
Normal file
42
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");
|
||||
}
|
||||
}
|
||||
}
|
274
Helpers/CommitGraph.cs
Normal file
274
Helpers/CommitGraph.cs
Normal file
|
@ -0,0 +1,274 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Helpers {
|
||||
|
||||
/// <summary>
|
||||
/// Tools to parse commit graph.
|
||||
/// </summary>
|
||||
public class CommitGraphMaker {
|
||||
/// <summary>
|
||||
/// Sizes
|
||||
/// </summary>
|
||||
public static readonly double UNIT_WIDTH = 12;
|
||||
public static readonly double HALF_WIDTH = 6;
|
||||
public static readonly double DOUBLE_WIDTH = 24;
|
||||
public static readonly double UNIT_HEIGHT = 24;
|
||||
public static readonly double HALF_HEIGHT = 12;
|
||||
|
||||
/// <summary>
|
||||
/// Colors
|
||||
/// </summary>
|
||||
public static Brush[] Colors = new Brush[] {
|
||||
Brushes.Orange,
|
||||
Brushes.ForestGreen,
|
||||
Brushes.Gold,
|
||||
Brushes.Magenta,
|
||||
Brushes.Red,
|
||||
Brushes.Gray,
|
||||
Brushes.Turquoise,
|
||||
Brushes.Olive,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Helpers to draw lines.
|
||||
/// </summary>
|
||||
public class LineHelper {
|
||||
private double lastX = 0;
|
||||
private double lastY = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Parent commit id.
|
||||
/// </summary>
|
||||
public string Next { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is merged into this tree.
|
||||
/// </summary>
|
||||
public bool IsMerged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Points in line
|
||||
/// </summary>
|
||||
public List<Point> Points { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Brush to draw line
|
||||
/// </summary>
|
||||
public Brush Brush { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current horizontal offset.
|
||||
/// </summary>
|
||||
public double HorizontalOffset => lastX;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="nextCommitId">Parent commit id</param>
|
||||
/// <param name="isMerged">Is merged in tree</param>
|
||||
/// <param name="colorIdx">Color index</param>
|
||||
/// <param name="startPoint">Start point</param>
|
||||
public LineHelper(string nextCommitId, bool isMerged, int colorIdx, Point startPoint) {
|
||||
Next = nextCommitId;
|
||||
IsMerged = isMerged;
|
||||
Points = new List<Point>() { startPoint };
|
||||
Brush = Colors[colorIdx % Colors.Length];
|
||||
|
||||
lastX = startPoint.X;
|
||||
lastY = startPoint.Y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Line to.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <param name="isEnd"></param>
|
||||
public void AddPoint(double x, double y, bool isEnd = false) {
|
||||
if (x > lastX) {
|
||||
Points.Add(new Point(lastX, lastY));
|
||||
Points.Add(new Point(x, y - HALF_HEIGHT));
|
||||
} else if (x < lastX) {
|
||||
Points.Add(new Point(lastX, lastY + HALF_HEIGHT));
|
||||
Points.Add(new Point(x, y));
|
||||
}
|
||||
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
|
||||
if (isEnd) {
|
||||
var last = Points.Last();
|
||||
if (last.X != lastX || last.Y != lastY) Points.Add(new Point(lastX, lastY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Short link between two commits.
|
||||
/// </summary>
|
||||
public struct ShortLink {
|
||||
public Point Start;
|
||||
public Point Control;
|
||||
public Point End;
|
||||
public Brush Brush;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dot
|
||||
/// </summary>
|
||||
public struct Dot {
|
||||
public double X;
|
||||
public double Y;
|
||||
public Brush Color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Independent lines in graph
|
||||
/// </summary>
|
||||
public List<LineHelper> Lines { get; set; } = new List<LineHelper>();
|
||||
|
||||
/// <summary>
|
||||
/// Short links.
|
||||
/// </summary>
|
||||
public List<ShortLink> Links { get; set; } = new List<ShortLink>();
|
||||
|
||||
/// <summary>
|
||||
/// All dots.
|
||||
/// </summary>
|
||||
public List<Dot> Dots { get; set; } = new List<Dot>();
|
||||
|
||||
/// <summary>
|
||||
/// Highlight commit id.
|
||||
/// </summary>
|
||||
public string Highlight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parse commits.
|
||||
/// </summary>
|
||||
/// <param name="commits"></param>
|
||||
/// <returns></returns>
|
||||
public static CommitGraphMaker Parse(List<Git.Commit> commits) {
|
||||
CommitGraphMaker maker = new CommitGraphMaker();
|
||||
|
||||
List<LineHelper> unsolved = new List<LineHelper>();
|
||||
List<LineHelper> ended = new List<LineHelper>();
|
||||
Dictionary<string, LineHelper> currentMap = new Dictionary<string, LineHelper>();
|
||||
double offsetY = -HALF_HEIGHT;
|
||||
int colorIdx = 0;
|
||||
|
||||
for (int i = 0; i < commits.Count; i++) {
|
||||
Git.Commit commit = commits[i];
|
||||
LineHelper major = null;
|
||||
bool isMerged = commit.IsHEAD || commit.IsMerged;
|
||||
int oldCount = unsolved.Count;
|
||||
|
||||
// 更新Y坐标
|
||||
offsetY += UNIT_HEIGHT;
|
||||
|
||||
// 找到当前的分支的HEAD,用于默认选中
|
||||
if (maker.Highlight == null && commit.IsHEAD) {
|
||||
maker.Highlight = commit.SHA;
|
||||
}
|
||||
|
||||
// 找到第一个依赖于本提交的树,将其他依赖于本提交的树标记为终止,并对已存在的线路调整(防止线重合)
|
||||
double offsetX = -HALF_WIDTH;
|
||||
foreach (var l in unsolved) {
|
||||
if (l.Next == commit.SHA) {
|
||||
if (major == null) {
|
||||
offsetX += UNIT_WIDTH;
|
||||
major = l;
|
||||
|
||||
if (commit.Parents.Count > 0) {
|
||||
major.Next = commit.Parents[0];
|
||||
if (!currentMap.ContainsKey(major.Next)) currentMap.Add(major.Next, major);
|
||||
} else {
|
||||
major.Next = "ENDED";
|
||||
}
|
||||
|
||||
major.AddPoint(offsetX, offsetY);
|
||||
} else {
|
||||
ended.Add(l);
|
||||
}
|
||||
|
||||
isMerged = isMerged || l.IsMerged;
|
||||
} else {
|
||||
if (!currentMap.ContainsKey(l.Next)) currentMap.Add(l.Next, l);
|
||||
offsetX += UNIT_WIDTH;
|
||||
l.AddPoint(offsetX, offsetY);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理本提交为非当前分支HEAD的情况(创建新依赖线路)
|
||||
if (major == null && commit.Parents.Count > 0) {
|
||||
offsetX += UNIT_WIDTH;
|
||||
major = new LineHelper(commit.Parents[0], isMerged, colorIdx, new Point(offsetX, offsetY));
|
||||
unsolved.Add(major);
|
||||
colorIdx++;
|
||||
}
|
||||
|
||||
// 确定本提交的点的位置
|
||||
Point position = new Point(offsetX, offsetY);
|
||||
if (major != null) {
|
||||
major.IsMerged = isMerged;
|
||||
position.X = major.HorizontalOffset;
|
||||
position.Y = offsetY;
|
||||
maker.Dots.Add(new Dot() { X = position.X - 3, Y = position.Y - 3, Color = major.Brush });
|
||||
} else {
|
||||
maker.Dots.Add(new Dot() { X = position.X - 3, Y = position.Y - 3, Color = Brushes.Orange });
|
||||
}
|
||||
|
||||
// 处理本提交的其他依赖
|
||||
for (int j = 1; j < commit.Parents.Count; j++) {
|
||||
var parent = commit.Parents[j];
|
||||
if (currentMap.ContainsKey(parent)) {
|
||||
var l = currentMap[parent];
|
||||
var link = new ShortLink();
|
||||
|
||||
link.Start = position;
|
||||
link.End = new Point(l.HorizontalOffset, offsetY + HALF_HEIGHT);
|
||||
link.Control = new Point(link.End.X, link.Start.Y);
|
||||
link.Brush = l.Brush;
|
||||
maker.Links.Add(link);
|
||||
} else {
|
||||
offsetX += UNIT_WIDTH;
|
||||
unsolved.Add(new LineHelper(commit.Parents[j], isMerged, colorIdx, position));
|
||||
colorIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理已终止的线
|
||||
foreach (var l in ended) {
|
||||
l.AddPoint(position.X, position.Y, true);
|
||||
maker.Lines.Add(l);
|
||||
unsolved.Remove(l);
|
||||
}
|
||||
|
||||
// 加入本次提交
|
||||
commit.IsMerged = isMerged;
|
||||
commit.GraphOffset = System.Math.Max(offsetX + HALF_WIDTH, oldCount * UNIT_WIDTH);
|
||||
|
||||
// 清理临时数据
|
||||
ended.Clear();
|
||||
currentMap.Clear();
|
||||
}
|
||||
|
||||
// 处理尚未终结的线
|
||||
for (int i = 0; i < unsolved.Count; i++) {
|
||||
var path = unsolved[i];
|
||||
path.AddPoint((i + 0.5) * UNIT_WIDTH, (commits.Count - 0.5) * UNIT_HEIGHT, true);
|
||||
maker.Lines.Add(path);
|
||||
}
|
||||
unsolved.Clear();
|
||||
|
||||
// 处理默认选中异常
|
||||
if (maker.Highlight == null && commits.Count > 0) {
|
||||
maker.Highlight = commits[0].SHA;
|
||||
}
|
||||
|
||||
return maker;
|
||||
}
|
||||
}
|
||||
}
|
143
Helpers/TextBoxHelper.cs
Normal file
143
Helpers/TextBoxHelper.cs
Normal file
|
@ -0,0 +1,143 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Helpers {
|
||||
|
||||
/// <summary>
|
||||
/// Attached properties to TextBox.
|
||||
/// </summary>
|
||||
public static class TextBoxHelper {
|
||||
|
||||
/// <summary>
|
||||
/// Placeholder property
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.RegisterAttached(
|
||||
"Placeholder",
|
||||
typeof(string),
|
||||
typeof(TextBoxHelper),
|
||||
new PropertyMetadata(string.Empty, OnPlaceholderChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Vertical alignment for placeholder.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PlaceholderBaselineProperty = DependencyProperty.RegisterAttached(
|
||||
"PlaceholderBaseline",
|
||||
typeof(AlignmentY),
|
||||
typeof(TextBoxHelper),
|
||||
new PropertyMetadata(AlignmentY.Center));
|
||||
|
||||
/// <summary>
|
||||
/// Property to store generated placeholder brush.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PlaceholderBrushProperty = DependencyProperty.RegisterAttached(
|
||||
"PlaceholderBrush",
|
||||
typeof(Brush),
|
||||
typeof(TextBoxHelper),
|
||||
new PropertyMetadata(Brushes.Transparent));
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when placeholder changed.
|
||||
/// </summary>
|
||||
/// <param name="d"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void OnPlaceholderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var textBox = d as TextBox;
|
||||
if (textBox != null) textBox.Loaded += OnTextLoaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setter for Placeholder property
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <param name="value"></param>
|
||||
public static void SetPlaceholder(UIElement element, string value) {
|
||||
element.SetValue(PlaceholderProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Getter for Placeholder property
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetPlaceholder(UIElement element) {
|
||||
return (string)element.GetValue(PlaceholderProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setter for PlaceholderBaseline property
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <param name="align"></param>
|
||||
public static void SetPlaceholderBaseline(UIElement element, AlignmentY align) {
|
||||
element.SetValue(PlaceholderBaselineProperty, align);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setter for PlaceholderBaseline property.
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <returns></returns>
|
||||
public static AlignmentY GetPlaceholderBaseline(UIElement element) {
|
||||
return (AlignmentY)element.GetValue(PlaceholderBaselineProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setter for PlaceholderBrush property.
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <param name="value"></param>
|
||||
public static void SetPlaceholderBrush(UIElement element, Brush value) {
|
||||
element.SetValue(PlaceholderBrushProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Getter for PlaceholderBrush property.
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <returns></returns>
|
||||
public static Brush GetPlaceholderBrush(UIElement element) {
|
||||
return (Brush)element.GetValue(PlaceholderBrushProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set placeholder as background when TextBox was loaded.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void OnTextLoaded(object sender, RoutedEventArgs e) {
|
||||
var textBox = sender as TextBox;
|
||||
if (textBox == null) return;
|
||||
|
||||
Label placeholder = new Label();
|
||||
placeholder.Content = textBox.GetValue(PlaceholderProperty);
|
||||
|
||||
VisualBrush brush = new VisualBrush();
|
||||
brush.AlignmentX = AlignmentX.Left;
|
||||
brush.AlignmentY = GetPlaceholderBaseline(textBox);
|
||||
brush.TileMode = TileMode.None;
|
||||
brush.Stretch = Stretch.None;
|
||||
brush.Opacity = 0.3;
|
||||
brush.Visual = placeholder;
|
||||
|
||||
textBox.SetValue(PlaceholderBrushProperty, brush);
|
||||
textBox.Background = brush;
|
||||
textBox.TextChanged += OnTextChanged;
|
||||
OnTextChanged(textBox, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dynamically hide/show placeholder.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void OnTextChanged(object sender, RoutedEventArgs e) {
|
||||
var textBox = sender as TextBox;
|
||||
if (string.IsNullOrEmpty(textBox.Text)) {
|
||||
textBox.Background = textBox.GetValue(PlaceholderBrushProperty) as Brush;
|
||||
} else {
|
||||
textBox.Background = Brushes.Transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
329
Helpers/TreeViewHelper.cs
Normal file
329
Helpers/TreeViewHelper.cs
Normal file
|
@ -0,0 +1,329 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SourceGit.Helpers {
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to enable multi-selection of TreeView
|
||||
/// </summary>
|
||||
public static class TreeViewHelper {
|
||||
|
||||
/// <summary>
|
||||
/// Definition of EnableMultiSelection property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty EnableMultiSelectionProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"EnableMultiSelection",
|
||||
typeof(bool),
|
||||
typeof(TreeViewHelper),
|
||||
new FrameworkPropertyMetadata(false, OnEnableMultiSelectionChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Getter of EnableMultiSelection
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static bool GetEnableMultiSelection(DependencyObject obj) {
|
||||
return (bool)obj.GetValue(EnableMultiSelectionProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setter of EnableMultiSelection
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="value"></param>
|
||||
public static void SetEnableMultiSelection(DependencyObject obj, bool value) {
|
||||
obj.SetValue(EnableMultiSelectionProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Definition of SelectedItems
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty SelectedItemsProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"SelectedItems",
|
||||
typeof(ObservableCollection<TreeViewItem>),
|
||||
typeof(TreeViewHelper),
|
||||
new FrameworkPropertyMetadata(null));
|
||||
|
||||
/// <summary>
|
||||
/// Getter of SelectedItems
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static ObservableCollection<TreeViewItem> GetSelectedItems(DependencyObject obj) {
|
||||
return (ObservableCollection<TreeViewItem>)obj.GetValue(SelectedItemsProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setter of SelectedItems
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="value"></param>
|
||||
public static void SetSelectedItems(DependencyObject obj, ObservableCollection<TreeViewItem> value) {
|
||||
obj.SetValue(SelectedItemsProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Definition of IsChecked property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsCheckedProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"IsChecked",
|
||||
typeof(bool),
|
||||
typeof(TreeViewHelper),
|
||||
new FrameworkPropertyMetadata(false));
|
||||
|
||||
/// <summary>
|
||||
/// Getter of IsChecked Property.
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static bool GetIsChecked(DependencyObject obj) {
|
||||
return (bool)obj.GetValue(IsCheckedProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setter of IsChecked property
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="value"></param>
|
||||
public static void SetIsChecked(DependencyObject obj, bool value) {
|
||||
obj.SetValue(IsCheckedProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Definition of MultiSelectionChangedEvent
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent MultiSelectionChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("MultiSelectionChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TreeViewHelper));
|
||||
|
||||
/// <summary>
|
||||
/// Add handler for MultiSelectionChanged event.
|
||||
/// </summary>
|
||||
/// <param name="d"></param>
|
||||
/// <param name="handler"></param>
|
||||
public static void AddMultiSelectionChangedHandler(DependencyObject d, RoutedEventHandler handler) {
|
||||
var tree = d as TreeView;
|
||||
if (tree != null) tree.AddHandler(MultiSelectionChangedEvent, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove handler for MultiSelectionChanged event.
|
||||
/// </summary>
|
||||
/// <param name="d"></param>
|
||||
/// <param name="handler"></param>
|
||||
public static void RemoveMultiSelectionChangedHandler(DependencyObject d, RoutedEventHandler handler) {
|
||||
var tree = d as TreeView;
|
||||
if (tree != null) tree.RemoveHandler(MultiSelectionChangedEvent, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select all items in tree.
|
||||
/// </summary>
|
||||
/// <param name="tree"></param>
|
||||
public static void SelectWholeTree(TreeView tree) {
|
||||
var selected = GetSelectedItems(tree);
|
||||
selected.Clear();
|
||||
SelectAll(selected, tree);
|
||||
tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selected one item by DataContext
|
||||
/// </summary>
|
||||
/// <param name="tree"></param>
|
||||
/// <param name="obj"></param>
|
||||
public static void SelectOneByContext(TreeView tree, object obj) {
|
||||
var item = FindTreeViewItemByDataContext(tree, obj);
|
||||
if (item != null) {
|
||||
var selected = GetSelectedItems(tree);
|
||||
selected.Add(item);
|
||||
item.SetValue(IsCheckedProperty, true);
|
||||
tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unselect the whole tree.
|
||||
/// </summary>
|
||||
/// <param name="tree"></param>
|
||||
public static void UnselectTree(TreeView tree) {
|
||||
var selected = GetSelectedItems(tree);
|
||||
if (selected.Count == 0) return;
|
||||
|
||||
foreach (var old in selected) old.SetValue(IsCheckedProperty, false);
|
||||
selected.Clear();
|
||||
tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hooks when EnableMultiSelection changed.
|
||||
/// </summary>
|
||||
/// <param name="d"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void OnEnableMultiSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
var tree = d as TreeView;
|
||||
if (tree != null && (bool)e.NewValue) {
|
||||
tree.SetValue(SelectedItemsProperty, new ObservableCollection<TreeViewItem>());
|
||||
tree.PreviewMouseDown += OnTreeMouseDown;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Preview mouse button select.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private static void OnTreeMouseDown(object sender, MouseButtonEventArgs e) {
|
||||
var tree = sender as TreeView;
|
||||
if (tree == null) return;
|
||||
|
||||
var hit = VisualTreeHelper.HitTest(tree, e.GetPosition(tree));
|
||||
if (hit == null || hit.VisualHit is null) return;
|
||||
|
||||
var item = FindTreeViewItem(hit.VisualHit as UIElement);
|
||||
if (item == null) return;
|
||||
|
||||
var selected = GetSelectedItems(tree);
|
||||
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) {
|
||||
if (GetIsChecked(item)) {
|
||||
selected.Remove(item);
|
||||
item.SetValue(IsCheckedProperty, false);
|
||||
} else {
|
||||
selected.Add(item);
|
||||
item.SetValue(IsCheckedProperty, true);
|
||||
}
|
||||
} else if ((Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) && selected.Count > 0) {
|
||||
var last = selected.Last();
|
||||
if (last == item) return;
|
||||
|
||||
var lastPos = last.PointToScreen(new Point(0, 0));
|
||||
var curPos = item.PointToScreen(new Point(0, 0));
|
||||
if (lastPos.Y > curPos.Y) {
|
||||
SelectRange(selected, tree, item, last);
|
||||
} else {
|
||||
SelectRange(selected, tree, last, item);
|
||||
}
|
||||
|
||||
selected.Add(item);
|
||||
item.SetValue(IsCheckedProperty, true);
|
||||
} else if (e.RightButton == MouseButtonState.Pressed) {
|
||||
if (GetIsChecked(item)) return;
|
||||
|
||||
foreach (var old in selected) old.SetValue(IsCheckedProperty, false);
|
||||
selected.Clear();
|
||||
selected.Add(item);
|
||||
item.SetValue(IsCheckedProperty, true);
|
||||
} else {
|
||||
foreach (var old in selected) old.SetValue(IsCheckedProperty, false);
|
||||
selected.Clear();
|
||||
selected.Add(item);
|
||||
item.SetValue(IsCheckedProperty, true);
|
||||
}
|
||||
|
||||
tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find TreeViewItem by child element.
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="child"></param>
|
||||
/// <returns></returns>
|
||||
private static TreeViewItem FindTreeViewItem(DependencyObject child) {
|
||||
if (child == null) return null;
|
||||
if (child is TreeViewItem) return child as TreeViewItem;
|
||||
if (child is TreeView) return null;
|
||||
return FindTreeViewItem(VisualTreeHelper.GetParent(child));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find TreeViewItem by DataContext
|
||||
/// </summary>
|
||||
/// <param name="control"></param>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
private static TreeViewItem FindTreeViewItemByDataContext(ItemsControl control, object obj) {
|
||||
if (control == null) return null;
|
||||
if (control.DataContext == obj) return control as TreeViewItem;
|
||||
|
||||
for (int i = 0; i < control.Items.Count; i++) {
|
||||
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as ItemsControl;
|
||||
var found = FindTreeViewItemByDataContext(child, obj);
|
||||
if (found != null) return found;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select all items.
|
||||
/// </summary>
|
||||
/// <param name="selected"></param>
|
||||
/// <param name="control"></param>
|
||||
private static void SelectAll(ObservableCollection<TreeViewItem> selected, ItemsControl control) {
|
||||
for (int i = 0; i < control.Items.Count; i++) {
|
||||
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
|
||||
if (child == null) continue;
|
||||
|
||||
selected.Add(child);
|
||||
child.SetValue(IsCheckedProperty, true);
|
||||
SelectAll(selected, child);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select range items between given.
|
||||
/// </summary>
|
||||
/// <param name="selected"></param>
|
||||
/// <param name="control"></param>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="to"></param>
|
||||
/// <param name="started"></param>
|
||||
private static int SelectRange(ObservableCollection<TreeViewItem> selected, ItemsControl control, TreeViewItem from, TreeViewItem to, int matches = 0) {
|
||||
for (int i = 0; i < control.Items.Count; i++) {
|
||||
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
|
||||
if (child == null) continue;
|
||||
|
||||
if (matches == 1) {
|
||||
if (child == to) return 2;
|
||||
selected.Add(child);
|
||||
child.SetValue(IsCheckedProperty, true);
|
||||
if (TryEndRangeSelection(selected, child, to)) return 2;
|
||||
} else if (child == from) {
|
||||
matches = 1;
|
||||
if (TryEndRangeSelection(selected, child, to)) return 2;
|
||||
} else {
|
||||
matches = SelectRange(selected, child, from, to, matches);
|
||||
if (matches == 2) return 2;
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
private static bool TryEndRangeSelection(ObservableCollection<TreeViewItem> selected, TreeViewItem control, TreeViewItem end) {
|
||||
for (int i = 0; i < control.Items.Count; i++) {
|
||||
var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
|
||||
if (child == null) continue;
|
||||
|
||||
if (child == end) {
|
||||
return true;
|
||||
} else {
|
||||
selected.Add(child);
|
||||
child.SetValue(IsCheckedProperty, true);
|
||||
|
||||
var ended = TryEndRangeSelection(selected, child, end);
|
||||
if (ended) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
124
Helpers/Validations.cs
Normal file
124
Helpers/Validations.cs
Normal file
|
@ -0,0 +1,124 @@
|
|||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SourceGit.Helpers {
|
||||
|
||||
/// <summary>
|
||||
/// Validate clone folder.
|
||||
/// </summary>
|
||||
public class CloneFolderRule : ValidationRule {
|
||||
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
|
||||
var badPath = "EXISTS and FULL ACCESS CONTROL needed";
|
||||
var path = value as string;
|
||||
return Directory.Exists(path) ? ValidationResult.ValidResult : new ValidationResult(false, badPath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate git remote URL
|
||||
/// </summary>
|
||||
public class RemoteUriRule : ValidationRule {
|
||||
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
|
||||
var badUrl = "Remote git URL not supported";
|
||||
return Git.Repository.IsValidUrl(value as string) ? ValidationResult.ValidResult : new ValidationResult(false, badUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate tag name.
|
||||
/// </summary>
|
||||
public class RemoteNameRule : ValidationRule {
|
||||
public Git.Repository Repo { get; set; }
|
||||
|
||||
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
|
||||
var regex = new Regex(@"^[\w\-\.]+$");
|
||||
var name = value as string;
|
||||
var remotes = Repo.Remotes();
|
||||
|
||||
if (string.IsNullOrEmpty(name)) return new ValidationResult(false, "Remote name can NOT be null");
|
||||
if (!regex.IsMatch(name)) return new ValidationResult(false, $"Bad name for remote. Regex: ^[\\w\\-\\.]+$");
|
||||
|
||||
foreach (var t in remotes) {
|
||||
if (t.Name == name) {
|
||||
return new ValidationResult(false, $"Remote '{name}' already exists");
|
||||
}
|
||||
}
|
||||
|
||||
return ValidationResult.ValidResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate branch name.
|
||||
/// </summary>
|
||||
public class BranchNameRule : ValidationRule {
|
||||
public Git.Repository Repo { get; set; }
|
||||
public string Prefix { get; set; } = "";
|
||||
|
||||
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
|
||||
var regex = new Regex(@"^[\w\-/\.]+$");
|
||||
var name = value as string;
|
||||
var branches = Repo.Branches();
|
||||
|
||||
if (string.IsNullOrEmpty(name)) return new ValidationResult(false, "Branch name can NOT be null");
|
||||
if (!regex.IsMatch(name)) return new ValidationResult(false, $"Bad name for branch. Regex: ^[\\w\\-/\\.]+$");
|
||||
|
||||
name = Prefix + name;
|
||||
|
||||
foreach (var b in branches) {
|
||||
if (b.Name == name) {
|
||||
return new ValidationResult(false, $"Branch '{name}' already exists");
|
||||
}
|
||||
}
|
||||
|
||||
return ValidationResult.ValidResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate tag name.
|
||||
/// </summary>
|
||||
public class TagNameRule : ValidationRule {
|
||||
public Git.Repository Repo { get; set; }
|
||||
|
||||
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
|
||||
var regex = new Regex(@"^[\w\-\.]+$");
|
||||
var name = value as string;
|
||||
var tags = Repo.Tags();
|
||||
|
||||
if (string.IsNullOrEmpty(name)) return new ValidationResult(false, "Tag name can NOT be null");
|
||||
if (!regex.IsMatch(name)) return new ValidationResult(false, $"Bad name for tag. Regex: ^[\\w\\-\\.]+$");
|
||||
|
||||
foreach (var t in tags) {
|
||||
if (t.Name == name) {
|
||||
return new ValidationResult(false, $"Tag '{name}' already exists");
|
||||
}
|
||||
}
|
||||
|
||||
return ValidationResult.ValidResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Required for commit subject.
|
||||
/// </summary>
|
||||
public class CommitSubjectRequiredRule : ValidationRule {
|
||||
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
|
||||
var subject = value as string;
|
||||
return string.IsNullOrWhiteSpace(subject) ? new ValidationResult(false, "Commit subject can NOT be empty") : ValidationResult.ValidResult;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Required for patch file.
|
||||
/// </summary>
|
||||
public class PatchFileRequiredRule : ValidationRule {
|
||||
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
|
||||
var path = value as string;
|
||||
var succ = !string.IsNullOrEmpty(path) && File.Exists(path);
|
||||
return !succ ? new ValidationResult(false, "Invalid path for patch file") : ValidationResult.ValidResult;
|
||||
}
|
||||
}
|
||||
}
|
20
LICENSE
Normal file
20
LICENSE
Normal file
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 leo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
BIN
Preview_Dark.png
Normal file
BIN
Preview_Dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 95 KiB |
BIN
Preview_Light.png
Normal file
BIN
Preview_Light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
56
Properties/AssemblyInfo.cs
Normal file
56
Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Source Git")]
|
||||
[assembly: AssemblyDescription("OpenSource GIT client for Windows")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Leo")]
|
||||
[assembly: AssemblyProduct("Source Git")]
|
||||
[assembly: AssemblyCopyright("Copyright © longshuang@msn.cn 2020")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
//In order to begin building localizable applications, set
|
||||
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
|
||||
//inside a <PropertyGroup>. For example, if you are using US english
|
||||
//in your source files, set the <UICulture> to en-US. Then uncomment
|
||||
//the NeutralResourceLanguage attribute below. Update the "en-US" in
|
||||
//the line below to match the UICulture setting in the project file.
|
||||
|
||||
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
|
||||
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
|
||||
#pragma warning disable CS7035
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("0.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
62
Properties/Resources.Designer.cs
generated
Normal file
62
Properties/Resources.Designer.cs
generated
Normal file
|
@ -0,0 +1,62 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace SourceGit.Properties {
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if ((resourceMan == null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SourceGit.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
117
Properties/Resources.resx
Normal file
117
Properties/Resources.resx
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
26
Properties/Settings.Designer.cs
generated
Normal file
26
Properties/Settings.Designer.cs
generated
Normal file
|
@ -0,0 +1,26 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace SourceGit.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
Properties/Settings.settings
Normal file
7
Properties/Settings.settings
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
12
README.md
Normal file
12
README.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# SourceGit
|
||||
|
||||
开源的Git客户端,仅用于Windows 10。单文件,无需安装,< 500KB。
|
||||
|
||||
* DarkTheme
|
||||
|
||||
![Preview_Dark](./Preview_Dark.png)
|
||||
|
||||
* LightTheme
|
||||
|
||||
![Preview_Light](./Preview_Light.png)
|
||||
|
22
Resources/Controls.xaml
Normal file
22
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>
|
59
Resources/Icons.xaml
Normal file
59
Resources/Icons.xaml
Normal file
|
@ -0,0 +1,59 @@
|
|||
<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.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.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.Manager">M780.512477 870.01493 780.512477 512l89.502453 0-358.013907-358.01493L153.98507 512l89.503477 0 0 358.01493 179.007977 0L422.496523 735.759203c0-49.427736 40.075741-89.503477 89.503477-89.503477s89.503477 40.075741 89.503477 89.503477l0 134.255727L780.512477 870.01493z</Geometry>
|
||||
<Geometry x:Key="Icon.Clone">M928 0c53.02 0 96 42.98 96 96v576c0 53.02-42.98 96-96 96H352c-53.02 0-96-42.98-96-96V96c0-53.02 42.98-96 96-96h576M352 832c-88.224 0-160-71.776-160-160V256H96c-53.02 0-96 42.98-96 96v576c0 53.02 42.98 96 96 96h576c53.02 0 96-42.98 96-96v-96H352z</Geometry>
|
||||
<Geometry x:Key="Icon.Book">M384 576H320V512h64v64z m0-192H320v64h64V384z m0-128H320v64h64V256z m0-128H320v64h64V128z m512-64v768c0 35.2-28.8 64-64 64H512v128l-96-96L320 1024v-128H192c-35.2 0-64-28.8-64-64V64c0-35.2 28.8-64 64-64h640c35.2 0 64 28.8 64 64z m-64 640H192v128h128v-64h192v64h320v-128z m0-640H256v576h576V64z</Geometry>
|
||||
<Geometry x:Key="Icon.Navigator">M989.866667 512L689.493333 802.133333 614.4 729.6 839.68 512 614.4 294.4 689.493333 221.866667z</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.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">M889.259 551.125c-39.638 0-74.112 21.163-93.483 52.608v-0.426c-16.896 30.378-64.597 27.52-81.28-0.811V319.488H445.781c-41.13-6.827-49.92-66.859-15.061-86.23h-0.384c31.445-19.37 52.608-53.845 52.608-93.482 0-60.8-49.28-110.123-110.08-110.123S262.699 78.976 262.699 139.776c0 39.637 21.162 74.112 52.608 93.483h-0.384c34.858 19.37 26.069 79.402-15.062 86.229H31.104v630.23H292.48c47.744 0 59.392-69.505 22.443-81.622h0.938c-44.757-21.376-75.904-66.688-75.904-119.595 0-73.386 59.478-132.864 132.864-132.864 73.387 0 132.864 59.478 132.864 132.864 0 52.907-31.146 98.219-75.904 119.595h0.939c-36.95 12.16-25.344 81.621 22.443 81.621h261.376V719.915c16.682-28.331 64.384-31.19 81.28-0.811v-0.384c19.37 31.445 53.845 52.608 93.482 52.608 60.8 0 110.08-49.28 110.08-110.123 0-60.8-49.322-110.08-110.122-110.08z</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
Resources/Styles/Border.xaml
Normal file
12
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
Resources/Styles/Button.xaml
Normal file
55
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
Resources/Styles/CheckBox.xaml
Normal file
38
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
Resources/Styles/ComboBox.xaml
Normal file
83
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
Resources/Styles/ContextMenu.xaml
Normal file
91
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
Resources/Styles/DataGrid.xaml
Normal file
44
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
Resources/Styles/HyperLink.xaml
Normal file
11
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
Resources/Styles/Label.xaml
Normal file
15
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
Resources/Styles/ListView.xaml
Normal file
66
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
Resources/Styles/Path.xaml
Normal file
21
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
Resources/Styles/RadioButton.xaml
Normal file
52
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
Resources/Styles/ScrollBar.xaml
Normal file
98
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
Resources/Styles/ScrollViewer.xaml
Normal file
54
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
Resources/Styles/TabControl.xaml
Normal file
62
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>
|
114
Resources/Styles/TextBox.xaml
Normal file
114
Resources/Styles/TextBox.xaml
Normal file
|
@ -0,0 +1,114 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<!-- 错误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="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="{x:Null}"
|
||||
BorderThickness="0"
|
||||
IsTabStop="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
Resources/Styles/ToggleButton.xaml
Normal file
111
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.FG2}"
|
||||
StrokeThickness="1"
|
||||
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
Resources/Styles/Tooltip.xaml
Normal file
21
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
Resources/Styles/TreeView.xaml
Normal file
201
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>
|
16
Resources/Themes/Dark.xaml
Normal file
16
Resources/Themes/Dark.xaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<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.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="#33007ACC"/>
|
||||
</ResourceDictionary>
|
15
Resources/Themes/Light.xaml
Normal file
15
Resources/Themes/Light.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">
|
||||
<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.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="#33007ACC"/>
|
||||
</ResourceDictionary>
|
515
SourceGit.csproj
Normal file
515
SourceGit.csproj
Normal file
|
@ -0,0 +1,515 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{6B38FAF0-57D6-44E6-9B21-9BEAC481EF9E}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>SourceGit</RootNamespace>
|
||||
<AssemblyName>SourceGit</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<GenerateSerializationAssemblies>On</GenerateSerializationAssemblies>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject>SourceGit.App</StartupObject>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>App.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup />
|
||||
<PropertyGroup>
|
||||
<RunPostBuildEvent>Always</RunPostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xaml">
|
||||
<RequiredTargetFramework>4.0</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="App.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
<Compile Include="Converters\FileStatusToColor.cs" />
|
||||
<Compile Include="Converters\FileStatusToIcon.cs" />
|
||||
<Compile Include="Converters\InverseBool.cs" />
|
||||
<Compile Include="Converters\InverseBoolToCollapsed.cs" />
|
||||
<Compile Include="Git\Blame.cs" />
|
||||
<Compile Include="Git\Branch.cs" />
|
||||
<Compile Include="Git\Decorator.cs" />
|
||||
<Compile Include="Git\MergeTool.cs" />
|
||||
<Compile Include="Git\Remote.cs" />
|
||||
<Compile Include="Git\Stash.cs" />
|
||||
<Compile Include="Git\Tag.cs" />
|
||||
<Compile Include="Helpers\CommitGraph.cs" />
|
||||
<Compile Include="Helpers\TreeViewHelper.cs" />
|
||||
<Compile Include="Helpers\Validations.cs" />
|
||||
<Compile Include="UI\Apply.xaml.cs">
|
||||
<DependentUpon>Apply.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Blame.xaml.cs">
|
||||
<DependentUpon>Blame.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\CherryPick.xaml.cs">
|
||||
<DependentUpon>CherryPick.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Clone.xaml.cs">
|
||||
<DependentUpon>Clone.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\CommitViewer.xaml.cs">
|
||||
<DependentUpon>CommitViewer.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\CreateBranch.xaml.cs">
|
||||
<DependentUpon>CreateBranch.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\CreateTag.xaml.cs">
|
||||
<DependentUpon>CreateTag.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Dashboard.xaml.cs">
|
||||
<DependentUpon>Dashboard.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\DeleteBranch.xaml.cs">
|
||||
<DependentUpon>DeleteBranch.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\DeleteRemote.xaml.cs">
|
||||
<DependentUpon>DeleteRemote.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\DeleteTag.xaml.cs">
|
||||
<DependentUpon>DeleteTag.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\DiffViewer.xaml.cs">
|
||||
<DependentUpon>DiffViewer.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Discard.xaml.cs">
|
||||
<DependentUpon>Discard.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Fetch.xaml.cs">
|
||||
<DependentUpon>Fetch.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\FileHistories.xaml.cs">
|
||||
<DependentUpon>FileHistories.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\GitFlowFinishBranch.xaml.cs">
|
||||
<DependentUpon>GitFlowFinishBranch.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\GitFlowSetup.xaml.cs">
|
||||
<DependentUpon>GitFlowSetup.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\GitFlowStartBranch.xaml.cs">
|
||||
<DependentUpon>GitFlowStartBranch.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Histories.xaml.cs">
|
||||
<DependentUpon>Histories.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Init.xaml.cs">
|
||||
<DependentUpon>Init.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\InteractiveRebase.xaml.cs">
|
||||
<DependentUpon>InteractiveRebase.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Manager.xaml.cs">
|
||||
<DependentUpon>Manager.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Merge.xaml.cs">
|
||||
<DependentUpon>Merge.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\PopupManager.xaml.cs">
|
||||
<DependentUpon>PopupManager.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Preference.xaml.cs">
|
||||
<DependentUpon>Preference.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Pull.xaml.cs">
|
||||
<DependentUpon>Pull.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Push.xaml.cs">
|
||||
<DependentUpon>Push.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\PushTag.xaml.cs">
|
||||
<DependentUpon>PushTag.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Rebase.xaml.cs">
|
||||
<DependentUpon>Rebase.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Remote.xaml.cs">
|
||||
<DependentUpon>Remote.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\RenameBranch.xaml.cs">
|
||||
<DependentUpon>RenameBranch.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Reset.xaml.cs">
|
||||
<DependentUpon>Reset.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Revert.xaml.cs">
|
||||
<DependentUpon>Revert.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Stash.xaml.cs">
|
||||
<DependentUpon>Stash.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Stashes.xaml.cs">
|
||||
<DependentUpon>Stashes.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\WorkingCopy.xaml.cs">
|
||||
<DependentUpon>WorkingCopy.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Page Include="Resources\Controls.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Resources\Icons.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\Border.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\Button.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\CheckBox.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\ComboBox.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\ContextMenu.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\DataGrid.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\HyperLink.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\Label.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\ListView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\Path.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\RadioButton.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\ScrollBar.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\ScrollViewer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\TabControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\TextBox.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\ToggleButton.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\Tooltip.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Styles\TreeView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Themes\Dark.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Resources\Themes\Light.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\About.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Apply.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Blame.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\CherryPick.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Clone.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\CommitViewer.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\CreateBranch.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\CreateTag.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Dashboard.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\DeleteBranch.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\DeleteRemote.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\DeleteTag.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\DiffViewer.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Discard.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Fetch.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\FileHistories.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\GitFlowFinishBranch.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\GitFlowSetup.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\GitFlowStartBranch.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Histories.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Init.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\InteractiveRebase.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Launcher.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Compile Include="App.xaml.cs">
|
||||
<DependentUpon>App.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Converters\PercentToDouble.cs" />
|
||||
<Compile Include="Git\Change.cs" />
|
||||
<Compile Include="Git\Commit.cs" />
|
||||
<Compile Include="Git\Preference.cs" />
|
||||
<Compile Include="Git\Repository.cs" />
|
||||
<Compile Include="Git\User.cs" />
|
||||
<Compile Include="Converters\BoolToCollapsed.cs" />
|
||||
<Compile Include="Converters\IndentToMargin.cs" />
|
||||
<Compile Include="Helpers\TextBoxHelper.cs" />
|
||||
<Compile Include="Converters\TreeViewItemDepthToMargin.cs" />
|
||||
<Compile Include="UI\About.xaml.cs">
|
||||
<DependentUpon>About.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Launcher.xaml.cs">
|
||||
<DependentUpon>Launcher.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Page Include="UI\Manager.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Merge.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\PopupManager.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Preference.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Pull.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Push.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\PushTag.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Rebase.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Remote.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\RenameBranch.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Reset.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Revert.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Stash.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\Stashes.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UI\WorkingCopy.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
</Compile>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<Resource Include="App.ico" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PreBuildEvent>@echo off &setlocal
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
set file=$(SolutionDir)Properties\AssemblyInfo.cs
|
||||
set temp=$(SolutionDir)Properties\AssemblyInfo.cs.tmp
|
||||
set bak=$(SolutionDir)Properties\AssemblyInfo.cs.bk
|
||||
|
||||
echo BACKUP AssemblyInfo
|
||||
copy /Y %25file%25 %25bak%25
|
||||
|
||||
echo Find Version
|
||||
git describe > VERSION
|
||||
set /P version=<VERSION
|
||||
|
||||
set search=1.0.0.0
|
||||
(for /f "delims=" %25%25i in (%25file%25) do (
|
||||
set "line=%25%25i"
|
||||
set "line=!line:%25search%25=%25version%25!"
|
||||
echo(!line!
|
||||
))>"%25temp%25"
|
||||
del /f %25file%25
|
||||
del /f VERSION
|
||||
move %25temp%25 %25file%25
|
||||
endlocal</PreBuildEvent>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>del /f $(SolutionDir)Properties\AssemblyInfo.cs
|
||||
move $(SolutionDir)Properties\AssemblyInfo.cs.bk $(SolutionDir)Properties\AssemblyInfo.cs</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
</Project>
|
25
SourceGit.sln
Normal file
25
SourceGit.sln
Normal file
|
@ -0,0 +1,25 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30011.22
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGit", "SourceGit.csproj", "{6B38FAF0-57D6-44E6-9B21-9BEAC481EF9E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{6B38FAF0-57D6-44E6-9B21-9BEAC481EF9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6B38FAF0-57D6-44E6-9B21-9BEAC481EF9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6B38FAF0-57D6-44E6-9B21-9BEAC481EF9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6B38FAF0-57D6-44E6-9B21-9BEAC481EF9E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {01F4EC04-5B3C-4D74-BB48-1C251B2D2853}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
77
UI/About.xaml
Normal file
77
UI/About.xaml
Normal file
|
@ -0,0 +1,77 @@
|
|||
<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"
|
||||
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="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Title -->
|
||||
<Label Content="ABOUT" FontWeight="Light"/>
|
||||
|
||||
<!-- Close Button -->
|
||||
<Button Click="Quit" Width="32" Grid.Column="2" 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
UI/About.xaml.cs
Normal file
46
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 FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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();
|
||||
}
|
||||
}
|
||||
}
|
85
UI/Apply.xaml
Normal file
85
UI/Apply.xaml
Normal file
|
@ -0,0 +1,85 @@
|
|||
<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"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="192" 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="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">
|
||||
<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>
|
||||
|
||||
<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 Grid.Row="0" Grid.RowSpan="6" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
|
||||
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="0"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
112
UI/Apply.xaml.cs
Normal file
112
UI/Apply.xaml.cs
Normal file
|
@ -0,0 +1,112 @@
|
|||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
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) {
|
||||
PopupManager.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;
|
||||
|
||||
PopupManager.Lock();
|
||||
|
||||
status.Visibility = Visibility.Visible;
|
||||
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
|
||||
anim.RepeatBehavior = RepeatBehavior.Forever;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
|
||||
|
||||
var mode = combWhitespaceOptions.SelectedItem as WhitespaceOption;
|
||||
await Task.Run(() => repo.Apply(PatchFile, mode.Arg));
|
||||
|
||||
status.Visibility = Visibility.Collapsed;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
||||
PopupManager.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel options.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
PopupManager.Close();
|
||||
}
|
||||
}
|
||||
}
|
201
UI/Blame.xaml
Normal file
201
UI/Blame.xaml
Normal file
|
@ -0,0 +1,201 @@
|
|||
<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"
|
||||
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"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
FontFamily="Consolas">
|
||||
<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>
|
211
UI/Blame.xaml.cs
Normal file
211
UI/Blame.xaml.cs
Normal file
|
@ -0,0 +1,211 @@
|
|||
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.MouseRightButtonDown += (sender, ev) => {
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
45
UI/CherryPick.xaml
Normal file
45
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
UI/CherryPick.xaml.cs
Normal file
54
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) {
|
||||
PopupManager.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);
|
||||
PopupManager.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
PopupManager.Close();
|
||||
}
|
||||
}
|
||||
}
|
98
UI/Clone.xaml
Normal file
98
UI/Clone.xaml
Normal file
|
@ -0,0 +1,98 @@
|
|||
<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="192">
|
||||
<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="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>
|
||||
|
||||
<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 Grid.Row="0" Grid.RowSpan="7" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="0"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
|
||||
<Label x:Name="statusMsg" Margin="0,8,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
115
UI/Clone.xaml.cs
Normal file
115
UI/Clone.xaml.cs
Normal file
|
@ -0,0 +1,115 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Clone dialog.
|
||||
/// </summary>
|
||||
public partial class Clone : UserControl {
|
||||
|
||||
/// <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>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public Clone() {
|
||||
ParentFolder = App.Preference.GitDefaultCloneDir;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show clone dialog.
|
||||
/// </summary>
|
||||
public static void Show() {
|
||||
PopupManager.Show(new Clone());
|
||||
}
|
||||
|
||||
/// <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 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;
|
||||
}
|
||||
|
||||
PopupManager.Lock();
|
||||
|
||||
status.Visibility = Visibility.Visible;
|
||||
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
|
||||
anim.RepeatBehavior = RepeatBehavior.Forever;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
|
||||
|
||||
Task.Run(() => {
|
||||
var repo = Git.Repository.Clone(RemoteUri, ParentFolder, repoName, msg => Dispatcher.Invoke(() => statusMsg.Content = msg));
|
||||
if (repo == null) {
|
||||
PopupManager.Unlock();
|
||||
Dispatcher.Invoke(() => {
|
||||
status.Visibility = Visibility.Collapsed;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
||||
});
|
||||
} else {
|
||||
Dispatcher.Invoke(() => PopupManager.Close(true));
|
||||
repo.Open();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
PopupManager.Close();
|
||||
}
|
||||
}
|
||||
}
|
408
UI/CommitViewer.xaml
Normal file
408
UI/CommitViewer.xaml
Normal file
|
@ -0,0 +1,408 @@
|
|||
<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=".5"/>
|
||||
<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=".5"/>
|
||||
<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"/>
|
||||
<Setter TargetName="Name" Property="FontWeight" Value="Bold"/>
|
||||
<Setter TargetName="Name" Property="Foreground" Value="{StaticResource Brush.FG}"/>
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- PARENTS -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="PARENTS" HorizontalAlignment="Right" Opacity=".5"/>
|
||||
<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=".5"/>
|
||||
<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=".5"/>
|
||||
<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=".5"/>
|
||||
<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=".5"/>
|
||||
<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=".5"/>
|
||||
<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=".5"/>
|
||||
<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=".5"/>
|
||||
<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"/>
|
||||
</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}">
|
||||
<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>
|
||||
</Border>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</UserControl>
|
468
UI/CommitViewer.xaml.cs
Normal file
468
UI/CommitViewer.xaml.cs
Normal file
|
@ -0,0 +1,468 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
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 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 async 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";
|
||||
}
|
||||
|
||||
List<string> data = new List<string>();
|
||||
|
||||
await Task.Run(() => {
|
||||
data = repo.Diff(start, commit.SHA, node.FilePath, node.OriginalPath);
|
||||
});
|
||||
|
||||
diffViewer.SetData(data, node.FilePath, node.OriginalPath);
|
||||
}
|
||||
|
||||
private async 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";
|
||||
}
|
||||
|
||||
List<string> data = new List<string>();
|
||||
|
||||
await Task.Run(() => {
|
||||
data = repo.Diff(start, commit.SHA, change.Path, change.OriginalPath);
|
||||
});
|
||||
|
||||
diffViewer.SetData(data, change.Path, 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;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region FILES
|
||||
private void SetRevisionFiles(List<string> files) {
|
||||
List<Node> fileTreeSource = new List<Node>();
|
||||
Dictionary<string, Node> folders = new Dictionary<string, Node>();
|
||||
|
||||
foreach (var path in files) {
|
||||
var sepIdx = path.IndexOf("/");
|
||||
if (sepIdx == -1) {
|
||||
Node node = new Node();
|
||||
node.FilePath = path;
|
||||
node.Name = path;
|
||||
node.IsFile = true;
|
||||
node.IsNodeExpanded = false;
|
||||
fileTreeSource.Add(node);
|
||||
} else {
|
||||
Node lastFolder = null;
|
||||
var start = 0;
|
||||
|
||||
while (sepIdx != -1) {
|
||||
var folder = 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 = path.IndexOf('/', start);
|
||||
}
|
||||
|
||||
Node node = new Node();
|
||||
node.FilePath = path;
|
||||
node.Name = path.Substring(start);
|
||||
node.IsFile = true;
|
||||
node.IsNodeExpanded = false;
|
||||
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 = "";
|
||||
|
||||
var node = e.NewValue as Node;
|
||||
if (node == null || !node.IsFile) return;
|
||||
|
||||
await Task.Run(() => {
|
||||
var data = commit.GetTextFileContent(repo, node.FilePath);
|
||||
Dispatcher.Invoke(() => filePreview.Text = data);
|
||||
});
|
||||
}
|
||||
#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.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
|
||||
}
|
||||
}
|
92
UI/CreateBranch.xaml
Normal file
92
UI/CreateBranch.xaml
Normal file
|
@ -0,0 +1,92 @@
|
|||
<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 Grid.Row="0" Grid.RowSpan="8" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="0"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
|
||||
<Label x:Name="statusMsg" Margin="0,8,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
143
UI/CreateBranch.xaml.cs
Normal file
143
UI/CreateBranch.xaml.cs
Normal file
|
@ -0,0 +1,143 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
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);
|
||||
PopupManager.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;
|
||||
PopupManager.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}";
|
||||
PopupManager.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;
|
||||
|
||||
PopupManager.Lock();
|
||||
|
||||
status.Visibility = Visibility.Visible;
|
||||
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
|
||||
anim.RepeatBehavior = RepeatBehavior.Forever;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
status.Visibility = Visibility.Collapsed;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
||||
PopupManager.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
PopupManager.Close();
|
||||
}
|
||||
}
|
||||
}
|
68
UI/CreateTag.xaml
Normal file
68
UI/CreateTag.xaml
Normal file
|
@ -0,0 +1,68 @@
|
|||
<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"
|
||||
AcceptsReturn="True"
|
||||
helpers:TextBoxHelper.Placeholder="Optional"/>
|
||||
|
||||
<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>
|
||||
|
92
UI/CreateTag.xaml.cs
Normal file
92
UI/CreateTag.xaml.cs
Normal file
|
@ -0,0 +1,92 @@
|
|||
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;
|
||||
PopupManager.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}";
|
||||
PopupManager.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);
|
||||
PopupManager.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
PopupManager.Close();
|
||||
}
|
||||
}
|
||||
}
|
450
UI/Dashboard.xaml
Normal file
450
UI/Dashboard.xaml
Normal file
|
@ -0,0 +1,450 @@
|
|||
<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:git="clr-namespace:SourceGit.Git"
|
||||
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="9999">
|
||||
<Border Background="{StaticResource Brush.BG1}">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect ShadowDepth="2" Direction="270" Opacity=".3"/>
|
||||
</Border.Effect>
|
||||
</Border>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Navigation -->
|
||||
<StackPanel Grid.Column="0" Margin="8,0,0,0" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<Button Click="Close" ToolTip="Back To Welcome" Padding="0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Manager}"/>
|
||||
<Label Content="Repositories"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Path Width="12" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Navigator}"/>
|
||||
<Path Margin="6,0,0,0" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Git}"/>
|
||||
<Label x:Name="repoName"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Common Git Options -->
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<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="2,0,0,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.SaveStash}"/>
|
||||
<Label Content="Stash"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Click="OpenApply" Margin="4,0">
|
||||
<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 Width="14" Height="14" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Search}"/>
|
||||
<Label Content="Search"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Click="OpenExplorer" Margin="4,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="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="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<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}">
|
||||
<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,0,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"
|
||||
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,0,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"
|
||||
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,0,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>
|
||||
<ListView
|
||||
Grid.Row="7"
|
||||
x:Name="tagList"
|
||||
Visibility="{Binding ElementName=tagListToggle, Path=IsChecked, Converter={StaticResource Bool2Collapsed}}"
|
||||
Background="{StaticResource Brush.BG3}"
|
||||
Height="200"
|
||||
LostFocus="TagLostFocus"
|
||||
SelectionChanged="TagSelectionChanged"
|
||||
ContextMenuOpening="TagContextMenuOpening"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
Style="{StaticResource Style.ListView.Borderless}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type git:Tag}">
|
||||
<Grid Margin="16, 0, 0, 0" Height="20">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Path Grid.Column="0" Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Tag}"/>
|
||||
<Label Grid.Column="1" Margin="4,0,0,0" Content="{Binding Name}" Padding="4,0,0,0"/>
|
||||
<ToggleButton
|
||||
Grid.Column="2"
|
||||
IsChecked="{Binding IsFiltered, Mode=TwoWay}"
|
||||
Checked="FilterChanged"
|
||||
Unchecked="FilterChanged"
|
||||
Style="{StaticResource Style.ToggleButton.Filter}"
|
||||
ToolTip="FILTER"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
|
||||
<!-- Splitter -->
|
||||
<GridSplitter Grid.Column="1" Width="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"/>
|
||||
|
||||
<!-- Right -->
|
||||
<Grid Grid.Column="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Abort panel -->
|
||||
<Grid x:Name="abortPanel" Grid.Row="0" Background="LightGoldenrodYellow" 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 Grid.Row="1"/>
|
||||
</Grid>
|
||||
</UserControl>
|
984
UI/Dashboard.xaml.cs
Normal file
984
UI/Dashboard.xaml.cs
Normal file
|
@ -0,0 +1,984 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace SourceGit.UI {
|
||||
|
||||
/// <summary>
|
||||
/// Branch node in tree.
|
||||
/// </summary>
|
||||
public class BranchNode {
|
||||
public string Name { get; set; }
|
||||
public Git.Branch Branch { get; set; }
|
||||
public bool IsExpanded { get; set; }
|
||||
public bool IsCurrent => Branch != null ? Branch.IsCurrent : false;
|
||||
public bool IsFiltered => Branch != null ? Branch.IsFiltered : false;
|
||||
public string Track => Branch != null ? Branch.UpstreamTrack : "";
|
||||
public Visibility FilterVisibility => Branch == null ? Visibility.Collapsed : Visibility.Visible;
|
||||
public Visibility TrackVisibility => (Branch != null && !Branch.IsSameWithUpstream) ? Visibility.Visible : Visibility.Collapsed;
|
||||
public List<BranchNode> Children { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remote node in tree.
|
||||
/// </summary>
|
||||
public class RemoteNode {
|
||||
public string Name { get; set; }
|
||||
public bool IsExpanded { get; set; }
|
||||
public List<BranchNode> Children { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dashboard for opened repository.
|
||||
/// </summary>
|
||||
public partial class Dashboard : UserControl {
|
||||
private Git.Repository repo = null;
|
||||
private List<BranchNode> cachedLocalBranches = new List<BranchNode>();
|
||||
private List<RemoteNode> cachedRemotes = new List<RemoteNode>();
|
||||
private string abortCommand = null;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="repo">Opened repository.</param>
|
||||
public Dashboard(Git.Repository opened) {
|
||||
opened.OnWorkingCopyChanged = UpdateLocalChanges;
|
||||
opened.OnTagChanged = UpdateTags;
|
||||
opened.OnStashChanged = UpdateStashes;
|
||||
opened.OnBranchChanged = () => UpdateBranches(false);
|
||||
opened.OnCommitsChanged = UpdateHistories;
|
||||
opened.OnNavigateCommit = commit => {
|
||||
Dispatcher.Invoke(() => {
|
||||
workspace.SelectedItem = historiesSwitch;
|
||||
histories.Navigate(commit);
|
||||
});
|
||||
};
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
repo = opened;
|
||||
repoName.Content = repo.Name;
|
||||
histories.Repo = opened;
|
||||
commits.Repo = opened;
|
||||
|
||||
UpdateBranches();
|
||||
UpdateHistories();
|
||||
UpdateLocalChanges();
|
||||
UpdateStashes();
|
||||
UpdateTags();
|
||||
}
|
||||
|
||||
#region DATA_UPDATE
|
||||
private void UpdateHistories() {
|
||||
Dispatcher.Invoke(() => {
|
||||
histories.SetLoadingEnabled(true);
|
||||
});
|
||||
|
||||
Task.Run(() => {
|
||||
var args = "-5000 ";
|
||||
if (repo.LogFilters.Count > 0) {
|
||||
args = args + string.Join(" ", repo.LogFilters);
|
||||
} else {
|
||||
args = args + "--branches --remotes --tags";
|
||||
}
|
||||
|
||||
var commits = repo.Commits(args);
|
||||
histories.SetCommits(commits);
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateLocalChanges() {
|
||||
Task.Run(() => {
|
||||
var changes = repo.LocalChanges();
|
||||
var conflicts = commits.SetData(changes);
|
||||
|
||||
Dispatcher.Invoke(() => {
|
||||
localChangesBadge.Visibility = changes.Count == 0 ? Visibility.Collapsed : Visibility.Visible;
|
||||
localChangesCount.Content = changes.Count;
|
||||
btnContinue.Visibility = conflicts ? Visibility.Collapsed : Visibility.Visible;
|
||||
DetectMergeState();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateStashes() {
|
||||
Task.Run(() => {
|
||||
var data = repo.Stashes();
|
||||
Dispatcher.Invoke(() => {
|
||||
stashBadge.Visibility = data.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
stashCount.Content = data.Count;
|
||||
stashes.SetData(repo, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void BackupBranchNodeExpandState(Dictionary<string, bool> states, List<BranchNode> nodes, string prefix) {
|
||||
foreach (var node in nodes) {
|
||||
var path = prefix + "/" + node.Name;
|
||||
states.Add(path, node.IsExpanded);
|
||||
BackupBranchNodeExpandState(states, node.Children, path);
|
||||
}
|
||||
}
|
||||
|
||||
private void MakeBranchNode(Git.Branch branch, List<BranchNode> collection, Dictionary<string, BranchNode> folders, Dictionary<string, bool> expandStates, string prefix) {
|
||||
var subs = branch.Name.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (!branch.IsLocal) {
|
||||
if (subs.Length < 2) return;
|
||||
subs = subs.Skip(1).ToArray();
|
||||
}
|
||||
|
||||
branch.IsFiltered = repo.LogFilters.Contains(branch.FullName);
|
||||
|
||||
if (subs.Length == 1) {
|
||||
var node = new BranchNode() {
|
||||
Name = subs[0],
|
||||
Branch = branch,
|
||||
Children = new List<BranchNode>(),
|
||||
};
|
||||
collection.Add(node);
|
||||
} else {
|
||||
BranchNode lastFolder = null;
|
||||
string path = prefix;
|
||||
for (int i = 0; i < subs.Length - 1; i++) {
|
||||
path = path + "/" + subs[i];
|
||||
if (folders.ContainsKey(path)) {
|
||||
lastFolder = folders[path];
|
||||
} else if (lastFolder == null) {
|
||||
lastFolder = new BranchNode() {
|
||||
Name = subs[i],
|
||||
IsExpanded = expandStates.ContainsKey(path) ? expandStates[path] : false,
|
||||
Children = new List<BranchNode>(),
|
||||
};
|
||||
collection.Add(lastFolder);
|
||||
folders.Add(path, lastFolder);
|
||||
} else {
|
||||
var folder = new BranchNode() {
|
||||
Name = subs[i],
|
||||
IsExpanded = expandStates.ContainsKey(path) ? expandStates[path] : false,
|
||||
Children = new List<BranchNode>(),
|
||||
};
|
||||
lastFolder.Children.Add(folder);
|
||||
folders.Add(path, folder);
|
||||
lastFolder = folder;
|
||||
}
|
||||
}
|
||||
|
||||
BranchNode node = new BranchNode();
|
||||
node.Name = subs[subs.Length - 1];
|
||||
node.Branch = branch;
|
||||
node.Children = new List<BranchNode>();
|
||||
lastFolder.Children.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
private void SortBranchNodes(List<BranchNode> collection) {
|
||||
collection.Sort((l, r) => {
|
||||
if (l.Branch != null) {
|
||||
return r.Branch != null ? l.Branch.Name.CompareTo(r.Branch.Name) : -1;
|
||||
} else {
|
||||
return r.Branch == null ? l.Name.CompareTo(r.Name) : 1;
|
||||
}
|
||||
});
|
||||
|
||||
foreach (var sub in collection) {
|
||||
if (sub.Children.Count > 0) SortBranchNodes(sub.Children);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateBranches(bool force = true) {
|
||||
Task.Run(() => {
|
||||
var branches = repo.Branches(force);
|
||||
var localBranchNodes = new List<BranchNode>();
|
||||
var remoteNodes = new List<RemoteNode>();
|
||||
var remoteMap = new Dictionary<string, RemoteNode>();
|
||||
var folders = new Dictionary<string, BranchNode>();
|
||||
var states = new Dictionary<string, bool>();
|
||||
|
||||
BackupBranchNodeExpandState(states, cachedLocalBranches, "locals");
|
||||
foreach (var r in cachedRemotes) {
|
||||
var prefix = $"remotes/{r.Name}";
|
||||
states.Add(prefix, r.IsExpanded);
|
||||
BackupBranchNodeExpandState(states, r.Children, prefix);
|
||||
}
|
||||
|
||||
foreach (var b in branches) {
|
||||
if (b.IsLocal) {
|
||||
MakeBranchNode(b, localBranchNodes, folders, states, "locals");
|
||||
} else {
|
||||
RemoteNode remote = null;
|
||||
|
||||
if (!remoteMap.ContainsKey(b.Remote)) {
|
||||
var key = "remotes/" + b.Remote;
|
||||
remote = new RemoteNode() {
|
||||
Name = b.Remote,
|
||||
IsExpanded = states.ContainsKey(key) ? states[key] : false,
|
||||
Children = new List<BranchNode>(),
|
||||
};
|
||||
remoteNodes.Add(remote);
|
||||
remoteMap.Add(b.Remote, remote);
|
||||
} else {
|
||||
remote = remoteMap[b.Remote];
|
||||
}
|
||||
|
||||
MakeBranchNode(b, remote.Children, folders, states, "remotes");
|
||||
}
|
||||
}
|
||||
|
||||
SortBranchNodes(localBranchNodes);
|
||||
foreach (var r in remoteNodes) SortBranchNodes(r.Children);
|
||||
|
||||
cachedLocalBranches = localBranchNodes;
|
||||
cachedRemotes = remoteNodes;
|
||||
|
||||
Dispatcher.Invoke(() => {
|
||||
localBranchTree.ItemsSource = localBranchNodes;
|
||||
remoteBranchTree.ItemsSource = remoteNodes;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateTags() {
|
||||
Task.Run(() => {
|
||||
var tags = repo.Tags(true);
|
||||
foreach (var t in tags) t.IsFiltered = repo.LogFilters.Contains(t.Name);
|
||||
|
||||
Dispatcher.Invoke(() => {
|
||||
tagCount.Content = $"TAGS ({tags.Count})";
|
||||
tagList.ItemsSource = tags;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void Cleanup(object sender, RoutedEventArgs e) {
|
||||
localBranchTree.ItemsSource = null;
|
||||
remoteBranchTree.ItemsSource = null;
|
||||
tagList.ItemsSource = null;
|
||||
cachedLocalBranches.Clear();
|
||||
cachedRemotes.Clear();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region TOOLBAR
|
||||
private void Close(object sender, RoutedEventArgs e) {
|
||||
if (PopupManager.IsLocked()) return;
|
||||
PopupManager.Close();
|
||||
|
||||
cachedLocalBranches.Clear();
|
||||
cachedRemotes.Clear();
|
||||
|
||||
repo.Close();
|
||||
}
|
||||
|
||||
private void OpenFetch(object sender, RoutedEventArgs e) {
|
||||
Fetch.Show(repo);
|
||||
}
|
||||
|
||||
private void OpenPull(object sender, RoutedEventArgs e) {
|
||||
Pull.Show(repo);
|
||||
}
|
||||
|
||||
private void OpenPush(object sender, RoutedEventArgs e) {
|
||||
Push.Show(repo);
|
||||
}
|
||||
|
||||
private void OpenStash(object sender, RoutedEventArgs e) {
|
||||
Stash.Show(repo, new List<string>());
|
||||
}
|
||||
|
||||
private void OpenApply(object sender, RoutedEventArgs e) {
|
||||
Apply.Show(repo);
|
||||
}
|
||||
|
||||
private void OpenSearch(object sender, RoutedEventArgs e) {
|
||||
workspace.SelectedItem = historiesSwitch;
|
||||
if (histories.searchBar.Margin.Top == 0) {
|
||||
histories.HideSearchBar();
|
||||
} else {
|
||||
histories.OpenSearchBar();
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenExplorer(object sender, RoutedEventArgs e) {
|
||||
Process.Start(repo.Path);
|
||||
}
|
||||
|
||||
private void OpenTerminal(object sender, RoutedEventArgs e) {
|
||||
var bash = Path.Combine(App.Preference.GitExecutable, "..", "bash.exe");
|
||||
if (!File.Exists(bash)) {
|
||||
App.RaiseError("Can NOT locate bash.exe. Make sure bash.exe exists under the same folder with git.exe");
|
||||
return;
|
||||
}
|
||||
|
||||
var start = new ProcessStartInfo();
|
||||
start.WorkingDirectory = repo.Path;
|
||||
start.FileName = bash;
|
||||
Process.Start(start);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region HOT_KEYS
|
||||
public void OpenSearchBar(object sender, ExecutedRoutedEventArgs e) {
|
||||
workspace.SelectedItem = historiesSwitch;
|
||||
histories.OpenSearchBar();
|
||||
}
|
||||
|
||||
public void HideSearchBar(object sender, ExecutedRoutedEventArgs e) {
|
||||
if (histories.Visibility == Visibility.Visible) {
|
||||
histories.HideSearchBar();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region MERGE_ABORTS
|
||||
public void DetectMergeState() {
|
||||
var gitDir = Path.Combine(repo.Path, ".git");
|
||||
var cherryPickMerge = Path.Combine(gitDir, "CHERRY_PICK_HEAD");
|
||||
var rebaseMerge = Path.Combine(gitDir, "REBASE_HEAD");
|
||||
var revertMerge = Path.Combine(gitDir, "REVERT_HEAD");
|
||||
var otherMerge = Path.Combine(gitDir, "MERGE_HEAD");
|
||||
|
||||
if (File.Exists(cherryPickMerge)) {
|
||||
abortCommand = "cherry-pick";
|
||||
txtMergeProcessing.Content = "Cherry-Pick merge request detected! Press 'Abort' to restore original HEAD";
|
||||
} else if (File.Exists(rebaseMerge)) {
|
||||
abortCommand = "rebase";
|
||||
txtMergeProcessing.Content = "Rebase merge request detected! Press 'Abort' to restore original HEAD";
|
||||
} else if (File.Exists(revertMerge)) {
|
||||
abortCommand = "revert";
|
||||
txtMergeProcessing.Content = "Revert merge request detected! Press 'Abort' to restore original HEAD";
|
||||
} else if (File.Exists(otherMerge)) {
|
||||
abortCommand = "merge";
|
||||
txtMergeProcessing.Content = "Merge request detected! Press 'Abort' to restore original HEAD";
|
||||
} else {
|
||||
abortCommand = null;
|
||||
}
|
||||
|
||||
if (abortCommand != null) {
|
||||
abortPanel.Visibility = Visibility.Visible;
|
||||
if (commits.Visibility == Visibility.Visible) {
|
||||
btnResolve.Visibility = Visibility.Collapsed;
|
||||
} else {
|
||||
btnResolve.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
commits.LoadMergeMessage();
|
||||
} else {
|
||||
abortPanel.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void Resolve(object sender, RoutedEventArgs e) {
|
||||
workspace.SelectedItem = workingCopySwitch;
|
||||
}
|
||||
|
||||
private async void Continue(object sender, RoutedEventArgs e) {
|
||||
if (abortCommand == null) return;
|
||||
|
||||
await Task.Run(() => {
|
||||
repo.SetWatcherEnabled(false);
|
||||
var errs = repo.RunCommand($"-c core.editor=true {abortCommand} --continue", null);
|
||||
repo.AssertCommand(errs);
|
||||
});
|
||||
|
||||
commits.ClearMessage();
|
||||
}
|
||||
|
||||
private async void Abort(object sender, RoutedEventArgs e) {
|
||||
if (abortCommand == null) return;
|
||||
|
||||
await Task.Run(() => {
|
||||
repo.SetWatcherEnabled(false);
|
||||
var errs = repo.RunCommand($"{abortCommand} --abort", null);
|
||||
repo.AssertCommand(errs);
|
||||
});
|
||||
|
||||
commits.ClearMessage();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region WORKSPACE
|
||||
private void SwitchWorkingCopy(object sender, RoutedEventArgs e) {
|
||||
if (commits == null || histories == null || stashes == null) return;
|
||||
|
||||
commits.Visibility = Visibility.Visible;
|
||||
histories.Visibility = Visibility.Collapsed;
|
||||
stashes.Visibility = Visibility.Collapsed;
|
||||
|
||||
if (abortPanel.Visibility == Visibility.Visible) {
|
||||
btnResolve.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void SwitchHistories(object sender, RoutedEventArgs e) {
|
||||
if (commits == null || histories == null || stashes == null) return;
|
||||
|
||||
commits.Visibility = Visibility.Collapsed;
|
||||
histories.Visibility = Visibility.Visible;
|
||||
stashes.Visibility = Visibility.Collapsed;
|
||||
|
||||
if (abortPanel.Visibility == Visibility.Visible) {
|
||||
btnResolve.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
private void SwitchStashes(object sender, RoutedEventArgs e) {
|
||||
if (commits == null || histories == null || stashes == null) return;
|
||||
|
||||
commits.Visibility = Visibility.Collapsed;
|
||||
histories.Visibility = Visibility.Collapsed;
|
||||
stashes.Visibility = Visibility.Visible;
|
||||
|
||||
if (abortPanel.Visibility == Visibility.Visible) {
|
||||
btnResolve.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region LOCAL_BRANCHES
|
||||
private void OpenNewBranch(object sender, RoutedEventArgs e) {
|
||||
CreateBranch.Show(repo);
|
||||
}
|
||||
|
||||
private void OpenGitFlow(object sender, RoutedEventArgs ev) {
|
||||
var button = sender as Button;
|
||||
if (button.ContextMenu == null) {
|
||||
button.ContextMenu = new ContextMenu();
|
||||
button.ContextMenu.PlacementTarget = button;
|
||||
button.ContextMenu.Placement = PlacementMode.Bottom;
|
||||
button.ContextMenu.StaysOpen = false;
|
||||
button.ContextMenu.Focusable = true;
|
||||
} else {
|
||||
button.ContextMenu.Items.Clear();
|
||||
}
|
||||
|
||||
if (repo.IsGitFlowEnabled()) {
|
||||
var startFeature = new MenuItem();
|
||||
startFeature.Header = "Start Feature ...";
|
||||
startFeature.Click += (o, e) => {
|
||||
GitFlowStartBranch.Show(repo, Git.Branch.Type.Feature);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var startRelease = new MenuItem();
|
||||
startRelease.Header = "Start Release ...";
|
||||
startRelease.Click += (o, e) => {
|
||||
GitFlowStartBranch.Show(repo, Git.Branch.Type.Release);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var startHotfix = new MenuItem();
|
||||
startHotfix.Header = "Start Hotfix ...";
|
||||
startHotfix.Click += (o, e) => {
|
||||
GitFlowStartBranch.Show(repo, Git.Branch.Type.Hotfix);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
button.ContextMenu.Items.Add(startFeature);
|
||||
button.ContextMenu.Items.Add(startRelease);
|
||||
button.ContextMenu.Items.Add(startHotfix);
|
||||
} else {
|
||||
var init = new MenuItem();
|
||||
init.Header = "Initialize Git-Flow";
|
||||
init.Click += (o, e) => {
|
||||
GitFlowSetup.Show(repo);
|
||||
e.Handled = true;
|
||||
};
|
||||
button.ContextMenu.Items.Add(init);
|
||||
}
|
||||
|
||||
button.ContextMenu.IsOpen = true;
|
||||
ev.Handled = true;
|
||||
}
|
||||
|
||||
private void LocalBranchSelected(object sender, RoutedPropertyChangedEventArgs<object> e) {
|
||||
var node = e.NewValue as BranchNode;
|
||||
if (node == null || node.Branch == null) return;
|
||||
repo.OnNavigateCommit?.Invoke(node.Branch.Head);
|
||||
}
|
||||
|
||||
private void LocalBranchMouseDoubleClick(object sender, MouseButtonEventArgs e) {
|
||||
var node = (sender as TreeViewItem).DataContext as BranchNode;
|
||||
if (node == null || node.Branch == null) return;
|
||||
Task.Run(() => repo.Checkout(node.Branch.Name));
|
||||
}
|
||||
|
||||
private void LocalBranchContextMenuOpening(object sender, ContextMenuEventArgs ev) {
|
||||
var node = (sender as TreeViewItem).DataContext as BranchNode;
|
||||
if (node == null || node.Branch == null) return;
|
||||
|
||||
var menu = new ContextMenu();
|
||||
var branch = node.Branch;
|
||||
|
||||
var push = new MenuItem();
|
||||
push.Header = $"Push '{branch.Name}'";
|
||||
push.Click += (o, e) => {
|
||||
Push.Show(repo, branch);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
if (branch.IsCurrent) {
|
||||
var discard = new MenuItem();
|
||||
discard.Header = "Discard all changes";
|
||||
discard.Click += (o, e) => {
|
||||
Discard.Show(repo, null);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(discard);
|
||||
menu.Items.Add(new Separator());
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
var pull = new MenuItem();
|
||||
pull.Header = $"Pull '{upstream}'";
|
||||
pull.Click += (o, e) => {
|
||||
Pull.Show(repo);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(fastForward);
|
||||
menu.Items.Add(pull);
|
||||
}
|
||||
|
||||
menu.Items.Add(push);
|
||||
} else {
|
||||
var current = repo.CurrentBranch();
|
||||
|
||||
var checkout = new MenuItem();
|
||||
checkout.Header = $"Checkout {branch.Name}";
|
||||
checkout.Click += (o, e) => {
|
||||
Task.Run(() => repo.Checkout(node.Branch.Name));
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(checkout);
|
||||
menu.Items.Add(new Separator());
|
||||
menu.Items.Add(push);
|
||||
|
||||
var merge = new MenuItem();
|
||||
merge.Header = $"Merge '{branch.Name}' into '{current.Name}'";
|
||||
merge.Click += (o, e) => {
|
||||
Merge.Show(repo, branch.Name, current.Name);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(merge);
|
||||
|
||||
var rebase = new MenuItem();
|
||||
rebase.Header = $"Rebase '{current.Name}' on '{branch.Name}'";
|
||||
rebase.Click += (o, e) => {
|
||||
Rebase.Show(repo, branch);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(rebase);
|
||||
}
|
||||
|
||||
if (branch.Kind != Git.Branch.Type.Normal) {
|
||||
menu.Items.Add(new Separator());
|
||||
|
||||
var icon = new System.Windows.Shapes.Path();
|
||||
icon.Style = FindResource("Style.Icon") as Style;
|
||||
icon.Data = FindResource("Icon.Flow") as Geometry;
|
||||
icon.Width = 10;
|
||||
|
||||
var finish = new MenuItem();
|
||||
finish.Header = $"Git Flow - Finish '{branch.Name}'";
|
||||
finish.Icon = icon;
|
||||
finish.Click += (o, e) => {
|
||||
GitFlowFinishBranch.Show(repo, branch);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
menu.Items.Add(finish);
|
||||
}
|
||||
|
||||
var rename = new MenuItem();
|
||||
rename.Header = $"Rename '{branch.Name}'";
|
||||
rename.Click += (o, e) => {
|
||||
RenameBranch.Show(repo, branch);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(new Separator());
|
||||
menu.Items.Add(rename);
|
||||
|
||||
var delete = new MenuItem();
|
||||
delete.Header = $"Delete '{branch.Name}'";
|
||||
delete.IsEnabled = !branch.IsCurrent;
|
||||
delete.Click += (o, e) => {
|
||||
DeleteBranch.Show(repo, branch);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(delete);
|
||||
menu.Items.Add(new Separator());
|
||||
|
||||
var createBranch = new MenuItem();
|
||||
createBranch.Header = "Create Branch";
|
||||
createBranch.Click += (o, e) => {
|
||||
CreateBranch.Show(repo, branch);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(createBranch);
|
||||
|
||||
var createTag = new MenuItem();
|
||||
createTag.Header = "Create Tag";
|
||||
createTag.Click += (o, e) => {
|
||||
CreateTag.Show(repo, branch);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(createTag);
|
||||
menu.Items.Add(new Separator());
|
||||
|
||||
var copy = new MenuItem();
|
||||
copy.Header = "Copy Branch Name";
|
||||
copy.Click += (o, e) => {
|
||||
Clipboard.SetText(branch.Name);
|
||||
e.Handled = true;
|
||||
};
|
||||
menu.Items.Add(copy);
|
||||
|
||||
menu.IsOpen = true;
|
||||
ev.Handled = true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region REMOTE_BRANCHES
|
||||
private void OpenRemote(object sender, RoutedEventArgs e) {
|
||||
Remote.Show(repo);
|
||||
}
|
||||
|
||||
private void OpenRemoteContextMenu(RemoteNode node) {
|
||||
var fetch = new MenuItem();
|
||||
fetch.Header = $"Fetch '{node.Name}'";
|
||||
fetch.Click += (o, e) => {
|
||||
Fetch.Show(repo, node.Name);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var edit = new MenuItem();
|
||||
edit.Header = $"Edit '{node.Name}'";
|
||||
edit.Click += (o, e) => {
|
||||
var remotes = repo.Remotes();
|
||||
var found = remotes.Find(r => r.Name == node.Name);
|
||||
if (found != null) Remote.Show(repo, found);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var delete = new MenuItem();
|
||||
delete.Header = $"Delete '{node.Name}'";
|
||||
delete.Click += (o, e) => {
|
||||
DeleteRemote.Show(repo, node.Name);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var copy = new MenuItem();
|
||||
copy.Header = "Copy Remote URL";
|
||||
copy.Click += (o, e) => {
|
||||
var remotes = repo.Remotes();
|
||||
var found = remotes.Find(r => r.Name == node.Name);
|
||||
if (found != null) Clipboard.SetText(found.URL);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var menu = new ContextMenu();
|
||||
menu.Items.Add(fetch);
|
||||
menu.Items.Add(new Separator());
|
||||
menu.Items.Add(edit);
|
||||
menu.Items.Add(delete);
|
||||
menu.Items.Add(new Separator());
|
||||
menu.Items.Add(copy);
|
||||
menu.IsOpen = true;
|
||||
}
|
||||
|
||||
private void OpenRemoteBranchContextMenu(BranchNode node) {
|
||||
var branch = node.Branch;
|
||||
var current = repo.CurrentBranch();
|
||||
if (current == null) return;
|
||||
|
||||
var checkout = new MenuItem();
|
||||
checkout.Header = $"Checkout '{branch.Name}'";
|
||||
checkout.Click += (o, e) => {
|
||||
var branches = repo.Branches();
|
||||
var tracked = null as Git.Branch;
|
||||
var upstream = $"refs/remotes/{branch.Name}";
|
||||
|
||||
foreach (var b in branches) {
|
||||
if (b.IsLocal && b.Upstream == upstream) {
|
||||
tracked = b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tracked == null) {
|
||||
CreateBranch.Show(repo, branch);
|
||||
} else if (!tracked.IsCurrent) {
|
||||
Task.Run(() => repo.Checkout(tracked.Name));
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var pull = new MenuItem();
|
||||
pull.Header = $"Pull '{branch.Name}' into '{current.Name}'";
|
||||
pull.Click += (o, e) => {
|
||||
Pull.Show(repo, branch.Name);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var merge = new MenuItem();
|
||||
merge.Header = $"Merge '{branch.Name}' into '{current.Name}'";
|
||||
merge.Click += (o, e) => {
|
||||
Merge.Show(repo, branch.Name, current.Name);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var rebase = new MenuItem();
|
||||
rebase.Header = $"Rebase '{current.Name}' on '{branch.Name}'";
|
||||
rebase.Click += (o, e) => {
|
||||
Rebase.Show(repo, branch);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var delete = new MenuItem();
|
||||
delete.Header = $"Delete '{branch.Name}'";
|
||||
delete.Click += (o, e) => {
|
||||
DeleteBranch.Show(repo, branch);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var createBranch = new MenuItem();
|
||||
createBranch.Header = "Create New Branch";
|
||||
createBranch.Click += (o, e) => {
|
||||
CreateBranch.Show(repo, branch);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var createTag = new MenuItem();
|
||||
createTag.Header = "Create New Tag";
|
||||
createTag.Click += (o, e) => {
|
||||
CreateTag.Show(repo, branch);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var copy = new MenuItem();
|
||||
copy.Header = "Copy Branch Name";
|
||||
copy.Click += (o, e) => {
|
||||
Clipboard.SetText(branch.Name);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
var menu = new ContextMenu();
|
||||
menu.Items.Add(checkout);
|
||||
menu.Items.Add(new Separator());
|
||||
menu.Items.Add(pull);
|
||||
menu.Items.Add(merge);
|
||||
menu.Items.Add(rebase);
|
||||
menu.Items.Add(new Separator());
|
||||
menu.Items.Add(delete);
|
||||
menu.Items.Add(new Separator());
|
||||
menu.Items.Add(createBranch);
|
||||
menu.Items.Add(createTag);
|
||||
menu.Items.Add(new Separator());
|
||||
menu.Items.Add(copy);
|
||||
menu.IsOpen = true;
|
||||
}
|
||||
|
||||
private void RemoteBranchSelected(object sender, RoutedPropertyChangedEventArgs<object> e) {
|
||||
var node = e.NewValue as BranchNode;
|
||||
if (node == null || node.Branch == null) return;
|
||||
repo.OnNavigateCommit?.Invoke(node.Branch.Head);
|
||||
}
|
||||
|
||||
private void RemoteContextMenuOpening(object sender, ContextMenuEventArgs ev) {
|
||||
var remoteNode = (sender as TreeViewItem).DataContext as RemoteNode;
|
||||
if (remoteNode != null) {
|
||||
OpenRemoteContextMenu(remoteNode);
|
||||
ev.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var branchNode = (sender as TreeViewItem).DataContext as BranchNode;
|
||||
if (branchNode != null && branchNode.Branch != null) {
|
||||
OpenRemoteBranchContextMenu(branchNode);
|
||||
ev.Handled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region TAGS
|
||||
private void OpenNewTag(object sender, RoutedEventArgs e) {
|
||||
CreateTag.Show(repo);
|
||||
}
|
||||
|
||||
private void TagLostFocus(object sender, RoutedEventArgs e) {
|
||||
(sender as ListView).UnselectAll();
|
||||
}
|
||||
|
||||
private void TagSelectionChanged(object sender, SelectionChangedEventArgs e) {
|
||||
if (e.AddedItems.Count == 1) {
|
||||
var item = e.AddedItems[0] as Git.Tag;
|
||||
repo.OnNavigateCommit?.Invoke(item.SHA);
|
||||
}
|
||||
}
|
||||
|
||||
private void TagContextMenuOpening(object sender, ContextMenuEventArgs e) {
|
||||
var tag = (sender as ListView).SelectedItem as Git.Tag;
|
||||
if (tag == null) return;
|
||||
|
||||
var createBranch = new MenuItem();
|
||||
createBranch.Header = "Create New Branch";
|
||||
createBranch.Click += (o, ev) => {
|
||||
CreateBranch.Show(repo, tag);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var pushTag = new MenuItem();
|
||||
pushTag.Header = $"Push '{tag.Name}'";
|
||||
pushTag.Click += (o, ev) => {
|
||||
PushTag.Show(repo, tag);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var deleteTag = new MenuItem();
|
||||
deleteTag.Header = $"Delete '{tag.Name}'";
|
||||
deleteTag.Click += (o, ev) => {
|
||||
DeleteTag.Show(repo, tag);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var copy = new MenuItem();
|
||||
copy.Header = "Copy Name";
|
||||
copy.Click += (o, ev) => {
|
||||
Clipboard.SetText(tag.Name);
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
var menu = new ContextMenu();
|
||||
menu.Items.Add(createBranch);
|
||||
menu.Items.Add(new Separator());
|
||||
menu.Items.Add(pushTag);
|
||||
menu.Items.Add(deleteTag);
|
||||
menu.Items.Add(new Separator());
|
||||
menu.Items.Add(copy);
|
||||
menu.IsOpen = true;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region TREES
|
||||
private TreeViewItem FindTreeViewItem(ItemsControl item, BranchNode node) {
|
||||
if (item == null) return null;
|
||||
|
||||
var data = item.DataContext as BranchNode;
|
||||
if (data == node) return item as TreeViewItem;
|
||||
|
||||
for (int i = 0; i < item.Items.Count; i++) {
|
||||
var childContainer = item.ItemContainerGenerator.ContainerFromIndex(i) as ItemsControl;
|
||||
var child = FindTreeViewItem(childContainer, node);
|
||||
if (child != null) return child;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void TreeLostFocus(object sender, RoutedEventArgs e) {
|
||||
var tree = sender as TreeView;
|
||||
var remote = tree.SelectedItem as RemoteNode;
|
||||
if (remote != null) {
|
||||
var remoteItem = tree.ItemContainerGenerator.ContainerFromItem(remote) as TreeViewItem;
|
||||
if (remoteItem != null) remoteItem.IsSelected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var node = tree.SelectedItem as BranchNode;
|
||||
if (node == null) return;
|
||||
|
||||
var item = FindTreeViewItem(tree, node);
|
||||
if (item != null) item.IsSelected = false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region FILETER
|
||||
private void FilterChanged(object sender, RoutedEventArgs e) {
|
||||
var toggle = sender as ToggleButton;
|
||||
if (toggle == null) return;
|
||||
|
||||
if (toggle.DataContext is BranchNode) {
|
||||
var branch = (toggle.DataContext as BranchNode).Branch;
|
||||
if (branch == null) return;
|
||||
|
||||
if (toggle.IsChecked == true) {
|
||||
if (!repo.LogFilters.Contains(branch.FullName)) {
|
||||
repo.LogFilters.Add(branch.FullName);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(branch.Upstream) && !repo.LogFilters.Contains(branch.Upstream)) {
|
||||
repo.LogFilters.Add(branch.Upstream);
|
||||
UpdateBranches(false);
|
||||
}
|
||||
} else {
|
||||
repo.LogFilters.Remove(branch.FullName);
|
||||
if (!string.IsNullOrEmpty(branch.Upstream)) {
|
||||
repo.LogFilters.Remove(branch.Upstream);
|
||||
UpdateBranches(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toggle.DataContext is Git.Tag) {
|
||||
var tag = toggle.DataContext as Git.Tag;
|
||||
|
||||
if (toggle.IsChecked == true) {
|
||||
if (!repo.LogFilters.Contains(tag.Name)) {
|
||||
repo.LogFilters.Add(tag.Name);
|
||||
}
|
||||
} else {
|
||||
repo.LogFilters.Remove(tag.Name);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateHistories();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
51
UI/DeleteBranch.xaml
Normal file
51
UI/DeleteBranch.xaml
Normal file
|
@ -0,0 +1,51 @@
|
|||
<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 Grid.Row="0" Grid.RowSpan="5" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
|
||||
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="0"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
66
UI/DeleteBranch.xaml.cs
Normal file
66
UI/DeleteBranch.xaml.cs
Normal file
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
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) {
|
||||
PopupManager.Show(new DeleteBranch(opened, branch));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private async void Sure(object sender, RoutedEventArgs e) {
|
||||
PopupManager.Lock();
|
||||
|
||||
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
|
||||
anim.RepeatBehavior = RepeatBehavior.Forever;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
|
||||
status.Visibility = Visibility.Visible;
|
||||
|
||||
await Task.Run(() => branch.Delete(repo));
|
||||
|
||||
status.Visibility = Visibility.Collapsed;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
||||
PopupManager.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
PopupManager.Close();
|
||||
}
|
||||
}
|
||||
}
|
50
UI/DeleteRemote.xaml
Normal file
50
UI/DeleteRemote.xaml
Normal file
|
@ -0,0 +1,50 @@
|
|||
<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 Grid.Row="0" Grid.RowSpan="5" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
|
||||
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="0"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
67
UI/DeleteRemote.xaml.cs
Normal file
67
UI/DeleteRemote.xaml.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
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) {
|
||||
PopupManager.Show(new DeleteRemote(opened, remote));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private async void Sure(object sender, RoutedEventArgs e) {
|
||||
PopupManager.Lock();
|
||||
|
||||
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
|
||||
anim.RepeatBehavior = RepeatBehavior.Forever;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
|
||||
status.Visibility = Visibility.Visible;
|
||||
|
||||
await Task.Run(() => Git.Remote.Delete(repo, remote));
|
||||
|
||||
status.Visibility = Visibility.Collapsed;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
||||
PopupManager.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
PopupManager.Close();
|
||||
}
|
||||
}
|
||||
}
|
53
UI/DeleteTag.xaml
Normal file
53
UI/DeleteTag.xaml
Normal file
|
@ -0,0 +1,53 @@
|
|||
<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 Grid.Row="0" Grid.RowSpan="6" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
|
||||
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="0"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
69
UI/DeleteTag.xaml.cs
Normal file
69
UI/DeleteTag.xaml.cs
Normal file
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
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) {
|
||||
PopupManager.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) {
|
||||
PopupManager.Lock();
|
||||
|
||||
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
|
||||
anim.RepeatBehavior = RepeatBehavior.Forever;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
|
||||
status.Visibility = Visibility.Visible;
|
||||
|
||||
var push = chkWithRemote.IsChecked == true;
|
||||
await Task.Run(() => Git.Tag.Delete(repo, tag.Name, push));
|
||||
|
||||
status.Visibility = Visibility.Collapsed;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
||||
PopupManager.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
PopupManager.Close();
|
||||
}
|
||||
}
|
||||
}
|
139
UI/DiffViewer.xaml
Normal file
139
UI/DiffViewer.xaml
Normal file
|
@ -0,0 +1,139 @@
|
|||
<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="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0" BorderBrush="{StaticResource Brush.Border2}" BorderThickness="0,0,0,1">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,4">
|
||||
<StackPanel 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>
|
||||
<Path Width="10" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.File}"/>
|
||||
<TextBlock x:Name="fileName" Margin="4,0" VerticalAlignment="Center" Foreground="{StaticResource Brush.FG}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Grid 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"
|
||||
Margin="4,0,4,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"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<RichTextBox.Document>
|
||||
<FlowDocument PageWidth="0"/>
|
||||
</RichTextBox.Document>
|
||||
</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"
|
||||
Margin="4,0,4,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"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<RichTextBox.Document>
|
||||
<FlowDocument PageWidth="0"/>
|
||||
</RichTextBox.Document>
|
||||
</RichTextBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<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>
|
294
UI/DiffViewer.xaml.cs
Normal file
294
UI/DiffViewer.xaml.cs
Normal file
|
@ -0,0 +1,294 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
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>
|
||||
/// Line mode.
|
||||
/// </summary>
|
||||
public enum LineMode {
|
||||
Normal,
|
||||
Indicator,
|
||||
Empty,
|
||||
Added,
|
||||
Deleted,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public DiffViewer() {
|
||||
InitializeComponent();
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="lines"></param>
|
||||
/// <param name="file"></param>
|
||||
/// <param name="orgFile"></param>
|
||||
public void SetData(List<string> lines, string file, string orgFile = null) {
|
||||
minWidth = Math.Max(leftText.ActualWidth, rightText.ActualWidth) - 16;
|
||||
|
||||
fileName.Text = file;
|
||||
if (!string.IsNullOrEmpty(orgFile)) {
|
||||
orgFileNamePanel.Visibility = Visibility.Visible;
|
||||
orgFileName.Text = orgFile;
|
||||
} else {
|
||||
orgFileNamePanel.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
leftText.Document.Blocks.Clear();
|
||||
rightText.Document.Blocks.Clear();
|
||||
|
||||
leftLineNumber.Text = "";
|
||||
rightLineNumber.Text = "";
|
||||
|
||||
Regex regex = new Regex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@", RegexOptions.None);
|
||||
bool started = false;
|
||||
|
||||
List<Paragraph> leftData = new List<Paragraph>();
|
||||
List<Paragraph> rightData = new List<Paragraph>();
|
||||
List<string> leftNumbers = new List<string>();
|
||||
List<string> rightNumbers = new List<string>();
|
||||
|
||||
int leftLine = 0;
|
||||
int rightLine = 0;
|
||||
bool bLastLeft = true;
|
||||
|
||||
foreach (var line in lines) {
|
||||
if (!started) {
|
||||
var match = regex.Match(line);
|
||||
if (!match.Success) continue;
|
||||
|
||||
MakeParagraph(leftData, line, LineMode.Indicator);
|
||||
MakeParagraph(rightData, line, LineMode.Indicator);
|
||||
leftNumbers.Add("");
|
||||
rightNumbers.Add("");
|
||||
|
||||
leftLine = int.Parse(match.Groups[1].Value);
|
||||
rightLine = int.Parse(match.Groups[2].Value);
|
||||
started = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line[0] == '-') {
|
||||
MakeParagraph(leftData, line.Substring(1), LineMode.Deleted);
|
||||
leftNumbers.Add(leftLine.ToString());
|
||||
leftLine++;
|
||||
bLastLeft = true;
|
||||
} else if (line[0] == '+') {
|
||||
MakeParagraph(rightData, line.Substring(1), LineMode.Added);
|
||||
rightNumbers.Add(rightLine.ToString());
|
||||
rightLine++;
|
||||
bLastLeft = false;
|
||||
} else if (line[0] == '\\') {
|
||||
if (bLastLeft) {
|
||||
MakeParagraph(leftData, line.Substring(1), LineMode.Indicator);
|
||||
leftNumbers.Add("");
|
||||
} else {
|
||||
MakeParagraph(rightData, line.Substring(1), LineMode.Indicator);
|
||||
rightNumbers.Add("");
|
||||
}
|
||||
} else {
|
||||
FitBothSide(leftData, leftNumbers, rightData, rightNumbers);
|
||||
bLastLeft = true;
|
||||
|
||||
var match = regex.Match(line);
|
||||
if (match.Success) {
|
||||
MakeParagraph(leftData, line, LineMode.Indicator);
|
||||
MakeParagraph(rightData, line, LineMode.Indicator);
|
||||
leftNumbers.Add("");
|
||||
rightNumbers.Add("");
|
||||
|
||||
leftLine = int.Parse(match.Groups[1].Value);
|
||||
rightLine = int.Parse(match.Groups[2].Value);
|
||||
} else {
|
||||
var data = line.Substring(1);
|
||||
MakeParagraph(leftData, data, LineMode.Normal);
|
||||
MakeParagraph(rightData, data, LineMode.Normal);
|
||||
leftNumbers.Add(leftLine.ToString());
|
||||
rightNumbers.Add(rightLine.ToString());
|
||||
leftLine++;
|
||||
rightLine++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FitBothSide(leftData, leftNumbers, rightData, rightNumbers);
|
||||
|
||||
if (leftData.Count == 0) {
|
||||
MakeParagraph(leftData, "NOT SUPPORTED OR NO DATA", LineMode.Indicator);
|
||||
MakeParagraph(rightData, "NOT SUPPORTED OR NO DATA", LineMode.Indicator);
|
||||
leftNumbers.Add("");
|
||||
rightNumbers.Add("");
|
||||
}
|
||||
|
||||
leftLineNumber.Text = string.Join("\n", leftNumbers);
|
||||
rightLineNumber.Text = string.Join("\n", rightNumbers);
|
||||
leftText.Document.PageWidth = minWidth + 16;
|
||||
rightText.Document.PageWidth = minWidth + 16;
|
||||
leftText.Document.Blocks.AddRange(leftData);
|
||||
rightText.Document.Blocks.AddRange(rightData);
|
||||
leftText.ScrollToHome();
|
||||
|
||||
mask.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset data.
|
||||
/// </summary>
|
||||
public void Reset() {
|
||||
mask.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make paragraph.
|
||||
/// </summary>
|
||||
/// <param name="collection"></param>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="mode"></param>
|
||||
private void MakeParagraph(List<Paragraph> collection, string content, LineMode mode) {
|
||||
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;
|
||||
|
||||
switch (mode) {
|
||||
case LineMode.Normal:
|
||||
break;
|
||||
case LineMode.Indicator:
|
||||
p.Foreground = Brushes.Gray;
|
||||
p.FontStyle = FontStyles.Italic;
|
||||
break;
|
||||
case LineMode.Empty:
|
||||
p.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0));
|
||||
break;
|
||||
case LineMode.Added:
|
||||
p.Background = new SolidColorBrush(Color.FromArgb(60, 0, 255, 0));
|
||||
break;
|
||||
case 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;
|
||||
collection.Add(p);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fit both side with empty lines.
|
||||
/// </summary>
|
||||
/// <param name="left"></param>
|
||||
/// <param name="leftNumbers"></param>
|
||||
/// <param name="right"></param>
|
||||
/// <param name="rightNumbers"></param>
|
||||
private void FitBothSide(List<Paragraph> left, List<string> leftNumbers, List<Paragraph> right, List<string> rightNumbers) {
|
||||
int leftCount = left.Count;
|
||||
int rightCount = right.Count;
|
||||
int diff = 0;
|
||||
List<Paragraph> fitContent = null;
|
||||
List<string> fitNumber = null;
|
||||
|
||||
if (leftCount > rightCount) {
|
||||
diff = leftCount - rightCount;
|
||||
fitContent = right;
|
||||
fitNumber = rightNumbers;
|
||||
} else if (rightCount > leftCount) {
|
||||
diff = rightCount - leftCount;
|
||||
fitContent = left;
|
||||
fitNumber = leftNumbers;
|
||||
}
|
||||
|
||||
for (int i = 0; i < diff; i++) {
|
||||
MakeParagraph(fitContent, "", LineMode.Empty);
|
||||
fitNumber.Add("");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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(4, -e.VerticalOffset, 4, 0);
|
||||
rightLineNumber.Margin = new Thickness(4, -e.VerticalOffset, 4, 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;
|
||||
}
|
||||
|
||||
private void LeftSizeChanged(object sender, SizeChangedEventArgs e) {
|
||||
if (leftText.Document.PageWidth < leftText.ActualWidth) {
|
||||
leftText.Document.PageWidth = leftText.ActualWidth;
|
||||
}
|
||||
}
|
||||
|
||||
private void RightSizeChanged(object sender, SizeChangedEventArgs e) {
|
||||
if (rightText.Document.PageWidth < rightText.ActualWidth) {
|
||||
rightText.Document.PageWidth = rightText.ActualWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
55
UI/Discard.xaml
Normal file
55
UI/Discard.xaml
Normal file
|
@ -0,0 +1,55 @@
|
|||
<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 Grid.Row="0" Grid.RowSpan="6" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
|
||||
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="0"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
67
UI/Discard.xaml.cs
Normal file
67
UI/Discard.xaml.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
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) {
|
||||
PopupManager.Show(new Discard(opened, targets));
|
||||
}
|
||||
|
||||
private async void Sure(object sender, RoutedEventArgs e) {
|
||||
PopupManager.Lock();
|
||||
|
||||
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
|
||||
anim.RepeatBehavior = RepeatBehavior.Forever;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
|
||||
status.Visibility = Visibility.Visible;
|
||||
|
||||
await Task.Run(() => repo.Discard(changes));
|
||||
|
||||
status.Visibility = Visibility.Collapsed;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
||||
PopupManager.Close(true);
|
||||
}
|
||||
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
PopupManager.Close();
|
||||
}
|
||||
}
|
||||
}
|
78
UI/Fetch.xaml
Normal file
78
UI/Fetch.xaml
Normal file
|
@ -0,0 +1,78 @@
|
|||
<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 Grid.Row="0" Grid.RowSpan="7" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="0"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
|
||||
<Label x:Name="statusMsg" Margin="0,8,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
85
UI/Fetch.xaml.cs
Normal file
85
UI/Fetch.xaml.cs
Normal file
|
@ -0,0 +1,85 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
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) {
|
||||
PopupManager.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;
|
||||
|
||||
PopupManager.Lock();
|
||||
|
||||
status.Visibility = Visibility.Visible;
|
||||
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
|
||||
anim.RepeatBehavior = RepeatBehavior.Forever;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
|
||||
|
||||
if (chkFetchAll.IsChecked == true) {
|
||||
await Task.Run(() => repo.Fetch(null, prune, msg => Dispatcher.Invoke(() => statusMsg.Content = msg)));
|
||||
} else {
|
||||
var remote = combRemotes.SelectedItem as Git.Remote;
|
||||
await Task.Run(() => repo.Fetch(remote, prune, msg => Dispatcher.Invoke(() => statusMsg.Content = msg)));
|
||||
}
|
||||
|
||||
status.Visibility = Visibility.Collapsed;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
||||
PopupManager.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
PopupManager.Close();
|
||||
}
|
||||
}
|
||||
}
|
169
UI/FileHistories.xaml
Normal file
169
UI/FileHistories.xaml
Normal file
|
@ -0,0 +1,169 @@
|
|||
<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"
|
||||
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>
|
||||
|
122
UI/FileHistories.xaml.cs
Normal file
122
UI/FileHistories.xaml.cs
Normal file
|
@ -0,0 +1,122 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
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 async 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";
|
||||
|
||||
List<string> data = new List<string>();
|
||||
await Task.Run(() => {
|
||||
data = repo.Diff(start, commit.SHA, file);
|
||||
});
|
||||
diff.SetData(data, $"{file} @ {commit.ShortSHA}");
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
50
UI/GitFlowFinishBranch.xaml
Normal file
50
UI/GitFlowFinishBranch.xaml
Normal file
|
@ -0,0 +1,50 @@
|
|||
<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 Grid.Row="0" Grid.RowSpan="5" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
|
||||
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="0"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
87
UI/GitFlowFinishBranch.xaml.cs
Normal file
87
UI/GitFlowFinishBranch.xaml.cs
Normal file
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
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:
|
||||
PopupManager.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) {
|
||||
PopupManager.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) {
|
||||
PopupManager.Lock();
|
||||
|
||||
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
|
||||
anim.RepeatBehavior = RepeatBehavior.Forever;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
|
||||
status.Visibility = Visibility.Visible;
|
||||
|
||||
await Task.Run(() => repo.FinishGitFlowBranch(branch));
|
||||
|
||||
status.Visibility = Visibility.Collapsed;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
||||
PopupManager.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel finish
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
PopupManager.Close();
|
||||
}
|
||||
}
|
||||
}
|
68
UI/GitFlowSetup.xaml
Normal file
68
UI/GitFlowSetup.xaml
Normal file
|
@ -0,0 +1,68 @@
|
|||
<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 Grid.Row="0" Grid.RowSpan="11" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
|
||||
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="0"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
134
UI/GitFlowSetup.xaml.cs
Normal file
134
UI/GitFlowSetup.xaml.cs
Normal file
|
@ -0,0 +1,134 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
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) {
|
||||
PopupManager.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) {
|
||||
PopupManager.Lock();
|
||||
|
||||
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
|
||||
anim.RepeatBehavior = RepeatBehavior.Forever;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
|
||||
status.Visibility = Visibility.Visible;
|
||||
|
||||
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));
|
||||
|
||||
status.Visibility = Visibility.Collapsed;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
||||
PopupManager.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
PopupManager.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;
|
||||
}
|
||||
}
|
||||
}
|
57
UI/GitFlowStartBranch.xaml
Normal file
57
UI/GitFlowStartBranch.xaml
Normal file
|
@ -0,0 +1,57 @@
|
|||
<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 Grid.Row="0" Grid.RowSpan="5" Grid.ColumnSpan="2" x:Name="status" Visibility="Collapsed" Background="{StaticResource Brush.BG1}" Opacity=".9">
|
||||
<Path x:Name="statusIcon" Width="48" Height="48" Style="{StaticResource Style.Icon}" Data="{StaticResource Icon.Loading}" RenderTransformOrigin=".5,.5">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="0"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
103
UI/GitFlowStartBranch.xaml.cs
Normal file
103
UI/GitFlowStartBranch.xaml.cs
Normal file
|
@ -0,0 +1,103 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
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:
|
||||
PopupManager.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) {
|
||||
PopupManager.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;
|
||||
|
||||
PopupManager.Lock();
|
||||
|
||||
DoubleAnimation anim = new DoubleAnimation(0, 360, TimeSpan.FromSeconds(1));
|
||||
anim.RepeatBehavior = RepeatBehavior.Forever;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, anim);
|
||||
status.Visibility = Visibility.Visible;
|
||||
|
||||
await Task.Run(() => repo.StartGitFlowBranch(type, SubName));
|
||||
|
||||
status.Visibility = Visibility.Collapsed;
|
||||
statusIcon.RenderTransform.BeginAnimation(RotateTransform.AngleProperty, null);
|
||||
PopupManager.Close(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void Cancel(object sender, RoutedEventArgs e) {
|
||||
PopupManager.Close();
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue