diff --git a/LibationWinForms/AsyncNotifyPropertyChanged.cs b/LibationWinForms/AsyncNotifyPropertyChanged.cs new file mode 100644 index 00000000..9de0911d --- /dev/null +++ b/LibationWinForms/AsyncNotifyPropertyChanged.cs @@ -0,0 +1,37 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace LibationWinForms +{ + public abstract class AsyncNotifyPropertyChanged : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId; + private bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId; + private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current; + + protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") + { + var propertyChangedArgs = new PropertyChangedEventArgs(propertyName); + + if (InvokeRequired) + { + SyncContext.Post( + PostPropertyChangedCallback, + new AsyncCompletedEventArgs(null, false, propertyChangedArgs)); + } + else + { + OnPropertyChanged(propertyChangedArgs); + } + } + private void PostPropertyChangedCallback(object asyncArgs) + { + var e = asyncArgs as AsyncCompletedEventArgs; + + OnPropertyChanged(e.UserState as PropertyChangedEventArgs); + } + private void OnPropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e); + } +} diff --git a/LibationWinForms/DataGridViewImageButtonColumn.cs b/LibationWinForms/DataGridViewImageButtonColumn.cs index 31d41780..e607a069 100644 --- a/LibationWinForms/DataGridViewImageButtonColumn.cs +++ b/LibationWinForms/DataGridViewImageButtonColumn.cs @@ -37,7 +37,7 @@ namespace LibationWinForms public class DataGridViewImageButtonCell : DataGridViewButtonCell { - protected void DrawImage(Graphics graphics, Bitmap image, Rectangle cellBounds) + protected void DrawButtonImage(Graphics graphics, Image image, Rectangle cellBounds) { var w = image.Width; var h = image.Height; diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs b/LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs index af5435cc..5bf4e6c9 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs @@ -31,7 +31,7 @@ namespace LibationWinForms.Dialogs { this.components = new System.ComponentModel.Container(); System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); - this.dataGridView1 = new System.Windows.Forms.DataGridView(); + this._dataGridView = new System.Windows.Forms.DataGridView(); this.removeDataGridViewCheckBoxColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn(); this.coverDataGridViewImageColumn = new System.Windows.Forms.DataGridViewImageColumn(); this.titleDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); @@ -41,25 +41,25 @@ namespace LibationWinForms.Dialogs this.gridEntryBindingSource = new System.Windows.Forms.BindingSource(this.components); this.btnRemoveBooks = new System.Windows.Forms.Button(); this.label1 = new System.Windows.Forms.Label(); - ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this._dataGridView)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).BeginInit(); this.SuspendLayout(); // - // dataGridView1 + // _dataGridView // - this.dataGridView1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + this._dataGridView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.dataGridView1.AutoGenerateColumns = false; - this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; - this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { + this._dataGridView.AutoGenerateColumns = false; + this._dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this._dataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { this.removeDataGridViewCheckBoxColumn, this.coverDataGridViewImageColumn, this.titleDataGridViewTextBoxColumn, this.authorsDataGridViewTextBoxColumn, this.miscDataGridViewTextBoxColumn, this.purchaseDateGridViewTextBoxColumn}); - this.dataGridView1.DataSource = this.gridEntryBindingSource; + this._dataGridView.DataSource = this.gridEntryBindingSource; dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Window; dataGridViewCellStyle2.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); @@ -67,13 +67,13 @@ namespace LibationWinForms.Dialogs dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight; dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText; dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.dataGridView1.DefaultCellStyle = dataGridViewCellStyle2; - this.dataGridView1.Location = new System.Drawing.Point(0, 0); - this.dataGridView1.Name = "dataGridView1"; - this.dataGridView1.RowHeadersVisible = false; - this.dataGridView1.RowTemplate.Height = 82; - this.dataGridView1.Size = new System.Drawing.Size(800, 409); - this.dataGridView1.TabIndex = 0; + this._dataGridView.DefaultCellStyle = dataGridViewCellStyle2; + this._dataGridView.Location = new System.Drawing.Point(0, 0); + this._dataGridView.Name = "_dataGridView"; + this._dataGridView.RowHeadersVisible = false; + this._dataGridView.RowTemplate.Height = 82; + this._dataGridView.Size = new System.Drawing.Size(800, 409); + this._dataGridView.TabIndex = 0; // // removeDataGridViewCheckBoxColumn // @@ -162,11 +162,11 @@ namespace LibationWinForms.Dialogs this.ClientSize = new System.Drawing.Size(800, 450); this.Controls.Add(this.label1); this.Controls.Add(this.btnRemoveBooks); - this.Controls.Add(this.dataGridView1); + this.Controls.Add(this._dataGridView); this.Name = "RemoveBooksDialog"; this.Text = "RemoveBooksDialog"; this.Shown += new System.EventHandler(this.RemoveBooksDialog_Shown); - ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this._dataGridView)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); @@ -175,7 +175,7 @@ namespace LibationWinForms.Dialogs #endregion - private System.Windows.Forms.DataGridView dataGridView1; + private System.Windows.Forms.DataGridView _dataGridView; private System.Windows.Forms.BindingSource gridEntryBindingSource; private System.Windows.Forms.Button btnRemoveBooks; private System.Windows.Forms.Label label1; diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.cs b/LibationWinForms/Dialogs/RemoveBooksDialog.cs index 5c7a9849..a31ca838 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.cs +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.cs @@ -31,9 +31,9 @@ namespace LibationWinForms.Dialogs InitializeComponent(); _labelFormat = label1.Text; - dataGridView1.CellContentClick += (s, e) => dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit); - dataGridView1.CellValueChanged += DataGridView1_CellValueChanged; - dataGridView1.BindingContextChanged += (s, e) => UpdateSelection(); + _dataGridView.CellContentClick += (s, e) => _dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit); + _dataGridView.CellValueChanged += DataGridView1_CellValueChanged; + _dataGridView.BindingContextChanged += (s, e) => UpdateSelection(); var orderedGridEntries = _libraryBooks .Select(lb => new RemovableGridEntry(lb)) @@ -43,7 +43,7 @@ namespace LibationWinForms.Dialogs _removableGridEntries = new SortableBindingList2(orderedGridEntries); gridEntryBindingSource.DataSource = _removableGridEntries; - dataGridView1.Enabled = false; + _dataGridView.Enabled = false; } private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) @@ -79,7 +79,7 @@ namespace LibationWinForms.Dialogs } finally { - dataGridView1.Enabled = true; + _dataGridView.Enabled = true; } } @@ -103,12 +103,12 @@ namespace LibationWinForms.Dialogs MessageBoxDefaultButton.Button1); if (result == DialogResult.Yes) - { + { using var context = DbContexts.GetContext(); var libBooks = context.GetLibrary_Flat_NoTracking(); - var removeLibraryBooks = libBooks.Where(lb => selectedBooks.Any(rge => rge.AudibleProductId == lb.Book.AudibleProductId)).ToArray(); + var removeLibraryBooks = libBooks.Where(lb => selectedBooks.Any(rge => rge.AudibleProductId == lb.Book.AudibleProductId)).ToList(); context.Library.RemoveRange(removeLibraryBooks); context.SaveChanges(); @@ -116,14 +116,14 @@ namespace LibationWinForms.Dialogs foreach (var rEntry in selectedBooks) _removableGridEntries.Remove(rEntry); - BooksRemoved = removeLibraryBooks.Length > 0; + BooksRemoved = removeLibraryBooks.Count > 0; UpdateSelection(); } } private void UpdateSelection() { - dataGridView1.Sort(dataGridView1.Columns[0], ListSortDirection.Descending); + _dataGridView.Sort(_dataGridView.Columns[0], ListSortDirection.Descending); var selectedCount = SelectedCount; label1.Text = string.Format(_labelFormat, selectedCount, selectedCount != 1 ? "s" : string.Empty); btnRemoveBooks.Enabled = selectedCount > 0; @@ -153,19 +153,18 @@ namespace LibationWinForms.Dialogs } } - public override object GetMemberValue(string propertyName) + public override object GetMemberValue(string memberName) { - if (propertyName == nameof(Remove)) + if (memberName == nameof(Remove)) return Remove; - return base.GetMemberValue(propertyName); + return base.GetMemberValue(memberName); } - public override IComparer GetComparer(Type propertyType) - { - if (propertyType == typeof(bool)) + public override IComparer GetMemberComparer(Type memberType) + { + if (memberType == typeof(bool)) return BoolComparer; - - return base.GetComparer(propertyType); - } + return base.GetMemberComparer(memberType); + } } } diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.resx b/LibationWinForms/Dialogs/RemoveBooksDialog.resx index 774ae444..a3058bc8 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.resx +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.resx @@ -1,64 +1,4 @@ - - - + @@ -117,4 +57,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 17, 17 + \ No newline at end of file diff --git a/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs b/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs index 58f39545..1f4cf04d 100644 --- a/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs +++ b/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs @@ -11,19 +11,26 @@ namespace LibationWinForms internal class EditTagsDataGridViewImageButtonCell : DataGridViewImageButtonCell { - private static readonly Bitmap ButtonImage = Properties.Resources.edit_tags_25x25; + private static readonly Image ButtonImage = Properties.Resources.edit_tags_25x25; private static readonly Color HiddenForeColor = Color.LightGray; protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts) { - var valueString = (string)value; + var tagsString = (string)value; - DataGridView.Rows[RowIndex].DefaultCellStyle.ForeColor = valueString?.Contains("hidden") == true ? HiddenForeColor : DataGridView.DefaultCellStyle.ForeColor; + var foreColor = tagsString?.Contains("hidden") == true ? HiddenForeColor : DataGridView.DefaultCellStyle.ForeColor; - if (valueString.Length == 0) + if (DataGridView.Rows[RowIndex].DefaultCellStyle.ForeColor != foreColor) + { + DataGridView.Rows[RowIndex].DefaultCellStyle.ForeColor = foreColor; + + DataGridView.InvalidateRow(RowIndex); + } + + if (tagsString.Length == 0) { base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts); - DrawImage(graphics, ButtonImage, cellBounds); + DrawButtonImage(graphics, ButtonImage, cellBounds); } else { diff --git a/LibationWinForms/Form1.cs b/LibationWinForms/Form1.cs index b48abc32..30bba304 100644 --- a/LibationWinForms/Form1.cs +++ b/LibationWinForms/Form1.cs @@ -337,6 +337,7 @@ namespace LibationWinForms scanLibraries(scanAccountsDialog.CheckedAccounts); } + private void removeThisAccountToolStripMenuItem_Click(object sender, EventArgs e) { using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); @@ -363,6 +364,7 @@ namespace LibationWinForms scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray()); } + private void scanLibrariesRemovedBooks(params Account[] accounts) { using var dialog = new RemoveBooksDialog(accounts); diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs index 25db3669..64a802f7 100644 --- a/LibationWinForms/GridEntry.cs +++ b/LibationWinForms/GridEntry.cs @@ -4,36 +4,32 @@ using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; using ApplicationServices; using DataLayer; using Dinah.Core.Drawing; namespace LibationWinForms { - internal class GridEntry : INotifyPropertyChanged, IObjectMemberComparable + internal class GridEntry : AsyncNotifyPropertyChanged, IObjectMemberComparable { - #region implementation properties - // hide from public fields from Data Source GUI with [Browsable(false)] + #region implementation properties + // hide from public fields from Data Source GUI with [Browsable(false)] - [Browsable(false)] + [Browsable(false)] public string AudibleProductId => Book.AudibleProductId; [Browsable(false)] public LibraryBook LibraryBook { get; } #endregion - public event PropertyChangedEventHandler PropertyChanged; private Book Book => LibraryBook.Book; - private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current; private Image _cover; public GridEntry(LibraryBook libraryBook) { LibraryBook = libraryBook; - _compareValues = CreatePropertyValueDictionary(); + _memberValues = CreateMemberValueDictionary(); //Get cover art. If it's default, subscribe to PictureCached var picDef = new FileManager.PictureDefinition(Book.PictureId, FileManager.PictureSize._80x80); @@ -42,7 +38,7 @@ namespace LibationWinForms if (isDefault) FileManager.PictureStorage.PictureCached += PictureStorage_PictureCached; - //Mutable property. Set the field so PropertyChanged doesn't fire. + //Mutable property. Set the field so PropertyChanged isn't fired. _cover = ImageReader.ToImage(picture); //Immutable properties @@ -50,9 +46,9 @@ namespace LibationWinForms Title = Book.Title; Series = Book.SeriesNames; Length = Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min"; - MyRating = GetStarString(Book.UserDefinedItem.Rating); + MyRating = ValueOrDefault(Book.UserDefinedItem.Rating?.ToStarString(), ""); PurchaseDate = libraryBook.DateAdded.ToString("d"); - ProductRating = GetStarString(Book.Rating); + ProductRating = ValueOrDefault(Book.Rating?.ToStarString(), ""); Authors = Book.AuthorNames; Narrators = Book.NarratorNames; Category = string.Join(" > ", Book.CategoriesNames); @@ -60,28 +56,20 @@ namespace LibationWinForms Description = GetDescriptionDisplay(Book); } - //DisplayTags and Liberate are live. + //DisplayTags and Liberate properties are live. } - private void PictureStorage_PictureCached(object sender, string pictureId) + private void PictureStorage_PictureCached(object sender, string pictureId) { if (pictureId == Book.PictureId) { - //GridEntry SHOULD be UI-ignorant, but PropertyChanged Cover = WindowsDesktopUtilities.WinAudibleImageServer.GetImage(pictureId, FileManager.PictureSize._80x80); FileManager.PictureStorage.PictureCached -= PictureStorage_PictureCached; } } - protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") - => SyncContext.Post( - args => OnPropertyChangedAsync(args as AsyncCompletedEventArgs), - new AsyncCompletedEventArgs(null, false, new PropertyChangedEventArgs(propertyName))); - - private void OnPropertyChangedAsync(AsyncCompletedEventArgs e) => - PropertyChanged?.Invoke(this, e.UserState as PropertyChangedEventArgs); - #region Data Source properties + public Image Cover { get @@ -106,7 +94,6 @@ namespace LibationWinForms public string Category { get; } public string Misc { get; } public string Description { get; } - public string DisplayTags => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated); public (LiberatedState, PdfState) Liberate => (LibraryCommands.Liberated_Status(Book), LibraryCommands.Pdf_Status(Book)); @@ -114,49 +101,43 @@ namespace LibationWinForms #region Data Sorting - private Dictionary> _compareValues { get; } - private static Dictionary _objectComparers; - - public virtual object GetMemberValue(string memberName) => _compareValues[memberName](); - public virtual IComparer GetMemberComparer(Type memberType) => _objectComparers[memberType]; + private Dictionary> _memberValues { get; } /// - /// Instantiate comparers for every type needed to sort columns. + /// Create getters for all member object values by name /// - static GridEntry() + Dictionary> CreateMemberValueDictionary() => new() { - _objectComparers = new Dictionary() - { - { typeof(string), new ObjectComparer() }, - { typeof(int), new ObjectComparer() }, - { typeof(float), new ObjectComparer() }, - { typeof(DateTime), new ObjectComparer() }, - { typeof(LiberatedState), new ObjectComparer() }, - }; - } + { nameof(Title), () => GetSortName(Book.Title) }, + { nameof(Series), () => GetSortName(Book.SeriesNames) }, + { nameof(Length), () => Book.LengthInMinutes }, + { nameof(MyRating), () => Book.UserDefinedItem.Rating.FirstScore }, + { nameof(PurchaseDate), () => LibraryBook.DateAdded }, + { nameof(ProductRating), () => Book.Rating.FirstScore }, + { nameof(Authors), () => Authors }, + { nameof(Narrators), () => Narrators }, + { nameof(Description), () => Description }, + { nameof(Category), () => Category }, + { nameof(Misc), () => Misc }, + { nameof(DisplayTags), () => DisplayTags }, + { nameof(Liberate), () => Liberate.Item1 } + }; - /// - /// Create getters for all member values by name - /// - Dictionary> CreatePropertyValueDictionary() => new() - { - { nameof(Title), () => getSortName(Book.Title)}, - { nameof(Series),() => getSortName(Book.SeriesNames)}, - { nameof(Length), () => Book.LengthInMinutes}, - { nameof(MyRating), () => Book.UserDefinedItem.Rating.FirstScore}, - { nameof(PurchaseDate), () => LibraryBook.DateAdded}, - { nameof(ProductRating), () => Book.Rating.FirstScore}, - { nameof(Authors), () => Authors}, - { nameof(Narrators), () => Narrators}, - { nameof(Description), () => Description}, - { nameof(Category), () => Category}, - { nameof(Misc), () => Misc}, - { nameof(DisplayTags), () => DisplayTags}, - { nameof(Liberate), () => Liberate.Item1} - }; + // Instantiate comparers for every exposed member object type. + private static readonly Dictionary _memberTypeComparers = new() + { + { typeof(string), new ObjectComparer() }, + { typeof(int), new ObjectComparer() }, + { typeof(float), new ObjectComparer() }, + { typeof(DateTime), new ObjectComparer() }, + { typeof(LiberatedState), new ObjectComparer() }, + }; - private static readonly string[] sortPrefixIgnores = { "the", "a", "an" }; - private static string getSortName(string unformattedName) + public virtual object GetMemberValue(string memberName) => _memberValues[memberName](); + public virtual IComparer GetMemberComparer(Type memberType) => _memberTypeComparers[memberType]; + + private static readonly string[] _sortPrefixIgnores = { "the", "a", "an" }; + private static string GetSortName(string unformattedName) { var sortName = unformattedName .Replace("|", "") @@ -164,98 +145,71 @@ namespace LibationWinForms .ToLowerInvariant() .Trim(); - if (sortPrefixIgnores.Any(prefix => sortName.StartsWith(prefix + " "))) + if (_sortPrefixIgnores.Any(prefix => sortName.StartsWith(prefix + " "))) sortName = sortName.Substring(sortName.IndexOf(" ") + 1).TrimStart(); return sortName; } - #endregion + #endregion #region Static library display functions - + public static (string mouseoverText, Bitmap buttonImage) GetLiberateDisplay(LiberatedState liberatedStatus, PdfState pdfStatus) { - string text; - Bitmap image; - - // get mouseover text + (string libState, string image_lib) = liberatedStatus switch { - var libState = liberatedStatus switch - { - LiberatedState.Liberated => "Liberated", - LiberatedState.PartialDownload => "File has been at least\r\npartially downloaded", - LiberatedState.NotDownloaded => "Book NOT downloaded", - _ => throw new Exception("Unexpected liberation state") - }; + LiberatedState.Liberated => ("Liberated", "green"), + LiberatedState.PartialDownload => ("File has been at least\r\npartially downloaded", "yellow"), + LiberatedState.NotDownloaded => ("Book NOT downloaded", "red"), + _ => throw new Exception("Unexpected liberation state") + }; - var pdfState = pdfStatus switch - { - PdfState.Downloaded => "\r\nPDF downloaded", - PdfState.NotDownloaded => "\r\nPDF NOT downloaded", - PdfState.NoPdf => "", - _ => throw new Exception("Unexpected PDF state") - }; - - text = libState + pdfState; - - if (liberatedStatus == LiberatedState.NotDownloaded || - liberatedStatus == LiberatedState.PartialDownload || - pdfStatus == PdfState.NotDownloaded) - text += "\r\nClick to complete"; - - } - - // get image + (string pdfState, string image_pdf) = pdfStatus switch { - var image_lib - = liberatedStatus == LiberatedState.NotDownloaded ? "red" - : liberatedStatus == LiberatedState.PartialDownload ? "yellow" - : liberatedStatus == LiberatedState.Liberated ? "green" - : throw new Exception("Unexpected liberation state"); - var image_pdf - = pdfStatus == PdfState.NoPdf ? "" - : pdfStatus == PdfState.NotDownloaded ? "_pdf_no" - : pdfStatus == PdfState.Downloaded ? "_pdf_yes" - : throw new Exception("Unexpected PDF state"); + PdfState.Downloaded => ("\r\nPDF downloaded", "_pdf_yes"), + PdfState.NotDownloaded => ("\r\nPDF NOT downloaded", "_pdf_no"), + PdfState.NoPdf => ("", ""), + _ => throw new Exception("Unexpected PDF state") + }; - image = (Bitmap)Properties.Resources.ResourceManager.GetObject($"liberate_{image_lib}{image_pdf}"); - } + var mouseoverText = libState + pdfState; - return (text, image); + if (liberatedStatus == LiberatedState.NotDownloaded || + liberatedStatus == LiberatedState.PartialDownload || + pdfStatus == PdfState.NotDownloaded) + mouseoverText += "\r\nClick to complete"; + + var buttonImage = (Bitmap)Properties.Resources.ResourceManager.GetObject($"liberate_{image_lib}{image_pdf}"); + + return (mouseoverText, buttonImage); } /// /// This information should not change during lifetime, so call only once. /// private static string GetDescriptionDisplay(Book book) - { + { var doc = new HtmlAgilityPack.HtmlDocument(); doc.LoadHtml(book.Description); var noHtml = doc.DocumentNode.InnerText; - return - noHtml.Length < 63? - noHtml : + return + noHtml.Length < 63 ? + noHtml : noHtml.Substring(0, 60) + "..."; } /// /// This information should not change during lifetime, so call only once. + /// Maximum of 5 text rows will fit in 80-pixel row height. /// private static string GetMiscDisplay(LibraryBook libraryBook) { - // max 5 text rows - var details = new List(); - var locale - = string.IsNullOrWhiteSpace(libraryBook.Book.Locale) - ? "[unknown]" - : libraryBook.Book.Locale; - var acct - = string.IsNullOrWhiteSpace(libraryBook.Account) - ? "[unknown]" - : libraryBook.Account; + var locale = ValueOrDefault(libraryBook.Book.Locale, "[unknown]"); + var acct = ValueOrDefault(libraryBook.Account, "[unknown]"); + details.Add($"Account: {locale} - {acct}"); if (libraryBook.Book.HasPdf) @@ -274,11 +228,10 @@ namespace LibationWinForms return string.Join("\r\n", details); } - private static string GetStarString(Rating rating) - => (rating?.FirstScore > 0f) - ? rating?.ToStarString() - : ""; + //Maybe add to Dinah StringExtensions? + private static string ValueOrDefault(string value, string defaultValue) + => string.IsNullOrWhiteSpace(value) ? defaultValue : value; #endregion - } + } } diff --git a/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs b/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs index 63d64257..e57e6897 100644 --- a/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs +++ b/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs @@ -21,7 +21,7 @@ namespace LibationWinForms { (string mouseoverText, Bitmap buttonImage) = GridEntry.GetLiberateDisplay(liberatedState, pdfState); - DrawImage(graphics, buttonImage, cellBounds); + DrawButtonImage(graphics, buttonImage, cellBounds); ToolTipText = mouseoverText; } diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs index 292d6a17..0f826435 100644 --- a/LibationWinForms/ProductsGrid.cs +++ b/LibationWinForms/ProductsGrid.cs @@ -137,9 +137,7 @@ namespace LibationWinForms // .ThenBy(ge => ge.Title) .ToList(); - // // BIND - // gridEntryBindingSource.DataSource = new SortableBindingList2(orderedGridEntries); // FILTER @@ -192,7 +190,7 @@ namespace LibationWinForms #endregion - #region DataGridView Macros + #region DataGridView Macro private int getRowIndex(Func func) => _dataGridView.GetRowIdOfBoundItem(func); private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem(rowIndex); diff --git a/LibationWinForms/SortableBindingList2[T].cs b/LibationWinForms/SortableBindingList2[T].cs index 8cc76d7b..838dda29 100644 --- a/LibationWinForms/SortableBindingList2[T].cs +++ b/LibationWinForms/SortableBindingList2[T].cs @@ -2,29 +2,23 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace LibationWinForms { class SortableBindingList2 : BindingList where T : IObjectMemberComparable { - private ObjectMemberComparer Comparer = new(); - private bool isSorted; private ListSortDirection listSortDirection; private PropertyDescriptor propertyDescriptor; public SortableBindingList2() : base(new List()) { } - public SortableBindingList2(IEnumerable enumeration) : base(new List(enumeration)) { } + private ObjectMemberComparer 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) @@ -34,8 +28,8 @@ namespace LibationWinForms Comparer.PropertyName = property.Name; Comparer.Direction = direction; - //Array.Sort and Liat.Sort are unstable sorts. OrderBy is stable. - var sortedItems = itemsList.OrderBy((ge) => ge, Comparer).ToArray(); + //Array.Sort() and List.Sort() are unstable sorts. OrderBy is stable. + var sortedItems = itemsList.OrderBy((ge) => ge, Comparer).ToList(); itemsList.Clear(); itemsList.AddRange(sortedItems); @@ -55,5 +49,27 @@ namespace LibationWinForms OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); } + //NOTE: Libation does not currently use BindingSource.Find anywhere, + //so this override may be removed (along with SupportsSearchingCore) + protected override int FindCore(PropertyDescriptor property, object key) + { + int count = Count; + + System.Collections.IComparer valueComparer = null; + + for (int i = 0; i < count; ++i) + { + T element = this[i]; + var elemValue = element.GetMemberValue(property.Name); + valueComparer ??= element.GetMemberComparer(elemValue.GetType()); + + if (valueComparer.Compare(elemValue, key) == 0) + { + return i; + } + } + + return -1; + } } }