OperationMode overhaul, improved event handling, fixed properties

- OperationMode now designed and implemented in a way that allows serialization
- Modes are now defined using an enumeration with with attributes decorated values
- MainWindow.xaml.cs's event handlers now only listen for changes to certain properties
- Property naming conventions fixed
- AvaloniaProperties are now public + static + readonly
This commit is contained in:
adroslice 2019-11-21 19:07:46 +01:00
parent 5633cd2afe
commit ae7c3a3fff
10 changed files with 160 additions and 92 deletions

View File

@ -27,4 +27,15 @@ namespace BFR.Helpers
catch(Exception) { return false; }
}
}
public static class EnumExtensions
{
public static T GetAttribute<T>(this Enum enumVal) where T : Attribute
{
var type = enumVal.GetType();
var memInfo = type.GetMember(enumVal.ToString());
var attributes = memInfo[0].GetCustomAttributes(typeof(T), false);
return (attributes.Length > 0) ? (T)attributes[0] : null;
}
}
}

View File

@ -166,31 +166,31 @@
<Expander Classes="OperationExpander">
<Grid ColumnDefinitions="auto,*" RowDefinitions="auto,auto,auto,auto,auto,auto,auto,auto,auto,auto,auto">
<TextBlock Grid.Row="0" Text="Mode:"/>
<ComboBox Grid.Row="0" Items="{Binding Modes}" SelectedItem="{Binding Mode}" PropertyChanged="PreviewChanged">
<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="Pattern:" IsVisible="{Binding Mode.IsPatternMode}"/>
<TextBox Grid.Row="1" Text="{Binding Pattern}" IsVisible="{Binding Mode.IsPatternMode}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="2" Text="Characters:" IsVisible="{Binding Mode.IsCharactersMode}"/>
<TextBox Grid.Row="2" Text="{Binding Characters}" IsVisible="{Binding Mode.IsCharactersMode}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="3" Text="From N:" IsVisible="{Binding Mode.IsFromNToNMode}"/>
<NumericUpDown Grid.Row="3" Value="{Binding FromN}" IsVisible="{Binding Mode.IsFromNToNMode}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="4" Text="To N:" IsVisible="{Binding Mode.IsFromNToNMode}"/>
<NumericUpDown Grid.Row="4" Value="{Binding ToN}" IsVisible="{Binding Mode.IsFromNToNMode}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="5" Text="First N:" IsVisible="{Binding Mode.IsFirstNMode}"/>
<NumericUpDown Grid.Row="5" Value="{Binding FirstN}" IsVisible="{Binding Mode.IsFirstNMode}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="6" Text="Last N:" IsVisible="{Binding Mode.IsLastNMode}"/>
<NumericUpDown Grid.Row="6" Value="{Binding LastN}" IsVisible="{Binding Mode.IsLastNMode}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="1" Text="Pattern:" IsVisible="{Binding #ModeSelector.SelectedItem.IsPattern}"/>
<TextBox Grid.Row="1" Text="{Binding Pattern}" IsVisible="{Binding #ModeSelector.SelectedItem.IsPattern}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="2" Text="Characters:" IsVisible="{Binding #ModeSelector.SelectedItem.IsCharacters}"/>
<TextBox Grid.Row="2" Text="{Binding Characters}" IsVisible="{Binding #ModeSelector.SelectedItem.IsCharacters}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="3" Text="From N:" IsVisible="{Binding #ModeSelector.SelectedItem.IsFromNToN}"/>
<NumericUpDown Grid.Row="3" Value="{Binding FromN}" IsVisible="{Binding #ModeSelector.SelectedItem.IsFromNToN}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="4" Text="To N:" IsVisible="{Binding #ModeSelector.SelectedItem.IsFromNToN}"/>
<NumericUpDown Grid.Row="4" Value="{Binding ToN}" IsVisible="{Binding #ModeSelector.SelectedItem.IsFromNToN}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="5" Text="First N:" IsVisible="{Binding #ModeSelector.SelectedItem.IsFirstN}"/>
<NumericUpDown Grid.Row="5" Value="{Binding FirstN}" IsVisible="{Binding #ModeSelector.SelectedItem.IsFirstN}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="6" Text="Last N:" IsVisible="{Binding #ModeSelector.SelectedItem.IsLastN}"/>
<NumericUpDown Grid.Row="6" Value="{Binding LastN}" IsVisible="{Binding #ModeSelector.SelectedItem.IsLastN}" PropertyChanged="PreviewChanged"/>
<!--TextBlock Grid.Row="7" Text="Replacement:"/>
<TextBox Grid.Row="7" Text="{Binding Replacement}" PropertyChanged="PreviewChanged"/-->
<TextBlock Grid.Row="8" Text="Regex:" IsVisible="{Binding Mode.IsPatternMode}"/>
<CheckBox Grid.Row="8" IsChecked="{Binding UseRegex}" IsVisible="{Binding Mode.IsPatternMode}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="9" Text="Replace Regex:" IsVisible="{Binding Mode.IsPatternMode}"/>
<CheckBox Grid.Row="9" IsChecked="{Binding UseRegexReplace}" IsVisible="{Binding Mode.IsPatternMode}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="8" Text="Regex:" IsVisible="{Binding #ModeSelector.SelectedItem.IsPattern}"/>
<CheckBox Grid.Row="8" IsChecked="{Binding UseRegex}" IsVisible="{Binding #ModeSelector.SelectedItem.IsPattern}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="9" Text="Replace Regex:" IsVisible="{Binding #ModeSelector.SelectedItem.IsPattern}"/>
<CheckBox Grid.Row="9" IsChecked="{Binding UseRegexReplace}" IsVisible="{Binding #ModeSelector.SelectedItem.IsPattern}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="10" Text="Full Name:"/>
<CheckBox Grid.Row="10" IsChecked="{Binding FullName}" PropertyChanged="PreviewChanged"/>
</Grid>
@ -200,31 +200,31 @@
<Expander Classes="OperationExpander">
<Grid ColumnDefinitions="auto,*" RowDefinitions="auto,auto,auto,auto,auto,auto,auto,auto,auto,auto,auto">
<TextBlock Grid.Row="0" Text="Mode:"/>
<ComboBox Grid.Row="0" Items="{Binding Modes}" SelectedItem="{Binding Mode}" PropertyChanged="PreviewChanged">
<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="Pattern:" IsVisible="{Binding Mode.IsPatternMode}"/>
<TextBox Grid.Row="1" Text="{Binding Pattern}" IsVisible="{Binding Mode.IsPatternMode}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="2" Text="Characters:" IsVisible="{Binding Mode.IsCharactersMode}"/>
<TextBox Grid.Row="2" Text="{Binding Characters}" IsVisible="{Binding Mode.IsCharactersMode}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="3" Text="From N:" IsVisible="{Binding Mode.IsFromNToNMode}"/>
<NumericUpDown Grid.Row="3" Value="{Binding FromN}" IsVisible="{Binding Mode.IsFromNToNMode}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="4" Text="To N:" IsVisible="{Binding Mode.IsFromNToNMode}"/>
<NumericUpDown Grid.Row="4" Value="{Binding ToN}" IsVisible="{Binding Mode.IsFromNToNMode}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="5" Text="First N:" IsVisible="{Binding Mode.IsFirstNMode}"/>
<NumericUpDown Grid.Row="5" Value="{Binding FirstN}" IsVisible="{Binding Mode.IsFirstNMode}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="6" Text="Last N:" IsVisible="{Binding Mode.IsLastNMode}"/>
<NumericUpDown Grid.Row="6" Value="{Binding LastN}" IsVisible="{Binding Mode.IsLastNMode}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="1" Text="Pattern:" IsVisible="{Binding #ModeSelector.SelectedItem.IsPattern}"/>
<TextBox Grid.Row="1" Text="{Binding Pattern}" IsVisible="{Binding #ModeSelector.SelectedItem.IsPattern}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="2" Text="Characters:" IsVisible="{Binding #ModeSelector.SelectedItem.IsCharacters}"/>
<TextBox Grid.Row="2" Text="{Binding Characters}" IsVisible="{Binding #ModeSelector.SelectedItem.IsCharacters}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="3" Text="From N:" IsVisible="{Binding #ModeSelector.SelectedItem.IsFromNToN}"/>
<NumericUpDown Grid.Row="3" Value="{Binding FromN}" IsVisible="{Binding #ModeSelector.SelectedItem.IsFromNToN}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="4" Text="To N:" IsVisible="{Binding #ModeSelector.SelectedItem.IsFromNToN}"/>
<NumericUpDown Grid.Row="4" Value="{Binding ToN}" IsVisible="{Binding #ModeSelector.SelectedItem.IsFromNToN}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="5" Text="First N:" IsVisible="{Binding #ModeSelector.SelectedItem.IsFirstN}"/>
<NumericUpDown Grid.Row="5" Value="{Binding FirstN}" IsVisible="{Binding #ModeSelector.SelectedItem.IsFirstN}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="6" Text="Last N:" IsVisible="{Binding #ModeSelector.SelectedItem.IsLastN}"/>
<NumericUpDown Grid.Row="6" Value="{Binding LastN}" IsVisible="{Binding #ModeSelector.SelectedItem.IsLastN}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="7" Text="Replacement:"/>
<TextBox Grid.Row="7" Text="{Binding Replacement}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="8" Text="Regex:" IsVisible="{Binding Mode.IsPatternMode}"/>
<CheckBox Grid.Row="8" IsChecked="{Binding UseRegex}" IsVisible="{Binding Mode.IsPatternMode}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="9" Text="Replace Regex:" IsVisible="{Binding Mode.IsPatternMode}"/>
<CheckBox Grid.Row="9" IsChecked="{Binding UseRegexReplace}" IsVisible="{Binding Mode.IsPatternMode}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="8" Text="Regex:" IsVisible="{Binding #ModeSelector.SelectedItem.IsPattern}"/>
<CheckBox Grid.Row="8" IsChecked="{Binding UseRegex}" IsVisible="{Binding #ModeSelector.SelectedItem.IsPattern}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="9" Text="Replace Regex:" IsVisible="{Binding #ModeSelector.SelectedItem.IsPattern}"/>
<CheckBox Grid.Row="9" IsChecked="{Binding UseRegexReplace}" IsVisible="{Binding #ModeSelector.SelectedItem.IsPattern}" PropertyChanged="PreviewChanged"/>
<TextBlock Grid.Row="10" Text="Full Name:"/>
<CheckBox Grid.Row="10" IsChecked="{Binding FullName}" PropertyChanged="PreviewChanged"/>
</Grid>

View File

@ -16,6 +16,8 @@ namespace BFR
{
public partial class MainWindow : Window
{
public readonly AvaloniaProperty[] HandledProperties = new AvaloniaProperty[] { TextBox.TextProperty, ComboBox.SelectedItemProperty, NumericUpDown.ValueProperty, CheckBox.IsCheckedProperty };
public async Task OpenDirectoryButtonClick() => OpenDirectory(await new OpenFolderDialog() { Directory = WorkingDirectory }.ShowAsync(this));
public void OpenDirectory(string directory)
{
@ -24,7 +26,11 @@ namespace BFR
Filter();
}
public void FilterChanged(object sender, AvaloniaPropertyChangedEventArgs e) => Filter(); // Bind to PropertyChanged
public void FilterChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
if(HandledProperties.Contains(e.Property))
Filter(); // Bind to PropertyChanged
}
public void Filter()
{
// Filter all files in the directory for those satisfying the given filters
@ -35,7 +41,11 @@ namespace BFR
Preview();
}
public void PreviewChanged(object sender, AvaloniaPropertyChangedEventArgs e) => Preview(); // Bind to PropertyChanged
public void PreviewChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (HandledProperties.Contains(e.Property))
Preview(); // Bind to PropertyChanged
}
public void Preview()
{
// Reset all files to how they currently exist

View File

@ -11,9 +11,9 @@ namespace BFR
{
public partial class MainWindow : Window
{
private readonly AvaloniaProperty<string> workingDirectoryProperty =
public static readonly AvaloniaProperty<string> WorkingDirectoryProperty =
AvaloniaProperty.Register<MainWindow, string>(nameof(WorkingDirectory));
public string WorkingDirectory { get => GetValue(workingDirectoryProperty); set => SetValue(workingDirectoryProperty, value); }
public string WorkingDirectory { get => GetValue(WorkingDirectoryProperty); set => SetValue(WorkingDirectoryProperty, value); }
public AvaloniaList<FileModel> AllFiles { get; } = new AvaloniaList<FileModel>();
public AvaloniaList<FileModel> Files { get; } = new AvaloniaList<FileModel>();
@ -28,12 +28,12 @@ namespace BFR
OperationType.Make<Remove>(),
};
public readonly AvaloniaProperty<bool> isCommitButtonEnabled =
public static readonly AvaloniaProperty<bool> IsCommitButtonEnabledProperty =
AvaloniaProperty.Register<MainWindow, bool>("IsCommitButtonEnabled", defaultValue: false);
public bool IsCommitButtonEnabled { get => GetValue(isCommitButtonEnabled); set => SetValue(isCommitButtonEnabled, value); }
public readonly AvaloniaProperty<int> undoCount =
public bool IsCommitButtonEnabled { get => GetValue(IsCommitButtonEnabledProperty); set => SetValue(IsCommitButtonEnabledProperty, value); }
public static readonly AvaloniaProperty<int> UndoCountProperty =
AvaloniaProperty.Register<MainWindow, int>("UndoCount", defaultValue: 0);
public int UndoCount { get => GetValue(undoCount); set => SetValue(undoCount, value); }
public int UndoCount { get => GetValue(UndoCountProperty); set => SetValue(UndoCountProperty, value); }
private readonly Stack<List<FileModel>> UndoStack = new Stack<List<FileModel>>();
// Filters

View File

@ -0,0 +1,12 @@
using System;
namespace BFR.Operations.Attributes
{
class DescriptionAttribute : Attribute
{
public string Description { get; }
public DescriptionAttribute(string description) =>
Description = description;
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace BFR.Operations.Attributes
{
class DisplayNameAttribute : Attribute
{
public string DisplayName { get; }
public DisplayNameAttribute(string displayName) =>
DisplayName = displayName;
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Xml.Serialization;
using Avalonia;
@ -11,8 +12,10 @@ namespace BFR.Operations
public abstract class Operation : AvaloniaObject
{
// Needs to be avalonia property to update UI with any potential error.
private readonly AvaloniaProperty<string> errorProperty = AvaloniaProperty.Register<MainWindow, string>(nameof(Error), defaultValue: "");
public string Error { get => GetValue(errorProperty); set => SetValue(errorProperty, value); }
[XmlIgnore]
public static readonly AvaloniaProperty<string> ErrorProperty = AvaloniaProperty.Register<MainWindow, string>(nameof(Error), defaultValue: "");
[XmlIgnore]
public string Error { get => GetValue(ErrorProperty); set => SetValue(ErrorProperty, value); }
public bool IsEnabled { get; set; } = true;
public abstract string Name { get; }
public abstract string Description { get; }

View File

@ -1,11 +1,14 @@
namespace BFR.Operations
{
public class OperationMode
{
public int Index { get; }
public string Name { get; }
using System;
public OperationMode(int index, string name) =>
(Index, Name) = (index, name);
namespace BFR.Operations
{
public abstract class OperationMode<T> where T : Enum
{
public T Index { get; }
public string Name { get; }
public string Description { get; }
protected OperationMode(T index, string name, string description) =>
(Index, Name, Description) = (index, name, description);
}
}

View File

@ -1,33 +1,27 @@
using System;
using System.Linq;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Avalonia;
using BFR.Helpers;
using BFR.DataModels;
using System.Text.RegularExpressions;
namespace BFR.Operations
{
public class Replace : Operation
{
private static readonly ReplaceMode DefaultMode = new ReplaceMode(0, "Pattern");
public override string Name => "Replace";
public override string Name => nameof(Replace);
public override string Description => "Replaces the selected part of the file name.";
public AvaloniaProperty<ReplaceMode> modeProperty =
AvaloniaProperty.Register<MainWindow, ReplaceMode>(nameof(Mode), defaultValue: DefaultMode);
public ReplaceMode Mode { get => GetValue(modeProperty); set => SetValue(modeProperty, value); }
public ReplaceMode[] Modes { get; } = new ReplaceMode[]
{
DefaultMode,
new ReplaceMode(1, "Characters"),
new ReplaceMode(2, "FromNToN"),
new ReplaceMode(3, "FirstN"),
new ReplaceMode(4, "LastN"),
};
public static readonly AvaloniaProperty<ReplaceMode> ModeProperty =
AvaloniaProperty.Register<MainWindow, ReplaceMode>(nameof(Mode), ReplaceMode.Pattern);
public ReplaceMode Mode { get => GetValue(ModeProperty); set => SetValue(ModeProperty, value); }
[XmlIgnore]
public ReplaceOperationMode[] Modes => ReplaceOperationMode.Modes;
public string Replacement { get; set; } = "";
public bool FullName { get; set; } = false;
@ -51,7 +45,7 @@ namespace BFR.Operations
if (FromN > ToN) throw new OperationException("From N can't be greater than To N.");
// Regex Failure
if(Mode.IsPatternMode && UseRegex)
if(Modes[(int)Mode].IsPattern && UseRegex)
try { "".Replace(Pattern, Replacement, true, false); }
catch(Exception) { throw new OperationException("Invalid Regex Pattern."); }
@ -60,13 +54,13 @@ namespace BFR.Operations
{
var newName = FullName ? file.FullName : file.Name;
newName = Mode.Index switch
newName = Mode switch
{
0 => newName.Replace(Pattern, Replacement, UseRegex, UseRegexReplace),
1 => Characters.Length > 0 ? Regex.Replace(newName, $"[{Characters}]", Replacement.Replace("$", "$$")) : newName,
2 => Regex.Replace(newName, $"^(.{{{FromN}}}).+(.{{{newName.Length - ToN - 1}}})$", $"$1{Replacement.Replace("$", "$$")}$2"),
3 => Regex.Replace(newName, $"^.{{{FirstN}}}", Replacement.Replace("$", "$$")),
4 => Regex.Replace(newName, $".{{{LastN}}}$", Replacement.Replace("$", "$$")),
ReplaceMode.Pattern => newName.Replace(Pattern, Replacement, UseRegex, UseRegexReplace),
ReplaceMode.Characters => Characters.Length > 0 ? Regex.Replace(newName, $"[{Characters}]", Replacement.Replace("$", "$$")) : newName,
ReplaceMode.FromNToN => Regex.Replace(newName, $"^(.{{{FromN}}}).+(.{{{newName.Length - ToN - 1}}})$", $"$1{Replacement.Replace("$", "$$")}$2"),
ReplaceMode.FirstN => Regex.Replace(newName, $"^.{{{FirstN}}}", Replacement.Replace("$", "$$")),
ReplaceMode.LastN => Regex.Replace(newName, $".{{{LastN}}}$", Replacement.Replace("$", "$$")),
_ => newName
};
@ -74,12 +68,5 @@ namespace BFR.Operations
else file.Name = newName;
}
}
private string ReplaceAllCharacters(string input, string characters, string replacement)
{
foreach (var character in characters)
input = input.Replace(character.ToString(), replacement);
return input;
}
}
}

View File

@ -1,14 +1,44 @@
namespace BFR.Operations
{
public class ReplaceMode : OperationMode
{
public bool IsPatternMode => Index == 0;
public bool IsCharactersMode => Index == 1;
public bool IsFromNToNMode => Index == 2;
public bool IsFirstNMode => Index == 3;
public bool IsLastNMode => Index == 4;
using System;
using System.Linq;
using System.Collections.Generic;
public ReplaceMode(int index, string name) :
base(index, name) { }
using BFR.Helpers;
using BFR.Operations.Attributes;
namespace BFR.Operations
{
public class ReplaceOperationMode : OperationMode<ReplaceMode>
{
public bool IsPattern => Index == ReplaceMode.Pattern;
public bool IsCharacters => Index == ReplaceMode.Characters;
public bool IsFromNToN => Index == ReplaceMode.FromNToN;
public bool IsFirstN => Index == ReplaceMode.FirstN;
public bool IsLastN => Index == ReplaceMode.LastN;
public readonly static ReplaceOperationMode[] Modes = All();
protected ReplaceOperationMode(ReplaceMode index, string name, string description) :
base(index, name, description) { }
protected static ReplaceOperationMode[] All() => ((IEnumerable<ReplaceMode>)Enum.GetValues(typeof(ReplaceMode))).Select(x =>
new ReplaceOperationMode(
x,
x.GetAttribute<DisplayNameAttribute>().DisplayName,
x.GetAttribute<DescriptionAttribute>().Description
)).ToArray();
}
public enum ReplaceMode
{
[DisplayName(nameof(Pattern)), Description("Replaces the specified pattern.")]
Pattern,
[DisplayName(nameof(Characters)), Description("Replaces each of the specified characters.")]
Characters,
[DisplayName("From N to N"), Description("Replaces everything in the specified range (zero-based indeces).")]
FromNToN,
[DisplayName("First N"), Description("Replaces the first N characters.")]
FirstN,
[DisplayName("Last N"), Description("Replaces the last N characters.")]
LastN,
}
}