Code Cleanup

This commit is contained in:
Michael Bucari-Tovo 2021-08-10 16:10:53 -06:00
parent 95766a43c5
commit ef35c2aee9
41 changed files with 1451 additions and 1818 deletions

View File

@ -13,7 +13,7 @@
<!-- <PublishSingleFile>true</PublishSingleFile> --> <!-- <PublishSingleFile>true</PublishSingleFile> -->
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<Version>5.4.9.120</Version> <Version>5.4.9.140</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

View File

@ -4,34 +4,34 @@ using System.Threading;
namespace LibationWinForms namespace LibationWinForms
{ {
public abstract class AsyncNotifyPropertyChanged : INotifyPropertyChanged public abstract class AsyncNotifyPropertyChanged : INotifyPropertyChanged
{ {
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId; private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId;
private bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId; private bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId;
private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current; private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{ {
var propertyChangedArgs = new PropertyChangedEventArgs(propertyName); var propertyChangedArgs = new PropertyChangedEventArgs(propertyName);
if (InvokeRequired) if (InvokeRequired)
{ {
SyncContext.Post( SyncContext.Post(
PostPropertyChangedCallback, PostPropertyChangedCallback,
new AsyncCompletedEventArgs(null, false, propertyChangedArgs)); new AsyncCompletedEventArgs(null, false, propertyChangedArgs));
} }
else else
{ {
OnPropertyChanged(propertyChangedArgs); OnPropertyChanged(propertyChangedArgs);
} }
} }
private void PostPropertyChangedCallback(object asyncArgs) private void PostPropertyChangedCallback(object asyncArgs)
{ {
var e = asyncArgs as AsyncCompletedEventArgs; var e = asyncArgs as AsyncCompletedEventArgs;
OnPropertyChanged(e.UserState as PropertyChangedEventArgs); OnPropertyChanged(e.UserState as PropertyChangedEventArgs);
} }
private void OnPropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e); private void OnPropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);
} }
} }

View File

@ -1,34 +1,34 @@
using System; using Dinah.Core.Windows.Forms;
using System;
using System.Windows.Forms; using System.Windows.Forms;
using Dinah.Core.Windows.Forms;
namespace LibationWinForms.BookLiberation namespace LibationWinForms.BookLiberation
{ {
public partial class AutomatedBackupsForm : Form public partial class AutomatedBackupsForm : Form
{ {
public bool KeepGoingChecked => keepGoingCb.Checked; public bool KeepGoingChecked => keepGoingCb.Checked;
public bool KeepGoing => keepGoingCb.Enabled && keepGoingCb.Checked; public bool KeepGoing => keepGoingCb.Enabled && keepGoingCb.Checked;
public AutomatedBackupsForm() public AutomatedBackupsForm()
{ {
InitializeComponent(); InitializeComponent();
} }
public void WriteLine(string text) public void WriteLine(string text)
{ {
if (!IsDisposed) if (!IsDisposed)
logTb.UIThread(() => logTb.AppendText($"{DateTime.Now} {text}{Environment.NewLine}")); logTb.UIThread(() => logTb.AppendText($"{DateTime.Now} {text}{Environment.NewLine}"));
} }
public void FinalizeUI() public void FinalizeUI()
{ {
keepGoingCb.Enabled = false; keepGoingCb.Enabled = false;
if (!IsDisposed) if (!IsDisposed)
logTb.AppendText(""); logTb.AppendText("");
} }
private void AutomatedBackupsForm_FormClosing(object sender, FormClosingEventArgs e) => keepGoingCb.Checked = false; private void AutomatedBackupsForm_FormClosing(object sender, FormClosingEventArgs e) => keepGoingCb.Checked = false;
} }
} }

View File

@ -1,54 +1,54 @@
using System; using Dinah.Core.Windows.Forms;
using System;
using System.Windows.Forms; using System.Windows.Forms;
using Dinah.Core.Windows.Forms;
namespace LibationWinForms.BookLiberation namespace LibationWinForms.BookLiberation
{ {
public partial class DecryptForm : Form public partial class DecryptForm : Form
{ {
public DecryptForm() => InitializeComponent(); public DecryptForm() => InitializeComponent();
// book info // book info
private string title; private string title;
private string authorNames; private string authorNames;
private string narratorNames; private string narratorNames;
public void SetTitle(string actionName, string title) public void SetTitle(string actionName, string title)
{ {
this.UIThread(() => this.Text = actionName + " " + title); this.UIThread(() => this.Text = actionName + " " + title);
this.title = title; this.title = title;
updateBookInfo(); updateBookInfo();
} }
public void SetAuthorNames(string authorNames) public void SetAuthorNames(string authorNames)
{ {
this.authorNames = authorNames; this.authorNames = authorNames;
updateBookInfo(); updateBookInfo();
} }
public void SetNarratorNames(string narratorNames) public void SetNarratorNames(string narratorNames)
{ {
this.narratorNames = narratorNames; this.narratorNames = narratorNames;
updateBookInfo(); updateBookInfo();
} }
// thread-safe UI updates // thread-safe UI updates
private void updateBookInfo() private void updateBookInfo()
=> bookInfoLbl.UIThread(() => bookInfoLbl.Text = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}"); => bookInfoLbl.UIThread(() => bookInfoLbl.Text = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}");
public void SetCoverImage(System.Drawing.Image coverImage) public void SetCoverImage(System.Drawing.Image coverImage)
=> pictureBox1.UIThread(() => pictureBox1.Image = coverImage); => pictureBox1.UIThread(() => pictureBox1.Image = coverImage);
public void UpdateProgress(int percentage) public void UpdateProgress(int percentage)
{ {
if (percentage == 0) if (percentage == 0)
updateRemainingTime(0); updateRemainingTime(0);
else else
progressBar1.UIThread(() => progressBar1.Value = percentage); progressBar1.UIThread(() => progressBar1.Value = percentage);
} }
public void UpdateRemainingTime(TimeSpan remaining) public void UpdateRemainingTime(TimeSpan remaining)
=> updateRemainingTime((int)remaining.TotalSeconds); => updateRemainingTime((int)remaining.TotalSeconds);
private void updateRemainingTime(int remaining) private void updateRemainingTime(int remaining)
=> remainingTimeLbl.UIThread(() => remainingTimeLbl.Text = $"ETA:\r\n{remaining} sec"); => remainingTimeLbl.UIThread(() => remainingTimeLbl.Text = $"ETA:\r\n{remaining} sec");
} }
} }

View File

@ -1,59 +1,59 @@
using System; using Dinah.Core.Windows.Forms;
using System;
using System.Windows.Forms; using System.Windows.Forms;
using Dinah.Core.Windows.Forms;
namespace LibationWinForms.BookLiberation namespace LibationWinForms.BookLiberation
{ {
public partial class DownloadForm : Form public partial class DownloadForm : Form
{ {
public DownloadForm() public DownloadForm()
{ {
InitializeComponent(); InitializeComponent();
progressLbl.Text = ""; progressLbl.Text = "";
filenameLbl.Text = ""; filenameLbl.Text = "";
} }
// thread-safe UI updates // thread-safe UI updates
public void UpdateFilename(string title) => filenameLbl.UIThread(() => filenameLbl.Text = title); public void UpdateFilename(string title) => filenameLbl.UIThread(() => filenameLbl.Text = title);
public void DownloadProgressChanged(long BytesReceived, long? TotalBytesToReceive) public void DownloadProgressChanged(long BytesReceived, long? TotalBytesToReceive)
{ {
// this won't happen with download file. it will happen with download string // this won't happen with download file. it will happen with download string
if (!TotalBytesToReceive.HasValue || TotalBytesToReceive.Value <= 0) if (!TotalBytesToReceive.HasValue || TotalBytesToReceive.Value <= 0)
return; return;
progressLbl.UIThread(() => progressLbl.Text = $"{BytesReceived:#,##0} of {TotalBytesToReceive.Value:#,##0}"); progressLbl.UIThread(() => progressLbl.Text = $"{BytesReceived:#,##0} of {TotalBytesToReceive.Value:#,##0}");
var d = double.Parse(BytesReceived.ToString()) / double.Parse(TotalBytesToReceive.Value.ToString()) * 100.0; var d = double.Parse(BytesReceived.ToString()) / double.Parse(TotalBytesToReceive.Value.ToString()) * 100.0;
var i = int.Parse(Math.Truncate(d).ToString()); var i = int.Parse(Math.Truncate(d).ToString());
progressBar1.UIThread(() => progressBar1.Value = i); progressBar1.UIThread(() => progressBar1.Value = i);
lastDownloadProgress = DateTime.Now; lastDownloadProgress = DateTime.Now;
} }
#region timer #region timer
private Timer timer { get; } = new Timer { Interval = 1000 }; private Timer timer { get; } = new Timer { Interval = 1000 };
private void DownloadForm_Load(object sender, EventArgs e) private void DownloadForm_Load(object sender, EventArgs e)
{ {
timer.Tick += new EventHandler(timer_Tick); timer.Tick += new EventHandler(timer_Tick);
timer.Start(); timer.Start();
} }
private DateTime lastDownloadProgress = DateTime.Now; private DateTime lastDownloadProgress = DateTime.Now;
private void timer_Tick(object sender, EventArgs e) private void timer_Tick(object sender, EventArgs e)
{ {
// if no update in the last 30 seconds, display frozen label // if no update in the last 30 seconds, display frozen label
lastUpdateLbl.UIThread(() => lastUpdateLbl.Visible = lastDownloadProgress.AddSeconds(30) < DateTime.Now); lastUpdateLbl.UIThread(() => lastUpdateLbl.Visible = lastDownloadProgress.AddSeconds(30) < DateTime.Now);
if (lastUpdateLbl.Visible) if (lastUpdateLbl.Visible)
{ {
var diff = DateTime.Now - lastDownloadProgress; var diff = DateTime.Now - lastDownloadProgress;
var min = (int)diff.TotalMinutes; var min = (int)diff.TotalMinutes;
var minText = min > 0 ? $"{min}min " : ""; var minText = min > 0 ? $"{min}min " : "";
lastUpdateLbl.UIThread(() => lastUpdateLbl.Text = $"Frozen? Last download activity: {minText}{diff.Seconds}sec ago"); lastUpdateLbl.UIThread(() => lastUpdateLbl.Text = $"Frozen? Last download activity: {minText}{diff.Seconds}sec ago");
} }
} }
private void DownloadForm_FormClosing(object sender, FormClosingEventArgs e) => timer.Stop(); private void DownloadForm_FormClosing(object sender, FormClosingEventArgs e) => timer.Stop();
#endregion #endregion
} }
} }

