Implement Illegal Char Replace dialog in Avalonia

This commit is contained in:
Michael Bucari-Tovo 2022-12-11 15:47:04 -07:00
parent dfedb23efd
commit 48e2d91fc8
11 changed files with 235 additions and 117 deletions

View File

@ -15,7 +15,7 @@ namespace FileManager
[JsonIgnore] public bool Mandatory { get; internal set; }
[JsonProperty] public char CharacterToReplace { get; private set; }
[JsonProperty] public string ReplacementString { get; set; }
[JsonProperty] public string Description { get; private set; }
[JsonProperty] public string Description { get; set; }
public override string ToString() => $"{CharacterToReplace} → {ReplacementString} ({Description})";
public Replacement(char charToReplace, string replacementString, string description)
@ -24,7 +24,7 @@ namespace FileManager
ReplacementString = replacementString;
Description = description;
}
private Replacement(char charToReplace, string replacementString, string description, bool mandatory)
private Replacement(char charToReplace, string replacementString, string description, bool mandatory = false)
: this(charToReplace, replacementString, description)
{
Mandatory = mandatory;
@ -169,9 +169,9 @@ namespace FileManager
public static bool ContainsInvalidPathChar(string path)
=> path.Any(c => invalidChars.Contains(c));
=> path.Any(c => invalidChars?.Contains(c) == true);
public static bool ContainsInvalidFilenameChar(string path)
=> path.Any(c => invalidChars.Concat(new char[] { '\\', '/' }).Contains(c));
=> path.Any(c => invalidChars?.Concat(new char[] { '\\', '/' })?.Contains(c) == true);
public string ReplaceInvalidFilenameChars(string fileName)
{

View File

@ -2,60 +2,68 @@
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" d:DesignWidth="800" d:DesignHeight="450"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
MinWidth="500" MinHeight="450"
x:Class="LibationAvalonia.Dialogs.EditReplacementChars"
Title="EditReplacementChars">
Title="Illegal Character Replacement"
Icon="/Assets/libation.ico">
<DataGrid
GridLinesVisibility="All"
AutoGenerateColumns="False"
Items="{Binding replacements}">
<Grid
RowDefinitions="*,Auto"
ColumnDefinitions="*,Auto">
<DataGrid.Columns>
<DataGrid
Grid.Row="0"
Grid.ColumnSpan="2"
GridLinesVisibility="All"
Margin="5"
Name="replacementGrid"
AutoGenerateColumns="False"
IsReadOnly="False"
Items="{Binding replacements}">
<DataGridTemplateColumn Width="Auto" Header="Char to&#xa;Replace">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextPresenter
Height="18"
Margin="10,0,10,0"
VerticalAlignment="Center"
FontFamily="SEGOEUI_Local"
Text="{Binding Replacement.CharacterToReplace}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGrid.Columns>
<DataGridTemplateColumn IsReadOnly="False" Width="Auto" Header="Replacement Text">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid RowDefinitions="*" ColumnDefinitions="*">
<DataGridTextColumn
IsReadOnly="False"
Binding="{Binding CharacterToReplace, Mode=TwoWay}"
Header="Char to&#xa;Replace"/>
<TextBox
Grid.Column="0"
Grid.Row="0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
FontSize="14"
FontFamily="SEGOEUI_Local"
Foreground="{StaticResource SystemControlTransparentBrush}"
SelectionBrush="{StaticResource SystemControlTransparentBrush}"
BorderBrush="{StaticResource SystemControlTransparentBrush}"
Text="{Binding ReplacementText, Mode=TwoWay}" />
<TextBlock
Grid.Column="0"
Grid.Row="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
FontSize="14"
FontFamily="SEGOEUI_Local"
Text="{Binding ReplacementText}" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn
IsReadOnly="False"
Binding="{Binding ReplacementText, Mode=TwoWay}"
Header="Replacement&#xa;Text"/>
<DataGridTextColumn Width="*"
IsReadOnly="False"
Binding="{Binding Description, Mode=TwoWay}"
Header="Description"/>
</DataGrid.Columns>
</DataGrid>
<StackPanel
Grid.Row="1"
Grid.Column="0"
Margin="5"
Orientation="Horizontal">
<Button Margin="0,0,10,0" Click="Defaults_Click" Content="Defaults" />
<Button Margin="0,0,10,0" Click="LoFiDefaults_Click" Content="LoFi Defaults" />
<Button Click="Barebones_Click" Content="Barebones" />
</StackPanel>
<StackPanel
Grid.Row="1"
Grid.Column="1"
Margin="5"
Orientation="Horizontal">
<Button Margin="0,0,10,0" Click="Cancel_Click" Content="Cancel" />
<Button Padding="20,5,20,6" Click="Save_Click" Content="Save" />
</StackPanel>
</Grid>
</DataGrid.Columns>
</DataGrid>
</Window>

View File

@ -1,54 +1,185 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using FileManager;
using LibationFileManager;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ReactiveUI;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Data;
namespace LibationAvalonia.Dialogs
{
public partial class EditReplacementChars : DialogWindow
{
Configuration config = Configuration.Instance;
public ObservableCollection<ReplacementsExt> replacements { get; }
Configuration config;
private readonly List<ReplacementsExt> SOURCE = new();
public DataGridCollectionView replacements { get; }
public EditReplacementChars()
{
InitializeComponent();
if (Design.IsDesignMode)
AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists();
replacements = new(config.ReplacementCharacters.Replacements.Select(r => new ReplacementsExt { Replacement = r }));
replacements = new(SOURCE);
if (Design.IsDesignMode)
{
LoadTable(ReplacementCharacters.Default.Replacements);
}
DataContext = this;
replacementGrid = this.FindControl<DataGrid>(nameof(replacementGrid));
replacementGrid.BeginningEdit += ReplacementGrid_BeginningEdit;
replacementGrid.CellEditEnding += ReplacementGrid_CellEditEnding;
replacementGrid.KeyDown += ReplacementGrid_KeyDown;
}
public EditReplacementChars(Configuration config) : this()
{
this.config = config;
LoadTable(config.ReplacementCharacters.Replacements);
}
public void Defaults_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> LoadTable(ReplacementCharacters.Default.Replacements);
public void LoFiDefaults_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> LoadTable(ReplacementCharacters.LoFiDefault.Replacements);
public void Barebones_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> LoadTable(ReplacementCharacters.Barebones.Replacements);
public void Save_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> SaveAndClose();
public void Cancel_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> Close();
protected override void SaveAndClose()
{
var replacements = SOURCE
.Where(r=> !r.IsDefault)
.Select(r => new Replacement(r.Character, r.ReplacementText, r.Description))
.ToList();
if (config is not null)
config.ReplacementCharacters = new ReplacementCharacters { Replacements = replacements };
base.SaveAndClose();
}
private void LoadTable(IReadOnlyList<Replacement> replacements)
{
SOURCE.Clear();
SOURCE.AddRange(replacements.Select(r => new ReplacementsExt(r)));
SOURCE.Add(new ReplacementsExt());
this.replacements.Refresh();
}
private void ReplacementGrid_KeyDown(object sender, Avalonia.Input.KeyEventArgs e)
{
if (e.Key == Avalonia.Input.Key.Delete
&& replacementGrid.SelectedItem is ReplacementsExt repl
&& !repl.Mandatory
&& !repl.IsDefault)
{
replacements.Remove(repl);
}
}
private void ReplacementGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
var replacement = e.Row.DataContext as ReplacementsExt;
var colBinding = columnBindingPath(e.Column);
//Prevent duplicate CharacterToReplace
if (e.EditingElement is TextBox tbox
&& colBinding == nameof(replacement.CharacterToReplace)
&& SOURCE.Any(r => r != replacement && r.CharacterToReplace == tbox.Text))
{
tbox.Text = replacement.CharacterToReplace;
}
//Add new blank row
void Replacement_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (!SOURCE.Any(r => r.IsDefault))
{
var rewRepl = new ReplacementsExt();
SOURCE.Add(rewRepl);
}
replacement.PropertyChanged -= Replacement_PropertyChanged;
}
replacement.PropertyChanged += Replacement_PropertyChanged;
}
private void ReplacementGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
var replacement = e.Row.DataContext as ReplacementsExt;
//Disallow editing of Mandatory CharacterToReplace and Descriptions
if (replacement.Mandatory
&& columnBindingPath(e.Column) != nameof(replacement.ReplacementText))
e.Cancel = true;
}
private static string columnBindingPath(DataGridColumn column)
=> ((Binding)((DataGridBoundColumn)column).Binding).Path;
public class ReplacementsExt : ViewModels.ViewModelBase
{
public Replacement Replacement { get; init; }
public ReplacementsExt()
{
_replacementText = string.Empty;
_description = string.Empty;
_characterToReplace = string.Empty;
IsDefault = true;
}
public ReplacementsExt(Replacement replacement)
{
_characterToReplace = replacement.CharacterToReplace == default ? "" : replacement.CharacterToReplace.ToString();
_replacementText = replacement.ReplacementString;
_description = replacement.Description;
Mandatory = replacement.Mandatory;
}
private string _replacementText;
private string _description;
private string _characterToReplace;
public bool Mandatory { get; }
public string ReplacementText
{
get => Replacement.ReplacementString;
get => _replacementText;
set
{
Replacement.ReplacementString = value;
this.RaisePropertyChanged(nameof(ReplacementText));
if (ReplacementCharacters.ContainsInvalidPathChar(value))
this.RaisePropertyChanged(nameof(ReplacementText));
else
this.RaiseAndSetIfChanged(ref _replacementText, value);
}
}
public string Description { get => _description; set => this.RaiseAndSetIfChanged(ref _description, value); }
public string CharacterToReplace
{
get => _characterToReplace;
set
{
if (value?.Length != 1 || !ReplacementCharacters.ContainsInvalidPathChar(value))
this.RaisePropertyChanged(nameof(CharacterToReplace));
else
{
IsDefault = false;
this.RaiseAndSetIfChanged(ref _characterToReplace, value);
}
}
}
public char Character => string.IsNullOrEmpty(_characterToReplace) ? default : _characterToReplace[0];
public bool IsDefault { get; private set; }
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void LoadTable(IReadOnlyList<Replacement> replacements)
{
}
}
}

