diff --git a/Source/AppScaffolding/AppScaffolding.csproj b/Source/AppScaffolding/AppScaffolding.csproj index db37ad59..37d7e6ca 100644 --- a/Source/AppScaffolding/AppScaffolding.csproj +++ b/Source/AppScaffolding/AppScaffolding.csproj @@ -3,7 +3,7 @@ net6.0-windows - 7.7.0.14 + 7.7.1.1 diff --git a/Source/LibationWinForms/Form1.Designer.cs b/Source/LibationWinForms/Form1.Designer.cs index 98a7b9f7..49c41b42 100644 --- a/Source/LibationWinForms/Form1.Designer.cs +++ b/Source/LibationWinForms/Form1.Designer.cs @@ -98,7 +98,7 @@ // filterBtn // this.filterBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.filterBtn.Location = new System.Drawing.Point(748, 3); + this.filterBtn.Location = new System.Drawing.Point(916, 3); this.filterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.filterBtn.Name = "filterBtn"; this.filterBtn.Size = new System.Drawing.Size(88, 27); @@ -114,7 +114,7 @@ this.filterSearchTb.Location = new System.Drawing.Point(196, 7); this.filterSearchTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.filterSearchTb.Name = "filterSearchTb"; - this.filterSearchTb.Size = new System.Drawing.Size(544, 23); + this.filterSearchTb.Size = new System.Drawing.Size(712, 23); this.filterSearchTb.TabIndex = 1; this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress); // @@ -132,7 +132,7 @@ this.menuStrip1.Location = new System.Drawing.Point(0, 0); this.menuStrip1.Name = "menuStrip1"; this.menuStrip1.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2); - this.menuStrip1.Size = new System.Drawing.Size(893, 24); + this.menuStrip1.Size = new System.Drawing.Size(1061, 24); this.menuStrip1.TabIndex = 0; this.menuStrip1.Text = "menuStrip1"; // @@ -396,7 +396,7 @@ this.statusStrip1.Location = new System.Drawing.Point(0, 618); this.statusStrip1.Name = "statusStrip1"; this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0); - this.statusStrip1.Size = new System.Drawing.Size(893, 22); + this.statusStrip1.Size = new System.Drawing.Size(1061, 22); this.statusStrip1.TabIndex = 6; this.statusStrip1.Text = "statusStrip1"; // @@ -410,7 +410,7 @@ // springLbl // this.springLbl.Name = "springLbl"; - this.springLbl.Size = new System.Drawing.Size(379, 17); + this.springLbl.Size = new System.Drawing.Size(547, 17); this.springLbl.Spring = true; // // backupsCountsLbl @@ -452,8 +452,8 @@ // splitContainer1.Panel2 // this.splitContainer1.Panel2.Controls.Add(this.processBookQueue1); - this.splitContainer1.Size = new System.Drawing.Size(1231, 640); - this.splitContainer1.SplitterDistance = 893; + this.splitContainer1.Size = new System.Drawing.Size(1463, 640); + this.splitContainer1.SplitterDistance = 1061; this.splitContainer1.SplitterWidth = 8; this.splitContainer1.TabIndex = 7; // @@ -470,7 +470,7 @@ this.panel1.Location = new System.Drawing.Point(0, 24); this.panel1.Margin = new System.Windows.Forms.Padding(0); this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(893, 594); + this.panel1.Size = new System.Drawing.Size(1061, 594); this.panel1.TabIndex = 7; // // productsDisplay @@ -482,16 +482,16 @@ this.productsDisplay.Location = new System.Drawing.Point(15, 36); this.productsDisplay.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.productsDisplay.Name = "productsDisplay"; - this.productsDisplay.Size = new System.Drawing.Size(863, 555); + this.productsDisplay.Size = new System.Drawing.Size(1031, 555); this.productsDisplay.TabIndex = 9; - this.productsDisplay.LiberateClicked += new System.EventHandler(this.ProductsDisplay_LiberateClicked); this.productsDisplay.VisibleCountChanged += new System.EventHandler(this.productsDisplay_VisibleCountChanged); + this.productsDisplay.LiberateClicked += new System.EventHandler(this.ProductsDisplay_LiberateClicked); this.productsDisplay.InitialLoaded += new System.EventHandler(this.productsDisplay_InitialLoaded); // // toggleQueueHideBtn // this.toggleQueueHideBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.toggleQueueHideBtn.Location = new System.Drawing.Point(845, 3); + this.toggleQueueHideBtn.Location = new System.Drawing.Point(1013, 3); this.toggleQueueHideBtn.Margin = new System.Windows.Forms.Padding(4, 3, 15, 3); this.toggleQueueHideBtn.Name = "toggleQueueHideBtn"; this.toggleQueueHideBtn.Size = new System.Drawing.Size(33, 27); @@ -507,14 +507,14 @@ this.processBookQueue1.Location = new System.Drawing.Point(0, 0); this.processBookQueue1.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.processBookQueue1.Name = "processBookQueue1"; - this.processBookQueue1.Size = new System.Drawing.Size(330, 640); + this.processBookQueue1.Size = new System.Drawing.Size(394, 640); this.processBookQueue1.TabIndex = 0; // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1231, 640); + this.ClientSize = new System.Drawing.Size(1463, 640); this.Controls.Add(this.splitContainer1); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.MainMenuStrip = this.menuStrip1; diff --git a/Source/LibationWinForms/Form1.resx b/Source/LibationWinForms/Form1.resx index 64da6d15..2505fa27 100644 --- a/Source/LibationWinForms/Form1.resx +++ b/Source/LibationWinForms/Form1.resx @@ -57,12 +57,48 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + True + + + True + + + True + 17, 17 + + True + 132, 17 + + True + + + True + + + True + + + True + + + True + + + True + + + True + + + True + diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs index 88c309a3..9acf1eb4 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs @@ -46,24 +46,31 @@ namespace LibationWinForms.ProcessQueue public ProcessQueueControl() { InitializeComponent(); - Logger = LogMe.RegisterForm(this); - runningTimeLbl.Text = string.Empty; popoutBtn.DisplayStyle = ToolStripItemDisplayStyle.Text; popoutBtn.Name = "popoutBtn"; popoutBtn.Text = "Pop Out"; popoutBtn.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; popoutBtn.Alignment = ToolStripItemAlignment.Right; popoutBtn.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; - statusStrip1.Items.Add(popoutBtn); + Logger = LogMe.RegisterForm(this); + virtualFlowControl2.RequestData += VirtualFlowControl1_RequestData; virtualFlowControl2.ButtonClicked += VirtualFlowControl2_ButtonClicked; Queue.QueuededCountChanged += Queue_QueuededCountChanged; Queue.CompletedCountChanged += Queue_CompletedCountChanged; + Load += ProcessQueueControl_Load; + } + + private void ProcessQueueControl_Load(object sender, EventArgs e) + { + if (DesignMode) return; + + runningTimeLbl.Text = string.Empty; QueuedCount = 0; ErrorCount = 0; CompletedCount = 0; diff --git a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs index 296e34bf..3c146703 100644 --- a/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs +++ b/Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs @@ -114,9 +114,6 @@ namespace LibationWinForms.ProcessQueue BookControls.Add(control); panel1.Controls.Add(control); - if (DesignMode) - return; - for (int i = 1; i < NUM_ACTUAL_CONTROLS; i++) { control = InitControl(VirtualControlHeight * i); diff --git a/Source/LibationWinForms/grid/GridEntry.cs b/Source/LibationWinForms/grid/GridEntry.cs index 5c53f7e9..d26ae575 100644 --- a/Source/LibationWinForms/grid/GridEntry.cs +++ b/Source/LibationWinForms/grid/GridEntry.cs @@ -5,37 +5,12 @@ using LibationFileManager; using System; using System.Collections; using System.Collections.Generic; -using System.ComponentModel;using System.Drawing; +using System.Drawing; using System.Linq; namespace LibationWinForms { - public interface IHierarchical where T : class - { - T Parent { get; } - } - - public class LiberateButtonStatus : IComparable - { - public LiberatedStatus BookStatus; - public LiberatedStatus? PdfStatus; - public bool IsSeries; - public bool Expanded; - - public int CompareTo(object obj) - { - if (obj is not LiberateButtonStatus second) return -1; - - if (IsSeries && !second.IsSeries) return -1; - else if (!IsSeries && second.IsSeries) return 1; - else if (IsSeries && second.IsSeries) return 0; - else if (BookStatus == LiberatedStatus.Liberated && second.BookStatus != LiberatedStatus.Liberated) return -1; - else if (BookStatus != LiberatedStatus.Liberated && second.BookStatus == LiberatedStatus.Liberated) return 1; - else return BookStatus.CompareTo(second.BookStatus); - } - } - - public abstract class GridEntry : AsyncNotifyPropertyChanged, IMemberComparable, IHierarchical + public abstract class GridEntry : AsyncNotifyPropertyChanged, IMemberComparable { protected abstract Book Book { get; } @@ -50,12 +25,7 @@ namespace LibationWinForms NotifyPropertyChanged(); } } - - [Browsable(false)] public new bool InvokeRequired => base.InvokeRequired; - [Browsable(false)] - public GridEntry Parent { get; set; } - [Browsable(false)] public abstract DateTime DateAdded { get; } public abstract string ProductRating { get; protected set; } public abstract string PurchaseDate { get; protected set; } @@ -116,9 +86,16 @@ namespace LibationWinForms internal static class GridEntryExtensions { + #nullable enable public static IEnumerable Series(this IEnumerable gridEntries) => gridEntries.Where(i => i is SeriesEntry).Cast(); public static IEnumerable LibraryBooks(this IEnumerable gridEntries) => gridEntries.Where(i => i is LibraryBookEntry).Cast(); + public static LibraryBookEntry? FindBookByAsin(this IEnumerable gridEntries, string audibleProductID) + => gridEntries.FirstOrDefault(i => i.AudibleProductId == audibleProductID); + public static SeriesEntry? FindBookSeriesEntry(this IEnumerable gridEntries, IEnumerable matchSeries) + => gridEntries.Series().FirstOrDefault(i => matchSeries.Any(s => s.Series.Name == i.Series)); + public static IEnumerable EmptySeries(this IEnumerable gridEntries) + => gridEntries.Series().Where(i => i.Children.Count == 0); } } diff --git a/Source/LibationWinForms/grid/FilterableSortableBindingList.cs b/Source/LibationWinForms/grid/GridEntryBindingList.cs similarity index 56% rename from Source/LibationWinForms/grid/FilterableSortableBindingList.cs rename to Source/LibationWinForms/grid/GridEntryBindingList.cs index dcb70524..fb6f7df7 100644 --- a/Source/LibationWinForms/grid/FilterableSortableBindingList.cs +++ b/Source/LibationWinForms/grid/GridEntryBindingList.cs @@ -8,7 +8,7 @@ using System.Linq; namespace LibationWinForms { /* - * Allows filtering of the underlying SortableBindingList + * Allows filtering and sorting of the underlying BindingList * by implementing IBindingListView and using SearchEngineCommands * * When filtering is applied, the filtered-out items are removed @@ -19,19 +19,34 @@ namespace LibationWinForms * 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 : SortableBindingList1, IBindingListView + internal class GridEntryBindingList : BindingList, IBindingListView { + private bool isSorted; + private ListSortDirection listSortDirection; + private PropertyDescriptor propertyDescriptor; + + public GridEntryBindingList() : base(new List()) { } + public GridEntryBindingList(IEnumerable enumeration) : base(new List(enumeration)) { } + + /// All items in the list, including those filtered out. + public List AllItems() => Items.Concat(FilterRemoved).ToList(); + + protected MemberComparer Comparer { get; } = new(); + protected override bool SupportsSortingCore => true; + protected override bool SupportsSearchingCore => true; + protected override bool IsSortedCore => isSorted; + protected override PropertyDescriptor SortPropertyCore => propertyDescriptor; + protected override ListSortDirection SortDirectionCore => listSortDirection; + public bool SupportsFiltering => true; + public string Filter { get => FilterString; set => ApplyFilter(value); } + /// /// Items that were removed from the base list due to filtering /// private readonly List FilterRemoved = new(); private string FilterString; private LibationSearchEngine.SearchResultSet SearchResults; - public FilterableSortableBindingList(IEnumerable enumeration) : base(enumeration) { } - public FilterableSortableBindingList() : base(new List()) { } - public bool SupportsFiltering => true; - public string Filter { get => FilterString; set => ApplyFilter(value); } #region Unused - Advanced Filtering public bool SupportsAdvancedSorting => false; @@ -49,9 +64,6 @@ namespace LibationWinForms base.Remove(entry); } - /// All items in the list, including those filtered out. - public List AllItems() => Items.Concat(FilterRemoved).ToList(); - private void ApplyFilter(string filterString) { if (filterString != FilterString) @@ -88,7 +100,7 @@ namespace LibationWinForms public void CollapseItem(SeriesEntry sEntry) { - foreach (var episode in Items.Where(b => b.Parent == sEntry).Cast().ToList()) + foreach (var episode in Items.LibraryBooks().Where(b => b.Parent == sEntry).ToList()) { FilterRemoved.Add(episode); base.Remove(episode); @@ -101,7 +113,7 @@ namespace LibationWinForms { var sindex = Items.IndexOf(sEntry); - foreach (var episode in FilterRemoved.Where(b => b.Parent == sEntry).Cast().ToList()) + foreach (var episode in FilterRemoved.LibraryBooks().Where(b => b.Parent == sEntry).ToList()) { if (SearchResults is null || SearchResults.Docs.Any(d => d.ProductId == episode.AudibleProductId)) { @@ -120,23 +132,19 @@ namespace LibationWinForms int visibleCount = Items.Count; - SuspendSorting = true; - foreach (var item in FilterRemoved.ToList()) { - if (item.Parent is null || item.Parent.Liberate.Expanded) + if (item is SeriesEntry || (item is LibraryBookEntry lbe && (lbe.Parent is null || lbe.Parent.Liberate.Expanded))) { FilterRemoved.Remove(item); base.InsertItem(visibleCount++, item); } } - SuspendSorting = false; - if (IsSortedCore) Sort(); else - //No user sort is applied, so do default sorting by PurchaseDate, descending + //No user sort is applied, so do default sorting by DateAdded, descending { Comparer.PropertyName = nameof(GridEntry.DateAdded); Comparer.Direction = ListSortDirection.Descending; @@ -148,5 +156,64 @@ namespace LibationWinForms FilterString = null; SearchResults = null; } + + protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) + { + Comparer.PropertyName = property.Name; + Comparer.Direction = direction; + + Sort(); + + propertyDescriptor = property; + listSortDirection = direction; + isSorted = true; + + OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); + } + + protected void Sort() + { + var itemsList = (List)Items; + + var sortedItems = Items.OrderBy(ge => ge, Comparer).ToList(); + + var children = sortedItems.LibraryBooks().Where(i => i.Parent is not null).ToList(); + + itemsList.Clear(); + + //Only add parentless items at this stage. After these items are added in the + //correct sorting order, go back and add the children beneath their parents. + itemsList.AddRange(sortedItems.Except(children)); + + foreach (var parent in children.Select(c => c.Parent).Distinct()) + { + var pIndex = itemsList.IndexOf(parent); + foreach (var c in children.Where(c => c.Parent == parent)) + itemsList.Insert(++pIndex, c); + } + } + + protected override void OnListChanged(ListChangedEventArgs e) + { + if (isSorted && e.ListChangedType == ListChangedType.ItemChanged && e.PropertyDescriptor == SortPropertyCore) + { + var item = Items[e.NewIndex]; + Sort(); + var newIndex = Items.IndexOf(item); + + base.OnListChanged(new ListChangedEventArgs(ListChangedType.ItemMoved, newIndex, e.NewIndex)); + } + else + base.OnListChanged(e); + } + + protected override void RemoveSortCore() + { + isSorted = false; + propertyDescriptor = base.SortPropertyCore; + listSortDirection = base.SortDirectionCore; + + OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); + } } } diff --git a/Source/LibationWinForms/grid/LiberateButtonStatus.cs b/Source/LibationWinForms/grid/LiberateButtonStatus.cs new file mode 100644 index 00000000..8814eb24 --- /dev/null +++ b/Source/LibationWinForms/grid/LiberateButtonStatus.cs @@ -0,0 +1,28 @@ +using DataLayer; +using System; + +namespace LibationWinForms +{ + public class LiberateButtonStatus : IComparable + { + public LiberatedStatus BookStatus { get; set; } + public LiberatedStatus? PdfStatus { get; set; } + public bool IsSeries { get; init; } + public bool Expanded { get; set; } + + /// + /// Defines the Liberate column's sorting behavior + /// + public int CompareTo(object obj) + { + if (obj is not LiberateButtonStatus second) return -1; + + if (IsSeries && !second.IsSeries) return -1; + else if (!IsSeries && second.IsSeries) return 1; + else if (IsSeries && second.IsSeries) return 0; + else if (BookStatus == LiberatedStatus.Liberated && second.BookStatus != LiberatedStatus.Liberated) return -1; + else if (BookStatus != LiberatedStatus.Liberated && second.BookStatus == LiberatedStatus.Liberated) return 1; + else return BookStatus.CompareTo(second.BookStatus); + } + } +} diff --git a/Source/LibationWinForms/grid/LibraryBookEntry.cs b/Source/LibationWinForms/grid/LibraryBookEntry.cs index a02686eb..3834e502 100644 --- a/Source/LibationWinForms/grid/LibraryBookEntry.cs +++ b/Source/LibationWinForms/grid/LibraryBookEntry.cs @@ -1,16 +1,10 @@ using System; -using System.Collections; using System.Collections.Generic; using System.ComponentModel; -using System.Drawing; using System.Linq; using ApplicationServices; using DataLayer; -using Dinah.Core.DataBinding; using Dinah.Core; -using Dinah.Core.Drawing; -using LibationFileManager; -using System.Threading.Tasks; namespace LibationWinForms { @@ -30,7 +24,6 @@ namespace LibationWinForms public string LongDescription { get; private set; } #endregion - // alias protected override Book Book => LibraryBook.Book; #region Model properties exposed to the view @@ -69,9 +62,9 @@ namespace LibationWinForms } #endregion - public LibraryBookEntry(LibraryBook libraryBook) => setLibraryBook(libraryBook); + public SeriesEntry Parent { get; init; } public void UpdateLibraryBook(LibraryBook libraryBook) { if (AudibleProductId != libraryBook.Book.AudibleProductId) diff --git a/Source/LibationWinForms/grid/ProductsGrid.Designer.cs b/Source/LibationWinForms/grid/ProductsGrid.Designer.cs index cd312da0..fec72094 100644 --- a/Source/LibationWinForms/grid/ProductsGrid.Designer.cs +++ b/Source/LibationWinForms/grid/ProductsGrid.Designer.cs @@ -47,10 +47,8 @@ this.tagAndDetailsGVColumn = new LibationWinForms.EditTagsDataGridViewImageButtonColumn(); this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components); this.syncBindingSource = new LibationWinForms.SyncBindingSource(this.components); - this.bindingSource = new System.Windows.Forms.BindingSource(this.components); ((System.ComponentModel.ISupportInitialize)(this.gridEntryDataGridView)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.syncBindingSource)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.bindingSource)).BeginInit(); this.SuspendLayout(); // // gridEntryDataGridView @@ -216,11 +214,7 @@ // // syncBindingSource // - this.syncBindingSource.DataSource = this.bindingSource; - // - // bindingSource - // - this.bindingSource.DataSource = typeof(LibationWinForms.GridEntry); + this.syncBindingSource.DataSource = typeof(LibationWinForms.GridEntry); // // ProductsGrid // @@ -233,7 +227,6 @@ this.Load += new System.EventHandler(this.ProductsGrid_Load); ((System.ComponentModel.ISupportInitialize)(this.gridEntryDataGridView)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.syncBindingSource)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.bindingSource)).EndInit(); this.ResumeLayout(false); } @@ -257,6 +250,5 @@ private System.Windows.Forms.DataGridViewTextBoxColumn miscGVColumn; private EditTagsDataGridViewImageButtonColumn tagAndDetailsGVColumn; private SyncBindingSource syncBindingSource; - private System.Windows.Forms.BindingSource bindingSource; } } diff --git a/Source/LibationWinForms/grid/ProductsGrid.cs b/Source/LibationWinForms/grid/ProductsGrid.cs index f0fc1497..8e3cfe8d 100644 --- a/Source/LibationWinForms/grid/ProductsGrid.cs +++ b/Source/LibationWinForms/grid/ProductsGrid.cs @@ -23,7 +23,7 @@ namespace LibationWinForms.grid public event LibraryBookEntryRectangleClickedEventHandler DescriptionClicked; public new event EventHandler Scroll; - private FilterableSortableBindingList bindingList; + private GridEntryBindingList bindingList; internal IEnumerable GetVisible() => bindingList .LibraryBooks(); @@ -89,20 +89,15 @@ namespace LibationWinForms.grid var episodes = dbBooks.Where(b => b.Book.ContentType is ContentType.Episode).ToList(); - var series = episodes.Select(lb => lb.Book.SeriesLink.First()).DistinctBy(s => s.Series).ToList(); - - foreach (var s in series) + foreach (var series in episodes.Select(lb => lb.Book.SeriesLink.First()).DistinctBy(s => s.Series)) { - var seriesEntry = new SeriesEntry(); - seriesEntry.Children = episodes.Where(lb => lb.Book.SeriesLink.First().Series == s.Book.SeriesLink.First().Series).Select(lb => new LibraryBookEntry(lb) { Parent = seriesEntry }).ToList(); - - seriesEntry.setSeriesBook(s); + var seriesEntry = new SeriesEntry(series, episodes.Where(lb => lb.Book.SeriesLink.First().Series == series.Book.SeriesLink.First().Series)); geList.Add(seriesEntry); geList.AddRange(seriesEntry.Children); } - bindingList = new FilterableSortableBindingList(geList.OrderByDescending(e => e.DateAdded)); + bindingList = new GridEntryBindingList(geList.OrderByDescending(e => e.DateAdded)); bindingList.CollapseAll(); syncBindingSource.DataSource = bindingList; VisibleCountChanged?.Invoke(this, bindingList.LibraryBooks().Count()); @@ -116,39 +111,36 @@ namespace LibationWinForms.grid //Add absent books to grid, or update current books var allItmes = bindingList.AllItems().LibraryBooks(); - for (var i = dbBooks.Count - 1; i >= 0; i--) + foreach (var libraryBook in dbBooks) { - var libraryBook = dbBooks[i]; - var existingItem = allItmes.FirstOrDefault(i => i.AudibleProductId == libraryBook.Book.AudibleProductId); + var existingItem = allItmes.FindBookByAsin(libraryBook.Book.AudibleProductId); // add new to top if (existingItem is null) { - var lb = new LibraryBookEntry(libraryBook); - if (libraryBook.Book.ContentType is ContentType.Episode) { + LibraryBookEntry lbe; //Find the series that libraryBook belongs to, if it exists - var series = bindingList.AllItems().Series().FirstOrDefault(i => libraryBook.Book.SeriesLink.Any(s => s.Series.Name == i.Series)); + var series = bindingList.AllItems().FindBookSeriesEntry(libraryBook.Book.SeriesLink); if (series is null) { //Series doesn't exist yet, so create and add it - var newSeries = new SeriesEntry { Children = new List { lb } }; - newSeries.setSeriesBook(libraryBook.Book.SeriesLink.First()); - lb.Parent = newSeries; + var newSeries = new SeriesEntry(libraryBook.Book.SeriesLink.First(), libraryBook); + lbe = newSeries.Children[0]; newSeries.Liberate.Expanded = true; bindingList.Insert(0, newSeries); series = newSeries; } else { - lb.Parent = series; - series.Children.Add(lb); + lbe = new(libraryBook) { Parent = series }; + series.Children.Add(lbe); } //Add episode beneath the parent int seriesIndex = bindingList.IndexOf(series); - bindingList.Insert(seriesIndex + 1, lb); + bindingList.Insert(seriesIndex + 1, lbe); if (series.Liberate.Expanded) bindingList.ExpandItem(series); @@ -159,7 +151,7 @@ namespace LibationWinForms.grid } else //Add the new product - bindingList.Insert(0, lb); + bindingList.Insert(0, new LibraryBookEntry(libraryBook)); } // update existing else @@ -168,6 +160,9 @@ namespace LibationWinForms.grid } } + //Re-filter after updating existing / adding new books to capture any changes + Filter(existingFilter); + // remove deleted from grid. // note: actual deletion from db must still occur via the RemoveBook feature. deleting from audible will not trigger this var removedBooks = @@ -176,26 +171,23 @@ namespace LibationWinForms.grid .LibraryBooks() .ExceptBy(dbBooks.Select(lb => lb.Book.AudibleProductId), ge => ge.AudibleProductId); + //Remove books in series from their parents' Children list foreach (var removed in removedBooks.Where(b => b.Parent is not null)) { - var series = removed.Parent as SeriesEntry; - series.Children.Remove(removed); - series.NotifyPropertyChanged(); + removed.Parent.Children.Remove(removed); + removed.Parent.NotifyPropertyChanged(); } //Remove series that have no children var removedSeries = bindingList .AllItems() - .Series() - .Where(i => i.Children.Count == 0); + .EmptySeries(); foreach (var removed in removedBooks.Cast().Concat(removedSeries)) //no need to re-filter for removed books bindingList.Remove(removed); - Filter(existingFilter); - VisibleCountChanged?.Invoke(this, bindingList.LibraryBooks().Count()); } diff --git a/Source/LibationWinForms/grid/SeriesEntry.cs b/Source/LibationWinForms/grid/SeriesEntry.cs index bc74f147..9646c52f 100644 --- a/Source/LibationWinForms/grid/SeriesEntry.cs +++ b/Source/LibationWinForms/grid/SeriesEntry.cs @@ -6,9 +6,9 @@ using System.Linq; namespace LibationWinForms { - internal class SeriesEntry : GridEntry + public class SeriesEntry : GridEntry { - public List Children { get; set; } + public List Children { get; init; } public override DateTime DateAdded => Children.Max(c => c.DateAdded); public override string ProductRating { @@ -55,7 +55,18 @@ namespace LibationWinForms private LiberateButtonStatus _liberate = new LiberateButtonStatus { IsSeries = true }; - public void setSeriesBook(SeriesBook seriesBook) + public SeriesEntry(SeriesBook seriesBook, IEnumerable children) + { + Children = children.Select(c=>new LibraryBookEntry(c) { Parent = this }).ToList(); + SetSeriesBook(seriesBook); + } + public SeriesEntry(SeriesBook seriesBook, LibraryBook child) + { + Children = new() { new LibraryBookEntry(child) { Parent = this } }; + SetSeriesBook(seriesBook); + } + + private void SetSeriesBook(SeriesBook seriesBook) { SeriesBook = seriesBook; _memberValues = CreateMemberValueDictionary(); diff --git a/Source/LibationWinForms/grid/SortableBindingList1.cs b/Source/LibationWinForms/grid/SortableBindingList1.cs deleted file mode 100644 index ec545340..00000000 --- a/Source/LibationWinForms/grid/SortableBindingList1.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Dinah.Core.DataBinding; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; - -namespace LibationWinForms -{ - internal class SortableBindingList1 : BindingList where T : class, IMemberComparable, IHierarchical - { - private bool isSorted; - private ListSortDirection listSortDirection; - private PropertyDescriptor propertyDescriptor; - - public SortableBindingList1() : base(new List()) { } - public SortableBindingList1(IEnumerable enumeration) : base(new List(enumeration)) { } - - protected bool SuspendSorting { get; set; } - protected MemberComparer Comparer { get; } = new(); - protected override bool SupportsSortingCore => true; - protected override bool SupportsSearchingCore => true; - protected override bool IsSortedCore => isSorted; - protected override PropertyDescriptor SortPropertyCore => propertyDescriptor; - protected override ListSortDirection SortDirectionCore => listSortDirection; - - protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) - { - Comparer.PropertyName = property.Name; - Comparer.Direction = direction; - - Sort(); - - propertyDescriptor = property; - listSortDirection = direction; - isSorted = true; - - OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); - } - - protected void Sort() - { - List itemsList = (List)Items; - - var sortedItems = Items.OrderBy(ge => ge, Comparer).ToList(); - - var children = sortedItems.Where(i => i.Parent is not null).ToList(); - - itemsList.Clear(); - - //Only add parentless items at this stage. After these items are added in the - //correct sorting order, go back and add the children beneath their parents. - itemsList.AddRange(sortedItems.Except(children)); - - foreach (var parent in children.Select(c => c.Parent).Distinct()) - { - var pIndex = itemsList.IndexOf(parent); - foreach (var c in children.Where(c=> c.Parent == parent)) - itemsList.Insert(++pIndex, c); - } - } - - protected override void OnListChanged(ListChangedEventArgs e) - { - if (isSorted && !SuspendSorting && - ((e.ListChangedType == ListChangedType.ItemChanged && e.PropertyDescriptor == SortPropertyCore) || - e.ListChangedType == ListChangedType.ItemAdded)) - { - var item = Items[e.NewIndex]; - Sort(); - var newIndex = Items.IndexOf(item); - - base.OnListChanged(new ListChangedEventArgs(ListChangedType.ItemMoved, newIndex, e.NewIndex)); - } - else - base.OnListChanged(e); - } - - protected override void RemoveSortCore() - { - isSorted = false; - propertyDescriptor = base.SortPropertyCore; - listSortDirection = base.SortDirectionCore; - - OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); - } - } -}