Merge pull request #432 from Mbucari/master

Add ability for users to edit book ratings from the main grid
This commit is contained in:
rmcrackan 2023-01-01 22:34:06 -05:00 committed by GitHub
commit f4189bf409
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 1456 additions and 638 deletions

View File

@ -13,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AAXClean.Codecs" Version="0.2.14" />
<PackageReference Include="AAXClean.Codecs" Version="0.2.15" />
</ItemGroup>
<ItemGroup>

View File

@ -32,6 +32,9 @@ namespace AaxDecrypter
AaxFile.AppleTags.Album = AaxFile.AppleTags.Album?.Replace(" (Unabridged)", "");
}
if (DownloadOptions.FixupFile)
AaxFile.AppleTags.AppleListBox.EditOrAddTag("TCOM", AaxFile.AppleTags.Narrator);
//Finishing configuring lame encoder.
if (DownloadOptions.OutputFormat == OutputFormat.Mp3)
MpegUtil.ConfigureLameOptions(

View File

@ -81,7 +81,6 @@ namespace AppScaffolding
//
Migrations.migrate_to_v6_6_9(config);
Migrations.migrate_from_7_10_1(config);
}
public static void PopulateMissingConfigValues(Configuration config)
@ -457,74 +456,5 @@ namespace AppScaffolding
UNSAFE_MigrationHelper.Settings_AddUniqueToArray("Serilog.Enrich", "WithExceptionDetails");
}
}
public static void migrate_from_7_10_1(Configuration config)
{
var lastMigrationThrew = config.GetNonString<bool>($"{nameof(migrate_from_7_10_1)}_ThrewError");
if (lastMigrationThrew) return;
try
{
//https://github.com/rmcrackan/Libation/issues/270#issuecomment-1152863629
//This migration helps fix databases contaminated with the 7.10.1 hack workaround
//and those with improperly identified or missing series. This does not solve cases
//where individual episodes are in the db with a valid series link, but said series'
//parents have not been imported into the database. For those cases, Libation will
//attempt fixup by retrieving parents from the catalog endpoint
using var context = DbContexts.GetContext();
//This migration removes books and series with SERIES_ prefix that were created
//as a hack workaround in 7.10.1. Said workaround was removed in 7.10.2
string removeHackSeries = "delete " +
"from series " +
"where AudibleSeriesId like 'SERIES%'";
string removeHackBooks = "delete " +
"from books " +
"where AudibleProductId like 'SERIES%'";
//Detect series parents that were added to the database as books with ContentType.Episode,
//and change them to ContentType.Parent
string updateContentType =
"UPDATE books " +
"SET contenttype = 4 " +
"WHERE audibleproductid IN (SELECT books.audibleproductid " +
"FROM books " +
"INNER JOIN series " +
"ON ( books.audibleproductid = " +
"series.audibleseriesid) " +
"WHERE books.contenttype = 2)";
//Then detect series parents that were added to the database as books with ContentType.Parent
//but are missing a series link, and add the link (don't know how this happened)
string addMissingSeriesLink =
"INSERT INTO seriesbook " +
"SELECT series.seriesid, " +
"books.bookid, " +
"'- 1' " +
"FROM books " +
"LEFT OUTER JOIN seriesbook " +
"ON books.bookid = seriesbook.bookid " +
"INNER JOIN series " +
"ON books.audibleproductid = series.audibleseriesid " +
"WHERE books.contenttype = 4 " +
"AND seriesbook.seriesid IS NULL";
context.Database.ExecuteSqlRaw(removeHackSeries);
context.Database.ExecuteSqlRaw(removeHackBooks);
context.Database.ExecuteSqlRaw(updateContentType);
context.Database.ExecuteSqlRaw(addMissingSeriesLink);
LibraryCommands.SaveContext(context);
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "An error occurred while running database migrations in {0}", nameof(migrate_from_7_10_1));
config.SetObject($"{nameof(migrate_from_7_10_1)}_ThrewError", true);
}
}
}
}

View File

@ -126,22 +126,6 @@ namespace ApplicationServices
if (totalCount == 0)
return default;
Log.Logger.Information("Begin scan for orphaned episode parents");
var newParents = await findAndAddMissingParents(accounts);
Log.Logger.Information($"Orphan episode scan complete. New parents count {newParents}");
if (newParents >= 0)
{
//If any episodes are still orphaned, their series have been
//removed from the catalog and we'll never be able to find them.
//only do this if findAndAddMissingParents returned >= 0. If it
//returned < 0, an error happened and there's still a chance that
//a future successful run will find missing parents.
removedOrphanedEpisodes();
}
Log.Logger.Information("Begin long-running import");
logTime($"pre {nameof(importIntoDbAsync)}");
var newCount = await importIntoDbAsync(importItems);
@ -235,84 +219,6 @@ namespace ApplicationServices
return newCount;
}
static void removedOrphanedEpisodes()
{
using var context = DbContexts.GetContext();
try
{
var orphanedEpisodes =
context
.GetLibrary_Flat_NoTracking(includeParents: true)
.FindOrphanedEpisodes();
context.LibraryBooks.RemoveRange(orphanedEpisodes);
context.Books.RemoveRange(orphanedEpisodes.Select(lb => lb.Book));
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "An error occurred while trying to remove orphaned episodes from the database");
}
}
static async Task<int> findAndAddMissingParents(Account[] accounts)
{
using var context = DbContexts.GetContext();
var library = context.GetLibrary_Flat_NoTracking(includeParents: true);
try
{
var orphanedEpisodes = library.FindOrphanedEpisodes().ToList();
if (!orphanedEpisodes.Any())
return -1;
var orphanedSeries =
orphanedEpisodes
.SelectMany(lb => lb.Book.SeriesLink)
.DistinctBy(s => s.Series.AudibleSeriesId)
.ToList();
// The Catalog endpoint does not require authentication.
var api = new ApiUnauthenticated(accounts[0].Locale);
var seriesParents = orphanedSeries.Select(o => o.Series.AudibleSeriesId).ToList();
var items = await api.GetCatalogProductsAsync(seriesParents, CatalogOptions.ResponseGroupOptions.ALL_OPTIONS);
List<ImportItem> newParentsImportItems = new();
foreach (var sp in orphanedSeries)
{
var seriesItem = items.First(i => i.Asin == sp.Series.AudibleSeriesId);
if (seriesItem.Relationships is null)
continue;
var episode = orphanedEpisodes.First(l => l.Book.AudibleProductId == sp.Book.AudibleProductId);
seriesItem.PurchaseDate = new DateTimeOffset(episode.DateAdded);
seriesItem.Series = new AudibleApi.Common.Series[]
{
new AudibleApi.Common.Series{ Asin = seriesItem.Asin, Title = seriesItem.TitleWithSubtitle, Sequence = "-1"}
};
newParentsImportItems.Add(new ImportItem { DtoItem = seriesItem, AccountId = episode.Account, LocaleName = episode.Book.Locale });
}
var newCount = new LibraryBookImporter(context)
.Import(newParentsImportItems);
await context.SaveChangesAsync();
return newCount;
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "An error occurred while trying to scan for orphaned episode parents.");
return -1;
}
}
public static int SaveContext(LibationContext context)
{
try
@ -415,14 +321,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(
this IEnumerable<Book> books,
string tags = null,
LiberatedStatus? bookStatus = null,
LiberatedStatus? pdfStatus = null)
LiberatedStatus? pdfStatus = null,
Rating rating = null)
=> updateUserDefinedItem(
books,
udi => {
@ -435,6 +343,9 @@ namespace ApplicationServices
// method handles null logic
udi.SetPdfStatus(pdfStatus);
if (rating is not null)
udi.UpdateRating(rating.OverallRating, rating.PerformanceRating, rating.StoryRating);
});
public static int UpdateBookStatus(this Book book, LiberatedStatus bookStatus)
@ -487,7 +398,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)

View File

@ -43,23 +43,18 @@ namespace ApplicationServices
else
{
foreach (var book in books)
{
UpdateLiberatedStatus(book);
UpdateBookTags(book);
}
UpdateUserDefinedItems(book);
}
}
public static void FullReIndex() => performSafeCommand(e =>
fullReIndex(e)
);
public static void FullReIndex() => performSafeCommand(fullReIndex);
internal static void UpdateLiberatedStatus(Book book) => performSafeCommand(e =>
e.UpdateLiberatedStatus(book)
);
internal static void UpdateBookTags(Book book) => performSafeCommand(e =>
e.UpdateTags(book.AudibleProductId, book.UserDefinedItem.Tags)
internal static void UpdateUserDefinedItems(Book book) => performSafeCommand(e =>
{
e.UpdateLiberatedStatus(book);
e.UpdateTags(book.AudibleProductId, book.UserDefinedItem.Tags);
e.UpdateUserRatings(book);
}
);
private static void performSafeCommand(Action<SearchEngine> action)
@ -87,7 +82,6 @@ namespace ApplicationServices
isUpdating = true;
action(new SearchEngine());
if (!prevIsUpdating)
SearchEngineUpdated?.Invoke(null, null);
}

View File

@ -1,5 +0,0 @@
<DataGridCheckBoxColumn xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="LibationAvalonia.Controls.DataGridCheckBoxColumnExt">
</DataGridCheckBoxColumn >

View File