View File

@ -1,21 +1,21 @@
using System; using AudibleApi;
using InternalUtilities;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using AudibleApi;
using InternalUtilities;
namespace LibationWinForms.Dialogs namespace LibationWinForms.Dialogs
{ {
public partial class AccountsDialog : Form public partial class AccountsDialog : Form
{ {
const string COL_Delete = nameof(DeleteAccount); private const string COL_Delete = nameof(DeleteAccount);
const string COL_LibraryScan = nameof(LibraryScan); private const string COL_LibraryScan = nameof(LibraryScan);
const string COL_AccountId = nameof(AccountId); private const string COL_AccountId = nameof(AccountId);
const string COL_AccountName = nameof(AccountName); private const string COL_AccountName = nameof(AccountName);
const string COL_Locale = nameof(Locale); private const string COL_Locale = nameof(Locale);
Form1 _parent { get; } private Form1 _parent { get; }
public AccountsDialog(Form1 parent) public AccountsDialog(Form1 parent)
{ {
@ -100,7 +100,7 @@ namespace LibationWinForms.Dialogs
this.Close(); this.Close();
} }
class AccountDto private class AccountDto
{ {
public string AccountId { get; set; } public string AccountId { get; set; }
public string AccountName { get; set; } public string AccountName { get; set; }

View File

@ -1,64 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <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:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">

View File

@ -3,25 +3,25 @@ using System.Windows.Forms;
namespace LibationWinForms.Dialogs namespace LibationWinForms.Dialogs
{ {
public partial class BookDetailsDialog : Form public partial class BookDetailsDialog : Form
{ {
public string NewTags { get; private set; } public string NewTags { get; private set; }
public BookDetailsDialog() public BookDetailsDialog()
{ {
InitializeComponent(); InitializeComponent();
} }
public BookDetailsDialog(string title, string rawTags) : this() public BookDetailsDialog(string title, string rawTags) : this()
{ {
this.Text = $"Edit Tags - {title}"; this.Text = $"Edit Tags - {title}";
this.newTagsTb.Text = rawTags; this.newTagsTb.Text = rawTags;
} }
private void SaveBtn_Click(object sender, EventArgs e) private void SaveBtn_Click(object sender, EventArgs e)
{ {
NewTags = this.newTagsTb.Text; NewTags = this.newTagsTb.Text;
DialogResult = DialogResult.OK; DialogResult = DialogResult.OK;
} }
} }
} }

View File

@ -1,64 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <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:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">

View File

@ -1,9 +1,7 @@
using System; using FileManager;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using Dinah.Core;
using FileManager;
namespace LibationWinForms.Dialogs namespace LibationWinForms.Dialogs
{ {

View File

@ -1,4 +1,5 @@
<root> <?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <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:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">

View File

@ -1,9 +1,9 @@
using System; using Dinah.Core;
using FileManager;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using Dinah.Core;
using FileManager;
namespace LibationWinForms.Dialogs namespace LibationWinForms.Dialogs
{ {
@ -13,7 +13,7 @@ namespace LibationWinForms.Dialogs
{ {
public string Description { get; } public string Description { get; }
public Configuration.KnownDirectories Value { get; } public Configuration.KnownDirectories Value { get; }
private DirectorySelectControl _parentControl; private readonly DirectorySelectControl _parentControl;
public string FullPath => _parentControl.AddSubDirectoryToPath(Configuration.GetKnownDirectoryPath(Value)); public string FullPath => _parentControl.AddSubDirectoryToPath(Configuration.GetKnownDirectoryPath(Value));

View File

@ -1,4 +1,5 @@
<root> <?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <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:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">

View File

@ -1,101 +1,100 @@
using System; using FileManager;
using System;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using FileManager;
namespace LibationWinForms.Dialogs namespace LibationWinForms.Dialogs
{ {
public partial class EditQuickFilters : Form public partial class EditQuickFilters : Form
{ {
const string BLACK_UP_POINTING_TRIANGLE = "\u25B2"; private const string BLACK_UP_POINTING_TRIANGLE = "\u25B2";
const string BLACK_DOWN_POINTING_TRIANGLE = "\u25BC"; private const string BLACK_DOWN_POINTING_TRIANGLE = "\u25BC";
private const string COL_Original = nameof(Original);
private const string COL_Delete = nameof(Delete);
private const string COL_Filter = nameof(Filter);
private const string COL_MoveUp = nameof(MoveUp);
private const string COL_MoveDown = nameof(MoveDown);
const string COL_Original = nameof(Original); private Form1 _parent { get; }
const string COL_Delete = nameof(Delete);
const string COL_Filter = nameof(Filter);
const string COL_MoveUp = nameof(MoveUp);
const string COL_MoveDown = nameof(MoveDown);
Form1 _parent { get; } public EditQuickFilters(Form1 parent)
{
_parent = parent;
public EditQuickFilters(Form1 parent) InitializeComponent();
{
_parent = parent;
InitializeComponent(); dataGridView1.Columns[COL_Filter].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridView1.Columns[COL_Filter].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; populateGridValues();
}
populateGridValues(); private void populateGridValues()
} {
var filters = QuickFilters.Filters;
if (!filters.Any())
return;
private void populateGridValues() foreach (var filter in filters)
{ dataGridView1.Rows.Add(filter, "X", filter, BLACK_UP_POINTING_TRIANGLE, BLACK_DOWN_POINTING_TRIANGLE);
var filters = QuickFilters.Filters; }
if (!filters.Any())
return;
foreach (var filter in filters) private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
dataGridView1.Rows.Add(filter, "X", filter, BLACK_UP_POINTING_TRIANGLE, BLACK_DOWN_POINTING_TRIANGLE); {
} e.Row.Cells[COL_Delete].Value = "X";
e.Row.Cells[COL_MoveUp].Value = BLACK_UP_POINTING_TRIANGLE;
e.Row.Cells[COL_MoveDown].Value = BLACK_DOWN_POINTING_TRIANGLE;
}
private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e) private void saveBtn_Click(object sender, EventArgs e)
{ {
e.Row.Cells[COL_Delete].Value = "X"; var list = dataGridView1.Rows
e.Row.Cells[COL_MoveUp].Value = BLACK_UP_POINTING_TRIANGLE; .OfType<DataGridViewRow>()
e.Row.Cells[COL_MoveDown].Value = BLACK_DOWN_POINTING_TRIANGLE; .Select(r => r.Cells[COL_Filter].Value?.ToString())
} .ToList();
QuickFilters.ReplaceAll(list);
private void saveBtn_Click(object sender, EventArgs e) _parent.UpdateFilterDropDown();
{ this.DialogResult = DialogResult.OK;
var list = dataGridView1.Rows this.Close();
.OfType<DataGridViewRow>() }
.Select(r => r.Cells[COL_Filter].Value?.ToString())
.ToList();
QuickFilters.ReplaceAll(list);
_parent.UpdateFilterDropDown(); private void cancelBtn_Click(object sender, EventArgs e)
this.DialogResult = DialogResult.OK; {
this.Close(); this.DialogResult = DialogResult.Cancel;
} this.Close();
}
private void cancelBtn_Click(object sender, EventArgs e) private void DataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
{ {
this.DialogResult = DialogResult.Cancel; var dgv = (DataGridView)sender;
this.Close();
}
private void DataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e) var col = dgv.Columns[e.ColumnIndex];
{ if (col is DataGridViewButtonColumn && e.RowIndex >= 0)
var dgv = (DataGridView)sender; {
var row = dgv.Rows[e.RowIndex];
var col = dgv.Columns[e.ColumnIndex]; switch (col.Name)
if (col is DataGridViewButtonColumn && e.RowIndex >= 0) {
{ case COL_Delete:
var row = dgv.Rows[e.RowIndex]; // if final/edit row: do nothing
switch (col.Name) if (e.RowIndex < dgv.RowCount - 1)
{ dgv.Rows.Remove(row);
case COL_Delete: break;
// if final/edit row: do nothing case COL_MoveUp:
if (e.RowIndex < dgv.RowCount - 1) // if top: do nothing
dgv.Rows.Remove(row); if (e.RowIndex < 1)
break; break;
case COL_MoveUp: dgv.Rows.Remove(row);
// if top: do nothing dgv.Rows.Insert(e.RowIndex - 1, row);
if (e.RowIndex < 1) break;
break; case COL_MoveDown:
dgv.Rows.Remove(row); // if final/edit row or bottom filter row: do nothing
dgv.Rows.Insert(e.RowIndex - 1, row); if (e.RowIndex >= dgv.RowCount - 2)
break; break;
case COL_MoveDown: dgv.Rows.Remove(row);
// if final/edit row or bottom filter row: do nothing dgv.Rows.Insert(e.RowIndex + 1, row);
if (e.RowIndex >= dgv.RowCount - 2) break;
break; }
dgv.Rows.Remove(row); }
dgv.Rows.Insert(e.RowIndex + 1, row); }
break; }
}
}
}
}
} }

View File

@ -1,64 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <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:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">

View File

@ -1,8 +1,8 @@
using System; using ApplicationServices;
using System.Windows.Forms;
using ApplicationServices;
using InternalUtilities; using InternalUtilities;
using LibationWinForms.Login; using LibationWinForms.Login;
using System;
using System.Windows.Forms;
namespace LibationWinForms.Dialogs namespace LibationWinForms.Dialogs
{ {

View File

@ -1,64 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <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:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">

View File

@ -1,6 +1,6 @@
using System; using FileManager;
using System;
using System.Windows.Forms; using System.Windows.Forms;
using FileManager;
namespace LibationWinForms.Dialogs namespace LibationWinForms.Dialogs
{ {

View File

@ -1,7 +1,7 @@
using System; using Dinah.Core;
using System.Windows.Forms;
using Dinah.Core;
using InternalUtilities; using InternalUtilities;
using System;
using System.Windows.Forms;
namespace LibationWinForms.Dialogs.Login namespace LibationWinForms.Dialogs.Login
{ {

View File

@ -1,11 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
namespace LibationWinForms.Dialogs.Login namespace LibationWinForms.Dialogs.Login
@ -14,7 +8,7 @@ namespace LibationWinForms.Dialogs.Login
{ {
private RadioButton[] radioButtons { get; } private RadioButton[] radioButtons { get; }
AudibleApi.MfaConfig _mfaConfig { get; } private AudibleApi.MfaConfig _mfaConfig { get; }
public MfaDialog(AudibleApi.MfaConfig mfaConfig) public MfaDialog(AudibleApi.MfaConfig mfaConfig)
{ {
@ -32,7 +26,8 @@ namespace LibationWinForms.Dialogs.Login
setRadioButton(1, this.radioButton2); setRadioButton(1, this.radioButton2);
setRadioButton(2, this.radioButton3); setRadioButton(2, this.radioButton3);
Serilog.Log.Logger.Information("{@DebugInfo}", new { Serilog.Log.Logger.Information("{@DebugInfo}", new
{
paramButtonCount = mfaConfig.Buttons.Count, paramButtonCount = mfaConfig.Buttons.Count,
visibleRadioButtonCount = radioButtons.Count(rb => rb.Visible) visibleRadioButtonCount = radioButtons.Count(rb => rb.Visible)
}); });
@ -65,7 +60,8 @@ namespace LibationWinForms.Dialogs.Login
{ {
var selected = radioButtons.FirstOrDefault(rb => rb.Checked); var selected = radioButtons.FirstOrDefault(rb => rb.Checked);
Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new
{
rb1_visible = radioButton1.Visible, rb1_visible = radioButton1.Visible,
rb1_checked = radioButton1.Checked, rb1_checked = radioButton1.Checked,

View File

@ -1,5 +1,4 @@
using System; using AudibleApi;
using AudibleApi;
using InternalUtilities; using InternalUtilities;
using LibationWinForms.Dialogs.Login; using LibationWinForms.Dialogs.Login;

View File

@ -1,7 +1,7 @@
using System; using Dinah.Core;
using System;
using System.Drawing; using System.Drawing;
using System.Windows.Forms; using System.Windows.Forms;
using Dinah.Core;
namespace LibationWinForms.Dialogs namespace LibationWinForms.Dialogs
{ {

View File

@ -3,27 +3,27 @@ using DataLayer;
using InternalUtilities; using InternalUtilities;
using LibationWinForms.Login; using LibationWinForms.Login;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Data; using System.Data;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using System.Collections;
namespace LibationWinForms.Dialogs namespace LibationWinForms.Dialogs
{ {
public partial class RemoveBooksDialog : Form public partial class RemoveBooksDialog : Form
{ {
public bool BooksRemoved { get; private set; } public bool BooksRemoved { get; private set; }
private Account[] _accounts { get; } private Account[] _accounts { get; }
private List<LibraryBook> _libraryBooks; private readonly List<LibraryBook> _libraryBooks;
private SortableBindingList2<RemovableGridEntry> _removableGridEntries; private readonly SortableBindingList2<RemovableGridEntry> _removableGridEntries;
private string _labelFormat; private readonly string _labelFormat;
private int SelectedCount => SelectedEntries?.Count() ?? 0; private int SelectedCount => SelectedEntries?.Count() ?? 0;
private IEnumerable<RemovableGridEntry> SelectedEntries => _removableGridEntries?.Where(b => b.Remove); private IEnumerable<RemovableGridEntry> SelectedEntries => _removableGridEntries?.Where(b => b.Remove);
public RemoveBooksDialog(params Account[] accounts) public RemoveBooksDialog(params Account[] accounts)
{ {
_libraryBooks = DbContexts.GetContext().GetLibrary_Flat_NoTracking(); _libraryBooks = DbContexts.GetContext().GetLibrary_Flat_NoTracking();
_accounts = accounts; _accounts = accounts;
@ -32,8 +32,8 @@ namespace LibationWinForms.Dialogs
_labelFormat = label1.Text; _labelFormat = label1.Text;
_dataGridView.CellContentClick += (s, e) => _dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit); _dataGridView.CellContentClick += (s, e) => _dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
_dataGridView.CellValueChanged += DataGridView1_CellValueChanged; _dataGridView.CellValueChanged += DataGridView1_CellValueChanged;
_dataGridView.BindingContextChanged += (s, e) => UpdateSelection(); _dataGridView.BindingContextChanged += (s, e) => UpdateSelection();
var orderedGridEntries = _libraryBooks var orderedGridEntries = _libraryBooks
.Select(lb => new RemovableGridEntry(lb)) .Select(lb => new RemovableGridEntry(lb))
@ -46,7 +46,7 @@ namespace LibationWinForms.Dialogs
_dataGridView.Enabled = false; _dataGridView.Enabled = false;
} }
private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{ {
if (e.ColumnIndex == 0) if (e.ColumnIndex == 0)
UpdateSelection(); UpdateSelection();
@ -78,9 +78,9 @@ namespace LibationWinForms.Dialogs
ex); ex);
} }
finally finally
{ {
_dataGridView.Enabled = true; _dataGridView.Enabled = true;
} }
} }
private void btnRemoveBooks_Click(object sender, EventArgs e) private void btnRemoveBooks_Click(object sender, EventArgs e)
@ -122,15 +122,15 @@ namespace LibationWinForms.Dialogs
} }
} }
private void UpdateSelection() private void UpdateSelection()
{ {
_dataGridView.Sort(_dataGridView.Columns[0], ListSortDirection.Descending); _dataGridView.Sort(_dataGridView.Columns[0], ListSortDirection.Descending);
var selectedCount = SelectedCount; var selectedCount = SelectedCount;
label1.Text = string.Format(_labelFormat, selectedCount, selectedCount != 1 ? "s" : string.Empty); label1.Text = string.Format(_labelFormat, selectedCount, selectedCount != 1 ? "s" : string.Empty);
btnRemoveBooks.Enabled = selectedCount > 0; btnRemoveBooks.Enabled = selectedCount > 0;
} }
} }
internal class RemovableGridEntry : GridEntry internal class RemovableGridEntry : GridEntry
{ {
private static readonly IComparer BoolComparer = new ObjectComparer<bool>(); private static readonly IComparer BoolComparer = new ObjectComparer<bool>();
@ -153,18 +153,18 @@ namespace LibationWinForms.Dialogs
} }
} }
public override object GetMemberValue(string memberName) public override object GetMemberValue(string memberName)
{ {
if (memberName == nameof(Remove)) if (memberName == nameof(Remove))
return Remove; return Remove;
return base.GetMemberValue(memberName); return base.GetMemberValue(memberName);
} }
public override IComparer GetMemberComparer(Type memberType) public override IComparer GetMemberComparer(Type memberType)
{ {
if (memberType == typeof(bool)) if (memberType == typeof(bool))
return BoolComparer; return BoolComparer;
return base.GetMemberComparer(memberType); return base.GetMemberComparer(memberType);
} }
} }
} }

View File

@ -1,4 +1,5 @@
<root> <?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <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:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">
@ -57,7 +58,4 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<metadata name="gridEntryBindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root> </root>

View File

@ -1,10 +1,7 @@
using System; using InternalUtilities;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using InternalUtilities;
namespace LibationWinForms.Dialogs namespace LibationWinForms.Dialogs
{ {
@ -12,7 +9,7 @@ namespace LibationWinForms.Dialogs
{ {
public List<Account> CheckedAccounts { get; } = new List<Account>(); public List<Account> CheckedAccounts { get; } = new List<Account>();
Form1 _parent { get; } private Form1 _parent { get; }
public ScanAccountsDialog(Form1 parent) public ScanAccountsDialog(Form1 parent)
{ {
@ -21,7 +18,7 @@ namespace LibationWinForms.Dialogs
InitializeComponent(); InitializeComponent();
} }
class listItem private class listItem
{ {
public Account Account { get; set; } public Account Account { get; set; }
public string Text { get; set; } public string Text { get; set; }

View File

@ -1,64 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <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:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">

View File

@ -3,18 +3,18 @@ using System.Windows.Forms;
namespace LibationWinForms.Dialogs namespace LibationWinForms.Dialogs
{ {
public partial class SearchSyntaxDialog : Form public partial class SearchSyntaxDialog : Form
{ {
public SearchSyntaxDialog() public SearchSyntaxDialog()
{ {
InitializeComponent(); InitializeComponent();
label2.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchStringFields()); label2.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchStringFields());
label3.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchNumberFields()); label3.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchNumberFields());
label4.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchBoolFields()); label4.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchBoolFields());
label5.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchIdFields()); label5.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchIdFields());
} }
private void CloseBtn_Click(object sender, EventArgs e) => this.Close(); private void CloseBtn_Click(object sender, EventArgs e) => this.Close();
} }
} }

View File

@ -1,64 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <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:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">

View File

@ -1,15 +1,15 @@
using System; using Dinah.Core;
using FileManager;
using System;
using System.IO; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using Dinah.Core;
using FileManager;
namespace LibationWinForms.Dialogs namespace LibationWinForms.Dialogs
{ {
public partial class SettingsDialog : Form public partial class SettingsDialog : Form
{ {
Configuration config { get; } = Configuration.Instance; private Configuration config { get; } = Configuration.Instance;
Func<string, string> desc { get; } = Configuration.GetDescription; private Func<string, string> desc { get; } = Configuration.GetDescription;
public SettingsDialog() => InitializeComponent(); public SettingsDialog() => InitializeComponent();
@ -57,13 +57,13 @@ namespace LibationWinForms.Dialogs
inProgressSelectControl.SelectDirectory(config.InProgress); inProgressSelectControl.SelectDirectory(config.InProgress);
} }
private void allowLibationFixupCbox_CheckedChanged(object sender, EventArgs e) private void allowLibationFixupCbox_CheckedChanged(object sender, EventArgs e)
{ {
convertLosslessRb.Enabled = allowLibationFixupCbox.Checked; convertLosslessRb.Enabled = allowLibationFixupCbox.Checked;
convertLossyRb.Enabled = allowLibationFixupCbox.Checked; convertLossyRb.Enabled = allowLibationFixupCbox.Checked;
if (!allowLibationFixupCbox.Checked) if (!allowLibationFixupCbox.Checked)
{ {
convertLosslessRb.Checked = true; convertLosslessRb.Checked = true;
} }
} }

View File

@ -1,4 +1,5 @@
<root> <?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <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:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">

View File

@ -1,4 +1,5 @@
<root> <?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <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:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">

View File

@ -1,9 +1,4 @@
using System; using ApplicationServices;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using ApplicationServices;
using DataLayer; using DataLayer;
using Dinah.Core; using Dinah.Core;
using Dinah.Core.Drawing; using Dinah.Core.Drawing;
@ -11,371 +6,376 @@ using Dinah.Core.Windows.Forms;
using FileManager; using FileManager;
using InternalUtilities; using InternalUtilities;
using LibationWinForms.Dialogs; using LibationWinForms.Dialogs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace LibationWinForms namespace LibationWinForms
{ {
public partial class Form1 : Form public partial class Form1 : Form
{ {
private string backupsCountsLbl_Format { get; } private string backupsCountsLbl_Format { get; }
private string pdfsCountsLbl_Format { get; } private string pdfsCountsLbl_Format { get; }
private string visibleCountLbl_Format { get; } private string visibleCountLbl_Format { get; }
private string beginBookBackupsToolStripMenuItem_format { get; } private string beginBookBackupsToolStripMenuItem_format { get; }
private string beginPdfBackupsToolStripMenuItem_format { get; } private string beginPdfBackupsToolStripMenuItem_format { get; }
public Form1() public Form1()
{ {
InitializeComponent(); InitializeComponent();
// back up string formats // back up string formats
backupsCountsLbl_Format = backupsCountsLbl.Text; backupsCountsLbl_Format = backupsCountsLbl.Text;
pdfsCountsLbl_Format = pdfsCountsLbl.Text; pdfsCountsLbl_Format = pdfsCountsLbl.Text;
visibleCountLbl_Format = visibleCountLbl.Text; visibleCountLbl_Format = visibleCountLbl.Text;
beginBookBackupsToolStripMenuItem_format = beginBookBackupsToolStripMenuItem.Text; beginBookBackupsToolStripMenuItem_format = beginBookBackupsToolStripMenuItem.Text;
beginPdfBackupsToolStripMenuItem_format = beginPdfBackupsToolStripMenuItem.Text; beginPdfBackupsToolStripMenuItem_format = beginPdfBackupsToolStripMenuItem.Text;
// after backing up formats: can set default/temp visible text // after backing up formats: can set default/temp visible text
backupsCountsLbl.Text = "[Calculating backed up book quantities]"; backupsCountsLbl.Text = "[Calculating backed up book quantities]";
pdfsCountsLbl.Text = "[Calculating backed up PDFs]"; pdfsCountsLbl.Text = "[Calculating backed up PDFs]";
setVisibleCount(null, 0); setVisibleCount(null, 0);
if (this.DesignMode) if (this.DesignMode)
return; return;
// independent UI updates // independent UI updates
this.Load += setBackupCountsAsync; this.Load += setBackupCountsAsync;
this.Load += (_, __) => RestoreSizeAndLocation(); this.Load += (_, __) => RestoreSizeAndLocation();
this.Load += (_, __) => RefreshImportMenu(); this.Load += (_, __) => RefreshImportMenu();
var format = System.Drawing.Imaging.ImageFormat.Jpeg; var format = System.Drawing.Imaging.ImageFormat.Jpeg;
PictureStorage.SetDefaultImage(PictureSize._80x80, Properties.Resources.default_cover_80x80.ToBytes(format)); PictureStorage.SetDefaultImage(PictureSize._80x80, Properties.Resources.default_cover_80x80.ToBytes(format));
PictureStorage.SetDefaultImage(PictureSize._300x300, Properties.Resources.default_cover_300x300.ToBytes(format)); PictureStorage.SetDefaultImage(PictureSize._300x300, Properties.Resources.default_cover_300x300.ToBytes(format));
PictureStorage.SetDefaultImage(PictureSize._500x500, Properties.Resources.default_cover_500x500.ToBytes(format)); PictureStorage.SetDefaultImage(PictureSize._500x500, Properties.Resources.default_cover_500x500.ToBytes(format));
} }
private void Form1_Load(object sender, EventArgs e) private void Form1_Load(object sender, EventArgs e)
{ {
if (this.DesignMode) if (this.DesignMode)
return; return;
reloadGrid(); reloadGrid();
// also applies filter. ONLY call AFTER loading grid // also applies filter. ONLY call AFTER loading grid
loadInitialQuickFilterState(); loadInitialQuickFilterState();
} }
private void Form1_FormClosing(object sender, FormClosingEventArgs e) private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{ {
SaveSizeAndLocation(); SaveSizeAndLocation();
} }
private void RestoreSizeAndLocation() private void RestoreSizeAndLocation()
{ {
var config = Configuration.Instance; var config = Configuration.Instance;
var width = config.MainFormWidth; var width = config.MainFormWidth;
var height = config.MainFormHeight; var height = config.MainFormHeight;
// too small -- something must have gone wrong. use defaults // too small -- something must have gone wrong. use defaults
if (width < 25 || height < 25) if (width < 25 || height < 25)
{ {
width = 1023; width = 1023;
height = 578; height = 578;
} }
// Fit to the current screen size in case the screen resolution changed since the size was last persisted // Fit to the current screen size in case the screen resolution changed since the size was last persisted
if (width > Screen.PrimaryScreen.WorkingArea.Width) if (width > Screen.PrimaryScreen.WorkingArea.Width)
width = Screen.PrimaryScreen.WorkingArea.Width; width = Screen.PrimaryScreen.WorkingArea.Width;
if (height > Screen.PrimaryScreen.WorkingArea.Height) if (height > Screen.PrimaryScreen.WorkingArea.Height)
height = Screen.PrimaryScreen.WorkingArea.Height; height = Screen.PrimaryScreen.WorkingArea.Height;
var x = config.MainFormX; var x = config.MainFormX;
var y = config.MainFormY; var y = config.MainFormY;
var rect = new System.Drawing.Rectangle(x, y, width, height); var rect = new System.Drawing.Rectangle(x, y, width, height);
// is proposed rect on a screen? // is proposed rect on a screen?
if (Screen.AllScreens.Any(screen => screen.WorkingArea.Contains(rect))) if (Screen.AllScreens.Any(screen => screen.WorkingArea.Contains(rect)))
{ {
this.StartPosition = FormStartPosition.Manual; this.StartPosition = FormStartPosition.Manual;
this.DesktopBounds = rect; this.DesktopBounds = rect;
} }
else else
{ {
this.StartPosition = FormStartPosition.WindowsDefaultLocation; this.StartPosition = FormStartPosition.WindowsDefaultLocation;
this.Size = rect.Size; this.Size = rect.Size;
} }
// FINAL: for Maximized: start normal state, set size and location, THEN set max state // FINAL: for Maximized: start normal state, set size and location, THEN set max state
this.WindowState = config.MainFormIsMaximized ? FormWindowState.Maximized : FormWindowState.Normal; this.WindowState = config.MainFormIsMaximized ? FormWindowState.Maximized : FormWindowState.Normal;
} }
private void SaveSizeAndLocation() private void SaveSizeAndLocation()
{ {
System.Drawing.Point location; System.Drawing.Point location;
System.Drawing.Size size; System.Drawing.Size size;
// save location and size if the state is normal // save location and size if the state is normal
if (this.WindowState == FormWindowState.Normal) if (this.WindowState == FormWindowState.Normal)
{ {
location = this.Location; location = this.Location;
size = this.Size; size = this.Size;
} }
else else
{ {
// save the RestoreBounds if the form is minimized or maximized // save the RestoreBounds if the form is minimized or maximized
location = this.RestoreBounds.Location; location = this.RestoreBounds.Location;
size = this.RestoreBounds.Size; size = this.RestoreBounds.Size;
} }
var config = Configuration.Instance; var config = Configuration.Instance;
config.MainFormX = location.X; config.MainFormX = location.X;
config.MainFormY = location.Y; config.MainFormY = location.Y;
config.MainFormWidth = size.Width; config.MainFormWidth = size.Width;
config.MainFormHeight = size.Height; config.MainFormHeight = size.Height;
config.MainFormIsMaximized = this.WindowState == FormWindowState.Maximized; config.MainFormIsMaximized = this.WindowState == FormWindowState.Maximized;
} }
#region reload grid #region reload grid
bool isProcessingGridSelect = false; private bool isProcessingGridSelect = false;
private void reloadGrid() private void reloadGrid()
{ {
// suppressed filter while init'ing UI // suppressed filter while init'ing UI
var prev_isProcessingGridSelect = isProcessingGridSelect; var prev_isProcessingGridSelect = isProcessingGridSelect;
isProcessingGridSelect = true; isProcessingGridSelect = true;
setGrid(); setGrid();
isProcessingGridSelect = prev_isProcessingGridSelect; isProcessingGridSelect = prev_isProcessingGridSelect;
// UI init complete. now we can apply filter // UI init complete. now we can apply filter
doFilter(lastGoodFilter); doFilter(lastGoodFilter);
} }
ProductsGrid currProductsGrid; private ProductsGrid currProductsGrid;
private void setGrid() private void setGrid()
{ {
SuspendLayout(); SuspendLayout();
{ {
if (currProductsGrid != null) if (currProductsGrid != null)
{ {
gridPanel.Controls.Remove(currProductsGrid); gridPanel.Controls.Remove(currProductsGrid);
currProductsGrid.VisibleCountChanged -= setVisibleCount; currProductsGrid.VisibleCountChanged -= setVisibleCount;
currProductsGrid.BackupCountsChanged -= setBackupCountsAsync; currProductsGrid.BackupCountsChanged -= setBackupCountsAsync;
currProductsGrid.Dispose(); currProductsGrid.Dispose();
} }
currProductsGrid = new ProductsGrid { Dock = DockStyle.Fill }; currProductsGrid = new ProductsGrid { Dock = DockStyle.Fill };
currProductsGrid.VisibleCountChanged += setVisibleCount; currProductsGrid.VisibleCountChanged += setVisibleCount;
currProductsGrid.BackupCountsChanged += setBackupCountsAsync; currProductsGrid.BackupCountsChanged += setBackupCountsAsync;
gridPanel.UIThread(() => gridPanel.Controls.Add(currProductsGrid)); gridPanel.UIThread(() => gridPanel.Controls.Add(currProductsGrid));
currProductsGrid.Display(); currProductsGrid.Display();
} }
ResumeLayout(); ResumeLayout();
} }
#endregion #endregion
#region bottom: qty books visible #region bottom: qty books visible
private void setVisibleCount(object _, int qty) => visibleCountLbl.Text = string.Format(visibleCountLbl_Format, qty); private void setVisibleCount(object _, int qty) => visibleCountLbl.Text = string.Format(visibleCountLbl_Format, qty);
#endregion #endregion
#region bottom: backup counts #region bottom: backup counts
private async void setBackupCountsAsync(object _, object __) private async void setBackupCountsAsync(object _, object __)
{ {
LibraryCommands.LibraryStats libraryStats = null; LibraryCommands.LibraryStats libraryStats = null;
await Task.Run(() => libraryStats = LibraryCommands.GetCounts()); await Task.Run(() => libraryStats = LibraryCommands.GetCounts());
setBookBackupCounts(libraryStats.booksFullyBackedUp, libraryStats.booksDownloadedOnly, libraryStats.booksNoProgress); setBookBackupCounts(libraryStats.booksFullyBackedUp, libraryStats.booksDownloadedOnly, libraryStats.booksNoProgress);
setPdfBackupCounts(libraryStats.pdfsDownloaded, libraryStats.pdfsNotDownloaded); setPdfBackupCounts(libraryStats.pdfsDownloaded, libraryStats.pdfsNotDownloaded);
} }
private void setBookBackupCounts(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress) private void setBookBackupCounts(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress)
{ {
// enable/disable export // enable/disable export
var hasResults = 0 < (booksFullyBackedUp + booksDownloadedOnly + booksNoProgress); var hasResults = 0 < (booksFullyBackedUp + booksDownloadedOnly + booksNoProgress);
exportLibraryToolStripMenuItem.Enabled = hasResults; exportLibraryToolStripMenuItem.Enabled = hasResults;
// update bottom numbers // update bottom numbers
var pending = booksNoProgress + booksDownloadedOnly; var pending = booksNoProgress + booksDownloadedOnly;
var statusStripText var statusStripText
= !hasResults ? "No books. Begin by importing your library" = !hasResults ? "No books. Begin by importing your library"
: pending > 0 ? string.Format(backupsCountsLbl_Format, booksNoProgress, booksDownloadedOnly, booksFullyBackedUp) : pending > 0 ? string.Format(backupsCountsLbl_Format, booksNoProgress, booksDownloadedOnly, booksFullyBackedUp)
: $"All {"book".PluralizeWithCount(booksFullyBackedUp)} backed up"; : $"All {"book".PluralizeWithCount(booksFullyBackedUp)} backed up";
// update menu item // update menu item
var menuItemText var menuItemText
= pending > 0 = pending > 0
? $"{pending} remaining" ? $"{pending} remaining"
: "All books have been liberated"; : "All books have been liberated";
// update UI // update UI
statusStrip1.UIThread(() => backupsCountsLbl.Text = statusStripText); statusStrip1.UIThread(() => backupsCountsLbl.Text = statusStripText);
menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Enabled = pending > 0); menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Enabled = pending > 0);
menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Text = string.Format(beginBookBackupsToolStripMenuItem_format, menuItemText)); menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Text = string.Format(beginBookBackupsToolStripMenuItem_format, menuItemText));
} }
private void setPdfBackupCounts(int pdfsDownloaded, int pdfsNotDownloaded) private void setPdfBackupCounts(int pdfsDownloaded, int pdfsNotDownloaded)
{ {
// update bottom numbers // update bottom numbers
var hasResults = 0 < (pdfsNotDownloaded + pdfsDownloaded); var hasResults = 0 < (pdfsNotDownloaded + pdfsDownloaded);
var statusStripText var statusStripText
= !hasResults ? "" = !hasResults ? ""
: pdfsNotDownloaded > 0 ? string.Format(pdfsCountsLbl_Format, pdfsNotDownloaded, pdfsDownloaded) : pdfsNotDownloaded > 0 ? string.Format(pdfsCountsLbl_Format, pdfsNotDownloaded, pdfsDownloaded)
: $"| All {pdfsDownloaded} PDFs downloaded"; : $"| All {pdfsDownloaded} PDFs downloaded";
// update menu item // update menu item
var menuItemText var menuItemText
= pdfsNotDownloaded > 0 = pdfsNotDownloaded > 0
? $"{pdfsNotDownloaded} remaining" ? $"{pdfsNotDownloaded} remaining"
: "All PDFs have been downloaded"; : "All PDFs have been downloaded";
// update UI // update UI
statusStrip1.UIThread(() => pdfsCountsLbl.Text = statusStripText); statusStrip1.UIThread(() => pdfsCountsLbl.Text = statusStripText);
menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Enabled = pdfsNotDownloaded > 0); menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Enabled = pdfsNotDownloaded > 0);
menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Text = string.Format(beginPdfBackupsToolStripMenuItem_format, menuItemText)); menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Text = string.Format(beginPdfBackupsToolStripMenuItem_format, menuItemText));
} }
#endregion #endregion
#region filter #region filter
private void filterHelpBtn_Click(object sender, EventArgs e) => new SearchSyntaxDialog().ShowDialog(); private void filterHelpBtn_Click(object sender, EventArgs e) => new SearchSyntaxDialog().ShowDialog();
private void AddFilterBtn_Click(object sender, EventArgs e) private void AddFilterBtn_Click(object sender, EventArgs e)
{ {
QuickFilters.Add(this.filterSearchTb.Text); QuickFilters.Add(this.filterSearchTb.Text);
UpdateFilterDropDown(); UpdateFilterDropDown();
} }
private void filterSearchTb_KeyPress(object sender, KeyPressEventArgs e) private void filterSearchTb_KeyPress(object sender, KeyPressEventArgs e)
{ {
if (e.KeyChar == (char)Keys.Return) if (e.KeyChar == (char)Keys.Return)
{ {
doFilter(); doFilter();
// silence the 'ding' // silence the 'ding'
e.Handled = true; e.Handled = true;
} }
} }
private void filterBtn_Click(object sender, EventArgs e) => doFilter(); private void filterBtn_Click(object sender, EventArgs e) => doFilter();
string lastGoodFilter = ""; private string lastGoodFilter = "";
private void doFilter(string filterString) private void doFilter(string filterString)
{ {
this.filterSearchTb.Text = filterString; this.filterSearchTb.Text = filterString;
doFilter(); doFilter();
} }
private void doFilter() private void doFilter()
{ {
if (isProcessingGridSelect || currProductsGrid == null) if (isProcessingGridSelect || currProductsGrid == null)
return; return;
try try
{ {
currProductsGrid.Filter(filterSearchTb.Text); currProductsGrid.Filter(filterSearchTb.Text);
lastGoodFilter = filterSearchTb.Text; lastGoodFilter = filterSearchTb.Text;
} }
catch (Exception ex) catch (Exception ex)
{ {
MessageBox.Show($"Bad filter string:\r\n\r\n{ex.Message}", "Bad filter string", MessageBoxButtons.OK, MessageBoxIcon.Error); MessageBox.Show($"Bad filter string:\r\n\r\n{ex.Message}", "Bad filter string", MessageBoxButtons.OK, MessageBoxIcon.Error);
// re-apply last good filter // re-apply last good filter
doFilter(lastGoodFilter); doFilter(lastGoodFilter);
} }
} }
#endregion #endregion
#region Import menu #region Import menu
public void RefreshImportMenu() public void RefreshImportMenu()
{ {
using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
var count = persister.AccountsSettings.Accounts.Count; var count = persister.AccountsSettings.Accounts.Count;
noAccountsYetAddAccountToolStripMenuItem.Visible = count == 0; noAccountsYetAddAccountToolStripMenuItem.Visible = count == 0;
scanLibraryToolStripMenuItem.Visible = count == 1; scanLibraryToolStripMenuItem.Visible = count == 1;
scanLibraryOfAllAccountsToolStripMenuItem.Visible = count > 1; scanLibraryOfAllAccountsToolStripMenuItem.Visible = count > 1;
scanLibraryOfSomeAccountsToolStripMenuItem.Visible = count > 1; scanLibraryOfSomeAccountsToolStripMenuItem.Visible = count > 1;
removeLibraryBooksToolStripMenuItem.Visible = count != 0; removeLibraryBooksToolStripMenuItem.Visible = count != 0;
if (count == 1) if (count == 1)
{ {
removeLibraryBooksToolStripMenuItem.Click += removeThisAccountToolStripMenuItem_Click; removeLibraryBooksToolStripMenuItem.Click += removeThisAccountToolStripMenuItem_Click;
} }
removeSomeAccountsToolStripMenuItem.Visible = count > 1; removeSomeAccountsToolStripMenuItem.Visible = count > 1;
removeAllAccountsToolStripMenuItem.Visible = count > 1; removeAllAccountsToolStripMenuItem.Visible = count > 1;
} }
private void noAccountsYetAddAccountToolStripMenuItem_Click(object sender, EventArgs e) private void noAccountsYetAddAccountToolStripMenuItem_Click(object sender, EventArgs e)
{ {
MessageBox.Show("To load your Audible library, come back here to the Import menu after adding your account"); MessageBox.Show("To load your Audible library, come back here to the Import menu after adding your account");
new AccountsDialog(this).ShowDialog(); new AccountsDialog(this).ShowDialog();
} }
private void scanLibraryToolStripMenuItem_Click(object sender, EventArgs e) private void scanLibraryToolStripMenuItem_Click(object sender, EventArgs e)
{ {
using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault(); var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault();
scanLibraries(firstAccount); scanLibraries(firstAccount);
} }
private void scanLibraryOfAllAccountsToolStripMenuItem_Click(object sender, EventArgs e) private void scanLibraryOfAllAccountsToolStripMenuItem_Click(object sender, EventArgs e)
{ {
using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
var allAccounts = persister.AccountsSettings.GetAll(); var allAccounts = persister.AccountsSettings.GetAll();
scanLibraries(allAccounts); scanLibraries(allAccounts);
} }
private void scanLibraryOfSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e) private void scanLibraryOfSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e)
{ {
using var scanAccountsDialog = new ScanAccountsDialog(this); using var scanAccountsDialog = new ScanAccountsDialog(this);
if (scanAccountsDialog.ShowDialog() != DialogResult.OK) if (scanAccountsDialog.ShowDialog() != DialogResult.OK)
return; return;
if (!scanAccountsDialog.CheckedAccounts.Any()) if (!scanAccountsDialog.CheckedAccounts.Any())
return; return;
scanLibraries(scanAccountsDialog.CheckedAccounts); scanLibraries(scanAccountsDialog.CheckedAccounts);
} }
private void removeThisAccountToolStripMenuItem_Click(object sender, EventArgs e) private void removeThisAccountToolStripMenuItem_Click(object sender, EventArgs e)
{ {
using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault(); var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault();
scanLibrariesRemovedBooks(firstAccount); scanLibrariesRemovedBooks(firstAccount);
} }
private void removeAllAccountsToolStripMenuItem_Click(object sender, EventArgs e) private void removeAllAccountsToolStripMenuItem_Click(object sender, EventArgs e)
{ {
using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
var allAccounts = persister.AccountsSettings.GetAll(); var allAccounts = persister.AccountsSettings.GetAll();
scanLibrariesRemovedBooks(allAccounts.ToArray()); scanLibrariesRemovedBooks(allAccounts.ToArray());
} }
private void removeSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e) private void removeSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e)
{ {
using var scanAccountsDialog = new ScanAccountsDialog(this); using var scanAccountsDialog = new ScanAccountsDialog(this);
if (scanAccountsDialog.ShowDialog() != DialogResult.OK) if (scanAccountsDialog.ShowDialog() != DialogResult.OK)
return; return;
if (!scanAccountsDialog.CheckedAccounts.Any()) if (!scanAccountsDialog.CheckedAccounts.Any())
return; return;
scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray()); scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray());
} }
private void scanLibrariesRemovedBooks(params Account[] accounts) private void scanLibrariesRemovedBooks(params Account[] accounts)
{ {
using var dialog = new RemoveBooksDialog(accounts); using var dialog = new RemoveBooksDialog(accounts);
dialog.ShowDialog(); dialog.ShowDialog();
if (dialog.BooksRemoved) if (dialog.BooksRemoved)
reloadGrid(); reloadGrid();
} }
private void scanLibraries(IEnumerable<Account> accounts) => scanLibraries(accounts.ToArray()); private void scanLibraries(IEnumerable<Account> accounts) => scanLibraries(accounts.ToArray());
private void scanLibraries(params Account[] accounts) private void scanLibraries(params Account[] accounts)
{ {
using var dialog = new IndexLibraryDialog(accounts); using var dialog = new IndexLibraryDialog(accounts);
dialog.ShowDialog(); dialog.ShowDialog();
@ -387,112 +387,112 @@ namespace LibationWinForms
if (totalProcessed > 0) if (totalProcessed > 0)
reloadGrid(); reloadGrid();
} }
#endregion #endregion
#region liberate menu #region liberate menu
private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e) private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e)
=> await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync(updateGridRow); => await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync(updateGridRow);
private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e) private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e)
=> await BookLiberation.ProcessorAutomationController.BackupAllPdfsAsync(updateGridRow); => await BookLiberation.ProcessorAutomationController.BackupAllPdfsAsync(updateGridRow);
private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e) private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e)
=> await BookLiberation.ProcessorAutomationController.ConvertAllBooksAsync(); => await BookLiberation.ProcessorAutomationController.ConvertAllBooksAsync();
private void updateGridRow(object _, LibraryBook libraryBook) => currProductsGrid.RefreshRow(libraryBook.Book.AudibleProductId); private void updateGridRow(object _, LibraryBook libraryBook) => currProductsGrid.RefreshRow(libraryBook.Book.AudibleProductId);
#endregion #endregion
#region Export menu #region Export menu
private void exportLibraryToolStripMenuItem_Click(object sender, EventArgs e) private void exportLibraryToolStripMenuItem_Click(object sender, EventArgs e)
{ {
try try
{ {
var saveFileDialog = new SaveFileDialog var saveFileDialog = new SaveFileDialog
{ {
Title = "Where to export Library", Title = "Where to export Library",
Filter = "Excel Workbook (*.xlsx)|*.xlsx|CSV files (*.csv)|*.csv|JSON files (*.json)|*.json" // + "|All files (*.*)|*.*" Filter = "Excel Workbook (*.xlsx)|*.xlsx|CSV files (*.csv)|*.csv|JSON files (*.json)|*.json" // + "|All files (*.*)|*.*"
}; };
if (saveFileDialog.ShowDialog() != DialogResult.OK) if (saveFileDialog.ShowDialog() != DialogResult.OK)
return; return;
// FilterIndex is 1-based, NOT 0-based // FilterIndex is 1-based, NOT 0-based
switch (saveFileDialog.FilterIndex) switch (saveFileDialog.FilterIndex)
{ {
case 1: // xlsx case 1: // xlsx
default: default:
LibraryExporter.ToXlsx(saveFileDialog.FileName); LibraryExporter.ToXlsx(saveFileDialog.FileName);
break; break;
case 2: // csv case 2: // csv
LibraryExporter.ToCsv(saveFileDialog.FileName); LibraryExporter.ToCsv(saveFileDialog.FileName);
break; break;
case 3: // json case 3: // json
LibraryExporter.ToJson(saveFileDialog.FileName); LibraryExporter.ToJson(saveFileDialog.FileName);
break; break;
} }
MessageBox.Show("Library exported to:\r\n" + saveFileDialog.FileName); MessageBox.Show("Library exported to:\r\n" + saveFileDialog.FileName);
} }
catch (Exception ex) catch (Exception ex)
{ {
MessageBoxAlertAdmin.Show("Error attempting to export your library.", "Error exporting", ex); MessageBoxAlertAdmin.Show("Error attempting to export your library.", "Error exporting", ex);
} }
} }
#endregion #endregion
#region quick filters menu #region quick filters menu
private void loadInitialQuickFilterState() private void loadInitialQuickFilterState()
{ {
// set inital state. do once only // set inital state. do once only
firstFilterIsDefaultToolStripMenuItem.Checked = QuickFilters.UseDefault; firstFilterIsDefaultToolStripMenuItem.Checked = QuickFilters.UseDefault;
// load default filter. do once only // load default filter. do once only
if (QuickFilters.UseDefault) if (QuickFilters.UseDefault)
doFilter(QuickFilters.Filters.FirstOrDefault()); doFilter(QuickFilters.Filters.FirstOrDefault());
// do after every save // do after every save
UpdateFilterDropDown(); UpdateFilterDropDown();
} }
private void FirstFilterIsDefaultToolStripMenuItem_Click(object sender, EventArgs e) private void FirstFilterIsDefaultToolStripMenuItem_Click(object sender, EventArgs e)
{ {
firstFilterIsDefaultToolStripMenuItem.Checked = !firstFilterIsDefaultToolStripMenuItem.Checked; firstFilterIsDefaultToolStripMenuItem.Checked = !firstFilterIsDefaultToolStripMenuItem.Checked;
QuickFilters.UseDefault = firstFilterIsDefaultToolStripMenuItem.Checked; QuickFilters.UseDefault = firstFilterIsDefaultToolStripMenuItem.Checked;
} }
object quickFilterTag { get; } = new object(); private object quickFilterTag { get; } = new object();
public void UpdateFilterDropDown() public void UpdateFilterDropDown()
{ {
// remove old // remove old
for (var i = quickFiltersToolStripMenuItem.DropDownItems.Count - 1; i >= 0; i--) for (var i = quickFiltersToolStripMenuItem.DropDownItems.Count - 1; i >= 0; i--)
{ {
var menuItem = quickFiltersToolStripMenuItem.DropDownItems[i]; var menuItem = quickFiltersToolStripMenuItem.DropDownItems[i];
if (menuItem.Tag == quickFilterTag) if (menuItem.Tag == quickFilterTag)
quickFiltersToolStripMenuItem.DropDownItems.Remove(menuItem); quickFiltersToolStripMenuItem.DropDownItems.Remove(menuItem);
} }
// re-populate // re-populate
var index = 0; var index = 0;
foreach (var filter in QuickFilters.Filters) foreach (var filter in QuickFilters.Filters)
{ {
var menuItem = new ToolStripMenuItem var menuItem = new ToolStripMenuItem
{ {
Tag = quickFilterTag, Tag = quickFilterTag,
Text = $"&{++index}: {filter}" Text = $"&{++index}: {filter}"
}; };
menuItem.Click += (_, __) => doFilter(filter); menuItem.Click += (_, __) => doFilter(filter);
quickFiltersToolStripMenuItem.DropDownItems.Add(menuItem); quickFiltersToolStripMenuItem.DropDownItems.Add(menuItem);
} }
} }
private void EditQuickFiltersToolStripMenuItem_Click(object sender, EventArgs e) => new EditQuickFilters(this).ShowDialog(); private void EditQuickFiltersToolStripMenuItem_Click(object sender, EventArgs e) => new EditQuickFilters(this).ShowDialog();
#endregion #endregion
#region settings menu #region settings menu
private void accountsToolStripMenuItem_Click(object sender, EventArgs e) => new AccountsDialog(this).ShowDialog(); private void accountsToolStripMenuItem_Click(object sender, EventArgs e) => new AccountsDialog(this).ShowDialog();
private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog(); private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog();
#endregion #endregion
} }
} }

