Merge pull request #432 from Mbucari/master
Add ability for users to edit book ratings from the main grid
This commit is contained in:
commit
f4189bf409
@ -13,7 +13,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AAXClean.Codecs" Version="0.2.14" />
|
||||
<PackageReference Include="AAXClean.Codecs" Version="0.2.15" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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 >
|
||||
@ -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)
|
||||
{
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Source/LibationAvalonia/Controls/DataGridMyRatingColumn.cs
Normal file
60
Source/LibationAvalonia/Controls/DataGridMyRatingColumn.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Source/LibationAvalonia/Controls/MyRatingCellEditor.axaml
Normal file
54
Source/LibationAvalonia/Controls/MyRatingCellEditor.axaml
Normal 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>
|
||||
108
Source/LibationAvalonia/Controls/MyRatingCellEditor.axaml.cs
Normal file
108
Source/LibationAvalonia/Controls/MyRatingCellEditor.axaml.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -13,8 +13,6 @@ namespace LibationAvalonia
|
||||
{
|
||||
static class Program
|
||||
{
|
||||
private static string EXE_DIR = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
static void Main()
|
||||
{
|
||||
//***********************************************//
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
Rating" CanUserSort="True" SortMemberPath="ProductRating" ClipboardContentBinding="{Binding ProductRating}">
|
||||
<controls:DataGridTemplateColumnExt Width="115" Header="Product
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>
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
368
Source/LibationWinForms/GridView/MyRatingCellEditor.Designer.cs
generated
Normal file
368
Source/LibationWinForms/GridView/MyRatingCellEditor.Designer.cs
generated
Normal 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;
|
||||
}
|
||||
}
|
||||
168
Source/LibationWinForms/GridView/MyRatingCellEditor.cs
Normal file
168
Source/LibationWinForms/GridView/MyRatingCellEditor.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
126
Source/LibationWinForms/GridView/MyRatingCellEditor.resx
Normal file
126
Source/LibationWinForms/GridView/MyRatingCellEditor.resx
Normal 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>
|
||||
62
Source/LibationWinForms/GridView/MyRatingGridViewColumn.cs
Normal file
62
Source/LibationWinForms/GridView/MyRatingGridViewColumn.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 { }
|
||||
|
||||
@ -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();
|
||||
|
||||
39
Source/LoadByOS/WindowsConfigApp/Form1.Designer.cs
generated
39
Source/LoadByOS/WindowsConfigApp/Form1.Designer.cs
generated
@ -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
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
namespace WindowsConfigApp
|
||||
{
|
||||
public partial class Form1 : Form
|
||||
{
|
||||
public Form1()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user