View File

@ -23,12 +23,12 @@
<TabControl Grid.Column="0">
<TabControl.Styles>
<Style Selector="ItemsPresenter#PART_ItemsPresenter">
<Setter Property="Height" Value="18"/>
<Setter Property="Height" Value="28"/>
</Style>
<Style Selector="TabItem">
<Setter Property="MinHeight" Value="30"/>
<Setter Property="Height" Value="30"/>
<Setter Property="Padding" Value="8,2,8,0"/>
<Setter Property="MinHeight" Value="40"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Padding" Value="8,2,8,10"/>
</Style>
<Style Selector="TabItem#Header TextBlock">
<Setter Property="MinHeight" Value="5"/>
@ -344,7 +344,6 @@
<Button
Grid.Row="6"
Grid.Column="0"
IsEnabled="False"
Content="{Binding DownloadDecryptSettings.EditCharReplacementText}"
Height="30"
Padding="30,3,30,3"

View File

@ -69,13 +69,10 @@ namespace LibationAvalonia.Dialogs
settingsDisp.DownloadDecryptSettings.ChapterFileTemplate = newTemplate;
}
public void EditCharReplacementButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
public async void EditCharReplacementButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
/*
var form = new LibationAvalonia.Dialogs.EditReplacementChars(config);
form.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
form.ShowDialog();
*/
var form = new EditReplacementChars(config);
await form.ShowDialog<DialogResult>(this);
}
public async void EditChapterTitleTemplateButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)