@ -1,10 +1,11 @@
using Avalonia.Controls;
using Avalonia.Controls;
using LibationAvalonia.ViewModels;
using System;
using System.Linq;
namespace LibationAvalonia.Controls
{
public partial class DataGridCheckBoxColumnExt : DataGridCheckBoxColumn
public class DataGridCheckBoxColumnExt : DataGridCheckBoxColumn
{
protected override IControl GenerateEditingElementDirect(DataGridCell cell, object dataItem)
{

View File

@ -1,12 +1,55 @@
using Avalonia.Collections;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using LibationAvalonia.ViewModels;
using System;
using System.Reflection;
namespace LibationAvalonia.Controls
{
{
internal static class DataGridContextMenus
{
public static event EventHandler<DataGridCellContextMenuStripNeededEventArgs> CellContextMenuStripNeeded;
private static readonly ContextMenu ContextMenu = new();
private static readonly AvaloniaList<MenuItem> MenuItems = new();
private static readonly PropertyInfo OwningColumnProperty;
static DataGridContextMenus()
{
ContextMenu.Items = MenuItems;
OwningColumnProperty = typeof(DataGridCell).GetProperty("OwningColumn", BindingFlags.Instance | BindingFlags.NonPublic);
}
public static void AttachContextMenu(this DataGridCell cell)
{
if (cell is not null && cell.ContextMenu is null)
{
cell.ContextRequested += Cell_ContextRequested;
cell.ContextMenu = ContextMenu;
}
}
private static void Cell_ContextRequested(object sender, ContextRequestedEventArgs e)
{
if (sender is DataGridCell cell && cell.DataContext is GridEntry entry)
{
var args = new DataGridCellContextMenuStripNeededEventArgs
{
Column = OwningColumnProperty.GetValue(cell) as DataGridColumn,
GridEntry = entry,
ContextMenu = ContextMenu
};
args.ContextMenuItems.Clear();
CellContextMenuStripNeeded?.Invoke(sender, args);
e.Handled = args.ContextMenuItems.Count == 0;
}
else
e.Handled = true;
}
}
public class DataGridCellContextMenuStripNeededEventArgs
{
private static readonly MethodInfo GetCellValueMethod;
@ -19,55 +62,10 @@ namespace LibationAvalonia.Controls
=> GetCellValueMethod.Invoke(column, new object[] { item, column.ClipboardContentBinding })?.ToString() ?? "";
public string CellClipboardContents => GetCellValue(Column, GridEntry);
public DataGridTemplateColumnExt Column { get; init; }
public DataGridColumn Column { get; init; }
public GridEntry GridEntry { get; init; }
public ContextMenu ContextMenu { get; init; }
public AvaloniaList<MenuItem> ContextMenuItems
=> ContextMenu.Items as AvaloniaList<MenuItem>;
}
public partial class DataGridTemplateColumnExt : DataGridTemplateColumn
{
public event EventHandler<DataGridCellContextMenuStripNeededEventArgs> CellContextMenuStripNeeded;
private static readonly ContextMenu ContextMenu = new();
private static readonly AvaloniaList<MenuItem> MenuItems = new();
public DataGridTemplateColumnExt()
{
AvaloniaXamlLoader.Load(this);
ContextMenu.Items = MenuItems;
}
private void Cell_ContextRequested(object sender, ContextRequestedEventArgs e)
{
if (sender is DataGridCell cell && cell.DataContext is GridEntry entry)
{
var args = new DataGridCellContextMenuStripNeededEventArgs
{
Column = this,
GridEntry = entry,
ContextMenu = ContextMenu
};
args.ContextMenuItems.Clear();
CellContextMenuStripNeeded?.Invoke(sender, args);
e.Handled = args.ContextMenuItems.Count == 0;
}
else
e.Handled = true;
}
protected override IControl GenerateElement(DataGridCell cell, object dataItem)
{
if (cell.ContextMenu is null)
{
cell.ContextRequested += Cell_ContextRequested;
cell.ContextMenu = ContextMenu;
}
return base.GenerateElement(cell, dataItem);
}
}
}

View File

@ -0,0 +1,60 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using DataLayer;
namespace LibationAvalonia.Controls
{
public class DataGridMyRatingColumn : DataGridBoundColumn
{
private static Rating DefaultRating => new Rating(0, 0, 0);
public DataGridMyRatingColumn()
{
BindingTarget = MyRatingCellEditor.RatingProperty;
}
protected override IControl GenerateElement(DataGridCell cell, object dataItem)
{
var myRatingElement = new MyRatingCellEditor
{
Name = "CellMyRatingDisplay",
IsEditingMode = false
};
ToolTip.SetTip(myRatingElement, "Click to change ratings");
cell?.AttachContextMenu();
if (Binding != null)
{
myRatingElement.Bind(BindingTarget, Binding);
}
return myRatingElement;
}
protected override IControl GenerateEditingElementDirect(DataGridCell cell, object dataItem)
{
var myRatingElement = new MyRatingCellEditor
{
Name = "CellMyRatingEditor",
IsEditingMode = true
};
return myRatingElement;
}
protected override object PrepareCellForEdit(IControl editingElement, RoutedEventArgs editingEventArgs)
=> editingElement is MyRatingCellEditor myRating
? myRating.Rating
: DefaultRating;
protected override void CancelCellEdit(IControl editingElement, object uneditedValue)
{
if (editingElement is MyRatingCellEditor myRating)
{
var uneditedRating = uneditedValue as Rating;
myRating.Rating = uneditedRating ?? DefaultRating;
}
}
}
}

View File

@ -1,7 +0,0 @@
<DataGridTemplateColumn xmlns="https://github.com/avaloniaui"
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"
x:Class="LibationAvalonia.Controls.DataGridTemplateColumnExt">
</DataGridTemplateColumn>

View File

@ -0,0 +1,15 @@
using Avalonia.Controls;
using System;
using System.Linq;
namespace LibationAvalonia.Controls
{
public partial class DataGridTemplateColumnExt : DataGridTemplateColumn
{
protected override IControl GenerateElement(DataGridCell cell, object dataItem)
{
cell?.AttachContextMenu();
return base.GenerateElement(cell, dataItem);
}
}
}

View File

@ -0,0 +1,54 @@
<UserControl xmlns="https://github.com/avaloniaui"
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="115" d:DesignHeight="80"
x:Class="LibationAvalonia.Controls.MyRatingCellEditor">
<Panel Background="Transparent" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid Name="ratingsGrid" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="3,0,0,0" ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto">
<Grid.Styles>
<Style Selector="TextBlock">
<Setter Property="FontSize" Value="11" />
</Style>
<Style Selector="StackPanel > TextBlock">
<Setter Property="Padding" Value="0,0,-2,0" />
</Style>
</Grid.Styles>
<TextBlock Grid.Column="0" Grid.Row="0" Name="tblockOverall" Text="Overall:" />
<TextBlock Grid.Column="0" Grid.Row="1" Name="tblockPerform" Text="Perform:" />
<TextBlock Grid.Column="0" Grid.Row="2" Name="tblockStory" Text="Story:" />
<Panel Background="Transparent" PointerExited="Panel_PointerExited" Grid.Column="1" Grid.Row="0">
<StackPanel Name="panelOverall" Orientation="Horizontal">
<TextBlock PointerEntered="Star_PointerEntered" Tapped="Star_Tapped" />
<TextBlock PointerEntered="Star_PointerEntered" Tapped="Star_Tapped" />
<TextBlock PointerEntered="Star_PointerEntered" Tapped="Star_Tapped" />
<TextBlock PointerEntered="Star_PointerEntered" Tapped="Star_Tapped" />
<TextBlock PointerEntered="Star_PointerEntered" Tapped="Star_Tapped" />
</StackPanel>
</Panel>
<Panel Background="Transparent" PointerExited="Panel_PointerExited" Grid.Column="1" Grid.Row="1">
<StackPanel Name="panelPerform" Orientation="Horizontal">
<TextBlock PointerEntered="Star_PointerEntered" Tapped="Star_Tapped" />
<TextBlock PointerEntered="Star_PointerEntered" Tapped="Star_Tapped" />
<TextBlock PointerEntered="Star_PointerEntered" Tapped="Star_Tapped" />
<TextBlock PointerEntered="Star_PointerEntered" Tapped="Star_Tapped" />
<TextBlock PointerEntered="Star_PointerEntered" Tapped="Star_Tapped" />
</StackPanel>
</Panel>
<Panel Background="Transparent" PointerExited="Panel_PointerExited" Grid.Column="1" Grid.Row="2">
<StackPanel Name="panelStory" Orientation="Horizontal">
<TextBlock PointerEntered="Star_PointerEntered" Tapped="Star_Tapped" />
<TextBlock PointerEntered="Star_PointerEntered" Tapped="Star_Tapped" />
<TextBlock PointerEntered="Star_PointerEntered" Tapped="Star_Tapped" />
<TextBlock PointerEntered="Star_PointerEntered" Tapped="Star_Tapped" />
<TextBlock PointerEntered="Star_PointerEntered" Tapped="Star_Tapped" />
</StackPanel>
</Panel>
</Grid>
</Panel>
</UserControl>

View File

@ -0,0 +1,108 @@
using Avalonia;
using Avalonia.Controls;
using DataLayer;
using System.Linq;
namespace LibationAvalonia.Controls
{
public partial class MyRatingCellEditor : UserControl
{
private const string SOLID_STAR = "★";
private const string HOLLOW_STAR = "☆";
public static readonly StyledProperty<Rating> RatingProperty =
AvaloniaProperty.Register<MyRatingCellEditor, Rating>(nameof(Rating));
public bool IsEditingMode { get; set; }
public Rating Rating { get => GetValue(RatingProperty); set => SetValue(RatingProperty, value); }
public MyRatingCellEditor()
{
InitializeComponent();
if (Design.IsDesignMode)
Rating = new Rating(5, 4, 3);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (change.Property.Name == nameof(Rating) && Rating is not null)
{
var blankValue = IsEditingMode ? HOLLOW_STAR : string.Empty;
int rating = 0;
foreach (TextBlock star in panelOverall.Children)
star.Tag = star.Text = Rating.OverallRating > rating++ ? SOLID_STAR : blankValue;
rating = 0;
foreach (TextBlock star in panelPerform.Children)
star.Tag = star.Text = Rating.PerformanceRating > rating++ ? SOLID_STAR : blankValue;
rating = 0;
foreach (TextBlock star in panelStory.Children)
star.Tag = star.Text = Rating.StoryRating > rating++ ? SOLID_STAR : blankValue;
SetVisible();
}
base.OnPropertyChanged(change);
}
private void SetVisible()
{
ratingsGrid.IsEnabled = IsEditingMode;
tblockOverall.IsVisible = panelOverall.IsVisible = IsEditingMode || Rating?.OverallRating > 0;
tblockPerform.IsVisible = panelPerform.IsVisible = IsEditingMode || Rating?.PerformanceRating > 0;
tblockStory.IsVisible = panelStory.IsVisible = IsEditingMode || Rating?.StoryRating > 0;
}
public void Panel_PointerExited(object sender, Avalonia.Input.PointerEventArgs e)
{
var panel = sender as Panel;
var stackPanel = panel.Children.OfType<StackPanel>().Single();
//Restore defaults
foreach (TextBlock child in stackPanel.Children)
child.Text = (string)child.Tag;
}
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 newRatingValue = 0;
foreach (var tbox in stackPanel.Children)
{
newRatingValue++;
if (tbox == thisTbox) break;
}
if (stackPanel == panelOverall)
overall = newRatingValue;
else if (stackPanel == panelPerform)
perform = newRatingValue;
else if (stackPanel == panelStory)
story = newRatingValue;
if (overall + perform + story == 0f) return;
Rating = new Rating(overall, perform, story);
}
}
}

View File

@ -131,7 +131,7 @@ namespace LibationAvalonia.Dialogs
var selectedFiles = await StorageProvider.OpenFilePickerAsync(openFileDialogOptions);
var selectedFile = selectedFiles.SingleOrDefault();
if (!selectedFile.TryGetUri(out var uri)) return;
if (selectedFile?.TryGetUri(out var uri) is not true) return;
try
{
@ -291,7 +291,7 @@ namespace LibationAvalonia.Dialogs
var selectedFile = await StorageProvider.SaveFilePickerAsync(options);
if (!selectedFile.TryGetUri(out var uri)) return;
if (selectedFile?.TryGetUri(out var uri) is not true) return;
try
{

View File

@ -51,7 +51,7 @@ namespace LibationAvalonia.Dialogs
{
Title = $"Save Sover Image",
SuggestedStartLocation = new Avalonia.Platform.Storage.FileIO.BclStorageFolder(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)),
SuggestedFileName = $"{PictureFileName}.jpg",
SuggestedFileName = PictureFileName,
DefaultExtension = "jpg",
ShowOverwritePrompt = true,
FileTypeChoices = new FilePickerFileType[]
@ -62,7 +62,7 @@ namespace LibationAvalonia.Dialogs
var selectedFile = await StorageProvider.SaveFilePickerAsync(options);
if (!selectedFile.TryGetUri(out var uri)) return;
if (selectedFile?.TryGetUri(out var uri) is not true) return;
try
{

View File

@ -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"

View File

@ -13,8 +13,6 @@ namespace LibationAvalonia
{
static class Program
{
private static string EXE_DIR = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
static void Main()
{
//***********************************************//

View File

@ -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,21 @@ 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 => MyRating?.ToStarString()?.DefaultIfNullOrWhiteSpace("");
protected Rating _myRating;
public Rating MyRating
{
get => _myRating;
set
{
if (_myRating != value
&& value.OverallRating != 0
&& updateReviewTask?.IsCompleted is not false)
{
updateReviewTask = UpdateRating(value);
}
}
}
protected bool? _remove = false;
public abstract bool? Remove { get; set; }
@ -56,6 +73,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(Book.UserDefinedItem.Tags, Book.UserDefinedItem.BookStatus, Book.UserDefinedItem.PdfStatus, rating);
}
this.RaisePropertyChanged(nameof(MyRating));
}
#endregion
#region Sorting
public GridEntry() => _memberValues = CreateMemberValueDictionary();

View File

@ -65,7 +65,9 @@ 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("");
//Ratings are changed using Update(), which is a problem for Avalonia data bindings because
//the reference doesn't change. Clone the rating so that it updates within Avalonia properly.
_myRating = new Rating(Book.UserDefinedItem.Rating.OverallRating, Book.UserDefinedItem.Rating.PerformanceRating, Book.UserDefinedItem.Rating.StoryRating);
PurchaseDate = libraryBook.DateAdded.ToString("d");
ProductRating = Book.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace("");
Authors = Book.AuthorNames();

View File

@ -10,6 +10,8 @@ using ApplicationServices;
using AudibleUtilities;
using LibationAvalonia.Dialogs.Login;
using Avalonia.Collections;
using LibationSearchEngine;
using Octokit.Internal;
namespace LibationAvalonia.ViewModels
{
@ -41,6 +43,7 @@ namespace LibationAvalonia.ViewModels
public ProductsDisplayViewModel()
{
SearchEngineCommands.SearchEngineUpdated += SearchEngineCommands_SearchEngineUpdated;
GridEntries = new(SOURCE);
GridEntries.Filter = CollectionFilter;
@ -156,15 +159,30 @@ namespace LibationAvalonia.ViewModels
{
if (string.IsNullOrEmpty(searchString)) return null;
var SearchResults = SearchEngineCommands.Search(searchString);
var searchResultSet = SearchEngineCommands.Search(searchString);
var booksFilteredIn = entries.BookEntries().Join(SearchResults.Docs, lbe => lbe.AudibleProductId, d => d.ProductId, (lbe, d) => (GridEntry)lbe);
var booksFilteredIn = entries.BookEntries().Join(searchResultSet.Docs, lbe => lbe.AudibleProductId, d => d.ProductId, (lbe, d) => (GridEntry)lbe);
//Find all series containing children that match the search criteria
var seriesFilteredIn = entries.SeriesEntries().Where(s => s.Children.Join(SearchResults.Docs, lbe => lbe.AudibleProductId, d => d.ProductId, (lbe, d) => lbe).Any());
var seriesFilteredIn = entries.SeriesEntries().Where(s => s.Children.Join(searchResultSet.Docs, lbe => lbe.AudibleProductId, d => d.ProductId, (lbe, d) => lbe).Any());
return booksFilteredIn.Concat(seriesFilteredIn).ToList();
}
}
private async void SearchEngineCommands_SearchEngineUpdated(object sender, EventArgs e)
{
var filterResults = QueryResults(SOURCE, FilterString);
if (filterResults is not null && FilteredInGridEntries.Intersect(filterResults).Count() != FilteredInGridEntries.Count)
{
FilteredInGridEntries = filterResults;
if (GridEntries.IsEditingItem)
GridEntries.CommitEdit();
await Dispatcher.UIThread.InvokeAsync(GridEntries.Refresh);
}
}
#endregion

View File

@ -1,5 +1,4 @@
using Avalonia.Media;
using DataLayer;
using DataLayer;
using Dinah.Core;
using ReactiveUI;
using System;
@ -69,7 +68,9 @@ namespace LibationAvalonia.ViewModels
Title = Book.Title;
Series = Book.SeriesNames();
MyRating = Book.UserDefinedItem.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace("");
//Ratings are changed using Update(), which is a problem for Avalonia data bindings because
//the reference doesn't change. Clone the rating so that it updates within Avalonia properly.
_myRating = new Rating(Book.UserDefinedItem.Rating.OverallRating, Book.UserDefinedItem.Rating.PerformanceRating, Book.UserDefinedItem.Rating.StoryRating);
ProductRating = Book.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace("");
Authors = Book.AuthorNames();
Narrators = Book.NarratorNames();

View File

@ -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());
}
}
}

View File

@ -34,7 +34,7 @@ namespace LibationAvalonia.Views
var selectedFile = await StorageProvider.SaveFilePickerAsync(options);
if (!selectedFile.TryGetUri(out var uri)) return;
if (selectedFile?.TryGetUri(out var uri) is not true) return;
var ext = System.IO.Path.GetExtension(uri.LocalPath);
switch (ext)

View File

@ -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

View File

@ -20,7 +20,7 @@ namespace LibationAvalonia.Views
{
public event EventHandler Load;
public event EventHandler<List<LibraryBook>> LibraryLoaded;
private MainWindowViewModel _viewModel;
private readonly MainWindowViewModel _viewModel;
public MainWindow()
{
@ -77,7 +77,7 @@ namespace LibationAvalonia.Views
try
{
(string zipFile, UpgradeProperties upgradeProperties) = await Task.Run(() => downloadUpdate());
(string zipFile, UpgradeProperties upgradeProperties) = await Task.Run(downloadUpdate);
if (string.IsNullOrEmpty(zipFile) || !System.IO.File.Exists(zipFile))
return;
@ -135,11 +135,9 @@ namespace LibationAvalonia.Views
try
{
System.Net.Http.HttpClient cli = new();
using (var fs = System.IO.File.OpenWrite(zipFile))
{
using (var dlStream = await cli.GetStreamAsync(new Uri(upgradeProperties.ZipUrl)))
await dlStream.CopyToAsync(fs);
}
using var fs = System.IO.File.OpenWrite(zipFile);
using var dlStream = await cli.GetStreamAsync(new Uri(upgradeProperties.ZipUrl));
await dlStream.CopyToAsync(fs);
}
catch (Exception ex)
{
@ -154,8 +152,6 @@ namespace LibationAvalonia.Views
var thisExe = Environment.ProcessPath;
var thisDir = System.IO.Path.GetDirectoryName(thisExe);
var args = $"--input {zipFile.SurroundWithQuotes()} --output {thisDir.SurroundWithQuotes()} --executable {thisExe.SurroundWithQuotes()}";
var zipExtractor = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "ZipExtractor.exe");
System.IO.File.Copy("ZipExtractor.exe", zipExtractor, overwrite: true);
@ -166,8 +162,16 @@ namespace LibationAvalonia.Views
UseShellExecute = true,
Verb = "runas",
WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal,
Arguments = args,
CreateNoWindow = true
CreateNoWindow = true,
ArgumentList =
{
"--input",
zipFile,
"--output",
thisDir,
"--executable",
thisExe
}
};
System.Diagnostics.Process.Start(psi);

View File

@ -124,7 +124,7 @@
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Panel Background="{Binding BackgroundBrush}" Opacity="{Binding Opacity}" Tapped="Description_Click" ToolTip.Tip="Click to see full description" >
<TextBlock Text="{Binding Description}" />
<TextBlock Text="{Binding Description}" FontSize="11" VerticalAlignment="Top" />
</Panel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
@ -140,7 +140,7 @@
</DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt Width="120" Header="Product&#xA;Rating" CanUserSort="True" SortMemberPath="ProductRating" ClipboardContentBinding="{Binding ProductRating}">
<controls:DataGridTemplateColumnExt Width="115" Header="Product&#xA;Rating" CanUserSort="True" SortMemberPath="ProductRating" ClipboardContentBinding="{Binding ProductRating}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Panel Background="{Binding BackgroundBrush}" Opacity="{Binding Opacity}">
@ -160,15 +160,7 @@
</DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt>
<controls:DataGridTemplateColumnExt Width="120" Header="My Rating" CanUserSort="True" SortMemberPath="MyRating" ClipboardContentBinding="{Binding MyRating}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Panel Background="{Binding BackgroundBrush}" Opacity="{Binding Opacity}">
<TextBlock Text="{Binding MyRating}" TextWrapping="NoWrap" FontSize="11" />
</Panel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumnExt>
<controls:DataGridMyRatingColumn IsReadOnly="false" Width="115" Header="My Rating" CanUserSort="True" SortMemberPath="MyRating" ClipboardContentBinding="{Binding MyRatingString}" Binding="{Binding MyRating, Mode=TwoWay}" />
<controls:DataGridTemplateColumnExt Width="135" Header="Misc" CanUserSort="True" SortMemberPath="Misc" ClipboardContentBinding="{Binding Misc}">
<DataGridTemplateColumn.CellTemplate>

View File

@ -71,6 +71,7 @@ namespace LibationAvalonia.Views
AvaloniaXamlLoader.Load(this);
productsGrid = this.FindControl<DataGrid>(nameof(productsGrid));
DataGridContextMenus.CellContextMenuStripNeeded += ProductsGrid_CellContextMenuStripNeeded;
}
#region Cell Context Menu
@ -121,7 +122,7 @@ namespace LibationAvalonia.Views
var selectedFiles = await this.GetParentWindow().StorageProvider.OpenFilePickerAsync(openFileDialogOptions);
var selectedFile = selectedFiles.SingleOrDefault();
if (selectedFile.TryGetUri(out var uri))
if (selectedFile?.TryGetUri(out var uri) is true)
FilePathCache.Insert(entry.AudibleProductId, uri.LocalPath);
}
catch (Exception ex)
@ -179,10 +180,6 @@ namespace LibationAvalonia.Views
foreach (var column in productsGrid.Columns)
{
//Wire up column context menu
if (column is DataGridTemplateColumnExt tc)
tc.CellContextMenuStripNeeded += ProductsGrid_CellContextMenuStripNeeded;
var itemName = column.SortMemberPath;
if (itemName == nameof(GridEntry.Remove))

View File

@ -90,8 +90,8 @@ namespace LibationSearchEngine
["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()
["UserRating"] = lb => userOverallRating(lb.Book),
["MyRating"] = lb => userOverallRating(lb.Book)
}
);
@ -136,7 +136,7 @@ namespace LibationSearchEngine
var narrators = lb.Book.Narrators.Select(a => a.Name).ToArray();
return authors.Intersect(narrators).Any();
}
private static string userOverallRating(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;
@ -150,10 +150,10 @@ namespace LibationSearchEngine
stringIndexRules[ALL_NARRATOR_NAMES],
stringIndexRules[ALL_SERIES_NAMES]
};
#endregion
#endregion
#region get search fields. used for display in help
public static IEnumerable<string> GetSearchIdFields()
#region get search fields. used for display in help
public static IEnumerable<string> GetSearchIdFields()
{
foreach (var key in idIndexRules.Keys)
yield return key;
@ -176,29 +176,29 @@ namespace LibationSearchEngine
foreach (var key in numberIndexRules.Keys)
yield return key;
}
#endregion
#endregion
#region create and update index
#region create and update index
/// <summary>create new. ie: full re-index</summary>
public void CreateNewIndex(IEnumerable<LibraryBook> library, bool overwrite = true)
{
// location of index/create the index
using var index = getIndex();
// location of index/create the index
using var index = getIndex();
var exists = IndexReader.IndexExists(index);
var createNewIndex = overwrite || !exists;
// analyzer for tokenizing text. same analyzer should be used for indexing and searching
using var analyzer = new StandardAnalyzer(Version);
using var ixWriter = new IndexWriter(index, analyzer, createNewIndex, IndexWriter.MaxFieldLength.UNLIMITED);
foreach (var libraryBook in library)
{
var doc = createBookIndexDocument(libraryBook);
ixWriter.AddDocument(doc);
}
// analyzer for tokenizing text. same analyzer should be used for indexing and searching
using var analyzer = new StandardAnalyzer(Version);
using var ixWriter = new IndexWriter(index, analyzer, createNewIndex, IndexWriter.MaxFieldLength.UNLIMITED);
foreach (var libraryBook in library)
{
var doc = createBookIndexDocument(libraryBook);
ixWriter.AddDocument(doc);
}
}
/// <summary>Long running. Use await Task.Run(() => UpdateBook(productId))</summary>
public void UpdateBook(LibationContext context, string productId)
/// <summary>Long running. Use await Task.Run(() => UpdateBook(productId))</summary>
public void UpdateBook(LibationContext context, string productId)
{
var libraryBook = context.GetLibraryBook_Flat_NoTracking(productId);
var term = new Term(_ID_, productId);
@ -206,12 +206,12 @@ namespace LibationSearchEngine
var document = createBookIndexDocument(libraryBook);
var createNewIndex = false;
using var index = getIndex();
using var analyzer = new StandardAnalyzer(Version);
using var ixWriter = new IndexWriter(index, analyzer, createNewIndex, IndexWriter.MaxFieldLength.UNLIMITED);
ixWriter.DeleteDocuments(term);
ixWriter.AddDocument(document);
}
using var index = getIndex();
using var analyzer = new StandardAnalyzer(Version);
using var ixWriter = new IndexWriter(index, analyzer, createNewIndex, IndexWriter.MaxFieldLength.UNLIMITED);
ixWriter.DeleteDocuments(term);
ixWriter.AddDocument(document);
}
private static Document createBookIndexDocument(LibraryBook libraryBook)
{
@ -262,7 +262,7 @@ namespace LibationSearchEngine
{
// 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.
d.RemoveField(fieldName);
d.RemoveField(fieldName.ToLower());
d.AddAnalyzed(fieldName, newValue);
});
@ -279,16 +279,34 @@ namespace LibationSearchEngine
// 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 = isLiberated(book);
d.RemoveField("IsLiberated");
d.RemoveField("isliberated");
d.AddBool("IsLiberated", v1);
d.RemoveField("Liberated");
d.RemoveField("liberated");
d.AddBool("Liberated", v1);
var v2 = liberatedError(book);
d.RemoveField("LiberatedError");
d.RemoveField("liberatederror");
d.AddBool("LiberatedError", v2);
});
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 = userOverallRating(book);
d.RemoveField("userrating");
d.AddNotAnalyzed("UserRating", v1);
d.RemoveField("myrating");
d.AddNotAnalyzed("MyRating", v1);
});
private static void updateDocument(string productId, Action<Document> action)
{
var productTerm = new Term(_ID_, productId);
@ -315,12 +333,12 @@ namespace LibationSearchEngine
using var ixWriter = new IndexWriter(index, analyzer, createNewIndex, IndexWriter.MaxFieldLength.UNLIMITED);
ixWriter.UpdateDocument(productTerm, document, analyzer);
}
#endregion
#endregion
// the workaround which allows displaying all books when query is empty
public const string ALL_QUERY = "*:*";
#region search
#region search
public SearchResultSet Search(string searchString)
{
Serilog.Log.Logger.Debug("original search string: {@DebugInfo}", new { searchString });
@ -353,8 +371,8 @@ namespace LibationSearchEngine
return searchString;
}
#region format query string
private static string parseTag(string tagSearchString)
#region format query string
private static string parseTag(string tagSearchString)
{
var allMatches = LuceneRegex
.TagRegex
@ -419,33 +437,33 @@ namespace LibationSearchEngine
{
var defaultField = ALL;
using var index = getIndex();
using var searcher = new IndexSearcher(index);
using var analyzer = new StandardAnalyzer(Version);
var query = analyzer.GetQuery(defaultField, searchString);
using var index = getIndex();
using var searcher = new IndexSearcher(index);
using var analyzer = new StandardAnalyzer(Version);
var query = analyzer.GetQuery(defaultField, searchString);
// lucene doesn't allow only negations. eg this returns nothing:
// -tags:hidden
// work arounds: https://kb.ucla.edu/articles/pure-negation-query-in-lucene
// HOWEVER, doing this to any other type of query can cause EVERYTHING to be a match unless "Occur" is carefully set
// this should really check that all leaf nodes are MUST_NOT
if (query is BooleanQuery boolQuery)
{
var occurs = getOccurs_recurs(boolQuery);
if (occurs.Any() && occurs.All(o => o == Occur.MUST_NOT))
boolQuery.Add(new MatchAllDocsQuery(), Occur.MUST);
}
// lucene doesn't allow only negations. eg this returns nothing:
// -tags:hidden
// work arounds: https://kb.ucla.edu/articles/pure-negation-query-in-lucene
// HOWEVER, doing this to any other type of query can cause EVERYTHING to be a match unless "Occur" is carefully set
// this should really check that all leaf nodes are MUST_NOT
if (query is BooleanQuery boolQuery)
{
var occurs = getOccurs_recurs(boolQuery);
if (occurs.Any() && occurs.All(o => o == Occur.MUST_NOT))
boolQuery.Add(new MatchAllDocsQuery(), Occur.MUST);
}
var docs = searcher
.Search(query, searcher.MaxDoc + 1)
.ScoreDocs
.Select(ds => new ScoreDocExplicit(searcher.Doc(ds.Doc), ds.Score))
.ToList();
var docs = searcher
.Search(query, searcher.MaxDoc + 1)
.ScoreDocs
.Select(ds => new ScoreDocExplicit(searcher.Doc(ds.Doc), ds.Score))
.ToList();
var queryString = query.ToString();
Serilog.Log.Logger.Debug("query: {@DebugInfo}", new { queryString });
return new SearchResultSet(queryString, docs);
}
return new SearchResultSet(queryString, docs);
}
private IEnumerable<Occur> getOccurs_recurs(BooleanQuery query)
{
@ -477,9 +495,9 @@ namespace LibationSearchEngine
// Serilog.Log.Logger.Debug($" [{f.Name}]={f.StringValue}");
//}
}
#endregion
#endregion
private static Directory getIndex() => FSDirectory.Open(SearchEngineDirectory);
private static Directory getIndex() => FSDirectory.Open(SearchEngineDirectory);
// not customizable. don't move to config
private static string SearchEngineDirectory { get; }

