diff --git a/Source/LibationAvalonia/Views/ProcessQueueControl.axaml b/Source/LibationAvalonia/Views/ProcessQueueControl.axaml
index bbb66db3..07b70824 100644
--- a/Source/LibationAvalonia/Views/ProcessQueueControl.axaml
+++ b/Source/LibationAvalonia/Views/ProcessQueueControl.axaml
@@ -82,6 +82,18 @@
Maximum="{Binding MaxAllowedConcurrentDownloads}"
Increment="1"
FormatString="0" />
+
+
Configuration.MinConcurrentDownloads;
///
- /// The spinner's upper bound: what this machine can usefully manage, but never below what is
- /// already stored. A spinner whose maximum sits under the stored value coerces its displayed
- /// value down to the maximum and, being two-way, writes that back - which would overwrite an 8
- /// chosen on a larger machine with a 2 just for opening the panel on a smaller one. Raising the
- /// bound to meet the stored value leaves the setting alone; lowering it is still the user's to do.
+ /// The spinner's upper bound: the flat hard limit, so both UIs agree and the bound is the same
+ /// number on every machine. Bounding it by machine capability instead makes the control lie in two
+ /// directions at once - a spinner whose maximum sits under the stored value coerces its display
+ /// down and, being two-way, writes that back, so opening the panel on a smaller machine would
+ /// overwrite an 8 chosen on a larger one; while raising the bound to meet the stored value leaves
+ /// a stored 8 showing 8 on a two-core box that will only ever run 2. Capability is applied where
+ /// it actually bites, in , and
+ /// says so on screen rather than leaving the two to disagree in silence.
///
- public int MaxAllowedConcurrentDownloads
- => Math.Max(Configuration.MaxAllowedConcurrentDownloads, MaxConcurrentDownloads);
+ public int MaxAllowedConcurrentDownloads => Configuration.ConcurrentDownloadsHardLimit;
+
+ ///
+ /// Sits beside the spinner when this machine cannot deliver the number the user chose, and is
+ /// null when it can. Closes the gap left by bounding the control at the hard limit: the setting
+ /// keeps saying what was asked for, and this says what will happen.
+ ///
+ public string? ConcurrencyHint
+ => EffectiveConcurrentDownloads < MaxConcurrentDownloads
+ ? $"({EffectiveConcurrentDownloads} on this machine)"
+ : null;
public string? RunningTime { get => field; set => RaiseAndSetIfChanged(ref field, value); }
public bool ProgressBarVisible { get => field; set => RaiseAndSetIfChanged(ref field, value); }
public bool AnyCompleted => CompletedCount > 0;
diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs
index 6fc3dd99..eda196f5 100644
--- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs
+++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs
@@ -41,6 +41,9 @@ internal partial class ProcessQueueControl : UserControl
autoScrollChk.CheckedChanged += (s, e) => ViewModel.AutoScrollQueue = autoScrollChk.Checked;
concurrencyNum.Minimum = ViewModel.MinConcurrentDownloads;
+ // The flat hard limit, not this machine's capability - the same bound Chardonnay uses. A
+ // maximum below the stored value would coerce the display down and write it back, losing an 8
+ // chosen elsewhere just for opening the panel here.
concurrencyNum.Maximum = ViewModel.MaxAllowedConcurrentDownloads;
concurrencyNum.ValueChanged += (s, e) => ViewModel.MaxConcurrentDownloads = (int)concurrencyNum.Value;
ProcessQueue_PropertyChanged(this, new PropertyChangedEventArgs(null));
@@ -141,8 +144,28 @@ internal partial class ProcessQueueControl : UserControl
autoScrollChk.Checked = ViewModel.AutoScrollQueue;
if (e.PropertyName is null or nameof(ViewModel.MaxConcurrentDownloads))
concurrencyNum.Value = ViewModel.MaxConcurrentDownloads;
+ if (e.PropertyName is null or nameof(ViewModel.ConcurrencyHint))
+ setConcurrencyHint();
+
+ // The settings table is full at three columns and the panel has no width to spare, so this
+ // says what Chardonnay says in its spare column, in a tooltip instead of another label.
+ void setConcurrencyHint()
+ {
+ const string what = "How many books download and decrypt at the same time. 1 downloads one at a time.";
+ concurrencyHintToolTip.SetToolTip(
+ concurrencyNum,
+ ViewModel.ConcurrencyHint is string hint
+ ? $"{what}\r\nOnly {hint.Trim('(', ')')} - this machine has fewer processors to decrypt them."
+ : what);
+ }
}
+ ///
+ /// Carries . Built here rather than in the
+ /// designer to keep the generated file out of the diff.
+ ///
+ private readonly ToolTip concurrencyHintToolTip = new();
+
///
/// View notified the model that a button was clicked
///