Added Sort-Operation + UI

This commit is contained in:
adroslice 2019-11-23 18:32:10 +01:00
parent a1d76d1920
commit 4b50f44985
4 changed files with 202 additions and 0 deletions

View File

@ -280,5 +280,23 @@
</Grid> </Grid>
</Expander> </Expander>
</DataTemplate> </DataTemplate>
<DataTemplate DataType="{x:Type ops:Sort}">
<Expander Classes="OperationExpander">
<Grid ColumnDefinitions="auto,*" RowDefinitions="auto,auto,auto">
<TextBlock Grid.Row="0" Text="Mode:"/>
<ComboBox Grid.Row="0" Items="{Binding Modes}" Name="ModeSelector" SelectedIndex="{Binding Mode}" PropertyChanged="PreviewChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="1" Text="Direction:" IsVisible="{Binding !#ModeSelector.SelectedItem.IsReverse}"/>
<ComboBox Grid.Row="1" Items="{Binding Directions}" SelectedIndex="{Binding Direction}" IsVisible="{Binding !#ModeSelector.SelectedItem.IsReverse}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="2" Text="Full Name:" IsVisible="{Binding !#ModeSelector.SelectedItem.IsReverse}"/>
<CheckBox Grid.Row="2" IsChecked="{Binding FullName}" IsVisible="{Binding !#ModeSelector.SelectedItem.IsReverse}" PropertyChanged="PreviewChanged"/>
</Grid>
</Expander>
</DataTemplate>
</Window.DataTemplates> </Window.DataTemplates>
</Window> </Window>

View File

@ -28,6 +28,7 @@ namespace BFR
OperationType.Make<Remove>(), OperationType.Make<Remove>(),
OperationType.Make<Number>(), OperationType.Make<Number>(),
OperationType.Make<Add>(), OperationType.Make<Add>(),
OperationType.Make<Sort>(),
}; };
public static readonly AvaloniaProperty<bool> IsCommitButtonEnabledProperty = public static readonly AvaloniaProperty<bool> IsCommitButtonEnabledProperty =

134
BFR/Operations/Sort.cs Normal file
View File

@ -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<SortMode> ModeProperty =
AvaloniaProperty.Register<MainWindow, SortMode>(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<FileModel, string> GetName => FullName
? (Func<FileModel, string>)(x => x.FullName)
: (Func<FileModel, string>)(x => x.Name);
protected override void ApplyToInternal(IList<FileModel> files)
{
// Fail conditions: No fail conditions.
// Apply operation
files.ReplaceAll(new List<FileModel> (Mode switch
{
SortMode.Normal => files.OrderBy(GetName, StringComparer.CurrentCulture),
SortMode.Natural => files.OrderBy(GetName, Comparer<string>.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<FileModel>(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;
}
}
}

View File

@ -0,0 +1,49 @@
using System;
using System.Linq;
using System.Collections.Generic;
using BFR.Helpers;
namespace BFR.Operations
{
public class SortOperationMode : OperationMode<SortMode>
{
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<SortMode>)Enum.GetValues(typeof(SortMode))).Select(x =>
new SortOperationMode(
x,
x.GetAttribute<OperationModeAttribute>().DisplayName,
x.GetAttribute<OperationModeAttribute>().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
}
}