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:
parent
657a7bb6bc
commit
bff9b67b72
@ -9,7 +9,7 @@ namespace LibationAvalonia.Controls
|
||||
{
|
||||
//Only SeriesEntry types have three-state checks, individual LibraryEntry books are binary.
|
||||
var ele = base.GenerateEditingElementDirect(cell, dataItem) as CheckBox;
|
||||
ele.IsThreeState = dataItem is ISeriesEntry;
|
||||
ele.IsThreeState = dataItem is SeriesEntry;
|
||||
return ele;
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,11 +34,11 @@ namespace LibationAvalonia.Controls
|
||||
private static void Cell_ContextRequested(object sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
if (sender is DataGridCell cell &&
|
||||
cell.DataContext is IGridEntry clickedEntry &&
|
||||
cell.DataContext is GridEntry clickedEntry &&
|
||||
OwningColumnProperty.GetValue(cell) is DataGridColumn column &&
|
||||
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);
|
||||
if (clickedIndex == -1)
|
||||
{
|
||||
@ -101,7 +101,7 @@ namespace LibationAvalonia.Controls
|
||||
private static string RemoveLineBreaks(string text)
|
||||
=> 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();
|
||||
return string.Join("\t", contents);
|
||||
@ -109,7 +109,7 @@ namespace LibationAvalonia.Controls
|
||||
|
||||
public required DataGrid Grid { 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 AvaloniaList<Control> ContextMenuItems
|
||||
=> ContextMenu.ItemsSource as AvaloniaList<Control>;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -57,7 +57,7 @@ namespace LibationAvalonia.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public void LiberateSeriesClicked(ISeriesEntry series)
|
||||
public void LiberateSeriesClicked(SeriesEntry series)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using LibationFileManager;
|
||||
using LibationUiBase;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
#nullable enable
|
||||
@ -23,6 +24,20 @@ namespace LibationAvalonia.ViewModels
|
||||
PictureStorage.SetDefaultImage(PictureSize.Native, ms3.ToArray());
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,9 +26,9 @@ namespace LibationAvalonia.ViewModels
|
||||
public event EventHandler<int>? RemovableCountChanged;
|
||||
|
||||
/// <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>
|
||||
private HashSet<IGridEntry>? FilteredInGridEntries;
|
||||
private HashSet<GridEntry>? FilteredInGridEntries;
|
||||
public string? FilterString { get; private set; }
|
||||
|
||||
private DataGridCollectionView? _gridEntries;
|
||||
@ -43,15 +43,15 @@ namespace LibationAvalonia.ViewModels
|
||||
|
||||
public List<LibraryBook> GetVisibleBookEntries()
|
||||
=> FilteredInGridEntries?
|
||||
.OfType<ILibraryBookEntry>()
|
||||
.OfType<LibraryBookEntry>()
|
||||
.Select(lbe => lbe.LibraryBook)
|
||||
.ToList()
|
||||
?? SOURCE
|
||||
.OfType<ILibraryBookEntry>()
|
||||
.OfType<LibraryBookEntry>()
|
||||
.Select(lbe => lbe.LibraryBook)
|
||||
.ToList();
|
||||
|
||||
private IEnumerable<ILibraryBookEntry> GetAllBookEntries()
|
||||
private IEnumerable<LibraryBookEntry> GetAllBookEntries()
|
||||
=> SOURCE
|
||||
.BookEntries();
|
||||
|
||||
@ -112,8 +112,8 @@ namespace LibationAvalonia.ViewModels
|
||||
var sc = await Dispatcher.UIThread.InvokeAsync(() => AvaloniaSynchronizationContext.Current);
|
||||
AvaloniaSynchronizationContext.SetSynchronizationContext(sc);
|
||||
|
||||
var geList = await LibraryBookEntry<AvaloniaEntryStatus>.GetAllProductsAsync(dbBooks);
|
||||
var seriesEntries = await SeriesEntry<AvaloniaEntryStatus>.GetAllSeriesEntriesAsync(dbBooks);
|
||||
var geList = await LibraryBookEntry.GetAllProductsAsync(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
|
||||
//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)
|
||||
{
|
||||
var count
|
||||
= FilteredInGridEntries?.OfType<ILibraryBookEntry>().Count()
|
||||
?? SOURCE.OfType<ILibraryBookEntry>().Count();
|
||||
= FilteredInGridEntries?.OfType<LibraryBookEntry>().Count()
|
||||
?? SOURCE.OfType<LibraryBookEntry>().Count();
|
||||
|
||||
VisibleCountChanged?.Invoke(this, count);
|
||||
}
|
||||
@ -223,9 +223,9 @@ namespace LibationAvalonia.ViewModels
|
||||
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)
|
||||
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)
|
||||
// Add the new product to top
|
||||
SOURCE.Insert(0, new LibraryBookEntry<AvaloniaEntryStatus>(book));
|
||||
SOURCE.Insert(0, new LibraryBookEntry(book));
|
||||
else
|
||||
// update existing
|
||||
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)
|
||||
{
|
||||
ILibraryBookEntry episodeEntry;
|
||||
LibraryBookEntry episodeEntry;
|
||||
|
||||
var seriesEntry = seriesEntries.FindSeriesParent(episodeBook);
|
||||
|
||||
@ -270,7 +270,7 @@ namespace LibationAvalonia.ViewModels
|
||||
return;
|
||||
}
|
||||
|
||||
seriesEntry = new SeriesEntry<AvaloniaEntryStatus>(seriesBook, episodeBook);
|
||||
seriesEntry = new SeriesEntry(seriesBook, episodeBook);
|
||||
seriesEntries.Add(seriesEntry);
|
||||
|
||||
episodeEntry = seriesEntry.Children[0];
|
||||
@ -280,7 +280,7 @@ namespace LibationAvalonia.ViewModels
|
||||
else
|
||||
{
|
||||
//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.Sort((c1, c2) => c1.SeriesIndex.CompareTo(c2.SeriesIndex));
|
||||
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;
|
||||
|
||||
@ -332,7 +332,7 @@ namespace LibationAvalonia.ViewModels
|
||||
|
||||
private bool CollectionFilter(object item)
|
||||
{
|
||||
if (item is ILibraryBookEntry lbe
|
||||
if (item is LibraryBookEntry lbe
|
||||
&& lbe.Liberate.IsEpisode
|
||||
&& lbe.Parent?.Liberate?.Expanded != true)
|
||||
return false;
|
||||
@ -454,7 +454,7 @@ namespace LibationAvalonia.ViewModels
|
||||
|
||||
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);
|
||||
RemovableCountChanged?.Invoke(this, removeCount);
|
||||
|
||||
@ -17,7 +17,7 @@ namespace LibationAvalonia.ViewModels
|
||||
public RowComparer(DataGridColumn? 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
|
||||
|
||||
@ -53,8 +53,8 @@ namespace LibationAvalonia.Views
|
||||
private void LiberateStatusButton_DataContextChanged(object sender, EventArgs e)
|
||||
{
|
||||
//Force book status recheck when an entry is scrolled into view.
|
||||
//This will force a recheck for a paprtially downloaded file.
|
||||
var status = DataContext as ILibraryBookEntry;
|
||||
//This will force a recheck for a partially downloaded file.
|
||||
var status = DataContext as LibraryBookEntry;
|
||||
status?.Liberate.Invalidate(nameof(status.Liberate.BookStatus));
|
||||
}
|
||||
|
||||
|
||||
@ -137,7 +137,7 @@ namespace LibationAvalonia.Views
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
BookDetailsDialog bookDetailsForm;
|
||||
|
||||
@ -59,7 +59,7 @@
|
||||
Width="75">
|
||||
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<DataTemplate x:DataType="uibase:GridEntry">
|
||||
<CheckBox
|
||||
HorizontalAlignment="Center"
|
||||
IsThreeState="True"
|
||||
@ -70,7 +70,7 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt CanUserResize="False" CanUserSort="True" Header="Liberate" SortMemberPath="Liberate" ClipboardContentBinding="{Binding Liberate.ToolTip}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<DataTemplate x:DataType="uibase:GridEntry">
|
||||
<views:LiberateStatusButton
|
||||
ToolTip.Tip="{CompiledBinding Liberate.ToolTip}"
|
||||
BookStatus="{CompiledBinding Liberate.BookStatus}"
|
||||
@ -85,7 +85,7 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt CanUserResize="False" CanUserSort="False" Header="Cover" SortMemberPath="Cover" ClipboardContentBinding="{Binding LibraryBook.Book.PictureLarge}">
|
||||
<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" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
@ -93,7 +93,7 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt Header="Title" MinWidth="10" Width="{Binding TitleWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Title" ClipboardContentBinding="{Binding Title}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<DataTemplate x:DataType="uibase:GridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}">
|
||||
<TextBlock Classes="h1" Text="{CompiledBinding Title}" />
|
||||
</Panel>
|
||||
@ -103,7 +103,7 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt Header="Authors" MinWidth="10" Width="{Binding AuthorsWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Authors" ClipboardContentBinding="{Binding Authors}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<DataTemplate x:DataType="uibase:GridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}">
|
||||
<TextBlock Text="{CompiledBinding Authors}" />
|
||||
</Panel>
|
||||
@ -113,7 +113,7 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt Header="Narrators" MinWidth="10" Width="{Binding NarratorsWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Narrators" ClipboardContentBinding="{Binding Narrators}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<DataTemplate x:DataType="uibase:GridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}">
|
||||
<TextBlock Text="{CompiledBinding Narrators}" />
|
||||
</Panel>
|
||||
@ -123,7 +123,7 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt Header="Length" MinWidth="10" Width="{Binding LengthWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Length" ClipboardContentBinding="{Binding Length}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<DataTemplate x:DataType="uibase:GridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}">
|
||||
<TextBlock Text="{CompiledBinding Length}" />
|
||||
</Panel>
|
||||
@ -133,7 +133,7 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt Header="Series" MinWidth="10" Width="{Binding SeriesWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Series" ClipboardContentBinding="{Binding Series}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<DataTemplate x:DataType="uibase:GridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}">
|
||||
<TextBlock Text="{CompiledBinding Series}" />
|
||||
</Panel>
|
||||
@ -143,7 +143,7 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt Header="Series
Order" MinWidth="10" Width="{Binding SeriesOrderWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="SeriesOrder" ClipboardContentBinding="{Binding Series}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<DataTemplate x:DataType="uibase:GridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}">
|
||||
<TextBlock Text="{CompiledBinding SeriesOrder}" HorizontalAlignment="Center" />
|
||||
</Panel>
|
||||
@ -153,7 +153,7 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt Header="Description" MinWidth="10" Width="{Binding DescriptionWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Description" ClipboardContentBinding="{Binding Description}">
|
||||
<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" >
|
||||
<TextBlock Text="{CompiledBinding Description}" VerticalAlignment="Top" />
|
||||
</Panel>
|
||||
@ -163,7 +163,7 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt Header="Category" MinWidth="10" Width="{Binding CategoryWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="Category" ClipboardContentBinding="{Binding Category}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<DataTemplate x:DataType="uibase:GridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}">
|
||||
<TextBlock Text="{CompiledBinding Category}" />
|
||||
</Panel>
|
||||
@ -172,7 +172,7 @@
|
||||
</controls:DataGridTemplateColumnExt>
|
||||
|
||||
<controls:DataGridMyRatingColumn
|
||||
x:DataType="uibase:IGridEntry"
|
||||
x:DataType="uibase:GridEntry"
|
||||
Header="Product
Rating"
|
||||
IsReadOnly="true"
|
||||
MinWidth="10" Width="{Binding ProductRatingWidth, Mode=TwoWay}"
|
||||
@ -183,7 +183,7 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt Header="Purchase
Date" MinWidth="10" Width="{Binding PurchaseDateWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="PurchaseDate" ClipboardContentBinding="{Binding PurchaseDate}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<DataTemplate x:DataType="uibase:GridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}">
|
||||
<TextBlock Text="{CompiledBinding PurchaseDate}" />
|
||||
</Panel>
|
||||
@ -192,7 +192,7 @@
|
||||
</controls:DataGridTemplateColumnExt>
|
||||
|
||||
<controls:DataGridMyRatingColumn
|
||||
x:DataType="uibase:IGridEntry"
|
||||
x:DataType="uibase:GridEntry"
|
||||
Header="My Rating"
|
||||
IsReadOnly="false"
|
||||
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}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<DataTemplate x:DataType="uibase:GridEntry">
|
||||
<Panel Opacity="{CompiledBinding Liberate.Opacity}">
|
||||
<TextBlock Text="{CompiledBinding Misc}" TextWrapping="WrapWithOverflow" />
|
||||
</Panel>
|
||||
@ -213,7 +213,7 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt Header="Last
Download" MinWidth="10" Width="{Binding LastDownloadWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="LastDownload" ClipboardContentBinding="{Binding LastDownload}">
|
||||
<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">
|
||||
<TextBlock Text="{CompiledBinding LastDownload}" TextWrapping="WrapWithOverflow" />
|
||||
</Panel>
|
||||
@ -223,7 +223,7 @@
|
||||
|
||||
<controls:DataGridTemplateColumnExt Header="Tags" MinWidth="10" Width="{Binding BookTagsWidth, Mode=TwoWay}" CanUserSort="True" SortMemberPath="BookTags" ClipboardContentBinding="{Binding BookTags}">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate x:DataType="uibase:IGridEntry">
|
||||
<DataTemplate x:DataType="uibase:GridEntry">
|
||||
<Button
|
||||
IsVisible="{CompiledBinding !Liberate.IsSeries}"
|
||||
VerticalAlignment="Stretch"
|
||||
|
||||
@ -26,7 +26,7 @@ namespace LibationAvalonia.Views
|
||||
public partial class ProductsDisplay : UserControl
|
||||
{
|
||||
public event EventHandler<LibraryBook[]>? LiberateClicked;
|
||||
public event EventHandler<ISeriesEntry>? LiberateSeriesClicked;
|
||||
public event EventHandler<SeriesEntry>? LiberateSeriesClicked;
|
||||
public event EventHandler<LibraryBook[]>? ConvertToMp3Clicked;
|
||||
public event EventHandler<LibraryBook>? TagsButtonClicked;
|
||||
|
||||
@ -102,7 +102,7 @@ namespace LibationAvalonia.Views
|
||||
|
||||
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");
|
||||
else
|
||||
e.Row.DynamicResource(DataGridRow.BackgroundProperty, "SystemRegionColor");
|
||||
@ -173,10 +173,10 @@ namespace LibationAvalonia.Views
|
||||
{
|
||||
switch (column.SortMemberPath)
|
||||
{
|
||||
case nameof(IGridEntry.Liberate):
|
||||
case nameof(GridEntry.Liberate):
|
||||
column.Width = new DataGridLength(BaseLiberateWidth * scaleFactor);
|
||||
break;
|
||||
case nameof(IGridEntry.Cover):
|
||||
case nameof(GridEntry.Cover):
|
||||
column.Width = new DataGridLength(BaseCoverWidth * scaleFactor);
|
||||
break;
|
||||
}
|
||||
@ -220,7 +220,7 @@ namespace LibationAvalonia.Views
|
||||
|
||||
#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()
|
||||
{
|
||||
@ -253,7 +253,7 @@ namespace LibationAvalonia.Views
|
||||
#endregion
|
||||
#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
|
||||
{
|
||||
@ -301,7 +301,7 @@ namespace LibationAvalonia.Views
|
||||
|
||||
#endregion
|
||||
#region Liberate All (multiple books only)
|
||||
if (entries.OfType<ILibraryBookEntry>().Count() > 1)
|
||||
if (entries.OfType<LibraryBookEntry>().Count() > 1)
|
||||
{
|
||||
args.ContextMenuItems.Add(new MenuItem
|
||||
{
|
||||
@ -325,7 +325,7 @@ namespace LibationAvalonia.Views
|
||||
|
||||
#endregion
|
||||
#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()
|
||||
{
|
||||
@ -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
|
||||
{
|
||||
@ -391,7 +391,7 @@ namespace LibationAvalonia.Views
|
||||
#endregion
|
||||
#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
|
||||
{
|
||||
@ -447,7 +447,7 @@ namespace LibationAvalonia.Views
|
||||
{
|
||||
var itemName = column.SortMemberPath;
|
||||
|
||||
if (itemName == nameof(IGridEntry.Remove))
|
||||
if (itemName == nameof(GridEntry.Remove))
|
||||
continue;
|
||||
|
||||
menuItems.Add
|
||||
@ -536,7 +536,7 @@ namespace LibationAvalonia.Views
|
||||
if (sender is not LiberateStatusButton button)
|
||||
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);
|
||||
|
||||
@ -544,7 +544,7 @@ namespace LibationAvalonia.Views
|
||||
//to the topright cell. Reset focus onto the clicked button's cell.
|
||||
button.Focus();
|
||||
}
|
||||
else if (button.DataContext is ILibraryBookEntry lbEntry)
|
||||
else if (button.DataContext is LibraryBookEntry lbEntry)
|
||||
{
|
||||
LiberateClicked?.Invoke(this, [lbEntry.LibraryBook]);
|
||||
}
|
||||
@ -558,13 +558,13 @@ namespace LibationAvalonia.Views
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (imageDisplayDialog is null || !imageDisplayDialog.IsVisible)
|
||||
@ -605,7 +605,7 @@ namespace LibationAvalonia.Views
|
||||
|
||||
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 displayWindow = new DescriptionDisplayDialog
|
||||
@ -632,7 +632,7 @@ namespace LibationAvalonia.Views
|
||||
{
|
||||
var button = args.Source as Button;
|
||||
|
||||
if (button?.DataContext is ILibraryBookEntry lbEntry)
|
||||
if (button?.DataContext is LibraryBookEntry lbEntry)
|
||||
{
|
||||
TagsButtonClicked?.Invoke(this, lbEntry.LibraryBook);
|
||||
}
|
||||
|
||||
@ -1,13 +1,35 @@
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
|
||||
#nullable enable
|
||||
namespace LibationUiBase
|
||||
{
|
||||
public static class BaseUtil
|
||||
{
|
||||
/// <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 void SetLoadImageDelegate(Func<byte[], PictureSize, object> tryLoadImage)
|
||||
=> LoadImage = tryLoadImage;
|
||||
public static Func<byte[], PictureSize, object?> LoadImage => s_LoadImage ?? DefaultLoadImageImpl;
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,22 +1,15 @@
|
||||
using ApplicationServices;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.Threading;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
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.
|
||||
//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.
|
||||
public abstract class EntryStatus : ReactiveObject, IComparable
|
||||
public class EntryStatus : ReactiveObject, IComparable
|
||||
{
|
||||
public LiberatedStatus? PdfStatus => LibraryCommands.Pdf_Status(Book);
|
||||
public LiberatedStatus BookStatus
|
||||
@ -70,7 +63,7 @@ namespace LibationUiBase.GridView
|
||||
private readonly bool isAbsent;
|
||||
private static readonly Dictionary<string, object> iconCache = new();
|
||||
|
||||
protected EntryStatus(LibraryBook libraryBook)
|
||||
internal EntryStatus(LibraryBook libraryBook)
|
||||
{
|
||||
Book = ArgumentValidator.EnsureNotNull(libraryBook, nameof(libraryBook)).Book;
|
||||
isAbsent = libraryBook.AbsentFromLastScan is true;
|
||||
@ -78,9 +71,6 @@ namespace LibationUiBase.GridView
|
||||
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>
|
||||
public void Invalidate(params string[] properties)
|
||||
{
|
||||
@ -179,7 +169,7 @@ namespace LibationUiBase.GridView
|
||||
private object GetAndCacheResource(string rescName)
|
||||
{
|
||||
if (!iconCache.ContainsKey(rescName))
|
||||
iconCache[rescName] = GetResourceImage(rescName);
|
||||
iconCache[rescName] = BaseUtil.LoadResourceImage(rescName);
|
||||
return iconCache[rescName];
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,17 +29,17 @@ public class GridContextMenu
|
||||
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 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 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 ReDownloadEnabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated);
|
||||
|
||||
private IGridEntry[] GridEntries { get; }
|
||||
public ILibraryBookEntry[] LibraryBookEntries { get; }
|
||||
private GridEntry[] GridEntries { get; }
|
||||
public LibraryBookEntry[] LibraryBookEntries { get; }
|
||||
public char Accelerator { get; }
|
||||
|
||||
public GridContextMenu(IGridEntry[] gridEntries, char accelerator)
|
||||
public GridContextMenu(GridEntry[] gridEntries, char accelerator)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(gridEntries, nameof(gridEntries));
|
||||
ArgumentOutOfRangeException.ThrowIfZero(gridEntries.Length, $"{nameof(gridEntries)}.{nameof(gridEntries.Length)}");
|
||||
@ -48,9 +48,9 @@ public class GridContextMenu
|
||||
Accelerator = accelerator;
|
||||
LibraryBookEntries
|
||||
= GridEntries
|
||||
.OfType<ISeriesEntry>()
|
||||
.OfType<SeriesEntry>()
|
||||
.SelectMany(s => s.Children)
|
||||
.Concat(GridEntries.OfType<ILibraryBookEntry>())
|
||||
.Concat(GridEntries.OfType<LibraryBookEntry>())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ namespace LibationUiBase.GridView
|
||||
}
|
||||
|
||||
/// <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 LibraryBook LibraryBook { get; protected set; }
|
||||
@ -100,7 +100,7 @@ namespace LibationUiBase.GridView
|
||||
LibraryBook = libraryBook;
|
||||
|
||||
var expanded = Liberate?.Expanded ?? false;
|
||||
Liberate = TStatus.Create(libraryBook);
|
||||
Liberate = new EntryStatus(libraryBook);
|
||||
Liberate.Expanded = expanded;
|
||||
|
||||
Title = Book.TitleWithSubtitle;
|
||||
@ -239,7 +239,7 @@ namespace LibationUiBase.GridView
|
||||
PictureStorage.PictureCached += PictureStorage_PictureCached;
|
||||
|
||||
// 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)
|
||||
@ -253,7 +253,7 @@ namespace LibationUiBase.GridView
|
||||
// logic validation
|
||||
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));
|
||||
PictureStorage.PictureCached -= PictureStorage_PictureCached;
|
||||
}
|
||||
@ -310,13 +310,12 @@ namespace LibationUiBase.GridView
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <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>
|
||||
/// <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)
|
||||
where TEntry : IGridEntry
|
||||
where TEntry : GridEntry
|
||||
{
|
||||
var products = libraryBooks.Where(includeIf).ToArray();
|
||||
if (products.Length == 0)
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
namespace LibationUiBase.GridView
|
||||
{
|
||||
public interface ILibraryBookEntry : IGridEntry
|
||||
{
|
||||
ISeriesEntry Parent { get; }
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -7,10 +7,10 @@ using System.Threading.Tasks;
|
||||
namespace LibationUiBase.GridView
|
||||
{
|
||||
/// <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 ISeriesEntry Parent { get; }
|
||||
[Browsable(false)] public SeriesEntry Parent { get; }
|
||||
|
||||
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;
|
||||
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"/>.
|
||||
/// </summary>
|
||||
/// <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)
|
||||
=> await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsProduct(), lb => new LibraryBookEntry<TStatus>(lb) as IGridEntry);
|
||||
public static async Task<List<GridEntry>> GetAllProductsAsync(IEnumerable<LibraryBook> libraryBooks)
|
||||
=> await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsProduct(), lb => new LibraryBookEntry(lb) as GridEntry);
|
||||
|
||||
protected override string GetBookTags() => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
|
||||
}
|
||||
@ -10,19 +10,19 @@ namespace LibationUiBase.GridView
|
||||
#nullable enable
|
||||
public static class QueryExtensions
|
||||
{
|
||||
public static IEnumerable<ILibraryBookEntry> BookEntries(this IEnumerable<IGridEntry> gridEntries)
|
||||
=> gridEntries.OfType<ILibraryBookEntry>();
|
||||
public static IEnumerable<LibraryBookEntry> BookEntries(this IEnumerable<GridEntry> gridEntries)
|
||||
=> gridEntries.OfType<LibraryBookEntry>();
|
||||
|
||||
public static IEnumerable<ISeriesEntry> SeriesEntries(this IEnumerable<IGridEntry> gridEntries)
|
||||
=> gridEntries.OfType<ISeriesEntry>();
|
||||
public static IEnumerable<SeriesEntry> SeriesEntries(this IEnumerable<GridEntry> gridEntries)
|
||||
=> 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);
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
@ -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 not null &&
|
||||
otherSet is not null &&
|
||||
searchSet.Intersect(otherSet).Count() != searchSet.Count);
|
||||
|
||||
[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))
|
||||
return null;
|
||||
@ -59,7 +59,7 @@ namespace LibationUiBase.GridView
|
||||
var booksFilteredIn = entries.IntersectBy(searchResultSet.Docs.Select(d => d.ProductId), l => l.AudibleProductId);
|
||||
|
||||
//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();
|
||||
}
|
||||
|
||||
@ -10,16 +10,16 @@ namespace LibationUiBase.GridView
|
||||
/// are sorted by PropertyName while all episodes remain immediately beneath their parents and remain
|
||||
/// sorted by series index, ascending.
|
||||
/// </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 int Compare(object? x, object? y)
|
||||
=> Compare(x as IGridEntry, y as IGridEntry);
|
||||
=> Compare(x as GridEntry, y as GridEntry);
|
||||
|
||||
protected abstract ListSortDirection GetSortOrder();
|
||||
|
||||
private int InternalCompare(IGridEntry x, IGridEntry y)
|
||||
private int InternalCompare(GridEntry x, GridEntry y)
|
||||
{
|
||||
var val1 = x.GetMemberValue(PropertyName);
|
||||
var val2 = y.GetMemberValue(PropertyName);
|
||||
@ -32,7 +32,7 @@ namespace LibationUiBase.GridView
|
||||
: 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 not null && geB is null) return 1;
|
||||
@ -40,12 +40,12 @@ namespace LibationUiBase.GridView
|
||||
|
||||
var sortDirection = GetSortOrder();
|
||||
|
||||
ISeriesEntry? parentA = null;
|
||||
ISeriesEntry? parentB = null;
|
||||
SeriesEntry? parentA = 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;
|
||||
if (geB is ILibraryBookEntry lbB && lbB.Parent is ISeriesEntry seB)
|
||||
if (geB is LibraryBookEntry lbB && lbB.Parent is SeriesEntry seB)
|
||||
parentB = seB;
|
||||
|
||||
//both entries are children
|
||||
@ -59,7 +59,7 @@ namespace LibationUiBase.GridView
|
||||
//and DateAdded, compare SeriesOrder instead..
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
@ -9,9 +9,9 @@ using System.Threading.Tasks;
|
||||
namespace LibationUiBase.GridView
|
||||
{
|
||||
/// <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);
|
||||
|
||||
private bool suspendCounting = false;
|
||||
@ -49,9 +49,9 @@ namespace LibationUiBase.GridView
|
||||
SeriesIndex = -1;
|
||||
|
||||
Children = children
|
||||
.Select(c => new LibraryBookEntry<TStatus>(c, this))
|
||||
.Select(c => new LibraryBookEntry(c, this))
|
||||
.OrderByDescending(c => c.SeriesOrder)
|
||||
.ToList<ILibraryBookEntry>();
|
||||
.ToList<LibraryBookEntry>();
|
||||
|
||||
UpdateLibraryBook(parent);
|
||||
LoadCover();
|
||||
@ -61,9 +61,9 @@ namespace LibationUiBase.GridView
|
||||
/// Creates <see cref="SeriesEntry{TStatus}"/> for all episodic series in an enumeration of <see cref="LibraryBook"/>.
|
||||
/// </summary>
|
||||
/// <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);
|
||||
await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsEpisodeChild(), CreateAndLinkEpisodeEntry);
|
||||
|
||||
@ -77,13 +77,13 @@ namespace LibationUiBase.GridView
|
||||
return seriesEntries.Where(s => s.Children.Count != 0).ToList();
|
||||
|
||||
//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)
|
||||
{
|
||||
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);
|
||||
return entry;
|
||||
}
|
||||
@ -92,7 +92,7 @@ namespace LibationUiBase.GridView
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveChild(ILibraryBookEntry lbe)
|
||||
public void RemoveChild(LibraryBookEntry lbe)
|
||||
{
|
||||
Children.Remove(lbe);
|
||||
PurchaseDate = GetPurchaseDateString();
|
||||
2
Source/LibationWinForms/Form1.Designer.cs
generated
2
Source/LibationWinForms/Form1.Designer.cs
generated
@ -538,7 +538,7 @@
|
||||
this.productsDisplay.VisibleCountChanged += new System.EventHandler<int>(this.productsDisplay_VisibleCountChanged);
|
||||
this.productsDisplay.RemovableCountChanged += new System.EventHandler<int>(this.productsDisplay_RemovableCountChanged);
|
||||
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.InitialLoaded += new System.EventHandler(this.productsDisplay_InitialLoaded);
|
||||
//
|
||||
|
||||
@ -48,7 +48,7 @@ namespace LibationWinForms
|
||||
}
|
||||
}
|
||||
|
||||
private void ProductsDisplay_LiberateSeriesClicked(object sender, ISeriesEntry series)
|
||||
private void ProductsDisplay_LiberateSeriesClicked(object sender, SeriesEntry series)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@ -22,6 +22,7 @@ namespace LibationWinForms
|
||||
PictureStorage.SetDefaultImage(PictureSize.Native, Properties.Resources.default_cover_500x500.ToBytes(format));
|
||||
|
||||
BaseUtil.SetLoadImageDelegate(WinFormsUtil.TryLoadImageOrDefault);
|
||||
BaseUtil.SetLoadResourceImageDelegate(Properties.Resources.ResourceManager.GetObject);
|
||||
|
||||
// wire-up event to automatically download after scan.
|
||||
// winforms only. this should NOT be allowed in cli
|
||||
|
||||
@ -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)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
@ -24,24 +24,24 @@ namespace LibationWinForms.GridView
|
||||
* event. Adding or removing from the underlying list will not change the
|
||||
* 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;
|
||||
ListChanged += GridEntryBindingList_ListChanged;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public IEnumerable<ILibraryBookEntry> GetFilteredInItems()
|
||||
public IEnumerable<LibraryBookEntry> GetFilteredInItems()
|
||||
=> FilteredInGridEntries?
|
||||
.OfType<ILibraryBookEntry>()
|
||||
.OfType<LibraryBookEntry>()
|
||||
?? FilterRemoved
|
||||
.OfType<ILibraryBookEntry>()
|
||||
.Union(Items.OfType<ILibraryBookEntry>());
|
||||
.OfType<LibraryBookEntry>()
|
||||
.Union(Items.OfType<LibraryBookEntry>());
|
||||
|
||||
public bool SupportsFiltering => true;
|
||||
public string Filter
|
||||
@ -67,12 +67,12 @@ namespace LibationWinForms.GridView
|
||||
protected override ListSortDirection SortDirectionCore => Comparer.SortOrder;
|
||||
|
||||
/// <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 bool isSorted;
|
||||
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>
|
||||
private HashSet<IGridEntry> FilteredInGridEntries;
|
||||
private HashSet<GridEntry> FilteredInGridEntries;
|
||||
|
||||
#region Unused - Advanced Filtering
|
||||
public bool SupportsAdvancedSorting => false;
|
||||
@ -84,7 +84,7 @@ namespace LibationWinForms.GridView
|
||||
public ListSortDescriptionCollection SortDescriptions => throw new NotImplementedException();
|
||||
#endregion
|
||||
|
||||
public new void Remove(IGridEntry entry)
|
||||
public new void Remove(GridEntry entry)
|
||||
{
|
||||
FilterRemoved.Remove(entry);
|
||||
base.Remove(entry);
|
||||
@ -122,13 +122,13 @@ namespace LibationWinForms.GridView
|
||||
ResetList();
|
||||
RaiseListChangedEvents = priorState;
|
||||
|
||||
void addRemovedItemsBack(List<IGridEntry> addBackEntries)
|
||||
void addRemovedItemsBack(List<GridEntry> addBackEntries)
|
||||
{
|
||||
//Add removed entries back into Items so they are displayed
|
||||
//(except for episodes that are collapsed)
|
||||
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;
|
||||
|
||||
FilterRemoved.Remove(addBack);
|
||||
@ -160,7 +160,7 @@ namespace LibationWinForms.GridView
|
||||
ExpandItem(series);
|
||||
}
|
||||
|
||||
public void CollapseItem(ISeriesEntry sEntry)
|
||||
public void CollapseItem(SeriesEntry sEntry)
|
||||
{
|
||||
foreach (var episode in sEntry.Children.Intersect(Items.BookEntries()).ToList())
|
||||
{
|
||||
@ -171,7 +171,7 @@ namespace LibationWinForms.GridView
|
||||
sEntry.Liberate.Expanded = false;
|
||||
}
|
||||
|
||||
public void ExpandItem(ISeriesEntry sEntry)
|
||||
public void ExpandItem(SeriesEntry sEntry)
|
||||
{
|
||||
var sindex = Items.IndexOf(sEntry);
|
||||
|
||||
@ -208,7 +208,7 @@ namespace LibationWinForms.GridView
|
||||
|
||||
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.
|
||||
var sortedItems = Comparer.OrderEntries(itemsList).ToList();
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
using DataLayer;
|
||||
using LibationUiBase.GridView;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
#nullable enable
|
||||
namespace LibationWinForms.GridView
|
||||
{
|
||||
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 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)
|
||||
//Don't paint the button graphic
|
||||
paintParts ^= DataGridViewPaintParts.ContentBackground | DataGridViewPaintParts.ContentForeground | DataGridViewPaintParts.SelectionBackground;
|
||||
|
||||
DataGridView.Rows[rowIndex].DefaultCellStyle.BackColor = (Color)status.BackgroundBrush;
|
||||
DataGridView.Rows[rowIndex].DefaultCellStyle.ForeColor = status.Opacity == 1 ? DataGridView.DefaultCellStyle.ForeColor : HiddenForeColor;
|
||||
row.DefaultCellStyle.BackColor = status.IsEpisode ? SERIES_BG_COLOR : grid.DefaultCellStyle.BackColor;
|
||||
row.DefaultCellStyle.ForeColor = status.Opacity == 1 ? grid.DefaultCellStyle.ForeColor : HiddenForeColor;
|
||||
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts);
|
||||
|
||||
DrawButtonImage(graphics, (Image)status.ButtonImage, cellBounds);
|
||||
|
||||
@ -22,7 +22,7 @@ namespace LibationWinForms.GridView
|
||||
public event EventHandler<int> VisibleCountChanged;
|
||||
public event EventHandler<int> RemovableCountChanged;
|
||||
public event EventHandler<LibraryBook[]> LiberateClicked;
|
||||
public event EventHandler<ISeriesEntry> LiberateSeriesClicked;
|
||||
public event EventHandler<SeriesEntry> LiberateSeriesClicked;
|
||||
public event EventHandler<LibraryBook[]> ConvertToMp3Clicked;
|
||||
public event EventHandler InitialLoaded;
|
||||
|
||||
@ -36,7 +36,7 @@ namespace LibationWinForms.GridView
|
||||
#region Button controls
|
||||
|
||||
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);
|
||||
|
||||
@ -71,7 +71,7 @@ namespace LibationWinForms.GridView
|
||||
imageDisplay.Show(null);
|
||||
}
|
||||
|
||||
private void productsGrid_DescriptionClicked(IGridEntry liveGridEntry, Rectangle cellRectangle)
|
||||
private void productsGrid_DescriptionClicked(GridEntry liveGridEntry, Rectangle cellRectangle)
|
||||
{
|
||||
var displayWindow = new DescriptionDisplay
|
||||
{
|
||||
@ -90,7 +90,7 @@ namespace LibationWinForms.GridView
|
||||
displayWindow.Show(this);
|
||||
}
|
||||
|
||||
private async void productsGrid_DetailsClicked(ILibraryBookEntry liveGridEntry)
|
||||
private async void productsGrid_DetailsClicked(LibraryBookEntry liveGridEntry)
|
||||
{
|
||||
// HACK: workaround for a Winforms bug.
|
||||
// This event is fired by the DataGridCell.OnMouseUpInternal
|
||||
@ -124,12 +124,12 @@ namespace LibationWinForms.GridView
|
||||
|
||||
#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, '&');
|
||||
#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()
|
||||
{
|
||||
@ -166,7 +166,7 @@ namespace LibationWinForms.GridView
|
||||
#endregion
|
||||
#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 };
|
||||
ctxMenu.Items.Add(locateFileMenuItem);
|
||||
@ -199,7 +199,7 @@ namespace LibationWinForms.GridView
|
||||
|
||||
#endregion
|
||||
#region Liberate All (multiple books only)
|
||||
if (entries.OfType<ILibraryBookEntry>().Count() > 1)
|
||||
if (entries.OfType<LibraryBookEntry>().Count() > 1)
|
||||
{
|
||||
var downloadSelectedMenuItem = new ToolStripMenuItem()
|
||||
{
|
||||
@ -230,7 +230,7 @@ namespace LibationWinForms.GridView
|
||||
#endregion
|
||||
#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()
|
||||
{
|
||||
@ -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 fileTemplateMenuItem = new ToolStripMenuItem { Text = ctx.FileTemplateText };
|
||||
@ -288,7 +288,7 @@ namespace LibationWinForms.GridView
|
||||
#endregion
|
||||
#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 };
|
||||
bookRecordMenuItem.Click += (_, _) => new BookRecordsDialog(entry3.LibraryBook).ShowDialog(this);
|
||||
@ -417,7 +417,7 @@ namespace LibationWinForms.GridView
|
||||
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
|
||||
&& !liveGridEntry.Liberate.IsUnavailable)
|
||||
|
||||
@ -270,7 +270,7 @@ namespace LibationWinForms.GridView
|
||||
//
|
||||
// syncBindingSource
|
||||
//
|
||||
syncBindingSource.DataSource = typeof(IGridEntry);
|
||||
syncBindingSource.DataSource = typeof(GridEntry);
|
||||
//
|
||||
// ProductsGrid
|
||||
//
|
||||
|
||||
@ -15,10 +15,10 @@ using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.GridView
|
||||
{
|
||||
public delegate void GridEntryClickedEventHandler(IGridEntry liveGridEntry);
|
||||
public delegate void LibraryBookEntryClickedEventHandler(ILibraryBookEntry liveGridEntry);
|
||||
public delegate void GridEntryRectangleClickedEventHandler(IGridEntry liveGridEntry, Rectangle cellRectangle);
|
||||
public delegate void ProductsGridCellContextMenuStripNeededEventHandler(IGridEntry[] liveGridEntry, ContextMenuStrip ctxMenu);
|
||||
public delegate void GridEntryClickedEventHandler(GridEntry liveGridEntry);
|
||||
public delegate void LibraryBookEntryClickedEventHandler(LibraryBookEntry liveGridEntry);
|
||||
public delegate void GridEntryRectangleClickedEventHandler(GridEntry liveGridEntry, Rectangle cellRectangle);
|
||||
public delegate void ProductsGridCellContextMenuStripNeededEventHandler(GridEntry[] liveGridEntry, ContextMenuStrip ctxMenu);
|
||||
|
||||
public partial class ProductsGrid : UserControl
|
||||
{
|
||||
@ -37,8 +37,8 @@ namespace LibationWinForms.GridView
|
||||
=> bindingList
|
||||
?.GetFilteredInItems()
|
||||
.Select(lbe => lbe.LibraryBook) ?? Enumerable.Empty<LibraryBook>();
|
||||
internal IEnumerable<ILibraryBookEntry> GetAllBookEntries()
|
||||
=> bindingList?.AllItems().BookEntries() ?? Enumerable.Empty<ILibraryBookEntry>();
|
||||
internal IEnumerable<LibraryBookEntry> GetAllBookEntries()
|
||||
=> bindingList?.AllItems().BookEntries() ?? Enumerable.Empty<LibraryBookEntry>();
|
||||
|
||||
public ProductsGrid()
|
||||
{
|
||||
@ -189,7 +189,7 @@ namespace LibationWinForms.GridView
|
||||
.Distinct()
|
||||
.OrderBy(r => r.Index)
|
||||
.Select(r => r.DataBoundItem)
|
||||
.OfType<IGridEntry>()
|
||||
.OfType<GridEntry>()
|
||||
.ToArray();
|
||||
|
||||
var clickedIndex = Array.IndexOf(allSelected, clickedEntry);
|
||||
@ -225,7 +225,7 @@ namespace LibationWinForms.GridView
|
||||
return;
|
||||
|
||||
var entry = getGridEntry(e.RowIndex);
|
||||
if (entry is ILibraryBookEntry lbEntry)
|
||||
if (entry is LibraryBookEntry lbEntry)
|
||||
{
|
||||
if (e.ColumnIndex == liberateGVColumn.Index)
|
||||
LiberateClicked?.Invoke(lbEntry);
|
||||
@ -236,7 +236,7 @@ namespace LibationWinForms.GridView
|
||||
else if (e.ColumnIndex == coverGVColumn.Index)
|
||||
CoverClicked?.Invoke(lbEntry);
|
||||
}
|
||||
else if (entry is ISeriesEntry sEntry)
|
||||
else if (entry is SeriesEntry sEntry)
|
||||
{
|
||||
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
|
||||
|
||||
@ -295,8 +295,8 @@ namespace LibationWinForms.GridView
|
||||
var sc = Invoke(() => System.Threading.SynchronizationContext.Current);
|
||||
System.Threading.SynchronizationContext.SetSynchronizationContext(sc);
|
||||
|
||||
var geList = await LibraryBookEntry<WinFormsEntryStatus>.GetAllProductsAsync(dbBooks);
|
||||
var seriesEntries = await SeriesEntry<WinFormsEntryStatus>.GetAllSeriesEntriesAsync(dbBooks);
|
||||
var geList = await LibraryBookEntry.GetAllProductsAsync(dbBooks);
|
||||
var seriesEntries = await SeriesEntry.GetAllSeriesEntriesAsync(dbBooks);
|
||||
|
||||
geList.AddRange(seriesEntries);
|
||||
//Sort descending by date (default sort property)
|
||||
@ -383,7 +383,7 @@ namespace LibationWinForms.GridView
|
||||
gridEntryDataGridView.FirstDisplayedScrollingRowIndex = topRow;
|
||||
}
|
||||
|
||||
public void RemoveBooks(IEnumerable<ILibraryBookEntry> removedBooks)
|
||||
public void RemoveBooks(IEnumerable<LibraryBookEntry> removedBooks)
|
||||
{
|
||||
if (bindingList == null)
|
||||
throw new InvalidOperationException($"Must call {nameof(BindToGridAsync)} before calling {nameof(RemoveBooks)}");
|
||||
@ -398,34 +398,34 @@ namespace LibationWinForms.GridView
|
||||
.AllItems()
|
||||
.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
|
||||
bindingList.Remove(removed);
|
||||
|
||||
VisibleCountChanged?.Invoke(this, bindingList.GetFilteredInItems().Count());
|
||||
}
|
||||
|
||||
private void AddOrUpdateBook(LibraryBook book, ILibraryBookEntry? existingBookEntry)
|
||||
private void AddOrUpdateBook(LibraryBook book, LibraryBookEntry? existingBookEntry)
|
||||
{
|
||||
if (bindingList == null)
|
||||
throw new InvalidOperationException($"Must call {nameof(BindToGridAsync)} before calling {nameof(AddOrUpdateBook)}");
|
||||
|
||||
if (existingBookEntry is null)
|
||||
// Add the new product to top
|
||||
bindingList.Insert(0, new LibraryBookEntry<WinFormsEntryStatus>(book));
|
||||
bindingList.Insert(0, new LibraryBookEntry(book));
|
||||
else
|
||||
// update existing
|
||||
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)
|
||||
throw new InvalidOperationException($"Must call {nameof(BindToGridAsync)} before calling {nameof(AddOrUpdateEpisode)}");
|
||||
|
||||
if (existingEpisodeEntry is null)
|
||||
{
|
||||
ILibraryBookEntry episodeEntry;
|
||||
LibraryBookEntry episodeEntry;
|
||||
|
||||
var seriesEntry = seriesEntries.FindSeriesParent(episodeBook);
|
||||
|
||||
@ -443,7 +443,7 @@ namespace LibationWinForms.GridView
|
||||
return;
|
||||
}
|
||||
|
||||
seriesEntry = new SeriesEntry<WinFormsEntryStatus>(seriesBook, episodeBook);
|
||||
seriesEntry = new SeriesEntry(seriesBook, episodeBook);
|
||||
seriesEntries.Add(seriesEntry);
|
||||
|
||||
episodeEntry = seriesEntry.Children[0];
|
||||
@ -453,7 +453,7 @@ namespace LibationWinForms.GridView
|
||||
else
|
||||
{
|
||||
//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.Sort((c1, c2) => c1.SeriesIndex.CompareTo(c2.SeriesIndex));
|
||||
var seriesBook = dbBooks.Single(lb => lb.Book.AudibleProductId == seriesEntry.LibraryBook.Book.AudibleProductId);
|
||||
|
||||
@ -8,13 +8,13 @@ namespace LibationWinForms.GridView
|
||||
internal class RowComparer : RowComparerBase
|
||||
{
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for ordering grid entries
|
||||
/// </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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user