View File

@ -83,6 +83,7 @@
<None Remove="Assets\SEGOEUI.TTF" />
<None Remove="Assets\up.png" />
<None Remove="Assets\WINGDING.TTF" />
<None Remove="Dialogs\SettingsDialog.axaml.cs~RF142224ff.TMP" />
</ItemGroup>
<ItemGroup>
@ -92,21 +93,6 @@
</ItemGroup>
<ItemGroup>
<Compile Update="Dialogs\LiberatedStatusBatchAutoDialog.axaml.cs">
<DependentUpon>LiberatedStatusBatchAutoDialog.axaml</DependentUpon>
</Compile>
<Compile Update="Dialogs\LiberatedStatusBatchManualDialog.axaml.cs">
<DependentUpon>LiberatedStatusBatchManualDialog.axaml</DependentUpon>
</Compile>
<Compile Update="Views\ProcessBookControl.axaml.cs">
<DependentUpon>ProcessBookControl.axaml</DependentUpon>
</Compile>
<Compile Update="Views\ProcessQueueControl.axaml.cs">
<DependentUpon>ProcessQueueControl.axaml</DependentUpon>
</Compile>
<Compile Update="Views\ProductsDisplay.axaml.cs">
<DependentUpon>ProductsDisplay.axaml</DependentUpon>
</Compile>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>

