mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-05-04 13:44:39 -04:00
Move ProcessQueueViewModel logic into LibationUiBase
Fix UI bug in classic when queue is in popped-out mode.
This commit is contained in:
@@ -1,495 +1,319 @@
|
||||
using System;
|
||||
using LibationFileManager;
|
||||
using LibationUiBase;
|
||||
using LibationUiBase.ProcessQueue;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using ApplicationServices;
|
||||
using LibationFileManager;
|
||||
using LibationUiBase;
|
||||
using LibationUiBase.ProcessQueue;
|
||||
|
||||
namespace LibationWinForms.ProcessQueue
|
||||
#nullable enable
|
||||
namespace LibationWinForms.ProcessQueue;
|
||||
|
||||
internal partial class ProcessQueueControl : UserControl
|
||||
{
|
||||
internal partial class ProcessQueueControl : UserControl, ILogForm, IProcessQueue
|
||||
public ProcessQueueViewModel ViewModel { get; } = new();
|
||||
public ToolStripButton PopoutButton { get; } = new()
|
||||
{
|
||||
private TrackedQueue<ProcessBookViewModel> Queue = new();
|
||||
private readonly LogMe Logger;
|
||||
private int QueuedCount
|
||||
{
|
||||
set
|
||||
{
|
||||
queueNumberLbl.Text = value.ToString();
|
||||
queueNumberLbl.Visible = value > 0;
|
||||
}
|
||||
}
|
||||
private int ErrorCount
|
||||
{
|
||||
set
|
||||
{
|
||||
errorNumberLbl.Text = value.ToString();
|
||||
errorNumberLbl.Visible = value > 0;
|
||||
}
|
||||
}
|
||||
DisplayStyle = ToolStripItemDisplayStyle.Text,
|
||||
Name = nameof(PopoutButton),
|
||||
Text = "Pop Out",
|
||||
TextAlign = ContentAlignment.MiddleCenter,
|
||||
Alignment = ToolStripItemAlignment.Right,
|
||||
Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
|
||||
};
|
||||
|
||||
private int CompletedCount
|
||||
{
|
||||
set
|
||||
{
|
||||
completedNumberLbl.Text = value.ToString();
|
||||
completedNumberLbl.Visible = value > 0;
|
||||
}
|
||||
}
|
||||
public ProcessQueueControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
public Task QueueRunner { get; private set; }
|
||||
public bool Running => !QueueRunner?.IsCompleted ?? false;
|
||||
public ToolStripButton popoutBtn = new();
|
||||
var speedLimitMBps = Configuration.Instance.DownloadSpeedLimit / 1024m / 1024;
|
||||
numericUpDown1.Value = speedLimitMBps > numericUpDown1.Maximum || speedLimitMBps < numericUpDown1.Minimum ? 0 : speedLimitMBps;
|
||||
statusStrip1.Items.Add(PopoutButton);
|
||||
|
||||
public ProcessQueueControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
virtualFlowControl2.RequestData += VirtualFlowControl1_RequestData;
|
||||
virtualFlowControl2.ButtonClicked += VirtualFlowControl2_ButtonClicked;
|
||||
|
||||
var speedLimitMBps = Configuration.Instance.DownloadSpeedLimit / 1024m / 1024;
|
||||
numericUpDown1.Value = speedLimitMBps > numericUpDown1.Maximum || speedLimitMBps < numericUpDown1.Minimum ? 0 : speedLimitMBps;
|
||||
ViewModel.LogWritten += (_, text) => WriteLine(text);
|
||||
ViewModel.PropertyChanged += ProcessQueue_PropertyChanged;
|
||||
ViewModel.BookPropertyChanged += ProcessBook_PropertyChanged;
|
||||
Load += ProcessQueueControl_Load;
|
||||
}
|
||||
|
||||
popoutBtn.DisplayStyle = ToolStripItemDisplayStyle.Text;
|
||||
popoutBtn.Name = "popoutBtn";
|
||||
popoutBtn.Text = "Pop Out";
|
||||
popoutBtn.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
popoutBtn.Alignment = ToolStripItemAlignment.Right;
|
||||
popoutBtn.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
statusStrip1.Items.Add(popoutBtn);
|
||||
private void ProcessQueueControl_Load(object? sender, EventArgs e)
|
||||
{
|
||||
if (DesignMode) return;
|
||||
ProcessQueue_PropertyChanged(this, new PropertyChangedEventArgs(null));
|
||||
}
|
||||
|
||||
Logger = LogMe.RegisterForm(this);
|
||||
public void WriteLine(string text)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
logDGV.Rows.Add(DateTime.Now, text.Trim());
|
||||
}
|
||||
|
||||
virtualFlowControl2.RequestData += VirtualFlowControl1_RequestData;
|
||||
virtualFlowControl2.ButtonClicked += VirtualFlowControl2_ButtonClicked;
|
||||
private async void cancelAllBtn_Click(object? sender, EventArgs e)
|
||||
{
|
||||
ViewModel.Queue.ClearQueue();
|
||||
if (ViewModel.Queue.Current is not null)
|
||||
await ViewModel.Queue.Current.CancelAsync();
|
||||
virtualFlowControl2.VirtualControlCount = ViewModel.Queue.Count;
|
||||
UpdateAllControls();
|
||||
}
|
||||
|
||||
Queue.QueuededCountChanged += Queue_QueuededCountChanged;
|
||||
Queue.CompletedCountChanged += Queue_CompletedCountChanged;
|
||||
|
||||
Load += ProcessQueueControl_Load;
|
||||
}
|
||||
|
||||
private void ProcessQueueControl_Load(object sender, EventArgs e)
|
||||
{
|
||||
if (DesignMode) return;
|
||||
private void btnClearFinished_Click(object? sender, EventArgs e)
|
||||
{
|
||||
ViewModel.Queue.ClearCompleted();
|
||||
virtualFlowControl2.VirtualControlCount = ViewModel.Queue.Count;
|
||||
UpdateAllControls();
|
||||
|
||||
if (!ViewModel.Running)
|
||||
runningTimeLbl.Text = string.Empty;
|
||||
QueuedCount = 0;
|
||||
ErrorCount = 0;
|
||||
CompletedCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private bool isBookInQueue(DataLayer.LibraryBook libraryBook)
|
||||
private void clearLogBtn_Click(object? sender, EventArgs e)
|
||||
{
|
||||
logDGV.Rows.Clear();
|
||||
}
|
||||
|
||||
private void LogCopyBtn_Click(object? sender, EventArgs e)
|
||||
{
|
||||
string logText = string.Join("\r\n", logDGV.Rows.Cast<DataGridViewRow>().Select(r => $"{r.Cells[0].Value}\t{r.Cells[1].Value}"));
|
||||
Clipboard.SetDataObject(logText, false, 5, 150);
|
||||
}
|
||||
|
||||
private void LogDGV_Resize(object? sender, EventArgs e)
|
||||
{
|
||||
logDGV.Columns[1].Width = logDGV.Width - logDGV.Columns[0].Width;
|
||||
}
|
||||
|
||||
#region View-Model update event handling
|
||||
|
||||
private void ProcessBook_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (sender is not ProcessBookViewModel pbvm)
|
||||
return;
|
||||
|
||||
int index = ViewModel.Queue.IndexOf(pbvm);
|
||||
UpdateControl(index, e.PropertyName);
|
||||
}
|
||||
|
||||
private void ProcessQueue_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName is null or nameof(ViewModel.QueuedCount))
|
||||
{
|
||||
var entry = Queue.FirstOrDefault(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId);
|
||||
if (entry == null)
|
||||
return false;
|
||||
else if (entry.Status is ProcessBookStatus.Cancelled or ProcessBookStatus.Failed)
|
||||
return !Queue.RemoveCompleted(entry);
|
||||
else
|
||||
return true;
|
||||
queueNumberLbl.Text = ViewModel.QueuedCount.ToString();
|
||||
queueNumberLbl.Visible = ViewModel.QueuedCount > 0;
|
||||
virtualFlowControl2.VirtualControlCount = ViewModel.Queue.Count;
|
||||
}
|
||||
|
||||
public bool RemoveCompleted(DataLayer.LibraryBook libraryBook)
|
||||
=> Queue.FirstOrDefault(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId) is ProcessBookViewModel entry
|
||||
&& entry.Status is ProcessBookStatus.Completed
|
||||
&& Queue.RemoveCompleted(entry);
|
||||
|
||||
public void AddDownloadPdf(IEnumerable<DataLayer.LibraryBook> entries)
|
||||
if (e.PropertyName is null or nameof(ViewModel.ErrorCount))
|
||||
{
|
||||
List<ProcessBookViewModel> procs = new();
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (isBookInQueue(entry))
|
||||
continue;
|
||||
|
||||
ProcessBookViewModel pbook = new(entry, Logger);
|
||||
pbook.PropertyChanged += Pbook_DataAvailable;
|
||||
pbook.AddDownloadPdf();
|
||||
procs.Add(pbook);
|
||||
}
|
||||
|
||||
Serilog.Log.Logger.Information("Queueing {count} books", procs.Count);
|
||||
AddToQueue(procs);
|
||||
errorNumberLbl.Text = ViewModel.ErrorCount.ToString();
|
||||
errorNumberLbl.Visible = ViewModel.ErrorCount > 0;
|
||||
}
|
||||
|
||||
public void AddDownloadDecrypt(IEnumerable<DataLayer.LibraryBook> entries)
|
||||
if (e.PropertyName is null or nameof(ViewModel.CompletedCount))
|
||||
{
|
||||
List<ProcessBookViewModel> procs = new();
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (isBookInQueue(entry))
|
||||
continue;
|
||||
|
||||
ProcessBookViewModel pbook = new(entry, Logger);
|
||||
pbook.PropertyChanged += Pbook_DataAvailable;
|
||||
pbook.AddDownloadDecryptBook();
|
||||
pbook.AddDownloadPdf();
|
||||
procs.Add(pbook);
|
||||
}
|
||||
|
||||
Serilog.Log.Logger.Information("Queueing {count} books", procs.Count);
|
||||
AddToQueue(procs);
|
||||
completedNumberLbl.Text = ViewModel.CompletedCount.ToString();
|
||||
completedNumberLbl.Visible = ViewModel.CompletedCount > 0;
|
||||
}
|
||||
|
||||
public void AddConvertMp3(IEnumerable<DataLayer.LibraryBook> entries)
|
||||
if (e.PropertyName is null or nameof(ViewModel.Progress))
|
||||
{
|
||||
List<ProcessBookViewModel> procs = new();
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (isBookInQueue(entry))
|
||||
continue;
|
||||
|
||||
ProcessBookViewModel pbook = new(entry, Logger);
|
||||
pbook.PropertyChanged += Pbook_DataAvailable;
|
||||
pbook.AddConvertToMp3();
|
||||
procs.Add(pbook);
|
||||
}
|
||||
|
||||
Serilog.Log.Logger.Information("Queueing {count} books", procs.Count);
|
||||
AddToQueue(procs);
|
||||
toolStripProgressBar1.Maximum = ViewModel.Queue.Count;
|
||||
toolStripProgressBar1.Value = ViewModel.Queue.Completed.Count;
|
||||
}
|
||||
private void AddToQueue(IEnumerable<ProcessBookViewModel> pbook)
|
||||
if (e.PropertyName is null or nameof(ViewModel.ProgressBarVisible))
|
||||
{
|
||||
if (!IsHandleCreated)
|
||||
CreateControl();
|
||||
|
||||
BeginInvoke(() =>
|
||||
{
|
||||
Queue.Enqueue(pbook);
|
||||
if (!Running)
|
||||
QueueRunner = QueueLoop();
|
||||
});
|
||||
toolStripProgressBar1.Visible = ViewModel.ProgressBarVisible;
|
||||
}
|
||||
|
||||
DateTime StartingTime;
|
||||
private async Task QueueLoop()
|
||||
if (e.PropertyName is null or nameof(ViewModel.RunningTime))
|
||||
{
|
||||
try
|
||||
{
|
||||
Serilog.Log.Logger.Information("Begin processing queue");
|
||||
|
||||
StartingTime = DateTime.Now;
|
||||
counterTimer.Start();
|
||||
|
||||
bool shownServiceOutageMessage = false;
|
||||
|
||||
while (Queue.MoveNext())
|
||||
{
|
||||
var nextBook = Queue.Current;
|
||||
|
||||
Serilog.Log.Logger.Information("Begin processing queued item. {item_LibraryBook}", nextBook?.LibraryBook);
|
||||
|
||||
var result = await nextBook.ProcessOneAsync();
|
||||
|
||||
Serilog.Log.Logger.Information("Completed processing queued item: {item_LibraryBook}\r\nResult: {result}", nextBook?.LibraryBook, result);
|
||||
|
||||
if (result == ProcessBookResult.ValidationFail)
|
||||
Queue.ClearCurrent();
|
||||
else if (result == ProcessBookResult.FailedAbort)
|
||||
Queue.ClearQueue();
|
||||
else if (result == ProcessBookResult.FailedSkip)
|
||||
nextBook.LibraryBook.UpdateBookStatus(DataLayer.LiberatedStatus.Error);
|
||||
else if (result == ProcessBookResult.LicenseDeniedPossibleOutage && !shownServiceOutageMessage)
|
||||
{
|
||||
MessageBox.Show(@$"
|
||||
You were denied a content license for {nextBook.LibraryBook.Book.TitleWithSubtitle}
|
||||
|
||||
This error appears to be caused by a temporary interruption of service that sometimes affects Libation's users. This type of error usually resolves itself in 1 to 2 days, and in the meantime you should still be able to access your books through Audible's website or app.
|
||||
",
|
||||
"Possible Interruption of Service",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Asterisk);
|
||||
shownServiceOutageMessage = true;
|
||||
}
|
||||
}
|
||||
Serilog.Log.Logger.Information("Completed processing queue");
|
||||
|
||||
Queue_CompletedCountChanged(this, 0);
|
||||
counterTimer.Stop();
|
||||
virtualFlowControl2.VirtualControlCount = Queue.Count;
|
||||
UpdateAllControls();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "An error was encountered while processing queued items");
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteLine(string text)
|
||||
{
|
||||
if (IsDisposed) return;
|
||||
|
||||
var timeStamp = DateTime.Now;
|
||||
Invoke(() => logDGV.Rows.Add(timeStamp, text.Trim()));
|
||||
}
|
||||
|
||||
#region Control event handlers
|
||||
|
||||
private void Queue_CompletedCountChanged(object sender, int e)
|
||||
{
|
||||
int errCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.FailedAbort or ProcessBookResult.FailedSkip or ProcessBookResult.FailedRetry or ProcessBookResult.ValidationFail);
|
||||
int completeCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.Success);
|
||||
|
||||
ErrorCount = errCount;
|
||||
CompletedCount = completeCount;
|
||||
UpdateProgressBar();
|
||||
}
|
||||
private void Queue_QueuededCountChanged(object sender, int cueCount)
|
||||
{
|
||||
QueuedCount = cueCount;
|
||||
virtualFlowControl2.VirtualControlCount = Queue.Count;
|
||||
UpdateProgressBar();
|
||||
}
|
||||
private void UpdateProgressBar()
|
||||
{
|
||||
toolStripProgressBar1.Maximum = Queue.Count;
|
||||
toolStripProgressBar1.Value = Queue.Completed.Count;
|
||||
}
|
||||
|
||||
private async void cancelAllBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
Queue.ClearQueue();
|
||||
if (Queue.Current is not null)
|
||||
await Queue.Current.CancelAsync();
|
||||
virtualFlowControl2.VirtualControlCount = Queue.Count;
|
||||
UpdateAllControls();
|
||||
}
|
||||
|
||||
private void btnClearFinished_Click(object sender, EventArgs e)
|
||||
{
|
||||
Queue.ClearCompleted();
|
||||
virtualFlowControl2.VirtualControlCount = Queue.Count;
|
||||
UpdateAllControls();
|
||||
|
||||
if (!Running)
|
||||
runningTimeLbl.Text = string.Empty;
|
||||
}
|
||||
|
||||
private void CounterTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
string timeToStr(TimeSpan time)
|
||||
{
|
||||
string minsSecs = $"{time:mm\\:ss}";
|
||||
if (time.TotalHours >= 1)
|
||||
return $"{time.TotalHours:F0}:{minsSecs}";
|
||||
return minsSecs;
|
||||
}
|
||||
|
||||
if (Running)
|
||||
runningTimeLbl.Text = timeToStr(DateTime.Now - StartingTime);
|
||||
}
|
||||
|
||||
private void clearLogBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
logDGV.Rows.Clear();
|
||||
}
|
||||
|
||||
private void LogCopyBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
string logText = string.Join("\r\n", logDGV.Rows.Cast<DataGridViewRow>().Select(r => $"{r.Cells[0].Value}\t{r.Cells[1].Value}"));
|
||||
Clipboard.SetDataObject(logText, false, 5, 150);
|
||||
}
|
||||
|
||||
private void LogDGV_Resize(object sender, EventArgs e)
|
||||
{
|
||||
logDGV.Columns[1].Width = logDGV.Width - logDGV.Columns[0].Width;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region View-Model update event handling
|
||||
|
||||
/// <summary>
|
||||
/// Index of the first <see cref="ProcessBookViewModel"/> visible in the <see cref="VirtualFlowControl"/>
|
||||
/// </summary>
|
||||
private int FirstVisible = 0;
|
||||
/// <summary>
|
||||
/// Number of <see cref="ProcessBookViewModel"/> visible in the <see cref="VirtualFlowControl"/>
|
||||
/// </summary>
|
||||
private int NumVisible = 0;
|
||||
/// <summary>
|
||||
/// Controls displaying the <see cref="ProcessBookViewModel"/> state, starting with <see cref="FirstVisible"/>
|
||||
/// </summary>
|
||||
private IReadOnlyList<ProcessBookControl> Panels;
|
||||
|
||||
/// <summary>
|
||||
/// Updates the display of a single <see cref="ProcessBookControl"/> at <paramref name="queueIndex"/> within <see cref="Queue"/>
|
||||
/// </summary>
|
||||
/// <param name="queueIndex">index of the <see cref="ProcessBookViewModel"/> within the <see cref="Queue"/></param>
|
||||
/// <param name="propertyName">The nme of the property that needs updating. If null, all properties are updated.</param>
|
||||
private void UpdateControl(int queueIndex, string propertyName = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
int i = queueIndex - FirstVisible;
|
||||
|
||||
if (i > NumVisible || i < 0) return;
|
||||
|
||||
var proc = Queue[queueIndex];
|
||||
|
||||
Invoke(() =>
|
||||
{
|
||||
Panels[i].SuspendLayout();
|
||||
if (propertyName is null or nameof(proc.Cover))
|
||||
Panels[i].SetCover(proc.Cover as Image);
|
||||
if (propertyName is null or nameof(proc.Title) or nameof(proc.Author) or nameof(proc.Narrator))
|
||||
Panels[i].SetBookInfo(proc.BookText);
|
||||
|
||||
if (proc.Result != ProcessBookResult.None)
|
||||
{
|
||||
Panels[i].SetResult(proc.Result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (propertyName is null or nameof(proc.Status))
|
||||
Panels[i].SetStatus(proc.Status);
|
||||
if (propertyName is null or nameof(proc.Progress))
|
||||
Panels[i].SetProgrss(proc.Progress);
|
||||
if (propertyName is null or nameof(proc.TimeRemaining))
|
||||
Panels[i].SetRemainingTime(proc.TimeRemaining);
|
||||
Panels[i].ResumeLayout();
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "Error updating the queued item's display.");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAllControls()
|
||||
{
|
||||
int numToShow = Math.Min(NumVisible, Queue.Count - FirstVisible);
|
||||
|
||||
for (int i = 0; i < numToShow; i++)
|
||||
UpdateControl(FirstVisible + i);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// View notified the model that a botton was clicked
|
||||
/// </summary>
|
||||
/// <param name="queueIndex">index of the <see cref="ProcessBookViewModel"/> within <see cref="Queue"/></param>
|
||||
/// <param name="panelClicked">The clicked control to update</param>
|
||||
private async void VirtualFlowControl2_ButtonClicked(int queueIndex, string buttonName, ProcessBookControl panelClicked)
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessBookViewModel item = Queue[queueIndex];
|
||||
if (buttonName == nameof(panelClicked.cancelBtn))
|
||||
{
|
||||
if (item is not null)
|
||||
await item.CancelAsync();
|
||||
Queue.RemoveQueued(item);
|
||||
virtualFlowControl2.VirtualControlCount = Queue.Count;
|
||||
}
|
||||
else if (buttonName == nameof(panelClicked.moveFirstBtn))
|
||||
{
|
||||
Queue.MoveQueuePosition(item, QueuePosition.Fisrt);
|
||||
UpdateAllControls();
|
||||
}
|
||||
else if (buttonName == nameof(panelClicked.moveUpBtn))
|
||||
{
|
||||
Queue.MoveQueuePosition(item, QueuePosition.OneUp);
|
||||
UpdateControl(queueIndex);
|
||||
if (queueIndex > 0)
|
||||
UpdateControl(queueIndex - 1);
|
||||
}
|
||||
else if (buttonName == nameof(panelClicked.moveDownBtn))
|
||||
{
|
||||
Queue.MoveQueuePosition(item, QueuePosition.OneDown);
|
||||
UpdateControl(queueIndex);
|
||||
if (queueIndex + 1 < Queue.Count)
|
||||
UpdateControl(queueIndex + 1);
|
||||
}
|
||||
else if (buttonName == nameof(panelClicked.moveLastBtn))
|
||||
{
|
||||
Queue.MoveQueuePosition(item, QueuePosition.Last);
|
||||
UpdateAllControls();
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "Error handling button click from queued item");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// View needs updating
|
||||
/// </summary>
|
||||
private void VirtualFlowControl1_RequestData(int firstIndex, int numVisible, IReadOnlyList<ProcessBookControl> panelsToFill)
|
||||
{
|
||||
FirstVisible = firstIndex;
|
||||
NumVisible = numVisible;
|
||||
Panels = panelsToFill;
|
||||
UpdateAllControls();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Model updates the view
|
||||
/// </summary>
|
||||
private void Pbook_DataAvailable(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
int index = Queue.IndexOf((ProcessBookViewModel)sender);
|
||||
UpdateControl(index, e.PropertyName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
|
||||
{
|
||||
var newValue = (long)(numericUpDown1.Value * 1024 * 1024);
|
||||
|
||||
var config = Configuration.Instance;
|
||||
config.DownloadSpeedLimit = newValue;
|
||||
if (config.DownloadSpeedLimit > newValue)
|
||||
numericUpDown1.Value =
|
||||
numericUpDown1.Value == 0.01m ? config.DownloadSpeedLimit / 1024m / 1024
|
||||
: 0;
|
||||
|
||||
numericUpDown1.Increment =
|
||||
numericUpDown1.Value > 100 ? 10
|
||||
: numericUpDown1.Value > 10 ? 1
|
||||
: numericUpDown1.Value > 1 ? 0.1m
|
||||
: 0.01m;
|
||||
|
||||
numericUpDown1.DecimalPlaces =
|
||||
numericUpDown1.Value >= 10 ? 0
|
||||
: numericUpDown1.Value >= 1 ? 1
|
||||
: 2;
|
||||
runningTimeLbl.Text = ViewModel.RunningTime;
|
||||
}
|
||||
}
|
||||
public class NumericUpDownSuffix : NumericUpDown
|
||||
|
||||
/// <summary>
|
||||
/// Index of the first <see cref="ProcessBookViewModel"/> visible in the <see cref="VirtualFlowControl"/>
|
||||
/// </summary>
|
||||
private int FirstVisible = 0;
|
||||
/// <summary>
|
||||
/// Number of <see cref="ProcessBookViewModel"/> visible in the <see cref="VirtualFlowControl"/>
|
||||
/// </summary>
|
||||
private int NumVisible = 0;
|
||||
/// <summary>
|
||||
/// Controls displaying the <see cref="ProcessBookViewModel"/> state, starting with <see cref="FirstVisible"/>
|
||||
/// </summary>
|
||||
private IReadOnlyList<ProcessBookControl>? Panels;
|
||||
|
||||
/// <summary>
|
||||
/// Updates the display of a single <see cref="ProcessBookControl"/> at <paramref name="queueIndex"/> within <see cref="Queue"/>
|
||||
/// </summary>
|
||||
/// <param name="queueIndex">index of the <see cref="ProcessBookViewModel"/> within the <see cref="Queue"/></param>
|
||||
/// <param name="propertyName">The nme of the property that needs updating. If null, all properties are updated.</param>
|
||||
private void UpdateControl(int queueIndex, string? propertyName = null)
|
||||
{
|
||||
[Description("Suffix displayed after numeric value."), Category("Data")]
|
||||
[Browsable(true)]
|
||||
[EditorBrowsable(EditorBrowsableState.Always)]
|
||||
[DisallowNull]
|
||||
public string Suffix
|
||||
try
|
||||
{
|
||||
get => _suffix;
|
||||
set
|
||||
int i = queueIndex - FirstVisible;
|
||||
|
||||
if (Panels is null || i > NumVisible || i < 0) return;
|
||||
|
||||
var proc = ViewModel.Queue[queueIndex];
|
||||
|
||||
Invoke(() =>
|
||||
{
|
||||
base.Text = string.IsNullOrEmpty(_suffix) ? base.Text : base.Text.Replace(_suffix, value);
|
||||
_suffix = value;
|
||||
ChangingText = true;
|
||||
Panels[i].SuspendLayout();
|
||||
if (propertyName is null or nameof(proc.Cover))
|
||||
Panels[i].SetCover(proc.Cover as Image);
|
||||
if (propertyName is null or nameof(proc.Title) or nameof(proc.Author) or nameof(proc.Narrator))
|
||||
Panels[i].SetBookInfo($"{proc.Title}\r\nBy {proc.Author}\r\nNarrated by {proc.Narrator}");
|
||||
|
||||
if (proc.Result != ProcessBookResult.None)
|
||||
{
|
||||
Panels[i].SetResult(proc.Result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (propertyName is null or nameof(proc.Status))
|
||||
Panels[i].SetStatus(proc.Status);
|
||||
if (propertyName is null or nameof(proc.Progress))
|
||||
Panels[i].SetProgrss(proc.Progress);
|
||||
if (propertyName is null or nameof(proc.TimeRemaining))
|
||||
Panels[i].SetRemainingTime(proc.TimeRemaining);
|
||||
Panels[i].ResumeLayout();
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "Error updating the queued item's display.");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAllControls()
|
||||
{
|
||||
int numToShow = Math.Min(NumVisible, ViewModel.Queue.Count - FirstVisible);
|
||||
|
||||
for (int i = 0; i < numToShow; i++)
|
||||
UpdateControl(FirstVisible + i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// View notified the model that a botton was clicked
|
||||
/// </summary>
|
||||
/// <param name="queueIndex">index of the <see cref="ProcessBookViewModel"/> within <see cref="Queue"/></param>
|
||||
/// <param name="panelClicked">The clicked control to update</param>
|
||||
private async void VirtualFlowControl2_ButtonClicked(int queueIndex, string buttonName, ProcessBookControl panelClicked)
|
||||
{
|
||||
try
|
||||
{
|
||||
var item = ViewModel.Queue[queueIndex];
|
||||
if (buttonName == nameof(panelClicked.cancelBtn))
|
||||
{
|
||||
if (item is not null)
|
||||
{
|
||||
await item.CancelAsync();
|
||||
if (ViewModel.Queue.RemoveQueued(item))
|
||||
virtualFlowControl2.VirtualControlCount = ViewModel.Queue.Count;
|
||||
}
|
||||
}
|
||||
else if (buttonName == nameof(panelClicked.moveFirstBtn))
|
||||
{
|
||||
ViewModel.Queue.MoveQueuePosition(item, QueuePosition.Fisrt);
|
||||
UpdateAllControls();
|
||||
}
|
||||
else if (buttonName == nameof(panelClicked.moveUpBtn))
|
||||
{
|
||||
ViewModel.Queue.MoveQueuePosition(item, QueuePosition.OneUp);
|
||||
UpdateControl(queueIndex);
|
||||
if (queueIndex > 0)
|
||||
UpdateControl(queueIndex - 1);
|
||||
}
|
||||
else if (buttonName == nameof(panelClicked.moveDownBtn))
|
||||
{
|
||||
ViewModel.Queue.MoveQueuePosition(item, QueuePosition.OneDown);
|
||||
UpdateControl(queueIndex);
|
||||
if (queueIndex + 1 < ViewModel.Queue.Count)
|
||||
UpdateControl(queueIndex + 1);
|
||||
}
|
||||
else if (buttonName == nameof(panelClicked.moveLastBtn))
|
||||
{
|
||||
ViewModel.Queue.MoveQueuePosition(item, QueuePosition.Last);
|
||||
UpdateAllControls();
|
||||
}
|
||||
}
|
||||
private string _suffix = string.Empty;
|
||||
public override string Text
|
||||
catch(Exception ex)
|
||||
{
|
||||
get => string.IsNullOrEmpty(Suffix) ? base.Text : base.Text.Replace(Suffix, string.Empty);
|
||||
set
|
||||
{
|
||||
if (Value == Minimum)
|
||||
base.Text = "∞";
|
||||
else
|
||||
base.Text = value + Suffix;
|
||||
}
|
||||
Serilog.Log.Logger.Error(ex, "Error handling button click from queued item");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// View needs updating
|
||||
/// </summary>
|
||||
private void VirtualFlowControl1_RequestData(int firstIndex, int numVisible, IReadOnlyList<ProcessBookControl> panelsToFill)
|
||||
{
|
||||
FirstVisible = firstIndex;
|
||||
NumVisible = numVisible;
|
||||
Panels = panelsToFill;
|
||||
UpdateAllControls();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void numericUpDown1_ValueChanged(object? sender, EventArgs e)
|
||||
{
|
||||
var newValue = (long)(numericUpDown1.Value * 1024 * 1024);
|
||||
|
||||
var config = Configuration.Instance;
|
||||
config.DownloadSpeedLimit = newValue;
|
||||
if (config.DownloadSpeedLimit > newValue)
|
||||
numericUpDown1.Value =
|
||||
numericUpDown1.Value == 0.01m ? config.DownloadSpeedLimit / 1024m / 1024
|
||||
: 0;
|
||||
|
||||
numericUpDown1.Increment =
|
||||
numericUpDown1.Value > 100 ? 10
|
||||
: numericUpDown1.Value > 10 ? 1
|
||||
: numericUpDown1.Value > 1 ? 0.1m
|
||||
: 0.01m;
|
||||
|
||||
numericUpDown1.DecimalPlaces =
|
||||
numericUpDown1.Value >= 10 ? 0
|
||||
: numericUpDown1.Value >= 1 ? 1
|
||||
: 2;
|
||||
}
|
||||
}
|
||||
|
||||
public class NumericUpDownSuffix : NumericUpDown
|
||||
{
|
||||
[Description("Suffix displayed after numeric value."), Category("Data")]
|
||||
[Browsable(true)]
|
||||
[EditorBrowsable(EditorBrowsableState.Always)]
|
||||
[DisallowNull]
|
||||
public string Suffix
|
||||
{
|
||||
get => _suffix;
|
||||
set
|
||||
{
|
||||
base.Text = string.IsNullOrEmpty(_suffix) ? base.Text : base.Text.Replace(_suffix, value);
|
||||
_suffix = value;
|
||||
ChangingText = true;
|
||||
}
|
||||
}
|
||||
private string _suffix = string.Empty;
|
||||
|
||||
[AllowNull]
|
||||
public override string Text
|
||||
{
|
||||
get => string.IsNullOrEmpty(Suffix) ? base.Text : base.Text.Replace(Suffix, string.Empty);
|
||||
set
|
||||
{
|
||||
if (Value == Minimum)
|
||||
base.Text = "∞";
|
||||
else
|
||||
base.Text = value + Suffix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user