diff --git a/BFR/MainWindow.xaml b/BFR/MainWindow.xaml
index e98ffcc..4e63193 100644
--- a/BFR/MainWindow.xaml
+++ b/BFR/MainWindow.xaml
@@ -280,5 +280,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BFR/MainWindowUIProperties.cs b/BFR/MainWindowUIProperties.cs
index d36b936..8f9e2b3 100644
--- a/BFR/MainWindowUIProperties.cs
+++ b/BFR/MainWindowUIProperties.cs
@@ -28,6 +28,7 @@ namespace BFR
OperationType.Make(),
OperationType.Make(),
OperationType.Make(),
+ OperationType.Make(),
};
public static readonly AvaloniaProperty IsCommitButtonEnabledProperty =
diff --git a/BFR/Operations/Sort.cs b/BFR/Operations/Sort.cs
new file mode 100644
index 0000000..e30b7c5
--- /dev/null
+++ b/BFR/Operations/Sort.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Linq;
+using System.Text;
+using System.Globalization;
+using System.Collections.Generic;
+
+using BFR.Helpers;
+using BFR.DataModels;
+
+using Avalonia;
+
+namespace BFR.Operations
+{
+ public class Sort : Operation
+ {
+ public override string Name => nameof(Sort);
+ public override string Description => "Sorts the file names according to how they are when they arrive at this operation.";
+
+ public static readonly AvaloniaProperty ModeProperty =
+ AvaloniaProperty.Register(nameof(Mode), SortMode.Normal);
+ public SortMode Mode { get => GetValue(ModeProperty); set => SetValue(ModeProperty, value); }
+ public SortDirection Direction { get; set; }
+
+ public SortOperationMode[] Modes => SortOperationMode.Modes;
+ public string[] Directions => Enum.GetNames(typeof(SortDirection));
+
+ public bool FullName { get; set; } = false;
+
+ private Func GetName => FullName
+ ? (Func)(x => x.FullName)
+ : (Func)(x => x.Name);
+
+ protected override void ApplyToInternal(IList files)
+ {
+ // Fail conditions: No fail conditions.
+ // Apply operation
+ files.ReplaceAll(new List (Mode switch
+ {
+ SortMode.Normal => files.OrderBy(GetName, StringComparer.CurrentCulture),
+ SortMode.Natural => files.OrderBy(GetName, Comparer.Create(CompareNatural)),
+ SortMode.Ordinal => files.OrderBy(GetName, StringComparer.Ordinal),
+ SortMode.Length => files.OrderBy(x => GetName(x).Length),
+ SortMode.Reverse => files.Reverse(),
+ _ => files
+ }));
+ if (Mode != SortMode.Reverse && Direction == SortDirection.Descending)
+ files.ReplaceAll(new List(files.Reverse()));
+ }
+
+ // Code taken and slightly modified from https://git.lastassault.de/speatzle/BulkFileRenamer
+ public static int CompareNatural(string strA, string strB) =>
+ CompareNatural(strA, strB, CultureInfo.CurrentCulture, CompareOptions.IgnoreCase);
+
+ public static int CompareNatural(string strA, string strB, CultureInfo culture, CompareOptions options)
+ {
+ var cmp = culture.CompareInfo;
+ var (iA, iB) = (0, 0);
+ var softResult = 0;
+ var softResultWeight = 0;
+ while (iA < strA.Length && iB < strB.Length)
+ {
+ var isDigitA = char.IsDigit(strA[iA]);
+ var isDigitB = char.IsDigit(strB[iB]);
+ if (isDigitA != isDigitB)
+ return cmp.Compare(strA, iA, strB, iB, options);
+ else if (!isDigitA && !isDigitB)
+ {
+ var jA = iA + 1;
+ var jB = iB + 1;
+ while (jA < strA.Length && !char.IsDigit(strA[jA])) jA++;
+ while (jB < strB.Length && !char.IsDigit(strB[jB])) jB++;
+ var cmpResult = cmp.Compare(strA, iA, jA - iA, strB, iB, jB - iB, options);
+ if (cmpResult != 0)
+ {
+ var (secA, secB) = (strA[iA..jA], strB[iB..jB]);
+ if (cmp.Compare(secA + "1", secB + "2", options) ==
+ cmp.Compare(secA + "2", secB + "1", options))
+ return cmp.Compare(strA, iA, strB, iB, options);
+ else if (softResultWeight < 1)
+ {
+ softResult = cmpResult;
+ softResultWeight = 1;
+ }
+ }
+ (iA, jA) = (iB, jB);
+ }
+ else
+ {
+ var zeroA = (char)(strA[iA] - (int)char.GetNumericValue(strA[iA]));
+ var zeroB = (char)(strB[iB] - (int)char.GetNumericValue(strB[iB]));
+ var (jA, jB) = (iA, iB);
+ while (jA < strA.Length && strA[jA] == zeroA) jA++;
+ while (jB < strB.Length && strB[jB] == zeroB) jB++;
+ int resultIfSameLength = 0;
+ do
+ {
+ isDigitA = jA < strA.Length && char.IsDigit(strA[jA]);
+ isDigitB = jB < strB.Length && char.IsDigit(strB[jB]);
+ var numA = isDigitA ? (int)char.GetNumericValue(strA[jA]) : 0;
+ var numB = isDigitB ? (int)char.GetNumericValue(strB[jB]) : 0;
+ if (isDigitA && (char)(strA[jA] - numA) != zeroA) isDigitA = false;
+ if (isDigitB && (char)(strB[jB] - numB) != zeroB) isDigitB = false;
+ if (isDigitA && isDigitB)
+ {
+ if (numA != numB && resultIfSameLength == 0)
+ resultIfSameLength = numA < numB ? -1 : 1;
+ jA++;
+ jB++;
+ }
+ }
+ while (isDigitA && isDigitB);
+ if (isDigitA != isDigitB)
+ return isDigitA ? 1 : -1;
+ else if (resultIfSameLength != 0)
+ return resultIfSameLength;
+ var (lA, lB) = (jA - iA, jB - iB);
+ if (lA != lB)
+ return lA > lB ? -1 : 1;
+ else if (zeroA != zeroB && softResultWeight < 2)
+ {
+ softResult = cmp.Compare(strA, iA, 1, strB, iB, 1, options);
+ softResultWeight = 2;
+ }
+ (iA, iB) = (jA, jB);
+ }
+ }
+ if (iA < strA.Length || iB < strB.Length)
+ return iA < strA.Length ? 1 : -1;
+ else if (softResult != 0)
+ return softResult;
+ return 0;
+ }
+ }
+}
diff --git a/BFR/Operations/SortMode.cs b/BFR/Operations/SortMode.cs
new file mode 100644
index 0000000..c8f4997
--- /dev/null
+++ b/BFR/Operations/SortMode.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+
+using BFR.Helpers;
+
+namespace BFR.Operations
+{
+ public class SortOperationMode : OperationMode
+ {
+ public bool IsAlphanumerical => Index == SortMode.Normal;
+ public bool IsNatural => Index == SortMode.Natural;
+ public bool IsLength => Index == SortMode.Length;
+ public bool IsReverse => Index == SortMode.Reverse;
+
+ public static readonly SortOperationMode[] Modes = All();
+
+ protected static SortOperationMode[] All() => ((IEnumerable)Enum.GetValues(typeof(SortMode))).Select(x =>
+ new SortOperationMode(
+ x,
+ x.GetAttribute().DisplayName,
+ x.GetAttribute().Description
+ )).ToArray();
+
+ public SortOperationMode(SortMode index, string name, string description) :
+ base(index, name, description)
+ { }
+ }
+
+ public enum SortMode
+ {
+ [OperationMode(nameof(Normal), "Compares file names based on current culture.")]
+ Normal,
+ [OperationMode(nameof(Ordinal), "Compares each successive ordinal character in a string (each character as its ASCII number value).")]
+ Ordinal,
+ [OperationMode(nameof(Natural), "Sorts by natural sort order (numeric values grouped).")]
+ Natural,
+ [OperationMode(nameof(Length), "Sorts by file name length.")]
+ Length,
+ [OperationMode(nameof(Reverse), "Reverses the list order.")]
+ Reverse,
+ }
+
+ public enum SortDirection
+ {
+ Ascending,
+ Descending
+ }
+}