View File

@ -18,7 +18,6 @@ namespace LibationAvalonia.ViewModels
/// <summary>Number of visible rows has changed</summary>
public event EventHandler<int> VisibleCountChanged;
public event EventHandler<int> RemovableCountChanged;
public event EventHandler InitialLoaded;
/// <summary>Backing list of all grid entries</summary>
private readonly List<GridEntry> SOURCE = new();
@ -45,6 +44,9 @@ namespace LibationAvalonia.ViewModels
{
GridEntries = new(SOURCE);
GridEntries.Filter = CollectionFilter;
GridEntries.CollectionChanged += (s, e)
=> VisibleCountChanged?.Invoke(this, GridEntries.OfType<LibraryBookEntry>().Count());
}
#region Display Functions
@ -131,10 +133,13 @@ namespace LibationAvalonia.ViewModels
if (searchString == FilterString)
return;
FilteredInGridEntries = QueryResults(SOURCE, searchString);
FilterString = searchString;
if (SOURCE.Count == 0)
return;
FilteredInGridEntries = QueryResults(SOURCE, searchString);
await Dispatcher.UIThread.InvokeAsync(GridEntries.Refresh);
}
@ -166,7 +171,6 @@ namespace LibationAvalonia.ViewModels
#endregion
#region Scan and Remove Books
public void DoneRemovingBooks()

View File

@ -60,11 +60,5 @@ namespace LibationAvalonia.Views
public async void editQuickFiltersToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
=> await new Dialogs.EditQuickFilters().ShowDialog(this);
public async void ProductsDisplay_Initialized(object sender, EventArgs e)
{
if (QuickFilters.UseDefault)
await performFilter(QuickFilters.Filters.FirstOrDefault());
}
}
}

View File

@ -181,7 +181,6 @@
<!-- Product Display Grid -->
<views:ProductsDisplay
Name="productsDisplay"
Initialized="ProductsDisplay_Initialized1"
DataContext="{Binding ProductsDisplay}"
LiberateClicked="ProductsDisplay_LiberateClicked"/>
</SplitView>

View File

@ -10,6 +10,8 @@ using DataLayer;
using System.Collections.Generic;
using System.Threading.Tasks;
using AppScaffolding;
using System.Linq;
using LibationAvalonia.Dialogs;
namespace LibationAvalonia.Views
{
@ -46,7 +48,6 @@ namespace LibationAvalonia.Views
// misc which belongs in winforms app but doesn't have a UI element
Configure_NonUI();
_viewModel.ProductsDisplay.InitialLoaded += ProductsDisplay_Initialized;
_viewModel.ProductsDisplay.RemovableCountChanged += ProductsDisplay_RemovableCountChanged;
_viewModel.ProductsDisplay.VisibleCountChanged += ProductsDisplay_VisibleCountChanged;
@ -67,6 +68,8 @@ namespace LibationAvalonia.Views
private async void MainWindow_Opened(object sender, EventArgs e)
{
var dialog = new EditReplacementChars();
await dialog.ShowDialog(this);
#if !DEBUG
//This is temporaty until we have a solution for linux/mac so that
//Libation doesn't download a zip every time it runs.
@ -172,13 +175,11 @@ namespace LibationAvalonia.Views
Environment.Exit(0);
}
public void ProductsDisplay_Initialized1(object sender, EventArgs e)
{
}
private async void MainWindow_LibraryLoaded(object sender, List<LibraryBook> dbBooks)
{
if (QuickFilters.UseDefault)
await performFilter(QuickFilters.Filters.FirstOrDefault());
await _viewModel.ProductsDisplay.DisplayBooks(dbBooks);
}

View File

@ -223,7 +223,6 @@ namespace LibationAvalonia.Views
if (sender is not Image tblock || tblock.DataContext is not GridEntry gEntry)
return;
if (imageDisplayDialog is null || !imageDisplayDialog.IsVisible)
{
imageDisplayDialog = new ImageDisplayDialog();