diff --git a/.gitignore b/.gitignore
index 0c66b11e..e686a534 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,3 +37,5 @@ build/*.deb
build/*.rpm
build/*.AppImage
SourceGit.app/
+build.command
+src/Properties/launchSettings.json
diff --git a/TRANSLATION.md b/TRANSLATION.md
index 479f5188..1a9d00d1 100644
--- a/TRANSLATION.md
+++ b/TRANSLATION.md
@@ -6,19 +6,41 @@ This document shows the translation status of each locale file in the repository
### 
-### 
+### 
Missing keys in de_DE.axaml
- Text.GitFlow.FinishWithPush
- Text.GitFlow.FinishWithSquash
+- Text.Hotkeys.Global.SwitchWorkspace
+- Text.Hotkeys.Global.SwitchTab
+- Text.Hotkeys.TextEditor.OpenExternalMergeTool
+- Text.Launcher.Workspaces
+- Text.Launcher.Pages
+- Text.Repository.ShowSubmodulesAsTree
+- Text.Submodule.Status
+- Text.Submodule.Status.Modified
+- Text.Submodule.Status.NotInited
+- Text.Submodule.Status.RevisionChanged
+- Text.Submodule.Status.Unmerged
+- Text.Submodule.URL
-### 
+### 
-### 
+
+Missing keys in es_ES.axaml
+
+- Text.Hotkeys.Global.SwitchWorkspace
+- Text.Hotkeys.Global.SwitchTab
+- Text.Launcher.Workspaces
+- Text.Launcher.Pages
+
+
+
+### 
Missing keys in fr_FR.axaml
@@ -42,13 +64,25 @@ This document shows the translation status of each locale file in the repository
- Text.ConfirmEmptyCommit.WithLocalChanges
- Text.GitFlow.FinishWithPush
- Text.GitFlow.FinishWithSquash
+- Text.Hotkeys.Global.SwitchWorkspace
+- Text.Hotkeys.Global.SwitchTab
+- Text.Hotkeys.TextEditor.OpenExternalMergeTool
+- Text.Launcher.Workspaces
+- Text.Launcher.Pages
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
- Text.Repository.BranchSort
- Text.Repository.BranchSort.ByCommitterDate
- Text.Repository.BranchSort.ByName
- Text.Repository.Search.ByContent
+- Text.Repository.ShowSubmodulesAsTree
- Text.Repository.ViewLogs
- Text.Repository.Visit
+- Text.Submodule.Status
+- Text.Submodule.Status.Modified
+- Text.Submodule.Status.NotInited
+- Text.Submodule.Status.RevisionChanged
+- Text.Submodule.Status.Unmerged
+- Text.Submodule.URL
- Text.ViewLogs
- Text.ViewLogs.Clear
- Text.ViewLogs.CopyLog
@@ -61,52 +95,19 @@ This document shows the translation status of each locale file in the repository
-### 
+### 
Missing keys in it_IT.axaml
-- Text.Bisect
-- Text.Bisect.Abort
-- Text.Bisect.Bad
-- Text.Bisect.Detecting
-- Text.Bisect.Good
-- Text.Bisect.Skip
-- Text.Bisect.WaitingForRange
-- Text.Checkout.RecurseSubmodules
-- Text.CommitCM.CopyAuthor
-- Text.CommitCM.CopyCommitter
-- Text.CommitCM.CopySubject
-- Text.CommitMessageTextBox.SubjectCount
-- Text.Configure.Git.PreferredMergeMode
-- Text.ConfirmEmptyCommit.Continue
-- Text.ConfirmEmptyCommit.NoLocalChanges
-- Text.ConfirmEmptyCommit.StageAllThenCommit
-- Text.ConfirmEmptyCommit.WithLocalChanges
-- Text.CopyFullPath
-- Text.GitFlow.FinishWithPush
-- Text.GitFlow.FinishWithSquash
-- Text.Preferences.General.ShowTagsInGraph
-- Text.Preferences.Git.IgnoreCRAtEOLInDiff
-- Text.Repository.BranchSort
-- Text.Repository.BranchSort.ByCommitterDate
-- Text.Repository.BranchSort.ByName
-- Text.Repository.Search.ByContent
-- Text.Repository.ViewLogs
-- Text.Repository.Visit
-- Text.ViewLogs
-- Text.ViewLogs.Clear
-- Text.ViewLogs.CopyLog
-- Text.ViewLogs.Delete
-- Text.WorkingCopy.ConfirmCommitWithFilter
-- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
-- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
-- Text.WorkingCopy.Conflicts.UseMine
-- Text.WorkingCopy.Conflicts.UseTheirs
+- Text.Hotkeys.Global.SwitchWorkspace
+- Text.Hotkeys.Global.SwitchTab
+- Text.Launcher.Workspaces
+- Text.Launcher.Pages
-### 
+### 
Missing keys in ja_JP.axaml
@@ -130,15 +131,27 @@ This document shows the translation status of each locale file in the repository
- Text.ConfirmEmptyCommit.WithLocalChanges
- Text.GitFlow.FinishWithPush
- Text.GitFlow.FinishWithSquash
+- Text.Hotkeys.Global.SwitchWorkspace
+- Text.Hotkeys.Global.SwitchTab
+- Text.Hotkeys.TextEditor.OpenExternalMergeTool
+- Text.Launcher.Workspaces
+- Text.Launcher.Pages
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
- Text.Repository.BranchSort
- Text.Repository.BranchSort.ByCommitterDate
- Text.Repository.BranchSort.ByName
- Text.Repository.FilterCommits
- Text.Repository.Search.ByContent
+- Text.Repository.ShowSubmodulesAsTree
- Text.Repository.Tags.OrderByNameDes
- Text.Repository.ViewLogs
- Text.Repository.Visit
+- Text.Submodule.Status
+- Text.Submodule.Status.Modified
+- Text.Submodule.Status.NotInited
+- Text.Submodule.Status.RevisionChanged
+- Text.Submodule.Status.Unmerged
+- Text.Submodule.URL
- Text.ViewLogs
- Text.ViewLogs.Clear
- Text.ViewLogs.CopyLog
@@ -151,7 +164,7 @@ This document shows the translation status of each locale file in the repository
-### 
+### 
Missing keys in pt_BR.axaml
@@ -204,10 +217,15 @@ This document shows the translation status of each locale file in the repository
- Text.GitFlow.FinishWithPush
- Text.GitFlow.FinishWithSquash
- Text.Hotkeys.Global.Clone
+- Text.Hotkeys.Global.SwitchWorkspace
+- Text.Hotkeys.Global.SwitchTab
+- Text.Hotkeys.TextEditor.OpenExternalMergeTool
- Text.InProgress.CherryPick.Head
- Text.InProgress.Merge.Operating
- Text.InProgress.Rebase.StoppedAt
- Text.InProgress.Revert.Head
+- Text.Launcher.Workspaces
+- Text.Launcher.Pages
- Text.Merge.Source
- Text.MergeMultiple
- Text.MergeMultiple.CommitChanges
@@ -231,6 +249,7 @@ This document shows the translation status of each locale file in the repository
- Text.Repository.Notifications.Clear
- Text.Repository.OnlyHighlightCurrentBranchInHistories
- Text.Repository.Search.ByContent
+- Text.Repository.ShowSubmodulesAsTree
- Text.Repository.Skip
- Text.Repository.Tags.OrderByCreatorDate
- Text.Repository.Tags.OrderByNameAsc
@@ -247,6 +266,12 @@ This document shows the translation status of each locale file in the repository
- Text.Stash.AutoRestore
- Text.Stash.AutoRestore.Tip
- Text.StashCM.SaveAsPatch
+- Text.Submodule.Status
+- Text.Submodule.Status.Modified
+- Text.Submodule.Status.NotInited
+- Text.Submodule.Status.RevisionChanged
+- Text.Submodule.Status.Unmerged
+- Text.Submodule.URL
- Text.ViewLogs
- Text.ViewLogs.Clear
- Text.ViewLogs.CopyLog
@@ -261,9 +286,17 @@ This document shows the translation status of each locale file in the repository
-### 
+### 
-### 
+
+Missing keys in ru_RU.axaml
+
+- Text.Hotkeys.Global.SwitchTab
+- Text.Launcher.Pages
+
+
+
+### 
Missing keys in ta_IN.axaml
@@ -287,13 +320,25 @@ This document shows the translation status of each locale file in the repository
- Text.ConfirmEmptyCommit.WithLocalChanges
- Text.GitFlow.FinishWithPush
- Text.GitFlow.FinishWithSquash
+- Text.Hotkeys.Global.SwitchWorkspace
+- Text.Hotkeys.Global.SwitchTab
+- Text.Hotkeys.TextEditor.OpenExternalMergeTool
+- Text.Launcher.Workspaces
+- Text.Launcher.Pages
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
- Text.Repository.BranchSort
- Text.Repository.BranchSort.ByCommitterDate
- Text.Repository.BranchSort.ByName
- Text.Repository.Search.ByContent
+- Text.Repository.ShowSubmodulesAsTree
- Text.Repository.ViewLogs
- Text.Repository.Visit
+- Text.Submodule.Status
+- Text.Submodule.Status.Modified
+- Text.Submodule.Status.NotInited
+- Text.Submodule.Status.RevisionChanged
+- Text.Submodule.Status.Unmerged
+- Text.Submodule.URL
- Text.UpdateSubmodules.Target
- Text.ViewLogs
- Text.ViewLogs.Clear
@@ -306,7 +351,7 @@ This document shows the translation status of each locale file in the repository
-### 
+### 
Missing keys in uk_UA.axaml
@@ -326,13 +371,25 @@ This document shows the translation status of each locale file in the repository
- Text.ConfigureWorkspace.Name
- Text.GitFlow.FinishWithPush
- Text.GitFlow.FinishWithSquash
+- Text.Hotkeys.Global.SwitchWorkspace
+- Text.Hotkeys.Global.SwitchTab
+- Text.Hotkeys.TextEditor.OpenExternalMergeTool
+- Text.Launcher.Workspaces
+- Text.Launcher.Pages
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
- Text.Repository.BranchSort
- Text.Repository.BranchSort.ByCommitterDate
- Text.Repository.BranchSort.ByName
- Text.Repository.Search.ByContent
+- Text.Repository.ShowSubmodulesAsTree
- Text.Repository.ViewLogs
- Text.Repository.Visit
+- Text.Submodule.Status
+- Text.Submodule.Status.Modified
+- Text.Submodule.Status.NotInited
+- Text.Submodule.Status.RevisionChanged
+- Text.Submodule.Status.Unmerged
+- Text.Submodule.URL
- Text.ViewLogs
- Text.ViewLogs.Clear
- Text.ViewLogs.CopyLog
diff --git a/VERSION b/VERSION
index 52dc0d52..60bea9b2 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2025.17
\ No newline at end of file
+2025.18
\ No newline at end of file
diff --git a/src/App.axaml.cs b/src/App.axaml.cs
index 6e45164d..45ab0b8c 100644
--- a/src/App.axaml.cs
+++ b/src/App.axaml.cs
@@ -107,13 +107,24 @@ namespace SourceGit
#region Utility Functions
public static void ShowWindow(object data, bool showAsDialog)
{
+ var impl = (Views.ChromelessWindow target, bool isDialog) =>
+ {
+ if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
+ {
+ if (isDialog)
+ target.ShowDialog(owner);
+ else
+ target.Show(owner);
+ }
+ else
+ {
+ target.Show();
+ }
+ };
+
if (data is Views.ChromelessWindow window)
{
- if (showAsDialog && Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
- window.ShowDialog(owner);
- else
- window.Show();
-
+ impl(window, showAsDialog);
return;
}
@@ -130,10 +141,7 @@ namespace SourceGit
if (window != null)
{
window.DataContext = data;
- if (showAsDialog && Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
- window.ShowDialog(owner);
- else
- window.Show();
+ impl(window, showAsDialog);
}
}
diff --git a/src/Commands/Discard.cs b/src/Commands/Discard.cs
index fcbe8591..e61fe1e6 100644
--- a/src/Commands/Discard.cs
+++ b/src/Commands/Discard.cs
@@ -35,7 +35,7 @@ namespace SourceGit.Commands
App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}");
});
}
-
+
new Restore(repo) { Log = log }.Exec();
if (includeIgnored)
new Clean(repo) { Log = log }.Exec();
diff --git a/src/Commands/QueryBranches.cs b/src/Commands/QueryBranches.cs
index 19514954..39794090 100644
--- a/src/Commands/QueryBranches.cs
+++ b/src/Commands/QueryBranches.cs
@@ -17,8 +17,10 @@ namespace SourceGit.Commands
Args = "branch -l --all -v --format=\"%(refname)%00%(committerdate:unix)%00%(objectname)%00%(HEAD)%00%(upstream)%00%(upstream:trackshort)\"";
}
- public List Result()
+ public List Result(out int localBranchesCount)
{
+ localBranchesCount = 0;
+
var branches = new List();
var rs = ReadToEnd();
if (!rs.IsSuccess)
@@ -34,6 +36,8 @@ namespace SourceGit.Commands
branches.Add(b);
if (!b.IsLocal)
remoteBranches.Add(b.FullName);
+ else
+ localBranchesCount++;
}
}
diff --git a/src/Commands/QueryLocalChanges.cs b/src/Commands/QueryLocalChanges.cs
index 4e626a79..404f5be6 100644
--- a/src/Commands/QueryLocalChanges.cs
+++ b/src/Commands/QueryLocalChanges.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
+using Avalonia.Threading;
namespace SourceGit.Commands
{
@@ -22,7 +23,10 @@ namespace SourceGit.Commands
var outs = new List();
var rs = ReadToEnd();
if (!rs.IsSuccess)
+ {
+ Dispatcher.UIThread.Post(() => App.RaiseException(Context, rs.StdErr));
return outs;
+ }
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
diff --git a/src/Commands/QuerySubmodules.cs b/src/Commands/QuerySubmodules.cs
index 6016b0be..86147f97 100644
--- a/src/Commands/QuerySubmodules.cs
+++ b/src/Commands/QuerySubmodules.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
@@ -6,12 +7,12 @@ namespace SourceGit.Commands
{
public partial class QuerySubmodules : Command
{
- [GeneratedRegex(@"^[\-\+ ][0-9a-f]+\s(.*)\s\(.*\)$")]
- private static partial Regex REG_FORMAT1();
- [GeneratedRegex(@"^[\-\+ ][0-9a-f]+\s(.*)$")]
- private static partial Regex REG_FORMAT2();
- [GeneratedRegex(@"^\s?[\w\?]{1,4}\s+(.+)$")]
+ [GeneratedRegex(@"^([U\-\+ ])([0-9a-f]+)\s(.*?)(\s\(.*\))?$")]
private static partial Regex REG_FORMAT_STATUS();
+ [GeneratedRegex(@"^\s?[\w\?]{1,4}\s+(.+)$")]
+ private static partial Regex REG_FORMAT_DIRTY();
+ [GeneratedRegex(@"^submodule\.(\S*)\.(\w+)=(.*)$")]
+ private static partial Regex REG_FORMAT_MODULE_INFO();
public QuerySubmodules(string repo)
{
@@ -25,52 +26,117 @@ namespace SourceGit.Commands
var submodules = new List();
var rs = ReadToEnd();
- var builder = new StringBuilder();
- var lines = rs.StdOut.Split(['\r', '\n'], System.StringSplitOptions.RemoveEmptyEntries);
+ var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
+ var map = new Dictionary();
+ var needCheckLocalChanges = false;
foreach (var line in lines)
{
- var match = REG_FORMAT1().Match(line);
+ var match = REG_FORMAT_STATUS().Match(line);
if (match.Success)
{
- var path = match.Groups[1].Value;
- builder.Append($"\"{path}\" ");
- submodules.Add(new Models.Submodule() { Path = path });
- continue;
- }
+ var stat = match.Groups[1].Value;
+ var sha = match.Groups[2].Value;
+ var path = match.Groups[3].Value;
- match = REG_FORMAT2().Match(line);
- if (match.Success)
- {
- var path = match.Groups[1].Value;
- builder.Append($"\"{path}\" ");
- submodules.Add(new Models.Submodule() { Path = path });
+ var module = new Models.Submodule() { Path = path, SHA = sha };
+ switch (stat[0])
+ {
+ case '-':
+ module.Status = Models.SubmoduleStatus.NotInited;
+ break;
+ case '+':
+ module.Status = Models.SubmoduleStatus.RevisionChanged;
+ break;
+ case 'U':
+ module.Status = Models.SubmoduleStatus.Unmerged;
+ break;
+ default:
+ module.Status = Models.SubmoduleStatus.Normal;
+ needCheckLocalChanges = true;
+ break;
+ }
+
+ map.Add(path, module);
+ submodules.Add(module);
}
}
if (submodules.Count > 0)
{
+ Args = "config --file .gitmodules --list";
+ rs = ReadToEnd();
+ if (rs.IsSuccess)
+ {
+ var modules = new Dictionary();
+ lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
+ foreach (var line in lines)
+ {
+ var match = REG_FORMAT_MODULE_INFO().Match(line);
+ if (match.Success)
+ {
+ var name = match.Groups[1].Value;
+ var key = match.Groups[2].Value;
+ var val = match.Groups[3].Value;
+
+ if (!modules.TryGetValue(name, out var m))
+ {
+ m = new ModuleInfo();
+ modules.Add(name, m);
+ }
+
+ if (key.Equals("path", StringComparison.Ordinal))
+ m.Path = val;
+ else if (key.Equals("url", StringComparison.Ordinal))
+ m.URL = val;
+ }
+ }
+
+ foreach (var kv in modules)
+ {
+ if (map.TryGetValue(kv.Value.Path, out var m))
+ m.URL = kv.Value.URL;
+ }
+ }
+ }
+
+ if (needCheckLocalChanges)
+ {
+ var builder = new StringBuilder();
+ foreach (var kv in map)
+ {
+ if (kv.Value.Status == Models.SubmoduleStatus.Normal)
+ {
+ builder.Append('"');
+ builder.Append(kv.Key);
+ builder.Append("\" ");
+ }
+ }
+
Args = $"--no-optional-locks status -uno --porcelain -- {builder}";
rs = ReadToEnd();
if (!rs.IsSuccess)
return submodules;
- var dirty = new HashSet();
- lines = rs.StdOut.Split(['\r', '\n'], System.StringSplitOptions.RemoveEmptyEntries);
+ lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
- var match = REG_FORMAT_STATUS().Match(line);
+ var match = REG_FORMAT_DIRTY().Match(line);
if (match.Success)
{
var path = match.Groups[1].Value;
- dirty.Add(path);
+ if (map.TryGetValue(path, out var m))
+ m.Status = Models.SubmoduleStatus.Modified;
}
}
-
- foreach (var submodule in submodules)
- submodule.IsDirty = dirty.Contains(submodule.Path);
}
return submodules;
}
+
+ private class ModuleInfo
+ {
+ public string Path { get; set; } = string.Empty;
+ public string URL { get; set; } = string.Empty;
+ }
}
}
diff --git a/src/Commands/QueryTags.cs b/src/Commands/QueryTags.cs
index 73f63d8b..4b706439 100644
--- a/src/Commands/QueryTags.cs
+++ b/src/Commands/QueryTags.cs
@@ -11,7 +11,7 @@ namespace SourceGit.Commands
Context = repo;
WorkingDirectory = repo;
- Args = $"tag -l --format=\"{_boundary}%(refname)%00%(objectname)%00%(*objectname)%00%(creatordate:unix)%00%(contents:subject)%0a%0a%(contents:body)\"";
+ Args = $"tag -l --format=\"{_boundary}%(refname)%00%(objecttype)%00%(objectname)%00%(*objectname)%00%(creatordate:unix)%00%(contents:subject)%0a%0a%(contents:body)\"";
}
public List Result()
@@ -25,16 +25,21 @@ namespace SourceGit.Commands
foreach (var record in records)
{
var subs = record.Split('\0', StringSplitOptions.None);
- if (subs.Length != 5)
+ if (subs.Length != 6)
continue;
- var message = subs[4].Trim();
+ var name = subs[0].Substring(10);
+ var message = subs[5].Trim();
+ if (!string.IsNullOrEmpty(message) && message.Equals(name, StringComparison.Ordinal))
+ message = null;
+
tags.Add(new Models.Tag()
{
- Name = subs[0].Substring(10),
- SHA = string.IsNullOrEmpty(subs[2]) ? subs[1] : subs[2],
- CreatorDate = ulong.Parse(subs[3]),
- Message = string.IsNullOrEmpty(message) ? null : message,
+ Name = name,
+ IsAnnotated = subs[1].Equals("tag", StringComparison.Ordinal),
+ SHA = string.IsNullOrEmpty(subs[3]) ? subs[2] : subs[3],
+ CreatorDate = ulong.Parse(subs[4]),
+ Message = message,
});
}
diff --git a/src/Commands/Tag.cs b/src/Commands/Tag.cs
index 6fc8dc34..10a7ba87 100644
--- a/src/Commands/Tag.cs
+++ b/src/Commands/Tag.cs
@@ -9,7 +9,7 @@ namespace SourceGit.Commands
var cmd = new Command();
cmd.WorkingDirectory = repo;
cmd.Context = repo;
- cmd.Args = $"tag {name} {basedOn}";
+ cmd.Args = $"tag --no-sign {name} {basedOn}";
cmd.Log = log;
return cmd.Exec();
}
diff --git a/src/Converters/ListConverters.cs b/src/Converters/ListConverters.cs
index 81cac8b7..e0c5967e 100644
--- a/src/Converters/ListConverters.cs
+++ b/src/Converters/ListConverters.cs
@@ -8,7 +8,7 @@ namespace SourceGit.Converters
public static class ListConverters
{
public static readonly FuncValueConverter ToCount =
- new FuncValueConverter(v => v == null ? " (0)" : $" ({v.Count})");
+ new FuncValueConverter(v => v == null ? "(0)" : $"({v.Count})");
public static readonly FuncValueConverter IsNullOrEmpty =
new FuncValueConverter(v => v == null || v.Count == 0);
diff --git a/src/Models/Submodule.cs b/src/Models/Submodule.cs
index ce00ac02..ca73a8de 100644
--- a/src/Models/Submodule.cs
+++ b/src/Models/Submodule.cs
@@ -1,8 +1,20 @@
namespace SourceGit.Models
{
+ public enum SubmoduleStatus
+ {
+ Normal = 0,
+ NotInited,
+ RevisionChanged,
+ Unmerged,
+ Modified,
+ }
+
public class Submodule
{
- public string Path { get; set; } = "";
- public bool IsDirty { get; set; } = false;
+ public string Path { get; set; } = string.Empty;
+ public string SHA { get; set; } = string.Empty;
+ public string URL { get; set; } = string.Empty;
+ public SubmoduleStatus Status { get; set; } = SubmoduleStatus.Normal;
+ public bool IsDirty => Status > SubmoduleStatus.NotInited;
}
}
diff --git a/src/Models/Tag.cs b/src/Models/Tag.cs
index 51681d93..20678530 100644
--- a/src/Models/Tag.cs
+++ b/src/Models/Tag.cs
@@ -12,6 +12,7 @@ namespace SourceGit.Models
public class Tag : ObservableObject
{
public string Name { get; set; } = string.Empty;
+ public bool IsAnnotated { get; set; } = false;
public string SHA { get; set; } = string.Empty;
public ulong CreatorDate { get; set; } = 0;
public string Message { get; set; } = string.Empty;
diff --git a/src/Native/Linux.cs b/src/Native/Linux.cs
index bfb98500..2bdcf561 100644
--- a/src/Native/Linux.cs
+++ b/src/Native/Linux.cs
@@ -5,6 +5,8 @@ using System.IO;
using System.Runtime.Versioning;
using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Platform;
namespace SourceGit.Native
{
@@ -16,6 +18,21 @@ namespace SourceGit.Native
builder.With(new X11PlatformOptions() { EnableIme = true });
}
+ public void SetupWindow(Window window)
+ {
+ if (OS.UseSystemWindowFrame)
+ {
+ window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.Default;
+ window.ExtendClientAreaToDecorationsHint = false;
+ }
+ else
+ {
+ window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome;
+ window.ExtendClientAreaToDecorationsHint = true;
+ window.Classes.Add("custom_window_frame");
+ }
+ }
+
public string FindGitExecutable()
{
return FindExecutable("git");
diff --git a/src/Native/MacOS.cs b/src/Native/MacOS.cs
index 0966233f..b76d239a 100644
--- a/src/Native/MacOS.cs
+++ b/src/Native/MacOS.cs
@@ -5,6 +5,8 @@ using System.IO;
using System.Runtime.Versioning;
using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Platform;
namespace SourceGit.Native
{
@@ -36,6 +38,12 @@ namespace SourceGit.Native
Environment.SetEnvironmentVariable("PATH", path);
}
+ public void SetupWindow(Window window)
+ {
+ window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome;
+ window.ExtendClientAreaToDecorationsHint = true;
+ }
+
public string FindGitExecutable()
{
var gitPathVariants = new List() {
diff --git a/src/Native/OS.cs b/src/Native/OS.cs
index 320b5208..4c891283 100644
--- a/src/Native/OS.cs
+++ b/src/Native/OS.cs
@@ -6,6 +6,7 @@ using System.Text;
using System.Text.RegularExpressions;
using Avalonia;
+using Avalonia.Controls;
namespace SourceGit.Native
{
@@ -14,6 +15,7 @@ namespace SourceGit.Native
public interface IBackend
{
void SetupApp(AppBuilder builder);
+ void SetupWindow(Window window);
string FindGitExecutable();
string FindTerminal(Models.ShellOrTerminal shell);
@@ -68,6 +70,12 @@ namespace SourceGit.Native
set;
} = [];
+ public static bool UseSystemWindowFrame
+ {
+ get => OperatingSystem.IsLinux() && _enableSystemWindowFrame;
+ set => _enableSystemWindowFrame = value;
+ }
+
static OS()
{
if (OperatingSystem.IsWindows())
@@ -121,6 +129,11 @@ namespace SourceGit.Native
ExternalTools = _backend.FindExternalTools();
}
+ public static void SetupForWindow(Window window)
+ {
+ _backend.SetupWindow(window);
+ }
+
public static string FindGitExecutable()
{
return _backend.FindGitExecutable();
@@ -225,5 +238,6 @@ namespace SourceGit.Native
private static IBackend _backend = null;
private static string _gitExecutable = string.Empty;
+ private static bool _enableSystemWindowFrame = false;
}
}
diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs
index eb354f10..83b11c47 100644
--- a/src/Native/Windows.cs
+++ b/src/Native/Windows.cs
@@ -8,6 +8,7 @@ using System.Text;
using Avalonia;
using Avalonia.Controls;
+using Avalonia.Platform;
using Avalonia.Threading;
namespace SourceGit.Native
@@ -15,16 +16,12 @@ namespace SourceGit.Native
[SupportedOSPlatform("windows")]
internal class Windows : OS.IBackend
{
- [StructLayout(LayoutKind.Sequential)]
- internal struct RTL_OSVERSIONINFOEX
+ internal struct RECT
{
- internal uint dwOSVersionInfoSize;
- internal uint dwMajorVersion;
- internal uint dwMinorVersion;
- internal uint dwBuildNumber;
- internal uint dwPlatformId;
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
- internal string szCSDVersion;
+ public int left;
+ public int top;
+ public int right;
+ public int bottom;
}
[StructLayout(LayoutKind.Sequential)]
@@ -36,9 +33,6 @@ namespace SourceGit.Native
public int cyBottomHeight;
}
- [DllImport("ntdll.dll")]
- private static extern int RtlGetVersion(ref RTL_OSVERSIONINFOEX lpVersionInformation);
-
[DllImport("dwmapi.dll")]
private static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins);
@@ -54,18 +48,79 @@ namespace SourceGit.Native
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = false)]
private static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, int cild, IntPtr apidl, int dwFlags);
+ [DllImport("user32.dll")]
+ private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
+
public void SetupApp(AppBuilder builder)
{
// Fix drop shadow issue on Windows 10
- RTL_OSVERSIONINFOEX v = new RTL_OSVERSIONINFOEX();
- v.dwOSVersionInfoSize = (uint)Marshal.SizeOf();
- if (RtlGetVersion(ref v) == 0 && (v.dwMajorVersion < 10 || v.dwBuildNumber < 22000))
+ if (!OperatingSystem.IsWindowsVersionAtLeast(10, 22000, 0))
{
Window.WindowStateProperty.Changed.AddClassHandler((w, _) => FixWindowFrameOnWin10(w));
Control.LoadedEvent.AddClassHandler((w, _) => FixWindowFrameOnWin10(w));
}
}
+ public void SetupWindow(Window window)
+ {
+ window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome;
+ window.ExtendClientAreaToDecorationsHint = true;
+ window.Classes.Add("fix_maximized_padding");
+
+ Win32Properties.AddWndProcHookCallback(window, (IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref bool handled) =>
+ {
+ // Custom WM_NCHITTEST
+ if (msg == 0x0084)
+ {
+ handled = true;
+
+ if (window.WindowState == WindowState.FullScreen || window.WindowState == WindowState.Maximized)
+ return 1; // HTCLIENT
+
+ var p = IntPtrToPixelPoint(lParam);
+ GetWindowRect(hWnd, out var rcWindow);
+
+ var borderThinkness = (int)(4 * window.RenderScaling);
+ int y = 1;
+ int x = 1;
+ if (p.X >= rcWindow.left && p.X < rcWindow.left + borderThinkness)
+ x = 0;
+ else if (p.X < rcWindow.right && p.X >= rcWindow.right - borderThinkness)
+ x = 2;
+
+ if (p.Y >= rcWindow.top && p.Y < rcWindow.top + borderThinkness)
+ y = 0;
+ else if (p.Y < rcWindow.bottom && p.Y >= rcWindow.bottom - borderThinkness)
+ y = 2;
+
+ var zone = y * 3 + x;
+ switch (zone)
+ {
+ case 0:
+ return 13; // HTTOPLEFT
+ case 1:
+ return 12; // HTTOP
+ case 2:
+ return 14; // HTTOPRIGHT
+ case 3:
+ return 10; // HTLEFT
+ case 4:
+ return 1; // HTCLIENT
+ case 5:
+ return 11; // HTRIGHT
+ case 6:
+ return 16; // HTBOTTOMLEFT
+ case 7:
+ return 15; // HTBOTTOM
+ default:
+ return 17; // HTBOTTOMRIGHT
+ }
+ }
+
+ return IntPtr.Zero;
+ });
+ }
+
public string FindGitExecutable()
{
var reg = Microsoft.Win32.RegistryKey.OpenBaseKey(
@@ -228,6 +283,12 @@ namespace SourceGit.Native
}, DispatcherPriority.Render);
}
+ private PixelPoint IntPtrToPixelPoint(IntPtr param)
+ {
+ var v = IntPtr.Size == 4 ? param.ToInt32() : (int)(param.ToInt64() & 0xFFFFFFFF);
+ return new PixelPoint((short)(v & 0xffff), (short)(v >> 16));
+ }
+
#region EXTERNAL_EDITOR_FINDER
private string FindVSCode()
{
diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml
index e2281a86..bb259272 100644
--- a/src/Resources/Locales/en_US.axaml
+++ b/src/Resources/Locales/en_US.axaml
@@ -386,6 +386,8 @@
Go to previous page
Create new page
Open Preferences dialog
+ Switch active workspace
+ Switch active page
REPOSITORY
Commit staged changes
Commit and push staged changes
@@ -406,6 +408,7 @@
Close search panel
Find next match
Find previous match
+ Open with external diff/merge tool
Open search panel
Discard
Stage
@@ -427,6 +430,8 @@
Open in Browser
ERROR
NOTICE
+ Workspaces
+ Pages
Merge Branch
Into:
Merge Option:
@@ -616,6 +621,7 @@
Message
SHA
Current Branch
+ Show Submodules as Tree
Show Tags as Tree
SKIP
Statistics
@@ -704,6 +710,12 @@
Relative Path:
Relative folder to store this module.
Delete Submodule
+ STATUS
+ modified
+ not initialized
+ revision changed
+ unmerged
+ URL
OK
Copy Tag Name
Copy Tag Message
diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml
index b11d0e98..e2b16329 100644
--- a/src/Resources/Locales/es_ES.axaml
+++ b/src/Resources/Locales/es_ES.axaml
@@ -410,6 +410,7 @@
Cerrar panel de búsqueda
Buscar siguiente coincidencia
Buscar coincidencia anterior
+ Abrir con herramienta diff/merge externa
Abrir panel de búsqueda
Descartar
Stage
@@ -620,6 +621,7 @@
Mensaje
SHA
Rama Actual
+ Mostrar Submódulos como Árbol
Mostrar Etiquetas como Árbol
OMITIR
Estadísticas
@@ -708,6 +710,12 @@
Ruta Relativa:
Carpeta relativa para almacenar este módulo.
Eliminar Submódulo
+ ESTADO
+ modificado
+ no inicializado
+ revisión cambiada
+ unmerged
+ URL
OK
Copiar Nombre de la Etiqueta
Copiar Mensaje de la Etiqueta
diff --git a/src/Resources/Locales/it_IT.axaml b/src/Resources/Locales/it_IT.axaml
index 545261e9..05fee42b 100644
--- a/src/Resources/Locales/it_IT.axaml
+++ b/src/Resources/Locales/it_IT.axaml
@@ -40,6 +40,13 @@
NESSUN FILE ASSUNTO COME INVARIATO
RIMUOVI
FILE BINARIO NON SUPPORTATO!!!
+ Biseca
+ Annulla
+ Cattiva
+ Bisecando. La HEAD corrente è buona o cattiva?
+ Buona
+ Salta
+ Bisecando. Marca il commit corrente come buono o cattivo e fai checkout di un altro.
Attribuisci
L'ATTRIBUZIONE SU QUESTO FILE NON È SUPPORTATA!!!
Checkout ${0}$...
@@ -79,6 +86,7 @@
Modifiche Locali:
Scarta
Stasha e Ripristina
+ Aggiorna tutti i sottomoduli
Branch:
Cherry Pick
Aggiungi sorgente al messaggio di commit
@@ -103,8 +111,11 @@
Cherry-Pick...
Confronta con HEAD
Confronta con Worktree
+ Autore
+ Committer
Informazioni
SHA
+ Oggetto
Azione Personalizzata
Riallinea Interattivamente ${0}$ fino a Qui
Unisci a ${0}$
@@ -136,6 +147,7 @@
SHA
Apri nel Browser
Descrizione
+ OGGETTO
Inserisci l'oggetto del commit
Configura Repository
TEMPLATE DI COMMIT
@@ -157,6 +169,7 @@
Recupera automaticamente i remoti
Minuto/i
Remoto Predefinito
+ Modalità di Merge Preferita
TRACCIAMENTO ISSUE
Aggiungi una regola di esempio per Azure DevOps
Aggiungi una regola di esempio per un Issue Gitee
@@ -181,6 +194,10 @@
Colore
Nome
Ripristina schede all'avvio
+ CONTINUA
+ Trovato un commit vuoto! Vuoi procedere (--allow-empty)?
+ STAGE DI TUTTO E COMMITTA
+ Trovato un commit vuoto! Vuoi procedere (--allow-empty) o fare lo stage di tutto e committare?
Guida Commit Convenzionali
Modifica Sostanziale:
Issue Chiusa:
@@ -190,6 +207,7 @@
Tipo di Modifica:
Copia
Copia Tutto il Testo
+ Copia Intero Percorso
Copia Percorso
Crea Branch...
Basato Su:
@@ -309,6 +327,8 @@
FLOW - Completa Hotfix
FLOW - Completa Rilascio
Target:
+ Invia al remote dopo aver finito
+ Esegui squash durante il merge
Hotfix:
Prefisso Hotfix:
Inizializza Git-Flow
@@ -390,6 +410,7 @@
Chiudi il pannello di ricerca
Trova il prossimo risultato
Trova il risultato precedente
+ Apri con uno strumento di diff/merge esterno
Apri il pannello di ricerca
Scarta
Aggiungi in stage
@@ -476,6 +497,7 @@
Numero massimo di commit nella cronologia
Mostra nel grafico l'orario dell'autore anziché quello del commit
Mostra i figli nei dettagli del commit
+ Mostra i tag nel grafico dei commit
Lunghezza Guida Oggetto
GIT
Abilita Auto CRLF
@@ -483,6 +505,7 @@
Email Utente
Email utente Git globale
Abilita --prune durante il fetch
+ Abilita --ignore-cr-at-eol nel diff
Questa applicazione richiede Git (>= 2.23.0)
Percorso Installazione
Abilita la verifica HTTP SSL
@@ -556,6 +579,9 @@
Branch:
ANNULLA
Recupero automatico delle modifiche dai remoti...
+ Ordina
+ Per data del committer
+ Per nome
Pulizia (GC e Potatura)
Esegui il comando `git gc` per questo repository.
Cancella tutto
@@ -589,11 +615,13 @@
AGGIUNGI REMOTO
Cerca Commit
Autore
- Committente
+ Committer
+ Contenuto
File
Messaggio
SHA
Branch Corrente
+ Mostra i Sottomoduli Come Albero
Mostra Tag come Albero
SALTA
Statistiche
@@ -608,6 +636,8 @@
Ordina
Apri nel Terminale
Usa tempo relativo nello storico
+ Visualizza i Log
+ Visita '{0}' nel Browser
WORKTREE
AGGIUNGI WORKTREE
POTATURA
@@ -680,6 +710,12 @@
Percorso Relativo:
Cartella relativa per memorizzare questo modulo.
Elimina Sottomodulo
+ STATO
+ modificato
+ non inizializzato
+ revisione cambiata
+ non unito
+ URL
OK
Copia Nome Tag
Copia Messaggio Tag
@@ -693,6 +729,10 @@
Sottomodulo:
Usa opzione --remote
URL:
+ Log
+ CANCELLA TUTTO
+ Copia
+ Elimina
Avviso
Pagina di Benvenuto
Crea Gruppo
@@ -722,8 +762,13 @@
Attiva evento click
Commit (Modifica)
Stage di tutte le modifiche e fai il commit
+ Hai stageato {0} file ma solo {1} file mostrati ({2} file sono stati filtrati). Vuoi procedere?
CONFLITTI RILEVATI
+ APRI STRUMENTO DI MERGE ESTERNO
+ APRI TUTTI I CONFLITTI NELLO STRUMENTO DI MERGE ESTERNO
CONFLITTI NEI FILE RISOLTI
+ USO IL MIO
+ USO IL LORO
INCLUDI FILE NON TRACCIATI
NESSUN MESSAGGIO RECENTE INSERITO
NESSUN TEMPLATE DI COMMIT
diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml
index 1c45f22f..77a7ba4f 100644
--- a/src/Resources/Locales/ru_RU.axaml
+++ b/src/Resources/Locales/ru_RU.axaml
@@ -390,6 +390,7 @@
Перейти на предыдущую вкладку
Создать новую вкладку
Открыть диалоговое окно настроек
+ Переключить активное рабочее место
РЕПОЗИТОРИЙ
Зафиксировать сформированные изменения
Зафиксировать и выложить сформированные изменения
@@ -410,6 +411,7 @@
Закрыть панель поиска
Найти следующее совпадение
Найти предыдущее совпадение
+ Открыть с внешним инструментом сравнения/слияние
Открыть панель поиска
Отклонить
Сформировать
@@ -431,6 +433,7 @@
Открыть в браузере
ОШИБКА
УВЕДОМЛЕНИЕ
+ Рабочие места
Влить ветку
В:
Опции слияния:
@@ -620,6 +623,7 @@
Сообщение
SHA
Текущая ветка
+ Показывать подмодули как дерево
Показывать метки как катлог
ПРОПУСТИТЬ
Статистикa
@@ -708,6 +712,12 @@
Каталог:
Относительный путь для хранения подмодуля.
Удалить подмодуль
+ СОСТОЯНИЕ
+ изменён
+ не создан
+ ревизия изменена
+ не слита
+ URL-адрес
ОК
Копировать имя метки
Копировать сообщение с метки
diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml
index d08c6f70..18569a14 100644
--- a/src/Resources/Locales/zh_CN.axaml
+++ b/src/Resources/Locales/zh_CN.axaml
@@ -390,6 +390,8 @@
切换到上一个页面
新建页面
打开偏好设置面板
+ 切换工作区
+ 切换显示页面
仓库页面快捷键
提交暂存区更改
提交暂存区更改并推送
@@ -410,6 +412,7 @@
关闭搜索
定位到下一个匹配搜索的位置
定位到上一个匹配搜索的位置
+ 使用外部比对工具查看
打开搜索
丢弃
暂存
@@ -431,6 +434,8 @@
在浏览器中访问
出错了
系统提示
+ 工作区列表
+ 页面列表
合并分支
目标分支 :
合并方式 :
@@ -620,6 +625,7 @@
提交信息
提交指纹
仅在当前分支中查找
+ 以树型结构展示
以树型结构展示
跳过此提交
提交统计
@@ -708,6 +714,12 @@
相对仓库路径 :
本地存放的相对路径。
删除子模块
+ 状态
+ 未提交修改
+ 未初始化
+ SHA变更
+ 未解决冲突
+ 仓库
确 定
复制标签名
复制标签信息
diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml
index 4cd68cad..ded99a14 100644
--- a/src/Resources/Locales/zh_TW.axaml
+++ b/src/Resources/Locales/zh_TW.axaml
@@ -390,6 +390,8 @@
切換到上一個頁面
新增頁面
開啟偏好設定面板
+ 切換工作區
+ 切換目前頁面
存放庫頁面快速鍵
提交暫存區變更
提交暫存區變更並推送
@@ -410,6 +412,7 @@
關閉搜尋面板
前往下一個搜尋相符的位置
前往上一個搜尋相符的位置
+ 使用外部比對工具檢視
開啟搜尋面板
捨棄
暫存
@@ -431,6 +434,8 @@
在瀏覽器中開啟連結
發生錯誤
系統提示
+ 工作區列表
+ 頁面列表
合併分支
目標分支:
合併方式:
@@ -620,6 +625,7 @@
提交訊息
提交編號
僅搜尋目前分支
+ 以樹型結構展示
以樹型結構展示
跳過此提交
提交統計
@@ -708,6 +714,12 @@
相對存放庫路徑:
本機存放的相對路徑。
刪除子模組
+ 狀態
+ 未提交變更
+ 未初始化
+ SHA 變更
+ 未解決的衝突
+ 存放庫
確 定
複製標籤名稱
複製標籤訊息
diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml
index 093ae4e0..923ef22b 100644
--- a/src/Resources/Styles.axaml
+++ b/src/Resources/Styles.axaml
@@ -40,7 +40,7 @@
-
@@ -72,30 +83,28 @@
-
+
-
-
diff --git a/src/Views/LauncherPageSwitcher.axaml.cs b/src/Views/LauncherPageSwitcher.axaml.cs
new file mode 100644
index 00000000..1effb93c
--- /dev/null
+++ b/src/Views/LauncherPageSwitcher.axaml.cs
@@ -0,0 +1,49 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+
+namespace SourceGit.Views
+{
+ public partial class LauncherPageSwitcher : UserControl
+ {
+ public LauncherPageSwitcher()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ base.OnKeyDown(e);
+
+ if (e.Key == Key.Enter && DataContext is ViewModels.LauncherPageSwitcher switcher)
+ {
+ switcher.Switch();
+ e.Handled = true;
+ }
+ }
+
+ private void OnItemDoubleTapped(object sender, TappedEventArgs e)
+ {
+ if (DataContext is ViewModels.LauncherPageSwitcher switcher)
+ {
+ switcher.Switch();
+ e.Handled = true;
+ }
+ }
+
+ private void OnSearchBoxKeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.Key == Key.Down && PagesListBox.ItemCount > 0)
+ {
+ PagesListBox.Focus(NavigationMethod.Directional);
+
+ if (PagesListBox.SelectedIndex < 0)
+ PagesListBox.SelectedIndex = 0;
+ else if (PagesListBox.SelectedIndex < PagesListBox.ItemCount)
+ PagesListBox.SelectedIndex++;
+
+ e.Handled = true;
+ }
+ }
+ }
+}
+
diff --git a/src/Views/LauncherTabBar.axaml b/src/Views/LauncherTabBar.axaml
index 0376e259..a56da2b0 100644
--- a/src/Views/LauncherTabBar.axaml
+++ b/src/Views/LauncherTabBar.axaml
@@ -40,7 +40,7 @@
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Views/LauncherTabBar.axaml.cs b/src/Views/LauncherTabBar.axaml.cs
index 12bca91f..270902bc 100644
--- a/src/Views/LauncherTabBar.axaml.cs
+++ b/src/Views/LauncherTabBar.axaml.cs
@@ -1,6 +1,7 @@
using System;
using Avalonia;
+using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
@@ -19,6 +20,20 @@ namespace SourceGit.Views
set => SetValue(IsScrollerVisibleProperty, value);
}
+ public static readonly StyledProperty SearchFilterProperty =
+ AvaloniaProperty.Register(nameof(SearchFilter));
+
+ public string SearchFilter
+ {
+ get => GetValue(SearchFilterProperty);
+ set => SetValue(SearchFilterProperty, value);
+ }
+
+ public AvaloniaList SelectablePages
+ {
+ get;
+ } = [];
+
public LauncherTabBar()
{
InitializeComponent();
@@ -126,6 +141,14 @@ namespace SourceGit.Views
context.DrawGeometry(fill, stroke, geo);
}
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == SearchFilterProperty)
+ UpdateSelectablePages();
+ }
+
private void ScrollTabs(object _, PointerWheelEventArgs e)
{
if (!e.KeyModifiers.HasFlag(KeyModifiers.Shift))
@@ -248,13 +271,95 @@ namespace SourceGit.Views
e.Handled = true;
}
- private void OnGotoSelectedPage(object sender, LauncherTabSelectedEventArgs e)
+ private void OnTabsDropdownOpened(object sender, EventArgs e)
{
- if (DataContext is ViewModels.Launcher vm)
- vm.ActivePage = e.Page;
+ UpdateSelectablePages();
+ }
- PageSelector.Flyout?.Hide();
- e.Handled = true;
+ private void OnTabsDropdownClosed(object sender, EventArgs e)
+ {
+ SelectablePages.Clear();
+ SearchFilter = string.Empty;
+ }
+
+ private void OnTabsDropdownKeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.Key == Key.Escape)
+ {
+ PageSelector.Flyout?.Hide();
+ e.Handled = true;
+ }
+ else if (e.Key == Key.Enter)
+ {
+ if (TabsDropdownList.SelectedItem is ViewModels.LauncherPage page &&
+ DataContext is ViewModels.Launcher vm)
+ {
+ vm.ActivePage = page;
+ PageSelector.Flyout?.Hide();
+ e.Handled = true;
+ }
+ }
+ }
+
+ private void OnTabsDropdownSearchBoxKeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.Key == Key.Down && TabsDropdownList.ItemCount > 0)
+ {
+ TabsDropdownList.Focus(NavigationMethod.Directional);
+
+ if (TabsDropdownList.SelectedIndex < 0)
+ TabsDropdownList.SelectedIndex = 0;
+ else if (TabsDropdownList.SelectedIndex < TabsDropdownList.ItemCount)
+ TabsDropdownList.SelectedIndex++;
+
+ e.Handled = true;
+ }
+ }
+
+ private void OnTabsDropdownLostFocus(object sender, RoutedEventArgs e)
+ {
+ if (sender is Control { IsFocused: false, IsKeyboardFocusWithin: false })
+ PageSelector.Flyout?.Hide();
+ }
+
+ private void OnClearSearchFilter(object sender, RoutedEventArgs e)
+ {
+ SearchFilter = string.Empty;
+ }
+
+ private void OnTabsDropdownItemDoubleTapped(object sender, TappedEventArgs e)
+ {
+ if (sender is Control { DataContext: ViewModels.LauncherPage page } &&
+ DataContext is ViewModels.Launcher vm)
+ {
+ vm.ActivePage = page;
+ PageSelector.Flyout?.Hide();
+ e.Handled = true;
+ }
+ }
+
+ private void UpdateSelectablePages()
+ {
+ if (DataContext is not ViewModels.Launcher vm)
+ return;
+
+ SelectablePages.Clear();
+
+ var pages = vm.Pages;
+ var filter = SearchFilter?.Trim() ?? "";
+ if (string.IsNullOrEmpty(filter))
+ {
+ SelectablePages.AddRange(pages);
+ return;
+ }
+
+ foreach (var page in pages)
+ {
+ var node = page.Node;
+ if (node.Name.Contains(filter, StringComparison.OrdinalIgnoreCase) ||
+ (node.IsRepository && node.Id.Contains(filter, StringComparison.OrdinalIgnoreCase)))
+ SelectablePages.Add(page);
+ }
}
private bool _pressedTab = false;
diff --git a/src/Views/LauncherTabsSelector.axaml.cs b/src/Views/LauncherTabsSelector.axaml.cs
deleted file mode 100644
index 61d7a966..00000000
--- a/src/Views/LauncherTabsSelector.axaml.cs
+++ /dev/null
@@ -1,120 +0,0 @@
-using System;
-
-using Avalonia;
-using Avalonia.Collections;
-using Avalonia.Controls;
-using Avalonia.Interactivity;
-
-namespace SourceGit.Views
-{
- public class LauncherTabSelectedEventArgs : RoutedEventArgs
- {
- public ViewModels.LauncherPage Page { get; }
-
- public LauncherTabSelectedEventArgs(ViewModels.LauncherPage page)
- {
- RoutedEvent = LauncherTabsSelector.PageSelectedEvent;
- Page = page;
- }
- }
-
- public partial class LauncherTabsSelector : UserControl
- {
- public static readonly StyledProperty> PagesProperty =
- AvaloniaProperty.Register>(nameof(Pages));
-
- public AvaloniaList Pages
- {
- get => GetValue(PagesProperty);
- set => SetValue(PagesProperty, value);
- }
-
- public static readonly StyledProperty SearchFilterProperty =
- AvaloniaProperty.Register(nameof(SearchFilter));
-
- public string SearchFilter
- {
- get => GetValue(SearchFilterProperty);
- set => SetValue(SearchFilterProperty, value);
- }
-
- public static readonly RoutedEvent PageSelectedEvent =
- RoutedEvent.Register(nameof(PageSelected), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
-
- public event EventHandler PageSelected
- {
- add { AddHandler(PageSelectedEvent, value); }
- remove { RemoveHandler(PageSelectedEvent, value); }
- }
-
- public AvaloniaList VisiblePages
- {
- get;
- private set;
- }
-
- public LauncherTabsSelector()
- {
- VisiblePages = new AvaloniaList();
- InitializeComponent();
- }
-
- protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
- {
- base.OnPropertyChanged(change);
-
- if (change.Property == PagesProperty || change.Property == SearchFilterProperty)
- UpdateVisiblePages();
- }
-
- private void OnClearSearchFilter(object sender, RoutedEventArgs e)
- {
- SearchFilter = string.Empty;
- }
-
- private void OnPageSelectionChanged(object sender, SelectionChangedEventArgs e)
- {
- if (sender is ListBox { SelectedItem: ViewModels.LauncherPage page })
- {
- _isProcessingSelection = true;
- RaiseEvent(new LauncherTabSelectedEventArgs(page));
- _isProcessingSelection = false;
- }
-
- e.Handled = true;
- }
-
- private void UpdateVisiblePages()
- {
- if (_isProcessingSelection)
- return;
-
- VisiblePages.Clear();
-
- if (Pages == null)
- return;
-
- var filter = SearchFilter?.Trim() ?? "";
- if (string.IsNullOrEmpty(filter))
- {
- foreach (var p in Pages)
- VisiblePages.Add(p);
-
- return;
- }
-
- foreach (var page in Pages)
- {
- if (!page.Node.IsRepository)
- continue;
-
- if (page.Node.Name.Contains(filter, StringComparison.OrdinalIgnoreCase) ||
- page.Node.Id.Contains(filter, StringComparison.OrdinalIgnoreCase))
- VisiblePages.Add(page);
- }
- }
-
- private bool _isProcessingSelection = false;
- }
-}
-
diff --git a/src/Views/Preferences.axaml b/src/Views/Preferences.axaml
index 6742bcfc..b5826d11 100644
--- a/src/Views/Preferences.axaml
+++ b/src/Views/Preferences.axaml
@@ -129,23 +129,23 @@
+ IsChecked="{Binding ShowAuthorTimeInGraph, Mode=TwoWay}"/>
+ IsChecked="{Binding ShowTagsInGraph, Mode=TwoWay}"/>
+ IsChecked="{Binding ShowChildren, Mode=TwoWay}"/>
+ IsChecked="{Binding Check4UpdatesOnStartup, Mode=TwoWay}"/>
@@ -257,12 +257,12 @@
+ IsChecked="{Binding UseFixedTabWidth, Mode=TwoWay}"/>
diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml
index 2896a5ff..4eb303fe 100644
--- a/src/Views/Repository.axaml
+++ b/src/Views/Repository.axaml
@@ -204,7 +204,10 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Views/SubmodulesView.axaml.cs b/src/Views/SubmodulesView.axaml.cs
new file mode 100644
index 00000000..81ccdc5d
--- /dev/null
+++ b/src/Views/SubmodulesView.axaml.cs
@@ -0,0 +1,182 @@
+using System;
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Layout;
+using Avalonia.Media;
+using Avalonia.VisualTree;
+
+namespace SourceGit.Views
+{
+ public class SubmoduleTreeNodeToggleButton : ToggleButton
+ {
+ protected override Type StyleKeyOverride => typeof(ToggleButton);
+
+ protected override void OnPointerPressed(PointerPressedEventArgs e)
+ {
+ if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed &&
+ DataContext is ViewModels.SubmoduleTreeNode { IsFolder: true } node)
+ {
+ var view = this.FindAncestorOfType();
+ view?.ToggleNodeIsExpanded(node);
+ }
+
+ e.Handled = true;
+ }
+ }
+
+ public class SubmoduleTreeNodeIcon : UserControl
+ {
+ public static readonly StyledProperty IsExpandedProperty =
+ AvaloniaProperty.Register(nameof(IsExpanded));
+
+ public bool IsExpanded
+ {
+ get => GetValue(IsExpandedProperty);
+ set => SetValue(IsExpandedProperty, value);
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == IsExpandedProperty)
+ UpdateContent();
+ }
+
+ protected override void OnDataContextChanged(EventArgs e)
+ {
+ base.OnDataContextChanged(e);
+ UpdateContent();
+ }
+
+ private void UpdateContent()
+ {
+ if (DataContext is not ViewModels.SubmoduleTreeNode node)
+ {
+ Content = null;
+ return;
+ }
+
+ if (node.Module != null)
+ CreateContent(new Thickness(0, 0, 0, 0), "Icons.Submodule");
+ else if (node.IsExpanded)
+ CreateContent(new Thickness(0, 2, 0, 0), "Icons.Folder.Open");
+ else
+ CreateContent(new Thickness(0, 2, 0, 0), "Icons.Folder");
+ }
+
+ private void CreateContent(Thickness margin, string iconKey)
+ {
+ var geo = this.FindResource(iconKey) as StreamGeometry;
+ if (geo == null)
+ return;
+
+ Content = new Avalonia.Controls.Shapes.Path()
+ {
+ Width = 12,
+ Height = 12,
+ HorizontalAlignment = HorizontalAlignment.Left,
+ VerticalAlignment = VerticalAlignment.Center,
+ Margin = margin,
+ Data = geo,
+ };
+ }
+ }
+
+ public partial class SubmodulesView : UserControl
+ {
+ public static readonly RoutedEvent RowsChangedEvent =
+ RoutedEvent.Register(nameof(RowsChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
+
+ public event EventHandler RowsChanged
+ {
+ add { AddHandler(RowsChangedEvent, value); }
+ remove { RemoveHandler(RowsChangedEvent, value); }
+ }
+
+ public int Rows
+ {
+ get;
+ private set;
+ }
+
+ public SubmodulesView()
+ {
+ InitializeComponent();
+ }
+
+ public void ToggleNodeIsExpanded(ViewModels.SubmoduleTreeNode node)
+ {
+ if (Content is ViewModels.SubmoduleCollectionAsTree tree)
+ {
+ tree.ToggleExpand(node);
+ Rows = tree.Rows.Count;
+ RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
+ }
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == ContentProperty)
+ {
+ if (Content is ViewModels.SubmoduleCollectionAsTree tree)
+ Rows = tree.Rows.Count;
+ else if (Content is ViewModels.SubmoduleCollectionAsList list)
+ Rows = list.Submodules.Count;
+ else
+ Rows = 0;
+
+ RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
+ }
+ else if (change.Property == IsVisibleProperty)
+ {
+ RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
+ }
+ }
+
+ private void OnItemDoubleTapped(object sender, TappedEventArgs e)
+ {
+ if (sender is Control control && DataContext is ViewModels.Repository repo)
+ {
+ if (control.DataContext is ViewModels.SubmoduleTreeNode node)
+ {
+ if (node.IsFolder)
+ ToggleNodeIsExpanded(node);
+ else if (node.Module.Status != Models.SubmoduleStatus.NotInited)
+ repo.OpenSubmodule(node.Module.Path);
+ }
+ else if (control.DataContext is Models.Submodule m && m.Status != Models.SubmoduleStatus.NotInited)
+ {
+ repo.OpenSubmodule(m.Path);
+ }
+ }
+
+ e.Handled = true;
+ }
+
+ private void OnItemContextRequested(object sender, ContextRequestedEventArgs e)
+ {
+ if (sender is Control control && DataContext is ViewModels.Repository repo)
+ {
+ if (control.DataContext is ViewModels.SubmoduleTreeNode node && node.Module != null)
+ {
+ var menu = repo.CreateContextMenuForSubmodule(node.Module);
+ menu?.Open(control);
+ }
+ else if (control.DataContext is Models.Submodule m)
+ {
+ var menu = repo.CreateContextMenuForSubmodule(m);
+ menu?.Open(control);
+ }
+ }
+
+ e.Handled = true;
+ }
+ }
+}
diff --git a/src/Views/TagsView.axaml b/src/Views/TagsView.axaml
index 2a575cb3..423d2425 100644
--- a/src/Views/TagsView.axaml
+++ b/src/Views/TagsView.axaml
@@ -23,14 +23,36 @@
+ SelectionChanged="OnSelectionChanged">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+ VerticalAlignment="Center">
-
+
+ Margin="8,0,0,0">
+
+
+
@@ -66,11 +88,29 @@
Margin="8,0,0,0"
ItemsSource="{Binding Tags}"
SelectionMode="Single"
- SelectionChanged="OnRowSelectionChanged">
+ SelectionChanged="OnSelectionChanged">
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
NodeProperty =
- AvaloniaProperty.Register(nameof(Node));
-
- public ViewModels.TagTreeNode Node
- {
- get => GetValue(NodeProperty);
- set => SetValue(NodeProperty, value);
- }
-
public static readonly StyledProperty IsExpandedProperty =
AvaloniaProperty.Register(nameof(IsExpanded));
@@ -49,16 +39,23 @@ namespace SourceGit.Views
set => SetValue(IsExpandedProperty, value);
}
- static TagTreeNodeIcon()
+ protected override void OnDataContextChanged(EventArgs e)
{
- NodeProperty.Changed.AddClassHandler((icon, _) => icon.UpdateContent());
- IsExpandedProperty.Changed.AddClassHandler((icon, _) => icon.UpdateContent());
+ base.OnDataContextChanged(e);
+ UpdateContent();
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+
+ if (change.Property == IsExpandedProperty)
+ UpdateContent();
}
private void UpdateContent()
{
- var node = Node;
- if (node == null)
+ if (DataContext is not ViewModels.TagTreeNode node)
{
Content = null;
return;
@@ -92,24 +89,6 @@ namespace SourceGit.Views
public partial class TagsView : UserControl
{
- public static readonly StyledProperty ShowTagsAsTreeProperty =
- AvaloniaProperty.Register(nameof(ShowTagsAsTree));
-
- public bool ShowTagsAsTree
- {
- get => GetValue(ShowTagsAsTreeProperty);
- set => SetValue(ShowTagsAsTreeProperty, value);
- }
-
- public static readonly StyledProperty> TagsProperty =
- AvaloniaProperty.Register>(nameof(Tags));
-
- public List Tags
- {
- get => GetValue(TagsProperty);
- set => SetValue(TagsProperty, value);
- }
-
public static readonly RoutedEvent SelectionChangedEvent =
RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
@@ -150,33 +129,7 @@ namespace SourceGit.Views
{
if (Content is ViewModels.TagCollectionAsTree tree)
{
- node.IsExpanded = !node.IsExpanded;
-
- var depth = node.Depth;
- var idx = tree.Rows.IndexOf(node);
- if (idx == -1)
- return;
-
- if (node.IsExpanded)
- {
- var subrows = new List();
- MakeTreeRows(subrows, node.Children);
- tree.Rows.InsertRange(idx + 1, subrows);
- }
- else
- {
- var removeCount = 0;
- for (int i = idx + 1; i < tree.Rows.Count; i++)
- {
- var row = tree.Rows[i];
- if (row.Depth <= depth)
- break;
-
- removeCount++;
- }
- tree.Rows.RemoveRange(idx + 1, removeCount);
- }
-
+ tree.ToggleExpand(node);
Rows = tree.Rows.Count;
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
}
@@ -186,9 +139,15 @@ namespace SourceGit.Views
{
base.OnPropertyChanged(change);
- if (change.Property == ShowTagsAsTreeProperty || change.Property == TagsProperty)
+ if (change.Property == ContentProperty)
{
- UpdateDataSource();
+ if (Content is ViewModels.TagCollectionAsTree tree)
+ Rows = tree.Rows.Count;
+ else if (Content is ViewModels.TagCollectionAsList list)
+ Rows = list.Tags.Count;
+ else
+ Rows = 0;
+
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
}
else if (change.Property == IsVisibleProperty)
@@ -197,7 +156,7 @@ namespace SourceGit.Views
}
}
- private void OnDoubleTappedNode(object sender, TappedEventArgs e)
+ private void OnItemDoubleTapped(object sender, TappedEventArgs e)
{
if (sender is Control { DataContext: ViewModels.TagTreeNode { IsFolder: true } node })
ToggleNodeIsExpanded(node);
@@ -205,7 +164,7 @@ namespace SourceGit.Views
e.Handled = true;
}
- private void OnRowPointerPressed(object sender, PointerPressedEventArgs e)
+ private void OnItemPointerPressed(object sender, PointerPressedEventArgs e)
{
var p = e.GetCurrentPoint(this);
if (!p.Properties.IsLeftButtonPressed)
@@ -220,7 +179,7 @@ namespace SourceGit.Views
repo.NavigateToCommit(nodeTag.SHA);
}
- private void OnRowContextRequested(object sender, ContextRequestedEventArgs e)
+ private void OnItemContextRequested(object sender, ContextRequestedEventArgs e)
{
var control = sender as Control;
if (control == null)
@@ -243,7 +202,7 @@ namespace SourceGit.Views
e.Handled = true;
}
- private void OnRowSelectionChanged(object sender, SelectionChangedEventArgs _)
+ private void OnSelectionChanged(object sender, SelectionChangedEventArgs _)
{
var selected = (sender as ListBox)?.SelectedItem;
var selectedTag = null as Models.Tag;
@@ -255,63 +214,6 @@ namespace SourceGit.Views
if (selectedTag != null)
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
}
-
- private void MakeTreeRows(List rows, List nodes)
- {
- foreach (var node in nodes)
- {
- rows.Add(node);
-
- if (!node.IsExpanded || !node.IsFolder)
- continue;
-
- MakeTreeRows(rows, node.Children);
- }
- }
-
- private void UpdateDataSource()
- {
- var tags = Tags;
- if (tags == null || tags.Count == 0)
- {
- Rows = 0;
- Content = null;
- return;
- }
-
- if (ShowTagsAsTree)
- {
- var oldExpanded = new HashSet();
- if (Content is ViewModels.TagCollectionAsTree oldTree)
- {
- foreach (var row in oldTree.Rows)
- {
- if (row.IsFolder && row.IsExpanded)
- oldExpanded.Add(row.FullPath);
- }
- }
-
- var tree = new ViewModels.TagCollectionAsTree();
- tree.Tree = ViewModels.TagTreeNode.Build(tags, oldExpanded);
-
- var rows = new List();
- MakeTreeRows(rows, tree.Tree);
- tree.Rows.AddRange(rows);
-
- Content = tree;
- Rows = rows.Count;
- }
- else
- {
- var list = new ViewModels.TagCollectionAsList();
- list.Tags.AddRange(tags);
-
- Content = list;
- Rows = tags.Count;
- }
-
- RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
- }
}
}
diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml
index f8640ec7..cc17f388 100644
--- a/src/Views/WorkingCopy.axaml
+++ b/src/Views/WorkingCopy.axaml
@@ -61,13 +61,15 @@
-
+
-
-
-
+
+
+
+
+
-
-
-
-
-
-
@@ -142,12 +144,14 @@
-
+
-
-
-
-
+
+
+
+
+
+
@@ -156,10 +160,10 @@
-
+
-
diff --git a/src/Views/WorkspaceSwitcher.axaml b/src/Views/WorkspaceSwitcher.axaml
new file mode 100644
index 00000000..4a324f3e
--- /dev/null
+++ b/src/Views/WorkspaceSwitcher.axaml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Views/WorkspaceSwitcher.axaml.cs b/src/Views/WorkspaceSwitcher.axaml.cs
new file mode 100644
index 00000000..04bdae1a
--- /dev/null
+++ b/src/Views/WorkspaceSwitcher.axaml.cs
@@ -0,0 +1,49 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+
+namespace SourceGit.Views
+{
+ public partial class WorkspaceSwitcher : UserControl
+ {
+ public WorkspaceSwitcher()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ base.OnKeyDown(e);
+
+ if (e.Key == Key.Enter && DataContext is ViewModels.WorkspaceSwitcher switcher)
+ {
+ switcher.Switch();
+ e.Handled = true;
+ }
+ }
+
+ private void OnItemDoubleTapped(object sender, TappedEventArgs e)
+ {
+ if (DataContext is ViewModels.WorkspaceSwitcher switcher)
+ {
+ switcher.Switch();
+ e.Handled = true;
+ }
+ }
+
+ private void OnSearchBoxKeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.Key == Key.Down && WorkspaceListBox.ItemCount > 0)
+ {
+ WorkspaceListBox.Focus(NavigationMethod.Directional);
+
+ if (WorkspaceListBox.SelectedIndex < 0)
+ WorkspaceListBox.SelectedIndex = 0;
+ else if (WorkspaceListBox.SelectedIndex < WorkspaceListBox.ItemCount)
+ WorkspaceListBox.SelectedIndex++;
+
+ e.Handled = true;
+ }
+ }
+ }
+}
+