View File

@ -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 != 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(Book.UserDefinedItem.Tags, Book.UserDefinedItem.BookStatus, Book.UserDefinedItem.PdfStatus, 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

View File

@ -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();

View File

@ -0,0 +1,368 @@
namespace LibationWinForms.GridView
{
partial class MyRatingCellEditor
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.lblOverall = new System.Windows.Forms.Label();
this.lblPerform = new System.Windows.Forms.Label();
this.lblStory = new System.Windows.Forms.Label();
this.panelOverall = new System.Windows.Forms.Panel();
this.noBorderLabel1 = new LibationWinForms.GridView.NoBorderLabel();
this.noBorderLabel2 = new LibationWinForms.GridView.NoBorderLabel();
this.noBorderLabel3 = new LibationWinForms.GridView.NoBorderLabel();
this.noBorderLabel4 = new LibationWinForms.GridView.NoBorderLabel();
this.noBorderLabel5 = new LibationWinForms.GridView.NoBorderLabel();
this.panelPerform = new System.Windows.Forms.Panel();
this.noBorderLabel6 = new LibationWinForms.GridView.NoBorderLabel();
this.noBorderLabel7 = new LibationWinForms.GridView.NoBorderLabel();
this.noBorderLabel8 = new LibationWinForms.GridView.NoBorderLabel();
this.noBorderLabel9 = new LibationWinForms.GridView.NoBorderLabel();
this.noBorderLabel10 = new LibationWinForms.GridView.NoBorderLabel();
this.panelStory = new System.Windows.Forms.Panel();
this.noBorderLabel11 = new LibationWinForms.GridView.NoBorderLabel();
this.noBorderLabel12 = new LibationWinForms.GridView.NoBorderLabel();
this.noBorderLabel13 = new LibationWinForms.GridView.NoBorderLabel();
this.noBorderLabel14 = new LibationWinForms.GridView.NoBorderLabel();
this.noBorderLabel15 = new LibationWinForms.GridView.NoBorderLabel();
this.panelOverall.SuspendLayout();
this.panelPerform.SuspendLayout();
this.panelStory.SuspendLayout();
this.SuspendLayout();
//
// lblOverall
//
this.lblOverall.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.lblOverall.AutoSize = true;
this.lblOverall.Location = new System.Drawing.Point(0, 1);
this.lblOverall.Margin = new System.Windows.Forms.Padding(0);
this.lblOverall.Name = "lblOverall";
this.lblOverall.Size = new System.Drawing.Size(47, 15);
this.lblOverall.TabIndex = 6;
this.lblOverall.Text = "Overall:";
//
// lblPerform
//
this.lblPerform.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.lblPerform.AutoSize = true;
this.lblPerform.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.lblPerform.Location = new System.Drawing.Point(0, 16);
this.lblPerform.Margin = new System.Windows.Forms.Padding(0);
this.lblPerform.Name = "lblPerform";
this.lblPerform.Size = new System.Drawing.Size(53, 15);
this.lblPerform.TabIndex = 8;
this.lblPerform.Text = "Perform:";
//
// lblStory
//
this.lblStory.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.lblStory.AutoSize = true;
this.lblStory.Location = new System.Drawing.Point(0, 31);
this.lblStory.Margin = new System.Windows.Forms.Padding(0);
this.lblStory.Name = "lblStory";
this.lblStory.Size = new System.Drawing.Size(37, 15);
this.lblStory.TabIndex = 10;
this.lblStory.Text = "Story:";
//
// panelOverall
//
this.panelOverall.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.panelOverall.Controls.Add(this.noBorderLabel1);
this.panelOverall.Controls.Add(this.noBorderLabel2);
this.panelOverall.Controls.Add(this.noBorderLabel3);
this.panelOverall.Controls.Add(this.noBorderLabel4);
this.panelOverall.Controls.Add(this.noBorderLabel5);
this.panelOverall.Location = new System.Drawing.Point(52, 4);
this.panelOverall.Name = "panelOverall";
this.panelOverall.Size = new System.Drawing.Size(50, 11);
this.panelOverall.TabIndex = 5;
//
// noBorderLabel1
//
this.noBorderLabel1.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.noBorderLabel1.LabelOffset = new System.Drawing.Point(-3, -3);
this.noBorderLabel1.Location = new System.Drawing.Point(0, 0);
this.noBorderLabel1.Name = "noBorderLabel1";
this.noBorderLabel1.Size = new System.Drawing.Size(10, 11);
this.noBorderLabel1.TabIndex = 0;
this.noBorderLabel1.Text = "☆";
this.noBorderLabel1.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick);
this.noBorderLabel1.MouseEnter += new System.EventHandler(this.Star_MouseEnter);
this.noBorderLabel1.MouseLeave += new System.EventHandler(this.Star_MouseLeave);
//
// noBorderLabel2
//
this.noBorderLabel2.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.noBorderLabel2.LabelOffset = new System.Drawing.Point(-3, -3);
this.noBorderLabel2.Location = new System.Drawing.Point(10, 0);
this.noBorderLabel2.Name = "noBorderLabel2";
this.noBorderLabel2.Size = new System.Drawing.Size(10, 11);
this.noBorderLabel2.TabIndex = 0;
this.noBorderLabel2.Text = "☆";
this.noBorderLabel2.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick);
this.noBorderLabel2.MouseEnter += new System.EventHandler(this.Star_MouseEnter);
this.noBorderLabel2.MouseLeave += new System.EventHandler(this.Star_MouseLeave);
//
// noBorderLabel3
//
this.noBorderLabel3.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.noBorderLabel3.LabelOffset = new System.Drawing.Point(-3, -3);
this.noBorderLabel3.Location = new System.Drawing.Point(20, 0);
this.noBorderLabel3.Name = "noBorderLabel3";
this.noBorderLabel3.Size = new System.Drawing.Size(10, 11);
this.noBorderLabel3.TabIndex = 0;
this.noBorderLabel3.Text = "☆";
this.noBorderLabel3.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick);
this.noBorderLabel3.MouseEnter += new System.EventHandler(this.Star_MouseEnter);
this.noBorderLabel3.MouseLeave += new System.EventHandler(this.Star_MouseLeave);
//
// noBorderLabel4
//
this.noBorderLabel4.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.noBorderLabel4.LabelOffset = new System.Drawing.Point(-3, -3);
this.noBorderLabel4.Location = new System.Drawing.Point(30, 0);
this.noBorderLabel4.Name = "noBorderLabel4";
this.noBorderLabel4.Size = new System.Drawing.Size(10, 11);
this.noBorderLabel4.TabIndex = 0;
this.noBorderLabel4.Text = "☆";
this.noBorderLabel4.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick);
this.noBorderLabel4.MouseEnter += new System.EventHandler(this.Star_MouseEnter);
this.noBorderLabel4.MouseLeave += new System.EventHandler(this.Star_MouseLeave);
//
// noBorderLabel5
//
this.noBorderLabel5.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.noBorderLabel5.LabelOffset = new System.Drawing.Point(-3, -3);
this.noBorderLabel5.Location = new System.Drawing.Point(40, 0);
this.noBorderLabel5.Name = "noBorderLabel5";
this.noBorderLabel5.Size = new System.Drawing.Size(10, 11);
this.noBorderLabel5.TabIndex = 0;
this.noBorderLabel5.Text = "☆";
this.noBorderLabel5.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick);
this.noBorderLabel5.MouseEnter += new System.EventHandler(this.Star_MouseEnter);
this.noBorderLabel5.MouseLeave += new System.EventHandler(this.Star_MouseLeave);
//
// panelPerform
//
this.panelPerform.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.panelPerform.Controls.Add(this.noBorderLabel6);
this.panelPerform.Controls.Add(this.noBorderLabel7);
this.panelPerform.Controls.Add(this.noBorderLabel8);
this.panelPerform.Controls.Add(this.noBorderLabel9);
this.panelPerform.Controls.Add(this.noBorderLabel10);
this.panelPerform.Location = new System.Drawing.Point(52, 19);
this.panelPerform.Name = "panelPerform";
this.panelPerform.Size = new System.Drawing.Size(50, 11);
this.panelPerform.TabIndex = 6;
//
// noBorderLabel6
//
this.noBorderLabel6.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.noBorderLabel6.LabelOffset = new System.Drawing.Point(-3, -3);
this.noBorderLabel6.Location = new System.Drawing.Point(0, 0);
this.noBorderLabel6.Name = "noBorderLabel6";
this.noBorderLabel6.Size = new System.Drawing.Size(10, 11);
this.noBorderLabel6.TabIndex = 0;
this.noBorderLabel6.Text = "☆";
this.noBorderLabel6.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick);
this.noBorderLabel6.MouseEnter += new System.EventHandler(this.Star_MouseEnter);
this.noBorderLabel6.MouseLeave += new System.EventHandler(this.Star_MouseLeave);
//
// noBorderLabel7
//
this.noBorderLabel7.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.noBorderLabel7.LabelOffset = new System.Drawing.Point(-3, -3);
this.noBorderLabel7.Location = new System.Drawing.Point(10, 0);
this.noBorderLabel7.Name = "noBorderLabel7";
this.noBorderLabel7.Size = new System.Drawing.Size(10, 11);
this.noBorderLabel7.TabIndex = 0;
this.noBorderLabel7.Text = "☆";
this.noBorderLabel7.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick);
this.noBorderLabel7.MouseEnter += new System.EventHandler(this.Star_MouseEnter);
this.noBorderLabel7.MouseLeave += new System.EventHandler(this.Star_MouseLeave);
//
// noBorderLabel8
//
this.noBorderLabel8.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.noBorderLabel8.LabelOffset = new System.Drawing.Point(-3, -3);
this.noBorderLabel8.Location = new System.Drawing.Point(20, 0);
this.noBorderLabel8.Name = "noBorderLabel8";
this.noBorderLabel8.Size = new System.Drawing.Size(10, 11);
this.noBorderLabel8.TabIndex = 0;
this.noBorderLabel8.Text = "☆";
this.noBorderLabel8.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick);
this.noBorderLabel8.MouseEnter += new System.EventHandler(this.Star_MouseEnter);
this.noBorderLabel8.MouseLeave += new System.EventHandler(this.Star_MouseLeave);
//
// noBorderLabel9
//
this.noBorderLabel9.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.noBorderLabel9.LabelOffset = new System.Drawing.Point(-3, -3);
this.noBorderLabel9.Location = new System.Drawing.Point(30, 0);
this.noBorderLabel9.Name = "noBorderLabel9";
this.noBorderLabel9.Size = new System.Drawing.Size(10, 11);
this.noBorderLabel9.TabIndex = 0;
this.noBorderLabel9.Text = "☆";
this.noBorderLabel9.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick);
this.noBorderLabel9.MouseEnter += new System.EventHandler(this.Star_MouseEnter);
this.noBorderLabel9.MouseLeave += new System.EventHandler(this.Star_MouseLeave);
//
// noBorderLabel10
//
this.noBorderLabel10.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.noBorderLabel10.LabelOffset = new System.Drawing.Point(-3, -3);
this.noBorderLabel10.Location = new System.Drawing.Point(40, 0);
this.noBorderLabel10.Name = "noBorderLabel10";
this.noBorderLabel10.Size = new System.Drawing.Size(10, 11);
this.noBorderLabel10.TabIndex = 0;
this.noBorderLabel10.Text = "☆";
this.noBorderLabel10.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick);
this.noBorderLabel10.MouseEnter += new System.EventHandler(this.Star_MouseEnter);
this.noBorderLabel10.MouseLeave += new System.EventHandler(this.Star_MouseLeave);
//
// panelStory
//
this.panelStory.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.panelStory.Controls.Add(this.noBorderLabel11);
this.panelStory.Controls.Add(this.noBorderLabel12);
this.panelStory.Controls.Add(this.noBorderLabel13);
this.panelStory.Controls.Add(this.noBorderLabel14);
this.panelStory.Controls.Add(this.noBorderLabel15);
this.panelStory.Location = new System.Drawing.Point(52, 34);
this.panelStory.Name = "panelStory";
this.panelStory.Size = new System.Drawing.Size(50, 11);
this.panelStory.TabIndex = 6;
//
// noBorderLabel11
//
this.noBorderLabel11.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.noBorderLabel11.LabelOffset = new System.Drawing.Point(-3, -3);
this.noBorderLabel11.Location = new System.Drawing.Point(0, 0);
this.noBorderLabel11.Name = "noBorderLabel11";
this.noBorderLabel11.Size = new System.Drawing.Size(10, 11);
this.noBorderLabel11.TabIndex = 0;
this.noBorderLabel11.Text = "☆";
this.noBorderLabel11.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick);
this.noBorderLabel11.MouseEnter += new System.EventHandler(this.Star_MouseEnter);
this.noBorderLabel11.MouseLeave += new System.EventHandler(this.Star_MouseLeave);
//
// noBorderLabel12
//
this.noBorderLabel12.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.noBorderLabel12.LabelOffset = new System.Drawing.Point(-3, -3);
this.noBorderLabel12.Location = new System.Drawing.Point(10, 0);
this.noBorderLabel12.Name = "noBorderLabel12";
this.noBorderLabel12.Size = new System.Drawing.Size(10, 11);
this.noBorderLabel12.TabIndex = 0;
this.noBorderLabel12.Text = "☆";
this.noBorderLabel12.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick);
this.noBorderLabel12.MouseEnter += new System.EventHandler(this.Star_MouseEnter);
this.noBorderLabel12.MouseLeave += new System.EventHandler(this.Star_MouseLeave);
//
// noBorderLabel13
//
this.noBorderLabel13.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.noBorderLabel13.LabelOffset = new System.Drawing.Point(-3, -3);
this.noBorderLabel13.Location = new System.Drawing.Point(20, 0);
this.noBorderLabel13.Name = "noBorderLabel13";
this.noBorderLabel13.Size = new System.Drawing.Size(10, 11);
this.noBorderLabel13.TabIndex = 0;
this.noBorderLabel13.Text = "☆";
this.noBorderLabel13.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick);
this.noBorderLabel13.MouseEnter += new System.EventHandler(this.Star_MouseEnter);
this.noBorderLabel13.MouseLeave += new System.EventHandler(this.Star_MouseLeave);
//
// noBorderLabel14
//
this.noBorderLabel14.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.noBorderLabel14.LabelOffset = new System.Drawing.Point(-3, -3);
this.noBorderLabel14.Location = new System.Drawing.Point(30, 0);
this.noBorderLabel14.Name = "noBorderLabel14";
this.noBorderLabel14.Size = new System.Drawing.Size(10, 11);
this.noBorderLabel14.TabIndex = 0;
this.noBorderLabel14.Text = "☆";
this.noBorderLabel14.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick);
this.noBorderLabel14.MouseEnter += new System.EventHandler(this.Star_MouseEnter);
this.noBorderLabel14.MouseLeave += new System.EventHandler(this.Star_MouseLeave);
//
// noBorderLabel15
//
this.noBorderLabel15.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.noBorderLabel15.LabelOffset = new System.Drawing.Point(-3, -3);
this.noBorderLabel15.Location = new System.Drawing.Point(40, 0);
this.noBorderLabel15.Name = "noBorderLabel15";
this.noBorderLabel15.Size = new System.Drawing.Size(10, 11);
this.noBorderLabel15.TabIndex = 0;
this.noBorderLabel15.Text = "☆";
this.noBorderLabel15.MouseClick += new System.Windows.Forms.MouseEventHandler(this.Star_MouseClick);
this.noBorderLabel15.MouseEnter += new System.EventHandler(this.Star_MouseEnter);
this.noBorderLabel15.MouseLeave += new System.EventHandler(this.Star_MouseLeave);
//
// MyRatingCellEditor
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.panelStory);
this.Controls.Add(this.panelPerform);
this.Controls.Add(this.lblStory);
this.Controls.Add(this.lblPerform);
this.Controls.Add(this.lblOverall);
this.Controls.Add(this.panelOverall);
this.Name = "MyRatingCellEditor";
this.Size = new System.Drawing.Size(110, 46);
this.panelOverall.ResumeLayout(false);
this.panelPerform.ResumeLayout(false);
this.panelStory.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Panel panelOverall;
private System.Windows.Forms.Label lblOverall;
private System.Windows.Forms.Label lblPerform;
private System.Windows.Forms.Label lblStory;
private NoBorderLabel noBorderLabel1;
private NoBorderLabel noBorderLabel5;
private NoBorderLabel noBorderLabel4;
private NoBorderLabel noBorderLabel3;
private NoBorderLabel noBorderLabel2;
private System.Windows.Forms.Panel panelPerform;
private NoBorderLabel noBorderLabel6;
private NoBorderLabel noBorderLabel7;
private NoBorderLabel noBorderLabel8;
private NoBorderLabel noBorderLabel9;
private NoBorderLabel noBorderLabel10;
private System.Windows.Forms.Panel panelStory;
private NoBorderLabel noBorderLabel11;
private NoBorderLabel noBorderLabel12;
private NoBorderLabel noBorderLabel13;
private NoBorderLabel noBorderLabel14;
private NoBorderLabel noBorderLabel15;
}
}

