From c900fe8461f9ce595052801b7c873ff5e82b92dc Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Fri, 30 Dec 2022 17:00:16 -0700 Subject: [PATCH] Add user rating editing to grid --- Source/AppScaffolding/LibationScaffolding.cs | 2 +- Source/ApplicationServices/LibraryCommands.cs | 16 +- .../SearchEngineCommands.cs | 5 + .../AudibleUtilities/AudibleUtilities.csproj | 2 +- Source/DataLayer/EfClasses/UserDefinedItem.cs | 2 +- .../LibationAvalonia/Controls/RatingBox.axaml | 52 +++ .../Controls/RatingBox.axaml.cs | 125 +++++++ .../Dialogs/SearchSyntaxDialog.axaml | 6 +- .../LibationAvalonia/ViewModels/GridEntry.cs | 36 +- .../ViewModels/LibraryBookEntry.cs | 2 +- .../ViewModels/SeriesEntry.cs | 5 +- .../Views/MainWindow.BackupCounts.cs | 29 +- .../Views/MainWindow.VisibleBooks.cs | 6 +- .../Views/ProductsDisplay.axaml | 6 +- Source/LibationSearchEngine/SearchEngine.cs | 30 +- Source/LibationWinForms/GridView/GridEntry.cs | 43 ++- .../GridView/LibraryBookEntry.cs | 2 +- .../GridView/MyRatingGridViewColumn.cs | 73 ++++ .../GridView/ProductsGrid.Designer.cs | 6 +- .../LibationWinForms/GridView/ProductsGrid.cs | 4 + .../GridView/RatingPicker.Designer.cs | 348 ++++++++++++++++++ .../LibationWinForms/GridView/RatingPicker.cs | 142 +++++++ .../GridView/RatingPicker.resx | 60 +++ .../LibationWinForms/GridView/SeriesEntry.cs | 2 +- 24 files changed, 942 insertions(+), 62 deletions(-) create mode 100644 Source/LibationAvalonia/Controls/RatingBox.axaml create mode 100644 Source/LibationAvalonia/Controls/RatingBox.axaml.cs create mode 100644 Source/LibationWinForms/GridView/MyRatingGridViewColumn.cs create mode 100644 Source/LibationWinForms/GridView/RatingPicker.Designer.cs create mode 100644 Source/LibationWinForms/GridView/RatingPicker.cs create mode 100644 Source/LibationWinForms/GridView/RatingPicker.resx diff --git a/Source/AppScaffolding/LibationScaffolding.cs b/Source/AppScaffolding/LibationScaffolding.cs index 924256db..2ab51557 100644 --- a/Source/AppScaffolding/LibationScaffolding.cs +++ b/Source/AppScaffolding/LibationScaffolding.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Reflection; using ApplicationServices; using AudibleUtilities; -using Dinah.Core.Collections.Generic; +using Dinah.Core; using Dinah.Core.IO; using Dinah.Core.Logging; using LibationFileManager; diff --git a/Source/ApplicationServices/LibraryCommands.cs b/Source/ApplicationServices/LibraryCommands.cs index d49c15aa..8d6f5a05 100644 --- a/Source/ApplicationServices/LibraryCommands.cs +++ b/Source/ApplicationServices/LibraryCommands.cs @@ -415,14 +415,16 @@ namespace ApplicationServices this Book book, string tags = null, LiberatedStatus? bookStatus = null, - LiberatedStatus? pdfStatus = null) - => new[] { book }.UpdateUserDefinedItem(tags, bookStatus, pdfStatus); + LiberatedStatus? pdfStatus = null, + Rating rating = null) + => new[] { book }.UpdateUserDefinedItem(tags, bookStatus, pdfStatus, rating); - public static int UpdateUserDefinedItem( + public static int UpdateUserDefinedItem( this IEnumerable books, string tags = null, LiberatedStatus? bookStatus = null, - LiberatedStatus? pdfStatus = null) + LiberatedStatus? pdfStatus = null, + Rating rating = null) => updateUserDefinedItem( books, udi => { @@ -435,6 +437,9 @@ namespace ApplicationServices // method handles null logic udi.SetPdfStatus(pdfStatus); + + if (rating is not null) + udi.Rating = rating; }); public static int UpdateBookStatus(this Book book, LiberatedStatus bookStatus) @@ -487,7 +492,10 @@ namespace ApplicationServices // Attach() NoTracking entities before SaveChanges() foreach (var book in books) + { context.Attach(book.UserDefinedItem).State = Microsoft.EntityFrameworkCore.EntityState.Modified; + context.Attach(book.UserDefinedItem.Rating).State = Microsoft.EntityFrameworkCore.EntityState.Modified; + } var qtyChanges = context.SaveChanges(); if (qtyChanges > 0) diff --git a/Source/ApplicationServices/SearchEngineCommands.cs b/Source/ApplicationServices/SearchEngineCommands.cs index efc21c7f..ad4b3fa8 100644 --- a/Source/ApplicationServices/SearchEngineCommands.cs +++ b/Source/ApplicationServices/SearchEngineCommands.cs @@ -46,6 +46,7 @@ namespace ApplicationServices { UpdateLiberatedStatus(book); UpdateBookTags(book); + UpdateUserRatings(book); } } } @@ -62,6 +63,10 @@ namespace ApplicationServices e.UpdateTags(book.AudibleProductId, book.UserDefinedItem.Tags) ); + internal static void UpdateUserRatings(Book book) => performSafeCommand(e => + e.UpdateUserRatings(book) + ); + private static void performSafeCommand(Action action) { try diff --git a/Source/AudibleUtilities/AudibleUtilities.csproj b/Source/AudibleUtilities/AudibleUtilities.csproj index 963084fd..50f6bdc0 100644 --- a/Source/AudibleUtilities/AudibleUtilities.csproj +++ b/Source/AudibleUtilities/AudibleUtilities.csproj @@ -5,7 +5,7 @@ - + diff --git a/Source/DataLayer/EfClasses/UserDefinedItem.cs b/Source/DataLayer/EfClasses/UserDefinedItem.cs index 91bf236b..177eb79b 100644 --- a/Source/DataLayer/EfClasses/UserDefinedItem.cs +++ b/Source/DataLayer/EfClasses/UserDefinedItem.cs @@ -95,7 +95,7 @@ namespace DataLayer #region Rating // owned: not an optional one-to-one /// The user's individual book rating - public Rating Rating { get; private set; } = new Rating(0, 0, 0); + public Rating Rating { get; set; } = new Rating(0, 0, 0); public void UpdateRating(float overallRating, float performanceRating, float storyRating) => Rating.Update(overallRating, performanceRating, storyRating); diff --git a/Source/LibationAvalonia/Controls/RatingBox.axaml b/Source/LibationAvalonia/Controls/RatingBox.axaml new file mode 100644 index 00000000..4f3f6a03 --- /dev/null +++ b/Source/LibationAvalonia/Controls/RatingBox.axaml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/LibationAvalonia/Controls/RatingBox.axaml.cs b/Source/LibationAvalonia/Controls/RatingBox.axaml.cs new file mode 100644 index 00000000..f517ec5c --- /dev/null +++ b/Source/LibationAvalonia/Controls/RatingBox.axaml.cs @@ -0,0 +1,125 @@ +using Avalonia; +using Avalonia.Controls; +using DataLayer; +using NPOI.POIFS.Storage; +using System.Linq; + +namespace LibationAvalonia.Controls +{ + public partial class RatingBox : UserControl + { + private const string SOLID_STAR = "★"; + private const string HOLLOW_STAR = "☆"; + private static readonly char[] FIVE_STARS = { '★', '★', '★', '★', '★' }; + + public static readonly StyledProperty RatingProperty = + AvaloniaProperty.Register(nameof(Rating)); + + public Rating Rating + { + get { return GetValue(RatingProperty); } + set { SetValue(RatingProperty, value); } + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + if (change.Property.Name == nameof(Rating) && Rating is not null) + { + tblockOverallRating.Text = StarRating((int)Rating.OverallRating); + tblockPerformRating.Text = StarRating((int)Rating.PerformanceRating); + tblockStoryRating.Text = StarRating((int)Rating.StoryRating); + + if (this.IsPointerOver) + RatingBox_PointerEntered(this, null); + else + RatingBox_PointerExited(this, null); + } + base.OnPropertyChanged(change); + } + public RatingBox() + { + InitializeComponent(); + PointerEntered += RatingBox_PointerEntered; + PointerExited += RatingBox_PointerExited; + } + + private void RatingBox_PointerExited(object sender, Avalonia.Input.PointerEventArgs e) + { + tblockOverall.IsVisible = Rating?.OverallRating > 0; + tblockPerform.IsVisible = Rating?.PerformanceRating > 0; + tblockStory.IsVisible = Rating?.StoryRating > 0; + } + + private void RatingBox_PointerEntered(object sender, Avalonia.Input.PointerEventArgs e) + { + tblockOverall.IsVisible = true; + tblockPerform.IsVisible = true; + tblockStory.IsVisible = true; + } + + private static string StarRating(int rating) => new string(FIVE_STARS, 0, rating); + public void Panel_PointerExited(object sender, Avalonia.Input.PointerEventArgs e) + { + var panel = sender as Panel; + var stackPanel = panel.Children.OfType().Single(); + + panel.Children.OfType().Single().IsVisible = true; + stackPanel.IsVisible = false; + + foreach (TextBlock child in stackPanel.Children) + child.Text = HOLLOW_STAR; + } + + public void Panel_PointerEntered(object sender, Avalonia.Input.PointerEventArgs e) + { + var panel = sender as Panel; + + panel.Children.OfType().Single().IsVisible = false; + panel.Children.OfType().Single().IsVisible = true; + } + + public void Star_PointerEntered(object sender, Avalonia.Input.PointerEventArgs e) + { + var thisTbox = sender as TextBlock; + var stackPanel = thisTbox.Parent as StackPanel; + + var star = SOLID_STAR; + + foreach (TextBlock child in stackPanel.Children) + { + child.Text = star; + if (child == thisTbox) star = HOLLOW_STAR; + } + } + + public void Star_Tapped(object sender, Avalonia.Input.TappedEventArgs e) + { + var overall = Rating.OverallRating; + var perform = Rating.PerformanceRating; + var story = Rating.StoryRating; + + var thisTbox = sender as TextBlock; + var stackPanel = thisTbox.Parent as StackPanel; + + int newRating = 0; + foreach (var tbox in stackPanel.Children) + { + newRating++; + if (tbox == thisTbox) break; + } + + var ratingName = ((Panel)stackPanel.Parent).Children.OfType().Single().Name; + + if (ratingName == tblockOverallRating.Name) + overall = newRating; + else if (ratingName == tblockPerformRating.Name) + perform = newRating; + else if (ratingName == tblockStoryRating.Name) + story = newRating; + + if (overall + perform + story == 0f) return; + + Rating = new Rating(overall, perform, story); + } + } +} diff --git a/Source/LibationAvalonia/Dialogs/SearchSyntaxDialog.axaml b/Source/LibationAvalonia/Dialogs/SearchSyntaxDialog.axaml index d3ad7aa9..0e769a26 100644 --- a/Source/LibationAvalonia/Dialogs/SearchSyntaxDialog.axaml +++ b/Source/LibationAvalonia/Dialogs/SearchSyntaxDialog.axaml @@ -2,9 +2,9 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" d:DesignWidth="950" d:DesignHeight="550" - MinWidth="950" MinHeight="550" - MaxWidth="950" MaxHeight="550" + mc:Ignorable="d" d:DesignWidth="950" d:DesignHeight="650" + MinWidth="950" MinHeight="650" + MaxWidth="950" MaxHeight="650" x:Class="LibationAvalonia.Dialogs.SearchSyntaxDialog" Title="Filter Options" WindowStartupLocation="CenterOwner" diff --git a/Source/LibationAvalonia/ViewModels/GridEntry.cs b/Source/LibationAvalonia/ViewModels/GridEntry.cs index 503965be..71007456 100644 --- a/Source/LibationAvalonia/ViewModels/GridEntry.cs +++ b/Source/LibationAvalonia/ViewModels/GridEntry.cs @@ -1,6 +1,8 @@ -using Avalonia.Media; +using ApplicationServices; +using Avalonia.Media; using DataLayer; using Dinah.Core; +using FileLiberator; using LibationFileManager; using ReactiveUI; using System; @@ -8,6 +10,7 @@ using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Threading.Tasks; namespace LibationAvalonia.ViewModels { @@ -42,7 +45,19 @@ namespace LibationAvalonia.ViewModels public string Misc { get; protected set; } public string Description { get; protected set; } public string ProductRating { get; protected set; } - public string MyRating { get; protected set; } + public string MyRatingString => Book.UserDefinedItem.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace(""); + protected Rating _myRating; + public Rating MyRating + { + get => _myRating; + set + { + if (_myRating != value && updateReviewTask?.IsCompleted is not false) + { + updateReviewTask = UpdateRating(value); + } + } + } protected bool? _remove = false; public abstract bool? Remove { get; set; } @@ -56,6 +71,23 @@ namespace LibationAvalonia.ViewModels #endregion + #region User rating + + private Task updateReviewTask; + private async Task UpdateRating(Rating rating) + { + var api = await LibraryBook.GetApiAsync(); + + if (await api.ReviewAsync(Book.AudibleProductId, (int)rating.OverallRating, (int)rating.PerformanceRating, (int)rating.StoryRating)) + { + _myRating = rating; + LibraryBook.Book.UpdateUserDefinedItem(null, null, null, rating); + } + + this.RaisePropertyChanged(nameof(MyRating)); + } + #endregion + #region Sorting public GridEntry() => _memberValues = CreateMemberValueDictionary(); diff --git a/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs b/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs index fce3e875..a172e87b 100644 --- a/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs +++ b/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs @@ -65,7 +65,7 @@ namespace LibationAvalonia.ViewModels Title = Book.Title; Series = Book.SeriesNames(); Length = Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min"; - MyRating = Book.UserDefinedItem.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace(""); + _myRating = Book.UserDefinedItem.Rating; PurchaseDate = libraryBook.DateAdded.ToString("d"); ProductRating = Book.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace(""); Authors = Book.AuthorNames(); diff --git a/Source/LibationAvalonia/ViewModels/SeriesEntry.cs b/Source/LibationAvalonia/ViewModels/SeriesEntry.cs index eb82a99b..67a46db7 100644 --- a/Source/LibationAvalonia/ViewModels/SeriesEntry.cs +++ b/Source/LibationAvalonia/ViewModels/SeriesEntry.cs @@ -1,5 +1,4 @@ -using Avalonia.Media; -using DataLayer; +using DataLayer; using Dinah.Core; using ReactiveUI; using System; @@ -69,7 +68,7 @@ namespace LibationAvalonia.ViewModels Title = Book.Title; Series = Book.SeriesNames(); - MyRating = Book.UserDefinedItem.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace(""); + _myRating = Book.UserDefinedItem.Rating; ProductRating = Book.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace(""); Authors = Book.AuthorNames(); Narrators = Book.NarratorNames(); diff --git a/Source/LibationAvalonia/Views/MainWindow.BackupCounts.cs b/Source/LibationAvalonia/Views/MainWindow.BackupCounts.cs index 50521969..41a5e1a8 100644 --- a/Source/LibationAvalonia/Views/MainWindow.BackupCounts.cs +++ b/Source/LibationAvalonia/Views/MainWindow.BackupCounts.cs @@ -2,43 +2,24 @@ using System; using System.Linq; using Avalonia.Threading; -using Dinah.Core; +using System.Threading.Tasks; namespace LibationAvalonia.Views { - //DONE public partial class MainWindow { - private System.ComponentModel.BackgroundWorker updateCountsBw = new(); + private Task updateCountsTask; private void Configure_BackupCounts() { Load += setBackupCounts; LibraryCommands.LibrarySizeChanged += setBackupCounts; LibraryCommands.BookUserDefinedItemCommitted += setBackupCounts; - - updateCountsBw.DoWork += UpdateCountsBw_DoWork; - updateCountsBw.RunWorkerCompleted += updateBottomNumbersAsync; } - private bool runBackupCountsAgain; + private void setBackupCounts(object _, object __) { - runBackupCountsAgain = true; - - if (!updateCountsBw.IsBusy) - updateCountsBw.RunWorkerAsync(); - } - private void UpdateCountsBw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) - { - while (runBackupCountsAgain) - { - runBackupCountsAgain = false; - e.Result = LibraryCommands.GetCounts(); - } - } - - private void updateBottomNumbersAsync(object _, System.ComponentModel.RunWorkerCompletedEventArgs e) - { - _viewModel.LibraryStats = e.Result as LibraryCommands.LibraryStats; + if (updateCountsTask?.IsCompleted is not false) + updateCountsTask = Dispatcher.UIThread.InvokeAsync(() => _viewModel.LibraryStats = LibraryCommands.GetCounts()); } } } diff --git a/Source/LibationAvalonia/Views/MainWindow.VisibleBooks.cs b/Source/LibationAvalonia/Views/MainWindow.VisibleBooks.cs index 3abf0ba4..d3a2402f 100644 --- a/Source/LibationAvalonia/Views/MainWindow.VisibleBooks.cs +++ b/Source/LibationAvalonia/Views/MainWindow.VisibleBooks.cs @@ -18,7 +18,7 @@ namespace LibationAvalonia.Views } private async void setLiberatedVisibleMenuItemAsync(object _, object __) - => await Task.Run(setLiberatedVisibleMenuItem); + => await Dispatcher.UIThread.InvokeAsync(setLiberatedVisibleMenuItem); public void liberateVisible(object sender, Avalonia.Interactivity.RoutedEventArgs args) { @@ -114,7 +114,7 @@ namespace LibationAvalonia.Views return; var bulkSetStatus = new BulkSetDownloadStatus(_viewModel.ProductsDisplay.GetVisibleBookEntries(), dialog.SetDownloaded, dialog.SetNotDownloaded); - var count = await Task.Run(() => bulkSetStatus.Discover()); + var count = await Task.Run(bulkSetStatus.Discover); if (count == 0) return; @@ -154,7 +154,7 @@ namespace LibationAvalonia.Views { _viewModel.VisibleCount = qty; - await Task.Run(setLiberatedVisibleMenuItem); + await Dispatcher.UIThread.InvokeAsync(setLiberatedVisibleMenuItem); } void setLiberatedVisibleMenuItem() => _viewModel.VisibleNotLiberated diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml b/Source/LibationAvalonia/Views/ProductsDisplay.axaml index a4a7fbae..7175106d 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml @@ -124,7 +124,7 @@ - + @@ -160,11 +160,11 @@ - + - + diff --git a/Source/LibationSearchEngine/SearchEngine.cs b/Source/LibationSearchEngine/SearchEngine.cs index e3e733d2..c4537d1f 100644 --- a/Source/LibationSearchEngine/SearchEngine.cs +++ b/Source/LibationSearchEngine/SearchEngine.cs @@ -89,9 +89,9 @@ namespace LibationSearchEngine ["Hours"] = lb => (lb.Book.LengthInMinutes / 60).ToLuceneString(), ["ProductRating"] = lb => lb.Book.Rating.OverallRating.ToLuceneString(), - ["Rating"] = lb => lb.Book.Rating.OverallRating.ToLuceneString(), - ["UserRating"] = lb => lb.Book.UserDefinedItem.Rating.OverallRating.ToLuceneString(), - ["MyRating"] = lb => lb.Book.UserDefinedItem.Rating.OverallRating.ToLuceneString() + ["Rating"] = lb => lb.Book.Rating.OverallRating.ToLuceneString(), + ["UserRating"] = lb => userRating(lb.Book), + ["MyRating"] = lb => userRating(lb.Book) } ); @@ -136,8 +136,8 @@ namespace LibationSearchEngine var narrators = lb.Book.Narrators.Select(a => a.Name).ToArray(); return authors.Intersect(narrators).Any(); } - - private static bool isLiberated(Book book) => book.UserDefinedItem.BookStatus == LiberatedStatus.Liberated; + private static string userRating(Book book) => book.UserDefinedItem.Rating.OverallRating.ToLuceneString(); + private static bool isLiberated(Book book) => book.UserDefinedItem.BookStatus == LiberatedStatus.Liberated; private static bool liberatedError(Book book) => book.UserDefinedItem.BookStatus == LiberatedStatus.Error; // use these common fields in the "all" default search field @@ -289,7 +289,25 @@ namespace LibationSearchEngine d.AddBool("LiberatedError", v2); }); - private static void updateDocument(string productId, Action action) + public void UpdateUserRatings(Book book) + => updateDocument( + book.AudibleProductId, + d => + { + // + // TODO: better synonym handling. This is too easy to mess up + // + + // fields are key value pairs. MULTIPLE FIELDS CAN POTENTIALLY HAVE THE SAME KEY. + // ie: must remove old before adding new else will create unwanted duplicates. + var v1 = userRating(book); + d.RemoveField("UserRating"); + d.AddNotAnalyzed("UserRating", v1); + d.RemoveField("MyRating"); + d.AddNotAnalyzed("MyRating", v1); + }); + + private static void updateDocument(string productId, Action action) { var productTerm = new Term(_ID_, productId); diff --git a/Source/LibationWinForms/GridView/GridEntry.cs b/Source/LibationWinForms/GridView/GridEntry.cs index b8cb97d2..c779d597 100644 --- a/Source/LibationWinForms/GridView/GridEntry.cs +++ b/Source/LibationWinForms/GridView/GridEntry.cs @@ -1,7 +1,9 @@ -using DataLayer; +using ApplicationServices; +using DataLayer; using Dinah.Core; using Dinah.Core.DataBinding; using Dinah.Core.WindowsDesktop.Drawing; +using FileLiberator; using LibationFileManager; using System; using System.Collections; @@ -9,6 +11,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; +using System.Threading.Tasks; namespace LibationWinForms.GridView { @@ -57,14 +60,44 @@ namespace LibationWinForms.GridView public string Misc { get; protected set; } public string Description { get; protected set; } public string ProductRating { get; protected set; } - public string MyRating { get; protected set; } + protected Rating _myRating; + public Rating MyRating + { + get => _myRating; + set + { + if (_myRating != value + && (value.OverallRating + value.PerformanceRating + value.StoryRating) > 0 + && updateReviewTask?.IsCompleted is not false) + { + updateReviewTask = UpdateRating(value); + } + } + } public abstract string DisplayTags { get; } - #endregion + #endregion - #region Sorting + #region User rating - public GridEntry() => _memberValues = CreateMemberValueDictionary(); + private Task updateReviewTask; + private async Task UpdateRating(Rating rating) + { + var api = await LibraryBook.GetApiAsync(); + + if (await api.ReviewAsync(Book.AudibleProductId, (int)rating.OverallRating, (int)rating.PerformanceRating, (int)rating.StoryRating)) + { + _myRating = rating; + LibraryBook.Book.UpdateUserDefinedItem(null, null, null, rating); + } + + this.NotifyPropertyChanged(nameof(MyRating)); + } + #endregion + + #region Sorting + + public GridEntry() => _memberValues = CreateMemberValueDictionary(); // These methods are implementation of Dinah.Core.DataBinding.IMemberComparable // Used by GridEntryBindingList for all sorting diff --git a/Source/LibationWinForms/GridView/LibraryBookEntry.cs b/Source/LibationWinForms/GridView/LibraryBookEntry.cs index 531a941d..315232b8 100644 --- a/Source/LibationWinForms/GridView/LibraryBookEntry.cs +++ b/Source/LibationWinForms/GridView/LibraryBookEntry.cs @@ -80,7 +80,7 @@ namespace LibationWinForms.GridView Title = Book.Title; Series = Book.SeriesNames(); Length = Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min"; - MyRating = Book.UserDefinedItem.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace(""); + MyRating = Book.UserDefinedItem.Rating; PurchaseDate = libraryBook.DateAdded.ToString("d"); ProductRating = Book.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace(""); Authors = Book.AuthorNames(); diff --git a/Source/LibationWinForms/GridView/MyRatingGridViewColumn.cs b/Source/LibationWinForms/GridView/MyRatingGridViewColumn.cs new file mode 100644 index 00000000..fdab268e --- /dev/null +++ b/Source/LibationWinForms/GridView/MyRatingGridViewColumn.cs @@ -0,0 +1,73 @@ +using DataLayer; +using System; +using System.ComponentModel; +using System.Linq; +using System.Windows.Forms; + +namespace LibationWinForms.GridView +{ + public class MyRatingGridViewColumn : DataGridViewColumn + { + public MyRatingGridViewColumn() : base(new MyRatingGridViewCell()) { } + + public override DataGridViewCell CellTemplate + { + get => base.CellTemplate; + set + { + if (value is not MyRatingGridViewCell) + throw new InvalidCastException("Must be a MyRatingGridViewCell"); + + base.CellTemplate = value; + } + } + } + + internal class MyRatingGridViewCell : DataGridViewTextBoxCell + { + public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle) + { + base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle); + + var ctl = DataGridView.EditingControl as RatingPicker; + + ctl.Rating = + Value is Rating rating + ? rating + : (Rating)DefaultNewRowValue; + } + + public override object ParseFormattedValue(object formattedValue, DataGridViewCellStyle cellStyle, TypeConverter formattedValueTypeConverter, TypeConverter valueTypeConverter) + { + const char SOLID_STAR = '★'; + if (formattedValue is string s) + { + int overall = 0, performance = 0, story = 0; + + foreach (var line in s.Split('\n')) + { + if (line.Contains("Overall")) + overall = line.Count(c => c == SOLID_STAR); + else if (line.Contains("Perform")) + performance = line.Count(c => c == SOLID_STAR); + else if (line.Contains("Story")) + story = line.Count(c => c == SOLID_STAR); + } + + return new Rating(overall, performance, story); + } + else + return DefaultNewRowValue; + } + + + protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context) + => value is Rating rating + ? rating.ToStarString() + : base.GetFormattedValue(value, rowIndex, ref cellStyle, valueTypeConverter, formattedValueTypeConverter, context); + + public override Type EditType => typeof(RatingPicker); + public override object DefaultNewRowValue => new Rating(0, 0, 0); + public override Type ValueType => typeof(Rating); + } +} diff --git a/Source/LibationWinForms/GridView/ProductsGrid.Designer.cs b/Source/LibationWinForms/GridView/ProductsGrid.Designer.cs index 6da37a16..8aa2b188 100644 --- a/Source/LibationWinForms/GridView/ProductsGrid.Designer.cs +++ b/Source/LibationWinForms/GridView/ProductsGrid.Designer.cs @@ -43,7 +43,7 @@ this.categoryGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.productRatingGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.purchaseDateGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.myRatingGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.myRatingGVColumn = new MyRatingGridViewColumn(); this.miscGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.tagAndDetailsGVColumn = new LibationWinForms.GridView.EditTagsDataGridViewImageButtonColumn(); this.showHideColumnsContextMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components); @@ -204,7 +204,7 @@ this.myRatingGVColumn.DataPropertyName = "MyRating"; this.myRatingGVColumn.HeaderText = "My Rating"; this.myRatingGVColumn.Name = "myRatingGVColumn"; - this.myRatingGVColumn.ReadOnly = true; + this.myRatingGVColumn.ReadOnly = false; this.myRatingGVColumn.Width = 108; // // miscGVColumn @@ -265,7 +265,7 @@ private System.Windows.Forms.DataGridViewTextBoxColumn categoryGVColumn; private System.Windows.Forms.DataGridViewTextBoxColumn productRatingGVColumn; private System.Windows.Forms.DataGridViewTextBoxColumn purchaseDateGVColumn; - private System.Windows.Forms.DataGridViewTextBoxColumn myRatingGVColumn; + private MyRatingGridViewColumn myRatingGVColumn; private System.Windows.Forms.DataGridViewTextBoxColumn miscGVColumn; private EditTagsDataGridViewImageButtonColumn tagAndDetailsGVColumn; } diff --git a/Source/LibationWinForms/GridView/ProductsGrid.cs b/Source/LibationWinForms/GridView/ProductsGrid.cs index 0ac93377..90093f27 100644 --- a/Source/LibationWinForms/GridView/ProductsGrid.cs +++ b/Source/LibationWinForms/GridView/ProductsGrid.cs @@ -40,6 +40,8 @@ namespace LibationWinForms.GridView EnableDoubleBuffering(); gridEntryDataGridView.Scroll += (_, s) => Scroll?.Invoke(this, s); removeGVColumn.Frozen = false; + + gridEntryDataGridView.EditMode = DataGridViewEditMode.EditOnEnter; } private void EnableDoubleBuffering() @@ -49,6 +51,8 @@ namespace LibationWinForms.GridView propertyInfo.SetValue(gridEntryDataGridView, true, null); } + + #region Button controls private void DataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e) { diff --git a/Source/LibationWinForms/GridView/RatingPicker.Designer.cs b/Source/LibationWinForms/GridView/RatingPicker.Designer.cs new file mode 100644 index 00000000..d6705129 --- /dev/null +++ b/Source/LibationWinForms/GridView/RatingPicker.Designer.cs @@ -0,0 +1,348 @@ +namespace LibationWinForms.GridView +{ + partial class RatingPicker + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.panel1 = new System.Windows.Forms.Panel(); + this.label6 = new System.Windows.Forms.Label(); + this.label7 = new System.Windows.Forms.Label(); + this.panel2 = new System.Windows.Forms.Panel(); + this.label8 = new System.Windows.Forms.Label(); + this.label9 = new System.Windows.Forms.Label(); + this.label10 = new System.Windows.Forms.Label(); + this.label11 = new System.Windows.Forms.Label(); + this.label12 = new System.Windows.Forms.Label(); + this.label13 = new System.Windows.Forms.Label(); + this.panel3 = new System.Windows.Forms.Panel(); + this.label14 = new System.Windows.Forms.Label(); + this.label15 = new System.Windows.Forms.Label(); + this.label16 = new System.Windows.Forms.Label(); + this.label17 = new System.Windows.Forms.Label(); + this.label18 = new System.Windows.Forms.Label(); + this.panel1.SuspendLayout(); + this.panel2.SuspendLayout(); + this.panel3.SuspendLayout(); + this.SuspendLayout(); + // + // label1 + // + this.label1.Location = new System.Drawing.Point(0, 0); + this.label1.Margin = new System.Windows.Forms.Padding(0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(12, 15); + this.label1.TabIndex = 0; + this.label1.Text = "☆"; + this.label1.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick); + this.label1.MouseEnter += new System.EventHandler(this.Star_MouseEnter); + this.label1.MouseLeave += new System.EventHandler(this.Star_MouseLeave); + // + // label2 + // + this.label2.Location = new System.Drawing.Point(12, 0); + this.label2.Margin = new System.Windows.Forms.Padding(0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(12, 15); + this.label2.TabIndex = 1; + this.label2.Text = "☆"; + this.label2.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick); + this.label2.MouseEnter += new System.EventHandler(this.Star_MouseEnter); + this.label2.MouseLeave += new System.EventHandler(this.Star_MouseLeave); + // + // label3 + // + this.label3.Location = new System.Drawing.Point(24, 0); + this.label3.Margin = new System.Windows.Forms.Padding(0); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(12, 15); + this.label3.TabIndex = 3; + this.label3.Text = "☆"; + this.label3.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick); + this.label3.MouseEnter += new System.EventHandler(this.Star_MouseEnter); + this.label3.MouseLeave += new System.EventHandler(this.Star_MouseLeave); + // + // label4 + // + this.label4.Location = new System.Drawing.Point(36, 0); + this.label4.Margin = new System.Windows.Forms.Padding(0); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(12, 15); + this.label4.TabIndex = 2; + this.label4.Text = "☆"; + this.label4.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick); + this.label4.MouseEnter += new System.EventHandler(this.Star_MouseEnter); + this.label4.MouseLeave += new System.EventHandler(this.Star_MouseLeave); + // + // label5 + // + this.label5.Location = new System.Drawing.Point(48, 0); + this.label5.Margin = new System.Windows.Forms.Padding(0); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(12, 15); + this.label5.TabIndex = 4; + this.label5.Text = "☆"; + this.label5.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick); + this.label5.MouseEnter += new System.EventHandler(this.Star_MouseEnter); + this.label5.MouseLeave += new System.EventHandler(this.Star_MouseLeave); + // + // panel1 + // + this.panel1.Controls.Add(this.label1); + this.panel1.Controls.Add(this.label2); + this.panel1.Controls.Add(this.label3); + this.panel1.Controls.Add(this.label4); + this.panel1.Controls.Add(this.label5); + this.panel1.Location = new System.Drawing.Point(45, 13); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(60, 15); + this.panel1.TabIndex = 5; + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(0, 13); + this.label6.Margin = new System.Windows.Forms.Padding(0); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(47, 15); + this.label6.TabIndex = 6; + this.label6.Text = "Overall:"; + // + // label7 + // + this.label7.AutoSize = true; + this.label7.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.label7.Location = new System.Drawing.Point(0, 32); + this.label7.Margin = new System.Windows.Forms.Padding(0); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(46, 15); + this.label7.TabIndex = 8; + this.label7.Text = "Perfrm:"; + // + // panel2 + // + this.panel2.Controls.Add(this.label8); + this.panel2.Controls.Add(this.label9); + this.panel2.Controls.Add(this.label10); + this.panel2.Controls.Add(this.label11); + this.panel2.Controls.Add(this.label12); + this.panel2.Location = new System.Drawing.Point(45, 32); + this.panel2.Name = "panel2"; + this.panel2.Size = new System.Drawing.Size(60, 15); + this.panel2.TabIndex = 7; + // + // label8 + // + this.label8.Location = new System.Drawing.Point(0, 0); + this.label8.Margin = new System.Windows.Forms.Padding(0); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(12, 15); + this.label8.TabIndex = 0; + this.label8.Text = "☆"; + this.label8.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick); + this.label8.MouseEnter += new System.EventHandler(this.Star_MouseEnter); + this.label8.MouseLeave += new System.EventHandler(this.Star_MouseLeave); + // + // label9 + // + this.label9.Location = new System.Drawing.Point(12, 0); + this.label9.Margin = new System.Windows.Forms.Padding(0); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(12, 15); + this.label9.TabIndex = 1; + this.label9.Text = "☆"; + this.label9.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick); + this.label9.MouseEnter += new System.EventHandler(this.Star_MouseEnter); + this.label9.MouseLeave += new System.EventHandler(this.Star_MouseLeave); + // + // label10 + // + this.label10.Location = new System.Drawing.Point(24, 0); + this.label10.Margin = new System.Windows.Forms.Padding(0); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(12, 15); + this.label10.TabIndex = 3; + this.label10.Text = "☆"; + this.label10.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick); + this.label10.MouseEnter += new System.EventHandler(this.Star_MouseEnter); + this.label10.MouseLeave += new System.EventHandler(this.Star_MouseLeave); + // + // label11 + // + this.label11.Location = new System.Drawing.Point(36, 0); + this.label11.Margin = new System.Windows.Forms.Padding(0); + this.label11.Name = "label11"; + this.label11.Size = new System.Drawing.Size(12, 15); + this.label11.TabIndex = 2; + this.label11.Text = "☆"; + this.label11.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick); + this.label11.MouseEnter += new System.EventHandler(this.Star_MouseEnter); + this.label11.MouseLeave += new System.EventHandler(this.Star_MouseLeave); + // + // label12 + // + this.label12.Location = new System.Drawing.Point(48, 0); + this.label12.Margin = new System.Windows.Forms.Padding(0); + this.label12.Name = "label12"; + this.label12.Size = new System.Drawing.Size(12, 15); + this.label12.TabIndex = 4; + this.label12.Text = "☆"; + this.label12.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick); + this.label12.MouseEnter += new System.EventHandler(this.Star_MouseEnter); + this.label12.MouseLeave += new System.EventHandler(this.Star_MouseLeave); + // + // label13 + // + this.label13.AutoSize = true; + this.label13.Location = new System.Drawing.Point(0, 51); + this.label13.Margin = new System.Windows.Forms.Padding(0); + this.label13.Name = "label13"; + this.label13.Size = new System.Drawing.Size(37, 15); + this.label13.TabIndex = 10; + this.label13.Text = "Story:"; + // + // panel3 + // + this.panel3.Controls.Add(this.label14); + this.panel3.Controls.Add(this.label15); + this.panel3.Controls.Add(this.label16); + this.panel3.Controls.Add(this.label17); + this.panel3.Controls.Add(this.label18); + this.panel3.Location = new System.Drawing.Point(45, 51); + this.panel3.Name = "panel3"; + this.panel3.Size = new System.Drawing.Size(60, 15); + this.panel3.TabIndex = 9; + // + // label14 + // + this.label14.Location = new System.Drawing.Point(0, 0); + this.label14.Margin = new System.Windows.Forms.Padding(0); + this.label14.Name = "label14"; + this.label14.Size = new System.Drawing.Size(12, 15); + this.label14.TabIndex = 0; + this.label14.Text = "☆"; + this.label14.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick); + this.label14.MouseEnter += new System.EventHandler(this.Star_MouseEnter); + this.label14.MouseLeave += new System.EventHandler(this.Star_MouseLeave); + // + // label15 + // + this.label15.Location = new System.Drawing.Point(12, 0); + this.label15.Margin = new System.Windows.Forms.Padding(0); + this.label15.Name = "label15"; + this.label15.Size = new System.Drawing.Size(12, 15); + this.label15.TabIndex = 1; + this.label15.Text = "☆"; + this.label15.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick); + this.label15.MouseEnter += new System.EventHandler(this.Star_MouseEnter); + this.label15.MouseLeave += new System.EventHandler(this.Star_MouseLeave); + // + // label16 + // + this.label16.Location = new System.Drawing.Point(24, 0); + this.label16.Margin = new System.Windows.Forms.Padding(0); + this.label16.Name = "label16"; + this.label16.Size = new System.Drawing.Size(12, 15); + this.label16.TabIndex = 3; + this.label16.Text = "☆"; + this.label16.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick); + this.label16.MouseEnter += new System.EventHandler(this.Star_MouseEnter); + this.label16.MouseLeave += new System.EventHandler(this.Star_MouseLeave); + // + // label17 + // + this.label17.Location = new System.Drawing.Point(36, 0); + this.label17.Margin = new System.Windows.Forms.Padding(0); + this.label17.Name = "label17"; + this.label17.Size = new System.Drawing.Size(12, 15); + this.label17.TabIndex = 2; + this.label17.Text = "☆"; + this.label17.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick); + this.label17.MouseEnter += new System.EventHandler(this.Star_MouseEnter); + this.label17.MouseLeave += new System.EventHandler(this.Star_MouseLeave); + // + // label18 + // + this.label18.Location = new System.Drawing.Point(48, 0); + this.label18.Margin = new System.Windows.Forms.Padding(0); + this.label18.Name = "label18"; + this.label18.Size = new System.Drawing.Size(12, 15); + this.label18.TabIndex = 4; + this.label18.Text = "☆"; + this.label18.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick); + this.label18.MouseEnter += new System.EventHandler(this.Star_MouseEnter); + this.label18.MouseLeave += new System.EventHandler(this.Star_MouseLeave); + // + // RatingPicker + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.label13); + this.Controls.Add(this.panel3); + this.Controls.Add(this.label7); + this.Controls.Add(this.panel2); + this.Controls.Add(this.label6); + this.Controls.Add(this.panel1); + this.Name = "RatingPicker"; + this.Size = new System.Drawing.Size(108, 80); + this.panel1.ResumeLayout(false); + this.panel2.ResumeLayout(false); + this.panel3.ResumeLayout(false); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.Panel panel2; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.Label label10; + private System.Windows.Forms.Label label11; + private System.Windows.Forms.Label label12; + private System.Windows.Forms.Label label13; + private System.Windows.Forms.Panel panel3; + private System.Windows.Forms.Label label14; + private System.Windows.Forms.Label label15; + private System.Windows.Forms.Label label16; + private System.Windows.Forms.Label label17; + private System.Windows.Forms.Label label18; + } +} diff --git a/Source/LibationWinForms/GridView/RatingPicker.cs b/Source/LibationWinForms/GridView/RatingPicker.cs new file mode 100644 index 00000000..94478dec --- /dev/null +++ b/Source/LibationWinForms/GridView/RatingPicker.cs @@ -0,0 +1,142 @@ +using DataLayer; +using Mpeg4Lib.Boxes; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace LibationWinForms.GridView +{ + public partial class RatingPicker : UserControl, IDataGridViewEditingControl + { + private const string SOLID_STAR = "★"; + private const string HOLLOW_STAR = "☆"; + + private Rating _rating; + public Rating Rating { + get => _rating; + set + { + _rating = value; + int rating = 0; + foreach (Label star in panel1.Controls) + star.Tag = star.Text = _rating.OverallRating > rating++ ? SOLID_STAR : HOLLOW_STAR; + + rating = 0; + foreach (Label star in panel2.Controls) + star.Tag = star.Text = _rating.PerformanceRating > rating++ ? SOLID_STAR : HOLLOW_STAR; + + rating = 0; + foreach (Label star in panel3.Controls) + star.Tag = star.Text = _rating.StoryRating > rating++ ? SOLID_STAR : HOLLOW_STAR; + } + } + public RatingPicker() + { + InitializeComponent(); + } + + private void Star_MouseEnter(object sender, EventArgs e) + { + var thisTbox = sender as Label; + var stackPanel = thisTbox.Parent as Panel; + var star = SOLID_STAR; + + foreach (Label child in stackPanel.Controls) + { + child.Text = star; + if (child == thisTbox) star = HOLLOW_STAR; + } + } + + private void Star_MouseLeave(object sender, EventArgs e) + { + var thisTbox = sender as Label; + var panel = thisTbox.Parent as Panel; + + //Artifically shrink rectangle to guarantee mouse is outside when exiting from the left (negative X) + var clientPt = panel.PointToClient(MousePosition); + var rect = new Rectangle(0, 0, panel.ClientRectangle.Width - 2, panel.ClientRectangle.Height); + if (!rect.Contains(clientPt.X - 2, clientPt.Y)) + { + //Restore defaults + foreach (Label child in panel.Controls) + child.Text = (string)child.Tag; + } + } + + private void Star_MouseClick(object sender, MouseEventArgs e) + { + var overall = Rating.OverallRating; + var perform = Rating.PerformanceRating; + var story = Rating.StoryRating; + + var thisTbox = sender as Label; + var panel = thisTbox.Parent as Panel; + + int newRating = 0; + foreach (var child in panel.Controls) + { + newRating++; + if (child == thisTbox) break; + } + + if (panel == panel1) + overall = newRating; + else if (panel == panel2) + perform = newRating; + else if (panel == panel3) + story = newRating; + + if (overall + perform + story == 0f) return; + + Rating = new Rating(overall, perform, story); + EditingControlValueChanged = true; + EditingControlDataGridView.NotifyCurrentCellDirty(true); + } + + DataGridView dataGridView; + private bool valueChanged = false; + int rowIndex; + + #region IDataGridViewEditingControl + public DataGridView EditingControlDataGridView { get => dataGridView; set => dataGridView = value; } + public object EditingControlFormattedValue { get => Rating.ToStarString(); set { } } + public int EditingControlRowIndex { get => rowIndex; set => rowIndex = value; } + public bool EditingControlValueChanged { get => valueChanged; set => valueChanged = value; } + + public Cursor EditingPanelCursor => base.Cursor; + + public bool RepositionEditingControlOnValueChange => false; + + public void ApplyCellStyleToEditingControl(DataGridViewCellStyle dataGridViewCellStyle) + { + this.Font = dataGridViewCellStyle.Font; + this.ForeColor = dataGridViewCellStyle.ForeColor; + this.BackColor = dataGridViewCellStyle.BackColor; + } + + public bool EditingControlWantsInputKey(Keys keyData, bool dataGridViewWantsInputKey) + { + switch (keyData & Keys.KeyCode) + { + case Keys.Enter: + case Keys.Escape: + return true; + default: + return !dataGridViewWantsInputKey; + } + } + + public object GetEditingControlFormattedValue(DataGridViewDataErrorContexts context) => EditingControlFormattedValue; + + public void PrepareEditingControlForEdit(bool selectAll) { } + + #endregion + } +} diff --git a/Source/LibationWinForms/GridView/RatingPicker.resx b/Source/LibationWinForms/GridView/RatingPicker.resx new file mode 100644 index 00000000..f298a7be --- /dev/null +++ b/Source/LibationWinForms/GridView/RatingPicker.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Source/LibationWinForms/GridView/SeriesEntry.cs b/Source/LibationWinForms/GridView/SeriesEntry.cs index 1ebe5e4f..53191808 100644 --- a/Source/LibationWinForms/GridView/SeriesEntry.cs +++ b/Source/LibationWinForms/GridView/SeriesEntry.cs @@ -89,7 +89,7 @@ namespace LibationWinForms.GridView Title = Book.Title; Series = Book.SeriesNames(); - MyRating = Book.UserDefinedItem.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace(""); + MyRating = Book.UserDefinedItem.Rating; PurchaseDate = Children.Min(c => c.LibraryBook.DateAdded).ToString("d"); ProductRating = Book.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace(""); Authors = Book.AuthorNames();