From 17cc302d17517b9e62f8a16867d52def3265d4ff Mon Sep 17 00:00:00 2001 From: Terry MacDonald Date: Sun, 4 Oct 2020 23:01:03 +1300 Subject: [PATCH] [WIP] Partial refactor of applying profile display UI --- HeliosPlus.Shared/ProfileRepository.cs | 49 +-- HeliosPlus/HeliosPlus.csproj | 10 + HeliosPlus/Program.cs | 73 ++++- HeliosPlus/ProgressReporter.cs | 160 ++++++++++ HeliosPlus/ShortcutRepository.cs | 6 +- .../UIForms/ApplyingChangesForm.Designer.cs | 8 +- HeliosPlus/UIForms/ApplyingChangesForm.cs | 16 +- .../UIForms/ApplyingProfileForm.Designer.cs | 149 +++++++++ HeliosPlus/UIForms/ApplyingProfileForm.cs | 300 ++++++++++++++++++ HeliosPlus/UIForms/ApplyingProfileForm.resx | 126 ++++++++ .../UIForms/DisplayProfileForm.Designer.cs | 3 +- HeliosPlus/UIForms/DisplayProfileForm.cs | 3 +- 12 files changed, 859 insertions(+), 44 deletions(-) create mode 100644 HeliosPlus/ProgressReporter.cs create mode 100644 HeliosPlus/UIForms/ApplyingProfileForm.Designer.cs create mode 100644 HeliosPlus/UIForms/ApplyingProfileForm.cs create mode 100644 HeliosPlus/UIForms/ApplyingProfileForm.resx diff --git a/HeliosPlus.Shared/ProfileRepository.cs b/HeliosPlus.Shared/ProfileRepository.cs index 30d9367..4602232 100644 --- a/HeliosPlus.Shared/ProfileRepository.cs +++ b/HeliosPlus.Shared/ProfileRepository.cs @@ -576,7 +576,7 @@ namespace HeliosPlus.Shared // Now lets start by changing the display topology Task applyProfileTopologyTask = Task.Run(() => { - Console.WriteLine("ShortcutRepository/SaveShortcutIconToCache : Applying Profile Topology " + profile.Name); + Console.WriteLine("ProfileRepository/SaveShortcutIconToCache : Applying Profile Topology " + profile.Name); ApplyTopology(profile); }); applyProfileTopologyTask.Wait(); @@ -584,7 +584,7 @@ namespace HeliosPlus.Shared // And then change the path information Task applyProfilePathInfoTask = Task.Run(() => { - Console.WriteLine("ShortcutRepository/SaveShortcutIconToCache : Applying Profile Path " + profile.Name); + Console.WriteLine("ProfileRepository/SaveShortcutIconToCache : Applying Profile Path " + profile.Name); ApplyPathInfo(profile); }); applyProfilePathInfoTask.Wait(); @@ -593,16 +593,17 @@ namespace HeliosPlus.Shared } catch (Exception ex) { - Console.WriteLine($"ShortcutRepository/ApplyTopology exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}"); + Console.WriteLine($"ProfileRepository/ApplyTopology exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}"); return false; } } - private static void ApplyTopology(ProfileItem profile) + public static bool ApplyTopology(ProfileItem profile) { - Debug.Print("ShortcutRepository.ApplyTopology()"); - if (profile == null) - return; + Debug.Print("ProfileRepository.ApplyTopology()"); + + if (!(profile is ProfileItem)) + return false; try { @@ -615,6 +616,7 @@ namespace HeliosPlus.Shared if (surroundTopologies.Length == 0) { + // This profile does not use NVIDIA Surround var currentTopologies = GridTopology.GetGridTopologies(); if (currentTopologies.Any(topology => topology.Rows * topology.Columns > 1)) @@ -625,34 +627,39 @@ namespace HeliosPlus.Shared .Select(displays => new GridTopology(1, 1, new[] { displays })) .ToArray(); } - } - - if (surroundTopologies.Length > 0) + } else if (surroundTopologies.Length > 0) { + // This profile is an NVIDIA Surround profile GridTopology.SetGridTopologies(surroundTopologies, SetDisplayTopologyFlag.MaximizePerformance); } + + return true; } catch (Exception ex) { - Console.WriteLine($"ShortcutRepository/ApplyTopology exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}"); - // ignored + Console.WriteLine($"ProfileRepository/ApplyTopology exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}"); + return false; } } - private static void ApplyPathInfo(ProfileItem profile) + public static bool ApplyPathInfo(ProfileItem profile) { - Debug.Print("ShortcutRepository.ApplyPathInfo()"); - if (profile == null) - return; + Debug.Print("ProfileRepository.ApplyPathInfo()"); + if (!(profile is ProfileItem)) + return false; - if (!profile.IsPossible) + try { - throw new InvalidOperationException( - $"ShortcutRepository/ApplyPathInfo exception: Problem applying the '{profile.Name}' Display Profile! The display configuration changed since this profile is created. Please re-create this profile."); + var pathInfos = profile.Viewports.Select(viewport => viewport.ToPathInfo()).Where(info => info != null).ToArray(); + WindowsDisplayAPI.DisplayConfig.PathInfo.ApplyPathInfos(pathInfos, true, true, true); + return true; + } + catch (Exception ex) + { + Console.WriteLine($"ProfileRepository/ApplyPathInfo exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}"); + return false; } - var pathInfos = profile.Viewports.Select(viewport => viewport.ToPathInfo()).Where(info => info != null).ToArray(); - WindowsDisplayAPI.DisplayConfig.PathInfo.ApplyPathInfos(pathInfos, true, true, true); } public static bool IsValidFilename(string testName) diff --git a/HeliosPlus/HeliosPlus.csproj b/HeliosPlus/HeliosPlus.csproj index 8580f36..62f724b 100644 --- a/HeliosPlus/HeliosPlus.csproj +++ b/HeliosPlus/HeliosPlus.csproj @@ -80,8 +80,15 @@ + + + Form + + + ApplyingProfileForm.cs + @@ -139,6 +146,9 @@ Language.Designer.cs Designer + + ApplyingProfileForm.cs + MainForm.cs Designer diff --git a/HeliosPlus/Program.cs b/HeliosPlus/Program.cs index fa0268f..2f16e0d 100644 --- a/HeliosPlus/Program.cs +++ b/HeliosPlus/Program.cs @@ -19,6 +19,7 @@ using HeliosPlus.UIForms; using System.Net.NetworkInformation; using System.Text.RegularExpressions; using System.Drawing; +using System.Diagnostics.Contracts; namespace HeliosPlus { public enum SupportedProgramMode @@ -329,14 +330,74 @@ namespace HeliosPlus { return true; } -/* public static string GetValidFilename(string uncheckedFilename) + /* public static string GetValidFilename(string uncheckedFilename) + { + string invalid = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); + foreach (char c in invalid) + { + uncheckedFilename = uncheckedFilename.Replace(c.ToString(), ""); + } + return uncheckedFilename; + }*/ + + // ApplyProfile lives here so that the UI works. + public static bool ApplyProfile(ProfileItem profile) { - string invalid = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); - foreach (char c in invalid) + // If we're already on the wanted profile then no need to change! + if (ProfileRepository.IsActiveProfile(profile)) + return true; + + // We need to check if the profile is valid + if (!profile.IsPossible) + return false; + + try { - uncheckedFilename = uncheckedFilename.Replace(c.ToString(), ""); + // Set up the UI forms to show + ApplyingProfileForm timeoutForm = new ApplyingProfileForm(3, 0, $"Applying Profile '{profile.Name}'", $"Press ESC to timeout"); + ApplyingProfileForm topologyForm = new ApplyingProfileForm(0, 30, $"Applying Profile '{profile.Name}' Topology"); + ApplyingProfileForm pathInfoForm = new ApplyingProfileForm(0, 30, $"Applying Profile '{profile.Name}' Path"); + + topologyForm.ShowDialog(); + // Now lets start by changing the display topology + Task applyTopologyTask = Task.Run(() => + { + Console.WriteLine("ProfileRepository/SaveShortcutIconToCache : Applying Profile Topology " + profile.Name); + ProfileRepository.ApplyTopology(profile); + }); + applyTopologyTask.Wait(); + topologyForm.Close(); + + if (applyTopologyTask.IsCompleted) + { + pathInfoForm.ShowDialog(); + Task applyPathInfoTask = Task.Run(() => { + Console.WriteLine("ProfileRepository/SaveShortcutIconToCache : Applying Profile Path " + profile.Name); + ProfileRepository.ApplyPathInfo(profile); + }); + + applyPathInfoTask.Wait(); + pathInfoForm.Close(); + // And then change the path information + if (applyPathInfoTask.IsCompleted) + return true; + + + return false; + } + else + { + return false; + } + + } - return uncheckedFilename; - }*/ + catch (Exception ex) + { + Console.WriteLine($"ProfileRepository/ApplyTopology exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}"); + return false; + } + } + } } \ No newline at end of file diff --git a/HeliosPlus/ProgressReporter.cs b/HeliosPlus/ProgressReporter.cs new file mode 100644 index 0000000..a4b8320 --- /dev/null +++ b/HeliosPlus/ProgressReporter.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace HeliosPlus +{ + using System; + using System.Threading; + using System.Threading.Tasks; + + /// + /// A class used by Tasks to report progress or completion updates back to the UI. + /// + public sealed class ProgressReporter + { + /// + /// The underlying scheduler for the UI's synchronization context. + /// + private readonly TaskScheduler scheduler; + + /// + /// Initializes a new instance of the class. + /// This should be run on a UI thread. + /// + public ProgressReporter() + { + this.scheduler = TaskScheduler.FromCurrentSynchronizationContext(); + } + + /// + /// Gets the task scheduler which executes tasks on the UI thread. + /// + public TaskScheduler Scheduler + { + get { return this.scheduler; } + } + + /// + /// Reports the progress to the UI thread. This method should be called from the task. + /// Note that the progress update is asynchronous with respect to the reporting Task. + /// For a synchronous progress update, wait on the returned . + /// + /// The action to perform in the context of the UI thread. + /// Note that this action is run asynchronously on the UI thread. + /// The task queued to the UI thread. + public Task ReportProgressAsync(Action action) + { + return Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, this.scheduler); + } + + /// + /// Reports the progress to the UI thread, and waits for the UI thread to process + /// the update before returning. This method should be called from the task. + /// + /// The action to perform in the context of the UI thread. + public void ReportProgress(Action action) + { + this.ReportProgressAsync(action).Wait(); + } + + /// + /// Registers a UI thread handler for when the specified task finishes execution, + /// whether it finishes with success, failiure, or cancellation. + /// + /// The task to monitor for completion. + /// The action to take when the task has completed, in the context of the UI thread. + /// The continuation created to handle completion. This is normally ignored. + public Task RegisterContinuation(Task task, Action action) + { + return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.None, this.scheduler); + } + + /// + /// Registers a UI thread handler for when the specified task finishes execution, + /// whether it finishes with success, failiure, or cancellation. + /// + /// The type of the task result. + /// The task to monitor for completion. + /// The action to take when the task has completed, in the context of the UI thread. + /// The continuation created to handle completion. This is normally ignored. + public Task RegisterContinuation(Task task, Action action) + { + return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.None, this.scheduler); + } + + /// + /// Registers a UI thread handler for when the specified task successfully finishes execution. + /// + /// The task to monitor for successful completion. + /// The action to take when the task has successfully completed, in the context of the UI thread. + /// The continuation created to handle successful completion. This is normally ignored. + public Task RegisterSucceededHandler(Task task, Action action) + { + return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, this.scheduler); + } + + /// + /// Registers a UI thread handler for when the specified task successfully finishes execution + /// and returns a result. + /// + /// The type of the task result. + /// The task to monitor for successful completion. + /// The action to take when the task has successfully completed, in the context of the UI thread. + /// The argument to the action is the return value of the task. + /// The continuation created to handle successful completion. This is normally ignored. + public Task RegisterSucceededHandler(Task task, Action action) + { + return task.ContinueWith(t => action(t.Result), CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, this.Scheduler); + } + + /// + /// Registers a UI thread handler for when the specified task becomes faulted. + /// + /// The task to monitor for faulting. + /// The action to take when the task has faulted, in the context of the UI thread. + /// The continuation created to handle faulting. This is normally ignored. + public Task RegisterFaultedHandler(Task task, Action action) + { + return task.ContinueWith(t => action(t.Exception), CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, this.Scheduler); + } + + /// + /// Registers a UI thread handler for when the specified task becomes faulted. + /// + /// The type of the task result. + /// The task to monitor for faulting. + /// The action to take when the task has faulted, in the context of the UI thread. + /// The continuation created to handle faulting. This is normally ignored. + public Task RegisterFaultedHandler(Task task, Action action) + { + return task.ContinueWith(t => action(t.Exception), CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, this.Scheduler); + } + + /// + /// Registers a UI thread handler for when the specified task is cancelled. + /// + /// The task to monitor for cancellation. + /// The action to take when the task is cancelled, in the context of the UI thread. + /// The continuation created to handle cancellation. This is normally ignored. + public Task RegisterCancelledHandler(Task task, Action action) + { + return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled, this.Scheduler); + } + + /// + /// Registers a UI thread handler for when the specified task is cancelled. + /// + /// The type of the task result. + /// The task to monitor for cancellation. + /// The action to take when the task is cancelled, in the context of the UI thread. + /// The continuation created to handle cancellation. This is normally ignored. + public Task RegisterCancelledHandler(Task task, Action action) + { + return task.ContinueWith(_ => action(), CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled, this.Scheduler); + } + } +} diff --git a/HeliosPlus/ShortcutRepository.cs b/HeliosPlus/ShortcutRepository.cs index 76c2dfd..d1c027d 100644 --- a/HeliosPlus/ShortcutRepository.cs +++ b/HeliosPlus/ShortcutRepository.cs @@ -484,7 +484,8 @@ namespace HeliosPlus IPCService.GetInstance().Status = InstanceStatus.Busy; // Apply the Profile! - if (!ProfileRepository.ApplyProfile(shortcutToUse.ProfileToUse)) + //if (!ProfileRepository.ApplyProfile(shortcutToUse.ProfileToUse)) + if (!Program.ApplyProfile(shortcutToUse.ProfileToUse)) { throw new Exception(Language.Cannot_change_active_profile); } @@ -694,7 +695,8 @@ namespace HeliosPlus // Change back to the original profile if it is different if (!ProfileRepository.IsActiveProfile(rollbackProfile)) { - if (!ProfileRepository.ApplyProfile(rollbackProfile)) + //if (!ProfileRepository.ApplyProfile(rollbackProfile)) + if (!Program.ApplyProfile(rollbackProfile)) { throw new Exception(Language.Cannot_change_active_profile); } diff --git a/HeliosPlus/UIForms/ApplyingChangesForm.Designer.cs b/HeliosPlus/UIForms/ApplyingChangesForm.Designer.cs index 9c20f46..03d11c4 100644 --- a/HeliosPlus/UIForms/ApplyingChangesForm.Designer.cs +++ b/HeliosPlus/UIForms/ApplyingChangesForm.Designer.cs @@ -37,7 +37,7 @@ namespace HeliosPlus.UIForms this.lbl_sub_message = new System.Windows.Forms.Label(); this.lbl_message = new System.Windows.Forms.Label(); this.progressBar = new CircularProgressBar.CircularProgressBar(); - this.t_start = new System.Windows.Forms.Timer(this.components); + this.t_cancellation = new System.Windows.Forms.Timer(this.components); this.t_countdown = new System.Windows.Forms.Timer(this.components); this.progressPanel.SuspendLayout(); this.SuspendLayout(); @@ -106,8 +106,8 @@ namespace HeliosPlus.UIForms // // t_start // - this.t_start.Interval = 1000; - this.t_start.Tick += new System.EventHandler(this.t_start_Tick); + this.t_cancellation.Interval = 1000; + this.t_cancellation.Tick += new System.EventHandler(this.t_cancellation_Tick); // // t_countdown // @@ -150,7 +150,7 @@ namespace HeliosPlus.UIForms private Panel progressPanel; private CircularProgressBar.CircularProgressBar progressBar; private System.Windows.Forms.Label lbl_message; - private System.Windows.Forms.Timer t_start; + private System.Windows.Forms.Timer t_cancellation; private System.Windows.Forms.Timer t_countdown; private Label lbl_sub_message; } diff --git a/HeliosPlus/UIForms/ApplyingChangesForm.cs b/HeliosPlus/UIForms/ApplyingChangesForm.cs index fdb360a..2b0da67 100644 --- a/HeliosPlus/UIForms/ApplyingChangesForm.cs +++ b/HeliosPlus/UIForms/ApplyingChangesForm.cs @@ -79,9 +79,9 @@ namespace HeliosPlus.UIForms return base.ProcessCmdKey(ref msg, keyData); } - if (t_start.Enabled) + if (t_cancellation.Enabled) { - t_start.Stop(); + t_cancellation.Stop(); t_countdown.Stop(); DialogResult = DialogResult.Cancel; Close(); @@ -133,14 +133,14 @@ namespace HeliosPlus.UIForms private void DoTimeout() { - lbl_message.Text = CancellationMessage; - lbl_sub_message.Text = CancellationSubMessage; - progressBar.ProgressColor = Color.DodgerBlue; if (_startCounter > 0) { + lbl_message.Text = CancellationMessage; + lbl_sub_message.Text = CancellationSubMessage; + progressBar.ProgressColor = Color.DodgerBlue; progressBar.Text = (progressBar.Value = progressBar.Maximum = _startCounter).ToString(); - t_start.Start(); + t_cancellation.Start(); } else { @@ -241,11 +241,11 @@ namespace HeliosPlus.UIForms Reposition(); } - private void t_start_Tick(object sender, EventArgs e) + private void t_cancellation_Tick(object sender, EventArgs e) { if (_startCounter < 0) { - t_start.Stop(); + t_cancellation.Stop(); DoJob(); return; diff --git a/HeliosPlus/UIForms/ApplyingProfileForm.Designer.cs b/HeliosPlus/UIForms/ApplyingProfileForm.Designer.cs new file mode 100644 index 0000000..8decd29 --- /dev/null +++ b/HeliosPlus/UIForms/ApplyingProfileForm.Designer.cs @@ -0,0 +1,149 @@ +namespace HeliosPlus.UIForms +{ + partial class ApplyingProfileForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.progressPanel = new System.Windows.Forms.Panel(); + this.lbl_sub_message = new System.Windows.Forms.Label(); + this.progressBar = new CircularProgressBar.CircularProgressBar(); + this.lbl_message = new System.Windows.Forms.Label(); + this.t_countdown = new System.Windows.Forms.Timer(this.components); + this.t_cancellation = new System.Windows.Forms.Timer(this.components); + this.progressPanel.SuspendLayout(); + this.SuspendLayout(); + // + // progressPanel + // + this.progressPanel.Anchor = System.Windows.Forms.AnchorStyles.None; + this.progressPanel.Controls.Add(this.lbl_sub_message); + this.progressPanel.Controls.Add(this.progressBar); + this.progressPanel.Controls.Add(this.lbl_message); + this.progressPanel.Location = new System.Drawing.Point(77, 154); + this.progressPanel.Name = "progressPanel"; + this.progressPanel.Size = new System.Drawing.Size(621, 270); + this.progressPanel.TabIndex = 1; + // + // lbl_sub_message + // + this.lbl_sub_message.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lbl_sub_message.ForeColor = System.Drawing.Color.White; + this.lbl_sub_message.Location = new System.Drawing.Point(159, 87); + this.lbl_sub_message.Name = "lbl_sub_message"; + this.lbl_sub_message.Size = new System.Drawing.Size(300, 16); + this.lbl_sub_message.TabIndex = 2; + this.lbl_sub_message.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // progressBar + // + this.progressBar.AnimationFunction = WinFormAnimation.KnownAnimationFunctions.Liner; + this.progressBar.AnimationSpeed = 1000; + this.progressBar.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(40)))), ((int)(((byte)(40))))); + this.progressBar.Font = new System.Drawing.Font("Microsoft Sans Serif", 39.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.progressBar.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224))))); + this.progressBar.InnerColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224))))); + this.progressBar.InnerMargin = 0; + this.progressBar.InnerWidth = 0; + this.progressBar.Location = new System.Drawing.Point(243, 125); + this.progressBar.MarqueeAnimationSpeed = 2000; + this.progressBar.Name = "progressBar"; + this.progressBar.OuterColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); + this.progressBar.OuterMargin = -8; + this.progressBar.OuterWidth = 6; + this.progressBar.ProgressColor = System.Drawing.Color.DodgerBlue; + this.progressBar.ProgressWidth = 10; + this.progressBar.SecondaryFont = new System.Drawing.Font("Microsoft Sans Serif", 26.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.progressBar.Size = new System.Drawing.Size(135, 135); + this.progressBar.StartAngle = 270; + this.progressBar.SubscriptColor = System.Drawing.Color.FromArgb(((int)(((byte)(166)))), ((int)(((byte)(166)))), ((int)(((byte)(166))))); + this.progressBar.SubscriptMargin = new System.Windows.Forms.Padding(10, -35, 0, 0); + this.progressBar.SubscriptText = ""; + this.progressBar.SuperscriptColor = System.Drawing.Color.Gray; + this.progressBar.SuperscriptMargin = new System.Windows.Forms.Padding(0, 30, 0, 0); + this.progressBar.SuperscriptText = ""; + this.progressBar.TabIndex = 0; + this.progressBar.TextMargin = new System.Windows.Forms.Padding(2, 5, 0, 0); + this.progressBar.Value = 68; + // + // lbl_message + // + this.lbl_message.Font = new System.Drawing.Font("Microsoft Sans Serif", 27.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(178))); + this.lbl_message.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224))))); + this.lbl_message.Location = new System.Drawing.Point(3, 0); + this.lbl_message.Name = "lbl_message"; + this.lbl_message.Size = new System.Drawing.Size(615, 61); + this.lbl_message.TabIndex = 1; + this.lbl_message.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // t_countdown + // + this.t_countdown.Interval = 1000; + this.t_countdown.Tick += new System.EventHandler(this.t_countdown_Tick); + // + // t_cancellation + // + this.t_cancellation.Interval = 1000; + this.t_cancellation.Tick += new System.EventHandler(this.t_cancellation_Tick); + // + // ApplyingProfileForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(40)))), ((int)(((byte)(40))))); + this.ClientSize = new System.Drawing.Size(800, 450); + this.Controls.Add(this.progressPanel); + this.DoubleBuffered = true; + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; + this.KeyPreview = true; + this.Name = "ApplyingProfileForm"; + this.Opacity = 0D; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "HeliosPlus - Please Wait"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ApplyingProfileForm_FormClosing); + this.Load += new System.EventHandler(this.ApplyingProfileForm_Reposition); + this.Shown += new System.EventHandler(this.ApplyingProfileForm_Shown); + this.LocationChanged += new System.EventHandler(this.ApplyingProfileForm_Reposition); + this.Leave += new System.EventHandler(this.ApplyingProfileForm_Reposition); + this.Move += new System.EventHandler(this.ApplyingProfileForm_Reposition); + this.progressPanel.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Panel progressPanel; + private CircularProgressBar.CircularProgressBar progressBar; + private System.Windows.Forms.Label lbl_sub_message; + private System.Windows.Forms.Label lbl_message; + private System.Windows.Forms.Timer t_countdown; + private System.Windows.Forms.Timer t_cancellation; + } +} \ No newline at end of file diff --git a/HeliosPlus/UIForms/ApplyingProfileForm.cs b/HeliosPlus/UIForms/ApplyingProfileForm.cs new file mode 100644 index 0000000..b767a6b --- /dev/null +++ b/HeliosPlus/UIForms/ApplyingProfileForm.cs @@ -0,0 +1,300 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using WinFormAnimation; + +namespace HeliosPlus.UIForms +{ + public partial class ApplyingProfileForm : Form + { + private readonly Bitmap _progressImage; + private readonly List _progressPositions = new List(); + private int _countdownCounter; + private readonly int _displayChangeMaxDelta = -1; + private int _displayChangeDelta; + private int _lastCount; + private bool _isClosing; + private int _cancellationCounter; + + public ApplyingProfileForm() + { + InitializeComponent(); + _progressImage = new Bitmap(progressPanel.Width, progressPanel.Height); + Controls.Remove(progressPanel); + progressPanel.BackColor = BackColor; + progressBar.Invalidated += (sender, args) => Invalidate(); + progressPanel.Invalidated += (sender, args) => Invalidate(); + Reposition(); + } + + public ApplyingProfileForm(int cancellationTimeout = 0, int countdown = 0, string title = null, string message = null, int displayChangeMaxDelta = 5) : this() + { + _cancellationCounter = cancellationTimeout; + _countdownCounter = countdown; + _lastCount = _countdownCounter; + _displayChangeMaxDelta = displayChangeMaxDelta; + if (!string.IsNullOrEmpty(title)) CountdownTitle = title; + if (!string.IsNullOrEmpty(message)) CountdownMessage = message; + } + + public string CancellationTitle { get; set; } = "Starting in ..."; + public string CancellationMessage { get; set; } = "Please press ESC to cancel"; + + public string CountdownTitle { get; set; } = "Please wait..."; + public string CountdownMessage { get; set; } = "It won't be long now!"; + + protected override void OnKeyDown(KeyEventArgs e) + { + e.Handled = true; + } + + protected override void OnPaint(PaintEventArgs e) + { + lock (_progressPositions) + { + progressPanel.DrawToBitmap(_progressImage, new Rectangle(Point.Empty, progressPanel.Size)); + + foreach (var position in _progressPositions) + { + e.Graphics.DrawImage(_progressImage, new Rectangle(position, progressPanel.Size)); + } + } + + base.OnPaint(e); + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + if (keyData != Keys.Escape) + { + return base.ProcessCmdKey(ref msg, keyData); + } + + if (t_cancellation.Enabled) + { + t_cancellation.Stop(); + t_countdown.Stop(); + DialogResult = DialogResult.Cancel; + Close(); + + return true; + } + + return true; + } + + private void HandleDisplayChangeDelta() + { + if (_displayChangeMaxDelta <= -1) return; + _displayChangeDelta = _lastCount - _countdownCounter; + if (_displayChangeDelta > _displayChangeMaxDelta) + { + Debug.Print("_displayChangeDelta > _displayChangeMaxDelta! " + _displayChangeDelta + " > " + _displayChangeMaxDelta); + DialogResult = DialogResult.OK; + Close(); + } + } + + private void DoCountdown() + { + if (_countdownCounter > 0) + { + lbl_message.Text = CountdownTitle; + lbl_sub_message.Text = CountdownMessage; + progressBar.ProgressColor = Color.OrangeRed; + progressBar.Text = (progressBar.Value = progressBar.Maximum = _countdownCounter).ToString(); + t_countdown.Start(); + } + else + { + progressBar.Style = ProgressBarStyle.Marquee; + progressBar.Text = ""; + progressBar.Maximum = 100; + progressBar.Value = 50; + progressBar.Style = ProgressBarStyle.Marquee; + DialogResult = DialogResult.OK; + Close(); + } + + HandleDisplayChangeDelta(); + } + + private void DoCancellationTimeout() + { + if (_cancellationCounter > 0) + { + lbl_message.Text = CancellationTitle; + lbl_sub_message.Text = CancellationMessage; + progressBar.ProgressColor = Color.DodgerBlue; + progressBar.Text = (progressBar.Value = progressBar.Maximum = _cancellationCounter).ToString(); + t_cancellation.Start(); + } + else + { + DoCountdown(); + } + HandleDisplayChangeDelta(); + } + + private void Reposition() + { + lock (_progressPositions) + { + + Screen[] screens = Screen.AllScreens; + Size = screens.Select(screen => screen.Bounds) + .Aggregate(Rectangle.Union) + .Size; + + _progressPositions.Clear(); + _progressPositions.AddRange( + screens.Select( + screen => + new Point(screen.Bounds.X + ((screen.Bounds.Width - progressPanel.Width) / 2), + screen.Bounds.Y + ((screen.Bounds.Height - progressPanel.Height) / 2)) + ) + ); + } + Invalidate(); + } + + private void ApplyingProfileForm_FormClosing(object sender, FormClosingEventArgs e) + { + if (_isClosing) + { + return; + } + + _isClosing = true; + e.Cancel = true; + var dialogResult = DialogResult; + new Animator(new Path((float)Opacity, 0, 200)) + .Play(new SafeInvoker(f => + { + try + { + Opacity = f; + } + catch (Exception ex) + { + Console.WriteLine($"ApplyingProfileForm/ApplyingProfileForm_FormClosing exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}"); + // ignored + } + }, this), new SafeInvoker(() => + { + DialogResult = dialogResult; + Close(); + }, this)); + } + + private void ApplyingProfileForm_Reposition(object sender, EventArgs e) + { + Reposition(); + } + + private void ApplyingProfileForm_Shown(object sender, EventArgs e) + { + new Animator(new Path((float)Opacity, 0.97f, 200)) + .Play(new SafeInvoker(f => + { + try + { + Opacity = f; + } + catch (Exception ex) + { + Console.WriteLine($"ApplyingProfileForm/ApplyingProfileForm_Shown exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}"); + // ignored + } + }, this), new SafeInvoker(DoCancellationTimeout, this)); + } + + private void t_countdown_Tick(object sender, EventArgs e) + { + if (_countdownCounter < 0) + { + t_countdown.Stop(); + DialogResult = DialogResult.OK; + Close(); + + return; + } + + HandleDisplayChangeDelta(); + + progressBar.Value = _countdownCounter; + progressBar.Text = progressBar.Value.ToString(); + _countdownCounter--; + Reposition(); + } + + private void t_cancellation_Tick(object sender, EventArgs e) + { + if (_cancellationCounter < 0) + { + t_cancellation.Stop(); + DoCountdown(); + + return; + } + + progressBar.Value = _cancellationCounter; + progressBar.Text = progressBar.Value.ToString(); + _cancellationCounter--; + Reposition(); + } + + protected override void WndProc(ref Message m) + { + const int WM_SETTINGCHANGE = 0x001A; + const int SPI_SETWORKAREA = 0x02F; + const int WM_DISPLAYCHANGE = 0x007E; + + const int x_bitshift = 0; + const int y_bitshift = 16; + const int xy_mask = 0xFFFF; + + bool displayChange = false; + + switch (m.Msg) + { + case WM_SETTINGCHANGE: + Debug.Print("Message: " + m.ToString()); + Debug.Print("WM_SETTINGCHANGE"); + switch ((int)m.WParam) + { + case SPI_SETWORKAREA: + Debug.Print("SPI_SETWORKAREA"); + displayChange = true; + break; + } + break; + case WM_DISPLAYCHANGE: + int cxScreen = (xy_mask & ((int)m.LParam) >> x_bitshift); + int cyScreen = (xy_mask & ((int)m.LParam) >> y_bitshift); + Debug.Print("Message: " + m.ToString()); + Debug.Print("WM_DISPLAYCHANGE"); + Debug.Print("cxScreen: " + cxScreen + " cyScreen: " + cyScreen); + displayChange = true; + break; + } + if (displayChange) + { + _displayChangeDelta = _lastCount - _countdownCounter; + _lastCount = _countdownCounter; + Debug.Print("Display Change Detected at t " + _lastCount + " difference between changes is " + _displayChangeDelta); + } + + base.WndProc(ref m); + } + + } +} + diff --git a/HeliosPlus/UIForms/ApplyingProfileForm.resx b/HeliosPlus/UIForms/ApplyingProfileForm.resx new file mode 100644 index 0000000..305cd01 --- /dev/null +++ b/HeliosPlus/UIForms/ApplyingProfileForm.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 143, 17 + + + 17, 17 + + \ No newline at end of file diff --git a/HeliosPlus/UIForms/DisplayProfileForm.Designer.cs b/HeliosPlus/UIForms/DisplayProfileForm.Designer.cs index 5ed0d87..f6183ae 100644 --- a/HeliosPlus/UIForms/DisplayProfileForm.Designer.cs +++ b/HeliosPlus/UIForms/DisplayProfileForm.Designer.cs @@ -312,9 +312,8 @@ namespace HeliosPlus.UIForms this.MinimumSize = new System.Drawing.Size(800, 400); this.Name = "DisplayProfileForm"; this.ShowInTaskbar = false; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "HeliosPlus - Setup Display Profiles"; - this.TopMost = true; this.Activated += new System.EventHandler(this.DisplayProfileForm_Activated); this.Load += new System.EventHandler(this.DisplayProfileForm_Load); this.menu_profiles.ResumeLayout(false); diff --git a/HeliosPlus/UIForms/DisplayProfileForm.cs b/HeliosPlus/UIForms/DisplayProfileForm.cs index a9d6c39..2b44f60 100644 --- a/HeliosPlus/UIForms/DisplayProfileForm.cs +++ b/HeliosPlus/UIForms/DisplayProfileForm.cs @@ -46,7 +46,8 @@ namespace HeliosPlus.UIForms } // Apply the Profile - ProfileRepository.ApplyProfile(_selectedProfile); + //ProfileRepository.ApplyProfile(_selectedProfile); + Program.ApplyProfile(_selectedProfile); }