View File

@ -0,0 +1,168 @@
using DataLayer;
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace LibationWinForms.GridView
{
public partial class MyRatingCellEditor : 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 (NoBorderLabel star in panelOverall.Controls)
star.Tag = star.Text = _rating.OverallRating > rating++ ? SOLID_STAR : HOLLOW_STAR;
rating = 0;
foreach (NoBorderLabel star in panelPerform.Controls)
star.Tag = star.Text = _rating.PerformanceRating > rating++ ? SOLID_STAR : HOLLOW_STAR;
rating = 0;
foreach (NoBorderLabel star in panelStory.Controls)
star.Tag = star.Text = _rating.StoryRating > rating++ ? SOLID_STAR : HOLLOW_STAR;
}
}
public MyRatingCellEditor()
{
InitializeComponent();
}
private void Star_MouseEnter(object sender, EventArgs e)
{
var thisTbox = sender as NoBorderLabel;
var panel = thisTbox.Parent as Panel;
var star = SOLID_STAR;
foreach (NoBorderLabel child in panel.Controls)
{
child.Text = star;
if (child == thisTbox) star = HOLLOW_STAR;
}
}
private void Star_MouseLeave(object sender, EventArgs e)
{
var thisTbox = sender as NoBorderLabel;
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 (NoBorderLabel 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 NoBorderLabel;
var panel = thisTbox.Parent as Panel;
int newRatingValue = 0;
foreach (var child in panel.Controls)
{
newRatingValue++;
if (child == thisTbox) break;
}
if (panel == panelOverall)
overall = newRatingValue;
else if (panel == panelPerform)
perform = newRatingValue;
else if (panel == panelStory)
story = newRatingValue;
if (overall + perform + story == 0f) return;
var newRating = new Rating(overall, perform, story);
if (newRating == Rating) return;
Rating = newRating;
EditingControlValueChanged = true;
EditingControlDataGridView.NotifyCurrentCellDirty(true);
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyCode == Keys.Escape)
{
EditingControlDataGridView.RefreshEdit();
EditingControlDataGridView.CancelEdit();
EditingControlDataGridView.CurrentCell.DetachEditingControl();
EditingControlDataGridView.CurrentCell = null;
}
base.OnKeyDown(e);
}
#region IDataGridViewEditingControl
public DataGridView EditingControlDataGridView { get; set; }
public int EditingControlRowIndex { get; set; }
public bool EditingControlValueChanged { get; set; }
public object EditingControlFormattedValue { get => Rating; set { } }
public Cursor EditingPanelCursor => Cursor;
public bool RepositionEditingControlOnValueChange => false;
public void ApplyCellStyleToEditingControl(DataGridViewCellStyle dataGridViewCellStyle)
{
Font = dataGridViewCellStyle.Font;
ForeColor = dataGridViewCellStyle.ForeColor;
BackColor = dataGridViewCellStyle.BackColor;
}
public bool EditingControlWantsInputKey(Keys keyData, bool dataGridViewWantsInputKey) => keyData == Keys.Escape;
public object GetEditingControlFormattedValue(DataGridViewDataErrorContexts context) => EditingControlFormattedValue;
public void PrepareEditingControlForEdit(bool selectAll) { }
#endregion
}
public class NoBorderLabel : Panel
{
private string _text;
[Description("Label text"), Category("Data")]
[Browsable(true)]
[EditorBrowsable(EditorBrowsableState.Always)]
[AllowNull]
public override string Text
{
get => _text;
set
{
_text = value;
Invalidate();
}
}
[Description("X and Y offset for text drawing position. May be negative."), Category("Layout")]
[Browsable(true)]
[EditorBrowsable(EditorBrowsableState.Always)]
public Point LabelOffset { get; set; }
protected override void OnPaint(PaintEventArgs e)
{
TextRenderer.DrawText(e, Text, this.Font, LabelOffset, this.ForeColor);
base.OnPaint(e);
}
}
}

