Merge pull request #249 from Mbucari/master
Add FilterableSortableBindingList to handle filtering the DataGridView
This commit is contained in:
commit
a89b07394f
@ -1,164 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using DataLayer;
|
|
||||||
using Dinah.Core.Net.Http;
|
|
||||||
using Dinah.Core.Threading;
|
|
||||||
using FileLiberator;
|
|
||||||
|
|
||||||
namespace LibationWinForms.BookLiberation.BaseForms
|
|
||||||
{
|
|
||||||
public class LiberationBaseForm : Form
|
|
||||||
{
|
|
||||||
protected Streamable Streamable { get; private set; }
|
|
||||||
protected LogMe LogMe { get; private set; }
|
|
||||||
private SynchronizeInvoker Invoker { get; init; }
|
|
||||||
|
|
||||||
public LiberationBaseForm()
|
|
||||||
{
|
|
||||||
//SynchronizationContext.Current will be null until the process contains a Form.
|
|
||||||
//If this is the first form created, it will not exist until after execution
|
|
||||||
//reaches inside the constructor (after base class has been initialized).
|
|
||||||
Invoker = new SynchronizeInvoker();
|
|
||||||
this.SetLibationIcon();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterFileLiberator(Streamable streamable, LogMe logMe = null)
|
|
||||||
{
|
|
||||||
if (streamable is null) return;
|
|
||||||
|
|
||||||
Streamable = streamable;
|
|
||||||
LogMe = logMe;
|
|
||||||
|
|
||||||
Subscribe(streamable);
|
|
||||||
|
|
||||||
if (Streamable is Processable processable)
|
|
||||||
Subscribe(processable);
|
|
||||||
if (Streamable is AudioDecodable audioDecodable)
|
|
||||||
Subscribe(audioDecodable);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Event Subscribers and Unsubscribers
|
|
||||||
private void Subscribe(Streamable streamable)
|
|
||||||
{
|
|
||||||
UnsubscribeStreamable(this, EventArgs.Empty);
|
|
||||||
|
|
||||||
streamable.StreamingBegin += OnStreamingBeginShow;
|
|
||||||
streamable.StreamingBegin += Streamable_StreamingBegin;
|
|
||||||
streamable.StreamingProgressChanged += Streamable_StreamingProgressChanged;
|
|
||||||
streamable.StreamingTimeRemaining += Streamable_StreamingTimeRemaining;
|
|
||||||
streamable.StreamingCompleted += Streamable_StreamingCompleted;
|
|
||||||
streamable.StreamingCompleted += OnStreamingCompletedClose;
|
|
||||||
|
|
||||||
Disposed += UnsubscribeStreamable;
|
|
||||||
}
|
|
||||||
private void Subscribe(Processable processable)
|
|
||||||
{
|
|
||||||
UnsubscribeProcessable(this, null);
|
|
||||||
|
|
||||||
processable.Begin += Processable_Begin;
|
|
||||||
processable.StatusUpdate += Processable_StatusUpdate;
|
|
||||||
processable.Completed += Processable_Completed;
|
|
||||||
|
|
||||||
//The form is created on Processable.Begin and we
|
|
||||||
//dispose of it on Processable.Completed
|
|
||||||
processable.Completed += OnCompletedDispose;
|
|
||||||
|
|
||||||
//Don't unsubscribe from Dispose because it fires when
|
|
||||||
//Streamable.StreamingCompleted closes the form, and
|
|
||||||
//the Processable events need to live past that event.
|
|
||||||
processable.Completed += UnsubscribeProcessable;
|
|
||||||
}
|
|
||||||
private void Subscribe(AudioDecodable audioDecodable)
|
|
||||||
{
|
|
||||||
UnsubscribeAudioDecodable(this, EventArgs.Empty);
|
|
||||||
|
|
||||||
audioDecodable.RequestCoverArt += AudioDecodable_RequestCoverArt;
|
|
||||||
audioDecodable.TitleDiscovered += AudioDecodable_TitleDiscovered;
|
|
||||||
audioDecodable.AuthorsDiscovered += AudioDecodable_AuthorsDiscovered;
|
|
||||||
audioDecodable.NarratorsDiscovered += AudioDecodable_NarratorsDiscovered;
|
|
||||||
audioDecodable.CoverImageDiscovered += AudioDecodable_CoverImageDiscovered;
|
|
||||||
|
|
||||||
Disposed += UnsubscribeAudioDecodable;
|
|
||||||
}
|
|
||||||
private void UnsubscribeStreamable(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
Disposed -= UnsubscribeStreamable;
|
|
||||||
|
|
||||||
Streamable.StreamingBegin -= OnStreamingBeginShow;
|
|
||||||
Streamable.StreamingBegin -= Streamable_StreamingBegin;
|
|
||||||
Streamable.StreamingProgressChanged -= Streamable_StreamingProgressChanged;
|
|
||||||
Streamable.StreamingTimeRemaining -= Streamable_StreamingTimeRemaining;
|
|
||||||
Streamable.StreamingCompleted -= Streamable_StreamingCompleted;
|
|
||||||
Streamable.StreamingCompleted -= OnStreamingCompletedClose;
|
|
||||||
}
|
|
||||||
private void UnsubscribeProcessable(object sender, LibraryBook e)
|
|
||||||
{
|
|
||||||
if (Streamable is not Processable processable)
|
|
||||||
return;
|
|
||||||
|
|
||||||
processable.Completed -= UnsubscribeProcessable;
|
|
||||||
processable.Completed -= OnCompletedDispose;
|
|
||||||
processable.Completed -= Processable_Completed;
|
|
||||||
processable.StatusUpdate -= Processable_StatusUpdate;
|
|
||||||
processable.Begin -= Processable_Begin;
|
|
||||||
}
|
|
||||||
private void UnsubscribeAudioDecodable(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (Streamable is not AudioDecodable audioDecodable)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Disposed -= UnsubscribeAudioDecodable;
|
|
||||||
audioDecodable.RequestCoverArt -= AudioDecodable_RequestCoverArt;
|
|
||||||
audioDecodable.TitleDiscovered -= AudioDecodable_TitleDiscovered;
|
|
||||||
audioDecodable.AuthorsDiscovered -= AudioDecodable_AuthorsDiscovered;
|
|
||||||
audioDecodable.NarratorsDiscovered -= AudioDecodable_NarratorsDiscovered;
|
|
||||||
audioDecodable.CoverImageDiscovered -= AudioDecodable_CoverImageDiscovered;
|
|
||||||
|
|
||||||
audioDecodable.Cancel();
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Form creation and disposal handling
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If the form was shown using Show (not ShowDialog), Form.Close calls Form.Dispose
|
|
||||||
/// </summary>
|
|
||||||
private void OnStreamingCompletedClose(object sender, string completedString) => this.UIThreadAsync(Close);
|
|
||||||
private void OnCompletedDispose(object sender, LibraryBook e) => this.UIThreadAsync(Dispose);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If StreamingBegin is fired from a worker thread, the window will be created on that
|
|
||||||
/// worker thread. We need to make certain that we show the window on the UI thread (same
|
|
||||||
/// thread that created form), otherwise the renderer will be on a worker thread which
|
|
||||||
/// could cause it to freeze. Form.BeginInvoke won't work until the form is created
|
|
||||||
/// (ie. shown) because Control doesn't get a window handle until it is Shown.
|
|
||||||
/// </summary>
|
|
||||||
private void OnStreamingBeginShow(object sender, string beginString) => Invoker.UIThreadAsync(Show);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Streamable event handlers
|
|
||||||
public virtual void Streamable_StreamingBegin(object sender, string beginString) { }
|
|
||||||
public virtual void Streamable_StreamingProgressChanged(object sender, DownloadProgress downloadProgress) { }
|
|
||||||
public virtual void Streamable_StreamingTimeRemaining(object sender, TimeSpan timeRemaining) { }
|
|
||||||
public virtual void Streamable_StreamingCompleted(object sender, string completedString) { }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Processable event handlers
|
|
||||||
public virtual void Processable_Begin(object sender, LibraryBook libraryBook) { }
|
|
||||||
public virtual void Processable_StatusUpdate(object sender, string statusUpdate) { }
|
|
||||||
public virtual void Processable_Completed(object sender, LibraryBook libraryBook) { }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region AudioDecodable event handlers
|
|
||||||
public virtual void AudioDecodable_TitleDiscovered(object sender, string title) { }
|
|
||||||
public virtual void AudioDecodable_AuthorsDiscovered(object sender, string authors) { }
|
|
||||||
public virtual void AudioDecodable_NarratorsDiscovered(object sender, string narrators) { }
|
|
||||||
|
|
||||||
public virtual void AudioDecodable_CoverImageDiscovered(object sender, byte[] coverArt) { }
|
|
||||||
public virtual void AudioDecodable_RequestCoverArt(object sender, Action<byte[]> setCoverArtDelegate) { }
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -2,30 +2,48 @@
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using Dinah.Core.Net.Http;
|
using Dinah.Core.Net.Http;
|
||||||
using Dinah.Core.Threading;
|
using Dinah.Core.Threading;
|
||||||
using LibationWinForms.BookLiberation.BaseForms;
|
using FileLiberator;
|
||||||
|
|
||||||
namespace LibationWinForms.BookLiberation
|
namespace LibationWinForms.BookLiberation
|
||||||
{
|
{
|
||||||
public partial class DownloadForm : LiberationBaseForm
|
public partial class DownloadForm : Form
|
||||||
{
|
{
|
||||||
|
protected Streamable Streamable { get; private set; }
|
||||||
|
protected LogMe LogMe { get; private set; }
|
||||||
|
private SynchronizeInvoker Invoker { get; init; }
|
||||||
|
|
||||||
public DownloadForm()
|
public DownloadForm()
|
||||||
{
|
{
|
||||||
|
//SynchronizationContext.Current will be null until the process contains a Form.
|
||||||
|
//If this is the first form created, it will not exist until after execution
|
||||||
|
//reaches inside the constructor (after base class has been initialized).
|
||||||
|
Invoker = new SynchronizeInvoker();
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
this.SetLibationIcon();
|
||||||
progressLbl.Text = "";
|
progressLbl.Text = "";
|
||||||
filenameLbl.Text = "";
|
filenameLbl.Text = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RegisterFileLiberator(Streamable streamable, LogMe logMe = null)
|
||||||
|
{
|
||||||
|
if (streamable is null) return;
|
||||||
|
streamable.StreamingBegin += Streamable_StreamingBegin;
|
||||||
|
streamable.StreamingProgressChanged += Streamable_StreamingProgressChanged;
|
||||||
|
streamable.StreamingCompleted += (_, _) => this.UIThreadAsync(Close);
|
||||||
|
Streamable = streamable;
|
||||||
|
LogMe = logMe;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#region Streamable event handler overrides
|
#region Streamable event handler overrides
|
||||||
public override void Streamable_StreamingBegin(object sender, string beginString)
|
public void Streamable_StreamingBegin(object sender, string beginString)
|
||||||
{
|
{
|
||||||
base.Streamable_StreamingBegin(sender, beginString);
|
Invoker.UIThreadAsync(Show);
|
||||||
filenameLbl.UIThreadAsync(() => filenameLbl.Text = beginString);
|
filenameLbl.UIThreadAsync(() => filenameLbl.Text = beginString);
|
||||||
}
|
}
|
||||||
public override void Streamable_StreamingProgressChanged(object sender, DownloadProgress downloadProgress)
|
public void Streamable_StreamingProgressChanged(object sender, DownloadProgress downloadProgress)
|
||||||
{
|
{
|
||||||
base.Streamable_StreamingProgressChanged(sender, downloadProgress);
|
|
||||||
// this won't happen with download file. it will happen with download string
|
// this won't happen with download file. it will happen with download string
|
||||||
if (!downloadProgress.TotalBytesToReceive.HasValue || downloadProgress.TotalBytesToReceive.Value <= 0)
|
if (!downloadProgress.TotalBytesToReceive.HasValue || downloadProgress.TotalBytesToReceive.Value <= 0)
|
||||||
return;
|
return;
|
||||||
|
|||||||
24
Source/LibationWinForms/Form1.Designer.cs
generated
24
Source/LibationWinForms/Form1.Designer.cs
generated
@ -73,7 +73,7 @@
|
|||||||
this.addQuickFilterBtn = new System.Windows.Forms.Button();
|
this.addQuickFilterBtn = new System.Windows.Forms.Button();
|
||||||
this.splitContainer1 = new System.Windows.Forms.SplitContainer();
|
this.splitContainer1 = new System.Windows.Forms.SplitContainer();
|
||||||
this.panel1 = new System.Windows.Forms.Panel();
|
this.panel1 = new System.Windows.Forms.Panel();
|
||||||
this.hideQueueBtn = new System.Windows.Forms.Button();
|
this.toggleQueueHideBtn = new System.Windows.Forms.Button();
|
||||||
this.processBookQueue1 = new LibationWinForms.ProcessQueue.ProcessQueueControl();
|
this.processBookQueue1 = new LibationWinForms.ProcessQueue.ProcessQueueControl();
|
||||||
this.menuStrip1.SuspendLayout();
|
this.menuStrip1.SuspendLayout();
|
||||||
this.statusStrip1.SuspendLayout();
|
this.statusStrip1.SuspendLayout();
|
||||||
@ -462,7 +462,7 @@
|
|||||||
// panel1
|
// panel1
|
||||||
//
|
//
|
||||||
this.panel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
|
this.panel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
|
||||||
this.panel1.Controls.Add(this.hideQueueBtn);
|
this.panel1.Controls.Add(this.toggleQueueHideBtn);
|
||||||
this.panel1.Controls.Add(this.gridPanel);
|
this.panel1.Controls.Add(this.gridPanel);
|
||||||
this.panel1.Controls.Add(this.addQuickFilterBtn);
|
this.panel1.Controls.Add(this.addQuickFilterBtn);
|
||||||
this.panel1.Controls.Add(this.filterHelpBtn);
|
this.panel1.Controls.Add(this.filterHelpBtn);
|
||||||
@ -477,15 +477,15 @@
|
|||||||
//
|
//
|
||||||
// hideQueueBtn
|
// hideQueueBtn
|
||||||
//
|
//
|
||||||
this.hideQueueBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
this.toggleQueueHideBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.hideQueueBtn.Location = new System.Drawing.Point(966, 4);
|
this.toggleQueueHideBtn.Location = new System.Drawing.Point(966, 4);
|
||||||
this.hideQueueBtn.Margin = new System.Windows.Forms.Padding(5, 4, 17, 4);
|
this.toggleQueueHideBtn.Margin = new System.Windows.Forms.Padding(5, 4, 17, 4);
|
||||||
this.hideQueueBtn.Name = "hideQueueBtn";
|
this.toggleQueueHideBtn.Name = "hideQueueBtn";
|
||||||
this.hideQueueBtn.Size = new System.Drawing.Size(38, 36);
|
this.toggleQueueHideBtn.Size = new System.Drawing.Size(38, 36);
|
||||||
this.hideQueueBtn.TabIndex = 8;
|
this.toggleQueueHideBtn.TabIndex = 8;
|
||||||
this.hideQueueBtn.Text = "❰❰❰";
|
this.toggleQueueHideBtn.Text = "❱❱❱";
|
||||||
this.hideQueueBtn.UseVisualStyleBackColor = true;
|
this.toggleQueueHideBtn.UseVisualStyleBackColor = true;
|
||||||
this.hideQueueBtn.Click += new System.EventHandler(this.HideQueueBtn_Click);
|
this.toggleQueueHideBtn.Click += new System.EventHandler(this.ToggleQueueHideBtn_Click);
|
||||||
//
|
//
|
||||||
// processBookQueue1
|
// processBookQueue1
|
||||||
//
|
//
|
||||||
@ -571,6 +571,6 @@
|
|||||||
private System.Windows.Forms.SplitContainer splitContainer1;
|
private System.Windows.Forms.SplitContainer splitContainer1;
|
||||||
private LibationWinForms.ProcessQueue.ProcessQueueControl processBookQueue1;
|
private LibationWinForms.ProcessQueue.ProcessQueueControl processBookQueue1;
|
||||||
private System.Windows.Forms.Panel panel1;
|
private System.Windows.Forms.Panel panel1;
|
||||||
private System.Windows.Forms.Button hideQueueBtn;
|
private System.Windows.Forms.Button toggleQueueHideBtn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,12 +11,18 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
//GetLibrary_Flat_NoTracking() may take a long time on a hugh library. so run in new thread
|
//GetLibrary_Flat_NoTracking() may take a long time on a hugh library. so run in new thread
|
||||||
private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e)
|
private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
=> await Task.Run(() => processBookQueue1.AddDownloadDecrypt(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking()
|
{
|
||||||
|
SetQueueCollapseState(false);
|
||||||
|
await Task.Run(() => processBookQueue1.AddDownloadDecrypt(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking()
|
||||||
.Where(lb => lb.Book.UserDefinedItem.PdfStatus is DataLayer.LiberatedStatus.NotLiberated || lb.Book.UserDefinedItem.BookStatus is DataLayer.LiberatedStatus.NotLiberated)));
|
.Where(lb => lb.Book.UserDefinedItem.PdfStatus is DataLayer.LiberatedStatus.NotLiberated || lb.Book.UserDefinedItem.BookStatus is DataLayer.LiberatedStatus.NotLiberated)));
|
||||||
|
}
|
||||||
|
|
||||||
private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e)
|
private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
=> await Task.Run(() => processBookQueue1.AddDownloadPdf(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking()
|
{
|
||||||
|
SetQueueCollapseState(false);
|
||||||
|
await Task.Run(() => processBookQueue1.AddDownloadPdf(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking()
|
||||||
.Where(lb => lb.Book.UserDefinedItem.PdfStatus is DataLayer.LiberatedStatus.NotLiberated)));
|
.Where(lb => lb.Book.UserDefinedItem.PdfStatus is DataLayer.LiberatedStatus.NotLiberated)));
|
||||||
|
}
|
||||||
|
|
||||||
private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e)
|
private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
@ -29,8 +35,11 @@ namespace LibationWinForms
|
|||||||
MessageBoxButtons.YesNo,
|
MessageBoxButtons.YesNo,
|
||||||
MessageBoxIcon.Warning);
|
MessageBoxIcon.Warning);
|
||||||
if (result == DialogResult.Yes)
|
if (result == DialogResult.Yes)
|
||||||
|
{
|
||||||
|
SetQueueCollapseState(false);
|
||||||
await Task.Run(() => processBookQueue1.AddConvertMp3(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking()
|
await Task.Run(() => processBookQueue1.AddConvertMp3(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking()
|
||||||
.Where(lb => lb.Book.UserDefinedItem.BookStatus is DataLayer.LiberatedStatus.Liberated)));
|
.Where(lb => lb.Book.UserDefinedItem.BookStatus is DataLayer.LiberatedStatus.Liberated)));
|
||||||
|
}
|
||||||
//Only Queue Liberated books for conversion. This isn't a perfect filter, but it's better than nothing.
|
//Only Queue Liberated books for conversion. This isn't a perfect filter, but it's better than nothing.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,41 +1,73 @@
|
|||||||
using ApplicationServices;
|
using DataLayer;
|
||||||
|
using Dinah.Core;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
using LibationWinForms.ProcessQueue;
|
using LibationWinForms.ProcessQueue;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
namespace LibationWinForms
|
namespace LibationWinForms
|
||||||
{
|
{
|
||||||
public partial class Form1
|
public partial class Form1
|
||||||
{
|
{
|
||||||
private void Configure_ProcessQueue()
|
|
||||||
{
|
|
||||||
productsGrid.LiberateClicked += (_, lb) => processBookQueue1.AddDownloadDecrypt(lb);
|
|
||||||
processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
int WidthChange = 0;
|
int WidthChange = 0;
|
||||||
private void HideQueueBtn_Click(object sender, EventArgs e)
|
private void Configure_ProcessQueue()
|
||||||
{
|
{
|
||||||
if (splitContainer1.Panel2Collapsed)
|
productsGrid.LiberateClicked += ProductsGrid_LiberateClicked;
|
||||||
{
|
processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut;
|
||||||
WidthChange = WidthChange == 0 ? splitContainer1.Panel2.Width + splitContainer1.SplitterWidth : WidthChange;
|
var coppalseState = Configuration.Instance.GetNonString<bool>(nameof(splitContainer1.Panel2Collapsed));
|
||||||
Width += WidthChange;
|
WidthChange = splitContainer1.Panel2.Width + splitContainer1.SplitterWidth;
|
||||||
splitContainer1.Panel2.Controls.Add(processBookQueue1);
|
SetQueueCollapseState(coppalseState);
|
||||||
splitContainer1.Panel2Collapsed = false;
|
|
||||||
processBookQueue1.popoutBtn.Visible = true;
|
|
||||||
hideQueueBtn.Text = "❰❰❰";
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
private void ProductsGrid_LiberateClicked(object sender, LibraryBook e)
|
||||||
|
{
|
||||||
|
if (e.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated)
|
||||||
|
{
|
||||||
|
SetQueueCollapseState(false);
|
||||||
|
processBookQueue1.AddDownloadDecrypt(e);
|
||||||
|
}
|
||||||
|
else if (e.Book.UserDefinedItem.PdfStatus is not null and LiberatedStatus.NotLiberated)
|
||||||
|
{
|
||||||
|
SetQueueCollapseState(false);
|
||||||
|
processBookQueue1.AddDownloadPdf(e);
|
||||||
|
}
|
||||||
|
else if (e.Book.Audio_Exists())
|
||||||
|
{
|
||||||
|
// liberated: open explorer to file
|
||||||
|
var filePath = AudibleFileStorage.Audio.GetPath(e.Book.AudibleProductId);
|
||||||
|
if (!Go.To.File(filePath))
|
||||||
|
{
|
||||||
|
var suffix = string.IsNullOrWhiteSpace(filePath) ? "" : $":\r\n{filePath}";
|
||||||
|
MessageBox.Show($"File not found" + suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetQueueCollapseState(bool collapsed)
|
||||||
|
{
|
||||||
|
if (collapsed && !splitContainer1.Panel2Collapsed)
|
||||||
{
|
{
|
||||||
WidthChange = splitContainer1.Panel2.Width + splitContainer1.SplitterWidth;
|
WidthChange = splitContainer1.Panel2.Width + splitContainer1.SplitterWidth;
|
||||||
splitContainer1.Panel2.Controls.Remove(processBookQueue1);
|
splitContainer1.Panel2.Controls.Remove(processBookQueue1);
|
||||||
splitContainer1.Panel2Collapsed = true;
|
splitContainer1.Panel2Collapsed = true;
|
||||||
Width -= WidthChange;
|
Width -= WidthChange;
|
||||||
hideQueueBtn.Text = "❱❱❱";
|
|
||||||
}
|
}
|
||||||
|
else if (!collapsed && splitContainer1.Panel2Collapsed)
|
||||||
|
{
|
||||||
|
Width += WidthChange;
|
||||||
|
splitContainer1.Panel2.Controls.Add(processBookQueue1);
|
||||||
|
splitContainer1.Panel2Collapsed = false;
|
||||||
|
processBookQueue1.popoutBtn.Visible = true;
|
||||||
|
}
|
||||||
|
toggleQueueHideBtn.Text = splitContainer1.Panel2Collapsed ? "❰❰❰" : "❱❱❱";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleQueueHideBtn_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
SetQueueCollapseState(!splitContainer1.Panel2Collapsed);
|
||||||
|
Configuration.Instance.SetObject(nameof(splitContainer1.Panel2Collapsed), splitContainer1.Panel2Collapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessBookQueue1_PopOut(object sender, EventArgs e)
|
private void ProcessBookQueue1_PopOut(object sender, EventArgs e)
|
||||||
@ -50,8 +82,8 @@ namespace LibationWinForms
|
|||||||
dockForm.PassControl(processBookQueue1);
|
dockForm.PassControl(processBookQueue1);
|
||||||
dockForm.Show();
|
dockForm.Show();
|
||||||
this.Width -= dockForm.WidthChange;
|
this.Width -= dockForm.WidthChange;
|
||||||
hideQueueBtn.Visible = false;
|
toggleQueueHideBtn.Visible = false;
|
||||||
int deltax = filterBtn.Margin.Right + hideQueueBtn.Width + hideQueueBtn.Margin.Left;
|
int deltax = filterBtn.Margin.Right + toggleQueueHideBtn.Width + toggleQueueHideBtn.Margin.Left;
|
||||||
filterBtn.Location= new System.Drawing.Point(filterBtn.Location.X + deltax, filterBtn.Location.Y);
|
filterBtn.Location= new System.Drawing.Point(filterBtn.Location.X + deltax, filterBtn.Location.Y);
|
||||||
filterSearchTb.Location = new System.Drawing.Point(filterSearchTb.Location.X + deltax, filterSearchTb.Location.Y);
|
filterSearchTb.Location = new System.Drawing.Point(filterSearchTb.Location.X + deltax, filterSearchTb.Location.Y);
|
||||||
}
|
}
|
||||||
@ -66,8 +98,8 @@ namespace LibationWinForms
|
|||||||
processBookQueue1.popoutBtn.Visible = true;
|
processBookQueue1.popoutBtn.Visible = true;
|
||||||
dockForm.SaveSizeAndLocation(Configuration.Instance);
|
dockForm.SaveSizeAndLocation(Configuration.Instance);
|
||||||
this.Focus();
|
this.Focus();
|
||||||
hideQueueBtn.Visible = true;
|
toggleQueueHideBtn.Visible = true;
|
||||||
int deltax = filterBtn.Margin.Right + hideQueueBtn.Width + hideQueueBtn.Margin.Left;
|
int deltax = filterBtn.Margin.Right + toggleQueueHideBtn.Width + toggleQueueHideBtn.Margin.Left;
|
||||||
filterBtn.Location = new System.Drawing.Point(filterBtn.Location.X - deltax, filterBtn.Location.Y);
|
filterBtn.Location = new System.Drawing.Point(filterBtn.Location.X - deltax, filterBtn.Location.Y);
|
||||||
filterSearchTb.Location = new System.Drawing.Point(filterSearchTb.Location.X - deltax, filterSearchTb.Location.Y);
|
filterSearchTb.Location = new System.Drawing.Point(filterSearchTb.Location.X - deltax, filterSearchTb.Location.Y);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,7 +61,10 @@ namespace LibationWinForms
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async void liberateVisible(object sender, EventArgs e)
|
private async void liberateVisible(object sender, EventArgs e)
|
||||||
=> await Task.Run(() => processBookQueue1.AddDownloadDecrypt(productsGrid.GetVisible()));
|
{
|
||||||
|
SetQueueCollapseState(false);
|
||||||
|
await Task.Run(() => processBookQueue1.AddDownloadDecrypt(productsGrid.GetVisible()));
|
||||||
|
}
|
||||||
|
|
||||||
private void replaceTagsToolStripMenuItem_Click(object sender, EventArgs e)
|
private void replaceTagsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -42,6 +42,8 @@ namespace LibationWinForms.ProcessQueue
|
|||||||
public bool Running => !QueueRunner?.IsCompleted ?? false;
|
public bool Running => !QueueRunner?.IsCompleted ?? false;
|
||||||
public ToolStripButton popoutBtn = new();
|
public ToolStripButton popoutBtn = new();
|
||||||
|
|
||||||
|
private System.Threading.SynchronizationContext syncContext { get; } = System.Threading.SynchronizationContext.Current;
|
||||||
|
|
||||||
public ProcessQueueControl()
|
public ProcessQueueControl()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@ -122,12 +124,13 @@ namespace LibationWinForms.ProcessQueue
|
|||||||
|
|
||||||
private void AddToQueue(ProcessBook pbook)
|
private void AddToQueue(ProcessBook pbook)
|
||||||
{
|
{
|
||||||
BeginInvoke(() =>
|
syncContext.Post(_ =>
|
||||||
{
|
{
|
||||||
Queue.Enqueue(pbook);
|
Queue.Enqueue(pbook);
|
||||||
if (!Running)
|
if (!Running)
|
||||||
QueueRunner = QueueLoop();
|
QueueRunner = QueueLoop();
|
||||||
});
|
},
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime StartintTime;
|
DateTime StartintTime;
|
||||||
@ -255,6 +258,7 @@ namespace LibationWinForms.ProcessQueue
|
|||||||
/// Updates the display of a single <see cref="ProcessBookControl"/> at <paramref name="queueIndex"/> within <see cref="Queue"/>
|
/// Updates the display of a single <see cref="ProcessBookControl"/> at <paramref name="queueIndex"/> within <see cref="Queue"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="queueIndex">index of the <see cref="ProcessBook"/> within the <see cref="Queue"/></param>
|
/// <param name="queueIndex">index of the <see cref="ProcessBook"/> within the <see cref="Queue"/></param>
|
||||||
|
/// <param name="propertyName">The nme of the property that needs updating. If null, all properties are updated.</param>
|
||||||
private void UpdateControl(int queueIndex, string propertyName = null)
|
private void UpdateControl(int queueIndex, string propertyName = null)
|
||||||
{
|
{
|
||||||
int i = queueIndex - FirstVisible;
|
int i = queueIndex - FirstVisible;
|
||||||
@ -263,12 +267,12 @@ namespace LibationWinForms.ProcessQueue
|
|||||||
|
|
||||||
var proc = Queue[queueIndex];
|
var proc = Queue[queueIndex];
|
||||||
|
|
||||||
Panels[i].Invoke(() =>
|
syncContext.Send(_ =>
|
||||||
{
|
{
|
||||||
Panels[i].SuspendLayout();
|
Panels[i].SuspendLayout();
|
||||||
if (propertyName is null || propertyName == nameof(proc.Cover))
|
if (propertyName is null or nameof(proc.Cover))
|
||||||
Panels[i].SetCover(proc.Cover);
|
Panels[i].SetCover(proc.Cover);
|
||||||
if (propertyName is null || propertyName == nameof(proc.BookText))
|
if (propertyName is null or nameof(proc.BookText))
|
||||||
Panels[i].SetBookInfo(proc.BookText);
|
Panels[i].SetBookInfo(proc.BookText);
|
||||||
|
|
||||||
if (proc.Result != ProcessBookResult.None)
|
if (proc.Result != ProcessBookResult.None)
|
||||||
@ -277,14 +281,15 @@ namespace LibationWinForms.ProcessQueue
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (propertyName is null || propertyName == nameof(proc.Status))
|
if (propertyName is null or nameof(proc.Status))
|
||||||
Panels[i].SetStatus(proc.Status);
|
Panels[i].SetStatus(proc.Status);
|
||||||
if (propertyName is null || propertyName == nameof(proc.Progress))
|
if (propertyName is null or nameof(proc.Progress))
|
||||||
Panels[i].SetProgrss(proc.Progress);
|
Panels[i].SetProgrss(proc.Progress);
|
||||||
if (propertyName is null || propertyName == nameof(proc.TimeRemaining))
|
if (propertyName is null or nameof(proc.TimeRemaining))
|
||||||
Panels[i].SetRemainingTime(proc.TimeRemaining);
|
Panels[i].SetRemainingTime(proc.TimeRemaining);
|
||||||
Panels[i].ResumeLayout();
|
Panels[i].ResumeLayout();
|
||||||
});
|
},
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateAllControls()
|
private void UpdateAllControls()
|
||||||
|
|||||||
@ -17,6 +17,8 @@ namespace LibationWinForms
|
|||||||
public SyncBindingSource(object dataSource, string dataMember) : base(dataSource, dataMember)
|
public SyncBindingSource(object dataSource, string dataMember) : base(dataSource, dataMember)
|
||||||
=> syncContext = SynchronizationContext.Current;
|
=> syncContext = SynchronizationContext.Current;
|
||||||
|
|
||||||
|
public override bool SupportsFiltering => true;
|
||||||
|
|
||||||
protected override void OnListChanged(ListChangedEventArgs e)
|
protected override void OnListChanged(ListChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (syncContext is not null)
|
if (syncContext is not null)
|
||||||
|
|||||||
@ -0,0 +1,92 @@
|
|||||||
|
using ApplicationServices;
|
||||||
|
using Dinah.Core.DataBinding;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace LibationWinForms
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Allows filtering of the underlying SortableBindingList<GridEntry>
|
||||||
|
* by implementing IBindingListView and using SearchEngineCommands
|
||||||
|
*
|
||||||
|
* When filtering is applied, the filtered-out items are removed
|
||||||
|
* from the base list and added to the private FilterRemoved list.
|
||||||
|
* When filtering is removed, items in the FilterRemoved list are
|
||||||
|
* added back to the base list.
|
||||||
|
*
|
||||||
|
* Remove is overridden to ensure that removed items are removed from
|
||||||
|
* the base list (visible items) as well as the FilterRemoved list.
|
||||||
|
*/
|
||||||
|
internal class FilterableSortableBindingList : SortableBindingList<GridEntry>, IBindingListView
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Items that were removed from the base list due to filtering
|
||||||
|
/// </summary>
|
||||||
|
private readonly List<GridEntry> FilterRemoved = new();
|
||||||
|
private string FilterString;
|
||||||
|
public FilterableSortableBindingList(IEnumerable<GridEntry> enumeration) : base(enumeration) { }
|
||||||
|
|
||||||
|
public bool SupportsFiltering => true;
|
||||||
|
public string Filter { get => FilterString; set => ApplyFilter(value); }
|
||||||
|
|
||||||
|
#region Unused - Advanced Filtering
|
||||||
|
public bool SupportsAdvancedSorting => false;
|
||||||
|
|
||||||
|
//This ApplySort overload is only called if SupportsAdvancedSorting is true.
|
||||||
|
//Otherwise BindingList.ApplySort() is used
|
||||||
|
public void ApplySort(ListSortDescriptionCollection sorts) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public ListSortDescriptionCollection SortDescriptions => throw new NotImplementedException();
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public new void Remove(GridEntry entry)
|
||||||
|
{
|
||||||
|
FilterRemoved.Remove(entry);
|
||||||
|
base.Remove(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <returns>All items in the list, including those filtered out.</returns>
|
||||||
|
public List<GridEntry> AllItems() => Items.Concat(FilterRemoved).ToList();
|
||||||
|
|
||||||
|
private void ApplyFilter(string filterString)
|
||||||
|
{
|
||||||
|
if (filterString != FilterString)
|
||||||
|
RemoveFilter();
|
||||||
|
|
||||||
|
FilterString = filterString;
|
||||||
|
|
||||||
|
var searchResults = SearchEngineCommands.Search(filterString);
|
||||||
|
var filteredOut = Items.ExceptBy(searchResults.Docs.Select(d => d.ProductId), ge => ge.AudibleProductId);
|
||||||
|
|
||||||
|
for (int i = Items.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (filteredOut.Contains(Items[i]))
|
||||||
|
{
|
||||||
|
FilterRemoved.Add(Items[i]);
|
||||||
|
Items.RemoveAt(i);
|
||||||
|
base.OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveFilter()
|
||||||
|
{
|
||||||
|
if (FilterString is null) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < FilterRemoved.Count; i++)
|
||||||
|
base.InsertItem(i, FilterRemoved[i]);
|
||||||
|
|
||||||
|
FilterRemoved.Clear();
|
||||||
|
|
||||||
|
if (IsSortedCore)
|
||||||
|
Sort();
|
||||||
|
else
|
||||||
|
//No user-defined sort is applied, so do default sorting by date added, descending
|
||||||
|
((List<GridEntry>)Items).Sort((i1, i2) => i2.LibraryBook.DateAdded.CompareTo(i1.LibraryBook.DateAdded));
|
||||||
|
|
||||||
|
FilterString = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -160,10 +160,12 @@ namespace LibationWinForms
|
|||||||
break;
|
break;
|
||||||
case nameof(udi.BookStatus):
|
case nameof(udi.BookStatus):
|
||||||
Book.UserDefinedItem.BookStatus = udi.BookStatus;
|
Book.UserDefinedItem.BookStatus = udi.BookStatus;
|
||||||
|
_bookStatus = udi.BookStatus;
|
||||||
NotifyPropertyChanged(nameof(Liberate));
|
NotifyPropertyChanged(nameof(Liberate));
|
||||||
break;
|
break;
|
||||||
case nameof(udi.PdfStatus):
|
case nameof(udi.PdfStatus):
|
||||||
Book.UserDefinedItem.PdfStatus = udi.PdfStatus;
|
Book.UserDefinedItem.PdfStatus = udi.PdfStatus;
|
||||||
|
_pdfStatus = udi.PdfStatus;
|
||||||
NotifyPropertyChanged(nameof(Liberate));
|
NotifyPropertyChanged(nameof(Liberate));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,9 +6,6 @@ using System.Threading.Tasks;
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using ApplicationServices;
|
using ApplicationServices;
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
using Dinah.Core;
|
|
||||||
using Dinah.Core.DataBinding;
|
|
||||||
using Dinah.Core.Threading;
|
|
||||||
using Dinah.Core.Windows.Forms;
|
using Dinah.Core.Windows.Forms;
|
||||||
using FileLiberator;
|
using FileLiberator;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
@ -55,8 +52,6 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
EnableDoubleBuffering();
|
EnableDoubleBuffering();
|
||||||
|
|
||||||
// sorting breaks filters. must reapply filters after sorting
|
|
||||||
_dataGridView.Sorted += reapplyFilter;
|
|
||||||
_dataGridView.CellContentClick += DataGridView_CellContentClick;
|
_dataGridView.CellContentClick += DataGridView_CellContentClick;
|
||||||
|
|
||||||
this.Load += ProductsGrid_Load;
|
this.Load += ProductsGrid_Load;
|
||||||
@ -132,20 +127,6 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
private void Liberate_Click(GridEntry liveGridEntry)
|
private void Liberate_Click(GridEntry liveGridEntry)
|
||||||
{
|
{
|
||||||
var libraryBook = liveGridEntry.LibraryBook;
|
|
||||||
|
|
||||||
// liberated: open explorer to file
|
|
||||||
if (libraryBook.Book.Audio_Exists())
|
|
||||||
{
|
|
||||||
var filePath = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId);
|
|
||||||
if (!Go.To.File(filePath))
|
|
||||||
{
|
|
||||||
var suffix = string.IsNullOrWhiteSpace(filePath) ? "" : $":\r\n{filePath}";
|
|
||||||
MessageBox.Show($"File not found" + suffix);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LiberateClicked?.Invoke(this, liveGridEntry.LibraryBook);
|
LiberateClicked?.Invoke(this, liveGridEntry.LibraryBook);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +141,7 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
#region UI display functions
|
#region UI display functions
|
||||||
|
|
||||||
private SortableBindingList<GridEntry> bindingList;
|
private FilterableSortableBindingList bindingList;
|
||||||
|
|
||||||
private bool hasBeenDisplayed;
|
private bool hasBeenDisplayed;
|
||||||
public event EventHandler InitialLoaded;
|
public event EventHandler InitialLoaded;
|
||||||
@ -169,124 +150,91 @@ namespace LibationWinForms
|
|||||||
// don't return early if lib size == 0. this will not update correctly if all books are removed
|
// don't return early if lib size == 0. this will not update correctly if all books are removed
|
||||||
var lib = DbContexts.GetLibrary_Flat_NoTracking();
|
var lib = DbContexts.GetLibrary_Flat_NoTracking();
|
||||||
|
|
||||||
var orderedBooks = lib
|
|
||||||
// default load order
|
|
||||||
.OrderByDescending(lb => lb.DateAdded)
|
|
||||||
//// more advanced example: sort by author, then series, then title
|
|
||||||
//.OrderBy(lb => lb.Book.AuthorNames)
|
|
||||||
// .ThenBy(lb => lb.Book.SeriesSortable)
|
|
||||||
// .ThenBy(lb => lb.Book.TitleSortable)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// bind
|
|
||||||
if (bindingList?.Count > 0)
|
|
||||||
updateGrid(orderedBooks);
|
|
||||||
else
|
|
||||||
bindToGrid(orderedBooks);
|
|
||||||
|
|
||||||
// re-apply previous filter
|
|
||||||
reapplyFilter();
|
|
||||||
|
|
||||||
if (!hasBeenDisplayed)
|
if (!hasBeenDisplayed)
|
||||||
{
|
{
|
||||||
|
// bind
|
||||||
|
bindToGrid(lib);
|
||||||
hasBeenDisplayed = true;
|
hasBeenDisplayed = true;
|
||||||
InitialLoaded?.Invoke(this, new());
|
InitialLoaded?.Invoke(this, new());
|
||||||
|
VisibleCountChanged?.Invoke(this, bindingList.Count);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
updateGrid(lib);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindToGrid(List<DataLayer.LibraryBook> orderedBooks)
|
private void bindToGrid(List<LibraryBook> dbBooks)
|
||||||
{
|
{
|
||||||
bindingList = new SortableBindingList<GridEntry>(orderedBooks.Select(lb => toGridEntry(lb)));
|
bindingList = new FilterableSortableBindingList(dbBooks.OrderByDescending(lb => lb.DateAdded).Select(lb => new GridEntry(lb)));
|
||||||
gridEntryBindingSource.DataSource = bindingList;
|
gridEntryBindingSource.DataSource = bindingList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateGrid(List<DataLayer.LibraryBook> orderedBooks)
|
private void updateGrid(List<LibraryBook> dbBooks)
|
||||||
{
|
{
|
||||||
for (var i = orderedBooks.Count - 1; i >= 0; i--)
|
int visibleCount = bindingList.Count;
|
||||||
|
string existingFilter = gridEntryBindingSource.Filter;
|
||||||
|
|
||||||
|
//Add absent books to grid, or update current books
|
||||||
|
|
||||||
|
var allItmes = bindingList.AllItems();
|
||||||
|
for (var i = dbBooks.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
var libraryBook = orderedBooks[i];
|
var libraryBook = dbBooks[i];
|
||||||
var existingItem = bindingList.FirstOrDefault(i => i.AudibleProductId == libraryBook.Book.AudibleProductId);
|
var existingItem = allItmes.FirstOrDefault(i => i.AudibleProductId == libraryBook.Book.AudibleProductId);
|
||||||
|
|
||||||
// add new to top
|
// add new to top
|
||||||
if (existingItem is null)
|
if (existingItem is null)
|
||||||
bindingList.Insert(0, toGridEntry(libraryBook));
|
bindingList.Insert(0, new GridEntry(libraryBook));
|
||||||
// update existing
|
// update existing
|
||||||
else
|
else
|
||||||
existingItem.UpdateLibraryBook(libraryBook);
|
existingItem.UpdateLibraryBook(libraryBook);
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove deleted from grid. note: actual deletion from db must still occur via the RemoveBook feature. deleting from audible will not trigger this
|
if (bindingList.Count != visibleCount)
|
||||||
var oldIds = bindingList.Select(ge => ge.AudibleProductId).ToList();
|
|
||||||
var newIds = orderedBooks.Select(lb => lb.Book.AudibleProductId).ToList();
|
|
||||||
var remove = oldIds.Except(newIds).ToList();
|
|
||||||
foreach (var id in remove)
|
|
||||||
{
|
{
|
||||||
var oldItem = bindingList.FirstOrDefault(ge => ge.AudibleProductId == id);
|
//re-filter for newly added items
|
||||||
if (oldItem is not null)
|
Filter(null);
|
||||||
bindingList.Remove(oldItem);
|
Filter(existingFilter);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private GridEntry toGridEntry(DataLayer.LibraryBook libraryBook)
|
// remove deleted from grid.
|
||||||
{
|
// note: actual deletion from db must still occur via the RemoveBook feature. deleting from audible will not trigger this
|
||||||
var entry = new GridEntry(libraryBook);
|
var removedBooks =
|
||||||
entry.Committed += reapplyFilter;
|
bindingList
|
||||||
// see also notes in Libation/Source/__ARCHITECTURE NOTES.txt :: MVVM
|
.AllItems()
|
||||||
entry.LibraryBookUpdated += (sender, _) => _dataGridView.InvalidateRow(_dataGridView.GetRowIdOfBoundItem((GridEntry)sender));
|
.ExceptBy(dbBooks.Select(lb => lb.Book.AudibleProductId), ge => ge.AudibleProductId)
|
||||||
return entry;
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var removed in removedBooks)
|
||||||
|
//no need to re-filter for removed books
|
||||||
|
bindingList.Remove(removed);
|
||||||
|
|
||||||
|
if (bindingList.Count != visibleCount)
|
||||||
|
VisibleCountChanged?.Invoke(this, bindingList.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Filter
|
#region Filter
|
||||||
|
|
||||||
private string _filterSearchString;
|
|
||||||
private void reapplyFilter(object _ = null, EventArgs __ = null) => Filter(_filterSearchString);
|
|
||||||
public void Filter(string searchString)
|
public void Filter(string searchString)
|
||||||
{
|
{
|
||||||
// empty string is valid. null is not
|
int visibleCount = bindingList.Count;
|
||||||
if (searchString is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_filterSearchString = searchString;
|
if (string.IsNullOrEmpty(searchString))
|
||||||
|
gridEntryBindingSource.RemoveFilter();
|
||||||
|
else
|
||||||
|
gridEntryBindingSource.Filter = searchString;
|
||||||
|
|
||||||
if (_dataGridView.Rows.Count == 0)
|
if (visibleCount != bindingList.Count)
|
||||||
return;
|
VisibleCountChanged?.Invoke(this, bindingList.Count);
|
||||||
|
|
||||||
var initVisible = getVisible().Count();
|
|
||||||
|
|
||||||
var searchResults = SearchEngineCommands.Search(searchString);
|
|
||||||
var productIds = searchResults.Docs.Select(d => d.ProductId).ToList();
|
|
||||||
|
|
||||||
// https://stackoverflow.com/a/18942430
|
|
||||||
var bindingContext = BindingContext[_dataGridView.DataSource];
|
|
||||||
bindingContext.SuspendBinding();
|
|
||||||
{
|
|
||||||
this.UIThreadSync(() =>
|
|
||||||
{
|
|
||||||
for (var r = _dataGridView.RowCount - 1; r >= 0; r--)
|
|
||||||
_dataGridView.Rows[r].Visible = productIds.Contains(getGridEntry(r).AudibleProductId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Causes repainting of the DataGridView
|
|
||||||
bindingContext.ResumeBinding();
|
|
||||||
|
|
||||||
var endVisible = getVisible().Count();
|
|
||||||
if (initVisible != endVisible)
|
|
||||||
VisibleCountChanged?.Invoke(this, endVisible);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private IEnumerable<DataGridViewRow> getVisible()
|
internal List<LibraryBook> GetVisible()
|
||||||
=> _dataGridView
|
=> bindingList
|
||||||
.AsEnumerable()
|
.Select(row => row.LibraryBook)
|
||||||
.Where(row => row.Visible);
|
|
||||||
|
|
||||||
internal List<DataLayer.LibraryBook> GetVisible()
|
|
||||||
=> getVisible()
|
|
||||||
.Select(row => ((GridEntry)row.DataBoundItem).LibraryBook)
|
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem<GridEntry>(rowIndex);
|
private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem<GridEntry>(rowIndex);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user