Add multi-select context menu support (rmcrackan/Libation#1195)
This commit is contained in:
parent
bfcd226795
commit
c77f2e2162
@ -2,6 +2,7 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using LibationUiBase.GridView;
|
using LibationUiBase.GridView;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace LibationAvalonia.Controls
|
namespace LibationAvalonia.Controls
|
||||||
@ -12,11 +13,13 @@ namespace LibationAvalonia.Controls
|
|||||||
private static readonly ContextMenu ContextMenu = new();
|
private static readonly ContextMenu ContextMenu = new();
|
||||||
private static readonly AvaloniaList<Control> MenuItems = new();
|
private static readonly AvaloniaList<Control> MenuItems = new();
|
||||||
private static readonly PropertyInfo OwningColumnProperty;
|
private static readonly PropertyInfo OwningColumnProperty;
|
||||||
|
private static readonly PropertyInfo OwningGridProperty;
|
||||||
|
|
||||||
static DataGridContextMenus()
|
static DataGridContextMenus()
|
||||||
{
|
{
|
||||||
ContextMenu.ItemsSource = MenuItems;
|
ContextMenu.ItemsSource = MenuItems;
|
||||||
OwningColumnProperty = typeof(DataGridCell).GetProperty("OwningColumn", BindingFlags.Instance | BindingFlags.NonPublic);
|
OwningColumnProperty = typeof(DataGridCell).GetProperty("OwningColumn", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
OwningGridProperty = typeof(DataGridColumn).GetProperty("OwningGrid", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AttachContextMenu(this DataGridCell cell)
|
public static void AttachContextMenu(this DataGridCell cell)
|
||||||
@ -30,19 +33,35 @@ 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 && cell.DataContext is IGridEntry entry)
|
if (sender is DataGridCell cell &&
|
||||||
|
cell.DataContext is IGridEntry clickedEntry &&
|
||||||
|
OwningColumnProperty.GetValue(cell) is DataGridColumn column &&
|
||||||
|
OwningGridProperty.GetValue(column) is DataGrid grid)
|
||||||
{
|
{
|
||||||
|
var allSelected = grid.SelectedItems.OfType<IGridEntry>().ToArray();
|
||||||
|
var clickedIndex = Array.IndexOf(allSelected, clickedEntry);
|
||||||
|
if (clickedIndex == -1)
|
||||||
|
{
|
||||||
|
//User didn't right-click on a selected cell
|
||||||
|
grid.SelectedItem = clickedEntry;
|
||||||
|
allSelected = [clickedEntry];
|
||||||
|
}
|
||||||
|
else if (clickedIndex > 0)
|
||||||
|
{
|
||||||
|
//Ensure the clicked entry is first in the list
|
||||||
|
(allSelected[0], allSelected[clickedIndex]) = (allSelected[clickedIndex], allSelected[0]);
|
||||||
|
}
|
||||||
|
|
||||||
var args = new DataGridCellContextMenuStripNeededEventArgs
|
var args = new DataGridCellContextMenuStripNeededEventArgs
|
||||||
{
|
{
|
||||||
Column = OwningColumnProperty.GetValue(cell) as DataGridColumn,
|
Column = column,
|
||||||
GridEntry = entry,
|
Grid = grid,
|
||||||
|
GridEntries = allSelected,
|
||||||
ContextMenu = ContextMenu
|
ContextMenu = ContextMenu
|
||||||
};
|
};
|
||||||
|
|
||||||
args.ContextMenuItems.Clear();
|
args.ContextMenuItems.Clear();
|
||||||
|
|
||||||
CellContextMenuStripNeeded?.Invoke(sender, args);
|
CellContextMenuStripNeeded?.Invoke(sender, args);
|
||||||
|
|
||||||
e.Handled = args.ContextMenuItems.Count == 0;
|
e.Handled = args.ContextMenuItems.Count == 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -61,10 +80,37 @@ namespace LibationAvalonia.Controls
|
|||||||
private static string GetCellValue(DataGridColumn column, object item)
|
private static string GetCellValue(DataGridColumn column, object item)
|
||||||
=> GetCellValueMethod.Invoke(column, new object[] { item, column.ClipboardContentBinding })?.ToString() ?? "";
|
=> GetCellValueMethod.Invoke(column, new object[] { item, column.ClipboardContentBinding })?.ToString() ?? "";
|
||||||
|
|
||||||
public string CellClipboardContents => GetCellValue(Column, GridEntry);
|
public string CellClipboardContents => GetCellValue(Column, GridEntries[0]);
|
||||||
public DataGridColumn Column { get; init; }
|
public string GetRowClipboardContents()
|
||||||
public IGridEntry GridEntry { get; init; }
|
{
|
||||||
public ContextMenu ContextMenu { get; init; }
|
if (GridEntries is null || GridEntries.Length == 0)
|
||||||
|
return string.Empty;
|
||||||
|
else if (GridEntries.Length == 1)
|
||||||
|
return HeaderNames + Environment.NewLine + GetRowClipboardContents(GridEntries[0]);
|
||||||
|
else
|
||||||
|
return string.Join(Environment.NewLine, GridEntries.Select(GetRowClipboardContents).Prepend(HeaderNames));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string HeaderNames
|
||||||
|
=> string.Join("\t",
|
||||||
|
Grid.Columns
|
||||||
|
.Where(c => c.IsVisible)
|
||||||
|
.OrderBy(c => c.DisplayIndex)
|
||||||
|
.Select(c => RemoveLineBreaks(c.Header.ToString())));
|
||||||
|
|
||||||
|
private static string RemoveLineBreaks(string text)
|
||||||
|
=> text.Replace("\r\n", "").Replace('\r', ' ').Replace('\n', ' ');
|
||||||
|
|
||||||
|
private string GetRowClipboardContents(IGridEntry 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public required DataGrid Grid { get; init; }
|
||||||
|
public required DataGridColumn Column { get; init; }
|
||||||
|
public required IGridEntry[] GridEntries { 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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,26 +33,29 @@ namespace LibationAvalonia.ViewModels
|
|||||||
setQueueCollapseState(collapseState);
|
setQueueCollapseState(collapseState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void LiberateClicked(LibraryBook libraryBook)
|
public async void LiberateClicked(LibraryBook[] libraryBooks)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (libraryBook.Book.UserDefinedItem.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload)
|
if (libraryBooks.Length == 1)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Information("Begin single book backup of {libraryBook}", libraryBook);
|
var item = libraryBooks[0];
|
||||||
setQueueCollapseState(false);
|
if (item.Book.UserDefinedItem.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload)
|
||||||
ProcessQueue.AddDownloadDecrypt(libraryBook);
|
|
||||||
}
|
|
||||||
else if (libraryBook.Book.UserDefinedItem.PdfStatus is LiberatedStatus.NotLiberated)
|
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Information("Begin single pdf backup of {libraryBook}", libraryBook);
|
Serilog.Log.Logger.Information("Begin single book backup of {libraryBook}", item);
|
||||||
setQueueCollapseState(false);
|
setQueueCollapseState(false);
|
||||||
ProcessQueue.AddDownloadPdf(libraryBook);
|
ProcessQueue.AddDownloadDecrypt(item);
|
||||||
}
|
}
|
||||||
else if (libraryBook.Book.Audio_Exists())
|
else if (item.Book.UserDefinedItem.PdfStatus is LiberatedStatus.NotLiberated)
|
||||||
|
{
|
||||||
|
Serilog.Log.Logger.Information("Begin single pdf backup of {libraryBook}", item);
|
||||||
|
setQueueCollapseState(false);
|
||||||
|
ProcessQueue.AddDownloadPdf(item);
|
||||||
|
}
|
||||||
|
else if (item.Book.Audio_Exists())
|
||||||
{
|
{
|
||||||
// liberated: open explorer to file
|
// liberated: open explorer to file
|
||||||
var filePath = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId);
|
var filePath = AudibleFileStorage.Audio.GetPath(item.Book.AudibleProductId);
|
||||||
|
|
||||||
if (!Go.To.File(filePath?.ShortPathName))
|
if (!Go.To.File(filePath?.ShortPathName))
|
||||||
{
|
{
|
||||||
@ -61,9 +64,23 @@ namespace LibationAvalonia.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var toLiberate
|
||||||
|
= libraryBooks
|
||||||
|
.Where(x => x.Book.UserDefinedItem.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload || x.Book.UserDefinedItem.PdfStatus is LiberatedStatus.NotLiberated)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (toLiberate.Length > 0)
|
||||||
|
{
|
||||||
|
setQueueCollapseState(false);
|
||||||
|
ProcessQueue.AddDownloadDecrypt(toLiberate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Error(ex, "An error occurred while handling the stop light button click for {libraryBook}", libraryBook);
|
Serilog.Log.Logger.Error(ex, "An error occurred while handling the stop light button click for {libraryBook}", libraryBooks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,20 +100,21 @@ namespace LibationAvalonia.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConvertToMp3Clicked(LibraryBook libraryBook)
|
public void ConvertToMp3Clicked(LibraryBook[] libraryBooks)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (libraryBook.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated)
|
var preLiberated = libraryBooks.Where(lb => lb.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated).ToArray();
|
||||||
|
if (preLiberated.Length > 0)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Information("Begin convert to mp3 {libraryBook}", libraryBook);
|
Serilog.Log.Logger.Information("Begin convert {count} books to mp3", preLiberated.Length);
|
||||||
setQueueCollapseState(false);
|
setQueueCollapseState(false);
|
||||||
ProcessQueue.AddConvertMp3(libraryBook);
|
ProcessQueue.AddConvertMp3(preLiberated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Error(ex, "An error occurred while handling the stop light button click for {libraryBook}", libraryBook);
|
Serilog.Log.Logger.Error(ex, "An error occurred while handling the stop light button click for {libraryBook}", libraryBooks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -91,19 +91,19 @@ namespace LibationAvalonia.ViewModels
|
|||||||
|
|
||||||
public decimal SpeedLimitIncrement { get; private set; }
|
public decimal SpeedLimitIncrement { get; private set; }
|
||||||
|
|
||||||
private async void Queue_CompletedCountChanged(object? sender, int e)
|
private void Queue_CompletedCountChanged(object? sender, int e)
|
||||||
{
|
{
|
||||||
int errCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.FailedAbort or ProcessBookResult.FailedSkip or ProcessBookResult.FailedRetry or ProcessBookResult.ValidationFail);
|
int errCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.FailedAbort or ProcessBookResult.FailedSkip or ProcessBookResult.FailedRetry or ProcessBookResult.ValidationFail);
|
||||||
int completeCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.Success);
|
int completeCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.Success);
|
||||||
|
|
||||||
ErrorCount = errCount;
|
ErrorCount = errCount;
|
||||||
CompletedCount = completeCount;
|
CompletedCount = completeCount;
|
||||||
await Dispatcher.UIThread.InvokeAsync(() => this.RaisePropertyChanged(nameof(Progress)));
|
Dispatcher.UIThread.Invoke(() => this.RaisePropertyChanged(nameof(Progress)));
|
||||||
}
|
}
|
||||||
private async void Queue_QueuededCountChanged(object? sender, int cueCount)
|
private void Queue_QueuededCountChanged(object? sender, int cueCount)
|
||||||
{
|
{
|
||||||
QueuedCount = cueCount;
|
QueuedCount = cueCount;
|
||||||
await Dispatcher.UIThread.InvokeAsync(() => this.RaisePropertyChanged(nameof(Progress)));
|
Dispatcher.UIThread.Invoke(() => this.RaisePropertyChanged(nameof(Progress)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteLine(string text)
|
public void WriteLine(string text)
|
||||||
|
|||||||
@ -134,9 +134,9 @@ namespace LibationAvalonia.Views
|
|||||||
Task.Run(() => vm.ProductsDisplay.BindToGridAsync(initialLibrary)));
|
Task.Run(() => vm.ProductsDisplay.BindToGridAsync(initialLibrary)));
|
||||||
}
|
}
|
||||||
|
|
||||||
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 _, ISeriesEntry 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;
|
||||||
public void ProductsDisplay_TagsButtonClicked(object _, LibraryBook libraryBook)
|
public void ProductsDisplay_TagsButtonClicked(object _, LibraryBook libraryBook)
|
||||||
|
|||||||
@ -24,9 +24,9 @@ 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<ISeriesEntry>? LiberateSeriesClicked;
|
||||||
public event EventHandler<LibraryBook>? ConvertToMp3Clicked;
|
public event EventHandler<LibraryBook[]>? ConvertToMp3Clicked;
|
||||||
public event EventHandler<LibraryBook>? TagsButtonClicked;
|
public event EventHandler<LibraryBook>? TagsButtonClicked;
|
||||||
|
|
||||||
private ProductsDisplayViewModel? _viewModel => DataContext as ProductsDisplayViewModel;
|
private ProductsDisplayViewModel? _viewModel => DataContext as ProductsDisplayViewModel;
|
||||||
@ -191,29 +191,41 @@ namespace LibationAvalonia.Views
|
|||||||
|
|
||||||
public void ProductsGrid_CellContextMenuStripNeeded(object? sender, DataGridCellContextMenuStripNeededEventArgs args)
|
public void ProductsGrid_CellContextMenuStripNeeded(object? sender, DataGridCellContextMenuStripNeededEventArgs args)
|
||||||
{
|
{
|
||||||
var entry = args.GridEntry;
|
var entries = args.GridEntries;
|
||||||
var ctx = new GridContextMenu(entry, '_');
|
var ctx = new GridContextMenu(entries, '_');
|
||||||
|
|
||||||
if (args.Column.SortMemberPath is not "Liberate" and not "Cover"
|
if (App.MainWindow?.Clipboard is IClipboard clipboard)
|
||||||
&& App.MainWindow?.Clipboard is IClipboard clipboard)
|
{
|
||||||
|
//Avalonia's DataGrid can't select individual cells, so add separate
|
||||||
|
//options for copying single cell's contents and who row contents.
|
||||||
|
if (entries.Length == 1 && args.Column.SortMemberPath is not "Liberate" and not "Cover")
|
||||||
{
|
{
|
||||||
args.ContextMenuItems.Add(new MenuItem
|
args.ContextMenuItems.Add(new MenuItem
|
||||||
{
|
{
|
||||||
Header = ctx.CopyCellText,
|
Header = ctx.CopyCellText,
|
||||||
Command = ReactiveCommand.CreateFromTask(() => clipboard?.SetTextAsync(args.CellClipboardContents) ?? Task.CompletedTask)
|
Command = ReactiveCommand.CreateFromTask(() => clipboard?.SetTextAsync(args.CellClipboardContents) ?? Task.CompletedTask)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
args.ContextMenuItems.Add(new MenuItem
|
||||||
|
{
|
||||||
|
Header = "_Copy Row Contents",
|
||||||
|
Command = ReactiveCommand.CreateFromTask(() => clipboard?.SetTextAsync(args.GetRowClipboardContents()) ?? Task.CompletedTask)
|
||||||
|
});
|
||||||
|
|
||||||
args.ContextMenuItems.Add(new Separator());
|
args.ContextMenuItems.Add(new Separator());
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Liberate all Episodes
|
|
||||||
|
|
||||||
if (entry.Liberate.IsSeries)
|
#region Liberate all Episodes (Single series only)
|
||||||
|
|
||||||
|
if (entries.Length == 1 && entries[0] is ISeriesEntry seriesEntry)
|
||||||
{
|
{
|
||||||
args.ContextMenuItems.Add(new MenuItem()
|
args.ContextMenuItems.Add(new MenuItem()
|
||||||
{
|
{
|
||||||
Header = ctx.LiberateEpisodesText,
|
Header = ctx.LiberateEpisodesText,
|
||||||
IsEnabled = ctx.LiberateEpisodesEnabled,
|
IsEnabled = ctx.LiberateEpisodesEnabled,
|
||||||
Command = ReactiveCommand.Create(() => LiberateSeriesClicked?.Invoke(this, (ISeriesEntry)entry))
|
Command = ReactiveCommand.Create(() => LiberateSeriesClicked?.Invoke(this, seriesEntry))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,20 +250,10 @@ namespace LibationAvalonia.Views
|
|||||||
});
|
});
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
#region Remove from library
|
#region Locate file (Single book only)
|
||||||
|
|
||||||
args.ContextMenuItems.Add(new MenuItem
|
if (entries.Length == 1 && entries[0] is ILibraryBookEntry entry)
|
||||||
{
|
{
|
||||||
Header = ctx.RemoveText,
|
|
||||||
Command = ReactiveCommand.CreateFromTask(ctx.RemoveAsync)
|
|
||||||
});
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
if (!entry.Liberate.IsSeries)
|
|
||||||
{
|
|
||||||
#region Locate file
|
|
||||||
|
|
||||||
args.ContextMenuItems.Add(new MenuItem
|
args.ContextMenuItems.Add(new MenuItem
|
||||||
{
|
{
|
||||||
Header = ctx.LocateFileText,
|
Header = ctx.LocateFileText,
|
||||||
@ -285,21 +287,44 @@ namespace LibationAvalonia.Views
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region Remove from library
|
||||||
|
|
||||||
|
args.ContextMenuItems.Add(new MenuItem
|
||||||
|
{
|
||||||
|
Header = ctx.RemoveText,
|
||||||
|
Command = ReactiveCommand.CreateFromTask(ctx.RemoveAsync)
|
||||||
|
});
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region Liberate All (multiple books only)
|
||||||
|
if (entries.OfType<ILibraryBookEntry>().Count() > 1)
|
||||||
|
{
|
||||||
|
args.ContextMenuItems.Add(new MenuItem
|
||||||
|
{
|
||||||
|
Header = ctx.DownloadSelectedText,
|
||||||
|
Command = ReactiveCommand.Create(() => LiberateClicked?.Invoke(this, ctx.LibraryBookEntries.Select(e => e.LibraryBook).ToArray()))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
#region Convert to Mp3
|
#region Convert to Mp3
|
||||||
|
|
||||||
|
if (ctx.LibraryBookEntries.Length > 0)
|
||||||
|
{
|
||||||
args.ContextMenuItems.Add(new MenuItem
|
args.ContextMenuItems.Add(new MenuItem
|
||||||
{
|
{
|
||||||
Header = ctx.ConvertToMp3Text,
|
Header = ctx.ConvertToMp3Text,
|
||||||
IsEnabled = ctx.ConvertToMp3Enabled,
|
IsEnabled = ctx.ConvertToMp3Enabled,
|
||||||
Command = ReactiveCommand.Create(() => ConvertToMp3Clicked?.Invoke(this, entry.LibraryBook))
|
Command = ReactiveCommand.Create(() => ConvertToMp3Clicked?.Invoke(this, ctx.LibraryBookEntries.Select(e => e.LibraryBook).ToArray()))
|
||||||
});
|
});
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Force Re-Download
|
#endregion
|
||||||
if (!entry.Liberate.IsSeries)
|
#region Force Re-Download (Single book only)
|
||||||
|
if (entries.Length == 1 && entries[0] is ILibraryBookEntry entry4)
|
||||||
{
|
{
|
||||||
args.ContextMenuItems.Add(new MenuItem()
|
args.ContextMenuItems.Add(new MenuItem()
|
||||||
{
|
{
|
||||||
@ -307,17 +332,23 @@ namespace LibationAvalonia.Views
|
|||||||
IsEnabled = ctx.ReDownloadEnabled,
|
IsEnabled = ctx.ReDownloadEnabled,
|
||||||
Command = ReactiveCommand.Create(() =>
|
Command = ReactiveCommand.Create(() =>
|
||||||
{
|
{
|
||||||
//No need to persist this change. It only needs to last long for the file to start downloading
|
//No need to persist these changes. They only needs to last long for the files to start downloading
|
||||||
entry.Book.UserDefinedItem.BookStatus = LiberatedStatus.NotLiberated;
|
entry4.Book.UserDefinedItem.BookStatus = LiberatedStatus.NotLiberated;
|
||||||
LiberateClicked?.Invoke(this, entry.LibraryBook);
|
if (entry4.Book.HasPdf())
|
||||||
|
entry4.Book.UserDefinedItem.SetPdfStatus(LiberatedStatus.NotLiberated);
|
||||||
|
LiberateClicked?.Invoke(this, [entry4.LibraryBook]);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
if (entries.Length > 1)
|
||||||
|
return;
|
||||||
|
|
||||||
args.ContextMenuItems.Add(new Separator());
|
args.ContextMenuItems.Add(new Separator());
|
||||||
|
|
||||||
#region Edit Templates
|
#region Edit Templates (Single book only)
|
||||||
|
|
||||||
async Task editTemplate<T>(LibraryBook libraryBook, string existingTemplate, Action<string> setNewTemplate)
|
async Task editTemplate<T>(LibraryBook libraryBook, string existingTemplate, Action<string> setNewTemplate)
|
||||||
where T : Templates, LibationFileManager.ITemplate, new()
|
where T : Templates, LibationFileManager.ITemplate, new()
|
||||||
{
|
{
|
||||||
@ -329,7 +360,7 @@ namespace LibationAvalonia.Views
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!entry.Liberate.IsSeries)
|
if (entries.Length == 1 && entries[0] is ILibraryBookEntry entry2)
|
||||||
{
|
{
|
||||||
args.ContextMenuItems.Add(new MenuItem
|
args.ContextMenuItems.Add(new MenuItem
|
||||||
{
|
{
|
||||||
@ -339,17 +370,17 @@ namespace LibationAvalonia.Views
|
|||||||
new MenuItem
|
new MenuItem
|
||||||
{
|
{
|
||||||
Header = ctx.FolderTemplateText,
|
Header = ctx.FolderTemplateText,
|
||||||
Command = ReactiveCommand.CreateFromTask(() => editTemplate<Templates.FolderTemplate>(entry.LibraryBook, Configuration.Instance.FolderTemplate, t => Configuration.Instance.FolderTemplate = t))
|
Command = ReactiveCommand.CreateFromTask(() => editTemplate<Templates.FolderTemplate>(entry2.LibraryBook, Configuration.Instance.FolderTemplate, t => Configuration.Instance.FolderTemplate = t))
|
||||||
},
|
},
|
||||||
new MenuItem
|
new MenuItem
|
||||||
{
|
{
|
||||||
Header = ctx.FileTemplateText,
|
Header = ctx.FileTemplateText,
|
||||||
Command = ReactiveCommand.CreateFromTask(() => editTemplate<Templates.FileTemplate>(entry.LibraryBook, Configuration.Instance.FileTemplate, t => Configuration.Instance.FileTemplate = t))
|
Command = ReactiveCommand.CreateFromTask(() => editTemplate<Templates.FileTemplate>(entry2.LibraryBook, Configuration.Instance.FileTemplate, t => Configuration.Instance.FileTemplate = t))
|
||||||
},
|
},
|
||||||
new MenuItem
|
new MenuItem
|
||||||
{
|
{
|
||||||
Header = ctx.MultipartTemplateText,
|
Header = ctx.MultipartTemplateText,
|
||||||
Command = ReactiveCommand.CreateFromTask(() => editTemplate<Templates.ChapterFileTemplate>(entry.LibraryBook, Configuration.Instance.ChapterFileTemplate, t => Configuration.Instance.ChapterFileTemplate = t))
|
Command = ReactiveCommand.CreateFromTask(() => editTemplate<Templates.ChapterFileTemplate>(entry2.LibraryBook, Configuration.Instance.ChapterFileTemplate, t => Configuration.Instance.ChapterFileTemplate = t))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -357,27 +388,26 @@ namespace LibationAvalonia.Views
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
#region View Bookmarks/Clips (Single book only)
|
||||||
|
|
||||||
#region View Bookmarks/Clips
|
if (entries.Length == 1 && entries[0] is ILibraryBookEntry entry3 && VisualRoot is Window window)
|
||||||
|
|
||||||
if (!entry.Liberate.IsSeries && VisualRoot is Window window)
|
|
||||||
{
|
{
|
||||||
args.ContextMenuItems.Add(new MenuItem
|
args.ContextMenuItems.Add(new MenuItem
|
||||||
{
|
{
|
||||||
Header = ctx.ViewBookmarksText,
|
Header = ctx.ViewBookmarksText,
|
||||||
Command = ReactiveCommand.CreateFromTask(() => new BookRecordsDialog(entry.LibraryBook).ShowDialog(window))
|
Command = ReactiveCommand.CreateFromTask(() => new BookRecordsDialog(entry3.LibraryBook).ShowDialog(window))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
#region View All Series
|
#region View All Series (Single book only)
|
||||||
|
|
||||||
if (entry.Book.SeriesLink.Any())
|
if (entries.Length == 1 && entries[0].Book.SeriesLink.Any())
|
||||||
{
|
{
|
||||||
args.ContextMenuItems.Add(new MenuItem
|
args.ContextMenuItems.Add(new MenuItem
|
||||||
{
|
{
|
||||||
Header = ctx.ViewSeriesText,
|
Header = ctx.ViewSeriesText,
|
||||||
Command = ReactiveCommand.Create(() => new SeriesViewDialog(entry.LibraryBook).Show())
|
Command = ReactiveCommand.Create(() => new SeriesViewDialog(entries[0].LibraryBook).Show())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,7 +545,7 @@ namespace LibationAvalonia.Views
|
|||||||
}
|
}
|
||||||
else if (button.DataContext is ILibraryBookEntry lbEntry)
|
else if (button.DataContext is ILibraryBookEntry lbEntry)
|
||||||
{
|
{
|
||||||
LiberateClicked?.Invoke(this, lbEntry.LibraryBook);
|
LiberateClicked?.Invoke(this, [lbEntry.LibraryBook]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@ using LibationFileManager;
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
|
||||||
|
|
||||||
namespace LibationUiBase.GridView;
|
namespace LibationUiBase.GridView;
|
||||||
|
|
||||||
@ -17,66 +16,68 @@ public class GridContextMenu
|
|||||||
public string SetNotDownloadedText => $"Set Download status to '{Accelerator}Not Downloaded'";
|
public string SetNotDownloadedText => $"Set Download status to '{Accelerator}Not Downloaded'";
|
||||||
public string RemoveText => $"{Accelerator}Remove from library";
|
public string RemoveText => $"{Accelerator}Remove from library";
|
||||||
public string LocateFileText => $"{Accelerator}Locate file...";
|
public string LocateFileText => $"{Accelerator}Locate file...";
|
||||||
public string LocateFileDialogTitle => $"Locate the audio file for '{GridEntry.Book.TitleWithSubtitle}'";
|
public string LocateFileDialogTitle => $"Locate the audio file for '{GridEntries[0].Book.TitleWithSubtitle}'";
|
||||||
public string LocateFileErrorMessage => "Error saving book's location";
|
public string LocateFileErrorMessage => "Error saving book's location";
|
||||||
public string ConvertToMp3Text => $"{Accelerator}Convert to Mp3";
|
public string ConvertToMp3Text => $"{Accelerator}Convert to Mp3";
|
||||||
public string ReDownloadText => "Re-download this audiobook";
|
public string ReDownloadText => "Re-download this audiobook";
|
||||||
|
public string DownloadSelectedText => "Download selected audiobooks";
|
||||||
public string EditTemplatesText => "Edit Templates";
|
public string EditTemplatesText => "Edit Templates";
|
||||||
public string FolderTemplateText => "Folder Template";
|
public string FolderTemplateText => "Folder Template";
|
||||||
public string FileTemplateText => "File Template";
|
public string FileTemplateText => "File Template";
|
||||||
public string MultipartTemplateText => "Multipart File Template";
|
public string MultipartTemplateText => "Multipart File Template";
|
||||||
public string ViewBookmarksText => "View _Bookmarks/Clips";
|
public string ViewBookmarksText => "View _Bookmarks/Clips";
|
||||||
public string ViewSeriesText => GridEntry.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 => GridEntry is ISeriesEntry sEntry && sEntry.Children.Any(c => c.Liberate.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload);
|
public bool LiberateEpisodesEnabled => GridEntries.OfType<ISeriesEntry>().Any(sEntry => sEntry.Children.Any(c => c.Liberate.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload));
|
||||||
public bool SetDownloadedEnabled => GridEntry.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated || GridEntry.Liberate.IsSeries;
|
public bool SetDownloadedEnabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated || ge.Liberate.IsSeries);
|
||||||
public bool SetNotDownloadedEnabled => GridEntry.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated || GridEntry.Liberate.IsSeries;
|
public bool SetNotDownloadedEnabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated || ge.Liberate.IsSeries);
|
||||||
public bool ConvertToMp3Enabled => GridEntry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated;
|
public bool ConvertToMp3Enabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated);
|
||||||
public bool ReDownloadEnabled => GridEntry.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated;
|
public bool ReDownloadEnabled => LibraryBookEntries.Any(ge => ge.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated);
|
||||||
|
|
||||||
public IGridEntry GridEntry { get; }
|
private IGridEntry[] GridEntries { get; }
|
||||||
|
public ILibraryBookEntry[] LibraryBookEntries { get; }
|
||||||
public char Accelerator { get; }
|
public char Accelerator { get; }
|
||||||
|
|
||||||
public GridContextMenu(IGridEntry gridEntry, char accelerator)
|
public GridContextMenu(IGridEntry[] gridEntries, char accelerator)
|
||||||
{
|
{
|
||||||
GridEntry = gridEntry;
|
ArgumentNullException.ThrowIfNull(gridEntries, nameof(gridEntries));
|
||||||
|
ArgumentOutOfRangeException.ThrowIfZero(gridEntries.Length, $"{nameof(gridEntries)}.{nameof(gridEntries.Length)}");
|
||||||
|
|
||||||
|
GridEntries = gridEntries;
|
||||||
Accelerator = accelerator;
|
Accelerator = accelerator;
|
||||||
|
LibraryBookEntries
|
||||||
|
= GridEntries
|
||||||
|
.OfType<ISeriesEntry>()
|
||||||
|
.SelectMany(s => s.Children)
|
||||||
|
.Concat(GridEntries.OfType<ILibraryBookEntry>())
|
||||||
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDownloaded()
|
public void SetDownloaded()
|
||||||
{
|
{
|
||||||
if (GridEntry is ISeriesEntry series)
|
LibraryBookEntries.Select(e => e.LibraryBook)
|
||||||
|
.UpdateUserDefinedItem(udi =>
|
||||||
{
|
{
|
||||||
series.Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.Liberated);
|
udi.BookStatus = LiberatedStatus.Liberated;
|
||||||
}
|
if (udi.Book.HasPdf())
|
||||||
else
|
udi.SetPdfStatus(LiberatedStatus.Liberated);
|
||||||
{
|
});
|
||||||
GridEntry.LibraryBook.UpdateBookStatus(LiberatedStatus.Liberated);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetNotDownloaded()
|
public void SetNotDownloaded()
|
||||||
{
|
{
|
||||||
if (GridEntry is ISeriesEntry series)
|
LibraryBookEntries.Select(e => e.LibraryBook)
|
||||||
|
.UpdateUserDefinedItem(udi =>
|
||||||
{
|
{
|
||||||
series.Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.NotLiberated);
|
udi.BookStatus = LiberatedStatus.NotLiberated;
|
||||||
}
|
if (udi.Book.HasPdf())
|
||||||
else
|
udi.SetPdfStatus(LiberatedStatus.NotLiberated);
|
||||||
{
|
});
|
||||||
GridEntry.LibraryBook.UpdateBookStatus(LiberatedStatus.NotLiberated);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RemoveAsync()
|
public async Task RemoveAsync()
|
||||||
{
|
{
|
||||||
if (GridEntry is ISeriesEntry series)
|
await LibraryBookEntries.Select(e => e.LibraryBook).RemoveBooksAsync();
|
||||||
{
|
|
||||||
await series.Children.Select(c => c.LibraryBook).RemoveBooksAsync();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await Task.Run(GridEntry.LibraryBook.RemoveBook);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITemplateEditor CreateTemplateEditor<T>(LibraryBook libraryBook, string existingTemplate)
|
public ITemplateEditor CreateTemplateEditor<T>(LibraryBook libraryBook, string existingTemplate)
|
||||||
|
|||||||
4
Source/LibationWinForms/Form1.Designer.cs
generated
4
Source/LibationWinForms/Form1.Designer.cs
generated
@ -537,9 +537,9 @@
|
|||||||
this.productsDisplay.TabIndex = 9;
|
this.productsDisplay.TabIndex = 9;
|
||||||
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 += new System.EventHandler<DataLayer.LibraryBook>(this.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.ISeriesEntry>(this.ProductsDisplay_LiberateSeriesClicked);
|
||||||
this.productsDisplay.ConvertToMp3Clicked += new System.EventHandler<DataLayer.LibraryBook>(this.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);
|
||||||
//
|
//
|
||||||
// toggleQueueHideBtn
|
// toggleQueueHideBtn
|
||||||
|
|||||||
@ -23,26 +23,29 @@ namespace LibationWinForms
|
|||||||
this.Width = width;
|
this.Width = width;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProductsDisplay_LiberateClicked(object sender, LibraryBook libraryBook)
|
private void ProductsDisplay_LiberateClicked(object sender, LibraryBook[] libraryBooks)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (libraryBook.Book.UserDefinedItem.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload)
|
if (libraryBooks.Length == 1)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Information("Begin single book backup of {libraryBook}", libraryBook);
|
var item = libraryBooks[0];
|
||||||
SetQueueCollapseState(false);
|
if (item.Book.UserDefinedItem.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload)
|
||||||
processBookQueue1.AddDownloadDecrypt(libraryBook);
|
|
||||||
}
|
|
||||||
else if (libraryBook.Book.UserDefinedItem.PdfStatus is LiberatedStatus.NotLiberated)
|
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Information("Begin single pdf backup of {libraryBook}", libraryBook);
|
Serilog.Log.Logger.Information("Begin single book backup of {libraryBook}", item);
|
||||||
SetQueueCollapseState(false);
|
SetQueueCollapseState(false);
|
||||||
processBookQueue1.AddDownloadPdf(libraryBook);
|
processBookQueue1.AddDownloadDecrypt(item);
|
||||||
}
|
}
|
||||||
else if (libraryBook.Book.Audio_Exists())
|
else if (item.Book.UserDefinedItem.PdfStatus is LiberatedStatus.NotLiberated)
|
||||||
|
{
|
||||||
|
Serilog.Log.Logger.Information("Begin single pdf backup of {libraryBook}", item);
|
||||||
|
SetQueueCollapseState(false);
|
||||||
|
processBookQueue1.AddDownloadPdf(item);
|
||||||
|
}
|
||||||
|
else if (item.Book.Audio_Exists())
|
||||||
{
|
{
|
||||||
// liberated: open explorer to file
|
// liberated: open explorer to file
|
||||||
var filePath = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId);
|
var filePath = AudibleFileStorage.Audio.GetPath(item.Book.AudibleProductId);
|
||||||
if (!Go.To.File(filePath?.ShortPathName))
|
if (!Go.To.File(filePath?.ShortPathName))
|
||||||
{
|
{
|
||||||
var suffix = string.IsNullOrWhiteSpace(filePath) ? "" : $":\r\n{filePath}";
|
var suffix = string.IsNullOrWhiteSpace(filePath) ? "" : $":\r\n{filePath}";
|
||||||
@ -50,9 +53,23 @@ namespace LibationWinForms
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var toLiberate
|
||||||
|
= libraryBooks
|
||||||
|
.Where(x => x.Book.UserDefinedItem.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload || x.Book.UserDefinedItem.PdfStatus is LiberatedStatus.NotLiberated)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (toLiberate.Length > 0)
|
||||||
|
{
|
||||||
|
SetQueueCollapseState(false);
|
||||||
|
processBookQueue1.AddDownloadDecrypt(toLiberate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Error(ex, "An error occurred while handling the stop light button click for {libraryBook}", libraryBook);
|
Serilog.Log.Logger.Error(ex, "An error occurred while handling the stop light button click for {libraryBook}", libraryBooks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,20 +89,21 @@ namespace LibationWinForms
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProductsDisplay_ConvertToMp3Clicked(object sender, LibraryBook libraryBook)
|
private void ProductsDisplay_ConvertToMp3Clicked(object sender, LibraryBook[] libraryBooks)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (libraryBook.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated)
|
var preLiberated = libraryBooks.Where(lb => lb.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated).ToArray();
|
||||||
|
if (preLiberated.Length > 0)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Information("Begin single pdf backup of {libraryBook}", libraryBook);
|
Serilog.Log.Logger.Information("Begin convert {count} books to mp3", preLiberated.Length);
|
||||||
SetQueueCollapseState(false);
|
SetQueueCollapseState(false);
|
||||||
processBookQueue1.AddConvertMp3(libraryBook);
|
processBookQueue1.AddConvertMp3(preLiberated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Error(ex, "An error occurred while handling the stop light button click for {libraryBook}", libraryBook);
|
Serilog.Log.Logger.Error(ex, "An error occurred while handling the stop light button click for {libraryBook}", libraryBooks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,9 +20,9 @@ namespace LibationWinForms.GridView
|
|||||||
/// <summary>Number of visible rows has changed</summary>
|
/// <summary>Number of visible rows has changed</summary>
|
||||||
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<ISeriesEntry> LiberateSeriesClicked;
|
||||||
public event EventHandler<LibraryBook> ConvertToMp3Clicked;
|
public event EventHandler<LibraryBook[]> ConvertToMp3Clicked;
|
||||||
public event EventHandler InitialLoaded;
|
public event EventHandler InitialLoaded;
|
||||||
|
|
||||||
private bool hasBeenDisplayed;
|
private bool hasBeenDisplayed;
|
||||||
@ -123,12 +123,12 @@ namespace LibationWinForms.GridView
|
|||||||
|
|
||||||
#region Cell Context Menu
|
#region Cell Context Menu
|
||||||
|
|
||||||
private void productsGrid_CellContextMenuStripNeeded(IGridEntry entry, ContextMenuStrip ctxMenu)
|
private void productsGrid_CellContextMenuStripNeeded(IGridEntry[] entries, ContextMenuStrip ctxMenu)
|
||||||
{
|
{
|
||||||
var ctx = new GridContextMenu(entry, '&');
|
var ctx = new GridContextMenu(entries, '&');
|
||||||
#region Liberate all Episodes
|
#region Liberate all Episodes (Single series only)
|
||||||
|
|
||||||
if (entry.Liberate.IsSeries)
|
if (entries.Length == 1 && entries[0] is ISeriesEntry seriesEntry)
|
||||||
{
|
{
|
||||||
var liberateEpisodesMenuItem = new ToolStripMenuItem()
|
var liberateEpisodesMenuItem = new ToolStripMenuItem()
|
||||||
{
|
{
|
||||||
@ -136,7 +136,7 @@ namespace LibationWinForms.GridView
|
|||||||
Enabled = ctx.LiberateEpisodesEnabled
|
Enabled = ctx.LiberateEpisodesEnabled
|
||||||
};
|
};
|
||||||
|
|
||||||
liberateEpisodesMenuItem.Click += (_, _) => LiberateSeriesClicked?.Invoke(this, (ISeriesEntry)entry);
|
liberateEpisodesMenuItem.Click += (_, _) => LiberateSeriesClicked?.Invoke(this, seriesEntry);
|
||||||
ctxMenu.Items.Add(liberateEpisodesMenuItem);
|
ctxMenu.Items.Add(liberateEpisodesMenuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,17 +163,10 @@ namespace LibationWinForms.GridView
|
|||||||
ctxMenu.Items.Add(setNotDownloadMenuItem);
|
ctxMenu.Items.Add(setNotDownloadMenuItem);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
#region Remove from library
|
#region Locate file (Single book only)
|
||||||
|
|
||||||
var removeMenuItem = new ToolStripMenuItem() { Text = ctx.RemoveText };
|
if (entries.Length == 1 && entries[0] is ILibraryBookEntry entry)
|
||||||
removeMenuItem.Click += async (_, _) => await ctx.RemoveAsync();
|
|
||||||
ctxMenu.Items.Add(removeMenuItem);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
if (!entry.Liberate.IsSeries)
|
|
||||||
{
|
{
|
||||||
#region Locate file
|
|
||||||
var locateFileMenuItem = new ToolStripMenuItem() { Text = ctx.LocateFileText };
|
var locateFileMenuItem = new ToolStripMenuItem() { Text = ctx.LocateFileText };
|
||||||
ctxMenu.Items.Add(locateFileMenuItem);
|
ctxMenu.Items.Add(locateFileMenuItem);
|
||||||
locateFileMenuItem.Click += (_, _) =>
|
locateFileMenuItem.Click += (_, _) =>
|
||||||
@ -194,23 +187,49 @@ namespace LibationWinForms.GridView
|
|||||||
MessageBoxLib.ShowAdminAlert(this, ctx.LocateFileErrorMessage, ctx.LocateFileErrorMessage, ex);
|
MessageBoxLib.ShowAdminAlert(this, ctx.LocateFileErrorMessage, ctx.LocateFileErrorMessage, ex);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region Remove from library
|
||||||
|
|
||||||
|
var removeMenuItem = new ToolStripMenuItem() { Text = ctx.RemoveText };
|
||||||
|
removeMenuItem.Click += async (_, _) => await ctx.RemoveAsync();
|
||||||
|
ctxMenu.Items.Add(removeMenuItem);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region Liberate All (multiple books only)
|
||||||
|
if (entries.OfType<ILibraryBookEntry>().Count() > 1)
|
||||||
|
{
|
||||||
|
var downloadSelectedMenuItem = new ToolStripMenuItem()
|
||||||
|
{
|
||||||
|
Text = ctx.DownloadSelectedText
|
||||||
|
};
|
||||||
|
ctxMenu.Items.Add(downloadSelectedMenuItem);
|
||||||
|
downloadSelectedMenuItem.Click += (s, _) =>
|
||||||
|
{
|
||||||
|
LiberateClicked?.Invoke(s, ctx.LibraryBookEntries.Select(e => e.LibraryBook).ToArray());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
#region Convert to Mp3
|
#region Convert to Mp3
|
||||||
|
|
||||||
|
if (ctx.LibraryBookEntries.Length > 0)
|
||||||
|
{
|
||||||
var convertToMp3MenuItem = new ToolStripMenuItem
|
var convertToMp3MenuItem = new ToolStripMenuItem
|
||||||
{
|
{
|
||||||
Text = ctx.ConvertToMp3Text,
|
Text = ctx.ConvertToMp3Text,
|
||||||
Enabled = ctx.ConvertToMp3Enabled
|
Enabled = ctx.ConvertToMp3Enabled
|
||||||
};
|
};
|
||||||
convertToMp3MenuItem.Click += (_, e) => ConvertToMp3Clicked?.Invoke(this, entry.LibraryBook);
|
convertToMp3MenuItem.Click += (_, e) => ConvertToMp3Clicked?.Invoke(this, ctx.LibraryBookEntries.Select(e => e.LibraryBook).ToArray());
|
||||||
ctxMenu.Items.Add(convertToMp3MenuItem);
|
ctxMenu.Items.Add(convertToMp3MenuItem);
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Force Re-Download
|
#endregion
|
||||||
if (!entry.Liberate.IsSeries)
|
#region Force Re-Download (Single book only)
|
||||||
|
|
||||||
|
if (entries.Length == 1 && entries[0] is ILibraryBookEntry entry4)
|
||||||
{
|
{
|
||||||
var reDownloadMenuItem = new ToolStripMenuItem()
|
var reDownloadMenuItem = new ToolStripMenuItem()
|
||||||
{
|
{
|
||||||
@ -220,13 +239,24 @@ namespace LibationWinForms.GridView
|
|||||||
ctxMenu.Items.Add(reDownloadMenuItem);
|
ctxMenu.Items.Add(reDownloadMenuItem);
|
||||||
reDownloadMenuItem.Click += (s, _) =>
|
reDownloadMenuItem.Click += (s, _) =>
|
||||||
{
|
{
|
||||||
//No need to persist this change. It only needs to last long for the file to start downloading
|
//No need to persist these changes. They only needs to last long for the files to start downloading
|
||||||
entry.Book.UserDefinedItem.BookStatus = LiberatedStatus.NotLiberated;
|
entry4.Book.UserDefinedItem.BookStatus = LiberatedStatus.NotLiberated;
|
||||||
LiberateClicked?.Invoke(s, entry.LibraryBook);
|
if (entry4.Book.HasPdf())
|
||||||
|
entry4.Book.UserDefinedItem.SetPdfStatus(LiberatedStatus.NotLiberated);
|
||||||
|
|
||||||
|
LiberateClicked?.Invoke(s, [entry4.LibraryBook]);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
#region Edit Templates
|
|
||||||
|
if (entries.Length > 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ctxMenu.Items.Add(new ToolStripSeparator());
|
||||||
|
|
||||||
|
#region Edit Templates (Single book only)
|
||||||
|
|
||||||
void editTemplate<T>(LibraryBook libraryBook, string existingTemplate, Action<string> setNewTemplate)
|
void editTemplate<T>(LibraryBook libraryBook, string existingTemplate, Action<string> setNewTemplate)
|
||||||
where T : Templates, LibationFileManager.ITemplate, new()
|
where T : Templates, LibationFileManager.ITemplate, new()
|
||||||
{
|
{
|
||||||
@ -238,14 +268,14 @@ namespace LibationWinForms.GridView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!entry.Liberate.IsSeries)
|
if (entries.Length == 1 && entries[0] is ILibraryBookEntry 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 };
|
||||||
var multiFileTemplateMenuItem = new ToolStripMenuItem { Text = ctx.MultipartTemplateText };
|
var multiFileTemplateMenuItem = new ToolStripMenuItem { Text = ctx.MultipartTemplateText };
|
||||||
folderTemplateMenuItem.Click += (s, _) => editTemplate<Templates.FolderTemplate>(entry.LibraryBook, Configuration.Instance.FolderTemplate, t => Configuration.Instance.FolderTemplate = t);
|
folderTemplateMenuItem.Click += (s, _) => editTemplate<Templates.FolderTemplate>(entry2.LibraryBook, Configuration.Instance.FolderTemplate, t => Configuration.Instance.FolderTemplate = t);
|
||||||
fileTemplateMenuItem.Click += (s, _) => editTemplate<Templates.FileTemplate>(entry.LibraryBook, Configuration.Instance.FileTemplate, t => Configuration.Instance.FileTemplate = t);
|
fileTemplateMenuItem.Click += (s, _) => editTemplate<Templates.FileTemplate>(entry2.LibraryBook, Configuration.Instance.FileTemplate, t => Configuration.Instance.FileTemplate = t);
|
||||||
multiFileTemplateMenuItem.Click += (s, _) => editTemplate<Templates.ChapterFileTemplate>(entry.LibraryBook, Configuration.Instance.ChapterFileTemplate, t => Configuration.Instance.ChapterFileTemplate = t);
|
multiFileTemplateMenuItem.Click += (s, _) => editTemplate<Templates.ChapterFileTemplate>(entry2.LibraryBook, Configuration.Instance.ChapterFileTemplate, t => Configuration.Instance.ChapterFileTemplate = t);
|
||||||
|
|
||||||
var editTemplatesMenuItem = new ToolStripMenuItem { Text = ctx.EditTemplatesText };
|
var editTemplatesMenuItem = new ToolStripMenuItem { Text = ctx.EditTemplatesText };
|
||||||
editTemplatesMenuItem.DropDownItems.AddRange(new[] { folderTemplateMenuItem, fileTemplateMenuItem, multiFileTemplateMenuItem });
|
editTemplatesMenuItem.DropDownItems.AddRange(new[] { folderTemplateMenuItem, fileTemplateMenuItem, multiFileTemplateMenuItem });
|
||||||
@ -255,25 +285,22 @@ namespace LibationWinForms.GridView
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
#region View Bookmarks/Clips (Single book only)
|
||||||
|
|
||||||
ctxMenu.Items.Add(new ToolStripSeparator());
|
if (entries.Length == 1 && entries[0] is ILibraryBookEntry entry3)
|
||||||
|
|
||||||
#region View Bookmarks/Clips
|
|
||||||
|
|
||||||
if (!entry.Liberate.IsSeries)
|
|
||||||
{
|
{
|
||||||
var bookRecordMenuItem = new ToolStripMenuItem { Text = ctx.ViewBookmarksText };
|
var bookRecordMenuItem = new ToolStripMenuItem { Text = ctx.ViewBookmarksText };
|
||||||
bookRecordMenuItem.Click += (_, _) => new BookRecordsDialog(entry.LibraryBook).ShowDialog(this);
|
bookRecordMenuItem.Click += (_, _) => new BookRecordsDialog(entry3.LibraryBook).ShowDialog(this);
|
||||||
ctxMenu.Items.Add(bookRecordMenuItem);
|
ctxMenu.Items.Add(bookRecordMenuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
#region View All Series
|
#region View All Series (Single book only)
|
||||||
|
|
||||||
if (entry.Book.SeriesLink.Any())
|
if (entries.Length == 1 && entries[0].Book.SeriesLink.Any())
|
||||||
{
|
{
|
||||||
var viewSeriesMenuItem = new ToolStripMenuItem { Text = ctx.ViewSeriesText };
|
var viewSeriesMenuItem = new ToolStripMenuItem { Text = ctx.ViewSeriesText };
|
||||||
viewSeriesMenuItem.Click += (_, _) => new SeriesViewDialog(entry.LibraryBook).Show();
|
viewSeriesMenuItem.Click += (_, _) => new SeriesViewDialog(entries[0].LibraryBook).Show();
|
||||||
ctxMenu.Items.Add(viewSeriesMenuItem);
|
ctxMenu.Items.Add(viewSeriesMenuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,7 +420,7 @@ namespace LibationWinForms.GridView
|
|||||||
{
|
{
|
||||||
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)
|
||||||
LiberateClicked?.Invoke(this, liveGridEntry.LibraryBook);
|
LiberateClicked?.Invoke(this, [liveGridEntry.LibraryBook]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void productsGrid_RemovableCountChanged(object sender, EventArgs e)
|
private void productsGrid_RemovableCountChanged(object sender, EventArgs e)
|
||||||
|
|||||||
@ -11,33 +11,34 @@ using System.Drawing;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
namespace LibationWinForms.GridView
|
namespace LibationWinForms.GridView
|
||||||
{
|
{
|
||||||
public delegate void GridEntryClickedEventHandler(IGridEntry liveGridEntry);
|
public delegate void GridEntryClickedEventHandler(IGridEntry liveGridEntry);
|
||||||
public delegate void LibraryBookEntryClickedEventHandler(ILibraryBookEntry liveGridEntry);
|
public delegate void LibraryBookEntryClickedEventHandler(ILibraryBookEntry liveGridEntry);
|
||||||
public delegate void GridEntryRectangleClickedEventHandler(IGridEntry liveGridEntry, Rectangle cellRectangle);
|
public delegate void GridEntryRectangleClickedEventHandler(IGridEntry liveGridEntry, Rectangle cellRectangle);
|
||||||
public delegate void ProductsGridCellContextMenuStripNeededEventHandler(IGridEntry liveGridEntry, ContextMenuStrip ctxMenu);
|
public delegate void ProductsGridCellContextMenuStripNeededEventHandler(IGridEntry[] liveGridEntry, ContextMenuStrip ctxMenu);
|
||||||
|
|
||||||
public partial class ProductsGrid : UserControl
|
public partial class ProductsGrid : UserControl
|
||||||
{
|
{
|
||||||
/// <summary>Number of visible rows has changed</summary>
|
/// <summary>Number of visible rows has changed</summary>
|
||||||
public event EventHandler<int> VisibleCountChanged;
|
public event EventHandler<int>? VisibleCountChanged;
|
||||||
public event LibraryBookEntryClickedEventHandler LiberateClicked;
|
public event LibraryBookEntryClickedEventHandler? LiberateClicked;
|
||||||
public event GridEntryClickedEventHandler CoverClicked;
|
public event GridEntryClickedEventHandler? CoverClicked;
|
||||||
public event LibraryBookEntryClickedEventHandler DetailsClicked;
|
public event LibraryBookEntryClickedEventHandler? DetailsClicked;
|
||||||
public event GridEntryRectangleClickedEventHandler DescriptionClicked;
|
public event GridEntryRectangleClickedEventHandler? DescriptionClicked;
|
||||||
public new event EventHandler<ScrollEventArgs> Scroll;
|
public new event EventHandler<ScrollEventArgs>? Scroll;
|
||||||
public event EventHandler RemovableCountChanged;
|
public event EventHandler? RemovableCountChanged;
|
||||||
public event ProductsGridCellContextMenuStripNeededEventHandler LiberateContextMenuStripNeeded;
|
public event ProductsGridCellContextMenuStripNeededEventHandler? LiberateContextMenuStripNeeded;
|
||||||
|
|
||||||
private GridEntryBindingList bindingList;
|
private GridEntryBindingList? bindingList;
|
||||||
internal IEnumerable<LibraryBook> GetVisibleBooks()
|
internal IEnumerable<LibraryBook> GetVisibleBooks()
|
||||||
=> bindingList
|
=> bindingList
|
||||||
.GetFilteredInItems()
|
?.GetFilteredInItems()
|
||||||
.Select(lbe => lbe.LibraryBook);
|
.Select(lbe => lbe.LibraryBook) ?? Enumerable.Empty<LibraryBook>();
|
||||||
internal IEnumerable<ILibraryBookEntry> GetAllBookEntries()
|
internal IEnumerable<ILibraryBookEntry> GetAllBookEntries()
|
||||||
=> bindingList.AllItems().BookEntries();
|
=> bindingList?.AllItems().BookEntries() ?? Enumerable.Empty<ILibraryBookEntry>();
|
||||||
|
|
||||||
public ProductsGrid()
|
public ProductsGrid()
|
||||||
{
|
{
|
||||||
@ -64,11 +65,17 @@ namespace LibationWinForms.GridView
|
|||||||
|
|
||||||
[PropertyChangeFilter(nameof(Configuration.GridFontScaleFactor))]
|
[PropertyChangeFilter(nameof(Configuration.GridFontScaleFactor))]
|
||||||
private void Configuration_FontScaleChanged(object sender, PropertyChangedEventArgsEx e)
|
private void Configuration_FontScaleChanged(object sender, PropertyChangedEventArgsEx e)
|
||||||
=> setGridFontScale((float)e.NewValue);
|
{
|
||||||
|
if (e.NewValue is float v)
|
||||||
|
setGridFontScale(v);
|
||||||
|
}
|
||||||
|
|
||||||
[PropertyChangeFilter(nameof(Configuration.GridScaleFactor))]
|
[PropertyChangeFilter(nameof(Configuration.GridScaleFactor))]
|
||||||
private void Configuration_ScaleChanged(object sender, PropertyChangedEventArgsEx e)
|
private void Configuration_ScaleChanged(object sender, PropertyChangedEventArgsEx e)
|
||||||
=> setGridScale((float)e.NewValue);
|
{
|
||||||
|
if (e.NewValue is float v)
|
||||||
|
setGridScale(v);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Keep track of the original dimensions for rescaling
|
/// Keep track of the original dimensions for rescaling
|
||||||
@ -106,10 +113,13 @@ namespace LibationWinForms.GridView
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private void GridEntryDataGridView_CellContextMenuStripNeeded(object sender, DataGridViewCellContextMenuStripNeededEventArgs e)
|
private static string? RemoveLineBreaks(string? text)
|
||||||
|
=> text?.Replace("\r\n", "").Replace('\r', ' ').Replace('\n', ' ');
|
||||||
|
|
||||||
|
private void GridEntryDataGridView_CellContextMenuStripNeeded(object? sender, DataGridViewCellContextMenuStripNeededEventArgs e)
|
||||||
{
|
{
|
||||||
// header
|
// header
|
||||||
if (e.RowIndex < 0)
|
if (e.RowIndex < 0 || sender is not DataGridView dgv)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
e.ContextMenuStrip = new ContextMenuStrip();
|
e.ContextMenuStrip = new ContextMenuStrip();
|
||||||
@ -120,25 +130,89 @@ namespace LibationWinForms.GridView
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var dgv = (DataGridView)sender;
|
string clipboardText;
|
||||||
var text = dgv[e.ColumnIndex, e.RowIndex].FormattedValue.ToString();
|
|
||||||
Clipboard.SetDataObject(text, false, 5, 150);
|
if (dgv.SelectedCells.Count <= 1)
|
||||||
|
{
|
||||||
|
//Copy contents only of cell that was right-clicked on.
|
||||||
|
clipboardText = dgv[e.ColumnIndex, e.RowIndex].FormattedValue?.ToString() ?? string.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Copy contents of selected cells. Each row is a new line,
|
||||||
|
//and columns are separated with tabs. Similar formatting to Microsoft Excel.
|
||||||
|
var selectedCells
|
||||||
|
= dgv.SelectedCells
|
||||||
|
.OfType<DataGridViewCell>()
|
||||||
|
.Where(c => c.OwningColumn is not null && c.OwningRow is not null)
|
||||||
|
.OrderBy(c => c.RowIndex)
|
||||||
|
.ThenBy(c => c.OwningColumn!.DisplayIndex)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var headerText
|
||||||
|
= string.Join("\t",
|
||||||
|
selectedCells
|
||||||
|
.Select(c => c.OwningColumn)
|
||||||
|
.Distinct()
|
||||||
|
.Select(c => RemoveLineBreaks(c?.HeaderText))
|
||||||
|
.OfType<string>());
|
||||||
|
|
||||||
|
List<string> linesOfText = [headerText];
|
||||||
|
foreach (var distinctRow in selectedCells.Select(c => c.RowIndex).Distinct())
|
||||||
|
{
|
||||||
|
linesOfText.Add(string.Join("\t",
|
||||||
|
selectedCells
|
||||||
|
.Where(c => c.RowIndex == distinctRow)
|
||||||
|
.Select(c => RemoveLineBreaks(c.FormattedValue?.ToString()) ?? string.Empty)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
clipboardText = string.Join(Environment.NewLine, linesOfText);
|
||||||
|
}
|
||||||
|
Clipboard.SetDataObject(clipboardText, false, 5, 150);
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
Serilog.Log.Logger.Error(ex, "Error copying text to clipboard");
|
||||||
}
|
}
|
||||||
catch { }
|
|
||||||
});
|
});
|
||||||
e.ContextMenuStrip.Items.Add(new ToolStripSeparator());
|
e.ContextMenuStrip.Items.Add(new ToolStripSeparator());
|
||||||
}
|
}
|
||||||
|
|
||||||
var entry = getGridEntry(e.RowIndex);
|
var clickedEntry = getGridEntry(e.RowIndex);
|
||||||
var name = gridEntryDataGridView.Columns[e.ColumnIndex].DataPropertyName;
|
|
||||||
LiberateContextMenuStripNeeded?.Invoke(entry, e.ContextMenuStrip);
|
var allSelected
|
||||||
|
= gridEntryDataGridView
|
||||||
|
.SelectedCells
|
||||||
|
.OfType<DataGridViewCell>()
|
||||||
|
.Select(c => c.OwningRow)
|
||||||
|
.OfType<DataGridViewRow>()
|
||||||
|
.Distinct()
|
||||||
|
.OrderBy(r => r.Index)
|
||||||
|
.Select(r => r.DataBoundItem)
|
||||||
|
.OfType<IGridEntry>()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var clickedIndex = Array.IndexOf(allSelected, clickedEntry);
|
||||||
|
if (clickedIndex == -1)
|
||||||
|
{
|
||||||
|
//User didn't right-click on a selected cell
|
||||||
|
gridEntryDataGridView.ClearSelection();
|
||||||
|
gridEntryDataGridView[e.ColumnIndex, e.RowIndex].Selected = true;
|
||||||
|
allSelected = [clickedEntry];
|
||||||
|
}
|
||||||
|
else if (clickedIndex > 0)
|
||||||
|
{
|
||||||
|
//Ensure the clicked entry is first in the list
|
||||||
|
(allSelected[0], allSelected[clickedIndex]) = (allSelected[clickedIndex], allSelected[0]);
|
||||||
|
}
|
||||||
|
LiberateContextMenuStripNeeded?.Invoke(allSelected, e.ContextMenuStrip);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnableDoubleBuffering()
|
private void EnableDoubleBuffering()
|
||||||
{
|
{
|
||||||
var propertyInfo = gridEntryDataGridView.GetType().GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
|
var propertyInfo = gridEntryDataGridView.GetType().GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
|
||||||
|
|
||||||
propertyInfo.SetValue(gridEntryDataGridView, true, null);
|
propertyInfo?.SetValue(gridEntryDataGridView, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Button controls
|
#region Button controls
|
||||||
@ -167,11 +241,11 @@ namespace LibationWinForms.GridView
|
|||||||
if (e.ColumnIndex == liberateGVColumn.Index)
|
if (e.ColumnIndex == liberateGVColumn.Index)
|
||||||
{
|
{
|
||||||
if (sEntry.Liberate.Expanded)
|
if (sEntry.Liberate.Expanded)
|
||||||
bindingList.CollapseItem(sEntry);
|
bindingList?.CollapseItem(sEntry);
|
||||||
else
|
else
|
||||||
bindingList.ExpandItem(sEntry);
|
bindingList?.ExpandItem(sEntry);
|
||||||
|
|
||||||
VisibleCountChanged?.Invoke(this, bindingList.GetFilteredInItems().Count());
|
VisibleCountChanged?.Invoke(this, bindingList?.GetFilteredInItems().Count() ?? 0);
|
||||||
}
|
}
|
||||||
else if (e.ColumnIndex == descriptionGVColumn.Index)
|
else if (e.ColumnIndex == descriptionGVColumn.Index)
|
||||||
DescriptionClicked?.Invoke(sEntry, gridEntryDataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false));
|
DescriptionClicked?.Invoke(sEntry, gridEntryDataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false));
|
||||||
@ -202,7 +276,7 @@ namespace LibationWinForms.GridView
|
|||||||
get => removeGVColumn.Visible;
|
get => removeGVColumn.Visible;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value)
|
if (value && bindingList is not null)
|
||||||
{
|
{
|
||||||
foreach (var book in bindingList.AllItems())
|
foreach (var book in bindingList.AllItems())
|
||||||
book.Remove = false;
|
book.Remove = false;
|
||||||
@ -248,13 +322,16 @@ namespace LibationWinForms.GridView
|
|||||||
|
|
||||||
internal void UpdateGrid(List<LibraryBook> dbBooks)
|
internal void UpdateGrid(List<LibraryBook> dbBooks)
|
||||||
{
|
{
|
||||||
|
if (bindingList == null)
|
||||||
|
throw new InvalidOperationException($"Must call {nameof(BindToGridAsync)} before calling {nameof(UpdateGrid)}");
|
||||||
|
|
||||||
//First row that is in view in the DataGridView
|
//First row that is in view in the DataGridView
|
||||||
var topRow = gridEntryDataGridView.Rows.Cast<DataGridViewRow>().FirstOrDefault(r => r.Displayed)?.Index ?? 0;
|
var topRow = gridEntryDataGridView.Rows.Cast<DataGridViewRow>().FirstOrDefault(r => r.Displayed)?.Index ?? 0;
|
||||||
|
|
||||||
#region Add new or update existing grid entries
|
#region Add new or update existing grid entries
|
||||||
|
|
||||||
//Remove filter prior to adding/updating boooks
|
//Remove filter prior to adding/updating books
|
||||||
string existingFilter = syncBindingSource.Filter;
|
string? existingFilter = syncBindingSource.Filter;
|
||||||
Filter(null);
|
Filter(null);
|
||||||
|
|
||||||
//Add absent entries to grid, or update existing entry
|
//Add absent entries to grid, or update existing entry
|
||||||
@ -308,6 +385,9 @@ namespace LibationWinForms.GridView
|
|||||||
|
|
||||||
public void RemoveBooks(IEnumerable<ILibraryBookEntry> removedBooks)
|
public void RemoveBooks(IEnumerable<ILibraryBookEntry> removedBooks)
|
||||||
{
|
{
|
||||||
|
if (bindingList == null)
|
||||||
|
throw new InvalidOperationException($"Must call {nameof(BindToGridAsync)} before calling {nameof(RemoveBooks)}");
|
||||||
|
|
||||||
//Remove books in series from their parents' Children list
|
//Remove books in series from their parents' Children list
|
||||||
foreach (var removed in removedBooks.Where(b => b.Liberate.IsEpisode))
|
foreach (var removed in removedBooks.Where(b => b.Liberate.IsEpisode))
|
||||||
removed.Parent.RemoveChild(removed);
|
removed.Parent.RemoveChild(removed);
|
||||||
@ -325,8 +405,11 @@ namespace LibationWinForms.GridView
|
|||||||
VisibleCountChanged?.Invoke(this, bindingList.GetFilteredInItems().Count());
|
VisibleCountChanged?.Invoke(this, bindingList.GetFilteredInItems().Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddOrUpdateBook(LibraryBook book, ILibraryBookEntry existingBookEntry)
|
private void AddOrUpdateBook(LibraryBook book, ILibraryBookEntry? existingBookEntry)
|
||||||
{
|
{
|
||||||
|
if (bindingList == null)
|
||||||
|
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<WinFormsEntryStatus>(book));
|
||||||
@ -335,8 +418,11 @@ namespace LibationWinForms.GridView
|
|||||||
existingBookEntry.UpdateLibraryBook(book);
|
existingBookEntry.UpdateLibraryBook(book);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddOrUpdateEpisode(LibraryBook episodeBook, ILibraryBookEntry existingEpisodeEntry, List<ISeriesEntry> seriesEntries, IEnumerable<LibraryBook> dbBooks)
|
private void AddOrUpdateEpisode(LibraryBook episodeBook, ILibraryBookEntry? existingEpisodeEntry, List<ISeriesEntry> seriesEntries, IEnumerable<LibraryBook> dbBooks)
|
||||||
{
|
{
|
||||||
|
if (bindingList == null)
|
||||||
|
throw new InvalidOperationException($"Must call {nameof(BindToGridAsync)} before calling {nameof(AddOrUpdateEpisode)}");
|
||||||
|
|
||||||
if (existingEpisodeEntry is null)
|
if (existingEpisodeEntry is null)
|
||||||
{
|
{
|
||||||
ILibraryBookEntry episodeEntry;
|
ILibraryBookEntry episodeEntry;
|
||||||
@ -397,7 +483,7 @@ namespace LibationWinForms.GridView
|
|||||||
|
|
||||||
#region Filter
|
#region Filter
|
||||||
|
|
||||||
public void Filter(string searchString)
|
public void Filter(string? searchString)
|
||||||
{
|
{
|
||||||
if (bindingList is null) return;
|
if (bindingList is null) return;
|
||||||
|
|
||||||
@ -485,16 +571,16 @@ namespace LibationWinForms.GridView
|
|||||||
removeGVColumn.IndeterminateValue = null;
|
removeGVColumn.IndeterminateValue = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HideMenuItem_Click(object sender, EventArgs e)
|
private void HideMenuItem_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var menuItem = sender as ToolStripMenuItem;
|
var menuItem = sender as ToolStripMenuItem;
|
||||||
var propertyName = menuItem.Tag as string;
|
var propertyName = menuItem?.Tag as string;
|
||||||
|
|
||||||
var column = gridEntryDataGridView.Columns
|
var column = gridEntryDataGridView.Columns
|
||||||
.Cast<DataGridViewColumn>()
|
.Cast<DataGridViewColumn>()
|
||||||
.FirstOrDefault(c => c.DataPropertyName == propertyName);
|
.FirstOrDefault(c => c.DataPropertyName == propertyName);
|
||||||
|
|
||||||
if (column != null)
|
if (column != null && menuItem != null && propertyName != null)
|
||||||
{
|
{
|
||||||
var visible = menuItem.Checked;
|
var visible = menuItem.Checked;
|
||||||
menuItem.Checked = !visible;
|
menuItem.Checked = !visible;
|
||||||
@ -508,7 +594,7 @@ namespace LibationWinForms.GridView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void gridEntryDataGridView_ColumnDisplayIndexChanged(object sender, DataGridViewColumnEventArgs e)
|
private void gridEntryDataGridView_ColumnDisplayIndexChanged(object? sender, DataGridViewColumnEventArgs e)
|
||||||
{
|
{
|
||||||
var config = Configuration.Instance;
|
var config = Configuration.Instance;
|
||||||
|
|
||||||
@ -517,7 +603,7 @@ namespace LibationWinForms.GridView
|
|||||||
config.GridColumnsDisplayIndices = dictionary;
|
config.GridColumnsDisplayIndices = dictionary;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void gridEntryDataGridView_CellToolTipTextNeeded(object sender, DataGridViewCellToolTipTextNeededEventArgs e)
|
private void gridEntryDataGridView_CellToolTipTextNeeded(object? sender, DataGridViewCellToolTipTextNeededEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.ColumnIndex == descriptionGVColumn.Index)
|
if (e.ColumnIndex == descriptionGVColumn.Index)
|
||||||
e.ToolTipText = "Click to see full description";
|
e.ToolTipText = "Click to see full description";
|
||||||
@ -525,7 +611,7 @@ namespace LibationWinForms.GridView
|
|||||||
e.ToolTipText = "Click to see full size";
|
e.ToolTipText = "Click to see full size";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void gridEntryDataGridView_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
|
private void gridEntryDataGridView_ColumnWidthChanged(object? sender, DataGridViewColumnEventArgs e)
|
||||||
{
|
{
|
||||||
var config = Configuration.Instance;
|
var config = Configuration.Instance;
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user