View File

@ -0,0 +1,126 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="lblOverall.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="lblPerform.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="lblStory.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="panelOverall.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="noBorderLabel1.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="noBorderLabel2.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="noBorderLabel3.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="noBorderLabel4.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="noBorderLabel5.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="panelPerform.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="noBorderLabel6.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="noBorderLabel7.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="noBorderLabel8.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="noBorderLabel9.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="noBorderLabel10.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="panelStory.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="noBorderLabel11.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="noBorderLabel12.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="noBorderLabel13.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="noBorderLabel14.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="noBorderLabel15.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="$this.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
</root>

View File

@ -0,0 +1,62 @@
using DataLayer;
using System;
using System.ComponentModel;
using System.Drawing;
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 {nameof(MyRatingGridViewCell)}");
base.CellTemplate = value;
}
}
}
internal class MyRatingGridViewCell : DataGridViewTextBoxCell
{
private static Rating DefaultRating => new Rating(0, 0, 0);
public override object DefaultNewRowValue => DefaultRating;
public override Type EditType => typeof(MyRatingCellEditor);
public override Type ValueType => typeof(Rating);
public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
{
base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle);
var ctl = DataGridView.EditingControl as MyRatingCellEditor;
ctl.Rating = Value is Rating rating ? rating : DefaultRating;
}
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
{
if (value is Rating rating)
{
ToolTipText = "Click to change ratings";
var starString = rating.ToStarString();
base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, starString, starString, errorText, cellStyle, advancedBorderStyle, paintParts);
}
else
base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, string.Empty, string.Empty, errorText, cellStyle, advancedBorderStyle, paintParts);
}
protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context)
=> value is Rating rating ? rating.ToStarString() : value?.ToString();
public override object ParseFormattedValue(object formattedValue, DataGridViewCellStyle cellStyle, TypeConverter formattedValueTypeConverter, TypeConverter valueTypeConverter)
=> formattedValue;
}
}

