Remove GridEntry derrived types and interfaces

Use existing BaseUtil.LoadImage delegate, obviating need for derrived classes to load images

Since GridEntry types are no longer generic, interfaces are unnecessary and deleted.
This commit is contained in:
Michael Bucari-Tovo 2025-07-21 10:47:10 -06:00
parent 657a7bb6bc
commit bff9b67b72
33 changed files with 214 additions and 279 deletions

View File

@ -9,7 +9,7 @@ namespace LibationAvalonia.Controls
{ {
//Only SeriesEntry types have three-state checks, individual LibraryEntry books are binary. //Only SeriesEntry types have three-state checks, individual LibraryEntry books are binary.
var ele = base.GenerateEditingElementDirect(cell, dataItem) as CheckBox; var ele = base.GenerateEditingElementDirect(cell, dataItem) as CheckBox;
ele.IsThreeState = dataItem is ISeriesEntry; ele.IsThreeState = dataItem is SeriesEntry;
return ele; return ele;
} }
} }

View File

@ -34,11 +34,11 @@ namespace LibationAvalonia.Controls
private static void Cell_ContextRequested(object sender, ContextRequestedEventArgs e) private static void Cell_ContextRequested(object sender, ContextRequestedEventArgs e)
{ {
if (sender is DataGridCell cell && if (sender is DataGridCell cell &&
cell.DataContext is IGridEntry clickedEntry && cell.DataContext is GridEntry clickedEntry &&
OwningColumnProperty.GetValue(cell) is DataGridColumn column && OwningColumnProperty.GetValue(cell) is DataGridColumn column &&
OwningGridProperty.GetValue(column) is DataGrid grid) OwningGridProperty.GetValue(column) is DataGrid grid)
{ {
var allSelected = grid.SelectedItems.OfType<IGridEntry>().ToArray(); var allSelected = grid.SelectedItems.OfType<GridEntry>().ToArray();
var clickedIndex = Array.IndexOf(allSelected, clickedEntry); var clickedIndex = Array.IndexOf(allSelected, clickedEntry);
if (clickedIndex == -1) if (clickedIndex == -1)
{ {
@ -101,7 +101,7 @@ namespace LibationAvalonia.Controls
private static string RemoveLineBreaks(string text) private static string RemoveLineBreaks(string text)
=> text.Replace("\r\n", "").Replace('\r', ' ').Replace('\n', ' '); => text.Replace("\r\n", "").Replace('\r', ' ').Replace('\n', ' ');
private string GetRowClipboardContents(IGridEntry gridEntry) private string GetRowClipboardContents(GridEntry gridEntry)
{ {
var contents = Grid.Columns.Where(c => c.IsVisible).OrderBy(c => c.DisplayIndex).Select(c => RemoveLineBreaks(GetCellValue(c, gridEntry))).ToArray(); var contents = Grid.Columns.Where(c => c.IsVisible).OrderBy(c => c.DisplayIndex).Select(c => RemoveLineBreaks(GetCellValue(c, gridEntry))).ToArray();
return string.Join("\t", contents); return string.Join("\t", contents);
@ -109,7 +109,7 @@ namespace LibationAvalonia.Controls
public required DataGrid Grid { get; init; } public required DataGrid Grid { get; init; }
public required DataGridColumn Column { get; init; } public required DataGridColumn Column { get; init; }
public required IGridEntry[] GridEntries { get; init; } public required GridEntry[] GridEntries { get; init; }
public required ContextMenu ContextMenu { get; init; } public required ContextMenu ContextMenu { get; init; }
public AvaloniaList<Control> ContextMenuItems public AvaloniaList<Control> ContextMenuItems
=> ContextMenu.ItemsSource as AvaloniaList<Control>; => ContextMenu.ItemsSource as AvaloniaList<Control>;

View File

@ -1,20 +0,0 @@
using Avalonia.Media.Imaging;
using DataLayer;
using LibationUiBase.GridView;
using System;
#nullable enable
namespace LibationAvalonia.ViewModels
{
public class AvaloniaEntryStatus : EntryStatus, IEntryStatus, IComparable
{
private AvaloniaEntryStatus(LibraryBook libraryBook) : base(libraryBook) { }
public static EntryStatus Create(LibraryBook libraryBook) => new AvaloniaEntryStatus(libraryBook);
protected override Bitmap LoadImage(byte[] picture)
=> AvaloniaUtils.TryLoadImageOrDefault(picture, LibationFileManager.PictureSize._80x80);
//Button icons are handled by LiberateStatusButton
protected override Bitmap? GetResourceImage(string rescName) => null;
}
}

View File

@ -57,7 +57,7 @@ namespace LibationAvalonia.ViewModels
} }
} }
public void LiberateSeriesClicked(ISeriesEntry series) public void LiberateSeriesClicked(SeriesEntry series)
{ {
try try
{ {

View File

@ -1,5 +1,6 @@
using LibationFileManager; using LibationFileManager;
using LibationUiBase; using LibationUiBase;
using System;
using System.IO; using System.IO;
#nullable enable #nullable enable
@ -23,6 +24,20 @@ namespace LibationAvalonia.ViewModels
PictureStorage.SetDefaultImage(PictureSize.Native, ms3.ToArray()); PictureStorage.SetDefaultImage(PictureSize.Native, ms3.ToArray());
BaseUtil.SetLoadImageDelegate(AvaloniaUtils.TryLoadImageOrDefault); BaseUtil.SetLoadImageDelegate(AvaloniaUtils.TryLoadImageOrDefault);
BaseUtil.SetLoadResourceImageDelegate(LoadResourceImage);
}
private static Avalonia.Media.Imaging.Bitmap? LoadResourceImage(string resourceName)
{
try
{
using var stream = App.OpenAsset(resourceName);
return new Avalonia.Media.Imaging.Bitmap(stream);
}
catch (Exception ex)
{
Serilog.Log.Error(ex, "Failed to load resource image: {ResourceName}", resourceName);
return null;
}
} }
} }
} }

View File

@ -26,9 +26,9 @@ namespace LibationAvalonia.ViewModels
public event EventHandler<int>? RemovableCountChanged; public event EventHandler<int>? RemovableCountChanged;
/// <summary>Backing list of all grid entries</summary> /// <summary>Backing list of all grid entries</summary>
private readonly AvaloniaList<IGridEntry> SOURCE = new(); private readonly AvaloniaList<GridEntry> SOURCE = new();
/// <summary>Grid entries included in the filter set. If null, all grid entries are shown</summary> /// <summary>Grid entries included in the filter set. If null, all grid entries are shown</summary>
private HashSet<IGridEntry>? FilteredInGridEntries; private HashSet<GridEntry>? FilteredInGridEntries;
public string? FilterString { get; private set; } public string? FilterString { get; private set; }
private DataGridCollectionView? _gridEntries; private DataGridCollectionView? _gridEntries;
@ -43,15 +43,15 @@ namespace LibationAvalonia.ViewModels
public List<LibraryBook> GetVisibleBookEntries() public List<LibraryBook> GetVisibleBookEntries()
=> FilteredInGridEntries? => FilteredInGridEntries?
.OfType<ILibraryBookEntry>() .OfType<LibraryBookEntry>()
.Select(lbe => lbe.LibraryBook) .Select(lbe => lbe.LibraryBook)
.ToList() .ToList()
?? SOURCE ?? SOURCE
.OfType<ILibraryBookEntry>() .OfType<LibraryBookEntry>()
.Select(lbe => lbe.LibraryBook) .Select(lbe => lbe.LibraryBook)
.ToList(); .ToList();
private IEnumerable<ILibraryBookEntry> GetAllBookEntries() private IEnumerable<LibraryBookEntry> GetAllBookEntries()
=> SOURCE => SOURCE
.BookEntries(); .BookEntries();
@ -112,8 +112,8 @@ namespace LibationAvalonia.ViewModels
var sc = await Dispatcher.UIThread.InvokeAsync(() => AvaloniaSynchronizationContext.Current); var sc = await Dispatcher.UIThread.InvokeAsync(() => AvaloniaSynchronizationContext.Current);
AvaloniaSynchronizationContext.SetSynchronizationContext(sc); AvaloniaSynchronizationContext.SetSynchronizationContext(sc);
var geList = await LibraryBookEntry<AvaloniaEntryStatus>.GetAllProductsAsync(dbBooks); var geList = await LibraryBookEntry.GetAllProductsAsync(dbBooks);
var seriesEntries = await SeriesEntry<AvaloniaEntryStatus>.GetAllSeriesEntriesAsync(dbBooks); var seriesEntries = await SeriesEntry.GetAllSeriesEntriesAsync(dbBooks);
//Add all IGridEntries to the SOURCE list. Note that SOURCE has not yet been linked to the UI via //Add all IGridEntries to the SOURCE list. Note that SOURCE has not yet been linked to the UI via
//the GridEntries property, so adding items to SOURCE will not trigger any refreshes or UI action. //the GridEntries property, so adding items to SOURCE will not trigger any refreshes or UI action.
@ -147,8 +147,8 @@ namespace LibationAvalonia.ViewModels
private void GridEntries_CollectionChanged(object? sender = null, EventArgs? e = null) private void GridEntries_CollectionChanged(object? sender = null, EventArgs? e = null)
{ {
var count var count
= FilteredInGridEntries?.OfType<ILibraryBookEntry>().Count() = FilteredInGridEntries?.OfType<LibraryBookEntry>().Count()
?? SOURCE.OfType<ILibraryBookEntry>().Count(); ?? SOURCE.OfType<LibraryBookEntry>().Count();
VisibleCountChanged?.Invoke(this, count); VisibleCountChanged?.Invoke(this, count);
} }
@ -223,9 +223,9 @@ namespace LibationAvalonia.ViewModels
GridEntries_CollectionChanged(); GridEntries_CollectionChanged();
} }
private void RemoveBooks(IEnumerable<ILibraryBookEntry> removedBooks, IEnumerable<ISeriesEntry> removedSeries) private void RemoveBooks(IEnumerable<LibraryBookEntry> removedBooks, IEnumerable<SeriesEntry> removedSeries)
{ {
foreach (var removed in removedBooks.Cast<IGridEntry>().Concat(removedSeries).Where(b => b is not null).ToList()) foreach (var removed in removedBooks.Cast<GridEntry>().Concat(removedSeries).Where(b => b is not null).ToList())
{ {
if (GridEntries?.PassesFilter(removed) ?? false) if (GridEntries?.PassesFilter(removed) ?? false)
GridEntries.Remove(removed); GridEntries.Remove(removed);
@ -238,21 +238,21 @@ namespace LibationAvalonia.ViewModels
} }
} }
private void UpsertBook(LibraryBook book, ILibraryBookEntry? existingBookEntry) private void UpsertBook(LibraryBook book, LibraryBookEntry? existingBookEntry)
{ {
if (existingBookEntry is null) if (existingBookEntry is null)
// Add the new product to top // Add the new product to top
SOURCE.Insert(0, new LibraryBookEntry<AvaloniaEntryStatus>(book)); SOURCE.Insert(0, new LibraryBookEntry(book));
else else
// update existing // update existing
existingBookEntry.UpdateLibraryBook(book); existingBookEntry.UpdateLibraryBook(book);
} }
private void UpsertEpisode(LibraryBook episodeBook, ILibraryBookEntry? existingEpisodeEntry, List<ISeriesEntry> seriesEntries, IEnumerable<LibraryBook> dbBooks) private void UpsertEpisode(LibraryBook episodeBook, LibraryBookEntry? existingEpisodeEntry, List<SeriesEntry> seriesEntries, IEnumerable<LibraryBook> dbBooks)
{ {
if (existingEpisodeEntry is null) if (existingEpisodeEntry is null)
{ {
ILibraryBookEntry episodeEntry; LibraryBookEntry episodeEntry;
var seriesEntry = seriesEntries.FindSeriesParent(episodeBook); var seriesEntry = seriesEntries.FindSeriesParent(episodeBook);
@ -270,7 +270,7 @@ namespace LibationAvalonia.ViewModels
return; return;
} }
seriesEntry = new SeriesEntry<AvaloniaEntryStatus>(seriesBook, episodeBook); seriesEntry = new SeriesEntry(seriesBook, episodeBook);
seriesEntries.Add(seriesEntry); seriesEntries.Add(seriesEntry);
episodeEntry = seriesEntry.Children[0]; episodeEntry = seriesEntry.Children[0];
@ -280,7 +280,7 @@ namespace LibationAvalonia.ViewModels
else else
{ {
//Series exists. Create and add episode child then update the SeriesEntry //Series exists. Create and add episode child then update the SeriesEntry
episodeEntry = new LibraryBookEntry<AvaloniaEntryStatus>(episodeBook, seriesEntry); episodeEntry = new LibraryBookEntry(episodeBook, seriesEntry);
seriesEntry.Children.Add(episodeEntry); seriesEntry.Children.Add(episodeEntry);
seriesEntry.Children.Sort((c1, c2) => c1.SeriesIndex.CompareTo(c2.SeriesIndex)); seriesEntry.Children.Sort((c1, c2) => c1.SeriesIndex.CompareTo(c2.SeriesIndex));
var seriesBook = dbBooks.Single(lb => lb.Book.AudibleProductId == seriesEntry.LibraryBook.Book.AudibleProductId); var seriesBook = dbBooks.Single(lb => lb.Book.AudibleProductId == seriesEntry.LibraryBook.Book.AudibleProductId);
@ -307,7 +307,7 @@ namespace LibationAvalonia.ViewModels
} }
} }
public async Task ToggleSeriesExpanded(ISeriesEntry seriesEntry) public async Task ToggleSeriesExpanded(SeriesEntry seriesEntry)
{ {
seriesEntry.Liberate.Expanded = !seriesEntry.Liberate.Expanded; seriesEntry.Liberate.Expanded = !seriesEntry.Liberate.Expanded;
@ -332,7 +332,7 @@ namespace LibationAvalonia.ViewModels
private bool CollectionFilter(object item) private bool CollectionFilter(object item)
{ {
if (item is ILibraryBookEntry lbe if (item is LibraryBookEntry lbe
&& lbe.Liberate.IsEpisode && lbe.Liberate.IsEpisode
&& lbe.Parent?.Liberate?.Expanded != true) && lbe.Parent?.Liberate?.Expanded != true)
return false; return false;
@ -454,7 +454,7 @@ namespace LibationAvalonia.ViewModels
private void GridEntry_PropertyChanged(object? sender, PropertyChangedEventArgs? e) private void GridEntry_PropertyChanged(object? sender, PropertyChangedEventArgs? e)
{ {
if (e?.PropertyName == nameof(IGridEntry.Remove) && sender is ILibraryBookEntry) if (e?.PropertyName == nameof(GridEntry.Remove) && sender is LibraryBookEntry)
{ {
int removeCount = GetAllBookEntries().Count(lbe => lbe.Remove is true); int removeCount = GetAllBookEntries().Count(lbe => lbe.Remove is true);
RemovableCountChanged?.Invoke(this, removeCount); RemovableCountChanged?.Invoke(this, removeCount);

View File

@ -17,7 +17,7 @@ namespace LibationAvalonia.ViewModels
public RowComparer(DataGridColumn? column) public RowComparer(DataGridColumn? column)
{ {
Column = column; Column = column;
PropertyName = Column?.SortMemberPath ?? nameof(IGridEntry.DateAdded); PropertyName = Column?.SortMemberPath ?? nameof(GridEntry.DateAdded);
} }
//Avalonia doesn't expose the column's CurrentSortingState, so we must get it through reflection //Avalonia doesn't expose the column's CurrentSortingState, so we must get it through reflection

View File

@ -53,8 +53,8 @@ namespace LibationAvalonia.Views
private void LiberateStatusButton_DataContextChanged(object sender, EventArgs e) private void LiberateStatusButton_DataContextChanged(object sender, EventArgs e)
{ {
//Force book status recheck when an entry is scrolled into view. //Force book status recheck when an entry is scrolled into view.
//This will force a recheck for a paprtially downloaded file. //This will force a recheck for a partially downloaded file.
var status = DataContext as ILibraryBookEntry; var status = DataContext as LibraryBookEntry;
status?.Liberate.Invalidate(nameof(status.Liberate.BookStatus)); status?.Liberate.Invalidate(nameof(status.Liberate.BookStatus));
} }

View File

@ -137,7 +137,7 @@ namespace LibationAvalonia.Views
} }
public void ProductsDisplay_LiberateClicked(object _, LibraryBook[] libraryBook) => ViewModel.LiberateClicked(libraryBook); public void ProductsDisplay_LiberateClicked(object _, LibraryBook[] libraryBook) => ViewModel.LiberateClicked(libraryBook);
public void ProductsDisplay_LiberateSeriesClicked(object _, ISeriesEntry series) => ViewModel.LiberateSeriesClicked(series); public void ProductsDisplay_LiberateSeriesClicked(object _, SeriesEntry series) => ViewModel.LiberateSeriesClicked(series);
public void ProductsDisplay_ConvertToMp3Clicked(object _, LibraryBook[] libraryBook) => ViewModel.ConvertToMp3Clicked(libraryBook); public void ProductsDisplay_ConvertToMp3Clicked(object _, LibraryBook[] libraryBook) => ViewModel.ConvertToMp3Clicked(libraryBook);
BookDetailsDialog bookDetailsForm; BookDetailsDialog bookDetailsForm;

View File

@ -59,7 +59,7 @@
Width="75"> Width="75">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:GridEntry">
<CheckBox <CheckBox
HorizontalAlignment="Center" HorizontalAlignment="Center"
IsThreeState="True" IsThreeState="True"
@ -70,7 +70,7 @@
<controls:DataGridTemplateColumnExt CanUserResize="False" CanUserSort="True" Header="Liberate" SortMemberPath="Liberate" ClipboardContentBinding="{Binding Liberate.ToolTip}"> <controls:DataGridTemplateColumnExt CanUserResize="False" CanUserSort="True" Header="Liberate" SortMemberPath="Liberate" ClipboardContentBinding="{Binding Liberate.ToolTip}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:GridEntry">
<views:LiberateStatusButton <views:LiberateStatusButton
ToolTip.Tip="{CompiledBinding Liberate.ToolTip}" ToolTip.Tip="{CompiledBinding Liberate.ToolTip}"
BookStatus="{CompiledBinding Liberate.BookStatus}" BookStatus="{CompiledBinding Liberate.BookStatus}"
@ -85,7 +85,7 @@
<controls:DataGridTemplateColumnExt CanUserResize="False" CanUserSort="False" Header="Cover" SortMemberPath="Cover" ClipboardContentBinding="{Binding LibraryBook.Book.PictureLarge}"> <controls:DataGridTemplateColumnExt CanUserResize="False" CanUserSort="False" Header="Cover" SortMemberPath="Cover" ClipboardContentBinding="{Binding LibraryBook.Book.PictureLarge}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:GridEntry">
<Image Opacity="{CompiledBinding Liberate.Opacity}" Tapped="Cover_Click" Source="{CompiledBinding Cover}" ToolTip.Tip="Click to see full size" /> <Image Opacity="{CompiledBinding Liberate.Opacity}" Tapped="Cover_Click" Source="{CompiledBinding Cover}" ToolTip.Tip="Click to see full size" />
</DataTemplate> </DataTemplate>
</DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn.CellTemplate>
@ -93,7 +93,7 @@
<controls:DataGridTemplateColumnExt Header="Title" MinWidth="10" Width="{Binding TitleWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Title" ClipboardContentBinding="{Binding Title}"> <controls:DataGridTemplateColumnExt Header="Title" MinWidth="10" Width="{Binding TitleWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Title" ClipboardContentBinding="{Binding Title}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:GridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}">
<TextBlock Classes="h1" Text="{CompiledBinding Title}" /> <TextBlock Classes="h1" Text="{CompiledBinding Title}" />
</Panel> </Panel>
@ -103,7 +103,7 @@
<controls:DataGridTemplateColumnExt Header="Authors" MinWidth="10" Width="{Binding AuthorsWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Authors" ClipboardContentBinding="{Binding Authors}"> <controls:DataGridTemplateColumnExt Header="Authors" MinWidth="10" Width="{Binding AuthorsWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Authors" ClipboardContentBinding="{Binding Authors}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:GridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}">
<TextBlock Text="{CompiledBinding Authors}" /> <TextBlock Text="{CompiledBinding Authors}" />
</Panel> </Panel>
@ -113,7 +113,7 @@
<controls:DataGridTemplateColumnExt Header="Narrators" MinWidth="10" Width="{Binding NarratorsWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Narrators" ClipboardContentBinding="{Binding Narrators}"> <controls:DataGridTemplateColumnExt Header="Narrators" MinWidth="10" Width="{Binding NarratorsWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Narrators" ClipboardContentBinding="{Binding Narrators}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:GridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}">
<TextBlock Text="{CompiledBinding Narrators}" /> <TextBlock Text="{CompiledBinding Narrators}" />
</Panel> </Panel>
@ -123,7 +123,7 @@
<controls:DataGridTemplateColumnExt Header="Length" MinWidth="10" Width="{Binding LengthWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Length" ClipboardContentBinding="{Binding Length}"> <controls:DataGridTemplateColumnExt Header="Length" MinWidth="10" Width="{Binding LengthWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Length" ClipboardContentBinding="{Binding Length}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:GridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}">
<TextBlock Text="{CompiledBinding Length}" /> <TextBlock Text="{CompiledBinding Length}" />
</Panel> </Panel>
@ -133,7 +133,7 @@
<controls:DataGridTemplateColumnExt Header="Series" MinWidth="10" Width="{Binding SeriesWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Series" ClipboardContentBinding="{Binding Series}"> <controls:DataGridTemplateColumnExt Header="Series" MinWidth="10" Width="{Binding SeriesWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Series" ClipboardContentBinding="{Binding Series}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:GridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}">
<TextBlock Text="{CompiledBinding Series}" /> <TextBlock Text="{CompiledBinding Series}" />
</Panel> </Panel>
@ -143,7 +143,7 @@
<controls:DataGridTemplateColumnExt Header="Series&#xA;Order" MinWidth="10" Width="{Binding SeriesOrderWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="SeriesOrder" ClipboardContentBinding="{Binding Series}"> <controls:DataGridTemplateColumnExt Header="Series&#xA;Order" MinWidth="10" Width="{Binding SeriesOrderWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="SeriesOrder" ClipboardContentBinding="{Binding Series}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:GridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}">
<TextBlock Text="{CompiledBinding SeriesOrder}" HorizontalAlignment="Center" /> <TextBlock Text="{CompiledBinding SeriesOrder}" HorizontalAlignment="Center" />
</Panel> </Panel>
@ -153,7 +153,7 @@
<controls:DataGridTemplateColumnExt Header="Description" MinWidth="10" Width="{Binding DescriptionWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Description" ClipboardContentBinding="{Binding Description}"> <controls:DataGridTemplateColumnExt Header="Description" MinWidth="10" Width="{Binding DescriptionWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Description" ClipboardContentBinding="{Binding Description}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:GridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" Tapped="Description_Click" ToolTip.Tip="Click to see full description" > <Panel Opacity="{CompiledBinding Liberate.Opacity}" Tapped="Description_Click" ToolTip.Tip="Click to see full description" >
<TextBlock Text="{CompiledBinding Description}" VerticalAlignment="Top" /> <TextBlock Text="{CompiledBinding Description}" VerticalAlignment="Top" />
</Panel> </Panel>
@ -163,7 +163,7 @@
<controls:DataGridTemplateColumnExt Header="Category" MinWidth="10" Width="{Binding CategoryWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Category" ClipboardContentBinding="{Binding Category}"> <controls:DataGridTemplateColumnExt Header="Category" MinWidth="10" Width="{Binding CategoryWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Category" ClipboardContentBinding="{Binding Category}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:GridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}">
<TextBlock Text="{CompiledBinding Category}" /> <TextBlock Text="{CompiledBinding Category}" />
</Panel> </Panel>
@ -172,7 +172,7 @@
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridMyRatingColumn <controls:DataGridMyRatingColumn
x:DataType="uibase:IGridEntry" x:DataType="uibase:GridEntry"
Header="Product&#xA;Rating" Header="Product&#xA;Rating"
IsReadOnly="true" IsReadOnly="true"
MinWidth="10" Width="{Binding ProductRatingWidth, Mode=TwoWay}" MinWidth="10" Width="{Binding ProductRatingWidth, Mode=TwoWay}"
@ -183,7 +183,7 @@
<controls:DataGridTemplateColumnExt Header="Purchase&#xA;Date" MinWidth="10" Width="{Binding PurchaseDateWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="PurchaseDate" ClipboardContentBinding="{Binding PurchaseDate}"> <controls:DataGridTemplateColumnExt Header="Purchase&#xA;Date" MinWidth="10" Width="{Binding PurchaseDateWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="PurchaseDate" ClipboardContentBinding="{Binding PurchaseDate}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:GridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}">
<TextBlock Text="{CompiledBinding PurchaseDate}" /> <TextBlock Text="{CompiledBinding PurchaseDate}" />
</Panel> </Panel>
@ -192,7 +192,7 @@
</controls:DataGridTemplateColumnExt> </controls:DataGridTemplateColumnExt>
<controls:DataGridMyRatingColumn <controls:DataGridMyRatingColumn
x:DataType="uibase:IGridEntry" x:DataType="uibase:GridEntry"
Header="My Rating" Header="My Rating"
IsReadOnly="false" IsReadOnly="false"
MinWidth="10" Width="{Binding MyRatingWidth, Mode=TwoWay}" MinWidth="10" Width="{Binding MyRatingWidth, Mode=TwoWay}"
@ -203,7 +203,7 @@
<controls:DataGridTemplateColumnExt Header="Misc" MinWidth="10" Width="{Binding MiscWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Misc" ClipboardContentBinding="{Binding Misc}"> <controls:DataGridTemplateColumnExt Header="Misc" MinWidth="10" Width="{Binding MiscWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Misc" ClipboardContentBinding="{Binding Misc}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:GridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}"> <Panel Opacity="{CompiledBinding Liberate.Opacity}">
<TextBlock Text="{CompiledBinding Misc}" TextWrapping="WrapWithOverflow" /> <TextBlock Text="{CompiledBinding Misc}" TextWrapping="WrapWithOverflow" />
</Panel> </Panel>
@ -213,7 +213,7 @@
<controls:DataGridTemplateColumnExt Header="Last&#xA;Download" MinWidth="10" Width="{Binding LastDownloadWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="LastDownload" ClipboardContentBinding="{Binding LastDownload}"> <controls:DataGridTemplateColumnExt Header="Last&#xA;Download" MinWidth="10" Width="{Binding LastDownloadWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="LastDownload" ClipboardContentBinding="{Binding LastDownload}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:GridEntry">
<Panel Opacity="{CompiledBinding Liberate.Opacity}" ToolTip.Tip="{CompiledBinding LastDownload.ToolTipText}" DoubleTapped="Version_DoubleClick"> <Panel Opacity="{CompiledBinding Liberate.Opacity}" ToolTip.Tip="{CompiledBinding LastDownload.ToolTipText}" DoubleTapped="Version_DoubleClick">
<TextBlock Text="{CompiledBinding LastDownload}" TextWrapping="WrapWithOverflow" /> <TextBlock Text="{CompiledBinding LastDownload}" TextWrapping="WrapWithOverflow" />
</Panel> </Panel>
@ -223,7 +223,7 @@
<controls:DataGridTemplateColumnExt Header="Tags" MinWidth="10" Width="{Binding BookTagsWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="BookTags" ClipboardContentBinding="{Binding BookTags}"> <controls:DataGridTemplateColumnExt Header="Tags" MinWidth="10" Width="{Binding BookTagsWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="BookTags" ClipboardContentBinding="{Binding BookTags}">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="uibase:IGridEntry"> <DataTemplate x:DataType="uibase:GridEntry">
<Button <Button
IsVisible="{CompiledBinding !Liberate.IsSeries}" IsVisible="{CompiledBinding !Liberate.IsSeries}"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"

View File

@ -26,7 +26,7 @@ namespace LibationAvalonia.Views
public partial class ProductsDisplay : UserControl public partial class ProductsDisplay : UserControl
{ {
public event EventHandler<LibraryBook[]>? LiberateClicked; public event EventHandler<LibraryBook[]>? LiberateClicked;
public event EventHandler<ISeriesEntry>? LiberateSeriesClicked; public event EventHandler<SeriesEntry>? LiberateSeriesClicked;
public event EventHandler<LibraryBook[]>? ConvertToMp3Clicked; public event EventHandler<LibraryBook[]>? ConvertToMp3Clicked;
public event EventHandler<LibraryBook>? TagsButtonClicked; public event EventHandler<LibraryBook>? TagsButtonClicked;
@ -102,7 +102,7 @@ namespace LibationAvalonia.Views
private void ProductsDisplay_LoadingRow(object sender, DataGridRowEventArgs e) private void ProductsDisplay_LoadingRow(object sender, DataGridRowEventArgs e)
{ {
if (e.Row.DataContext is LibraryBookEntry<AvaloniaEntryStatus> entry && entry.Liberate.IsEpisode) if (e.Row.DataContext is LibraryBookEntry entry && entry.Liberate.IsEpisode)
e.Row.DynamicResource(DataGridRow.BackgroundProperty, "SeriesEntryGridBackgroundBrush"); e.Row.DynamicResource(DataGridRow.BackgroundProperty, "SeriesEntryGridBackgroundBrush");
else else
e.Row.DynamicResource(DataGridRow.BackgroundProperty, "SystemRegionColor"); e.Row.DynamicResource(DataGridRow.BackgroundProperty, "SystemRegionColor");
@ -173,10 +173,10 @@ namespace LibationAvalonia.Views
{ {
switch (column.SortMemberPath) switch (column.SortMemberPath)
{ {
case nameof(IGridEntry.Liberate): case nameof(GridEntry.Liberate):
column.Width = new DataGridLength(BaseLiberateWidth * scaleFactor); column.Width = new DataGridLength(BaseLiberateWidth * scaleFactor);
break; break;
case nameof(IGridEntry.Cover): case nameof(GridEntry.Cover):
column.Width = new DataGridLength(BaseCoverWidth * scaleFactor); column.Width = new DataGridLength(BaseCoverWidth * scaleFactor);
break; break;
} }
@ -220,7 +220,7 @@ namespace LibationAvalonia.Views
#region Liberate all Episodes (Single series only) #region Liberate all Episodes (Single series only)
if (entries.Length == 1 && entries[0] is ISeriesEntry seriesEntry) if (entries.Length == 1 && entries[0] is SeriesEntry seriesEntry)
{ {
args.ContextMenuItems.Add(new MenuItem() args.ContextMenuItems.Add(new MenuItem()
{ {
@ -253,7 +253,7 @@ namespace LibationAvalonia.Views
#endregion #endregion
#region Locate file (Single book only) #region Locate file (Single book only)
if (entries.Length == 1 && entries[0] is ILibraryBookEntry entry) if (entries.Length == 1 && entries[0] is LibraryBookEntry entry)
{ {
args.ContextMenuItems.Add(new MenuItem args.ContextMenuItems.Add(new MenuItem
{ {
@ -301,7 +301,7 @@ namespace LibationAvalonia.Views
#endregion #endregion
#region Liberate All (multiple books only) #region Liberate All (multiple books only)
if (entries.OfType<ILibraryBookEntry>().Count() > 1) if (entries.OfType<LibraryBookEntry>().Count() > 1)
{ {
args.ContextMenuItems.Add(new MenuItem args.ContextMenuItems.Add(new MenuItem
{ {
@ -325,7 +325,7 @@ namespace LibationAvalonia.Views
#endregion #endregion
#region Force Re-Download (Single book only) #region Force Re-Download (Single book only)
if (entries.Length == 1 && entries[0] is ILibraryBookEntry entry4) if (entries.Length == 1 && entries[0] is LibraryBookEntry entry4)
{ {
args.ContextMenuItems.Add(new MenuItem() args.ContextMenuItems.Add(new MenuItem()
{ {
@ -361,7 +361,7 @@ namespace LibationAvalonia.Views
} }
} }
if (entries.Length == 1 && entries[0] is ILibraryBookEntry entry2) if (entries.Length == 1 && entries[0] is LibraryBookEntry entry2)
{ {
args.ContextMenuItems.Add(new MenuItem args.ContextMenuItems.Add(new MenuItem
{ {
@ -391,7 +391,7 @@ namespace LibationAvalonia.Views
#endregion #endregion
#region View Bookmarks/Clips (Single book only) #region View Bookmarks/Clips (Single book only)
if (entries.Length == 1 && entries[0] is ILibraryBookEntry entry3 && VisualRoot is Window window) if (entries.Length == 1 && entries[0] is LibraryBookEntry entry3 && VisualRoot is Window window)
{ {
args.ContextMenuItems.Add(new MenuItem args.ContextMenuItems.Add(new MenuItem
{ {
@ -447,7 +447,7 @@ namespace LibationAvalonia.Views
{ {
var itemName = column.SortMemberPath; var itemName = column.SortMemberPath;
if (itemName == nameof(IGridEntry.Remove)) if (itemName == nameof(GridEntry.Remove))
continue; continue;
menuItems.Add menuItems.Add
@ -536,7 +536,7 @@ namespace LibationAvalonia.Views
if (sender is not LiberateStatusButton button) if (sender is not LiberateStatusButton button)
return; return;
if (button.DataContext is ISeriesEntry sEntry && _viewModel is not null) if (button.DataContext is SeriesEntry sEntry && _viewModel is not null)
{ {
await _viewModel.ToggleSeriesExpanded(sEntry); await _viewModel.ToggleSeriesExpanded(sEntry);
@ -544,7 +544,7 @@ namespace LibationAvalonia.Views
//to the topright cell. Reset focus onto the clicked button's cell. //to the topright cell. Reset focus onto the clicked button's cell.
button.Focus(); button.Focus();
} }
else if (button.DataContext is ILibraryBookEntry lbEntry) else if (button.DataContext is LibraryBookEntry lbEntry)
{ {
LiberateClicked?.Invoke(this, [lbEntry.LibraryBook]); LiberateClicked?.Invoke(this, [lbEntry.LibraryBook]);
} }
@ -558,13 +558,13 @@ namespace LibationAvalonia.Views
public void Version_DoubleClick(object sender, Avalonia.Input.TappedEventArgs args) public void Version_DoubleClick(object sender, Avalonia.Input.TappedEventArgs args)
{ {
if (sender is Control panel && panel.DataContext is ILibraryBookEntry lbe && lbe.LastDownload.IsValid) if (sender is Control panel && panel.DataContext is LibraryBookEntry lbe && lbe.LastDownload.IsValid)
lbe.LastDownload.OpenReleaseUrl(); lbe.LastDownload.OpenReleaseUrl();
} }
public void Cover_Click(object sender, Avalonia.Input.TappedEventArgs args) public void Cover_Click(object sender, Avalonia.Input.TappedEventArgs args)
{ {
if (sender is not Image tblock || tblock.DataContext is not IGridEntry gEntry) if (sender is not Image tblock || tblock.DataContext is not GridEntry gEntry)
return; return;
if (imageDisplayDialog is null || !imageDisplayDialog.IsVisible) if (imageDisplayDialog is null || !imageDisplayDialog.IsVisible)
@ -605,7 +605,7 @@ namespace LibationAvalonia.Views
public void Description_Click(object sender, Avalonia.Input.TappedEventArgs args) public void Description_Click(object sender, Avalonia.Input.TappedEventArgs args)
{ {
if (sender is Control tblock && tblock.DataContext is IGridEntry gEntry) if (sender is Control tblock && tblock.DataContext is GridEntry gEntry)
{ {
var pt = tblock.PointToScreen(tblock.Bounds.TopRight); var pt = tblock.PointToScreen(tblock.Bounds.TopRight);
var displayWindow = new DescriptionDisplayDialog var displayWindow = new DescriptionDisplayDialog
@ -632,7 +632,7 @@ namespace LibationAvalonia.Views
{ {
var button = args.Source as Button; var button = args.Source as Button;
if (button?.DataContext is ILibraryBookEntry lbEntry) if (button?.DataContext is LibraryBookEntry lbEntry)
{ {
TagsButtonClicked?.Invoke(this, lbEntry.LibraryBook); TagsButtonClicked?.Invoke(this, lbEntry.LibraryBook);
} }

View File

@ -1,13 +1,35 @@
using LibationFileManager; using LibationFileManager;
using System; using System;
#nullable enable
namespace LibationUiBase namespace LibationUiBase
{ {
public static class BaseUtil public static class BaseUtil
{ {
/// <summary>A delegate that loads image bytes into the the UI framework's image format.</summary> /// <summary>A delegate that loads image bytes into the the UI framework's image format.</summary>
public static Func<byte[], PictureSize, object> LoadImage { get; private set; } public static Func<byte[], PictureSize, object?> LoadImage => s_LoadImage ?? DefaultLoadImageImpl;
public static void SetLoadImageDelegate(Func<byte[], PictureSize, object> tryLoadImage)
=> LoadImage = tryLoadImage; /// <summary>A delegate that loads a named resource into the the UI framework's image format.</summary>
public static Func<string, object?> LoadResourceImage => s_LoadResourceImage ?? DefaultLoadResourceImageImpl;
public static void SetLoadImageDelegate(Func<byte[], PictureSize, object?> tryLoadImage)
=> s_LoadImage = tryLoadImage;
public static void SetLoadResourceImageDelegate(Func<string, object?> tryLoadResourceImage)
=> s_LoadResourceImage = tryLoadResourceImage;
private static Func<byte[], PictureSize, object?>? s_LoadImage;
private static Func<string, object?>? s_LoadResourceImage;
private static object? DefaultLoadImageImpl(byte[] imageBytes, PictureSize size)
{
Serilog.Log.Error("{LoadImage} called without a delegate set. Picture size: {PictureSize}", nameof(LoadImage), size);
return null;
}
private static object? DefaultLoadResourceImageImpl(string resourceName)
{
Serilog.Log.Error("{LoadResourceImage} called without a delegate set. Resource name: {ResourceName}", nameof(LoadResourceImage), resourceName);
return null;
}
} }
} }

View File

@ -1,22 +1,15 @@
using ApplicationServices; using ApplicationServices;
using DataLayer; using DataLayer;
using Dinah.Core; using Dinah.Core;
using Dinah.Core.Threading;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
namespace LibationUiBase.GridView namespace LibationUiBase.GridView
{ {
public interface IEntryStatus
{
static abstract EntryStatus Create(LibraryBook libraryBook);
}
//This Class holds all book entry status info to help the grid properly render entries. //This Class holds all book entry status info to help the grid properly render entries.
//The reason this info is in here instead of GridEntry is because all of this info is needed //The reason this info is in here instead of GridEntry is because all of this info is needed
//for the "Liberate" column's display and sorting functions. //for the "Liberate" column's display and sorting functions.
public abstract class EntryStatus : ReactiveObject, IComparable public class EntryStatus : ReactiveObject, IComparable
{ {
public LiberatedStatus? PdfStatus => LibraryCommands.Pdf_Status(Book); public LiberatedStatus? PdfStatus => LibraryCommands.Pdf_Status(Book);
public LiberatedStatus BookStatus public LiberatedStatus BookStatus
@ -70,7 +63,7 @@ namespace LibationUiBase.GridView
private readonly bool isAbsent; private readonly bool isAbsent;
private static readonly Dictionary<string, object> iconCache = new(); private static readonly Dictionary<string, object> iconCache = new();
protected EntryStatus(LibraryBook libraryBook) internal EntryStatus(LibraryBook libraryBook)
{ {
Book = ArgumentValidator.EnsureNotNull(libraryBook, nameof(libraryBook)).Book; Book = ArgumentValidator.EnsureNotNull(libraryBook, nameof(libraryBook)).Book;
isAbsent = libraryBook.AbsentFromLastScan is true; isAbsent = libraryBook.AbsentFromLastScan is true;
@ -78,9 +71,6 @@ namespace LibationUiBase.GridView
IsSeries = Book.ContentType is ContentType.Parent; IsSeries = Book.ContentType is ContentType.Parent;
} }
internal protected abstract object LoadImage(byte[] picture);
protected abstract object GetResourceImage(string rescName);
/// <summary>Refresh BookStatus (so partial download files are checked again in the filesystem) and raise PropertyChanged for property names.</summary> /// <summary>Refresh BookStatus (so partial download files are checked again in the filesystem) and raise PropertyChanged for property names.</summary>
public void Invalidate(params string[] properties) public void Invalidate(params string[] properties)
{ {
@ -179,7 +169,7 @@ namespace LibationUiBase.GridView
private object GetAndCacheResource(string rescName) private object GetAndCacheResource(string rescName)
{ {
if (!iconCache.ContainsKey(rescName)) if (!iconCache.ContainsKey(rescName))
iconCache[rescName] = GetResourceImage(rescName); iconCache[rescName] = BaseUtil.LoadResourceImage(rescName);
return iconCache[rescName]; return iconCache[rescName];
} }
} }

View File

@ -29,17 +29,17 @@ public class GridContextMenu
public string ViewBookmarksText => $"View {Accelerator}Bookmarks/Clips"; public string ViewBookmarksText => $"View {Accelerator}Bookmarks/Clips";
public string ViewSeriesText => GridEntries[0].Liberate.IsSeries ? "View All Episodes in Series" : "View All Books in Series"; public string ViewSeriesText => GridEntries[0].Liberate.IsSeries ? "View All Episodes in Series" : "View All Books in Series";
public bool LiberateEpisodesEnabled => GridEntries.OfType<ISeriesEntry>().Any(sEntry => sEntry.Children.Any(c => c.Liberate.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload)); public bool LiberateEpisodesEnabled => GridEntries.OfType<SeriesEntry>().Any(sEntry => sEntry.Children.Any(c => c.Liberate.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload));
public bool SetDownloadedEnabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated || ge.Liberate.IsSeries); public bool SetDownloadedEnabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated || ge.Liberate.IsSeries);
public bool SetNotDownloadedEnabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated || ge.Liberate.IsSeries); public bool SetNotDownloadedEnabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated || ge.Liberate.IsSeries);
public bool ConvertToMp3Enabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated); public bool ConvertToMp3Enabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated);
public bool ReDownloadEnabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated); public bool ReDownloadEnabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated);
private IGridEntry[] GridEntries { get; } private GridEntry[] GridEntries { get; }
public ILibraryBookEntry[] LibraryBookEntries { get; } public LibraryBookEntry[] LibraryBookEntries { get; }
public char Accelerator { get; } public char Accelerator { get; }
public GridContextMenu(IGridEntry[] gridEntries, char accelerator) public GridContextMenu(GridEntry[] gridEntries, char accelerator)
{ {
ArgumentNullException.ThrowIfNull(gridEntries, nameof(gridEntries)); ArgumentNullException.ThrowIfNull(gridEntries, nameof(gridEntries));
ArgumentOutOfRangeException.ThrowIfZero(gridEntries.Length, $"{nameof(gridEntries)}.{nameof(gridEntries.Length)}"); ArgumentOutOfRangeException.ThrowIfZero(gridEntries.Length, $"{nameof(gridEntries)}.{nameof(gridEntries.Length)}");
@ -48,9 +48,9 @@ public class GridContextMenu
Accelerator = accelerator; Accelerator = accelerator;
LibraryBookEntries LibraryBookEntries
= GridEntries = GridEntries
.OfType<ISeriesEntry>() .OfType<SeriesEntry>()
.SelectMany(s => s.Children) .SelectMany(s => s.Children)
.Concat(GridEntries.OfType<ILibraryBookEntry>()) .Concat(GridEntries.OfType<LibraryBookEntry>())
.ToArray(); .ToArray();
} }

View File

@ -21,7 +21,7 @@ namespace LibationUiBase.GridView
} }
/// <summary>The View Model base for the DataGridView</summary> /// <summary>The View Model base for the DataGridView</summary>
public abstract class GridEntry<TStatus> : ReactiveObject, IGridEntry where TStatus : IEntryStatus public abstract class GridEntry : ReactiveObject
{ {
[Browsable(false)] public string AudibleProductId => Book.AudibleProductId; [Browsable(false)] public string AudibleProductId => Book.AudibleProductId;
[Browsable(false)] public LibraryBook LibraryBook { get; protected set; } [Browsable(false)] public LibraryBook LibraryBook { get; protected set; }
@ -100,7 +100,7 @@ namespace LibationUiBase.GridView
LibraryBook = libraryBook; LibraryBook = libraryBook;
var expanded = Liberate?.Expanded ?? false; var expanded = Liberate?.Expanded ?? false;
Liberate = TStatus.Create(libraryBook); Liberate = new EntryStatus(libraryBook);
Liberate.Expanded = expanded; Liberate.Expanded = expanded;
Title = Book.TitleWithSubtitle; Title = Book.TitleWithSubtitle;
@ -239,7 +239,7 @@ namespace LibationUiBase.GridView
PictureStorage.PictureCached += PictureStorage_PictureCached; PictureStorage.PictureCached += PictureStorage_PictureCached;
// Mutable property. Set the field so PropertyChanged isn't fired. // Mutable property. Set the field so PropertyChanged isn't fired.
_lazyCover = new Lazy<object>(() => Liberate.LoadImage(picture)); _lazyCover = new Lazy<object>(() => BaseUtil.LoadImage(picture, PictureSize._80x80));
} }
private void PictureStorage_PictureCached(object sender, PictureCachedEventArgs e) private void PictureStorage_PictureCached(object sender, PictureCachedEventArgs e)
@ -253,7 +253,7 @@ namespace LibationUiBase.GridView
// logic validation // logic validation
if (e.Definition.PictureId == Book.PictureId) if (e.Definition.PictureId == Book.PictureId)
{ {
_lazyCover = new Lazy<object>(() => Liberate.LoadImage(e.Picture)); _lazyCover = new Lazy<object>(() => BaseUtil.LoadImage(e.Picture, PictureSize._80x80));
RaisePropertyChanged(nameof(Cover)); RaisePropertyChanged(nameof(Cover));
PictureStorage.PictureCached -= PictureStorage_PictureCached; PictureStorage.PictureCached -= PictureStorage_PictureCached;
} }
@ -310,13 +310,12 @@ namespace LibationUiBase.GridView
#endregion #endregion
/// <summary> /// <summary>
/// Creates <see cref="IGridEntry"/> for all non-episode books in an enumeration of <see cref="DataLayer.LibraryBook"/>. /// Creates <see cref="GridEntry"/> for all non-episode books in an enumeration of <see cref="DataLayer.LibraryBook"/>.
/// </summary> /// </summary>
/// <remarks>Can be called from any thread, but requires the calling thread's <see cref="SynchronizationContext.Current"/> to be valid.</remarks> /// <remarks>Can be called from any thread, but requires the calling thread's <see cref="SynchronizationContext.Current"/> to be valid.</remarks>
public static async Task<List<TEntry>> GetAllProductsAsync<TEntry>(IEnumerable<LibraryBook> libraryBooks, Func<LibraryBook, bool> includeIf, Func<LibraryBook, TEntry> factory) public static async Task<List<TEntry>> GetAllProductsAsync<TEntry>(IEnumerable<LibraryBook> libraryBooks, Func<LibraryBook, bool> includeIf, Func<LibraryBook, TEntry> factory)
where TEntry : IGridEntry where TEntry : GridEntry
{ {
var products = libraryBooks.Where(includeIf).ToArray(); var products = libraryBooks.Where(includeIf).ToArray();
if (products.Length == 0) if (products.Length == 0)

View File

@ -1,34 +0,0 @@
using DataLayer;
using Dinah.Core.DataBinding;
using System;
using System.ComponentModel;
namespace LibationUiBase.GridView
{
public interface IGridEntry : IMemberComparable, INotifyPropertyChanged
{
EntryStatus Liberate { get; }
float SeriesIndex { get; }
string AudibleProductId { get; }
LibraryBook LibraryBook { get; }
Book Book { get; }
DateTime DateAdded { get; }
bool? Remove { get; set; }
string PurchaseDate { get; }
object Cover { get; }
string Length { get; }
LastDownloadStatus LastDownload { get; }
string Series { get; }
SeriesOrder SeriesOrder { get; }
string Title { get; }
string Authors { get; }
string Narrators { get; }
string Category { get; }
string Misc { get; }
string Description { get; }
Rating ProductRating { get; }
Rating MyRating { get; set; }
string BookTags { get; }
void UpdateLibraryBook(LibraryBook libraryBook);
}
}

View File

@ -1,7 +0,0 @@
namespace LibationUiBase.GridView
{
public interface ILibraryBookEntry : IGridEntry
{
ISeriesEntry Parent { get; }
}
}

View File

@ -1,11 +0,0 @@
using System.Collections.Generic;
namespace LibationUiBase.GridView
{
public interface ISeriesEntry : IGridEntry
{
List<ILibraryBookEntry> Children { get; }
void ChildRemoveUpdate();
void RemoveChild(ILibraryBookEntry libraryBookEntry);
}
}

View File

@ -7,10 +7,10 @@ using System.Threading.Tasks;
namespace LibationUiBase.GridView namespace LibationUiBase.GridView
{ {
/// <summary>The View Model for a LibraryBook that is ContentType.Product or ContentType.Episode</summary> /// <summary>The View Model for a LibraryBook that is ContentType.Product or ContentType.Episode</summary>
public class LibraryBookEntry<TStatus> : GridEntry<TStatus>, ILibraryBookEntry where TStatus : IEntryStatus public class LibraryBookEntry : GridEntry
{ {
[Browsable(false)] public override DateTime DateAdded => LibraryBook.DateAdded; [Browsable(false)] public override DateTime DateAdded => LibraryBook.DateAdded;
[Browsable(false)] public ISeriesEntry Parent { get; } [Browsable(false)] public SeriesEntry Parent { get; }
public override bool? Remove public override bool? Remove
{ {
@ -24,7 +24,7 @@ namespace LibationUiBase.GridView
} }
} }
public LibraryBookEntry(LibraryBook libraryBook, ISeriesEntry parent = null) public LibraryBookEntry(LibraryBook libraryBook, SeriesEntry parent = null)
{ {
Parent = parent; Parent = parent;
UpdateLibraryBook(libraryBook); UpdateLibraryBook(libraryBook);
@ -35,8 +35,8 @@ namespace LibationUiBase.GridView
/// Creates <see cref="LibraryBookEntry{TStatus}"/> for all non-episode books in an enumeration of <see cref="LibraryBook"/>. /// Creates <see cref="LibraryBookEntry{TStatus}"/> for all non-episode books in an enumeration of <see cref="LibraryBook"/>.
/// </summary> /// </summary>
/// <remarks>Can be called from any thread, but requires the calling thread's <see cref="System.Threading.SynchronizationContext.Current"/> to be valid.</remarks> /// <remarks>Can be called from any thread, but requires the calling thread's <see cref="System.Threading.SynchronizationContext.Current"/> to be valid.</remarks>
public static async Task<List<IGridEntry>> GetAllProductsAsync(IEnumerable<LibraryBook> libraryBooks) public static async Task<List<GridEntry>> GetAllProductsAsync(IEnumerable<LibraryBook> libraryBooks)
=> await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsProduct(), lb => new LibraryBookEntry<TStatus>(lb) as IGridEntry); => await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsProduct(), lb => new LibraryBookEntry(lb) as GridEntry);
protected override string GetBookTags() => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated); protected override string GetBookTags() => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
} }

View File

@ -10,19 +10,19 @@ namespace LibationUiBase.GridView
#nullable enable #nullable enable
public static class QueryExtensions public static class QueryExtensions
{ {
public static IEnumerable<ILibraryBookEntry> BookEntries(this IEnumerable<IGridEntry> gridEntries) public static IEnumerable<LibraryBookEntry> BookEntries(this IEnumerable<GridEntry> gridEntries)
=> gridEntries.OfType<ILibraryBookEntry>(); => gridEntries.OfType<LibraryBookEntry>();
public static IEnumerable<ISeriesEntry> SeriesEntries(this IEnumerable<IGridEntry> gridEntries) public static IEnumerable<SeriesEntry> SeriesEntries(this IEnumerable<GridEntry> gridEntries)
=> gridEntries.OfType<ISeriesEntry>(); => gridEntries.OfType<SeriesEntry>();
public static T? FindByAsin<T>(this IEnumerable<T> gridEntries, string audibleProductID) where T : IGridEntry public static T? FindByAsin<T>(this IEnumerable<T> gridEntries, string audibleProductID) where T : GridEntry
=> gridEntries.FirstOrDefault(i => i.AudibleProductId == audibleProductID); => gridEntries.FirstOrDefault(i => i.AudibleProductId == audibleProductID);
public static IEnumerable<ISeriesEntry> EmptySeries(this IEnumerable<IGridEntry> gridEntries) public static IEnumerable<SeriesEntry> EmptySeries(this IEnumerable<GridEntry> gridEntries)
=> gridEntries.SeriesEntries().Where(i => i.Children.Count == 0); => gridEntries.SeriesEntries().Where(i => i.Children.Count == 0);
public static ISeriesEntry? FindSeriesParent(this IEnumerable<IGridEntry> gridEntries, LibraryBook seriesEpisode) public static SeriesEntry? FindSeriesParent(this IEnumerable<GridEntry> gridEntries, LibraryBook seriesEpisode)
{ {
if (seriesEpisode.Book.SeriesLink is null) return null; if (seriesEpisode.Book.SeriesLink is null) return null;
@ -42,14 +42,14 @@ namespace LibationUiBase.GridView
} }
} }
public static bool SearchSetsDiffer(this HashSet<IGridEntry>? searchSet, HashSet<IGridEntry>? otherSet) public static bool SearchSetsDiffer(this HashSet<GridEntry>? searchSet, HashSet<GridEntry>? otherSet)
=> searchSet is null != otherSet is null || => searchSet is null != otherSet is null ||
(searchSet is not null && (searchSet is not null &&
otherSet is not null && otherSet is not null &&
searchSet.Intersect(otherSet).Count() != searchSet.Count); searchSet.Intersect(otherSet).Count() != searchSet.Count);
[return: NotNullIfNotNull(nameof(searchString))] [return: NotNullIfNotNull(nameof(searchString))]
public static HashSet<IGridEntry>? FilterEntries(this IEnumerable<IGridEntry> entries, string? searchString) public static HashSet<GridEntry>? FilterEntries(this IEnumerable<GridEntry> entries, string? searchString)
{ {
if (string.IsNullOrEmpty(searchString)) if (string.IsNullOrEmpty(searchString))
return null; return null;
@ -59,7 +59,7 @@ namespace LibationUiBase.GridView
var booksFilteredIn = entries.IntersectBy(searchResultSet.Docs.Select(d => d.ProductId), l => l.AudibleProductId); var booksFilteredIn = entries.IntersectBy(searchResultSet.Docs.Select(d => d.ProductId), l => l.AudibleProductId);
//Find all series containing children that match the search criteria //Find all series containing children that match the search criteria
var seriesFilteredIn = booksFilteredIn.OfType<ILibraryBookEntry>().Where(lbe => lbe.Parent is not null).Select(lbe => lbe.Parent).Distinct(); var seriesFilteredIn = booksFilteredIn.OfType<LibraryBookEntry>().Where(lbe => lbe.Parent is not null).Select(lbe => lbe.Parent).Distinct();
return booksFilteredIn.Concat(seriesFilteredIn).ToHashSet(); return booksFilteredIn.Concat(seriesFilteredIn).ToHashSet();
} }

View File

@ -10,16 +10,16 @@ namespace LibationUiBase.GridView
/// are sorted by PropertyName while all episodes remain immediately beneath their parents and remain /// are sorted by PropertyName while all episodes remain immediately beneath their parents and remain
/// sorted by series index, ascending. /// sorted by series index, ascending.
/// </summary> /// </summary>
public abstract class RowComparerBase : IComparer, IComparer<IGridEntry>, IComparer<object> public abstract class RowComparerBase : IComparer, IComparer<GridEntry>, IComparer<object>
{ {
public abstract string? PropertyName { get; set; } public abstract string? PropertyName { get; set; }
public int Compare(object? x, object? y) public int Compare(object? x, object? y)
=> Compare(x as IGridEntry, y as IGridEntry); => Compare(x as GridEntry, y as GridEntry);
protected abstract ListSortDirection GetSortOrder(); protected abstract ListSortDirection GetSortOrder();
private int InternalCompare(IGridEntry x, IGridEntry y) private int InternalCompare(GridEntry x, GridEntry y)
{ {
var val1 = x.GetMemberValue(PropertyName); var val1 = x.GetMemberValue(PropertyName);
var val2 = y.GetMemberValue(PropertyName); var val2 = y.GetMemberValue(PropertyName);
@ -32,7 +32,7 @@ namespace LibationUiBase.GridView
: compare; : compare;
} }
public int Compare(IGridEntry? geA, IGridEntry? geB) public int Compare(GridEntry? geA, GridEntry? geB)
{ {
if (geA is null && geB is not null) return -1; if (geA is null && geB is not null) return -1;
if (geA is not null && geB is null) return 1; if (geA is not null && geB is null) return 1;
@ -40,12 +40,12 @@ namespace LibationUiBase.GridView
var sortDirection = GetSortOrder(); var sortDirection = GetSortOrder();
ISeriesEntry? parentA = null; SeriesEntry? parentA = null;
ISeriesEntry? parentB = null; SeriesEntry? parentB = null;
if (geA is ILibraryBookEntry lbA && lbA.Parent is ISeriesEntry seA) if (geA is LibraryBookEntry lbA && lbA.Parent is SeriesEntry seA)
parentA = seA; parentA = seA;
if (geB is ILibraryBookEntry lbB && lbB.Parent is ISeriesEntry seB) if (geB is LibraryBookEntry lbB && lbB.Parent is SeriesEntry seB)
parentB = seB; parentB = seB;
//both entries are children //both entries are children
@ -59,7 +59,7 @@ namespace LibationUiBase.GridView
//and DateAdded, compare SeriesOrder instead.. //and DateAdded, compare SeriesOrder instead..
return PropertyName switch return PropertyName switch
{ {
nameof(IGridEntry.DateAdded) or nameof(IGridEntry.PurchaseDate) => geA.SeriesOrder.CompareTo(geB.SeriesOrder), nameof(GridEntry.DateAdded) or nameof(GridEntry.PurchaseDate) => geA.SeriesOrder.CompareTo(geB.SeriesOrder),
_ => InternalCompare(geA, geB), _ => InternalCompare(geA, geB),
}; };
} }

View File

@ -9,9 +9,9 @@ using System.Threading.Tasks;
namespace LibationUiBase.GridView namespace LibationUiBase.GridView
{ {
/// <summary>The View Model for a LibraryBook that is ContentType.Parent</summary> /// <summary>The View Model for a LibraryBook that is ContentType.Parent</summary>
public class SeriesEntry<TStatus> : GridEntry<TStatus>, ISeriesEntry where TStatus : IEntryStatus public class SeriesEntry : GridEntry
{ {
public List<ILibraryBookEntry> Children { get; } public List<LibraryBookEntry> Children { get; }
public override DateTime DateAdded => Children.Max(c => c.DateAdded); public override DateTime DateAdded => Children.Max(c => c.DateAdded);
private bool suspendCounting = false; private bool suspendCounting = false;
@ -49,9 +49,9 @@ namespace LibationUiBase.GridView
SeriesIndex = -1; SeriesIndex = -1;
Children = children Children = children
.Select(c => new LibraryBookEntry<TStatus>(c, this)) .Select(c => new LibraryBookEntry(c, this))
.OrderByDescending(c => c.SeriesOrder) .OrderByDescending(c => c.SeriesOrder)
.ToList<ILibraryBookEntry>(); .ToList<LibraryBookEntry>();
UpdateLibraryBook(parent); UpdateLibraryBook(parent);
LoadCover(); LoadCover();
@ -61,9 +61,9 @@ namespace LibationUiBase.GridView
/// Creates <see cref="SeriesEntry{TStatus}"/> for all episodic series in an enumeration of <see cref="LibraryBook"/>. /// Creates <see cref="SeriesEntry{TStatus}"/> for all episodic series in an enumeration of <see cref="LibraryBook"/>.
/// </summary> /// </summary>
/// <remarks>Can be called from any thread, but requires the calling thread's <see cref="SynchronizationContext.Current"/> to be valid.</remarks> /// <remarks>Can be called from any thread, but requires the calling thread's <see cref="SynchronizationContext.Current"/> to be valid.</remarks>
public static async Task<List<ISeriesEntry>> GetAllSeriesEntriesAsync(IEnumerable<LibraryBook> libraryBooks) public static async Task<List<SeriesEntry>> GetAllSeriesEntriesAsync(IEnumerable<LibraryBook> libraryBooks)
{ {
var seriesEntries = await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsEpisodeParent(), lb => new SeriesEntry<TStatus>(lb, []) as ISeriesEntry); var seriesEntries = await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsEpisodeParent(), lb => new SeriesEntry(lb, []));
var seriesDict = seriesEntries.ToDictionarySafe(s => s.AudibleProductId); var seriesDict = seriesEntries.ToDictionarySafe(s => s.AudibleProductId);
await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsEpisodeChild(), CreateAndLinkEpisodeEntry); await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsEpisodeChild(), CreateAndLinkEpisodeEntry);
@ -77,13 +77,13 @@ namespace LibationUiBase.GridView
return seriesEntries.Where(s => s.Children.Count != 0).ToList(); return seriesEntries.Where(s => s.Children.Count != 0).ToList();
//Create a LibraryBookEntry for an episode and link it to its series parent //Create a LibraryBookEntry for an episode and link it to its series parent
LibraryBookEntry<TStatus> CreateAndLinkEpisodeEntry(LibraryBook episode) LibraryBookEntry CreateAndLinkEpisodeEntry(LibraryBook episode)
{ {
foreach (var s in episode.Book.SeriesLink) foreach (var s in episode.Book.SeriesLink)
{ {
if (seriesDict.TryGetValue(s.Series.AudibleSeriesId, out var seriesParent)) if (seriesDict.TryGetValue(s.Series.AudibleSeriesId, out var seriesParent))
{ {
var entry = new LibraryBookEntry<TStatus>(episode, seriesParent); var entry = new LibraryBookEntry(episode, seriesParent);
seriesParent.Children.Add(entry); seriesParent.Children.Add(entry);
return entry; return entry;
} }
@ -92,7 +92,7 @@ namespace LibationUiBase.GridView
} }
} }
public void RemoveChild(ILibraryBookEntry lbe) public void RemoveChild(LibraryBookEntry lbe)
{ {
Children.Remove(lbe); Children.Remove(lbe);
PurchaseDate = GetPurchaseDateString(); PurchaseDate = GetPurchaseDateString();

View File

@ -538,7 +538,7 @@
this.productsDisplay.VisibleCountChanged += new System.EventHandler<int>(this.productsDisplay_VisibleCountChanged); this.productsDisplay.VisibleCountChanged += new System.EventHandler<int>(this.productsDisplay_VisibleCountChanged);
this.productsDisplay.RemovableCountChanged += new System.EventHandler<int>(this.productsDisplay_RemovableCountChanged); this.productsDisplay.RemovableCountChanged += new System.EventHandler<int>(this.productsDisplay_RemovableCountChanged);
this.productsDisplay.LiberateClicked += ProductsDisplay_LiberateClicked; this.productsDisplay.LiberateClicked += ProductsDisplay_LiberateClicked;
this.productsDisplay.LiberateSeriesClicked += new System.EventHandler<LibationUiBase.GridView.ISeriesEntry>(this.ProductsDisplay_LiberateSeriesClicked); this.productsDisplay.LiberateSeriesClicked += new System.EventHandler<LibationUiBase.GridView.SeriesEntry>(this.ProductsDisplay_LiberateSeriesClicked);
this.productsDisplay.ConvertToMp3Clicked += ProductsDisplay_ConvertToMp3Clicked; this.productsDisplay.ConvertToMp3Clicked += ProductsDisplay_ConvertToMp3Clicked;
this.productsDisplay.InitialLoaded += new System.EventHandler(this.productsDisplay_InitialLoaded); this.productsDisplay.InitialLoaded += new System.EventHandler(this.productsDisplay_InitialLoaded);
// //

View File

@ -48,7 +48,7 @@ namespace LibationWinForms
} }
} }
private void ProductsDisplay_LiberateSeriesClicked(object sender, ISeriesEntry series) private void ProductsDisplay_LiberateSeriesClicked(object sender, SeriesEntry series)
{ {
try try
{ {

View File

@ -22,6 +22,7 @@ namespace LibationWinForms
PictureStorage.SetDefaultImage(PictureSize.Native, Properties.Resources.default_cover_500x500.ToBytes(format)); PictureStorage.SetDefaultImage(PictureSize.Native, Properties.Resources.default_cover_500x500.ToBytes(format));
BaseUtil.SetLoadImageDelegate(WinFormsUtil.TryLoadImageOrDefault); BaseUtil.SetLoadImageDelegate(WinFormsUtil.TryLoadImageOrDefault);
BaseUtil.SetLoadResourceImageDelegate(Properties.Resources.ResourceManager.GetObject);
// wire-up event to automatically download after scan. // wire-up event to automatically download after scan.
// winforms only. this should NOT be allowed in cli // winforms only. this should NOT be allowed in cli

View File

@ -28,7 +28,7 @@ namespace LibationWinForms.GridView
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts) protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
{ {
// series // series
if (rowIndex >= 0 && DataGridView.GetBoundItem<IGridEntry>(rowIndex) is ISeriesEntry) if (rowIndex >= 0 && DataGridView.GetBoundItem<GridEntry>(rowIndex) is SeriesEntry)
{ {
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, DataGridViewPaintParts.Background | DataGridViewPaintParts.Border); base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, DataGridViewPaintParts.Background | DataGridViewPaintParts.Border);
} }

View File

@ -24,24 +24,24 @@ namespace LibationWinForms.GridView
* event. Adding or removing from the underlying list will not change the * event. Adding or removing from the underlying list will not change the
* BindingList's subscription to that item. * BindingList's subscription to that item.
*/ */
internal class GridEntryBindingList : BindingList<IGridEntry>, IBindingListView internal class GridEntryBindingList : BindingList<GridEntry>, IBindingListView
{ {
public GridEntryBindingList(IEnumerable<IGridEntry> enumeration) : base(new List<IGridEntry>(enumeration)) public GridEntryBindingList(IEnumerable<GridEntry> enumeration) : base(new List<GridEntry>(enumeration))
{ {
SearchEngineCommands.SearchEngineUpdated += SearchEngineCommands_SearchEngineUpdated; SearchEngineCommands.SearchEngineUpdated += SearchEngineCommands_SearchEngineUpdated;
ListChanged += GridEntryBindingList_ListChanged; ListChanged += GridEntryBindingList_ListChanged;
} }
/// <returns>All items in the list, including those filtered out.</returns> /// <returns>All items in the list, including those filtered out.</returns>
public List<IGridEntry> AllItems() => Items.Concat(FilterRemoved).ToList(); public List<GridEntry> AllItems() => Items.Concat(FilterRemoved).ToList();
/// <summary>All items that pass the current filter</summary> /// <summary>All items that pass the current filter</summary>
public IEnumerable<ILibraryBookEntry> GetFilteredInItems() public IEnumerable<LibraryBookEntry> GetFilteredInItems()
=> FilteredInGridEntries? => FilteredInGridEntries?
.OfType<ILibraryBookEntry>() .OfType<LibraryBookEntry>()
?? FilterRemoved ?? FilterRemoved
.OfType<ILibraryBookEntry>() .OfType<LibraryBookEntry>()
.Union(Items.OfType<ILibraryBookEntry>()); .Union(Items.OfType<LibraryBookEntry>());
public bool SupportsFiltering => true; public bool SupportsFiltering => true;
public string Filter public string Filter
@ -67,12 +67,12 @@ namespace LibationWinForms.GridView
protected override ListSortDirection SortDirectionCore => Comparer.SortOrder; protected override ListSortDirection SortDirectionCore => Comparer.SortOrder;
/// <summary> Items that were removed from the base list due to filtering </summary> /// <summary> Items that were removed from the base list due to filtering </summary>
private readonly List<IGridEntry> FilterRemoved = new(); private readonly List<GridEntry> FilterRemoved = new();
private string FilterString; private string FilterString;
private bool isSorted; private bool isSorted;
private PropertyDescriptor propertyDescriptor; private PropertyDescriptor propertyDescriptor;
/// <summary> All GridEntries present in the current filter set. If null, no filter is applied and all entries are filtered in.(This was a performance choice)</summary> /// <summary> All GridEntries present in the current filter set. If null, no filter is applied and all entries are filtered in.(This was a performance choice)</summary>
private HashSet<IGridEntry> FilteredInGridEntries; private HashSet<GridEntry> FilteredInGridEntries;
#region Unused - Advanced Filtering #region Unused - Advanced Filtering
public bool SupportsAdvancedSorting => false; public bool SupportsAdvancedSorting => false;
@ -84,7 +84,7 @@ namespace LibationWinForms.GridView
public ListSortDescriptionCollection SortDescriptions => throw new NotImplementedException(); public ListSortDescriptionCollection SortDescriptions => throw new NotImplementedException();
#endregion #endregion
public new void Remove(IGridEntry entry) public new void Remove(GridEntry entry)
{ {
FilterRemoved.Remove(entry); FilterRemoved.Remove(entry);
base.Remove(entry); base.Remove(entry);
@ -122,13 +122,13 @@ namespace LibationWinForms.GridView
ResetList(); ResetList();
RaiseListChangedEvents = priorState; RaiseListChangedEvents = priorState;
void addRemovedItemsBack(List<IGridEntry> addBackEntries) void addRemovedItemsBack(List<GridEntry> addBackEntries)
{ {
//Add removed entries back into Items so they are displayed //Add removed entries back into Items so they are displayed
//(except for episodes that are collapsed) //(except for episodes that are collapsed)
foreach (var addBack in addBackEntries) foreach (var addBack in addBackEntries)
{ {
if (addBack is ILibraryBookEntry lbe && lbe.Parent is ISeriesEntry se && !se.Liberate.Expanded) if (addBack is LibraryBookEntry lbe && lbe.Parent is SeriesEntry se && !se.Liberate.Expanded)
continue; continue;
FilterRemoved.Remove(addBack); FilterRemoved.Remove(addBack);
@ -160,7 +160,7 @@ namespace LibationWinForms.GridView
ExpandItem(series); ExpandItem(series);
} }
public void CollapseItem(ISeriesEntry sEntry) public void CollapseItem(SeriesEntry sEntry)
{ {
foreach (var episode in sEntry.Children.Intersect(Items.BookEntries()).ToList()) foreach (var episode in sEntry.Children.Intersect(Items.BookEntries()).ToList())
{ {
@ -171,7 +171,7 @@ namespace LibationWinForms.GridView
sEntry.Liberate.Expanded = false; sEntry.Liberate.Expanded = false;
} }
public void ExpandItem(ISeriesEntry sEntry) public void ExpandItem(SeriesEntry sEntry)
{ {
var sindex = Items.IndexOf(sEntry); var sindex = Items.IndexOf(sEntry);
@ -208,7 +208,7 @@ namespace LibationWinForms.GridView
private void SortInternal() private void SortInternal()
{ {
var itemsList = (List<IGridEntry>)Items; var itemsList = (List<GridEntry>)Items;
//User Order/OrderDescending and replace items in list instead of using List.Sort() to achieve stable sorting. //User Order/OrderDescending and replace items in list instead of using List.Sort() to achieve stable sorting.
var sortedItems = Comparer.OrderEntries(itemsList).ToList(); var sortedItems = Comparer.OrderEntries(itemsList).ToList();

View File

@ -1,7 +1,9 @@
using DataLayer; using DataLayer;
using LibationUiBase.GridView;
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
#nullable enable
namespace LibationWinForms.GridView namespace LibationWinForms.GridView
{ {
public class LiberateDataGridViewImageButtonColumn : DataGridViewButtonColumn, IDataGridScaleColumn public class LiberateDataGridViewImageButtonColumn : DataGridViewButtonColumn, IDataGridScaleColumn
@ -20,16 +22,18 @@ namespace LibationWinForms.GridView
private static readonly Brush DISABLED_GRAY = new SolidBrush(Color.FromArgb(0x60, Color.LightGray)); private static readonly Brush DISABLED_GRAY = new SolidBrush(Color.FromArgb(0x60, Color.LightGray));
private static readonly Color HiddenForeColor = Color.LightGray; private static readonly Color HiddenForeColor = Color.LightGray;
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts) private static readonly Color SERIES_BG_COLOR = Color.FromArgb(230, 255, 230);
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object? value, object? formattedValue, string? errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
{ {
if (value is WinFormsEntryStatus status) if (OwningRow is DataGridViewRow row && row.DataGridView is DataGridView grid && value is EntryStatus status)
{ {
if (status.BookStatus is LiberatedStatus.Error || status.IsUnavailable) if (status.BookStatus is LiberatedStatus.Error || status.IsUnavailable)
//Don't paint the button graphic //Don't paint the button graphic
paintParts ^= DataGridViewPaintParts.ContentBackground | DataGridViewPaintParts.ContentForeground | DataGridViewPaintParts.SelectionBackground; paintParts ^= DataGridViewPaintParts.ContentBackground | DataGridViewPaintParts.ContentForeground | DataGridViewPaintParts.SelectionBackground;
DataGridView.Rows[rowIndex].DefaultCellStyle.BackColor = (Color)status.BackgroundBrush; row.DefaultCellStyle.BackColor = status.IsEpisode ? SERIES_BG_COLOR : grid.DefaultCellStyle.BackColor;
DataGridView.Rows[rowIndex].DefaultCellStyle.ForeColor = status.Opacity == 1 ? DataGridView.DefaultCellStyle.ForeColor : HiddenForeColor; row.DefaultCellStyle.ForeColor = status.Opacity == 1 ? grid.DefaultCellStyle.ForeColor : HiddenForeColor;
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts); base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts);
DrawButtonImage(graphics, (Image)status.ButtonImage, cellBounds); DrawButtonImage(graphics, (Image)status.ButtonImage, cellBounds);

View File

@ -22,7 +22,7 @@ namespace LibationWinForms.GridView
public event EventHandler<int> VisibleCountChanged; public event EventHandler<int> VisibleCountChanged;
public event EventHandler<int> RemovableCountChanged; public event EventHandler<int> RemovableCountChanged;
public event EventHandler<LibraryBook[]> LiberateClicked; public event EventHandler<LibraryBook[]> LiberateClicked;
public event EventHandler<ISeriesEntry> LiberateSeriesClicked; public event EventHandler<SeriesEntry> LiberateSeriesClicked;
public event EventHandler<LibraryBook[]> ConvertToMp3Clicked; public event EventHandler<LibraryBook[]> ConvertToMp3Clicked;
public event EventHandler InitialLoaded; public event EventHandler InitialLoaded;
@ -36,7 +36,7 @@ namespace LibationWinForms.GridView
#region Button controls #region Button controls
private ImageDisplay imageDisplay; private ImageDisplay imageDisplay;
private void productsGrid_CoverClicked(IGridEntry liveGridEntry) private void productsGrid_CoverClicked(GridEntry liveGridEntry)
{ {
var picDef = new PictureDefinition(liveGridEntry.LibraryBook.Book.PictureLarge ?? liveGridEntry.LibraryBook.Book.PictureId, PictureSize.Native); var picDef = new PictureDefinition(liveGridEntry.LibraryBook.Book.PictureLarge ?? liveGridEntry.LibraryBook.Book.PictureId, PictureSize.Native);
@ -71,7 +71,7 @@ namespace LibationWinForms.GridView
imageDisplay.Show(null); imageDisplay.Show(null);
} }
private void productsGrid_DescriptionClicked(IGridEntry liveGridEntry, Rectangle cellRectangle) private void productsGrid_DescriptionClicked(GridEntry liveGridEntry, Rectangle cellRectangle)
{ {
var displayWindow = new DescriptionDisplay var displayWindow = new DescriptionDisplay
{ {
@ -90,7 +90,7 @@ namespace LibationWinForms.GridView
displayWindow.Show(this); displayWindow.Show(this);
} }
private async void productsGrid_DetailsClicked(ILibraryBookEntry liveGridEntry) private async void productsGrid_DetailsClicked(LibraryBookEntry liveGridEntry)
{ {
// HACK: workaround for a Winforms bug. // HACK: workaround for a Winforms bug.
// This event is fired by the DataGridCell.OnMouseUpInternal // This event is fired by the DataGridCell.OnMouseUpInternal
@ -124,12 +124,12 @@ namespace LibationWinForms.GridView
#region Cell Context Menu #region Cell Context Menu
private void productsGrid_CellContextMenuStripNeeded(IGridEntry[] entries, ContextMenuStrip ctxMenu) private void productsGrid_CellContextMenuStripNeeded(GridEntry[] entries, ContextMenuStrip ctxMenu)
{ {
var ctx = new GridContextMenu(entries, '&'); var ctx = new GridContextMenu(entries, '&');
#region Liberate all Episodes (Single series only) #region Liberate all Episodes (Single series only)
if (entries.Length == 1 && entries[0] is ISeriesEntry seriesEntry) if (entries.Length == 1 && entries[0] is SeriesEntry seriesEntry)
{ {
var liberateEpisodesMenuItem = new ToolStripMenuItem() var liberateEpisodesMenuItem = new ToolStripMenuItem()
{ {
@ -166,7 +166,7 @@ namespace LibationWinForms.GridView
#endregion #endregion
#region Locate file (Single book only) #region Locate file (Single book only)
if (entries.Length == 1 && entries[0] is ILibraryBookEntry entry) if (entries.Length == 1 && entries[0] is LibraryBookEntry entry)
{ {
var locateFileMenuItem = new ToolStripMenuItem() { Text = ctx.LocateFileText }; var locateFileMenuItem = new ToolStripMenuItem() { Text = ctx.LocateFileText };
ctxMenu.Items.Add(locateFileMenuItem); ctxMenu.Items.Add(locateFileMenuItem);
@ -199,7 +199,7 @@ namespace LibationWinForms.GridView
#endregion #endregion
#region Liberate All (multiple books only) #region Liberate All (multiple books only)
if (entries.OfType<ILibraryBookEntry>().Count() > 1) if (entries.OfType<LibraryBookEntry>().Count() > 1)
{ {
var downloadSelectedMenuItem = new ToolStripMenuItem() var downloadSelectedMenuItem = new ToolStripMenuItem()
{ {
@ -230,7 +230,7 @@ namespace LibationWinForms.GridView
#endregion #endregion
#region Force Re-Download (Single book only) #region Force Re-Download (Single book only)
if (entries.Length == 1 && entries[0] is ILibraryBookEntry entry4) if (entries.Length == 1 && entries[0] is LibraryBookEntry entry4)
{ {
var reDownloadMenuItem = new ToolStripMenuItem() var reDownloadMenuItem = new ToolStripMenuItem()
{ {
@ -269,7 +269,7 @@ namespace LibationWinForms.GridView
} }
} }
if (entries.Length == 1 && entries[0] is ILibraryBookEntry entry2) if (entries.Length == 1 && entries[0] is LibraryBookEntry entry2)
{ {
var folderTemplateMenuItem = new ToolStripMenuItem { Text = ctx.FolderTemplateText }; var folderTemplateMenuItem = new ToolStripMenuItem { Text = ctx.FolderTemplateText };
var fileTemplateMenuItem = new ToolStripMenuItem { Text = ctx.FileTemplateText }; var fileTemplateMenuItem = new ToolStripMenuItem { Text = ctx.FileTemplateText };
@ -288,7 +288,7 @@ namespace LibationWinForms.GridView
#endregion #endregion
#region View Bookmarks/Clips (Single book only) #region View Bookmarks/Clips (Single book only)
if (entries.Length == 1 && entries[0] is ILibraryBookEntry entry3) if (entries.Length == 1 && entries[0] is LibraryBookEntry entry3)
{ {
var bookRecordMenuItem = new ToolStripMenuItem { Text = ctx.ViewBookmarksText }; var bookRecordMenuItem = new ToolStripMenuItem { Text = ctx.ViewBookmarksText };
bookRecordMenuItem.Click += (_, _) => new BookRecordsDialog(entry3.LibraryBook).ShowDialog(this); bookRecordMenuItem.Click += (_, _) => new BookRecordsDialog(entry3.LibraryBook).ShowDialog(this);
@ -417,7 +417,7 @@ namespace LibationWinForms.GridView
VisibleCountChanged?.Invoke(this, count); VisibleCountChanged?.Invoke(this, count);
} }
private void productsGrid_LiberateClicked(ILibraryBookEntry liveGridEntry) private void productsGrid_LiberateClicked(LibraryBookEntry liveGridEntry)
{ {
if (liveGridEntry.LibraryBook.Book.UserDefinedItem.BookStatus is not LiberatedStatus.Error if (liveGridEntry.LibraryBook.Book.UserDefinedItem.BookStatus is not LiberatedStatus.Error
&& !liveGridEntry.Liberate.IsUnavailable) && !liveGridEntry.Liberate.IsUnavailable)

View File

@ -270,7 +270,7 @@ namespace LibationWinForms.GridView
// //
// syncBindingSource // syncBindingSource
// //
syncBindingSource.DataSource = typeof(IGridEntry); syncBindingSource.DataSource = typeof(GridEntry);
// //
// ProductsGrid // ProductsGrid
// //

View File

@ -15,10 +15,10 @@ using System.Windows.Forms;
namespace LibationWinForms.GridView namespace LibationWinForms.GridView
{ {
public delegate void GridEntryClickedEventHandler(IGridEntry liveGridEntry); public delegate void GridEntryClickedEventHandler(GridEntry liveGridEntry);
public delegate void LibraryBookEntryClickedEventHandler(ILibraryBookEntry liveGridEntry); public delegate void LibraryBookEntryClickedEventHandler(LibraryBookEntry liveGridEntry);
public delegate void GridEntryRectangleClickedEventHandler(IGridEntry liveGridEntry, Rectangle cellRectangle); public delegate void GridEntryRectangleClickedEventHandler(GridEntry liveGridEntry, Rectangle cellRectangle);
public delegate void ProductsGridCellContextMenuStripNeededEventHandler(IGridEntry[] liveGridEntry, ContextMenuStrip ctxMenu); public delegate void ProductsGridCellContextMenuStripNeededEventHandler(GridEntry[] liveGridEntry, ContextMenuStrip ctxMenu);
public partial class ProductsGrid : UserControl public partial class ProductsGrid : UserControl
{ {
@ -37,8 +37,8 @@ namespace LibationWinForms.GridView
=> bindingList => bindingList
?.GetFilteredInItems() ?.GetFilteredInItems()
.Select(lbe => lbe.LibraryBook) ?? Enumerable.Empty<LibraryBook>(); .Select(lbe => lbe.LibraryBook) ?? Enumerable.Empty<LibraryBook>();
internal IEnumerable<ILibraryBookEntry> GetAllBookEntries() internal IEnumerable<LibraryBookEntry> GetAllBookEntries()
=> bindingList?.AllItems().BookEntries() ?? Enumerable.Empty<ILibraryBookEntry>(); => bindingList?.AllItems().BookEntries() ?? Enumerable.Empty<LibraryBookEntry>();
public ProductsGrid() public ProductsGrid()
{ {
@ -189,7 +189,7 @@ namespace LibationWinForms.GridView
.Distinct() .Distinct()
.OrderBy(r => r.Index) .OrderBy(r => r.Index)
.Select(r => r.DataBoundItem) .Select(r => r.DataBoundItem)
.OfType<IGridEntry>() .OfType<GridEntry>()
.ToArray(); .ToArray();
var clickedIndex = Array.IndexOf(allSelected, clickedEntry); var clickedIndex = Array.IndexOf(allSelected, clickedEntry);
@ -225,7 +225,7 @@ namespace LibationWinForms.GridView
return; return;
var entry = getGridEntry(e.RowIndex); var entry = getGridEntry(e.RowIndex);
if (entry is ILibraryBookEntry lbEntry) if (entry is LibraryBookEntry lbEntry)
{ {
if (e.ColumnIndex == liberateGVColumn.Index) if (e.ColumnIndex == liberateGVColumn.Index)
LiberateClicked?.Invoke(lbEntry); LiberateClicked?.Invoke(lbEntry);
@ -236,7 +236,7 @@ namespace LibationWinForms.GridView
else if (e.ColumnIndex == coverGVColumn.Index) else if (e.ColumnIndex == coverGVColumn.Index)
CoverClicked?.Invoke(lbEntry); CoverClicked?.Invoke(lbEntry);
} }
else if (entry is ISeriesEntry sEntry) else if (entry is SeriesEntry sEntry)
{ {
if (e.ColumnIndex == liberateGVColumn.Index) if (e.ColumnIndex == liberateGVColumn.Index)
{ {
@ -265,7 +265,7 @@ namespace LibationWinForms.GridView
} }
} }
private IGridEntry getGridEntry(int rowIndex) => gridEntryDataGridView.GetBoundItem<IGridEntry>(rowIndex); private GridEntry getGridEntry(int rowIndex) => gridEntryDataGridView.GetBoundItem<GridEntry>(rowIndex);
#endregion #endregion
@ -295,8 +295,8 @@ namespace LibationWinForms.GridView
var sc = Invoke(() => System.Threading.SynchronizationContext.Current); var sc = Invoke(() => System.Threading.SynchronizationContext.Current);
System.Threading.SynchronizationContext.SetSynchronizationContext(sc); System.Threading.SynchronizationContext.SetSynchronizationContext(sc);
var geList = await LibraryBookEntry<WinFormsEntryStatus>.GetAllProductsAsync(dbBooks); var geList = await LibraryBookEntry.GetAllProductsAsync(dbBooks);
var seriesEntries = await SeriesEntry<WinFormsEntryStatus>.GetAllSeriesEntriesAsync(dbBooks); var seriesEntries = await SeriesEntry.GetAllSeriesEntriesAsync(dbBooks);
geList.AddRange(seriesEntries); geList.AddRange(seriesEntries);
//Sort descending by date (default sort property) //Sort descending by date (default sort property)
@ -383,7 +383,7 @@ namespace LibationWinForms.GridView
gridEntryDataGridView.FirstDisplayedScrollingRowIndex = topRow; gridEntryDataGridView.FirstDisplayedScrollingRowIndex = topRow;
} }
public void RemoveBooks(IEnumerable<ILibraryBookEntry> removedBooks) public void RemoveBooks(IEnumerable<LibraryBookEntry> removedBooks)
{ {
if (bindingList == null) if (bindingList == null)
throw new InvalidOperationException($"Must call {nameof(BindToGridAsync)} before calling {nameof(RemoveBooks)}"); throw new InvalidOperationException($"Must call {nameof(BindToGridAsync)} before calling {nameof(RemoveBooks)}");
@ -398,34 +398,34 @@ namespace LibationWinForms.GridView
.AllItems() .AllItems()
.EmptySeries(); .EmptySeries();
foreach (var removed in removedBooks.Cast<IGridEntry>().Concat(removedSeries)) foreach (var removed in removedBooks.Cast<GridEntry>().Concat(removedSeries))
//no need to re-filter for removed books //no need to re-filter for removed books
bindingList.Remove(removed); bindingList.Remove(removed);
VisibleCountChanged?.Invoke(this, bindingList.GetFilteredInItems().Count()); VisibleCountChanged?.Invoke(this, bindingList.GetFilteredInItems().Count());
} }
private void AddOrUpdateBook(LibraryBook book, ILibraryBookEntry? existingBookEntry) private void AddOrUpdateBook(LibraryBook book, LibraryBookEntry? existingBookEntry)
{ {
if (bindingList == null) if (bindingList == null)
throw new InvalidOperationException($"Must call {nameof(BindToGridAsync)} before calling {nameof(AddOrUpdateBook)}"); throw new InvalidOperationException($"Must call {nameof(BindToGridAsync)} before calling {nameof(AddOrUpdateBook)}");
if (existingBookEntry is null) if (existingBookEntry is null)
// Add the new product to top // Add the new product to top
bindingList.Insert(0, new LibraryBookEntry<WinFormsEntryStatus>(book)); bindingList.Insert(0, new LibraryBookEntry(book));
else else
// update existing // update existing
existingBookEntry.UpdateLibraryBook(book); existingBookEntry.UpdateLibraryBook(book);
} }
private void AddOrUpdateEpisode(LibraryBook episodeBook, ILibraryBookEntry? existingEpisodeEntry, List<ISeriesEntry> seriesEntries, IEnumerable<LibraryBook> dbBooks) private void AddOrUpdateEpisode(LibraryBook episodeBook, LibraryBookEntry? existingEpisodeEntry, List<SeriesEntry> seriesEntries, IEnumerable<LibraryBook> dbBooks)
{ {
if (bindingList == null) if (bindingList == null)
throw new InvalidOperationException($"Must call {nameof(BindToGridAsync)} before calling {nameof(AddOrUpdateEpisode)}"); throw new InvalidOperationException($"Must call {nameof(BindToGridAsync)} before calling {nameof(AddOrUpdateEpisode)}");
if (existingEpisodeEntry is null) if (existingEpisodeEntry is null)
{ {
ILibraryBookEntry episodeEntry; LibraryBookEntry episodeEntry;
var seriesEntry = seriesEntries.FindSeriesParent(episodeBook); var seriesEntry = seriesEntries.FindSeriesParent(episodeBook);
@ -443,7 +443,7 @@ namespace LibationWinForms.GridView
return; return;
} }
seriesEntry = new SeriesEntry<WinFormsEntryStatus>(seriesBook, episodeBook); seriesEntry = new SeriesEntry(seriesBook, episodeBook);
seriesEntries.Add(seriesEntry); seriesEntries.Add(seriesEntry);
episodeEntry = seriesEntry.Children[0]; episodeEntry = seriesEntry.Children[0];
@ -453,7 +453,7 @@ namespace LibationWinForms.GridView
else else
{ {
//Series exists. Create and add episode child then update the SeriesEntry //Series exists. Create and add episode child then update the SeriesEntry
episodeEntry = new LibraryBookEntry<WinFormsEntryStatus>(episodeBook, seriesEntry); episodeEntry = new LibraryBookEntry(episodeBook, seriesEntry);
seriesEntry.Children.Add(episodeEntry); seriesEntry.Children.Add(episodeEntry);
seriesEntry.Children.Sort((c1, c2) => c1.SeriesIndex.CompareTo(c2.SeriesIndex)); seriesEntry.Children.Sort((c1, c2) => c1.SeriesIndex.CompareTo(c2.SeriesIndex));
var seriesBook = dbBooks.Single(lb => lb.Book.AudibleProductId == seriesEntry.LibraryBook.Book.AudibleProductId); var seriesBook = dbBooks.Single(lb => lb.Book.AudibleProductId == seriesEntry.LibraryBook.Book.AudibleProductId);

View File

@ -8,13 +8,13 @@ namespace LibationWinForms.GridView
internal class RowComparer : RowComparerBase internal class RowComparer : RowComparerBase
{ {
public ListSortDirection SortOrder { get; set; } = ListSortDirection.Descending; public ListSortDirection SortOrder { get; set; } = ListSortDirection.Descending;
public override string PropertyName { get; set; } = nameof(IGridEntry.DateAdded); public override string PropertyName { get; set; } = nameof(GridEntry.DateAdded);
protected override ListSortDirection GetSortOrder() => SortOrder; protected override ListSortDirection GetSortOrder() => SortOrder;
/// <summary> /// <summary>
/// Helper method for ordering grid entries /// Helper method for ordering grid entries
/// </summary> /// </summary>
public IOrderedEnumerable<IGridEntry> OrderEntries(IEnumerable<IGridEntry> entries) public IOrderedEnumerable<GridEntry> OrderEntries(IEnumerable<GridEntry> entries)
=> SortOrder is ListSortDirection.Descending ? entries.OrderDescending(this) : entries.Order(this); => SortOrder is ListSortDirection.Descending ? entries.OrderDescending(this) : entries.Order(this);
} }
} }

View File

@ -1,24 +0,0 @@
using DataLayer;
using LibationUiBase.GridView;
using System.Drawing;
namespace LibationWinForms.GridView
{
public class WinFormsEntryStatus : EntryStatus, IEntryStatus
{
private static readonly Color SERIES_BG_COLOR = Color.FromArgb(230, 255, 230);
public Color BackgroundBrush => IsEpisode ? SERIES_BG_COLOR : SystemColors.ControlLightLight;
private WinFormsEntryStatus(LibraryBook libraryBook) : base(libraryBook) { }
public static EntryStatus Create(LibraryBook libraryBook) => new WinFormsEntryStatus(libraryBook);
protected override Image LoadImage(byte[] picture)
=> WinFormsUtil.TryLoadImageOrDefault(picture, LibationFileManager.PictureSize._80x80);
protected override Image GetResourceImage(string rescName)
{
var image = Properties.Resources.ResourceManager.GetObject(rescName);
return image as Bitmap;
}
}
}