View File

@ -1,12 +1,12 @@
using System; using ApplicationServices;
using DataLayer;
using Dinah.Core.Drawing;
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using ApplicationServices;
using DataLayer;
using Dinah.Core.Drawing;
namespace LibationWinForms namespace LibationWinForms
{ {
@ -106,7 +106,7 @@ namespace LibationWinForms
/// <summary> /// <summary>
/// Create getters for all member object values by name /// Create getters for all member object values by name
/// </summary> /// </summary>
Dictionary<string, Func<object>> CreateMemberValueDictionary() => new() private Dictionary<string, Func<object>> CreateMemberValueDictionary() => new()
{ {
{ nameof(Title), () => GetSortName(Book.Title) }, { nameof(Title), () => GetSortName(Book.Title) },
{ nameof(Series), () => GetSortName(Book.SeriesNames) }, { nameof(Series), () => GetSortName(Book.SeriesNames) },

View File

@ -3,7 +3,7 @@ using System.Collections;
namespace LibationWinForms namespace LibationWinForms
{ {
interface IObjectMemberComparable internal interface IObjectMemberComparable
{ {
IComparer GetMemberComparer(Type memberType); IComparer GetMemberComparer(Type memberType);
object GetMemberValue(string memberName); object GetMemberValue(string memberName);

View File

@ -1,9 +1,5 @@
using System; using LibationWinForms.Dialogs;
using System.Collections.Generic; using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using LibationWinForms.Dialogs;
namespace LibationWinForms namespace LibationWinForms
{ {

View File

@ -1,7 +1,6 @@
using System; using Dinah.Core.Logging;
using System.Windows.Forms;
using Dinah.Core.Logging;
using Serilog; using Serilog;
using System.Windows.Forms;
namespace LibationWinForms namespace LibationWinForms
{ {

View File

@ -3,7 +3,7 @@ using System.Collections;
namespace LibationWinForms namespace LibationWinForms
{ {
class ObjectComparer<T> : IComparer where T : IComparable internal class ObjectComparer<T> : IComparer where T : IComparable
{ {
public int Compare(object x, object y) => ((T)x).CompareTo((T)y); public int Compare(object x, object y) => ((T)x).CompareTo((T)y);
} }

View File

@ -3,19 +3,19 @@ using System.ComponentModel;
namespace LibationWinForms namespace LibationWinForms
{ {
class ObjectMemberComparer<T> : IComparer<T> where T : IObjectMemberComparable internal class ObjectMemberComparer<T> : IComparer<T> where T : IObjectMemberComparable
{ {
public ListSortDirection Direction { get; set; } = ListSortDirection.Ascending; public ListSortDirection Direction { get; set; } = ListSortDirection.Ascending;
public string PropertyName { get; set; } public string PropertyName { get; set; }
public int Compare(T x, T y) public int Compare(T x, T y)
{ {
var val1 = x.GetMemberValue(PropertyName); var val1 = x.GetMemberValue(PropertyName);
var val2 = y.GetMemberValue(PropertyName); var val2 = y.GetMemberValue(PropertyName);
return DirMult * x.GetMemberComparer(val1.GetType()).Compare(val1, val2); return DirMult * x.GetMemberComparer(val1.GetType()).Compare(val1, val2);
} }
private int DirMult => Direction == ListSortDirection.Descending ? -1 : 1; private int DirMult => Direction == ListSortDirection.Descending ? -1 : 1;
} }
} }

View File

@ -1,200 +1,200 @@
using System; using ApplicationServices;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using ApplicationServices;
using DataLayer; using DataLayer;
using Dinah.Core; using Dinah.Core;
using Dinah.Core.Windows.Forms; using Dinah.Core.Windows.Forms;
using LibationWinForms.Dialogs; using LibationWinForms.Dialogs;
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace LibationWinForms namespace LibationWinForms
{ {
// INSTRUCTIONS TO UPDATE DATA_GRID_VIEW // INSTRUCTIONS TO UPDATE DATA_GRID_VIEW
// - delete current DataGridView // - delete current DataGridView
// - view > other windows > data sources // - view > other windows > data sources
// - refresh // - refresh
// OR // OR
// - Add New Data Source // - Add New Data Source
// Object. Next // Object. Next
// LibationWinForms // LibationWinForms
// AudibleDTO // AudibleDTO
// GridEntry // GridEntry
// - go to Design view // - go to Design view
// - click on Data Sources > ProductItem. drowdown: DataGridView // - click on Data Sources > ProductItem. drowdown: DataGridView
// - drag/drop ProductItem on design surface // - drag/drop ProductItem on design surface
// AS OF AUGUST 2021 THIS DOES NOT WORK IN VS2019 WITH .NET-5 PROJECTS // AS OF AUGUST 2021 THIS DOES NOT WORK IN VS2019 WITH .NET-5 PROJECTS
public partial class ProductsGrid : UserControl public partial class ProductsGrid : UserControl
{ {
public event EventHandler<int> VisibleCountChanged; public event EventHandler<int> VisibleCountChanged;
public event EventHandler BackupCountsChanged; public event EventHandler BackupCountsChanged;
// alias // alias
private DataGridView _dataGridView => gridEntryDataGridView; private DataGridView _dataGridView => gridEntryDataGridView;
public ProductsGrid() public ProductsGrid()
{ {
InitializeComponent(); InitializeComponent();
// sorting breaks filters. must reapply filters after sorting // sorting breaks filters. must reapply filters after sorting
_dataGridView.Sorted += (_, __) => Filter(); _dataGridView.Sorted += (_, __) => Filter();
_dataGridView.CellContentClick += DataGridView_CellContentClick; _dataGridView.CellContentClick += DataGridView_CellContentClick;
EnableDoubleBuffering(); EnableDoubleBuffering();
} }
private void EnableDoubleBuffering() private void EnableDoubleBuffering()
{ {
var propertyInfo = _dataGridView.GetType().GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); var propertyInfo = _dataGridView.GetType().GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
propertyInfo.SetValue(_dataGridView, true, null); propertyInfo.SetValue(_dataGridView, true, null);
} }
#region Button controls #region Button controls
private async void DataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e) private async void DataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e)
{ {
// handle grid button click: https://stackoverflow.com/a/13687844 // handle grid button click: https://stackoverflow.com/a/13687844
if (e.RowIndex < 0 || _dataGridView.Columns[e.ColumnIndex] is not DataGridViewButtonColumn) if (e.RowIndex < 0 || _dataGridView.Columns[e.ColumnIndex] is not DataGridViewButtonColumn)
return; return;
var liveGridEntry = getGridEntry(e.RowIndex); var liveGridEntry = getGridEntry(e.RowIndex);
switch (_dataGridView.Columns[e.ColumnIndex].DataPropertyName) switch (_dataGridView.Columns[e.ColumnIndex].DataPropertyName)
{ {
case nameof(liveGridEntry.Liberate): case nameof(liveGridEntry.Liberate):
await Liberate_Click(liveGridEntry); await Liberate_Click(liveGridEntry);
break; break;
case nameof(liveGridEntry.DisplayTags): case nameof(liveGridEntry.DisplayTags):
EditTags_Click(liveGridEntry); EditTags_Click(liveGridEntry);
break; break;
} }
} }
private async Task Liberate_Click(GridEntry liveGridEntry) private async Task Liberate_Click(GridEntry liveGridEntry)
{ {
var libraryBook = liveGridEntry.LibraryBook; var libraryBook = liveGridEntry.LibraryBook;
// liberated: open explorer to file // liberated: open explorer to file
if (TransitionalFileLocator.Audio_Exists(libraryBook.Book)) if (TransitionalFileLocator.Audio_Exists(libraryBook.Book))
{ {
var filePath = TransitionalFileLocator.Audio_GetPath(libraryBook.Book); var filePath = TransitionalFileLocator.Audio_GetPath(libraryBook.Book);
if (!Go.To.File(filePath)) if (!Go.To.File(filePath))
MessageBox.Show($"File not found:\r\n{filePath}"); MessageBox.Show($"File not found:\r\n{filePath}");
return; return;
} }
// else: liberate // else: liberate
await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(libraryBook, (_, __) => RefreshRow(libraryBook.Book.AudibleProductId)); await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(libraryBook, (_, __) => RefreshRow(libraryBook.Book.AudibleProductId));
} }
private void EditTags_Click(GridEntry liveGridEntry) private void EditTags_Click(GridEntry liveGridEntry)
{ {
var bookDetailsForm = new BookDetailsDialog(liveGridEntry.Title, liveGridEntry.LibraryBook.Book.UserDefinedItem.Tags); var bookDetailsForm = new BookDetailsDialog(liveGridEntry.Title, liveGridEntry.LibraryBook.Book.UserDefinedItem.Tags);
if (bookDetailsForm.ShowDialog() != DialogResult.OK) if (bookDetailsForm.ShowDialog() != DialogResult.OK)
return; return;
var qtyChanges = LibraryCommands.UpdateTags(liveGridEntry.LibraryBook.Book, bookDetailsForm.NewTags); var qtyChanges = LibraryCommands.UpdateTags(liveGridEntry.LibraryBook.Book, bookDetailsForm.NewTags);
if (qtyChanges == 0) if (qtyChanges == 0)
return; return;
//Re-apply filters //Re-apply filters
Filter(); Filter();
} }
#endregion #endregion
#region UI display functions #region UI display functions
private bool hasBeenDisplayed = false; private bool hasBeenDisplayed = false;
public void Display() public void Display()
{ {
if (hasBeenDisplayed) if (hasBeenDisplayed)
return; return;
hasBeenDisplayed = true; hasBeenDisplayed = true;
// //
// transform into sorted GridEntry.s BEFORE binding // transform into sorted GridEntry.s BEFORE binding
// //
using var context = DbContexts.GetContext(); using var context = DbContexts.GetContext();
var lib = context.GetLibrary_Flat_NoTracking(); var lib = context.GetLibrary_Flat_NoTracking();
// if no data. hide all columns. return // if no data. hide all columns. return
if (!lib.Any()) if (!lib.Any())
{ {
for (var i = _dataGridView.ColumnCount - 1; i >= 0; i--) for (var i = _dataGridView.ColumnCount - 1; i >= 0; i--)
_dataGridView.Columns.RemoveAt(i); _dataGridView.Columns.RemoveAt(i);
return; return;
} }
var orderedGridEntries = lib var orderedGridEntries = lib
.Select(lb => new GridEntry(lb)).ToList() .Select(lb => new GridEntry(lb)).ToList()
// default load order // default load order
.OrderByDescending(ge => (DateTime)ge.GetMemberValue(nameof(ge.PurchaseDate))) .OrderByDescending(ge => (DateTime)ge.GetMemberValue(nameof(ge.PurchaseDate)))
//// more advanced example: sort by author, then series, then title //// more advanced example: sort by author, then series, then title
//.OrderBy(ge => ge.Authors) //.OrderBy(ge => ge.Authors)
// .ThenBy(ge => ge.Series) // .ThenBy(ge => ge.Series)
// .ThenBy(ge => ge.Title) // .ThenBy(ge => ge.Title)
.ToList(); .ToList();
// BIND // BIND
gridEntryBindingSource.DataSource = new SortableBindingList2<GridEntry>(orderedGridEntries); gridEntryBindingSource.DataSource = new SortableBindingList2<GridEntry>(orderedGridEntries);
// FILTER // FILTER
Filter(); Filter();
BackupCountsChanged?.Invoke(this, EventArgs.Empty); BackupCountsChanged?.Invoke(this, EventArgs.Empty);
} }
public void RefreshRow(string productId) public void RefreshRow(string productId)
{ {
var rowIndex = getRowIndex((ge) => ge.AudibleProductId == productId); var rowIndex = getRowIndex((ge) => ge.AudibleProductId == productId);
// update cells incl Liberate button text // update cells incl Liberate button text
_dataGridView.InvalidateRow(rowIndex); _dataGridView.InvalidateRow(rowIndex);
// needed in case filtering by -IsLiberated and it gets changed to Liberated. want to immediately show the change // needed in case filtering by -IsLiberated and it gets changed to Liberated. want to immediately show the change
Filter(); Filter();
BackupCountsChanged?.Invoke(this, EventArgs.Empty); BackupCountsChanged?.Invoke(this, EventArgs.Empty);
} }
#endregion #endregion
#region Filter #region Filter
string _filterSearchString; private string _filterSearchString;
private void Filter() => Filter(_filterSearchString); private void Filter() => Filter(_filterSearchString);
public void Filter(string searchString) public void Filter(string searchString)
{ {
_filterSearchString = searchString; _filterSearchString = searchString;
if (_dataGridView.Rows.Count == 0) if (_dataGridView.Rows.Count == 0)
return; return;
var searchResults = SearchEngineCommands.Search(searchString); var searchResults = SearchEngineCommands.Search(searchString);
var productIds = searchResults.Docs.Select(d => d.ProductId).ToList(); var productIds = searchResults.Docs.Select(d => d.ProductId).ToList();
// https://stackoverflow.com/a/18942430 // https://stackoverflow.com/a/18942430
var bindingContext = BindingContext[_dataGridView.DataSource]; var bindingContext = BindingContext[_dataGridView.DataSource];
bindingContext.SuspendBinding(); bindingContext.SuspendBinding();
{ {
for (var r = _dataGridView.RowCount - 1; r >= 0; r--) for (var r = _dataGridView.RowCount - 1; r >= 0; r--)
_dataGridView.Rows[r].Visible = productIds.Contains(getGridEntry(r).AudibleProductId); _dataGridView.Rows[r].Visible = productIds.Contains(getGridEntry(r).AudibleProductId);
} }
//Causes repainting of the DataGridView //Causes repainting of the DataGridView
bindingContext.ResumeBinding(); bindingContext.ResumeBinding();
VisibleCountChanged?.Invoke(this, _dataGridView.AsEnumerable().Count(r => r.Visible)); VisibleCountChanged?.Invoke(this, _dataGridView.AsEnumerable().Count(r => r.Visible));
} }
#endregion #endregion
#region DataGridView Macro #region DataGridView Macro
private int getRowIndex(Func<GridEntry, bool> func) => _dataGridView.GetRowIdOfBoundItem(func); private int getRowIndex(Func<GridEntry, bool> func) => _dataGridView.GetRowIdOfBoundItem(func);
private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem<GridEntry>(rowIndex); private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem<GridEntry>(rowIndex);
#endregion #endregion
} }
} }

View File

@ -1,75 +1,74 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
namespace LibationWinForms namespace LibationWinForms
{ {
class SortableBindingList2<T> : BindingList<T> where T : IObjectMemberComparable internal class SortableBindingList2<T> : BindingList<T> where T : IObjectMemberComparable
{ {
private bool isSorted; private bool isSorted;
private ListSortDirection listSortDirection; private ListSortDirection listSortDirection;
private PropertyDescriptor propertyDescriptor; private PropertyDescriptor propertyDescriptor;
public SortableBindingList2() : base(new List<T>()) { } public SortableBindingList2() : base(new List<T>()) { }
public SortableBindingList2(IEnumerable<T> enumeration) : base(new List<T>(enumeration)) { } public SortableBindingList2(IEnumerable<T> enumeration) : base(new List<T>(enumeration)) { }
private ObjectMemberComparer<T> Comparer { get; } = new(); private ObjectMemberComparer<T> Comparer { get; } = new();
protected override bool SupportsSortingCore => true; protected override bool SupportsSortingCore => true;
protected override bool SupportsSearchingCore => true; protected override bool SupportsSearchingCore => true;
protected override bool IsSortedCore => isSorted; protected override bool IsSortedCore => isSorted;
protected override PropertyDescriptor SortPropertyCore => propertyDescriptor; protected override PropertyDescriptor SortPropertyCore => propertyDescriptor;
protected override ListSortDirection SortDirectionCore => listSortDirection; protected override ListSortDirection SortDirectionCore => listSortDirection;
protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction)
{ {
List<T> itemsList = (List<T>)Items; List<T> itemsList = (List<T>)Items;
Comparer.PropertyName = property.Name; Comparer.PropertyName = property.Name;
Comparer.Direction = direction; Comparer.Direction = direction;
//Array.Sort() and List<T>.Sort() are unstable sorts. OrderBy is stable. //Array.Sort() and List<T>.Sort() are unstable sorts. OrderBy is stable.
var sortedItems = itemsList.OrderBy((ge) => ge, Comparer).ToList(); var sortedItems = itemsList.OrderBy((ge) => ge, Comparer).ToList();
itemsList.Clear(); itemsList.Clear();
itemsList.AddRange(sortedItems); itemsList.AddRange(sortedItems);
propertyDescriptor = property; propertyDescriptor = property;
listSortDirection = direction; listSortDirection = direction;
isSorted = true; isSorted = true;
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
} }
protected override void RemoveSortCore() protected override void RemoveSortCore()
{ {
isSorted = false; isSorted = false;
propertyDescriptor = base.SortPropertyCore; propertyDescriptor = base.SortPropertyCore;
listSortDirection = base.SortDirectionCore; listSortDirection = base.SortDirectionCore;
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
} }
//NOTE: Libation does not currently use BindingSource.Find anywhere, //NOTE: Libation does not currently use BindingSource.Find anywhere,
//so this override may be removed (along with SupportsSearchingCore) //so this override may be removed (along with SupportsSearchingCore)
protected override int FindCore(PropertyDescriptor property, object key) protected override int FindCore(PropertyDescriptor property, object key)
{ {
int count = Count; int count = Count;
System.Collections.IComparer valueComparer = null; System.Collections.IComparer valueComparer = null;
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
{ {
T element = this[i]; T element = this[i];
var elemValue = element.GetMemberValue(property.Name); var elemValue = element.GetMemberValue(property.Name);
valueComparer ??= element.GetMemberComparer(elemValue.GetType()); valueComparer ??= element.GetMemberComparer(elemValue.GetType());
if (valueComparer.Compare(elemValue, key) == 0) if (valueComparer.Compare(elemValue, key) == 0)
{ {
return i; return i;
} }
} }
return -1; return -1;
} }
} }
} }