View File

@ -28,39 +28,39 @@
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
this.gridEntryDataGridView = new System.Windows.Forms.DataGridView();
this.removeGVColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn();
this.liberateGVColumn = new LibationWinForms.GridView.LiberateDataGridViewImageButtonColumn();
this.coverGVColumn = new System.Windows.Forms.DataGridViewImageColumn();
this.titleGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.authorsGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.narratorsGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.lengthGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.seriesGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.descriptionGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
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.miscGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.tagAndDetailsGVColumn = new LibationWinForms.GridView.EditTagsDataGridViewImageButtonColumn();
this.showHideColumnsContextMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components);
this.syncBindingSource = new LibationWinForms.GridView.SyncBindingSource(this.components);
((System.ComponentModel.ISupportInitialize)(this.gridEntryDataGridView)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.syncBindingSource)).BeginInit();
this.SuspendLayout();
//
// gridEntryDataGridView
//
this.gridEntryDataGridView.AllowUserToAddRows = false;
this.gridEntryDataGridView.AllowUserToDeleteRows = false;
this.gridEntryDataGridView.AllowUserToOrderColumns = true;
this.gridEntryDataGridView.AllowUserToResizeRows = false;
this.gridEntryDataGridView.AutoGenerateColumns = false;
this.gridEntryDataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.gridEntryDataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.components = new System.ComponentModel.Container();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
this.gridEntryDataGridView = new System.Windows.Forms.DataGridView();
this.removeGVColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn();
this.liberateGVColumn = new LibationWinForms.GridView.LiberateDataGridViewImageButtonColumn();
this.coverGVColumn = new System.Windows.Forms.DataGridViewImageColumn();
this.titleGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.authorsGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.narratorsGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.lengthGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.seriesGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.descriptionGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.categoryGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.productRatingGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.purchaseDateGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.myRatingGVColumn = new LibationWinForms.GridView.MyRatingGridViewColumn();
this.miscGVColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.tagAndDetailsGVColumn = new LibationWinForms.GridView.EditTagsDataGridViewImageButtonColumn();
this.showHideColumnsContextMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components);
this.syncBindingSource = new LibationWinForms.GridView.SyncBindingSource(this.components);
((System.ComponentModel.ISupportInitialize)(this.gridEntryDataGridView)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.syncBindingSource)).BeginInit();
this.SuspendLayout();
//
// gridEntryDataGridView
//
this.gridEntryDataGridView.AllowUserToAddRows = false;
this.gridEntryDataGridView.AllowUserToDeleteRows = false;
this.gridEntryDataGridView.AllowUserToOrderColumns = true;
this.gridEntryDataGridView.AllowUserToResizeRows = false;
this.gridEntryDataGridView.AutoGenerateColumns = false;
this.gridEntryDataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.gridEntryDataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.removeGVColumn,
this.liberateGVColumn,
this.coverGVColumn,
@ -76,175 +76,176 @@
this.myRatingGVColumn,
this.miscGVColumn,
this.tagAndDetailsGVColumn});
this.gridEntryDataGridView.ContextMenuStrip = this.showHideColumnsContextMenuStrip;
this.gridEntryDataGridView.DataSource = this.syncBindingSource;
dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Window;
dataGridViewCellStyle1.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.ControlText;
dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.gridEntryDataGridView.DefaultCellStyle = dataGridViewCellStyle1;
this.gridEntryDataGridView.Dock = System.Windows.Forms.DockStyle.Fill;
this.gridEntryDataGridView.Location = new System.Drawing.Point(0, 0);
this.gridEntryDataGridView.Name = "gridEntryDataGridView";
this.gridEntryDataGridView.RowHeadersVisible = false;
this.gridEntryDataGridView.RowTemplate.Height = 82;
this.gridEntryDataGridView.Size = new System.Drawing.Size(1570, 380);
this.gridEntryDataGridView.TabIndex = 0;
this.gridEntryDataGridView.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.DataGridView_CellContentClick);
this.gridEntryDataGridView.CellContextMenuStripNeeded += new System.Windows.Forms.DataGridViewCellContextMenuStripNeededEventHandler(this.gridEntryDataGridView_CellContextMenuStripNeeded);
this.gridEntryDataGridView.CellToolTipTextNeeded += new System.Windows.Forms.DataGridViewCellToolTipTextNeededEventHandler(this.gridEntryDataGridView_CellToolTipTextNeeded);
//
// removeGVColumn
//
this.removeGVColumn.DataPropertyName = "Remove";
this.removeGVColumn.FalseValue = "";
this.removeGVColumn.Frozen = true;
this.removeGVColumn.HeaderText = "Remove";
this.removeGVColumn.IndeterminateValue = "";
this.removeGVColumn.MinimumWidth = 60;
this.removeGVColumn.Name = "removeGVColumn";
this.removeGVColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.removeGVColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
this.removeGVColumn.ThreeState = true;
this.removeGVColumn.TrueValue = "";
this.removeGVColumn.Width = 60;
//
// liberateGVColumn
//
this.liberateGVColumn.DataPropertyName = "Liberate";
this.liberateGVColumn.HeaderText = "Liberate";
this.liberateGVColumn.Name = "liberateGVColumn";
this.liberateGVColumn.ReadOnly = true;
this.liberateGVColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.liberateGVColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
this.liberateGVColumn.Width = 75;
//
// coverGVColumn
//
this.coverGVColumn.DataPropertyName = "Cover";
this.coverGVColumn.HeaderText = "Cover";
this.coverGVColumn.Name = "coverGVColumn";
this.coverGVColumn.ReadOnly = true;
this.coverGVColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.coverGVColumn.ToolTipText = "Cover Art";
this.coverGVColumn.Width = 80;
//
// titleGVColumn
//
this.titleGVColumn.DataPropertyName = "Title";
this.titleGVColumn.HeaderText = "Title";
this.titleGVColumn.Name = "titleGVColumn";
this.titleGVColumn.ReadOnly = true;
this.titleGVColumn.Width = 200;
//
// authorsGVColumn
//
this.authorsGVColumn.DataPropertyName = "Authors";
this.authorsGVColumn.HeaderText = "Authors";
this.authorsGVColumn.Name = "authorsGVColumn";
this.authorsGVColumn.ReadOnly = true;
//
// narratorsGVColumn
//
this.narratorsGVColumn.DataPropertyName = "Narrators";
this.narratorsGVColumn.HeaderText = "Narrators";
this.narratorsGVColumn.Name = "narratorsGVColumn";
this.narratorsGVColumn.ReadOnly = true;
//
// lengthGVColumn
//
this.lengthGVColumn.DataPropertyName = "Length";
this.lengthGVColumn.HeaderText = "Length";
this.lengthGVColumn.Name = "lengthGVColumn";
this.lengthGVColumn.ReadOnly = true;
this.lengthGVColumn.ToolTipText = "Recording Length";
//
// seriesGVColumn
//
this.seriesGVColumn.DataPropertyName = "Series";
this.seriesGVColumn.HeaderText = "Series";
this.seriesGVColumn.Name = "seriesGVColumn";
this.seriesGVColumn.ReadOnly = true;
//
// descriptionGVColumn
//
this.descriptionGVColumn.DataPropertyName = "Description";
this.descriptionGVColumn.HeaderText = "Description";
this.descriptionGVColumn.Name = "descriptionGVColumn";
this.descriptionGVColumn.ReadOnly = true;
this.descriptionGVColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
//
// categoryGVColumn
//
this.categoryGVColumn.DataPropertyName = "Category";
this.categoryGVColumn.HeaderText = "Category";
this.categoryGVColumn.Name = "categoryGVColumn";
this.categoryGVColumn.ReadOnly = true;
//
// productRatingGVColumn
//
this.productRatingGVColumn.DataPropertyName = "ProductRating";
this.productRatingGVColumn.HeaderText = "Product Rating";
this.productRatingGVColumn.Name = "productRatingGVColumn";
this.productRatingGVColumn.ReadOnly = true;
this.productRatingGVColumn.Width = 108;
//
// purchaseDateGVColumn
//
this.purchaseDateGVColumn.DataPropertyName = "PurchaseDate";
this.purchaseDateGVColumn.HeaderText = "Purchase Date";
this.purchaseDateGVColumn.Name = "purchaseDateGVColumn";
this.purchaseDateGVColumn.ReadOnly = true;
//
// myRatingGVColumn
//
this.myRatingGVColumn.DataPropertyName = "MyRating";
this.myRatingGVColumn.HeaderText = "My Rating";
this.myRatingGVColumn.Name = "myRatingGVColumn";
this.myRatingGVColumn.ReadOnly = true;
this.myRatingGVColumn.Width = 108;
//
// miscGVColumn
//
this.miscGVColumn.DataPropertyName = "Misc";
this.miscGVColumn.HeaderText = "Misc";
this.miscGVColumn.Name = "miscGVColumn";
this.miscGVColumn.ReadOnly = true;
this.miscGVColumn.Width = 135;
//
// tagAndDetailsGVColumn
//
this.tagAndDetailsGVColumn.DataPropertyName = "DisplayTags";
this.tagAndDetailsGVColumn.HeaderText = "Tags and Details";
this.tagAndDetailsGVColumn.Name = "tagAndDetailsGVColumn";
this.tagAndDetailsGVColumn.ReadOnly = true;
this.tagAndDetailsGVColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.tagAndDetailsGVColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
//
// showHideColumnsContextMenuStrip
//
this.showHideColumnsContextMenuStrip.Name = "contextMenuStrip1";
this.showHideColumnsContextMenuStrip.Size = new System.Drawing.Size(181, 26);
//
// syncBindingSource
//
this.syncBindingSource.DataSource = typeof(LibationWinForms.GridView.GridEntry);
//
// ProductsGrid
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.AutoScroll = true;
this.Controls.Add(this.gridEntryDataGridView);
this.Name = "ProductsGrid";
this.Size = new System.Drawing.Size(1570, 380);
this.Load += new System.EventHandler(this.ProductsGrid_Load);
((System.ComponentModel.ISupportInitialize)(this.gridEntryDataGridView)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.syncBindingSource)).EndInit();
this.ResumeLayout(false);
this.gridEntryDataGridView.ContextMenuStrip = this.showHideColumnsContextMenuStrip;
this.gridEntryDataGridView.DataSource = this.syncBindingSource;
dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Window;
dataGridViewCellStyle1.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.ControlText;
dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.gridEntryDataGridView.DefaultCellStyle = dataGridViewCellStyle1;
this.gridEntryDataGridView.Dock = System.Windows.Forms.DockStyle.Fill;
this.gridEntryDataGridView.EditMode = System.Windows.Forms.DataGridViewEditMode.EditOnEnter;
this.gridEntryDataGridView.Location = new System.Drawing.Point(0, 0);
this.gridEntryDataGridView.Name = "gridEntryDataGridView";
this.gridEntryDataGridView.RowHeadersVisible = false;
this.gridEntryDataGridView.RowTemplate.Height = 82;
this.gridEntryDataGridView.Size = new System.Drawing.Size(1570, 380);
this.gridEntryDataGridView.TabIndex = 0;
this.gridEntryDataGridView.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.DataGridView_CellContentClick);
this.gridEntryDataGridView.CellContextMenuStripNeeded += new System.Windows.Forms.DataGridViewCellContextMenuStripNeededEventHandler(this.gridEntryDataGridView_CellContextMenuStripNeeded);
this.gridEntryDataGridView.CellToolTipTextNeeded += new System.Windows.Forms.DataGridViewCellToolTipTextNeededEventHandler(this.gridEntryDataGridView_CellToolTipTextNeeded);
//
// removeGVColumn
//
this.removeGVColumn.DataPropertyName = "Remove";
this.removeGVColumn.FalseValue = "";
this.removeGVColumn.Frozen = true;
this.removeGVColumn.HeaderText = "Remove";
this.removeGVColumn.IndeterminateValue = "";
this.removeGVColumn.MinimumWidth = 60;
this.removeGVColumn.Name = "removeGVColumn";
this.removeGVColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.removeGVColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
this.removeGVColumn.ThreeState = true;
this.removeGVColumn.TrueValue = "";
this.removeGVColumn.Width = 60;
//
// liberateGVColumn
//
this.liberateGVColumn.DataPropertyName = "Liberate";
this.liberateGVColumn.HeaderText = "Liberate";
this.liberateGVColumn.Name = "liberateGVColumn";
this.liberateGVColumn.ReadOnly = true;
this.liberateGVColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.liberateGVColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
this.liberateGVColumn.Width = 75;
//
// coverGVColumn
//
this.coverGVColumn.DataPropertyName = "Cover";
this.coverGVColumn.HeaderText = "Cover";
this.coverGVColumn.Name = "coverGVColumn";
this.coverGVColumn.ReadOnly = true;
this.coverGVColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.coverGVColumn.ToolTipText = "Cover Art";
this.coverGVColumn.Width = 80;
//
// titleGVColumn
//
this.titleGVColumn.DataPropertyName = "Title";
this.titleGVColumn.HeaderText = "Title";
this.titleGVColumn.Name = "titleGVColumn";
this.titleGVColumn.ReadOnly = true;
this.titleGVColumn.Width = 200;
//
// authorsGVColumn
//
this.authorsGVColumn.DataPropertyName = "Authors";
this.authorsGVColumn.HeaderText = "Authors";
this.authorsGVColumn.Name = "authorsGVColumn";
this.authorsGVColumn.ReadOnly = true;
//
// narratorsGVColumn
//
this.narratorsGVColumn.DataPropertyName = "Narrators";
this.narratorsGVColumn.HeaderText = "Narrators";
this.narratorsGVColumn.Name = "narratorsGVColumn";
this.narratorsGVColumn.ReadOnly = true;
//
// lengthGVColumn
//
this.lengthGVColumn.DataPropertyName = "Length";
this.lengthGVColumn.HeaderText = "Length";
this.lengthGVColumn.Name = "lengthGVColumn";
this.lengthGVColumn.ReadOnly = true;
this.lengthGVColumn.ToolTipText = "Recording Length";
//
// seriesGVColumn
//
this.seriesGVColumn.DataPropertyName = "Series";
this.seriesGVColumn.HeaderText = "Series";
this.seriesGVColumn.Name = "seriesGVColumn";
this.seriesGVColumn.ReadOnly = true;
//
// descriptionGVColumn
//
this.descriptionGVColumn.DataPropertyName = "Description";
this.descriptionGVColumn.HeaderText = "Description";
this.descriptionGVColumn.Name = "descriptionGVColumn";
this.descriptionGVColumn.ReadOnly = true;
this.descriptionGVColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
//
// categoryGVColumn
//
this.categoryGVColumn.DataPropertyName = "Category";
this.categoryGVColumn.HeaderText = "Category";
this.categoryGVColumn.Name = "categoryGVColumn";
this.categoryGVColumn.ReadOnly = true;
//
// productRatingGVColumn
//
this.productRatingGVColumn.DataPropertyName = "ProductRating";
this.productRatingGVColumn.HeaderText = "Product Rating";
this.productRatingGVColumn.Name = "productRatingGVColumn";
this.productRatingGVColumn.ReadOnly = true;
this.productRatingGVColumn.Width = 108;
//
// purchaseDateGVColumn
//
this.purchaseDateGVColumn.DataPropertyName = "PurchaseDate";
this.purchaseDateGVColumn.HeaderText = "Purchase Date";
this.purchaseDateGVColumn.Name = "purchaseDateGVColumn";
this.purchaseDateGVColumn.ReadOnly = true;
//
// myRatingGVColumn
//
this.myRatingGVColumn.DataPropertyName = "MyRating";
this.myRatingGVColumn.HeaderText = "My Rating";
this.myRatingGVColumn.Name = "myRatingGVColumn";
this.myRatingGVColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
this.myRatingGVColumn.Width = 108;
//
// miscGVColumn
//
this.miscGVColumn.DataPropertyName = "Misc";
this.miscGVColumn.HeaderText = "Misc";
this.miscGVColumn.Name = "miscGVColumn";
this.miscGVColumn.ReadOnly = true;
this.miscGVColumn.Width = 135;
//
// tagAndDetailsGVColumn
//
this.tagAndDetailsGVColumn.DataPropertyName = "DisplayTags";
this.tagAndDetailsGVColumn.HeaderText = "Tags and Details";
this.tagAndDetailsGVColumn.Name = "tagAndDetailsGVColumn";
this.tagAndDetailsGVColumn.ReadOnly = true;
this.tagAndDetailsGVColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
this.tagAndDetailsGVColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
//
// showHideColumnsContextMenuStrip
//
this.showHideColumnsContextMenuStrip.Name = "contextMenuStrip1";
this.showHideColumnsContextMenuStrip.Size = new System.Drawing.Size(61, 4);
//
// syncBindingSource
//
this.syncBindingSource.DataSource = typeof(LibationWinForms.GridView.GridEntry);
//
// ProductsGrid
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.AutoScroll = true;
this.Controls.Add(this.gridEntryDataGridView);
this.Name = "ProductsGrid";
this.Size = new System.Drawing.Size(1570, 380);
this.Load += new System.EventHandler(this.ProductsGrid_Load);
((System.ComponentModel.ISupportInitialize)(this.gridEntryDataGridView)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.syncBindingSource)).EndInit();
this.ResumeLayout(false);
}
@ -265,7 +266,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;
}

View File

@ -120,7 +120,7 @@ namespace LibationWinForms.GridView
try
{
var dgv = (DataGridView)sender;
var text = dgv[e.ColumnIndex, e.RowIndex].Value.ToString();
var text = dgv[e.ColumnIndex, e.RowIndex].FormattedValue.ToString();
InteropFactory.Create().CopyTextToClipboard(text);
}
catch { }

View File

@ -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();

View File

@ -1,39 +0,0 @@
namespace WindowsConfigApp
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Text = "Form1";
}
#endregion
}
}

View File

@ -1,10 +0,0 @@
namespace WindowsConfigApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
}

View File

@ -7,7 +7,6 @@ namespace WindowsConfigApp
public override Type InteropFunctionsType => typeof(WinInterop);
public override Type[] ReferencedTypes => new Type[]
{
typeof(Form1),
typeof(Bitmap),
typeof(Dinah.Core.WindowsDesktop.GitClient),
typeof(Accessibility.IAccIdentity),